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.
184 lines
7.1 KiB
PowerShell
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
|