Initial import of IC.Rebuild
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
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"; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user