Add save file validation when backing up or restoring a save

This commit is contained in:
Philippe Symons
2026-01-07 18:30:06 +01:00
parent d106863b50
commit 0ff264c13c
6 changed files with 1094 additions and 909 deletions

View File

@@ -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

View File

@@ -1,199 +1,222 @@
#ifndef _TRANSFERPAKDATACOPIER_H
#define _TRANSFERPAKDATACOPIER_H
#include <cstdio>
#include <cstdint>
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 <cstdio>
#include <cstdint>
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

View File

@@ -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:

View File

@@ -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 <system.h>
//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<Gen1GameType>(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<Gen2GameType>(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<uint32_t>(COPY_CHUNK_SIZE_IN_BYTES, totalBytesToCopy_ - copyDestination_->getNumberOfBytesWritten());
copier_->copyChunk(numBytesToCopy);
setProgress(static_cast<double>(copyDestination_->getNumberOfBytesWritten()) / static_cast<double>(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 <system.h>
#include <unistd.h>
#include <cstdio>
//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<Gen1GameType>(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<Gen2GameType>(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<uint32_t>(COPY_CHUNK_SIZE_IN_BYTES, totalBytesToCopy_ - copyDestination_->getNumberOfBytesWritten());
copier_->copyChunk(numBytesToCopy);
setProgress(static_cast<double>(copyDestination_->getNumberOfBytesWritten()) / static_cast<double>(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<FileValidationCopyDestination*>(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;
}

View File

@@ -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);

View File

@@ -1,295 +1,357 @@
#include "transferpak/TransferPakDataCopier.h"
#include "transferpak/TransferPakRomReader.h"
#include "transferpak/TransferPakSaveManager.h"
#include <cstring>
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<uint32_t>(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<uint32_t>(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 <cstring>
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<uint32_t>(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<uint32_t>(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;
}