diff --git a/.clang-format-ignore b/.clang-format-ignore index 55eb7cebd..7b186a053 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -4,3 +4,4 @@ arm9/lib/ arm7/lib/ include/nitro/ +tools/nitrogfx/ diff --git a/tools/nitrogfx/.gitignore b/tools/nitrogfx/.gitignore index 4379fab8f..80659a2f7 100644 --- a/tools/nitrogfx/.gitignore +++ b/tools/nitrogfx/.gitignore @@ -1 +1,3 @@ nitrogfx +nitrogfx-debug +.vscode/ diff --git a/tools/nitrogfx/Makefile b/tools/nitrogfx/Makefile index f4c67d07a..948416ea1 100644 --- a/tools/nitrogfx/Makefile +++ b/tools/nitrogfx/Makefile @@ -6,7 +6,7 @@ ifeq ($(HAVE_LIBPNG),1) $(error No package 'libpng' found) endif -CFLAGS = -Wall -Wextra -Werror -Wno-sign-compare -std=c11 -O2 -DPNG_SKIP_SETJMP_CHECK $(shell pkg-config --cflags libpng zlib) +CFLAGS = -Wall -Wextra -Werror -Wno-sign-compare -std=gnu17 -DPNG_SKIP_SETJMP_CHECK $(shell pkg-config --cflags libpng zlib) LIBS = $(shell pkg-config --libs libpng zlib) @@ -22,7 +22,7 @@ nitrogfx-debug: $(SRCS) convert_png.h gfx.h global.h jasc_pal.h lz.h rl.h util.h $(CC) $(CFLAGS) -g -DDEBUG $(SRCS) -o $@ $(LDFLAGS) $(LIBS) nitrogfx: $(SRCS) convert_png.h gfx.h global.h jasc_pal.h lz.h rl.h util.h font.h json.h cJSON.h - $(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) $(LIBS) + $(CC) $(CFLAGS) -O2 $(SRCS) -o $@ $(LDFLAGS) $(LIBS) clean: $(RM) -r nitrogfx nitrogfx.exe $(OBJS) diff --git a/tools/nitrogfx/cJSON.c b/tools/nitrogfx/cJSON.c index 3063f74e9..d7c72363d 100644 --- a/tools/nitrogfx/cJSON.c +++ b/tools/nitrogfx/cJSON.c @@ -96,9 +96,9 @@ CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) return (const char*) (global_error.json + global_error.position); } -CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) { - if (!cJSON_IsString(item)) + if (!cJSON_IsString(item)) { return NULL; } @@ -106,9 +106,9 @@ CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) return item->valuestring; } -CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) { - if (!cJSON_IsNumber(item)) + if (!cJSON_IsNumber(item)) { return (double) NAN; } @@ -117,7 +117,7 @@ CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) } /* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ -#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 15) +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 18) #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. #endif @@ -263,10 +263,12 @@ CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) { global_hooks.deallocate(item->valuestring); + item->valuestring = NULL; } if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) { global_hooks.deallocate(item->string); + item->string = NULL; } global_hooks.deallocate(item); item = next; @@ -397,16 +399,33 @@ CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) return object->valuedouble = number; } +/* Note: when passing a NULL valuestring, cJSON_SetValuestring treats this as an error and return NULL */ CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) { char *copy = NULL; + size_t v1_len; + size_t v2_len; /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ - if (!(object->type & cJSON_String) || (object->type & cJSON_IsReference)) + if ((object == NULL) || !(object->type & cJSON_String) || (object->type & cJSON_IsReference)) { return NULL; } - if (strlen(valuestring) <= strlen(object->valuestring)) + /* return NULL if the object is corrupted or valuestring is NULL */ + if (object->valuestring == NULL || valuestring == NULL) { + return NULL; + } + + v1_len = strlen(valuestring); + v2_len = strlen(object->valuestring); + + if (v1_len <= v2_len) + { + /* strcpy does not handle overlapping string: [X1, X2] [Y1, Y2] => X2 < Y1 or Y2 < X1 */ + if (!( valuestring + v1_len < object->valuestring || object->valuestring + v2_len < valuestring )) + { + return NULL; + } strcpy(object->valuestring, valuestring); return object->valuestring; } @@ -511,7 +530,7 @@ static unsigned char* ensure(printbuffer * const p, size_t needed) return NULL; } - + memcpy(newbuffer, p->buffer, p->offset + 1); p->hooks.deallocate(p->buffer); } @@ -562,6 +581,10 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out { length = sprintf((char*)number_buffer, "null"); } + else if(d == (double)item->valueint) + { + length = sprintf((char*)number_buffer, "%d", item->valueint); + } else { /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ @@ -884,6 +907,7 @@ static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_bu if (output != NULL) { input_buffer->hooks.deallocate(output); + output = NULL; } if (input_pointer != NULL) @@ -1103,7 +1127,7 @@ CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer } buffer.content = (const unsigned char*)value; - buffer.length = buffer_length; + buffer.length = buffer_length; buffer.offset = 0; buffer.hooks = global_hooks; @@ -1226,6 +1250,7 @@ static unsigned char *print(const cJSON * const item, cJSON_bool format, const i /* free the buffer */ hooks->deallocate(buffer->buffer); + buffer->buffer = NULL; } return printed; @@ -1234,11 +1259,13 @@ static unsigned char *print(const cJSON * const item, cJSON_bool format, const i if (buffer->buffer != NULL) { hooks->deallocate(buffer->buffer); + buffer->buffer = NULL; } if (printed != NULL) { hooks->deallocate(printed); + printed = NULL; } return NULL; @@ -1279,6 +1306,7 @@ CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON if (!print_value(item, &p)) { global_hooks.deallocate(p.buffer); + p.buffer = NULL; return NULL; } @@ -1650,6 +1678,11 @@ static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_bu current_item = new_item; } + if (cannot_access_at_index(input_buffer, 1)) + { + goto fail; /* nothing comes after the comma */ + } + /* parse the name of the child */ input_buffer->offset++; buffer_skip_whitespace(input_buffer); @@ -2182,7 +2215,7 @@ CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * c CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) { - if ((parent == NULL) || (item == NULL)) + if ((parent == NULL) || (item == NULL) || (item != parent->child && item->prev == NULL)) { return NULL; } @@ -2260,7 +2293,7 @@ CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON { cJSON *after_inserted = NULL; - if (which < 0) + if (which < 0 || newitem == NULL) { return false; } @@ -2271,6 +2304,11 @@ CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON return add_item_to_array(array, newitem); } + if (after_inserted != array->child && after_inserted->prev == NULL) { + /* return false if after_inserted is a corrupted array item */ + return false; + } + newitem->next = after_inserted; newitem->prev = after_inserted->prev; after_inserted->prev = newitem; @@ -2287,7 +2325,7 @@ CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) { - if ((parent == NULL) || (replacement == NULL) || (item == NULL)) + if ((parent == NULL) || (parent->child == NULL) || (replacement == NULL) || (item == NULL)) { return false; } @@ -2357,6 +2395,11 @@ static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSO cJSON_free(replacement->string); } replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if (replacement->string == NULL) + { + return false; + } + replacement->type &= ~cJSON_StringIsConst; return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); @@ -2689,12 +2732,19 @@ CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int co if (a && a->child) { a->child->prev = n; } - + return a; } /* Duplication */ +cJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse); + CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + return cJSON_Duplicate_rec(item, 0, recurse ); +} + +cJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse) { cJSON *newitem = NULL; cJSON *child = NULL; @@ -2741,7 +2791,10 @@ CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) child = item->child; while (child != NULL) { - newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if(depth >= CJSON_CIRCULAR_LIMIT) { + goto fail; + } + newchild = cJSON_Duplicate_rec(child, depth + 1, true); /* Duplicate (with recurse) each item in the ->next chain */ if (!newchild) { goto fail; @@ -3107,4 +3160,5 @@ CJSON_PUBLIC(void *) cJSON_malloc(size_t size) CJSON_PUBLIC(void) cJSON_free(void *object) { global_hooks.deallocate(object); + object = NULL; } diff --git a/tools/nitrogfx/cJSON.h b/tools/nitrogfx/cJSON.h index 92907a2cd..37520bbcf 100644 --- a/tools/nitrogfx/cJSON.h +++ b/tools/nitrogfx/cJSON.h @@ -81,7 +81,7 @@ then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJ /* project version */ #define CJSON_VERSION_MAJOR 1 #define CJSON_VERSION_MINOR 7 -#define CJSON_VERSION_PATCH 15 +#define CJSON_VERSION_PATCH 18 #include @@ -137,6 +137,12 @@ typedef int cJSON_bool; #define CJSON_NESTING_LIMIT 1000 #endif +/* Limits the length of circular references can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_CIRCULAR_LIMIT +#define CJSON_CIRCULAR_LIMIT 10000 +#endif + /* returns the version of cJSON as a string */ CJSON_PUBLIC(const char*) cJSON_Version(void); @@ -279,6 +285,13 @@ CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); /* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); +/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/ +#define cJSON_SetBoolValue(object, boolValue) ( \ + (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \ + (object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \ + cJSON_Invalid\ +) + /* Macro for iterating over an array or object */ #define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) diff --git a/tools/nitrogfx/font.c b/tools/nitrogfx/font.c index 0dd6fbc3e..f929e30ce 100644 --- a/tools/nitrogfx/font.c +++ b/tools/nitrogfx/font.c @@ -4,323 +4,486 @@ #include #include #include +#include #include "global.h" #include "font.h" #include "gfx.h" +#include "options.h" #include "util.h" unsigned char gFontPalette[][3] = { - {0x90, 0xC8, 0xFF}, // bg (saturated blue that contrasts well with the shadow color) - {0x38, 0x38, 0x38}, // fg (dark grey) - {0xD8, 0xD8, 0xD8}, // shadow (light grey) - {0xFF, 0xFF, 0xFF} // box (white) + {0x90, 0xC8, 0xFF}, // bg (saturated blue that contrasts well with the shadow color) + {0x38, 0x38, 0x38}, // fg (dark grey) + {0xD8, 0xD8, 0xD8}, // shadow (light grey) + {0xFF, 0xFF, 0xFF} // box (white) +}; + +// special palette for DS subscreen font +unsigned char gFontPalette_Subscreen[][3] = { + {0x90, 0xC8, 0xFF}, // bg (saturated blue that contrasts well with the shadow color) + {0xFF, 0xFF, 0xFF}, // fg (white) + {0xD8, 0xD8, 0xD8}, // shadow (light grey) + {0x38, 0x38, 0x38}, // outline (dark grey) }; static void ConvertFromLatinFont(unsigned char *src, unsigned char *dest, unsigned int numRows) { - unsigned int srcPixelsOffset = 0; + unsigned int srcPixelsOffset = 0; - for (unsigned int row = 0; row < numRows; row++) { - for (unsigned int column = 0; column < 16; column++) { - for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) { - unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8); + for (unsigned int row = 0; row < numRows; row++) { + for (unsigned int column = 0; column < 16; column++) { + for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) { + unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8); - for (unsigned int i = 0; i < 8; i++) { - unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i; - unsigned int destPixelsOffset = (pixelsY * 64) + (pixelsX / 4); + for (unsigned int i = 0; i < 8; i++) { + unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i; + unsigned int destPixelsOffset = (pixelsY * 64) + (pixelsX / 4); - dest[destPixelsOffset] = src[srcPixelsOffset + 1]; - dest[destPixelsOffset + 1] = src[srcPixelsOffset]; + dest[destPixelsOffset] = src[srcPixelsOffset + 1]; + dest[destPixelsOffset + 1] = src[srcPixelsOffset]; - srcPixelsOffset += 2; - } - } - } - } + srcPixelsOffset += 2; + } + } + } + } } static void ConvertToLatinFont(unsigned char *src, unsigned char *dest, unsigned int numRows) { - unsigned int destPixelsOffset = 0; + unsigned int destPixelsOffset = 0; - for (unsigned int row = 0; row < numRows; row++) { - for (unsigned int column = 0; column < 16; column++) { - for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) { - unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8); + for (unsigned int row = 0; row < numRows; row++) { + for (unsigned int column = 0; column < 16; column++) { + for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) { + unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8); - for (unsigned int i = 0; i < 8; i++) { - unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i; - unsigned int srcPixelsOffset = (pixelsY * 64) + (pixelsX / 4); + for (unsigned int i = 0; i < 8; i++) { + unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i; + unsigned int srcPixelsOffset = (pixelsY * 64) + (pixelsX / 4); - dest[destPixelsOffset] = src[srcPixelsOffset + 1]; - dest[destPixelsOffset + 1] = src[srcPixelsOffset]; + dest[destPixelsOffset] = src[srcPixelsOffset + 1]; + dest[destPixelsOffset + 1] = src[srcPixelsOffset]; - destPixelsOffset += 2; - } - } - } - } + destPixelsOffset += 2; + } + } + } + } } static void ConvertFromHalfwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows) { - for (unsigned int row = 0; row < numRows; row++) { - for (unsigned int column = 0; column < 16; column++) { - unsigned int glyphIndex = (row * 16) + column; - - for (unsigned int glyphTile = 0; glyphTile < 2; glyphTile++) { - unsigned int pixelsX = column * 8; - unsigned int srcPixelsOffset = 512 * (glyphIndex >> 4) + 16 * (glyphIndex & 0xF) + 256 * glyphTile; - - for (unsigned int i = 0; i < 8; i++) { - unsigned int pixelsY = (row * 16) + (glyphTile * 8) + i; - unsigned int destPixelsOffset = (pixelsY * 32) + (pixelsX / 4); - - dest[destPixelsOffset] = src[srcPixelsOffset + 1]; - dest[destPixelsOffset + 1] = src[srcPixelsOffset]; - - srcPixelsOffset += 2; - } - } - } - } + for (unsigned int row = 0; row < numRows; row++) { + for (unsigned int column = 0; column < 16; column++) { + unsigned int glyphIndex = (row * 16) + column; + + for (unsigned int glyphTile = 0; glyphTile < 2; glyphTile++) { + unsigned int pixelsX = column * 8; + unsigned int srcPixelsOffset = 512 * (glyphIndex >> 4) + 16 * (glyphIndex & 0xF) + 256 * glyphTile; + + for (unsigned int i = 0; i < 8; i++) { + unsigned int pixelsY = (row * 16) + (glyphTile * 8) + i; + unsigned int destPixelsOffset = (pixelsY * 32) + (pixelsX / 4); + + dest[destPixelsOffset] = src[srcPixelsOffset + 1]; + dest[destPixelsOffset + 1] = src[srcPixelsOffset]; + + srcPixelsOffset += 2; + } + } + } + } } static void ConvertToHalfwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows) { - for (unsigned int row = 0; row < numRows; row++) { - for (unsigned int column = 0; column < 16; column++) { - unsigned int glyphIndex = (row * 16) + column; - - for (unsigned int glyphTile = 0; glyphTile < 2; glyphTile++) { - unsigned int pixelsX = column * 8; - unsigned int destPixelsOffset = 512 * (glyphIndex >> 4) + 16 * (glyphIndex & 0xF) + 256 * glyphTile; - - for (unsigned int i = 0; i < 8; i++) { - unsigned int pixelsY = (row * 16) + (glyphTile * 8) + i; - unsigned int srcPixelsOffset = (pixelsY * 32) + (pixelsX / 4); - - dest[destPixelsOffset] = src[srcPixelsOffset + 1]; - dest[destPixelsOffset + 1] = src[srcPixelsOffset]; - - destPixelsOffset += 2; - } - } - } - } + for (unsigned int row = 0; row < numRows; row++) { + for (unsigned int column = 0; column < 16; column++) { + unsigned int glyphIndex = (row * 16) + column; + + for (unsigned int glyphTile = 0; glyphTile < 2; glyphTile++) { + unsigned int pixelsX = column * 8; + unsigned int destPixelsOffset = 512 * (glyphIndex >> 4) + 16 * (glyphIndex & 0xF) + 256 * glyphTile; + + for (unsigned int i = 0; i < 8; i++) { + unsigned int pixelsY = (row * 16) + (glyphTile * 8) + i; + unsigned int srcPixelsOffset = (pixelsY * 32) + (pixelsX / 4); + + dest[destPixelsOffset] = src[srcPixelsOffset + 1]; + dest[destPixelsOffset + 1] = src[srcPixelsOffset]; + + destPixelsOffset += 2; + } + } + } + } } static void ConvertFromFullwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows) { - for (unsigned int row = 0; row < numRows; row++) { - for (unsigned int column = 0; column < 16; column++) { - unsigned int glyphIndex = (row * 16) + column; - - for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) { - unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8); - unsigned int srcPixelsOffset = 512 * (glyphIndex >> 3) + 32 * (glyphIndex & 7) + 256 * (glyphTile >> 1) + 16 * (glyphTile & 1); - - for (unsigned int i = 0; i < 8; i++) { - unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i; - unsigned int destPixelsOffset = (pixelsY * 64) + (pixelsX / 4); - - dest[destPixelsOffset] = src[srcPixelsOffset + 1]; - dest[destPixelsOffset + 1] = src[srcPixelsOffset]; - - srcPixelsOffset += 2; - } - } - } - } + for (unsigned int row = 0; row < numRows; row++) { + for (unsigned int column = 0; column < 16; column++) { + unsigned int glyphIndex = (row * 16) + column; + + for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) { + unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8); + unsigned int srcPixelsOffset = 512 * (glyphIndex >> 3) + 32 * (glyphIndex & 7) + 256 * (glyphTile >> 1) + 16 * (glyphTile & 1); + + for (unsigned int i = 0; i < 8; i++) { + unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i; + unsigned int destPixelsOffset = (pixelsY * 64) + (pixelsX / 4); + + dest[destPixelsOffset] = src[srcPixelsOffset + 1]; + dest[destPixelsOffset + 1] = src[srcPixelsOffset]; + + srcPixelsOffset += 2; + } + } + } + } } static void ConvertToFullwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows) { - for (unsigned int row = 0; row < numRows; row++) { - for (unsigned int column = 0; column < 16; column++) { - unsigned int glyphIndex = (row * 16) + column; - - for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) { - unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8); - unsigned int destPixelsOffset = 512 * (glyphIndex >> 3) + 32 * (glyphIndex & 7) + 256 * (glyphTile >> 1) + 16 * (glyphTile & 1); - - for (unsigned int i = 0; i < 8; i++) { - unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i; - unsigned int srcPixelsOffset = (pixelsY * 64) + (pixelsX / 4); - - dest[destPixelsOffset] = src[srcPixelsOffset + 1]; - dest[destPixelsOffset + 1] = src[srcPixelsOffset]; - - destPixelsOffset += 2; - } - } - } - } + for (unsigned int row = 0; row < numRows; row++) { + for (unsigned int column = 0; column < 16; column++) { + unsigned int glyphIndex = (row * 16) + column; + + for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) { + unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8); + unsigned int destPixelsOffset = 512 * (glyphIndex >> 3) + 32 * (glyphIndex & 7) + 256 * (glyphTile >> 1) + 16 * (glyphTile & 1); + + for (unsigned int i = 0; i < 8; i++) { + unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i; + unsigned int srcPixelsOffset = (pixelsY * 64) + (pixelsX / 4); + + dest[destPixelsOffset] = src[srcPixelsOffset + 1]; + dest[destPixelsOffset + 1] = src[srcPixelsOffset]; + + destPixelsOffset += 2; + } + } + } + } +} + +static void ConvertFromNitroFont(unsigned char *src, unsigned char *dest, unsigned int numRows, struct NtrFontMetadata *metadata) +{ + unsigned int srcPixelsOffset = 0; + unsigned int curGlyph = 0; + + for (unsigned int row = 0; row < numRows; row++) { + for (unsigned int column = 0; column < 16 && curGlyph < metadata->numGlyphs; column++, curGlyph++) { + for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) { + unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8); + + for (unsigned int i = 0; i < 8; i++) { + unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i; + unsigned int destPixelsOffset = (pixelsY * 64) + (pixelsX / 4); + + dest[destPixelsOffset] = src[srcPixelsOffset + 1]; + dest[destPixelsOffset + 1] = src[srcPixelsOffset]; + + srcPixelsOffset += 2; + } + } + } + } +} + +static void ConvertToNitroFont(unsigned char *src, unsigned char *dest, unsigned int numRows, struct NtrFontMetadata *metadata) +{ + unsigned int destPixelsOffset = 0; + unsigned int curGlyph = 0; + + for (unsigned int row = 0; row < numRows; row++) { + for (unsigned int column = 0; column < 16 && curGlyph < metadata->numGlyphs; column++, curGlyph++) { + for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) { + unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8); + + for (unsigned int i = 0; i < 8; i++) { + unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i; + unsigned int srcPixelsOffset = (pixelsY * 64) + (pixelsX / 4); + + dest[destPixelsOffset] = src[srcPixelsOffset + 1]; + dest[destPixelsOffset + 1] = src[srcPixelsOffset]; + + destPixelsOffset += 2; + } + } + } + } } static void SetFontPalette(struct Image *image) { - image->hasPalette = true; + image->hasPalette = true; - image->palette.numColors = 4; + image->palette.numColors = 4; - for (int i = 0; i < image->palette.numColors; i++) { - image->palette.colors[i].red = gFontPalette[i][0]; - image->palette.colors[i].green = gFontPalette[i][1]; - image->palette.colors[i].blue = gFontPalette[i][2]; - } + for (int i = 0; i < image->palette.numColors; i++) { + image->palette.colors[i].red = gFontPalette[i][0]; + image->palette.colors[i].green = gFontPalette[i][1]; + image->palette.colors[i].blue = gFontPalette[i][2]; + } - image->hasTransparency = false; + image->hasTransparency = false; +} + +static void SetSubscreenFontPalette(struct Image *image) +{ + image->hasPalette = true; + + image->palette.numColors = 4; + + for (int i = 0; i < image->palette.numColors; i++) { + image->palette.colors[i].red = gFontPalette_Subscreen[i][0]; + image->palette.colors[i].green = gFontPalette_Subscreen[i][1]; + image->palette.colors[i].blue = gFontPalette_Subscreen[i][2]; + } + + image->hasTransparency = false; } void ReadLatinFont(char *path, struct Image *image) { - int fileSize; - unsigned char *buffer = ReadWholeFile(path, &fileSize); + int fileSize; + unsigned char *buffer = ReadWholeFile(path, &fileSize); - int numGlyphs = fileSize / 64; + int numGlyphs = fileSize / 64; - if (numGlyphs % 16 != 0) - FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs); + if (numGlyphs % 16 != 0) + FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs); - int numRows = numGlyphs / 16; + int numRows = numGlyphs / 16; - image->width = 256; - image->height = numRows * 16; - image->bitDepth = 2; - image->pixels = malloc(fileSize); + image->width = 256; + image->height = numRows * 16; + image->bitDepth = 2; + image->pixels = malloc(fileSize); - if (image->pixels == NULL) - FATAL_ERROR("Failed to allocate memory for font.\n"); + if (image->pixels == NULL) + FATAL_ERROR("Failed to allocate memory for font.\n"); - ConvertFromLatinFont(buffer, image->pixels, numRows); + ConvertFromLatinFont(buffer, image->pixels, numRows); - free(buffer); + free(buffer); - SetFontPalette(image); + SetFontPalette(image); } void WriteLatinFont(char *path, struct Image *image) { - if (image->width != 256) - FATAL_ERROR("The width of the font image (%d) is not 256.\n", image->width); + if (image->width != 256) + FATAL_ERROR("The width of the font image (%d) is not 256.\n", image->width); - if (image->height % 16 != 0) - FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height); + if (image->height % 16 != 0) + FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height); - int numRows = image->height / 16; - int bufferSize = numRows * 16 * 64; - unsigned char *buffer = malloc(bufferSize); + int numRows = image->height / 16; + int bufferSize = numRows * 16 * 64; + unsigned char *buffer = malloc(bufferSize); - if (buffer == NULL) - FATAL_ERROR("Failed to allocate memory for font.\n"); + if (buffer == NULL) + FATAL_ERROR("Failed to allocate memory for font.\n"); - ConvertToLatinFont(image->pixels, buffer, numRows); + ConvertToLatinFont(image->pixels, buffer, numRows); - WriteWholeFile(path, buffer, bufferSize); + WriteWholeFile(path, buffer, bufferSize); - free(buffer); + free(buffer); } void ReadHalfwidthJapaneseFont(char *path, struct Image *image) { - int fileSize; - unsigned char *buffer = ReadWholeFile(path, &fileSize); + int fileSize; + unsigned char *buffer = ReadWholeFile(path, &fileSize); - int glyphSize = 32; + int glyphSize = 32; - if (fileSize % glyphSize != 0) - FATAL_ERROR("The file size (%d) is not a multiple of %d.\n", fileSize, glyphSize); + if (fileSize % glyphSize != 0) + FATAL_ERROR("The file size (%d) is not a multiple of %d.\n", fileSize, glyphSize); - int numGlyphs = fileSize / glyphSize; - - if (numGlyphs % 16 != 0) - FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs); + int numGlyphs = fileSize / glyphSize; + + if (numGlyphs % 16 != 0) + FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs); - int numRows = numGlyphs / 16; + int numRows = numGlyphs / 16; - image->width = 128; - image->height = numRows * 16; - image->bitDepth = 2; - image->pixels = malloc(fileSize); + image->width = 128; + image->height = numRows * 16; + image->bitDepth = 2; + image->pixels = malloc(fileSize); - if (image->pixels == NULL) - FATAL_ERROR("Failed to allocate memory for font.\n"); + if (image->pixels == NULL) + FATAL_ERROR("Failed to allocate memory for font.\n"); - ConvertFromHalfwidthJapaneseFont(buffer, image->pixels, numRows); + ConvertFromHalfwidthJapaneseFont(buffer, image->pixels, numRows); - free(buffer); + free(buffer); - SetFontPalette(image); + SetFontPalette(image); } void WriteHalfwidthJapaneseFont(char *path, struct Image *image) { - if (image->width != 128) - FATAL_ERROR("The width of the font image (%d) is not 128.\n", image->width); + if (image->width != 128) + FATAL_ERROR("The width of the font image (%d) is not 128.\n", image->width); - if (image->height % 16 != 0) - FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height); + if (image->height % 16 != 0) + FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height); - int numRows = image->height / 16; - int bufferSize = numRows * 16 * 32; - unsigned char *buffer = malloc(bufferSize); + int numRows = image->height / 16; + int bufferSize = numRows * 16 * 32; + unsigned char *buffer = malloc(bufferSize); - if (buffer == NULL) - FATAL_ERROR("Failed to allocate memory for font.\n"); + if (buffer == NULL) + FATAL_ERROR("Failed to allocate memory for font.\n"); - ConvertToHalfwidthJapaneseFont(image->pixels, buffer, numRows); + ConvertToHalfwidthJapaneseFont(image->pixels, buffer, numRows); - WriteWholeFile(path, buffer, bufferSize); + WriteWholeFile(path, buffer, bufferSize); - free(buffer); + free(buffer); } void ReadFullwidthJapaneseFont(char *path, struct Image *image) { - int fileSize; - unsigned char *buffer = ReadWholeFile(path, &fileSize); + int fileSize; + unsigned char *buffer = ReadWholeFile(path, &fileSize); - int numGlyphs = fileSize / 64; + int numGlyphs = fileSize / 64; - if (numGlyphs % 16 != 0) - FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs); + if (numGlyphs % 16 != 0) + FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs); - int numRows = numGlyphs / 16; + int numRows = numGlyphs / 16; - image->width = 256; - image->height = numRows * 16; - image->bitDepth = 2; - image->pixels = malloc(fileSize); + image->width = 256; + image->height = numRows * 16; + image->bitDepth = 2; + image->pixels = malloc(fileSize); - if (image->pixels == NULL) - FATAL_ERROR("Failed to allocate memory for font.\n"); + if (image->pixels == NULL) + FATAL_ERROR("Failed to allocate memory for font.\n"); - ConvertFromFullwidthJapaneseFont(buffer, image->pixels, numRows); + ConvertFromFullwidthJapaneseFont(buffer, image->pixels, numRows); - free(buffer); + free(buffer); - SetFontPalette(image); + SetFontPalette(image); } void WriteFullwidthJapaneseFont(char *path, struct Image *image) { - if (image->width != 256) - FATAL_ERROR("The width of the font image (%d) is not 256.\n", image->width); + if (image->width != 256) + FATAL_ERROR("The width of the font image (%d) is not 256.\n", image->width); + + if (image->height % 16 != 0) + FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height); + + int numRows = image->height / 16; + int bufferSize = numRows * 16 * 64; + unsigned char *buffer = malloc(bufferSize); + + if (buffer == NULL) + FATAL_ERROR("Failed to allocate memory for font.\n"); + + ConvertToFullwidthJapaneseFont(image->pixels, buffer, numRows); + + WriteWholeFile(path, buffer, bufferSize); + + free(buffer); +} + +static inline uint32_t ReadLittleEndianWord(unsigned char *buffer, size_t start) +{ + return (buffer[start + 3] << 24) + | (buffer[start + 2] << 16) + | (buffer[start + 1] << 8) + | (buffer[start]); +} + +void ReadNtrFont(char *path, struct Image *image, struct NtrFontMetadata *metadata, bool useSubscreenPalette) +{ + int filesize; + unsigned char *buffer = ReadWholeFile(path, &filesize); - if (image->height % 16 != 0) - FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height); + metadata->size = ReadLittleEndianWord(buffer, 0x00); + metadata->widthTableOffset = ReadLittleEndianWord(buffer, 0x04); + metadata->numGlyphs = ReadLittleEndianWord(buffer, 0x08); + metadata->maxWidth = buffer[0x0C]; + metadata->maxHeight = buffer[0x0D]; + metadata->glyphWidth = buffer[0x0E]; + metadata->glyphHeight = buffer[0x0F]; - int numRows = image->height / 16; - int bufferSize = numRows * 16 * 64; - unsigned char *buffer = malloc(bufferSize); + int numRows = (metadata->numGlyphs + 15) / 16; // Round up to next multiple of 16. - if (buffer == NULL) - FATAL_ERROR("Failed to allocate memory for font.\n"); + metadata->glyphWidthTable = malloc(metadata->numGlyphs); + memcpy(metadata->glyphWidthTable, buffer + metadata->widthTableOffset, metadata->numGlyphs); - ConvertToFullwidthJapaneseFont(image->pixels, buffer, numRows); + image->width = 256; + image->height = numRows * 16; + image->bitDepth = 2; + image->pixels = malloc(filesize); - WriteWholeFile(path, buffer, bufferSize); + if (image->pixels == NULL) + FATAL_ERROR("Failed to allocate memory for font.\n"); - free(buffer); + ConvertFromNitroFont(buffer + metadata->size, image->pixels, numRows, metadata); + + free(buffer); + + if (useSubscreenPalette) + SetSubscreenFontPalette(image); + else + SetFontPalette(image); +} + +void WriteNtrFont(char *path, struct Image *image, struct NtrFontMetadata *metadata) +{ + if (image->width != 256) + FATAL_ERROR("The width of the font image (%d) is not 256.\n", image->width); + + if (image->height % 16 != 0) + FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height); + + int numRows = image->height / 16; + int bufferSize = metadata->widthTableOffset + metadata->numGlyphs; + unsigned char *buffer = malloc(bufferSize); + + if (buffer == NULL) + FATAL_ERROR("Failed to allocate memory for font.\n"); + + buffer[0x00] = (metadata->size & 0x000000FF); + buffer[0x01] = (metadata->size & 0x0000FF00) >> 8; + buffer[0x02] = (metadata->size & 0x00FF0000) >> 16; + buffer[0x03] = (metadata->size & 0xFF000000) >> 24; + buffer[0x04] = (metadata->widthTableOffset & 0x000000FF); + buffer[0x05] = (metadata->widthTableOffset & 0x0000FF00) >> 8; + buffer[0x06] = (metadata->widthTableOffset & 0x00FF0000) >> 16; + buffer[0x07] = (metadata->widthTableOffset & 0xFF000000) >> 24; + buffer[0x08] = (metadata->numGlyphs & 0x000000FF); + buffer[0x09] = (metadata->numGlyphs & 0x0000FF00) >> 8; + buffer[0x0A] = (metadata->numGlyphs & 0x00FF0000) >> 16; + buffer[0x0B] = (metadata->numGlyphs & 0xFF000000) >> 24; + buffer[0x0C] = metadata->maxWidth; + buffer[0x0D] = metadata->maxHeight; + buffer[0x0E] = metadata->glyphWidth; + buffer[0x0F] = metadata->glyphHeight; + + ConvertToNitroFont(image->pixels, buffer + metadata->size, numRows, metadata); + memcpy(buffer + metadata->widthTableOffset, metadata->glyphWidthTable, metadata->numGlyphs); + + WriteWholeFile(path, buffer, bufferSize); + + free(buffer); +} + +void FreeNtrFontMetadata(struct NtrFontMetadata *metadata) +{ + free(metadata->glyphWidthTable); + free(metadata); } diff --git a/tools/nitrogfx/font.h b/tools/nitrogfx/font.h index 45086d02a..70dca0808 100644 --- a/tools/nitrogfx/font.h +++ b/tools/nitrogfx/font.h @@ -5,6 +5,7 @@ #include #include "gfx.h" +#include "options.h" void ReadLatinFont(char *path, struct Image *image); void WriteLatinFont(char *path, struct Image *image); @@ -12,5 +13,8 @@ void ReadHalfwidthJapaneseFont(char *path, struct Image *image); void WriteHalfwidthJapaneseFont(char *path, struct Image *image); void ReadFullwidthJapaneseFont(char *path, struct Image *image); void WriteFullwidthJapaneseFont(char *path, struct Image *image); +void ReadNtrFont(char *path, struct Image *image, struct NtrFontMetadata *metadata, bool useSubscreenPalette); +void WriteNtrFont(char *path, struct Image *image, struct NtrFontMetadata *metadata); +void FreeNtrFontMetadata(struct NtrFontMetadata *metadata); #endif // FONT_H diff --git a/tools/nitrogfx/gfx.c b/tools/nitrogfx/gfx.c index 7e87236c9..ffbece9ad 100644 --- a/tools/nitrogfx/gfx.c +++ b/tools/nitrogfx/gfx.c @@ -9,6 +9,26 @@ #include "gfx.h" #include "util.h" +static unsigned int FindNitroDataBlock(const unsigned char *data, const char *ident, unsigned int fileSize, unsigned int *blockSize_out) +{ + unsigned int offset = 0x10; + while (offset < fileSize) + { + unsigned int blockSize = data[offset + 4] | (data[offset + 5] << 8) | (data[offset + 6] << 16) | (data[offset + 7] << 24); + if (offset + blockSize > fileSize) + { + FATAL_ERROR("corrupted NTR file"); + } + if (memcmp(data + offset, ident, 4) == 0) + { + *blockSize_out = blockSize; + return offset; + } + offset += blockSize; + } + return -1u; +} + #define GET_GBA_PAL_RED(x) (((x) >> 0) & 0x1F) #define GET_GBA_PAL_GREEN(x) (((x) >> 5) & 0x1F) #define GET_GBA_PAL_BLUE(x) (((x) >> 10) & 0x1F) @@ -694,7 +714,7 @@ void ReadGbaPalette(char *path, struct Palette *palette) free(data); } -void ReadNtrPalette(char *path, struct Palette *palette, int bitdepth, int palIndex) +void ReadNtrPalette(char *path, struct Palette *palette, int bitdepth, int palIndex, bool inverted) { int fileSize; unsigned char *data = ReadWholeFile(path, &fileSize); @@ -719,6 +739,7 @@ void ReadNtrPalette(char *path, struct Palette *palette, int bitdepth, int palIn bitdepth = bitdepth ? bitdepth : palette->bitDepth; size_t paletteSize = (paletteHeader[0x10]) | (paletteHeader[0x11] << 8) | (paletteHeader[0x12] << 16) | (paletteHeader[0x13] << 24); + if (inverted) paletteSize = 0x200 - paletteSize; if (palIndex == 0) { palette->numColors = paletteSize / 2; } else { @@ -769,7 +790,7 @@ void WriteGbaPalette(char *path, struct Palette *palette) fclose(fp); } -void WriteNtrPalette(char *path, struct Palette *palette, bool ncpr, bool ir, int bitdepth, bool pad, int compNum, bool pcmp) +void WriteNtrPalette(char *path, struct Palette *palette, bool ncpr, bool ir, int bitdepth, bool pad, int compNum, bool pcmp, bool inverted) { FILE *fp = fopen(path, "wb"); @@ -823,10 +844,11 @@ void WriteNtrPalette(char *path, struct Palette *palette, bool ncpr, bool ir, in } //size - palHeader[16] = size & 0xFF; - palHeader[17] = (size >> 8) & 0xFF; - palHeader[18] = (size >> 16) & 0xFF; - palHeader[19] = (size >> 24) & 0xFF; + int colorSize = inverted ? 0x200 - size : size; + palHeader[16] = colorSize & 0xFF; + palHeader[17] = (colorSize >> 8) & 0xFF; + palHeader[18] = (colorSize >> 16) & 0xFF; + palHeader[19] = (colorSize >> 24) & 0xFF; fwrite(palHeader, 1, 0x18, fp); @@ -888,38 +910,30 @@ void WriteNtrPalette(char *path, struct Palette *palette, bool ncpr, bool ir, in fclose(fp); } -void ReadNtrCell(char *path, struct JsonToCellOptions *options) +void ReadNtrCell_CEBK(unsigned char * restrict data, unsigned int blockOffset, unsigned int blockSize, struct JsonToCellOptions *options) { - int fileSize; - unsigned char *data = ReadWholeFile(path, &fileSize); + options->cellCount = data[blockOffset + 0x8] | (data[blockOffset + 0x9] << 8); + options->extended = data[blockOffset + 0xA] == 1; - if (memcmp(data, "RECN", 4) != 0) //NCER - { - FATAL_ERROR("Not a valid NCER cell file.\n"); - } - - options->labelEnabled = data[0xE] != 1; - - if (memcmp(data + 0x10, "KBEC", 4) != 0 ) //KBEC - { - FATAL_ERROR("Not a valid KBEC cell file.\n"); - } - - options->cellCount = data[0x18] | (data[0x19] << 8); - options->extended = data[0x1A] == 1; + int vramTransferOffset = (data[blockOffset + 0x14] | data[blockOffset + 0x15] << 8); + options->vramTransferEnabled = vramTransferOffset > 0; /*if (!options->extended) { //in theory not extended should be implemented, however not 100% sure FATAL_ERROR("Don't know how to deal with not extended yet, bug red031000.\n"); }*/ - options->mappingType = data[0x20]; + options->mappingType = data[blockOffset + 0x10]; options->cells = malloc(sizeof(struct Cell *) * options->cellCount); + int celSize = options->extended ? 0x10 : 0x8; for (int i = 0; i < options->cellCount; i++) { - int offset = 0x30 + (i * (options->extended ? 0x10 : 0x8)); + int offset = blockOffset + 0x20 + (i * celSize); + if (offset + celSize > blockOffset + blockSize) { + FATAL_ERROR("corrupted CEBK block\n"); + } options->cells[i] = malloc(sizeof(struct Cell)); options->cells[i]->oamCount = data[offset] | (data[offset + 1] << 8); short cellAttrs = data[offset + 2] | (data[offset + 3] << 8); @@ -938,7 +952,7 @@ void ReadNtrCell(char *path, struct JsonToCellOptions *options) } } - int offset = 0x30 + (options->cellCount * (options->extended ? 0x10 : 0x8)); + int offset = blockOffset + 0x20 + (options->cellCount * celSize); for (int i = 0; i < options->cellCount; i++) { options->cells[i]->oam = malloc(sizeof(struct OAM) * options->cells[i]->oamCount); @@ -993,35 +1007,84 @@ void ReadNtrCell(char *path, struct JsonToCellOptions *options) } } - if (options->labelEnabled) + if (options->vramTransferEnabled) { - int count = 0; - int offset = 0x30 + (options->cellCount * 0x16) + 0x8; - bool flag = false; - //this entire thing is a huge assumption, it will not work with labels that are less than 2 characters long - while (!flag) + offset = blockOffset + 0x08 + vramTransferOffset; + + // first 2 dwords are max size and offset, offset *should* always be 0x08 since the transfer data list immediately follows this + options->vramTransferMaxSize = data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24); + offset += 0x08; + + // read 1 VRAM transfer data block for each cell (this is an assumption based on the NCERs I looked at) + options->transferData = malloc(sizeof(struct CellVramTransferData *) * options->cellCount); + for (int idx = 0; idx < options->cellCount; idx++) { - if (strlen((char *) data + offset) < 2) - { - //probably a pointer, maybe? - count++; - offset += 4; - } - else - { - //huzzah a string - flag = true; - } + options->transferData[idx] = malloc(sizeof(struct CellVramTransferData)); + options->transferData[idx]->sourceDataOffset = data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24); + options->transferData[idx]->size = data[offset + 4] | (data[offset + 5] << 8) | (data[offset + 6] << 16) | (data[offset + 7] << 24); + offset += 8; } - options->labelCount = count; - options->labels = malloc(sizeof(char *) * count); - for (int i = 0; i < count; i++) + } +} + +void ReadNtrCell_LABL(unsigned char * restrict data, unsigned int blockOffset, unsigned int blockSize, struct JsonToCellOptions *options) +{ + int count = 0; + unsigned int textStart = blockOffset + 8; + while (textStart < blockOffset + blockSize) + { + unsigned int labelOffset = data[textStart] | (data[textStart + 1] << 8) | (data[textStart + 2] << 16) | (data[textStart + 3] << 24); + if (labelOffset > blockSize) + { + break; + } + else { + ++count; + textStart += 4; + } + } + options->labelCount = count; + options->labels = malloc(sizeof(char *) * count); + for (int i = 0; i < count; ++i) + { + int offset = textStart + (data[blockOffset + 4 * i + 8] | (data[blockOffset + 4 * i + 9] << 8) | (data[blockOffset + 4 * i + 10] << 16) | (data[blockOffset + 4 * i + 11] << 24)); + if (offset > blockOffset + blockSize) { - options->labels[i] = malloc(strlen((char *) data + offset) + 1); - strcpy(options->labels[i], (char *) data + offset); - offset += strlen(options->labels[i]) + 1; + FATAL_ERROR("corrupted LABL block\n"); } - //after this should be txeu, if everything was done right + unsigned long slen = strnlen((char *)data + offset, blockSize - offset); + options->labels[i] = malloc(slen + 1); + strncpy(options->labels[i], (char *)data + offset, slen + 1); + } +} + +void ReadNtrCell(char *path, struct JsonToCellOptions *options) +{ + int fileSize; + unsigned char *data = ReadWholeFile(path, &fileSize); + unsigned int offset = 0x10; + + if (memcmp(data, "RECN", 4) != 0) //NCER + { + FATAL_ERROR("Not a valid NCER cell file.\n"); + } + + options->labelEnabled = false; + + unsigned int blockSize; + offset = FindNitroDataBlock(data, "KBEC", fileSize, &blockSize); + if (offset != -1u) + { + ReadNtrCell_CEBK(data, offset, blockSize, options); + } + else { + FATAL_ERROR("missing CEBK block"); + } + offset = FindNitroDataBlock(data, "LBAL", fileSize, &blockSize); + if (offset != -1u) + { + options->labelEnabled = true; + ReadNtrCell_LABL(data, offset, blockSize, options); } free(data); @@ -1034,7 +1097,25 @@ void WriteNtrCell(char *path, struct JsonToCellOptions *options) if (fp == NULL) FATAL_ERROR("Failed to open \"%s\" for writing.\n", path); - unsigned int totalSize = (options->labelEnabled > 0 ? 0x34 : 0x20) + options->cellCount * (options->extended ? 0x16 : 0xe); + int iterNum = (options->extended ? 0x10 : 0x8); + + // KBEC base size: 0x08 per bank, or 0x10 per extended bank + unsigned int kbecSize = options->cellCount * (options->extended ? 0x10 : 0x08); + // if VRAM transfer is enabled, add 0x08 for the header and 0x08 for each cell + if (options->vramTransferEnabled) + { + kbecSize += 0x08 + (0x08 * options->cellCount); + } + // add 0x06 for number of OAMs - can be more than 1 + for (int idx = 0; idx < options->cellCount * iterNum; idx += iterNum) + { + kbecSize += options->cells[idx / iterNum]->oamCount * 0x06; + } + + // KBEC size is padded to be 4-byte aligned + kbecSize += kbecSize % 4; + + unsigned int totalSize = (options->labelEnabled > 0 ? 0x34 : 0x20) + kbecSize; if (options->labelEnabled) { @@ -1059,18 +1140,27 @@ void WriteNtrCell(char *path, struct JsonToCellOptions *options) KBECHeader[10] = 1; //extended } - unsigned int size = options->cellCount * (options->extended ? 0x16 : 0xe); - - KBECHeader[4] = (size + 0x20) & 0xFF; //size - KBECHeader[5] = (size + 0x20) >> 8; //unlikely to be more than 16 bits, but there are 32 allocated, change if necessary + KBECHeader[4] = (kbecSize + 0x20) & 0xFF; //size + KBECHeader[5] = (kbecSize + 0x20) >> 8; //unlikely to be more than 16 bits, but there are 32 allocated, change if necessary KBECHeader[16] = (options->mappingType & 0xFF); //not possible to be more than 8 bits, though 32 are allocated + // offset to VRAM transfer data within KBEC section (offset from KBEC start + 0x08) + if (options->vramTransferEnabled) + { + unsigned int vramTransferLength = 0x08 + (0x08 * options->cellCount); + unsigned int vramTransferOffset = (kbecSize + 0x20) - vramTransferLength - 0x08; + KBECHeader[20] = vramTransferOffset & 0xFF; + KBECHeader[21] = (vramTransferOffset >> 8) & 0xFF; + KBECHeader[22] = (vramTransferOffset >> 16) & 0xFF; + KBECHeader[23] = (vramTransferOffset >> 24) & 0xFF; + } + fwrite(KBECHeader, 1, 0x20, fp); - unsigned char *KBECContents = malloc(size); + unsigned char *KBECContents = malloc(kbecSize); - memset(KBECContents, 0, size); + memset(KBECContents, 0, kbecSize); /*if (!options->extended) { @@ -1079,7 +1169,6 @@ void WriteNtrCell(char *path, struct JsonToCellOptions *options) }*/ int i; - int iterNum = (options->extended ? 0x10 : 0x8); int totalOam = 0; for (i = 0; i < options->cellCount * iterNum; i += iterNum) { @@ -1163,7 +1252,38 @@ void WriteNtrCell(char *path, struct JsonToCellOptions *options) } } - fwrite(KBECContents, 1, size, fp); + // VRAM transfer data + if (options->vramTransferEnabled) + { + // max transfer size + fixed offset 0x08 + KBECContents[offset] = options->vramTransferMaxSize & 0xFF; + KBECContents[offset + 1] = (options->vramTransferMaxSize >> 8) & 0xFF; + KBECContents[offset + 2] = (options->vramTransferMaxSize >> 16) & 0xFF; + KBECContents[offset + 3] = (options->vramTransferMaxSize >> 24) & 0xFF; + + KBECContents[offset + 4] = 0x08; + + offset += 8; + + // write a VRAM transfer block for each cell + for (int idx = 0; idx < options->cellCount; idx++) + { + // offset + KBECContents[offset] = options->transferData[idx]->sourceDataOffset & 0xFF; + KBECContents[offset + 1] = (options->transferData[idx]->sourceDataOffset >> 8) & 0xFF; + KBECContents[offset + 2] = (options->transferData[idx]->sourceDataOffset >> 16) & 0xFF; + KBECContents[offset + 3] = (options->transferData[idx]->sourceDataOffset >> 24) & 0xFF; + + // size + KBECContents[offset + 4] = options->transferData[idx]->size & 0xFF; + KBECContents[offset + 5] = (options->transferData[idx]->size >> 8) & 0xFF; + KBECContents[offset + 6] = (options->transferData[idx]->size >> 16) & 0xFF; + KBECContents[offset + 7] = (options->transferData[idx]->size >> 24) & 0xFF; + offset += 8; + } + } + + fwrite(KBECContents, 1, kbecSize, fp); free(KBECContents); @@ -1318,6 +1438,7 @@ void ReadNtrAnimation(char *path, struct JsonToAnimationOptions *options) { if (resultOffsets[k] == options->sequenceData[i]->frameData[j]->resultOffset) { + options->sequenceData[i]->frameData[j]->resultId = k; present = true; break; } @@ -1330,6 +1451,7 @@ void ReadNtrAnimation(char *path, struct JsonToAnimationOptions *options) { if (resultOffsets[k] == -1) { + options->sequenceData[i]->frameData[j]->resultId = k; resultOffsets[k] = options->sequenceData[i]->frameData[j]->resultOffset; break; } @@ -1360,37 +1482,49 @@ void ReadNtrAnimation(char *path, struct JsonToAnimationOptions *options) options->animationResults[i] = malloc(sizeof(struct AnimationResults)); } - int resultOffset = 0; - for (int i = 0; i < options->resultCount; i++) + // store the animationElement of the corresponding sequence as this result's resultType + for (int i = 0; i < options->sequenceCount; i++) { - if (data[offset + 2] == 0xCC && data[offset + 3] == 0xCC) - { - options->animationResults[i]->resultType = 0; - } - else if (data[offset + 2] == 0xEF && data[offset + 3] == 0xBE) - { - options->animationResults[i]->resultType = 2; - } - else + for (int j = 0; j < options->sequenceData[i]->frameCount; j++) { - options->animationResults[i]->resultType = 1; + options->animationResults[options->sequenceData[i]->frameData[j]->resultId]->resultType = options->sequenceData[i]->animationElement; } + } + + int resultOffset = 0; + int lastSequence = 0; + for (int i = 0; i < options->resultCount; i++) + { + // find the earliest sequence matching this animation result, + // and add padding if the sequence changes + the total offset is not 4-byte aligned. + bool found = false; for (int j = 0; j < options->sequenceCount; j++) { for (int k = 0; k < options->sequenceData[j]->frameCount; k++) { - if (options->sequenceData[j]->frameData[k]->resultOffset == resultOffset) + if (options->sequenceData[j]->frameData[k]->resultId == i) { - options->sequenceData[j]->frameData[k]->resultId = i; + if (lastSequence != j) + { + lastSequence = j; + if (resultOffset % 4 != 0) + { + resultOffset += 0x2; + offset += 0x2; + } + } + found = true; + break; } } + if (found) break; } switch (options->animationResults[i]->resultType) { case 0: //index options->animationResults[i]->index = data[offset] | (data[offset + 1] << 8); - resultOffset += 0x4; - offset += 0x4; + resultOffset += 0x2; + offset += 0x2; break; case 1: //SRT @@ -1414,6 +1548,9 @@ void ReadNtrAnimation(char *path, struct JsonToAnimationOptions *options) } } + // add any missed padding from the final frame before processing labels + if (offset % 4 != 0) offset += 2; + if (options->labelEnabled) { options->labelCount = options->sequenceCount; //*should* be the same @@ -1439,17 +1576,69 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) unsigned int totalSize = 0x20 + options->sequenceCount * 0x10 + options->frameCount * 0x8; - //todo: check these for (int i = 0; i < options->resultCount; i++) { if (options->animationResults[i]->resultType == 0) - totalSize += 0x4; + totalSize += 0x2; else if (options->animationResults[i]->resultType == 1) totalSize += 0x10; else if (options->animationResults[i]->resultType == 2) totalSize += 0x8; } + // foreach sequence, need to check whether padding is applied for its results + // then add 0x02 to totalSize if padding exists. + // padding exists if the animation results for that sequence are not 4-byte aligned. + // also flag the last result for the sequence with `padded` to save having to redo this same step later. + int *usedResults = malloc(sizeof(int) * options->frameCount); + memset(usedResults, -1, sizeof(int) * options->frameCount); + for (int i = 0; i < options->sequenceCount; i++) + { + int sequenceLen = 0; + int resultIndex = 0; + int lastNewResultIndex = -1; + for (int j = 0; j < options->sequenceData[i]->frameCount; j++) + { + // check if the result has already been used + bool isUsed = false; + for (resultIndex = 0; resultIndex < options->resultCount; resultIndex++) + { + if (usedResults[resultIndex] == options->sequenceData[i]->frameData[j]->resultId) + { + isUsed = true; + break; + } + + // if not already used, add it to the list + if (usedResults[resultIndex] == -1) + { + usedResults[resultIndex] = options->sequenceData[i]->frameData[j]->resultId; + lastNewResultIndex = options->sequenceData[i]->frameData[j]->resultId; + break; + } + } + + // if not already used, add it to the result size for the sequence + if (!isUsed) + { + if (options->animationResults[resultIndex]->resultType == 0) + sequenceLen += 0x2; + else if (options->animationResults[resultIndex]->resultType == 1) + sequenceLen += 0x10; + else if (options->animationResults[resultIndex]->resultType == 2) + sequenceLen += 0x8; + } + } + if (sequenceLen % 4 != 0 && lastNewResultIndex != -1) + { + totalSize += 0x02; + // mark the last new animationResult index for the sequence as padded, this saves needing to check this again later + options->animationResults[lastNewResultIndex]->padded = true; + } + } + + free(usedResults); + unsigned int KNBASize = totalSize; if (options->labelEnabled) @@ -1508,9 +1697,9 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) KBNAContents[i + 2] = options->sequenceData[i / 0x10]->loopStartFrame & 0xff; KBNAContents[i + 3] = options->sequenceData[i / 0x10]->loopStartFrame >> 8; KBNAContents[i + 4] = options->sequenceData[i / 0x10]->animationElement & 0xff; - KBNAContents[i + 5] = options->sequenceData[i / 0x10]->animationElement >> 8; + KBNAContents[i + 5] = (options->sequenceData[i / 0x10]->animationElement >> 8) & 0xff; KBNAContents[i + 6] = options->sequenceData[i / 0x10]->animationType & 0xff; - KBNAContents[i + 7] = options->sequenceData[i / 0x10]->animationType >> 8; + KBNAContents[i + 7] = (options->sequenceData[i / 0x10]->animationType >> 8) & 0xff; KBNAContents[i + 8] = options->sequenceData[i / 0x10]->playbackMode & 0xff; KBNAContents[i + 9] = (options->sequenceData[i / 0x10]->playbackMode >> 8) & 0xff; KBNAContents[i + 10] = (options->sequenceData[i / 0x10]->playbackMode >> 16) & 0xff; @@ -1530,11 +1719,13 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) int resPtr = 0; for (int l = 0; l < options->sequenceData[m]->frameData[k]->resultId; l++) { if (options->animationResults[l]->resultType == 0) - resPtr += 0x4; + resPtr += 0x2; else if (options->animationResults[l]->resultType == 1) resPtr += 0x10; else if (options->animationResults[l]->resultType == 2) resPtr += 0x8; + + if (options->animationResults[l]->padded) resPtr += 0x02; } KBNAContents[j + (k * 8)] = resPtr & 0xff; KBNAContents[j + (k * 8) + 1] = (resPtr >> 8) & 0xff; @@ -1548,7 +1739,6 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) j += options->sequenceData[m]->frameCount * 8; } - //todo: these are extrapolated, need confirming int resPtrCounter = j; for (int k = 0; k < options->resultCount; k++) { @@ -1557,9 +1747,7 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) case 0: KBNAContents[resPtrCounter] = options->animationResults[k]->index & 0xff; KBNAContents[resPtrCounter + 1] = options->animationResults[k]->index >> 8; - KBNAContents[resPtrCounter + 2] = 0xCC; - KBNAContents[resPtrCounter + 3] = 0xCC; - resPtrCounter += 0x4; + resPtrCounter += 0x2; break; case 1: @@ -1594,6 +1782,14 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) resPtrCounter += 0x8; break; } + + // use the `padded` flag which was stored earlier to inject padding + if (options->animationResults[k]->padded) + { + KBNAContents[resPtrCounter] = 0xCC; + KBNAContents[resPtrCounter + 1] = 0xCC; + resPtrCounter += 0x2; + } } fwrite(KBNAContents, 1, contentsSize, fp); diff --git a/tools/nitrogfx/gfx.h b/tools/nitrogfx/gfx.h index 91f697875..705b98ab3 100644 --- a/tools/nitrogfx/gfx.h +++ b/tools/nitrogfx/gfx.h @@ -58,9 +58,9 @@ void WriteNtrImage(char *path, int numTiles, int bitDepth, int colsPerChunk, int uint32_t mappingType, uint32_t key, bool wrongSize); void FreeImage(struct Image *image); void ReadGbaPalette(char *path, struct Palette *palette); -void ReadNtrPalette(char *path, struct Palette *palette, int bitdepth, int palIndex); +void ReadNtrPalette(char *path, struct Palette *palette, int bitdepth, int palIndex, bool inverted); void WriteGbaPalette(char *path, struct Palette *palette); -void WriteNtrPalette(char *path, struct Palette *palette, bool ncpr, bool ir, int bitdepth, bool pad, int compNum, bool pcmp); +void WriteNtrPalette(char *path, struct Palette *palette, bool ncpr, bool ir, int bitdepth, bool pad, int compNum, bool pcmp, bool inverted); void ReadNtrCell(char *path, struct JsonToCellOptions *options); void WriteNtrCell(char *path, struct JsonToCellOptions *options); void WriteNtrScreen(char *path, struct JsonToScreenOptions *options); diff --git a/tools/nitrogfx/json.c b/tools/nitrogfx/json.c index 12bc8a956..f0156700c 100644 --- a/tools/nitrogfx/json.c +++ b/tools/nitrogfx/json.c @@ -47,11 +47,13 @@ struct JsonToCellOptions *ParseNCERJson(char *path) } cJSON *labelBool = cJSON_GetObjectItemCaseSensitive(json, "labelEnabled"); + cJSON *vramTransferBool = cJSON_GetObjectItemCaseSensitive(json, "vramTransferEnabled"); cJSON *extended = cJSON_GetObjectItemCaseSensitive(json, "extended"); cJSON *cellCount = cJSON_GetObjectItemCaseSensitive(json, "cellCount"); cJSON *mappingType = cJSON_GetObjectItemCaseSensitive(json, "mappingType"); options->labelEnabled = GetBool(labelBool); + options->vramTransferEnabled = GetBool(vramTransferBool); options->extended = GetBool(extended); options->cellCount = GetInt(cellCount); options->mappingType = GetInt(mappingType); @@ -77,6 +79,30 @@ struct JsonToCellOptions *ParseNCERJson(char *path) } } + if (options->vramTransferEnabled) + { + cJSON *vramTransferMaxSize = cJSON_GetObjectItemCaseSensitive(json, "vramTransferMaxSize"); + options->vramTransferMaxSize = GetInt(vramTransferMaxSize); + + options->transferData = malloc(sizeof(struct CellVramTransferData *) * options->cellCount); + + cJSON *transfers = cJSON_GetObjectItemCaseSensitive(json, "transferData"); + cJSON *transfer = NULL; + + int j = 0; + cJSON_ArrayForEach(transfer, transfers) + { + cJSON *vramTransferOffset = cJSON_GetObjectItemCaseSensitive(transfer, "offset"); + cJSON *vramTransferSize = cJSON_GetObjectItemCaseSensitive(transfer, "size"); + + options->transferData[j] = malloc(sizeof(struct CellVramTransferData)); + options->transferData[j]->sourceDataOffset = GetInt(vramTransferOffset); + options->transferData[j]->size = GetInt(vramTransferSize); + + j++; + } + } + for (int i = 0; i < options->cellCount; i++) { options->cells[i] = malloc(sizeof(struct Cell)); @@ -195,6 +221,7 @@ char *GetNCERJson(struct JsonToCellOptions *options) cJSON_AddBoolToObject(ncer, "labelEnabled", options->labelEnabled); cJSON_AddBoolToObject(ncer, "extended", options->extended); + cJSON_AddBoolToObject(ncer, "vramTransferEnabled", options->vramTransferEnabled); cJSON_AddNumberToObject(ncer, "cellCount", options->cellCount); cJSON_AddNumberToObject(ncer, "mappingType", options->mappingType); @@ -263,6 +290,20 @@ char *GetNCERJson(struct JsonToCellOptions *options) cJSON_AddNumberToObject(ncer, "labelCount", options->labelCount); } + if (options->vramTransferEnabled) + { + cJSON_AddNumberToObject(ncer, "vramTransferMaxSize", options->vramTransferMaxSize); + cJSON *transfers = cJSON_AddArrayToObject(ncer, "transferData"); + + for (int idx = 0; idx < options->cellCount; idx++) + { + cJSON *transfer = cJSON_CreateObject(); + cJSON_AddNumberToObject(transfer, "offset", options->transferData[idx]->sourceDataOffset); + cJSON_AddNumberToObject(transfer, "size", options->transferData[idx]->size); + cJSON_AddItemToArray(transfers, transfer); + } + } + char *jsonString = cJSON_Print(ncer); cJSON_Delete(ncer); return jsonString; @@ -600,6 +641,14 @@ void FreeNCERCell(struct JsonToCellOptions *options) } free(options->labels); } + if (options->vramTransferEnabled) + { + for (int j = 0; j < options->cellCount; j++) + { + free(options->transferData[j]); + } + free(options->transferData); + } free(options->cells); free(options); } @@ -638,3 +687,79 @@ void FreeNANRAnimation(struct JsonToAnimationOptions *options) free(options->animationResults); free(options); } + +char *GetNtrFontMetadataJson(struct NtrFontMetadata *metadata) +{ + cJSON *json = cJSON_CreateObject(); + + cJSON_AddNumberToObject(json, "maxGlyphWidth", metadata->maxWidth); + cJSON_AddNumberToObject(json, "maxGlyphHeight", metadata->maxHeight); + + cJSON *glyphWidths = cJSON_AddArrayToObject(json, "glyphWidths"); + for (int i = 0; i < metadata->numGlyphs; i++) + { + cJSON *width = cJSON_CreateNumber(metadata->glyphWidthTable[i]); + cJSON_AddItemToArray(glyphWidths, width); + } + + char *jsonString = cJSON_Print(json); + cJSON_Delete(json); + return jsonString; +} + +#define TILE_DIMENSION_PIXELS 8 +#define PIXELS_FOR_DIMENSION(dim) ((dim) * TILE_DIMENSION_PIXELS) +#define TILES_FOR_PIXELS(num) (((num) + TILE_DIMENSION_PIXELS - 1) / TILE_DIMENSION_PIXELS) +#define PIXELS_PER_BYTE_2BPP 4 +#define NTR_FONT_HEADER_SIZE 16 + +struct NtrFontMetadata *ParseNtrFontMetadataJson(char *path) +{ + int fileLength; + unsigned char *jsonString = ReadWholeFile(path, &fileLength); + + cJSON *json = cJSON_Parse((const char *)jsonString); + if (json == NULL) + { + const char *errorPtr = cJSON_GetErrorPtr(); + FATAL_ERROR("Error in line \"%s\"\n", errorPtr); + } + + cJSON *labelMaxGlyphWidth = cJSON_GetObjectItemCaseSensitive(json, "maxGlyphWidth"); + cJSON *labelMaxGlyphHeight = cJSON_GetObjectItemCaseSensitive(json, "maxGlyphHeight"); + cJSON *labelGlyphWidths = cJSON_GetObjectItemCaseSensitive(json, "glyphWidths"); + int numGlyphs = cJSON_GetArraySize(labelGlyphWidths); + + struct NtrFontMetadata *metadata = malloc(sizeof(struct NtrFontMetadata)); + + metadata->size = NTR_FONT_HEADER_SIZE; + metadata->numGlyphs = numGlyphs; + metadata->maxWidth = GetInt(labelMaxGlyphWidth); + metadata->maxHeight = GetInt(labelMaxGlyphHeight); + + metadata->glyphWidth = TILES_FOR_PIXELS(metadata->maxWidth); + metadata->glyphHeight = TILES_FOR_PIXELS(metadata->maxHeight); + + int glyphBitmapSize = (PIXELS_FOR_DIMENSION(metadata->glyphWidth) * PIXELS_FOR_DIMENSION(metadata->glyphHeight)) / PIXELS_PER_BYTE_2BPP; + metadata->widthTableOffset = metadata->size + (metadata->numGlyphs * glyphBitmapSize); + + metadata->glyphWidthTable = malloc(metadata->numGlyphs); + + uint8_t *glyphWidthCursor = metadata->glyphWidthTable; + cJSON *glyphWidthIter = NULL; + cJSON_ArrayForEach(glyphWidthIter, labelGlyphWidths) + { + if (!cJSON_IsNumber(glyphWidthIter)) + { + const char *errorPtr = cJSON_GetErrorPtr(); + FATAL_ERROR("Error in line \"%s\"\n", errorPtr); + } + + *glyphWidthCursor = glyphWidthIter->valueint; + glyphWidthCursor++; + } + + cJSON_Delete(json); + free(jsonString); + return metadata; +} diff --git a/tools/nitrogfx/json.h b/tools/nitrogfx/json.h index eb7e2add7..8bdf307ff 100644 --- a/tools/nitrogfx/json.h +++ b/tools/nitrogfx/json.h @@ -13,5 +13,7 @@ char *GetNANRJson(struct JsonToAnimationOptions *options); void FreeNCERCell(struct JsonToCellOptions *options); void FreeNSCRScreen(struct JsonToScreenOptions *options); void FreeNANRAnimation(struct JsonToAnimationOptions *options); +char *GetNtrFontMetadataJson(struct NtrFontMetadata *metadata); +struct NtrFontMetadata *ParseNtrFontMetadataJson(char *path); #endif //JSON_H diff --git a/tools/nitrogfx/lz.c b/tools/nitrogfx/lz.c index 97434ce50..de553178b 100644 --- a/tools/nitrogfx/lz.c +++ b/tools/nitrogfx/lz.c @@ -1,153 +1,188 @@ // Copyright (c) 2015 YamaArashi +#include #include #include +#include #include "global.h" #include "lz.h" unsigned char *LZDecompress(unsigned char *src, int srcSize, int *uncompressedSize) { - if (srcSize < 4) - goto fail; + if (srcSize < 4) + goto fail; - int destSize = (src[3] << 16) | (src[2] << 8) | src[1]; + int destSize = (src[3] << 16) | (src[2] << 8) | src[1]; - unsigned char *dest = malloc(destSize); + unsigned char *dest = malloc(destSize); - if (dest == NULL) - goto fail; + if (dest == NULL) + goto fail; - int srcPos = 4; - int destPos = 0; + int srcPos = 4; + int destPos = 0; - for (;;) { - if (srcPos >= srcSize) - goto fail; + for (;;) { + if (srcPos >= srcSize) + goto fail; - unsigned char flags = src[srcPos++]; + unsigned char flags = src[srcPos++]; - for (int i = 0; i < 8; i++) { - if (flags & 0x80) { - if (srcPos + 1 >= srcSize) - goto fail; + for (int i = 0; i < 8; i++) { + if (flags & 0x80) { + if (srcPos + 1 >= srcSize) + goto fail; - int blockSize = (src[srcPos] >> 4) + 3; - int blockDistance = (((src[srcPos] & 0xF) << 8) | src[srcPos + 1]) + 1; + int blockSize = (src[srcPos] >> 4) + 3; + int blockDistance = (((src[srcPos] & 0xF) << 8) | src[srcPos + 1]) + 1; - srcPos += 2; + srcPos += 2; - int blockPos = destPos - blockDistance; + int blockPos = destPos - blockDistance; - // Some Ruby/Sapphire tilesets overflow. - if (destPos + blockSize > destSize) { - blockSize = destSize - destPos; - fprintf(stderr, "Destination buffer overflow.\n"); - } + // Some Ruby/Sapphire tilesets overflow. + if (destPos + blockSize > destSize) { + blockSize = destSize - destPos; + fprintf(stderr, "Destination buffer overflow.\n"); + } - if (blockPos < 0) - goto fail; + if (blockPos < 0) + goto fail; - for (int j = 0; j < blockSize; j++) - dest[destPos++] = dest[blockPos + j]; - } else { - if (srcPos >= srcSize || destPos >= destSize) - goto fail; + for (int j = 0; j < blockSize; j++) + dest[destPos++] = dest[blockPos + j]; + } else { + if (srcPos >= srcSize || destPos >= destSize) + goto fail; - dest[destPos++] = src[srcPos++]; - } + dest[destPos++] = src[srcPos++]; + } - if (destPos == destSize) { - *uncompressedSize = destSize; - return dest; - } + if (destPos == destSize) { + *uncompressedSize = destSize; + return dest; + } - flags <<= 1; - } - } + flags <<= 1; + } + } fail: - FATAL_ERROR("Fatal error while decompressing LZ file.\n"); + FATAL_ERROR("Fatal error while decompressing LZ file.\n"); } -unsigned char *LZCompress(unsigned char *src, int srcSize, int *compressedSize, const int minDistance) +static void FindBestBlockForwards(unsigned char *src, int srcPos, int srcSize, const int minDistance, int *outBestBlockDistance, int *outBestBlockSize) { - if (srcSize <= 0) - goto fail; - - int worstCaseDestSize = 4 + srcSize + ((srcSize + 7) / 8); - - // Round up to the next multiple of four. - worstCaseDestSize = (worstCaseDestSize + 3) & ~3; - - unsigned char *dest = malloc(worstCaseDestSize); - - if (dest == NULL) - goto fail; - - // header - dest[0] = 0x10; // LZ compression type - dest[1] = (unsigned char)srcSize; - dest[2] = (unsigned char)(srcSize >> 8); - dest[3] = (unsigned char)(srcSize >> 16); - - int srcPos = 0; - int destPos = 4; - - for (;;) { - unsigned char *flags = &dest[destPos++]; - *flags = 0; - - for (int i = 0; i < 8; i++) { - int bestBlockDistance = 0; - int bestBlockSize = 0; - int blockDistance = minDistance; - - while (blockDistance <= srcPos && blockDistance <= 0x1000) { - int blockStart = srcPos - blockDistance; - int blockSize = 0; - - while (blockSize < 18 - && srcPos + blockSize < srcSize - && src[blockStart + blockSize] == src[srcPos + blockSize]) - blockSize++; - - if (blockSize > bestBlockSize) { - bestBlockDistance = blockDistance; - bestBlockSize = blockSize; - - if (blockSize == 18) - break; - } - - blockDistance++; - } - - if (bestBlockSize >= 3) { - *flags |= (0x80 >> i); - srcPos += bestBlockSize; - bestBlockSize -= 3; - bestBlockDistance--; - dest[destPos++] = (bestBlockSize << 4) | ((unsigned int)bestBlockDistance >> 8); - dest[destPos++] = (unsigned char)bestBlockDistance; - } else { - dest[destPos++] = src[srcPos++]; - } - - if (srcPos == srcSize) { - // Pad to multiple of 4 bytes. - int remainder = destPos % 4; - - if (remainder != 0) { - for (int i = 0; i < 4 - remainder; i++) - dest[destPos++] = 0; - } - - *compressedSize = destPos; - return dest; - } - } - } + int blockStart = srcPos < 0x1000 ? 0 : srcPos - 0x1000; + while (blockStart != srcPos) { + int blockSize = 0; + + while (blockSize < 18 + && srcPos + blockSize < srcSize + && src[blockStart + blockSize] == src[srcPos + blockSize]) + blockSize++; + + if (blockSize > *outBestBlockSize + && srcPos - blockStart >= minDistance) { + *outBestBlockDistance = srcPos - blockStart; + *outBestBlockSize = blockSize; + + if (blockSize == 18) + break; + } + + blockStart++; + } +} + +static void FindBestBlockBackwards(unsigned char *src, int srcPos, int srcSize, const int minDistance, int *outBestBlockDistance, int *outBestBlockSize) +{ + int blockDistance = minDistance; + + while (blockDistance <= srcPos && blockDistance <= 0x1000) { + int blockStart = srcPos - blockDistance; + int blockSize = 0; + + while (blockSize < 18 + && srcPos + blockSize < srcSize + && src[blockStart + blockSize] == src[srcPos + blockSize]) + blockSize++; + + if (blockSize > *outBestBlockSize) { + *outBestBlockDistance = blockDistance; + *outBestBlockSize = blockSize; + + if (blockSize == 18) + break; + } + + blockDistance++; + } +} + +typedef void (*FindBestBlockFunc)(unsigned char *src, int srcPos, int srcSize, const int minDistance, int *outBestBlockDistance, int *outBestBlockSize); + +unsigned char *LZCompress(unsigned char *src, int srcSize, int *compressedSize, const int minDistance, bool forwardIteration) +{ + if (srcSize <= 0) + goto fail; + + int worstCaseDestSize = 4 + srcSize + ((srcSize + 7) / 8); + + // Round up to the next multiple of four. + worstCaseDestSize = (worstCaseDestSize + 3) & ~3; + + unsigned char *dest = malloc(worstCaseDestSize); + + if (dest == NULL) + goto fail; + + // header + dest[0] = 0x10; // LZ compression type + dest[1] = (unsigned char)srcSize; + dest[2] = (unsigned char)(srcSize >> 8); + dest[3] = (unsigned char)(srcSize >> 16); + + int srcPos = 0; + int destPos = 4; + FindBestBlockFunc FindBestBlock = forwardIteration ? FindBestBlockForwards : FindBestBlockBackwards; + + for (;;) { + unsigned char *flags = &dest[destPos++]; + *flags = 0; + + for (int i = 0; i < 8; i++) { + int bestBlockDistance = 0; + int bestBlockSize = 0; + + FindBestBlock(src, srcPos, srcSize, minDistance, &bestBlockDistance, &bestBlockSize); + + if (bestBlockSize >= 3) { + *flags |= (0x80 >> i); + srcPos += bestBlockSize; + bestBlockSize -= 3; + bestBlockDistance--; + dest[destPos++] = (bestBlockSize << 4) | ((unsigned int)bestBlockDistance >> 8); + dest[destPos++] = (unsigned char)bestBlockDistance; + } else { + dest[destPos++] = src[srcPos++]; + } + + if (srcPos == srcSize) { + // Pad to multiple of 4 bytes. + int remainder = destPos % 4; + + if (remainder != 0) { + for (int i = 0; i < 4 - remainder; i++) + dest[destPos++] = 0; + } + + *compressedSize = destPos; + return dest; + } + } + } fail: - FATAL_ERROR("Fatal error while compressing LZ file.\n"); + FATAL_ERROR("Fatal error while compressing LZ file.\n"); } diff --git a/tools/nitrogfx/lz.h b/tools/nitrogfx/lz.h index 90f56b643..fdc137023 100644 --- a/tools/nitrogfx/lz.h +++ b/tools/nitrogfx/lz.h @@ -3,7 +3,9 @@ #ifndef LZ_H #define LZ_H +#include "stdbool.h" + unsigned char *LZDecompress(unsigned char *src, int srcSize, int *uncompressedSize); -unsigned char *LZCompress(unsigned char *src, int srcSize, int *compressedSize, const int minDistance); +unsigned char *LZCompress(unsigned char *src, int srcSize, int *compressedSize, const int minDistance, bool forwardIteration); #endif // LZ_H diff --git a/tools/nitrogfx/main.c b/tools/nitrogfx/main.c index 07413676c..88fc48715 100644 --- a/tools/nitrogfx/main.c +++ b/tools/nitrogfx/main.c @@ -74,7 +74,7 @@ void ConvertNtrToPng(char *inputPath, char *outputPath, struct NtrToPngOptions * if (options->paletteFilePath != NULL) { - ReadNtrPalette(options->paletteFilePath, &image.palette, options->bitDepth, options->palIndex); + ReadNtrPalette(options->paletteFilePath, &image.palette, options->bitDepth, options->palIndex, false); image.hasPalette = true; } else @@ -141,7 +141,7 @@ void ConvertPngToNtr(char *inputPath, char *outputPath, struct PngToNtrOptions * fclose(fp); struct Image image; - image.bitDepth = options->bitDepth; + image.bitDepth = options->bitDepth == 0 ? 4 : options->bitDepth; ReadPng(inputPath, &image); @@ -160,7 +160,9 @@ void ConvertPngToNtr(char *inputPath, char *outputPath, struct PngToNtrOptions * free(string); } - WriteNtrImage(outputPath, options->numTiles, image.bitDepth, options->colsPerChunk, options->rowsPerChunk, + options->bitDepth = options->bitDepth == 0 ? image.bitDepth : options->bitDepth; + + WriteNtrImage(outputPath, options->numTiles, options->bitDepth, options->colsPerChunk, options->rowsPerChunk, &image, !image.hasPalette, options->clobberSize, options->byteOrder, options->version101, options->sopc, options->vramTransfer, options->scanMode, options->mappingType, key, options->wrongSize); @@ -420,7 +422,7 @@ void HandlePngToNtrCommand(char *inputPath, char *outputPath, int argc, char **a { struct PngToNtrOptions options; options.numTiles = 0; - options.bitDepth = 4; + options.bitDepth = 0; options.colsPerChunk = 1; options.rowsPerChunk = 1; options.wrongSize = false; @@ -565,6 +567,7 @@ void HandlePngToNtrPaletteCommand(char *inputPath, char *outputPath, int argc, c int bitdepth = 0; int compNum = 0; bool pcmp = false; + bool inverted = false; for (int i = 3; i < argc; i++) { @@ -612,6 +615,10 @@ void HandlePngToNtrPaletteCommand(char *inputPath, char *outputPath, int argc, c { pcmp = true; } + else if (strcmp(option, "-invertsize") == 0) + { + inverted = true; + } else { FATAL_ERROR("Unrecognized option \"%s\".\n", option); @@ -619,7 +626,7 @@ void HandlePngToNtrPaletteCommand(char *inputPath, char *outputPath, int argc, c } ReadPngPalette(inputPath, &palette); - WriteNtrPalette(outputPath, &palette, ncpr, ir, bitdepth, !nopad, compNum, pcmp); + WriteNtrPalette(outputPath, &palette, ncpr, ir, bitdepth, !nopad, compNum, pcmp, inverted); } void HandleGbaToJascPaletteCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED) @@ -634,6 +641,7 @@ void HandleNtrToJascPaletteCommand(char *inputPath, char *outputPath, int argc, { struct Palette palette; int bitdepth = 0; + bool inverted = false; for (int i = 3; i < argc; i++) { @@ -652,13 +660,17 @@ void HandleNtrToJascPaletteCommand(char *inputPath, char *outputPath, int argc, if (bitdepth != 4 && bitdepth != 8) FATAL_ERROR("Bitdepth must be 4 or 8.\n"); } + else if (strcmp(option, "-invertsize") == 0) + { + inverted = true; + } else { FATAL_ERROR("Unrecognized option \"%s\".\n", option); } } - ReadNtrPalette(inputPath, &palette, bitdepth, 0); + ReadNtrPalette(inputPath, &palette, bitdepth, 0, inverted); WriteJascPalette(outputPath, &palette); } @@ -708,6 +720,7 @@ void HandleJascToNtrPaletteCommand(char *inputPath, char *outputPath, int argc, int bitdepth = 0; int compNum = 0; bool pcmp = false; + bool inverted = false; for (int i = 3; i < argc; i++) { @@ -768,6 +781,10 @@ void HandleJascToNtrPaletteCommand(char *inputPath, char *outputPath, int argc, { pcmp = true; } + else if (strcmp(option, "-invertsize") == 0) + { + inverted = true; + } else { FATAL_ERROR("Unrecognized option \"%s\".\n", option); @@ -781,7 +798,7 @@ void HandleJascToNtrPaletteCommand(char *inputPath, char *outputPath, int argc, if (numColors != 0) palette.numColors = numColors; - WriteNtrPalette(outputPath, &palette, ncpr, ir, bitdepth, !nopad, compNum, pcmp); + WriteNtrPalette(outputPath, &palette, ncpr, ir, bitdepth, !nopad, compNum, pcmp, inverted); } void HandleJsonToNtrCellCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED) @@ -955,6 +972,7 @@ void HandleLZCompressCommand(char *inputPath, char *outputPath, int argc, char * { int overflowSize = 0; int minDistance = 2; // default, for compatibility with LZ77UnCompVram() + bool forwardIteration = true; for (int i = 3; i < argc; i++) { @@ -986,6 +1004,10 @@ void HandleLZCompressCommand(char *inputPath, char *outputPath, int argc, char * if (minDistance < 1) FATAL_ERROR("LZ min search distance must be positive.\n"); } + else if (strcmp(option, "-reverse") == 0) + { + forwardIteration = false; + } else { FATAL_ERROR("Unrecognized option \"%s\".\n", option); @@ -1002,7 +1024,7 @@ void HandleLZCompressCommand(char *inputPath, char *outputPath, int argc, char * unsigned char *buffer = ReadWholeFileZeroPadded(inputPath, &fileSize, overflowSize); int compressedSize; - unsigned char *compressedData = LZCompress(buffer, fileSize + overflowSize, &compressedSize, minDistance); + unsigned char *compressedData = LZCompress(buffer, fileSize + overflowSize, &compressedSize, minDistance, forwardIteration); compressedData[1] = (unsigned char)fileSize; compressedData[2] = (unsigned char)(fileSize >> 8); @@ -1115,6 +1137,75 @@ void HandleHuffDecompressCommand(char *inputPath, char *outputPath, int argc UNU free(uncompressedData); } +void HandleNtrFontToPngCommand(char *inputPath, char *outputPath, int argc, char **argv) +{ + struct NtrFontOptions options; + options.metadataFilePath = NULL; + options.useSubscreenPalette = false; + + for (int i = 3; i < argc; i++) + { + char *option = argv[i]; + + if (strcmp(option, "-metadata") == 0) + { + if (i + 1 >= argc) + FATAL_ERROR("No file path following \"-metadata\".\n"); + + options.metadataFilePath = argv[++i]; + } + else if (strcmp(option, "-subscreen") == 0) + { + options.useSubscreenPalette = true; + } + } + + if (options.metadataFilePath == NULL) + FATAL_ERROR("No file path given for \"-metadata\".\n"); + + struct Image image; + struct NtrFontMetadata metadata; + ReadNtrFont(inputPath, &image, &metadata, options.useSubscreenPalette); + WritePng(outputPath, &image); + + char *metadataJson = GetNtrFontMetadataJson(&metadata); + WriteWholeStringToFile(options.metadataFilePath, metadataJson); + + free(metadata.glyphWidthTable); + FreeImage(&image); +} + +void HandlePngToNtrFontCommand(char *inputPath, char *outputPath, int argc, char **argv) +{ + struct NtrFontOptions options; + options.metadataFilePath = NULL; + + for (int i = 3; i < argc; i++) + { + char *option = argv[i]; + + if (strcmp(option, "-metadata") == 0) + { + if (i + 1 >= argc) + FATAL_ERROR("No file path following \"-metadata\".\n"); + + options.metadataFilePath = argv[++i]; + } + } + + if (options.metadataFilePath == NULL) + FATAL_ERROR("No file path given for \"-metadata\".\n"); + + struct NtrFontMetadata *metadata = ParseNtrFontMetadataJson(options.metadataFilePath); + struct Image image = { .bitDepth = 2 }; + + ReadPng(inputPath, &image); + WriteNtrFont(outputPath, &image, metadata); + + FreeNtrFontMetadata(metadata); + FreeImage(&image); +} + int main(int argc, char **argv) { if (argc < 3) @@ -1159,6 +1250,8 @@ int main(int argc, char **argv) { "lz", NULL, HandleLZDecompressCommand }, { NULL, "rl", HandleRLCompressCommand }, { "rl", NULL, HandleRLDecompressCommand }, + { "NFGR", "png", HandleNtrFontToPngCommand }, + { "png", "NFGR", HandlePngToNtrFontCommand }, { NULL, NULL, NULL } }; diff --git a/tools/nitrogfx/options.h b/tools/nitrogfx/options.h index 4304f1eca..ed5dab6e2 100644 --- a/tools/nitrogfx/options.h +++ b/tools/nitrogfx/options.h @@ -51,6 +51,11 @@ struct NtrToPngOptions { bool handleEmpty; }; +struct CellVramTransferData { + int sourceDataOffset; + int size; +}; + struct Attr0 { int YCoordinate; bool Rotation; @@ -100,9 +105,12 @@ struct Cell { struct JsonToCellOptions { bool labelEnabled; bool extended; + bool vramTransferEnabled; int mappingType; int cellCount; struct Cell **cells; + int vramTransferMaxSize; + struct CellVramTransferData **transferData; char **labels; int labelCount; }; @@ -147,6 +155,7 @@ struct AnimationDataT { struct AnimationResults { short resultType; + bool padded; union { short index; struct AnimationDataSRT dataSrt; @@ -166,4 +175,21 @@ struct JsonToAnimationOptions { short resultCount; }; +struct NtrFontOptions { + char *metadataFilePath; + bool useSubscreenPalette; +}; + +struct NtrFontMetadata { + uint32_t size; + uint32_t widthTableOffset; + uint32_t numGlyphs; + uint8_t maxWidth; + uint8_t maxHeight; + uint8_t glyphWidth; + uint8_t glyphHeight; + + uint8_t *glyphWidthTable; +}; + #endif // OPTIONS_H diff --git a/tools/nitrogfx/util.c b/tools/nitrogfx/util.c index 65df5e229..4d668dc7a 100644 --- a/tools/nitrogfx/util.c +++ b/tools/nitrogfx/util.c @@ -12,129 +12,129 @@ bool ParseNumber(char *s, char **end, int radix, int *intValue) { - char *localEnd; + char *localEnd; - if (end == NULL) - end = &localEnd; + if (end == NULL) + end = &localEnd; - errno = 0; + errno = 0; - const long longValue = strtol(s, end, radix); + const long longValue = strtol(s, end, radix); - if (*end == s) - return false; // not a number + if (*end == s) + return false; // not a number - if ((longValue == LONG_MIN || longValue == LONG_MAX) && errno == ERANGE) - return false; + if ((longValue == LONG_MIN || longValue == LONG_MAX) && errno == ERANGE) + return false; - if (longValue > INT_MAX) - return false; + if (longValue > INT_MAX) + return false; - if (longValue < INT_MIN) - return false; + if (longValue < INT_MIN) + return false; - *intValue = (int)longValue; + *intValue = (int)longValue; - return true; + return true; } char *GetFileExtension(char *path) { - char *extension = path; + char *extension = path; - while (*extension != 0) - extension++; + while (*extension != 0) + extension++; - while (extension > path && *extension != '.') - extension--; + while (extension > path && *extension != '.') + extension--; - if (extension == path) - return NULL; + if (extension == path) + return NULL; - extension++; + extension++; - if (*extension == 0) - return NULL; + if (*extension == 0) + return NULL; - return extension; + return extension; } unsigned char *ReadWholeFile(char *path, int *size) { - FILE *fp = fopen(path, "rb"); + FILE *fp = fopen(path, "rb"); - if (fp == NULL) - FATAL_ERROR("Failed to open \"%s\" for reading.\n", path); + if (fp == NULL) + FATAL_ERROR("Failed to open \"%s\" for reading.\n", path); - fseek(fp, 0, SEEK_END); + fseek(fp, 0, SEEK_END); - *size = ftell(fp); + *size = ftell(fp); - unsigned char *buffer = malloc(*size); + unsigned char *buffer = malloc(*size); - if (buffer == NULL) - FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path); + if (buffer == NULL) + FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path); - rewind(fp); + rewind(fp); - if (fread(buffer, *size, 1, fp) != 1) - FATAL_ERROR("Failed to read \"%s\".\n", path); + if (fread(buffer, *size, 1, fp) != 1) + FATAL_ERROR("Failed to read \"%s\".\n", path); - fclose(fp); + fclose(fp); - return buffer; + return buffer; } unsigned char *ReadWholeFileZeroPadded(char *path, int *size, int padAmount) { - FILE *fp = fopen(path, "rb"); + FILE *fp = fopen(path, "rb"); - if (fp == NULL) - FATAL_ERROR("Failed to open \"%s\" for reading.\n", path); + if (fp == NULL) + FATAL_ERROR("Failed to open \"%s\" for reading.\n", path); - fseek(fp, 0, SEEK_END); + fseek(fp, 0, SEEK_END); - *size = ftell(fp); + *size = ftell(fp); - unsigned char *buffer = calloc(*size + padAmount, 1); + unsigned char *buffer = calloc(*size + padAmount, 1); - if (buffer == NULL) - FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path); + if (buffer == NULL) + FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path); - rewind(fp); + rewind(fp); - if (fread(buffer, *size, 1, fp) != 1) - FATAL_ERROR("Failed to read \"%s\".\n", path); + if (fread(buffer, *size, 1, fp) != 1) + FATAL_ERROR("Failed to read \"%s\".\n", path); - fclose(fp); + fclose(fp); - return buffer; + return buffer; } void WriteWholeStringToFile(char *path, char *string) { - FILE *fp = fopen(path, "wb"); + FILE *fp = fopen(path, "wb"); - if (fp == NULL) - FATAL_ERROR("Failed to open \"%s\" for writing.\n", path); + if (fp == NULL) + FATAL_ERROR("Failed to open \"%s\" for writing.\n", path); - if (fputs(string, fp) == EOF) - FATAL_ERROR("Failed to write to \"%s\".\n", path); + if (fputs(string, fp) == EOF) + FATAL_ERROR("Failed to write to \"%s\".\n", path); - fclose(fp); + fclose(fp); } void WriteWholeFile(char *path, void *buffer, int bufferSize) { - FILE *fp = fopen(path, "wb"); + FILE *fp = fopen(path, "wb"); - if (fp == NULL) - FATAL_ERROR("Failed to open \"%s\" for writing.\n", path); + if (fp == NULL) + FATAL_ERROR("Failed to open \"%s\" for writing.\n", path); - if (fwrite(buffer, bufferSize, 1, fp) != 1) - FATAL_ERROR("Failed to write to \"%s\".\n", path); + if (fwrite(buffer, bufferSize, 1, fp) != 1) + FATAL_ERROR("Failed to write to \"%s\".\n", path); - fclose(fp); + fclose(fp); } void WriteGenericNtrHeader(FILE* fp, const char* magicNumber, uint32_t size, bool byteorder, bool version101, uint16_t sectionCount)