From 747bdd4b4b27c8d1bd9c44f0d651d4c8c439b90c Mon Sep 17 00:00:00 2001 From: risingPhil Date: Fri, 19 Jul 2024 21:31:16 +0200 Subject: [PATCH] Fix bug gen2 addItem --- examples/Makefile | 2 + examples/gen2_gsball/Makefile | 48 ++++++++++++++ examples/gen2_gsball/main.cpp | 116 ++++++++++++++++++++++++++++++++++ src/gen2/Gen2Common.cpp | 49 +++++++++++--- 4 files changed, 205 insertions(+), 10 deletions(-) create mode 100644 examples/gen2_gsball/Makefile create mode 100644 examples/gen2_gsball/main.cpp diff --git a/examples/Makefile b/examples/Makefile index 97e62fb..3c4e918 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -8,6 +8,7 @@ all: $(MAKE) -C encodeText_gen2 $(MAKECMDGOALS) $(MAKEOVERRIDES) $(MAKE) -C decodeSprite $(MAKECMDGOALS) $(MAKEOVERRIDES) $(MAKE) -C addDistributionPoke $(MAKECMDGOALS) $(MAKEOVERRIDES) + $(MAKE) -C gen2_gsball $(MAKECMDGOALS) $(MAKEOVERRIDES) clean: $(MAKE) -C do_stuff_gen1 clean @@ -16,3 +17,4 @@ clean: $(MAKE) -C encodeText_gen2 clean $(MAKE) -C decodeSprite clean $(MAKE) -C addDistributionPoke clean + $(MAKE) -C gen2_gsball clean diff --git a/examples/gen2_gsball/Makefile b/examples/gen2_gsball/Makefile new file mode 100644 index 0000000..f2aeccb --- /dev/null +++ b/examples/gen2_gsball/Makefile @@ -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 := ../../gen2_gsball + +# 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) diff --git a/examples/gen2_gsball/main.cpp b/examples/gen2_gsball/main.cpp new file mode 100644 index 0000000..b51bfc7 --- /dev/null +++ b/examples/gen2_gsball/main.cpp @@ -0,0 +1,116 @@ +#include "gen1/Gen1GameReader.h" +#include "gen1/Gen1DistributionPokemon.h" +#include "gen2/Gen2GameReader.h" +#include "gen2/Gen2DistributionPokemon.h" +#include "RomReader.h" +#include "SaveManager.h" +#include "SpriteRenderer.h" +#include "utils.h" + +#include +#include +#include + +#define POKEMON_CRYSTAL_ITEM_ID_GS_BALL 0x73 + +static void gen2ReceiveGSBall(Gen2GameReader& gameReader) +{ + bool alreadyHasOne = false; + + const char* trainerName = gameReader.getTrainerName(); + Gen2ItemList keyItemPocket = gameReader.getItemList(Gen2ItemListType::GEN2_ITEMLISTTYPE_KEYITEMPOCKET); + if(keyItemPocket.getCount() > 0) + { + uint8_t itemId; + uint8_t itemCount; + bool gotEntry = keyItemPocket.getEntry(0, itemId, itemCount); + + while(gotEntry) + { + if(itemId == POKEMON_CRYSTAL_ITEM_ID_GS_BALL) + { + alreadyHasOne = true; + break; + } + gotEntry = keyItemPocket.getNextEntry(itemId, itemCount); + } + } + + if(alreadyHasOne) + { + fprintf(stderr, "ERROR: It appears you already have one!"); + } + else + { + keyItemPocket.add(POKEMON_CRYSTAL_ITEM_ID_GS_BALL, 1); + gameReader.finishSave(); + fprintf(stdout, "%s obtained a GS Ball!", trainerName); + } +} + +int main(int argc, char** argv) +{ + if(argc != 3) + { + fprintf(stderr, "Usage: addDistributionPoke \n"); + return 1; + } + + uint8_t* romBuffer; + uint8_t* saveBuffer; + uint32_t romFileSize; + uint32_t saveFileSize; + + romBuffer = readFileIntoBuffer(argv[1], romFileSize); + if(!romBuffer) + { + fprintf(stderr, "ERROR: Couldn't read file %s\n", argv[1]); + return 1; + } + saveBuffer = readFileIntoBuffer(argv[2], saveFileSize); + if(!saveBuffer) + { + fprintf(stderr, "ERROR: Couldn't read file %s\n", argv[2]); + return 1; + } + + GameboyCartridgeHeader cartridgeHeader; + BufferBasedRomReader romReader(romBuffer, romFileSize); + BufferBasedSaveManager saveManager(saveBuffer, saveFileSize); + + readGameboyCartridgeHeader(romReader, cartridgeHeader); + + //check if we're dealing with gen 2 + const Gen2GameType gen2Type = gen2_determineGameType(cartridgeHeader); + if(gen2Type == Gen2GameType::INVALID) + { + fprintf(stderr, "ERROR: not a gen 2 game!\n"); + return 1; + } + if(gen2Type != Gen2GameType::CRYSTAL) + { + fprintf(stderr, "ERROR: not Pokémon Crystal!\n"); +// return 1; + } + + Gen2GameReader gameReader(romReader, saveManager, gen2Type); + gen2ReceiveGSBall(gameReader); + + // Test for adding rare candy +#if 0 + Gen2ItemList itemPocket = gameReader.getItemList(Gen2ItemListType::GEN2_ITEMLISTTYPE_ITEMPOCKET); + itemPocket.add(0x20, 27); + gameReader.finishSave(); +#endif + + FILE* f = fopen(argv[2], "w"); + fwrite(saveBuffer, 1, saveFileSize, f); + fclose(f); + + free(romBuffer); + free(saveBuffer); + romBuffer = 0; + saveBuffer = 0; + + return 0; +} diff --git a/src/gen2/Gen2Common.cpp b/src/gen2/Gen2Common.cpp index 193ba78..45e705b 100644 --- a/src/gen2/Gen2Common.cpp +++ b/src/gen2/Gen2Common.cpp @@ -155,7 +155,7 @@ uint8_t Gen2ItemList::getCount() uint8_t Gen2ItemList::getCapacity() { - return 20; + return (type_ == Gen2ItemListType::GEN2_ITEMLISTTYPE_KEYITEMPOCKET) ? 26 : 20; } bool Gen2ItemList::getEntry(uint8_t index, uint8_t &outItemId, uint8_t &outCount) @@ -165,7 +165,11 @@ bool Gen2ItemList::getEntry(uint8_t index, uint8_t &outItemId, uint8_t &outCount return false; } - if (!saveManager_.advance(1 + (index * 2))) + // It looks like the key item pocket doesn't have a count value. + // During my test if I try to write a GS ball with count 1 to crystal and store the count value, I get a random master ball showing up (which has itemId 1) + // before the gs ball in the list. + const uint32_t advanceAmount = (type_ == Gen2ItemListType::GEN2_ITEMLISTTYPE_KEYITEMPOCKET) ? 1 + index : 1 + (index * 2); + if (!saveManager_.advance(advanceAmount)) { return false; } @@ -183,8 +187,20 @@ bool Gen2ItemList::getNextEntry(uint8_t &outItemId, uint8_t &outCount) return false; } - saveManager_.readByte(outCount); + // 2 things here: + // - The bulbapedia article: https://bulbapedia.bulbagarden.net/wiki/Save_data_structure_(Generation_II)#Item_lists + // gets the order of count vs itemId wrong. At least during my tests with a Pokémon crystal AND gold save + // - The key item pocket doesn't appear to store a count value at all. + // see my other comment at ::getEntry() for more info saveManager_.readByte(outItemId); + if(type_ != Gen2ItemListType::GEN2_ITEMLISTTYPE_KEYITEMPOCKET) + { + saveManager_.readByte(outCount); + } + else + { + outCount = 1; + } return true; } @@ -203,13 +219,19 @@ bool Gen2ItemList::add(uint8_t itemId, uint8_t itemCount) { if(curItemId == itemId) { - uint8_t newCount = curItemCount + itemCount; - if(newCount > 99) + // the key item pocket doesn't appear to have a count field per entry. + // For more info see the related comment @getEntry() + if(type_ != Gen2ItemListType::GEN2_ITEMLISTTYPE_KEYITEMPOCKET) { - newCount = 99; + uint8_t newCount = curItemCount + itemCount; + if(newCount > 99) + { + newCount = 99; + } + saveManager_.rewind(1); // count is the second field of each entry (yes, Bulbapedia appears to get it wrong here) + saveManager_.writeByte(newCount); + saveManager_.advance(); } - saveManager_.rewind(); - saveManager_.writeByte(newCount); return true; } } @@ -221,12 +243,19 @@ bool Gen2ItemList::add(uint8_t itemId, uint8_t itemCount) return false; } + // Bulbapedia gets the order wrong here. itemId should go first, then itemCount + // at least this is the case during my tests with a Pokémon Crystal AND Gold save + saveManager_.writeByte(itemId); // in the while loop, getNextEntry has returned false on the last iteration, so that means // we've arrived at the terminator of the list. This is the exact position at which we need to write our new entry. // convenient, isn't it? // so let's write the new entry... - saveManager_.writeByte(itemCount); - saveManager_.writeByte(itemId); + if(type_ != Gen2ItemListType::GEN2_ITEMLISTTYPE_KEYITEMPOCKET) + { + // the key item pocket doesn't appear to have a count field per entry. + // For more info see the related comment @getEntry() + saveManager_.writeByte(itemCount); + } // now write the new terminator saveManager_.writeByte(0xFF);