Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion 3ds/assets/romfs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"additional_extdata_folders": {

},
"dsiware_saves": true,
"nand_saves": false,
"scan_cart": false,
"version": 3
"version": 4
}
2 changes: 2 additions & 0 deletions 3ds/include/archive.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ typedef enum { MODE_SAVE, MODE_EXTDATA } Mode_t;

namespace Archive {
Result init(void);
Result initTWLN(void);
void exit(void);

Mode_t mode(void);
void mode(Mode_t v);
FS_Archive sdmc(void);
FS_Archive twln(void);

Result save(FS_Archive* archive, FS_MediaType mediatype, u32 lowid, u32 highid);
Result extdata(FS_Archive* archive, u32 extdata);
Expand Down
8 changes: 6 additions & 2 deletions 3ds/include/configuration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
#include <unordered_set>
#include <vector>

#define CONFIG_VERSION 3
#define CONFIG_VERSION 4

class Configuration {
public:
Expand All @@ -46,15 +46,19 @@ class Configuration {

bool filter(u64 id);
bool favorite(u64 id);
bool dsiwareSaves(void);
bool nandSaves(void);
bool shouldScanCard(void);
void disableDSiWareSaves();
std::vector<std::u16string> additionalSaveFolders(u64 id);
std::vector<std::u16string> additionalExtdataFolders(u64 id);

private:
Configuration(void);
~Configuration() = default;

void editJsonElement(const char* element, nlohmann::json value);

void store(void);
nlohmann::json loadJson(const std::string& path);
void storeJson(nlohmann::json& json, const std::string& path);
Expand All @@ -65,7 +69,7 @@ class Configuration {
nlohmann::json mJson;
std::unordered_set<u64> mFilterIds, mFavoriteIds;
std::unordered_map<u64, std::vector<std::u16string>> mAdditionalSaveFolders, mAdditionalExtdataFolders;
bool mNandSaves, mScanCard;
bool mDSiWareSaves, mNandSaves, mScanCard;
std::string BASEPATH = "/3ds/Checkpoint/config.json";
};

Expand Down
1 change: 1 addition & 0 deletions 3ds/include/io.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ namespace io {
void copyFile(FS_Archive srcArch, FS_Archive dstArch, const std::u16string& srcPath, const std::u16string& dstPath);
Result createDirectory(FS_Archive archive, const std::u16string& path);
void deleteBackupFolder(const std::u16string& path);
Result deleteFolderContentsRecursively(FS_Archive arch, const std::u16string& path);
Result deleteFolderRecursively(FS_Archive arch, const std::u16string& path);
bool directoryExists(FS_Archive archive, const std::u16string& path);
bool fileExists(FS_Archive archive, const std::u16string& path);
Expand Down
12 changes: 12 additions & 0 deletions 3ds/source/archive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "archive.hpp"

static FS_Archive mSdmc;
static FS_Archive mTwln;
static Mode_t mMode = MODE_SAVE;

Mode_t Archive::mode(void)
Expand All @@ -44,16 +45,27 @@ Result Archive::init(void)
return FSUSER_OpenArchive(&mSdmc, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""));
}

Result Archive::initTWLN(void)
{
return FSUSER_OpenArchive(&mTwln, ARCHIVE_NAND_TWL_FS, fsMakePath(PATH_EMPTY, ""));
}

void Archive::exit(void)
{
FSUSER_CloseArchive(mSdmc);
FSUSER_CloseArchive(mTwln);
}

FS_Archive Archive::sdmc(void)
{
return mSdmc;
}

FS_Archive Archive::twln(void)
{
return mTwln;
}

Result Archive::save(FS_Archive* archive, FS_MediaType mediatype, u32 lowid, u32 highid)
{
if (mediatype == MEDIATYPE_NAND) {
Expand Down
26 changes: 24 additions & 2 deletions 3ds/source/configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ Configuration::Configuration(void)
mJson["version"] = CONFIG_VERSION;
updateJson = true;
}
if (!(mJson.contains("dsiware_saves") && mJson["dsiware_saves"].is_boolean())) {
mJson["dsiware_saves"] = true;
updateJson = true;
}
if (!(mJson.contains("nand_saves") && mJson["nand_saves"].is_boolean())) {
mJson["nand_saves"] = false;
updateJson = true;
Expand Down Expand Up @@ -122,8 +126,9 @@ Configuration::Configuration(void)
mFavoriteIds.emplace(strtoull(id.c_str(), NULL, 16));
}

mNandSaves = mJson["nand_saves"];
mScanCard = mJson["scan_cart"];
mDSiWareSaves = mJson["dsiware_saves"];
mNandSaves = mJson["nand_saves"];
mScanCard = mJson["scan_cart"];

// parse additional save folders
auto js = mJson["additional_save_folders"];
Expand All @@ -148,6 +153,11 @@ Configuration::Configuration(void)
}
}

void Configuration::editJsonElement(const char* element, nlohmann::json value)
{
mJson[element] = value;
}

nlohmann::json Configuration::loadJson(const std::string& path)
{
nlohmann::json json;
Expand Down Expand Up @@ -188,6 +198,11 @@ bool Configuration::favorite(u64 id)
return mFavoriteIds.find(id) != mFavoriteIds.end();
}

bool Configuration::dsiwareSaves(void)
{
return mDSiWareSaves;
}

bool Configuration::nandSaves(void)
{
return mNandSaves;
Expand All @@ -211,3 +226,10 @@ bool Configuration::shouldScanCard(void)
{
return mScanCard;
}

void Configuration::disableDSiWareSaves(void)
{
mDSiWareSaves = false;
editJsonElement("dsiware_saves", false);
storeJson(mJson, BASEPATH);
}
82 changes: 81 additions & 1 deletion 3ds/source/io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ bool io::directoryExists(FS_Archive archive, const std::u16string& path)
return true;
}

Result io::deleteFolderRecursively(FS_Archive arch, const std::u16string& path)
Result io::deleteFolderContentsRecursively(FS_Archive arch, const std::u16string& path)
{
Directory dir(arch, path);
if (!dir.good()) {
Expand All @@ -161,6 +161,16 @@ Result io::deleteFolderRecursively(FS_Archive arch, const std::u16string& path)
}
}

return 0;
}

Result io::deleteFolderRecursively(FS_Archive arch, const std::u16string& path)
{
Result res = deleteFolderContentsRecursively(arch, path);
if (R_FAILED(res))
{
return res;
}
FSUSER_DeleteDirectory(arch, fsMakePath(PATH_UTF16, path.data()));
return 0;
}
Expand Down Expand Up @@ -242,6 +252,58 @@ std::tuple<bool, Result, std::string> io::backup(size_t index, size_t cellIndex)

FSUSER_CloseArchive(archive);
}
else if (title.mediaType() == MEDIATYPE_NAND) {
// DSiWare
std::string suggestion = DateTime::dateTimeStr();

std::u16string customPath;
if (MS::multipleSelectionEnabled()) {
customPath = isNewFolder ? StringUtils::UTF8toUTF16(suggestion.c_str()) : StringUtils::UTF8toUTF16("");
}
else {
customPath = isNewFolder ? KeyboardManager::get().keyboard(suggestion) : StringUtils::UTF8toUTF16("");
}

std::u16string dstPath;
if (!isNewFolder) {
// we're overriding an existing folder
dstPath = title.fullSavePath(cellIndex);
}
else {
dstPath = title.savePath();
dstPath += StringUtils::UTF8toUTF16("/") + customPath;
}

if (!isNewFolder || io::directoryExists(Archive::sdmc(), dstPath)) {
res = FSUSER_DeleteDirectoryRecursively(Archive::sdmc(), fsMakePath(PATH_UTF16, dstPath.data()));
if (R_FAILED(res)) {
Logger::getInstance().log(Logger::ERROR, "Failed to delete the existing backup directory recursively with result 0x%08lX.", res);
return std::make_tuple(false, res, "Failed to delete the existing backup\ndirectory recursively.");
}
}

res = io::createDirectory(Archive::sdmc(), dstPath);
if (R_FAILED(res)) {
Logger::getInstance().log(Logger::ERROR, "Failed to create destination directory.");
return std::make_tuple(false, res, "Failed to create destination directory.");
}

std::u16string copyPath = dstPath + StringUtils::UTF8toUTF16("/");

char* saveDir = new char[31];
sprintf(saveDir, "/title/%08lx/%08lx/data/", (title.highId() & 0x00000FFF) | 0x00030000, title.lowId());
res = io::copyDirectory(Archive::twln(), Archive::sdmc(), StringUtils::UTF8toUTF16(saveDir), copyPath);
delete[] saveDir;

if (R_FAILED(res)) {
std::string message = "Failed to backup save.";
FSUSER_DeleteDirectoryRecursively(Archive::sdmc(), fsMakePath(PATH_UTF16, dstPath.data()));
Logger::getInstance().log(Logger::ERROR, message + " Result 0x%08lX.", res);
return std::make_tuple(false, res, message);
}

refreshDirectories(title.id());
}
else {
CardType cardType = title.SPICardType();
u32 saveSize = SPIGetCapacity(cardType);
Expand Down Expand Up @@ -386,6 +448,24 @@ std::tuple<bool, Result, std::string> io::restore(size_t index, size_t cellIndex

FSUSER_CloseArchive(archive);
}
else if (title.mediaType() == MEDIATYPE_NAND) {
std::u16string srcPath = title.fullSavePath(cellIndex);
srcPath += StringUtils::UTF8toUTF16("/");

char* saveDir = new char[31];
sprintf(saveDir, "/title/%08lx/%08lx/data/", (title.highId() & 0x00000FFF) | 0x00030000, title.lowId());
std::u16string dstPath = StringUtils::UTF8toUTF16(saveDir);
delete[] saveDir;

deleteFolderContentsRecursively(Archive::twln(), dstPath);

res = io::copyDirectory(Archive::sdmc(), Archive::twln(), srcPath, dstPath);
if (R_FAILED(res)) {
std::string message = "Failed to restore save.";
Logger::getInstance().log(Logger::ERROR, message + ". Result 0x%08lX.", res);
return std::make_tuple(false, res, message);
}
}
else {
CardType cardType = title.SPICardType();
u32 saveSize = SPIGetCapacity(cardType);
Expand Down
Loading