From 73a91283695e7194f4f94934cc15b1f4aca0e5ee Mon Sep 17 00:00:00 2001 From: NovaRain Date: Sun, 16 May 2021 13:05:38 +0800 Subject: [PATCH] Implemented an improved animation registration process --- sfall/Modules/Animations.cpp | 269 ++++++++++++++++++++++++++++++++++- 1 file changed, 263 insertions(+), 6 deletions(-) diff --git a/sfall/Modules/Animations.cpp b/sfall/Modules/Animations.cpp index db9f9025..3b0d27b8 100644 --- a/sfall/Modules/Animations.cpp +++ b/sfall/Modules/Animations.cpp @@ -37,12 +37,15 @@ enum AnimFlags { e_Append = 0x100, // sfall flag }; -//static int animationLimit = 32; +static int animationLimit = 32; +static int lockLimit; //pointers to new animation struct arrays static std::vector sf_anim_set; static std::vector sf_sad; +static std::vector lockAnimSet; + static fo::AnimationSet* animSet = (fo::AnimationSet*)FO_VAR_anim_set; static fo::AnimationSad* animSad = (fo::AnimationSad*)FO_VAR_sad; @@ -90,8 +93,9 @@ static struct AnimSetHack { 0x413AA4, 0x413DBC, }; - const DWORD anim_set_10[6] = { // anim_0.animType - 0x413C7E, 0x413E8A, 0x413F17, 0x415C24, 0x415D16, 0x415D44, + const DWORD anim_set_10[5] = { // anim_0.animType + 0x413C7E, 0x413F17, 0x415C24, 0x415D16, 0x415D44, + /*0x413E8A, - conflict with 0x413E88*/ }; const DWORD anim_set_14[6] = { // anim_0.source @@ -168,6 +172,236 @@ static struct SadHack { //////////////////////////////////////////////////////////////////////////////// +long regSlot = -2; + +/* + When the check_registry_ function finds an object for which a new animation + is being registered in the list of a sequence of already registered + animations, the engine stops registering animations for this object. + + This implementation will copy all currently registered animations from the + current slot to another slot with already registered animations, appending + them to the end of the sequence. + + This allows the engine to continue to animate the object sequentially. +*/ +static long __fastcall CopyRegistry(long checkSlot) { + long currRegSlot = fo::var::curr_anim_set; + if (!(animSet[currRegSlot].flags & AnimFlags::e_Reserved)) return 0; + + if (currRegSlot == checkSlot) { // this will be bug! + #ifndef NDEBUG + BREAKPOINT; + #endif + return 0; + } + long currRegAnims = fo::var::curr_anim_counter; + long lastAnim = animSet[checkSlot].totalAnimCount; + + devlog_f("\n\nCopyRegistry: animSet[%d] anims:%d >> animSet[%d] anims:%d", 0, currRegSlot, currRegAnims, checkSlot, lastAnim); + devlog_f("\n checkSlot: currAnim:%d, counter:%d", 0, animSet[checkSlot].currentAnim, animSet[checkSlot].counter); + + for (long i = animSet[checkSlot].counter; i < lastAnim; i++) { + long animType = animSet[checkSlot].animations[i].animType; + devlog_f("\n anim[%d]: animType:%d, animCode:%d", 0, i, animType, animSet[checkSlot].animations[i].animCode); + if (animType < 4) return 0; // don't add to "move to" types of animations + } + + long totalRegAnims = (animSet[checkSlot].flags & AnimFlags::e_Append) ? currRegAnims : lastAnim + currRegAnims; + if (totalRegAnims >= 55) return 0; // not enough slots for animations + + // copy the animations of the registered slot to the slot with the existing animations + for (long i = 0; i < currRegAnims; i++) { + devlog_f("\n Copy slot: animSet[%d].anim[%d] >> animSet[%d].anim[%d]", 0, currRegSlot, i, checkSlot, lastAnim); + animSet[checkSlot].animations[lastAnim++] = animSet[currRegSlot].animations[i]; + + //std::memset(&animSet[currRegSlot].animations[i], -1, 48); + } + animSet[checkSlot].flags |= animSet[currRegSlot].flags | AnimFlags::e_Append | AnimFlags::e_Suspend; // set flags + animSet[currRegSlot].flags = 0; + + fo::var::curr_anim_counter = totalRegAnims; // set the current number of registered animations in the slot + fo::var::curr_anim_set = checkSlot; // replace the currently registered slot with a slot with existing registered animations + + regSlot = checkSlot; + return lastAnim; +} + +static void __declspec(naked) check_registry_hack() { + __asm { + mov eax, animSet; + cmp [eax][edx][0x10], 0x0B; // animSet[].anim[].animType == Must_Call + je skip; + //cmp [eax][edx][0x10], 0x0C; // animSet[].anim[].animType == Call3 + //je skip; + test [eax][esi][0x0C], e_EndAnim; // anim_set[].flags + jnz skip; + push ecx; + push edx; + call CopyRegistry; + test eax, eax; + cmovnz ebx, eax; // set ebx to the current count of animations to check the next anim_set slot + pop edx; + pop ecx; + // (re)set ZF + setz al; + test al, al; +skip: + retn; + } +} + +static long __fastcall anim_cleanup_sub(long currAnims) { + //long currAnims = fo::var::curr_anim_counter; + fo::AnimationSet* set = &animSet[fo::var::curr_anim_set]; + if (set->flags & AnimFlags::e_Append) { + set->flags ^= AnimFlags::e_Append; + if (set->currentAnim == -1000 || set->totalAnimCount >= currAnims) return -1; + return set->totalAnimCount; + } + // default + return (currAnims <= 0) ? -1 : 0; +} + +static void __declspec(naked) anim_cleanup_hack() { + __asm { + call anim_cleanup_sub; + mov ecx, eax; // ecx - anims slot index + mov ebx, eax; // ebx - anims counter + test eax, eax; + retn; + } +} + +static long __fastcall LockAnimSlot(long slot) { + if (animSet[slot].counter != animSet[slot].totalAnimCount) { + lockAnimSet[slot] = 8; + } + return slot; +} + +static long __fastcall UnlockAnimSlot(long slot) { + long lockCount = 0; + for (auto it = lockAnimSet.begin(); it != lockAnimSet.end(); ++it) { + if (*it > 0) { + (*it)--; + lockCount++; + } + } + if (lockCount > lockLimit) fo::func::debug_printf("\n[SFALL] Warning: The number of animated slots in the lock is too large, locked %d of %d", lockCount, animationLimit); + + //if (slot) slot /= sizeof(fo::AnimationSet); + //lockAnimSet[slot] = 0; + return 0; +} + +static BYTE __fastcall CheckLockAnimSlot(long, long slot) { + return lockAnimSet[slot]; +} + +#ifndef NDEBUG +static void CheckReg() { + long s = regSlot; + regSlot = -2; + if (s != fo::var::curr_anim_set) return; + + dlog_f("\nregister_end: anim_set: %d, total_anim: %d", 0, fo::var::curr_anim_set, fo::var::curr_anim_counter); + for (long i = 0; i < fo::var::curr_anim_counter; i++) { + dlog_f("\n anim[%d]: animType:%d, animCode:%d, source:%d (%s), callFunc:%d, callFunc3:%d", 0, i, + animSet[s].animations[i].animType, animSet[s].animations[i].animCode, + animSet[s].animations[i].source, fo::func::critter_name(animSet[s].animations[i].source), + animSet[s].animations[i].callFunc, animSet[s].animations[i].callFunc3 + ); + } +} +#endif + +static void __declspec(naked) register_end_hack_begin() { + __asm { + #ifndef NDEBUG + pushadc; + call CheckReg; + popadc; + #endif + mov edx, ds:[FO_VAR_curr_anim_counter]; + mov esi, animSet; + and [esi][eax][0xC], ~e_InUse; // animSet[].flags (unset inUse flag) + test word ptr [esi][eax][0xC], e_Append; // animSet[].flags + jnz isAppend; // is set + retn; +isAppend: + mov [esi][eax][0x8], edx; // animSet[].totalAnimCount + add esp, 4; + mov edx, 0x413D14; + mov esi, eax; // keep offset to anim_set slot + jmp edx; + } +} + +static void __declspec(naked) register_end_hack_end() { + __asm { + mov eax, animSet; + mov dx, [eax][esi][0xC]; // animSet[].flags + test dx, e_Append; // slot with added animation? + jz skip; + and dx, ~(e_Append | e_Suspend); // unset flags +skip: + mov [eax][esi][0xC], dx; // animSet[].flags + //mov ecx, esi; + call UnlockAnimSlot; + pop esi; + pop edx; + pop ecx; + retn; + } +} + +static void __declspec(naked) register_clear_hook() { + __asm { + mov ecx, eax; + call LockAnimSlot; + jmp fo::funcoffs::anim_set_end_; + } +} + +static void __declspec(naked) anim_free_slot_hack() { + static const DWORD anim_free_slot_next = 0x413BD5; + using namespace fo; + __asm { + pushadc; + call CheckLockAnimSlot; + test al, al; + popadc; + jnz isLock; + test word ptr [esp + 0x10 - 0x10 + 4], RB_UNKNOWN; // command & RB_100_ (register_ping_) + retn; +isLock: + add esp, 4; + jmp anim_free_slot_next; + } +} + +static void ClearAllLock() { + std::fill(lockAnimSet.begin(), lockAnimSet.end(), 0); +} + +static void __declspec(naked) anim_stop_hack() { + __asm { + call ClearAllLock; + mov edx, 1; + retn; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +#ifndef NDEBUG +static void __fastcall ClearDataAnimations(long slot) { + std::memset(animSet[slot].animations, 0, sizeof(fo::AnimationSet) - 16); + //dlog_f("\nanim_set_end: clear all animations data in slot %d", 0, slot); +} +#endif + static void __declspec(naked) anim_set_end_hack() { __asm { test dl, e_InCombat; // is combat flag set? @@ -176,6 +410,10 @@ static void __declspec(naked) anim_set_end_hack() { skip: mov eax, animSet; mov [eax][esi], ebx; // anim_set[].curr_anim = -1000 + #ifndef NDEBUG + mov ecx, [esp + 0x2C - 0x1C + 4]; // current slot + call ClearDataAnimations; + #endif retn; } } @@ -242,8 +480,6 @@ skip: } void ApplyAnimationsAtOncePatches(signed char aniMax) { - if (aniMax <= 32) return; - //allocate memory to store larger animation struct arrays sf_anim_set.resize(aniMax + 1); // include a dummy sf_sad.resize(aniMax + 1); // -8? @@ -299,7 +535,8 @@ void ApplyAnimationsAtOncePatches(signed char aniMax) { } void Animations::init() { - int animationLimit = IniReader::GetConfigInt("Misc", "AnimationsAtOnceLimit", 32); + animationLimit = IniReader::GetConfigInt("Misc", "AnimationsAtOnceLimit", 32); + if (animationLimit < 32) animationLimit = 32; if (animationLimit > 32) { if (animationLimit > 127) { animationLimit = 127; @@ -309,6 +546,26 @@ void Animations::init() { dlogr(" Done", DL_INIT); } + // Improved implementation of animation registration + MakeCall(0x413E88, check_registry_hack, 2); + MakeCall(0x413DCE, anim_cleanup_hack, 1); + SafeWrite16(0x413DD4, 0x4478); // js 0x413E1A + MakeCall(0x413CE8, register_end_hack_begin, 1); + MakeJump(0x413D64, register_end_hack_end); + SafeWrite16(0x413D0B, 0xC689); // and dl, not 8 > mov esi, eax (keep offset to anim_set slot) + SafeWrite8(0x413D0D, CodeType::Nop); + + // Implement a temporary lock on an animation slot after it is cleared by the register_clear_ function + // to prevent it from being used as a free slot when registering a nested animation + HookCall(0x413C97, register_clear_hook); + MakeCall(0x413BB7, anim_free_slot_hack); + MakeCall(0x4186CF, anim_stop_hack); + + lockAnimSet.resize(animationLimit); + lockLimit = animationLimit / 4; + + LoadGameHook::OnGameReset() += ClearAllLock; + // Fix for calling anim() functions in combat MakeCall(0x415DE2, anim_set_end_hack, 1); SafeWrite8(0x415DEB, CodeType::JumpShort); // jz > jmp