Still don't know the details of the algorithm in whole, but still looking into it.
We made a C program that can decode single-chunk compressed .bmp files from Lemmings Revolution using the code from its EXE file. It requires an x86 architecture to work, but that's how it goes.
Usage: lrdecomp <inputfile> <outputfile>
A compiled version is attached to this post, while the source code is this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PRG_SIZE 609
#define PRG_BASE 0x00467C53
// Codewalker's hax
char staticmemory[0x100000];
int len;
char *outbuf;
void *decp1dst = (void*)0x00467c53;
unsigned char *out_bmp;
// The mystery program data
static unsigned char PRG_ROM[PRG_SIZE] = {
0x51,0x57,0x8D,0x3D,0x28,0x12,0x4C,0x00,0x89,0x3D,0x2C,0x0F,0x4C,0x00,0x83,0xC7,
0x11,0x89,0x3D,0x30,0x0F,0x4C,0x00,0x83,0xC7,0x03,0x33,0xDB,0xE8,0x83,0x00,0x00,
0x00,0x72,0x16,0xBB,0x01,0x00,0x00,0x00,0xE8,0x77,0x00,0x00,0x00,0x72,0x0A,0xBB,
0x02,0x00,0x00,0x00,0xE8,0x6B,0x00,0x00,0x00,0x5F,0x59,0x73,0x02,0xF9,0xC3,0x49,
0xB2,0x80,0xFC,0x51,0x33,0xDB,0xE8,0x8F,0x01,0x00,0x00,0x85,0xC0,0x74,0x21,0x8B,
0xC8,0x8B,0xD8,0xB0,0x01,0x8A,0x26,0x22,0xE2,0xD0,0xCA,0x83,0xD6,0x00,0x80,0xC4,
0xFF,0xD0,0xD0,0x73,0xF0,0xAA,0xE2,0xEB,0x8B,0xC3,0x59,0x2B,0xC8,0x72,0x2E,0x51,
0xBB,0x01,0x00,0x00,0x00,0xE8,0x60,0x01,0x00,0x00,0x50,0xBB,0x02,0x00,0x00,0x00,
0xE8,0x55,0x01,0x00,0x00,0x8B,0xDE,0x8B,0xF7,0xF9,0x1B,0xF0,0x59,0x83,0xC1,0x02,
0x8B,0xC1,0xF3,0xA4,0x8B,0xF3,0x59,0x2B,0xC8,0x72,0x02,0xEB,0xA6,0xD0,0xC2,0xF5,
0x83,0xD6,0x00,0xC3,0x8A,0x06,0x46,0xA8,0x0F,0x75,0x0A,0x88,0x83,0x3A,0x0F,0x4C,
0x00,0x3C,0x01,0xF5,0xC3,0x53,0x57,0x8B,0x3D,0x2C,0x0F,0x4C,0x00,0x8A,0xF8,0x66,
0x83,0xE0,0x0F,0x04,0x02,0x8A,0xE8,0xB1,0x04,0xD2,0xEF,0x38,0xFC,0x73,0x02,0x8A,
0xE7,0x88,0x3F,0x47,0xFE,0xCD,0x74,0x15,0x8A,0x1E,0x46,0x8A,0xFB,0x80,0xE3,0x0F,
0x38,0xDC,0x73,0x02,0x8A,0xE3,0x88,0x1F,0x47,0xFE,0xCD,0x75,0xDC,0xA2,0x34,0x0F,
0x4C,0x00,0x88,0x25,0x35,0x0F,0x4C,0x00,0x5F,0x5B,0x56,0x8B,0xF3,0x88,0x86,0x3A,
0x0F,0x4C,0x00,0xC1,0xE6,0x02,0x89,0xBE,0x3E,0x0F,0x4C,0x00,0x89,0x3D,0x36,0x0F,
0x4C,0x00,0xC6,0x07,0x00,0x47,0x8B,0x35,0x30,0x0F,0x4C,0x00,0xC6,0x06,0x00,0xB6,
0x80,0xB5,0x01,0x8B,0x1D,0x2C,0x0F,0x4C,0x00,0x8A,0x0D,0x34,0x0F,0x4C,0x00,0x38,
0x2B,0x74,0x1F,0x43,0xFE,0xC9,0x75,0xF7,0x88,0x0F,0x47,0xD0,0xCE,0x83,0xD6,0x00,
0x8A,0xC6,0xF6,0xD0,0x20,0x06,0xFE,0xC5,0x38,0x2D,0x35,0x0F,0x4C,0x00,0x73,0xD3,
0xEB,0x20,0x8A,0x25,0x34,0x0F,0x4C,0x00,0x2A,0xE1,0xB0,0x00,0x66,0x89,0x07,0x47,
0x47,0x8A,0xD5,0x8A,0xC5,0x84,0x36,0x74,0x0C,0xD0,0xC6,0x83,0xDE,0x00,0xFE,0xC8,
0x75,0xF3,0x5E,0xF8,0xC3,0x08,0x36,0x53,0xB6,0x80,0x8B,0x35,0x30,0x0F,0x4C,0x00,
0x8B,0x1D,0x36,0x0F,0x4C,0x00,0x84,0x36,0x75,0x03,0x43,0xEB,0x2C,0x8A,0x03,0x66,
0x25,0xFF,0x00,0x75,0x22,0x8B,0xC7,0x2B,0xC3,0x3D,0x00,0x01,0x00,0x00,0x73,0x22,
0x88,0x03,0xFE,0xCA,0x74,0x20,0xC6,0x07,0x00,0x47,0xD0,0xCE,0x83,0xD6,0x00,0x8A,
0xC6,0xF6,0xD0,0x20,0x06,0xEB,0xEB,0x03,0xD8,0xD0,0xCE,0x83,0xD6,0x00,0xFE,0xCA,
0x75,0xC4,0x5B,0x5E,0xF9,0xC3,0x5B,0x43,0xFE,0xC9,0x75,0x05,0xE9,0x67,0xFF,0xFF,
0xFF,0x38,0x2B,0x75,0xF2,0xE9,0x78,0xFF,0xFF,0xFF,0x8B,0xCB,0x0F,0xB6,0x83,0x3A,
0x0F,0x4C,0x00,0x84,0xC0,0x75,0x33,0xB9,0x00,0x00,0x00,0x00,0x84,0x16,0x74,0x09,
0xFE,0xC1,0xD0,0xCA,0x83,0xD6,0x00,0xEB,0xF3,0xD0,0xCA,0x83,0xD6,0x00,0x33,0xC0,
0x84,0xC9,0x74,0x15,0x40,0x8A,0x2E,0x22,0xEA,0x80,0xC5,0xFF,0x66,0xD1,0xD0,0xD0,
0xCA,0x83,0xD6,0x00,0xFE,0xC9,0x75,0xED,0x48,0xC3,0x03,0xD9,0xD1,0xE3,0x8B,0x9B,
0x3E,0x0F,0x4C,0x00,0x84,0x16,0x75,0x03,0x43,0xEB,0x05,0x0F,0xB6,0x03,0x03,0xD8,
0xD0,0xCA,0x83,0xD6,0x00,0x80,0x3B,0x00,0x75,0xEA,0x43,0x8A,0x0B,0x80,0xF9,0x02,
0x72,0x1B,0xFE,0xC9,0xB8,0x01,0x00,0x00,0x00,0x8A,0x2E,0x22,0xEA,0x80,0xC5,0xFF,
0x66,0xD1,0xD0,0xD0,0xCA,0x83,0xD6,0x00,0xFE,0xC9,0x75,0xED,0xC3,0x0F,0xB6,0xC1,
0xC3};
// Calculates the total unpacked size of a file and returns the length
int lrdGetSize(unsigned char *data, int size) {
int chunks, packed, unpacked = 0, x, off = 1;
// Error checking
if (data == NULL || size < 6) return 0;
// Get the number of chunks in the file
chunks = (int) data[0];
if (chunks < 1) return 0;
// Read the unpacked sizes for all chunks
for (x = 0; x < chunks; x++) {
if (off + 4 > size) return 0;
packed = (int) data[off + 1] << 8 | (int) data[off];
if (off + packed > size) return 0;
unpacked += (int) data[off + 3] << 8 | (int) data[off + 2];
off += packed;
}
return unpacked;
}
// Decompresses a Lemmings Revolution bitmap file
int lrdDecompress(unsigned char *src, int srclen,
unsigned char *dst, int dstlen) {
// Error checking
if (src == NULL || srclen < 6 || dst == NULL || dstlen < 1) return 1;
//memset(staticmemory, 0, 0x100000);
memcpy((void *) PRG_BASE, PRG_ROM, PRG_SIZE);
out_bmp = src;
outbuf = dst;
len = (int) dstlen;
memset(outbuf, 0, len);
asm("movl _out_bmp, %esi\n"
"add $6, %esi\n"
"movl _outbuf, %edi\n"
"movl _len, %ecx\n"
"call *_decp1dst");
return 0;
}
// Program entry point
int main(int argc, char **argv) {
FILE *fPtr;
int fLen, uLen;
unsigned char *fData, *uData;
if (argc != 3) {
printf("Usage: %s <inputfile> <outputfile>\n", argv[0]);
return 0;
}
fPtr = fopen(argv[1], "rb");
if (fPtr == NULL) {
printf("Could not open %s\n", argv[1]);
return 1;
}
fseek(fPtr, 0, SEEK_END);
fLen = ftell(fPtr);
if (fLen < 6) {
fclose(fPtr);
printf("Invalid data in %s\n", argv[1]);
return 1;
}
fData = malloc(fLen);
fseek(fPtr, 0, SEEK_SET);
uLen = fread(fData, 1, fLen, fPtr);
fclose(fPtr);
if (uLen != fLen) {
printf("Could not read data from %s\n", argv[1]);
return 1;
}
uLen = lrdGetSize(fData, fLen);
if (!uLen) {
free(fData);
printf("Invalid data in %s\n", argv[1]);
return 1;
}
printf("File size: %04X\n", uLen);
uData = malloc(uLen);
fLen = lrdDecompress(fData, fLen, uData, uLen);
fPtr = fopen(argv[2], "wb");
if (fPtr == NULL) {
printf("Error writing %s\n", argv[2]);
} else {
fwrite(uData, 1, uLen, fPtr);
fclose(fPtr);
printf("Wrote %s\n", argv[2]);
}
free(fData);
free(uData);
return 0;
}