From 0ff264c13c0de1a8fa2ecadac5c7c9ec877c1966 Mon Sep 17 00:00:00 2001 From: Philippe Symons Date: Wed, 7 Jan 2026 18:30:06 +0100 Subject: [PATCH] Add save file validation when backing up or restoring a save --- include/scenes/DataCopyScene.h | 112 +-- include/transferpak/TransferPakDataCopier.h | 419 +++++----- src/scenes/AboutScene.cpp | 2 +- src/scenes/DataCopyScene.cpp | 818 +++++++++++--------- src/scenes/InitTransferPakScene.cpp | 2 +- src/transferpak/TransferPakDataCopier.cpp | 650 +++++++++------- 6 files changed, 1094 insertions(+), 909 deletions(-) diff --git a/include/scenes/DataCopyScene.h b/include/scenes/DataCopyScene.h index 9f6a17f..9779769 100644 --- a/include/scenes/DataCopyScene.h +++ b/include/scenes/DataCopyScene.h @@ -1,55 +1,59 @@ -#ifndef _DATACOPYSCENE_H -#define _DATACOPYSCENE_H - -#include "scenes/SceneWithProgressBar.h" -#include "transferpak/TransferPakManager.h" -#include "transferpak/TransferPakRomReader.h" -#include "transferpak/TransferPakSaveManager.h" -#include "transferpak/TransferPakDataCopier.h" - -enum class DataCopyOperation -{ - BACKUP_SAVE, - BACKUP_ROM, - RESTORE_SAVE, - WIPE_SAVE -}; - -typedef struct DataCopySceneContext -{ - DataCopyOperation operation; - ManagedString saveToRestorePath; -} DataCopySceneContext; - -class DataCopyScene : public SceneWithProgressBar -{ -public: - DataCopyScene(SceneDependencies& deps, void* context); - virtual ~DataCopyScene(); - - void init() override; - void destroy() override; - - void processUserInput() override; - void render(RDPQGraphics& gfx, const Rectangle& sceneBounds) override; - - void onDialogDone(); -protected: - void setupDialog(DialogWidgetStyle& style) override; - void setupProgressBar(ProgressBarWidgetStyle& style) override; -private: - TransferPakRomReader romReader_; - TransferPakSaveManager saveManager_; - DataCopySceneContext* sceneContext_; - ITransferPakDataCopySource* copySource_; - ITransferPakDataCopyDestination* copyDestination_; - TransferPakDataCopier* copier_; - sprite_t* dialogWidgetSprite_; - sprite_t* progressBackgroundSprite_; - DialogData diag_; - uint32_t totalBytesToCopy_; -}; - -void deleteDataCopySceneContext(void* context); - +#ifndef _DATACOPYSCENE_H +#define _DATACOPYSCENE_H + +#include "scenes/SceneWithProgressBar.h" +#include "transferpak/TransferPakManager.h" +#include "transferpak/TransferPakRomReader.h" +#include "transferpak/TransferPakSaveManager.h" +#include "transferpak/TransferPakDataCopier.h" + +enum class DataCopyOperation +{ + BACKUP_SAVE, + BACKUP_ROM, + RESTORE_SAVE, + WIPE_SAVE +}; + +typedef struct DataCopySceneContext +{ + DataCopyOperation operation; + ManagedString saveToRestorePath; +} DataCopySceneContext; + +class DataCopyScene : public SceneWithProgressBar +{ +public: + DataCopyScene(SceneDependencies& deps, void* context); + virtual ~DataCopyScene(); + + void init() override; + void destroy() override; + + void processUserInput() override; + void render(RDPQGraphics& gfx, const Rectangle& sceneBounds) override; + + void onDialogDone(); +protected: + void setupDialog(DialogWidgetStyle& style) override; + void setupProgressBar(ProgressBarWidgetStyle& style) override; +private: + TransferPakRomReader romReader_; + TransferPakSaveManager saveManager_; + DataCopySceneContext* sceneContext_; + ITransferPakDataCopySource* copySource_; + ITransferPakDataCopyDestination* copyDestination_; + TransferPakDataCopier* copier_; + sprite_t* dialogWidgetSprite_; + sprite_t* progressBackgroundSprite_; + DialogData diag_; + uint32_t totalBytesToCopy_; + bool needsValidation_; + bool isValidating_; + bool shouldResetRTC_; + char savOutputPath_[4096]; +}; + +void deleteDataCopySceneContext(void* context); + #endif \ No newline at end of file diff --git a/include/transferpak/TransferPakDataCopier.h b/include/transferpak/TransferPakDataCopier.h index aed4db1..84f6bbb 100644 --- a/include/transferpak/TransferPakDataCopier.h +++ b/include/transferpak/TransferPakDataCopier.h @@ -1,199 +1,222 @@ -#ifndef _TRANSFERPAKDATACOPIER_H -#define _TRANSFERPAKDATACOPIER_H - -#include -#include - -class TransferPakRomReader; -class TransferPakSaveManager; - -/** - * This interface is used to define a transfer pak datasource to copy from - */ -class ITransferPakDataCopySource -{ -public: - virtual ~ITransferPakDataCopySource(); - - virtual bool readyForTransfer() const = 0; - - virtual uint16_t getCurrentBankIndex() const = 0; - virtual uint32_t getNumberOfBytesRead() const = 0; - - virtual uint32_t read(uint8_t *buffer, uint32_t bytesToRead) = 0; - -protected: -private: -}; - -/** - * This class implements the ITransferPakDataCopySource interface with TransferPakRomReader - */ -class TransferPakRomReaderCopySource : public ITransferPakDataCopySource -{ -public: - TransferPakRomReaderCopySource(TransferPakRomReader &romReader); - virtual ~TransferPakRomReaderCopySource(); - - bool readyForTransfer() const override; - - uint16_t getCurrentBankIndex() const override; - uint32_t getNumberOfBytesRead() const override; - - uint32_t read(uint8_t *buffer, uint32_t bytesToRead) override; - -protected: -private: - TransferPakRomReader &romReader_; - uint32_t bytesRead_; -}; - -/** - * This class implements the ITransferPakDataCopySource interface with TransferPakRomReader - */ -class TransferPakSaveManagerCopySource : public ITransferPakDataCopySource -{ -public: - TransferPakSaveManagerCopySource(TransferPakSaveManager &saveManager); - virtual ~TransferPakSaveManagerCopySource(); - - bool readyForTransfer() const override; - - uint16_t getCurrentBankIndex() const override; - uint32_t getNumberOfBytesRead() const override; - - uint32_t read(uint8_t *buffer, uint32_t bytesToRead) override; - -protected: -private: - TransferPakSaveManager &saveManager_; - uint32_t bytesRead_; -}; - -class TransferPakFileCopySource : public ITransferPakDataCopySource -{ -public: - TransferPakFileCopySource(const char *filePath); - virtual ~TransferPakFileCopySource(); - - bool readyForTransfer() const override; - - uint16_t getCurrentBankIndex() const override; - uint32_t getNumberOfBytesRead() const override; - - uint32_t read(uint8_t *buffer, uint32_t bytesToRead) override; - -protected: -private: - FILE *inputFile_; - uint32_t bytesRead_; -}; - -class TransferPakNullCopySource : public ITransferPakDataCopySource -{ -public: - TransferPakNullCopySource(); - virtual ~TransferPakNullCopySource(); - - bool readyForTransfer() const override; - - uint16_t getCurrentBankIndex() const override; - uint32_t getNumberOfBytesRead() const override; - - uint32_t read(uint8_t *buffer, uint32_t bytesToRead) override; -protected: -private: - uint32_t bytesRead_; -}; - -/** - * @brief This interface is used to define a transfer pak data destination to copy to - * - */ -class ITransferPakDataCopyDestination -{ -public: - virtual ~ITransferPakDataCopyDestination(); - - virtual bool readyForTransfer() const = 0; - - virtual uint16_t getCurrentBankIndex() const = 0; - virtual uint32_t getNumberOfBytesWritten() const = 0; - - virtual uint32_t write(uint8_t *buffer, uint32_t bytesToWrite) = 0; - - virtual void close() = 0; - -protected: -private: -}; - -class TransferPakSaveManagerDestination : public ITransferPakDataCopyDestination -{ -public: - TransferPakSaveManagerDestination(TransferPakSaveManager& saveManager); - virtual ~TransferPakSaveManagerDestination(); - - bool readyForTransfer() const override; - - uint16_t getCurrentBankIndex() const override; - uint32_t getNumberOfBytesWritten() const override; - - uint32_t write(uint8_t *buffer, uint32_t bytesToWrite) override; - - void close() override; -protected: -private: - TransferPakSaveManager& saveManager_; - uint32_t bytesWritten_; -}; - -class TransferPakFileCopyDestination : public ITransferPakDataCopyDestination -{ -public: - TransferPakFileCopyDestination(const char *pathOnSDCard, bool resetRTC = false); - virtual ~TransferPakFileCopyDestination(); - - bool readyForTransfer() const override; - - uint16_t getCurrentBankIndex() const override; - uint32_t getNumberOfBytesWritten() const override; - - uint32_t write(uint8_t *buffer, uint32_t bytesToWrite) override; - - void close() override; - -protected: -private: - FILE *outputFile_; - uint32_t bytesWritten_; - bool resetRTC_; -}; - -/** - * This class directs the copy process from the source to the specified output file - * - * It exists to abstract the source (rom/SRAM) and control the copy flow/speed and to - * allow us to give UI feedback on the copy process. - * - * After all: I found that the transfer pak is able to read 32 bytes every 1,5-2milliseconds - * We don't want the UI to remain frozen during that time. - * (source: http://n64devkit.square7.ch/pro-man/pro26/26-07.htm) - */ -class TransferPakDataCopier -{ -public: - TransferPakDataCopier(ITransferPakDataCopySource &source, ITransferPakDataCopyDestination &destination); - ~TransferPakDataCopier(); - - uint16_t getCurrentBankIndex() const; - uint32_t getNumberOfBytesRead() const; - size_t copyChunk(uint32_t numBytesToCopy); - -protected: -private: - ITransferPakDataCopySource &source_; - ITransferPakDataCopyDestination &destination_; -}; - +#ifndef _TRANSFERPAKDATACOPIER_H +#define _TRANSFERPAKDATACOPIER_H + +#include +#include + +class TransferPakRomReader; +class TransferPakSaveManager; + +/** + * This interface is used to define a transfer pak datasource to copy from + */ +class ITransferPakDataCopySource +{ +public: + virtual ~ITransferPakDataCopySource(); + + virtual bool readyForTransfer() const = 0; + + virtual uint16_t getCurrentBankIndex() const = 0; + virtual uint32_t getNumberOfBytesRead() const = 0; + + virtual uint32_t read(uint8_t *buffer, uint32_t bytesToRead) = 0; + +protected: +private: +}; + +/** + * This class implements the ITransferPakDataCopySource interface with TransferPakRomReader + */ +class TransferPakRomReaderCopySource : public ITransferPakDataCopySource +{ +public: + TransferPakRomReaderCopySource(TransferPakRomReader &romReader); + virtual ~TransferPakRomReaderCopySource(); + + bool readyForTransfer() const override; + + uint16_t getCurrentBankIndex() const override; + uint32_t getNumberOfBytesRead() const override; + + uint32_t read(uint8_t *buffer, uint32_t bytesToRead) override; + +protected: +private: + TransferPakRomReader &romReader_; + uint32_t bytesRead_; +}; + +/** + * This class implements the ITransferPakDataCopySource interface with TransferPakRomReader + */ +class TransferPakSaveManagerCopySource : public ITransferPakDataCopySource +{ +public: + TransferPakSaveManagerCopySource(TransferPakSaveManager &saveManager); + virtual ~TransferPakSaveManagerCopySource(); + + bool readyForTransfer() const override; + + uint16_t getCurrentBankIndex() const override; + uint32_t getNumberOfBytesRead() const override; + + uint32_t read(uint8_t *buffer, uint32_t bytesToRead) override; + +protected: +private: + TransferPakSaveManager &saveManager_; + uint32_t bytesRead_; +}; + +class TransferPakFileCopySource : public ITransferPakDataCopySource +{ +public: + TransferPakFileCopySource(const char *filePath); + virtual ~TransferPakFileCopySource(); + + bool readyForTransfer() const override; + + uint16_t getCurrentBankIndex() const override; + uint32_t getNumberOfBytesRead() const override; + + uint32_t read(uint8_t *buffer, uint32_t bytesToRead) override; + +protected: +private: + FILE *inputFile_; + uint32_t bytesRead_; +}; + +class TransferPakNullCopySource : public ITransferPakDataCopySource +{ +public: + TransferPakNullCopySource(); + virtual ~TransferPakNullCopySource(); + + bool readyForTransfer() const override; + + uint16_t getCurrentBankIndex() const override; + uint32_t getNumberOfBytesRead() const override; + + uint32_t read(uint8_t *buffer, uint32_t bytesToRead) override; +protected: +private: + uint32_t bytesRead_; +}; + +/** + * @brief This interface is used to define a transfer pak data destination to copy to + * + */ +class ITransferPakDataCopyDestination +{ +public: + virtual ~ITransferPakDataCopyDestination(); + + virtual bool readyForTransfer() const = 0; + + virtual uint16_t getCurrentBankIndex() const = 0; + virtual uint32_t getNumberOfBytesWritten() const = 0; + + virtual uint32_t write(uint8_t *buffer, uint32_t bytesToWrite) = 0; + + virtual void close() = 0; + +protected: +private: +}; + +class TransferPakSaveManagerDestination : public ITransferPakDataCopyDestination +{ +public: + TransferPakSaveManagerDestination(TransferPakSaveManager& saveManager); + virtual ~TransferPakSaveManagerDestination(); + + bool readyForTransfer() const override; + + uint16_t getCurrentBankIndex() const override; + uint32_t getNumberOfBytesWritten() const override; + + uint32_t write(uint8_t *buffer, uint32_t bytesToWrite) override; + + void close() override; +protected: +private: + TransferPakSaveManager& saveManager_; + uint32_t bytesWritten_; +}; + +class TransferPakFileCopyDestination : public ITransferPakDataCopyDestination +{ +public: + TransferPakFileCopyDestination(const char *pathOnSDCard); + virtual ~TransferPakFileCopyDestination(); + + bool readyForTransfer() const override; + + uint16_t getCurrentBankIndex() const override; + uint32_t getNumberOfBytesWritten() const override; + + uint32_t write(uint8_t *buffer, uint32_t bytesToWrite) override; + + void close() override; + +protected: +private: + FILE *outputFile_; + uint32_t bytesWritten_; +}; + +/** This class is used to validate the copied data. */ +class FileValidationCopyDestination : public ITransferPakDataCopyDestination +{ +public: + FileValidationCopyDestination(const char *pathOnSDCard); + virtual ~FileValidationCopyDestination(); + + bool readyForTransfer() const override; + + uint16_t getCurrentBankIndex() const override; + uint32_t getNumberOfBytesWritten() const override; + + uint32_t write(uint8_t *buffer, uint32_t bytesToWrite) override; + + void close() override; + + bool isDataValid() const; +protected: +private: + FILE *inputFile_; + uint32_t bytesValidated_; + bool isValid_; +}; + +/** + * This class directs the copy process from the source to the specified output file + * + * It exists to abstract the source (rom/SRAM) and control the copy flow/speed and to + * allow us to give UI feedback on the copy process. + * + * After all: I found that the transfer pak is able to read 32 bytes every 1,5-2milliseconds + * We don't want the UI to remain frozen during that time. + * (source: http://n64devkit.square7.ch/pro-man/pro26/26-07.htm) + */ +class TransferPakDataCopier +{ +public: + TransferPakDataCopier(ITransferPakDataCopySource &source, ITransferPakDataCopyDestination &destination); + ~TransferPakDataCopier(); + + uint16_t getCurrentBankIndex() const; + uint32_t getNumberOfBytesRead() const; + size_t copyChunk(uint32_t numBytesToCopy); + +protected: +private: + ITransferPakDataCopySource &source_; + ITransferPakDataCopyDestination &destination_; +}; + #endif \ No newline at end of file diff --git a/src/scenes/AboutScene.cpp b/src/scenes/AboutScene.cpp index 3110ea4..9de2417 100644 --- a/src/scenes/AboutScene.cpp +++ b/src/scenes/AboutScene.cpp @@ -37,7 +37,7 @@ TESTING/VALIDATION MajorUpgrade )delim"; -static const char* headerTextString = R"delim(PokeMe64 Version 0.3 +static const char* headerTextString = R"delim(PokeMe64 Version 0.3.1-dev by risingPhil SPECIAL THANKS TO: diff --git a/src/scenes/DataCopyScene.cpp b/src/scenes/DataCopyScene.cpp index fb1bc48..2b678c6 100644 --- a/src/scenes/DataCopyScene.cpp +++ b/src/scenes/DataCopyScene.cpp @@ -1,362 +1,458 @@ -#include "scenes/DataCopyScene.h" -#include "core/DragonUtils.h" -#include "scenes/SceneManager.h" -#include "menu/MenuFunctions.h" -#include "gen1/Gen1Common.h" -#include "gen2/Gen2Common.h" - -#include - -//missing function declaration in libdragons' system.h, but the definition exists in system.c -int mkdir( const char * path, mode_t mode ); - -/** - * Copying from or to the transfer pak is a blocking operation. - * So while we're doing that, we can't render anything. - * - * So, in order to not just entirely freeze until the copy operation is done, we copy - * in chunks. - * - * We know from http://n64devkit.square7.ch/pro-man/pro26/26-07.htm - * that the transfer pak is able to read 32 bytes every 1,5 - 2 milliseconds. - * - * Our copy operation at the moment isn't entirely efficient, so it likely is slower. - * But just basing off that number, 4096 bytes should be transferred within +- 256 ms - * - * That means we would theoretically render at 4fps during the copy with this chunk size. - * (due to our copy implementation, it's likely less though) - */ -static int COPY_CHUNK_SIZE_IN_BYTES = 4096; - -static void dialogFinishedCallback(void* context) -{ - DataCopyScene* scene = (DataCopyScene*)context; - scene->onDialogDone(); -} - -/** - * @brief The reason this function exists is because I first tried to use the cartridge header title + trainerName (max 11 chars) + unique number as the game save filename - * but it turned out being a bit too close to fill the entire second line of the DialogWidget because the path became too long. - * The solution is to just create a shorter game title (just "Blue" or "Red" or "Crystal"). That frees up some room in the DialogWidget - * for the trainername and save number - */ -static void generateRomTitle(char* outputPath, const gameboy_cartridge_header& gbHeader, uint8_t generation, uint8_t specificGenVersion) -{ - if(generation == 1) - { - switch(static_cast(specificGenVersion)) - { - case Gen1GameType::BLUE: - strcpy(outputPath, "Blue"); - break; - case Gen1GameType::RED: - strcpy(outputPath, "Red"); - break; - case Gen1GameType::GREEN: - strcpy(outputPath, "Green"); - break; - case Gen1GameType::YELLOW: - strcpy(outputPath, "Yellow"); - break; - default: - strcpy(outputPath, "Unknown"); - break; - } - } - else if(generation == 2) - { - switch(static_cast(specificGenVersion)) - { - case Gen2GameType::GOLD: - strcpy(outputPath, "Gold"); - break; - case Gen2GameType::SILVER: - strcpy(outputPath, "Silver"); - break; - case Gen2GameType::CRYSTAL: - strcpy(outputPath, "Crystal"); - break; - default: - strcpy(outputPath, "Unknown"); - break; - } - } - else - { - // the title field of the gameboy header is likely truncated. - // create a copy and make sure to append a null character so we won't crash when trying to use it as a string - memcpy(outputPath, gbHeader.new_title.title, 11); - outputPath[11] = '\0'; - } -} - -static void generateSaveFileName(char* savOutputPath, size_t bufferSize, const char* gameTitle, const char* playerName) -{ - struct stat statStruct; - unsigned uniqueNumber = 0; - const size_t playerNameSize = strlen(playerName); - - if(playerNameSize) - { - snprintf(savOutputPath, bufferSize - 1, "sd:/PokeMe64/%s_%s.sav", gameTitle, playerName); - } - else - { - snprintf(savOutputPath, bufferSize - 1, "sd:/PokeMe64/%s.sav", gameTitle); - } - - while(stat(savOutputPath, &statStruct) == 0) - { - if(playerNameSize) - { - snprintf(savOutputPath, bufferSize - 1, "sd:/PokeMe64/%s_%s_%u.sav", gameTitle, playerName, uniqueNumber); - } - else - { - snprintf(savOutputPath, bufferSize - 1, "sd:/PokeMe64/%s_%u.sav", gameTitle, uniqueNumber); - } - ++uniqueNumber; - } -} - -DataCopyScene::DataCopyScene(SceneDependencies& deps, void* context) - : SceneWithProgressBar(deps) - , romReader_(deps.tpakManager) - , saveManager_(deps.tpakManager) - , sceneContext_((DataCopySceneContext*)context) - , copySource_(nullptr) - , copyDestination_(nullptr) - , copier_(nullptr) - , dialogWidgetSprite_(nullptr) - , progressBackgroundSprite_(nullptr) - , diag_({0}) - , totalBytesToCopy_(0) -{ - (void)context; -} - -DataCopyScene::~DataCopyScene() -{ -} - -void DataCopyScene::init() -{ - char savOutputPath[4096]; - char romOutputPath[4096]; - char gameTitle[12]; - dialogWidgetSprite_ = sprite_load("rom://menu-bg-9slice.sprite"); - progressBackgroundSprite_ = sprite_load("rom://bg-nineslice-transparant-border.sprite"); - - SceneWithProgressBar::init(); - - // check if the n64 flashcart is supported - if(!doesN64FlashCartSupportSDCardAccess()) - { - setDialogDataText(diag_, "Sorry! This is only supported on 64Drive, Everdrive64, ED64Plus and SummerCart64!"); - showDialog(&diag_); - return; - } - - // check if the sd card is mounted - if(!sdcard_mounted) - { - setDialogDataText(diag_, "ERROR: SD card is not mounted!"); - showDialog(&diag_); - return; - } - - mkdir("sd:/PokeMe64", 0777); - - gameboy_cartridge_header gbHeader; - deps_.tpakManager.readCartridgeHeader(gbHeader); - - generateRomTitle(gameTitle, gbHeader, deps_.generation, deps_.specificGenVersion); - - auto msg2 = new DialogData{ - .shouldDeleteWhenDone = true - }; - - switch(sceneContext_->operation) - { - case DataCopyOperation::BACKUP_SAVE: - generateSaveFileName(savOutputPath, sizeof(savOutputPath), gameTitle, deps_.playerName); - copySource_ = new TransferPakSaveManagerCopySource(saveManager_); - copyDestination_ = new TransferPakFileCopyDestination(savOutputPath, (deps_.generation == 2)); - totalBytesToCopy_ = convertSRAMSizeIntoNumBytes(gbHeader.ram_size_code); - setDialogDataText(*msg2, "The save was backed up to %s!", savOutputPath); - break; - case DataCopyOperation::BACKUP_ROM: - snprintf(romOutputPath, sizeof(savOutputPath) - 1, "sd:/PokeMe64/%s.gbc", gameTitle); - copySource_ = new TransferPakRomReaderCopySource(romReader_); - copyDestination_ = new TransferPakFileCopyDestination(romOutputPath); - totalBytesToCopy_ = convertROMSizeIntoNumBytes(gbHeader.rom_size_code); - setDialogDataText(*msg2, "The cartridge rom was backed up to %s!", romOutputPath); - break; - case DataCopyOperation::RESTORE_SAVE: - copySource_ = new TransferPakFileCopySource(sceneContext_->saveToRestorePath.get()); - copyDestination_ = new TransferPakSaveManagerDestination(saveManager_); - totalBytesToCopy_ = convertSRAMSizeIntoNumBytes(gbHeader.ram_size_code); - setDialogDataText(*msg2, "The save was restored to the cartridge!", romOutputPath); - break; - case DataCopyOperation::WIPE_SAVE: - copySource_ = new TransferPakNullCopySource(); - copyDestination_ = new TransferPakSaveManagerDestination(saveManager_); - totalBytesToCopy_ = convertSRAMSizeIntoNumBytes(gbHeader.ram_size_code); - setDialogDataText(*msg2, "The save file was wiped from the cartridge!"); - break; - } - - if(!copySource_->readyForTransfer()) - { - if(sceneContext_->operation == DataCopyOperation::RESTORE_SAVE) - { - setDialogDataText(diag_, "ERROR: Could not read from file %s!", sceneContext_->saveToRestorePath.get()); - } - else - { - setDialogDataText(diag_, "ERROR: Could not read from cartridge!"); - } - - // not needed - delete msg2; - msg2 = nullptr; - - // now show the error dialog - showDialog(&diag_); - return; - } - - if(!copyDestination_->readyForTransfer()) - { - if(sceneContext_->operation == DataCopyOperation::RESTORE_SAVE) - { - setDialogDataText(diag_, "ERROR: Could not write to cartridge!"); - } - else - { - const char* outputPath = (sceneContext_->operation == DataCopyOperation::BACKUP_SAVE) ? savOutputPath : romOutputPath; - setDialogDataText(diag_, "ERROR: Could not write to file %s!", outputPath); - } - - // not needed - delete msg2; - msg2 = nullptr; - // now show the error dialog - showDialog(&diag_); - return; - } - - if(sceneContext_->operation == DataCopyOperation::WIPE_SAVE) - { - setDialogDataText(diag_, "Wiping. Please Wait..."); - } - { - setDialogDataText(diag_, "Copying. Please Wait..."); - } - diag_.userAdvanceBlocked = true; - diag_.next = msg2; - showDialog(&diag_); - - deps_.tpakManager.setRAMEnabled(true); - copier_ = new TransferPakDataCopier(*copySource_, *copyDestination_); -} - -void DataCopyScene::destroy() -{ - sprite_free(dialogWidgetSprite_); - dialogWidgetSprite_ = nullptr; - sprite_free(progressBackgroundSprite_); - progressBackgroundSprite_ = nullptr; - - if(copier_) - { - delete copier_; - copier_ = nullptr; - } - - if(copySource_) - { - delete copySource_; - copySource_ = nullptr; - } - - if(copyDestination_) - { - delete copyDestination_; - copyDestination_ = nullptr; - } - - deps_.tpakManager.setRAMEnabled(false); - SceneWithProgressBar::destroy(); -} - -void DataCopyScene::processUserInput() -{ - if(copier_ && copyDestination_ && copyDestination_->getNumberOfBytesWritten() < totalBytesToCopy_) - { - const uint32_t numBytesToCopy = std::min(COPY_CHUNK_SIZE_IN_BYTES, totalBytesToCopy_ - copyDestination_->getNumberOfBytesWritten()); - copier_->copyChunk(numBytesToCopy); - - setProgress(static_cast(copyDestination_->getNumberOfBytesWritten()) / static_cast(totalBytesToCopy_)); - } - - if(copier_ && copyDestination_ && copyDestination_->getNumberOfBytesWritten() >= totalBytesToCopy_) - { - deps_.tpakManager.setRAMEnabled(false); - copyDestination_->close(); - delete copySource_; - copySource_ = nullptr; - delete copyDestination_; - copyDestination_ = nullptr; - delete copier_; - - // The copy operation is done, now advance the blocked dialog entry to the final one - advanceDialog(); - } - - SceneWithProgressBar::processUserInput(); -} - -void DataCopyScene::render(RDPQGraphics& gfx, const Rectangle& sceneBounds) -{ - SceneWithProgressBar::render(gfx, sceneBounds); -} - -void DataCopyScene::onDialogDone() -{ - deps_.sceneManager.goBackToPreviousScene(); -} - -void DataCopyScene::setupDialog(DialogWidgetStyle& style) -{ - style.background.sprite = dialogWidgetSprite_; - style.background.spriteSettings = { - .renderMode = SpriteRenderMode::NINESLICE, - .srcRect = { 6, 6, 6, 6 } - }; - - SceneWithProgressBar::setupDialog(style); - - dialogWidget_.setOnDialogFinishedCallback(dialogFinishedCallback, this); - dialogWidget_.setVisible(false); -} - -void DataCopyScene::setupProgressBar(ProgressBarWidgetStyle& style) -{ - SceneWithProgressBar::setupProgressBar(style); - - style.background = { - .sprite = progressBackgroundSprite_, - .renderSettings = { - .renderMode = SpriteRenderMode::NINESLICE, - .srcRect = { 6, 6, 6, 6 } - } - }; -} - -void deleteDataCopySceneContext(void* context) -{ - DataCopySceneContext* sceneContext = (DataCopySceneContext*)context; - - delete sceneContext; +#include "scenes/DataCopyScene.h" +#include "core/DragonUtils.h" +#include "scenes/SceneManager.h" +#include "menu/MenuFunctions.h" +#include "gen1/Gen1Common.h" +#include "gen2/Gen2Common.h" + +#include +#include +#include + +//missing function declaration in libdragons' system.h, but the definition exists in system.c +int mkdir( const char * path, mode_t mode ); + +/** + * Copying from or to the transfer pak is a blocking operation. + * So while we're doing that, we can't render anything. + * + * So, in order to not just entirely freeze until the copy operation is done, we copy + * in chunks. + * + * We know from http://n64devkit.square7.ch/pro-man/pro26/26-07.htm + * that the transfer pak is able to read 32 bytes every 1,5 - 2 milliseconds. + * + * Our copy operation at the moment isn't entirely efficient, so it likely is slower. + * But just basing off that number, 4096 bytes should be transferred within +- 256 ms + * + * That means we would theoretically render at 4fps during the copy with this chunk size. + * (due to our copy implementation, it's likely less though) + */ +static int COPY_CHUNK_SIZE_IN_BYTES = 4096; + +static void dialogFinishedCallback(void* context) +{ + DataCopyScene* scene = (DataCopyScene*)context; + scene->onDialogDone(); +} + +/** + * @brief The reason this function exists is because I first tried to use the cartridge header title + trainerName (max 11 chars) + unique number as the game save filename + * but it turned out being a bit too close to fill the entire second line of the DialogWidget because the path became too long. + * The solution is to just create a shorter game title (just "Blue" or "Red" or "Crystal"). That frees up some room in the DialogWidget + * for the trainername and save number + */ +static void generateRomTitle(char* outputPath, const gameboy_cartridge_header& gbHeader, uint8_t generation, uint8_t specificGenVersion) +{ + if(generation == 1) + { + switch(static_cast(specificGenVersion)) + { + case Gen1GameType::BLUE: + strcpy(outputPath, "Blue"); + break; + case Gen1GameType::RED: + strcpy(outputPath, "Red"); + break; + case Gen1GameType::GREEN: + strcpy(outputPath, "Green"); + break; + case Gen1GameType::YELLOW: + strcpy(outputPath, "Yellow"); + break; + default: + strcpy(outputPath, "Unknown"); + break; + } + } + else if(generation == 2) + { + switch(static_cast(specificGenVersion)) + { + case Gen2GameType::GOLD: + strcpy(outputPath, "Gold"); + break; + case Gen2GameType::SILVER: + strcpy(outputPath, "Silver"); + break; + case Gen2GameType::CRYSTAL: + strcpy(outputPath, "Crystal"); + break; + default: + strcpy(outputPath, "Unknown"); + break; + } + } + else + { + // the title field of the gameboy header is likely truncated. + // create a copy and make sure to append a null character so we won't crash when trying to use it as a string + memcpy(outputPath, gbHeader.new_title.title, 11); + outputPath[11] = '\0'; + } +} + +static void generateSaveFileName(char* savOutputPath, size_t bufferSize, const char* gameTitle, const char* playerName) +{ + struct stat statStruct; + unsigned uniqueNumber = 0; + const size_t playerNameSize = strlen(playerName); + + if(playerNameSize) + { + snprintf(savOutputPath, bufferSize - 1, "sd:/PokeMe64/%s_%s.sav", gameTitle, playerName); + } + else + { + snprintf(savOutputPath, bufferSize - 1, "sd:/PokeMe64/%s.sav", gameTitle); + } + + while(stat(savOutputPath, &statStruct) == 0) + { + if(playerNameSize) + { + snprintf(savOutputPath, bufferSize - 1, "sd:/PokeMe64/%s_%s_%u.sav", gameTitle, playerName, uniqueNumber); + } + else + { + snprintf(savOutputPath, bufferSize - 1, "sd:/PokeMe64/%s_%u.sav", gameTitle, uniqueNumber); + } + ++uniqueNumber; + } +} + +/** + * @brief This function forces a Gen II game to request the user to reconfigure the RTC clock. + */ +static void resetGen2RTCInSavFile(const char* pathOnSDCard) +{ + FILE* outputFile = fopen(pathOnSDCard, "w"); + + // The game checks bit 7 on the sRTCStatusFlags field in SRAM + // this is set when the game detects wrong RTC register values. + // In order to let the game prompt to reconfigure the RTC clock, we just have to set this bit + // Based on sRTCStatusFlags, RecordRTCStatus, .set_bit_7 in + // https://github.com/pret/pokecrystal + // https://github.com/pret/pokegold + const uint8_t rtcStatusFieldValue = 0xC0; + if(fseek(outputFile, 0xC60, SEEK_SET) == 0) + { + // seek successful + fwrite(&rtcStatusFieldValue, 1, 1, outputFile); + } + + fclose(outputFile); +} + +DataCopyScene::DataCopyScene(SceneDependencies& deps, void* context) + : SceneWithProgressBar(deps) + , romReader_(deps.tpakManager) + , saveManager_(deps.tpakManager) + , sceneContext_((DataCopySceneContext*)context) + , copySource_(nullptr) + , copyDestination_(nullptr) + , copier_(nullptr) + , dialogWidgetSprite_(nullptr) + , progressBackgroundSprite_(nullptr) + , diag_({0}) + , totalBytesToCopy_(0) + , needsValidation_(false) + , isValidating_(false) + , shouldResetRTC_(false) + , savOutputPath_() +{ + (void)context; +} + +DataCopyScene::~DataCopyScene() +{ +} + +void DataCopyScene::init() +{ + char romOutputPath[4096]; + char gameTitle[12]; + + dialogWidgetSprite_ = sprite_load("rom://menu-bg-9slice.sprite"); + progressBackgroundSprite_ = sprite_load("rom://bg-nineslice-transparant-border.sprite"); + + SceneWithProgressBar::init(); + + // check if the n64 flashcart is supported + if(!doesN64FlashCartSupportSDCardAccess()) + { + setDialogDataText(diag_, "Sorry! This is only supported on 64Drive, Everdrive64, ED64Plus and SummerCart64!"); + showDialog(&diag_); + return; + } + + // check if the sd card is mounted + if(!sdcard_mounted) + { + setDialogDataText(diag_, "ERROR: SD card is not mounted!"); + showDialog(&diag_); + return; + } + + mkdir("sd:/PokeMe64", 0777); + + gameboy_cartridge_header gbHeader; + deps_.tpakManager.readCartridgeHeader(gbHeader); + + generateRomTitle(gameTitle, gbHeader, deps_.generation, deps_.specificGenVersion); + + auto msg2 = new DialogData{ + .shouldDeleteWhenDone = true + }; + + switch(sceneContext_->operation) + { + case DataCopyOperation::BACKUP_SAVE: + generateSaveFileName(savOutputPath_, sizeof(savOutputPath_), gameTitle, deps_.playerName); + copySource_ = new TransferPakSaveManagerCopySource(saveManager_); + copyDestination_ = new TransferPakFileCopyDestination(savOutputPath_); + totalBytesToCopy_ = convertSRAMSizeIntoNumBytes(gbHeader.ram_size_code); + setDialogDataText(*msg2, "The save was backed up to %s!", savOutputPath_); + needsValidation_ = true; + shouldResetRTC_ = (deps_.generation == 2); + break; + case DataCopyOperation::BACKUP_ROM: + snprintf(romOutputPath, sizeof(savOutputPath_) - 1, "sd:/PokeMe64/%s.gbc", gameTitle); + copySource_ = new TransferPakRomReaderCopySource(romReader_); + copyDestination_ = new TransferPakFileCopyDestination(romOutputPath); + totalBytesToCopy_ = convertROMSizeIntoNumBytes(gbHeader.rom_size_code); + setDialogDataText(*msg2, "The cartridge rom was backed up to %s!", romOutputPath); + break; + case DataCopyOperation::RESTORE_SAVE: + copySource_ = new TransferPakFileCopySource(sceneContext_->saveToRestorePath.get()); + copyDestination_ = new TransferPakSaveManagerDestination(saveManager_); + totalBytesToCopy_ = convertSRAMSizeIntoNumBytes(gbHeader.ram_size_code); + needsValidation_ = true; + setDialogDataText(*msg2, "The save was restored to the cartridge!", romOutputPath); + break; + case DataCopyOperation::WIPE_SAVE: + copySource_ = new TransferPakNullCopySource(); + copyDestination_ = new TransferPakSaveManagerDestination(saveManager_); + totalBytesToCopy_ = convertSRAMSizeIntoNumBytes(gbHeader.ram_size_code); + setDialogDataText(*msg2, "The save file was wiped from the cartridge!"); + break; + } + + if(!copySource_->readyForTransfer()) + { + if(sceneContext_->operation == DataCopyOperation::RESTORE_SAVE) + { + setDialogDataText(diag_, "ERROR: Could not read from file %s!", sceneContext_->saveToRestorePath.get()); + } + else + { + setDialogDataText(diag_, "ERROR: Could not read from cartridge!"); + } + + // not needed + delete msg2; + msg2 = nullptr; + + // now show the error dialog + showDialog(&diag_); + return; + } + + if(!copyDestination_->readyForTransfer()) + { + if(sceneContext_->operation == DataCopyOperation::RESTORE_SAVE) + { + setDialogDataText(diag_, "ERROR: Could not write to cartridge!"); + } + else + { + const char* outputPath = (sceneContext_->operation == DataCopyOperation::BACKUP_SAVE) ? savOutputPath_ : romOutputPath; + setDialogDataText(diag_, "ERROR: Could not write to file %s!", outputPath); + } + + // not needed + delete msg2; + msg2 = nullptr; + // now show the error dialog + showDialog(&diag_); + return; + } + + if(sceneContext_->operation == DataCopyOperation::WIPE_SAVE) + { + setDialogDataText(diag_, "Wiping. Please Wait..."); + } + { + setDialogDataText(diag_, "Copying. Please Wait..."); + } + + if(needsValidation_) + { + auto validationMsg = new DialogData{ + .next = msg2, + .shouldDeleteWhenDone = true, + .userAdvanceBlocked = true + }; + + setDialogDataText(*validationMsg, "Validating. Please Wait..."); + diag_.next = validationMsg; + } + else + { + diag_.next = msg2; + } + + diag_.userAdvanceBlocked = true; + showDialog(&diag_); + + deps_.tpakManager.setRAMEnabled(true); + copier_ = new TransferPakDataCopier(*copySource_, *copyDestination_); +} + +void DataCopyScene::destroy() +{ + sprite_free(dialogWidgetSprite_); + dialogWidgetSprite_ = nullptr; + sprite_free(progressBackgroundSprite_); + progressBackgroundSprite_ = nullptr; + + if(copier_) + { + delete copier_; + copier_ = nullptr; + } + + if(copySource_) + { + delete copySource_; + copySource_ = nullptr; + } + + if(copyDestination_) + { + delete copyDestination_; + copyDestination_ = nullptr; + } + + deps_.tpakManager.setRAMEnabled(false); + SceneWithProgressBar::destroy(); +} + +void DataCopyScene::processUserInput() +{ + if(copier_ && copyDestination_ && copyDestination_->getNumberOfBytesWritten() < totalBytesToCopy_) + { + const uint32_t numBytesToCopy = std::min(COPY_CHUNK_SIZE_IN_BYTES, totalBytesToCopy_ - copyDestination_->getNumberOfBytesWritten()); + copier_->copyChunk(numBytesToCopy); + + setProgress(static_cast(copyDestination_->getNumberOfBytesWritten()) / static_cast(totalBytesToCopy_)); + } + + if(copier_ && copyDestination_ && copyDestination_->getNumberOfBytesWritten() >= totalBytesToCopy_) + { + bool isDataValid = false; + + // if validating, we need to retrieve the result before destroying copyDestination_ + if(isValidating_) + { + auto validationDest = static_cast(copyDestination_); + isDataValid = validationDest->isDataValid(); + } + + deps_.tpakManager.setRAMEnabled(false); + copyDestination_->close(); + delete copySource_; + copySource_ = nullptr; + delete copyDestination_; + copyDestination_ = nullptr; + delete copier_; + + // we reached the end of a phase (either copy or validation) + // if the current phase was the validation phase, we need to + // check the validation result and show error dialog if validation failed + if(isValidating_) + { + if(!isDataValid) + { + auto errMsg = new DialogData{ + .shouldDeleteWhenDone = true, + }; + setDialogDataText(*errMsg, "ERROR: Validation failed! Check Controller or Transfer Pak connection please!"); + unlink(savOutputPath_); + showDialog(errMsg); + return; + } + + // Validation succeeded. But for gen 2 saves, we want to reset the RTC data to make sure + // the clock can be reconfigured after restoring the save to a cartridge after a cartridge swap, + // a different cartridge alltogether or when using it in an emulator + // We do this step AFTER validation to avoid tampering with the save data before validation + if(shouldResetRTC_) + { + resetGen2RTCInSavFile(savOutputPath_); + } + } + + // Set up validation phase if needed + if(needsValidation_) + { + isValidating_ = true; + needsValidation_ = false; + + copySource_ = new TransferPakSaveManagerCopySource(saveManager_); + copyDestination_ = new FileValidationCopyDestination(savOutputPath_); + copier_ = new TransferPakDataCopier(*copySource_, *copyDestination_); + + deps_.tpakManager.setRAMEnabled(true); + } + + // The copy operation is done, now advance the blocked dialog entry + advanceDialog(); + } + + SceneWithProgressBar::processUserInput(); +} + +void DataCopyScene::render(RDPQGraphics& gfx, const Rectangle& sceneBounds) +{ + SceneWithProgressBar::render(gfx, sceneBounds); +} + +void DataCopyScene::onDialogDone() +{ + deps_.sceneManager.goBackToPreviousScene(); +} + +void DataCopyScene::setupDialog(DialogWidgetStyle& style) +{ + style.background.sprite = dialogWidgetSprite_; + style.background.spriteSettings = { + .renderMode = SpriteRenderMode::NINESLICE, + .srcRect = { 6, 6, 6, 6 } + }; + + SceneWithProgressBar::setupDialog(style); + + dialogWidget_.setOnDialogFinishedCallback(dialogFinishedCallback, this); + dialogWidget_.setVisible(false); +} + +void DataCopyScene::setupProgressBar(ProgressBarWidgetStyle& style) +{ + SceneWithProgressBar::setupProgressBar(style); + + style.background = { + .sprite = progressBackgroundSprite_, + .renderSettings = { + .renderMode = SpriteRenderMode::NINESLICE, + .srcRect = { 6, 6, 6, 6 } + } + }; +} + +void deleteDataCopySceneContext(void* context) +{ + DataCopySceneContext* sceneContext = (DataCopySceneContext*)context; + + delete sceneContext; } \ No newline at end of file diff --git a/src/scenes/InitTransferPakScene.cpp b/src/scenes/InitTransferPakScene.cpp index 872ca8a..543f847 100755 --- a/src/scenes/InitTransferPakScene.cpp +++ b/src/scenes/InitTransferPakScene.cpp @@ -77,7 +77,7 @@ void InitTransferPakScene::destroy() void InitTransferPakScene::render(RDPQGraphics& gfx, const Rectangle& sceneBounds) { - gfx.drawText(Rectangle{0, 10, 320, 16}, "PokeMe64 by risingPhil. Version 0.3", pokeMe64TextSettings_); + gfx.drawText(Rectangle{0, 10, 320, 16}, "PokeMe64 by risingPhil. Version 0.3.1-dev", pokeMe64TextSettings_); tpakDetectWidget_.render(gfx, sceneBounds); SceneWithDialogWidget::render(gfx, sceneBounds); diff --git a/src/transferpak/TransferPakDataCopier.cpp b/src/transferpak/TransferPakDataCopier.cpp index b3ff47a..ad3f1d0 100644 --- a/src/transferpak/TransferPakDataCopier.cpp +++ b/src/transferpak/TransferPakDataCopier.cpp @@ -1,295 +1,357 @@ -#include "transferpak/TransferPakDataCopier.h" -#include "transferpak/TransferPakRomReader.h" -#include "transferpak/TransferPakSaveManager.h" - -#include - -ITransferPakDataCopySource::~ITransferPakDataCopySource() -{ -} - -ITransferPakDataCopyDestination::~ITransferPakDataCopyDestination() -{ -} - -TransferPakRomReaderCopySource::TransferPakRomReaderCopySource(TransferPakRomReader& romReader) - : romReader_(romReader) - , bytesRead_(0) -{ -} - -TransferPakRomReaderCopySource::~TransferPakRomReaderCopySource() -{ -} - - -bool TransferPakRomReaderCopySource::readyForTransfer() const -{ - return true; -} - -uint16_t TransferPakRomReaderCopySource::getCurrentBankIndex() const -{ - return romReader_.getCurrentBankIndex(); -} - -uint32_t TransferPakRomReaderCopySource::getNumberOfBytesRead() const -{ - return bytesRead_; -} - -uint32_t TransferPakRomReaderCopySource::read(uint8_t* buffer, uint32_t bytesToRead) -{ - if(romReader_.read(buffer, bytesToRead)) - { - bytesRead_ += bytesToRead; - return bytesToRead; - } - return 0; -} - -TransferPakSaveManagerCopySource::TransferPakSaveManagerCopySource(TransferPakSaveManager& saveManager) - : saveManager_(saveManager) - , bytesRead_(0) -{ -} - -TransferPakSaveManagerCopySource::~TransferPakSaveManagerCopySource() -{ -} - -bool TransferPakSaveManagerCopySource::readyForTransfer() const -{ - return true; -} - -uint16_t TransferPakSaveManagerCopySource::getCurrentBankIndex() const -{ - return saveManager_.getCurrentBankIndex(); -} - -uint32_t TransferPakSaveManagerCopySource::getNumberOfBytesRead() const -{ - return bytesRead_; -} - -uint32_t TransferPakSaveManagerCopySource::read(uint8_t* buffer, uint32_t bytesToRead) -{ - if(saveManager_.read(buffer, bytesToRead)) - { - bytesRead_ += bytesToRead; - return bytesToRead; - } - return 0; -} - -TransferPakFileCopySource::TransferPakFileCopySource(const char* filePath) - : inputFile_(nullptr) - , bytesRead_(0) -{ - inputFile_ = fopen(filePath, "r"); -} - -TransferPakFileCopySource::~TransferPakFileCopySource() -{ - if(inputFile_) - { - fclose(inputFile_); - inputFile_ = nullptr; - } -} - -bool TransferPakFileCopySource::readyForTransfer() const -{ - return (inputFile_ != nullptr); -} - -uint16_t TransferPakFileCopySource::getCurrentBankIndex() const -{ - return 1; -} - -uint32_t TransferPakFileCopySource::getNumberOfBytesRead() const -{ - return bytesRead_; -} - -uint32_t TransferPakFileCopySource::read(uint8_t* buffer, uint32_t bytesToRead) -{ - uint32_t ret = static_cast(fread(buffer, sizeof(char), bytesToRead, inputFile_)); - bytesRead_ += ret; - return ret; -} - -TransferPakNullCopySource::TransferPakNullCopySource() - : bytesRead_(0) -{ -} - -TransferPakNullCopySource::~TransferPakNullCopySource() -{ -} - -bool TransferPakNullCopySource::readyForTransfer() const -{ - return true; -} - -uint16_t TransferPakNullCopySource::getCurrentBankIndex() const -{ - return 1; -} - -uint32_t TransferPakNullCopySource::getNumberOfBytesRead() const -{ - return bytesRead_; -} - -uint32_t TransferPakNullCopySource::read(uint8_t *buffer, uint32_t bytesToRead) -{ - memset(buffer, 0, bytesToRead); - return bytesToRead; -} - -TransferPakSaveManagerDestination::TransferPakSaveManagerDestination(TransferPakSaveManager& saveManager) - : saveManager_(saveManager) - , bytesWritten_(0) -{ -} - -TransferPakSaveManagerDestination::~TransferPakSaveManagerDestination() -{ - close(); -} - -bool TransferPakSaveManagerDestination::readyForTransfer() const -{ - return true; -} - -uint16_t TransferPakSaveManagerDestination::getCurrentBankIndex() const -{ - return saveManager_.getCurrentBankIndex(); -} - -uint32_t TransferPakSaveManagerDestination::getNumberOfBytesWritten() const -{ - return bytesWritten_; -} - -uint32_t TransferPakSaveManagerDestination::write(uint8_t* buffer, uint32_t bytesToWrite) -{ - saveManager_.write(buffer, bytesToWrite); - bytesWritten_ += bytesToWrite; - return bytesToWrite; -} - -void TransferPakSaveManagerDestination::close() -{ - // dummy -} - -TransferPakFileCopyDestination::TransferPakFileCopyDestination(const char* pathOnSDCard, bool resetRTC) - : outputFile_(nullptr) - , bytesWritten_(0) - , resetRTC_(resetRTC) -{ - outputFile_ = fopen(pathOnSDCard, "w"); -} - -TransferPakFileCopyDestination::~TransferPakFileCopyDestination() -{ - close(); -} - -bool TransferPakFileCopyDestination::readyForTransfer() const -{ - return (outputFile_ != nullptr); -} - -uint16_t TransferPakFileCopyDestination::getCurrentBankIndex() const -{ - return 1; -} - -uint32_t TransferPakFileCopyDestination::getNumberOfBytesWritten() const -{ - return bytesWritten_; -} - -uint32_t TransferPakFileCopyDestination::write(uint8_t* buffer, uint32_t bytesToWrite) -{ - const uint32_t ret = static_cast(fwrite(buffer, sizeof(char), bytesToWrite, outputFile_)); - bytesWritten_ += ret; - return ret; -} - -void TransferPakFileCopyDestination::close() -{ - if(!outputFile_) - { - return; - } - - if(resetRTC_) - { - // The game checks bit 7 on the sRTCStatusFlags field in SRAM - // this is set when the game detects wrong RTC register values. - // In order to let the game prompt to reconfigure the RTC clock, we just have to set this bit - // Based on sRTCStatusFlags, RecordRTCStatus, .set_bit_7 in - // https://github.com/pret/pokecrystal - // https://github.com/pret/pokegold - const uint8_t rtcStatusFieldValue = 0xC0; - if(fseek(outputFile_, 0xC60, SEEK_SET) == 0) - { - // seek successful - fwrite(&rtcStatusFieldValue, 1, 1, outputFile_); - } - } - - fclose(outputFile_); - outputFile_ = nullptr; -} - -TransferPakDataCopier::TransferPakDataCopier(ITransferPakDataCopySource& source, ITransferPakDataCopyDestination& destination) - : source_(source) - , destination_(destination) -{ -} - -TransferPakDataCopier::~TransferPakDataCopier() -{ -} - -uint16_t TransferPakDataCopier::getCurrentBankIndex() const -{ - return source_.getCurrentBankIndex(); -} - -uint32_t TransferPakDataCopier::getNumberOfBytesRead() const -{ - return source_.getNumberOfBytesRead(); -} - -size_t TransferPakDataCopier::copyChunk(uint32_t numBytesToCopy) -{ - constexpr uint16_t bufferSize = 256; - uint8_t buffer[bufferSize]; - uint32_t bytesRemaining = numBytesToCopy; - uint32_t bytesToRead; - - while(bytesRemaining > 0) - { - bytesToRead = (bufferSize < bytesRemaining) ? bufferSize : bytesRemaining; - - if(!source_.read(buffer, bytesToRead)) - { - // no bytes read. Abort - break; - } - - // now write the bytes to the destination - bytesRemaining -= destination_.write(buffer, bytesToRead); - } - return numBytesToCopy - bytesRemaining; +#include "transferpak/TransferPakDataCopier.h" +#include "transferpak/TransferPakRomReader.h" +#include "transferpak/TransferPakSaveManager.h" + +#include + +ITransferPakDataCopySource::~ITransferPakDataCopySource() +{ +} + +ITransferPakDataCopyDestination::~ITransferPakDataCopyDestination() +{ +} + +TransferPakRomReaderCopySource::TransferPakRomReaderCopySource(TransferPakRomReader& romReader) + : romReader_(romReader) + , bytesRead_(0) +{ +} + +TransferPakRomReaderCopySource::~TransferPakRomReaderCopySource() +{ +} + + +bool TransferPakRomReaderCopySource::readyForTransfer() const +{ + return true; +} + +uint16_t TransferPakRomReaderCopySource::getCurrentBankIndex() const +{ + return romReader_.getCurrentBankIndex(); +} + +uint32_t TransferPakRomReaderCopySource::getNumberOfBytesRead() const +{ + return bytesRead_; +} + +uint32_t TransferPakRomReaderCopySource::read(uint8_t* buffer, uint32_t bytesToRead) +{ + if(romReader_.read(buffer, bytesToRead)) + { + bytesRead_ += bytesToRead; + return bytesToRead; + } + return 0; +} + +TransferPakSaveManagerCopySource::TransferPakSaveManagerCopySource(TransferPakSaveManager& saveManager) + : saveManager_(saveManager) + , bytesRead_(0) +{ +} + +TransferPakSaveManagerCopySource::~TransferPakSaveManagerCopySource() +{ +} + +bool TransferPakSaveManagerCopySource::readyForTransfer() const +{ + return true; +} + +uint16_t TransferPakSaveManagerCopySource::getCurrentBankIndex() const +{ + return saveManager_.getCurrentBankIndex(); +} + +uint32_t TransferPakSaveManagerCopySource::getNumberOfBytesRead() const +{ + return bytesRead_; +} + +uint32_t TransferPakSaveManagerCopySource::read(uint8_t* buffer, uint32_t bytesToRead) +{ + if(saveManager_.read(buffer, bytesToRead)) + { + bytesRead_ += bytesToRead; + return bytesToRead; + } + return 0; +} + +TransferPakFileCopySource::TransferPakFileCopySource(const char* filePath) + : inputFile_(nullptr) + , bytesRead_(0) +{ + inputFile_ = fopen(filePath, "r"); +} + +TransferPakFileCopySource::~TransferPakFileCopySource() +{ + if(inputFile_) + { + fclose(inputFile_); + inputFile_ = nullptr; + } +} + +bool TransferPakFileCopySource::readyForTransfer() const +{ + return (inputFile_ != nullptr); +} + +uint16_t TransferPakFileCopySource::getCurrentBankIndex() const +{ + return 1; +} + +uint32_t TransferPakFileCopySource::getNumberOfBytesRead() const +{ + return bytesRead_; +} + +uint32_t TransferPakFileCopySource::read(uint8_t* buffer, uint32_t bytesToRead) +{ + uint32_t ret = static_cast(fread(buffer, sizeof(char), bytesToRead, inputFile_)); + bytesRead_ += ret; + return ret; +} + +TransferPakNullCopySource::TransferPakNullCopySource() + : bytesRead_(0) +{ +} + +TransferPakNullCopySource::~TransferPakNullCopySource() +{ +} + +bool TransferPakNullCopySource::readyForTransfer() const +{ + return true; +} + +uint16_t TransferPakNullCopySource::getCurrentBankIndex() const +{ + return 1; +} + +uint32_t TransferPakNullCopySource::getNumberOfBytesRead() const +{ + return bytesRead_; +} + +uint32_t TransferPakNullCopySource::read(uint8_t *buffer, uint32_t bytesToRead) +{ + memset(buffer, 0, bytesToRead); + return bytesToRead; +} + +TransferPakSaveManagerDestination::TransferPakSaveManagerDestination(TransferPakSaveManager& saveManager) + : saveManager_(saveManager) + , bytesWritten_(0) +{ +} + +TransferPakSaveManagerDestination::~TransferPakSaveManagerDestination() +{ + close(); +} + +bool TransferPakSaveManagerDestination::readyForTransfer() const +{ + return true; +} + +uint16_t TransferPakSaveManagerDestination::getCurrentBankIndex() const +{ + return saveManager_.getCurrentBankIndex(); +} + +uint32_t TransferPakSaveManagerDestination::getNumberOfBytesWritten() const +{ + return bytesWritten_; +} + +uint32_t TransferPakSaveManagerDestination::write(uint8_t* buffer, uint32_t bytesToWrite) +{ + saveManager_.write(buffer, bytesToWrite); + bytesWritten_ += bytesToWrite; + return bytesToWrite; +} + +void TransferPakSaveManagerDestination::close() +{ + // dummy +} + +TransferPakFileCopyDestination::TransferPakFileCopyDestination(const char* pathOnSDCard) + : outputFile_(nullptr) + , bytesWritten_(0) +{ + outputFile_ = fopen(pathOnSDCard, "w"); +} + +TransferPakFileCopyDestination::~TransferPakFileCopyDestination() +{ + close(); +} + +bool TransferPakFileCopyDestination::readyForTransfer() const +{ + return (outputFile_ != nullptr); +} + +uint16_t TransferPakFileCopyDestination::getCurrentBankIndex() const +{ + return 1; +} + +uint32_t TransferPakFileCopyDestination::getNumberOfBytesWritten() const +{ + return bytesWritten_; +} + +uint32_t TransferPakFileCopyDestination::write(uint8_t* buffer, uint32_t bytesToWrite) +{ + const uint32_t ret = static_cast(fwrite(buffer, sizeof(char), bytesToWrite, outputFile_)); + bytesWritten_ += ret; + return ret; +} + +void TransferPakFileCopyDestination::close() +{ + if(!outputFile_) + { + return; + } + + fclose(outputFile_); + outputFile_ = nullptr; +} + +FileValidationCopyDestination::FileValidationCopyDestination(const char *pathOnSDCard) + : inputFile_(nullptr) + , bytesValidated_(0) + , isValid_(true) +{ + inputFile_ = fopen(pathOnSDCard, "r"); +} + +FileValidationCopyDestination::~FileValidationCopyDestination() +{ + close(); +} + +bool FileValidationCopyDestination::readyForTransfer() const +{ + return (inputFile_ != nullptr); +} + +uint16_t FileValidationCopyDestination::getCurrentBankIndex() const +{ + return 1; +} + +uint32_t FileValidationCopyDestination::getNumberOfBytesWritten() const +{ + return bytesValidated_; +} + +uint32_t FileValidationCopyDestination::write(uint8_t *buffer, uint32_t bytesToWrite) +{ + if(!inputFile_) + { + return 0; + } + + uint8_t fileBuffer[256]; + uint32_t bytesRemaining = bytesToWrite; + uint32_t bytesToRead; + size_t ret; + + while(bytesRemaining > 0) + { + bytesToRead = (sizeof(fileBuffer) < bytesRemaining) ? sizeof(fileBuffer) : bytesRemaining; + + ret = fread(fileBuffer, sizeof(char), bytesToRead, inputFile_); + if(ret != bytesToRead) + { + // couldn't read enough bytes + isValid_ = false; + break; + } + + if(memcmp(buffer + (bytesToWrite - bytesRemaining), fileBuffer, bytesToRead) != 0) + { + // data mismatch + isValid_ = false; + } + + bytesRemaining -= bytesToRead; + bytesValidated_ += bytesToRead; + } + + return bytesToWrite - bytesRemaining; +} + +void FileValidationCopyDestination::close() +{ + if(inputFile_) + { + fclose(inputFile_); + inputFile_ = nullptr; + } +} + +bool FileValidationCopyDestination::isDataValid() const +{ + return isValid_; +} + +TransferPakDataCopier::TransferPakDataCopier(ITransferPakDataCopySource& source, ITransferPakDataCopyDestination& destination) + : source_(source) + , destination_(destination) +{ +} + +TransferPakDataCopier::~TransferPakDataCopier() +{ +} + +uint16_t TransferPakDataCopier::getCurrentBankIndex() const +{ + return source_.getCurrentBankIndex(); +} + +uint32_t TransferPakDataCopier::getNumberOfBytesRead() const +{ + return source_.getNumberOfBytesRead(); +} + +size_t TransferPakDataCopier::copyChunk(uint32_t numBytesToCopy) +{ + constexpr uint16_t bufferSize = 256; + uint8_t buffer[bufferSize]; + uint32_t bytesRemaining = numBytesToCopy; + uint32_t bytesToRead; + + while(bytesRemaining > 0) + { + bytesToRead = (bufferSize < bytesRemaining) ? bufferSize : bytesRemaining; + + if(!source_.read(buffer, bytesToRead)) + { + // no bytes read. Abort + break; + } + + // now write the bytes to the destination + bytesRemaining -= destination_.write(buffer, bytesToRead); + } + return numBytesToCopy - bytesRemaining; } \ No newline at end of file