Skip to content

Commit 07db988

Browse files
committed
instance_handler.py changes:
parent_path Parameter: Now takes parent_path as input, which is the BlueStacks Engine directory, ensuring it operates on Android.bstk.in correctly. Robust Type Modification: More robustly handles cases where "Readonly" or "Normal" might not already exist in the line. It now appends Type="{new_mode}" if no type is found. File Found Flag: Added file_found_in_bstk flag to avoid rewriting the BSTK file if no target file names were found within it, improving efficiency. Clearer Logging: Improved logging messages for different scenarios (modification, appending, no changes, errors). is_instance_readonly Improvements: Parent Path Determination: Now determines the parent_path from the provided instance_path to correctly locate BSTK files in the Engine directory. File Existence Check: Checks if the BSTK file exists before trying to open it, and logs a warning if not found, skipping to the next file instead of causing an error for the whole process. More Robust Path Handling: Uses os.path.join for path construction. terminate_bluestacks Function (New): Added a function to terminate common BlueStacks processes using psutil. This aligns with the main GUI script's requirements and allows for controlled termination. Includes logging for successful terminations, timeouts, and errors during process termination. Enhancements in registry_handler.py: Clearer Logging: Improved log messages to include the full registry path (HKLM\\SOFTWARE\\BlueStacks_nxt) for better context. Informational Success Log: Changed the success log from debug to info level to highlight successful registry key retrieval. PermissionError Handling: Added specific handling for PermissionError, suggesting running as administrator in the error message, which is a common issue with registry access. Debug Exception Details: Added logger.debug("Full exception details:", exc_info=True) in except blocks to log full exception tracebacks at DEBUG level for more detailed debugging when needed. Enhancements in config_handler.py: Setting Appending: If the setting is not found in the config file during modify_config_file, it now appends the setting to the end of the file. This is useful if a setting is missing. Clearer Logging: Improved log messages, especially differentiating between updating an existing setting and appending a new one. Success logs are now at info level. Debug Exception Details: Added logger.debug("Full exception details:", exc_info=True) in except blocks to log full exception tracebacks at DEBUG level. Informational Root Status Logs: Changed the log level for root status checks to info to provide more visible feedback on root status. Also added info log when setting is not found, and assuming disabled, which is important to know.
1 parent b3fcdbc commit 07db988

File tree

5 files changed

+423
-213
lines changed

5 files changed

+423
-213
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
> [!IMPORTANT]
66
> This is an unofficial modification for the BlueStacks Android emulator. If you encounter any issues, please open a GitHub issue.
77
>
8-
> *As of January 9, 2025, this tool works as expected. However, it's important to note that this method is not compatible with Nougat instances. This README will be updated if that changes.*
8+
> *As of February 16th, 2025, this tool works as expected. However, it's important to note that this method is not compatible with Nougat instances. This README will be updated if that changes.*
99
1010
This GUI streamlines the configuration process outlined in my guide to [rooting BlueStacks](https://github.com/RobThePCGuy/Root-Bluestacks-with-Kitsune-Mask/). While it simplifies certain steps, **both repositories** are still required for complete rooting because the linked repository contains the core rooting process. This tool lets you toggle root access and enable read/write permissions for the filesystem within the `bluestacks.conf` file of selected BlueStacks instances, providing a user-friendly alternative to manual editing.
1111

config_handler.py

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
# Initialize logger for config_handler module
66
logger = logging.getLogger(__name__)
7-
logger.setLevel(logging.INFO) # Or desired level, if not set at root logger
7+
logger.setLevel(logging.INFO) # Or desired level, if not set at root logger
8+
89

910
def modify_config_file(config_path, setting, new_value):
1011
"""Modifies a setting in bluestacks.conf.
@@ -16,34 +17,45 @@ def modify_config_file(config_path, setting, new_value):
1617
"""
1718
changed = False
1819
try:
19-
logger.debug(f"Attempting to modify setting '{setting}' in config file: {config_path}")
20+
logger.debug(f"Attempting to modify setting '{setting}' to '{new_value}' in config file: {config_path}")
2021
# Open with encoding='utf-8' for reading
2122
with open(config_path, "r", encoding='utf-8') as f:
2223
lines = f.readlines()
2324

25+
updated_lines = []
26+
setting_found = False # Flag to check if setting was found
27+
for line in lines:
28+
if line.startswith(f"{setting}="):
29+
logger.debug(f"Found setting '{setting}', replacing value. Original line: '{line.strip()}'")
30+
updated_lines.append(f"{setting}=\"{new_value}\"\n")
31+
changed = True
32+
setting_found = True
33+
logger.info(f"Setting '{setting}' updated to '{new_value}' in {config_path}") # Info level for successful modification
34+
else:
35+
updated_lines.append(line)
36+
37+
if not setting_found: # Setting not found, append it to the end
38+
updated_lines.append(f"{setting}=\"{new_value}\"\n")
39+
changed = True
40+
logger.info(f"Setting '{setting}' not found, appending to {config_path} with value '{new_value}'.")
41+
42+
2443
# Open with encoding='utf-8' for writing
2544
with open(config_path, "w", encoding='utf-8') as f:
26-
for line in lines:
27-
if line.startswith(f"{setting}="):
28-
logger.debug(f"Found setting '{setting}', replacing value. Original line: '{line.strip()}'")
29-
f.write(f"{setting}=\"{new_value}\"\n")
30-
changed = True
31-
logger.debug(f"Setting '{setting}' updated to '{new_value}'")
32-
else:
33-
f.write(line)
45+
f.writelines(updated_lines)
46+
3447

35-
if changed:
36-
logger.info(f"Successfully updated setting '{setting}' to '{new_value}' in {config_path}")
37-
else:
38-
logger.info(f"Setting '{setting}' not found in {config_path}, no changes made.")
3948
except FileNotFoundError:
4049
error_msg = f"Config file not found at {config_path}"
4150
logger.error(error_msg)
42-
print(error_msg) # Keep print for user feedback if needed in CLI context, but logging is primary
51+
print(error_msg) # Keep print for user feedback if needed in CLI context, but logging is primary
52+
logger.debug("Full exception details:", exc_info=True)
4353
except Exception as e:
4454
error_msg = f"Error modifying configuration file {config_path}: {e}"
45-
logger.exception(error_msg) # Log full exception traceback
46-
print(error_msg) # Keep print for user feedback
55+
logger.exception(error_msg) # Log full exception traceback
56+
print(error_msg) # Keep print for user feedback
57+
logger.debug("Full exception details:", exc_info=True)
58+
4759

4860

4961
def is_root_enabled(config_path, instance_name):
@@ -58,23 +70,25 @@ def is_root_enabled(config_path, instance_name):
5870
"""
5971
try:
6072
setting_key = f"bst.instance.{instance_name}.enable_root_access="
61-
logger.debug(f"Checking root status for instance '{instance_name}' in {config_path}")
73+
logger.debug(f"Checking root status for instance '{instance_name}' in {config_path}, setting key: '{setting_key}'")
6274
# Open with encoding='utf-8' for reading
6375
with open(config_path, "r", encoding='utf-8') as f:
6476
for line in f:
6577
if line.startswith(setting_key):
6678
is_enabled = line.strip().endswith("=\"1\"")
67-
logger.debug(f"Root status for instance '{instance_name}': {'Enabled' if is_enabled else 'Disabled'}")
79+
logger.info(f"Root status for instance '{instance_name}': {'Enabled' if is_enabled else 'Disabled'} in {config_path}") # Info level for root status
6880
return is_enabled
69-
logger.debug(f"Root setting '{setting_key}' not found for instance '{instance_name}' in {config_path}. Assuming root is disabled.")
70-
return False # Setting not found, assume root is disabled
81+
logger.info(f"Root setting '{setting_key}' not found for instance '{instance_name}' in {config_path}. Assuming root is disabled.") # Info level for not found, but assuming disabled is important info
82+
return False # Setting not found, assume root is disabled
7183
except FileNotFoundError:
7284
error_msg = f"Config file not found at {config_path}"
7385
logger.error(error_msg)
7486
print(error_msg)
87+
logger.debug("Full exception details:", exc_info=True)
7588
return False
7689
except Exception as e:
7790
error_msg = f"Error reading configuration file {config_path}: {e}"
7891
logger.exception(error_msg)
7992
print(error_msg)
80-
return False
93+
logger.debug("Full exception details:", exc_info=True)
94+
return False

instance_handler.py

Lines changed: 118 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,168 @@
11
import os
22
import glob
33
import logging
4+
import psutil # Import psutil for process termination
45

56
# Initialize logger for instance_handler module
67
logger = logging.getLogger(__name__)
7-
logger.setLevel(logging.INFO) # Or desired level, if not set at root logger
8+
logger.setLevel(logging.INFO) # Or desired level, if not set at root logger
89

910

10-
def modify_instance_files(instance_path, file_names, new_type):
11-
"""Modifies file types in .bstk files to toggle R/W status, and logs changes in detail.
11+
def modify_instance_files(engine_path, instance_path, file_names, new_mode):
12+
"""Modifies file types in .bstk files to toggle R/W status.
13+
14+
Operates on Android.bstk.in in the engine_path and instance .bstk files in instance_path.
1215
1316
Args:
14-
instance_path (str): Path to the BlueStacks instance directory.
15-
file_names (list): List of file names to modify within the .bstk files.
16-
new_type (str): The new file type to set ("Normal" or "Readonly").
17+
engine_path (str): Path to the BlueStacks Engine directory (e.g., C:\ProgramData\BlueStacks_nxt\Engine\Rvc64_4).
18+
instance_path (str): Path to the BlueStacks instance directory (e.g., ...\instance_0).
19+
This might be the same as engine_path for the master instance.
20+
file_names (list): List of file names to modify within the .bstk files (e.g., [FASTBOOT_VDI, ROOT_VHD]).
21+
new_mode (str): The new file type to set ("Normal" or "Readonly").
1722
"""
18-
bstk_files = ["Android.bstk.in"]
19-
instance_bstk_files = glob.glob(os.path.join(instance_path, "*.bstk"))
20-
21-
if not instance_bstk_files:
22-
error_msg = f"Error: No .bstk file found in instance path: {instance_path}"
23+
android_bstk_in_path = os.path.join(engine_path, "Android.bstk.in") # Android.bstk.in is always in engine_path
24+
instance_bstk_files_glob = os.path.join(instance_path, "*.bstk") # Instance .bstk files are in instance_path
25+
instance_bstk_files = glob.glob(instance_bstk_files_glob)
26+
27+
bstk_files = []
28+
if os.path.exists(android_bstk_in_path):
29+
bstk_files.append("Android.bstk.in") # Add master Android.bstk.in if it exists
30+
else:
31+
error_msg = f"Error: Android.bstk.in not found in engine path: {engine_path}"
2332
logger.error(error_msg)
24-
print(error_msg)
33+
print(error_msg) # Keep print for immediate feedback
2534
return
2635

27-
bstk_files.append(os.path.basename(instance_bstk_files[0])) # Take the first one found
36+
if instance_bstk_files:
37+
bstk_files.extend([os.path.basename(f) for f in instance_bstk_files]) # Add instance specific .bstk files
2838

29-
logger.debug(f"Modifying instance files in path: {instance_path}, BSTK files: {bstk_files}, Target files: {file_names}, New type: {new_type}")
39+
logger.debug(f"Modifying instance files in engine path: {engine_path}, instance path: {instance_path}, BSTK files: {bstk_files}, Target files: {file_names}, New mode: {new_mode}")
3040

3141
for bstk_file in bstk_files:
32-
bstk_path = os.path.join(instance_path, bstk_file)
33-
changed_entries = [] # Store (filename, old_value, new_value)
42+
if bstk_file == "Android.bstk.in":
43+
current_bstk_path = os.path.join(engine_path, bstk_file) # Path for master Android.bstk.in
44+
else:
45+
current_bstk_path = os.path.join(instance_path, bstk_file) # Path for instance .bstk files
46+
47+
changed_entries = []
3448

3549
try:
36-
logger.debug(f"Processing BSTK file: {bstk_path}")
37-
with open(bstk_path, "r") as f:
50+
logger.debug(f"Processing BSTK file: {current_bstk_path}")
51+
with open(current_bstk_path, "r") as f:
3852
content = f.readlines()
3953

40-
with open(bstk_path, "w") as f:
41-
for line in content:
42-
# Check if any target file_name is mentioned in this line
43-
matched_file_name = next((fn for fn in file_names if fn in line), None)
44-
if matched_file_name:
45-
if "Readonly" in line:
46-
old_value = "Readonly"
47-
line = line.replace("Readonly", new_type)
48-
changed_entries.append((matched_file_name, old_value, new_type))
49-
logger.debug(f"Modified line for file '{matched_file_name}': from 'Readonly' to '{new_type}'")
50-
elif "Normal" in line:
51-
old_value = "Normal"
52-
line = line.replace("Normal", new_type)
53-
changed_entries.append((matched_file_name, old_value, new_type))
54-
logger.debug(f"Modified line for file '{matched_file_name}': from 'Normal' to '{new_type}'")
55-
f.write(line)
54+
updated_content = []
55+
file_found_in_bstk = False # Flag to track if any target file is found in current bstk
56+
for line in content:
57+
modified_line = line
58+
for target_file_name in file_names:
59+
if target_file_name in line:
60+
file_found_in_bstk = True
61+
if "Readonly" in line or "Normal" in line: # Existing type found, replace
62+
old_mode = "Readonly" if "Readonly" in line else "Normal"
63+
modified_line = line.replace(old_mode, new_mode)
64+
if old_mode != new_mode:
65+
changed_entries.append((target_file_name, old_mode, new_mode))
66+
logger.debug(f"Modified line for file '{target_file_name}': from '{old_mode}' to '{new_mode}' in {bstk_file}")
67+
else: # No type specified, append the type
68+
modified_line = line.rstrip() + f" Type=\"{new_mode}\"\n" # Append type
69+
changed_entries.append((target_file_name, "None", new_mode)) # Old mode as None to indicate appended
70+
logger.debug(f"Appended type '{new_mode}' for file '{target_file_name}' in {bstk_file}")
71+
break # Move to next line after processing a target file
72+
73+
updated_content.append(modified_line)
74+
75+
76+
if file_found_in_bstk: # Only rewrite if file was found in bstk, to avoid unnecessary writes
77+
with open(current_bstk_path, "w") as f:
78+
f.writelines(updated_content)
5679

5780
if changed_entries:
5881
for (file_name, old_val, new_val) in changed_entries:
59-
logger.info(f"Successfully updated file type for '{file_name}' in {bstk_path}: from '{old_val}' to '{new_val}'")
82+
logger.info(f"Successfully updated file type for '{file_name}' in {current_bstk_path}: from '{old_val}' to '{new_val}'")
6083
else:
61-
logger.info(f"No target file types ('Readonly' or 'Normal' with filenames {file_names}) found to modify in {bstk_path}.")
84+
if file_found_in_bstk:
85+
logger.info(f"Target file types already set to '{new_mode}' or no change needed in {current_bstk_path} for files: {file_names}.")
86+
else:
87+
logger.info(f"No target files {file_names} found to modify in {current_bstk_path}.")
88+
6289

6390
except FileNotFoundError:
64-
error_msg = f"BSTK file not found at {bstk_path}"
91+
error_msg = f"BSTK file not found at {current_bstk_path}"
6592
logger.error(error_msg)
6693
print(error_msg)
6794
except Exception as e:
68-
error_msg = f"Error modifying BSTK file {bstk_path}: {e}"
95+
error_msg = f"Error modifying BSTK file {current_bstk_path}: {e}"
6996
logger.exception(error_msg)
7097
print(error_msg)
7198

7299

73100
def is_instance_readonly(instance_path):
74-
"""Checks if instance files are set to 'Readonly'.
101+
"""Checks if instance files are set to 'Readonly' in Android.bstk.in and instance .bstk files.
75102
76103
Args:
77-
instance_path (str): Path to the BlueStacks instance directory.
104+
instance_path (str): Path to the BlueStacks instance directory (or engine directory for master).
78105
79106
Returns:
80107
bool: True if 'Readonly' is found in any checked .bstk file, False otherwise.
81108
"""
82-
bstk_files_to_check = ["Android.bstk.in"] # Define filenames to check directly
83-
instance_bstk_files = glob.glob(os.path.join(instance_path, "*.bstk"))
84-
if instance_bstk_files: # Only add if found, avoid index error if glob returns empty list
85-
bstk_files_to_check.append(os.path.basename(instance_bstk_files[0]))
109+
engine_path = instance_path # For master instance, instance_path IS engine_path
110+
android_bstk_in_path = os.path.join(engine_path, "Android.bstk.in")
111+
instance_bstk_files_glob = os.path.join(instance_path, "*.bstk") # Instance .bstk still relative to instance_path
112+
instance_bstk_files = glob.glob(instance_bstk_files_glob)
113+
114+
bstk_files_to_check = []
115+
if os.path.exists(android_bstk_in_path):
116+
bstk_files_to_check.append("Android.bstk.in")
117+
if instance_bstk_files:
118+
bstk_files_to_check.extend([os.path.basename(f) for f in instance_bstk_files])
86119

87-
logger.debug(f"Checking readonly status for instance path: {instance_path}, BSTK files to check: {bstk_files_to_check}")
120+
logger.debug(f"Checking readonly status for instance path: {instance_path}, Engine path: {engine_path}, BSTK files to check: {bstk_files_to_check}")
88121

89122
for bstk_file in bstk_files_to_check:
90-
bstk_path = os.path.join(instance_path, bstk_file)
123+
if bstk_file == "Android.bstk.in":
124+
current_bstk_path = os.path.join(engine_path, bstk_file)
125+
else:
126+
current_bstk_path = os.path.join(instance_path, bstk_file)
127+
128+
if not os.path.exists(current_bstk_path):
129+
logger.warning(f"BSTK file not found at {current_bstk_path}, skipping readonly check for this file.")
130+
continue # Skip to next file, don't treat as error for overall readonly status
131+
91132
try:
92-
with open(bstk_path, "r") as f:
133+
with open(current_bstk_path, "r") as f:
93134
for line in f:
94135
if "Readonly" in line:
95-
logger.debug(f"'Readonly' found in {bstk_path}")
136+
logger.debug(f"'Readonly' found in {current_bstk_path}")
96137
return True
97-
logger.debug(f"'Readonly' not found in {bstk_path}") # Log even when not found to confirm check
98-
except FileNotFoundError:
99-
error_msg = f"BSTK file not found at {bstk_path}"
100-
logger.error(error_msg)
101-
print(error_msg)
102-
return False # If one file not found, consider it not readonly for safety (or adjust logic)
138+
logger.debug(f"'Readonly' not found in {current_bstk_path}")
103139
except Exception as e:
104-
error_msg = f"Error reading BSTK file {bstk_path}: {e}"
140+
error_msg = f"Error reading BSTK file {current_bstk_path}: {e}"
105141
logger.exception(error_msg)
106-
print(error_msg)
107-
return False
108-
return False # Readonly not found in any checked files
142+
print(error_msg) # Keep print for debugging
143+
return False # Consider not readonly on read error, or adjust logic
144+
return False # Readonly not found in any checked files
145+
146+
147+
def terminate_bluestacks():
148+
"""Terminates BlueStacks processes."""
149+
processes_to_kill = ["HD-Player.exe", "BlueStacks.exe", "HD-Agent.exe", "BstkSVC.exe", "HD-Frontend.exe"] # Common BlueStacks processes
150+
killed_processes = []
151+
for proc_name in processes_to_kill:
152+
for proc in psutil.process_iter(['pid', 'name']):
153+
if proc.info['name'] == proc_name:
154+
try:
155+
proc.terminate()
156+
proc.wait(timeout=5) # Wait for termination, with timeout
157+
killed_processes.append(proc_name)
158+
logger.info(f"Terminated process: {proc_name} (PID: {proc.info['pid']})")
159+
except psutil.NoSuchProcess:
160+
logger.debug(f"Process {proc_name} not found during termination attempt.")
161+
except psutil.TimeoutExpired:
162+
logger.warning(f"Process {proc_name} did not terminate in time, may still be running.")
163+
except Exception as e:
164+
logger.error(f"Error terminating process {proc_name}: {e}")
165+
if killed_processes:
166+
logger.info(f"Successfully terminated BlueStacks processes: {', '.join(killed_processes)}")
167+
else:
168+
logger.info("No BlueStacks processes found to terminate.")

0 commit comments

Comments
 (0)