Initial import of IC.Rebuild
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net10.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable></PropertyGroup>
|
||||
<ItemGroup><Reference Include="Lumina;Lumina.Excel"><HintPath>$(APPDATA)\XIVLauncher\addon\Hooks\dev\%(Identity).dll</HintPath></Reference></ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,16 @@
|
||||
using Lumina;
|
||||
using System.Text.Json;
|
||||
if(args.Length is <2 or >3) throw new ArgumentException("ActionAudit sqpack-path name-fragment|--movement [output-json]");
|
||||
using var game=new GameData(args[0]);
|
||||
var rows=new List<object>();
|
||||
var timelines=new HashSet<uint>();
|
||||
var ids=args[1].StartsWith("--ids:") ? args[1][6..].Split(',').Select(uint.Parse).ToHashSet() : null;
|
||||
foreach(var action in game.GetExcelSheet<Lumina.Excel.Sheets.Action>() ?? throw new InvalidDataException("Action sheet unavailable"))
|
||||
if(ids!=null ? ids.Contains(action.RowId) : args[1]=="--movement" ? action.IsPlayerAction && action.AffectsPosition : action.Name.ToString().Contains(args[1],StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteLine($"{action.RowId} {action.Name}: AffectsPosition={action.AffectsPosition}, Hostile={action.CanTargetHostile}, AnimationEnd={action.AnimationEnd.RowId}");
|
||||
rows.Add(new { ActionId=action.RowId,Name=action.Name.ToString(),Timeline=action.AnimationEnd.RowId,PvP=action.IsPvP,PlayerAction=action.IsPlayerAction,AffectsPosition=action.AffectsPosition });
|
||||
if(action.AnimationEnd.RowId is >0 and <=ushort.MaxValue) timelines.Add(action.AnimationEnd.RowId);
|
||||
}
|
||||
Console.WriteLine($"Skills={rows.Count}, Timelines={timelines.Count}");
|
||||
if(args.Length==3) File.WriteAllText(args[2],JsonSerializer.Serialize(rows,new JsonSerializerOptions { WriteIndented=true }));
|
||||
@@ -0,0 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net10.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable></PropertyGroup>
|
||||
<ItemGroup><Compile Include="../../Core/**/*.cs" Link="Core/%(Filename)%(Extension)" /></ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using ICRebuild.Core;
|
||||
|
||||
if(args.Length!=3) throw new ArgumentException("CaptureAudit capture.jsonl opcodes.json report.json");
|
||||
var packets=PacketFiles.Import(args[0]);
|
||||
var versions=packets.Select(p=>p.GameVersion).Distinct().ToArray();
|
||||
if(versions.Length!=1 || packets.Length==0) throw new InvalidDataException("Expected one nonempty capture version");
|
||||
var map=new OpcodeMap(); map.Load(File.ReadAllText(args[1]),versions[0],"local cache");
|
||||
string? Name(PacketRecord p)=>map.Resolve(p.GameVersion,p.Direction,p.Opcode);
|
||||
var supported=new HashSet<string>{
|
||||
"UP_PingReq","DOWN_PingRsp","UP_UpdatePositionHandler","UP_UpdatePositionInstance","UP_EventStart",
|
||||
"UP_ActionSend","UP_ActionSendPos","UP_ClientTrigger",
|
||||
"UP_EventFinish","UP_EventFinish4","UP_EventFinish8","UP_EventFinish16","UP_EventFinish32","UP_EventFinish64","UP_EventFinish128","UP_EventFinish255",
|
||||
"UP_EventAction","UP_EventAction4","UP_EventAction8","UP_EventAction16","UP_EventAction32","UP_EventAction64","UP_EventAction128","UP_EventAction255",
|
||||
"DOWN_EffectResult","DOWN_UpdateHate","DOWN_UpdateHater","DOWN_EffectResultBasic","DOWN_JobGauge","DOWN_UpdateClassInfo","DOWN_FirstAttack",
|
||||
"DOWN_ActorControl","DOWN_ActorControlSelf","DOWN_ActorControlTarget","DOWN_Effect","DOWN_StatusEffectList","DOWN_ActorDelete","DOWN_ObjectSpawn","DOWN_NpcSpawn"
|
||||
};
|
||||
var parsed=packets.Select(p=>(Packet:p,Name:Name(p),Result:p.PayloadComplete ? PacketParser.Parse(Name(p),p.Payload,p.GameVersion) : new ParseResult(false,"incomplete"))).ToArray();
|
||||
var correlations=PacketCorrelator.Ping(packets,map);
|
||||
var actionCorrelations=PacketCorrelator.Actions(packets,map);
|
||||
// Replay owned sample data through the manager and recorder; no native calls or sockets.
|
||||
using var manager=new NetworkPipeline(versions[0]);
|
||||
var replayBuffer=new PacketBuffer { Paused=false };
|
||||
var recorder=new PacketRecorder(replayBuffer);
|
||||
using var up=manager.Subscribe(NetworkPhase.BeforeSend,recorder.Record);
|
||||
using var down=manager.Subscribe(NetworkPhase.Received,recorder.Record);
|
||||
long returned=0;
|
||||
using var post=manager.Subscribe(NetworkPhase.AfterSend,_=>returned++);
|
||||
foreach(var p in packets)
|
||||
{
|
||||
CaptureRead Read(Func<int,bool> accepts)=>accepts(p.Opcode) ? new(new(p.Opcode,p.Target,p.Payload,p.PayloadComplete,p.CaptureIssue)) : new(null);
|
||||
if(p.Direction==PacketDirection.Up) manager.Send(()=>1,Read); else manager.Receive(()=>1,Read);
|
||||
}
|
||||
var replayed=replayBuffer.Snapshot();
|
||||
var replayMatches=replayed.Length==packets.Length && replayed.Zip(packets).All(pair=>
|
||||
pair.First.Direction==pair.Second.Direction && pair.First.Opcode==pair.Second.Opcode &&
|
||||
pair.First.GameVersion==pair.Second.GameVersion && pair.First.Target==pair.Second.Target &&
|
||||
pair.First.PayloadComplete==pair.Second.PayloadComplete && pair.First.CaptureIssue==pair.Second.CaptureIssue &&
|
||||
pair.First.Payload.SequenceEqual(pair.Second.Payload));
|
||||
if(!replayMatches || manager.ObserverErrors!=0 || returned!=packets.Count(p=>p.Direction==PacketDirection.Up))
|
||||
throw new InvalidDataException("Manager replay changed sample data or missed send completion observations");
|
||||
var report=new {
|
||||
Source=Path.GetFileName(args[0]),SHA256=Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(args[0]))),
|
||||
MapSHA256=Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(args[1]))),MapSource=map.Snapshot.Source,Version=versions[0],
|
||||
Records=packets.Length,Up=packets.Count(p=>p.Direction==PacketDirection.Up),Down=packets.Count(p=>p.Direction==PacketDirection.Down),
|
||||
ManagerReplay=new {Matches=replayMatches,Records=replayed.Length,SendReturns=returned,manager.ObserverErrors,manager.ReadErrors},
|
||||
FirstTime=packets.Min(p=>p.Time),LastTime=packets.Max(p=>p.Time),FirstSequence=packets.First().Sequence,LastSequence=packets.Last().Sequence,
|
||||
SequenceContinuous=packets.Zip(packets.Skip(1)).All(p=>p.Second.Sequence==p.First.Sequence+1),
|
||||
MonotonicOrder=packets.Zip(packets.Skip(1)).All(p=>p.Second.MonotonicTicks>=p.First.MonotonicTicks),
|
||||
Complete=packets.Count(p=>p.PayloadComplete),Incomplete=packets.Count(p=>!p.PayloadComplete),CaptureIssues=packets.Count(p=>p.CaptureIssue!=null),
|
||||
PayloadBytes=packets.Sum(p=>p.Payload.Length),MinPayload=packets.Min(p=>p.Payload.Length),MaxPayload=packets.Max(p=>p.Payload.Length),
|
||||
EmptyPayloads=packets.Count(p=>p.Payload.Length==0),Known=packets.Count(p=>Name(p)!=null),Unknown=packets.Count(p=>Name(p)==null),
|
||||
Parsed=parsed.Count(p=>p.Result.Parsed),SupportedButRejected=parsed.Count(p=>p.Name!=null && supported.Contains(p.Name) && !p.Result.Parsed),
|
||||
Groups=parsed.GroupBy(p=>new{p.Packet.Direction,p.Packet.Opcode,p.Name,Length=p.Packet.Payload.Length,p.Packet.PayloadComplete,p.Result.Parsed})
|
||||
.OrderByDescending(g=>g.Count()).Select(g=>new{g.Key,Count=g.Count(),Rejection=g.Key.Name!=null && supported.Contains(g.Key.Name) && !g.Key.Parsed ? g.First().Result.Summary : null}),
|
||||
PingPairs=correlations.Select(p=>new{p.Request,p.Response,Milliseconds=p.Elapsed.TotalMilliseconds}),
|
||||
ActionPairs=actionCorrelations.Select(p=>new{p.Request,p.Response,Milliseconds=p.Elapsed.TotalMilliseconds}),
|
||||
Limitations="No raw payload, coordinates or actor IDs included. Successful parsing does not verify all fields. Header-read failures absent from export require UI counters."
|
||||
};
|
||||
File.WriteAllText(args[2],JsonSerializer.Serialize(report,new JsonSerializerOptions{WriteIndented=true}));
|
||||
Console.WriteLine($"Records={report.Records}; up={report.Up}; down={report.Down}; complete={report.Complete}; known={report.Known}; parsed={report.Parsed}; rejected={report.SupportedButRejected}; pingPairs={correlations.Length}; actionPairs={actionCorrelations.Length}");
|
||||
@@ -0,0 +1,82 @@
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$AssemblyPath,
|
||||
[Parameter(Mandatory)][string]$OutputPath,
|
||||
[string]$CecilPath = (Join-Path $env:APPDATA 'XIVLauncher\addon\Hooks\dev\Mono.Cecil.dll')
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
# Read the target as data with Cecil. Never load or execute the target assembly.
|
||||
Add-Type -Path $CecilPath
|
||||
$icInput = (Resolve-Path -LiteralPath $AssemblyPath).Path
|
||||
$icAssembly = [Mono.Cecil.AssemblyDefinition]::ReadAssembly($icInput)
|
||||
try {
|
||||
$icSelected = $icAssembly.MainModule.Types | Where-Object {
|
||||
$_.FullName -match '^StatusEffectList(Entry)?$|^SamplePlugin\.Hook\.(ActorRadiusHook|NoActionMoveHook|YMove|StatusCheck)$'
|
||||
}
|
||||
$icTypes = foreach ($icType in $icSelected) {
|
||||
$icMethods = foreach ($icMethod in $icType.Methods) {
|
||||
$icState = 'NoBody'
|
||||
$icError = $null
|
||||
$icCodeSize = $null
|
||||
$icInstructions = $null
|
||||
if ($icMethod.HasBody) {
|
||||
try {
|
||||
$icBody = $icMethod.get_Body()
|
||||
$icCodeSize = $icBody.CodeSize
|
||||
$icInstructions = @($icBody.Instructions | ForEach-Object { $_.ToString() })
|
||||
# Parsing success alone is not validation of a protected method body.
|
||||
$icState = 'ParsedUnvalidated'
|
||||
$icMissingOperands = @($icBody.Instructions | Where-Object {
|
||||
$_.OpCode.OperandType.ToString() -ne 'InlineNone' -and $null -eq $_.Operand
|
||||
})
|
||||
if ($icCodeSize -gt 1048576 -or $icInstructions.Count -eq 0 -or
|
||||
$icBody.Instructions[0].OpCode.Name -eq 'ldelem.i1' -or $icMissingOperands.Count -gt 0) {
|
||||
$icState = 'SuspiciousBody'
|
||||
$icInstructions = $null
|
||||
}
|
||||
} catch {
|
||||
$icState = 'ReadError'
|
||||
$icError = $_.Exception.GetBaseException().Message
|
||||
}
|
||||
}
|
||||
[ordered]@{
|
||||
Signature = $icMethod.FullName
|
||||
Token = $icMethod.MetadataToken.ToUInt32()
|
||||
RVA = ('0x{0:X}' -f $icMethod.RVA)
|
||||
BodyState = $icState
|
||||
DeclaredCodeSize = $icCodeSize
|
||||
Error = $icError
|
||||
UnvalidatedIL = $icInstructions
|
||||
}
|
||||
}
|
||||
[ordered]@{
|
||||
Name = $icType.FullName
|
||||
Attributes = $icType.Attributes.ToString()
|
||||
DeclaredSize = $icType.ClassSize
|
||||
PackingSize = $icType.PackingSize
|
||||
Fields = @($icType.Fields | ForEach-Object {
|
||||
[ordered]@{Name=$_.Name; Type=$_.FieldType.FullName; Offset=$_.Offset}
|
||||
})
|
||||
NestedTypes = @($icType.NestedTypes | ForEach-Object {
|
||||
[ordered]@{
|
||||
Name=$_.FullName
|
||||
DeclaredSize=$_.ClassSize
|
||||
DelegateInvoke=@($_.Methods | Where-Object Name -eq 'Invoke' | ForEach-Object { $_.FullName })
|
||||
}
|
||||
})
|
||||
Methods = @($icMethods)
|
||||
}
|
||||
}
|
||||
$icReport = [ordered]@{
|
||||
Source = $icInput
|
||||
SHA256 = (Get-FileHash -LiteralPath $icInput -Algorithm SHA256).Hash
|
||||
Assembly = $icAssembly.Name.FullName
|
||||
Runtime = $icAssembly.MainModule.RuntimeVersion
|
||||
References = @($icAssembly.MainModule.AssemblyReferences | ForEach-Object {
|
||||
[ordered]@{Name=$_.Name; Version=$_.Version.ToString()}
|
||||
})
|
||||
Types = @($icTypes)
|
||||
Limitation = 'Metadata only. Method-body parsing is not proof of executable logic. Target assembly was not executed.'
|
||||
}
|
||||
$icReport | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $OutputPath -Encoding utf8
|
||||
"Saved metadata audit: $OutputPath"
|
||||
} finally { $icAssembly.Dispose() }
|
||||
@@ -0,0 +1,73 @@
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$FirstPath,
|
||||
[Parameter(Mandatory)][string]$SecondPath,
|
||||
[Parameter(Mandatory)][string]$OutputPath,
|
||||
[string]$CecilPath = (Join-Path $env:APPDATA 'XIVLauncher\addon\Hooks\dev\Mono.Cecil.dll')
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Add-Type -Path $CecilPath
|
||||
function Get-IcTypes($Types) {
|
||||
foreach ($icType in $Types) {
|
||||
$icType
|
||||
Get-IcTypes $icType.NestedTypes
|
||||
}
|
||||
}
|
||||
function Get-IcMetadata([string]$Path) {
|
||||
$icFile = Get-Item -LiteralPath $Path
|
||||
$icAsm = [Mono.Cecil.AssemblyDefinition]::ReadAssembly($icFile.FullName)
|
||||
try {
|
||||
$icAllTypes = @(Get-IcTypes $icAsm.MainModule.Types)
|
||||
$icNamedTypes = @($icAllTypes | Where-Object { $_.FullName -match '^[\x20-\x7E]+$' })
|
||||
$icShapes = [ordered]@{}
|
||||
foreach ($icType in $icNamedTypes) {
|
||||
$icShapes[$icType.FullName] = @(
|
||||
"TYPE $($icType.Attributes) SIZE $($icType.ClassSize) PACK $($icType.PackingSize) BASE $($icType.BaseType.FullName)"
|
||||
$icType.Interfaces | ForEach-Object { "INTERFACE $($_.InterfaceType.FullName)" }
|
||||
$icType.Fields | ForEach-Object { "FIELD $($_.FullName) OFFSET $($_.Offset) FLAGS $($_.Attributes)" }
|
||||
$icType.Methods | ForEach-Object { "METHOD $($_.FullName) FLAGS $($_.Attributes) IMPL $($_.ImplAttributes)" }
|
||||
) | Sort-Object
|
||||
}
|
||||
[ordered]@{
|
||||
File=$icFile.Name
|
||||
Size=$icFile.Length
|
||||
SHA256=(Get-FileHash -LiteralPath $icFile.FullName).Hash
|
||||
Assembly=$icAsm.Name.FullName
|
||||
MVID=$icAsm.MainModule.Mvid.ToString()
|
||||
Architecture=$icAsm.MainModule.Architecture.ToString()
|
||||
Attributes=@($icAsm.CustomAttributes | Where-Object {
|
||||
$_.AttributeType.Name -match '^TargetFrameworkAttribute$|^Assembly(File|Informational)VersionAttribute$'
|
||||
} | ForEach-Object {
|
||||
[ordered]@{Type=$_.AttributeType.FullName; Values=@($_.ConstructorArguments | ForEach-Object { [string]$_.Value })}
|
||||
})
|
||||
References=@($icAsm.MainModule.AssemblyReferences | ForEach-Object { "$($_.Name) $($_.Version)" })
|
||||
Resources=@($icAsm.MainModule.Resources | ForEach-Object { "$($_.ResourceType) $($_.Name)" })
|
||||
TopLevelTypeCount=$icAsm.MainModule.Types.Count
|
||||
TotalTypeCount=$icAllTypes.Count
|
||||
AsciiNamedTypeCount=$icNamedTypes.Count
|
||||
TypeShapes=$icShapes
|
||||
}
|
||||
} finally { $icAsm.Dispose() }
|
||||
}
|
||||
$icFirst = Get-IcMetadata $FirstPath
|
||||
$icSecond = Get-IcMetadata $SecondPath
|
||||
$icOnlyFirst = @($icFirst.TypeShapes.Keys | Where-Object { !$icSecond.TypeShapes.Contains($_) })
|
||||
$icOnlySecond = @($icSecond.TypeShapes.Keys | Where-Object { !$icFirst.TypeShapes.Contains($_) })
|
||||
$icChanges = foreach ($icName in $icFirst.TypeShapes.Keys) {
|
||||
if (!$icSecond.TypeShapes.Contains($icName)) { continue }
|
||||
$icDiff = @(Compare-Object $icFirst.TypeShapes[$icName] $icSecond.TypeShapes[$icName])
|
||||
if ($icDiff.Count -gt 0) {
|
||||
[ordered]@{Type=$icName; Differences=@($icDiff | ForEach-Object {
|
||||
[ordered]@{OnlyIn=if ($_.SideIndicator -eq '=>') {'Second'} else {'First'}; Declaration=$_.InputObject}
|
||||
})}
|
||||
}
|
||||
}
|
||||
$icOutput = [ordered]@{
|
||||
First=$icFirst
|
||||
Second=$icSecond
|
||||
AsciiTypesOnlyInFirst=$icOnlyFirst
|
||||
AsciiTypesOnlyInSecond=$icOnlySecond
|
||||
CommonTypeMetadataChanges=@($icChanges)
|
||||
Limitation='Static declarations only; equal metadata does not establish equal method bodies or behavior. No target assembly was executed.'
|
||||
}
|
||||
$icOutput | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $OutputPath -Encoding utf8
|
||||
"Saved comparison: $OutputPath"
|
||||
@@ -0,0 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net10.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable></PropertyGroup>
|
||||
<ItemGroup><Reference Include="Iced"><HintPath>$(APPDATA)\XIVLauncher\addon\Hooks\dev\Iced.dll</HintPath></Reference></ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,125 @@
|
||||
using System.Reflection.PortableExecutable;
|
||||
using Iced.Intel;
|
||||
using System.Buffers.Binary;
|
||||
|
||||
// Read-only on-disk signature audit. Does not open a process or modify the executable.
|
||||
if(args.Length<2) throw new ArgumentException("NativeAudit executable signature|rva:HEX [instruction-count]");
|
||||
using var input=File.OpenRead(args[0]);
|
||||
using var pe=new PEReader(input);
|
||||
var section=pe.PEHeaders.SectionHeaders.Single(x=>x.Name==".text");
|
||||
var bytes=pe.GetSectionData(section.VirtualAddress).GetContent().ToArray();
|
||||
if(args[1].StartsWith("xrefstr:") || args[1].StartsWith("xrefs:"))
|
||||
{
|
||||
int target;
|
||||
if(args[1].StartsWith("xrefstr:"))
|
||||
{
|
||||
var image=File.ReadAllBytes(args[0]);
|
||||
var offset=image.AsSpan().IndexOf(System.Text.Encoding.UTF8.GetBytes(args[1][8..]+"\0"));
|
||||
if(offset<0) throw new InvalidDataException("String not found");
|
||||
var dataSection=pe.PEHeaders.SectionHeaders.Single(s=>offset>=s.PointerToRawData && offset<s.PointerToRawData+s.SizeOfRawData);
|
||||
target=offset-dataSection.PointerToRawData+dataSection.VirtualAddress;
|
||||
}
|
||||
else target=Convert.ToInt32(args[1][6..].Split('-')[0],16);
|
||||
var endTarget=args[1].StartsWith("xrefs:") && args[1].Contains('-') ? Convert.ToInt32(args[1].Split('-')[1],16) : target;
|
||||
var directory=pe.PEHeaders.PEHeader!.ExceptionTableDirectory;
|
||||
var functions=pe.GetSectionData(directory.RelativeVirtualAddress).GetContent(0,directory.Size).ToArray();
|
||||
Console.WriteLine($"XREF TARGET RVA=0x{target:X}");
|
||||
bool IsStringPointer(ulong pointer)
|
||||
{
|
||||
if(pointer<pe.PEHeaders.PEHeader!.ImageBase) return false;
|
||||
var rva=(long)(pointer-pe.PEHeaders.PEHeader.ImageBase);
|
||||
var data=pe.PEHeaders.SectionHeaders.FirstOrDefault(s=>rva>=s.VirtualAddress && rva<s.VirtualAddress+s.SizeOfRawData);
|
||||
if(data.SizeOfRawData==0) return false;
|
||||
var content=pe.GetSectionData((int)rva).GetContent(0,Math.Min(128,data.VirtualAddress+data.SizeOfRawData-(int)rva)).ToArray();
|
||||
var end=Array.IndexOf(content,(byte)0);
|
||||
return end>=3 && content.Take(end).All(b=>b>=32 && b<127);
|
||||
}
|
||||
foreach(var dataSection in pe.PEHeaders.SectionHeaders.Where(s=>s.Name!=".text" && s.SizeOfRawData>0))
|
||||
{
|
||||
var raw=pe.GetSectionData(dataSection.VirtualAddress).GetContent().ToArray();
|
||||
for(int at=0;at+8<=raw.Length;at+=8)
|
||||
if(BinaryPrimitives.ReadUInt64LittleEndian(raw.AsSpan(at))==pe.PEHeaders.PEHeader!.ImageBase+(uint)target)
|
||||
{
|
||||
Console.WriteLine($"DATA REF RVA=0x{dataSection.VirtualAddress+at:X}");
|
||||
var before=at;
|
||||
while(before>=8 && at-before<8192 && IsStringPointer(BinaryPrimitives.ReadUInt64LittleEndian(raw.AsSpan(before-8)))) before-=8;
|
||||
Console.WriteLine($"STRING TABLE candidate RVA=0x{dataSection.VirtualAddress+before:X}, preceding={(at-before)/8}");
|
||||
}
|
||||
}
|
||||
for(int i=0;i+7<=bytes.Length;i++)
|
||||
{
|
||||
var callTarget=section.VirtualAddress+i+5+BinaryPrimitives.ReadInt32LittleEndian(bytes.AsSpan(i+1));
|
||||
var ripTarget=section.VirtualAddress+i+7+BinaryPrimitives.ReadInt32LittleEndian(bytes.AsSpan(i+3));
|
||||
bool call=(bytes[i]==0xE8 || bytes[i]==0xE9) && callTarget>=target && callTarget<=endTarget;
|
||||
bool rip=(bytes[i] is 0x48 or 0x4C) && bytes[i+1] is 0x8D or 0x8B && (bytes[i+2]&0xC7)==5 && ripTarget>=target && ripTarget<=endTarget;
|
||||
var rawTarget=BinaryPrimitives.ReadInt32LittleEndian(bytes.AsSpan(i));
|
||||
bool immediate=rawTarget>=target && rawTarget<=endTarget;
|
||||
if(!call && !rip && !immediate) continue;
|
||||
var rva=section.VirtualAddress+i;
|
||||
bool described=false;
|
||||
for(int at=0;at+12<=functions.Length;at+=12)
|
||||
{
|
||||
int begin=BinaryPrimitives.ReadInt32LittleEndian(functions.AsSpan(at)),end=BinaryPrimitives.ReadInt32LittleEndian(functions.AsSpan(at+4));
|
||||
if(rva>=begin && rva<end) { Console.WriteLine($"REF RVA=0x{rva:X}, FUNCTION=0x{begin:X}..0x{end:X}, {(call ? "CALL/JMP" : rip ? "RIP" : "IMM/CANDIDATE")} target=0x{(call ? callTarget : rip ? ripTarget : rawTarget):X}"); described=true; break; }
|
||||
}
|
||||
if(!described) Console.WriteLine($"REF RVA=0x{rva:X}, FUNCTION=leaf/unknown, {(call ? "CALL/JMP" : rip ? "RIP" : "IMM/CANDIDATE")} target=0x{(call ? callTarget : rip ? ripTarget : rawTarget):X}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
var containing=args[1].StartsWith("func:",StringComparison.OrdinalIgnoreCase);
|
||||
var direct=containing || args[1].StartsWith("rva:",StringComparison.OrdinalIgnoreCase);
|
||||
var requested=direct ? Convert.ToInt32(args[1][(containing ? 5 : 4)..],16) : 0;
|
||||
if(containing)
|
||||
{
|
||||
var directory=pe.PEHeaders.PEHeader!.ExceptionTableDirectory;
|
||||
var functions=pe.GetSectionData(directory.RelativeVirtualAddress).GetContent(0,directory.Size).ToArray();
|
||||
bool found=false;
|
||||
for(int at=0;at+12<=functions.Length;at+=12)
|
||||
{
|
||||
int begin=BinaryPrimitives.ReadInt32LittleEndian(functions.AsSpan(at)),end=BinaryPrimitives.ReadInt32LittleEndian(functions.AsSpan(at+4));
|
||||
if(requested>=begin && requested<end)
|
||||
{
|
||||
Console.WriteLine($"CONTAINING RVA=0x{begin:X}..0x{end:X}");
|
||||
var unwind=BinaryPrimitives.ReadInt32LittleEndian(functions.AsSpan(at+8));
|
||||
for(int depth=0;depth<16;depth++)
|
||||
{
|
||||
var header=pe.GetSectionData(unwind).GetContent(0,4).ToArray();
|
||||
if(((header[0]>>3)&4)==0) break;
|
||||
var chainAt=(4+header[2]*2+3)&~3;
|
||||
var chain=pe.GetSectionData(unwind+chainAt).GetContent(0,12).ToArray();
|
||||
begin=BinaryPrimitives.ReadInt32LittleEndian(chain); end=BinaryPrimitives.ReadInt32LittleEndian(chain.AsSpan(4));
|
||||
unwind=BinaryPrimitives.ReadInt32LittleEndian(chain.AsSpan(8));
|
||||
Console.WriteLine($"CHAINED ROOT RVA=0x{begin:X}..0x{end:X}");
|
||||
}
|
||||
requested=begin;found=true;break;
|
||||
}
|
||||
}
|
||||
if(!found) throw new InvalidDataException("No unwind entry for requested RVA");
|
||||
}
|
||||
var start=direct ? requested-section.VirtualAddress : 0;
|
||||
if(start<0 || start>=bytes.Length) throw new ArgumentException("RVA outside .text");
|
||||
var pattern=direct ? "" : args[1].Replace(" ","");
|
||||
if(pattern.Length%2!=0) throw new ArgumentException("Odd signature length");
|
||||
var values=Enumerable.Range(0,pattern.Length/2).Select(i=>pattern.Substring(i*2,2)).ToArray();
|
||||
var count=args.Length>2 ? int.Parse(args[2]) : 100;
|
||||
int matches=0;
|
||||
for(int i=start;i<=(direct ? start : bytes.Length-values.Length);i++)
|
||||
{
|
||||
bool match=true;
|
||||
for(int j=0;j<values.Length;j++)
|
||||
if(values[j]!="??" && bytes[i+j]!=Convert.ToByte(values[j],16)) { match=false; break; }
|
||||
if(!match) continue;
|
||||
matches++;
|
||||
var decoder=Decoder.Create(64,new ByteArrayCodeReader(bytes,i,Math.Min(8192,bytes.Length-i)));
|
||||
decoder.IP=pe.PEHeaders.PEHeader!.ImageBase+(uint)section.VirtualAddress+(uint)i;
|
||||
Console.WriteLine($"MATCH RVA=0x{section.VirtualAddress+i:X}");
|
||||
Console.WriteLine("PREFIX "+Convert.ToHexString(bytes.AsSpan(i,Math.Min(64,bytes.Length-i))));
|
||||
var formatter=new IntelFormatter(); var output=new StringOutput();
|
||||
for(int n=0;n<count;n++)
|
||||
{
|
||||
decoder.Decode(out var instruction); formatter.Format(instruction,output);
|
||||
Console.WriteLine($"{instruction.IP:X16} {output.ToStringAndReset()}");
|
||||
if(instruction.FlowControl==FlowControl.Return) break;
|
||||
}
|
||||
}
|
||||
Console.WriteLine($"Matches: {matches}");
|
||||
@@ -0,0 +1,38 @@
|
||||
using Lumina;
|
||||
using ICRebuild.Core;
|
||||
using System.Text.Json;
|
||||
|
||||
if(args.Length!=2) throw new ArgumentException("StatusAudit sqpack-directory output-json");
|
||||
using var game=new GameData(args[0]);
|
||||
var sheet=game.GetExcelSheet<Lumina.Excel.Sheets.Status>() ?? throw new InvalidDataException("Status sheet unavailable");
|
||||
var result=new List<object>();
|
||||
var root=game.GetFile<Lumina.Data.Files.Excel.ExcelListFile>("exd/root.exl");
|
||||
if(root!=null)
|
||||
foreach(var entry in root.ExdMap.Where(x=>x.Value==0x14A)) Console.WriteLine($"Native movement-check sheet 0x14A = {entry.Key}");
|
||||
foreach(var (group,id) in StatusCatalog.Entries())
|
||||
{
|
||||
var row=sheet.GetRowOrDefault(id);
|
||||
if(row==null) { Console.WriteLine($"MISSING {group} {id}"); result.Add(new{Group=group,Id=id,Missing=true}); continue; }
|
||||
var s=row.Value;
|
||||
Console.WriteLine($"{group} {id} {s.Name}: movement={s.LockMovement} control={s.LockControl} actions={s.LockActions}");
|
||||
result.Add(new {Group=group,Id=id,Name=s.Name.ToString(),Description=s.Description.ToString(),s.LockMovement,s.LockControl,s.LockActions,s.Flags,s.Flag2,s.ParamEffect,s.ParamModifier});
|
||||
}
|
||||
foreach(var s in sheet)
|
||||
{
|
||||
var name=s.Name.ToString();
|
||||
if((name=="Heavy" || name=="Leaden") && !StatusCatalog.SlowCandidates.Contains(s.RowId))
|
||||
{
|
||||
Console.WriteLine($"UNLISTED_SLOW {s.RowId} {name}: {s.Description}");
|
||||
result.Add(new{Group="UnlistedSlowNameSearch",Id=s.RowId,Name=name,Description=s.Description.ToString()});
|
||||
}
|
||||
if(!new[]{"charm","fear","hysteria","seduc","terror"}.Any(x=>name.Contains(x,StringComparison.OrdinalIgnoreCase))) continue;
|
||||
Console.WriteLine($"CONTROL {s.RowId} {name}: movement={s.LockMovement} control={s.LockControl} actions={s.LockActions}");
|
||||
result.Add(new {Group="ControlNameSearch",Id=s.RowId,Name=name,Description=s.Description.ToString(),s.LockMovement,s.LockControl,s.LockActions,s.Flags,s.Flag2,s.ParamEffect,s.ParamModifier});
|
||||
}
|
||||
var territories=game.GetExcelSheet<Lumina.Excel.Sheets.TerritoryType>();
|
||||
foreach(uint id in new uint[]{1108,1290})
|
||||
{
|
||||
var territory=territories?.GetRowOrDefault(id);
|
||||
if(territory.HasValue) Console.WriteLine($"TERRITORY {id}: use={territory.Value.TerritoryIntendedUse.RowId}, name={territory.Value.PlaceName.Value.Name}");
|
||||
}
|
||||
File.WriteAllText(args[1],JsonSerializer.Serialize(result,new JsonSerializerOptions{WriteIndented=true}));
|
||||
@@ -0,0 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net10.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable></PropertyGroup>
|
||||
<ItemGroup><Reference Include="Lumina;Lumina.Excel"><HintPath>$(APPDATA)\XIVLauncher\addon\Hooks\dev\%(Identity).dll</HintPath></Reference><Compile Include="../../Core/StatusCatalog.cs" Link="StatusCatalog.cs" /></ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user