Overview
This is a basic description of a reverse engineer of Robico’s first text adventure: Island of Xaan.
Although it is one of their weakest, it shows a number of trademark features of Robico’s adventures: lots of detail descriptions, lots of humour and some strange logic. The parser, being just verb and noun, is quite weak compared to Robico’s later adventures.
The adventure was only available on the BBC Micro and Acorn Electron computers. The version I’m reverse engineering is the BBC Micro one, taken from the Stairway to Hell archive and is designed to run in &1900 DFS, using MODE 7.
This write up uses BBC Micro conventions (e.g. using & to signify a hexadecimal number) and assumes that some knowledge is known about the BBC micro.
All notes and scrap code is available from my github.
The disk
The disk contains six files, three of which are loaders:
I’ll get these out of the way, as they’re the simplest and have nothing really interesting from a reverse engineering perspective:
- $.!BOOT – is loaded when the disc is run with shift-break and just runs $.LOAD
- $.LOAD – is the general Stairway to Hell loader and just runs $.LOADER
- $.LOADER – prints the introduction, then loads and runs $.XAAN1. It also shows the address for Robico software, which is an unimposing semi-detached house in Wales.
The other files are code for the adventure which are loaded in the following order, and locations:
- $.XAAN1 which is loaded from &880 to &D00 with an exec address of &888
- $.XAAN which is loaded from &1200 to &7BFC with an exec address of &225D
- $.XAAN2 which is loaded from &2000 to &2400 with an exec address of &2000
XAAN1
XAAN1 contains the intial loader, a selection of library routines, such as the message printer, and several command routines, for short commands, probably placed here because memory was short.
Once loaded it performs the basic system calls, before handing over control to the XAAN file:
- OSBYTE 225 (disable function keys)
- OSBYTE 200 3 (disable escape and clear memory on break)
- OSBYTE 139 1 (*OPT 1 equivalent – report nothing during file ops)
- OSCLI “R. XAAN” (Load XAAN and jump to its executable address)
Other obvious functions include (these are the names I’ve given the functions):
- printmsg – to decompress and print a message. This uses OSWRCH to print to the screen.
- findmsg – finds the message passed in X and passes it to printmsg
- getinput – retrieves a input from the command line, using OSWORD 0. This also splits the input into a verb (stored at &41f) and a noun (stored at &43e)
- searchlist – looks for the passed word in the passed list and returns the index in X. This is used to convert the verb and noun to a number.
- handlecmd – looks up the entered verb and the looks for the appropriate address from a list of pointers.
- the routines for the commands WAIT, SIT and QUIT
XAAN2
XAAN2 looks like it was originally the copy protection file – it contains a selection of calls to OSWORD to read sectors directly from the disk. This is unused on the Stairway to Hell disk.
XAAN
XAAN contains nearly everything, the code, the game data and everything else, apart from a few routines in XAAN1.
XAAN has multiple chunks of data with roughly the following uses:
From | To | Addr | Purpose |
0000 | 10be | 1200 | Code |
10bf | 30ff | 22bf | Empty data |
3100 | 3248 | 4300 | Introduction text |
3249 | 32ff | 4449 | Unknown |
3300 | 5c56 | 4500 | Messages |
5c57 | 5c5b | 6e57 | Unknown |
5c5c | 6e5c | Room exits | |
62f6 | 74f6 | Object locations and flags | |
6354 | 7554 | Room messages | |
6400 | 6437 | 7600 | Verb action pointer (low) |
6438 | 646f | 7638 | Verb action point (high) |
6473 | 65a8 | 7673 | List of verbs |
65a9 | 66e1 | 77a9 | List of nouns |
66e4 | 69fb | 78e4 | List of tokens |
As far as we’re concerned, the data is all listed towards the end of free memory (&7c00 in Mode 7). The bit that surprises me is the amount of spare memory – bearing in mind that the BBC Micro only had 32KB of memory, of which large swathes are unusable due to disk and screen memory, there’s still around 8 KB of free memory whilst XAAN is running!
I’ll go into detail of the different sections later, but for now, the code block about performs all the stuff to run the game and apply the verbs. Unfortunately Robico decided against using a form of bytecode (like other text adventure creators), so all the logic is done in raw 6502.
The machine code does do a lot of JMPing to JMP instructions suggesting to me that it was probably assembled in blocks to reduce memory usage whilst coding it.
Objects are handled in a slightly strange way – every noun is counted as an object, with a location byte.
Anyway, to the data formats…
Strings
All strings are compressed with routine referred to by the creator as MIDGE. THis is relatively easy to decompress and there have been public domain examples found in the past.
This uses a dictionary of common patterns with some handy special characters.
Decompressing it is a matter of reading the next byte and then expanding according to the following rules. The best way to show this is to show my notes for the first location in the game Rick Hanson (which uses MIDGE too):
45 = "you" FC = "'" ED = "r" = 0xed - 0xdb (size of database) 59 = "e " 48 = "in " 3a = "the " 00 = "entrance " 70 = "ha" 93 = "ll" FB = " " 49 = "of " 5D = "a " EE = "s" 81 = "ma" 93 = "ll" F9 = "," DF = "d" 73 = "es" 66 = "er" 6D = "te" 5C = "d " 43 = "rail" 4B = "way" FB = " "
Internally, this is all handled by printmsg in the XAAN file.
To save space, messages in the code are constructed from several different messages, e.g. from the THROW command:
.canthrow lda invsize bne stuffinv ; if we have stuff in inv ldx #&10 ; message 16 "You are not" jsr findmsg ldx #&1a ; message 26 "holding" jsr findmsg ldx #&18 ; "anything" jmp prtmsg
This is used to compress the intro message (at offset &3100), the bank of messages (at offset &3300) and the room descriptions (more later).
Rooms
Rooms are stored in two chunks:
- exits (at offset &5c5c)
- descriptions (at offset &6354)
Room numbers start at 52; this is because they are treated as nouns (see later).
For each room there is are a sequence of bytes. The bottom nybble of the first byte signifies the direction (up to 10, including IN and OUT); the top nybble indicates whether the exit is blocked (&40) or whether there are no exits (&80).
If there is an exit, the flag byte is followed by the destination.
Descriptions are stored as a list of tokens, like the messages.
Objects
Object and nouns (and rooms) all share a common number, which follows the ranges of:
- 2 – 47 nouns
- 52 – 223 rooms
For each noun/room there is a location byte (at offset &62f6) which has the location of the object. 0 is the inventory and 1 is being worn. This does mean that potentially items can be carried by other items!
Verbs/NounsThese are a simple list of strings separated by an “@” character. The number offset is used as an internal reference for the verb/noun number.
Running verb specific actions
There is a table of action entry points, at offset &6400. This is divided into two sections – low byte and high byte; this is to make it easy to do a vector jump:
lda verbptrsl,x ; lookup table to control each verb sta verbv lda verbptrsh,x sta verbvh jsr setnouns jmp (verbv)
Out of curiosity
I found one example of self modifying code in the handlecmd function, where it sets the colour of the output text. It does this by modifying this statement in printmsg:
colourinst = &0922 ; &10921 .printmsg { lda #&86 ; this instruction can be altered jmp oswrch ; VDU 134 (Colour = cyan)
We see this location modified in both xaan1.asm (in handlecmd) and in xaan.asm, for example:
.l20ca lda #&86 sta l0922 rts