using Dalamud.Hooking; using ICRebuild.Core; namespace ICRebuild.Runtime; public sealed unsafe class MovementModules(GameContext game, Configuration config, ModuleManager modules) { private delegate nint Move(nuint actor, nint data, uint arg); private delegate float Speed(long actor); private delegate void Acceleration(long actor); private delegate long Permission(long a1, uint a2, int a3, int a4); private delegate long Knock(long actor, float angle, float distance, float seconds, byte a5, long a6); private delegate nint Fall(nuint a1, uint flags); private delegate long FallCheck(long a1, long a2, long a3); private delegate nint Forced(nint actor, float x, float y, float z, float movementParameter, nint options); [ThreadStatic] private static HashSet? adjusted; public HeightActivation Height { get; } = new(); public float EffectiveHeight => !modules.IsEnabled(9) ? 0 : Policies.HeightDelta(game.Ready,modules.IsEnabled(10),game.ReadDungeon(),Height.Applied); public void Register() { modules.Register(11, _ => { }); modules.Register(10, _ => { }, 9); modules.Register(9, StartHeight); modules.Register(1, scope => { Hook? hook = null; hook = game.Hook(scope, Signatures.SpeedAddress, actor => { var original = hook!.Original(actor); try { if (!game.Ready || game.IsSlowed) return original; return float.IsFinite(original) ? Math.Max(0, original + config.Value(1)) : original; } catch(Exception ex) { game.Fault(1,ex); return original; } }); hook.Enable(); }); modules.Register(2, scope => { if(!AccelerationPolicy.IsAuditedVersion(game.GameVersion)) throw new InvalidOperationException($"最大加速度仅完成已核对客户端的字段核对,当前为 {game.GameVersion}。"); Hook? hook = null; hook = game.Hook(scope, "40 ?? 48 ?? ?? ?? 80 79 ?? ?? 48 ?? ?? 0f 84 ?? ?? ?? ?? 48 89 7c 24 ?? 48 ?? ?? ??", actor => { var frame=game.Frames.Read(); if(actor==0 || !frame.Ready || *(byte*)((nint)actor+0x3C)==0) { hook!.Original(actor); return; } var controller=*(nint*)((nint)actor+0x20); var owner=controller==0 ? 0 : *(nint*)(controller+0x3E0); ref var field=ref *(float*)((nint)actor+0x44); var prime=AccelerationPolicy.ShouldPrime(frame,owner,true,field); AccelerationPolicy.Run(ref field,prime,()=>hook!.Original(actor)); }); hook.Enable(); }); modules.Register(3, scope => { Hook? hook = null; hook = game.Hook(scope, Signatures.MovePermissionAddress, (a1,a2,a3,a4) => game.Ready && NativeIds.MovementPermissionChecks.Contains(a2) ? 1 : hook!.Original(a1,a2,a3,a4)); hook.Enable(); }); modules.Register(4, scope => { Hook? hook = null; hook = game.Hook(scope, Signatures.AntiKnockAddress, (actor,angle,distance,seconds,a5,a6) => { if (actor != 0 && actor == game.LocalAddress) { distance = config.Value(4); seconds = config.Published.KnockSeconds; } return hook!.Original(actor,angle,distance,seconds,a5,a6); }); hook.Enable(); }); modules.Register(5, scope => { Hook? hook = null; hook = game.Hook(scope, Signatures.OnSendFlag, (a1,flags) => hook!.Original(a1,game.Ready ? Policies.FallFlags(flags) : flags)); hook.Enable(); }); modules.Register(6, scope => { Hook? hook = null; hook = game.Hook(scope, Signatures.FallCheckPatch, (a1,a2,a3) => game.Ready ? -1 : hook!.Original(a1,a2,a3)); hook.Enable(); }); modules.Register(7, scope => { if(!AccelerationPolicy.IsAuditedVersion(game.GameVersion)) throw new InvalidOperationException($"强制移动入口参数仅核对了已审计客户端,当前为 {game.GameVersion}。"); Hook? hook = null; hook = game.Hook(scope, Signatures.FuckBewithAction, (actor,x,y,z,movementParameter,options) => { // This intercepts a forced-coordinate movement path; it does not remove a status. if(actor!=0 && actor==game.LocalAddress) return 0; return hook!.Original(actor,x,y,z,movementParameter,options); }); hook.Enable(); }); } private void StartHeight(ResourceScope scope) { Height.Reset(); Hook? normal = null, combat = null; normal = game.Hook(scope, Signatures.OnSendNormalMove, (actor,data,arg) => ApplyHeight(normal!.Original,actor,data,arg,false)); combat = game.Hook(scope, Signatures.OnSendCombatMove, (actor,data,arg) => ApplyHeight(combat!.Original,actor,data,arg,true)); scope.Defer(Height.Reset); normal.Enable(); combat.Enable(); } private nint ApplyHeight(Move original, nuint actor, nint data, uint arg, bool combat) { float delta; try { delta=EffectiveHeight; } catch(Exception ex) { game.Fault(9,ex); return original(actor,data,arg); } if (data == 0 || delta == 0) return original(actor,data,arg); adjusted ??= []; if (!adjusted.Add(data)) return original(actor,data,arg); var values = (float*)data; var first = combat ? 4 : 3; float old = values[first], old2 = combat ? values[7] : 0; try { if (!float.IsFinite(old) || (combat && !float.IsFinite(old2))) return original(actor,data,arg); values[first] = old + delta; if (combat) values[7] = old2 + delta; try { return original(actor,data,arg); } finally { values[first] = old; if (combat) values[7] = old2; } } finally { adjusted.Remove(data); } } }