From 0a893d4d3e6dcf00d720603b1916e0f46d4bc58a Mon Sep 17 00:00:00 2001 From: Rachel Date: Sun, 13 Oct 2024 15:47:33 -0700 Subject: [PATCH] Add support for font files used by Gen4 Pokemon --- font.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ font.h | 4 ++ json.c | 76 ++++++++++++++++++++++++ json.h | 2 + main.c | 71 +++++++++++++++++++++++ options.h | 17 ++++++ 6 files changed, 341 insertions(+) diff --git a/font.c b/font.c index 0dd6fbc..c70eb2f 100644 --- a/font.c +++ b/font.c @@ -4,9 +4,11 @@ #include #include #include +#include #include "global.h" #include "font.h" #include "gfx.h" +#include "options.h" #include "util.h" unsigned char gFontPalette[][3] = { @@ -16,6 +18,14 @@ unsigned char gFontPalette[][3] = { {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; @@ -158,6 +168,54 @@ static void ConvertToFullwidthJapaneseFont(unsigned char *src, unsigned char *de } } +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; @@ -173,6 +231,21 @@ static void SetFontPalette(struct Image *image) 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; @@ -324,3 +397,101 @@ void WriteFullwidthJapaneseFont(char *path, struct Image *image) 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); + + 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]; + + printf("header size: %lu\n", metadata->size); + printf("width table offset: %lu\n", metadata->widthTableOffset); + printf("num glyphs: %lu\n", metadata->numGlyphs); + printf("max width: %hhu\n", metadata->maxWidth); + printf("max height: %hhu\n", metadata->maxHeight); + printf("glyph width: %hhu\n", metadata->glyphWidth); + printf("glyph height: %hhu\n", metadata->glyphHeight); + + int numRows = (metadata->numGlyphs + 15) / 16; // Round up to next multiple of 16. + + metadata->glyphWidthTable = malloc(metadata->numGlyphs); + memcpy(metadata->glyphWidthTable, buffer + metadata->widthTableOffset, metadata->numGlyphs); + + 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"); + + 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/font.h b/font.h index 45086d0..70dca08 100644 --- a/font.h +++ b/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/json.c b/json.c index 12bc8a9..0edf85a 100644 --- a/json.c +++ b/json.c @@ -638,3 +638,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/json.h b/json.h index eb7e2ad..8bdf307 100644 --- a/json.h +++ b/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/main.c b/main.c index 0741367..c6872f8 100644 --- a/main.c +++ b/main.c @@ -1115,6 +1115,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 +1228,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/options.h b/options.h index 4304f1e..1276430 100644 --- a/options.h +++ b/options.h @@ -166,4 +166,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