summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/gbagfx/.gitignore1
-rw-r--r--tools/gbagfx/Makefile15
-rw-r--r--tools/gbagfx/convert_png.c167
-rw-r--r--tools/gbagfx/convert_png.h11
-rw-r--r--tools/gbagfx/gfx.c332
-rw-r--r--tools/gbagfx/gfx.h36
-rw-r--r--tools/gbagfx/global.h35
-rw-r--r--tools/gbagfx/jasc_pal.c164
-rw-r--r--tools/gbagfx/jasc_pal.h9
-rw-r--r--tools/gbagfx/lz.c143
-rw-r--r--tools/gbagfx/lz.h9
-rw-r--r--tools/gbagfx/main.c260
-rw-r--r--tools/gbagfx/util.c140
-rw-r--r--tools/gbagfx/util.h17
-rw-r--r--tools/scaninc/.gitignore1
-rw-r--r--tools/scaninc/Makefile13
-rw-r--r--tools/scaninc/scaninc.cpp305
17 files changed, 1658 insertions, 0 deletions
diff --git a/tools/gbagfx/.gitignore b/tools/gbagfx/.gitignore
new file mode 100644
index 000000000..dbbb3f04c
--- /dev/null
+++ b/tools/gbagfx/.gitignore
@@ -0,0 +1 @@
+gbagfx
diff --git a/tools/gbagfx/Makefile b/tools/gbagfx/Makefile
new file mode 100644
index 000000000..1bf37e301
--- /dev/null
+++ b/tools/gbagfx/Makefile
@@ -0,0 +1,15 @@
+CC = gcc
+
+CFLAGS = -Wall -std=c11 -O2
+
+LIBS = -lz -lpng
+
+SRCS = main.c convert_png.c gfx.c jasc_pal.c lz.c util.c
+
+.PHONY: clean
+
+gbagfx: $(SRCS) convert_png.h gfx.h global.h jasc_pal.h lz.h util.h
+ $(CC) $(CFLAGS) $(SRCS) -o $@ $(LIBS)
+
+clean:
+ $(RM) gbagfx gbagfx.exe
diff --git a/tools/gbagfx/convert_png.c b/tools/gbagfx/convert_png.c
new file mode 100644
index 000000000..8a3d03943
--- /dev/null
+++ b/tools/gbagfx/convert_png.c
@@ -0,0 +1,167 @@
+#include <stdio.h>
+#include <setjmp.h>
+#include <png.h>
+#include "global.h"
+#include "convert_png.h"
+#include "gfx.h"
+
+void ReadPng(char *path, struct Image *image)
+{
+ 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);
+
+ int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
+
+ if (bit_depth != image->bitDepth)
+ FATAL_ERROR("\"%s\" has a bit depth of %d, but the expected bit depth is %d.\n", path, bit_depth, image->bitDepth);
+
+ 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);
+}
+
+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->isObject) {
+ 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/gbagfx/convert_png.h b/tools/gbagfx/convert_png.h
new file mode 100644
index 000000000..55d3d6942
--- /dev/null
+++ b/tools/gbagfx/convert_png.h
@@ -0,0 +1,11 @@
+// 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);
+
+#endif // CONVERT_PNG_H
diff --git a/tools/gbagfx/gfx.c b/tools/gbagfx/gfx.c
new file mode 100644
index 000000000..71459c607
--- /dev/null
+++ b/tools/gbagfx/gfx.c
@@ -0,0 +1,332 @@
+// 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 ConvertFromTiles1Bpp(unsigned char *src, unsigned char *dest, int numTiles, int tilesWidth, bool invertColors)
+{
+ int tilesX = 0;
+ int tilesY = 0;
+ int pitch = tilesWidth;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int destY = tilesY * 8 + j;
+ int destX = tilesX;
+ 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;
+ }
+ }
+
+ tilesX++;
+
+ if (tilesX == tilesWidth) {
+ tilesX = 0;
+ tilesY++;
+ }
+ }
+}
+
+static void ConvertFromTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int tilesWidth, bool invertColors)
+{
+ int tilesX = 0;
+ int tilesY = 0;
+ int pitch = tilesWidth * 4;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int destY = tilesY * 8 + j;
+
+ for (int k = 0; k < 4; k++) {
+ int destX = tilesX * 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;
+ }
+ }
+
+ tilesX++;
+
+ if (tilesX == tilesWidth) {
+ tilesX = 0;
+ tilesY++;
+ }
+ }
+}
+
+static void ConvertFromTiles8Bpp(unsigned char *src, unsigned char *dest, int numTiles, int tilesWidth, bool invertColors)
+{
+ int tilesX = 0;
+ int tilesY = 0;
+ int pitch = tilesWidth * 8;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int destY = tilesY * 8 + j;
+
+ for (int k = 0; k < 8; k++) {
+ int destX = tilesX * 8 + k;
+ unsigned char srcPixel = *src++;
+
+ if (invertColors)
+ srcPixel = 255 - srcPixel;
+
+ dest[destY * pitch + destX] = srcPixel;
+ }
+ }
+
+ tilesX++;
+
+ if (tilesX == tilesWidth) {
+ tilesX = 0;
+ tilesY++;
+ }
+ }
+}
+
+static void ConvertToTiles1Bpp(unsigned char *src, unsigned char *dest, int numTiles, int tilesWidth, bool invertColors)
+{
+ int tilesX = 0;
+ int tilesY = 0;
+ int pitch = tilesWidth;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int srcY = tilesY * 8 + j;
+ int srcX = tilesX;
+ 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;
+ }
+ }
+
+ tilesX++;
+
+ if (tilesX == tilesWidth) {
+ tilesX = 0;
+ tilesY++;
+ }
+ }
+}
+
+static void ConvertToTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int tilesWidth, bool invertColors)
+{
+ int tilesX = 0;
+ int tilesY = 0;
+ int pitch = tilesWidth * 4;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int srcY = tilesY * 8 + j;
+
+ for (int k = 0; k < 4; k++) {
+ int srcX = tilesX * 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;
+ }
+ }
+
+ tilesX++;
+
+ if (tilesX == tilesWidth) {
+ tilesX = 0;
+ tilesY++;
+ }
+ }
+}
+
+static void ConvertToTiles8Bpp(unsigned char *src, unsigned char *dest, int numTiles, int tilesWidth, bool invertColors)
+{
+ int tilesX = 0;
+ int tilesY = 0;
+ int pitch = tilesWidth * 8;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int srcY = tilesY * 8 + j;
+
+ for (int k = 0; k < 8; k++) {
+ int srcX = tilesX * 8 + k;
+ unsigned char srcPixel = src[srcY * pitch + srcX];
+
+ if (invertColors)
+ srcPixel = 255 - srcPixel;
+
+ *dest++ = srcPixel;
+ }
+ }
+
+ tilesX++;
+
+ if (tilesX == tilesWidth) {
+ tilesX = 0;
+ tilesY++;
+ }
+ }
+}
+
+void ReadImage(char *path, int tilesWidth, int bitDepth, 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;
+
+ 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");
+
+ switch (bitDepth) {
+ case 1:
+ ConvertFromTiles1Bpp(buffer, image->pixels, numTiles, tilesWidth, invertColors);
+ break;
+ case 4:
+ ConvertFromTiles4Bpp(buffer, image->pixels, numTiles, tilesWidth, invertColors);
+ break;
+ case 8:
+ ConvertFromTiles8Bpp(buffer, image->pixels, numTiles, tilesWidth, invertColors);
+ break;
+ }
+
+ free(buffer);
+}
+
+void WriteImage(char *path, int numTiles, int bitDepth, 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;
+
+ 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");
+
+ switch (bitDepth) {
+ case 1:
+ ConvertToTiles1Bpp(image->pixels, buffer, numTiles, tilesWidth, invertColors);
+ break;
+ case 4:
+ ConvertToTiles4Bpp(image->pixels, buffer, numTiles, tilesWidth, invertColors);
+ break;
+ case 8:
+ ConvertToTiles8Bpp(image->pixels, buffer, numTiles, tilesWidth, 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);
+
+ palette->numColors = fileSize / 2;
+
+ if (palette->numColors != 16 && palette->numColors != 256)
+ FATAL_ERROR("\"%s\" contains %d colors, but the number of colors must be 16 or 256.\n", path, palette->numColors);
+
+ 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/gbagfx/gfx.h b/tools/gbagfx/gfx.h
new file mode 100644
index 000000000..473640d44
--- /dev/null
+++ b/tools/gbagfx/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 isObject;
+};
+
+void ReadImage(char *path, int tilesWidth, int bitDepth, struct Image *image, bool invertColors);
+void WriteImage(char *path, int numTiles, int bitDepth, 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/gbagfx/global.h b/tools/gbagfx/global.h
new file mode 100644
index 000000000..3ea5966af
--- /dev/null
+++ b/tools/gbagfx/global.h
@@ -0,0 +1,35 @@
+// 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)
+
+#else
+
+#define FATAL_ERROR(format, ...) \
+do { \
+ fprintf(stderr, format, ##__VA_ARGS__); \
+ exit(1); \
+} while (0)
+
+#endif // _MSC_VER
+
+#define GBAGFX_MAX_PATH 255
+
+#define CHECK_PATH_LENGTH(path) \
+do { \
+ if (strlen(path) > GBAGFX_MAX_PATH) \
+ FATAL_ERROR("\"%s\" is too long a path.\n", path); \
+} while (0)
+
+#endif // GLOBAL_H
diff --git a/tools/gbagfx/jasc_pal.c b/tools/gbagfx/jasc_pal.c
new file mode 100644
index 000000000..02b6ed915
--- /dev/null
+++ b/tools/gbagfx/jasc_pal.c
@@ -0,0 +1,164 @@
+// 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)
+//
+// 16 or 256 times (depending on above line):
+// "<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");
+
+ 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 != 16 && palette->numColors != 256)
+ FATAL_ERROR("%d is an invalid number of colors. The number of colors must be 16 or 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/gbagfx/jasc_pal.h b/tools/gbagfx/jasc_pal.h
new file mode 100644
index 000000000..b60b31fc8
--- /dev/null
+++ b/tools/gbagfx/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/gbagfx/lz.c b/tools/gbagfx/lz.c
new file mode 100644
index 000000000..7669dab9a
--- /dev/null
+++ b/tools/gbagfx/lz.c
@@ -0,0 +1,143 @@
+// 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)
+ return NULL;
+
+ int destSize = (src[3] << 16) | (src[2] << 8) | src[1];
+
+ unsigned char *dest = malloc(destSize);
+
+ if (dest == NULL)
+ return NULL;
+
+ int srcPos = 4;
+ int destPos = 0;
+
+ for (;;) {
+ if (srcPos >= srcSize)
+ return NULL;
+
+ unsigned char flags = src[srcPos++];
+
+ for (int i = 0; i < 8; i++) {
+ if (flags & 0x80) {
+ if (srcPos + 1 >= srcSize)
+ return NULL;
+
+ int blockSize = (src[srcPos] >> 4) + 3;
+ int blockDistance = (((src[srcPos] & 0xF) << 8) | src[srcPos + 1]) + 1;
+
+ srcPos += 2;
+
+ int blockPos = destPos - blockDistance;
+
+ if (destPos + blockSize > destSize || blockPos < 0)
+ return NULL;
+
+ for (int j = 0; j < blockSize; j++)
+ dest[destPos++] = dest[blockPos + j];
+ } else {
+ if (srcPos >= srcSize || destPos >= destSize)
+ return NULL;
+
+ dest[destPos++] = src[srcPos++];
+ }
+
+ if (destPos == destSize) {
+ *uncompressedSize = destSize;
+ return dest;
+ }
+
+ flags <<= 1;
+ }
+ }
+}
+
+unsigned char *LZCompress(unsigned char *src, int srcSize, int *compressedSize)
+{
+ const int minDistance = 2; // for compatibility with LZ77UnCompVram()
+
+ if (srcSize <= 0)
+ return NULL;
+
+ 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)
+ return NULL;
+
+ // 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;
+ }
+ }
+ }
+}
diff --git a/tools/gbagfx/lz.h b/tools/gbagfx/lz.h
new file mode 100644
index 000000000..164d62279
--- /dev/null
+++ b/tools/gbagfx/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);
+
+#endif // LZ_H
diff --git a/tools/gbagfx/main.c b/tools/gbagfx/main.c
new file mode 100644
index 000000000..8a750f03a
--- /dev/null
+++ b/tools/gbagfx/main.c
@@ -0,0 +1,260 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include "global.h"
+#include "util.h"
+#include "gfx.h"
+#include "convert_png.h"
+#include "jasc_pal.h"
+#include "lz.h"
+
+void ConvertToPng(char *imageFilePath, char *paletteFilePath, bool isObject, int width)
+{
+ struct Image image;
+ int bitDepth = 0;
+
+ char *extension = GetFileExtension(imageFilePath);
+
+ if (extension == NULL)
+ FATAL_ERROR("\"%s\" has no file extension.\n", imageFilePath);
+
+ if (strcmp(extension, "1bpp") == 0)
+ bitDepth = 1;
+ else if (strcmp(extension, "4bpp") == 0)
+ bitDepth = 4;
+ else if (strcmp(extension, "8bpp") == 0)
+ bitDepth = 8;
+ else
+ FATAL_ERROR("Unexpected file extension \"%s\". Expected \"1bpp\", \"4bpp\", or \"8bpp\".\n", extension);
+
+ if (paletteFilePath != NULL) {
+ ReadGbaPalette(paletteFilePath, &image.palette);
+ image.hasPalette = true;
+ } else {
+ image.hasPalette = false;
+ }
+
+ ReadImage(imageFilePath, width, bitDepth, &image, !image.hasPalette);
+
+ image.isObject = isObject;
+
+ ChangeFileExtension(imageFilePath, "png");
+
+ WritePng(imageFilePath, &image);
+
+ FreeImage(&image);
+}
+
+void ConvertFromPng(char *imageFilePath, int numTiles, int bitDepth)
+{
+ struct Image image;
+
+ image.bitDepth = bitDepth;
+
+ ExpectFileExtension(imageFilePath, "png");
+
+ ReadPng(imageFilePath, &image);
+
+ char newExtension[5];
+ snprintf(newExtension, 5, "%dbpp", bitDepth);
+ ChangeFileExtension(imageFilePath, newExtension);
+
+ WriteImage(imageFilePath, numTiles, bitDepth, &image, !image.hasPalette);
+
+ FreeImage(&image);
+}
+
+void ConvertToJascPalette(char *paletteFilePath)
+{
+ struct Palette palette;
+
+ ExpectFileExtension(paletteFilePath, "gbapal");
+
+ ReadGbaPalette(paletteFilePath, &palette);
+
+ ChangeFileExtension(paletteFilePath, "pal");
+
+ WriteJascPalette(paletteFilePath, &palette);
+}
+
+void ConvertFromJascPalette(char *paletteFilePath)
+{
+ struct Palette palette;
+
+ ExpectFileExtension(paletteFilePath, "pal");
+
+ ReadJascPalette(paletteFilePath, &palette);
+
+ ChangeFileExtension(paletteFilePath, "gbapal");
+
+ WriteGbaPalette(paletteFilePath, &palette);
+}
+
+void LZCompressFile(char *path)
+{
+ int fileSize;
+ unsigned char *buffer = ReadWholeFile(path, &fileSize);
+
+ int compressedSize;
+ unsigned char *compressedData = LZCompress(buffer, fileSize, &compressedSize);
+
+ free(buffer);
+
+ AddFileExtension(path, "lz");
+
+ WriteWholeFile(path, compressedData, compressedSize);
+
+ free(compressedData);
+}
+
+void LZDecompressFile(char *path)
+{
+ ExpectFileExtension(path, "lz");
+
+ int fileSize;
+ unsigned char *buffer = ReadWholeFile(path, &fileSize);
+
+ int uncompressedSize;
+ unsigned char *uncompressedData = LZDecompress(buffer, fileSize, &uncompressedSize);
+
+ free(buffer);
+
+ RemoveFileExtension(path);
+
+ WriteWholeFile(path, uncompressedData, uncompressedSize);
+
+ free(uncompressedData);
+}
+
+int main(int argc, char **argv)
+{
+ if (argc < 2)
+ FATAL_ERROR("No args.\n");
+
+ char *command = argv[1];
+
+ if (strcmp(command, "png") == 0) {
+ if (argc < 3)
+ FATAL_ERROR("No image file path arg.\n");
+
+ CHECK_PATH_LENGTH(argv[2]);
+
+ char imageFilePath[GBAGFX_MAX_PATH + 1];
+ strcpy(imageFilePath, argv[2]);
+
+ char paletteFilePath[GBAGFX_MAX_PATH + 1];
+ bool hasPalette = false;
+ bool isObject = false;
+ int width = 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++;
+
+ CHECK_PATH_LENGTH(argv[i]);
+
+ strcpy(paletteFilePath, argv[i]);
+
+ hasPalette = true;
+ } else if (strcmp(option, "-object") == 0) {
+ isObject = 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, &width))
+ FATAL_ERROR("Failed to parse width.\n");
+
+ if (width < 1)
+ FATAL_ERROR("Width must be positive.\n");
+ } else {
+ FATAL_ERROR("Unrecognized option \"%s\".\n", option);
+ }
+ }
+
+ ConvertToPng(imageFilePath, hasPalette ? paletteFilePath : NULL, isObject, width);
+ } else if (strcmp(command, "1bpp") == 0 || strcmp(command, "4bpp") == 0 || strcmp(command, "8bpp") == 0) {
+ if (argc < 3)
+ FATAL_ERROR("No image file path arg.\n");
+
+ CHECK_PATH_LENGTH(argv[2]);
+
+ char imageFilePath[GBAGFX_MAX_PATH + 1];
+ strcpy(imageFilePath, argv[2]);
+
+ int numTiles = 0;
+ int bitDepth = command[0] - '0';
+
+ 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, &numTiles))
+ FATAL_ERROR("Failed to parse number of tiles.\n");
+
+ if (numTiles < 1)
+ FATAL_ERROR("Number of tiles must be positive.\n");
+ } else {
+ FATAL_ERROR("Unrecognized option \"%s\".\n", option);
+ }
+ }
+
+ ConvertFromPng(imageFilePath, numTiles, bitDepth);
+ } else if (strcmp(command, "pal") == 0) {
+ if (argc < 3)
+ FATAL_ERROR("No palette file path arg.\n");
+
+ CHECK_PATH_LENGTH(argv[2]);
+
+ char paletteFilePath[GBAGFX_MAX_PATH + 1];
+ strcpy(paletteFilePath, argv[2]);
+
+ ConvertToJascPalette(paletteFilePath);
+ } else if (strcmp(command, "gbapal") == 0) {
+ if (argc < 3)
+ FATAL_ERROR("No palette file path arg.\n");
+
+ CHECK_PATH_LENGTH(argv[2]);
+
+ char paletteFilePath[GBAGFX_MAX_PATH + 1];
+ strcpy(paletteFilePath, argv[2]);
+
+ ConvertFromJascPalette(paletteFilePath);
+ } else if (strcmp(command, "lz") == 0) {
+ if (argc < 3)
+ FATAL_ERROR("No file path arg.\n");
+
+ CHECK_PATH_LENGTH(argv[2]);
+
+ char path[GBAGFX_MAX_PATH + 1];
+ strcpy(path, argv[2]);
+
+ LZCompressFile(path);
+ } else if (strcmp(command, "unlz") == 0) {
+ if (argc < 3)
+ FATAL_ERROR("No file path arg.\n");
+
+ CHECK_PATH_LENGTH(argv[2]);
+
+ char path[GBAGFX_MAX_PATH + 1];
+ strcpy(path, argv[2]);
+
+ LZDecompressFile(path);
+ } else {
+ FATAL_ERROR("Unrecognized command \"%s\".\n", command);
+ }
+
+ return 0;
+}
diff --git a/tools/gbagfx/util.c b/tools/gbagfx/util.c
new file mode 100644
index 000000000..180b93d9b
--- /dev/null
+++ b/tools/gbagfx/util.c
@@ -0,0 +1,140 @@
+// 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;
+}
+
+void ExpectFileExtension(char *path, char *expectedExtension)
+{
+ char *extension = GetFileExtension(path);
+
+ if (extension == NULL)
+ FATAL_ERROR("\"%s\" has no file extension.\n", path);
+
+ if (strcmp(extension, expectedExtension) != 0)
+ FATAL_ERROR("Unexpected file extension \"%s\". Expected \"%s\".\n", extension, expectedExtension);
+}
+
+void AddFileExtension(char *path, char *extension)
+{
+ int pathLength = strlen(path);
+ int extensionLength = strlen(extension);
+
+ if (pathLength + 1 + extensionLength > GBAGFX_MAX_PATH)
+ FATAL_ERROR("\"%s\" is too long a path to add the extension \"%s\".\n", path, extension);
+
+ path[pathLength] = '.';
+ memcpy(path + pathLength + 1, extension, extensionLength);
+ path[pathLength + 1 + extensionLength] = 0;
+}
+
+void RemoveFileExtension(char *path)
+{
+ char *extension = GetFileExtension(path);
+
+ if (extension == NULL)
+ FATAL_ERROR("\"%s\" doesn't have a file extension.\n", path);
+
+ extension--;
+
+ *extension = 0;
+}
+
+void ChangeFileExtension(char *path, char *extension)
+{
+ RemoveFileExtension(path);
+ AddFileExtension(path, 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;
+}
+
+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/gbagfx/util.h b/tools/gbagfx/util.h
new file mode 100644
index 000000000..ec81471c0
--- /dev/null
+++ b/tools/gbagfx/util.h
@@ -0,0 +1,17 @@
+// 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);
+void ExpectFileExtension(char *path, char *expectedExtension);
+void AddFileExtension(char *path, char *extension);
+void RemoveFileExtension(char *path);
+void ChangeFileExtension(char *path, char *extension);
+unsigned char *ReadWholeFile(char *path, int *size);
+void WriteWholeFile(char *path, void *buffer, int bufferSize);
+
+#endif // UTIL_H
diff --git a/tools/scaninc/.gitignore b/tools/scaninc/.gitignore
new file mode 100644
index 000000000..94bfbf989
--- /dev/null
+++ b/tools/scaninc/.gitignore
@@ -0,0 +1 @@
+scaninc
diff --git a/tools/scaninc/Makefile b/tools/scaninc/Makefile
new file mode 100644
index 000000000..0efe283f1
--- /dev/null
+++ b/tools/scaninc/Makefile
@@ -0,0 +1,13 @@
+CXX = g++
+
+CXXFLAGS = -Wall -std=c++11 -O2
+
+SRCS = scaninc.cpp
+
+.PHONY: clean
+
+scaninc: $(SRCS)
+ $(CXX) $(CXXFLAGS) $(SRCS) -o $@
+
+clean:
+ $(RM) scaninc scaninc.exe
diff --git a/tools/scaninc/scaninc.cpp b/tools/scaninc/scaninc.cpp
new file mode 100644
index 000000000..fa5ab87b8
--- /dev/null
+++ b/tools/scaninc/scaninc.cpp
@@ -0,0 +1,305 @@
+#include <cstdio>
+#include <cstdlib>
+#include <stack>
+#include <set>
+#include <string>
+
+#ifdef _MSC_VER
+
+#define FATAL_INPUT_ERROR(format, ...) \
+do { \
+ fprintf(stderr, "%s:%d " format, m_path.c_str(), m_lineNum, __VA_ARGS__); \
+ exit(1); \
+} while (0)
+
+#define FATAL_ERROR(format, ...) \
+do { \
+ fprintf(stderr, format, __VA_ARGS__); \
+ exit(1); \
+} while (0)
+
+#else
+
+#define FATAL_INPUT_ERROR(format, ...) \
+do { \
+ fprintf(stderr, "%s:%d " format, m_path.c_str(), m_lineNum, ##__VA_ARGS__); \
+ exit(1); \
+} while (0)
+
+#define FATAL_ERROR(format, ...) \
+do { \
+ fprintf(stderr, format, ##__VA_ARGS__); \
+ exit(1); \
+} while (0)
+
+#endif // _MSC_VER
+
+#define SCANINC_MAX_PATH 255
+
+enum class IncDirectiveType {
+ None,
+ Include,
+ Incbin
+};
+
+class AsmFile
+{
+public:
+ AsmFile(std::string path);
+ ~AsmFile();
+ IncDirectiveType ReadUntilIncDirective(std::string &path);
+
+private:
+ char *m_buffer;
+ int m_pos;
+ int m_size;
+ int m_lineNum;
+ std::string m_path;
+
+ int GetChar()
+ {
+ if (m_pos >= m_size)
+ return -1;
+
+ int c = m_buffer[m_pos++];
+
+ if (c == '\r') {
+ if (m_pos < m_size && m_buffer[m_pos++] == '\n') {
+ m_lineNum++;
+ return '\n';
+ } else {
+ FATAL_INPUT_ERROR("CR line endings are not supported\n");
+ }
+ }
+
+ if (c == '\n')
+ m_lineNum++;
+
+ return c;
+ }
+
+ // No newline translation because it's not needed for any use of this function.
+ int PeekChar()
+ {
+ if (m_pos >= m_size)
+ return -1;
+
+ return m_buffer[m_pos];
+ }
+
+ void SkipTabsAndSpaces()
+ {
+ while (m_pos < m_size && (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' '))
+ m_pos++;
+ }
+
+ bool MatchIncDirective(std::string directiveName, std::string &path)
+ {
+ int length = directiveName.length();
+ int i;
+
+ for (i = 0; i < length && m_pos + i < m_size; i++)
+ if (directiveName[i] != m_buffer[m_pos + i])
+ return false;
+
+ if (i < length)
+ return false;
+
+ m_pos += length;
+
+ SkipTabsAndSpaces();
+
+ if (GetChar() != '"')
+ FATAL_INPUT_ERROR("no path after \".%s\" directive\n", directiveName.c_str());
+
+ path = ReadPath();
+
+ return true;
+ }
+
+ std::string ReadPath();
+ void SkipEndOfLineComment();
+ void SkipMultiLineComment();
+ void SkipString();
+};
+
+AsmFile::AsmFile(std::string path)
+{
+ m_path = path;
+
+ FILE *fp = fopen(path.c_str(), "rb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for reading.\n", path.c_str());
+
+ fseek(fp, 0, SEEK_END);
+
+ m_size = ftell(fp);
+
+ m_buffer = new char[m_size];
+
+ rewind(fp);
+
+ if (fread(m_buffer, m_size, 1, fp) != 1)
+ FATAL_ERROR("Failed to read \"%s\".\n", path.c_str());
+
+ fclose(fp);
+
+ m_pos = 0;
+ m_lineNum = 1;
+}
+
+AsmFile::~AsmFile()
+{
+ delete[] m_buffer;
+}
+
+IncDirectiveType AsmFile::ReadUntilIncDirective(std::string &path)
+{
+ // At the beginning of each loop iteration, the current file position
+ // should be at the start of a line or at the end of the file.
+ for (;;) {
+ SkipTabsAndSpaces();
+
+ IncDirectiveType incDirectiveType = IncDirectiveType::None;
+
+ if (PeekChar() == '.') {
+ m_pos++;
+
+ if (MatchIncDirective("incbin", path))
+ incDirectiveType = IncDirectiveType::Incbin;
+ else if (MatchIncDirective("include", path))
+ incDirectiveType = IncDirectiveType::Include;
+ }
+
+ for (;;) {
+ int c = GetChar();
+
+ if (c == -1)
+ return incDirectiveType;
+
+ if (c == ';') {
+ SkipEndOfLineComment();
+ break;
+ } else if (c == '/' && PeekChar() == '*') {
+ m_pos++;
+ SkipMultiLineComment();
+ } else if (c == '"') {
+ SkipString();
+ } else if (c == '\n') {
+ break;
+ }
+ }
+
+ if (incDirectiveType != IncDirectiveType::None)
+ return incDirectiveType;
+ }
+}
+
+std::string AsmFile::ReadPath()
+{
+ int length = 0;
+ int startPos = m_pos;
+
+ for (;;) {
+ int c = GetChar();
+
+ if (c == '"')
+ break;
+
+ if (c == -1)
+ FATAL_INPUT_ERROR("unexpected EOF in include string\n");
+
+ if (c == 0)
+ FATAL_INPUT_ERROR("unexpected NUL character in include string\n");
+
+ if (c == '\n')
+ FATAL_INPUT_ERROR("unexpected end of line character in include string\n");
+
+ if (c == '\\') {
+ c = GetChar();
+
+ if (c != '"')
+ FATAL_INPUT_ERROR("unknown escape \"\\%c\" in include string\n", c);
+ }
+
+ length++;
+
+ if (length > SCANINC_MAX_PATH)
+ FATAL_INPUT_ERROR("path is too long");
+ }
+
+ return std::string(m_buffer, startPos, length);
+}
+
+void AsmFile::SkipEndOfLineComment()
+{
+ int c;
+
+ do {
+ c = GetChar();
+ } while (c != -1 && c != '\n');
+}
+
+void AsmFile::SkipMultiLineComment()
+{
+ for (;;) {
+ int c = GetChar();
+
+ if (c == '*') {
+ if (PeekChar() == '/') {
+ m_pos++;
+ return;
+ }
+ } else if (c == -1) {
+ return;
+ }
+ }
+}
+
+void AsmFile::SkipString()
+{
+ for (;;) {
+ int c = GetChar();
+
+ if (c == '"')
+ break;
+
+ if (c == -1)
+ FATAL_INPUT_ERROR("unexpected EOF in string\n");
+
+ if (c == '\\') {
+ c = GetChar();
+ }
+ }
+}
+
+int main(int argc, char **argv)
+{
+ if (argc < 2)
+ FATAL_ERROR("Usage: scaninc ASM_FILE_PATH\n");
+
+ std::stack<std::string> filesToProcess;
+ std::set<std::string> dependencies;
+
+ filesToProcess.push(std::string(argv[1]));
+
+ while (!filesToProcess.empty()) {
+ AsmFile file(filesToProcess.top());
+
+ filesToProcess.pop();
+
+ IncDirectiveType incDirectiveType;
+ std::string path;
+
+ while ((incDirectiveType = file.ReadUntilIncDirective(path)) != IncDirectiveType::None) {
+ bool inserted = dependencies.insert(path).second;
+ if (inserted && incDirectiveType == IncDirectiveType::Include)
+ filesToProcess.push(path);
+ }
+ }
+
+ for (const std::string &path : dependencies) {
+ printf("%s\n", path.c_str());
+ }
+}