Files
IC.Rebuild/Runtime/GameContext.cs
T

116 lines
5.4 KiB
C#

using Dalamud.Game.ClientState.Conditions;
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Event;
using ICRebuild.Core;
using System.Collections.Concurrent;
namespace ICRebuild.Runtime;
public sealed unsafe class GameContext(IObjectTable objects, IClientState client, ICondition condition,
IDataManager data, ISigScanner scanner, IGameInteropProvider interop, ITargetManager targets)
{
public IObjectTable Objects { get; } = objects;
public IClientState Client { get; } = client;
public ICondition Conditions { get; } = condition;
public IDataManager Data { get; } = data;
public ITargetManager Targets { get; } = targets;
public FrameStore Frames { get; } = new();
public string GameVersion { get; } = ReadGameVersion();
private readonly ConcurrentQueue<(int Id,string Error)> faults = new();
public void Fault(int id,Exception error) { if(faults.Count<32) faults.Enqueue((id,error.Message)); }
public void DrainFaults(ModuleManager modules)
{
while(faults.TryDequeue(out var fault))
{
modules.Disable(fault.Id);
modules.Status[fault.Id].State=ModuleState.Failed;
modules.Status[fault.Id].Detail=$"运行错误,已停用:{fault.Error}";
}
}
public bool Ready => Frames.Read().Ready;
public nint LocalAddress => Frames.Read().LocalAddress;
public bool IsPvP => Frames.Read().IsPvP;
public bool IsSlowed => Frames.Read().IsSlowed;
public DungeonSnapshot ReadDungeon() => Frames.Read().Dungeon;
// Called exclusively from Framework.Update, never from a native hook or worker thread.
public void Refresh(bool sampleDungeon)
{
try
{
var player=Objects.LocalPlayer;
if(!Client.IsLoggedIn || player==null || Conditions[ConditionFlag.BetweenAreas] || Conditions[ConditionFlag.BetweenAreas51])
{ Frames.Clear(); return; }
var dungeon=sampleDungeon ? SampleDungeon() : DungeonSnapshot.Empty;
var target=Targets.Target;
Frames.Publish(new(true,player.Address,Client.IsPvP,player.StatusList.Any(s=>StatusCatalog.AuditedSlow.Contains(s.StatusId)),dungeon,
(nint)FFXIVClientStructs.FFXIV.Client.Game.ActionManager.Instance(),target?.Address ?? 0,target?.EntityId ?? 0,
(nint)(&((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)player.Address)->StatusManager),
player.EntityId,Conditions[ConditionFlag.DutyRecorderPlayback]));
}
catch { Frames.Clear(); throw; }
}
private DungeonSnapshot SampleDungeon()
{
if (!Conditions[ConditionFlag.InDeepDungeon]) return DungeonSnapshot.Empty;
var framework = EventFramework.Instance();
if (framework == null) return DungeonSnapshot.Empty;
var dd = framework->GetInstanceContentDeepDungeon();
return dd == null || dd->Floor == 0 ? DungeonSnapshot.Empty : new(true, Client.TerritoryType, dd->DeepDungeonId, dd->Floor);
}
public Hook<T> Hook<T>(ResourceScope scope, string signature, T detour) where T : Delegate
{
if (!scanner.TryScanText(signature, out var address) || address == 0)
throw new InvalidOperationException("签名未匹配,此模块未适配当前客户端。");
return scope.Own(interop.HookFromAddress(address, detour));
}
public nint Scan(string signature)
{
if (!scanner.TryScanText(signature, out var address) || address == 0)
throw new InvalidOperationException("签名未匹配。");
return address;
}
public nint StaticAddress(string signature, int offset) => scanner.GetStaticAddressFromSig(signature, offset);
public Hook<T> HookAddress<T>(ResourceScope scope,nint address,T detour) where T:Delegate
{
if(address==0) throw new InvalidOperationException("原生函数地址未解析");
return scope.Own(interop.HookFromAddress(address,detour));
}
public SendSession ReadSendSession()
{
var frame = Frames.Read();
return frame.Ready && !frame.IsReplay
? new(true, Client.TerritoryType, frame.LocalAddress, frame.LocalEntityId)
: SendSession.Empty;
}
public AdaptationReport EvaluateAdaptation(ModuleManager modules)
{
var offsetsValid =
System.Runtime.InteropServices.Marshal.OffsetOf<FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara>(
nameof(FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara.StatusManager)).ToInt32() == StatusBlockPolicy.ManagerOffset &&
System.Runtime.InteropServices.Marshal.OffsetOf<FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject>(
nameof(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject.EntityId)).ToInt32() == StatusBlockPolicy.EntityIdOffset;
return AdaptationManifest.Evaluate(modules, GameVersion,
sig => scanner.TryScanText(sig, out var addr) && addr != 0 ? 1 : 0,
offsetsValid);
}
public static string ReadGameVersion()
{
try
{
var dir = Path.GetDirectoryName(Environment.ProcessPath);
var file = Path.Combine(dir ?? "", "ffxivgame.ver");
var version = File.Exists(file) ? File.ReadAllText(file).Trim() : "unknown";
return version.Length is > 0 and < 128 ? version : "unknown";
}
catch { return "unknown"; }
}
}