From 1dc99443b771074db4523f90f3224fc56df8b958 Mon Sep 17 00:00:00 2001 From: "opencode.energia.arti.ee" Date: Mon, 2 Nov 2026 11:04:39 +0000 Subject: [PATCH] Map SGI Indy IP24 PROM 5.3 Rev B10 Fetch and verify the four MAME indy_4610 PROM dumps, wrap the newest dump in an ELF32 BE MIPS image for Ghidra/REA, and record the resulting structural map: image layout, exception vector table, boot flow, function anchors and firmware feature clusters. --- .gitignore | 17 + README.md | 101 ++++ docs/procedure-index.csv | 1075 +++++++++++++++++++++++++++++++++++++ docs/rea-setup.md | 89 +++ docs/rom-map.json | 494 +++++++++++++++++ docs/rom-map.md | 368 +++++++++++++ roms/README.md | 15 + roms/SHA1SUMS | 4 + tools/fetch-roms.sh | 39 ++ tools/make-elf-wrapper.py | 115 ++++ tools/rom-map.py | 245 +++++++++ 11 files changed, 2562 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 docs/procedure-index.csv create mode 100644 docs/rea-setup.md create mode 100644 docs/rom-map.json create mode 100644 docs/rom-map.md create mode 100644 roms/README.md create mode 100644 roms/SHA1SUMS create mode 100755 tools/fetch-roms.sh create mode 100755 tools/make-elf-wrapper.py create mode 100755 tools/rom-map.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc4dcb8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# SGI firmware dumps and generated wrappers (copyrighted, fetch on demand) +roms/*.bin +roms/*.zip +roms/*.elf + +# Local REA evidence bundles, scratch output +evidence/ +*.rea.backup +*.rea.backup.* + +# Python bytecode +__pycache__/ +*.pyc + +# Editor/OS noise +.DS_Store +*.swp diff --git a/README.md b/README.md new file mode 100644 index 0000000..352dbdc --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +# sgi-indy + +Research workspace for the Silicon Graphics Indy (IP24) workstation, focused on +its boot PROM. + +The repository contains: + +- a reproducible fetcher and checksum manifest for the four MAME `indy_4610` + PROM dumps; +- a tool that wraps a raw PROM dump in a minimal ELF32 big-endian MIPS image so + it can be opened by Ghidra/REA at its real address; +- a structural map of the newest PROM, `ip24prom.070-9101-011.bin` + (SGI Version 5.3 Rev B10, 12 Feb 1996); +- the full 1,074-entry function index produced by Ghidra auto-analysis; +- notes for reproducing the REA + Ghidra setup. + +## Result at a glance + +| Item | Value | +| --- | --- | +| Newest Indy PROM | `ip24prom.070-9101-011.bin`, 524,288 bytes | +| Version string | `SGI Version 5.3 Rev B10 R4X00/R5000 IP24 Feb 12, 1996` | +| SHA-1 | `ac663a4db68528e400487e98cbf901f620fb30ce` (matches MAME) | +| SHA-256 | `333a0b2e39bb828a28a758b6f633ae7290030129dc747297a1be68dc317b86ae` | +| Load address | `0x1FC00000` (BIOS window) | +| Analyzed code span | `0x1FC00000` - `0x1FC4A8A0`, 1,074 functions | +| Defined strings | 1,418, from `0x1FC01B7C` to `0x1FC7C16B` | +| Used bytes | `0x00000` - `0x7F9FF`; remainder is `0xFF` padding | + +See [docs/rom-map.md](docs/rom-map.md) for the memory map, exception-vector +table, boot flow and function anchors. +See [docs/rom-map.json](docs/rom-map.json) for the machine-readable layout and +[docs/procedure-index.csv](docs/procedure-index.csv) for every function start. + +## ROM files are not committed + +The PROM dumps are copyrighted SGI firmware. `roms/*.bin`, the MAME zip and the +generated ELF wrapper are ignored by git. Fetch and verify them with: + +```bash +./tools/fetch-roms.sh +``` + +The fetch script downloads the MAME 0.236 `indy_4610` set and checks every SHA-1 +against MAME's `src/mame/sgi/indy_indigo2.cpp`. + +## Reproduce the analysis + +Prerequisites: Node.js 22.23.4+, REA (`npm install -g rea-agents`), Ghidra 12.1.x +and JDK 21. This workspace used REA 6.8.0, Ghidra 12.1.2 and OpenJDK 21. + +```bash +export GHIDRA_INSTALL_DIR=/opt/ghidra +export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 + +./tools/fetch-roms.sh +./tools/make-elf-wrapper.py \ + roms/ip24prom.070-9101-011.bin -o roms/indy-ip24-5.3-b10.elf + +rea analyze roms/indy-ip24-5.3-b10.elf --provider ghidra --json +``` + +REA's headerless `raw-binary` lane does not include MIPS. The wrapper declares +ELF32, big-endian, `ET_EXEC`, `e_flags = EF_MIPS_ARCH_32` and no ABI-flags +record, which selects REA's recordless MIPS admission and pins Ghidra to +`MIPS:BE:32:default`. The ROM bytes are copied unchanged at file offset `0x1000` +and mapped at `0x1FC00000`. + +To regenerate the checked-in map and function index, open the wrapper in a REA +session, export its evidence bundle, then run: + +```bash +./tools/rom-map.py roms/ip24prom.070-9101-011.bin \ + --evidence evidence/rea-bundle.json \ + --output docs/rom-map.json \ + --procedures-csv docs/procedure-index.csv +``` + +## Repository layout + +``` +docs/ map, setup notes, generated map JSON and function index +roms/ fetched ROMs (ignored) and the SHA-1 manifest +tools/ fetch, ELF wrapper, map generator +evidence/ local REA bundle (ignored) +``` + +## Setup notes + +The Gitea remote is +. +See [docs/rea-setup.md](docs/rea-setup.md) for the exact REA/OpenCode +configuration used here. + +## Sources and attribution + +- MAME `indy_indigo2.cpp` for the IP22/IP24 memory map and ROM identities: + +- MAME 0.236 ROM set archive used for the dumps. +- MiSTer `SGIIndy_MiSTer` ROM directory used as a cross-check. +- REA: diff --git a/docs/procedure-index.csv b/docs/procedure-index.csv new file mode 100644 index 0000000..646f088 --- /dev/null +++ b/docs/procedure-index.csv @@ -0,0 +1,1075 @@ +address,name,thunk_target +0x1fc00000,entry,0x1fc003c0 +0x1fc003c0,FUN_1fc003c0, +0x1fc00624,FUN_1fc00624, +0x1fc00828,FUN_1fc00828, +0x1fc009d4,FUN_1fc009d4, +0x1fc00a3c,FUN_1fc00a3c, +0x1fc00b58,FUN_1fc00b58, +0x1fc00bd0,FUN_1fc00bd0, +0x1fc00d58,FUN_1fc00d58, +0x1fc00dcc,FUN_1fc00dcc, +0x1fc01070,FUN_1fc01070, +0x1fc014f0,FUN_1fc014f0, +0x1fc01590,FUN_1fc01590, +0x1fc015e8,FUN_1fc015e8, +0x1fc01640,FUN_1fc01640, +0x1fc01860,FUN_1fc01860, +0x1fc018a8,FUN_1fc018a8, +0x1fc01984,FUN_1fc01984, +0x1fc019d4,FUN_1fc019d4, +0x1fc01a00,FUN_1fc01a00, +0x1fc01a5c,FUN_1fc01a5c, +0x1fc01b08,FUN_1fc01b08, +0x1fc01e00,FUN_1fc01e00, +0x1fc01e34,FUN_1fc01e34, +0x1fc01e68,FUN_1fc01e68, +0x1fc01e9c,FUN_1fc01e9c, +0x1fc01f2c,FUN_1fc01f2c, +0x1fc02000,FUN_1fc02000, +0x1fc02230,FUN_1fc02230, +0x1fc02470,FUN_1fc02470, +0x1fc025d4,FUN_1fc025d4, +0x1fc02624,FUN_1fc02624, +0x1fc026a8,FUN_1fc026a8, +0x1fc0276c,FUN_1fc0276c, +0x1fc02808,FUN_1fc02808, +0x1fc02830,FUN_1fc02830, +0x1fc02884,FUN_1fc02884, +0x1fc028c4,FUN_1fc028c4, +0x1fc028e4,FUN_1fc028e4, +0x1fc02904,FUN_1fc02904, +0x1fc02924,FUN_1fc02924, +0x1fc02944,FUN_1fc02944, +0x1fc02964,FUN_1fc02964, +0x1fc02984,FUN_1fc02984, +0x1fc029a4,FUN_1fc029a4, +0x1fc029c4,FUN_1fc029c4, +0x1fc029e4,FUN_1fc029e4, +0x1fc02a04,FUN_1fc02a04, +0x1fc02a24,FUN_1fc02a24, +0x1fc02a50,FUN_1fc02a50, +0x1fc02c60,FUN_1fc02c60, +0x1fc02cac,FUN_1fc02cac, +0x1fc02cf0,FUN_1fc02cf0, +0x1fc02d60,FUN_1fc02d60, +0x1fc02e80,FUN_1fc02e80, +0x1fc02f0c,FUN_1fc02f0c, +0x1fc02ff4,FUN_1fc02ff4, +0x1fc030b4,FUN_1fc030b4, +0x1fc032a4,FUN_1fc032a4, +0x1fc032cc,FUN_1fc032cc, +0x1fc0355c,FUN_1fc0355c, +0x1fc03578,FUN_1fc03578, +0x1fc03650,FUN_1fc03650, +0x1fc039e0,FUN_1fc039e0, +0x1fc03bd0,FUN_1fc03bd0, +0x1fc03c10,FUN_1fc03c10, +0x1fc03c34,FUN_1fc03c34, +0x1fc03cd4,FUN_1fc03cd4, +0x1fc03de0,FUN_1fc03de0, +0x1fc03eb0,FUN_1fc03eb0, +0x1fc040b0,FUN_1fc040b0, +0x1fc041d4,FUN_1fc041d4, +0x1fc04228,FUN_1fc04228, +0x1fc04350,FUN_1fc04350, +0x1fc046f0,FUN_1fc046f0, +0x1fc04760,FUN_1fc04760, +0x1fc0477c,FUN_1fc0477c, +0x1fc04798,FUN_1fc04798, +0x1fc047d8,FUN_1fc047d8, +0x1fc04818,FUN_1fc04818, +0x1fc04830,FUN_1fc04830, +0x1fc04df0,FUN_1fc04df0, +0x1fc04f30,FUN_1fc04f30, +0x1fc051fc,FUN_1fc051fc, +0x1fc052f4,FUN_1fc052f4, +0x1fc0553c,FUN_1fc0553c, +0x1fc060f4,FUN_1fc060f4, +0x1fc062c4,FUN_1fc062c4, +0x1fc06350,FUN_1fc06350, +0x1fc063c0,FUN_1fc063c0, +0x1fc065a0,FUN_1fc065a0, +0x1fc06600,FUN_1fc06600, +0x1fc06660,FUN_1fc06660, +0x1fc06a50,FUN_1fc06a50, +0x1fc06abc,FUN_1fc06abc, +0x1fc06b50,FUN_1fc06b50, +0x1fc07270,FUN_1fc07270, +0x1fc07328,FUN_1fc07328, +0x1fc074bc,FUN_1fc074bc, +0x1fc07630,FUN_1fc07630, +0x1fc077f0,FUN_1fc077f0, +0x1fc078ec,FUN_1fc078ec, +0x1fc07a78,FUN_1fc07a78, +0x1fc07b04,FUN_1fc07b04, +0x1fc07be0,FUN_1fc07be0, +0x1fc07d9c,FUN_1fc07d9c, +0x1fc08120,FUN_1fc08120, +0x1fc08230,FUN_1fc08230, +0x1fc082d4,FUN_1fc082d4, +0x1fc08378,FUN_1fc08378, +0x1fc08420,FUN_1fc08420, +0x1fc084e0,FUN_1fc084e0, +0x1fc08534,FUN_1fc08534, +0x1fc08710,FUN_1fc08710, +0x1fc089f8,FUN_1fc089f8, +0x1fc08a54,FUN_1fc08a54, +0x1fc08af0,FUN_1fc08af0, +0x1fc099a0,FUN_1fc099a0, +0x1fc09a58,FUN_1fc09a58, +0x1fc09a98,FUN_1fc09a98, +0x1fc09ac0,FUN_1fc09ac0, +0x1fc09b00,FUN_1fc09b00, +0x1fc09b64,FUN_1fc09b64, +0x1fc09d28,FUN_1fc09d28, +0x1fc09dd0,FUN_1fc09dd0, +0x1fc09ee4,FUN_1fc09ee4, +0x1fc0a098,FUN_1fc0a098, +0x1fc0a0fc,FUN_1fc0a0fc, +0x1fc0a144,FUN_1fc0a144, +0x1fc0a410,FUN_1fc0a410, +0x1fc0a4f4,FUN_1fc0a4f4, +0x1fc0a810,FUN_1fc0a810, +0x1fc0a83c,FUN_1fc0a83c, +0x1fc0a864,FUN_1fc0a864, +0x1fc0a89c,FUN_1fc0a89c, +0x1fc0a904,FUN_1fc0a904, +0x1fc0a9d0,FUN_1fc0a9d0, +0x1fc0aa8c,FUN_1fc0aa8c, +0x1fc0ab00,FUN_1fc0ab00, +0x1fc0ad64,FUN_1fc0ad64, +0x1fc0adf4,FUN_1fc0adf4, +0x1fc0ae68,FUN_1fc0ae68, +0x1fc0aea4,FUN_1fc0aea4, +0x1fc0af00,FUN_1fc0af00, +0x1fc0af44,FUN_1fc0af44, +0x1fc0afcc,FUN_1fc0afcc, +0x1fc0afe4,FUN_1fc0afe4, +0x1fc0b000,FUN_1fc0b000, +0x1fc0b028,FUN_1fc0b028, +0x1fc0b078,FUN_1fc0b078, +0x1fc0b0a4,FUN_1fc0b0a4, +0x1fc0b0f0,FUN_1fc0b0f0, +0x1fc0b594,FUN_1fc0b594, +0x1fc0b5d0,FUN_1fc0b5d0, +0x1fc0b744,FUN_1fc0b744, +0x1fc0bc4c,FUN_1fc0bc4c, +0x1fc0bcf8,FUN_1fc0bcf8, +0x1fc0bd00,FUN_1fc0bd00, +0x1fc0bd70,FUN_1fc0bd70, +0x1fc0bde4,FUN_1fc0bde4, +0x1fc0bdec,FUN_1fc0bdec, +0x1fc0bdf8,FUN_1fc0bdf8, +0x1fc0be04,FUN_1fc0be04, +0x1fc0be10,FUN_1fc0be10, +0x1fc0be38,FUN_1fc0be38, +0x1fc0be84,FUN_1fc0be84, +0x1fc0bee8,FUN_1fc0bee8, +0x1fc0bf1c,FUN_1fc0bf1c, +0x1fc0bf24,FUN_1fc0bf24, +0x1fc0bf30,FUN_1fc0bf30, +0x1fc0c3c8,FUN_1fc0c3c8, +0x1fc0c44c,FUN_1fc0c44c, +0x1fc0c49c,FUN_1fc0c49c, +0x1fc0c500,FUN_1fc0c500, +0x1fc0c54c,FUN_1fc0c54c, +0x1fc0c5b8,FUN_1fc0c5b8, +0x1fc0c630,FUN_1fc0c630, +0x1fc0c778,FUN_1fc0c778, +0x1fc0c838,FUN_1fc0c838, +0x1fc0c874,FUN_1fc0c874, +0x1fc0ca28,FUN_1fc0ca28, +0x1fc0cb48,FUN_1fc0cb48, +0x1fc0cb70,FUN_1fc0cb70, +0x1fc0cba8,FUN_1fc0cba8, +0x1fc0cbc8,FUN_1fc0cbc8, +0x1fc0ccf4,FUN_1fc0ccf4, +0x1fc0cdcc,FUN_1fc0cdcc, +0x1fc0ce6c,FUN_1fc0ce6c, +0x1fc0ce9c,FUN_1fc0ce9c, +0x1fc0cef0,FUN_1fc0cef0, +0x1fc0cf58,FUN_1fc0cf58, +0x1fc0cfdc,FUN_1fc0cfdc, +0x1fc0d018,FUN_1fc0d018, +0x1fc0d220,FUN_1fc0d220, +0x1fc0d348,FUN_1fc0d348, +0x1fc0d350,FUN_1fc0d350, +0x1fc0d360,FUN_1fc0d360, +0x1fc0d588,FUN_1fc0d588, +0x1fc0d7cc,FUN_1fc0d7cc, +0x1fc0d86c,FUN_1fc0d86c, +0x1fc0d970,FUN_1fc0d970, +0x1fc0d9c8,FUN_1fc0d9c8, +0x1fc0db04,FUN_1fc0db04, +0x1fc0db74,FUN_1fc0db74, +0x1fc0db98,FUN_1fc0db98, +0x1fc0dc14,FUN_1fc0dc14, +0x1fc0e14c,FUN_1fc0e14c, +0x1fc0e278,FUN_1fc0e278, +0x1fc0e2c4,FUN_1fc0e2c4, +0x1fc0e590,FUN_1fc0e590, +0x1fc0e5e8,FUN_1fc0e5e8, +0x1fc0e618,FUN_1fc0e618, +0x1fc0e648,FUN_1fc0e648, +0x1fc0e69c,FUN_1fc0e69c, +0x1fc0e6f0,FUN_1fc0e6f0, +0x1fc0e76c,FUN_1fc0e76c, +0x1fc0ea90,FUN_1fc0ea90, +0x1fc0eaa0,FUN_1fc0eaa0, +0x1fc0eac0,FUN_1fc0eac0, +0x1fc0ec54,FUN_1fc0ec54, +0x1fc0edf8,FUN_1fc0edf8, +0x1fc0f0b0,FUN_1fc0f0b0, +0x1fc0f2ac,FUN_1fc0f2ac, +0x1fc0f568,FUN_1fc0f568, +0x1fc0f7b0,FUN_1fc0f7b0, +0x1fc0f86c,FUN_1fc0f86c, +0x1fc0f9e0,FUN_1fc0f9e0, +0x1fc0fac4,FUN_1fc0fac4, +0x1fc0fb10,FUN_1fc0fb10, +0x1fc0fc34,FUN_1fc0fc34, +0x1fc1004c,FUN_1fc1004c, +0x1fc1030c,FUN_1fc1030c, +0x1fc1046c,FUN_1fc1046c, +0x1fc1053c,FUN_1fc1053c, +0x1fc10608,FUN_1fc10608, +0x1fc106d0,FUN_1fc106d0, +0x1fc106f8,FUN_1fc106f8, +0x1fc107b4,FUN_1fc107b4, +0x1fc10878,FUN_1fc10878, +0x1fc10924,FUN_1fc10924, +0x1fc10944,FUN_1fc10944, +0x1fc10964,FUN_1fc10964, +0x1fc10a04,FUN_1fc10a04, +0x1fc10a40,FUN_1fc10a40, +0x1fc10ab8,FUN_1fc10ab8, +0x1fc10b90,FUN_1fc10b90, +0x1fc10bd0,FUN_1fc10bd0, +0x1fc10c14,FUN_1fc10c14, +0x1fc10d40,FUN_1fc10d40, +0x1fc10e8c,FUN_1fc10e8c, +0x1fc11050,FUN_1fc11050, +0x1fc110b0,FUN_1fc110b0, +0x1fc1111c,FUN_1fc1111c, +0x1fc11144,FUN_1fc11144, +0x1fc111c4,FUN_1fc111c4, +0x1fc1130c,FUN_1fc1130c, +0x1fc11508,FUN_1fc11508, +0x1fc115f0,FUN_1fc115f0, +0x1fc11684,FUN_1fc11684, +0x1fc116e8,FUN_1fc116e8, +0x1fc11754,FUN_1fc11754, +0x1fc11844,FUN_1fc11844, +0x1fc118ac,FUN_1fc118ac, +0x1fc118dc,FUN_1fc118dc, +0x1fc11904,FUN_1fc11904, +0x1fc11950,FUN_1fc11950, +0x1fc11ce8,FUN_1fc11ce8, +0x1fc11e00,FUN_1fc11e00, +0x1fc11e88,FUN_1fc11e88, +0x1fc122a4,FUN_1fc122a4, +0x1fc122d4,FUN_1fc122d4, +0x1fc12428,FUN_1fc12428, +0x1fc12714,FUN_1fc12714, +0x1fc12748,FUN_1fc12748, +0x1fc12770,FUN_1fc12770, +0x1fc12804,FUN_1fc12804, +0x1fc12a00,FUN_1fc12a00, +0x1fc12b24,FUN_1fc12b24, +0x1fc12c30,FUN_1fc12c30, +0x1fc12d64,FUN_1fc12d64, +0x1fc12de0,FUN_1fc12de0, +0x1fc12e38,FUN_1fc12e38, +0x1fc13044,FUN_1fc13044, +0x1fc1314c,FUN_1fc1314c, +0x1fc132d0,FUN_1fc132d0, +0x1fc13320,FUN_1fc13320, +0x1fc13380,FUN_1fc13380, +0x1fc135a0,FUN_1fc135a0, +0x1fc135d0,FUN_1fc135d0, +0x1fc13724,FUN_1fc13724, +0x1fc1379c,FUN_1fc1379c, +0x1fc1386c,FUN_1fc1386c, +0x1fc13b40,FUN_1fc13b40, +0x1fc13ba8,FUN_1fc13ba8, +0x1fc13c78,FUN_1fc13c78, +0x1fc140a4,FUN_1fc140a4, +0x1fc141e0,FUN_1fc141e0, +0x1fc142b0,FUN_1fc142b0, +0x1fc144ec,FUN_1fc144ec, +0x1fc14834,FUN_1fc14834, +0x1fc148e0,FUN_1fc148e0, +0x1fc15104,FUN_1fc15104, +0x1fc1512c,FUN_1fc1512c, +0x1fc157ac,FUN_1fc157ac, +0x1fc15860,FUN_1fc15860, +0x1fc15894,FUN_1fc15894, +0x1fc15a18,FUN_1fc15a18, +0x1fc15a60,FUN_1fc15a60, +0x1fc15aa0,FUN_1fc15aa0, +0x1fc15b04,FUN_1fc15b04, +0x1fc15b2c,FUN_1fc15b2c, +0x1fc15b6c,FUN_1fc15b6c, +0x1fc15ba4,FUN_1fc15ba4, +0x1fc15bf8,FUN_1fc15bf8, +0x1fc15c40,FUN_1fc15c40, +0x1fc15c98,FUN_1fc15c98, +0x1fc15f60,FUN_1fc15f60, +0x1fc15fbc,FUN_1fc15fbc, +0x1fc16188,FUN_1fc16188, +0x1fc161fc,FUN_1fc161fc, +0x1fc1624c,FUN_1fc1624c, +0x1fc16340,FUN_1fc16340, +0x1fc16474,FUN_1fc16474, +0x1fc164d0,FUN_1fc164d0, +0x1fc16a18,FUN_1fc16a18, +0x1fc16a54,FUN_1fc16a54, +0x1fc16a90,FUN_1fc16a90, +0x1fc16bac,FUN_1fc16bac, +0x1fc16db8,FUN_1fc16db8, +0x1fc16ed4,FUN_1fc16ed4, +0x1fc16f30,FUN_1fc16f30, +0x1fc17068,FUN_1fc17068, +0x1fc1713c,FUN_1fc1713c, +0x1fc171c4,FUN_1fc171c4, +0x1fc17520,FUN_1fc17520, +0x1fc1756c,FUN_1fc1756c, +0x1fc17820,FUN_1fc17820, +0x1fc17ba4,FUN_1fc17ba4, +0x1fc18590,FUN_1fc18590, +0x1fc187d0,FUN_1fc187d0, +0x1fc18850,FUN_1fc18850, +0x1fc18a9c,FUN_1fc18a9c, +0x1fc18b48,FUN_1fc18b48, +0x1fc18ddc,FUN_1fc18ddc, +0x1fc18f10,FUN_1fc18f10, +0x1fc1918c,FUN_1fc1918c, +0x1fc195f4,FUN_1fc195f4, +0x1fc19808,FUN_1fc19808, +0x1fc198e4,FUN_1fc198e4, +0x1fc199bc,FUN_1fc199bc, +0x1fc19a44,FUN_1fc19a44, +0x1fc19d70,FUN_1fc19d70, +0x1fc19fa0,FUN_1fc19fa0, +0x1fc1a014,FUN_1fc1a014, +0x1fc1a070,FUN_1fc1a070, +0x1fc1a364,FUN_1fc1a364, +0x1fc1a3f8,FUN_1fc1a3f8, +0x1fc1a528,FUN_1fc1a528, +0x1fc1a81c,FUN_1fc1a81c, +0x1fc1ab50,FUN_1fc1ab50, +0x1fc1ae94,FUN_1fc1ae94, +0x1fc1b080,FUN_1fc1b080, +0x1fc1b1a8,FUN_1fc1b1a8, +0x1fc1b204,FUN_1fc1b204, +0x1fc1b494,FUN_1fc1b494, +0x1fc1b570,FUN_1fc1b570, +0x1fc1b784,FUN_1fc1b784, +0x1fc1b7d4,FUN_1fc1b7d4, +0x1fc1b934,FUN_1fc1b934, +0x1fc1be2c,FUN_1fc1be2c, +0x1fc1beec,FUN_1fc1beec, +0x1fc1c044,FUN_1fc1c044, +0x1fc1c0a8,FUN_1fc1c0a8, +0x1fc1c164,FUN_1fc1c164, +0x1fc1c1f4,FUN_1fc1c1f4, +0x1fc1c380,FUN_1fc1c380, +0x1fc1c3fc,FUN_1fc1c3fc, +0x1fc1c56c,FUN_1fc1c56c, +0x1fc1c780,FUN_1fc1c780, +0x1fc1cd90,FUN_1fc1cd90, +0x1fc1ce00,FUN_1fc1ce00, +0x1fc1ce84,FUN_1fc1ce84, +0x1fc1cec8,FUN_1fc1cec8, +0x1fc1cf8c,FUN_1fc1cf8c, +0x1fc1d290,FUN_1fc1d290, +0x1fc1d460,FUN_1fc1d460, +0x1fc1d57c,FUN_1fc1d57c, +0x1fc1d828,FUN_1fc1d828, +0x1fc1d98c,FUN_1fc1d98c, +0x1fc1d9e4,FUN_1fc1d9e4, +0x1fc1da78,FUN_1fc1da78, +0x1fc1dd48,FUN_1fc1dd48, +0x1fc1dde4,FUN_1fc1dde4, +0x1fc1df08,FUN_1fc1df08, +0x1fc1dfe0,FUN_1fc1dfe0, +0x1fc1e134,FUN_1fc1e134, +0x1fc1e6fc,FUN_1fc1e6fc, +0x1fc1e91c,FUN_1fc1e91c, +0x1fc1eb50,FUN_1fc1eb50, +0x1fc1f078,FUN_1fc1f078, +0x1fc1f130,FUN_1fc1f130, +0x1fc1f230,FUN_1fc1f230, +0x1fc1f24c,FUN_1fc1f24c, +0x1fc1f320,FUN_1fc1f320, +0x1fc1f4a0,FUN_1fc1f4a0, +0x1fc1f64c,FUN_1fc1f64c, +0x1fc1f770,FUN_1fc1f770, +0x1fc1f91c,FUN_1fc1f91c, +0x1fc1fa5c,FUN_1fc1fa5c, +0x1fc1fae4,FUN_1fc1fae4, +0x1fc1fc84,FUN_1fc1fc84, +0x1fc1fd04,FUN_1fc1fd04, +0x1fc1fe58,FUN_1fc1fe58, +0x1fc1fec4,FUN_1fc1fec4, +0x1fc1ff90,FUN_1fc1ff90, +0x1fc1ffdc,FUN_1fc1ffdc, +0x1fc20024,FUN_1fc20024, +0x1fc20298,FUN_1fc20298, +0x1fc20388,FUN_1fc20388, +0x1fc20440,FUN_1fc20440, +0x1fc20818,FUN_1fc20818, +0x1fc20930,FUN_1fc20930, +0x1fc20970,FUN_1fc20970, +0x1fc209c4,FUN_1fc209c4, +0x1fc20a60,FUN_1fc20a60, +0x1fc20c40,FUN_1fc20c40, +0x1fc20d5c,FUN_1fc20d5c, +0x1fc20e1c,FUN_1fc20e1c, +0x1fc20e84,FUN_1fc20e84, +0x1fc20f7c,FUN_1fc20f7c, +0x1fc21094,FUN_1fc21094, +0x1fc21324,FUN_1fc21324, +0x1fc21424,FUN_1fc21424, +0x1fc214c8,FUN_1fc214c8, +0x1fc215a0,FUN_1fc215a0, +0x1fc21638,FUN_1fc21638, +0x1fc216d0,FUN_1fc216d0, +0x1fc21718,FUN_1fc21718, +0x1fc217ac,FUN_1fc217ac, +0x1fc21930,FUN_1fc21930, +0x1fc21960,FUN_1fc21960, +0x1fc21a1c,FUN_1fc21a1c, +0x1fc21abc,FUN_1fc21abc, +0x1fc21c0c,FUN_1fc21c0c, +0x1fc21d10,FUN_1fc21d10, +0x1fc21d40,FUN_1fc21d40, +0x1fc21e00,FUN_1fc21e00, +0x1fc21e54,FUN_1fc21e54, +0x1fc21efc,FUN_1fc21efc, +0x1fc21f44,FUN_1fc21f44, +0x1fc22000,FUN_1fc22000, +0x1fc22024,FUN_1fc22024, +0x1fc22080,FUN_1fc22080, +0x1fc220d0,FUN_1fc220d0, +0x1fc2219c,FUN_1fc2219c, +0x1fc221ec,FUN_1fc221ec, +0x1fc222a4,FUN_1fc222a4, +0x1fc222e4,FUN_1fc222e4, +0x1fc223b8,FUN_1fc223b8, +0x1fc22428,FUN_1fc22428, +0x1fc2254c,FUN_1fc2254c, +0x1fc225a0,FUN_1fc225a0, +0x1fc225fc,FUN_1fc225fc, +0x1fc22680,FUN_1fc22680, +0x1fc22a60,FUN_1fc22a60, +0x1fc22ba0,FUN_1fc22ba0, +0x1fc231e0,FUN_1fc231e0, +0x1fc234f8,FUN_1fc234f8, +0x1fc235e8,FUN_1fc235e8, +0x1fc237e4,FUN_1fc237e4, +0x1fc239a0,FUN_1fc239a0, +0x1fc23af8,FUN_1fc23af8, +0x1fc23c48,FUN_1fc23c48, +0x1fc23dcc,FUN_1fc23dcc, +0x1fc23f50,FUN_1fc23f50, +0x1fc242e0,FUN_1fc242e0, +0x1fc243a4,FUN_1fc243a4, +0x1fc24444,FUN_1fc24444, +0x1fc245d0,FUN_1fc245d0, +0x1fc246c0,FUN_1fc246c0, +0x1fc2474c,FUN_1fc2474c, +0x1fc2476c,FUN_1fc2476c, +0x1fc2478c,FUN_1fc2478c, +0x1fc247c4,FUN_1fc247c4, +0x1fc2485c,FUN_1fc2485c, +0x1fc2486c,FUN_1fc2486c, +0x1fc24a28,FUN_1fc24a28, +0x1fc24b48,FUN_1fc24b48, +0x1fc24b6c,FUN_1fc24b6c, +0x1fc24c94,FUN_1fc24c94, +0x1fc24e20,FUN_1fc24e20, +0x1fc24e88,FUN_1fc24e88, +0x1fc24fa4,FUN_1fc24fa4, +0x1fc250cc,FUN_1fc250cc, +0x1fc25180,FUN_1fc25180, +0x1fc25274,FUN_1fc25274, +0x1fc252a4,FUN_1fc252a4, +0x1fc25708,FUN_1fc25708, +0x1fc25794,FUN_1fc25794, +0x1fc259b8,FUN_1fc259b8, +0x1fc25d6c,FUN_1fc25d6c, +0x1fc264b4,FUN_1fc264b4, +0x1fc26878,FUN_1fc26878, +0x1fc26c98,FUN_1fc26c98, +0x1fc2715c,FUN_1fc2715c, +0x1fc27310,FUN_1fc27310, +0x1fc27a6c,FUN_1fc27a6c, +0x1fc27aa8,FUN_1fc27aa8, +0x1fc282d8,FUN_1fc282d8, +0x1fc28324,FUN_1fc28324, +0x1fc28740,FUN_1fc28740, +0x1fc28878,FUN_1fc28878, +0x1fc288d0,FUN_1fc288d0, +0x1fc28930,FUN_1fc28930, +0x1fc289a0,FUN_1fc289a0, +0x1fc28b10,FUN_1fc28b10, +0x1fc28b40,FUN_1fc28b40, +0x1fc28c58,FUN_1fc28c58, +0x1fc28d18,FUN_1fc28d18, +0x1fc28fac,FUN_1fc28fac, +0x1fc29120,FUN_1fc29120, +0x1fc293c8,FUN_1fc293c8, +0x1fc29404,FUN_1fc29404, +0x1fc294c0,FUN_1fc294c0, +0x1fc294f8,FUN_1fc294f8, +0x1fc29634,FUN_1fc29634, +0x1fc29680,FUN_1fc29680, +0x1fc2a2a8,FUN_1fc2a2a8, +0x1fc2a5d4,FUN_1fc2a5d4, +0x1fc2a6a0,FUN_1fc2a6a0, +0x1fc2a770,FUN_1fc2a770, +0x1fc2a798,FUN_1fc2a798, +0x1fc2a958,FUN_1fc2a958, +0x1fc2aa6c,FUN_1fc2aa6c, +0x1fc2ad20,FUN_1fc2ad20, +0x1fc2ae2c,FUN_1fc2ae2c, +0x1fc2aff0,FUN_1fc2aff0, +0x1fc2b328,FUN_1fc2b328, +0x1fc2b3f4,FUN_1fc2b3f4, +0x1fc2b47c,FUN_1fc2b47c, +0x1fc2b5bc,FUN_1fc2b5bc, +0x1fc2b6c0,FUN_1fc2b6c0, +0x1fc2b6f0,FUN_1fc2b6f0, +0x1fc2b808,FUN_1fc2b808, +0x1fc2b89c,FUN_1fc2b89c, +0x1fc2b8e4,FUN_1fc2b8e4, +0x1fc2b9e4,FUN_1fc2b9e4, +0x1fc2bb24,FUN_1fc2bb24, +0x1fc2bcd4,FUN_1fc2bcd4, +0x1fc2bf90,FUN_1fc2bf90, +0x1fc2bfd8,FUN_1fc2bfd8, +0x1fc2c0a0,FUN_1fc2c0a0, +0x1fc2c15c,FUN_1fc2c15c, +0x1fc2c168,FUN_1fc2c168, +0x1fc2c1f8,FUN_1fc2c1f8, +0x1fc2c204,FUN_1fc2c204, +0x1fc2c228,FUN_1fc2c228, +0x1fc2c234,FUN_1fc2c234, +0x1fc2c284,FUN_1fc2c284, +0x1fc2c548,FUN_1fc2c548, +0x1fc2c674,FUN_1fc2c674, +0x1fc2c694,FUN_1fc2c694, +0x1fc2c770,FUN_1fc2c770, +0x1fc2c96c,FUN_1fc2c96c, +0x1fc2c9ac,FUN_1fc2c9ac, +0x1fc2ca38,FUN_1fc2ca38, +0x1fc2ca7c,FUN_1fc2ca7c, +0x1fc2cac4,FUN_1fc2cac4, +0x1fc2cbfc,FUN_1fc2cbfc, +0x1fc2cc64,FUN_1fc2cc64, +0x1fc2cd14,FUN_1fc2cd14, +0x1fc2cdc0,FUN_1fc2cdc0, +0x1fc2ce34,FUN_1fc2ce34, +0x1fc2ce8c,FUN_1fc2ce8c, +0x1fc2cf50,FUN_1fc2cf50, +0x1fc2cf90,FUN_1fc2cf90, +0x1fc2d024,FUN_1fc2d024, +0x1fc2d0a4,FUN_1fc2d0a4, +0x1fc2d130,FUN_1fc2d130, +0x1fc2d7a8,FUN_1fc2d7a8, +0x1fc2d8b8,FUN_1fc2d8b8, +0x1fc2da8c,FUN_1fc2da8c, +0x1fc2daf0,FUN_1fc2daf0, +0x1fc2dc7c,FUN_1fc2dc7c, +0x1fc2dd30,FUN_1fc2dd30, +0x1fc2e1c4,FUN_1fc2e1c4, +0x1fc2e6fc,FUN_1fc2e6fc, +0x1fc2e760,FUN_1fc2e760, +0x1fc2e89c,FUN_1fc2e89c, +0x1fc2e938,FUN_1fc2e938, +0x1fc2ecb4,FUN_1fc2ecb4, +0x1fc2ed68,FUN_1fc2ed68, +0x1fc2f520,FUN_1fc2f520, +0x1fc2f584,FUN_1fc2f584, +0x1fc2f5f0,FUN_1fc2f5f0, +0x1fc2f79c,FUN_1fc2f79c, +0x1fc2f850,FUN_1fc2f850, +0x1fc2f924,FUN_1fc2f924, +0x1fc2fba8,FUN_1fc2fba8, +0x1fc2fbb4,FUN_1fc2fbb4, +0x1fc2fbd4,FUN_1fc2fbd4, +0x1fc2fd34,FUN_1fc2fd34, +0x1fc2fd98,FUN_1fc2fd98, +0x1fc2fe00,FUN_1fc2fe00, +0x1fc2fe78,FUN_1fc2fe78, +0x1fc2feac,FUN_1fc2feac, +0x1fc30044,FUN_1fc30044, +0x1fc30088,FUN_1fc30088, +0x1fc30270,FUN_1fc30270, +0x1fc302f8,FUN_1fc302f8, +0x1fc304c0,FUN_1fc304c0, +0x1fc30f74,FUN_1fc30f74, +0x1fc30fc0,FUN_1fc30fc0, +0x1fc31120,FUN_1fc31120, +0x1fc3115c,FUN_1fc3115c, +0x1fc311e0,FUN_1fc311e0, +0x1fc31418,FUN_1fc31418, +0x1fc31490,FUN_1fc31490, +0x1fc31594,FUN_1fc31594, +0x1fc315d0,FUN_1fc315d0, +0x1fc3164c,FUN_1fc3164c, +0x1fc317ec,FUN_1fc317ec, +0x1fc31820,FUN_1fc31820, +0x1fc31888,FUN_1fc31888, +0x1fc318f8,FUN_1fc318f8, +0x1fc31920,FUN_1fc31920, +0x1fc31928,FUN_1fc31928, +0x1fc31a00,FUN_1fc31a00, +0x1fc31a08,FUN_1fc31a08, +0x1fc31a14,FUN_1fc31a14, +0x1fc31a1c,FUN_1fc31a1c, +0x1fc31a3c,FUN_1fc31a3c, +0x1fc31a64,FUN_1fc31a64, +0x1fc31a8c,FUN_1fc31a8c, +0x1fc31b64,FUN_1fc31b64, +0x1fc31bd8,FUN_1fc31bd8, +0x1fc31ec0,FUN_1fc31ec0, +0x1fc31ee4,FUN_1fc31ee4, +0x1fc32008,FUN_1fc32008, +0x1fc32030,FUN_1fc32030, +0x1fc321ec,FUN_1fc321ec, +0x1fc3242c,FUN_1fc3242c, +0x1fc325d0,FUN_1fc325d0, +0x1fc32cd0,FUN_1fc32cd0, +0x1fc33040,FUN_1fc33040, +0x1fc3317c,FUN_1fc3317c, +0x1fc33308,FUN_1fc33308, +0x1fc33360,FUN_1fc33360, +0x1fc334cc,FUN_1fc334cc, +0x1fc3364c,FUN_1fc3364c, +0x1fc336a0,FUN_1fc336a0, +0x1fc336ac,FUN_1fc336ac, +0x1fc3371c,FUN_1fc3371c, +0x1fc33770,FUN_1fc33770, +0x1fc337d0,FUN_1fc337d0, +0x1fc33870,FUN_1fc33870, +0x1fc33930,FUN_1fc33930, +0x1fc33a20,FUN_1fc33a20, +0x1fc33ac0,FUN_1fc33ac0, +0x1fc33adc,FUN_1fc33adc, +0x1fc33bf4,FUN_1fc33bf4, +0x1fc33cc0,FUN_1fc33cc0, +0x1fc33ce8,FUN_1fc33ce8, +0x1fc33d74,FUN_1fc33d74, +0x1fc33dd4,FUN_1fc33dd4, +0x1fc33e80,FUN_1fc33e80, +0x1fc33f48,FUN_1fc33f48, +0x1fc33f70,FUN_1fc33f70, +0x1fc34160,FUN_1fc34160, +0x1fc34244,FUN_1fc34244, +0x1fc3436c,FUN_1fc3436c, +0x1fc34420,FUN_1fc34420, +0x1fc344e4,FUN_1fc344e4, +0x1fc34680,FUN_1fc34680, +0x1fc346e4,FUN_1fc346e4, +0x1fc34a40,FUN_1fc34a40, +0x1fc34a64,FUN_1fc34a64, +0x1fc34aa8,FUN_1fc34aa8, +0x1fc34c08,FUN_1fc34c08, +0x1fc34cf0,FUN_1fc34cf0, +0x1fc34d9c,FUN_1fc34d9c, +0x1fc34e64,FUN_1fc34e64, +0x1fc34f74,FUN_1fc34f74, +0x1fc34fc0,FUN_1fc34fc0, +0x1fc3504c,FUN_1fc3504c, +0x1fc350c8,FUN_1fc350c8, +0x1fc351f8,FUN_1fc351f8, +0x1fc352ac,FUN_1fc352ac, +0x1fc35374,FUN_1fc35374, +0x1fc3540c,FUN_1fc3540c, +0x1fc35464,FUN_1fc35464, +0x1fc354b8,FUN_1fc354b8, +0x1fc3554c,FUN_1fc3554c, +0x1fc355a0,FUN_1fc355a0, +0x1fc3566c,FUN_1fc3566c, +0x1fc356d8,FUN_1fc356d8, +0x1fc35744,FUN_1fc35744, +0x1fc357b0,FUN_1fc357b0, +0x1fc3581c,FUN_1fc3581c, +0x1fc35888,FUN_1fc35888, +0x1fc358b4,FUN_1fc358b4, +0x1fc35904,FUN_1fc35904, +0x1fc35a6c,FUN_1fc35a6c, +0x1fc35b40,FUN_1fc35b40, +0x1fc35c68,FUN_1fc35c68, +0x1fc35d84,FUN_1fc35d84, +0x1fc35e78,FUN_1fc35e78, +0x1fc35f6c,FUN_1fc35f6c, +0x1fc36314,FUN_1fc36314, +0x1fc36450,FUN_1fc36450, +0x1fc368b0,FUN_1fc368b0, +0x1fc36960,FUN_1fc36960, +0x1fc36a04,FUN_1fc36a04, +0x1fc36fcc,FUN_1fc36fcc, +0x1fc370e8,FUN_1fc370e8, +0x1fc371b0,FUN_1fc371b0, +0x1fc37238,FUN_1fc37238, +0x1fc3727c,FUN_1fc3727c, +0x1fc37368,FUN_1fc37368, +0x1fc374b0,FUN_1fc374b0, +0x1fc3752c,FUN_1fc3752c, +0x1fc377b0,FUN_1fc377b0, +0x1fc37910,FUN_1fc37910, +0x1fc379cc,FUN_1fc379cc, +0x1fc37a30,FUN_1fc37a30, +0x1fc37b04,FUN_1fc37b04, +0x1fc37b30,FUN_1fc37b30, +0x1fc37b98,FUN_1fc37b98, +0x1fc37bc0,FUN_1fc37bc0, +0x1fc37e7c,FUN_1fc37e7c, +0x1fc38144,FUN_1fc38144, +0x1fc383f0,FUN_1fc383f0, +0x1fc383f8,FUN_1fc383f8, +0x1fc38520,FUN_1fc38520, +0x1fc386e8,FUN_1fc386e8, +0x1fc38808,FUN_1fc38808, +0x1fc38978,FUN_1fc38978, +0x1fc38af8,FUN_1fc38af8, +0x1fc38bb4,FUN_1fc38bb4, +0x1fc38bf0,FUN_1fc38bf0, +0x1fc38c10,FUN_1fc38c10, +0x1fc38c60,FUN_1fc38c60, +0x1fc38c80,FUN_1fc38c80, +0x1fc38ca0,FUN_1fc38ca0, +0x1fc38cf0,FUN_1fc38cf0, +0x1fc38d30,FUN_1fc38d30, +0x1fc38d50,FUN_1fc38d50, +0x1fc38d70,FUN_1fc38d70, +0x1fc38d98,FUN_1fc38d98, +0x1fc38f7c,FUN_1fc38f7c, +0x1fc39114,FUN_1fc39114, +0x1fc392f0,FUN_1fc392f0, +0x1fc39390,FUN_1fc39390, +0x1fc39528,FUN_1fc39528, +0x1fc39598,FUN_1fc39598, +0x1fc39600,FUN_1fc39600, +0x1fc39650,FUN_1fc39650, +0x1fc39680,FUN_1fc39680, +0x1fc39710,FUN_1fc39710, +0x1fc39b38,FUN_1fc39b38, +0x1fc39b60,FUN_1fc39b60, +0x1fc39da4,FUN_1fc39da4, +0x1fc39fb0,FUN_1fc39fb0, +0x1fc3a0a0,FUN_1fc3a0a0, +0x1fc3a0c0,FUN_1fc3a0c0, +0x1fc3a0e8,FUN_1fc3a0e8, +0x1fc3a110,FUN_1fc3a110, +0x1fc3a120,FUN_1fc3a120, +0x1fc3a1b4,FUN_1fc3a1b4, +0x1fc3a224,FUN_1fc3a224, +0x1fc3a488,FUN_1fc3a488, +0x1fc3a6e0,FUN_1fc3a6e0, +0x1fc3ac94,FUN_1fc3ac94, +0x1fc3add8,FUN_1fc3add8, +0x1fc3aea4,FUN_1fc3aea4, +0x1fc3af00,FUN_1fc3af00, +0x1fc3af44,FUN_1fc3af44, +0x1fc3af98,FUN_1fc3af98, +0x1fc3b104,FUN_1fc3b104, +0x1fc3b22c,FUN_1fc3b22c, +0x1fc3b400,FUN_1fc3b400, +0x1fc3b470,FUN_1fc3b470, +0x1fc3b53c,FUN_1fc3b53c, +0x1fc3b600,FUN_1fc3b600, +0x1fc3b7e0,FUN_1fc3b7e0, +0x1fc3b828,FUN_1fc3b828, +0x1fc3b8c8,FUN_1fc3b8c8, +0x1fc3b9b0,FUN_1fc3b9b0, +0x1fc3b9f4,FUN_1fc3b9f4, +0x1fc3baa0,FUN_1fc3baa0, +0x1fc3bae0,FUN_1fc3bae0, +0x1fc3bb74,FUN_1fc3bb74, +0x1fc3bbf8,FUN_1fc3bbf8, +0x1fc3bc1c,FUN_1fc3bc1c, +0x1fc3bc6c,FUN_1fc3bc6c, +0x1fc3bd54,FUN_1fc3bd54, +0x1fc3be3c,FUN_1fc3be3c, +0x1fc3be80,FUN_1fc3be80, +0x1fc3bed0,FUN_1fc3bed0, +0x1fc3bf1c,FUN_1fc3bf1c, +0x1fc3c120,FUN_1fc3c120, +0x1fc3c144,FUN_1fc3c144, +0x1fc3c2d0,FUN_1fc3c2d0, +0x1fc3c620,FUN_1fc3c620, +0x1fc3c960,FUN_1fc3c960, +0x1fc3ca58,FUN_1fc3ca58, +0x1fc3cac0,FUN_1fc3cac0, +0x1fc3cc38,FUN_1fc3cc38, +0x1fc3cda8,FUN_1fc3cda8, +0x1fc3ce24,FUN_1fc3ce24, +0x1fc3ceac,FUN_1fc3ceac, +0x1fc3ceb8,FUN_1fc3ceb8, +0x1fc3d044,FUN_1fc3d044, +0x1fc3d7e8,FUN_1fc3d7e8, +0x1fc3d8f4,FUN_1fc3d8f4, +0x1fc3d978,FUN_1fc3d978, +0x1fc3daa8,FUN_1fc3daa8, +0x1fc3dbc8,FUN_1fc3dbc8, +0x1fc3dc80,FUN_1fc3dc80, +0x1fc3dc9c,FUN_1fc3dc9c, +0x1fc3dd10,FUN_1fc3dd10, +0x1fc3df58,FUN_1fc3df58, +0x1fc3dff4,FUN_1fc3dff4, +0x1fc3e0fc,FUN_1fc3e0fc, +0x1fc3e178,FUN_1fc3e178, +0x1fc3e190,FUN_1fc3e190, +0x1fc3e1b8,FUN_1fc3e1b8, +0x1fc3e200,FUN_1fc3e200, +0x1fc3e230,FUN_1fc3e230, +0x1fc3e238,FUN_1fc3e238, +0x1fc3e240,FUN_1fc3e240, +0x1fc3e250,FUN_1fc3e250, +0x1fc3e268,FUN_1fc3e268, +0x1fc3e27c,FUN_1fc3e27c, +0x1fc3e294,FUN_1fc3e294, +0x1fc3e2b0,FUN_1fc3e2b0, +0x1fc3e2c8,FUN_1fc3e2c8, +0x1fc3e300,FUN_1fc3e300, +0x1fc3e3c0,FUN_1fc3e3c0, +0x1fc3e3e0,FUN_1fc3e3e0, +0x1fc3e400,FUN_1fc3e400, +0x1fc3e46c,FUN_1fc3e46c, +0x1fc3e534,FUN_1fc3e534, +0x1fc3e574,FUN_1fc3e574, +0x1fc3e620,FUN_1fc3e620, +0x1fc3e6b0,FUN_1fc3e6b0, +0x1fc3e6c4,FUN_1fc3e6c4, +0x1fc3e6fc,FUN_1fc3e6fc, +0x1fc3e72c,FUN_1fc3e72c, +0x1fc3e75c,FUN_1fc3e75c, +0x1fc3e794,FUN_1fc3e794, +0x1fc3e7cc,FUN_1fc3e7cc, +0x1fc3e874,FUN_1fc3e874, +0x1fc3e884,FUN_1fc3e884, +0x1fc3e890,FUN_1fc3e890, +0x1fc3e980,FUN_1fc3e980, +0x1fc3ec2c,FUN_1fc3ec2c, +0x1fc3ed20,FUN_1fc3ed20, +0x1fc3ed90,FUN_1fc3ed90, +0x1fc3ee0c,FUN_1fc3ee0c, +0x1fc3ee40,FUN_1fc3ee40, +0x1fc3effc,FUN_1fc3effc, +0x1fc3f0d8,FUN_1fc3f0d8, +0x1fc3f188,FUN_1fc3f188, +0x1fc3f1c4,FUN_1fc3f1c4, +0x1fc3f2ac,FUN_1fc3f2ac, +0x1fc3f360,FUN_1fc3f360, +0x1fc3f3f8,FUN_1fc3f3f8, +0x1fc3f4ec,FUN_1fc3f4ec, +0x1fc3f698,FUN_1fc3f698, +0x1fc3f90c,FUN_1fc3f90c, +0x1fc3fb3c,FUN_1fc3fb3c, +0x1fc3fc38,FUN_1fc3fc38, +0x1fc3fd00,FUN_1fc3fd00, +0x1fc3fd30,FUN_1fc3fd30, +0x1fc400a0,FUN_1fc400a0, +0x1fc40160,FUN_1fc40160, +0x1fc40230,FUN_1fc40230, +0x1fc40260,FUN_1fc40260, +0x1fc403b0,FUN_1fc403b0, +0x1fc405a0,FUN_1fc405a0, +0x1fc40630,FUN_1fc40630, +0x1fc407dc,FUN_1fc407dc, +0x1fc40860,FUN_1fc40860, +0x1fc4088c,FUN_1fc4088c, +0x1fc409a0,FUN_1fc409a0, +0x1fc40ac0,FUN_1fc40ac0, +0x1fc40c1c,FUN_1fc40c1c, +0x1fc40e08,FUN_1fc40e08, +0x1fc4119c,FUN_1fc4119c, +0x1fc416e0,FUN_1fc416e0, +0x1fc41770,FUN_1fc41770, +0x1fc41a80,FUN_1fc41a80, +0x1fc41c28,FUN_1fc41c28, +0x1fc41cb8,FUN_1fc41cb8, +0x1fc41cf0,FUN_1fc41cf0, +0x1fc41db0,FUN_1fc41db0, +0x1fc41e90,FUN_1fc41e90, +0x1fc41f3c,FUN_1fc41f3c, +0x1fc41f88,FUN_1fc41f88, +0x1fc41fdc,FUN_1fc41fdc, +0x1fc42020,FUN_1fc42020, +0x1fc42030,FUN_1fc42030, +0x1fc42064,FUN_1fc42064, +0x1fc420b0,FUN_1fc420b0, +0x1fc421e0,FUN_1fc421e0, +0x1fc42464,FUN_1fc42464, +0x1fc426b0,FUN_1fc426b0, +0x1fc42710,FUN_1fc42710, +0x1fc428f0,FUN_1fc428f0, +0x1fc42b94,FUN_1fc42b94, +0x1fc42bb4,FUN_1fc42bb4, +0x1fc42c40,FUN_1fc42c40, +0x1fc42ce0,FUN_1fc42ce0, +0x1fc42fc8,FUN_1fc42fc8, +0x1fc43038,FUN_1fc43038, +0x1fc431d0,FUN_1fc431d0, +0x1fc431f0,FUN_1fc431f0, +0x1fc4323c,FUN_1fc4323c, +0x1fc43250,FUN_1fc43250, +0x1fc432bc,FUN_1fc432bc, +0x1fc432f0,FUN_1fc432f0, +0x1fc4336c,FUN_1fc4336c, +0x1fc43394,FUN_1fc43394, +0x1fc433f0,FUN_1fc433f0, +0x1fc43404,FUN_1fc43404, +0x1fc43464,FUN_1fc43464, +0x1fc43484,FUN_1fc43484, +0x1fc435a4,FUN_1fc435a4, +0x1fc43668,FUN_1fc43668, +0x1fc43764,FUN_1fc43764, +0x1fc43898,FUN_1fc43898, +0x1fc439cc,FUN_1fc439cc, +0x1fc43a80,FUN_1fc43a80, +0x1fc43b00,FUN_1fc43b00, +0x1fc43b5c,FUN_1fc43b5c, +0x1fc43c48,FUN_1fc43c48, +0x1fc43ed0,FUN_1fc43ed0, +0x1fc43fd8,FUN_1fc43fd8, +0x1fc44070,FUN_1fc44070, +0x1fc44100,FUN_1fc44100, +0x1fc44150,FUN_1fc44150, +0x1fc4419c,FUN_1fc4419c, +0x1fc441d0,FUN_1fc441d0, +0x1fc441f8,FUN_1fc441f8, +0x1fc44300,FUN_1fc44300, +0x1fc44384,FUN_1fc44384, +0x1fc44420,FUN_1fc44420, +0x1fc44470,FUN_1fc44470, +0x1fc444b4,FUN_1fc444b4, +0x1fc44724,FUN_1fc44724, +0x1fc44770,FUN_1fc44770, +0x1fc447a8,FUN_1fc447a8, +0x1fc4485c,FUN_1fc4485c, +0x1fc44940,FUN_1fc44940, +0x1fc449e8,FUN_1fc449e8, +0x1fc44a1c,FUN_1fc44a1c, +0x1fc44a38,FUN_1fc44a38, +0x1fc44a50,FUN_1fc44a50, +0x1fc44acc,FUN_1fc44acc, +0x1fc44b44,FUN_1fc44b44, +0x1fc44bc4,FUN_1fc44bc4, +0x1fc44bf0,FUN_1fc44bf0, +0x1fc44c20,FUN_1fc44c20, +0x1fc44cbc,FUN_1fc44cbc, +0x1fc44d58,FUN_1fc44d58, +0x1fc44e10,FUN_1fc44e10, +0x1fc44e7c,FUN_1fc44e7c, +0x1fc45008,FUN_1fc45008, +0x1fc451e8,FUN_1fc451e8, +0x1fc45324,FUN_1fc45324, +0x1fc454e8,FUN_1fc454e8, +0x1fc45624,FUN_1fc45624, +0x1fc456a0,FUN_1fc456a0, +0x1fc45744,FUN_1fc45744, +0x1fc45820,FUN_1fc45820, +0x1fc45898,FUN_1fc45898, +0x1fc45930,FUN_1fc45930, +0x1fc45990,FUN_1fc45990, +0x1fc459a0,FUN_1fc459a0, +0x1fc45a30,FUN_1fc45a30, +0x1fc45a84,FUN_1fc45a84, +0x1fc45ab8,FUN_1fc45ab8, +0x1fc45ac4,FUN_1fc45ac4, +0x1fc45ff4,FUN_1fc45ff4, +0x1fc46070,FUN_1fc46070, +0x1fc46164,FUN_1fc46164, +0x1fc46180,FUN_1fc46180, +0x1fc461d8,FUN_1fc461d8, +0x1fc46230,FUN_1fc46230, +0x1fc46298,FUN_1fc46298, +0x1fc462c0,FUN_1fc462c0, +0x1fc463d4,FUN_1fc463d4, +0x1fc46490,FUN_1fc46490, +0x1fc464f8,FUN_1fc464f8, +0x1fc4650c,FUN_1fc4650c, +0x1fc465e4,FUN_1fc465e4, +0x1fc46644,FUN_1fc46644, +0x1fc46720,FUN_1fc46720, +0x1fc46760,FUN_1fc46760, +0x1fc46ad0,FUN_1fc46ad0, +0x1fc46af8,FUN_1fc46af8, +0x1fc46b50,FUN_1fc46b50, +0x1fc46b68,FUN_1fc46b68, +0x1fc46b80,FUN_1fc46b80, +0x1fc46bf0,FUN_1fc46bf0, +0x1fc46c30,FUN_1fc46c30, +0x1fc46ce8,FUN_1fc46ce8, +0x1fc46d0c,FUN_1fc46d0c, +0x1fc46d8c,FUN_1fc46d8c, +0x1fc47090,FUN_1fc47090, +0x1fc47280,FUN_1fc47280, +0x1fc47364,FUN_1fc47364, +0x1fc47610,FUN_1fc47610, +0x1fc4765c,FUN_1fc4765c, +0x1fc476fc,FUN_1fc476fc, +0x1fc47768,FUN_1fc47768, +0x1fc479f0,FUN_1fc479f0, +0x1fc47a04,FUN_1fc47a04, +0x1fc47a64,FUN_1fc47a64, +0x1fc47b40,FUN_1fc47b40, +0x1fc47bc0,FUN_1fc47bc0, +0x1fc47c4c,FUN_1fc47c4c, +0x1fc47c74,FUN_1fc47c74, +0x1fc47ce0,FUN_1fc47ce0, +0x1fc47d74,FUN_1fc47d74, +0x1fc47e78,FUN_1fc47e78, +0x1fc47fe0,FUN_1fc47fe0, +0x1fc4804c,FUN_1fc4804c, +0x1fc480cc,FUN_1fc480cc, +0x1fc48184,FUN_1fc48184, +0x1fc4829c,FUN_1fc4829c, +0x1fc48324,FUN_1fc48324, +0x1fc48364,FUN_1fc48364, +0x1fc48370,FUN_1fc48370, +0x1fc48444,FUN_1fc48444, +0x1fc48820,FUN_1fc48820, +0x1fc48884,FUN_1fc48884, +0x1fc488d0,FUN_1fc488d0, +0x1fc48a44,FUN_1fc48a44, +0x1fc48b20,FUN_1fc48b20, +0x1fc48b30,FUN_1fc48b30, +0x1fc48b40,FUN_1fc48b40, +0x1fc48b90,FUN_1fc48b90, +0x1fc48bc0,FUN_1fc48bc0, +0x1fc48c78,FUN_1fc48c78, +0x1fc48d00,FUN_1fc48d00, +0x1fc48d3c,FUN_1fc48d3c, +0x1fc48dc8,FUN_1fc48dc8, +0x1fc48e00,FUN_1fc48e00, +0x1fc48eec,FUN_1fc48eec, +0x1fc49028,FUN_1fc49028, +0x1fc492b0,FUN_1fc492b0, +0x1fc493b4,FUN_1fc493b4, +0x1fc494f0,FUN_1fc494f0, +0x1fc49814,FUN_1fc49814, +0x1fc4990c,FUN_1fc4990c, +0x1fc499c4,FUN_1fc499c4, +0x1fc49a00,FUN_1fc49a00, +0x1fc49a8c,FUN_1fc49a8c, +0x1fc49b38,FUN_1fc49b38, +0x1fc49cf0,FUN_1fc49cf0, +0x1fc49e8c,FUN_1fc49e8c, +0x1fc49ec0,FUN_1fc49ec0, +0x1fc49fb0,FUN_1fc49fb0, +0x1fc4a100,FUN_1fc4a100, +0x1fc4a2d0,FUN_1fc4a2d0, +0x1fc4a460,FUN_1fc4a460, +0x1fc4a488,FUN_1fc4a488, +0x1fc4a4e4,FUN_1fc4a4e4, +0x1fc4a530,FUN_1fc4a530, +0x1fc4a570,FUN_1fc4a570, +0x1fc4a688,FUN_1fc4a688, +0x1fc4a7e0,FUN_1fc4a7e0, diff --git a/docs/rea-setup.md b/docs/rea-setup.md new file mode 100644 index 0000000..b81cd6d --- /dev/null +++ b/docs/rea-setup.md @@ -0,0 +1,89 @@ +# REA setup used for this project + +[REA](https://github.com/morluto/rea) is the reverse-engineering MCP server and +CLI used to analyze the Indy PROM. This note records the exact environment so +the analysis can be reproduced. + +## Installed components + +| Component | Version | Location | +| --- | --- | --- | +| Node.js | 22.23.4 | `/opt/node-v22.23.4` (`node`, `npm`, `npx` symlinked into `/usr/local/bin`) | +| REA | 6.8.0 | global npm install; `rea` in `/usr/local/bin` | +| Ghidra | 12.1.2 | `/opt/ghidra` | +| JDK | OpenJDK 21.0.13 | `/usr/lib/jvm/java-21-openjdk-amd64` | + +REA requires Node `^22.23.4 || ^24.22.1 || >=26.12.1`. Debian 13 ships +Node 20.19, so the official Node 22 tarball was installed under `/opt` instead +of the distribution package. + +## Registration + +The setup was applied for OpenCode only: + +```bash +export GHIDRA_INSTALL_DIR=/opt/ghidra +export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 + +rea setup --client opencode --dry-run --json # reviewed plan +rea setup --client opencode --yes +``` + +The plan had two actions and no provider installation: + +- update `/home/arti/.config/opencode/opencode.json` with an MCP server entry + whose launcher is `/usr/local/bin/rea mcp`; +- install the bundled `reverse-engineer-anything` skill under + `~/.agents/skills/reverse-engineer-anything`. + +Setup preserved the existing `kagi` MCP entry and wrote +`opencode.json.rea.backup`. OpenCode must be restarted before the MCP server is +available in a session; the CLI (`rea ...`) works immediately. + +`rea doctor` after setup reports the Ghidra provider as available with +`GHIDRA_INSTALL_DIR=/opt/ghidra`. + +The machine's SSH key is not authorized on the Gitea host, so the git remote +uses HTTPS with the configured `tea` credential helper: + +``` +https://git.wut.ee/opencode.energia.arti.ee/sgi-indy.git +``` + +## The MIPS problem + +REA 6.8.0's `--target-format raw-binary` lane accepts `x86`, `x86_64`, `arm`, +`arm64`, `6502` and `m68k` only. There is no raw MIPS architecture. MIPS is +supported through the Ghidra ELF lane (`docs/ghidra-mips.md` upstream). + +The Indy PROM is a headerless 512 KiB dump, so the analysis uses +`tools/make-elf-wrapper.py` to build a load-image-only ELF32 big-endian MIPS +file: + +- `e_type = ET_EXEC`, `e_machine = EM_MIPS`; +- `e_flags = 0x50000000` (`EF_MIPS_ARCH_32`, no ABI bits); +- no `PT_MIPS_ABIFLAGS` record; +- one `PT_LOAD` mapping file offset `0x1000` to virtual address `0x1FC00000`; +- entry point `0x1FC00000`. + +That combination hits REA's "recordless legacy" admission. REA records that o32 +is an analysis assumption, not a declared ABI fact, and pins Ghidra to +`MIPS:BE:32:default`. The import log confirms: + +``` +Using Language/Compiler: MIPS:BE:32:default:default +``` + +## What was collected + +The MCP session performed: + +- `binary_overview` (segments, counts); +- `list_procedures` (1,074 functions); +- `list_strings` (1,418 defined strings); +- `batch_decompile` of the reset path, main entry and exception handlers; +- `read_function_instructions` for the reset handler and a cache/HPC3 stub; +- `export_evidence_bundle` to `evidence/rea-bundle.json`. + +The bundle is a local, git-ignored artifact. `tools/rom-map.py` reads it to +produce the checked-in JSON map and CSV function index. diff --git a/docs/rom-map.json b/docs/rom-map.json new file mode 100644 index 0000000..10271a7 --- /dev/null +++ b/docs/rom-map.json @@ -0,0 +1,494 @@ +{ + "source": { + "bytes": 524288, + "sha1": "ac663a4db68528e400487e98cbf901f620fb30ce", + "sha256": "333a0b2e39bb828a28a758b6f633ae7290030129dc747297a1be68dc317b86ae", + "version_string": "SGI Version 5.3 Rev B10 R4X00/R5000 IP24 Feb 12, 1996", + "last_non_ff_offset": 522751, + "last_non_ff_address": "0x1fc7f9ff" + }, + "image": { + "base": "0x1fc00000", + "end": "0x1fc80000", + "page_size": 4096 + }, + "code": { + "start": "0x1fc00000", + "last_function": "0x1fc4a7e0", + "count": 1074 + }, + "strings": { + "start": "0x1fc01b7c", + "end": "0x1fc7c16b", + "count": 1418 + }, + "page_kinds": { + "exception-vectors": 1, + "code": 74, + "strings": 17, + "binary-data": 31, + "zero-data": 5 + }, + "regions": [ + { + "kind": "exception-vectors", + "start": "0x1fc00000", + "end": "0x1fc01000", + "procedures": 10, + "strings": 0 + }, + { + "kind": "code", + "start": "0x1fc01000", + "end": "0x1fc4b000", + "procedures": 1064, + "strings": 103 + }, + { + "kind": "strings", + "start": "0x1fc4b000", + "end": "0x1fc56000", + "procedures": 0, + "strings": 1272 + }, + { + "kind": "binary-data", + "start": "0x1fc56000", + "end": "0x1fc6d000", + "procedures": 0, + "strings": 0 + }, + { + "kind": "strings", + "start": "0x1fc6d000", + "end": "0x1fc6f000", + "procedures": 0, + "strings": 30 + }, + { + "kind": "zero-data", + "start": "0x1fc6f000", + "end": "0x1fc71000", + "procedures": 0, + "strings": 0 + }, + { + "kind": "binary-data", + "start": "0x1fc71000", + "end": "0x1fc72000", + "procedures": 0, + "strings": 0 + }, + { + "kind": "strings", + "start": "0x1fc72000", + "end": "0x1fc74000", + "procedures": 0, + "strings": 2 + }, + { + "kind": "zero-data", + "start": "0x1fc74000", + "end": "0x1fc76000", + "procedures": 0, + "strings": 0 + }, + { + "kind": "binary-data", + "start": "0x1fc76000", + "end": "0x1fc78000", + "procedures": 0, + "strings": 0 + }, + { + "kind": "zero-data", + "start": "0x1fc78000", + "end": "0x1fc79000", + "procedures": 0, + "strings": 0 + }, + { + "kind": "binary-data", + "start": "0x1fc79000", + "end": "0x1fc7b000", + "procedures": 0, + "strings": 0 + }, + { + "kind": "strings", + "start": "0x1fc7b000", + "end": "0x1fc7d000", + "procedures": 0, + "strings": 11 + }, + { + "kind": "binary-data", + "start": "0x1fc7d000", + "end": "0x1fc80000", + "procedures": 0, + "strings": 0 + } + ], + "exception_vectors": [ + { + "offset": 0, + "address": "0x1fc00000", + "word": "0x0bf000f0", + "target": "0x1fc003c0" + }, + { + "offset": 16, + "address": "0x1fc00010", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 32, + "address": "0x1fc00020", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 48, + "address": "0x1fc00030", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 64, + "address": "0x1fc00040", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 80, + "address": "0x1fc00050", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 96, + "address": "0x1fc00060", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 112, + "address": "0x1fc00070", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 128, + "address": "0x1fc00080", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 144, + "address": "0x1fc00090", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 160, + "address": "0x1fc000a0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 176, + "address": "0x1fc000b0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 192, + "address": "0x1fc000c0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 208, + "address": "0x1fc000d0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 224, + "address": "0x1fc000e0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 240, + "address": "0x1fc000f0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 256, + "address": "0x1fc00100", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 272, + "address": "0x1fc00110", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 288, + "address": "0x1fc00120", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 304, + "address": "0x1fc00130", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 320, + "address": "0x1fc00140", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 336, + "address": "0x1fc00150", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 352, + "address": "0x1fc00160", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 368, + "address": "0x1fc00170", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 384, + "address": "0x1fc00180", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 400, + "address": "0x1fc00190", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 416, + "address": "0x1fc001a0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 432, + "address": "0x1fc001b0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 448, + "address": "0x1fc001c0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 464, + "address": "0x1fc001d0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 480, + "address": "0x1fc001e0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 496, + "address": "0x1fc001f0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 512, + "address": "0x1fc00200", + "word": "0x0bf003c8", + "target": "0x1fc00f20" + }, + { + "offset": 528, + "address": "0x1fc00210", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 544, + "address": "0x1fc00220", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 560, + "address": "0x1fc00230", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 576, + "address": "0x1fc00240", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 592, + "address": "0x1fc00250", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 608, + "address": "0x1fc00260", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 624, + "address": "0x1fc00270", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 640, + "address": "0x1fc00280", + "word": "0x0bf003ca", + "target": "0x1fc00f28" + }, + { + "offset": 656, + "address": "0x1fc00290", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 672, + "address": "0x1fc002a0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 688, + "address": "0x1fc002b0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 704, + "address": "0x1fc002c0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 720, + "address": "0x1fc002d0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 736, + "address": "0x1fc002e0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 752, + "address": "0x1fc002f0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 768, + "address": "0x1fc00300", + "word": "0x0bf003c6", + "target": "0x1fc00f18" + }, + { + "offset": 784, + "address": "0x1fc00310", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 800, + "address": "0x1fc00320", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 816, + "address": "0x1fc00330", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 832, + "address": "0x1fc00340", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 848, + "address": "0x1fc00350", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 864, + "address": "0x1fc00360", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 880, + "address": "0x1fc00370", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 896, + "address": "0x1fc00380", + "word": "0x0bf00390", + "target": "0x1fc00e40" + }, + { + "offset": 912, + "address": "0x1fc00390", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 928, + "address": "0x1fc003a0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + }, + { + "offset": 944, + "address": "0x1fc003b0", + "word": "0x0bf001f1", + "target": "0x1fc007c4" + } + ] +} diff --git a/docs/rom-map.md b/docs/rom-map.md new file mode 100644 index 0000000..3763b1c --- /dev/null +++ b/docs/rom-map.md @@ -0,0 +1,368 @@ +# Indy IP24 PROM 5.3 Rev B10: structural map + +This document maps the newest known SGI Indy boot PROM, +`ip24prom.070-9101-011.bin`. All addresses are PROM addresses as mapped by the +hardware; file offsets are `address - 0x1FC00000`. + +Facts in this document come from three places: + +1. byte-level inspection of the dump (padding, strings, jump vectors); +2. Ghidra 12.1.2 auto-analysis driven through REA 6.8.0; +3. MAME's IP22/IP24 machine definitions for the surrounding address map. + +Function names such as `FUN_1fc02000` are Ghidra auto-generated. The +interpretations attached to them are inferences from their code, call sites and +the strings they reference; they are labelled as such. + +## Identity + +| Field | Value | +| --- | --- | +| File | `ip24prom.070-9101-011.bin` | +| Size | 524,288 bytes (512 KiB) | +| Part number | 070-9101-011 | +| Version | `SGI Version 5.3 Rev B10 R4X00/R5000 IP24 Feb 12, 1996` | +| SHA-1 | `ac663a4db68528e400487e98cbf901f620fb30ce` | +| SHA-256 | `333a0b2e39bb828a28a758b6f633ae7290030129dc747297a1be68dc317b86ae` | +| MAME set | `indy_4610` | +| Load address | `0x1FC00000` | +| Analyzed functions | 1,074 | +| Analyzed strings | 1,418 | + +The SHA-1 matches MAME's `src/mame/sgi/indy_indigo2.cpp` ROM entry exactly. + +### The other dumps in the set + +The `indy_4610` set contains four PROM revisions. SHA-1 values below are the +ones MAME records. + +| File | Version | SHA-1 | +| --- | --- | --- | +| `ip24prom.070-9101-005.bin` | 5.1.2 Rev B4, 9 Dec 1993 | `28b769b28218a1fcd0400dceef9a284dcfbdda5b` | +| `ip24prom.070-9101-007.bin` | 5.0 Rev B6, 28 Sep 1994 | `ade54cd2ecb7064957f8602894f05685e2f4e8fb` | +| `ip24prom.070-9101-008.bin` | 5.3 Rev B7, 16 Feb 1995 | `a752a4aef7e2c6086b8b0244e9f064861a11870f` | +| `ip24prom.070-9101-011.bin` | 5.3 Rev B10, 12 Feb 1996 | `ac663a4db68528e400487e98cbf901f620fb30ce` | + +`011` is the newest, and the only one whose version string claims +`R4X00/R5000` support. MAME selects it as the default BIOS for both +`indy_4610` (R4600) and `indy_5015` (R5000). + +## How the image was opened + +The dump is headerless. REA's raw-binary lane has no MIPS architecture, so a +minimal ELF wrapper is produced by `tools/make-elf-wrapper.py`: + +- ELF32, big-endian, `ET_EXEC`, `EM_MIPS`; +- `e_flags = 0x50000000` (`EF_MIPS_ARCH_32`, no ABI field); +- no `PT_MIPS_ABIFLAGS` record; +- one `PT_LOAD` at file offset `0x1000`, virtual address `0x1FC00000`; +- entry `0x1FC00000`. + +REA admits this through its recordless legacy MIPS lane and pins Ghidra to +`MIPS:BE:32:default`. The import log confirms that language. The ROM bytes are +not modified. + +The declared ABI is therefore absent by construction: o32 calling conventions +are an analysis assumption of the lane, not a property recovered from the ROM. +This affects parameter recovery in decompiled output, not instruction decoding +or control flow. + +## Physical placement + +MAME's IP24 map places the 512 KiB BIOS window at `0x1FC00000`. Through the MIPS +segments the same bytes are also visible at: + +| View | Address | +| --- | --- | +| Physical / KUSEG | `0x1FC00000` | +| KSEG0 (cached) | `0x9FC00000` | +| KSEG1 (uncached) | `0xBFC00000` | + +Ghidra analyzed the physical mapping. The reset code itself uses KSEG0/KSEG1 +addresses such as `0x9FC7DD18`, `0x9FC4AAC8` and `0xBFA0xxxx`; those appear in +decompiled output as their unmapped KSEG forms. + +Relevant neighbours from MAME (`src/mame/sgi/indy_indigo2.cpp`): + +| Range | Device | +| --- | --- | +| `0x00000000` - `0x0007FFFF` | Alias for first 512 KiB of RAM | +| `0x08000000` - `0x17FFFFFF` | Low system memory | +| `0x1F000000` - `0x1F3FFFFF` | GIO64 graphics | +| `0x1F400000` - `0x1F5FFFFF` | GIO64 EXP0 | +| `0x1FA00000` - `0x1FA1FFFF` | Memory controller | +| `0x1FB00000` - `0x1FB7FFFF` | HPC3 CHIP1 | +| `0x1FB80000` - `0x1FBFFFFF` | HPC3 CHIP0 | +| `0x1FC00000` - `0x1FFFFFFF` | BIOS (this ROM) | +| `0x20000000` - `0x2FFFFFFF` | High system memory | +| `0x80000000` - `0xFFFFFFFF` | EISA memory | + +The reset code touches `0xBFA00xxx`/`0xBFA01xxx` (KSEG1 alias of the memory +controller) and `0xBFBD9xxx`/`0xBFBBxxxx` (KSEG1 alias inside HPC3 CHIP0). + +## Image layout + +Page-level detail is in [rom-map.json](rom-map.json). The compressed view: + +| File offset | PROM address | Size | Content | +| --- | --- | --- | --- | +| `0x00000` - `0x003BF` | `0x1FC00000` - `0x1FC003BF` | 960 B | 60-entry, 16-byte exception jump table | +| `0x003C0` - `0x4A89F` | `0x1FC003C0` - `0x1FC4A89F` | ~298 KiB | Reset handler, code and inline diagnostic strings; 1,074 functions, last one starting at `0x1FC4A7E0` | +| `0x4A8A0` - `0x5568F` | `0x1FC4A8A0` - `0x1FC5568F` | ~44 KiB | Core message blob: boot sequence, diagnostics, monitor help, environment and ARCS strings | +| `0x56000` - `0x6CFFF` | `0x1FC56000` - `0x1FC6CFFF` | 92 KiB | Binary tables; no Ghidra-defined strings | +| `0x6D000` - `0x6EA4C` | `0x1FC6D000` - `0x1FC6EA4C` | ~6.5 KiB | Path-test strings and the environment variable/default table | +| `0x6EA4D` - `0x7BF8F` | `0x1FC6EA4D` - `0x1FC7BF8F` | ~54 KiB | Mostly zero/binary pages plus two isolated strings | +| `0x7BF90` - `0x7C16B` | `0x1FC7BF90` - `0x1FC7C16B` | ~480 B | SCSI tape drive name table | +| `0x7C16C` - `0x7F9FF` | `0x1FC7C16C` - `0x1FC7F9FF` | ~14 KiB | Remaining data tables | +| `0x7FA00` - `0x7FFFF` | `0x1FC7FA00` - `0x1FC7FFFF` | 1536 B | `0xFF` padding | + +Only four `0xFF` runs of 16 bytes or more exist; three are tiny alignment holes +inside the string area, and the real padding is the final 1,536 bytes. The last +non-`0xFF` byte is at file offset `0x7F9FF`. + +The code and message regions overlap at the page level: page +`0x1FC4A000`-`0x1FC4AFFF` holds the last nine functions and 64 strings, so the +"code ends" boundary above is the end of executable content, not the start of +all strings. + +## Exception vector table + +The first 960 bytes are 60 cells of 16 bytes: + +``` +0x1FC00000 + (index * 0x10) +``` + +Each cell begins with an absolute MIPS `J` instruction followed by three +`NOP`s. The reset handler lives at `0x1FC003C0`, immediately after the table, so +the final four 16-byte slots are not table entries. + +Observed targets: + +| Cell address | Index | Target | Count | +| --- | --- | --- | --- | +| `0x1FC00000` | 0 | `0x1FC003C0` (reset handler) | 1 | +| `0x1FC00200` | 32 | `0x1FC00F20` | 1 | +| `0x1FC00280` | 40 | `0x1FC00F28` | 1 | +| `0x1FC00300` | 48 | `0x1FC00F18` | 1 | +| `0x1FC00380` | 56 | `0x1FC00E40` | 1 | +| all other cells | - | `0x1FC007C4` (shared handler) | 55 | + +The architectural MIPS R4000 vector offsets (`0x080` TLB refill, `0x100` general +exception, `0x180` XTLB refill, `0x200` cache error) fall inside this table. Of +those, only `0x200` is special; the rest use the shared handler. The meaning of +indices 32, 40, 48 and 56 is not established by this analysis. One plausible +reading is an SGI dispatch table indexed by a cause-derived value rather than by +the architectural vector offset, but that is an inference, not an observation. + +The four special targets all land inside the block at `0x1FC00E40`-`0x1FC00F28`. +Ghidra did not split that block into separate functions; auto-analysis treats +`0x1FC00DCC` as the nearest function start. The bytes there are worth a focused +follow-up with manual function creation. + +## Reset and boot flow + +### Reset handler, `0x1FC003C0` + +Observed instruction sequence (REA/Ghidra): + +1. `lui v0, 0x3041; mtc0 v0, Status` and clear `Cause`, `PageMask`, + `WatchLo`, `WatchHi`. +2. `Config = 2`, then test `Config` bit 15. If clear, call `FUN_1fc0a810`, + which reads and rewrites a CP0 configuration value and writes + `0xBFA00000 = 0x10000`. +3. Write memory-controller registers at `0xBFA000EC`, `0xBFA000FC`, + `0xBFA00004`, `0xBFA0000C`, `0xBFA00084`, `0xBFA000D4`, + `0xBFA000DC` and `0xBFA0002C`. The value stored at `0xBFA00084` is loaded + from `0x9FC7DD18`. +4. Poll `0xBFA01004` until the counter advances by at least `0x271`. +5. Call `0x1FC00A3C`, `0x1FC00BD0`, `0x1FC00DCC`, `0x1FC00D58`. +6. Set `s8 = 0x9FC03660`, then call `0x1FC039E0`, `0x1FC03DE0`, + `0x1FC03EB0`, `0x1FC01070`. +7. Sample `0xBFA000C4`, test bits `0x2200`, optionally set low bits of + `0xBFA00004`. +8. Call `0x1FC009D4`, then `j 0x1FC02000`. + +The Ghidra decompiler renders the end of this function as an infinite loop +because it does not model the final `j` as a return. The instruction listing is +the authority here. + +### Cold-start main, `0x1FC02000` + +The function reached by the reset jump initializes HPC3/IOC registers around +`0xBFBD9xxx`, calls a long sequence of hardware setup routines, and then reads +the `diagmode` environment variable at `0x9FC4AAC8`. + +Two paths follow: + +- diagnostics/menu path (`FUN_1fc02a50`) when the diagnostic state is set; +- autoboot path (`FUN_1fc0fb10`) otherwise. + +`FUN_1fc0fb10` reads `AutoLoad` (`0x1FC4D090`) and `NoAutoLoad` +(`0x1FC4D09C`). If autoload is enabled it prints the maintenance prompt +`To perform system maintenance instead, press ` and attempts a boot; a +failure prints `Unable to boot; press any key to continue:` and falls back +through `FUN_1fc00624`. + +`FUN_1fc02a50` is the menu/boot loop. It reads `VERBOSE` and `diskless`, +touches IOC interrupt-enable bits, and loops through the maintenance menu whose +strings sit at `0x1FC4D99C`: + +- `Install System Software` +- `Recover System` +- `Enter Command Monitor` +- `Select Keyboard Layout` + +The same area contains `Autoboot failed`, `Diagnostics` and +`Command Monitor. Type "exit" %sto return to the menu.` + +### Boot-sequence messages + +The ordered diagnostic strings beginning at `0x1FC4A8A0` describe the intended +power-on sequence: + +1. `Power switch released.` +2. `Initializing primary/secondary caches.` +3. `Secondary cache size is 0x%x (%d Kbytes)` +4. `Initializing exception handlers.` +5. `Initializing Status Register.` +6. `Enabling PLP, ISDN B/A DMA channels.` +7. `Selecting Gen Control register bits.` +8. `Initialize Map Polarity.` +9. `Deasserting VIDEO Reset.` +10. `Playing startup tune.` +11. `Initializing environment.` +12. `Executing remainder of diagnostic.` +13. `Initializing software and devices.` +14. `All initialization and diagnostic completed.` + +## Function anchors + +| Address | Auto name | Interpretation | Basis | +| --- | --- | --- | --- | +| `0x1FC00000` | `entry` | Table slot 0 thunk to the reset handler | Ghidra symbol, thunk target | +| `0x1FC003C0` | `FUN_1fc003c0` | Reset handler | CP0 init, memory-controller setup, jump to main | +| `0x1FC00624` | `FUN_1fc00624` | Shared exception/fallback path | Vector-table default target; cache and memory scrub code | +| `0x1FC00DCC` | `FUN_1fc00dcc` | Short HPC3/cache stub | Writes `0xBFB91004`, `0xBFBC0003`, `0xBFBC0007`; nearest function for special vectors | +| `0x1FC02000` | `FUN_1fc02000` | Cold-start main | Reached by reset jump; HPC3/IOC init; branches to diagnostics or autoboot | +| `0x1FC02A50` | `FUN_1fc02a50` | Menu/boot loop | Reads `VERBOSE`/`diskless`; loops over maintenance menu | +| `0x1FC0FB10` | `FUN_1fc0fb10` | Autoboot policy | Reads `AutoLoad`/`NoAutoLoad`; maintenance prompt and boot failure text | + +The full list of 1,074 function starts is in +[procedure-index.csv](procedure-index.csv). The CSV has three columns: +`address,name,thunk_target`. + +## Firmware feature map + +### Command monitor + +Help strings at `0x1FC4ACE8`-`0x1FC4B054` enumerate the monitor commands: + +`autoboot` (`auto`), `boot`, `checksum`, `date`, `dump`, `exit`, `fill`, `get`, +`go`, `help`/`?`, `initialize` (`init`), `inventory`, `passwd`, `printenv`, +`put`, `reboot`, `resetenv`, `resetpw`, `setenv`, `single`, `unsetenv`, +`version`. + +The banner is `PROM Monitor %s (BE)` at `0x1FC4B090`, followed by the version +string. + +### Boot targets + +Strings at `0x1FC4AAF0`-`0x1FC4AB44` name the default load paths: + +- `bootp()ide.IP24` +- `sashIP24` +- `bootp()sashIP24` +- `bootp()unix.IP24` + +Elsewhere the image references `sashARCS`, `part(8)sashARCS`, `/stand/ide`, +IDE distribution CD boot, and ARCS-style boot. + +### Diagnostics + +Diagnostic result strings name the tests the PROM can run: + +- memory probe, memory data test, memory address test, walking-bit test; +- cache diagnostic; +- graphics diagnostic; +- interrupt controller diagnostic; +- SCSI controller, SCSI device/cable, WD SCSI0 path test; +- RTC path test, INT path test, data path test; +- PC keyboard/mouse controller, keyboard, mouse; +- CPU base board, graphics board, CPU module, SIMM replacement prompts. + +The display-side strings also expose the VDMA and DMA error registers +(`DMA_CAUSE`, `DMA_MEMADR`, `CPU_MEMACC`, `GIO_MEMACC`, `CPU_ERR_ADDR`, +`CPU_ERR_STAT`, `GIO_ERR_ADDR`, `GIO_ERR_STAT`) and the MIPS exception dump +fields (`ErrEPC`, `BadVaddr`, `Cause`, `Status`, `CacheErr`, `CpuParityErr`, +`GioParityErr`, `LIOstatus0..2`, `CpuErrorAddr`, `GioErrorAddr`). + +### Environment variables + +The environment table around `0x1FC6E4F0`-`0x1FC6EA4C` and the help strings +around `0x1FC4CD00`-`0x1FC4E000` cover: + +`AutoLoad`, `NoAutoLoad`, `diagmode`, `console`, `diskless`, `VERBOSE`, +`eaddr`, `netaddr`, `srvaddr`, `gateaddr`, `netinsthost`, `netinstfile`, +`tapedevice`, `scsihostid`, `scsiretries`, `pagecolor`, `TimeZone`, +`autopower`, `SystemPartition`, `OSLoader`, `OSLoadPartition`, +`OSLoadFilename`/`OSLoadFileName`, `OSLoadOptions`, `ConsoleIn`, +`ConsoleOut`, `ConsoleWarning`, `passwd_key`, `netpasswd_key`. + +The NVRAM path has its own messages: `NVRAM checksum is incorrect: +reinitializing.`, `nvchecksum:`, `Checksum mismatch: is 0x%x, s/b 0x%x`, +`Enter new password:`, `Confirm new password:`, `Password cleared.` and the +password-jumper warnings. + +### ARCS and SCSI tables + +The string blob around `0x1FC5549C`-`0x1FC55524` contains the ARCS component +names `ExceptionBlock`, `SystemParameterBlock`, `FreeContiguous`, `FreeMemory`, +`BadMemory`, `LoadedProgram`, `FirmwareTemporary` and `FirmwarePermanent`. + +The cluster at `0x1FC7BF90`-`0x1FC7C16B` is a SCSI tape-drive name table: +`CIPHER`, `TANDBERG TDC 3660`, `ARCHIVE VIPER 150`, `VIPER 60`, `Python`, +`EXABYTE` and `KENNEDY`. + +The sentinel value `cafebabedeadbeef` appears at `0x1FC6EA4C`; it is likely an +uninitialised-memory pattern used by the environment/NVRAM code, but that is an +inference. + +## Confidence and limitations + +- Function boundaries and names come from Ghidra auto-analysis. There are no + symbols in the image beyond the imported `entry` label. +- The ELF wrapper declares no ABI. o32 is an analysis assumption, so recovered + parameter counts and types are weaker than the control flow. +- The vector table's 60 x 16-byte structure and observed targets are facts. + What each index means is not established. +- The decompiler output is heuristic. Where it conflicts with the instruction + listing (for example the reset handler's final jump), the instruction listing + is authoritative. +- String inventory covers Ghidra-defined string data only (1,418 entries). + Undefined or packed text may be missing, and an absent string is not proof + that the text is absent from the artifact. +- No code was executed. Nothing here establishes runtime behaviour. +- Ghidra 12.1.2 was used; REA's bridge is verified against 12.1.4 and accepts + other 12.1.x builds. + +## Reproduce + +```bash +./tools/fetch-roms.sh +./tools/make-elf-wrapper.py \ + roms/ip24prom.070-9101-011.bin -o roms/indy-ip24-5.3-b10.elf + +export GHIDRA_INSTALL_DIR=/opt/ghidra +rea analyze roms/indy-ip24-5.3-b10.elf --provider ghidra --json +``` + +The checked-in artifacts were produced with the REA MCP session described in +[rea-setup.md](rea-setup.md), then: + +```bash +./tools/rom-map.py roms/ip24prom.070-9101-011.bin \ + --evidence evidence/rea-bundle.json \ + --output docs/rom-map.json \ + --procedures-csv docs/procedure-index.csv +``` diff --git a/roms/README.md b/roms/README.md new file mode 100644 index 0000000..1cd6692 --- /dev/null +++ b/roms/README.md @@ -0,0 +1,15 @@ +# ROM dumps + +The binaries themselves are not committed; they are copyrighted SGI firmware. +Run `../tools/fetch-roms.sh` to download the MAME 0.236 `indy_4610` set and +verify it against `SHA1SUMS`. + +| File | Version | Notes | +| --- | --- | --- | +| `ip24prom.070-9101-005.bin` | 5.1.2 Rev B4, 1993-12-09 | Oldest dump in the set | +| `ip24prom.070-9101-007.bin` | 5.0 Rev B6, 1994-09-28 | | +| `ip24prom.070-9101-008.bin` | 5.3 Rev B7, 1995-02-16 | | +| `ip24prom.070-9101-011.bin` | 5.3 Rev B10, 1996-02-12 | Newest; `.elf` wrapper used for analysis | + +`indy-ip24-5.3-b10.elf` is generated locally by +`../tools/make-elf-wrapper.py` and is also ignored. diff --git a/roms/SHA1SUMS b/roms/SHA1SUMS new file mode 100644 index 0000000..1827111 --- /dev/null +++ b/roms/SHA1SUMS @@ -0,0 +1,4 @@ +ac663a4db68528e400487e98cbf901f620fb30ce ip24prom.070-9101-011.bin +a752a4aef7e2c6086b8b0244e9f064861a11870f ip24prom.070-9101-008.bin +ade54cd2ecb7064957f8602894f05685e2f4e8fb ip24prom.070-9101-007.bin +28b769b28218a1fcd0400dceef9a284dcfbdda5b ip24prom.070-9101-005.bin diff --git a/tools/fetch-roms.sh b/tools/fetch-roms.sh new file mode 100755 index 0000000..bbc551d --- /dev/null +++ b/tools/fetch-roms.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Fetch the MAME 0.236 SGI Indy (IP24) PROM set and verify every dump. +# +# The four images are the complete ROM set for MAME's indy_4610 machine. +# SHA-1 values come from MAME's src/mame/sgi/indy_indigo2.cpp ROM definitions. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +DEST="${ROOT}/roms" +WORK="$(mktemp -d)" +trap 'rm -rf "${WORK}"' EXIT + +URL="https://archive.org/download/mame-0.236-roms-split/MAME%200.236%20ROMs%20%28split%29/indy_4610.zip" + +mkdir -p "${DEST}" + +echo "Downloading ${URL}" +curl -fSL --retry 3 -o "${WORK}/indy_4610.zip" "${URL}" +unzip -q -o "${WORK}/indy_4610.zip" -d "${DEST}" + +declare -A SHA1=( + [ip24prom.070-9101-005.bin]=28b769b28218a1fcd0400dceef9a284dcfbdda5b + [ip24prom.070-9101-007.bin]=ade54cd2ecb7064957f8602894f05685e2f4e8fb + [ip24prom.070-9101-008.bin]=a752a4aef7e2c6086b8b0244e9f064861a11870f + [ip24prom.070-9101-011.bin]=ac663a4db68528e400487e98cbf901f620fb30ce +) + +status=0 +for name in "${!SHA1[@]}"; do + got="$(sha1sum "${DEST}/${name}" | cut -d' ' -f1)" + if [[ "${got}" == "${SHA1[${name}]}" ]]; then + printf 'ok %s %s\n' "${got}" "${name}" + else + printf 'FAIL %s %s (expected %s)\n' "${got}" "${name}" "${SHA1[${name}]}" >&2 + status=1 + fi +done + +exit "${status}" diff --git a/tools/make-elf-wrapper.py b/tools/make-elf-wrapper.py new file mode 100755 index 0000000..1b3e0e9 --- /dev/null +++ b/tools/make-elf-wrapper.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +"""Wrap a raw SGI Indy IP24 PROM dump in a minimal ELF32 big-endian MIPS image. + +REA's Ghidra MIPS lane accepts ELF32 ET_EXEC images, but not headerless MIPS +binaries. This tool produces a load-image-only wrapper so the PROM can be +opened at its real physical address (0x1FC00000) without modifying the ROM +bytes. + +The wrapper deliberately declares EF_MIPS_ARCH_32 with an unspecified ABI and +provides no PT_MIPS_ABIFLAGS record. That selects REA's "recordless legacy" +admission, which pins Ghidra to MIPS:BE:32:default, a suitable match for the +R4000/R4600-era Indy PROM. +""" + +from __future__ import annotations + +import argparse +import os +import struct +import sys + +ROM_BASE = 0x1FC00000 +PAGE_SIZE = 0x1000 +EM_MIPS = 8 +ET_EXEC = 2 +EF_MIPS_ARCH_32 = 0x50000000 + +EI_NIDENT = 16 + + +def build_elf(rom: bytes, base: int, entry: int) -> bytes: + payload_offset = PAGE_SIZE + e_ident = bytearray(EI_NIDENT) + e_ident[0:4] = b"\x7fELF" + e_ident[4] = 1 # ELFCLASS32 + e_ident[5] = 2 # ELFDATA2MSB + e_ident[6] = 1 # EV_CURRENT + e_ident[7] = 0 # ELFOSABI_NONE + + e_entry = entry + e_phoff = 52 + e_shoff = 0 + e_flags = EF_MIPS_ARCH_32 + e_ehsize = 52 + e_phentsize = 32 + e_phnum = 1 + e_shentsize = 0 + e_shnum = 0 + e_shstrndx = 0 + + ehdr = struct.pack( + ">16sHHIIIIIHHHHHH", + bytes(e_ident), + ET_EXEC, + EM_MIPS, + 1, + e_entry, + e_phoff, + e_shoff, + e_flags, + e_ehsize, + e_phentsize, + e_phnum, + e_shentsize, + e_shnum, + e_shstrndx, + ) + + # PT_LOAD: file offset 0x1000 -> virtual address base, whole ROM. + phdr = struct.pack( + ">IIIIIIII", + 1, # PT_LOAD + payload_offset, + base, + base, + len(rom), + len(rom), + 5, # PF_R | PF_X + PAGE_SIZE, + ) + + header = ehdr + phdr + assert len(header) <= payload_offset + return header.ljust(payload_offset, b"\x00") + rom + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("rom", help="raw PROM binary (512 KiB)") + parser.add_argument("-o", "--output", help="output ELF path") + parser.add_argument("--base", type=lambda s: int(s, 0), default=ROM_BASE) + parser.add_argument("--entry", type=lambda s: int(s, 0), default=None) + args = parser.parse_args() + + with open(args.rom, "rb") as fh: + rom = fh.read() + if len(rom) != 0x80000: + print( + f"warning: {args.rom} is {len(rom)} bytes, expected 524288", file=sys.stderr + ) + + entry = args.base if args.entry is None else args.entry + out = args.output + if out is None: + stem, _ = os.path.splitext(args.rom) + out = stem + ".elf" + + with open(out, "wb") as fh: + fh.write(build_elf(rom, args.base, entry)) + print(out) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/rom-map.py b/tools/rom-map.py new file mode 100755 index 0000000..09130b2 --- /dev/null +++ b/tools/rom-map.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +"""Build a structural map of an SGI Indy IP24 PROM image. + +Combines byte-level layout facts (padding, printable density, MIPS jump +vectors) with procedure and string inventories exported from a REA session. + +Usage: + tools/rom-map.py roms/ip24prom.070-9101-011.bin \ + --evidence evidence/rea-bundle.json \ + --output docs/rom-map.json +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import struct +from collections import Counter +from pathlib import Path + +BASE = 0x1FC00000 +PAGE = 0x1000 +VECTOR_STRIDE = 0x10 +# 60 entries of 16 bytes. The reset handler itself starts at 0x1FC003C0 and +# overwrites what would otherwise be the final four entries. +VECTOR_TABLE_SIZE = 0x3C0 + + +def sha1(data: bytes) -> str: + return hashlib.sha1(data).hexdigest() + + +def sha256(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def printable_ratio(chunk: bytes) -> float: + if not chunk: + return 0.0 + printable = sum( + 1 for b in chunk if 0x20 <= b <= 0x7E or b in (0x09, 0x0A, 0x0D) + ) + return round(printable / len(chunk), 4) + + +def zero_ratio(chunk: bytes) -> float: + return round(chunk.count(0) / len(chunk), 4) if chunk else 0.0 + + +def ff_ratio(chunk: bytes) -> float: + return round(chunk.count(0xFF) / len(chunk), 4) if chunk else 0.0 + + +def j_target(word: int, pc: int) -> int | None: + """Decode a MIPS J/JAL absolute target, or None for other opcodes.""" + if (word >> 26) not in (0x02, 0x03): + return None + return ((pc + 4) & 0xF0000000) | ((word & 0x03FFFFFF) << 2) + + +def vector_table(rom: bytes) -> list[dict]: + rows = [] + for off in range(0, min(VECTOR_TABLE_SIZE, len(rom)), VECTOR_STRIDE): + word = struct.unpack(">I", rom[off : off + 4])[0] + target = j_target(word, BASE + off) + rows.append( + { + "offset": off, + "address": f"0x{BASE + off:08x}", + "word": f"0x{word:08x}", + "target": f"0x{target:08x}" if target is not None else None, + } + ) + return rows + + +def load_inventory(path: Path | None) -> tuple[list[dict], list[dict]]: + if path is None or not path.exists(): + return [], [] + bundle = json.loads(path.read_text()) + procedures: list[dict] = [] + strings: list[dict] = [] + for record in bundle.get("records", []): + operation = record.get("operation") + if operation == "list_procedures": + procedures = record.get("normalized_result", []) + elif operation == "list_strings": + strings = record.get("normalized_result", []) + return procedures, strings + + +def classify(page: dict, last_non_ff: int) -> str: + if page["offset"] >= last_non_ff or page["ff_ratio"] > 0.9: + return "padding" + if page["address"] < BASE + VECTOR_TABLE_SIZE: + return "exception-vectors" + if page["procedures"]: + return "code" + if page["strings"]: + return "strings" + if page["printable_ratio"] > 0.6: + return "text-data" + if page["zero_ratio"] > 0.5: + return "zero-data" + return "binary-data" + + +def build(rom: bytes, procedures: list[dict], strings: list[dict]) -> dict: + proc_addrs = sorted(int(p["address"], 16) for p in procedures) + str_addrs = sorted(int(s["address"], 16) for s in strings) + + last_non_ff = max((i for i, b in enumerate(rom) if b != 0xFF), default=-1) + + pages = [] + for off in range(0, len(rom), PAGE): + chunk = rom[off : off + PAGE] + addr = BASE + off + page = { + "offset": off, + "address": addr, + "procedures": sum(1 for a in proc_addrs if addr <= a < addr + PAGE), + "strings": sum(1 for a in str_addrs if addr <= a < addr + PAGE), + "printable_ratio": printable_ratio(chunk), + "zero_ratio": zero_ratio(chunk), + "ff_ratio": ff_ratio(chunk), + } + page["kind"] = classify(page, last_non_ff) + pages.append(page) + + # Collapse equal neighbouring classifications into ranges. + regions = [] + for page in pages: + if regions and regions[-1]["kind"] == page["kind"]: + regions[-1]["end"] = page["address"] + PAGE + regions[-1]["procedures"] += page["procedures"] + regions[-1]["strings"] += page["strings"] + else: + regions.append( + { + "kind": page["kind"], + "start": page["address"], + "end": page["address"] + PAGE, + "procedures": page["procedures"], + "strings": page["strings"], + } + ) + + version = next( + (s["value"] for s in strings if s["value"].startswith("SGI Version")), None + ) + + code_span = None + if proc_addrs: + code_span = { + "start": f"0x{min(proc_addrs):08x}", + "last_function": f"0x{max(proc_addrs):08x}", + "count": len(proc_addrs), + } + + string_span = None + if str_addrs: + string_span = { + "start": f"0x{min(str_addrs):08x}", + "end": f"0x{max(str_addrs):08x}", + "count": len(str_addrs), + } + + histogram = Counter(p["kind"] for p in pages) + + return { + "source": { + "bytes": len(rom), + "sha1": sha1(rom), + "sha256": sha256(rom), + "version_string": version, + "last_non_ff_offset": last_non_ff, + "last_non_ff_address": ( + f"0x{BASE + last_non_ff:08x}" if last_non_ff >= 0 else None + ), + }, + "image": { + "base": f"0x{BASE:08x}", + "end": f"0x{BASE + len(rom):08x}", + "page_size": PAGE, + }, + "code": code_span, + "strings": string_span, + "page_kinds": dict(histogram), + "regions": [ + { + **r, + "start": f"0x{r['start']:08x}", + "end": f"0x{r['end']:08x}", + } + for r in regions + ], + "exception_vectors": vector_table(rom), + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("rom") + parser.add_argument("--evidence", type=Path, default=None) + parser.add_argument("--output", type=Path, default=None) + parser.add_argument( + "--procedures-csv", + type=Path, + default=None, + help="write the analysed function start addresses as CSV", + ) + args = parser.parse_args() + + rom = Path(args.rom).read_bytes() + procedures, strings = load_inventory(args.evidence) + doc = build(rom, procedures, strings) + + if args.procedures_csv and procedures: + args.procedures_csv.parent.mkdir(parents=True, exist_ok=True) + rows = ["address,name,thunk_target"] + for proc in sorted(procedures, key=lambda p: int(p["address"], 16)): + rows.append( + "{address},{value},{target}".format( + address=proc["address"], + value=proc["value"], + target=proc.get("procedure", {}).get("thunk_target") or "", + ) + ) + args.procedures_csv.write_text("\n".join(rows) + "\n") + print(args.procedures_csv) + + text = json.dumps(doc, indent=2) + if args.output: + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(text + "\n") + print(args.output) + else: + print(text) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())