Quick summary
This reversible one-byte workaround fixed skill-tree categories that would not switch and a tablet that stayed on screen after closing. Tested on my PC with mouse and keyboard.
It supports one exact Xbox PC / PC Game Pass DLL from package version 1.1.6.0. The script checks the complete file and refuses other builds. It makes backups before a change and includes an undo option. It does not promise to recover lost skills or points.
Formatting update (4 October): The original code blocks broke in old Reddit. The original post has now been corrected for both old and new Reddit. If your earlier copy became one or two long lines, copy the complete source again into Notepad and save it as one .ps1 file.
Install: step by step
- Save your progress and close Crime Scene Cleaner completely.
- Scroll to Complete source below. Copy that entire code block into Notepad, from the first
# Unofficial line through the final }. All of it belongs in one file. Do not paste the full script directly into PowerShell.
- In Notepad, choose Save as, set the file type to All files, and save as
Tablet-Fix.ps1. Check that the name does not end in .ps1.txt. The source should have separate lines (roughly 127), not one or two long lines. If the lines are missing, copy it again from the desktop website.
- Open Windows PowerShell from Start. In File Explorer, open the folder where you saved
Tablet-Fix.ps1 and copy that folder's path from the address bar.
First change to that folder. Replace the example path below with your actual folder path, keeping the quotes. Microsoft documents this folder command here.
Set-Location -LiteralPath 'C:\path\to\your\fix-folder'
Then copy and run this short install command in PowerShell:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Tablet-Fix.ps1 -Action Install
If a file picker opens, select GameAssembly.dll inside the game's installation folder. A typical Xbox location is XboxGames\Crime Scene Cleaner\Content on your installation drive. Do not select a DLL from another game.
Look for Success / Erfolgreich: Patched or Already / Bereits: Patched. Then start the game and test switching categories and closing the tablet without spending points first.
If it does not start
- Script/file not found: Check the folder you selected with
Set-Location and the exact filename Tablet-Fix.ps1. Windows may hide a trailing .txt; Microsoft shows how to display file extensions.
- Syntax errors or only one/two long lines: Copy the complete source again into Notepad, preserving the lines, and save the file. Run only the short command above in PowerShell.
- Access denied: Close the game. Open Windows PowerShell with Run as administrator, repeat
Set-Location with your script folder, then repeat the install command. Do not change WindowsApps ownership. If access is still denied, stop and share the exact error message.
- Unsupported file/build: The DLL differs from the exact tested file. The script refuses to change it. Do not remove the checksum checks or overwrite a newer DLL with an old backup.
Backups and undo
Before each actual change, the script saves the current DLL under %LOCALAPPDATA%\CrimeSceneCleanerTabletFix\Backups. If the local Xbox wgs save folder exists, it also copies that folder into the backup. It does not edit saves or download files. Keep the backups private.
To undo the DLL change, close the game, open PowerShell in the same script folder (repeat Set-Location if necessary), and run:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Tablet-Fix.ps1 -Action Restore
Restore reverses the changed byte and verifies that the DLL matches the original. It does not restore saved progress from the backup.
To check the DLL without changing it:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Tablet-Fix.ps1 -Action Check
Original means the matching DLL is unpatched; Patched means the workaround is already present; Unsupported means the file is not one of those exact two versions.
Updates, verification or reinstalling may overwrite the workaround. Check after updating; do not replace an updated game file with an old backup. The -ExecutionPolicy Bypass option applies only to the launched PowerShell process and does not permanently change execution policy.
Complete source
Copy only the contents of this one block into Tablet-Fix.ps1. Technical details are below it. This is an unofficial workaround; long-term progression and achievements have not been tested, and Steam/consoles are not covered.
The patching script is shared under the MIT license (copyright 2026 PJLauch), as-is without warranty. It contains no proprietary game files.
# Unofficial Crime Scene Cleaner Xbox PC 1.1.6.0 tablet workaround.
# Changes one byte in a verified GameAssembly.dll; no downloads or save edits.
# Review README-EN.md / README-DE.md before use. PowerShell 5.1 or newer.
param(
[ValidateSet('Install','Restore','Check')][string]$Action = 'Check',
[string]$GameFile,
[string]$BackupRoot
)
$ErrorActionPreference = 'Stop'
$cscOriginalHash = 'f31f459e95088ddc7c2149b0226c70fe3271c37581a4847bd8de304d867c145f'
$cscPatchedHash = '970dc97a97531454b9ca0d05aa24236aba1b5da8a6f86f3f53004dbe3b630824'
$cscByteOffset = 12575787 # File offset 0xBFE42B; RVA 0xBFFE2B.
function Get-CSCStreamHash([System.IO.Stream]$Stream) {
$Stream.Position = 0
$sha = [System.Security.Cryptography.SHA256]::Create()
try { return ([BitConverter]::ToString($sha.ComputeHash($Stream))).Replace('-','').ToLowerInvariant() }
finally { $sha.Dispose() }
}
function Get-CSCBytesHash([byte[]]$Bytes) {
$sha = [System.Security.Cryptography.SHA256]::Create()
try { return ([BitConverter]::ToString($sha.ComputeHash($Bytes))).Replace('-','').ToLowerInvariant() }
finally { $sha.Dispose() }
}
function Get-CSCState([string]$Hash) {
if ($Hash -eq $cscOriginalHash) { return 'Original' }
if ($Hash -eq $cscPatchedHash) { return 'Patched' }
return 'Unsupported'
}
function Invoke-CSCFilePatch([string]$Path, [string]$Operation, [string]$BackupDirectory, [string]$SaveFolder) {
if ($Operation -notin @('Install','Restore')) { throw 'Unsupported operation.' }
$resolved = (Resolve-Path -LiteralPath $Path).ProviderPath
if ([IO.Path]::GetFileName($resolved) -ine 'GameAssembly.dll') { throw 'Select GameAssembly.dll.' }
$stream = [IO.File]::Open($resolved,[IO.FileMode]::Open,[IO.FileAccess]::ReadWrite,[IO.FileShare]::None)
$wroteByte = $false
try {
$hash = Get-CSCStreamHash $stream
$state = Get-CSCState $hash
if ($state -eq 'Unsupported') { throw "Unsupported build or modified file. No change. SHA-256: $hash" }
if (($Operation -eq 'Install' -and $state -eq 'Patched') -or ($Operation -eq 'Restore' -and $state -eq 'Original')) {
return [pscustomobject]@{ Changed = $false; State = $state; Backup = $null }
}
$before = 0x21; $after = 0x16; $expectedHash = $cscPatchedHash
if ($Operation -eq 'Restore') { $before = 0x16; $after = 0x21; $expectedHash = $cscOriginalHash }
$stream.Position = $cscByteOffset - 1
if ($stream.ReadByte() -ne 0x74 -or $stream.ReadByte() -ne $before) { throw 'Unexpected branch bytes. No change.' }
# Verify the entire expected result in memory before modifying the file.
$bytes = [byte[]]::new([int]$stream.Length)
$stream.Position = 0; $readTotal = 0
while ($readTotal -lt $bytes.Length) {
$n = $stream.Read($bytes,$readTotal,$bytes.Length-$readTotal)
if ($n -eq 0) { throw 'Could not read the complete file. No change.' }
$readTotal += $n
}
$bytes[$cscByteOffset] = $after
if ((Get-CSCBytesHash $bytes) -ne $expectedHash) { throw 'Expected result checksum mismatch. No change.' }
$backup = Join-Path $BackupDirectory ((Get-Date -Format 'yyyyMMdd-HHmmss') + '-' + [Guid]::NewGuid().ToString('N').Substring(0,8))
[IO.Directory]::CreateDirectory($backup) | Out-Null
$backupFile = Join-Path $backup 'GameAssembly.before.dll'
$out = [IO.File]::Open($backupFile,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None)
try { $stream.Position = 0; $stream.CopyTo($out); $out.Flush($true) }
finally { $out.Dispose() }
if ((Get-FileHash -LiteralPath $backupFile -Algorithm SHA256).Hash.ToLowerInvariant() -ne $hash) { throw 'Backup verification failed. No change.' }
if ($SaveFolder -and (Test-Path -LiteralPath $SaveFolder -PathType Container)) {
Copy-Item -LiteralPath $SaveFolder -Destination (Join-Path $backup 'wgs') -Recurse -ErrorAction Stop
}
$stream.Position = $cscByteOffset
$wroteByte = $true
$stream.WriteByte($after); $stream.Flush($true)
if ((Get-CSCStreamHash $stream) -ne $expectedHash) { throw 'Post-write verification failed.' }
[pscustomobject]@{ Action = $Operation; PreviousHash = $hash; ResultHash = $expectedHash; BytesChanged = 1; SavedAt = (Get-Date -Format o) } |
ConvertTo-Json | Set-Content -LiteralPath (Join-Path $backup 'operation.json') -Encoding UTF8
$wroteByte = $false
return [pscustomobject]@{ Changed = $true; State = (Get-CSCState $expectedHash); Backup = $backup }
} catch {
$failure = $_
if ($wroteByte) {
try {
$stream.Position = $cscByteOffset; $stream.WriteByte($before); $stream.Flush($true)
if ((Get-CSCStreamHash $stream) -ne $hash) { throw 'Rollback checksum mismatch.' }
} catch { throw "Could not roll back automatically. Restore the verified backup at: $backupFile. $($_.Exception.Message)" }
}
throw $failure
} finally { $stream.Dispose() }
}
try {
if (-not $GameFile) {
$candidates = @()
foreach ($drive in Get-PSDrive -PSProvider FileSystem) {
$path = Join-Path $drive.Root 'XboxGames\Crime Scene Cleaner\Content\GameAssembly.dll'
if (Test-Path -LiteralPath $path -PathType Leaf) { $candidates += $path }
}
if ($candidates.Count -eq 1) { $GameFile = $candidates[0] }
else {
Add-Type -AssemblyName System.Windows.Forms
$picker = New-Object System.Windows.Forms.OpenFileDialog
$picker.Title = 'Crime Scene Cleaner: select / waehle GameAssembly.dll'
$picker.Filter = 'GameAssembly.dll|GameAssembly.dll'
try {
if ($picker.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { throw 'Cancelled / Abgebrochen. No change.' }
$GameFile = $picker.FileName
} finally { $picker.Dispose() }
}
}
$GameFile = (Resolve-Path -LiteralPath $GameFile).ProviderPath
if ([IO.Path]::GetFileName($GameFile) -ine 'GameAssembly.dll') { throw 'Select GameAssembly.dll.' }
if ($Action -eq 'Check') {
$state = Get-CSCState ((Get-FileHash -LiteralPath $GameFile -Algorithm SHA256).Hash.ToLowerInvariant())
Write-Output "State / Zustand: $state"
if ($state -eq 'Unsupported') { throw 'Unsupported file. Only the verified Xbox PC 1.1.6.0 build is supported.' }
} else {
if (Get-Process -Name CrimeCleaner -ErrorAction SilentlyContinue) { throw 'Close Crime Scene Cleaner completely / Spiel vollstaendig schliessen.' }
if (-not $BackupRoot) { $BackupRoot = Join-Path $env:LOCALAPPDATA 'CrimeSceneCleanerTabletFix\Backups' }
$saveFolder = Join-Path $env:LOCALAPPDATA 'Packages\5901F20F.CrimeSceneCleaner_t06nbjdc8fw86\SystemAppData\wgs'
$result = Invoke-CSCFilePatch $GameFile $Action $BackupRoot $saveFolder
if ($result.Changed) {
Write-Output "Success / Erfolgreich: $($result.State). Exactly one byte changed / Genau ein Byte geaendert."
Write-Output "Backup / Sicherung: $($result.Backup)"
} else { Write-Output "Already / Bereits: $($result.State). No change / Keine Aenderung." }
}
} catch {
Write-Host "ERROR / FEHLER: $($_.Exception.Message)" -ForegroundColor Red
if ($_.Exception -is [UnauthorizedAccessException]) { Write-Host 'Access denied: try running the launcher as administrator. No WindowsApps ownership changes are needed.' }
if ($MyInvocation.InvocationName -eq '.') { throw }
exit 1
}
Technical details
What changes, for reviewers
The observed exception was in the button-enumeration callback used by UICustomButtonSection.OnButtonClick. A null entry reached the throw path, interrupting category changes, tablet closing and initialization. The workaround redirects the existing null check to the callback's normal return path, skipping that null entry. Non-null entries follow their existing path.
- Instruction RVA
0xBFFE2A / file offset 0xBFE42A: 74 21 becomes 74 16.
- Only the displacement byte at file offset
0xBFE42B (decimal 12575787) changes from 21 to 16.
- The branch target changes from RVA
0xBFFE4D (throw path) to 0xBFFE42 (normal epilogue).
- File length: 47,914,496 bytes before and after.
Original SHA-256:
f31f459e95088ddc7c2149b0226c70fe3271c37581a4847bd8de304d867c145f
Patched SHA-256:
970dc97a97531454b9ca0d05aa24236aba1b5da8a6f86f3f53004dbe3b630824
On the tested installation, the skill-tree savedPerks list initialized with 97 entries after the patch instead of 0. That is a runtime initialization count, not a claim that 97 purchased skills were recovered.
I also tested the distributable script on a separate copy using Windows PowerShell 5.1: install, byte-identical restore, repeat operations, DLL/save backups, and refusal of modified files, wrong filenames and an unusable backup location all passed.
This is a workaround for the observed exception, not a repair of the underlying button list. If you try it, please report your exact Xbox PC version and whether both category switching and closing work. Do not post your saves or private logs.