tcl-fw 3.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tcl_fw-3.0.0/LICENSE +21 -0
- tcl_fw-3.0.0/PKG-INFO +169 -0
- tcl_fw-3.0.0/README.md +141 -0
- tcl_fw-3.0.0/pyproject.toml +48 -0
- tcl_fw-3.0.0/setup.cfg +4 -0
- tcl_fw-3.0.0/tcl_fw/__init__.py +16 -0
- tcl_fw-3.0.0/tcl_fw/__main__.py +5 -0
- tcl_fw-3.0.0/tcl_fw/adb.py +99 -0
- tcl_fw-3.0.0/tcl_fw/cli.py +247 -0
- tcl_fw-3.0.0/tcl_fw/crypto.py +77 -0
- tcl_fw-3.0.0/tcl_fw/data/devices.json +1 -0
- tcl_fw-3.0.0/tcl_fw/devices.py +93 -0
- tcl_fw-3.0.0/tcl_fw/download.py +106 -0
- tcl_fw-3.0.0/tcl_fw/fota.py +229 -0
- tcl_fw-3.0.0/tcl_fw/naming.py +147 -0
- tcl_fw-3.0.0/tcl_fw/puller.py +165 -0
- tcl_fw-3.0.0/tcl_fw.egg-info/PKG-INFO +169 -0
- tcl_fw-3.0.0/tcl_fw.egg-info/SOURCES.txt +27 -0
- tcl_fw-3.0.0/tcl_fw.egg-info/dependency_links.txt +1 -0
- tcl_fw-3.0.0/tcl_fw.egg-info/entry_points.txt +5 -0
- tcl_fw-3.0.0/tcl_fw.egg-info/requires.txt +11 -0
- tcl_fw-3.0.0/tcl_fw.egg-info/top_level.txt +2 -0
- tcl_fw-3.0.0/tcl_fw_gui/__init__.py +18 -0
- tcl_fw-3.0.0/tcl_fw_gui/__main__.py +5 -0
- tcl_fw-3.0.0/tcl_fw_gui/app.py +41 -0
- tcl_fw-3.0.0/tcl_fw_gui/main_window.py +465 -0
- tcl_fw-3.0.0/tcl_fw_gui/workers.py +132 -0
- tcl_fw-3.0.0/tests/test_crypto.py +62 -0
- tcl_fw-3.0.0/tests/test_naming.py +56 -0
tcl_fw-3.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tcl_fw-3.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tcl-fw
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Pull and decrypt official TCL (MediaTek) FOTA firmware — flashable service packages, fully offline.
|
|
5
|
+
Author: vehoelite, Littlenine Ennea
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/vehoelite/tcl-fota-tool
|
|
8
|
+
Project-URL: Credit, https://github.com/LittlenineEnnea
|
|
9
|
+
Keywords: tcl,fota,firmware,mediatek,android,mbn,decrypt,scatter
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Utilities
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: typer>=0.12
|
|
19
|
+
Requires-Dist: rich>=13.0
|
|
20
|
+
Requires-Dist: pycryptodome>=3.20
|
|
21
|
+
Provides-Extra: gui
|
|
22
|
+
Requires-Dist: PySide6>=6.6; extra == "gui"
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pyinstaller>=6.0; extra == "dev"
|
|
26
|
+
Requires-Dist: PySide6>=6.6; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# tcl-fw
|
|
30
|
+
|
|
31
|
+
**Pull and decrypt official TCL (MediaTek) firmware — flashable service packages, fully offline.**
|
|
32
|
+
|
|
33
|
+
`tcl-fw` talks to TCL's own FOTA download servers the way the on-device updater
|
|
34
|
+
does (no Google, no account, no dongle), lists a device's complete factory
|
|
35
|
+
"service" fileset, streams the plaintext partitions, and **AES-decrypts** the
|
|
36
|
+
small partitions that ship inside an encrypted 4 MiB header — producing clean,
|
|
37
|
+
flashable images (`lk.img`, `boot.img`, `vbmeta.img`, `preloader_*.bin`, the
|
|
38
|
+
scatter, …).
|
|
39
|
+
|
|
40
|
+
> ### Credit
|
|
41
|
+
> The header-decryption scheme that makes this tool possible — **AES-128-ECB
|
|
42
|
+
> with a universal key recovered from `sugar_otu_r.dll`** — was cracked by
|
|
43
|
+
> **[Littlenine Ennea](https://github.com/LittlenineEnnea)**. Mode 4 (full-image
|
|
44
|
+
> decryption) exists entirely because of that work. Thank you.
|
|
45
|
+
|
|
46
|
+
Works on TCL-made Android devices (TCL, REVVL, Alcatel).
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install tcl-fw # CLI only
|
|
54
|
+
pip install "tcl-fw[gui]" # CLI + desktop app (PySide6)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or grab the standalone `tcl-fw` / `tcl-fw.exe` (CLI) or `tcl-fw-gui.exe`
|
|
58
|
+
(desktop app) from
|
|
59
|
+
[Releases](https://github.com/vehoelite/tcl-fota-tool/releases) — no Python needed.
|
|
60
|
+
|
|
61
|
+
## Desktop app
|
|
62
|
+
|
|
63
|
+
Prefer clicking to typing? Launch the GUI:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
tcl-fw-gui # or: python -m tcl_fw_gui
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Pick (or **Detect**) a device → **Load** to see every partition with real sizes
|
|
70
|
+
→ tick what you want → **Pull**. Per-partition progress, live decrypt log, and
|
|
71
|
+
SHA-1 verification, all over the exact same backend as the CLI. On Windows the
|
|
72
|
+
GUI uses the native `adb`, so **Detect phone** works without any usbipd/WSL
|
|
73
|
+
plumbing.
|
|
74
|
+
|
|
75
|
+
## Quickstart
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Plug in a phone with USB debugging on — tcl-fw reads the curef itself:
|
|
79
|
+
tcl-fw pull
|
|
80
|
+
|
|
81
|
+
# …or name the device explicitly:
|
|
82
|
+
tcl-fw list T704SP-EAUHUS12-V # see every partition, size, name
|
|
83
|
+
tcl-fw pull T704SP-EAUHUS12-V # download + decrypt the whole package
|
|
84
|
+
tcl-fw pull T704SP-EAUHUS12-V --small # just the small parts (lk/preloader/… fast)
|
|
85
|
+
tcl-fw pull T704SP-EAUHUS12-V --only lk,boot,vbmeta
|
|
86
|
+
tcl-fw decrypt some_header.bin # decrypt one local header blob
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Find your curef on a handset:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
adb shell getprop ro.tct.curef
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Commands
|
|
96
|
+
|
|
97
|
+
| Command | What it does |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `tcl-fw pull [curef]` | Download + decrypt a device's service package into flashable images. Auto-detects the curef from a plugged-in phone if omitted. `--small`, `--only p1,p2`, `--out DIR`, `--no-verify`. |
|
|
100
|
+
| `tcl-fw list [curef]` | Resolve a device and list every partition: name, real size, and whether it comes from the body or the encrypted header. |
|
|
101
|
+
| `tcl-fw decrypt <blob>` | Decrypt a single local encrypted-header blob and name it by content. |
|
|
102
|
+
| `tcl-fw devices [--detect]` | List known devices, or probe for a connected phone. |
|
|
103
|
+
|
|
104
|
+
## How it works
|
|
105
|
+
|
|
106
|
+
TCL's FOTA server delivers each partition in one of two ways, and `tcl-fw`
|
|
107
|
+
handles both automatically:
|
|
108
|
+
|
|
109
|
+
- **Large partitions** (`super`, `system`, `vendor`, `boot`, `md1img`, …) — the
|
|
110
|
+
plaintext **body** *is* the image; it's streamed straight to disk (with resume
|
|
111
|
+
and SHA-1 verification against the server's `checksum.php`).
|
|
112
|
+
- **Small partitions** (`lk`, `preloader`, `tee`/`atf`, `vbmeta`, `spmfw`,
|
|
113
|
+
`scatter`, …) — the body is empty; the real image lives inside an encrypted
|
|
114
|
+
~4 MiB header fetched from `encrypt_header.php`. That blob is **AES-128-ECB**
|
|
115
|
+
with the single universal key
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
KEY = ascii( md5("TeleExtTest" + "t0523" + "jP7GHdmuBz").hexdigest()[:16] )
|
|
119
|
+
= e26baba108b08a28
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The header is padded with a constant filler block, which `tcl-fw` detects and
|
|
123
|
+
trims to recover the exact image.
|
|
124
|
+
|
|
125
|
+
Partitions are named **authoritatively** from the server: the `check_new.php`
|
|
126
|
+
manifest is joined to the `.sca` scatter's `rename_prefix → file_name` map, so
|
|
127
|
+
you get real names, not guesses. When the scatter is unavailable, images are
|
|
128
|
+
identified by content (MTK GFH partition name, ext4 volume label, AVB/boot/dtbo
|
|
129
|
+
magic).
|
|
130
|
+
|
|
131
|
+
## Output
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
pkg_<curef>/
|
|
135
|
+
lk.img boot.img vbmeta.img super.img preloader_*.bin …
|
|
136
|
+
<device>.sca # the flash-tool scatter
|
|
137
|
+
manifest.json # what was pulled, sizes, checksum results
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Feed these to SP Flash Tool, `fastboot`, or `mtkclient`.
|
|
141
|
+
|
|
142
|
+
## Related — Image Anarchy
|
|
143
|
+
|
|
144
|
+
Pulled a package and want to flash, repack, or explore it? Check out
|
|
145
|
+
**[Image Anarchy](https://github.com/vehoelite/image-anarchy)** — a companion
|
|
146
|
+
toolkit for working with Android firmware images. `tcl-fw` gets you the clean,
|
|
147
|
+
named partitions; Image Anarchy helps you do something with them.
|
|
148
|
+
|
|
149
|
+
## Legal / ethical use
|
|
150
|
+
|
|
151
|
+
This tool downloads firmware that TCL's own servers serve publicly, for the
|
|
152
|
+
purpose of repairing, restoring, or inspecting **a device you own**. It uses no
|
|
153
|
+
exploit against the device and asks the servers only for what the on-device
|
|
154
|
+
updater already requests. Respect your local laws and TCL's terms.
|
|
155
|
+
|
|
156
|
+
## Credits
|
|
157
|
+
|
|
158
|
+
- **[Littlenine Ennea](https://github.com/LittlenineEnnea)** — cracked the
|
|
159
|
+
AES-128-ECB header-decryption scheme and the universal key; the reference
|
|
160
|
+
implementation lives in [`mode4/tcl-fw.py`](mode4/tcl-fw.py). Mode 4 is theirs.
|
|
161
|
+
- **[vehoelite](https://github.com/vehoelite)** — the original `tcl-fota-tool`
|
|
162
|
+
FOTA protocol client (check/download signing, fileset parsing), preserved in
|
|
163
|
+
[`legacy/`](legacy/), and the companion
|
|
164
|
+
[Image Anarchy](https://github.com/vehoelite/image-anarchy) firmware toolkit.
|
|
165
|
+
- Predecessor protocol research: `mbirth/tcl_ota_check`, `thurask/bbarchivist`.
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT — see [LICENSE](LICENSE).
|
tcl_fw-3.0.0/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# tcl-fw
|
|
2
|
+
|
|
3
|
+
**Pull and decrypt official TCL (MediaTek) firmware — flashable service packages, fully offline.**
|
|
4
|
+
|
|
5
|
+
`tcl-fw` talks to TCL's own FOTA download servers the way the on-device updater
|
|
6
|
+
does (no Google, no account, no dongle), lists a device's complete factory
|
|
7
|
+
"service" fileset, streams the plaintext partitions, and **AES-decrypts** the
|
|
8
|
+
small partitions that ship inside an encrypted 4 MiB header — producing clean,
|
|
9
|
+
flashable images (`lk.img`, `boot.img`, `vbmeta.img`, `preloader_*.bin`, the
|
|
10
|
+
scatter, …).
|
|
11
|
+
|
|
12
|
+
> ### Credit
|
|
13
|
+
> The header-decryption scheme that makes this tool possible — **AES-128-ECB
|
|
14
|
+
> with a universal key recovered from `sugar_otu_r.dll`** — was cracked by
|
|
15
|
+
> **[Littlenine Ennea](https://github.com/LittlenineEnnea)**. Mode 4 (full-image
|
|
16
|
+
> decryption) exists entirely because of that work. Thank you.
|
|
17
|
+
|
|
18
|
+
Works on TCL-made Android devices (TCL, REVVL, Alcatel).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install tcl-fw # CLI only
|
|
26
|
+
pip install "tcl-fw[gui]" # CLI + desktop app (PySide6)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or grab the standalone `tcl-fw` / `tcl-fw.exe` (CLI) or `tcl-fw-gui.exe`
|
|
30
|
+
(desktop app) from
|
|
31
|
+
[Releases](https://github.com/vehoelite/tcl-fota-tool/releases) — no Python needed.
|
|
32
|
+
|
|
33
|
+
## Desktop app
|
|
34
|
+
|
|
35
|
+
Prefer clicking to typing? Launch the GUI:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
tcl-fw-gui # or: python -m tcl_fw_gui
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Pick (or **Detect**) a device → **Load** to see every partition with real sizes
|
|
42
|
+
→ tick what you want → **Pull**. Per-partition progress, live decrypt log, and
|
|
43
|
+
SHA-1 verification, all over the exact same backend as the CLI. On Windows the
|
|
44
|
+
GUI uses the native `adb`, so **Detect phone** works without any usbipd/WSL
|
|
45
|
+
plumbing.
|
|
46
|
+
|
|
47
|
+
## Quickstart
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Plug in a phone with USB debugging on — tcl-fw reads the curef itself:
|
|
51
|
+
tcl-fw pull
|
|
52
|
+
|
|
53
|
+
# …or name the device explicitly:
|
|
54
|
+
tcl-fw list T704SP-EAUHUS12-V # see every partition, size, name
|
|
55
|
+
tcl-fw pull T704SP-EAUHUS12-V # download + decrypt the whole package
|
|
56
|
+
tcl-fw pull T704SP-EAUHUS12-V --small # just the small parts (lk/preloader/… fast)
|
|
57
|
+
tcl-fw pull T704SP-EAUHUS12-V --only lk,boot,vbmeta
|
|
58
|
+
tcl-fw decrypt some_header.bin # decrypt one local header blob
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Find your curef on a handset:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
adb shell getprop ro.tct.curef
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Commands
|
|
68
|
+
|
|
69
|
+
| Command | What it does |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `tcl-fw pull [curef]` | Download + decrypt a device's service package into flashable images. Auto-detects the curef from a plugged-in phone if omitted. `--small`, `--only p1,p2`, `--out DIR`, `--no-verify`. |
|
|
72
|
+
| `tcl-fw list [curef]` | Resolve a device and list every partition: name, real size, and whether it comes from the body or the encrypted header. |
|
|
73
|
+
| `tcl-fw decrypt <blob>` | Decrypt a single local encrypted-header blob and name it by content. |
|
|
74
|
+
| `tcl-fw devices [--detect]` | List known devices, or probe for a connected phone. |
|
|
75
|
+
|
|
76
|
+
## How it works
|
|
77
|
+
|
|
78
|
+
TCL's FOTA server delivers each partition in one of two ways, and `tcl-fw`
|
|
79
|
+
handles both automatically:
|
|
80
|
+
|
|
81
|
+
- **Large partitions** (`super`, `system`, `vendor`, `boot`, `md1img`, …) — the
|
|
82
|
+
plaintext **body** *is* the image; it's streamed straight to disk (with resume
|
|
83
|
+
and SHA-1 verification against the server's `checksum.php`).
|
|
84
|
+
- **Small partitions** (`lk`, `preloader`, `tee`/`atf`, `vbmeta`, `spmfw`,
|
|
85
|
+
`scatter`, …) — the body is empty; the real image lives inside an encrypted
|
|
86
|
+
~4 MiB header fetched from `encrypt_header.php`. That blob is **AES-128-ECB**
|
|
87
|
+
with the single universal key
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
KEY = ascii( md5("TeleExtTest" + "t0523" + "jP7GHdmuBz").hexdigest()[:16] )
|
|
91
|
+
= e26baba108b08a28
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The header is padded with a constant filler block, which `tcl-fw` detects and
|
|
95
|
+
trims to recover the exact image.
|
|
96
|
+
|
|
97
|
+
Partitions are named **authoritatively** from the server: the `check_new.php`
|
|
98
|
+
manifest is joined to the `.sca` scatter's `rename_prefix → file_name` map, so
|
|
99
|
+
you get real names, not guesses. When the scatter is unavailable, images are
|
|
100
|
+
identified by content (MTK GFH partition name, ext4 volume label, AVB/boot/dtbo
|
|
101
|
+
magic).
|
|
102
|
+
|
|
103
|
+
## Output
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
pkg_<curef>/
|
|
107
|
+
lk.img boot.img vbmeta.img super.img preloader_*.bin …
|
|
108
|
+
<device>.sca # the flash-tool scatter
|
|
109
|
+
manifest.json # what was pulled, sizes, checksum results
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Feed these to SP Flash Tool, `fastboot`, or `mtkclient`.
|
|
113
|
+
|
|
114
|
+
## Related — Image Anarchy
|
|
115
|
+
|
|
116
|
+
Pulled a package and want to flash, repack, or explore it? Check out
|
|
117
|
+
**[Image Anarchy](https://github.com/vehoelite/image-anarchy)** — a companion
|
|
118
|
+
toolkit for working with Android firmware images. `tcl-fw` gets you the clean,
|
|
119
|
+
named partitions; Image Anarchy helps you do something with them.
|
|
120
|
+
|
|
121
|
+
## Legal / ethical use
|
|
122
|
+
|
|
123
|
+
This tool downloads firmware that TCL's own servers serve publicly, for the
|
|
124
|
+
purpose of repairing, restoring, or inspecting **a device you own**. It uses no
|
|
125
|
+
exploit against the device and asks the servers only for what the on-device
|
|
126
|
+
updater already requests. Respect your local laws and TCL's terms.
|
|
127
|
+
|
|
128
|
+
## Credits
|
|
129
|
+
|
|
130
|
+
- **[Littlenine Ennea](https://github.com/LittlenineEnnea)** — cracked the
|
|
131
|
+
AES-128-ECB header-decryption scheme and the universal key; the reference
|
|
132
|
+
implementation lives in [`mode4/tcl-fw.py`](mode4/tcl-fw.py). Mode 4 is theirs.
|
|
133
|
+
- **[vehoelite](https://github.com/vehoelite)** — the original `tcl-fota-tool`
|
|
134
|
+
FOTA protocol client (check/download signing, fileset parsing), preserved in
|
|
135
|
+
[`legacy/`](legacy/), and the companion
|
|
136
|
+
[Image Anarchy](https://github.com/vehoelite/image-anarchy) firmware toolkit.
|
|
137
|
+
- Predecessor protocol research: `mbirth/tcl_ota_check`, `thurask/bbarchivist`.
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tcl-fw"
|
|
7
|
+
version = "3.0.0"
|
|
8
|
+
description = "Pull and decrypt official TCL (MediaTek) FOTA firmware — flashable service packages, fully offline."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "vehoelite" },
|
|
14
|
+
{ name = "Littlenine Ennea" },
|
|
15
|
+
]
|
|
16
|
+
keywords = ["tcl", "fota", "firmware", "mediatek", "android", "mbn", "decrypt", "scatter"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Topic :: Utilities",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"typer>=0.12",
|
|
26
|
+
"rich>=13.0",
|
|
27
|
+
"pycryptodome>=3.20",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
gui = ["PySide6>=6.6"]
|
|
32
|
+
dev = ["pytest>=8.0", "pyinstaller>=6.0", "PySide6>=6.6"]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/vehoelite/tcl-fota-tool"
|
|
36
|
+
Credit = "https://github.com/LittlenineEnnea"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
tcl-fw = "tcl_fw.cli:app"
|
|
40
|
+
|
|
41
|
+
[project.gui-scripts]
|
|
42
|
+
tcl-fw-gui = "tcl_fw_gui:main"
|
|
43
|
+
|
|
44
|
+
[tool.setuptools]
|
|
45
|
+
packages = ["tcl_fw", "tcl_fw_gui"]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.package-data]
|
|
48
|
+
tcl_fw = ["data/*.json"]
|
tcl_fw-3.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tcl-fw — pull and decrypt official TCL (MediaTek) FOTA firmware.
|
|
3
|
+
|
|
4
|
+
A firmware puller + .mbn header decryptor for TCL-made Android devices
|
|
5
|
+
(TCL, REVVL, Alcatel). Talks to TCL's own FOTA download servers (no Google,
|
|
6
|
+
no account), streams the plaintext bodies, and AES-decrypts the small
|
|
7
|
+
partitions that ship inside an encrypted 4 MiB header — producing a clean,
|
|
8
|
+
flashable service package fully offline.
|
|
9
|
+
|
|
10
|
+
The header-decryption scheme (AES-128-ECB, universal key) was cracked by
|
|
11
|
+
Littlenine Ennea <https://github.com/LittlenineEnnea>; mode 4 exists because
|
|
12
|
+
of that work. See crypto.py.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
__version__ = "3.0.0"
|
|
16
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
adb.py — read a plugged-in TCL phone's identity so the user types nothing.
|
|
3
|
+
|
|
4
|
+
The whole point of "auto-CUREF": if a phone is connected with USB debugging on,
|
|
5
|
+
we can read its curef (and firmware version) directly, then let fota.discover()
|
|
6
|
+
turn that into a tv/fw_id. No manual lookup, no dongle.
|
|
7
|
+
|
|
8
|
+
Finds adb from (in order): the TCL_FW_ADB env var, a bundled platform-tools/
|
|
9
|
+
next to the package, or the system PATH.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import shutil
|
|
16
|
+
import subprocess
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Device:
|
|
24
|
+
serial: str
|
|
25
|
+
curef: Optional[str] = None
|
|
26
|
+
fv: Optional[str] = None # ro.build.version.incremental
|
|
27
|
+
model: Optional[str] = None
|
|
28
|
+
name: Optional[str] = None # marketing name, if any
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def adb_path() -> Optional[str]:
|
|
32
|
+
"""Locate an adb binary, or None if unavailable."""
|
|
33
|
+
env = os.environ.get("TCL_FW_ADB")
|
|
34
|
+
if env and Path(env).exists():
|
|
35
|
+
return env
|
|
36
|
+
bundled = Path(__file__).resolve().parent.parent / "platform-tools" / (
|
|
37
|
+
"adb.exe" if os.name == "nt" else "adb"
|
|
38
|
+
)
|
|
39
|
+
if bundled.exists():
|
|
40
|
+
return str(bundled)
|
|
41
|
+
return shutil.which("adb")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _adb(args: list[str], serial: Optional[str] = None, timeout: int = 10) -> str:
|
|
45
|
+
exe = adb_path()
|
|
46
|
+
if not exe:
|
|
47
|
+
raise RuntimeError("adb not found (set TCL_FW_ADB, add adb to PATH, "
|
|
48
|
+
"or drop platform-tools/ next to tcl-fw)")
|
|
49
|
+
cmd = [exe]
|
|
50
|
+
if serial:
|
|
51
|
+
cmd += ["-s", serial]
|
|
52
|
+
cmd += args
|
|
53
|
+
out = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
|
54
|
+
if out.returncode != 0:
|
|
55
|
+
raise RuntimeError((out.stderr or out.stdout or "adb error").strip())
|
|
56
|
+
return out.stdout.strip()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def available() -> bool:
|
|
60
|
+
return adb_path() is not None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def list_serials() -> list[str]:
|
|
64
|
+
"""Authorized, online device serials (skips 'unauthorized' / 'offline')."""
|
|
65
|
+
try:
|
|
66
|
+
out = _adb(["devices"])
|
|
67
|
+
except Exception:
|
|
68
|
+
return []
|
|
69
|
+
serials = []
|
|
70
|
+
for line in out.splitlines()[1:]:
|
|
71
|
+
parts = line.split()
|
|
72
|
+
if len(parts) >= 2 and parts[1] == "device":
|
|
73
|
+
serials.append(parts[0])
|
|
74
|
+
return serials
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _getprop(serial: str, prop: str) -> Optional[str]:
|
|
78
|
+
try:
|
|
79
|
+
v = _adb(["shell", "getprop", prop], serial=serial).strip()
|
|
80
|
+
return v or None
|
|
81
|
+
except Exception:
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def read_device(serial: str) -> Device:
|
|
86
|
+
"""Read curef / firmware-version / model from one device."""
|
|
87
|
+
curef = _getprop(serial, "ro.tct.curef") or _getprop(serial, "ro.vendor.tct.curef")
|
|
88
|
+
fv = _getprop(serial, "ro.build.version.incremental")
|
|
89
|
+
model = _getprop(serial, "ro.product.model")
|
|
90
|
+
name = _getprop(serial, "ro.tct.setupwizard.marketname") or model
|
|
91
|
+
return Device(serial=serial, curef=curef, fv=fv, model=model, name=name)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def detect() -> Optional[Device]:
|
|
95
|
+
"""Return the first connected device's identity, or None if nothing usable."""
|
|
96
|
+
serials = list_serials()
|
|
97
|
+
if not serials:
|
|
98
|
+
return None
|
|
99
|
+
return read_device(serials[0])
|