├── .gitignore ├── README.md ├── io.h ├── Makefile ├── io.cpp ├── decompress.cpp └── compress.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bin/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gba-lz77 2 | 3 | A GBA-compatible implementation of LZ77 compression/decompression. 4 | 5 | The compressor will produce byte-identical results to whatever was used on LEGO Island 2 (GBA, 2001), and presumably other GBA games too. 6 | 7 | Each tool takes two commandline arguments: the input file path followed by the output file path. -------------------------------------------------------------------------------- /io.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace io 7 | { 8 | uint8_t readU8(FILE* fh); 9 | uint16_t readU16(FILE* fh); 10 | uint32_t readU32(FILE* fh); 11 | void writeU8(FILE* fh, uint8_t value); 12 | void writeU16(FILE* fh, uint16_t value); 13 | void writeU32(FILE* fh, uint32_t value); 14 | size_t length(FILE* fh); 15 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CCFLAGS = -lstdc++ -std=c++14 -Wall -Wextra -Wpedantic -Werror 2 | 3 | all: bin/gba-lz77-compress bin/gba-lz77-decompress 4 | 5 | bin/gba-lz77-decompress: io.h io.cpp decompress.cpp Makefile 6 | clang $(CCFLAGS) decompress.cpp io.cpp -o bin/gba-lz77-decompress 7 | 8 | bin/gba-lz77-compress: io.h io.cpp compress.cpp Makefile 9 | clang $(CCFLAGS) compress.cpp io.cpp -o bin/gba-lz77-compress 10 | -------------------------------------------------------------------------------- /io.cpp: -------------------------------------------------------------------------------- 1 | #include "io.h" 2 | 3 | namespace io 4 | { 5 | uint8_t readU8(FILE* fh) 6 | { 7 | uint8_t value; 8 | fread(&value, sizeof(value), 1, fh); 9 | return value; 10 | } 11 | 12 | uint16_t readU16(FILE* fh) 13 | { 14 | uint16_t value; 15 | fread(&value, sizeof(value), 1, fh); 16 | return value; 17 | } 18 | 19 | uint32_t readU32(FILE* fh) 20 | { 21 | uint32_t value; 22 | fread(&value, sizeof(value), 1, fh); 23 | return value; 24 | } 25 | 26 | void writeU8(FILE* fh, uint8_t value) 27 | { 28 | fwrite(&value, sizeof(value), 1, fh); 29 | } 30 | 31 | void writeU16(FILE* fh, uint16_t value) 32 | { 33 | fwrite(&value, sizeof(value), 1, fh); 34 | } 35 | 36 | void writeU32(FILE* fh, uint32_t value) 37 | { 38 | fwrite(&value, sizeof(value), 1, fh); 39 | } 40 | 41 | size_t length(FILE* fh) 42 | { 43 | size_t pos = ftell(fh); 44 | fseek(fh, 0, SEEK_END); 45 | size_t length = ftell(fh); 46 | fseek(fh, pos, SEEK_SET); 47 | return length; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /decompress.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "io.h" 5 | 6 | int main(int argc, char** argv) 7 | { 8 | if (argc <= 2) 9 | return 1; 10 | 11 | const char* inPath = argv[1]; 12 | const char* outPath = argv[2]; 13 | 14 | FILE* fhIn = fopen(inPath, "rb"); 15 | if (!fhIn) 16 | return 1; 17 | 18 | uint32_t header = io::readU32(fhIn); 19 | uint8_t magic = (header & 0xff); 20 | uint32_t decompressedLength = (header >> 8); 21 | 22 | if (magic != 0x10) { 23 | printf("Incorrect magic number, expected 0x10, got 0x%02x\n", magic); 24 | return 1; 25 | } 26 | 27 | uint8_t* destBuffer = new uint8_t[decompressedLength]; 28 | size_t bytesWritten = 0; 29 | while (bytesWritten < decompressedLength) 30 | { 31 | uint8_t flags = io::readU8(fhIn); 32 | for (int i = 0; i < 8 && bytesWritten < decompressedLength; ++i) 33 | { 34 | bool type = (flags & (0x80 >> i)); 35 | if (!type) 36 | { 37 | uint8_t value = io::readU8(fhIn); 38 | destBuffer[bytesWritten] = value; 39 | bytesWritten++; 40 | } 41 | else 42 | { 43 | uint16_t value = io::readU16(fhIn); 44 | uint16_t disp = ((value & 0xf) << 8) | (value >> 8); 45 | uint8_t n = ((value >> 4) & 0xf); 46 | // printf("pos %x, src %x, length %d\n", bytesWritten, bytesWritten-disp-1, n+3); 47 | for (int j = 0; j < n + 3; ++j) 48 | { 49 | destBuffer[bytesWritten] = destBuffer[bytesWritten-disp-1]; 50 | bytesWritten++; 51 | } 52 | } 53 | } 54 | } 55 | fclose(fhIn); 56 | 57 | printf("decompressed %zu bytes\n", bytesWritten); 58 | 59 | { 60 | FILE* fhOut = fopen(outPath, "wb"); 61 | if (!fhOut) 62 | return 1; 63 | fwrite(destBuffer, 1, decompressedLength, fhOut); 64 | fclose(fhOut); 65 | } 66 | 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /compress.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "io.h" 5 | 6 | int findTokenWithLength(uint8_t* buffer, size_t offset, size_t inputLength, int length) 7 | { 8 | if (offset + length > inputLength) 9 | return 0; 10 | 11 | for (uint8_t* ptr = buffer + offset - 8; ptr >= buffer; ptr--) 12 | { 13 | int delta = (ptr - buffer) - offset; 14 | 15 | int encodedDelta = -delta-1; 16 | if (encodedDelta > 0xFFF) 17 | return 0; 18 | 19 | bool match = true; 20 | for (int i = 0; i < length && match; ++i) 21 | { 22 | if (ptr[i] != buffer[offset+i]) 23 | match = false; 24 | } 25 | if (match) 26 | return delta; 27 | } 28 | 29 | return 0; 30 | } 31 | 32 | int main(int argc, char** argv) 33 | { 34 | if (argc <= 2) 35 | return 1; 36 | 37 | const char* inPath = argv[1]; 38 | const char* outPath = argv[2]; 39 | 40 | FILE* fhIn = fopen(inPath, "rb"); 41 | if (!fhIn) 42 | return 1; 43 | 44 | size_t inputLength = io::length(fhIn); 45 | 46 | uint8_t* srcBuffer = new uint8_t[inputLength]; 47 | fread(srcBuffer, 1, inputLength, fhIn); 48 | 49 | uint8_t* dstBuffer = new uint8_t[inputLength * 2]; 50 | 51 | size_t inputBytesProcessed = 0; 52 | size_t bytesWritten = 0; 53 | size_t lastFlagPosition = 0; 54 | size_t lastFlagInputPosition = 0; 55 | int numFlagBits = 0; 56 | uint8_t flags = 0; 57 | 58 | while (inputBytesProcessed < inputLength) 59 | { 60 | if (numFlagBits == 0) 61 | { 62 | dstBuffer[bytesWritten] = 0x37; 63 | lastFlagPosition = bytesWritten; 64 | lastFlagInputPosition = inputBytesProcessed; 65 | bytesWritten++; 66 | } 67 | 68 | if (findTokenWithLength(srcBuffer, inputBytesProcessed, inputLength, 3)) 69 | { 70 | int tokenSize = 0; 71 | int tokenDelta = 0; 72 | for (int attemptTokenSize = 3; attemptTokenSize <= 18; ++attemptTokenSize) 73 | { 74 | int attemptTokenDelta = findTokenWithLength(srcBuffer, inputBytesProcessed, inputLength, attemptTokenSize); 75 | if (attemptTokenDelta != 0) { 76 | tokenSize = attemptTokenSize; 77 | tokenDelta = attemptTokenDelta; 78 | } else { 79 | break; 80 | } 81 | } 82 | 83 | //printf("found token of %d bytes at %08x with offset %d\n", tokenSize, inputBytesProcessed, tokenDelta); 84 | 85 | int flippedTokenDelta = -tokenDelta-1; 86 | dstBuffer[bytesWritten] = (((tokenSize-3) & 0xf) << 4) | ((flippedTokenDelta & 0xf00) >> 8); 87 | bytesWritten++; 88 | dstBuffer[bytesWritten] = (flippedTokenDelta & 0xff); 89 | bytesWritten++; 90 | inputBytesProcessed += tokenSize; 91 | flags |= (0x80 >> numFlagBits); 92 | } 93 | else 94 | { 95 | dstBuffer[bytesWritten] = srcBuffer[inputBytesProcessed]; 96 | bytesWritten++; 97 | inputBytesProcessed++; 98 | // don't set a flag 99 | } 100 | numFlagBits++; 101 | if (numFlagBits == 8 || inputBytesProcessed >= inputLength) { 102 | numFlagBits = 0; 103 | dstBuffer[lastFlagPosition] = flags; 104 | flags = 0; 105 | } 106 | } 107 | 108 | printf("compressed %zu bytes to %zu bytes\n", inputLength, bytesWritten); 109 | 110 | while ((bytesWritten % 4) != 0) 111 | ++bytesWritten; 112 | 113 | { 114 | FILE* fhOut = fopen(outPath, "wb"); 115 | if (!fhOut) 116 | return 1; 117 | io::writeU32(fhOut, (inputLength << 8) | 0x10); 118 | fwrite(dstBuffer, 1, bytesWritten, fhOut); 119 | fclose(fhOut); 120 | } 121 | 122 | return 0; 123 | } 124 | --------------------------------------------------------------------------------