Bocfel Z-machine interpreter

Overview

Bocfel is an interpreter for the Z-machine. It fully supports versions 1-5, 7, and 8, and contains partial support for version 6. For detailed information on Bocfel’s operation, please consult the man page.

Bocfel can be downloaded from the downloads page.

The Z-machine

The Z-machine was designed to run text adventure games, also known as interactive fiction. Originally the only Z-machine games were created by Infocom, the company that created the Z-machine. These include such games as Zork, The Hitchhiker’s Guide to the Galaxy, and Planetfall. Fans of the genre figured out the format of the Z-machine, allowing non-Infocom interpreters, such as this one, to be built.

After that, a compiler for the Z-machine, called Inform, was released. Initially a language meant for programmers, the latest iteration of Inform is intended to allow non-programmers to easily write interactive fiction. Thanks to Inform, hundreds of Z-machine story files have been written by enthusiasts, a number of which are of extremely high quality.

Although interactive fiction is today a rather niche genre, it has a loyal following of both players and authors, and can be explored through the following resources, among others:

Screenshots

Some of Bocfel’s features

  • Almost no platform-specific functions are used—and those that are used are optional—so almost any system with a recent C compiler should be able to build and run Bocfel.
  • Makes use of Glk for input/ouput, with complete support for Gargoyle; a non-Glk “dumb” interface is also available.
  • Support (assuming a font with Unicode box-drawing characters) for the character graphics font used by Beyond Zork.
  • Unicode support: see, for example, Chinese characters used by Mingsheng or the Russian translation of Spider and Web.
  • Multiple undo in almost all games, including those which do not natively support it.
  • Full control over command recording, transcripting, and command-record playback.
  • Rudimentary “cheating” support, designed to disable hunger and thirst daemons.

Only the source code to Bocfel is provided here. If you are interested in a runnable binary, Ben Cressey has integrated Bocfel into the latest version of Gargoyle as the default Z-machine interpreter.

Cheating

Bocfel includes rudimentary support for cheating, by allowing certain areas of memory (represented by their addresses) to be frozen such that they always contain specific values. As a result, you are able to do things such as always having money, never being hungry, and so on.

Addresses are typically represented in hexadecimal. However, the Z-machine has the concept of global variables. Global variables are simply named addresses. For example, the global variable G00 of a particular game might map to the address 0x1212, and as such, these two addresses are interchangeable. When displaying addresses, the global variable notation is used if an address is a global variable. Otherwise, the address is displayed in hexadecimal.

This section provides cheats for some Infocom games and also explains how to find other cheats.

Cheats

This section gathers cheats for various games that can be employed using Bocfel’s cheat system. In a few places, the value 65535 is used and might appear to be out of place. When interpreted as a negative value on a 16-bit system (like the Z-machine), 65535 represents -1. For cheats, however, only unsigned (non-negative) values are used, so for negative values, they must be “encoded” as positive values.

Please note: These cheats have not been thoroughly tested, or tested much at all. Use at your own risk. There is no guarantee that a game will be completable if these cheats are used.

Infocom version 1-3 games

Version 1 and 2 games are score games, and version 3 games are either score or time games. Score games keep track of a user’s score and number of moves (e.g. Enchanter), while time games keep track of the time (e.g. Wishbringer). The same memory locations are always used to store these, so the following cheats apply to all version 1-3 games.

For score games, the score is kept in global variable G01 and the number of moves is kept in global variable G02.

For time games, the hour (in the range [0, 23]) is kept in global variable G01 and the minutes (in the range [0, 59]) are kept in global variable G02.

Examples

# Lock the score at 50.
cheat = freezew:G01:50

# Lock the number of moves at 25.
cheat = freezew:G02:25

# Lock the time at 5:45PM.
cheat = freezew:G01:17
cheat = freezew:G02:45

Cutthroats

These cheats are for Cutthroats version 23-840809.

Hunger

The global variable Gb9 tracks hunger. It can take the following values:

  • 0: Not hungry.
  • 1: A bit hungry.
  • 2: Pretty hungry.
  • 3: Very hungry.
  • 4: Extremely hungry.
cheat = freezew:Gb9:0

Thirst

The global variable G07 tracks thirst. It can take the following values:

  • 0: Not thirsty.
  • 1: A little thirsty.
  • 2: Fairly thirsty.
  • 3: Very thirsty.
  • 4: Utterly parched.
cheat = freezew:G07:0

Sleepiness

The global variable Gba tracks sleepiness. It can take the following values:

  • 0: Half awake.
  • 1: Wide awake (cannot sleep, even in bed).
  • 2: Wide awake (but can still sleep in bed).
  • 3: A bit drowsy.
  • 4: Fairly tired.
  • 5: Very sleepy.
  • 6: Dead on your feet.
cheat = freezew:Gba:1

Enchanter

These cheats are for Enchanter version 29-860820.

Hunger

The global variable G40 tracks hunger. It can take the following values:

  • 0: Well fed.
  • 1: A bit hungry.
  • 2: Becoming quite hungry.
  • 3: Very hungry.
  • 4: Fairly starving.
  • 5: Faint from lack of food.
cheat = freezew:G40:0

Alternatively, the bread that you find early in the game can be made perpetually available as follows:

cheat = freezew:0xf68:8

Thirst

The global variable Ga5 tracks thirst. It can take the following values:

  • 0: Well hydrated.
  • 1: A bit thirsty.
  • 2: Quite thirsty.
  • 3: Very thirsty.
  • 4: Extremely thirsty.
  • 5: Faint from lack of water.
cheat = freezew:Ga5:0

Alternatively, the jug of water that you find early in the game can be made perpetually full as follows:

cheat = freezew:0x1c09:4

Sleepiness

The global variable G13 tracks sleepiness. It can take the following values:

  • 65535: Wide awake.
  • 0: Beginning to tire.
  • 1: Feeling tired.
  • 2: Worn out.
  • 3: Wrung out and tired.
  • 4: Getting more and more tired.
  • 5: Dead on your feet.
  • 6: So tired you can barely put one foot in front of another.
  • 7: Practically asleep.
  • 8: Moving only on your last reserves of strength.
  • 9: Barely able to move your arms.
  • 10: Unable to keep your eyes open for more than a few moments at a time.
cheat = freezew:G13:65535

Notes

Enchanter periodically reports how hungry and thirsty you are. If you set the global variables indicating that you are fully sated, these periodic status reports will be gibberish (e.g. “cmhgfh iseems gdon’t glk jmqcf” instead of “Your mouth is getting rather dry.”). This is because the “print status” messages assume that the value will never be 0, and appears to be harmless.

Similarly, if sleepiness is set to 65535, the message “You are faint from lack of water and the spells you’ve memorized are becoming confused.” will periodically appear. This is, as above, the game not expecting the value 65535, and should be harmless.

Sorcerer

These cheats are for Sorcerer version 15-851108.

Thirst

The global variable G4f tracks thirst. It can take the following values:

  • 0: Not thirsty.
  • 1: Somewhat thirsty.
  • 2: Quite thirsty.
  • 3: Very thirsty.
  • 4: Extremely thirsty.
  • 5: Incredibly thirsty.
  • 6: Dangerously thirsty.
cheat = freezew:G4f:0

Hunger

The global variable G6c tracks hunger. It can take the following values:

  • 0: Not hungry.
  • 1: Somewhat hungry.
  • 2: Quite hungry.
  • 3: Very hungry.
  • 4: Extremely hungry.
  • 5: Incredibly hungry.
  • 6: Dangerously hungry.
cheat = freezew:G6c:0

Spellbreaker

This cheat is for Spellbreaker version 87-860904.

Sleepiness

The global variable G0f tracks sleepiness. It can take the following values:

  • 65535: Wide awake.
  • 0: Beginning to tire.
  • 1: Feeling tired.
  • 2: Getting more and more tired.
  • 3: Worn out.
  • 4: Dead tired.
  • 5: So tired you can barely concentrate.
  • 6: Moving on your last reserves of strength.
  • 7: Practically asleep.
  • 8: Barely able to keep your eyes open.
  • 9: About to keel over from exhaustion.
cheat = freezew:G0f:0

Notes

As with Enchanter, Spellbreaker will periodically give status updates which are nonsensical if sleepiness is set to 65535 (e.g. “You are large bird circling the tower and eyeing you suspiciously..”). These appear to be harmless.

Planetfall

These cheats are for Planetfall version 37-851003.

Hunger and thirst

The global variable Gbc tracks hunger and thirst. It can take the following values:

  • 0: Well-fed.
  • 1, 2: Fairly thirsty and hungry.
  • 3, 4: Noticeably thirsty and hungry.
  • 5: Awesomely phenomenally thirsty and hungry.
cheat = freezew:Gbc:0

Sleepiness

The global variable Gbe tracks sleepiness. It can take the following values:

  • 0: Well-rested.
  • 1: Sort of tired.
  • 2: Quite tired.
  • 3: Phenomenally tired.
cheat = freezew:Gbe:0

Health

The global variable Gba tracks health. It can take the following values:

  • 0: Perfect health.
  • 1, 2, 3: A bit sick and feverish.
  • 4, 5: Somewhat sick and feverish.
  • 6, 7: Very sick and feverish.
  • 8: Severely sick and feverish.
cheat = freezew:Gba:0

Carrying capacity

The global variable Gda specifies the amount of weight that can be carried, while G80 specifies the number of items. Because these are treated as signed 16-bit values, 32767 is the maximum for each: larger values will be interpreted as negative numbers.

cheat = freezew:Gda:32000
cheat = freezew:G80:32000

Finding Cheats

Bocfel contains a few ways to help discover cheats in games. See /debug help for a complete list of debugging commands.

Printing

The value of a word (16-bit value) at a particular address can be shown:

/debug print G00
0 (0x0)

The value is printed in both signed decimal, and unsigned hexadecimal.

Freezing

Cheating is implemented by freezing certain areas of memory such that they always report the same values. While this is not a method for finding cheats, once a cheat has been found, freezing is a way to test that cheat. Freezing can be done via Bocfel’s configuration file, or can be done in-game, to quickly test any cheats that you have found.

Knowing that the score is kept in global variable G01, in Zork:

> score
Your score is 0 (total of 350 points), in 10 moves.
This gives you the rank of Beginner.

> /debug freeze G01 350
[frozen]

> score
Your score is 350 (total of 350 points), in 10 moves.
This gives you the rank of Master Adventurer.

Watching

Addresses can be watched for changes. Knowing that the number of moves is kept in global variable G02, in Zork:

> /debug watch G02
[watching G02 for changes]

> e
The door is boarded and you can’t remove the boards.
[G02 changed: 0 -> 1 (pc = 0x54ec)]

> z
Time passes...
[G02 changed: 1 -> 2 (pc = 0x54ec)]
[G02 changed: 2 -> 3 (pc = 0x54ec)]
[G02 changed: 3 -> 4 (pc = 0x54ec)]

Scanning

It is possible to scan memory for words (16-bit values) matching a specific value. This can be used if you know the specific value of something and want to find it, such as the amount of money being carried. The commands are:

/debug scan start: Restart scanning. This forgets any previous scan that was in progress.

/debug scan N: Scan memory for the value N. If N starts with 0x it is hexadecimal and unsigned; otherwise it is decimal and can range from -32768 to 65535. When this command is run, the number of memory locations which contain the specific value is printed. If this command is run again, the list will be narrowed down. This process can be repeated until, with luck, only a single memory location contains the specified value.

/debug scan show: Print out all locations matching the values that have been scanned for since the last scan start.

For example, in Beyond Zork:

Kitchen
  Coils of greasy steam rise from a cauldron bubbling over a roaring hearth. The ceiling is hung with crusty pots and strips of old meat.
  A closed door in the corner bears the legend, "Keepeth Out."
  A skinny old cook is bustling around the kitchen.
  There's a giant onion here.

>$
You have 1 zorkmid.

>/debug scan start
[debug scan reset]

>/debug scan 1
[183 locations]

>buy onion
[with your zorkmid]
  The cook gives you a bewildered look, shrugs, and accepts your zorkmid without question.

>$
You're broke.

>/debug scan 0
[1 location]

>/debug scan show
Gd3

>/debug freeze Gd3 100
[frozen]

>$
You have 100 zorkmids.

Changes

Sometimes you can be confident that a word (16-bit value) is increasing or decreasing, but not know the specific values being used, such as when a water or food source is being consumed. The commands are:

/debug change start: Restart change tracking. This forgets any previous change tracking that was in progress.

/debug change dec: Display the location of all words which have decreased since the last check.

/debug change inc: Display the location of all words which have increased since the last check.

For example, in Enchanter:

>x jug
The jug is empty.

>/debug change start
[debug change reset]

>fill jug
The jug is now full of water.

>/debug change start

>sw then drink water
Trailhead

The delicious spring water tasted great, and there's lots more where that came from.

>/debug change dec
0x60e: 14099 -> -25325
0x682: -16913 -> -17097
0x1c09: 4 -> 3
0x1c0a: 1065 -> 809
[...]

>ne then fill jug
Shady Brook

The jug is now full of water.

>/debug change inc
0x60e: -25325 -> 14099
0x682: -17097 -> -16913
0x1c09: 3 -> 4
0x1c0a: 809 -> 1065
[...]

>sw then z.z.z then drink water
Trail Head

Time passes...
[...]

The delicious spring water tasted great, and there's lots more where that came from.

>/debug change dec
0x60e: 14099 -> -25325
0x682: -16913 -> -17097
0x1c09: 4 -> 3
0x1c0a: 1065 -> 809
[...]

> z.z.z.z.z.z.z.z.z.z.z.z.z.z.z.
Time passes...
[...]

>drink water
The delicious spring water tasted great, and there's plenty more.

>/debug change dec
0x1c09: 3 -> 2
0x1c0a: 809 -> 553

> ne then fill jug
Shady Brook

The jug is now full of water.

>/debug change inc
0x1c09: 2 -> 4
0x1c0a: 553 -> 1065

At this point it’s been narrowed down to two variables. 0x1c09 looks promising because it keeps track in small increments, which keeps in line with how the water is managed in Enchanter (the jug being full, nearly full, half full, mostly empty, and empty). To test, you can freeze the address at specific values and see what happens:

>/debug freeze 0x1c09 4 
[frozen]

>x jug
The jug is full.

>/debug freeze 0x1c09 3
[frozen]

>x jug
The jug is nearly full.

>/debug freeze 0x1c09 2
[frozen]

>x jug
The jug is half full.

>/debug freeze 0x1c09 1
[frozen]

>x jug
The jug is mostly empty.

>/debug freeze 0x1c09 0
[frozen]

>x jug
The jug is foo

Looks like 0x1c09 tracks how much water is available. Why is there odd output when the water is set to 0? Enchanter handles water by having both a water object and a jug object. When the jug is filled, the water object is placed into the jug. The water object keeps track of how much water there is, with the values 4, 3, 2, and 1. When the water object is at 1 and the player drinks that, instead of reducing the value to 0, the water object is removed entirely from the jug, and now instead of the water being in charge of determining how much water is left, the jug is in charge, and since it’s empty, it says so. The upshot is that the game never expects the water object to be in the jug while at level 0: at that point, the water should be outside of the jug. By freezing it at 0, the water stays in the jug but at an “impossible” value so far as the game is concerned. It looks up what 0 means and finds a junk text string, which happens to be “foo”.

These sorts of interactions can be tricky to deal with, because while it may seem that a single word tracks something, there may be more to it than that. In this case, looking at the disassembly of the Enchanter game helps figure out how the water and jug interact, but it’s not really necessary to do so: it was enough to learn that freezing 0x1c09 at 4 keeps the jug perpetually full.

The man page for Bocfel is available here.

Bocfel Downloads

The source code for Bocfel is provided here.

Latest version: 1.0

Bocfel 1.0

bocfel-1.0.tar.gz

Release date: December 6, 2017

Release notes:

  • Add the ability to patch known bugs in games at runtime.
  • The username can now be set.

Bocfel 0.9

bocfel-0.9.tar.gz

Release date: March 19, 2017

Release notes:

  • UTF-8 is now used for all transcripts and command scripts.
  • Watchpoints can now be set through the debugging interface.
  • The upper window now tracks resizes properly even when it is closed.

Bocfel 0.6.3.2

bocfel-0.6.3.2.tar.gz

Release date: December 16, 2014

Release notes:

  • Fix detection of Sherlock version 26-880127.

Bocfel 0.6.3.1

bocfel-0.6.3.1.tar.gz

Release date: June 13, 2014

Release notes:

  • Fix a potential use of uninitialized variable.

Bocfel 0.6.3

bocfel-0.6.3.tar.gz

Release date: February 2, 2014

Release notes:

  • Improved handling of abbreviations (x, g, z, and o).
  • The “attribute 48” bug of Sherlock is now properly handled.
  • Blorb sounds are now properly loaded if the story file itself is wrapped in a Blorb file.
  • Support for “debugging” meta commands, the main use of which is to help find cheats without needing to know details of the Z-machine architecture.

Bocfel 0.6.2

bocfel-0.6.2.tar.gz

Release date: December 1, 2012

Release notes:

  • Enable /undo in V5 games which do not support undo (e.g. Hitchhiker’s Guide).
  • Add new meta commands /say (allowing text strings which begin with / to be sent to the game) and /disable (which disables meta commands for the rest of the game session).
  • Add new meta commands /ps and /pop which allow in-memory saves to be created, providing arbitrary save points.
  • Meta commands are no longer included in transcripts.

Bocfel 0.6.1

bocfel-0.6.1.tar.gz

Release date: February 27, 2012

Release notes:

  • Bocfel is now dual-licensed under both the GPLv2 and GPLv3.
  • Small bugfixes and general cleanup.

Bocfel 0.6.0

bocfel-0.6.0.tar.gz

Release date: June 26, 2011

Release notes:

  • The configuration options script_on and script_name have been renamed to record_on and record_name.
  • Support for some meta commands (/undo, /replay, etc.) has been added.
  • Assorted minor bugfixes and features.

Bocfel 0.5.5

bocfel-0.5.5.tar.gz

Release date: April 22, 2011

Release notes:

  • Preliminary @sound_effect support.
  • Small bugfixes and general cleanup.

Bocfel 0.5.4

bocfel-0.5.4.tar.gz

Release date: March 25, 2011

Release notes:

  • Fix a rather obscure bug triggered when @restart is called inside an interrupt.
  • Improved resilience in the face of utterly broken story files.
  • Minor improvements to the syntax for cheats.
  • Improved handling of the configuration file on Windows.

Bocfel 0.5.3

bocfel-0.5.3.tar.gz

Release date: March 9, 2011

Release notes:

  • The initial random seed can now be read from a device such as /dev/random.
  • Support for Z-machine versions 1 and 2 is now built unconditionally.
  • Small bugfixes and general cleanup.

Bocfel 0.5.2

bocfel-0.5.2.tar.gz

Release date: February 22, 2011

Release notes:

  • Fix a display problem when a newline is printed at the right edge of the upper window.
  • Provide better diagnostics when a save file cannot be loaded.

Bocfel 0.5.1

bocfel-0.5.1.tar.gz

Release date: February 18, 2011

Release notes:

  • Bocfel now reports itself as a Standard 1.1-compliant interpreter.
  • The stack is now properly saved in a @save_undo call.
  • Bocfel generates error messages on a few rather questionable uses of the Z-machine; some of these instances have been modified to return sensible results instead of aborting.
  • The praxix and strictz tests are now passed.

Bocfel 0.5.0

bocfel-0.5.0.tar.gz

Release date: February 15, 2011

Release notes:

  • Initial release.