Master the fundamental concepts of emulation through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksSave states serialize entire machine RAM, registers, and peripheral state so players jump backward instantly. Unlike battery saves (just game SRAM), snapshots freeze the full hardware at one instant.
Minimum payload:
Load must restore bit-identical state or games desync. Version your snapshot format for when you add new chips.
For example, saving mid-jump in Super Mario Bros. requires capturing PPU scroll latch and OAM positions, not just CPU registers.
\`\`\`c fwrite(cpu, sizeof(cpu), 1, fp); fwrite(ram, ram_size, 1, fp); fwrite(&ppu, sizeof(ppu), 1, fp); \`\`\`
Include a hash of the loaded ROM in snapshots so loading state against the wrong game fails fast. Silent mismatches produce ghosts that are miserable to debug.
This exercise asks you to design save/load for your emulator's CPU and memory subsystems. You will implement serialization that restores identical execution state across save and load cycles.
You will use the same mental model here when reading production interpreter source later in the track. Sketch one concrete input on paper, predict the outcome, then confirm with code. That discipline catches logic errors early and makes debugging far faster when you extend the implementation in follow-on tasks.
Add save states to an emulator: serialize the machine into a versioned, chunked, checksummed binary image, and load it back safely. The loader rejects corrupt, truncated, foreign-ROM and future-version images, migrates old versions, and skips chunks it doesn't know. The machine is small: A, X, Y, SP (8-bit), PC (16-bit), a 32-bit frame counter and 256 bytes of RAM. Power-on values are SP=FD and PC=8000, with everything else 0.
| offset | size | field |
|---|---|---|
| 0 | 4 | magic 53 53 54 1A ("SST\x1A") |
| 4 | 2 | version (1 or 2) |
| 6 | 4 | CRC32 of the loaded ROM |
| 10 | 4 | payload length P |
| 14 | P | chunks: 4-byte tag, u32 length, data |
| 14+P | 4 | CRC32 of bytes 0 .. 14+P-1 |
CPU (A, X, Y, SP, PC as u16), then RAM (256 bytes), then, in version 2 only, TIME (the frame as u32).0xEDB88320, with init and final XOR 0xFFFFFFFF.One command per line (; starts a comment):
cLoading…
Check, in this order:
truncated.bad magic.unsupported version N.truncated.CRC mismatch (stored X, computed Y).ROM mismatch (state was made with X, loaded ROM is Y).truncated. Known tags with the right length are applied, and anything else counts as a skipped unknown chunk. Missing CPU or RAM gives missing CPU chunk / missing RAM chunk.On failure, print load S: REASON; state unchanged. Nothing is modified. A version 1 image has no TIME chunk, so the frame becomes 0.
cLoading…
all zero. dump prefixes a %04d offset.load S: slot is emptyset: unknown field Kpoke: ADDR VALUE (hex, 00-FF)save: SLOT (0-3) [v1], load: SLOT (0-3)dump: SLOT OFFSET LENGTH (1-32), dump: slot S has N bytespatch: SLOT OFFSET VALUE, patch: slot S has N bytestruncate: SLOT LENGTHappend: SLOT TAG(4 chars) HEXBYTESunknown command XInput:
cLoading…
Output:
cLoading…
fwrite a struct, because padding and endianness would make the format depend on the compiler.Hidden tests cover the byte layout, CRC detection of a flipped byte, ROM mismatch, a v1 image with migration, unknown chunks skipped, truncation, bad magic, an empty slot, and command errors.