Skip to content

Commit a092f2f

Browse files
committed
v2.1
1 parent 30644a5 commit a092f2f

8 files changed

Lines changed: 2089 additions & 0 deletions

File tree

README.md

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# BlueStacks Root GUI
2+
3+
[![GitHub Repo Stars](https://img.shields.io/github/stars/RobThePCGuy/BlueStacks-Root-GUI?style=social)](https://github.com/RobThePCGuy/BlueStacks-Root-GUI) [![YouTube Video Views](https://img.shields.io/youtube/views/zpihBs3FtEc?style=social)](https://youtu.be/zpihBs3FtEc) [![Last Updated](https://img.shields.io/github/last-commit/RobThePCGuy/BlueStacks-Root-GUI)](https://github.com/RobThePCGuy/BlueStacks-Root-GUI/commits/main)
4+
5+
---
6+
7+
![GUI Screenshot](https://github.com/user-attachments/assets/10f965eb-e1cc-4d61-9b6f-0cbb484a4ef0)
8+
9+
BlueStacks Root GUI is a utility designed to easily toggle root access settings and enable read/write (R/W) permissions for your BlueStacks 5 instances (specifically targeting BlueStacks_nxt structure). It aims to simplify the process described in the original guide: **[Root BlueStacks with Kitsune Mask](https://github.com/RobThePCGuy/Root-Bluestacks-with-Kitsune-Mask/)** by providing a graphical interface.
10+
11+
---
12+
13+
## Table of Contents
14+
15+
- [Features](#features)
16+
- [Prerequisites](#prerequisites)
17+
- [Installation & Download](#installation--download)
18+
- [Usage Guide](#usage-guide)
19+
- [Troubleshooting](#troubleshooting)
20+
- [Development](#development)
21+
- [Changelog](#changelog)
22+
- [Contributing](#contributing)
23+
- [License](#license)
24+
25+
---
26+
27+
## Features
28+
29+
- **Auto-Detection:** Discovers BlueStacks installation paths via the Windows Registry (`SOFTWARE\BlueStacks_nxt`).
30+
- **Instance Listing:** Reads `bluestacks.conf` to find and list configured instances.
31+
- **Root Toggle:** Modifies `bst.instance.<name>.enable_root_access` and `bst.feature.rooting` in `bluestacks.conf`.
32+
- **Read/Write Toggle:** Modifies the `Type` attribute (`Normal` vs `Readonly`) for key disk files (`fastboot.vdi`, `Root.vhd`) within instance-specific `.bstk` files.
33+
- **Process Handling:** Detects running BlueStacks processes and attempts graceful termination before applying changes.
34+
- **Status Display:** Shows the current Root and R/W status for each detected instance.
35+
- **Responsive UI:** Uses background threads (`QThread`) for potentially long operations (file I/O, process termination) to keep the GUI responsive.
36+
- **Basic Internationalization:** Includes English and Japanese translations.
37+
38+
---
39+
40+
## Prerequisites
41+
42+
- **Operating System:** Windows 10 or later (due to registry keys and file paths used).
43+
- **BlueStacks Version:** BlueStacks 5 (specifically versions using the `BlueStacks_nxt` registry key and configuration structure). *Compatibility with other versions is not guaranteed.*
44+
- **Python (for development):** Python 3.7+
45+
- **Administrator Rights:** **Required** to read the HKLM registry and terminate BlueStacks processes effectively. Run the application as an administrator.
46+
- **Dependencies:** Listed in `requirements.txt`. Key dependencies include `PyQt5`, `pywin32`, `psutil`.
47+
48+
---
49+
50+
## Installation & Download
51+
52+
### For End Users (Executable Download)
53+
54+
1. **Download the Latest Executable:** Go to the **[Releases](https://github.com/RobThePCGuy/BlueStacks-Root-GUI/releases)** page on GitHub and download the latest `.exe` file.
55+
2. **Run as Administrator:** Right-click the downloaded `.exe` and select "Run as administrator". This is necessary for registry access and process termination.
56+
3. **Important Pre-Run Steps:**
57+
* **Clean BlueStacks Install Recommended:** If you encounter issues, fully uninstall *all* previous BlueStacks versions using the official **[BlueStacks Cleaner tool](https://support.bluestacks.com/hc/en-us/articles/360057724751-How-to-uninstall-BlueStacks-5-BlueStacks-X-and-BlueStacks-Services-completely-from-your-PC)**.
58+
* **Install Latest BlueStacks 5:** Download and install the latest version from the official **[BlueStacks website](https://www.bluestacks.com/)**.
59+
60+
### For Developers (Building from Source)
61+
62+
1. **Clone the Repository:**
63+
```bash
64+
git clone https://github.com/RobThePCGuy/BlueStacks-Root-GUI.git
65+
cd BlueStacks-Root-GUI
66+
```
67+
2. **Create a Virtual Environment (Recommended):**
68+
```bash
69+
python -m venv venv
70+
.\venv\Scripts\activate
71+
```
72+
3. **Install Dependencies:**
73+
```bash
74+
pip install -r requirements.txt
75+
```
76+
4. **Run the Application:**
77+
```bash
78+
python main.py
79+
```
80+
*(Remember to run your terminal/IDE as administrator if running directly)*
81+
5. **Build the Executable (Optional):**
82+
```bash
83+
pip install pyinstaller
84+
pyinstaller --onefile --windowed --icon=favicon.ico --name BlueStacksRootGUI main.py
85+
```
86+
The executable will be in the `dist/` folder.
87+
88+
---
89+
90+
## Usage Guide
91+
92+
1. **Launch as Administrator:** Start the GUI (`.exe` or `python main.py`) with administrator privileges.
93+
2. **Instance Detection:** The GUI will attempt to find your BlueStacks installation and list the instances found in `bluestacks.conf`. Statuses (Root, R/W) will be displayed.
94+
3. **Select Instances:** Check the box(es) next to the instance(s) you want to modify.
95+
4. **Toggle Root:**
96+
* Click **"Toggle Root"**. This enables the necessary settings in `bluestacks.conf`.
97+
* **Turn this ON only temporarily** while you are installing Kitsune Mask.
98+
5. **Toggle R/W:**
99+
* Click **"Toggle R/W"**. This sets the instance's disk files (`Root.vhd`, `fastboot.vdi`) to `Normal` (Read/Write) mode.
100+
* **This needs to be left ON** for the system modifications (like Kitsune Mask) to persist after the instance restarts.
101+
6. **Install Kitsune Mask:**
102+
* Ensure **Root is ON** and **R/W is ON** in the GUI for the target instance.
103+
* Download the latest **[Kitsune Mask APK](https://github.com/1q23lyc45/KitsuneMagisk/releases)**.
104+
* Launch the modified instance using the BlueStacks Multi-Instance Manager.
105+
* Install the downloaded Kitsune Mask APK onto the instance (drag-and-drop usually works).
106+
* Open the Kitsune Mask app inside the instance.
107+
* Tap **Install**.
108+
* Tap **Next**.
109+
* Select the option **"Direct Install to /system"**.
110+
* *Troubleshooting:* If the "Direct Install" option is missing, fully close and reopen the Kitsune Mask app *inside* BlueStacks. It should then appear.
111+
* Let the installation complete and reboot when prompted (the instance will restart).
112+
7. **Final GUI Step:**
113+
* **Crucially:** Once Kitsune Mask is successfully installed to `/system`, return to the BlueStacks Root GUI.
114+
* Select the instance again.
115+
* Click **"Toggle Root"** to turn the configuration setting **OFF**.
116+
* **Leave "Toggle R/W" ON.**
117+
8. **Verify:** Launch the instance. Open Kitsune Mask; it should show as installed and active. Root applications should now work.
118+
9. **Close:** Close the BlueStacks Root GUI.
119+
120+
---
121+
122+
## Troubleshooting
123+
124+
- **"Path Not Found" / No Instances Listed:**
125+
* Ensure you ran the GUI as **administrator**.
126+
* Verify BlueStacks 5 is installed correctly and the registry keys (`HKLM\SOFTWARE\BlueStacks_nxt\UserDefinedDir` and `DataDir`) exist.
127+
* A clean reinstall of BlueStacks using the official cleaner tool might be necessary.
128+
- **Permission Errors during Toggle:**
129+
* You *must* run the GUI as administrator.
130+
- **R/W Toggle Doesn't Stick:**
131+
* Ensure BlueStacks processes (`HD-Player.exe`, `HD-Agent.exe`, etc.) were fully terminated before toggling. The GUI attempts this, but manual termination via Task Manager might be needed if issues persist.
132+
* Ensure you are leaving the **R/W** setting **ON** in the GUI after installing Kitsune Mask.
133+
- **"Direct Install to /system" Missing in Kitsune Mask:**
134+
* Make sure **Root** and **R/W** were both **ON** in the GUI *before* launching the instance and attempting installation.
135+
* Try closing and reopening the Kitsune Mask app within the BlueStacks instance.
136+
- **Errors during Toggle Operations:** Check the status bar in the GUI and the application logs (if run from source/console) for specific error messages.
137+
138+
---
139+
140+
## Development
141+
142+
Follow the steps in [Installation & Download > For Developers](#for-developers-building-from-source).
143+
144+
Key modules:
145+
- `main.py`: PyQt5 GUI, application logic, threading.
146+
- `config_handler.py`: Reads/writes `bluestacks.conf`.
147+
- `instance_handler.py`: Modifies `.bstk` files, handles processes.
148+
- `registry_handler.py`: Reads BlueStacks paths from Windows Registry.
149+
- `constants.py`: Shared constant values (keys, filenames, modes, etc.).
150+
151+
---
152+
153+
## Changelog
154+
155+
*(See commit history for details)*
156+
157+
---
158+
159+
## Contributing
160+
161+
Contributions are welcome! Please follow these guidelines:
162+
163+
- Maintain code style and structure.
164+
- Use the `logging` module appropriately.
165+
- Add/update docstrings for new/modified code.
166+
- Ensure UI remains responsive (use background threads for blocking tasks).
167+
- Update `constants.py` if adding new configurable values.
168+
- Submit pull requests with clear descriptions of changes.
169+
- Open an issue to discuss significant changes beforehand.
170+
171+
---
172+
173+
## License
174+
175+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file (if included, standard MIT otherwise) for details.

config_handler.py

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# config_handler.py
2+
import os
3+
import logging
4+
import re
5+
from typing import Dict, Optional
6+
7+
8+
import constants
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
def modify_config_file(config_path: str, setting: str, new_value: str) -> bool:
14+
"""
15+
Modifies a specific setting in the bluestacks.conf file.
16+
17+
If the setting exists, its value is updated. If it doesn't exist,
18+
it's appended to the end of the file. Handles values enclosed
19+
in double quotes. Ensures the file is only written if changes are made.
20+
21+
Args:
22+
config_path: Absolute path to the bluestacks.conf file.
23+
setting: The configuration key to modify (e.g., "bst.instance.Nougat64.enable_root_access").
24+
new_value: The desired string value for the setting (e.g., "1", "0").
25+
26+
Returns:
27+
True if the file was modified, False otherwise.
28+
29+
Raises:
30+
FileNotFoundError: If config_path does not point to an existing file.
31+
IOError: If there are problems reading or writing the file (caught as Exception).
32+
Exception: For other unexpected file I/O errors.
33+
"""
34+
if not os.path.isfile(config_path):
35+
logger.error(f"Config file not found: {config_path}")
36+
raise FileNotFoundError(f"Config file not found: {config_path}")
37+
38+
logger.debug(
39+
f"Attempting to modify setting '{setting}' to '{new_value}' in {config_path}"
40+
)
41+
42+
new_line_content = f'{setting}="{new_value}"'
43+
44+
lines = []
45+
try:
46+
47+
with open(config_path, "r", encoding="utf-8") as file:
48+
lines = file.readlines()
49+
except Exception as e:
50+
logger.exception(f"Error reading configuration file {config_path}")
51+
raise IOError(f"Error reading configuration file {config_path}: {e}") from e
52+
53+
updated_lines = []
54+
setting_found_and_updated = False
55+
changed = False
56+
57+
setting_pattern = re.compile(r"^\s*" + re.escape(setting) + r"\s*=")
58+
59+
for line in lines:
60+
stripped_line = line.strip()
61+
62+
if setting_pattern.match(stripped_line):
63+
64+
if stripped_line != new_line_content:
65+
logger.info(
66+
f"Updating setting '{setting}'. Old line: '{stripped_line}', New line: '{new_line_content}'"
67+
)
68+
updated_lines.append(new_line_content + "\n")
69+
changed = True
70+
else:
71+
logger.debug(
72+
f"Setting '{setting}' already has the desired value '{new_value}'. No change needed."
73+
)
74+
updated_lines.append(line)
75+
setting_found_and_updated = True
76+
else:
77+
updated_lines.append(line)
78+
79+
if not setting_found_and_updated:
80+
logger.info(
81+
f"Setting '{setting}' not found. Appending with value '{new_value}'."
82+
)
83+
84+
if updated_lines and not updated_lines[-1].endswith("\n"):
85+
updated_lines[-1] += "\n"
86+
updated_lines.append(new_line_content + "\n")
87+
changed = True
88+
89+
if changed:
90+
try:
91+
with open(config_path, "w", encoding="utf-8") as file:
92+
file.writelines(updated_lines)
93+
logger.debug(f"Successfully wrote changes to {config_path}")
94+
except Exception as e:
95+
logger.exception(f"Error writing updated configuration file {config_path}")
96+
raise IOError(
97+
f"Error writing updated configuration file {config_path}: {e}"
98+
) from e
99+
else:
100+
logger.debug(f"No changes were made to {config_path}.")
101+
102+
return changed
103+
104+
105+
def get_all_instance_root_statuses(config_path: str) -> Dict[str, bool]:
106+
"""
107+
Reads the config file and returns a dictionary of instance names
108+
and their root status (True if enabled, False otherwise).
109+
110+
Args:
111+
config_path: Path to the bluestacks.conf file.
112+
113+
Returns:
114+
A dictionary mapping instance name (str) to root status (bool).
115+
Returns an empty dictionary if the file is not found or an error occurs during reading.
116+
"""
117+
statuses: Dict[str, bool] = {}
118+
if not os.path.isfile(config_path):
119+
logger.warning(
120+
f"Config file not found for reading root statuses: {config_path}"
121+
)
122+
return statuses
123+
124+
instance_pattern = re.compile(
125+
r"^"
126+
+ re.escape(constants.INSTANCE_PREFIX)
127+
+ r"([^.]+)"
128+
+ re.escape(constants.ENABLE_ROOT_KEY)
129+
+ r'\s*=\s*"([^"]*)"',
130+
re.IGNORECASE,
131+
)
132+
133+
try:
134+
with open(config_path, "r", encoding="utf-8") as file:
135+
for line in file:
136+
match = instance_pattern.match(line.strip())
137+
if match:
138+
instance_name = match.group(1)
139+
value = match.group(2)
140+
is_enabled = value == "1"
141+
statuses[instance_name] = is_enabled
142+
logger.debug(
143+
f"Found root status for instance '{instance_name}': {'Enabled' if is_enabled else 'Disabled'}"
144+
)
145+
except Exception:
146+
logger.exception(f"Error reading config file {config_path} for root statuses.")
147+
return {}
148+
149+
if not statuses:
150+
logger.info(f"No instance root status settings found in {config_path}.")
151+
152+
return statuses
153+
154+
155+
def is_root_enabled(config_path: str, instance_name: str) -> Optional[bool]:
156+
"""
157+
Checks if root access is enabled for a *single* given instance.
158+
159+
Less efficient than get_all_instance_root_statuses if checking multiple instances.
160+
161+
Args:
162+
config_path: Path to the configuration file.
163+
instance_name: The name of the instance.
164+
165+
Returns:
166+
True if root is enabled ('="1"'), False if disabled ('="0"' or setting not found),
167+
None if the file cannot be read or another error occurs.
168+
"""
169+
if not os.path.isfile(config_path):
170+
logger.warning(f"Config file not found for checking root status: {config_path}")
171+
return None
172+
173+
setting_key = (
174+
f"{constants.INSTANCE_PREFIX}{instance_name}{constants.ENABLE_ROOT_KEY}"
175+
)
176+
177+
enabled_pattern = re.compile(
178+
r"^" + re.escape(setting_key) + r'\s*=\s*"1"\s*$', re.IGNORECASE
179+
)
180+
181+
setting_exists_pattern = re.compile(
182+
r"^" + re.escape(setting_key) + r'\s*=\s*"[^"]*"\s*$', re.IGNORECASE
183+
)
184+
185+
setting_found = False
186+
try:
187+
with open(config_path, "r", encoding="utf-8") as file:
188+
for line in file:
189+
stripped_line = line.strip()
190+
if enabled_pattern.match(stripped_line):
191+
logger.debug(
192+
f"Root access for instance '{instance_name}' is explicitly enabled ('= \"1\"')."
193+
)
194+
return True
195+
if setting_exists_pattern.match(stripped_line):
196+
197+
setting_found = True
198+
199+
logger.debug(
200+
f"Found root access setting for '{instance_name}', but value is not '1'."
201+
)
202+
break
203+
204+
if setting_found:
205+
logger.debug(
206+
f"Root access for instance '{instance_name}' is disabled (setting found but not '= \"1\"')."
207+
)
208+
return False
209+
else:
210+
211+
logger.debug(
212+
f"Root access setting key '{setting_key}' not found in {config_path}."
213+
)
214+
return False
215+
216+
except Exception:
217+
logger.exception(
218+
f"Error reading config file {config_path} while checking root for {instance_name}."
219+
)
220+
return None

0 commit comments

Comments
 (0)