summaryrefslogtreecommitdiff
path: root/tools/gbagfx/lz.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/gbagfx/lz.c')
-rw-r--r--tools/gbagfx/lz.c155
1 files changed, 155 insertions, 0 deletions
diff --git a/tools/gbagfx/lz.c b/tools/gbagfx/lz.c
new file mode 100644
index 0000000..c2ba3e3
--- /dev/null
+++ b/tools/gbagfx/lz.c
@@ -0,0 +1,155 @@
+// Copyright (c) 2015 YamaArashi
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include "global.h"
+#include "lz.h"
+
+unsigned char *LZDecompress(unsigned char *src, int srcSize, int *uncompressedSize)
+{
+ if (srcSize < 4)
+ goto fail;
+
+ int destSize = (src[3] << 16) | (src[2] << 8) | src[1];
+
+ unsigned char *dest = malloc(destSize);
+
+ if (dest == NULL)
+ goto fail;
+
+ int srcPos = 4;
+ int destPos = 0;
+
+ for (;;) {
+ if (srcPos >= srcSize)
+ goto fail;
+
+ unsigned char flags = src[srcPos++];
+
+ for (int i = 0; i < 8; i++) {
+ if (flags & 0x80) {
+ if (srcPos + 1 >= srcSize)
+ goto fail;
+
+ int blockSize = (src[srcPos] >> 4) + 3;
+ int blockDistance = (((src[srcPos] & 0xF) << 8) | src[srcPos + 1]) + 1;
+
+ srcPos += 2;
+
+ int blockPos = destPos - blockDistance;
+
+ // Some Ruby/Sapphire tilesets overflow.
+ if (destPos + blockSize > destSize) {
+ blockSize = destSize - destPos;
+ fprintf(stderr, "Destination buffer overflow.\n");
+ }
+
+ if (blockPos < 0)
+ goto fail;
+
+ for (int j = 0; j < blockSize; j++)
+ dest[destPos++] = dest[blockPos + j];
+ } else {
+ if (srcPos >= srcSize || destPos >= destSize)
+ goto fail;
+
+ dest[destPos++] = src[srcPos++];
+ }
+
+ if (destPos == destSize) {
+ *uncompressedSize = destSize;
+ return dest;
+ }
+
+ flags <<= 1;
+ }
+ }
+
+fail:
+ FATAL_ERROR("Fatal error while decompressing LZ file.\n");
+}
+
+unsigned char *LZCompress(unsigned char *src, int srcSize, int *compressedSize)
+{
+ const int minDistance = 2; // for compatibility with LZ77UnCompVram()
+
+ if (srcSize <= 0)
+ goto fail;
+
+ int worstCaseDestSize = 4 + srcSize + ((srcSize + 7) / 8);
+
+ // Round up to the next multiple of four.
+ worstCaseDestSize = (worstCaseDestSize + 3) & ~3;
+
+ unsigned char *dest = malloc(worstCaseDestSize);
+
+ if (dest == NULL)
+ goto fail;
+
+ // header
+ dest[0] = 0x10; // LZ compression type
+ dest[1] = (unsigned char)srcSize;
+ dest[2] = (unsigned char)(srcSize >> 8);
+ dest[3] = (unsigned char)(srcSize >> 16);
+
+ int srcPos = 0;
+ int destPos = 4;
+
+ for (;;) {
+ unsigned char *flags = &dest[destPos++];
+ *flags = 0;
+
+ for (int i = 0; i < 8; i++) {
+ int bestBlockDistance = 0;
+ int bestBlockSize = 0;
+ int blockDistance = minDistance;
+
+ while (blockDistance <= srcPos && blockDistance <= 0x1000) {
+ int blockStart = srcPos - blockDistance;
+ int blockSize = 0;
+
+ while (blockSize < 18
+ && srcPos + blockSize < srcSize
+ && src[blockStart + blockSize] == src[srcPos + blockSize])
+ blockSize++;
+
+ if (blockSize > bestBlockSize) {
+ bestBlockDistance = blockDistance;
+ bestBlockSize = blockSize;
+
+ if (blockSize == 18)
+ break;
+ }
+
+ blockDistance++;
+ }
+
+ if (bestBlockSize >= 3) {
+ *flags |= (0x80 >> i);
+ srcPos += bestBlockSize;
+ bestBlockSize -= 3;
+ bestBlockDistance--;
+ dest[destPos++] = (bestBlockSize << 4) | ((unsigned int)bestBlockDistance >> 8);
+ dest[destPos++] = (unsigned char)bestBlockDistance;
+ } else {
+ dest[destPos++] = src[srcPos++];
+ }
+
+ if (srcPos == srcSize) {
+ // Pad to multiple of 4 bytes.
+ int remainder = destPos % 4;
+
+ if (remainder != 0) {
+ for (int i = 0; i < 4 - remainder; i++)
+ dest[destPos++] = 0;
+ }
+
+ *compressedSize = destPos;
+ return dest;
+ }
+ }
+ }
+
+fail:
+ FATAL_ERROR("Fatal error while compressing LZ file.\n");
+}