Wanted to share some progress and, more usefully, document the specific traps, since anyone porting this engine to x64 is going to hit the exact same ones.
Short version: I have a native x64 build of the client (Game64.exe) that's playable end to end against the unmodified 32-bit servers. Login, character select, world load, rendering, combat all work. No server changes required.
Why it can talk to 32-bit servers unchanged: Windows x64 is LLP64, so int and long stay 32-bit and only pointers grow to 8 bytes. The packets serialize field by field and carry no raw pointers, so the wire format is identical between the 32-bit and 64-bit builds. That's the key realization that lets you do an x64 client without touching the server at all.
Approach: every x64 change is wrapped in #ifdef _WIN64, the 32-bit build stays byte-identical, and the x64 output is isolated to its own folder. I deliberately did not refactor anything while porting. The goal was "get to playable x64 with zero regression to the shipping client," and keeping the two builds surgically separated is what made that achievable.
The mechanical stuff (expected): inline x86 asm rewritten as portable C (MSVC x64 forbids __asm), ODBC indicator vars to SQLLEN/SQLULEN, SetWindowLongPtr/INT_PTR for the window and dialog procs, and sourcing x64 builds of every third-party dep (DX9 libs, LuaJIT, SDL2 in place of SDL 1.2.7, discord-rpc, CryptoPP with asm disabled).
The part actually worth documenting, because LLP64 means only pointer-bearing serialized structs break:
1. lwTexInfo::data is a void*. It's a 4-byte slot in the on-disk model file but 8 bytes in the x64 struct, so a raw fread of the record over-reads and desyncs all the material/texture data, then crashes during resource load. It is the only directly-fread pointer-bearing struct in the whole model/mesh/material/bone/anim path, so the fix is narrow: read each record through a 32-bit on-disk mirror struct and leave the rest untouched.
2. Table .bin: CRawDataInfo:

Data is also a void*, which breaks the encrypted record stride on x64. I read these through a mirror with the on-disk stride derived from the record count rather than sizeof, because tail-padding on x64 makes a sizeof-based guess wrong for any table whose derived fields aren't an 8-byte multiple (that one cost me a crash before I figured it out).
3. Biggest landmine, flagging this one loudly: if your x64 build ever calls the "make bin" path, it rewrites the shared .bin files (StringSet.bin in my case) in 64-bit record layout. Your 32-bit client then reads that as garbage and crashes on load. If you run both builds against shared data, guard every bin-write path with #ifndef _WIN64 or you'll corrupt your working 32-bit client from the x64 one and spend a while wondering why.
Still open: the bad-model handler is currently a try/catch net rather than a real fix, deeper in-game testing, and then the server x64 phase, which will hit the same pointer-in-serialized-struct issues for the server-side data structs.
None of this is a replacement for the proper modernization work happening on the engine, it's the conservative "keep 32-bit intact, get x64 running alongside it" route. Happy to compare notes with anyone going down the same path, the serialization gotchas above are engine-wide and not specific to my fork.