@echo off
rem ===========================================================================
rem  MEGA MAN v5 + GOLD WAR MACHINE v5 -- Marvel vs. Capcom (CPS-2, mvsc)
rem
rem  DRAG mvsc.zip ONTO THIS FILE
rem       or run:  MVSC-MEGAMAN-GWM-COMBINED-v3.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:MVSCBOTHV3_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 v5 + GOLD WAR MACHINE v5 COMBINED v3 -- MvC'
Write-Host '  ======================================================================='
Write-Host ('  Mega Man: ' + 'Mega Man on the ground: QCB + any two punches; simultaneous or a short one/two-update piano roll')
Write-Host ('  Gold War Machine: ' + 'Gold War Machine while airborne in a normal or super jump: QCF + any two punches; simultaneous or a one-update held-plus-new piano roll')
Write-Host '  Gold super cost: two levels.'
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-v3.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)
    }
}
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 v5: scoped common-palette reload/repaint gate' }
    @{ File='mvce.03a'; Offset=0x01240C; Before='8556cda558da31f5'; After='552243378da2e4d5'; Purpose='Mega v5: exact-row stock final-team Hyper-title eligibility gate' }
    @{ File='mvce.03a'; Offset=0x01A952; Before='2d79ed0ac7c578e8'; After='c7f9ca6602e17b37'; Purpose='Gold v5: marked Gold private victim row before hit-damage read' }
    @{ File='mvce.03a'; Offset=0x01CD1E; Before='b7e6ba813793ed6b'; After='59a8a5a973b6c4af'; Purpose='Combined v3 sole shared Mega/Gold attacker-meter dispatcher owner' }
    @{ File='mvce.03a'; Offset=0x02896A; Before='140985fcc3c6'; After='9ac65ab03140'; Purpose='Gold v5: Gold War Machine airborne QCF+PP recognizer (normal/super jump)' }
    @{ File='mvce.03a'; Offset=0x02A41E; Before='ff7be8697134'; After='6a8530b84426'; Purpose='Gold v5: Gold/War Machine special dispatcher interpose' }
    @{ File='mvce.03a'; Offset=0x0610C4; Before='c36825e7add9'; After='b1ca59e65ca0'; Purpose='Mega v5: ground command hook' }
    @{ File='mvce.03a'; Offset=0x062D04; Before='925c9362484a'; After='9623014d8b83'; Purpose='Mega v5: Mega Man dispatcher hook' }
    @{ File='mvce.04a'; Offset=0x016044; Before='f6d4debdb3a893ff'; After='ddf2452db7578840'; Purpose='Mega v5: Magnetic Shockwave palette-lifetime hook' }
    @{ File='mvce.04a'; Offset=0x0162C0; Before='767552890694'; After='7675b5c15945'; Purpose='Mega v5: Magnetic Shockwave final-object palette restore' }
    @{ File='mvce.04a'; Offset=0x0254F4; Before='2fd01a6efe19'; After='f2f6e06b1939'; Purpose='Gold v5: class-$B4 native/custom owner gate and cleanup' }
    @{ File='mvce.04a'; Offset=0x02558E; Before='b8d5aefd1994'; After='2a50f3cb355e'; Purpose='Gold v5: class-$B4 subtype-0 Onslaught identity tail' }
    @{ File='mvce.04a'; Offset=0x0255D0; Before='77ce50ea9ea5'; After='aeea45fd79d9'; Purpose='Gold v5: class-$B4 subtype-0 forward/vertical calibration' }
    @{ File='mvce.04a'; Offset=0x0257C4; Before='f4439fcdfdc7'; After='58a8e06c86fa'; Purpose='Gold v5: class-$B4 subtype-2 Onslaught identity tail' }
    @{ File='mvce.04a'; Offset=0x02593C; Before='b7bb8047b87f'; After='ac2b79b21d78'; Purpose='Gold v5: class-$B4 subtype-4 Onslaught identity tail' }
    @{ File='mvce.04a'; Offset=0x025AAC; Before='d651d637798f'; After='e7aaa7738167'; Purpose='Gold v5: class-$B4 subtype-6 Onslaught identity tail' }
    @{ File='mvce.04a'; Offset=0x025AB8; Before='1aca3265bbf1'; After='4f4cde874687'; Purpose='Gold v5: 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=0x0573D4; Before='2a751064aa988c4fb367'; After='c3a256c6b0cdb238ac96'; Purpose='Gold v5: marked Gold private hit row before common collision math' }
    @{ 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=1206; PlainSha='74a7cce1b3dc00c9421b7bf7745b60f3610597d775bb75ebf937b26cf31d4d33'; DiskSha='7df2bccecff123901edad7ffc86140f021b38b758affa3aad9f8d5a279f756b9'; Hex='00615a03004a0c66b94e010050a1f94e0600ca10754efa450801ee47c801b94e3f00ec12fa4e580400000000000000000000000000000000000000000000000000000000000000000000000000000000000000002e0c1c00e6000c6700702e10e600f94e06000a2d2e3008000c67400c020000676000006098006e540800b94e01003476b94e0000b0a40070402d1400402d1800402d1c00402d20007c3d3c004e007c1d460080006e428400ee50a1032e4a020006666d5226c004606d5228c014700472b94e0100e635b94e0b00205ef84ed81db84ed81d6e534e003c676e0c2c004e0028666e5408002e429a0300616001f870cf72b94e0100fe48b94e0200ee7100610e01fa412800b84efc1d754eb84ed81d6e534e00f466f94e0100ba6f030000801c00000004000500010070000000ff7f3100f6be009000a0000006000000000000000061f601004a0e666e2054002e300400f94e09004c60754e0000000000000000000000000000e748f2c00061400008202c674e2448266d224e5a09201467cab30a67492c00612600cbb1126769222c00e8602d4ab7790e664b2c00610001df4c034ff84e9e45df4c034f754e6e0c2400240054666e0c220052004c662e4a26000e6b2e0c1c0027003e666e2054002a606e20540008203067680c240024002866680c220052002066284a26001a6b280c1c00270012666820540008200a67680c200052000266754ec891754e00726d204e5a08202a67680c240024001c66680c220052001466284a26000e6be8bd540008667c111c002700015268202c00d260014a046600615600754e2e0ca500ce0144676e20c8022e42cf012e4a77020a677c1d0100cf01fcd06000e8414000ee43d0017c241c0044bc0570d022da20c851faffee4300020170d022da20c851faff7c1da500ce01f94e0000b0a42e0ca500ce0140666e20c8022e4acf010467fcd06000e8414000ee43d0010570d920a942fcffc851f8ffee4300020170d920a942fcffc851f8ff2e42ce012e42cf01f94e0000b0a4754e00600a01000000002e10b000000c2800126700550e6300510a6700590a626e4a080004660170754e0070754e00000000000000000000000000000000000000000000000000000070402dc801403dcc01754e006172fe08201267e7487e7f48264b2cb94e0200de72df4cfe7e0070754e2e3010006eb0c4021c6600618cff004a14670060a6fc000000000000000000000000000000000061aeff0070754ee748c2802f201000800c0100aecc0867800c010016cd1c664e224c2c00610afec9b11066ac0c0f0072788c000666df4c0143754edf4c01432e4ae8000667f94e010026cdf94e010030cd4020fcb10f0072780e666d0c000a00c00666f94e01002224280805000400f94e01001424130c08003067b94e010010977066130c08006e662b0c0e00010066662e30d600400270005c670032415340c254662b5301000270754e2e30d6004002700036670032415340c218662b0c0d00010008672b0c0c0001001e662b5301000270754e2e32d800414640c20c6700708026403704000170754eb94e0100109704670170754e0070754e000c02002e67004a146700702e1002002d0138c00c662e0c0100740204640070754e7c1d04004d007c1d1c00e600f94e0600f0170270754e'; Purpose='Mega Man Magnetic Shockwave v5' }
    @{ File='mvc.10'; Offset=0x072000; Cpu='$3F2000'; Bytes=2168; PlainSha='5888cd0154a204a3c8d6eca385eb01dc335f510909f889b5983740985d56cfd5'; DiskSha='fa8929581df26afabb6354df37999f0a21ff8524f983caa44390e172616b1227'; Hex='6e0c28005200086600610e00004a0666f94e0200b68b754e2e4ace012466fa454e00ee47c80100614e04186b146700702e1002002d0138c00c662e0c0200740204640070754eb94e0100889a7c1d04004d007c1d1a00e6006e4206006e1db000b1007c1d2a00b000f94e02002c8c030000801a00000004000600020070006e0c2800520008662e0c1a00e6000c6700702e10e600f94e020024a46e0c28005200006620002e3008001e67400c020000677a00400c04000067ae00400c060000670601f94e0100b6776e5408002e42cf010070402d1400402d1800402d1c00402d20002e30c402400420006eb01000046c403d10007c1d0d0080002e4284002e4286002e423c007c3d3c004e002e4a020006666d5226c004606d5228c018700272b94e0100e635f84ed81d0061d400b84ed81d2e4284002e428600ae0800003c000c666e534e001c66f94e0100b6776e5408002e4284002e42860000615a057c3da0004e00754e006198002d4ab7790867ee080000cf01ec602e080000cf011267ae080000cf012e0ca500ce0104660061a60200613003714e714e2e4ace011466b94e0a006e5408646e424e0000601200006100026e4a4e0006676e534e00a4667c3d060008002d4ab7791a662e4284002e4286000061b601004a0a6600614003f94e0100b67700611800754effffffffffffffffffffffffffffffffffffffff0070402d1400402d1800402d1c00402d2000754e6e205400680c220052000a66680c2c02600000678200280ca500ce010467006070002d4ab77900666000680c2800520000663e00280c2a00b00000663400280c1a00e60000662a00680c0400080000662000082fb94e3f0058265f2600618202004a0a660061d202714e714e714e754e082fb94e0a00345b5f260061fe00004aec6600612e01754ee8080000cf01754ef94e0a00345bf94e0a000c5500612600f94e0a009a5500611c00f94e0a00d05700611200f94e0a00485900610800f94e0a00b85a680c280052002666280ca500ce011e667c1d040047007c3d220052007c2d0f00fcd768007c2d3a0000886c00754e682d68006800682d6c006c00754e6e205400680c280052001466280ca500ce010c667c3dafff1c003c30ebff0a607c3d7bff1c003c30c9ff2e4a4b0002674044403d1400f94e0a00e6556e205400680c280052001466280ca500ce010c667c3daeff1c003c30ecff0a607c3d7aff1c003c30caff2e4a4b0002674044403d1400f94e0a00ce5a4e266d224e5a09201867690cb40024000666e9b75400066769222c00e8600170754e0070754e2e0ca500ce010a6700617a007c1da500ce01754e0e2f4b2c04615f2c754e2e0ca500ce010e662e42ce012e42cf01b94e0000b0a4754e2d4ab77940662e0ca500ce013a666e0c2800520032662e0c2a00b0002a662e0c1a00e60022663c3017001122810200f000f09082c1221822810000f000f0c124c851e8ff0861754ef94e0000cea46e208802e84120007c241c00e4bd07701022810200f000f09a82c120c851f2ff6e208c02e84120007c241c00e4bd07701a22810000f000f0c120c851f4ff754e00604e01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2e4284002e428600ae0c160060fe34001c642e304e00400203001266b84ed81d2e4284002e428600ae0800003c00754eae0c0f00d2788c000a667c2d3f00f8278c001260ae0c0f00f2788c0008667c2d3f0018288c00006072fe0061b0fe2e0c0200600008662e0c120061000a6702701272b94e0100e6352e4284002e428600ae0800003c00754e0e2f4b2c7c3d060008000061c6ff5f2c754effff970c0100aecc0c67970c010016cd2a660172026000726e0c280052001c662e0ca500ce0114666c0cb40024000c66ecbd540006660061d001754e2e4ae8000667f94e010026cdf94e010030cdffffffffffffffffffffffffffffffffffffffffffffffffffff2e0c2a00b000006786000061cc00004a00667c00130c08003067b94e010010976866130c080066662b0c0e0001005e662e30d6004002700054670032415340c24c662b530100ff70754e2e30d600400270002e670032415340c210662b0c0d0001001e662b530100ff70754e2e32d800414640c20c6700708026403704000170754eb94e0100109704670170754e0070754e082f1861003fb94e0a000c551f305f20404a06677c1d80008400754e0070284a020008667c24ff00003406607c24ff0000302a0c0800b0000a666a0c0400080002660170754e00702e4a020008666d4a34c0c056754e6d4a36c0c056754e3c309eff3c329fff0076b94e020058a40170f94e0100fecd48298c006819070085006c0cb40024003c66092f6c225400690c280052002c66290ca500ce012466fcb10f00d2780e67fcb10f00f2781466fa4110010460fa41ea0048298c006819070085005f22754e6c0cb40024003666092f6c225400690c280052002666290ca500ce011e66fcb10f00d2780e67fcb10f00f2780e66fa41ca000460fa41a40048298c005f22482d8c006e208c00754e082f6f200c00ac0c0f00d2788c004467ac0c3f00f8278c003a67ac0c3f0038288c003067ac0c0f00f2788c001467ac0c3f0018288c000a67ac0c3f0058288c004c667c293f0018288c007c213f0018288c003a60014a0e672e080100cf011866ee080100cf017c293f00f8278c007c213f00f8278c001660ae080100cf017c293f0038288c007c213f0038288c005f20754e0007c003301008000f000a190580240c640412001400010400000000000000000007c003301008000f000a190580240c640512001400010600000000000000000007c003301008000f000a190580240c640412001400010600000000000000000007c003301008000f000a190580240c64051200140001060000000000000000'; Purpose='Gold War Machine Mega Optic Blast v5' }
    @{ File='mvc.10'; Offset=0x073000; Cpu='$3F3000'; Bytes=20; PlainSha='ccdb66cdfe85f5cf65c48d15974512617f0e231618bdac04b7209fd10139c6d1'; DiskSha='bb1e9ec07e7454bff4fbe93ef21fe762216a2bcf901c14f8fb330e5f5db5aa09'; Hex='6c0cb40024000667f94e3f008a13f94e3f006025'; Purpose='Combined v3 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'] = 'de3d0f77357766e52207e34242fa5eb831f6f53bbe157057420375094a93a3cd'
$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'] = '8ed976e94655c717282448c2f107cdf17b1625913fbfe527d32c5a9e054d241a'
$Patched['mvce.04a'] = '4328548cdd84a264bf275307ea9d23fa421f56fb292c26708047a913572407e0'
$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) 'mvsc_megaman_gwm_combined_v3.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
    status = 'installed'
    feature = 'Mega Man Magnetic Shockwave + Gold War Machine Mega Optic Blast'
    componentRevisions = [pscustomobject][ordered]@{ magneticShockwave = 5; goldMegaOptic = 5; bundle = 3 }
    variant = 'mvsc'
    variantTitle = 'Marvel vs. Capcom (Euro 980123, encrypted parent)'
    commands = @(
        'Mega Man on the ground: QCB + any two punches; simultaneous or a short one/two-update piano roll'
        'Gold War Machine while airborne in a normal or super jump: QCF + any two punches; simultaneous or a one-update held-plus-new piano roll'
    )
    cleanBaseOnly = $true
    stackingSupported = $false
    composition = 'one-pass union from exact clean parent; never stacked'
    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 = 'af80e769d61bd7d260f618b76648a8ca8d707fd7c3c8554579f48c1a04bace14'
    compositionFingerprint = 'af80e769d61bd7d260f618b76648a8ca8d707fd7c3c8554579f48c1a04bace14'
    componentSourceSha256 = [pscustomobject][ordered]@{ mega = 'cae1075df20f2f6f538d798a9f9c579901d8745d3144dd9e9282da6e739d4a42'; gold = '97197eb0986b66960de86383dc80d1b8034af4920bb8c7d2de145173de191e62' }
    componentResourceSha256 = [pscustomobject][ordered]@{ mega = '1a28666bc7f4468b7caf8b8b75db490426e2494135ced85884c177758ef7375b'; gold = '6252bae7ba2abeca572d5df1d5f18841e4696fbc689982e103986cafaee710a9' }
    componentFreezeSha256 = [pscustomobject][ordered]@{ mega = '2c7a773001291753f64f4ae1e9f080ad100ae68db875c180c859bb8ce7f01356'; gold = '5cbc7b714a24406e29821a69eaa4e1f1cbaef53adaed72cf8213faf64b585803' }
    releaseLockSha256 = 'deb24f6971d6fdeacf3fa063d35bd9249ab3dbb024bc577bfb2dc606241adb3f'
    releaseRequirements = [pscustomobject][ordered]@{ combinedInstallerOnly = $true; goldCustomGraphicsWrites = $false; goldDuoPolicy = 'reject Gold Mega Optic activation before parser consumption while the native Variable Cross countdown is active'; goldGuardTotalChip = 7; goldHitContacts = 17; goldHitDamageByte = '$07'; goldHitTotalDamage = 52; goldMeterCostLevels = 2; goldOwnerScopedNoOtg = $true; goldRecoveryPolicy = 'stock unlit air-MP group $02/subaction $12 followed by native RETURN_TO_AIR gravity and landing'; goldStockUnlitGraphics = $true; noStandalonePlayerPackages = $true; romIncludedInPublicPackage = $false }
    sharedMeterHook = [pscustomobject][ordered]@{ cpuAddress = '$01CD1E'; ownerCount = 1; dispatcherCpuAddress = '$3F3000'; encryptedDataOrderHex = 'A859A9A5B673AFC4'; plainFetchedHex = '4EF9003F30004E71'; componentWrapperCpuAddresses = [pscustomobject][ordered]@{ mega = '$3F138A'; gold = '$3F2560' } }
}
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.componentRevisions.bundle -ne 3) { throw 'receipt bundle revision mismatch.' }
    if ([int]$ReceiptCheck.sharedMeterHook.ownerCount -ne 1) { throw 'shared meter owner count mismatch.' }
    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 'af80e769d61bd7d260f618b76648a8ca8d707fd7c3c8554579f48c1a04bace14') { 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 ('  Mega : ' + 'Mega Man on the ground: QCB + any two punches; simultaneous or a short one/two-update piano roll')
Write-Host ('  Gold : ' + 'Gold War Machine while airborne in a normal or super jump: QCF + any two punches; simultaneous or a one-update held-plus-new piano roll')
Write-Host '  One clean-parent pass; standalone/patched outputs are rejected and never stacked.'
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    74a7cce1b3dc00c9421b7bf7745b60f3610597d775bb75ebf937b26cf31d4d33'
Write-Host '    Gold payload SHA    5888cd0154a204a3c8d6eca385eb01dc335f510909f889b5983740985d56cfd5'
Write-Host '    shared meter SHA    ccdb66cdfe85f5cf65c48d15974512617f0e231618bdac04b7209fd10139c6d1'
Write-Host '    patch fingerprint   af80e769d61bd7d260f618b76648a8ca8d707fd7c3c8554579f48c1a04bace14'
Write-Host '    native title token  $5A'
Write-Host '    title record SHA    f1088827140a3b3fc63e5da69b2ff63d5ea65ac3a5e57f7ce148397498808c1f'
Write-Host '    stock GFX members   unchanged'
Write-Host ''
Pause-IfConsole
exit 0
