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() }