Categories
GSoC 2026

WEEK 9

After last week’s big push to get Chamber of the Sci-Mutant Priestess over the finish line, this week was all about MacVenture — turning last week’s “the games boot!” moment into something people can actually install and play.

Getting the games out of Steam

The biggest chunk of work went into the tooling. Last week I explained the awkward situation with the Steam releases: the actual game data isn’t sitting there as loose files, it’s a Mac HFS disk image buried inside a Chromium wrapper executable. Extracting that by hand once, for debugging, is one thing — but nobody buying the games on Steam should have to do that.

So I taught ScummVM’s dumper-companion how to do it. Point it at the Steam .exe, and it now digs the HFS disk image out of the PE resource, punycodes the filenames so they survive on a non-Mac filesystem, and hands you a clean set of game data. What used to be a fragile manual ritual is now a single command.

The Apple IIGS rabbit hole

While I was in there, I noticed the Steam executables actually carry two disk images — the Mac one everybody expects, and an Apple IIGS version tucked away in a separate resource. The IIGS releases have their own look and feel, so it felt wrong to leave them on the table.

Two problems stood in the way. First, the dumper only knew about the Mac disk, so I extended it to pull the IIGS .2mg image as well. Second — and this was the more surprising one — ScummVM’s detection for the IIGS games was quietly dead. The detection entries existed, but there was no data-fork fallback, so the files these games actually ship with never matched anything. I fixed the detection so it hashes the right fork, and suddenly the IIGS versions light up in the launcher like they always should have.

A detour back to Chamber

One small but satisfying fix landed on the Chamber side too. A user reported (bug #17004) that if you had both the CGA and EGA data in the same folder, the engine would stubbornly force CGA regardless of what you picked. It turned out to be a detection-hint issue, and now the engine honors the variant you actually chose.

Testing begins

With the tooling in place, I’ve started the part I’ve been looking forward to: real playthroughs. Déjà Vu is first up — the noir detective one, the natural place to start — and so far it’s going really well. The window manager behaves, items drag and drop where they should, the text scrolls cleanly. No showstoppers yet, which is exactly what you want to see from the first of four games.

Next week

The plan is to keep grinding through the playthroughs — finish Déjà Vu, then move on to Déjà Vu II, Uninvited, and Shadowgate — noting anything that misbehaves along the way. If they hold up like Déjà Vu has, the goal is to get MacVenture’s Steam and IIGS releases marked as testing so players can start reporting back.

As always, thanks to my mentors for the steady guidance — onward to the testing grind!

Categories
GSoC 2026

WEEK 8

Last week I was still deep in the reverse-engineering weeds of the Amiga port. This week the mood changed completely: it was the week Chamber of the Sci-Mutant Priestess finally stepped out into the light, and the week I started the work on a second engine called MacVenture.

Chamber goes to testing

The headline first: Chamber is now enabled for the upcoming ScummVM release and marked as testing. After all the months of decoding scripts, chasing endianness bugs, and rebuilding renderers one palette at a time, it felt genuinely strange to finally flip the switch.

The change enables the engine to build by default and promotes all five game variants — the multi-language CGA build, the US CGA build, the EGA build, and both Amiga builds (EU and US) — from ADGF_UNSTABLE to ADGF_TESTING. That last part matters: it means the games now show up for players who want to try them and report issues, instead of being hidden behind a developer flag.

It also carried a couple of last-minute fixes I wanted in before the release:

  • The endgame saucer animation was accumulating frames on the linear back buffer in EGA and Amiga modes, leaving a smeared trail instead of a clean animation. Fixed.

  • Hercules mode mouse mapping was off, so hotspot detection didn’t line up with what you saw on screen. Corrected the coordinate mapping.

And, on a more personal note, my name went into the credits with this work. Small thing on a diff, big thing for me.

A second engine: the MacVenture Steam versions

With Chamber wrapped up for release, my mentor pointed me at the next target: getting the MacVenture games — Shadowgate, Déjà Vu, Déjà Vu II, and Uninvited — running from their Steam releases, so we can eventually announce those for testing too, exactly like we just did with Chamber.

I expected to spend the week playing games. Instead I spent the first part of it doing detective work, because the Steam builds are not what you’d think.

Where is the game?

Each Steam download is basically a small Chromium (CEF) web wrapper: a .exe and a few DLLs. There are no loose game files anywhere. It turns out these are the “MacVenture Series” web ports, and the actual game data — the original Macintosh files, resource forks and all — is stored as an 800K Mac HFS disk image embedded as a resource inside the .exe.

So the pipeline to feed ScummVM became:

  1. Pull the disk image (.dsk) out of the PE resources in the executable.

  2. Run it through ScummVM’s own dumper-companion tool, which extracts the Mac files and, crucially, punyencodes the filenames. That’s the part that lets a name like Déjà Vu survive as xn--Dj Vu-sqa5d on a normal filesystem while ScummVM still recognizes it.

The nice surprise: once extracted, all four games were detected immediately as the existing “1993 rerelease” entries — the resource forks are identical — so no detection work was needed at all.

The crash that blocked everything

The bad surprise: all four games crashed on startup with an assertion failure the moment the GUI tried to initialize.

Assertion 'isValidRect()' failed (common/rect.h:201)

This one was a proper rabbit hole, and a good reminder of how far an uninitialized value can travel before it hurts you. Tracing it back through the window manager and the nine-patch border renderer, the story was:

  • MacVenture builds its window border offsets in borderOffsets(), setting every field of the BorderOffsets struct explicitly… except a newer field, titlePadding, which was never assigned.

  • That garbage value fed straight into the console window’s title width calculation.

  • In nine_patch.cpp, the border width is computed as dw = _h._fix + _titleWidth. With a garbage title width of ~32,800, dw overflowed to 32,864.

  • That produced an invalid Common::Rect (its right edge wrapped past its left edge), which tripped the assertion and aborted the game.

The fix is a single line — initialize titlePadding to 0 alongside the other offsets — but finding it meant instrumenting the whole border-drawing path to watch the bad number appear. And because it’s a genuine upstream bug, the fix helps the ordinary 1993 rereleases too, not just the Steam ones.

After that, all four games boot and run cleanly.

Reflection and what’s next

Two milestones in one week: Chamber crossing the finish line into testing, and MacVenture going from “won’t even start” to “boots all four games.” I owe a big thank-you to my mentor, sev, for all the help and patience.

Next week is the fun part I thought I’d be doing this week: actual playthroughs of all four MacVenture games. I’ll be watching closely for the Window Manager quirks, the lasso/marquee item selection, and the console text scrolling — the areas most likely to still hide bugs. Once those hold up, we’ll document the extraction steps and announce MacVenture for testing as well.

See you next week.

Categories
GSoC 2026

WEEK 7

Hello, everyone! With both the European and US Amiga releases of Kult / Chamber of the Sci-Mutant Priestess playable from start to finish, this week I shifted focus from adding features to hardening what was already there. Last week I mentioned that ScummVM runs the engine through the Coverity and PVS-Studio static analysers, and that their reports had turned up a handful of issues worth looking at. This week I went through those reports properly, and on top of that I chased down a save/load bug that had been quietly breaking a couple of rooms after loading a game.

Working through the Coverity report

Static analysers are good at spotting the kind of code that is technically wrong but rarely bites in day-to-day play: unreachable branches, resources that are allocated but never freed, and fields that are read before they are written. Chamber is a reimplementation of a 1989 DOS game, so a lot of the code mirrors the original binary very closely — which means some of these findings are actually faithful reproductions of dead code that was already dead in the original.

That distinction mattered while reviewing the reports, because I did not want to “fix” something that was deliberately mirroring the disassembly. So for each finding I went back to the original code to confirm what was really going on before touching anything.

A few of the more interesting ones:

  • In CMD_21_VortTalk, Coverity flagged a branch guarded by rand_value >= 170 as unreachable. Checking the disassembly, the num == 7 case it belonged to is dead in the original too, so the branch could go.

  • In drawRoomStatics, there was a door check for index == 91, but by the time that code runs the index is already constrained to the 50..60 range, so the check can never be true.

  • In the CGA blitter, blitToScreen had an endY > 200 clamp that could never fire, because dy and h are hardcoded to 0 and 200 just above it.

Alongside the dead code, the report caught two genuinely useful things: a memory leak and an uninitialised field. Several call sites were calling loadFond / ega_loadFond / loadSplash, which return a Graphics::Surface, and then throwing that surface away without freeing it — a small leak every time a room background or splash was loaded. And the engine’s _speaker member was not being initialised in the constructor. Both are the sort of thing that is easy to miss by eye and exactly what these tools are for.

Working through the PVS-Studio report

PVS-Studio leans towards a different class of finding — dead stores and redundant expressions — and it caught a similar mix:

  • Another impossible clamp in the CGA blitter, this time endX_bytes > 80 on the horizontal axis, again dead because the width is hardcoded to a full 320 pixels.

  • A dead ofs++ in cga_ZoomInplace, immediately followed by ofs being reassigned to oofs, so the increment never had any effect.

  • In SCR_23_HidePortrait, a left/right button branch where both arms were identical — this handler simply has no special behaviour to skip, so the branch collapsed to a single path.

  • In SCR_46_DeProfundisLowerHook, the return value of getPuzzlSprite() was being stored into sprofs and then overwritten on the very next line, so the first store was pointless.

There were also a couple of intentional busy-wait loops that PVS-Studio flagged for having an empty ; body. These are genuinely meant to be empty — they are timing delays — so instead of silencing the warning I gave them an explicit {} body to make the intent obvious to both the analyser and the next person reading the code.

None of these are dramatic bugs, but clearing them keeps the engine’s future reports clean, so real regressions do not get lost in a wall of pre-existing noise.

Fixing room state that vanished after loading a save

The most interesting problem this week was not a static-analysis finding at all — it was a save/load bug I ran into while testing.

Chamber builds each room from a base decor, but scripts frequently draw persistent changes on top of that: alternate decor sets, extra objects that appear once triggered, and the final frames of animations. The catch is that they draw these directly into the backbuffer. On a fresh visit that is fine, but when you saved and reloaded, the engine rebuilt the room from the base decor only — so anything a script had painted into the backbuffer was simply gone.

In practice this meant that things like the De Profundis monster, or Deilos, would fail to reappear after loading a game, even though the game state said they should be there. The state was correct; the pixels that represented it had never been saved.

The fix was to bump the save version to 2 and store what was actually missing: the zone palette, the current video mode, and the backbuffer contents themselves. On load, if the video mode matches, the backbuffer is restored verbatim; otherwise the engine falls back to the old rebuild-from-decor behaviour, so older saves and cross-mode loads still work. While I was in there I also made load reapply the room palette and honour the launcher’s save-slot option, so booting straight into a save from the launcher lands you in a correctly coloured room.

Looking ahead

That wraps up the cleanup pass I wanted to do before moving on. The engine is now noticeably tidier and more robust, which is a perfect state to leave it in. Starting next week, I will be shifting my focus to a completely different engine. I wonder if anyone can guess which one it will be?(Hint: It will be a great adventure!) Stay tuned, and thanks for reading.

Categories
GSoC 2026

WEEK 6

Crossing the Atlantic: the US Amiga Release

Last week ended with the European Amiga version of Kult running almost perfectly, and a promise: next up, the US release. In America, the game shipped as Chamber of the Sci-Mutant Priestess, published by Draconian, and this week was all about making that version boot and play too.

Same game, different executable

My hope was that the US build would just be the EU one with translated text files. It wasn’t. The Draconian build lays out the KULT executable completely differently:

  • Every static resource lives at a different offset. On the EU side, I had a table of file offsets for the resources embedded in the executable (palettes, icons, zone data…). The US executable shifts them around non-uniformly, so I couldn’t just add a constant — I had to reverse-engineer the US binary and rebuild the whole offset table entry by entry. The good news: once located, 8 of the 12 embedded resources turned out to be byte-identical to the EU ones; only ZONES, TEMPL, CARAC, and ICONE actually differ.

  • The English text banks are jammed straight into the executable, completely abandoning the neat separate files the EU release used. This meant writing a different extraction path just for the US text.

  • The filenames are lowercase.Normally, this would be an immediate problem on case-sensitive file systems like Linux. Thankfully, ScummVM’s Common::File API handles case-insensitivity out of the box, which saved me from having to write any extra fallback logic!

A different welcome screen

The US version also greets you differently: it shows an intro.pia title screen before the usual pres.bin presentation. It’s a small detail, but until the engine knew about it, the game wouldn’t even get past boot.

Zone Scan, the Amiga way

While testing, I also finished the Amiga version of the Zone Scan psi power. The Amiga renderer draws it differently from DOS: the effect applies a dedicated palette delta that tints the room green while the scan line sweeps across the screen. Getting the line itself right took one more fix — the sweep works on chunky pixels now, so the line width and the inversion effect had to be adapted from the original planar code.

Detection entries

With both variants playable, I added proper detection entries for the Amiga EU and US releases. ScummVM now recognizes all of them straight from the launcher.

Looking back at these past two weeks, I am incredibly happy to see the Amiga versions finally fully animated and playable. Building the Amiga support from absolute scratch was a massive, daunting challenge, and I honestly couldn’t have reached this milestone without the immense help and guidance from my mentor. Seeing these versions breathe and run flawlessly makes all the heavy lifting worth it!

Next week

While all this was happening, sev ran static analysis over the engine and sent me a nice stack of Coverity and PVS-Studio reports. While chasing those, I found a bug that wasn’t in my engine at all, but in ScummVM itself. More about that (and about a monster that refused to survive a save/load cycle) in the next post!).Stay tuned!