134 lines
6.2 KiB
C#
134 lines
6.2 KiB
C#
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<nint>? 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<Speed>? hook = null;
|
|
hook = game.Hook<Speed>(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(game.GameVersion!=AccelerationPolicy.AuditedVersion) throw new InvalidOperationException("最大加速度仅完成 2026.09.15 客户端的字段核对。");
|
|
Hook<Acceleration>? hook = null;
|
|
hook = game.Hook<Acceleration>(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<Permission>? hook = null;
|
|
hook = game.Hook<Permission>(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<Knock>? hook = null;
|
|
hook = game.Hook<Knock>(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<Fall>? hook = null;
|
|
hook = game.Hook<Fall>(scope, Signatures.OnSendFlag, (a1,flags) => hook!.Original(a1,game.Ready ? Policies.FallFlags(flags) : flags));
|
|
hook.Enable();
|
|
});
|
|
modules.Register(6, scope =>
|
|
{
|
|
Hook<FallCheck>? hook = null;
|
|
hook = game.Hook<FallCheck>(scope, Signatures.FallCheckPatch, (a1,a2,a3) => game.Ready ? -1 : hook!.Original(a1,a2,a3));
|
|
hook.Enable();
|
|
});
|
|
modules.Register(7, scope =>
|
|
{
|
|
if(game.GameVersion!=AccelerationPolicy.AuditedVersion) throw new InvalidOperationException("强制移动入口参数仅核对了 2026.09.15 客户端");
|
|
Hook<Forced>? hook = null;
|
|
hook = game.Hook<Forced>(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<Move>? normal = null, combat = null;
|
|
normal = game.Hook<Move>(scope, Signatures.OnSendNormalMove, (actor,data,arg) => ApplyHeight(normal!.Original,actor,data,arg,false));
|
|
combat = game.Hook<Move>(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); }
|
|
}
|
|
}
|