diff --git a/3ds/include/archive.hpp b/3ds/include/archive.hpp index 89bff2a1..dca6be1e 100644 --- a/3ds/include/archive.hpp +++ b/3ds/include/archive.hpp @@ -43,8 +43,10 @@ namespace Archive { FS_Archive sdmc(void); Result save(FS_Archive* archive, FS_MediaType mediatype, u32 lowid, u32 highid); + Result rawSave(FSPXI_Archive* archive, FS_MediaType mediatype, u32 lowid, u32 highid); Result extdata(FS_Archive* archive, u32 extdata); bool accessible(FS_MediaType mediatype, u32 lowid, u32 highid); // save + bool accessibleRaw(FS_MediaType mediatype, u32 lowid, u32 highid); // raw save bool accessible(u32 extdata); // extdata bool setPlayCoins(void); } diff --git a/3ds/include/csvc.hpp b/3ds/include/csvc.hpp new file mode 100644 index 00000000..6ff68718 --- /dev/null +++ b/3ds/include/csvc.hpp @@ -0,0 +1,43 @@ +/* This paricular file is licensed under the following terms: */ + +/* +* This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable +* for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it +* and redistribute it freely, subject to the following restrictions: +* +* The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +* If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +* +* Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +* This notice may not be removed or altered from any source distribution. +*/ + +/* This file was modified from https://github.com/AuroraWright/Luma3DS to only have svcControlService */ + +#pragma once + +#include <3ds/types.h> + +/// Operations for svcControlService +enum ServiceOp +{ + SERVICEOP_STEAL_CLIENT_SESSION = 0, ///< Steal a client session given a service or global port name + SERVICEOP_GET_NAME, ///< Get the name of a service or global port given a client or session handle +}; + +inline Handle FsPxiHandle; + +extern "C" +{ + /** + * @brief Performs actions related to services or global handles. + * @param op The operation to perform, see @ref ServiceOp. + * + * Examples: + * svcControlService(SERVICEOP_GET_NAME, (char [12])outName, (Handle)clientOrSessionHandle); + * svcControlService(SERVICEOP_STEAL_CLIENT_SESSION, (Handle *)&outHandle, (const char *)name); + */ + Result svcControlService(ServiceOp op, ...); +} diff --git a/3ds/include/fsstream.hpp b/3ds/include/fsstream.hpp index 2a3f31da..94a91da1 100644 --- a/3ds/include/fsstream.hpp +++ b/3ds/include/fsstream.hpp @@ -29,12 +29,17 @@ #include <3ds.h> #include +#include + +using MultiHandle = std::variant; class FSStream { public: FSStream(FS_Archive archive, const std::u16string& path, u32 flags); FSStream(FS_Archive archive, const std::u16string& path, u32 flags, u32 size); - ~FSStream(void){}; + FSStream(FSPXI_Archive archive, u32 flags); + FSStream(FSPXI_Archive archive, u32 flags, u32 size); + ~FSStream(){}; Result close(void); bool eof(void); @@ -47,7 +52,7 @@ class FSStream { u32 write(const void* buf, u32 size); private: - Handle mHandle; + MultiHandle mHandle; u32 mSize; u32 mOffset; Result mResult; diff --git a/3ds/include/io.hpp b/3ds/include/io.hpp index 13c0c6f3..7a85ee99 100644 --- a/3ds/include/io.hpp +++ b/3ds/include/io.hpp @@ -45,6 +45,7 @@ namespace io { Result copyDirectory(FS_Archive srcArch, FS_Archive dstArch, const std::u16string& srcPath, const std::u16string& dstPath); void copyFile(FS_Archive srcArch, FS_Archive dstArch, const std::u16string& srcPath, const std::u16string& dstPath); + Result copyPxiSaveFile(FSPXI_Archive pxiArch, FS_Archive regularArch, const std::u16string& path, bool fromPxi); Result createDirectory(FS_Archive archive, const std::u16string& path); void deleteBackupFolder(const std::u16string& path); Result deleteFolderRecursively(FS_Archive arch, const std::u16string& path); diff --git a/3ds/include/title.hpp b/3ds/include/title.hpp index fefc1433..72e30e83 100644 --- a/3ds/include/title.hpp +++ b/3ds/include/title.hpp @@ -52,6 +52,7 @@ class Title { ~Title(void); bool accessibleSave(void); + bool isGBAVC(void); bool accessibleExtdata(void); FS_CardType cardType(void); std::vector extdata(void); @@ -64,8 +65,8 @@ class Title { bool isActivityLog(void); void load(void); bool load(u64 id, FS_MediaType mediaType, FS_CardType cardType); - void load(u64 id, u8* productCode, bool accessibleSave, bool accessibleExtdata, std::u16string shortDescription, std::u16string longDescription, - std::u16string savePath, std::u16string extdataPath, FS_MediaType media, FS_CardType cardType, CardType card); + void load(u64 id, u8* productCode, bool accessibleSave, bool saveIsGBA, bool accessibleExtdata, std::u16string shortDescription, + std::u16string longDescription, std::u16string savePath, std::u16string extdataPath, FS_MediaType media, FS_CardType cardType, CardType card); std::string longDescription(void); std::u16string getLongDescription(void); u32 lowId(void); @@ -96,6 +97,7 @@ class Title { std::vector mExtdata; std::vector mFullExtdataPaths; u64 mId; + bool mGBA; FS_MediaType mMedia; FS_CardType mCard; CardType mCardType; diff --git a/3ds/source/archive.cpp b/3ds/source/archive.cpp index 68cb5888..94926f69 100644 --- a/3ds/source/archive.cpp +++ b/3ds/source/archive.cpp @@ -25,6 +25,8 @@ */ #include "archive.hpp" +#include "fsstream.hpp" +#include "csvc.hpp" static FS_Archive mSdmc; static Mode_t mMode = MODE_SAVE; @@ -41,7 +43,9 @@ void Archive::mode(Mode_t v) Result Archive::init(void) { - return FSUSER_OpenArchive(&mSdmc, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")); + Result res = FSUSER_OpenArchive(&mSdmc, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")); + if(R_FAILED(res)) return res; + return svcControlService(SERVICEOP_STEAL_CLIENT_SESSION, &FsPxiHandle, "PxiFS0"); } void Archive::exit(void) @@ -67,6 +71,12 @@ Result Archive::save(FS_Archive* archive, FS_MediaType mediatype, u32 lowid, u32 return 0; } +Result Archive::rawSave(FSPXI_Archive* archive, FS_MediaType mediatype, u32 lowid, u32 highid) +{ + const u32 path[4] = {lowid, highid, mediatype, 1}; + return FSPXI_OpenArchive(FsPxiHandle, archive, ARCHIVE_SAVEDATA_AND_CONTENT, {PATH_BINARY, 16, path}); +} + Result Archive::extdata(FS_Archive* archive, u32 ext) { const u32 path[3] = {MEDIATYPE_SD, ext, 0}; @@ -84,6 +94,22 @@ bool Archive::accessible(FS_MediaType mediatype, u32 lowid, u32 highid) return false; } +bool Archive::accessibleRaw(FS_MediaType mediatype, u32 lowid, u32 highid) +{ + FSPXI_Archive archive; + Result res = rawSave(&archive, mediatype, lowid, highid); + if (R_SUCCEEDED(res)) { + FSStream file(archive, FS_OPEN_READ); + if(file.good()) + { + file.close(); + FSPXI_CloseArchive(FsPxiHandle, archive); + return true; + } + } + return false; +} + bool Archive::accessible(u32 ext) { FS_Archive archive; @@ -118,4 +144,4 @@ bool Archive::setPlayCoins(void) FSUSER_CloseArchive(archive); } return false; -} \ No newline at end of file +} diff --git a/3ds/source/csvc.s b/3ds/source/csvc.s new file mode 100644 index 00000000..f9ee99ae --- /dev/null +++ b/3ds/source/csvc.s @@ -0,0 +1,36 @@ +@ This paricular file is licensed under the following terms: + +@ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable +@ for any damages arising from the use of this software. +@ +@ Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it +@ and redistribute it freely, subject to the following restrictions: +@ +@ The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +@ If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +@ +@ Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +@ This notice may not be removed or altered from any source distribution. + +@ This file was modified from https://github.com/AuroraWright/Luma3DS to only have svcControlService + +.arm +.balign 4 + +.macro SVC_BEGIN name + .section .text.\name, "ax", %progbits + .global \name + .type \name, %function + .align 2 + .cfi_startproc +\name: +.endm + +.macro SVC_END + .cfi_endproc +.endm + +SVC_BEGIN svcControlService + svc 0xB0 + bx lr +SVC_END diff --git a/3ds/source/fsstream.cpp b/3ds/source/fsstream.cpp index 11c4cbdf..25092cae 100644 --- a/3ds/source/fsstream.cpp +++ b/3ds/source/fsstream.cpp @@ -25,16 +25,19 @@ */ #include "fsstream.hpp" +#include "csvc.hpp" FSStream::FSStream(FS_Archive archive, const std::u16string& path, u32 flags) { mGood = false; mSize = 0; mOffset = 0; + Handle hnd; - mResult = FSUSER_OpenFile(&mHandle, archive, fsMakePath(PATH_UTF16, path.data()), flags, 0); + mResult = FSUSER_OpenFile(&hnd, archive, fsMakePath(PATH_UTF16, path.data()), flags, 0); if (R_SUCCEEDED(mResult)) { - FSFILE_GetSize(mHandle, (u64*)&mSize); + FSFILE_GetSize(hnd, (u64*)&mSize); + mHandle = hnd; mGood = true; } } @@ -44,12 +47,13 @@ FSStream::FSStream(FS_Archive archive, const std::u16string& path, u32 flags, u3 mGood = false; mSize = size; mOffset = 0; + Handle hnd; - mResult = FSUSER_OpenFile(&mHandle, archive, fsMakePath(PATH_UTF16, path.data()), flags, 0); + mResult = FSUSER_OpenFile(&hnd, archive, fsMakePath(PATH_UTF16, path.data()), flags, 0); if (R_FAILED(mResult)) { mResult = FSUSER_CreateFile(archive, fsMakePath(PATH_UTF16, path.data()), 0, mSize); if (R_SUCCEEDED(mResult)) { - mResult = FSUSER_OpenFile(&mHandle, archive, fsMakePath(PATH_UTF16, path.data()), flags, 0); + mResult = FSUSER_OpenFile(&hnd, archive, fsMakePath(PATH_UTF16, path.data()), flags, 0); if (R_SUCCEEDED(mResult)) { mGood = true; } @@ -58,11 +62,58 @@ FSStream::FSStream(FS_Archive archive, const std::u16string& path, u32 flags, u3 else { mGood = true; } + + mHandle = hnd; +} + +static const u32 pxi_path[5] = { + 1, + 1, + 3, + 0, + 0 +}; +FSStream::FSStream(FSPXI_Archive archive, u32 flags) +{ + mGood = false; + mSize = 0; + mOffset = 0; + FSPXI_File hnd; + + mResult = FSPXI_OpenFile(FsPxiHandle, &hnd, archive, {PATH_BINARY, 20, pxi_path}, flags, 0); + if(R_SUCCEEDED(mResult)) + { + FSPXI_GetFileSize(FsPxiHandle, hnd, (u64*)&mSize); + mHandle = hnd; + mGood = true; + } +} + +FSStream::FSStream(FSPXI_Archive archive, u32 flags, u32 size) +{ + mGood = false; + mSize = size; + mOffset = 0; + FSPXI_File hnd; + + mResult = FSPXI_OpenFile(FsPxiHandle, &hnd, archive, {PATH_BINARY, 20, pxi_path}, flags, 0); + if(R_SUCCEEDED(mResult)) + { + mHandle = hnd; + mGood = true; + } } Result FSStream::close(void) { - mResult = FSFILE_Close(mHandle); + if(mHandle.index() == 0) + { + mResult = FSPXI_CloseFile(FsPxiHandle, std::get<0>(mHandle)); + } + else + { + mResult = FSFILE_Close(std::get<1>(mHandle)); + } return mResult; } @@ -81,10 +132,26 @@ u32 FSStream::size(void) return mSize; } +using ReadFunction = Result(*)(MultiHandle, u32*, u64, void*, u32); u32 FSStream::read(void* buf, u32 sz) { u32 rd = 0; - mResult = FSFILE_Read(mHandle, &rd, mOffset, buf, sz); + ReadFunction calling = nullptr; + + if(mHandle.index() == 0) + { + calling = [](MultiHandle hnd, u32* readAmount, u64 off, void* b, u32 s) -> Result { + return FSPXI_ReadFile(FsPxiHandle, std::get<0>(hnd), readAmount, off, b, s); + }; + } + else + { + calling = [](MultiHandle hnd, u32* readAmount, u64 off, void* b, u32 s) -> Result { + return FSFILE_Read(std::get<1>(hnd), readAmount, off, b, s); + }; + } + + mResult = calling(mHandle, &rd, mOffset, buf, sz); if (R_FAILED(mResult)) { if (rd > sz) { rd = sz; @@ -94,10 +161,26 @@ u32 FSStream::read(void* buf, u32 sz) return rd; } +using WriteFunction = Result(*)(MultiHandle, u32*, u64, const void*, u32); u32 FSStream::write(const void* buf, u32 sz) { u32 wt = 0; - mResult = FSFILE_Write(mHandle, &wt, mOffset, buf, sz, FS_WRITE_FLUSH); + WriteFunction calling = nullptr; + + if(mHandle.index() == 0) + { + calling = [](MultiHandle hnd, u32* readAmount, u64 off, const void* b, u32 s) -> Result { + return FSPXI_WriteFile(FsPxiHandle, std::get<0>(hnd), readAmount, off, b, s, FS_WRITE_FLUSH); + }; + } + else + { + calling = [](MultiHandle hnd, u32* readAmount, u64 off, const void* b, u32 s) -> Result { + return FSFILE_Write(std::get<1>(hnd), readAmount, off, b, s, FS_WRITE_FLUSH); + }; + } + + mResult = calling(mHandle, &wt, mOffset, buf, sz); mOffset += wt; return wt; } diff --git a/3ds/source/io.cpp b/3ds/source/io.cpp index 8def60b7..4ebb55f6 100644 --- a/3ds/source/io.cpp +++ b/3ds/source/io.cpp @@ -25,6 +25,17 @@ */ #include "io.hpp" +#include "csvc.hpp" +#include + +struct FSPXI_Archive_s { + FSPXI_Archive archive; +}; +struct FS_Archive_s { + FS_Archive archive; +}; +using MultiArchive = std::variant; + bool io::fileExists(const std::string& path) { @@ -88,6 +99,54 @@ void io::copyFile(FS_Archive srcArch, FS_Archive dstArch, const std::u16string& g_isTransferringFile = false; } +Result io::copyPxiSaveFile(FSPXI_Archive pxiArch, FS_Archive regularArch, const std::u16string& path, bool fromPxi) +{ + g_isTransferringFile = true; + + u32 size = 0; + FSStream input = fromPxi ? FSStream(pxiArch, FS_OPEN_READ) : FSStream(regularArch, path, FS_OPEN_READ); + if (input.good()) { + size = input.size() > BUFFER_SIZE ? BUFFER_SIZE : input.size(); + } + else { + Logger::getInstance().log(Logger::ERROR, + "Failed to open source file " + (fromPxi ? std::string("GBA save") : StringUtils::UTF16toUTF8(path)) + " during copy with result 0x%08lX. Skipping...", input.result()); + return input.result(); + } + + FSStream output= fromPxi ? FSStream(regularArch, path, FS_OPEN_WRITE, input.size()) : FSStream(pxiArch, FS_OPEN_WRITE, input.size()); + if (output.good()) { + size_t slashpos = path.rfind(StringUtils::UTF8toUTF16("/")); + g_currentFile = path.substr(slashpos + 1, path.length() - slashpos - 1); + + u32 rd; + auto buf = std::make_unique(size); + do { + rd = input.read(buf.get(), size); + output.write(buf.get(), rd); + + // avoid freezing the UI + // this will be made less horrible next time... + C3D_FrameBegin(C3D_FRAME_SYNCDRAW); + g_screen->drawTop(); + C2D_SceneBegin(g_bottom); + g_screen->drawBottom(); + Gui::frameEnd(); + } while (!input.eof()); + } + else { + Logger::getInstance().log(Logger::ERROR, + "Failed to open destination file " + (fromPxi ? StringUtils::UTF16toUTF8(path) : std::string("GBA save")) + " during copy with result 0x%08lX. Skipping...", + output.result()); + } + + input.close(); + output.close(); + + g_isTransferringFile = false; + return output.result(); +} + Result io::copyDirectory(FS_Archive srcArch, FS_Archive dstArch, const std::u16string& srcPath, const std::u16string& dstPath) { Result res = 0; @@ -177,12 +236,23 @@ std::tuple io::backup(size_t index, size_t cellIndex) Logger::getInstance().log(Logger::INFO, "Started backup of %s. Title id: 0x%08lX.", title.shortDescription().c_str(), title.lowId()); if (title.cardType() == CARD_CTR) { - FS_Archive archive; + MultiArchive varchive; if (mode == MODE_SAVE) { - res = Archive::save(&archive, title.mediaType(), title.lowId(), title.highId()); + if(title.isGBAVC()) { + FSPXI_Archive_s archive; + res = Archive::save(&archive.archive, title.mediaType(), title.lowId(), title.highId()); + varchive = archive; + } + else { + FS_Archive_s archive; + res = Archive::save(&archive.archive, title.mediaType(), title.lowId(), title.highId()); + varchive = archive; + } } else if (mode == MODE_EXTDATA) { - res = Archive::extdata(&archive, title.extdataId()); + FS_Archive_s archive; + res = Archive::extdata(&archive.archive, title.extdataId()); + varchive = archive; } if (R_SUCCEEDED(res)) { @@ -209,7 +279,12 @@ std::tuple io::backup(size_t index, size_t cellIndex) if (!isNewFolder || io::directoryExists(Archive::sdmc(), dstPath)) { res = FSUSER_DeleteDirectoryRecursively(Archive::sdmc(), fsMakePath(PATH_UTF16, dstPath.data())); if (R_FAILED(res)) { - FSUSER_CloseArchive(archive); + if(varchive.index() == 0) { + FSPXI_CloseArchive(FsPxiHandle, std::get<0>(varchive).archive); + } + else { + FSUSER_CloseArchive(std::get<1>(varchive).archive); + } 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."); } @@ -217,30 +292,49 @@ std::tuple io::backup(size_t index, size_t cellIndex) res = io::createDirectory(Archive::sdmc(), dstPath); if (R_FAILED(res)) { - FSUSER_CloseArchive(archive); + if(varchive.index() == 0) { + FSPXI_CloseArchive(FsPxiHandle, std::get<0>(varchive).archive); + } + else { + FSUSER_CloseArchive(std::get<1>(varchive).archive); + } 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("/"); - - res = io::copyDirectory(archive, Archive::sdmc(), StringUtils::UTF8toUTF16("/"), copyPath); - if (R_FAILED(res)) { - std::string message = mode == MODE_SAVE ? "Failed to backup save." : "Failed to backup extdata."; + if(title.isGBAVC()) + { + FSPXI_Archive archive = std::get<0>(varchive).archive;; + dstPath += StringUtils::UTF8toUTF16("/00000001.sav"); + res = io::copyPxiSaveFile(archive, Archive::sdmc(), dstPath, true); + if (R_FAILED(res)) { + std::string message = "Failed to backup GBA save."; + FSPXI_CloseArchive(FsPxiHandle, archive); + Logger::getInstance().log(Logger::ERROR, message + ". Result 0x%08lX.", res); + return std::make_tuple(false, res, message); + } + FSPXI_CloseArchive(FsPxiHandle, archive); + } + else + { + FS_Archive archive = std::get<1>(varchive).archive;; + std::u16string copyPath = dstPath + StringUtils::UTF8toUTF16("/"); + res = io::copyDirectory(archive, Archive::sdmc(), StringUtils::UTF8toUTF16("/"), copyPath); + if (R_FAILED(res)) { + std::string message = mode == MODE_SAVE ? "Failed to backup save." : "Failed to backup extdata."; + FSUSER_CloseArchive(archive); + 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); + } FSUSER_CloseArchive(archive); - 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 { Logger::getInstance().log(Logger::ERROR, "Failed to open save archive with result 0x%08lX.", res); return std::make_tuple(false, res, "Failed to open save archive."); } - - FSUSER_CloseArchive(archive); } else { CardType cardType = title.SPICardType(); @@ -333,58 +427,87 @@ std::tuple io::restore(size_t index, size_t cellIndex Logger::getInstance().log(Logger::INFO, "Started restore of %s. Title id: 0x%08lX.", title.shortDescription().c_str(), title.lowId()); if (title.cardType() == CARD_CTR) { - FS_Archive archive; + MultiArchive varchive; if (mode == MODE_SAVE) { - res = Archive::save(&archive, title.mediaType(), title.lowId(), title.highId()); + if(title.isGBAVC()) { + FSPXI_Archive_s archive; + res = Archive::save(&archive.archive, title.mediaType(), title.lowId(), title.highId()); + varchive = archive; + } + else { + FS_Archive_s archive; + res = Archive::save(&archive.archive, title.mediaType(), title.lowId(), title.highId()); + varchive = archive; + } } else if (mode == MODE_EXTDATA) { - res = Archive::extdata(&archive, title.extdataId()); + FS_Archive_s archive; + res = Archive::extdata(&archive.archive, title.extdataId()); + varchive = archive; } if (R_SUCCEEDED(res)) { std::u16string srcPath = mode == MODE_SAVE ? title.fullSavePath(cellIndex) : title.fullExtdataPath(cellIndex); srcPath += StringUtils::UTF8toUTF16("/"); - std::u16string dstPath = StringUtils::UTF8toUTF16("/"); - if (mode != MODE_EXTDATA) { - FSUSER_DeleteDirectoryRecursively(archive, fsMakePath(PATH_UTF16, dstPath.data())); - } - else { - deleteFolderRecursively(archive, dstPath); - } + if(title.isGBAVC()) { + FSPXI_Archive archive = std::get<0>(varchive).archive; - res = io::copyDirectory(Archive::sdmc(), archive, srcPath, dstPath); - if (R_FAILED(res)) { - std::string message = mode == MODE_SAVE ? "Failed to restore save." : "Failed to restore extdata."; - FSUSER_CloseArchive(archive); - Logger::getInstance().log(Logger::ERROR, message + ". Result 0x%08lX.", res); - return std::make_tuple(false, res, message); + srcPath += StringUtils::UTF8toUTF16("00000001.sav"); + res = io::copyPxiSaveFile(archive, Archive::sdmc(), srcPath, false); + if (R_FAILED(res)) { + std::string message = "Failed to restore GBA save."; + FSPXI_CloseArchive(FsPxiHandle, archive); + Logger::getInstance().log(Logger::ERROR, message + ". Result 0x%08lX.", res); + return std::make_tuple(false, res, message); + } + FSPXI_CloseArchive(FsPxiHandle, archive); } + else + { + std::u16string dstPath = StringUtils::UTF8toUTF16("/"); - if (mode == MODE_SAVE) { - res = FSUSER_ControlArchive(archive, ARCHIVE_ACTION_COMMIT_SAVE_DATA, NULL, 0, NULL, 0); - if (R_FAILED(res)) { - FSUSER_CloseArchive(archive); - Logger::getInstance().log(Logger::ERROR, "Failed to commit save data with result 0x%08lX.", res); - return std::make_tuple(false, res, "Failed to commit save data."); + FS_Archive archive = std::get<1>(varchive).archive;; + if (mode != MODE_EXTDATA) { + FSUSER_DeleteDirectoryRecursively(archive, fsMakePath(PATH_UTF16, dstPath.data())); + } + else { + deleteFolderRecursively(archive, dstPath); } - u8 out; - u64 secureValue = ((u64)SECUREVALUE_SLOT_SD << 32) | (title.uniqueId() << 8); - res = FSUSER_ControlSecureSave(SECURESAVE_ACTION_DELETE, &secureValue, 8, &out, 1); + res = io::copyDirectory(Archive::sdmc(), archive, srcPath, dstPath); if (R_FAILED(res)) { + std::string message = mode == MODE_SAVE ? "Failed to restore save." : "Failed to restore extdata."; FSUSER_CloseArchive(archive); - Logger::getInstance().log(Logger::ERROR, "Failed to fix secure value with result 0x%08lX.", res); - return std::make_tuple(false, res, "Failed to fix secure value."); + Logger::getInstance().log(Logger::ERROR, message + ". Result 0x%08lX.", res); + return std::make_tuple(false, res, message); } + + if (mode == MODE_SAVE) { + res = FSUSER_ControlArchive(archive, ARCHIVE_ACTION_COMMIT_SAVE_DATA, NULL, 0, NULL, 0); + if (R_FAILED(res)) { + FSUSER_CloseArchive(archive); + Logger::getInstance().log(Logger::ERROR, "Failed to commit save data with result 0x%08lX.", res); + return std::make_tuple(false, res, "Failed to commit save data."); + } + + u8 out; + u64 secureValue = ((u64)SECUREVALUE_SLOT_SD << 32) | (title.uniqueId() << 8); + res = FSUSER_ControlSecureSave(SECURESAVE_ACTION_DELETE, &secureValue, 8, &out, 1); + if (R_FAILED(res)) { + FSUSER_CloseArchive(archive); + Logger::getInstance().log(Logger::ERROR, "Failed to fix secure value with result 0x%08lX.", res); + return std::make_tuple(false, res, "Failed to fix secure value."); + } + } + + FSUSER_CloseArchive(archive); } } else { Logger::getInstance().log(Logger::ERROR, "Failed to open save archive with result 0x%08lX.", res); return std::make_tuple(false, res, "Failed to open save archive."); } - - FSUSER_CloseArchive(archive); } else { CardType cardType = title.SPICardType(); diff --git a/3ds/source/title.cpp b/3ds/source/title.cpp index 05864ba8..1748451d 100644 --- a/3ds/source/title.cpp +++ b/3ds/source/title.cpp @@ -90,15 +90,17 @@ void Title::load(void) mExtdataPath = StringUtils::UTF8toUTF16(""); mAccessibleSave = false; mAccessibleExtdata = false; + mGBA = false; mSaves.clear(); mExtdata.clear(); } -void Title::load(u64 id, u8* _productCode, bool accessibleSave, bool accessibleExtdata, std::u16string shortDescription, +void Title::load(u64 id, u8* _productCode, bool accessibleSave, bool saveIsGBA, bool accessibleExtdata, std::u16string shortDescription, std::u16string longDescription, std::u16string savePath, std::u16string extdataPath, FS_MediaType media, FS_CardType cardType, CardType card) { mId = id; mAccessibleSave = accessibleSave; + mGBA = saveIsGBA; mAccessibleExtdata = accessibleExtdata; mShortDescription = shortDescription; mLongDescription = longDescription; @@ -141,9 +143,10 @@ bool Title::load(u64 _id, FS_MediaType _media, FS_CardType _card) AM_GetTitleProductCode(mMedia, mId, productCode); mAccessibleSave = Archive::accessible(mediaType(), lowId(), highId()); + mGBA = (!mAccessibleSave) && Archive::accessibleRaw(mediaType(), lowId(), highId()); mAccessibleExtdata = mMedia == MEDIATYPE_NAND ? false : Archive::accessible(extdataId()); - if (mAccessibleSave) { + if (mAccessibleSave || mGBA) { loadTitle = true; if (!io::directoryExists(Archive::sdmc(), mSavePath)) { Result res = io::createDirectory(Archive::sdmc(), mSavePath); @@ -210,6 +213,7 @@ bool Title::load(u64 _id, FS_MediaType _media, FS_CardType _card) mAccessibleSave = true; mAccessibleExtdata = false; + mGBA = false; loadTitle = true; if (!io::directoryExists(Archive::sdmc(), mSavePath)) { @@ -232,6 +236,11 @@ bool Title::accessibleSave(void) return mAccessibleSave; } +bool Title::isGBAVC(void) +{ + return mGBA; +} + bool Title::accessibleExtdata(void) { return mAccessibleExtdata; @@ -310,7 +319,7 @@ void Title::refreshDirectories(void) mFullSavePaths.clear(); mFullExtdataPaths.clear(); - if (accessibleSave()) { + if (accessibleSave() || isGBAVC()) { // standard save backups Directory savelist(Archive::sdmc(), mSavePath); if (savelist.good()) { @@ -549,8 +558,8 @@ void loadTitles(bool forceRefresh) if (Configuration::getInstance().nandSaves()) { AM_GetTitleCount(MEDIATYPE_NAND, &count); - u64 ids_nand[count]; - AM_GetTitleList(NULL, MEDIATYPE_NAND, count, ids_nand); + auto ids_nand = std::make_unique(count); + AM_GetTitleList(NULL, MEDIATYPE_NAND, count, ids_nand.get()); for (u32 i = 0; i < count; i++) { if (validId(ids_nand[i])) { @@ -567,8 +576,8 @@ void loadTitles(bool forceRefresh) count = 0; AM_GetTitleCount(MEDIATYPE_SD, &count); - u64 ids[count]; - AM_GetTitleList(NULL, MEDIATYPE_SD, count, ids); + auto ids = std::make_unique(count); + AM_GetTitleList(NULL, MEDIATYPE_SD, count, ids.get()); for (u32 i = 0; i < count; i++) { if (validId(ids[i])) { @@ -577,6 +586,9 @@ void loadTitles(bool forceRefresh) if (title.accessibleSave()) { titleSaves.push_back(title); } + else if(title.isGBAVC()) { + titleSaves.push_back(title); + } if (title.accessibleExtdata()) { titleExtdatas.push_back(title); @@ -755,7 +767,7 @@ static void exportTitleListCache(std::vector& list, const std::u16string& u8* cache = new u8[list.size() * ENTRYSIZE](); for (size_t i = 0; i < list.size(); i++) { u64 id = list.at(i).id(); - bool accessibleSave = list.at(i).accessibleSave(); + u8 accessibleSaveRaw = list.at(i).accessibleSave() ? 1 : (list.at(i).isGBAVC() ? 2 : 0); bool accessibleExtdata = list.at(i).accessibleExtdata(); std::string shortDescription = StringUtils::UTF16toUTF8(list.at(i).getShortDescription()); std::string longDescription = StringUtils::UTF16toUTF8(list.at(i).getLongDescription()); @@ -775,7 +787,7 @@ static void exportTitleListCache(std::vector<Title>& list, const std::u16string& memcpy(cache + i * ENTRYSIZE + 0, &id, sizeof(u64)); memcpy(cache + i * ENTRYSIZE + 8, list.at(i).productCode, 16); - memcpy(cache + i * ENTRYSIZE + 24, &accessibleSave, sizeof(u8)); + memcpy(cache + i * ENTRYSIZE + 24, &accessibleSaveRaw, sizeof(u8)); memcpy(cache + i * ENTRYSIZE + 25, &accessibleExtdata, sizeof(u8)); memcpy(cache + i * ENTRYSIZE + 26, shortDescription.c_str(), shortDescription.length()); memcpy(cache + i * ENTRYSIZE + 90, longDescription.c_str(), longDescription.length()); @@ -825,7 +837,9 @@ static void importTitleListCache(void) for (size_t i = 0; i < sizesaves; i++) { u64 id; u8 productCode[16]; + u8 accessibleSaveRaw; bool accessibleSave; + bool saveIsGBA; bool accessibleExtdata; char shortDescription[0x40]; char longDescription[0x80]; @@ -837,7 +851,7 @@ static void importTitleListCache(void) memcpy(&id, cachesaves + i * ENTRYSIZE, sizeof(u64)); memcpy(productCode, cachesaves + i * ENTRYSIZE + 8, 16); - memcpy(&accessibleSave, cachesaves + i * ENTRYSIZE + 24, sizeof(u8)); + memcpy(&accessibleSaveRaw, cachesaves + i * ENTRYSIZE + 24, sizeof(u8)); memcpy(&accessibleExtdata, cachesaves + i * ENTRYSIZE + 25, sizeof(u8)); memcpy(shortDescription, cachesaves + i * ENTRYSIZE + 26, 0x40); memcpy(longDescription, cachesaves + i * ENTRYSIZE + 90, 0x80); @@ -847,8 +861,11 @@ static void importTitleListCache(void) memcpy(&cardType, cachesaves + i * ENTRYSIZE + 731, sizeof(u8)); memcpy(&card, cachesaves + i * ENTRYSIZE + 732, sizeof(u8)); + accessibleSave = accessibleSaveRaw & 1; + saveIsGBA = accessibleSaveRaw & 2; + Title title; - title.load(id, productCode, accessibleSave, accessibleExtdata, StringUtils::UTF8toUTF16(shortDescription), + title.load(id, productCode, accessibleSave, saveIsGBA, accessibleExtdata, StringUtils::UTF8toUTF16(shortDescription), StringUtils::UTF8toUTF16(longDescription), StringUtils::UTF8toUTF16(savePath), StringUtils::UTF8toUTF16(extdataPath), media, cardType, card); @@ -873,7 +890,9 @@ static void importTitleListCache(void) std::vector<u64>::iterator it = find(alreadystored.begin(), alreadystored.end(), id); if (it == alreadystored.end()) { u8 productCode[16]; + u8 accessibleSaveRaw; bool accessibleSave; + bool saveIsGBA; bool accessibleExtdata; char shortDescription[0x40]; char longDescription[0x80]; @@ -884,7 +903,7 @@ static void importTitleListCache(void) CardType card; memcpy(productCode, cacheextdatas + i * ENTRYSIZE + 8, 16); - memcpy(&accessibleSave, cacheextdatas + i * ENTRYSIZE + 24, sizeof(u8)); + memcpy(&accessibleSaveRaw, cacheextdatas + i * ENTRYSIZE + 24, sizeof(u8)); memcpy(&accessibleExtdata, cacheextdatas + i * ENTRYSIZE + 25, sizeof(u8)); memcpy(shortDescription, cacheextdatas + i * ENTRYSIZE + 26, 0x40); memcpy(longDescription, cacheextdatas + i * ENTRYSIZE + 90, 0x80); @@ -894,8 +913,12 @@ static void importTitleListCache(void) memcpy(&cardType, cacheextdatas + i * ENTRYSIZE + 731, sizeof(u8)); memcpy(&card, cacheextdatas + i * ENTRYSIZE + 732, sizeof(u8)); + + accessibleSave = accessibleSaveRaw & 1; + saveIsGBA = accessibleSaveRaw & 2; + Title title; - title.load(id, productCode, accessibleSave, accessibleExtdata, StringUtils::UTF8toUTF16(shortDescription), + title.load(id, productCode, accessibleSave, saveIsGBA, accessibleExtdata, StringUtils::UTF8toUTF16(shortDescription), StringUtils::UTF8toUTF16(longDescription), StringUtils::UTF8toUTF16(savePath), StringUtils::UTF8toUTF16(extdataPath), media, cardType, card);