From 3f34a4d132c3686544861a295940819de0b0f4e1 Mon Sep 17 00:00:00 2001 From: Retnuhytnuob Date: Thu, 26 Feb 2026 04:54:27 -0600 Subject: [PATCH 01/10] proof of concept --- tools/gbagfx/gfx.c | 437 ++++++++++++++++++++++++--- tools/gbagfx/gfx.h | 4 +- tools/gbagfx/main.c | 34 ++- tools/gbagfx/oam_slices/oam_slicer.c | 92 ++++-- tools/gbagfx/oam_slices/oam_slicer.h | 12 + tools/gbagfx/options.h | 2 + 6 files changed, 502 insertions(+), 79 deletions(-) diff --git a/tools/gbagfx/gfx.c b/tools/gbagfx/gfx.c index 127bbe94..e748f004 100755 --- a/tools/gbagfx/gfx.c +++ b/tools/gbagfx/gfx.c @@ -10,6 +10,179 @@ #include "util.h" #include "oam_slices/oam_slicer.h" +static struct OamOverrideSegment *g_oam_sequence = NULL; +static int g_oam_sequence_count = 0; +static int g_oam_tilecount = 0; +static int g_oam_png_tiles = 0; + +static void clear_oam_sequence(void) { + if (g_oam_sequence) { + free(g_oam_sequence); + g_oam_sequence = NULL; + g_oam_sequence_count = 0; + g_oam_tilecount = 0; + g_oam_png_tiles = 0; + } +} + +// Load oam sequence file. +// JSON matching the schema provided by the user. +// Returns number of segments, 0 if none, or -1 on IO/error. +static int load_oam_sequence_file(char *path, struct OamOverrideSegment **outSeq) { + if (path == NULL) return 0; + int fileSize; + unsigned char *buf = ReadWholeFile(path, &fileSize); + if (buf == NULL) return -1; + + // skip leading whitespace + char *p = (char*)buf; + char *end = (char*)buf + fileSize; + while (p < end && (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')) ++p; + + // If JSON (starts with '{'), parse JSON-ish format for our schema + if (p < end && *p == '{') { + // Minimal JSON parsing for expected keys: pngsize, tilecount, pieces + int tilecount = -1; + + // find "tilecount" + char *tok = strstr(p, "\"tilecount\""); + if (tok) { + char *col = strchr(tok, ':'); + if (col) tilecount = atoi(col + 1); + } + + // find "pngsize" + tok = strstr(p, "\"pngsize\""); + int png_w = -1, png_h = -1; + if (tok) { + char *col = strchr(tok, ':'); + if (col) { + char *quote1 = strchr(col, '"'); + if (quote1) { + char *quote2 = strchr(quote1 + 1, '"'); + if (quote2) { + int a, b; + if (sscanf(quote1 + 1, "%d x %d", &a, &b) == 2 || sscanf(quote1 + 1, "%dx%d", &a, &b) == 2) { + png_w = a; + png_h = b; + g_oam_png_tiles = png_w * png_h; + } + } + } + } + } + + // find pieces array + tok = strstr(p, "\"pieces\""); + if (!tok) { + free(buf); + *outSeq = NULL; + return -1; + } + + char *arrstart = strchr(tok, '['); + if (!arrstart) { free(buf); *outSeq = NULL; return -1; } + + // allocate temporary list of segments up to a reasonable bound (count braces) + int maxSegments = 0; + for (char *q = arrstart; q < end; ++q) if (*q == '{') ++maxSegments; + if (maxSegments == 0) { free(buf); *outSeq = NULL; return 0; } + + struct OamOverrideSegment *arr = malloc(sizeof(struct OamOverrideSegment) * maxSegments); + int idx = 0; + + char *q = arrstart; + while (q < end) { + char *obj = strchr(q, '{'); + if (!obj) break; + char *objend = strchr(obj, '}'); + if (!objend) break; + + // find pos + int offX = 0, offY = 0, w = 0, h = 0; + int spacer = 0; + char *posTok = strstr(obj, "\"pos\""); + if (posTok && posTok < objend) { + char *col = strchr(posTok, ':'); + if (col) { + char *q1 = strchr(col, '"'); + if (q1 && q1 < objend) { + char *q2 = strchr(q1 + 1, '"'); + if (q2 && q2 < objend) { + sscanf(q1 + 1, "%d,%d", &offX, &offY); + } + } + } + } + + // find size + char *sizeTok = strstr(obj, "\"size\""); + if (sizeTok && sizeTok < objend) { + char *col = strchr(sizeTok, ':'); + if (col) { + char *q1 = strchr(col, '"'); + if (q1 && q1 < objend) { + char *q2 = strchr(q1 + 1, '"'); + if (q2 && q2 < objend) { + if (sscanf(q1 + 1, "%d x %d", &w, &h) != 2) { + sscanf(q1 + 1, "%dx%d", &w, &h); + } + } + } + } + } + + // find spacer (optional) + char *spTok = strstr(obj, "\"spacer\""); + if (spTok && spTok < objend) { + char *col = strchr(spTok, ':'); + if (col) { + if (strstr(col, "true") != NULL) spacer = 1; + else spacer = atoi(col + 1); + } + } + + // validate non-spacer shapes + if (!spacer) { + if (!is_valid_oam_shape(w, h)) { + free(arr); + free(buf); + *outSeq = NULL; + return -1; + } + } + + arr[idx].offX = (unsigned short)offX; + arr[idx].offY = (unsigned short)offY; + arr[idx].width = (unsigned short)w; + arr[idx].height = (unsigned short)h; + arr[idx].spacer = spacer ? true : false; + ++idx; + + q = objend + 1; + } + + // compute tilecount if missing: sum of non-spacer w*h + if (tilecount < 0) { + int sum = 0; + for (int i = 0; i < idx; ++i) if (!arr[i].spacer) sum += arr[i].width * arr[i].height; + tilecount = sum; + } + + // store computed tilecount globally for validation + g_oam_tilecount = tilecount; + + free(buf); + if (idx == 0) { free(arr); *outSeq = NULL; return 0; } + *outSeq = arr; + return idx; + } + + free(buf); + *outSeq = NULL; + return -1; +} + #define GET_GBA_PAL_RED(x) (((x) >> 0) & 0x1F) #define GET_GBA_PAL_GREEN(x) (((x) >> 5) & 0x1F) #define GET_GBA_PAL_BLUE(x) (((x) >> 10) & 0x1F) @@ -80,7 +253,7 @@ static void ConvertFromTiles1Bpp(unsigned char *src, unsigned char *dest, int nu } } -static void ConvertFromTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors, bool oamMap) +static void ConvertFromTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors, bool oamMap, int tilesHeight) { int subTileX = 0; int subTileY = 0; @@ -90,10 +263,18 @@ static void ConvertFromTiles4Bpp(unsigned char *src, unsigned char *dest, int nu TileCoord tileMappingCoord[MAX_OAM_TILE_SIDE_LENGTH_SQUARED]; int tileCount = 0; + struct OamSegment *oamSegments = NULL; if (oamMap) { - tileCount = getOamTileIndex(metatileWidth, metatileHeight, tileMappingCoord); + tileCount = getOamTileIndexEx(metatileWidth, metatileHeight, tileMappingCoord, g_oam_sequence, g_oam_sequence_count, &oamSegments); + } + + /* If using OAM mapping, start placement at the first mapped coordinate so + the first data tile goes to the first non-spacer slot instead of (0,0). */ + if (oamMap && tileCount > 0) { + subTileX = tileMappingCoord[0].x; + subTileY = tileMappingCoord[0].y; } for (int i = 0; i < numTiles; i++) { @@ -121,6 +302,44 @@ static void ConvertFromTiles4Bpp(unsigned char *src, unsigned char *dest, int nu AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight); } } + + // If there are spacer segments, fill those tile regions across all metatiles with a 2x2 checkerboard + if (oamMap && oamSegments) { + int metatilesHigh = tilesHeight / metatileHeight; + + // For each spacer segment, repeat across every metatile and fill its tiles + struct OamSegment *seg = oamSegments; + while (seg) { + if (seg->spacer) { + for (int my = 0; my < metatilesHigh; ++my) { + for (int mx = 0; mx < metatilesWide; ++mx) { + for (int ty = 0; ty < seg->height; ++ty) { + for (int tx = 0; tx < seg->width; ++tx) { + int baseTileX = mx * metatileWidth + seg->offX + tx; + int baseTileY = my * metatileHeight + seg->offY + ty; + for (int j = 0; j < 8; j++) { + int destY = (baseTileY) * 8 + j; + for (int k = 0; k < 4; k++) { + int destX = baseTileX * 4 + k; + // compute two pixels per byte: left at (px = k*2), right at px+1 + int px0 = k * 2; + int py = j; + unsigned char left = (((px0 / 2) + (py / 2)) & 1) ? 0xF : 0x0; + unsigned char right = ((((px0+1) / 2) + (py / 2)) & 1) ? 0xF : 0x0; + dest[destY * ((metatilesWide * metatileWidth) * 4) + destX] = (left << 4) | right; + } + } + } + } + } + } + } + seg = seg->next; + } + + free_oam_segments(oamSegments); + oamSegments = NULL; + } } static void ConvertFromTiles8Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors) @@ -211,47 +430,102 @@ void Convert4BppImageWithPaletteMap(struct Image *image) image->bitDepth = 8; } -static void ConvertToTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors, bool oamMap) +static void ConvertToTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, int tilesHeight, bool invertColors, bool oamMap) { int subTileX = 0; int subTileY = 0; int metatileX = 0; int metatileY = 0; int pitch = (metatilesWide * metatileWidth) * 4; - - TileCoord tileMappingCoord[MAX_OAM_TILE_SIDE_LENGTH_SQUARED]; - int tileCount = 0; - - if (oamMap) - { - tileCount = getOamTileIndex(metatileWidth, metatileHeight, tileMappingCoord); - } - - for (int i = 0; i < numTiles; i++) { - for (int j = 0; j < 8; j++) { - int srcY = (metatileY * metatileHeight + subTileY) * 8 + j; - - for (int k = 0; k < 4; k++) { - int srcX = (metatileX * metatileWidth + subTileX) * 4 + k; - unsigned char srcPixelPair = src[srcY * pitch + srcX]; - unsigned char leftPixel = srcPixelPair >> 4; - unsigned char rightPixel = srcPixelPair & 0xF; - - if (invertColors) { - leftPixel = 15 - leftPixel; - rightPixel = 15 - rightPixel; + (void)tilesHeight; // may be unused in some builds + + if (!oamMap) { + // Fallback to standard ordering + for (int i = 0; i < numTiles; i++) { + for (int j = 0; j < 8; j++) { + int srcY = (metatileY * metatileHeight + subTileY) * 8 + j; + + for (int k = 0; k < 4; k++) { + int srcX = (metatileX * metatileWidth + subTileX) * 4 + k; + unsigned char srcPixelPair = src[srcY * pitch + srcX]; + unsigned char leftPixel = srcPixelPair >> 4; + unsigned char rightPixel = srcPixelPair & 0xF; + + if (invertColors) { + leftPixel = 15 - leftPixel; + rightPixel = 15 - rightPixel; + } + + *dest++ = (rightPixel << 4) | leftPixel; } + } - *dest++ = (rightPixel << 4) | leftPixel; + AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight); + } + } else { + // Build full ordering across all metatiles using the segment sequence + struct OamSegment *segments = NULL; + TileCoord dummy[MAX_OAM_TILE_SIDE_LENGTH_SQUARED]; + // getOamTileIndexEx returns the compressed mapping for one metatile and the segments list + getOamTileIndexEx(metatileWidth, metatileHeight, dummy, g_oam_sequence, g_oam_sequence_count, &segments); + + int metatilesHigh = tilesHeight / metatileHeight; + int totalTiles = numTiles; + + TileCoord *fullOrder = malloc(sizeof(TileCoord) * totalTiles); + bool *fullSpacer = malloc(sizeof(bool) * totalTiles); + int idx = 0; + for (int my = 0; my < metatilesHigh; ++my) { + for (int mx = 0; mx < metatilesWide; ++mx) { + struct OamSegment *seg = segments; + while (seg) { + for (int ty = 0; ty < seg->height; ++ty) { + for (int tx = 0; tx < seg->width; ++tx) { + fullOrder[idx].x = mx * metatileWidth + seg->offX + tx; + fullOrder[idx].y = my * metatileHeight + seg->offY + ty; + fullSpacer[idx] = seg->spacer; + ++idx; + } + } + seg = seg->next; + } } } - if (oamMap) { - AdvanceOamMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, tileMappingCoord, tileCount, i); - } else { - AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight); + // write out tiles in full order, but skip spacer slots when writing + // so the output tile stream only contains actual data tiles. Leave the + // remainder of the destination buffer zeroed so the caller can detect + // trailing zero padding. + unsigned char *destPtr = dest; + for (int p = 0; p < totalTiles; ++p) { + if (fullSpacer[p]) continue; + + int baseTileX = fullOrder[p].x; + int baseTileY = fullOrder[p].y; + for (int j = 0; j < 8; j++) { + int srcY = baseTileY * 8 + j; + for (int k = 0; k < 4; k++) { + int srcX = baseTileX * 4 + k; + unsigned char srcPixelPair = src[srcY * pitch + srcX]; + unsigned char leftPixel = srcPixelPair >> 4; + unsigned char rightPixel = srcPixelPair & 0xF; + + if (invertColors) { + leftPixel = 15 - leftPixel; + rightPixel = 15 - rightPixel; + } + + *destPtr++ = (rightPixel << 4) | leftPixel; + } + } } + + free(fullOrder); + free(fullSpacer); + free_oam_segments(segments); } + + return; } static void ConvertToTiles8Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors) @@ -434,7 +708,7 @@ static unsigned char *DecodeTilemap(unsigned char *tiles, struct Tilemap *tilema return decoded; } -void ReadTileImage(char *path, int tilesWidth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors, bool oamMap) +void ReadTileImage(char *path, int tilesWidth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors, bool oamMap, char *oamSequenceFilePath) { int tileSize = image->bitDepth * 8; @@ -453,13 +727,39 @@ void ReadTileImage(char *path, int tilesWidth, int metatileWidth, int metatileHe } } + // load custom oam sequence if provided (needed for validation) + clear_oam_sequence(); + if (oamSequenceFilePath != NULL) { + int cnt = load_oam_sequence_file(oamSequenceFilePath, &g_oam_sequence); + if (cnt < 0) + FATAL_ERROR("Failed to read oam sequence file: %s\n", oamSequenceFilePath); + g_oam_sequence_count = cnt; + } + int tilesHeight = (numTiles + tilesWidth - 1) / tilesWidth; - if (tilesWidth % metatileWidth != 0) - FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)\n", tilesWidth, metatileWidth); + // If an OAM shape is provided, validate by per-metatile tilecount + if (g_oam_tilecount > 0) { + if (numTiles % g_oam_tilecount != 0) + FATAL_ERROR("The number of tiles (%d) is not a multiple of the OAM shape tile count (%d)\n", numTiles, g_oam_tilecount); + + if (tilesWidth % metatileWidth != 0) + FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)\n", tilesWidth, metatileWidth); + + int totalMetatiles = numTiles / g_oam_tilecount; + int metatilesWide = tilesWidth / metatileWidth; + if (totalMetatiles % metatilesWide != 0) + FATAL_ERROR("Tile data contains %d metatiles which is not divisible by metatiles-wide (%d)\n", totalMetatiles, metatilesWide); - if (tilesHeight % metatileHeight != 0) - FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)\n", tilesHeight, metatileHeight); + int metatilesHigh = totalMetatiles / metatilesWide; + tilesHeight = metatilesHigh * metatileHeight; + } else { + if (tilesWidth % metatileWidth != 0) + FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)\n", tilesWidth, metatileWidth); + + if (tilesHeight % metatileHeight != 0) + FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)\n", tilesHeight, metatileHeight); + } image->width = tilesWidth * 8; image->height = tilesHeight * 8; @@ -475,7 +775,7 @@ void ReadTileImage(char *path, int tilesWidth, int metatileWidth, int metatileHe ConvertFromTiles1Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors); break; case 4: - ConvertFromTiles4Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors, oamMap); + ConvertFromTiles4Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors, oamMap, tilesHeight); break; case 8: ConvertFromTiles8Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors); @@ -485,7 +785,7 @@ void ReadTileImage(char *path, int tilesWidth, int metatileWidth, int metatileHe free(buffer); } -void WriteTileImage(char *path, enum NumTilesMode numTilesMode, int numTiles, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors, bool oamMap) +void WriteTileImage(char *path, enum NumTilesMode numTilesMode, int numTiles, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors, bool oamMap, char *oamSequenceFilePath) { int tileSize = image->bitDepth * 8; @@ -498,13 +798,47 @@ void WriteTileImage(char *path, enum NumTilesMode numTilesMode, int numTiles, in int tilesWidth = image->width / 8; int tilesHeight = image->height / 8; - if (tilesWidth % metatileWidth != 0) - FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)\n", tilesWidth, metatileWidth); - - if (tilesHeight % metatileHeight != 0) - FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)\n", tilesHeight, metatileHeight); + // load sequence if provided (so we can validate using per-metatile tilecount) + clear_oam_sequence(); + if (oamSequenceFilePath != NULL) { + int cnt = load_oam_sequence_file(oamSequenceFilePath, &g_oam_sequence); + if (cnt < 0) + FATAL_ERROR("Failed to read oam sequence file: %s\n", oamSequenceFilePath); + g_oam_sequence_count = cnt; + } int maxNumTiles = tilesWidth * tilesHeight; + if (g_oam_png_tiles > 0) { + if (maxNumTiles % g_oam_png_tiles != 0) + FATAL_ERROR("The image contains %d tiles which is not a multiple of the OAM shape pngsize (%d)\n", maxNumTiles, g_oam_png_tiles); + + if (tilesWidth % metatileWidth != 0) + FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)\n", tilesWidth, metatileWidth); + + int totalMetatiles = maxNumTiles / g_oam_png_tiles; + int metatilesWide = tilesWidth / metatileWidth; + if (totalMetatiles % metatilesWide != 0) + FATAL_ERROR("Image supplies %d metatiles which is not divisible by metatiles-wide (%d)\n", totalMetatiles, metatilesWide); + } else if (g_oam_tilecount > 0) { + if (maxNumTiles % g_oam_tilecount != 0) + FATAL_ERROR("The image contains %d tiles which is not a multiple of the OAM shape tile count (%d)\n", maxNumTiles, g_oam_tilecount); + + if (tilesWidth % metatileWidth != 0) + FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)\n", tilesWidth, metatileWidth); + + int totalMetatiles = maxNumTiles / g_oam_tilecount; + int metatilesWide = tilesWidth / metatileWidth; + if (totalMetatiles % metatilesWide != 0) + FATAL_ERROR("Image supplies %d metatiles which is not divisible by metatiles-wide (%d)\n", totalMetatiles, metatilesWide); + } else { + if (tilesWidth % metatileWidth != 0) + FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)\n", tilesWidth, metatileWidth); + + if (tilesHeight % metatileHeight != 0) + FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)\n", tilesHeight, metatileHeight); + } + + if (numTiles == 0) numTiles = maxNumTiles; @@ -513,6 +847,12 @@ void WriteTileImage(char *path, enum NumTilesMode numTilesMode, int numTiles, in int bufferSize = numTiles * tileSize; int maxBufferSize = maxNumTiles * tileSize; + + if (g_oam_png_tiles > 0 && g_oam_tilecount > 0) { + bufferSize = numTiles * tileSize / g_oam_png_tiles * g_oam_tilecount ; + maxBufferSize = maxBufferSize / g_oam_png_tiles * g_oam_tilecount ; + } + unsigned char *buffer = malloc(maxBufferSize); if (buffer == NULL) @@ -520,12 +860,21 @@ void WriteTileImage(char *path, enum NumTilesMode numTilesMode, int numTiles, in int metatilesWide = tilesWidth / metatileWidth; - switch (image->bitDepth) { + // load sequence if provided + clear_oam_sequence(); + if (oamSequenceFilePath != NULL) { + int cnt = load_oam_sequence_file(oamSequenceFilePath, &g_oam_sequence); + if (cnt < 0) + FATAL_ERROR("Failed to read oam sequence file: %s\n", oamSequenceFilePath); + g_oam_sequence_count = cnt; + } + + switch (image->bitDepth) { case 1: ConvertToTiles1Bpp(image->pixels, buffer, maxNumTiles, metatilesWide, metatileWidth, metatileHeight, invertColors); break; case 4: - ConvertToTiles4Bpp(image->pixels, buffer, maxNumTiles, metatilesWide, metatileWidth, metatileHeight, invertColors, oamMap); + ConvertToTiles4Bpp(image->pixels, buffer, maxNumTiles, metatilesWide, metatileWidth, metatileHeight, tilesHeight, invertColors, oamMap); break; case 8: ConvertToTiles8Bpp(image->pixels, buffer, maxNumTiles, metatilesWide, metatileWidth, metatileHeight, invertColors); diff --git a/tools/gbagfx/gfx.h b/tools/gbagfx/gfx.h index 358532d0..5d88d1e3 100755 --- a/tools/gbagfx/gfx.h +++ b/tools/gbagfx/gfx.h @@ -60,8 +60,8 @@ typedef struct { #define MAX_OAM_TILE_SIDE_LENGTH 32 #define MAX_OAM_TILE_SIDE_LENGTH_SQUARED MAX_OAM_TILE_SIDE_LENGTH * MAX_OAM_TILE_SIDE_LENGTH -void ReadTileImage(char *path, int tilesWidth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors, bool optomized_2n_map); -void WriteTileImage(char *path, enum NumTilesMode numTilesMode, int numTiles, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors, bool optomized_2n_map); +void ReadTileImage(char *path, int tilesWidth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors, bool optomized_2n_map, char *oamSequenceFilePath); +void WriteTileImage(char *path, enum NumTilesMode numTilesMode, int numTiles, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors, bool optomized_2n_map, char *oamSequenceFilePath); void ReadPlainImage(char *path, int dataWidth, struct Image *image, bool invertColors); void WritePlainImage(char *path, int dataWidth, struct Image *image, bool invertColors); void FreeImage(struct Image *image); diff --git a/tools/gbagfx/main.c b/tools/gbagfx/main.c index 258cfbff..272ecc11 100755 --- a/tools/gbagfx/main.c +++ b/tools/gbagfx/main.c @@ -68,7 +68,7 @@ void ConvertGbaToPng(char *inputPath, char *outputPath, struct GbaToPngOptions * image.tilemap.size = fileSize; } - ReadTileImage(inputPath, options->width, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette, options->oamSprite); + ReadTileImage(inputPath, options->width, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette, options->oamSprite, options->oamSequenceFilePath); if (image.paletteMap != NULL && image.bitDepth == 4) { Convert4BppImageWithPaletteMap(&image); @@ -97,7 +97,7 @@ void ConvertPngToGba(char *inputPath, char *outputPath, struct PngToGbaOptions * ReadPng(inputPath, &image); if (options->isTiled) - WriteTileImage(outputPath, options->numTilesMode, options->numTiles, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette, options->oamSprite); + WriteTileImage(outputPath, options->numTilesMode, options->numTiles, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette, options->oamSprite, options->oamSequenceFilePath); else WritePlainImage(outputPath, options->dataWidth, &image, !image.hasPalette); @@ -120,6 +120,7 @@ void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **a options.isTiled = true; options.dataWidth = 1; options.oamSprite = false; + options.oamSequenceFilePath = NULL; for (int i = 3; i < argc; i++) { @@ -189,6 +190,27 @@ void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **a else if (strcmp(option, "-oam") == 0) { options.oamSprite = 1; } + else if (strcmp(option, "-oam-seq-file") == 0) { + if (i + 1 >= argc) + FATAL_ERROR("No oam sequence file path following \"-oam-seq-file\".\n"); + + i++; + options.oamSequenceFilePath = argv[i]; + } + else if (strcmp(option, "-oam-shape") == 0 || strcmp(option, "-oamshape") == 0) { + if (i + 1 >= argc) + FATAL_ERROR("No oam shape file path following \"-oam-shape\".\n"); + + i++; + options.oamSequenceFilePath = argv[i]; + } + else if (strcmp(option, "-oam-shape") == 0 || strcmp(option, "-oamshape") == 0) { + if (i + 1 >= argc) + FATAL_ERROR("No oam shape file path following \"-oam-shape\".\n"); + + i++; + options.oamSequenceFilePath = argv[i]; + } else if (strcmp(option, "-tilemap") == 0) { if (i + 1 >= argc) @@ -248,6 +270,7 @@ void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **a options.isTiled = true; options.dataWidth = 1; options.oamSprite = false; + options.oamSequenceFilePath = NULL; for (int i = 3; i < argc; i++) { @@ -301,6 +324,13 @@ void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **a else if (strcmp(option, "-oam") == 0) { options.oamSprite = 1; } + else if (strcmp(option, "-oam-seq-file") == 0) { + if (i + 1 >= argc) + FATAL_ERROR("No oam sequence file path following \"-oam-seq-file\".\n"); + + i++; + options.oamSequenceFilePath = argv[i]; + } else if (strcmp(option, "-plain") == 0) { options.isTiled = false; diff --git a/tools/gbagfx/oam_slices/oam_slicer.c b/tools/gbagfx/oam_slices/oam_slicer.c index c1fed5f5..f99adc6e 100644 --- a/tools/gbagfx/oam_slices/oam_slicer.c +++ b/tools/gbagfx/oam_slices/oam_slicer.c @@ -14,14 +14,22 @@ const struct OamShape oamShapes[] = { {1, 4, OAM_SIZE_1, OAM_V_RECTANGLE}, {1, 2, OAM_SIZE_0, OAM_V_RECTANGLE}, {1, 1, OAM_SIZE_0, OAM_SQUARE}, }; -// Define manual overrides for specific sprite dimentions, which were observed using a different sequencing -// than the standard algorithm would produce. +bool is_valid_oam_shape(int width, int height) { + for (int i = 0; i < sizeof(oamShapes)/sizeof(oamShapes[0]); ++i) { + if (oamShapes[i].width == width && oamShapes[i].height == height) + return true; + } + return false; +} + +// Define manual overrides for specific sprite dimentions, which were observed using a different sequencing +// than the standard algorithm would produce. const struct OamOverride oamShapeOverrides[] = { { 13, 8, 5, (const struct OamOverrideSegment[]) { //2 8x4 segments, rather than a single 8x8 - {0, 0, 8, 4}, {0, 4, 8, 4}, {8, 0, 4, 8}, {12, 0, 1, 4}, {12, 4, 1, 4} + {0, 0, 8, 4, false}, {0, 4, 8, 4, false}, {8, 0, 4, 8, false}, {12, 0, 1, 4, false}, {12, 4, 1, 4, false} }}, { 6, 3, 4, (const struct OamOverrideSegment[]) { //ordering of middle 2 segments - {0,0,4,2}, {0,2,4,1}, {4,0,2,2}, {4,2,2,1} + {0,0,4,2,false}, {0,2,4,1,false}, {4,0,2,2,false}, {4,2,2,1,false} }}, }; @@ -30,13 +38,14 @@ static bool check_oam_shape_fits(int x, int y, int width, int height, int shapeW return (x + shapeW <= width && y + shapeH <= height); } -// Helper to allocate new segment -static struct OamSegment *new_oam_segment(int x, int y, int w, int h) { +// Helper to allocate new segment (spacer marks a region that has no tile data) +static struct OamSegment *new_oam_segment(int x, int y, int w, int h, bool spacer) { struct OamSegment *seg = malloc(sizeof(struct OamSegment)); seg->offX = x; seg->offY = y; seg->width = w; seg->height = h; + seg->spacer = spacer; seg->next = NULL; return seg; } @@ -48,7 +57,7 @@ static struct OamSegment *check_oam_manual_override(int width, int height) { struct OamSegment *head = NULL, **tail = &head; for (int j = 0; j < ovr->numSegments; ++j) { const struct OamOverrideSegment *seg = &ovr->segments[j]; - *tail = new_oam_segment(seg->offX, seg->offY, seg->width, seg->height); + *tail = new_oam_segment(seg->offX, seg->offY, seg->width, seg->height, seg->spacer); tail = &((*tail)->next); } return head; @@ -90,7 +99,7 @@ static OamSegment *slice_sprite_to_oam(int width, int height) { used[y + dy][x + dx] = true; // Add segment - *tail = new_oam_segment(x, y, shape.width, shape.height); + *tail = new_oam_segment(x, y, shape.width, shape.height, false); tail = &((*tail)->next); break; } @@ -102,6 +111,48 @@ static OamSegment *slice_sprite_to_oam(int width, int height) { return head; } +int getOamTileIndexEx(int width, int height, TileCoord *tileCoordinates, const struct OamOverrideSegment *sequence, int numSequenceSegments, struct OamSegment **outSegments) { + int tileIndex = 0; + + struct OamSegment *segments = NULL; + + if (sequence != NULL && numSequenceSegments > 0) { + struct OamSegment *head = NULL, **tail = &head; + for (int j = 0; j < numSequenceSegments; ++j) { + const struct OamOverrideSegment *seg = &sequence[j]; + *tail = new_oam_segment(seg->offX, seg->offY, seg->width, seg->height, seg->spacer); + tail = &((*tail)->next); + } + segments = head; + } else { + segments = check_oam_manual_override(width, height); + if (segments == NULL) { + segments = slice_sprite_to_oam(width, height); + } + } + + struct OamSegment *segment = segments; + while (segment) { + if (!segment->spacer) { + for (int y = 0; y < segment->height; ++y) { + for (int x = 0; x < segment->width; ++x) { + tileCoordinates[tileIndex].x = segment->offX + x; + tileCoordinates[tileIndex].y = segment->offY + y; + ++tileIndex; + } + } + } + segment = segment->next; + } + + if (outSegments) + *outSegments = segments; + else + free_oam_segments(segments); + + return tileIndex; +} + void print_oam_segments(struct OamSegment *segList) { printf("OAM Segments: "); while (segList) { @@ -113,29 +164,8 @@ void print_oam_segments(struct OamSegment *segList) { } int getOamTileIndex(int width, int height, TileCoord *tileCoordinates) { - int tileIndex = 0; - int tileCount = width * height; - - struct OamSegment *segments; - segments = check_oam_manual_override(width, height); - if (segments == NULL) { - segments = slice_sprite_to_oam(width, height); - } - - struct OamSegment *segment = segments; - while (segment) { - for (int y = 0; y < segment->height; ++y) { - for (int x = 0; x < segment->width; ++x) { - tileCoordinates[tileIndex].x = segment->offX + x; - tileCoordinates[tileIndex].y = segment->offY + y; - ++tileIndex; - } - } - segment = segment->next; - } - - free_oam_segments(segments); - return tileCount; + // legacy wrapper: no custom sequence provided + return getOamTileIndexEx(width, height, tileCoordinates, NULL, 0, NULL); } void free_oam_segments(struct OamSegment *segList) { diff --git a/tools/gbagfx/oam_slices/oam_slicer.h b/tools/gbagfx/oam_slices/oam_slicer.h index a173cb30..ddf0a1df 100644 --- a/tools/gbagfx/oam_slices/oam_slicer.h +++ b/tools/gbagfx/oam_slices/oam_slicer.h @@ -1,6 +1,7 @@ #ifndef GUARD_OAM_SLICER_H #define GUARD_OAM_SLICER_H #include "../gfx.h" +#include enum OamShapeEnum { OAM_SQUARE = 0, @@ -27,6 +28,7 @@ typedef struct OamOverrideSegment { unsigned short offY; unsigned short width; unsigned short height; + bool spacer; } OamOverrideSegment; typedef struct OamOverride { @@ -42,11 +44,21 @@ typedef struct OamSegment { short offY; unsigned short width; // in tiles unsigned short height; // in tiles + bool spacer; // indicates this segment represents a spacer (no tile data) } OamSegment; void print_oam_segments(struct OamSegment *segList); void free_oam_segments(struct OamSegment *segList); int getOamTileIndex(int width, int height, TileCoord *tileCoordinates); +// Extended variant: provide an explicit sequence of segments (optional). +// If sequence==NULL, behaves like getOamTileIndex. +// Extended variant: fills `tileCoordinates` with the full ordering (including spacer positions). +// If `sequence==NULL` behaves like getOamTileIndex. If `outSpacerMask` is non-NULL it will be filled +// with `width*height` boolean entries marking which positions are spacers. +int getOamTileIndexEx(int width, int height, TileCoord *tileCoordinates, const OamOverrideSegment *sequence, int numSequenceSegments, struct OamSegment **outSegments); + +// Validate whether a WxH tile piece is a valid OAM shape +bool is_valid_oam_shape(int width, int height); void PrintTileCoords(TileCoord *tileSequence, int tile_count); #endif // GUARD_OAM_SLICER_H \ No newline at end of file diff --git a/tools/gbagfx/options.h b/tools/gbagfx/options.h index 66b86f27..1b6a0d6b 100755 --- a/tools/gbagfx/options.h +++ b/tools/gbagfx/options.h @@ -20,6 +20,7 @@ struct GbaToPngOptions { bool isTiled; int dataWidth; bool oamSprite; + char *oamSequenceFilePath; }; struct PngToGbaOptions { @@ -33,6 +34,7 @@ struct PngToGbaOptions { bool isTiled; int dataWidth; bool oamSprite; + char *oamSequenceFilePath; }; #endif // OPTIONS_H From 87c777419a82261bf7b00ad50217e22ddea5a4a5 Mon Sep 17 00:00:00 2001 From: Retnuhytnuob Date: Thu, 26 Feb 2026 05:13:09 -0600 Subject: [PATCH 02/10] Refactored --- tools/gbagfx/gfx.c | 244 ++++----------------------- tools/gbagfx/oam_slices/oam_slicer.c | 163 ++++++++++++++++++ tools/gbagfx/oam_slices/oam_slicer.h | 16 ++ 3 files changed, 214 insertions(+), 209 deletions(-) diff --git a/tools/gbagfx/gfx.c b/tools/gbagfx/gfx.c index e748f004..7f4d4e88 100755 --- a/tools/gbagfx/gfx.c +++ b/tools/gbagfx/gfx.c @@ -10,179 +10,6 @@ #include "util.h" #include "oam_slices/oam_slicer.h" -static struct OamOverrideSegment *g_oam_sequence = NULL; -static int g_oam_sequence_count = 0; -static int g_oam_tilecount = 0; -static int g_oam_png_tiles = 0; - -static void clear_oam_sequence(void) { - if (g_oam_sequence) { - free(g_oam_sequence); - g_oam_sequence = NULL; - g_oam_sequence_count = 0; - g_oam_tilecount = 0; - g_oam_png_tiles = 0; - } -} - -// Load oam sequence file. -// JSON matching the schema provided by the user. -// Returns number of segments, 0 if none, or -1 on IO/error. -static int load_oam_sequence_file(char *path, struct OamOverrideSegment **outSeq) { - if (path == NULL) return 0; - int fileSize; - unsigned char *buf = ReadWholeFile(path, &fileSize); - if (buf == NULL) return -1; - - // skip leading whitespace - char *p = (char*)buf; - char *end = (char*)buf + fileSize; - while (p < end && (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')) ++p; - - // If JSON (starts with '{'), parse JSON-ish format for our schema - if (p < end && *p == '{') { - // Minimal JSON parsing for expected keys: pngsize, tilecount, pieces - int tilecount = -1; - - // find "tilecount" - char *tok = strstr(p, "\"tilecount\""); - if (tok) { - char *col = strchr(tok, ':'); - if (col) tilecount = atoi(col + 1); - } - - // find "pngsize" - tok = strstr(p, "\"pngsize\""); - int png_w = -1, png_h = -1; - if (tok) { - char *col = strchr(tok, ':'); - if (col) { - char *quote1 = strchr(col, '"'); - if (quote1) { - char *quote2 = strchr(quote1 + 1, '"'); - if (quote2) { - int a, b; - if (sscanf(quote1 + 1, "%d x %d", &a, &b) == 2 || sscanf(quote1 + 1, "%dx%d", &a, &b) == 2) { - png_w = a; - png_h = b; - g_oam_png_tiles = png_w * png_h; - } - } - } - } - } - - // find pieces array - tok = strstr(p, "\"pieces\""); - if (!tok) { - free(buf); - *outSeq = NULL; - return -1; - } - - char *arrstart = strchr(tok, '['); - if (!arrstart) { free(buf); *outSeq = NULL; return -1; } - - // allocate temporary list of segments up to a reasonable bound (count braces) - int maxSegments = 0; - for (char *q = arrstart; q < end; ++q) if (*q == '{') ++maxSegments; - if (maxSegments == 0) { free(buf); *outSeq = NULL; return 0; } - - struct OamOverrideSegment *arr = malloc(sizeof(struct OamOverrideSegment) * maxSegments); - int idx = 0; - - char *q = arrstart; - while (q < end) { - char *obj = strchr(q, '{'); - if (!obj) break; - char *objend = strchr(obj, '}'); - if (!objend) break; - - // find pos - int offX = 0, offY = 0, w = 0, h = 0; - int spacer = 0; - char *posTok = strstr(obj, "\"pos\""); - if (posTok && posTok < objend) { - char *col = strchr(posTok, ':'); - if (col) { - char *q1 = strchr(col, '"'); - if (q1 && q1 < objend) { - char *q2 = strchr(q1 + 1, '"'); - if (q2 && q2 < objend) { - sscanf(q1 + 1, "%d,%d", &offX, &offY); - } - } - } - } - - // find size - char *sizeTok = strstr(obj, "\"size\""); - if (sizeTok && sizeTok < objend) { - char *col = strchr(sizeTok, ':'); - if (col) { - char *q1 = strchr(col, '"'); - if (q1 && q1 < objend) { - char *q2 = strchr(q1 + 1, '"'); - if (q2 && q2 < objend) { - if (sscanf(q1 + 1, "%d x %d", &w, &h) != 2) { - sscanf(q1 + 1, "%dx%d", &w, &h); - } - } - } - } - } - - // find spacer (optional) - char *spTok = strstr(obj, "\"spacer\""); - if (spTok && spTok < objend) { - char *col = strchr(spTok, ':'); - if (col) { - if (strstr(col, "true") != NULL) spacer = 1; - else spacer = atoi(col + 1); - } - } - - // validate non-spacer shapes - if (!spacer) { - if (!is_valid_oam_shape(w, h)) { - free(arr); - free(buf); - *outSeq = NULL; - return -1; - } - } - - arr[idx].offX = (unsigned short)offX; - arr[idx].offY = (unsigned short)offY; - arr[idx].width = (unsigned short)w; - arr[idx].height = (unsigned short)h; - arr[idx].spacer = spacer ? true : false; - ++idx; - - q = objend + 1; - } - - // compute tilecount if missing: sum of non-spacer w*h - if (tilecount < 0) { - int sum = 0; - for (int i = 0; i < idx; ++i) if (!arr[i].spacer) sum += arr[i].width * arr[i].height; - tilecount = sum; - } - - // store computed tilecount globally for validation - g_oam_tilecount = tilecount; - - free(buf); - if (idx == 0) { free(arr); *outSeq = NULL; return 0; } - *outSeq = arr; - return idx; - } - - free(buf); - *outSeq = NULL; - return -1; -} - #define GET_GBA_PAL_RED(x) (((x) >> 0) & 0x1F) #define GET_GBA_PAL_GREEN(x) (((x) >> 5) & 0x1F) #define GET_GBA_PAL_BLUE(x) (((x) >> 10) & 0x1F) @@ -253,7 +80,7 @@ static void ConvertFromTiles1Bpp(unsigned char *src, unsigned char *dest, int nu } } -static void ConvertFromTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors, bool oamMap, int tilesHeight) +static void ConvertFromTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors, bool oamMap, int tilesHeight, OamSequence *oamSeq) { int subTileX = 0; int subTileY = 0; @@ -267,7 +94,10 @@ static void ConvertFromTiles4Bpp(unsigned char *src, unsigned char *dest, int nu if (oamMap) { - tileCount = getOamTileIndexEx(metatileWidth, metatileHeight, tileMappingCoord, g_oam_sequence, g_oam_sequence_count, &oamSegments); + if (oamSeq) + tileCount = getOamTileIndexEx(metatileWidth, metatileHeight, tileMappingCoord, oamSeq->segments, oamSeq->numSegments, &oamSegments); + else + tileCount = getOamTileIndexEx(metatileWidth, metatileHeight, tileMappingCoord, NULL, 0, &oamSegments); } /* If using OAM mapping, start placement at the first mapped coordinate so @@ -430,7 +260,7 @@ void Convert4BppImageWithPaletteMap(struct Image *image) image->bitDepth = 8; } -static void ConvertToTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, int tilesHeight, bool invertColors, bool oamMap) +static void ConvertToTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, int tilesHeight, bool invertColors, bool oamMap, OamSequence *oamSeq) { int subTileX = 0; int subTileY = 0; @@ -467,7 +297,10 @@ static void ConvertToTiles4Bpp(unsigned char *src, unsigned char *dest, int numT struct OamSegment *segments = NULL; TileCoord dummy[MAX_OAM_TILE_SIDE_LENGTH_SQUARED]; // getOamTileIndexEx returns the compressed mapping for one metatile and the segments list - getOamTileIndexEx(metatileWidth, metatileHeight, dummy, g_oam_sequence, g_oam_sequence_count, &segments); + if (oamSeq) + getOamTileIndexEx(metatileWidth, metatileHeight, dummy, oamSeq->segments, oamSeq->numSegments, &segments); + else + getOamTileIndexEx(metatileWidth, metatileHeight, dummy, NULL, 0, &segments); int metatilesHigh = tilesHeight / metatileHeight; int totalTiles = numTiles; @@ -728,25 +561,24 @@ void ReadTileImage(char *path, int tilesWidth, int metatileWidth, int metatileHe } // load custom oam sequence if provided (needed for validation) - clear_oam_sequence(); + OamSequence *oamSeq = NULL; if (oamSequenceFilePath != NULL) { - int cnt = load_oam_sequence_file(oamSequenceFilePath, &g_oam_sequence); + int cnt = oam_sequence_load_from_file(oamSequenceFilePath, &oamSeq); if (cnt < 0) FATAL_ERROR("Failed to read oam sequence file: %s\n", oamSequenceFilePath); - g_oam_sequence_count = cnt; } int tilesHeight = (numTiles + tilesWidth - 1) / tilesWidth; // If an OAM shape is provided, validate by per-metatile tilecount - if (g_oam_tilecount > 0) { - if (numTiles % g_oam_tilecount != 0) - FATAL_ERROR("The number of tiles (%d) is not a multiple of the OAM shape tile count (%d)\n", numTiles, g_oam_tilecount); + if (oamSeq && oamSeq->tilecount > 0) { + if (numTiles % oamSeq->tilecount != 0) + FATAL_ERROR("The number of tiles (%d) is not a multiple of the OAM shape tile count (%d)\n", numTiles, oamSeq->tilecount); if (tilesWidth % metatileWidth != 0) FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)\n", tilesWidth, metatileWidth); - int totalMetatiles = numTiles / g_oam_tilecount; + int totalMetatiles = numTiles / oamSeq->tilecount; int metatilesWide = tilesWidth / metatileWidth; if (totalMetatiles % metatilesWide != 0) FATAL_ERROR("Tile data contains %d metatiles which is not divisible by metatiles-wide (%d)\n", totalMetatiles, metatilesWide); @@ -775,7 +607,7 @@ void ReadTileImage(char *path, int tilesWidth, int metatileWidth, int metatileHe ConvertFromTiles1Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors); break; case 4: - ConvertFromTiles4Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors, oamMap, tilesHeight); + ConvertFromTiles4Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors, oamMap, tilesHeight, oamSeq); break; case 8: ConvertFromTiles8Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors); @@ -783,6 +615,7 @@ void ReadTileImage(char *path, int tilesWidth, int metatileWidth, int metatileHe } free(buffer); + if (oamSeq) oam_sequence_free(oamSeq); } void WriteTileImage(char *path, enum NumTilesMode numTilesMode, int numTiles, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors, bool oamMap, char *oamSequenceFilePath) @@ -799,34 +632,33 @@ void WriteTileImage(char *path, enum NumTilesMode numTilesMode, int numTiles, in int tilesHeight = image->height / 8; // load sequence if provided (so we can validate using per-metatile tilecount) - clear_oam_sequence(); + OamSequence *oamSeq = NULL; if (oamSequenceFilePath != NULL) { - int cnt = load_oam_sequence_file(oamSequenceFilePath, &g_oam_sequence); + int cnt = oam_sequence_load_from_file(oamSequenceFilePath, &oamSeq); if (cnt < 0) FATAL_ERROR("Failed to read oam sequence file: %s\n", oamSequenceFilePath); - g_oam_sequence_count = cnt; } int maxNumTiles = tilesWidth * tilesHeight; - if (g_oam_png_tiles > 0) { - if (maxNumTiles % g_oam_png_tiles != 0) - FATAL_ERROR("The image contains %d tiles which is not a multiple of the OAM shape pngsize (%d)\n", maxNumTiles, g_oam_png_tiles); + if (oamSeq && oamSeq->png_tiles > 0) { + if (maxNumTiles % oamSeq->png_tiles != 0) + FATAL_ERROR("The image contains %d tiles which is not a multiple of the OAM shape pngsize (%d)\n", maxNumTiles, oamSeq->png_tiles); if (tilesWidth % metatileWidth != 0) FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)\n", tilesWidth, metatileWidth); - int totalMetatiles = maxNumTiles / g_oam_png_tiles; + int totalMetatiles = maxNumTiles / oamSeq->png_tiles; int metatilesWide = tilesWidth / metatileWidth; if (totalMetatiles % metatilesWide != 0) FATAL_ERROR("Image supplies %d metatiles which is not divisible by metatiles-wide (%d)\n", totalMetatiles, metatilesWide); - } else if (g_oam_tilecount > 0) { - if (maxNumTiles % g_oam_tilecount != 0) - FATAL_ERROR("The image contains %d tiles which is not a multiple of the OAM shape tile count (%d)\n", maxNumTiles, g_oam_tilecount); + } else if (oamSeq && oamSeq->tilecount > 0) { + if (maxNumTiles % oamSeq->tilecount != 0) + FATAL_ERROR("The image contains %d tiles which is not a multiple of the OAM shape tile count (%d)\n", maxNumTiles, oamSeq->tilecount); if (tilesWidth % metatileWidth != 0) FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)\n", tilesWidth, metatileWidth); - int totalMetatiles = maxNumTiles / g_oam_tilecount; + int totalMetatiles = maxNumTiles / oamSeq->tilecount; int metatilesWide = tilesWidth / metatileWidth; if (totalMetatiles % metatilesWide != 0) FATAL_ERROR("Image supplies %d metatiles which is not divisible by metatiles-wide (%d)\n", totalMetatiles, metatilesWide); @@ -848,9 +680,10 @@ void WriteTileImage(char *path, enum NumTilesMode numTilesMode, int numTiles, in int bufferSize = numTiles * tileSize; int maxBufferSize = maxNumTiles * tileSize; - if (g_oam_png_tiles > 0 && g_oam_tilecount > 0) { - bufferSize = numTiles * tileSize / g_oam_png_tiles * g_oam_tilecount ; - maxBufferSize = maxBufferSize / g_oam_png_tiles * g_oam_tilecount ; + // Caused by differences in the input and output size, due to 'spacer' tiles. + if (oamSeq && oamSeq->png_tiles > 0 && oamSeq->tilecount > 0) { + bufferSize = numTiles * tileSize / oamSeq->png_tiles * oamSeq->tilecount ; + maxBufferSize = maxBufferSize / oamSeq->png_tiles * oamSeq->tilecount ; } unsigned char *buffer = malloc(maxBufferSize); @@ -860,21 +693,12 @@ void WriteTileImage(char *path, enum NumTilesMode numTilesMode, int numTiles, in int metatilesWide = tilesWidth / metatileWidth; - // load sequence if provided - clear_oam_sequence(); - if (oamSequenceFilePath != NULL) { - int cnt = load_oam_sequence_file(oamSequenceFilePath, &g_oam_sequence); - if (cnt < 0) - FATAL_ERROR("Failed to read oam sequence file: %s\n", oamSequenceFilePath); - g_oam_sequence_count = cnt; - } - switch (image->bitDepth) { case 1: ConvertToTiles1Bpp(image->pixels, buffer, maxNumTiles, metatilesWide, metatileWidth, metatileHeight, invertColors); break; case 4: - ConvertToTiles4Bpp(image->pixels, buffer, maxNumTiles, metatilesWide, metatileWidth, metatileHeight, tilesHeight, invertColors, oamMap); + ConvertToTiles4Bpp(image->pixels, buffer, maxNumTiles, metatilesWide, metatileWidth, metatileHeight, tilesHeight, invertColors, oamMap, oamSeq); break; case 8: ConvertToTiles8Bpp(image->pixels, buffer, maxNumTiles, metatilesWide, metatileWidth, metatileHeight, invertColors); @@ -903,6 +727,8 @@ void WriteTileImage(char *path, enum NumTilesMode numTilesMode, int numTiles, in WriteWholeFile(path, buffer, zeroPadded ? bufferSize : maxBufferSize); free(buffer); + + if (oamSeq) oam_sequence_free(oamSeq); } void ReadPlainImage(char *path, int dataWidth, struct Image *image, bool invertColors) diff --git a/tools/gbagfx/oam_slices/oam_slicer.c b/tools/gbagfx/oam_slices/oam_slicer.c index f99adc6e..1401ad53 100644 --- a/tools/gbagfx/oam_slices/oam_slicer.c +++ b/tools/gbagfx/oam_slices/oam_slicer.c @@ -1,10 +1,173 @@ #include #include #include +#include #include "../gfx.h" #include "oam_slicer.h" +#include "../util.h" +// Load oam sequence file. +// JSON matching the schema provided by the user. +// Returns number of segments, 0 if none, or -1 on IO/error. +int oam_sequence_load_from_file(const char *path, OamSequence **outSeq) { + if (path == NULL) return 0; + int fileSize; + unsigned char *buf = ReadWholeFile((char*)path, &fileSize); + if (buf == NULL) return -1; + + // skip leading whitespace + char *p = (char*)buf; + char *end = (char*)buf + fileSize; + while (p < end && (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')) ++p; + + // If JSON (starts with '{'), parse JSON-ish format for our schema + if (p < end && *p == '{') { + int tilecount = -1; + + // find "tilecount" + char *tok = strstr(p, "\"tilecount\""); + if (tok) { + char *col = strchr(tok, ':'); + if (col) tilecount = atoi(col + 1); + } + + // find "pngsize" + tok = strstr(p, "\"pngsize\""); + int png_w = -1, png_h = -1; + int png_tiles = 0; + if (tok) { + char *col = strchr(tok, ':'); + if (col) { + char *quote1 = strchr(col, '"'); + if (quote1) { + char *quote2 = strchr(quote1 + 1, '"'); + if (quote2) { + int a, b; + if (sscanf(quote1 + 1, "%d x %d", &a, &b) == 2 || sscanf(quote1 + 1, "%dx%d", &a, &b) == 2) { + png_w = a; + png_h = b; + png_tiles = png_w * png_h; + } + } + } + } + } + + // find pieces array + tok = strstr(p, "\"pieces\""); + if (!tok) { + free(buf); + *outSeq = NULL; + return -1; + } + + char *arrstart = strchr(tok, '['); + if (!arrstart) { free(buf); *outSeq = NULL; return -1; } + + int maxSegments = 0; + for (char *q = arrstart; q < end; ++q) if (*q == '{') ++maxSegments; + if (maxSegments == 0) { free(buf); *outSeq = NULL; return 0; } + + struct OamOverrideSegment *arr = malloc(sizeof(struct OamOverrideSegment) * maxSegments); + int idx = 0; + + char *q = arrstart; + while (q < end) { + char *obj = strchr(q, '{'); + if (!obj) break; + char *objend = strchr(obj, '}'); + if (!objend) break; + + int offX = 0, offY = 0, w = 0, h = 0; + int spacer = 0; + char *posTok = strstr(obj, "\"pos\""); + if (posTok && posTok < objend) { + char *col = strchr(posTok, ':'); + if (col) { + char *q1 = strchr(col, '"'); + if (q1 && q1 < objend) { + char *q2 = strchr(q1 + 1, '"'); + if (q2 && q2 < objend) { + sscanf(q1 + 1, "%d,%d", &offX, &offY); + } + } + } + } + + char *sizeTok = strstr(obj, "\"size\""); + if (sizeTok && sizeTok < objend) { + char *col = strchr(sizeTok, ':'); + if (col) { + char *q1 = strchr(col, '"'); + if (q1 && q1 < objend) { + char *q2 = strchr(q1 + 1, '"'); + if (q2 && q2 < objend) { + if (sscanf(q1 + 1, "%d x %d", &w, &h) != 2) { + sscanf(q1 + 1, "%dx%d", &w, &h); + } + } + } + } + } + + char *spTok = strstr(obj, "\"spacer\""); + if (spTok && spTok < objend) { + char *col = strchr(spTok, ':'); + if (col) { + if (strstr(col, "true") != NULL) spacer = 1; + else spacer = atoi(col + 1); + } + } + + if (!spacer) { + if (!is_valid_oam_shape(w, h)) { + free(arr); + free(buf); + *outSeq = NULL; + return -1; + } + } + + arr[idx].offX = (unsigned short)offX; + arr[idx].offY = (unsigned short)offY; + arr[idx].width = (unsigned short)w; + arr[idx].height = (unsigned short)h; + arr[idx].spacer = spacer ? true : false; + ++idx; + + q = objend + 1; + } + + if (tilecount < 0) { + int sum = 0; + for (int i = 0; i < idx; ++i) if (!arr[i].spacer) sum += arr[i].width * arr[i].height; + tilecount = sum; + } + + OamSequence *seq = malloc(sizeof(OamSequence)); + seq->segments = arr; + seq->numSegments = idx; + seq->tilecount = tilecount; + seq->png_tiles = png_tiles; + + free(buf); + if (idx == 0) { free(arr); free(seq); *outSeq = NULL; return 0; } + *outSeq = seq; + return idx; + } + + free(buf); + *outSeq = NULL; + return -1; +} + +void oam_sequence_free(OamSequence *seq) { + if (!seq) return; + if (seq->segments) free(seq->segments); + free(seq); +} + // Define the available OAM shapes. // Ordered by the width, then height. const struct OamShape oamShapes[] = { diff --git a/tools/gbagfx/oam_slices/oam_slicer.h b/tools/gbagfx/oam_slices/oam_slicer.h index ddf0a1df..2b54be72 100644 --- a/tools/gbagfx/oam_slices/oam_slicer.h +++ b/tools/gbagfx/oam_slices/oam_slicer.h @@ -47,6 +47,22 @@ typedef struct OamSegment { bool spacer; // indicates this segment represents a spacer (no tile data) } OamSegment; +// Represents a parsed OAM sequence file. Contains an array of override +// segments (as declared above), the number of segments, and metadata +// parsed from the file (tilecount and pngsize in tiles). +typedef struct OamSequence { + struct OamOverrideSegment *segments; + int numSegments; + int tilecount; // sum of non-spacer tiles (or -1 if not provided) + int png_tiles; // pngsize in tiles (w*h) if provided, 0 otherwise +} OamSequence; + +// Load an OAM sequence from a JSON file. Returns number of segments (>0), +// 0 if none, or -1 on IO/error. On success *outSeq will be a heap-allocated +// OamSequence that must be freed with oam_sequence_free(). +int oam_sequence_load_from_file(const char *path, OamSequence **outSeq); +void oam_sequence_free(OamSequence *seq); + void print_oam_segments(struct OamSegment *segList); void free_oam_segments(struct OamSegment *segList); int getOamTileIndex(int width, int height, TileCoord *tileCoordinates); From f62b50ef735a17515568b2452d48a4458dcc8912 Mon Sep 17 00:00:00 2001 From: Retnuhytnuob Date: Sat, 28 Feb 2026 02:11:45 -0600 Subject: [PATCH 03/10] groudon simplification --- data/rom_1.s | 3 +- graphics/stage/groudon/boulder_shape.json | 24 +++ graphics/stage/groudon/boulders.png | Bin 0 -> 4043 bytes graphics/stage/groudon/crystal_shape.json | 24 +++ .../stage/groudon/groudon_head_shape.json | 42 ++++ graphics/stage/groudon/groudon_stage_gfx.json | 182 ++++-------------- .../stage/groudon/intro_sprite_boulders.png | Bin 0 -> 299 bytes .../groudon/intro_sprite_crystal_1_bottom.png | Bin 100 -> 0 bytes .../groudon/intro_sprite_crystal_1_left.png | Bin 106 -> 0 bytes .../intro_sprite_crystal_1_top_right.png | Bin 163 -> 0 bytes .../groudon/intro_sprite_crystal_2_bottom.png | Bin 100 -> 0 bytes .../groudon/intro_sprite_crystal_2_left.png | Bin 106 -> 0 bytes .../intro_sprite_crystal_2_top_right.png | Bin 165 -> 0 bytes .../groudon/intro_sprite_crystal_3_bottom.png | Bin 100 -> 0 bytes .../groudon/intro_sprite_crystal_3_left.png | Bin 106 -> 0 bytes .../intro_sprite_crystal_3_top_right.png | Bin 165 -> 0 bytes .../stage/groudon/intro_sprite_crystals.png | Bin 0 -> 487 bytes .../stage/groudon/intro_sprite_face_turn.png | Bin 0 -> 623 bytes .../stage/groudon/intro_sprite_face_yell.png | Bin 0 -> 401 bytes .../groudon/intro_sprite_groudon_chin.png | Bin 102 -> 0 bytes .../groudon/intro_sprite_groudon_chin_2.png | Bin 108 -> 0 bytes .../groudon/intro_sprite_groudon_chin_3.png | Bin 112 -> 0 bytes .../groudon/intro_sprite_groudon_face.png | Bin 232 -> 0 bytes .../groudon/intro_sprite_groudon_face_2.png | Bin 231 -> 0 bytes .../groudon/intro_sprite_groudon_face_3.png | Bin 223 -> 0 bytes .../groudon/intro_sprite_groudon_fin_3.png | Bin 98 -> 0 bytes .../groudon/intro_sprite_groudon_fins.png | Bin 98 -> 0 bytes .../groudon/intro_sprite_groudon_fins_2.png | Bin 98 -> 0 bytes .../intro_sprite_groudon_profile_chin.png | Bin 95 -> 0 bytes .../intro_sprite_groudon_profile_face.png | Bin 213 -> 0 bytes .../intro_sprite_groudon_profile_fin.png | Bin 105 -> 0 bytes .../intro_sprite_groudon_quarter_chin.png | Bin 111 -> 0 bytes .../intro_sprite_groudon_quarter_face.png | Bin 221 -> 0 bytes .../intro_sprite_groudon_quarter_fin.png | Bin 100 -> 0 bytes .../intro_sprite_groudon_yell_chin.png | Bin 93 -> 0 bytes .../intro_sprite_groudon_yell_chin_2.png | Bin 93 -> 0 bytes .../intro_sprite_groudon_yell_face.png | Bin 238 -> 0 bytes .../intro_sprite_groudon_yell_face_2.png | Bin 239 -> 0 bytes .../groudon/intro_sprite_groudon_yell_fin.png | Bin 97 -> 0 bytes .../intro_sprite_groudon_yell_fin_2.png | Bin 93 -> 0 bytes .../stage/groudon/intro_sprite_head_1.png | Bin 0 -> 308 bytes .../stage/groudon/intro_sprite_head_2.png | Bin 0 -> 311 bytes .../groudon/intro_sprite_rock_1_left.png | Bin 77 -> 0 bytes .../groudon/intro_sprite_rock_1_right.png | Bin 261 -> 0 bytes .../stage/groudon/intro_sprite_rock_1_top.png | Bin 72 -> 0 bytes .../groudon/intro_sprite_rock_2_left.png | Bin 77 -> 0 bytes .../groudon/intro_sprite_rock_2_right.png | Bin 261 -> 0 bytes .../stage/groudon/intro_sprite_rock_2_top.png | Bin 72 -> 0 bytes .../groudon/intro_sprite_rock_3_left.png | Bin 77 -> 0 bytes .../groudon/intro_sprite_rock_3_right.png | Bin 261 -> 0 bytes .../stage/groudon/intro_sprite_rock_3_top.png | Bin 72 -> 0 bytes tools/scripts/generate_graphics_rules.sh | 11 +- 52 files changed, 139 insertions(+), 147 deletions(-) create mode 100644 graphics/stage/groudon/boulder_shape.json create mode 100644 graphics/stage/groudon/boulders.png create mode 100644 graphics/stage/groudon/crystal_shape.json create mode 100644 graphics/stage/groudon/groudon_head_shape.json create mode 100644 graphics/stage/groudon/intro_sprite_boulders.png delete mode 100644 graphics/stage/groudon/intro_sprite_crystal_1_bottom.png delete mode 100644 graphics/stage/groudon/intro_sprite_crystal_1_left.png delete mode 100644 graphics/stage/groudon/intro_sprite_crystal_1_top_right.png delete mode 100644 graphics/stage/groudon/intro_sprite_crystal_2_bottom.png delete mode 100644 graphics/stage/groudon/intro_sprite_crystal_2_left.png delete mode 100644 graphics/stage/groudon/intro_sprite_crystal_2_top_right.png delete mode 100644 graphics/stage/groudon/intro_sprite_crystal_3_bottom.png delete mode 100644 graphics/stage/groudon/intro_sprite_crystal_3_left.png delete mode 100644 graphics/stage/groudon/intro_sprite_crystal_3_top_right.png create mode 100644 graphics/stage/groudon/intro_sprite_crystals.png create mode 100644 graphics/stage/groudon/intro_sprite_face_turn.png create mode 100644 graphics/stage/groudon/intro_sprite_face_yell.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_chin.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_chin_2.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_chin_3.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_face.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_face_2.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_face_3.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_fin_3.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_fins.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_fins_2.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_profile_chin.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_profile_face.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_profile_fin.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_quarter_chin.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_quarter_face.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_quarter_fin.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_yell_chin.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_yell_chin_2.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_yell_face.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_yell_face_2.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_yell_fin.png delete mode 100644 graphics/stage/groudon/intro_sprite_groudon_yell_fin_2.png create mode 100644 graphics/stage/groudon/intro_sprite_head_1.png create mode 100644 graphics/stage/groudon/intro_sprite_head_2.png delete mode 100644 graphics/stage/groudon/intro_sprite_rock_1_left.png delete mode 100644 graphics/stage/groudon/intro_sprite_rock_1_right.png delete mode 100644 graphics/stage/groudon/intro_sprite_rock_1_top.png delete mode 100644 graphics/stage/groudon/intro_sprite_rock_2_left.png delete mode 100644 graphics/stage/groudon/intro_sprite_rock_2_right.png delete mode 100644 graphics/stage/groudon/intro_sprite_rock_2_top.png delete mode 100644 graphics/stage/groudon/intro_sprite_rock_3_left.png delete mode 100644 graphics/stage/groudon/intro_sprite_rock_3_right.png delete mode 100644 graphics/stage/groudon/intro_sprite_rock_3_top.png diff --git a/data/rom_1.s b/data/rom_1.s index 925a8f8d..d7ade3e1 100644 --- a/data/rom_1.s +++ b/data/rom_1.s @@ -1291,7 +1291,8 @@ gUnknown_0849F1CC:: @ 0x0849F1CC .incbin "baserom.gba", 0x49F1CC, 0x2020 gUnknown_084A11EC:: @ 0x084A11EC - .incbin "baserom.gba", 0x4A11EC, 0x5D00 + .incbin "graphics/stage/groudon/boulders.4bpp"; + @.incbin "baserom.gba", 0x4A11EC, 0x5D00 gUnknown_084A6EEC:: @ 0x084A6EEC .incbin "baserom.gba", 0x4A6EEC, 0x1680 diff --git a/graphics/stage/groudon/boulder_shape.json b/graphics/stage/groudon/boulder_shape.json new file mode 100644 index 00000000..036f2780 --- /dev/null +++ b/graphics/stage/groudon/boulder_shape.json @@ -0,0 +1,24 @@ +{ + "kind": "oam-shape", + "version":1, + "pngsize": "5x5", + "pieces": [ + { + "pos":"0,0", + "size": "1x1", + "spacer":true + }, + { + "pos":"1,0", + "size": "4x1" + }, + { + "pos":"0,1", + "size": "1x4" + }, + { + "pos":"1,1", + "size": "4x4" + } + ] +} \ No newline at end of file diff --git a/graphics/stage/groudon/boulders.png b/graphics/stage/groudon/boulders.png new file mode 100644 index 0000000000000000000000000000000000000000..41d9b4ae7d440c08190f0e817a5b18f54654f4a4 GIT binary patch literal 4043 zcmYLMdpy(s_g6$0_l6OTEt$&)b4dvcn;~TGA2zWtrHeN2NKwgUW-iOU+_F&0B#BMs zmdIV7GS{@_+C&pRa{cM^{p0t?>-Bv7aUSQq9_Mk+^E^+YvlB*QztVmoAt4DnTa?QV z!i9uH21RxrLdkU|Y#|}3dv+*Gw}`wSd1uHBTiMo4L6cf-rB#v1yv{r$X{{+pt= z_L6dMJ0!8}HNU2yf$^?Kk;9k%?TRcd*^3#e)I61j{PI*}mPe=qPw=6dlKYJR+LGZb zCvPSaDwna;O^Z$omAJazB*OR42TbIRoPD(B4L@~kJ6D->%5CeGw$Z%;PRRW0)Ja84 zcgMKmoGIkhI~AF2s?9NnR(HKgB)G%tpSYl}nv8o@VJ}H%c8tdCt8%rM5lGfnpAvvu z^M|m~C-o$MV&ZsK=h5%*_&Z)RDO7+F>IsaR&IL1G#<#qfY3`8{)_G{Gyct(IZL4P) zr85kZ6WiPq(ApzYKDI~>2SGjV9)4u&dtR{6*ndR*ZDX(T#yFiiUn9?_VN#SQbUG&l zb5-|W@8=m_{Cng@gQ;v)uS3*UZ6cBUIbNbXzKDgME(SN^g0SW zm}512*y{6-(=A;fF%dz3^By_x_bz6jU&*c2i3Cq{kgd8{-Fx2dwCfaJ&qa)O5)LJL z4U~29Zt!eOT>UEs0ne!Gqf+X?!a8{7?R&4;W>er;w{&T7sVdKAf%MLB(t zEbRpC`sgou99dmg30Pe_-#XNYTdb0zzP<4Su&6wEa+`Fs0b*YNMh(1&P7rC4xGoO6 zgpsH2KVhz{=2YAm8Tm2NC&Pz}8H)5Qr$aK=OO<|OSr)X-$Cv9bOkk3~ zzxtSJUxPi##WdPmcs8dh=?t-!VCh1&cF1ORNb$_ZOjSU181d9@U=AbAa*6(=zF`}# ziTo(&%ipw>+@?f-(%7K*gPfFG+vSAY+CQ7MwXb7`p6s|T`I5ZZ?ssGp?gv68(wAX( z=*yCgj~Bscq{|Fu@CXxjH%@v2uVA6h=w;S|vAfc{%{J-KTY zSJs&JD#D$}k}OyJ`CM0yr1JWe&fG&a{F7@w1Ig_G8AxMEdX$B=lJjMJ=zNm)B91tm z%xVX6FKJV8*$;k?9p_AbVJ23jN>-8|yAW zhM_jarqC0T{6RhaCpdc$f;D{>6Kg-kW`D8l{ zBD;R=K*O+E%jLVCKdlpjc0Y{(CzjP0D<(~i$;y=3tc=MDgs$|x^?|OOdLC69h7_0% zHM9o$>TOvmW8({&dNFHbMjJB(pgvNCIn`2VCL;# zlKhO-txa(~{ST_qiw#RQudi+Q#y1RN7slDQ>Tye$*@e!2lX@|ix$29b;)GKhbE__D z=G4;*V}l{z{11+N8N1AKOf)ooGhDCXPF(tcs8#KnJIEu*9cqqV3KO7EwSHFt2x=~E z)S)>6XR?vLE<3wwkRxmI?`sznqjsQSyl!1ob<( zmF6<-OnknzM8@$5m5%9;0(pIvvb>)Ytqjm+3^z*C?gBuZ{D%jUaua=GKYxB zfouS>y(RN>RbRO^MDso82=Fx6{N}=?7xh;2XOrj(KQi+CK`3f}sDINra2z>+pLB5g zLi9BUO4p;^d$mbaVT6{t1wCHB4el+~)K1+E(-=ssa5e%ae|#&*(!H(uS68CYz79HKRY-BfU@IJ~pRz449pNzd2J~@dkd_ui+5T@Zp8_ zcY{vvz@x(moKSpKX*hU@mWm85`%(Wg2ntfbZzexdToJSBV`TS2w$T&QP$}#!NdAU zS+F9O5P-I%$*9{9k!wSLbQp8B!lI=DRHc+nx^TX!O zCLb05espj;Dt>6#q1&!-*GCSq#3L=_JmYKUv}m6e>h!~_kb6bN{z1Nd7I%Na^`D&J z9|n2QqC~;w^M*^;6jfFia&r@np8W*-siPK$8y9PHyoL6q4_Et& zb-AC83lEKdc{`sV({uaDcqYGYmBDfkcAzZkP zal0xHk z2YbUOOt}Wg`~2aZ6#0es09O_nLwj}e0%%`{4^HpH6g&XQs9$CFHbV4Qx)5)5~0A23sU}c0C^CxX+>p zbO8TN?1YDdTltqB@dHPKM&xTMhG-Kh6n*$3$C+nr0d%f+B+$Lo2;n(Ndkr1j1A zJMt~jCyarHBLl*XLkmi|>ht^iC9mI7oTV23U?`Y4Ji8)(xUrw=btceamywU=vU$}p zf+ZToB=P7Jz)2`PgXcr#ibDkqD8)?`o+|1WfNR$%!*VI1Pu z=-BaLI2oV%&mAnYC07J3np+(eKba|Q@;;RpIykY_v7#Bl^tpT=#jO4ZxX-`np)}3@ zc(coW!c;oDt4E*HZE_suCD>c0Klew{O($&E zY|O|-#s8F9%v1e_GK@=U_gZu~tu}NsO@%Z<%&D#(BEITXC5?52?p&i#YZ5+a|Fx4n zKr%jKnr$;SUWZR+IpLwUMipc3t8cCqDKbKNYcj|obKxo=I$-AhnIxSlwt|NI@7QU! zWb66&WV}>p9y$Nemj5PPGaAMTtaS+hU#I^eXPJY6%yZ!11V6BZ#ZDu}EIa>C6I!WDIFXC$N=;m`i-3>La%^4wvcqI2B+}C1GEk@ zK<4nLBsu}}DN~_|dpNB-uy(-yQZRGmEP>f_1zE(Je-D~Fn#n(A#)(3t>W)tDh}KP5 zEb{{ssC$d3R3 literal 0 HcmV?d00001 diff --git a/graphics/stage/groudon/crystal_shape.json b/graphics/stage/groudon/crystal_shape.json new file mode 100644 index 00000000..e0a15c78 --- /dev/null +++ b/graphics/stage/groudon/crystal_shape.json @@ -0,0 +1,24 @@ +{ + "kind": "oam-shape", + "version":1, + "pngsize": "3x3", + "pieces": [ + { + "pos":"0,0", + "size": "1x2" + }, + { + "pos":"0,2", + "size": "1x1", + "spacer":true + }, + { + "pos":"1,2", + "size": "2x1" + }, + { + "pos":"1,0", + "size": "2x2" + } + ] +} \ No newline at end of file diff --git a/graphics/stage/groudon/groudon_head_shape.json b/graphics/stage/groudon/groudon_head_shape.json new file mode 100644 index 00000000..ed471bfc --- /dev/null +++ b/graphics/stage/groudon/groudon_head_shape.json @@ -0,0 +1,42 @@ +{ + "kind": "oam-shape", + "version":1, + "pngsize": "4x4", + "pieces": [ + { + "pos":"0,0", + "size": "1x1", + "spacer":true + }, + { + "_comment":"Fins", + "pos":"1,0", + "size": "2x1" + }, + { + "pos":"3,0", + "size": "1x1", + "spacer":true + }, + { + "_comment":"Face", + "pos":"0,1", + "size": "4x2" + }, + { + "pos":"0,3", + "size": "1x1", + "spacer":true + }, + { + "_comment":"Chin", + "pos":"1,3", + "size": "2x1" + }, + { + "pos":"3,3", + "size": "1x1", + "spacer":true + } + ] +} \ No newline at end of file diff --git a/graphics/stage/groudon/groudon_stage_gfx.json b/graphics/stage/groudon/groudon_stage_gfx.json index 43b63503..54a7020e 100644 --- a/graphics/stage/groudon/groudon_stage_gfx.json +++ b/graphics/stage/groudon/groudon_stage_gfx.json @@ -83,80 +83,22 @@ "width": 6 }, { - "segfile": "intro_sprite_rock_1_top", - "width": 4 - }, - { - "segfile": "intro_sprite_rock_1_left", - "width": 1 - }, - { - "segfile": "intro_sprite_rock_1_right", - "width": 4 - }, - { - "segfile": "intro_sprite_rock_2_top", - "width": 4 - }, - { - "segfile": "intro_sprite_rock_2_left", - "width": 1 - }, - { - "segfile": "intro_sprite_rock_2_right", - "width": 4 - }, - { - "segfile": "intro_sprite_rock_3_top", - "width": 4 - }, - { - "segfile": "intro_sprite_rock_3_left", - "width": 1 - }, - { - "segfile": "intro_sprite_rock_3_right", - "width": 4 + "segfile": "intro_sprite_boulders", + "oam": true, + "mwidth": 5, + "mheight": 5, + "oamshape": "boulder_shape.json" }, { "segfile": "intro_sprite_quarter_circle", "width": 8 }, { - "segfile": "intro_sprite_crystal_1_left", - "width": 1 - }, - { - "segfile": "intro_sprite_crystal_1_bottom", - "width": 2 - }, - { - "segfile": "intro_sprite_crystal_1_top_right", - "width": 2 - }, - { - "segfile": "intro_sprite_crystal_2_left", - "width": 1 - }, - { - "segfile": "intro_sprite_crystal_2_bottom", - "width": 2 - }, - { - "segfile": "intro_sprite_crystal_2_top_right", - "width": 2 - }, - { - "segfile": "intro_sprite_crystal_3_left", - "width": 1 - }, - { - "segfile": "intro_sprite_crystal_3_bottom", - "width": 2 - }, - { - "segfile": "intro_sprite_crystal_3_top_right", - "width": 2 + "segfile": "intro_sprite_crystals", + "oam": true, + "mwidth": 3, + "mheight": 3, + "oamshape": "crystal_shape.json" }, { "segfile": "intro_sprite_flame_pillars", @@ -171,16 +113,11 @@ "width": 4 }, { - "segfile": "intro_sprite_groudon_fins", - "width": 2 - }, - { - "segfile": "intro_sprite_groudon_face", - "width": 4 - }, - { - "segfile": "intro_sprite_groudon_chin", - "width": 2 + "segfile": "intro_sprite_head_1", + "oam": true, + "mwidth": 4, + "mheight": 4, + "oamshape": "groudon_head_shape.json" }, { "segfile": "intro_sprite_groudon_body", @@ -195,16 +132,11 @@ "width": 2 }, { - "segfile": "intro_sprite_groudon_fins_2", - "width": 2 - }, - { - "segfile": "intro_sprite_groudon_face_2", - "width": 4 - }, - { - "segfile": "intro_sprite_groudon_chin_2", - "width": 2 + "segfile": "intro_sprite_head_2", + "oam": true, + "mwidth": 4, + "mheight": 4, + "oamshape": "groudon_head_shape.json" }, { "segfile": "intro_sprite_groudon_leg_2", @@ -243,40 +175,11 @@ "width": 2 }, { - "segfile": "intro_sprite_groudon_fin_3", - "width": 2 - }, - { - "segfile": "intro_sprite_groudon_face_3", - "width": 4 - }, - { - "segfile": "intro_sprite_groudon_chin_3", - "width": 2 - }, - { - "segfile": "intro_sprite_groudon_profile_fin", - "width": 2 - }, - { - "segfile": "intro_sprite_groudon_profile_face", - "width": 4 - }, - { - "segfile": "intro_sprite_groudon_profile_chin", - "width": 2 - }, - { - "segfile": "intro_sprite_groudon_quarter_fin", - "width": 2 - }, - { - "segfile": "intro_sprite_groudon_quarter_face", - "width": 4 - }, - { - "segfile": "intro_sprite_groudon_quarter_chin", - "width": 2 + "segfile": "intro_sprite_face_turn", + "oam": true, + "mwidth": 4, + "mheight": 4, + "oamshape": "groudon_head_shape.json" }, { "segfile": "intro_sprite_rock_bits_2", @@ -287,28 +190,11 @@ "width": 2 }, { - "segfile": "intro_sprite_groudon_yell_fin", - "width": 2 - }, - { - "segfile": "intro_sprite_groudon_yell_face", - "width": 4 - }, - { - "segfile": "intro_sprite_groudon_yell_chin", - "width": 2 - }, - { - "segfile": "intro_sprite_groudon_yell_fin_2", - "width": 2 - }, - { - "segfile": "intro_sprite_groudon_yell_face_2", - "width": 4 - }, - { - "segfile": "intro_sprite_groudon_yell_chin_2", - "width": 2 + "segfile": "intro_sprite_face_yell", + "oam": true, + "mwidth": 4, + "mheight": 4, + "oamshape": "groudon_head_shape.json" }, { "segfile": "intro_sprite_groudon_arms", @@ -347,6 +233,14 @@ "width": 7 } ] + }, + { + "gfx_filename": "boulders", + "oam": true, + "oamshape": "boulder_shape.json", + "mwidth": 5, + "mheight": 5, + "width": 10 } ] } diff --git a/graphics/stage/groudon/intro_sprite_boulders.png b/graphics/stage/groudon/intro_sprite_boulders.png new file mode 100644 index 0000000000000000000000000000000000000000..f7e003e0eefcc4b1686ca0857c12d829a84e6573 GIT binary patch literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^8bDmZ0wfp`UNgA^sZX9Rjv*eMZ!d2YYBJzqdtg@= zJLjbA7esiShwt8j)l0`LW4tH z;^o?R+hT5fOv*3MVVEAK`tC;f-6mZ=X{A?N4I0G+S>v4Lg)h9lalYB;mUvZNCXhq#5HNV=P$OfMM)o{@BOG)yv?bcjbuP2uZ vw(W1M>3QUtd@c|SFX|gS*LeQow2Up|!nV+BddenopiuC1^>bP0l+XkKvYLJA literal 0 HcmV?d00001 diff --git a/graphics/stage/groudon/intro_sprite_crystal_1_bottom.png b/graphics/stage/groudon/intro_sprite_crystal_1_bottom.png deleted file mode 100644 index 45e3f5eb95e1a23b9df9564fe28a7addde26d184..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 100 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0wfp&-k$XWQrey_jv*W~lYji*Q*(9K?cJ{= y{{4?G{VpqD@aO;kSNG}_7>xeyZ8uV2;Advgjj-u6EwNq%((dW%=d#Wzp$P!0bs*LN diff --git a/graphics/stage/groudon/intro_sprite_crystal_1_left.png b/graphics/stage/groudon/intro_sprite_crystal_1_left.png deleted file mode 100644 index 41116556e4f8ae5d9fbfb8e36b9b718c5e8e3a8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106 zcmeAS@N?(olHy`uVBq!ia0vp^96&6<0wfqt3-&AkQih%`jv*W~lYjjGf8Ux-!BE!a zO^fhu!_NQf9gjo{eC%gj!M);be5L7x-+VgUMo<6OGsFj5KmWBVtrKL3r>mdKI;Vst E00wU&%K!iX diff --git a/graphics/stage/groudon/intro_sprite_crystal_1_top_right.png b/graphics/stage/groudon/intro_sprite_crystal_1_top_right.png deleted file mode 100644 index 542d6bc75e6ff963fb740fb823fff8bcc48eb8e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^0wBx+BpCi@`0fExS)MMAAsjPJ&pL87IB>Kk=IdTw zl-=pukHx-tjzt|`XNte;)atX(Ym y@BiG&XL#Xb6Mw<&;$Tf2Oz5e diff --git a/graphics/stage/groudon/intro_sprite_crystal_2_left.png b/graphics/stage/groudon/intro_sprite_crystal_2_left.png deleted file mode 100644 index c7a8c0d72c4cbff553fd722b40cd6ee0c15ae1ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106 zcmeAS@N?(olHy`uVBq!ia0vp^96&6<0wfqt3-&AkQih%`jv*W~lYjjG|2~#Y;Ub$- z*n!T_8Atv{D?H-s_;H%y3hR|`=DOS`_Hs{So%!j%J%iB}tKIK9E;|4)OehT0Pkz9>$LY1ZLf>9UFMg>~)n@6LA2^^O0Sl-D=vpIiCyflNfw_kITCmru@IY@8La= N{hqFVF6*2UngAooL52VT diff --git a/graphics/stage/groudon/intro_sprite_crystal_3_bottom.png b/graphics/stage/groudon/intro_sprite_crystal_3_bottom.png deleted file mode 100644 index 20f18b92ae1ea1be856bdac9d6da5f588c336b79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 100 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0wfp&-k$XWQrey_jv*W~lYi8klsua?*EmVy y-~X9AfAa_!{P|yBbiQ7J!RX(db|VD_erAS!FRa#2D==9O((dW%=d#Wzp$Pyt(jQR( diff --git a/graphics/stage/groudon/intro_sprite_crystal_3_left.png b/graphics/stage/groudon/intro_sprite_crystal_3_left.png deleted file mode 100644 index 5124ad2ad6e5eaf758855f5083e89b5cb41bed07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106 zcmeAS@N?(olHy`uVBq!ia0vp^96&6<0wfqt3-&AkQih%`jv*W~lYjjGe_oPJ;Ubq( z+JVlQGmiY9tnkRBg zemn9+1FMBatHw!Fh1m{LLYw$rSl2H9?rg_g-}sM7d3~e)xs@Lu$V3$Gw0p;xtr)+B z#biYKdrxV*uR1V+}KiHkt? Nd%F6$taD0e0syj)JS6}C diff --git a/graphics/stage/groudon/intro_sprite_crystals.png b/graphics/stage/groudon/intro_sprite_crystals.png new file mode 100644 index 0000000000000000000000000000000000000000..a87f96f94df57daeac7019d630a199af04cd8b2c GIT binary patch literal 487 zcmVxC6cUjJ>ArjYbAL_Lh!)jRy8??cc0bK!cb8Td#Z#_dJGO z2653vqrb}H49p%vh|@`oeQ${F*9_EBeOv1C_5So;wchA(nSMbL_s9X9s&`BqX7Ee0GzW(sHOV0(BZ4id}L5_0c!fQUjnOx dI{DY$eglr(!oj%%Ufcix002ovPDHLkV1gyA8nu6osi4u!qW$jqXB`DN~p3d6rJ@>P<_hOavUKZ=zX1V!>mPn1ZIls71o)6i6kd zC!oqdk&HW~DdEBQ!}Sq$@9_a(0K<6XckmY&&WHwl=)^|@)zC)8yQuIqT3Q@w;b|CP zHTa_fod)6<5bFntLp2~H;8=sYdk!)WJe&c9X!euRIyQiHKP3DhL(qm4KSkp9fT!?a63Mwzt1;&d{`p~ z9#?xjy|-WpfjYavfjIzy0dhj&bU6>EtDT)`2$st<1v@*_Sv>iYuKLQ(bQbpj#0E&x zt7-P}OLpsnfAxo)H1C+N!Lx>CzD7f{Bl9%4UbfRvjv>|`x&d1|mMG_<%A z4f}EgZPPG}YY()oY(Qi*jHoFl5Wo=>1;I~{y+4g$yQS47`mso?#|m`Z%6i`{#B z0mcFUd$+IF5Z>V3EAvB^N9yH=23~$>;PRu^z@>vKGrYIp;T)Lwu$JO=sp12EGl0=j z1_~sJdYyqMw@Af@GKlK9{;lGJT*XG?*GDAd!`UH+OvZ-?bbM6y>LNafhz|?;TEz!) zZyN@po*SUzVj|-Mx&MARQSsqsIs-aBy!_Dd@jO3z&tot1#~&eQVowp4F(Lo}002ov JPDHLkV1l^Z8ruK> literal 0 HcmV?d00001 diff --git a/graphics/stage/groudon/intro_sprite_face_yell.png b/graphics/stage/groudon/intro_sprite_face_yell.png new file mode 100644 index 0000000000000000000000000000000000000000..877a4a1b34937795a93ad37814d9eda1e7121410 GIT binary patch literal 401 zcmV;C0dD?@P)#S zo_+H0n{(DGP}FtJ3{X`gs1(R|WnEXiawiZUd0Cb$pFl&O(xTvZ)Ird!BY(rogTJ*J z3HsGKrnKag?yQykSm8V;(Kcs?kK+CZGa%VR&LAn^9NHz|d6osm^cG<+f`E|MO#Pe< zZ!d&^0o=v#K!-p!v~)WFx64$g%dwj7OLaN{)oF*o)qDAm-QFa_6bAi3VRXhQOep4l zw5qWlbuffM9SmWrnIG(|gZ5*lLm#0I8|{Zr8+lTG#8hsd8h*qyXEZka$l&oEY{L)m v0y5~J{XiW|Kco&9eh~6kKQ45*#cqEAx8>q7*d0eJ00000NkvXXu0mjfBeSn{ literal 0 HcmV?d00001 diff --git a/graphics/stage/groudon/intro_sprite_groudon_chin.png b/graphics/stage/groudon/intro_sprite_groudon_chin.png deleted file mode 100644 index c8232e9660c8d92b512cbdc956cb1e1f6d7b05e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0wfp&-k$XWQo5cljv*W~lOH4|cc^>ru diff --git a/graphics/stage/groudon/intro_sprite_groudon_chin_3.png b/graphics/stage/groudon/intro_sprite_groudon_chin_3.png deleted file mode 100644 index 56a81301d2b9492dae102b6cff387f55b5f4640d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0wfp&-k$XWQs$m6jv*W~lOH7d3n!%LTO2rW zc*3->xN`*!jnn7r?%ng?Fss62W@cvl2_GB(+Y8h@NdLjlI)jIS*GP)9SPKTG}M^0;8TlYZ|qWSq#I+@DGEg zxyB002b8X!k#1l>tuRUhqW^{L3vPeVG?3i^gu6hO3Z;~Ut7D)GNWqm7KyasF^aGXx z=e(uZ@AZarWQTH&+0p&eWH>B50dGT!meAo$2nRxn^~#VlTFW+VK2UkJ#Q6r3jbaa5 i=(2rlTTzrG@f%(gP{*|>R8wdG0000F4zzr7a)d%cpTRpqY3sZteXhLr4i|xXxn&!0Zsr?3;3ADWb$S5PX@-R zh6>9EmcE(6G_;@>7?cIs{X+Hy*FUIh%`4kv(QGg5L!#5q%|;v5l>MF z(J&HrX2aX_T{*>U*!F2NJRToWG%psFIs<@)brG0ssQ% h^;_3D1jI?+@B$B)#%P0pD#`!=002ovPDHLkV1j-2U8n#6 diff --git a/graphics/stage/groudon/intro_sprite_groudon_face_3.png b/graphics/stage/groudon/intro_sprite_groudon_face_3.png deleted file mode 100644 index da90b087d1e24a2e0bb8a142c894441799c9fa3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmV<503iQ~P))4P*Km zss~iD-;o~>z|PQ{0J{B!{0nY>AOz%h03A=@sJ7M`xZ(gVC8e}h1Bsl6K1n$RDWzgj z()tU0;QMN*KWVBWc+U~oAEtnhLj%5j4!`e#)33^y85}Sb4q9e0J9(-9smFU diff --git a/graphics/stage/groudon/intro_sprite_groudon_fins.png b/graphics/stage/groudon/intro_sprite_groudon_fins.png deleted file mode 100644 index a6a3949d0cd61865ad2da32566194039293eedf4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 98 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0wfp&-k$XWQktGFjv*W~lYj8D@*KWVBWc+U~oAEtnhLj%5j4!`e#)33^y85}Sb4q9e0J9(-9smFU diff --git a/graphics/stage/groudon/intro_sprite_groudon_fins_2.png b/graphics/stage/groudon/intro_sprite_groudon_fins_2.png deleted file mode 100644 index a6a3949d0cd61865ad2da32566194039293eedf4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 98 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0wfp&-k$XWQktGFjv*W~lYj8D@*KWVBWc+U~oAEtnhLj%5j4!`e#)33^y85}Sb4q9e0J9(-9smFU diff --git a/graphics/stage/groudon/intro_sprite_groudon_profile_chin.png b/graphics/stage/groudon/intro_sprite_groudon_profile_chin.png deleted file mode 100644 index acd859d679db5f97f72c2a9526979ca3745ff17c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 95 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0wfp&-k$XWQfi(qjv*W~lM}r4_0xBp{y+aA t^Mro|5C7M<{paENZ~viLBEW%%nPJK?(``4>D%XN^db;|#taD0e0sxVO9n1g# diff --git a/graphics/stage/groudon/intro_sprite_groudon_profile_face.png b/graphics/stage/groudon/intro_sprite_groudon_profile_face.png deleted file mode 100644 index 82e7c09e02bed6d1a66ceac5a46dca363a5a378c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Et0wfrw&pzJ*q~>_KIEF|}^*wLRcgTQ;E#W(p z&5?6==5iOZn2D(^q9oYc(=54 z`DUln%_}?1eElMf!uW1$m0Y=>()P!jMKfy|Pkh58BYmBK1MgOSV7|?GE#*b_%{3tR Ndb;|#taD0e0sy}aQ+5CV diff --git a/graphics/stage/groudon/intro_sprite_groudon_profile_fin.png b/graphics/stage/groudon/intro_sprite_groudon_profile_fin.png deleted file mode 100644 index 495e9f3dd13c20c27a8cb341fc0e661ecc2b1684..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0wfp&-k$XWQU;zbjv*W~lYj8D^4ySo|8H+S zd(xli@2~D!2|T`fyL7vw(*JLF>vq4FoAB|M?u1f)hP{t$8cvvbF9Vt3>FVdQ&MBb@ E09V8%kN^Mx diff --git a/graphics/stage/groudon/intro_sprite_groudon_quarter_chin.png b/graphics/stage/groudon/intro_sprite_groudon_quarter_chin.png deleted file mode 100644 index 21d55c89854748bcff9f22d583e7828c0cfc2f46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 111 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0wfp&-k$XWQf8hmjv*W~lOH4sC#2|GoIZcx z@U*bFa|I9Vnf0BkDi5}EJFwX^GuwB3Z2S)d4owg3MG}k{+|@Ok_!OQoflTsr^>bP0 Hl+XkKV;>>W diff --git a/graphics/stage/groudon/intro_sprite_groudon_quarter_face.png b/graphics/stage/groudon/intro_sprite_groudon_quarter_face.png deleted file mode 100644 index 028cfb5f00d8e8435e5cf7bc03fd798416dc772f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 221 zcmV<303!d1P)u$Y$d*?1(;Lf>w z_wK$u(a{eTc=r}4aPMx%J-PQ_fq!y+XV0BGccb^3pd3_1)0?wr-!k>yv*U-F@ksCN zxf@Eocllw4?&x^;wxeS&l0fg7yEnSeUVuCObitW>?0XObAMP-Kz*&TEc^H9w0f+zq XeBsbo*_hj~00000NkvXXu0mjfYZPMo diff --git a/graphics/stage/groudon/intro_sprite_groudon_quarter_fin.png b/graphics/stage/groudon/intro_sprite_groudon_quarter_fin.png deleted file mode 100644 index 8e888c049e5213be36638dd290e755d8a1dc9fe6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 100 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0wfp&-k$XWQrey_jv*W~lYj8D@gTe~DWM4fkBc5I diff --git a/graphics/stage/groudon/intro_sprite_groudon_yell_chin_2.png b/graphics/stage/groudon/intro_sprite_groudon_yell_chin_2.png deleted file mode 100644 index bd97dc2d194b529ff344531dfec9f5966f4ca87d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0wfp&-k$XWQYxMbP0l+XkKjcpyN diff --git a/graphics/stage/groudon/intro_sprite_groudon_yell_face.png b/graphics/stage/groudon/intro_sprite_groudon_yell_face.png deleted file mode 100644 index 49a9746826d18fb0b3cb95c1e38da0d766c559c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 238 zcmV|3L3!sXF^f>L>sazpa%T<_>2&i*OGNY12y?}ueKzS5|2g~~O%a&mn zt1q#rF>$9EriKfc6?*-~j*>Jx`5$wRV#y~g258>d#`>b1{lmGw#07*qoM6N<$g4fGpJOBUy diff --git a/graphics/stage/groudon/intro_sprite_groudon_yell_face_2.png b/graphics/stage/groudon/intro_sprite_groudon_yell_face_2.png deleted file mode 100644 index 240a42483bd22431fe832fbbd486e2d1c42050dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 239 zcmVgsejg_kGDZu5K1IJy1Qh z%>p`RG_B=}rkoKS>BVb9-eB p{L9He<$ei(n1VkDeG}+s_yRr1zeaP7ZzKQ!002ovPDHLkV1g^JUWfny diff --git a/graphics/stage/groudon/intro_sprite_groudon_yell_fin.png b/graphics/stage/groudon/intro_sprite_groudon_yell_fin.png deleted file mode 100644 index 99d46f95593bf3aa1f6dc84f68d2e26d0735d2bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 97 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0wfp&-k$XWQW~Bvjv*W~lYj8D^8Aqa|8GCL vlEP!t`*wNzY88&%uHAm0N%Fs6v*dfm>8q{Wn%k4iKzco0{an^LB{Ts5s1O~6 diff --git a/graphics/stage/groudon/intro_sprite_groudon_yell_fin_2.png b/graphics/stage/groudon/intro_sprite_groudon_yell_fin_2.png deleted file mode 100644 index 3b70971cd54fb08714e39eb20f20b5d351a38720..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0wfp&-k$XWQYxMfwMv?nvkUmdWKbLh*2~7Yr4;+jD diff --git a/graphics/stage/groudon/intro_sprite_head_1.png b/graphics/stage/groudon/intro_sprite_head_1.png new file mode 100644 index 0000000000000000000000000000000000000000..4bcd0cc869129c6a47a3427eb3feff84acb9d863 GIT binary patch literal 308 zcmV-40n7f0P)Zq}6b9f&D>Xx@om?QQa*!S;UD>#TCCXJYBQ>!Yt0q_-Q43c0=z&jwi+SLZbm|iW z`h@;x{{UF9)3|r|1NKS?I@|US!SKPE$$KVe8E2GVC}$ZX_>As2Kugfi2%Hx6g9Lhl zt|qA3OW4ie{R)Z#cb|wAAE8Mqt#yrWjG$SIgVMS}5i}0E)jCU1DOIMy%AL)!r|ePo zF*oV$H)XJF^Mb0}q%ek>S0NM%ahSH4eU_f;GAVxHxIERt-h&MxS_TWq{cXd=b{oeL zfomv)yXQE9+;Zq}6b9f&E0v+dPA(8tIY^H~S2nI-iE@?9NKGupstHy{)PmJLdf*e_Vr*QRPJLoP zpV0s89{>${9`g==KwpW4LHFZhVfx@g_&wo^j0^Hl#X{LQ*v&@g9jbK8X$nu-!=;4br2>N zOhXaGahgKuJB{ewQ<|vKce1ry^fe~ea{^#E=;2W|XYuG9e(gOeh002ov JPDHLkV1m`#fPnx2 literal 0 HcmV?d00001 diff --git a/graphics/stage/groudon/intro_sprite_rock_1_left.png b/graphics/stage/groudon/intro_sprite_rock_1_left.png deleted file mode 100644 index 4300b31fe478a1c3b02f59603e26eaee4625bc9f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77 zcmeAS@N?(olHy`uVBq!ia0vp^96+qV0wfsDXLs`gDG^T>#}JO0$v^m6c%BIV_fR~s ZftlfkZGA;BTl8*_I!{+Wmvv4FO#n#<5_A9n diff --git a/graphics/stage/groudon/intro_sprite_rock_1_right.png b/graphics/stage/groudon/intro_sprite_rock_1_right.png deleted file mode 100644 index a5eb95961aa1350cc2d78a5094eced8127593fb4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 261 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk^Bp4>8psC6k_&bQCkMn9J`qbCr_oA^kqXzFYqNzl6;e-8l4Y>%L=be^?i+ zUSu&pdD_;d>q6iAxuS2iTbF#~c8KTtA(U+(sj_f#3maQp%ufGw5p|tCH*(rI4W_>} zY?hwJBNnXA6*#>`n8iis!<1QW3TXu^1=}Vx#H%qjx3J!^F=S}v+U%OydMey3ePT!b z#g;oa&RM(Im^zm(GugB!vG@3dmq%NU`?wye;NHl%Q}fr)JbOvC9UdP}wXlNx=jrO_ Jvd$@?2>@@|V}Sqw diff --git a/graphics/stage/groudon/intro_sprite_rock_1_top.png b/graphics/stage/groudon/intro_sprite_rock_1_top.png deleted file mode 100644 index 2e1dbc7b38ac426743f55800c4efd0737d7851f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72 zcmeAS@N?(olHy`uVBq!ia0vp^3P8-k0wfqJ#}JO0$v^m6c%BIV_fR~s ZftlfkZGA;BTl8*_I!{+Wmvv4FO#n#<5_A9n diff --git a/graphics/stage/groudon/intro_sprite_rock_2_right.png b/graphics/stage/groudon/intro_sprite_rock_2_right.png deleted file mode 100644 index a5eb95961aa1350cc2d78a5094eced8127593fb4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 261 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk^Bp4>8psC6k_&bQCkMn9J`qbCr_oA^kqXzFYqNzl6;e-8l4Y>%L=be^?i+ zUSu&pdD_;d>q6iAxuS2iTbF#~c8KTtA(U+(sj_f#3maQp%ufGw5p|tCH*(rI4W_>} zY?hwJBNnXA6*#>`n8iis!<1QW3TXu^1=}Vx#H%qjx3J!^F=S}v+U%OydMey3ePT!b z#g;oa&RM(Im^zm(GugB!vG@3dmq%NU`?wye;NHl%Q}fr)JbOvC9UdP}wXlNx=jrO_ Jvd$@?2>@@|V}Sqw diff --git a/graphics/stage/groudon/intro_sprite_rock_2_top.png b/graphics/stage/groudon/intro_sprite_rock_2_top.png deleted file mode 100644 index 2e1dbc7b38ac426743f55800c4efd0737d7851f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72 zcmeAS@N?(olHy`uVBq!ia0vp^3P8-k0wfqJ#}JO0$v^m6c%BIV_fR~s ZftlfkZGA;BTl8*_I!{+Wmvv4FO#n#<5_A9n diff --git a/graphics/stage/groudon/intro_sprite_rock_3_right.png b/graphics/stage/groudon/intro_sprite_rock_3_right.png deleted file mode 100644 index a5eb95961aa1350cc2d78a5094eced8127593fb4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 261 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk^Bp4>8psC6k_&bQCkMn9J`qbCr_oA^kqXzFYqNzl6;e-8l4Y>%L=be^?i+ zUSu&pdD_;d>q6iAxuS2iTbF#~c8KTtA(U+(sj_f#3maQp%ufGw5p|tCH*(rI4W_>} zY?hwJBNnXA6*#>`n8iis!<1QW3TXu^1=}Vx#H%qjx3J!^F=S}v+U%OydMey3ePT!b z#g;oa&RM(Im^zm(GugB!vG@3dmq%NU`?wye;NHl%Q}fr)JbOvC9UdP}wXlNx=jrO_ Jvd$@?2>@@|V}Sqw diff --git a/graphics/stage/groudon/intro_sprite_rock_3_top.png b/graphics/stage/groudon/intro_sprite_rock_3_top.png deleted file mode 100644 index 2e1dbc7b38ac426743f55800c4efd0737d7851f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72 zcmeAS@N?(olHy`uVBq!ia0vp^3P8-k0wfqJ Date: Sat, 28 Feb 2026 02:48:36 -0600 Subject: [PATCH 04/10] sharpedo --- graphics/stage/ruby/ruby_gfx.json | 6 ++-- graphics/stage/ruby/sharpedo.png | Bin 762 -> 847 bytes graphics/stage/ruby/sharpedo_shape.json | 42 ++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 graphics/stage/ruby/sharpedo_shape.json diff --git a/graphics/stage/ruby/ruby_gfx.json b/graphics/stage/ruby/ruby_gfx.json index bdf892bd..85008363 100644 --- a/graphics/stage/ruby/ruby_gfx.json +++ b/graphics/stage/ruby/ruby_gfx.json @@ -18,8 +18,10 @@ }, { "gfx_filename": "sharpedo", - "mwidth":19, - "mheight":3 + "oam": true, + "mwidth": 5, + "mheight": 6, + "oamshape": "sharpedo_shape.json" }, { "gfx_filename": "shop", diff --git a/graphics/stage/ruby/sharpedo.png b/graphics/stage/ruby/sharpedo.png index f626f07d4696bef71a3159e7d3856b42765e2f77..6619b25e27757db6a77511ddc0ad7ff633c6e4e8 100644 GIT binary patch literal 847 zcmV-V1F-ywP)R7@50C;6&_*4+c{oeW=2mx^ zQ(9Nz5i<3LR62flc9OI2s2CU!;<9~ozwiC-&nHGmAR!0Re^w|g)D9oYq?SxRwPZ%Q zB{Rw`nUmbi)1 zn2;>4L0quBf~#_X1V*kyJ}u~g5aj6P0HHb|i4LP_0TW$9+M;Se9Wf;erWKrHj0+kv zs3v7SKxi>77-Lkzz=0{}iz8A;7s7r>tB-0B_C}J;-EQsPIVI_u-><_t zNn5-7t$L=w@BDo+Yrt~6ZoZ|}u);>U1|M?Mc=7*zH!}Zy-=Wy_eU8Ii^L?P;@=u`j zos@q9xTyJF{s{=I`>up<`i`sB{|?`KZT*f)-~GCqrSBb!?{E68TBh>7)2(L;9Qb?B z_gi%+#GUVpyIOf|SfN(lfDgH8y!c=GZgcZ~`kQVx)fP5ks|&UGeqY2EH>su1h^A&H z2y13enAs=1=5r&H7tMvru@qjv4tV10N_Y+O38QW{uHO-848mn?9pY)>O5xgCIls*n zbH^HtKsk71uBxxf`K?mUAuGtSoXA*`f!c9(u^~G+x)=?qOP%toEg_#?os5Po51jsyF&Ovtm)P4`uyt; z$x;$=vsNic!8UeU<@3Z&;}RA_R?>Qz6b8ois<;}Qfuv-_nB6X<;45Kg5mw|5HkOe{ z&Gn2@n#&jjLmsma4bDOMzekY`Ow9yu?Cdp`VM8~=NQgACmplgmsbP^v@y_+&6k#tT z>R^iQhkI&jVU!N%up76VL*`(Zab{rMRC2tB5B-Gi5+kFLdNAIbUyzA`Np*L_=P-;z zm^_QPhb#<*$gW5MvpRyyZ@8Ri`EW>T(kv!A3iJLX#}@_hY`6x-#b~vgg8s7<-~=;n z)v;7!Yld3f!!S!GT(=GY4kcr&gz1tg6=R&D(IK_4`v&}rXU+fU*@sSJ?IEnjGrajE z&zi0F5^U(^(z6+jvx;ZkTF;_z_x0XVWXqnF(pt|%y=S*!sBR5xE^WPMH2*8loV1U5 zMysBIDm`o0!g%s@&$MXPv#?&;Y6DycE20&&r)vf*MKuz$rC|{v_pwc%zu9`xS!lpr zo|G_HwW7svaC9-~OJ92S)W9t%XnQAYS2PU*(sm7)!+O$f7GscpID!InW(B4~qO2MKzJX!Qr0u3|Ckmh1|GFL=>nvFmVRE__{NsAKyWL#qfQ1oa zBD*WQspus4weV)UI6Lek>T{nMahUJ?)KlR}lzr}Y5 zb84+@tOwgc=h1c=HPH5%zc~ZXMoEiG(KX~9S75An?VqCYCW?s)S}3x4a_L- sxuR(mPK57!UU?p_yxI!ZTaUv20934-_5&FwS^xk507*qoM6N<$f))mD2LJ#7 diff --git a/graphics/stage/ruby/sharpedo_shape.json b/graphics/stage/ruby/sharpedo_shape.json new file mode 100644 index 00000000..040720d0 --- /dev/null +++ b/graphics/stage/ruby/sharpedo_shape.json @@ -0,0 +1,42 @@ +{ + "kind": "oam-shape", + "version":1, + "pngsize": "5x6", + "pieces": [ + { + "pos":"0,0", + "size": "1x2", + "spacer":true + }, + { + "_comment":"Fin", + "pos":"1,0", + "size": "1x2" + }, + { + "pos":"2,0", + "size": "3x2", + "spacer":true + }, + { + "_comment":"body", + "pos":"0,2", + "size": "4x4" + }, + { + "pos":"4,2", + "size": "1x1", + "spacer":true + }, + { + "_comment":"flipper", + "pos":"4,3", + "size": "1x1" + }, + { + "pos":"4,4", + "size": "1x2", + "spacer":true + } + ] +} \ No newline at end of file From 8f4200667a8fec92756da563c5bcc18be45521f1 Mon Sep 17 00:00:00 2001 From: Retnuhytnuob Date: Sat, 28 Feb 2026 03:02:37 -0600 Subject: [PATCH 05/10] ruby_intro --- graphics/stage/ruby/intro_sprite_linoone.png | Bin 0 -> 230 bytes graphics/stage/ruby/intro_sprite_sharpedo.png | Bin 0 -> 422 bytes graphics/stage/ruby/linoone_shape.json | 27 ++++++++++++ graphics/stage/ruby/ruby_gfx.json | 40 +++++------------- 4 files changed, 37 insertions(+), 30 deletions(-) create mode 100644 graphics/stage/ruby/intro_sprite_linoone.png create mode 100644 graphics/stage/ruby/intro_sprite_sharpedo.png create mode 100644 graphics/stage/ruby/linoone_shape.json diff --git a/graphics/stage/ruby/intro_sprite_linoone.png b/graphics/stage/ruby/intro_sprite_linoone.png new file mode 100644 index 0000000000000000000000000000000000000000..1b27a777bcebc76c7dbd0ff4ba9024472c020ac4 GIT binary patch literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^5aHDOZDj84oV@wJZq3eqZkgh|SutG2{FYPl z50uV-XeCvA;({EzrCR^WWAA)jj|i+4ZE`u5EHYtP^y$`1sVwqut55Uz7H(Ya%4Dit z;$GnN=2^wEi&HEZvIWnXNTv3QI#e|>b$G2_JxvN}hT*s9}sM?ZB;$AXBH-3R;6XK$f;3H>hgZ$O!_K@}b=sy#!!G zUrvzJ0)!8fouS4Nz@)4LxD#t|P}Z>EoEse@siD#nq=?cWqCUcCNG+S{7!EEoEh#JN z^mz3+A%-ww6pI*lgRFxR^6@f5nbyovc_c zM6 Date: Sat, 28 Feb 2026 03:07:05 -0600 Subject: [PATCH 06/10] name boulders --- data/rom_1.s | 3 +-- src/board_process3_groudon.c | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/data/rom_1.s b/data/rom_1.s index d7ade3e1..140665c3 100644 --- a/data/rom_1.s +++ b/data/rom_1.s @@ -1290,9 +1290,8 @@ gUnknown_0849B8CC:: @ 0x0849B8CC gUnknown_0849F1CC:: @ 0x0849F1CC .incbin "baserom.gba", 0x49F1CC, 0x2020 -gUnknown_084A11EC:: @ 0x084A11EC +gGroudonBoardBoulders_Gfx:: @ 0x084A11EC .incbin "graphics/stage/groudon/boulders.4bpp"; - @.incbin "baserom.gba", 0x4A11EC, 0x5D00 gUnknown_084A6EEC:: @ 0x084A6EEC .incbin "baserom.gba", 0x4A6EEC, 0x1680 diff --git a/src/board_process3_groudon.c b/src/board_process3_groudon.c index 0b3a927a..f5e8641a 100644 --- a/src/board_process3_groudon.c +++ b/src/board_process3_groudon.c @@ -7,7 +7,7 @@ extern const u8 gGroudonBonusClear_Gfx[]; extern const u8 gUnknown_08352BD8[]; extern const u8 gUnknown_0849F1CC[]; -extern const s8 gUnknown_084A11EC[][0x300]; +extern const s8 gGroudonBoardBoulders_Gfx[][0x300]; extern struct SongHeader se_unk_118; extern struct SongHeader se_unk_11b; extern struct SongHeader se_unk_11c; @@ -1329,7 +1329,7 @@ void sub_3CBC4(void) } var0 = gCurrentPinballGame->unk4D2[i]; - DmaCopy16(3, gUnknown_084A11EC[var0], (void *)0x06010FA0 + i * 0x300, 0x300); + DmaCopy16(3, gGroudonBoardBoulders_Gfx[var0], (void *)0x06010FA0 + i * 0x300, 0x300); group->baseX = (gCurrentPinballGame->unk4EC[i].x / 10) + i - gCurrentPinballGame->unk58; group->baseY = (gCurrentPinballGame->unk4E4[i] / 10) + (gCurrentPinballGame->unk4EC[i].y / 10) - gCurrentPinballGame->unk5A; From 725e0ee12f2e8f3a22c9652a2f4f86e0f9f49090 Mon Sep 17 00:00:00 2001 From: Retnuhytnuob Date: Sat, 28 Feb 2026 03:17:39 -0600 Subject: [PATCH 07/10] standardize param name oamshape --- tools/gbagfx/main.c | 20 +++----------------- tools/scripts/generate_graphics_rules.sh | 4 ++-- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/tools/gbagfx/main.c b/tools/gbagfx/main.c index 272ecc11..604a1429 100755 --- a/tools/gbagfx/main.c +++ b/tools/gbagfx/main.c @@ -190,21 +190,7 @@ void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **a else if (strcmp(option, "-oam") == 0) { options.oamSprite = 1; } - else if (strcmp(option, "-oam-seq-file") == 0) { - if (i + 1 >= argc) - FATAL_ERROR("No oam sequence file path following \"-oam-seq-file\".\n"); - - i++; - options.oamSequenceFilePath = argv[i]; - } - else if (strcmp(option, "-oam-shape") == 0 || strcmp(option, "-oamshape") == 0) { - if (i + 1 >= argc) - FATAL_ERROR("No oam shape file path following \"-oam-shape\".\n"); - - i++; - options.oamSequenceFilePath = argv[i]; - } - else if (strcmp(option, "-oam-shape") == 0 || strcmp(option, "-oamshape") == 0) { + else if (strcmp(option, "-oamshape") == 0) { if (i + 1 >= argc) FATAL_ERROR("No oam shape file path following \"-oam-shape\".\n"); @@ -324,9 +310,9 @@ void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **a else if (strcmp(option, "-oam") == 0) { options.oamSprite = 1; } - else if (strcmp(option, "-oam-seq-file") == 0) { + else if (strcmp(option, "-oamshape") == 0) { if (i + 1 >= argc) - FATAL_ERROR("No oam sequence file path following \"-oam-seq-file\".\n"); + FATAL_ERROR("No oam sequence file path following \"-oamshape\".\n"); i++; options.oamSequenceFilePath = argv[i]; diff --git a/tools/scripts/generate_graphics_rules.sh b/tools/scripts/generate_graphics_rules.sh index e7bfa43e..a61e8744 100755 --- a/tools/scripts/generate_graphics_rules.sh +++ b/tools/scripts/generate_graphics_rules.sh @@ -63,7 +63,7 @@ emit_rules() { flags="$flags -oam" fi if [ -n "$seg_oamshape" ] && [ "$seg_oamshape" != "null" ]; then - flags="$flags -oam-seq-file $dir/$seg_oamshape" + flags="$flags -oamshape $dir/$seg_oamshape" fi printf '\t$(GFX) %s %s%s; \\\n' "$seg_png" "$seg_4bpp" "$flags" done @@ -94,7 +94,7 @@ emit_rules() { flags="$flags -oam" fi if [ -n "$oamshape" ] && [ "$oamshape" != "null" ]; then - flags="$flags -oam-seq-file $dir/$oamshape" + flags="$flags -oamshape $dir/$oamshape" fi # Skip if no special flags and no alignment — the generic From 4dce2816a9d265d6500cccd98cdf8e96e9e2dc6a Mon Sep 17 00:00:00 2001 From: Retnuhytnuob Date: Sat, 28 Feb 2026 03:36:26 -0600 Subject: [PATCH 08/10] simplify use of shape json --- graphics/stage/groudon/groudon_stage_gfx.json | 24 +------------------ graphics/stage/ruby/ruby_gfx.json | 9 ------- tools/gbagfx/gfx.c | 9 +++++++ tools/gbagfx/main.c | 6 +++-- tools/gbagfx/oam_slices/oam_slicer.c | 2 ++ tools/gbagfx/oam_slices/oam_slicer.h | 2 ++ 6 files changed, 18 insertions(+), 34 deletions(-) diff --git a/graphics/stage/groudon/groudon_stage_gfx.json b/graphics/stage/groudon/groudon_stage_gfx.json index 54a7020e..3b6daeca 100644 --- a/graphics/stage/groudon/groudon_stage_gfx.json +++ b/graphics/stage/groudon/groudon_stage_gfx.json @@ -84,9 +84,6 @@ }, { "segfile": "intro_sprite_boulders", - "oam": true, - "mwidth": 5, - "mheight": 5, "oamshape": "boulder_shape.json" }, { @@ -95,9 +92,6 @@ }, { "segfile": "intro_sprite_crystals", - "oam": true, - "mwidth": 3, - "mheight": 3, "oamshape": "crystal_shape.json" }, { @@ -114,9 +108,6 @@ }, { "segfile": "intro_sprite_head_1", - "oam": true, - "mwidth": 4, - "mheight": 4, "oamshape": "groudon_head_shape.json" }, { @@ -133,9 +124,6 @@ }, { "segfile": "intro_sprite_head_2", - "oam": true, - "mwidth": 4, - "mheight": 4, "oamshape": "groudon_head_shape.json" }, { @@ -176,9 +164,6 @@ }, { "segfile": "intro_sprite_face_turn", - "oam": true, - "mwidth": 4, - "mheight": 4, "oamshape": "groudon_head_shape.json" }, { @@ -191,9 +176,6 @@ }, { "segfile": "intro_sprite_face_yell", - "oam": true, - "mwidth": 4, - "mheight": 4, "oamshape": "groudon_head_shape.json" }, { @@ -236,11 +218,7 @@ }, { "gfx_filename": "boulders", - "oam": true, - "oamshape": "boulder_shape.json", - "mwidth": 5, - "mheight": 5, - "width": 10 + "oamshape": "boulder_shape.json" } ] } diff --git a/graphics/stage/ruby/ruby_gfx.json b/graphics/stage/ruby/ruby_gfx.json index e61bb8ec..ba81a39e 100644 --- a/graphics/stage/ruby/ruby_gfx.json +++ b/graphics/stage/ruby/ruby_gfx.json @@ -18,9 +18,6 @@ }, { "gfx_filename": "sharpedo", - "oam": true, - "mwidth": 5, - "mheight": 6, "oamshape": "sharpedo_shape.json" }, { @@ -170,16 +167,10 @@ }, { "segfile": "intro_sprite_linoone", - "oam": true, - "mwidth": 3, - "mheight": 3, "oamshape": "linoone_shape.json" }, { "segfile": "intro_sprite_sharpedo", - "oam": true, - "mwidth": 5, - "mheight": 6, "oamshape": "sharpedo_shape.json" }, { diff --git a/tools/gbagfx/gfx.c b/tools/gbagfx/gfx.c index 7f4d4e88..c5d4f766 100755 --- a/tools/gbagfx/gfx.c +++ b/tools/gbagfx/gfx.c @@ -566,6 +566,13 @@ void ReadTileImage(char *path, int tilesWidth, int metatileWidth, int metatileHe int cnt = oam_sequence_load_from_file(oamSequenceFilePath, &oamSeq); if (cnt < 0) FATAL_ERROR("Failed to read oam sequence file: %s\n", oamSequenceFilePath); + metatileHeight = oamSeq->png_height; + metatileWidth = oamSeq->png_width; + + //Allow passing this in; default it otherwise + if (tilesWidth == 1){ + tilesWidth = metatileWidth; + } } int tilesHeight = (numTiles + tilesWidth - 1) / tilesWidth; @@ -637,6 +644,8 @@ void WriteTileImage(char *path, enum NumTilesMode numTilesMode, int numTiles, in int cnt = oam_sequence_load_from_file(oamSequenceFilePath, &oamSeq); if (cnt < 0) FATAL_ERROR("Failed to read oam sequence file: %s\n", oamSequenceFilePath); + metatileHeight = oamSeq->png_height; + metatileWidth = oamSeq->png_width; } int maxNumTiles = tilesWidth * tilesHeight; diff --git a/tools/gbagfx/main.c b/tools/gbagfx/main.c index 604a1429..d0d0ccca 100755 --- a/tools/gbagfx/main.c +++ b/tools/gbagfx/main.c @@ -195,6 +195,7 @@ void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **a FATAL_ERROR("No oam shape file path following \"-oam-shape\".\n"); i++; + options.oamSprite = 1; options.oamSequenceFilePath = argv[i]; } else if (strcmp(option, "-tilemap") == 0) @@ -230,7 +231,7 @@ void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **a } } - if (options.oamSprite){ + if (options.oamSprite && options.oamSequenceFilePath == NULL){ if (options.metatileHeight == 1 && options.metatileWidth == 1){ FATAL_ERROR("Must specify metatile dimensions when using oam chunk mapping.\n"); } @@ -315,6 +316,7 @@ void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **a FATAL_ERROR("No oam sequence file path following \"-oamshape\".\n"); i++; + options.oamSprite = 1; options.oamSequenceFilePath = argv[i]; } else if (strcmp(option, "-plain") == 0) @@ -339,7 +341,7 @@ void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **a } } - if (options.oamSprite){ + if (options.oamSprite && options.oamSequenceFilePath == NULL){ if (options.metatileHeight == 1 && options.metatileWidth == 1){ FATAL_ERROR("Must specify metatile dimensions when using oam mapping.\n"); } diff --git a/tools/gbagfx/oam_slices/oam_slicer.c b/tools/gbagfx/oam_slices/oam_slicer.c index 1401ad53..19ce6c63 100644 --- a/tools/gbagfx/oam_slices/oam_slicer.c +++ b/tools/gbagfx/oam_slices/oam_slicer.c @@ -150,6 +150,8 @@ int oam_sequence_load_from_file(const char *path, OamSequence **outSeq) { seq->numSegments = idx; seq->tilecount = tilecount; seq->png_tiles = png_tiles; + seq->png_width = png_w; + seq->png_height = png_h; free(buf); if (idx == 0) { free(arr); free(seq); *outSeq = NULL; return 0; } diff --git a/tools/gbagfx/oam_slices/oam_slicer.h b/tools/gbagfx/oam_slices/oam_slicer.h index 2b54be72..42fd6369 100644 --- a/tools/gbagfx/oam_slices/oam_slicer.h +++ b/tools/gbagfx/oam_slices/oam_slicer.h @@ -55,6 +55,8 @@ typedef struct OamSequence { int numSegments; int tilecount; // sum of non-spacer tiles (or -1 if not provided) int png_tiles; // pngsize in tiles (w*h) if provided, 0 otherwise + int png_height; + int png_width; } OamSequence; // Load an OAM sequence from a JSON file. Returns number of segments (>0), From 271ad7628aa75ce52bba31daa275cfaefd81da8a Mon Sep 17 00:00:00 2001 From: Retnuhytnuob Date: Sat, 28 Feb 2026 04:02:09 -0600 Subject: [PATCH 09/10] remove ruby intro pieces that are no longer needed as separate pieces --- graphics/stage/ruby/intro_sprite_linoone1.png | Bin 185 -> 0 bytes graphics/stage/ruby/intro_sprite_linoone1_arm.png | Bin 101 -> 0 bytes graphics/stage/ruby/intro_sprite_linoone2.png | Bin 185 -> 0 bytes graphics/stage/ruby/intro_sprite_linoone2_arm.png | Bin 101 -> 0 bytes .../stage/ruby/intro_sprite_sharpedo_body.png | Bin 340 -> 0 bytes .../stage/ruby/intro_sprite_sharpedo_side_fin.png | Bin 88 -> 0 bytes .../stage/ruby/intro_sprite_sharpedo_top_fin.png | Bin 104 -> 0 bytes 7 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 graphics/stage/ruby/intro_sprite_linoone1.png delete mode 100644 graphics/stage/ruby/intro_sprite_linoone1_arm.png delete mode 100644 graphics/stage/ruby/intro_sprite_linoone2.png delete mode 100644 graphics/stage/ruby/intro_sprite_linoone2_arm.png delete mode 100644 graphics/stage/ruby/intro_sprite_sharpedo_body.png delete mode 100644 graphics/stage/ruby/intro_sprite_sharpedo_side_fin.png delete mode 100644 graphics/stage/ruby/intro_sprite_sharpedo_top_fin.png diff --git a/graphics/stage/ruby/intro_sprite_linoone1.png b/graphics/stage/ruby/intro_sprite_linoone1.png deleted file mode 100644 index 8ea43906c0d7a775a9540317c4c74377a349f645..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 185 zcmeAS@N?(olHy`uVBq!ia0vp^5-+A3b9S&#FE}-PId6Xo((5e(@qBM?|<;BSnCvc}qaY zrZ1n|95-(HQenW(ajCIrk%3>>flqFQbsFluh6;Nb53K~MPk5ctD#6Q`z-(o7#385S iqHL$6?8=1+-|86-{-{0?R^w6va+9a4pUXO@geCyOCrLg4 diff --git a/graphics/stage/ruby/intro_sprite_linoone1_arm.png b/graphics/stage/ruby/intro_sprite_linoone1_arm.png deleted file mode 100644 index 6c049ddd1d9b364d1b7b2b049dac9f5033dfe6dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 101 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0wfp&-k$XWQaYY4jv*W~lV5mn^Y;G#|KHx( y#ogUK%5K6((>;rQ19(pS_-`x1;lQJD;4lM&f0}W;?Uj#`ApM@MelF{r5}E+~W*vqA diff --git a/graphics/stage/ruby/intro_sprite_linoone2.png b/graphics/stage/ruby/intro_sprite_linoone2.png deleted file mode 100644 index 8ea43906c0d7a775a9540317c4c74377a349f645..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 185 zcmeAS@N?(olHy`uVBq!ia0vp^5-+A3b9S&#FE}-PId6Xo((5e(@qBM?|<;BSnCvc}qaY zrZ1n|95-(HQenW(ajCIrk%3>>flqFQbsFluh6;Nb53K~MPk5ctD#6Q`z-(o7#385S iqHL$6?8=1+-|86-{-{0?R^w6va+9a4pUXO@geCyOCrLg4 diff --git a/graphics/stage/ruby/intro_sprite_linoone2_arm.png b/graphics/stage/ruby/intro_sprite_linoone2_arm.png deleted file mode 100644 index 6c049ddd1d9b364d1b7b2b049dac9f5033dfe6dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 101 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c0wfp&-k$XWQaYY4jv*W~lV5mn^Y;G#|KHx( y#ogUK%5K6((>;rQ19(pS_-`x1;lQJD;4lM&f0}W;?Uj#`ApM@MelF{r5}E+~W*vqA diff --git a/graphics/stage/ruby/intro_sprite_sharpedo_body.png b/graphics/stage/ruby/intro_sprite_sharpedo_body.png deleted file mode 100644 index 9d8d7cf4ea977262ee3d1f070cd434681c7975a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 340 zcmV-a0jvIrP)>{6ugfo*!BQhRA6Rs24U88H{7i<*dvS?1b7!W zX`-SXN`AWY{cRCbm1IRO5)2kPp6J~1>#|{L2xf(kHg1OkNM`*#!R@>PK mQQdB~@pl5px;cmVPxu3dHkQMi^<9nt0000}Qjv*W~lYj8D@K|(Qc6ju= zR3Wfh#%&I((Eaxfoqru3nFZXPfBUxNzw7fX`P3LH&so_>u`maMjPP{zb6Mw<&;$T+ CrypDZ From 31722f7f6121fe40b36a483dd355051c97bc9e46 Mon Sep 17 00:00:00 2001 From: Retnuhytnuob Date: Sun, 22 Mar 2026 20:41:46 -0500 Subject: [PATCH 10/10] ball opening to catch --- data/rom_1.s | 3 +- graphics/stage/main/ball_open_to_catch.json | 41 ++++++++++++++++++++ graphics/stage/main/ball_open_to_catch.png | Bin 0 -> 599 bytes graphics/stage/main/main_stage_gfx.json | 4 ++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 graphics/stage/main/ball_open_to_catch.json create mode 100644 graphics/stage/main/ball_open_to_catch.png diff --git a/data/rom_1.s b/data/rom_1.s index a170ccc5..a1fe3f57 100644 --- a/data/rom_1.s +++ b/data/rom_1.s @@ -706,7 +706,8 @@ gBallFlashPalette:: @ 0x08137F14 .incbin "baserom.gba", 0x137F14, 0x100 gBallUpgradeTilesGfx:: @ 0x08138014 - .incbin "baserom.gba", 0x138014, 0x820 + .incbin "graphics/stage/main/ball_open_to_catch.4bpp" + .space 0x20 gDusclopsBonusClear_Gfx:: @ 0x08138834 .incbin "graphics/stage/dusclops/dusclops_bonus_clear.4bpp" diff --git a/graphics/stage/main/ball_open_to_catch.json b/graphics/stage/main/ball_open_to_catch.json new file mode 100644 index 00000000..ee0f479d --- /dev/null +++ b/graphics/stage/main/ball_open_to_catch.json @@ -0,0 +1,41 @@ +{ + "kind": "oam-shape", + "version":1, + "pngsize": "3x6", + "pieces": [ + { + "pos":"0,0", + "size": "2x2" + }, + { + "pos":"2,0", + "size": "1x2" + }, + { + "pos":"0,2", + "size": "2x1" + }, + { + "pos":"2,2", + "size": "1x1" + "spacer":true + }, + { + "pos":"0,3", + "size": "2x2" + }, + { + "pos":"2,3", + "size": "1x2" + }, + { + "pos":"0,5", + "size": "1x1" + "spacer":true + }, + { + "pos":"1,5", + "size": "2x1" + } + ] +} \ No newline at end of file diff --git a/graphics/stage/main/ball_open_to_catch.png b/graphics/stage/main/ball_open_to_catch.png new file mode 100644 index 0000000000000000000000000000000000000000..e6b081534f62db7b935265548d0374431f3986fe GIT binary patch literal 599 zcmV-d0;v6oP)D~ZxcivnGSVdx(1Th*C8vJ}7x=x%_ugoGufZGb zJ9tw!o95L)bHn+@dRxk`E6%y?4S^Fli8OC~Z#0MA_Be;$_Hl1*)Eit+Zz}SJ?a1J* zd0A~jZ)z31rSc_K^^NrS-biTZjTT&QX^P%_Z?N6V%J(*jG)k;sQB_F{(?8dnbY$LB zaF(gJXK*Ky#9aV|#p8!haFvzi+ABMc#I?H;BDK#ga~o9}I@ lRChb~mVKFf`*an({RFJtkX@Et?u7sV002ovPDHLkV1fyyDUbjF literal 0 HcmV?d00001 diff --git a/graphics/stage/main/main_stage_gfx.json b/graphics/stage/main/main_stage_gfx.json index fb37b61a..b56345d2 100644 --- a/graphics/stage/main/main_stage_gfx.json +++ b/graphics/stage/main/main_stage_gfx.json @@ -158,6 +158,10 @@ { "gfx_filename": "pokeball_master", "mwidth":2 + }, + { + "gfx_filename": "ball_open_to_catch", + "oamshape": "ball_open_to_catch.json" } ] }