Last active
July 12, 2025 15:08
-
-
Save playday3008/c26833299fe8373a4190ec9360687a77 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| QCDT (Qualcomm Device Tree) Patcher | |
| Decompresses LZ4 QCDT files, patches header structure, and saves modified data. | |
| Basically: | |
| 1. Read LZ4 compressed QCDT | |
| 2. Decompress | |
| 3. Parse header | |
| 4. Check if extended | |
| - If extended: | |
| 1. Parse table | |
| 2. Remove model field from table | |
| 3. Save table | |
| 4. Save QCDT | |
| - If no: | |
| 1. Save QCDT | |
| """ | |
| from typing import cast | |
| import argparse | |
| import struct | |
| import sys | |
| from pathlib import Path | |
| try: | |
| import lz4.frame | |
| except ImportError: | |
| print("Error: lz4 module not found. Install it using your package manager or pip: pip install lz4") | |
| sys.exit(1) | |
| class QCDTHeader: | |
| """Represents the QCDT header structure""" | |
| def __init__(self, data: bytes): | |
| if len(data) < 12: | |
| raise ValueError("Invalid QCDT header: too short") | |
| # Parse header: magic(4) + version(1) + extended(1) + reserved(2) + num_entries(4) | |
| self.magic, self.version, self.extended, self.reserved, self.num_entries = struct.unpack('<4sBBHI', data[:12]) | |
| if self.magic != b'QCDT': | |
| raise ValueError(f"Invalid magic: expected b'QCDT', got {self.magic}") | |
| print(f"QCDT Header:") | |
| print(f" Magic: {self.magic}") | |
| print(f" Version: {self.version}") | |
| print(f" Extended: {self.extended}") | |
| print(f" Reserved: {self.reserved}") | |
| print(f" Number of entries: {self.num_entries}") | |
| def pack(self): | |
| """Pack header back to bytes""" | |
| return struct.pack('<4sBBHI', self.magic, self.version, self.extended, self.reserved, self.num_entries) | |
| class DTEntry: | |
| """Represents a device tree entry""" | |
| # Original structure size: 4*4 + 4*4 + 4 + 4 + 32 = 72 bytes | |
| ORIGINAL_SIZE = 72 | |
| # New structure size: 4*4 + 4*4 + 4 + 4 = 40 bytes (removing 32-byte model field) | |
| NEW_SIZE = 40 | |
| def __init__(self, data: bytes, offset: int = 0): | |
| if len(data) < offset + self.ORIGINAL_SIZE: | |
| raise ValueError("Invalid DT entry: insufficient data") | |
| entry_data = data[offset:offset + self.ORIGINAL_SIZE] | |
| # Parse entry: platform_id(4) + variant_id(4) + board_hw_subtype(4) + soc_rev(4) + | |
| # pmic_rev[4](16) + offset(4) + size(4) + model(32) | |
| unpacked = struct.unpack('<IIII4III32s', entry_data) | |
| self.platform_id = unpacked[0] | |
| self.variant_id = unpacked[1] | |
| self.board_hw_subtype = unpacked[2] | |
| self.soc_rev = unpacked[3] | |
| self.pmic_rev = list(unpacked[4:8]) | |
| self.dt_offset = unpacked[8] | |
| self.dt_size = unpacked[9] | |
| self.model = unpacked[10].rstrip(b'\x00') # Remove null padding | |
| print(f" Entry: platform_id={self.platform_id}, variant_id=0x{self.variant_id:X}, " | |
| f"model='{self.model.decode('utf-8', errors='ignore')}', " | |
| f"size=0x{self.dt_size:X}, offset=0x{self.dt_offset:X}") | |
| def pack_new_format(self): | |
| """Pack entry in new format (without model field)""" | |
| return struct.pack('<IIII4III', | |
| self.platform_id, | |
| self.variant_id, | |
| self.board_hw_subtype, | |
| self.soc_rev, | |
| *self.pmic_rev, | |
| self.dt_offset, | |
| self.dt_size) | |
| def decompress_lz4_file(input_file: Path) -> bytes | None: | |
| """Decompress LZ4 file into memory""" | |
| print(f"Decompressing {input_file}...") | |
| try: | |
| with open(input_file, 'rb') as f: | |
| compressed_data = f.read() | |
| decompressed_data = cast(bytes, lz4.frame.decompress(compressed_data)) | |
| print(f"Successfully decompressed {len(compressed_data)} bytes to {len(decompressed_data)} bytes") | |
| return decompressed_data | |
| except Exception as e: | |
| print(f"Error decompressing file: {e}") | |
| return None | |
| def patch_qcdt_data(data: bytes) -> bytes: | |
| """Patch QCDT data to remove model field from entries""" | |
| print("Patching QCDT data...") | |
| # Parse header | |
| header = QCDTHeader(data) | |
| if header.version != 3 and header.extended != 1: | |
| return data | |
| header.extended = 0 | |
| header_size = 12 | |
| original_entry_size = DTEntry.ORIGINAL_SIZE | |
| new_entry_size = DTEntry.NEW_SIZE | |
| # Find where device tree data starts (after header and all entries) | |
| dt_data_start = header_size + (header.num_entries * original_entry_size) | |
| if len(data) < dt_data_start: | |
| raise ValueError("Invalid QCDT file: file too small for declared entries") | |
| # Parse entries | |
| entries: list[DTEntry] = [] | |
| for i in range(header.num_entries): | |
| entry_offset = header_size + (i * original_entry_size) | |
| entry = DTEntry(data, entry_offset) | |
| entries.append(entry) | |
| # Calculate space to fill with zeros | |
| space_per_entry = original_entry_size - new_entry_size | |
| total_zero_fill = header.num_entries * space_per_entry | |
| print(f"Will fill {total_zero_fill} bytes with zeros") | |
| # Build new data | |
| new_data = bytearray() | |
| # Add header | |
| new_data.extend(header.pack()) | |
| # Add entries in new format | |
| for entry in entries: | |
| new_data.extend(entry.pack_new_format()) | |
| # Fill emptied space with zeros | |
| zero_fill = b'\x00' * total_zero_fill | |
| new_data.extend(zero_fill) | |
| # Add device tree data (unchanged) | |
| dt_data = data[dt_data_start:] | |
| new_data.extend(dt_data) | |
| print(f"Patched data: {len(data)} bytes -> {len(new_data)} bytes") | |
| return bytes(new_data) | |
| def save_file(data: bytes, output_file: Path): | |
| """Save data to file""" | |
| print(f"Saving to {output_file}...") | |
| try: | |
| with open(output_file, 'wb') as f: | |
| f.write(data) | |
| print(f"Successfully saved {len(data)} bytes to {output_file}") | |
| except Exception as e: | |
| print(f"Error saving file: {e}") | |
| return False | |
| return True | |
| def main(): | |
| parser = argparse.ArgumentParser(description="QCDT Patcher: Decompresses and patches Qualcomm Device Tree files.") | |
| parser.add_argument('input_file', type=str, help='Input LZ4 compressed QCDT file') | |
| parser.add_argument('output_file', type=str, help='Output file for patched QCDT data') | |
| args = parser.parse_args() | |
| input_file = Path(args.input_file) | |
| output_file = Path(args.output_file) | |
| if not input_file.exists(): | |
| print(f"Error: Input file '{input_file}' not found") | |
| sys.exit(1) | |
| if not input_file.is_file(): | |
| print(f"Error: Input file '{input_file}' is not a regular file") | |
| sys.exit(1) | |
| decompressed_data = decompress_lz4_file(input_file) | |
| if decompressed_data is None: | |
| sys.exit(1) | |
| try: | |
| patched_data = patch_qcdt_data(decompressed_data) | |
| if save_file(patched_data, output_file): | |
| print("Success! QCDT file has been patched.") | |
| else: | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f"Error processing QCDT data: {e}") | |
| sys.exit(1) | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import std.mem; | |
| import std.string; | |
| import hex.core; | |
| import type.magic; | |
| #define MAGIC_HEAD "SINGLE_N_LONELY" | |
| #define MAGIC_TAIL "LONELY_N_SINGLE" | |
| struct Part { | |
| std::string::NullString name; | |
| padding[0xF8 - sizeof(name)]; | |
| u64 size; | |
| u8 data[size]; | |
| padding[while(std::mem::read_unsigned($, 1) == 0x00)]; | |
| hex::core::add_virtual_file(name, data); | |
| } [[name(name)]]; | |
| struct Bootloader { | |
| type::Magic<MAGIC_HEAD> magic_head [[hidden]]; | |
| padding[0x100 - sizeof(magic_head)]; | |
| Part parts[while(!std::mem::reached(std::mem::size() - 0x100))] [[inline]]; | |
| type::Magic<MAGIC_TAIL> magic_tail [[hidden]]; | |
| padding[0x100 - sizeof(magic_tail)]; | |
| }; | |
| Bootloader bootloader @ 0x0 [[inline]]; | |
| // Check "Virtual Filesystem" tab below |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type.magic; | |
| struct dt_entry<auto Extended> | |
| { | |
| u32 platform_id; | |
| u32 variant_id; | |
| u32 board_hw_subtype; | |
| u32 soc_rev; | |
| u32 pmic_rev[4]; | |
| u32 offset; | |
| u32 size; | |
| if (Extended) | |
| char model[32]; | |
| u8 data[size] @ offset; | |
| }; | |
| struct dt_table | |
| { | |
| type::Magic<"QCDT"> magic; | |
| u8 version; | |
| u8 extended; | |
| u16 reserved; | |
| u32 num_entries; | |
| }; | |
| dt_table table @ $; | |
| dt_entry<table.extended> entries[table.num_entries] @ $; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment