#requires -Version 7.0 [CmdletBinding()] param( [ValidateSet('Check', 'Install', 'Restore')] [string]$Action = 'Check', [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string[]]$GameDirectory ) $ErrorActionPreference = 'Stop' $originalHash = '8F90A4A3856540CB97D03CA07B4BB10B7A08F7B4AB3C524BE2BEC1AFDD29FECA' $patchedHash = '62B2079A988D23EB251D4E83366AC04068A6F47E0D38EFE92A76DF022826F9F0' $offset = 0x115F0 $originalCode = [Convert]::FromHexString('4885C97403895104C3CCCCCCCCCCCCCC') $patchedCode = [Convert]::FromHexString('4885C97407C7410400000000C3CCCCCC') function Get-BytesHash([byte[]]$Bytes) { [Convert]::ToHexString([Security.Cryptography.SHA256]::HashData($Bytes)) } if ($Action -ne 'Check' -and (Get-Process -Name 'oriwotw' -ErrorAction SilentlyContinue)) { throw '请先退出所有奥日 2 游戏窗口,再安装或还原补丁。' } # Preflight every selected directory before any mutation. Unknown builds, # unrelated modifications and conflicting backups are never overwritten. $plans = foreach ($directory in $GameDirectory) { $root = (Resolve-Path -LiteralPath $directory).Path $target = Join-Path $root 'oriwotw_Data\Plugins\AkSoundEngine.dll' $backup = $target + '.before-ori-affinity-fix' $bytes = [IO.File]::ReadAllBytes($target) $hash = Get-BytesHash $bytes if ($hash -notin @($originalHash, $patchedHash)) { throw "文件版本或内容不匹配,未修改:$target;SHA256=$hash" } if (Test-Path -LiteralPath $backup) { if ((Get-FileHash -LiteralPath $backup -Algorithm SHA256).Hash -ne $originalHash) { throw "现有备份不匹配,未覆盖:$backup" } } if ($Action -eq 'Restore' -and $hash -eq $patchedHash -and -not (Test-Path -LiteralPath $backup)) { throw "找不到经过校验的原文件备份,无法还原:$backup" } if ($hash -eq $originalHash) { for ($i = 0; $i -lt $originalCode.Length; $i++) { if ($bytes[$offset + $i] -ne $originalCode[$i]) { throw '原始指令校验失败。' } } $candidate = [byte[]]$bytes.Clone() [Array]::Copy($patchedCode, 0, $candidate, $offset, $patchedCode.Length) if ((Get-BytesHash $candidate) -ne $patchedHash) { throw '补丁结果校验失败。' } } else { $candidate = $bytes } [pscustomobject]@{ Root=$root; Target=$target; Backup=$backup; Hash=$hash; Candidate=$candidate } } foreach ($plan in $plans) { if ($Action -eq 'Check') { [pscustomobject]@{ Directory=$plan.Root State=if ($plan.Hash -eq $patchedHash) { '已安装补丁' } else { '原版,符合补丁要求' } SHA256=$plan.Hash BackupExists=(Test-Path -LiteralPath $plan.Backup) } continue } if ($Action -eq 'Install' -and $plan.Hash -eq $patchedHash) { Write-Host "已安装,无需重复修改:$($plan.Root)" continue } if ($Action -eq 'Restore' -and $plan.Hash -eq $originalHash) { Write-Host "已经是原文件:$($plan.Root)" continue } if ($Action -eq 'Install') { if (-not (Test-Path -LiteralPath $plan.Backup)) { [IO.File]::Copy($plan.Target, $plan.Backup, $false) } if ((Get-FileHash -LiteralPath $plan.Backup -Algorithm SHA256).Hash -ne $originalHash) { throw '备份验证失败;未覆盖游戏 DLL。' } $newBytes = $plan.Candidate $expected = $patchedHash } else { $newBytes = [IO.File]::ReadAllBytes($plan.Backup) $expected = $originalHash } # Detect changes made by another program since preflight. if ((Get-FileHash -LiteralPath $plan.Target -Algorithm SHA256).Hash -ne $plan.Hash) { throw "检查后文件发生变化,停止修改:$($plan.Target)" } if (Get-Process -Name 'oriwotw' -ErrorAction SilentlyContinue) { throw '游戏刚刚启动,已停止修改;请先退出游戏。' } $staging = $plan.Target + '.ori-fix-' + [Guid]::NewGuid().ToString('N') + '.tmp' try { $stream = [IO.File]::Open($staging, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None) try { $stream.Write($newBytes); $stream.Flush($true) } finally { $stream.Dispose() } if ((Get-FileHash -LiteralPath $staging -Algorithm SHA256).Hash -ne $expected) { throw '临时文件验证失败,未覆盖游戏 DLL。' } [IO.File]::Move($staging, $plan.Target, $true) if ((Get-FileHash -LiteralPath $plan.Target -Algorithm SHA256).Hash -ne $expected) { throw '写入后的校验失败,请保留备份并停止启动游戏。' } } finally { if (Test-Path -LiteralPath $staging) { Remove-Item -LiteralPath $staging } } Write-Host "${Action} 成功:$($plan.Target)" Write-Host "原文件备份:$($plan.Backup)" }