Track tools
This commit is contained in:
@@ -0,0 +1 @@
|
||||
gbagfx
|
||||
@@ -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.
|
||||
@@ -0,0 +1,18 @@
|
||||
CC = gcc
|
||||
|
||||
CFLAGS = -Wall -Wextra -Werror -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
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: gbagfx
|
||||
@:
|
||||
|
||||
gbagfx: $(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) gbagfx gbagfx.exe
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,535 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.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 inline void swap_bytes(unsigned char * orig, unsigned char * dest) {
|
||||
unsigned char tmp = *orig;
|
||||
*orig = *dest;
|
||||
*dest = tmp;
|
||||
}
|
||||
|
||||
static inline unsigned char swap_nybbles(unsigned char orig)
|
||||
{
|
||||
return (orig >> 4) | (orig << 4);
|
||||
}
|
||||
|
||||
static inline void swap_bytes_nybswap(unsigned char * orig, unsigned char * dest) {
|
||||
unsigned char tmp = swap_nybbles(*orig);
|
||||
*orig = swap_nybbles(*dest);
|
||||
*dest = tmp;
|
||||
}
|
||||
|
||||
static inline unsigned char reverse_bits(unsigned char orig) {
|
||||
unsigned char dest = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
dest <<= 1;
|
||||
dest |= orig & 1;
|
||||
orig >>= 1;
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
static void vflip(unsigned char * tile, int bitDepth) {
|
||||
for (int x = 0; x < bitDepth; x++)
|
||||
{
|
||||
unsigned char * col = tile + x;
|
||||
swap_bytes(col + 0 * bitDepth, col + 7 * bitDepth);
|
||||
swap_bytes(col + 1 * bitDepth, col + 6 * bitDepth);
|
||||
swap_bytes(col + 2 * bitDepth, col + 5 * bitDepth);
|
||||
swap_bytes(col + 3 * bitDepth, col + 4 * bitDepth);
|
||||
}
|
||||
}
|
||||
|
||||
static void hflip(unsigned char * tile, int bitDepth) {
|
||||
for (int y = 0; y < 8; y++)
|
||||
{
|
||||
unsigned char * row = tile + y * bitDepth;
|
||||
switch (bitDepth)
|
||||
{
|
||||
case 1:
|
||||
*row = reverse_bits(*row);
|
||||
break;
|
||||
case 4:
|
||||
swap_bytes_nybswap(row + 0, row + 3);
|
||||
swap_bytes_nybswap(row + 1, row + 2);
|
||||
break;
|
||||
case 8:
|
||||
swap_bytes(row + 0, row + 56);
|
||||
swap_bytes(row + 8, row + 48);
|
||||
swap_bytes(row + 16, row + 40);
|
||||
swap_bytes(row + 24, row + 32);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned char * ApplyTilemap(struct Image *image, unsigned char * buffer, int bitDepth)
|
||||
{
|
||||
int tileSize = bitDepth * 8;
|
||||
unsigned char * tiles = calloc(image->tileMap.numTiles, tileSize);
|
||||
int i;
|
||||
struct Tile tileInfo;
|
||||
|
||||
for (i = 0; i < image->tileMap.numTiles; i++) {
|
||||
tileInfo = image->tileMap.data[i];
|
||||
unsigned char * tile = tiles + i * tileSize;
|
||||
memcpy(tile, buffer + tileInfo.index * tileSize, tileSize);
|
||||
if (tileInfo.xflip)
|
||||
hflip(tile, bitDepth);
|
||||
if (tileInfo.yflip)
|
||||
vflip(tile, bitDepth);
|
||||
}
|
||||
free(buffer);
|
||||
return tiles;
|
||||
}
|
||||
|
||||
static unsigned char * BuildTilemap(struct Image *image, unsigned char * buffer, int * bufferSize)
|
||||
{
|
||||
int tileSize = image->bitDepth * 8;
|
||||
unsigned char * outputPixels = calloc(1024, tileSize);
|
||||
int nTilesIn = image->height * image->width / 64;
|
||||
image->tileMap.data = calloc(nTilesIn, sizeof(struct Tilemap));
|
||||
image->tileMap.numTiles = nTilesIn;
|
||||
int nTilesOut = 0;
|
||||
unsigned char curTile1[tileSize];
|
||||
unsigned char curTile2[tileSize];
|
||||
|
||||
for (int i = 0; i < nTilesIn; i++) {
|
||||
bool xflip = false;
|
||||
bool yflip = false;
|
||||
int j;
|
||||
memcpy(curTile1, buffer + i * tileSize, tileSize);
|
||||
|
||||
for (j = 0; j < nTilesOut; j++) {
|
||||
memcpy(curTile2, outputPixels + j * tileSize, tileSize);
|
||||
if (memcmp(curTile1, curTile2, tileSize) == 0)
|
||||
break;
|
||||
xflip = true;
|
||||
hflip(curTile2, image->bitDepth);
|
||||
if (memcmp(curTile1, curTile2, tileSize) == 0)
|
||||
break;
|
||||
yflip = true;
|
||||
vflip(curTile2, image->bitDepth);
|
||||
if (memcmp(curTile1, curTile2, tileSize) == 0)
|
||||
break;
|
||||
xflip = false;
|
||||
hflip(curTile2, image->bitDepth);
|
||||
if (memcmp(curTile1, curTile2, tileSize) == 0)
|
||||
break;
|
||||
yflip = false;
|
||||
}
|
||||
image->tileMap.data[i].index = j;
|
||||
image->tileMap.data[i].xflip = xflip;
|
||||
image->tileMap.data[i].yflip = yflip;
|
||||
image->tileMap.data[i].palno = 0;
|
||||
if (j >= nTilesOut) {
|
||||
if (nTilesOut >= 1024)
|
||||
FATAL_ERROR("Cannot reduce image to 1024 or fewer tiles.\n");
|
||||
memcpy(outputPixels + nTilesOut * tileSize, curTile1, tileSize);
|
||||
nTilesOut++;
|
||||
}
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
*bufferSize = nTilesOut * tileSize;
|
||||
return outputPixels;
|
||||
}
|
||||
|
||||
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;
|
||||
if (image->hasTilemap) {
|
||||
buffer = ApplyTilemap(image, buffer, bitDepth);
|
||||
numTiles = image->tileMap.numTiles;
|
||||
}
|
||||
else
|
||||
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)\n", tilesWidth, metatileWidth);
|
||||
|
||||
if (tilesHeight % metatileHeight != 0)
|
||||
FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)\n", 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;
|
||||
}
|
||||
|
||||
if (image->hasTilemap)
|
||||
buffer = BuildTilemap(image, buffer, &bufferSize);
|
||||
|
||||
WriteWholeFile(path, buffer, bufferSize);
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
void FreeImage(struct Image *image)
|
||||
{
|
||||
free(image->pixels);
|
||||
image->pixels = NULL;
|
||||
if (image->hasTilemap && image->tileMap.data != NULL) {
|
||||
free(image->tileMap.data);
|
||||
image->tileMap.data = NULL;
|
||||
image->tileMap.numTiles = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void ReadGbaTilemap(char *path, struct Tilemap *tileMap)
|
||||
{
|
||||
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);
|
||||
|
||||
tileMap->numTiles = fileSize / 2;
|
||||
tileMap->data = malloc(tileMap->numTiles * sizeof(struct Tile));
|
||||
|
||||
for (int i = 0; i < tileMap->numTiles; i++)
|
||||
{
|
||||
uint16_t raw = data[2 * i + 0] | (data[2 * i + 1] << 8);
|
||||
tileMap->data[i].index = raw & 0x3FF;
|
||||
tileMap->data[i].xflip = raw & 0x400 ? 1 : 0;
|
||||
tileMap->data[i].yflip = raw & 0x800 ? 1 : 0;
|
||||
tileMap->data[i].palno = raw >> 12;
|
||||
}
|
||||
|
||||
free(data);
|
||||
}
|
||||
|
||||
void WriteGbaTilemap(char *path, struct Tilemap *tileMap)
|
||||
{
|
||||
FILE *fp = fopen(path, "wb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
|
||||
|
||||
for (int i = 0; i < tileMap->numTiles; i++) {
|
||||
uint16_t raw = tileMap->data[i].index
|
||||
| (tileMap->data[i].xflip << 10)
|
||||
| (tileMap->data[i].yflip << 11)
|
||||
| (tileMap->data[i].palno << 12);
|
||||
fputc(raw & 0xFF, fp);
|
||||
fputc(raw >> 8, fp);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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 __attribute__((packed)) Tile {
|
||||
unsigned short index:10;
|
||||
unsigned short xflip:1;
|
||||
unsigned short yflip:1;
|
||||
unsigned short palno:4;
|
||||
};
|
||||
|
||||
struct Tilemap {
|
||||
struct Tile *data;
|
||||
int numTiles;
|
||||
};
|
||||
|
||||
struct Image {
|
||||
int width;
|
||||
int height;
|
||||
int bitDepth;
|
||||
unsigned char *pixels;
|
||||
bool hasPalette;
|
||||
struct Palette palette;
|
||||
bool hasTransparency;
|
||||
struct Tilemap tileMap;
|
||||
bool hasTilemap;
|
||||
|
||||
};
|
||||
|
||||
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);
|
||||
void ReadGbaTilemap(char *path, struct Tilemap *tileMap);
|
||||
void WriteGbaTilemap(char *path, struct Tilemap *tileMap);
|
||||
|
||||
#endif // GFX_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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,539 @@
|
||||
// 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"
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (options->tilemapFilePath != NULL)
|
||||
{
|
||||
ReadGbaTilemap(options->tilemapFilePath, &image.tileMap);
|
||||
image.hasTilemap = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
image.tileMap.data = NULL;
|
||||
image.hasTilemap = 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;
|
||||
image.hasTilemap = options->tilemapFilePath == NULL ? false : true;
|
||||
image.tileMap.data = NULL;
|
||||
image.tileMap.numTiles = 0;
|
||||
|
||||
ReadPng(inputPath, &image);
|
||||
|
||||
WriteImage(outputPath, options->numTiles, options->bitDepth, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette);
|
||||
|
||||
if (image.hasTilemap)
|
||||
WriteGbaTilemap(options->tilemapFilePath, &image.tileMap);
|
||||
|
||||
FreeImage(&image);
|
||||
}
|
||||
|
||||
void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **argv)
|
||||
{
|
||||
char *inputFileExtension = GetFileExtension(inputPath);
|
||||
struct GbaToPngOptions options;
|
||||
options.paletteFilePath = NULL;
|
||||
options.tilemapFilePath = 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, "-tilemap") == 0)
|
||||
{
|
||||
if (i + 1 >= argc)
|
||||
FATAL_ERROR("No tilemap file path following \"-tilemap\".\n");
|
||||
|
||||
i++;
|
||||
|
||||
options.tilemapFilePath = 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;
|
||||
options.tilemapFilePath = NULL;
|
||||
|
||||
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, "-tilemap") == 0)
|
||||
{
|
||||
if (i + 1 >= argc)
|
||||
FATAL_ERROR("No tilemap path following \"-tilemap\".\n");
|
||||
|
||||
i++;
|
||||
options.tilemapFilePath = argv[i];
|
||||
}
|
||||
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;
|
||||
|
||||
image.hasTilemap = false;
|
||||
image.tileMap.data = NULL;
|
||||
image.tileMap.numTiles = 0;
|
||||
|
||||
ReadLatinFont(inputPath, &image);
|
||||
WritePng(outputPath, &image);
|
||||
|
||||
FreeImage(&image);
|
||||
}
|
||||
|
||||
void HandlePngToLatinFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Image image;
|
||||
|
||||
image.hasTilemap = false;
|
||||
image.tileMap.data = NULL;
|
||||
image.tileMap.numTiles = 0;
|
||||
|
||||
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;
|
||||
|
||||
image.hasTilemap = false;
|
||||
image.tileMap.data = NULL;
|
||||
image.tileMap.numTiles = 0;
|
||||
|
||||
ReadHalfwidthJapaneseFont(inputPath, &image);
|
||||
WritePng(outputPath, &image);
|
||||
|
||||
FreeImage(&image);
|
||||
}
|
||||
|
||||
void HandlePngToHalfwidthJapaneseFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Image image;
|
||||
|
||||
image.hasTilemap = false;
|
||||
image.tileMap.data = NULL;
|
||||
image.tileMap.numTiles = 0;
|
||||
|
||||
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;
|
||||
|
||||
image.hasTilemap = false;
|
||||
image.tileMap.data = NULL;
|
||||
image.tileMap.numTiles = 0;
|
||||
|
||||
ReadFullwidthJapaneseFont(inputPath, &image);
|
||||
WritePng(outputPath, &image);
|
||||
|
||||
FreeImage(&image);
|
||||
}
|
||||
|
||||
void HandlePngToFullwidthJapaneseFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Image image;
|
||||
|
||||
image.hasTilemap = false;
|
||||
image.tileMap.data = NULL;
|
||||
image.tileMap.numTiles = 0;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 3)
|
||||
FATAL_ERROR("Usage: gbagfx 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, "lz", HandleLZCompressCommand },
|
||||
{ "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);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2018 huderlem
|
||||
|
||||
#ifndef OPTIONS_H
|
||||
#define OPTIONS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct GbaToPngOptions {
|
||||
char *paletteFilePath;
|
||||
char *tilemapFilePath;
|
||||
int bitDepth;
|
||||
bool hasTransparency;
|
||||
int width;
|
||||
int metatileWidth;
|
||||
int metatileHeight;
|
||||
};
|
||||
|
||||
struct PngToGbaOptions {
|
||||
char *tilemapFilePath;
|
||||
int numTiles;
|
||||
int bitDepth;
|
||||
int metatileWidth;
|
||||
int metatileHeight;
|
||||
};
|
||||
|
||||
#endif // OPTIONS_H
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user