Post-release patching a TADS3 game

Deriving from some code posted by @RealNC in the Changing verb grammar at runtime thread, I’ve put together a T3 module for patching games post-release.

The basic patching mechanism is essentially identical to the one posted by @RealNC in the linked thread. When a game is compiled to include the module, then every time at startup it will:

  • Check for an optional patch bootloader in a file called patchBootstrap.t in the same directory as the game file. The file name can be changed by changing patchLoader.patchBootstrapFile. If the file isn’t present, the builtin patch bootloader is used instead, so using this file is only necessary if you need to change something about how patches are loaded/applied.
  • Check for a patch in a file called patch.t in the same directory as the game file. The file name can be changed by changing patchLoader.patchFile. If the file doesn’t exist, the patch process is silently skipped.
  • Actually apply the patch.

The patch will also be applied whenever a save state is restored.

In addition to the above, there is optional support for:

  • Base64 encoding patch files. This is to lightly obfuscate the patch itself, to prevent accidental spoilers
  • Rudimentary code signing. This uses the SHA256 hash of a supplied passphrase plus the patch source to generate a signature which is then checked when loading the patch file. This is to prevent accidental loading of something other than a patch for the game (for example, a patch for a different game in the same directory). It is not indented to be a security feature.

The documentation is still pretty nonexistent and I haven’t done any compatibility testing outside of FrobTADS, so all appropriate caveats apply. But you can get the source from the git repo.

I’ll ping the thread if/when I add additional features.

7 Likes

Wait, so is this for easily applying updates to a game that already has a running save file…?

Very cool!

The Glk port of TADS 3 wouldn’t support loading a .t file. (It probably would remove the .t and then add a .txt). The patch file could be renamed (by the player, or by the author, and authors could even distribute both), but maybe it would be better to just recommend naming the patch file .txt from the beginning?

Say you discover a bug. You fix it in source and recompile. Boom, no save made with the old version will be loadable in the new version.

So instead of distributing an updated .t3 (or rather in addition to distributing a fixed .t3) you can distribute a patch file. It goes in the same directory as the game file, and when the game is started (from the old .t3) the patch is applied at runtime, and players with existing save files can continue to use them.

I’m not sure I understand why. Both emglken and gargoyle seem to load patches fine.

I’m not sure how to get it working with parchment, though. It emulates a little filesystem for the interpreter, right? I have no idea how to make other files available for loading at runtime in it.

1 Like

Oh, interesting. It must be loading the files outside the Glk system then, I assume.

It wouldn’t be possible now, but I have ideas for how it could be done.

It just uses TADS3’s internal file handling: File.openTextFile(), readFile(), and closeFile().

Oh my god, you brilliant madlad!

How many sleepless nights did this take??

None, since I’m just copying (with permission) the clever bit from @RealNC (read file into string, compile string at runtime using the stuff from TADS3’s dynafunc.t, set the result as a method on an existing object).

I asked if it was already available as a module/library and they said no, so I asked if it was okay if I made one and they said yes. Because, yeah, it’s really a nice option to have for post-release support.

My additions were just putting it together as a module and adding a few usability tweaks (more configurability about how the patch bootloader and patch itself are loaded) and optional features (Base64 encoding, simple code signing).

1 Like

Just a little update: you can now generate a signed/encoded patch file using only TADS3.

There’s a little demo “game” in the repo, under ./demo/src/patchGen.t. The “game” just reads a “raw” patch file, outputs the signed and encoded patch file, and then exits. The interesting bits are just a couple of lines:

                local buf;

                // Load the raw patch file and sign/encode it.
                buf = patchLoader.generatePatch('patchSource.t');

                // Write the signed/encoded patch to a file.
                patchLoader.writePatch(buf, 'patch.t');

This is entirely so authors can generate signed/encoded patch files more or less anywhere TADS3 runs, without the need for any external widgets (a base64 encoder or a SHA256 hash generator).

2 Likes

I was going to just copy Nikos’ code myself, but since you’ve done this extra step, I plan to use it when I start putting out my game for testing. So, thanks!

1 Like