Fix GameReader::decodePokemonIcon() API inconsistency in relation to decodeSprite()

This commit is contained in:
risingPhil 2024-09-25 22:15:46 +02:00
parent a44b908587
commit bd518d0d87
11 changed files with 371 additions and 257 deletions

View File

@ -10,21 +10,22 @@
#include <cstdlib>
using OutputFormat = SpriteRenderer::OutputFormat;
using TileOrder = SpriteRenderer::TileOrder;
static void decodeGen1Icon(IRomReader& romReader, ISaveManager& saveManager, Gen1GameType gen1Type, Gen1PokemonIconType iconType, bool firstFrame)
{
Gen1GameReader gameReader(romReader, saveManager, gen1Type);
SpriteRenderer renderer;
char fileNameBuf[100];
uint8_t *outputBuffer;
uint8_t* spriteBuffer;
uint8_t* outputBuffer;
outputBuffer = gameReader.decodePokemonIcon(iconType, renderer, OutputFormat::RGB, firstFrame);
if(!outputBuffer)
spriteBuffer = gameReader.decodePokemonIcon(iconType, firstFrame);
if(!spriteBuffer)
{
fprintf(stderr, "ERROR: Could not decode icon for icon type %d and firstFrame %d!\n", (int)iconType, firstFrame);
return;
}
outputBuffer = renderer.draw(spriteBuffer, OutputFormat::RGB, monochromeGBColorPalette, GEN1_ICON_WIDTH_IN_TILES, GEN1_ICON_HEIGHT_IN_TILES);
snprintf(fileNameBuf, sizeof(fileNameBuf), "%d_frame%u.png", (int)iconType, (firstFrame) ? 1 : 2);
write_png(fileNameBuf, outputBuffer, 2 * 8, 2 * 8, false);
}
@ -34,14 +35,16 @@ static void decodeGen2Icon(IRomReader& romReader, ISaveManager& saveManager, Gen
Gen2GameReader gameReader(romReader, saveManager, gen2Type);
SpriteRenderer renderer;
char fileNameBuf[100];
uint8_t *outputBuffer;
uint8_t* spriteBuffer;
uint8_t* outputBuffer;
outputBuffer = gameReader.decodePokemonIcon(iconType, renderer, OutputFormat::RGB, firstFrame);
if(!outputBuffer)
spriteBuffer = gameReader.decodePokemonIcon(iconType, firstFrame);
if(!spriteBuffer)
{
fprintf(stderr, "ERROR: Could not decode icon for icon type %d and firstFrame %d!\n", (int)iconType, firstFrame);
return;
}
outputBuffer = renderer.draw(spriteBuffer, OutputFormat::RGB, gen2_iconColorPalette, GEN2_ICON_WIDTH_IN_TILES, GEN2_ICON_HEIGHT_IN_TILES);
snprintf(fileNameBuf, sizeof(fileNameBuf), "%d_frame%u.png", (int)iconType, (firstFrame) ? 1 : 2);
write_png(fileNameBuf, outputBuffer, 2 * 8, 2 * 8, false);
}

View File

@ -20,15 +20,6 @@ public:
RGBA32
};
/**
* @brief This enum indicates the order the tiles appear in the input data
*/
enum class TileOrder
{
HORIZONTAL,
VERTICAL
};
SpriteRenderer();
/**
@ -42,16 +33,7 @@ public:
* NOTE: the next call to this function or drawTile() overwrites the data inside the returned buffer. So if you want to keep this data around, copy it.
* You don't need to free() the data.
*/
uint8_t* draw(const uint8_t* spriteBuffer, OutputFormat format, uint16_t palette[4], uint8_t numHTiles, uint8_t numVTiles, TileOrder tileOrder = TileOrder::VERTICAL);
/**
* @brief This function draws the given buffer that supposedly contains a tile (16 bytes at 2BPP) to the internal buffer_ at the given [xOffset, yOffset] and
* returns this internal buffer_
*
* NOTE: the next call to this function or draw() overwrites the data inside the returned buffer. So if you want to keep this data around, copy it.
* You don't need to free() the data.
*/
uint8_t* drawTile(const uint8_t* tileBuffer, OutputFormat format, uint16_t palette[4], uint16_t xOffset, uint16_t yOffset, uint8_t outputBufferHTiles, bool mirrorHorizontally = false);
uint8_t* draw(const uint8_t* spriteBuffer, OutputFormat format, uint16_t palette[4], uint8_t numHTiles, uint8_t numVTiles);
/**
* @brief This function removes the white background in the current internal buffer_ and returns this buffer
@ -62,7 +44,7 @@ public:
uint8_t* removeWhiteBackground(uint8_t numHTiles, uint8_t numVTiles);
protected:
private:
void internalDrawTile(const uint8_t* tileBuffer, uint16_t xOffset, uint16_t yOffset, uint16_t spriteBufferWidthInPixels, bool mirrorHorizontally);
void internalDrawTile(const uint8_t* tileBuffer, uint16_t xOffset, uint16_t yOffset, uint16_t spriteBufferWidthInPixels);
void setFormatAndPalette(OutputFormat format, uint16_t palette[4]);

View File

@ -2,9 +2,9 @@
#define GEN1GAMEREADER_H
#include "gen1/Gen1SpriteDecoder.h"
#include "gen1/Gen1IconDecoder.h"
#include "gen1/Gen1PlayerPokemonStorage.h"
#include "gen1/Gen1DistributionPokemon.h"
#include "SpriteRenderer.h"
class IRomReader;
class ISaveManager;
@ -120,10 +120,14 @@ public:
uint8_t* decodeSprite(uint8_t bankIndex, uint16_t pointer);
/**
* @brief This function decodes the given pokemon icon and returns the internal buffer of the given SpriteRenderer instance
* The size of the returned buffer is 16x16 pixels
* @brief This function decodes the given pokemon icon and returns an internal buffer
* Note that this returns a 16x16 buffer in gameboy format with tiles in vertical order.
* You need to feed it to an instance of SpriteRenderer to convert it to a useful format
*
* WARNING: this function returns a buffer to an internal buffer of Gen1IconDecoder. That means it will get overwritten on the next call to this function.
* If you want to keep the content around for longer, make a copy of this data
*/
uint8_t* decodePokemonIcon(Gen1PokemonIconType iconType, SpriteRenderer& renderer, SpriteRenderer::OutputFormat outputFormat, bool firstFrame = true);
uint8_t* decodePokemonIcon(Gen1PokemonIconType iconType, bool firstFrame = true);
/**
* @brief Adds a pokemon to the save. Tries to add it to the party first. If there's no more room there, it tries to add it to the
@ -180,6 +184,7 @@ private:
IRomReader& romReader_;
ISaveManager& saveManager_;
Gen1SpriteDecoder spriteDecoder_;
Gen1IconDecoder iconDecoder_;
Gen1GameType gameType_;
};

View File

@ -0,0 +1,38 @@
#ifndef _GEN1POKEMONICONDECODER_H
#define _GEN1POKEMONICONDECODER_H
#include "gen1/Gen1Common.h"
#define GEN1_TILE_BITS_PER_PIXEL 2
#define GEN1_BYTES_PER_TILE 16
#define GEN1_ICON_WIDTH_IN_TILES 2
#define GEN1_ICON_HEIGHT_IN_TILES 2
#define GEN1_ICON_NUM_BYTES GEN1_BYTES_PER_TILE * 4
class IRomReader;
/**
* @brief This class is responsible for decoding pokemon party menu icons for Gen 1 games
*/
class Gen1IconDecoder
{
public:
Gen1IconDecoder(IRomReader& romReader, Gen1GameType gameType);
/**
* @brief Decodes the specified icon type. either frame 1 or frame 2.
* This function returns a buffer containing gameboy tiles.
*
* The result is supposed to be fed to an instance of the SpriteRenderer class to convert it into an actual usable format
*
* @return Internal buffer containing the icon tiles in vertical tile order.
*/
uint8_t* decode(Gen1PokemonIconType iconType, bool firstFrame);
protected:
private:
uint8_t buffer_[GEN1_ICON_NUM_BYTES];
IRomReader& romReader_;
Gen1GameType gameType_;
};
#endif

View File

@ -2,9 +2,9 @@
#define _GEN2GAMEREADER_H
#include "gen2/Gen2SpriteDecoder.h"
#include "gen2/Gen2IconDecoder.h"
#include "gen2/Gen2PlayerPokemonStorage.h"
#include "gen2/Gen2DistributionPokemon.h"
#include "SpriteRenderer.h"
class IRomReader;
class ISaveManager;
@ -75,10 +75,14 @@ public:
uint8_t *decodeSprite(uint8_t bankIndex, uint16_t pointer);
/**
* @brief This function decodes the given pokemon icon and returns the internal buffer of the given SpriteRenderer instance
* The size of the returned buffer is 16x16 pixels
* @brief This function decodes the given pokemon icon and returns an internal buffer
* Note that this returns a 16x16 buffer in gameboy format with tiles in vertical order.
* You need to feed it to an instance of SpriteRenderer to convert it to a useful format
*
* WARNING: this function returns a buffer to an internal buffer of Gen2IconDecoder. That means it will get overwritten on the next call to this function.
* If you want to keep the content around for longer, make a copy of this data
*/
uint8_t* decodePokemonIcon(Gen2PokemonIconType iconType, SpriteRenderer& renderer, SpriteRenderer::OutputFormat outputFormat, bool firstFrame = true);
uint8_t* decodePokemonIcon(Gen2PokemonIconType iconType, bool firstFrame = true);
/**
* @brief Adds a pokemon to the save. Tries to add it to the party first. If there's no more room there, it tries to add it to the
@ -227,6 +231,7 @@ private:
IRomReader &romReader_;
ISaveManager &saveManager_;
Gen2SpriteDecoder spriteDecoder_;
Gen2IconDecoder iconDecoder_;
Gen2GameType gameType_;
};

View File

@ -0,0 +1,38 @@
#ifndef _GEN2ICONDECODER_H
#define _GEN2ICONDECODER_H
#include "gen2/Gen2Common.h"
#define GEN2_TILE_BITS_PER_PIXEL 2
#define GEN2_BYTES_PER_TILE 16
#define GEN2_ICON_WIDTH_IN_TILES 2
#define GEN2_ICON_HEIGHT_IN_TILES 2
#define GEN2_ICON_NUM_BYTES GEN2_BYTES_PER_TILE * 4
class IRomReader;
/**
* @brief This class is responsible for decoding pokemon party menu icons for Gen 2 games
*/
class Gen2IconDecoder
{
public:
Gen2IconDecoder(IRomReader& romReader, Gen2GameType gameType);
/**
* @brief Decodes the specified icon type. either frame 1 or frame 2.
* This function returns a buffer containing gameboy tiles.
*
* The result is supposed to be fed to an instance of the SpriteRenderer class to convert it into an actual usable format
*
* @return Internal buffer containing the icon tiles in vertical tile order.
*/
uint8_t* decode(Gen2PokemonIconType iconType, bool firstFrame);
protected:
private:
uint8_t buffer_[GEN2_ICON_NUM_BYTES];
IRomReader& romReader_;
Gen2GameType gameType_;
};
#endif

View File

@ -458,7 +458,7 @@ uint32_t SpriteRenderer::getNumRGBBytesFor(uint8_t numHTiles, uint8_t numVTiles)
return numPixels * 3;
}
uint8_t* SpriteRenderer::draw(const uint8_t* spriteBuffer, OutputFormat format, uint16_t palette[4], uint8_t numHTiles, uint8_t numVTiles, TileOrder tileOrder)
uint8_t* SpriteRenderer::draw(const uint8_t* spriteBuffer, OutputFormat format, uint16_t palette[4], uint8_t numHTiles, uint8_t numVTiles)
{
const uint16_t spriteBufferHeightInPixels = numVTiles * 8;
const uint16_t spriteBufferWidthInPixels = numHTiles * 8;
@ -471,25 +471,13 @@ uint8_t* SpriteRenderer::draw(const uint8_t* spriteBuffer, OutputFormat format,
while(i < spriteBufferSize)
{
internalDrawTile(spriteBuffer + i, x, y, spriteBufferWidthInPixels, false);
internalDrawTile(spriteBuffer + i, x, y, spriteBufferWidthInPixels);
if(tileOrder == TileOrder::VERTICAL)
{
y += 8;
if(y >= spriteBufferHeightInPixels)
{
x += 8;
y = 0;
}
}
else
y += 8;
if(y >= spriteBufferHeightInPixels)
{
x += 8;
if(x >= spriteBufferWidthInPixels)
{
x = 0;
y += 8;
}
y = 0;
}
i += BYTES_PER_TILE;
@ -498,16 +486,6 @@ uint8_t* SpriteRenderer::draw(const uint8_t* spriteBuffer, OutputFormat format,
return buffer_;
}
uint8_t* SpriteRenderer::drawTile(const uint8_t* tileBuffer, OutputFormat format, uint16_t palette[4], uint16_t xOffset, uint16_t yOffset, uint8_t outputBufferHTiles, bool mirrorHorizontally)
{
const uint16_t spriteBufferWidthInPixels = outputBufferHTiles * 8;
setFormatAndPalette(format, palette);
internalDrawTile(tileBuffer, xOffset, yOffset, spriteBufferWidthInPixels, mirrorHorizontally);
return buffer_;
}
uint8_t* SpriteRenderer::removeWhiteBackground(uint8_t numHTiles, uint8_t numVTiles)
{
if(format_ == OutputFormat::RGB)
@ -523,7 +501,7 @@ uint8_t* SpriteRenderer::removeWhiteBackground(uint8_t numHTiles, uint8_t numVTi
return buffer_;
}
void SpriteRenderer::internalDrawTile(const uint8_t* tileBuffer, uint16_t xOffset, uint16_t yOffset, uint16_t spriteBufferWidthInPixels, bool mirrorHorizontally)
void SpriteRenderer::internalDrawTile(const uint8_t* tileBuffer, uint16_t xOffset, uint16_t yOffset, uint16_t spriteBufferWidthInPixels)
{
uint32_t offset;
uint32_t i = 0;
@ -544,16 +522,8 @@ void SpriteRenderer::internalDrawTile(const uint8_t* tileBuffer, uint16_t xOffse
{
offset = ((yOffset + y) * spriteBufferWidthInPixels + xOffset + x) * numColorComponents_;
if(!mirrorHorizontally)
{
bit0 = (byte0 >> (7 - x)) & 0x1;
bit1 = (byte1 >> (7 - x)) & 0x1;
}
else
{
bit0 = (byte0 >> x) & 0x1;
bit1 = (byte1 >> x) & 0x1;
}
bit0 = (byte0 >> (7 - x)) & 0x1;
bit1 = (byte1 >> (7 - x)) & 0x1;
paletteIndex = (bit1 << 1) | bit0;

View File

@ -51,6 +51,7 @@ Gen1GameReader::Gen1GameReader(IRomReader &romReader, ISaveManager &saveManager,
: romReader_(romReader)
, saveManager_(saveManager)
, spriteDecoder_(romReader_)
, iconDecoder_(romReader, gameType)
, gameType_(gameType)
{
}
@ -455,159 +456,9 @@ uint8_t* Gen1GameReader::decodeSprite(uint8_t bankIndex, uint16_t pointer)
return spriteDecoder_.decode(bankIndex, pointer);
}
/*
* In gen 1, the behaviour of the party icons is a bit messy.
* There's the MonPartySpritePointers of the various icon sprites. But while some of these sprites are stored fully,
* for some only the left half is stored. (and the code is supposed to mirror this half while rendering)
* In fact, for the latter, the top and bottom left tiles are separate entries in the table. This is not the case for the icons that are
* stored fully.
*
* There's also no real flag to indicate this.
*
* 2 icons don't even have a second frame stored. As a matter of fact, the HELIX icon doesn't even have an entry in the table at all!
*
* Anyway, we need to deal with this mess and that makes our code below a bit messy as well.
*/
uint8_t* Gen1GameReader::decodePokemonIcon(Gen1PokemonIconType iconType, SpriteRenderer& renderer, SpriteRenderer::OutputFormat outputFormat, bool firstFrame)
uint8_t* Gen1GameReader::decodePokemonIcon(Gen1PokemonIconType iconType, bool firstFrame)
{
const uint32_t romOffset = (gameType_ == Gen1GameType::YELLOW) ? 0x7184D : 0x717C0;
const uint8_t MAX_NUM_TILES = 8;
const uint8_t TILE_WIDTH = 8;
const uint8_t TILE_HEIGHT = 8;
const uint8_t BITS_PER_PIXEL = 2;
const uint8_t BYTES_PER_TILE = ((TILE_WIDTH * TILE_HEIGHT) / 8) * BITS_PER_PIXEL;
const uint32_t ENTRY_SIZE = 6;
uint8_t iconSrcBuffer[MAX_NUM_TILES * BYTES_PER_TILE];
uint16_t pointer;
uint8_t numTiles; // total number of tiles of the sprite (to indicate byte size)
uint8_t bankIndex;
uint16_t vSpritesTileOffset;
uint8_t entryIndex;
// this boolean indicates that the sprite only stores the left half, but it's symmetric
bool isSymmetric;
switch(iconType)
{
case GEN1_ICONTYPE_MON:
entryIndex = (firstFrame) ? 14 : 0;
isSymmetric = false;
break;
case GEN1_ICONTYPE_BALL:
entryIndex = (firstFrame) ? 1 : 0xFF;
isSymmetric = false;
break;
case GEN1_ICONTYPE_FAIRY:
entryIndex = (firstFrame) ? 16 : 2;
isSymmetric = false;
break;
case GEN1_ICONTYPE_BIRD:
entryIndex = (firstFrame) ? 17 : 3;
isSymmetric = false;
break;
case GEN1_ICONTYPE_WATER:
entryIndex = (firstFrame) ? 4 : 18;
isSymmetric = false;
break;
case GEN1_ICONTYPE_BUG:
entryIndex = (firstFrame) ? 19 : 5;
isSymmetric = true;
break;
case GEN1_ICONTYPE_GRASS:
entryIndex = (firstFrame) ? 21 : 7;
isSymmetric = true;
break;
case GEN1_ICONTYPE_SNAKE:
entryIndex = (firstFrame) ? 23 : 9;
isSymmetric = true;
break;
case GEN1_ICONTYPE_QUADRUPED:
entryIndex = (firstFrame) ? 25 : 11;
isSymmetric = true;
break;
case GEN1_ICONTYPE_PIKACHU:
{
if(gameType_ != Gen1GameType::YELLOW)
{
entryIndex = 0xFF;
}
else
{
entryIndex = (firstFrame) ? 13 : 27;
}
isSymmetric = false;
break;
}
default:
entryIndex = 0xFF;
isSymmetric = false;
break;
}
// HACK: In pokemon yellow, there's an additional PIKACHU entry in the MonPartySpritePointers table.
// This shifts every entryIndex by 1 after this new entry.
// In practice, this means that mostly the second frame of icons are affected.
// so we need to increase the entryIndex by one in this case.
if(gameType_ == Gen1GameType::YELLOW && entryIndex != 0xFF && entryIndex > 13)
{
++entryIndex;
}
if(entryIndex == 0xFF)
{
if(iconType == Gen1PokemonIconType::GEN1_ICONTYPE_HELIX && firstFrame)
{
// The helix sprite is not part of the MonPartySpritePointers table for some reason
bankIndex = 4;
pointer = (gameType_ == Gen1GameType::YELLOW) ? 0x7525 : 0x5180;
numTiles = 4;
}
else
{
return nullptr;
}
}
else
{
romReader_.seek(romOffset);
romReader_.advance(ENTRY_SIZE * entryIndex);
romReader_.readUint16(pointer);
romReader_.readByte(numTiles);
romReader_.readByte(bankIndex);
romReader_.readUint16(vSpritesTileOffset);
// HACK: so, in the case that only the left half of the icon is stored, every tile (top left + bottom left)
// gets a separate entry in the table. But that's a bit annoying to work with. Since both tiles are stored
// contiguously on the cartridge, let's just manipulate the numTiles field because we're going to render the tiles
// separately anyway because we're aware that we're in this scenario.
if(numTiles == 1 && isSymmetric)
{
numTiles = 2;
}
}
romReader_.seekToRomPointer(pointer, bankIndex);
romReader_.read(iconSrcBuffer, BYTES_PER_TILE * numTiles);
if(isSymmetric)
{
// only the left half is stored. We're supposed to mirror it to get the right half.
const uint8_t* srcTop = iconSrcBuffer;
const uint8_t* srcBottom = iconSrcBuffer + BYTES_PER_TILE;
// top left corner
renderer.drawTile(srcTop, outputFormat, monochromeGBColorPalette, 0, 0, 2, false);
// top right corner (mirrored)
renderer.drawTile(srcTop, outputFormat, monochromeGBColorPalette, TILE_WIDTH, 0, 2, true);
// bottom left corner
renderer.drawTile(srcBottom, outputFormat, monochromeGBColorPalette, 0, TILE_HEIGHT, 2, false);
// bottom right corner (mirrored)
return renderer.drawTile(srcBottom, outputFormat, monochromeGBColorPalette, TILE_WIDTH, TILE_HEIGHT, 2, true);
}
else
{
// full sprite is stored in a horizontal tile order. So we can decode them this way.
return renderer.draw(iconSrcBuffer, outputFormat, monochromeGBColorPalette, 2, 2, SpriteRenderer::TileOrder::HORIZONTAL);
}
return iconDecoder_.decode(iconType, firstFrame);
}
uint8_t Gen1GameReader::addPokemon(Gen1TrainerPokemon& poke, const char* originalTrainerID, const char* nickname)

View File

@ -0,0 +1,193 @@
#include "gen1/Gen1IconDecoder.h"
#include "RomReader.h"
#include <cstring>
static uint8_t reverseByte(uint8_t incoming)
{
uint8_t result = 0;
uint8_t maskIncoming = 0x80;
uint8_t flagOutgoing = 1;
while(maskIncoming)
{
if(incoming & maskIncoming)
{
result |= flagOutgoing;
}
maskIncoming >>= 1;
flagOutgoing <<= 1;
}
return result;
}
Gen1IconDecoder::Gen1IconDecoder(IRomReader& romReader, Gen1GameType gameType)
: buffer_()
, romReader_(romReader)
, gameType_(gameType)
{
}
/*
* In gen 1, the behaviour of the party icons is a bit messy.
* There's the MonPartySpritePointers of the various icon sprites. But while some of these sprites are stored fully,
* for some only the left half is stored. (and the code is supposed to mirror this half while rendering)
* In fact, for the latter, the top and bottom left tiles are separate entries in the table. This is not the case for the icons that are
* stored fully.
*
* There's also no real flag to indicate this.
*
* 2 icons don't even have a second frame stored. As a matter of fact, the HELIX icon doesn't even have an entry in the table at all!
*
* Anyway, we need to deal with this mess and that makes our code below a bit messy as well.
*/
uint8_t* Gen1IconDecoder::decode(Gen1PokemonIconType iconType, bool firstFrame)
{
const uint32_t romOffset = (gameType_ == Gen1GameType::YELLOW) ? 0x7184D : 0x717C0;
const uint32_t ENTRY_SIZE = 6;
const uint8_t MAX_NUM_TILES = 8;
uint8_t iconSrcBuffer[GEN1_BYTES_PER_TILE * MAX_NUM_TILES];
uint16_t pointer;
uint16_t vSpritesTileOffset;
uint8_t numTiles; // total number of tiles of the sprite (to indicate byte size)
uint8_t bankIndex;
uint8_t entryIndex;
// this boolean indicates that the sprite only stores the left half, but it's symmetric
bool isSymmetric;
switch(iconType)
{
case GEN1_ICONTYPE_MON:
entryIndex = (firstFrame) ? 14 : 0;
isSymmetric = false;
break;
case GEN1_ICONTYPE_BALL:
entryIndex = (firstFrame) ? 1 : 0xFF;
isSymmetric = false;
break;
case GEN1_ICONTYPE_FAIRY:
entryIndex = (firstFrame) ? 16 : 2;
isSymmetric = false;
break;
case GEN1_ICONTYPE_BIRD:
entryIndex = (firstFrame) ? 17 : 3;
isSymmetric = false;
break;
case GEN1_ICONTYPE_WATER:
entryIndex = (firstFrame) ? 4 : 18;
isSymmetric = false;
break;
case GEN1_ICONTYPE_BUG:
entryIndex = (firstFrame) ? 19 : 5;
isSymmetric = true;
break;
case GEN1_ICONTYPE_GRASS:
entryIndex = (firstFrame) ? 21 : 7;
isSymmetric = true;
break;
case GEN1_ICONTYPE_SNAKE:
entryIndex = (firstFrame) ? 23 : 9;
isSymmetric = true;
break;
case GEN1_ICONTYPE_QUADRUPED:
entryIndex = (firstFrame) ? 25 : 11;
isSymmetric = true;
break;
case GEN1_ICONTYPE_PIKACHU:
{
if(gameType_ != Gen1GameType::YELLOW)
{
entryIndex = 0xFF;
}
else
{
entryIndex = (firstFrame) ? 13 : 27;
}
isSymmetric = false;
break;
}
default:
entryIndex = 0xFF;
isSymmetric = false;
break;
}
// HACK: In pokemon yellow, there's an additional PIKACHU entry in the MonPartySpritePointers table.
// This shifts every entryIndex by 1 after this new entry.
// In practice, this means that mostly the second frame of icons are affected.
// so we need to increase the entryIndex by one in this case.
if(gameType_ == Gen1GameType::YELLOW && entryIndex != 0xFF && entryIndex > 13)
{
++entryIndex;
}
if(entryIndex == 0xFF)
{
if(iconType == Gen1PokemonIconType::GEN1_ICONTYPE_HELIX && firstFrame)
{
// The helix sprite is not part of the MonPartySpritePointers table for some reason
bankIndex = 4;
pointer = (gameType_ == Gen1GameType::YELLOW) ? 0x7525 : 0x5180;
numTiles = 4;
}
else
{
return nullptr;
}
}
else
{
romReader_.seek(romOffset);
romReader_.advance(ENTRY_SIZE * entryIndex);
romReader_.readUint16(pointer);
romReader_.readByte(numTiles);
romReader_.readByte(bankIndex);
romReader_.readUint16(vSpritesTileOffset);
// HACK: so, in the case that only the left half of the icon is stored, every tile (top left + bottom left)
// gets a separate entry in the table. But that's a bit annoying to work with. Since both tiles are stored
// contiguously on the cartridge, let's just manipulate the numTiles field because we're going to render the tiles
// separately anyway because we're aware that we're in this scenario.
if(numTiles == 1 && isSymmetric)
{
numTiles = 2;
}
}
romReader_.seekToRomPointer(pointer, bankIndex);
romReader_.read(iconSrcBuffer, GEN1_BYTES_PER_TILE * numTiles);
if(isSymmetric)
{
const uint8_t numRowBytes = GEN1_BYTES_PER_TILE * GEN1_ICON_HEIGHT_IN_TILES;
const uint8_t* const dataEnd = iconSrcBuffer + numRowBytes;
const uint8_t* cur = iconSrcBuffer;
uint8_t* outCur = buffer_ + numRowBytes;
// in this scenario, the tiles are stored in vertical order, but only the left half (2 tiles)
// for the right half, we're supposed to mirror the bytes
memcpy(buffer_, iconSrcBuffer, numRowBytes);
while(cur < dataEnd)
{
(*outCur) = reverseByte((*cur));
++cur;
++outCur;
}
}
else
{
// even though we read the entire icon at once, they are stored in the rom/cartridge in horizontal tile order.
// But WE want to return the data in vertical order
// (this keeps rendering with SpriteRenderer simple)
// so we need to reorder the tiles from horizontal to vertical
memcpy(buffer_, iconSrcBuffer, GEN1_BYTES_PER_TILE);
memcpy(buffer_ + GEN1_BYTES_PER_TILE, iconSrcBuffer + (2 * GEN1_BYTES_PER_TILE), GEN1_BYTES_PER_TILE);
memcpy(buffer_ + (2 * GEN1_BYTES_PER_TILE), iconSrcBuffer + GEN1_BYTES_PER_TILE, GEN1_BYTES_PER_TILE);
memcpy(buffer_ + (3 * GEN1_BYTES_PER_TILE), iconSrcBuffer + (3 * GEN1_BYTES_PER_TILE), GEN1_BYTES_PER_TILE);
}
return buffer_;
}

View File

@ -281,7 +281,11 @@ static bool hasBackupSave(ISaveManager& saveManager, bool isCrystal)
}
Gen2GameReader::Gen2GameReader(IRomReader &romReader, ISaveManager &saveManager, Gen2GameType gameType)
: romReader_(romReader), saveManager_(saveManager), spriteDecoder_(romReader), gameType_(gameType)
: romReader_(romReader)
, saveManager_(saveManager)
, spriteDecoder_(romReader)
, iconDecoder_(romReader, gameType)
, gameType_(gameType)
{
}
@ -462,38 +466,9 @@ uint8_t *Gen2GameReader::decodeSprite(uint8_t bankIndex, uint16_t pointer)
return spriteDecoder_.decode(bankIndex, pointer);
}
uint8_t *Gen2GameReader::decodePokemonIcon(Gen2PokemonIconType iconType, SpriteRenderer& renderer, SpriteRenderer::OutputFormat outputFormat, bool firstFrame)
uint8_t *Gen2GameReader::decodePokemonIcon(Gen2PokemonIconType iconType, bool firstFrame)
{
const uint32_t romOffset = (gameType_ == Gen2GameType::CRYSTAL) ? 0x8EBBF : 0x8EA70;
const uint8_t MAX_NUM_TILES = 4;
const uint8_t TILE_WIDTH = 8;
const uint8_t TILE_HEIGHT = 8;
const uint8_t BITS_PER_PIXEL = 2;
const uint8_t BYTES_PER_TILE = ((TILE_WIDTH * TILE_HEIGHT) / 8) * BITS_PER_PIXEL;
const uint8_t TILES_PER_ICON = 4;
const uint32_t ENTRY_SIZE = 2;
uint8_t iconSrcBuffer[MAX_NUM_TILES * BYTES_PER_TILE];
const uint8_t bankIndex = 0x23;
uint16_t pointer;
// read IconPointers table entry
romReader_.seek(romOffset);
romReader_.advance(((uint32_t)iconType) * ENTRY_SIZE);
romReader_.readUint16(pointer);
// now jump to the pointer to find the sprite
romReader_.seekToRomPointer(pointer, bankIndex);
// every sprite contains 2 frames.
// if we want the second one, we need to skip 1 16x16 sprite
if(!firstFrame)
{
romReader_.advance(TILES_PER_ICON * BYTES_PER_TILE);
}
romReader_.read(iconSrcBuffer, TILES_PER_ICON * BYTES_PER_TILE);
return renderer.draw(iconSrcBuffer, outputFormat, gen2_iconColorPalette, 2, 2, SpriteRenderer::TileOrder::HORIZONTAL);
return iconDecoder_.decode(iconType, firstFrame);
}
uint8_t Gen2GameReader::addPokemon(Gen2TrainerPokemon &poke, bool isEgg, const char *originalTrainerID, const char *nickname)

View File

@ -0,0 +1,54 @@
#include "gen2/Gen2IconDecoder.h"
#include "RomReader.h"
#include <cstring>
Gen2IconDecoder::Gen2IconDecoder(IRomReader& romReader, Gen2GameType gameType)
: buffer_()
, romReader_(romReader)
, gameType_(gameType)
{
}
uint8_t* Gen2IconDecoder::decode(Gen2PokemonIconType iconType, bool firstFrame)
{
const uint32_t romOffset = (gameType_ == Gen2GameType::CRYSTAL) ? 0x8EBBF : 0x8EA70;
const uint8_t MAX_NUM_TILES = 4;
const uint8_t TILE_WIDTH = 8;
const uint8_t TILE_HEIGHT = 8;
const uint8_t BITS_PER_PIXEL = 2;
const uint8_t BYTES_PER_TILE = ((TILE_WIDTH * TILE_HEIGHT) / 8) * BITS_PER_PIXEL;
const uint8_t TILES_PER_ICON = 4;
const uint32_t ENTRY_SIZE = 2;
uint8_t iconSrcBuffer[MAX_NUM_TILES * BYTES_PER_TILE];
const uint8_t bankIndex = 0x23;
uint16_t pointer;
// read IconPointers table entry
romReader_.seek(romOffset);
romReader_.advance(((uint32_t)iconType) * ENTRY_SIZE);
romReader_.readUint16(pointer);
// now jump to the pointer to find the sprite
romReader_.seekToRomPointer(pointer, bankIndex);
// every sprite contains 2 frames.
// if we want the second one, we need to skip 1 16x16 sprite
if(!firstFrame)
{
romReader_.advance(TILES_PER_ICON * BYTES_PER_TILE);
}
romReader_.read(iconSrcBuffer, TILES_PER_ICON * BYTES_PER_TILE);
// even though we read the entire icon at once, they are stored in the rom/cartridge in horizontal tile order.
// But WE want to return the data in vertical order
// (this keeps rendering with SpriteRenderer simple)
// so we need to reorder the tiles from horizontal to vertical
memcpy(buffer_, iconSrcBuffer, GEN2_BYTES_PER_TILE);
memcpy(buffer_ + GEN2_BYTES_PER_TILE, iconSrcBuffer + (2 * GEN2_BYTES_PER_TILE), GEN2_BYTES_PER_TILE);
memcpy(buffer_ + (2 * GEN2_BYTES_PER_TILE), iconSrcBuffer + GEN2_BYTES_PER_TILE, GEN2_BYTES_PER_TILE);
memcpy(buffer_ + (3 * GEN2_BYTES_PER_TILE), iconSrcBuffer + (3 * GEN2_BYTES_PER_TILE), GEN2_BYTES_PER_TILE);
return buffer_;
}