pokemon PNGs and palettes
This commit is contained in:
1
tools/gbagfx/.gitignore
vendored
Normal file
1
tools/gbagfx/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
gbagfx
|
||||
15
tools/gbagfx/Makefile
Normal file
15
tools/gbagfx/Makefile
Normal file
@@ -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
|
||||
167
tools/gbagfx/convert_png.c
Normal file
167
tools/gbagfx/convert_png.c
Normal file
@@ -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);
|
||||
}
|
||||
11
tools/gbagfx/convert_png.h
Normal file
11
tools/gbagfx/convert_png.h
Normal file
@@ -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
|
||||
332
tools/gbagfx/gfx.c
Normal file
332
tools/gbagfx/gfx.c
Normal file
@@ -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);
|
||||
}
|
||||
36
tools/gbagfx/gfx.h
Normal file
36
tools/gbagfx/gfx.h
Normal file
@@ -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
|
||||
35
tools/gbagfx/global.h
Normal file
35
tools/gbagfx/global.h
Normal file
@@ -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
|
||||
164
tools/gbagfx/jasc_pal.c
Normal file
164
tools/gbagfx/jasc_pal.c
Normal file
@@ -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);
|
||||
}
|
||||
9
tools/gbagfx/jasc_pal.h
Normal file
9
tools/gbagfx/jasc_pal.h
Normal file
@@ -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
|
||||
143
tools/gbagfx/lz.c
Normal file
143
tools/gbagfx/lz.c
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
tools/gbagfx/lz.h
Normal file
9
tools/gbagfx/lz.h
Normal file
@@ -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
|
||||
260
tools/gbagfx/main.c
Normal file
260
tools/gbagfx/main.c
Normal file
@@ -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;
|
||||
}
|
||||
140
tools/gbagfx/util.c
Normal file
140
tools/gbagfx/util.c
Normal file
@@ -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);
|
||||
}
|
||||
17
tools/gbagfx/util.h
Normal file
17
tools/gbagfx/util.h
Normal file
@@ -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
|
||||
1
tools/scaninc/.gitignore
vendored
Normal file
1
tools/scaninc/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
scaninc
|
||||
13
tools/scaninc/Makefile
Normal file
13
tools/scaninc/Makefile
Normal file
@@ -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
|
||||
305
tools/scaninc/scaninc.cpp
Normal file
305
tools/scaninc/scaninc.cpp
Normal file
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user