summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPikalaxALT <PikalaxALT@gmail.com>2020-04-15 20:20:40 -0400
committerPikalaxALT <PikalaxALT@gmail.com>2020-04-15 20:20:40 -0400
commit09d866602ecc76fdc5e9be9e3bd1ae0b8547ff13 (patch)
tree312c771cf265503718f3e8c2eb42b92708c55f36
parentb661c7888c57280a19801b320cf7860e6555f1d3 (diff)
ROM titles
-rw-r--r--Makefile15
-rw-r--r--asm/icon.s6
-rw-r--r--asm/title.s16
-rw-r--r--asm/unk_339000.s2
-rw-r--r--data/title/title.txtbin0 -> 50 bytes
-rw-r--r--graphics/icon.pngbin0 -> 377 bytes
-rw-r--r--ld_script.txt14
-rw-r--r--tools/nitrogfx/.gitignore1
-rw-r--r--tools/nitrogfx/LICENSE19
-rw-r--r--tools/nitrogfx/Makefile21
-rw-r--r--tools/nitrogfx/convert_png.c254
-rw-r--r--tools/nitrogfx/convert_png.h12
-rw-r--r--tools/nitrogfx/font.c326
-rw-r--r--tools/nitrogfx/font.h16
-rw-r--r--tools/nitrogfx/gfx.c344
-rw-r--r--tools/nitrogfx/gfx.h36
-rw-r--r--tools/nitrogfx/global.h31
-rw-r--r--tools/nitrogfx/huff.c398
-rw-r--r--tools/nitrogfx/huff.h38
-rw-r--r--tools/nitrogfx/jasc_pal.c172
-rw-r--r--tools/nitrogfx/jasc_pal.h9
-rw-r--r--tools/nitrogfx/lz.c153
-rw-r--r--tools/nitrogfx/lz.h9
-rw-r--r--tools/nitrogfx/main.c537
-rw-r--r--tools/nitrogfx/options.h24
-rw-r--r--tools/nitrogfx/rl.c149
-rw-r--r--tools/nitrogfx/rl.h9
-rw-r--r--tools/nitrogfx/util.c124
-rw-r--r--tools/nitrogfx/util.h14
29 files changed, 2747 insertions, 2 deletions
diff --git a/Makefile b/Makefile
index 577c034b..60335286 100644
--- a/Makefile
+++ b/Makefile
@@ -55,6 +55,7 @@ CFLAGS = -O4,p -proc arm946e -thumb -fp soft -lang c -Cpp_exceptions off
TOOLS_DIR = tools
SHA1SUM = sha1sum
JSONPROC = $(TOOLS_DIR)/jsonproc/jsonproc
+GFX = $(TOOLS_DIR)/nitrogfx/nitrogfx
TOOLDIRS = $(filter-out $(TOOLS_DIR)/mwccarm,$(wildcard $(TOOLS_DIR)/*))
TOOLBASE = $(TOOLDIRS:$(TOOLS_DIR)/%=%)
@@ -107,6 +108,20 @@ $(ROM): $(ELF)
# Make sure build directory exists before compiling anything
DUMMY != mkdir -p $(ALL_DIRS)
+%.4bpp: %.png
+ $(GFX) $< $@
+
+%.gbapal: %.png
+ $(GFX) $< $@
+
+%.gbapal: %.pal
+ $(GFX) $< $@
+
+%.lz: %
+ $(GFX) $< $@
+
+$(BUILD_DIR)/asm/icon.o: graphics/icon.4bpp graphics/icon.gbapal
+
### Debug Print ###
print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) @true
diff --git a/asm/icon.s b/asm/icon.s
index fb77c2c7..58be77f1 100644
--- a/asm/icon.s
+++ b/asm/icon.s
@@ -1,2 +1,6 @@
.text
- .incbin "baserom.nds", 0x338600, 0x371d8c0
+ .2byte 0x0001
+ .2byte 0x048B
+ .space 0x1C
+ .incbin "graphics/icon.4bpp"
+ .incbin "graphics/icon.gbapal"
diff --git a/asm/title.s b/asm/title.s
new file mode 100644
index 00000000..7410b8b2
--- /dev/null
+++ b/asm/title.s
@@ -0,0 +1,16 @@
+ .text
+ .global ROMTitles
+ROMTitles:
+ @ UTF16LE has a leading short that should be skipped
+ .incbin "data/title/title.txt", 2
+ .space 0x100-(.-ROMTitles)
+ .incbin "data/title/title.txt", 2
+ .space 0x200-(.-ROMTitles)
+ .incbin "data/title/title.txt", 2
+ .space 0x300-(.-ROMTitles)
+ .incbin "data/title/title.txt", 2
+ .space 0x400-(.-ROMTitles)
+ .incbin "data/title/title.txt", 2
+ .space 0x500-(.-ROMTitles)
+ .incbin "data/title/title.txt", 2
+ .space 0x600-(.-ROMTitles)
diff --git a/asm/unk_339000.s b/asm/unk_339000.s
new file mode 100644
index 00000000..b517c6d2
--- /dev/null
+++ b/asm/unk_339000.s
@@ -0,0 +1,2 @@
+ .text
+ .incbin "baserom.nds", 0x339000, 0x371CEC0
diff --git a/data/title/title.txt b/data/title/title.txt
new file mode 100644
index 00000000..2d687892
--- /dev/null
+++ b/data/title/title.txt
Binary files differ
diff --git a/graphics/icon.png b/graphics/icon.png
new file mode 100644
index 00000000..ecf9a4dc
--- /dev/null
+++ b/graphics/icon.png
Binary files differ
diff --git a/ld_script.txt b/ld_script.txt
index e5b0cd10..7846a772 100644
--- a/ld_script.txt
+++ b/ld_script.txt
@@ -76,10 +76,22 @@ SECTIONS {
END_SEG(FileAllocationTable)
. = 0x338600;__romPos = .;
- BEGIN_SEG(Icon, 0)
+ BEGIN_SEG(Icon, __romPos)
{
build/asm/icon.o(.text);
+ build/asm/title.o(.text);
}
END_SEG(Icon)
+ . = 0x339000;__romPos = .;
+ BEGIN_SEG(Unk339000, 0)
+ {
+ build/asm/unk_339000.o(.text);
+ }
+ END_SEG(Unk339000)
+
+ /DISCARD/ :
+ {
+ *(*);
+ }
}
diff --git a/tools/nitrogfx/.gitignore b/tools/nitrogfx/.gitignore
new file mode 100644
index 00000000..4379fab8
--- /dev/null
+++ b/tools/nitrogfx/.gitignore
@@ -0,0 +1 @@
+nitrogfx
diff --git a/tools/nitrogfx/LICENSE b/tools/nitrogfx/LICENSE
new file mode 100644
index 00000000..b66bf81c
--- /dev/null
+++ b/tools/nitrogfx/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 YamaArashi
+
+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.
diff --git a/tools/nitrogfx/Makefile b/tools/nitrogfx/Makefile
new file mode 100644
index 00000000..5b75d652
--- /dev/null
+++ b/tools/nitrogfx/Makefile
@@ -0,0 +1,21 @@
+CC = gcc
+
+CFLAGS = -Wall -Wextra -Werror -Wno-sign-compare -std=c11 -O2 -DPNG_SKIP_SETJMP_CHECK
+
+LIBS = -lpng -lz
+
+SRCS = main.c convert_png.c gfx.c jasc_pal.c lz.c rl.c util.c font.c huff.c
+
+.PHONY: all clean
+
+all: nitrogfx
+ @:
+
+nitrogfx-debug: $(SRCS) convert_png.h gfx.h global.h jasc_pal.h lz.h rl.h util.h font.h
+ $(CC) $(CFLAGS) -DDEBUG $(SRCS) -o $@ $(LDFLAGS) $(LIBS)
+
+nitrogfx: $(SRCS) convert_png.h gfx.h global.h jasc_pal.h lz.h rl.h util.h font.h
+ $(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) $(LIBS)
+
+clean:
+ $(RM) nitrogfx nitrogfx.exe
diff --git a/tools/nitrogfx/convert_png.c b/tools/nitrogfx/convert_png.c
new file mode 100644
index 00000000..cdfa39a7
--- /dev/null
+++ b/tools/nitrogfx/convert_png.c
@@ -0,0 +1,254 @@
+// Copyright (c) 2015 YamaArashi
+
+#include <stdio.h>
+#include <setjmp.h>
+#include <png.h>
+#include "global.h"
+#include "convert_png.h"
+#include "gfx.h"
+
+static FILE *PngReadOpen(char *path, png_structp *pngStruct, png_infop *pngInfo)
+{
+ FILE *fp = fopen(path, "rb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
+
+ unsigned char sig[8];
+
+ if (fread(sig, 8, 1, fp) != 1)
+ FATAL_ERROR("Failed to read PNG signature from \"%s\".\n", path);
+
+ if (png_sig_cmp(sig, 0, 8))
+ FATAL_ERROR("\"%s\" does not have a valid PNG signature.\n", path);
+
+ png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+ if (!png_ptr)
+ FATAL_ERROR("Failed to create PNG read struct.\n");
+
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+
+ if (!info_ptr)
+ FATAL_ERROR("Failed to create PNG info struct.\n");
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ FATAL_ERROR("Failed to init I/O for reading \"%s\".\n", path);
+
+ png_init_io(png_ptr, fp);
+ png_set_sig_bytes(png_ptr, 8);
+ png_read_info(png_ptr, info_ptr);
+
+ *pngStruct = png_ptr;
+ *pngInfo = info_ptr;
+
+ return fp;
+}
+
+static unsigned char *ConvertBitDepth(unsigned char *src, int srcBitDepth, int destBitDepth, int numPixels)
+{
+ // Round the number of bits up to the next 8 and divide by 8 to get the number of bytes.
+ int srcSize = ((numPixels * srcBitDepth + 7) & ~7) / 8;
+ int destSize = ((numPixels * destBitDepth + 7) & ~7) / 8;
+ unsigned char *output = calloc(destSize, 1);
+ unsigned char *dest = output;
+ int i;
+ int j;
+ int destBit = 8 - destBitDepth;
+
+ for (i = 0; i < srcSize; i++)
+ {
+ unsigned char srcByte = src[i];
+
+ for (j = 8 - srcBitDepth; j >= 0; j -= srcBitDepth)
+ {
+ unsigned char pixel = (srcByte >> j) % (1 << srcBitDepth);
+
+ if (pixel >= (1 << destBitDepth))
+ FATAL_ERROR("Image exceeds the maximum color value for a %ibpp image.\n", destBitDepth);
+ *dest |= pixel << destBit;
+ destBit -= destBitDepth;
+ if (destBit < 0)
+ {
+ dest++;
+ destBit = 8 - destBitDepth;
+ }
+ }
+ }
+
+ return output;
+}
+
+void ReadPng(char *path, struct Image *image)
+{
+ png_structp png_ptr;
+ png_infop info_ptr;
+
+ FILE *fp = PngReadOpen(path, &png_ptr, &info_ptr);
+
+ int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
+
+ int color_type = png_get_color_type(png_ptr, info_ptr);
+
+ if (color_type != PNG_COLOR_TYPE_GRAY && color_type != PNG_COLOR_TYPE_PALETTE)
+ FATAL_ERROR("\"%s\" has an unsupported color type.\n", path);
+
+ // Check if the image has a palette so that we can tell if the colors need to be inverted later.
+ // Don't read the palette because it's not needed for now.
+ image->hasPalette = (color_type == PNG_COLOR_TYPE_PALETTE);
+
+ image->width = png_get_image_width(png_ptr, info_ptr);
+ image->height = png_get_image_height(png_ptr, info_ptr);
+
+ int rowbytes = png_get_rowbytes(png_ptr, info_ptr);
+
+ image->pixels = malloc(image->height * rowbytes);
+
+ if (image->pixels == NULL)
+ FATAL_ERROR("Failed to allocate pixel buffer.\n");
+
+ png_bytepp row_pointers = malloc(image->height * sizeof(png_bytep));
+
+ if (row_pointers == NULL)
+ FATAL_ERROR("Failed to allocate row pointers.\n");
+
+ for (int i = 0; i < image->height; i++)
+ row_pointers[i] = (png_bytep)(image->pixels + (i * rowbytes));
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ FATAL_ERROR("Error reading from \"%s\".\n", path);
+
+ png_read_image(png_ptr, row_pointers);
+
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+
+ free(row_pointers);
+ fclose(fp);
+
+ if (bit_depth != image->bitDepth)
+ {
+ unsigned char *src = image->pixels;
+
+ if (bit_depth != 1 && bit_depth != 2 && bit_depth != 4 && bit_depth != 8)
+ FATAL_ERROR("Bit depth of image must be 1, 2, 4, or 8.\n");
+ image->pixels = ConvertBitDepth(image->pixels, bit_depth, image->bitDepth, image->width * image->height);
+ free(src);
+ image->bitDepth = bit_depth;
+ }
+}
+
+void ReadPngPalette(char *path, struct Palette *palette)
+{
+ png_structp png_ptr;
+ png_infop info_ptr;
+ png_colorp colors;
+ int numColors;
+
+ FILE *fp = PngReadOpen(path, &png_ptr, &info_ptr);
+
+ if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE)
+ FATAL_ERROR("The image \"%s\" does not contain a palette.\n", path);
+
+ if (png_get_PLTE(png_ptr, info_ptr, &colors, &numColors) != PNG_INFO_PLTE)
+ FATAL_ERROR("Failed to retrieve palette from \"%s\".\n", path);
+
+ if (numColors > 256)
+ FATAL_ERROR("Images with more than 256 colors are not supported.\n");
+
+ palette->numColors = numColors;
+ for (int i = 0; i < numColors; i++) {
+ palette->colors[i].red = colors[i].red;
+ palette->colors[i].green = colors[i].green;
+ palette->colors[i].blue = colors[i].blue;
+ }
+
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+
+ fclose(fp);
+}
+
+void SetPngPalette(png_structp png_ptr, png_infop info_ptr, struct Palette *palette)
+{
+ png_colorp colors = malloc(palette->numColors * sizeof(png_color));
+
+ if (colors == NULL)
+ FATAL_ERROR("Failed to allocate PNG palette.\n");
+
+ for (int i = 0; i < palette->numColors; i++) {
+ colors[i].red = palette->colors[i].red;
+ colors[i].green = palette->colors[i].green;
+ colors[i].blue = palette->colors[i].blue;
+ }
+
+ png_set_PLTE(png_ptr, info_ptr, colors, palette->numColors);
+
+ free(colors);
+}
+
+void WritePng(char *path, struct Image *image)
+{
+ FILE *fp = fopen(path, "wb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
+
+ png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+ if (!png_ptr)
+ FATAL_ERROR("Failed to create PNG write struct.\n");
+
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+
+ if (!info_ptr)
+ FATAL_ERROR("Failed to create PNG info struct.\n");
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ FATAL_ERROR("Failed to init I/O for writing \"%s\".\n", path);
+
+ png_init_io(png_ptr, fp);
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ FATAL_ERROR("Error writing header for \"%s\".\n", path);
+
+ int color_type = image->hasPalette ? PNG_COLOR_TYPE_PALETTE : PNG_COLOR_TYPE_GRAY;
+
+ png_set_IHDR(png_ptr, info_ptr, image->width, image->height,
+ image->bitDepth, color_type, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+ if (image->hasPalette) {
+ SetPngPalette(png_ptr, info_ptr, &image->palette);
+
+ if (image->hasTransparency) {
+ png_byte trans = 0;
+ png_set_tRNS(png_ptr, info_ptr, &trans, 1, 0);
+ }
+ }
+
+ png_write_info(png_ptr, info_ptr);
+
+ png_bytepp row_pointers = malloc(image->height * sizeof(png_bytep));
+
+ if (row_pointers == NULL)
+ FATAL_ERROR("Failed to allocate row pointers.\n");
+
+ int rowbytes = png_get_rowbytes(png_ptr, info_ptr);
+
+ for (int i = 0; i < image->height; i++)
+ row_pointers[i] = (png_bytep)(image->pixels + (i * rowbytes));
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ FATAL_ERROR("Error writing \"%s\".\n", path);
+
+ png_write_image(png_ptr, row_pointers);
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ FATAL_ERROR("Error ending write of \"%s\".\n", path);
+
+ png_write_end(png_ptr, NULL);
+
+ fclose(fp);
+
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ free(row_pointers);
+}
diff --git a/tools/nitrogfx/convert_png.h b/tools/nitrogfx/convert_png.h
new file mode 100644
index 00000000..caf081b7
--- /dev/null
+++ b/tools/nitrogfx/convert_png.h
@@ -0,0 +1,12 @@
+// Copyright (c) 2015 YamaArashi
+
+#ifndef CONVERT_PNG_H
+#define CONVERT_PNG_H
+
+#include "gfx.h"
+
+void ReadPng(char *path, struct Image *image);
+void WritePng(char *path, struct Image *image);
+void ReadPngPalette(char *path, struct Palette *palette);
+
+#endif // CONVERT_PNG_H
diff --git a/tools/nitrogfx/font.c b/tools/nitrogfx/font.c
new file mode 100644
index 00000000..0dd6fbc3
--- /dev/null
+++ b/tools/nitrogfx/font.c
@@ -0,0 +1,326 @@
+// Copyright (c) 2015 YamaArashi
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "global.h"
+#include "font.h"
+#include "gfx.h"
+#include "util.h"
+
+unsigned char gFontPalette[][3] = {
+ {0x90, 0xC8, 0xFF}, // bg (saturated blue that contrasts well with the shadow color)
+ {0x38, 0x38, 0x38}, // fg (dark grey)
+ {0xD8, 0xD8, 0xD8}, // shadow (light grey)
+ {0xFF, 0xFF, 0xFF} // box (white)
+};
+
+static void ConvertFromLatinFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
+{
+ unsigned int srcPixelsOffset = 0;
+
+ for (unsigned int row = 0; row < numRows; row++) {
+ for (unsigned int column = 0; column < 16; column++) {
+ for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
+ unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
+
+ for (unsigned int i = 0; i < 8; i++) {
+ unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
+ unsigned int destPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
+
+ dest[destPixelsOffset] = src[srcPixelsOffset + 1];
+ dest[destPixelsOffset + 1] = src[srcPixelsOffset];
+
+ srcPixelsOffset += 2;
+ }
+ }
+ }
+ }
+}
+
+static void ConvertToLatinFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
+{
+ unsigned int destPixelsOffset = 0;
+
+ for (unsigned int row = 0; row < numRows; row++) {
+ for (unsigned int column = 0; column < 16; column++) {
+ for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
+ unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
+
+ for (unsigned int i = 0; i < 8; i++) {
+ unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
+ unsigned int srcPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
+
+ dest[destPixelsOffset] = src[srcPixelsOffset + 1];
+ dest[destPixelsOffset + 1] = src[srcPixelsOffset];
+
+ destPixelsOffset += 2;
+ }
+ }
+ }
+ }
+}
+
+static void ConvertFromHalfwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
+{
+ for (unsigned int row = 0; row < numRows; row++) {
+ for (unsigned int column = 0; column < 16; column++) {
+ unsigned int glyphIndex = (row * 16) + column;
+
+ for (unsigned int glyphTile = 0; glyphTile < 2; glyphTile++) {
+ unsigned int pixelsX = column * 8;
+ unsigned int srcPixelsOffset = 512 * (glyphIndex >> 4) + 16 * (glyphIndex & 0xF) + 256 * glyphTile;
+
+ for (unsigned int i = 0; i < 8; i++) {
+ unsigned int pixelsY = (row * 16) + (glyphTile * 8) + i;
+ unsigned int destPixelsOffset = (pixelsY * 32) + (pixelsX / 4);
+
+ dest[destPixelsOffset] = src[srcPixelsOffset + 1];
+ dest[destPixelsOffset + 1] = src[srcPixelsOffset];
+
+ srcPixelsOffset += 2;
+ }
+ }
+ }
+ }
+}
+
+static void ConvertToHalfwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
+{
+ for (unsigned int row = 0; row < numRows; row++) {
+ for (unsigned int column = 0; column < 16; column++) {
+ unsigned int glyphIndex = (row * 16) + column;
+
+ for (unsigned int glyphTile = 0; glyphTile < 2; glyphTile++) {
+ unsigned int pixelsX = column * 8;
+ unsigned int destPixelsOffset = 512 * (glyphIndex >> 4) + 16 * (glyphIndex & 0xF) + 256 * glyphTile;
+
+ for (unsigned int i = 0; i < 8; i++) {
+ unsigned int pixelsY = (row * 16) + (glyphTile * 8) + i;
+ unsigned int srcPixelsOffset = (pixelsY * 32) + (pixelsX / 4);
+
+ dest[destPixelsOffset] = src[srcPixelsOffset + 1];
+ dest[destPixelsOffset + 1] = src[srcPixelsOffset];
+
+ destPixelsOffset += 2;
+ }
+ }
+ }
+ }
+}
+
+static void ConvertFromFullwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
+{
+ for (unsigned int row = 0; row < numRows; row++) {
+ for (unsigned int column = 0; column < 16; column++) {
+ unsigned int glyphIndex = (row * 16) + column;
+
+ for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
+ unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
+ unsigned int srcPixelsOffset = 512 * (glyphIndex >> 3) + 32 * (glyphIndex & 7) + 256 * (glyphTile >> 1) + 16 * (glyphTile & 1);
+
+ for (unsigned int i = 0; i < 8; i++) {
+ unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
+ unsigned int destPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
+
+ dest[destPixelsOffset] = src[srcPixelsOffset + 1];
+ dest[destPixelsOffset + 1] = src[srcPixelsOffset];
+
+ srcPixelsOffset += 2;
+ }
+ }
+ }
+ }
+}
+
+static void ConvertToFullwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
+{
+ for (unsigned int row = 0; row < numRows; row++) {
+ for (unsigned int column = 0; column < 16; column++) {
+ unsigned int glyphIndex = (row * 16) + column;
+
+ for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
+ unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
+ unsigned int destPixelsOffset = 512 * (glyphIndex >> 3) + 32 * (glyphIndex & 7) + 256 * (glyphTile >> 1) + 16 * (glyphTile & 1);
+
+ for (unsigned int i = 0; i < 8; i++) {
+ unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
+ unsigned int srcPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
+
+ dest[destPixelsOffset] = src[srcPixelsOffset + 1];
+ dest[destPixelsOffset + 1] = src[srcPixelsOffset];
+
+ destPixelsOffset += 2;
+ }
+ }
+ }
+ }
+}
+
+static void SetFontPalette(struct Image *image)
+{
+ image->hasPalette = true;
+
+ image->palette.numColors = 4;
+
+ for (int i = 0; i < image->palette.numColors; i++) {
+ image->palette.colors[i].red = gFontPalette[i][0];
+ image->palette.colors[i].green = gFontPalette[i][1];
+ image->palette.colors[i].blue = gFontPalette[i][2];
+ }
+
+ image->hasTransparency = false;
+}
+
+void ReadLatinFont(char *path, struct Image *image)
+{
+ int fileSize;
+ unsigned char *buffer = ReadWholeFile(path, &fileSize);
+
+ int numGlyphs = fileSize / 64;
+
+ if (numGlyphs % 16 != 0)
+ FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs);
+
+ int numRows = numGlyphs / 16;
+
+ image->width = 256;
+ image->height = numRows * 16;
+ image->bitDepth = 2;
+ image->pixels = malloc(fileSize);
+
+ if (image->pixels == NULL)
+ FATAL_ERROR("Failed to allocate memory for font.\n");
+
+ ConvertFromLatinFont(buffer, image->pixels, numRows);
+
+ free(buffer);
+
+ SetFontPalette(image);
+}
+
+void WriteLatinFont(char *path, struct Image *image)
+{
+ if (image->width != 256)
+ FATAL_ERROR("The width of the font image (%d) is not 256.\n", image->width);
+
+ if (image->height % 16 != 0)
+ FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height);
+
+ int numRows = image->height / 16;
+ int bufferSize = numRows * 16 * 64;
+ unsigned char *buffer = malloc(bufferSize);
+
+ if (buffer == NULL)
+ FATAL_ERROR("Failed to allocate memory for font.\n");
+
+ ConvertToLatinFont(image->pixels, buffer, numRows);
+
+ WriteWholeFile(path, buffer, bufferSize);
+
+ free(buffer);
+}
+
+void ReadHalfwidthJapaneseFont(char *path, struct Image *image)
+{
+ int fileSize;
+ unsigned char *buffer = ReadWholeFile(path, &fileSize);
+
+ int glyphSize = 32;
+
+ if (fileSize % glyphSize != 0)
+ FATAL_ERROR("The file size (%d) is not a multiple of %d.\n", fileSize, glyphSize);
+
+ int numGlyphs = fileSize / glyphSize;
+
+ if (numGlyphs % 16 != 0)
+ FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs);
+
+ int numRows = numGlyphs / 16;
+
+ image->width = 128;
+ image->height = numRows * 16;
+ image->bitDepth = 2;
+ image->pixels = malloc(fileSize);
+
+ if (image->pixels == NULL)
+ FATAL_ERROR("Failed to allocate memory for font.\n");
+
+ ConvertFromHalfwidthJapaneseFont(buffer, image->pixels, numRows);
+
+ free(buffer);
+
+ SetFontPalette(image);
+}
+
+void WriteHalfwidthJapaneseFont(char *path, struct Image *image)
+{
+ if (image->width != 128)
+ FATAL_ERROR("The width of the font image (%d) is not 128.\n", image->width);
+
+ if (image->height % 16 != 0)
+ FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height);
+
+ int numRows = image->height / 16;
+ int bufferSize = numRows * 16 * 32;
+ unsigned char *buffer = malloc(bufferSize);
+
+ if (buffer == NULL)
+ FATAL_ERROR("Failed to allocate memory for font.\n");
+
+ ConvertToHalfwidthJapaneseFont(image->pixels, buffer, numRows);
+
+ WriteWholeFile(path, buffer, bufferSize);
+
+ free(buffer);
+}
+
+void ReadFullwidthJapaneseFont(char *path, struct Image *image)
+{
+ int fileSize;
+ unsigned char *buffer = ReadWholeFile(path, &fileSize);
+
+ int numGlyphs = fileSize / 64;
+
+ if (numGlyphs % 16 != 0)
+ FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs);
+
+ int numRows = numGlyphs / 16;
+
+ image->width = 256;
+ image->height = numRows * 16;
+ image->bitDepth = 2;
+ image->pixels = malloc(fileSize);
+
+ if (image->pixels == NULL)
+ FATAL_ERROR("Failed to allocate memory for font.\n");
+
+ ConvertFromFullwidthJapaneseFont(buffer, image->pixels, numRows);
+
+ free(buffer);
+
+ SetFontPalette(image);
+}
+
+void WriteFullwidthJapaneseFont(char *path, struct Image *image)
+{
+ if (image->width != 256)
+ FATAL_ERROR("The width of the font image (%d) is not 256.\n", image->width);
+
+ if (image->height % 16 != 0)
+ FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height);
+
+ int numRows = image->height / 16;
+ int bufferSize = numRows * 16 * 64;
+ unsigned char *buffer = malloc(bufferSize);
+
+ if (buffer == NULL)
+ FATAL_ERROR("Failed to allocate memory for font.\n");
+
+ ConvertToFullwidthJapaneseFont(image->pixels, buffer, numRows);
+
+ WriteWholeFile(path, buffer, bufferSize);
+
+ free(buffer);
+}
diff --git a/tools/nitrogfx/font.h b/tools/nitrogfx/font.h
new file mode 100644
index 00000000..45086d02
--- /dev/null
+++ b/tools/nitrogfx/font.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2015 YamaArashi
+
+#ifndef FONT_H
+#define FONT_H
+
+#include <stdbool.h>
+#include "gfx.h"
+
+void ReadLatinFont(char *path, struct Image *image);
+void WriteLatinFont(char *path, struct Image *image);
+void ReadHalfwidthJapaneseFont(char *path, struct Image *image);
+void WriteHalfwidthJapaneseFont(char *path, struct Image *image);
+void ReadFullwidthJapaneseFont(char *path, struct Image *image);
+void WriteFullwidthJapaneseFont(char *path, struct Image *image);
+
+#endif // FONT_H
diff --git a/tools/nitrogfx/gfx.c b/tools/nitrogfx/gfx.c
new file mode 100644
index 00000000..f927deed
--- /dev/null
+++ b/tools/nitrogfx/gfx.c
@@ -0,0 +1,344 @@
+// Copyright (c) 2015 YamaArashi
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "global.h"
+#include "gfx.h"
+#include "util.h"
+
+#define GET_GBA_PAL_RED(x) (((x) >> 0) & 0x1F)
+#define GET_GBA_PAL_GREEN(x) (((x) >> 5) & 0x1F)
+#define GET_GBA_PAL_BLUE(x) (((x) >> 10) & 0x1F)
+
+#define SET_GBA_PAL(r, g, b) (((b) << 10) | ((g) << 5) | (r))
+
+#define UPCONVERT_BIT_DEPTH(x) (((x) * 255) / 31)
+
+#define DOWNCONVERT_BIT_DEPTH(x) ((x) / 8)
+
+static void AdvanceMetatilePosition(int *subTileX, int *subTileY, int *metatileX, int *metatileY, int metatilesWide, int metatileWidth, int metatileHeight)
+{
+ (*subTileX)++;
+ if (*subTileX == metatileWidth) {
+ *subTileX = 0;
+ (*subTileY)++;
+ if (*subTileY == metatileHeight) {
+ *subTileY = 0;
+ (*metatileX)++;
+ if (*metatileX == metatilesWide) {
+ *metatileX = 0;
+ (*metatileY)++;
+ }
+ }
+ }
+}
+
+static void ConvertFromTiles1Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
+{
+ int subTileX = 0;
+ int subTileY = 0;
+ int metatileX = 0;
+ int metatileY = 0;
+ int pitch = metatilesWide * metatileWidth;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int destY = (metatileY * metatileHeight + subTileY) * 8 + j;
+ int destX = metatileX * metatileWidth + subTileX;
+ unsigned char srcPixelOctet = *src++;
+ unsigned char *destPixelOctet = &dest[destY * pitch + destX];
+
+ for (int k = 0; k < 8; k++) {
+ *destPixelOctet <<= 1;
+ *destPixelOctet |= (srcPixelOctet & 1) ^ invertColors;
+ srcPixelOctet >>= 1;
+ }
+ }
+
+ AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
+ }
+}
+
+static void ConvertFromTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
+{
+ int subTileX = 0;
+ int subTileY = 0;
+ int metatileX = 0;
+ int metatileY = 0;
+ int pitch = (metatilesWide * metatileWidth) * 4;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int destY = (metatileY * metatileHeight + subTileY) * 8 + j;
+
+ for (int k = 0; k < 4; k++) {
+ int destX = (metatileX * metatileWidth + subTileX) * 4 + k;
+ unsigned char srcPixelPair = *src++;
+ unsigned char leftPixel = srcPixelPair & 0xF;
+ unsigned char rightPixel = srcPixelPair >> 4;
+
+ if (invertColors) {
+ leftPixel = 15 - leftPixel;
+ rightPixel = 15 - rightPixel;
+ }
+
+ dest[destY * pitch + destX] = (leftPixel << 4) | rightPixel;
+ }
+ }
+
+ AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
+ }
+}
+
+static void ConvertFromTiles8Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
+{
+ int subTileX = 0;
+ int subTileY = 0;
+ int metatileX = 0;
+ int metatileY = 0;
+ int pitch = (metatilesWide * metatileWidth) * 8;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int destY = (metatileY * metatileHeight + subTileY) * 8 + j;
+
+ for (int k = 0; k < 8; k++) {
+ int destX = (metatileX * metatileWidth + subTileX) * 8 + k;
+ unsigned char srcPixel = *src++;
+
+ if (invertColors)
+ srcPixel = 255 - srcPixel;
+
+ dest[destY * pitch + destX] = srcPixel;
+ }
+ }
+
+ AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
+ }
+}
+
+static void ConvertToTiles1Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
+{
+ int subTileX = 0;
+ int subTileY = 0;
+ int metatileX = 0;
+ int metatileY = 0;
+ int pitch = metatilesWide * metatileWidth;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int srcY = (metatileY * metatileHeight + subTileY) * 8 + j;
+ int srcX = metatileX * metatileWidth + subTileX;
+ unsigned char srcPixelOctet = src[srcY * pitch + srcX];
+ unsigned char *destPixelOctet = dest++;
+
+ for (int k = 0; k < 8; k++) {
+ *destPixelOctet <<= 1;
+ *destPixelOctet |= (srcPixelOctet & 1) ^ invertColors;
+ srcPixelOctet >>= 1;
+ }
+ }
+
+ AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
+ }
+}
+
+static void ConvertToTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
+{
+ int subTileX = 0;
+ int subTileY = 0;
+ int metatileX = 0;
+ int metatileY = 0;
+ int pitch = (metatilesWide * metatileWidth) * 4;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int srcY = (metatileY * metatileHeight + subTileY) * 8 + j;
+
+ for (int k = 0; k < 4; k++) {
+ int srcX = (metatileX * metatileWidth + subTileX) * 4 + k;
+ unsigned char srcPixelPair = src[srcY * pitch + srcX];
+ unsigned char leftPixel = srcPixelPair >> 4;
+ unsigned char rightPixel = srcPixelPair & 0xF;
+
+ if (invertColors) {
+ leftPixel = 15 - leftPixel;
+ rightPixel = 15 - rightPixel;
+ }
+
+ *dest++ = (rightPixel << 4) | leftPixel;
+ }
+ }
+
+ AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
+ }
+}
+
+static void ConvertToTiles8Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
+{
+ int subTileX = 0;
+ int subTileY = 0;
+ int metatileX = 0;
+ int metatileY = 0;
+ int pitch = (metatilesWide * metatileWidth) * 8;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int srcY = (metatileY * metatileHeight + subTileY) * 8 + j;
+
+ for (int k = 0; k < 8; k++) {
+ int srcX = (metatileX * metatileWidth + subTileX) * 8 + k;
+ unsigned char srcPixel = src[srcY * pitch + srcX];
+
+ if (invertColors)
+ srcPixel = 255 - srcPixel;
+
+ *dest++ = srcPixel;
+ }
+ }
+
+ AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
+ }
+}
+
+void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors)
+{
+ int tileSize = bitDepth * 8;
+
+ int fileSize;
+ unsigned char *buffer = ReadWholeFile(path, &fileSize);
+
+ int numTiles = fileSize / tileSize;
+
+ int tilesHeight = (numTiles + tilesWidth - 1) / tilesWidth;
+
+ if (tilesWidth % metatileWidth != 0)
+ FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)", tilesWidth, metatileWidth);
+
+ if (tilesHeight % metatileHeight != 0)
+ FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)", tilesHeight, metatileHeight);
+
+ image->width = tilesWidth * 8;
+ image->height = tilesHeight * 8;
+ image->bitDepth = bitDepth;
+ image->pixels = calloc(tilesWidth * tilesHeight, tileSize);
+
+ if (image->pixels == NULL)
+ FATAL_ERROR("Failed to allocate memory for pixels.\n");
+
+ int metatilesWide = tilesWidth / metatileWidth;
+
+ switch (bitDepth) {
+ case 1:
+ ConvertFromTiles1Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
+ break;
+ case 4:
+ ConvertFromTiles4Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
+ break;
+ case 8:
+ ConvertFromTiles8Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
+ break;
+ }
+
+ free(buffer);
+}
+
+void WriteImage(char *path, int numTiles, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors)
+{
+ int tileSize = bitDepth * 8;
+
+ if (image->width % 8 != 0)
+ FATAL_ERROR("The width in pixels (%d) isn't a multiple of 8.\n", image->width);
+
+ if (image->height % 8 != 0)
+ FATAL_ERROR("The height in pixels (%d) isn't a multiple of 8.\n", image->height);
+
+ int tilesWidth = image->width / 8;
+ int tilesHeight = image->height / 8;
+
+ if (tilesWidth % metatileWidth != 0)
+ FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)", tilesWidth, metatileWidth);
+
+ if (tilesHeight % metatileHeight != 0)
+ FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)", tilesHeight, metatileHeight);
+
+ int maxNumTiles = tilesWidth * tilesHeight;
+
+ if (numTiles == 0)
+ numTiles = maxNumTiles;
+ else if (numTiles > maxNumTiles)
+ FATAL_ERROR("The specified number of tiles (%d) is greater than the maximum possible value (%d).\n", numTiles, maxNumTiles);
+
+ int bufferSize = numTiles * tileSize;
+ unsigned char *buffer = malloc(bufferSize);
+
+ if (buffer == NULL)
+ FATAL_ERROR("Failed to allocate memory for pixels.\n");
+
+ int metatilesWide = tilesWidth / metatileWidth;
+
+ switch (bitDepth) {
+ case 1:
+ ConvertToTiles1Bpp(image->pixels, buffer, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
+ break;
+ case 4:
+ ConvertToTiles4Bpp(image->pixels, buffer, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
+ break;
+ case 8:
+ ConvertToTiles8Bpp(image->pixels, buffer, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
+ break;
+ }
+
+ WriteWholeFile(path, buffer, bufferSize);
+
+ free(buffer);
+}
+
+void FreeImage(struct Image *image)
+{
+ free(image->pixels);
+ image->pixels = NULL;
+}
+
+void ReadGbaPalette(char *path, struct Palette *palette)
+{
+ int fileSize;
+ unsigned char *data = ReadWholeFile(path, &fileSize);
+
+ if (fileSize % 2 != 0)
+ FATAL_ERROR("The file size (%d) is not a multiple of 2.\n", fileSize);
+
+ palette->numColors = fileSize / 2;
+
+ for (int i = 0; i < palette->numColors; i++) {
+ uint16_t paletteEntry = (data[i * 2 + 1] << 8) | data[i * 2];
+ palette->colors[i].red = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_RED(paletteEntry));
+ palette->colors[i].green = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_GREEN(paletteEntry));
+ palette->colors[i].blue = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_BLUE(paletteEntry));
+ }
+
+ free(data);
+}
+
+void WriteGbaPalette(char *path, struct Palette *palette)
+{
+ FILE *fp = fopen(path, "wb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
+
+ for (int i = 0; i < palette->numColors; i++) {
+ unsigned char red = DOWNCONVERT_BIT_DEPTH(palette->colors[i].red);
+ unsigned char green = DOWNCONVERT_BIT_DEPTH(palette->colors[i].green);
+ unsigned char blue = DOWNCONVERT_BIT_DEPTH(palette->colors[i].blue);
+
+ uint16_t paletteEntry = SET_GBA_PAL(red, green, blue);
+
+ fputc(paletteEntry & 0xFF, fp);
+ fputc(paletteEntry >> 8, fp);
+ }
+
+ fclose(fp);
+}
diff --git a/tools/nitrogfx/gfx.h b/tools/nitrogfx/gfx.h
new file mode 100644
index 00000000..5355ced8
--- /dev/null
+++ b/tools/nitrogfx/gfx.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2015 YamaArashi
+
+#ifndef GFX_H
+#define GFX_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+struct Color {
+ unsigned char red;
+ unsigned char green;
+ unsigned char blue;
+};
+
+struct Palette {
+ struct Color colors[256];
+ int numColors;
+};
+
+struct Image {
+ int width;
+ int height;
+ int bitDepth;
+ unsigned char *pixels;
+ bool hasPalette;
+ struct Palette palette;
+ bool hasTransparency;
+};
+
+void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors);
+void WriteImage(char *path, int numTiles, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors);
+void FreeImage(struct Image *image);
+void ReadGbaPalette(char *path, struct Palette *palette);
+void WriteGbaPalette(char *path, struct Palette *palette);
+
+#endif // GFX_H
diff --git a/tools/nitrogfx/global.h b/tools/nitrogfx/global.h
new file mode 100644
index 00000000..65dd351d
--- /dev/null
+++ b/tools/nitrogfx/global.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2015 YamaArashi
+
+#ifndef GLOBAL_H
+#define GLOBAL_H
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef _MSC_VER
+
+#define FATAL_ERROR(format, ...) \
+do { \
+ fprintf(stderr, format, __VA_ARGS__); \
+ exit(1); \
+} while (0)
+
+#define UNUSED
+
+#else
+
+#define FATAL_ERROR(format, ...) \
+do { \
+ fprintf(stderr, format, ##__VA_ARGS__); \
+ exit(1); \
+} while (0)
+
+#define UNUSED __attribute__((__unused__))
+
+#endif // _MSC_VER
+
+#endif // GLOBAL_H
diff --git a/tools/nitrogfx/huff.c b/tools/nitrogfx/huff.c
new file mode 100644
index 00000000..143ed79b
--- /dev/null
+++ b/tools/nitrogfx/huff.c
@@ -0,0 +1,398 @@
+#include <stdbool.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdint.h>
+#include "global.h"
+#include "huff.h"
+
+static int cmp_tree(const void * a0, const void * b0) {
+ return ((struct HuffData *)a0)->value - ((struct HuffData *)b0)->value;
+}
+
+typedef int (*cmpfun)(const void *, const void *);
+
+int msort_r(void * data, size_t count, size_t size, cmpfun cmp, void * buffer) {
+ /*
+ * Out-of-place mergesort (stable sort)
+ * Returns 1 on success, 0 on failure
+ */
+ void * leftPtr;
+ void * rightPtr;
+ void * leftEnd;
+ void * rightEnd;
+ int i;
+
+ switch (count) {
+ case 0:
+ // Should never be here
+ return 0;
+
+ case 1:
+ // Nothing to do here
+ break;
+
+ case 2:
+ // Swap the two entries if the right one compares higher.
+ if (cmp(data, data + size) > 0) {
+ memcpy(buffer, data, size);
+ memcpy(data, data + size, size);
+ memcpy(data + size, buffer, size);
+ }
+ break;
+ default:
+ // Merge sort out-of-place.
+ leftPtr = data;
+ leftEnd = rightPtr = data + count / 2 * size;
+ rightEnd = data + count * size;
+
+ // Sort the left half
+ if (!msort_r(leftPtr, count / 2, size, cmp, buffer))
+ return 0;
+
+ // Sort the right half
+ if (!msort_r(rightPtr, count / 2 + (count & 1), size, cmp, buffer))
+ return 0;
+
+ // Merge the sorted halves out of place
+ i = 0;
+ do {
+ if (cmp(leftPtr, rightPtr) <= 0) {
+ memcpy(buffer + i * size, leftPtr, size);
+ leftPtr += size;
+ } else {
+ memcpy(buffer + i * size, rightPtr, size);
+ rightPtr += size;
+ }
+
+ } while (++i < count && leftPtr < leftEnd && rightPtr < rightEnd);
+
+ // Copy the remainder
+ if (i < count) {
+ if (leftPtr < leftEnd) {
+ memcpy(buffer + i * size, leftPtr, leftEnd - leftPtr);
+ }
+ else {
+ memcpy(buffer + i * size, rightPtr, rightEnd - rightPtr);
+ }
+ }
+
+ // Copy the merged data back
+ memcpy(data, buffer, count * size);
+ break;
+ }
+
+ return 1;
+}
+
+int msort(void * data, size_t count, size_t size, cmpfun cmp) {
+ void * buffer = malloc(count * size);
+ if (buffer == NULL) return 0;
+ int result = msort_r(data, count, size, cmp, buffer);
+ free(buffer);
+ return result;
+}
+
+static void write_tree(unsigned char * dest, HuffNode_t * tree, int nitems, struct BitEncoding * encoding) {
+ /*
+ * The example used to guide this function encodes the tree in a
+ * breadth-first manner. We attempt to emulate that here.
+ */
+
+ int i, j, k;
+
+ // There are (2 * nitems - 1) nodes in the binary tree. Allocate that.
+ HuffNode_t * traversal = calloc(2 * nitems - 1, sizeof(HuffNode_t));
+ if (traversal == NULL)
+ FATAL_ERROR("Fatal error while compressing Huff file.\n");
+
+ // The first node is the root of the tree.
+ traversal[0] = *tree;
+ i = 1;
+
+ // Copy the tree into a breadth-first ordering using brute force.
+ for (int depth = 1; i < 2 * nitems - 1; depth++) {
+ // Consider every possible path up to the current depth.
+ for (j = 0; i < 2 * nitems - 1 && j < 1 << depth; j++) {
+ // The index of the path is used to encode the path itself.
+ // Start from the most significant relevant bit and work our way down.
+ // Keep track of the current and previous nodes.
+ HuffNode_t * currNode = traversal;
+ HuffNode_t * parent = NULL;
+ for (k = 0; k < depth; k++) {
+ if (currNode->header.isLeaf)
+ break;
+ parent = currNode;
+ if ((j >> (depth - k - 1)) & 1)
+ currNode = currNode->branch.right;
+ else
+ currNode = currNode->branch.left;
+ }
+ // Check that the length of the current path equals the current depth.
+ if (k == depth) {
+ // Make sure we can encode the current branch.
+ // Bail here if we cannot.
+ // This is only applicable for 8-bit encodings.
+ if (traversal + i - parent > 128)
+ FATAL_ERROR("Fatal error while compressing Huff file: unable to encode binary tree.\n");
+ // Copy the current node, and update its parent.
+ traversal[i] = *currNode;
+ if (parent != NULL) {
+ if ((j & 1) == 1)
+ parent->branch.right = traversal + i;
+ else
+ parent->branch.left = traversal + i;
+ }
+ // Encode the path through the tree in the lookup table
+ if (traversal[i].header.isLeaf) {
+ encoding[traversal[i].leaf.key].nbits = depth;
+ encoding[traversal[i].leaf.key].bitstring = j;
+ }
+ i++;
+ }
+ }
+ }
+
+ // Encode the size of the tree.
+ // This is used by the decompressor to skip the tree.
+ dest[4] = nitems - 1;
+
+ // Encode each node in the tree.
+ for (i = 0; i < 2 * nitems - 1; i++) {
+ HuffNode_t * currNode = traversal + i;
+ if (currNode->header.isLeaf) {
+ dest[5 + i] = traversal[i].leaf.key;
+ } else {
+ dest[5 + i] = (((currNode->branch.right - traversal - i) / 2) - 1);
+ if (currNode->branch.left->header.isLeaf)
+ dest[5 + i] |= 0x80;
+ if (currNode->branch.right->header.isLeaf)
+ dest[5 + i] |= 0x40;
+ }
+ }
+
+ free(traversal);
+}
+
+static inline void write_32_le(unsigned char * dest, int * destPos, uint32_t * buff, int * buffPos) {
+ dest[*destPos] = *buff;
+ dest[*destPos + 1] = *buff >> 8;
+ dest[*destPos + 2] = *buff >> 16;
+ dest[*destPos + 3] = *buff >> 24;
+ *destPos += 4;
+ *buff = 0;
+ *buffPos = 0;
+}
+
+static inline void read_32_le(unsigned char * src, int * srcPos, uint32_t * buff) {
+ uint32_t tmp = src[*srcPos];
+ tmp |= src[*srcPos + 1] << 8;
+ tmp |= src[*srcPos + 2] << 16;
+ tmp |= src[*srcPos + 3] << 24;
+ *srcPos += 4;
+ *buff = tmp;
+}
+
+static void write_bits(unsigned char * dest, int * destPos, struct BitEncoding * encoding, int value, uint32_t * buff, int * buffBits) {
+ int nbits = encoding[value].nbits;
+ uint32_t bitstring = encoding[value].bitstring;
+
+ if (*buffBits + nbits >= 32) {
+ int diff = *buffBits + nbits - 32;
+ *buff <<= nbits - diff;
+ *buff |= bitstring >> diff;
+ bitstring &= ~(1 << diff);
+ nbits = diff;
+ write_32_le(dest, destPos, buff, buffBits);
+ }
+ if (nbits != 0) {
+ *buff <<= nbits;
+ *buff |= bitstring;
+ *buffBits += nbits;
+ }
+}
+
+/*
+=======================================
+MAIN COMPRESSION/DECOMPRESSION ROUTINES
+=======================================
+ */
+
+unsigned char * HuffCompress(unsigned char * src, int srcSize, int * compressedSize_p, int bitDepth) {
+ if (srcSize <= 0)
+ goto fail;
+
+ int worstCaseDestSize = 4 + (2 << bitDepth) + srcSize * 3;
+
+ unsigned char *dest = malloc(worstCaseDestSize);
+ if (dest == NULL)
+ goto fail;
+
+ int nitems = 1 << bitDepth;
+
+ HuffNode_t * freqs = calloc(nitems, sizeof(HuffNode_t));
+ if (freqs == NULL)
+ goto fail;
+
+ struct BitEncoding * encoding = calloc(nitems, sizeof(struct BitEncoding));
+ if (encoding == NULL)
+ goto fail;
+
+ // Set up the frequencies table. This will inform the tree.
+ for (int i = 0; i < nitems; i++) {
+ freqs[i].header.isLeaf = 1;
+ freqs[i].header.value = 0;
+ freqs[i].leaf.key = i;
+ }
+
+ // Count each nybble or byte.
+ for (int i = 0; i < srcSize; i++) {
+ if (bitDepth == 8) {
+ freqs[src[i]].header.value++;
+ } else {
+ freqs[src[i] >> 4].header.value++;
+ freqs[src[i] & 0xF].header.value++;
+ }
+ }
+
+#ifdef DEBUG
+ for (int i = 0; i < nitems; i++) {
+ fprintf(stderr, "%d: %d\n", i, freqs[i].header.value);
+ }
+#endif // DEBUG
+
+ // Sort the frequency table.
+ if (!msort(freqs, nitems, sizeof(HuffNode_t), cmp_tree))
+ goto fail;
+
+ // Prune zero-frequency values.
+ for (int i = 0; i < nitems; i++) {
+ if (freqs[i].header.value != 0) {
+ if (i > 0) {
+ for (int j = i; j < nitems; j++) {
+ freqs[j - i] = freqs[j];
+ }
+ nitems -= i;
+ }
+ break;
+ }
+ // This should never happen:
+ if (i == nitems - 1)
+ goto fail;
+ }
+
+ HuffNode_t * tree = calloc(nitems * 2 - 1, sizeof(HuffNode_t));
+ if (tree == NULL)
+ goto fail;
+
+ // Iteratively collapse the two least frequent nodes.
+ HuffNode_t * endptr = freqs + nitems - 2;
+
+ for (int i = 0; i < nitems - 1; i++) {
+ HuffNode_t * left = freqs;
+ HuffNode_t * right = freqs + 1;
+ tree[i * 2] = *right;
+ tree[i * 2 + 1] = *left;
+ for (int j = 0; j < nitems - i - 2; j++)
+ freqs[j] = freqs[j + 2];
+ endptr->header.isLeaf = 0;
+ endptr->header.value = tree[i * 2].header.value + tree[i * 2 + 1].header.value;
+ endptr->branch.left = tree + i * 2;
+ endptr->branch.right = tree + i * 2 + 1;
+ endptr--;
+ if (i < nitems - 2 && !msort(freqs, nitems - i - 1, sizeof(HuffNode_t), cmp_tree))
+ goto fail;
+ }
+
+ // Write the tree breadth-first, and create the path lookup table.
+ write_tree(dest, freqs, nitems, encoding);
+
+ free(tree);
+ free(freqs);
+
+ // Encode the data itself.
+ int destPos = 4 + nitems * 2;
+ uint32_t destBuf = 0;
+ uint32_t srcBuf = 0;
+ int destBitPos = 0;
+
+ for (int srcPos = 0; srcPos < srcSize;) {
+ read_32_le(src, &srcPos, &srcBuf);
+ for (int i = 0; i < 32 / bitDepth; i++) {
+ write_bits(dest, &destPos, encoding, srcBuf & (0xFF >> (8 - bitDepth)), &destBuf, &destBitPos);
+ srcBuf >>= bitDepth;
+ }
+ }
+
+ if (destBitPos != 0) {
+ write_32_le(dest, &destPos, &destBuf, &destBitPos);
+ }
+
+ free(encoding);
+
+ // Write the header.
+ dest[0] = bitDepth | 0x20;
+ dest[1] = srcSize;
+ dest[2] = srcSize >> 8;
+ dest[3] = srcSize >> 16;
+ *compressedSize_p = (destPos + 3) & ~3;
+ return dest;
+
+fail:
+ FATAL_ERROR("Fatal error while compressing Huff file.\n");
+}
+
+unsigned char * HuffDecompress(unsigned char * src, int srcSize, int * uncompressedSize_p) {
+ if (srcSize < 4)
+ goto fail;
+
+ int bitDepth = *src & 15;
+ if (bitDepth != 4 && bitDepth != 8)
+ goto fail;
+
+ int destSize = (src[3] << 16) | (src[2] << 8) | src[1];
+
+ unsigned char *dest = malloc(destSize);
+
+ if (dest == NULL)
+ goto fail;
+
+ int treePos = 5;
+ int treeSize = (src[4] + 1) * 2;
+ int srcPos = 4 + treeSize;
+ int destPos = 0;
+ int curValPos = 0;
+ uint32_t destTmp = 0;
+ uint32_t window;
+
+ for (;;)
+ {
+ if (srcPos >= srcSize)
+ goto fail;
+ read_32_le(src, &srcPos, &window);
+ for (int i = 0; i < 32; i++) {
+ int curBit = (window >> 31) & 1;
+ unsigned char treeView = src[treePos];
+ bool isLeaf = ((treeView << curBit) & 0x80) != 0;
+ treePos &= ~1; // align
+ treePos += ((treeView & 0x3F) + 1) * 2 + curBit;
+ if (isLeaf) {
+ destTmp >>= bitDepth;
+ destTmp |= (src[treePos] << (32 - bitDepth));
+ curValPos++;
+ if (curValPos == 32 / bitDepth) {
+ write_32_le(dest, &destPos, &destTmp, &curValPos);
+ if (destPos == destSize) {
+ *uncompressedSize_p = destSize;
+ return dest;
+ }
+ }
+ treePos = 5;
+ }
+ window <<= 1;
+ }
+ }
+
+fail:
+ FATAL_ERROR("Fatal error while decompressing Huff file.\n");
+}
diff --git a/tools/nitrogfx/huff.h b/tools/nitrogfx/huff.h
new file mode 100644
index 00000000..6002fe95
--- /dev/null
+++ b/tools/nitrogfx/huff.h
@@ -0,0 +1,38 @@
+#ifndef HUFF_H
+#define HUFF_H
+
+union HuffNode;
+
+struct HuffData {
+ unsigned value:31;
+ unsigned isLeaf:1;
+};
+
+struct HuffLeaf {
+ struct HuffData header;
+ unsigned char key;
+};
+
+struct HuffBranch {
+ struct HuffData header;
+ union HuffNode * left;
+ union HuffNode * right;
+};
+
+union HuffNode {
+ struct HuffData header;
+ struct HuffLeaf leaf;
+ struct HuffBranch branch;
+};
+
+typedef union HuffNode HuffNode_t;
+
+struct BitEncoding {
+ unsigned long long nbits:6;
+ unsigned long long bitstring:58;
+};
+
+unsigned char * HuffCompress(unsigned char * buffer, int srcSize, int * compressedSize_p, int bitDepth);
+unsigned char * HuffDecompress(unsigned char * buffer, int srcSize, int * uncompressedSize_p);
+
+#endif //HUFF_H
diff --git a/tools/nitrogfx/jasc_pal.c b/tools/nitrogfx/jasc_pal.c
new file mode 100644
index 00000000..e5ba9c3c
--- /dev/null
+++ b/tools/nitrogfx/jasc_pal.c
@@ -0,0 +1,172 @@
+// Copyright (c) 2015 YamaArashi
+
+#include <stdio.h>
+#include <string.h>
+#include "global.h"
+#include "gfx.h"
+#include "util.h"
+
+// Read/write Paint Shop Pro palette files.
+
+// Format of a Paint Shop Pro palette file, line by line:
+// "JASC-PAL\r\n" (signature)
+// "0100\r\n" (version; seems to always be "0100")
+// "<NUMBER_OF_COLORS>\r\n" (number of colors in decimal)
+//
+// <NUMBER_OF_COLORS> times:
+// "<RED> <GREEN> <BLUE>\r\n" (color entry)
+//
+// Each color component is a decimal number from 0 to 255.
+// Examples:
+// Black - "0 0 0\r\n"
+// Blue - "0 0 255\r\n"
+// Brown - "150 75 0\r\n"
+
+#define MAX_LINE_LENGTH 11
+
+void ReadJascPaletteLine(FILE *fp, char *line)
+{
+ int c;
+ int length = 0;
+
+ for (;;)
+ {
+ c = fgetc(fp);
+
+ if (c == '\r')
+ {
+ c = fgetc(fp);
+
+ if (c != '\n')
+ FATAL_ERROR("CR line endings aren't supported.\n");
+
+ line[length] = 0;
+
+ return;
+ }
+
+ if (c == '\n')
+ FATAL_ERROR("LF line endings aren't supported.\n");
+
+ if (c == EOF)
+ FATAL_ERROR("Unexpected EOF. No CRLF at end of file.\n");
+
+ if (c == 0)
+ FATAL_ERROR("NUL character in file.\n");
+
+ if (length == MAX_LINE_LENGTH)
+ {
+ line[length] = 0;
+ FATAL_ERROR("The line \"%s\" is too long.\n", line);
+ }
+
+ line[length++] = c;
+ }
+}
+
+void ReadJascPalette(char *path, struct Palette *palette)
+{
+ char line[MAX_LINE_LENGTH + 1];
+
+ FILE *fp = fopen(path, "rb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open JASC-PAL file \"%s\" for reading.\n", path);
+
+ ReadJascPaletteLine(fp, line);
+
+ if (strcmp(line, "JASC-PAL") != 0)
+ FATAL_ERROR("Invalid JASC-PAL signature.\n");
+
+ ReadJascPaletteLine(fp, line);
+
+ if (strcmp(line, "0100") != 0)
+ FATAL_ERROR("Unsuported JASC-PAL version.\n");
+
+ ReadJascPaletteLine(fp, line);
+
+ if (!ParseNumber(line, NULL, 10, &palette->numColors))
+ FATAL_ERROR("Failed to parse number of colors.\n");
+
+ if (palette->numColors < 1 || palette->numColors > 256)
+ FATAL_ERROR("%d is an invalid number of colors. The number of colors must be in the range [1, 256].\n", palette->numColors);
+
+ for (int i = 0; i < palette->numColors; i++)
+ {
+ ReadJascPaletteLine(fp, line);
+
+ char *s = line;
+ char *end;
+
+ int red;
+ int green;
+ int blue;
+
+ if (!ParseNumber(s, &end, 10, &red))
+ FATAL_ERROR("Failed to parse red color component.\n");
+
+ s = end;
+
+ if (*s != ' ')
+ FATAL_ERROR("Expected a space after red color component.\n");
+
+ s++;
+
+ if (*s < '0' || *s > '9')
+ FATAL_ERROR("Expected only a space between red and green color components.\n");
+
+ if (!ParseNumber(s, &end, 10, &green))
+ FATAL_ERROR("Failed to parse green color component.\n");
+
+ s = end;
+
+ if (*s != ' ')
+ FATAL_ERROR("Expected a space after green color component.\n");
+
+ s++;
+
+ if (*s < '0' || *s > '9')
+ FATAL_ERROR("Expected only a space between green and blue color components.\n");
+
+ if (!ParseNumber(s, &end, 10, &blue))
+ FATAL_ERROR("Failed to parse blue color component.\n");
+
+ if (*end != 0)
+ FATAL_ERROR("Garbage after blue color component.\n");
+
+ if (red < 0 || red > 255)
+ FATAL_ERROR("Red color component (%d) is outside the range [0, 255].\n", red);
+
+ if (green < 0 || green > 255)
+ FATAL_ERROR("Green color component (%d) is outside the range [0, 255].\n", green);
+
+ if (blue < 0 || blue > 255)
+ FATAL_ERROR("Blue color component (%d) is outside the range [0, 255].\n", blue);
+
+ palette->colors[i].red = red;
+ palette->colors[i].green = green;
+ palette->colors[i].blue = blue;
+ }
+
+ if (fgetc(fp) != EOF)
+ FATAL_ERROR("Garbage after color data.\n");
+
+ fclose(fp);
+}
+
+void WriteJascPalette(char *path, struct Palette *palette)
+{
+ FILE *fp = fopen(path, "wb");
+
+ fputs("JASC-PAL\r\n", fp);
+ fputs("0100\r\n", fp);
+ fprintf(fp, "%d\r\n", palette->numColors);
+
+ for (int i = 0; i < palette->numColors; i++)
+ {
+ struct Color *color = &palette->colors[i];
+ fprintf(fp, "%d %d %d\r\n", color->red, color->green, color->blue);
+ }
+
+ fclose(fp);
+}
diff --git a/tools/nitrogfx/jasc_pal.h b/tools/nitrogfx/jasc_pal.h
new file mode 100644
index 00000000..b60b31fc
--- /dev/null
+++ b/tools/nitrogfx/jasc_pal.h
@@ -0,0 +1,9 @@
+// Copyright (c) 2015 YamaArashi
+
+#ifndef JASC_PAL_H
+#define JASC_PAL_H
+
+void ReadJascPalette(char *path, struct Palette *palette);
+void WriteJascPalette(char *path, struct Palette *palette);
+
+#endif // JASC_PAL_H
diff --git a/tools/nitrogfx/lz.c b/tools/nitrogfx/lz.c
new file mode 100644
index 00000000..97434ce5
--- /dev/null
+++ b/tools/nitrogfx/lz.c
@@ -0,0 +1,153 @@
+// 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)
+{
+ 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");
+}
diff --git a/tools/nitrogfx/lz.h b/tools/nitrogfx/lz.h
new file mode 100644
index 00000000..90f56b64
--- /dev/null
+++ b/tools/nitrogfx/lz.h
@@ -0,0 +1,9 @@
+// Copyright (c) 2015 YamaArashi
+
+#ifndef LZ_H
+#define LZ_H
+
+unsigned char *LZDecompress(unsigned char *src, int srcSize, int *uncompressedSize);
+unsigned char *LZCompress(unsigned char *src, int srcSize, int *compressedSize, const int minDistance);
+
+#endif // LZ_H
diff --git a/tools/nitrogfx/main.c b/tools/nitrogfx/main.c
new file mode 100644
index 00000000..b9f4272c
--- /dev/null
+++ b/tools/nitrogfx/main.c
@@ -0,0 +1,537 @@
+// Copyright (c) 2015 YamaArashi
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include "global.h"
+#include "util.h"
+#include "options.h"
+#include "gfx.h"
+#include "convert_png.h"
+#include "jasc_pal.h"
+#include "lz.h"
+#include "rl.h"
+#include "font.h"
+#include "huff.h"
+
+struct CommandHandler
+{
+ const char *inputFileExtension;
+ const char *outputFileExtension;
+ void(*function)(char *inputPath, char *outputPath, int argc, char **argv);
+};
+
+void ConvertGbaToPng(char *inputPath, char *outputPath, struct GbaToPngOptions *options)
+{
+ struct Image image;
+
+ if (options->paletteFilePath != NULL)
+ {
+ ReadGbaPalette(options->paletteFilePath, &image.palette);
+ image.hasPalette = true;
+ }
+ else
+ {
+ image.hasPalette = false;
+ }
+
+ ReadImage(inputPath, options->width, options->bitDepth, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette);
+
+ image.hasTransparency = options->hasTransparency;
+
+ WritePng(outputPath, &image);
+
+ FreeImage(&image);
+}
+
+void ConvertPngToGba(char *inputPath, char *outputPath, struct PngToGbaOptions *options)
+{
+ struct Image image;
+
+ image.bitDepth = options->bitDepth;
+
+ ReadPng(inputPath, &image);
+
+ WriteImage(outputPath, options->numTiles, options->bitDepth, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette);
+
+ FreeImage(&image);
+}
+
+void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **argv)
+{
+ char *inputFileExtension = GetFileExtension(inputPath);
+ struct GbaToPngOptions options;
+ options.paletteFilePath = NULL;
+ options.bitDepth = inputFileExtension[0] - '0';
+ options.hasTransparency = false;
+ options.width = 1;
+ options.metatileWidth = 1;
+ options.metatileHeight = 1;
+
+ for (int i = 3; i < argc; i++)
+ {
+ char *option = argv[i];
+
+ if (strcmp(option, "-palette") == 0)
+ {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No palette file path following \"-palette\".\n");
+
+ i++;
+
+ options.paletteFilePath = argv[i];
+ }
+ else if (strcmp(option, "-object") == 0)
+ {
+ options.hasTransparency = true;
+ }
+ else if (strcmp(option, "-width") == 0)
+ {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No width following \"-width\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &options.width))
+ FATAL_ERROR("Failed to parse width.\n");
+
+ if (options.width < 1)
+ FATAL_ERROR("Width must be positive.\n");
+ }
+ else if (strcmp(option, "-mwidth") == 0)
+ {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No metatile width value following \"-mwidth\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &options.metatileWidth))
+ FATAL_ERROR("Failed to parse metatile width.\n");
+
+ if (options.metatileWidth < 1)
+ FATAL_ERROR("metatile width must be positive.\n");
+ }
+ else if (strcmp(option, "-mheight") == 0)
+ {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No metatile height value following \"-mheight\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &options.metatileHeight))
+ FATAL_ERROR("Failed to parse metatile height.\n");
+
+ if (options.metatileHeight < 1)
+ FATAL_ERROR("metatile height must be positive.\n");
+ }
+ else
+ {
+ FATAL_ERROR("Unrecognized option \"%s\".\n", option);
+ }
+ }
+
+ if (options.metatileWidth > options.width)
+ options.width = options.metatileWidth;
+
+ ConvertGbaToPng(inputPath, outputPath, &options);
+}
+
+void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **argv)
+{
+ char *outputFileExtension = GetFileExtension(outputPath);
+ int bitDepth = outputFileExtension[0] - '0';
+ struct PngToGbaOptions options;
+ options.numTiles = 0;
+ options.bitDepth = bitDepth;
+ options.metatileWidth = 1;
+ options.metatileHeight = 1;
+
+ for (int i = 3; i < argc; i++)
+ {
+ char *option = argv[i];
+
+ if (strcmp(option, "-num_tiles") == 0)
+ {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No number of tiles following \"-num_tiles\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &options.numTiles))
+ FATAL_ERROR("Failed to parse number of tiles.\n");
+
+ if (options.numTiles < 1)
+ FATAL_ERROR("Number of tiles must be positive.\n");
+ }
+ else if (strcmp(option, "-mwidth") == 0)
+ {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No metatile width value following \"-mwidth\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &options.metatileWidth))
+ FATAL_ERROR("Failed to parse metatile width.\n");
+
+ if (options.metatileWidth < 1)
+ FATAL_ERROR("metatile width must be positive.\n");
+ }
+ else if (strcmp(option, "-mheight") == 0)
+ {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No metatile height value following \"-mheight\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &options.metatileHeight))
+ FATAL_ERROR("Failed to parse metatile height.\n");
+
+ if (options.metatileHeight < 1)
+ FATAL_ERROR("metatile height must be positive.\n");
+ }
+ else
+ {
+ FATAL_ERROR("Unrecognized option \"%s\".\n", option);
+ }
+ }
+
+ ConvertPngToGba(inputPath, outputPath, &options);
+}
+
+void HandlePngToGbaPaletteCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
+{
+ struct Palette palette;
+
+ ReadPngPalette(inputPath, &palette);
+ WriteGbaPalette(outputPath, &palette);
+}
+
+void HandleGbaToJascPaletteCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
+{
+ struct Palette palette;
+
+ ReadGbaPalette(inputPath, &palette);
+ WriteJascPalette(outputPath, &palette);
+}
+
+void HandleJascToGbaPaletteCommand(char *inputPath, char *outputPath, int argc, char **argv)
+{
+ int numColors = 0;
+
+ for (int i = 3; i < argc; i++)
+ {
+ char *option = argv[i];
+
+ if (strcmp(option, "-num_colors") == 0)
+ {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No number of colors following \"-num_colors\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &numColors))
+ FATAL_ERROR("Failed to parse number of colors.\n");
+
+ if (numColors < 1)
+ FATAL_ERROR("Number of colors must be positive.\n");
+ }
+ else
+ {
+ FATAL_ERROR("Unrecognized option \"%s\".\n", option);
+ }
+ }
+
+ struct Palette palette;
+
+ ReadJascPalette(inputPath, &palette);
+
+ if (numColors != 0)
+ palette.numColors = numColors;
+
+ WriteGbaPalette(outputPath, &palette);
+}
+
+void HandleLatinFontToPngCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
+{
+ struct Image image;
+
+ ReadLatinFont(inputPath, &image);
+ WritePng(outputPath, &image);
+
+ FreeImage(&image);
+}
+
+void HandlePngToLatinFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
+{
+ struct Image image;
+
+ image.bitDepth = 2;
+
+ ReadPng(inputPath, &image);
+ WriteLatinFont(outputPath, &image);
+
+ FreeImage(&image);
+}
+
+void HandleHalfwidthJapaneseFontToPngCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
+{
+ struct Image image;
+
+ ReadHalfwidthJapaneseFont(inputPath, &image);
+ WritePng(outputPath, &image);
+
+ FreeImage(&image);
+}
+
+void HandlePngToHalfwidthJapaneseFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
+{
+ struct Image image;
+
+ image.bitDepth = 2;
+
+ ReadPng(inputPath, &image);
+ WriteHalfwidthJapaneseFont(outputPath, &image);
+
+ FreeImage(&image);
+}
+
+void HandleFullwidthJapaneseFontToPngCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
+{
+ struct Image image;
+
+ ReadFullwidthJapaneseFont(inputPath, &image);
+ WritePng(outputPath, &image);
+
+ FreeImage(&image);
+}
+
+void HandlePngToFullwidthJapaneseFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
+{
+ struct Image image;
+
+ image.bitDepth = 2;
+
+ ReadPng(inputPath, &image);
+ WriteFullwidthJapaneseFont(outputPath, &image);
+
+ FreeImage(&image);
+}
+
+void HandleLZCompressCommand(char *inputPath, char *outputPath, int argc, char **argv)
+{
+ int overflowSize = 0;
+ int minDistance = 2; // default, for compatibility with LZ77UnCompVram()
+
+ for (int i = 3; i < argc; i++)
+ {
+ char *option = argv[i];
+
+ if (strcmp(option, "-overflow") == 0)
+ {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No size following \"-overflow\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &overflowSize))
+ FATAL_ERROR("Failed to parse overflow size.\n");
+
+ if (overflowSize < 1)
+ FATAL_ERROR("Overflow size must be positive.\n");
+ }
+ else if (strcmp(option, "-search") == 0)
+ {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No size following \"-overflow\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &minDistance))
+ FATAL_ERROR("Failed to parse LZ min search distance.\n");
+
+ if (minDistance < 1)
+ FATAL_ERROR("LZ min search distance must be positive.\n");
+ }
+ else
+ {
+ FATAL_ERROR("Unrecognized option \"%s\".\n", option);
+ }
+ }
+
+ // The overflow option allows a quirk in some of Ruby/Sapphire's tilesets
+ // to be reproduced. It works by appending a number of zeros to the data
+ // before compressing it and then amending the LZ header's size field to
+ // reflect the expected size. This will cause an overflow when decompressing
+ // the data.
+
+ int fileSize;
+ unsigned char *buffer = ReadWholeFileZeroPadded(inputPath, &fileSize, overflowSize);
+
+ int compressedSize;
+ unsigned char *compressedData = LZCompress(buffer, fileSize + overflowSize, &compressedSize, minDistance);
+
+ compressedData[1] = (unsigned char)fileSize;
+ compressedData[2] = (unsigned char)(fileSize >> 8);
+ compressedData[3] = (unsigned char)(fileSize >> 16);
+
+ free(buffer);
+
+ WriteWholeFile(outputPath, compressedData, compressedSize);
+
+ free(compressedData);
+}
+
+void HandleLZDecompressCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
+{
+ int fileSize;
+ unsigned char *buffer = ReadWholeFile(inputPath, &fileSize);
+
+ int uncompressedSize;
+ unsigned char *uncompressedData = LZDecompress(buffer, fileSize, &uncompressedSize);
+
+ free(buffer);
+
+ WriteWholeFile(outputPath, uncompressedData, uncompressedSize);
+
+ free(uncompressedData);
+}
+
+void HandleRLCompressCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
+{
+ int fileSize;
+ unsigned char *buffer = ReadWholeFile(inputPath, &fileSize);
+
+ int compressedSize;
+ unsigned char *compressedData = RLCompress(buffer, fileSize, &compressedSize);
+
+ free(buffer);
+
+ WriteWholeFile(outputPath, compressedData, compressedSize);
+
+ free(compressedData);
+}
+
+void HandleRLDecompressCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
+{
+ int fileSize;
+ unsigned char *buffer = ReadWholeFile(inputPath, &fileSize);
+
+ int uncompressedSize;
+ unsigned char *uncompressedData = RLDecompress(buffer, fileSize, &uncompressedSize);
+
+ free(buffer);
+
+ WriteWholeFile(outputPath, uncompressedData, uncompressedSize);
+
+ free(uncompressedData);
+}
+
+void HandleHuffCompressCommand(char *inputPath, char *outputPath, int argc, char **argv)
+{
+ int fileSize;
+ int bitDepth = 4;
+
+ for (int i = 3; i < argc; i++)
+ {
+ char *option = argv[i];
+
+ if (strcmp(option, "-depth") == 0)
+ {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No size following \"-depth\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &bitDepth))
+ FATAL_ERROR("Failed to parse bit depth.\n");
+
+ if (bitDepth != 4 && bitDepth != 8)
+ FATAL_ERROR("GBA only supports bit depth of 4 or 8.\n");
+ }
+ else
+ {
+ FATAL_ERROR("Unrecognized option \"%s\".\n", option);
+ }
+ }
+
+ unsigned char *buffer = ReadWholeFile(inputPath, &fileSize);
+
+ int compressedSize;
+ unsigned char *compressedData = HuffCompress(buffer, fileSize, &compressedSize, bitDepth);
+
+ free(buffer);
+
+ WriteWholeFile(outputPath, compressedData, compressedSize);
+
+ free(compressedData);
+}
+
+void HandleHuffDecompressCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
+{
+ int fileSize;
+ unsigned char *buffer = ReadWholeFile(inputPath, &fileSize);
+
+ int uncompressedSize;
+ unsigned char *uncompressedData = HuffDecompress(buffer, fileSize, &uncompressedSize);
+
+ free(buffer);
+
+ WriteWholeFile(outputPath, uncompressedData, uncompressedSize);
+
+ free(uncompressedData);
+}
+
+int main(int argc, char **argv)
+{
+ if (argc < 3)
+ FATAL_ERROR("Usage: nitrogfx INPUT_PATH OUTPUT_PATH [options...]\n");
+
+ struct CommandHandler handlers[] =
+ {
+ { "1bpp", "png", HandleGbaToPngCommand },
+ { "4bpp", "png", HandleGbaToPngCommand },
+ { "8bpp", "png", HandleGbaToPngCommand },
+ { "png", "1bpp", HandlePngToGbaCommand },
+ { "png", "4bpp", HandlePngToGbaCommand },
+ { "png", "8bpp", HandlePngToGbaCommand },
+ { "png", "gbapal", HandlePngToGbaPaletteCommand },
+ { "gbapal", "pal", HandleGbaToJascPaletteCommand },
+ { "pal", "gbapal", HandleJascToGbaPaletteCommand },
+ { "latfont", "png", HandleLatinFontToPngCommand },
+ { "png", "latfont", HandlePngToLatinFontCommand },
+ { "hwjpnfont", "png", HandleHalfwidthJapaneseFontToPngCommand },
+ { "png", "hwjpnfont", HandlePngToHalfwidthJapaneseFontCommand },
+ { "fwjpnfont", "png", HandleFullwidthJapaneseFontToPngCommand },
+ { "png", "fwjpnfont", HandlePngToFullwidthJapaneseFontCommand },
+ { NULL, "huff", HandleHuffCompressCommand },
+ { NULL, "lz", HandleLZCompressCommand },
+ { "huff", NULL, HandleHuffDecompressCommand },
+ { "lz", NULL, HandleLZDecompressCommand },
+ { NULL, "rl", HandleRLCompressCommand },
+ { "rl", NULL, HandleRLDecompressCommand },
+ { NULL, NULL, NULL }
+ };
+
+ char *inputPath = argv[1];
+ char *outputPath = argv[2];
+ char *inputFileExtension = GetFileExtension(inputPath);
+ char *outputFileExtension = GetFileExtension(outputPath);
+
+ if (inputFileExtension == NULL)
+ FATAL_ERROR("Input file \"%s\" has no extension.\n", inputPath);
+
+ if (outputFileExtension == NULL)
+ FATAL_ERROR("Output file \"%s\" has no extension.\n", outputPath);
+
+ for (int i = 0; handlers[i].function != NULL; i++)
+ {
+ if ((handlers[i].inputFileExtension == NULL || strcmp(handlers[i].inputFileExtension, inputFileExtension) == 0)
+ && (handlers[i].outputFileExtension == NULL || strcmp(handlers[i].outputFileExtension, outputFileExtension) == 0))
+ {
+ handlers[i].function(inputPath, outputPath, argc, argv);
+ return 0;
+ }
+ }
+
+ FATAL_ERROR("Don't know how to convert \"%s\" to \"%s\".\n", inputPath, outputPath);
+}
diff --git a/tools/nitrogfx/options.h b/tools/nitrogfx/options.h
new file mode 100644
index 00000000..2ff3967a
--- /dev/null
+++ b/tools/nitrogfx/options.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2018 huderlem
+
+#ifndef OPTIONS_H
+#define OPTIONS_H
+
+#include <stdbool.h>
+
+struct GbaToPngOptions {
+ char *paletteFilePath;
+ int bitDepth;
+ bool hasTransparency;
+ int width;
+ int metatileWidth;
+ int metatileHeight;
+};
+
+struct PngToGbaOptions {
+ int numTiles;
+ int bitDepth;
+ int metatileWidth;
+ int metatileHeight;
+};
+
+#endif // OPTIONS_H
diff --git a/tools/nitrogfx/rl.c b/tools/nitrogfx/rl.c
new file mode 100644
index 00000000..968c9347
--- /dev/null
+++ b/tools/nitrogfx/rl.c
@@ -0,0 +1,149 @@
+// Copyright (c) 2016 YamaArashi
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include "global.h"
+#include "rl.h"
+
+unsigned char *RLDecompress(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++];
+ bool compressed = ((flags & 0x80) != 0);
+
+ if (compressed)
+ {
+ int length = (flags & 0x7F) + 3;
+ unsigned char data = src[srcPos++];
+
+ if (destPos + length > destSize)
+ goto fail;
+
+ for (int i = 0; i < length; i++)
+ dest[destPos++] = data;
+ }
+ else
+ {
+ int length = (flags & 0x7F) + 1;
+
+ if (destPos + length > destSize)
+ goto fail;
+
+ for (int i = 0; i < length; i++)
+ dest[destPos++] = src[srcPos++];
+ }
+
+ if (destPos == destSize)
+ {
+ *uncompressedSize = destSize;
+ return dest;
+ }
+ }
+
+fail:
+ FATAL_ERROR("Fatal error while decompressing RL file.\n");
+}
+
+unsigned char *RLCompress(unsigned char *src, int srcSize, int *compressedSize)
+{
+ if (srcSize <= 0)
+ goto fail;
+
+ int worstCaseDestSize = 4 + srcSize * 2;
+
+ // 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] = 0x30; // RL 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 (;;)
+ {
+ bool compress = false;
+ int uncompressedStart = srcPos;
+ int uncompressedLength = 0;
+
+ while (srcPos < srcSize && uncompressedLength < (0x7F + 1))
+ {
+ compress = (srcPos + 2 < srcSize && src[srcPos] == src[srcPos + 1] && src[srcPos] == src[srcPos + 2]);
+
+ if (compress)
+ break;
+
+ srcPos++;
+ uncompressedLength++;
+ }
+
+ if (uncompressedLength > 0)
+ {
+ dest[destPos++] = uncompressedLength - 1;
+
+ for (int i = 0; i < uncompressedLength; i++)
+ dest[destPos++] = src[uncompressedStart + i];
+ }
+
+ if (compress)
+ {
+ unsigned char data = src[srcPos];
+ int compressedLength = 0;
+
+ while (compressedLength < (0x7F + 3)
+ && srcPos + compressedLength < srcSize
+ && src[srcPos + compressedLength] == data)
+ {
+ compressedLength++;
+ }
+
+ dest[destPos++] = 0x80 | (compressedLength - 3);
+ dest[destPos++] = data;
+
+ srcPos += compressedLength;
+ }
+
+ 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 RL file.\n");
+}
diff --git a/tools/nitrogfx/rl.h b/tools/nitrogfx/rl.h
new file mode 100644
index 00000000..02ad8d6d
--- /dev/null
+++ b/tools/nitrogfx/rl.h
@@ -0,0 +1,9 @@
+// Copyright (c) 2016 YamaArashi
+
+#ifndef RL_H
+#define RL_H
+
+unsigned char *RLDecompress(unsigned char *src, int srcSize, int *uncompressedSize);
+unsigned char *RLCompress(unsigned char *src, int srcSize, int *compressedSize);
+
+#endif // RL_H
diff --git a/tools/nitrogfx/util.c b/tools/nitrogfx/util.c
new file mode 100644
index 00000000..87abeb31
--- /dev/null
+++ b/tools/nitrogfx/util.c
@@ -0,0 +1,124 @@
+// Copyright (c) 2015 YamaArashi
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <limits.h>
+#include "global.h"
+#include "util.h"
+
+bool ParseNumber(char *s, char **end, int radix, int *intValue)
+{
+ char *localEnd;
+
+ if (end == NULL)
+ end = &localEnd;
+
+ errno = 0;
+
+ const long longValue = strtol(s, end, radix);
+
+ if (*end == s)
+ return false; // not a number
+
+ if ((longValue == LONG_MIN || longValue == LONG_MAX) && errno == ERANGE)
+ return false;
+
+ if (longValue > INT_MAX)
+ return false;
+
+ if (longValue < INT_MIN)
+ return false;
+
+ *intValue = (int)longValue;
+
+ return true;
+}
+
+char *GetFileExtension(char *path)
+{
+ char *extension = path;
+
+ while (*extension != 0)
+ extension++;
+
+ while (extension > path && *extension != '.')
+ extension--;
+
+ if (extension == path)
+ return NULL;
+
+ extension++;
+
+ if (*extension == 0)
+ return NULL;
+
+ return extension;
+}
+
+unsigned char *ReadWholeFile(char *path, int *size)
+{
+ FILE *fp = fopen(path, "rb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
+
+ fseek(fp, 0, SEEK_END);
+
+ *size = ftell(fp);
+
+ unsigned char *buffer = malloc(*size);
+
+ if (buffer == NULL)
+ FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path);
+
+ rewind(fp);
+
+ if (fread(buffer, *size, 1, fp) != 1)
+ FATAL_ERROR("Failed to read \"%s\".\n", path);
+
+ fclose(fp);
+
+ return buffer;
+}
+
+unsigned char *ReadWholeFileZeroPadded(char *path, int *size, int padAmount)
+{
+ FILE *fp = fopen(path, "rb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
+
+ fseek(fp, 0, SEEK_END);
+
+ *size = ftell(fp);
+
+ unsigned char *buffer = calloc(*size + padAmount, 1);
+
+ if (buffer == NULL)
+ FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path);
+
+ rewind(fp);
+
+ if (fread(buffer, *size, 1, fp) != 1)
+ FATAL_ERROR("Failed to read \"%s\".\n", path);
+
+ fclose(fp);
+
+ return buffer;
+}
+
+void WriteWholeFile(char *path, void *buffer, int bufferSize)
+{
+ FILE *fp = fopen(path, "wb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
+
+ if (fwrite(buffer, bufferSize, 1, fp) != 1)
+ FATAL_ERROR("Failed to write to \"%s\".\n", path);
+
+ fclose(fp);
+}
diff --git a/tools/nitrogfx/util.h b/tools/nitrogfx/util.h
new file mode 100644
index 00000000..6d7a9c21
--- /dev/null
+++ b/tools/nitrogfx/util.h
@@ -0,0 +1,14 @@
+// Copyright (c) 2015 YamaArashi
+
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <stdbool.h>
+
+bool ParseNumber(char *s, char **end, int radix, int *intValue);
+char *GetFileExtension(char *path);
+unsigned char *ReadWholeFile(char *path, int *size);
+unsigned char *ReadWholeFileZeroPadded(char *path, int *size, int padAmount);
+void WriteWholeFile(char *path, void *buffer, int bufferSize);
+
+#endif // UTIL_H