# 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\\ 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