Age of Wonders
Most recent update (All By Hand(TM)): 5-Dec-2007 23:20

 

I wipe it off on tile, the light is brighter this time
Everything is 3D blasphemy
My eyes are red and gold, the hair is standing straight up
This is not the way I pictured me

Slipknot, Wait and Bleed

 

Reading ILBs

As you can see in the previous article, reading ILBs is not as straightforward as one might hope. I spend a lot of time "getting it right", and one of the checks I had was my home-grown ILB dump utility, a program that shows as much information about each part of the file as possible, down to the very last bit.

The data dumper I describe next reads and displays everything. If you're just interested in displaying the images, that's a bit of overkill – you won't need every item for that –, but you'll still need to read everything, even if you throw it away immediately. If you forget to interpret just a single byte, your reading routine is off track, and the data after that won't make any sense anymore.

The program runs from the command line and accepts a single argument: the name of the file you want to check out (be sure to specify its full path if the ILB file is in another folder). Every line of output is written to stdout; that's the screen (for fast readers). Alternatively, you can redirect the output into a text file with this: dumpilb image.ilb >> image.txt will write the output to (not surprisingly) a text file called image.txt, to be loaded in your favourite text editor and read at your leisure.

As it runs from the command line only – no fancy GUI or graphics needed – you can compile and run it on your Mac and/or Linux machines as well; I think it's platform and CPU independent at the mo'.

I'll try to cut the smalltalk inbetween to a bare minimum, although I can't promise anything solid as I make this page up while writing :)

I'm assuming basic knowledge of C++ programming, with plenty of commenting all around (so unlike my actual programming style!). If you don't know how or where to enter code, how to compile it into an executable, or how to run it afterwards, you're on your own and probably even shouldn't be reading these pages.

Getting things started

Usually, I start by deciding which #includes I need and jump straight to main. As this is a straightforward program without any non-obvious subroutines, I'll do the same here. It is considered "bad style" by those-who-know, but frankly I can't be bothered for such a linear task as this one.

#include <stdio.h>  // We're reading files
                    // Nothing else needed!

// First, a tiny subroutine to read 4 byte integers,
// required to make it CPU independent. Actually, you
// could simply use fread(integer,1,4,f) on any Win machine
int read_int (FILE *f)
{
  unsigned int a,b,c,d;

  a = fgetc(f);
  b = fgetc(f);
  c = fgetc(f);
  d = fgetc(f);

  return (signed int)(a | (b<<8) | (c<<16) | (d<<24));
}

// The classic invocation for a command line program:
void main (int argc, char **argv)
{
// We need a FILE object to read from.
  FILE *file;
// ..as well as a few variables
  int h,i, type, InfoByte, size, numPalette, isComposite;

// Let's first check if the command line was properly formed
  if (argc != 2)
  {
    printf ("You forgot this program needs one single input.\n"
            "Supply a filename (and only one)\n");
    return;
  }

// It'd better be a file that can be read
  file = fopen (argv[1], "rb");
  if (file == NULL)
  {
    printf ("Unable to read your input %s\n", argv[1]);
    return;
  }

At this point, the file is ready to be read byte for byte.

The ILB header

We could read and check the first 4 bytes of the magic identifier one by one; but it's faster to do it in one go. I also don't bother about a floating point representation of the version number, I just check against their (known) hex values.

For clarity, I display some values both in hex and in decimal where it may be relevant (such as for unknown values – they might make sense in either one). Unknown bytes are displayed in 2 hex digits, integers in their full 8 digit form.

// Check the magic value
  h = read_int (file);
  if (h != 0x424C4904)
  {
    fclose (file);
    printf ("Your input %s is not an ILB file\n", argv[1]);
    return;
  }
  printf ("Magic ID: %08Xh\n", h);

// Read & discard unknown value
  h = read_int (file);
  printf ("Unknown: %08Xh (%d)\n", h, h);

// Read version number
  h = read_int (file);
  if (h == 0x40400000)
      printf ("Version: %08Xh (3.0)\n", h);
  else
    if (h == 0x40800000)
      printf ("Version: %08Xh (4.0)\n", h);
    else
    {
      fclose (file);
      printf ("Unknown version %08Xh\n", h);
      return;
    }

// Read header length
  h = read_int (file);
  printf ("Hdr Length: %08Xh (%d)\n", h, h);

// v3.0 headers are done. If that last value
// was 24, it's a v4.0 header
  if (h != 16 && h != 24)
  {
      fclose (file);
      printf ("Unknown header length!\n");
      return;
  }
// Display extra info
  if (h == 16)
  {
    // Unknown (palette?)
    h = read_int (file);
    printf ("Unknown: %08Xh (%d)\n", h, h);
  }
  if (h == 24)
  {
    h = read_int (file);
    printf ("Img Directory: %08Xh (%d) bytes\n", h, h);
    h = read_int (file);
    printf ("File length: %08Xh (%d) bytes\n", h, h);

    // We save the number of palettes, so
    // if needed, we can check if they're there.
    numPalette = read_int (file);
    printf ("Palettes: %d\n", numPalette);
    // Read the palettes
    while (numPalette-- > 0)
    {
      i = read_int (file);
      printf ("  Palette id: %08Xh\n", i);
      if (i != 0x88801B18)
      {
        fclose (file);
        printf ("Unknown palette id!\n");
        return;
      }
      // Skip actual palette data (no need to show'em)
      for (i=0; i<256; i++)
      {
        read_int (file);
      }
    }
  }

And with that we're at the end of the file header. That last skip palette loop could be avoided in a number of ways, for example with fseek (file, 1024, SEEK_CUR), but this way it's obvious what happens.

The mini image header

This part starts the looping over all images. We set the isComposite flag to zero first, because composites require some extra handling. It will be set to 1 if we enter a composite image, and to zero again when it's done.

  isComposite = 0;

  // This is an endless loop.
  while (1)
  {

// Skip the id for composites!
// A simple check for not zero:
    if (!isComposite)
    {
      // This images' identifier
      printf ("================\n");
      h = read_int (file);
      printf ("Id: %08Xh (#%d)\n", h, h);
    } else
    {
      printf ("----------------\n");
    }

    // We're done if this is an end code.
    // If so, break out of the endless loop.
    if (h == 0xffffffff)
    {
      // This should not be happening:
      if (isComposite)
      {
        printf ("Error: End of file in a composite!\n");
      }
      break;
    }

    // Either isComposite or an image type
    // We need the type a few times, so
    // save it in another variable.
    type = read_int (file);
    if (type == 256)
    {
      // Yup. Set the flag, read actual type.
      printf ("Composite image: %08Xh\n", type);
      isComposite = 1;
      type = read_int (file);
    }
    printf ("Image type: %d = ", type);
    // Check for empty image or end of composite
    if (type == 0)
    {
      // Yup. Reset the flag and loop to the beginning.
      if (isComposite)
        printf ("End composite.\n");
      else
        printf ("Empty.\n");
      isComposite = 0;
      // Skip the end value first
      h = read_int (file);
      printf ("Empty flag: %08Xh\n\n", h);
      continue;
    }

  // Test type; stop on unknown ones
    switch (type)
    {
      case  1:
        printf ("Picture08\n");
        fclose (file);
        return;
      case  2: printf ("RLESprite08\n"); break;
      case 16: printf ("Picture16\n"); break;
      case 17: printf ("RLESprite16\n"); break;
      case 18: printf ("TransparentRLESprite16\n"); break;
      case 19:
        printf ("BitMask\n");
        fclose (file);
        return;
      case 20:
        printf ("Shadow\n");
        fclose (file);
        return;
      case 21:
        printf ("TransparentPicture16\n");
        fclose (file);
        return;
      case 22: printf ("Sprite16\n"); break;
      default:
        printf ("unknown\n");
        fclose (file);
        return;
    }

Now we're well underway, we start reading the type-dependent data. Fortunately, we don't have to repeat the entire first part for each separate image, as the format of the first elements is common to all images.

Common image data

    // A single important byte
    InfoByte = fgetc (file);
    printf ("InfoByte: %d\n", InfoByte);

  // The length of the name
    h = read_int (file);
    printf ("Name length: %d bytes\n", h);
  // Basic sanity checking...
    if (h < 0 || h > 100)
    {
        printf ("Rather unrealistic, I'm afraid.\n");
        fclose (file);
        return;
    }
    printf ("Name: ");
  // A simple reading loop
  // You could check if the characters are
  // all valid for a file name.
    while (h-- > 0)
    {
      i = fgetc (file);
      printf ("%c", i);
    }
    printf ("\n");
  // Basic image parameters
    h = read_int (file);
    printf ("Image width: %d\n", h);
    h = read_int (file);
    printf ("Image height: %d\n", h);
    h = read_int (file);
    printf ("X offset: %d\n", h);
    h = read_int (file);
    printf ("Y offset: %d\n", h);

  // Only of interest for composites:
    h = read_int (file);
    printf ("Subid: %d\n", h);

  // Our first unknown byte!
    h = fgetc (file);
    printf ("UnknownA: %02Xh (%d)\n", h, h);

  // The image data size; save it for now
    size = read_int (file);
    printf ("Data size: %d bytes\n", size);

  // If InfoByte = 1 (and the version is 3.0)
  // there is no data offset
    if (InfoByte != 1)
    {
      h = read_int (file);
      printf ("Data offset: %d\n", h);
    }

  // The offset width and height
    h = read_int (file);
    printf ("Offset width: %d\n", h);
    h = read_int (file);
    printf ("Offset height: %d\n", h);

For the name length check, I originally had if (h <= 0), but to my dismay there doesn't have to be an image name (saw that one at least once). So this will have to do. The name actually never exceeds some 20 bytes, but hey well, it's a format checking program.

We need to save the size, so we can skip the data after the image info for a v3.0 file.

Image-type specific data

The next part also has a few lines repeated, but it gets a bit clumsy to avoid them at all. This is just easier.

  // A switch on each image type
  // No need to check again for unknown types.
    switch (type)
    {
      case  2:  // RLESprite08
        // Unknown byte and 2 ints
        h = fgetc (file);
        printf ("UnknownB: %02Xh (%d)\n", h, h);
        h = read_int (file);
        printf ("UnknownC: %08Xh (%d)\n", h, h);
        h = read_int (file);
        printf ("UnknownD: %08Xh (%d)\n", h, h);
        // Palette number to use
        h = read_int (file);
        printf ("Palette #: %d\n", h);
        // Palette sanity check!
        // I don't consider this fatal--for now...
        if (h < 0 || h >= numPalette)
        {
          printf ("Palette number out of range!\n");
        }
        h = read_int (file);
        printf ("Clipwidth: %d\n", h);
        h = read_int (file);
        printf ("Clipheight: %d\n", h);
        h = read_int (file);
        printf ("Clip X offset: %d\n", h);
        h = read_int (file);
        printf ("Clip Y offset: %d\n", h);
        h = read_int (file);
        printf ("Transparency index: %d\n", h);
        // A final unknown integer
        h = read_int (file);
        printf ("UnknownE: %08Xh (%d)\n", h, h);
        break;

      case 16:  // Picture16
        // Blend info only if InfoByte is 3
        if (InfoByte == 3)
        {
          h = read_int (file);
          printf ("ShowMode: %08Xh", h);
          // Parse this into its components
          switch (h & 0x000000ff)
          {
            case 0: printf (" = smOpaque"); break;
            case 1: printf (" = smTransparent"); break;
            case 2: printf (" = smBlended, ");
              // Show extra blend info
              // Include unknown ones just for good measure
              switch ((h >> 8) & 0x000000ff)
              {
                case 0: printf ("bmUser"); break;
                case 1: printf ("bmAlpha"); break;
                case 2: printf ("bmBrighten"); break;
                case 3: printf ("bmIntensity"); break;
                case 4: printf ("bmShadow"); break;
                case 5: printf ("bmLinearAlpha"); break;
                default: printf ("Unknown");
              }
              break;
            default: printf (" -- Unknown");
          }
          printf ("\n");
          // This one is always there, although
          // only used with smBlended mode
          h = read_int (file);
          printf ("BlendValue: %d\n", h);
        }
        h = read_int (file);
        printf ("PixelFormat: %08Xh\n", h);
        break;

      // The next two contain the same data:
      case 17:  // RLESprite16
      case 18:  // TransparentRLESprite16
        // Blend info only if InfoByte is 3
        if (InfoByte == 3)
        {
          h = read_int (file);
          printf ("ShowMode: %08Xh", h);
          // Parse this into its components
          switch (h & 0x000000ff)
          {
            case 0: printf (" = smOpaque"); break;
            case 1: printf (" = smTransparent"); break;
            case 2: printf (" = smBlended, ");
              // Show extra blend info
              // Include unknown ones just for good measure
              switch ((h >> 8) & 0x000000ff)
              {
                case 0: printf ("bmUser"); break;
                case 1: printf ("bmAlpha"); break;
                case 2: printf ("bmBrighten"); break;
                case 3: printf ("bmIntensity"); break;
                case 4: printf ("bmShadow"); break;
                case 5: printf ("bmLinearAlpha"); break;
                default: printf ("Unknown");
              }
              break;
            default: printf (" -- Unknown");
          }
          printf ("\n");
          // This one is always there, although
          // only used with smBlended mode
          h = read_int (file);
          printf ("BlendValue: %d\n", h);
        }
        h = read_int (file);
        printf ("PixelFormat: %08Xh\n", h);
        h = read_int (file);
        printf ("Clipwidth: %d\n", h);
        h = read_int (file);
        printf ("Clipheight: %d\n", h);
        h = read_int (file);
        printf ("Clip X offset: %d\n", h);
        h = read_int (file);
        printf ("Clip Y offset: %d\n", h);
        h = read_int (file);
        printf ("Transparent colour: %08Xh\n", h);
        // That unknown integer again
        h = read_int (file);
        printf ("UnknownE: %08Xh (%d)\n", h, h);
        break;

      // The same, without that last integer:
      case 22:  // Sprite16
        // Blend info only if InfoByte is 3
        if (InfoByte == 3)
        {
          h = read_int (file);
          printf ("ShowMode: %08Xh", h);
          // Parse this into its components
          switch (h & 0x000000ff)
          {
            case 0: printf (" = smOpaque"); break;
            case 1: printf (" = smTransparent"); break;
            case 2: printf (" = smBlended, ");
              // Show extra blend info
              // Include unknown ones just for good measure
              switch ((h >> 8) & 0x000000ff)
              {
                case 0: printf ("bmUser"); break;
                case 1: printf ("bmAlpha"); break;
                case 2: printf ("bmBrighten"); break;
                case 3: printf ("bmIntensity"); break;
                case 4: printf ("bmShadow"); break;
                case 5: printf ("bmLinearAlpha"); break;
                default: printf ("Unknown");
              }
              break;
            default: printf (" -- Unknown");
          }
          printf ("\n");
          // This one is always there, although
          // only used with smBlended mode
          h = read_int (file);
          printf ("BlendValue: %d\n", h);
        }
        h = read_int (file);
        printf ("PixelFormat: %08Xh\n", h);
        h = read_int (file);
        printf ("Clipwidth: %d\n", h);
        h = read_int (file);
        printf ("Clipheight: %d\n", h);
        h = read_int (file);
        printf ("Clip X offset: %d\n", h);
        h = read_int (file);
        printf ("Clip Y offset: %d\n", h);
        h = read_int (file);
        printf ("Transparent colour: %08Xh\n", h);
        break;
    }

The final bits

At this point all information is shown. If the ILB version is 3.0h, we need to skip the image data, followed by a closing flag 0xFFFFFFFF. If not, the close flag follows immediately.

  // Test if we need to skip data;
  // this we needed to save the size for.
  // A simple file reposition will do.
    if (InfoByte == 1)
    {
      fseek (file, size, SEEK_CUR);
    }
    if (isComposite) continue;
  // We should be at the end of an image.
  // If not, something is wrong.
    h = read_int (file);
    if (h != 0xffffffff)
    {
      printf ("Unexpected end value %08Xh!\n", h);
      fclose (file);
      return;
    }
    printf ("\n");

Looping and closing down

All the information for the first image is now read. At this point, there are two possibilities: the image is a standalone, so we should loop to reading the next image id, or it is part of a composite, and we should loop to reading the isComposite flag.

Since all checks are done at the start of the loop, all that's needed here is a single }, and the program jumps to the start of the endless loop.

The loop breaks on an end-of-header code (an Id of 0xffffffff), so all there is left is to close the file.

  }  // End of the while loop

  fclose (file);
  printf ("Successfully ended!\n");
}

Source, output, and signing off

You are free to copy all the code above and paste it together in a single file, to compile at will. But for your convenience, here is both source and a Windows compatible executable in one handy package.

Output of one of the smaller files should look like this:

>dumpilb "C:\Games\Age Of Wonders\Images\str_amgic.ilb"

Magic ID: 424C4904h
Unknown: 0040DB00h (4250368)
Version: 40800000h (4.0)
Hdr Length: 00000018h (24)
Img Directory: 0000007Fh (127) bytes
File length: 00009A57h (39511) bytes
Palettes: 0
================
Id: 00000000h (#0)
Image type: 18 = TransparentRLESprite16
InfoByte: 2
Name length: 13 bytes
Name: str_amgic.BMP
Image width: 194
Image height: 151
X offset: 0
Y offset: 0
Subid: 0
UnknownA: 01h (1)
Data size: 39384 bytes
Data offset: 0
Offset width: 194
Offset height: 151
PixelFormat: 56509310h
Clipwidth: 184
Clipheight: 133
Clip X offset: 7
Clip Y offset: 14
Transparent colour: 0000000Fh
UnknownE: 00000000h (0)

================
Id: FFFFFFFFh (#-1)
Successfully ended!

 


That concludes reading the ILB header. In the future, I'assuming all of this as known, so I might refer to items such as "the clipwidth of the image should be added to..." – without specifying what it is and where you can find it. It's all a case of using your header.

Next time, we'll see how to draw some actual pixels, instead of lookin' at boring lists!


<< The ILB Format Reading ILBs

 

 

 

[Jongware]

If you feel I missed something, or perhaps just want to express your gratitude, contact me at the Jongware headquarters for all your praise, corrections, updates, and other stuff you think might be of interest to me.

E-mail address protected by MailTo Protector 3.0 in an attempt to ward off SPAM. If it doesn't work, this mailbox may be invalidated without further notice.

[Jongware] logo by Aernout Tas