Add support for everything Firmware_extractor supported - #146

Open
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master
Open

Add support for everything Firmware_extractor supported#146
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master

Conversation

@akhilnarang

@akhilnarangakhilnarang commented Jun 28, 2026

Copy link
Copy Markdown

Summary

This change adds a Rust parser module for Android firmware files. PyO3 provides the parser functions to Python.

Firmware formats

The module adds these parsers:

  • Android sparse images and sparse image chunks
  • Amlogic firmware
  • KDDI firmware
  • MediaTek signed images
  • Nokia NB0 files
  • Oppo OZIP files
  • Spreadtrum PAC files
  • Qualcomm QFIL files
  • Rockchip RKFW and AFP files
  • Sony SIN and FTF files
  • ZTE firmware

Extraction changes

  • Detect a known firmware format before generic archive extraction.
  • Detect nested firmware files after archive extraction.
  • Use ZIP extraction for archives that have a nonstandard file extension.
  • Remove a common vendor prefix from extracted file names.
  • Use the Rust sparse-image parser when the module is available.
  • Use simg2img when the Rust module is not available.
  • Search nested ZIP files for partition markers.
  • Keep a canonical partition when an alias also exists.
  • Accept partition files that use the .bin suffix.
  • Write all_files.txt with UTF-8 encoding.

Compatibility and checks

  • Use PyO3 0.29.
  • Support Python 3.14.
  • Add type information for the optional parser module.
  • Define the Ruff lint rules for stable CI results.
  • Fix the issues that Codacy reported.
  • Apply the standard Python and Rust formats.

The following checks pass:

  • Origin ruff-check
  • Origin ruff-format
  • Origin pyright
  • Upstream Codacy analysis
  • python -m compileall -q dumpyara
  • cargo fmt --manifest-path firmware-parsers/Cargo.toml -- --check
  • cargo check --locked --manifest-path firmware-parsers/Cargo.toml
  • cargo test --locked --manifest-path firmware-parsers/Cargo.toml

@codacy-production

codacy-productionBot commented Jun 28, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues1 minor

Alerts:
⚠ 1 issue (≤ 0 issues of at least minor severity)

Results:
1 new issue

CategoryResults
Security1 minor

View in Codacy

🟢 Metrics629 complexity · 70 duplication

MetricResults
Complexity629
Duplication70

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@akhilnarang
akhilnarangforce-pushed the master branch 3 times, most recently from ef73a12 to b5195ceCompareJuly 24, 2026 06:41
deadman96385and others added 13 commits August 16, 2026 22:44
Add a new Rust + PyO3 native extension (firmware-parsers/) that provides
Android sparse image conversion, replacing the external simg2img binary
dependency. The crate exposes sparse_to_raw, sparse_chunks_to_raw, and
is_sparse functions to Python via the firmware_parsers module.
All simg2img subprocess calls in multipartitions.py, raw_image.py, and
sparsed_images.py now use the native Rust implementation when available,
with graceful fallback to the simg2img binary when firmware_parsers is
not installed. The simg2img tool requirement is also conditionally
removed from REQUIRED_TOOLS in dumpyara.py.
Add Rust extractors for NB0, PAC, and MTK signed image formats and expose them through the PyO3 module.
Update format detection to recognize PAC and NB0 files directly and inspect zip archives for MTK signed image payloads.
Implement Phases 3-6 of the firmware-parsers plan:
- Phase 3: OPPO ozip (AES-128-ECB decrypt, 35 keys, mode-1 direct and
mode-2 zip-wrapped with inner .ozip entry decryption) and Sony SIN/FTF
(v3/v4/v5 + legacy SSSS/BFBF + Sony sparse chunks 0xCAC1-0xCAC5)
- Phase 4: Amlogic USB burning tool and Rockchip RKFW/AFP containers
- Phase 5: QFIL rawprogram XML, ZTE update.zip, KDDI .bin extractors
- Phase 6: Wire firmware_parsers.detect() into extract_archive.py with
graceful fallback to shutil.unpack_archive on failure
Also fix issues found in review of Phase 1-2 code:
- Remove "sparse" from detect() returns (no matching Python function)
- Strengthen NB0 probe with printable-ASCII filename validation
- Validate BFBF/MTK header magic before 0x4040 byte strip
- Sanitize output filenames in nb0/pac/amlogic/rockchip/qfil (path traversal)
- Fix SIN tar entry processing (per-entry instead of concatenation)
- Use static OnceLock<Regex> in ZTE/KDDI instead of per-call compilation
- Expand partition name whitelist for ZTE/KDDI bin detection
Format-specific fixes:
- OZIP: handle PK-wrapped mode-2 inputs, decrypt inner .ozip entries,
strip .ozip extension so downstream recognizes decrypted payloads;
preserve decrypted filename for non-zip payloads (e.g.
system.new.dat.br.ozip -> system.new.dat.br, not .br.img);
use temp work directory for zip extraction to avoid leaked temp files;
propagate decryption failure instead of silently renaming ciphertext
- QFIL: group <program> entries by label to merge sparsechunks instead
of overwriting; resolve filenames by basename for flattened layouts;
sanitize XML labels against path traversal; honor file_sector_offset
when rebuilding partitions from shared backing files; branch on
all_same_file without requiring non-zero first offset
- Amlogic: detect tar.bz2 wrappers by content (BZh magic) instead of
filename extension; verify Amlogic magic before accepting tar entries
to avoid selecting non-Amlogic .img files that precede the payload
- SIN: stream-scan for gzip/tar offset instead of reading entire file
into memory (avoids OOM on multi-GB files); strip .sin extension
case-insensitively to match case-insensitive FTF detection
- detect.rs: scan all tar.bz2 members for Amlogic magic (not just
the first entry, since packages may have readme/manifest first);
tighten bzip2 check from 2-byte "BZ" to 3-byte "BZh"
Housekeeping:
- Remove unused Cargo dependencies (thiserror, memmap2, bytemuck, byteorder)
Rockchip RKFW header uses packed/unaligned fields:
- AFP container offset at 0x21 (not 0x1C)
- AFP container size at 0x25 (not 0x20)
AFP container structure corrected:
- AFP header is 0x8C bytes (not 0x4C)
- entry_count at offset 0x88 (not 0x44)
- Each entry is 0x70 / 112 bytes (not 0x48 / 72)
- Entry offset at 0x60, size at 0x6C (not 0x20/0x28)
When a firmware ships both a real partition and its alias (e.g. modem.img
alongside NON-HLOS.img on recent Oppo/ColorOS builds), fix_aliases logged
"Ignoring <alias> (<name> already extracted)" and unlinked the alias, but
then fell through and still attempted move(alias, partition). Since the
alias was just deleted, this raised FileNotFoundError and aborted the dump.
Add the missing continue so the redundant alias is dropped and the real
partition is left untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Some firmware (e.g. aftermarket car head units) wrap the real dumpable ROM
one zip deeper -- the outer archive holds vendor blobs and APK dirs, while
the actual block-based OTA (system.new.dat.br + *.transfer.list, payload.bin,
super*.img, *.tar.md5) lives inside an inner .zip such as update_car.zip.
dumpyara only scanned the outer archive's top level, found no recognized
partition container, and aborted with "System folder doesn't exist".
Extend nested-archive handling: for each top-level .zip not already covered
by NESTED_ARCHIVES, peek its central directory (zipfile namelist, no
extraction) and recurse only when it contains a partition marker. The marker
patterns are anchored to a path boundary and the *.new.dat.br / *.transfer.list
markers are restricted to known partition names, so APK/config zips (which
carry none of these) are never exploded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Fixes dumping OPPO and/or OnePlus stock firmwares that use Chinese
characters for app paths
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Define the Ruff rule set.
Add type information for the optional parser module.
Resolve the fallback executable paths.
Use PyO3 0.29 for Python 3.14.
Apply the standard Python and Rust formats.
akhilnarangand others added 5 commits August 16, 2026 23:03
The extractor did not extract a nested image zip when the zip held loose
raw partition images. Such a zip has no super image and no payload file.
Pixel factory images use this layout. They ship system.img, vendor.img,
and product.img directly.
The marker check missed these files. Therefore the extractor skipped the
nested zip. The system partition did not reach the output. The system
folder assertion then failed.
Add a marker pattern for loose raw partition images. Anchor the pattern
on the known partition names. Allow an optional A/B slot suffix. Allow
the image file extensions that get_raw_image accepts. The pattern does
not match super_empty.img. The pattern does not match an unrelated image
file.
Derive the partition names from get_partition_names_with_alias. Add the
return type to two partition helper functions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The vendored Python payload parser is single-threaded, iterates
partitions and operations serially, and shells out to xzcat/bzcat
once per operation. On a 3.5 GB Pixel foldable OTA (grizzly, ~40
partitions), that hits our 1-hour bot timeout; running otadump on
the same input finishes in ~1-2 min.
Use the otadump binary when it's on PATH (Rayon-parallel per
partition, in-process zstd/xz/bzip2), fall back to the vendored
parser otherwise. Stage otadump's output in a sibling tempdir and
move on success so a partial failure doesn't leave stray images
next to payload.bin.
Fallback triggers only on binary-absent, not on error — the
Python impl is less strict about hash verification and silently
falling back could hide corruption.
Since Xiaomi HyperOS 4, Xiaomi added new partition called "mi_product". For now it's quite empty, but it might change in the future.
Sign up for freeto 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.

4 participants

@akhilnarang@deadman96385@muhammad23012009@kacskrz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Add support for everything Firmware_extractor supported - #146

Open
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master
Open

Add support for everything Firmware_extractor supported#146
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master

Conversation

@akhilnarang

@akhilnarangakhilnarang commented Jun 28, 2026

Copy link
Copy Markdown

Summary

This change adds a Rust parser module for Android firmware files. PyO3 provides the parser functions to Python.

Firmware formats

The module adds these parsers:

  • Android sparse images and sparse image chunks
  • Amlogic firmware
  • KDDI firmware
  • MediaTek signed images
  • Nokia NB0 files
  • Oppo OZIP files
  • Spreadtrum PAC files
  • Qualcomm QFIL files
  • Rockchip RKFW and AFP files
  • Sony SIN and FTF files
  • ZTE firmware

Extraction changes

  • Detect a known firmware format before generic archive extraction.
  • Detect nested firmware files after archive extraction.
  • Use ZIP extraction for archives that have a nonstandard file extension.
  • Remove a common vendor prefix from extracted file names.
  • Use the Rust sparse-image parser when the module is available.
  • Use simg2img when the Rust module is not available.
  • Search nested ZIP files for partition markers.
  • Keep a canonical partition when an alias also exists.
  • Accept partition files that use the .bin suffix.
  • Write all_files.txt with UTF-8 encoding.

Compatibility and checks

  • Use PyO3 0.29.
  • Support Python 3.14.
  • Add type information for the optional parser module.
  • Define the Ruff lint rules for stable CI results.
  • Fix the issues that Codacy reported.
  • Apply the standard Python and Rust formats.

The following checks pass:

  • Origin ruff-check
  • Origin ruff-format
  • Origin pyright
  • Upstream Codacy analysis
  • python -m compileall -q dumpyara
  • cargo fmt --manifest-path firmware-parsers/Cargo.toml -- --check
  • cargo check --locked --manifest-path firmware-parsers/Cargo.toml
  • cargo test --locked --manifest-path firmware-parsers/Cargo.toml

@codacy-production

codacy-productionBot commented Jun 28, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues1 minor

Alerts:
⚠ 1 issue (≤ 0 issues of at least minor severity)

Results:
1 new issue

CategoryResults
Security1 minor

View in Codacy

🟢 Metrics629 complexity · 70 duplication

MetricResults
Complexity629
Duplication70

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@akhilnarang
akhilnarangforce-pushed the master branch 3 times, most recently from ef73a12 to b5195ceCompareJuly 24, 2026 06:41
deadman96385and others added 13 commits August 16, 2026 22:44
Add a new Rust + PyO3 native extension (firmware-parsers/) that provides
Android sparse image conversion, replacing the external simg2img binary
dependency. The crate exposes sparse_to_raw, sparse_chunks_to_raw, and
is_sparse functions to Python via the firmware_parsers module.
All simg2img subprocess calls in multipartitions.py, raw_image.py, and
sparsed_images.py now use the native Rust implementation when available,
with graceful fallback to the simg2img binary when firmware_parsers is
not installed. The simg2img tool requirement is also conditionally
removed from REQUIRED_TOOLS in dumpyara.py.
Add Rust extractors for NB0, PAC, and MTK signed image formats and expose them through the PyO3 module.
Update format detection to recognize PAC and NB0 files directly and inspect zip archives for MTK signed image payloads.
Implement Phases 3-6 of the firmware-parsers plan:
- Phase 3: OPPO ozip (AES-128-ECB decrypt, 35 keys, mode-1 direct and
mode-2 zip-wrapped with inner .ozip entry decryption) and Sony SIN/FTF
(v3/v4/v5 + legacy SSSS/BFBF + Sony sparse chunks 0xCAC1-0xCAC5)
- Phase 4: Amlogic USB burning tool and Rockchip RKFW/AFP containers
- Phase 5: QFIL rawprogram XML, ZTE update.zip, KDDI .bin extractors
- Phase 6: Wire firmware_parsers.detect() into extract_archive.py with
graceful fallback to shutil.unpack_archive on failure
Also fix issues found in review of Phase 1-2 code:
- Remove "sparse" from detect() returns (no matching Python function)
- Strengthen NB0 probe with printable-ASCII filename validation
- Validate BFBF/MTK header magic before 0x4040 byte strip
- Sanitize output filenames in nb0/pac/amlogic/rockchip/qfil (path traversal)
- Fix SIN tar entry processing (per-entry instead of concatenation)
- Use static OnceLock<Regex> in ZTE/KDDI instead of per-call compilation
- Expand partition name whitelist for ZTE/KDDI bin detection
Format-specific fixes:
- OZIP: handle PK-wrapped mode-2 inputs, decrypt inner .ozip entries,
strip .ozip extension so downstream recognizes decrypted payloads;
preserve decrypted filename for non-zip payloads (e.g.
system.new.dat.br.ozip -> system.new.dat.br, not .br.img);
use temp work directory for zip extraction to avoid leaked temp files;
propagate decryption failure instead of silently renaming ciphertext
- QFIL: group <program> entries by label to merge sparsechunks instead
of overwriting; resolve filenames by basename for flattened layouts;
sanitize XML labels against path traversal; honor file_sector_offset
when rebuilding partitions from shared backing files; branch on
all_same_file without requiring non-zero first offset
- Amlogic: detect tar.bz2 wrappers by content (BZh magic) instead of
filename extension; verify Amlogic magic before accepting tar entries
to avoid selecting non-Amlogic .img files that precede the payload
- SIN: stream-scan for gzip/tar offset instead of reading entire file
into memory (avoids OOM on multi-GB files); strip .sin extension
case-insensitively to match case-insensitive FTF detection
- detect.rs: scan all tar.bz2 members for Amlogic magic (not just
the first entry, since packages may have readme/manifest first);
tighten bzip2 check from 2-byte "BZ" to 3-byte "BZh"
Housekeeping:
- Remove unused Cargo dependencies (thiserror, memmap2, bytemuck, byteorder)
Rockchip RKFW header uses packed/unaligned fields:
- AFP container offset at 0x21 (not 0x1C)
- AFP container size at 0x25 (not 0x20)
AFP container structure corrected:
- AFP header is 0x8C bytes (not 0x4C)
- entry_count at offset 0x88 (not 0x44)
- Each entry is 0x70 / 112 bytes (not 0x48 / 72)
- Entry offset at 0x60, size at 0x6C (not 0x20/0x28)
When a firmware ships both a real partition and its alias (e.g. modem.img
alongside NON-HLOS.img on recent Oppo/ColorOS builds), fix_aliases logged
"Ignoring <alias> (<name> already extracted)" and unlinked the alias, but
then fell through and still attempted move(alias, partition). Since the
alias was just deleted, this raised FileNotFoundError and aborted the dump.
Add the missing continue so the redundant alias is dropped and the real
partition is left untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Some firmware (e.g. aftermarket car head units) wrap the real dumpable ROM
one zip deeper -- the outer archive holds vendor blobs and APK dirs, while
the actual block-based OTA (system.new.dat.br + *.transfer.list, payload.bin,
super*.img, *.tar.md5) lives inside an inner .zip such as update_car.zip.
dumpyara only scanned the outer archive's top level, found no recognized
partition container, and aborted with "System folder doesn't exist".
Extend nested-archive handling: for each top-level .zip not already covered
by NESTED_ARCHIVES, peek its central directory (zipfile namelist, no
extraction) and recurse only when it contains a partition marker. The marker
patterns are anchored to a path boundary and the *.new.dat.br / *.transfer.list
markers are restricted to known partition names, so APK/config zips (which
carry none of these) are never exploded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Fixes dumping OPPO and/or OnePlus stock firmwares that use Chinese
characters for app paths
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Define the Ruff rule set.
Add type information for the optional parser module.
Resolve the fallback executable paths.
Use PyO3 0.29 for Python 3.14.
Apply the standard Python and Rust formats.
akhilnarangand others added 5 commits August 16, 2026 23:03
The extractor did not extract a nested image zip when the zip held loose
raw partition images. Such a zip has no super image and no payload file.
Pixel factory images use this layout. They ship system.img, vendor.img,
and product.img directly.
The marker check missed these files. Therefore the extractor skipped the
nested zip. The system partition did not reach the output. The system
folder assertion then failed.
Add a marker pattern for loose raw partition images. Anchor the pattern
on the known partition names. Allow an optional A/B slot suffix. Allow
the image file extensions that get_raw_image accepts. The pattern does
not match super_empty.img. The pattern does not match an unrelated image
file.
Derive the partition names from get_partition_names_with_alias. Add the
return type to two partition helper functions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The vendored Python payload parser is single-threaded, iterates
partitions and operations serially, and shells out to xzcat/bzcat
once per operation. On a 3.5 GB Pixel foldable OTA (grizzly, ~40
partitions), that hits our 1-hour bot timeout; running otadump on
the same input finishes in ~1-2 min.
Use the otadump binary when it's on PATH (Rayon-parallel per
partition, in-process zstd/xz/bzip2), fall back to the vendored
parser otherwise. Stage otadump's output in a sibling tempdir and
move on success so a partial failure doesn't leave stray images
next to payload.bin.
Fallback triggers only on binary-absent, not on error — the
Python impl is less strict about hash verification and silently
falling back could hide corruption.
Since Xiaomi HyperOS 4, Xiaomi added new partition called "mi_product". For now it's quite empty, but it might change in the future.
Sign up for freeto 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.

4 participants

@akhilnarang@deadman96385@muhammad23012009@kacskrz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add support for everything Firmware_extractor supported - #146

Open
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master
Open

Add support for everything Firmware_extractor supported#146
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master

Conversation

@akhilnarang

@akhilnarangakhilnarang commented Jun 28, 2026

Copy link
Copy Markdown

Summary

This change adds a Rust parser module for Android firmware files. PyO3 provides the parser functions to Python.

Firmware formats

The module adds these parsers:

  • Android sparse images and sparse image chunks
  • Amlogic firmware
  • KDDI firmware
  • MediaTek signed images
  • Nokia NB0 files
  • Oppo OZIP files
  • Spreadtrum PAC files
  • Qualcomm QFIL files
  • Rockchip RKFW and AFP files
  • Sony SIN and FTF files
  • ZTE firmware

Extraction changes

  • Detect a known firmware format before generic archive extraction.
  • Detect nested firmware files after archive extraction.
  • Use ZIP extraction for archives that have a nonstandard file extension.
  • Remove a common vendor prefix from extracted file names.
  • Use the Rust sparse-image parser when the module is available.
  • Use simg2img when the Rust module is not available.
  • Search nested ZIP files for partition markers.
  • Keep a canonical partition when an alias also exists.
  • Accept partition files that use the .bin suffix.
  • Write all_files.txt with UTF-8 encoding.

Compatibility and checks

  • Use PyO3 0.29.
  • Support Python 3.14.
  • Add type information for the optional parser module.
  • Define the Ruff lint rules for stable CI results.
  • Fix the issues that Codacy reported.
  • Apply the standard Python and Rust formats.

The following checks pass:

  • Origin ruff-check
  • Origin ruff-format
  • Origin pyright
  • Upstream Codacy analysis
  • python -m compileall -q dumpyara
  • cargo fmt --manifest-path firmware-parsers/Cargo.toml -- --check
  • cargo check --locked --manifest-path firmware-parsers/Cargo.toml
  • cargo test --locked --manifest-path firmware-parsers/Cargo.toml

@codacy-production

codacy-productionBot commented Jun 28, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues1 minor

Alerts:
⚠ 1 issue (≤ 0 issues of at least minor severity)

Results:
1 new issue

CategoryResults
Security1 minor

View in Codacy

🟢 Metrics629 complexity · 70 duplication

MetricResults
Complexity629
Duplication70

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@akhilnarang
akhilnarangforce-pushed the master branch 3 times, most recently from ef73a12 to b5195ceCompareJuly 24, 2026 06:41
deadman96385and others added 13 commits August 16, 2026 22:44
Add a new Rust + PyO3 native extension (firmware-parsers/) that provides
Android sparse image conversion, replacing the external simg2img binary
dependency. The crate exposes sparse_to_raw, sparse_chunks_to_raw, and
is_sparse functions to Python via the firmware_parsers module.
All simg2img subprocess calls in multipartitions.py, raw_image.py, and
sparsed_images.py now use the native Rust implementation when available,
with graceful fallback to the simg2img binary when firmware_parsers is
not installed. The simg2img tool requirement is also conditionally
removed from REQUIRED_TOOLS in dumpyara.py.
Add Rust extractors for NB0, PAC, and MTK signed image formats and expose them through the PyO3 module.
Update format detection to recognize PAC and NB0 files directly and inspect zip archives for MTK signed image payloads.
Implement Phases 3-6 of the firmware-parsers plan:
- Phase 3: OPPO ozip (AES-128-ECB decrypt, 35 keys, mode-1 direct and
mode-2 zip-wrapped with inner .ozip entry decryption) and Sony SIN/FTF
(v3/v4/v5 + legacy SSSS/BFBF + Sony sparse chunks 0xCAC1-0xCAC5)
- Phase 4: Amlogic USB burning tool and Rockchip RKFW/AFP containers
- Phase 5: QFIL rawprogram XML, ZTE update.zip, KDDI .bin extractors
- Phase 6: Wire firmware_parsers.detect() into extract_archive.py with
graceful fallback to shutil.unpack_archive on failure
Also fix issues found in review of Phase 1-2 code:
- Remove "sparse" from detect() returns (no matching Python function)
- Strengthen NB0 probe with printable-ASCII filename validation
- Validate BFBF/MTK header magic before 0x4040 byte strip
- Sanitize output filenames in nb0/pac/amlogic/rockchip/qfil (path traversal)
- Fix SIN tar entry processing (per-entry instead of concatenation)
- Use static OnceLock<Regex> in ZTE/KDDI instead of per-call compilation
- Expand partition name whitelist for ZTE/KDDI bin detection
Format-specific fixes:
- OZIP: handle PK-wrapped mode-2 inputs, decrypt inner .ozip entries,
strip .ozip extension so downstream recognizes decrypted payloads;
preserve decrypted filename for non-zip payloads (e.g.
system.new.dat.br.ozip -> system.new.dat.br, not .br.img);
use temp work directory for zip extraction to avoid leaked temp files;
propagate decryption failure instead of silently renaming ciphertext
- QFIL: group <program> entries by label to merge sparsechunks instead
of overwriting; resolve filenames by basename for flattened layouts;
sanitize XML labels against path traversal; honor file_sector_offset
when rebuilding partitions from shared backing files; branch on
all_same_file without requiring non-zero first offset
- Amlogic: detect tar.bz2 wrappers by content (BZh magic) instead of
filename extension; verify Amlogic magic before accepting tar entries
to avoid selecting non-Amlogic .img files that precede the payload
- SIN: stream-scan for gzip/tar offset instead of reading entire file
into memory (avoids OOM on multi-GB files); strip .sin extension
case-insensitively to match case-insensitive FTF detection
- detect.rs: scan all tar.bz2 members for Amlogic magic (not just
the first entry, since packages may have readme/manifest first);
tighten bzip2 check from 2-byte "BZ" to 3-byte "BZh"
Housekeeping:
- Remove unused Cargo dependencies (thiserror, memmap2, bytemuck, byteorder)
Rockchip RKFW header uses packed/unaligned fields:
- AFP container offset at 0x21 (not 0x1C)
- AFP container size at 0x25 (not 0x20)
AFP container structure corrected:
- AFP header is 0x8C bytes (not 0x4C)
- entry_count at offset 0x88 (not 0x44)
- Each entry is 0x70 / 112 bytes (not 0x48 / 72)
- Entry offset at 0x60, size at 0x6C (not 0x20/0x28)
When a firmware ships both a real partition and its alias (e.g. modem.img
alongside NON-HLOS.img on recent Oppo/ColorOS builds), fix_aliases logged
"Ignoring <alias> (<name> already extracted)" and unlinked the alias, but
then fell through and still attempted move(alias, partition). Since the
alias was just deleted, this raised FileNotFoundError and aborted the dump.
Add the missing continue so the redundant alias is dropped and the real
partition is left untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Some firmware (e.g. aftermarket car head units) wrap the real dumpable ROM
one zip deeper -- the outer archive holds vendor blobs and APK dirs, while
the actual block-based OTA (system.new.dat.br + *.transfer.list, payload.bin,
super*.img, *.tar.md5) lives inside an inner .zip such as update_car.zip.
dumpyara only scanned the outer archive's top level, found no recognized
partition container, and aborted with "System folder doesn't exist".
Extend nested-archive handling: for each top-level .zip not already covered
by NESTED_ARCHIVES, peek its central directory (zipfile namelist, no
extraction) and recurse only when it contains a partition marker. The marker
patterns are anchored to a path boundary and the *.new.dat.br / *.transfer.list
markers are restricted to known partition names, so APK/config zips (which
carry none of these) are never exploded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Fixes dumping OPPO and/or OnePlus stock firmwares that use Chinese
characters for app paths
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Define the Ruff rule set.
Add type information for the optional parser module.
Resolve the fallback executable paths.
Use PyO3 0.29 for Python 3.14.
Apply the standard Python and Rust formats.
akhilnarangand others added 5 commits August 16, 2026 23:03
The extractor did not extract a nested image zip when the zip held loose
raw partition images. Such a zip has no super image and no payload file.
Pixel factory images use this layout. They ship system.img, vendor.img,
and product.img directly.
The marker check missed these files. Therefore the extractor skipped the
nested zip. The system partition did not reach the output. The system
folder assertion then failed.
Add a marker pattern for loose raw partition images. Anchor the pattern
on the known partition names. Allow an optional A/B slot suffix. Allow
the image file extensions that get_raw_image accepts. The pattern does
not match super_empty.img. The pattern does not match an unrelated image
file.
Derive the partition names from get_partition_names_with_alias. Add the
return type to two partition helper functions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The vendored Python payload parser is single-threaded, iterates
partitions and operations serially, and shells out to xzcat/bzcat
once per operation. On a 3.5 GB Pixel foldable OTA (grizzly, ~40
partitions), that hits our 1-hour bot timeout; running otadump on
the same input finishes in ~1-2 min.
Use the otadump binary when it's on PATH (Rayon-parallel per
partition, in-process zstd/xz/bzip2), fall back to the vendored
parser otherwise. Stage otadump's output in a sibling tempdir and
move on success so a partial failure doesn't leave stray images
next to payload.bin.
Fallback triggers only on binary-absent, not on error — the
Python impl is less strict about hash verification and silently
falling back could hide corruption.
Since Xiaomi HyperOS 4, Xiaomi added new partition called "mi_product". For now it's quite empty, but it might change in the future.
Sign up for freeto 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.

4 participants

@akhilnarang@deadman96385@muhammad23012009@kacskrz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add support for everything Firmware_extractor supported - #146

Open
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master
Open

Add support for everything Firmware_extractor supported#146
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master

Conversation

@akhilnarang

@akhilnarangakhilnarang commented Jun 28, 2026

Copy link
Copy Markdown

Summary

This change adds a Rust parser module for Android firmware files. PyO3 provides the parser functions to Python.

Firmware formats

The module adds these parsers:

  • Android sparse images and sparse image chunks
  • Amlogic firmware
  • KDDI firmware
  • MediaTek signed images
  • Nokia NB0 files
  • Oppo OZIP files
  • Spreadtrum PAC files
  • Qualcomm QFIL files
  • Rockchip RKFW and AFP files
  • Sony SIN and FTF files
  • ZTE firmware

Extraction changes

  • Detect a known firmware format before generic archive extraction.
  • Detect nested firmware files after archive extraction.
  • Use ZIP extraction for archives that have a nonstandard file extension.
  • Remove a common vendor prefix from extracted file names.
  • Use the Rust sparse-image parser when the module is available.
  • Use simg2img when the Rust module is not available.
  • Search nested ZIP files for partition markers.
  • Keep a canonical partition when an alias also exists.
  • Accept partition files that use the .bin suffix.
  • Write all_files.txt with UTF-8 encoding.

Compatibility and checks

  • Use PyO3 0.29.
  • Support Python 3.14.
  • Add type information for the optional parser module.
  • Define the Ruff lint rules for stable CI results.
  • Fix the issues that Codacy reported.
  • Apply the standard Python and Rust formats.

The following checks pass:

  • Origin ruff-check
  • Origin ruff-format
  • Origin pyright
  • Upstream Codacy analysis
  • python -m compileall -q dumpyara
  • cargo fmt --manifest-path firmware-parsers/Cargo.toml -- --check
  • cargo check --locked --manifest-path firmware-parsers/Cargo.toml
  • cargo test --locked --manifest-path firmware-parsers/Cargo.toml

@codacy-production

codacy-productionBot commented Jun 28, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues1 minor

Alerts:
⚠ 1 issue (≤ 0 issues of at least minor severity)

Results:
1 new issue

CategoryResults
Security1 minor

View in Codacy

🟢 Metrics629 complexity · 70 duplication

MetricResults
Complexity629
Duplication70

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@akhilnarang
akhilnarangforce-pushed the master branch 3 times, most recently from ef73a12 to b5195ceCompareJuly 24, 2026 06:41
deadman96385and others added 13 commits August 16, 2026 22:44
Add a new Rust + PyO3 native extension (firmware-parsers/) that provides
Android sparse image conversion, replacing the external simg2img binary
dependency. The crate exposes sparse_to_raw, sparse_chunks_to_raw, and
is_sparse functions to Python via the firmware_parsers module.
All simg2img subprocess calls in multipartitions.py, raw_image.py, and
sparsed_images.py now use the native Rust implementation when available,
with graceful fallback to the simg2img binary when firmware_parsers is
not installed. The simg2img tool requirement is also conditionally
removed from REQUIRED_TOOLS in dumpyara.py.
Add Rust extractors for NB0, PAC, and MTK signed image formats and expose them through the PyO3 module.
Update format detection to recognize PAC and NB0 files directly and inspect zip archives for MTK signed image payloads.
Implement Phases 3-6 of the firmware-parsers plan:
- Phase 3: OPPO ozip (AES-128-ECB decrypt, 35 keys, mode-1 direct and
mode-2 zip-wrapped with inner .ozip entry decryption) and Sony SIN/FTF
(v3/v4/v5 + legacy SSSS/BFBF + Sony sparse chunks 0xCAC1-0xCAC5)
- Phase 4: Amlogic USB burning tool and Rockchip RKFW/AFP containers
- Phase 5: QFIL rawprogram XML, ZTE update.zip, KDDI .bin extractors
- Phase 6: Wire firmware_parsers.detect() into extract_archive.py with
graceful fallback to shutil.unpack_archive on failure
Also fix issues found in review of Phase 1-2 code:
- Remove "sparse" from detect() returns (no matching Python function)
- Strengthen NB0 probe with printable-ASCII filename validation
- Validate BFBF/MTK header magic before 0x4040 byte strip
- Sanitize output filenames in nb0/pac/amlogic/rockchip/qfil (path traversal)
- Fix SIN tar entry processing (per-entry instead of concatenation)
- Use static OnceLock<Regex> in ZTE/KDDI instead of per-call compilation
- Expand partition name whitelist for ZTE/KDDI bin detection
Format-specific fixes:
- OZIP: handle PK-wrapped mode-2 inputs, decrypt inner .ozip entries,
strip .ozip extension so downstream recognizes decrypted payloads;
preserve decrypted filename for non-zip payloads (e.g.
system.new.dat.br.ozip -> system.new.dat.br, not .br.img);
use temp work directory for zip extraction to avoid leaked temp files;
propagate decryption failure instead of silently renaming ciphertext
- QFIL: group <program> entries by label to merge sparsechunks instead
of overwriting; resolve filenames by basename for flattened layouts;
sanitize XML labels against path traversal; honor file_sector_offset
when rebuilding partitions from shared backing files; branch on
all_same_file without requiring non-zero first offset
- Amlogic: detect tar.bz2 wrappers by content (BZh magic) instead of
filename extension; verify Amlogic magic before accepting tar entries
to avoid selecting non-Amlogic .img files that precede the payload
- SIN: stream-scan for gzip/tar offset instead of reading entire file
into memory (avoids OOM on multi-GB files); strip .sin extension
case-insensitively to match case-insensitive FTF detection
- detect.rs: scan all tar.bz2 members for Amlogic magic (not just
the first entry, since packages may have readme/manifest first);
tighten bzip2 check from 2-byte "BZ" to 3-byte "BZh"
Housekeeping:
- Remove unused Cargo dependencies (thiserror, memmap2, bytemuck, byteorder)
Rockchip RKFW header uses packed/unaligned fields:
- AFP container offset at 0x21 (not 0x1C)
- AFP container size at 0x25 (not 0x20)
AFP container structure corrected:
- AFP header is 0x8C bytes (not 0x4C)
- entry_count at offset 0x88 (not 0x44)
- Each entry is 0x70 / 112 bytes (not 0x48 / 72)
- Entry offset at 0x60, size at 0x6C (not 0x20/0x28)
When a firmware ships both a real partition and its alias (e.g. modem.img
alongside NON-HLOS.img on recent Oppo/ColorOS builds), fix_aliases logged
"Ignoring <alias> (<name> already extracted)" and unlinked the alias, but
then fell through and still attempted move(alias, partition). Since the
alias was just deleted, this raised FileNotFoundError and aborted the dump.
Add the missing continue so the redundant alias is dropped and the real
partition is left untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Some firmware (e.g. aftermarket car head units) wrap the real dumpable ROM
one zip deeper -- the outer archive holds vendor blobs and APK dirs, while
the actual block-based OTA (system.new.dat.br + *.transfer.list, payload.bin,
super*.img, *.tar.md5) lives inside an inner .zip such as update_car.zip.
dumpyara only scanned the outer archive's top level, found no recognized
partition container, and aborted with "System folder doesn't exist".
Extend nested-archive handling: for each top-level .zip not already covered
by NESTED_ARCHIVES, peek its central directory (zipfile namelist, no
extraction) and recurse only when it contains a partition marker. The marker
patterns are anchored to a path boundary and the *.new.dat.br / *.transfer.list
markers are restricted to known partition names, so APK/config zips (which
carry none of these) are never exploded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Fixes dumping OPPO and/or OnePlus stock firmwares that use Chinese
characters for app paths
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Define the Ruff rule set.
Add type information for the optional parser module.
Resolve the fallback executable paths.
Use PyO3 0.29 for Python 3.14.
Apply the standard Python and Rust formats.
akhilnarangand others added 5 commits August 16, 2026 23:03
The extractor did not extract a nested image zip when the zip held loose
raw partition images. Such a zip has no super image and no payload file.
Pixel factory images use this layout. They ship system.img, vendor.img,
and product.img directly.
The marker check missed these files. Therefore the extractor skipped the
nested zip. The system partition did not reach the output. The system
folder assertion then failed.
Add a marker pattern for loose raw partition images. Anchor the pattern
on the known partition names. Allow an optional A/B slot suffix. Allow
the image file extensions that get_raw_image accepts. The pattern does
not match super_empty.img. The pattern does not match an unrelated image
file.
Derive the partition names from get_partition_names_with_alias. Add the
return type to two partition helper functions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The vendored Python payload parser is single-threaded, iterates
partitions and operations serially, and shells out to xzcat/bzcat
once per operation. On a 3.5 GB Pixel foldable OTA (grizzly, ~40
partitions), that hits our 1-hour bot timeout; running otadump on
the same input finishes in ~1-2 min.
Use the otadump binary when it's on PATH (Rayon-parallel per
partition, in-process zstd/xz/bzip2), fall back to the vendored
parser otherwise. Stage otadump's output in a sibling tempdir and
move on success so a partial failure doesn't leave stray images
next to payload.bin.
Fallback triggers only on binary-absent, not on error — the
Python impl is less strict about hash verification and silently
falling back could hide corruption.
Since Xiaomi HyperOS 4, Xiaomi added new partition called "mi_product". For now it's quite empty, but it might change in the future.
Sign up for freeto 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.

4 participants

@akhilnarang@deadman96385@muhammad23012009@kacskrz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Add support for everything Firmware_extractor supported - #146

Open
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master
Open

Add support for everything Firmware_extractor supported#146
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master

Conversation

@akhilnarang

@akhilnarangakhilnarang commented Jun 28, 2026

Copy link
Copy Markdown

Summary

This change adds a Rust parser module for Android firmware files. PyO3 provides the parser functions to Python.

Firmware formats

The module adds these parsers:

  • Android sparse images and sparse image chunks
  • Amlogic firmware
  • KDDI firmware
  • MediaTek signed images
  • Nokia NB0 files
  • Oppo OZIP files
  • Spreadtrum PAC files
  • Qualcomm QFIL files
  • Rockchip RKFW and AFP files
  • Sony SIN and FTF files
  • ZTE firmware

Extraction changes

  • Detect a known firmware format before generic archive extraction.
  • Detect nested firmware files after archive extraction.
  • Use ZIP extraction for archives that have a nonstandard file extension.
  • Remove a common vendor prefix from extracted file names.
  • Use the Rust sparse-image parser when the module is available.
  • Use simg2img when the Rust module is not available.
  • Search nested ZIP files for partition markers.
  • Keep a canonical partition when an alias also exists.
  • Accept partition files that use the .bin suffix.
  • Write all_files.txt with UTF-8 encoding.

Compatibility and checks

  • Use PyO3 0.29.
  • Support Python 3.14.
  • Add type information for the optional parser module.
  • Define the Ruff lint rules for stable CI results.
  • Fix the issues that Codacy reported.
  • Apply the standard Python and Rust formats.

The following checks pass:

  • Origin ruff-check
  • Origin ruff-format
  • Origin pyright
  • Upstream Codacy analysis
  • python -m compileall -q dumpyara
  • cargo fmt --manifest-path firmware-parsers/Cargo.toml -- --check
  • cargo check --locked --manifest-path firmware-parsers/Cargo.toml
  • cargo test --locked --manifest-path firmware-parsers/Cargo.toml

@codacy-production

codacy-productionBot commented Jun 28, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues1 minor

Alerts:
⚠ 1 issue (≤ 0 issues of at least minor severity)

Results:
1 new issue

CategoryResults
Security1 minor

View in Codacy

🟢 Metrics629 complexity · 70 duplication

MetricResults
Complexity629
Duplication70

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@akhilnarang
akhilnarangforce-pushed the master branch 3 times, most recently from ef73a12 to b5195ceCompareJuly 24, 2026 06:41
deadman96385and others added 13 commits August 16, 2026 22:44
Add a new Rust + PyO3 native extension (firmware-parsers/) that provides
Android sparse image conversion, replacing the external simg2img binary
dependency. The crate exposes sparse_to_raw, sparse_chunks_to_raw, and
is_sparse functions to Python via the firmware_parsers module.
All simg2img subprocess calls in multipartitions.py, raw_image.py, and
sparsed_images.py now use the native Rust implementation when available,
with graceful fallback to the simg2img binary when firmware_parsers is
not installed. The simg2img tool requirement is also conditionally
removed from REQUIRED_TOOLS in dumpyara.py.
Add Rust extractors for NB0, PAC, and MTK signed image formats and expose them through the PyO3 module.
Update format detection to recognize PAC and NB0 files directly and inspect zip archives for MTK signed image payloads.
Implement Phases 3-6 of the firmware-parsers plan:
- Phase 3: OPPO ozip (AES-128-ECB decrypt, 35 keys, mode-1 direct and
mode-2 zip-wrapped with inner .ozip entry decryption) and Sony SIN/FTF
(v3/v4/v5 + legacy SSSS/BFBF + Sony sparse chunks 0xCAC1-0xCAC5)
- Phase 4: Amlogic USB burning tool and Rockchip RKFW/AFP containers
- Phase 5: QFIL rawprogram XML, ZTE update.zip, KDDI .bin extractors
- Phase 6: Wire firmware_parsers.detect() into extract_archive.py with
graceful fallback to shutil.unpack_archive on failure
Also fix issues found in review of Phase 1-2 code:
- Remove "sparse" from detect() returns (no matching Python function)
- Strengthen NB0 probe with printable-ASCII filename validation
- Validate BFBF/MTK header magic before 0x4040 byte strip
- Sanitize output filenames in nb0/pac/amlogic/rockchip/qfil (path traversal)
- Fix SIN tar entry processing (per-entry instead of concatenation)
- Use static OnceLock<Regex> in ZTE/KDDI instead of per-call compilation
- Expand partition name whitelist for ZTE/KDDI bin detection
Format-specific fixes:
- OZIP: handle PK-wrapped mode-2 inputs, decrypt inner .ozip entries,
strip .ozip extension so downstream recognizes decrypted payloads;
preserve decrypted filename for non-zip payloads (e.g.
system.new.dat.br.ozip -> system.new.dat.br, not .br.img);
use temp work directory for zip extraction to avoid leaked temp files;
propagate decryption failure instead of silently renaming ciphertext
- QFIL: group <program> entries by label to merge sparsechunks instead
of overwriting; resolve filenames by basename for flattened layouts;
sanitize XML labels against path traversal; honor file_sector_offset
when rebuilding partitions from shared backing files; branch on
all_same_file without requiring non-zero first offset
- Amlogic: detect tar.bz2 wrappers by content (BZh magic) instead of
filename extension; verify Amlogic magic before accepting tar entries
to avoid selecting non-Amlogic .img files that precede the payload
- SIN: stream-scan for gzip/tar offset instead of reading entire file
into memory (avoids OOM on multi-GB files); strip .sin extension
case-insensitively to match case-insensitive FTF detection
- detect.rs: scan all tar.bz2 members for Amlogic magic (not just
the first entry, since packages may have readme/manifest first);
tighten bzip2 check from 2-byte "BZ" to 3-byte "BZh"
Housekeeping:
- Remove unused Cargo dependencies (thiserror, memmap2, bytemuck, byteorder)
Rockchip RKFW header uses packed/unaligned fields:
- AFP container offset at 0x21 (not 0x1C)
- AFP container size at 0x25 (not 0x20)
AFP container structure corrected:
- AFP header is 0x8C bytes (not 0x4C)
- entry_count at offset 0x88 (not 0x44)
- Each entry is 0x70 / 112 bytes (not 0x48 / 72)
- Entry offset at 0x60, size at 0x6C (not 0x20/0x28)
When a firmware ships both a real partition and its alias (e.g. modem.img
alongside NON-HLOS.img on recent Oppo/ColorOS builds), fix_aliases logged
"Ignoring <alias> (<name> already extracted)" and unlinked the alias, but
then fell through and still attempted move(alias, partition). Since the
alias was just deleted, this raised FileNotFoundError and aborted the dump.
Add the missing continue so the redundant alias is dropped and the real
partition is left untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Some firmware (e.g. aftermarket car head units) wrap the real dumpable ROM
one zip deeper -- the outer archive holds vendor blobs and APK dirs, while
the actual block-based OTA (system.new.dat.br + *.transfer.list, payload.bin,
super*.img, *.tar.md5) lives inside an inner .zip such as update_car.zip.
dumpyara only scanned the outer archive's top level, found no recognized
partition container, and aborted with "System folder doesn't exist".
Extend nested-archive handling: for each top-level .zip not already covered
by NESTED_ARCHIVES, peek its central directory (zipfile namelist, no
extraction) and recurse only when it contains a partition marker. The marker
patterns are anchored to a path boundary and the *.new.dat.br / *.transfer.list
markers are restricted to known partition names, so APK/config zips (which
carry none of these) are never exploded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Fixes dumping OPPO and/or OnePlus stock firmwares that use Chinese
characters for app paths
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Define the Ruff rule set.
Add type information for the optional parser module.
Resolve the fallback executable paths.
Use PyO3 0.29 for Python 3.14.
Apply the standard Python and Rust formats.
akhilnarangand others added 5 commits August 16, 2026 23:03
The extractor did not extract a nested image zip when the zip held loose
raw partition images. Such a zip has no super image and no payload file.
Pixel factory images use this layout. They ship system.img, vendor.img,
and product.img directly.
The marker check missed these files. Therefore the extractor skipped the
nested zip. The system partition did not reach the output. The system
folder assertion then failed.
Add a marker pattern for loose raw partition images. Anchor the pattern
on the known partition names. Allow an optional A/B slot suffix. Allow
the image file extensions that get_raw_image accepts. The pattern does
not match super_empty.img. The pattern does not match an unrelated image
file.
Derive the partition names from get_partition_names_with_alias. Add the
return type to two partition helper functions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The vendored Python payload parser is single-threaded, iterates
partitions and operations serially, and shells out to xzcat/bzcat
once per operation. On a 3.5 GB Pixel foldable OTA (grizzly, ~40
partitions), that hits our 1-hour bot timeout; running otadump on
the same input finishes in ~1-2 min.
Use the otadump binary when it's on PATH (Rayon-parallel per
partition, in-process zstd/xz/bzip2), fall back to the vendored
parser otherwise. Stage otadump's output in a sibling tempdir and
move on success so a partial failure doesn't leave stray images
next to payload.bin.
Fallback triggers only on binary-absent, not on error — the
Python impl is less strict about hash verification and silently
falling back could hide corruption.
Since Xiaomi HyperOS 4, Xiaomi added new partition called "mi_product". For now it's quite empty, but it might change in the future.
Sign up for freeto 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.

4 participants

@akhilnarang@deadman96385@muhammad23012009@kacskrz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add support for everything Firmware_extractor supported - #146

Open
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master
Open

Add support for everything Firmware_extractor supported#146
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master

Conversation

@akhilnarang

@akhilnarangakhilnarang commented Jun 28, 2026

Copy link
Copy Markdown

Summary

This change adds a Rust parser module for Android firmware files. PyO3 provides the parser functions to Python.

Firmware formats

The module adds these parsers:

  • Android sparse images and sparse image chunks
  • Amlogic firmware
  • KDDI firmware
  • MediaTek signed images
  • Nokia NB0 files
  • Oppo OZIP files
  • Spreadtrum PAC files
  • Qualcomm QFIL files
  • Rockchip RKFW and AFP files
  • Sony SIN and FTF files
  • ZTE firmware

Extraction changes

  • Detect a known firmware format before generic archive extraction.
  • Detect nested firmware files after archive extraction.
  • Use ZIP extraction for archives that have a nonstandard file extension.
  • Remove a common vendor prefix from extracted file names.
  • Use the Rust sparse-image parser when the module is available.
  • Use simg2img when the Rust module is not available.
  • Search nested ZIP files for partition markers.
  • Keep a canonical partition when an alias also exists.
  • Accept partition files that use the .bin suffix.
  • Write all_files.txt with UTF-8 encoding.

Compatibility and checks

  • Use PyO3 0.29.
  • Support Python 3.14.
  • Add type information for the optional parser module.
  • Define the Ruff lint rules for stable CI results.
  • Fix the issues that Codacy reported.
  • Apply the standard Python and Rust formats.

The following checks pass:

  • Origin ruff-check
  • Origin ruff-format
  • Origin pyright
  • Upstream Codacy analysis
  • python -m compileall -q dumpyara
  • cargo fmt --manifest-path firmware-parsers/Cargo.toml -- --check
  • cargo check --locked --manifest-path firmware-parsers/Cargo.toml
  • cargo test --locked --manifest-path firmware-parsers/Cargo.toml

@codacy-production

codacy-productionBot commented Jun 28, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues1 minor

Alerts:
⚠ 1 issue (≤ 0 issues of at least minor severity)

Results:
1 new issue

CategoryResults
Security1 minor

View in Codacy

🟢 Metrics629 complexity · 70 duplication

MetricResults
Complexity629
Duplication70

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@akhilnarang
akhilnarangforce-pushed the master branch 3 times, most recently from ef73a12 to b5195ceCompareJuly 24, 2026 06:41
deadman96385and others added 13 commits August 16, 2026 22:44
Add a new Rust + PyO3 native extension (firmware-parsers/) that provides
Android sparse image conversion, replacing the external simg2img binary
dependency. The crate exposes sparse_to_raw, sparse_chunks_to_raw, and
is_sparse functions to Python via the firmware_parsers module.
All simg2img subprocess calls in multipartitions.py, raw_image.py, and
sparsed_images.py now use the native Rust implementation when available,
with graceful fallback to the simg2img binary when firmware_parsers is
not installed. The simg2img tool requirement is also conditionally
removed from REQUIRED_TOOLS in dumpyara.py.
Add Rust extractors for NB0, PAC, and MTK signed image formats and expose them through the PyO3 module.
Update format detection to recognize PAC and NB0 files directly and inspect zip archives for MTK signed image payloads.
Implement Phases 3-6 of the firmware-parsers plan:
- Phase 3: OPPO ozip (AES-128-ECB decrypt, 35 keys, mode-1 direct and
mode-2 zip-wrapped with inner .ozip entry decryption) and Sony SIN/FTF
(v3/v4/v5 + legacy SSSS/BFBF + Sony sparse chunks 0xCAC1-0xCAC5)
- Phase 4: Amlogic USB burning tool and Rockchip RKFW/AFP containers
- Phase 5: QFIL rawprogram XML, ZTE update.zip, KDDI .bin extractors
- Phase 6: Wire firmware_parsers.detect() into extract_archive.py with
graceful fallback to shutil.unpack_archive on failure
Also fix issues found in review of Phase 1-2 code:
- Remove "sparse" from detect() returns (no matching Python function)
- Strengthen NB0 probe with printable-ASCII filename validation
- Validate BFBF/MTK header magic before 0x4040 byte strip
- Sanitize output filenames in nb0/pac/amlogic/rockchip/qfil (path traversal)
- Fix SIN tar entry processing (per-entry instead of concatenation)
- Use static OnceLock<Regex> in ZTE/KDDI instead of per-call compilation
- Expand partition name whitelist for ZTE/KDDI bin detection
Format-specific fixes:
- OZIP: handle PK-wrapped mode-2 inputs, decrypt inner .ozip entries,
strip .ozip extension so downstream recognizes decrypted payloads;
preserve decrypted filename for non-zip payloads (e.g.
system.new.dat.br.ozip -> system.new.dat.br, not .br.img);
use temp work directory for zip extraction to avoid leaked temp files;
propagate decryption failure instead of silently renaming ciphertext
- QFIL: group <program> entries by label to merge sparsechunks instead
of overwriting; resolve filenames by basename for flattened layouts;
sanitize XML labels against path traversal; honor file_sector_offset
when rebuilding partitions from shared backing files; branch on
all_same_file without requiring non-zero first offset
- Amlogic: detect tar.bz2 wrappers by content (BZh magic) instead of
filename extension; verify Amlogic magic before accepting tar entries
to avoid selecting non-Amlogic .img files that precede the payload
- SIN: stream-scan for gzip/tar offset instead of reading entire file
into memory (avoids OOM on multi-GB files); strip .sin extension
case-insensitively to match case-insensitive FTF detection
- detect.rs: scan all tar.bz2 members for Amlogic magic (not just
the first entry, since packages may have readme/manifest first);
tighten bzip2 check from 2-byte "BZ" to 3-byte "BZh"
Housekeeping:
- Remove unused Cargo dependencies (thiserror, memmap2, bytemuck, byteorder)
Rockchip RKFW header uses packed/unaligned fields:
- AFP container offset at 0x21 (not 0x1C)
- AFP container size at 0x25 (not 0x20)
AFP container structure corrected:
- AFP header is 0x8C bytes (not 0x4C)
- entry_count at offset 0x88 (not 0x44)
- Each entry is 0x70 / 112 bytes (not 0x48 / 72)
- Entry offset at 0x60, size at 0x6C (not 0x20/0x28)
When a firmware ships both a real partition and its alias (e.g. modem.img
alongside NON-HLOS.img on recent Oppo/ColorOS builds), fix_aliases logged
"Ignoring <alias> (<name> already extracted)" and unlinked the alias, but
then fell through and still attempted move(alias, partition). Since the
alias was just deleted, this raised FileNotFoundError and aborted the dump.
Add the missing continue so the redundant alias is dropped and the real
partition is left untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Some firmware (e.g. aftermarket car head units) wrap the real dumpable ROM
one zip deeper -- the outer archive holds vendor blobs and APK dirs, while
the actual block-based OTA (system.new.dat.br + *.transfer.list, payload.bin,
super*.img, *.tar.md5) lives inside an inner .zip such as update_car.zip.
dumpyara only scanned the outer archive's top level, found no recognized
partition container, and aborted with "System folder doesn't exist".
Extend nested-archive handling: for each top-level .zip not already covered
by NESTED_ARCHIVES, peek its central directory (zipfile namelist, no
extraction) and recurse only when it contains a partition marker. The marker
patterns are anchored to a path boundary and the *.new.dat.br / *.transfer.list
markers are restricted to known partition names, so APK/config zips (which
carry none of these) are never exploded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Fixes dumping OPPO and/or OnePlus stock firmwares that use Chinese
characters for app paths
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Define the Ruff rule set.
Add type information for the optional parser module.
Resolve the fallback executable paths.
Use PyO3 0.29 for Python 3.14.
Apply the standard Python and Rust formats.
akhilnarangand others added 5 commits August 16, 2026 23:03
The extractor did not extract a nested image zip when the zip held loose
raw partition images. Such a zip has no super image and no payload file.
Pixel factory images use this layout. They ship system.img, vendor.img,
and product.img directly.
The marker check missed these files. Therefore the extractor skipped the
nested zip. The system partition did not reach the output. The system
folder assertion then failed.
Add a marker pattern for loose raw partition images. Anchor the pattern
on the known partition names. Allow an optional A/B slot suffix. Allow
the image file extensions that get_raw_image accepts. The pattern does
not match super_empty.img. The pattern does not match an unrelated image
file.
Derive the partition names from get_partition_names_with_alias. Add the
return type to two partition helper functions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The vendored Python payload parser is single-threaded, iterates
partitions and operations serially, and shells out to xzcat/bzcat
once per operation. On a 3.5 GB Pixel foldable OTA (grizzly, ~40
partitions), that hits our 1-hour bot timeout; running otadump on
the same input finishes in ~1-2 min.
Use the otadump binary when it's on PATH (Rayon-parallel per
partition, in-process zstd/xz/bzip2), fall back to the vendored
parser otherwise. Stage otadump's output in a sibling tempdir and
move on success so a partial failure doesn't leave stray images
next to payload.bin.
Fallback triggers only on binary-absent, not on error — the
Python impl is less strict about hash verification and silently
falling back could hide corruption.
Since Xiaomi HyperOS 4, Xiaomi added new partition called "mi_product". For now it's quite empty, but it might change in the future.
Sign up for freeto 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.

4 participants

@akhilnarang@deadman96385@muhammad23012009@kacskrz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add support for everything Firmware_extractor supported - #146

Open
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master
Open

Add support for everything Firmware_extractor supported#146
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master

Conversation

@akhilnarang

@akhilnarangakhilnarang commented Jun 28, 2026

Copy link
Copy Markdown

Summary

This change adds a Rust parser module for Android firmware files. PyO3 provides the parser functions to Python.

Firmware formats

The module adds these parsers:

  • Android sparse images and sparse image chunks
  • Amlogic firmware
  • KDDI firmware
  • MediaTek signed images
  • Nokia NB0 files
  • Oppo OZIP files
  • Spreadtrum PAC files
  • Qualcomm QFIL files
  • Rockchip RKFW and AFP files
  • Sony SIN and FTF files
  • ZTE firmware

Extraction changes

  • Detect a known firmware format before generic archive extraction.
  • Detect nested firmware files after archive extraction.
  • Use ZIP extraction for archives that have a nonstandard file extension.
  • Remove a common vendor prefix from extracted file names.
  • Use the Rust sparse-image parser when the module is available.
  • Use simg2img when the Rust module is not available.
  • Search nested ZIP files for partition markers.
  • Keep a canonical partition when an alias also exists.
  • Accept partition files that use the .bin suffix.
  • Write all_files.txt with UTF-8 encoding.

Compatibility and checks

  • Use PyO3 0.29.
  • Support Python 3.14.
  • Add type information for the optional parser module.
  • Define the Ruff lint rules for stable CI results.
  • Fix the issues that Codacy reported.
  • Apply the standard Python and Rust formats.

The following checks pass:

  • Origin ruff-check
  • Origin ruff-format
  • Origin pyright
  • Upstream Codacy analysis
  • python -m compileall -q dumpyara
  • cargo fmt --manifest-path firmware-parsers/Cargo.toml -- --check
  • cargo check --locked --manifest-path firmware-parsers/Cargo.toml
  • cargo test --locked --manifest-path firmware-parsers/Cargo.toml

@codacy-production

codacy-productionBot commented Jun 28, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues1 minor

Alerts:
⚠ 1 issue (≤ 0 issues of at least minor severity)

Results:
1 new issue

CategoryResults
Security1 minor

View in Codacy

🟢 Metrics629 complexity · 70 duplication

MetricResults
Complexity629
Duplication70

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@akhilnarang
akhilnarangforce-pushed the master branch 3 times, most recently from ef73a12 to b5195ceCompareJuly 24, 2026 06:41
deadman96385and others added 13 commits August 16, 2026 22:44
Add a new Rust + PyO3 native extension (firmware-parsers/) that provides
Android sparse image conversion, replacing the external simg2img binary
dependency. The crate exposes sparse_to_raw, sparse_chunks_to_raw, and
is_sparse functions to Python via the firmware_parsers module.
All simg2img subprocess calls in multipartitions.py, raw_image.py, and
sparsed_images.py now use the native Rust implementation when available,
with graceful fallback to the simg2img binary when firmware_parsers is
not installed. The simg2img tool requirement is also conditionally
removed from REQUIRED_TOOLS in dumpyara.py.
Add Rust extractors for NB0, PAC, and MTK signed image formats and expose them through the PyO3 module.
Update format detection to recognize PAC and NB0 files directly and inspect zip archives for MTK signed image payloads.
Implement Phases 3-6 of the firmware-parsers plan:
- Phase 3: OPPO ozip (AES-128-ECB decrypt, 35 keys, mode-1 direct and
mode-2 zip-wrapped with inner .ozip entry decryption) and Sony SIN/FTF
(v3/v4/v5 + legacy SSSS/BFBF + Sony sparse chunks 0xCAC1-0xCAC5)
- Phase 4: Amlogic USB burning tool and Rockchip RKFW/AFP containers
- Phase 5: QFIL rawprogram XML, ZTE update.zip, KDDI .bin extractors
- Phase 6: Wire firmware_parsers.detect() into extract_archive.py with
graceful fallback to shutil.unpack_archive on failure
Also fix issues found in review of Phase 1-2 code:
- Remove "sparse" from detect() returns (no matching Python function)
- Strengthen NB0 probe with printable-ASCII filename validation
- Validate BFBF/MTK header magic before 0x4040 byte strip
- Sanitize output filenames in nb0/pac/amlogic/rockchip/qfil (path traversal)
- Fix SIN tar entry processing (per-entry instead of concatenation)
- Use static OnceLock<Regex> in ZTE/KDDI instead of per-call compilation
- Expand partition name whitelist for ZTE/KDDI bin detection
Format-specific fixes:
- OZIP: handle PK-wrapped mode-2 inputs, decrypt inner .ozip entries,
strip .ozip extension so downstream recognizes decrypted payloads;
preserve decrypted filename for non-zip payloads (e.g.
system.new.dat.br.ozip -> system.new.dat.br, not .br.img);
use temp work directory for zip extraction to avoid leaked temp files;
propagate decryption failure instead of silently renaming ciphertext
- QFIL: group <program> entries by label to merge sparsechunks instead
of overwriting; resolve filenames by basename for flattened layouts;
sanitize XML labels against path traversal; honor file_sector_offset
when rebuilding partitions from shared backing files; branch on
all_same_file without requiring non-zero first offset
- Amlogic: detect tar.bz2 wrappers by content (BZh magic) instead of
filename extension; verify Amlogic magic before accepting tar entries
to avoid selecting non-Amlogic .img files that precede the payload
- SIN: stream-scan for gzip/tar offset instead of reading entire file
into memory (avoids OOM on multi-GB files); strip .sin extension
case-insensitively to match case-insensitive FTF detection
- detect.rs: scan all tar.bz2 members for Amlogic magic (not just
the first entry, since packages may have readme/manifest first);
tighten bzip2 check from 2-byte "BZ" to 3-byte "BZh"
Housekeeping:
- Remove unused Cargo dependencies (thiserror, memmap2, bytemuck, byteorder)
Rockchip RKFW header uses packed/unaligned fields:
- AFP container offset at 0x21 (not 0x1C)
- AFP container size at 0x25 (not 0x20)
AFP container structure corrected:
- AFP header is 0x8C bytes (not 0x4C)
- entry_count at offset 0x88 (not 0x44)
- Each entry is 0x70 / 112 bytes (not 0x48 / 72)
- Entry offset at 0x60, size at 0x6C (not 0x20/0x28)
When a firmware ships both a real partition and its alias (e.g. modem.img
alongside NON-HLOS.img on recent Oppo/ColorOS builds), fix_aliases logged
"Ignoring <alias> (<name> already extracted)" and unlinked the alias, but
then fell through and still attempted move(alias, partition). Since the
alias was just deleted, this raised FileNotFoundError and aborted the dump.
Add the missing continue so the redundant alias is dropped and the real
partition is left untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Some firmware (e.g. aftermarket car head units) wrap the real dumpable ROM
one zip deeper -- the outer archive holds vendor blobs and APK dirs, while
the actual block-based OTA (system.new.dat.br + *.transfer.list, payload.bin,
super*.img, *.tar.md5) lives inside an inner .zip such as update_car.zip.
dumpyara only scanned the outer archive's top level, found no recognized
partition container, and aborted with "System folder doesn't exist".
Extend nested-archive handling: for each top-level .zip not already covered
by NESTED_ARCHIVES, peek its central directory (zipfile namelist, no
extraction) and recurse only when it contains a partition marker. The marker
patterns are anchored to a path boundary and the *.new.dat.br / *.transfer.list
markers are restricted to known partition names, so APK/config zips (which
carry none of these) are never exploded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Fixes dumping OPPO and/or OnePlus stock firmwares that use Chinese
characters for app paths
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Define the Ruff rule set.
Add type information for the optional parser module.
Resolve the fallback executable paths.
Use PyO3 0.29 for Python 3.14.
Apply the standard Python and Rust formats.
akhilnarangand others added 5 commits August 16, 2026 23:03
The extractor did not extract a nested image zip when the zip held loose
raw partition images. Such a zip has no super image and no payload file.
Pixel factory images use this layout. They ship system.img, vendor.img,
and product.img directly.
The marker check missed these files. Therefore the extractor skipped the
nested zip. The system partition did not reach the output. The system
folder assertion then failed.
Add a marker pattern for loose raw partition images. Anchor the pattern
on the known partition names. Allow an optional A/B slot suffix. Allow
the image file extensions that get_raw_image accepts. The pattern does
not match super_empty.img. The pattern does not match an unrelated image
file.
Derive the partition names from get_partition_names_with_alias. Add the
return type to two partition helper functions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The vendored Python payload parser is single-threaded, iterates
partitions and operations serially, and shells out to xzcat/bzcat
once per operation. On a 3.5 GB Pixel foldable OTA (grizzly, ~40
partitions), that hits our 1-hour bot timeout; running otadump on
the same input finishes in ~1-2 min.
Use the otadump binary when it's on PATH (Rayon-parallel per
partition, in-process zstd/xz/bzip2), fall back to the vendored
parser otherwise. Stage otadump's output in a sibling tempdir and
move on success so a partial failure doesn't leave stray images
next to payload.bin.
Fallback triggers only on binary-absent, not on error — the
Python impl is less strict about hash verification and silently
falling back could hide corruption.
Since Xiaomi HyperOS 4, Xiaomi added new partition called "mi_product". For now it's quite empty, but it might change in the future.
Sign up for freeto 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.

4 participants

@akhilnarang@deadman96385@muhammad23012009@kacskrz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Add support for everything Firmware_extractor supported - #146

Open
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master
Open

Add support for everything Firmware_extractor supported#146
akhilnarang wants to merge 18 commits into
sebaubuntu-python:masterfrom
AndroidDumps:master

Conversation

@akhilnarang

@akhilnarangakhilnarang commented Jun 28, 2026

Copy link
Copy Markdown

Summary

This change adds a Rust parser module for Android firmware files. PyO3 provides the parser functions to Python.

Firmware formats

The module adds these parsers:

  • Android sparse images and sparse image chunks
  • Amlogic firmware
  • KDDI firmware
  • MediaTek signed images
  • Nokia NB0 files
  • Oppo OZIP files
  • Spreadtrum PAC files
  • Qualcomm QFIL files
  • Rockchip RKFW and AFP files
  • Sony SIN and FTF files
  • ZTE firmware

Extraction changes

  • Detect a known firmware format before generic archive extraction.
  • Detect nested firmware files after archive extraction.
  • Use ZIP extraction for archives that have a nonstandard file extension.
  • Remove a common vendor prefix from extracted file names.
  • Use the Rust sparse-image parser when the module is available.
  • Use simg2img when the Rust module is not available.
  • Search nested ZIP files for partition markers.
  • Keep a canonical partition when an alias also exists.
  • Accept partition files that use the .bin suffix.
  • Write all_files.txt with UTF-8 encoding.

Compatibility and checks

  • Use PyO3 0.29.
  • Support Python 3.14.
  • Add type information for the optional parser module.
  • Define the Ruff lint rules for stable CI results.
  • Fix the issues that Codacy reported.
  • Apply the standard Python and Rust formats.

The following checks pass:

  • Origin ruff-check
  • Origin ruff-format
  • Origin pyright
  • Upstream Codacy analysis
  • python -m compileall -q dumpyara
  • cargo fmt --manifest-path firmware-parsers/Cargo.toml -- --check
  • cargo check --locked --manifest-path firmware-parsers/Cargo.toml
  • cargo test --locked --manifest-path firmware-parsers/Cargo.toml

@codacy-production

codacy-productionBot commented Jun 28, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues1 minor

Alerts:
⚠ 1 issue (≤ 0 issues of at least minor severity)

Results:
1 new issue

CategoryResults
Security1 minor

View in Codacy

🟢 Metrics629 complexity · 70 duplication

MetricResults
Complexity629
Duplication70

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@akhilnarang
akhilnarangforce-pushed the master branch 3 times, most recently from ef73a12 to b5195ceCompareJuly 24, 2026 06:41
deadman96385and others added 13 commits August 16, 2026 22:44
Add a new Rust + PyO3 native extension (firmware-parsers/) that provides
Android sparse image conversion, replacing the external simg2img binary
dependency. The crate exposes sparse_to_raw, sparse_chunks_to_raw, and
is_sparse functions to Python via the firmware_parsers module.
All simg2img subprocess calls in multipartitions.py, raw_image.py, and
sparsed_images.py now use the native Rust implementation when available,
with graceful fallback to the simg2img binary when firmware_parsers is
not installed. The simg2img tool requirement is also conditionally
removed from REQUIRED_TOOLS in dumpyara.py.
Add Rust extractors for NB0, PAC, and MTK signed image formats and expose them through the PyO3 module.
Update format detection to recognize PAC and NB0 files directly and inspect zip archives for MTK signed image payloads.
Implement Phases 3-6 of the firmware-parsers plan:
- Phase 3: OPPO ozip (AES-128-ECB decrypt, 35 keys, mode-1 direct and
mode-2 zip-wrapped with inner .ozip entry decryption) and Sony SIN/FTF
(v3/v4/v5 + legacy SSSS/BFBF + Sony sparse chunks 0xCAC1-0xCAC5)
- Phase 4: Amlogic USB burning tool and Rockchip RKFW/AFP containers
- Phase 5: QFIL rawprogram XML, ZTE update.zip, KDDI .bin extractors
- Phase 6: Wire firmware_parsers.detect() into extract_archive.py with
graceful fallback to shutil.unpack_archive on failure
Also fix issues found in review of Phase 1-2 code:
- Remove "sparse" from detect() returns (no matching Python function)
- Strengthen NB0 probe with printable-ASCII filename validation
- Validate BFBF/MTK header magic before 0x4040 byte strip
- Sanitize output filenames in nb0/pac/amlogic/rockchip/qfil (path traversal)
- Fix SIN tar entry processing (per-entry instead of concatenation)
- Use static OnceLock<Regex> in ZTE/KDDI instead of per-call compilation
- Expand partition name whitelist for ZTE/KDDI bin detection
Format-specific fixes:
- OZIP: handle PK-wrapped mode-2 inputs, decrypt inner .ozip entries,
strip .ozip extension so downstream recognizes decrypted payloads;
preserve decrypted filename for non-zip payloads (e.g.
system.new.dat.br.ozip -> system.new.dat.br, not .br.img);
use temp work directory for zip extraction to avoid leaked temp files;
propagate decryption failure instead of silently renaming ciphertext
- QFIL: group <program> entries by label to merge sparsechunks instead
of overwriting; resolve filenames by basename for flattened layouts;
sanitize XML labels against path traversal; honor file_sector_offset
when rebuilding partitions from shared backing files; branch on
all_same_file without requiring non-zero first offset
- Amlogic: detect tar.bz2 wrappers by content (BZh magic) instead of
filename extension; verify Amlogic magic before accepting tar entries
to avoid selecting non-Amlogic .img files that precede the payload
- SIN: stream-scan for gzip/tar offset instead of reading entire file
into memory (avoids OOM on multi-GB files); strip .sin extension
case-insensitively to match case-insensitive FTF detection
- detect.rs: scan all tar.bz2 members for Amlogic magic (not just
the first entry, since packages may have readme/manifest first);
tighten bzip2 check from 2-byte "BZ" to 3-byte "BZh"
Housekeeping:
- Remove unused Cargo dependencies (thiserror, memmap2, bytemuck, byteorder)
Rockchip RKFW header uses packed/unaligned fields:
- AFP container offset at 0x21 (not 0x1C)
- AFP container size at 0x25 (not 0x20)
AFP container structure corrected:
- AFP header is 0x8C bytes (not 0x4C)
- entry_count at offset 0x88 (not 0x44)
- Each entry is 0x70 / 112 bytes (not 0x48 / 72)
- Entry offset at 0x60, size at 0x6C (not 0x20/0x28)
When a firmware ships both a real partition and its alias (e.g. modem.img
alongside NON-HLOS.img on recent Oppo/ColorOS builds), fix_aliases logged
"Ignoring <alias> (<name> already extracted)" and unlinked the alias, but
then fell through and still attempted move(alias, partition). Since the
alias was just deleted, this raised FileNotFoundError and aborted the dump.
Add the missing continue so the redundant alias is dropped and the real
partition is left untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Some firmware (e.g. aftermarket car head units) wrap the real dumpable ROM
one zip deeper -- the outer archive holds vendor blobs and APK dirs, while
the actual block-based OTA (system.new.dat.br + *.transfer.list, payload.bin,
super*.img, *.tar.md5) lives inside an inner .zip such as update_car.zip.
dumpyara only scanned the outer archive's top level, found no recognized
partition container, and aborted with "System folder doesn't exist".
Extend nested-archive handling: for each top-level .zip not already covered
by NESTED_ARCHIVES, peek its central directory (zipfile namelist, no
extraction) and recurse only when it contains a partition marker. The marker
patterns are anchored to a path boundary and the *.new.dat.br / *.transfer.list
markers are restricted to known partition names, so APK/config zips (which
carry none of these) are never exploded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Fixes dumping OPPO and/or OnePlus stock firmwares that use Chinese
characters for app paths
Signed-off-by: Muhammad Asif <thevancedgamer@mentallysanemainliners.org>
Define the Ruff rule set.
Add type information for the optional parser module.
Resolve the fallback executable paths.
Use PyO3 0.29 for Python 3.14.
Apply the standard Python and Rust formats.
akhilnarangand others added 5 commits August 16, 2026 23:03
The extractor did not extract a nested image zip when the zip held loose
raw partition images. Such a zip has no super image and no payload file.
Pixel factory images use this layout. They ship system.img, vendor.img,
and product.img directly.
The marker check missed these files. Therefore the extractor skipped the
nested zip. The system partition did not reach the output. The system
folder assertion then failed.
Add a marker pattern for loose raw partition images. Anchor the pattern
on the known partition names. Allow an optional A/B slot suffix. Allow
the image file extensions that get_raw_image accepts. The pattern does
not match super_empty.img. The pattern does not match an unrelated image
file.
Derive the partition names from get_partition_names_with_alias. Add the
return type to two partition helper functions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The vendored Python payload parser is single-threaded, iterates
partitions and operations serially, and shells out to xzcat/bzcat
once per operation. On a 3.5 GB Pixel foldable OTA (grizzly, ~40
partitions), that hits our 1-hour bot timeout; running otadump on
the same input finishes in ~1-2 min.
Use the otadump binary when it's on PATH (Rayon-parallel per
partition, in-process zstd/xz/bzip2), fall back to the vendored
parser otherwise. Stage otadump's output in a sibling tempdir and
move on success so a partial failure doesn't leave stray images
next to payload.bin.
Fallback triggers only on binary-absent, not on error — the
Python impl is less strict about hash verification and silently
falling back could hide corruption.
Since Xiaomi HyperOS 4, Xiaomi added new partition called "mi_product". For now it's quite empty, but it might change in the future.
Sign up for freeto 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.

4 participants

@akhilnarang@deadman96385@muhammad23012009@kacskrz