mirror of
https://github.com/risingPhil/libpokemegb.git
synced 2026-03-21 17:44:24 -05:00
Add party icon decoding for gen 1 pokémon games.
Now you can decode the pokemon mini sprites/party icons from the rom into an RGB/RGBA buffer
This commit is contained in:
parent
de5381da81
commit
2643a00ca7
|
|
@ -14,6 +14,7 @@ all:
|
|||
$(MAKE) -C gen2_removeItem $(MAKECMDGOALS) $(MAKEOVERRIDES)
|
||||
$(MAKE) -C gen2_getEventFlag $(MAKECMDGOALS) $(MAKEOVERRIDES)
|
||||
$(MAKE) -C gen2_setEventFlag $(MAKECMDGOALS) $(MAKEOVERRIDES)
|
||||
$(MAKE) -C decodePartyIcons $(MAKECMDGOALS) $(MAKEOVERRIDES)
|
||||
|
||||
clean:
|
||||
$(MAKE) -C do_stuff_gen1 clean
|
||||
|
|
@ -28,3 +29,4 @@ clean:
|
|||
$(MAKE) -C gen2_removeItem clean
|
||||
$(MAKE) -C gen2_getEventFlag clean
|
||||
$(MAKE) -C gen2_setEventFlag clean
|
||||
$(MAKE) -C decodePartyIcons clean
|
||||
|
|
|
|||
48
examples/decodePartyIcons/Makefile
Normal file
48
examples/decodePartyIcons/Makefile
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# # Compiler flags
|
||||
CXXFLAGS := -std=c++11 -fno-rtti -fno-exceptions -fno-unwind-tables -Wall -Wextra -I $(CURDIR) -I $(CURDIR)/../../include -g -Os
|
||||
|
||||
# Source files directory
|
||||
SRC_DIR := .
|
||||
# Build directory
|
||||
BUILD_DIR := build
|
||||
|
||||
# Source files (add more as needed)
|
||||
SRCS := $(shell find $(SRC_DIR) -type f -name '*.cpp')
|
||||
# Object files
|
||||
OBJS := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(SRCS))
|
||||
|
||||
LIBS := -lpokemegb
|
||||
ifeq ($(PNG_SUPPORT),1)
|
||||
LIBS += -lpng
|
||||
endif
|
||||
|
||||
# Ensure necessary directories exist
|
||||
# This function ensures the directory for the target exists
|
||||
define make_directory
|
||||
@mkdir -p $(dir $@)
|
||||
endef
|
||||
|
||||
# Target executable
|
||||
TARGET := ../../decodePartyIcons
|
||||
|
||||
# Phony targets
|
||||
.PHONY: all clean
|
||||
|
||||
# Default target
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(CXX) $(CXXFLAGS) $^ -o $@ $(LIBS) -L ../../
|
||||
|
||||
# Rule to compile source files
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp | $(BUILD_DIR)
|
||||
$(make_directory)
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
# Create the build directory if it doesn't exist
|
||||
$(BUILD_DIR):
|
||||
mkdir -p $(BUILD_DIR)
|
||||
|
||||
# Clean rule
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR) $(TARGET)
|
||||
87
examples/decodePartyIcons/main.cpp
Normal file
87
examples/decodePartyIcons/main.cpp
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#include "gen1/Gen1GameReader.h"
|
||||
#include "gen1/Gen1SpriteDecoder.h"
|
||||
#include "SpriteRenderer.h"
|
||||
#include "RomReader.h"
|
||||
#include "SaveManager.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
using OutputFormat = SpriteRenderer::OutputFormat;
|
||||
using TileOrder = SpriteRenderer::TileOrder;
|
||||
|
||||
void decodeGen1Icons(IRomReader& romReader, ISaveManager& saveManager, Gen1GameType gen1Type)
|
||||
{
|
||||
Gen1GameReader gameReader(romReader, saveManager, gen1Type);
|
||||
SpriteRenderer renderer;
|
||||
char fileNameBuf[100];
|
||||
PokemonIconType iconType;
|
||||
uint8_t *outputBuffer;
|
||||
bool firstFrame;
|
||||
|
||||
for(int i=0; i < GEN1_ICONTYPE_MAX; ++i)
|
||||
{
|
||||
iconType = (PokemonIconType)i;
|
||||
firstFrame = true;
|
||||
outputBuffer = gameReader.decodePokemonIcon(iconType, renderer, OutputFormat::RGB, firstFrame);
|
||||
if(!outputBuffer)
|
||||
{
|
||||
fprintf(stderr, "ERROR: Could not decode icon for icon type %d and firstFrame %d!\n", (int)iconType, firstFrame);
|
||||
continue;
|
||||
}
|
||||
snprintf(fileNameBuf, sizeof(fileNameBuf), "%d_frame1.png", i);
|
||||
write_png(fileNameBuf, outputBuffer, 2 * 8, 2 * 8, false);
|
||||
|
||||
firstFrame = false;
|
||||
outputBuffer = gameReader.decodePokemonIcon(iconType, renderer, OutputFormat::RGB, firstFrame);
|
||||
if(!outputBuffer)
|
||||
{
|
||||
fprintf(stderr, "ERROR: Could not decode icon for type %d and firstFrame %d!\n", (int)iconType, firstFrame);
|
||||
continue;
|
||||
}
|
||||
snprintf(fileNameBuf, sizeof(fileNameBuf), "%d_frame2.png", i);
|
||||
write_png(fileNameBuf, outputBuffer, 2 * 8, 2 * 8, false);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if(argc != 2)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s <path/to/rom.gbc>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t* romBuffer;
|
||||
uint32_t romFileSize;
|
||||
|
||||
romBuffer = readFileIntoBuffer(argv[1], romFileSize);
|
||||
if(!romBuffer)
|
||||
{
|
||||
fprintf(stderr, "ERROR: Couldn't read file %s\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
GameboyCartridgeHeader cartridgeHeader;
|
||||
BufferBasedRomReader romReader(romBuffer, romFileSize);
|
||||
BufferBasedSaveManager saveManager(nullptr, 0);
|
||||
|
||||
readGameboyCartridgeHeader(romReader, cartridgeHeader);
|
||||
|
||||
//check if we're dealing with gen 1
|
||||
const Gen1GameType gen1Type = gen1_determineGameType(cartridgeHeader);
|
||||
if(gen1Type == Gen1GameType::INVALID)
|
||||
{
|
||||
fprintf(stderr, "ERROR: sorry, this tool only supports gen 1!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
decodeGen1Icons(romReader, saveManager, gen1Type);
|
||||
|
||||
free(romBuffer);
|
||||
romBuffer = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -50,7 +50,11 @@ static void decodeGen1Sprite(Gen1GameReader &gameReader, uint8_t pokeIndex)
|
|||
}
|
||||
|
||||
// convert it to the outputFormat
|
||||
outputBuffer = renderer.draw(spriteBuffer, outputFormat, colorPalette, 7, 7, g_removeBackground);
|
||||
outputBuffer = renderer.draw(spriteBuffer, outputFormat, colorPalette, 7, 7);
|
||||
if(g_removeBackground)
|
||||
{
|
||||
renderer.removeWhiteBackground(7, 7);
|
||||
}
|
||||
|
||||
snprintf(fileNameBuf, sizeof(fileNameBuf), "%s.png", pokeName);
|
||||
write_png(fileNameBuf, outputBuffer, SPRITE_BUFFER_WIDTH_IN_PIXELS, SPRITE_BUFFER_HEIGHT_IN_PIXELS, g_removeBackground);
|
||||
|
|
@ -97,7 +101,11 @@ static void decodeGen2Sprite(Gen2GameReader &gameReader, uint8_t pokeIndex)
|
|||
}
|
||||
|
||||
// convert it to the outputFormat
|
||||
outputBuffer = renderer.draw(spriteBuffer, outputFormat, colorPalette, spriteWidthInTiles, spriteHeightInTiles, g_removeBackground);
|
||||
outputBuffer = renderer.draw(spriteBuffer, outputFormat, colorPalette, spriteWidthInTiles, spriteHeightInTiles);
|
||||
if(g_removeBackground)
|
||||
{
|
||||
renderer.removeWhiteBackground(spriteWidthInTiles, spriteHeightInTiles);
|
||||
}
|
||||
|
||||
snprintf(fileNameBuf, sizeof(fileNameBuf), "%s.png", pokeName);
|
||||
write_png(fileNameBuf, outputBuffer, spriteWidthInTiles * 8, spriteHeightInTiles * 8, g_removeBackground);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,17 @@ public:
|
|||
RGBA32
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This enum indicates the order the tiles appear in the input data
|
||||
*/
|
||||
enum class TileOrder
|
||||
{
|
||||
HORIZONTAL,
|
||||
VERTICAL
|
||||
};
|
||||
|
||||
SpriteRenderer();
|
||||
|
||||
/**
|
||||
* Returns the number of RGB bytes to store RGB values for a sprite with the given number of horizontal and vertical tiles
|
||||
*/
|
||||
|
|
@ -28,13 +39,38 @@ public:
|
|||
/**
|
||||
* @brief This function draw the given spriteBuffer to the internal rgbBuffer_ and returns this rgbBuffer_ pointer
|
||||
*
|
||||
* NOTE: the next call to this function overwrites the data inside the returned buffer. So if you want to keep this data around, copy it.
|
||||
* 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, bool removeWhiteBackground = false);
|
||||
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);
|
||||
|
||||
/**
|
||||
* @brief This function removes the white background in the current internal buffer_ and returns this buffer
|
||||
*
|
||||
* NOTE: the next call to draw() 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* 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 setFormatAndPalette(OutputFormat format, uint16_t palette[4]);
|
||||
|
||||
uint8_t buffer_[MAX_SPRITE_PIXEL_WIDTH * MAX_SPRITE_PIXEL_HEIGHT * MAX_NUM_BYTES_PER_COLOR_COMPONENT];
|
||||
OutputFormat format_;
|
||||
const uint8_t* rgbPalette_;
|
||||
const uint16_t* rgba16Palette_;
|
||||
uint8_t numColorComponents_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -87,6 +87,22 @@ enum Gen1PokeType
|
|||
GEN1_PT_DRAGON = 0x1A
|
||||
};
|
||||
|
||||
enum PokemonIconType
|
||||
{
|
||||
GEN1_ICONTYPE_MON = 0,
|
||||
GEN1_ICONTYPE_BALL,
|
||||
GEN1_ICONTYPE_HELIX,
|
||||
GEN1_ICONTYPE_FAIRY,
|
||||
GEN1_ICONTYPE_BIRD,
|
||||
GEN1_ICONTYPE_WATER,
|
||||
GEN1_ICONTYPE_BUG,
|
||||
GEN1_ICONTYPE_GRASS,
|
||||
GEN1_ICONTYPE_SNAKE,
|
||||
GEN1_ICONTYPE_QUADRUPED,
|
||||
GEN1_ICONTYPE_PIKACHU,
|
||||
GEN1_ICONTYPE_MAX
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implementation based on https://bulbapedia.bulbagarden.net/wiki/Save_data_structure_(Generation_I)#Checksum
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "gen1/Gen1SpriteDecoder.h"
|
||||
#include "gen1/Gen1PlayerPokemonStorage.h"
|
||||
#include "gen1/Gen1DistributionPokemon.h"
|
||||
#include "SpriteRenderer.h"
|
||||
|
||||
class IRomReader;
|
||||
class ISaveManager;
|
||||
|
|
@ -113,6 +114,12 @@ 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
|
||||
*/
|
||||
uint8_t* decodePokemonIcon(PokemonIconType iconType, SpriteRenderer& renderer, SpriteRenderer::OutputFormat outputFormat, 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
|
||||
* first ingame PC box with open slots
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@
|
|||
|
||||
using OutputFormat = SpriteRenderer::OutputFormat;
|
||||
|
||||
static const uint8_t TILE_WIDTH = 8;
|
||||
static const uint8_t TILE_HEIGHT = 8;
|
||||
static const uint8_t BITS_PER_PIXEL = 2;
|
||||
static const uint8_t BYTES_PER_TILE = ((TILE_WIDTH * TILE_HEIGHT) / 8) * BITS_PER_PIXEL;
|
||||
|
||||
// The next set of functions are defined to remove the white background from a decoded RGBA pokémon sprite by doing edge detection.
|
||||
// Both RGBA32 and RGBA16 formats are supported
|
||||
|
||||
|
|
@ -438,116 +443,167 @@ static void removeBackground(uint8_t *buffer, OutputFormat format, int spriteWid
|
|||
|
||||
// end of the removeBackground specific code
|
||||
|
||||
SpriteRenderer::SpriteRenderer()
|
||||
: buffer_()
|
||||
, format_(OutputFormat::RGB)
|
||||
, rgbPalette_(nullptr)
|
||||
, rgba16Palette_(nullptr)
|
||||
, numColorComponents_(0)
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t SpriteRenderer::getNumRGBBytesFor(uint8_t numHTiles, uint8_t numVTiles) const
|
||||
{
|
||||
const uint16_t numPixels = (numHTiles * 8) * (numVTiles * 8);
|
||||
return numPixels * 3;
|
||||
}
|
||||
|
||||
uint8_t* SpriteRenderer::draw(const uint8_t* spriteBuffer, OutputFormat format, uint16_t palette[4], uint8_t numHTiles, uint8_t numVTiles, bool removeWhiteBackground)
|
||||
uint8_t* SpriteRenderer::draw(const uint8_t* spriteBuffer, OutputFormat format, uint16_t palette[4], uint8_t numHTiles, uint8_t numVTiles, TileOrder tileOrder)
|
||||
{
|
||||
const uint8_t* rgbPalette;
|
||||
const uint16_t* rgba16Palette;
|
||||
uint8_t byte0;
|
||||
uint8_t byte1;
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
uint8_t numColorComponents;
|
||||
|
||||
// convert -size 56x56 -depth 8 rgb:sprite.rgb output.png
|
||||
|
||||
// We're working with a gameboy specific format here:
|
||||
// every 2 bytes contain the bits for 8 horizontal pixels
|
||||
// the first of those 2 bytes contain the least significant bits for every of those 8 horizontal pixels
|
||||
// the second of those 2 bytes contain the most significant bits for every of those 8 horizontal pixels.
|
||||
// however, the data is stored in column first order: that means that we're walking through the sprite vertically.
|
||||
// this is a bit confusing, but the gist of it is: you get 8 horizontal pixels (=2 bytes), then you move to the next vertical row.
|
||||
// then you get another 8 horizontal pixels of that row. And then you move again to the next vertical row.
|
||||
// you keep doing this until you reach the bottom of the sprite, after which the next column begins. And there you repeat the process
|
||||
|
||||
uint32_t i = 0;
|
||||
uint32_t offset;
|
||||
uint8_t paletteIndex;
|
||||
uint8_t bit0;
|
||||
uint8_t bit1;
|
||||
// every tile is 8x8
|
||||
const uint16_t spriteBufferHeightInPixels = numVTiles * 8;
|
||||
const uint16_t spriteBufferWidthInPixels = numHTiles * 8;
|
||||
const uint16_t spriteBufferSize = spriteBufferWidthInPixels * spriteBufferHeightInPixels / 8;
|
||||
const uint32_t spriteBufferSize = (spriteBufferWidthInPixels / 8) * spriteBufferHeightInPixels * BITS_PER_PIXEL;
|
||||
uint32_t i = 0;
|
||||
uint16_t x = 0;
|
||||
uint16_t y = 0;
|
||||
|
||||
switch(format)
|
||||
{
|
||||
case OutputFormat::RGB:
|
||||
rgbPalette = convertGBColorPaletteToRGB24(palette);
|
||||
rgba16Palette = nullptr;
|
||||
numColorComponents = 3;
|
||||
break;
|
||||
case OutputFormat::RGBA32:
|
||||
rgbPalette = convertGBColorPaletteToRGB24(palette);
|
||||
rgba16Palette = nullptr;
|
||||
numColorComponents = 4;
|
||||
break;
|
||||
case OutputFormat::RGBA16:
|
||||
rgbPalette = nullptr;
|
||||
rgba16Palette = convertGBColorPaletteToRGBA16(palette);
|
||||
numColorComponents = 2;
|
||||
break;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
setFormatAndPalette(format, palette);
|
||||
|
||||
while(i < spriteBufferSize)
|
||||
{
|
||||
byte0 = spriteBuffer[i * 2];
|
||||
byte1 = spriteBuffer[i * 2 + 1];
|
||||
internalDrawTile(spriteBuffer + i, x, y, spriteBufferWidthInPixels, false);
|
||||
|
||||
// printf("0x%hhu 0x%hhu\n", byte0, byte1);
|
||||
|
||||
x = (i / (spriteBufferHeightInPixels)) * 8;
|
||||
y = (i % (spriteBufferHeightInPixels));
|
||||
|
||||
for(uint8_t bitIndex = 0; bitIndex < 8; ++bitIndex)
|
||||
if(tileOrder == TileOrder::VERTICAL)
|
||||
{
|
||||
offset = (y * spriteBufferWidthInPixels + x + bitIndex) * numColorComponents;
|
||||
y += 8;
|
||||
if(y >= spriteBufferHeightInPixels)
|
||||
{
|
||||
x += 8;
|
||||
y = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
x += 8;
|
||||
if(x >= spriteBufferWidthInPixels)
|
||||
{
|
||||
x = 0;
|
||||
y += 8;
|
||||
}
|
||||
}
|
||||
|
||||
bit0 = (byte0 >> (7 - bitIndex)) & 0x1;
|
||||
bit1 = (byte1 >> (7 - bitIndex)) & 0x1;
|
||||
i += BYTES_PER_TILE;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
fprintf(stderr, "[SpriteRenderer]: %s: ERROR: removeBackground is not supported for OutputFormat::RGB!", __FUNCTION__);
|
||||
return buffer_;
|
||||
}
|
||||
|
||||
const uint16_t spriteBufferHeightInPixels = numVTiles * 8;
|
||||
const uint16_t spriteBufferWidthInPixels = numHTiles * 8;
|
||||
|
||||
removeBackground(buffer_, format_, spriteBufferWidthInPixels, spriteBufferHeightInPixels);
|
||||
return buffer_;
|
||||
}
|
||||
|
||||
void SpriteRenderer::internalDrawTile(const uint8_t* tileBuffer, uint16_t xOffset, uint16_t yOffset, uint16_t spriteBufferWidthInPixels, bool mirrorHorizontally)
|
||||
{
|
||||
uint32_t offset;
|
||||
uint32_t i = 0;
|
||||
uint16_t x;
|
||||
uint16_t y = 0;
|
||||
uint8_t byte0;
|
||||
uint8_t byte1;
|
||||
uint8_t bit0;
|
||||
uint8_t bit1;
|
||||
uint8_t paletteIndex;
|
||||
|
||||
while(i < BYTES_PER_TILE)
|
||||
{
|
||||
byte0 = tileBuffer[i];
|
||||
byte1 = tileBuffer[i + 1];
|
||||
|
||||
for(x = 0; x < 8; ++x)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
paletteIndex = (bit1 << 1) | bit0;
|
||||
|
||||
if(format == OutputFormat::RGBA16)
|
||||
if(format_ == OutputFormat::RGBA16)
|
||||
{
|
||||
(*((uint16_t*)(buffer_ + offset))) = rgba16Palette[paletteIndex];
|
||||
(*((uint16_t*)(buffer_ + offset))) = rgba16Palette_[paletteIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
// R
|
||||
buffer_[offset] = rgbPalette[paletteIndex * 3 + 0];
|
||||
buffer_[offset] = rgbPalette_[paletteIndex * 3 + 0];
|
||||
// G
|
||||
buffer_[offset + 1] = rgbPalette[paletteIndex * 3 + 1];
|
||||
buffer_[offset + 1] = rgbPalette_[paletteIndex * 3 + 1];
|
||||
// B
|
||||
buffer_[offset + 2] = rgbPalette[paletteIndex * 3 + 2];
|
||||
buffer_[offset + 2] = rgbPalette_[paletteIndex * 3 + 2];
|
||||
|
||||
if(format == OutputFormat::RGBA32)
|
||||
if(format_ == OutputFormat::RGBA32)
|
||||
{
|
||||
// A
|
||||
buffer_[offset + 3] = 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
++y;
|
||||
|
||||
if(removeWhiteBackground)
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteRenderer::setFormatAndPalette(OutputFormat format, uint16_t palette[4])
|
||||
{
|
||||
format_ = format;
|
||||
switch(format)
|
||||
{
|
||||
if(format == OutputFormat::RGB)
|
||||
{
|
||||
fprintf(stderr, "[SpriteRenderer]: %s: ERROR: removeBackground is not supported for OutputFormat::RGB!", __FUNCTION__);
|
||||
return buffer_;
|
||||
}
|
||||
|
||||
removeBackground(buffer_, format, spriteBufferWidthInPixels, spriteBufferHeightInPixels);
|
||||
case OutputFormat::RGB:
|
||||
rgbPalette_ = convertGBColorPaletteToRGB24(palette);
|
||||
rgba16Palette_ = nullptr;
|
||||
numColorComponents_ = 3;
|
||||
break;
|
||||
case OutputFormat::RGBA32:
|
||||
rgbPalette_ = convertGBColorPaletteToRGB24(palette);
|
||||
rgba16Palette_ = nullptr;
|
||||
numColorComponents_ = 4;
|
||||
break;
|
||||
case OutputFormat::RGBA16:
|
||||
rgbPalette_ = nullptr;
|
||||
rgba16Palette_ = convertGBColorPaletteToRGBA16(palette);
|
||||
numColorComponents_ = 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return buffer_;
|
||||
}
|
||||
|
|
@ -419,6 +419,158 @@ 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(PokemonIconType iconType, SpriteRenderer& renderer, SpriteRenderer::OutputFormat outputFormat, 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;
|
||||
|
||||
// TODO: for some of these icons, only the left half is stored.
|
||||
// they are supposed to be mirrored
|
||||
|
||||
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 == PokemonIconType::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);
|
||||
|
||||
if(numTiles == 1 && isSymmetric)
|
||||
{
|
||||
numTiles = 2;
|
||||
}
|
||||
}
|
||||
|
||||
romReader_.seekToRomPointer(pointer, bankIndex);
|
||||
romReader_.read(iconSrcBuffer, BYTES_PER_TILE * numTiles);
|
||||
|
||||
if(isSymmetric)
|
||||
{
|
||||
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
|
||||
{
|
||||
return renderer.draw(iconSrcBuffer, outputFormat, monochromeGBColorPalette, 2, 2, SpriteRenderer::TileOrder::HORIZONTAL);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Gen1GameReader::addPokemon(Gen1TrainerPokemon& poke, const char* originalTrainerID, const char* nickname)
|
||||
{
|
||||
Gen1Party party = getParty();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user