mirror of
https://github.com/risingPhil/PokeMe64.git
synced 2026-09-07 08:15:40 -05:00
Add save file validation when backing up or restoring a save
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user