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 + | 
