Files
onion-mc/onion-perf-setup.ps1
T
logan f949c169a2 feat(perf): add one-step tier setup script for existing players
Default Options only seeds configs when absent, so existing players never
got the tuned settings. build.py --ship now also writes the Deck/Mid/High
tier files to config/onion-perf/<tier>/, and onion-perf-setup.{sh,ps1,bat}
(installed into the instance folder) backs up and replaces the tier's
config files and merges only the tuned video keys into options.txt,
keeping keybinds, audio and vsync/max-FPS choices.

PLAYER-TUNING: point players at the script; bench B4 GC verdict (G1 for
everyone; ZGC/Shenandoah tested worse); 16 GB PCs 5-6 GB heap.
2026-10-18 15:31:20 -04:00

184 lines
7.1 KiB
PowerShell

# Onion MC: apply a performance tier's tuned settings to this instance.
#
# onion-perf-setup.bat [deck|mid|high] [--yes] (or run this .ps1 directly)
#
# Lives in the instance's game folder (the one that holds config\ and options.txt)
# after a pack update. Close Minecraft first. Every file it changes is backed up to
# onion-perf-backup\<date-time>\ first. options.txt is merged key by key: only the
# tier's video keys change; keybinds, audio, resource packs etc. are left alone.
# No network access. Windows PowerShell 5.1 compatible.
$ErrorActionPreference = 'Stop'
function Fail([string]$msg) {
[Console]::Error.WriteLine("error: $msg")
exit 1
}
$Game = $PSScriptRoot
if (-not $Game) { $Game = Split-Path -Parent $MyInvocation.MyCommand.Path }
$Tier = ''
$Yes = $false
foreach ($a in $args) {
$s = ([string]$a).ToLowerInvariant()
if (@('deck', 'mid', 'high') -contains $s) {
if ($Tier) { Fail 'more than one tier given' }
$Tier = $s
} elseif (@('-y', '--yes', '-yes', '/y') -contains $s) {
$Yes = $true
} else {
Fail "unknown argument '$a' (use: deck | mid | high, optionally --yes)"
}
}
$Perf = Join-Path $Game 'config\onion-perf'
if (-not (Test-Path -LiteralPath (Join-Path $Perf 'mid') -PathType Container)) {
Fail "$Perf not found. Update the modpack first (it ships these settings), and run this script from the instance's game folder."
}
$Interactive = [Environment]::UserInteractive -and -not [Console]::IsInputRedirected
if (-not $Tier) {
if ($Interactive) {
Write-Host 'Which tier fits this machine?'
Write-Host ' deck Steam Deck, handheld, laptop/integrated graphics'
Write-Host ' mid GTX 1060 / RX 580 class (~6 GB VRAM), 4-6 core CPU [pack default]'
Write-Host ' high 8 GB+ VRAM graphics card (GTX 1080 Ti, RTX 3060 and up)'
$Tier = (Read-Host 'Tier [mid]').Trim().ToLowerInvariant()
if (-not $Tier) { $Tier = 'mid' }
if (@('deck', 'mid', 'high') -notcontains $Tier) { Fail "unknown tier '$Tier'" }
} else {
$Tier = 'mid'
}
}
$Src = Join-Path $Perf $Tier
if (-not (Test-Path -LiteralPath $Src -PathType Container)) { Fail "$Src not found. Update the modpack first." }
if (-not $Yes) {
if (-not $Interactive) { Fail 'not running interactively: close Minecraft, then re-run with --yes' }
$ans = Read-Host 'Is Minecraft closed? [y/N]'
if (@('y', 'yes') -notcontains $ans.Trim().ToLowerInvariant()) {
Write-Host 'Close Minecraft and run this again.'
exit 1
}
}
$Stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$Backup = Join-Path $Game (Join-Path 'onion-perf-backup' $Stamp)
$n = 1
while (Test-Path -LiteralPath $Backup) {
$Backup = Join-Path $Game (Join-Path 'onion-perf-backup' "$Stamp-$n")
$n++
}
# .NET file calls, not New-Item/Copy-Item: instance folders often contain '[' (Prism
# names the game dir after the instance) and -Path/Copy-Item -Destination glob those.
[IO.Directory]::CreateDirectory($Backup) | Out-Null
function Save-Backup([string]$rel) {
$from = Join-Path $Game $rel
if (-not (Test-Path -LiteralPath $from -PathType Leaf)) { return }
$to = Join-Path $Backup $rel
[IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($to)) | Out-Null
[IO.File]::Copy($from, $to, $true)
}
function Test-SameFile([string]$a, [string]$b) {
if (-not (Test-Path -LiteralPath $b -PathType Leaf)) { return $false }
$x = [IO.File]::ReadAllBytes($a)
$y = [IO.File]::ReadAllBytes($b)
if ($x.Length -ne $y.Length) { return $false }
return [Convert]::ToBase64String($x) -ceq [Convert]::ToBase64String($y)
}
$replaced = 0
$unchanged = 0
Write-Host "Applying tier '$Tier' to $Game"
$srcConfig = Join-Path $Src 'config'
$files = @()
if (Test-Path -LiteralPath $srcConfig) {
$srcConfig = (Get-Item -LiteralPath $srcConfig).FullName.TrimEnd('\', '/')
$files = @(Get-ChildItem -LiteralPath $srcConfig -Recurse -File | Sort-Object FullName)
}
foreach ($f in $files) {
$rel = 'config' + $f.FullName.Substring($srcConfig.Length)
$dst = Join-Path $Game $rel
if (Test-SameFile $f.FullName $dst) { $unchanged++; continue }
Save-Backup $rel
[IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($dst)) | Out-Null
[IO.File]::Copy($f.FullName, $dst, $true)
Write-Host " replaced $($rel -replace '\\', '/')"
$replaced++
}
$Opt = Join-Path $Game 'options.txt'
$SrcOpt = Join-Path $Src 'options.txt'
$Utf8 = New-Object Text.UTF8Encoding $false
if (-not (Test-Path -LiteralPath $SrcOpt -PathType Leaf)) {
Write-Host ' (tier has no options.txt)'
} elseif (-not (Test-Path -LiteralPath $Opt -PathType Leaf)) {
Write-Host ' options.txt not found: skipped (Default Options seeds it on first launch)'
} else {
$skip = @('version', 'maxFps', 'enableVsync')
$want = @{}
$order = New-Object System.Collections.Generic.List[string]
foreach ($line in [IO.File]::ReadAllLines($SrcOpt, $Utf8)) {
$i = $line.IndexOf(':')
if ($i -lt 0) { continue }
$k = $line.Substring(0, $i)
if ($skip -ccontains $k) { continue }
if (-not $want.ContainsKey($k)) { $order.Add($k) }
$want[$k] = $line.Substring($i + 1)
}
$raw = [IO.File]::ReadAllText($Opt, $Utf8)
$nl = "`n"
if ($raw.Contains("`r`n")) { $nl = "`r`n" }
$lines = $raw -split "`r?`n"
$trailing = $raw.EndsWith("`n")
if ($trailing) { $lines = $lines[0..($lines.Length - 2)] }
if ($raw.Length -eq 0) { $lines = @() }
$out = New-Object System.Collections.Generic.List[string]
$done = @{}
$changes = New-Object System.Collections.Generic.List[string]
foreach ($line in $lines) {
$i = $line.IndexOf(':')
if ($i -gt 0) {
$k = $line.Substring(0, $i)
if ($want.ContainsKey($k) -and -not $done.ContainsKey($k)) {
$v = $line.Substring($i + 1)
if ($v -cne $want[$k]) { $changes.Add(" ${k}: $v -> $($want[$k])") }
$out.Add("${k}:$($want[$k])")
$done[$k] = $true
continue
}
}
$out.Add($line)
}
foreach ($k in $order) {
if (-not $done.ContainsKey($k)) {
$changes.Add(" ${k}: (unset) -> $($want[$k])")
$out.Add("${k}:$($want[$k])")
}
}
if ($changes.Count -gt 0) {
Save-Backup 'options.txt'
# WriteAllText (not WriteAllLines): same UTF-8-without-BOM bytes, but joins with
# the file's own line endings - WriteAllLines always uses Environment.NewLine.
$text = [string]::Join($nl, $out.ToArray()) + $nl
[IO.File]::WriteAllText($Opt, $text, $Utf8)
Write-Host ' options.txt keys changed:'
foreach ($c in $changes) { Write-Host $c }
} else {
Write-Host ' options.txt: already matches the tier'
}
}
if (@(Get-ChildItem -LiteralPath $Backup -Force).Count -eq 0) {
Remove-Item -LiteralPath $Backup -Force
$Backup = '(nothing changed, no backup needed)'
}
Write-Host "Done: tier '$Tier'; $replaced file(s) replaced, $unchanged already up to date."
Write-Host "Backup: $Backup"
Write-Host 'Also set Java memory in your launcher (5 GB is enough): see PLAYER-TUNING.md section 2.'
exit 0