mirror of
https://github.com/risingPhil/libpokemegb.git
synced 2026-03-21 17:44:24 -05:00
Fix GameReader::decodePokemonIcon() API inconsistency in relation to decodeSprite()
This commit is contained in:
parent
a44b908587
commit
bd518d0d87
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
};
|
||||
|
||||
|
|
|
|||
38
include/gen1/Gen1IconDecoder.h
Normal file
38
include/gen1/Gen1IconDecoder.h
Normal 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
|
||||
|
|
@ -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_;
|
||||
};
|
||||
|
||||
|
|
|
|||
38
include/gen2/Gen2IconDecoder.h
Normal file
38
include/gen2/Gen2IconDecoder.h
Normal 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
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
193
src/gen1/Gen1IconDecoder.cpp
Normal file
193
src/gen1/Gen1IconDecoder.cpp
Normal 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_;
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
54
src/gen2/Gen2IconDecoder.cpp
Normal file
54
src/gen2/Gen2IconDecoder.cpp
Normal 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_;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user