<# Builds a friend-ready Radicade August 2023 client bundle from a WORKING install. It reads the source install, verifies every required piece against the reference hashes, copies them to -OutDir, drops the triage script and a README in with them, and optionally zips the result. Nothing in -GameDir is ever modified. Usage: powershell -ExecutionPolicy Bypass -File make-august-client-pack.ps1 -OutDir "D:\radicade-pack" powershell -ExecutionPolicy Bypass -File make-august-client-pack.ps1 -OutDir "D:\radicade-pack" -Zip Verification is fail-closed: if a piece is unpatched or a different build, the script stops and names it instead of producing a bundle that cannot work. #> [CmdletBinding()] param( [string]$GameDir, [Parameter(Mandatory = $true)][string]$OutDir, [switch]$Zip, [switch]$Force ) $ErrorActionPreference = 'Stop' # Reference values: the install that logs in and loads rooms. $expected = @{ AssemblySize = 167390208 AssemblyMd5 = '11e14eb35671e2725bd0c78a6a91bb48' MetadataSize = 38654794 MetadataMd5 = 'cc9c32014b5fc3dd1a2a8fb12376be15' InteropSize = 38654794 InteropMd5 = '4a1a28078f957af470f3b31951b32648' BootSize = 173 BootMd5 = 'dc2e5f7af7d5df736a4cc3477b4b1a1f' WinhttpSize = 26112 WinhttpMd5 = 'b2a4d0cb04bf8f5a27ccab237ecc2586' LoaderSize = 60928 LoaderMd5 = '9430bb84349d52b753a181788a86c5e2' PluginSize = 62464 PluginMd5 = '8718a45dd42d7cb72b3e46e04b744d91' InteropCount = 335 } function Get-Md5 { param([string]$Path) return (Get-FileHash -LiteralPath $Path -Algorithm MD5).Hash.ToLower() } function Assert-Piece { param( [string]$Label, [string]$Path, [long]$Size, [string]$Md5 ) if (-not (Test-Path -LiteralPath $Path)) { throw "$Label is missing: $Path" } $info = Get-Item -LiteralPath $Path $actual = Get-Md5 $Path if ($info.Length -ne $Size -or $actual -ne $Md5) { $detail = "$Label does not match the reference: {0} b md5 {1} (expected {2} b md5 {3})" -f $info.Length, $actual, $Size, $Md5 if ($Force) { Write-Host ("WARNING: " + $detail) } else { throw ($detail + ' - fix it or pass -Force if you know this build is fine.') } } else { Write-Host (" ok {0}" -f $Label) } } # --- locate the source install ------------------------------------------------ if (-not $GameDir) { $candidates = @( (Join-Path $env:USERPROFILE 'Downloads\2023-08-29T06-53-33Z') ) $downloads = Join-Path $env:USERPROFILE 'Downloads' if (Test-Path -LiteralPath $downloads) { $candidates += (Get-ChildItem -LiteralPath $downloads -Directory -ErrorAction SilentlyContinue | Where-Object { Test-Path -LiteralPath (Join-Path $_.FullName 'RecRoom.exe') } | ForEach-Object { $_.FullName }) } foreach ($c in $candidates) { if (Test-Path -LiteralPath (Join-Path $c 'BepInEx\plugins\RadicadeAugustClient.dll')) { $GameDir = $c; break } } } if (-not $GameDir) { $GameDir = Read-Host 'Path to the patched Rec Room folder (the one that works)' } $GameDir = $GameDir.TrimEnd('\') $metadataDir = Join-Path $GameDir 'Recroom_Data\il2cpp_data\Metadata' $bepInEx = Join-Path $GameDir 'BepInEx' Write-Host 'Verifying the source install against the reference hashes...' Assert-Piece 'GameAssembly.dll (build identity)' (Join-Path $GameDir 'GameAssembly.dll') $expected.AssemblySize $expected.AssemblyMd5 Assert-Piece 'global-metadata.dat (host literals patched)' (Join-Path $metadataDir 'global-metadata.dat') $expected.MetadataSize $expected.MetadataMd5 Assert-Piece 'global-metadata.interop.dat' (Join-Path $metadataDir 'global-metadata.interop.dat') $expected.InteropSize $expected.InteropMd5 Assert-Piece 'Recroom_Data\boot.config' (Join-Path $GameDir 'Recroom_Data\boot.config') $expected.BootSize $expected.BootMd5 Assert-Piece 'winhttp.dll (Doorstop 4.3.0)' (Join-Path $GameDir 'winhttp.dll') $expected.WinhttpSize $expected.WinhttpMd5 Assert-Piece 'BepInEx.Unity.IL2CPP.dll (patched loader)' (Join-Path $bepInEx 'core\BepInEx.Unity.IL2CPP.dll') $expected.LoaderSize $expected.LoaderMd5 Assert-Piece 'RadicadeAugustClient.dll' (Join-Path $bepInEx 'plugins\RadicadeAugustClient.dll') $expected.PluginSize $expected.PluginMd5 if (-not (Test-Path -LiteralPath (Join-Path $GameDir 'doorstop_config.ini'))) { throw 'doorstop_config.ini is missing' } $interopCount = (Get-ChildItem -LiteralPath (Join-Path $bepInEx 'interop') -File -ErrorAction SilentlyContinue).Count if ($interopCount -lt $expected.InteropCount) { throw ("BepInEx\interop holds {0} assemblies, expected at least {1} - interop generation was incomplete" -f $interopCount, $expected.InteropCount) } Write-Host (" ok BepInEx\interop ({0} assemblies)" -f $interopCount) Write-Host 'Source install verified.' Write-Host '' # --- assemble the bundle ------------------------------------------------------ $bundleRoot = Join-Path $OutDir 'RadicadeAugustClient' if (Test-Path -LiteralPath $bundleRoot) { if ($Force) { Remove-Item -LiteralPath $bundleRoot -Recurse -Force } else { throw "$bundleRoot already exists - delete it or pass -Force to replace it." } } New-Item -ItemType Directory -Path $bundleRoot -Force | Out-Null Write-Host ("Copying into {0} ..." -f $bundleRoot) # Root-level loader pieces and the patched data files. Copy-Item -LiteralPath (Join-Path $GameDir 'winhttp.dll') -Destination $bundleRoot Copy-Item -LiteralPath (Join-Path $GameDir 'doorstop_config.ini') -Destination $bundleRoot foreach ($name in @('.doorstop_version', 'changelog.txt')) { $p = Join-Path $GameDir $name if (Test-Path -LiteralPath $p) { Copy-Item -LiteralPath $p -Destination $bundleRoot } } $dataTarget = Join-Path $bundleRoot 'Recroom_Data' New-Item -ItemType Directory -Path $dataTarget -Force | Out-Null Copy-Item -LiteralPath (Join-Path $GameDir 'Recroom_Data\boot.config') -Destination $dataTarget foreach ($name in @('radicade-patch.json')) { $p = Join-Path $GameDir ('Recroom_Data\' + $name) if (Test-Path -LiteralPath $p) { Copy-Item -LiteralPath $p -Destination $dataTarget } } $metaTarget = Join-Path $dataTarget 'il2cpp_data\Metadata' New-Item -ItemType Directory -Path $metaTarget -Force | Out-Null foreach ($name in @('global-metadata.dat', 'global-metadata.interop.dat')) { Copy-Item -LiteralPath (Join-Path $metadataDir $name) -Destination $metaTarget } # The whole BepInEx tree, minus logs, sidecar backups and caches. $bepTarget = Join-Path $bundleRoot 'BepInEx' # Excluded: logs, sidecar backups of previous builds, and the diagnostic RVA map the # plugin writes (18 MB of local crash-analysis output). robocopy $bepInEx $bepTarget /E /NFL /NDL /NJH /NJS /NP ` /XF 'LogOutput.log' '*.log' '*.before-*' '*.tracer-*' '*.march-backup' '*.baseline-disabled' ` 'radicade-addr-map.txt' | Out-Null if ($LASTEXITCODE -ge 8) { throw ("robocopy failed with exit code {0}" -f $LASTEXITCODE) } $global:LASTEXITCODE = 0 # The triage script travels with the bundle so the friend can produce a report. $triage = Join-Path $PSScriptRoot 'august-client-triage.ps1' if (Test-Path -LiteralPath $triage) { Copy-Item -LiteralPath $triage -Destination $bundleRoot } $assemblyInfo = Get-Item -LiteralPath (Join-Path $GameDir 'GameAssembly.dll') $readme = @() $readme += 'Radicade August 2023 client patch' $readme += '' $readme += 'This bundle carries the client half of a Rec Room 2023-08-29 (game version 20230825,' $readme += 'Unity 2020.3.33f1) setup pointed at a Radicade server on YOUR OWN machine.' $readme += '' $readme += 'It only works on the identical game build. Check yours before copying anything:' $readme += (" GameAssembly.dll must be {0} bytes, md5 {1}" -f $assemblyInfo.Length, $expected.AssemblyMd5) $readme += '' $readme += 'Install, into the folder that holds RecRoom.exe:' $readme += ' 1. copy winhttp.dll, doorstop_config.ini and .doorstop_version into the game root' $readme += ' 2. copy BepInEx\ next to them' $readme += ' 3. copy Recroom_Data\boot.config and Recroom_Data\il2cpp_data\Metadata\*.dat over the' $readme += ' existing files (keep copies of the originals; global-metadata.dat.orig is the' $readme += ' patcher''s own backup and must stay untouched)' $readme += ' 4. rename any winhttp.dll.baseline-disabled you already have out of the way' $readme += ' 5. start your Radicade server first - https://localhost:3030 plus http://127.0.0.1:3031,' $readme += ' ASPNETCORE_ENVIRONMENT=Development - and only then launch the game' $readme += '' $readme += 'Why the server has to be local: the patched global-metadata.dat rewrites the client''s' $readme += 'bootstrap literals to https://localhost:3030, so a remote server cannot answer them.' $readme += '' $readme += 'Then check the install with the bundled triage script:' $readme += ' powershell -ExecutionPolicy Bypass -File august-client-triage.ps1' $readme += 'It prints one PASS/MISSING line per required piece and writes august-client-report.txt.' $readme += '' $readme += 'The line "Failed to load Type RecRoom.Analytics.Statsig.StatsigDebugOverrides" in' $readme += 'Player.log is NORMAL and appears in a completely unpatched launch of this build too.' $readme += 'It is never the reason a patched install fails.' $readme += '' $readme += 'Pieces and their reference hashes (the triage script checks these for you):' $readme += ' GameAssembly.dll 167390208 b 11e14eb35671e2725bd0c78a6a91bb48' $readme += ' Recroom_Data/il2cpp_data/Metadata/global-metadata.dat' $readme += ' 38654794 b cc9c32014b5fc3dd1a2a8fb12376be15' $readme += ' Recroom_Data/il2cpp_data/Metadata/global-metadata.interop.dat' $readme += ' 38654794 b 4a1a28078f957af470f3b31951b32648' $readme += ' Recroom_Data/boot.config 173 b dc2e5f7af7d5df736a4cc3477b4b1a1f' $readme += ' winhttp.dll 26112 b b2a4d0cb04bf8f5a27ccab237ecc2586' $readme += ' BepInEx/core/BepInEx.Unity.IL2CPP.dll' $readme += ' 60928 b 9430bb84349d52b753a181788a86c5e2' $readme += ' BepInEx/plugins/RadicadeAugustClient.dll' $readme += ' 62464 b 8718a45dd42d7cb72b3e46e04b744d91' $readme += ' BepInEx/interop/ 335 assemblies' $readme += '' Set-Content -LiteralPath (Join-Path $bundleRoot 'README-FRIEND.txt') -Value ($readme -join "`r`n") -Encoding UTF8 $size = (Get-ChildItem -LiteralPath $bundleRoot -Recurse -File | Measure-Object -Property Length -Sum).Sum Write-Host ('Bundle assembled: {0} ({1:N1} MB)' -f $bundleRoot, ($size / 1MB)) if ($Zip) { $zipPath = $bundleRoot.TrimEnd('\') + '.zip' if (Test-Path -LiteralPath $zipPath) { Remove-Item -LiteralPath $zipPath -Force } Write-Host ('Compressing to {0} (this takes a while)...' -f $zipPath) Compress-Archive -Path (Join-Path $bundleRoot '*') -DestinationPath $zipPath -CompressionLevel Optimal Write-Host ('Zip written: {0} ({1:N1} MB)' -f $zipPath, ((Get-Item -LiteralPath $zipPath).Length / 1MB)) } Write-Host '' Write-Host 'Remind the friend that he still needs the game build above and his own Radicade server.'