From 87d035341d167205f6a0252cdbf3f934c60f51ba Mon Sep 17 00:00:00 2001 From: LiquidFenrir Date: Sun, 26 Apr 2020 23:32:59 +0200 Subject: [PATCH 1/6] Add GBA VC save dumping (hopefully) --- 3ds/include/csvc.hpp | 38 ++++++ 3ds/include/fspxi.hpp | 50 ++++++++ 3ds/include/title.hpp | 6 +- 3ds/source/archive.cpp | 1 + 3ds/source/csvc.s | 34 ++++++ 3ds/source/fspxi.cpp | 201 +++++++++++++++++++++++++++++++ 3ds/source/io.cpp | 260 +++++++++++++++++++++++++++-------------- 3ds/source/title.cpp | 37 ++++-- 8 files changed, 530 insertions(+), 97 deletions(-) create mode 100644 3ds/include/csvc.hpp create mode 100644 3ds/include/fspxi.hpp create mode 100644 3ds/source/csvc.s create mode 100644 3ds/source/fspxi.cpp diff --git a/3ds/include/csvc.hpp b/3ds/include/csvc.hpp new file mode 100644 index 00000000..729fa45f --- /dev/null +++ b/3ds/include/csvc.hpp @@ -0,0 +1,38 @@ +/* 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 +}; + +/** + * @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/fspxi.hpp b/3ds/include/fspxi.hpp new file mode 100644 index 00000000..6fff2de6 --- /dev/null +++ b/3ds/include/fspxi.hpp @@ -0,0 +1,50 @@ +/* + * This file is part of Checkpoint + * Copyright (C) 2017-2019 Bernardo Giordano, FlagBrew + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional Terms 7.b and 7.c of GPLv3 apply to this file: + * * Requiring preservation of specified reasonable legal notices or + * author attributions in that material or in the Appropriate Legal + * Notices displayed by works containing it. + * * Prohibiting misrepresentation of the origin of that material, + * or requiring that modified versions of such material be marked in + * reasonable ways as different from the original version. + */ + +#ifndef FSPXI_HPP +#define FSPXI_HPP + +#include <3ds.h> +#include + +namespace FSPXI { + struct PArchive { + u32 lower; + u32 upper; + }; + + Result init(void); + + Result writeToFile(PArchive archive, const std::vector& data); + Result readFromFile(PArchive archive, std::vector& data); + + Result closeArchive(PArchive archive); + + Result gbasave(PArchive* archive, FS_MediaType mediatype, u32 lowid, u32 highid); + bool accessible(FS_MediaType mediatype, u32 lowid, u32 highid); // gbasave +}; + +#endif \ No newline at end of file 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..9734b91e 100644 --- a/3ds/source/archive.cpp +++ b/3ds/source/archive.cpp @@ -25,6 +25,7 @@ */ #include "archive.hpp" +#include "fspxi.hpp" static FS_Archive mSdmc; static Mode_t mMode = MODE_SAVE; diff --git a/3ds/source/csvc.s b/3ds/source/csvc.s new file mode 100644 index 00000000..612eb71a --- /dev/null +++ b/3ds/source/csvc.s @@ -0,0 +1,34 @@ +@ 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. + +.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/fspxi.cpp b/3ds/source/fspxi.cpp new file mode 100644 index 00000000..f0112b52 --- /dev/null +++ b/3ds/source/fspxi.cpp @@ -0,0 +1,201 @@ +/* + * This file is part of Checkpoint + * Copyright (C) 2017-2019 Bernardo Giordano, FlagBrew + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional Terms 7.b and 7.c of GPLv3 apply to this file: + * * Requiring preservation of specified reasonable legal notices or + * author attributions in that material or in the Appropriate Legal + * Notices displayed by works containing it. + * * Prohibiting misrepresentation of the origin of that material, + * or requiring that modified versions of such material be marked in + * reasonable ways as different from the original version. + */ + +#include "fspxi.hpp" +#include "csvc.hpp" + +static Handle mFsPxiHandle; + +struct PFile { + u32 lower = 0; + u32 upper = 0; + + ~PFile() + { + if(lower == 0 && upper == 0) return; + + u32 *cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0xF,2,0); // 0xF0080, FSPXI:CloseFile according to 3dbrew + cmdbuf[1] = lower; + cmdbuf[2] = upper; + + svcSendSyncRequest(mFsPxiHandle); + } +}; + +static Result openFile(PFile* file, FSPXI::PArchive archive, u32 flags) +{ + static constexpr u32 save_file_path[5] = { + 1, + 1, + 3, + 0, + 0 + }; + + Result res = 0; + + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x1,7,2); // 0x101C2, FSPXI:OpenFile according to 3dbrew + cmdbuf[1] = 0; + cmdbuf[2] = archive.lower; + cmdbuf[3] = archive.upper; + cmdbuf[4] = PATH_BINARY; + cmdbuf[5] = sizeof(save_file_path); + cmdbuf[6] = flags; + cmdbuf[7] = 0; + cmdbuf[8] = IPC_Desc_PXIBuffer(sizeof(save_file_path), 0, 1); + cmdbuf[9] = (uintptr_t)save_file_path; + + res = svcSendSyncRequest(mFsPxiHandle); + if(R_FAILED(res)) return res; + + res = cmdbuf[1]; + if(R_FAILED(res)) return res; + + file->lower = cmdbuf[2]; + file->upper = cmdbuf[3]; + + return 0; +} + +Result FSPXI::init(void) +{ + return svcControlService(SERVICEOP_STEAL_CLIENT_SESSION, &mFsPxiHandle, "PxiFS0"); +} + +Result FSPXI::writeToFile(PArchive archive, const std::vector& data) +{ + PFile file; + Result res = openFile(&file, archive, FS_OPEN_WRITE); + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0xB,6,2); // 0xB0182, FSPXI:WriteFile according to 3dbrew + cmdbuf[1] = file.lower; + cmdbuf[2] = file.upper; + cmdbuf[3] = 0; + cmdbuf[4] = 0; + cmdbuf[5] = 0; // FLUSH_FLAGS + cmdbuf[6] = data.size(); + cmdbuf[7] = IPC_Desc_PXIBuffer(data.size(), 0, 1); + cmdbuf[8] = (u32)data.data(); + + res = svcSendSyncRequest(mFsPxiHandle); + if(R_FAILED(res)) return res; + + return cmdbuf[1]; +} + +Result FSPXI::readFromFile(PArchive archive, std::vector& data) +{ + PFile file; + Result res = openFile(&file, archive, FS_OPEN_READ); + if(R_FAILED(res)) return res; + + u8 read_data[0x1000]; + u32 read_amount = 0; + u64 offset = 0; + + do { + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x9,5,2); // 0x90142, FSPXI:ReadFile according to 3dbrew + cmdbuf[1] = file.lower; + cmdbuf[2] = file.upper; + cmdbuf[3] = (u32)(offset & 0xFFFFFFFF); + cmdbuf[4] = (u32)((offset >> 32) & 0xFFFFFFFF); + cmdbuf[5] = sizeof(read_data); + cmdbuf[6] = IPC_Desc_PXIBuffer(sizeof(read_data), 0, 0); + cmdbuf[7] = (u32)read_data; + + res = svcSendSyncRequest(mFsPxiHandle); + if(R_FAILED(res)) return res; + + res = cmdbuf[1]; + if(R_FAILED(res)) return res; + + read_amount = cmdbuf[2]; + data.insert(data.end(), read_data, read_data + read_amount); + offset += read_amount; + } while(read_amount == sizeof(read_data)); + + return 0; +} + +Result FSPXI::closeArchive(PArchive archive) +{ + Result res = 0; + + u32 *cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x16,2,0); + cmdbuf[1] = archive.lower; + cmdbuf[2] = archive.upper; + + res = svcSendSyncRequest(mFsPxiHandle); + if(R_FAILED(res)) return res; + + return cmdbuf[1]; +} + +Result FSPXI::gbasave(PArchive* archive, FS_MediaType mediatype, u32 lowid, u32 highid) +{ + const u32 path[4] = { + lowid, + highid, + (u32)mediatype, + (u32)1 + }; + + Result res = 0; + + u32 *cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x12,3,2); // 0x1200C2, FSPXI:OpenArchive according to 3dbrew + cmdbuf[1] = ARCHIVE_SAVEDATA_AND_CONTENT; + cmdbuf[2] = PATH_BINARY; + cmdbuf[3] = sizeof(path); + cmdbuf[4] = IPC_Desc_PXIBuffer(sizeof(path), 0, 1); + cmdbuf[5] = (u32)path; + + res = svcSendSyncRequest(mFsPxiHandle); + if(R_FAILED(res)) return res; + + archive->lower = cmdbuf[2]; + archive->upper = cmdbuf[3]; + + return cmdbuf[1]; +} + +bool FSPXI::accessible(FS_MediaType mediatype, u32 lowid, u32 highid) +{ + PArchive archive; + Result res = gbasave(&archive, mediatype, lowid, highid); + if (R_SUCCEEDED(res)) { + closeArchive(archive); + return true; + } + return false; +} diff --git a/3ds/source/io.cpp b/3ds/source/io.cpp index 8def60b7..b2520df7 100644 --- a/3ds/source/io.cpp +++ b/3ds/source/io.cpp @@ -25,6 +25,7 @@ */ #include "io.hpp" +#include "fspxi.hpp" bool io::fileExists(const std::string& path) { @@ -177,70 +178,128 @@ 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; - if (mode == MODE_SAVE) { - res = Archive::save(&archive, title.mediaType(), title.lowId(), title.highId()); - } - else if (mode == MODE_EXTDATA) { - res = Archive::extdata(&archive, title.extdataId()); - } + if(title.isGBAVC()) { + FSPXI::PArchive archive; + Result res = FSPXI::gbasave(&archive, title.mediaType(), title.lowId(), title.highId()); + if(R_SUCCEEDED(res)) + { + 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)) { + FSPXI::closeArchive(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."); + } + } + + res = io::createDirectory(Archive::sdmc(), dstPath); + if (R_FAILED(res)) { + FSPXI::closeArchive(archive); + Logger::getInstance().log(Logger::ERROR, "Failed to create destination directory."); + return std::make_tuple(false, res, "Failed to create destination directory."); + } - if (R_SUCCEEDED(res)) { - std::string suggestion = DateTime::dateTimeStr(); + std::vector data; + FSPXI::readFromFile(archive, data); + FSStream output(Archive::sdmc(), dstPath, FS_OPEN_WRITE, data.size()); + FSPXI::closeArchive(archive); - std::u16string customPath; - if (MS::multipleSelectionEnabled()) { - customPath = isNewFolder ? StringUtils::UTF8toUTF16(suggestion.c_str()) : StringUtils::UTF8toUTF16(""); + output.write(data.data(), data.size()); + + refreshDirectories(title.id()); } else { - customPath = isNewFolder ? KeyboardManager::get().keyboard(suggestion) : StringUtils::UTF8toUTF16(""); + Logger::getInstance().log(Logger::ERROR, "Failed to open GBA save archive with result 0x%08lX.", res); + return std::make_tuple(false, res, "Failed to open GBA save archive."); } - - std::u16string dstPath; - if (!isNewFolder) { - // we're overriding an existing folder - dstPath = mode == MODE_SAVE ? title.fullSavePath(cellIndex) : title.fullExtdataPath(cellIndex); + } + else { + FS_Archive archive; + if (mode == MODE_SAVE) { + res = Archive::save(&archive, title.mediaType(), title.lowId(), title.highId()); } - else { - dstPath = mode == MODE_SAVE ? title.savePath() : title.extdataPath(); - dstPath += StringUtils::UTF8toUTF16("/") + customPath; + else if (mode == MODE_EXTDATA) { + res = Archive::extdata(&archive, title.extdataId()); } - if (!isNewFolder || io::directoryExists(Archive::sdmc(), dstPath)) { - res = FSUSER_DeleteDirectoryRecursively(Archive::sdmc(), fsMakePath(PATH_UTF16, dstPath.data())); + if (R_SUCCEEDED(res)) { + 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 = mode == MODE_SAVE ? title.fullSavePath(cellIndex) : title.fullExtdataPath(cellIndex); + } + else { + dstPath = mode == MODE_SAVE ? title.savePath() : title.extdataPath(); + 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)) { + FSUSER_CloseArchive(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."); + } + } + + res = io::createDirectory(Archive::sdmc(), dstPath); if (R_FAILED(res)) { FSUSER_CloseArchive(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."); + Logger::getInstance().log(Logger::ERROR, "Failed to create destination directory."); + return std::make_tuple(false, res, "Failed to create destination directory."); } - } - res = io::createDirectory(Archive::sdmc(), dstPath); - if (R_FAILED(res)) { - FSUSER_CloseArchive(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("/"); - 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); + } - 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); + 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."); } - refreshDirectories(title.id()); + 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(); @@ -333,58 +392,85 @@ 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; - if (mode == MODE_SAVE) { - res = Archive::save(&archive, title.mediaType(), title.lowId(), title.highId()); - } - else if (mode == MODE_EXTDATA) { - res = Archive::extdata(&archive, title.extdataId()); - } - - 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())); + if(title.isGBAVC()) { + FSPXI::PArchive archive; + Result res = FSPXI::gbasave(&archive, title.mediaType(), title.lowId(), title.highId()); + if(R_SUCCEEDED(res)) + { + std::u16string srcPath = title.fullSavePath(cellIndex) + StringUtils::UTF8toUTF16("/00000001.sav"); + FSStream input(Archive::sdmc(), srcPath, FS_OPEN_READ); + if (input.good()) { + std::vector data(input.size()); // relax, max size is about 3Mbit. can handle that in 1 pass, yeah? + input.read(data.data(), data.size()); + FSPXI::writeToFile(archive, data); + FSPXI::closeArchive(archive); + } + else { + FSPXI::closeArchive(archive); + Logger::getInstance().log(Logger::ERROR, + "Failed to open source file " + StringUtils::UTF16toUTF8(srcPath) + " during restore with result 0x%08lX.", input.result()); + return std::make_tuple(false, res, "Failed to open save file."); + } } else { - deleteFolderRecursively(archive, dstPath); + Logger::getInstance().log(Logger::ERROR, "Failed to open GBA save archive with result 0x%08lX.", res); + return std::make_tuple(false, res, "Failed to open GBA save 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); + } + else { + FS_Archive archive; + if (mode == MODE_SAVE) { + res = Archive::save(&archive, title.mediaType(), title.lowId(), title.highId()); + } + else if (mode == MODE_EXTDATA) { + res = Archive::extdata(&archive, title.extdataId()); } - 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."); + 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); } - 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."); + } } } - } - 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."); - } + 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); + FSUSER_CloseArchive(archive); + } } else { CardType cardType = title.SPICardType(); diff --git a/3ds/source/title.cpp b/3ds/source/title.cpp index 05864ba8..9802fb06 100644 --- a/3ds/source/title.cpp +++ b/3ds/source/title.cpp @@ -25,6 +25,7 @@ */ #include "title.hpp" +#include "fspxi.hpp" static bool validId(u64 id); static C2D_Image loadTextureIcon(smdh_s* smdh); @@ -90,15 +91,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 +144,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) && FSPXI::accessible(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 +214,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 +237,11 @@ bool Title::accessibleSave(void) return mAccessibleSave; } +bool Title::isGBAVC(void) +{ + return mGBA; +} + bool Title::accessibleExtdata(void) { return mAccessibleExtdata; @@ -755,7 +765,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() ? (list.at(i).isGBAVC() ? (2 | 1) : 1) : 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 +785,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 +835,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 +849,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 +859,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 +888,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 +901,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 +911,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); From 49d35bbf3b37c91b71c8e3fe6927bb663c63b2e2 Mon Sep 17 00:00:00 2001 From: LiquidFenrir <liquidfenrir@outlook.fr> Date: Sun, 26 Apr 2020 23:34:30 +0200 Subject: [PATCH 2/6] remove leftover from hurrying --- 3ds/source/archive.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/3ds/source/archive.cpp b/3ds/source/archive.cpp index 9734b91e..2010a97b 100644 --- a/3ds/source/archive.cpp +++ b/3ds/source/archive.cpp @@ -25,7 +25,6 @@ */ #include "archive.hpp" -#include "fspxi.hpp" static FS_Archive mSdmc; static Mode_t mMode = MODE_SAVE; @@ -119,4 +118,4 @@ bool Archive::setPlayCoins(void) FSUSER_CloseArchive(archive); } return false; -} \ No newline at end of file +} From 5046b4bf6e90177bcd1366c4d6b275c227e21892 Mon Sep 17 00:00:00 2001 From: LiquidFenrir <liquidfenrir@outlook.fr> Date: Mon, 27 Apr 2020 00:10:00 +0200 Subject: [PATCH 3/6] fix gba stuff --- 3ds/include/csvc.hpp | 21 ++++++++++++--------- 3ds/source/fspxi.cpp | 6 +++++- 3ds/source/io.cpp | 2 ++ 3ds/source/title.cpp | 13 ++++++++----- 3ds/source/util.cpp | 2 ++ 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/3ds/include/csvc.hpp b/3ds/include/csvc.hpp index 729fa45f..86d54291 100644 --- a/3ds/include/csvc.hpp +++ b/3ds/include/csvc.hpp @@ -27,12 +27,15 @@ enum ServiceOp SERVICEOP_GET_NAME, ///< Get the name of a service or global port given a client or session handle }; -/** - * @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, ...); +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/source/fspxi.cpp b/3ds/source/fspxi.cpp index f0112b52..af8c32b9 100644 --- a/3ds/source/fspxi.cpp +++ b/3ds/source/fspxi.cpp @@ -183,10 +183,14 @@ Result FSPXI::gbasave(PArchive* archive, FS_MediaType mediatype, u32 lowid, u32 res = svcSendSyncRequest(mFsPxiHandle); if(R_FAILED(res)) return res; + res = cmdbuf[1]; + if(R_FAILED(res)) return res; + archive->lower = cmdbuf[2]; archive->upper = cmdbuf[3]; - return cmdbuf[1]; + PFile f; + return openFile(&f, *archive, FS_OPEN_READ); } bool FSPXI::accessible(FS_MediaType mediatype, u32 lowid, u32 highid) diff --git a/3ds/source/io.cpp b/3ds/source/io.cpp index b2520df7..8da8c6aa 100644 --- a/3ds/source/io.cpp +++ b/3ds/source/io.cpp @@ -219,6 +219,8 @@ std::tuple<bool, Result, std::string> io::backup(size_t index, size_t cellIndex) return std::make_tuple(false, res, "Failed to create destination directory."); } + dstPath += StringUtils::UTF8toUTF16("/00000001.sav"); + std::vector<u8> data; FSPXI::readFromFile(archive, data); FSStream output(Archive::sdmc(), dstPath, FS_OPEN_WRITE, data.size()); diff --git a/3ds/source/title.cpp b/3ds/source/title.cpp index 9802fb06..194e7404 100644 --- a/3ds/source/title.cpp +++ b/3ds/source/title.cpp @@ -320,7 +320,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()) { @@ -559,8 +559,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<u64[]>(count); + AM_GetTitleList(NULL, MEDIATYPE_NAND, count, ids_nand.get()); for (u32 i = 0; i < count; i++) { if (validId(ids_nand[i])) { @@ -577,8 +577,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<u64[]>(count); + AM_GetTitleList(NULL, MEDIATYPE_SD, count, ids.get()); for (u32 i = 0; i < count; i++) { if (validId(ids[i])) { @@ -587,6 +587,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); diff --git a/3ds/source/util.cpp b/3ds/source/util.cpp index e78e33cf..42cb5511 100644 --- a/3ds/source/util.cpp +++ b/3ds/source/util.cpp @@ -25,6 +25,7 @@ */ #include "util.hpp" +#include "fspxi.hpp" static Result consoleDisplayError(const std::string& message, Result res) { @@ -49,6 +50,7 @@ Result servicesInit(void) { Result res = 0; + FSPXI::init(); if (R_FAILED(res = Archive::init())) { return consoleDisplayError("Archive::init failed.", res); } From 9302f64560fd7eee48765debd1f2db8934af54fe Mon Sep 17 00:00:00 2001 From: LiquidFenrir <liquidfenrir@outlook.fr> Date: Mon, 27 Apr 2020 00:17:53 +0200 Subject: [PATCH 4/6] fix the cache not remembering gba vc titles --- 3ds/source/title.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3ds/source/title.cpp b/3ds/source/title.cpp index 194e7404..7162f36e 100644 --- a/3ds/source/title.cpp +++ b/3ds/source/title.cpp @@ -768,7 +768,7 @@ static void exportTitleListCache(std::vector<Title>& 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(); - u8 accessibleSaveRaw = list.at(i).accessibleSave() ? (list.at(i).isGBAVC() ? (2 | 1) : 1) : 0; + 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()); From 0ac4d4d27c302689bf44b7d650856dad40b53a12 Mon Sep 17 00:00:00 2001 From: LiquidFenrir <liquidfenrir@outlook.fr> Date: Mon, 27 Apr 2020 01:53:33 +0200 Subject: [PATCH 5/6] legal stuff for csvc.s --- 3ds/source/csvc.s | 2 ++ 1 file changed, 2 insertions(+) diff --git a/3ds/source/csvc.s b/3ds/source/csvc.s index 612eb71a..f9ee99ae 100644 --- a/3ds/source/csvc.s +++ b/3ds/source/csvc.s @@ -12,6 +12,8 @@ @ 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 From 2948636974ee9075865056b1ade7abd40dce3c31 Mon Sep 17 00:00:00 2001 From: LiquidFenrir <liquidfenrir@outlook.fr> Date: Fri, 15 May 2020 23:21:21 +0200 Subject: [PATCH 6/6] update to match the new ctrulib with fspxi in it FSStreams can now contain either a Handle or a FSPXI_File, using a variant same idea is applied in the io namespace to reuse code instead of the big copy paste used before --- 3ds/include/archive.hpp | 2 + 3ds/include/csvc.hpp | 2 + 3ds/include/fspxi.hpp | 50 ------- 3ds/include/fsstream.hpp | 9 +- 3ds/include/io.hpp | 1 + 3ds/source/archive.cpp | 28 +++- 3ds/source/fspxi.cpp | 205 -------------------------- 3ds/source/fsstream.cpp | 97 ++++++++++++- 3ds/source/io.cpp | 307 ++++++++++++++++++++++----------------- 3ds/source/title.cpp | 3 +- 3ds/source/util.cpp | 2 - 11 files changed, 301 insertions(+), 405 deletions(-) delete mode 100644 3ds/include/fspxi.hpp delete mode 100644 3ds/source/fspxi.cpp 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 index 86d54291..6ff68718 100644 --- a/3ds/include/csvc.hpp +++ b/3ds/include/csvc.hpp @@ -27,6 +27,8 @@ enum ServiceOp SERVICEOP_GET_NAME, ///< Get the name of a service or global port given a client or session handle }; +inline Handle FsPxiHandle; + extern "C" { /** diff --git a/3ds/include/fspxi.hpp b/3ds/include/fspxi.hpp deleted file mode 100644 index 6fff2de6..00000000 --- a/3ds/include/fspxi.hpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * This file is part of Checkpoint - * Copyright (C) 2017-2019 Bernardo Giordano, FlagBrew - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * Additional Terms 7.b and 7.c of GPLv3 apply to this file: - * * Requiring preservation of specified reasonable legal notices or - * author attributions in that material or in the Appropriate Legal - * Notices displayed by works containing it. - * * Prohibiting misrepresentation of the origin of that material, - * or requiring that modified versions of such material be marked in - * reasonable ways as different from the original version. - */ - -#ifndef FSPXI_HPP -#define FSPXI_HPP - -#include <3ds.h> -#include <vector> - -namespace FSPXI { - struct PArchive { - u32 lower; - u32 upper; - }; - - Result init(void); - - Result writeToFile(PArchive archive, const std::vector<u8>& data); - Result readFromFile(PArchive archive, std::vector<u8>& data); - - Result closeArchive(PArchive archive); - - Result gbasave(PArchive* archive, FS_MediaType mediatype, u32 lowid, u32 highid); - bool accessible(FS_MediaType mediatype, u32 lowid, u32 highid); // gbasave -}; - -#endif \ No newline at end of file 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 <string> +#include <variant> + +using MultiHandle = std::variant<FSPXI_File, Handle>; 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/source/archive.cpp b/3ds/source/archive.cpp index 2010a97b..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; diff --git a/3ds/source/fspxi.cpp b/3ds/source/fspxi.cpp deleted file mode 100644 index af8c32b9..00000000 --- a/3ds/source/fspxi.cpp +++ /dev/null @@ -1,205 +0,0 @@ -/* - * This file is part of Checkpoint - * Copyright (C) 2017-2019 Bernardo Giordano, FlagBrew - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * Additional Terms 7.b and 7.c of GPLv3 apply to this file: - * * Requiring preservation of specified reasonable legal notices or - * author attributions in that material or in the Appropriate Legal - * Notices displayed by works containing it. - * * Prohibiting misrepresentation of the origin of that material, - * or requiring that modified versions of such material be marked in - * reasonable ways as different from the original version. - */ - -#include "fspxi.hpp" -#include "csvc.hpp" - -static Handle mFsPxiHandle; - -struct PFile { - u32 lower = 0; - u32 upper = 0; - - ~PFile() - { - if(lower == 0 && upper == 0) return; - - u32 *cmdbuf = getThreadCommandBuffer(); - cmdbuf[0] = IPC_MakeHeader(0xF,2,0); // 0xF0080, FSPXI:CloseFile according to 3dbrew - cmdbuf[1] = lower; - cmdbuf[2] = upper; - - svcSendSyncRequest(mFsPxiHandle); - } -}; - -static Result openFile(PFile* file, FSPXI::PArchive archive, u32 flags) -{ - static constexpr u32 save_file_path[5] = { - 1, - 1, - 3, - 0, - 0 - }; - - Result res = 0; - - u32 *cmdbuf = getThreadCommandBuffer(); - - cmdbuf[0] = IPC_MakeHeader(0x1,7,2); // 0x101C2, FSPXI:OpenFile according to 3dbrew - cmdbuf[1] = 0; - cmdbuf[2] = archive.lower; - cmdbuf[3] = archive.upper; - cmdbuf[4] = PATH_BINARY; - cmdbuf[5] = sizeof(save_file_path); - cmdbuf[6] = flags; - cmdbuf[7] = 0; - cmdbuf[8] = IPC_Desc_PXIBuffer(sizeof(save_file_path), 0, 1); - cmdbuf[9] = (uintptr_t)save_file_path; - - res = svcSendSyncRequest(mFsPxiHandle); - if(R_FAILED(res)) return res; - - res = cmdbuf[1]; - if(R_FAILED(res)) return res; - - file->lower = cmdbuf[2]; - file->upper = cmdbuf[3]; - - return 0; -} - -Result FSPXI::init(void) -{ - return svcControlService(SERVICEOP_STEAL_CLIENT_SESSION, &mFsPxiHandle, "PxiFS0"); -} - -Result FSPXI::writeToFile(PArchive archive, const std::vector<u8>& data) -{ - PFile file; - Result res = openFile(&file, archive, FS_OPEN_WRITE); - u32 *cmdbuf = getThreadCommandBuffer(); - - cmdbuf[0] = IPC_MakeHeader(0xB,6,2); // 0xB0182, FSPXI:WriteFile according to 3dbrew - cmdbuf[1] = file.lower; - cmdbuf[2] = file.upper; - cmdbuf[3] = 0; - cmdbuf[4] = 0; - cmdbuf[5] = 0; // FLUSH_FLAGS - cmdbuf[6] = data.size(); - cmdbuf[7] = IPC_Desc_PXIBuffer(data.size(), 0, 1); - cmdbuf[8] = (u32)data.data(); - - res = svcSendSyncRequest(mFsPxiHandle); - if(R_FAILED(res)) return res; - - return cmdbuf[1]; -} - -Result FSPXI::readFromFile(PArchive archive, std::vector<u8>& data) -{ - PFile file; - Result res = openFile(&file, archive, FS_OPEN_READ); - if(R_FAILED(res)) return res; - - u8 read_data[0x1000]; - u32 read_amount = 0; - u64 offset = 0; - - do { - u32 *cmdbuf = getThreadCommandBuffer(); - - cmdbuf[0] = IPC_MakeHeader(0x9,5,2); // 0x90142, FSPXI:ReadFile according to 3dbrew - cmdbuf[1] = file.lower; - cmdbuf[2] = file.upper; - cmdbuf[3] = (u32)(offset & 0xFFFFFFFF); - cmdbuf[4] = (u32)((offset >> 32) & 0xFFFFFFFF); - cmdbuf[5] = sizeof(read_data); - cmdbuf[6] = IPC_Desc_PXIBuffer(sizeof(read_data), 0, 0); - cmdbuf[7] = (u32)read_data; - - res = svcSendSyncRequest(mFsPxiHandle); - if(R_FAILED(res)) return res; - - res = cmdbuf[1]; - if(R_FAILED(res)) return res; - - read_amount = cmdbuf[2]; - data.insert(data.end(), read_data, read_data + read_amount); - offset += read_amount; - } while(read_amount == sizeof(read_data)); - - return 0; -} - -Result FSPXI::closeArchive(PArchive archive) -{ - Result res = 0; - - u32 *cmdbuf = getThreadCommandBuffer(); - cmdbuf[0] = IPC_MakeHeader(0x16,2,0); - cmdbuf[1] = archive.lower; - cmdbuf[2] = archive.upper; - - res = svcSendSyncRequest(mFsPxiHandle); - if(R_FAILED(res)) return res; - - return cmdbuf[1]; -} - -Result FSPXI::gbasave(PArchive* archive, FS_MediaType mediatype, u32 lowid, u32 highid) -{ - const u32 path[4] = { - lowid, - highid, - (u32)mediatype, - (u32)1 - }; - - Result res = 0; - - u32 *cmdbuf = getThreadCommandBuffer(); - cmdbuf[0] = IPC_MakeHeader(0x12,3,2); // 0x1200C2, FSPXI:OpenArchive according to 3dbrew - cmdbuf[1] = ARCHIVE_SAVEDATA_AND_CONTENT; - cmdbuf[2] = PATH_BINARY; - cmdbuf[3] = sizeof(path); - cmdbuf[4] = IPC_Desc_PXIBuffer(sizeof(path), 0, 1); - cmdbuf[5] = (u32)path; - - res = svcSendSyncRequest(mFsPxiHandle); - if(R_FAILED(res)) return res; - - res = cmdbuf[1]; - if(R_FAILED(res)) return res; - - archive->lower = cmdbuf[2]; - archive->upper = cmdbuf[3]; - - PFile f; - return openFile(&f, *archive, FS_OPEN_READ); -} - -bool FSPXI::accessible(FS_MediaType mediatype, u32 lowid, u32 highid) -{ - PArchive archive; - Result res = gbasave(&archive, mediatype, lowid, highid); - if (R_SUCCEEDED(res)) { - closeArchive(archive); - return true; - } - return false; -} 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 8da8c6aa..4ebb55f6 100644 --- a/3ds/source/io.cpp +++ b/3ds/source/io.cpp @@ -25,7 +25,17 @@ */ #include "io.hpp" -#include "fspxi.hpp" +#include "csvc.hpp" +#include <variant> + +struct FSPXI_Archive_s { + FSPXI_Archive archive; +}; +struct FS_Archive_s { + FS_Archive archive; +}; +using MultiArchive = std::variant<FSPXI_Archive_s, FS_Archive_s>; + bool io::fileExists(const std::string& path) { @@ -89,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<u8[]>(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; @@ -178,111 +236,89 @@ std::tuple<bool, Result, std::string> 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) { - if(title.isGBAVC()) { - FSPXI::PArchive archive; - Result res = FSPXI::gbasave(&archive, title.mediaType(), title.lowId(), title.highId()); - if(R_SUCCEEDED(res)) - { - 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)) { - FSPXI::closeArchive(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."); - } - } - - res = io::createDirectory(Archive::sdmc(), dstPath); - if (R_FAILED(res)) { - FSPXI::closeArchive(archive); - Logger::getInstance().log(Logger::ERROR, "Failed to create destination directory."); - return std::make_tuple(false, res, "Failed to create destination directory."); - } - - dstPath += StringUtils::UTF8toUTF16("/00000001.sav"); - - std::vector<u8> data; - FSPXI::readFromFile(archive, data); - FSStream output(Archive::sdmc(), dstPath, FS_OPEN_WRITE, data.size()); - FSPXI::closeArchive(archive); - - output.write(data.data(), data.size()); - - refreshDirectories(title.id()); + MultiArchive varchive; + if (mode == MODE_SAVE) { + if(title.isGBAVC()) { + FSPXI_Archive_s archive; + res = Archive::save(&archive.archive, title.mediaType(), title.lowId(), title.highId()); + varchive = archive; } else { - Logger::getInstance().log(Logger::ERROR, "Failed to open GBA save archive with result 0x%08lX.", res); - return std::make_tuple(false, res, "Failed to open GBA save archive."); + FS_Archive_s archive; + res = Archive::save(&archive.archive, title.mediaType(), title.lowId(), title.highId()); + varchive = archive; } } - else { - FS_Archive archive; - if (mode == MODE_SAVE) { - res = Archive::save(&archive, title.mediaType(), title.lowId(), title.highId()); + else if (mode == MODE_EXTDATA) { + FS_Archive_s archive; + res = Archive::extdata(&archive.archive, title.extdataId()); + varchive = archive; + } + + if (R_SUCCEEDED(res)) { + std::string suggestion = DateTime::dateTimeStr(); + + std::u16string customPath; + if (MS::multipleSelectionEnabled()) { + customPath = isNewFolder ? StringUtils::UTF8toUTF16(suggestion.c_str()) : StringUtils::UTF8toUTF16(""); } - else if (mode == MODE_EXTDATA) { - res = Archive::extdata(&archive, title.extdataId()); + else { + customPath = isNewFolder ? KeyboardManager::get().keyboard(suggestion) : StringUtils::UTF8toUTF16(""); } - if (R_SUCCEEDED(res)) { - std::string suggestion = DateTime::dateTimeStr(); + std::u16string dstPath; + if (!isNewFolder) { + // we're overriding an existing folder + dstPath = mode == MODE_SAVE ? title.fullSavePath(cellIndex) : title.fullExtdataPath(cellIndex); + } + else { + dstPath = mode == MODE_SAVE ? title.savePath() : title.extdataPath(); + dstPath += StringUtils::UTF8toUTF16("/") + customPath; + } - std::u16string customPath; - if (MS::multipleSelectionEnabled()) { - customPath = isNewFolder ? StringUtils::UTF8toUTF16(suggestion.c_str()) : StringUtils::UTF8toUTF16(""); - } - else { - customPath = isNewFolder ? KeyboardManager::get().keyboard(suggestion) : StringUtils::UTF8toUTF16(""); + if (!isNewFolder || io::directoryExists(Archive::sdmc(), dstPath)) { + res = FSUSER_DeleteDirectoryRecursively(Archive::sdmc(), fsMakePath(PATH_UTF16, dstPath.data())); + if (R_FAILED(res)) { + 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."); } + } - std::u16string dstPath; - if (!isNewFolder) { - // we're overriding an existing folder - dstPath = mode == MODE_SAVE ? title.fullSavePath(cellIndex) : title.fullExtdataPath(cellIndex); + res = io::createDirectory(Archive::sdmc(), dstPath); + if (R_FAILED(res)) { + if(varchive.index() == 0) { + FSPXI_CloseArchive(FsPxiHandle, std::get<0>(varchive).archive); } else { - dstPath = mode == MODE_SAVE ? title.savePath() : title.extdataPath(); - 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)) { - FSUSER_CloseArchive(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."); - } + 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."); + } - res = io::createDirectory(Archive::sdmc(), dstPath); + 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)) { - FSUSER_CloseArchive(archive); - Logger::getInstance().log(Logger::ERROR, "Failed to create destination directory."); - return std::make_tuple(false, res, "Failed to create destination directory."); + 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."; @@ -291,17 +327,14 @@ std::tuple<bool, Result, std::string> io::backup(size_t index, size_t cellIndex) 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); } - - FSUSER_CloseArchive(archive); + 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."); } - } else { CardType cardType = title.SPICardType(); @@ -394,45 +427,47 @@ std::tuple<bool, Result, std::string> 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) { - if(title.isGBAVC()) { - FSPXI::PArchive archive; - Result res = FSPXI::gbasave(&archive, title.mediaType(), title.lowId(), title.highId()); - if(R_SUCCEEDED(res)) - { - std::u16string srcPath = title.fullSavePath(cellIndex) + StringUtils::UTF8toUTF16("/00000001.sav"); - FSStream input(Archive::sdmc(), srcPath, FS_OPEN_READ); - if (input.good()) { - std::vector<u8> data(input.size()); // relax, max size is about 3Mbit. can handle that in 1 pass, yeah? - input.read(data.data(), data.size()); - FSPXI::writeToFile(archive, data); - FSPXI::closeArchive(archive); - } - else { - FSPXI::closeArchive(archive); - Logger::getInstance().log(Logger::ERROR, - "Failed to open source file " + StringUtils::UTF16toUTF8(srcPath) + " during restore with result 0x%08lX.", input.result()); - return std::make_tuple(false, res, "Failed to open save file."); - } + MultiArchive varchive; + if (mode == MODE_SAVE) { + if(title.isGBAVC()) { + FSPXI_Archive_s archive; + res = Archive::save(&archive.archive, title.mediaType(), title.lowId(), title.highId()); + varchive = archive; } else { - Logger::getInstance().log(Logger::ERROR, "Failed to open GBA save archive with result 0x%08lX.", res); - return std::make_tuple(false, res, "Failed to open GBA save archive."); + FS_Archive_s archive; + res = Archive::save(&archive.archive, title.mediaType(), title.lowId(), title.highId()); + varchive = archive; } } - else { - FS_Archive archive; - if (mode == MODE_SAVE) { - res = Archive::save(&archive, title.mediaType(), title.lowId(), title.highId()); - } - else if (mode == MODE_EXTDATA) { - res = Archive::extdata(&archive, title.extdataId()); - } + else if (mode == MODE_EXTDATA) { + 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("/"); + if (R_SUCCEEDED(res)) { + std::u16string srcPath = mode == MODE_SAVE ? title.fullSavePath(cellIndex) : title.fullExtdataPath(cellIndex); + srcPath += StringUtils::UTF8toUTF16("/"); + + if(title.isGBAVC()) { + FSPXI_Archive archive = std::get<0>(varchive).archive; + + 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("/"); + FS_Archive archive = std::get<1>(varchive).archive;; if (mode != MODE_EXTDATA) { FSUSER_DeleteDirectoryRecursively(archive, fsMakePath(PATH_UTF16, dstPath.data())); } @@ -465,13 +500,13 @@ std::tuple<bool, Result, std::string> io::restore(size_t index, size_t cellIndex return std::make_tuple(false, res, "Failed to fix secure value."); } } - } - 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); + 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."); } } else { diff --git a/3ds/source/title.cpp b/3ds/source/title.cpp index 7162f36e..1748451d 100644 --- a/3ds/source/title.cpp +++ b/3ds/source/title.cpp @@ -25,7 +25,6 @@ */ #include "title.hpp" -#include "fspxi.hpp" static bool validId(u64 id); static C2D_Image loadTextureIcon(smdh_s* smdh); @@ -144,7 +143,7 @@ bool Title::load(u64 _id, FS_MediaType _media, FS_CardType _card) AM_GetTitleProductCode(mMedia, mId, productCode); mAccessibleSave = Archive::accessible(mediaType(), lowId(), highId()); - mGBA = (!mAccessibleSave) && FSPXI::accessible(mediaType(), lowId(), highId()); + mGBA = (!mAccessibleSave) && Archive::accessibleRaw(mediaType(), lowId(), highId()); mAccessibleExtdata = mMedia == MEDIATYPE_NAND ? false : Archive::accessible(extdataId()); if (mAccessibleSave || mGBA) { diff --git a/3ds/source/util.cpp b/3ds/source/util.cpp index 42cb5511..e78e33cf 100644 --- a/3ds/source/util.cpp +++ b/3ds/source/util.cpp @@ -25,7 +25,6 @@ */ #include "util.hpp" -#include "fspxi.hpp" static Result consoleDisplayError(const std::string& message, Result res) { @@ -50,7 +49,6 @@ Result servicesInit(void) { Result res = 0; - FSPXI::init(); if (R_FAILED(res = Archive::init())) { return consoleDisplayError("Archive::init failed.", res); }