@echo off
rem ===========================================================================
rem  MEGA MAN + GOLD WAR MACHINE -- Marvel vs. Capcom (CPS-2, mvsc)
rem
rem  DRAG mvsc.zip ONTO THIS FILE
rem       or run:  MVSC-MEGAMAN-GWM-COMBINED-v1.cmd "C:\path\mvsc.zip"
rem
rem  It writes a NEW zip beside the input.  The original is never modified.
rem  Nothing to install: Windows PowerShell ships with Windows.
rem
rem  This installer contains patch code and checksums only.  It does not
rem  contain or download Capcom ROM data.  Supply your own clean Euro parent
rem  mvsc.zip (the Fightcade-style set containing mvce.03a and mvce.04a).
rem ===========================================================================

setlocal
set "ROMPATH=%~1"
set "SELFPATH=%~f0"

where powershell.exe >nul 2>&1
if errorlevel 1 (
  echo.
  echo   ERROR: Windows PowerShell was not found.
  echo   It normally ships with Windows. Try:
  echo   %%SystemRoot%%\System32\WindowsPowerShell\v1.0\powershell.exe
  echo.
  pause
  exit /b 1
)

rem The marker is split here so this launcher cannot match its own command.
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "$t=[IO.File]::ReadAllText($env:SELFPATH);$m='#=PS'+'BEGIN=#';$i=$t.IndexOf($m);if($i -lt 0){Write-Host '  ERROR: installer marker missing; re-download this file.' -ForegroundColor Red;exit 9};& ([ScriptBlock]::Create($t.Substring($i+$m.Length)))"
set "RC=%errorlevel%"
endlocal & exit /b %RC%

#=PSBEGIN=#
$ErrorActionPreference = 'Stop'

function Pause-IfConsole {
    if ($Host.Name -eq 'ConsoleHost' -and -not $env:MVSCBOTH_NOPAUSE) {
        try { Read-Host '  Press Enter to close' | Out-Null } catch {}
    }
}

function Fail([string]$Message, [int]$Code = 1) {
    Write-Host ''
    Write-Host "  ERROR: $Message" -ForegroundColor Red
    Write-Host ''
    Pause-IfConsole
    exit $Code
}

function ConvertFrom-Hex([string]$Hex) {
    if (($Hex.Length % 2) -ne 0) { Fail 'internal hex string has odd length.' 8 }
    $Bytes = New-Object byte[] ($Hex.Length / 2)
    for ($i = 0; $i -lt $Bytes.Length; $i++) {
        $Bytes[$i] = [Convert]::ToByte($Hex.Substring($i * 2, 2), 16)
    }
    return ,$Bytes
}

function Get-Sha256([byte[]]$Bytes) {
    $Hash = [System.Security.Cryptography.SHA256]::Create()
    try {
        return (([BitConverter]::ToString($Hash.ComputeHash($Bytes))).Replace('-', '').ToLowerInvariant())
    } finally {
        $Hash.Dispose()
    }
}

function Test-Bytes([byte[]]$Buffer, [int]$Offset, [byte[]]$Expected) {
    if ($Offset -lt 0 -or $Offset + $Expected.Length -gt $Buffer.Length) { return $false }
    for ($i = 0; $i -lt $Expected.Length; $i++) {
        if ($Buffer[$Offset + $i] -ne $Expected[$i]) { return $false }
    }
    return $true
}

function Read-ZipMembers([string]$Path) {
    $Entries = New-Object 'System.Collections.Generic.Dictionary[string,byte[]]'
    $Metadata = New-Object 'System.Collections.Generic.Dictionary[string,object]'
    $Order = New-Object 'System.Collections.Generic.List[string]'
    try { $Zip = [System.IO.Compression.ZipFile]::OpenRead($Path) }
    catch { throw "not a readable ZIP archive: $Path. Give it mvsc.zip, not a directory or .7z file." }
    try {
        foreach ($Entry in $Zip.Entries) {
            if ($Entries.ContainsKey($Entry.FullName)) {
                throw "archive has a duplicate member named $($Entry.FullName)."
            }
            $Memory = New-Object System.IO.MemoryStream
            $Stream = $Entry.Open()
            try { $Stream.CopyTo($Memory) } finally { $Stream.Dispose() }
            $Entries.Add($Entry.FullName, $Memory.ToArray())
            $Metadata.Add($Entry.FullName, [pscustomobject]@{
                LastWriteTime = $Entry.LastWriteTime
                ExternalAttributes = $Entry.ExternalAttributes
            })
            [void]$Order.Add($Entry.FullName)
            $Memory.Dispose()
        }
    } finally {
        $Zip.Dispose()
    }
    return [pscustomobject]@{ Entries=$Entries; Metadata=$Metadata; Order=$Order }
}

Write-Host ''
Write-Host '  MEGA MAN MAGNETIC SHOCKWAVE + GOLD WAR MACHINE MEGA OPTIC -- MvC'
Write-Host '  ======================================================================='
Write-Host '  Mega Man: ground QCB + any two punches; costs one level.'
Write-Host '  Gold War Machine: air QCF + any two punches; normal/super jump; one level.'
Write-Host '  A final-team KO shows MAGNETIC / SHOCKWAVE through the stock title renderer.'

$Source = $env:ROMPATH
if ([string]::IsNullOrWhiteSpace($Source)) {
    $Candidates = @(
        (Join-Path ([Environment]::CurrentDirectory) 'mvsc.zip'),
        (Join-Path (Split-Path -Parent $env:SELFPATH) 'mvsc.zip')
    ) | Select-Object -Unique
    $Source = $Candidates | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1
}
if ([string]::IsNullOrWhiteSpace($Source)) {
    Write-Host ''
    Write-Host '  Drag your clean mvsc.zip onto this file, or run:'
    Write-Host '    .\MVSC-MEGAMAN-GWM-COMBINED-v1.cmd "C:\path\mvsc.zip"'
    Fail 'no ROM archive was supplied and mvsc.zip was not found here.'
}
try { $Source = (Resolve-Path -LiteralPath $Source -ErrorAction Stop).ProviderPath }
catch { Fail "file not found: $Source" }
if (-not (Test-Path -LiteralPath $Source -PathType Leaf)) { Fail "not a file: $Source" }

Add-Type -AssemblyName System.IO.Compression -ErrorAction SilentlyContinue
Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction SilentlyContinue

$Vanilla = @(
    @{ Name='mvc.01'; Size=131072; Sha256='565f2c1befc6dbcd727b65c8954d3263b1e8105cd16be3ff7399c0ff513d144e' }
    @{ Name='mvc.02'; Size=131072; Sha256='b460ea0d95c967e5276d0f1f6e0a68d3bcc6008ce7952b970069a211e460ec63' }
    @{ Name='mvc.05a'; Size=524288; Sha256='1dfd50972c8796624051857e16ddc2296932c39fb05ecfcd1bae6180a369285d' }
    @{ Name='mvc.06a'; Size=524288; Sha256='132316e99d02d197cd8b4b9227dccd9215f5f741bcd21d5abf5c3f07fb64354b' }
    @{ Name='mvc.07'; Size=524288; Sha256='8b29a492800ec41c9818f1cf880697b298af69c75d0dd6864ed06e65223930eb' }
    @{ Name='mvc.08'; Size=524288; Sha256='402a848e3860f20cdcf3ec80c640f27eaef36f4e7cd78a330913baedcd5356d8' }
    @{ Name='mvc.09'; Size=524288; Sha256='fd0e680e0e1b14a5d1e913ddaef37076dd80203326e305b1fe1f8070be0cd6b2' }
    @{ Name='mvc.10'; Size=524288; Sha256='e24119140931bc861bf791758af8eb375892260d8ffb633369ebedcbe21f172a' }
    @{ Name='mvc.11m'; Size=4194304; Sha256='5587602fb97953d0cd951392b3bbaef2d317ae943ff2f6608c46cb627b7b16ca' }
    @{ Name='mvc.12m'; Size=4194304; Sha256='971b949ca3ad2471bf5ddc5c87c10286eacba51c30b4cc2261328ec46732b920' }
    @{ Name='mvc.13m'; Size=4194304; Sha256='689b9f2e2f9105f6b6a456e836a289c65caf642d9c7d246ea95c2db256f2ce0d' }
    @{ Name='mvc.14m'; Size=4194304; Sha256='1be6ba60b3cc0e6b647c2a54ca06b948dbc72eb32efdead84bcaa1d2efd69db5' }
    @{ Name='mvc.15m'; Size=4194304; Sha256='37365288e38a8c41fb5d2eb72808038478def8c8aa368d85816b70a89f6a6f55' }
    @{ Name='mvc.16m'; Size=4194304; Sha256='c94231770a7377bf6c7817427f28b87db79f3be03287279e8a19bea34cca85be' }
    @{ Name='mvc.17m'; Size=4194304; Sha256='dc29f2f0bf56d60408e9b9a2fcfae6c684f8be97712c94f146386c9bbb9ab46f' }
    @{ Name='mvc.18m'; Size=4194304; Sha256='4f3ceefa11958fd92f88047f876e37a5a9a226ca9c28e3b9915a03f39a268947' }
    @{ Name='mvc.19m'; Size=4194304; Sha256='d245405b8fa57b315300d915b9b5f96859bc31b9a97f653e370c7f0f28a1fc7f' }
    @{ Name='mvc.20m'; Size=4194304; Sha256='6cb08b23e33cc97bd14c6bf0ed179c99952146328f0dc0f01e8135c2783466bd' }
    @{ Name='mvce.03a'; Size=524288; Sha256='48128b6f98c5171a0e734e99f01a273f526f2da74f08635732068c60c891d757' }
    @{ Name='mvce.04a'; Size=524288; Sha256='71ded7040f279d0ddea1326fc062465159ccd832a327230098e5740d2a747d67' }
    @{ Name='mvsc.key'; Size=20; Sha256='9ba914d01fdb50c562fef4d4a5c7ff4779f92340322272696a46025d33f7155c' }
)
$VanillaLookup = New-Object 'System.Collections.Generic.Dictionary[string,object]'
foreach ($Record in $Vanilla) { $VanillaLookup.Add([string]$Record.Name, $Record) }

try { $Read = Read-ZipMembers $Source }
catch { Fail $_.Exception.Message }
$Entries = $Read.Entries
$Metadata = $Read.Metadata
$Order = $Read.Order

if ($Entries.Count -ne $Vanilla.Count) {
    Fail "wrong archive member count: got $($Entries.Count), expected $($Vanilla.Count).`n         This patch accepts only the clean Euro parent mvsc.zip."
}
for ($i = 0; $i -lt $Vanilla.Count; $i++) {
    if ($Order[$i] -cne [string]$Vanilla[$i].Name) {
        Fail ("archive member order mismatch at index {0}: got '{1}', expected '{2}'.`n         Repacking is allowed only when all 21 member names, order, and bytes remain exact." -f $i,$Order[$i],$Vanilla[$i].Name)
    }
}
for ($i = 0; $i -lt $Vanilla.Count; $i++) {
    if ($Order[$i] -cne [string]$Vanilla[$i].Name) {
        Fail ("archive member order mismatch at index {0}: got '{1}', expected '{2}'.`n         Repacking is allowed only when all 21 member names, order, and bytes remain exact." -f $i,$Order[$i],$Vanilla[$i].Name)
    }
}
foreach ($Name in $Order) {
    if (-not $VanillaLookup.ContainsKey($Name)) {
        Fail "unexpected archive member '$Name'. This is not the exact Euro parent set."
    }
}
foreach ($Record in $Vanilla) {
    $Name = [string]$Record.Name
    if (-not $Entries.ContainsKey($Name)) { Fail "required archive member '$Name' is missing." }
    $Bytes = [byte[]]$Entries[$Name]
    if ($Bytes.Length -ne [int]$Record.Size) {
        Fail "$Name size is $($Bytes.Length), expected $($Record.Size)."
    }
    $Got = Get-Sha256 $Bytes
    if ($Got -ne [string]$Record.Sha256) {
        Fail "$Name SHA-256 mismatch.`n         expected $($Record.Sha256)`n         got      $Got`n         Start from an unmodified Euro parent mvsc.zip."
    }
}

Write-Host ''
Write-Host "  Input : $Source"
Write-Host '  Set   : mvsc Euro 980123 encrypted parent -- all 21 members SHA-256 verified'

$Patches = @(
    @{ File='mvce.03a'; Offset=0x00A4C6; Before='ab7daecec156ef83'; After='ce7ab586a5031ece'; Purpose='Gold: scoped common-palette reload/repaint gate' }
    @{ File='mvce.03a'; Offset=0x01240C; Before='8556cda558da31f5'; After='552243378da2e4d5'; Purpose='Mega: exact-row stock final-team Hyper-title eligibility gate' }
    @{ File='mvce.03a'; Offset=0x01CD1E; Before='b7e6ba813793ed6b'; After='59a8a5a973b6c4af'; Purpose='shared Mega/Gold attacker-meter dispatcher' }
    @{ File='mvce.03a'; Offset=0x02896A; Before='140985fcc3c6'; After='9ac65ab03140'; Purpose='Gold: Gold War Machine airborne QCF+PP recognizer (normal/super jump)' }
    @{ File='mvce.03a'; Offset=0x02A41E; Before='ff7be8697134'; After='6a8530b84426'; Purpose='Gold: Gold/War Machine special dispatcher interpose' }
    @{ File='mvce.03a'; Offset=0x0610C4; Before='c36825e7add9'; After='b1ca59e65ca0'; Purpose='Mega: ground command hook' }
    @{ File='mvce.03a'; Offset=0x062D04; Before='925c9362484a'; After='9623014d8b83'; Purpose='Mega: Mega Man dispatcher hook' }
    @{ File='mvce.04a'; Offset=0x016044; Before='f6d4debdb3a893ff'; After='ddf2452db7578840'; Purpose='Mega: Magnetic Shockwave palette-lifetime hook' }
    @{ File='mvce.04a'; Offset=0x0162C0; Before='767552890694'; After='7675b5c15945'; Purpose='Mega: Magnetic Shockwave final-object palette restore' }
    @{ File='mvce.04a'; Offset=0x0254F4; Before='2fd01a6efe19'; After='f2f6e06b1939'; Purpose='Gold: class-$B4 native/custom owner gate and cleanup' }
    @{ File='mvce.04a'; Offset=0x02558E; Before='b8d5aefd1994'; After='2a50f3cb355e'; Purpose='Gold: class-$B4 subtype-0 Onslaught identity tail' }
    @{ File='mvce.04a'; Offset=0x0255D0; Before='77ce50ea9ea5'; After='aeea45fd79d9'; Purpose='Gold: class-$B4 subtype-0 forward/vertical calibration' }
    @{ File='mvce.04a'; Offset=0x0257C4; Before='f4439fcdfdc7'; After='58a8e06c86fa'; Purpose='Gold: class-$B4 subtype-2 Onslaught identity tail' }
    @{ File='mvce.04a'; Offset=0x02593C; Before='b7bb8047b87f'; After='ac2b79b21d78'; Purpose='Gold: class-$B4 subtype-4 Onslaught identity tail' }
    @{ File='mvce.04a'; Offset=0x025AAC; Before='d651d637798f'; After='e7aaa7738167'; Purpose='Gold: class-$B4 subtype-6 Onslaught identity tail' }
    @{ File='mvce.04a'; Offset=0x025AB8; Before='1aca3265bbf1'; After='4f4cde874687'; Purpose='Gold: class-$B4 subtype-6 forward/vertical calibration' }
    @{ File='mvce.04a'; Offset=0x043C90; Before='ee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6cee6c'; After='ee6cee6cee6cee6cec6a2e6be46a406b086be86a206b026b286bee6cee6cee6cee6cee6cee6cee6ce86a046b2a6bec6a0e6b006be46a0c6bee6cee6cee6cee6c'; Purpose='stock class-$C0 MAGNETIC / SHOCKWAVE glyph record (token $5A)' }
    @{ File='mvce.04a'; Offset=0x07788A; Before='0000'; After='5a00'; Purpose='assign native finish-title token $5A to the exact Magnetic row' }
)
foreach ($Patch in $Patches) {
    $Buffer = [byte[]]$Entries[[string]$Patch.File]
    $Before = ConvertFrom-Hex ([string]$Patch.Before)
    $After = ConvertFrom-Hex ([string]$Patch.After)
    if ($Before.Length -ne $After.Length) { Fail 'internal hook length mismatch.' 8 }
    if (-not (Test-Bytes $Buffer ([int]$Patch.Offset) $Before)) {
        Fail ("{0} stock-byte proof failed at +0x{1:X6}; nothing was written." -f $Patch.File,$Patch.Offset)
    }
    [Array]::Copy($After, 0, $Buffer, [int]$Patch.Offset, $After.Length)
    Write-Host ("  patch : {0} +0x{1:X6} ({2} bytes) -- {3}" -f $Patch.File,$Patch.Offset,$After.Length,$Patch.Purpose)
}

$Payloads = @(
    @{ File='mvc.10'; Offset=0x071000; Cpu='$3F1000'; Bytes=1016; PlainSha='1ab0d5821438356ca3bc248244c6d71153fcda3c464e089038b0db5202c278c2'; DiskSha='8efc777e3313e0f69b33d6117f0d4711e5cabcc9cdd7c34c1ef2a95d0b41048d'; Hex='00615a03004a0c66b94e010050a1f94e0600ca10754efa450801ee47c801b94e3f00ec122a6700702e1002002d0138c00c662e0c0100740204640070754e7c1d04004d007c1d1c00e600f94e0600f0170070754e2e0c1c00e6000c6700702e10e600f94e06000a2d2e3008000c67400c020000676000006098006e540800b94e01003476b94e0000b0a40070402d1400402d1800402d1c00402d20007c3d3c004e007c1d460080006e428400ee50a1032e4a020006666d5226c004606d5228c014700472b94e0100e635b94e0b00205ef84ed81db84ed81d6e534e003c676e0c2c004e0028666e5408002e429a0300616001f870cf72b94e0100fe48b94e0200ee7100610e01fa412800b84efc1d754eb84ed81d6e534e00f466f94e0100ba6f030000101c00000004000500010070000000ff7f3100f6be009000a0000006000000000000000061f601004a0e666e2054002e300400f94e09004c60754e0000000000000000000000000000e748f2c00061400008202c674e2448266d224e5a09201467cab30a67492c00612600cbb1126769222c00e8602d4ab7790e664b2c00610001df4c034ff84e9e45df4c034f754e6e0c2400240054666e0c220052004c662e4a26000e6b2e0c1c0027003e666e2054002a606e20540008203067680c240024002866680c220052002066284a26001a6b280c1c00270012666820540008200a67680c200052000266754ec891754e00726d204e5a08202a67680c240024001c66680c220052001466284a26000e6be8bd540008667c111c002700015268202c00d260014a046600615600754e2e0ca500ce0144676e20c8022e42cf012e4a77020a677c1d0100cf01fcd06000e8414000ee43d0017c241c0044bc0570d022da20c851faffee4300020170d022da20c851faff7c1da500ce01f94e0000b0a42e0ca500ce0140666e20c8022e4acf010467fcd06000e8414000ee43d0010570d920a942fcffc851f8ffee4300020170d920a942fcffc851f8ff2e42ce012e42cf01f94e0000b0a4754eb94e01001097004a3267e74880c000722e12020041d2ed414cc4c1d0103001722e4ab3000267027240c21267400270000032415341c00667df4c0301754edf4c03010070754e0070402dc801403dcc01754e006172fe08201267e7487e7f48264b2cb94e0200de72df4cfe7e0070754e2e3010006eb0c4021c662e10b0001267000c0a000c67000c0c000a666e4a08000466006096fc0061aeff0070754ee748c2802f201000800c0100aecc0867800c010016cd1c664e224c2c00610afec9b11066ac0c0f0072788c000666df4c0143754edf4c01432e4ae8000667f94e010026cdf94e010030cd4020fcb10f0072780e666d0c000a00c00666f94e01002224280805000400f94e01001424'; Purpose='Mega Man Magnetic Shockwave' }
    @{ File='mvc.10'; Offset=0x072000; Cpu='$3F2000'; Bytes=1478; PlainSha='75bc57f2a5cadd1f5d3528c4e9a6fd1850bb8214e962a048353186c330e4dca4'; DiskSha='0efa58a5edae0dd7350b37042e104ca105afb3d3c8588a495b9791ca14dff2b0'; Hex='6e0c28005200086600610e00004a0666f94e0200b68b754e2e4ace012466fa454e00ee47c80100614e04714e146700702e1002002d0138c00c662e0c0100740204640070754eb94e0100889a7c1d04004d007c1d1a00e6006e4206006e1db000b1007c1d2a00b000f94e02002c8c030000a01a00000004000600020070006e0c2800520008662e0c1a00e6000c6700702e10e600f94e020024a46e0c28005200006620002e3008001e67400c020000677a00400c04000067ba00400c060000670e01f94e0100b6776e5408002e42cf010070402d1400402d1800402d1c00402d20002e30c402400420006eb01000046c403d10007c1d0d0080002e4284002e4286002e423c007c3d3c004e002e4a020006666d5226c004606d5228c018700272b94e0100e635f84ed81d0061d400b84ed81d2e4284002e428600ae0800003c000c666e534e002866f94e0100b6776e5408002e4284002e4286003c309eff3c329fff0076b94e020058a47c3da0004e00754e00618c002d4ab77908677c1d0100cf01ec602e4acf0110672e42cf012e0ca500ce01046600619e0200612803714e714e2e4ace011466b94e0a006e5408646e424e00006012000061f8016e4a4e0006676e534e00a8667c3d06000800006130002d4ab77998662e4284002e4286000061aa01004a88660061e6016e4a4e0008676e534e00006678fff94e0100b6770070402d1400402d1800402d1c00402d2000754e6e205400680c220052000a66680c2c02600000678200280ca500ce010467006070002d4ab77900666000680c2800520000663e00280c2a00b00000663400280c1a00e60000662a00680c0400080000662000082fb94e0a000c555f2600618202004a0a6600614c017c3706000800754e082fb94e0a00345b5f260061fe00004aec6600612e01754e7c110100cf01754ef94e0a00345bf94e0a000c5500612600f94e0a009a5500611c00f94e0a00d05700611200f94e0a00485900610800f94e0a00b85a680c280052002666280ca500ce011e667c1d040047007c3d220052007c2d0f00fcd768007c2d3a0000886c00754e682d68006800682d6c006c00754e6e205400680c280052001466280ca500ce010c667c3dafff1c003c30ebff0a607c3d7bff1c003c30c9ff2e4a4b0002674044403d1400f94e0a00e6556e205400680c280052001466280ca500ce010c667c3daeff1c003c30ecff0a607c3d7aff1c003c30caff2e4a4b0002674044403d1400f94e0a00ce5a4e266d224e5a09201867690cb40024000666e9b75400066769222c00e8600170754e0070754e2e0ca500ce010a6700617a007c1da500ce01754e0e2f4b2c04615f2c754e2e0ca500ce010e662e42ce012e42cf01b94e0000b0a4754e2d4ab77940662e0ca500ce013a666e0c2800520032662e0c2a00b0002a662e0c1a00e60022663c3017001122810200f000f09082c1221822810000f000f0c124c851e8ff0861754ef94e0000cea46e208802e84120007c241c00e4bd07701022810200f000f09a82c120c851f2ff6e208c02e84120007c241c00e4bd07701a22810000f000f0c120c851f4ff754e2e0c2a00b0004267b94e01001097004a3267e74880c000722e12020041d2ed414cc4c1d0103002722e4ab3000267017240c21267400270000032415341c00667df4c0301754edf4c03010070754e2e4284002e428600ae0c160060fe34001c642e304e00400203001266b84ed81d2e4284002e428600ae0800003c00754e006098feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff970c0100aecc0867970c010016cd42666e0c280052003a662e0ca500ce0132666c0cb40024002a666c0c0200260008676c0c040026001a66ecbd54001466ac0c0f00d2788c001c67ac0c0f00f2788c0012672e4ae8000667f94e010026cdf94e010030cd754e'; Purpose='Gold War Machine Mega Optic Blast' }
    @{ File='mvc.10'; Offset=0x073000; Cpu='$3F3000'; Bytes=20; PlainSha='ccdb66cdfe85f5cf65c48d15974512617f0e231618bdac04b7209fd10139c6d1'; DiskSha='bb1e9ec07e7454bff4fbe93ef21fe762216a2bcf901c14f8fb330e5f5db5aa09'; Hex='6c0cb40024000667f94e3f008a13f94e3f006025'; Purpose='shared attacker-meter dispatcher' }
)
foreach ($Record in $Payloads) {
    $Payload = ConvertFrom-Hex ([string]$Record.Hex)
    if ($Payload.Length -ne [int]$Record.Bytes) { Fail 'internal payload length mismatch.' 8 }
    if ((Get-Sha256 $Payload) -ne [string]$Record.DiskSha) { Fail 'internal disk-payload SHA-256 mismatch.' 8 }
    $PlainPayload = New-Object byte[] $Payload.Length
    for ($i = 0; $i -lt $Payload.Length; $i += 2) {
        $PlainPayload[$i] = $Payload[$i + 1]
        $PlainPayload[$i + 1] = $Payload[$i]
    }
    if ((Get-Sha256 $PlainPayload) -ne [string]$Record.PlainSha) {
        Fail "embedded payload '$($Record.Purpose)' is not the declared CPS-2 word-swap." 8
    }
    $Body = [byte[]]$Entries[[string]$Record.File]
    $Empty = New-Object byte[] $Payload.Length
    for ($i = 0; $i -lt $Empty.Length; $i++) { $Empty[$i] = 0xFF }
    if (-not (Test-Bytes $Body ([int]$Record.Offset) $Empty)) {
        Fail ("{0} payload window +0x{1:X6} is not untouched stock 0xFF; nothing was written." -f $Record.File,$Record.Offset)
    }
    [Array]::Copy($Payload, 0, $Body, [int]$Record.Offset, $Payload.Length)
    Write-Host ("  patch : {0} +0x{1:X6} ({2} bytes) -- {3} at CPU {4}" -f $Record.File,$Record.Offset,$Payload.Length,$Record.Purpose,$Record.Cpu)
}

$Patched = New-Object 'System.Collections.Generic.Dictionary[string,string]'
$Patched['mvc.01'] = '565f2c1befc6dbcd727b65c8954d3263b1e8105cd16be3ff7399c0ff513d144e'
$Patched['mvc.02'] = 'b460ea0d95c967e5276d0f1f6e0a68d3bcc6008ce7952b970069a211e460ec63'
$Patched['mvc.05a'] = '1dfd50972c8796624051857e16ddc2296932c39fb05ecfcd1bae6180a369285d'
$Patched['mvc.06a'] = '132316e99d02d197cd8b4b9227dccd9215f5f741bcd21d5abf5c3f07fb64354b'
$Patched['mvc.07'] = '8b29a492800ec41c9818f1cf880697b298af69c75d0dd6864ed06e65223930eb'
$Patched['mvc.08'] = '402a848e3860f20cdcf3ec80c640f27eaef36f4e7cd78a330913baedcd5356d8'
$Patched['mvc.09'] = 'fd0e680e0e1b14a5d1e913ddaef37076dd80203326e305b1fe1f8070be0cd6b2'
$Patched['mvc.10'] = '5c48ce749bd56875ecd429ff960735fad042d82e1c2864b6e4f48ecb1480c732'
$Patched['mvc.11m'] = '5587602fb97953d0cd951392b3bbaef2d317ae943ff2f6608c46cb627b7b16ca'
$Patched['mvc.12m'] = '971b949ca3ad2471bf5ddc5c87c10286eacba51c30b4cc2261328ec46732b920'
$Patched['mvc.13m'] = '689b9f2e2f9105f6b6a456e836a289c65caf642d9c7d246ea95c2db256f2ce0d'
$Patched['mvc.14m'] = '1be6ba60b3cc0e6b647c2a54ca06b948dbc72eb32efdead84bcaa1d2efd69db5'
$Patched['mvc.15m'] = '37365288e38a8c41fb5d2eb72808038478def8c8aa368d85816b70a89f6a6f55'
$Patched['mvc.16m'] = 'c94231770a7377bf6c7817427f28b87db79f3be03287279e8a19bea34cca85be'
$Patched['mvc.17m'] = 'dc29f2f0bf56d60408e9b9a2fcfae6c684f8be97712c94f146386c9bbb9ab46f'
$Patched['mvc.18m'] = '4f3ceefa11958fd92f88047f876e37a5a9a226ca9c28e3b9915a03f39a268947'
$Patched['mvc.19m'] = 'd245405b8fa57b315300d915b9b5f96859bc31b9a97f653e370c7f0f28a1fc7f'
$Patched['mvc.20m'] = '6cb08b23e33cc97bd14c6bf0ed179c99952146328f0dc0f01e8135c2783466bd'
$Patched['mvce.03a'] = '2cef56c5507147bd048ef6169ad63896ffb8e80dc0192a053522d4eb2d19997d'
$Patched['mvce.04a'] = '648b26d33e694831aa87917f8f919cff77ba12ad25147476b4d5b1269ba62cf5'
$Patched['mvsc.key'] = '9ba914d01fdb50c562fef4d4a5c7ff4779f92340322272696a46025d33f7155c'
foreach ($Record in $Vanilla) {
    $Name = [string]$Record.Name
    $Got = Get-Sha256 ([byte[]]$Entries[$Name])
    if ($Got -ne $Patched[$Name]) {
        Fail "internal patched-member proof failed for $Name; nothing was written." 8
    }
}

$Destination = Join-Path (Split-Path -Parent $Source) (([IO.Path]::GetFileNameWithoutExtension($Source)) + '_megaman_gwm_combined_v1.zip')
$Destination = [IO.Path]::GetFullPath($Destination)
if ($Destination -eq [IO.Path]::GetFullPath($Source)) { Fail 'refusing to overwrite the input archive.' }
if (Test-Path -LiteralPath $Destination) {
    Fail "output already exists: $Destination`n         Delete or move that old output yourself, then run this installer again."
}
$Temporary = "$Destination.part"
if (Test-Path -LiteralPath $Temporary) {
    Fail "stale temporary output exists: $Temporary`n         Delete it yourself after confirming it is not needed."
}
$ReceiptPath = "$Destination.receipt.json"
$ReceiptTemporary = "$ReceiptPath.part"
if (Test-Path -LiteralPath $ReceiptPath) {
    Fail "receipt already exists: $ReceiptPath`n         Delete or move that old receipt yourself, then run this installer again."
}
if (Test-Path -LiteralPath $ReceiptTemporary) {
    Fail "stale temporary receipt exists: $ReceiptTemporary`n         Delete it yourself after confirming it is not needed."
}

$File = [IO.File]::Open($Temporary, [IO.FileMode]::CreateNew, [IO.FileAccess]::ReadWrite, [IO.FileShare]::None)
try {
    $Output = New-Object System.IO.Compression.ZipArchive($File, [IO.Compression.ZipArchiveMode]::Create, $true)
    try {
        foreach ($Name in $Order) {
            $NewEntry = $Output.CreateEntry($Name, [IO.Compression.CompressionLevel]::Optimal)
            try {
                $NewEntry.LastWriteTime = $Metadata[$Name].LastWriteTime
                $NewEntry.ExternalAttributes = $Metadata[$Name].ExternalAttributes
            } catch {
                # Member data/order are authoritative; metadata preservation is best effort.
            }
            $Stream = $NewEntry.Open()
            try {
                $Bytes = [byte[]]$Entries[$Name]
                $Stream.Write($Bytes, 0, $Bytes.Length)
            } finally { $Stream.Dispose() }
        }
    } finally { $Output.Dispose() }
} catch {
    $WriteError = $_.Exception.Message
    $File.Dispose()
    if (Test-Path -LiteralPath $Temporary) { Remove-Item -LiteralPath $Temporary -Force }
    Fail "could not write output ZIP: $WriteError" 8
} finally {
    $File.Dispose()
}

# Re-open the actual bytes on disk. Verify member order, names, sizes and every
# patched SHA-256 before the .part file is promoted to the final output name.
try {
    $Written = Read-ZipMembers $Temporary
    if ($Written.Order.Count -ne $Order.Count) { throw 'output member count changed during ZIP write.' }
    for ($i = 0; $i -lt $Order.Count; $i++) {
        if ($Written.Order[$i] -cne $Order[$i]) { throw "output member order changed at index $i." }
    }
    foreach ($Record in $Vanilla) {
        $Name = [string]$Record.Name
        if (-not $Written.Entries.ContainsKey($Name)) { throw "output is missing $Name." }
        $Bytes = [byte[]]$Written.Entries[$Name]
        if ($Bytes.Length -ne [int]$Record.Size) { throw "output size check failed for $Name." }
        $Got = Get-Sha256 $Bytes
        if ($Got -ne $Patched[$Name]) { throw "output SHA-256 check failed for $Name." }
    }
} catch {
    $VerificationError = $_.Exception.Message
    if (Test-Path -LiteralPath $Temporary) { Remove-Item -LiteralPath $Temporary -Force }
    Fail "output verification failed: $VerificationError" 8
}

$ReceiptInputMembers = @(
    foreach ($Record in $Vanilla) {
        [pscustomobject][ordered]@{
            name = [string]$Record.Name
            size = [int]$Record.Size
            sha256 = [string]$Record.Sha256
        }
    }
)
$ReceiptOutputMembers = @(
    foreach ($Record in $Vanilla) {
        $Name = [string]$Record.Name
        [pscustomobject][ordered]@{
            name = $Name
            size = ([byte[]]$Entries[$Name]).Length
            sha256 = [string]$Patched[$Name]
        }
    }
)
$ReceiptHooks = @(
    foreach ($Hook in $Patches) {
        [pscustomobject][ordered]@{
            member = [string]$Hook.File
            offset = ('0x{0:X6}' -f [int]$Hook.Offset)
            before = [string]$Hook.Before
            after = [string]$Hook.After
            purpose = [string]$Hook.Purpose
        }
    }
)
$ReceiptPayloads = @(
    foreach ($Payload in $Payloads) {
        [pscustomobject][ordered]@{
            name = [string]$Payload.Purpose
            member = [string]$Payload.File
            offset = ('0x{0:X6}' -f [int]$Payload.Offset)
            cpuAddress = [string]$Payload.Cpu
            bytes = [int]$Payload.Bytes
            plainSha256 = [string]$Payload.PlainSha
            diskSha256 = [string]$Payload.DiskSha
        }
    }
)
$Receipt = [pscustomobject][ordered]@{
    format = 1
    feature = 'Mega Man Magnetic Shockwave + Gold War Machine Mega Optic Blast'
    componentRevisions = [pscustomobject][ordered]@{ magneticShockwave = 3; goldMegaOptic = 3; bundle = 1 }
    variant = 'mvsc'
    variantTitle = 'Marvel vs. Capcom (Euro 980123, encrypted parent)'
    commands = @(
        'Mega Man: ground QCB + any two punches; costs one level'
        'Gold War Machine: airborne QCF + any two punches in a normal or super jump; costs one level'
    )
    romExpansion = $false
    inputArchive = [IO.Path]::GetFileName($Source)
    inputArchiveSha256 = Get-Sha256 ([IO.File]::ReadAllBytes($Source))
    outputArchive = [IO.Path]::GetFileName($Destination)
    outputArchiveSha256 = Get-Sha256 ([IO.File]::ReadAllBytes($Temporary))
    inputMembers = $ReceiptInputMembers
    outputMembers = $ReceiptOutputMembers
    patches = $ReceiptHooks
    payloads = $ReceiptPayloads
    nativeTitle = [pscustomobject][ordered]@{
        token = '$5A'
        recordSha256 = 'f1088827140a3b3fc63e5da69b2ff63d5ea65ac3a5e57f7ce148397498808c1f'
        policy = 'stock renderer; exact Magnetic row; final opposing teammate only'
    }
    customGraphicsWrites = $false
    patchFingerprint = '835969dcce5d5545c4e38170c06b3b1242c78b311f8673548163a69c276c7a0f'
}
try {
    $ReceiptJson = ($Receipt | ConvertTo-Json -Depth 9) + [Environment]::NewLine
    $Utf8NoBom = New-Object System.Text.UTF8Encoding -ArgumentList $false
    [IO.File]::WriteAllText($ReceiptTemporary, $ReceiptJson, $Utf8NoBom)
    $ReceiptCheck = Get-Content -LiteralPath $ReceiptTemporary -Raw | ConvertFrom-Json
    if ([int]$ReceiptCheck.inputMembers.Count -ne $Vanilla.Count) { throw 'receipt input manifest count mismatch.' }
    if ([int]$ReceiptCheck.outputMembers.Count -ne $Vanilla.Count) { throw 'receipt output manifest count mismatch.' }
    if ([int]$ReceiptCheck.patches.Count -ne $Patches.Count) { throw 'receipt patch count mismatch.' }
    if ([int]$ReceiptCheck.payloads.Count -ne $Payloads.Count) { throw 'receipt payload count mismatch.' }
    if ([string]$ReceiptCheck.patchFingerprint -ne '835969dcce5d5545c4e38170c06b3b1242c78b311f8673548163a69c276c7a0f') { throw 'receipt patch fingerprint mismatch.' }
} catch {
    $ReceiptError = $_.Exception.Message
    if (Test-Path -LiteralPath $ReceiptTemporary) { Remove-Item -LiteralPath $ReceiptTemporary -Force }
    if (Test-Path -LiteralPath $Temporary) { Remove-Item -LiteralPath $Temporary -Force }
    Fail "could not write or verify receipt: $ReceiptError" 8
}

try {
    Move-Item -LiteralPath $Temporary -Destination $Destination
    try {
        Move-Item -LiteralPath $ReceiptTemporary -Destination $ReceiptPath
    } catch {
        if (Test-Path -LiteralPath $Destination) { Remove-Item -LiteralPath $Destination -Force }
        throw
    }
} catch {
    $PromotionError = $_.Exception.Message
    if (Test-Path -LiteralPath $Temporary) { Remove-Item -LiteralPath $Temporary -Force }
    if (Test-Path -LiteralPath $ReceiptTemporary) { Remove-Item -LiteralPath $ReceiptTemporary -Force }
    Fail "could not promote verified output and receipt: $PromotionError" 8
}

Write-Host ''
Write-Host '  DONE. Wrote and re-verified:' -ForegroundColor Green
Write-Host "    $Destination"
Write-Host "    $ReceiptPath"
Write-Host ''
Write-Host '  Inputs: Mega Man ground QCB+PP; Gold War Machine airborne QCF+PP.'
Write-Host '  The original ROM was not changed. To undo the patch, delete the new ZIP.'
Write-Host ''
Write-Host '  NETPLAY / CRC NOTE' -ForegroundColor Yellow
Write-Host '  This is an unofficial modified ROM. An emulator or frontend may show CRC'
Write-Host '  warnings or refuse the set. For any peer-to-peer session, both players'
Write-Host '  need the identical patched ROM and an emulator/session that accepts it;'
Write-Host '  mismatched program bytes can desync. This is not official Fightcade support.'
Write-Host ''
Write-Host '  Emulators normally identify this set by the filename mvsc.zip. Keep your'
Write-Host '  clean original safe; copy/rename the new ZIP only in the ROM folder where'
Write-Host '  you deliberately want to test the mod.'
Write-Host ''
Write-Host '  Embedded build:'
Write-Host '    Mega payload SHA    1ab0d5821438356ca3bc248244c6d71153fcda3c464e089038b0db5202c278c2'
Write-Host '    Gold payload SHA    75bc57f2a5cadd1f5d3528c4e9a6fd1850bb8214e962a048353186c330e4dca4'
Write-Host '    shared meter SHA    ccdb66cdfe85f5cf65c48d15974512617f0e231618bdac04b7209fd10139c6d1'
Write-Host '    patch fingerprint   835969dcce5d5545c4e38170c06b3b1242c78b311f8673548163a69c276c7a0f'
Write-Host '    native title token  $5A'
Write-Host '    title record SHA    f1088827140a3b3fc63e5da69b2ff63d5ea65ac3a5e57f7ce148397498808c1f'
Write-Host '    stock GFX members   unchanged'
Write-Host ''
Pause-IfConsole
exit 0
