diff --git a/compress.c b/compress.c new file mode 100644 index 0000000..5d322dc --- /dev/null +++ b/compress.c @@ -0,0 +1,462 @@ +/* + exhal / inhal (de)compression routines + by Devin Acker + + This code is released under the terms of the MIT license. + See copying.txt for details. +*/ + +#include +#include "compress.h" + +#ifdef DEBUG_OUT +#define debug(...) printf(__VA_ARGS__) +#else +#define debug(...) +#endif + +uint8_t rotate (uint8_t); +rle_t rle_check (uint8_t*, uint8_t*, uint32_t); +backref_t ref_search (uint8_t*, uint8_t*, uint32_t); +uint16_t write_backref (uint8_t*, uint16_t, backref_t); +uint16_t write_rle (uint8_t*, uint16_t, rle_t); +uint16_t write_raw (uint8_t*, uint16_t, uint8_t*, uint16_t); + +// Compresses a file of up to 64 kb. +// unpacked/packed are 65536 byte buffers to read/from write to, +// inputsize is the length of the uncompressed data. +// Returns the size of the compressed data in bytes. +size_t pack(uint8_t *unpacked, uint32_t inputsize, uint8_t *packed) { + // current input/output positions + uint32_t inpos = 0; + uint32_t outpos = 0; + + // backref and RLE compression candidates + backref_t backref; + rle_t rle; + + // used to collect data which should be written uncompressed + uint8_t dontpack[LONG_RUN_SIZE]; + uint16_t dontpacksize = 0; + + debug("inputsize = %d\n", inputsize); + + while (inpos < inputsize) { + // check for a potential back reference + backref = ref_search(unpacked, unpacked + inpos, inputsize); + // and for a potential RLE + rle = rle_check(unpacked, unpacked + inpos, inputsize); + + // if the backref is a better candidate, use it + if (backref.size > 3 && backref.size > rle.size) { + // flush the raw data buffer first + outpos += write_raw(packed, outpos, dontpack, dontpacksize); + dontpacksize = 0; + + outpos += write_backref(packed, outpos, backref); + inpos += backref.size; + } + // or if the RLE is a better candidate, use it instead + else if (rle.size >= 2) { + // flush the raw data buffer first + + outpos += write_raw(packed, outpos, dontpack, dontpacksize); + dontpacksize = 0; + + outpos += write_rle(packed, outpos, rle); + inpos += rle.size; + + } + // otherwise, write this byte uncompressed + else { + dontpack[dontpacksize++] = unpacked[inpos++]; + + // if the raw data buffer is full, flush it + if (dontpacksize == LONG_RUN_SIZE) { + outpos += write_raw(packed, outpos, dontpack, dontpacksize); + dontpacksize = 0; + } + } + } + + // flush any remaining uncompressed data + outpos += write_raw(packed, outpos, dontpack, dontpacksize); + + //add the terminating byte + packed[outpos++] = 0xFF; + + return (size_t)outpos; +} + +// Decompresses a file of up to 64 kb. +// unpacked/packed are 65536 byte buffers to read/from write to, +// Returns the size of the uncompressed data in bytes. +size_t unpack(uint8_t *packed, uint8_t *unpacked) { + // current input/output positions + uint32_t inpos = 0; + uint32_t outpos = 0; + + uint8_t input; + uint16_t command, length, offset; + int methoduse[7] = {0}; + + while (1) { + // read command byte from input + input = packed[inpos++]; + + // command 0xff = end of data + if (input == 0xFF) + break; + + // check if it is a long or regular command, get the command no. and size + if ((input & 0xE0) == 0xE0) { + command = (input >> 2) & 0x07; + // get LSB of length from next byte + length = (((input & 0x03) << 8) | packed[inpos++]) + 1; + } else { + command = input >> 5; + length = (input & 0x1F) + 1; + } + + switch (command) { + // write uncompressed bytes + case 0: + for (int i = 0; i < length; i++) + unpacked[outpos++] = packed[inpos++]; + + break; + + // 8-bit RLE + case 1: + for (int i = 0; i < length; i++) + unpacked[outpos++] = packed[inpos]; + + inpos++; + break; + + // 16-bit RLE + case 2: + for (int i = 0; i < length; i++) { + unpacked[outpos++] = packed[inpos]; + unpacked[outpos++] = packed[inpos+1]; + } + + inpos += 2; + break; + + // 8-bit increasing sequence + case 3: + for (int i = 0; i < length; i++) + unpacked[outpos++] = packed[inpos] + i; + + inpos++; + break; + + // regular backref + // (offset is big-endian) + case 4: + case 7: + command = 4; + + offset = (packed[inpos] << 8) | packed[inpos+1]; + for (int i = 0; i < length; i++) + unpacked[outpos++] = unpacked[offset + i]; + + inpos += 2; + break; + + // backref with bit rotation + // (offset is big-endian) + case 5: + offset = (packed[inpos] << 8) | packed[inpos+1]; + for (int i = 0; i < length; i++) + unpacked[outpos++] = rotate(unpacked[offset + i]); + + inpos += 2; + break; + + // backwards backref + // (offset is big-endian) + case 6: + offset = (packed[inpos] << 8) | packed[inpos+1]; + for (int i = 0; i < length; i++) + unpacked[outpos++] = unpacked[offset - i]; + + inpos += 2; + break; + + default: + printf("Error: Unsupported method %i near 0x%06x\n", command, inpos); + exit(-1); + } + + // keep track of how many times each compression method is used + methoduse[command]++; + } + +#ifdef EXTRA_OUT + printf("Method Uses\n"); + printf("No compression : %i\n", methoduse[0]); + printf("RLE (8-bit) : %i\n", methoduse[1]); + printf("RLE (16-bit) : %i\n", methoduse[2]); + printf("RLE (sequence) : %i\n", methoduse[3]); + printf("Backref (normal) : %i\n", methoduse[4]); + printf("Backref (rotate) : %i\n", methoduse[5]); + printf("Backref (reverse): %i\n", methoduse[6]); +#endif + + return (size_t)outpos; +} + +// Decompress data from an offset into a file +size_t unpack_from_file (FILE *file, unsigned int offset, uint8_t *unpacked) { + uint8_t packed[DATA_SIZE]; + + fseek(file, offset, SEEK_SET); + fread((void*)packed, DATA_SIZE, 1, file); + return unpack(packed, unpacked); +} + +// Reverses the order of bits in a byte. +// One of the back reference methods does this. As far as game data goes, it seems to be +// pretty useful for compressing graphics. +uint8_t rotate (uint8_t i) { + uint8_t j = 0; + if (i & 0x01) j |= 0x80; + if (i & 0x02) j |= 0x40; + if (i & 0x04) j |= 0x20; + if (i & 0x08) j |= 0x10; + if (i & 0x10) j |= 0x08; + if (i & 0x20) j |= 0x04; + if (i & 0x40) j |= 0x02; + if (i & 0x80) j |= 0x01; + + return j; +} + +// Searches for possible RLE compressed data. +// Currently only does 8-bit RLE and sequence RLE (since 16-bit can be handled decently +// using the LZ77-like backrefs.) +// start and current are positions within the uncompressed input stream +rle_t rle_check (uint8_t *start, uint8_t *current, uint32_t insize) { + rle_t candidate = { 0, 0, 0 }; + int size; + + // check for possible 8-bit RLE + for (size = 0; current + size < start + insize; size++) + if (current[size] != current[0]) break; + + // if this is better than the current candidate, use it + if (size > LONG_RUN_SIZE) size = LONG_RUN_SIZE; + + if (size > 2 && size > candidate.size) { + candidate.size = size; + candidate.data = current[0]; + candidate.method = rle_8; + + debug("\trle_check: found new candidate (size = %d, method = %d)\n", candidate.size, candidate.method); + } + + // check for possible 16-bit RLE + uint16_t first = current[0] | (current[1] << 8); + for (size = 0; current + size < start + insize; size += 2) { + uint16_t next = current[size] | (current[size + 1] << 8); + if (next != first) break; + } + + // if this is better than the current candidate, use it + if (size > LONG_RUN_SIZE) size = LONG_RUN_SIZE; + + if (size > 2 && size > candidate.size) { + candidate.size = size; + candidate.data = first; + candidate.method = rle_16; + + debug("\trle_check: found new candidate (size = %d, method = %d)\n", candidate.size, candidate.method); + } + + // check for possible sequence RLE + for (size = 0; current + size < start + insize; size++) + if (current[size] != (current[0] + size)) break; + + // if this is better than the current candidate, use it + if (size > LONG_RUN_SIZE) size = LONG_RUN_SIZE; + + if (size > 2 && size > candidate.size) { + candidate.size = size; + candidate.data = current[0]; + candidate.method = rle_seq; + + debug("\trle_check: found new candidate (size = %d, method = %d)\n", candidate.size, candidate.method); + } + + return candidate; +} + +// Searches for the best possible back reference. +// start and current are positions within the uncompressed input stream +backref_t ref_search (uint8_t *start, uint8_t *current, uint32_t insize) { + backref_t candidate = { 0, 0, 0 }; + uint16_t size; + + for (uint8_t *pos = start; pos < current; pos++) { + // see how many bytes in a row are the same between the current uncompressed data + // and the data at the position being searched + for (size = 0; size <= LONG_RUN_SIZE && current + size < start + insize; size++) + if (pos[size] != current[size]) break; + + // if this is better than the current candidate, use it + if (size > LONG_RUN_SIZE) size = LONG_RUN_SIZE; + + if (size > 3 && size > candidate.size) { + candidate.size = size; + candidate.offset = pos - start; + candidate.method = lz_norm; + + debug("\tref_search: found new candidate (offset: %4x, size: %d, method = %d)\n", candidate.offset, candidate.size, candidate.method); + } + + // now repeat the check with the bit rotation method + for (size = 0; size <= LONG_RUN_SIZE && current + size < start + insize; size++) + if (pos[size] != rotate(current[size])) break; + + // if this is better than the current candidate, use it + if (size > LONG_RUN_SIZE) size = LONG_RUN_SIZE; + + if (size > 3 && size > candidate.size) { + candidate.size = size; + candidate.offset = pos - start; + candidate.method = lz_rot; + + debug("\tref_search: found new candidate (offset: %4x, size: %d, method = %d)\n", candidate.offset, candidate.size, candidate.method); + } + + // now repeat the check but go backwards + for (size = 0; size <= LONG_RUN_SIZE && current + size < start + insize; size++) + if (start[pos - start - size] != current[size]) break; + + // if this is better than the current candidate, use it + if (size > LONG_RUN_SIZE) size = LONG_RUN_SIZE; + + if (size > 3 && size > candidate.size) { + candidate.size = size; + candidate.offset = pos - start; + candidate.method = lz_rev; + + debug("\tref_search: found new candidate (offset: %4x, size: %d, method = %d)\n", candidate.offset, candidate.size, candidate.method); + } + } + + return candidate; +} + +// Writes a back reference to the compressed output stream. +// Returns number of bytes written +uint16_t write_backref (uint8_t *out, uint16_t outpos, backref_t backref) { + uint16_t size = backref.size - 1; + int outsize; + + debug("write_backref: writing backref to %4x, size %d (method %d)\n", backref.offset, backref.size, backref.method); + + // long run + if (size >= RUN_SIZE) { + // write command byte / MSB of size + out[outpos++] = (0xF0 + (backref.method << 2)) | (size >> 8); + // write LSB of size + out[outpos++] = size & 0xFF; + + outsize = 4; + } + // normal size run + else { + // write command byte / size + out[outpos++] = (0x80 + (backref.method << 5)) | size; + outsize = 3; + } + + // write MSB of offset + out[outpos++] = backref.offset >> 8; + // write LSB of offset + out[outpos++] = backref.offset & 0xFF; + + return outsize; +} + +// Writes RLE data to the compressed output stream. +// Returns number of bytes written +uint16_t write_rle (uint8_t *out, uint16_t outpos, rle_t rle) { + uint16_t size; + int outsize; + + if (rle.method == rle_16) + size = (rle.size / 2) - 1; + else + size = rle.size - 1; + + debug("write_rle: writing %d bytes of data 0x%02x (method %d)\n", rle.size, rle.data, rle.method); + + // long run + if (size >= RUN_SIZE) { + // write command byte / MSB of size + out[outpos++] = (0xE4 + (rle.method << 2)) | (size >> 8); + // write LSB of size + out[outpos++] = size & 0xFF; + + outsize = 3; + } + // normal size run + else { + // write command byte / size + out[outpos++] = (0x20 + (rle.method << 5)) | size; + + outsize = 2; + } + + out[outpos++] = rle.data; + // write upper byte of 16-bit RLE (and adjust written data size) + if (rle.method == rle_16) { + out[outpos++] = rle.data >> 8; + outsize++; + } + + return outsize; +} + +// Write uncompressed data to the output stream. +// Returns number of bytes written. +uint16_t write_raw (uint8_t *out, uint16_t outpos, uint8_t *in, uint16_t insize) { + if (!insize) return 0; + +#ifdef DEBUG_OUT + printf("write_raw: writing %d bytes unpacked data: ", insize); + for (int i = 0; i < insize; i++) + printf("%02x ", in[i]); + + printf("\n"); +#endif + + uint16_t size = insize - 1; + int outsize; + + if (size >= RUN_SIZE) { + // write command byte + MSB of size + out[outpos++] = 0xE0 + (size >> 8); + // write LSB of size + out[outpos++] = size & 0xFF; + + outsize = insize + 2; + } + // normal size run + else { + // write command byte / size + out[outpos++] = size; + + outsize = insize + 1; + } + + // write data + for (int i = 0; i < insize; i++) + out[outpos++] = in[i]; + + return outsize; +} diff --git a/compress.h b/compress.h new file mode 100644 index 0000000..f8201dc --- /dev/null +++ b/compress.h @@ -0,0 +1,48 @@ +/* + exhal / inhal (de)compression routines + by Devin Acker + + This code is released under the terms of the MIT license. + See copying.txt for details. +*/ + +#ifndef _COMPRESS_H +#define _COMPRESS_H + +#include +#include +#include + +#define DATA_SIZE 65536 +#define RUN_SIZE 32 +#define LONG_RUN_SIZE 1024 + +// compression method values for backref_t and rle_t +typedef enum { + rle_8 = 0, + rle_16 = 1, + rle_seq = 2, + + lz_norm = 0, + lz_rot = 1, + lz_rev = 2 +} method_e; + +// used to store and compare backref candidates +typedef struct { + uint16_t offset, size; + method_e method; +} backref_t; + +// used to store RLE candidates +typedef struct { + uint16_t size, data; + method_e method; +} rle_t; + +size_t pack (uint8_t *unpacked, size_t inputsize, uint8_t *packed); +size_t unpack (uint8_t *packed, uint8_t *unpacked); + +size_t unpack_from_file (FILE *file, unsigned int offset, uint8_t *unpacked); + +#endif diff --git a/copying.txt b/copying.txt new file mode 100644 index 0000000..1146740 --- /dev/null +++ b/copying.txt @@ -0,0 +1,19 @@ +Copyright (c) 2013 Devin Acker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/exhal.c b/exhal.c new file mode 100644 index 0000000..6aba55e --- /dev/null +++ b/exhal.c @@ -0,0 +1,64 @@ +/* + exhal - HAL Laboratory decompression tool + by Devin Acker + + Usage: + exhal romfile offset outfile + + This code is released under the terms of the MIT license. + See copying.txt for details. +*/ + +#include +#include "compress.h" + +int main (int argc, char **argv) { + printf("exhal - %s %s\nby Devin Acker (Revenant)\n\n", __DATE__, __TIME__); + + if (argc != 4) { + printf("Usage:\n%s romfile offset outfile\n", argv[0]); + printf("Example: %s kirbybowl.sfc 0x70000 test.bin\n\n", argv[0]); + printf("offset can be in either decimal or hex.\n"); + exit(-1); + } + + FILE *infile, *outfile; + + // open ROM file for input + infile = fopen(argv[1], "rb"); + if (!infile) { + printf("Error: unable to open %s\n", argv[1]); + exit(-1); + } + + // open target file for output + outfile = fopen(argv[3], "wb"); + if (!outfile) { + printf("Error: unable to open %s\n", argv[1]); + exit(-1); + } + + size_t outputsize; + uint32_t fileoffset; + uint8_t unpacked[DATA_SIZE]; + uint8_t packed[DATA_SIZE]; + memset(packed, 0, DATA_SIZE); + + fileoffset = strtol(argv[2], NULL, 0); + + // read the file + fseek(infile, fileoffset, SEEK_SET); + fread(packed, sizeof(uint8_t), DATA_SIZE, infile); + + // compress the file + outputsize = unpack(packed, unpacked); + + // write the uncompressed data to the file + fseek(infile, 0, SEEK_SET); + fwrite((const void*)unpacked, 1, outputsize, outfile); + + printf("\nUncompressed size: %d bytes\n", outputsize); + + fclose(infile); + fclose(outfile); +} diff --git a/gamenotes.txt b/gamenotes.txt new file mode 100644 index 0000000..80baa88 --- /dev/null +++ b/gamenotes.txt @@ -0,0 +1,59 @@ +This is an incomplete list of decompression routine addresses for games which are supported by the +exhal compression tool. You can use these addresses to try finding compressed data by searching a +disassembly (or doing a binary search, i.e. JSR $87CA -> 20 CA 87; JSL $8087C6 -> 22 C6 87 80.) + +Alcahest (SNES) + JP: JSR $87CA (in bank 80) or JSL $8087C6 + +Arcana / Card Master (SNES) + US/JP: JSR $8766 (in bank 00) or JSL $808762 + +EarthBound / Mother 2 (SNES) + US: JSL $C41A9E + JP: JSL $C419EA + +HAL's Hole in One Golf / Jumbo Ozaki no Hole in One (SNES) + US/EU/JP: JSR $89AA (in bank 00) or JSL $0089A6 + +HyperZone (SNES) + US/EU/JP: JSR $89E6 (in bank 00) or JSL $0089E2 + +Itoi Shigesato no Bass Tsuri No. 1 (SNES) + JP: JSL $01DD8A or JSL $01DEAA + +Kirby no KiraKira Kids (SNES) + JP: JSR $89DF (in bank 80) or JSL $8089DB + +Kirby Super Star (SNES) + US/EU/JP: JSL $00889A + +Kirby's Dream Course / Kirby Bowl (SNES) + US/EU: JSL $809F18 + JP: JSL $809F1A + +Kirby's Dream Land 3 (SNES) + US/JP: JSL $00AA55 or JSL $00AA63 + +Othello World (SNES) + JP: JSR $CC48 (in bank 00/80) + +Okamoto Ayako to Match Play Golf (SNES) + JP: JSR $983B (in bank 00) + +SimCity (SNES) [unused?] + US: JSR $90DD + EU: JSR $90D3 + JP: JSR $90A6 + +SimCity 2000 (SNES) + US/EU/JP: JSL $C10000 + +Special Tee Shot (BS-X) + JP: JSL $838E13 + +Super Famicom Box BIOS (SNES) + JP: JSR $88A2 (in bank 00) or JSL $00889E + +Vegas Stakes (SNES) + US/EU: JSR $87DB (in bank 00) or JSL $0087D7 + JP: JSR $87F6 (in bank 00) or JSL $0087F2 diff --git a/inhal.c b/inhal.c new file mode 100644 index 0000000..a02b532 --- /dev/null +++ b/inhal.c @@ -0,0 +1,87 @@ +/* + inhal - HAL Laboratory compression tool + by Devin Acker + + Usage: + inhal infile romfile offset + inhal -n infile outfile + + This code is released under the terms of the MIT license. + See copying.txt for details. +*/ + +#include +#include "compress.h" + +int main (int argc, char **argv) { + printf("inhal - %s %s\nby Devin Acker (Revenant)\n\n", __DATE__, __TIME__); + + if (argc != 4) { + printf("To insert compressed data into a ROM:\n"); + printf("%s infile romfile offset\n", argv[0]); + + printf("To write compressed data to a new file:\n"); + printf("%s -n infile outfile\n", argv[0]); + + printf("\nExample:\n%s test.chr kirbybowl.sfc 0x70000\n", argv[0]); + printf("%s -n test.chr test-packed.bin\n\n", argv[0]); + printf("offset can be in either decimal or hex.\n"); + exit(-1); + } + + FILE *infile, *outfile; + int fileoffset; + + // check for -n switch + if (!strcmp(argv[1], "-n")) { + fileoffset = 0; + infile = fopen(argv[2], "rb"); + outfile = fopen(argv[3], "wb"); + } else { + fileoffset = strtol(argv[3], NULL, 0); + infile = fopen(argv[1], "rb"); + outfile = fopen(argv[2], "r+b"); + } + + if (!infile) { + printf("Error: unable to open input file\n"); + exit(-1); + } + if (!outfile) { + printf("Error: unable to open output file\n"); + exit(-1); + } + + size_t inputsize, outputsize; + uint8_t unpacked[DATA_SIZE]; + uint8_t packed[DATA_SIZE]; + memset(packed, 0, DATA_SIZE); + + // check size of input file + fseek(infile, 0, SEEK_END); + inputsize = ftell(infile); + + printf("Uncompressed size: %d bytes\n", inputsize); + + if (inputsize > DATA_SIZE) { + printf("Error: File must be a maximum of 65,536 bytes!\n"); + exit(-1); + } + // read the file + fseek(infile, 0, SEEK_SET); + fread(unpacked, sizeof(uint8_t), inputsize, infile); + + // compress the file + outputsize = pack(unpacked, inputsize, packed); + + // write the compressed data to the file + fseek(outfile, fileoffset, SEEK_SET); + fwrite((const void*)packed, 1, outputsize, outfile); + + printf("Compressed size: %d bytes\n", outputsize); + printf("Compression ratio: %3.2f\n\n", (double)outputsize / inputsize); + printf("Inserted at 0x%06X - 0x%06X\n", fileoffset, ftell(outfile) - 1); + + fclose(infile); + fclose(outfile); +} diff --git a/makefile b/makefile new file mode 100644 index 0000000..1beb538 --- /dev/null +++ b/makefile @@ -0,0 +1,30 @@ +# HAL (de)compression tools +# copyright 2013 Devin Acker (Revenant) +# See copying.txt for legal information. + +CC = gcc +FLAGS = -std=c99 -O2 -Wall + +# Add extension when compiling for Windows +ifdef SystemRoot + EXT = .exe +endif + +# Comment this line to suppress detailed decompression information on stdout +DEFINES += -DEXTRA_OUT +# Uncomment this line to enable debug output +#DEFINES += -DDEBUG_OUT + +all: inhal$(EXT) exhal$(EXT) + +clean: + del inhal$(EXT) exhal$(EXT) compress.o + +inhal$(EXT): inhal.c compress.o + $(CC) $(DEFINES) $(FLAGS) -o inhal$(EXT) inhal.c compress.o + +exhal$(EXT): exhal.c compress.o + $(CC) $(DEFINES) $(FLAGS) -o exhal$(EXT) exhal.c compress.o + +compress.o: compress.c + $(CC) $(DEFINES) $(FLAGS) -c compress.c \ No newline at end of file diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..223762f --- /dev/null +++ b/readme.txt @@ -0,0 +1,80 @@ +exhal / inhal +HAL Laboratory NES/SNES/GB (de)compression tools +by Devin Acker (Revenant), 2013 + +These programs are released under the terms of the MIT license. See copying.txt for legal info. + +exhal and inhal are tools designed to decompress and recompress/insert data used by several NES, +SNES and Game Boy games developed by HAL Laboratory. + +Due to the design of the original decompression algorithm (and hardware limitations), the size of +a file to be compressed is limited to 64 kilobytes (65,536 bytes). Please note that depending on +which system you are working with, the actual useful limit may be much smaller. + +To use exhal (the decompressor): +exhal romfile offset outfile + +To insert compressed data into a ROM: +inhal infile romfile offset + +To write compressed data to a new file: +inhal -n infile outfile + +Offsets can be specified in either hexadecimal (recommended) or decimal. + +This is a list of games which are known to use the supported compression method, or are assumed +to, based on a binary search of the games' ROMs: + +Adventures of Lolo (NES/GB) +Adventures of Lolo 2 (NES) +Adventures of Lolo 3 (NES) +Alcahest (SNES) +Arcana / Card Master (SNES) +EarthBound / Mother 2 (SNES) +Ghostbusters II (GB) +HAL's Hole in One Golf / Jumbo Ozaki no Hole in One (SNES) +HyperZone (SNES) +Itoi Shigesato no Bass Tsuri No. 1 (SNES) +Kirby no KiraKira Kids (SNES) +Kirby Super Star (SNES) +Kirby's Adventure (NES) +Kirby's Dream Course / Kirby Bowl (SNES) +Kirby's Dream Land (GB) +Kirby's Dream Land 2 (GB) +Kirby's Dream Land 3 (SNES) +Kirby's Pinball Land (GB) +Kirby's Star Stacker / KiraKira Kids (GB) +NES Open Tournament Golf (NES) +New Ghostbusters II (NES) +Othello World (SNES) +Okamoto Ayako to Match Play Golf (SNES) +Revenge of the Gator / 66 Hiki no Wani Daikoushin (GB) +SimCity (SNES) [unused?] +SimCity 2000 (SNES) +Special Tee Shot (BS-X) +Super Famicom Box BIOS (SNES) +Trax / Totsugeki! Ponkotsu Tank (GB) +Vegas Stakes (SNES/GB) + +Also note, unfortunately, that exhal cannot automatically detect or locate compressed data. +The included file "gamenotes.txt" contains an incomplete list of decompression routine addresses +to make searching easier. + +These tools were originally used in the development of my Kirby's Dream Course editor. I hope you +find your own exciting use for them. (I'm not the only Kirby hacker in the West, right? *sob*) + +Contact me: + + Email : d at revenant1.net + IRC : "devin" on irc.badnik.net + "Revenant" on irc.oftc.net + "Revenant`" on irc.synirc.net + and irc.dal.net + Forums: http://jul.rustedlogic.net/profile.php?id=504 + http://www.romhacking.net/forum/index.php?action=profile;u=10455 + +Special thanks to: + + - andlabs for helping me make the list of supported games + - BMF54123 for naming the programs + - You for downloading (and using?) my software \ No newline at end of file