Infidel goes wild
Infidel Goes Wild
Zarf Updates | Exploring interactive fiction, game narratives, and the technical depths of the Z-machine.
The Discovery of a "Sinfull" Bug
While the public release of Planetfall happened yesterday, my primary focus over the last few weeks has been Infidel. During this process, I stumbled upon what I consider the most egregious bug ever found in an Infocom title.
Detecting this issue through standard gameplay is nearly impossible. I likely only spotted it because I am the first person to approach the game using a memory-level debugger. To a casual observer, this would be a trivial bug since it rarely disrupts the player's experience. However, from a technical perspective, it is a wild pointer bug that scribbles over memory haphazardly.
As a C programmer, I am legally obligated to view memory corruption as the absolute worst of all possible sins.
Technical Parameters
- Analyzed Version: Infidel release 22, serial
840522(Macintosh update). - Note: The original release (serial
830916) contains the same flaw, though the specific memory addresses differ.
The Mechanics of the Endless Desert
To understand the bug, we first need to understand how the game handles the vast wasteland.
If you wander too far from your camp or the specific region containing the buried pyramid, you become "lost." In reality, you aren't moving through a massive map; instead, you are trapped in a single room called ENDLESS-DESERT. This is a clever design trick first utilized in Enchanter.
How the "Endless" loop works:
- Move in any direction Loop back to
ENDLESS-DESERT. - Drop an item Item is shifted "offstage."
- Record location The item's coordinates are stored in the
DESERT-TABLE. - Return to coordinates The game moves the item from the table back into the room.
Note: There is a probability that an item dropped in the desert is immediately buried by sand and deleted. This randomness actually makes the bug harder to trigger and detect.
Gameplay Example
Imagine the following sequence:
- Location:
Desert(You are in a wasteland of sand and heat). - Event: A gust of wind blinds you; you lose your shovel.
- Action: Move
EASTYou are still in theDesert. - Action: Move
WESTYou are still in theDesert.
Even though you are in the same ENDLESS-DESERT room, the game maintains the illusion of distance.
Diving into the ZIL Code
The logic governing this movement is handled by the DESERT-TO-TABLE routine. Here is the high-level ZIL (Zork Implementation Language) logic:
ROUTINE DESERT-TO-TABLE ( SLOC AUX (TBL ,DESERT-TABLE) (CNT 0) (F FIRST?)
GET .TBL .CNT 0
PUT .TBL .CNT .SLOC
PUT .TBL + .CNT 1 .F
SET CNT + .CNT 2
REMOVE .F
RETURN
) (ELSE SET CNT + .CNT 2 ) ) SET F .N
Data Structure Breakdown
The routine takes one required argument: SLOC (the encoded latitude/longitude coordinate). It uses "AUX" for optional arguments/local variables:
TBL: The target table.CNT: The current index counter.F: The first object in theENDLESS-DESERTcontents list.
| Feature | Detail |
|---|---|
| Table Name | DESERT-TABLE |
| Start Address | 11129 |
| Total Size | 100 values (200 bytes) |
| Entry Size | 2 values per object (SLOC + Object ID) |
The function iterates through all objects in the ENDLESS-DESERT. It ignores scenery and the player, removing every portable object and storing its coordinate and ID in the TBL array.
Visualizing the Flow
The "C Nerd" Concern: Array Overflow
Whenever a programmer sees a fixed-size array, the first question is: What happens when it overflows?
I checked the source comments for DESERT-TABLE, which stated:
; length should be 2*number of takeable objects
Using LaTeX to verify the math:
Since there are only approximately 45 portable objects in the entire game, it seems impossible to overflow the table. To verify this, I added a monitor to my debugger's "State" tab to watch the DESERT-TABLE in real-time.
The Deep Dive: Disassembly
To truly see what was happening, I disassembled the function from the game file.
Routine 109bc (5 locals: 0000, 001e, 0000, 0000, 0000)
109c7: GET_CHILD ENDLESS-DESERT - .F [TRUE]
109cb
109cb: JZ .F [TRUE] RTRUE
109ce: GET_SIBLING .F - .N [TRUE]
109d2
109d2: JE .F, G70 [FALSE]
109d9
109d6: JUMP 10a02
109d9: TEST_ATTR .F, #0f [FALSE]
10a02
109dd: LOADW .TBL, .CNT - -(SP)
109e1: JZ (SP)+ [FALSE]
109fb
109e4: STOREW .TBL, .CNT, .SLOC
109e9: ADD .CNT, #01 - -(SP)
109ed: STOREW .TBL, (
(The disassembly continues, revealing the underlying Z-machine instructions that manage the memory movement...)