Files
CVRManager/scripts/migrations/2026-10-05-import-branch-codes.ps1
Exterrata 9bb4f85802 CVR Manager 1.0.0
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 02:46:35 -04:00

100 lines
5.5 KiB
PowerShell

<#
One-off migration: import the beta access codes listed in "<library>\branch codes.txt" (one code per line) into CVRManager.
Each code is resolved with Steam (anonymous CheckAppBetaPassword, the same call the app's "Enter beta code" box makes), so a
code that unlocks several branches is stored once per branch. For every branch it unlocks:
- config.json -> Unlocked gets { Name, Code } (an existing entry for that branch, e.g. a name-only one, gets the code filled in)
- installs on that branch get BranchPassword set, so Update works without opening the dropdown
Codes are never printed; they are shown as a length only.
The codes are checked as your signed-in Steam account, so sign in first: CVRManager > Settings > "Sign in (beta codes)".
The saved login token is read with the same Windows user that signed in, so run this as that user.
Needs a built app for the resolver: -BinDir (default bin\v26 next to the project) must contain CVRManager.dll and SteamKit2.dll.
Run it OUTSIDE Claude's sandbox (a normal terminal), otherwise it edits a private copy of %APPDATA%.
Dry run by default (resolves codes, writes nothing).
pwsh -File 2026-10-05-import-branch-codes.ps1
pwsh -File 2026-10-05-import-branch-codes.ps1 -Apply
Close CVRManager first with -Apply. config.json is backed up next to itself.
#>
[CmdletBinding()]
param(
[string]$ConfigDir = (Join-Path $env:APPDATA 'CVRManager'),
[string]$CodesFile,
[string]$BinDir = (Join-Path $PSScriptRoot '..\..\bin\v26'),
[switch]$Apply
)
$ErrorActionPreference = 'Stop'
$configPath = Join-Path $ConfigDir 'config.json'
if (-not (Test-Path $configPath)) { throw "No config at $configPath" }
$cfg = Get-Content $configPath -Raw | ConvertFrom-Json
$lib = if ($cfg.LibraryRoot) { $cfg.LibraryRoot } else { Join-Path $env:LOCALAPPDATA 'CVRManager' }
if (-not $CodesFile) { $CodesFile = Join-Path $lib 'branch codes.txt' }
if (-not (Test-Path $CodesFile)) { throw "No codes file at $CodesFile" }
if ($Apply -and (Get-Process CVRManager -ErrorAction SilentlyContinue)) { throw 'Close CVRManager first; it would overwrite the change.' }
$BinDir = (Resolve-Path $BinDir).Path
[void][Reflection.Assembly]::LoadFrom((Join-Path $BinDir 'SteamKit2.dll'))
[void][Reflection.Assembly]::LoadFrom((Join-Path $BinDir 'CVRManager.dll'))
$resolver = [CVRManager.Services.BranchCodeService]
$account = $cfg.SteamUser
$token = [CVRManager.Services.TokenVault]::Unprotect($cfg.SteamToken)
if (-not $token) { throw 'Not signed in to Steam in the app. Open CVRManager > Settings > "Sign in (beta codes)", then run this again.' }
"Checking codes as Steam account '$account'."
$codes = @(Get-Content $CodesFile | ForEach-Object { $_.Trim() } | Where-Object { $_ } | Select-Object -Unique)
"Codes in file: $($codes.Count)"
$known = @($cfg.Unlocked | Where-Object { $_.Code } | ForEach-Object { $_.Code } | Select-Object -Unique |
Where-Object { $c = $_; -not @($cfg.Unlocked | Where-Object { $_.Code -ceq $c -and -not $_.Key }).Count })
$resolved = @()
$n = 0
foreach ($code in $codes) {
$n++
if ($known -ccontains $code) { " code $n (len $($code.Length)): already stored with its key, skipped"; continue }
for ($try = 1; $try -le 6; $try++) {
try {
$keys = $resolver::ResolveAsync($code, $account, $token, [Threading.CancellationToken]::None).GetAwaiter().GetResult()
$names = @($keys.Keys)
" code $n (len $($code.Length)): unlocks $($names.Count) branch(es): $($names -join ', ')"
foreach ($name in $names) { $resolved += [pscustomobject]@{ Name = $name; Code = $code; Key = $resolver::ProtectKey($keys[$name]) } }
break
}
catch {
$msg = $_.Exception.InnerException.Message ?? $_.Exception.Message
if ($msg -match 'RateLimitExceeded' -and $try -lt 6) { " code ${n}: Steam rate limit, waiting 60 s (try $try of 5)"; Start-Sleep 60; continue }
" code $n (len $($code.Length)): NOT resolved - $msg"
break
}
}
}
if ($resolved.Count -eq 0) { 'Nothing resolved; nothing to do.'; return }
$unlocked = [System.Collections.Generic.List[object]]::new()
foreach ($u in @($cfg.Unlocked)) { $unlocked.Add($u) }
$added = 0; $filled = 0; $same = 0; $installsSet = 0
foreach ($r in $resolved) {
$existing = $unlocked | Where-Object { $_.Name -eq $r.Name } | Select-Object -First 1
if (-not $existing) { $unlocked.Add([pscustomobject]@{ Name = $r.Name; Code = $r.Code; Key = $r.Key }); $added++ }
elseif ($existing.Code -ceq $r.Code) { $same++; $existing | Add-Member -NotePropertyName Key -NotePropertyValue $r.Key -Force }
else { if ([string]::IsNullOrEmpty($existing.Code)) { $filled++ } else { " note: replacing the stored code for '$($r.Name)'" }; $existing.Code = $r.Code; $existing | Add-Member -NotePropertyName Key -NotePropertyValue $r.Key -Force }
foreach ($i in @($cfg.Installs | Where-Object { $_.Branch -eq $r.Name -and $_.BranchPassword -cne $r.Code })) { $i.BranchPassword = $r.Code; $installsSet++ }
}
"Unlocked entries: $added added, $filled filled in, $same already correct. Installs given a branch password: $installsSet."
if (-not $Apply) { ''; 'Dry run only. Re-run with -Apply to save.'; return }
$backup = "$configPath.bak-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
Copy-Item $configPath $backup
"Config backed up to $backup"
$cfg.Unlocked = @($unlocked)
[IO.File]::WriteAllText($configPath, ($cfg | ConvertTo-Json -Depth 30), (New-Object Text.UTF8Encoding($false)))
'Config updated. Start CVRManager again.'