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"