Skip to content

Add "Sideload Magisk Module" button (push .zip to a running instance)#36

Merged
RobThePCGuy merged 5 commits into
masterfrom
feat/sideload-module
Jul 9, 2026
Merged

Add "Sideload Magisk Module" button (push .zip to a running instance)#36
RobThePCGuy merged 5 commits into
masterfrom
feat/sideload-module

Conversation

@RobThePCGuy

Copy link
Copy Markdown
Owner

Problem

BlueStacks' in-app file picker hands Magisk/Kitsune a Windows-style URI that its module installer rejects with "Invalid Uri", so a module .zip on the Windows side can't be flashed from the picker.

Fix

A one-click "Sideload Magisk Module (.zip)" button that pushes the chosen module into a running instance's /sdcard/Download/ over BlueStacks' bundled ADB. The user then flashes it from Magisk/Kitsune's own storage picker (which reads guest storage fine): Modules → Install from storage → Download.

  • adb_handler.py (new): finds HD-Adb.exe in the install dir, reads the target instance's adb_port from bluestacks.conf, connects, and pushes to /sdcard/Download/. Falls back to the sole attached device if the port isn't recorded yet. Surfaces clear errors ("start the instance…", "multiple instances running…", push failures).
  • main.py: the button requires exactly one selected (running) instance, opens a file dialog, runs on the existing QThread worker like every other op, and shows the flash instructions in a dialog on success. Disabled while busy.

This is the only action in the app that needs the instance running (its ADB port must be open); everything else operates on shut-down disks.

Testing

14 offline unit tests for adb_handler (exe discovery, port parsing incl. no-substring-false-match, adb devices state parsing, push happy-path, missing file, no device reachable, push failure, and port-unknown → sole-device fallback) — all pass, using an injected runner so no real ADB/BlueStacks is needed. GUI builds headlessly; button is wired into the busy-state handling. py_compile clean; no new lint. The live push against a running instance is left for on-device verification.

BlueStacks' in-app file picker hands Magisk/Kitsune a Windows-style URI its
module installer rejects with "Invalid Uri", so modules can't be flashed from
the picker. This adds a one-click path around it:

- adb_handler.py: locate BlueStacks' bundled HD-Adb.exe, read the target
  instance's adb_port from bluestacks.conf, connect, and push the .zip to
  /sdcard/Download/ (falls back to the sole attached device if the port is
  unknown). Clear errors when nothing is reachable.
- main.py: "Sideload Magisk Module (.zip)" button. Requires exactly one
  (running) instance selected; opens a file dialog; runs on the worker thread
  like every other op; shows the flash instructions in a dialog on success.

The user then flashes it from Magisk's own storage picker, which reads guest
storage fine. This is the only action that needs the instance running.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new "Sideload Magisk Module" feature, allowing users to push module .zip files directly into a running BlueStacks instance's Download folder via ADB to bypass "Invalid Uri" errors. This includes the addition of adb_handler.py for ADB operations and UI updates in main.py to support the sideload action. The code review feedback suggests two key improvements in adb_handler.py: catching Exception instead of OSError when reading bluestacks.conf to prevent crashes from UnicodeDecodeError, and dynamically parsing the adb devices output instead of using a hardcoded slice index to handle potential daemon startup messages robustly.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread adb_handler.py
m = key.match(line.strip())
if m:
return int(m.group(1))
except OSError:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When reading bluestacks.conf, open(..., encoding="utf-8") or iterating over the file can raise a UnicodeDecodeError if the file contains invalid UTF-8 bytes (e.g., due to corruption or a different encoding used by certain BlueStacks versions). Since UnicodeDecodeError inherits from ValueError (not OSError), it will bypass the except OSError: block and cause the application to crash.

We should catch Exception to ensure the application handles any read or decoding failures gracefully.

Suggested change
except OSError:
except Exception:

Comment thread adb_handler.py
Comment on lines +77 to +80
for line in stdout.splitlines()[1:]: # skip "List of devices attached"
parts = line.split()
if len(parts) >= 2 and parts[1] == "device":
serials.append(parts[0])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Relying on a hardcoded slice index [1:] to skip the header line is fragile. If ADB outputs any daemon startup messages, warnings, or other diagnostic lines before "List of devices attached", the slice index will be incorrect, potentially skipping actual devices or attempting to parse daemon messages as device serials.

Instead, we should dynamically find the "List of devices attached" header line and only parse the lines following it.

Suggested change
for line in stdout.splitlines()[1:]: # skip "List of devices attached"
parts = line.split()
if len(parts) >= 2 and parts[1] == "device":
serials.append(parts[0])
lines = stdout.splitlines()
try:
header_idx = next(i for i, line in enumerate(lines) if "List of devices attached" in line)
device_lines = lines[header_idx + 1:]
except StopIteration:
device_lines = lines
for line in device_lines:
parts = line.split()
if len(parts) >= 2 and parts[1] == "device":
serials.append(parts[0])

The button now flashes the module over an ADB root shell
(magisk --install-module) -- the same command we run by hand -- so it's a
one-click install, not a push-then-flash-yourself. Restart the instance to
activate. If the root shell / Magisk isn't reachable, it falls back to leaving
the zip in the instance's Download folder with instructions.

- adb_handler: extract _resolve_serial(); replace push_module() with
  install_module() (push to /data/local/tmp -> magisk --install-module ->
  cleanup, with the Download fallback on failure).
- main.py: rename to "Install Magisk Module", call install_module, and report
  install + restart in the success dialog.
When magisk --install-module is rejected over adb (the guest hasn't granted
root to the shell), the fallback message now tells the user to set Superuser
access -> "Apps and ADB" and retry, in addition to the manual-flash path.
@RobThePCGuy RobThePCGuy merged commit ded6828 into master Jul 9, 2026
4 checks passed
@RobThePCGuy RobThePCGuy deleted the feat/sideload-module branch July 9, 2026 04:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant