118 lines
4.0 KiB
PowerShell
118 lines
4.0 KiB
PowerShell
<#
|
|||
|
|
English-patches a CN Star Rail client.
|
||
|
|
|
||
|
|
The language table lives in a .bytes file under
|
||
|
|
StarRail_Data/StreamingAssets/DesignData/Windows. It looks like:
|
||
|
|
|
||
|
|
cn..Chinese(PRC) en..English jp..Japanese kr..Korean
|
||
|
|
os en kr jp en cn cn cn <- voice block
|
||
|
|
os cn en kr jp en cn cn cn <- text block
|
||
|
|
|
||
|
|
Each entry is a length byte (0x02) followed by two ASCII chars, so cn -> en
|
||
|
|
is one byte per entry. The first "cn cn" pair is voice, the second is text.
|
||
|
|
|
||
|
|
Every change is backed up next to the original as *.capysr-backup.
|
||
|
|
Run with -Revert to put the originals back.
|
||
|
|
#>
|
||
|
|
param(
|
||
|
|
[string]$ClientPath,
|
||
|
|
[ValidateSet('en', 'cn', 'jp', 'kr')] [string]$Text = 'en',
|
||
|
|
[ValidateSet('en', 'cn', 'jp', 'kr')] [string]$Voice = '',
|
||
|
|
[switch]$Revert
|
||
|
|
)
|
||
|
|
|
||
|
|
$ErrorActionPreference = 'Stop'
|
||
|
|
|
||
|
|
if (-not $ClientPath) {
|
||
|
|
$configPath = Join-Path (Split-Path -Parent $PSScriptRoot) 'config\config.json'
|
||
|
|
if (Test-Path $configPath) {
|
||
|
|
$ClientPath = (Get-Content $configPath -Raw | ConvertFrom-Json).Client.Path
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (-not $ClientPath -or -not (Test-Path $ClientPath)) {
|
||
|
|
Write-Host "client folder not found. pass -ClientPath <folder with StarRail.exe>" -ForegroundColor Red
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
$dir = Join-Path $ClientPath 'StarRail_Data\StreamingAssets\DesignData\Windows'
|
||
|
|
if (-not (Test-Path $dir)) {
|
||
|
|
Write-Host "no DesignData at $dir" -ForegroundColor Red
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($Revert) {
|
||
|
|
$backups = Get-ChildItem $dir -Filter '*.capysr-backup' -ErrorAction SilentlyContinue
|
||
|
|
if (-not $backups) { Write-Host 'nothing to revert' -ForegroundColor Yellow; exit 0 }
|
||
|
|
|
||
|
|
foreach ($b in $backups) {
|
||
|
|
$target = $b.FullName -replace '\.capysr-backup$', ''
|
||
|
|
Copy-Item $b.FullName $target -Force
|
||
|
|
Remove-Item $b.FullName -Force
|
||
|
|
Write-Host " reverted $(Split-Path $target -Leaf)" -ForegroundColor Green
|
||
|
|
}
|
||
|
|
exit 0
|
||
|
|
}
|
||
|
|
|
||
|
|
$patched = 0
|
||
|
|
|
||
|
|
foreach ($file in Get-ChildItem $dir -Filter '*.bytes') {
|
||
|
|
$data = [System.IO.File]::ReadAllBytes($file.FullName)
|
||
|
|
|
||
|
|
# the table sits just past the literal "Korean"
|
||
|
|
$marker = [System.Text.Encoding]::ASCII.GetBytes('Korean')
|
||
|
|
$at = -1
|
||
|
|
for ($i = 0; $i -le $data.Length - $marker.Length; $i++) {
|
||
|
|
$hit = $true
|
||
|
|
for ($j = 0; $j -lt $marker.Length; $j++) {
|
||
|
|
if ($data[$i + $j] -ne $marker[$j]) { $hit = $false; break }
|
||
|
|
}
|
||
|
|
if ($hit) { $at = $i; break }
|
||
|
|
}
|
||
|
|
if ($at -lt 0) { continue }
|
||
|
|
|
||
|
|
# collect the "02 'c' 'n'" entries that follow, in order
|
||
|
|
$entries = @()
|
||
|
|
for ($i = $at + 6; $i -lt [Math]::Min($at + 140, $data.Length - 3); $i++) {
|
||
|
|
if ($data[$i] -eq 0x02 -and $data[$i + 1] -eq 0x63 -and $data[$i + 2] -eq 0x6E) {
|
||
|
|
$entries += ($i + 1)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# voice block then text block; the last two entries of each are the pair
|
||
|
|
if ($entries.Count -lt 6) { continue }
|
||
|
|
|
||
|
|
Write-Host " $($file.Name): $($entries.Count) cn entries at $($entries -join ', ')" -ForegroundColor DarkGray
|
||
|
|
|
||
|
|
$backup = "$($file.FullName).capysr-backup"
|
||
|
|
if (-not (Test-Path $backup)) { Copy-Item $file.FullName $backup }
|
||
|
|
|
||
|
|
function Set-Code($offset, $code) {
|
||
|
|
$bytes = [System.Text.Encoding]::ASCII.GetBytes($code)
|
||
|
|
$data[$offset] = $bytes[0]
|
||
|
|
$data[$offset + 1] = $bytes[1]
|
||
|
|
}
|
||
|
|
|
||
|
|
# each block is: standalone cn, then the "cn cn" pair.
|
||
|
|
# block 1 pair = entries 1,2 (voice); block 2 pair = entries 5,6 (text).
|
||
|
|
if ($Voice) {
|
||
|
|
Set-Code $entries[1] $Voice
|
||
|
|
Set-Code $entries[2] $Voice
|
||
|
|
}
|
||
|
|
|
||
|
|
Set-Code $entries[5] $Text
|
||
|
|
Set-Code $entries[6] $Text
|
||
|
|
|
||
|
|
[System.IO.File]::WriteAllBytes($file.FullName, $data)
|
||
|
|
$patched++
|
||
|
|
Write-Host " text -> $Text$(if ($Voice) { ", voice -> $Voice" })" -ForegroundColor Green
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($patched -eq 0) {
|
||
|
|
Write-Host 'no language table found - the client layout may have changed' -ForegroundColor Yellow
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
Write-Host ''
|
||
|
|
Write-Host "patched $patched file(s). backups are *.capysr-backup" -ForegroundColor Cyan
|
||
|
|
Write-Host 'revert with: tools\patch-client-language.ps1 -Revert' -ForegroundColor DarkGray
|