Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions README.md
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
```
Comment on lines +55 to +70

Copy link
Copy Markdown

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 text because this block contains a directory tree and comments.

Proposed fix
-```
+```text
 mtk_gui/
 ...
-```
+```
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
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
```
🧰 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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 55 - 70, Update the fenced directory-tree block in
the README around the mtk_gui structure to specify the text language identifier,
changing the opening fence to use text while leaving the tree content unchanged.

Source: Linters/SAST tools


## 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.
64 changes: 64 additions & 0 deletions mtk-gui.spec
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',
)
1 change: 1 addition & 0 deletions mtk_gui/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""MTK-GUI: Standalone GUI for mtkclient."""
65 changes: 65 additions & 0 deletions mtk_gui/app.py
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()
1 change: 1 addition & 0 deletions mtk_gui/backend/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Backend layer — wraps mtkclient. No PySide6 imports."""
Loading