Sync tools directory
This commit is contained in:
+10
-4
@@ -1,4 +1,4 @@
|
|||||||
CC = gcc
|
CC ?= gcc
|
||||||
|
|
||||||
CFLAGS = -Wall -Wextra -Werror -Wno-sign-compare -std=c11 -O3 -flto -DPNG_SKIP_SETJMP_CHECK
|
CFLAGS = -Wall -Wextra -Werror -Wno-sign-compare -std=c11 -O3 -flto -DPNG_SKIP_SETJMP_CHECK
|
||||||
CFLAGS += $(shell pkg-config --cflags libpng)
|
CFLAGS += $(shell pkg-config --cflags libpng)
|
||||||
@@ -8,15 +8,21 @@ LDFLAGS += $(shell pkg-config --libs-only-L libpng)
|
|||||||
|
|
||||||
SRCS = main.c convert_png.c gfx.c jasc_pal.c lz.c rl.c util.c font.c huff.c
|
SRCS = main.c convert_png.c gfx.c jasc_pal.c lz.c rl.c util.c font.c huff.c
|
||||||
|
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
EXE := .exe
|
||||||
|
else
|
||||||
|
EXE :=
|
||||||
|
endif
|
||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|
||||||
all: gbagfx
|
all: gbagfx$(EXE)
|
||||||
@:
|
@:
|
||||||
|
|
||||||
gbagfx-debug: $(SRCS) convert_png.h gfx.h global.h jasc_pal.h lz.h rl.h util.h font.h
|
gbagfx-debug$(EXE): $(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)
|
$(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
|
gbagfx$(EXE): $(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)
|
$(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) $(LIBS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ static unsigned char *ConvertBitDepth(unsigned char *src, int srcBitDepth, int d
|
|||||||
|
|
||||||
for (j = 8 - srcBitDepth; j >= 0; j -= srcBitDepth)
|
for (j = 8 - srcBitDepth; j >= 0; j -= srcBitDepth)
|
||||||
{
|
{
|
||||||
unsigned char pixel = (srcByte >> j) % (1 << destBitDepth);
|
unsigned char pixel = ((srcByte >> j) % (1 << srcBitDepth)) % (1 << destBitDepth);
|
||||||
*dest |= pixel << destBit;
|
*dest |= pixel << destBit;
|
||||||
destBit -= destBitDepth;
|
destBit -= destBitDepth;
|
||||||
if (destBit < 0)
|
if (destBit < 0)
|
||||||
@@ -130,7 +130,6 @@ void ReadPng(char *path, struct Image *image)
|
|||||||
FATAL_ERROR("Bit depth of image must be 1, 2, 4, or 8.\n");
|
FATAL_ERROR("Bit depth of image must be 1, 2, 4, or 8.\n");
|
||||||
image->pixels = ConvertBitDepth(image->pixels, bit_depth, image->bitDepth, image->width * image->height);
|
image->pixels = ConvertBitDepth(image->pixels, bit_depth, image->bitDepth, image->width * image->height);
|
||||||
free(src);
|
free(src);
|
||||||
image->bitDepth = bit_depth;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+77
-15
@@ -204,6 +204,18 @@ static void ConvertToTiles8Bpp(unsigned char *src, unsigned char *dest, int numT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For untiled, plain images
|
||||||
|
static void CopyPlainPixels(unsigned char *src, unsigned char *dest, int size, int dataWidth, bool invertColors)
|
||||||
|
{
|
||||||
|
if (dataWidth == 0) return;
|
||||||
|
for (int i = 0; i < size; i += dataWidth) {
|
||||||
|
for (int j = dataWidth; j > 0; j--) {
|
||||||
|
unsigned char pixels = src[i + j - 1];
|
||||||
|
*dest++ = invertColors ? ~pixels : pixels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void DecodeAffineTilemap(unsigned char *input, unsigned char *output, unsigned char *tilemap, int tileSize, int numTiles)
|
static void DecodeAffineTilemap(unsigned char *input, unsigned char *output, unsigned char *tilemap, int tileSize, int numTiles)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < numTiles; i++)
|
for (int i = 0; i < numTiles; i++)
|
||||||
@@ -345,9 +357,9 @@ static unsigned char *DecodeTilemap(unsigned char *tiles, struct Tilemap *tilema
|
|||||||
return decoded;
|
return decoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors)
|
void ReadTileImage(char *path, int tilesWidth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors)
|
||||||
{
|
{
|
||||||
int tileSize = bitDepth * 8;
|
int tileSize = image->bitDepth * 8;
|
||||||
|
|
||||||
int fileSize;
|
int fileSize;
|
||||||
unsigned char *buffer = ReadWholeFile(path, &fileSize);
|
unsigned char *buffer = ReadWholeFile(path, &fileSize);
|
||||||
@@ -355,26 +367,25 @@ void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int
|
|||||||
int numTiles = fileSize / tileSize;
|
int numTiles = fileSize / tileSize;
|
||||||
if (image->tilemap.data.affine != NULL)
|
if (image->tilemap.data.affine != NULL)
|
||||||
{
|
{
|
||||||
int outTileSize = (bitDepth == 4 && image->palette.numColors > 16) ? 64 : tileSize;
|
int outTileSize = (image->bitDepth == 4 && image->palette.numColors > 16) ? 64 : tileSize;
|
||||||
buffer = DecodeTilemap(buffer, &image->tilemap, &numTiles, image->isAffine, tileSize, outTileSize, bitDepth);
|
buffer = DecodeTilemap(buffer, &image->tilemap, &numTiles, image->isAffine, tileSize, outTileSize, image->bitDepth);
|
||||||
if (outTileSize == 64)
|
if (outTileSize == 64)
|
||||||
{
|
{
|
||||||
tileSize = 64;
|
tileSize = 64;
|
||||||
image->bitDepth = bitDepth = 8;
|
image->bitDepth = 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int tilesHeight = (numTiles + tilesWidth - 1) / tilesWidth;
|
int tilesHeight = (numTiles + tilesWidth - 1) / tilesWidth;
|
||||||
|
|
||||||
if (tilesWidth % metatileWidth != 0)
|
if (tilesWidth % metatileWidth != 0)
|
||||||
FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)", tilesWidth, metatileWidth);
|
FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)\n", tilesWidth, metatileWidth);
|
||||||
|
|
||||||
if (tilesHeight % metatileHeight != 0)
|
if (tilesHeight % metatileHeight != 0)
|
||||||
FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)", tilesHeight, metatileHeight);
|
FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)\n", tilesHeight, metatileHeight);
|
||||||
|
|
||||||
image->width = tilesWidth * 8;
|
image->width = tilesWidth * 8;
|
||||||
image->height = tilesHeight * 8;
|
image->height = tilesHeight * 8;
|
||||||
image->bitDepth = bitDepth;
|
|
||||||
image->pixels = calloc(tilesWidth * tilesHeight, tileSize);
|
image->pixels = calloc(tilesWidth * tilesHeight, tileSize);
|
||||||
|
|
||||||
if (image->pixels == NULL)
|
if (image->pixels == NULL)
|
||||||
@@ -382,7 +393,7 @@ void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int
|
|||||||
|
|
||||||
int metatilesWide = tilesWidth / metatileWidth;
|
int metatilesWide = tilesWidth / metatileWidth;
|
||||||
|
|
||||||
switch (bitDepth) {
|
switch (image->bitDepth) {
|
||||||
case 1:
|
case 1:
|
||||||
ConvertFromTiles1Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
|
ConvertFromTiles1Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
|
||||||
break;
|
break;
|
||||||
@@ -397,9 +408,9 @@ void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int
|
|||||||
free(buffer);
|
free(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WriteImage(char *path, enum NumTilesMode numTilesMode, int numTiles, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors)
|
void WriteTileImage(char *path, enum NumTilesMode numTilesMode, int numTiles, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors)
|
||||||
{
|
{
|
||||||
int tileSize = bitDepth * 8;
|
int tileSize = image->bitDepth * 8;
|
||||||
|
|
||||||
if (image->width % 8 != 0)
|
if (image->width % 8 != 0)
|
||||||
FATAL_ERROR("The width in pixels (%d) isn't a multiple of 8.\n", image->width);
|
FATAL_ERROR("The width in pixels (%d) isn't a multiple of 8.\n", image->width);
|
||||||
@@ -411,10 +422,10 @@ void WriteImage(char *path, enum NumTilesMode numTilesMode, int numTiles, int bi
|
|||||||
int tilesHeight = image->height / 8;
|
int tilesHeight = image->height / 8;
|
||||||
|
|
||||||
if (tilesWidth % metatileWidth != 0)
|
if (tilesWidth % metatileWidth != 0)
|
||||||
FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)", tilesWidth, metatileWidth);
|
FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)\n", tilesWidth, metatileWidth);
|
||||||
|
|
||||||
if (tilesHeight % metatileHeight != 0)
|
if (tilesHeight % metatileHeight != 0)
|
||||||
FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)", tilesHeight, metatileHeight);
|
FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)\n", tilesHeight, metatileHeight);
|
||||||
|
|
||||||
int maxNumTiles = tilesWidth * tilesHeight;
|
int maxNumTiles = tilesWidth * tilesHeight;
|
||||||
|
|
||||||
@@ -432,7 +443,7 @@ void WriteImage(char *path, enum NumTilesMode numTilesMode, int numTiles, int bi
|
|||||||
|
|
||||||
int metatilesWide = tilesWidth / metatileWidth;
|
int metatilesWide = tilesWidth / metatileWidth;
|
||||||
|
|
||||||
switch (bitDepth) {
|
switch (image->bitDepth) {
|
||||||
case 1:
|
case 1:
|
||||||
ConvertToTiles1Bpp(image->pixels, buffer, maxNumTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
|
ConvertToTiles1Bpp(image->pixels, buffer, maxNumTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
|
||||||
break;
|
break;
|
||||||
@@ -468,9 +479,60 @@ void WriteImage(char *path, enum NumTilesMode numTilesMode, int numTiles, int bi
|
|||||||
free(buffer);
|
free(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ReadPlainImage(char *path, int dataWidth, struct Image *image, bool invertColors)
|
||||||
|
{
|
||||||
|
int fileSize;
|
||||||
|
unsigned char *buffer = ReadWholeFile(path, &fileSize);
|
||||||
|
|
||||||
|
if (fileSize % dataWidth != 0)
|
||||||
|
FATAL_ERROR("The image data size (%d) isn't a multiple of the specified data width %d.\n", fileSize, dataWidth);
|
||||||
|
|
||||||
|
// png scanlines have wasted bits if they do not align to byte boundaries.
|
||||||
|
// pngs misaligned in this way are not currently handled.
|
||||||
|
int pixelsPerByte = 8 / image->bitDepth;
|
||||||
|
if (image->width % pixelsPerByte != 0)
|
||||||
|
FATAL_ERROR("The width in pixels (%d) isn't a multiple of %d.\n", image->width, pixelsPerByte);
|
||||||
|
|
||||||
|
int numPixels = fileSize * pixelsPerByte;
|
||||||
|
image->height = (numPixels + image->width - 1) / image->width;
|
||||||
|
image->pixels = calloc(image->width * image->height * image->bitDepth / 8, 1);
|
||||||
|
|
||||||
|
if (image->pixels == NULL)
|
||||||
|
FATAL_ERROR("Failed to allocate memory for pixels.\n");
|
||||||
|
|
||||||
|
CopyPlainPixels(buffer, image->pixels, fileSize, dataWidth, invertColors);
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WritePlainImage(char *path, int dataWidth, struct Image *image, bool invertColors)
|
||||||
|
{
|
||||||
|
int bufferSize = image->width * image->height * image->bitDepth / 8;
|
||||||
|
|
||||||
|
if (bufferSize % dataWidth != 0)
|
||||||
|
FATAL_ERROR("The image data size (%d) isn't a multiple of the specified data width %d.\n", bufferSize, dataWidth);
|
||||||
|
|
||||||
|
// png scanlines have wasted bits if they do not align to byte boundaries.
|
||||||
|
// pngs misaligned in this way are not currently handled.
|
||||||
|
int pixelsPerByte = 8 / image->bitDepth;
|
||||||
|
if (image->width % pixelsPerByte != 0)
|
||||||
|
FATAL_ERROR("The width in pixels (%d) isn't a multiple of %d.\n", image->width, pixelsPerByte);
|
||||||
|
|
||||||
|
unsigned char *buffer = malloc(bufferSize);
|
||||||
|
|
||||||
|
if (buffer == NULL)
|
||||||
|
FATAL_ERROR("Failed to allocate memory for pixels.\n");
|
||||||
|
|
||||||
|
CopyPlainPixels(image->pixels, buffer, bufferSize, dataWidth, invertColors);
|
||||||
|
|
||||||
|
WriteWholeFile(path, buffer, bufferSize);
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
void FreeImage(struct Image *image)
|
void FreeImage(struct Image *image)
|
||||||
{
|
{
|
||||||
if (image->tilemap.data.affine != NULL)
|
if (image->tilemap.data.affine != NULL)
|
||||||
{
|
{
|
||||||
free(image->tilemap.data.affine);
|
free(image->tilemap.data.affine);
|
||||||
image->tilemap.data.affine = NULL;
|
image->tilemap.data.affine = NULL;
|
||||||
|
|||||||
+4
-2
@@ -50,8 +50,10 @@ enum NumTilesMode {
|
|||||||
NUM_TILES_ERROR,
|
NUM_TILES_ERROR,
|
||||||
};
|
};
|
||||||
|
|
||||||
void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors);
|
void ReadTileImage(char *path, int tilesWidth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors);
|
||||||
void WriteImage(char *path, enum NumTilesMode numTilesMode, int numTiles, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors);
|
void WriteTileImage(char *path, enum NumTilesMode numTilesMode, int numTiles, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors);
|
||||||
|
void ReadPlainImage(char *path, int dataWidth, struct Image *image, bool invertColors);
|
||||||
|
void WritePlainImage(char *path, int dataWidth, struct Image *image, bool invertColors);
|
||||||
void FreeImage(struct Image *image);
|
void FreeImage(struct Image *image);
|
||||||
void ReadGbaPalette(char *path, struct Palette *palette);
|
void ReadGbaPalette(char *path, struct Palette *palette);
|
||||||
void WriteGbaPalette(char *path, struct Palette *palette);
|
void WriteGbaPalette(char *path, struct Palette *palette);
|
||||||
|
|||||||
@@ -46,10 +46,14 @@ void ReadJascPaletteLine(FILE *fp, char *line)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (c == '\n')
|
if (c == '\n')
|
||||||
FATAL_ERROR("LF line endings aren't supported.\n");
|
{
|
||||||
|
line[length] = 0;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (c == EOF)
|
if (c == EOF)
|
||||||
FATAL_ERROR("Unexpected EOF. No CRLF at end of file.\n");
|
FATAL_ERROR("Unexpected EOF. No LF or CRLF at end of file.\n");
|
||||||
|
|
||||||
if (c == 0)
|
if (c == 0)
|
||||||
FATAL_ERROR("NUL character in file.\n");
|
FATAL_ERROR("NUL character in file.\n");
|
||||||
|
|||||||
+58
-14
@@ -25,6 +25,9 @@ void ConvertGbaToPng(char *inputPath, char *outputPath, struct GbaToPngOptions *
|
|||||||
{
|
{
|
||||||
struct Image image;
|
struct Image image;
|
||||||
|
|
||||||
|
image.bitDepth = options->bitDepth;
|
||||||
|
image.tilemap.data.affine = NULL;
|
||||||
|
|
||||||
if (options->paletteFilePath != NULL)
|
if (options->paletteFilePath != NULL)
|
||||||
{
|
{
|
||||||
char *paletteFileExtension = GetFileExtensionAfterDot(options->paletteFilePath);
|
char *paletteFileExtension = GetFileExtensionAfterDot(options->paletteFilePath);
|
||||||
@@ -45,22 +48,25 @@ void ConvertGbaToPng(char *inputPath, char *outputPath, struct GbaToPngOptions *
|
|||||||
image.hasPalette = false;
|
image.hasPalette = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options->tilemapFilePath != NULL)
|
if (options->isTiled)
|
||||||
{
|
{
|
||||||
int fileSize;
|
if (options->tilemapFilePath != NULL)
|
||||||
image.tilemap.data.affine = ReadWholeFile(options->tilemapFilePath, &fileSize);
|
{
|
||||||
if (options->isAffineMap && options->bitDepth != 8)
|
int fileSize;
|
||||||
FATAL_ERROR("affine maps are necessarily 8bpp\n");
|
image.tilemap.data.affine = ReadWholeFile(options->tilemapFilePath, &fileSize);
|
||||||
image.isAffine = options->isAffineMap;
|
if (options->isAffineMap && options->bitDepth != 8)
|
||||||
image.tilemap.size = fileSize;
|
FATAL_ERROR("affine maps are necessarily 8bpp\n");
|
||||||
|
image.isAffine = options->isAffineMap;
|
||||||
|
image.tilemap.size = fileSize;
|
||||||
|
}
|
||||||
|
ReadTileImage(inputPath, options->width, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
image.tilemap.data.affine = NULL;
|
image.width = options->width;
|
||||||
|
ReadPlainImage(inputPath, options->dataWidth, &image, !image.hasPalette);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadImage(inputPath, options->width, options->bitDepth, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette);
|
|
||||||
|
|
||||||
image.hasTransparency = options->hasTransparency;
|
image.hasTransparency = options->hasTransparency;
|
||||||
|
|
||||||
WritePng(outputPath, &image);
|
WritePng(outputPath, &image);
|
||||||
@@ -77,7 +83,10 @@ void ConvertPngToGba(char *inputPath, char *outputPath, struct PngToGbaOptions *
|
|||||||
|
|
||||||
ReadPng(inputPath, &image);
|
ReadPng(inputPath, &image);
|
||||||
|
|
||||||
WriteImage(outputPath, options->numTilesMode, options->numTiles, options->bitDepth, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette);
|
if (options->isTiled)
|
||||||
|
WriteTileImage(outputPath, options->numTilesMode, options->numTiles, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette);
|
||||||
|
else
|
||||||
|
WritePlainImage(outputPath, options->dataWidth, &image, !image.hasPalette);
|
||||||
|
|
||||||
FreeImage(&image);
|
FreeImage(&image);
|
||||||
}
|
}
|
||||||
@@ -94,6 +103,8 @@ void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **a
|
|||||||
options.metatileHeight = 1;
|
options.metatileHeight = 1;
|
||||||
options.tilemapFilePath = NULL;
|
options.tilemapFilePath = NULL;
|
||||||
options.isAffineMap = false;
|
options.isAffineMap = false;
|
||||||
|
options.isTiled = true;
|
||||||
|
options.dataWidth = 1;
|
||||||
|
|
||||||
for (int i = 3; i < argc; i++)
|
for (int i = 3; i < argc; i++)
|
||||||
{
|
{
|
||||||
@@ -162,6 +173,22 @@ void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **a
|
|||||||
{
|
{
|
||||||
options.isAffineMap = true;
|
options.isAffineMap = true;
|
||||||
}
|
}
|
||||||
|
else if (strcmp(option, "-plain") == 0)
|
||||||
|
{
|
||||||
|
options.isTiled = false;
|
||||||
|
}
|
||||||
|
else if (strcmp(option, "-data_width") == 0)
|
||||||
|
{
|
||||||
|
if (i + 1 >= argc)
|
||||||
|
FATAL_ERROR("No data width value following \"-data_width\".\n");
|
||||||
|
i++;
|
||||||
|
|
||||||
|
if (!ParseNumber(argv[i], NULL, 10, &options.dataWidth))
|
||||||
|
FATAL_ERROR("Failed to parse data width.\n");
|
||||||
|
|
||||||
|
if (options.dataWidth < 1)
|
||||||
|
FATAL_ERROR("Data width must be positive.\n");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FATAL_ERROR("Unrecognized option \"%s\".\n", option);
|
FATAL_ERROR("Unrecognized option \"%s\".\n", option);
|
||||||
@@ -177,15 +204,16 @@ void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **a
|
|||||||
void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **argv)
|
void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **argv)
|
||||||
{
|
{
|
||||||
char *outputFileExtension = GetFileExtensionAfterDot(outputPath);
|
char *outputFileExtension = GetFileExtensionAfterDot(outputPath);
|
||||||
int bitDepth = outputFileExtension[0] - '0';
|
|
||||||
struct PngToGbaOptions options;
|
struct PngToGbaOptions options;
|
||||||
options.numTilesMode = NUM_TILES_IGNORE;
|
options.numTilesMode = NUM_TILES_IGNORE;
|
||||||
options.numTiles = 0;
|
options.numTiles = 0;
|
||||||
options.bitDepth = bitDepth;
|
options.bitDepth = outputFileExtension[0] - '0';
|
||||||
options.metatileWidth = 1;
|
options.metatileWidth = 1;
|
||||||
options.metatileHeight = 1;
|
options.metatileHeight = 1;
|
||||||
options.tilemapFilePath = NULL;
|
options.tilemapFilePath = NULL;
|
||||||
options.isAffineMap = false;
|
options.isAffineMap = false;
|
||||||
|
options.isTiled = true;
|
||||||
|
options.dataWidth = 1;
|
||||||
|
|
||||||
for (int i = 3; i < argc; i++)
|
for (int i = 3; i < argc; i++)
|
||||||
{
|
{
|
||||||
@@ -236,6 +264,22 @@ void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **a
|
|||||||
if (options.metatileHeight < 1)
|
if (options.metatileHeight < 1)
|
||||||
FATAL_ERROR("metatile height must be positive.\n");
|
FATAL_ERROR("metatile height must be positive.\n");
|
||||||
}
|
}
|
||||||
|
else if (strcmp(option, "-plain") == 0)
|
||||||
|
{
|
||||||
|
options.isTiled = false;
|
||||||
|
}
|
||||||
|
else if (strcmp(option, "-data_width") == 0)
|
||||||
|
{
|
||||||
|
if (i + 1 >= argc)
|
||||||
|
FATAL_ERROR("No data width value following \"-data_width\".\n");
|
||||||
|
i++;
|
||||||
|
|
||||||
|
if (!ParseNumber(argv[i], NULL, 10, &options.dataWidth))
|
||||||
|
FATAL_ERROR("Failed to parse data width.\n");
|
||||||
|
|
||||||
|
if (options.dataWidth < 1)
|
||||||
|
FATAL_ERROR("Data width must be positive.\n");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FATAL_ERROR("Unrecognized option \"%s\".\n", option);
|
FATAL_ERROR("Unrecognized option \"%s\".\n", option);
|
||||||
@@ -403,7 +447,7 @@ void HandleLZCompressCommand(char *inputPath, char *outputPath, int argc, char *
|
|||||||
else if (strcmp(option, "-search") == 0)
|
else if (strcmp(option, "-search") == 0)
|
||||||
{
|
{
|
||||||
if (i + 1 >= argc)
|
if (i + 1 >= argc)
|
||||||
FATAL_ERROR("No size following \"-overflow\".\n");
|
FATAL_ERROR("No size following \"-search\".\n");
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ struct GbaToPngOptions {
|
|||||||
int metatileHeight;
|
int metatileHeight;
|
||||||
char *tilemapFilePath;
|
char *tilemapFilePath;
|
||||||
bool isAffineMap;
|
bool isAffineMap;
|
||||||
|
bool isTiled;
|
||||||
|
int dataWidth;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PngToGbaOptions {
|
struct PngToGbaOptions {
|
||||||
@@ -25,6 +27,8 @@ struct PngToGbaOptions {
|
|||||||
int metatileHeight;
|
int metatileHeight;
|
||||||
char *tilemapFilePath;
|
char *tilemapFilePath;
|
||||||
bool isAffineMap;
|
bool isAffineMap;
|
||||||
|
bool isTiled;
|
||||||
|
int dataWidth;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // OPTIONS_H
|
#endif // OPTIONS_H
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
+10
-4
@@ -1,6 +1,6 @@
|
|||||||
CXX := g++
|
CXX ?= g++
|
||||||
|
|
||||||
CXXFLAGS := -Wall -std=c++11 -O2
|
CXXFLAGS := -Wall -std=c++17 -O2
|
||||||
|
|
||||||
INCLUDES := -I .
|
INCLUDES := -I .
|
||||||
|
|
||||||
@@ -8,12 +8,18 @@ SRCS := jsonproc.cpp
|
|||||||
|
|
||||||
HEADERS := jsonproc.h inja.hpp nlohmann/json.hpp
|
HEADERS := jsonproc.h inja.hpp nlohmann/json.hpp
|
||||||
|
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
EXE := .exe
|
||||||
|
else
|
||||||
|
EXE :=
|
||||||
|
endif
|
||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|
||||||
all: jsonproc
|
all: jsonproc$(EXE)
|
||||||
@:
|
@:
|
||||||
|
|
||||||
jsonproc: $(SRCS) $(HEADERS)
|
jsonproc$(EXE): $(SRCS) $(HEADERS)
|
||||||
$(CXX) $(CXXFLAGS) $(INCLUDES) $(SRCS) -o $@ $(LDFLAGS)
|
$(CXX) $(CXXFLAGS) $(INCLUDES) $(SRCS) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
Regular → Executable
+2440
-3020
File diff suppressed because it is too large
Load Diff
Regular → Executable
+14
-11
@@ -1,4 +1,6 @@
|
|||||||
// jsonproc.cpp
|
// jsonproc.cpp
|
||||||
|
// jsonproc converts JSON data to an output file based on an Inja template.
|
||||||
|
// https://github.com/pantor/inja
|
||||||
|
|
||||||
#include "jsonproc.h"
|
#include "jsonproc.h"
|
||||||
|
|
||||||
@@ -7,6 +9,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
using std::string; using std::to_string;
|
using std::string; using std::to_string;
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
using std::replace_if;
|
||||||
|
|
||||||
#include <inja.hpp>
|
#include <inja.hpp>
|
||||||
using namespace inja;
|
using namespace inja;
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
@@ -40,13 +45,6 @@ int main(int argc, char *argv[])
|
|||||||
return "//\n// DO NOT MODIFY THIS FILE! It is auto-generated from " + jsonfilepath +" and Inja template " + templateFilepath + "\n//\n";
|
return "//\n// DO NOT MODIFY THIS FILE! It is auto-generated from " + jsonfilepath +" and Inja template " + templateFilepath + "\n//\n";
|
||||||
});
|
});
|
||||||
|
|
||||||
env.add_callback("contains", 2, [](Arguments& args) {
|
|
||||||
string word = args.at(0)->get<string>();
|
|
||||||
string check = args.at(1)->get<string>();
|
|
||||||
|
|
||||||
return word.find(check) != std::string::npos;
|
|
||||||
});
|
|
||||||
|
|
||||||
env.add_callback("subtract", 2, [](Arguments& args) {
|
env.add_callback("subtract", 2, [](Arguments& args) {
|
||||||
int minuend = args.at(0)->get<int>();
|
int minuend = args.at(0)->get<int>();
|
||||||
int subtrahend = args.at(1)->get<int>();
|
int subtrahend = args.at(1)->get<int>();
|
||||||
@@ -109,11 +107,16 @@ int main(int argc, char *argv[])
|
|||||||
});
|
});
|
||||||
|
|
||||||
env.add_callback("cleanString", 1, [](Arguments& args) {
|
env.add_callback("cleanString", 1, [](Arguments& args) {
|
||||||
string badChars = ".'{} \n\t-_\u00e9";
|
|
||||||
string str = args.at(0)->get<string>();
|
string str = args.at(0)->get<string>();
|
||||||
str.erase(remove_if(str.begin(), str.end(), [&badChars](const char &c) {
|
for (unsigned int i = 0; i < str.length(); i++) {
|
||||||
return badChars.find(c) != std::string::npos;
|
// This code is not Unicode aware, so UTF-8 is not easily parsable without introducing
|
||||||
}), str.end());
|
// another library. Just filter out any non-alphanumeric characters for now.
|
||||||
|
// TODO: proper Unicode string normalization
|
||||||
|
if ((i == 0 && isdigit(str[i]))
|
||||||
|
|| !isalnum(str[i])) {
|
||||||
|
str[i] = '_';
|
||||||
|
}
|
||||||
|
}
|
||||||
return str;
|
return str;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
+10438
-9148
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
CXX := g++
|
CXX ?= g++
|
||||||
|
|
||||||
CXXFLAGS := -Wall -std=c++11 -O2
|
CXXFLAGS := -Wall -std=c++11 -O2
|
||||||
|
|
||||||
@@ -6,12 +6,18 @@ SRCS := json11.cpp mapjson.cpp
|
|||||||
|
|
||||||
HEADERS := mapjson.h
|
HEADERS := mapjson.h
|
||||||
|
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
EXE := .exe
|
||||||
|
else
|
||||||
|
EXE :=
|
||||||
|
endif
|
||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|
||||||
all: mapjson
|
all: mapjson$(EXE)
|
||||||
@:
|
@:
|
||||||
|
|
||||||
mapjson: $(SRCS) $(HEADERS)
|
mapjson$(EXE): $(SRCS) $(HEADERS)
|
||||||
$(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS)
|
$(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
+67
-45
@@ -30,6 +30,8 @@ using json11::Json;
|
|||||||
#include "mapjson.h"
|
#include "mapjson.h"
|
||||||
|
|
||||||
string version;
|
string version;
|
||||||
|
// System directory separator
|
||||||
|
string sep;
|
||||||
|
|
||||||
string read_text_file(string filepath) {
|
string read_text_file(string filepath) {
|
||||||
ifstream in_file(filepath);
|
ifstream in_file(filepath);
|
||||||
@@ -330,13 +332,22 @@ string generate_map_events_text(Json map_data) {
|
|||||||
return text.str();
|
return text.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
string get_directory_name(string filename) {
|
string strip_trailing_separator(string filename) {
|
||||||
size_t dir_pos = filename.find_last_of("/\\");
|
if(filename.back() == '/' || filename.back() == '\\')
|
||||||
|
filename.pop_back();
|
||||||
|
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
void infer_separator(string filename) {
|
||||||
|
size_t dir_pos = filename.find_last_of("/\\");
|
||||||
|
sep = filename[dir_pos];
|
||||||
|
}
|
||||||
|
string file_parent(string filename){
|
||||||
|
size_t dir_pos = filename.find_last_of("/\\");
|
||||||
return filename.substr(0, dir_pos + 1);
|
return filename.substr(0, dir_pos + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void process_map(string map_filepath, string layouts_filepath) {
|
void process_map(string map_filepath, string layouts_filepath, string output_dir) {
|
||||||
string mapdata_err, layouts_err;
|
string mapdata_err, layouts_err;
|
||||||
|
|
||||||
string mapdata_json_text = read_text_file(map_filepath);
|
string mapdata_json_text = read_text_file(map_filepath);
|
||||||
@@ -354,10 +365,10 @@ void process_map(string map_filepath, string layouts_filepath) {
|
|||||||
string events_text = generate_map_events_text(map_data);
|
string events_text = generate_map_events_text(map_data);
|
||||||
string connections_text = generate_map_connections_text(map_data);
|
string connections_text = generate_map_connections_text(map_data);
|
||||||
|
|
||||||
string files_dir = get_directory_name(map_filepath);
|
string out_dir = strip_trailing_separator(output_dir).append(sep);
|
||||||
write_text_file(files_dir + "header.inc", header_text);
|
write_text_file(out_dir + "header.inc", header_text);
|
||||||
write_text_file(files_dir + "events.inc", events_text);
|
write_text_file(out_dir + "events.inc", events_text);
|
||||||
write_text_file(files_dir + "connections.inc", connections_text);
|
write_text_file(out_dir + "connections.inc", connections_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
string generate_groups_text(Json groups_data) {
|
string generate_groups_text(Json groups_data) {
|
||||||
@@ -382,7 +393,7 @@ string generate_groups_text(Json groups_data) {
|
|||||||
return text.str();
|
return text.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
string generate_connections_text(Json groups_data) {
|
string generate_connections_text(Json groups_data, string include_path) {
|
||||||
vector<Json> map_names;
|
vector<Json> map_names;
|
||||||
|
|
||||||
for (auto &group : groups_data["group_order"].array_items())
|
for (auto &group : groups_data["group_order"].array_items())
|
||||||
@@ -407,12 +418,12 @@ string generate_connections_text(Json groups_data) {
|
|||||||
text << "@\n@ DO NOT MODIFY THIS FILE! It is auto-generated from data/maps/map_groups.json\n@\n\n";
|
text << "@\n@ DO NOT MODIFY THIS FILE! It is auto-generated from data/maps/map_groups.json\n@\n\n";
|
||||||
|
|
||||||
for (Json map_name : map_names)
|
for (Json map_name : map_names)
|
||||||
text << "\t.include \"data/maps/" << json_to_string(map_name) << "/connections.inc\"\n";
|
text << "\t.include \"" << include_path << "/" << json_to_string(map_name) << "/connections.inc\"\n";
|
||||||
|
|
||||||
return text.str();
|
return text.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
string generate_headers_text(Json groups_data) {
|
string generate_headers_text(Json groups_data, string include_path) {
|
||||||
vector<string> map_names;
|
vector<string> map_names;
|
||||||
|
|
||||||
for (auto &group : groups_data["group_order"].array_items())
|
for (auto &group : groups_data["group_order"].array_items())
|
||||||
@@ -424,12 +435,12 @@ string generate_headers_text(Json groups_data) {
|
|||||||
text << "@\n@ DO NOT MODIFY THIS FILE! It is auto-generated from data/maps/map_groups.json\n@\n\n";
|
text << "@\n@ DO NOT MODIFY THIS FILE! It is auto-generated from data/maps/map_groups.json\n@\n\n";
|
||||||
|
|
||||||
for (string map_name : map_names)
|
for (string map_name : map_names)
|
||||||
text << "\t.include \"data/maps/" << map_name << "/header.inc\"\n";
|
text << "\t.include \"" << include_path << "/" << map_name << "/header.inc\"\n";
|
||||||
|
|
||||||
return text.str();
|
return text.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
string generate_events_text(Json groups_data) {
|
string generate_events_text(Json groups_data, string include_path) {
|
||||||
vector<string> map_names;
|
vector<string> map_names;
|
||||||
|
|
||||||
for (auto &group : groups_data["group_order"].array_items())
|
for (auto &group : groups_data["group_order"].array_items())
|
||||||
@@ -438,17 +449,16 @@ string generate_events_text(Json groups_data) {
|
|||||||
|
|
||||||
ostringstream text;
|
ostringstream text;
|
||||||
|
|
||||||
text << "@\n@ DO NOT MODIFY THIS FILE! It is auto-generated from data/maps/map_groups.json\n@\n\n";
|
text << "@\n@ DO NOT MODIFY THIS FILE! It is auto-generated from " << include_path << "/map_groups.json\n@\n\n";
|
||||||
|
|
||||||
for (string map_name : map_names)
|
for (string map_name : map_names)
|
||||||
text << "\t.include \"data/maps/" << map_name << "/events.inc\"\n";
|
text << "\t.include \"" << include_path << "/" << map_name << "/events.inc\"\n";
|
||||||
|
|
||||||
return text.str();
|
return text.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
string generate_map_constants_text(string groups_filepath, Json groups_data) {
|
string generate_map_constants_text(string groups_filepath, Json groups_data) {
|
||||||
string file_dir = get_directory_name(groups_filepath);
|
string file_dir = file_parent(groups_filepath) + sep;
|
||||||
char dir_separator = file_dir.back();
|
|
||||||
|
|
||||||
ostringstream text;
|
ostringstream text;
|
||||||
|
|
||||||
@@ -466,7 +476,7 @@ string generate_map_constants_text(string groups_filepath, Json groups_data) {
|
|||||||
size_t max_length = 0;
|
size_t max_length = 0;
|
||||||
|
|
||||||
for (auto &map_name : groups_data[groupName].array_items()) {
|
for (auto &map_name : groups_data[groupName].array_items()) {
|
||||||
string map_filepath = file_dir + json_to_string(map_name) + dir_separator + "map.json";
|
string map_filepath = file_dir + json_to_string(map_name) + sep + "map.json";
|
||||||
string err_str;
|
string err_str;
|
||||||
Json map_data = Json::parse(read_text_file(map_filepath), err_str);
|
Json map_data = Json::parse(read_text_file(map_filepath), err_str);
|
||||||
if (map_data == Json())
|
if (map_data == Json())
|
||||||
@@ -493,7 +503,11 @@ string generate_map_constants_text(string groups_filepath, Json groups_data) {
|
|||||||
return text.str();
|
return text.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
void process_groups(string groups_filepath) {
|
// Output paths are directories with trailing path separators
|
||||||
|
void process_groups(string groups_filepath, string output_asm, string output_c) {
|
||||||
|
output_asm = strip_trailing_separator(output_asm); // Remove separator if existing.
|
||||||
|
output_c = strip_trailing_separator(output_c);
|
||||||
|
|
||||||
string err;
|
string err;
|
||||||
Json groups_data = Json::parse(read_text_file(groups_filepath), err);
|
Json groups_data = Json::parse(read_text_file(groups_filepath), err);
|
||||||
|
|
||||||
@@ -501,19 +515,16 @@ void process_groups(string groups_filepath) {
|
|||||||
FATAL_ERROR("%s\n", err.c_str());
|
FATAL_ERROR("%s\n", err.c_str());
|
||||||
|
|
||||||
string groups_text = generate_groups_text(groups_data);
|
string groups_text = generate_groups_text(groups_data);
|
||||||
string connections_text = generate_connections_text(groups_data);
|
string connections_text = generate_connections_text(groups_data, output_asm);
|
||||||
string headers_text = generate_headers_text(groups_data);
|
string headers_text = generate_headers_text(groups_data, output_asm);
|
||||||
string events_text = generate_events_text(groups_data);
|
string events_text = generate_events_text(groups_data, output_asm);
|
||||||
string map_header_text = generate_map_constants_text(groups_filepath, groups_data);
|
string map_header_text = generate_map_constants_text(groups_filepath, groups_data);
|
||||||
|
|
||||||
string file_dir = get_directory_name(groups_filepath);
|
write_text_file(output_asm + sep + "groups.inc", groups_text);
|
||||||
char s = file_dir.back();
|
write_text_file(output_asm + sep + "connections.inc", connections_text);
|
||||||
|
write_text_file(output_asm + sep + "headers.inc", headers_text);
|
||||||
write_text_file(file_dir + "groups.inc", groups_text);
|
write_text_file(output_asm + sep + "events.inc", events_text);
|
||||||
write_text_file(file_dir + "connections.inc", connections_text);
|
write_text_file(output_c + sep + "map_groups.h", map_header_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) {
|
string generate_layout_headers_text(Json layouts_data) {
|
||||||
@@ -586,7 +597,10 @@ string generate_layouts_constants_text(Json layouts_data) {
|
|||||||
return text.str();
|
return text.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
void process_layouts(string layouts_filepath) {
|
void process_layouts(string layouts_filepath, string output_asm, string output_c) {
|
||||||
|
output_asm = strip_trailing_separator(output_asm).append(sep);
|
||||||
|
output_c = strip_trailing_separator(output_c).append(sep);
|
||||||
|
|
||||||
string err;
|
string err;
|
||||||
Json layouts_data = Json::parse(read_text_file(layouts_filepath), err);
|
Json layouts_data = Json::parse(read_text_file(layouts_filepath), err);
|
||||||
|
|
||||||
@@ -597,12 +611,9 @@ void process_layouts(string layouts_filepath) {
|
|||||||
string layouts_table_text = generate_layouts_table_text(layouts_data);
|
string layouts_table_text = generate_layouts_table_text(layouts_data);
|
||||||
string layouts_constants_text = generate_layouts_constants_text(layouts_data);
|
string layouts_constants_text = generate_layouts_constants_text(layouts_data);
|
||||||
|
|
||||||
string file_dir = get_directory_name(layouts_filepath);
|
write_text_file(output_asm + "layouts.inc", layout_headers_text);
|
||||||
char s = file_dir.back();
|
write_text_file(output_asm + "layouts_table.inc", layouts_table_text);
|
||||||
|
write_text_file(output_c + "layouts.h", layouts_constants_text);
|
||||||
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[]) {
|
int main(int argc, char *argv[]) {
|
||||||
@@ -620,29 +631,40 @@ int main(int argc, char *argv[]) {
|
|||||||
FATAL_ERROR("ERROR: <mode> must be 'layouts', 'map', or 'groups'.\n");
|
FATAL_ERROR("ERROR: <mode> must be 'layouts', 'map', or 'groups'.\n");
|
||||||
|
|
||||||
if (mode == "map") {
|
if (mode == "map") {
|
||||||
if (argc != 5)
|
if (argc != 6)
|
||||||
FATAL_ERROR("USAGE: mapjson map <game-version> <map_file> <layouts_file>\n");
|
FATAL_ERROR("USAGE: mapjson map <game-version> <map_file> <layouts_file> <output_dir>\n");
|
||||||
|
|
||||||
|
infer_separator(argv[3]);
|
||||||
string filepath(argv[3]);
|
string filepath(argv[3]);
|
||||||
string layouts_filepath(argv[4]);
|
string layouts_filepath(argv[4]);
|
||||||
|
string output_dir(argv[5]);
|
||||||
|
|
||||||
process_map(filepath, layouts_filepath);
|
process_map(filepath, layouts_filepath, output_dir);
|
||||||
}
|
}
|
||||||
else if (mode == "groups") {
|
else if (mode == "groups") {
|
||||||
if (argc != 4)
|
if (argc != 6)
|
||||||
FATAL_ERROR("USAGE: mapjson groups <game-version> <groups_file>\n");
|
FATAL_ERROR("USAGE: mapjson groups <game-version> <groups_file> <output_asm_dir> <output_c_dir>\n");
|
||||||
|
|
||||||
|
infer_separator(argv[3]);
|
||||||
string filepath(argv[3]);
|
string filepath(argv[3]);
|
||||||
|
string output_asm(argv[4]);
|
||||||
|
string output_c(argv[5]);
|
||||||
|
|
||||||
process_groups(filepath);
|
process_groups(filepath, output_asm, output_c);
|
||||||
}
|
}
|
||||||
else if (mode == "layouts") {
|
else if (mode == "layouts") {
|
||||||
if (argc != 4)
|
if (argc != 6)
|
||||||
FATAL_ERROR("USAGE: mapjson layouts <game-version> <layouts_file>\n");
|
FATAL_ERROR("USAGE: mapjson layouts <game-version> <layouts_file> <output_asm_dir> <output_c_dir>\n");
|
||||||
|
|
||||||
|
infer_separator(argv[3]);
|
||||||
string filepath(argv[3]);
|
string filepath(argv[3]);
|
||||||
|
string output_asm(argv[4]);
|
||||||
|
string output_c(argv[5]);
|
||||||
|
|
||||||
process_layouts(filepath);
|
process_layouts(filepath, output_asm, output_c);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
FATAL_ERROR("ERROR: <mode> must be 'layouts', 'map', or 'groups'.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
+11
-5
@@ -1,19 +1,25 @@
|
|||||||
CXX := g++
|
CXX ?= g++
|
||||||
|
|
||||||
CXXFLAGS := -std=c++11 -O2 -Wall -Wno-switch -Werror
|
CXXFLAGS := -std=c++11 -O2 -Wall -Wno-switch -Werror
|
||||||
|
|
||||||
SRCS := asm_file.cpp c_file.cpp charmap.cpp preproc.cpp string_parser.cpp \
|
SRCS := asm_file.cpp c_file.cpp charmap.cpp preproc.cpp string_parser.cpp \
|
||||||
utf8.cpp
|
utf8.cpp io.cpp
|
||||||
|
|
||||||
HEADERS := asm_file.h c_file.h char_util.h charmap.h preproc.h string_parser.h \
|
HEADERS := asm_file.h c_file.h char_util.h charmap.h preproc.h string_parser.h \
|
||||||
utf8.h
|
utf8.h io.h
|
||||||
|
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
EXE := .exe
|
||||||
|
else
|
||||||
|
EXE :=
|
||||||
|
endif
|
||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|
||||||
all: preproc
|
all: preproc$(EXE)
|
||||||
@:
|
@:
|
||||||
|
|
||||||
preproc: $(SRCS) $(HEADERS)
|
preproc$(EXE): $(SRCS) $(HEADERS)
|
||||||
$(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS)
|
$(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
+318
-60
@@ -26,33 +26,13 @@
|
|||||||
#include "char_util.h"
|
#include "char_util.h"
|
||||||
#include "utf8.h"
|
#include "utf8.h"
|
||||||
#include "string_parser.h"
|
#include "string_parser.h"
|
||||||
|
#include "../../include/characters.h"
|
||||||
|
#include "io.h"
|
||||||
|
|
||||||
AsmFile::AsmFile(std::string filename) : m_filename(filename)
|
AsmFile::AsmFile(std::string filename, bool isStdin, bool doEnum) : m_filename(filename)
|
||||||
{
|
{
|
||||||
FILE *fp = std::fopen(filename.c_str(), "rb");
|
m_buffer = ReadFileToBuffer(filename.c_str(), isStdin, &m_size);
|
||||||
|
m_doEnum = doEnum;
|
||||||
if (fp == NULL)
|
|
||||||
FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename.c_str());
|
|
||||||
|
|
||||||
std::fseek(fp, 0, SEEK_END);
|
|
||||||
|
|
||||||
m_size = std::ftell(fp);
|
|
||||||
|
|
||||||
if (m_size < 0)
|
|
||||||
FATAL_ERROR("File size of \"%s\" is less than zero.\n", filename.c_str());
|
|
||||||
else if (m_size == 0)
|
|
||||||
return; // Empty file
|
|
||||||
|
|
||||||
m_buffer = new char[m_size + 1];
|
|
||||||
|
|
||||||
std::rewind(fp);
|
|
||||||
|
|
||||||
if (std::fread(m_buffer, m_size, 1, fp) != 1)
|
|
||||||
FATAL_ERROR("Failed to read \"%s\".\n", filename.c_str());
|
|
||||||
|
|
||||||
m_buffer[m_size] = 0;
|
|
||||||
|
|
||||||
std::fclose(fp);
|
|
||||||
|
|
||||||
m_pos = 0;
|
m_pos = 0;
|
||||||
m_lineNum = 1;
|
m_lineNum = 1;
|
||||||
@@ -64,6 +44,7 @@ AsmFile::AsmFile(std::string filename) : m_filename(filename)
|
|||||||
AsmFile::AsmFile(AsmFile&& other) : m_filename(std::move(other.m_filename))
|
AsmFile::AsmFile(AsmFile&& other) : m_filename(std::move(other.m_filename))
|
||||||
{
|
{
|
||||||
m_buffer = other.m_buffer;
|
m_buffer = other.m_buffer;
|
||||||
|
m_doEnum = other.m_doEnum;
|
||||||
m_pos = other.m_pos;
|
m_pos = other.m_pos;
|
||||||
m_size = other.m_size;
|
m_size = other.m_size;
|
||||||
m_lineNum = other.m_lineNum;
|
m_lineNum = other.m_lineNum;
|
||||||
@@ -173,6 +154,8 @@ Directive AsmFile::GetDirective()
|
|||||||
return Directive::String;
|
return Directive::String;
|
||||||
else if (CheckForDirective(".braille"))
|
else if (CheckForDirective(".braille"))
|
||||||
return Directive::Braille;
|
return Directive::Braille;
|
||||||
|
else if (CheckForDirective("enum"))
|
||||||
|
return Directive::Enum;
|
||||||
else
|
else
|
||||||
return Directive::Unknown;
|
return Directive::Unknown;
|
||||||
}
|
}
|
||||||
@@ -283,7 +266,7 @@ int AsmFile::ReadString(unsigned char* s)
|
|||||||
|
|
||||||
while (length < padLength)
|
while (length < padLength)
|
||||||
{
|
{
|
||||||
s[length++] = 0;
|
s[length++] = CHAR_SPACE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,40 +275,92 @@ int AsmFile::ReadString(unsigned char* s)
|
|||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AsmFile::VerifyStringLength(int length)
|
||||||
|
{
|
||||||
|
if (length == kMaxStringLength)
|
||||||
|
RaiseError("mapped string longer than %d bytes", kMaxStringLength);
|
||||||
|
}
|
||||||
|
|
||||||
int AsmFile::ReadBraille(unsigned char* s)
|
int AsmFile::ReadBraille(unsigned char* s)
|
||||||
{
|
{
|
||||||
static std::map<char, unsigned char> encoding =
|
static std::map<char, unsigned char> encoding =
|
||||||
{
|
{
|
||||||
{ 'A', 0x01 },
|
{ 'A', BRAILLE_CHAR_A },
|
||||||
{ 'B', 0x05 },
|
{ 'B', BRAILLE_CHAR_B },
|
||||||
{ 'C', 0x03 },
|
{ 'C', BRAILLE_CHAR_C },
|
||||||
{ 'D', 0x0B },
|
{ 'D', BRAILLE_CHAR_D },
|
||||||
{ 'E', 0x09 },
|
{ 'E', BRAILLE_CHAR_E },
|
||||||
{ 'F', 0x07 },
|
{ 'F', BRAILLE_CHAR_F },
|
||||||
{ 'G', 0x0F },
|
{ 'G', BRAILLE_CHAR_G },
|
||||||
{ 'H', 0x0D },
|
{ 'H', BRAILLE_CHAR_H },
|
||||||
{ 'I', 0x06 },
|
{ 'I', BRAILLE_CHAR_I },
|
||||||
{ 'J', 0x0E },
|
{ 'J', BRAILLE_CHAR_J },
|
||||||
{ 'K', 0x11 },
|
{ 'K', BRAILLE_CHAR_K },
|
||||||
{ 'L', 0x15 },
|
{ 'L', BRAILLE_CHAR_L },
|
||||||
{ 'M', 0x13 },
|
{ 'M', BRAILLE_CHAR_M },
|
||||||
{ 'N', 0x1B },
|
{ 'N', BRAILLE_CHAR_N },
|
||||||
{ 'O', 0x19 },
|
{ 'O', BRAILLE_CHAR_O },
|
||||||
{ 'P', 0x17 },
|
{ 'P', BRAILLE_CHAR_P },
|
||||||
{ 'Q', 0x1F },
|
{ 'Q', BRAILLE_CHAR_Q },
|
||||||
{ 'R', 0x1D },
|
{ 'R', BRAILLE_CHAR_R },
|
||||||
{ 'S', 0x16 },
|
{ 'S', BRAILLE_CHAR_S },
|
||||||
{ 'T', 0x1E },
|
{ 'T', BRAILLE_CHAR_T },
|
||||||
{ 'U', 0x31 },
|
{ 'U', BRAILLE_CHAR_U },
|
||||||
{ 'V', 0x35 },
|
{ 'V', BRAILLE_CHAR_V },
|
||||||
{ 'W', 0x2E },
|
{ 'W', BRAILLE_CHAR_W },
|
||||||
{ 'X', 0x33 },
|
{ 'X', BRAILLE_CHAR_X },
|
||||||
{ 'Y', 0x3B },
|
{ 'Y', BRAILLE_CHAR_Y },
|
||||||
{ 'Z', 0x39 },
|
{ 'Z', BRAILLE_CHAR_Z },
|
||||||
{ ' ', 0x00 },
|
{ 'a', BRAILLE_CHAR_A },
|
||||||
{ ',', 0x04 },
|
{ 'b', BRAILLE_CHAR_B },
|
||||||
{ '.', 0x2C },
|
{ 'c', BRAILLE_CHAR_C },
|
||||||
{ '$', 0xFF },
|
{ 'd', BRAILLE_CHAR_D },
|
||||||
|
{ 'e', BRAILLE_CHAR_E },
|
||||||
|
{ 'f', BRAILLE_CHAR_F },
|
||||||
|
{ 'g', BRAILLE_CHAR_G },
|
||||||
|
{ 'h', BRAILLE_CHAR_H },
|
||||||
|
{ 'i', BRAILLE_CHAR_I },
|
||||||
|
{ 'j', BRAILLE_CHAR_J },
|
||||||
|
{ 'k', BRAILLE_CHAR_K },
|
||||||
|
{ 'l', BRAILLE_CHAR_L },
|
||||||
|
{ 'm', BRAILLE_CHAR_M },
|
||||||
|
{ 'n', BRAILLE_CHAR_N },
|
||||||
|
{ 'o', BRAILLE_CHAR_O },
|
||||||
|
{ 'p', BRAILLE_CHAR_P },
|
||||||
|
{ 'q', BRAILLE_CHAR_Q },
|
||||||
|
{ 'r', BRAILLE_CHAR_R },
|
||||||
|
{ 's', BRAILLE_CHAR_S },
|
||||||
|
{ 't', BRAILLE_CHAR_T },
|
||||||
|
{ 'u', BRAILLE_CHAR_U },
|
||||||
|
{ 'v', BRAILLE_CHAR_V },
|
||||||
|
{ 'w', BRAILLE_CHAR_W },
|
||||||
|
{ 'x', BRAILLE_CHAR_X },
|
||||||
|
{ 'y', BRAILLE_CHAR_Y },
|
||||||
|
{ 'z', BRAILLE_CHAR_Z },
|
||||||
|
{ '0', BRAILLE_CHAR_0 },
|
||||||
|
{ '1', BRAILLE_CHAR_1 },
|
||||||
|
{ '2', BRAILLE_CHAR_2 },
|
||||||
|
{ '3', BRAILLE_CHAR_3 },
|
||||||
|
{ '4', BRAILLE_CHAR_4 },
|
||||||
|
{ '5', BRAILLE_CHAR_5 },
|
||||||
|
{ '6', BRAILLE_CHAR_6 },
|
||||||
|
{ '7', BRAILLE_CHAR_7 },
|
||||||
|
{ '8', BRAILLE_CHAR_8 },
|
||||||
|
{ '9', BRAILLE_CHAR_9 },
|
||||||
|
{ ' ', BRAILLE_CHAR_SPACE },
|
||||||
|
{ ',', BRAILLE_CHAR_COMMA },
|
||||||
|
{ '.', BRAILLE_CHAR_PERIOD },
|
||||||
|
{ '?', BRAILLE_CHAR_QUESTION_MARK },
|
||||||
|
{ '!', BRAILLE_CHAR_EXCL_MARK },
|
||||||
|
{ ':', BRAILLE_CHAR_COLON },
|
||||||
|
{ ';', BRAILLE_CHAR_SEMICOLON },
|
||||||
|
{ '-', BRAILLE_CHAR_HYPHEN },
|
||||||
|
{ '/', BRAILLE_CHAR_SLASH },
|
||||||
|
{ '(', BRAILLE_CHAR_PAREN },
|
||||||
|
{ ')', BRAILLE_CHAR_PAREN },
|
||||||
|
{ '\'', BRAILLE_CHAR_APOSTROPHE },
|
||||||
|
{ '#', BRAILLE_CHAR_NUMBER },
|
||||||
|
{ '$', EOS },
|
||||||
};
|
};
|
||||||
|
|
||||||
SkipWhitespace();
|
SkipWhitespace();
|
||||||
@@ -337,14 +372,13 @@ int AsmFile::ReadBraille(unsigned char* s)
|
|||||||
|
|
||||||
m_pos++;
|
m_pos++;
|
||||||
|
|
||||||
|
bool inNumber = false;
|
||||||
while (m_buffer[m_pos] != '"')
|
while (m_buffer[m_pos] != '"')
|
||||||
{
|
{
|
||||||
if (length == kMaxStringLength)
|
|
||||||
RaiseError("mapped string longer than %d bytes", kMaxStringLength);
|
|
||||||
|
|
||||||
if (m_buffer[m_pos] == '\\' && m_buffer[m_pos + 1] == 'n')
|
if (m_buffer[m_pos] == '\\' && m_buffer[m_pos + 1] == 'n')
|
||||||
{
|
{
|
||||||
s[length++] = 0xFE;
|
VerifyStringLength(length);
|
||||||
|
s[length++] = CHAR_NEWLINE;
|
||||||
m_pos += 2;
|
m_pos += 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -359,6 +393,21 @@ int AsmFile::ReadBraille(unsigned char* s)
|
|||||||
RaiseError("character '\\x%02X' not valid in braille string", m_buffer[m_pos]);
|
RaiseError("character '\\x%02X' not valid in braille string", m_buffer[m_pos]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!inNumber && c >= '0' && c <= '9' )
|
||||||
|
{
|
||||||
|
// Output number indicator at start of a number
|
||||||
|
inNumber = true;
|
||||||
|
VerifyStringLength(length);
|
||||||
|
s[length++] = BRAILLE_CHAR_NUMBER;
|
||||||
|
}
|
||||||
|
else if (inNumber && encoding[c] == BRAILLE_CHAR_SPACE)
|
||||||
|
{
|
||||||
|
// Number ends at a space.
|
||||||
|
// Non-number characters encountered before a space will simply be output as is.
|
||||||
|
inNumber = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
VerifyStringLength(length);
|
||||||
s[length++] = encoding[c];
|
s[length++] = encoding[c];
|
||||||
m_pos++;
|
m_pos++;
|
||||||
}
|
}
|
||||||
@@ -460,6 +509,88 @@ void AsmFile::OutputLine()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parses an assumed C `enum`. Returns false if `enum { ...` is not matched
|
||||||
|
bool AsmFile::ParseEnum()
|
||||||
|
{
|
||||||
|
if (!m_doEnum)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
long fallbackPosition = m_pos;
|
||||||
|
std::string headerFilename = "";
|
||||||
|
long currentHeaderLine = SkipWhitespaceAndEol();
|
||||||
|
std::string enumName = ReadIdentifier();
|
||||||
|
currentHeaderLine += SkipWhitespaceAndEol();
|
||||||
|
std::string enumBase = "0";
|
||||||
|
long enumCounter = 0;
|
||||||
|
long symbolCount = 0;
|
||||||
|
|
||||||
|
if (m_buffer[m_pos] != '{') // assume assembly macro, otherwise assume enum and report errors accordingly
|
||||||
|
{
|
||||||
|
m_pos = fallbackPosition - 4;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentHeaderLine += FindLastLineNumber(headerFilename);
|
||||||
|
m_pos++;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
currentHeaderLine += SkipWhitespaceAndEol();
|
||||||
|
std::string currentIdentName = ReadIdentifier();
|
||||||
|
if (!currentIdentName.empty())
|
||||||
|
{
|
||||||
|
std::printf("# %ld \"%s\"\n", currentHeaderLine, headerFilename.c_str());
|
||||||
|
currentHeaderLine += SkipWhitespaceAndEol();
|
||||||
|
if (m_buffer[m_pos] == '=')
|
||||||
|
{
|
||||||
|
m_pos++;
|
||||||
|
SkipWhitespace();
|
||||||
|
enumBase.clear();
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (m_pos == m_size)
|
||||||
|
RaiseError("unexpected EOF");
|
||||||
|
if (m_buffer[m_pos] == ',')
|
||||||
|
break;
|
||||||
|
if (m_buffer[m_pos] == '\n')
|
||||||
|
{
|
||||||
|
currentHeaderLine++;
|
||||||
|
enumBase.push_back(' ');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
enumBase.push_back(m_buffer[m_pos]);
|
||||||
|
}
|
||||||
|
m_pos++;
|
||||||
|
}
|
||||||
|
enumCounter = 0;
|
||||||
|
}
|
||||||
|
std::printf(".equiv %s, (%s) + %ld\n", currentIdentName.c_str(), enumBase.c_str(), enumCounter);
|
||||||
|
enumCounter++;
|
||||||
|
symbolCount++;
|
||||||
|
}
|
||||||
|
else if (symbolCount == 0)
|
||||||
|
{
|
||||||
|
RaiseError("%s:%ld: empty enum is invalid", headerFilename.c_str(), currentHeaderLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_buffer[m_pos] != ',')
|
||||||
|
{
|
||||||
|
currentHeaderLine += SkipWhitespaceAndEol();
|
||||||
|
if (m_buffer[m_pos++] == '}' && m_buffer[m_pos++] == ';')
|
||||||
|
{
|
||||||
|
ExpectEmptyRestOfLine();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RaiseError("unterminated enum from included file %s:%ld", headerFilename.c_str(), currentHeaderLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_pos++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Asserts that the rest of the line is empty and moves to the next one.
|
// Asserts that the rest of the line is empty and moves to the next one.
|
||||||
void AsmFile::ExpectEmptyRestOfLine()
|
void AsmFile::ExpectEmptyRestOfLine()
|
||||||
{
|
{
|
||||||
@@ -532,3 +663,130 @@ void AsmFile::RaiseWarning(const char* format, ...)
|
|||||||
{
|
{
|
||||||
DO_REPORT("warning");
|
DO_REPORT("warning");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skips Whitespace including newlines and returns the amount of newlines skipped
|
||||||
|
int AsmFile::SkipWhitespaceAndEol()
|
||||||
|
{
|
||||||
|
int newlines = 0;
|
||||||
|
while (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ' || m_buffer[m_pos] == '\n')
|
||||||
|
{
|
||||||
|
if (m_buffer[m_pos] == '\n')
|
||||||
|
newlines++;
|
||||||
|
m_pos++;
|
||||||
|
}
|
||||||
|
return newlines;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the last line indicator and its corresponding file name without modifying the token index
|
||||||
|
int AsmFile::FindLastLineNumber(std::string& filename)
|
||||||
|
{
|
||||||
|
long pos = m_pos;
|
||||||
|
long linebreaks = 0;
|
||||||
|
while (m_buffer[pos] != '#' && pos >= 0)
|
||||||
|
{
|
||||||
|
if (m_buffer[pos] == '\n')
|
||||||
|
linebreaks++;
|
||||||
|
pos--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos < 0)
|
||||||
|
RaiseError("line indicator for header file not found before `enum`");
|
||||||
|
|
||||||
|
pos++;
|
||||||
|
while (m_buffer[pos] == ' ' || m_buffer[pos] == '\t')
|
||||||
|
pos++;
|
||||||
|
|
||||||
|
if (!IsAsciiDigit(m_buffer[pos]))
|
||||||
|
RaiseError("malformatted line indicator found before `enum`, expected line number");
|
||||||
|
|
||||||
|
unsigned n = 0;
|
||||||
|
int digit = 0;
|
||||||
|
while ((digit = ConvertDigit(m_buffer[pos++], 10)) != -1)
|
||||||
|
n = 10 * n + digit;
|
||||||
|
|
||||||
|
while (m_buffer[pos] == ' ' || m_buffer[pos] == '\t')
|
||||||
|
pos++;
|
||||||
|
|
||||||
|
if (m_buffer[pos++] != '"')
|
||||||
|
RaiseError("malformatted line indicator found before `enum`, expected filename");
|
||||||
|
|
||||||
|
while (m_buffer[pos] != '"')
|
||||||
|
{
|
||||||
|
unsigned char c = m_buffer[pos++];
|
||||||
|
|
||||||
|
if (c == 0)
|
||||||
|
{
|
||||||
|
if (pos >= m_size)
|
||||||
|
RaiseError("unexpected EOF in line indicator");
|
||||||
|
else
|
||||||
|
RaiseError("unexpected null character in line indicator");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsAsciiPrintable(c))
|
||||||
|
RaiseError("unexpected character '\\x%02X' in line indicator", c);
|
||||||
|
|
||||||
|
if (c == '\\')
|
||||||
|
{
|
||||||
|
c = m_buffer[pos];
|
||||||
|
RaiseError("unexpected escape '\\%c' in line indicator", c);
|
||||||
|
}
|
||||||
|
|
||||||
|
filename += c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return n + linebreaks - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AsmFile::ReadIdentifier()
|
||||||
|
{
|
||||||
|
long start = m_pos;
|
||||||
|
if (!IsIdentifierStartingChar(m_buffer[m_pos]))
|
||||||
|
return std::string();
|
||||||
|
|
||||||
|
m_pos++;
|
||||||
|
|
||||||
|
while (IsIdentifierChar(m_buffer[m_pos]))
|
||||||
|
m_pos++;
|
||||||
|
|
||||||
|
return std::string(&m_buffer[start], m_pos - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
long AsmFile::ReadInteger(std::string filename, long line)
|
||||||
|
{
|
||||||
|
bool negate = false;
|
||||||
|
int radix = 10;
|
||||||
|
if (!IsAsciiDigit(m_buffer[m_pos]))
|
||||||
|
{
|
||||||
|
if (m_buffer[m_pos++] == '-')
|
||||||
|
negate = true;
|
||||||
|
else
|
||||||
|
RaiseError("expected number in included file %s:%ld", filename.c_str(), line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_buffer[m_pos] == '0' && m_buffer[m_pos + 1] == 'x')
|
||||||
|
{
|
||||||
|
radix = 16;
|
||||||
|
m_pos += 2;
|
||||||
|
}
|
||||||
|
else if (m_buffer[m_pos] == '0' && m_buffer[m_pos + 1] == 'b')
|
||||||
|
{
|
||||||
|
radix = 2;
|
||||||
|
m_pos += 2;
|
||||||
|
}
|
||||||
|
else if (m_buffer[m_pos] == '0' && IsAsciiDigit(m_buffer[m_pos+1]))
|
||||||
|
{
|
||||||
|
radix = 8;
|
||||||
|
m_pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
long n = 0;
|
||||||
|
int digit;
|
||||||
|
|
||||||
|
while ((digit = ConvertDigit(m_buffer[m_pos], radix)) != -1)
|
||||||
|
{
|
||||||
|
n = n * radix + digit;
|
||||||
|
m_pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return negate ? -n : n;
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,13 +31,14 @@ enum class Directive
|
|||||||
Include,
|
Include,
|
||||||
String,
|
String,
|
||||||
Braille,
|
Braille,
|
||||||
|
Enum,
|
||||||
Unknown
|
Unknown
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsmFile
|
class AsmFile
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AsmFile(std::string filename);
|
AsmFile(std::string filename, bool isStdin, bool doEnum);
|
||||||
AsmFile(AsmFile&& other);
|
AsmFile(AsmFile&& other);
|
||||||
AsmFile(const AsmFile&) = delete;
|
AsmFile(const AsmFile&) = delete;
|
||||||
~AsmFile();
|
~AsmFile();
|
||||||
@@ -49,9 +50,11 @@ public:
|
|||||||
bool IsAtEnd();
|
bool IsAtEnd();
|
||||||
void OutputLine();
|
void OutputLine();
|
||||||
void OutputLocation();
|
void OutputLocation();
|
||||||
|
bool ParseEnum();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
char* m_buffer;
|
char* m_buffer;
|
||||||
|
bool m_doEnum;
|
||||||
long m_pos;
|
long m_pos;
|
||||||
long m_size;
|
long m_size;
|
||||||
long m_lineNum;
|
long m_lineNum;
|
||||||
@@ -67,6 +70,11 @@ private:
|
|||||||
void ReportDiagnostic(const char* type, const char* format, std::va_list args);
|
void ReportDiagnostic(const char* type, const char* format, std::va_list args);
|
||||||
void RaiseError(const char* format, ...);
|
void RaiseError(const char* format, ...);
|
||||||
void RaiseWarning(const char* format, ...);
|
void RaiseWarning(const char* format, ...);
|
||||||
|
void VerifyStringLength(int length);
|
||||||
|
int SkipWhitespaceAndEol();
|
||||||
|
int FindLastLineNumber(std::string& filename);
|
||||||
|
std::string ReadIdentifier();
|
||||||
|
long ReadInteger(std::string filename, long line);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ASM_FILE_H
|
#endif // ASM_FILE_H
|
||||||
|
|||||||
+13
-24
@@ -23,39 +23,27 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cerrno>
|
||||||
#include "preproc.h"
|
#include "preproc.h"
|
||||||
#include "c_file.h"
|
#include "c_file.h"
|
||||||
#include "char_util.h"
|
#include "char_util.h"
|
||||||
#include "utf8.h"
|
#include "utf8.h"
|
||||||
#include "string_parser.h"
|
#include "string_parser.h"
|
||||||
|
#include "io.h"
|
||||||
|
|
||||||
CFile::CFile(std::string filename) : m_filename(filename)
|
CFile::CFile(const char * filenameCStr, bool isStdin)
|
||||||
{
|
{
|
||||||
FILE *fp = std::fopen(filename.c_str(), "rb");
|
if (isStdin)
|
||||||
|
m_filename = std::string{"<stdin>/"}.append(filenameCStr);
|
||||||
|
else
|
||||||
|
m_filename = std::string(filenameCStr);
|
||||||
|
|
||||||
if (fp == NULL)
|
m_buffer = ReadFileToBuffer(filenameCStr, isStdin, &m_size);
|
||||||
FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename.c_str());
|
|
||||||
|
|
||||||
std::fseek(fp, 0, SEEK_END);
|
|
||||||
|
|
||||||
m_size = std::ftell(fp);
|
|
||||||
|
|
||||||
if (m_size < 0)
|
|
||||||
FATAL_ERROR("File size of \"%s\" is less than zero.\n", filename.c_str());
|
|
||||||
|
|
||||||
m_buffer = new char[m_size + 1];
|
|
||||||
|
|
||||||
std::rewind(fp);
|
|
||||||
|
|
||||||
if (std::fread(m_buffer, m_size, 1, fp) != 1)
|
|
||||||
FATAL_ERROR("Failed to read \"%s\".\n", filename.c_str());
|
|
||||||
|
|
||||||
m_buffer[m_size] = 0;
|
|
||||||
|
|
||||||
std::fclose(fp);
|
|
||||||
|
|
||||||
m_pos = 0;
|
m_pos = 0;
|
||||||
m_lineNum = 1;
|
m_lineNum = 1;
|
||||||
|
m_isStdin = isStdin;
|
||||||
}
|
}
|
||||||
|
|
||||||
CFile::CFile(CFile&& other) : m_filename(std::move(other.m_filename))
|
CFile::CFile(CFile&& other) : m_filename(std::move(other.m_filename))
|
||||||
@@ -64,13 +52,14 @@ CFile::CFile(CFile&& other) : m_filename(std::move(other.m_filename))
|
|||||||
m_pos = other.m_pos;
|
m_pos = other.m_pos;
|
||||||
m_size = other.m_size;
|
m_size = other.m_size;
|
||||||
m_lineNum = other.m_lineNum;
|
m_lineNum = other.m_lineNum;
|
||||||
|
m_isStdin = other.m_isStdin;
|
||||||
|
|
||||||
other.m_buffer = nullptr;
|
other.m_buffer = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
CFile::~CFile()
|
CFile::~CFile()
|
||||||
{
|
{
|
||||||
delete[] m_buffer;
|
free(m_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CFile::Preproc()
|
void CFile::Preproc()
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
class CFile
|
class CFile
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CFile(std::string filename);
|
CFile(const char * filenameCStr, bool isStdin);
|
||||||
CFile(CFile&& other);
|
CFile(CFile&& other);
|
||||||
CFile(const CFile&) = delete;
|
CFile(const CFile&) = delete;
|
||||||
~CFile();
|
~CFile();
|
||||||
@@ -42,6 +42,7 @@ private:
|
|||||||
long m_size;
|
long m_size;
|
||||||
long m_lineNum;
|
long m_lineNum;
|
||||||
std::string m_filename;
|
std::string m_filename;
|
||||||
|
bool m_isStdin;
|
||||||
|
|
||||||
bool ConsumeHorizontalWhitespace();
|
bool ConsumeHorizontalWhitespace();
|
||||||
bool ConsumeNewline();
|
bool ConsumeNewline();
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#include "preproc.h"
|
||||||
|
#include "io.h"
|
||||||
|
#include <string>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
char *ReadFileToBuffer(const char *filename, bool isStdin, long *size)
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
if (isStdin)
|
||||||
|
fp = stdin;
|
||||||
|
else
|
||||||
|
fp = std::fopen(filename, "rb");
|
||||||
|
|
||||||
|
if (fp == NULL)
|
||||||
|
FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename);
|
||||||
|
|
||||||
|
*size = 0;
|
||||||
|
char *buffer = (char *)malloc(CHUNK_SIZE + 1);
|
||||||
|
if (buffer == NULL) {
|
||||||
|
FATAL_ERROR("Failed to allocate memory to process file \"%s\"!", filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t numAllocatedBytes = CHUNK_SIZE + 1;
|
||||||
|
std::size_t bufferOffset = 0;
|
||||||
|
std::size_t count;
|
||||||
|
|
||||||
|
while ((count = std::fread(buffer + bufferOffset, 1, CHUNK_SIZE, fp)) != 0) {
|
||||||
|
if (!std::ferror(fp)) {
|
||||||
|
*size += count;
|
||||||
|
|
||||||
|
if (std::feof(fp)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
numAllocatedBytes += CHUNK_SIZE;
|
||||||
|
bufferOffset += CHUNK_SIZE;
|
||||||
|
buffer = (char *)realloc(buffer, numAllocatedBytes);
|
||||||
|
if (buffer == NULL) {
|
||||||
|
FATAL_ERROR("Failed to allocate memory to process file \"%s\"!", filename);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FATAL_ERROR("Failed to read \"%s\". (error: %s)", filename, std::strerror(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[*size] = 0;
|
||||||
|
|
||||||
|
std::fclose(fp);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef IO_H_
|
||||||
|
#define IO_H_
|
||||||
|
|
||||||
|
#define CHUNK_SIZE 4096
|
||||||
|
|
||||||
|
char *ReadFileToBuffer(const char *filename, bool isStdin, long *size);
|
||||||
|
|
||||||
|
#endif // IO_H_
|
||||||
+61
-14
@@ -20,11 +20,14 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
|
#include <unistd.h>
|
||||||
#include "preproc.h"
|
#include "preproc.h"
|
||||||
#include "asm_file.h"
|
#include "asm_file.h"
|
||||||
#include "c_file.h"
|
#include "c_file.h"
|
||||||
#include "charmap.h"
|
#include "charmap.h"
|
||||||
|
|
||||||
|
static void UsageAndExit(const char *program);
|
||||||
|
|
||||||
Charmap* g_charmap;
|
Charmap* g_charmap;
|
||||||
|
|
||||||
void PrintAsmBytes(unsigned char *s, int length)
|
void PrintAsmBytes(unsigned char *s, int length)
|
||||||
@@ -43,11 +46,12 @@ void PrintAsmBytes(unsigned char *s, int length)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreprocAsmFile(std::string filename)
|
void PreprocAsmFile(std::string filename, bool isStdin, bool doEnum)
|
||||||
{
|
{
|
||||||
std::stack<AsmFile> stack;
|
std::stack<AsmFile> stack;
|
||||||
|
|
||||||
stack.push(AsmFile(filename));
|
stack.push(AsmFile(filename, isStdin, doEnum));
|
||||||
|
std::printf("# 1 \"%s\"\n", filename.c_str());
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
@@ -66,7 +70,7 @@ void PreprocAsmFile(std::string filename)
|
|||||||
switch (directive)
|
switch (directive)
|
||||||
{
|
{
|
||||||
case Directive::Include:
|
case Directive::Include:
|
||||||
stack.push(AsmFile(stack.top().ReadPath()));
|
stack.push(AsmFile(stack.top().ReadPath(), false, doEnum));
|
||||||
stack.top().OutputLocation();
|
stack.top().OutputLocation();
|
||||||
break;
|
break;
|
||||||
case Directive::String:
|
case Directive::String:
|
||||||
@@ -83,6 +87,12 @@ void PreprocAsmFile(std::string filename)
|
|||||||
PrintAsmBytes(s, length);
|
PrintAsmBytes(s, length);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Directive::Enum:
|
||||||
|
{
|
||||||
|
if (!stack.top().ParseEnum())
|
||||||
|
stack.top().OutputLine();
|
||||||
|
break;
|
||||||
|
}
|
||||||
case Directive::Unknown:
|
case Directive::Unknown:
|
||||||
{
|
{
|
||||||
std::string globalLabel = stack.top().GetGlobalLabel();
|
std::string globalLabel = stack.top().GetGlobalLabel();
|
||||||
@@ -103,15 +113,15 @@ void PreprocAsmFile(std::string filename)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreprocCFile(std::string filename)
|
void PreprocCFile(const char * filename, bool isStdin)
|
||||||
{
|
{
|
||||||
CFile cFile(filename);
|
CFile cFile(filename, isStdin);
|
||||||
cFile.Preproc();
|
cFile.Preproc();
|
||||||
}
|
}
|
||||||
|
|
||||||
char* GetFileExtension(char* filename)
|
const char* GetFileExtension(const char* filename)
|
||||||
{
|
{
|
||||||
char* extension = filename;
|
const char* extension = filename;
|
||||||
|
|
||||||
while (*extension != 0)
|
while (*extension != 0)
|
||||||
extension++;
|
extension++;
|
||||||
@@ -130,27 +140,64 @@ char* GetFileExtension(char* filename)
|
|||||||
return extension;
|
return extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void UsageAndExit(const char *program)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "Usage: %s [-i] [-e] SRC_FILE CHARMAP_FILE\nwhere -i denotes if input is from stdin\n -e enables enum handling\n", program);
|
||||||
|
std::exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
if (argc != 3)
|
int opt;
|
||||||
|
const char *source = NULL;
|
||||||
|
const char *charmap = NULL;
|
||||||
|
bool isStdin = false;
|
||||||
|
bool doEnum = false;
|
||||||
|
|
||||||
|
/* preproc [-i] [-e] SRC_FILE CHARMAP_FILE */
|
||||||
|
while ((opt = getopt(argc, argv, "ie")) != -1)
|
||||||
{
|
{
|
||||||
std::fprintf(stderr, "Usage: %s SRC_FILE CHARMAP_FILE", argv[0]);
|
switch (opt)
|
||||||
return 1;
|
{
|
||||||
|
case 'i':
|
||||||
|
isStdin = true;
|
||||||
|
break;
|
||||||
|
case 'e':
|
||||||
|
doEnum = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UsageAndExit(argv[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g_charmap = new Charmap(argv[2]);
|
if (optind + 2 != argc)
|
||||||
|
UsageAndExit(argv[0]);
|
||||||
|
|
||||||
char* extension = GetFileExtension(argv[1]);
|
source = argv[optind + 0];
|
||||||
|
charmap = argv[optind + 1];
|
||||||
|
|
||||||
|
g_charmap = new Charmap(charmap);
|
||||||
|
|
||||||
|
const char* extension = GetFileExtension(source);
|
||||||
|
|
||||||
if (!extension)
|
if (!extension)
|
||||||
FATAL_ERROR("\"%s\" has no file extension.\n", argv[1]);
|
FATAL_ERROR("\"%s\" has no file extension.\n", argv[1]);
|
||||||
|
|
||||||
if ((extension[0] == 's') && extension[1] == 0)
|
if ((extension[0] == 's') && extension[1] == 0)
|
||||||
PreprocAsmFile(argv[1]);
|
{
|
||||||
|
PreprocAsmFile(source, isStdin, doEnum);
|
||||||
|
}
|
||||||
else if ((extension[0] == 'c' || extension[0] == 'i') && extension[1] == 0)
|
else if ((extension[0] == 'c' || extension[0] == 'i') && extension[1] == 0)
|
||||||
PreprocCFile(argv[1]);
|
{
|
||||||
|
if (doEnum)
|
||||||
|
FATAL_ERROR("-e is invalid for C sources\n");
|
||||||
|
PreprocCFile(source, isStdin);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
FATAL_ERROR("\"%s\" has an unknown file extension of \"%s\".\n", argv[1], extension);
|
FATAL_ERROR("\"%s\" has an unknown file extension of \"%s\".\n", argv[1], extension);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
CXX = g++
|
CXX ?= g++
|
||||||
|
|
||||||
CXXFLAGS = -Wall -Werror -std=c++11 -O2
|
CXXFLAGS = -Wall -Werror -std=c++11 -O2
|
||||||
|
|
||||||
@@ -8,10 +8,16 @@ HEADERS := scaninc.h asm_file.h c_file.h source_file.h
|
|||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|
||||||
all: scaninc
|
ifeq ($(OS),Windows_NT)
|
||||||
|
EXE := .exe
|
||||||
|
else
|
||||||
|
EXE :=
|
||||||
|
endif
|
||||||
|
|
||||||
|
all: scaninc$(EXE)
|
||||||
@:
|
@:
|
||||||
|
|
||||||
scaninc: $(SRCS) $(HEADERS)
|
scaninc$(EXE): $(SRCS) $(HEADERS)
|
||||||
$(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS)
|
$(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
#include <queue>
|
#include <queue>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <tuple>
|
||||||
|
#include <fstream>
|
||||||
#include "scaninc.h"
|
#include "scaninc.h"
|
||||||
#include "source_file.h"
|
#include "source_file.h"
|
||||||
|
|
||||||
@@ -38,15 +41,19 @@ bool CanOpenFile(std::string path)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *const USAGE = "Usage: scaninc [-I INCLUDE_PATH] FILE_PATH\n";
|
const char *const USAGE = "Usage: scaninc [-I INCLUDE_PATH] [-M DEPENDENCY_OUT_PATH] FILE_PATH\n";
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
std::queue<std::string> filesToProcess;
|
std::queue<std::string> filesToProcess;
|
||||||
std::set<std::string> dependencies;
|
std::set<std::string> dependencies;
|
||||||
|
std::set<std::string> dependencies_includes;
|
||||||
|
|
||||||
std::vector<std::string> includeDirs;
|
std::vector<std::string> includeDirs;
|
||||||
|
|
||||||
|
bool makeformat = false;
|
||||||
|
std::string make_outfile;
|
||||||
|
|
||||||
argc--;
|
argc--;
|
||||||
argv++;
|
argv++;
|
||||||
|
|
||||||
@@ -68,6 +75,13 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
includeDirs.push_back(includeDir);
|
includeDirs.push_back(includeDir);
|
||||||
}
|
}
|
||||||
|
else if(arg.substr(0, 2) == "-M")
|
||||||
|
{
|
||||||
|
makeformat = true;
|
||||||
|
argc--;
|
||||||
|
argv++;
|
||||||
|
make_outfile = std::string(argv[0]);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FATAL_ERROR(USAGE);
|
FATAL_ERROR(USAGE);
|
||||||
@@ -112,6 +126,7 @@ int main(int argc, char **argv)
|
|||||||
{
|
{
|
||||||
path = include;
|
path = include;
|
||||||
}
|
}
|
||||||
|
dependencies_includes.insert(path);
|
||||||
bool inserted = dependencies.insert(path).second;
|
bool inserted = dependencies.insert(path).second;
|
||||||
if (inserted && exists)
|
if (inserted && exists)
|
||||||
{
|
{
|
||||||
@@ -121,8 +136,36 @@ int main(int argc, char **argv)
|
|||||||
includeDirs.pop_back();
|
includeDirs.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const std::string &path : dependencies)
|
if(!makeformat)
|
||||||
{
|
{
|
||||||
std::printf("%s\n", path.c_str());
|
for (const std::string &path : dependencies)
|
||||||
|
{
|
||||||
|
std::printf("%s\n", path.c_str());
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Write out make rules to a file
|
||||||
|
std::ofstream output(make_outfile);
|
||||||
|
|
||||||
|
// Print a make rule for the object file
|
||||||
|
size_t ext_pos = make_outfile.find_last_of(".");
|
||||||
|
auto object_file = make_outfile.substr(0, ext_pos + 1) + "o";
|
||||||
|
output << object_file.c_str() << ": ";
|
||||||
|
for (const std::string &path : dependencies)
|
||||||
|
{
|
||||||
|
output << path << " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dependency list rule.
|
||||||
|
// Although these rules are identical, they need to be separate, else make will trigger the rule again after the file is created for the first time.
|
||||||
|
output << "\n" << make_outfile.c_str() << ": ";
|
||||||
|
for (const std::string &path : dependencies_includes)
|
||||||
|
{
|
||||||
|
output << path << " ";
|
||||||
|
}
|
||||||
|
output.flush();
|
||||||
|
output.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user