Skip to content

fix: respect blocked_relocation_sources in x86 abs32 reconstruction#2

Merged
bwrsandman merged 3 commits into
coff_prototypefrom
fix/noreloc-abs32-sources
Jul 11, 2026
Merged

fix: respect blocked_relocation_sources in x86 abs32 reconstruction#2
bwrsandman merged 3 commits into
coff_prototypefrom
fix/noreloc-abs32-sources

Conversation

@handsomematt

Copy link
Copy Markdown
Member

The noreloc symbol attribute (and config block_relocations with a source) populate blocked_relocation_sources, but neither x86 abs32 pass consulted it:

  • resolve_abs32_candidates (src/analysis/x86.rs) — resolves the abs32 candidates collected during analysis, and
  • reconstruct_abs32_relocations_by_scan (src/util/coff.rs) — the /FIXED image data scan.

So noreloc parsed fine but had no effect for PE/COFF data, and data whose contents merely look like in-image pointers still got relocations invented for it.

Concrete case from BW1W120's LHScript.cpp unit: UTF-16 string literals. The wide chars "X\0_\0" in L"MAX_DEPTH" read as dword 0x005F0058, which lands inside the image, so the data scan stamped a reloc (rewriting the extracted bytes to the addend form 18 01 00 00) and the split object's string no longer byte-matched the compiler's ??_C@ comdat:

target:  4D 00 41 00 18 01 00 00 44 00 45 00 ...   ("M A <reloc> D E ...")
ours:    4D 00 41 00 58 00 5F 00 44 00 45 00 ...   ("M A X _ D E ...")

With this fix, marking the affected string symbols noreloc in symbols.txt keeps them as raw bytes; all ~90 string literals in that unit now match 100%.

Both passes get the same 4-line guard: skip a candidate whose source operand address falls inside a blocked range.

Tested against runblack-decrypted.exe (BW1W120): the LHScript unit's data goes from 16 corrupted string objects to all 98 data objects matching; no changes to any other unit's output (verified with a full report diff against a pre-change baseline).

Note for the release: a binary built from the v0.0.19 tag also rejects a pre-existing trnsctrl.obj split/symbol overlap in the bw1 config that the published v0.0.19 binary tolerated (fixed on the bw1 side by giving ?_GetRangeOfTrysToCheck an explicit size:0x7B), so the published binary appears to predate the tag.

🤖 Generated with Claude Code

https://claude.ai/code/session_014kEDAMC4whgcjekmVde9PA

handsomematt and others added 3 commits July 11, 2026 19:55
The `noreloc` symbol attribute (and config `block_relocations` sources)
populate `blocked_relocation_sources`, but neither x86 abs32 pass
consulted it:

- `resolve_abs32_candidates` (analysis/x86.rs), which resolves the
  abs32 candidates collected during analysis, and
- `reconstruct_abs32_relocations_by_scan` (util/coff.rs), the /FIXED
  image data scan.

As a result, data whose contents merely look like in-image pointers
still got relocations invented for it. Concrete case from BW1: UTF-16
string literals — e.g. the wide chars "X\0_\0" in "MAX_DEPTH" read as
0x005F0058, which lands in the image, so the scan stamped a reloc with
addend over the string and the extracted bytes no longer matched the
compiler's comdat.

Skip candidates whose source address falls inside a blocked range in
both passes, making `noreloc` effective for PE/COFF data.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014kEDAMC4whgcjekmVde9PA
Pre-existing on coff_prototype; CI runs latest stable clippy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014kEDAMC4whgcjekmVde9PA
- anyhow 1.0.100 -> 1.0.103 (RUSTSEC-2026-0190)
- crossbeam-epoch 0.9.18 -> 0.9.20 (RUSTSEC-2026-0204)
- ignore RUSTSEC-2026-0194/0195: quick-xml 0.36 is pinned by nodtool 1.4;
  the fixed quick-xml (>=0.41) is only reachable via nodtool 2.0 alphas

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014kEDAMC4whgcjekmVde9PA
@bwrsandman

Copy link
Copy Markdown
Member

Dtk has a string hint, wouldn't using that be a better hint than this new one one never heard of before?

@bwrsandman bwrsandman merged commit e47ada4 into coff_prototype Jul 11, 2026
30 checks passed
@bwrsandman bwrsandman deleted the fix/noreloc-abs32-sources branch July 11, 2026 23:40
handsomematt added a commit to openblack/bw1-decomp that referenced this pull request Jul 12, 2026
Implement the LHLib text script engine (BW1W120 0x7E7260-0x7EA0B0) as the
genuine template it was: LHScriptX<T>, LHScriptCommandX<T>, LHScriptPramX<T>
and LHScriptVariable<T>, instantiated for char and wchar_t through the
original's own bodge_to_force_compilation_of_versions(). Static data
(Commands/Conditions/NamesToCondition tables, the Pram buffers and
LastLineParsed) reconstructed verbatim from the binary.

Key build-model findings, all evidence-driven:
- The TU was compiled with inlining disabled (/Ob0) plus /GX: every tiny
  header-inline member (LHScreen::Depth/MaxDepth, LHSpriteList ctor,
  LHStringOps wrappers, makechar/fromchar) exists as a called comdat inside
  the TU's range. Depth/MaxDepth move from LHScreen.cpp into the header.
- The memory-tracking operator new has a matching placement delete
  (declared in LHWin.h); its presence is what gives /GX functions using
  new(__FILE__, __LINE__) on ctor types their EH frames, and its calls are
  the unwind funclets parked at 0x8A7B20+.
- #line pins reproduce the original __FILE__/__LINE__ values
  (C:\Dev\Libs\lionhead\lhlib\VER5.0\LHScript.cpp, lines 312/333/355/737/
  1032/1035), matching both the pushed line immediates and the path string.
- LH_RETURN's real enumerator names (LH_OK/LH_FAIL/LH_ERROR/LH_NO_MEMORY)
  fall out of the script condition tables; the enum moves to LHReturn.h.
- LHStringOps<char>/<wchar_t> CRT wrappers land in LHString.h (name
  fabricated); LHText gains LoadFont and the LHSpriteList ctor/Set.

Result: 29 of 44 target functions at 100.0% instruction match and all 98
data objects (tables, both Pram statics, ~90 string literals) matching.
ScanLine, ProcessCommand and GetConditionValue remain nonmatching with
TODO notes; switch jump-table symbols are capped by diff-tool
representation, not bytes.

The wide string literals required decomp-toolkit v0.0.20 (dtk_tag bumped):
its abs32 reconstruction previously ignored noreloc symbols and invented
relocations inside UTF-16 text whose characters look like in-image
pointers (openblack/decomp-toolkit#2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bwrsandman pushed a commit to openblack/bw1-decomp that referenced this pull request Jul 12, 2026
Implement the LHLib text script engine (BW1W120 0x7E7260-0x7EA0B0) as the
genuine template it was: LHScriptX<T>, LHScriptCommandX<T>, LHScriptPramX<T>
and LHScriptVariable<T>, instantiated for char and wchar_t through the
original's own bodge_to_force_compilation_of_versions(). Static data
(Commands/Conditions/NamesToCondition tables, the Pram buffers and
LastLineParsed) reconstructed verbatim from the binary.

Key build-model findings, all evidence-driven:
- The TU was compiled with inlining disabled (/Ob0) plus /GX: every tiny
  header-inline member (LHScreen::Depth/MaxDepth, LHSpriteList ctor,
  LHStringOps wrappers, makechar/fromchar) exists as a called comdat inside
  the TU's range. Depth/MaxDepth move from LHScreen.cpp into the header.
- The memory-tracking operator new has a matching placement delete
  (declared in LHWin.h); its presence is what gives /GX functions using
  new(__FILE__, __LINE__) on ctor types their EH frames, and its calls are
  the unwind funclets parked at 0x8A7B20+.
- #line pins reproduce the original __FILE__/__LINE__ values
  (C:\Dev\Libs\lionhead\lhlib\VER5.0\LHScript.cpp, lines 312/333/355/737/
  1032/1035), matching both the pushed line immediates and the path string.
- LH_RETURN's real enumerator names (LH_OK/LH_FAIL/LH_ERROR/LH_NO_MEMORY)
  fall out of the script condition tables; the enum moves to LHReturn.h.
- LHStringOps<char>/<wchar_t> CRT wrappers land in LHString.h (name
  fabricated); LHText gains LoadFont and the LHSpriteList ctor/Set.

Result: 29 of 44 target functions at 100.0% instruction match and all 98
data objects (tables, both Pram statics, ~90 string literals) matching.
ScanLine, ProcessCommand and GetConditionValue remain nonmatching with
TODO notes; switch jump-table symbols are capped by diff-tool
representation, not bytes.

The wide string literals required decomp-toolkit v0.0.20 (dtk_tag bumped):
its abs32 reconstruction previously ignored noreloc symbols and invented
relocations inside UTF-16 text whose characters look like in-image
pointers (openblack/decomp-toolkit#2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants