We just moved to a different server. Please be patient until all files and pages are restored and the MediaWiki software has been updated. Thank you


From REWiki
(Redirected from .GRA)
Jump to: navigation, search

This is the basic scene container file format. In some versions (european DW1 CD version known) .SCN files are named .GRA. It is also used for dialogue containers (*.TXT).


basic container format

  • all values are little-endian
  • chunks:
u16 chunk_type
u16 magic /* 34 33 LSB, 0x3334 */
u32 offset /* of next chunk (absolute) */
u8* data /* length is determined by offset of next chunk */

32 bit addresses

cross-file addressing is done in a special way:

  • DW1:
    • 9 bit file id (=> INDEX order, addr & 0xFF800000 >> 23)
    • 23 bit offset (addr & 0x007FFFFF)
  • DW2:
    • 7 bit file id (=> INDEX order, addr & 0xFE000000 >> 25)
    • 25 bit offset (addr & 0x01FFFFFF)

chunk types

chunk ID given title DW1 DW2 DWN
0x0001 DIALOGUE x x x
0x0002 SCNFILE x x x
0x0003 BLOCK_LISTS x
0x0004 BLOCKS x
0x0005 PALETTES x x
0x0006 GRAPHICS_INFO x x x
0x0007 GRAPHICS_LIST x x x
0x0009 UNKNOWN x
0x000A SCRIPTS x x x
0x000B SCRIPT LIST 1 x x x
0x000D SCRIPT LIST 2 x x
0x000E SCENE INFO x x x
0x000F UNKNOWN x x
0x0012 UNKNOWN x
0x0013 UNKNOWN x
0x0018 UNKNOWN x
0x0019 GRAPHICS_DW2 x x
0x001B UNKNOWN x x
0x001C UNKNOWN x x
0x001D UNKNOWN x x
0x001E UNKNOWN x
0x0020 UNKNOWN x
0x0031 UNKNOWN x



  • multiple blocks (dialogue lines):
u8 length
char* text

Each chunk contains 64 entries. Each chunk is padded with 0x00 bytes (when necessary) to a DWORD boundary.



  • at beginning of every .scn file, no payload
  • not present in .TXT files



  • referenced by offset from section 0x0006
  • list of block indexes in section 0x0004 (u16 index), e.g. 320x200 image: (320 / 4) * (200 / 4) = 80 * 50 = 4000 blocks
  • some blocks have bit 0x8000 set, from objects.scn?



  • list of 4x4 pixel blocks (16 bytes per block)
  • pixel value is palette index (see section 0x0005)
  • referenced by index from section 0x0003



  • number of palettes determined by size of chunk: 1024 byte per palette:
typedef struct {
  uint8 r, g, b, unused;
  } color;

struct palette {



  • number of graphics is section size divided by 16
  • list of graphics:
uint16 width; /* lower 15 bit, highest bit unknown */
uint16 height; /* lower 15 bit, highest bit unknown */
uint32 unknown1;
uint32 offset;
uint32 offset_pal; /* lower 24 bit for DW2 */



  • list of offsets to section 0x0006:
uint32 offset;
uint32 unknown1; /* always 0x00000000? */



  • blocks of different length referenced from section 0x000A
  • new block often starts with 0x0000000c or 0x00000006
  • examples:


00051dec: 0x00000006 0x00000002 0x09051bac
00051df8: 0x09051bc4 0x09051ccc 0x09051ce4 0x09050240
00051e08: 0x00000001 0x00000159 0x00000000 0x00000000
00051e18: 0x00000002 0x09050240 0x00000001 0xfffffffe
00052e2c: 0x0000000c 0x00000002 0x09052b9c
00052e38: 0x09052bb4 0x09052ce4 0x09052cfc 0x09050d40
00052e48: 0x00000001 0x0000015d 0x00000000 0x00000000
00052e58: 0x00000002 0x09050d40 0x00000001 0xfffffffe


00052e68: 0x0000000c 0x00000001 0x09052e44 0x09052e5c

legend: offset in section 0x0007 | offset in section 0x0008 (this one)



  • some information here is just guessed and may be wrong
  • Tinsel uses a stack-based virtual machine to execute the scripts. Each opcode has no or one argument, which can be a byte, a uint16 or a uint32, depending on the opcode. Most opcodes have a variant for each argument type. The stack is made up of uint32 values.
  • Note that jump addresses are absolute to the script start, NOT the start of the script chunk!
  • Scripts always have no more than one RET opcode.
  • TODO: Sort the opcodes and document more.
opcode name argument(s) description
0x00 ret end of script, also used for padding


push uint32 push argument onto the stack
0x02 push 0 - push 0 onto the stack
0x03 push 1 - push 1 onto the stack
0x04 push -1 - push -1 onto the stack
0x0A getglobal uint32 push global var with arg as index onto the stack
0x4A getglobal byte push global var with arg as index onto the stack
0x8A getglobal uint16 push global var with arg as index onto the stack
0x0C setglobal uint32 pop value from stack into global var with arg as index
0x4C setglobal byte pop value from stack into global var with arg as index
0x8C setglobal uint16 pop value from stack into global var with arg as index
0x0E call uint32 call internal function with arg as index
0x4E call byte call internal function with arg as index
0x8E call uint16 call internal function with arg as index
0x10 addsp sint32 add arg value to stack pointer
0x50 addsp sint8 add arg value to stack pointer
0x90 addsp sint16 add arg value to stack pointer
0x91 jump uint16 jump to absolute address given in arg
0x92 jump_false uint16 jump to absolute address given in arg if value popped from stack is 0
0x93 jump_true uint16 jump to absolute address given in arg if value popped from stack is != 0
0x14 eq - equal to
0x15 lt - lower than
0x16 le - lower than or equal
0x17 neq - not equal
0x18 gt - greater than
0x19 ge - greater than or equal
0x1A add - addition
0x1B sub - subtraction
0x1C bool_or - boolean or
0x1D mul - multiplication
0x1E div - division
0x1F mod - modulo
0x21 or - binary or
0x22 xor - binary xor
0x23 bool_and - boolean and
0x24 is_false -
0x25 not - boolean not
0x26 neg - negation
0x27 dup - duplicates value on top of the stack



  • list of:
uint32 unknown1; /* flags? */
uint32 offset; /* script start address? */



  • list of polygons and links between them



  • list of:
uint32 unknown1; /* flags? */
uint32 unknown2; /* size? */ 
uint32 offset; /* script start address? */




  • this section is present in all .SCN files except dw.scn and objects.scn
  • the offset_* fields are the offset of the data part of the corresponding section, so it points to the bytes after the chunk type, magic and size
  • the offset_* fields are sometimes 0x00000000, if the section is not present
uint32 num_entries_0x000B;
uint32 polygonCount;
uint32 actorCount;
uint32 unknown2;
uint32 offset_0x000A;
uint32 offset_0x000B;
uint32 polygonResourceId;
uint32 actorResourceId;


The .SCN files in Discworld Noir are compressed with a LZSS-based algorithm. The files are a headerless bitstream. Single bits are read bytewise from MSB to LSB. Integer numbers are read from the bitstream in MSB to LSB order.

During decompression, a 4096-byte big window is maintained, and an offset into the window. The offet is in the range 0 to 4095 (assuming the window offset is zero-based).

When a byte is written to the output, it's also written to the window to the current window offset, and the window offset is increased by 1. Make sure the offset never exceeds 4095.

To decompress the data, repeat the following steps until the LZ-offset is -1:

 read a bit
 if bit is 0:
   read 'LZ-offset' = next 12 bits minus 1
   if 'LZ-offset' = -1 finish decompression
   read 'LZ-len' = next 4 bits plus 2
   copy 'LZ-len' bytes from the LZ window, starting at 'LZ-offset'
   (don't use memcpy etc. here since the bytes may overlap)
 if bit is 1:
   get 8 bits and write them to the output

An example implementation (Exe + C++ source) can be found at http://gamefileformats.the-underdogs.info/files/scnx-exe.zip and http://gamefileformats.the-underdogs.info/files/scnx-src.zip.

Personal tools