Learning project — This was built as a hands-on exercise to understand how binary file formats work: reading/writing little-endian integers, IEEE-754 floats, length-prefixed strings, and locating data by byte-pattern signatures rather than hardcoded offsets. The
.cms21bsave format made for a good real-world target since it's complex enough to be interesting but small enough to reverse-engineer without tooling.
A save-file editor for Car Mechanic Simulator 2021 that runs entirely in your browser — no installation, no Python, no server.
Always back up your save files before editing. Default save location on Windows:
%USERPROFILE%\AppData\LocalLow\Red Dot Games\Car Mechanic Simulator 2021\Saves\
The easiest way — just open the link, drag in your save, make edits, and download.
Your save file never leaves your computer. Everything is processed locally in the browser.
| Category | What you can edit |
|---|---|
| Player stats | Money (credits), level, XP, available skill points |
| Skills | Individual skill purchased/unlocked state, per-tier flags |
| Garage upgrades | Lock / unlock any of the 23 garage items (dyno, paint shop, OBD scanner, lifter, etc.) |
| Parts | Per-part condition (0–100%), bulk repair all worn parts |
- Open the editor in your browser.
- Drag and drop your
.cms21bsave file onto the drop zone, or click Browse file… to pick it. - Edit what you want across the Overview, Garage, and Parts tabs.
- Click ↓ Download .cms21b to save the modified file.
- Copy the downloaded file back to your saves folder, replacing the original.
| Platform | Path |
|---|---|
| Windows (Steam/Epic) | %USERPROFILE%\AppData\LocalLow\Red Dot Games\Car Mechanic Simulator 2021\Saves\ |
| Windows (Xbox Game Pass) | %LOCALAPPDATA%\Packages\RedDotGames.CarMechanicSimulator2021_h6vh6...\SystemAppData\wgs\ |
| Linux (Steam) | ~/.steam/steam/userdata/<id>/1190000/remote/ |
The Game Pass version uses the Windows Game Save (WGS) system, which is different from the standard Steam version:
- Hash Filenames: Files in the
wgsfolder have no extensions and use long hexadecimal names (e.g.,551DBD...). - Identification: To find your save, look for the largest file (typically 8KB–30KB) in the most recently modified subfolder.
- Cloud Sync Override: Xbox will often overwrite your modified file with the cloud version. To prevent this:
- Disconnect from the Internet.
- Replace the original hash file with your edited one (Keep the original hash name!).
- Start the game while offline.
- Once at the main menu, Reconnect to the Internet.
- Make a small ingame change (e.g., move a car) to trigger a sync.
- If prompted, choose "Use Local Save".
Profiles are named profile0.cms21b, profile1.cms21b, etc.
You only need Node.js 18+. No Python required.
git clone https://github.com/your-username/cms2021-save-editor.git
cd cms2021-save-editor/web
npm install
npm run devThen open http://localhost:5173 in your browser.
cd web
npm run buildThe web/dist/ folder is a self-contained static site — host it anywhere (GitHub Pages, Netlify, Cloudflare Pages, etc.).
- Push the repository to GitHub.
- Go to Settings → Pages and set the source to GitHub Actions.
- Create
.github/workflows/deploy.yml:
name: Deploy to GitHub Pages
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install
working-directory: web
- run: npm run build
working-directory: web
- uses: actions/upload-pages-artifact@v3
with:
path: web/dist
- uses: actions/deploy-pages@v4Every push to main will automatically rebuild and publish the site.
cms2021-save-editor/
└── web/ # The entire application (static React + Vite)
├── src/
│ ├── codec.js # Binary decoder / encoder (pure JS, no dependencies)
│ ├── App.jsx # Main app — state, file handling, download
│ ├── App.css
│ └── components/
│ ├── DropZone.jsx
│ ├── Overview.jsx
│ ├── Garage.jsx
│ └── Parts.jsx
├── index.html
├── vite.config.js
└── package.json
decode.pyandeditor.pyin the repo root are a legacy Python CLI tool for power users who prefer the command line. They are not needed to run the web app.
The .cms21b format uses little-endian integers, IEEE-754 floats, and length-prefixed UTF-8 strings (1-byte length + data). See web/src/codec.js for the full annotated implementation.
File structure:
Magic 8 bytes "PJOOOTER"
Header block variable profile name, date, version, 0xff padding
Parts section variable per-part binary records (47 / 63 / 64 bytes each)
Tail section variable skills, garage state, car records, player stats
Key offsets in the tail section (all dynamic — located by byte-pattern search):
| Field | How it's found |
|---|---|
| Stats block (money/level/XP) | Last occurrence of 5c fe ff ff 00 00 f0 41, then +17 bytes |
| Money | stats_offset + 0, uint32 LE |
| Level | stats_offset + 4, uint32 LE, stored as level - 1 |
| XP | stats_offset + 8, uint32 LE |
| Skill points | garage_offset − 4, uint32 LE |
| Garage section | First occurrence of 17 00 00 00 09 paintshop; 23 items × 8 bytes; byte[0] = tier/purchase state |
| Skills section | First occurrence of fast_movement, back up to the preceding uint32 count |
Pull requests are welcome. If you discover new fields or format details, please document them in the PR description.
To run a round-trip sanity check in the browser console:
import { decode, encode } from './src/codec.js'
const buf = await fetch('profile0.cms21b').then(r => r.arrayBuffer())
const save = decode(buf)
const reencoded = encode(save)
console.assert(
new Uint8Array(buf).every((b, i) => b === reencoded[i]),
'Round-trip mismatch!'
)
console.log('Round-trip OK')MIT — see LICENSE file.