Difference between revisions of "SCUMM/Technical Reference/Room resources"
(→Header) |
m (Small text tweaks) |
||
(7 intermediate revisions by 2 users not shown) | |||
Line 11: | Line 11: | ||
<tr><td>0x00</td><td>UInt16LE</td><td>Size of the room resource chunk</td></tr> | <tr><td>0x00</td><td>UInt16LE</td><td>Size of the room resource chunk</td></tr> | ||
<tr><td>0x02</td><td>???</td><td>Unknown</td></tr> | <tr><td>0x02</td><td>???</td><td>Unknown</td></tr> | ||
<tr><td>0x04</td><td>UInt16LE</td><td>Room width in tiles</td></tr> | <tr><td>0x04</td><td>UInt16LE</td><td>Room width in tiles (always <code>0x1C</code> or <code>0x3C</code>)</td></tr> | ||
<tr><td>0x06</td><td>UInt16LE</td><td>Room height in tiles</td></tr> | <tr><td>0x06</td><td>UInt16LE</td><td>Room height in tiles (always <code>0x10</code> on NES)</td></tr> | ||
<tr><td>0x08</td><td>???</td><td>Unknown (Always 0 on NES)</td></tr> | <tr><td>0x08</td><td>???</td><td>Unknown (Always 0 on NES)</td></tr> | ||
<tr><td>0x0a</td><td>UInt16LE</td><td>Offset to | <tr><td>0x0a</td><td>UInt16LE</td><td>Offset to 1 byte for the tileset index, followed by 16 bytes of palette data, and then the [https://www.nesdev.org/wiki/Tile_compression RLE encoded] nametable data (NES)</td></tr> | ||
<tr><td>0x0c</td><td>UInt16LE</td><td>Offset to | <tr><td>0x0c</td><td>UInt16LE</td><td>Offset to the RLE encoded attribute table (NES)</td></tr> | ||
<tr><td>0x0e</td><td>UInt16LE</td><td>Offset to mask flag value | <tr><td>0x0e</td><td>UInt16LE</td><td>Offset to the mask flag location. A value of <code>0x01</code> is followed by the RLE encoded mask data, otherwise <code>0x01</code>.</td></tr> | ||
<tr><td>0x10</td><td>???</td><td>Unknown (On NES both these values are equal)</td></tr> | <tr><td>0x10</td><td>???</td><td>Unknown (On NES both these values are equal)</td></tr> | ||
<tr><td>0x12</td><td>???</td><td>Unknown</td></tr> | <tr><td>0x12</td><td>???</td><td>Unknown</td></tr> | ||
Line 31: | Line 31: | ||
'''Note:''' Because of the following constraints: | '''Note:''' Because of the following constraints: | ||
* Offset to box number location is stored as an UInt8, thus must be within the first 255 bytes of the chunk | * Offset to box number location is stored as an <code>UInt8</code>, thus must be within the first 255 bytes of the chunk | ||
* The header takes 28 bytes | * The header takes 28 bytes | ||
* The combined size of object image and object | * The combined size of object image and object code offsets is 4 bytes per object | ||
The total number of objects per room must be less than 57: | The total number of objects per room must be less than 57: | ||
<pre>(256 - 28) / 4 = 57</pre> | <pre>(256 - 28) / 4 = 57</pre> | ||
== | == Content == | ||
=== Object image offsets === | === Object image offsets === | ||
From byte 0x1c starts the offsets to object images: | From byte <code>0x1c</code> starts the offsets to object images, 2 bytes per object: | ||
<table border="2" cellspacing="0" cellpadding="4"> | <table border="2" cellspacing="0" cellpadding="4"> | ||
<tr><th>Location</th><th>Format</th><th>Data</th></tr> | <tr><th>Location</th><th>Format</th><th>Data</th></tr> | ||
<tr><td>0x1c</td><td>UInt16LE</td><td>Offset to object 1 image location</td></tr> | <tr><td>0x1c</td><td>UInt16LE</td><td>Offset to object 1 image location</td></tr> | ||
<tr><td>0x1e</td><td>UInt16LE</td><td>Offset to object 2 image location</td></tr> | <tr><td>0x1e</td><td>UInt16LE</td><td>Offset to object 2 image location</td></tr> | ||
<tr><td>...</td><td>UInt16LE</td><td>Repeated objNum times</td></tr> | <tr><td>...</td><td>UInt16LE</td><td>Repeated <code>objNum</code> times</td></tr> | ||
</table> | </table> | ||
On the NES this is RLE encoded nametable and attribute table data based on the height and width of the object. The nametable data is encoded per row. These graphical updates are triggered in SCUMM by <code>setState08</code> and <code>clearState08</code>. Objects that have no state changes must still have an entry and can point arbitrarily. | |||
=== Object code offsets === | |||
Next starts the list of offsets to each object's data: | |||
<table border="2" cellspacing="0" cellpadding="4"> | <table border="2" cellspacing="0" cellpadding="4"> | ||
<tr><th>Location</th><th>Format</th><th>Data</th></tr> | <tr><th>Location</th><th>Format</th><th>Data</th></tr> | ||
<tr><td>0x1c + objNum * 2</td><td>UInt16LE</td><td>Offset to object 1 content location</td></tr> | <tr><td>0x1c + objNum * 2</td><td>UInt16LE</td><td>Offset to object 1 content location</td></tr> | ||
<tr><td> | <tr><td>0x1c + objNum * 2</td><td>UInt16LE</td><td>Offset to object 2 content location</td></tr> | ||
<tr><td>...</td><td>UInt16LE</td><td>Repeated objNum times</td></tr> | <tr><td>...</td><td>UInt16LE</td><td>Repeated <code>objNum</code> times</td></tr> | ||
</table> | </table> | ||
See table below for a description of the object data. | |||
=== Unknown data === | |||
At this point there are 4 bytes of unknown data on the NES. | |||
=== Number of boxes === | === Number of boxes === | ||
The boxes | The number of boxes (<code>boxNum</code>) in the room is located at the offset specified in the header. | ||
<table border="2" cellspacing="0" cellpadding="4"> | <table border="2" cellspacing="0" cellpadding="4"> | ||
Line 68: | Line 76: | ||
</table> | </table> | ||
'''Note:''' Some LFL files have unknown data between the last object | '''Note:''' Some <code>LFL</code> files have unknown data between the last object code offset and the boxes number offset location. For most files however, the data is contiguous. | ||
=== Boxes === | === Boxes === | ||
If the number of boxes is greater then 0 then 1 byte later starts the boxes payload. Each box has 8 bytes described as follows: | |||
<table border="2" cellspacing="0" cellpadding="4"> | <table border="2" cellspacing="0" cellpadding="4"> | ||
Line 85: | Line 93: | ||
<tr><td>0x07</td><td>UInt8</td><td>flags</td></tr> | <tr><td>0x07</td><td>UInt8</td><td>flags</td></tr> | ||
</table> | </table> | ||
The flags byte has various uses including: | |||
<table border="2" cellspacing="0" cellpadding="4"> | |||
<tr><th>Flag Value</th><th>Meaning</th></tr> | |||
<tr><td>0x08</td><td>X flip</td></tr> | |||
<tr><td>0x10</td><td>Y flip</td></tr> | |||
<tr><td>0x20</td><td>Unused in V1</td></tr> | |||
<tr><td>0x40</td><td>Locked</td></tr> | |||
<tr><td>0x80</td><td>Invisible</td></tr> | |||
</table> | |||
=== Boxes matrix table === | |||
The box matrix payload starts with an offset table that points to the beginning of each box's data. It is <code>boxNum</code> wide. | |||
=== Box matrix === | === Box matrix === | ||
Immediately following the last box | Immediately following the last box's data is the box matrix which determines how the boxes are connected. | ||
There are a minimum of <code>boxNum * boxNum</code> entries read as <code>UInt8</code>. Each entry consists of at least two pairs of three bytes which determine how the boxes connect. The table allows more values to be used in each entry if necessary. Example pseudo code: | |||
<pre>boxMatrix = [ | |||
[1, 1, 1, 1, 1, 0], | |||
[1, 2, 2, 2, 2, 1], | |||
[1, 2, 3, 3, 3, 2], | |||
[2, 2, 3, 4, 4, 3], | |||
[3, 3, 3, 4, 5, 4], | |||
[4, 4, 4, 4, 5, 5] | |||
]; | |||
function nextBox (from, to) | |||
{ | |||
var box = boxMatrix[from]; | |||
if (box[0] <= to && to <= box[1]) | |||
return box[2]; | |||
if (box[3] <= to && to <= box[4]) | |||
return box[5]; | |||
return -1; | |||
}</pre> | |||
=== Tileset index === | |||
The two bytes of room data at <code>0x0a</code> point to the tileset index which is used to look up the tileset details in a separate table. The tileset table provides the bank and memory offset for the given tileset. | |||
=== Palettes === | |||
One byte later starts the background palettes for a total of 16 bytes. | |||
=== | === Nametable data === | ||
Next starts the RLE encoded nametable data (NES). The room height and width at bytes <code>0x06</code> and <code>0x04</code> of the room data are used to determine how much data is decoded. | |||
=== Attribute table data === | |||
The two bytes at <code>0x0c</code> of the room data point to the RLE encoded attribute table (NES). The amount of data is decoded according to the room height and width. | |||
=== Mask Flag === | |||
Byte <code>0x0e</code> of the room data points to this mask flag. It has a value of <code>0x00</code> or <code>0x01</code>. | |||
=== Mask data === | |||
If the mask flag is <code>0x01</code> then the RLE encoded mask data is next. This is bitmap data where each bit corresponds to a single tile, so a single byte covers a row of 8 tiles. A bit value of 1 = masked (background priority), and 0 = unmasked (sprite priority). The bits in each byte are reversed (or read right to left) before they are applied. | |||
=== Object image data === | |||
Next starts the data that is pointed to by the object image offsets beginning at byte <code>0x1c</code> in the room data. This is RLE encoded nametable and attribute table data for each object. The amount of data for each object is based on the height and width of the object. The data is RLE encoded per row. These graphical updates are triggered in SCUMM by <code>setState08</code> and <code>clearState08</code>. | |||
=== Object code === | |||
The object code section has the following structure starting at its corresponding object code offset: | |||
==== Object code header ==== | |||
<table border="2" cellspacing="0" cellpadding="4"> | <table border="2" cellspacing="0" cellpadding="4"> | ||
<tr><th>Relative location</th><th>Format</th><th>Data</th></tr> | <tr><th>Relative location</th><th>Format</th><th>Data</th></tr> | ||
<tr><td>0x00</td><td>UInt8</td><td>Size of the object | <tr><td>0x00</td><td>UInt8</td><td>Size of the object code chunk</td></tr> | ||
<tr><td>0x02</td><td>???</td><td>Unknown (Always 0 on NES)</td></tr> | <tr><td>0x02</td><td>???</td><td>Unknown (Always 0 on NES)</td></tr> | ||
<tr><td>0x03</td><td>???</td><td>Unknown (Always 0 on NES)</td></tr> | <tr><td>0x03</td><td>???</td><td>Unknown (Always 0 on NES)</td></tr> | ||
<tr><td>0x04</td><td>UInt16LE</td><td>The object number (used to reference object in scripts)</td></tr> | <tr><td>0x04</td><td>UInt16LE</td><td>The object number (used to reference object in scripts). Because of the way the data is stored in-game, the object number must be 255 (<code>0xFF</code>) or less in order to be picked up.</td></tr> | ||
<tr><td>0x06</td><td>??</td><td>Unknown (Always 0 on NES)</td></tr> | <tr><td>0x06</td><td>??</td><td>Unknown (Always 0 on NES)</td></tr> | ||
<tr><td>0x07</td><td>UInt8</td><td>X position of object</td></tr> | <tr><td>0x07</td><td>UInt8</td><td>X position of object</td></tr> | ||
<tr><td>0x08</td><td>UInt8</td><td>Y position of object | <tr><td>0x08</td><td>UInt8</td><td>Object parent state + Y position of object (<code>0xSSSYYYYY</code>). Parent state is always <code>0x04</code> if present and is used in combination with the Object parent value at <code>0x0a</code> to hide objects behind doors (e.g. items in the refrigerator).</td></tr> | ||
<tr><td>0x09</td><td>UInt8</td><td>Object width in tiles</td></tr> | <tr><td>0x09</td><td>UInt8</td><td>Object width in tiles</td></tr> | ||
<tr><td>0x0a</td><td>UInt8</td><td>Object parent</td></tr> | <tr><td>0x0a</td><td>UInt8</td><td>Object parent</td></tr> | ||
<tr><td>0x0b</td><td>UInt8</td><td>Walk to X position</td></tr> | <tr><td>0x0b</td><td>UInt8</td><td>Walk to X position</td></tr> | ||
<tr><td>0x0c</td><td>UInt8</td><td>Walk to Y position</td></tr> | <tr><td>0x0c</td><td>UInt8</td><td>Preposition + Walk to Y position (<code>0xPPPYYYYY</code>). Possible preposition values are: <code>0x001</code> = in, <code>0x010</code> = with, and <code>0x011</code> = on.</td></tr> | ||
<tr><td>0x0d</td><td>UInt8</td><td> | <tr><td>0x0d</td><td>UInt8</td><td>Object height in tiles + Actor facing direction (<code>0xHHHHHDDD</code>). Possible facing values are: <code>0x100</code> = left, <code>0x101</code> = right, <code>0x110</code> = front (facing the player), and <code>0x111</code> = back (facing away from the player).</td></tr> | ||
<tr><td>0x0e</td><td>UInt8</td><td>Offset to object name</td></tr> | <tr><td>0x0e</td><td>UInt8</td><td>Offset to object name</td></tr> | ||
</table> | </table> | ||
The object name starts at specified | '''Note:''' The object code data starts at byte 15. The verb-script pairs terminate with a null byte (<code>0x00</code>). This means that the object name offset value is always an even number, 16 or above. | ||
==== Object code content ==== | |||
Object code content contains 3 optional sections, in this order: | |||
* 0 or more pairs of verb id and script offsets | |||
* The name of the object | |||
* 0 or more object scripts consisting of SCUMM bytecode | |||
===== Object script offsets ===== | |||
Starting at byte 15, two <code>UInt8</code> values are read at a time. | |||
The first value is a the verb ID and the second value is an offset to the beginning of the object script. Multiple verbs can have the same offset. The verb IDs are as follows: | |||
<table border="2" cellspacing="0" cellpadding="4"> | |||
<tr><th>Verb ID</th><th>Meaning</th></tr> | |||
<tr><td>0x01</td><td>Open</td></tr> | |||
<tr><td>0x02</td><td>Close</td></tr> | |||
<tr><td>0x03</td><td>Give</td></tr> | |||
<tr><td>0x04</td><td>Turn On</td></tr> | |||
<tr><td>0x05</td><td>Turn Off</td></tr> | |||
<tr><td>0x06</td><td>Fix (Unused)</td></tr> | |||
<tr><td>0x07</td><td>New Kid (Unused)</td></tr> | |||
<tr><td>0x08</td><td>Unlock</td></tr> | |||
<tr><td>0x09</td><td>Push</td></tr> | |||
<tr><td>0x0A</td><td>Pull</td></tr> | |||
<tr><td>0x0B</td><td>Use</td></tr> | |||
<tr><td>0x0C</td><td>Read</td></tr> | |||
<tr><td>0x0D</td><td>Walk To</td></tr> | |||
<tr><td>0x0E</td><td>Get</td></tr> | |||
<tr><td>0x0F</td><td>What is (Unused)</td></tr> | |||
<tr><td>0xFF</td><td>All</td></tr> | |||
</table> | |||
This part ends with <code>0x00</code>. | |||
===== Object name ===== | |||
The object name starts at the offset specified in byte <code>0x0e</code> of the object data. Each character is read until a <code>0x00</code> value is found. | |||
===== Object scripts ===== | |||
The object scripts are composed of SCUMM bytecode. Each object script ends with <code>0x00</code> (SCUMM opcode for <code>stopObjectCode()</code>). |
Latest revision as of 02:43, 17 May 2024
SCUMM V1
A room resource is located as the first chunk in a LFL File.
Header
The room resource starts with a 28 bytes header containing the following information:
Location | Format | Data |
---|---|---|
0x00 | UInt16LE | Size of the room resource chunk |
0x02 | ??? | Unknown |
0x04 | UInt16LE | Room width in tiles (always 0x1C or 0x3C ) |
0x06 | UInt16LE | Room height in tiles (always 0x10 on NES) |
0x08 | ??? | Unknown (Always 0 on NES) |
0x0a | UInt16LE | Offset to 1 byte for the tileset index, followed by 16 bytes of palette data, and then the RLE encoded nametable data (NES) |
0x0c | UInt16LE | Offset to the RLE encoded attribute table (NES) |
0x0e | UInt16LE | Offset to the mask flag location. A value of 0x01 is followed by the RLE encoded mask data, otherwise 0x01 . |
0x10 | ??? | Unknown (On NES both these values are equal) |
0x12 | ??? | Unknown |
0x14 | UInt8 | Number of objects in room |
0x15 | UInt8 | Offset to number of boxes location |
0x16 | UInt8 | Number of sounds in room (Always 0 on NES) |
0x17 | UInt8 | Number of scripts in room |
0x18 | UInt16LE | Offset to exit script location |
0x1a | UInt16LE | Offset to entry script location |
All offsets are relative to the start of the chunk.
Note: Because of the following constraints:
- Offset to box number location is stored as an
UInt8
, thus must be within the first 255 bytes of the chunk - The header takes 28 bytes
- The combined size of object image and object code offsets is 4 bytes per object
The total number of objects per room must be less than 57:
(256 - 28) / 4 = 57
Content
Object image offsets
From byte 0x1c
starts the offsets to object images, 2 bytes per object:
Location | Format | Data |
---|---|---|
0x1c | UInt16LE | Offset to object 1 image location |
0x1e | UInt16LE | Offset to object 2 image location |
... | UInt16LE | Repeated objNum times |
On the NES this is RLE encoded nametable and attribute table data based on the height and width of the object. The nametable data is encoded per row. These graphical updates are triggered in SCUMM by setState08
and clearState08
. Objects that have no state changes must still have an entry and can point arbitrarily.
Object code offsets
Next starts the list of offsets to each object's data:
Location | Format | Data |
---|---|---|
0x1c + objNum * 2 | UInt16LE | Offset to object 1 content location |
0x1c + objNum * 2 | UInt16LE | Offset to object 2 content location |
... | UInt16LE | Repeated objNum times |
See table below for a description of the object data.
Unknown data
At this point there are 4 bytes of unknown data on the NES.
Number of boxes
The number of boxes (boxNum
) in the room is located at the offset specified in the header.
Location | Format | Data |
---|---|---|
Offset to number of boxes | UInt8 | Number of boxes in room |
Note: Some LFL
files have unknown data between the last object code offset and the boxes number offset location. For most files however, the data is contiguous.
Boxes
If the number of boxes is greater then 0 then 1 byte later starts the boxes payload. Each box has 8 bytes described as follows:
Relative location | Format | Data |
---|---|---|
0x00 | UInt8 | uy |
0x01 | UInt8 | ly |
0x02 | UInt8 | ulx |
0x03 | UInt8 | urx |
0x04 | UInt8 | llx |
0x05 | UInt8 | lrx |
0x06 | UInt8 | mask |
0x07 | UInt8 | flags |
The flags byte has various uses including:
Flag Value | Meaning |
---|---|
0x08 | X flip |
0x10 | Y flip |
0x20 | Unused in V1 |
0x40 | Locked |
0x80 | Invisible |
Boxes matrix table
The box matrix payload starts with an offset table that points to the beginning of each box's data. It is boxNum
wide.
Box matrix
Immediately following the last box's data is the box matrix which determines how the boxes are connected.
There are a minimum of boxNum * boxNum
entries read as UInt8
. Each entry consists of at least two pairs of three bytes which determine how the boxes connect. The table allows more values to be used in each entry if necessary. Example pseudo code:
boxMatrix = [ [1, 1, 1, 1, 1, 0], [1, 2, 2, 2, 2, 1], [1, 2, 3, 3, 3, 2], [2, 2, 3, 4, 4, 3], [3, 3, 3, 4, 5, 4], [4, 4, 4, 4, 5, 5] ]; function nextBox (from, to) { var box = boxMatrix[from]; if (box[0] <= to && to <= box[1]) return box[2]; if (box[3] <= to && to <= box[4]) return box[5]; return -1; }
Tileset index
The two bytes of room data at 0x0a
point to the tileset index which is used to look up the tileset details in a separate table. The tileset table provides the bank and memory offset for the given tileset.
Palettes
One byte later starts the background palettes for a total of 16 bytes.
Nametable data
Next starts the RLE encoded nametable data (NES). The room height and width at bytes 0x06
and 0x04
of the room data are used to determine how much data is decoded.
Attribute table data
The two bytes at 0x0c
of the room data point to the RLE encoded attribute table (NES). The amount of data is decoded according to the room height and width.
Mask Flag
Byte 0x0e
of the room data points to this mask flag. It has a value of 0x00
or 0x01
.
Mask data
If the mask flag is 0x01
then the RLE encoded mask data is next. This is bitmap data where each bit corresponds to a single tile, so a single byte covers a row of 8 tiles. A bit value of 1 = masked (background priority), and 0 = unmasked (sprite priority). The bits in each byte are reversed (or read right to left) before they are applied.
Object image data
Next starts the data that is pointed to by the object image offsets beginning at byte 0x1c
in the room data. This is RLE encoded nametable and attribute table data for each object. The amount of data for each object is based on the height and width of the object. The data is RLE encoded per row. These graphical updates are triggered in SCUMM by setState08
and clearState08
.
Object code
The object code section has the following structure starting at its corresponding object code offset:
Object code header
Relative location | Format | Data |
---|---|---|
0x00 | UInt8 | Size of the object code chunk |
0x02 | ??? | Unknown (Always 0 on NES) |
0x03 | ??? | Unknown (Always 0 on NES) |
0x04 | UInt16LE | The object number (used to reference object in scripts). Because of the way the data is stored in-game, the object number must be 255 (0xFF ) or less in order to be picked up. |
0x06 | ?? | Unknown (Always 0 on NES) |
0x07 | UInt8 | X position of object |
0x08 | UInt8 | Object parent state + Y position of object (0xSSSYYYYY ). Parent state is always 0x04 if present and is used in combination with the Object parent value at 0x0a to hide objects behind doors (e.g. items in the refrigerator). |
0x09 | UInt8 | Object width in tiles |
0x0a | UInt8 | Object parent |
0x0b | UInt8 | Walk to X position |
0x0c | UInt8 | Preposition + Walk to Y position (0xPPPYYYYY ). Possible preposition values are: 0x001 = in, 0x010 = with, and 0x011 = on. |
0x0d | UInt8 | Object height in tiles + Actor facing direction (0xHHHHHDDD ). Possible facing values are: 0x100 = left, 0x101 = right, 0x110 = front (facing the player), and 0x111 = back (facing away from the player). |
0x0e | UInt8 | Offset to object name |
Note: The object code data starts at byte 15. The verb-script pairs terminate with a null byte (0x00
). This means that the object name offset value is always an even number, 16 or above.
Object code content
Object code content contains 3 optional sections, in this order:
- 0 or more pairs of verb id and script offsets
- The name of the object
- 0 or more object scripts consisting of SCUMM bytecode
Object script offsets
Starting at byte 15, two UInt8
values are read at a time.
The first value is a the verb ID and the second value is an offset to the beginning of the object script. Multiple verbs can have the same offset. The verb IDs are as follows:
Verb ID | Meaning |
---|---|
0x01 | Open |
0x02 | Close |
0x03 | Give |
0x04 | Turn On |
0x05 | Turn Off |
0x06 | Fix (Unused) |
0x07 | New Kid (Unused) |
0x08 | Unlock |
0x09 | Push |
0x0A | Pull |
0x0B | Use |
0x0C | Read |
0x0D | Walk To |
0x0E | Get |
0x0F | What is (Unused) |
0xFF | All |
This part ends with 0x00
.
Object name
The object name starts at the offset specified in byte 0x0e
of the object data. Each character is read until a 0x00
value is found.
Object scripts
The object scripts are composed of SCUMM bytecode. Each object script ends with 0x00
(SCUMM opcode for stopObjectCode()
).