ScummVM logo Main website - Forums - BuildBot - Doxygen - Planet
Contact us - Buy Supported Games: GOG.com
Log in curved edge

SCUMM/Technical Reference/Costume resourcesSCUMM/Technical Reference/Costume resources

Costume formats

1. COST

Costumes store the animations used for characters in the game. The format is relatively complex. It basically consists of a bunch of frames and commands to make use of them :) I had a hard time really figuring out how this works.

  size               : 32le can be 0 (see num anim) or the size
                       (sometimes with an offset of one ??)
  header             : 2*8 always contain "CO"
  num anim           : 8  if(size) num_anim++
  format             : 8  bit 7 set means that west anims must NOT be mirrored,
                       bit 0 is the palette size (0: 16 colors, 1: 32 colors)
  palette            : 8*num colors coded in format
  anim cmds offset   : 16le access the anim cmds array
  limbs offset       : 16*16le access limb picture table
  anim offsets       : 16le*num anim  access anim definitions
  anim
    limb mask        : 16le
    anim definitions : variable length, one definition for each bit set
                       to 1 in the limb mask.
        0xFFFF       : 16le disabled limb code
     OR
        start        : 16le
        noloop       : 1
        end offset   : 7 offset of the last frame, or len-1
  anim cmds
    cmd              : 8
  limbs
    pict  offset     : 16le
  picts
    width            : 16le
    height           : 16le
    rel_x            : s16le
    rel_y            : s16le
    move_x           : s16le
    move_y           : s16le
    redir_limb       : 8 only present if((format & 0x7E) == 0x60)
    redir_pict       : 8 only present if((format & 0x7E) == 0x60)
    rle data

Each animation can have up to 16 limbs, each with its own sequence of pictures. The sequences are defined in the anim block. A definition starts with a 16le mask telling which limbs are used. Each bit of the mask stands for a limb. After the mask is a 16le index, giving the start position of the sequence in the anim cmds array. An index of 0xFFFF is used to turn off the limb; if the index is not 0xFFFF, then it's followed by the length of the sequence (8 bits). The highest bit of the length is used to indicate whether the sequence should loop, if it is set the animation doesn't loop.

So an animation is defined by a part of the anim cmds array. This array contains either picture numbers or some special values used to fire sounds, etc. Now to access the pictures, one has to look in the limb table. The table again contains offsets which finally allow us to find the right picture. So each limb has its own set of pictures, although sometimes several limbs share the same table.

As you can see, the format 0x60 has 2 extra fields in the picture header. If they are not 0xFF, they indicate a redirection to the picture redir_pict of the limb redir_limb. Also note that sometimes several entries in the limb table will point to the same picture.

It seems the data is always properly ordered. That is, the first picture of the first limb comes right after the last limb table. The first limb table start right after the cmd array, and so on. Currently this seems to be the only way to determine how long the cmd array is, or how long the last limb table is. Clumsy but it works, however a simple decoder doesn't need to compute these lengths :)

Note: All the offsets are relative to the block start (without the standard 8 byte SCUMM block header).

WARNING: When one numbers the limbs from their corresponding bit in the limb masks, they are then indexed in reverse order. This means the first entry in the limb table is limb 15, then comes limb 14, etc. The same holds for the anim definitions. Rendering should also be done in that order.

Note 2: The transparent color is always 0.

1.2 Commands

There are very few commands available and they have no arguments.

  • 0x71-0x78: addSound()

In Lucas games add sound cmd-0x71, in Humongous games add sound 0x78-cmd.

  • 0x79: stop()
  • 0x7A: start()
  • 0x7B: hide()
  • 0x7C: skipFrame()

1.3 RLE compression

The compression used for the costume data is a simple byte based RLE compression. However, it works by columns, not by lines. Each byte contains the color in the high bits and the repetition count in the low bits. If the repetition count is 0, then the next byte contains the actual repetition count. How many bits are used for the color depends on the palette size: for a 16 color palette, 4 bits are used for the color; for 32 colors, 5 bits are used.

  if(palette_size == 16) {
    shift = 4;
    mask = 0xF;
  } else {
    shift = 3;
    mask = 0x7;
  }

  while(1) {
    rep = read_byte();
    color = rep >> shift;
    rep &= mask;
    if(!rep)
      rep = read_byte();
    while(rep > 0) {
      set_pixel(x,y,color);
      rep--;
      y++;
      if(y >= height) {
        y = 0;
        x++;
        if(x >= width) break;
      }
    }
  }

1.4 Animations

Costume animations are always grouped in blocks of 4, one for each direction. The four directions are:

  • 0: WEST (or right)
  • 1: EAST (or left)
  • 2: SOUTH (or down)
  • 3: NORTH (or up)

Costumes used for normal actors have some predefined animations:

  • 00-03: unknown
  • 04-07: init
  • 08-11: walk
  • 12-15: stand
  • 16-19: talk start
  • 20-23: talk stop

ScummVM maps the following animations:

  • 56: init frame (04)
  • 57: walk frame (08)
  • 58: stand frame (12)
  • 59: talk start frame (16)
  • 60: talk stop frame (20)

There is also an assert preventing 62 (0x3E), but I really have no clue why.

When actor animations are selected these have a special effect:

  • 244-247: turn to new direction
  • 248-251: change direction immediately
  • 252-255: stop walking

So as far as I can tell, this leaves the ranges 24-55, 64-112 and perhaps 125-243 free for other animations. In DOTT, Hoaggi's costume uses the following:

  • 32-35: pick something up raising the arm
  • 36-39: pick something up in front
  • 40-43: pick something up off the ground
  • 44-47: smile

2. AKOS

AKOS, which are used in SCUMM 7 and 8, are much more powerful than the old COST and their data structure seems much cleaner. Could it be worst than the COST ? ;)

Note: some of this come from "LucasHacks":http://scumm.mixnmojo.com/!

2.1 Structure

An AKOS block is made of some block unlike the COST.

  • AKHD: Header
  • AKPL: Palette (optional)
  • RGBS: RGB Values (optional)
  • AKSQ: Commands sequence
  • AKCH: Anim offset table and definitions
  • AKOF: Offset table
  • AKCI: Frames definition
  • AKCD: Frames data

I found these in the code but not in comi, so i suppose it's only used by he engines.

  • AKCT: Condition table ???
  • AKST: Sequence table (set seq3, apparenly only used by he games) ???
  • AKSF: Sequence table (set seq1 and seq2, apparenly only used by he games) ???
  • AKFO: Sequence jump table, 16 bits list

2.2 Blocks content

2.2.1 AKHD

  unk1       : 16
  flags      : 8
  unk2       : 8
  num anims  : 16le
  num frames : 16le
  codec      : 16le

Flags: bit 0 is mirror, bit 1 is the number of directions the costume have (4 or 8)

2.2.2 AKPL

AKPL blocks are just a list of 8 bits values.

2.2.3 RGBS

RGBS blocks are just a list of 8 bits triplet coding colors.

2.2.4 AKOF

  frames
    AKCD offset : 32le
    AKCI offset : 16le

2.2.5 AKCI

  frames
    width  : 16le
    height : 16le
    rel_x  : s16le
    rel_y  : s16le
    move_x : s16le
    move_y : s16le

2.2.6 AKCD

Just all the commpressed frames packed together.

2.2.7 AKCH

The AKCH block define the various animations, they are pretty similar to the anim definitions in the COST. It start with an offset table giving access to all entries, followed by all definitions. The definitions start with a mask indicating which limb are active, followed by the actual limb definitions.

  offset table    : num anims time
    offset        : 16le
  definitions     : num anims time
    limb mask     : 16le
      mode        : 8
      start       : 16le
      len         : 16le

The start and end are there only if mode is not equal to 1, 4 or 5.

mode

  • 0: Disabled
  • 1: Single frame, no commands ???
  • 2: Loop
  • 3: Play once
  • 4: Stop limb
  • 5: Start limb
  • 6: Ignore first commande/pic mode ?
  • 7: Unk
  • 8: Same as mode 6 ?

2.2.8 AKSQ

In AKOS the picture index are mixed with the commands like in the COST, they are found in the AKSQ block. The AKOS have a lot more commands than the old COST so the stream is a mix of 8 and 16 bits values. It is read with something like this:

  code = p[0];
  if(code & 0x80)
    code = (code << 8) | p[1];

8 bits values are always picture index. All 16 bits value with 0xC0 in the MSB are commands, the rest are picture index to which a mask of 0xFFF is applied.

2.3 Commands

  • 0xC001: return()
  • 0xC010: setVar(word value,byte *var)
  • 0xC015: startSound(byte snd)
  • 0xC016: ifVarSoundIsRunning(uword jmp,byte *snd)
  • 0xC017: ifVarSoundIsNotRunning(uword jmp,byte *snd)
  • 0xC018: ifSoundIsRunning(uword jmp,byte snd)
  • 0xC019: ifSoundIsNotRunning(uword jmp,byte snd)
  • 0xC020: complexChan(word ???,byte ???)

Use an alternative rendering mode, apparently it render the limb several time, moving it betwen each frame.

  • 0xC021: ???(byte narg,...)

load seq3Idx with the list and switch to complexChan limb rendering

  • 0xC022: ???(byte narg,...)

load seq3Idx with the list and switch to complexChan2 limb rendering

  • 0xC025: complexChan2(word ???,word ????)

Similar to complexChan but with a subtile difference, which ??

  • 0xC030: jump(uword jmp)
  • 0xC031: jumpIfSet(uword jmp,byte *var)
  • 0xC040: addVar(word val,byte *var)
  • 0xC042: startSound(byte snd)
  • 0xC044: startVarSound(byte *snd)
  • 0xC045: setUserCondition(byte narg,byte slot,byte *set)
  • 0xC046: isUserConditionSet(byte narg,byte slot,byte *ret)
  • 0xC047: setTalkCondition(byte narg,byte slot)
  • 0xC048: isTalkConditionSet(byte narg,byte slot,byte *ret)
  • 0xC050: ignore()
  • 0xC060: incVar0()
  • 0xC061: startSound0()
  • 0xC070: jumpE(uword jmp,byte *a, word b)
  • 0xC071: jumpNE(uword jmp,byte *a, word b)
  • 0xC072: jumpL(uword jmp,byte *a, word b)
  • 0xC073: jumpLE(uword jmp,byte *a, word b)
  • 0xC074: jumpG(uword jmp,byte *a, word b)
  • 0xC075: jumpGE(uword jmp,byte *a, word b)
  • 0xC080: startAnim(byte anim)
  • 0xC081: startVarAnim(byte *anim)
  • 0xC082: random(word min,word max,byte *ret)
  • 0xC083: setActorClip(byte val)
  • 0xC084: startAnimInActor(byte *actor,byte *anim)
  • 0xC085: setVarInActor(byte *actor,byte *var,word val)
  • 0xC086: hideActor()
  • 0xC087: setDrawOffs(word a,word b)
  • 0xC088: jumpTable(byte *entry)
  • 0xC089: soundStuff(byte snd,byte ???,byte cmd,byte ???,byte val)
  • 0xC08A: flip(word val)
  • 0xC08B: cmd3(byte ???,byte ???, byte ???, byte ???)
  • 0xC08C: ignore3(byte ???)
  • 0xC08D: ignore2(byte ???)
  • 0xC08E: resetVolume(word snd)
  • 0xC090: skipE(word a, byte *b)
  • 0xC091: skipNE(word a, byte *b)
  • 0xC092: skipL(word a, byte *b)
  • 0xC093: skipLE(word a, byte *b)
  • 0xC094: skipG(word a, byte *b)
  • 0xC095: skipGE(word a, byte *b)
  • 0xC09F: clearFlag()
  • 0xC0A0: resetPan(byte snd,byte ???)
  • 0xC0A1: jumpIfTalking(uword jmp)
  • 0xC0A2: jumpIfNotTalking(uword jmp)
  • 0xC0A3: resetVarPan(byte *snd)
  • 0xC0A4: ???
  • 0xC0A5: ???
  • 0xC0A6: ???
  • 0xC0A7: ???
  • 0xC0FF: enqSeq

2.4 Codecs

There are 4 known codec: 1, 5, 16 and 32. The latter is only supported by HE engines.

2.4.1 Codec 1

This codec seems identical to the one used in the old COST, except it also support 64 colors. I suppose it's not very efficient in 64 colors mode as any repeat of more than 3 will need 2 bytes.

2.4.2 Codec 5

This one use the same encoding as BMOP image. See Scumm Image formats.

2.4.3 Codec 16

  bpp = read_byte();
  color = read_byte();
  repeat = 0;
  while(pos < width*height) {
    write_pixel(pos%width,pos/width,color);
    if(repeat > 0) repeat--;
    else if(read_bit()) {
        if(read_bit()) {
          delta = read_bits(3);
          if(delta != 4)
            color += delta-4;
          else
            repeat = read_bits(8) - 1;
        } else
            color = read_bits(bpp);
    }
    pos++;
  }

 

curved edge   curved edge