From 8f8996b73b93390a597f7f45574bb95446e49255 Mon Sep 17 00:00:00 2001 From: 4Luke4 Date: Fri, 22 May 2026 13:17:28 +0200 Subject: [PATCH 1/5] CGameEffect, CItem (sprite) --- EEex/copy/EEex_scripts/EEex_Assembly.lua | 95 ++++++++++++++++++++- EEex/copy/EEex_scripts/EEex_Marshal.lua | 102 +++++++++++++++++++++++ 2 files changed, 195 insertions(+), 2 deletions(-) diff --git a/EEex/copy/EEex_scripts/EEex_Assembly.lua b/EEex/copy/EEex_scripts/EEex_Assembly.lua index d368165e..d9aaa7e2 100644 --- a/EEex/copy/EEex_scripts/EEex_Assembly.lua +++ b/EEex/copy/EEex_scripts/EEex_Assembly.lua @@ -1287,25 +1287,116 @@ end ----------------------- EEex_UserDataAuxiliary = {} +EEex_UserDataAuxiliaryPersistent = {} + +function EEex_UDAux_Private_AssertPersistentValue(value, path, seenTables) + local valueType = type(value) + if valueType == "boolean" or valueType == "string" then + return value + elseif valueType == "number" then + if value ~= value or value % 1 ~= 0 then + EEex_Error("Persistent UDAux value at "..path.." must be an integer number") + end + if value < -0x10000000000000000 or value > 0xFFFFFFFFFFFFFFFF then + EEex_Error("Persistent UDAux number at "..path.." is outside the supported marshal range") + end + return value + elseif valueType ~= "table" then + EEex_Error("Persistent UDAux value at "..path.." has unsupported type "..valueType) + end + + if seenTables[value] then + EEex_Error("Persistent UDAux table cycle detected at "..path) + end + seenTables[value] = true + + local copy = {} + for k, v in pairs(value) do + local kType = type(k) + if kType ~= "boolean" and kType ~= "number" and kType ~= "string" then + EEex_Error("Persistent UDAux key at "..path.." has unsupported type "..kType) + end + local keyPath = path.."["..tostring(k).."]" + copy[EEex_UDAux_Private_AssertPersistentValue(k, path.."", seenTables)] = + EEex_UDAux_Private_AssertPersistentValue(v, keyPath, seenTables) + end + + seenTables[value] = nil + return copy +end + +function EEex_UDAux_Private_IsLightUDPersistent(lud) + return EEex_UserDataAuxiliaryPersistent[lud] == true +end + +function EEex_UDAux_Private_IsPersistent(ud) + return EEex_UDAux_Private_IsLightUDPersistent(EEex_UDToLightUD(ud)) +end + +function EEex_UDAux_Private_Export(ud, path) + local lud = EEex_UDToLightUD(ud) + if not EEex_UDAux_Private_IsLightUDPersistent(lud) then + return nil + end + + local auxiliary = EEex_UserDataAuxiliary[lud] + if not auxiliary or next(auxiliary) == nil then + return nil + end + return EEex_UDAux_Private_AssertPersistentValue(auxiliary, path or "persistent UDAux", {}) +end + +function EEex_UDAux_Private_Import(ud, auxiliary) + local lud = EEex_UDToLightUD(ud) + EEex_UserDataAuxiliary[lud] = EEex_UDAux_Private_AssertPersistentValue(auxiliary, "persistent UDAux import", {}) + EEex_UserDataAuxiliaryPersistent[lud] = true + return EEex_UserDataAuxiliary[lud] +end + +function EEex_UDAux_Private_CopyByLightUD(srcLud, dstLud) + local srcAuxiliary = EEex_UserDataAuxiliary[srcLud] + if EEex_UserDataAuxiliaryPersistent[srcLud] and srcAuxiliary then + EEex_UserDataAuxiliary[dstLud] = EEex_UDAux_Private_AssertPersistentValue(srcAuxiliary, "persistent UDAux copy", {}) + else + EEex_UserDataAuxiliary[dstLud] = nil + end + + if EEex_UserDataAuxiliaryPersistent[srcLud] then + EEex_UserDataAuxiliaryPersistent[dstLud] = true + else + EEex_UserDataAuxiliaryPersistent[dstLud] = nil + end +end + +function EEex_UDAux_Private_DeleteByLightUD(lud) + EEex_UserDataAuxiliary[lud] = nil + EEex_UserDataAuxiliaryPersistent[lud] = nil +end function EEex_DeleteUserDataAuxiliary(ud) if type(ud) ~= "userdata" then EEex_Error("ud is not a userdata object ("..type(ud)..")!") end - EEex_UserDataAuxiliary[EEex_UDToLightUD(ud)] = nil + EEex_UDAux_Private_DeleteByLightUD(EEex_UDToLightUD(ud)) end EEex_DeleteUDAux = EEex_DeleteUserDataAuxiliary -function EEex_GetUserDataAuxiliary(ud) +function EEex_GetUserDataAuxiliary(ud, persistent) if type(ud) ~= "userdata" then EEex_Error("ud is not a userdata object ("..type(ud)..")!") end + if persistent ~= nil and type(persistent) ~= "boolean" then + EEex_Error("persistent must be nil or a boolean ("..type(persistent)..")!") + end local lud = EEex_UDToLightUD(ud) local auxiliary = EEex_UserDataAuxiliary[lud] if not auxiliary then auxiliary = {} EEex_UserDataAuxiliary[lud] = auxiliary end + if persistent then + EEex_UserDataAuxiliaryPersistent[lud] = true + end return auxiliary end EEex_GetUDAux = EEex_GetUserDataAuxiliary diff --git a/EEex/copy/EEex_scripts/EEex_Marshal.lua b/EEex/copy/EEex_scripts/EEex_Marshal.lua index 84c18d56..21c2d0ec 100644 --- a/EEex/copy/EEex_scripts/EEex_Marshal.lua +++ b/EEex/copy/EEex_scripts/EEex_Marshal.lua @@ -15,17 +15,119 @@ EEex_Sprite_AddMarshalHandlers("EEex", end ) +function EEex_Marshal_Private_ApplyPendingItemUDAux(sprite) + local spriteAux = EEex_TryGetUDAux(sprite) + local pendingItems = spriteAux and spriteAux["EEex_UDAux_PendingItemAux"] + if not pendingItems then + return + end + + local anyPending = false + for i = 0, 38 do + local auxiliary = pendingItems[i] + if auxiliary then + local item = sprite.m_equipment.m_items:get(i) + if item then + EEex_UDAux_Private_Import(item, auxiliary) + pendingItems[i] = nil + else + anyPending = true + end + end + end + + if not anyPending then + spriteAux["EEex_UDAux_PendingItemAux"] = nil + end +end + +EEex_Sprite_AddMarshalHandlers("EEex_UDAux", + function(sprite) + + if EEex.IsMarshallingCopy() then + return nil + end + + local toMarshal = {} + + local exportEffectList = function(list, path) + local exported = {} + local i = 1 + EEex_Utility_IterateCPtrList(list, function(effect) + exported[i] = EEex_UDAux_Private_Export(effect, path.."["..i.."]") + i = i + 1 + end) + return next(exported) and exported or nil + end + + local timedEffects = exportEffectList(sprite.m_timedEffectList, "CGameSprite.m_timedEffectList") + if timedEffects then + toMarshal["timedEffects"] = timedEffects + end + + local equippedEffects = exportEffectList(sprite.m_equipedEffectList, "CGameSprite.m_equipedEffectList") + if equippedEffects then + toMarshal["equippedEffects"] = equippedEffects + end + + local items = {} + for i = 0, 38 do + local item = sprite.m_equipment.m_items:get(i) + if item then + items[i] = EEex_UDAux_Private_Export(item, "CGameSprite.m_equipment.m_items["..i.."]") + end + end + if next(items) then + toMarshal["items"] = items + end + + return next(toMarshal) and toMarshal or nil + end, + function(sprite, read) + + local importEffectList = function(list, data) + if not data then + return + end + local i = 1 + EEex_Utility_IterateCPtrList(list, function(effect) + local auxiliary = data[i] + if auxiliary then + EEex_UDAux_Private_Import(effect, auxiliary) + end + i = i + 1 + end) + end + + importEffectList(sprite.m_timedEffectList, read["timedEffects"]) + importEffectList(sprite.m_equipedEffectList, read["equippedEffects"]) + + local items = read["items"] + if items then + -- The extra creature marshal block is read while the sprite is still being rebuilt. + -- Equipment item pointers can be unavailable at this point, so keep slot data on the + -- sprite and retry from normal post-load/update hooks until each item exists. + EEex_GetUDAux(sprite)["EEex_UDAux_PendingItemAux"] = items + EEex_Marshal_Private_ApplyPendingItemUDAux(sprite) + end + end +) + function EEex_Marshal_Private_OnSummonerLoaded(sprite, loadedSprite) sprite.m_lSummonedBy:Set(loadedSprite:virtual_GetAIType()) end EEex_Sprite_AddLoadedListener(function(sprite) + EEex_Marshal_Private_ApplyPendingItemUDAux(sprite) + local summonerUUID = EEex_GetUDAux(sprite)["EEex_SummonerUUID"] if summonerUUID then sprite:loadedWithUUIDCallback(summonerUUID, EEex_Marshal_Private_OnSummonerLoaded) end end) +EEex_Opcode_AddListsResolvedListener(EEex_Marshal_Private_ApplyPendingItemUDAux) + EEex_AIBase_AddScriptingObjectUpdatedListener(function(aiBase, scriptingObject) if not aiBase:isSprite(true) then From 616b83fe1e74052cc09f909d47594c4a723e011f Mon Sep 17 00:00:00 2001 From: 4Luke4 Date: Sat, 23 May 2026 12:23:58 +0200 Subject: [PATCH 2/5] CGameContainer, CItem (container) --- EEex/copy/EEex_scripts/EEex_Assembly.lua | 84 ---- EEex/copy/EEex_scripts/EEex_Main.lua | 3 + EEex/copy/EEex_scripts/EEex_Marshal.lua | 35 +- EEex/copy/EEex_scripts/EEex_Marshal_Codec.lua | 393 ++++++++++++++++++ EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua | 111 +++++ EEex/copy/EEex_scripts/EEex_Sprite.lua | 328 +-------------- EEex/copy/EEex_scripts/EEex_UDAux.lua | 252 +++++++++++ EEex/loader/InfinityLoader.db | 24 ++ 8 files changed, 799 insertions(+), 431 deletions(-) create mode 100644 EEex/copy/EEex_scripts/EEex_Marshal_Codec.lua create mode 100644 EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua create mode 100644 EEex/copy/EEex_scripts/EEex_UDAux.lua diff --git a/EEex/copy/EEex_scripts/EEex_Assembly.lua b/EEex/copy/EEex_scripts/EEex_Assembly.lua index d9aaa7e2..28d04015 100644 --- a/EEex/copy/EEex_scripts/EEex_Assembly.lua +++ b/EEex/copy/EEex_scripts/EEex_Assembly.lua @@ -1289,90 +1289,6 @@ end EEex_UserDataAuxiliary = {} EEex_UserDataAuxiliaryPersistent = {} -function EEex_UDAux_Private_AssertPersistentValue(value, path, seenTables) - local valueType = type(value) - if valueType == "boolean" or valueType == "string" then - return value - elseif valueType == "number" then - if value ~= value or value % 1 ~= 0 then - EEex_Error("Persistent UDAux value at "..path.." must be an integer number") - end - if value < -0x10000000000000000 or value > 0xFFFFFFFFFFFFFFFF then - EEex_Error("Persistent UDAux number at "..path.." is outside the supported marshal range") - end - return value - elseif valueType ~= "table" then - EEex_Error("Persistent UDAux value at "..path.." has unsupported type "..valueType) - end - - if seenTables[value] then - EEex_Error("Persistent UDAux table cycle detected at "..path) - end - seenTables[value] = true - - local copy = {} - for k, v in pairs(value) do - local kType = type(k) - if kType ~= "boolean" and kType ~= "number" and kType ~= "string" then - EEex_Error("Persistent UDAux key at "..path.." has unsupported type "..kType) - end - local keyPath = path.."["..tostring(k).."]" - copy[EEex_UDAux_Private_AssertPersistentValue(k, path.."", seenTables)] = - EEex_UDAux_Private_AssertPersistentValue(v, keyPath, seenTables) - end - - seenTables[value] = nil - return copy -end - -function EEex_UDAux_Private_IsLightUDPersistent(lud) - return EEex_UserDataAuxiliaryPersistent[lud] == true -end - -function EEex_UDAux_Private_IsPersistent(ud) - return EEex_UDAux_Private_IsLightUDPersistent(EEex_UDToLightUD(ud)) -end - -function EEex_UDAux_Private_Export(ud, path) - local lud = EEex_UDToLightUD(ud) - if not EEex_UDAux_Private_IsLightUDPersistent(lud) then - return nil - end - - local auxiliary = EEex_UserDataAuxiliary[lud] - if not auxiliary or next(auxiliary) == nil then - return nil - end - return EEex_UDAux_Private_AssertPersistentValue(auxiliary, path or "persistent UDAux", {}) -end - -function EEex_UDAux_Private_Import(ud, auxiliary) - local lud = EEex_UDToLightUD(ud) - EEex_UserDataAuxiliary[lud] = EEex_UDAux_Private_AssertPersistentValue(auxiliary, "persistent UDAux import", {}) - EEex_UserDataAuxiliaryPersistent[lud] = true - return EEex_UserDataAuxiliary[lud] -end - -function EEex_UDAux_Private_CopyByLightUD(srcLud, dstLud) - local srcAuxiliary = EEex_UserDataAuxiliary[srcLud] - if EEex_UserDataAuxiliaryPersistent[srcLud] and srcAuxiliary then - EEex_UserDataAuxiliary[dstLud] = EEex_UDAux_Private_AssertPersistentValue(srcAuxiliary, "persistent UDAux copy", {}) - else - EEex_UserDataAuxiliary[dstLud] = nil - end - - if EEex_UserDataAuxiliaryPersistent[srcLud] then - EEex_UserDataAuxiliaryPersistent[dstLud] = true - else - EEex_UserDataAuxiliaryPersistent[dstLud] = nil - end -end - -function EEex_UDAux_Private_DeleteByLightUD(lud) - EEex_UserDataAuxiliary[lud] = nil - EEex_UserDataAuxiliaryPersistent[lud] = nil -end - function EEex_DeleteUserDataAuxiliary(ud) if type(ud) ~= "userdata" then EEex_Error("ud is not a userdata object ("..type(ud)..")!") diff --git a/EEex/copy/EEex_scripts/EEex_Main.lua b/EEex/copy/EEex_scripts/EEex_Main.lua index d2bb710a..525ff0be 100644 --- a/EEex/copy/EEex_scripts/EEex_Main.lua +++ b/EEex/copy/EEex_scripts/EEex_Main.lua @@ -38,6 +38,8 @@ EEex_Main_Private_StartupFiles = { "EEex_Resource", -- "EEex_Script", -- "EEex_Script_Patch", -- + "EEex_Marshal_Codec", -- + "EEex_UDAux", -- "EEex_Sprite", -- "EEex_Sprite_Patch", -- "EEex_Stats", -- @@ -54,6 +56,7 @@ EEex_Main_Private_StartupFiles = { "EEex_Fix", -- "EEex_Fix_Patch", -- "EEex_Marshal", -- + "EEex_Marshal_Patch", -- "EEex_Module", -- "EEex_OptionsLate", -- Here so it can register listeners provided by other EEex files "EEex_Profiler", -- diff --git a/EEex/copy/EEex_scripts/EEex_Marshal.lua b/EEex/copy/EEex_scripts/EEex_Marshal.lua index 21c2d0ec..da42215d 100644 --- a/EEex/copy/EEex_scripts/EEex_Marshal.lua +++ b/EEex/copy/EEex_scripts/EEex_Marshal.lua @@ -15,32 +15,6 @@ EEex_Sprite_AddMarshalHandlers("EEex", end ) -function EEex_Marshal_Private_ApplyPendingItemUDAux(sprite) - local spriteAux = EEex_TryGetUDAux(sprite) - local pendingItems = spriteAux and spriteAux["EEex_UDAux_PendingItemAux"] - if not pendingItems then - return - end - - local anyPending = false - for i = 0, 38 do - local auxiliary = pendingItems[i] - if auxiliary then - local item = sprite.m_equipment.m_items:get(i) - if item then - EEex_UDAux_Private_Import(item, auxiliary) - pendingItems[i] = nil - else - anyPending = true - end - end - end - - if not anyPending then - spriteAux["EEex_UDAux_PendingItemAux"] = nil - end -end - EEex_Sprite_AddMarshalHandlers("EEex_UDAux", function(sprite) @@ -108,7 +82,7 @@ EEex_Sprite_AddMarshalHandlers("EEex_UDAux", -- Equipment item pointers can be unavailable at this point, so keep slot data on the -- sprite and retry from normal post-load/update hooks until each item exists. EEex_GetUDAux(sprite)["EEex_UDAux_PendingItemAux"] = items - EEex_Marshal_Private_ApplyPendingItemUDAux(sprite) + EEex_UDAux_Private_ApplyPendingItemAux(sprite) end end ) @@ -117,17 +91,16 @@ function EEex_Marshal_Private_OnSummonerLoaded(sprite, loadedSprite) sprite.m_lSummonedBy:Set(loadedSprite:virtual_GetAIType()) end -EEex_Sprite_AddLoadedListener(function(sprite) - EEex_Marshal_Private_ApplyPendingItemUDAux(sprite) +EEex_Sprite_AddLoadedListener(EEex_UDAux_Private_ApplyPendingItemAux) +EEex_Opcode_AddListsResolvedListener(EEex_UDAux_Private_ApplyPendingItemAux) +EEex_Sprite_AddLoadedListener(function(sprite) local summonerUUID = EEex_GetUDAux(sprite)["EEex_SummonerUUID"] if summonerUUID then sprite:loadedWithUUIDCallback(summonerUUID, EEex_Marshal_Private_OnSummonerLoaded) end end) -EEex_Opcode_AddListsResolvedListener(EEex_Marshal_Private_ApplyPendingItemUDAux) - EEex_AIBase_AddScriptingObjectUpdatedListener(function(aiBase, scriptingObject) if not aiBase:isSprite(true) then diff --git a/EEex/copy/EEex_scripts/EEex_Marshal_Codec.lua b/EEex/copy/EEex_scripts/EEex_Marshal_Codec.lua new file mode 100644 index 00000000..e1c8b832 --- /dev/null +++ b/EEex/copy/EEex_scripts/EEex_Marshal_Codec.lua @@ -0,0 +1,393 @@ + +-- IMPORTANT: These enum values are part of the shared EEex marshal table schema. +-- Do not modify existing values without implementing migration code for every channel. +EEex_Marshal_Private_TableFieldType = { + ["TABLE_END"] = 0, + ["TABLE_START"] = 1, + ["STRING"] = 2, + ["INT8"] = 3, + ["INTU8"] = 4, + ["INT16"] = 5, + ["INTU16"] = 6, + ["INT32"] = 7, + ["INTU32"] = 8, + ["INT64"] = 9, + ["INTU64"] = 10, + ["BOOLEAN_FALSE"] = 11, + ["BOOLEAN_TRUE"] = 12, +} + +function EEex_Marshal_Private_NewTableBundle() + return { + ["entries"] = {}, + ["entryCount"] = 0, + ["tableToMeta"] = {}, + ["memorySize"] = 0, + } +end + +function EEex_Marshal_Private_ResetTableBundle(bundle) + bundle["entries"] = {} + bundle["entryCount"] = 0 + bundle["tableToMeta"] = {} + bundle["memorySize"] = 0 +end + +function EEex_Marshal_Private_AssertTableString(value, context) + if value:find("\0", 1, true) then + EEex_Error((context or "Marshal").." strings cannot contain NUL bytes") + end +end + +function EEex_Marshal_Private_AddTableBundleEntry(bundle, handlerName, toExport, context) + if type(handlerName) ~= "string" or handlerName == "" then + EEex_Error((context or "Marshal").." handler name must be a non-empty string") + end + EEex_Marshal_Private_AssertTableString(handlerName, context) + if type(toExport) ~= "table" then + EEex_Error((context or "Marshal").." handler must export table or nil") + end + if bundle["tableToMeta"][toExport] then + EEex_Error((context or "Marshal").." exported the same table more than once") + end + bundle["entryCount"] = bundle["entryCount"] + 1 + bundle["entries"][bundle["entryCount"]] = toExport + bundle["tableToMeta"][toExport] = { + ["handlerName"] = handlerName, + } +end + +function EEex_Marshal_Private_DetermineTableNumberInfo(number, context) + if number ~= number or number % 1 ~= 0 then + EEex_Error((context or "Marshal").." number must be an integer") + end + if number >= 0 then + if number <= 0xFF then + return EEex_Marshal_Private_TableFieldType.INTU8, EEex_WriteU8, 1 + elseif number <= 0xFFFF then + return EEex_Marshal_Private_TableFieldType.INTU16, EEex_WriteU16, 2 + elseif number <= 0xFFFFFFFF then + return EEex_Marshal_Private_TableFieldType.INTU32, EEex_WriteU32, 4 + elseif number <= 0xFFFFFFFFFFFFFFFF then + return EEex_Marshal_Private_TableFieldType.INTU64, EEex_WriteU64, 8 + else + EEex_Error((context or "Marshal").." number is outside the supported range") + end + else + if number >= -0x100 then + return EEex_Marshal_Private_TableFieldType.INT8, EEex_Write8, 1 + elseif number >= -0x10000 then + return EEex_Marshal_Private_TableFieldType.INT16, EEex_Write16, 2 + elseif number >= -0x100000000 then + return EEex_Marshal_Private_TableFieldType.INT32, EEex_Write32, 4 + elseif number >= -0x10000000000000000 then + return EEex_Marshal_Private_TableFieldType.INT64, EEex_Write64, 8 + else + EEex_Error((context or "Marshal").." number is outside the supported range") + end + end +end + +function EEex_Marshal_Private_CalculateTableBundleSize(bundle, context) + + local accumulator = 0 + local lengthTypeSwitch = { + ["boolean"] = function(v) + return 0 + end, + ["number"] = function(v) + local _, _, writeAdvance = EEex_Marshal_Private_DetermineTableNumberInfo(v, context) + return writeAdvance + end, + ["string"] = function(v) + EEex_Marshal_Private_AssertTableString(v, context) + return #v + 1 + end, + } + + local processStack = {{bundle["entries"], nil}} -- toProcessT, iterK + local activeTables = { + [bundle["entries"]] = true, + } + local stackTop = 1 + + while true do + + ::continue:: + local toProcess = processStack[stackTop] + local toProcessT = toProcess[1] + + while true do + + local k, v = next(toProcessT, toProcess[2]) + if k == nil then + break + end + local kType = type(k) + if kType ~= "boolean" and kType ~= "number" and kType ~= "string" then + EEex_Error((context or "Marshal").." only supports boolean, number, and string keys") + end + + toProcess[2] = k + + if stackTop == 1 then + local meta = bundle["tableToMeta"][v] + if not meta then + EEex_Error((context or "Marshal").." top-level table is missing handler metadata") + end + local handlerName = meta["handlerName"] + accumulator = accumulator + #handlerName + 1 + if activeTables[v] then + EEex_Error((context or "Marshal").." table cycle detected") + end + activeTables[v] = true + stackTop = stackTop + 1 + processStack[stackTop] = {v, nil} + goto continue + else + local vType = type(v) + if vType ~= "boolean" and vType ~= "number" and vType ~= "string" and vType ~= "table" then + EEex_Error((context or "Marshal").." only supports boolean, number, string, and table values") + end + if vType == "table" then + -- KEY_FIELD_TYPE + KEY_LENGTH + TABLE_START + accumulator = accumulator + 1 + lengthTypeSwitch[kType](k) + 1 + if activeTables[v] then + EEex_Error((context or "Marshal").." table cycle detected") + end + activeTables[v] = true + stackTop = stackTop + 1 + processStack[stackTop] = {v, nil} + goto continue + end + -- KEY_FIELD_TYPE + KEY_LENGTH + VALUE_FIELD_TYPE + VALUE_LENGTH + accumulator = accumulator + 1 + lengthTypeSwitch[kType](k) + 1 + lengthTypeSwitch[vType](v) + end + end + + accumulator = accumulator + 1 -- TABLE_END + + activeTables[toProcessT] = nil + processStack[stackTop] = nil + stackTop = stackTop - 1 + + if stackTop == 0 then + break + end + end + + return accumulator +end + +function EEex_Marshal_Private_WriteTableBundle(memoryPtr, bundle, context) + + local writeNumber = function(number) + local typeByte, writeFunc, writeAdvance = EEex_Marshal_Private_DetermineTableNumberInfo(number, context) + EEex_Write8(memoryPtr, typeByte) + memoryPtr = memoryPtr + 1 + writeFunc(memoryPtr, number) + memoryPtr = memoryPtr + writeAdvance + end + + local writeTypeSwitch = { + ["boolean"] = function(v) + EEex_Write8(memoryPtr, v + and EEex_Marshal_Private_TableFieldType.BOOLEAN_TRUE + or EEex_Marshal_Private_TableFieldType.BOOLEAN_FALSE + ) + memoryPtr = memoryPtr + 1 + end, + ["number"] = writeNumber, + ["string"] = function(v) + EEex_Marshal_Private_AssertTableString(v, context) + EEex_Write8(memoryPtr, EEex_Marshal_Private_TableFieldType.STRING) + memoryPtr = memoryPtr + 1 + EEex_WriteString(memoryPtr, v) + memoryPtr = memoryPtr + #v + 1 + end, + ["table"] = function(v) + EEex_Write8(memoryPtr, EEex_Marshal_Private_TableFieldType.TABLE_START) + memoryPtr = memoryPtr + 1 + end, + } + + local processStack = {{bundle["entries"], nil}} -- toProcessT, iterK + local activeTables = { + [bundle["entries"]] = true, + } + local stackTop = 1 + + while true do + + ::continue:: + local toProcess = processStack[stackTop] + local toProcessT = toProcess[1] + + while true do + + local k, v = next(toProcessT, toProcess[2]) + if k == nil then + break + end + local kType = type(k) + if kType ~= "boolean" and kType ~= "number" and kType ~= "string" then + EEex_Error((context or "Marshal").." only supports boolean, number, and string keys") + end + + toProcess[2] = k + + if stackTop == 1 then + local meta = bundle["tableToMeta"][v] + if not meta then + EEex_Error((context or "Marshal").." top-level table is missing handler metadata") + end + local handlerName = meta["handlerName"] + EEex_Marshal_Private_AssertTableString(handlerName, context) + EEex_WriteString(memoryPtr, handlerName) + memoryPtr = memoryPtr + #handlerName + 1 + else + local vType = type(v) + if vType ~= "boolean" and vType ~= "number" and vType ~= "string" and vType ~= "table" then + EEex_Error((context or "Marshal").." only supports boolean, number, string, and table values") + end + writeTypeSwitch[kType](k) + writeTypeSwitch[vType](v) + end + + if type(v) == "table" then + if activeTables[v] then + EEex_Error((context or "Marshal").." table cycle detected") + end + activeTables[v] = true + stackTop = stackTop + 1 + processStack[stackTop] = {v, nil} + goto continue + end + end + + EEex_Write8(memoryPtr, EEex_Marshal_Private_TableFieldType.TABLE_END) + memoryPtr = memoryPtr + 1 + + activeTables[toProcessT] = nil + processStack[stackTop] = nil + stackTop = stackTop - 1 + + if stackTop == 0 then + break + end + end +end + +function EEex_Marshal_Private_ReadTableBundle(memory, handlerReader) + + local baseMemory = memory + + while true do + + local toFill = {} + local handlerStr = EEex_ReadString(memory) + memory = memory + #handlerStr + 1 + + -- The top-level list writes TABLE_END('\0') to signal that all + -- marshalled data has ended, which reads as an empty string. + if handlerStr == "" then + break + end + + local fieldReadSwitch = { + [EEex_Marshal_Private_TableFieldType.STRING] = function() + local read = EEex_ReadString(memory) + memory = memory + #read + 1 + return read + end, + [EEex_Marshal_Private_TableFieldType.INT8] = function() + local read = EEex_Read8(memory) + memory = memory + 1 + return read + end, + [EEex_Marshal_Private_TableFieldType.INTU8] = function() + local read = EEex_ReadU8(memory) + memory = memory + 1 + return read + end, + [EEex_Marshal_Private_TableFieldType.INT16] = function() + local read = EEex_Read16(memory) + memory = memory + 2 + return read + end, + [EEex_Marshal_Private_TableFieldType.INTU16] = function() + local read = EEex_ReadU16(memory) + memory = memory + 2 + return read + end, + [EEex_Marshal_Private_TableFieldType.INT32] = function() + local read = EEex_Read32(memory) + memory = memory + 4 + return read + end, + [EEex_Marshal_Private_TableFieldType.INTU32] = function() + local read = EEex_ReadU32(memory) + memory = memory + 4 + return read + end, + [EEex_Marshal_Private_TableFieldType.INT64] = function() + local read = EEex_Read64(memory) + memory = memory + 8 + return read + end, + [EEex_Marshal_Private_TableFieldType.INTU64] = function() + local read = EEex_ReadU64(memory) + memory = memory + 8 + return read + end, + [EEex_Marshal_Private_TableFieldType.BOOLEAN_FALSE] = function() + return false + end, + [EEex_Marshal_Private_TableFieldType.BOOLEAN_TRUE] = function() + return true + end, + } + + local tableStack = {} + local tableStackTop = 0 + + while true do + + local keyFieldType = EEex_Read8(memory) + memory = memory + 1 + + if keyFieldType == EEex_Marshal_Private_TableFieldType.TABLE_END then + if tableStackTop == 0 then + break + end + toFill = tableStack[tableStackTop] + tableStackTop = tableStackTop - 1 + else + local keyReader = fieldReadSwitch[keyFieldType] + if not keyReader then + EEex_Error("Unknown marshal key field type "..keyFieldType) + end + local key = keyReader() + local valueFieldType = EEex_Read8(memory) + memory = memory + 1 + if valueFieldType == EEex_Marshal_Private_TableFieldType.TABLE_START then + local subTable = {} + toFill[key] = subTable + tableStackTop = tableStackTop + 1 + tableStack[tableStackTop] = toFill + toFill = subTable + else + local valueReader = fieldReadSwitch[valueFieldType] + if not valueReader then + EEex_Error("Unknown marshal value field type "..valueFieldType) + end + toFill[key] = valueReader() + end + end + end + + handlerReader(handlerStr, toFill) + end + + return memory - baseMemory +end diff --git a/EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua b/EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua new file mode 100644 index 00000000..6269a38f --- /dev/null +++ b/EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua @@ -0,0 +1,111 @@ + +(function() + + EEex_DisableCodeProtection() + + local EEex_Marshal_Patch_Private_VolatileRegisterIgnores = { + {"hook_integrity_watchdog_ignore_registers", { + EEex_HookIntegrityWatchdogRegister.RAX, + EEex_HookIntegrityWatchdogRegister.RCX, + EEex_HookIntegrityWatchdogRegister.RDX, + EEex_HookIntegrityWatchdogRegister.R8, + EEex_HookIntegrityWatchdogRegister.R9, + EEex_HookIntegrityWatchdogRegister.R10, + EEex_HookIntegrityWatchdogRegister.R11, + }}, + } + + local EEex_Marshal_Patch_Private_EntryStackVolatileRegisterIgnores = EEex_FlattenTable({ + {{"stack_mod", 8}}, + EEex_Marshal_Patch_Private_VolatileRegisterIgnores, + }) + + EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameArea::Marshal(unsigned char**,unsigned int*,unsigned char)-FirstInstruction"), 0, 8, 8, + EEex_Marshal_Patch_Private_EntryStackVolatileRegisterIgnores, + {[[ + #MAKE_SHADOW_SPACE(40) + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)], rdx + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-24)], r8 + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-32)], r9 + + call #L(EEex::UDAux_Hook_OnBeforeAreaMarshal) + + mov r9, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-32)] + mov r8, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-24)] + mov rdx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)] + mov rcx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)] + #DESTROY_SHADOW_SPACE + ]]} + ) + + EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameArea::Marshal(unsigned char**,unsigned int*,unsigned char)-AfterMarshal"), 0, 8, 8, + EEex_Marshal_Patch_Private_VolatileRegisterIgnores, + {[[ + #MAKE_SHADOW_SPACE(8) + call #L(EEex::UDAux_Hook_OnAfterAreaMarshal) + #DESTROY_SHADOW_SPACE + ]]} + ) + + EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameContainer::Marshal(SAreaFileWrapper*)-FirstInstruction"), 0, 5, 5, + EEex_Marshal_Patch_Private_EntryStackVolatileRegisterIgnores, + {[[ + #MAKE_SHADOW_SPACE(40) + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)], rdx + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-24)], r8 + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-32)], r9 + + call #L(EEex::UDAux_Hook_OnAreaContainerMarshal) + + mov r9, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-32)] + mov r8, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-24)] + mov rdx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)] + mov rcx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)] + #DESTROY_SHADOW_SPACE + ]]} + ) + + EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameArea::Unmarshal(unsigned char*,unsigned int,unsigned char)-FirstInstruction"), 0, 8, 8, + EEex_Marshal_Patch_Private_EntryStackVolatileRegisterIgnores, + {[[ + #MAKE_SHADOW_SPACE(40) + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)], rdx + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-24)], r8 + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-32)], r9 + + call #L(EEex::UDAux_Hook_OnBeforeAreaUnmarshal) + + mov r9, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-32)] + mov r8, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-24)] + mov rdx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)] + mov rcx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)] + #DESTROY_SHADOW_SPACE + ]]} + ) + + EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameArea::Unmarshal(unsigned char*,unsigned int,unsigned char)-AfterUnmarshal"), 0, 8, 8, + EEex_Marshal_Patch_Private_VolatileRegisterIgnores, + {[[ + #MAKE_SHADOW_SPACE(8) + mov ecx, r12d + call #L(EEex::UDAux_Hook_OnAfterAreaUnmarshal) + #DESTROY_SHADOW_SPACE + ]]} + ) + + EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameContainer::CGameContainer(CGameArea*,CAreaFileContainer*,...)-AfterConstruct"), 0, 7, 7, + EEex_Marshal_Patch_Private_VolatileRegisterIgnores, + {[[ + #MAKE_SHADOW_SPACE(8) + mov rcx, r13 + call #L(EEex::UDAux_Hook_OnAfterAreaContainerConstruct) + #DESTROY_SHADOW_SPACE + ]]} + ) + + EEex_EnableCodeProtection() + +end)() diff --git a/EEex/copy/EEex_scripts/EEex_Sprite.lua b/EEex/copy/EEex_scripts/EEex_Sprite.lua index b2d8d930..0e2b9c99 100644 --- a/EEex/copy/EEex_scripts/EEex_Sprite.lua +++ b/EEex/copy/EEex_scripts/EEex_Sprite.lua @@ -1063,207 +1063,7 @@ end -- -- end --- IMPORTANT: This is part of the X-BIV1.0 schema, don't modify existing enum values without implementing migration code! -EEex_Sprite_Private_MarshalHandlerFieldType = { - ["TABLE_END"] = 0, - ["TABLE_START"] = 1, - ["STRING"] = 2, - ["INT8"] = 3, - ["INTU8"] = 4, - ["INT16"] = 5, - ["INTU16"] = 6, - ["INT32"] = 7, - ["INTU32"] = 8, - ["INT64"] = 9, - ["INTU64"] = 10, - ["BOOLEAN_FALSE"] = 11, - ["BOOLEAN_TRUE"] = 12, -} - -EEex_Sprite_Private_CurrentSpriteMarshalHandlerData = {} -EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_TableSize = 0 -EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_TableToMeta = {} -EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_MemorySize = 0 - -function EEex_Sprite_Private_DetermineSpriteMarshalHandlerNumberInfo(number) - if number >= 0 then - if number <= 0xFF then - return EEex_Sprite_Private_MarshalHandlerFieldType.INTU8, EEex_WriteU8, 1 - elseif number <= 0xFFFF then - return EEex_Sprite_Private_MarshalHandlerFieldType.INTU16, EEex_WriteU16, 2 - elseif number <= 0xFFFFFFFF then - return EEex_Sprite_Private_MarshalHandlerFieldType.INTU32, EEex_WriteU32, 4 - elseif number <= 0xFFFFFFFFFFFFFFFF then - return EEex_Sprite_Private_MarshalHandlerFieldType.INTU64, EEex_WriteU64, 8 - else - EEex_Error("Number too large to be marshalled in creature handler") - end - else - if number >= -0x100 then - return EEex_Sprite_Private_MarshalHandlerFieldType.INT8, EEex_Write8, 1 - elseif number >= -0x10000 then - return EEex_Sprite_Private_MarshalHandlerFieldType.INT16, EEex_Write16, 2 - elseif number >= -0x100000000 then - return EEex_Sprite_Private_MarshalHandlerFieldType.INT32, EEex_Write32, 4 - elseif number >= -0x10000000000000000 then - return EEex_Sprite_Private_MarshalHandlerFieldType.INT64, EEex_Write64, 8 - else - EEex_Error("Number too large to be marshalled in creature handler") - end - end -end - -function EEex_Sprite_Private_CalculateSpriteMarshalHandlerDataSize(t) - - local accumulator = 0 - local lengthTypeSwitch = { - ["boolean"] = function(v) - return 0 - end, - ["number"] = function(v) - local _, _, writeAdvance = EEex_Sprite_Private_DetermineSpriteMarshalHandlerNumberInfo(v) - return writeAdvance - end, - ["string"] = function(v) - return #v + 1 - end, - } - - local processStack = {{t, nil}} -- toProcessT, iterK - local stackTop = 1 - - while true do - - ::continue:: - local toProcess = processStack[stackTop] - local toProcessT = toProcess[1] - - while true do - - local k, v = next(toProcessT, toProcess[2]) - if k == nil then - break - end - local kType = type(k) - if kType ~= "boolean" and kType ~= "number" and kType ~= "string" then - EEex_Error("Only booleans / numbers / strings can be used as keys in creature marshal") - end - - toProcess[2] = k - - if stackTop == 1 then - local handlerName = EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_TableToMeta[v].handlerName - -- HANDLER_STRING_LENGTH - accumulator = accumulator + #handlerName + 1 - stackTop = stackTop + 1 - processStack[stackTop] = {v, nil} - goto continue - else - local vType = type(v) - if vType ~= "boolean" and vType ~= "number" and vType ~= "string" and vType ~= "table" then - EEex_Error("Only booleans / numbers / strings / tables can be used as values in creature marshal") - end - if vType == "table" then - -- KEY_FIELD_TYPE + KEY_LENGTH + TABLE_START - accumulator = accumulator + 1 + lengthTypeSwitch[kType](k) + 1 - stackTop = stackTop + 1 - processStack[stackTop] = {v, nil} - goto continue - end - -- KEY_FIELD_TYPE + KEY_LENGTH + VALUE_FIELD_TYPE + VALUE_LENGTH - accumulator = accumulator + 1 + lengthTypeSwitch[kType](k) + 1 + lengthTypeSwitch[vType](v) - end - end - - accumulator = accumulator + 1 -- TABLE_END - - processStack[stackTop] = nil - stackTop = stackTop - 1 - - if stackTop == 0 then - break - end - end - - return accumulator -end - -function EEex_Sprite_Private_WriteSpriteMarshalHandlerData(memoryPtr, t) - - local writeNumber = function(number) - local typeByte, writeFunc, writeAdvance = EEex_Sprite_Private_DetermineSpriteMarshalHandlerNumberInfo(number) - EEex_Write8(memoryPtr, typeByte) - memoryPtr = memoryPtr + 1 - writeFunc(memoryPtr, number) - memoryPtr = memoryPtr + writeAdvance - end - - local writeTypeSwitch = { - ["boolean"] = function(v) - EEex_Write8(memoryPtr, v - and EEex_Sprite_Private_MarshalHandlerFieldType.BOOLEAN_TRUE - or EEex_Sprite_Private_MarshalHandlerFieldType.BOOLEAN_FALSE - ) - memoryPtr = memoryPtr + 1 - end, - ["number"] = writeNumber, - ["string"] = function(v) - EEex_Write8(memoryPtr, EEex_Sprite_Private_MarshalHandlerFieldType.STRING) - memoryPtr = memoryPtr + 1 - EEex_WriteString(memoryPtr, v) - memoryPtr = memoryPtr + #v + 1 - end, - ["table"] = function(v) - EEex_Write8(memoryPtr, EEex_Sprite_Private_MarshalHandlerFieldType.TABLE_START) - memoryPtr = memoryPtr + 1 - end, - } - - local processStack = {{t, nil}} -- toProcessT, iterK - local stackTop = 1 - - while true do - - ::continue:: - local toProcess = processStack[stackTop] - local toProcessT = toProcess[1] - - while true do - - local k, v = next(toProcessT, toProcess[2]) - if k == nil then - break - end - - toProcess[2] = k - - if stackTop == 1 then - local handlerName = EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_TableToMeta[v].handlerName - EEex_WriteString(memoryPtr, handlerName) - memoryPtr = memoryPtr + #handlerName + 1 - else - writeTypeSwitch[type(k)](k) - writeTypeSwitch[type(v)](v) - end - - if type(v) == "table" then - stackTop = stackTop + 1 - processStack[stackTop] = {v, nil} - goto continue - end - end - - EEex_Write8(memoryPtr, EEex_Sprite_Private_MarshalHandlerFieldType.TABLE_END) - memoryPtr = memoryPtr + 1 - - processStack[stackTop] = nil - stackTop = stackTop - 1 - - if stackTop == 0 then - break - end - end -end +EEex_Sprite_Private_CurrentMarshalBundle = EEex_Marshal_Private_NewTableBundle() function EEex_Sprite_Hook_CalculateExtraEffectListMarshalSize(sprite) @@ -1280,11 +1080,7 @@ function EEex_Sprite_Hook_CalculateExtraEffectListMarshalSize(sprite) if type(toExport) ~= "table" then EEex_Error("Creature marshal handler must export table or nil") end - EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_TableSize = EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_TableSize + 1 - EEex_Sprite_Private_CurrentSpriteMarshalHandlerData[EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_TableSize] = toExport - EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_TableToMeta[toExport] = { - ["handlerName"] = handlerName, - } + EEex_Marshal_Private_AddTableBundleEntry(EEex_Sprite_Private_CurrentMarshalBundle, handlerName, toExport, "Creature marshal") end for handlerName, handler in pairs(EEex_Sprite_Private_MarshalHandlers) do @@ -1302,12 +1098,12 @@ function EEex_Sprite_Hook_CalculateExtraEffectListMarshalSize(sprite) -- Round up to multiple of CGameEffectBase to match an effect boundary on the CRE. -- If only 8 bytes are needed (no data is marshalled), skip writing entirely. - local extraMarshalSize = 8 + EEex_Sprite_Private_CalculateSpriteMarshalHandlerDataSize(EEex_Sprite_Private_CurrentSpriteMarshalHandlerData) - EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_MemorySize = (extraMarshalSize ~= 8 and + local extraMarshalSize = 8 + EEex_Marshal_Private_CalculateTableBundleSize(EEex_Sprite_Private_CurrentMarshalBundle, "Creature marshal") + EEex_Sprite_Private_CurrentMarshalBundle["memorySize"] = (extraMarshalSize ~= 8 and EEex_RoundUp(extraMarshalSize, CGameEffectBase.sizeof) or 0) - 8 - return EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_MemorySize + 8 + return EEex_Sprite_Private_CurrentMarshalBundle["memorySize"] + 8 end function EEex_Sprite_Hook_WriteExtraEffectListMarshal(memory) @@ -1316,118 +1112,18 @@ function EEex_Sprite_Hook_WriteExtraEffectListMarshal(memory) return end - if EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_MemorySize > 0 then + if EEex_Sprite_Private_CurrentMarshalBundle["memorySize"] > 0 then EEex_WriteLString(memory, "X-BIV1.0", 8) local marshalPtr = memory + 8 - EEex_Memset(marshalPtr, 0, EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_MemorySize) - EEex_Sprite_Private_WriteSpriteMarshalHandlerData(marshalPtr, EEex_Sprite_Private_CurrentSpriteMarshalHandlerData) + EEex_Memset(marshalPtr, 0, EEex_Sprite_Private_CurrentMarshalBundle["memorySize"]) + EEex_Marshal_Private_WriteTableBundle(marshalPtr, EEex_Sprite_Private_CurrentMarshalBundle, "Creature marshal") end - EEex_Sprite_Private_CurrentSpriteMarshalHandlerData = {} - EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_TableSize = 0 - EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_TableToMeta = {} - EEex_Sprite_Private_CurrentSpriteMarshalHandlerData_MemorySize = 0 + EEex_Marshal_Private_ResetTableBundle(EEex_Sprite_Private_CurrentMarshalBundle) end function EEex_Sprite_LuaHook_ReadExtraEffectListUnmarshal(sprite, baseMemory) - local memory = baseMemory + 8 - - while true do - - local toFill = {} - local handlerStr = EEex_ReadString(memory) - memory = memory + #handlerStr + 1 - - -- The top level list writes TABLE_END('\0') to signal that all - -- marshalled data has ended, which reads as an empty string - if handlerStr == "" then - break - end - - local fieldReadSwitch = { - [EEex_Sprite_Private_MarshalHandlerFieldType.STRING] = function() - local read = EEex_ReadString(memory) - memory = memory + #read + 1 - return read - end, - [EEex_Sprite_Private_MarshalHandlerFieldType.INT8] = function() - local read = EEex_Read8(memory) - memory = memory + 1 - return read - end, - [EEex_Sprite_Private_MarshalHandlerFieldType.INTU8] = function() - local read = EEex_ReadU8(memory) - memory = memory + 1 - return read - end, - [EEex_Sprite_Private_MarshalHandlerFieldType.INT16] = function() - local read = EEex_Read16(memory) - memory = memory + 2 - return read - end, - [EEex_Sprite_Private_MarshalHandlerFieldType.INTU16] = function() - local read = EEex_ReadU16(memory) - memory = memory + 2 - return read - end, - [EEex_Sprite_Private_MarshalHandlerFieldType.INT32] = function() - local read = EEex_Read32(memory) - memory = memory + 4 - return read - end, - [EEex_Sprite_Private_MarshalHandlerFieldType.INTU32] = function() - local read = EEex_ReadU32(memory) - memory = memory + 4 - return read - end, - [EEex_Sprite_Private_MarshalHandlerFieldType.INT64] = function() - local read = EEex_Read64(memory) - memory = memory + 8 - return read - end, - [EEex_Sprite_Private_MarshalHandlerFieldType.INTU64] = function() - local read = EEex_ReadU64(memory) - memory = memory + 8 - return read - end, - [EEex_Sprite_Private_MarshalHandlerFieldType.BOOLEAN_FALSE] = function() - return false - end, - [EEex_Sprite_Private_MarshalHandlerFieldType.BOOLEAN_TRUE] = function() - return true - end, - } - - local tableStack = {} - local tableStackTop = 0 - - while true do - - local keyFieldType = EEex_Read8(memory) - memory = memory + 1 - - if keyFieldType == EEex_Sprite_Private_MarshalHandlerFieldType.TABLE_END then - if tableStackTop == 0 then - break - end - toFill = tableStack[tableStackTop] - tableStackTop = tableStackTop - 1 - else - local key = fieldReadSwitch[keyFieldType]() - local valueFieldType = EEex_Read8(memory) - memory = memory + 1 - if valueFieldType == EEex_Sprite_Private_MarshalHandlerFieldType.TABLE_START then - local subTable = {} - toFill[key] = subTable - tableStackTop = tableStackTop + 1 - tableStack[tableStackTop] = toFill - toFill = subTable - else - toFill[key] = fieldReadSwitch[valueFieldType]() - end - end - end - + local bytesRead = EEex_Marshal_Private_ReadTableBundle(baseMemory + 8, function(handlerStr, toFill) local handlers = EEex_Sprite_Private_MarshalHandlers[handlerStr] if handlers then EEex_Utility_TryIgnore(handlers.importer, sprite, toFill) @@ -1436,10 +1132,10 @@ function EEex_Sprite_LuaHook_ReadExtraEffectListUnmarshal(sprite, baseMemory) local fallbackStorage = EEex_Utility_GetOrCreateTable(EEex_GetUDAux(sprite), "EEex_Sprite_FallbackMarshalStorage") fallbackStorage[handlerStr] = toFill end - end + end) -- Return to the C++ hook how many effects were EEex binary data - return EEex_RoundUp(memory - baseMemory, CGameEffectBase.sizeof) / CGameEffectBase.sizeof + return EEex_RoundUp(8 + bytesRead, CGameEffectBase.sizeof) / CGameEffectBase.sizeof end EEex_Sprite_Private_CustomConcentrationCheckFuncName = nil diff --git a/EEex/copy/EEex_scripts/EEex_UDAux.lua b/EEex/copy/EEex_scripts/EEex_UDAux.lua new file mode 100644 index 00000000..8324aa15 --- /dev/null +++ b/EEex/copy/EEex_scripts/EEex_UDAux.lua @@ -0,0 +1,252 @@ + +if EEex_UDAux_AlreadyLoaded then + return +end +EEex_UDAux_AlreadyLoaded = true + +function EEex_UDAux_Private_AssertPersistentValue(value, path, seenTables) + local valueType = type(value) + if valueType == "boolean" then + return value + elseif valueType == "string" then + if value:find("\0", 1, true) then + EEex_Error("Persistent UDAux string at "..path.." cannot contain NUL bytes") + end + return value + elseif valueType == "number" then + if value ~= value or value % 1 ~= 0 then + EEex_Error("Persistent UDAux value at "..path.." must be an integer number") + end + if value < -0x10000000000000000 or value > 0xFFFFFFFFFFFFFFFF then + EEex_Error("Persistent UDAux number at "..path.." is outside the supported marshal range") + end + return value + elseif valueType ~= "table" then + EEex_Error("Persistent UDAux value at "..path.." has unsupported type "..valueType) + end + + if seenTables[value] then + EEex_Error("Persistent UDAux table cycle detected at "..path) + end + seenTables[value] = true + + local copy = {} + for k, v in pairs(value) do + local kType = type(k) + if kType ~= "boolean" and kType ~= "number" and kType ~= "string" then + EEex_Error("Persistent UDAux key at "..path.." has unsupported type "..kType) + end + local keyPath = path.."["..tostring(k).."]" + copy[EEex_UDAux_Private_AssertPersistentValue(k, path.."", seenTables)] = + EEex_UDAux_Private_AssertPersistentValue(v, keyPath, seenTables) + end + + seenTables[value] = nil + return copy +end + +function EEex_UDAux_Private_IsLightUDPersistent(lud) + return EEex_UserDataAuxiliaryPersistent[lud] == true +end + +function EEex_UDAux_Private_IsPersistent(ud) + return EEex_UDAux_Private_IsLightUDPersistent(EEex_UDToLightUD(ud)) +end + +function EEex_UDAux_Private_Export(ud, path) + local lud = EEex_UDToLightUD(ud) + if not EEex_UDAux_Private_IsLightUDPersistent(lud) then + return nil + end + + local auxiliary = EEex_UserDataAuxiliary[lud] + if not auxiliary or next(auxiliary) == nil then + return nil + end + return EEex_UDAux_Private_AssertPersistentValue(auxiliary, path or "persistent UDAux", {}) +end + +function EEex_UDAux_Private_Import(ud, auxiliary) + local lud = EEex_UDToLightUD(ud) + EEex_UserDataAuxiliary[lud] = EEex_UDAux_Private_AssertPersistentValue(auxiliary, "persistent UDAux import", {}) + EEex_UserDataAuxiliaryPersistent[lud] = true + return EEex_UserDataAuxiliary[lud] +end + +function EEex_UDAux_Private_CopyByLightUD(srcLud, dstLud) + local srcAuxiliary = EEex_UserDataAuxiliary[srcLud] + if EEex_UserDataAuxiliaryPersistent[srcLud] and srcAuxiliary then + EEex_UserDataAuxiliary[dstLud] = EEex_UDAux_Private_AssertPersistentValue(srcAuxiliary, "persistent UDAux copy", {}) + else + EEex_UserDataAuxiliary[dstLud] = nil + end + + if EEex_UserDataAuxiliaryPersistent[srcLud] then + EEex_UserDataAuxiliaryPersistent[dstLud] = true + else + EEex_UserDataAuxiliaryPersistent[dstLud] = nil + end +end + +function EEex_UDAux_Private_DeleteByLightUD(lud) + EEex_UserDataAuxiliary[lud] = nil + EEex_UserDataAuxiliaryPersistent[lud] = nil +end + +EEex_UDAux_Private_AreaMarshalData = nil +EEex_UDAux_Private_AreaMarshalContainerIndex = 0 +EEex_UDAux_Private_AreaUnmarshalContainers = nil +EEex_UDAux_Private_AreaUnmarshalContainerIndex = 0 + +function EEex_UDAux_Private_ForEachContainerItem(container, func) + local node = container.m_lstItems.m_pNodeHead + local index = 1 + while node do + local item = node.data + if item then + func(index, item) + end + node = node.pNext + index = index + 1 + end +end + +function EEex_UDAux_Private_NewAreaMarshalBundle(marshalData) + local bundle = EEex_Marshal_Private_NewTableBundle() + EEex_Marshal_Private_AddTableBundleEntry(bundle, "EEex_UDAux_Area", marshalData, "Area UDAux marshal") + return bundle +end + +function EEex_UDAux_Private_BeginAreaMarshal(area) + EEex_UDAux_Private_AreaMarshalData = { + ["containers"] = {}, + } + EEex_UDAux_Private_AreaMarshalContainerIndex = 0 +end + +function EEex_UDAux_Private_OnAreaContainerMarshal(container) + local marshalData = EEex_UDAux_Private_AreaMarshalData + if not marshalData then + return + end + + EEex_UDAux_Private_AreaMarshalContainerIndex = EEex_UDAux_Private_AreaMarshalContainerIndex + 1 + local containerIndex = EEex_UDAux_Private_AreaMarshalContainerIndex + local containerData = {} + + local containerAux = EEex_UDAux_Private_Export(container, "CGameArea.m_lContainers["..containerIndex.."]") + if containerAux then + containerData["aux"] = containerAux + end + + local itemData = {} + EEex_UDAux_Private_ForEachContainerItem(container, function(itemIndex, item) + local itemAux = EEex_UDAux_Private_Export(item, "CGameArea.m_lContainers["..containerIndex.."].m_lstItems["..itemIndex.."]") + if itemAux then + itemData[itemIndex] = itemAux + end + end) + + if next(itemData) then + containerData["items"] = itemData + end + if next(containerData) then + marshalData["containers"][containerIndex] = containerData + end +end + +function EEex_UDAux_Private_CalculateAreaMarshalExtensionSize() + local marshalData = EEex_UDAux_Private_AreaMarshalData + if not marshalData or next(marshalData["containers"]) == nil then + return 0 + end + return EEex_Marshal_Private_CalculateTableBundleSize(EEex_UDAux_Private_NewAreaMarshalBundle(marshalData), "Area UDAux marshal") +end + +function EEex_UDAux_Private_WriteAreaMarshalExtensionPayload(memory, size) + local marshalData = EEex_UDAux_Private_AreaMarshalData + if not marshalData then + return + end + EEex_Memset(memory, 0, size) + EEex_Marshal_Private_WriteTableBundle(memory, EEex_UDAux_Private_NewAreaMarshalBundle(marshalData), "Area UDAux marshal") +end + +function EEex_UDAux_Private_EndAreaMarshal() + EEex_UDAux_Private_AreaMarshalData = nil + EEex_UDAux_Private_AreaMarshalContainerIndex = 0 +end + +function EEex_UDAux_Private_BeginAreaUnmarshal(memory, size) + EEex_UDAux_Private_AreaUnmarshalContainers = {} + EEex_UDAux_Private_AreaUnmarshalContainerIndex = 0 + + if memory == 0 or size == 0 then + return + end + + EEex_Marshal_Private_ReadTableBundle(memory, function(handlerStr, toFill) + if handlerStr == "EEex_UDAux_Area" and type(toFill) == "table" then + EEex_UDAux_Private_AreaUnmarshalContainers = toFill["containers"] or {} + end + end) +end + +function EEex_UDAux_Private_OnAreaContainerConstruct(container) + local containers = EEex_UDAux_Private_AreaUnmarshalContainers + if not containers then + return + end + + EEex_UDAux_Private_AreaUnmarshalContainerIndex = EEex_UDAux_Private_AreaUnmarshalContainerIndex + 1 + local containerData = containers[EEex_UDAux_Private_AreaUnmarshalContainerIndex] + if not containerData then + return + end + + local containerAux = containerData["aux"] + if containerAux then + EEex_UDAux_Private_Import(container, containerAux) + end + + local items = containerData["items"] + if items then + EEex_UDAux_Private_ForEachContainerItem(container, function(itemIndex, item) + local itemAux = items[itemIndex] + if itemAux then + EEex_UDAux_Private_Import(item, itemAux) + end + end) + end +end + +function EEex_UDAux_Private_EndAreaUnmarshal() + EEex_UDAux_Private_AreaUnmarshalContainers = nil + EEex_UDAux_Private_AreaUnmarshalContainerIndex = 0 +end + +function EEex_UDAux_Private_ApplyPendingItemAux(sprite) + local spriteAux = EEex_TryGetUDAux(sprite) + local pendingItems = spriteAux and spriteAux["EEex_UDAux_PendingItemAux"] + if not pendingItems then + return + end + + local anyPending = false + for i = 0, 38 do + local auxiliary = pendingItems[i] + if auxiliary then + local item = sprite.m_equipment.m_items:get(i) + if item then + EEex_UDAux_Private_Import(item, auxiliary) + pendingItems[i] = nil + else + anyPending = true + end + end + end + + if not anyPending then + spriteAux["EEex_UDAux_PendingItemAux"] = nil + end +end diff --git a/EEex/loader/InfinityLoader.db b/EEex/loader/InfinityLoader.db index 0b182a93..9a2cfcb4 100644 --- a/EEex/loader/InfinityLoader.db +++ b/EEex/loader/InfinityLoader.db @@ -2142,6 +2142,30 @@ Operations=ADD 26 Pattern=448B9934060000 Operations=ADD -15 +[Hook-CGameArea::Marshal(unsigned char**,unsigned int*,unsigned char)-FirstInstruction] +Pattern=4157488DAC2488FEFFFF +Operations=ADD -8 + +[Hook-CGameArea::Marshal(unsigned char**,unsigned int*,unsigned char)-AfterMarshal] +Pattern=4C8BA42470020000488B8D60010000 +Operations=ADD 0 + +[Hook-CGameArea::Unmarshal(unsigned char*,unsigned int,unsigned char)-FirstInstruction] +Pattern=405556574154415541564157488DAC24E0FEFFFF +Operations=ADD 0 + +[Hook-CGameArea::Unmarshal(unsigned char*,unsigned int,unsigned char)-AfterUnmarshal] +Pattern=488B9C2470020000418BC4 +Operations=ADD 0 + +[Hook-CGameContainer::CGameContainer(CGameArea*,CAreaFileContainer*,...)-AfterConstruct] +Pattern=498BC5488B4D704833CC +Operations=ADD 0 + +[Hook-CGameContainer::Marshal(SAreaFileWrapper*)-FirstInstruction] +Pattern=4889542410555741564157 +Operations=ADD 0 + [Hook-CGameContainer::Render()-B3EmptyContainer] Pattern=85C00F8893000000 Operations=ADD 150 From a366446611a9cef5dfaf532bbd947897eb4f2e40 Mon Sep 17 00:00:00 2001 From: 4Luke4 Date: Sat, 23 May 2026 22:36:35 +0200 Subject: [PATCH 3/5] CStoreFileItem --- EEex/copy/EEex_scripts/EEex_Marshal_Codec.lua | 73 ++++++++++--- EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua | 79 ++++++++++++++ EEex/copy/EEex_scripts/EEex_UDAux.lua | 103 +++++++++++++++++- EEex/loader/InfinityLoader.db | 20 ++++ 4 files changed, 261 insertions(+), 14 deletions(-) diff --git a/EEex/copy/EEex_scripts/EEex_Marshal_Codec.lua b/EEex/copy/EEex_scripts/EEex_Marshal_Codec.lua index e1c8b832..1057d6c8 100644 --- a/EEex/copy/EEex_scripts/EEex_Marshal_Codec.lua +++ b/EEex/copy/EEex_scripts/EEex_Marshal_Codec.lua @@ -18,6 +18,8 @@ EEex_Marshal_Private_TableFieldType = { } function EEex_Marshal_Private_NewTableBundle() + -- A bundle is a top-level sequence of named table payloads. Each handler name lets an + -- extension channel ignore unknown entries without corrupting data owned by other handlers. return { ["entries"] = {}, ["entryCount"] = 0, @@ -58,6 +60,8 @@ function EEex_Marshal_Private_AddTableBundleEntry(bundle, handlerName, toExport, end function EEex_Marshal_Private_DetermineTableNumberInfo(number, context) + -- Use the smallest integer encoding that can round-trip the value. The enum values are fixed + -- because older saves may already contain any of these field tags. if number ~= number or number % 1 ~= 0 then EEex_Error((context or "Marshal").." number must be an integer") end @@ -90,6 +94,8 @@ end function EEex_Marshal_Private_CalculateTableBundleSize(bundle, context) + -- The writer below is iterative to avoid Lua recursion limits. Keep the sizing walk structurally + -- identical so payload allocation exactly matches what WriteTableBundle will emit. local accumulator = 0 local lengthTypeSwitch = { ["boolean"] = function(v) @@ -181,6 +187,11 @@ end function EEex_Marshal_Private_WriteTableBundle(memoryPtr, bundle, context) + -- Layout: + -- handlerName\0, encoded table fields..., TABLE_END + -- handlerName\0, encoded table fields..., TABLE_END + -- TABLE_END + -- The final TABLE_END is read as an empty handler string. local writeNumber = function(number) local typeByte, writeFunc, writeAdvance = EEex_Marshal_Private_DetermineTableNumberInfo(number, context) EEex_Write8(memoryPtr, typeByte) @@ -278,15 +289,50 @@ function EEex_Marshal_Private_WriteTableBundle(memoryPtr, bundle, context) end end -function EEex_Marshal_Private_ReadTableBundle(memory, handlerReader) +function EEex_Marshal_Private_ReadTableBundle(memory, handlerReader, size) + -- When size is provided, keep reads inside the extension payload. The sprite extra-effect + -- channel predates bounded reads and still calls this without size, preserving that behavior. local baseMemory = memory + local endMemory = size and (memory + size) or nil + + local assertAvailable = function(byteCount, what) + if endMemory and memory + byteCount > endMemory then + EEex_Error("Marshal payload ended while reading "..what) + end + end + + local readByte = function(what) + assertAvailable(1, what) + local read = EEex_ReadU8(memory) + memory = memory + 1 + return read + end + + local readString = function(what) + if not endMemory then + local read = EEex_ReadString(memory) + memory = memory + #read + 1 + return read + end + + local cursor = memory + while cursor < endMemory do + if EEex_ReadU8(cursor) == 0 then + local read = EEex_ReadString(memory) + memory = cursor + 1 + return read + end + cursor = cursor + 1 + end + + EEex_Error("Marshal payload ended while reading "..what) + end while true do local toFill = {} - local handlerStr = EEex_ReadString(memory) - memory = memory + #handlerStr + 1 + local handlerStr = readString("handler name") -- The top-level list writes TABLE_END('\0') to signal that all -- marshalled data has ended, which reads as an empty string. @@ -296,46 +342,49 @@ function EEex_Marshal_Private_ReadTableBundle(memory, handlerReader) local fieldReadSwitch = { [EEex_Marshal_Private_TableFieldType.STRING] = function() - local read = EEex_ReadString(memory) - memory = memory + #read + 1 - return read + return readString("string field") end, [EEex_Marshal_Private_TableFieldType.INT8] = function() + assertAvailable(1, "int8 field") local read = EEex_Read8(memory) memory = memory + 1 return read end, [EEex_Marshal_Private_TableFieldType.INTU8] = function() - local read = EEex_ReadU8(memory) - memory = memory + 1 - return read + return readByte("uint8 field") end, [EEex_Marshal_Private_TableFieldType.INT16] = function() + assertAvailable(2, "int16 field") local read = EEex_Read16(memory) memory = memory + 2 return read end, [EEex_Marshal_Private_TableFieldType.INTU16] = function() + assertAvailable(2, "uint16 field") local read = EEex_ReadU16(memory) memory = memory + 2 return read end, [EEex_Marshal_Private_TableFieldType.INT32] = function() + assertAvailable(4, "int32 field") local read = EEex_Read32(memory) memory = memory + 4 return read end, [EEex_Marshal_Private_TableFieldType.INTU32] = function() + assertAvailable(4, "uint32 field") local read = EEex_ReadU32(memory) memory = memory + 4 return read end, [EEex_Marshal_Private_TableFieldType.INT64] = function() + assertAvailable(8, "int64 field") local read = EEex_Read64(memory) memory = memory + 8 return read end, [EEex_Marshal_Private_TableFieldType.INTU64] = function() + assertAvailable(8, "uint64 field") local read = EEex_ReadU64(memory) memory = memory + 8 return read @@ -353,8 +402,7 @@ function EEex_Marshal_Private_ReadTableBundle(memory, handlerReader) while true do - local keyFieldType = EEex_Read8(memory) - memory = memory + 1 + local keyFieldType = readByte("key field type") if keyFieldType == EEex_Marshal_Private_TableFieldType.TABLE_END then if tableStackTop == 0 then @@ -368,8 +416,7 @@ function EEex_Marshal_Private_ReadTableBundle(memory, handlerReader) EEex_Error("Unknown marshal key field type "..keyFieldType) end local key = keyReader() - local valueFieldType = EEex_Read8(memory) - memory = memory + 1 + local valueFieldType = readByte("value field type") if valueFieldType == EEex_Marshal_Private_TableFieldType.TABLE_START then local subTable = {} toFill[key] = subTable diff --git a/EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua b/EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua index 6269a38f..63ec6061 100644 --- a/EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua +++ b/EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua @@ -23,6 +23,8 @@ EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameArea::Marshal(unsigned char**,unsigned int*,unsigned char)-FirstInstruction"), 0, 8, 8, EEex_Marshal_Patch_Private_EntryStackVolatileRegisterIgnores, {[[ + ; rcx = CGameArea*, rdx = unsigned char**, r8 = unsigned int*, r9b = version. + ; Start a per-area UDAux collection before vanilla container marshal callbacks fire. #MAKE_SHADOW_SPACE(40) mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)], rdx @@ -42,6 +44,7 @@ EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameArea::Marshal(unsigned char**,unsigned int*,unsigned char)-AfterMarshal"), 0, 8, 8, EEex_Marshal_Patch_Private_VolatileRegisterIgnores, {[[ + ; The vanilla blob has been allocated and sized; append the X-UDA1.0 extension now. #MAKE_SHADOW_SPACE(8) call #L(EEex::UDAux_Hook_OnAfterAreaMarshal) #DESTROY_SHADOW_SPACE @@ -51,6 +54,7 @@ EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameContainer::Marshal(SAreaFileWrapper*)-FirstInstruction"), 0, 5, 5, EEex_Marshal_Patch_Private_EntryStackVolatileRegisterIgnores, {[[ + ; Container marshal order is the stable save ordinal used by the area UDAux payload. #MAKE_SHADOW_SPACE(40) mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)], rdx @@ -70,6 +74,7 @@ EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameArea::Unmarshal(unsigned char*,unsigned int,unsigned char)-FirstInstruction"), 0, 8, 8, EEex_Marshal_Patch_Private_EntryStackVolatileRegisterIgnores, {[[ + ; Parse X-UDA1.0 before vanilla unmarshal constructs CGameContainer instances. #MAKE_SHADOW_SPACE(40) mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)], rdx @@ -89,6 +94,7 @@ EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameArea::Unmarshal(unsigned char*,unsigned int,unsigned char)-AfterUnmarshal"), 0, 8, 8, EEex_Marshal_Patch_Private_VolatileRegisterIgnores, {[[ + ; r12d holds the CGameArea::Unmarshal result at this epilogue path. #MAKE_SHADOW_SPACE(8) mov ecx, r12d call #L(EEex::UDAux_Hook_OnAfterAreaUnmarshal) @@ -99,6 +105,7 @@ EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameContainer::CGameContainer(CGameArea*,CAreaFileContainer*,...)-AfterConstruct"), 0, 7, 7, EEex_Marshal_Patch_Private_VolatileRegisterIgnores, {[[ + ; r13 is the newly constructed CGameContainer; its item list has been populated. #MAKE_SHADOW_SPACE(8) mov rcx, r13 call #L(EEex::UDAux_Hook_OnAfterAreaContainerConstruct) @@ -106,6 +113,78 @@ ]]} ) + EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CStore::~CStore()-FirstInstruction"), 0, 5, 5, + EEex_Marshal_Patch_Private_EntryStackVolatileRegisterIgnores, + {[[ + ; rcx = CStore*. Delete item aux before the destructor drains m_lInventory. + #MAKE_SHADOW_SPACE(8) + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx + + call #L(EEex::UDAux_Hook_OnBeforeStoreInventoryClear) + + mov rcx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)] + #DESTROY_SHADOW_SPACE + ]]} + ) + + EEex_HookBeforeCallWithLabels(EEex_Label("Hook-CStore::Marshal()-dimmServiceFromMemory"), + EEex_Marshal_Patch_Private_VolatileRegisterIgnores, + {[[ + ; At this call: rcx = CRes*, rdx = marshalled STO buffer, r8d = size, rsi = CStore*. + ; The hook may replace rdx/r8d with a buffer containing the X-UDS1.0 payload. + #MAKE_SHADOW_SPACE(24) + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)], r9 + mov dword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-20)], r8d + + mov rcx, rsi + lea r9, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-20)] + call #L(EEex::UDAux_Hook_OnStoreMarshalData) + + mov rdx, rax + mov r8d, dword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-20)] + mov r9, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)] + mov rcx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)] + #DESTROY_SHADOW_SPACE + ]]} + ) + + EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CStore::SetResRef(CResRef const&)-BeforeInventoryClear"), 0, 7, 7, + EEex_Marshal_Patch_Private_VolatileRegisterIgnores, + {[[ + ; rbx = CStore*. Existing runtime CStoreFileItem nodes are about to be freed. + #MAKE_SHADOW_SPACE(8) + mov rcx, rbx + call #L(EEex::UDAux_Hook_OnBeforeStoreInventoryClear) + #DESTROY_SHADOW_SPACE + ]]} + ) + + EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CStore::SetResRef(CResRef const&)-BeforeHeaderRead"), 0, 10, 10, + EEex_Marshal_Patch_Private_VolatileRegisterIgnores, + {[[ + ; rbx = CStore*, rdi = demanded raw STO data, [rsp+20h] = CResStore* helper. + ; Import only records aux data here; runtime item pointers do not exist yet. + mov r8, qword ptr ss:[rsp+20h] + #MAKE_SHADOW_SPACE(8) + mov rcx, rbx + mov rdx, rdi + call #L(EEex::UDAux_Hook_OnBeforeStoreLoad) + #DESTROY_SHADOW_SPACE + ]]} + ) + + EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CStore::SetResRef(CResRef const&)-AfterInventoryPopulate"), 0, 6, 6, + EEex_Marshal_Patch_Private_VolatileRegisterIgnores, + {[[ + ; rbx still names the CStore after inventory/drink/spell arrays are rebuilt. + #MAKE_SHADOW_SPACE(8) + mov rcx, rbx + call #L(EEex::UDAux_Hook_OnAfterStoreLoad) + #DESTROY_SHADOW_SPACE + ]]} + ) + EEex_EnableCodeProtection() end)() diff --git a/EEex/copy/EEex_scripts/EEex_UDAux.lua b/EEex/copy/EEex_scripts/EEex_UDAux.lua index 8324aa15..1311ff95 100644 --- a/EEex/copy/EEex_scripts/EEex_UDAux.lua +++ b/EEex/copy/EEex_scripts/EEex_UDAux.lua @@ -5,6 +5,8 @@ end EEex_UDAux_AlreadyLoaded = true function EEex_UDAux_Private_AssertPersistentValue(value, path, seenTables) + -- Save data must stay in the shared marshal codec's safe domain. Return a deep copy so + -- later Lua mutations cannot change the already-validated payload while native code writes it. local valueType = type(value) if valueType == "boolean" then return value @@ -97,8 +99,11 @@ EEex_UDAux_Private_AreaMarshalData = nil EEex_UDAux_Private_AreaMarshalContainerIndex = 0 EEex_UDAux_Private_AreaUnmarshalContainers = nil EEex_UDAux_Private_AreaUnmarshalContainerIndex = 0 +EEex_UDAux_Private_StoreMarshalData = nil +EEex_UDAux_Private_StoreUnmarshalItems = nil function EEex_UDAux_Private_ForEachContainerItem(container, func) + -- Container items are saved in linked-list order, matching the order used by the area file. local node = container.m_lstItems.m_pNodeHead local index = 1 while node do @@ -111,13 +116,29 @@ function EEex_UDAux_Private_ForEachContainerItem(container, func) end end +function EEex_UDAux_Private_ForEachStoreItem(storeItemPtrs, func) + -- Native code passes ordinal -> pointer because CStore itself is not a bound userdata type. + for index, itemPtr in pairs(storeItemPtrs) do + if itemPtr ~= 0 then + func(index, EEex_PtrToUD(itemPtr, "CStoreFileItem")) + end + end +end + function EEex_UDAux_Private_NewAreaMarshalBundle(marshalData) local bundle = EEex_Marshal_Private_NewTableBundle() EEex_Marshal_Private_AddTableBundleEntry(bundle, "EEex_UDAux_Area", marshalData, "Area UDAux marshal") return bundle end +function EEex_UDAux_Private_NewStoreMarshalBundle(marshalData) + local bundle = EEex_Marshal_Private_NewTableBundle() + EEex_Marshal_Private_AddTableBundleEntry(bundle, "EEex_UDAux_Store", marshalData, "Store UDAux marshal") + return bundle +end + function EEex_UDAux_Private_BeginAreaMarshal(area) + -- Area marshal state is filled by CGameContainer::Marshal callbacks as the engine walks containers. EEex_UDAux_Private_AreaMarshalData = { ["containers"] = {}, } @@ -134,6 +155,7 @@ function EEex_UDAux_Private_OnAreaContainerMarshal(container) local containerIndex = EEex_UDAux_Private_AreaMarshalContainerIndex local containerData = {} + -- Persist by ordinal, not pointer. Pointers are process-local and change after load. local containerAux = EEex_UDAux_Private_Export(container, "CGameArea.m_lContainers["..containerIndex.."]") if containerAux then containerData["aux"] = containerAux @@ -178,6 +200,7 @@ function EEex_UDAux_Private_EndAreaMarshal() end function EEex_UDAux_Private_BeginAreaUnmarshal(memory, size) + -- Keep decoded data until each CGameContainer constructor callback supplies the matching runtime object. EEex_UDAux_Private_AreaUnmarshalContainers = {} EEex_UDAux_Private_AreaUnmarshalContainerIndex = 0 @@ -189,7 +212,7 @@ function EEex_UDAux_Private_BeginAreaUnmarshal(memory, size) if handlerStr == "EEex_UDAux_Area" and type(toFill) == "table" then EEex_UDAux_Private_AreaUnmarshalContainers = toFill["containers"] or {} end - end) + end, size) end function EEex_UDAux_Private_OnAreaContainerConstruct(container) @@ -225,6 +248,82 @@ function EEex_UDAux_Private_EndAreaUnmarshal() EEex_UDAux_Private_AreaUnmarshalContainerIndex = 0 end +function EEex_UDAux_Private_CalculateStoreMarshalExtensionSize(storeItemPtrs) + EEex_UDAux_Private_StoreMarshalData = nil + + local marshalData = { + ["items"] = {}, + } + + EEex_UDAux_Private_ForEachStoreItem(storeItemPtrs, function(itemIndex, item) + -- Store-owned CStoreFileItem pointers are runtime list nodes; save their aux by list ordinal. + local itemAux = EEex_UDAux_Private_Export(item, "CStore.m_lInventory["..itemIndex.."]") + if itemAux then + marshalData["items"][itemIndex] = itemAux + end + end) + + if next(marshalData["items"]) == nil then + return 0 + end + + EEex_UDAux_Private_StoreMarshalData = marshalData + return EEex_Marshal_Private_CalculateTableBundleSize(EEex_UDAux_Private_NewStoreMarshalBundle(marshalData), "Store UDAux marshal") +end + +function EEex_UDAux_Private_WriteStoreMarshalExtensionPayload(memory, size) + local marshalData = EEex_UDAux_Private_StoreMarshalData + if not marshalData then + return + end + EEex_Memset(memory, 0, size) + EEex_Marshal_Private_WriteTableBundle(memory, EEex_UDAux_Private_NewStoreMarshalBundle(marshalData), "Store UDAux marshal") +end + +function EEex_UDAux_Private_EndStoreMarshal() + EEex_UDAux_Private_StoreMarshalData = nil +end + +function EEex_UDAux_Private_BeginStoreUnmarshal(memory, size) + -- Store SetResRef has not populated m_lInventory yet; keep item data until the native after-load hook. + EEex_UDAux_Private_StoreUnmarshalItems = {} + + if memory == 0 or size == 0 then + return + end + + EEex_Marshal_Private_ReadTableBundle(memory, function(handlerStr, toFill) + if handlerStr == "EEex_UDAux_Store" and type(toFill) == "table" then + EEex_UDAux_Private_StoreUnmarshalItems = toFill["items"] or {} + end + end, size) +end + +function EEex_UDAux_Private_OnStoreLoaded(storeItemPtrs) + local items = EEex_UDAux_Private_StoreUnmarshalItems + if not items then + return + end + + EEex_UDAux_Private_ForEachStoreItem(storeItemPtrs, function(itemIndex, item) + local itemAux = items[itemIndex] + if itemAux then + EEex_UDAux_Private_Import(item, itemAux) + end + end) +end + +function EEex_UDAux_Private_EndStoreUnmarshal() + EEex_UDAux_Private_StoreUnmarshalItems = nil +end + +function EEex_UDAux_Private_OnStoreInventoryClear(storeItemPtrs) + -- This runs before the engine frees CStoreFileItem nodes, so deleting by pointer is still valid. + EEex_UDAux_Private_ForEachStoreItem(storeItemPtrs, function(_, item) + EEex_UDAux_Private_DeleteByLightUD(EEex_UDToLightUD(item)) + end) +end + function EEex_UDAux_Private_ApplyPendingItemAux(sprite) local spriteAux = EEex_TryGetUDAux(sprite) local pendingItems = spriteAux and spriteAux["EEex_UDAux_PendingItemAux"] @@ -236,6 +335,8 @@ function EEex_UDAux_Private_ApplyPendingItemAux(sprite) for i = 0, 38 do local auxiliary = pendingItems[i] if auxiliary then + -- Creature extra marshal data can arrive before equipment pointers are rebuilt. + -- Retry from load/list-resolved callbacks until every saved slot has a CItem. local item = sprite.m_equipment.m_items:get(i) if item then EEex_UDAux_Private_Import(item, auxiliary) diff --git a/EEex/loader/InfinityLoader.db b/EEex/loader/InfinityLoader.db index 9a2cfcb4..69335230 100644 --- a/EEex/loader/InfinityLoader.db +++ b/EEex/loader/InfinityLoader.db @@ -2166,6 +2166,26 @@ Operations=ADD 0 Pattern=4889542410555741564157 Operations=ADD 0 +[Hook-CStore::~CStore()-FirstInstruction] +Pattern=4883EC2083B9B800000000 +Operations=ADD -6 + +[Hook-CStore::Marshal()-dimmServiceFromMemory] +Pattern=41B101448BC5 +Operations=ADD 9 + +[Hook-CStore::SetResRef(CResRef const&)-BeforeInventoryClear] +Pattern=4439BBB80000007426 +Operations=ADD 0 + +[Hook-CStore::SetResRef(CResRef const&)-BeforeHeaderRead] +Pattern=488B4C2420488BF8 +Operations=ADD 18 + +[Hook-CStore::SetResRef(CResRef const&)-AfterInventoryPopulate] +Pattern=4C8B7C2440488B7C2450 +Operations=ADD -6 + [Hook-CGameContainer::Render()-B3EmptyContainer] Pattern=85C00F8893000000 Operations=ADD 150 From 8faac90518640b99e574191ed8eb25883c78b76f Mon Sep 17 00:00:00 2001 From: 4Luke4 Date: Sat, 23 May 2026 22:52:03 +0200 Subject: [PATCH 4/5] Update EEex_Marshal_Patch.lua --- EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua b/EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua index 63ec6061..aef2354d 100644 --- a/EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua +++ b/EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua @@ -20,11 +20,11 @@ EEex_Marshal_Patch_Private_VolatileRegisterIgnores, }) + -- rcx = CGameArea*, rdx = unsigned char**, r8 = unsigned int*, r9b = version. + -- Start a per-area UDAux collection before vanilla container marshal callbacks fire. EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameArea::Marshal(unsigned char**,unsigned int*,unsigned char)-FirstInstruction"), 0, 8, 8, EEex_Marshal_Patch_Private_EntryStackVolatileRegisterIgnores, {[[ - ; rcx = CGameArea*, rdx = unsigned char**, r8 = unsigned int*, r9b = version. - ; Start a per-area UDAux collection before vanilla container marshal callbacks fire. #MAKE_SHADOW_SPACE(40) mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)], rdx @@ -41,20 +41,20 @@ ]]} ) + -- The vanilla blob has been allocated and sized; append the X-UDA1.0 extension now. EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameArea::Marshal(unsigned char**,unsigned int*,unsigned char)-AfterMarshal"), 0, 8, 8, EEex_Marshal_Patch_Private_VolatileRegisterIgnores, {[[ - ; The vanilla blob has been allocated and sized; append the X-UDA1.0 extension now. #MAKE_SHADOW_SPACE(8) call #L(EEex::UDAux_Hook_OnAfterAreaMarshal) #DESTROY_SHADOW_SPACE ]]} ) + -- Container marshal order is the stable save ordinal used by the area UDAux payload. EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameContainer::Marshal(SAreaFileWrapper*)-FirstInstruction"), 0, 5, 5, EEex_Marshal_Patch_Private_EntryStackVolatileRegisterIgnores, {[[ - ; Container marshal order is the stable save ordinal used by the area UDAux payload. #MAKE_SHADOW_SPACE(40) mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)], rdx @@ -71,10 +71,10 @@ ]]} ) + -- Parse X-UDA1.0 before vanilla unmarshal constructs CGameContainer instances. EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameArea::Unmarshal(unsigned char*,unsigned int,unsigned char)-FirstInstruction"), 0, 8, 8, EEex_Marshal_Patch_Private_EntryStackVolatileRegisterIgnores, {[[ - ; Parse X-UDA1.0 before vanilla unmarshal constructs CGameContainer instances. #MAKE_SHADOW_SPACE(40) mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)], rdx @@ -91,10 +91,10 @@ ]]} ) + -- r12d holds the CGameArea::Unmarshal result at this epilogue path. EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameArea::Unmarshal(unsigned char*,unsigned int,unsigned char)-AfterUnmarshal"), 0, 8, 8, EEex_Marshal_Patch_Private_VolatileRegisterIgnores, {[[ - ; r12d holds the CGameArea::Unmarshal result at this epilogue path. #MAKE_SHADOW_SPACE(8) mov ecx, r12d call #L(EEex::UDAux_Hook_OnAfterAreaUnmarshal) @@ -102,10 +102,10 @@ ]]} ) + -- r13 is the newly constructed CGameContainer; its item list has been populated. EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameContainer::CGameContainer(CGameArea*,CAreaFileContainer*,...)-AfterConstruct"), 0, 7, 7, EEex_Marshal_Patch_Private_VolatileRegisterIgnores, {[[ - ; r13 is the newly constructed CGameContainer; its item list has been populated. #MAKE_SHADOW_SPACE(8) mov rcx, r13 call #L(EEex::UDAux_Hook_OnAfterAreaContainerConstruct) @@ -113,10 +113,10 @@ ]]} ) + -- rcx = CStore*. Delete item aux before the destructor drains m_lInventory. EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CStore::~CStore()-FirstInstruction"), 0, 5, 5, EEex_Marshal_Patch_Private_EntryStackVolatileRegisterIgnores, {[[ - ; rcx = CStore*. Delete item aux before the destructor drains m_lInventory. #MAKE_SHADOW_SPACE(8) mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx @@ -127,11 +127,11 @@ ]]} ) + -- At this call: rcx = CRes*, rdx = marshalled STO buffer, r8d = size, rsi = CStore*. + -- The hook may replace rdx/r8d with a buffer containing the X-UDS1.0 payload. EEex_HookBeforeCallWithLabels(EEex_Label("Hook-CStore::Marshal()-dimmServiceFromMemory"), EEex_Marshal_Patch_Private_VolatileRegisterIgnores, {[[ - ; At this call: rcx = CRes*, rdx = marshalled STO buffer, r8d = size, rsi = CStore*. - ; The hook may replace rdx/r8d with a buffer containing the X-UDS1.0 payload. #MAKE_SHADOW_SPACE(24) mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)], r9 @@ -149,10 +149,10 @@ ]]} ) + -- rbx = CStore*. Existing runtime CStoreFileItem nodes are about to be freed. EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CStore::SetResRef(CResRef const&)-BeforeInventoryClear"), 0, 7, 7, EEex_Marshal_Patch_Private_VolatileRegisterIgnores, {[[ - ; rbx = CStore*. Existing runtime CStoreFileItem nodes are about to be freed. #MAKE_SHADOW_SPACE(8) mov rcx, rbx call #L(EEex::UDAux_Hook_OnBeforeStoreInventoryClear) @@ -160,11 +160,11 @@ ]]} ) + -- rbx = CStore*, rdi = demanded raw STO data, [rsp+20h] = CResStore* helper. + -- Import only records aux data here; runtime item pointers do not exist yet. EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CStore::SetResRef(CResRef const&)-BeforeHeaderRead"), 0, 10, 10, EEex_Marshal_Patch_Private_VolatileRegisterIgnores, {[[ - ; rbx = CStore*, rdi = demanded raw STO data, [rsp+20h] = CResStore* helper. - ; Import only records aux data here; runtime item pointers do not exist yet. mov r8, qword ptr ss:[rsp+20h] #MAKE_SHADOW_SPACE(8) mov rcx, rbx @@ -174,10 +174,10 @@ ]]} ) + -- rbx still names the CStore after inventory/drink/spell arrays are rebuilt. EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CStore::SetResRef(CResRef const&)-AfterInventoryPopulate"), 0, 6, 6, EEex_Marshal_Patch_Private_VolatileRegisterIgnores, {[[ - ; rbx still names the CStore after inventory/drink/spell arrays are rebuilt. #MAKE_SHADOW_SPACE(8) mov rcx, rbx call #L(EEex::UDAux_Hook_OnAfterStoreLoad) From c441cc2e303da741285ac895d14aa15500ab71fd Mon Sep 17 00:00:00 2001 From: 4Luke4 Date: Sun, 31 May 2026 13:07:07 +0200 Subject: [PATCH 5/5] CStore --- EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua | 4 +- EEex/copy/EEex_scripts/EEex_UDAux.lua | 45 ++++++++++++++++--- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua b/EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua index aef2354d..dfb3eddc 100644 --- a/EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua +++ b/EEex/copy/EEex_scripts/EEex_Marshal_Patch.lua @@ -113,7 +113,7 @@ ]]} ) - -- rcx = CStore*. Delete item aux before the destructor drains m_lInventory. + -- rcx = CStore*. Delete store/item aux before the destructor drains m_lInventory. EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CStore::~CStore()-FirstInstruction"), 0, 5, 5, EEex_Marshal_Patch_Private_EntryStackVolatileRegisterIgnores, {[[ @@ -149,7 +149,7 @@ ]]} ) - -- rbx = CStore*. Existing runtime CStoreFileItem nodes are about to be freed. + -- rbx = CStore*. Existing runtime CStore data is about to be replaced. EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CStore::SetResRef(CResRef const&)-BeforeInventoryClear"), 0, 7, 7, EEex_Marshal_Patch_Private_VolatileRegisterIgnores, {[[ diff --git a/EEex/copy/EEex_scripts/EEex_UDAux.lua b/EEex/copy/EEex_scripts/EEex_UDAux.lua index 1311ff95..18321af3 100644 --- a/EEex/copy/EEex_scripts/EEex_UDAux.lua +++ b/EEex/copy/EEex_scripts/EEex_UDAux.lua @@ -100,6 +100,7 @@ EEex_UDAux_Private_AreaMarshalContainerIndex = 0 EEex_UDAux_Private_AreaUnmarshalContainers = nil EEex_UDAux_Private_AreaUnmarshalContainerIndex = 0 EEex_UDAux_Private_StoreMarshalData = nil +EEex_UDAux_Private_StoreUnmarshalAux = nil EEex_UDAux_Private_StoreUnmarshalItems = nil function EEex_UDAux_Private_ForEachContainerItem(container, func) @@ -117,7 +118,7 @@ function EEex_UDAux_Private_ForEachContainerItem(container, func) end function EEex_UDAux_Private_ForEachStoreItem(storeItemPtrs, func) - -- Native code passes ordinal -> pointer because CStore itself is not a bound userdata type. + -- Native code snapshots ordinal -> pointer because m_lInventory is a native linked list. for index, itemPtr in pairs(storeItemPtrs) do if itemPtr ~= 0 then func(index, EEex_PtrToUD(itemPtr, "CStoreFileItem")) @@ -248,13 +249,24 @@ function EEex_UDAux_Private_EndAreaUnmarshal() EEex_UDAux_Private_AreaUnmarshalContainerIndex = 0 end -function EEex_UDAux_Private_CalculateStoreMarshalExtensionSize(storeItemPtrs) +function EEex_UDAux_Private_CalculateStoreMarshalExtensionSize(store, storeItemPtrs) EEex_UDAux_Private_StoreMarshalData = nil + if storeItemPtrs == nil then + storeItemPtrs = store + store = nil + end local marshalData = { ["items"] = {}, } + if store then + local storeAux = EEex_UDAux_Private_Export(store, "CStore") + if storeAux then + marshalData["aux"] = storeAux + end + end + EEex_UDAux_Private_ForEachStoreItem(storeItemPtrs, function(itemIndex, item) -- Store-owned CStoreFileItem pointers are runtime list nodes; save their aux by list ordinal. local itemAux = EEex_UDAux_Private_Export(item, "CStore.m_lInventory["..itemIndex.."]") @@ -263,7 +275,7 @@ function EEex_UDAux_Private_CalculateStoreMarshalExtensionSize(storeItemPtrs) end end) - if next(marshalData["items"]) == nil then + if not marshalData["aux"] and next(marshalData["items"]) == nil then return 0 end @@ -285,7 +297,8 @@ function EEex_UDAux_Private_EndStoreMarshal() end function EEex_UDAux_Private_BeginStoreUnmarshal(memory, size) - -- Store SetResRef has not populated m_lInventory yet; keep item data until the native after-load hook. + -- Store SetResRef has not populated m_lInventory yet; keep decoded data until the after-load hook. + EEex_UDAux_Private_StoreUnmarshalAux = nil EEex_UDAux_Private_StoreUnmarshalItems = {} if memory == 0 or size == 0 then @@ -294,12 +307,23 @@ function EEex_UDAux_Private_BeginStoreUnmarshal(memory, size) EEex_Marshal_Private_ReadTableBundle(memory, function(handlerStr, toFill) if handlerStr == "EEex_UDAux_Store" and type(toFill) == "table" then + EEex_UDAux_Private_StoreUnmarshalAux = toFill["aux"] EEex_UDAux_Private_StoreUnmarshalItems = toFill["items"] or {} end end, size) end -function EEex_UDAux_Private_OnStoreLoaded(storeItemPtrs) +function EEex_UDAux_Private_OnStoreLoaded(store, storeItemPtrs) + if storeItemPtrs == nil then + storeItemPtrs = store + store = nil + end + + local storeAux = EEex_UDAux_Private_StoreUnmarshalAux + if storeAux and store then + EEex_UDAux_Private_Import(store, storeAux) + end + local items = EEex_UDAux_Private_StoreUnmarshalItems if not items then return @@ -314,11 +338,20 @@ function EEex_UDAux_Private_OnStoreLoaded(storeItemPtrs) end function EEex_UDAux_Private_EndStoreUnmarshal() + EEex_UDAux_Private_StoreUnmarshalAux = nil EEex_UDAux_Private_StoreUnmarshalItems = nil end -function EEex_UDAux_Private_OnStoreInventoryClear(storeItemPtrs) +function EEex_UDAux_Private_OnStoreInventoryClear(store, storeItemPtrs) + if storeItemPtrs == nil then + storeItemPtrs = store + store = nil + end + -- This runs before the engine frees CStoreFileItem nodes, so deleting by pointer is still valid. + if store then + EEex_UDAux_Private_DeleteByLightUD(EEex_UDToLightUD(store)) + end EEex_UDAux_Private_ForEachStoreItem(storeItemPtrs, function(_, item) EEex_UDAux_Private_DeleteByLightUD(EEex_UDToLightUD(item)) end)