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

 

There was a time that the pieces fit,
but I watched them fall away.
Mildewed and smouldering,
strangled by our coveting.
I've done the math enough to know
the dangers of our second guessing;
doomed to crumble unless we grow
and strengthen our communication ...

Tool, Schism

 

The Graphics of Wonders

Age of Wonders is one of my all-time favourite Role Playing games; not in the least for the vast number of lovingly hand drawn graphics. There are twelve different races, each with its own distinct set of warriors and cities. The landscape consists of a beautiful diversity of grassland, forests, icescapes, and mountain ranges, both on the surface and in underground caves, and a whole set of "special" structures, such as the Altars and the Power nodes. No wonder: the graphics total over 100 Mb of data in more than 500 files. And in each graphics file, there can be hundreds of different images – spellicn.ilb, for example, contains 216 different images, 2 for each of the 108(!) spells in the game, each image consisting of a layer of transparency and an overlay in black.

 

The graphics file format has been the subject of much contemplation amongst the game fans. There already are a number of tools to display and/or extract images from most of the ILB files. For a taste of these fan works, visit the excellent Age of Wonders Heaven; there you can find forum discussions about every aspect of the game, fan maps to play, hand-made alterations to the game (the so-called "Rule sets"), and several useful utilities, as well as a few side remarks on the graphics file format. However, a full description is absent. No wonder, since it's quite complex – a tell-tale sign that the programmers and graphic designers made up the file format whilst programming the game.

 

How does one investigate a graphics format? A good starting point is a hex editor – not to edit the files, just to view its contents. It helps if you can write code, of course. I wrote my own years ago, and every now and then I insert one more useful function. But even the most basic hex viewer will do, and you can do even without that.

Do you need to be able to program graphics? Perhaps strangely enough, you don't. I did the major part of investigations using a text editor which could display program output – my text editor of recommendation is TextPad, as it allows external tools (such as a C compiler) to be run at the stroke of a key, and can capture the output of your program in a new window, ready for further examination.

 

Given the right tools, it's a simple task to write a program to view the contents of a file in pure hexadecimal encoding. When that's done, it comes down to staring at the hex bytes long enough to make sense of that jumble of numerics and letters, and that's one of my favourite parts! One byte at a time, I recognized numbers, names, references to other parts of the file, and constant values; piece by piece, I added lines of code to display these in a more regular format, right up until, by way of tour de force, I could display the graphics in glorious ASCII characters. At that point, I summarized the entire format into a C++ library, to be included in a full-fledged Windows application which displayed them "for real". Okay, 'nuff with the shoulder tapping! Let's get to the format description.

The ILB header

ILB files (this presumably stands for "Image Library") come in two different flavours (a 3rd data-less variant is used in release.hss). The version number in the header specifies the way data is stored. A side note: it's possible the newer variant was to supersede the older one, but the programmers never got around doing that. That happens more than you think.

The header is followed by actual image data, which is sort-of the same for both types, so that's described in a separate section. The different headers are distinguished by their version number only – there are a few more differences, but they appear to be more because of the versions, rather than signifying them.

In these pages, I use the term word or int for the native data size of your CPU. FYI, that's a 4 byte integer. To indicate a 2 byte value, I use the term short; with either byte or char I mean the conventional amount of 8 bits. The qualifiers signed and unsigned relate to their behaviour in a mathematical sense. I might as well tell you now I tend to override data size and signedness with another one just to shut the compiler up if the list of warnings gets too long. Numbers are usually in hex (with a 0x prefix or h suffix), but may be in decimal as well; hopefully, the context will make it obvious what it is.

ILB version 3.0

This is the older version, with just a few items.

int magic;         // This contains 0x04,"ILB"
int someId;        // Unknown what it's for
float version;     // This is 0x40400000, or 3.0 as 4 byte float
int hdrLength;     // This always contains the value 0x10
int unknown;       // Unknown (palette?)

The magic field is used to identify the file as an ILB. Its value is always 0x424C4904, the number "4" and the characters "ILB". One would expect the later AoW II to use a same sort of header, but oddly enough, that one starts right away with data. I guess the programmers reasoned "the file is already called 'ILB', so why double check?".

someId might be a further identification code, but I couldn't find any use for it, so I plainly ignore it. It's not always the same number, although the values have other ranges for 3.0 versus 4.0 headers.

As mentioned above, the version field contains the actual version as a 4 byte floating point value. Using a floating point means you could get all kinds of weird errors if you compare them, since not every floating point value can be expressed in 4 bytes. Fortunately, both "3.0" and "4.0" have an exact representation in hex. A tip: if you ever create your own graphics format, you'd better stick to integer values only.

The hdrLength field appears to hold the size in bytes of this header, excluding the last unknown value. I guess that's the right description, because it is 16 for this 3.0 header, and 24 for the 4.0 header. As the header sizes have a fixed size, I don't have any use for this either.

The unknown is zero for all 3.0 files I saw. The guess "palette" is based on this and v4.0 headers' size.

ILB version 4.0

This header contains a single new item which couldn't be stuffed into the older version; it allows one or more colour palettes to be shared among its 8 bit (indexed) images. The other two new values are for house-keeping only.

int magic;         // This contains 0x04,"ILB"
int someId;        // Still unknown what it's for
float version;     // 0x40800000, or 4.0
int hdrLength;     // This always contains the value 0x18, or 24 -- this header is 6 words long
int imgDirectory;  // The size of everything before the actual image data
int fileLength;    // The size of this entire file
int numPalette;    // The number of palettes following the file header

magic is the same as in version 3.0.

And so is someId (i.e., I still don't know what it means).

version contains, as explained before, the value 4.0

As you can see, hdrLength now is 24, and, indeed, this header is 24 bytes long, excluding the palette entry. It still might be a coincidence, but I'd need to see a version 5.0 header to confirm; all 3.0 headers have a 16, all 4.0 headers have a 24.

imgDirectory is a house-keeping number. In case you don't read the entire image directory (or got lost while reading), you can find out where the data starts: it's simply at imgDirectory bytes from the start of the file (this value includes the 24 bytes of the header itself, as well as the total size of all palettes).

fileLength is one of those seemingly good ideas. Perhaps the programmers had a wonky internet connection, and found files might be truncated. No actual need for this one.

The final one, numPalette, is a good idea. The file might contain indexed colour images, and these need a colour palette. Of course you can store the palette along with the image itself, but it's cheaper to store a shared palette once, and in the image just tell which one to use (there may be more than one palette per ILB).

The palettes

Only in v4.0 files I encountered something inbetween the header and the image data; these are (guess!) the shared palettes.

A fast 101 on palettes and indexed colour: an indexed colour image consists not of true colour values, but of indexes into a colour table. Usually, the image uses one single byte per pixel, so the colour table may have up to 256 entries. When drawing the image, for every value n you fetch colour palette entry n. What that entry contains, is implementation defined (although usually, it's an RGB value). Another well-known name for palettes is Colour Look-up Table (or briefly, CLUT).

The programmers of AoW thought it a good idea to precede the palette with a further identifying word. The idea probably was "we might have a few different formats of palettes, so we'd better say what it is" – we'll see a similar reasoning for colour depth in images. This idea was never used, so each palette starts with the same identifier: 88801B18h. It seems random, but if you look closely, you'll find the first (high) 2 bytes contain the number of bits per colour channel (8 for red, 8 for green, 8 for blue), and the final byte contains the size of one entry (24). I don't have a clue about the value '27' in the middle... The identifier is the same for all colour palettes in all images, so there is not enough variation to make heads or tails of it; I just check if it's there, and if so, the palette data follows. (If it's not there, there is an error in the file, since the header specifies how many palettes should follow. Or, more likely, an error in your program!)

Directly following the palette identifier, the palette data itself follows. This always consists of 256 entries of Red, Green, and Blue, followed by a padding byte (which is always 0, but we don't care, do we?). The padding byte is there for a reason: first, most modern CPUs are uncomfortably slower when reading data from odd-numbered addresses (be assured, it's nothing you ordinarily should notice). Secondly, if you want to fetch a value from this table, you take the index number and shift it left twice to get the index value times 4, instead of multiply by 3 – shifting a number is faster than multiplying. Another likely reason is this is the same data order as prescribed by Windows – the entire palette can be copied at once into a corresponding Windows structure.

The total size of a single palette part is 256 * sizeof(palette_entry) plus 4 bytes – one palette entry contains 4 bytes, and the final 4 is the size of the identifier. The total size of the entire palette section is numPalette * 1028 bytes; if numPalette is zero, there is no data at all and the file continues with image data. Whatever the case, at this point you should be at the position imgDirectory from the header.

The image directory

Now we successfully circumnavigated the file header and palettes, we come to the main part of the file. The image directory is a list of specifications for each separate image in the file, including a pointer to where the image data bytes can be found. As I told before, the two ILB versions store this image data at different locations; however, it seems to be specified in the image directory itself as well, so we probably can ignore the actual file version.

There are 5 rather different types of images in use (4 more are defined in the game, but not used in the ILBs), and as these are the most important piece of data, they will be explained in separate sections. But let's start at the beginning.

// The first few words
int Id;           // a unique number
int isComposite;  // optional flag; explained below
int Type;         // what kind of data we have
int ZeroFiller;   // an unknown; only if Type is zero
...               // image type specific data follows

The very first number is the image Id, a unique value which you can use to select images with. This is an alternative to index order, where you could simply draw image #2 of this file, followed by image #5, etc. The unique id means you can select images by it, so, for example, image id #2 is always the one with snow on it. If there is no image id #2 in your file, you must select another (and your game engine should specify what to do – perhaps select a default value, or do nothing). Some time later, I'll show how this id is used for animations in release.hss. One value of Id is a special one: an Id of 0xffffffff (or, shortly, -1) marks the end of the image directory.

Technically, the Id is optional, since it's not repeated for composite images. But I feel it's a bad thing to start a description with an optional item, so you'll have to bear with me; later on this will make sense.

The second value is a flag: isComposite has a constant value 256, and if it's there it means all next images should be processed right after this one. This creates layered images, where one part can be transparent (for example, a shadow), and another is opaque. As this is an optional number, it means you should read an integer and check if it has the value 256. If so, the entire image is composite; if not, that number was the final element of this small header.

That final value is Type, and identifies the type of image info and data that follows – one of the 9 different types. As with the Id, there is one special value: a 0 (zero) means the image has no data at all (apart from an unused integer, that is – nothing you can use). It is usually used to mark the end of a sequence of composite images, but there may be "undefined" images in the file. For a Type 0, skip the next integer; then you can omit any further parsing of data and continue with the next image (starting again at a new Id).

If Type is zero, it is followed by a single integer. It has various values for the empty images I saw, but as far as I can see there is no need or use for this value.

The optional isComposite makes things just a bit more complicated. It would have been easier to parse if this was simply a bit in the Type field (the Type field only uses one byte of the four available). Ahhh, hindsight ...

The different image types share a lot of data, but on the other hand there is also a lot of variety. I'll explain the entire data set for the first image type; for next ones I'm just gonna refer to the first full explanation. I know I could copy and paste the entire story six times, but it makes boring reading if I repeat myself all the time.

The structures described below contain highly variable amounts of data. First of all, one of the first items is an image name, which is stored Pascal style: an integer specifying the length, then that much characters (that should probably be "Delphi style"). Also, there are also a few optional elements, whose presence is dictated by special values in other fields. What does that mean? Well, you can't use a predefined structure to read the data in: the size of this image info is variable! So you'll have to parse it byte for byte.

Type 2: RLESprite08

This is the only colour indexed image type, and if used, there should be a palette in the ILB header. The "RLE" part of the name means the image data is packed (it stands for "Run Length Encoding"), something I'll come back to later on. Examples of this type: almost all of the images\units images, as well as the first ones in the images\races images. In these, the animated flags at the beginning are RLESprite08's – the rest of the images are high-colour ones, thus showing that you can mix different image types in one file.

In the units images you can see a good reason for colour indexed images: lots and lots of small images, all using a single palette. The only exceptions are a few half-transparent units – the Syron, the Air Elemental, the Wraith, and others – because for this image type you can't define transparency effects.

Its data looks like this:

char InfoByte;
int  nameLength;
char name[nameLength];
int  wide;
int  high;
int  xshift;
int  yshift;
int  subid;
char unknownA;
int  size;
int  offset;      // Not here if InfoByte is 1
int  totalwide;
int  totalhigh;
char unknownB;    // unique for Type 2 images -- always 0?
int  unknownC:    // always 1?  \ if InfoByte is 3
int  unknownD:    // always 0? _/
int  palette;     // /
int  clipwide;    // |
int  cliphigh;    // |
int  clipxoff;    // | These are required fields for Type 2s
int  clipyoff;    // |
int  transparent; // |
int  clip_x;      // \
...               // Image data if InfoByte is 1
int  lastWord;    // Always -1

Let's go through that one by one, shall we.

InfoByte controls whether there are a few extra fields. See below.

nameLength is the length of the following name, characters only (there is no closing 0 byte).

name is that name. C programmers beware, you should allocate nameLength+1 bytes for this and add a closing 0 yourself. The name probably is just there for house-keeping, since it's never used anywhere – all images can be identified by their Id. And if you check, you'll find that the names aren't even unique in each ILB.

wide is the width of the entire graphic, including pad space. The image data may be less than this. Only useful if you want to display the entire bounding box.

high is the height of the entire graphic.

xshift is an offset to add to the position before drawing it, in conjunction with the next one:

yshift. To display this image at position (x,y) the image's top left corner should be at (x+xshift,y+xshift). Used to position subimages for a composite image.

subid is a unique Id, to differentiate between the elements of a composite. Theoretically, it allows your code to select and draw a single subimage out of a composite one – maybe useful while defining?

unknownA is one of those pesky unknowns. It appears to be 0, 1, or 2 for all images.

size is the actual size in bytes of the pure image data.

offset is the offset from the end of the image directory to the start of the pure image data, so it is 0 for the first image, this images' size for the second, and so on. Useful to locate a single image inbetween others. This is the big difference between ILB v3.0 and ILB v4.0: in 3.0s, the image data follows immediately after the info part, and so you don't need an offset field. In 4.0s, all image data is packed together after the entire image directory, so there you need to use this. Didn't I say before you don't need to remember the ILB version number? Here's the reason: in 3.0s, the InfoByte is always 1, in 4.0s it's always 2 or 3.

totalwide is an obsolete value. It's the entire image bounding box, its width and xshift added together.

totalhigh is its vertical counterpart. Oh well, maybe it's not that obsolete; you can test the entire bounding box to check if any part of the image is on screen. But you can still add width and xshift, and height and yshift, to get these same numbers.

unknownB is unique for Type 2 images, and is 0 in all files I saw.

unknownC and unknownD are ... eh, two more unknowns. They are only present when the InfoByte is 3.

palette: Hah! I know that one. As I already said, there can be more than 1 palette in the ILB header, and this one tells you which one to use for this image. A unique field for Type 2s, because it's the only index coloured one.

clipwide and cliphigh pop up in a few more image types. These are the width and height of actual image data, without its bounding box.

clipxoff and clipyoff are the horizontal and vertical offsets of the actual image inside the bounding box.

transparent is the index number of the transparent colour. You can draw these pixels in their defined colour (taken from the palette), but it's meant to be transparent.

clip_x likely has nothing to do with either clip or x, but I figgered that the rest of these fields all have to do with clipping, so this one might as well. Found no use for it.

If the InfoByte is 1, the raw image data follows here, totalling size bytes.

lastWord is a closing word to signify the end of this image info block, and is always 0xFFFFFFFF (-1). (IIRC, it may have a value in release.hss, but I'll come to that later.)

 

The clipping stuff pops up for a number of image types, and it's worth to explain it in more detail. The following image show where the variables are used.

Definition of the clip variables

The only part of the image that is defined in its data (RLE packed or not) is the square inside clipwide and cliphigh. The entire border around is only defined by virtue of the wide and high fields, and since its colour is transparent (by definition), you actually never ever have to draw it. Oh well, except when you want to see it for some reason. If you want to draw the image at a position (x,y) using transparency, simply draw its data at (x+clipxoff,y+clipyoff), or to be precise – the entire image may also be shifted – at (x+xshift+clipxoff,y+yshift+clipyoff).

Type 16: Picture16

This is a simple rectangular image in high-colour mode – 16 bits, 2 bytes per pixel. Examples are: all images in the images\faces folder.

char InfoByte;
int  nameLength;
char name[nameLength];
int  wide;
int  high;
int  xshift;
int  yshift;
int  subid;
char unknownA;
int  size;
int  offset;      // Not here if InfoByte is 1
int  totalwide;
int  totalhigh;
int  drawMode;    // | Only if InfoByte is 3
int  blendValue;  // |
int  pixelFormat;
...               // Image data if InfoByte is 1
int  lastWord;    // Always -1

All the fields up to totalhigh are the same as for RLESprite08 above. After that, a few new ones follow:

drawMode is an interesting construction. It seems to be divided into two: the lowest byte is the showMode, the 2nd byte is the blendMode – the other two bytes are always zero.

blendValue is used in conjunction with the drawMode above. Read on for more about this. These two fields are only present if InfoByte is 3.

pixelFormat defines what pixel format the image data is stored in (hah). It may be any one of

  • 55509310h, short sized data in 16 bit packed RGB format, 5 bits each (0RRR:RRGG:GGGB:BBBB)
  • 56509310h, short sized data in 16 bit packed RGB format, 5/6/5 bits (RRRR:RGGG:GGGB:BBBB)
  • 88809318h, 3 bytes of data in true colour RGB format, 8 bits each (RRRRRRRR GGGGGGGG BBBBBBBB)
  • 88889320h, 4 bytes of data in 32 bit RGB format, 8 bits each (00000000 RRRRRRRR GGGGGGGG BBBBBBBB)
although the only one I've seen in the ILBs is 56509310h. You can sort of see how they got to define these constants: the high 2 bytes of the integer show the number of bits (555, 565, 888), the lowest byte shows the amount of data per pixel in bits (16, 24, or 32). This looks quite the same as the palette definition; here, too, I don't know what that 2nd byte value (93h) is doin' there.

lastWord, finally, is again always 0xFFFFFFFF.

I'll return to the pixel format when discussing how to actually draw an image, together with the blend modes.

Type 17: RLESprite16

This is an RLE packed image with a transparent colour, in high colour mode. Examples: all of the images in the folder images\items.

char InfoByte;
int  nameLength;
char name[nameLength];
int  wide;
int  high;
int  xshift;
int  yshift;
int  subid;
char unknownA;
int  size;
int  offset;      // Not here if InfoByte is 1
int  totalwide;
int  totalhigh;
int  drawMode;    // | Only if InfoByte is 3
int  blendValue;  // |
int  pixelFormat;
int  clipwide;
int  cliphigh;
int  clipxoff;
int  clipyoff;
int  transparent;
int  clip_x;
...               // Image data if InfoByte is 1
int  lastWord;    // Always -1

Here you see the repetition kicking in, all of these fields have been described above. The only one with a slightly different meaning is transparent. In the RLESprite08 format, it indicates the palette index for the transparent colour; as this is a high colour format, it has no palette, and this is the exact colour value. The format of this colour value should match the image's pixelFormat (since this always is 56509310h, it always is a short value, but stored as an integer).

Also note that the fields drawMode and blendValue may be missing if InfoByte is not 3.

The difference with the Picture16 defined above is that this one has transparency and a clipping box – two items that usually go together. And, of course, its data is RLE packed, rather than stored straight away.

Type 18: TransparentRLESprite16

This is exactly the same as RLESprite16, but it has a slight twist: when displaying this image, you should blend it 50% with the background. Examples: in the file int\ShieldsM.ILB you can find three sets of 13 shields (one for each race plus one for "independents"). The first set are all Sprite16s (see below), the second set are TransparentRLESprite16s, and the third are Picture16s.

Although in this format a drawMode with its associate blendValue may be defined (only if InfoByte equals 3), they aren't used.

Type 22: Sprite16

At first glance, this is again the same as RLESprite16. The difference here is that its data is not packed, but copied straight away, and a single field is missing: clip_x, that unknown value near the end of the info, is not there.

Examples are: images\units\element\elmar.ILB – this one also uses a custom blend mode – and the first 13 images in int\ShieldsM.ILB.

For appearances, here is the entire list without clip_x.

char InfoByte;
int  nameLength;
char name[nameLength];
int  wide;
int  high;
int  xshift;
int  yshift;
int  subid;
char unknownA;
int  size;
int  offset;      // Not here if InfoByte is 1
int  totalwide;
int  totalhigh;
int  drawMode;    // | Only if InfoByte is 3
int  blendValue;  // |
int  pixelFormat;
int  clipwide;
int  cliphigh;
int  clipxoff;
int  clipyoff;
int  transparent;
...               // Image data if InfoByte is 1
int  lastWord;    // Always -1

Types 1:Picture08; 19:BitMask; 20:Shadow; 21:TransparentPicture16

Just to be complete. These types are defined in the game, but they are never used as an ILB file. Possibly, images in one the other types may be converted internally to one of these, but we might never know for sure.

From the names, you can guess how Picture08 and TransparentPicture16 are defined, and it's possible that if you insert these in the game, they'll work as expected. But I have no clue on the bitmask (single bit data?) and the shadow (gray scale data?) ones.

Show and Blend modes

As you have seen, the high colour images may define a drawMode, with an associate blendValue. This ties in to the way the image is to be rendered on screen. Recall that the drawMode consists of a showMode in its lowest byte (drawMode & 0x00ff) and a blendMode in its 2nd byte ((drawMode >> 8) & 0x00ff).

showMode may be one of 0 (smOpaque), 1 (smTransparent), or 2 (smBlended).

smOpaque

This is the default drawing mode if no other one is specified. Pixels of the image which are not transparent are to be copied to the screen in the regular fashion, replacing what's already there.

And when I say it's the 'default' drawing mode, that's obviously not true for the TransparentRLE16 format; there it is smTransparent. But see the next section on that one!

smTransparent

Here is one of those little clues the ILB format evolved over some time. This blend mode, for any image, defines it should be blended into the background with 50%. "Now wait", you might say, "didn't you already define an entire image type just to do that?" And the answer is simply "No, I didn't. The game programmers did."

So if an image is either a TransparentRLE16 one, or it has a blend mode of smTransparent, draw it with 50% opacity.

smBlended

This mode uses the second part of drawMode and the blendValue. The blendValue is a percentage, ranging from 0 (effect is not visible at all) to 100 (effect at its fullest). What the result is depends on the blendMode, one of 0: bmUser, 1: bmAlpha, 2: bmBrighten, 3: bmIntensity, 4: bmShadow, or 5: bmLinearAlpha.

  • bmUser is not used anywhere in the ILBs, but it might be used internally for custom work. Perhaps with a callback routine per pixel?
  • bmAlpha blends the image onto the background – just like smTransparency, although this one uses blendValue to specify the percentage of blending. You can see it in the Undead Wraith (the file is called images\units\undgh.ILB), which is drawn with 70% opacity. This code may seem even more redundant, but drawing an image at 50% can be done much faster than with a random amount (I might tell later on how that's done).
  • bmBrighten is an interesting one. There is actually some maths going on in the background, as this adds the image colour to the background; hence the name (it's a bit more complicated than that, as I will show later). The magic sparks in Images\Effects\MSparks.ILB use this blend mode.
  • bmIntensity does the same, only even more so: before being added to the background, the pixel colours are beefed up to 175% of their original value, and thus appear much brighter. Most of the magic effects in the Images\Effects use this to achieve a blinding washout effect, such as the Life Storm and all images in images\effects\Magic.ILB.
  • bmShadow is also quite interesting, as it does the opposite. The pixel values are multiplied with the background, which gives an entirely different effect. A black input pixel (a value of 0) results in a black pixel on screen, a white one (a value of 255) results in the original colour. This is used to darken parts of the screen, such as the ominous black flame of the Death node (that one is images #140 to #150 in images\str_node.ilb).
  • The final one, bmLinearAlpha, is defined in the game, but never used in any ILB. Even from the name I don't have a clue what it's supposed to do. More transparency?

The blendValue prescribes how much of the input or source value is to be used – the pixel value in the image you're drawing – against the destination value – the colour already on the screen. When I show how to draw images, you'll see I refer to these values as source and destination, the common names for these operations.

Looping over all images

There is one thing left to explain about the image directory. I started by stating that an image may be composite; that is, a single image with a single Id may consist of several subimages drawn over eachother. This is how, for example, the Altar of Light in images\a_life.ilb is drawn: the first subimage is opaque, and draws the base of the altar. The second one is transparent, and draws the huge yellow sphere over the top of the altar. The images aren't the same size (the altar is 56 x 63 pixels, the light bulb is 56 x 55 pixels), so the altar base is shifted by 3x3 pixels to its correct position.

A subimage starts at the Type field of the image, not at an Id. Check if the Type is zero to see if it is the last (empty) one in the list. If so, you're done and can continue with the next (full) image. If not, it is one of the predefined image types, and you can parse it and add it to your "current" image list; this composite image still has the Id which started the whole thing.

Only after you read an Id of 0xFFFFFFFF you can stop reading image info. In the case of a v4.0 ILB, you can start right away with reading image data – this continues right up to the end of the file; if it was a v3.0 ILB, you already read (or skipped) the image data, and the file ends then and there.

 

 


That wraps up the formal description of the ILB image format. Next, I will nail it down into a single handy routine, which loads the image directory for all images in an ILB and can output all and every detail you probably never wanted to know to begin with, and explain how the RLE encoded images can be decoded into a coherent image.


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