<# One-off migration: copy the list of ChilloutVR beta branches you have unlocked in Steam into CVRManager's branch dropdown. Only branch NAMES are copied; no codes (Steam does not keep them in a readable form). Reads \config\config.vdf -> InstallConfigStore/Software/Valve/Steam/apps/661130, keys "betahash_". Writes the names to config.json -> Unlocked (skipping names already present). Dry run by default. Run with pwsh. pwsh -File 2026-10-05-import-steam-unlocked-branches.ps1 # show what would be added pwsh -File 2026-10-05-import-steam-unlocked-branches.ps1 -Apply Close CVRManager first (it rewrites config.json). config.json is backed up next to itself. #> [CmdletBinding()] param( [string]$ConfigDir = (Join-Path $env:APPDATA 'CVRManager'), [string]$SteamDir, [switch]$Apply, [switch]$SkipAppCheck # only for testing against a throwaway copy ) $ErrorActionPreference = 'Stop' if (-not $SteamDir) { $SteamDir = (Get-ItemProperty 'HKCU:\Software\Valve\Steam' -ErrorAction SilentlyContinue).SteamPath } if (-not $SteamDir) { throw 'Could not find Steam; pass -SteamDir.' } $vdf = Join-Path $SteamDir 'config\config.vdf' if (-not (Test-Path $vdf)) { throw "No $vdf" } function Get-Block([string]$text, [int]$from) { $open = $text.IndexOf('{', $from) if ($open -lt 0) { return $null } $depth = 0 for ($i = $open; $i -lt $text.Length; $i++) { if ($text[$i] -eq '{') { $depth++ } elseif ($text[$i] -eq '}') { $depth--; if ($depth -eq 0) { return $text.Substring($open, $i - $open + 1) } } } $null } $text = Get-Content $vdf -Raw $apps = [regex]::Match($text, '"apps"\s*\{') if (-not $apps.Success) { throw 'No "apps" section in config.vdf' } $appsBlock = Get-Block $text $apps.Index $app = [regex]::Match($appsBlock, '"661130"\s*\{') if (-not $app.Success) { 'ChilloutVR has no entry in Steam''s config; nothing to import.'; return } $appBlock = Get-Block $appsBlock $app.Index $names = @([regex]::Matches($appBlock, '"betahash_([^"]+)"') | ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique) "Branches Steam has unlocked for ChilloutVR ($($names.Count)): $($names -join ', ')" $configPath = Join-Path $ConfigDir 'config.json' if (-not (Test-Path $configPath)) { throw "No config at $configPath" } if ($Apply -and -not $SkipAppCheck -and (Get-Process CVRManager -ErrorAction SilentlyContinue)) { throw 'Close CVRManager first; it would overwrite the change.' } $cfg = Get-Content $configPath -Raw | ConvertFrom-Json $existing = @($cfg.Unlocked | ForEach-Object { $_.Name }) $toAdd = @($names | Where-Object { $existing -inotcontains $_ }) "Already in CVRManager: $($existing.Count). To add: $($toAdd.Count)$(if ($toAdd.Count) { ' (' + ($toAdd -join ', ') + ')' })" if ($toAdd.Count -eq 0) { 'Nothing to do.'; return } if (-not $Apply) { ''; 'Dry run only. Re-run with -Apply to add them.'; return } $backup = "$configPath.bak-$(Get-Date -Format 'yyyyMMdd-HHmmss')" Copy-Item $configPath $backup "Config backed up to $backup" $merged = @($cfg.Unlocked) + @($toAdd | ForEach-Object { [pscustomobject]@{ Name = $_; Code = '' } }) $cfg.Unlocked = @($merged) [IO.File]::WriteAllText($configPath, ($cfg | ConvertTo-Json -Depth 30), (New-Object Text.UTF8Encoding($false))) "Added $($toAdd.Count) branch name(s). Start CVRManager again."