diff --git a/include/decompress.h b/include/decompress.h index 18340d54b2..6c66b738f9 100644 --- a/include/decompress.h +++ b/include/decompress.h @@ -8,6 +8,8 @@ extern u8 ALIGNED(4) gDecompressionBuffer[0x4000]; void LZDecompressWram(const u32 *src, void *dest); void LZDecompressVram(const u32 *src, void *dest); +bool32 IsLZ77Data(const void *ptr, u32 minSize, u32 maxSize); + u16 LoadCompressedSpriteSheet(const struct CompressedSpriteSheet *src); void LoadCompressedSpriteSheetOverrideBuffer(const struct CompressedSpriteSheet *src, void *buffer); bool8 LoadCompressedSpriteSheetUsingHeap(const struct CompressedSpriteSheet *src); diff --git a/src/data/object_events/object_event_graphics_info_followers.h b/src/data/object_events/object_event_graphics_info_followers.h index eaa9e737f9..f0ccf078b9 100644 --- a/src/data/object_events/object_event_graphics_info_followers.h +++ b/src/data/object_events/object_event_graphics_info_followers.h @@ -426,6 +426,17 @@ const struct ObjectEventGraphicsInfo gCastformObjectGraphics[] = {TAG_NONE, OBJ_EVENT_PAL_TAG_CASTFORM_SNOWY, OBJ_EVENT_PAL_TAG_NONE, 512, 32, 32, 2, SHADOW_SIZE_M, FALSE, FALSE, TRACKS_NONE, &gObjectEventBaseOam_32x32, sOamTables_32x32, sAnimTable_Following, sPicTable_CastformSnowy, gDummySpriteAffineAnimTable}, }; +// Standalone follower palettes +// If not NULL, entries here override the front-sprite-based pals +// used by OBJ_EVENT_PAL_TAG_DYNAMIC +// Palette data may be compressed, or not +const void* const gFollowerPalettes[][2] = +{ + // Must have at least one entry, or ARRAY_COUNT comparison fails + // (SPECIES_NONE does not use OBJ_EVENT_PAL_TAG_DYNAMIC anyway) + [SPECIES_NONE] = {gMonPalette_CircledQuestionMark, gMonShinyPalette_CircledQuestionMark}, +}; + #if OW_MON_POKEBALLS #define POKEBALL_GFX_INFO(NAME) \ diff --git a/src/decompress.c b/src/decompress.c index 8452f340ed..248ce346e7 100644 --- a/src/decompress.c +++ b/src/decompress.c @@ -19,6 +19,24 @@ void LZDecompressVram(const u32 *src, void *dest) LZ77UnCompVram(src, dest); } +// Checks if `ptr` is likely LZ77 data +// Checks word-alignment, min/max size, and header byte +bool32 IsLZ77Data(const void *ptr, u32 minSize, u32 maxSize) { + const u8 *data = ptr; + u32 size; + // Compressed data must be word aligned + if (((u32)ptr) & 3) + return FALSE; + // Check LZ77 header byte + // See https://problemkaputt.de/gbatek.htm#biosdecompressionfunctions + if (data[0] != 0x10) + return FALSE; + + // Read 24-bit uncompressed size + size = data[1] | (data[2] << 8) | (data[3] << 16); + return (size >= minSize && size <= maxSize); +} + u16 LoadCompressedSpriteSheet(const struct CompressedSpriteSheet *src) { struct SpriteSheet dest; diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 3e3e1f0272..4ee9b5a988 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -19,6 +19,7 @@ #include "fieldmap.h" #include "follower_helper.h" #include "gpu_regs.h" +#include "graphics.h" #include "mauville_old_man.h" #include "metatile_behavior.h" #include "overworld.h" @@ -189,7 +190,7 @@ static u8 DoJumpSpecialSpriteMovement(struct Sprite *); static void CreateLevitateMovementTask(struct ObjectEvent *); static void DestroyLevitateMovementTask(u8); static bool8 GetFollowerInfo(u16 *species, u8 *form, u8 *shiny); -static u8 LoadDynamicFollowerPalette(u16 species, u8 form, bool8 shiny); +static u8 LoadDynamicFollowerPalette(u16 species, u8 form, bool32 shiny); static const struct ObjectEventGraphicsInfo * SpeciesToGraphicsInfo(u16 species, u8 form); static bool8 NpcTakeStep(struct Sprite *); static bool8 IsElevationMismatchAt(u8, s16, s16); @@ -1698,7 +1699,7 @@ u8 CreateObjectGraphicsSprite(u16 graphicsId, void (*callback)(struct Sprite *), u16 species; u8 form; bool8 shiny; - u8 paletteNum; + u32 paletteNum; spriteTemplate = Alloc(sizeof(struct SpriteTemplate)); if (graphicsId == OBJ_EVENT_GFX_OW_MON && GetFollowerInfo(&species, &form, &shiny)) { @@ -1715,9 +1716,8 @@ u8 CreateObjectGraphicsSprite(u16 graphicsId, void (*callback)(struct Sprite *), CopyObjectGraphicsInfoToSpriteTemplate(graphicsId, callback, spriteTemplate, &subspriteTables); if (spriteTemplate->paletteTag == OBJ_EVENT_PAL_TAG_DYNAMIC) { - const struct CompressedSpritePalette *spritePalette = &(shiny ? gMonShinyPaletteTable : gMonPaletteTable)[species]; paletteNum = LoadDynamicFollowerPalette(species, form, shiny); - spriteTemplate->paletteTag = spritePalette->tag; + spriteTemplate->paletteTag = GetSpritePaletteTagByPaletteNum(paletteNum); } else if (spriteTemplate->paletteTag != TAG_NONE) LoadObjectEventPalette(spriteTemplate->paletteTag); @@ -1825,18 +1825,31 @@ static const struct ObjectEventGraphicsInfo * SpeciesToGraphicsInfo(u16 species, } // Find, or load, the palette for the specified pokemon info -static u8 LoadDynamicFollowerPalette(u16 species, u8 form, bool8 shiny) { +static u8 LoadDynamicFollowerPalette(u16 species, u8 form, bool32 shiny) { u32 paletteNum; // Note that the shiny palette tag is `species + SPECIES_SHINY_TAG`, which must be increased with more pokemon // so that palette tags do not overlap - const struct CompressedSpritePalette *spritePalette = &(shiny ? gMonShinyPaletteTable : gMonPaletteTable)[species]; - if ((paletteNum = IndexOfSpritePaletteTag(spritePalette->tag)) == 0xFF) { - // Load compressed palette - LoadCompressedSpritePalette(spritePalette); - paletteNum = IndexOfSpritePaletteTag(spritePalette->tag); // Tag is always present - if (gWeatherPtr->currWeather != WEATHER_FOG_HORIZONTAL) // don't want to weather blend in fog - UpdateSpritePaletteWithWeather(paletteNum); + struct SpritePalette spritePalette = {.tag = shiny ? (species + SPECIES_SHINY_TAG) : species}; + // palette already loaded + if ((paletteNum = IndexOfSpritePaletteTag(spritePalette.tag)) < 16) + return paletteNum; + + // Use matching front sprite's normal/shiny palettes + spritePalette.data = (u16*)((shiny ? gMonShinyPaletteTable : gMonPaletteTable)[species].data); + // Use standalone palette, unless entry is OOB or NULL (fallback to front-sprite-based) + if (species < ARRAY_COUNT(gFollowerPalettes) && gFollowerPalettes[species][shiny & 1]) + spritePalette.data = gFollowerPalettes[species][shiny & 1]; + + // Check if pal data must be decompressed + if (IsLZ77Data(spritePalette.data, PLTT_SIZE_4BPP, PLTT_SIZE_4BPP)) { + // IsLZ77Data guarantees word-alignment, so casting this is safe + LZ77UnCompWram((u32*)spritePalette.data, gDecompressionBuffer); + spritePalette.data = (void*)gDecompressionBuffer; } + + paletteNum = LoadSpritePalette(&spritePalette); + if (gWeatherPtr->currWeather != WEATHER_FOG_HORIZONTAL) // don't want to weather blend in fog + UpdateSpritePaletteWithWeather(paletteNum); return paletteNum; }