diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/gbafix/gbafix.c | 2 | ||||
-rw-r--r-- | tools/gbagfx/Makefile | 7 | ||||
-rw-r--r-- | tools/gbagfx/gfx.c | 197 | ||||
-rw-r--r-- | tools/gbagfx/gfx.h | 17 | ||||
-rw-r--r-- | tools/gbagfx/huff.c | 398 | ||||
-rw-r--r-- | tools/gbagfx/huff.h | 38 | ||||
-rw-r--r-- | tools/gbagfx/main.c | 118 | ||||
-rw-r--r-- | tools/gbagfx/options.h | 2 | ||||
-rwxr-xr-x | tools/mapjson/.gitignore | 1 | ||||
-rw-r--r-- | tools/mapjson/Makefile | 15 | ||||
-rw-r--r-- | tools/mapjson/json11.cpp | 786 | ||||
-rw-r--r-- | tools/mapjson/json11.h | 230 | ||||
-rw-r--r-- | tools/mapjson/mapjson.cpp | 538 | ||||
-rw-r--r-- | tools/mapjson/mapjson.h | 31 | ||||
-rw-r--r-- | tools/scaninc/Makefile | 4 | ||||
-rw-r--r-- | tools/scaninc/asm_file.cpp | 3 | ||||
-rw-r--r-- | tools/scaninc/c_file.cpp | 2 | ||||
-rw-r--r-- | tools/scaninc/scaninc.cpp | 88 | ||||
-rw-r--r-- | tools/scaninc/source_file.cpp | 125 | ||||
-rw-r--r-- | tools/scaninc/source_file.h | 71 |
20 files changed, 2327 insertions, 346 deletions
diff --git a/tools/gbafix/gbafix.c b/tools/gbafix/gbafix.c index d5e2f62ab..81c8c04c0 100644 --- a/tools/gbafix/gbafix.c +++ b/tools/gbafix/gbafix.c @@ -160,7 +160,7 @@ int main(int argc, char *argv[]) // get filename for (arg=1; arg<argc; arg++) { - if ((ARGV[0] != '-')) { argfile=ARGV; } + if (ARGV[0] != '-') { argfile=ARGV; } if (strncmp("--silent", &ARGV[0], 7) == 0) { silent = 1; } } diff --git a/tools/gbagfx/Makefile b/tools/gbagfx/Makefile index 339585b92..93bea4bdc 100644 --- a/tools/gbagfx/Makefile +++ b/tools/gbagfx/Makefile @@ -1,16 +1,19 @@ CC = gcc -CFLAGS = -Wall -Wextra -Werror -std=c11 -O2 -DPNG_SKIP_SETJMP_CHECK +CFLAGS = -Wall -Wextra -Werror -Wno-sign-compare -std=c11 -O2 -s -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 +SRCS = main.c convert_png.c gfx.c jasc_pal.c lz.c rl.c util.c font.c huff.c .PHONY: all clean all: gbagfx @: +gbagfx-debug: $(SRCS) convert_png.h gfx.h global.h jasc_pal.h lz.h rl.h util.h font.h + $(CC) $(CFLAGS) -DDEBUG $(SRCS) -o $@ $(LDFLAGS) $(LIBS) + 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) diff --git a/tools/gbagfx/gfx.c b/tools/gbagfx/gfx.c index da92771b9..f927deed9 100644 --- a/tools/gbagfx/gfx.c +++ b/tools/gbagfx/gfx.c @@ -4,7 +4,6 @@ #include <stdlib.h> #include <stdint.h> #include <stdbool.h> -#include <string.h> #include "global.h" #include "gfx.h" #include "util.h" @@ -19,140 +18,6 @@ #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)++; @@ -345,21 +210,15 @@ void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int 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 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); + 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)\n", tilesHeight, metatileHeight); + FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)", tilesHeight, metatileHeight); image->width = tilesWidth * 8; image->height = tilesHeight * 8; @@ -432,9 +291,6 @@ void WriteImage(char *path, int numTiles, int bitDepth, int metatileWidth, int m break; } - if (image->hasTilemap) - buffer = BuildTilemap(image, buffer, &bufferSize); - WriteWholeFile(path, buffer, bufferSize); free(buffer); @@ -444,11 +300,6 @@ 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) @@ -491,45 +342,3 @@ void WriteGbaPalette(char *path, struct Palette *palette) 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); -} diff --git a/tools/gbagfx/gfx.h b/tools/gbagfx/gfx.h index 15a3c6a6c..5355ced85 100644 --- a/tools/gbagfx/gfx.h +++ b/tools/gbagfx/gfx.h @@ -17,18 +17,6 @@ struct Palette { 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; @@ -37,9 +25,6 @@ struct Image { 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); @@ -47,7 +32,5 @@ void WriteImage(char *path, int numTiles, int bitDepth, int metatileWidth, int m 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 diff --git a/tools/gbagfx/huff.c b/tools/gbagfx/huff.c new file mode 100644 index 000000000..143ed79be --- /dev/null +++ b/tools/gbagfx/huff.c @@ -0,0 +1,398 @@ +#include <stdbool.h> +#include <string.h> +#include <assert.h> +#include <stdio.h> +#include <stdint.h> +#include "global.h" +#include "huff.h" + +static int cmp_tree(const void * a0, const void * b0) { + return ((struct HuffData *)a0)->value - ((struct HuffData *)b0)->value; +} + +typedef int (*cmpfun)(const void *, const void *); + +int msort_r(void * data, size_t count, size_t size, cmpfun cmp, void * buffer) { + /* + * Out-of-place mergesort (stable sort) + * Returns 1 on success, 0 on failure + */ + void * leftPtr; + void * rightPtr; + void * leftEnd; + void * rightEnd; + int i; + + switch (count) { + case 0: + // Should never be here + return 0; + + case 1: + // Nothing to do here + break; + + case 2: + // Swap the two entries if the right one compares higher. + if (cmp(data, data + size) > 0) { + memcpy(buffer, data, size); + memcpy(data, data + size, size); + memcpy(data + size, buffer, size); + } + break; + default: + // Merge sort out-of-place. + leftPtr = data; + leftEnd = rightPtr = data + count / 2 * size; + rightEnd = data + count * size; + + // Sort the left half + if (!msort_r(leftPtr, count / 2, size, cmp, buffer)) + return 0; + + // Sort the right half + if (!msort_r(rightPtr, count / 2 + (count & 1), size, cmp, buffer)) + return 0; + + // Merge the sorted halves out of place + i = 0; + do { + if (cmp(leftPtr, rightPtr) <= 0) { + memcpy(buffer + i * size, leftPtr, size); + leftPtr += size; + } else { + memcpy(buffer + i * size, rightPtr, size); + rightPtr += size; + } + + } while (++i < count && leftPtr < leftEnd && rightPtr < rightEnd); + + // Copy the remainder + if (i < count) { + if (leftPtr < leftEnd) { + memcpy(buffer + i * size, leftPtr, leftEnd - leftPtr); + } + else { + memcpy(buffer + i * size, rightPtr, rightEnd - rightPtr); + } + } + + // Copy the merged data back + memcpy(data, buffer, count * size); + break; + } + + return 1; +} + +int msort(void * data, size_t count, size_t size, cmpfun cmp) { + void * buffer = malloc(count * size); + if (buffer == NULL) return 0; + int result = msort_r(data, count, size, cmp, buffer); + free(buffer); + return result; +} + +static void write_tree(unsigned char * dest, HuffNode_t * tree, int nitems, struct BitEncoding * encoding) { + /* + * The example used to guide this function encodes the tree in a + * breadth-first manner. We attempt to emulate that here. + */ + + int i, j, k; + + // There are (2 * nitems - 1) nodes in the binary tree. Allocate that. + HuffNode_t * traversal = calloc(2 * nitems - 1, sizeof(HuffNode_t)); + if (traversal == NULL) + FATAL_ERROR("Fatal error while compressing Huff file.\n"); + + // The first node is the root of the tree. + traversal[0] = *tree; + i = 1; + + // Copy the tree into a breadth-first ordering using brute force. + for (int depth = 1; i < 2 * nitems - 1; depth++) { + // Consider every possible path up to the current depth. + for (j = 0; i < 2 * nitems - 1 && j < 1 << depth; j++) { + // The index of the path is used to encode the path itself. + // Start from the most significant relevant bit and work our way down. + // Keep track of the current and previous nodes. + HuffNode_t * currNode = traversal; + HuffNode_t * parent = NULL; + for (k = 0; k < depth; k++) { + if (currNode->header.isLeaf) + break; + parent = currNode; + if ((j >> (depth - k - 1)) & 1) + currNode = currNode->branch.right; + else + currNode = currNode->branch.left; + } + // Check that the length of the current path equals the current depth. + if (k == depth) { + // Make sure we can encode the current branch. + // Bail here if we cannot. + // This is only applicable for 8-bit encodings. + if (traversal + i - parent > 128) + FATAL_ERROR("Fatal error while compressing Huff file: unable to encode binary tree.\n"); + // Copy the current node, and update its parent. + traversal[i] = *currNode; + if (parent != NULL) { + if ((j & 1) == 1) + parent->branch.right = traversal + i; + else + parent->branch.left = traversal + i; + } + // Encode the path through the tree in the lookup table + if (traversal[i].header.isLeaf) { + encoding[traversal[i].leaf.key].nbits = depth; + encoding[traversal[i].leaf.key].bitstring = j; + } + i++; + } + } + } + + // Encode the size of the tree. + // This is used by the decompressor to skip the tree. + dest[4] = nitems - 1; + + // Encode each node in the tree. + for (i = 0; i < 2 * nitems - 1; i++) { + HuffNode_t * currNode = traversal + i; + if (currNode->header.isLeaf) { + dest[5 + i] = traversal[i].leaf.key; + } else { + dest[5 + i] = (((currNode->branch.right - traversal - i) / 2) - 1); + if (currNode->branch.left->header.isLeaf) + dest[5 + i] |= 0x80; + if (currNode->branch.right->header.isLeaf) + dest[5 + i] |= 0x40; + } + } + + free(traversal); +} + +static inline void write_32_le(unsigned char * dest, int * destPos, uint32_t * buff, int * buffPos) { + dest[*destPos] = *buff; + dest[*destPos + 1] = *buff >> 8; + dest[*destPos + 2] = *buff >> 16; + dest[*destPos + 3] = *buff >> 24; + *destPos += 4; + *buff = 0; + *buffPos = 0; +} + +static inline void read_32_le(unsigned char * src, int * srcPos, uint32_t * buff) { + uint32_t tmp = src[*srcPos]; + tmp |= src[*srcPos + 1] << 8; + tmp |= src[*srcPos + 2] << 16; + tmp |= src[*srcPos + 3] << 24; + *srcPos += 4; + *buff = tmp; +} + +static void write_bits(unsigned char * dest, int * destPos, struct BitEncoding * encoding, int value, uint32_t * buff, int * buffBits) { + int nbits = encoding[value].nbits; + uint32_t bitstring = encoding[value].bitstring; + + if (*buffBits + nbits >= 32) { + int diff = *buffBits + nbits - 32; + *buff <<= nbits - diff; + *buff |= bitstring >> diff; + bitstring &= ~(1 << diff); + nbits = diff; + write_32_le(dest, destPos, buff, buffBits); + } + if (nbits != 0) { + *buff <<= nbits; + *buff |= bitstring; + *buffBits += nbits; + } +} + +/* +======================================= +MAIN COMPRESSION/DECOMPRESSION ROUTINES +======================================= + */ + +unsigned char * HuffCompress(unsigned char * src, int srcSize, int * compressedSize_p, int bitDepth) { + if (srcSize <= 0) + goto fail; + + int worstCaseDestSize = 4 + (2 << bitDepth) + srcSize * 3; + + unsigned char *dest = malloc(worstCaseDestSize); + if (dest == NULL) + goto fail; + + int nitems = 1 << bitDepth; + + HuffNode_t * freqs = calloc(nitems, sizeof(HuffNode_t)); + if (freqs == NULL) + goto fail; + + struct BitEncoding * encoding = calloc(nitems, sizeof(struct BitEncoding)); + if (encoding == NULL) + goto fail; + + // Set up the frequencies table. This will inform the tree. + for (int i = 0; i < nitems; i++) { + freqs[i].header.isLeaf = 1; + freqs[i].header.value = 0; + freqs[i].leaf.key = i; + } + + // Count each nybble or byte. + for (int i = 0; i < srcSize; i++) { + if (bitDepth == 8) { + freqs[src[i]].header.value++; + } else { + freqs[src[i] >> 4].header.value++; + freqs[src[i] & 0xF].header.value++; + } + } + +#ifdef DEBUG + for (int i = 0; i < nitems; i++) { + fprintf(stderr, "%d: %d\n", i, freqs[i].header.value); + } +#endif // DEBUG + + // Sort the frequency table. + if (!msort(freqs, nitems, sizeof(HuffNode_t), cmp_tree)) + goto fail; + + // Prune zero-frequency values. + for (int i = 0; i < nitems; i++) { + if (freqs[i].header.value != 0) { + if (i > 0) { + for (int j = i; j < nitems; j++) { + freqs[j - i] = freqs[j]; + } + nitems -= i; + } + break; + } + // This should never happen: + if (i == nitems - 1) + goto fail; + } + + HuffNode_t * tree = calloc(nitems * 2 - 1, sizeof(HuffNode_t)); + if (tree == NULL) + goto fail; + + // Iteratively collapse the two least frequent nodes. + HuffNode_t * endptr = freqs + nitems - 2; + + for (int i = 0; i < nitems - 1; i++) { + HuffNode_t * left = freqs; + HuffNode_t * right = freqs + 1; + tree[i * 2] = *right; + tree[i * 2 + 1] = *left; + for (int j = 0; j < nitems - i - 2; j++) + freqs[j] = freqs[j + 2]; + endptr->header.isLeaf = 0; + endptr->header.value = tree[i * 2].header.value + tree[i * 2 + 1].header.value; + endptr->branch.left = tree + i * 2; + endptr->branch.right = tree + i * 2 + 1; + endptr--; + if (i < nitems - 2 && !msort(freqs, nitems - i - 1, sizeof(HuffNode_t), cmp_tree)) + goto fail; + } + + // Write the tree breadth-first, and create the path lookup table. + write_tree(dest, freqs, nitems, encoding); + + free(tree); + free(freqs); + + // Encode the data itself. + int destPos = 4 + nitems * 2; + uint32_t destBuf = 0; + uint32_t srcBuf = 0; + int destBitPos = 0; + + for (int srcPos = 0; srcPos < srcSize;) { + read_32_le(src, &srcPos, &srcBuf); + for (int i = 0; i < 32 / bitDepth; i++) { + write_bits(dest, &destPos, encoding, srcBuf & (0xFF >> (8 - bitDepth)), &destBuf, &destBitPos); + srcBuf >>= bitDepth; + } + } + + if (destBitPos != 0) { + write_32_le(dest, &destPos, &destBuf, &destBitPos); + } + + free(encoding); + + // Write the header. + dest[0] = bitDepth | 0x20; + dest[1] = srcSize; + dest[2] = srcSize >> 8; + dest[3] = srcSize >> 16; + *compressedSize_p = (destPos + 3) & ~3; + return dest; + +fail: + FATAL_ERROR("Fatal error while compressing Huff file.\n"); +} + +unsigned char * HuffDecompress(unsigned char * src, int srcSize, int * uncompressedSize_p) { + if (srcSize < 4) + goto fail; + + int bitDepth = *src & 15; + if (bitDepth != 4 && bitDepth != 8) + goto fail; + + int destSize = (src[3] << 16) | (src[2] << 8) | src[1]; + + unsigned char *dest = malloc(destSize); + + if (dest == NULL) + goto fail; + + int treePos = 5; + int treeSize = (src[4] + 1) * 2; + int srcPos = 4 + treeSize; + int destPos = 0; + int curValPos = 0; + uint32_t destTmp = 0; + uint32_t window; + + for (;;) + { + if (srcPos >= srcSize) + goto fail; + read_32_le(src, &srcPos, &window); + for (int i = 0; i < 32; i++) { + int curBit = (window >> 31) & 1; + unsigned char treeView = src[treePos]; + bool isLeaf = ((treeView << curBit) & 0x80) != 0; + treePos &= ~1; // align + treePos += ((treeView & 0x3F) + 1) * 2 + curBit; + if (isLeaf) { + destTmp >>= bitDepth; + destTmp |= (src[treePos] << (32 - bitDepth)); + curValPos++; + if (curValPos == 32 / bitDepth) { + write_32_le(dest, &destPos, &destTmp, &curValPos); + if (destPos == destSize) { + *uncompressedSize_p = destSize; + return dest; + } + } + treePos = 5; + } + window <<= 1; + } + } + +fail: + FATAL_ERROR("Fatal error while decompressing Huff file.\n"); +} diff --git a/tools/gbagfx/huff.h b/tools/gbagfx/huff.h new file mode 100644 index 000000000..6002fe954 --- /dev/null +++ b/tools/gbagfx/huff.h @@ -0,0 +1,38 @@ +#ifndef HUFF_H +#define HUFF_H + +union HuffNode; + +struct HuffData { + unsigned value:31; + unsigned isLeaf:1; +}; + +struct HuffLeaf { + struct HuffData header; + unsigned char key; +}; + +struct HuffBranch { + struct HuffData header; + union HuffNode * left; + union HuffNode * right; +}; + +union HuffNode { + struct HuffData header; + struct HuffLeaf leaf; + struct HuffBranch branch; +}; + +typedef union HuffNode HuffNode_t; + +struct BitEncoding { + unsigned long long nbits:6; + unsigned long long bitstring:58; +}; + +unsigned char * HuffCompress(unsigned char * buffer, int srcSize, int * compressedSize_p, int bitDepth); +unsigned char * HuffDecompress(unsigned char * buffer, int srcSize, int * uncompressedSize_p); + +#endif //HUFF_H diff --git a/tools/gbagfx/main.c b/tools/gbagfx/main.c index 2174e37a5..aa0681fb6 100644 --- a/tools/gbagfx/main.c +++ b/tools/gbagfx/main.c @@ -12,6 +12,7 @@ #include "lz.h" #include "rl.h" #include "font.h" +#include "huff.h" struct CommandHandler { @@ -34,17 +35,6 @@ void ConvertGbaToPng(char *inputPath, char *outputPath, struct GbaToPngOptions * 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; @@ -59,17 +49,11 @@ void ConvertPngToGba(char *inputPath, char *outputPath, struct PngToGbaOptions * 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); } @@ -78,7 +62,6 @@ void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **a char *inputFileExtension = GetFileExtension(inputPath); struct GbaToPngOptions options; options.paletteFilePath = NULL; - options.tilemapFilePath = NULL; options.bitDepth = inputFileExtension[0] - '0'; options.hasTransparency = false; options.width = 1; @@ -98,15 +81,6 @@ void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **a 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; @@ -171,7 +145,6 @@ void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **a options.bitDepth = bitDepth; options.metatileWidth = 1; options.metatileHeight = 1; - options.tilemapFilePath = NULL; for (int i = 3; i < argc; i++) { @@ -190,14 +163,6 @@ void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **a 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) @@ -290,10 +255,6 @@ void HandleLatinFontToPngCommand(char *inputPath, char *outputPath, int argc UNU { struct Image image; - image.hasTilemap = false; - image.tileMap.data = NULL; - image.tileMap.numTiles = 0; - ReadLatinFont(inputPath, &image); WritePng(outputPath, &image); @@ -304,10 +265,6 @@ void HandlePngToLatinFontCommand(char *inputPath, char *outputPath, int argc UNU { struct Image image; - image.hasTilemap = false; - image.tileMap.data = NULL; - image.tileMap.numTiles = 0; - image.bitDepth = 2; ReadPng(inputPath, &image); @@ -320,10 +277,6 @@ void HandleHalfwidthJapaneseFontToPngCommand(char *inputPath, char *outputPath, { struct Image image; - image.hasTilemap = false; - image.tileMap.data = NULL; - image.tileMap.numTiles = 0; - ReadHalfwidthJapaneseFont(inputPath, &image); WritePng(outputPath, &image); @@ -334,10 +287,6 @@ void HandlePngToHalfwidthJapaneseFontCommand(char *inputPath, char *outputPath, { struct Image image; - image.hasTilemap = false; - image.tileMap.data = NULL; - image.tileMap.numTiles = 0; - image.bitDepth = 2; ReadPng(inputPath, &image); @@ -350,10 +299,6 @@ void HandleFullwidthJapaneseFontToPngCommand(char *inputPath, char *outputPath, { struct Image image; - image.hasTilemap = false; - image.tileMap.data = NULL; - image.tileMap.numTiles = 0; - ReadFullwidthJapaneseFont(inputPath, &image); WritePng(outputPath, &image); @@ -364,10 +309,6 @@ void HandlePngToFullwidthJapaneseFontCommand(char *inputPath, char *outputPath, { struct Image image; - image.hasTilemap = false; - image.tileMap.data = NULL; - image.tileMap.numTiles = 0; - image.bitDepth = 2; ReadPng(inputPath, &image); @@ -485,6 +426,61 @@ void HandleRLDecompressCommand(char *inputPath, char *outputPath, int argc UNUSE free(uncompressedData); } +void HandleHuffCompressCommand(char *inputPath, char *outputPath, int argc, char **argv) +{ + int fileSize; + int bitDepth = 4; + + for (int i = 3; i < argc; i++) + { + char *option = argv[i]; + + if (strcmp(option, "-depth") == 0) + { + if (i + 1 >= argc) + FATAL_ERROR("No size following \"-depth\".\n"); + + i++; + + if (!ParseNumber(argv[i], NULL, 10, &bitDepth)) + FATAL_ERROR("Failed to parse bit depth.\n"); + + if (bitDepth != 4 && bitDepth != 8) + FATAL_ERROR("GBA only supports bit depth of 4 or 8.\n"); + } + else + { + FATAL_ERROR("Unrecognized option \"%s\".\n", option); + } + } + + unsigned char *buffer = ReadWholeFile(inputPath, &fileSize); + + int compressedSize; + unsigned char *compressedData = HuffCompress(buffer, fileSize, &compressedSize, bitDepth); + + free(buffer); + + WriteWholeFile(outputPath, compressedData, compressedSize); + + free(compressedData); +} + +void HandleHuffDecompressCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED) +{ + int fileSize; + unsigned char *buffer = ReadWholeFile(inputPath, &fileSize); + + int uncompressedSize; + unsigned char *uncompressedData = HuffDecompress(buffer, fileSize, &uncompressedSize); + + free(buffer); + + WriteWholeFile(outputPath, uncompressedData, uncompressedSize); + + free(uncompressedData); +} + int main(int argc, char **argv) { if (argc < 3) @@ -507,7 +503,9 @@ int main(int argc, char **argv) { "png", "hwjpnfont", HandlePngToHalfwidthJapaneseFontCommand }, { "fwjpnfont", "png", HandleFullwidthJapaneseFontToPngCommand }, { "png", "fwjpnfont", HandlePngToFullwidthJapaneseFontCommand }, + { NULL, "huff", HandleHuffCompressCommand }, { NULL, "lz", HandleLZCompressCommand }, + { "huff", NULL, HandleHuffDecompressCommand }, { "lz", NULL, HandleLZDecompressCommand }, { NULL, "rl", HandleRLCompressCommand }, { "rl", NULL, HandleRLDecompressCommand }, diff --git a/tools/gbagfx/options.h b/tools/gbagfx/options.h index 463ee2455..2ff3967a4 100644 --- a/tools/gbagfx/options.h +++ b/tools/gbagfx/options.h @@ -7,7 +7,6 @@ struct GbaToPngOptions { char *paletteFilePath; - char *tilemapFilePath; int bitDepth; bool hasTransparency; int width; @@ -16,7 +15,6 @@ struct GbaToPngOptions { }; struct PngToGbaOptions { - char *tilemapFilePath; int numTiles; int bitDepth; int metatileWidth; diff --git a/tools/mapjson/.gitignore b/tools/mapjson/.gitignore new file mode 100755 index 000000000..a5d568479 --- /dev/null +++ b/tools/mapjson/.gitignore @@ -0,0 +1 @@ +mapjson diff --git a/tools/mapjson/Makefile b/tools/mapjson/Makefile new file mode 100644 index 000000000..d09acad50 --- /dev/null +++ b/tools/mapjson/Makefile @@ -0,0 +1,15 @@ +CXX := g++ + +CXXFLAGS := -Wall -std=c++11 -O2 + +SRCS := json11.cpp mapjson.cpp + +HEADERS := mapjson.h + +.PHONY: clean + +mapjson: $(SRCS) $(HEADERS) + $(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS) + +clean: + $(RM) mapjson mapjson.exe diff --git a/tools/mapjson/json11.cpp b/tools/mapjson/json11.cpp new file mode 100644 index 000000000..1da530206 --- /dev/null +++ b/tools/mapjson/json11.cpp @@ -0,0 +1,786 @@ +/* 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. + */ + +#include "json11.h" +#include <cassert> +#include <cmath> +#include <cstdlib> +#include <cstdio> +#include <limits> + +namespace json11 { + +static const int max_depth = 200; + +using std::string; +using std::vector; +using std::map; +using std::make_shared; +using std::initializer_list; +using std::move; + +/* Helper for representing null - just a do-nothing struct, plus comparison + * operators so the helpers in JsonValue work. We can't use nullptr_t because + * it may not be orderable. + */ +struct NullStruct { + bool operator==(NullStruct) const { return true; } + bool operator<(NullStruct) const { return false; } +}; + +/* * * * * * * * * * * * * * * * * * * * + * Serialization + */ + +static void dump(NullStruct, string &out) { + out += "null"; +} + +static void dump(double value, string &out) { + if (std::isfinite(value)) { + char buf[32]; + snprintf(buf, sizeof buf, "%.17g", value); + out += buf; + } else { + out += "null"; + } +} + +static void dump(int value, string &out) { + char buf[32]; + snprintf(buf, sizeof buf, "%d", value); + out += buf; +} + +static void dump(bool value, string &out) { + out += value ? "true" : "false"; +} + +static void dump(const string &value, string &out) { + out += '"'; + for (size_t i = 0; i < value.length(); i++) { + const char ch = value[i]; + if (ch == '\\') { + out += "\\\\"; + } else if (ch == '"') { + out += "\\\""; + } else if (ch == '\b') { + out += "\\b"; + } else if (ch == '\f') { + out += "\\f"; + } else if (ch == '\n') { + out += "\\n"; + } else if (ch == '\r') { + out += "\\r"; + } else if (ch == '\t') { + out += "\\t"; + } else if (static_cast<uint8_t>(ch) <= 0x1f) { + char buf[8]; + snprintf(buf, sizeof buf, "\\u%04x", ch); + out += buf; + } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80 + && static_cast<uint8_t>(value[i+2]) == 0xa8) { + out += "\\u2028"; + i += 2; + } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80 + && static_cast<uint8_t>(value[i+2]) == 0xa9) { + out += "\\u2029"; + i += 2; + } else { + out += ch; + } + } + out += '"'; +} + +static void dump(const Json::array &values, string &out) { + bool first = true; + out += "["; + for (const auto &value : values) { + if (!first) + out += ", "; + value.dump(out); + first = false; + } + out += "]"; +} + +static void dump(const Json::object &values, string &out) { + bool first = true; + out += "{"; + for (const auto &kv : values) { + if (!first) + out += ", "; + dump(kv.first, out); + out += ": "; + kv.second.dump(out); + first = false; + } + out += "}"; +} + +void Json::dump(string &out) const { + m_ptr->dump(out); +} + +/* * * * * * * * * * * * * * * * * * * * + * Value wrappers + */ + +template <Json::Type tag, typename T> +class Value : public JsonValue { +protected: + + // Constructors + explicit Value(const T &value) : m_value(value) {} + explicit Value(T &&value) : m_value(move(value)) {} + + // Get type tag + Json::Type type() const override { + return tag; + } + + // Comparisons + bool equals(const JsonValue * other) const override { + return m_value == static_cast<const Value<tag, T> *>(other)->m_value; + } + bool less(const JsonValue * other) const override { + return m_value < static_cast<const Value<tag, T> *>(other)->m_value; + } + + const T m_value; + void dump(string &out) const override { json11::dump(m_value, out); } +}; + +class JsonDouble final : public Value<Json::NUMBER, double> { + double number_value() const override { return m_value; } + int int_value() const override { return static_cast<int>(m_value); } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonDouble(double value) : Value(value) {} +}; + +class JsonInt final : public Value<Json::NUMBER, int> { + double number_value() const override { return m_value; } + int int_value() const override { return m_value; } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonInt(int value) : Value(value) {} +}; + +class JsonBoolean final : public Value<Json::BOOL, bool> { + bool bool_value() const override { return m_value; } +public: + explicit JsonBoolean(bool value) : Value(value) {} +}; + +class JsonString final : public Value<Json::STRING, string> { + const string &string_value() const override { return m_value; } +public: + explicit JsonString(const string &value) : Value(value) {} + explicit JsonString(string &&value) : Value(move(value)) {} +}; + +class JsonArray final : public Value<Json::ARRAY, Json::array> { + const Json::array &array_items() const override { return m_value; } + const Json & operator[](size_t i) const override; +public: + explicit JsonArray(const Json::array &value) : Value(value) {} + explicit JsonArray(Json::array &&value) : Value(move(value)) {} +}; + +class JsonObject final : public Value<Json::OBJECT, Json::object> { + const Json::object &object_items() const override { return m_value; } + const Json & operator[](const string &key) const override; +public: + explicit JsonObject(const Json::object &value) : Value(value) {} + explicit JsonObject(Json::object &&value) : Value(move(value)) {} +}; + +class JsonNull final : public Value<Json::NUL, NullStruct> { +public: + JsonNull() : Value({}) {} +}; + +/* * * * * * * * * * * * * * * * * * * * + * Static globals - static-init-safe + */ +struct Statics { + const std::shared_ptr<JsonValue> null = make_shared<JsonNull>(); + const std::shared_ptr<JsonValue> t = make_shared<JsonBoolean>(true); + const std::shared_ptr<JsonValue> f = make_shared<JsonBoolean>(false); + const string empty_string; + const vector<Json> empty_vector; + const map<string, Json> empty_map; + Statics() {} +}; + +static const Statics & statics() { + static const Statics s {}; + return s; +} + +static const Json & static_null() { + // This has to be separate, not in Statics, because Json() accesses statics().null. + static const Json json_null; + return json_null; +} + +/* * * * * * * * * * * * * * * * * * * * + * Constructors + */ + +Json::Json() noexcept : m_ptr(statics().null) {} +Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {} +Json::Json(double value) : m_ptr(make_shared<JsonDouble>(value)) {} +Json::Json(int value) : m_ptr(make_shared<JsonInt>(value)) {} +Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {} +Json::Json(const string &value) : m_ptr(make_shared<JsonString>(value)) {} +Json::Json(string &&value) : m_ptr(make_shared<JsonString>(move(value))) {} +Json::Json(const char * value) : m_ptr(make_shared<JsonString>(value)) {} +Json::Json(const Json::array &values) : m_ptr(make_shared<JsonArray>(values)) {} +Json::Json(Json::array &&values) : m_ptr(make_shared<JsonArray>(move(values))) {} +Json::Json(const Json::object &values) : m_ptr(make_shared<JsonObject>(values)) {} +Json::Json(Json::object &&values) : m_ptr(make_shared<JsonObject>(move(values))) {} + +/* * * * * * * * * * * * * * * * * * * * + * Accessors + */ + +Json::Type Json::type() const { return m_ptr->type(); } +double Json::number_value() const { return m_ptr->number_value(); } +int Json::int_value() const { return m_ptr->int_value(); } +bool Json::bool_value() const { return m_ptr->bool_value(); } +const string & Json::string_value() const { return m_ptr->string_value(); } +const vector<Json> & Json::array_items() const { return m_ptr->array_items(); } +const map<string, Json> & Json::object_items() const { return m_ptr->object_items(); } +const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; } +const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; } + +double JsonValue::number_value() const { return 0; } +int JsonValue::int_value() const { return 0; } +bool JsonValue::bool_value() const { return false; } +const string & JsonValue::string_value() const { return statics().empty_string; } +const vector<Json> & JsonValue::array_items() const { return statics().empty_vector; } +const map<string, Json> & JsonValue::object_items() const { return statics().empty_map; } +const Json & JsonValue::operator[] (size_t) const { return static_null(); } +const Json & JsonValue::operator[] (const string &) const { return static_null(); } + +const Json & JsonObject::operator[] (const string &key) const { + auto iter = m_value.find(key); + return (iter == m_value.end()) ? static_null() : iter->second; +} +const Json & JsonArray::operator[] (size_t i) const { + if (i >= m_value.size()) return static_null(); + else return m_value[i]; +} + +/* * * * * * * * * * * * * * * * * * * * + * Comparison + */ + +bool Json::operator== (const Json &other) const { + if (m_ptr == other.m_ptr) + return true; + if (m_ptr->type() != other.m_ptr->type()) + return false; + + return m_ptr->equals(other.m_ptr.get()); +} + +bool Json::operator< (const Json &other) const { + if (m_ptr == other.m_ptr) + return false; + if (m_ptr->type() != other.m_ptr->type()) + return m_ptr->type() < other.m_ptr->type(); + + return m_ptr->less(other.m_ptr.get()); +} + +/* * * * * * * * * * * * * * * * * * * * + * Parsing + */ + +/* esc(c) + * + * Format char c suitable for printing in an error message. + */ +static inline string esc(char c) { + char buf[12]; + if (static_cast<uint8_t>(c) >= 0x20 && static_cast<uint8_t>(c) <= 0x7f) { + snprintf(buf, sizeof buf, "'%c' (%d)", c, c); + } else { + snprintf(buf, sizeof buf, "(%d)", c); + } + return string(buf); +} + +static inline bool in_range(long x, long lower, long upper) { + return (x >= lower && x <= upper); +} + +namespace { +/* JsonParser + * + * Object that tracks all state of an in-progress parse. + */ +struct JsonParser final { + + /* State + */ + const string &str; + size_t i; + string &err; + bool failed; + const JsonParse strategy; + + /* fail(msg, err_ret = Json()) + * + * Mark this parse as failed. + */ + Json fail(string &&msg) { + return fail(move(msg), Json()); + } + + template <typename T> + T fail(string &&msg, const T err_ret) { + if (!failed) + err = std::move(msg); + failed = true; + return err_ret; + } + + /* consume_whitespace() + * + * Advance until the current character is non-whitespace. + */ + void consume_whitespace() { + while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t') + i++; + } + + /* consume_comment() + * + * Advance comments (c-style inline and multiline). + */ + bool consume_comment() { + bool comment_found = false; + if (str[i] == '/') { + i++; + if (i == str.size()) + return fail("unexpected end of input after start of comment", false); + if (str[i] == '/') { // inline comment + i++; + // advance until next line, or end of input + while (i < str.size() && str[i] != '\n') { + i++; + } + comment_found = true; + } + else if (str[i] == '*') { // multiline comment + i++; + if (i > str.size()-2) + return fail("unexpected end of input inside multi-line comment", false); + // advance until closing tokens + while (!(str[i] == '*' && str[i+1] == '/')) { + i++; + if (i > str.size()-2) + return fail( + "unexpected end of input inside multi-line comment", false); + } + i += 2; + comment_found = true; + } + else + return fail("malformed comment", false); + } + return comment_found; + } + + /* consume_garbage() + * + * Advance until the current character is non-whitespace and non-comment. + */ + void consume_garbage() { + consume_whitespace(); + if(strategy == JsonParse::COMMENTS) { + bool comment_found = false; + do { + comment_found = consume_comment(); + if (failed) return; + consume_whitespace(); + } + while(comment_found); + } + } + + /* get_next_token() + * + * Return the next non-whitespace character. If the end of the input is reached, + * flag an error and return 0. + */ + char get_next_token() { + consume_garbage(); + if (failed) return static_cast<char>(0); + if (i == str.size()) + return fail("unexpected end of input", static_cast<char>(0)); + + return str[i++]; + } + + /* encode_utf8(pt, out) + * + * Encode pt as UTF-8 and add it to out. + */ + void encode_utf8(long pt, string & out) { + if (pt < 0) + return; + + if (pt < 0x80) { + out += static_cast<char>(pt); + } else if (pt < 0x800) { + out += static_cast<char>((pt >> 6) | 0xC0); + out += static_cast<char>((pt & 0x3F) | 0x80); + } else if (pt < 0x10000) { + out += static_cast<char>((pt >> 12) | 0xE0); + out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80); + out += static_cast<char>((pt & 0x3F) | 0x80); + } else { + out += static_cast<char>((pt >> 18) | 0xF0); + out += static_cast<char>(((pt >> 12) & 0x3F) | 0x80); + out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80); + out += static_cast<char>((pt & 0x3F) | 0x80); + } + } + + /* parse_string() + * + * Parse a string, starting at the current position. + */ + string parse_string() { + string out; + long last_escaped_codepoint = -1; + while (true) { + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + char ch = str[i++]; + + if (ch == '"') { + encode_utf8(last_escaped_codepoint, out); + return out; + } + + if (in_range(ch, 0, 0x1f)) + return fail("unescaped " + esc(ch) + " in string", ""); + + // The usual case: non-escaped characters + if (ch != '\\') { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + out += ch; + continue; + } + + // Handle escapes + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + ch = str[i++]; + + if (ch == 'u') { + // Extract 4-byte escape sequence + string esc = str.substr(i, 4); + // Explicitly check length of the substring. The following loop + // relies on std::string returning the terminating NUL when + // accessing str[length]. Checking here reduces brittleness. + if (esc.length() < 4) { + return fail("bad \\u escape: " + esc, ""); + } + for (size_t j = 0; j < 4; j++) { + if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F') + && !in_range(esc[j], '0', '9')) + return fail("bad \\u escape: " + esc, ""); + } + + long codepoint = strtol(esc.data(), nullptr, 16); + + // JSON specifies that characters outside the BMP shall be encoded as a pair + // of 4-hex-digit \u escapes encoding their surrogate pair components. Check + // whether we're in the middle of such a beast: the previous codepoint was an + // escaped lead (high) surrogate, and this is a trail (low) surrogate. + if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF) + && in_range(codepoint, 0xDC00, 0xDFFF)) { + // Reassemble the two surrogate pairs into one astral-plane character, per + // the UTF-16 algorithm. + encode_utf8((((last_escaped_codepoint - 0xD800) << 10) + | (codepoint - 0xDC00)) + 0x10000, out); + last_escaped_codepoint = -1; + } else { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = codepoint; + } + + i += 4; + continue; + } + + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + + if (ch == 'b') { + out += '\b'; + } else if (ch == 'f') { + out += '\f'; + } else if (ch == 'n') { + out += '\n'; + } else if (ch == 'r') { + out += '\r'; + } else if (ch == 't') { + out += '\t'; + } else if (ch == '"' || ch == '\\' || ch == '/') { + out += ch; + } else { + return fail("invalid escape character " + esc(ch), ""); + } + } + } + + /* parse_number() + * + * Parse a double. + */ + Json parse_number() { + size_t start_pos = i; + + if (str[i] == '-') + i++; + + // Integer part + if (str[i] == '0') { + i++; + if (in_range(str[i], '0', '9')) + return fail("leading 0s not permitted in numbers"); + } else if (in_range(str[i], '1', '9')) { + i++; + while (in_range(str[i], '0', '9')) + i++; + } else { + return fail("invalid " + esc(str[i]) + " in number"); + } + + if (str[i] != '.' && str[i] != 'e' && str[i] != 'E' + && (i - start_pos) <= static_cast<size_t>(std::numeric_limits<int>::digits10)) { + return std::atoi(str.c_str() + start_pos); + } + + // Decimal part + if (str[i] == '.') { + i++; + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in fractional part"); + + while (in_range(str[i], '0', '9')) + i++; + } + + // Exponent part + if (str[i] == 'e' || str[i] == 'E') { + i++; + + if (str[i] == '+' || str[i] == '-') + i++; + + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in exponent"); + + while (in_range(str[i], '0', '9')) + i++; + } + + return std::strtod(str.c_str() + start_pos, nullptr); + } + + /* expect(str, res) + * + * Expect that 'str' starts at the character that was just read. If it does, advance + * the input and return res. If not, flag an error. + */ + Json expect(const string &expected, Json res) { + assert(i != 0); + i--; + if (str.compare(i, expected.length(), expected) == 0) { + i += expected.length(); + return res; + } else { + return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length())); + } + } + + /* parse_json() + * + * Parse a JSON object. + */ + Json parse_json(int depth) { + if (depth > max_depth) { + return fail("exceeded maximum nesting depth"); + } + + char ch = get_next_token(); + if (failed) + return Json(); + + if (ch == '-' || (ch >= '0' && ch <= '9')) { + i--; + return parse_number(); + } + + if (ch == 't') + return expect("true", true); + + if (ch == 'f') + return expect("false", false); + + if (ch == 'n') + return expect("null", Json()); + + if (ch == '"') + return parse_string(); + + if (ch == '{') { + map<string, Json> data; + ch = get_next_token(); + if (ch == '}') + return data; + + while (1) { + if (ch != '"') + return fail("expected '\"' in object, got " + esc(ch)); + + string key = parse_string(); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch != ':') + return fail("expected ':' in object, got " + esc(ch)); + + data[std::move(key)] = parse_json(depth + 1); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == '}') + break; + if (ch != ',') + return fail("expected ',' in object, got " + esc(ch)); + + ch = get_next_token(); + } + return data; + } + + if (ch == '[') { + vector<Json> data; + ch = get_next_token(); + if (ch == ']') + return data; + + while (1) { + i--; + data.push_back(parse_json(depth + 1)); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == ']') + break; + if (ch != ',') + return fail("expected ',' in list, got " + esc(ch)); + + ch = get_next_token(); + (void)ch; + } + return data; + } + + return fail("expected value, got " + esc(ch)); + } +}; +}//namespace { + +Json Json::parse(const string &in, string &err, JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + Json result = parser.parse_json(0); + + // Check for any trailing garbage + parser.consume_garbage(); + if (parser.failed) + return Json(); + if (parser.i != in.size()) + return parser.fail("unexpected trailing " + esc(in[parser.i])); + + return result; +} + +// Documented in json11.hpp +vector<Json> Json::parse_multi(const string &in, + std::string::size_type &parser_stop_pos, + string &err, + JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + parser_stop_pos = 0; + vector<Json> json_vec; + while (parser.i != in.size() && !parser.failed) { + json_vec.push_back(parser.parse_json(0)); + if (parser.failed) + break; + + // Check for another object + parser.consume_garbage(); + if (parser.failed) + break; + parser_stop_pos = parser.i; + } + return json_vec; +} + +/* * * * * * * * * * * * * * * * * * * * + * Shape-checking + */ + +bool Json::has_shape(const shape & types, string & err) const { + if (!is_object()) { + err = "expected JSON object, got " + dump(); + return false; + } + + for (auto & item : types) { + if ((*this)[item.first].type() != item.second) { + err = "bad type for " + item.first + " in " + dump(); + return false; + } + } + + return true; +} + +} // namespace json11 diff --git a/tools/mapjson/json11.h b/tools/mapjson/json11.h new file mode 100644 index 000000000..c04c4362f --- /dev/null +++ b/tools/mapjson/json11.h @@ -0,0 +1,230 @@ +/* json11 + * + * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. + * + * The core object provided by the library is json11::Json. A Json object represents any JSON + * value: null, bool, number (int or double), string (std::string), array (std::vector), or + * object (std::map). + * + * Json objects act like values: they can be assigned, copied, moved, compared for equality or + * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and + * Json::parse (static) to parse a std::string as a Json object. + * + * Internally, the various types of Json object are represented by the JsonValue class + * hierarchy. + * + * A note on numbers - JSON specifies the syntax of number formatting but not its semantics, + * so some JSON implementations distinguish between integers and floating-point numbers, while + * some don't. In json11, we choose the latter. Because some JSON implementations (namely + * Javascript itself) treat all numbers as the same type, distinguishing the two leads + * to JSON that will be *silently* changed by a round-trip through those implementations. + * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also + * provides integer helpers. + * + * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the + * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64 + * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch + * will be exact for +/- 275 years.) + */ + +/* 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. + */ + +#pragma once + +#include <string> +#include <vector> +#include <map> +#include <memory> +#include <initializer_list> + +#ifdef _MSC_VER + #if _MSC_VER <= 1800 // VS 2013 + #ifndef noexcept + #define noexcept throw() + #endif + + #ifndef snprintf + #define snprintf _snprintf_s + #endif + #endif +#endif + +namespace json11 { + +enum JsonParse { + STANDARD, COMMENTS +}; + +class JsonValue; + +class Json final { +public: + // Types + enum Type { + NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT + }; + + // Array and object typedefs + typedef std::vector<Json> array; + typedef std::map<std::string, Json> object; + + // Constructors for the various types of JSON value. + Json() noexcept; // NUL + Json(std::nullptr_t) noexcept; // NUL + Json(double value); // NUMBER + Json(int value); // NUMBER + Json(bool value); // BOOL + Json(const std::string &value); // STRING + Json(std::string &&value); // STRING + Json(const char * value); // STRING + Json(const array &values); // ARRAY + Json(array &&values); // ARRAY + Json(const object &values); // OBJECT + Json(object &&values); // OBJECT + + // Implicit constructor: anything with a to_json() function. + template <class T, class = decltype(&T::to_json)> + Json(const T & t) : Json(t.to_json()) {} + + // Implicit constructor: map-like objects (std::map, std::unordered_map, etc) + template <class M, typename std::enable_if< + std::is_constructible<std::string, decltype(std::declval<M>().begin()->first)>::value + && std::is_constructible<Json, decltype(std::declval<M>().begin()->second)>::value, + int>::type = 0> + Json(const M & m) : Json(object(m.begin(), m.end())) {} + + // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc) + template <class V, typename std::enable_if< + std::is_constructible<Json, decltype(*std::declval<V>().begin())>::value, + int>::type = 0> + Json(const V & v) : Json(array(v.begin(), v.end())) {} + + // This prevents Json(some_pointer) from accidentally producing a bool. Use + // Json(bool(some_pointer)) if that behavior is desired. + Json(void *) = delete; + + // Accessors + Type type() const; + + bool is_null() const { return type() == NUL; } + bool is_number() const { return type() == NUMBER; } + bool is_bool() const { return type() == BOOL; } + bool is_string() const { return type() == STRING; } + bool is_array() const { return type() == ARRAY; } + bool is_object() const { return type() == OBJECT; } + + // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not + // distinguish between integer and non-integer numbers - number_value() and int_value() + // can both be applied to a NUMBER-typed object. + double number_value() const; + int int_value() const; + + // Return the enclosed value if this is a boolean, false otherwise. + bool bool_value() const; + // Return the enclosed string if this is a string, "" otherwise. + const std::string &string_value() const; + // Return the enclosed std::vector if this is an array, or an empty vector otherwise. + const array &array_items() const; + // Return the enclosed std::map if this is an object, or an empty map otherwise. + const object &object_items() const; + + // Return a reference to arr[i] if this is an array, Json() otherwise. + const Json & operator[](size_t i) const; + // Return a reference to obj[key] if this is an object, Json() otherwise. + const Json & operator[](const std::string &key) const; + + // Serialize. + void dump(std::string &out) const; + std::string dump() const { + std::string out; + dump(out); + return out; + } + + // Parse. If parse fails, return Json() and assign an error message to err. + static Json parse(const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + static Json parse(const char * in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + if (in) { + return parse(std::string(in), err, strategy); + } else { + err = "null input"; + return nullptr; + } + } + // Parse multiple objects, concatenated or separated by whitespace + static std::vector<Json> parse_multi( + const std::string & in, + std::string::size_type & parser_stop_pos, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + + static inline std::vector<Json> parse_multi( + const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + std::string::size_type parser_stop_pos; + return parse_multi(in, parser_stop_pos, err, strategy); + } + + bool operator== (const Json &rhs) const; + bool operator< (const Json &rhs) const; + bool operator!= (const Json &rhs) const { return !(*this == rhs); } + bool operator<= (const Json &rhs) const { return !(rhs < *this); } + bool operator> (const Json &rhs) const { return (rhs < *this); } + bool operator>= (const Json &rhs) const { return !(*this < rhs); } + + /* has_shape(types, err) + * + * Return true if this is a JSON object and, for each item in types, has a field of + * the given type. If not, return false and set err to a descriptive message. + */ + typedef std::initializer_list<std::pair<std::string, Type>> shape; + bool has_shape(const shape & types, std::string & err) const; + +private: + std::shared_ptr<JsonValue> m_ptr; +}; + +// Internal class hierarchy - JsonValue objects are not exposed to users of this API. +class JsonValue { +protected: + friend class Json; + friend class JsonInt; + friend class JsonDouble; + virtual Json::Type type() const = 0; + virtual bool equals(const JsonValue * other) const = 0; + virtual bool less(const JsonValue * other) const = 0; + virtual void dump(std::string &out) const = 0; + virtual double number_value() const; + virtual int int_value() const; + virtual bool bool_value() const; + virtual const std::string &string_value() const; + virtual const Json::array &array_items() const; + virtual const Json &operator[](size_t i) const; + virtual const Json::object &object_items() const; + virtual const Json &operator[](const std::string &key) const; + virtual ~JsonValue() {} +}; + +} // namespace json11 diff --git a/tools/mapjson/mapjson.cpp b/tools/mapjson/mapjson.cpp new file mode 100644 index 000000000..1cf196fda --- /dev/null +++ b/tools/mapjson/mapjson.cpp @@ -0,0 +1,538 @@ +// mapjson.cpp + +#include <iostream> +using std::cout; using std::endl; + +#include <string> +using std::string; + +#include <vector> +using std::vector; + +#include <algorithm> +using std::sort; using std::find; + +#include <map> +using std::map; + +#include <fstream> +using std::ofstream; using std::ifstream; + +#include <sstream> +using std::ostringstream; + +#include <limits> +using std::numeric_limits; + +#include "json11.h" +using json11::Json; + +#include "mapjson.h" + + +string read_text_file(string filepath) { + ifstream in_file(filepath); + + if (!in_file.is_open()) + FATAL_ERROR("Cannot open file %s for reading.\n", filepath.c_str()); + + string text; + + in_file.seekg(0, std::ios::end); + text.resize(in_file.tellg()); + + in_file.seekg(0, std::ios::beg); + in_file.read(&text[0], text.size()); + + in_file.close(); + + return text; +} + +void write_text_file(string filepath, string text) { + ofstream out_file(filepath, std::ofstream::binary); + + if (!out_file.is_open()) + FATAL_ERROR("Cannot open file %s for writing.\n", filepath.c_str()); + + out_file << text; + + out_file.close(); +} + +string generate_map_header_text(Json map_data, Json layouts_data, string version) { + string map_layout_id = map_data["layout"].string_value(); + + vector<Json> matched; + + for (auto &field : layouts_data["layouts"].array_items()) { + if (map_layout_id == field["id"].string_value()) + matched.push_back(field); + } + + if (matched.size() != 1) + FATAL_ERROR("Failed to find matching layout for %s.\n", map_layout_id.c_str()); + + Json layout = matched[0]; + + ostringstream text; + + text << map_data["name"].string_value() << ":\n" + << "\t.4byte " << layout["name"].string_value() << "\n"; + + if (map_data.object_items().find("shared_events_map") != map_data.object_items().end()) + text << "\t.4byte " << map_data["shared_events_map"].string_value() << "_MapEvents\n"; + else + text << "\t.4byte " << map_data["name"].string_value() << "_MapEvents\n"; + + if (map_data.object_items().find("shared_scripts_map") != map_data.object_items().end()) + text << "\t.4byte " << map_data["shared_scripts_map"].string_value() << "_MapScripts\n"; + else + text << "\t.4byte " << map_data["name"].string_value() << "_MapScripts\n"; + + if (map_data.object_items().find("connections") != map_data.object_items().end() + && map_data["connections"].array_items().size() > 0) + text << "\t.4byte " << map_data["name"].string_value() << "_MapConnections\n"; + else + text << "\t.4byte 0x0\n"; + + text << "\t.2byte " << map_data["music"].string_value() << "\n" + << "\t.2byte " << layout["id"].string_value() << "\n" + << "\t.byte " << map_data["region_map_section"].string_value() << "\n" + << "\t.byte " << map_data["requires_flash"].bool_value() << "\n" + << "\t.byte " << map_data["weather"].string_value() << "\n" + << "\t.byte " << map_data["map_type"].string_value() << "\n" + << "\t.2byte 0\n"; + + if (version == "ruby") + text << "\t.byte " << map_data["show_map_name"].bool_value() << "\n"; + else if (version == "emerald") + text << "\tmap_header_flags " + << "allow_bike=" << map_data["allow_bike"].bool_value() << ", " + << "allow_escape_rope=" << map_data["allow_escape_rope"].bool_value() << ", " + << "allow_run=" << map_data["allow_running"].bool_value() << ", " + << "show_map_name=" << map_data["show_map_name"].bool_value() << "\n"; + + text << "\t.byte " << map_data["battle_scene"].string_value() << "\n\n"; + + return text.str(); +} + +string generate_map_connections_text(Json map_data) { + if (map_data["connections"] == Json()) + return string("\n"); + + ostringstream text; + + text << map_data["name"].string_value() << "_MapConnectionsList:\n"; + + for (auto &connection : map_data["connections"].array_items()) { + text << "\tconnection " + << connection["direction"].string_value() << ", " + << connection["offset"].int_value() << ", " + << connection["map"].string_value() << "\n"; + } + + text << "\n" << map_data["name"].string_value() << "_MapConnections:\n" + << "\t.4byte " << map_data["connections"].array_items().size() << "\n" + << "\t.4byte " << map_data["name"].string_value() << "_MapConnectionsList\n\n"; + + return text.str(); +} + +string generate_map_events_text(Json map_data) { + if (map_data.object_items().find("shared_events_map") != map_data.object_items().end()) + return string("\n"); + + ostringstream text; + + string objects_label, warps_label, coords_label, bgs_label; + + if (map_data["object_events"].array_items().size() > 0) { + objects_label = map_data["name"].string_value() + "_EventObjects"; + text << objects_label << ":\n"; + for (unsigned int i = 0; i < map_data["object_events"].array_items().size(); i++) { + auto obj_event = map_data["object_events"].array_items()[i]; + text << "\tobject_event " << i + 1 << ", " + << obj_event["graphics_id"].string_value() << ", 0, " + << obj_event["x"].int_value() << ", " + << obj_event["y"].int_value() << ", " + << obj_event["elevation"].int_value() << ", " + << obj_event["movement_type"].string_value() << ", " + << obj_event["movement_range_x"].int_value() << ", " + << obj_event["movement_range_y"].int_value() << ", " + << obj_event["trainer_type"].string_value() << ", " + << obj_event["trainer_sight_or_berry_tree_id"].string_value() << ", " + << obj_event["script"].string_value() << ", " + << obj_event["flag"].string_value() << "\n"; + } + text << "\n"; + } else { + objects_label = "0x0"; + } + + if (map_data["warp_events"].array_items().size() > 0) { + warps_label = map_data["name"].string_value() + "_MapWarps"; + text << warps_label << ":\n"; + for (auto &warp_event : map_data["warp_events"].array_items()) { + text << "\twarp_def " + << warp_event["x"].int_value() << ", " + << warp_event["y"].int_value() << ", " + << warp_event["elevation"].int_value() << ", " + << warp_event["dest_warp_id"].int_value() << ", " + << warp_event["dest_map"].string_value() << "\n"; + } + text << "\n"; + } else { + warps_label = "0x0"; + } + + if (map_data["coord_events"].array_items().size() > 0) { + coords_label = map_data["name"].string_value() + "_MapCoordEvents"; + text << coords_label << ":\n"; + for (auto &coord_event : map_data["coord_events"].array_items()) { + if (coord_event["type"].string_value() == "trigger") { + text << "\tcoord_event " + << coord_event["x"].int_value() << ", " + << coord_event["y"].int_value() << ", " + << coord_event["elevation"].int_value() << ", " + << coord_event["var"].string_value() << ", " + << coord_event["var_value"].string_value() << ", " + << coord_event["script"].string_value() << "\n"; + } + else if (coord_event["type"] == "weather") { + text << "\tcoord_weather_event " + << coord_event["x"].int_value() << ", " + << coord_event["y"].int_value() << ", " + << coord_event["elevation"].int_value() << ", " + << coord_event["weather"].string_value() << "\n"; + } + } + text << "\n"; + } else { + coords_label = "0x0"; + } + + if (map_data["bg_events"].array_items().size() > 0) { + bgs_label = map_data["name"].string_value() + "_MapBGEvents"; + text << bgs_label << ":\n"; + for (auto &bg_event : map_data["bg_events"].array_items()) { + if (bg_event["type"] == "sign") { + text << "\tbg_event " + << bg_event["x"].int_value() << ", " + << bg_event["y"].int_value() << ", " + << bg_event["elevation"].int_value() << ", " + << bg_event["player_facing_dir"].string_value() << ", " + << bg_event["script"].string_value() << "\n"; + } + else if (bg_event["type"] == "hidden_item") { + text << "\tbg_hidden_item_event " + << bg_event["x"].int_value() << ", " + << bg_event["y"].int_value() << ", " + << bg_event["elevation"].int_value() << ", " + << bg_event["item"].string_value() << ", " + << bg_event["flag"].string_value() << "\n"; + } + else if (bg_event["type"] == "secret_base") { + text << "\tbg_secret_base_event " + << bg_event["x"].int_value() << ", " + << bg_event["y"].int_value() << ", " + << bg_event["elevation"].int_value() << ", " + << bg_event["secret_base_id"].string_value() << "\n"; + } + } + text << "\n"; + } else { + bgs_label = "0x0"; + } + + text << map_data["name"].string_value() << "_MapEvents::\n" + << "\tmap_events " << objects_label << ", " << warps_label << ", " + << coords_label << ", " << bgs_label << "\n\n"; + + return text.str(); +} + +string get_directory_name(string filename) { + size_t dir_pos = filename.find_last_of("/\\"); + + return filename.substr(0, dir_pos + 1); +} + +void process_map(string map_filepath, string layouts_filepath, string version) { + string mapdata_err, layouts_err; + + string mapdata_json_text = read_text_file(map_filepath); + string layouts_json_text = read_text_file(layouts_filepath); + + Json map_data = Json::parse(mapdata_json_text, mapdata_err); + if (map_data == Json()) + FATAL_ERROR("%s\n", mapdata_err.c_str()); + + Json layouts_data = Json::parse(layouts_json_text, layouts_err); + if (layouts_data == Json()) + FATAL_ERROR("%s\n", layouts_err.c_str()); + + string header_text = generate_map_header_text(map_data, layouts_data, version); + string events_text = generate_map_events_text(map_data); + string connections_text = generate_map_connections_text(map_data); + + string files_dir = get_directory_name(map_filepath); + write_text_file(files_dir + "header.inc", header_text); + write_text_file(files_dir + "events.inc", events_text); + write_text_file(files_dir + "connections.inc", connections_text); +} + +string generate_groups_text(Json groups_data) { + ostringstream text; + + for (auto &key : groups_data["group_order"].array_items()) { + string group = key.string_value(); + text << group << "::\n"; + auto maps = groups_data[group].array_items(); + for (Json &map_name : maps) + text << "\t.4byte " << map_name.string_value() << "\n"; + text << "\n"; + } + + text << "\t.align 2\n" << "gMapGroups::\n"; + for (auto &group : groups_data["group_order"].array_items()) + text << "\t.4byte " << group.string_value() << "\n"; + text << "\n"; + + return text.str(); +} + +string generate_connections_text(Json groups_data) { + vector<Json> map_names; + + for (auto &group : groups_data["group_order"].array_items()) + for (auto map_name : groups_data[group.string_value()].array_items()) + map_names.push_back(map_name); + + vector<Json> connections_include_order = groups_data["connections_include_order"].array_items(); + + if (connections_include_order.size() > 0) + sort(map_names.begin(), map_names.end(), [connections_include_order](const Json &a, const Json &b) { + auto iter_a = find(connections_include_order.begin(), connections_include_order.end(), a); + if (iter_a == connections_include_order.end()) + iter_a = connections_include_order.begin() + numeric_limits<int>::max(); + auto iter_b = find(connections_include_order.begin(), connections_include_order.end(), b); + if (iter_b == connections_include_order.end()) + iter_b = connections_include_order.begin() + numeric_limits<int>::max(); + return iter_a < iter_b; + }); + + ostringstream text; + + for (Json map_name : map_names) + text << "\t.include \"data/maps/" << map_name.string_value() << "/connections.inc\"\n"; + + return text.str(); +} + +string generate_headers_text(Json groups_data) { + vector<string> map_names; + + for (auto &group : groups_data["group_order"].array_items()) + for (auto map_name : groups_data[group.string_value()].array_items()) + map_names.push_back(map_name.string_value()); + + ostringstream text; + + for (string map_name : map_names) + text << "\t.include \"data/maps/" << map_name << "/header.inc\"\n"; + + return text.str(); +} + +string generate_events_text(Json groups_data) { + vector<string> map_names; + + for (auto &group : groups_data["group_order"].array_items()) + for (auto map_name : groups_data[group.string_value()].array_items()) + map_names.push_back(map_name.string_value()); + + ostringstream text; + + for (string map_name : map_names) + text << "\t.include \"data/maps/" << map_name << "/events.inc\"\n"; + + return text.str(); +} + +string generate_map_constants_text(string groups_filepath, Json groups_data) { + string file_dir = get_directory_name(groups_filepath); + char dir_separator = file_dir.back(); + + ostringstream text; + + text << "#ifndef GUARD_CONSTANTS_MAP_GROUPS_H\n" + << "#define GUARD_CONSTANTS_MAP_GROUPS_H\n\n"; + + int group_num = 0; + + for (auto &group : groups_data["group_order"].array_items()) { + text << "// Map Group " << group_num << "\n"; + vector<Json> map_ids; + size_t max_length = 0; + + for (auto &map_name : groups_data[group.string_value()].array_items()) { + string header_filepath = file_dir + map_name.string_value() + dir_separator + "map.json"; + string err_str; + Json map_data = Json::parse(read_text_file(header_filepath), err_str); + map_ids.push_back(map_data["id"]); + if (map_data["id"].string_value().length() > max_length) + max_length = map_data["id"].string_value().length(); + } + + int map_id_num = 0; + for (Json map_id : map_ids) { + text << "#define " << map_id.string_value() << string((max_length - map_id.string_value().length() + 1), ' ') + << "(" << map_id_num++ << " | (" << group_num << " << 8))\n"; + } + text << "\n"; + + group_num++; + } + + text << "#define MAP_GROUPS_COUNT " << group_num << "\n\n"; + text << "#endif // GUARD_CONSTANTS_MAP_GROUPS_H\n"; + + return text.str(); +} + +void process_groups(string groups_filepath) { + string err; + Json groups_data = Json::parse(read_text_file(groups_filepath), err); + + if (groups_data == Json()) + FATAL_ERROR("%s\n", err.c_str()); + + string groups_text = generate_groups_text(groups_data); + string connections_text = generate_connections_text(groups_data); + string headers_text = generate_headers_text(groups_data); + string events_text = generate_events_text(groups_data); + string map_header_text = generate_map_constants_text(groups_filepath, groups_data); + + string file_dir = get_directory_name(groups_filepath); + char s = file_dir.back(); + + write_text_file(file_dir + "groups.inc", groups_text); + write_text_file(file_dir + "connections.inc", connections_text); + write_text_file(file_dir + "headers.inc", headers_text); + write_text_file(file_dir + "events.inc", events_text); + write_text_file(file_dir + ".." + s + ".." + s + "include" + s + "constants" + s + "map_groups.h", map_header_text); +} + +string generate_layout_headers_text(Json layouts_data) { + ostringstream text; + + for (auto &layout : layouts_data["layouts"].array_items()) { + string border_label = layout["name"].string_value() + "_Border"; + string blockdata_label = layout["name"].string_value() + "_Blockdata"; + text << border_label << "::\n" + << "\t.incbin \"" << layout["border_filepath"].string_value() << "\"\n\n" + << blockdata_label << "::\n" + << "\t.incbin \"" << layout["blockdata_filepath"].string_value() << "\"\n\n" + << "\t.align 2\n" + << layout["name"].string_value() << "::\n" + << "\t.4byte " << layout["width"].int_value() << "\n" + << "\t.4byte " << layout["height"].int_value() << "\n" + << "\t.4byte " << border_label << "\n" + << "\t.4byte " << blockdata_label << "\n" + << "\t.4byte " << layout["primary_tileset"].string_value() << "\n" + << "\t.4byte " << layout["secondary_tileset"].string_value() << "\n\n"; + } + + return text.str(); +} + +string generate_layouts_table_text(Json layouts_data) { + ostringstream text; + + text << "\t.align 2\n" + << layouts_data["layouts_table_label"].string_value() << "::\n"; + + for (auto &layout : layouts_data["layouts"].array_items()) + text << "\t.4byte " << layout["name"].string_value() << "\n"; + + return text.str(); +} + +string generate_layouts_constants_text(Json layouts_data) { + ostringstream text; + + text << "#ifndef GUARD_CONSTANTS_LAYOUTS_H\n" + << "#define GUARD_CONSTANTS_LAYOUTS_H\n\n"; + + int i = 0; + for (auto &layout : layouts_data["layouts"].array_items()) + text << "#define " << layout["id"].string_value() << " " << ++i << "\n"; + + text << "\n#endif // GUARD_CONSTANTS_LAYOUTS_H\n"; + + return text.str(); +} + +void process_layouts(string layouts_filepath) { + string err; + Json layouts_data = Json::parse(read_text_file(layouts_filepath), err); + + if (layouts_data == Json()) + FATAL_ERROR("%s\n", err.c_str()); + + string layout_headers_text = generate_layout_headers_text(layouts_data); + string layouts_table_text = generate_layouts_table_text(layouts_data); + string layouts_constants_text = generate_layouts_constants_text(layouts_data); + + string file_dir = get_directory_name(layouts_filepath); + char s = file_dir.back(); + + write_text_file(file_dir + "layouts.inc", layout_headers_text); + write_text_file(file_dir + "layouts_table.inc", layouts_table_text); + write_text_file(file_dir + ".." + s + ".." + s + "include" + s + "constants" + s + "layouts.h", layouts_constants_text); +} + +int main(int argc, char *argv[]) { + if (argc < 3) + FATAL_ERROR("USAGE: mapjson <mode> <game-version> [options]\n"); + + char *version_arg = argv[2]; + string version(version_arg); + if (version != "emerald" && version != "ruby") + FATAL_ERROR("ERROR: <game-version> must be 'emerald' or 'ruby'.\n"); + + char *mode_arg = argv[1]; + string mode(mode_arg); + if (mode != "layouts" && mode != "map" && mode != "groups") + FATAL_ERROR("ERROR: <mode> must be 'layouts', 'map', or 'groups'.\n"); + + if (mode == "map") { + if (argc != 5) + FATAL_ERROR("USAGE: mapjson map <game-version> <map_file> <layouts_file>\n"); + + string filepath(argv[3]); + string layouts_filepath(argv[4]); + + process_map(filepath, layouts_filepath, version); + } + else if (mode == "groups") { + if (argc != 4) + FATAL_ERROR("USAGE: mapjson groups <game-version> <groups_file>\n"); + + string filepath(argv[3]); + + process_groups(filepath); + } + else if (mode == "layouts") { + if (argc != 4) + FATAL_ERROR("USAGE: mapjson layouts <game-version> <layouts_file>\n"); + + string filepath(argv[3]); + + process_layouts(filepath); + } + + return 0; +} diff --git a/tools/mapjson/mapjson.h b/tools/mapjson/mapjson.h new file mode 100644 index 000000000..6e961a28e --- /dev/null +++ b/tools/mapjson/mapjson.h @@ -0,0 +1,31 @@ +// mapjson.h + +#ifndef MAPJSON_H +#define MAPJSON_H + +#include <cstdio> +using std::fprintf; using std::exit; + +#include <cstdlib> + +#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 + +#endif // MAPJSON_H diff --git a/tools/scaninc/Makefile b/tools/scaninc/Makefile index 367a3350b..53c9d0060 100644 --- a/tools/scaninc/Makefile +++ b/tools/scaninc/Makefile @@ -2,9 +2,9 @@ CXX = g++ CXXFLAGS = -Wall -Werror -std=c++11 -O2 -SRCS = scaninc.cpp c_file.cpp asm_file.cpp +SRCS = scaninc.cpp c_file.cpp asm_file.cpp source_file.cpp -HEADERS := scaninc.h asm_file.h c_file.h +HEADERS := scaninc.h asm_file.h c_file.h source_file.h .PHONY: clean diff --git a/tools/scaninc/asm_file.cpp b/tools/scaninc/asm_file.cpp index 6322749e2..109e604a2 100644 --- a/tools/scaninc/asm_file.cpp +++ b/tools/scaninc/asm_file.cpp @@ -64,7 +64,8 @@ IncDirectiveType AsmFile::ReadUntilIncDirective(std::string &path) IncDirectiveType incDirectiveType = IncDirectiveType::None; - if (PeekChar() == '.') + char c = PeekChar(); + if (c == '.' || c == '#') { m_pos++; diff --git a/tools/scaninc/c_file.cpp b/tools/scaninc/c_file.cpp index d470f959d..595f366cb 100644 --- a/tools/scaninc/c_file.cpp +++ b/tools/scaninc/c_file.cpp @@ -134,7 +134,7 @@ bool CFile::ConsumeComment() if (m_buffer[m_pos] == '/' && m_buffer[m_pos + 1] == '*') { m_pos += 2; - while (m_buffer[m_pos] != '*' && m_buffer[m_pos + 1] != '/') + while (m_buffer[m_pos] != '*' || m_buffer[m_pos + 1] != '/') { if (m_buffer[m_pos] == 0) return false; diff --git a/tools/scaninc/scaninc.cpp b/tools/scaninc/scaninc.cpp index 3dc221479..b95cbd033 100644 --- a/tools/scaninc/scaninc.cpp +++ b/tools/scaninc/scaninc.cpp @@ -25,8 +25,7 @@ #include <set> #include <string> #include "scaninc.h" -#include "asm_file.h" -#include "c_file.h" +#include "source_file.h" bool CanOpenFile(std::string path) { @@ -46,7 +45,7 @@ int main(int argc, char **argv) std::queue<std::string> filesToProcess; std::set<std::string> dependencies; - std::list<std::string> includeDirs; + std::vector<std::string> includeDirs; argc--; argv++; @@ -63,7 +62,7 @@ int main(int argc, char **argv) argv++; includeDir = std::string(argv[0]); } - if (includeDir.back() != '/') + if (!includeDir.empty() && includeDir.back() != '/') { includeDir += '/'; } @@ -83,79 +82,36 @@ int main(int argc, char **argv) std::string initialPath(argv[0]); - std::size_t pos = initialPath.find_last_of('.'); + filesToProcess.push(initialPath); - if (pos == std::string::npos) - FATAL_ERROR("no file extension in path \"%s\"\n", initialPath.c_str()); - - std::string extension = initialPath.substr(pos + 1); - - std::string srcDir(""); - std::size_t slash = initialPath.rfind('/'); - if (slash != std::string::npos) - { - srcDir = initialPath.substr(0, slash + 1); - } - includeDirs.push_back(srcDir); - - if (extension == "c" || extension == "h") + while (!filesToProcess.empty()) { - filesToProcess.push(initialPath); + std::string filePath = filesToProcess.front(); + SourceFile file(filePath); + filesToProcess.pop(); - while (!filesToProcess.empty()) + includeDirs.push_back(file.GetSrcDir()); + for (auto incbin : file.GetIncbins()) { - CFile file(filesToProcess.front()); - filesToProcess.pop(); - - file.FindIncbins(); - for (auto incbin : file.GetIncbins()) - { - dependencies.insert(incbin); - } - for (auto include : file.GetIncludes()) + dependencies.insert(incbin); + } + for (auto include : file.GetIncludes()) + { + for (auto includeDir : includeDirs) { - for (auto includeDir : includeDirs) + std::string path(includeDir + include); + if (CanOpenFile(path)) { - std::string path(includeDir + include); - if (CanOpenFile(path)) + bool inserted = dependencies.insert(path).second; + if (inserted) { - bool inserted = dependencies.insert(path).second; - if (inserted) - { - filesToProcess.push(path); - } - break; + filesToProcess.push(path); } + break; } } } - } - else if (extension == "s" || extension == "inc") - { - filesToProcess.push(initialPath); - - while (!filesToProcess.empty()) - { - AsmFile file(filesToProcess.front()); - - 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 - && CanOpenFile(path)) - filesToProcess.push(path); - } - } - } - else - { - FATAL_ERROR("unknown extension \"%s\"\n", extension.c_str()); + includeDirs.pop_back(); } for (const std::string &path : dependencies) diff --git a/tools/scaninc/source_file.cpp b/tools/scaninc/source_file.cpp new file mode 100644 index 000000000..f23ff6db6 --- /dev/null +++ b/tools/scaninc/source_file.cpp @@ -0,0 +1,125 @@ +// Copyright(c) 2019 Phlosioneer +// +// 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. + +#include <new> +#include "source_file.h" + + +SourceFileType GetFileType(std::string& path) +{ + std::size_t pos = path.find_last_of('.'); + + if (pos == std::string::npos) + FATAL_ERROR("no file extension in path \"%s\"\n", path.c_str()); + + std::string extension = path.substr(pos + 1); + + if (extension == "c") + return SourceFileType::Cpp; + else if (extension == "s") + return SourceFileType::Asm; + else if (extension == "h") + return SourceFileType::Header; + else if (extension == "inc") + return SourceFileType::Inc; + else + FATAL_ERROR("Unrecognized extension \"%s\"\n", extension.c_str()); + + // Unreachable + return SourceFileType::Cpp; +} + +std::string GetDir(std::string& path) +{ + std::size_t slash = path.rfind('/'); + + if (slash != std::string::npos) + return path.substr(0, slash + 1); + else + return std::string(""); +} + +SourceFile::SourceFile(std::string path) +{ + m_file_type = GetFileType(path); + + m_src_dir = GetDir(path); + + if (m_file_type == SourceFileType::Cpp + || m_file_type == SourceFileType::Header) + { + new (&m_source_file.c_file) CFile(path); + m_source_file.c_file.FindIncbins(); + } + else + { + AsmFile file(path); + std::set<std::string> incbins; + std::set<std::string> includes; + + IncDirectiveType incDirectiveType; + std::string outputPath; + + while ((incDirectiveType = file.ReadUntilIncDirective(outputPath)) != IncDirectiveType::None) + { + if (incDirectiveType == IncDirectiveType::Include) + includes.insert(outputPath); + else + incbins.insert(outputPath); + } + + new (&m_source_file.asm_wrapper) SourceFile::InnerUnion::AsmWrapper{incbins, includes}; + } +} + +SourceFile::~SourceFile() +{ + if (m_file_type == SourceFileType::Cpp || m_file_type == SourceFileType::Header) + { + m_source_file.c_file.~CFile(); + } + else + { + m_source_file.asm_wrapper.asm_incbins.~set(); + m_source_file.asm_wrapper.asm_includes.~set(); + } +} + +const std::set<std::string>& SourceFile::GetIncbins() +{ + if (m_file_type == SourceFileType::Cpp || m_file_type == SourceFileType::Header) + return m_source_file.c_file.GetIncbins(); + else + return m_source_file.asm_wrapper.asm_incbins; +} + +const std::set<std::string>& SourceFile::GetIncludes() +{ + if (m_file_type == SourceFileType::Cpp || m_file_type == SourceFileType::Header) + return m_source_file.c_file.GetIncludes(); + else + return m_source_file.asm_wrapper.asm_includes; +} + +std::string& SourceFile::GetSrcDir() +{ + return m_src_dir; +} + diff --git a/tools/scaninc/source_file.h b/tools/scaninc/source_file.h new file mode 100644 index 000000000..f7b6412bd --- /dev/null +++ b/tools/scaninc/source_file.h @@ -0,0 +1,71 @@ +// Copyright(c) 2019 Phlosioneer +// +// 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. + +#ifndef SOURCE_FILE_H +#define SOURCE_FILE_H + +#include <string> +#include "scaninc.h" +#include "asm_file.h" +#include "c_file.h" + +enum class SourceFileType +{ + Cpp, + Header, + Asm, + Inc +}; + +SourceFileType GetFileType(std::string& path); + +class SourceFile +{ +public: + + SourceFile(std::string path); + ~SourceFile(); + SourceFile(SourceFile const&) = delete; + SourceFile(SourceFile&&) = delete; + SourceFile& operator =(SourceFile const&) = delete; + SourceFile& operator =(SourceFile&&) = delete; + bool HasIncbins(); + const std::set<std::string>& GetIncbins(); + const std::set<std::string>& GetIncludes(); + std::string& GetSrcDir(); + +private: + union InnerUnion { + CFile c_file; + struct AsmWrapper { + std::set<std::string> asm_incbins; + std::set<std::string> asm_includes; + } asm_wrapper; + + // Construction and destruction handled by SourceFile. + InnerUnion() {}; + ~InnerUnion() {}; + } m_source_file; + SourceFileType m_file_type; + std::string m_src_dir; +}; + +#endif // SOURCE_FILE_H + |