From c2941e2c9f43cd28f742747cfe52c3cc70a0834d Mon Sep 17 00:00:00 2001 From: 4Luke4 Date: Fri, 22 May 2026 13:11:51 +0200 Subject: [PATCH 1/4] CGameEffect, CItem (sprite) --- EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp b/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp index 9a80717..460de22 100644 --- a/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp +++ b/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp @@ -590,6 +590,23 @@ void getUDAux(lua_State *const L, void *const ptr) { lua_remove(L, -2); // 1 [ ..., t ] } +static void callLuaUDAuxLightUDHandler(const char* functionName, void* ptr) { + lua_State *const L = luaState(); + luaCallProtected(L, 1, 0, [&](int) { + lua_getglobal(L, functionName); + lua_pushlightuserdata(L, ptr); + }); +} + +static void callLuaUDAuxLightUDHandler(const char* functionName, void* srcPtr, void* dstPtr) { + lua_State *const L = luaState(); + luaCallProtected(L, 2, 0, [&](int) { + lua_getglobal(L, functionName); + lua_pushlightuserdata(L, srcPtr); + lua_pushlightuserdata(L, dstPtr); + }); +} + // Expects: n [ ... ] // Returns: n + 1 [ ..., castPtrUD ] void getCastUD(lua_State* L, const char* castBaseName, const char* castFuncName, void* toCastPtr) { @@ -3816,6 +3833,7 @@ void EEex::Opcode_Hook_OnCopy(CGameEffect* pSrcEffect, CGameEffect* pDstEffect) STUTTER_LOG_START(void, "EEex::Opcode_Hook_OnCopy") exEffectInfoMap[pDstEffect] = exEffectInfoMap[pSrcEffect]; + callLuaUDAuxLightUDHandler("EEex_UDAux_Private_CopyByLightUD", pSrcEffect, pDstEffect); STUTTER_LOG_END } @@ -3825,6 +3843,7 @@ void EEex::Opcode_Hook_OnDestruct(CGameEffect* pEffect) { STUTTER_LOG_START(void, "EEex::Opcode_Hook_OnDestruct") exEffectInfoMap.erase(pEffect); + callLuaUDAuxLightUDHandler("EEex_UDAux_Private_DeleteByLightUD", pEffect); STUTTER_LOG_END } @@ -4106,6 +4125,12 @@ void CGameEffectList::Override_Unmarshal(byte* pData, uint nSize, CGameSprite* p this->AddTail(pEffect); } } + + if (pSprite != nullptr) { + for (auto* pNode = this->m_pNodeHead; pNode != nullptr; pNode = pNode->pNext) { + pNode->data->virtual_OnLoad(pSprite); + } + } } } From 6deaa38b8340ad3f214d6751e9df7fc6cb3db7ee Mon Sep 17 00:00:00 2001 From: 4Luke4 Date: Sat, 23 May 2026 12:24:58 +0200 Subject: [PATCH 2/4] CGameContainer, CItem (container) --- EEex-v2.6.6.0/headers/EEex-v2.6.6.0/EEex.h | 11 ++ EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp | 204 ++++++++++++++++++++ EEex-v2.6.6.0/source/EEex-v2.6.6.0/main.cpp | 11 ++ 3 files changed, 226 insertions(+) diff --git a/EEex-v2.6.6.0/headers/EEex-v2.6.6.0/EEex.h b/EEex-v2.6.6.0/headers/EEex-v2.6.6.0/EEex.h index edc3203..1de9260 100644 --- a/EEex-v2.6.6.0/headers/EEex-v2.6.6.0/EEex.h +++ b/EEex-v2.6.6.0/headers/EEex-v2.6.6.0/EEex.h @@ -129,6 +129,17 @@ namespace EEex { void Sprite_Hook_OnBeforeEffectListMarshalled(CGameSprite* pSprite); byte Sprite_Hook_OnGetAttackFrameType(CGameSprite* pSprite, byte numAttacks); + //////////// + // UDAux // + //////////// + + void UDAux_Hook_OnBeforeAreaMarshal(CGameArea* pArea, unsigned char** ppData, unsigned int* pSize); + void UDAux_Hook_OnAreaContainerMarshal(CGameContainer* pContainer); + void UDAux_Hook_OnAfterAreaMarshal(); + void UDAux_Hook_OnBeforeAreaUnmarshal(CGameArea* pArea, unsigned char* pData, unsigned int size); + void UDAux_Hook_OnAfterAreaContainerConstruct(CGameContainer* pContainer); + void UDAux_Hook_OnAfterAreaUnmarshal(int result); + //////////// // Action // //////////// diff --git a/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp b/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp index 460de22..f849962 100644 --- a/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp +++ b/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp @@ -1,5 +1,7 @@ #include +#include +#include #include #include #include @@ -607,6 +609,208 @@ static void callLuaUDAuxLightUDHandler(const char* functionName, void* srcPtr, v }); } +namespace { + +constexpr char UDAUX_AREA_EXTENSION_SIGNATURE[8] = { 'X', '-', 'U', 'D', 'A', '1', '.', '0' }; +constexpr unsigned int UDAUX_AREA_EXTENSION_FOOTER_SIZE = sizeof(UDAUX_AREA_EXTENSION_SIGNATURE) + sizeof(uint32_t); + +struct UDAuxAreaMarshalContext { + CGameArea* pArea = nullptr; + unsigned char** ppData = nullptr; + unsigned int* pSize = nullptr; + bool active = false; +}; + +struct UDAuxAreaUnmarshalContext { + CGameArea* pArea = nullptr; + unsigned char* pData = nullptr; + unsigned int size = 0; + bool active = false; +}; + +thread_local UDAuxAreaMarshalContext udaAuxAreaMarshalContext; +thread_local UDAuxAreaUnmarshalContext udaAuxAreaUnmarshalContext; + +static bool callLuaUDAuxNoReturn(const char* functionName, int nArgs, const std::function& pushArgs) { + lua_State *const L = luaState(); + return luaCallProtected(L, nArgs, 0, [&](int) { + lua_getglobal(L, functionName); + pushArgs(L); + }); +} + +static unsigned int callLuaUDAuxSizeReturn(const char* functionName) { + + lua_State *const L = luaState(); + unsigned int toReturn = 0; + + if (!luaCallProtected(L, 0, 1, [&](int) { + lua_getglobal(L, functionName); + })) { + return 0; + } + + if (!lua_isnumber(L, -1)) { + FPrint("[!][EEex.dll] %s() returned %s instead of number\n", functionName, lua_typename(L, lua_type(L, -1))); + } + else { + lua_Number luaSize = lua_tonumber(L, -1); + if (luaSize < 0 || luaSize > static_cast(UINT32_MAX) || luaSize != static_cast(static_cast(luaSize))) { + FPrint("[!][EEex.dll] %s() returned invalid size %.0f\n", functionName, luaSize); + } + else { + toReturn = static_cast(luaSize); + } + } + + lua_pop(L, 1); + return toReturn; +} + +static bool callLuaUDAuxAreaMemoryHandler(const char* functionName, unsigned char* pMemory, unsigned int size) { + return callLuaUDAuxNoReturn(functionName, 2, [&](lua_State* L) { + lua_pushinteger(L, reinterpret_cast(pMemory)); + lua_pushinteger(L, size); + }); +} + +static bool findUDAuxAreaExtension(unsigned char* pData, unsigned int size, unsigned char** ppPayload, unsigned int* pPayloadSize) { + + if (pData == nullptr || size < UDAUX_AREA_EXTENSION_FOOTER_SIZE) { + return false; + } + + unsigned char* const pFooter = pData + size - UDAUX_AREA_EXTENSION_FOOTER_SIZE; + if (std::memcmp(pFooter, UDAUX_AREA_EXTENSION_SIGNATURE, sizeof(UDAUX_AREA_EXTENSION_SIGNATURE)) != 0) { + return false; + } + + uint32_t payloadSize = 0; + std::memcpy(&payloadSize, pFooter + sizeof(UDAUX_AREA_EXTENSION_SIGNATURE), sizeof(payloadSize)); + if (payloadSize > size - UDAUX_AREA_EXTENSION_FOOTER_SIZE) { + FPrint("[!][EEex.dll] Ignoring malformed X-UDA1.0 area extension: payload size exceeds area size\n"); + return false; + } + + *ppPayload = pFooter - payloadSize; + *pPayloadSize = payloadSize; + return true; +} + +} + +void EEex::UDAux_Hook_OnBeforeAreaMarshal(CGameArea* pArea, unsigned char** ppData, unsigned int* pSize) { + + udaAuxAreaMarshalContext = { pArea, ppData, pSize, true }; + + callLuaUDAuxNoReturn("EEex_UDAux_Private_BeginAreaMarshal", 1, [&](lua_State* L) { + tolua_pushusertype(L, pArea, "CGameArea"); + }); +} + +void EEex::UDAux_Hook_OnAreaContainerMarshal(CGameContainer* pContainer) { + + if (!udaAuxAreaMarshalContext.active) { + return; + } + + callLuaUDAuxNoReturn("EEex_UDAux_Private_OnAreaContainerMarshal", 1, [&](lua_State* L) { + tolua_pushusertype(L, pContainer, "CGameContainer"); + }); +} + +void EEex::UDAux_Hook_OnAfterAreaMarshal() { + + if (!udaAuxAreaMarshalContext.active) { + return; + } + + UDAuxAreaMarshalContext context = udaAuxAreaMarshalContext; + udaAuxAreaMarshalContext = {}; + + if (context.ppData == nullptr || context.pSize == nullptr || *context.ppData == nullptr) { + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndAreaMarshal", 0, [](lua_State*) {}); + return; + } + + const unsigned int payloadSize = callLuaUDAuxSizeReturn("EEex_UDAux_Private_CalculateAreaMarshalExtensionSize"); + if (payloadSize == 0) { + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndAreaMarshal", 0, [](lua_State*) {}); + return; + } + + const unsigned int oldSize = *context.pSize; + if (oldSize > UINT32_MAX - payloadSize - UDAUX_AREA_EXTENSION_FOOTER_SIZE) { + FPrint("[!][EEex.dll] Could not append X-UDA1.0 area extension: size overflow\n"); + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndAreaMarshal", 0, [](lua_State*) {}); + return; + } + + const unsigned int newSize = oldSize + payloadSize + UDAUX_AREA_EXTENSION_FOOTER_SIZE; + unsigned char* const pNewData = reinterpret_cast(p_malloc(newSize)); + if (pNewData == nullptr) { + FPrint("[!][EEex.dll] Could not append X-UDA1.0 area extension: allocation failed\n"); + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndAreaMarshal", 0, [](lua_State*) {}); + return; + } + + std::memcpy(pNewData, *context.ppData, oldSize); + if (!callLuaUDAuxAreaMemoryHandler("EEex_UDAux_Private_WriteAreaMarshalExtensionPayload", pNewData + oldSize, payloadSize)) { + p_free(pNewData); + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndAreaMarshal", 0, [](lua_State*) {}); + return; + } + std::memcpy(pNewData + oldSize + payloadSize, UDAUX_AREA_EXTENSION_SIGNATURE, sizeof(UDAUX_AREA_EXTENSION_SIGNATURE)); + std::memcpy(pNewData + oldSize + payloadSize + sizeof(UDAUX_AREA_EXTENSION_SIGNATURE), &payloadSize, sizeof(payloadSize)); + + p_free(*context.ppData); + *context.ppData = pNewData; + *context.pSize = newSize; + + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndAreaMarshal", 0, [](lua_State*) {}); +} + +void EEex::UDAux_Hook_OnBeforeAreaUnmarshal(CGameArea* pArea, unsigned char* pData, unsigned int size) { + + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndAreaUnmarshal", 0, [](lua_State*) {}); + udaAuxAreaUnmarshalContext = { pArea, pData, size, false }; + + unsigned char* pPayload = nullptr; + unsigned int payloadSize = 0; + if (!findUDAuxAreaExtension(pData, size, &pPayload, &payloadSize)) { + return; + } + + udaAuxAreaUnmarshalContext.active = true; + if (!callLuaUDAuxAreaMemoryHandler("EEex_UDAux_Private_BeginAreaUnmarshal", pPayload, payloadSize)) { + udaAuxAreaUnmarshalContext.active = false; + } +} + +void EEex::UDAux_Hook_OnAfterAreaContainerConstruct(CGameContainer* pContainer) { + + if (!udaAuxAreaUnmarshalContext.active) { + return; + } + + callLuaUDAuxNoReturn("EEex_UDAux_Private_OnAreaContainerConstruct", 1, [&](lua_State* L) { + tolua_pushusertype(L, pContainer, "CGameContainer"); + }); +} + +void EEex::UDAux_Hook_OnAfterAreaUnmarshal(int result) { + + if (!udaAuxAreaUnmarshalContext.active) { + udaAuxAreaUnmarshalContext = {}; + return; + } + + udaAuxAreaUnmarshalContext = {}; + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndAreaUnmarshal", 1, [&](lua_State* L) { + lua_pushinteger(L, result); + }); +} + // Expects: n [ ... ] // Returns: n + 1 [ ..., castPtrUD ] void getCastUD(lua_State* L, const char* castBaseName, const char* castFuncName, void* toCastPtr) { diff --git a/EEex-v2.6.6.0/source/EEex-v2.6.6.0/main.cpp b/EEex-v2.6.6.0/source/EEex-v2.6.6.0/main.cpp index a0cb76e..bafcf68 100644 --- a/EEex-v2.6.6.0/source/EEex-v2.6.6.0/main.cpp +++ b/EEex-v2.6.6.0/source/EEex-v2.6.6.0/main.cpp @@ -148,6 +148,17 @@ static void exportPatterns() { exportPattern(TEXT("EEex::Sprite_Hook_OnBeforeEffectListMarshalled"), EEex::Sprite_Hook_OnBeforeEffectListMarshalled); exportPattern(TEXT("EEex::Sprite_Hook_OnGetAttackFrameType"), EEex::Sprite_Hook_OnGetAttackFrameType); + //////////// + // UDAux // + //////////// + + exportPattern(TEXT("EEex::UDAux_Hook_OnBeforeAreaMarshal"), EEex::UDAux_Hook_OnBeforeAreaMarshal); + exportPattern(TEXT("EEex::UDAux_Hook_OnAreaContainerMarshal"), EEex::UDAux_Hook_OnAreaContainerMarshal); + exportPattern(TEXT("EEex::UDAux_Hook_OnAfterAreaMarshal"), EEex::UDAux_Hook_OnAfterAreaMarshal); + exportPattern(TEXT("EEex::UDAux_Hook_OnBeforeAreaUnmarshal"), EEex::UDAux_Hook_OnBeforeAreaUnmarshal); + exportPattern(TEXT("EEex::UDAux_Hook_OnAfterAreaContainerConstruct"), EEex::UDAux_Hook_OnAfterAreaContainerConstruct); + exportPattern(TEXT("EEex::UDAux_Hook_OnAfterAreaUnmarshal"), EEex::UDAux_Hook_OnAfterAreaUnmarshal); + //////////// // Action // //////////// From 3ef1f83282c6ec2407866c232777bc2cf879d9e9 Mon Sep 17 00:00:00 2001 From: 4Luke4 Date: Sat, 23 May 2026 22:31:07 +0200 Subject: [PATCH 3/4] CStoreFileItem --- EEex-v2.6.6.0/headers/EEex-v2.6.6.0/EEex.h | 4 + EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp | 219 ++++++++++++++++++-- EEex-v2.6.6.0/source/EEex-v2.6.6.0/main.cpp | 4 + 3 files changed, 214 insertions(+), 13 deletions(-) diff --git a/EEex-v2.6.6.0/headers/EEex-v2.6.6.0/EEex.h b/EEex-v2.6.6.0/headers/EEex-v2.6.6.0/EEex.h index 1de9260..db119d4 100644 --- a/EEex-v2.6.6.0/headers/EEex-v2.6.6.0/EEex.h +++ b/EEex-v2.6.6.0/headers/EEex-v2.6.6.0/EEex.h @@ -139,6 +139,10 @@ namespace EEex { void UDAux_Hook_OnBeforeAreaUnmarshal(CGameArea* pArea, unsigned char* pData, unsigned int size); void UDAux_Hook_OnAfterAreaContainerConstruct(CGameContainer* pContainer); void UDAux_Hook_OnAfterAreaUnmarshal(int result); + void UDAux_Hook_OnBeforeStoreInventoryClear(CStore* pStore); + unsigned char* UDAux_Hook_OnStoreMarshalData(CStore* pStore, unsigned char* pData, unsigned int size, unsigned int* pOutSize); + void UDAux_Hook_OnBeforeStoreLoad(CStore* pStore, unsigned char* pData, CResStore* pResStore); + void UDAux_Hook_OnAfterStoreLoad(CStore* pStore); //////////// // Action // diff --git a/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp b/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp index f849962..55f54df 100644 --- a/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp +++ b/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp @@ -613,6 +613,12 @@ namespace { constexpr char UDAUX_AREA_EXTENSION_SIGNATURE[8] = { 'X', '-', 'U', 'D', 'A', '1', '.', '0' }; constexpr unsigned int UDAUX_AREA_EXTENSION_FOOTER_SIZE = sizeof(UDAUX_AREA_EXTENSION_SIGNATURE) + sizeof(uint32_t); +constexpr char UDAUX_STORE_EXTENSION_SIGNATURE[8] = { 'X', '-', 'U', 'D', 'S', '1', '.', '0' }; +constexpr unsigned int UDAUX_STORE_EXTENSION_FOOTER_SIZE = sizeof(UDAUX_STORE_EXTENSION_SIGNATURE) + sizeof(uint32_t); + +// Store load hooks receive the engine's CRes* pointer, including the vfptr. The generated +// CRes binding omits that vfptr, so read nSize by the documented engine offset instead. +constexpr size_t CRES_NSIZE_ENGINE_OFFSET = 0x48; struct UDAuxAreaMarshalContext { CGameArea* pArea = nullptr; @@ -628,8 +634,14 @@ struct UDAuxAreaUnmarshalContext { bool active = false; }; +struct UDAuxStoreUnmarshalContext { + CStore* pStore = nullptr; + bool active = false; +}; + thread_local UDAuxAreaMarshalContext udaAuxAreaMarshalContext; thread_local UDAuxAreaUnmarshalContext udaAuxAreaUnmarshalContext; +thread_local UDAuxStoreUnmarshalContext udaAuxStoreUnmarshalContext; static bool callLuaUDAuxNoReturn(const char* functionName, int nArgs, const std::function& pushArgs) { lua_State *const L = luaState(); @@ -639,13 +651,14 @@ static bool callLuaUDAuxNoReturn(const char* functionName, int nArgs, const std: }); } -static unsigned int callLuaUDAuxSizeReturn(const char* functionName) { +static unsigned int callLuaUDAuxSizeReturn(const char* functionName, int nArgs, const std::function& pushArgs) { lua_State *const L = luaState(); unsigned int toReturn = 0; - if (!luaCallProtected(L, 0, 1, [&](int) { + if (!luaCallProtected(L, nArgs, 1, [&](int) { lua_getglobal(L, functionName); + pushArgs(L); })) { return 0; } @@ -667,28 +680,43 @@ static unsigned int callLuaUDAuxSizeReturn(const char* functionName) { return toReturn; } -static bool callLuaUDAuxAreaMemoryHandler(const char* functionName, unsigned char* pMemory, unsigned int size) { +static unsigned int callLuaUDAuxSizeReturn(const char* functionName) { + return callLuaUDAuxSizeReturn(functionName, 0, [](lua_State*) {}); +} + +static bool callLuaUDAuxMemoryHandler(const char* functionName, unsigned char* pMemory, unsigned int size) { return callLuaUDAuxNoReturn(functionName, 2, [&](lua_State* L) { lua_pushinteger(L, reinterpret_cast(pMemory)); lua_pushinteger(L, size); }); } -static bool findUDAuxAreaExtension(unsigned char* pData, unsigned int size, unsigned char** ppPayload, unsigned int* pPayloadSize) { +static bool findUDAuxExtension( + unsigned char* pData, + unsigned int size, + const char* pSignature, + unsigned int signatureSize, + unsigned int footerSize, + const char* extensionName, + unsigned char** ppPayload, + unsigned int* pPayloadSize) +{ - if (pData == nullptr || size < UDAUX_AREA_EXTENSION_FOOTER_SIZE) { + // EEex extensions are appended as: original data, payload, 8-byte signature, uint32 payload size. + // This keeps legacy readers compatible because the base resource remains byte-for-byte first. + if (pData == nullptr || size < footerSize) { return false; } - unsigned char* const pFooter = pData + size - UDAUX_AREA_EXTENSION_FOOTER_SIZE; - if (std::memcmp(pFooter, UDAUX_AREA_EXTENSION_SIGNATURE, sizeof(UDAUX_AREA_EXTENSION_SIGNATURE)) != 0) { + unsigned char* const pFooter = pData + size - footerSize; + if (std::memcmp(pFooter, pSignature, signatureSize) != 0) { return false; } uint32_t payloadSize = 0; - std::memcpy(&payloadSize, pFooter + sizeof(UDAUX_AREA_EXTENSION_SIGNATURE), sizeof(payloadSize)); - if (payloadSize > size - UDAUX_AREA_EXTENSION_FOOTER_SIZE) { - FPrint("[!][EEex.dll] Ignoring malformed X-UDA1.0 area extension: payload size exceeds area size\n"); + std::memcpy(&payloadSize, pFooter + signatureSize, sizeof(payloadSize)); + if (payloadSize > size - footerSize) { + FPrint("[!][EEex.dll] Ignoring malformed %s extension: payload size exceeds resource size\n", extensionName); return false; } @@ -697,10 +725,62 @@ static bool findUDAuxAreaExtension(unsigned char* pData, unsigned int size, unsi return true; } +static bool findUDAuxAreaExtension(unsigned char* pData, unsigned int size, unsigned char** ppPayload, unsigned int* pPayloadSize) { + return findUDAuxExtension( + pData, + size, + UDAUX_AREA_EXTENSION_SIGNATURE, + sizeof(UDAUX_AREA_EXTENSION_SIGNATURE), + UDAUX_AREA_EXTENSION_FOOTER_SIZE, + "X-UDA1.0 area", + ppPayload, + pPayloadSize + ); +} + +static bool findUDAuxStoreExtension(unsigned char* pData, unsigned int size, unsigned char** ppPayload, unsigned int* pPayloadSize) { + return findUDAuxExtension( + pData, + size, + UDAUX_STORE_EXTENSION_SIGNATURE, + sizeof(UDAUX_STORE_EXTENSION_SIGNATURE), + UDAUX_STORE_EXTENSION_FOOTER_SIZE, + "X-UDS1.0 store", + ppPayload, + pPayloadSize + ); +} + +static unsigned int getCResEngineSize(CRes* pRes) { + return *reinterpret_cast(reinterpret_cast(pRes) + CRES_NSIZE_ENGINE_OFFSET); +} + +static void pushUDAuxStoreItemPointerTable(lua_State* L, CStore* pStore) { + + // CStore is not exposed as a Lua userdata type. Snapshot ordinal -> pointer in C++ and let + // Lua wrap individual CStoreFileItem pointers, which are valid until the inventory is cleared. + lua_newtable(L); + if (pStore == nullptr) { + return; + } + + int index = 1; + for (auto* pNode = pStore->m_lInventory.m_pNodeHead; pNode != nullptr; pNode = pNode->pNext) { + if (pNode->data != nullptr) { + lua_pushinteger(L, index); + lua_pushinteger(L, reinterpret_cast(pNode->data)); + lua_rawset(L, -3); + } + ++index; + } +} + } void EEex::UDAux_Hook_OnBeforeAreaMarshal(CGameArea* pArea, unsigned char** ppData, unsigned int* pSize) { + // CGameArea::Marshal invokes CGameContainer::Marshal for each container. Keep the area output + // pointers here so the after-marshal hook can append one extension after the vanilla blob exists. udaAuxAreaMarshalContext = { pArea, ppData, pSize, true }; callLuaUDAuxNoReturn("EEex_UDAux_Private_BeginAreaMarshal", 1, [&](lua_State* L) { @@ -714,6 +794,7 @@ void EEex::UDAux_Hook_OnAreaContainerMarshal(CGameContainer* pContainer) { return; } + // The callback order is the save order, so Lua can persist container and item aux by ordinal. callLuaUDAuxNoReturn("EEex_UDAux_Private_OnAreaContainerMarshal", 1, [&](lua_State* L) { tolua_pushusertype(L, pContainer, "CGameContainer"); }); @@ -755,7 +836,7 @@ void EEex::UDAux_Hook_OnAfterAreaMarshal() { } std::memcpy(pNewData, *context.ppData, oldSize); - if (!callLuaUDAuxAreaMemoryHandler("EEex_UDAux_Private_WriteAreaMarshalExtensionPayload", pNewData + oldSize, payloadSize)) { + if (!callLuaUDAuxMemoryHandler("EEex_UDAux_Private_WriteAreaMarshalExtensionPayload", pNewData + oldSize, payloadSize)) { p_free(pNewData); callLuaUDAuxNoReturn("EEex_UDAux_Private_EndAreaMarshal", 0, [](lua_State*) {}); return; @@ -772,6 +853,8 @@ void EEex::UDAux_Hook_OnAfterAreaMarshal() { void EEex::UDAux_Hook_OnBeforeAreaUnmarshal(CGameArea* pArea, unsigned char* pData, unsigned int size) { + // Parse the extension before vanilla unmarshal creates containers. Container aux is applied + // from the constructor hook, after each CGameContainer has rebuilt its item list. callLuaUDAuxNoReturn("EEex_UDAux_Private_EndAreaUnmarshal", 0, [](lua_State*) {}); udaAuxAreaUnmarshalContext = { pArea, pData, size, false }; @@ -782,7 +865,7 @@ void EEex::UDAux_Hook_OnBeforeAreaUnmarshal(CGameArea* pArea, unsigned char* pDa } udaAuxAreaUnmarshalContext.active = true; - if (!callLuaUDAuxAreaMemoryHandler("EEex_UDAux_Private_BeginAreaUnmarshal", pPayload, payloadSize)) { + if (!callLuaUDAuxMemoryHandler("EEex_UDAux_Private_BeginAreaUnmarshal", pPayload, payloadSize)) { udaAuxAreaUnmarshalContext.active = false; } } @@ -811,6 +894,112 @@ void EEex::UDAux_Hook_OnAfterAreaUnmarshal(int result) { }); } +void EEex::UDAux_Hook_OnBeforeStoreInventoryClear(CStore* pStore) { + + if (pStore == nullptr) { + return; + } + + // SetResRef and destruction both drain m_lInventory. Delete aux before the CStoreFileItem + // allocations disappear, and cancel any pending import for this store before old data wins. + if (udaAuxStoreUnmarshalContext.pStore == pStore) { + udaAuxStoreUnmarshalContext = {}; + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndStoreUnmarshal", 0, [](lua_State*) {}); + } + + callLuaUDAuxNoReturn("EEex_UDAux_Private_OnStoreInventoryClear", 1, [&](lua_State* L) { + pushUDAuxStoreItemPointerTable(L, pStore); + }); +} + +unsigned char* EEex::UDAux_Hook_OnStoreMarshalData(CStore* pStore, unsigned char* pData, unsigned int size, unsigned int* pOutSize) { + + if (pOutSize != nullptr) { + *pOutSize = size; + } + + if (pStore == nullptr || pData == nullptr || pOutSize == nullptr) { + return pData; + } + + const unsigned int payloadSize = callLuaUDAuxSizeReturn("EEex_UDAux_Private_CalculateStoreMarshalExtensionSize", 1, [&](lua_State* L) { + pushUDAuxStoreItemPointerTable(L, pStore); + }); + if (payloadSize == 0) { + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndStoreMarshal", 0, [](lua_State*) {}); + return pData; + } + + if (size > UINT32_MAX - payloadSize - UDAUX_STORE_EXTENSION_FOOTER_SIZE) { + FPrint("[!][EEex.dll] Could not append X-UDS1.0 store extension: size overflow\n"); + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndStoreMarshal", 0, [](lua_State*) {}); + return pData; + } + + const unsigned int newSize = size + payloadSize + UDAUX_STORE_EXTENSION_FOOTER_SIZE; + unsigned char* const pNewData = reinterpret_cast(p_malloc(newSize)); + if (pNewData == nullptr) { + FPrint("[!][EEex.dll] Could not append X-UDS1.0 store extension: allocation failed\n"); + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndStoreMarshal", 0, [](lua_State*) {}); + return pData; + } + + std::memcpy(pNewData, pData, size); + if (!callLuaUDAuxMemoryHandler("EEex_UDAux_Private_WriteStoreMarshalExtensionPayload", pNewData + size, payloadSize)) { + p_free(pNewData); + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndStoreMarshal", 0, [](lua_State*) {}); + return pData; + } + std::memcpy(pNewData + size + payloadSize, UDAUX_STORE_EXTENSION_SIGNATURE, sizeof(UDAUX_STORE_EXTENSION_SIGNATURE)); + std::memcpy(pNewData + size + payloadSize + sizeof(UDAUX_STORE_EXTENSION_SIGNATURE), &payloadSize, sizeof(payloadSize)); + + // The following dimmServiceFromMemory() call consumes the pointer and size we return here. + // Free only the original transient marshal buffer after the replacement is fully populated. + p_free(pData); + *pOutSize = newSize; + + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndStoreMarshal", 0, [](lua_State*) {}); + return pNewData; +} + +void EEex::UDAux_Hook_OnBeforeStoreLoad(CStore* pStore, unsigned char* pData, CResStore* pResStore) { + + // Store resources are demanded before m_lInventory is rebuilt. Capture the extension now, + // then apply it after SetResRef has copied file items into runtime CStoreFileItem nodes. + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndStoreUnmarshal", 0, [](lua_State*) {}); + udaAuxStoreUnmarshalContext = { pStore, false }; + + if (pStore == nullptr || pData == nullptr || pResStore == nullptr) { + return; + } + + unsigned char* pPayload = nullptr; + unsigned int payloadSize = 0; + if (!findUDAuxStoreExtension(pData, getCResEngineSize(pResStore), &pPayload, &payloadSize)) { + return; + } + + udaAuxStoreUnmarshalContext.active = true; + if (!callLuaUDAuxMemoryHandler("EEex_UDAux_Private_BeginStoreUnmarshal", pPayload, payloadSize)) { + udaAuxStoreUnmarshalContext.active = false; + } +} + +void EEex::UDAux_Hook_OnAfterStoreLoad(CStore* pStore) { + + if (!udaAuxStoreUnmarshalContext.active || udaAuxStoreUnmarshalContext.pStore != pStore) { + udaAuxStoreUnmarshalContext = {}; + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndStoreUnmarshal", 0, [](lua_State*) {}); + return; + } + + udaAuxStoreUnmarshalContext = {}; + callLuaUDAuxNoReturn("EEex_UDAux_Private_OnStoreLoaded", 1, [&](lua_State* L) { + pushUDAuxStoreItemPointerTable(L, pStore); + }); + callLuaUDAuxNoReturn("EEex_UDAux_Private_EndStoreUnmarshal", 0, [](lua_State*) {}); +} + // Expects: n [ ... ] // Returns: n + 1 [ ..., castPtrUD ] void getCastUD(lua_State* L, const char* castBaseName, const char* castFuncName, void* toCastPtr) { @@ -4037,6 +4226,8 @@ void EEex::Opcode_Hook_OnCopy(CGameEffect* pSrcEffect, CGameEffect* pDstEffect) STUTTER_LOG_START(void, "EEex::Opcode_Hook_OnCopy") exEffectInfoMap[pDstEffect] = exEffectInfoMap[pSrcEffect]; + // Effect copies are real engine-owned effects; persistent aux follows the copy so later + // save/load sees the copied effect state, not the source pointer's lifetime. callLuaUDAuxLightUDHandler("EEex_UDAux_Private_CopyByLightUD", pSrcEffect, pDstEffect); STUTTER_LOG_END @@ -4047,6 +4238,7 @@ void EEex::Opcode_Hook_OnDestruct(CGameEffect* pEffect) { STUTTER_LOG_START(void, "EEex::Opcode_Hook_OnDestruct") exEffectInfoMap.erase(pEffect); + // Effects own their userdata lifetime. Clear both transient and persistent UDAux state here. callLuaUDAuxLightUDHandler("EEex_UDAux_Private_DeleteByLightUD", pEffect); STUTTER_LOG_END @@ -4294,7 +4486,8 @@ void CGameEffectList::Override_Unmarshal(byte* pData, uint nSize, CGameSprite* p if (memcmp(&pEffectBase->m_version, "X-BIV1.0", 8) == 0) { // Is this effect the start of EEex's binary data? - // Call out to Lua to parse it + // Call out to Lua before OnLoad runs for any decoded v2 effect, so persistent + // CGameEffect UDAux is restored by ordinal before scripts observe the effect. if (luaCallProtected(L, 2, 1, [&](int) { lua_getglobal(L, "EEex_Sprite_LuaHook_ReadExtraEffectListUnmarshal"); // 1 [ ..., EEex_Sprite_LuaHook_ReadExtraEffectListUnmarshal ] tolua_pushusertype(L, pSprite, "CGameSprite"); // 2 [ ..., EEex_Sprite_LuaHook_ReadExtraEffectListUnmarshal, pSpriteUD ] diff --git a/EEex-v2.6.6.0/source/EEex-v2.6.6.0/main.cpp b/EEex-v2.6.6.0/source/EEex-v2.6.6.0/main.cpp index bafcf68..a1b856c 100644 --- a/EEex-v2.6.6.0/source/EEex-v2.6.6.0/main.cpp +++ b/EEex-v2.6.6.0/source/EEex-v2.6.6.0/main.cpp @@ -158,6 +158,10 @@ static void exportPatterns() { exportPattern(TEXT("EEex::UDAux_Hook_OnBeforeAreaUnmarshal"), EEex::UDAux_Hook_OnBeforeAreaUnmarshal); exportPattern(TEXT("EEex::UDAux_Hook_OnAfterAreaContainerConstruct"), EEex::UDAux_Hook_OnAfterAreaContainerConstruct); exportPattern(TEXT("EEex::UDAux_Hook_OnAfterAreaUnmarshal"), EEex::UDAux_Hook_OnAfterAreaUnmarshal); + exportPattern(TEXT("EEex::UDAux_Hook_OnBeforeStoreInventoryClear"), EEex::UDAux_Hook_OnBeforeStoreInventoryClear); + exportPattern(TEXT("EEex::UDAux_Hook_OnStoreMarshalData"), EEex::UDAux_Hook_OnStoreMarshalData); + exportPattern(TEXT("EEex::UDAux_Hook_OnBeforeStoreLoad"), EEex::UDAux_Hook_OnBeforeStoreLoad); + exportPattern(TEXT("EEex::UDAux_Hook_OnAfterStoreLoad"), EEex::UDAux_Hook_OnAfterStoreLoad); //////////// // Action // From 2b63e2283726ab5047defb55800f0a9fc1694134 Mon Sep 17 00:00:00 2001 From: 4Luke4 Date: Sun, 31 May 2026 13:06:02 +0200 Subject: [PATCH 4/4] CStore --- EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp b/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp index 55f54df..6c9d62d 100644 --- a/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp +++ b/EEex-v2.6.6.0/source/EEex-v2.6.6.0/EEex.cpp @@ -757,8 +757,8 @@ static unsigned int getCResEngineSize(CRes* pRes) { static void pushUDAuxStoreItemPointerTable(lua_State* L, CStore* pStore) { - // CStore is not exposed as a Lua userdata type. Snapshot ordinal -> pointer in C++ and let - // Lua wrap individual CStoreFileItem pointers, which are valid until the inventory is cleared. + // Snapshot ordinal -> pointer in C++ and let Lua wrap individual CStoreFileItem pointers, + // which are valid until the inventory is cleared. lua_newtable(L); if (pStore == nullptr) { return; @@ -900,14 +900,15 @@ void EEex::UDAux_Hook_OnBeforeStoreInventoryClear(CStore* pStore) { return; } - // SetResRef and destruction both drain m_lInventory. Delete aux before the CStoreFileItem - // allocations disappear, and cancel any pending import for this store before old data wins. + // SetResRef and destruction both drain m_lInventory. Delete store/item aux before + // CStoreFileItem allocations disappear, and cancel stale pending imports for this store. if (udaAuxStoreUnmarshalContext.pStore == pStore) { udaAuxStoreUnmarshalContext = {}; callLuaUDAuxNoReturn("EEex_UDAux_Private_EndStoreUnmarshal", 0, [](lua_State*) {}); } - callLuaUDAuxNoReturn("EEex_UDAux_Private_OnStoreInventoryClear", 1, [&](lua_State* L) { + callLuaUDAuxNoReturn("EEex_UDAux_Private_OnStoreInventoryClear", 2, [&](lua_State* L) { + tolua_pushusertype(L, pStore, "CStore"); pushUDAuxStoreItemPointerTable(L, pStore); }); } @@ -922,7 +923,8 @@ unsigned char* EEex::UDAux_Hook_OnStoreMarshalData(CStore* pStore, unsigned char return pData; } - const unsigned int payloadSize = callLuaUDAuxSizeReturn("EEex_UDAux_Private_CalculateStoreMarshalExtensionSize", 1, [&](lua_State* L) { + const unsigned int payloadSize = callLuaUDAuxSizeReturn("EEex_UDAux_Private_CalculateStoreMarshalExtensionSize", 2, [&](lua_State* L) { + tolua_pushusertype(L, pStore, "CStore"); pushUDAuxStoreItemPointerTable(L, pStore); }); if (payloadSize == 0) { @@ -994,7 +996,8 @@ void EEex::UDAux_Hook_OnAfterStoreLoad(CStore* pStore) { } udaAuxStoreUnmarshalContext = {}; - callLuaUDAuxNoReturn("EEex_UDAux_Private_OnStoreLoaded", 1, [&](lua_State* L) { + callLuaUDAuxNoReturn("EEex_UDAux_Private_OnStoreLoaded", 2, [&](lua_State* L) { + tolua_pushusertype(L, pStore, "CStore"); pushUDAuxStoreItemPointerTable(L, pStore); }); callLuaUDAuxNoReturn("EEex_UDAux_Private_EndStoreUnmarshal", 0, [](lua_State*) {});