-
Notifications
You must be signed in to change notification settings - Fork 0
feat: complete standalone GUI for mtkclient #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sudotsu
wants to merge
3
commits into
main
Choose a base branch
from
feat/initial-gui
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| # MTK-GUI | ||
|
|
||
| A standalone GUI for [mtkclient](https://github.com/bkerler/mtkclient) — the open-source MediaTek reverse engineering tool. | ||
|
|
||
| ## Why this exists | ||
|
|
||
| mtkclient ships with `mtk_gui.py`, a 717-line single-file GUI that's effectively unmaintained. It works for basic operations but is fragile, hard to extend, and freezes during USB operations because everything runs on the main thread. | ||
|
|
||
| MTK-GUI is a ground-up replacement built with PySide6. It's modular (12 tabs, each in its own file), runs all device I/O on background threads, and has a plugin system for extensibility. | ||
|
|
||
| ## Features | ||
|
|
||
| - **12 operation tabs**: Device, Read, Write, Erase, Keys, Bootloader, Memory, RPMB, IMEI, Exploit, eFuse, Server | ||
| - **Non-blocking UI**: USB polling, partition loading, and all device operations run on background threads | ||
| - **Input validation**: Hex fields, IMEI digits, file paths — validated before reaching the backend | ||
| - **Destructive operation safety**: Confirmation dialogs with type-to-confirm for writes, erases, bootloader changes | ||
| - **Button-disable during operations**: Prevents concurrent conflicting operations on the same device session | ||
| - **Dark/light theme**: Toggle from the View menu, persisted across sessions | ||
| - **Plugin system**: Drop `.py` files in the `plugins/` directory to add tabs, menu items, or hooks | ||
| - **Dismissable warning banners**: Erase and Write tabs warn about destructive operations; dismiss persists | ||
| - **Tooltips**: Hover help explaining why settings exist and when to use them | ||
|
|
||
| ## Requirements | ||
|
|
||
| - Python 3.9+ | ||
| - [mtkclient](https://github.com/bkerler/mtkclient) installed and on `sys.path` | ||
| - PySide6 >= 6.5 | ||
| - pyusb (for automatic device detection) | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| # Clone this repo | ||
| git clone https://github.com/sudotsu/mtk_gui.git | ||
| cd mtk_gui | ||
|
|
||
| # Install dependencies | ||
| pip install PySide6 mtkclient | ||
|
|
||
| # Run | ||
| python run.py | ||
| ``` | ||
|
|
||
| ## Building the executable | ||
|
|
||
| ```bash | ||
| pip install pyinstaller | ||
| pyinstaller mtk-gui.spec --noconfirm | ||
| ``` | ||
|
|
||
| The standalone executable will be at `dist/mtk-gui/mtk-gui.exe`. The entire `dist/mtk-gui/` folder is distributable. | ||
|
|
||
| ## Project structure | ||
|
|
||
| ``` | ||
| mtk_gui/ | ||
| ├── app.py # QApplication entry point | ||
| ├── main_window.py # Signal wiring, operation handlers | ||
| ├── constants.py # App metadata, USB IDs, part types | ||
| ├── backend/ | ||
| │ ├── device_manager.py # Connection state machine, threaded USB poll | ||
| │ ├── log_interceptor.py # GuiSignalProxy for mtkclient logging bridge | ||
| │ ├── mtk_wrapper.py # Clean facade over mtkclient DA operations | ||
| │ └── worker.py # QThread worker with cancel support | ||
| ├── ui/ | ||
| │ ├── tabs/ # 12 tab widgets (one file each) | ||
| │ └── widgets/ # Reusable widgets (log panel, hex viewer, etc.) | ||
| ├── plugins/ # Plugin base class and loader | ||
| └── theme/ # QSS dark/light stylesheets | ||
| ``` | ||
|
|
||
| ## Plugins | ||
|
|
||
| Create a Python file in `plugins/` that subclasses `MtkPlugin`: | ||
|
|
||
| ```python | ||
| from mtk_gui.plugins.base_plugin import MtkPlugin | ||
|
|
||
| class MyPlugin(MtkPlugin): | ||
| name = "My Plugin" | ||
| version = "1.0" | ||
|
|
||
| def register(self, ctx): | ||
| ctx.add_menu_item("My Plugin/Do Thing", self.do_thing) | ||
|
|
||
| def do_thing(self): | ||
| print("Plugin action") | ||
| ``` | ||
|
|
||
| Plugins are loaded at startup from the app-local `plugins/` directory and from the user config directory (`%APPDATA%/mtk-gui/plugins` on Windows, `~/.config/mtk-gui/plugins` on Linux). | ||
|
|
||
| ## License | ||
|
|
||
| GPLv3 — same as mtkclient. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # -*- mode: python ; coding: utf-8 -*- | ||
| """PyInstaller spec for MTK-GUI.""" | ||
| import os | ||
| import sys | ||
| from PyInstaller.utils.hooks import collect_submodules, collect_data_files | ||
|
|
||
| block_cipher = None | ||
|
|
||
| # Collect all mtkclient submodules — many are loaded dynamically | ||
| mtkclient_hiddenimports = collect_submodules('mtkclient') | ||
| usb_hiddenimports = collect_submodules('usb') | ||
|
|
||
| # Collect mtkclient data files (DA loaders, configs, etc.) | ||
| mtkclient_datas = collect_data_files('mtkclient') | ||
|
|
||
| a = Analysis( | ||
| ['run.py'], | ||
| pathex=[], | ||
| binaries=[], | ||
| datas=[ | ||
| ('mtk_gui/theme/*.qss', 'mtk_gui/theme'), | ||
| ('plugins', 'plugins'), | ||
| ] + mtkclient_datas, | ||
| hiddenimports=mtkclient_hiddenimports + usb_hiddenimports + [ | ||
| 'PySide6.QtCore', | ||
| 'PySide6.QtGui', | ||
| 'PySide6.QtWidgets', | ||
| ], | ||
| hookspath=[], | ||
| hooksconfig={}, | ||
| runtime_hooks=[], | ||
| excludes=[], | ||
| win_no_prefer_redirects=False, | ||
| win_private_assemblies=False, | ||
| cipher=block_cipher, | ||
| noarchive=False, | ||
| ) | ||
|
|
||
| pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) | ||
|
|
||
| exe = EXE( | ||
| pyz, | ||
| a.scripts, | ||
| [], | ||
| exclude_binaries=True, | ||
| name='mtk-gui', | ||
| debug=False, | ||
| bootloader_ignore_signals=False, | ||
| strip=False, | ||
| upx=True, | ||
| console=False, # windowed app, no console | ||
| icon=None, | ||
| ) | ||
|
|
||
| coll = COLLECT( | ||
| exe, | ||
| a.binaries, | ||
| a.zipfiles, | ||
| a.datas, | ||
| strip=False, | ||
| upx=True, | ||
| upx_exclude=[], | ||
| name='mtk-gui', | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """MTK-GUI: Standalone GUI for mtkclient.""" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| """QApplication setup, theme init, entry point.""" | ||
| import os | ||
| import sys | ||
|
|
||
| from PySide6.QtWidgets import QApplication | ||
| from PySide6.QtCore import Qt | ||
|
|
||
| from mtk_gui.constants import APP_NAME, ORG_NAME | ||
| from mtk_gui.main_window import MainWindow | ||
| from mtk_gui.plugins.loader import load_plugins | ||
|
|
||
|
|
||
| def get_plugin_dirs() -> list: | ||
| """ | ||
| Provide the application-local and user configuration directories used to search for plugins. | ||
|
|
||
| Returns: | ||
| list[str]: Plugin search directory paths. | ||
| """ | ||
| dirs = [] | ||
| # App-local plugins dir | ||
| app_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | ||
| dirs.append(os.path.join(app_dir, "plugins")) | ||
| # User config plugins dir | ||
| if sys.platform == "win32": | ||
| appdata = os.environ.get("APPDATA") | ||
| if appdata and os.path.isabs(appdata): | ||
| dirs.append(os.path.join(appdata, "mtk-gui", "plugins")) | ||
| else: | ||
| config_dir = os.path.join(os.path.expanduser("~"), ".config", "mtk-gui", "plugins") | ||
| if os.path.isabs(config_dir): | ||
| dirs.append(config_dir) | ||
| return dirs | ||
|
|
||
|
|
||
| def main(): | ||
| """ | ||
| Initialize the application, load plugins, display the main window, and start the Qt event loop. | ||
| """ | ||
| QApplication.setOrganizationName(ORG_NAME) | ||
| QApplication.setApplicationName(APP_NAME) | ||
|
|
||
| app = QApplication(sys.argv) | ||
|
|
||
| # High DPI support | ||
| app.setHighDpiScaleFactorRoundingPolicy( | ||
| Qt.HighDpiScaleFactorRoundingPolicy.PassThrough | ||
| ) | ||
|
|
||
| window = MainWindow() | ||
| window.apply_initial_theme() | ||
|
|
||
| # Load plugins | ||
| class AppContext: | ||
| main_window = window | ||
|
|
||
| plugins = load_plugins(AppContext(), get_plugin_dirs()) | ||
| window.set_plugins(plugins) | ||
|
|
||
| window.show() | ||
| sys.exit(app.exec()) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Backend layer — wraps mtkclient. No PySide6 imports.""" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Specify a language for the fenced block.
Add a language identifier to satisfy Markdown rule MD040. Use
textbecause this block contains a directory tree and comments.Proposed fix
📝 Committable suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)
[warning] 55-55: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Source: Linters/SAST tools