Skip to content

Instantly share code, notes, and snippets.

@TestItOnlyOnce
Created January 11, 2019 16:43
Show Gist options
  • Save TestItOnlyOnce/e18b61e520293a17388cba17994bbf20 to your computer and use it in GitHub Desktop.
Save TestItOnlyOnce/e18b61e520293a17388cba17994bbf20 to your computer and use it in GitHub Desktop.
Binary PowerShell patcher (fc /b format); Бинарный патчер [codepage: win-1251]
<#
.КАК ИСПОЛЬЗОВАТЬ
0. Разрешите локальные скрипты вбив set-executionpolicy remotesigned в консоли PowerShell ISE или
запустив скрипт как powershell -executionpolicy bypass -File "patcher.ps1".
1. Скопируйте .fix файл в папку с пропатчиваемой программой.
2. Запустите этот скрипт.
3. Укажите путь к .fix файлу.
Всё...
Патчер подходит только для относительно небольших файлов,
т.к. в процессе полностью копирует их содержимое в память.
.ФОРМАТ .fix ФАЙЛА
Первая строка: описание патча одной строкой
названиефайла1.эхэ
патч1: регэксп(.{3}).по(и)ска1 строка$1\x20замены$2\x01
патч2: регэксп(.{3}).по(и)ска2 строка$1\x20замены$2\x02
адрес1: старыйбайт1 новыйбайт1
адрес2: старыйбайт2 новыйбайт2
.\подкаталог\названиефайла2.эхэ
патч1: регэксп(.{3}).по(и)ска1 строка$1\x20замены$2\x01
патч2: регэксп(.{3}).по(и)ска2 строка$1\x20замены$2\x02
адрес1: старыйбайт1 новыйбайт1
адрес2: старыйбайт2 новыйбайт2
Пробелы в регэкспах заменяйте на '\s', т.к. ' ' тут как разделитель между ними.
#>
param([string]$Fix);
#Формат патча -> название: регэксп_поиска строка_замены
function patchString
{
param ([ref]$StringData, [ref]$PatchLine, [ref]$CounterRef)
if ($PatchLine.value[0] -eq "#") { return }
if (-Not ($PatchLine.value -match [regex]"([^:]+): ([^\s]+) ([^\s]+)")) { return }
$PatchName = $matches[1].Trim()
$PatchRegExp = $matches[2].Trim()
$Replacer = $matches[3].Trim()
Try
{
$BytePatch = [Convert]::ToInt32($matches[2], 16)
$ByteReplacer = [Convert]::ToInt32($matches[3] , 16)
$Address = [Convert]::ToInt64($matches[1], 16)
return
}
Catch
{}
$PatchRegExp = [Regex] $PatchRegExp
$Counter = 0
$matchEvaluator =
[System.Text.RegularExpressions.MatchEvaluator] {
param($m)
$Counter++
write-output ([regex]::Unescape([regex]::Replace($m.value, $PatchRegExp, $Replacer)))
}
Write-Host ('Патч "' + $PatchLine.value) -nonewline
$StringData.value = [regex]::Replace($StringData.value, $PatchRegExp, $matchEvaluator);
if ($Counter -eq 0)
{
Write-Host '" не применён.'
}
else
{
Write-Host '" применён.'
}
$CounterRef.value += $Counter
}
#Формат патча -> адрес: старыйбайт новыйбайт
function patchByte
{
param ([ref]$ByteArray, [ref]$PatchLine, [ref]$CounterRef)
if ($PatchLine.value[0] -eq "#") { return }
if (-Not ($PatchLine.value -match [regex]"([^:]+):\s+([a-fA-F0-9]+)\s+([a-fA-F0-9]+)")) { return }
Try
{
$BytePatch = [Convert]::ToInt32($matches[2], 16)
$ByteReplacer = [Convert]::ToInt32($matches[3], 16)
$Address = [Convert]::ToInt64($matches[1].Trim(), 16)
if (($ByteReplacer -gt 255) -or ($BytePatch -gt 255))
{
Write-Host "Неверный формат патча:"$PatchLine.value
return
}
Write-Host ('Патч "' + $PatchLine.value) -nonewline
if ($ByteArray.value[$Address] -eq $BytePatch)
{
$CounterRef.value++
$ByteArray.value[$Address] = $ByteReplacer
Write-Host '" применён.'
}
else
{
Write-Host '" не применён.'
}
}
Catch
{}
}
function patchFile
{
param([ref]$Patch, [int]$index)
$patcheeName = ($Patch.value[$index]).Trim()
$localPath = ""
Write-Host "`nПрименяем патч к"$patcheeName"."
$BinaryString = ""
Try
{
$localPath = [string](Resolve-Path (".\" + $patcheeName))
$BinaryString = [System.IO.File]::ReadAllBytes($localPath);
}
Catch
{
Write-Host "Не найден файл" $patcheeName "для патча."
for ($i=$index+1; $i -le $Patch.value.length-1; $i++)
{
$Line = $Patch.value[$i].Trim()
if ([string]::IsNullOrEmpty($Line)) { continue }
if ($Line -match [regex]"^([^#:/$]+)$")
{
patchFile $Patch $i
return
}
}
return
}
$Counter = 0
$nextFileIndex = 0
for ($i=$index+1; $i -le $Patch.value.length-1; $i++)
{
$Line = $Patch.value[$i].Trim()
if ([string]::IsNullOrEmpty($Line)) { continue }
if ($Line -match [regex]"^([^#:/$]+)$") { break }
patchByte ([ref]$BinaryString) ([ref]$Line) ([ref]$Counter)
}
$BinaryString = [Text.Encoding]::GetEncoding(28591).GetString($BinaryString);
for ($i=$index+1; $i -le $Patch.value.length-1; $i++)
{
$Line = $Patch.value[$i].Trim()
if ([string]::IsNullOrEmpty($Line)) { continue }
if ($Line -match [regex]"^([^#:/$]+)$")
{
$nextFileIndex = $i
break
}
patchString ([ref]$BinaryString) ([ref]$Line) ([ref]$Counter)
}
if ($Counter -gt 0)
{
Try
{
Rename-Item -Path $localPath -NewName $localPath".bak"
}
Catch
{}
[System.IO.File]::WriteAllBytes($localPath, [Text.Encoding]::GetEncoding(28591).GetBytes($BinaryString));
Write-Host "Патч выполнен и бекап сохранён как"$patcheeName".bak."
}
else
{
Write-Host "Файл" $patcheeName "не изменён, проверьте соответствие версий патча и файла."
}
if ($nextFileIndex -gt 0)
{
patchFile $Patch $nextFileIndex
}
}
#Окно выбора файла
function openFileDialog
{
param([string]$WindowTitle)
Add-Type -AssemblyName System.Windows.Forms
$openFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$openFileDialog.initialDirectory = $MyInvocation.MyCommand.Path
$openFileDialog.Title = $WindowTitle
$openFileDialog.filter = "Patch files (*.fix)| *.fix"
$openFileDialog.ShowDialog() > $null
return $openFileDialog.Filename
}
function doPatch
{
param([string]$filePath)
Set-Location (Split-Path -Path $filePath)
$patchArray = Get-Content $filePath
Write-Host "`n[Патч]"$patchArray[0]
for ($i=1; $i -le $patchArray.length-1; $i++)
{
$patchLine = $patchArray[$i].Trim()
if ([string]::IsNullOrEmpty($patchLine)) { continue }
patchFile ([ref]$patchArray) $i
break
}
}
Write-Host "`nПараметры командной строки (консоль):`n`tpatcher.ps1 [-fix <путь к .fix файлу>]"
if ($Fix)
{
doPatch -filePath $Fix
}
else
{
Add-Type -AssemblyName System.Windows.Forms
$App = New-Object System.Windows.Forms.Form -Property @{
AutoSize = $false
MinimizeBox = $false
MaximizeBox = $false
StartPosition = "CenterScreen"
Size = New-Object System.Drawing.Size(180,125)
Text = "PowerShell патчер"
FormBorderStyle = "FixedDialog"
}
$Button = New-Object System.Windows.Forms.Button -Property @{
Location = New-Object System.Drawing.Point -Property @{
X = 12
Y = 10
}
Size = New-Object System.Drawing.Size(150, 35)
Text = "Патчить"
}
$ButtonExit = New-Object System.Windows.Forms.Button -Property @{
Location = New-Object System.Drawing.Point -Property @{
X = 12
Y = 50
}
Size = New-Object System.Drawing.Size(150, 35)
Text = "Выход"
}
$Button.Add_Click({
$Button.Enabled = $false
$FixPath = openFileDialog -WindowTitle "Выберите файл патча"
if ([string]::IsNullOrEmpty($FixPath))
{
Write-Host "Не выбран файл патча."
$Button.Enabled = $true
return
}
doPatch -filePath $FixPath
$Button.Enabled = $true
})
$ButtonExit.Add_Click({
$App.Close()
})
$App.Controls.Add($Button)
$App.Controls.Add($ButtonExit)
$App.ShowDialog() > $null
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment