<# One-off migration: import the beta access codes listed in "\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.'