|
1 | 1 | import hashlib
|
| 2 | +import struct |
| 3 | +from dataclasses import dataclass |
2 | 4 |
|
3 | 5 | from colorama import Fore, Style
|
4 | 6 | from Crypto.Cipher import AES
|
|
12 | 14 | ParsingError,
|
13 | 15 | )
|
14 | 16 | from .patch import FirmwarePatchMixin
|
15 |
| -from .utils import round_down_word, round_up_word |
| 17 | +from .utils import round_down_word, round_up_page, round_up_word |
16 | 18 |
|
17 | 19 |
|
18 | 20 | def _val_to_color(val):
|
@@ -40,6 +42,55 @@ def __repr__(self):
|
40 | 42 | return "\n".join(substrs)
|
41 | 43 |
|
42 | 44 |
|
| 45 | +METADATA_MAGIC = 0x4 # chosen because no real executable code starts with address 0x4 |
| 46 | + |
| 47 | + |
| 48 | +@dataclass |
| 49 | +class HeaderMetaData: |
| 50 | + """4 bytes of data that can be stored at the hdmi-cec handler in the vector-table (0x01B8)""" |
| 51 | + |
| 52 | + external_flash_size: int # Actual size in bytes (will be divided by 4096 for storage) |
| 53 | + |
| 54 | + # 4 LSb are used for MAGIC |
| 55 | + is_mario: bool # 1 bit |
| 56 | + is_zelda: bool # 1 bit |
| 57 | + # 2 MSb are free |
| 58 | + |
| 59 | + def pack(self) -> bytes: |
| 60 | + # Convert size to 4K blocks (right shift by 12) |
| 61 | + blocks_4k = round_up_page(self.external_flash_size) >> 12 |
| 62 | + |
| 63 | + # Ensure the shifted value fits in 3 bytes |
| 64 | + if not (0 <= blocks_4k < (1 << 24)): |
| 65 | + raise ValueError( |
| 66 | + "external_flash_size must fit in 3 bytes when divided by 4096" |
| 67 | + ) |
| 68 | + |
| 69 | + # Pack the flags into a single byte |
| 70 | + flags = METADATA_MAGIC | (int(self.is_mario) << 4) | (int(self.is_zelda) << 5) |
| 71 | + |
| 72 | + # Pack as little-endian: |
| 73 | + # - First 3 bytes: external_flash_size |
| 74 | + # - Last byte: flags |
| 75 | + return struct.pack("<I", (blocks_4k & 0xFFFFFF) | (flags << 24)) |
| 76 | + |
| 77 | + @classmethod |
| 78 | + def unpack(cls, data: bytes) -> "HeaderMetaData": |
| 79 | + # Unpack the 32-bit value |
| 80 | + [value] = struct.unpack("<I", data) |
| 81 | + |
| 82 | + # Extract fields |
| 83 | + # Convert from 4K blocks back to bytes (left shift by 12) |
| 84 | + external_flash_size = (value & 0xFFFFFF) << 12 |
| 85 | + flags = (value >> 24) & 0xFF |
| 86 | + if (flags & 0x0F) != METADATA_MAGIC: |
| 87 | + raise ValueError("Invalid Magic.") |
| 88 | + is_mario = bool(flags & (1 << 4)) |
| 89 | + is_zelda = bool(flags & (1 << 5)) |
| 90 | + |
| 91 | + return cls(external_flash_size, is_mario, is_zelda) |
| 92 | + |
| 93 | + |
43 | 94 | class Firmware(FirmwarePatchMixin, bytearray):
|
44 | 95 |
|
45 | 96 | RAM_BASE = 0x02000000
|
@@ -675,8 +726,23 @@ def move_to_compressed_memory(self, ext, size, reference):
|
675 | 726 | return new_loc
|
676 | 727 |
|
677 | 728 | def __call__(self):
|
| 729 | + from . import MarioGnW, ZeldaGnW |
| 730 | + |
678 | 731 | self.int_pos = self.internal.empty_offset
|
679 |
| - return self.patch() |
| 732 | + out = self.patch() |
| 733 | + is_mario, is_zelda = False, False |
| 734 | + if isinstance(self, MarioGnW): |
| 735 | + is_mario = True |
| 736 | + elif isinstance(self, ZeldaGnW): |
| 737 | + is_zelda = True |
| 738 | + metadata = HeaderMetaData( |
| 739 | + external_flash_size=len(self.external), |
| 740 | + is_mario=is_mario, |
| 741 | + is_zelda=is_zelda, |
| 742 | + ) |
| 743 | + # hdmi-cec = 0x01B8; not used in the gnw hardware. |
| 744 | + self.internal.replace(0x01B8, metadata.pack()) |
| 745 | + return out |
680 | 746 |
|
681 | 747 | def patch(self):
|
682 | 748 | """Device specific argument parsing and patching routine.
|
|
0 commit comments