Lemmings Revolution file formats

Started by ccexplore, January 08, 2012, 02:13:09 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

ccexplore

Starting a thread to capture all information currently known about file formats for the Lemmings Revolution game, in the same vein as what we documented for a few other Lemmings games in the series.  Interest in Revolution may be low, but then again so are games like L3, but we currently know more about that game's file formats than Revolution.  That should change at some point in the future.

Information given on this thread are not guaranteed 100% accurate, but should be pretty good, and will be updated with corrections if any are reported.

We start off with the BOX file format, which is just a way to package a set of files in a directory tree into a single file, without any compression.  Yeah, not sure why they did that, but they did.   Attached text file is same info as below, just for convenience.

Code: [Select]Lemmings Revolution (PC) BOX format
  documented by ccexplore

For some reason, Lemmings Revolution packs most of its data files into a single
large BOX file, instead of simply letting them live as individual files in
individual directories.  There isn't even any compression going on inside BOX
files. Go figure.

The format is really simple though.

Sidenote: the "Dragon unPACKer" program (as of version 5.6.2 [build 268]) has
major bugs in its extraction code for BOX files. It produces the correct
directory listing, but does not correctly associate each file entry with the
correct set of bytes in the BOX file, at least when I tried it on the files on
my game CD.

----------------
BOX format
----------------

[NOTE: per Intel convention, all integers are little-endian, meaning that
a 4-byte hexadecimal integer 0xdeadbeef is stored in bytes as
0xef, 0xbe, 0xad, 0xde.]

Header Info (14 bytes)
0x0000-0x0005: "LEMBOX"
0x0006-0x0009: 4-byte integer, number of files N
0x000A-0x000D: 4-byte integer, offset from first byte after the header to File
               Locations table

File Directory (variable size)
0x000E:  N filename entries (variable size), each entry as follows:
        0x00-0x03: 4-byte integer, length L of text string following
        0x04: L bytes in 8-bit ASCII, name of file (including path), no
              0-termination

File Locations table (variable size)
        0x00-0x03: 4-byte integer, number of table entries N' (should be equal
                   to N)
        0x04: N' table entries
              each entry is a 4-byte integer, offset from start of BOX file to
              first byte of contents of the file

File Lengths table (variable size)
        0x00-0x03: 4-byte integer, number of table entries N" (should be equal
                   to N)
        0x04: N" table entries
              each entry is a 4-byte integer, length of file (in bytes)

remainder of BOX file are the bytes for the individual files contained within

----------------
Example
----------------

Suppose we want to capture the following files in the directory tree below into
a BOX file:

AFILE.EXT        (file contents: 0x42)
DIR1\FILE1.ZZZ   (file contents: 0xde 0xad 0xbe 0xef 0xde 0xad 0xbe 0xef)
DIR1\2ND.MMM     (file contents: 0xab 0xcd 0xef)
D2\SD3\Y.CD      (file contents: 0xac 0xdc)

Below is one possible encoding. "One possible" because the ordering of files
can be anything--files under the same subdirectory do not have to come up
in consecutive table entries in the BOX file, as demonstrated in the example
encoding below.

Similarly, the contents of each file may not necessarily come in the same order
as the file ordering in the tables, because the File Locations table provides
direct offsets into the BOX file for each file's contents, theoretically allow
you to place the contents of each file in any order consistent with the offsets
and lengths given in the tables. That said, in the BOX files from the game, I
think the file contents do come in the same order as the files do in the tables.

Resulting BOX file, viewed in hex editor:

4C 45 4D 42 4F 58 04 00 00 00 3E 00 00 00 0E 00
00 00 44 49 52 31 5C 46 49 4C 45 31 2E 5A 5A 5A
09 00 00 00 41 46 49 4C 45 2E 45 58 54 0C 00 00
00 44 49 52 31 5C 32 4E 44 2E 4D 4D 4D 0B 00 00
00 44 32 5C 53 44 33 5C 59 2E 43 44 04 00 00 00
74 00 00 00 7C 00 00 00 7D 00 00 00 80 00 00 00
04 00 00 00 08 00 00 00 01 00 00 00 03 00 00 00
02 00 00 00 DE AD BE EF DE AD BE EF 42 AB CD EF
AC DC

Below is the breakdown:

4C 45 4D 42 4F 58: "LEMBOX"
04 00 00 00: 0x00000004=4 files
3E 00 00 00: offset to File Locations table=0xE (header length) + 0x0000003E
            =0x0000004C from start of BOX file (counting from zero).

File Directory, which we know has 4 entries for 4 files

0E 00 00 00 44 49 52 31 5C 46 49 4C 45 31 2E 5A 5A 5A:
  "DIR1\FILE1.ZZZ", string length 0x0000000E=14 bytes

09 00 00 00 41 46 49 4C 45 2E 45 58 54:
  "AFILE.EXT", string length 0x00000009=9 bytes

0C 00 00 00 44 49 52 31 5C 32 4E 44 2E 4D 4D 4D:
  "DIR1\2ND.MMM", string length 0x0000000C=12 bytes

0B 00 00 00 44 32 5C 53 44 33 5C 59 2E 43 44:
  "D2\SD3\Y.CD", string length 0x0000000B=11 bytes

File Locations table

04 00 00 00: 0x00000004=4 files, as expected
74 00 00 00: location in BOX file for the 1st file's contents, which according
             to File Directory is the file DIR1\FILE1.ZZZ
             so contents of DIR\FILE1.ZZZ starts at 0x00000074 from
             start of BOX file
7C 00 00 00: AFILE.EXT's contents starts at 0x0000007C from start of BOX file
7D 00 00 00: DIR1\2ND.MMM's contents @ 0x0000007D from start of BOX file
80 00 00 00: D2\SD3\Y.CD's contents @ 0x00000080 from start of BOX file

File Lengths table

04 00 00 00: 0x00000004=4 files, as expected
08 00 00 00: DIR1\FILE1.ZZZ's length is 0x00000008=8 bytes
01 00 00 00: AFILE.EXT's length is 0x00000001=1 byte
03 00 00 00: DIR1\2ND.MMM's length is 0x00000003=3 bytes
02 00 00 00: D2\SD3\Y.CD's length is 0x00000002=2 bytes

DE AD BE EF DE AD BE EF: these 8 bytes are DIR1\FILE1.ZZZ's file contents
                         located at offset 0x00000074 as specified earlier

42: this 1 byte is AFILE.EXT's file contents
    located at offset 0x0000007C as specified earlier

AB CD EF: these 3 bytes are DIR1\2ND.MMM's file contents
          located at offset 0x0000007D as specified earlier

AC DC: these 2 bytes are D2\SD3\Y.CD's file contents
       located at offset 0x00000080 as specified earlier

There are apparently some programs already available that can extract from BOX files, but they may be buggy and not extract every file inside the BOX correctly, or mix up contents of one file with another.  My documentation here should be more accurate than such programs, sadly.

Mod Edit: Restored attachments.

ccexplore

While GuyPerfect is sorting out the bmp compression in a portable manner, here are some tools one can use on Windows at the moment to try to extract and decompress files from the BOX.  They are not user friendly at all since both my time and general demand is pretty low.  If you never used this thing called a "command prompt", then wait for someone else to extract some files for you instead. http://www.lemmingsforums.com/Smileys/lemmings/winktounge.gif" alt=";P" title="Wink-Tongue" class="smiley" />

example usage:

Code: [Select]
unbox c:\foo\bar\lemmings.box c:\output
Unpack everytyhing in c:\foo\bar\lemmings.box into c:\output folder, preserving the folder structure stored in lemmings.box.  No file decompression is done.

Code: [Select]
lrdecomp c:\output\textures\main_logo.bmp c:\output\textures\main_logo-D.bmp
Decompresses c:\output\textures\main_logo.bmp and save decompressed data to c:\output\textures\main_logo-D.bmp.

Invalid/incorrect parameters just result in nothing happening.  Only works on compressed files, anything else will just crash program.  AFAIK any files with .BMP extension in BOX are compressed (and decompresses to the MS BMP bitmap file format), everything else aren't.

GuyPerfect

http://www.lemmingsforums.com/index.php?topic=594.msg13744#msg13744">Quote from: ccexplore on 2012-05-01 23:06:47
While GuyPerfect is sorting out the bmp compression in a portable manner

Done! Also, where is your source code, mister? D-:<
__________

I'll have a write-up of the bitmap compression algorithm in this thread at some point tomorrow, but for now here's a fully-functional decompressor.

A compiled EXE of this is attached to this post, and the format is the same as ccexplore's utility:

Code: [Select]
lrz <infile> <outfile>
Where infile is a compressed .bmp from the Lemmings Revolution BOX file and outfile is the desired name of the uncompressed file.

The source code is as follows, subject to improvements later on:

lrz.h
Code: [Select]
#ifndef __LRZ_LEMMINGS__
#define __LRZ_LEMMINGS__

// API error constants
#define LRZ_SUCCESS    0
#define LRZ_BADPARAMS -1
#define LRZ_BADDATA   -2

// Function prototypes
int lrzDecode(unsigned char *, int, unsigned char *, int);
int lrzGetSize(unsigned char *, int);

#endif // __LRZ_LEMMINGS__


lrz.c
Code: [Select]
#include <stdio.h>
#include <stdlib.h>
#include "lrz.h"

// Node type constants
#define NODE_UNKNOWN   0
#define NODE_BRANCHING 1
#define NODE_VALUE     2

// Decoder state constants
#define STATE_RAW  0
#define STATE_COPY 1

// Linked list data structure for binary tree nodes
typedef struct LRZ_NODE LRZ_NODE;
struct  LRZ_NODE {
    int type, value, done;
    LRZ_NODE *parent, *child[2];
};

// Atomic data processing unit for file chunks
typedef struct {

    // Input/output properties
    unsigned char *src, *dst;
    int srclen, dstlen, srcoff, dstoff;

    // Binary trees
    LRZ_NODE *rawtree, *lentree, *disttree;

    // Bit processing properties
    int bitsleft, reg, subbits, state;
} LRZ_CHUNK;



////////////////////////////////////////////////////////////////////////////////
//                             Decoder Functions                              //
////////////////////////////////////////////////////////////////////////////////

// This macro is used in lrzdFreeTree() to handle deletion of individual nodes
#define DeleteNode() {                                                  \
    if (node == node->parent->child[0]) node->parent->child[0] = NULL;  \
    if (node == node->parent->child[1]) node->parent->child[1] = NULL;  \
    temp = node->parent; free(node); node = temp; continue;             \
}

// Deletes memory allocated for a binary tree object
static void lrzdFreeTree(LRZ_NODE *root) {
    LRZ_NODE *temp, *node = root;

    // Exit now if the tree wasn't initialized
    if (root == NULL) return;

    // Traverse the tree until all child nodes are deleted
    while (1) {

        // Terminal nodes are deleted outright
        if (node->type != NODE_BRANCHING) DeleteNode();

        // Delete a branching node if both its children are gone
        if (node->child[0] == NULL && node->child[1] == NULL) {
            if (node == root) break; // Root has no parent, so exit here
            DeleteNode();
        }

        // Select the next non-deleted child node
        if (node->child[0] != NULL)
            node = node->child[0];
        else node = node->child[1];
    }

    // Delete root and return
    free(root);
    return;
}

// Deletes memory allocated for a chunk object
static void lrzdFreeChunk(LRZ_CHUNK *chunk) {
    lrzdFreeTree(chunk->rawtree);
    lrzdFreeTree(chunk->lentree);
    lrzdFreeTree(chunk->disttree);
    return;
}

// Decodes a binary tree from a chunk header
static int lrzdGetTree(LRZ_CHUNK *chunk, LRZ_NODE **out) {
    int nibnum, x, nodenum = 3, donenum = 0, level = 1;
    unsigned char nibs[17], curbyte;
    LRZ_NODE *root, *node;

    // Error checking
    if (chunk->srcoff >= chunk->srclen) return 1;

    // Get the number of nibbles for this tree
    curbyte = chunk->src[chunk->srcoff++];
    nibnum = (curbyte & 0x0F) + 2;
    if (nibnum == 2) return 0; // No nibbles for this tree
    if (chunk->srcoff + (nibnum >> 1) >= chunk->srclen)
        return 1; // Not enough bytes for this tree's definition

    // Process all of the nibbles for this tree
    for (x = 0; x < nibnum; x++) {
        if (x & 1) curbyte = chunk->src[chunk->srcoff++];
        else       curbyte >>= 4;
        nibs
  • = curbyte & 0x0F;
        }

        // Initialize the tree
        root = *out           = calloc(sizeof(LRZ_NODE), 1);
        node = root->child[0] = calloc(sizeof(LRZ_NODE), 1);
        root->child[1]        = calloc(sizeof(LRZ_NODE), 1);
        root->type = NODE_BRANCHING;
        root->child[0]->parent = root;
        root->child[1]->parent = root;

        // Construct the remainder of the tree
        while (!root->done) {

            // Check if this node needs further processing
            if (node->done) {
                node = node->parent;
                level--;
                continue;
            }

            // If a branching node, step down into the tree a level
            if (node->type == NODE_BRANCHING) {

                // Check if both child nodes are finished
                if (node->child[0]->done && node->child[1]->done) {
                    node->done = 1;
                    donenum++;
                    continue;
                }

                // Step into the next unfinished child node
                if (node->child[0]->type == NODE_UNKNOWN)
                    node = node->child[0];
                else node = node->child[1];
                level++;
                continue;
            }

            // If all the nibbles are less than the current level, return error
            for (x = 0; x < nibnum; x++)
                if (nibs
  • >= level) break;
            if (x == nibnum) return 1;

            // Try to find a value for this node
            for (x = 0; x < nibnum; x++)
                if (nibs
  • == level) break;

            // A value was found for this node
            if (x < nibnum) {
                nibs
  • = 0;
                node->type = NODE_VALUE;
                node->value = x;
                node->done = 1;
                donenum++;
                continue;
            }

            // No value could be found for this node; make it a branching node
            node->type = NODE_BRANCHING;
            node->child[0] = calloc(sizeof(LRZ_NODE), 1);
            node->child[1] = calloc(sizeof(LRZ_NODE), 1);
            node->child[0]->parent = node;
            node->child[1]->parent = node;
            nodenum += 2;
        }

        // If not all the nodes created were finalized, return error
        if (donenum != nodenum) return 1;

        // Return success
        return 0;
    }

    // Returns the value of the specified number of bits from the input stream
    static int lrzdGetBits(LRZ_CHUNK *chunk, int count) {
        int ret;

        // Error checking
        if (chunk->bitsleft < count || count > 15) return -1;
        chunk->bitsleft -= count;

        // Load the bits from the stream into the register
        while (count > chunk->subbits) {
            chunk->reg <<= chunk->subbits;
            if (chunk->srcoff < chunk->srclen)
                chunk->reg |= (int) chunk->src[chunk->srcoff++];
            count -= chunk->subbits;
            chunk->subbits = 8;
        }
        chunk->reg <<= count;
        chunk->subbits -= count;

        // Process the bits from the register
        ret = (chunk->reg & 0x00FFFF00) >> 8;
        chunk->reg &= 0x000000FF;
        return ret;
    }

    // Loads a number from input data encoded as a normal value
    static int lrzdParse(LRZ_CHUNK *chunk) {
        int x = 1, count = 0;

        // Read (and count) bits until a 0 is encountered
        while (x) {
            x = lrzdGetBits(chunk, 1);
            if (x < 0) return -1; // Error reading bit
            count += x;
        }

        // Read that many additional bits from the input
        x = lrzdGetBits(chunk, count);
        if (x < 0) return -1; // Error reading bits

        // Return the composited value
        return (1 << count) - 1 + x;
    }

    // Loads a number from input data by traversing a binary tree
    static int lrzdTraverse(LRZ_CHUNK *chunk, LRZ_NODE *node) {
        int x, ret;

        // Traverse the tree to a terminal node
        while (node->type == NODE_BRANCHING) {
            x = lrzdGetBits(chunk, 1);
            if (x < 0) return -1; // Error reading bit
            node = node->child
  • ;
        }

        // Return the number as a literal
        x = node->value;
        if (x < 2) return x;

        // Read additional bits from the data
        ret = 1 << --x;
        x = lrzdGetBits(chunk, x);
        if (x < 0) return -1; // Error reading bits
        return ret | x;
    }

    // This macro is used by lrzdChunk() to load a number from the input stream
    #define GetNumber(var, tree) {           \
        if (tree != NULL)                    \
            var = lrzdTraverse(chunk, tree); \
        else var = lrzdParse(chunk);         \
    }

    // Processes a file chunk
    static int lrzdChunk(LRZ_CHUNK *chunk) {
        int x, dist, len;

        // Initialize input/output variables
        chunk->srcoff = 0;
        chunk->dstoff = 0;

        // Process binary trees
        chunk->rawtree = chunk->lentree = chunk->disttree = NULL;
        if (lrzdGetTree(chunk, &chunk->rawtree))  return 1;
        if (lrzdGetTree(chunk, &chunk->lentree))  return 1;
        if (lrzdGetTree(chunk, &chunk->disttree)) return 1;

        // Initialize bit processing variables
        chunk->bitsleft = (chunk->srclen - chunk->srcoff) * 8;
        if (!chunk->bitsleft) return 1; // Insufficient data
        chunk->reg = (int) chunk->src[chunk->srcoff++];
        chunk->subbits = 8;

        // Process input data until all output data is written
        chunk->state = STATE_RAW;
        while (chunk->dstoff < chunk->dstlen) {

            // Read raw bytes from the input
            if (chunk->state == STATE_RAW) {

                // Read the number of bytes to copy
                GetNumber(x, chunk->rawtree);
                if (x < 0 || chunk->dstoff + x > chunk->dstlen)
                    return LRZ_BADDATA;

                // Copy that many bytes
                for (len = x; len > 0; len--) {
                    x = lrzdGetBits(chunk, 8);
                    if (x < 0) return LRZ_BADDATA;
                    chunk->dst[chunk->dstoff++] =
                        (unsigned char) x;
                }
            }

            // Copy data with a distance/length pair
            else {

                // Read the length and distance values
                GetNumber(len,  chunk->lentree);
                GetNumber(dist, chunk->disttree);
                if (len < 0 || dist < 0) return LRZ_BADDATA;
                len += 2; dist++; // Implicit processing
                if (chunk->dstoff + len > chunk->dstlen ||
                    chunk->dstoff - dist < 0)
                    return LRZ_BADDATA;

                // Copy the bytes from earlier in the output
                for (x = chunk->dstoff - dist; len > 0; len--)
                    chunk->dst[chunk->dstoff++] = chunk->dst[x++];
            }

            // Alternate processing states
            chunk->state = 1 - chunk->state;
        }

        // Return success
        return 0;
    }



    ////////////////////////////////////////////////////////////////////////////////
    //                               API Functions                                //
    ////////////////////////////////////////////////////////////////////////////////

    // Decompresses a compressed buffer
    int lrzDecode(unsigned char *src, int srclen, unsigned char *dst, int dstlen) {
        int chunks, packed, unpacked, srcoff = 1, dstoff = 0, x;
        LRZ_CHUNK chunk;

        // Error checking
        if (src == NULL || dst == NULL || srclen < 1 || dstlen < 1)
            return LRZ_BADPARAMS;

        // Read the chunk count and check input size
        chunks = (int) src[0];
        if (!chunks || chunks * 5 + 1 > srclen) return LRZ_BADDATA;

        // Process all chunks
        for (; chunks > 0; chunks--, srcoff += packed, dstoff += unpacked) {

            // Process the chunk header
            packed   = ((int) src[srcoff + 1] << 8) | (int) src[srcoff];
            if (srcoff + packed > srclen) return LRZ_BADDATA;
            unpacked = ((int) src[srcoff + 3] << 8) | (int) src[srcoff + 2];
            if (dstoff + unpacked > dstlen) return LRZ_BADDATA;

            // Construct and process a chunk object
            chunk.src    = &src[srcoff + 5]; chunk.dst    = &dst[dstoff];
            chunk.srclen = packed - 5;       chunk.dstlen = unpacked;
            x = lrzdChunk(&chunk); lrzdFreeChunk(&chunk);
            if (x) return LRZ_BADDATA;
        }

        // Return success
        return LRZ_SUCCESS;
    }

    // Examines a compressed buffer to determine its decompressed size
    int lrzGetSize(unsigned char *data, int len) {
        int chunks, packed, unpacked = 0, off = 1;

        // Error checking
        if (data == NULL || len < 1) return LRZ_BADPARAMS;

        // Read the chunk count and check input size
        chunks = (int) data[0];
        if (!chunks || chunks * 5 + 1 > len) return LRZ_BADDATA;

        // Check each chunk's packed size
        for (; chunks > 0; chunks--, off += packed) {
            packed = ((int) data[off + 1] << 8) | (int) data[off];
            if (off + packed > len) return LRZ_BADDATA;
            unpacked += (((int) data[off + 3] << 8) | (int) data[off + 2]);
        }

        // Return the accumulated unpacked size
        return unpacked;
    }


    lrz_main.c
    Code: [Select]
    #include <stdio.h>
    #include <stdlib.h>
    #include "lrz.h"

    // Program entry point
    int main(int argc, char **argv) {
        unsigned char *src, *dst;
        int srclen, dstlen, x;
        FILE *fPtr;

        // Check the command line arguments
        if (argc != 3)
            { printf("Usage: %s <infile> <outfile>\n", argv[0]); return 0; }

        // Get the size of the file
        fPtr = fopen(argv[1], "rb");
        if (fPtr == NULL)
            { printf("Could not open %s\n", argv[1]); return 1; }
        fseek(fPtr, 0, SEEK_END);
        srclen = ftell(fPtr);

        // Check the file size for errors
        if (!srclen) {
            fclose(fPtr);
            printf("%s contains no data\n", argv[1]);
            return 1;
        }

        // Load the file data
        fseek(fPtr, 0, SEEK_SET);
        src = malloc(srclen);
        x = fread(src, 1, srclen, fPtr);
        close(fPtr);
        if (x != srclen) { printf("Error reading %s\n", argv[1]); return 1; }

        // Get the uncompressed size of the file
        dstlen = lrzGetSize(src, srclen);
        if (dstlen < 1) {
            free(src);
            printf("%s contains errors\n", argv[1]);
            return 1;
        }

        // Decode the file
        dst = malloc(dstlen);
        x = lrzDecode(src, srclen, dst, dstlen);
        if (x != LRZ_SUCCESS) {
            free(dst); free(src);
            printf("%s contains errors\n", argv[1]);
            return 1;
        }

        // Save the decoded output to disk
        fPtr = fopen(argv[2], "wb");
        if (fPtr == NULL) {
            free(dst); free(src);
            printf("Cannot write to %s\n", argv[2]);
            return 1;
        }
        fwrite(dst, 1, dstlen, fPtr);
        fclose(fPtr);

        // User output
        printf("Wrote to %s\n", argv[2]);

        // Clean up and exit
        free(dst);
        free(src);
        return 0;
    }


    EDIT:
    Fixed a bug with the error checking, where I was using a value that wasn't being helpful. (-:

    If I did my job correctly, this should now be error-proof, so use any files you want and it won't crash.

GuyPerfect

The image files in Lemmings Revolution are somewhat misleadingly named ".bmp", since the files themselves are not actual Windows Bitmap files. However, the Lemmings files do represent Windows Bitmap files through a layer of compression.


The File Format

The file format is as follows:

Code: [Select]
File
=============================================================================
ChunkNum        UInt8      The number of chunks in the file
Chunks          Chunk[]    The data chunks
=============================================================================

Chunk
=============================================================================
Header          ChunkHeader    Describes how the chunk is encoded
Data            Byte[]         The compressed input data
=============================================================================

ChunkHeader
=============================================================================
PackedSize      UInt16     The total size of this chunk, including the header
UnpackedSize    UInt16     The size of the data this chunk represents
(Unknown)       UInt8      Value of unknown significance; usually (always?) 4
RawTree         BinTree    Binary tree for raw byte counts
LengthTree      BinTree    Binary tree for distance/length lengths
DistanceTree    BinTree    Binary tree for distance/length distances
=============================================================================

BinTree
=============================================================================
The lower 4 bits of the first byte indicates how many additional values need
to be read from the input. If the value is 0, no additional input is needed
as the binary tree will not be used. If it's greater than 0, then add 2 to it
and read that many more values.

The values themselves are in 4-bit increments starting with the high bits of
the byte that contained the count, followed by however many bytes are
necessary to encode the remaining 4-bit values. For each byte, the lower 4
bits are read first, followed by the higher 4 bits.

The last byte may have 4 bits of padding so that the next data begins on a
byte boundary.
=============================================================================

Since the compression algorithm's implementation was designed for 16-bit systems, chunks are limited to an unpacked size of 64 KB. For files larger than this, additional chunks are present and they are simply concatenated to form the decompressed file.


Binary Trees

UPDATE: Apparently the means by which these trees are stored in the file is called http://en.wikipedia.org/wiki/Canonical_Huffman_code" class="bbc_link" target="_blank">canonical Huffman code.

Each chunk header can define up to 3 binary trees that are used to more concisely encode numbers into the input data. Rather, the trees are stored in the data for decoding those concise numbers. The first tree is used when reading raw byte counts, the second for when reading distance/length lengths, and the third for when reading distance/length distances. More on those later.

Note: For the uninitiated, you can mask out the lower 4 bits of a value by performing a http://en.wikipedia.org/wiki/Bitwise_operation#AND" class="bbc_link" target="_blank">bitwise AND with the number 15.

The lower 4 bits of the first byte read for a binary tree indicates how many values are to be added to the tree, and those values describe how to construct that tree. These values are all 4 bits, and can be decoded with the following algorithm:
  • Read a byte from the input into CURBYTE
  • Copy the lower 4 bits of CURBYTE into COUNT
  • If COUNT is 0
    • This binary tree will not be used
  • Otherwise
    • Add 2 to COUNT
    • Create an array NIBBLES that contains COUNT elements, with the first element being at index 0 in the list
    • LOOP: Iterate X from 0 to COUNT - 1
      • If X is even, divide CURBYTE by 16 (or shift CURBYTE right by 4)
      • If X is odd, read the next byte from the input into CURBYTE
      • Copy the lower 4 bits of CURBYTE into NIBBLES[X]
For example, the byte string 22 31 03 will yield 4 nibbles: 2, 1, 3, 3 and the 0 is just padding.

The final structure of the binary tree is not known until all of the nibbles are processed. Note that any given node in a binary tree can either be a terminal node with a value, or a branching node that has two child nodes. These are the only two types of nodes that can exist in a binary tree, and the topmost node that connects everything else is called the root node.

Initially, the root node is set to be a branching node and its child nodes are of unknown type (in regards to terminal or branching). The root node is at the top of the tree, meaning it has no "depth" into the tree. For this documentation, I'll refer to this as having a "depth level" of 0. The two child nodes of the root node have a depth level of 1. If they have child nodes, their children will be at depth level 2 and so-on.

The initial tree looks like this:

Code: [Select]
Level 0    root
          0/  \1
Level 1   ?    ?

To determine the significance of these nodes, the list of nibbles is searched in order to find one where the nibble value is equal to the depth level of the node in question.

IMPORTANT: The order in which the nodes are considered is determined by the http://www.programmerinterview.com/index.php/data-structures/preorder-traversal-algorithm/" class="bbc_link" target="_blank">preorder traversal algorithm, where "left" corresponds with the 0 side (not the 1 side).

Consider the list of nibbles from earlier:

Code: [Select]
Nibble Value: 2 1 3 3
Nibble Index: 0 1 2 3

In this case, the first node of unknown significance is the root node's 0 child (according to preorder traversal), which is in depth level 1. Therefore, a nibble with a value of 1 is sought. There is one, so the nibble's index becomes the node's value--in this case also a 1--and the nibble is processed in such a way so that it will not be considered in future searches: it is effectively "crossed out."

At this point, the tree looks like this:

Code: [Select]
Level 0    root
          0/  \1
Level 1  (1)   ?

Nibble Value: 2 x 3 3
Nibble Index: 0 1 2 3

Note that the value of the nibble at index 1 has been crossed out.

When the list of nibbles is searched again for another nibble with a value of 1 (for the current depth level), none can be found because the only remaining nibbles contain 2, 3 and 3. In this situation, the node becomes a branching node and that node's 0 child is the next to be considered (according to the preorder traversal algorithm):

Code: [Select]
Level 0     root
           0/  \1
Level 1   (1)   @
              0/ \1
Level 2       ?   ?

Nibble Value: 2 x 3 3
Nibble Index: 0 1 2 3

The remainder of the nibbles and nodes are processed in the same manner--again, according to preorder traversal--and the remaining steps look like this:

Code: [Select]
      =Step 3=                =Step 4=                =Step 5=                 =Step 6=

Level 0     root        Level 0    root         Level 0    root          Level 0     root
           0/  \1                 0/  \1                  0/  \1                    0/  \1
Level 1   (1)   @       Level 1   (1)  @        Level 1   (1)  @         Level 1   (1)   @
              0/ \1                  0/ \1                   0/ \1                     0/ \1
Level 2      (0)  ?     Level 2     (0)  @      Level 2     (0)  @       Level 2      (0)  @
                                       0/ \1                   0/ \1                     0/ \1
                        Level 3        ?   ?    Level 3       (2)  ?     Level 3        (2) (3)

Nibble Value: x x 3 3   Nibble Value: x x 3 3   Nibble Value: x x x 3    Nibble Value: x x x x
Nibble Index: 0 1 2 3   Nibble Index: 0 1 2 3   Nibble Index: 0 1 2 3    Nibble Index: 0 1 2 3

Note: Nibbles need not define any values for a particular depth level. In fact, in most situations, level 1 will not contain any definitions and thus both of the child nodes of the root node themselves become branching nodes. It's important to stick to the preorder traversal algorithm and attempt to use the nibbles to assign values at each node, one step at a time.

Note: If a given nibble index doesn't need to appear in the binary tree as a node value, the nibble value at that index can be 0. Since a nibble specifying depth level 0 will never be searched for, this will effectively force that index to be skipped. For the same reason, nibbles can be "crossed out" by setting their values to 0.

Note: Data errors can easily make the tree impossible to construct. If, when searching, all of the nibbles have a value less than the current depth level, the process should be aborted before an endless loop and a memory leak occur.

Note: The process ordinarily terminates when there are no further nodes of unknown significance. In the event there are still remaining, "unused" nibbles, they are simply ignored.


The Compression Algorithm

The compression algorithm itself is a type of http://en.wikibooks.org/wiki/Data_Compression/Dictionary_compression#Reduced_offset_Limpel_Ziv_.28ROLZ.29" class="bbc_link" target="_blank">ROLZ that, in addition to encoding repeating strings of bytes in distance/length references (which is the technique used by http://en.wikipedia.org/wiki/LZ77_and_LZ78" class="bbc_link" target="_blank">LZ77), is able to reduce the size of literal numbers in the data by using a binary tree, presumably constructed with the http://en.wikipedia.org/wiki/Huffman_coding" class="bbc_link" target="_blank">Huffman technique. This particular implementation, however, is quite restrictive and will lose out over conventional compression algorithms for larger files. For the small data in Lemmings Revolution, however, this variant of ROLZ will generally out-perform http://en.wikipedia.org/wiki/Deflate" class="bbc_link" target="_blank">DEFLATE, the algorithm used in ZIP files, for total compression ratio.

The general decompression algorithm looks like this:
  • Initialize the decoder to the RAW state
  • LOOP: Begin decoding until all output data is processed or an error occurs
    • If the current decoder state is RAW
      • Read a number from the input
      • Read that number of bytes (8 bit values) from the input to the output
    • If the current decoder state is COPY
      • Read a number from the input for LENGTH and add 2
      • Read a number from the input for DISTANCE and add 1
      • From the current position in the output buffer, start reading from DISTANCE bytes earlier and copy LENGTH bytes to the end of the output
    • Alternate decoder states: if it's RAW, switch to COPY and vice-versa
Note: It's totally valid for LENGTH to be greater than DISTANCE. All that means is that it will copy bytes that have already been copied during the current operation. When repeating a single byte many times, DISTANCE can just be 1 and LENGTH can be however many copies you want.


Reading Bits

For the purpose of reading bits from the input data, the first bit is the highest bit (2^7) of the first byte of the input data. The second bit is 2^6 and so-forth. The ninth bit will be the highest bit of the second byte, and this continues through the input data. For example:
  • Input data (as hex): 9A 36 E3
  • Input data (as bits): 10011010 00110110 11101011
  • To read 3 bits will return 100
  • To read another 4 bits will return 1101
  • To read another 10 bits will return 0001101101

Reading Numbers

To "read a number from the input," as the algorithm above indicates, follows a particular convention. Numbers can be read in one of two ways, depending on whether or not there is a binary tree present for the purpose of the number being read (raw, length or distance). This is determined by the number of nibbles read for the tree: if the number was 0, then the tree is not present.

If no tree is present for the current purpose, then numbers are read entirely from the input and conform to a "normal" format.

If a tree is present for the current purpose, then the contents of that tree will dictate what bits are read from the input and what the value of the number will be.


The Normal Number Format

The Normal format for numbers is as follows:
  • Initialize COUNT to 0
  • LOOP: Begin reading bits from the input until the bit read is a 0
    • If the bit is a 1, increment COUNT
  • Read an additional COUNT bits from the input into BITS
  • The value of the number is 2 ^ COUNT - 1 + BITS
Note: If COUNT reaches 16 (perhaps 17?), then the data contains an error and the loop should be aborted. This isn't a violation of the format, per sé, but it will cause the 16-bit Lemmings Revolution implementation to overflow.


The Tree Number Format

The Tree format for numbers is as follows:
  • Initialize NODE to the root node of the appropriate binary tree
  • LOOP: Read 1 bit at a time from the input until NODE is a terminal node
    • If the bit is a 0, set NODE to its 0 child node
    • If the bit is a 1, set NODE to its 1 child node
  • Copy the value of NODE into PARAM
  • If PARAM is less than 2
    • The value of the number is PARAM
  • Otherwise
    • Subtract 1 from PARAM
    • Read PARAM bits from the input into BITS
    • The value of the number is 2 ^ PARAM + BITS

GuyPerfect

Holy low-hanging fruit, Batman!

The .ALF file format... isn't really a format. It's just a layer of an image stored separately from the other layer. In detail, the .BMP images contain the RGB channels, and if there is a corresponding .ALF file with the same filename in the same directory, it will specify the alpha channel.

Pixels in an ALF file are stored left-to-right, top-to-bottom. This contrasts the Windows Bitmap, which stores pixels left-to-right, bottom-to-top. Each byte in the .ALF file represents a whole pixel, where the alpha value ranges from 0 (fully transparent) to 255 (fully opaque). As such, the total necessary size of any given .ALF file is the product of the pixel dimensions of its corresponding .BMP file (width * height * 1 byte per pixel).

http://i7.photobucket.com/albums/y255/bgng/LRH_ALF.png" alt="" class="bbc_img" />
__________

There's a naming convention in place where bitmap files that need to be processed with blending of some sort have a character stuck in front:
  • ^ indicates there is a .ALF file present
  • + is the same as ^? There's only one of these and it has a .ALF
  • $ indicates any pixel with color value #404040 is transparent
  • _ indicates any pixel with color value #000000 is transparent
  • ~ indicates that the image uses additive blending
All other images are drawn without blending, or could be interpreted as images where every pixel has full opacity.

http://i7.photobucket.com/albums/y255/bgng/LRH_Other.png" alt="" class="bbc_img" />

(Note that you can't create an alpha mask for additive blending. The above image is an approximation)

GuyPerfect

LEVELMAP.DAT file format:

Code: [Select]
File
========================================================================
Levels          Level[125]      Meta data for all levels
========================================================================

Level
========================================================================
IDSize          UInt32          Size of ID string
ID              String          IDSize bytes, ASCII, not null-terminated
NameSize        UInt32          Size of name string
Name            String          NameSize bytes, ASCII, not terminated
Time            UInt32          Number of seconds for this level
Save            UInt32          Number of Lemmings to save
Lemmings        UInt32          Total number of Lemmings
========================================================================

Here's some processed output of the data:

Code: [Select]
Level 0
  ID       = MM_CLIMB_002
  Name     = Just Climb
  Time     = 5:00
  Save     = 1
  Lemmings = 10

Level 1
  ID       = MM_FLOAT
  Name     = Just Float
  Time     = 5:00
  Save     = 1
  Lemmings = 10

Level 2
  ID       = MM_BOMB
  Name     = Just Bomb
  Time     = 5:00
  Save     = 10
  Lemmings = 20

Level 3
  ID       = MM_BLOCK_001_A
  Name     = Just Block
  Time     = 5:00
  Save     = 15
  Lemmings = 30

Level 4
  ID       = MM_BUILD
  Name     = Just Build
  Time     = 5:00
  Save     = 10
  Lemmings = 20

Level 5
  ID       = MM_MINE
  Name     = Just Mine
  Time     = 5:00
  Save     = 10
  Lemmings = 20

Level 6
  ID       = MM_BASH_002
  Name     = Just Bash
  Time     = 5:00
  Save     = 10
  Lemmings = 10

Level 7
  ID       = MM_DIG_000
  Name     = Just Dig
  Time     = 5:00
  Save     = 10
  Lemmings = 20

Level 8
  ID       = MM_W_012
  Name     = Introducing - Water Lemmings
  Time     = 5:00
  Save     = 10
  Lemmings = 20

Level 9
  ID       = MM_Block_Bomb_A
  Name     = Block And Bomb
  Time     = 10:00
  Save     = 10
  Lemmings = 20

Level 10
  ID       = MM_BASH_MINE_WLR1
  Name     = Bash and Mine
  Time     = 10:00
  Save     = 10
  Lemmings = 20

Level 11
  ID       = MM_BASH_DIG_001_A
  Name     = Bash and Dig
  Time     = 5:00
  Save     = 10
  Lemmings = 20

Level 12
  ID       = MM_FLOAT_CLIMB2
  Name     = Float and Climb
  Time     = 10:00
  Save     = 6
  Lemmings = 10

Level 13
  ID       = MM_BUILD_BLOCK_A
  Name     = Build and Block
  Time     = 5:00
  Save     = 10
  Lemmings = 20

Level 14
  ID       = MM_BLOCK_BUILD_BASH
  Name     = Seafood Sarnie
  Time     = 10:00
  Save     = 20
  Lemmings = 25

Level 15
  ID       = MM_E4_4_SKILLS
  Name     = A Long and Lonely Road
  Time     = 10:00
  Save     = 20
  Lemmings = 30

Level 16
  ID       = MM_E1_3_SKILLS
  Name     = Lems at Loggerheads
  Time     = 10:00
  Save     = 5
  Lemmings = 10

Level 17
  ID       = MM_E2_4SKILLS
  Name     = The Abyss
  Time     = 10:00
  Save     = 10
  Lemmings = 15

Level 18
  ID       = E19_KF_2
  Name     = Kriss Kross
  Time     = 5:00
  Save     = 20
  Lemmings = 30

Level 19
  ID       = MM_E1_4SKILLS
  Name     = Seargeant Bash
  Time     = 10:00
  Save     = 10
  Lemmings = 20

Level 20
  ID       = E20_mg_1
  Name     = The Crowded House
  Time     = 10:00
  Save     = 5
  Lemmings = 10

Level 21
  ID       = MM_ALL_8_001
  Name     = One Way Ticket
  Time     = 8:00
  Save     = 40
  Lemmings = 50

Level 22
  ID       = MM_E1_5_Skills
  Name     = Swings Both Ways
  Time     = 10:00
  Save     = 16
  Lemmings = 20

Level 23
  ID       = MM_ALL_8_002
  Name     = Wandering Free
  Time     = 10:00
  Save     = 10
  Lemmings = 20

Level 24
  ID       = MM_ALL_8_003
  Name     = The Iron Curtain
  Time     = 10:00
  Save     = 20
  Lemmings = 30

Level 25
  ID       = E18_KF_2
  Name     = Make Mine a Large One
  Time     = 5:00
  Save     = 25
  Lemmings = 30

Level 26
  ID       = E22_KF_1
  Name     = Now you're stalking
  Time     = 6:00
  Save     = 49
  Lemmings = 50

Level 27
  ID       = E11_KF_1
  Name     = Escape to victory
  Time     = 8:00
  Save     = 40
  Lemmings = 45

Level 28
  ID       = M08_KF_1
  Name     = 2 sides to every story
  Time     = 10:00
  Save     = 40
  Lemmings = 50

Level 29
  ID       = E16_KF_1
  Name     = Wood you believe it?
  Time     = 5:00
  Save     = 45
  Lemmings = 50

Level 30
  ID       = M08_mg_1
  Name     = Wheelbarrows of Doom
  Time     = 4:00
  Save     = 98
  Lemmings = 100

Level 31
  ID       = M11_KF_1
  Name     = Designed with love
  Time     = 10:00
  Save     = 85
  Lemmings = 99

Level 32
  ID       = M04_KF_1
  Name     = Penelope Lem Stop
  Time     = 6:00
  Save     = 32
  Lemmings = 32

Level 33
  ID       = H02_JC_1
  Name     = How do we get up there!
  Time     = 4:00
  Save     = 50
  Lemmings = 50

Level 34
  ID       = E07_JC_1
  Name     = There`s only one way down!
  Time     = 5:00
  Save     = 10
  Lemmings = 25

Level 35
  ID       = M02_MG_1
  Name     = What a Fantastic Pair of Birds
  Time     = 5:00
  Save     = 20
  Lemmings = 20

Level 36
  ID       = MM_TELE_003
  Name     = The Lone Ranger
  Time     = 10:00
  Save     = 90
  Lemmings = 100

Level 37
  ID       = M03_mg_1
  Name     = Swarthy Seadogs!
  Time     = 10:00
  Save     = 99
  Lemmings = 100

Level 38
  ID       = E17_KF_1
  Name     = Build 'em up ....then bring em down
  Time     = 10:00
  Save     = 45
  Lemmings = 50

Level 39
  ID       = M01_KF_1
  Name     = Love train boogie
  Time     = 10:00
  Save     = 25
  Lemmings = 25

Level 40
  ID       = M10_JC_1
  Name     = Bounce around the world!
  Time     = 5:00
  Save     = 46
  Lemmings = 50

Level 41
  ID       = M16_mg_1
  Name     = Lock In
  Time     = 10:00
  Save     = 11
  Lemmings = 14

Level 42
  ID       = M17_MT_1
  Name     = Under and Up.
  Time     = 5:00
  Save     = 48
  Lemmings = 60

Level 43
  ID       = VH04_JC1
  Name     = Nice shootin tex!
  Time     = 4:00
  Save     = 42
  Lemmings = 50

Level 44
  ID       = M03_MT_1
  Name     = Quick as you can!
  Time     = 4:00
  Save     = 96
  Lemmings = 101

Level 45
  ID       = M18_MT_1
  Name     = Watch Out! Evil about!
  Time     = 6:00
  Save     = 45
  Lemmings = 50

Level 46
  ID       = Show_MT3
  Name     = Take Her to Warp Speed, Captain
  Time     = 8:00
  Save     = 12
  Lemmings = 25

Level 47
  ID       = M20_MT_1
  Name     = Bridges
  Time     = 3:30
  Save     = 48
  Lemmings = 53

Level 48
  ID       = M21_KF_1
  Name     = Walk the plank or join the crew?
  Time     = 15:00
  Save     = 75
  Lemmings = 100

Level 49
  ID       = M19_MT_1
  Name     = Bamboo Maze
  Time     = 5:00
  Save     = 48
  Lemmings = 50

Level 50
  ID       = H01_KF_1
  Name     = Can't Get There From Here
  Time     = 6:00
  Save     = 49
  Lemmings = 50

Level 51
  ID       = H03_KF_1
  Name     = Lemmings Four the Drop
  Time     = 5:00
  Save     = 4
  Lemmings = 4

Level 52
  ID       = M12_MT_1
  Name     = Booby trap.
  Time     = 8:00
  Save     = 46
  Lemmings = 50

Level 53
  ID       = H17_mg_1
  Name     = Hit 'n' Run
  Time     = 10:00
  Save     = 10
  Lemmings = 11

Level 54
  ID       = H05_KF_1
  Name     = Battle o' Lemmuckburn
  Time     = 10:00
  Save     = 43
  Lemmings = 50

Level 55
  ID       = H06_MT_1
  Name     = Feeling Gravity's Pull
  Time     = 6:00
  Save     = 50
  Lemmings = 66

Level 56
  ID       = VH02_JC1
  Name     = It's a hard life
  Time     = 7:00
  Save     = 45
  Lemmings = 50

Level 57
  ID       = H08_KF_1
  Name     = Waste not...want not.part1
  Time     = 10:00
  Save     = 10
  Lemmings = 10

Level 58
  ID       = H09_KF_1
  Name     = Ouch me head.
  Time     = 5:00
  Save     = 50
  Lemmings = 50

Level 59
  ID       = M07_mg_1
  Name     = Topsy Turvy
  Time     = 10:00
  Save     = 98
  Lemmings = 100

Level 60
  ID       = M15_mg_1
  Name     = The Legend of Smelly Belly
  Time     = 10:00
  Save     = 15
  Lemmings = 15

Level 61
  ID       = H12_mg_1
  Name     = Where's Carol Vorderlem?
  Time     = 20:00
  Save     = 4
  Lemmings = 10

Level 62
  ID       = H13_MT_1
  Name     = Use the Force Lem...
  Time     = 3:45
  Save     = 99
  Lemmings = 100

Level 63
  ID       = H10_MT_1
  Name     = Scale that wall....
  Time     = 5:00
  Save     = 46
  Lemmings = 50

Level 64
  ID       = H15_MT_1
  Name     = Rocket Science.
  Time     = 5:30
  Save     = 30
  Lemmings = 50

Level 65
  ID       = E23_JC_1
  Name     = Time is of the essence!
  Time     = 3:20
  Save     = 48
  Lemmings = 50

Level 66
  ID       = H16_MT_1
  Name     = Odd Jobs.
  Time     = 6:00
  Save     = 99
  Lemmings = 100

Level 67
  ID       = H18_mg_1
  Name     = Reduce and Simmer
  Time     = 10:00
  Save     = 16
  Lemmings = 20

Level 68
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 69
  ID       = H20_KF_1
  Name     = Waste not...want not.part2
  Time     = 10:00
  Save     = 10
  Lemmings = 10

Level 70
  ID       = E25_JC_1
  Name     = Touch the ground turn around.
  Time     = 1:35
  Save     = 19
  Lemmings = 20

Level 71
  ID       = H22_MT_1
  Name     = Which Switch is Which?
  Time     = 4:00
  Save     = 48
  Lemmings = 50

Level 72
  ID       = H23_mg_1
  Name     = When Two Tribes go to War
  Time     = 10:00
  Save     = 18
  Lemmings = 20

Level 73
  ID       = H24_KF_1
  Name     = Bash Street Kids
  Time     = 10:00
  Save     = 64
  Lemmings = 70

Level 74
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 75
  ID       = VH01_JC1
  Name     = Something Fishy!
  Time     = 5:00
  Save     = 49
  Lemmings = 50

Level 76
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 77
  ID       = H08_MT_1
  Name     = T is for Teamwork.
  Time     = 10:00
  Save     = 2
  Lemmings = 2

Level 78
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 79
  ID       = VH05_JC1
  Name     = Don`t Let them out!
  Time     = 5:00
  Save     = 40
  Lemmings = 50

Level 80
  ID       = VH06_MT
  Name     = Speedy Gonzalez
  Time     = 4:00
  Save     = 52
  Lemmings = 53

Level 81
  ID       = VH07_MT
  Name     = Goonies
  Time     = 3:45
  Save     = 48
  Lemmings = 50

Level 82
  ID       = VH08_MT
  Name     = Lemming be thy name...
  Time     = 5:00
  Save     = 51
  Lemmings = 60

Level 83
  ID       = VH09_MT
  Name     = 'Tanks' a Lot
  Time     = 4:00
  Save     = 76
  Lemmings = 76

Level 84
  ID       = VH10_MT
  Name     = Two Laps Finale
  Time     = 6:15
  Save     = 50
  Lemmings = 50

Level 85
  ID       = VH11_MT
  Name     = The Diving Board
  Time     = 5:00
  Save     = 98
  Lemmings = 100

Level 86
  ID       = VH12_KF1
  Name     = Green with Envy
  Time     = 10:00
  Save     = 15
  Lemmings = 15

Level 87
  ID       = VH13_KF_1
  Name     = Anger, Love, Hate
  Time     = 5:00
  Save     = 40
  Lemmings = 50

Level 88
  ID       = VH13_MT
  Name     = All in..
  Time     = 5:00
  Save     = 51
  Lemmings = 51

Level 89
  ID       = VH15_KF_1
  Name     = No clues ...hee hee hee hee!
  Time     = 10:00
  Save     = 40
  Lemmings = 50

Level 90
  ID       = VH16_MT
  Name     = Last one to the top.....
  Time     = 5:00
  Save     = 29
  Lemmings = 30

Level 91
  ID       = M02_JC
  Name     = The Long and Winding Road
  Time     = 5:00
  Save     = 5
  Lemmings = 20

Level 92
  ID       = VH18_JC
  Name     = Just you wait
  Time     = 4:10
  Save     = 98
  Lemmings = 100

Level 93
  ID       = VH19_JC
  Name     = Turn on! Tune in! Switch on!
  Time     = 3:45
  Save     = 94
  Lemmings = 100

Level 94
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 95
  ID       = VH21_JC
  Name     = My friend the end.
  Time     = 3:30
  Save     = 49
  Lemmings = 50

Level 96
  ID       = VH22_KF1
  Name     = My finest moment
  Time     = 3:00
  Save     = 49
  Lemmings = 50

Level 97
  ID       = MM_H_001
  Name     = Mission Impossible
  Time     = 2:00
  Save     = 20
  Lemmings = 20

Level 98
  ID       = MM_H_002
  Name     = Race Against Time
  Time     = 3:00
  Save     = 45
  Lemmings = 45

Level 99
  ID       = MM_H_003
  Name     = It's Going to Take Time
  Time     = 10:00
  Save     = 99
  Lemmings = 100

Level 100
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 101
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 102
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 103
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 104
  ID       = m06_kf_1
  Name     = Sometimes up..sometimes down.
  Time     = 3:00
  Save     = 50
  Lemmings = 100

Level 105
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 106
  ID       = show_kf3
  Name     = Water way to go.
  Time     = 10:00
  Save     = 25
  Lemmings = 30

Level 107
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 108
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 109
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 110
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 111
  ID       = vh22_jc
  Name     = Going in all directions
  Time     = 3:15
  Save     = 44
  Lemmings = 50

Level 112
  ID       = e06_kf_1
  Name     = Lets play 'catch'.
  Time     = 5:00
  Save     = 5
  Lemmings = 10

Level 113
  ID       = e10_kf_1
  Name     = LEMTRIS
  Time     = 3:00
  Save     = 50
  Lemmings = 50

Level 114
  ID       = e10_kf_2
  Name     = Lets get those fingers moving
  Time     = 5:00
  Save     = 12
  Lemmings = 15

Level 115
  ID       = e14_kf_1
  Name     = The high dive
  Time     = 5:00
  Save     = 20
  Lemmings = 40

Level 116
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 117
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 118
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 119
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 120
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 121
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 122
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 123
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Level 124
  ID       = blank
  Name     = blank
  Time     = 1:00
  Save     = 0
  Lemmings = 100

Note that these levels are not in the order they appear on the level select screen. This is just defining the levels as-is. Also note that "The high dive" is the last level defined, which may somewhat explain why that's the only level that crashes the game after a Typical install. (Note to guests: you can fix the crash by copying lemmings.box from the CD yourself after the installer does it wrong)

The ID strings are arbitrary and assigned by the developers, but we can glean some insights by looking at them. Some parts appear to be level numbers, some appear to be difficulty descriptors, and some appear to be creator initials. Not that it really matters, but might be interesting to look through.
__________

LEVELS.MAP file format:

Code: [Select]
File
===========================================================================
Unk1            UInt16          Unknown value
Unk2            UInt16          Unknown value
Slots           Slot[125]       Matches levels with map slots
===========================================================================

Slot
===========================================================================
Unk1            UInt8           If not 1, Unk1a is present
  Unk1a         UInt16          Unknown value
Unk2            UInt8           If 0, Unk2aSize and Unk2a are present
  Unk2aSize     UInt16          Size of unknown string
  Unk2a         String          Unk2aSize bytes, ASCII, not null-terminated
IDSize          UInt16          Size of level ID string
ID              String          ID string, ASCII, not terminated
Unk3            UInt32          Unknown value
Unk4            UInt32          Unknown value
Unk5            UInt32          Unknown value
===========================================================================

Once again, the processed output:

Code: [Select]
(Unk1) = 0x0019
(Unk2) = 0xFFFF

Slot 0
  (Unk1) = 0x01
  (Unk2) = 0x00
    (Unk2a) = CLevelSlot
  ID     = MM_CLIMB_002
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 1
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_FLOAT
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 2
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_BOMB
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 3
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_BLOCK_001_A
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 4
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_BUILD
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 5
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_MINE
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 6
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_BASH_002
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 7
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_DIG_000
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 8
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_W_012
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 9
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_Block_Bomb_A
  (Unk3) = 1
  (Unk4) = 1
  (Unk5) = 0

Slot 10
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_BASH_MINE_WLR1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 11
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_BASH_DIG_001_A
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 12
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_FLOAT_CLIMB2
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 13
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_BUILD_BLOCK_A
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 14
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_BLOCK_BUILD_BASH
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 15
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_E4_4_SKILLS
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 16
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_E1_3_SKILLS
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 17
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_E2_4SKILLS
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 18
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = E19_KF_2
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 19
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_E1_4SKILLS
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 20
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = E20_mg_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 21
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_ALL_8_001
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 22
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_E1_5_Skills
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 23
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_ALL_8_002
  (Unk3) = 1
  (Unk4) = 1
  (Unk5) = 0

Slot 24
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_ALL_8_003
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 25
  (Unk1) = 0x19
    (Unk1a) = 256
  (Unk2) = 0x80
  ID     = E18_KF_2
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 26
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = E22_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 27
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = E11_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 28
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M08_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 29
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = E16_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 30
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M08_mg_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 31
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M11_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 32
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M04_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 33
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H02_JC_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 34
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = E07_JC_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 35
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M02_MG_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 36
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_TELE_003
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 37
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M03_mg_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 38
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = E17_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 39
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M01_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 40
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M10_JC_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 41
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M16_mg_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 42
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M17_MT_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 43
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH04_JC1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 44
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M03_MT_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 45
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M18_MT_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 46
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = Show_MT3
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 47
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M20_MT_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 48
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M21_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 49
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M19_MT_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 50
  (Unk1) = 0x19
    (Unk1a) = 256
  (Unk2) = 0x80
  ID     = H01_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 51
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H03_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 52
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M12_MT_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 53
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H17_mg_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 54
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H05_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 55
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H06_MT_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 56
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH02_JC1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 57
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H08_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 58
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H09_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 59
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M07_mg_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 60
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M15_mg_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 61
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H12_mg_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 62
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H13_MT_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 63
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H10_MT_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 64
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H15_MT_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 65
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = E23_JC_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 66
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H16_MT_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 67
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H18_mg_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 68
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 69
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H20_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 70
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = E25_JC_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 71
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H22_MT_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 72
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H23_mg_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 73
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H24_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 74
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 75
  (Unk1) = 0x19
    (Unk1a) = 256
  (Unk2) = 0x80
  ID     = VH01_JC1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 76
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 77
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = H08_MT_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 78
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 79
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH05_JC1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 80
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH06_MT
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 81
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH07_MT
  (Unk3) = 1
  (Unk4) = 1
  (Unk5) = 0

Slot 82
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH08_MT
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 83
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH09_MT
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 84
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH10_MT
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 85
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH11_MT
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 86
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH12_KF1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 87
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH13_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 88
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH13_MT
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 89
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH15_KF_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 90
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH16_MT
  (Unk3) = 1
  (Unk4) = 1
  (Unk5) = 0

Slot 91
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = M02_JC
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 92
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH18_JC
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 93
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH19_JC
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 94
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 95
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH21_JC
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 96
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = VH22_KF1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 97
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_H_001
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 98
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_H_002
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 99
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = MM_H_003
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 100
  (Unk1) = 0x19
    (Unk1a) = 256
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 101
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 102
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 103
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 104
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = m06_kf_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 105
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 106
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = show_kf3
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 107
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 108
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 109
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 110
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 111
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = vh22_jc
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 112
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = e06_kf_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 113
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = e10_kf_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 114
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = e10_kf_2
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 115
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = e14_kf_1
  (Unk3) = 1
  (Unk4) = 0
  (Unk5) = 0

Slot 116
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 117
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 118
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 119
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 120
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 121
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 122
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 123
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

Slot 124
  (Unk1) = 0x01
  (Unk2) = 0x80
  ID     = blank
  (Unk3) = 0
  (Unk4) = 0
  (Unk5) = 0

I'll have to revise this later, because the order in the file doesn't line up precisely with the level select screen using any apparent uniform algorithm. The values of Unk3 and Unk4 might assist in placing these slots on the map.
__________

RELEASERATE.PC file format:

Code: [Select]
File
=============================================
Rates           Rate[99]        Release rates
=============================================

Rate
=============================================
Unk1            UInt3           Unknown value
Unk2            Float           Unknown value
=============================================

I don't really know what precisely these values represent, but it's apparent from the game and the number of entries that they affect the time delay between Lemming entry for various release rate settings.

The floats are encoded as http://en.wikipedia.org/wiki/Single-precision_floating-point_format" class="bbc_link" target="_blank">32-bit IEEE-754 values.

Whatever the case, we don't need to mess with this file when producing a level editor.

GuyPerfect

Egads, I've proven myself wrong. The .WAV files, while ordinary Microsoft Wave files, are in fact http://en.wikipedia.org/wiki/Pulse-code_modulation" class="bbc_link" target="_blank">uncompressed PCM. Further details are in the file headers, though I suspect they all match the title music, which is 22050 hz, mono, 16-bit signed.

mobius

Isn't it odd there's a bunch of "blank" levels in there. In between real ones no less. I wonder if these were levels they made and couldn't fit into the game  http://www.lemmingsforums.com/Smileys/lemmings/undecided.gif" alt=":-\" title="Undecided" class="smiley" />.
Or (morel likely) They're probably just blank level templates they made for all the levels before they started designing each individual level. If that's the case, that may give you good insight into how the levels themselves are made.

anyway, great work so far! http://www.lemmingsforums.com/Smileys/lemmings/thumbsup.gif" alt=":thumbsup:" title="Thumbs Up" class="smiley" />

everything by me: https://www.lemmingsforums.net/index.php?topic=5982.msg96035#msg96035

"Not knowing how near the truth is, we seek it far away."
-Hakuin Ekaku

"I have seen a heap of trouble in my life, and most of it has never come to pass" - Mark Twain


GuyPerfect

Next up is the .LTX file:

Code: [Select]
File
===========================================================================
Count           UInt32          Number of strings in this file
Offsets         UInt32[]        Count starts of string data
Strings         Byte[]          Remainder of file--CP-1252, null-terminated
===========================================================================

Strings are stored in http://en.wikipedia.org/wiki/Windows-1252" class="bbc_link" target="_blank">Windows Code Page 1252, which was the default character set on Windows 95. The lower 128 values are identical to ASCII, though the higher 128 values are used for western European ideographs and diacritical marks.

There are six such files, one for English, French, German, Italian, Polish and Spanish. All of them have the same number of entries, suggesting that the entries in each are parallel to each other--effectively allowing translation between multiple languages.

Processed output:

Code: [Select]
130 strings in UK.LTX
130 strings in GERMAN.LTX
130 strings in SPAIN.LTX
130 strings in FRANCE.LTX
130 strings in ITALY.LTX
130 strings in POLISH.LTX

String 0
  EN =
  DE =
  ES =
  FR =
  IT =
  PL =

String 1
  EN = Press X button
  DE = Weiter mit X-Taste
  ES = Presionar el botón X para continuar
  FR = Appuyez sur le bouton X
  IT = Premi il tasto X per continuare
  PL = Wciœnij klawisz X

String 2
  EN = Start
  DE = Start
  ES = Comenzar
  FR = Commencer
  IT = Avvio
  PL = Start

String 3
  EN = Options
  DE = Optionen
  ES = Opciones
  FR = Options
  IT = Opzioni
  PL = Opcje

String 4
  EN = Game Options
  DE = Spiel-Optionen
  ES = Opciones Del Juego
  FR = Options du jeu
  IT = Opzioni Di Gioco
  PL = Opcje Gry

String 5
  EN = Controls
  DE = Steuerung
  ES = Controles
  FR = Contrôles
  IT = Comandi
  PL = Sterowanie

String 6
  EN = Audio
  DE = Audio
  ES = Sonido
  FR = Audio
  IT = Audio
  PL = Audio

String 7
  EN = Video
  DE = Bildschirm
  ES = Video
  FR = Vidéo
  IT = Video
  PL = Wideo

String 8
  EN = Load Game
  DE = Spiel Laden
  ES = Cargar Partida
  FR = Charger partie
  IT = Carica Partita
  PL = Wczytaj Grê

String 9
  EN = View Credits
  DE = Credits
  ES = Ver Créditos
  FR = Voir crédits
  IT = Visualizza Autori
  PL = Autorzy

String 10
  EN = English
  DE = Deutsch
  ES = Español
  FR = Français
  IT = Italiano
  PL = Polski

String 11
  EN = Music
  DE = Musik
  ES = Música
  FR = Musique
  IT = Musica
  PL = Muzyka

String 12
  EN = SFX
  DE = SFX
  ES = Efectos de sonido
  FR = Effets sonores
  IT = SFX
  PL = Efekty dŸwiêkowe

String 13
  EN = Mono
  DE = Mono
  ES = Mono
  FR = Mono
  IT = Mono
  PL = Mono

String 14
  EN = Stereo
  DE = Stereo
  ES = Estéreo
  FR = Stéréo
  IT = Stereo
  PL = Stereo

String 15
  EN = Select
  DE = Auswählen
  ES = Selección
  FR = Sélectionner
  IT = Seleziona
  PL = Wybierz

String 16
  EN = Return
  DE = Zurück
  ES = Volver
  FR = Retour
  IT = Invio
  PL = Powrót

String 17
  EN = Climb
  DE = Klettern
  ES = Trepar
  FR = Grimper
  IT = Scala
  PL = Wspinanie

String 18
  EN = Reset Controls
  DE = Standard-Steuerung?
  ES = ¿Restablecer controles?
  FR = Rétablir contrôles
  IT = Ripristina comandi?
  PL = Zresetuj ustawienia sterowania

String 19
  EN = Reset Screen
  DE = Standard-Anzeige
  ES = Restablecer pantalla
  FR = Rétablir écran
  IT = Ripristina videata
  PL = Zresetuj ekran

String 20
  EN = Reset
  DE = Reset
  ES = Reiniciar
  FR = Rétablir
  IT = Reset
  PL = Zresetuj

String 21
  EN = Yes
  DE = Ja
  ES = Sí
  FR = Oui
  IT = Sì
  PL = Tak

String 22
  EN = No
  DE = Nein
  ES = No
  FR = Non
  IT = No
  PL = Nie

String 23
  EN = Start Stage
  DE = Anfangslevel
  ES = Iniciar etapa
  FR = Commencer scène
  IT = Inizio livello
  PL = Rozpocznij Etap

String 24
  EN = Accept Changes And Exit
  DE = Annehmen Und Schließen?
  ES = Guardar Cambios Y Salir?
  FR = Accepter les modifications et quitter
  IT = Accetti Le Modifiche Ed Esci?
  PL = ZatwierdŸ zmiany i opuœæ

String 25
  EN = Press appropriate button
  DE = Bitte Taste Wählen!
  ES = ¡Presiona el botón correcto!
  FR = Appuyer sur la bonne touche !
  IT = Premi il tasto appropriato!
  PL = Wciœnij odpowiedni klawisz

String 26
  EN = Save Game?
  DE = Speichern?
  ES = ¿Guardar Juego?
  FR = Enregistrer la partie ?
  IT = Salvare La Partita?
  PL = Zachowaæ grê?

String 27
  EN = Lemmings
  DE = Lemmings
  ES = Lemmings
  FR = Lemmings
  IT = Lemmings
  PL = Lemmings

String 28
  EN = YOU WILL BE UNABLE TO SAVE GAMES
  DE = Sie können nicht speichern
  ES = No se pueden guardar los juegos.
  FR = VOUS NE POURREZ PAS ENREGISTRER LES PARTIES
  IT = Impossibile salvare le partite
  PL = NIE BÊDZIESZ MÓG£ ZACHOWYWAÆ GIER

String 29
  EN = Empty
  DE = Leer
  ES = Vacío
  FR = Vide
  IT = Vuoto
  PL = Pusty

String 30
  EN = Player
  DE = Spieler
  ES = Jugador
  FR = Joueur
  IT = Giocatore
  PL = Gracz

String 31
  EN = Difficulty Level
  DE = Schwierigkeitsgrad
  ES = Nivel De Dificultad
  FR = Niveau de difficulté
  IT = Livello Di Difficoltà
  PL = Poziom Gry

String 32
  EN = Easy
  DE = Leicht
  ES = Fácil
  FR = Facile
  IT = Facile
  PL = £atwy

String 33
  EN = Hard
  DE = Schwer
  ES = Difícil
  FR = Difficile
  IT = Difficile
  PL = Trudny

String 34
  EN = Medium
  DE = Mittel
  ES = Intermedio
  FR = Moyen
  IT = Intermedio
  PL = Œredni

String 35
  EN = Nightmare
  DE = Alptraum
  ES = Pesadilla
  FR = Cauchemar
  IT = Incubo
  PL = Koszmarny

String 36
  EN = Loading
  DE = Ladevorgang
  ES = Cargando
  FR = Chargement en cours
  IT = Caricamento
  PL = Wczytywanie

String 37
  EN = Select Option
  DE = Option wählen
  ES = Seleccionar opción
  FR = Sélectionner une option
  IT = Seleziona opzione
  PL = Wybierz Opcjê

String 38
  EN = Save Game
  DE = Gespeicherte Spiele
  ES = Guardar Partida
  FR = Enregistrer la partie
  IT = Salva Partita
  PL = Zachowaj Grê

String 39
  EN = Credits
  DE = Info
  ES = Créditos
  FR = Crédits
  IT = Autori
  PL = Autorzy

String 40
  EN = Well Done - Now try it on Hard difficulty.
  DE = Gut gemacht - versuch' jetzt den schweren Schwierigkeitsgrad.
  ES = Bien hecho - Ahora inténtalo con el nivel Difícil.
  FR = Bien joué ! Essayez à présent en mode Difficile.
  IT = Bel lavoro! Adesso prova il livello Difficile.
  PL = Dobrze - a teraz spróbuj na poziomie Trudnym

String 41
  EN = Well Done - Now try it on Medium difficulty.
  DE = Gut gemacht - versuch' jetzt den mittleren Schwierigkeitsgrad.
  ES = Bien hecho - Ahora inténtalo con el nivel Intermedio.
  FR = Bien joué ! Essayez à présent en mode Moyen.
  IT = Bel lavoro! Adesso prova il livello Intermedio.
  PL = Dobrze - a teraz spróbuj na poziomie Œrednim

String 42
  EN = Well Done - Now try it on Nightmare difficulty.
  DE = Gut gemacht - versuch' jetzt den Alptraum-Modus.
  ES = Bien hecho - Ahora inténtalo con el nivel Pesadilla
  FR = Bien joué ! Essayez à présent en mode Cauchemar.
  IT = Bel lavoro! Adesso prova il livello Incubo.
  PL = Dobrze - a teraz spróbuj na poziomie Koszmarnym

String 43
  EN = Quit
  DE = Beenden
  ES = Abandonar
  FR = Quitter
  IT = Esci
  PL = Opuœæ

String 44
  EN = Resume
  DE = Wiederaufnehmen
  ES = Reanudar
  FR = Reprendre
  IT = Riprendi
  PL = Wznów grê

String 45
  EN = Very Hard
  DE = Vieben Harden
  ES = Mucho Hardez
  FR = Très difficile
  IT = Mucho Hardo
  PL = Bardzo Trudny

String 46
  EN = Special
  DE = Speziell
  ES = Especial
  FR = Spécial
  IT = Speciale
  PL = Specjalny

String 47
  EN = Continue
  DE = Weiter
  ES = Continuar
  FR = Continuer
  IT = Continua
  PL = Kontynuuj

String 48
  EN = Try Again
  DE = Nochmals versuchen
  ES = Volver a intentarlo
  FR = Recommencer
  IT = Riprova
  PL = Spróbuj jeszcze raz

String 49
  EN = You Failed To Rescue Enough Lemmings! Please try again!
  DE = Du hast nicht genug Lemminge gerettet! Versuch es nochmals!
  ES = ¡No has conseguido rescatar suficientes Lemmings! Vuelve a intentarlo.
  FR = Vous n'avez pas sauvé assez de Lemmings ! Recommencez S.V.P. !
  IT = Non è stato salvato un numero sufficiente di Lemming. Riprovare
  PL = Nie uda³o ci siê uratowaæ doœæ Lemmingów! Spróbuj jeszcze raz!

String 50
  EN = You Totally Stormed That Level!
  DE = Du hast diesen Level im Sturm genommen!!
  ES = ¡Has arrasado el nivel!
  FR = Vous avez remporté de niveau haut la main !
  IT = Avete proprio sfondato in quel livello!
  PL = Totalnie rozgromi³eœ ten poziom!

String 51
  EN = Fast Rotate
  DE = Schnelle Umdrehung
  ES = Rápido Rote
  FR = Rotation rapide
  IT = Veloce Ruotare
  PL = Szybki obrót

String 52
  EN = Flip
  DE = Leichter Schlag
  ES = Tirón
  FR = Retourner
  IT = Vibrazione
  PL = Flip

String 53
  EN = Rotate Cylinder
  DE = Drehen Sie Zylinder
  ES = Rote El Cilindro
  FR = Rotation cylindre
  IT = Ruotare Il Cilindro
  PL = Rotate Cylinder

String 54
  EN = Select Skill
  DE = Wählen Sie Fähigkeit Aus
  ES = Seleccione La Habilidad
  FR = Sélectionner capacité
  IT = Selezionare L' Abilità
  PL = Wybierz poziom umiejêtnoœci

String 55
  EN = Play Level
  DE = Level spielen
  ES = Jugar nivel
  FR = Jouer niveau
  IT = Gioca livello
  PL = Rozegraj poziom

String 56
  EN = Tutorial
  DE = Lektion
  ES = Guía
  FR = Didacticiel
  IT = Lezione Privata
  PL = Przewodnik

String 57
  EN = Record Demo
  DE =
  ES =
  FR = Enregistrer démo
  IT =
  PL = Nagraj demo

String 58
  EN = Recorder off
  DE =
  ES =
  FR = Enregistrement désactivé
  IT =
  PL = Wy³¹cz nagrywanie

String 59
  EN = Playback Demo
  DE =
  ES =
  FR = Lire démo
  IT =
  PL = Odtwórz demo

String 60
  EN = Cursor Reversed
  DE = Cursor Aufgehoben
  ES = Cursor Invertido
  FR = Curseur inversé
  IT = Cursore Invertito
  PL = Kursor odwrócony

String 61
  EN = Cursor Not Reversed
  DE = Cursor Nicht Aufgehoben
  ES = Cursor No Invertido
  FR = Curseur non inversé
  IT = Cursore Non Invertito
  PL = Kursor nie odwrócony

String 62
  EN = Fixed Cursor
  DE = Örtlich Festgelegter Cursor
  ES = Fijo Cursor
  FR = Curseur fixe
  IT = Fisso Cursore
  PL = Kursor sta³y

String 63
  EN = Dead zone cursor
  DE = Toter Zone Cursor
  ES = Cursor muerto de la zona
  FR = Curseur zone morte
  IT = Cursore guasto di zona
  PL = Martwa strefa kursora

String 64
  EN = Free Floating Cursor
  DE = Freier Schwimmen Cursor
  ES = Cursor Libre De La Flotación
  FR = Curseur flottant
  IT = Cursore Libero Di Galleggiante
  PL = Kursor swobodny

String 65
  EN = Toggle Fullscreen
  DE = Schalten Sie Vollen Bildschirm
  ES = Cambie Completa La Pantalla
  FR = Basculer plein écran
  IT = Passare Pieno Lo Schermo
  PL = Pe³ny ekran

String 66
  EN = Choose Video Device Driver
  DE = Wählen Sie VideoEinheit
  ES = Elija Video El Dispositivo
  FR = Choisir pilote d'affichage
  IT = Scegliere Il Video Dispositivo
  PL = Wybierz sterownik graficzny

String 67
  EN = Time Remaining
  DE = Verbleibende Zeit
  ES = Tiempo restante
  FR = Temps restant
  IT = Tempo residuo
  PL = Pozosta³y czas

String 68
  EN = Out
  DE = Aus
  ES = Fuera
  FR = Sortie
  IT = Fuori
  PL = Out

String 69
  EN = Saved
  DE = Gerettet
  ES = Salvados
  FR = Sauvés
  IT = Salvati
  PL = Zachowano

String 70
  EN = Loading
  DE = Ladevorgang
  ES = Cargando
  FR = Chargement en cours
  IT = Caricamento
  PL = Wczytywanie

String 71
  EN = Let's Go!
  DE = Los geht's!
  ES = ¡Adelante!
  FR = Allons-y !
  IT = Avanti!
  PL = Ruszamy!

String 72
  EN = Min
  DE = Min
  ES = Min
  FR = Min
  IT = Min.
  PL = Min.

String 73
  EN = Sec
  DE = Sek
  ES = Seg.
  FR = Sec
  IT = Sec
  PL = Sek.

String 74
  EN = Climber
  DE = Kletterer
  ES = Trepador
  FR = Grimpeur
  IT = Scalatore
  PL = Wspinacz

String 75
  EN = Floater
  DE = Springer
  ES = Paracaidista
  FR = Flotteur
  IT = Galleggiatore
  PL = Fruwacz

String 76
  EN = Bomber
  DE = Bombe
  ES = Bombardero
  FR = Lemming à explosion
  IT = Bomba
  PL = Bomber

String 77
  EN = Blocker
  DE = Blocker
  ES = Bloque
  FR = Intercepteur
  IT = Bloccatore
  PL = Bloker

String 78
  EN = Builder
  DE = Brückenbauer
  ES = Constructor
  FR = Constructeur
  IT = Costruttore
  PL = Budowniczy

String 79
  EN = Basher
  DE = Horizontalbuddler
  ES = Golpe
  FR = Cogneur
  IT = Sfondatore
  PL = Rozbijacz

String 80
  EN = Miner
  DE = Diagonalgräber
  ES = Minero
  FR = Mineur
  IT = Minatore
  PL = Miner

String 81
  EN = Digger
  DE = Vertikalwühler
  ES = Cavador
  FR = Creuseur
  IT = Scavatore
  PL = Kopacz

String 82
  EN = Speed Up
  DE = Tempo
  ES = Acelerar
  FR = Accélérer
  IT = Accelera
  PL = Przyspieszenie

String 83
  EN = Nuke!
  DE = Feuer!
  ES = ¡Bombardea!
  FR = Annihiler !
  IT = Annienta!
  PL = Nuke!

String 84
  EN = Paused
  DE = Unterbrochen
  ES = Pausa
  FR = Interrompu
  IT = Interrotto
  PL = Pauza

String 85
  EN = Skill Names Enabled
  DE = Fähigkeit Namen Sind Sichtbar
  ES = Nombres De La Habilidad Son Visibles
  FR = Noms des capacités activés
  IT = I Nomi Di Abilità Sono Visibile
  PL = Nazwy umiejêtnoœci w³¹czone

String 86
  EN = Skill Names Disabled
  DE = Fähigkeit Namen Sind Nicht Sichtbar
  ES = Nombres De La Habilidad No Son Visibles
  FR = Noms des capacités désactivés
  IT = I Nomi Di Abilità Non Sono Visibile
  PL = Nazwy umiejêtnoœci wy³¹czone

String 87
  EN = You must save the Lemmings!
  DE = Sie müssen den Lemmings retten!
  ES = Usted debe rescatar el Lemmings!
  FR = Vous devez sauver les Lemmings !
  IT = Dovete salvare il Lemmings!
  PL = Musisz uratowaæ Lemmingi!

String 88
  EN = Help them to reach their balloon
  DE = Helfen Sie ihnen, zu ihrem Ballon zu entgehen
  ES = Ayude al Lemmings para alcanzar su globo
  FR = Aidez-les à atteindre leur montgolfière
  IT = Aiutare il Lemmings per raggiungere il loro aerostato
  PL = Pomó¿ im dotrzeæ do balonu

String 89
  EN = Click and hold RIGHT MOUSE BUTTON to rotate
  DE = Klicken Sie an und halten Sie die RECHTE^MAUSTASTE nieder sich drehen
  ES = BOTÓN de RATÓN DERECHO del tecleo y del considerar a rotar
  FR = Cliquez sur le BOUTON DROIT DE LA SOURIS et maintenez-le enfoncé pour pivoter
  IT = TASTO di MOUSE DI DESTRA della stretta^e di scatto da ruotare
  PL = Aby obróciæ, kliknij i przytrzymaj lewy klawisz myszy

String 90
  EN = For a closer look press SPACE BAR
  DE = Für eine genauere Blickpresse-cLeertaste
  ES = Para mirar más cercana prensa SPACEBAR
  FR = Pour voir les choses de plus près, appuyez sur la BARRE D'ESPACE
  IT = Premere la BARRA di SPAZIO per osservare^più molto attentamente
  PL = Aby przyjrzeæ siê bli¿ej, wciœnij SPACJÊ

String 91
  EN = Press SPACE BAR again to zoom out
  DE = Betätigen Sie ' LEERTASTE ' wieder Zoom aus
  ES = Presione la BARRA de ESPACIO otra vez para enfocar fuera de
  FR = Appuyez à nouveau sur la BARRE D'ESPACE pour effectuer un zoom arrière
  IT = Premere ancora la BARRA di SPAZIO per zumare verso l'esterno
  PL = Aby siê odsun¹æ, ponownie wciœnij SPACJÊ

String 92
  EN = You can give the Lemmings SKILLS^LEFT CLICK on the SKILL ICON
  DE = Sie konnen die FAHIGKEITEN Lemmings geban^KLICKEN VERLIESS auf der FÄHIGKEIT IKONE
  ES = Usted puede dar las HABILIDADES al Lemmings^TECLEO IZQUIERDO mientras que el cursor está concluído el ICONO de la HABILIDAD
  FR = Vous pouvez donner des CAPACITÉS aux Lemmings^CLIQUEZ SUR L'ICÔNE D'UNE CAPACITÉ à l'aide du BOUTON GAUCHE DE LA SOURIS
  IT = Potete dare le ABILITÀ al Lemmings^DI SINISTRA SCATTARE sopra l' ICONA di ABILITÀ
  PL = Mo¿esz nadaæ Lemmingom UMIEJÊTNOŒÆ^KLIKNIJ LEWYM IKONÊ UMIEJÊTNOŒCI

String 93
  EN = Now move the cursor over a Lemming^LEFT CLICK to activate the skill
  DE = Verschieben Sie jetzt den CURSOR über einem Lemming ^und einem LEFT-CLICK wieder
  ES = Ahora mueva el cursor concluído un Lemming^TECLEO IZQUIERDO para activar la habilidad
  FR = Amenez à présent le curseur au-dessus d'un Lemming^CLIQUEZ À L'AIDE DU BOUTON GAUCHE DE LA SOURIS pour activer la capacité
  IT = Ora spostare il cursore sopra un Lemming^SCATTO DI SINISTRA per attivare l' abilità
  PL = Teraz przesuñ kursor nad Lemminga^KLIKNIJ LEWYM, aby uaktywniæ umiejêtnoœæ

String 94
  EN = Here we've made a BLOCKER Lemming
  DE = Hier haben wir GEBLOCKT einen Lemming
  ES = Aquí hemos hecho un BLOQUE Lemming
  FR = Nous venons de créer un Lemming BLOQUEUR
  IT = Qui abbiamo fatto uno BLOCCATORE Lemming
  PL = Tutaj mamy Lemminga BLOKERA

String 95
  EN = All Lemmings will turn around when they meet him
  DE = Alles Lemmings dreht sich herum^wenn sie treffen ihn
  ES = Todo el Lemmings dará vuelta alrededor cuando lo satisfacen
  FR = Tous les Lemmings feront demi-tour lorsqu'ils le rencontreront
  IT = Tutto il Lemmings girerà intorno quando lo vengono a contatto di
  PL = Wszystkie Lemmingi odwróc¹ siê, kiedy na niego trafi¹

String 96
  EN = To get over the wall we need CLIMBERS
  DE = Über der Wand müssen wir erhalten BERGSTEIGER
  ES = Para conseguir concluído la pared necesitamos a TREPADORES
  FR = Pour passer au-dessus du mur, nous avons besoin de GRIMPEURS
  IT = Per ottenere sopra la parete abbiamo bisogno dei SCALATORE
  PL = Aby przedostaæ siê przez mur, potrzebujemy WSPINACZY

String 97
  EN = LEFT CLICK on the Icon^LEFT CLICK on the Lemming
  DE = LEFT-CLICK auf der Ikone^LEFT-CLICK auf dem Lemming
  ES = IZQUIERDO HAGA CLIC encendido el icono^IZQUIERDO HAGA CLIC encendido el Lemming
  FR = Pour choisir un Lemming qui va vers la GAUCHE^Maintenez la TOUCHE PORTANT UNE FLÈCHE VERS LA GAUCHE enfoncée
  IT = DI SINISTRA SCATTARE sopra l' icona^DI SINISTRA SCATTARE sopra il Lemming
  PL = KLIKNIJ LEWYM ikonê^KLIKNIJ LEWYM Lemminga

String 98
  EN = To choose a Lemming moving LEFT^Hold down the LEFT ARROW KEY
  DE = um ein Lemming zu wählen, das sich NACH LINKS bewegt^halten Sie die LINKE PFEILTASTE nach unten^und wenden Sie die Fähigkeit an
  ES = Para elegir una IZQUIERDA móvil de Lemming^mantenga IZQUIERDO el CLAVE de FLECHA
  FR = Pour choisir un Lemming qui va vers la DROITE^Maintenez la TOUCHE PORTANT UNE FLÈCHE VERS LA DROITE enfoncée
  IT = Per scegliere una PARTE DI SINISTRA commovente^di Lemming mantenere DI SINISTRA il TASTO di FRECCIA
  PL = Jeœli chcesz, ¿eby Lemming poszed³ W LEWO^Wciœnij LEWY KURSOR

String 99
  EN = To choose a Lemming moving RIGHT^Hold down the RIGHT ARROW KEY
  DE = um ein Lemming zu wählen, das sich NACH RECHTS bewegt^halten Sie die TASTE "CCursor NACH RECHTS" nach unten^und wenden Sie die Fähigkeit an
  ES = Para elegir una DERECHA móvil de Lemming^mantenga DERECHO el CLAVE de FLECHA
  FR = Pour donner une COMPÉTENCE à un Lemming^qui se déplace vers la DROITE^maintenez la TOUCHE DE DÉPLACEMENT DU CURSEUR DROITE
  IT = Per scegliere una DESTRA commovente di Lemming^mantenere il TASTO della FRECCIA A DESTRA
  PL = Jeœli chcesz, ¿eby Lemming poszed³ W PRAWO^Wciœnij PRAWY KURSOR

String 100
  EN = Look out for the drop!
  DE = Schauen Sie heraus nach dem Tropfen!     
  ES = Guárdese de la gota!
  FR = Attention à la chute !
  IT = Guardarsi dalla goccia!
  PL = Wypatruj kropli!

String 101
  EN = Ooops
  DE = Ooops
  ES = Ooops
  FR = Oups !
  IT = Ooops
  PL = £aaa

String 102
  EN = Give them the FLOATER skill^to fall safely from a great height
  DE = Geben Sie ihnen die FALLSCHIRM-Fähigkeit^zum Fall sicher von einer großen Höhe
  ES = Déles la habilidad del PARACAIDISTA a la caída^con seguridad de una gran altura
  FR = Donnez-leur la capacité de FLOTTEUR^pour leur permettre de tomber de haut en toute sécurité
  IT = Dare loro la caduta di skill^to del FLOATER sicuro grande da un' altezza
  PL = Nadaj im umiejêtnoœæ FRUWACZA^aby mog³y opaœæ bezpiecznie z du¿ej wysokoœci

String 103
  EN = Look out for the water!^These Lemmings will drown
  DE = Schauen Sie heraus nach dem water!^ Diese Lemmings ertrinkt
  ES = Guárdese del agua!^Este Lemmings se ahogará
  FR = Attention à l'eau !^Ces Lemmings vont se noyer
  IT = Guardarsi dall' acqua!^Questo Lemmings si annegherà
  PL = Wypatruj wody!^Te Lemmingi siê utopi¹.

String 104
  EN = We need a BUILDER to reach the other side
  DE = Wir benötigen einen HORIZONTALBUDDLER,^das andere seitliche zu erreichen
  ES = Necesitamos a un CONSTRUCTOR alcanzar el otro lateral
  FR = Nous aurons besoin d'un CONSTRUCTEUR pour atteindre l'autre côté
  IT = Abbiamo bisogno d'un COSTRUTTORE^di raggiungere l' altro laterale
  PL = Aby dostaæ siê na drug¹ stronê, potrzebujemy BUDOWNICZEGO

String 105
  EN = Now let's use a MINER
  DE = Verwenden Sie jetzt ein BERGMANN
  ES = Ahora utilicemos a un MINERO
  FR = Utilisons maintenant un MINEUR
  IT = Ora usiamo un MINATORE
  PL = A teraz u¿yjmy MINERA

String 106
  EN = A MINER cannot mine through metal^Try again on softer ground
  DE = Ein BERGMANN kann nicht durch Metall gewinnen^Versuchen noch einmal auf weicherem Boden
  ES = Un MINERO no puede minar con metal^Try otra vez en más lisa tierra
  FR = Le MINEUR ne peut pas creuser le métal^Essayez de nouveau sur un sol plus tendre
  IT = Un MINATORE non può estrarre attraverso metallo^Prova ancora più molle su terra
  PL = MINER nie mo¿e siê wkopaæ w metal^Spróbuj znaleŸæ miêkkie pod³o¿e

String 107
  EN = This is also true for DIGGERS and BASHERS
  DE = Dies gilt auch für GRÄBERS und HORIZONTALBUDDLERS
  ES = Esto es también verdad para los CAVADORES y BASHERS
  FR = C'est également le cas des CREUSEURS et des COGNEURS
  IT = Ciò è egualmente allineare per gli SCAVATORI e SFONDATORE
  PL = Dotyczy to tak¿e KOPACZY i ROZBIJACZY

String 108
  EN = Almost there!
  DE = Wir haben fast beendet!
  ES = Casi hemos acabado!
  FR = Vous y êtes presque !
  IT = Quasi abbiamo rifinito!
  PL = No prawie!

String 109
  EN = A DIGGER can now escape^But don't forget the others!
  DE = Ein VERTIKALWÜHLER kann jetzt entgehen^Aber vergessen Sie nicht die anderen!
  ES = Un CAVADOR puede ahora ser free^But usted no puede olvidarse del otros!
  FR = Un CREUSEUR peut à présent s'échapper^Mais n'oubliez pas les autres !
  IT = Uno SCAVATORE può ora fuoriuscire^Ma non dimenticare l' altri!
  PL = KOPACZ mo¿e teraz uciec^Ale nie zapomnij o innych!

String 110
  EN = A BASHER can free them^The BASHER must be next to the wall
  DE = Ein HORIZONTALBUDDLER kann sie^Ein HORIZONTALBUDDLER freigeben muß nahe bei der Sperre
  ES = Un GOLPE puede liberar them^The GOLPE debe estar al lado de la pared
  FR = Un COGNEUR peut les libérer^Il doit se trouver à proximité du mur
  IT = Un SFONDATORE può liberare them^The SFONDATORE deve essere vicino alla parete
  PL = ROZBIJACZ mo¿e je uwolniæ^ROZBIJACZ musi staæ obok muru

String 111
  EN = A BOMBER Lemming will explode
  DE = Ein BOMBE Lemming explodiert
  ES = Un BOMBARDERO Lemming estallará
  FR = Un Lemming À EXPLOSION va exploser
  IT = Un BOMBA Lemming esploderà
  PL = Lemming BOMBER wybuchnie

String 112
  EN = Click and hold on the balloon^to speed up time
  DE = Klicken Sie und halten Sie auf dem Ballon an,^um Zeit zu beschleunigen
  ES = Haga clic y sostenga en el globo para^acelerar tiempo
  FR = Cliquez sur la montgolfière et maintenez le bouton enfoncé pour accélérer l'horloge
  IT = Aerostato del sull del tenere di Scattarsi^e ' per il tempo del il del accelerare
  PL = Kliknij i przytrzymaj balon^aby przyspieszyæ up³yw czasu

String 113
  EN = If things go wrong^NUKE the level with a DOUBLE CLICK
  DE = Wenn Sachen gegangenes wrong^NUKE die Stufe mit einem DOPPELTEN Mäuse-cKlicken
  ES = Si van las cosas mal^NUKE el nivel con un DOBLE HACEN CLIC
  FR = Si la situation se gâte^un DOUBLE CLIC vous permet d'ANNIHILER le niveau
  IT = Se le cose vanno wrong^NUKE il livello con un DOPPIO SI SCATTANO
  PL = Jeœli sprawy potocz¹ siê Ÿle^NUKNIJ poziom PODWÓJNYM KLIKNIÊCIEM

String 114
  EN = Now it's your turn....
  DE = Jetzt ist es Ihre Umdrehung
  ES = Ahora es su vuelta
  FR = À vous de jouer...
  IT = Ora è la vostra girata
  PL = Teraz twoja kolej...

String 115
  EN = English
  DE = English
  ES = English
  FR = English
  IT = English
  PL = English

String 116
  EN = Français
  DE = Français
  ES = Français
  FR = Français
  IT = Français
  PL = Français

String 117
  EN = Deutsch
  DE = Deutsch
  ES = Deutsch
  FR = Deutsch
  IT = Deutsch
  PL = Deutsch

String 118
  EN = Italiano
  DE = Italiano
  ES = Italiano
  FR = Italiano
  IT = Italiano
  PL = Italiano

String 119
  EN = Español
  DE = Español
  ES = Español
  FR = Español
  IT = Español
  PL = Español

String 120
  EN = Language Selection
  DE = Sprachenwahl
  ES = Selección de idioma
  FR = Sélection de la langue
  IT = Selezione lingua
  PL = Wybór jêzyka

String 121
  EN = You Needed
  DE = Benötigt:
  ES = Necesitabas
  FR = Il vous en fallait
  IT = Occorreva
  PL = Potrzebnych

String 122
  EN = You Saved
  DE = Gerettet:
  ES = Salvaste
  FR = Vous en avez sauvé
  IT = Sono stati salvati
  PL = Uratowanych

String 123
  EN = Save
  DE = Rettung
  ES = Rescate
  FR = Enregistrer
  IT = Salvataggio
  PL = Zachowaj

String 124
  EN = Self-playing tutorial: Press P to pause
  DE = Lektion: P, um zu pausieren
  ES = Guía: Presione P para detenerse brevemente
  FR = Didacticiel automatique : Appuyez sur P pour interrompre
  IT = Lezione Privata: P per fare una pausa
  PL = Przewodnik: Aby w³¹czyæ pauzê, wciœnij klawisz P

String 125
  EN = Start New Game
  DE = Neues Spiel starten
  ES = Comenzar partida nueva
  FR = Commencer nouvelle partie
  IT = Inizia nuova partita
  PL = Rozpocznij now¹ grê

String 126
  EN = Play Current Game
  DE = Aktuelles Spiel spielen
  ES = Jugar partida en curso
  FR = Jouer partie actuelle
  IT = Gioca partita in corso
  PL = Rozegraj aktualn¹ grê

String 127
  EN = Back to Main Menu
  DE = Zurück zum Hauptmenü
  ES = Volver al menú principal
  FR = Retour au menu principal
  IT = Indietro al menu principale
  PL = Powrót do menu g³ównego

String 128
  EN = Polski
  DE = Polski
  ES = Polski
  FR = Polski
  IT = Polski
  PL = Polski

String 129
  EN = Continue?
  DE = Weiter?
  ES = Continuar?
  FR = Continuer?
  IT = Continua?
  PL = Kontynuuj?

According to this, uh... there's a self-playing tutorial? O-: How do you access it?

GuyPerfect

And now for the .SPR file format:

Code: [Select]
File
==================================================================
AnimCount       UInt32          Number of animations in this file
Animations      Animation[]     Animation definitions
(Unk1)          UInt32          Unknown value--if 0, is EoF
(Unk2)          UInt32          Unknown value
(UnkList)       UnkItem[]       Unknown list--remainder of file
==================================================================

Animation
==================================================================
FrameCount      UInt32          Number of frames in this animation
Frames          Frame[]         Frame definitions
IDSize          UInt32          Size of animation ID string
ID              UInt32          Animation ID string
(Unk1)          UInt32          Unknown value
(Unk2)          Float           Unknown value
==================================================================

Frame
==================================================================
AngleCount      UInt32          Number of angles for this frame
Angles          Angle[]         Angle definitions
==================================================================

Angle
==================================================================
(Unk1)          UInt32          Unknown value
(Unk2)          UInt32          Unknown value
(Unk3)          UInt32          Unknown value
(Unk4)          UInt32          Unknown value
(Unk5)          UInt32          Unknown value
(Unk6)          Float           Unknown value
(Unk7)          UInt32          Unknown value
(Unk8)          Float           Unknown value
(Unk9)          Float           Unknown value (may be UInt32)
(Unk10)         Float           Unknown value
(Unk11)         Float           Unknown value (may be UInt32)
(Unk12)         Float           Unknown value
(Unk13)         UInt32          Unknown value
(Unk14)         UInt32          Unknown value
(Unk15)         UInt32          Unknown value
(Unk16)         UInt32          Unknown value
TextureSize     UInt32          Size of texture name string
Texture         String          Texture name string
==================================================================

UnkItem -- The total number of these is not apparent in the data
==================================================================
(Unk1)          UInt32          Unknown value
(Unk2)          Float           Unknown value
==================================================================

Obviously, I haven't investigated this too thoroughly. Most of the values in the file have unknown significance, but I wanted to get the file structure documented.

Nothing appears to be vanilla ST texture coordinates, but besides that and timing information there can't be much in there. Maybe if someone's bored they can look through it a little closer.

Sample output from SPRITES\FIRE.SPR:

Code: [Select]
Animations = 1
  Animation 0
    Frames = 16
      Frame 0
        Angles = 1
          Angle 0
            (Unk1)  = 57
            (Unk2)  = 32
            (Unk3)  = 15
            (Unk4)  = 34
            (Unk5)  = 0xD1BC2503
            (Unk6)  = 1.796687f
            (Unk7)  = 0x0F6BF3AA
            (Unk8)  = 1.692771f
            (Unk9)  = 0.000000f
            (Unk10) = 1.484375f
            (Unk11) = 0.000000f
            (Unk12) = 1.632813f
            (Unk13) = 9
            (Unk14) = 0
            (Unk15) = 19
            (Unk16) = 25
            Texture = Sheets\_Fire0
      Frame 1
        Angles = 1
          Angle 0
            (Unk1)  = 59
            (Unk2)  = 31
            (Unk3)  = 71
            (Unk4)  = 90
            (Unk5)  = 0x9A3784A0
            (Unk6)  = 1.802711f
            (Unk7)  = 0x46F0940C
            (Unk8)  = 1.686747f
            (Unk9)  = 0.000000f
            (Unk10) = 1.763672f
            (Unk11) = 0.000000f
            (Unk12) = 1.800781f
            (Unk13) = 9
            (Unk14) = 0
            (Unk15) = 19
            (Unk16) = 28
            Texture = Sheets\_Fire0
      Frame 2
        Angles = 1
          Angle 0
            (Unk1)  = 83
            (Unk2)  = 59
            (Unk3)  = 37
            (Unk4)  = 56
            (Unk5)  = 0x00000000
            (Unk6)  = 1.875000f
            (Unk7)  = 0x9A3784A0
            (Unk8)  = 1.802711f
            (Unk9)  = 0.000000f
            (Unk10) = 1.644531f
            (Unk11) = 0.000000f
            (Unk12) = 1.718750f
            (Unk13) = 9
            (Unk14) = 0
            (Unk15) = 19
            (Unk16) = 24
            Texture = Sheets\_Fire0
      Frame 3
        Angles = 1
          Angle 0
            (Unk1)  = 58
            (Unk2)  = 32
            (Unk3)  = 34
            (Unk4)  = 53
            (Unk5)  = 0xB5F9D4D2
            (Unk6)  = 1.799699f
            (Unk7)  = 0x0F6BF3AA
            (Unk8)  = 1.692771f
            (Unk9)  = 0.000000f
            (Unk10) = 1.632813f
            (Unk11) = 0.000000f
            (Unk12) = 1.707031f
            (Unk13) = 9
            (Unk14) = 0
            (Unk15) = 19
            (Unk16) = 26
            Texture = Sheets\_Fire0
      Frame 4
        Angles = 1
          Angle 0
            (Unk1)  = 58
            (Unk2)  = 29
            (Unk3)  = 108
            (Unk4)  = 127
            (Unk5)  = 0xB5F9D4D2
            (Unk6)  = 1.799699f
            (Unk7)  = 0xB5F9D4D2
            (Unk8)  = 1.674699f
            (Unk9)  = 0.000000f
            (Unk10) = 1.835938f
            (Unk11) = 0.000000f
            (Unk12) = 1.873047f
            (Unk13) = 9
            (Unk14) = 0
            (Unk15) = 19
            (Unk16) = 29
            Texture = Sheets\_Fire0
      Frame 5
        Angles = 1
          Angle 0
            (Unk1)  = 60
            (Unk2)  = 31
            (Unk3)  = 90
            (Unk4)  = 108
            (Unk5)  = 0x7E75346F
            (Unk6)  = 1.805723f
            (Unk7)  = 0x46F0940C
            (Unk8)  = 1.686747f
            (Unk9)  = 0.000000f
            (Unk10) = 1.800781f
            (Unk11) = 0.000000f
            (Unk12) = 1.835938f
            (Unk13) = 8
            (Unk14) = 0
            (Unk15) = 18
            (Unk16) = 29
            Texture = Sheets\_Fire0
      Frame 6
        Angles = 1
          Angle 0
            (Unk1)  = 28
            (Unk2)  = 0
            (Unk3)  = 111
            (Unk4)  = 128
            (Unk5)  = 0xED7E7534
            (Unk6)  = 1.668675f
            (Unk7)  = 0x00000000
            (Unk8)  = 0.000000f
            (Unk9)  = 0.000000f
            (Unk10) = 1.841797f
            (Unk11) = 0.000000f
            (Unk12) = 1.875000f
            (Unk13) = 7
            (Unk14) = 0
            (Unk15) = 17
            (Unk16) = 28
            Texture = Sheets\_Fire0
      Frame 7
        Angles = 1
          Angle 0
            (Unk1)  = 29
            (Unk2)  = 0
            (Unk3)  = 92
            (Unk4)  = 111
            (Unk5)  = 0xB5F9D4D2
            (Unk6)  = 1.674699f
            (Unk7)  = 0x00000000
            (Unk8)  = 0.000000f
            (Unk9)  = 0.000000f
            (Unk10) = 1.804688f
            (Unk11) = 0.000000f
            (Unk12) = 1.841797f
            (Unk13) = 8
            (Unk14) = 0
            (Unk15) = 19
            (Unk16) = 29
            Texture = Sheets\_Fire0
      Frame 8
        Angles = 1
          Angle 0
            (Unk1)  = 31
            (Unk2)  = 0
            (Unk3)  = 56
            (Unk4)  = 75
            (Unk5)  = 0x46F0940C
            (Unk6)  = 1.686747f
            (Unk7)  = 0x00000000
            (Unk8)  = 0.000000f
            (Unk9)  = 0.000000f
            (Unk10) = 1.718750f
            (Unk11) = 0.000000f
            (Unk12) = 1.771484f
            (Unk13) = 8
            (Unk14) = 0
            (Unk15) = 19
            (Unk16) = 31
            Texture = Sheets\_Fire0
      Frame 9
        Angles = 1
          Angle 0
            (Unk1)  = 32
            (Unk2)  = 0
            (Unk3)  = 19
            (Unk4)  = 38
            (Unk5)  = 0x0F6BF3AA
            (Unk6)  = 1.692771f
            (Unk7)  = 0x00000000
            (Unk8)  = 0.000000f
            (Unk9)  = 0.000000f
            (Unk10) = 1.523438f
            (Unk11) = 0.000000f
            (Unk12) = 1.648438f
            (Unk13) = 8
            (Unk14) = 0
            (Unk15) = 19
            (Unk16) = 32
            Texture = Sheets\_Fire0
      Frame 10
        Angles = 1
          Angle 0
            (Unk1)  = 32
            (Unk2)  = 0
            (Unk3)  = 38
            (Unk4)  = 56
            (Unk5)  = 0x0F6BF3AA
            (Unk6)  = 1.692771f
            (Unk7)  = 0x00000000
            (Unk8)  = 0.000000f
            (Unk9)  = 0.000000f
            (Unk10) = 1.648438f
            (Unk11) = 0.000000f
            (Unk12) = 1.718750f
            (Unk13) = 8
            (Unk14) = 0
            (Unk15) = 18
            (Unk16) = 32
            Texture = Sheets\_Fire0
      Frame 11
        Angles = 1
          Angle 0
            (Unk1)  = 32
            (Unk2)  = 0
            (Unk3)  = 0
            (Unk4)  = 19
            (Unk5)  = 0x0F6BF3AA
            (Unk6)  = 1.692771f
            (Unk7)  = 0x00000000
            (Unk8)  = 0.000000f
            (Unk9)  = 0.000000f
            (Unk10) = 0.000000f
            (Unk11) = 0.000000f
            (Unk12) = 1.523438f
            (Unk13) = 8
            (Unk14) = 0
            (Unk15) = 19
            (Unk16) = 32
            Texture = Sheets\_Fire0
      Frame 12
        Angles = 1
          Angle 0
            (Unk1)  = 31
            (Unk2)  = 0
            (Unk3)  = 75
            (Unk4)  = 92
            (Unk5)  = 0x46F0940C
            (Unk6)  = 1.686747f
            (Unk7)  = 0x00000000
            (Unk8)  = 0.000000f
            (Unk9)  = 0.000000f
            (Unk10) = 1.771484f
            (Unk11) = 0.000000f
            (Unk12) = 1.804688f
            (Unk13) = 8
            (Unk14) = 0
            (Unk15) = 17
            (Unk16) = 31
            Texture = Sheets\_Fire0
      Frame 13
        Angles = 1
          Angle 0
            (Unk1)  = 59
            (Unk2)  = 32
            (Unk3)  = 53
            (Unk4)  = 71
            (Unk5)  = 0x9A3784A0
            (Unk6)  = 1.802711f
            (Unk7)  = 0x0F6BF3AA
            (Unk8)  = 1.692771f
            (Unk9)  = 0.000000f
            (Unk10) = 1.707031f
            (Unk11) = 0.000000f
            (Unk12) = 1.763672f
            (Unk13) = 8
            (Unk14) = 0
            (Unk15) = 18
            (Unk16) = 27
            Texture = Sheets\_Fire0
      Frame 14
        Angles = 1
          Angle 0
            (Unk1)  = 82
            (Unk2)  = 57
            (Unk3)  = 0
            (Unk4)  = 18
            (Unk5)  = 0x1BC25031
            (Unk6)  = 1.871988f
            (Unk7)  = 0xD1BC2503
            (Unk8)  = 1.796687f
            (Unk9)  = 0.000000f
            (Unk10) = 0.000000f
            (Unk11) = 0.000000f
            (Unk12) = 1.515625f
            (Unk13) = 8
            (Unk14) = 1
            (Unk15) = 18
            (Unk16) = 25
            Texture = Sheets\_Fire0
      Frame 15
        Angles = 1
          Angle 0
            (Unk1)  = 82
            (Unk2)  = 58
            (Unk3)  = 18
            (Unk4)  = 37
            (Unk5)  = 0x1BC25031
            (Unk6)  = 1.871988f
            (Unk7)  = 0xB5F9D4D2
            (Unk8)  = 1.799699f
            (Unk9)  = 0.000000f
            (Unk10) = 1.515625f
            (Unk11) = 0.000000f
            (Unk12) = 1.644531f
            (Unk13) = 9
            (Unk14) = 0
            (Unk15) = 19
            (Unk16) = 24
            Texture = Sheets\_Fire0
      ID     = fire
      (Unk1) = 0
      (Unk2) = 1.875000f
(Unk1) = 0

And for the curious, here are the animation IDs for SPRITES\LEMMINGSHI.SPR:

Animations = 15
  Animation 0:       ID = bash
  Animation 1:       ID = block
  Animation 2:       ID = build
  Animation 3:       ID = climb
  Animation 4:       ID = dig
  Animation 5:       ID = drown
  Animation 6:       ID = explode
  Animation 7:       ID = fall
  Animation 8:       ID = flip
  Animation 9:       ID = mine
  Animation 10:     ID = shrug
  Animation 11:     ID = float
  Animation 12:     ID = umbopen
  Animation 13:     ID = walk
  Animation 14:     ID = cartwheel

GuyPerfect

Sorry to leave you guys hanging, but I wanted to get through all the other file formats first before looking into the .LVL format. And this post documents the last of those, the .JBF file.

There's only one of these: TEXTURES\SHEETS\PSPBRWSE.JBF

Turns out this isn't actually a Lemmings Revolution file. Have you ever been browsing a ZIP file or something that you made and noticed http://www.forensicon.com/resources/casesummaries/cs_vosburgh.asp" class="bbc_link" target="_blank">that mysterious thumbs.db file that seems to have come out of nowhere? Well, this is almost exactly the same thing. The .JBF files are, or at one point were, produced by http://en.wikipedia.org/wiki/Paint_Shop_Pro" class="bbc_link" target="_blank">Paint Shop Pro to accelerate file browsing. They include thumbnails for images in a directory and presumably some other information on the file such as their dimensions and formats. I didn't look into it since I didn't really care--I was too anxious to rip it open and see if we could get a candid glimpse of some pre-release material that they never intended for the end user to see.

Well... not so much. The file appears to be recent as of the time the game was packaged for release--it contains thumbnails for all the final image files in TEXTURES\SHEETS\ and nothing more. Ah well. I'll take what I can get. Kinda funny that we have it, though. Somebody forgot to exclude it from the CD (or rather, from the .BOX file).

Code: [Select]
File
=========================================================================
Identifier      String*16       ASCII "JASC BROWS FILE" (null-terminated)
Junk            Byte[0x03F0]    Appears to be arbitrary, overwritten data
Files           CachedFile[]    Remainder of file
=========================================================================

CachedFile
=========================================================================
FilenameSize    UInt32          Size of filename
Filename        String          Filename--ASCII, not null-terminated
AppData         Byte[0x2C]      Unknown data--used by PaintShopPro
ThumbnailSize   UInt32          Size of thumbnail
Thumbnail       JPEG            Thumbnail data--expresses a JPEG file
=========================================================================

That "junk" region really does appear to just be 1008 bytes of nothing in particular. It's got some string remnants floating about, though:
  • D:\lrpc\Textures\Sheets\
  • 168)
  • down the SHIFT key to add to the selection, or the CTRL key to remove from the selection.
  • Magic Wand
  • chase the li
  • nsed vrsion. You are on day 7 of your 30 day evaluation period.
  • DRIVE 2l: 256 x 255 x 256 - (67
And the thumbnails aren't even all that good-looking. They're JPEGs with some pretty terrible compression going on:

http://i7.photobucket.com/albums/y255/bgng/NotQuiteBetaCalibur.png" alt="" class="bbc_img" />

GuyPerfect

So the guy I was working with on the compression algorithm was able to locate a name for the way the binary trees are stored as lists of numbers: http://en.wikipedia.org/wiki/Canonical_Huffman_code" class="bbc_link" target="_blank">canonical Huffman code. The article even uses the list 2, 1, 3, 3 as its example. (-:

Attached to this post is a new version of the LRZ utility with a working compressor, but it's slow. It's doing about the least efficient search possible for dictionary matches and while it's a well-traveled road speeding that up, it's not one I'm terribly familiar with. Fortunately, there's just one little function in the program that's imposing a bottleneck, so making that more efficient will make the entire program run faster.

The encoder does not make use of the binary trees. I'm not prepared to implement an algorithm that uses them; not without weeks of studying it.

It also occurs to me after examining the multi-language text strings that this game was at one point likely to have been in development for the Sony Playstation. I remember reading about how there was a PC demo for the game and Sony made the decision that the game would not see a PC release... well, enter Talonsoft to do the publishing work. I think that self-playing tutorial really existed, but nowadays there's no triangle button to exit out of it.

mobius

Quote
LEVELMAP.DAT file format:
"String 112
  EN = Click and hold on the balloon^to speed up time"

 http://www.lemmingsforums.com/Smileys/lemmings/huh.gif" alt="???" title="Huh?" class="smiley" /> this possibly explains something I've been wondering about (I thought it was a glitch)...

if you hover over the balloon notice the cursor changes. And If you try to scroll with right mouse button when your over the balloon, it doesn't work and your mouse moves like when your on top of the entrance (and you change the RR instead of scrolling)
[it makes a little more sense after reading that they apparently changed quite a bit before the game's release especially if they were gonna make it for the playstation] but clicking on the balloon to speed up? I'm glad they changed that.

I would love to see that self playing tutorial  http://www.lemmingsforums.com/Smileys/lemmings/laugh.gif" alt=":D" title="Laugh" class="smiley" />
in the instruction booklet (which I have since lost...) there's a complete walkthrough for the level "Now Your stalking"
everything by me: https://www.lemmingsforums.net/index.php?topic=5982.msg96035#msg96035

"Not knowing how near the truth is, we seek it far away."
-Hakuin Ekaku

"I have seen a heap of trouble in my life, and most of it has never come to pass" - Mark Twain


GuyPerfect

While I'm at it, the .BOX format (with some revisions since the time ccexplore documented it):

Code: [Select]
File
==============================================================================
Identifier      String*6        ASCII "LEMBOX"
NameCount       UInt32          Number of filename strings *
NamesSize       UInt32          Size of filename data
Names           Filename[]      NameCount elements within NamesSize bytes
OffsetCount     UInt32          Number of file offsets *
Offsets         UInt32[]        Offsets of files from the beginning of the BOX
SizeCount       UInt32          Number of file sizes *
Sizes           UInt32[]        Lengths of packed files
Data            Byte[]          Remainder of file--packed file data
==============================================================================
* NameCount, OffsetCount and SizeCount must all match

Filename
==============================================================================
NameSize        UInt32          Size of the filename string
Filename        String          NameSize bytes--ASCII, not null-terminated
==============================================================================

It's worth noting that there is no implied directory structure within the .BOX file. All filenames have their full paths prepended, which is rather inefficient use of storage space...

GuyPerfect

And last but not least, I've compiled a set of documents for all file formats up to this point in the thread, as well as information on the .BMP compression algorithm and naming convention.

The .LVL files are going to be the most complex, so I'll be starting another thread for those to work out the details. This thread should be reserved for merely documenting the format once it's figured out.

Attached to this post is a ZIP file containing the documents in question.