nmarshall commited on
Commit
e639531
·
1 Parent(s): a68f9ad

Recover SDL debug maps and Windows executable resource variants

Browse files
Files changed (26) hide show
  1. assets/analysis/native/macos--Quake3-UrT.x86_64.stabs.jsonl.gz +3 -0
  2. assets/analysis/native/macos--libSDL-1.2.0.dylib.stabs.jsonl.gz +3 -0
  3. assets/analysis/native/resources/windows--Quake3-UrT.exe/000000.bin +3 -0
  4. assets/analysis/native/resources/windows--Quake3-UrT.exe/000001.bin +3 -0
  5. assets/analysis/native/resources/windows--Quake3-UrT.exe/000002.bin +3 -0
  6. assets/analysis/native/resources/windows--Quake3-UrT.exe/000003.bin +3 -0
  7. assets/analysis/native/resources/windows--Quake3-UrT.exe/000004.bin +3 -0
  8. assets/analysis/native/resources/windows--Quake3-UrT.exe/000005.bin +3 -0
  9. assets/analysis/native/resources/windows--Quake3-UrT.exe/000006.bin +3 -0
  10. assets/analysis/native/resources/windows--Quake3-UrT.exe/000007.bin +3 -0
  11. assets/analysis/native/resources/windows--Quake3-UrT.exe/000008.bin +3 -0
  12. assets/analysis/native/resources/windows--Quake3-UrT.exe/000008.ico +3 -0
  13. docs/FINDINGS.md +5 -0
  14. docs/RESEARCH_LEDGER.md +5 -0
  15. docs/REVERSE_ENGINEERING_TODO.md +7 -2
  16. docs/research/README.md +4 -1
  17. docs/research/native/METADATA.md +47 -0
  18. docs/research/native/README.md +4 -0
  19. docs/research/native/macos--Quake3-UrT.x86_64.metadata.json +113 -0
  20. docs/research/native/macos--libSDL-1.2.0.dylib.metadata.json +0 -0
  21. docs/research/native/metadata-summary.json +32 -0
  22. docs/research/native/windows--Quake3-UrT-Ded.exe.metadata.json +10 -0
  23. docs/research/native/windows--Quake3-UrT.exe.metadata.json +363 -0
  24. scripts/analyze_binaries.py +2 -1
  25. scripts/analyze_native_metadata.py +250 -0
  26. scripts/test_native_metadata.py +103 -0
assets/analysis/native/macos--Quake3-UrT.x86_64.stabs.jsonl.gz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ec0f05bd9d67f7745c355f0fa6ea350ac478eef8d3dfdb888ef38f4016240f74
3
+ size 845
assets/analysis/native/macos--libSDL-1.2.0.dylib.stabs.jsonl.gz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aeced137341935613e82e9945c340402d49f696415b06adebfed025ef4fbc458
3
+ size 55820
assets/analysis/native/resources/windows--Quake3-UrT.exe/000000.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7e91bc2f2c1cae398aed49c77e076f72fb7df4d37acf6e10535b5c87579c472c
3
+ size 1384
assets/analysis/native/resources/windows--Quake3-UrT.exe/000001.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d5697c5c2d3c4f6f9c3ecfd99c0137eea1098ff9941435974b2f9a62b4fa21e9
3
+ size 1320
assets/analysis/native/resources/windows--Quake3-UrT.exe/000002.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5f182e38fd1044fb6cb701d0ffe4929546155f5a10c135a31e14f3b431227581
3
+ size 2216
assets/analysis/native/resources/windows--Quake3-UrT.exe/000003.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7a766ebde718a8d82cade2d990b1025c6ee6e8b0e83361e7282c0dcb6e46899c
3
+ size 5160
assets/analysis/native/resources/windows--Quake3-UrT.exe/000004.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:561be52eacd9c73b5fb5141c636a8c3fc6090977476396fd7597c54472b195c9
3
+ size 3752
assets/analysis/native/resources/windows--Quake3-UrT.exe/000005.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:79e8a802daf7aa78838fc3e5d7837cffd4e09a7922264da9690bb28d0e286bb5
3
+ size 11560
assets/analysis/native/resources/windows--Quake3-UrT.exe/000006.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8c3a46e5fdc315824a8100c11a5bbf479df8d56266a4efc078a7d2a111e7e92f
3
+ size 327720
assets/analysis/native/resources/windows--Quake3-UrT.exe/000007.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:963f626117e91ab3f1f97e30ce012bb855bd564bbb3e8b745ee2a7424753c44c
3
+ size 52
assets/analysis/native/resources/windows--Quake3-UrT.exe/000008.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:35f2a898666580776a08e4e56ff6db778dc8abcfa74b6729a03407e0938884ef
3
+ size 104
assets/analysis/native/resources/windows--Quake3-UrT.exe/000008.ico ADDED

Git LFS Details

  • SHA256: 40c88b2ed549500a26e3fa2a23b2e3d56d8b880afc9b651391418f1259f1e4bd
  • Pointer size: 131 Bytes
  • Size of remote file: 353 kB
docs/FINDINGS.md CHANGED
@@ -180,3 +180,8 @@ Exact native fork/source revision, runtime resource lookup, legacy launch enviro
180
  **F-028 — Observed/inferred: four extension entry roles.** Matching QVM bodies, string references and Linux ELF64 native callers identify qagame 11 as heartbeat, 12 as shutdown, 13 as server authentication message handling, and UI 11 as authentication response handling. These are descriptive roles, not recovered source names or complete protocol specifications. All four QVM entry cases discard the callee result and return zero. [ABI-001 evidence and limits](research/VM_EXTENSIONS.md).
181
 
182
  **F-029 — Observed: Linux ELF64 network/auth extension boundaries.** Game 600/UI 88 parse a string into a VM address buffer; game 601/UI 90 pass socket, length, packet buffer and resolved destination to `NET_SendPacket`, then return zero without checking the parser result in the dispatch case. Game 603 converts two pointers and checks the client index before calling `SV_Auth_DropClient`. The parser/send boundary uses a 12-byte native address, with IPv4 at offset 4 and port at offset 8. Other platform contracts and full error/packet semantics remain unresolved. [ABI-002/003](research/VM_EXTENSIONS.md).
 
 
 
 
 
 
180
  **F-028 — Observed/inferred: four extension entry roles.** Matching QVM bodies, string references and Linux ELF64 native callers identify qagame 11 as heartbeat, 12 as shutdown, 13 as server authentication message handling, and UI 11 as authentication response handling. These are descriptive roles, not recovered source names or complete protocol specifications. All four QVM entry cases discard the callee result and return zero. [ABI-001 evidence and limits](research/VM_EXTENSIONS.md).
181
 
182
  **F-029 — Observed: Linux ELF64 network/auth extension boundaries.** Game 600/UI 88 parse a string into a VM address buffer; game 601/UI 90 pass socket, length, packet buffer and resolved destination to `NET_SendPacket`, then return zero without checking the parser result in the dispatch case. Game 603 converts two pointers and checks the client index before calling `SV_Auth_DropClient`. The parser/send boundary uses a 12-byte native address, with IPv4 at offset 4 and port at offset 8. Other platform contracts and full error/packet semantics remain unresolved. [ABI-002/003](research/VM_EXTENSIONS.md).
183
+
184
+
185
+ **F-030 — Observed: SDL/client Mach-O debug-map ranges.** Raw STABS yield 1,012 nonoverlapping SDL function ranges associated with 78 source/object entries, plus six client SDLMain launcher ranges. No local/parameter/type-description classes were observed. Source paths suggest SDL 1.2.15 build provenance, not a verified upstream implementation match. SDL text still contains 524 bytes outside these ranges. [META-001](research/native/METADATA.md).
186
+
187
+ **F-031 — Observed: Windows resource contents and icon discrepancy.** The client PE has nine leaves: seven icon images, one string block and one icon group, all language 1033; the dedicated PE has no resource directory. A reconstructed ICO preserves every image byte. Four 32-bit image payloads exceed their standard DIB pixel/mask extent; their surplus remains preserved and unclassified. [META-002/003](research/native/METADATA.md).
docs/RESEARCH_LEDGER.md CHANGED
@@ -73,3 +73,8 @@ Document inspection tooling used in this pass: Python standard library (`pathlib
73
  ## 2026-10-11 — VM extension follow-up
74
 
75
  Completed RE-211–213: extra entry command evidence, six QVM network/auth call-site cross-references, and selected Linux ELF64 native helpers. Findings F-028/029 and ABI-001–003 are documented in [VM_EXTENSIONS.md](research/VM_EXTENSIONS.md). `analyze_vm_extensions.py` verifies source/disassembly hashes and emits a deterministic focused report; five new fixture/boundary tests pass. Native command scanning includes tail calls and rejects local candidates after intervening control transfers/register writes. Reports retain broader limitations and unresolved routing, error paths, helper semantics and all-platform coverage. Originals remain in ignored cache; no original binary or QVM was run.
 
 
 
 
 
 
73
  ## 2026-10-11 — VM extension follow-up
74
 
75
  Completed RE-211–213: extra entry command evidence, six QVM network/auth call-site cross-references, and selected Linux ELF64 native helpers. Findings F-028/029 and ABI-001–003 are documented in [VM_EXTENSIONS.md](research/VM_EXTENSIONS.md). `analyze_vm_extensions.py` verifies source/disassembly hashes and emits a deterministic focused report; five new fixture/boundary tests pass. Native command scanning includes tail calls and rejects local candidates after intervening control transfers/register writes. Reports retain broader limitations and unresolved routing, error paths, helper semantics and all-platform coverage. Originals remain in ignored cache; no original binary or QVM was run.
76
+
77
+
78
+ ## 2026-10-11 — Native metadata follow-up
79
+
80
+ Completed static milestones RE-214–216 with `analyze_native_metadata.py`: all 4,753 Mach-O STABS records retained, 1,018 paired function ranges hashed, nine Windows resource leaves preserved by variant, string slots decoded, icon group reconstructed and unexpected image extents recorded. Findings F-030/031 and META-001–003 distinguish structural recovery from source matching or rendering semantics. Seven tests cover malformed structures and real function/resource reconstruction. New derived binary evidence stays under Xet-filtered assets; originals remain in ignored cache. RE-217/218 track icon surplus rendering and uncovered byte regions.
docs/REVERSE_ENGINEERING_TODO.md CHANGED
@@ -247,9 +247,14 @@ Purpose: turn the supplied installation into an evidence-backed specification of
247
  - [ ] RE-205 Name and specify qagame entry commands 11–13 and UI entry command 11 using native VM callers, argument flow and deterministic reference fixtures (VM-002). **Progress 2026-10-11:** All four roles have matching QVM/native evidence: heartbeat, shutdown, server auth messages and UI auth responses. Complete grammar, state effects, routing predicates and runtime fixtures remain open ([ABI-001](research/VM_EXTENSIONS.md)).
248
  - [ ] RE-206 Recover game trap 600/601/603 and UI trap 88/90 argument/result layouts, packet/address semantics and error paths on every native platform (NAT-004). **Progress 2026-10-11:** Linux ELF64 argument slots, VM pointer conversions, send/parser return handling, client-index guard and 12-byte address layout documented. Invalid-index continuation, helper semantics and other platforms remain open ([ABI-002/003](research/VM_EXTENSIONS.md)).
249
  - [ ] RE-207 Extend native switch recognition to i386, Windows and macOS; compare numeric cases, pointer width, validation and results with the two ELF64 tables.
250
- - [ ] RE-208 Decode all retained SDL/app STABS source/type/function records; compare SDL 1.2.15 implementations and identify modifications (NAT-003).
251
  - [ ] RE-209 Resolve all 519 currently indirect QVM calls and remaining computed jumps through stack/dataflow analysis, retaining unresolved coverage rather than inventing targets (VM-003/004).
252
- - [ ] RE-210 Decode Windows client resource leaves, native load/bind/relocation details and unclassified tail/embedded regions; extend recursion beyond supported plain-image signatures (NAT-001/002).
253
  - [x] RE-211 Collect source-verified QVM bodies and Linux ELF64 callers for all four extra entry commands, including native tail calls. **Evidence:** `scripts/analyze_vm_extensions.py`, `docs/research/vm-extensions.json`, ABI-001; five focused tests pass. This completes static evidence collection, not RE-205's behavioral specification.
254
  - [x] RE-212 Cross-reference all six observed call sites for game 600/601/603 and UI 88/90 with both ELF64 game dispatchers and the ELF64 UI dispatcher. **Evidence:** generated service cases and local QVM argument windows; ABI-002; fixture tests validate trap encoding and native helper targets.
255
  - [x] RE-213 Preserve and inspect complete Linux ELF64 address-parser/send/drop helper bodies; document address layout, port conversion and dispatch return handling. **Evidence:** ABI-003 and hash-verified generated native functions. Full helper semantics and platform parity remain part of RE-206.
 
 
 
 
 
 
247
  - [ ] RE-205 Name and specify qagame entry commands 11–13 and UI entry command 11 using native VM callers, argument flow and deterministic reference fixtures (VM-002). **Progress 2026-10-11:** All four roles have matching QVM/native evidence: heartbeat, shutdown, server auth messages and UI auth responses. Complete grammar, state effects, routing predicates and runtime fixtures remain open ([ABI-001](research/VM_EXTENSIONS.md)).
248
  - [ ] RE-206 Recover game trap 600/601/603 and UI trap 88/90 argument/result layouts, packet/address semantics and error paths on every native platform (NAT-004). **Progress 2026-10-11:** Linux ELF64 argument slots, VM pointer conversions, send/parser return handling, client-index guard and 12-byte address layout documented. Invalid-index continuation, helper semantics and other platforms remain open ([ABI-002/003](research/VM_EXTENSIONS.md)).
249
  - [ ] RE-207 Extend native switch recognition to i386, Windows and macOS; compare numeric cases, pointer width, validation and results with the two ELF64 tables.
250
+ - [ ] RE-208 Decode all retained SDL/app STABS source/type/function records; compare SDL 1.2.15 implementations and identify modifications (NAT-003). **Progress 2026-10-11:** All raw debug-map records retained; SDL has 1,012 paired function ranges associated with 78 source/object entries, launcher six ranges. No type-description records observed; upstream comparison remains open ([META-001](research/native/METADATA.md)).
251
  - [ ] RE-209 Resolve all 519 currently indirect QVM calls and remaining computed jumps through stack/dataflow analysis, retaining unresolved coverage rather than inventing targets (VM-003/004).
252
+ - [ ] RE-210 Decode Windows client resource leaves, native load/bind/relocation details and unclassified tail/embedded regions; extend recursion beyond supported plain-image signatures (NAT-001/002). **Progress 2026-10-11:** Nine Windows client resource leaves extracted, string block decoded and seven-image icon group reconstructed; dedicated resource directory absent. Icon surplus, byte gaps and loader metadata remain open ([META-002/003](research/native/METADATA.md)).
253
  - [x] RE-211 Collect source-verified QVM bodies and Linux ELF64 callers for all four extra entry commands, including native tail calls. **Evidence:** `scripts/analyze_vm_extensions.py`, `docs/research/vm-extensions.json`, ABI-001; five focused tests pass. This completes static evidence collection, not RE-205's behavioral specification.
254
  - [x] RE-212 Cross-reference all six observed call sites for game 600/601/603 and UI 88/90 with both ELF64 game dispatchers and the ELF64 UI dispatcher. **Evidence:** generated service cases and local QVM argument windows; ABI-002; fixture tests validate trap encoding and native helper targets.
255
  - [x] RE-213 Preserve and inspect complete Linux ELF64 address-parser/send/drop helper bodies; document address layout, port conversion and dispatch return handling. **Evidence:** ABI-003 and hash-verified generated native functions. Full helper semantics and platform parity remain part of RE-206.
256
+ - [x] RE-214 Recover raw Mach-O STABS, source/object associations and bounded paired function ranges for SDL and the client launcher. **Evidence:** `scripts/analyze_native_metadata.py`, both Mach-O metadata reports, META-001; 1,018 ranges with source hashes, no unmatched function records; seven metadata tests pass. Source comparison and types remain RE-208.
257
+ - [x] RE-215 Enumerate every supplied Windows PE resource leaf by type/name/language, preserve all variants, decode the string block and reconstruct the linked icon group. **Evidence:** nine client leaves, zero dedicated leaves, seven-image ICO with identical payloads, META-002.
258
+ - [x] RE-216 Calculate supplied icon DIB header/palette/pixel/mask extents; hash and preserve unexpected surplus bytes. **Evidence:** META-003 and per-leaf structural reports. This completes extent analysis, not visual/pixel semantics.
259
+ - [ ] RE-217 Explain/render the four 32-bit Windows icon payloads with surplus bytes; compare alpha/mask behavior across reference decoders before normalizing them.
260
+ - [ ] RE-218 Reconcile the 524 SDL text bytes outside paired debug-map functions and all Windows resource-region metadata/padding gaps; preserve evidence for every byte classification.
docs/research/README.md CHANGED
@@ -8,6 +8,8 @@ Read [QVM findings and ABI candidates](qvm/README.md), [native images, libraries
8
 
9
  The [extension follow-up](VM_EXTENSIONS.md) traces all four extra game/UI entry commands and five network/auth syscall numbers through QVM callers and the Linux ELF64 engines. Its [focused generated report](vm-extensions.json) preserves selected function bodies and instruction evidence; descriptive roles and partial signatures are distinguished from unresolved behavior.
10
 
 
 
11
  ## Reproduce
12
 
13
  From the repository root, with the verified source installation and extracted assets present:
@@ -16,10 +18,11 @@ From the repository root, with the verified source installation and extracted as
16
  python3 scripts/analyze_binaries.py
17
  python3 scripts/test_binary_analysis.py
18
  python3 scripts/test_vm_extensions.py
 
19
  python3 scripts/storage_policy.py check --require-setup
20
  ```
21
 
22
- The pipeline runs `fetch_analysis_references.py`, `analyze_qvm.py`, `analyze_native.py`, `analyze_vm_abi.py`, `analyze_native_dispatch.py`, and `analyze_vm_extensions.py` in that order. Python 3.9+ and **LLVM objdump** with x86 ELF/COFF/Mach-O support are required. This pass used Apple LLVM 17.0.0 on macOS arm64; it disassembled x86 images without running them. Upstream references are downloaded only if absent, checked against the committed SHA-256 manifest, and cached under `.cache/research/upstream/`. A clone also needs `git lfs pull` to obtain the derived artifacts/extracted members.
23
 
24
  Generated JSON/JSONL and compressed evidence are regenerated in place; keep prose notes in Markdown. Rerunning the four analysis stages reproduced every generated report and gzip artifact byte-for-byte on the same toolchain. gzip timestamps and stored filenames are fixed. Different objdump versions can produce different disassembly text, so tool version and uncompressed evidence hashes are recorded. Reports are UTF-8 normal Git text, with individual files below the storage policy's 2 MiB limit.
25
 
 
8
 
9
  The [extension follow-up](VM_EXTENSIONS.md) traces all four extra game/UI entry commands and five network/auth syscall numbers through QVM callers and the Linux ELF64 engines. Its [focused generated report](vm-extensions.json) preserves selected function bodies and instruction evidence; descriptive roles and partial signatures are distinguished from unresolved behavior.
10
 
11
+ The [native metadata follow-up](native/METADATA.md) recovers SDL/client debug-map ranges and all supplied Windows resource leaves, reconstructs the icon group and records unclassified icon surplus.
12
+
13
  ## Reproduce
14
 
15
  From the repository root, with the verified source installation and extracted assets present:
 
18
  python3 scripts/analyze_binaries.py
19
  python3 scripts/test_binary_analysis.py
20
  python3 scripts/test_vm_extensions.py
21
+ python3 scripts/test_native_metadata.py
22
  python3 scripts/storage_policy.py check --require-setup
23
  ```
24
 
25
+ The pipeline runs `fetch_analysis_references.py`, `analyze_qvm.py`, `analyze_native.py`, `analyze_vm_abi.py`, `analyze_native_dispatch.py`, `analyze_vm_extensions.py`, and `analyze_native_metadata.py` in that order. Python 3.9+ and **LLVM objdump** with x86 ELF/COFF/Mach-O support are required. This pass used Apple LLVM 17.0.0 on macOS arm64; it disassembled x86 images without running them. Upstream references are downloaded only if absent, checked against the committed SHA-256 manifest, and cached under `.cache/research/upstream/`. A clone also needs `git lfs pull` to obtain the derived artifacts/extracted members.
26
 
27
  Generated JSON/JSONL and compressed evidence are regenerated in place; keep prose notes in Markdown. Rerunning the four analysis stages reproduced every generated report and gzip artifact byte-for-byte on the same toolchain. gzip timestamps and stored filenames are fixed. Different objdump versions can produce different disassembly text, so tool version and uncompressed evidence hashes are recorded. Reports are UTF-8 normal Git text, with individual files below the storage policy's 2 MiB limit.
28
 
docs/research/native/METADATA.md ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Native debug maps and Windows resources
2
+
3
+ Static analysis, 2026-10-11. Reproduce with `python3 scripts/analyze_native_metadata.py`; validate with `python3 scripts/test_native_metadata.py`. The collector is part of `analyze_binaries.py`. [Summary](metadata-summary.json) and per-image metadata reports record original SHA-256 identities. No supplied executable or library was run.
4
+
5
+ ## META-001: Mach-O STABS debug maps
6
+
7
+ The reader walks the original `LC_SYMTAB` nlist array, including anonymous/zero-string-index records, rather than relying on a filtered symbol inventory. It retains original symbol indices, file offsets, type, section, description and value. Record names follow [Apple's STABS definitions](https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/stab.h). Paired `N_FUN` records delimit the function ranges in these images; closing values are treated as lengths and checked against file-backed executable sections. Embedded source paths and object timestamps are unverified build evidence.
8
+
9
+ | Image | Records | Source entries / object entries | Paired function ranges | Bytes in those ranges | Unmatched/invalid function records |
10
+ | --- | --- | --- | --- | --- | --- |
11
+ | SDL dylib | 4,719 | 78 / 78 | 1,012 | 327,111 | 0 |
12
+ | macOS client executable | 34 | 1 / 1 | 6 | 2,289 | 0 |
13
+
14
+ The [SDL report](macos--libSDL-1.2.0.dylib.metadata.json) associates functions with source files and object paths and hashes each byte range. Tests confirm the ranges do not overlap. SDL's `__text` section contains 327,635 bytes; the remaining 524 bytes are outside these function ranges and need reconciliation as padding or other code/data. Range recovery does not mean each function has been analyzed or matched to upstream source.
15
+
16
+ SDL source names cover common event/video/audio surfaces, software blitters, Quartz, X11/DGA and X extensions, CoreAudio, Darwin joystick/CD code, pthreads, Unix timers and dynamic loading. These are compilation provenance candidates, not proof that every backend is reachable on this macOS build. The recorded build prefix identifies `SDL-1.2.15`; upstream implementation comparison remains open.
17
+
18
+ The records comprise 2,024 `N_FUN`, 1,012 each `N_BNSYM`/`N_ENSYM`, 350 `N_STSYM`, 234 `N_SO`, 78 `N_OSO` and nine `N_SOL`. No local/register/parameter/type-description record classes were observed. Full C data types cannot be recovered from a debug map that does not contain them. Static and include records remain searchable in the raw compressed evidence under `assets/analysis/native/`.
19
+
20
+ The [client report](macos--Quake3-UrT.x86_64.metadata.json) identifies SDLMain Objective-C methods for termination, working-directory setup, file opening, launch handling, NSString replacement, and `_main`. Its object path mentions `libSDLmain.a(SDLMain.o)` inside an ioq3-for-UrbanTerror build. This is a small retained launcher debug map, not debug coverage for the whole engine.
21
+
22
+ ## META-002: Windows resource variants
23
+
24
+ The parser follows directory-relative tree offsets and separately maps leaf payload RVAs to file-backed sections, with bounds/cycle checks. Paths retain type, name and language; named entries use UTF-16. This follows the [Microsoft PE resource tree definition](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-rsrc-section). Every leaf is preserved separately under `assets/analysis/native/resources/windows--Quake3-UrT.exe/`, using existing Xet filters.
25
+
26
+ The [client report](windows--Quake3-UrT.exe.metadata.json) contains **nine leaves**, all language ID 1033: seven type-3 icon images, one type-6 string block and one type-14 icon group. Payloads total **353,268 bytes** within the 353,788-byte resource directory region. Tree metadata, alignment and any remaining byte gaps require separate coverage reconciliation. No version, manifest or dialog leaf occurs in this tree. The [dedicated report](windows--Quake3-UrT-Ded.exe.metadata.json) confirms its resource directory is absent; this says nothing about Windows installation files outside the supplied snapshot.
27
+
28
+ The string block has sixteen length-prefixed UTF-16 slots. Only ID 1 is nonempty: `Quake3-UrT`. Empty entries are retained. The group links images at 16, 32 and 48 pixels with both 8-bit and 32-bit variants, plus a 256-pixel 32-bit image. Reconstruction into a **353,230-byte ICO** preserves the seven image payloads exactly and validates image size and language matches, using [Microsoft's group/icon structures](https://learn.microsoft.com/en-us/previous-versions/ms997538(v=msdn.10)). Raw group and image leaves are preserved alongside the ICO.
29
+
30
+ ## META-003: icon extents need rendering checks
31
+
32
+ All seven images use a 40-byte uncompressed DIB header with a doubled stored height for pixel/mask planes. The three indexed images exactly match the computed palette, aligned pixel rows and standard one-bit AND mask extent. Four 32-bit variants have additional bytes:
33
+
34
+ | Dimensions | Payload bytes | Standard layout bytes | Surplus bytes retained |
35
+ | --- | --- | --- | --- |
36
+ | 16 × 16 | 1,320 | 1,128 | 192 |
37
+ | 32 × 32 | 5,160 | 4,264 | 896 |
38
+ | 48 × 48 | 11,560 | 9,640 | 1,920 |
39
+ | 256 × 256 | 327,720 | 270,376 | 57,344 |
40
+
41
+ The payload lengths fit a larger mask/data extent, but its interpretation is not established. All declared image-size fields are zero. The collector records and hashes surplus bytes instead of dropping them. ICO structural reconstruction and payload equality pass; visual correctness, alpha/mask behavior and operating-system handling of those bytes have not been tested. A future converter must retain the originals and compare rendered variants before normalization.
42
+
43
+ ## Verification and remaining tasks
44
+
45
+ Seven tests cover relative offsets versus RVAs, named resources, cycles, unbacked payloads, malformed string tables, language/size mismatch, function closure interpretation, every recovered real function hash, nonoverlapping ranges and all reconstructed icon payloads. Reports and derived assets regenerate byte-for-byte.
46
+
47
+ RE-214–216 complete this static metadata pass. RE-208 remains open for pinned SDL source comparison, implementation differences, absent-type reconstruction and helper behavior. RE-210 remains open for load/bind/relocation details and uncovered regions. Follow up icon surplus/mask rendering, the 524 SDL text bytes outside debug-map functions, and exact resource-region byte coverage before treating these formats as fully understood.
docs/research/native/README.md CHANGED
@@ -69,3 +69,7 @@ The 100-series memory/math traps illustrate module-specific numbering: game slot
69
  All eight images have complete static section/symbol and LLVM disassembly artifacts, but their functions are mostly unreviewed. ELF64 switch recovery covers four dispatchers only; i386/Windows/macOS correspondence needs separate recognizers and validation. Dynamic loads, transitive dependencies, DLL runtime behavior, resources, STABS types, full relocation/bind metadata, native CFGs and source correspondence remain open. Preserve each binary's distinct address space/hash when tracing the same named function across platforms.
70
 
71
  Use the generated `*.symbols.jsonl` and `anchors` lists to import analysis tools and prioritize VM loading/calls, pointer validation, syscall dispatch, filesystem order and frame loops. Follow the QVM call sites into their argument layouts before writing Rust services.
 
 
 
 
 
69
  All eight images have complete static section/symbol and LLVM disassembly artifacts, but their functions are mostly unreviewed. ELF64 switch recovery covers four dispatchers only; i386/Windows/macOS correspondence needs separate recognizers and validation. Dynamic loads, transitive dependencies, DLL runtime behavior, resources, STABS types, full relocation/bind metadata, native CFGs and source correspondence remain open. Preserve each binary's distinct address space/hash when tracing the same named function across platforms.
70
 
71
  Use the generated `*.symbols.jsonl` and `anchors` lists to import analysis tools and prioritize VM loading/calls, pointer validation, syscall dispatch, filesystem order and frame loops. Follow the QVM call sites into their argument layouts before writing Rust services.
72
+
73
+ ## Metadata follow-up
74
+
75
+ [META-001–003](METADATA.md) recover all retained Mach-O debug-map records and paired ranges, plus Windows resource variants, string contents and a reconstructed ICO. Function implementation comparison and icon surplus interpretation remain open.
docs/research/native/macos--Quake3-UrT.x86_64.metadata.json ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "functions": [
3
+ {
4
+ "address": 4296138656,
5
+ "bytes": 32,
6
+ "directory": "/Users/Admin/Downloads/SDL-1.2.15/",
7
+ "end_record": 5,
8
+ "file_offset": 1171360,
9
+ "name": "-[NSApplication(SDLApplication) terminate:]",
10
+ "object": "/Volumes/BARBIE/ioq3-for-UrbanTerror-4/build/release-darwin-x86_64/libSDLmain.a(SDLMain.o)",
11
+ "record": 4,
12
+ "section": 1,
13
+ "sha256": "a7d95529b23866a6dce18e81b2ac826b99ed498d5c993771813b8ef09f87a7a9",
14
+ "source": "./src/main/macosx/SDLMain.m"
15
+ },
16
+ {
17
+ "address": 4296138688,
18
+ "bytes": 176,
19
+ "directory": "/Users/Admin/Downloads/SDL-1.2.15/",
20
+ "end_record": 9,
21
+ "file_offset": 1171392,
22
+ "name": "-[SDLMain setupWorkingDirectory:]",
23
+ "object": "/Volumes/BARBIE/ioq3-for-UrbanTerror-4/build/release-darwin-x86_64/libSDLmain.a(SDLMain.o)",
24
+ "record": 8,
25
+ "section": 1,
26
+ "sha256": "6b740a1727e8f1223527bfe3f4b6eaa9ef0a46588204437ddce29eafe412a88f",
27
+ "source": "./src/main/macosx/SDLMain.m"
28
+ },
29
+ {
30
+ "address": 4296138864,
31
+ "bytes": 240,
32
+ "directory": "/Users/Admin/Downloads/SDL-1.2.15/",
33
+ "end_record": 13,
34
+ "file_offset": 1171568,
35
+ "name": "-[SDLMain application:openFile:]",
36
+ "object": "/Volumes/BARBIE/ioq3-for-UrbanTerror-4/build/release-darwin-x86_64/libSDLmain.a(SDLMain.o)",
37
+ "record": 12,
38
+ "section": 1,
39
+ "sha256": "cc261d68512fac8d0966a416ce74d78f0b9bc490b083ba1cec589209ff0b999c",
40
+ "source": "./src/main/macosx/SDLMain.m"
41
+ },
42
+ {
43
+ "address": 4296139104,
44
+ "bytes": 64,
45
+ "directory": "/Users/Admin/Downloads/SDL-1.2.15/",
46
+ "end_record": 17,
47
+ "file_offset": 1171808,
48
+ "name": "-[SDLMain applicationDidFinishLaunching:]",
49
+ "object": "/Volumes/BARBIE/ioq3-for-UrbanTerror-4/build/release-darwin-x86_64/libSDLmain.a(SDLMain.o)",
50
+ "record": 16,
51
+ "section": 1,
52
+ "sha256": "431014a664456fda2b2a741d0c12f9ce49b8bcb9da7088ad60293cd0cc651f59",
53
+ "source": "./src/main/macosx/SDLMain.m"
54
+ },
55
+ {
56
+ "address": 4296139168,
57
+ "bytes": 304,
58
+ "directory": "/Users/Admin/Downloads/SDL-1.2.15/",
59
+ "end_record": 21,
60
+ "file_offset": 1171872,
61
+ "name": "-[NSString(ReplaceSubString) stringByReplacingRange:with:]",
62
+ "object": "/Volumes/BARBIE/ioq3-for-UrbanTerror-4/build/release-darwin-x86_64/libSDLmain.a(SDLMain.o)",
63
+ "record": 20,
64
+ "section": 1,
65
+ "sha256": "3989d46a01f29da7a6668d76090813ca573ff70700665e171f20cf105bc0fc7e",
66
+ "source": "./src/main/macosx/SDLMain.m"
67
+ },
68
+ {
69
+ "address": 4296139472,
70
+ "bytes": 1473,
71
+ "directory": "/Users/Admin/Downloads/SDL-1.2.15/",
72
+ "end_record": 25,
73
+ "file_offset": 1172176,
74
+ "name": "_main",
75
+ "object": "/Volumes/BARBIE/ioq3-for-UrbanTerror-4/build/release-darwin-x86_64/libSDLmain.a(SDLMain.o)",
76
+ "record": 24,
77
+ "section": 1,
78
+ "sha256": "e92ecdfcc2202d456809dec21790660833a1b0b6719458ed907c27184aba399c",
79
+ "source": "./src/main/macosx/SDLMain.m"
80
+ }
81
+ ],
82
+ "kind_counts": {
83
+ "N_BNSYM": 6,
84
+ "N_ENSYM": 6,
85
+ "N_FUN": 12,
86
+ "N_OSO": 1,
87
+ "N_SO": 3,
88
+ "N_STSYM": 6
89
+ },
90
+ "limitations": "N_FUN end values are interpreted as byte lengths for this linker debug-map pattern. Source/object paths and timestamps are embedded build evidence, not verified source identities. Raw records retain begin/end/include/static symbols; source types are not reconstructed from absent records.",
91
+ "objects": [
92
+ {
93
+ "mtime_untrusted": 1417210094,
94
+ "object": "/Volumes/BARBIE/ioq3-for-UrbanTerror-4/build/release-darwin-x86_64/libSDLmain.a(SDLMain.o)",
95
+ "record": 2
96
+ }
97
+ ],
98
+ "raw_records": "assets/analysis/native/macos--Quake3-UrT.x86_64.stabs.jsonl.gz",
99
+ "record_count": 34,
100
+ "source": {
101
+ "bytes": 1713680,
102
+ "path": "Quake3-UrT.app/Contents/MacOS/Quake3-UrT.x86_64",
103
+ "sha256": "cb5e347d57124e317d3e15c0faebaabb98b314d2d3b7fa297f8639b6c69d182e"
104
+ },
105
+ "sources": [
106
+ {
107
+ "directory": "/Users/Admin/Downloads/SDL-1.2.15/",
108
+ "record": 1,
109
+ "source": "./src/main/macosx/SDLMain.m"
110
+ }
111
+ ],
112
+ "unresolved": []
113
+ }
docs/research/native/macos--libSDL-1.2.0.dylib.metadata.json ADDED
The diff for this file is too large to render. See raw diff
 
docs/research/native/metadata-summary.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "function_ranges": 1012,
4
+ "image": "macos--libSDL-1.2.0.dylib",
5
+ "objects": 78,
6
+ "stabs": 4719,
7
+ "unresolved": 0
8
+ },
9
+ {
10
+ "function_ranges": 6,
11
+ "image": "macos--Quake3-UrT.x86_64",
12
+ "objects": 1,
13
+ "stabs": 34,
14
+ "unresolved": 0
15
+ },
16
+ {
17
+ "icon_groups": 1,
18
+ "image": "windows--Quake3-UrT.exe",
19
+ "resource_leaves": 9,
20
+ "type_counts": {
21
+ "14": 1,
22
+ "3": 7,
23
+ "6": 1
24
+ }
25
+ },
26
+ {
27
+ "icon_groups": 0,
28
+ "image": "windows--Quake3-UrT-Ded.exe",
29
+ "resource_leaves": 0,
30
+ "type_counts": {}
31
+ }
32
+ ]
docs/research/native/windows--Quake3-UrT-Ded.exe.metadata.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "icon_groups": [],
3
+ "leaves": [],
4
+ "limitations": "All leaves are preserved. Shipped icon groups, DIB headers/extents and string tables are decoded. Pixel/mask behavior and surplus icon bytes remain unclassified; other resource types are not decoded.",
5
+ "source": {
6
+ "bytes": 1082800,
7
+ "path": "Quake3-UrT-Ded.exe",
8
+ "sha256": "808369f9b16054e0408e91bdf15f56098fac78279e4a092c95a24d943381352b"
9
+ }
10
+ }
docs/research/native/windows--Quake3-UrT.exe.metadata.json ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "icon_groups": [
3
+ {
4
+ "bytes": 353230,
5
+ "entries": [
6
+ {
7
+ "bits": 8,
8
+ "bytes": 1384,
9
+ "colors": 0,
10
+ "height": 16,
11
+ "planes": 1,
12
+ "resource_id": 1,
13
+ "width": 16
14
+ },
15
+ {
16
+ "bits": 32,
17
+ "bytes": 1320,
18
+ "colors": 0,
19
+ "height": 16,
20
+ "planes": 1,
21
+ "resource_id": 2,
22
+ "width": 16
23
+ },
24
+ {
25
+ "bits": 8,
26
+ "bytes": 2216,
27
+ "colors": 0,
28
+ "height": 32,
29
+ "planes": 1,
30
+ "resource_id": 3,
31
+ "width": 32
32
+ },
33
+ {
34
+ "bits": 32,
35
+ "bytes": 5160,
36
+ "colors": 0,
37
+ "height": 32,
38
+ "planes": 1,
39
+ "resource_id": 4,
40
+ "width": 32
41
+ },
42
+ {
43
+ "bits": 8,
44
+ "bytes": 3752,
45
+ "colors": 0,
46
+ "height": 48,
47
+ "planes": 1,
48
+ "resource_id": 5,
49
+ "width": 48
50
+ },
51
+ {
52
+ "bits": 32,
53
+ "bytes": 11560,
54
+ "colors": 0,
55
+ "height": 48,
56
+ "planes": 1,
57
+ "resource_id": 6,
58
+ "width": 48
59
+ },
60
+ {
61
+ "bits": 32,
62
+ "bytes": 327720,
63
+ "colors": 0,
64
+ "height": 256,
65
+ "planes": 1,
66
+ "resource_id": 7,
67
+ "width": 256
68
+ }
69
+ ],
70
+ "group_path": [
71
+ 14,
72
+ 1,
73
+ 1033
74
+ ],
75
+ "output": "assets/analysis/native/resources/windows--Quake3-UrT.exe/000008.ico",
76
+ "sha256": "40c88b2ed549500a26e3fa2a23b2e3d56d8b880afc9b651391418f1259f1e4bd"
77
+ }
78
+ ],
79
+ "leaves": [
80
+ {
81
+ "bytes": 1384,
82
+ "codepage": 0,
83
+ "data_entry_relative_offset": 376,
84
+ "dib": {
85
+ "bits": 8,
86
+ "declared_image_bytes": 0,
87
+ "height": 16,
88
+ "palette_bytes": 1024,
89
+ "standard_and_mask_bytes": 64,
90
+ "standard_layout_bytes": 1384,
91
+ "surplus_bytes": 0,
92
+ "surplus_sha256": null,
93
+ "width": 16,
94
+ "xor_bytes": 256
95
+ },
96
+ "file_offset": 1972232,
97
+ "output": "assets/analysis/native/resources/windows--Quake3-UrT.exe/000000.bin",
98
+ "path": [
99
+ 3,
100
+ 1,
101
+ 1033
102
+ ],
103
+ "rva": 13074952,
104
+ "sha256": "7e91bc2f2c1cae398aed49c77e076f72fb7df4d37acf6e10535b5c87579c472c"
105
+ },
106
+ {
107
+ "bytes": 1320,
108
+ "codepage": 0,
109
+ "data_entry_relative_offset": 392,
110
+ "dib": {
111
+ "bits": 32,
112
+ "declared_image_bytes": 0,
113
+ "height": 16,
114
+ "palette_bytes": 0,
115
+ "standard_and_mask_bytes": 64,
116
+ "standard_layout_bytes": 1128,
117
+ "surplus_bytes": 192,
118
+ "surplus_sha256": "e663401f7744b55a1ea571d084fe6ce72af609925658462600176fe93b95f717",
119
+ "width": 16,
120
+ "xor_bytes": 1024
121
+ },
122
+ "file_offset": 1973616,
123
+ "output": "assets/analysis/native/resources/windows--Quake3-UrT.exe/000001.bin",
124
+ "path": [
125
+ 3,
126
+ 2,
127
+ 1033
128
+ ],
129
+ "rva": 13076336,
130
+ "sha256": "d5697c5c2d3c4f6f9c3ecfd99c0137eea1098ff9941435974b2f9a62b4fa21e9"
131
+ },
132
+ {
133
+ "bytes": 2216,
134
+ "codepage": 0,
135
+ "data_entry_relative_offset": 408,
136
+ "dib": {
137
+ "bits": 8,
138
+ "declared_image_bytes": 0,
139
+ "height": 32,
140
+ "palette_bytes": 1024,
141
+ "standard_and_mask_bytes": 128,
142
+ "standard_layout_bytes": 2216,
143
+ "surplus_bytes": 0,
144
+ "surplus_sha256": null,
145
+ "width": 32,
146
+ "xor_bytes": 1024
147
+ },
148
+ "file_offset": 1974936,
149
+ "output": "assets/analysis/native/resources/windows--Quake3-UrT.exe/000002.bin",
150
+ "path": [
151
+ 3,
152
+ 3,
153
+ 1033
154
+ ],
155
+ "rva": 13077656,
156
+ "sha256": "5f182e38fd1044fb6cb701d0ffe4929546155f5a10c135a31e14f3b431227581"
157
+ },
158
+ {
159
+ "bytes": 5160,
160
+ "codepage": 0,
161
+ "data_entry_relative_offset": 424,
162
+ "dib": {
163
+ "bits": 32,
164
+ "declared_image_bytes": 0,
165
+ "height": 32,
166
+ "palette_bytes": 0,
167
+ "standard_and_mask_bytes": 128,
168
+ "standard_layout_bytes": 4264,
169
+ "surplus_bytes": 896,
170
+ "surplus_sha256": "bb319f4f341ebd5c30485c842ec737f0ee3a5652b00221e65914ebf069fb6655",
171
+ "width": 32,
172
+ "xor_bytes": 4096
173
+ },
174
+ "file_offset": 1977152,
175
+ "output": "assets/analysis/native/resources/windows--Quake3-UrT.exe/000003.bin",
176
+ "path": [
177
+ 3,
178
+ 4,
179
+ 1033
180
+ ],
181
+ "rva": 13079872,
182
+ "sha256": "7a766ebde718a8d82cade2d990b1025c6ee6e8b0e83361e7282c0dcb6e46899c"
183
+ },
184
+ {
185
+ "bytes": 3752,
186
+ "codepage": 0,
187
+ "data_entry_relative_offset": 440,
188
+ "dib": {
189
+ "bits": 8,
190
+ "declared_image_bytes": 0,
191
+ "height": 48,
192
+ "palette_bytes": 1024,
193
+ "standard_and_mask_bytes": 384,
194
+ "standard_layout_bytes": 3752,
195
+ "surplus_bytes": 0,
196
+ "surplus_sha256": null,
197
+ "width": 48,
198
+ "xor_bytes": 2304
199
+ },
200
+ "file_offset": 1982312,
201
+ "output": "assets/analysis/native/resources/windows--Quake3-UrT.exe/000004.bin",
202
+ "path": [
203
+ 3,
204
+ 5,
205
+ 1033
206
+ ],
207
+ "rva": 13085032,
208
+ "sha256": "561be52eacd9c73b5fb5141c636a8c3fc6090977476396fd7597c54472b195c9"
209
+ },
210
+ {
211
+ "bytes": 11560,
212
+ "codepage": 0,
213
+ "data_entry_relative_offset": 456,
214
+ "dib": {
215
+ "bits": 32,
216
+ "declared_image_bytes": 0,
217
+ "height": 48,
218
+ "palette_bytes": 0,
219
+ "standard_and_mask_bytes": 384,
220
+ "standard_layout_bytes": 9640,
221
+ "surplus_bytes": 1920,
222
+ "surplus_sha256": "8d510c750cfecb5deb96233ebb713d61fec182fca34431897067294394fb0e15",
223
+ "width": 48,
224
+ "xor_bytes": 9216
225
+ },
226
+ "file_offset": 1986064,
227
+ "output": "assets/analysis/native/resources/windows--Quake3-UrT.exe/000005.bin",
228
+ "path": [
229
+ 3,
230
+ 6,
231
+ 1033
232
+ ],
233
+ "rva": 13088784,
234
+ "sha256": "79e8a802daf7aa78838fc3e5d7837cffd4e09a7922264da9690bb28d0e286bb5"
235
+ },
236
+ {
237
+ "bytes": 327720,
238
+ "codepage": 0,
239
+ "data_entry_relative_offset": 472,
240
+ "dib": {
241
+ "bits": 32,
242
+ "declared_image_bytes": 0,
243
+ "height": 256,
244
+ "palette_bytes": 0,
245
+ "standard_and_mask_bytes": 8192,
246
+ "standard_layout_bytes": 270376,
247
+ "surplus_bytes": 57344,
248
+ "surplus_sha256": "df0269970fcb45f8ca856ead57e3a630d5f43f84bd9fbefb496921536c836131",
249
+ "width": 256,
250
+ "xor_bytes": 262144
251
+ },
252
+ "file_offset": 1997624,
253
+ "output": "assets/analysis/native/resources/windows--Quake3-UrT.exe/000006.bin",
254
+ "path": [
255
+ 3,
256
+ 7,
257
+ 1033
258
+ ],
259
+ "rva": 13100344,
260
+ "sha256": "8c3a46e5fdc315824a8100c11a5bbf479df8d56266a4efc078a7d2a111e7e92f"
261
+ },
262
+ {
263
+ "bytes": 52,
264
+ "codepage": 0,
265
+ "data_entry_relative_offset": 488,
266
+ "file_offset": 2325344,
267
+ "output": "assets/analysis/native/resources/windows--Quake3-UrT.exe/000007.bin",
268
+ "path": [
269
+ 6,
270
+ 1,
271
+ 1033
272
+ ],
273
+ "rva": 13428064,
274
+ "sha256": "963f626117e91ab3f1f97e30ce012bb855bd564bbb3e8b745ee2a7424753c44c",
275
+ "strings": [
276
+ {
277
+ "id": 0,
278
+ "text": ""
279
+ },
280
+ {
281
+ "id": 1,
282
+ "text": "Quake3-UrT"
283
+ },
284
+ {
285
+ "id": 2,
286
+ "text": ""
287
+ },
288
+ {
289
+ "id": 3,
290
+ "text": ""
291
+ },
292
+ {
293
+ "id": 4,
294
+ "text": ""
295
+ },
296
+ {
297
+ "id": 5,
298
+ "text": ""
299
+ },
300
+ {
301
+ "id": 6,
302
+ "text": ""
303
+ },
304
+ {
305
+ "id": 7,
306
+ "text": ""
307
+ },
308
+ {
309
+ "id": 8,
310
+ "text": ""
311
+ },
312
+ {
313
+ "id": 9,
314
+ "text": ""
315
+ },
316
+ {
317
+ "id": 10,
318
+ "text": ""
319
+ },
320
+ {
321
+ "id": 11,
322
+ "text": ""
323
+ },
324
+ {
325
+ "id": 12,
326
+ "text": ""
327
+ },
328
+ {
329
+ "id": 13,
330
+ "text": ""
331
+ },
332
+ {
333
+ "id": 14,
334
+ "text": ""
335
+ },
336
+ {
337
+ "id": 15,
338
+ "text": ""
339
+ }
340
+ ]
341
+ },
342
+ {
343
+ "bytes": 104,
344
+ "codepage": 0,
345
+ "data_entry_relative_offset": 504,
346
+ "file_offset": 2325396,
347
+ "output": "assets/analysis/native/resources/windows--Quake3-UrT.exe/000008.bin",
348
+ "path": [
349
+ 14,
350
+ 1,
351
+ 1033
352
+ ],
353
+ "rva": 13428116,
354
+ "sha256": "35f2a898666580776a08e4e56ff6db778dc8abcfa74b6729a03407e0938884ef"
355
+ }
356
+ ],
357
+ "limitations": "All leaves are preserved. Shipped icon groups, DIB headers/extents and string tables are decoded. Pixel/mask behavior and surplus icon bytes remain unclassified; other resource types are not decoded.",
358
+ "source": {
359
+ "bytes": 2634689,
360
+ "path": "Quake3-UrT.exe",
361
+ "sha256": "94111a3091c7e70e534720585df58820f0481c0894ba7b29f4f7de09accbaf90"
362
+ }
363
+ }
scripts/analyze_binaries.py CHANGED
@@ -9,7 +9,8 @@ def main():
9
  if not Path('docs/inventory/snapshot.json').is_file():
10
  raise SystemExit('Run from the repository root')
11
  for script in ('fetch_analysis_references.py', 'analyze_qvm.py', 'analyze_native.py',
12
- 'analyze_vm_abi.py', 'analyze_native_dispatch.py', 'analyze_vm_extensions.py'):
 
13
  subprocess.run([sys.executable, str(Path('scripts')/script)], check=True)
14
 
15
 
 
9
  if not Path('docs/inventory/snapshot.json').is_file():
10
  raise SystemExit('Run from the repository root')
11
  for script in ('fetch_analysis_references.py', 'analyze_qvm.py', 'analyze_native.py',
12
+ 'analyze_vm_abi.py', 'analyze_native_dispatch.py', 'analyze_vm_extensions.py',
13
+ 'analyze_native_metadata.py'):
14
  subprocess.run([sys.executable, str(Path('scripts')/script)], check=True)
15
 
16
 
scripts/analyze_native_metadata.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Bounded Mach-O STABS and PE resource recovery; no native code execution."""
3
+ from collections import Counter
4
+ import hashlib
5
+ import json
6
+ from pathlib import Path
7
+ import struct
8
+ from analyze_native import parse, take, unpack, cstring
9
+ from analyze_qvm import write_json, write_rows
10
+ from analyze_vm_extensions import verified
11
+
12
+ STABS = {0x20: 'N_GSYM', 0x22: 'N_FNAME', 0x24: 'N_FUN', 0x26: 'N_STSYM',
13
+ 0x28: 'N_LCSYM', 0x2e: 'N_BNSYM', 0x32: 'N_AST', 0x3c: 'N_OPT',
14
+ 0x40: 'N_RSYM', 0x44: 'N_SLINE', 0x4e: 'N_ENSYM', 0x60: 'N_SSYM',
15
+ 0x64: 'N_SO', 0x66: 'N_OSO', 0x80: 'N_LSYM', 0x82: 'N_BINCL',
16
+ 0x84: 'N_SOL', 0x86: 'N_PARAMS', 0x88: 'N_VERSION', 0x8a: 'N_OLEVEL',
17
+ 0xa0: 'N_PSYM', 0xa2: 'N_EINCL', 0xa4: 'N_ENTRY', 0xc0: 'N_LBRAC',
18
+ 0xc2: 'N_EXCL', 0xe0: 'N_RBRAC', 0xe2: 'N_BCOMM', 0xe4: 'N_ECOMM'}
19
+
20
+
21
+ def stab_records(data, layout):
22
+ if layout['format'] != 'Mach-O' or layout['bits'] != 64:
23
+ raise ValueError('STABS reader supports thin Mach-O64 only')
24
+ cmd = next(c for c in layout['load_commands'] if c['command'] == '0x2')
25
+ offset, count, strings_at, strings_bytes = unpack('<4I', data, cmd['file_offset']+8)
26
+ strings = take(data, strings_at, strings_bytes)
27
+ take(data, offset, count*16)
28
+ rows = []
29
+ for i in range(count):
30
+ name, typ, section, desc, value = unpack('<IBBHQ', data, offset+i*16)
31
+ if typ & 0xe0:
32
+ rows.append({'symbol_index': i, 'file_offset': offset+i*16, 'name': cstring(strings, name),
33
+ 'type': typ, 'kind': STABS.get(typ, 'UNKNOWN'), 'section': section,
34
+ 'description': desc, 'value': value})
35
+ return rows
36
+
37
+
38
+ def decode_stabs(data, layout, rows):
39
+ sources, objects, functions, unresolved = [], [], [], []
40
+ directory = source = obj = None
41
+ active = None
42
+ for row in rows:
43
+ kind, name = row['kind'], row['name']
44
+ if kind == 'N_SO':
45
+ if name.endswith('/'):
46
+ directory = name
47
+ elif name:
48
+ source = name
49
+ sources.append({'directory': directory, 'source': source, 'record': row['symbol_index']})
50
+ else:
51
+ source = obj = None
52
+ directory = None
53
+ elif kind == 'N_OSO':
54
+ obj = name
55
+ objects.append({'object': obj, 'mtime_untrusted': row['value'], 'record': row['symbol_index']})
56
+ elif kind == 'N_FUN' and name:
57
+ if active:
58
+ unresolved.append({'reason': 'function without closing N_FUN', 'record': active['record']})
59
+ active = {'name': name, 'address': row['value'], 'section': row['section'],
60
+ 'directory': directory, 'source': source, 'object': obj, 'record': row['symbol_index']}
61
+ elif kind == 'N_FUN' and not name:
62
+ if not active:
63
+ unresolved.append({'reason': 'closing N_FUN without open function', 'record': row['symbol_index']})
64
+ continue
65
+ size = row['value']
66
+ section = next((s for s in layout['sections'] if s['index'] == active['section'] and
67
+ s['executable'] and not s['zero_fill'] and s['address'] <= active['address'] and
68
+ active['address']+size <= s['address']+s['bytes']), None)
69
+ if section is None or size == 0:
70
+ unresolved.append({'reason': 'function range is not nonempty file-backed executable code',
71
+ 'record': active['record'], 'size': size})
72
+ else:
73
+ offset = section['file_offset']+active['address']-section['address']
74
+ functions.append({**active, 'bytes': size, 'file_offset': offset,
75
+ 'sha256': hashlib.sha256(take(data, offset, size)).hexdigest(),
76
+ 'end_record': row['symbol_index']})
77
+ active = None
78
+ elif kind == 'UNKNOWN':
79
+ unresolved.append({'reason': 'unknown STABS type', 'record': row['symbol_index']})
80
+ if active:
81
+ unresolved.append({'reason': 'unclosed final function', 'record': active['record']})
82
+ return {'record_count': len(rows), 'kind_counts': dict(sorted(Counter(r['kind'] for r in rows).items())),
83
+ 'sources': sources, 'objects': objects, 'functions': functions, 'unresolved': unresolved,
84
+ 'limitations': 'N_FUN end values are interpreted as byte lengths for this linker debug-map pattern. '
85
+ 'Source/object paths and timestamps are embedded build evidence, not verified source identities. '
86
+ 'Raw records retain begin/end/include/static symbols; source types are not reconstructed from absent records.'}
87
+
88
+
89
+ def rva_range(data, layout, rva, size):
90
+ matches = [s for s in layout['sections'] if s['rva'] <= rva and rva+size <= s['rva']+s['bytes']]
91
+ if len(matches) != 1:
92
+ raise ValueError('resource range is not uniquely file-backed')
93
+ offset = matches[0]['file_offset']+rva-matches[0]['rva']
94
+ return offset, take(data, offset, size)
95
+
96
+
97
+ def resource_leaves(data, layout):
98
+ if layout['format'] != 'PE':
99
+ raise ValueError('resource reader expects PE')
100
+ directory = next((d for d in layout['data_directories'] if d['index'] == 2), None)
101
+ if not directory or not directory['bytes']:
102
+ return []
103
+ base = directory['rva_or_security_file_offset']
104
+ _, tree = rva_range(data, layout, base, directory['bytes'])
105
+ leaves = []
106
+ nodes = 0
107
+ def walk(offset, path, ancestors):
108
+ nonlocal nodes
109
+ nodes += 1
110
+ if offset in ancestors or len(path) > 8 or nodes > 65536:
111
+ raise ValueError('cyclic or excessive resource tree')
112
+ header = unpack('<IIHHHH', tree, offset)
113
+ named, ids = header[-2:]
114
+ entries = take(tree, offset+16, (named+ids)*8)
115
+ for i in range(named+ids):
116
+ name, target = unpack('<II', entries, i*8)
117
+ if bool(name & 0x80000000) != (i < named):
118
+ raise ValueError('resource named/ID entry ordering mismatch')
119
+ if name & 0x80000000:
120
+ at = name & 0x7fffffff
121
+ length = unpack('<H', tree, at)[0]
122
+ component = take(tree, at+2, length*2).decode('utf-16le')
123
+ else:
124
+ component = name
125
+ current = path+[component]
126
+ if target & 0x80000000:
127
+ walk(target & 0x7fffffff, current, ancestors | {offset})
128
+ else:
129
+ rva, size, codepage, reserved = unpack('<4I', tree, target)
130
+ if reserved:
131
+ raise ValueError('nonzero resource data reserved field')
132
+ file_offset, payload = rva_range(data, layout, rva, size)
133
+ leaves.append({'path': current, 'data_entry_relative_offset': target,
134
+ 'rva': rva, 'file_offset': file_offset, 'bytes': size, 'codepage': codepage,
135
+ 'sha256': hashlib.sha256(payload).hexdigest()})
136
+ walk(0, [], set())
137
+ return leaves
138
+
139
+
140
+ def icon_group(payload):
141
+ reserved, kind, count = unpack('<3H', payload, 0)
142
+ if reserved or kind != 1 or len(payload) != 6+14*count:
143
+ raise ValueError('invalid group icon header/length')
144
+ rows = []
145
+ for i in range(count):
146
+ width, height, colors, reserved, planes, bits, size, ident = unpack('<4B2HIH', payload, 6+i*14)
147
+ if reserved:
148
+ raise ValueError('invalid icon entry reserved field')
149
+ rows.append({'width': width or 256, 'height': height or 256, 'colors': colors,
150
+ 'planes': planes, 'bits': bits, 'bytes': size, 'resource_id': ident})
151
+ return rows
152
+
153
+
154
+ def string_table(payload, block):
155
+ if not isinstance(block, int) or block < 1:
156
+ raise ValueError('string table requires a positive numeric block ID')
157
+ rows, offset = [], 0
158
+ for i in range(16):
159
+ length = unpack('<H', payload, offset)[0]
160
+ offset += 2
161
+ value = take(payload, offset, length*2).decode('utf-16le')
162
+ offset += length*2
163
+ rows.append({'id': (block-1)*16+i, 'text': value})
164
+ if offset != len(payload):
165
+ raise ValueError('trailing string table bytes')
166
+ return rows
167
+
168
+
169
+ def icon_dib(payload):
170
+ # Retain surplus bytes rather than assuming Windows ignores them.
171
+ size, width, doubled_height, planes, bits, compression, image_bytes, _, _, colors, _ = unpack('<IiiHHIIiiII', payload, 0)
172
+ if size != 40 or width <= 0 or doubled_height <= 0 or doubled_height % 2 or planes != 1 or bits not in (8, 32) or compression:
173
+ raise ValueError('unsupported icon DIB layout')
174
+ height = doubled_height//2
175
+ palette = (colors or (1 << bits if bits <= 8 else 0))*4
176
+ xor = ((width*bits+31)//32)*4*height
177
+ mask = ((width+31)//32)*4*height
178
+ expected = 40+palette+xor+mask
179
+ take(payload, 0, expected)
180
+ return {'width': width, 'height': height, 'bits': bits, 'declared_image_bytes': image_bytes,
181
+ 'palette_bytes': palette, 'xor_bytes': xor, 'standard_and_mask_bytes': mask,
182
+ 'standard_layout_bytes': expected, 'surplus_bytes': len(payload)-expected,
183
+ 'surplus_sha256': hashlib.sha256(payload[expected:]).hexdigest() if len(payload)>expected else None}
184
+
185
+
186
+ def reconstruct_icon(data, leaves, group):
187
+ rows = icon_group(take(data, group['file_offset'], group['bytes']))
188
+ header = bytearray(struct.pack('<3H', 0, 1, len(rows)))
189
+ images = []
190
+ offset = 6+16*len(rows)
191
+ for row in rows:
192
+ matches = [r for r in leaves if r['path'] == [3, row['resource_id'], group['path'][2]]]
193
+ if len(matches) != 1 or matches[0]['bytes'] != row['bytes']:
194
+ raise ValueError('missing/ambiguous icon language or mismatched image size')
195
+ leaf = matches[0]
196
+ image = take(data, leaf['file_offset'], leaf['bytes'])
197
+ header.extend(struct.pack('<4B2H2I', row['width'] % 256, row['height'] % 256,
198
+ row['colors'], 0, row['planes'], row['bits'], len(image), offset))
199
+ images.append(image)
200
+ offset += len(image)
201
+ return bytes(header)+b''.join(images), rows
202
+
203
+
204
+ def main():
205
+ summaries = []
206
+ for label in ('macos--libSDL-1.2.0.dylib', 'macos--Quake3-UrT.x86_64',
207
+ 'windows--Quake3-UrT.exe', 'windows--Quake3-UrT-Ded.exe'):
208
+ report = json.loads(Path(f'docs/research/native/{label}.json').read_text())
209
+ data = verified((Path('.cache/UrbanTerror43')/report['source']['path']).read_bytes(), report['source']['sha256'])
210
+ layout, _ = parse(data)
211
+ output = {'source': report['source']}
212
+ if layout['format'] == 'Mach-O':
213
+ rows = stab_records(data, layout)
214
+ output.update(decode_stabs(data, layout, rows))
215
+ artifact = Path(f'assets/analysis/native/{label}.stabs.jsonl.gz')
216
+ write_rows(artifact, rows)
217
+ output['raw_records'] = str(artifact)
218
+ summaries.append({'image': label, 'stabs': len(rows), 'function_ranges': len(output['functions']),
219
+ 'objects': len(output['objects']), 'unresolved': len(output['unresolved'])})
220
+ else:
221
+ leaves = resource_leaves(data, layout)
222
+ root = Path(f'assets/analysis/native/resources/{label}')
223
+ icons = []
224
+ for i, leaf in enumerate(leaves):
225
+ path = root/f'{i:06d}.bin'
226
+ path.parent.mkdir(parents=True, exist_ok=True)
227
+ path.write_bytes(take(data, leaf['file_offset'], leaf['bytes']))
228
+ leaf['output'] = str(path)
229
+ if len(leaf['path']) == 3 and leaf['path'][0] == 3:
230
+ leaf['dib'] = icon_dib(take(data, leaf['file_offset'], leaf['bytes']))
231
+ if len(leaf['path']) == 3 and leaf['path'][0] == 6:
232
+ leaf['strings'] = string_table(take(data, leaf['file_offset'], leaf['bytes']), leaf['path'][1])
233
+ if len(leaf['path']) == 3 and leaf['path'][0] == 14:
234
+ ico, entries = reconstruct_icon(data, leaves, leaf)
235
+ path = root/f'{i:06d}.ico'
236
+ path.write_bytes(ico)
237
+ icons.append({'group_path': leaf['path'], 'entries': entries, 'output': str(path),
238
+ 'bytes': len(ico), 'sha256': hashlib.sha256(ico).hexdigest()})
239
+ output.update({'leaves': leaves, 'icon_groups': icons, 'limitations':
240
+ 'All leaves are preserved. Shipped icon groups, DIB headers/extents and string tables are decoded. '
241
+ 'Pixel/mask behavior and surplus icon bytes remain unclassified; other resource types are not decoded.'})
242
+ summaries.append({'image': label, 'resource_leaves': len(leaves),
243
+ 'type_counts': dict(Counter(str(r['path'][0]) for r in leaves)), 'icon_groups': len(icons)})
244
+ write_json(Path(f'docs/research/native/{label}.metadata.json'), output)
245
+ write_json(Path('docs/research/native/metadata-summary.json'), summaries)
246
+ print(json.dumps(summaries, indent=2))
247
+
248
+
249
+ if __name__ == '__main__':
250
+ main()
scripts/test_native_metadata.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Malformed metadata, resource variants and supplied image reconstruction checks."""
3
+ import gzip
4
+ import hashlib
5
+ import json
6
+ from pathlib import Path
7
+ import struct
8
+ import unittest
9
+ from analyze_native import parse
10
+ from analyze_native_metadata import resource_leaves, decode_stabs, string_table, icon_group, reconstruct_icon, icon_dib
11
+
12
+
13
+ def resource_fixture():
14
+ data = bytearray(512)
15
+ for offset, ident, target in [(0, 3, 0x80000018), (24, 7, 0x80000030), (48, 1033, 72)]:
16
+ struct.pack_into('<IIHHHHII', data, offset, 0, 0, 0, 0, 0, 1, ident, target)
17
+ struct.pack_into('<4I', data, 72, 0x1080, 4, 0, 0)
18
+ data[128:132] = b'icon'
19
+ return data, {'format': 'PE', 'data_directories': [{'index': 2, 'rva_or_security_file_offset': 0x1000, 'bytes': 128}],
20
+ 'sections': [{'rva': 0x1000, 'bytes': 512, 'file_offset': 0}]}
21
+
22
+
23
+ class MetadataTests(unittest.TestCase):
24
+ def test_rva_and_relative_offsets(self):
25
+ data, layout = resource_fixture()
26
+ r = resource_leaves(data, layout)
27
+ self.assertEqual(r[0]['path'], [3, 7, 1033])
28
+ self.assertEqual(r[0]['file_offset'], 128)
29
+ self.assertEqual(r[0]['sha256'], hashlib.sha256(b'icon').hexdigest())
30
+
31
+ def test_named_resource(self):
32
+ data, layout = resource_fixture()
33
+ struct.pack_into('<HH', data, 12, 1, 0)
34
+ struct.pack_into('<I', data, 16, 0x80000064)
35
+ struct.pack_into('<H', data, 100, 2)
36
+ data[102:106] = '名字'.encode('utf-16le')
37
+ self.assertEqual(resource_leaves(data, layout)[0]['path'][0], '名字')
38
+
39
+ def test_cycle_and_unbacked_payload_rejected(self):
40
+ for offset, value in [(20, 0x80000000), (72, 0x11ff)]:
41
+ data, layout = resource_fixture()
42
+ struct.pack_into('<I', data, offset, value)
43
+ with self.subTest(offset=offset), self.assertRaises(ValueError):
44
+ resource_leaves(data, layout)
45
+
46
+ def test_string_table_lengths_and_ids(self):
47
+ payload = struct.pack('<H', 2)+'Hi'.encode('utf-16le')+b'\0\0'*15
48
+ self.assertEqual(string_table(payload, 2)[0], {'id': 16, 'text': 'Hi'})
49
+ for malformed in (payload[:-1], payload+b'\0'):
50
+ with self.assertRaises(ValueError):
51
+ string_table(malformed, 2)
52
+
53
+ def test_icon_variant_must_match_language_and_length(self):
54
+ group = struct.pack('<3H4B2HIH', 0, 1, 1, 16, 16, 0, 0, 1, 32, 4, 7)
55
+ rows = icon_group(group)
56
+ self.assertEqual(rows[0]['resource_id'], 7)
57
+ leaf = {'path': [14, 1, 1033], 'file_offset': 0, 'bytes': len(group)}
58
+ for variant in ({'path': [3, 7, 1031], 'bytes': 4}, {'path': [3, 7, 1033], 'bytes': 3}):
59
+ with self.assertRaises(ValueError):
60
+ reconstruct_icon(group, [variant], leaf)
61
+
62
+ def test_stabs_end_is_length_and_unmatched_retained(self):
63
+ layout = {'sections': [{'index': 1, 'executable': True, 'zero_fill': False,
64
+ 'address': 0x1000, 'bytes': 16, 'file_offset': 0}]}
65
+ rows = [{'kind': 'N_FUN', 'name': '_fn', 'value': 0x1004, 'section': 1, 'symbol_index': 1},
66
+ {'kind': 'N_FUN', 'name': '', 'value': 4, 'section': 0, 'symbol_index': 2}]
67
+ r = decode_stabs(b'0123456789abcdef', layout, rows)
68
+ self.assertEqual(r['functions'][0]['sha256'], hashlib.sha256(b'4567').hexdigest())
69
+ self.assertEqual(len(decode_stabs(b'0'*16, layout, rows[:1])['unresolved']), 1)
70
+
71
+ def test_shipped_ranges_and_icon_reconstruction(self):
72
+ for label, count in [('macos--libSDL-1.2.0.dylib', 1012), ('macos--Quake3-UrT.x86_64', 6)]:
73
+ r = json.loads(Path(f'docs/research/native/{label}.metadata.json').read_text())
74
+ data = (Path('.cache/UrbanTerror43')/r['source']['path']).read_bytes()
75
+ self.assertEqual(hashlib.sha256(data).hexdigest(), r['source']['sha256'])
76
+ self.assertEqual(len(r['functions']), count)
77
+ self.assertEqual(r['unresolved'], [])
78
+ previous_end = 0
79
+ for f in sorted(r['functions'], key=lambda f: f['address']):
80
+ self.assertGreaterEqual(f['address'], previous_end)
81
+ previous_end = f['address']+f['bytes']
82
+ self.assertEqual(hashlib.sha256(data[f['file_offset']:f['file_offset']+f['bytes']]).hexdigest(), f['sha256'])
83
+ raw = [json.loads(line) for line in gzip.open(r['raw_records'], 'rt')]
84
+ self.assertEqual(len(raw), r['record_count'])
85
+ r = json.loads(Path('docs/research/native/windows--Quake3-UrT.exe.metadata.json').read_text())
86
+ data = (Path('.cache/UrbanTerror43')/r['source']['path']).read_bytes()
87
+ leaves = resource_leaves(data, parse(data)[0])
88
+ self.assertEqual(len(leaves), 9)
89
+ for leaf in r['leaves']:
90
+ self.assertEqual(hashlib.sha256(Path(leaf['output']).read_bytes()).hexdigest(), leaf['sha256'])
91
+ group = next(l for l in leaves if l['path'][0] == 14)
92
+ ico, rows = reconstruct_icon(data, leaves, group)
93
+ self.assertEqual(ico, Path(r['icon_groups'][0]['output']).read_bytes())
94
+ self.assertEqual(len(rows), 7)
95
+ for i, row in enumerate(rows):
96
+ _, _, _, _, _, _, size, offset = struct.unpack_from('<4B2H2I', ico, 6+i*16)
97
+ image = ico[offset:offset+size]
98
+ self.assertEqual(size, row['bytes'])
99
+ self.assertEqual(icon_dib(image)['width'], row['width'])
100
+
101
+
102
+ if __name__ == '__main__':
103
+ unittest.main()