diff --git a/examples/extractReproSave/main.cpp b/examples/extractReproSave/main.cpp index d9fb5de..b913b96 100644 --- a/examples/extractReproSave/main.cpp +++ b/examples/extractReproSave/main.cpp @@ -1,5 +1,6 @@ #include "gen1/Gen1GameReader.h" #include "gen2/Gen2GameReader.h" +#include "gen2/Gen2ReproSaveManager.h" #include "RomReader.h" #include "SaveManager.h" #include "utils.h" @@ -39,6 +40,7 @@ int main(int argc, char** argv) GameboyCartridgeHeader cartridgeHeader; BufferBasedRomReader romReader(romBuffer, romFileSize); + Gen2ReproSaveManager saveManager(romReader, Gen2ReproType::ALIEXPRESS_TYPE1, false); readGameboyCartridgeHeader(romReader, cartridgeHeader); @@ -63,11 +65,19 @@ int main(int argc, char** argv) return 1; } + uint8_t savBankBuffer[GB_SRAM_BANK_SIZE]; + + for(uint8_t i=0; i < 4; ++i) + { + saveManager.read(savBankBuffer, GB_SRAM_BANK_SIZE); + fwrite(savBankBuffer, sizeof(uint8_t), GB_SRAM_BANK_SIZE, outFile); + } +#if 0 + // So, turns out that these reproductions dedicate an entire rom bank // for every SRAM bank. // As a matter of fact, each SRAM bank is located at an offset of 0x2000 within // said rom bank. - uint8_t savBankBuffer[GB_SRAM_BANK_SIZE]; const uint8_t* cur = romBuffer + GEN2_REPRO_SAVE_OFFSET_IN_ROM; const uint8_t* const end = cur + (GB_ROM_BANK_SIZE * 4); while(cur < end) @@ -76,6 +86,7 @@ int main(int argc, char** argv) fwrite(savBankBuffer, sizeof(uint8_t), GB_SRAM_BANK_SIZE, outFile); cur += GB_ROM_BANK_SIZE; } +#endif fclose(outFile); diff --git a/include/gen2/Gen2ReproSaveManager.h b/include/gen2/Gen2ReproSaveManager.h new file mode 100644 index 0000000..0966f28 --- /dev/null +++ b/include/gen2/Gen2ReproSaveManager.h @@ -0,0 +1,89 @@ +#ifndef _GEN2REPROSAVEMANAGER_H +#define _GEN2REPROSAVEMANAGER_H + +#include "RomReader.h" +#include "SaveManager.h" + +enum class Gen2ReproType +{ + INVALID, + ALIEXPRESS_TYPE1 +}; + +typedef struct Gen2ReproSRAMRomOffsetMetadata +{ + // A list of rom offsets for each SRAM bank + struct { + uint8_t romBankIndex; + uint16_t romBankOffset; + } sramBankOffsets[4]; +} Gen2ReproSRAMRomOffsetMetadata; + +/** + * @brief This class provides a way to read save data from a reproduction cart + * Reproduction carts save their data inside the rom data. + */ +class Gen2ReproSaveManager : public BaseSaveManager +{ +public: + Gen2ReproSaveManager(IRomReader& romReader, Gen2ReproType reproType, bool writeEnabled); + virtual ~Gen2ReproSaveManager(); + + /** + * @brief This function reads a byte, returns it and advances the internal pointer by 1 byte + * + * @return uint8_t + */ + bool readByte(uint8_t& outByte) override; + void writeByte(uint8_t byte) override; + + /** + * @brief This function reads multiple bytes into the specified output buffer + * @note: bytesToRead should be <= 0x2000 (SRAM bank size) + * + * @return whether the operation was successful + */ + bool read(uint8_t* outBuffer, uint32_t bytesToRead) override; + void write(const uint8_t* buffer, uint32_t bytesToWrite) override; + + /** + * @brief This function reads the current byte without advancing the internal pointer by 1 byte + * + * @return uint8_t + */ + uint8_t peek() override; + + /** + * @brief This function advances the internal pointer by the specified numBytes. + * This can be considered a relative seek + * + */ + bool advance(uint32_t numBytes = 1) override; + + /** + * @brief This function rewinds the internal pointer with the specified numBytes + * This can be considered a backward relative seek + */ + bool rewind(uint32_t numBytes = 1) override; + + /** + * @brief This function seeks to the specified absolute SRAM offset + * + * @param absoluteOffset + */ + bool seek(uint32_t absoluteOffset) override; + + /** + * @brief Returns the index of the current bank + */ + uint8_t getCurrentBankIndex() const override; +protected: +private: + IRomReader& romReader_; + Gen2ReproType reproType_; + const Gen2ReproSRAMRomOffsetMetadata* reproRomOffsetMeta_; + uint32_t absoluteOffset_; + bool writeEnabled_; +}; + +#endif \ No newline at end of file diff --git a/src/gen2/Gen2ReproSaveManager.cpp b/src/gen2/Gen2ReproSaveManager.cpp new file mode 100644 index 0000000..3a18a4d --- /dev/null +++ b/src/gen2/Gen2ReproSaveManager.cpp @@ -0,0 +1,124 @@ +#include "gen2/Gen2ReproSaveManager.h" + +static const Gen2ReproSRAMRomOffsetMetadata aliexpressReproRomOffsets = { + .sramBankOffsets = { + { + .romBankIndex = 0x74, + .romBankOffset = 0x6000 + }, + { + .romBankIndex = 0x75, + .romBankOffset = 0x6000 + }, + { + .romBankIndex = 0x76, + .romBankOffset = 0x6000 + }, + { + .romBankIndex = 0x77, + .romBankOffset = 0x6000 + } + } +}; + +static uint8_t calculateBankIndex(uint32_t absoluteSRAMOffset) +{ + return (absoluteSRAMOffset / 0x2000); +} + +static const Gen2ReproSRAMRomOffsetMetadata* getReproSaveRomOffsets(Gen2ReproType reproType) +{ + switch(reproType) + { + case Gen2ReproType::ALIEXPRESS_TYPE1: + return &aliexpressReproRomOffsets; + default: + return nullptr; + } +} + +Gen2ReproSaveManager::Gen2ReproSaveManager(IRomReader& romReader, Gen2ReproType reproType, bool writeEnabled) + : romReader_(romReader) + , reproType_(reproType) + , reproRomOffsetMeta_(getReproSaveRomOffsets(reproType)) + , absoluteOffset_(0) + , writeEnabled_(writeEnabled) +{ + seek(0); +} + +Gen2ReproSaveManager::~Gen2ReproSaveManager() +{ +} + +bool Gen2ReproSaveManager::readByte(uint8_t& outByte) +{ + return read(&outByte, 1); +} + +void Gen2ReproSaveManager::writeByte(uint8_t byte) +{ + write(&byte, 1); +} + +bool Gen2ReproSaveManager::read(uint8_t* outBuffer, uint32_t bytesToRead) +{ + bool ret = romReader_.read(outBuffer, bytesToRead); + if(!ret) + { + return ret; + } + + ret &= advance(bytesToRead); + + return ret; +} + +void Gen2ReproSaveManager::write(const uint8_t* buffer, uint32_t bytesToWrite) +{ + (void)buffer; + (void)bytesToWrite; + if(!writeEnabled_) + { + return; + } + + //TODO: +} + +uint8_t Gen2ReproSaveManager::peek() +{ + return romReader_.peek(); +} + +bool Gen2ReproSaveManager::advance(uint32_t numBytes) +{ + absoluteOffset_ += numBytes; + return seek(absoluteOffset_); +} + +bool Gen2ReproSaveManager::rewind(uint32_t numBytes) +{ + absoluteOffset_ -= numBytes; + return seek(absoluteOffset_); +} + +bool Gen2ReproSaveManager::seek(uint32_t absoluteOffset) +{ + const uint8_t bankIndex = calculateBankIndex(absoluteOffset); + + if(!reproRomOffsetMeta_ || bankIndex > 3) + { + return false; + } + + absoluteOffset_ = absoluteOffset; + + const uint16_t localOffset = absoluteOffset % 0x2000; + return romReader_.seekToRomPointer(reproRomOffsetMeta_->sramBankOffsets[bankIndex].romBankOffset + localOffset, reproRomOffsetMeta_->sramBankOffsets[bankIndex].romBankIndex); +} + +uint8_t Gen2ReproSaveManager::getCurrentBankIndex() const +{ + return calculateBankIndex(absoluteOffset_); +} \ No newline at end of file