113 lines
4.8 KiB
PowerShell
113 lines
4.8 KiB
PowerShell
#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)"
|
||
}
|