diff --git a/EEex/copy/EEex_scripts/EEex_Opcode_Patch.lua b/EEex/copy/EEex_scripts/EEex_Opcode_Patch.lua index 871cb2f..10f7976 100644 --- a/EEex/copy/EEex_scripts/EEex_Opcode_Patch.lua +++ b/EEex/copy/EEex_scripts/EEex_Opcode_Patch.lua @@ -273,6 +273,120 @@ ]]} ) + --[[ + +----------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | Opcode #261 | + +----------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | (special & 1) != 0 -> Process only m_effectAmount; do not continue the vanilla lower-level fallback loop | + | (special & 2) != 0 -> Replace the vanilla first eligible memorized spell at the current level with a random eligible spell from that same level | + +----------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | [EEex.dll] EEex::Opcode_Hook_Op261_SelectRandomSpell( | + | pEffect: CGameEffect*, pSprite: CGameSprite*, pVanillaSpell: CCreatureFileMemorizedSpell*, | + | pMemorizedList: CTypedPtrList*) -> CCreatureFileMemorizedSpell* | + | return -> pVanillaSpell unless bit1 applies and the current mage / priest list has a random eligible replacement | + +----------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | [EEex.dll] EEex::Opcode_Hook_Op261_ShouldStopAfterCurrentLevel(pEffect: CGameEffect*) -> bool | + | return -> true when bit0 is set | + +----------------------------------------------------------------------------------------------------------------------------------------------------------------+ + --]] + + ------------------------------------------------------------ + -- [EEex.dll] EEex::Opcode_Hook_Op261_SelectRandomSpell() -- + ------------------------------------------------------------ + + -- Mage path: RBX is the first CCreatureFileMemorizedSpell* whose m_flags bit0 is clear. + -- R14 -> CGameEffect, RDI -> CGameSprite, and R12 is the vanilla per-level list offset. + -- The C++ helper preserves vanilla behavior for class 19 / 21 empty-resref special paths. + EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameEffectRememorizeSpell::ApplyEffect()-MageSelectedSpell"), 0, 6, 6, { + {"hook_integrity_watchdog_ignore_registers", { + EEex_HookIntegrityWatchdogRegister.RAX, EEex_HookIntegrityWatchdogRegister.RBX, EEex_HookIntegrityWatchdogRegister.RCX, + EEex_HookIntegrityWatchdogRegister.RDX, EEex_HookIntegrityWatchdogRegister.R8, EEex_HookIntegrityWatchdogRegister.R9, + EEex_HookIntegrityWatchdogRegister.R10, EEex_HookIntegrityWatchdogRegister.R11 + }}}, + {[[ + #MAKE_SHADOW_SPACE(32) + 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 + + mov rcx, r14 ; pEffect + mov rdx, rdi ; pSprite + mov r8, rbx ; pVanillaSpell + lea r9, qword ptr ds:[r12+rdi+#OFFSET_OF(CGameSprite.m_memorizedSpellsMage)] ; pMemorizedList for current mage level + call #L(EEex::Opcode_Hook_Op261_SelectRandomSpell) + mov rbx, rax ; Use either vanilla or random selected spell + + 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 + ]]} + ) + + -- Priest path: same register ownership as the mage path, but the current list is m_memorizedSpellsPriest. + EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameEffectRememorizeSpell::ApplyEffect()-PriestSelectedSpell"), 0, 6, 6, { + {"hook_integrity_watchdog_ignore_registers", { + EEex_HookIntegrityWatchdogRegister.RAX, EEex_HookIntegrityWatchdogRegister.RBX, EEex_HookIntegrityWatchdogRegister.RCX, + EEex_HookIntegrityWatchdogRegister.RDX, EEex_HookIntegrityWatchdogRegister.R8, EEex_HookIntegrityWatchdogRegister.R9, + EEex_HookIntegrityWatchdogRegister.R10, EEex_HookIntegrityWatchdogRegister.R11 + }}}, + {[[ + #MAKE_SHADOW_SPACE(32) + 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 + + mov rcx, r14 ; pEffect + mov rdx, rdi ; pSprite + mov r8, rbx ; pVanillaSpell + lea r9, qword ptr ds:[r12+rdi+#OFFSET_OF(CGameSprite.m_memorizedSpellsPriest)] ; pMemorizedList for current priest level + call #L(EEex::Opcode_Hook_Op261_SelectRandomSpell) + mov rbx, rax ; Use either vanilla or random selected spell + + 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.dll] EEex::Opcode_Hook_Op261_ShouldStopAfterCurrentLevel() -- + ---------------------------------------------------------------------- + + local op261StrictLevelLoopHook = function(labelName) + -- The hook site is after vanilla decrements ESI for the next lower level and before it adjusts R12/R13. + -- If bit0 is set, clear ESI so the untouched vanilla "test esi, esi / jg loop" falls through to completion. + EEex_HookBeforeRestoreWithLabels(EEex_Label(labelName), 0, 8, 8, { + {"hook_integrity_watchdog_ignore_registers", { + EEex_HookIntegrityWatchdogRegister.RAX, EEex_HookIntegrityWatchdogRegister.RCX, EEex_HookIntegrityWatchdogRegister.RDX, + EEex_HookIntegrityWatchdogRegister.RSI, EEex_HookIntegrityWatchdogRegister.R8, EEex_HookIntegrityWatchdogRegister.R9, + EEex_HookIntegrityWatchdogRegister.R10, EEex_HookIntegrityWatchdogRegister.R11 + }}}, + {[[ + #MAKE_SHADOW_SPACE(8) + mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx + + mov rcx, r14 ; pEffect + call #L(EEex::Opcode_Hook_Op261_ShouldStopAfterCurrentLevel) + test al, al + + mov rcx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)] + #DESTROY_SHADOW_SPACE + jz #L(return) + + xor esi, esi ; Force vanilla loop condition to fail + ]]} + ) + end + + op261StrictLevelLoopHook("Hook-CGameEffectRememorizeSpell::ApplyEffect()-MageAfterLevelDecrement") + op261StrictLevelLoopHook("Hook-CGameEffectRememorizeSpell::ApplyEffect()-PriestAfterLevelDecrement") + --[[ +------------------------------------------------------------------------------------------------------+ | Opcode #280 | diff --git a/EEex/loader/InfinityLoader.db b/EEex/loader/InfinityLoader.db index 0b182a9..6ee3cf5 100644 --- a/EEex/loader/InfinityLoader.db +++ b/EEex/loader/InfinityLoader.db @@ -2251,6 +2251,22 @@ Operations=ADD -43 Pattern=488D8FC81C0000 Operations=ADD 20 +[Hook-CGameEffectRememorizeSpell::ApplyEffect()-MageAfterLevelDecrement] +Pattern=0F8FEEFEFFFFE95B010000 +Operations=ADD -10 + +[Hook-CGameEffectRememorizeSpell::ApplyEffect()-MageSelectedSpell] +Pattern=0F8E8D020000 +Operations=ADD 91 + +[Hook-CGameEffectRememorizeSpell::ApplyEffect()-PriestAfterLevelDecrement] +Pattern=41899E14010000 +Operations=ADD -16 + +[Hook-CGameEffectRememorizeSpell::ApplyEffect()-PriestSelectedSpell] +Pattern=85C00F8E2E010000 +Operations=ADD 94 + [Hook-CGameEffectSaveVsDeath::ApplyEffect()-ImmediateSaveWriteOffset] Pattern=0F852E0100000FB687AC050000 Operations=ADD -8