Add mapjson; update gbagfx, scaninc
This commit is contained in:
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
+3
-194
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,397 @@
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.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");
|
||||
}
|
||||
@@ -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
|
||||
+58
-60
@@ -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 },
|
||||
|
||||
@@ -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;
|
||||
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
mapjson
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -64,7 +64,8 @@ IncDirectiveType AsmFile::ReadUntilIncDirective(std::string &path)
|
||||
|
||||
IncDirectiveType incDirectiveType = IncDirectiveType::None;
|
||||
|
||||
if (PeekChar() == '.')
|
||||
char c = PeekChar();
|
||||
if (c == '.' || c == '#')
|
||||
{
|
||||
m_pos++;
|
||||
|
||||
|
||||
+22
-66
@@ -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)
|
||||
while (!filesToProcess.empty())
|
||||
{
|
||||
srcDir = initialPath.substr(0, slash + 1);
|
||||
}
|
||||
includeDirs.push_back(srcDir);
|
||||
std::string filePath = filesToProcess.front();
|
||||
SourceFile file(filePath);
|
||||
filesToProcess.pop();
|
||||
|
||||
if (extension == "c" || extension == "h")
|
||||
{
|
||||
filesToProcess.push(initialPath);
|
||||
|
||||
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())
|
||||
{
|
||||
for (auto includeDir : includeDirs)
|
||||
{
|
||||
dependencies.insert(incbin);
|
||||
}
|
||||
for (auto include : file.GetIncludes())
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user