From 7d36c42baf46be01bf7be4ce6ee0fcb1dfc5902d Mon Sep 17 00:00:00 2001 From: devtejasx Date: Tue, 8 Sep 2026 16:29:35 +0530 Subject: [PATCH] Type the game's 0x20 slot as D2ClientInfoStrc* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `D2GameStrc::nGameData` at 0x20 was a bare `uint32_t`, filled by a callback declared to return `uint32_t` and read by a callback declared to take `uint32_t*`. The rest of the table makes clear what actually lives there: - `pfLeaveGame`, `pfGetDatabaseCharacter`, `pfSaveDatabaseCharacter` and `pfRelockDatabaseCharacter` all take `D2ClientInfoStrc** ppClientInfo`, and the call sites pass `&pClient->pClientInfo` or a local `D2ClientInfoStrc*`. - `pfUnlockDatabaseCharacter` is the odd one out, typed `uint32_t* pGameData`, and `GAME_JoinGame` passes it `&pGame->nGameData` — the *game's* slot, in the branch where `CLIENTS_AddToGame` failed and there is no client to take a `pClientInfo` from. So the same kind of handle is reached through two different types depending on whether a client exists. Field 0x20 is now `D2ClientInfoStrc* pClientInfo`, `FnUnlockDatabaseCharacter` takes `D2ClientInfoStrc**` like its four siblings, and `FnSetGameData` becomes `FnCreateClientInfo` returning `D2ClientInfoStrc*`, which is what `CLIENTS_SetGameData` stores into the slot. The layout is unchanged: `D2GameStrc` is packed for a 32-bit target, where the pointer occupies the same four bytes the `uint32_t` did. #160 also gives the callback a `D2ClientStrc* pClient` parameter. That half is left alone: the only call site, `CLIENTS_SetGameData`, is reached from `GAME_AllocGame` and `GAME_FreeGame` with no client in scope, and passes no argument today. Deciding whether the original takes one in ECX needs the disassembly, not this tree. Syntax-checked both touched translation units with g++ -fsyntax-only -m32 and the DLL_DECL macros defined. Error counts are identical before and after (Game.cpp 14, Clients.cpp 4) — all pre-existing MSVC-isms under GCC, including the struct-size static_asserts, which fail on the unmodified tree too. Fixes #160 --- source/D2Game/include/GAME/Game.h | 11 +++++++---- source/D2Game/src/GAME/Clients.cpp | 4 ++-- source/D2Game/src/GAME/Game.cpp | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/source/D2Game/include/GAME/Game.h b/source/D2Game/include/GAME/Game.h index 75411227b..3a2191719 100644 --- a/source/D2Game/include/GAME/Game.h +++ b/source/D2Game/include/GAME/Game.h @@ -68,12 +68,15 @@ using FnServerLogMessage = void(*)(int32_t nLogLevel, const char* szFormat, ...) using FnEnterGame = void(__fastcall*)(WORD nGameId, const char* szCharName, int32_t nClassId, int32_t nLevel, uint32_t nFlags); using FnFindPlayerToken = int32_t(__fastcall*)(const char* szCharName, int32_t nTokenId, WORD nGameId, char* pszOutAccountName, int32_t* pOutCharSaveTransactionToken, int32_t* a6, int32_t* a7); //TODO: Last 2 args /*UNUSED*/ using FnSaveDatabaseGuild = int(__fastcall*)(const char*, char*, size_t); -using FnUnlockDatabaseCharacter = void(__fastcall*)(uint32_t* pGameData, const char* szCharName, const char* szAccountName); +using FnUnlockDatabaseCharacter = void(__fastcall*)(D2ClientInfoStrc** ppClientInfo, const char* szCharName, const char* szAccountName); /*UNUSED*/ using FnUnk0x24 = int(__fastcall*)(int, int); using FnUpdateCharacterLadder = void(__fastcall*)(const char* szCharName, int32_t nClassId, int32_t nLevel, uint32_t nExperience, int32_t nZero, uint32_t nFlags, FILETIME* pSaveCreationTimestamp); using FnUpdateGameInformation = void(__fastcall*)(WORD nGameId, const char* szCharName, int32_t nClassId, int32_t nLevel); using FnHandlePacket = void(__fastcall*)(void* pPacket, int32_t nPacketSize); -using FnSetGameData = uint32_t(__fastcall*)(); +// The server hands back a D2ClientInfoStrc*, which is stored in D2GameStrc::pClientInfo +// and later passed to pfUnlockDatabaseCharacter the same way D2ClientStrc::pClientInfo is +// passed to the other database callbacks. +using FnCreateClientInfo = D2ClientInfoStrc*(__fastcall*)(); using FnRelockDatabaseCharacter = void(__fastcall*)(D2ClientInfoStrc** ppClientInfo, const char* szCharName, const char* szAccountName); /*UNUSED*/ using FnLoadComplete = int32_t(__stdcall*)(int32_t); @@ -95,7 +98,7 @@ struct D2ServerCallbackFunctions // sizeof 0x40 FnUpdateCharacterLadder pfUpdateCharacterLadder; //0x28 FnUpdateGameInformation pfUpdateGameInformation; //0x2C FnHandlePacket pfHandlePacket; //0x30 - FnSetGameData pfSetGameData; //0x34 + FnCreateClientInfo pfCreateClientInfo; //0x34 FnRelockDatabaseCharacter pfRelockDatabaseCharacter; //0x38 FnLoadComplete pfLoadComplete; //0x3C }; @@ -146,7 +149,7 @@ struct D2GameStrc : TSHashObject // called SGAMEDATA i { LPCRITICAL_SECTION lpCriticalSection; //0x18 void* pMemoryPool; //0x1C - uint32_t nGameData; //0x20 + D2ClientInfoStrc* pClientInfo; //0x20 uint32_t unk0x24; //0x24 uint16_t nGameId; //0x28 char szGameName[16]; //0x2A diff --git a/source/D2Game/src/GAME/Clients.cpp b/source/D2Game/src/GAME/Clients.cpp index a4d0787f6..506a32c04 100644 --- a/source/D2Game/src/GAME/Clients.cpp +++ b/source/D2Game/src/GAME/Clients.cpp @@ -528,9 +528,9 @@ void __fastcall CLIENTS_SetGameData(D2GameStrc* pGame) pGame->nClients = 0; pGame->pClientList = nullptr; - if (gpD2EventCallbackTable_6FD45830 && gpD2EventCallbackTable_6FD45830->pfSetGameData) + if (gpD2EventCallbackTable_6FD45830 && gpD2EventCallbackTable_6FD45830->pfCreateClientInfo) { - pGame->nGameData = gpD2EventCallbackTable_6FD45830->pfSetGameData(); + pGame->pClientInfo = gpD2EventCallbackTable_6FD45830->pfCreateClientInfo(); } } diff --git a/source/D2Game/src/GAME/Game.cpp b/source/D2Game/src/GAME/Game.cpp index 0253a74ff..4bff91baf 100644 --- a/source/D2Game/src/GAME/Game.cpp +++ b/source/D2Game/src/GAME/Game.cpp @@ -1133,7 +1133,7 @@ void __fastcall GAME_JoinGame(int32_t dwClientId, uint16_t nGameId, int32_t nCla GAME_LogMessage(6, "[SERVER] SrvJoinGame: *** Unable to add client %d '%s' to game %d (unlocking character)", dwClientId, szClientName, nGameId); D2_ASSERT(gpD2EventCallbackTable_6FD45830->pfUnlockDatabaseCharacter); - gpD2EventCallbackTable_6FD45830->pfUnlockDatabaseCharacter(&pGame->nGameData, szClientName, szAccountName); + gpD2EventCallbackTable_6FD45830->pfUnlockDatabaseCharacter(&pGame->pClientInfo, szClientName, szAccountName); } D2_UNLOCK(pGame->lpCriticalSection);