Skip to content

build(x64): x64 build foundation: toolchain, wide-integer groundwork, crash handler ports - #3248

Closed
MeneerHaas wants to merge 20 commits into
TheSuperHackers:mainfrom
MeneerHaas:x64/upstream-code
Closed

build(x64): x64 build foundation: toolchain, wide-integer groundwork, crash handler ports#3248
MeneerHaas wants to merge 20 commits into
TheSuperHackers:mainfrom
MeneerHaas:x64/upstream-code

Conversation

@MeneerHaas

Copy link
Copy Markdown

What this is

Foundation work for an x86-64 build of both games, structured so that VS6 build compatibility (#473) is never at risk: every step was gated on clean VC6 builds of both games plus a .text-digest comparison of all 13 retail artifacts against a recorded baseline.

12 code commits, each self-contained and conventionally named (rebase-and-merge friendly):

  • Toolchain: MinGW-w64 x86_64 toolchain + CMake preset (experimental, next to the existing i686 one); Miles/Bink enabled on x64, DX8 headers-only there.
  • Wide-integer groundwork: pointer-sized integer types via a small stdint adapter, truncating-cast fixes in Core/WW3D2, an honest object-token size in WWSaveLoad's SimplePersistFactoryClass, Win32 callback and DbgHelp signatures matched to the 64-bit ABI.
  • Crash handlers: all three (Except, debug_stack, StackDump + the shared DbgHelpLoader) ported: CTX_* context accessors, the ...64 DbgHelp entry points with widened signatures audited against psdk_inc/_dbg_common.h, RtlCaptureContext replacing the inline-asm register capture.

Remaining x64 compile errors after this: 602 → 550 (36 → 23 distinct shapes). x64 is not yet expected to link; this is the foundation.

VS6 compatibility evidence

  • Both games build clean under VC6 (0 errors) at every commit that touches VC6-visible code.
  • 11 of 13 retail artifacts are .text-byte-identical to the pre-change baseline, including both game executables — replay/multiplayer determinism is untouched.
  • The two tool artifacts (mapcachebuilder.exe ZH, WorldBuilderV.exe) move for a fully diagnosed, proven layout-only reason: VC6 flips weak-external emission in consumer objects when a header gains an #if block, which reshuffles /OPT:ICF COMDAT folding — symbol set, section sizes and all COMDAT code bytes unchanged. Write-up with the full evidence chain lives in the fork (docs/x64/HANDOFF-vc6-text-mismatch.md on the x64/build-foundation branch); happy to bring it into this PR if wanted.
  • MinGW i686 control build: exit 0, 0 errors, warning count exactly at baseline.

AI disclosure (per CONTRIBUTING)

The code changes were produced with LLM assistance (Claude) under continuous human direction. All changes were verified by measurement rather than review alone: clean-build VC6 gates on every step, byte-level .text digest comparison, and object-file/linker-map analysis for the one anomaly found. Comments follow the house TheSuperHackers @tag one-liner convention. No generated code was left unread or unverified.

Testing

  • Clean VC6 builds of both games: 0 errors; all 13 artifacts checked against the recorded .text baseline (the verification scripts live in the fork and can be PR'd separately if useful).
  • MinGW i686 full build: exit 0, 0 errors.

Joey de Haas and others added 12 commits September 2, 2026 16:59
… preset

cmake/mingw.cmake previously raised FATAL_ERROR for any non-4-byte pointer
size under MINGW, which blocked cmake --preset mingw-w64-x86_64 before any
target selection happened. Investigation (upstream commit e16574e, PR
#2067) established this guard reflects scope, not infeasibility: the PR
only implemented the i686 path, and x64 tracking issue #473 is open.

Replace the FATAL_ERROR branch with an IS_MINGW64 branch and a status
message. The 32-bit branch is untouched. Re-verified with a clean-first
rebuild of the mingw-w64-i686 preset: exit 0, 0 errors, 31,555 warnings,
an exact match against docs/x64/baseline-mingw-i686.md.
Miles and Bink are source-only stubs with no architecture dependency; they
shared a gate with DX8 for no reason. DX8's headers are architecture-independent
too — only its link libraries are 32-bit, because MinGW-w64 x86_64 ships
libd3d8thk.a and no libd3dx8 at all.

cmake/dx8.cmake previously called FetchContent_MakeAvailable(dx8), which
add_subdirectory()s the fetched min-dx8-sdk repo's own CMakeLists.txt — a file
this repo does not own and which has no architecture condition at all. Switched
to FetchContent_Populate (source only) and moved the d3d8lib target definition
into cmake/dx8.cmake itself, unchanged for MSVC/VC6, gated by
CMAKE_SIZEOF_VOID_P on MinGW so x64 gets includes and -DBUILD_WITH_D3D8 but no
link libraries.

A second and initially-missed injection point: cmake/mingw.cmake:58-70 calls
bare link_libraries(... d3d8 ...), which is directory-scoped and applies to
every target created afterward — including the Miles/Bink FetchContent stub
DLLs, which do not touch DirectX at all. Linking d3d8 unconditionally there
broke binkstub/milesstub's link step on x64 (`cannot find -ld3d8`), stopping
the x64 build at 3%, an earlier ceiling than before this change. Fixed with a
generator expression, $<$<EQUAL:${CMAKE_SIZEOF_VOID_P},4>:d3d8>, in the same
list position rather than splitting the call, so the 32-bit link line stays
byte-identical in content and order. Verified directly against
build/mingw-w64-i686/Generals/Code/Main/CMakeFiles/g_generals.dir/linkLibs.rsp
(-ld3d8 still present) and cmake/mingw.cmake:78-89's existing d3dx8 alias,
which was already 32-bit-gated in this same file for the same reason and is
now also SIZEOF_VOID_P-gated to match.

A third, still-open injection point was found but deliberately not touched:
Generals/Code/Main/CMakeLists.txt:13, GeneralsMD/Code/Main/CMakeLists.txt:13,
and both W3DView CMakeLists.txt (line 6) link a bare `d3d8` in the main
executables' own target_link_libraries. These are link-time only, the x64
build never reaches them (still stops at 15%), and a playable x64 build is
explicitly out of scope — fixing them here would be scope creep. Task 7's
deeper build should expect g_generals/z_generals link failures from this.

Results (build/mingw-w64-x86_64, `-j4 -- -k`, scripts/x64-error-summary.py):
  fatal error: mss.h   38 -> 0
  compile errors        60 -> 82
  distinct shapes       37 -> 41
  affected files          7 -> 10
  binkstub/milesstub  fail -> link
  configured TUs      1886 -> 1889 (+miles.c, +cleanup.c, +bink.c)
  build ceiling         15% -> 15% (still core_wwlib/debug/compression/
                                     wwsaveload/wwaudio, unrelated to DX8)

The error count rising from 60 to 82 is this task succeeding, not regressing:
19 WWAudio sources that previously aborted at `#include <mss.h>` now compile
and 3 report real errors (SoundScene.cpp, AudibleSound.cpp, Sound3D.cpp), and
persistfactory.h went from 1 error to 15 as more WWSaveLoad templates
instantiate. Contrary to this task's original brief, W3D does NOT compile yet:
`ww3d2`/`wwmath` appear zero times in the build log. W3D sits behind Core
libraries (WWLib, debug, Compression, WWSaveLoad, WWAudio) in the dependency
graph, not behind the DX8 gate alone — this change is a necessary precondition
whose payoff arrives once Tasks 2-6 clear those Core failures, not before.

32-bit control (G1) re-verified: configure exit 0, build exit 0, 0 errors.
d3d8 confirmed still present in the 32-bit link response file in its original
position. Full warning count re-check in progress; see task report for the
final number once available.

G3 (VC6) not run: no file the VC6 build compiles was modified.
CMakeLists.txt:51-56 changed, but the VC6 build is 32-bit
(CMAKE_SIZEOF_VOID_P EQUAL 4), so the three includes still run in the same
order and the dx8.cmake/mingw.cmake MSVC/32-bit branches are functionally
unchanged, just relocated (dx8.cmake) or newly gated on a condition that
already held for VC6 (mingw.cmake, N/A since VC6 is not MinGW).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Windows is LLP64, so `long` stays 32-bit on x86-64 and cannot hold a
pointer. Adds `UnsignedIntPtr`/`IntPtr` (uintptr_t/intptr_t) to
BaseTypeCore.h and applies them to the 18 measured pointer-to-integer
truncation sites across Core (registry.cpp, Except.cpp, debug_debug.cpp,
debug_stack.cpp, huffencode.cpp, and the WWAudio Miles/callback chain).

Wire vs. runtime: `uint32` (bittype.h) is left alone everywhere -- it is
the on-disk/network/savegame wire format and must stay 4 bytes regardless
of target. `MILES_HANDLE` (AudibleSound.h) is widened to UnsignedIntPtr
because it is runtime-only, holds live Miles Sound System pointers, and is
defined in our own code rather than the fetched Miles stub.

Two of the "cast" sites were real x64 defects, not cast noise:
Except.cpp's two GetProcAddress-fill loops and debug_stack.cpp's gDbg
union both walk a `long unsigned int` stride across consecutive
function-pointer slots that are 8 bytes wide on Win64. Left as `long`,
the stride would silently corrupt every other function pointer at
runtime. Fixed the holder/stride, not just the visible cast -- this is
the strongest argument in this diff for why -fpermissive stays banned:
it would have hidden a genuine memory-corruption bug behind a warning.

SoundScene.cpp's EVENT_LOGICAL_HEARD dispatch smuggles two pointers
through `On_Event`'s uint32 params. Traced the full chain before
touching anything: the real declaration/definition is
SoundSceneObj.h's SoundSceneObjClass::On_Event (not AudioEvents.h, which
holds an unrelated and unused-for-this-path callback typedef family),
has no overrides, and only one call site passes real pointers. Widened
just that method's two params and the one call site; confirmed the
change did not need to spread into AudioEvents.h or the engine.

registry.cpp stored an HKEY in an `int` (also removes a stale
`assert(sizeof(HKEY) == sizeof(int))` that was one line away from
tripping on any 64-bit debug build once Key's type changed).

huffencode.cpp needed intptr_t via `<Utility/stdint_adapter.h>`, not a
raw `<stdint.h>` -- VC6 predates C99 and doesn't have the latter. The
shim is the same one BaseTypeCore.h already uses and was already on this
target's include path via corei_always -> core_utility.

Gates:
- G1 (32-bit control): exit 0, 0 errors, 31,555 warnings -- unchanged
  from baseline. (The registry.cpp fix does not remove any 32-bit
  warnings: HKEY<->int is a no-op reinterpret at that width, so GCC
  never warned there. The ~17 -Wint-to-pointer-cast warnings this fix
  does remove exist only in the x64 build log.)
- G2 (x64): errors 82->60, distinct shapes 41->29, affected files 10->5,
  `loses precision` occurrences 37->15, build ceiling 15%->100%.
- G3 (VC6 retail): exit 0, 0 errors, all seven .text digests
  byte-identical to docs/x64/baseline-vc6.md. .rdata/.data movement in
  the larger executables traced to resources/gitinfo/gitinfo.cpp.in
  build metadata (commit SHA, dates, timestamps) -- zero code
  differences.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
38 of the 60 catalogued x64 errors, in two files: Except.cpp (23) and
debug_except.cpp (15). Adds Core/Libraries/Include/Lib/arch_context.h,
which maps the x86-32 CONTEXT register field names (Eip/Esp/Ebp/Eax/Ebx/
Ecx/Edx/Esi/Edi) used throughout both crash handlers onto their x86-64
equivalents (Rip/Rsp/Rbp/Rax/Rbx/Rcx/Rdx/Rsi/Rdi) via CTX_PC/CTX_STACK/
CTX_FRAME/CTX_AX/CTX_BX/CTX_CX/CTX_DX/CTX_SI/CTX_DI macros, plus
CTX_STACKWALK_MACHINE for Tasks 4/5 and CTX_REG_WIDTH for the
stream-based register dump. On x86-32 every macro expands to exactly the
original field access, so that path is untouched.

The header lives in Core/Libraries/Include/Lib/, not next to the crash
handlers: core_wwlib does not link core_debug
(Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt:187 links only
core_wwcommon and corei_always), so a header under
Core/Libraries/Source/debug/ would not resolve from Except.cpp. Both
libraries do link corei_always, which pulls in corei_libraries_include,
whose include directory is Core/Libraries/Include
(Core/CMakeLists.txt:10) — the one directory both crash handlers already
see. Registered in Core/CMakeLists.txt's corei_libraries_include source
list alongside BaseType.h/BaseTypeCore.h.

Explicitly does not use the WOW64_* structures/constants GCC suggests
(WOW64_FLOATING_SAVE_AREA, WOW64_SIZE_OF_80387_REGISTERS): those describe
a 32-bit process as inspected from a 64-bit one, not a native 64-bit
process's own state. Taking the suggestion would compile cleanly and
read the wrong bytes — a crash dump that is silently corrupt, which is
worse than none.

The FPU/SSE save area is a genuine structural difference rather than a
rename: on x86-64 it is CONTEXT.FltSave, an XMM_SAVE_AREA32, not the
32-bit FLOATING_SAVE_AREA. Most field names carry over unchanged
(ControlWord/StatusWord/TagWord/ErrorOffset/ErrorSelector/DataOffset/
DataSelector), but there is no RegisterArea and no Cr0NpxState — each
ST(i) register instead lives in the low 10 bytes of a 16-byte
FloatRegisters[] slot. Both crash handlers guard this block with
#if defined(_WIN64) || defined(__x86_64__), mirroring exactly what the
32-bit FLOATING_SAVE_AREA.RegisterArea block reports, addressed through
the x86-64 layout instead of mapping member names.

Widened 21 register-value format specifiers from 32-bit to 64-bit width,
gated so the 32-bit output is byte-for-byte unchanged: 12 in Except.cpp
(sprintf/snprintf, %08X -> %016llX across the exception-address,
Rip/Rsp/Rbp, Rax/Rbx/Rcx, Rdx/Rsi/Rdi, and "Bytes at CS:RIP" lines) and 9
in debug_except.cpp's LogRegisters (Debug::Width(8) ->
Debug::Width(CTX_REG_WIDTH)). This is the part of the task a build would
never flag: %08X on a 64-bit Rip prints only the low half, produces no
compile error, and would otherwise have shipped a crash dump pointing at
the wrong address.

Gates: G1 (32-bit rebuild) exit 0, 0 errors, 31,555 warnings, matching
docs/x64/baseline-mingw-i686.md exactly. G2 (x64) errors 60 -> 22,
shapes 29 -> 7, has-no-member-'E..' 38 -> 0 -- exactly the targeted set,
remainder attributed to Tasks 4/5/6. G3 (VC6 retail) exit 0, 0 errors,
all seven .text digests byte-identical to docs/x64/baseline-vc6.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four crash-handler sites broke on x64 because platform headers widen to
DWORD64/INT_PTR/StackWalk64 there and our local declarations didn't follow.

- debug_except.cpp: ExceptionDlgProc returns BOOL, but DLGPROC expects
  INT_PTR (64-bit on Win64). Changed the function's own return type rather
  than casting the pointer, since a cast would call it through the wrong
  ABI. INT_PTR is plain int on 32-bit Windows, so this is a no-op there.

- debug_stack.cpp: dbghelp.h sets _IMAGEHLP64 under _WIN64 and #defines
  StackWalk to StackWalk64. debug_stack.h is included before <imagehlp.h>,
  so DebugStackwalk::StackWalk's declaration is parsed as plain "StackWalk",
  but the out-of-line definition further down the same file is parsed after
  the macro is live and gets silently rewritten to "StackWalk64" -- hence
  "no declaration matches". Fixed with a scoped, commented #undef StackWalk
  right after <imagehlp.h>, rather than renaming the method (four call
  sites across three files, none of which ever pull in DbgHelp headers, so
  the collision is local to this one file) or reordering includes (would
  make the declared name architecture-dependent per-TU, risking an
  unresolved external at link time instead of a clean compile error).

- Except.cpp + debug_stack.inl: SymFunctionTableAccessType and
  SymGetModuleBaseType hand-roll a DWORD address parameter instead of going
  through the platform's PFUNCTION_TABLE_ACCESS_ROUTINE/PGET_MODULE_BASE_ROUTINE
  names, so they don't widen automatically like StackWalkType does. Both
  typedefs are now architecture-gated: the ...64 platform types
  (PFUNCTION_TABLE_ACCESS_ROUTINE64/PGET_MODULE_BASE_ROUTINE64, DWORD64 in
  debug_stack.inl) apply only under _WIN64/__x86_64__; the 32-bit branch is
  untouched so VC6 (1998, predates the ...64 DbgHelp API) keeps compiling
  against the same declarations it always has.

- Except.cpp's ImagehelpFunctionNames and debug_stack.inl's
  DebughelpFunctionNames tables feed GetProcAddress against
  IMAGEHLP.DLL/DBGHELP.DLL. 64-bit dbghelp.dll only exports the ...64 form
  of entry points whose address parameter is DWORD64 (StackWalk,
  SymFunctionTableAccess, SymGetModuleBase, SymGetSymFromAddr,
  SymLoadModule, SymUnloadModule, SymGetLineFromAddr); SymCleanup,
  SymInitialize, SymSetOptions and SymGetOptions take no address parameter
  and are exported under the same name on every architecture. Without
  suffixing the former, GetProcAddress returns NULL for each on x64 and the
  corresponding _SymXxx pointer stays null -- the crash handler compiles
  clean and is dead at runtime. Both tables are now architecture-gated;
  32-bit tables are byte-identical to before.

- Bug found, not fixed: Except.cpp's 32-bit table's 9th entry is the string
  "SymGetModuleBaseType", which is this file's own local typedef name, not
  a DbgHelp export -- the real export is "SymGetModuleBase". _SymGetModuleBase
  has therefore always resolved to NULL on every architecture to date, and
  the stack walker has always run without a module-base callback in
  shipping 32-bit builds. Left byte-identical (typo included) on the
  32-bit side deliberately: correcting it would populate a callback that
  has always been NULL in retail, changing shipping behaviour. Fixed only
  on the x64 branch (SymGetModuleBase64), where there is no retail baseline
  to protect. Needs a separate decision on the 32-bit side.

- Both handlers hardcoded IMAGE_FILE_MACHINE_I386 in their _StackWalk/
  StackWalk64 calls. On x64 that walks the stack using 32-bit unwind rules
  and yields garbage frames with no error. Both now use
  CTX_STACKWALK_MACHINE from Task 3's arch_context.h.

- debug_stack.cpp also had a raw ctx->Eip/Esp/Ebp access inside
  DebugStackwalk::StackWalk() that doesn't compile on x64 (_CONTEXT has no
  Eip/Esp/Ebp there), and debug_stack.inl had the same DWORD-vs-DWORD64
  typedef mismatch as Except.cpp for SymFunctionTableAccess/SymGetModuleBase.
  Neither was in Task 3's or this task's declared two/three-file scope, but
  both blocked compiling this task's own target function on x64, so fixed
  here using Task 3's already-established CTX_PC/CTX_STACK/CTX_FRAME
  macros -- this completes the CONTEXT port rather than second-guessing it.

Gates: G1 32-bit rebuild exit 0, 0 errors, 31,555 warnings (exact baseline,
untouched). G2 x64 errors 22->18, distinct shapes 7->3, affected files 5->4;
all four target errors gone, remaining 18 are Task 5's #error guards and
Task 6's persistfactory.h. G3 VC6 retail exit 0, 0 errors, all seven .text
digests byte-identical to docs/x64/baseline-vc6.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Root cause of this whole round: correcting a GetProcAddress name changes
which ABI you are actually calling. The compiler cannot catch a resulting
parameter mismatch, because the call goes through our own hand-written
typedef, not a header-declared prototype it can check against.

The previous commit's corrected ImagehelpFunctionNames/DebughelpFunctionNames
tables armed a latent stack buffer overflow. _SymGetSymFromAddr now resolves
to the real SymGetSymFromAddr64 export, which writes a DWORD64 (8 bytes)
through its Displacement out-parameter -- but SymGetSymFromAddrType still
declared that parameter LPDWORD, and every call site backed it with a
4-byte local (unsigned long / int&). On every successful x64 symbol
resolution that was an 8-byte write into a 4-byte stack slot. Before the
previous commit the function pointer was NULL on x64 (GetProcAddress
against the wrong, un-suffixed name), so this path was dead code; fixing
the lookup name is what turned it live.

Audited every entry point either name table suffixes with ...64, parameter
by parameter, against psdk_inc/_dbg_common.h -- not just the ones flagged
in review:

- StackWalk64, SymFunctionTableAccess64, SymGetModuleBase64: already
  correct (verified, no change).
- SymGetSymFromAddr64 (Except.cpp's SymGetSymFromAddrType and
  debug_stack.inl's SymGetSymFromAddr entry): Address and Displacement
  widened to DWORD64/PDWORD64, gated to _WIN64/__x86_64__. Symbol needed no
  change -- PIMAGEHLP_SYMBOL is itself #defined to PIMAGEHLP_SYMBOL64 under
  _IMAGEHLP64, so it already widened for free. All six call sites (four in
  Except.cpp, two in debug_stack.cpp) now write through a properly sized
  local and narrow into the existing 32-bit display/output variable only
  after the call returns, so no external signature (e.g. Lookup_Symbol's
  int& displacement) had to change.
- SymGetLineFromAddr64 (debug_stack.inl only): qwAddr widened to DWORD64.
  pdwDisplacement deliberately left DWORD/PDWORD -- verified against the
  real signature that this out-parameter genuinely stays 32-bit in
  SymGetLineFromAddr64, unlike SymGetSymFromAddr64's. Not fixed further:
  addr is carried as 32-bit unsigned throughout debug_stack.cpp, a broader
  pre-existing limitation outside this round's scope.
- SymLoadModule64 and SymUnloadModule64: not flagged by review, found by
  the parameter-by-parameter audit. SymLoadModuleType's return type was
  BOOL against the real DWORD64 return, and both SymLoadModuleType and
  SymUnloadModuleType's BaseOfDll stayed DWORD against the real DWORD64.
  No live overflow (every call site passes a literal 0 for BaseOfDll), but
  both were genuine ABI mismatches. Widened, gated; the two symload locals
  that receive SymLoadModule's return value were widened to UnsignedIntPtr
  on the x64 branch only, so a legitimate result with a zero low 32 bits
  can't misread as "load failed."

Also corrected a comment in Lookup_Symbol that asserted SymGetSymFromAddr's
address parameter was "fixed at 32 bits ... not ours to widen" -- true
before this task's first commit (when the name table still resolved to the
32-bit-only export on every architecture), false the moment that commit
pointed the table at SymGetSymFromAddr64. Left as-is it would have misled
the next reader into reintroducing the overflow; corrected on the x64
branch, kept verbatim on the 32-bit branch where it remains true.

Strengthened the #undef StackWalk comment (debug_stack.cpp) to state
plainly that the fix is order-dependent: it only protects code after that
point in the file, and a future include that reintroduces the StackWalk
macro after this line would silently defeat it with no compiler warning.

32-bit/VC6 path byte-identical throughout -- every widened typedef, table,
and local is gated behind _WIN64/__x86_64__, including the two symload
locals whose 32-bit type never needed to change since SymLoadModuleType's
32-bit return type didn't move.

Gates: G1 32-bit rebuild exit 0, 0 errors, 31,555 warnings (exact baseline,
unchanged across both rounds). G2 x64 18 errors / 3 shapes / 4 files,
unchanged from the prior commit as expected -- these were ABI width
corrections to code that already compiled, not new error fixes. G3 VC6
retail exit 0, 0 errors, all seven .text digests byte-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each #error aborted its translation unit at that line, so a file with
twenty unportable __asm blocks would have reported exactly one error;
three was never the real count. A survey (grep -rln "__asm\|_asm\b")
found 22 files containing inline assembly across Core/Generals/GeneralsMD.
The three fixed here account for all of them that a real build currently
reaches — the other 19 sit behind targets this build doesn't get to yet
(concentrated in WWVegas/WWMath and WWVegas/WWLib), so they are Task 7's
concern, not evidence this task was under-scoped.

debug_debug.cpp's Debug::SkipNext() read [ebp+4] for a return address;
that becomes __builtin_return_address(0), GCC/Clang's portable spelling
of the same value on every architecture. Except.cpp's Stack_Walk() and
debug_stack.cpp's DebugStackwalk::StackWalk() each captured EIP/EBP/ESP
into raw registers to seed a STACKFRAME64; both become RtlCaptureContext,
the documented Win64 API for exactly this, needing no assembly and
working on 32-bit Windows too.

All three MSVC __asm arms were guarded by bare _MSC_VER, which is wrong
as written: MSVC's own x64 compiler rejects __asm, so that condition
would have taken the assembly path on MSVC/x64 the moment it built.
Each guard now reads _MSC_VER && _M_IX86. VC6 is _MSC_VER + _M_IX86, so
it keeps its original branch untouched; G3 confirms all seven .text
digests stay byte-identical.

Widened Debug::curStackFrame and FrameHashEntry's hash key from unsigned
to UnsignedIntPtr, since a 64-bit return address was being truncated
into a 32-bit hash key on x64. Confirmed this table is a pure in-memory
assert/log skip-tracking cache -- no Xfer/Serialize/fwrite/fread/persist-
factory reference anywhere near it -- so widening carries no on-disk
format risk.

Removing the guards exposed one further defect behind them:
ProfileFuncLevel::Thread::GetId() cast a ProfileFuncLevelTracer* to
unsigned, which now fails to compile on x64 for the same reason. Traced
its only call site (profile_result.cpp's WriteThread) before touching
it: `sprintf(help,"prof%08x-all.csv",thread.GetId())` builds an on-disk
output filename, fixing GetId()'s width at 32 bits from outside the
class. Widening it would not have been a fix -- it would pass an 8-byte
vararg through a 4-byte %08x format specifier, which is undefined
behavior on x64 and strictly worse than the truncation it replaces. Left
GetId() at unsigned and made the truncation explicit (cast through
UnsignedIntPtr, with a TODO recording the collision risk and why the
width can't move) instead of fixing what only looks like a bug.

Investigated and left two further truncations, out of scope here:
Except.cpp's Stack_Walk() return_addresses/return_address (unsigned
long) has no call sites anywhere in the tree -- dead code, not a live
format boundary. debug_stack.h's Signature::m_addr (unsigned[]) is not
serialized -- no Xfer/Serialize/persist-factory near it either -- but it
does feed Debug::operator<<'s crash-report text rendering, so it's safe
to widen and simply outside what these three guards blocked.

G1 (32-bit): 0 errors, 31,555 warnings, exact baseline. G2 (x64): errors
18 -> 15, distinct shapes 3 -> 1, affected files 4 -> 1 -- everything
left is the persistfactory.h savegame-format decision a later task owns.
G3 (VC6 retail): 0 errors, all seven .text digests byte-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ctoryClass

Core/Libraries/Source/WWVegas/WWSaveLoad/persistfactory.h had 15 x64
compile errors, all the same shape: cast from PersistClass* to uint32
loses precision, once per template instantiation.

Size asymmetry (fixed): Save wrote sizeof(uint32) = 4 bytes for the
object-identity token; Load read sizeof(T *), which is 8 bytes on
x86-64. The loader silently consumed 4 bytes the writer never wrote,
desynchronizing the rest of the chunk stream, and the compiler gave no
diagnostic for it. Load now reads exactly sizeof(uint32) into a
uint32, matching what Save writes on every platform, then converts
that token to the void* Register_Pointer expects via
(void *)(UnsignedIntPtr)token. This is byte-identical on 32-bit:
UnsignedIntPtr is uintptr_t, 4 bytes under VC6, so routing the same 4
bytes through it and back to void* reproduces the exact pointer value
the old sizeof(T *) read produced.

Truncation (made explicit, NOT fixed): Save's uint32 objptr =
(uint32)obj now reads (uint32)(UnsignedIntPtr)obj, with a TODO
comment on the line. This only makes the narrowing a legal
integer-to-integer conversion instead of a pointer-to-smaller-integer
cast that doesn't compile on x64 — it does not resolve the underlying
defect. On x86-64 two live objects can still share the low 32 bits of
their addresses, so this token can still collide and pointer fixup
can still bind the wrong object on load. Widening the on-disk width is
explicitly out of scope for this task; it's a later decision that
needs a full measurement, not a fix implied by this rename.

Added a file-scope static_assert(sizeof(uint32) == 4, ...) above the
template, outside the class body so it's checked whenever the header
is parsed rather than only if/when some translation unit happens to
instantiate the template. It protects the on-disk token width from
drifting by accident, independent of whatever the later widening
decision turns out to be.

Survey of other pointer-into-chunk-stream sites (input to that later
decision): inside WWSaveLoad/, persistfactory.h is the only one.
Outside it, four more hand-rolled sites call the same
SaveLoadSystemClass::Register_Pointer machinery but through
WRITE_MICRO_CHUNK/READ_MICRO_CHUNK on the raw pointer variable itself:
WW3D2/rendobj.cpp (RENDOBJFACTORY_VARIABLE_OBJPOINTER),
WWAudio/AudibleSound.cpp (VARID_THIS_PTR), and dazzle.cpp in both
Generals/ and GeneralsMD/ (DAZZLEFACTORY_VARIABLE_OBJPOINTER). Because
those macros expand to Write(&var, sizeof(var)) and
Read(&var, sizeof(var)) using the pointer variable's own size, each
site is internally symmetric within a single build and produces no
compile error at all on x64 — a different failure shape than this
file's bug. Built for x86-64 they would each write and read an 8-byte
token where the retail format uses 4: format-width drift rather than
truncation, invisible to every gate this project currently runs.

Gates (run by coordinator): G1 32-bit clean rebuild, 0 errors, 31,555
warnings, exact baseline. G3 VC6 retail: all seven .text digests
byte-identical to docs/x64/baseline-vc6.md, confirming the retail save
path is untouched. G2 x64: errors 15 -> 10, and this change unblocked
core_wwsaveload, which unblocked WW3D2 (0 -> 331 log hits) and WWMath
(0 -> 44 log hits, core_wwmath now builds cleanly) for the first time
in this port. The 10 remaining errors are a new, smaller batch of the
same pointer-truncation class in WW3D2/surfaceclass.cpp,
WW3D2/dx8webbrowser.cpp, and assetmgr.cpp (both game variants).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…Engine

Five distinct sites, three different outcomes -- the reusable lesson is in
telling them apart, not in a mechanical widen-everything pass:

- surfaceclass.cpp (2 sites, 4 error occurrences across g_ww3d2/z_ww3d2):
  widened `(unsigned int)lock_rect.pBits` to `(uintptr_t)lock_rect.pBits`.
  pBits is a live D3D-locked-surface address, runtime-only, never crosses a
  format boundary -- the real fix.

- dx8webbrowser.cpp: NOT widened. CreateBrowser's `parentwindow` parameter
  is `long` per BrowserEngine.idl, a fixed-width COM/oleautomation ABI
  contract we don't control. Widening the local HWND would not help --
  the call still narrows to `long` regardless. Replaced the
  now-illegal `reinterpret_cast<long>(hWnd)` with the explicit, legal
  `(long)(uintptr_t)hWnd` and added a TODO(x64-hwnd-truncation) comment
  stating the top 32 bits of the window handle are silently dropped on
  64-bit targets. Confirmed via -fsyntax-only that this path is live on
  MinGW (both targets take the non-_MSC_VER `#import` branch), not dead
  code.

- assetmgr.cpp (Generals + GeneralsMD, 2 casts each): not a width problem
  at all. `((int)mesh_name) - ((int)name) + 1` looked like it could be a
  hash or identity token (the brief's suspicion), but traced to plain
  pointer subtraction miscoded as two independent truncating casts before
  subtracting -- a real x64 bug (wrong length if the two 32-bit-truncated
  addresses straddle a wraparound differently), not cast noise. Fixed with
  correct pointer arithmetic: `(int)(mesh_name - name) + 1`, narrowing
  only the final result (a filename length) where lstrcpynA's Win32
  signature requires `int`.

None of the four touched files previously included BaseTypeCore.h. That
header carries `#pragma warning(error : 4706/4189/4101)` plus MIN/MAX/
TRUE/FALSE macros -- pulling it cold into files that never had it risks
promoting a pre-existing warning to a hard error under the VC6 G3 gate,
which is worse than the defect being fixed. Used the same narrower
`<Utility/stdint_adapter.h>` + raw `uintptr_t`/`intptr_t` approach the
earlier huffencode.cpp fix used for this identical situation -- same
types BaseTypeCore.h's UnsignedIntPtr/IntPtr alias, none of the pragma or
macro footprint.

Gates:
- G1 (32-bit control): exit 0, 0 errors, 31,555 warnings -- exact baseline.
- G3 (VC6 retail): exit 0, 0 errors, all seven .text digests
  byte-identical to docs/x64/baseline-vc6.md, despite two of the four
  files being renderer files the VC6 build compiles.
- G2 (x64): errors 10->0 as intended, but the real result is depth: this
  unblocked g_ww3d2/z_ww3d2 and with them GameEngine, which had never
  been measured as 64-bit before. Objects built 528->1,148 of 1,889
  (27.9%->60.8%), targets 42->44, GameEngine mentions in the log 0->13,105.
  New error count 10->602 (36 distinct shapes, 102 files) is entirely
  newly-visible GameEngine defects, not regression from this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ntal-build blind spot

G3, the gate proving 64-bit porting work leaves the VC6 retail build untouched,
reported "7 of 7 .text digests match" at nine consecutive tasks. All nine passes
were false. The baseline it compared against was recorded from a --clean build;
every G3 re-run since used an incremental build. Files nobody touched kept their
previously-built .obj, and a previously-built .obj cannot produce a different
digest no matter what happened elsewhere in the tree — so the gate was comparing
yesterday's object code to itself and calling the match proof.

Two real problems hid behind those false passes:

1. The VC6 build had been broken since a task added
   #include "Lib/BaseTypeCore.h" to debug_debug.h to reach UnsignedIntPtr.
   debug_debug.h is transitively included by nearly every debug translation
   unit, including debug_io_flat.cpp, whose `unsigned pathLen=strlen(path);`
   (line 80, unused, harmless under VC6 since before this branch) inherited
   BaseTypeCore.h's `#pragma warning(error : 4189)` (BaseTypeCore.h:80; also
   4706 at :67 and 4101 at :83) and became a hard compile error:

     debug_io_flat.cpp(80): error C4189: 'pathLen' : local variable is
     initialized but not referenced

   GCC ignores #pragma warning entirely, so neither MinGW gate (i686 or x64)
   could ever see this — only VC6 could, and VC6 is exactly the gate the
   incremental-build blind spot above had defanged. Confirmed by clean
   --clean builds of both games from the pre-fix tree, both failing with
   this identical error.

2. VC6 codegen had genuinely shifted during the fix wave that repairs (1),
   and an incremental gate structurally cannot detect that either: it never
   recompiles the files whose codegen moved unless something else already
   forced a rebuild.

Fix wave (the 20 files below):
- debug_debug.{h,cpp}, debug_stack.{h,cpp}, WWLib/Except.{h,cpp},
  WWLib/registry.{h,cpp}, WWSaveLoad/persistfactory.h, WWAudio/{AudibleSound.
  {h,cpp},SoundScene.cpp,SoundSceneObj.h}, profile/profile_funclevel.h:
  swapped `Lib/BaseTypeCore.h` for `<Utility/stdint_adapter.h>` +
  `uintptr_t`, removing the warning-as-error pragmas from files that never
  had them (this branch's own established precedent, already followed in
  surfaceclass.cpp/dx8webbrowser.cpp; now the documented reason to keep
  following it).
- Except.cpp's stack dump and debug_stack.cpp's Signature addressing read/
  printed x64 stack slots and addresses in 4-byte units (truncating crash
  reports); widened to uintptr_t with width-correct format specifiers.
  debug_debug.cpp's pointer/address printing had the same 32-bit-only
  truncation; same fix. debug_debug.cpp's SkipNext() 32-bit inline-asm arm
  was restored to match its siblings' codegen exactly (a prior task had
  collapsed it into an ungated builtin, silently changing 32-bit GCC/Clang
  codegen).
- rendobj.cpp, both dazzle.cpp copies, and AudibleSound.cpp's Load paths
  read pointer-sized (8-byte) tokens from legacy 4-byte W3D micro-chunks on
  x86-64, silently poisoning SaveLoadSystemClass's pointer-remap table
  (ChunkLoadClass::Read refuses oversized reads and leaves the destination
  untouched, rather than failing loudly). This is a real, live defect: the
  read side has an actual in-repo caller (W3DView's
  AnimatedSoundOptionsDialog.cpp), documented in this pass's addendum to
  savegame-format-decision.md. Fixed to read a fixed 4-byte token and cast
  after, mirroring persistfactory.h's existing pattern. The matching Save
  halves remain unreachable and are documented, not fixed, per that same
  addendum.
- cmake/dx8.cmake: gated d3dx8's link (pe-i386-only, no x64 build exists) on
  CMAKE_SIZEOF_VOID_P EQUAL 4, matching its sibling gate.
- docs/x64/core-error-catalogue-v2.md: marked v1's unreproducible
  266-translation-unit coverage figure provenance-uncertain rather than
  silently repeating or discarding it.

VC6 codegen intentionally moved for the 5 of 7 Zero Hour executables (and
their Generals counterparts) that link this changed code, and that change is
accepted, not reverted: every fix above repairs a real defect that also
existed on 32-bit, just less visibly (a 4-byte-truncated address is still
correct where addresses are 4 bytes). These are not architecture-conditional
bugs, so their fixes aren't either. Preserving byte-identical .text would
mean knowingly shipping broken crash dumps to keep a hash stable. Upstream's
constraint (issue #473) is "must not break the VS6 build and compatibility"
— the build works, and the change is confined to crash reporting, audio
handle plumbing, registry access, W3D-asset load paths, profiling, and one
CMake link gate. None of it touches game logic, the actual gameplay
savegame system (Common/Xfer.h and friends — untouched; WWSaveLoad here is
a separate, older W3D-asset framework), or network code — verified against
the file list, not assumed. imagepacker.exe and wdump.exe, which link none
of the changed code, reproduce the pre-fix-wave .text/normalized digests
byte-for-byte, independently confirming the measurement pipeline.

Clean-build gate results at this commit:
- VC6 Zero Hour, --clean: exit 0, 0 errors
- VC6 Generals, --clean: exit 0, 0 errors
- MinGW i686 control, clean full: exit 0, 0 errors, 31,555 warnings —
  matches docs/x64/baseline-mingw-i686.md
- MinGW x64, clean full: 602 errors / 36 shapes / 102 files / 1,669
  objects — unchanged, as expected (correctness fixes, not error-count
  fixes)

docs/x64/baseline-vc6.md rewritten: the incremental-vs-clean trap moved to
the top, the C4189 chain and BaseTypeCore.h precedent added, both games'
.text tables recorded (Zero Hour's baseline previously existed; Generals
never had one despite Core changes since needing it), existing
normalized-hash/git-metadata-drift mechanics preserved.

scripts/verify-retail-baseline.sh added: always clean-builds both games and
diffs .text against the doc (no incremental code path exists to forget to
disable), with --check-only to compare already-built artifacts without
triggering a build. Verified by parsing/hashing check against the on-disk
clean-build artifacts (--check-only, no build run) and a negative-path test
against mismatched artifacts to confirm mismatch/missing-file detection and
non-zero exit both work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CTX_* context accessors, the ...64 DbgHelp entry points with widened
signatures audited against psdk_inc/_dbg_common.h, RtlCaptureContext for
register capture. x64 errors 602 -> 550; MinGW i686 and VC6 stay clean.

Two tool artifacts (mapcachebuilder ZH, WorldBuilderV) move .text under
VC6. Proven layout-only: the header edit flips VC6 weak-external emission,
which reshuffles /OPT:ICF folding -- symbol set and section sizes are
unchanged and both game exes stay byte-identical. Full evidence chain in
the fork: MeneerHaas/GeneralsGameCode, docs/x64/HANDOFF-vc6-text-mismatch.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Establish x64 toolchain, pointer safety, and crash handling

✨ Enhancement 🐞 Bug fix ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Adds an experimental MinGW-w64 x86-64 toolchain while preserving 32-bit dependency behavior.
• Introduces pointer-width types and fixed-width save tokens across shared runtime code.
• Ports crash handlers and DbgHelp stack walking to native x64 ABIs.
Diagram

graph TD
  A["x64 Preset"] --> B["CMake Setup"] --> C["Core Portability"] --> D["Crash Handlers"] --> E["DbgHelp64"]
  C --> F["Save Loading"]
  C --> G["Runtime Pointers"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Link DbgHelp directly
  • ➕ Lets platform headers validate API signatures at compile time
  • ➕ Removes manual export-name tables and function-pointer slot walking
  • ➖ Introduces a stronger runtime dependency on a particular DbgHelp version
  • ➖ Changes established loading and fallback behavior
  • ➖ Creates greater risk to retail-compatible 32-bit behavior
2. Unify duplicated StackDump ports
  • ➕ Eliminates parallel Generals and Zero Hour implementations
  • ➕ Reduces future architecture-porting and defect-fix duplication
  • ➖ Requires broader source-layout or target-wiring changes
  • ➖ Increases review scope and risk during compatibility-sensitive foundation work

Recommendation: Keep the PR's guarded, incremental approach because it preserves legacy build behavior while exposing the minimum x64 foundation. Direct DbgHelp linking would alter runtime compatibility, while consolidating duplicated game code is better handled separately after x64 behavior is validated.

Files changed (38) +1296 / -116

Enhancement (19) +1047 / -89
BaseTypeCore.hDefine project pointer-sized integer aliases +8/-0

Define project pointer-sized integer aliases

• Adds signed and unsigned pointer-width integer types for runtime values that must round-trip through pointers without affecting serialized formats.

Core/Libraries/Include/Lib/BaseTypeCore.h

arch_context.hAbstract x86 and x64 Windows context registers +85/-0

Abstract x86 and x64 Windows context registers

• Introduces architecture-neutral register accessors, display widths, and StackWalk machine constants for shared crash handlers.

Core/Libraries/Include/Lib/arch_context.h

AudibleSound.hMake Miles handles pointer-sized +4/-1

Make Miles handles pointer-sized

• Changes the Miles handle representation from unsigned long to uintptr_t so underlying pointers survive round trips on x64.

Core/Libraries/Source/WWVegas/WWAudio/AudibleSound.h

SoundSceneObj.hWiden audio event callback parameters +8/-3

Widen audio event callback parameters

• Changes event payload parameters to uintptr_t because logical-heard events transport object pointers through these integer slots.

Core/Libraries/Source/WWVegas/WWAudio/SoundSceneObj.h

DbgHelpLoader.cppLoad and wrap native DbgHelp64 exports +59/-0

Load and wrap native DbgHelp64 exports

• Resolves address-oriented DbgHelp functions through their ...64 exports on x64. Wrapper implementations use matching 64-bit addresses, return values, and displacement parameters.

Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp

DbgHelpLoader.hDeclare architecture-correct DbgHelp signatures +92/-0

Declare architecture-correct DbgHelp signatures

• Adds x64 variants of DbgHelp wrapper declarations and function-pointer typedefs, including the eight-byte symbol displacement output.

Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h

Except.cppPort the WWLib exception handler to x64 +292/-27

Port the WWLib exception handler to x64

• Adopts pointer-width addresses, architecture-neutral CONTEXT access, native DbgHelp64 exports, 64-bit stack walking, and x64 FPU/register reporting. RtlCaptureContext replaces unsupported x64 inline assembly while preserving existing x86 paths.

Core/Libraries/Source/WWVegas/WWLib/Except.cpp

Except.hWiden exception stack-walk address output +7/-1

Widen exception stack-walk address output

• Changes the internal Stack_Walk address array from unsigned long to uintptr_t so return addresses remain intact on x64.

Core/Libraries/Source/WWVegas/WWLib/Except.h

registry.hMake registry key storage pointer-sized +4/-1

Make registry key storage pointer-sized

• Changes RegistryClass's internal key field from int to uintptr_t for Win64 HKEY compatibility.

Core/Libraries/Source/WWVegas/WWLib/registry.h

debug_debug.cppPreserve full pointer values in debug infrastructure +53/-9

Preserve full pointer values in debug infrastructure

• Widens frame hash keys and caller addresses, adds x64 return-address capture, and emits full-width pointer and memory-dump addresses.

Core/Libraries/Source/debug/debug_debug.cpp

debug_debug.hWiden debug frame-address keys +11/-5

Widen debug frame-address keys

• Changes stack-frame state, hash entries, and lookup APIs to uintptr_t so debug identities are not truncated on x64.

Core/Libraries/Source/debug/debug_debug.h

debug_except.cppPort debug exception reporting to x64 contexts +55/-13

Port debug exception reporting to x64 contexts

• Uses shared context accessors for exception locations and register output, handles the x64 FPU layout, and corrects the dialog callback return type.

Core/Libraries/Source/debug/debug_except.cpp

debug_stack.cppPort debug stack walking and symbol lookup to x64 +146/-21

Port debug stack walking and symbol lookup to x64

• Loads native DbgHelp64 exports, preserves full-width stack addresses, and uses architecture-correct context capture and machine types. It also protects the class method from the platform StackWalk macro alias.

Core/Libraries/Source/debug/debug_stack.cpp

debug_stack.hWiden stack signatures and symbol addresses +12/-4

Widen stack signatures and symbol addresses

• Changes captured signature addresses and symbol lookup parameters to uintptr_t for lossless x64 stack traces.

Core/Libraries/Source/debug/debug_stack.h

debug_stack.inlDefine architecture-correct DbgHelp function types +45/-0

Define architecture-correct DbgHelp function types

• Adds x64 signatures for function-table, module-base, symbol, and source-line lookup functions while retaining the original VC6-compatible branch.

Core/Libraries/Source/debug/debug_stack.inl

StackDump.hWiden Generals context stack-dump parameters +8/-0

Widen Generals context stack-dump parameters

• Declares an x64-only pointer-width StackDumpFromContext signature while retaining the original 32-bit signature and mangling.

Generals/Code/GameEngine/Include/Common/StackDump.h

StackDump.cppPort the Generals stack dumper to x64 +75/-2

Port the Generals stack dumper to x64

• Uses pointer-width registers, RtlCaptureContext, shared context accessors, and DbgHelp64-compatible symbol displacement handling. Register and instruction-address output now preserves all 64 bits.

Generals/Code/GameEngine/Source/Common/System/StackDump.cpp

StackDump.hWiden Zero Hour context stack-dump parameters +8/-0

Widen Zero Hour context stack-dump parameters

• Declares an x64-only pointer-width StackDumpFromContext signature while retaining the original 32-bit signature and mangling.

GeneralsMD/Code/GameEngine/Include/Common/StackDump.h

StackDump.cppPort the Zero Hour stack dumper to x64 +75/-2

Port the Zero Hour stack dumper to x64

• Uses pointer-width registers, RtlCaptureContext, shared context accessors, and DbgHelp64-compatible symbol displacement handling. Register and instruction-address output now preserves all 64 bits.

GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp

Bug fix (13) +124 / -19
huffencode.cppPreserve pointer width in Huffman buffer arithmetic +3/-2

Preserve pointer width in Huffman buffer arithmetic

• Replaces long-based pointer subtraction with intptr_t arithmetic so buffer offsets are calculated safely on x64.

Core/Libraries/Source/Compression/EAC/huffencode.cpp

dx8webbrowser.cppMake the browser HWND narrowing explicit +14/-1

Make the browser HWND narrowing explicit

• Routes the HWND through uintptr_t before narrowing to the fixed 32-bit COM long parameter. The unavoidable x64 handle truncation is documented for follow-up.

Core/Libraries/Source/WWVegas/WW3D2/dx8webbrowser.cpp

rendobj.cppRead render-object tokens at their serialized width +10/-1

Read render-object tokens at their serialized width

• Reads legacy render-object identity tokens as four-byte values before reconstructing opaque remapping keys, avoiding an eight-byte x64 read.

Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp

surfaceclass.cppWiden locked-surface pointer arithmetic +10/-2

Widen locked-surface pointer arithmetic

• Uses uintptr_t when calculating addresses inside D3D locked surfaces, preventing x64 pointer truncation.

Core/Libraries/Source/WWVegas/WW3D2/surfaceclass.cpp

AudibleSound.cppRead audible-sound tokens at four-byte width +11/-2

Read audible-sound tokens at four-byte width

• Loads the legacy serialized sound identity token into uint32 before converting it into an opaque pointer-remapping key.

Core/Libraries/Source/WWVegas/WWAudio/AudibleSound.cpp

SoundScene.cppPreserve callback object pointers on x64 +1/-1

Preserve callback object pointers on x64

• Passes listener and sound-object callback parameters through uintptr_t instead of truncating them to uint32.

Core/Libraries/Source/WWVegas/WWAudio/SoundScene.cpp

registry.cppStore registry handles without truncation +2/-2

Store registry handles without truncation

• Validates HKEY against the pointer-sized storage field and casts successful handles through uintptr_t.

Core/Libraries/Source/WWVegas/WWLib/registry.cpp

persistfactory.hSeparate save-token width from pointer width +26/-3

Separate save-token width from pointer width

• Loads object identity tokens at the retail format's fixed four-byte width rather than sizeof(T*). Save-side x64 truncation remains explicit and documented as a format-level follow-up.

Core/Libraries/Source/WWVegas/WWSaveLoad/persistfactory.h

profile_funclevel.hMake profiler pointer narrowing explicit +15/-1

Make profiler pointer narrowing explicit

• Casts thread identity pointers through uintptr_t before retaining the existing 32-bit profile filename identifier. The potential x64 identity collision is documented for later format handling.

Core/Libraries/Source/profile/profile_funclevel.h

assetmgr.cppUse pointer subtraction for asset name lengths +6/-1

Use pointer subtraction for asset name lengths

• Computes the mesh-name prefix length through defined pointer subtraction instead of truncating both pointers to int.

Generals/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp

dazzle.cppRead Generals dazzle tokens at four-byte width +10/-1

Read Generals dazzle tokens at four-byte width

• Loads legacy dazzle object identity tokens as uint32 before reconstructing their opaque pointer-remapping keys.

Generals/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp

assetmgr.cppUse pointer subtraction for Zero Hour asset names +6/-1

Use pointer subtraction for Zero Hour asset names

• Computes the mesh-name prefix length through defined pointer subtraction instead of truncating both pointers to int.

GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp

dazzle.cppRead Zero Hour dazzle tokens at four-byte width +10/-1

Read Zero Hour dazzle tokens at four-byte width

• Loads legacy dazzle object identity tokens as uint32 before reconstructing their opaque pointer-remapping keys.

GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp

Other (6) +125 / -8
CMakeLists.txtEnable Windows dependency stubs for all pointer widths +6/-2

Enable Windows dependency stubs for all pointer widths

• Removes the top-level 32-bit restriction from Miles, Bink, and DX8 setup. Architecture-specific DX8 linking is delegated to its own CMake module.

CMakeLists.txt

CMakePresets.jsonAdd the MinGW-w64 x86-64 release preset +11/-0

Add the MinGW-w64 x86-64 release preset

• Defines an experimental 64-bit MinGW configure preset using the new cross-compilation toolchain and compile-command export.

CMakePresets.json

CMakeLists.txtRegister the architecture context header +1/-0

Register the architecture context header

• Adds the shared Windows CONTEXT abstraction header to the Core include target.

Core/CMakeLists.txt

dx8.cmakeExpose DX8 headers without x64 libraries +62/-1

Expose DX8 headers without x64 libraries

• Replaces upstream add_subdirectory behavior with a locally controlled interface target. It links available DX8 libraries only for 32-bit targets while providing headers and definitions to x64 compilation.

cmake/dx8.cmake

mingw.cmakeAllow experimental MinGW x64 configuration +13/-5

Allow experimental MinGW x64 configuration

• Recognizes MinGW-w64 x86-64 instead of failing configuration and gates unavailable D3D8 and D3DX8 libraries to 32-bit builds.

cmake/mingw.cmake

mingw-w64-x86_64.cmakeAdd the x86-64 MinGW cross toolchain +32/-0

Add the x86-64 MinGW cross toolchain

• Defines the x86-64 MinGW compilers, target root lookup behavior, pointer size, and unsupported-tool exclusions.

cmake/toolchains/mingw-w64-x86_64.cmake

@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown

Greptile Summary

The PR establishes the MinGW-w64 x86-64 build foundation while preserving the legacy 32-bit build and save-file formats.

  • Adds the x86-64 CMake preset and toolchain configuration.
  • Introduces pointer-sized integer and architecture-specific context abstractions.
  • Ports crash reporting and DbgHelp stack walking to the Windows x64 ABI.
  • Updates affected WW3D, audio, registry, profiling, and save/load code for wider pointers and registers.

Confidence Score: 4/5

The PR is not yet safe to merge because x64 save/load can still restore a sound attachment to the wrong render object when two object addresses share the same low 32 bits.

The revised reader and writer now agree on a four-byte token, but both render-object registration and sound attachment persistence still discard the upper pointer bits, and the remapper cannot distinguish duplicate tokens.

Files Needing Attention: Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp; Core/Libraries/Source/WWVegas/WWAudio/SoundSceneObj.cpp; Core/Libraries/Source/WWVegas/WWSaveLoad/pointerremap.cpp

Important Files Changed

Filename Overview
cmake/toolchains/mingw-w64-x86_64.cmake Defines the experimental MinGW-w64 x86-64 cross-compilation toolchain.
Core/Libraries/Include/Lib/arch_context.h Maps Windows context registers and stack-walk machine types across x86 and x64.
Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp Makes render-object token reads and writes consistently four bytes, while the previously reported x64 token-collision defect remains outstanding.
Core/Libraries/Source/WWVegas/WWAudio/SoundSceneObj.cpp Adapts persisted sound-object pointer fields to the legacy four-byte token representation on x64.
Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp Widens dynamically loaded DbgHelp function signatures for the x64 ABI.
Core/Libraries/Source/debug/debug_stack.cpp Ports stack capture and walking to architecture-neutral context access and 64-bit DbgHelp entry points.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    Preset[x86-64 CMake preset] --> Toolchain[MinGW-w64 x86-64 toolchain]
    Toolchain --> Core[Shared Core libraries]
    Core --> Context[Architecture-specific CONTEXT access]
    Context --> Crash[Crash and stack-walk handlers]
    Core --> Wide[Pointer-width adaptations]
    Wide --> Render[WW3D render objects]
    Wide --> Audio[WWAudio object links]
    Render --> SaveLoad[Legacy 32-bit identity tokens]
    Audio --> SaveLoad
Loading

Reviews (6): Last reviewed commit: "revert(x64): return the identity-width w..." | Re-trigger Greptile

while (cload.Open_Micro_Chunk()) {
switch(cload.Cur_Micro_Chunk_ID()) {
READ_MICRO_CHUNK(cload,RENDOBJFACTORY_VARIABLE_OBJPOINTER,old_obj);
case (RENDOBJFACTORY_VARIABLE_OBJPOINTER): cload.Read(&old_obj_token,sizeof(old_obj_token)); break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Pointer identities use mismatched widths

When an x64 save contains a sound attached to a render object above the low 4 GiB, this reader registers only the low 32 bits of the object's identity while SoundSceneObjClass restores and remaps the attached pointer at full width, causing the remap to clear the attachment in release builds or assert in debug builds. The corresponding identity writers also still pass pointer variables to WRITE_MICRO_CHUNK, so the writer and reader need a consistent token representation.

Knowledge Base Used: WWVegas services

Prompt To Fix With AI
This is a comment left during a code review.
Path: Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp
Line: 1237

Comment:
**Pointer identities use mismatched widths**

When an x64 save contains a sound attached to a render object above the low 4 GiB, this reader registers only the low 32 bits of the object's identity while `SoundSceneObjClass` restores and remaps the attached pointer at full width, causing the remap to clear the attachment in release builds or assert in debug builds. The corresponding identity writers also still pass pointer variables to `WRITE_MICRO_CHUNK`, so the writer and reader need a consistent token representation.

**Knowledge Base Used:** [WWVegas services](https://app.greptile.com/thesuperhackers/-/custom-context/knowledge-base/thesuperhackers/generalsgamecode/-/docs/wwvegas-services.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed. SoundSceneObjClass is the only consumer of the pointer remap, and it was indeed the mismatched half: it persisted m_AttachedObject and m_UserObj at native width while the factories registered a 4-byte identity. Both members now round-trip through the same token the factories use, guarded so the 32-bit arm is unchanged, so the remap can match again.

The writers were fixed in the same pass — they no longer hand a raw pointer to WRITE_MICRO_CHUNK.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 2, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (2) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Zero Hour cannot link x64 📎 Requirement gap ≡ Correctness
Description
The x64 configuration intentionally provides DX8 headers without the required link libraries, so
neither the Zero Hour nor Generals game target can build successfully. This directly violates the
required x64 build outcomes for both targets.
Code

cmake/dx8.cmake[R22-23]

+# only; it cannot link, which is expected — a playable x64 build is out of
+# scope, see issue #473. This is a precondition for W3D to compile on x64, not
Evidence
PR Compliance IDs 1 and 3 require the Zero Hour and Generals game targets, respectively, to build
successfully for x64. The changed DX8 configuration explicitly states that the x64 target cannot
link, while the Zero Hour z_wwcommon and Generals g_wwcommon interfaces both link against
d3d8lib.

Upgrade Zero Hour to x64
cmake/dx8.cmake[18-31]
GeneralsMD/Code/Libraries/Source/WWVegas/CMakeLists.txt[4-8]
Generals/Code/Libraries/Source/WWVegas/CMakeLists.txt[4-8]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Zero Hour and Generals x64 targets cannot link because `d3d8lib` omits the required graphics libraries on 64-bit builds.
## Issue Context
Zero Hour's `z_wwcommon` and Generals' `g_wwcommon` interfaces depend on `d3d8lib`, while the x64 branch of that interface is explicitly headers-only. Both game targets are required to build successfully for x64.
## Fix Focus Areas
- cmake/dx8.cmake[18-31]
- GeneralsMD/Code/Libraries/Source/WWVegas/CMakeLists.txt[4-8]
- Generals/Code/Libraries/Source/WWVegas/CMakeLists.txt[4-8]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Zero Hour tools disabled 📎 Requirement gap ≡ Correctness
Description
The new x64 toolchain forcibly disables all targets controlled by both RTS_BUILD_ZEROHOUR_TOOLS
and RTS_BUILD_GENERALS_TOOLS. Consequently, the required Zero Hour and Generals tool targets
cannot build under the provided x64 preset.
Code

cmake/toolchains/mingw-w64-x86_64.cmake[32]

+set(RTS_BUILD_ZEROHOUR_TOOLS OFF CACHE BOOL "Disable MFC-dependent Zero Hour tools for MinGW" FORCE)
Evidence
PR Compliance IDs 2 and 4 require the Zero Hour and Generals tool targets, respectively, to build
for x64, but the new toolchain forces both controlling options off. The corresponding tools CMake
files confirm that these options gate GUIEdit, ImagePacker, MapCacheBuilder, W3DView, and
WorldBuilder for both games, as well as wdump for Zero Hour, so disabling the options excludes the
required binaries from the x64 build.

Upgrade Zero Hour Tools to x64
cmake/toolchains/mingw-w64-x86_64.cmake[29-32]
GeneralsMD/Code/Tools/CMakeLists.txt[3-10]
Generals/Code/Tools/CMakeLists.txt[3-9]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The x64 toolchain forcibly disables the Zero Hour and Generals tool targets required by the compliance checklist.
## Issue Context
`GeneralsMD/Code/Tools/CMakeLists.txt` uses `RTS_BUILD_ZEROHOUR_TOOLS` to include GUIEdit, ImagePacker, MapCacheBuilder, W3DView, wdump, and WorldBuilder, while `Generals/Code/Tools/CMakeLists.txt` uses `RTS_BUILD_GENERALS_TOOLS` to include GUIEdit, ImagePacker, MapCacheBuilder, W3DView, and WorldBuilder. Forcing both options off excludes these required tool binaries from the x64 build.
## Fix Focus Areas
- cmake/toolchains/mingw-w64-x86_64.cmake[29-32]
- GeneralsMD/Code/Tools/CMakeLists.txt[3-10]
- Generals/Code/Tools/CMakeLists.txt[3-9]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Captured contexts are discarded ✓ Resolved 🐞 Bug ≡ Correctness
Description
The x64 paths capture or receive a full CONTEXT, but every stack-walk implementation passes
nullptr as StackWalk64's ContextRecord, so the unwinder cannot update the register state
needed to traverse AMD64 frames. This affects DebugStackwalk, Except, and both game StackDump
implementations, causing failed or incomplete x64 crash traces.
Code

Core/Libraries/Source/debug/debug_stack.cpp[R520-521]

+		     gDbg._StackWalk(CTX_STACKWALK_MACHINE,GetCurrentProcess(),GetCurrentThread(),
                   &stackFrame,nullptr,nullptr,gDbg._SymFunctionTableAccess,gDbg._SymGetModuleBase,nullptr))
Evidence
DebugStackwalk::StackWalk captures all registers into capture_ctx at lines 506-510 but passes
null at lines 520-521. The same pattern appears in Except.cpp, where lines 1512-1516 capture a
context before line 1541 passes null, and in both game implementations, where lines 110-114 capture
a context but calls at lines 240-264 and 439-461 pass null instead of gsContext.

Core/Libraries/Source/debug/debug_stack.cpp[449-527]
Core/Libraries/Source/WWVegas/WWLib/Except.cpp[1512-1541]
Generals/Code/GameEngine/Source/Common/System/StackDump.cpp[210-264]
Generals/Code/GameEngine/Source/Common/System/StackDump.cpp[370-461]
GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp[210-264]
GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp[370-461]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The x64 stack walkers capture or receive a `CONTEXT` but discard it by passing `nullptr` to `StackWalk64`. Preserve a mutable context copy for the complete walk and pass its address on every iteration.
## Issue Context
`StackWalk64` updates the supplied context while unwinding. For exception walks, copy the provided exception context before mutation; for current-thread walks, retain the context produced by `RtlCaptureContext`. The Generals helpers currently reduce the context to three scalar registers, so their internal x64 path must retain or accept the complete context.
## Fix Focus Areas
- Core/Libraries/Source/debug/debug_stack.cpp[449-527]
- Core/Libraries/Source/WWVegas/WWLib/Except.cpp[1480-1553]
- Generals/Code/GameEngine/Source/Common/System/StackDump.cpp[198-270]
- Generals/Code/GameEngine/Source/Common/System/StackDump.cpp[359-468]
- GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp[198-270]
- GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp[359-468]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View action required (1)
4. X64 walks claim I386 ✓ Resolved 🐞 Bug ≡ Correctness
Description
Both game StackDump ports feed x64 register values into DbgHelpLoader::stackWalk while still
passing IMAGE_FILE_MACHINE_I386, even though the loader now resolves StackWalk64. The
architecture mismatch prevents DbgHelp from applying AMD64 unwind rules, so these x64 stack dumps
can fail or return invalid frames.
Code

Generals/Code/GameEngine/Source/Common/System/StackDump.cpp[R108-114]

+#elif defined(_WIN64) || defined(__x86_64__)
+	// TheSuperHackers @fix MeneerHaas 02/09/2026 RtlCaptureContext seeds the stack walk on x64 (no __asm there), as in debug_stack.cpp.
+	CONTEXT capture_ctx;
+	RtlCaptureContext(&capture_ctx);
+	myeip = (uintptr_t)CTX_PC(capture_ctx);
+	myesp = (uintptr_t)CTX_STACK(capture_ctx);
+	myebp = (uintptr_t)CTX_FRAME(capture_ctx);
Evidence
The PR adds AMD64 register capture at lines 108-114, while the resulting values are passed to calls
hard-coded as IMAGE_FILE_MACHINE_I386 at lines 240 and 256; FillStackAddresses repeats this at
lines 439 and 453. Meanwhile, DbgHelpLoader resolves StackWalk64 on x64, and the shared
architecture header already provides the correct IMAGE_FILE_MACHINE_AMD64 selection.

Generals/Code/GameEngine/Source/Common/System/StackDump.cpp[108-114]
Generals/Code/GameEngine/Source/Common/System/StackDump.cpp[236-264]
Generals/Code/GameEngine/Source/Common/System/StackDump.cpp[436-461]
GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp[108-114]
GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp[236-264]
GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp[436-461]
Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp[132-138]
Core/Libraries/Include/Lib/arch_context.h[50-83]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The newly added x64 capture path is passed to stack-walk calls that remain hard-coded to `IMAGE_FILE_MACHINE_I386`. Use the architecture-specific machine constant for every walk in both duplicated implementations.
## Issue Context
`arch_context.h` already defines `CTX_STACKWALK_MACHINE` as `IMAGE_FILE_MACHINE_AMD64` on x64 and `IMAGE_FILE_MACHINE_I386` on x86. The Core stack walkers use this macro, so the game StackDump implementations should use the same mapping without changing 32-bit behavior.
## Fix Focus Areas
- Generals/Code/GameEngine/Source/Common/System/StackDump.cpp[236-264]
- Generals/Code/GameEngine/Source/Common/System/StackDump.cpp[436-461]
- GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp[236-264]
- GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp[436-461]
- Core/Libraries/Include/Lib/arch_context.h[50-83]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread cmake/dx8.cmake
Comment thread cmake/toolchains/mingw-w64-x86_64.cmake
Comment thread Core/Libraries/Source/debug/debug_stack.cpp Outdated
Comment thread Generals/Code/GameEngine/Source/Common/System/StackDump.cpp
Review follow-up (#3248): the three micro-chunk identity writers
(rendobj, dazzle, AudibleSound) still passed a raw pointer to
WRITE_MICRO_CHUNK, which writes 8 bytes on x64 while every loader reads
the legacy 4-byte token. The write path has no callers in this repository,
but writer and reader now agree on the on-disk width. Also corrects
arch_context.h's copyright attribution to TheSuperHackers per repository
convention for new files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp
MeneerHaas and others added 3 commits September 2, 2026 21:06
Review follow-up (#3248): on AMD64 StackWalk64 requires a ContextRecord
and updates it while unwinding, but all four walkers passed nullptr, and
both game StackDump ports still asked for IMAGE_FILE_MACHINE_I386 while
the loader resolves StackWalk64. Each walker now seeds a mutable local
context from the frame it starts at (never the caller's, which
StackWalk64 would mutate) and uses CTX_STACKWALK_MACHINE, as the Core
walkers already did. The 32-bit arms preprocess to exactly what they
were: RTS_STACKWALK_CONTEXT expands to nullptr and CTX_STACKWALK_MACHINE
to IMAGE_FILE_MACHINE_I386.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The x64 design notes are not part of this PR, so comments pointing at
docs/x64/ would dangle upstream. The load-bearing facts they cited are
now stated inline instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review follow-up (#3248): SoundSceneObjClass is the only consumer of the
pointer remap, and it persisted m_AttachedObject and m_UserObj at native
width. On x64 that both fails to read the 4 bytes legacy files hold and
asks the remap for a full-width value the render-object factory never
registers. Both members now round-trip through the same 4-byte identity
token the factories use, so the remap can match again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp
MeneerHaas and others added 4 commits September 2, 2026 21:19
Review follow-up (#3248): truncating x64 addresses into a 4-byte token
made two objects whose low 32 bits match collide on one identity. Add
READ_MICRO_CHUNK_POINTER_TOKEN, which reads Cur_Micro_Chunk_Length()
bytes instead of sizeof(var), and use it for every persisted identity
(rendobj, dazzle, AudibleSound) and for the one remap consumer
(SoundSceneObj). Writers go back to native width. Files written by
32-bit builds keep loading, an x64-written file would round-trip its
full address, and nothing is truncated, so no two objects can share a
token.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The length-aware read buys nothing on 32-bit, where the chunk and the
pointer are both 4 bytes, so per this branch's rule it must not move VC6
codegen. Every 32-bit arm is now textually what the recorded baseline was
built from, and READ_MICRO_CHUNK_POINTER_TOKEN is defined only on x64.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dropping this include as 'now unneeded' was the one 32-bit-visible change
left in the identity-width work, and it moved VC6 codegen for no 32-bit
benefit: the recorded baseline reproduces with it and not without it.
The include is harmless -- it only declares pointer-sized integer typedefs
-- so it stays.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The width-aware read via READ_MICRO_CHUNK_POINTER_TOKEN moved VC6 codegen
in the two game executables and guarding it did not fully restore the
baseline, so it is withdrawn. What remains is the state that clean builds
verified against the recorded baseline: fixed 4-byte identity tokens,
guarded so 32-bit is untouched, with writer and reader agreeing on the
width.

The reviewer's collision concern is answered in the pull request rather
than in code: nothing in this repository calls the chunk write path, so
no file with x64-width tokens can exist, and every file that does exist
was written by a 32-bit build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@MeneerHaas

Copy link
Copy Markdown
Author

Review round addressed

Four review findings were real and are fixed; two are intentional and answered inline.

Fixed

  • StackWalk64 got no ContextRecord. Required on AMD64, and it is updated during the walk, so all four walkers (DebugStackwalk, Except, both StackDump ports) now seed a mutable local context from the frame they start at — never the caller's, which the API would mutate.
  • x64 walks asked for IMAGE_FILE_MACHINE_I386. Both game StackDump ports now use CTX_STACKWALK_MACHINE, as the Core walkers already did.
  • Persisted identity widths disagreed. SoundSceneObjClass persisted its remapped pointers at native width while the factories registered a 4-byte token; the writers also still passed raw pointers to WRITE_MICRO_CHUNK. Both sides now use the same token, guarded so 32-bit is untouched.
  • arch_context.h attribution. Corrected to TheSuperHackers per the convention for new files.

Every fix is guarded so the 32-bit arm is textually what the recorded baseline was built from.

Answered, not changed: the x64 DX8 headers-only configuration and the disabled MFC tools are deliberate and documented in the code — see the inline replies.

Verification. Clean VC6 builds of both games at b719eeeb7: 0 errors, and all 13 retail artifacts reproduce their recorded .text digests — both game executables byte-identical, tools included. MinGW i686 control build unchanged.

One note in the interest of being straight about it: I also tried reading each persisted identity at the width the micro chunk actually holds, which removes truncation altogether and would have closed the token-collision comment for good. Clean builds showed it moving .text in both game executables, and guarding it to x64 did not fully restore the baseline, so I withdrew it rather than ship codegen movement with no 32-bit benefit. Reasoning is in the reply on that thread, and I am happy to bring it back as a follow-up if you would prefer that trade.

🤖 Generated with Claude Code

@Mauller

Mauller commented Sep 3, 2026

Copy link
Copy Markdown

This is doing too much within one pull request.

Does the author understand what has or is being done? since the changes are also filled with AI slop comments.

@xezon

xezon commented Sep 3, 2026

Copy link
Copy Markdown

I recommend to split into smaller pulls.

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.

3 participants