diff --git a/tools/nitrogfx/LICENSE b/tools/nitrogfx/LICENSE index be4a59938..828b9152d 100644 --- a/tools/nitrogfx/LICENSE +++ b/tools/nitrogfx/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015 YamaArashi, 2021-2024 red031000 +Copyright (c) 2015 YamaArashi, 2021-2025 red031000 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/tools/nitrogfx/cJSON.c b/tools/nitrogfx/cJSON.c index d7c72363d..6e4fb0dd3 100644 --- a/tools/nitrogfx/cJSON.c +++ b/tools/nitrogfx/cJSON.c @@ -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 != 18) +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 19) #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. #endif @@ -308,9 +308,11 @@ static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_bu { double number = 0; unsigned char *after_end = NULL; - unsigned char number_c_string[64]; + unsigned char *number_c_string; unsigned char decimal_point = get_decimal_point(); size_t i = 0; + size_t number_string_length = 0; + cJSON_bool has_decimal_point = false; if ((input_buffer == NULL) || (input_buffer->content == NULL)) { @@ -320,7 +322,7 @@ static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_bu /* copy the number into a temporary buffer and replace '.' with the decimal point * of the current locale (for strtod) * This also takes care of '\0' not necessarily being available for marking the end of the input */ - for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + for (i = 0; can_access_at_index(input_buffer, i); i++) { switch (buffer_at_offset(input_buffer)[i]) { @@ -338,11 +340,12 @@ static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_bu case '-': case 'e': case 'E': - number_c_string[i] = buffer_at_offset(input_buffer)[i]; + number_string_length++; break; case '.': - number_c_string[i] = decimal_point; + number_string_length++; + has_decimal_point = true; break; default: @@ -350,11 +353,33 @@ static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_bu } } loop_end: - number_c_string[i] = '\0'; + /* malloc for temporary buffer, add 1 for '\0' */ + number_c_string = (unsigned char *) input_buffer->hooks.allocate(number_string_length + 1); + if (number_c_string == NULL) + { + return false; /* allocation failure */ + } + + memcpy(number_c_string, buffer_at_offset(input_buffer), number_string_length); + number_c_string[number_string_length] = '\0'; + + if (has_decimal_point) + { + for (i = 0; i < number_string_length; i++) + { + if (number_c_string[i] == '.') + { + /* replace '.' with the decimal point of the current locale (for strtod) */ + number_c_string[i] = decimal_point; + } + } + } number = strtod((const char*)number_c_string, (char**)&after_end); if (number_c_string == after_end) { + /* free the temporary buffer */ + input_buffer->hooks.deallocate(number_c_string); return false; /* parse_error */ } @@ -377,6 +402,8 @@ loop_end: item->type = cJSON_Number; input_buffer->offset += (size_t)(after_end - number_c_string); + /* free the temporary buffer */ + input_buffer->hooks.deallocate(number_c_string); return true; } diff --git a/tools/nitrogfx/cJSON.h b/tools/nitrogfx/cJSON.h index 37520bbcf..cab5feb42 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 18 +#define CJSON_VERSION_PATCH 19 #include diff --git a/tools/nitrogfx/gfx.c b/tools/nitrogfx/gfx.c index ffbece9ad..0e620d3c7 100644 --- a/tools/nitrogfx/gfx.c +++ b/tools/nitrogfx/gfx.c @@ -1,4 +1,4 @@ -// Copyright (c) 2015 YamaArashi, 2021-2024 red031000 +// Copyright (c) 2015 YamaArashi, 2021-2025 red031000 #include #include @@ -7,6 +7,7 @@ #include #include "global.h" #include "gfx.h" +#include "json.h" #include "util.h" static unsigned int FindNitroDataBlock(const unsigned char *data, const char *ident, unsigned int fileSize, unsigned int *blockSize_out) @@ -113,33 +114,108 @@ static void ConvertFromTiles4Bpp(unsigned char *src, unsigned char *dest, int nu } } -static uint32_t ConvertFromScanned4Bpp(unsigned char *src, unsigned char *dest, int fileSize, bool invertColours, bool scanFrontToBack) +static void Convert8BppFrom4BppTiles(unsigned char *src, unsigned char *dest, int numTiles, int chunksWide, int colsPerChunk, int rowsPerChunk, bool invertColors, int palIndex) { - uint32_t encValue = 0; - if (scanFrontToBack) { - encValue = (src[1] << 8) | src[0]; - for (int i = 0; i < fileSize; i += 2) - { - uint16_t val = src[i] | (src[i + 1] << 8); - val ^= (encValue & 0xFFFF); - src[i] = val; - src[i + 1] = val >> 8; - encValue = encValue * 1103515245; - encValue = encValue + 24691; - } - } else { - encValue = (src[fileSize - 1] << 8) | src[fileSize - 2]; - for (int i = fileSize; i > 0; i -= 2) - { - uint16_t val = (src[i - 1] << 8) | src[i - 2]; - val ^= (encValue & 0xFFFF); - src[i - 1] = (val >> 8); - src[i - 2] = val; - encValue = encValue * 1103515245; - encValue = encValue + 24691; + int tilesSoFar = 0; + int rowsSoFar = 0; + int chunkStartX = 0; + int chunkStartY = 0; + int pitch = (chunksWide * colsPerChunk) * 8; + + for (int i = 0; i < numTiles; i++) { + for (int j = 0; j < 8; j++) { + int idxComponentY = (chunkStartY * rowsPerChunk + rowsSoFar) * 8 + j; + + for (int k = 0; k < 8; k++) { + int idxComponentX = (chunkStartX * colsPerChunk + tilesSoFar) * 8 + k; + unsigned char srcPixelPair = *src++; + unsigned char leftPixel = srcPixelPair & 0xF; + unsigned char rightPixel = srcPixelPair >> 4; + + if (invertColors) { + leftPixel = 15 - leftPixel; + rightPixel = 15 - rightPixel; + } + + dest[idxComponentY * pitch + idxComponentX] = ((palIndex - 1) << 4) | leftPixel; + dest[idxComponentY * pitch + idxComponentX + 1] = ((palIndex - 1) << 4) | rightPixel; + k++; + } } + + AdvanceTilePosition(&tilesSoFar, &rowsSoFar, &chunkStartX, &chunkStartY, chunksWide, colsPerChunk, rowsPerChunk); } - for (int i = 0; i < fileSize; i++) +} + +static void ConvertFromTiles4BppCell(unsigned char *src, unsigned char *dest, int oamWidth, int oamHeight, int imageWidth, int startX, int startY, bool hFlip, bool vFlip, bool hvFlip, bool toPNG) +{ + int tilesSoFar = 0; + int rowsSoFar = 0; + int chunkStartX = 0; + int chunkStartY = 0; + int pitch = imageWidth / 2; + + for (int i = 0; i < oamHeight * oamWidth; i++) { + for (int j = 0; j < 8; j++) { + int idxComponentY = (chunkStartY + rowsSoFar) * 8 + j + startY; + if (vFlip) + { + idxComponentY = (rowsSoFar + oamHeight - chunkStartY) * 8 + j + startY; + } + if (hvFlip) + { + idxComponentY += 8 - j * 2; + } + + for (int k = 0; k < 4; k++) { + int idxComponentX = (chunkStartX + tilesSoFar) * 4 + k + startX/2; + + if (hFlip) + { + idxComponentX = (tilesSoFar + oamWidth - chunkStartX) * 4 + - k + startX/2 - 1; + + unsigned char srcPixelPair = *src; + unsigned char leftPixel = srcPixelPair & 0xF; + unsigned char rightPixel = srcPixelPair >> 4; + + if (toPNG) + { + srcPixelPair = *src++; + leftPixel = srcPixelPair & 0xF; + rightPixel = srcPixelPair >> 4; + + dest[idxComponentY * pitch + idxComponentX] = (leftPixel << 4) | rightPixel; + } + else + { + srcPixelPair = src[idxComponentY * pitch + idxComponentX]; + leftPixel = srcPixelPair & 0xF; + rightPixel = srcPixelPair >> 4; + + *dest++ = (leftPixel << 4) | rightPixel; + } + } + else + { + if (toPNG) + { + dest[idxComponentY * pitch + idxComponentX] = *src++; + } + else + { + *dest++ = src[idxComponentY * pitch + idxComponentX]; + } + } + } + } + + AdvanceTilePosition(&tilesSoFar, &rowsSoFar, &chunkStartX, &chunkStartY, oamWidth, 1, 1); + } +} + +static void ConvertScanned4Bpp(unsigned char *src, unsigned char *dest, int charDataSize, bool invertColours) +{ + for (int i = 0; i < charDataSize; i++) { unsigned char srcPixelPair = src[i]; unsigned char leftPixel = srcPixelPair & 0xF; @@ -152,7 +228,6 @@ static uint32_t ConvertFromScanned4Bpp(unsigned char *src, unsigned char *dest, dest[i] = (leftPixel << 4) | rightPixel; } - return encValue; } static void ConvertFromTiles8Bpp(unsigned char *src, unsigned char *dest, int numTiles, int chunksWide, int colsPerChunk, int rowsPerChunk, bool invertColors) @@ -182,33 +257,51 @@ static void ConvertFromTiles8Bpp(unsigned char *src, unsigned char *dest, int nu } } -static uint32_t ConvertFromScanned8Bpp(unsigned char *src, unsigned char *dest, int fileSize, bool invertColours, bool scanFrontToBack) +static void ConvertFromTiles8BppCell(unsigned char *src, unsigned char *dest, int oamWidth, int oamHeight, int imageWidth, int startX, int startY, bool hFlip, bool vFlip, bool hvFlip, bool toPNG) { - uint32_t encValue = 0; - if (scanFrontToBack) { - encValue = (src[1] << 8) | src[0]; - for (int i = 0; i < fileSize; i += 2) - { - uint16_t val = src[i] | (src[i + 1] << 8); - val ^= (encValue & 0xFFFF); - src[i] = val; - src[i + 1] = val >> 8; - encValue = encValue * 1103515245; - encValue = encValue + 24691; - } - } else { - encValue = (src[fileSize - 1] << 8) | src[fileSize - 2]; - for (int i = fileSize; i > 0; i -= 2) - { - uint16_t val = (src[i - 1] << 8) | src[i - 2]; - val ^= (encValue & 0xFFFF); - src[i - 1] = (val >> 8); - src[i - 2] = val; - encValue = encValue * 1103515245; - encValue = encValue + 24691; + int tilesSoFar = 0; + int rowsSoFar = 0; + int chunkStartX = 0; + int chunkStartY = 0; + int pitch = imageWidth; + + for (int i = 0; i < oamHeight * oamWidth; i++) { + for (int j = 0; j < 8; j++) { + int idxComponentY = (chunkStartY + rowsSoFar) * 8 + j + startY; + if (vFlip) + { + idxComponentY = (rowsSoFar + oamHeight - chunkStartY) * 8 + j + startY; + } + if (hvFlip) + { + idxComponentY += 8 - j * 2; + } + + for (int k = 0; k < 8; k++) { + int idxComponentX = (chunkStartX + tilesSoFar) * 8 + k + startX; + if (hFlip) + { + idxComponentX = (tilesSoFar + oamWidth - chunkStartX) * 4 + - k + startX; + } + + if (toPNG) + { + dest[idxComponentY * pitch + idxComponentX] = *src++; + } + else + { + *dest++ = src[idxComponentY * pitch + idxComponentX]; + } + } } + + AdvanceTilePosition(&tilesSoFar, &rowsSoFar, &chunkStartX, &chunkStartY, oamWidth, 1, 1); } - for (int i = 0; i < fileSize; i++) +} + +static void ConvertScanned8Bpp(unsigned char *src, unsigned char *dest, int charDataSize, bool invertColours) +{ + for (int i = 0; i < charDataSize; i++) { unsigned char srcPixel = src[i]; @@ -218,7 +311,6 @@ static uint32_t ConvertFromScanned8Bpp(unsigned char *src, unsigned char *dest, dest[i] = srcPixel; } - return encValue; } static void ConvertToTiles1Bpp(unsigned char *src, unsigned char *dest, int numTiles, int chunksWide, int colsPerChunk, int rowsPerChunk, bool invertColors) @@ -278,42 +370,6 @@ static void ConvertToTiles4Bpp(unsigned char *src, unsigned char *dest, int numT } } -static void ConvertToScanned4Bpp(unsigned char *src, unsigned char *dest, int fileSize, bool invertColours, uint32_t encValue, uint32_t scanMode) -{ - for (int i = 0; i < fileSize; i++) - { - unsigned char srcPixelPair = src[i]; - unsigned char leftPixel = srcPixelPair & 0xF; - unsigned char rightPixel = srcPixelPair >> 4; - if (invertColours) { - leftPixel = 15 - leftPixel; - rightPixel = 15 - rightPixel; - } - dest[i] = (leftPixel << 4) | rightPixel; - } - - if (scanMode == 2) { // front to back - for (int i = fileSize - 1; i > 0; i -= 2) - { - uint16_t val = dest[i - 1] | (dest[i] << 8); - encValue = (encValue - 24691) * 4005161829; - val ^= (encValue & 0xFFFF); - dest[i] = (val >> 8); - dest[i - 1] = val; - } - } - else if (scanMode == 1) { - for (int i = 1; i < fileSize; i += 2) - { - uint16_t val = (dest[i] << 8) | dest[i - 1]; - encValue = (encValue - 24691) * 4005161829; - val ^= (encValue & 0xFFFF); - dest[i] = (val >> 8); - dest[i - 1] = val; - } - } -} - static void ConvertToTiles8Bpp(unsigned char *src, unsigned char *dest, int numTiles, int chunksWide, int colsPerChunk, int rowsPerChunk, bool invertColors) { int tilesSoFar = 0; @@ -341,6 +397,89 @@ static void ConvertToTiles8Bpp(unsigned char *src, unsigned char *dest, int numT } } +static uint32_t Decode(unsigned char *src, int charDataSize, uint32_t encodeMode) +{ + uint32_t encValue = 0; + if (encodeMode == 2) { // front to back + encValue = (src[1] << 8) | src[0]; + for (int i = 0; i < charDataSize; i += 2) + { + uint16_t val = src[i] | (src[i + 1] << 8); + val ^= (encValue & 0xFFFF); + src[i] = val; + src[i + 1] = val >> 8; + encValue = encValue * 1103515245; + encValue = encValue + 24691; + } + } else if (encodeMode == 1) { // back to front + encValue = (src[charDataSize - 1] << 8) | src[charDataSize - 2]; + for (int i = charDataSize; i > 0; i -= 2) + { + uint16_t val = (src[i - 1] << 8) | src[i - 2]; + val ^= (encValue & 0xFFFF); + src[i - 1] = (val >> 8); + src[i - 2] = val; + encValue = encValue * 1103515245; + encValue = encValue + 24691; + } + } + return encValue; +} + +static void Encode(unsigned char *dest, int charDataSize, uint32_t encValue, uint32_t encodeMode) +{ + if (encodeMode == 2) { // front to back + for (int i = charDataSize - 1; i > 0; i -= 2) + { + uint16_t val = dest[i - 1] | (dest[i] << 8); + encValue = (encValue - 24691) * 4005161829; + val ^= (encValue & 0xFFFF); + dest[i] = (val >> 8); + dest[i - 1] = val; + } + } + else if (encodeMode == 1) { // back to front + for (int i = 1; i < charDataSize; i += 2) + { + uint16_t val = (dest[i] << 8) | dest[i - 1]; + encValue = (encValue - 24691) * 4005161829; + val ^= (encValue & 0xFFFF); + dest[i] = (val >> 8); + dest[i - 1] = val; + } + } +} + +static void Convert8BppTo4BppTiles(unsigned char *src, unsigned char *dest, int numTiles, int chunksWide, int colsPerChunk, int rowsPerChunk, bool invertColors) +{ + int tilesSoFar = 0; + int rowsSoFar = 0; + int chunkStartX = 0; + int chunkStartY = 0; + int pitch = (chunksWide * colsPerChunk) * 8; + + for (int i = 0; i < numTiles; i++) { + for (int j = 0; j < 8; j++) { + int idxComponentY = (chunkStartY * rowsPerChunk + rowsSoFar) * 8 + j; + + for (int k = 0; k < 8; k += 2) { + int idxComponentX = (chunkStartX * colsPerChunk + tilesSoFar) * 8 + k; + unsigned char leftPixel = src[idxComponentY * pitch + idxComponentX] & 0xF; + unsigned char rightPixel = src[idxComponentY * pitch + idxComponentX + 1] & 0xF; + + if (invertColors) { + leftPixel = 15 - leftPixel; + rightPixel = 15 - rightPixel; + } + + *dest++ = (rightPixel << 4) | leftPixel; + } + } + + AdvanceTilePosition(&tilesSoFar, &rowsSoFar, &chunkStartX, &chunkStartY, chunksWide, colsPerChunk, rowsPerChunk); + } +} + void ReadImage(char *path, int tilesWide, int bitDepth, int colsPerChunk, int rowsPerChunk, struct Image *image, bool invertColors) { int tileSize = bitDepth * 8; // number of bytes per tile @@ -383,7 +522,7 @@ void ReadImage(char *path, int tilesWide, int bitDepth, int colsPerChunk, int ro free(buffer); } -uint32_t ReadNtrImage(char *path, int tilesWide, int bitDepth, int colsPerChunk, int rowsPerChunk, struct Image *image, bool invertColors, bool scanFrontToBack) +uint32_t ReadNtrImage(char *path, int tilesWide, int bitDepth, int colsPerChunk, int rowsPerChunk, struct Image *image, bool invertColors, uint32_t encodeMode, bool convertTo8Bpp, int palIndex, bool verbose) { int fileSize; unsigned char *buffer = ReadWholeFile(path, &fileSize); @@ -402,16 +541,53 @@ uint32_t ReadNtrImage(char *path, int tilesWide, int bitDepth, int colsPerChunk, bitDepth = bitDepth ? bitDepth : (charHeader[0xC] == 3 ? 4 : 8); - if (bitDepth == 4) - { - image->palette.numColors = 16; - } - unsigned char *imageData = charHeader + 0x20; bool scanned = charHeader[0x14]; + if (verbose) + { + if (!convertTo8Bpp) { + printf("-bitdepth %d ", bitDepth); + } else { + printf("-convertTo4Bpp "); + } + + if (buffer[0x6] == 1) { + printf("-version101 "); + } + + if (charHeader[0x8] == 0xFF && charHeader[0x9] == 0xFF && charHeader[0xA] == 0xFF && charHeader[0xB] == 0xFF) + { + printf("-clobbersize "); + } + + if (buffer[0xE] == 2) { + printf("-sopc "); + } + + if (charHeader[0x12]) { + printf("-mappingtype %d ", 1 << (5 + (charHeader[0x12] >> 4))); + } + + if (scanned) + { + printf("-scanned "); + } + + if (charHeader[0x15] == 1) { + printf("-vram "); + } + } + + if (bitDepth == 4 && (scanned || !convertTo8Bpp)) + { + image->palette.numColors = 16; + } + int tileSize = bitDepth * 8; // number of bytes per tile + if (bitDepth == 4 && convertTo8Bpp && !scanned) + tileSize *= 2; if (tilesWide == 0) { tilesWide = ReadS16(charHeader, 0xA); @@ -435,7 +611,7 @@ uint32_t ReadNtrImage(char *path, int tilesWide, int bitDepth, int colsPerChunk, image->width = tilesWide * 8; image->height = tilesTall * 8; - image->bitDepth = bitDepth; + image->bitDepth = !scanned && convertTo8Bpp ? 8 : bitDepth; image->pixels = calloc(tilesWide * tilesTall, tileSize); if (image->pixels == NULL) @@ -444,15 +620,20 @@ uint32_t ReadNtrImage(char *path, int tilesWide, int bitDepth, int colsPerChunk, int chunksWide = tilesWide / colsPerChunk; // how many chunks side-by-side are needed for the full width of the image uint32_t key = 0; + uint32_t charDataSize = ReadS32(charHeader, 0x4) - 0x20; // read explicitly to account for possible SOPC chunk + if (encodeMode) + { + key = Decode(imageData, charDataSize, encodeMode); + } if (scanned) { switch (bitDepth) { case 4: - key = ConvertFromScanned4Bpp(imageData, image->pixels, fileSize - 0x30, invertColors, scanFrontToBack); + ConvertScanned4Bpp(imageData, image->pixels, charDataSize, invertColors); break; case 8: - key = ConvertFromScanned8Bpp(imageData, image->pixels, fileSize - 0x30, invertColors, scanFrontToBack); + ConvertScanned8Bpp(imageData, image->pixels, charDataSize, invertColors); break; } } @@ -461,8 +642,16 @@ uint32_t ReadNtrImage(char *path, int tilesWide, int bitDepth, int colsPerChunk, switch (bitDepth) { case 4: - ConvertFromTiles4Bpp(imageData, image->pixels, numTiles, chunksWide, colsPerChunk, rowsPerChunk, - invertColors); + if (convertTo8Bpp) + { + Convert8BppFrom4BppTiles(imageData, image->pixels, numTiles, chunksWide, colsPerChunk, rowsPerChunk, + invertColors, palIndex); + } + else + { + ConvertFromTiles4Bpp(imageData, image->pixels, numTiles, chunksWide, colsPerChunk, rowsPerChunk, + invertColors); + } break; case 8: ConvertFromTiles8Bpp(imageData, image->pixels, numTiles, chunksWide, colsPerChunk, rowsPerChunk, @@ -475,6 +664,333 @@ uint32_t ReadNtrImage(char *path, int tilesWide, int bitDepth, int colsPerChunk, return key; } +// accounts for OAMs overlapping by a few pixels +static int SnapToTile(int val) +{ + int displacement = val % 8; + if (displacement < 4) + { + val -= displacement; + } + else + { + val += 8 - displacement; + } + return val; +} + +struct Dimensions { + int width; + int height; +}; + +static struct Dimensions CalculateOAMDimensions(struct OAM *oam) +{ + struct Dimensions oamdim = { + .width = 0, + .height = 0, + }; + + int oamSize = oam->attr1.Size; + if (oamSize > 3) + { + FATAL_ERROR("oamSize greater than expected\n"); + } + switch (oam->attr0.Shape) + { + case 0: + oamdim.height = 1 << oamSize; + oamdim.width = oamdim.height; + break; + case 1: + switch (oamSize) + { + case 0: + oamdim.height = 1; + oamdim.width = 2; + break; + case 1: + oamdim.height = 1; + oamdim.width = 4; + break; + case 2: + oamdim.height = 2; + oamdim.width = 4; + break; + case 3: + oamdim.height = 4; + oamdim.width = 8; + break; + } + break; + case 2: + switch (oamSize) + { + case 0: + oamdim.height = 2; + oamdim.width = 1; + break; + case 1: + oamdim.height = 4; + oamdim.width = 1; + break; + case 2: + oamdim.height = 4; + oamdim.width = 2; + break; + case 3: + oamdim.height = 8; + oamdim.width = 4; + break; + } + break; + } + + return oamdim; +} + +struct CellInfo { + int height; + int minX; + int minY; +}; + +void ApplyCellsToImage(char *cellFilePath, struct Image *image, bool toPNG, bool snap) +{ + char *cellFileExtension = GetFileExtension(cellFilePath); + if (cellFileExtension == NULL) + { + FATAL_ERROR("NULL cell file path\n"); + } + struct JsonToCellOptions *options; + + if (strcmp(cellFileExtension, "NCER") == 0) + { + options = malloc(sizeof(struct JsonToCellOptions)); + ReadNtrCell(cellFilePath, options); + } + else + { + if (strcmp(cellFileExtension, "json") == 0) + { + options = ParseNCERJson(cellFilePath); + } + else + { + FATAL_ERROR("Incompatible cell file type\n"); + } + } + + int outputHeight = -1; + int outputWidth = 0; + int numTiles = 0; + struct CellInfo *cellInfo = malloc(sizeof(struct CellInfo) * options->cellCount); + + for (int i = 0; i < options->cellCount; i++) + { + if (options->cells[i]->oamCount == 0) + { + continue; + } + int cellHeight = 0; + int cellWidth = 0; + if (options->cells[i]->attributes.boundingRect) + { + cellHeight = options->cells[i]->maxY - options->cells[i]->minY; + cellWidth = options->cells[i]->maxX - options->cells[i]->minX; + if (snap) + { + cellHeight = SnapToTile(cellHeight); + cellWidth = SnapToTile(cellWidth); + } + cellInfo[i].minX = options->cells[i]->minX; + cellInfo[i].minY = options->cells[i]->minY; + } + else + { + int minX = 0; + int minY = 0; + int maxX = 0; + int maxY = 0; + for (int j = 0; j < options->cells[i]->oamCount; j++) + { + struct Dimensions oamdim = CalculateOAMDimensions(&options->cells[i]->oam[j]); + int xCoord = options->cells[i]->oam[j].attr1.XCoordinate; + if (xCoord & (1 << 8)) + { + xCoord |= ~0x1FF; + } + int yCoord = options->cells[i]->oam[j].attr0.YCoordinate; + if (yCoord & (1 << 7)) + { + yCoord |= ~0xFF; + } + if (xCoord < minX || j == 0) + { + minX = xCoord; + } + if (yCoord < minY || j == 0) + { + minY = yCoord; + } + if (xCoord + (oamdim.width * 8) > maxX || j == 0) + { + maxX = xCoord + (oamdim.width * 8); + } + if (yCoord + (oamdim.height * 8) > maxY || j == 0) + { + maxY = yCoord + (oamdim.height * 8); + } + } + cellWidth = maxX - minX; + cellHeight = maxY - minY; + cellInfo[i].minX = minX; + cellInfo[i].minY = minY; + } + + outputHeight += cellHeight + 1; + if (outputWidth < cellWidth) + { + outputWidth = cellWidth; + } + cellInfo[i].height = cellHeight; + } + + if (outputHeight < 1 || outputWidth == 0) + { + FATAL_ERROR("No cells. Incompatible NCER\n"); + } + unsigned char *newPixels = malloc(outputHeight * outputWidth); + memset(newPixels, 255, outputHeight * outputWidth); + + int scanHeight = -1; + int tileMask[outputHeight * outputWidth]; // check for unused (starting) tiles + memset(tileMask, 0, outputHeight * outputWidth * sizeof(int)); + for (int i = 0; i < options->cellCount; i++) + { + if (options->cells[i]->oamCount == 0) + { + continue; + } + scanHeight++; + int cellHeight = cellInfo[i].height; + if (snap) + { + cellHeight = SnapToTile(cellHeight); + } + int uniqueOAMs = options->cells[i]->oamCount; + + for (int j = 0; j < options->cells[i]->oamCount; j++) + { + struct Dimensions oamdim = CalculateOAMDimensions(&options->cells[i]->oam[j]); + + int x = options->cells[i]->oam[j].attr1.XCoordinate; + if (x & (1 << 8)) + { + x |= ~0x1FF; + } + int y = options->cells[i]->oam[j].attr0.YCoordinate; + if (y & (1 << 7)) + { + y |= ~0xFF; + } + x -= cellInfo[i].minX; + y -= cellInfo[i].minY; + + if (snap) + { + x = SnapToTile(x); + y = SnapToTile(y); + } + + int pixelOffset = 0; + switch (options->mappingType) + { + case 0: + pixelOffset = options->cells[i]->oam[j].attr2.CharName * 32; + break; + case 1: + pixelOffset = options->cells[i]->oam[j].attr2.CharName * 64; + break; + case 2: + pixelOffset = options->cells[i]->oam[j].attr2.CharName * 128; + break; + case 3: + pixelOffset = options->cells[i]->oam[j].attr2.CharName * 256; + break; + } + + if (options->vramTransferEnabled) + { + pixelOffset += options->transferData[i]->sourceDataOffset; + } + if (tileMask[pixelOffset]) + { + uniqueOAMs--; + continue; + } + tileMask[pixelOffset] = 1; + numTiles += oamdim.height * oamdim.width; + + bool rotationScaling = options->cells[i]->oam[j].attr1.RotationScaling; + bool hFlip = options->cells[i]->attributes.hFlip && rotationScaling; + bool vFlip = options->cells[i]->attributes.vFlip && rotationScaling; + bool hvFlip = options->cells[i]->attributes.hvFlip && rotationScaling; + + switch (image->bitDepth) + { + case 4: + if (toPNG) + { + ConvertFromTiles4BppCell(image->pixels + pixelOffset, newPixels, oamdim.width, oamdim.height, outputWidth, x, y + scanHeight, hFlip, vFlip, hvFlip, true); + } + else + { + ConvertFromTiles4BppCell(image->pixels, newPixels + pixelOffset, oamdim.width, oamdim.height, outputWidth, x, y + scanHeight, hFlip, vFlip, hvFlip, false); + } + break; + case 8: + pixelOffset *= 2; + if (toPNG) + { + ConvertFromTiles8BppCell(image->pixels + pixelOffset, newPixels, oamdim.width, oamdim.height, outputWidth, x, y + scanHeight, hFlip, vFlip, hvFlip, true); + } + else + { + ConvertFromTiles8BppCell(image->pixels, newPixels + pixelOffset, oamdim.width, oamdim.height, outputWidth, x, y + scanHeight, hFlip, vFlip, hvFlip, false); + } + break; + } + } + + if (uniqueOAMs == 0) + { + outputHeight -= cellHeight + 1; + scanHeight--; + } + else + { + scanHeight += cellHeight; + } + } + + free(image->pixels); + free(cellInfo); + if (toPNG) + { + image->pixels = newPixels; + image->height = outputHeight; + image->width = outputWidth; + } + else + { + image->pixels = newPixels; + image->height = numTiles * 8; + image->width = 8; + } + FreeNCERCell(options); +} + void WriteImage(char *path, int numTiles, int bitDepth, int colsPerChunk, int rowsPerChunk, struct Image *image, bool invertColors) { int tileSize = bitDepth * 8; // number of bytes per tile @@ -527,8 +1043,8 @@ void WriteImage(char *path, int numTiles, int bitDepth, int colsPerChunk, int ro } void WriteNtrImage(char *path, int numTiles, int bitDepth, int colsPerChunk, int rowsPerChunk, struct Image *image, - bool invertColors, bool clobberSize, bool byteOrder, bool version101, bool sopc, bool vram, uint32_t scanMode, - uint32_t mappingType, uint32_t key, bool wrongSize) + bool invertColors, bool clobberSize, bool byteOrder, bool version101, bool sopc, bool vram, bool scan, + uint32_t encodeMode, uint32_t mappingType, uint32_t key, bool wrongSize, bool convertTo4Bpp) { FILE *fp = fopen(path, "wb"); @@ -536,6 +1052,8 @@ void WriteNtrImage(char *path, int numTiles, int bitDepth, int colsPerChunk, int FATAL_ERROR("Failed to open \"%s\" for writing.\n", path); int tileSize = bitDepth * 8; // number of bytes per tile + if (bitDepth == 8 && convertTo4Bpp && !scan) + tileSize /= 2; if (image->width % 8 != 0) FATAL_ERROR("The width in pixels (%d) isn't a multiple of 8.\n", image->width); @@ -567,15 +1085,15 @@ void WriteNtrImage(char *path, int numTiles, int bitDepth, int colsPerChunk, int int chunksWide = tilesWide / colsPerChunk; // how many chunks side-by-side are needed for the full width of the image - if (scanMode) + if (scan) { switch (bitDepth) { case 4: - ConvertToScanned4Bpp(image->pixels, pixelBuffer, bufferSize, invertColors, key, scanMode); + ConvertScanned4Bpp(image->pixels, pixelBuffer, bufferSize, invertColors); break; case 8: - FATAL_ERROR("8Bpp not supported yet.\n"); + ConvertScanned8Bpp(image->pixels, pixelBuffer, bufferSize, invertColors); break; } } @@ -588,11 +1106,23 @@ void WriteNtrImage(char *path, int numTiles, int bitDepth, int colsPerChunk, int invertColors); break; case 8: - ConvertToTiles8Bpp(image->pixels, pixelBuffer, numTiles, chunksWide, colsPerChunk, rowsPerChunk, - invertColors); + if (convertTo4Bpp) + { + Convert8BppTo4BppTiles(image->pixels, pixelBuffer, numTiles, chunksWide, colsPerChunk, rowsPerChunk, + invertColors); + } + else + { + ConvertToTiles8Bpp(image->pixels, pixelBuffer, numTiles, chunksWide, colsPerChunk, rowsPerChunk, + invertColors); + } break; } } + if (encodeMode) + { + Encode(pixelBuffer, bufferSize, key, encodeMode); + } WriteGenericNtrHeader(fp, "RGCN", bufferSize + (sopc ? 0x30 : 0x20) + (wrongSize ? -8 : 0), byteOrder, version101, sopc ? 2 : 1); @@ -627,7 +1157,7 @@ void WriteNtrImage(char *path, int numTiles, int bitDepth, int colsPerChunk, int } } - charHeader[12] = bitDepth == 4 ? 3 : 4; + charHeader[12] = bitDepth == 4 || convertTo4Bpp ? 3 : 4; if (mappingType != 0) { uint32_t val = 0; @@ -652,7 +1182,7 @@ void WriteNtrImage(char *path, int numTiles, int bitDepth, int colsPerChunk, int charHeader[18] = val; } - if (scanMode) + if (scan) { charHeader[20] = 1; //implies BMP } @@ -714,7 +1244,7 @@ void ReadGbaPalette(char *path, struct Palette *palette) free(data); } -void ReadNtrPalette(char *path, struct Palette *palette, int bitdepth, int palIndex, bool inverted) +void ReadNtrPalette(char *path, struct Palette *palette, int bitdepth, int palIndex, bool inverted, bool convertTo8Bpp) { int fileSize; unsigned char *data = ReadWholeFile(path, &fileSize); @@ -743,7 +1273,7 @@ void ReadNtrPalette(char *path, struct Palette *palette, int bitdepth, int palIn if (palIndex == 0) { palette->numColors = paletteSize / 2; } else { - palette->numColors = bitdepth == 4 ? 16 : 256; //remove header and divide by 2 + palette->numColors = bitdepth == 4 && !convertTo8Bpp ? 16 : 256; //remove header and divide by 2 --palIndex; } @@ -753,7 +1283,7 @@ void ReadNtrPalette(char *path, struct Palette *palette, int bitdepth, int palIn { if (i < palette->numColors) { - uint16_t paletteEntry = (paletteData[(32 * palIndex) + i * 2 + 1] << 8) | paletteData[(32 * palIndex) + i * 2]; + uint16_t paletteEntry = (paletteData[(32 * (convertTo8Bpp ? 0 : palIndex)) + i * 2 + 1] << 8) | paletteData[(32 * (convertTo8Bpp ? 0 : palIndex)) + i * 2]; palette->colors[i].red = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_RED(paletteEntry)); palette->colors[i].green = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_GREEN(paletteEntry)); palette->colors[i].blue = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_BLUE(paletteEntry)); @@ -790,7 +1320,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, bool inverted) +void WriteNtrPalette(char *path, struct Palette *palette, bool ncpr, bool ir, int bitdepth, bool pad, int compNum, bool pcmp, int pcmpStartIndex, bool inverted, bool convertTo4Bpp) { FILE *fp = fopen(path, "wb"); @@ -836,7 +1366,7 @@ void WriteNtrPalette(char *path, struct Palette *palette, bool ncpr, bool ir, in bitdepth = bitdepth ? bitdepth : palette->bitDepth; //bit depth - palHeader[8] = bitdepth == 4 ? 0x03: 0x04; + palHeader[8] = bitdepth == 4 || convertTo4Bpp ? 0x03 : 0x04; if (compNum) { @@ -900,8 +1430,9 @@ void WriteNtrPalette(char *path, struct Palette *palette, bool ncpr, bool ir, in FATAL_ERROR("failed to alloc pcmp_data\n"); } for (int i = 0; i < pcmpColorNum; ++i) { - pcmp_data[i * 2] = i & 0xFF; - pcmp_data[i * 2 + 1] = (i >> 8) & 0xFF; + int index = i + pcmpStartIndex; + pcmp_data[i * 2] = index & 0xFF; + pcmp_data[i * 2 + 1] = (index >> 8) & 0xFF; } fwrite(pcmp_data, 1, pcmpColorNum * 2, fp); free(pcmp_data); @@ -916,12 +1447,9 @@ void ReadNtrCell_CEBK(unsigned char * restrict data, unsigned int blockOffset, u options->extended = data[blockOffset + 0xA] == 1; int vramTransferOffset = (data[blockOffset + 0x14] | data[blockOffset + 0x15] << 8); + unsigned int ucatOffset = (data[blockOffset + 0x1c] | data[blockOffset + 0x1d] << 8 | data[blockOffset + 0x1e] << 16 | data[blockOffset + 0x1f] << 24); 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->ucatEnabled = ucatOffset > 0; options->mappingType = data[blockOffset + 0x10]; @@ -1025,6 +1553,18 @@ void ReadNtrCell_CEBK(unsigned char * restrict data, unsigned int blockOffset, u offset += 8; } } + + if (options->ucatEnabled) + { + offset = blockOffset + 0x18 + ucatOffset + 0x04 * options->cellCount; + + options->ucatCellAttribtes = malloc(sizeof(uint32_t) * options->cellCount); + for (int i = 0; i < options->cellCount; i++) + { + options->ucatCellAttribtes[i] = data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24); + offset += 0x04; + } + } } void ReadNtrCell_LABL(unsigned char * restrict data, unsigned int blockOffset, unsigned int blockSize, struct JsonToCellOptions *options) @@ -1075,6 +1615,7 @@ void ReadNtrCell(char *path, struct JsonToCellOptions *options) offset = FindNitroDataBlock(data, "KBEC", fileSize, &blockSize); if (offset != -1u) { + options->dontPadKbec = blockSize % 4 != 0; ReadNtrCell_CEBK(data, offset, blockSize, options); } else { @@ -1106,14 +1647,24 @@ void WriteNtrCell(char *path, struct JsonToCellOptions *options) { kbecSize += 0x08 + (0x08 * options->cellCount); } + // if UCAT is enabled add size to KBEC + unsigned int ucatSize = 0; + if (options->ucatEnabled) + { + ucatSize = options->cellCount * 0x08 + 0x10; + kbecSize += ucatSize; + } // 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; + if (!options->dontPadKbec) + { + // KBEC size is padded to be 4-byte aligned + kbecSize = (kbecSize + 3) & ~3; + } unsigned int totalSize = (options->labelEnabled > 0 ? 0x34 : 0x20) + kbecSize; @@ -1145,11 +1696,21 @@ void WriteNtrCell(char *path, struct JsonToCellOptions *options) KBECHeader[16] = (options->mappingType & 0xFF); //not possible to be more than 8 bits, though 32 are allocated + // offset to UCAT data within KBEC section (offset from KBEC start + 0x1c) + if (options->ucatEnabled) + { + unsigned int ucatOffset = (kbecSize + 0x20) - ucatSize - 0x08; + KBECHeader[28] = ucatOffset & 0xFF; + KBECHeader[29] = (ucatOffset >> 8) & 0xFF; + KBECHeader[30] = (ucatOffset >> 16) & 0xFF; + KBECHeader[31] = (ucatOffset >> 24) & 0xFF; + } + // 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; + unsigned int vramTransferOffset = (kbecSize + 0x20) - vramTransferLength - ucatSize - 0x08; KBECHeader[20] = vramTransferOffset & 0xFF; KBECHeader[21] = (vramTransferOffset >> 8) & 0xFF; KBECHeader[22] = (vramTransferOffset >> 16) & 0xFF; @@ -1162,12 +1723,6 @@ void WriteNtrCell(char *path, struct JsonToCellOptions *options) memset(KBECContents, 0, kbecSize); - /*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"); - }*/ - int i; int totalOam = 0; for (i = 0; i < options->cellCount * iterNum; i += iterNum) @@ -1252,6 +1807,12 @@ void WriteNtrCell(char *path, struct JsonToCellOptions *options) } } + // word-aligned + if (offset % 4 > 0) + { + offset += 4 - (offset % 4); + } + // VRAM transfer data if (options->vramTransferEnabled) { @@ -1283,6 +1844,57 @@ void WriteNtrCell(char *path, struct JsonToCellOptions *options) } } + // UCAT data + if (options->ucatEnabled) + { + // UCAT magic + strcpy((char *) (KBECContents + offset), "TACU"); + offset += 0x04; + + // ucat size + KBECContents[offset] = ucatSize & 0xFF; + KBECContents[offset + 1] = (ucatSize >> 8) & 0xFF; + KBECContents[offset + 2] = (ucatSize >> 16) & 0xFF; + KBECContents[offset + 3] = (ucatSize >> 24) & 0xFF; + offset += 0x04; + + // num cells + KBECContents[offset] = options->cellCount & 0xFF; + KBECContents[offset + 1] = (options->cellCount >> 8) & 0xFF; + offset += 0x02; + + // num attributes per cell + KBECContents[offset] = 0x01; + offset += 0x02; + + // **attr + KBECContents[offset] = 0x08; + offset += 0x04; + + // *attr + unsigned int attributeAddress = options->cellCount * 0x04 + 0x08; + for (int i = 0; i < options->cellCount; i++) + { + KBECContents[offset] = attributeAddress & 0xFF; + KBECContents[offset + 1] = (attributeAddress >> 8) & 0xFF; + KBECContents[offset + 2] = (attributeAddress >> 16) & 0xFF; + KBECContents[offset + 3] = (attributeAddress >> 24) & 0xFF; + offset += 0x04; + attributeAddress += 0x04; + } + + // attr + for (int i = 0; i < options->cellCount; i++) + { + unsigned int ucatAttribute = options->ucatCellAttribtes[i]; + KBECContents[offset] = ucatAttribute & 0xFF; + KBECContents[offset + 1] = (ucatAttribute >> 8) & 0xFF; + KBECContents[offset + 2] = (ucatAttribute >> 16) & 0xFF; + KBECContents[offset + 3] = (ucatAttribute >> 24) & 0xFF; + offset += 0x04; + } + } + fwrite(KBECContents, 1, kbecSize, fp); free(KBECContents); @@ -1392,6 +2004,9 @@ void ReadNtrAnimation(char *path, struct JsonToAnimationOptions *options) options->sequenceCount = data[0x18] | (data[0x19] << 8); options->frameCount = data[0x1A] | (data[0x1B] << 8); + int uaatOffset = (data[0x2c] | data[0x2d] << 8 | data[0x2e] << 16 | data[0x2f] << 24); + options->uaatEnabled = uaatOffset > 0; + options->sequenceData = malloc(sizeof(struct SequenceData *) * options->sequenceCount); for (int i = 0; i < options->sequenceCount; i++) @@ -1462,6 +2077,25 @@ void ReadNtrAnimation(char *path, struct JsonToAnimationOptions *options) free(frameOffsets); + if (options->uaatEnabled) + { + offset = 0x28 + uaatOffset + 0x0c * options->sequenceCount + 0x04 * options->frameCount; // index of first attribute + + options->uaatData.sequenceAttributes = malloc(sizeof(uint32_t) * options->sequenceCount); + for (int i = 0; i < options->sequenceCount; i++) + { + options->uaatData.sequenceAttributes[i] = data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24); + offset += 0x04; + } + + options->uaatData.frameAttributes = malloc(sizeof(uint32_t) * options->frameCount); + for (int i = 0; i < options->frameCount; i++) + { + options->uaatData.frameAttributes[i] = data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24); + offset += 0x04; + } + } + offset = 0x18 + (data[0x24] | (data[0x25] << 8) | (data[0x26] << 16) | (data[0x27] << 24)); //start of animation results int k; @@ -1548,8 +2182,7 @@ void ReadNtrAnimation(char *path, struct JsonToAnimationOptions *options) } } - // add any missed padding from the final frame before processing labels - if (offset % 4 != 0) offset += 2; + offset = 0x10 + (data[0x14] | (data[0x15] << 8) | (data[0x26] << 16) | (data[0x17] << 24)); //start of label results if (options->labelEnabled) { @@ -1639,6 +2272,13 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) free(usedResults); + unsigned int uaatSize = 0; + if (options->uaatEnabled) + { + uaatSize = 0x10 + 0x10 * options->sequenceCount + 0x08 * options->frameCount; + totalSize += uaatSize; + } + unsigned int KNBASize = totalSize; if (options->labelEnabled) @@ -1683,10 +2323,21 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) KBNAHeader[22] = (resultsOffset >> 16) & 0xff; KBNAHeader[23] = resultsOffset >> 24; + unsigned int uaatOffset = 0; + if (options->uaatEnabled) + { + uaatOffset = KNBASize - uaatSize - 0x08; + KBNAHeader[28] = uaatOffset & 0xff; + KBNAHeader[29] = (uaatOffset >> 8) & 0xff; + KBNAHeader[30] = (uaatOffset >> 16) & 0xff; + KBNAHeader[31] = (uaatOffset >> 24) & 0xff; + } + fwrite(KBNAHeader, 1, 0x20, fp); int contentsSize = KNBASize - 0x20; unsigned char *KBNAContents = malloc(contentsSize); + memset(KBNAContents, 0, contentsSize); int i; int framePtrCounter = 0; @@ -1792,6 +2443,100 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) } } + // UAAT data + if (options->uaatEnabled) + { + int offset = uaatOffset - 0x18; + + // UAAT magic + strcpy((char *) (KBNAContents + offset), "TAAU"); + offset += 0x04; + + // uaat size + KBNAContents[offset] = uaatSize & 0xFF; + KBNAContents[offset + 1] = (uaatSize >> 8) & 0xFF; + KBNAContents[offset + 2] = (uaatSize >> 16) & 0xFF; + KBNAContents[offset + 3] = (uaatSize >> 24) & 0xFF; + offset += 0x04; + + // num sequences + KBNAContents[offset] = options->sequenceCount & 0xFF; + KBNAContents[offset + 1] = (options->sequenceCount >> 8) & 0xFF; + offset += 0x02; + + // num attributes per frame + KBNAContents[offset] = 0x01; + offset += 0x02; + + // fixed offset 0x08 + KBNAContents[offset] = 0x08; + offset += 0x04; + + unsigned int uaatSinglePointer = 0x08 + 0x0c * options->sequenceCount + 0x04 * options->frameCount; + unsigned int uaatDoublePointer = 0x08 + 0x0c * options->sequenceCount; + for (int i = 0; i < options->sequenceCount; i++) + { + // frame count in this sequence + KBNAContents[offset] = options->sequenceData[i]->frameCount & 0xFF; + KBNAContents[offset + 1] = (options->sequenceData[i]->frameCount >> 8) & 0xFF; + offset += 0x02; + + // 0xBEEF + KBNAContents[offset] = 0xEF; + KBNAContents[offset + 1] = 0xBE; + offset += 0x02; + + // sequence attributes * + KBNAContents[offset] = uaatSinglePointer & 0xFF; + KBNAContents[offset + 1] = (uaatSinglePointer >> 8) & 0xFF; + KBNAContents[offset + 2] = (uaatSinglePointer >> 16) & 0xFF; + KBNAContents[offset + 3] = (uaatSinglePointer >> 24) & 0xFF; + offset += 0x04; + uaatSinglePointer += 0x04; + + // frame attributes ** + KBNAContents[offset] = uaatDoublePointer & 0xFF; + KBNAContents[offset + 1] = (uaatDoublePointer >> 8) & 0xFF; + KBNAContents[offset + 2] = (uaatDoublePointer >> 16) & 0xFF; + KBNAContents[offset + 3] = (uaatDoublePointer >> 24) & 0xFF; + offset += 0x04; + uaatDoublePointer += options->sequenceData[i]->frameCount * 0x04; + } + + for (int i = 0; i < options->frameCount; i++) + { + // frame attributes * + KBNAContents[offset] = uaatSinglePointer & 0xFF; + KBNAContents[offset + 1] = (uaatSinglePointer >> 8) & 0xFF; + KBNAContents[offset + 2] = (uaatSinglePointer >> 16) & 0xFF; + KBNAContents[offset + 3] = (uaatSinglePointer >> 24) & 0xFF; + offset += 0x04; + uaatSinglePointer += 0x04; + } + + for (int i = 0; i < options->sequenceCount; i++) + { + // sequence attributes + unsigned int uaatSequenceAttribute = options->uaatData.sequenceAttributes[i]; + KBNAContents[offset] = uaatSequenceAttribute & 0xFF; + KBNAContents[offset + 1] = (uaatSequenceAttribute >> 8) & 0xFF; + KBNAContents[offset + 2] = (uaatSequenceAttribute >> 16) & 0xFF; + KBNAContents[offset + 3] = (uaatSequenceAttribute >> 24) & 0xFF; + offset += 0x04; + } + + for (int i = 0; i < options->frameCount; i++) + { + // frame attributes + unsigned int uaatFrameAttribute = options->uaatData.frameAttributes[i]; + KBNAContents[offset] = uaatFrameAttribute & 0xFF; + KBNAContents[offset + 1] = (uaatFrameAttribute >> 8) & 0xFF; + KBNAContents[offset + 2] = (uaatFrameAttribute >> 16) & 0xFF; + KBNAContents[offset + 3] = (uaatFrameAttribute >> 24) & 0xFF; + offset += 0x04; + } + } + fwrite(KBNAContents, 1, contentsSize, fp); free(KBNAContents); diff --git a/tools/nitrogfx/gfx.h b/tools/nitrogfx/gfx.h index 705b98ab3..5220fdd3e 100644 --- a/tools/nitrogfx/gfx.h +++ b/tools/nitrogfx/gfx.h @@ -1,4 +1,4 @@ -// Copyright (c) 2015 YamaArashi, 2021-2024 red031000 +// Copyright (c) 2015 YamaArashi, 2021-2025 red031000 #ifndef GFX_H #define GFX_H @@ -51,16 +51,17 @@ struct Image { }; void ReadImage(char *path, int tilesWide, int bitDepth, int colsPerChunk, int rowsPerChunk, struct Image *image, bool invertColors); -uint32_t ReadNtrImage(char *path, int tilesWide, int bitDepth, int colsPerChunk, int rowsPerChunk, struct Image *image, bool invertColors, bool scanFrontToBack); +uint32_t ReadNtrImage(char *path, int tilesWide, int bitDepth, int colsPerChunk, int rowsPerChunk, struct Image *image, bool invertColors, uint32_t encodeMode, bool convertTo8Bpp, int palIndex, bool verbose); +void ApplyCellsToImage(char *cellFilePath, struct Image *image, bool toPNG, bool snap); void WriteImage(char *path, int numTiles, int bitDepth, int colsPerChunk, int rowsPerChunk, struct Image *image, bool invertColors); void WriteNtrImage(char *path, int numTiles, int bitDepth, int colsPerChunk, int rowsPerChunk, struct Image *image, - bool invertColors, bool clobberSize, bool byteOrder, bool version101, bool sopc, bool vram, uint32_t scanMode, - uint32_t mappingType, uint32_t key, bool wrongSize); + bool invertColors, bool clobberSize, bool byteOrder, bool version101, bool sopc, bool vram, bool scan, + uint32_t encodeMode, uint32_t mappingType, uint32_t key, bool wrongSize, bool convertTo4Bpp); void FreeImage(struct Image *image); void ReadGbaPalette(char *path, struct Palette *palette); -void ReadNtrPalette(char *path, struct Palette *palette, int bitdepth, int palIndex, bool inverted); +void ReadNtrPalette(char *path, struct Palette *palette, int bitdepth, int palIndex, bool inverted, bool convertTo8Bpp); 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, bool inverted); +void WriteNtrPalette(char *path, struct Palette *palette, bool ncpr, bool ir, int bitdepth, bool pad, int compNum, bool pcmp, int pcmpStartIndex, bool inverted, bool convertTo4Bpp); 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 f0156700c..a8f822d26 100644 --- a/tools/nitrogfx/json.c +++ b/tools/nitrogfx/json.c @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2024 red031000 +// Copyright (c) 2021-2025 red031000 #include "global.h" #include "cJSON.h" @@ -47,13 +47,17 @@ struct JsonToCellOptions *ParseNCERJson(char *path) } cJSON *labelBool = cJSON_GetObjectItemCaseSensitive(json, "labelEnabled"); + cJSON *dontPadKbecBool = cJSON_GetObjectItemCaseSensitive(json, "dontPadKbec"); cJSON *vramTransferBool = cJSON_GetObjectItemCaseSensitive(json, "vramTransferEnabled"); + cJSON *ucatBool = cJSON_GetObjectItemCaseSensitive(json, "ucatEnabled"); cJSON *extended = cJSON_GetObjectItemCaseSensitive(json, "extended"); cJSON *cellCount = cJSON_GetObjectItemCaseSensitive(json, "cellCount"); cJSON *mappingType = cJSON_GetObjectItemCaseSensitive(json, "mappingType"); options->labelEnabled = GetBool(labelBool); + options->dontPadKbec = GetBool(dontPadKbecBool); options->vramTransferEnabled = GetBool(vramTransferBool); + options->ucatEnabled = GetBool(ucatBool); options->extended = GetBool(extended); options->cellCount = GetInt(cellCount); options->mappingType = GetInt(mappingType); @@ -103,6 +107,18 @@ struct JsonToCellOptions *ParseNCERJson(char *path) } } + if (options->ucatEnabled) + { + cJSON *ucatCells = cJSON_GetObjectItemCaseSensitive(json, "cellAttributes"); + + options->ucatCellAttribtes = malloc(sizeof(uint32_t) * options->cellCount); + + for (int i = 0; i < options->cellCount; i++) + { + options->ucatCellAttribtes[i] = GetInt(cJSON_GetArrayItem(ucatCells, i)); + } + } + for (int i = 0; i < options->cellCount; i++) { options->cells[i] = malloc(sizeof(struct Cell)); @@ -174,7 +190,12 @@ struct JsonToCellOptions *ParseNCERJson(char *path) cJSON *Colours = cJSON_GetObjectItemCaseSensitive(Attr0, "Colours"); cJSON *Shape = cJSON_GetObjectItemCaseSensitive(Attr0, "Shape"); - options->cells[i]->oam[j].attr0.YCoordinate = GetInt(YCoordinate); + int y = GetInt(YCoordinate); + if (y & (1 << 7)) + { + y &= 0xFF; + } + options->cells[i]->oam[j].attr0.YCoordinate = y; options->cells[i]->oam[j].attr0.Rotation = GetBool(Rotation); options->cells[i]->oam[j].attr0.SizeDisable = GetBool(SizeDisable); options->cells[i]->oam[j].attr0.Mode = GetInt(Mode); @@ -189,7 +210,12 @@ struct JsonToCellOptions *ParseNCERJson(char *path) cJSON *RotationScaling = cJSON_GetObjectItemCaseSensitive(Attr1, "RotationScaling"); cJSON *Size = cJSON_GetObjectItemCaseSensitive(Attr1, "Size"); - options->cells[i]->oam[j].attr1.XCoordinate = GetInt(XCoordinate); + int x = GetInt(XCoordinate); + if (x & (1 << 8)) + { + x &= 0x1FF; + } + options->cells[i]->oam[j].attr1.XCoordinate = x; options->cells[i]->oam[j].attr1.RotationScaling = GetInt(RotationScaling); options->cells[i]->oam[j].attr1.Size = GetInt(Size); @@ -220,8 +246,10 @@ char *GetNCERJson(struct JsonToCellOptions *options) cJSON *ncer = cJSON_CreateObject(); cJSON_AddBoolToObject(ncer, "labelEnabled", options->labelEnabled); + cJSON_AddBoolToObject(ncer, "dontPadKbec", options->dontPadKbec); cJSON_AddBoolToObject(ncer, "extended", options->extended); cJSON_AddBoolToObject(ncer, "vramTransferEnabled", options->vramTransferEnabled); + cJSON_AddBoolToObject(ncer, "ucatEnabled", options->ucatEnabled); cJSON_AddNumberToObject(ncer, "cellCount", options->cellCount); cJSON_AddNumberToObject(ncer, "mappingType", options->mappingType); @@ -257,7 +285,12 @@ char *GetNCERJson(struct JsonToCellOptions *options) cJSON *Attr0 = cJSON_AddObjectToObject(OAM, "Attr0"); - cJSON_AddNumberToObject(Attr0, "YCoordinate", options->cells[i]->oam[j].attr0.YCoordinate); + int y = options->cells[i]->oam[j].attr0.YCoordinate; + if (y & (1 << 7)) + { + y |= ~0xFF; + } + cJSON_AddNumberToObject(Attr0, "YCoordinate", y); cJSON_AddBoolToObject(Attr0, "Rotation", options->cells[i]->oam[j].attr0.Rotation); cJSON_AddBoolToObject(Attr0, "SizeDisable", options->cells[i]->oam[j].attr0.SizeDisable); cJSON_AddNumberToObject(Attr0, "Mode", options->cells[i]->oam[j].attr0.Mode); @@ -267,7 +300,12 @@ char *GetNCERJson(struct JsonToCellOptions *options) cJSON *Attr1 = cJSON_AddObjectToObject(OAM, "Attr1"); - cJSON_AddNumberToObject(Attr1, "XCoordinate", options->cells[i]->oam[j].attr1.XCoordinate); + int x = options->cells[i]->oam[j].attr1.XCoordinate; + if (x & (1 << 8)) + { + x |= ~0x1FF; + } + cJSON_AddNumberToObject(Attr1, "XCoordinate", x); cJSON_AddNumberToObject(Attr1, "RotationScaling", options->cells[i]->oam[j].attr1.RotationScaling); cJSON_AddNumberToObject(Attr1, "Size", options->cells[i]->oam[j].attr1.Size); @@ -304,6 +342,16 @@ char *GetNCERJson(struct JsonToCellOptions *options) } } + if (options->ucatEnabled) + { + cJSON *ucatCells = cJSON_AddArrayToObject(ncer, "cellAttributes"); + + for (int i = 0; i < options->cellCount; i++) + { + cJSON_AddNumberToObject(ucatCells, "cellAttr", options->ucatCellAttribtes[i]); + } + } + char *jsonString = cJSON_Print(ncer); cJSON_Delete(ncer); return jsonString; @@ -477,6 +525,9 @@ struct JsonToAnimationOptions *ParseNANRJson(char *path) if (i > options->resultCount - 1) FATAL_ERROR("Frame count is incorrect.\n"); + //init padding to false, this is used in gfx.c to control padding, and is therefore checked there + options->animationResults[i]->padded = false; + cJSON *resultType = cJSON_GetObjectItemCaseSensitive(animationResult, "resultType"); options->animationResults[i]->resultType = GetInt(resultType); switch (options->animationResults[i]->resultType) { @@ -542,6 +593,30 @@ struct JsonToAnimationOptions *ParseNANRJson(char *path) } } + cJSON *uaatBool = cJSON_GetObjectItemCaseSensitive(json, "uaatEnabled"); + options->uaatEnabled = GetBool(uaatBool); + + if (options->uaatEnabled) + { + cJSON *uaatData = cJSON_GetObjectItemCaseSensitive(json, "uaatData"); + + cJSON *uaatSequences = cJSON_GetObjectItemCaseSensitive(uaatData, "sequenceAttributes"); + options->uaatData.sequenceAttributes = malloc(sizeof(uint32_t) * options->sequenceCount); + for (int i = 0; i < options->sequenceCount; i++) + { + cJSON *uaatSeq = cJSON_GetArrayItem(uaatSequences, i); + options->uaatData.sequenceAttributes[i] = GetInt(uaatSeq); + } + + cJSON *uaatFrames = cJSON_GetObjectItemCaseSensitive(uaatData, "frameAttributes"); + options->uaatData.frameAttributes = malloc(sizeof(uint32_t) * options->frameCount); + for (int i = 0; i < options->frameCount; i++) + { + cJSON *uaatFra = cJSON_GetArrayItem(uaatFrames, i); + options->uaatData.frameAttributes[i] = GetInt(uaatFra); + } + } + cJSON_Delete(json); free(jsonString); return options; @@ -552,6 +627,7 @@ char *GetNANRJson(struct JsonToAnimationOptions *options) cJSON *nanr = cJSON_CreateObject(); cJSON_AddBoolToObject(nanr, "labelEnabled", options->labelEnabled); + cJSON_AddBoolToObject(nanr, "uaatEnabled", options->uaatEnabled); cJSON_AddNumberToObject(nanr, "sequenceCount", options->sequenceCount); cJSON_AddNumberToObject(nanr, "frameCount", options->frameCount); @@ -621,6 +697,23 @@ char *GetNANRJson(struct JsonToAnimationOptions *options) cJSON_AddNumberToObject(nanr, "labelCount", options->labelCount); } + if (options->uaatEnabled) + { + cJSON *uaat = cJSON_AddObjectToObject(nanr, "uaatData"); + + cJSON *uaatSequences = cJSON_AddArrayToObject(uaat, "sequenceAttributes"); + for (int i = 0; i < options->sequenceCount; i++) + { + cJSON_AddNumberToObject(uaatSequences, "seqAttr", options->uaatData.sequenceAttributes[i]); + } + + cJSON *uaatFrames = cJSON_AddArrayToObject(uaat, "frameAttributes"); + for (int i = 0; i < options->frameCount; i++) + { + cJSON_AddNumberToObject(uaatFrames, "fraAttr", options->uaatData.frameAttributes[i]); + } + } + char *jsonString = cJSON_Print(nanr); cJSON_Delete(nanr); return jsonString; @@ -649,6 +742,10 @@ void FreeNCERCell(struct JsonToCellOptions *options) } free(options->transferData); } + if (options->ucatEnabled) + { + free(options->ucatCellAttribtes); + } free(options->cells); free(options); } @@ -675,6 +772,11 @@ void FreeNANRAnimation(struct JsonToAnimationOptions *options) { free(options->animationResults[i]); } + if (options->uaatEnabled) + { + free(options->uaatData.sequenceAttributes); + free(options->uaatData.frameAttributes); + } if (options->labelEnabled) { for (int j = 0; j < options->labelCount; j++) diff --git a/tools/nitrogfx/json.h b/tools/nitrogfx/json.h index 8bdf307ff..78a55a341 100644 --- a/tools/nitrogfx/json.h +++ b/tools/nitrogfx/json.h @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2024 red031000 +// Copyright (c) 2021-2025 red031000 #ifndef JSON_H #define JSON_H diff --git a/tools/nitrogfx/lz.c b/tools/nitrogfx/lz.c index de553178b..e9bea861d 100644 --- a/tools/nitrogfx/lz.c +++ b/tools/nitrogfx/lz.c @@ -122,7 +122,7 @@ static void FindBestBlockBackwards(unsigned char *src, int srcPos, int srcSize, 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) +unsigned char *LZCompress(unsigned char *src, int srcSize, int *compressedSize, const int minDistance, bool forwardIteration, bool pad) { if (srcSize <= 0) goto fail; @@ -169,12 +169,14 @@ unsigned char *LZCompress(unsigned char *src, int srcSize, int *compressedSize, } if (srcPos == srcSize) { - // Pad to multiple of 4 bytes. - int remainder = destPos % 4; + if (pad) { + // Pad to multiple of 4 bytes. + int remainder = destPos % 4; - if (remainder != 0) { - for (int i = 0; i < 4 - remainder; i++) - dest[destPos++] = 0; + if (remainder != 0) { + for (int i = 0; i < 4 - remainder; i++) + dest[destPos++] = 0; + } } *compressedSize = destPos; diff --git a/tools/nitrogfx/lz.h b/tools/nitrogfx/lz.h index fdc137023..21f18a829 100644 --- a/tools/nitrogfx/lz.h +++ b/tools/nitrogfx/lz.h @@ -6,6 +6,6 @@ #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, bool forwardIteration); +unsigned char *LZCompress(unsigned char *src, int srcSize, int *compressedSize, const int minDistance, bool forwardIteration, bool pad); #endif // LZ_H diff --git a/tools/nitrogfx/main.c b/tools/nitrogfx/main.c index 88fc48715..614acd2a2 100644 --- a/tools/nitrogfx/main.c +++ b/tools/nitrogfx/main.c @@ -1,4 +1,4 @@ -// Copyright (c) 2015 YamaArashi, 2021-2024 red031000 +// Copyright (c) 2015 YamaArashi, 2021-2025 red031000 #include #include @@ -23,6 +23,10 @@ struct CommandHandler void(*function)(char *inputPath, char *outputPath, int argc, char **argv); }; +static int CountLzCompressArgs(int argc, char **argv); +static void HandleLZCompressCommand(char *inputPath, char *outputPath, int argc, char **argv); +static void HandleLZDecompressCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED); + void ConvertGbaToPng(char *inputPath, char *outputPath, struct GbaToPngOptions *options) { struct Image image; @@ -49,10 +53,9 @@ void ConvertGbaToPng(char *inputPath, char *outputPath, struct GbaToPngOptions * void ConvertNtrToPng(char *inputPath, char *outputPath, struct NtrToPngOptions *options) { // handle empty files if possible - FILE *fp = fopen(inputPath, "rb"); - if (options->handleEmpty) { + FILE *fp = fopen(inputPath, "rb"); if (fp != NULL) { fseek(fp, 0, SEEK_END); @@ -61,20 +64,22 @@ void ConvertNtrToPng(char *inputPath, char *outputPath, struct NtrToPngOptions * if (size == 0) { FILE *out = fopen(outputPath, "wb+"); - fclose(out); + if (out != NULL) + { + fclose(out); + } fclose(fp); return; } + fclose(fp); } } - fclose(fp); - struct Image image; if (options->paletteFilePath != NULL) { - ReadNtrPalette(options->paletteFilePath, &image.palette, options->bitDepth, options->palIndex, false); + ReadNtrPalette(options->paletteFilePath, &image.palette, options->bitDepth, options->palIndex, false, options->convertTo8Bpp); image.hasPalette = true; } else @@ -82,7 +87,7 @@ void ConvertNtrToPng(char *inputPath, char *outputPath, struct NtrToPngOptions * image.hasPalette = false; } - uint32_t key = ReadNtrImage(inputPath, options->width, 0, options->colsPerChunk, options->rowsPerChunk, &image, !image.hasPalette, options->scanFrontToBack); + uint32_t key = ReadNtrImage(inputPath, options->width, 0, options->colsPerChunk, options->rowsPerChunk, &image, !image.hasPalette, options->encodeMode, options->convertTo8Bpp, options->palIndex, options->verbose); if (key) { @@ -98,6 +103,11 @@ void ConvertNtrToPng(char *inputPath, char *outputPath, struct NtrToPngOptions * image.hasTransparency = options->hasTransparency; + if (options->cellFilePath != NULL) + { + ApplyCellsToImage(options->cellFilePath, &image, true, options->cellSnap); + } + WritePng(outputPath, &image); FreeImage(&image); @@ -119,10 +129,9 @@ void ConvertPngToGba(char *inputPath, char *outputPath, struct PngToGbaOptions * void ConvertPngToNtr(char *inputPath, char *outputPath, struct PngToNtrOptions *options) { // handle empty files if possible - FILE *fp = fopen(inputPath, "rb"); - if (options->handleEmpty) { + FILE *fp = fopen(inputPath, "rb"); if (fp != NULL) { fseek(fp, 0, SEEK_END); @@ -131,14 +140,17 @@ void ConvertPngToNtr(char *inputPath, char *outputPath, struct PngToNtrOptions * if (size == 0) { FILE *out = fopen(outputPath, "wb+"); - fclose(out); + if (out != NULL) + { + fclose(out); + } fclose(fp); return; } + fclose(fp); } } - fclose(fp); struct Image image; image.bitDepth = options->bitDepth == 0 ? 4 : options->bitDepth; @@ -146,25 +158,31 @@ void ConvertPngToNtr(char *inputPath, char *outputPath, struct PngToNtrOptions * ReadPng(inputPath, &image); uint32_t key = 0; - if (options->scanMode) + if (options->encodeMode) { char* string = malloc(strlen(inputPath) + 5); sprintf(string, "%s.key", inputPath); - FILE *fp2 = fopen(string, "rb"); - if (fp2 == NULL) + FILE *fp = fopen(string, "rb"); + if (fp == NULL) FATAL_ERROR("Failed to open key file for reading.\n"); - size_t count = fread(&key, 4, 1, fp2); + size_t count = fread(&key, 4, 1, fp); if (count != 1) FATAL_ERROR("Not a valid key file.\n"); - fclose(fp2); + fclose(fp); free(string); } options->bitDepth = options->bitDepth == 0 ? image.bitDepth : options->bitDepth; + if (options->cellFilePath != NULL) + { + ApplyCellsToImage(options->cellFilePath, &image, false, options->cellSnap); + } + 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); + options->sopc, options->vramTransfer, options->scan, options->encodeMode, options->mappingType, + key, options->wrongSize, options->convertTo4Bpp); FreeImage(&image); } @@ -255,13 +273,17 @@ void HandleNtrToPngCommand(char *inputPath, char *outputPath, int argc, char **a { struct NtrToPngOptions options; options.paletteFilePath = NULL; + options.cellFilePath = NULL; + options.cellSnap = true; options.hasTransparency = false; options.width = 0; options.colsPerChunk = 1; options.rowsPerChunk = 1; options.palIndex = 1; - options.scanFrontToBack = false; options.handleEmpty = false; + options.encodeMode = 0; + options.convertTo8Bpp = false; + options.verbose = false; for (int i = 3; i < argc; i++) { @@ -276,6 +298,24 @@ void HandleNtrToPngCommand(char *inputPath, char *outputPath, int argc, char **a options.paletteFilePath = argv[i]; } + else if (strcmp(option, "-cell") == 0) + { + if (i + 1 >= argc) + FATAL_ERROR("No cell file path following \"-cell\".\n"); + + i++; + + options.cellFilePath = argv[i]; + + if (i + 1 < argc) + { + if (strcmp(argv[i+1], "-nosnap") == 0) + { + options.cellSnap = false; + i++; + } + } + } else if (strcmp(option, "-object") == 0) { options.hasTransparency = true; @@ -334,12 +374,35 @@ void HandleNtrToPngCommand(char *inputPath, char *outputPath, int argc, char **a } else if (strcmp(option, "-scanfronttoback") == 0) { - options.scanFrontToBack = true; + // maintained for compatibility + if (options.encodeMode != 0) + FATAL_ERROR("Encode mode specified more than once.\n-encodebacktofront goes back to front as in DP, -encodefronttoback goes front to back as in PtHGSS\n"); + options.encodeMode = 2; + } + else if (strcmp(option, "-encodebacktofront") == 0) + { + if (options.encodeMode != 0) + FATAL_ERROR("Encode mode specified more than once.\n-encodebacktofront goes back to front as in DP, -encodefronttoback goes front to back as in PtHGSS\n"); + options.encodeMode = 1; + } + else if (strcmp(option, "-encodefronttoback") == 0) + { + if (options.encodeMode != 0) + FATAL_ERROR("Encode mode specified more than once.\n-encodebacktofront goes back to front as in DP, -encodefronttoback goes front to back as in PtHGSS\n"); + options.encodeMode = 2; } else if (strcmp(option, "-handleempty") == 0) { options.handleEmpty = true; } + else if (strcmp(option, "-convertTo8Bpp") == 0) + { + options.convertTo8Bpp = true; + } + else if (strcmp(option, "-verbose") == 0) + { + options.verbose = true; + } else { FATAL_ERROR("Unrecognized option \"%s\".\n", option); @@ -352,6 +415,13 @@ void HandleNtrToPngCommand(char *inputPath, char *outputPath, int argc, char **a ConvertNtrToPng(inputPath, outputPath, &options); } +void HandleNtrLzToPngCommand(char *inputPath, char *outputPath, int argc, char **argv) +{ + HandleLZDecompressCommand(inputPath, outputPath, argc, argv); + + HandleNtrToPngCommand(outputPath, outputPath, argc, argv); +} + void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **argv) { char *outputFileExtension = GetFileExtension(outputPath); @@ -421,6 +491,8 @@ void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **a void HandlePngToNtrCommand(char *inputPath, char *outputPath, int argc, char **argv) { struct PngToNtrOptions options; + options.cellFilePath = NULL; + options.cellSnap = true; options.numTiles = 0; options.bitDepth = 0; options.colsPerChunk = 1; @@ -430,10 +502,12 @@ void HandlePngToNtrCommand(char *inputPath, char *outputPath, int argc, char **a options.byteOrder = true; options.version101 = false; options.sopc = false; - options.scanMode = 0; + options.scan = false; options.handleEmpty = false; options.vramTransfer = false; options.mappingType = 0; + options.encodeMode = 0; + options.convertTo4Bpp = false; for (int i = 3; i < argc; i++) { @@ -452,6 +526,24 @@ void HandlePngToNtrCommand(char *inputPath, char *outputPath, int argc, char **a if (options.numTiles < 1) FATAL_ERROR("Number of tiles must be positive.\n"); } + else if (strcmp(option, "-cell") == 0) + { + if (i + 1 >= argc) + FATAL_ERROR("No cell file path following \"-cell\".\n"); + + i++; + + options.cellFilePath = argv[i]; + + if (i + 1 < argc) + { + if (strcmp(argv[i+1], "-nosnap") == 0) + { + options.cellSnap = false; + i++; + } + } + } else if (strcmp(option, "-mwidth") == 0 || strcmp(option, "-cpc") == 0) { if (i + 1 >= argc) @@ -506,17 +598,37 @@ void HandlePngToNtrCommand(char *inputPath, char *outputPath, int argc, char **a { options.sopc = true; } + else if (strcmp(option, "-scan") == 0) + { + options.scan = true; + } else if (strcmp(option, "-scanned") == 0) { - if (options.scanMode != 0) - FATAL_ERROR("Scan mode specified more than once.\n-scanned goes back to front as in DP, -scanfronttoback goes front to back as in PtHGSS\n"); - options.scanMode = 1; + // maintained for compatibility + if (options.encodeMode != 0) + FATAL_ERROR("Encode mode specified more than once.\n-encodebacktofront goes back to front as in DP, -encodefronttoback goes front to back as in PtHGSS\n"); + options.encodeMode = 1; + options.scan = true; } else if (strcmp(option, "-scanfronttoback") == 0) { - if (options.scanMode != 0) - FATAL_ERROR("Scan mode specified more than once.\n-scanned goes back to front as in DP, -scanfronttoback goes front to back as in PtHGSS\n"); - options.scanMode = 2; + // maintained for compatibility + if (options.encodeMode != 0) + FATAL_ERROR("Encode mode specified more than once.\n-encodebacktofront goes back to front as in DP, -encodefronttoback goes front to back as in PtHGSS\n"); + options.encodeMode = 2; + options.scan = true; + } + else if (strcmp(option, "-encodebacktofront") == 0) + { + if (options.encodeMode != 0) + FATAL_ERROR("Encode mode specified more than once.\n-encodebacktofront goes back to front as in DP, -encodefronttoback goes front to back as in PtHGSS\n"); + options.encodeMode = 1; + } + else if (strcmp(option, "-encodefronttoback") == 0) + { + if (options.encodeMode != 0) + FATAL_ERROR("Encode mode specified more than once.\n-encodebacktofront goes back to front as in DP, -encodefronttoback goes front to back as in PtHGSS\n"); + options.encodeMode = 2; } else if (strcmp(option, "-wrongsize") == 0) { options.wrongSize = true; @@ -541,6 +653,10 @@ void HandlePngToNtrCommand(char *inputPath, char *outputPath, int argc, char **a if (options.mappingType != 0 && options.mappingType != 32 && options.mappingType != 64 && options.mappingType != 128 && options.mappingType != 256) FATAL_ERROR("bitdepth must be one of the following: 0, 32, 64, 128, or 256\n"); } + else if (strcmp(option, "-convertTo4Bpp") == 0) + { + options.convertTo4Bpp = true; + } else { FATAL_ERROR("Unrecognized option \"%s\".\n", option); @@ -550,6 +666,15 @@ void HandlePngToNtrCommand(char *inputPath, char *outputPath, int argc, char **a ConvertPngToNtr(inputPath, outputPath, &options); } +void HandlePngToNtrLzCommand(char *inputPath, char *outputPath, int argc, char **argv) +{ + int numLzArgs = CountLzCompressArgs(argc, argv); + + HandlePngToNtrCommand(inputPath, outputPath, argc - numLzArgs, argv); + + HandleLZCompressCommand(outputPath, outputPath, 3 + numLzArgs, &(argv[argc - 3 - numLzArgs])); +} + void HandlePngToGbaPaletteCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED) { struct Palette palette; @@ -566,8 +691,10 @@ void HandlePngToNtrPaletteCommand(char *inputPath, char *outputPath, int argc, c bool nopad = false; int bitdepth = 0; int compNum = 0; + int pcmpStartIndex = 0; bool pcmp = false; bool inverted = false; + bool convertTo4Bpp = false; for (int i = 3; i < argc; i++) { @@ -614,11 +741,25 @@ void HandlePngToNtrPaletteCommand(char *inputPath, char *outputPath, int argc, c else if (strcmp(option, "-pcmp") == 0) { pcmp = true; + + if (i + 2 < argc) + { + if (strcmp(argv[i + 1], "-start") == 0) + { + i += 2; + if (!ParseNumber(argv[i], NULL, 10, &pcmpStartIndex)) + FATAL_ERROR("Failed to parse PCMP start index value.\n"); + } + } } else if (strcmp(option, "-invertsize") == 0) { inverted = true; } + else if (strcmp(option, "-convertTo4Bpp") == 0) + { + convertTo4Bpp = true; + } else { FATAL_ERROR("Unrecognized option \"%s\".\n", option); @@ -626,7 +767,7 @@ void HandlePngToNtrPaletteCommand(char *inputPath, char *outputPath, int argc, c } ReadPngPalette(inputPath, &palette); - WriteNtrPalette(outputPath, &palette, ncpr, ir, bitdepth, !nopad, compNum, pcmp, inverted); + WriteNtrPalette(outputPath, &palette, ncpr, ir, bitdepth, !nopad, compNum, pcmp, pcmpStartIndex, inverted, convertTo4Bpp); } void HandleGbaToJascPaletteCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED) @@ -670,7 +811,7 @@ void HandleNtrToJascPaletteCommand(char *inputPath, char *outputPath, int argc, } } - ReadNtrPalette(inputPath, &palette, bitdepth, 0, inverted); + ReadNtrPalette(inputPath, &palette, bitdepth, 0, inverted, false); WriteJascPalette(outputPath, &palette); } @@ -719,6 +860,7 @@ void HandleJascToNtrPaletteCommand(char *inputPath, char *outputPath, int argc, bool nopad = false; int bitdepth = 0; int compNum = 0; + int pcmpStartIndex = 0; bool pcmp = false; bool inverted = false; @@ -780,6 +922,16 @@ void HandleJascToNtrPaletteCommand(char *inputPath, char *outputPath, int argc, else if (strcmp(option, "-pcmp") == 0) { pcmp = true; + + if (i + 2 < argc) + { + if (strcmp(argv[i + 1], "-start") == 0) + { + i += 2; + if (!ParseNumber(argv[i], NULL, 10, &pcmpStartIndex)) + FATAL_ERROR("Failed to parse PCMP start index value.\n"); + } + } } else if (strcmp(option, "-invertsize") == 0) { @@ -798,7 +950,7 @@ void HandleJascToNtrPaletteCommand(char *inputPath, char *outputPath, int argc, if (numColors != 0) palette.numColors = numColors; - WriteNtrPalette(outputPath, &palette, ncpr, ir, bitdepth, !nopad, compNum, pcmp, inverted); + WriteNtrPalette(outputPath, &palette, ncpr, ir, bitdepth, !nopad, compNum, pcmp, pcmpStartIndex, inverted, false); } void HandleJsonToNtrCellCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED) @@ -812,6 +964,13 @@ void HandleJsonToNtrCellCommand(char *inputPath, char *outputPath, int argc UNUS FreeNCERCell(options); } +void HandleJsonToNtrCellLzCommand(char *inputPath, char *outputPath, int argc, char **argv) +{ + HandleJsonToNtrCellCommand(inputPath, outputPath, argc, argv); + + HandleLZCompressCommand(outputPath, outputPath, argc, argv); +} + void HandleNtrCellToJsonCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED) { struct JsonToCellOptions *options = malloc(sizeof(struct JsonToCellOptions)); @@ -825,6 +984,13 @@ void HandleNtrCellToJsonCommand(char *inputPath, char *outputPath, int argc UNUS FreeNCERCell(options); } +void HandleNtrCellLzToJsonCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED) +{ + HandleLZDecompressCommand(inputPath, outputPath, argc, argv); + + HandleNtrCellToJsonCommand(outputPath, outputPath, argc, argv); +} + void HandleJsonToNtrScreenCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED) { struct JsonToScreenOptions *options; @@ -876,6 +1042,13 @@ void HandleJsonToNtrAnimationCommand(char *inputPath, char *outputPath, int argc FreeNANRAnimation(options); } +void HandleJsonToNtrAnimationLzCommand(char *inputPath, char *outputPath, int argc, char **argv) +{ + HandleJsonToNtrAnimationCommand(inputPath, outputPath, argc, argv); + + HandleLZCompressCommand(outputPath, outputPath, argc, argv); +} + void HandleNtrAnimationToJsonCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED) { struct JsonToAnimationOptions *options = malloc(sizeof(struct JsonToAnimationOptions)); @@ -889,6 +1062,13 @@ void HandleNtrAnimationToJsonCommand(char *inputPath, char *outputPath, int argc FreeNANRAnimation(options); } +void HandleNtrAnimationLzToJsonCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED) +{ + HandleLZDecompressCommand(inputPath, outputPath, argc, argv); + + HandleNtrAnimationToJsonCommand(outputPath, outputPath, argc, argv); +} + void HandleJsonToNtrMulticellAnimationCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED) { struct JsonToAnimationOptions *options; @@ -968,11 +1148,51 @@ void HandlePngToFullwidthJapaneseFontCommand(char *inputPath, char *outputPath, FreeImage(&image); } -void HandleLZCompressCommand(char *inputPath, char *outputPath, int argc, char **argv) +static int CountLzCompressArgs(int argc, char **argv) +{ + int count = 0; + + for (int i = 3; i < argc; i++) + { + char *option = argv[i]; + + if (strcmp(option, "-overflow") == 0) + { + if (i + 1 >= argc) + FATAL_ERROR("No size following \"-overflow\".\n"); + + i++; + + count += 2; + } + else if (strcmp(option, "-search") == 0) + { + if (i + 1 >= argc) + FATAL_ERROR("No size following \"-overflow\".\n"); + + i++; + + count += 2; + } + else if (strcmp(option, "-reverse") == 0) + { + count++; + } + else if (strcmp(option, "-nopad") == 0) + { + count++; + } + } + + return count; +} + +static void HandleLZCompressCommand(char *inputPath, char *outputPath, int argc, char **argv) { int overflowSize = 0; int minDistance = 2; // default, for compatibility with LZ77UnCompVram() bool forwardIteration = true; + bool nopad = false; for (int i = 3; i < argc; i++) { @@ -1008,6 +1228,10 @@ void HandleLZCompressCommand(char *inputPath, char *outputPath, int argc, char * { forwardIteration = false; } + else if (strcmp(option, "-nopad") == 0) + { + nopad = true; + } else { FATAL_ERROR("Unrecognized option \"%s\".\n", option); @@ -1024,7 +1248,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, forwardIteration); + unsigned char *compressedData = LZCompress(buffer, fileSize + overflowSize, &compressedSize, minDistance, forwardIteration, !nopad); compressedData[1] = (unsigned char)fileSize; compressedData[2] = (unsigned char)(fileSize >> 8); @@ -1037,7 +1261,7 @@ void HandleLZCompressCommand(char *inputPath, char *outputPath, int argc, char * free(compressedData); } -void HandleLZDecompressCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED) +static void HandleLZDecompressCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED) { int fileSize; unsigned char *buffer = ReadWholeFile(inputPath, &fileSize); @@ -1218,11 +1442,13 @@ int main(int argc, char **argv) { "8bpp", "png", HandleGbaToPngCommand }, { "nbfc", "png", HandleGbaToPngCommand }, { "NCGR", "png", HandleNtrToPngCommand }, + { "NCGR.lz", "png", HandleNtrLzToPngCommand }, { "png", "1bpp", HandlePngToGbaCommand }, { "png", "4bpp", HandlePngToGbaCommand }, { "png", "nbfc", HandlePngToGbaCommand }, { "png", "8bpp", HandlePngToGbaCommand }, { "png", "NCGR", HandlePngToNtrCommand }, + { "png", "NCGR.lz", HandlePngToNtrLzCommand }, { "png", "gbapal", HandlePngToGbaPaletteCommand }, { "png", "nbfp", HandlePngToGbaPaletteCommand }, { "png", "NCLR", HandlePngToNtrPaletteCommand }, @@ -1238,10 +1464,14 @@ int main(int argc, char **argv) { "fwjpnfont", "png", HandleFullwidthJapaneseFontToPngCommand }, { "png", "fwjpnfont", HandlePngToFullwidthJapaneseFontCommand }, { "json", "NCER", HandleJsonToNtrCellCommand }, + { "json", "NCER.lz", HandleJsonToNtrCellLzCommand }, { "NCER", "json", HandleNtrCellToJsonCommand }, + { "NCER.lz", "json", HandleNtrCellLzToJsonCommand }, { "json", "NSCR", HandleJsonToNtrScreenCommand }, { "json", "NANR", HandleJsonToNtrAnimationCommand }, + { "json", "NANR.lz", HandleJsonToNtrAnimationLzCommand }, { "NANR", "json", HandleNtrAnimationToJsonCommand }, + { "NANR.lz", "json", HandleNtrAnimationLzToJsonCommand }, { "json", "NMAR", HandleJsonToNtrMulticellAnimationCommand }, { "NMAR", "json", HandleNtrAnimationToJsonCommand }, { NULL, "huff", HandleHuffCompressCommand }, @@ -1268,8 +1498,10 @@ int main(int argc, char **argv) for (int i = 0; handlers[i].function != NULL; i++) { - if ((handlers[i].inputFileExtension == NULL || strcmp(handlers[i].inputFileExtension, inputFileExtension) == 0) + if (((handlers[i].inputFileExtension == NULL || strcmp(handlers[i].inputFileExtension, inputFileExtension) == 0) && (handlers[i].outputFileExtension == NULL || strcmp(handlers[i].outputFileExtension, outputFileExtension) == 0)) + || (handlers[i].inputFileExtension == NULL && strrchr(outputFileExtension, '.') && strstr(outputFileExtension, handlers[i].outputFileExtension)) + || (handlers[i].outputFileExtension == NULL && strrchr(inputFileExtension, '.') && strstr(inputFileExtension, handlers[i].inputFileExtension))) { handlers[i].function(inputPath, outputPath, argc, argv); return 0; diff --git a/tools/nitrogfx/options.h b/tools/nitrogfx/options.h index ed5dab6e2..5dfaa7ea5 100644 --- a/tools/nitrogfx/options.h +++ b/tools/nitrogfx/options.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018 huderlem, 2021-2024 red031000 +// Copyright (c) 2018 huderlem, 2021-2025 red031000 #ifndef OPTIONS_H #define OPTIONS_H @@ -24,6 +24,8 @@ struct PngToGbaOptions { }; struct PngToNtrOptions { + char *cellFilePath; + bool cellSnap; int numTiles; int bitDepth; int colsPerChunk; @@ -32,23 +34,29 @@ struct PngToNtrOptions { bool byteOrder; bool version101; bool sopc; - uint32_t scanMode; + bool scan; bool wrongSize; bool handleEmpty; bool vramTransfer; int mappingType; + uint32_t encodeMode; + bool convertTo4Bpp; }; struct NtrToPngOptions { char *paletteFilePath; + char *cellFilePath; + bool cellSnap; int bitDepth; bool hasTransparency; int width; int colsPerChunk; int rowsPerChunk; int palIndex; - bool scanFrontToBack; bool handleEmpty; + uint32_t encodeMode; + bool convertTo8Bpp; + bool verbose; }; struct CellVramTransferData { @@ -104,8 +112,10 @@ struct Cell { struct JsonToCellOptions { bool labelEnabled; + bool dontPadKbec; bool extended; bool vramTransferEnabled; + bool ucatEnabled; int mappingType; int cellCount; struct Cell **cells; @@ -113,6 +123,7 @@ struct JsonToCellOptions { struct CellVramTransferData **transferData; char **labels; int labelCount; + int *ucatCellAttribtes; }; struct JsonToScreenOptions { @@ -163,6 +174,11 @@ struct AnimationResults { }; }; +struct UaatData { + int *sequenceAttributes; + int *frameAttributes; +}; + struct JsonToAnimationOptions { bool multiCell; short sequenceCount; @@ -173,6 +189,8 @@ struct JsonToAnimationOptions { char **labels; int labelCount; short resultCount; + bool uaatEnabled; + struct UaatData uaatData; }; struct NtrFontOptions { diff --git a/tools/nitrogfx/util.c b/tools/nitrogfx/util.c index 4d668dc7a..0ac86be8a 100644 --- a/tools/nitrogfx/util.c +++ b/tools/nitrogfx/util.c @@ -56,6 +56,19 @@ char *GetFileExtension(char *path) if (*extension == 0) return NULL; + if (strcmp(extension,"lz") == 0) + { + char *plainName = malloc(strlen(path) + 1); + strcpy(plainName, path); + plainName[strlen(path) - 3] = 0; + char *newExtension = GetFileExtension(plainName); + if (newExtension != NULL) + { + extension -= strlen(newExtension) + 1; + } + free(plainName); + } + return extension; }