Yet Another Gamecube Documentation

16  Compression Formats


index

16.1  Yay0


This format is used to store the fonts in the BIOS/IPL. It is compressed similar to the the Zelda 64 'Yaz0' compression.
index

16.1.1  compression


start end size description
0x0000   4 'Yay0' signature
0x0004   4 size of decoded data in bytes
0x0008   4 offset to link table
0x000c   4 offset to non-linked chunks and count modifiers table
0x0010     packed data (32 bit words)
 
 
The packed data is a bitstream (padded to a multiple of 32bits), with each bit having the following meaning:

0 linked chunk, copy block from the link table (offset 0x0008)
1 non linked chunk, copy next byte from non-linked chunks and count modifiers table (offset at 0x000c)


todo
index

16.1.2  de-compression Code


void Decode(void *s, void *d) 
{ 
u32 i, j, k; 
u32 p, q; 
u32 cnt; 
    i = r21 = *(u32 *)(s + 4); // size of decoded data 
    j = r29 = *(u32 *)(s + 8); // link table 
    k = r23 = *(u32 *)(s + 12); // byte chunks and count modifiers 
    q = r31 = 0; // current offset in dest buffer 
    cnt = r28 = 0; // mask bit counter 
    p = r24 = 16; // current offset in mask table 
    do 
    { 
        // if all bits are done, get next mask 
        if(cnt == 0) 
        { 
            // read word from mask data block 
            r22 = *(u32 *)(s + p); 
            p += 4; 
            cnt = 32; // bit counter 
        } 
        // if next bit is set, chunk is non-linked 
        if(r22 & 0x80000000) 
        { 
            // get next byte 
            *(u8 *)(d + q) = *(u8 *)(s + k); 
            k++; q++; 
        } 
        // do copy, otherwise 
        else 
        { 
            // read 16-bit from link table 
            r26 = *(u16 *)(s + j); 
            j += 2; 
            // 'offset' 
            r25 = q - (r26 & 0xfff); 
            // 'count' 
            r30 = r26 > > 12; 
            if(r30 == 0) 
            { 
                // get 'count' modifier 
                r5 = *(u8 *)(s + k); 
                k++; 
                r30 = r5 + 18; 
            } 
            else r30 += 2; 
            // do block copy 
            r5 = d + r25; 
            for(i=0; i<r30; i++) 
            { 
                *(u8 *)(d + q) = *(u8 *)(r5 - 1); 
                q++; r5++; 
            } 
        } 
        // next bit in mask 
        r22 < <= 1; 
        cnt--; 
    } while(q < i); 
} 
 
index

16.1.3  Font Data


start end size description
0x0000   2 Font Type
0x0002   2 first Character in Font
0x0004   2 last Character in Font
0x0006   2 Character to use for substituting invalid Characters
0x0008   2 ascent Units
0x000a   2 descent Units
0x000c   2 width of widest Character
0x000e   2 leading Space
0x0010   2 Cell width
0x0012   2 Cell Height
0x0014   4 Texture Size
0x0018   2 Texture Format
0x001a   2 Texture Columns
0x001c   2 Texture Rows
0x001e   2 Texture Width
0x0020   2 Texture Height
0x0022   2 offset to Character-width Table
0x0024   4 offset to Tile-Data
0x0028   4 Tile-Data Size

index

16.2  Yaz0


Yaz0 compression is reportedly used in quite a few Nintendo datafiles. I have seen it in SuperMario Sunshine's .szs files for example, and I heard that it is used in Windwaker and Majoras Mask as well. The first 16 bytes of a Yaz0-compressed data block are the data header. The first 4 bytes of the header are 'Y', 'a', 'z', '0', so you can easily see in your hex editor that there's a Yaz0 block waiting for you :-) The second 4 bytes are a single uint32 (big-endian of course) that tells you the size of the decompressed data, so you know how large your working buffer has to be. The next 8 bytes are always zero. Next comes the actual compressed data. Yaz0 is some kind of RLE compression. You decode it as follows: First you read a "code" byte that tells you for the next 8 "read operations" what you have to do. Each bit of the "code" byte represents one "read operation" (from left to right, that is, 0x80 first, 0x01 last). If the bit is 1, copy one byte from the input buffer to the output buffer. Easy. If the bit is 0, things are a little bit more complicated, RLE compressed data is ahead. You have to read the next two bytes to decide how long your run is and what you should write to your output buffer.
15 8 7 0
a b    

The upper nibble of the first byte (a) contains the information you need to determine how many bytes you're going to write to your output buffer for this "read operation". if a == 0, then you have to read a third byte from your input buffer, and add 0x12 to it. Otherwise, you simply add 2 to a. This is the number of bytes to write ("count") in this "read operation". byte2 and the lower nibble of byte1 (b) tell you from where to copy data to your output buffer: you move (dist = (b < &lt
8) - byte2 + 1) bytes back in your outputBuffer and copy "count" bytes from there to the end of the buffer. Note that count could be greater than dist which means that the copy source and copy destination might overlap.
index

16.2.1  de-compression Code


//src points to the yaz0 source data (to the "real" source data, not at the header!) 
//dst points to a buffer uncompressedSize bytes large (you get uncompressedSize from 
//the second 4 bytes in the Yaz0 header). 
 
void decode(u8* src, u8* dst, int uncompressedSize) 
{ 
int srcPlace = 0, dstPlace = 0; //current read/write positions 
u32 validBitCount = 0; //number of valid bits left in "code" byte 
u8 currCodeByte; 
    while(dstPlace < uncompressedSize) 
    { 
        //read new "code" byte if the current one is used up 
        if(validBitCount == 0) 
        { 
            currCodeByte = src[srcPlace]; 
            ++srcPlace; 
            validBitCount = 8; 
        } 
        if((currCodeByte & 0x80) != 0) 
        { 
            //straight copy 
            dst[dstPlace] = src[srcPlace]; 
            dstPlace++; 
            srcPlace++; 
        } 
        else 
        { 
            //RLE part 
            u8 byte1 = src[srcPlace]; 
            u8 byte2 = src[srcPlace + 1]; 
            srcPlace += 2; 
            u32 dist = ((byte1 & 0xF) < < 8) - byte2; 
            u32 copySource = dstPlace - (dist + 1); 
            u32 numBytes = byte1 > > 4; 
            if(numBytes == 0) 
            { 
                numBytes = src[srcPlace] + 0x12; 
                srcPlace++; 
            } 
            else 
                numBytes += 2; 
            //copy run 
            for(int i = 0; i < numBytes; ++i) 
            { 
                dst[dstPlace] = dst[copySource]; 
                copySource++; 
                dstPlace++; 
            } 
        } 
        //use next bit from "code" byte 
        currCodeByte < <= 1; 
        validBitCount-=1;  
    } 
} 
 
index