This page captures the knowledge that I have so far on the Quill binary data formats (i.e. the structure of the data after it has been compiled into a game). Primarily this is covering the ZX Spectrum format at the moment, although all platforms had fairly similar formats (although with minor differences). I've highlighted the differences where I have the information, although the details on non-ZX Spectrum formats will be expanded over time. The BBC Micro format is quite different in a number of respects, so may require a separate document. This does not yet include details on the expansion dictionary (used for compressed games) or a definition of how the graphics are stored, but this may be added in due course.
The main source of this knowledge is the source code of the UnQuill tool by John Elliot and my own experiences writing a reverse engineering tool based on the knowledge of this format. I have started investigating the Atari AdventureWriter data format, which is very similar (although even closer to the C64 data format) and will expand this page as my investigations progress.
All Quill formats start from a base address in memory which allows a lookup table to be located. For Spectrum formats, this can be determined by looking for a series of bytes $10,$11,$12,$13,$14,$15 interspersed with colour values. Determining the base address for other formats may not be as straightforward. There is a possibility that the base address is consistent for all adventures on a given hardware platform, however more investigation is needed before I can be certain of this.
The colour table is 13 bytes long, each entry taking one byte.
Offset | Entry | Comments |
---|---|---|
00 | $10 | Always $10 |
01 | Ink colour | Between $00 and $09 |
02 | $11 | Always $11 |
03 | Paper colour | Between $00 and $09 |
04 | $12 | Always $12 |
05 | Flash state | $00 or $01 |
06 | $13 | Always $13 |
07 | Bright state | $00 or $01 |
08 | $14 | Always $14 |
09 | Inverse state | $00 or $01 |
10 | $15 | Always $15 |
11 | Over state | $00 or $01 |
12 | Border colour | Between $00 and $07 |
Following this the lookup table starts at the next memory location.
The ZX Spectrum colour codes are as per the following table:
Code | Colour |
---|---|
$00 | Black |
$01 | Blue |
$02 | Red |
$03 | Magenta |
$04 | Green |
$05 | Cyan |
$06 | Yellow |
$07 | White |
$08 | Transparent |
$09 | Contrast |
Offset | Entry | Size |
---|---|---|
0 | Max number of objects carriable | 1 byte |
1 | Number of objects in game | 1 byte |
2 | Number of locations in game | 1 byte |
3 | Number of messages in game | 1 byte |
4 | Address of response table | 2 bytes |
6 | Address of status table | 2 bytes |
8 | Address of object table | 2 bytes |
10 | Address of location table | 2 bytes |
12 | Address of message table | 2 bytes |
14 | Address of connections table | 2 bytes |
16 | Address of vocabulary table | 2 bytes |
18 | Address of object position table | 2 bytes |
Version C games differ from Version A in that they are not constrained to 32 system messages and there is also a table of words associated with objects (supporting AutoGet, AutoDrop etc actions)
Offset | Entry | Size |
---|---|---|
0 | Max number of objects carriable | 1 byte |
1 | Number of objects in game | 1 byte |
2 | Number of locations in game | 1 byte |
3 | Number of messages in game | 1 byte |
4 | Number of system messages in game | 1 byte |
5 | Address of response table | 2 bytes |
7 | Address of status table | 2 bytes |
9 | Address of object table | 2 bytes |
11 | Address of location table | 2 bytes |
13 | Address of message table | 2 bytes |
15 | Address of system message table | 2 bytes |
17 | Address of connections table | 2 bytes |
19 | Address of vocabulary table | 2 bytes |
21 | Address of object position table | 2 bytes |
23 | Address of object words table | 2 bytes |
For other platforms, the lookup table typically follows the ZX Spectrum Version C structure. (Confirmed for C64, Amstrad and Atari; BBC Micro may be different.)
The address of the object table, location table, message table and system message table (for version C games) and the number of entries in each can be found in the lookup table. Each entry is two bytes long.
Offset | Entry |
---|---|
0 | Address of Object/Loc/Msg 0 text |
2 | Address of Object/Loc/Msg 1 text |
4 | Address of Object/Loc/Msg 2 text |
etc... |
In ZX Spectrum Version A files, there is a fixed number of system messages (32) and the address of System Message 0 is always found at memory address $5C7B
System messages on this version are all stored contiguously in memory so further system messages can be found by scanning through and counting the number of terminator ($1F) bytes seen.
The object, location or message text can be found at the memory address specified in the object, location or message table.
Each text entry is a set of bytes, terminated by $1F. Each byte should be converted to a character by subtracting it from 255 (or doing an Exclusive OR operation with $FF, which is equivalent).
(Other platforms may have different termination characters. For example, Atari games have text terminated by an $FF byte; other platforms are to be confirmed).
On the ZX Spectrum, each character is interpreted as follows:
Value | Meaning |
---|---|
$06 | A tab of 16 spaces. If beyond column 16, this is interpreted as a newline. |
$08 | Backspace |
$0D | Newline |
$10 | Set ink colour. The byte following should be converted to a Spectrum colour by subtracting it from 255. |
$11 | Set paper colour. The byte following should be converted to a Spectrum colour by subtracting it from 255. |
$12 | Set flash state. The byte following is $FF or $FE specifying off or on respectively. |
$13 | Set bright state. The byte following is $FF or $FE specifying off or on respectively. |
$14 | Set inverse state. The byte following is $FF or $FE specifying off or on respectively. |
$15 | Set over state. The byte following is $FF or $FE specifying off or on respectively. |
$17 | Tab. The byte following specifies the number of spaces. |
$20-$7F | Standard ASCII characters |
$80-$FF | Special characters including UDGs (user-defined graphics) |
Characters $00-$05,$09-$0C,$0E,$0F and $18-$1F are still to be confirmed. Handling of character codes for other platforms to be confirmed.
The address of the Connections table is found in the lookup table. This table contains an entry for every location in the game. Each entry is two bytes long and give the address of where the Connection Details Block for that location can be found.
Offset | Entry |
---|---|
0 | Address of Location 0 connection details |
2 | Address of Location 1 connection details |
4 | Address of Location 2 connection details |
etc... |
The Connection Details Block for each location allows for movement to take place between locations. The connection details block contains an arbitrary number of connection entries, depending on how many exits there are from the given location and is terminated by a $FF byte. Each connection entry is two bytes, as shown below.
Offset | Entry |
---|---|
0 | Id of movement word |
1 | Destination location number |
Note that the connections table does not necessarily describe every exit from a location. Some exits may be conditional on other factors, e.g. an object being carried or a flag being set. In this case, those exits are represented by Response table entries.
The address of the Vocabulary table is found in the lookup table. The number of entries is not specified, routines using this table scan the table until reaching a $00 termination byte.
Each entry consists of five bytes - four bytes giving the word and 1 byte giving the id of the word.
Each letter in the word is stored as 255 minus the ASCII value (or by doing an Exclusive OR with $FF). Words with the same id value are considered synonyms.
Offset | Entry | Size |
---|---|---|
0 | Word 1 | 4 bytes |
4 | Id of Word 1 | 1 byte |
5 | Word 2 | 4 bytes |
9 | Id of Word 2 | 1 byte |
etc... |
The address of the object start position table is given in the lookup table. There is one byte per object in game, each byte representing the start location of the object.
Offset | Entry |
---|---|
0 | Location of Object 0 |
1 | Location of Object 1 |
2 | Location of Object 2 |
etc... |
The following locations are treated as special:
Location | Meaning |
---|---|
252 ($FC) | Not in game |
253 ($FD) | Object is worn |
254 ($FE) | Object is carried |
The address of the object word table is given in lookup table. There is one byte per object in game
Offset | Entry |
---|---|
0 | Id of word for Object 0 |
1 | Id of word for Object 1 |
2 | Id of word for Object 2 |
etc... |
These tables have the same format; the only difference is that the verb/noun in the status table is ignored - all entries are processed every turn regardless of what the user has entered whereas the response table entries are only processed if they match user input.
Each entry has the following form:
Offset | Entry | Size |
---|---|---|
0 | Id of vocab entry matching verb | 1 byte |
1 | Id of vocab entry matching noun | 1 byte |
2 | Address of CondActs (conditions/actions) to be applied | 2 bytes |
This table is terminated by a 0 byte. The size of these tables is not specified, so processing needs to be through each entry in turn until the 0 terminator is reached.
These are the entries referenced in the Response and Status tables.
Each CondAct entry is comprised of:
Entry | Size |
---|---|
A list of conditions | 2 or 3 bytes per condition - see Condition definition table below |
$FF byte terminating the conditions list | 1 byte |
A list of actions | 1, 2 or 3 bytes per action - see Action definition table below |
$FF byte terminating the actions list | 1 byte |
These appear to be consistent across all Quill file formats
Code | Condition | Parameters | Size | Meaning |
---|---|---|---|---|
$00 | AT | location | 2 bytes | Is player at location? |
$01 | NOTAT | location | 2 bytes | Is player not at location? |
$02 | ATGT | location | 2 bytes | Is player at location with number greater than the given location? |
$03 | ATLT | location | 2 bytes | Is player at location with number less than the given location? |
$04 | PRESENT | object | 2 bytes | Is the given object present (in current location, carried or worn)? |
$05 | ABSENT | object | 2 bytes | Is the given object absent from the current location? |
$06 | WORN | object | 2 bytes | Is the given object worn? |
$07 | NOTWORN | object | 2 bytes | Is the given object not currently worn? |
$08 | CARRIED | object | 2 bytes | Is the given object being carried? |
$09 | NOTCARR | object | 2 bytes | Is the given object not currently carried? |
$0A | CHANCE | percent | 2 bytes | This condition is true a given percentage of the time |
$0B | ZERO | flag | 2 bytes | Is the given flag equal to zero? |
$0C | NOTZERO | flag | 2 bytes | Is the given flag not equal to zero? |
$0D | EQ | flag, value | 3 bytes | Is the given flag equal to the given value? |
$0E | GT | flag, value | 3 bytes | Is the given flag greater than the given value? |
$0F | LT | flag, value | 3 bytes | Is the given flag less than the given value? |
The mapping of bytes to actions varies between Quill file formats.
ZX Spectrum Version A has the following action definitions:
Code | Action | Parameters | Size | Meaning |
---|---|---|---|---|
$00 | INVEN | none | 1 byte | Display inventory |
$01 | DESC | none | 1 byte | Display current location description |
$02 | QUIT | none | 1 byte | Asks user if they want to Quit |
$03 | END | none | 1 byte | Displays game over message and gives user option of restarting |
$04 | DONE | none | 1 byte | Process no more actions |
$05 | OK | none | 1 byte | Display an OK message |
$06 | ANYKEY | none | 1 byte | Prompt the user to press a key |
$07 | SAVE | none | 1 byte | Save the game |
$08 | LOAD | none | 1 byte | Load a saved game |
$09 | TURNS | none | 1 byte | Display the number of turns the player has taken |
$0A | SCORE | none | 1 byte | Display the current score |
$0B | PAUSE | length | 2 bytes | Pause for (length/50) seconds |
$0C | GOTO | location | 2 bytes | Set the current player location to that given |
$0D | MESSAGE | message number | 2 bytes | Display the message with the given number |
$0E | REMOVE | object | 2 bytes | Attempt to remove (un-wear) the given object |
$0F | GET | object | 2 bytes | Attempt to get the given object |
$10 | DROP | object | 2 bytes | Attempt to drop the given object |
$11 | WEAR | object | 2 bytes | Attempt to wear the given object |
$12 | DESTROY | object | 2 bytes | Remove the given object from the game (set its location to 252) |
$13 | CREATE | object | 2 bytes | Place the given object at the current location |
$14 | SWAP | two objects | 3 bytes | Swap the location of the two given objects |
$15 | SET | flag | 2 bytes | Set the given flag to 255 |
$16 | CLEAR | flag | 2 bytes | Set the given flag to 0 |
$17 | PLUS | flag, value | 3 bytes | Increment the given flag by the given value |
$18 | MINUS | flag, value | 3 bytes | Decrement the given flag by the given value |
$19 | LET | flag, value | 3 bytes | Set the given flag to the given value |
$1A | BEEP | duration, pitch | 3 bytes | Make a sound at the given pitch for the given duration |
ZX Spectrum Version C has additional actions: CLS, DROPALL, AUTOG, AUTOD, AUTOW, AUTOR, PAPER, INK, BORDER and PLACE
ZX Spectrum Version C has the following action definitions:
Code | Action | Parameters | Size | Meaning |
---|---|---|---|---|
$00 | INVEN | none | 1 byte | Display inventory |
$01 | DESC | none | 1 byte | Display current location description |
$02 | QUIT | none | 1 byte | Asks user if they want to Quit |
$03 | END | none | 1 byte | Displays game over message and gives user option of restarting |
$04 | DONE | none | 1 byte | Process no more actions |
$05 | OK | none | 1 byte | Display an OK message |
$06 | ANYKEY | none | 1 byte | Prompt the user to press a key |
$07 | SAVE | none | 1 byte | Save the game |
$08 | LOAD | none | 1 byte | Load a saved game |
$09 | TURNS | none | 1 byte | Display the number of turns the player has taken |
$0A | SCORE | none | 1 byte | Display the current score |
$0B | CLS | none | 1 byte | Clear the screen |
$0C | DROPALL | none | 1 byte | Moves all objects carried or worn into the current location |
$0D | AUTOG | none | 1 byte | Attempts to get the object referred to by a given noun |
$0E | AUTOD | none | 1 byte | Attempts to drop the object referred to by a given noun |
$0F | AUTOW | none | 1 byte | Attempts to wear the object referred to by a given noun |
$10 | AUTOR | none | 1 byte | Attempts to remove the object referred to by a given noun |
$11 | PAUSE | length | 2 bytes | Pause for (length/50) seconds |
$12 | PAPER | colour | 2 bytes | Set the paper (background) colour to that given |
$13 | INK | colour | 2 bytes | Set the ink (foreground) colour to that given |
$14 | BORDER | colour | 2 bytes | Set the ink (foreground) colour to that given |
$15 | GOTO | location | 2 bytes | Set the current player location to that given |
$16 | MESSAGE | message number | 2 bytes | Display the message with the given number |
$17 | REMOVE | object | 2 bytes | Attempt to remove (un-wear) the given object |
$18 | GET | object | 2 bytes | Attempt to get the given object |
$19 | DROP | object | 2 bytes | Attempt to drop the given object |
$1A | WEAR | object | 2 bytes | Attempt to wear the given object |
$1B | DESTROY | object | 2 bytes | Remove the given object from the game (set its location to 252) |
$1C | CREATE | object | 2 bytes | Place the given object at the current location |
$1D | SWAP | two objects | 3 bytes | Swap the location of the two given objects |
$1E | PLACE | object, location | 3 bytes | Set the location of the given object to the location given |
$1F | SET | flag | 2 bytes | Set the given flag to 255 |
$20 | CLEAR | flag | 2 bytes | Set the given flag to 0 |
$21 | PLUS | flag, value | 3 bytes | Increment the given flag by the given value |
$22 | MINUS | flag, value | 3 bytes | Decrement the given flag by the given value |
$23 | LET | flag, value | 3 bytes | Set the given flag to the given value |
$24 | BEEP | duration, pitch | 3 bytes | Make a sound at the given pitch for the given duration |
Other platforms have their own subsets of action definitions, the majority being the same as for the ZX Spectrum, but with some platform-specific differences. These are to be confirmed.