diff --git a/CMakeLists.txt b/CMakeLists.txt
index 39062e3fbe6..4f4a5a8350d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -48,8 +48,12 @@ if(MINGW)
include(cmake/widl.cmake)
endif()
-# Find/Add build dependencies and stubs shared by all projects
-if((WIN32 OR "${CMAKE_SYSTEM}" MATCHES "Windows") AND ${CMAKE_SIZEOF_VOID_P} EQUAL 4)
+# Find/Add build dependencies and stubs shared by all projects.
+# Miles and Bink are source-only stubs with no architecture dependency, so they
+# build for any pointer size. DX8 is gated separately inside dx8.cmake: its
+# headers are architecture-independent but MinGW-w64 x86_64 ships no libd3d8.a
+# and no libd3dx8d.a, so the link libraries are 32-bit only.
+if(WIN32 OR "${CMAKE_SYSTEM}" MATCHES "Windows")
include(cmake/miles.cmake)
include(cmake/bink.cmake)
include(cmake/dx8.cmake)
diff --git a/CMakePresets.json b/CMakePresets.json
index 4274c6357d2..9dec124f059 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -191,6 +191,17 @@
"cacheVariables": {
"RTS_BUILD_OPTION_PROFILE": "ON"
}
+ },
+ {
+ "name": "mingw-w64-x86_64",
+ "displayName": "MinGW-w64 64-bit (x86_64) Release",
+ "generator": "Unix Makefiles",
+ "binaryDir": "${sourceDir}/build/${presetName}",
+ "toolchainFile": "${sourceDir}/cmake/toolchains/mingw-w64-x86_64.cmake",
+ "cacheVariables": {
+ "CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
+ "CMAKE_BUILD_TYPE": "Release"
+ }
}
],
"buildPresets": [
diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt
index 110ff1152d0..85bcd82d3b3 100644
--- a/Core/CMakeLists.txt
+++ b/Core/CMakeLists.txt
@@ -12,6 +12,7 @@ target_include_directories(corei_libraries_source_wwvegas INTERFACE "Libraries/S
target_include_directories(corei_main INTERFACE "Main")
target_sources(corei_libraries_include PRIVATE
+ Libraries/Include/Lib/arch_context.h
Libraries/Include/Lib/BaseType.h
Libraries/Include/Lib/BaseTypeCore.h
Libraries/Include/Lib/trig.h
diff --git a/Core/Libraries/Include/Lib/BaseTypeCore.h b/Core/Libraries/Include/Lib/BaseTypeCore.h
index ab702efd496..ceafaca005a 100644
--- a/Core/Libraries/Include/Lib/BaseTypeCore.h
+++ b/Core/Libraries/Include/Lib/BaseTypeCore.h
@@ -123,3 +123,11 @@ typedef bool Bool; //
// note, the types below should use "long long", but MSVC doesn't support it yet
typedef int64_t Int64; // 8 bytes
typedef uint64_t UnsignedInt64; // 8 bytes
+
+// Pointer-sized integers. Required for 64-bit targets: Windows is LLP64, so
+// `long` stays 32 bits on x86-64 and cannot hold a pointer. Use these for
+// values that must round-trip through a pointer — never for values that are
+// written to a file, sent over the network, or stored in a savegame, because
+// their width changes with the target.
+typedef uintptr_t UnsignedIntPtr; // 4 bytes on 32-bit, 8 on 64-bit
+typedef intptr_t IntPtr; // 4 bytes on 32-bit, 8 on 64-bit
diff --git a/Core/Libraries/Include/Lib/arch_context.h b/Core/Libraries/Include/Lib/arch_context.h
new file mode 100644
index 00000000000..99044dd7d90
--- /dev/null
+++ b/Core/Libraries/Include/Lib/arch_context.h
@@ -0,0 +1,85 @@
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2026 TheSuperHackers
+**
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation, either version 3 of the License, or
+** (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program. If not, see .
+*/
+
+// FILE: arch_context.h //////////////////////////////////////////////////////
+//
+// Maps the x86-32 Win32 CONTEXT register field names used throughout the
+// two crash handlers (Core/Libraries/Source/WWVegas/WWLib/Except.cpp and
+// Core/Libraries/Source/debug/debug_except.cpp) onto whichever field names
+// the target architecture's CONTEXT struct actually has, plus the machine
+// constant the stack-walk call sites need.
+//
+// On x86-32 the general-purpose registers are Eip/Esp/Ebp/Eax/Ebx/Ecx/Edx/
+// Esi/Edi. On x86-64 they are Rip/Rsp/Rbp/Rax/Rbx/Rcx/Rdx/Rsi/Rdi and are
+// twice as wide. CTX_PC/CTX_STACK/CTX_FRAME/CTX_AX/CTX_BX/CTX_CX/CTX_DX/
+// CTX_SI/CTX_DI hide that difference behind one name per register so call
+// sites don't need an #ifdef each. On x86-32 every one of these macros
+// expands to exactly the original field access (e.g. CTX_PC(ctx) is
+// ((ctx).Eip)), so the 32-bit build is unaffected.
+//
+// Deliberately NOT using 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 FPU/SSE state. Taking that suggestion would compile
+// cleanly and read the wrong bytes -- a crash dump that is silently
+// corrupt is worse than no crash dump. The FPU/SSE save area itself is a
+// structural difference (CONTEXT.FltSave, an XMM_SAVE_AREA32, vs the
+// 32-bit FLOATING_SAVE_AREA) rather than a field rename, so it is not
+// covered by macros here -- the two crash handlers guard that block with
+// their own #if per architecture, mirroring what the 32-bit block reports.
+
+#pragma once
+
+#include
+
+#if defined(_WIN64) || defined(__x86_64__)
+
+#define CTX_PC(ctx) ((ctx).Rip)
+#define CTX_STACK(ctx) ((ctx).Rsp)
+#define CTX_FRAME(ctx) ((ctx).Rbp)
+#define CTX_AX(ctx) ((ctx).Rax)
+#define CTX_BX(ctx) ((ctx).Rbx)
+#define CTX_CX(ctx) ((ctx).Rcx)
+#define CTX_DX(ctx) ((ctx).Rdx)
+#define CTX_SI(ctx) ((ctx).Rsi)
+#define CTX_DI(ctx) ((ctx).Rdi)
+
+// Hex-digit width of a full register dump column, for the stream-based
+// (Debug::Width()) register printers in debug_except.cpp -- 16 digits show
+// a full 64-bit register instead of just its low half.
+#define CTX_REG_WIDTH 16
+
+#define CTX_STACKWALK_MACHINE IMAGE_FILE_MACHINE_AMD64
+
+#else
+
+#define CTX_PC(ctx) ((ctx).Eip)
+#define CTX_STACK(ctx) ((ctx).Esp)
+#define CTX_FRAME(ctx) ((ctx).Ebp)
+#define CTX_AX(ctx) ((ctx).Eax)
+#define CTX_BX(ctx) ((ctx).Ebx)
+#define CTX_CX(ctx) ((ctx).Ecx)
+#define CTX_DX(ctx) ((ctx).Edx)
+#define CTX_SI(ctx) ((ctx).Esi)
+#define CTX_DI(ctx) ((ctx).Edi)
+
+#define CTX_REG_WIDTH 8
+
+#define CTX_STACKWALK_MACHINE IMAGE_FILE_MACHINE_I386
+
+#endif
diff --git a/Core/Libraries/Source/Compression/EAC/huffencode.cpp b/Core/Libraries/Source/Compression/EAC/huffencode.cpp
index 06f51b39831..c7d35fd661e 100644
--- a/Core/Libraries/Source/Compression/EAC/huffencode.cpp
+++ b/Core/Libraries/Source/Compression/EAC/huffencode.cpp
@@ -22,6 +22,7 @@
#define __HUFWRITE 1
#include
+#include
#include "codex.h"
#include "huffcodex.h"
@@ -1050,8 +1051,8 @@ static void HUFF_pack(struct HuffEncodeContext *EC,
if (!i3)
HUFF_writecode(EC,dest,i);
- if (((long) bptr1- (long) EC->buffer) >= (long)(EC->plen+curpc))
- curpc = (long) bptr1 - (long) EC->buffer - EC->plen;
+ if (((intptr_t) bptr1- (intptr_t) EC->buffer) >= (intptr_t)(EC->plen+curpc))
+ curpc = (intptr_t) bptr1 - (intptr_t) EC->buffer - EC->plen;
}
/* write EOF ([clue] 0gn [10]) */
diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8webbrowser.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8webbrowser.cpp
index 180e3aa03e0..96a65889f7c 100644
--- a/Core/Libraries/Source/WWVegas/WW3D2/dx8webbrowser.cpp
+++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8webbrowser.cpp
@@ -33,6 +33,9 @@
#include "dx8webbrowser.h"
#include "ww3d.h"
#include "dx8wrapper.h"
+// Only pulls the pointer-sized-int typedefs (uintptr_t); avoids dragging in
+// BaseTypeCore.h's warning-as-error pragmas into a file that never had them.
+#include
#if ENABLE_EMBEDDED_BROWSER
@@ -193,7 +196,17 @@ void DX8WebBrowser::CreateBrowser(const char* browsername, const char* url, int
if(pBrowser)
{
_bstr_t brsname(browsername);
- pBrowser->CreateBrowser(brsname, _bstr_t(url), reinterpret_cast(hWnd), x, y, w, h, options, gamedispatch);
+ // TODO(x64-hwnd-truncation): IFEBrowserEngine2::CreateBrowser's
+ // parentwindow parameter is `long` per BrowserEngine.idl -- a fixed
+ // COM/oleautomation ABI width we don't control (dual/oleautomation
+ // interfaces cannot carry 64-bit ints as `long`; that's a VT_I4).
+ // On x64, HWND is a full 8-byte handle and this truncates it. NOT
+ // FIXED: the top 32 bits of the window handle are silently dropped
+ // whenever this path runs on a 64-bit target. Widening the local
+ // would not help -- the COM call still narrows to `long` regardless
+ // -- so the truncation is made explicit and legal here instead of
+ // left as a compile error.
+ pBrowser->CreateBrowser(brsname, _bstr_t(url), (long)(uintptr_t)hWnd, x, y, w, h, options, gamedispatch);
pBrowser->SetUpdateRate(brsname, updateticks);
}
}
diff --git a/Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp b/Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp
index 4b04540bad0..f396f993054 100644
--- a/Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp
+++ b/Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp
@@ -1219,6 +1219,14 @@ PersistClass * RenderObjPersistFactoryClass::Load(ChunkLoadClass & cload) const
char name[64];
name[0] = '\0';
+ // Read exactly what Save wrote: a fixed-width 4-byte identity token, not
+ // sizeof(old_obj). On x86-64 sizeof(RenderObjClass*) is 8, so reading
+ // sizeof(old_obj) here (as READ_MICRO_CHUNK would) would ask for more
+ // bytes than the legacy 4-byte micro chunk holds; ChunkLoadClass::Read
+ // then refuses to read anything at all and old_obj stays null, silently
+ // poisoning SaveLoadSystemClass's pointer remap table. See persistfactory.h.
+ uint32 old_obj_token = 0;
+
while (cload.Open_Chunk()) {
switch (cload.Cur_Chunk_ID()) {
@@ -1226,7 +1234,7 @@ PersistClass * RenderObjPersistFactoryClass::Load(ChunkLoadClass & cload) const
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;
READ_MICRO_CHUNK(cload,RENDOBJFACTORY_VARIABLE_TRANSFORM,tm);
READ_MICRO_CHUNK_STRING(cload,RENDOBJFACTORY_VARIABLE_NAME,name,sizeof(name));
}
@@ -1269,6 +1277,7 @@ PersistClass * RenderObjPersistFactoryClass::Load(ChunkLoadClass & cload) const
new_obj->Set_Transform(tm);
}
+ old_obj = (RenderObjClass *)(uintptr_t)old_obj_token;
SaveLoadSystemClass::Register_Pointer(old_obj,new_obj);
return new_obj;
}
@@ -1280,7 +1289,13 @@ void RenderObjPersistFactoryClass::Save(ChunkSaveClass & csave,PersistClass * ob
const Matrix3D& tm = robj->Get_Transform();
csave.Begin_Chunk(RENDOBJFACTORY_CHUNKID_VARIABLES);
+#if defined(_WIN64) || defined(__x86_64__)
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 Write the 4-byte identity token the loader reads; see persistfactory.h.
+ uint32 robj_token = (uint32)(uintptr_t)robj;
+ WRITE_MICRO_CHUNK(csave,RENDOBJFACTORY_VARIABLE_OBJPOINTER,robj_token);
+#else
WRITE_MICRO_CHUNK(csave,RENDOBJFACTORY_VARIABLE_OBJPOINTER,robj);
+#endif
WRITE_MICRO_CHUNK_STRING(csave,RENDOBJFACTORY_VARIABLE_NAME,name);
WRITE_MICRO_CHUNK(csave,RENDOBJFACTORY_VARIABLE_TRANSFORM,tm);
csave.End_Chunk();
diff --git a/Core/Libraries/Source/WWVegas/WW3D2/surfaceclass.cpp b/Core/Libraries/Source/WWVegas/WW3D2/surfaceclass.cpp
index 2de64d69a39..74ade557e49 100644
--- a/Core/Libraries/Source/WWVegas/WW3D2/surfaceclass.cpp
+++ b/Core/Libraries/Source/WWVegas/WW3D2/surfaceclass.cpp
@@ -53,6 +53,9 @@
#include "WWMath/vector2i.h"
#include "colorspace.h"
#include "WWLib/bound.h"
+// Only pulls the pointer-sized-int typedefs (uintptr_t); avoids dragging in
+// BaseTypeCore.h's warning-as-error pragmas into a file that never had them.
+#include
#include
void Convert_Pixel(Vector3 &rgb, const SurfaceClass::SurfaceDescription &sd, const unsigned char * pixel)
@@ -575,7 +578,10 @@ void SurfaceClass::FindBB(Vector2i *min,Vector2i*max)
for (x = min->I; x < max->I; x++) {
// HY - this is not endian safe
- unsigned char *alpha=(unsigned char*) ((unsigned int)lock_rect.pBits+(y-min->J)*lock_rect.Pitch+(x-min->I)*size);
+ // pBits is a live D3D-locked surface pointer; the arithmetic below
+ // walks it row/column by row/column and stays runtime-only (never
+ // serialized), so widen the holder to pointer size on x64.
+ unsigned char *alpha=(unsigned char*) ((uintptr_t)lock_rect.pBits+(y-min->J)*lock_rect.Pitch+(x-min->I)*size);
unsigned char myalpha=alpha[size-1];
myalpha=(myalpha>>(8-alphabits)) & mask;
if (myalpha) {
@@ -649,7 +655,9 @@ bool SurfaceClass::Is_Transparent_Column(unsigned int column)
for (y = 0; y < (int) sd.Height; y++)
{
// HY - this is not endian safe
- unsigned char *alpha=(unsigned char*) ((unsigned int)lock_rect.pBits+y*lock_rect.Pitch);
+ // Same live D3D-locked surface pointer as above: runtime-only, widen
+ // the holder to pointer size on x64.
+ unsigned char *alpha=(unsigned char*) ((uintptr_t)lock_rect.pBits+y*lock_rect.Pitch);
unsigned char myalpha=alpha[size-1];
myalpha=(myalpha>>(8-alphabits)) & mask;
if (myalpha) {
diff --git a/Core/Libraries/Source/WWVegas/WWAudio/AudibleSound.cpp b/Core/Libraries/Source/WWVegas/WWAudio/AudibleSound.cpp
index 7e9bdc689d0..facabfb244e 100644
--- a/Core/Libraries/Source/WWVegas/WWAudio/AudibleSound.cpp
+++ b/Core/Libraries/Source/WWVegas/WWAudio/AudibleSound.cpp
@@ -1654,8 +1654,14 @@ AudibleSoundClass::Save (ChunkSaveClass &csave)
WRITE_MICRO_CHUNK_STRING (csave, VARID_FILENAME, m_Buffer->Get_Filename ());
}
+#if defined(_WIN64) || defined(__x86_64__)
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 Write the 4-byte identity token the loader reads; see persistfactory.h.
+ uint32 this_ptr_token = (uint32)(uintptr_t)this;
+ WRITE_MICRO_CHUNK (csave, VARID_THIS_PTR, this_ptr_token);
+#else
AudibleSoundClass *this_ptr = this;
WRITE_MICRO_CHUNK (csave, VARID_THIS_PTR, this_ptr);
+#endif
csave.End_Chunk ();
@@ -1712,8 +1718,17 @@ AudibleSoundClass::Load (ChunkLoadClass &cload)
case VARID_THIS_PTR:
{
- AudibleSoundClass *old_ptr = nullptr;
- cload.Read(&old_ptr, sizeof (old_ptr));
+ // Read exactly what Save wrote: a fixed-width 4-byte
+ // identity token, not sizeof(old_ptr). On x86-64
+ // sizeof(AudibleSoundClass*) is 8, so reading
+ // sizeof(old_ptr) here would ask for more bytes than
+ // the legacy 4-byte micro chunk holds; ChunkLoadClass::Read
+ // then refuses to read anything at all and old_ptr
+ // stays null, silently poisoning SaveLoadSystemClass's
+ // pointer remap table. See persistfactory.h.
+ uint32 old_ptr_token = 0;
+ cload.Read(&old_ptr_token, sizeof (old_ptr_token));
+ AudibleSoundClass *old_ptr = (AudibleSoundClass *)(uintptr_t)old_ptr_token;
SaveLoadSystemClass::Register_Pointer (old_ptr, this);
}
break;
diff --git a/Core/Libraries/Source/WWVegas/WWAudio/AudibleSound.h b/Core/Libraries/Source/WWVegas/WWAudio/AudibleSound.h
index 36205805613..79028dc2d50 100644
--- a/Core/Libraries/Source/WWVegas/WWAudio/AudibleSound.h
+++ b/Core/Libraries/Source/WWVegas/WWAudio/AudibleSound.h
@@ -69,7 +69,10 @@ class SoundHandleClass;
//
// Typedefs
//
-typedef unsigned long MILES_HANDLE;
+// Miles Sound System handles are pointers under the hood; this must be
+// pointer-sized to round-trip through Get_2D_Sample/Get_3D_Sample without
+// truncation on 64-bit targets. Runtime-only, never serialized.
+typedef uintptr_t MILES_HANDLE;
typedef enum
{
diff --git a/Core/Libraries/Source/WWVegas/WWAudio/SoundScene.cpp b/Core/Libraries/Source/WWVegas/WWAudio/SoundScene.cpp
index 4427f3d7bff..0cd7a2859a5 100644
--- a/Core/Libraries/Source/WWVegas/WWAudio/SoundScene.cpp
+++ b/Core/Libraries/Source/WWVegas/WWAudio/SoundScene.cpp
@@ -199,7 +199,7 @@ SoundSceneClass::Collect_Logical_Sounds (unsigned int milliseconds, int listener
// Is the sound ready to notify?
//
if (sound_obj->Allow_Notify (timestamp)) {
- listener->On_Event (AudioCallbackClass::EVENT_LOGICAL_HEARD, (uint32)listener, (uint32)sound_obj);
+ listener->On_Event (AudioCallbackClass::EVENT_LOGICAL_HEARD, (uintptr_t)listener, (uintptr_t)sound_obj);
}
}
}
diff --git a/Core/Libraries/Source/WWVegas/WWAudio/SoundSceneObj.cpp b/Core/Libraries/Source/WWVegas/WWAudio/SoundSceneObj.cpp
index 7a0bd0ad12a..a5aad110c79 100644
--- a/Core/Libraries/Source/WWVegas/WWAudio/SoundSceneObj.cpp
+++ b/Core/Libraries/Source/WWVegas/WWAudio/SoundSceneObj.cpp
@@ -34,6 +34,7 @@
#include "SoundSceneObj.h"
+#include
#include "WW3D2/camera.h"
#include "WW3D2/rendobj.h"
#include "WWSaveLoad/persistfactory.h"
@@ -259,10 +260,22 @@ SoundSceneObjClass::Save (ChunkSaveClass &csave)
csave.End_Chunk ();
csave.Begin_Chunk (CHUNKID_VARIABLES);
+#if defined(_WIN64) || defined(__x86_64__)
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 Persisted pointers are 4-byte identity tokens: that is what the
+ // legacy files hold and what RenderObjPersistFactory registers, so the remap below can match.
+ uint32 attached_obj_token = (uint32)(uintptr_t)m_AttachedObject;
+ WRITE_MICRO_CHUNK (csave, VARID_ATTACHED_OBJ, attached_obj_token);
+#else
WRITE_MICRO_CHUNK (csave, VARID_ATTACHED_OBJ, m_AttachedObject);
+#endif
WRITE_MICRO_CHUNK (csave, VARID_ATTACHED_BONE, m_AttachedBone);
WRITE_MICRO_CHUNK (csave, VARID_USER_DATA, m_UserData);
+#if defined(_WIN64) || defined(__x86_64__)
+ uint32 user_obj_token = (uint32)(uintptr_t)m_UserObj;
+ WRITE_MICRO_CHUNK (csave, VARID_USER_OBJ, user_obj_token);
+#else
WRITE_MICRO_CHUNK (csave, VARID_USER_OBJ, m_UserObj);
+#endif
WRITE_MICRO_CHUNK (csave, VARID_ID, m_ID);
csave.End_Chunk ();
return true;
@@ -294,10 +307,33 @@ SoundSceneObjClass::Load (ChunkLoadClass &cload)
while (cload.Open_Micro_Chunk ()) {
switch (cload.Cur_Micro_Chunk_ID ()) {
+#if defined(_WIN64) || defined(__x86_64__)
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 Read exactly the 4 bytes Save wrote; sizeof(pointer)
+ // is 8 here, and ChunkLoadClass::Read refuses a short chunk outright, leaving the
+ // member unset and poisoning the pointer remap.
+ case VARID_ATTACHED_OBJ:
+ {
+ uint32 attached_obj_token = 0;
+ cload.Read (&attached_obj_token, sizeof (attached_obj_token));
+ m_AttachedObject = (RenderObjClass *)(uintptr_t)attached_obj_token;
+ break;
+ }
+#else
READ_MICRO_CHUNK (cload, VARID_ATTACHED_OBJ, m_AttachedObject);
+#endif
READ_MICRO_CHUNK (cload, VARID_ATTACHED_BONE, m_AttachedBone);
READ_MICRO_CHUNK (cload, VARID_USER_DATA, m_UserData);
+#if defined(_WIN64) || defined(__x86_64__)
+ case VARID_USER_OBJ:
+ {
+ uint32 user_obj_token = 0;
+ cload.Read (&user_obj_token, sizeof (user_obj_token));
+ m_UserObj = (RefCountClass *)(uintptr_t)user_obj_token;
+ break;
+ }
+#else
READ_MICRO_CHUNK (cload, VARID_USER_OBJ, m_UserObj);
+#endif
READ_MICRO_CHUNK (cload, VARID_ID, id);
}
diff --git a/Core/Libraries/Source/WWVegas/WWAudio/SoundSceneObj.h b/Core/Libraries/Source/WWVegas/WWAudio/SoundSceneObj.h
index a5d6f7d0b0a..12bc4838699 100644
--- a/Core/Libraries/Source/WWVegas/WWAudio/SoundSceneObj.h
+++ b/Core/Libraries/Source/WWVegas/WWAudio/SoundSceneObj.h
@@ -40,6 +40,9 @@
#include "WWSaveLoad/persist.h"
#include "WWLib/multilist.h"
#include "WWLib/mutex.h"
+// Only pulls the pointer-sized-int typedef (uintptr_t); avoids dragging
+// BaseTypeCore.h's warning-as-error pragmas into a file that never had them.
+#include
/////////////////////////////////////////////////////////////////////////////////
// Forward declarations
@@ -123,7 +126,9 @@ class SoundSceneObjClass : public MultiListObjectClass, public PersistClass, pub
//////////////////////////////////////////////////////////////////////
// Event handling
//////////////////////////////////////////////////////////////////////
- virtual void On_Event (AudioCallbackClass::EVENTS event, uint32 param1 = 0, uint32 param2 = 0);
+ // param1/param2 double as pointers smuggled through integer parameters for
+ // EVENT_LOGICAL_HEARD (see the inline definition below); must be pointer-sized.
+ virtual void On_Event (AudioCallbackClass::EVENTS event, uintptr_t param1 = 0, uintptr_t param2 = 0);
virtual void Register_Callback (AudioCallbackClass::EVENTS events, AudioCallbackClass *callback);
//////////////////////////////////////////////////////////////////////
@@ -225,8 +230,8 @@ __inline void
SoundSceneObjClass::On_Event
(
AudioCallbackClass::EVENTS event,
- uint32 param1,
- uint32 param2
+ uintptr_t param1,
+ uintptr_t param2
)
{
if ((m_pCallback != nullptr) && (m_RegisteredEvents & event)) {
diff --git a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp
index dab6686125c..7177637912f 100644
--- a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp
+++ b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp
@@ -111,16 +111,31 @@ bool DbgHelpLoader::load()
Inst->m_loadedFromSystem = true;
}
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 x64 dbghelp.dll only exports the ...64 names for the
+ // address-taking entry points; un-suffixed GetProcAddress returns NULL there. 32-bit call order kept.
Inst->m_symInitialize = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymInitialize"));
Inst->m_symCleanup = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymCleanup"));
+#if defined(_WIN64) || defined(__x86_64__)
+ Inst->m_symLoadModule = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymLoadModule64"));
+ Inst->m_symUnloadModule = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymUnloadModule64"));
+ Inst->m_symGetModuleBase = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymGetModuleBase64"));
+ Inst->m_symGetSymFromAddr = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymGetSymFromAddr64"));
+ Inst->m_symGetLineFromAddr = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymGetLineFromAddr64"));
+#else
Inst->m_symLoadModule = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymLoadModule"));
Inst->m_symUnloadModule = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymUnloadModule"));
Inst->m_symGetModuleBase = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymGetModuleBase"));
Inst->m_symGetSymFromAddr = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymGetSymFromAddr"));
Inst->m_symGetLineFromAddr = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymGetLineFromAddr"));
+#endif
Inst->m_symSetOptions = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymSetOptions"));
+#if defined(_WIN64) || defined(__x86_64__)
+ Inst->m_symFunctionTableAccess = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymFunctionTableAccess64"));
+ Inst->m_stackWalk = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "StackWalk64"));
+#else
Inst->m_symFunctionTableAccess = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymFunctionTableAccess"));
Inst->m_stackWalk = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "StackWalk"));
+#endif
#ifdef RTS_ENABLE_CRASHDUMP
Inst->m_miniDumpWriteDump = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "MiniDumpWriteDump"));
#endif
@@ -232,6 +247,15 @@ BOOL DbgHelpLoader::symCleanup(
return FALSE;
}
+#if defined(_WIN64) || defined(__x86_64__)
+DWORD64 DbgHelpLoader::symLoadModule(
+ HANDLE hProcess,
+ HANDLE hFile,
+ LPSTR ImageName,
+ LPSTR ModuleName,
+ DWORD64 BaseOfDll,
+ DWORD SizeOfDll)
+#else
BOOL DbgHelpLoader::symLoadModule(
HANDLE hProcess,
HANDLE hFile,
@@ -239,6 +263,7 @@ BOOL DbgHelpLoader::symLoadModule(
LPSTR ModuleName,
DWORD BaseOfDll,
DWORD SizeOfDll)
+#endif
{
CriticalSectionClass::LockClass lock(CriticalSection);
@@ -248,9 +273,15 @@ BOOL DbgHelpLoader::symLoadModule(
return FALSE;
}
+#if defined(_WIN64) || defined(__x86_64__)
+DWORD64 DbgHelpLoader::symGetModuleBase(
+ HANDLE hProcess,
+ DWORD64 dwAddr)
+#else
DWORD DbgHelpLoader::symGetModuleBase(
HANDLE hProcess,
DWORD dwAddr)
+#endif
{
CriticalSectionClass::LockClass lock(CriticalSection);
@@ -260,9 +291,15 @@ DWORD DbgHelpLoader::symGetModuleBase(
return 0u;
}
+#if defined(_WIN64) || defined(__x86_64__)
+BOOL DbgHelpLoader::symUnloadModule(
+ HANDLE hProcess,
+ DWORD64 BaseOfDll)
+#else
BOOL DbgHelpLoader::symUnloadModule(
HANDLE hProcess,
DWORD BaseOfDll)
+#endif
{
CriticalSectionClass::LockClass lock(CriticalSection);
@@ -272,11 +309,19 @@ BOOL DbgHelpLoader::symUnloadModule(
return FALSE;
}
+#if defined(_WIN64) || defined(__x86_64__)
+BOOL DbgHelpLoader::symGetSymFromAddr(
+ HANDLE hProcess,
+ DWORD64 Address,
+ PDWORD64 Displacement,
+ PIMAGEHLP_SYMBOL Symbol)
+#else
BOOL DbgHelpLoader::symGetSymFromAddr(
HANDLE hProcess,
DWORD Address,
LPDWORD Displacement,
PIMAGEHLP_SYMBOL Symbol)
+#endif
{
CriticalSectionClass::LockClass lock(CriticalSection);
@@ -286,11 +331,19 @@ BOOL DbgHelpLoader::symGetSymFromAddr(
return FALSE;
}
+#if defined(_WIN64) || defined(__x86_64__)
+BOOL DbgHelpLoader::symGetLineFromAddr(
+ HANDLE hProcess,
+ DWORD64 dwAddr,
+ PDWORD pdwDisplacement,
+ PIMAGEHLP_LINE Line)
+#else
BOOL DbgHelpLoader::symGetLineFromAddr(
HANDLE hProcess,
DWORD dwAddr,
PDWORD pdwDisplacement,
PIMAGEHLP_LINE Line)
+#endif
{
CriticalSectionClass::LockClass lock(CriticalSection);
@@ -311,9 +364,15 @@ DWORD DbgHelpLoader::symSetOptions(
return 0u;
}
+#if defined(_WIN64) || defined(__x86_64__)
+LPVOID DbgHelpLoader::symFunctionTableAccess(
+ HANDLE hProcess,
+ DWORD64 AddrBase)
+#else
LPVOID DbgHelpLoader::symFunctionTableAccess(
HANDLE hProcess,
DWORD AddrBase)
+#endif
{
CriticalSectionClass::LockClass lock(CriticalSection);
diff --git a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h
index 556cbafcc61..d1dfa32d9c4 100644
--- a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h
+++ b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h
@@ -66,6 +66,17 @@ class DbgHelpLoader
static BOOL WINAPI symCleanup(
HANDLE hProcess);
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 Widened to the ...64 ABI on x64, audited against
+ // psdk_inc/_dbg_common.h; SymGetLineFromAddr64's pdwDisplacement genuinely stays PDWORD.
+#if defined(_WIN64) || defined(__x86_64__)
+ static DWORD64 WINAPI symLoadModule(
+ HANDLE hProcess,
+ HANDLE hFile,
+ LPSTR ImageName,
+ LPSTR ModuleName,
+ DWORD64 BaseOfDll,
+ DWORD SizeOfDll);
+#else
static BOOL WINAPI symLoadModule(
HANDLE hProcess,
HANDLE hFile,
@@ -73,33 +84,70 @@ class DbgHelpLoader
LPSTR ModuleName,
DWORD BaseOfDll,
DWORD SizeOfDll);
+#endif
+#if defined(_WIN64) || defined(__x86_64__)
+ static DWORD64 WINAPI symGetModuleBase(
+ HANDLE hProcess,
+ DWORD64 dwAddr);
+#else
static DWORD WINAPI symGetModuleBase(
HANDLE hProcess,
DWORD dwAddr);
+#endif
+#if defined(_WIN64) || defined(__x86_64__)
+ static BOOL WINAPI symUnloadModule(
+ HANDLE hProcess,
+ DWORD64 BaseOfDll);
+#else
static BOOL WINAPI symUnloadModule(
HANDLE hProcess,
DWORD BaseOfDll);
+#endif
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 Displacement is a write target: 4-byte slot under an
+ // 8-byte SymGetSymFromAddr64 write is a stack buffer overflow.
+#if defined(_WIN64) || defined(__x86_64__)
+ static BOOL WINAPI symGetSymFromAddr(
+ HANDLE hProcess,
+ DWORD64 Address,
+ PDWORD64 Displacement,
+ PIMAGEHLP_SYMBOL Symbol);
+#else
static BOOL WINAPI symGetSymFromAddr(
HANDLE hProcess,
DWORD Address,
LPDWORD Displacement,
PIMAGEHLP_SYMBOL Symbol);
+#endif
+#if defined(_WIN64) || defined(__x86_64__)
+ static BOOL WINAPI symGetLineFromAddr(
+ HANDLE hProcess,
+ DWORD64 dwAddr,
+ PDWORD pdwDisplacement,
+ PIMAGEHLP_LINE Line);
+#else
static BOOL WINAPI symGetLineFromAddr(
HANDLE hProcess,
DWORD dwAddr,
PDWORD pdwDisplacement,
PIMAGEHLP_LINE Line);
+#endif
static DWORD WINAPI symSetOptions(
DWORD SymOptions);
+#if defined(_WIN64) || defined(__x86_64__)
+ static LPVOID WINAPI symFunctionTableAccess(
+ HANDLE hProcess,
+ DWORD64 AddrBase);
+#else
static LPVOID WINAPI symFunctionTableAccess(
HANDLE hProcess,
DWORD AddrBase);
+#endif
static BOOL WINAPI stackWalk(
DWORD MachineType,
@@ -135,6 +183,15 @@ class DbgHelpLoader
typedef BOOL (WINAPI *SymCleanup_t) (
HANDLE hProcess);
+#if defined(_WIN64) || defined(__x86_64__)
+ typedef DWORD64 (WINAPI *SymLoadModule_t) (
+ HANDLE hProcess,
+ HANDLE hFile,
+ LPSTR ImageName,
+ LPSTR ModuleName,
+ DWORD64 BaseOfDll,
+ DWORD SizeOfDll);
+#else
typedef BOOL (WINAPI *SymLoadModule_t) (
HANDLE hProcess,
HANDLE hFile,
@@ -142,33 +199,68 @@ class DbgHelpLoader
LPSTR ModuleName,
DWORD BaseOfDll,
DWORD SizeOfDll);
+#endif
+#if defined(_WIN64) || defined(__x86_64__)
+ typedef DWORD64 (WINAPI *SymGetModuleBase_t) (
+ HANDLE hProcess,
+ DWORD64 dwAddr);
+#else
typedef DWORD (WINAPI *SymGetModuleBase_t) (
HANDLE hProcess,
DWORD dwAddr);
+#endif
+#if defined(_WIN64) || defined(__x86_64__)
+ typedef BOOL (WINAPI *SymUnloadModule_t) (
+ HANDLE hProcess,
+ DWORD64 BaseOfDll);
+#else
typedef BOOL (WINAPI *SymUnloadModule_t) (
HANDLE hProcess,
DWORD BaseOfDll);
+#endif
+#if defined(_WIN64) || defined(__x86_64__)
+ typedef BOOL (WINAPI *SymGetSymFromAddr_t) (
+ HANDLE hProcess,
+ DWORD64 Address,
+ PDWORD64 Displacement,
+ PIMAGEHLP_SYMBOL Symbol);
+#else
typedef BOOL (WINAPI *SymGetSymFromAddr_t) (
HANDLE hProcess,
DWORD Address,
LPDWORD Displacement,
PIMAGEHLP_SYMBOL Symbol);
+#endif
+#if defined(_WIN64) || defined(__x86_64__)
+ typedef BOOL (WINAPI* SymGetLineFromAddr_t) (
+ HANDLE hProcess,
+ DWORD64 dwAddr,
+ PDWORD pdwDisplacement,
+ PIMAGEHLP_LINE Line);
+#else
typedef BOOL (WINAPI* SymGetLineFromAddr_t) (
HANDLE hProcess,
DWORD dwAddr,
PDWORD pdwDisplacement,
PIMAGEHLP_LINE Line);
+#endif
typedef DWORD (WINAPI *SymSetOptions_t) (
DWORD SymOptions);
+#if defined(_WIN64) || defined(__x86_64__)
+ typedef LPVOID (WINAPI *SymFunctionTableAccess_t) (
+ HANDLE hProcess,
+ DWORD64 AddrBase);
+#else
typedef LPVOID (WINAPI *SymFunctionTableAccess_t) (
HANDLE hProcess,
DWORD AddrBase);
+#endif
typedef BOOL (WINAPI *StackWalk_t) (
DWORD MachineType,
diff --git a/Core/Libraries/Source/WWVegas/WWLib/Except.cpp b/Core/Libraries/Source/WWVegas/WWLib/Except.cpp
index be9c958cdf1..0b7a0251623 100644
--- a/Core/Libraries/Source/WWVegas/WWLib/Except.cpp
+++ b/Core/Libraries/Source/WWVegas/WWLib/Except.cpp
@@ -54,6 +54,19 @@
#include "assert.h"
#include "cpudetect.h"
#include "Except.h"
+// Only pulls the pointer-sized-int typedef (uintptr_t); avoids dragging
+// BaseTypeCore.h's warning-as-error pragmas into a file that never had them.
+#include
+#include "Lib/arch_context.h"
+
+// TheSuperHackers @fix MeneerHaas 02/09/2026 StackWalk64 requires a ContextRecord on AMD64 (it is optional on x86) and
+// updates it while unwinding, so the walker below seeds a mutable local walk_ctx and passes it
+// through this macro. On 32-bit it expands to the retail nullptr, leaving that arm unchanged.
+#if defined(_WIN64) || defined(__x86_64__)
+#define RTS_STACKWALK_CONTEXT (&walk_ctx)
+#else
+#define RTS_STACKWALK_CONTEXT nullptr
+#endif
//#include "debug.h"
#include "MPU.h"
//#include "commando\nat.h"
@@ -117,14 +130,76 @@ DynamicVectorClass ThreadList;
**
*/
typedef BOOL (WINAPI *SymCleanupType) (HANDLE hProcess);
+
+// Correcting the name table below (see ImagehelpFunctionNames) to resolve
+// this to the real 64-bit export, SymGetSymFromAddr64, means the ABI it's
+// actually called through changed too -- every parameter has to be
+// re-checked against psdk_inc/_dbg_common.h, not just the ones the compiler
+// would catch. Real signature: BOOL SymGetSymFromAddr64(HANDLE hProcess,
+// DWORD64 qwAddr, PDWORD64 pdwDisplacement, PIMAGEHLP_SYMBOL64 Symbol).
+// Address and Displacement are hand-rolled DWORD/LPDWORD here and must be
+// widened explicitly on x64, or SymGetSymFromAddr64 writes 8 bytes through
+// a 4-byte Displacement target -- a stack buffer overflow on every
+// successful symbol lookup. Symbol needs no separate widening: dbghelp.h
+// already #defines PIMAGEHLP_SYMBOL to PIMAGEHLP_SYMBOL64 under
+// _IMAGEHLP64 (same mechanism as LPSTACKFRAME above), and is
+// already included above this point in the file, so it widens for free at
+// this typedef's declaration site.
+#if defined(_WIN64) || defined(__x86_64__)
+typedef BOOL (WINAPI *SymGetSymFromAddrType) (HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, PIMAGEHLP_SYMBOL Symbol);
+#else
typedef BOOL (WINAPI *SymGetSymFromAddrType) (HANDLE hProcess, DWORD Address, LPDWORD Displacement, PIMAGEHLP_SYMBOL Symbol);
+#endif
+
typedef BOOL (WINAPI *SymInitializeType) (HANDLE hProcess, LPSTR UserSearchPath, BOOL fInvadeProcess);
+
+// Real SymLoadModule64: DWORD64 SymLoadModule64(HANDLE hProcess, HANDLE
+// hFile, PCSTR ImageName, PCSTR ModuleName, DWORD64 BaseOfDll, DWORD
+// SizeOfDll) -- both the return type and BaseOfDll widen relative to the
+// deprecated 32-bit SymLoadModule. Every call site below passes a literal
+// 0 for BaseOfDll (letting DbgHelp pick the base), so there's no
+// overflow risk there, but the return-type mismatch (BOOL vs DWORD64) is
+// still a real function-pointer-signature mismatch worth correcting, not
+// just a cosmetic one.
+#if defined(_WIN64) || defined(__x86_64__)
+typedef DWORD64 (WINAPI *SymLoadModuleType) (HANDLE hProcess, HANDLE hFile, LPSTR ImageName, LPSTR ModuleName, DWORD64 BaseOfDll, DWORD SizeOfDll);
+#else
typedef BOOL (WINAPI *SymLoadModuleType) (HANDLE hProcess, HANDLE hFile, LPSTR ImageName, LPSTR ModuleName, DWORD BaseOfDll, DWORD SizeOfDll);
+#endif
+
typedef DWORD (WINAPI *SymSetOptionsType) (DWORD SymOptions);
+
+// Real SymUnloadModule64: WINBOOL SymUnloadModule64(HANDLE hProcess,
+// DWORD64 BaseOfDll). Both call sites below pass a literal 0, so no
+// overflow risk, but BaseOfDll still needs widening to match the real
+// export's signature.
+#if defined(_WIN64) || defined(__x86_64__)
+typedef BOOL (WINAPI *SymUnloadModuleType) (HANDLE hProcess, DWORD64 BaseOfDll);
+#else
typedef BOOL (WINAPI *SymUnloadModuleType) (HANDLE hProcess, DWORD BaseOfDll);
+#endif
+
+// StackWalkType needs no architecture-specific branch: every one of its
+// parameter type names (LPSTACKFRAME, PREAD_PROCESS_MEMORY_ROUTINE,
+// PFUNCTION_TABLE_ACCESS_ROUTINE, PGET_MODULE_BASE_ROUTINE,
+// PTRANSLATE_ADDRESS_ROUTINE) is a platform macro that dbghelp.h itself
+// redirects to its ...64 form under _IMAGEHLP64, so this typedef already
+// matches StackWalk64's real signature on x64 and StackWalk's on 32-bit.
typedef BOOL (WINAPI *StackWalkType) (DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME StackFrame, LPVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE TranslateAddress);
+
+// Unlike StackWalkType above, these next two typedefs don't go through
+// platform macro names -- they hand-roll a DWORD address parameter -- so
+// they need an explicit 64-bit branch or _StackWalk's call site below
+// won't accept them as the FunctionTableAccessRoutine / GetModuleBaseRoutine
+// arguments. VC6 (1998) predates the ...64 DbgHelp API, so the 32-bit
+// branch is untouched -- this is not made unconditional.
+#if defined(_WIN64) || defined(__x86_64__)
+typedef PFUNCTION_TABLE_ACCESS_ROUTINE64 SymFunctionTableAccessType;
+typedef PGET_MODULE_BASE_ROUTINE64 SymGetModuleBaseType;
+#else
typedef LPVOID (WINAPI *SymFunctionTableAccessType) (HANDLE hProcess, DWORD AddrBase);
typedef DWORD (WINAPI *SymGetModuleBaseType) (HANDLE hProcess, DWORD dwAddr);
+#endif
static SymCleanupType _SymCleanup = nullptr;
@@ -137,6 +212,46 @@ static StackWalkType _StackWalk = nullptr;
static SymFunctionTableAccessType _SymFunctionTableAccess = nullptr;
static SymGetModuleBaseType _SymGetModuleBase = nullptr;
+// This table is walked in lockstep with the _SymXxx globals above (see the
+// fptr loop in Dump_Exception_Info() / Load_Image_Helper()) to GetProcAddress
+// each name out of IMAGEHLP.DLL / DBGHELP.DLL, so order must stay in sync
+// with the globals' declaration order.
+//
+// 64-bit dbghelp.dll only exports the ...64 forms of the entry points whose
+// address parameter is DWORD64 (SymGetSymFromAddr, SymLoadModule,
+// SymUnloadModule, StackWalk, SymFunctionTableAccess, SymGetModuleBase);
+// GetProcAddress with the un-suffixed name returns NULL for those on x64,
+// which would leave the corresponding _SymXxx pointer null and silently
+// disable that part of the crash handler rather than fail to compile.
+// SymCleanup, SymInitialize and SymSetOptions take no address parameter and
+// are exported under the same name on every architecture (verified against
+// mingw-w64's psdk_inc/_dbg_common.h: no #define redirects them under
+// _IMAGEHLP64), so they are unchanged.
+//
+// Entry 9 is "SymGetModuleBaseType" on the 32-bit side, which is wrong on
+// every architecture: it is the name of this file's local typedef (see
+// SymGetModuleBaseType above), not a DbgHelp export -- the real export is
+// "SymGetModuleBase". That means _SymGetModuleBase has always resolved to
+// nullptr and the stack walker has always run without a module-base
+// callback on 32-bit. Left byte-identical (typo included) here because
+// correcting it would change retail runtime behaviour -- a previously-NULL
+// callback would suddenly be populated in shipping builds. See task-4-report
+// for the writeup; fix is deliberately deferred to a separate decision.
+#if defined(_WIN64) || defined(__x86_64__)
+static char const *const ImagehelpFunctionNames[] =
+{
+ "SymCleanup",
+ "SymGetSymFromAddr64",
+ "SymInitialize",
+ "SymLoadModule64",
+ "SymSetOptions",
+ "SymUnloadModule64",
+ "StackWalk64",
+ "SymFunctionTableAccess64",
+ "SymGetModuleBase64",
+ nullptr
+};
+#else
static char const *const ImagehelpFunctionNames[] =
{
"SymCleanup",
@@ -150,6 +265,7 @@ static char const *const ImagehelpFunctionNames[] =
"SymGetModuleBaseType",
nullptr
};
+#endif
@@ -355,13 +471,16 @@ void Dump_Exception_Info(EXCEPTION_POINTERS *e_info)
if (imagehelp != nullptr) {
DebugString ("Exception Handler: Found IMAGEHLP.DLL - linking to required functions\n");
char const *function_name = nullptr;
- unsigned long *fptr = (unsigned long*) &_SymCleanup;
+ // fptr walks across the consecutive _SymXxx globals below, each of which
+ // is an actual function pointer (8 bytes on Win64) -- must be
+ // pointer-sized or the stride only covers half of each slot on 64-bit.
+ uintptr_t *fptr = (uintptr_t*) &_SymCleanup;
int count = 0;
do {
function_name = ImagehelpFunctionNames[count];
if (function_name) {
- *fptr = (unsigned long) GetProcAddress(imagehelp, function_name);
+ *fptr = (uintptr_t) GetProcAddress(imagehelp, function_name);
fptr++;
count++;
}
@@ -378,7 +497,18 @@ void Dump_Exception_Info(EXCEPTION_POINTERS *e_info)
_SymSetOptions(SYMOPT_DEFERRED_LOADS);
}
+ // SymLoadModuleType's return type is DWORD64 on x64 (matching the real
+ // SymLoadModule64 export); uintptr_t is pointer-width (4 bytes on
+ // 32-bit, 8 on 64-bit) so the assignment below never truncates a
+ // nonzero result down to a false "load failed" reading. Gated (rather
+ // than just widening unconditionally) purely to keep the 32-bit/VC6
+ // codegen for this line textually identical to before -- the 32-bit
+ // return type never changed, so there's nothing to fix on that branch.
+#if defined(_WIN64) || defined(__x86_64__)
+ uintptr_t symload = 0;
+#else
int symload = 0;
+#endif
int symbols_available = false;
if (_SymInitialize != nullptr && _SymInitialize (GetCurrentProcess(), nullptr, false)) {
@@ -465,18 +595,35 @@ void Dump_Exception_Info(EXCEPTION_POINTERS *e_info)
symptr->SizeOfStruct = sizeof (IMAGEHLP_SYMBOL);
symptr->MaxNameLength = 256-sizeof (IMAGEHLP_SYMBOL);
symptr->Size = 0;
- symptr->Address = context->Eip;
-
- if (!IsBadCodePtr((FARPROC)context->Eip)) {
- if (_SymGetSymFromAddr != nullptr && _SymGetSymFromAddr (GetCurrentProcess(), context->Eip, &displacement, symptr)) {
+ symptr->Address = CTX_PC(*context);
+
+ if (!IsBadCodePtr((FARPROC)CTX_PC(*context))) {
+#if defined(_WIN64) || defined(__x86_64__)
+ // SymGetSymFromAddr64's Displacement out-param is PDWORD64; writing
+ // through &displacement (unsigned long, 4 bytes) here would let the
+ // API write 8 bytes into a 4-byte stack slot. Capture into a
+ // properly sized local and narrow into the display variable only
+ // after the call returns.
+ DWORD64 displacement64;
+ if (_SymGetSymFromAddr != nullptr && _SymGetSymFromAddr (GetCurrentProcess(), CTX_PC(*context), &displacement64, symptr)) {
+ displacement = (unsigned long)displacement64;
+ snprintf(scrap, ARRAY_SIZE(scrap), "Exception occurred at %016llX - %s + %08X\r\n",
+ (unsigned long long)CTX_PC(*context), symptr->Name, displacement);
+#else
+ if (_SymGetSymFromAddr != nullptr && _SymGetSymFromAddr (GetCurrentProcess(), CTX_PC(*context), &displacement, symptr)) {
snprintf(scrap, ARRAY_SIZE(scrap), "Exception occurred at %08X - %s + %08X\r\n",
context->Eip, symptr->Name, displacement);
+#endif
} else {
DebugString ("Exception Handler: Failed to get symbol for EIP\r\n");
if (_SymGetSymFromAddr != nullptr) {
DebugString ("Exception Handler: SymGetSymFromAddr failed with code %d - %s\n", GetLastError(), Last_Error_Text());
}
+#if defined(_WIN64) || defined(__x86_64__)
+ sprintf (scrap, "Exception occurred at %016llX\r\n", (unsigned long long)CTX_PC(*context));
+#else
sprintf (scrap, "Exception occurred at %08X\r\n", context->Eip);
+#endif
}
} else {
DebugString ("Exception Handler: context->Eip is bad code pointer\n");
@@ -490,12 +637,12 @@ void Dump_Exception_Info(EXCEPTION_POINTERS *e_info)
DebugString("Stack walk...\n");
Add_Txt("\r\n Stack walk...\r\n");
- unsigned long return_addresses[256];
+ uintptr_t return_addresses[256];
int num_addresses = Stack_Walk(return_addresses, 256, context);
if (num_addresses) {
for (int s=0 ; sSize = 0;
symptr->Address = temp_addr;
+#if defined(_WIN64) || defined(__x86_64__)
+ // See the comment on the first _SymGetSymFromAddr call above:
+ // its Displacement out-param is PDWORD64 on x64.
+ DWORD64 displacement64;
+ if (_SymGetSymFromAddr != nullptr && _SymGetSymFromAddr (GetCurrentProcess(), temp_addr, &displacement64, symptr)) {
+ displacement = (unsigned long)displacement64;
+#else
if (_SymGetSymFromAddr != nullptr && _SymGetSymFromAddr (GetCurrentProcess(), temp_addr, &displacement, symptr)) {
+#endif
char symbuf[256];
snprintf(symbuf, ARRAY_SIZE(symbuf), "%s + %08X\r\n", symptr->Name, displacement);
Add_Txt(symbuf);
}
} else {
char symbuf[256];
- sprintf(symbuf, "%08x\r\n", temp_addr);
+#if defined(_WIN64) || defined(__x86_64__)
+ sprintf(symbuf, "%016llX\r\n", (unsigned long long)temp_addr);
+#else
+ sprintf(symbuf, "%08x\r\n", (unsigned)temp_addr);
+#endif
Add_Txt(symbuf);
}
}
@@ -585,12 +744,21 @@ void Dump_Exception_Info(EXCEPTION_POINTERS *e_info)
/*
** Dump the registers.
*/
+#if defined(_WIN64) || defined(__x86_64__)
+ sprintf(scrap, "Rip:%016llX\tRsp:%016llX\tRbp:%016llX\r\n", (unsigned long long)CTX_PC(*context), (unsigned long long)CTX_STACK(*context), (unsigned long long)CTX_FRAME(*context));
+ Add_Txt(scrap);
+ sprintf(scrap, "Rax:%016llX\tRbx:%016llX\tRcx:%016llX\r\n", (unsigned long long)CTX_AX(*context), (unsigned long long)CTX_BX(*context), (unsigned long long)CTX_CX(*context));
+ Add_Txt(scrap);
+ sprintf(scrap, "Rdx:%016llX\tRsi:%016llX\tRdi:%016llX\r\n", (unsigned long long)CTX_DX(*context), (unsigned long long)CTX_SI(*context), (unsigned long long)CTX_DI(*context));
+ Add_Txt(scrap);
+#else
sprintf(scrap, "Eip:%08X\tEsp:%08X\tEbp:%08X\r\n", context->Eip, context->Esp, context->Ebp);
Add_Txt(scrap);
sprintf(scrap, "Eax:%08X\tEbx:%08X\tEcx:%08X\r\n", context->Eax, context->Ebx, context->Ecx);
Add_Txt(scrap);
sprintf(scrap, "Edx:%08X\tEsi:%08X\tEdi:%08X\r\n", context->Edx, context->Esi, context->Edi);
Add_Txt(scrap);
+#endif
sprintf(scrap, "EFlags:%08X \r\n", context->EFlags);
Add_Txt(scrap);
sprintf(scrap, "CS:%04x SS:%04x DS:%04x ES:%04x FS:%04x GS:%04x\r\n", context->SegCs, context->SegSs, context->SegDs, context->SegEs, context->SegFs, context->SegGs);
@@ -620,6 +788,34 @@ void Dump_Exception_Info(EXCEPTION_POINTERS *e_info)
Add_Txt(scrap);
#endif
+#if defined(_WIN64) || defined(__x86_64__)
+ // x86-64: FPU/SSE state is in CONTEXT.FltSave (an XMM_SAVE_AREA32), not
+ // the 32-bit FLOATING_SAVE_AREA, and there is no RegisterArea. Each
+ // ST(i) register instead lives in the low 10 bytes of a 16-byte
+ // FloatRegisters[] slot (the remaining 6 bytes are reserved padding),
+ // so this reports exactly what the 32-bit block below reports, just
+ // addressed through the x86-64 layout.
+ for (int fp=0 ; fp<8 ; fp++) {
+ sprintf(scrap, "ST%d : ", fp);
+ Add_Txt(scrap);
+ BYTE *reg_bytes = (BYTE*)&context->FltSave.FloatRegisters[fp];
+ for (int b=0 ; b<10 ; b++) {
+ sprintf(scrap, "%02X", reg_bytes[b]);
+ Add_Txt(scrap);
+ }
+
+ void *fp_data_ptr = (void*)reg_bytes;
+
+ // TheSuperHackers @refactor Replaced MSVC inline assembly with portable C++ cast for MinGW compatibility
+ /*
+ ** Convert FP dump from temporary real value (10 bytes) to double (8 bytes).
+ ** On x86, long double is the 10-byte x87 format, so we can just cast.
+ */
+ double fp_value = (double)(*(long double*)fp_data_ptr);
+ sprintf(scrap, " %+#.17e\r\n", fp_value);
+ Add_Txt(scrap);
+ }
+#else
for (int fp=0 ; fpEip);
+#endif
- unsigned char *eip_ptr = (unsigned char *) (context->Eip);
+ unsigned char *eip_ptr = (unsigned char *) (CTX_PC(*context));
char bytestr[32];
for (int c = 0 ; c < 32 ; c++) {
@@ -667,10 +868,18 @@ void Dump_Exception_Info(EXCEPTION_POINTERS *e_info)
*/
DebugString("Stack dump...\n");
Add_Txt("Stack dump (* indicates possible code address) :\r\n");
- unsigned long *stackptr = (unsigned long*) context->Esp;
+ // Pointer-width, not `unsigned long`: on x86-64 (LLP64) `long` stays 32
+ // bits, but each stack slot is 8 bytes. Reading `unsigned long` here would
+ // walk the stack in 4-byte steps, printing alternating halves of
+ // neighbouring 8-byte slots and resolving symbols against low-32-bit
+ // fragments -- output that looks plausible but points at addresses that
+ // do not exist (see arch_context.h). uintptr_t keeps the 32-bit path
+ // identical (same width as `unsigned long` there) while making the
+ // 64-bit path read whole slots.
+ uintptr_t *stackptr = (uintptr_t*) CTX_STACK(*context);
for (int j=0 ; j<2048 ; j++) {
- if (IsBadReadPtr(stackptr, 4)) {
+ if (IsBadReadPtr(stackptr, sizeof(*stackptr))) {
/*
** The stack contents cannot be read so just print up question marks.
*/
@@ -681,10 +890,18 @@ void Dump_Exception_Info(EXCEPTION_POINTERS *e_info)
** If this stack address is in our memory space then try to match it with a code symbol.
*/
if (IsBadCodePtr((FARPROC)*stackptr)) {
- sprintf(scrap, "%p: %08lX ", static_cast(stackptr), *stackptr);
+#if defined(_WIN64) || defined(__x86_64__)
+ sprintf(scrap, "%p: %016llX ", static_cast(stackptr), (unsigned long long)*stackptr);
+#else
+ sprintf(scrap, "%p: %08lX ", static_cast(stackptr), (unsigned long)*stackptr);
+#endif
strlcat(scrap, "DATA_PTR\r\n", ARRAY_SIZE(scrap));
} else {
- sprintf(scrap, "%p: %08lX", static_cast(stackptr), *stackptr);
+#if defined(_WIN64) || defined(__x86_64__)
+ sprintf(scrap, "%p: %016llX", static_cast(stackptr), (unsigned long long)*stackptr);
+#else
+ sprintf(scrap, "%p: %08lX", static_cast(stackptr), (unsigned long)*stackptr);
+#endif
if (symbols_available) {
symptr->SizeOfStruct = sizeof(symbol);
@@ -692,7 +909,15 @@ void Dump_Exception_Info(EXCEPTION_POINTERS *e_info)
symptr->Size = 0;
symptr->Address = *stackptr;
+#if defined(_WIN64) || defined(__x86_64__)
+ // See the comment on the first _SymGetSymFromAddr call
+ // above: its Displacement out-param is PDWORD64 on x64.
+ DWORD64 displacement64;
+ if (_SymGetSymFromAddr != nullptr && _SymGetSymFromAddr (GetCurrentProcess(), *stackptr, &displacement64, symptr)) {
+ displacement = (unsigned long)displacement64;
+#else
if (_SymGetSymFromAddr != nullptr && _SymGetSymFromAddr (GetCurrentProcess(), *stackptr, &displacement, symptr)) {
+#endif
char symbuf[256];
snprintf(symbuf, ARRAY_SIZE(symbuf), " - %s + %08X", symptr->Name, displacement);
strlcat(scrap, symbuf, ARRAY_SIZE(scrap));
@@ -1065,13 +1290,14 @@ void Load_Image_Helper()
if (ImageHelp != nullptr) {
char const *function_name = nullptr;
- unsigned long *fptr = (unsigned long *) &_SymCleanup;
+ // Same pointer-sized stride requirement as Dump_Exception_Info() above.
+ uintptr_t *fptr = (uintptr_t *) &_SymCleanup;
int count = 0;
do {
function_name = ImagehelpFunctionNames[count];
if (function_name) {
- *fptr = (unsigned long) GetProcAddress(ImageHelp, function_name);
+ *fptr = (uintptr_t) GetProcAddress(ImageHelp, function_name);
fptr++;
count++;
}
@@ -1086,7 +1312,14 @@ void Load_Image_Helper()
_SymSetOptions(SYMOPT_DEFERRED_LOADS);
}
+ // See the comment on Dump_Exception_Info's symload above: pointer-width
+ // so a nonzero DWORD64 result on x64 never truncates to a false 0,
+ // gated to keep 32-bit/VC6 codegen textually unchanged.
+#if defined(_WIN64) || defined(__x86_64__)
+ uintptr_t symload = 0;
+#else
int symload = 0;
+#endif
if (_SymInitialize != nullptr && _SymInitialize(GetCurrentProcess(), nullptr, FALSE)) {
@@ -1169,12 +1402,39 @@ bool Lookup_Symbol(void *code_ptr, char *symbol, int &displacement)
symbol_struct_ptr->SizeOfStruct = sizeof (symbol_struct_buf);
symbol_struct_ptr->MaxNameLength = sizeof(symbol_struct_buf)-sizeof (IMAGEHLP_SYMBOL);
symbol_struct_ptr->Size = 0;
- symbol_struct_ptr->Address = (unsigned long)code_ptr;
+#if defined(_WIN64) || defined(__x86_64__)
+ // Correction to the comment this replaced: on x64 IMAGEHLP_SYMBOL is
+ // #defined to IMAGEHLP_SYMBOL64 (dbghelp.h, under _IMAGEHLP64), so
+ // ::Address here is DWORD64, and the name table below resolves
+ // _SymGetSymFromAddr to the real SymGetSymFromAddr64 export, whose
+ // Address parameter is DWORD64 too -- this is not "fixed at 32 bits"
+ // on this architecture. Use the full pointer width, not a narrowed one.
+ symbol_struct_ptr->Address = (uintptr_t)code_ptr;
+#else
+ // IMAGEHLP_SYMBOL::Address and SymGetSymFromAddr's DWORD parameter are
+ // fixed at 32 bits by the (32-bit-only) DbgHelp API on this
+ // architecture. Cast through uintptr_t so the narrowing is an
+ // explicit int-to-int conversion rather than a flagged pointer
+ // truncation; 32-bit codegen is unchanged.
+ symbol_struct_ptr->Address = (unsigned long)(uintptr_t)code_ptr;
+#endif
/*
** See if we have the symbol for that address.
*/
- if (_SymGetSymFromAddr(GetCurrentProcess(), (unsigned long)code_ptr, (unsigned long *)&displacement, symbol_struct_ptr)) {
+#if defined(_WIN64) || defined(__x86_64__)
+ // SymGetSymFromAddr64's Displacement out-param is PDWORD64; writing
+ // through &displacement (the caller's int&, 4 bytes) would be a stack
+ // buffer overflow -- the API writes 8 bytes through it. Capture into a
+ // properly sized local and narrow into the caller's int& only after
+ // the call returns, so this function's own signature (and every
+ // caller of it) is unaffected.
+ DWORD64 displacement64;
+ if (_SymGetSymFromAddr(GetCurrentProcess(), (DWORD64)(uintptr_t)code_ptr, &displacement64, symbol_struct_ptr)) {
+ displacement = (int)displacement64;
+#else
+ if (_SymGetSymFromAddr(GetCurrentProcess(), (unsigned long)(uintptr_t)code_ptr, (unsigned long *)&displacement, symbol_struct_ptr)) {
+#endif
/*
** Copy it back into the buffer provided.
@@ -1204,7 +1464,7 @@ bool Lookup_Symbol(void *code_ptr, char *symbol, int &displacement)
* HISTORY: *
* 6/12/2001 11:57AM ST : Created *
*=============================================================================================*/
-int Stack_Walk(unsigned long *return_addresses, int num_addresses, CONTEXT *context)
+int Stack_Walk(uintptr_t *return_addresses, int num_addresses, CONTEXT *context)
{
static HINSTANCE _imagehelp = (HINSTANCE) -1;
@@ -1229,9 +1489,14 @@ int Stack_Walk(unsigned long *return_addresses, int num_addresses, CONTEXT *cont
STACKFRAME stack_frame;
memset(&stack_frame, 0, sizeof(stack_frame));
- unsigned long reg_eip, reg_ebp, reg_esp;
+ // uintptr_t rather than unsigned long: these feed
+ // stack_frame.AddrPC/AddrFrame/AddrStack.Offset, which are DWORD64 in
+ // STACKFRAME64 (STACKFRAME becomes STACKFRAME64 on x64 -- see
+ // imagehlp.h's _IMAGEHLP64 mechanism), and `unsigned long` stays 32 bits
+ // under Win64's LLP64 model, so it would truncate there.
+ uintptr_t reg_eip, reg_ebp, reg_esp;
-#if defined(_MSC_VER)
+#if defined(_MSC_VER) && defined(_M_IX86)
__asm {
here:
lea eax,here
@@ -1248,7 +1513,16 @@ int Stack_Walk(unsigned long *return_addresses, int num_addresses, CONTEXT *cont
: "=r" (reg_eip), "=r" (reg_ebp), "=r" (reg_esp)
);
#else
-#error "Unsupported compiler or architecture for register capture"
+ // x86-64 and anything else: RtlCaptureContext fills a CONTEXT with the
+ // caller's register state. This is the documented Win64 way to seed a
+ // StackWalk64, and it needs no inline assembly. It also works on 32-bit
+ // Windows, but the __asm/__asm__ arms above are kept for VC6 and 32-bit
+ // GCC/Clang retail-compatibility.
+ CONTEXT capture_ctx;
+ RtlCaptureContext(&capture_ctx);
+ reg_eip = (uintptr_t)CTX_PC(capture_ctx);
+ reg_ebp = (uintptr_t)CTX_FRAME(capture_ctx);
+ reg_esp = (uintptr_t)CTX_STACK(capture_ctx);
#endif
stack_frame.AddrPC.Mode = AddrModeFlat;
@@ -1262,18 +1536,31 @@ int Stack_Walk(unsigned long *return_addresses, int num_addresses, CONTEXT *cont
** Use the context struct if it was provided.
*/
if (context) {
- stack_frame.AddrPC.Offset = context->Eip;
- stack_frame.AddrStack.Offset = context->Esp;
- stack_frame.AddrFrame.Offset = context->Ebp;
+ stack_frame.AddrPC.Offset = CTX_PC(*context);
+ stack_frame.AddrStack.Offset = CTX_STACK(*context);
+ stack_frame.AddrFrame.Offset = CTX_FRAME(*context);
}
+#if defined(_WIN64) || defined(__x86_64__)
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 Walk a mutable copy seeded from the frame this walk
+ // starts at -- StackWalk64 updates it, so never hand it the caller's context.
+ CONTEXT walk_ctx;
+ if (context)
+ walk_ctx = *context;
+ else
+ RtlCaptureContext(&walk_ctx);
+ CTX_PC(walk_ctx) = stack_frame.AddrPC.Offset;
+ CTX_STACK(walk_ctx) = stack_frame.AddrStack.Offset;
+ CTX_FRAME(walk_ctx) = stack_frame.AddrFrame.Offset;
+#endif
+
int pointer_index = 0;
/*
** Walk the stack by the requested number of return address iterations.
*/
for (int i = 0; i < num_addresses + 1; i++) {
- if (_StackWalk(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(), GetCurrentThread(), &stack_frame, nullptr, nullptr, _SymFunctionTableAccess, _SymGetModuleBase, nullptr)) {
+ if (_StackWalk(CTX_STACKWALK_MACHINE, GetCurrentProcess(), GetCurrentThread(), &stack_frame, RTS_STACKWALK_CONTEXT, nullptr, _SymFunctionTableAccess, _SymGetModuleBase, nullptr)) {
/*
** First result will always be the return address we were called from.
@@ -1281,7 +1568,7 @@ int Stack_Walk(unsigned long *return_addresses, int num_addresses, CONTEXT *cont
if (i==0 && context == nullptr) {
continue;
}
- unsigned long return_address = stack_frame.AddrReturn.Offset;
+ uintptr_t return_address = stack_frame.AddrReturn.Offset;
return_addresses[pointer_index++] = return_address;
} else {
break;
diff --git a/Core/Libraries/Source/WWVegas/WWLib/Except.h b/Core/Libraries/Source/WWVegas/WWLib/Except.h
index 3cc178cc373..78ef6a94a89 100644
--- a/Core/Libraries/Source/WWVegas/WWLib/Except.h
+++ b/Core/Libraries/Source/WWVegas/WWLib/Except.h
@@ -39,6 +39,7 @@
#if defined(_WIN32)
#include "win.h"
+#include
/*
** Forward Declarations
*/
@@ -46,7 +47,12 @@ typedef struct _EXCEPTION_POINTERS EXCEPTION_POINTERS;
typedef struct _CONTEXT CONTEXT;
int Exception_Handler(int exception_code, EXCEPTION_POINTERS *e_info);
-int Stack_Walk(unsigned long *return_addresses, int num_addresses, CONTEXT *context = nullptr);
+// return_addresses is uintptr_t, not unsigned long: it holds raw return
+// addresses captured off the stack (see Except.cpp's Stack_Walk), and on
+// x86-64 (LLP64) `long` stays 32 bits while an address is 64. Stack_Walk has
+// exactly one call site (Except.cpp), so this is a self-contained widening,
+// not a public-ABI change in practice.
+int Stack_Walk(uintptr_t *return_addresses, int num_addresses, CONTEXT *context = nullptr);
bool Lookup_Symbol(void *code_ptr, char *symbol, int &displacement);
void Load_Image_Helper();
void Register_Thread_ID(unsigned long thread_id, char *thread_name, bool main = false);
diff --git a/Core/Libraries/Source/WWVegas/WWLib/registry.cpp b/Core/Libraries/Source/WWVegas/WWLib/registry.cpp
index 94eeee4073c..13b41e97df0 100644
--- a/Core/Libraries/Source/WWVegas/WWLib/registry.cpp
+++ b/Core/Libraries/Source/WWVegas/WWLib/registry.cpp
@@ -65,7 +65,7 @@ RegistryClass::RegistryClass( const char * sub_key, bool create ) :
IsValid( false )
{
HKEY key;
- assert( sizeof(HKEY) == sizeof(int) );
+ assert( sizeof(HKEY) == sizeof(Key) );
LONG result = -1;
@@ -79,7 +79,7 @@ RegistryClass::RegistryClass( const char * sub_key, bool create ) :
if (ERROR_SUCCESS == result) {
IsValid = true;
- Key = (int)key;
+ Key = (uintptr_t)key;
}
}
diff --git a/Core/Libraries/Source/WWVegas/WWLib/registry.h b/Core/Libraries/Source/WWVegas/WWLib/registry.h
index deccd441252..5705e6362c5 100644
--- a/Core/Libraries/Source/WWVegas/WWLib/registry.h
+++ b/Core/Libraries/Source/WWVegas/WWLib/registry.h
@@ -39,6 +39,9 @@
#include "Vector.h"
#include "wwstring.h"
#include "widestring.h"
+// Only pulls the pointer-sized-int typedef (uintptr_t); avoids dragging
+// BaseTypeCore.h's warning-as-error pragmas into a file that never had them.
+#include
class INIClass;
@@ -107,7 +110,7 @@ class RegistryClass {
static void Save_Registry_Values(HKEY key, char *path, INIClass *ini);
- int Key;
+ uintptr_t Key;
bool IsValid;
//
diff --git a/Core/Libraries/Source/WWVegas/WWSaveLoad/persistfactory.h b/Core/Libraries/Source/WWVegas/WWSaveLoad/persistfactory.h
index 2281a626520..406378be3e0 100644
--- a/Core/Libraries/Source/WWVegas/WWSaveLoad/persistfactory.h
+++ b/Core/Libraries/Source/WWVegas/WWSaveLoad/persistfactory.h
@@ -42,6 +42,9 @@
#include "WWDebug/wwdebug.h"
#include "saveload.h"
#include "persist.h"
+// Only pulls the pointer-sized-int typedef (uintptr_t); avoids dragging
+// BaseTypeCore.h's warning-as-error pragmas into a file that never had them.
+#include
/*
** PersistFactoryClass
@@ -77,6 +80,13 @@ class PersistFactoryClass
** object. Simply instantiate a single static instance of this template with the
** type and chunkid in the .cpp file of your class.
*/
+// The on-disk width of the object-identity token is fixed by the retail save
+// format at 4 bytes (uint32, i.e. `unsigned long` on our Windows targets).
+// Changing it changes the format. Not guarded by a static_assert here: under
+// VC6, Dependencies/Utility/Utility/CppMacros.h defines static_assert(expr,msg)
+// as empty, so it would silently compile away on exactly the toolchain this
+// project builds for first, giving no real protection.
+
template class SimplePersistFactoryClass : public PersistFactoryClass
{
public:
@@ -100,11 +110,18 @@ template PersistClass *
SimplePersistFactoryClass::Load(ChunkLoadClass & cload) const
{
T * new_obj = W3DNEW T;
- T * old_obj = nullptr;
+
+ // Read exactly what Save wrote: a fixed-width 4-byte identity token, not
+ // sizeof(T *). On x86-64 sizeof(T *) is 8, so reading sizeof(T *) here would
+ // consume four bytes the writer never wrote and desynchronize the chunk
+ // stream. The token is not a real pointer (see the TODO in Save, below); it
+ // is carried through as an opaque value and only ever compared for equality
+ // by Register_Pointer's pointer table.
+ uint32 old_obj_token = 0;
cload.Open_Chunk();
WWASSERT(cload.Cur_Chunk_ID() == SIMPLEFACTORY_CHUNKID_OBJPOINTER);
- cload.Read(&old_obj,sizeof(T *));
+ cload.Read(&old_obj_token,sizeof(uint32));
cload.Close_Chunk();
cload.Open_Chunk();
@@ -112,6 +129,7 @@ SimplePersistFactoryClass::Load(ChunkLoadClass & cload) const
new_obj->Load(cload);
cload.Close_Chunk();
+ void * old_obj = (void *)(uintptr_t)old_obj_token;
SaveLoadSystemClass::Register_Pointer(old_obj,new_obj);
return new_obj;
}
@@ -120,7 +138,12 @@ SimplePersistFactoryClass::Load(ChunkLoadClass & cload) const
template void
SimplePersistFactoryClass::Save(ChunkSaveClass & csave,PersistClass * obj) const
{
- uint32 objptr = (uint32)obj;
+ // TODO(x64-savegame-format): on x86-64 this truncates a 64-bit pointer to a
+ // 32-bit on-disk identity token, so two live objects can collide and pointer
+ // fixup can bind the wrong object on load. The on-disk width is fixed by the
+ // retail save format and cannot be widened here without breaking it.
+ // The write path has no callers in this repository; writer and reader still agree on the width.
+ uint32 objptr = (uint32)(uintptr_t)obj;
csave.Begin_Chunk(SIMPLEFACTORY_CHUNKID_OBJPOINTER);
csave.Write(&objptr,sizeof(uint32));
csave.End_Chunk();
diff --git a/Core/Libraries/Source/debug/debug_debug.cpp b/Core/Libraries/Source/debug/debug_debug.cpp
index 291a06aff27..3cb698b9865 100644
--- a/Core/Libraries/Source/debug/debug_debug.cpp
+++ b/Core/Libraries/Source/debug/debug_debug.cpp
@@ -35,6 +35,9 @@
#include
#include
#include // needed for placement new prototype
+// Only pulls the pointer-sized-int typedef (uintptr_t); avoids dragging
+// BaseTypeCore.h's warning-as-error pragmas into a file that never had them.
+#include
// a little dummy variable that makes the linker actually include
// us...
@@ -73,7 +76,7 @@ Debug::LogDescription::LogDescription(const char *fileOrGroup, const char *descr
Debug Debug::Instance;
// more class static members
-unsigned Debug::curStackFrame;
+uintptr_t Debug::curStackFrame;
// this constructor is empty on purpose because all construction
// work is done in PreStaticInit (and some in PostStaticInit)
@@ -305,21 +308,40 @@ bool Debug::SkipNext()
// do not implement this function inline, we do need
// a valid frame pointer here!
- unsigned help;
-#if defined(_MSC_VER)
+ // uintptr_t is `unsigned int` (4 bytes) on the VC6 32-bit target, so
+ // the _asm block below -- which needs a 4-byte destination to match eax --
+ // is byte-identical to the original `unsigned help;` version there.
+ uintptr_t help;
+#if defined(_MSC_VER) && defined(_M_IX86)
_asm
{
mov eax,[ebp+4] // return address
mov help,eax
};
#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(_M_IX86))
- // GCC/Clang inline assembly for x86-32
+ // GCC/Clang inline assembly for x86-32. Kept as its own arm, gated the same
+ // way as the equivalent blocks in Except.cpp and debug_stack.cpp, so 32-bit
+ // GCC/Clang codegen for this function is unchanged from before this x64
+ // port touched it -- an earlier version of this arm covered every
+ // GCC/Clang architecture (including x86-64, where it does not apply) and
+ // was narrowed here to match its siblings rather than left broad.
__asm__ __volatile__(
"mov 4(%%ebp), %0"
: "=r"(help)
:
: "memory"
);
+#elif defined(__GNUC__) || defined(__clang__)
+ // Everything else GCC/Clang targets (x86-64 in practice): unlike
+ // Except.cpp's Stack_Walk and debug_stack.cpp's captured-register path,
+ // this function only ever needs the immediate caller's return address, not
+ // a full register set to seed a multi-frame StackWalk64. There is no
+ // matching ebp-relative asm trick on x86-64 (no frame-pointer-at-fixed-
+ // offset convention to rely on), so __builtin_return_address(0) -- GCC/
+ // Clang's portable spelling of "caller's return address" on every
+ // architecture they target -- is used directly instead of a CONTEXT-capture
+ // dance that would be overkill for a single value.
+ help = (uintptr_t)__builtin_return_address(0);
#else
#error "Unsupported compiler or architecture for inline assembly"
#endif
@@ -899,8 +921,19 @@ Debug& Debug::operator<<(const void *ptr)
(*this) << "ptr:";
if (ptr)
{
+ // Full pointer width, not the low 32 bits: a crash report's register
+ // dump is already 16 hex digits on x64 (operator<<(unsigned __int64)
+ // below), so an address truncated to 8 digits here would silently drop
+ // the high half next to registers that don't. 32-bit output is
+ // unchanged -- uintptr_t is unsigned int there, so this arm still
+ // resolves to the exact same _ultoa(...,help,16) call as before.
+#if defined(_WIN64) || defined(__x86_64__)
+ char help[64+1]; // sign, 64 digits, NUL -- matches operator<<(unsigned __int64)'s buffer
+ (*this) << "0x" << _ui64toa((unsigned __int64)(uintptr_t)ptr,help,16);
+#else
char help[9];
- (*this) << "0x" << _ultoa((unsigned long)ptr,help,16);
+ (*this) << "0x" << _ultoa((unsigned long)(uintptr_t)ptr,help,16);
+#endif
}
else
(*this) << "null";
@@ -931,8 +964,15 @@ Debug& Debug::operator<<(const MemDump &dump)
for (unsigned i=0;iinteger
+ // conversion on every target (it used to truncate through `unsigned` on
+ // x64; that guard is gone now that the hash key is pointer-width).
+ FrameHashEntry *e=Instance.LookupFrame((uintptr_t)fileOrGroup);
if (!e)
- e=Instance.AddFrameEntry((unsigned)fileOrGroup,FrameTypeLog,fileOrGroup,0);
+ e=Instance.AddFrameEntry((uintptr_t)fileOrGroup,FrameTypeLog,fileOrGroup,0);
if (e->status==Unknown)
Instance.UpdateFrameStatus(*e);
return e->status==NoSkip;
@@ -1195,7 +1239,7 @@ void Debug::Update()
}
}
-Debug::FrameHashEntry* Debug::AddFrameEntry(unsigned addr, unsigned type,
+Debug::FrameHashEntry* Debug::AddFrameEntry(uintptr_t addr, unsigned type,
const char *fileOrGroup, int line)
{
__ASSERT(LookupFrame(addr)==nullptr);
diff --git a/Core/Libraries/Source/debug/debug_debug.h b/Core/Libraries/Source/debug/debug_debug.h
index b03aea1994b..132d11e41aa 100644
--- a/Core/Libraries/Source/debug/debug_debug.h
+++ b/Core/Libraries/Source/debug/debug_debug.h
@@ -29,6 +29,12 @@
#pragma once
+// Only pulls the pointer-sized-int typedef (uintptr_t); avoids dragging
+// BaseTypeCore.h's warning-as-error pragmas into a file that never had them.
+// debug_debug.h is included by nearly every debug translation unit, so any
+// warning-as-error pragma pulled in here becomes effectively global.
+#include
+
/**
\class Debug debug.h
@@ -872,7 +878,7 @@ DLOG( "My HResult is: " << Debug::HResult(SomeHRESULTValue) << "\n" );
CmdInterfaceListEntry *firstCmdGroup;
/// \internal current stack frame (used by SkipNext)
- static unsigned curStackFrame;
+ static uintptr_t curStackFrame;
/** \internal
@@ -920,7 +926,7 @@ DLOG( "My HResult is: " << Debug::HResult(SomeHRESULTValue) << "\n" );
FrameHashEntry *next;
/// frame address
- unsigned frameAddr;
+ uintptr_t frameAddr;
/// frame type (FrameTypeAssert, FrameTypeCheck, or FrameTypeLog)
unsigned frameType;
@@ -960,7 +966,7 @@ DLOG( "My HResult is: " << Debug::HResult(SomeHRESULTValue) << "\n" );
\param addr frame address
\return FrameHashEntry found or 0 if nothing found
*/
- __forceinline FrameHashEntry *LookupFrame(unsigned addr)
+ __forceinline FrameHashEntry *LookupFrame(uintptr_t addr)
{
for (FrameHashEntry *e=frameHash[addr%FRAME_HASH_SIZE];e;e=e->next)
if (e->frameAddr==addr)
@@ -980,7 +986,7 @@ DLOG( "My HResult is: " << Debug::HResult(SomeHRESULTValue) << "\n" );
\param line line number
\return the entry just added
*/
- FrameHashEntry *AddFrameEntry(unsigned addr, unsigned type,
+ FrameHashEntry *AddFrameEntry(uintptr_t addr, unsigned type,
const char *fileOrGroup, int line);
/** \internal
@@ -1002,7 +1008,7 @@ DLOG( "My HResult is: " << Debug::HResult(SomeHRESULTValue) << "\n" );
\param line line number
\return the entry just added (or the already existing entry)
*/
- FrameHashEntry *GetFrameEntry(unsigned addr, unsigned type,
+ FrameHashEntry *GetFrameEntry(uintptr_t addr, unsigned type,
const char *fileOrGroup, int line)
{
FrameHashEntry *e=LookupFrame(addr);
diff --git a/Core/Libraries/Source/debug/debug_except.cpp b/Core/Libraries/Source/debug/debug_except.cpp
index a8c5a286757..66333b33612 100644
--- a/Core/Libraries/Source/debug/debug_except.cpp
+++ b/Core/Libraries/Source/debug/debug_except.cpp
@@ -30,6 +30,7 @@
#include "internal_except.h"
#include
#include
+#include "Lib/arch_context.h"
DebugExceptionhandler::DebugExceptionhandler()
{
@@ -110,7 +111,7 @@ void DebugExceptionhandler::LogExceptionLocation(Debug &dbg, struct _EXCEPTION_P
struct _CONTEXT &ctx=*exptr->ContextRecord;
char buf[512];
- DebugStackwalk::Signature::GetSymbol(ctx.Eip,buf,sizeof(buf));
+ DebugStackwalk::Signature::GetSymbol(CTX_PC(ctx),buf,sizeof(buf));
dbg << "Exception occured at\n" << buf << ".";
}
@@ -120,15 +121,15 @@ void DebugExceptionhandler::LogRegisters(Debug &dbg, struct _EXCEPTION_POINTERS
dbg << Debug::FillChar('0')
<< Debug::Hex()
- << "EAX:" << Debug::Width(8) << ctx.Eax
- << " EBX:" << Debug::Width(8) << ctx.Ebx
- << " ECX:" << Debug::Width(8) << ctx.Ecx << "\n"
- << "EDX:" << Debug::Width(8) << ctx.Edx
- << " ESI:" << Debug::Width(8) << ctx.Esi
- << " EDI:" << Debug::Width(8) << ctx.Edi << "\n"
- << "EIP:" << Debug::Width(8) << ctx.Eip
- << " ESP:" << Debug::Width(8) << ctx.Esp
- << " EBP:" << Debug::Width(8) << ctx.Ebp << "\n"
+ << "EAX:" << Debug::Width(CTX_REG_WIDTH) << CTX_AX(ctx)
+ << " EBX:" << Debug::Width(CTX_REG_WIDTH) << CTX_BX(ctx)
+ << " ECX:" << Debug::Width(CTX_REG_WIDTH) << CTX_CX(ctx) << "\n"
+ << "EDX:" << Debug::Width(CTX_REG_WIDTH) << CTX_DX(ctx)
+ << " ESI:" << Debug::Width(CTX_REG_WIDTH) << CTX_SI(ctx)
+ << " EDI:" << Debug::Width(CTX_REG_WIDTH) << CTX_DI(ctx) << "\n"
+ << "EIP:" << Debug::Width(CTX_REG_WIDTH) << CTX_PC(ctx)
+ << " ESP:" << Debug::Width(CTX_REG_WIDTH) << CTX_STACK(ctx)
+ << " EBP:" << Debug::Width(CTX_REG_WIDTH) << CTX_FRAME(ctx) << "\n"
<< "Flags:" << Debug::Bin() << Debug::Width(32) << ctx.EFlags << Debug::Hex() << "\n"
<< "CS:" << Debug::Width(4) << ctx.SegCs
<< " DS:" << Debug::Width(4) << ctx.SegDs
@@ -148,6 +149,44 @@ void DebugExceptionhandler::LogFPURegisters(Debug &dbg, struct _EXCEPTION_POINTE
return;
}
+#if defined(_WIN64) || defined(__x86_64__)
+ // x86-64: FPU/SSE state is in CONTEXT.FltSave (an XMM_SAVE_AREA32), not
+ // the 32-bit FLOATING_SAVE_AREA. ControlWord/StatusWord/TagWord/
+ // ErrorOffset/ErrorSelector/DataOffset/DataSelector still exist under
+ // the same names; there is no Cr0NpxState, and each ST(i) register
+ // lives in the low 10 bytes of a 16-byte FloatRegisters[] slot rather
+ // than a flat RegisterArea, mirroring what the 32-bit block below
+ // reports.
+ XMM_SAVE_AREA32 &flt=ctx.FltSave;
+ dbg << Debug::Bin() << Debug::FillChar('0')
+ << "CW:" << Debug::Width(16) << (flt.ControlWord&0xffff) << "\n"
+ << "SW:" << Debug::Width(16) << (flt.StatusWord&0xffff) << "\n"
+ << "TW:" << Debug::Width(16) << (flt.TagWord&0xffff) << "\n"
+ << Debug::Hex()
+ << "ErrOfs: " << Debug::Width(8) << flt.ErrorOffset
+ << " ErrSel: " << Debug::Width(8) << flt.ErrorSelector << "\n"
+ << "DataOfs: " << Debug::Width(8) << flt.DataOffset
+ << " DataSel: " << Debug::Width(8) << flt.DataSelector << "\n"
+ ;
+
+ for (unsigned k=0;k<8;++k)
+ {
+ dbg << Debug::Dec() << "ST(" << k << ") ";
+ dbg.SetPrefixAndRadix("",16);
+
+ BYTE *value=(BYTE*)&flt.FloatRegisters[k];
+ for (unsigned i=0;i<10;i++)
+ dbg << Debug::Width(2) << value[i];
+
+ // TheSuperHackers @refactor Replaced MSVC inline assembly with portable C++ cast for MinGW compatibility
+ // Convert from temporary real (10 byte) to double (8 bytes).
+ // On x86, long double is the 10-byte x87 format, so we can just cast.
+ double fpVal = (double)(*(long double*)value);
+ dbg << " " << fpVal;
+
+ dbg << "\n";
+ }
+#else
FLOATING_SAVE_AREA &flt=ctx.FloatSave;
dbg << Debug::Bin() << Debug::FillChar('0')
<< "CW:" << Debug::Width(16) << (flt.ControlWord&0xffff) << "\n"
@@ -180,6 +219,7 @@ void DebugExceptionhandler::LogFPURegisters(Debug &dbg, struct _EXCEPTION_POINTE
dbg << "\n";
}
+#endif
dbg << Debug::FillChar() << Debug::Dec();
}
@@ -195,7 +235,9 @@ static char regInfo[1024],verInfo[256];
// and this saves us from doing a stack walk twice
static DebugStackwalk::Signature sig;
-static BOOL CALLBACK ExceptionDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+// DLGPROC returns INT_PTR (64-bit on Win64). INT_PTR is plain int on 32-bit
+// Windows, so this is a no-op signature change for the VC6/mingw-i686 builds.
+static INT_PTR CALLBACK ExceptionDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
@@ -240,7 +282,7 @@ static BOOL CALLBACK ExceptionDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
// address
struct _CONTEXT &ctx=*exPtrs->ContextRecord;
- DebugStackwalk::Signature::GetSymbol(ctx.Eip,regInfo,sizeof(regInfo));
+ DebugStackwalk::Signature::GetSymbol(CTX_PC(ctx),regInfo,sizeof(regInfo));
SendDlgItemMessage(hWnd,102,WM_SETTEXT,0,(LPARAM)regInfo);
// stack
@@ -396,7 +438,7 @@ LONG __stdcall DebugExceptionhandler::ExceptionFilter(struct _EXCEPTION_POINTERS
dbg.m_stackWalk.StackWalk(sig,pExPtrs->ContextRecord);
dbg << sig << "\n";
- dbg << "Bytes around EIP:" << Debug::MemDump::Char(((char *)(pExPtrs->ContextRecord->Eip))-32,80);
+ dbg << "Bytes around EIP:" << Debug::MemDump::Char(((char *)(CTX_PC(*pExPtrs->ContextRecord)))-32,80);
dbg.FlushOutput();
diff --git a/Core/Libraries/Source/debug/debug_stack.cpp b/Core/Libraries/Source/debug/debug_stack.cpp
index 8e0aca49557..2710781c507 100644
--- a/Core/Libraries/Source/debug/debug_stack.cpp
+++ b/Core/Libraries/Source/debug/debug_stack.cpp
@@ -32,6 +32,41 @@
#include
#include "WWLib/stringex.h"
#include
+#include
+// Only pulls the pointer-sized-int typedef (uintptr_t); avoids dragging
+// BaseTypeCore.h's warning-as-error pragmas into a file that never had them.
+#include
+#include "Lib/arch_context.h"
+
+// TheSuperHackers @fix MeneerHaas 02/09/2026 StackWalk64 requires a ContextRecord on AMD64 (it is optional on x86) and
+// updates it while unwinding, so the walker below seeds a mutable local walk_ctx and passes it
+// through this macro. On 32-bit it expands to the retail nullptr, leaving that arm unchanged.
+#if defined(_WIN64) || defined(__x86_64__)
+#define RTS_STACKWALK_CONTEXT (&walk_ctx)
+#else
+#define RTS_STACKWALK_CONTEXT nullptr
+#endif
+
+// imagehlp.h (via dbghelp.h's psdk_inc/_dbg_common.h) #defines StackWalk to
+// StackWalk64 on 64-bit builds, because _IMAGEHLP64 is set whenever _WIN64
+// is defined. DebugStackwalk::StackWalk below is our own class method, not
+// a direct call into the Win32 API (that goes through the gDbg._StackWalk
+// function pointer instead), so the platform macro must not be allowed to
+// rewrite its name. debug_stack.h is included above, before this macro
+// exists, so the class declaration is unaffected; without this #undef the
+// out-of-line definition further down would be silently renamed to
+// StackWalk64 and no longer match its own declaration.
+//
+// This fix is order-dependent: it only protects code that appears *after*
+// this point in this translation unit. If a future #include added below
+// this line (directly or transitively) pulls in /
+// again, or otherwise redefines StackWalk, the mismatch this guards
+// against comes back with no compiler warning -- #undef is silent by
+// design. Keep this as the last DbgHelp-related include in the file, or
+// re-apply the #undef immediately after whatever reintroduces the macro.
+#ifdef StackWalk
+#undef StackWalk
+#endif
// Definitions to allow run-time linking to the dbghelp.dll functions.
@@ -46,16 +81,60 @@ static union
{
#include "debug_stack.inl"
};
- unsigned funcPtr[1];
+ // Overlays the struct above, whose members are actual function pointers
+ // (8 bytes on Win64). Must be pointer-sized or the aliasing/stride used
+ // by InitDbghelp() below only covers half of each slot on 64-bit.
+ uintptr_t funcPtr[1];
} gDbg;
#undef DBGHELP
+// GetProcAddress'd against DBGHELP.DLL by InitDbghelp() below, one name per
+// DBGHELP() entry in debug_stack.inl, in the same order as the gDbg struct
+// above.
+//
+// 64-bit dbghelp.dll only exports the ...64 form of an entry point whose
+// address parameter is DWORD64 -- StackWalk, SymFunctionTableAccess,
+// SymGetModuleBase, SymGetSymFromAddr and SymGetLineFromAddr here.
+// GetProcAddress with the un-suffixed name returns NULL for those on x64,
+// which would leave the matching gDbg._SymXxx pointer null and silently
+// disable that part of the stack walker rather than fail to compile.
+// SymInitialize, SymGetOptions, SymSetOptions and SymCleanup take no address
+// parameter and are exported under the same name on every architecture
+// (verified against mingw-w64's psdk_inc/_dbg_common.h: no #define
+// redirects them under _IMAGEHLP64), so they are unchanged.
+#if defined(_WIN64) || defined(__x86_64__)
+#define DBGHELP(name,ret,par) DBGHELP_APINAME_##name,
+#define DBGHELP_APINAME_SymInitialize "SymInitialize"
+#define DBGHELP_APINAME_SymGetOptions "SymGetOptions"
+#define DBGHELP_APINAME_SymSetOptions "SymSetOptions"
+#define DBGHELP_APINAME_StackWalk "StackWalk64"
+#define DBGHELP_APINAME_SymFunctionTableAccess "SymFunctionTableAccess64"
+#define DBGHELP_APINAME_SymGetModuleBase "SymGetModuleBase64"
+#define DBGHELP_APINAME_SymGetSymFromAddr "SymGetSymFromAddr64"
+#define DBGHELP_APINAME_SymGetLineFromAddr "SymGetLineFromAddr64"
+#define DBGHELP_APINAME_SymCleanup "SymCleanup"
+static char const *const DebughelpFunctionNames[] =
+{
+#include "debug_stack.inl"
+ nullptr
+};
+#undef DBGHELP_APINAME_SymInitialize
+#undef DBGHELP_APINAME_SymGetOptions
+#undef DBGHELP_APINAME_SymSetOptions
+#undef DBGHELP_APINAME_StackWalk
+#undef DBGHELP_APINAME_SymFunctionTableAccess
+#undef DBGHELP_APINAME_SymGetModuleBase
+#undef DBGHELP_APINAME_SymGetSymFromAddr
+#undef DBGHELP_APINAME_SymGetLineFromAddr
+#undef DBGHELP_APINAME_SymCleanup
+#else
#define DBGHELP(name,ret,par) #name,
static char const *const DebughelpFunctionNames[] =
{
#include "debug_stack.inl"
nullptr
};
+#endif
#undef DBGHELP
// local dbghelp.dll module handle
@@ -89,11 +168,11 @@ static void InitDbghelp()
return;
// Get function addresses
- unsigned *funcptr=gDbg.funcPtr;
+ uintptr_t *funcptr=gDbg.funcPtr;
unsigned k=0;
for (;DebughelpFunctionNames[k];++k,++funcptr)
{
- *funcptr=(unsigned)GetProcAddress(g_dbghelp,DebughelpFunctionNames[k]);
+ *funcptr=(uintptr_t)GetProcAddress(g_dbghelp,DebughelpFunctionNames[k]);
if (!*funcptr)
break;
}
@@ -135,13 +214,13 @@ DebugStackwalk::Signature& DebugStackwalk::Signature::operator=(const Signature&
return *this;
}
-unsigned DebugStackwalk::Signature::GetAddress(int n) const
+uintptr_t DebugStackwalk::Signature::GetAddress(int n) const
{
DFAIL_IF_MSG(n<0||n>=MAX_ADDR,n << "/" << MAX_ADDR) return 0;
return m_addr[n];
}
-void DebugStackwalk::Signature::GetSymbol(unsigned addr, char *buf, unsigned bufSize)
+void DebugStackwalk::Signature::GetSymbol(uintptr_t addr, char *buf, unsigned bufSize)
{
DFAIL_IF(!buf) return;
DFAIL_IF(bufSize<64||bufSize>=0x80000000) return;
@@ -150,10 +229,22 @@ void DebugStackwalk::Signature::GetSymbol(unsigned addr, char *buf, unsigned buf
char *bufEnd=buf+bufSize;
*buf=0;
- buf+=wsprintf(buf,"%08x",addr);
+#if defined(_WIN64) || defined(__x86_64__)
+ // sprintf (CRT), not wsprintf (User32's own limited formatter, used for
+ // every other format string in this function): wsprintf's documented
+ // format support does not include a 64-bit-width specifier, and this is
+ // the one field in this function that can actually need one.
+ buf+=sprintf(buf,"%016llX",(unsigned long long)addr);
+#else
+ buf+=wsprintf(buf,"%08x",(unsigned)addr);
+#endif
// determine module
- unsigned modBase=gDbg._SymGetModuleBase((HANDLE)GetCurrentProcessId(),addr);
+ // Pointer-width, not `unsigned`: _SymGetModuleBase resolves to
+ // SymGetModuleBase64 on x64 (see debug_stack.inl) and returns a DWORD64;
+ // truncating it here would corrupt every `addr-modBase` relative offset
+ // computed below.
+ uintptr_t modBase=gDbg._SymGetModuleBase((HANDLE)GetCurrentProcessId(),addr);
if (!modBase)
{
strcpy(buf," (unknown module)");
@@ -161,7 +252,7 @@ void DebugStackwalk::Signature::GetSymbol(unsigned addr, char *buf, unsigned buf
}
// illegal code ptr?
- if (IsBadReadPtr((void *)addr,4)||IsBadCodePtr((FARPROC)addr))
+ if (IsBadReadPtr((void *)addr,sizeof(addr))||IsBadCodePtr((FARPROC)addr))
{
strcpy(buf," (invalid code addr)");
return;
@@ -177,7 +268,12 @@ void DebugStackwalk::Signature::GetSymbol(unsigned addr, char *buf, unsigned buf
buf+=strlen(buf);
if (bufEnd-buf<32)
return;
- buf+=wsprintf(buf,"+0x%x",addr-modBase);
+ // Cast to unsigned: a module-relative offset is well under 4GB in
+ // practice (it's an offset within a single loaded module, not an
+ // absolute address), and wsprintf's "%x" is a 32-bit format regardless
+ // of argument width -- passing the full uintptr_t here would mismatch
+ // the format on x64.
+ buf+=wsprintf(buf,"+0x%x",(unsigned)(addr-modBase));
// determine symbol
PIMAGEHLP_SYMBOL symPtr=(PIMAGEHLP_SYMBOL)symbolBuffer;
@@ -185,8 +281,19 @@ void DebugStackwalk::Signature::GetSymbol(unsigned addr, char *buf, unsigned buf
symPtr->SizeOfStruct=sizeof(IMAGEHLP_SYMBOL);
symPtr->MaxNameLength=sizeof(symbolBuffer)-sizeof(IMAGEHLP_SYMBOL);
DWORD displacement;
+#if defined(_WIN64) || defined(__x86_64__)
+ // SymGetSymFromAddr64's Displacement out-param is PDWORD64; &displacement
+ // (DWORD, 4 bytes) would overflow. Capture into a properly sized local
+ // and narrow into displacement, which is then reused below for the
+ // SymGetLineFromAddr call, whose Displacement stays PDWORD on x64.
+ DWORD64 displacement64;
+ if (!gDbg._SymGetSymFromAddr((HANDLE)GetCurrentProcessId(),addr,&displacement64,symPtr))
+ return;
+ displacement=(DWORD)displacement64;
+#else
if (!gDbg._SymGetSymFromAddr((HANDLE)GetCurrentProcessId(),addr,&displacement,symPtr))
return;
+#endif
if ((unsigned int)(bufEnd-buf)Name)+16)
return;
buf+=wsprintf(buf,", %s+0x%x",symPtr->Name,displacement);
@@ -206,7 +313,7 @@ void DebugStackwalk::Signature::GetSymbol(unsigned addr, char *buf, unsigned buf
buf+=wsprintf(buf,", %s:%i+0x%x",p,line.LineNumber,displacement);
}
-void DebugStackwalk::Signature::GetSymbol(unsigned addr,
+void DebugStackwalk::Signature::GetSymbol(uintptr_t addr,
char *bufMod, unsigned sizeMod, unsigned *relMod,
char *bufSym, unsigned sizeSym, unsigned *relSym,
char *bufFile, unsigned sizeFile, unsigned *linePtr, unsigned *relLine)
@@ -226,8 +333,9 @@ void DebugStackwalk::Signature::GetSymbol(unsigned addr,
DFAIL_IF(bufSym&&sizeSym<16) return;
DFAIL_IF(bufFile&&sizeFile<16) return;
- // determine module
- unsigned modBase=gDbg._SymGetModuleBase((HANDLE)GetCurrentProcessId(),addr);
+ // determine module (see the other GetSymbol overload's comment: pointer-
+ // width, not `unsigned`, since this resolves to SymGetModuleBase64 on x64)
+ uintptr_t modBase=gDbg._SymGetModuleBase((HANDLE)GetCurrentProcessId(),addr);
if (!modBase)
{
if (bufMod)
@@ -238,7 +346,7 @@ void DebugStackwalk::Signature::GetSymbol(unsigned addr,
}
// illegal code ptr?
- if (IsBadReadPtr((void *)addr,4)||IsBadCodePtr((FARPROC)addr))
+ if (IsBadReadPtr((void *)addr,sizeof(addr))||IsBadCodePtr((FARPROC)addr))
{
if (bufMod)
strcpy(bufMod,"(inv code addr)");
@@ -256,8 +364,11 @@ void DebugStackwalk::Signature::GetSymbol(unsigned addr,
p=p?p+1:symbolBuffer;
strlcpy(bufMod,p,sizeMod);
}
+ // relMod is `unsigned *`, unchanged: a module-relative offset is well
+ // under 4GB in practice, unlike the absolute addr/modBase this is derived
+ // from.
if (relMod)
- *relMod=addr-modBase;
+ *relMod=(unsigned)(addr-modBase);
// determine symbol
if (bufSym)
@@ -267,8 +378,17 @@ void DebugStackwalk::Signature::GetSymbol(unsigned addr,
symPtr->SizeOfStruct=sizeof(IMAGEHLP_SYMBOL);
symPtr->MaxNameLength=sizeof(symbolBuffer)-sizeof(IMAGEHLP_SYMBOL);
DWORD displacement;
+#if defined(_WIN64) || defined(__x86_64__)
+ // See the comment on the first _SymGetSymFromAddr call above: its
+ // Displacement out-param is PDWORD64 on x64.
+ DWORD64 displacement64;
+ if (gDbg._SymGetSymFromAddr((HANDLE)GetCurrentProcessId(),addr,&displacement64,symPtr))
+ {
+ displacement=(DWORD)displacement64;
+#else
if (gDbg._SymGetSymFromAddr((HANDLE)GetCurrentProcessId(),addr,&displacement,symPtr))
{
+#endif
strlcpy(bufSym,symPtr->Name,sizeSym);
if (relSym)
*relSym=displacement;
@@ -356,15 +476,19 @@ int DebugStackwalk::StackWalk(Signature &sig, struct _CONTEXT *ctx)
// Use the context struct if it was provided.
if (ctx)
{
- stackFrame.AddrPC.Offset = ctx->Eip;
- stackFrame.AddrStack.Offset = ctx->Esp;
- stackFrame.AddrFrame.Offset = ctx->Ebp;
+ stackFrame.AddrPC.Offset = CTX_PC(*ctx);
+ stackFrame.AddrStack.Offset = CTX_STACK(*ctx);
+ stackFrame.AddrFrame.Offset = CTX_FRAME(*ctx);
}
else
{
// walk stack back using current call chain
- unsigned long reg_eip, reg_ebp, reg_esp;
-#if defined(_MSC_VER)
+ // uintptr_t rather than unsigned long: these feed
+ // stackFrame.AddrPC/AddrFrame/AddrStack.Offset, which are DWORD64 in
+ // STACKFRAME64 (STACKFRAME becomes STACKFRAME64 on x64), and
+ // `unsigned long` stays 32 bits under Win64's LLP64 model.
+ uintptr_t reg_eip, reg_ebp, reg_esp;
+#if defined(_MSC_VER) && defined(_M_IX86)
__asm
{
here:
@@ -382,18 +506,41 @@ int DebugStackwalk::StackWalk(Signature &sig, struct _CONTEXT *ctx)
: "=r" (reg_eip), "=r" (reg_ebp), "=r" (reg_esp)
);
#else
-#error "Unsupported compiler or architecture for register capture"
+ // x86-64 and anything else: RtlCaptureContext fills a CONTEXT with the
+ // caller's register state -- the documented Win64 way to seed a
+ // StackWalk64, needing no inline assembly. Mirrors the eip/ebp/esp set
+ // this function captures (same register set as Except.cpp's
+ // Stack_Walk); the ctx-provided branch above already reads the same
+ // three fields via CTX_PC/CTX_FRAME/CTX_STACK.
+ CONTEXT capture_ctx;
+ RtlCaptureContext(&capture_ctx);
+ reg_eip = (uintptr_t)CTX_PC(capture_ctx);
+ reg_ebp = (uintptr_t)CTX_FRAME(capture_ctx);
+ reg_esp = (uintptr_t)CTX_STACK(capture_ctx);
#endif
stackFrame.AddrPC.Offset = reg_eip;
stackFrame.AddrStack.Offset = reg_esp;
stackFrame.AddrFrame.Offset = reg_ebp;
}
+#if defined(_WIN64) || defined(__x86_64__)
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 Walk a mutable copy seeded from the frame this walk
+ // starts at -- StackWalk64 updates it, so never hand it the caller's context.
+ CONTEXT walk_ctx;
+ if (ctx)
+ walk_ctx = *ctx;
+ else
+ RtlCaptureContext(&walk_ctx);
+ CTX_PC(walk_ctx) = stackFrame.AddrPC.Offset;
+ CTX_STACK(walk_ctx) = stackFrame.AddrStack.Offset;
+ CTX_FRAME(walk_ctx) = stackFrame.AddrFrame.Offset;
+#endif
+
// Walk the stack by the requested number of return address iterations.
bool skipFirst=!ctx;
while (sig.m_numAddr
+
/// \brief stack walker class (singleton)
class DebugStackwalk
{
@@ -56,7 +60,11 @@ class DebugStackwalk
unsigned m_numAddr;
/// addresses
- unsigned m_addr[MAX_ADDR];
+ // Pointer-width, not `unsigned`: callers pass CTX_PC(ctx), a DWORD64 on
+ // x64 (debug_except.cpp), and a 32-bit slot here would silently truncate
+ // every address stored, corrupting both signature dedup and symbol
+ // lookup on x64.
+ uintptr_t m_addr[MAX_ADDR];
public:
explicit Signature(): m_numAddr(0) {}
@@ -78,7 +86,7 @@ class DebugStackwalk
\param n index, 0..Size()-1
\return signature address
*/
- unsigned GetAddress(int n) const;
+ uintptr_t GetAddress(int n) const;
/**
\brief Strong ordering operator.
@@ -110,7 +118,7 @@ class DebugStackwalk
\param buf return buffer
\param bufSize size of return buffer, minimum is 64 bytes (256 recommended)
*/
- static void GetSymbol(unsigned addr, char *buf, unsigned bufSize);
+ static void GetSymbol(uintptr_t addr, char *buf, unsigned bufSize);
/**
\brief Determines symbol for given address.
@@ -127,7 +135,7 @@ class DebugStackwalk
\param line line number, may be nullptr
\param relLine relative address within line, may be nullptr
*/
- static void GetSymbol(unsigned addr,
+ static void GetSymbol(uintptr_t addr,
char *bufMod, unsigned sizeMod, unsigned *relMod,
char *bufSym, unsigned sizeSym, unsigned *relSym,
char *bufFile, unsigned sizeFile, unsigned *line, unsigned *relLine);
diff --git a/Core/Libraries/Source/debug/debug_stack.inl b/Core/Libraries/Source/debug/debug_stack.inl
index 7dbba8f7485..e51d0a42320 100644
--- a/Core/Libraries/Source/debug/debug_stack.inl
+++ b/Core/Libraries/Source/debug/debug_stack.inl
@@ -39,6 +39,23 @@ DBGHELP(StackWalk,
PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine,
PTRANSLATE_ADDRESS_ROUTINE TranslateAddress))
+// LPSTACKFRAME / PFUNCTION_TABLE_ACCESS_ROUTINE / PGET_MODULE_BASE_ROUTINE
+// above are #defined to their ...64 forms by dbghelp.h on 64-bit builds
+// (_IMAGEHLP64), so StackWalkType's signature already widens for free. These
+// next two entries hand-roll a DWORD address parameter instead of going
+// through those platform names, so gDbg._StackWalk's call site (which does
+// use the widened StackWalkType) rejects them as arguments unless widened to
+// match explicitly. VC6 (1998) predates the ...64 DbgHelp API, so the 32-bit
+// branch is kept exactly as it was.
+#if defined(_WIN64) || defined(__x86_64__)
+DBGHELP(SymFunctionTableAccess,
+ LPVOID,
+ (HANDLE hProcess, DWORD64 AddrBase))
+
+DBGHELP(SymGetModuleBase,
+ DWORD64,
+ (HANDLE hProcess, DWORD64 dwAddr))
+#else
DBGHELP(SymFunctionTableAccess,
LPVOID,
(HANDLE hProcess, DWORD AddrBase))
@@ -46,16 +63,44 @@ DBGHELP(SymFunctionTableAccess,
DBGHELP(SymGetModuleBase,
DWORD,
(HANDLE hProcess, DWORD dwAddr))
+#endif
+// Real SymGetSymFromAddr64: BOOL(HANDLE, DWORD64 qwAddr,
+// PDWORD64 pdwDisplacement, PIMAGEHLP_SYMBOL64 Symbol). Address and
+// Displacement are hand-rolled DWORD/LPDWORD, same class of gap as
+// SymFunctionTableAccess/SymGetModuleBase above, and Displacement is a
+// write target: leaving it 32-bit on x64 is a stack buffer overflow every
+// time a symbol is resolved (SymGetSymFromAddr64 writes 8 bytes through
+// it). Symbol needs no separate widening here: PIMAGEHLP_SYMBOL is
+// #defined to PIMAGEHLP_SYMBOL64 under _IMAGEHLP64 (imagehlp.h is already
+// included above this point in debug_stack.cpp), so it widens for free.
+#if defined(_WIN64) || defined(__x86_64__)
+DBGHELP(SymGetSymFromAddr,
+ BOOL,
+ (HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement,
+ PIMAGEHLP_SYMBOL Symbol))
+#else
DBGHELP(SymGetSymFromAddr,
BOOL,
(HANDLE hProcess, DWORD Address, LPDWORD Displacement,
PIMAGEHLP_SYMBOL Symbol))
+#endif
+// Real SymGetLineFromAddr64: BOOL(HANDLE, DWORD64 qwAddr,
+// PDWORD pdwDisplacement, PIMAGEHLP_LINE64 Line64) -- only the address
+// parameter widens; pdwDisplacement genuinely stays PDWORD (not a write
+// overflow), and Line widens for free the same way Symbol does above.
+#if defined(_WIN64) || defined(__x86_64__)
+DBGHELP(SymGetLineFromAddr,
+ BOOL,
+ (HANDLE hProcess, DWORD64 dwAddr, PDWORD pdwDisplacement,
+ PIMAGEHLP_LINE Line))
+#else
DBGHELP(SymGetLineFromAddr,
BOOL,
(HANDLE hProcess, DWORD dwAddr, PDWORD pdwDisplacement,
PIMAGEHLP_LINE Line))
+#endif
// keep this always as last entry
DBGHELP(SymCleanup,
diff --git a/Core/Libraries/Source/profile/profile_funclevel.h b/Core/Libraries/Source/profile/profile_funclevel.h
index a153a5f9e28..849052eb010 100644
--- a/Core/Libraries/Source/profile/profile_funclevel.h
+++ b/Core/Libraries/Source/profile/profile_funclevel.h
@@ -29,6 +29,10 @@
#pragma once
+// Only pulls the pointer-sized-int typedef (uintptr_t); avoids dragging
+// BaseTypeCore.h's warning-as-error pragmas into a file that never had them.
+#include
+
/**
\brief The function level profiler.
@@ -182,7 +186,17 @@ class ProfileFuncLevel
*/
unsigned GetId() const
{
- return unsigned(m_threadID);
+ // TODO(x64-profile-id): truncates a 64-bit pointer to a 32-bit
+ // identity token; two tracers can collide on x86-64. Width is fixed
+ // by ProfileResultFileCSV::WriteThread() (profile_result.cpp), which
+ // sprintf()s this value through "prof%08x-all.csv" to name the output
+ // file -- that %08x is the format boundary, so GetId() cannot be
+ // widened without also changing the on-disk file-naming convention.
+ // Cast through uintptr_t so the narrowing is an explicit
+ // pointer-to-int-to-int conversion rather than a silent pointer
+ // truncation; 32-bit output is unchanged since uintptr_t is
+ // unsigned int there.
+ return unsigned(uintptr_t(m_threadID));
}
private:
diff --git a/Generals/Code/GameEngine/Include/Common/StackDump.h b/Generals/Code/GameEngine/Include/Common/StackDump.h
index 2d11b754b94..3a9dbf232ce 100644
--- a/Generals/Code/GameEngine/Include/Common/StackDump.h
+++ b/Generals/Code/GameEngine/Include/Common/StackDump.h
@@ -24,6 +24,9 @@
#pragma once
+// TheSuperHackers @fix MeneerHaas 02/09/2026 stdint_adapter over BaseTypeCore.h to avoid its warning-as-error pragmas.
+#include
+
#ifndef IG_DEBUG_STACKTRACE
#define IG_DEBUG_STACKTRACE 1
#endif // Unsure about this one -ML 3/25/03
@@ -35,7 +38,12 @@ void StackDump(void (*callback)(const char*));
// Writes a stackdump (provide a callback : gets called per line)
// If callback is nullptr then will write using OuputDebugString
+// TheSuperHackers @fix MeneerHaas 02/09/2026 uintptr_t on x64; guarded so the 32-bit signature and mangling are unchanged.
+#if defined(_WIN64) || defined(__x86_64__)
+void StackDumpFromContext(uintptr_t eip,uintptr_t esp,uintptr_t ebp, void (*callback)(const char*));
+#else
void StackDumpFromContext(DWORD eip,DWORD esp,DWORD ebp, void (*callback)(const char*));
+#endif
// Gets count* addresses from the current stack
void FillStackAddresses(void**addresses, unsigned int count, unsigned int skip = 0);
diff --git a/Generals/Code/GameEngine/Source/Common/System/StackDump.cpp b/Generals/Code/GameEngine/Source/Common/System/StackDump.cpp
index 485a459a187..09843b08728 100644
--- a/Generals/Code/GameEngine/Source/Common/System/StackDump.cpp
+++ b/Generals/Code/GameEngine/Source/Common/System/StackDump.cpp
@@ -33,11 +33,29 @@
#include "WWLib/DbgHelpLoader.h"
+// TheSuperHackers @fix MeneerHaas 02/09/2026 stdint_adapter over BaseTypeCore.h to avoid its warning-as-error pragmas.
+#include
+#include "Lib/arch_context.h"
+
+// TheSuperHackers @fix MeneerHaas 02/09/2026 StackWalk64 requires a ContextRecord on AMD64 (it is optional on x86) and
+// updates it while unwinding, so the walkers below seed a mutable local walk_ctx and pass it
+// through this macro. On 32-bit it expands to the retail nullptr, leaving that arm unchanged.
+#if defined(_WIN64) || defined(__x86_64__)
+#define RTS_STACKWALK_CONTEXT (&walk_ctx)
+#else
+#define RTS_STACKWALK_CONTEXT nullptr
+#endif
+
//*****************************************************************************
// Prototypes
//*****************************************************************************
BOOL InitSymbolInfo();
+// TheSuperHackers @fix MeneerHaas 02/09/2026 uintptr_t on x64; guarded so the 32-bit mangled name is unchanged.
+#if defined(_WIN64) || defined(__x86_64__)
+void MakeStackTrace(uintptr_t myeip,uintptr_t myesp,uintptr_t myebp, int skipFrames, void (*callback)(const char*));
+#else
void MakeStackTrace(DWORD myeip,DWORD myesp,DWORD myebp, int skipFrames, void (*callback)(const char*));
+#endif
void GetFunctionDetails(void *pointer, char*name, char*filename, unsigned int* linenumber, unsigned int* address);
void WriteStackLine(void*address, void (*callback)(const char*));
@@ -67,9 +85,14 @@ void StackDump(void (*callback)(const char*))
if (!InitSymbolInfo())
return;
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 uintptr_t on x64; guarded to keep 32-bit codegen identical.
+#if defined(_WIN64) || defined(__x86_64__)
+ uintptr_t myeip,myesp,myebp;
+#else
DWORD myeip,myesp,myebp;
+#endif
-#if defined(_MSC_VER)
+#if defined(_MSC_VER) && defined(_M_IX86)
_asm
{
MYEIP1:
@@ -91,6 +114,13 @@ _asm
:
: "memory"
);
+#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);
#else
#error "Unsupported compiler or architecture for register capture"
#endif
@@ -102,7 +132,11 @@ _asm
//*****************************************************************************
//*****************************************************************************
+#if defined(_WIN64) || defined(__x86_64__)
+void StackDumpFromContext(uintptr_t eip,uintptr_t esp,uintptr_t ebp, void (*callback)(const char*))
+#else
void StackDumpFromContext(DWORD eip,DWORD esp,DWORD ebp, void (*callback)(const char*))
+#endif
{
if (callback == nullptr)
{
@@ -170,7 +204,11 @@ BOOL InitSymbolInfo()
//*****************************************************************************
//*****************************************************************************
+#if defined(_WIN64) || defined(__x86_64__)
+void MakeStackTrace(uintptr_t myeip,uintptr_t myesp,uintptr_t myebp, int skipFrames, void (*callback)(const char*))
+#else
void MakeStackTrace(DWORD myeip,DWORD myesp,DWORD myebp, int skipFrames, void (*callback)(const char*))
+#endif
{
STACKFRAME stack_frame;
BOOL b_ret = TRUE;
@@ -188,6 +226,14 @@ stack_frame.AddrStack.Mode = AddrModeFlat;
stack_frame.AddrStack.Offset = myesp;
stack_frame.AddrFrame.Mode = AddrModeFlat;
stack_frame.AddrFrame.Offset = myebp;
+#if defined(_WIN64) || defined(__x86_64__)
+// TheSuperHackers @fix MeneerHaas 02/09/2026 Seed the walk context from the frame this walk actually starts at.
+CONTEXT walk_ctx;
+RtlCaptureContext(&walk_ctx);
+CTX_PC(walk_ctx) = myeip;
+CTX_STACK(walk_ctx) = myesp;
+CTX_FRAME(walk_ctx) = myebp;
+#endif
{
/*
if(GetThreadContext(thread, &gsContext))
@@ -208,11 +254,11 @@ stack_frame.AddrFrame.Offset = myebp;
unsigned int skip = skipFrames;
while (b_ret&&skip)
{
- b_ret = DbgHelpLoader::stackWalk( IMAGE_FILE_MACHINE_I386,
+ b_ret = DbgHelpLoader::stackWalk( CTX_STACKWALK_MACHINE,
process,
thread,
&stack_frame,
- nullptr, //&gsContext,
+ RTS_STACKWALK_CONTEXT, //&gsContext,
nullptr,
DbgHelpLoader::symFunctionTableAccess,
DbgHelpLoader::symGetModuleBase,
@@ -224,11 +270,11 @@ stack_frame.AddrFrame.Offset = myebp;
while(b_ret&&skip)
{
- b_ret = DbgHelpLoader::stackWalk( IMAGE_FILE_MACHINE_I386,
+ b_ret = DbgHelpLoader::stackWalk( CTX_STACKWALK_MACHINE,
process,
thread,
&stack_frame,
- nullptr, //&gsContext,
+ RTS_STACKWALK_CONTEXT, //&gsContext,
nullptr,
DbgHelpLoader::symFunctionTableAccess,
DbgHelpLoader::symGetModuleBase,
@@ -278,8 +324,17 @@ void GetFunctionDetails(void *pointer, char*name, char*filename, unsigned int* l
psymbol->SizeOfStruct = sizeof(symbol_buffer);
psymbol->MaxNameLength = 512;
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 (uintptr_t)pointer: a (DWORD) cast would truncate live code addresses on x64.
+#if defined(_WIN64) || defined(__x86_64__)
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 SymGetSymFromAddr64 writes 8 bytes; capture wide, then narrow for SymGetLineFromAddr.
+ DWORD64 displacement64;
+ if (DbgHelpLoader::symGetSymFromAddr(process, (uintptr_t) pointer, &displacement64, psymbol))
+ {
+ displacement = (DWORD)displacement64;
+#else
if (DbgHelpLoader::symGetSymFromAddr(process, (DWORD) pointer, &displacement, psymbol))
{
+#endif
if (name)
{
strcpy(name, psymbol->Name);
@@ -292,7 +347,11 @@ void GetFunctionDetails(void *pointer, char*name, char*filename, unsigned int* l
memset(&line,0,sizeof(line));
line.SizeOfStruct = sizeof(line);
+#if defined(_WIN64) || defined(__x86_64__)
+ if (DbgHelpLoader::symGetLineFromAddr(process, (uintptr_t) pointer, &displacement, &line))
+#else
if (DbgHelpLoader::symGetLineFromAddr(process, (DWORD) pointer, &displacement, &line))
+#endif
{
if (filename)
{
@@ -328,8 +387,12 @@ void FillStackAddresses(void**addresses, unsigned int count, unsigned int skip)
memset(&gsContext, 0, sizeof(CONTEXT));
gsContext.ContextFlags = CONTEXT_FULL;
+#if defined(_WIN64) || defined(__x86_64__)
+ uintptr_t myeip,myesp,myebp;
+#else
DWORD myeip,myesp,myebp;
-#if defined(_MSC_VER)
+#endif
+#if defined(_MSC_VER) && defined(_M_IX86)
_asm
{
MYEIP2:
@@ -353,6 +416,13 @@ _asm
:
: "eax", "memory"
);
+#elif defined(_WIN64) || defined(__x86_64__)
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 RtlCaptureContext replaces the inline-asm register capture on x64.
+ 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);
#else
#error "Unsupported compiler or architecture for register capture"
#endif
@@ -364,6 +434,14 @@ stack_frame.AddrStack.Offset = myesp;
stack_frame.AddrFrame.Mode = AddrModeFlat;
stack_frame.AddrFrame.Offset = myebp;
+#if defined(_WIN64) || defined(__x86_64__)
+// TheSuperHackers @fix MeneerHaas 02/09/2026 Seed the walk context from the frame this walk actually starts at.
+CONTEXT walk_ctx;
+RtlCaptureContext(&walk_ctx);
+CTX_PC(walk_ctx) = myeip;
+CTX_STACK(walk_ctx) = myesp;
+CTX_FRAME(walk_ctx) = myebp;
+#endif
{
/*
if(GetThreadContext(thread, &gsContext))
@@ -383,11 +461,11 @@ stack_frame.AddrFrame.Offset = myebp;
// Skip some?
while (stillgoing&&skip)
{
- stillgoing = DbgHelpLoader::stackWalk(IMAGE_FILE_MACHINE_I386,
+ stillgoing = DbgHelpLoader::stackWalk(CTX_STACKWALK_MACHINE,
process,
thread,
&stack_frame,
- nullptr, //&gsContext,
+ RTS_STACKWALK_CONTEXT, //&gsContext,
nullptr,
DbgHelpLoader::symFunctionTableAccess,
DbgHelpLoader::symGetModuleBase,
@@ -397,11 +475,11 @@ stack_frame.AddrFrame.Offset = myebp;
while(stillgoing&&count)
{
- stillgoing = DbgHelpLoader::stackWalk(IMAGE_FILE_MACHINE_I386,
+ stillgoing = DbgHelpLoader::stackWalk(CTX_STACKWALK_MACHINE,
process,
thread,
&stack_frame,
- nullptr, //&gsContext,
+ RTS_STACKWALK_CONTEXT, //&gsContext,
nullptr,
DbgHelpLoader::symFunctionTableAccess,
DbgHelpLoader::symGetModuleBase,
@@ -589,7 +667,11 @@ void DumpExceptionInfo( unsigned int u, EXCEPTION_POINTERS* e_info )
}
DOUBLE_DEBUG (("\nStack Dump:"));
+#if defined(_WIN64) || defined(__x86_64__)
+ StackDumpFromContext(CTX_PC(*context), CTX_STACK(*context), CTX_FRAME(*context), nullptr);
+#else
StackDumpFromContext(context->Eip, context->Esp, context->Ebp, nullptr);
+#endif
DOUBLE_DEBUG (("\nDetails:"));
@@ -598,9 +680,15 @@ void DumpExceptionInfo( unsigned int u, EXCEPTION_POINTERS* e_info )
/*
** Dump the registers.
*/
+#if defined(_WIN64) || defined(__x86_64__)
+ DOUBLE_DEBUG ( ( "Rip:%016llX\tRsp:%016llX\tRbp:%016llX", (unsigned long long)CTX_PC(*context), (unsigned long long)CTX_STACK(*context), (unsigned long long)CTX_FRAME(*context)));
+ DOUBLE_DEBUG ( ( "Rax:%016llX\tRbx:%016llX\tRcx:%016llX", (unsigned long long)CTX_AX(*context), (unsigned long long)CTX_BX(*context), (unsigned long long)CTX_CX(*context)));
+ DOUBLE_DEBUG ( ( "Rdx:%016llX\tRsi:%016llX\tRdi:%016llX", (unsigned long long)CTX_DX(*context), (unsigned long long)CTX_SI(*context), (unsigned long long)CTX_DI(*context)));
+#else
DOUBLE_DEBUG ( ( "Eip:%08X\tEsp:%08X\tEbp:%08X", context->Eip, context->Esp, context->Ebp));
DOUBLE_DEBUG ( ( "Eax:%08X\tEbx:%08X\tEcx:%08X", context->Eax, context->Ebx, context->Ecx));
DOUBLE_DEBUG ( ( "Edx:%08X\tEsi:%08X\tEdi:%08X", context->Edx, context->Esi, context->Edi));
+#endif
DOUBLE_DEBUG ( ( "EFlags:%08X ", context->EFlags));
DOUBLE_DEBUG ( ( "CS:%04x SS:%04x DS:%04x ES:%04x FS:%04x GS:%04x", context->SegCs, context->SegSs, context->SegDs, context->SegEs, context->SegFs, context->SegGs));
@@ -609,9 +697,19 @@ void DumpExceptionInfo( unsigned int u, EXCEPTION_POINTERS* e_info )
*/
char scrap[512];
DOUBLE_DEBUG ( ("EIP bytes dump..."));
+#if defined(_WIN64) || defined(__x86_64__)
+ // wsprintf is Win32's own limited formatter and has no %llX -- sprintf
+ // (CRT) is used here instead, matching Except.cpp's identical case.
+ sprintf (scrap, "\nBytes at CS:RIP (%016llX) : ", (unsigned long long)CTX_PC(*context));
+#else
wsprintf (scrap, "\nBytes at CS:EIP (%08X) : ", context->Eip);
+#endif
+#if defined(_WIN64) || defined(__x86_64__)
+ unsigned char *eip_ptr = (unsigned char *) (CTX_PC(*context));
+#else
unsigned char *eip_ptr = (unsigned char *) (context->Eip);
+#endif
char bytestr[32];
for (int c = 0 ; c < 32 ; c++)
diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp
index a9dac652860..5bb2139a5b4 100644
--- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp
+++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp
@@ -794,7 +794,12 @@ RenderObjClass * WW3DAssetManager::Create_Render_Obj(const char * name)
char filename [MAX_PATH];
const char *mesh_name = ::strchr (name, '.');
if (mesh_name != nullptr) {
- ::lstrcpyn (filename, name, ((int)mesh_name) - ((int)name) + 1);
+ // This is pointer subtraction (distance from name to the '.'),
+ // not a wire value -- lstrcpynA's count parameter is `int` on
+ // Win32, and the string length trivially fits. Compute the
+ // difference with proper pointer arithmetic instead of
+ // truncating each 64-bit pointer to `int` before subtracting.
+ ::lstrcpyn (filename, name, (int)(mesh_name - name) + 1);
::lstrcat (filename, ".w3d");
} else {
snprintf( filename, ARRAY_SIZE(filename), "%s.w3d", name);
diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp
index 3f0dd3302ab..91f51591cac 100644
--- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp
+++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp
@@ -1349,6 +1349,14 @@ PersistClass * DazzlePersistFactoryClass::Load(ChunkLoadClass & cload) const
char dazzle_type[256];
dazzle_type[0] = 0;
+ // Read exactly what Save wrote: a fixed-width 4-byte identity token, not
+ // sizeof(old_obj). On x86-64 sizeof(DazzleRenderObjClass*) is 8, so
+ // reading sizeof(old_obj) here (as READ_MICRO_CHUNK would) would ask for
+ // more bytes than the legacy 4-byte micro chunk holds; ChunkLoadClass::Read
+ // then refuses to read anything at all and old_obj stays null, silently
+ // poisoning SaveLoadSystemClass's pointer remap table. See persistfactory.h.
+ uint32 old_obj_token = 0;
+
/*
** Load the dazzle parameters
*/
@@ -1359,7 +1367,7 @@ PersistClass * DazzlePersistFactoryClass::Load(ChunkLoadClass & cload) const
while (cload.Open_Micro_Chunk()) {
switch(cload.Cur_Micro_Chunk_ID()) {
- READ_MICRO_CHUNK(cload,DAZZLEFACTORY_VARIABLE_OBJPOINTER,old_obj);
+ case (DAZZLEFACTORY_VARIABLE_OBJPOINTER): cload.Read(&old_obj_token,sizeof(old_obj_token)); break;
READ_MICRO_CHUNK(cload,DAZZLEFACTORY_VARIABLE_TRANSFORM,tm);
READ_MICRO_CHUNK_STRING(cload,DAZZLEFACTORY_VARIABLE_TYPENAME,dazzle_type,sizeof(dazzle_type));
}
@@ -1403,6 +1411,7 @@ PersistClass * DazzlePersistFactoryClass::Load(ChunkLoadClass & cload) const
/*
** Register the old pointer for re-mapping to the new pointer
*/
+ old_obj = (DazzleRenderObjClass *)(uintptr_t)old_obj_token;
SaveLoadSystemClass::Register_Pointer(old_obj,new_obj);
return new_obj;
}
@@ -1415,7 +1424,13 @@ void DazzlePersistFactoryClass::Save(ChunkSaveClass & csave,PersistClass * obj)
const Matrix3D& tm = robj->Get_Transform();
csave.Begin_Chunk(DAZZLEFACTORY_CHUNKID_VARIABLES);
+#if defined(_WIN64) || defined(__x86_64__)
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 Write the 4-byte identity token the loader reads; see persistfactory.h.
+ uint32 robj_token = (uint32)(uintptr_t)robj;
+ WRITE_MICRO_CHUNK(csave,DAZZLEFACTORY_VARIABLE_OBJPOINTER,robj_token);
+#else
WRITE_MICRO_CHUNK(csave,DAZZLEFACTORY_VARIABLE_OBJPOINTER,robj);
+#endif
WRITE_MICRO_CHUNK(csave,DAZZLEFACTORY_VARIABLE_TRANSFORM,tm);
WRITE_MICRO_CHUNK_STRING(csave,DAZZLEFACTORY_VARIABLE_TYPENAME,dazzle_type_name);
diff --git a/GeneralsMD/Code/GameEngine/Include/Common/StackDump.h b/GeneralsMD/Code/GameEngine/Include/Common/StackDump.h
index ce84c0af736..487fc7a494e 100644
--- a/GeneralsMD/Code/GameEngine/Include/Common/StackDump.h
+++ b/GeneralsMD/Code/GameEngine/Include/Common/StackDump.h
@@ -24,6 +24,9 @@
#pragma once
+// TheSuperHackers @fix MeneerHaas 02/09/2026 stdint_adapter over BaseTypeCore.h to avoid its warning-as-error pragmas.
+#include
+
#ifndef IG_DEBUG_STACKTRACE
#define IG_DEBUG_STACKTRACE 1
#endif // Unsure about this one -ML 3/25/03
@@ -35,7 +38,12 @@ void StackDump(void (*callback)(const char*));
// Writes a stackdump (provide a callback : gets called per line)
// If callback is nullptr then will write using OuputDebugString
+// TheSuperHackers @fix MeneerHaas 02/09/2026 uintptr_t on x64; guarded so the 32-bit signature and mangling are unchanged.
+#if defined(_WIN64) || defined(__x86_64__)
+void StackDumpFromContext(uintptr_t eip,uintptr_t esp,uintptr_t ebp, void (*callback)(const char*));
+#else
void StackDumpFromContext(DWORD eip,DWORD esp,DWORD ebp, void (*callback)(const char*));
+#endif
// Gets count* addresses from the current stack
void FillStackAddresses(void**addresses, unsigned int count, unsigned int skip = 0);
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp
index 08328666992..fe53728f3a2 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp
@@ -33,11 +33,29 @@
#include "WWLib/DbgHelpLoader.h"
+// TheSuperHackers @fix MeneerHaas 02/09/2026 stdint_adapter over BaseTypeCore.h to avoid its warning-as-error pragmas.
+#include
+#include "Lib/arch_context.h"
+
+// TheSuperHackers @fix MeneerHaas 02/09/2026 StackWalk64 requires a ContextRecord on AMD64 (it is optional on x86) and
+// updates it while unwinding, so the walkers below seed a mutable local walk_ctx and pass it
+// through this macro. On 32-bit it expands to the retail nullptr, leaving that arm unchanged.
+#if defined(_WIN64) || defined(__x86_64__)
+#define RTS_STACKWALK_CONTEXT (&walk_ctx)
+#else
+#define RTS_STACKWALK_CONTEXT nullptr
+#endif
+
//*****************************************************************************
// Prototypes
//*****************************************************************************
BOOL InitSymbolInfo();
+// TheSuperHackers @fix MeneerHaas 02/09/2026 uintptr_t on x64; guarded so the 32-bit mangled name is unchanged.
+#if defined(_WIN64) || defined(__x86_64__)
+void MakeStackTrace(uintptr_t myeip,uintptr_t myesp,uintptr_t myebp, int skipFrames, void (*callback)(const char*));
+#else
void MakeStackTrace(DWORD myeip,DWORD myesp,DWORD myebp, int skipFrames, void (*callback)(const char*));
+#endif
void GetFunctionDetails(void *pointer, char*name, char*filename, unsigned int* linenumber, unsigned int* address);
void WriteStackLine(void*address, void (*callback)(const char*));
@@ -67,9 +85,14 @@ void StackDump(void (*callback)(const char*))
if (!InitSymbolInfo())
return;
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 uintptr_t on x64; guarded to keep 32-bit codegen identical.
+#if defined(_WIN64) || defined(__x86_64__)
+ uintptr_t myeip,myesp,myebp;
+#else
DWORD myeip,myesp,myebp;
+#endif
-#if defined(_MSC_VER)
+#if defined(_MSC_VER) && defined(_M_IX86)
_asm
{
MYEIP1:
@@ -91,6 +114,13 @@ _asm
:
: "memory"
);
+#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);
#else
#error "Unsupported compiler or architecture for register capture"
#endif
@@ -102,7 +132,11 @@ _asm
//*****************************************************************************
//*****************************************************************************
+#if defined(_WIN64) || defined(__x86_64__)
+void StackDumpFromContext(uintptr_t eip,uintptr_t esp,uintptr_t ebp, void (*callback)(const char*))
+#else
void StackDumpFromContext(DWORD eip,DWORD esp,DWORD ebp, void (*callback)(const char*))
+#endif
{
if (callback == nullptr)
{
@@ -170,7 +204,11 @@ BOOL InitSymbolInfo()
//*****************************************************************************
//*****************************************************************************
+#if defined(_WIN64) || defined(__x86_64__)
+void MakeStackTrace(uintptr_t myeip,uintptr_t myesp,uintptr_t myebp, int skipFrames, void (*callback)(const char*))
+#else
void MakeStackTrace(DWORD myeip,DWORD myesp,DWORD myebp, int skipFrames, void (*callback)(const char*))
+#endif
{
STACKFRAME stack_frame;
BOOL b_ret = TRUE;
@@ -188,6 +226,14 @@ stack_frame.AddrStack.Mode = AddrModeFlat;
stack_frame.AddrStack.Offset = myesp;
stack_frame.AddrFrame.Mode = AddrModeFlat;
stack_frame.AddrFrame.Offset = myebp;
+#if defined(_WIN64) || defined(__x86_64__)
+// TheSuperHackers @fix MeneerHaas 02/09/2026 Seed the walk context from the frame this walk actually starts at.
+CONTEXT walk_ctx;
+RtlCaptureContext(&walk_ctx);
+CTX_PC(walk_ctx) = myeip;
+CTX_STACK(walk_ctx) = myesp;
+CTX_FRAME(walk_ctx) = myebp;
+#endif
{
/*
if(GetThreadContext(thread, &gsContext))
@@ -208,11 +254,11 @@ stack_frame.AddrFrame.Offset = myebp;
unsigned int skip = skipFrames;
while (b_ret&&skip)
{
- b_ret = DbgHelpLoader::stackWalk( IMAGE_FILE_MACHINE_I386,
+ b_ret = DbgHelpLoader::stackWalk( CTX_STACKWALK_MACHINE,
process,
thread,
&stack_frame,
- nullptr, //&gsContext,
+ RTS_STACKWALK_CONTEXT, //&gsContext,
nullptr,
DbgHelpLoader::symFunctionTableAccess,
DbgHelpLoader::symGetModuleBase,
@@ -224,11 +270,11 @@ stack_frame.AddrFrame.Offset = myebp;
while(b_ret&&skip)
{
- b_ret = DbgHelpLoader::stackWalk( IMAGE_FILE_MACHINE_I386,
+ b_ret = DbgHelpLoader::stackWalk( CTX_STACKWALK_MACHINE,
process,
thread,
&stack_frame,
- nullptr, //&gsContext,
+ RTS_STACKWALK_CONTEXT, //&gsContext,
nullptr,
DbgHelpLoader::symFunctionTableAccess,
DbgHelpLoader::symGetModuleBase,
@@ -278,8 +324,17 @@ void GetFunctionDetails(void *pointer, char*name, char*filename, unsigned int* l
psymbol->SizeOfStruct = sizeof(symbol_buffer);
psymbol->MaxNameLength = 512;
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 (uintptr_t)pointer: a (DWORD) cast would truncate live code addresses on x64.
+#if defined(_WIN64) || defined(__x86_64__)
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 SymGetSymFromAddr64 writes 8 bytes; capture wide, then narrow for SymGetLineFromAddr.
+ DWORD64 displacement64;
+ if (DbgHelpLoader::symGetSymFromAddr(process, (uintptr_t) pointer, &displacement64, psymbol))
+ {
+ displacement = (DWORD)displacement64;
+#else
if (DbgHelpLoader::symGetSymFromAddr(process, (DWORD) pointer, &displacement, psymbol))
{
+#endif
if (name)
{
strcpy(name, psymbol->Name);
@@ -292,7 +347,11 @@ void GetFunctionDetails(void *pointer, char*name, char*filename, unsigned int* l
memset(&line,0,sizeof(line));
line.SizeOfStruct = sizeof(line);
+#if defined(_WIN64) || defined(__x86_64__)
+ if (DbgHelpLoader::symGetLineFromAddr(process, (uintptr_t) pointer, &displacement, &line))
+#else
if (DbgHelpLoader::symGetLineFromAddr(process, (DWORD) pointer, &displacement, &line))
+#endif
{
if (filename)
{
@@ -328,8 +387,12 @@ void FillStackAddresses(void**addresses, unsigned int count, unsigned int skip)
memset(&gsContext, 0, sizeof(CONTEXT));
gsContext.ContextFlags = CONTEXT_FULL;
+#if defined(_WIN64) || defined(__x86_64__)
+ uintptr_t myeip,myesp,myebp;
+#else
DWORD myeip,myesp,myebp;
-#if defined(_MSC_VER)
+#endif
+#if defined(_MSC_VER) && defined(_M_IX86)
_asm
{
MYEIP2:
@@ -353,6 +416,13 @@ _asm
:
: "eax", "memory"
);
+#elif defined(_WIN64) || defined(__x86_64__)
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 RtlCaptureContext replaces the inline-asm register capture on x64.
+ 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);
#else
#error "Unsupported compiler or architecture for register capture"
#endif
@@ -364,6 +434,14 @@ stack_frame.AddrStack.Offset = myesp;
stack_frame.AddrFrame.Mode = AddrModeFlat;
stack_frame.AddrFrame.Offset = myebp;
+#if defined(_WIN64) || defined(__x86_64__)
+// TheSuperHackers @fix MeneerHaas 02/09/2026 Seed the walk context from the frame this walk actually starts at.
+CONTEXT walk_ctx;
+RtlCaptureContext(&walk_ctx);
+CTX_PC(walk_ctx) = myeip;
+CTX_STACK(walk_ctx) = myesp;
+CTX_FRAME(walk_ctx) = myebp;
+#endif
{
/*
if(GetThreadContext(thread, &gsContext))
@@ -383,11 +461,11 @@ stack_frame.AddrFrame.Offset = myebp;
// Skip some?
while (stillgoing&&skip)
{
- stillgoing = DbgHelpLoader::stackWalk(IMAGE_FILE_MACHINE_I386,
+ stillgoing = DbgHelpLoader::stackWalk(CTX_STACKWALK_MACHINE,
process,
thread,
&stack_frame,
- nullptr, //&gsContext,
+ RTS_STACKWALK_CONTEXT, //&gsContext,
nullptr,
DbgHelpLoader::symFunctionTableAccess,
DbgHelpLoader::symGetModuleBase,
@@ -397,11 +475,11 @@ stack_frame.AddrFrame.Offset = myebp;
while(stillgoing&&count)
{
- stillgoing = DbgHelpLoader::stackWalk(IMAGE_FILE_MACHINE_I386,
+ stillgoing = DbgHelpLoader::stackWalk(CTX_STACKWALK_MACHINE,
process,
thread,
&stack_frame,
- nullptr, //&gsContext,
+ RTS_STACKWALK_CONTEXT, //&gsContext,
nullptr,
DbgHelpLoader::symFunctionTableAccess,
DbgHelpLoader::symGetModuleBase,
@@ -589,7 +667,11 @@ void DumpExceptionInfo( unsigned int u, EXCEPTION_POINTERS* e_info )
}
DOUBLE_DEBUG (("\nStack Dump:"));
+#if defined(_WIN64) || defined(__x86_64__)
+ StackDumpFromContext(CTX_PC(*context), CTX_STACK(*context), CTX_FRAME(*context), nullptr);
+#else
StackDumpFromContext(context->Eip, context->Esp, context->Ebp, nullptr);
+#endif
DOUBLE_DEBUG (("\nDetails:"));
@@ -598,9 +680,15 @@ void DumpExceptionInfo( unsigned int u, EXCEPTION_POINTERS* e_info )
/*
** Dump the registers.
*/
+#if defined(_WIN64) || defined(__x86_64__)
+ DOUBLE_DEBUG ( ( "Rip:%016llX\tRsp:%016llX\tRbp:%016llX", (unsigned long long)CTX_PC(*context), (unsigned long long)CTX_STACK(*context), (unsigned long long)CTX_FRAME(*context)));
+ DOUBLE_DEBUG ( ( "Rax:%016llX\tRbx:%016llX\tRcx:%016llX", (unsigned long long)CTX_AX(*context), (unsigned long long)CTX_BX(*context), (unsigned long long)CTX_CX(*context)));
+ DOUBLE_DEBUG ( ( "Rdx:%016llX\tRsi:%016llX\tRdi:%016llX", (unsigned long long)CTX_DX(*context), (unsigned long long)CTX_SI(*context), (unsigned long long)CTX_DI(*context)));
+#else
DOUBLE_DEBUG ( ( "Eip:%08X\tEsp:%08X\tEbp:%08X", context->Eip, context->Esp, context->Ebp));
DOUBLE_DEBUG ( ( "Eax:%08X\tEbx:%08X\tEcx:%08X", context->Eax, context->Ebx, context->Ecx));
DOUBLE_DEBUG ( ( "Edx:%08X\tEsi:%08X\tEdi:%08X", context->Edx, context->Esi, context->Edi));
+#endif
DOUBLE_DEBUG ( ( "EFlags:%08X ", context->EFlags));
DOUBLE_DEBUG ( ( "CS:%04x SS:%04x DS:%04x ES:%04x FS:%04x GS:%04x", context->SegCs, context->SegSs, context->SegDs, context->SegEs, context->SegFs, context->SegGs));
@@ -609,9 +697,19 @@ void DumpExceptionInfo( unsigned int u, EXCEPTION_POINTERS* e_info )
*/
char scrap[512];
DOUBLE_DEBUG ( ("EIP bytes dump..."));
+#if defined(_WIN64) || defined(__x86_64__)
+ // wsprintf is Win32's own limited formatter and has no %llX -- sprintf
+ // (CRT) is used here instead, matching Except.cpp's identical case.
+ sprintf (scrap, "\nBytes at CS:RIP (%016llX) : ", (unsigned long long)CTX_PC(*context));
+#else
wsprintf (scrap, "\nBytes at CS:EIP (%08X) : ", context->Eip);
+#endif
+#if defined(_WIN64) || defined(__x86_64__)
+ unsigned char *eip_ptr = (unsigned char *) (CTX_PC(*context));
+#else
unsigned char *eip_ptr = (unsigned char *) (context->Eip);
+#endif
char bytestr[32];
for (int c = 0 ; c < 32 ; c++)
diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp
index ce6f67a0324..b6ffc006bef 100644
--- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp
+++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp
@@ -799,7 +799,12 @@ RenderObjClass * WW3DAssetManager::Create_Render_Obj(const char * name)
char filename [MAX_PATH];
const char *mesh_name = ::strchr (name, '.');
if (mesh_name != nullptr) {
- ::lstrcpyn (filename, name, ((int)mesh_name) - ((int)name) + 1);
+ // This is pointer subtraction (distance from name to the '.'),
+ // not a wire value -- lstrcpynA's count parameter is `int` on
+ // Win32, and the string length trivially fits. Compute the
+ // difference with proper pointer arithmetic instead of
+ // truncating each 64-bit pointer to `int` before subtracting.
+ ::lstrcpyn (filename, name, (int)(mesh_name - name) + 1);
::lstrcat (filename, ".w3d");
} else {
snprintf( filename, ARRAY_SIZE(filename), "%s.w3d", name);
diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp
index cfe2e251433..05abfdbdafd 100644
--- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp
+++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp
@@ -1452,6 +1452,14 @@ PersistClass * DazzlePersistFactoryClass::Load(ChunkLoadClass & cload) const
char dazzle_type[256];
dazzle_type[0] = 0;
+ // Read exactly what Save wrote: a fixed-width 4-byte identity token, not
+ // sizeof(old_obj). On x86-64 sizeof(DazzleRenderObjClass*) is 8, so
+ // reading sizeof(old_obj) here (as READ_MICRO_CHUNK would) would ask for
+ // more bytes than the legacy 4-byte micro chunk holds; ChunkLoadClass::Read
+ // then refuses to read anything at all and old_obj stays null, silently
+ // poisoning SaveLoadSystemClass's pointer remap table. See persistfactory.h.
+ uint32 old_obj_token = 0;
+
/*
** Load the dazzle parameters
*/
@@ -1462,7 +1470,7 @@ PersistClass * DazzlePersistFactoryClass::Load(ChunkLoadClass & cload) const
while (cload.Open_Micro_Chunk()) {
switch(cload.Cur_Micro_Chunk_ID()) {
- READ_MICRO_CHUNK(cload,DAZZLEFACTORY_VARIABLE_OBJPOINTER,old_obj);
+ case (DAZZLEFACTORY_VARIABLE_OBJPOINTER): cload.Read(&old_obj_token,sizeof(old_obj_token)); break;
READ_MICRO_CHUNK(cload,DAZZLEFACTORY_VARIABLE_TRANSFORM,tm);
READ_MICRO_CHUNK_STRING(cload,DAZZLEFACTORY_VARIABLE_TYPENAME,dazzle_type,sizeof(dazzle_type));
}
@@ -1506,6 +1514,7 @@ PersistClass * DazzlePersistFactoryClass::Load(ChunkLoadClass & cload) const
/*
** Register the old pointer for re-mapping to the new pointer
*/
+ old_obj = (DazzleRenderObjClass *)(uintptr_t)old_obj_token;
SaveLoadSystemClass::Register_Pointer(old_obj,new_obj);
return new_obj;
}
@@ -1518,7 +1527,13 @@ void DazzlePersistFactoryClass::Save(ChunkSaveClass & csave,PersistClass * obj)
const Matrix3D& tm = robj->Get_Transform();
csave.Begin_Chunk(DAZZLEFACTORY_CHUNKID_VARIABLES);
+#if defined(_WIN64) || defined(__x86_64__)
+ // TheSuperHackers @fix MeneerHaas 02/09/2026 Write the 4-byte identity token the loader reads; see persistfactory.h.
+ uint32 robj_token = (uint32)(uintptr_t)robj;
+ WRITE_MICRO_CHUNK(csave,DAZZLEFACTORY_VARIABLE_OBJPOINTER,robj_token);
+#else
WRITE_MICRO_CHUNK(csave,DAZZLEFACTORY_VARIABLE_OBJPOINTER,robj);
+#endif
WRITE_MICRO_CHUNK(csave,DAZZLEFACTORY_VARIABLE_TRANSFORM,tm);
WRITE_MICRO_CHUNK_STRING(csave,DAZZLEFACTORY_VARIABLE_TYPENAME,dazzle_type_name);
diff --git a/cmake/dx8.cmake b/cmake/dx8.cmake
index dd08f56119a..42a44ca7524 100644
--- a/cmake/dx8.cmake
+++ b/cmake/dx8.cmake
@@ -4,4 +4,65 @@ FetchContent_Declare(
GIT_TAG 7bddff8c01f5fb931c3cb73d4aa8e66d303d97bc
)
-FetchContent_MakeAvailable(dx8)
+# Populate the source only (do not add_subdirectory it): the fetched
+# min-dx8-sdk repo's own CMakeLists.txt has no architecture condition at all,
+# so the d3d8lib target is defined here instead, where it can diverge by
+# CMAKE_SIZEOF_VOID_P without forking that upstream repo.
+FetchContent_GetProperties(dx8)
+if(NOT dx8_POPULATED)
+ FetchContent_Populate(dx8)
+endif()
+
+add_library(d3d8lib INTERFACE)
+
+# Common libraries for all compilers.
+# 64-bit: MinGW-w64 x86_64 provides libdinput8.a and libdxguid.a but no
+# libd3d8.a (only libd3d8thk.a) and no libd3dx8 at all. The headers are
+# architecture-independent, so on x64 this target carries includes and defines
+# 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
+# a guarantee: W3D also depends on several Core libraries (WWLib, debug,
+# Compression, WWSaveLoad, WWAudio) that fail for unrelated reasons and still
+# block it as of this change.
+if(CMAKE_SIZEOF_VOID_P EQUAL 4)
+ target_link_libraries(d3d8lib INTERFACE d3d8 dinput8 dxguid)
+else()
+ message(STATUS "DX8: x64 build — headers only, no D3D8 link libraries available")
+endif()
+
+# MSVC-specific configuration
+if(MSVC)
+ # Use bundled MSVC-compiled .lib files. d3dx8.lib is pe-i386 (32-bit)
+ # only -- there is no 64-bit build of it in the fetched min-dx8-sdk repo
+ # -- so linking it into a 64-bit target would fail, the same reason the
+ # top-level d3d8/dinput8/dxguid link above is gated on
+ # CMAKE_SIZEOF_VOID_P EQUAL 4. No x64 MSVC preset exercises this today
+ # (issue #473), but the condition should match its sibling regardless.
+ if(CMAKE_SIZEOF_VOID_P EQUAL 4)
+ target_link_libraries(d3d8lib INTERFACE d3dx8)
+ endif()
+ target_link_directories(d3d8lib BEFORE INTERFACE ${dx8_SOURCE_DIR})
+ target_link_options(d3d8lib INTERFACE /NODEFAULTLIB:libci.lib)
+
+ if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "12.0.8804")
+ # Modern MSVC (VS 2013+) has complete DirectX headers in Windows SDK
+ target_link_libraries(d3d8lib INTERFACE legacy_stdio_definitions)
+ target_link_options(d3d8lib INTERFACE /SAFESEH:NO)
+ else()
+ # VC6 and older MSVC need extra headers - their DirectX SDK is missing newer definitions
+ target_include_directories(d3d8lib INTERFACE ${dx8_SOURCE_DIR}/extra)
+ endif()
+endif()
+
+# MinGW-specific configuration
+if(MINGW)
+ # MinGW-w64 DirectX 8 support varies by architecture:
+ # i686 (32-bit): libd3d8.a + libd3dx8d.a (debug only, no release version)
+ # x86_64 (64-bit): libd3d8thk.a only (no libd3dx8 libraries at all)
+ if(CMAKE_SIZEOF_VOID_P EQUAL 4)
+ target_link_libraries(d3d8lib INTERFACE d3dx8d)
+ endif()
+endif()
+
+target_compile_definitions(d3d8lib INTERFACE -DBUILD_WITH_D3D8)
+target_include_directories(d3d8lib INTERFACE ${dx8_SOURCE_DIR})
diff --git a/cmake/mingw.cmake b/cmake/mingw.cmake
index c0953430552..74209a77578 100644
--- a/cmake/mingw.cmake
+++ b/cmake/mingw.cmake
@@ -9,7 +9,8 @@ if(MINGW)
set(IS_MINGW32 TRUE)
message(STATUS "MinGW-w64 32-bit (i686) detected")
else()
- message(FATAL_ERROR "MinGW-w64 64-bit (x86_64) detected, but this project only supports 32-bit builds. Use the i686-w64-mingw32 toolchain.")
+ set(IS_MINGW64 TRUE)
+ message(STATUS "MinGW-w64 64-bit (x86_64) detected — experimental, see issue #473")
endif()
# Windows subsystem
@@ -53,7 +54,13 @@ if(MINGW)
)
endif()
- # Required Windows libraries for DX8 + COM
+ # Required Windows libraries for DX8 + COM.
+ # d3d8 is 32-bit only: MinGW-w64 x86_64 ships libd3d8thk.a but no
+ # libd3d8.a, so it is gated with a generator expression rather than
+ # linked unconditionally. link_libraries() is directory-scoped and
+ # applies to every target created after this point, including the
+ # Miles/Bink FetchContent stub DLLs, which do not use DirectX at all —
+ # linking d3d8 unconditionally here broke their link step on x64.
link_libraries(
uuid # COM GUIDs
ole32 # COM runtime
@@ -63,7 +70,7 @@ if(MINGW)
comctl32 # Common controls
winmm # Multimedia (timeGetTime, etc.)
vfw32 # Video for Windows (AVIFile functions)
- d3d8 # Direct3D 8
+ $<$:d3d8> # Direct3D 8 — 32-bit only
dinput8 # DirectInput 8
dsound # DirectSound
imm32 # Input Method Manager (IME)
@@ -78,8 +85,9 @@ if(MINGW)
# MinGW-w64 only provides libd3dx8d.a (debug library), not libd3dx8.a
# The min-dx8-sdk (dx8.cmake) handles this correctly via d3d8lib interface target,
# but for compatibility with direct library references in main executables,
- # we create an alias so that linking to d3dx8 automatically uses d3dx8d
- if(NOT TARGET d3dx8)
+ # we create an alias so that linking to d3dx8 automatically uses d3dx8d.
+ # 32-bit only: x86_64 MinGW-w64 ships neither libd3dx8.a nor libd3dx8d.a.
+ if(CMAKE_SIZEOF_VOID_P EQUAL 4 AND NOT TARGET d3dx8)
add_library(d3dx8 INTERFACE IMPORTED GLOBAL)
set_target_properties(d3dx8 PROPERTIES
INTERFACE_LINK_LIBRARIES "d3dx8d"
diff --git a/cmake/toolchains/mingw-w64-x86_64.cmake b/cmake/toolchains/mingw-w64-x86_64.cmake
new file mode 100644
index 00000000000..8e0f993c906
--- /dev/null
+++ b/cmake/toolchains/mingw-w64-x86_64.cmake
@@ -0,0 +1,32 @@
+# MinGW-w64 64-bit (x86_64) Toolchain File
+# Use with: cmake -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64-x86_64.cmake
+
+set(CMAKE_SYSTEM_NAME Windows)
+set(CMAKE_SYSTEM_PROCESSOR x86_64)
+
+# Specify the cross compiler
+set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
+set(CMAKE_AR x86_64-w64-mingw32-ar)
+set(CMAKE_RANLIB x86_64-w64-mingw32-ranlib)
+set(CMAKE_DLLTOOL x86_64-w64-mingw32-dlltool)
+
+# Target environment
+set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
+
+# Adjust the default behavior of the FIND_XXX() commands:
+# search programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+
+# search headers and libraries in the target environment
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+
+# Force 64-bit pointer size
+set(CMAKE_SIZEOF_VOID_P 8)
+
+# Disable MFC-dependent tools (not compatible with MinGW-w64)
+set(RTS_BUILD_CORE_TOOLS OFF CACHE BOOL "Disable MFC-dependent core tools for MinGW" FORCE)
+set(RTS_BUILD_GENERALS_TOOLS OFF CACHE BOOL "Disable MFC-dependent Generals tools for MinGW" FORCE)
+set(RTS_BUILD_ZEROHOUR_TOOLS OFF CACHE BOOL "Disable MFC-dependent Zero Hour tools for MinGW" FORCE)