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..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 @@ -129,6 +129,21 @@ 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); + 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 9a80717..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 @@ -1,5 +1,7 @@ #include +#include +#include #include #include #include @@ -590,6 +592,417 @@ 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); + }); +} + +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; + 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; +}; + +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(); + return luaCallProtected(L, nArgs, 0, [&](int) { + lua_getglobal(L, functionName); + pushArgs(L); + }); +} + +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, nArgs, 1, [&](int) { + lua_getglobal(L, functionName); + pushArgs(L); + })) { + 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 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 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) +{ + + // 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 - footerSize; + if (std::memcmp(pFooter, pSignature, signatureSize) != 0) { + return false; + } + + uint32_t payloadSize = 0; + 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; + } + + *ppPayload = pFooter - payloadSize; + *pPayloadSize = payloadSize; + 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) { + + // 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) { + tolua_pushusertype(L, pArea, "CGameArea"); + }); +} + +void EEex::UDAux_Hook_OnAreaContainerMarshal(CGameContainer* pContainer) { + + if (!udaAuxAreaMarshalContext.active) { + 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"); + }); +} + +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 (!callLuaUDAuxMemoryHandler("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) { + + // 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 }; + + unsigned char* pPayload = nullptr; + unsigned int payloadSize = 0; + if (!findUDAuxAreaExtension(pData, size, &pPayload, &payloadSize)) { + return; + } + + udaAuxAreaUnmarshalContext.active = true; + if (!callLuaUDAuxMemoryHandler("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); + }); +} + +void EEex::UDAux_Hook_OnBeforeStoreInventoryClear(CStore* pStore) { + + if (pStore == nullptr) { + return; + } + + // 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", 2, [&](lua_State* L) { + tolua_pushusertype(L, pStore, "CStore"); + 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", 2, [&](lua_State* L) { + tolua_pushusertype(L, pStore, "CStore"); + 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", 2, [&](lua_State* L) { + tolua_pushusertype(L, pStore, "CStore"); + 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) { @@ -3816,6 +4229,9 @@ 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 } @@ -3825,6 +4241,8 @@ 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 } @@ -4071,7 +4489,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 ] @@ -4106,6 +4525,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); + } + } } } 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..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 @@ -148,6 +148,21 @@ 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); + 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 // ////////////