dc65a4c8a9
Phase 5 — TransferService extraction: - New nxst::TransferService owns all sender/receiver state, threads, and atomics (previously 11 file-statics across transfer_sender.cpp + transfer_receiver.cpp) - startReceive() calls io::restore internally; UI never touches infra/fs or infra/net - TitlesLayout routes all transfer ops through mainApp->transfer - g_currentUId removed from global scope; TitlesLayout::InitTitles(AccountUid) - MainApplication gains transfer member; layout refs renamed to snake_case - userAppExit() updated to cancel via service Phase 6 — Result<T> + RAII: - include/nxst/domain/result.hpp: 85-line tagged-union Result<T,E> + Result<void,E> - include/nxst/infra/fs/handles.hpp: FsFileSystemHandle (auto fsFsClose), FileHandle (auto fclose) — eliminates manual close on every error path - io::backup / io::restore return nxst::Result<std::string> (was tuple<bool,Result,string>) - new u8[] + malloc in copyFile/restore replaced with std::vector<u8> - NACP save-creation extracted to createSaveIfNeeded() helper in io.cpp Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
331 lines
12 KiB
C++
331 lines
12 KiB
C++
/*
|
|
* This file is part of Checkpoint
|
|
* Copyright (C) 2017-2021 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 <nxst/infra/fs/io.hpp>
|
|
#include <nxst/infra/fs/handles.hpp>
|
|
#include <nxst/app/main.hpp>
|
|
#include <nxst/infra/sys/logger.hpp>
|
|
#include <vector>
|
|
|
|
bool io::fileExists(const std::string& path)
|
|
{
|
|
struct stat buffer;
|
|
return (stat(path.c_str(), &buffer) == 0);
|
|
}
|
|
|
|
void io::copyFile(const std::string& srcPath, const std::string& dstPath)
|
|
{
|
|
g_isTransferringFile = true;
|
|
|
|
nxst::FileHandle src(fopen(srcPath.c_str(), "rb"));
|
|
if (!src) {
|
|
nxst::log::error("Failed to open source file %s during copy with errno %d. Skipping...",
|
|
srcPath.c_str(), errno);
|
|
g_isTransferringFile = false;
|
|
return;
|
|
}
|
|
nxst::FileHandle dst(fopen(dstPath.c_str(), "wb"));
|
|
if (!dst) {
|
|
nxst::log::error("Failed to open destination file %s during copy with errno %d. Skipping...",
|
|
dstPath.c_str(), errno);
|
|
g_isTransferringFile = false;
|
|
return;
|
|
}
|
|
|
|
fseek(src.get(), 0, SEEK_END);
|
|
u64 sz = (u64)ftell(src.get());
|
|
rewind(src.get());
|
|
|
|
std::vector<u8> buf(BUFFER_SIZE);
|
|
u64 offset = 0;
|
|
|
|
size_t slashpos = srcPath.rfind('/');
|
|
g_currentFile = srcPath.substr(slashpos + 1, srcPath.length() - slashpos - 1);
|
|
|
|
while (offset < sz) {
|
|
u32 count = (u32)fread(buf.data(), 1, BUFFER_SIZE, src.get());
|
|
if (count == 0) {
|
|
nxst::log::error("fread returned 0 for %s at offset %llu/%llu (errno %d). Aborting.",
|
|
srcPath.c_str(), (unsigned long long)offset,
|
|
(unsigned long long)sz, errno);
|
|
break;
|
|
}
|
|
fwrite(buf.data(), 1, count, dst.get());
|
|
offset += count;
|
|
}
|
|
|
|
if (dstPath.rfind("save:/", 0) == 0) {
|
|
fsdevCommitDevice("save");
|
|
}
|
|
|
|
g_isTransferringFile = false;
|
|
}
|
|
|
|
Result io::copyDirectory(const std::string& srcPath, const std::string& dstPath)
|
|
{
|
|
Result res = 0;
|
|
bool quit = false;
|
|
Directory items(srcPath);
|
|
|
|
if (!items.good()) {
|
|
return items.error();
|
|
}
|
|
|
|
for (size_t i = 0, sz = items.size(); i < sz && !quit; i++) {
|
|
std::string newsrc = srcPath + items.entry(i);
|
|
std::string newdst = dstPath + items.entry(i);
|
|
|
|
if (items.folder(i)) {
|
|
res = io::createDirectory(newdst);
|
|
if (R_SUCCEEDED(res)) {
|
|
newsrc += "/";
|
|
newdst += "/";
|
|
res = io::copyDirectory(newsrc, newdst);
|
|
}
|
|
else {
|
|
quit = true;
|
|
}
|
|
}
|
|
else {
|
|
io::copyFile(newsrc, newdst);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
Result io::createDirectory(const std::string& path)
|
|
{
|
|
mkdir(path.c_str(), 0777);
|
|
return 0;
|
|
}
|
|
|
|
bool io::directoryExists(const std::string& path)
|
|
{
|
|
struct stat sb;
|
|
return (stat(path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode));
|
|
}
|
|
|
|
Result io::deleteFolderRecursively(const std::string& path)
|
|
{
|
|
Directory dir(path);
|
|
if (!dir.good()) {
|
|
return dir.error();
|
|
}
|
|
|
|
for (size_t i = 0, sz = dir.size(); i < sz; i++) {
|
|
if (dir.folder(i)) {
|
|
std::string newpath = path + "/" + dir.entry(i) + "/";
|
|
deleteFolderRecursively(newpath);
|
|
newpath = path + dir.entry(i);
|
|
rmdir(newpath.c_str());
|
|
}
|
|
else {
|
|
std::string newpath = path + dir.entry(i);
|
|
std::remove(newpath.c_str());
|
|
}
|
|
}
|
|
|
|
rmdir(path.c_str());
|
|
return 0;
|
|
}
|
|
|
|
nxst::Result<std::string> io::backup(size_t index, AccountUid uid)
|
|
{
|
|
Title title;
|
|
getTitle(title, uid, index);
|
|
|
|
nxst::log::info("Started backup of %s. Title id: 0x%016lX; User id: 0x%lX%lX.",
|
|
title.name().c_str(), title.id(),
|
|
title.userId().uid[1], title.userId().uid[0]);
|
|
|
|
nxst::FsFileSystemHandle fsHandle;
|
|
Result res = FileSystem::mount(fsHandle.get(), title.id(), title.userId());
|
|
if (R_FAILED(res)) {
|
|
nxst::log::error("Failed to mount filesystem during backup with result 0x%08lX. "
|
|
"Title id: 0x%016lX; User id: 0x%lX%lX.",
|
|
res, title.id(), title.userId().uid[1], title.userId().uid[0]);
|
|
return nxst::Result<std::string>::failure("Failed to mount save.");
|
|
}
|
|
fsHandle.valid = true;
|
|
|
|
if (FileSystem::mount(*fsHandle.get()) == -1) {
|
|
nxst::log::error("Failed to mount devfs during backup. Title id: 0x%016lX; User id: 0x%lX%lX.",
|
|
title.id(), title.userId().uid[1], title.userId().uid[0]);
|
|
FileSystem::unmount();
|
|
return nxst::Result<std::string>::failure("Failed to mount save.");
|
|
}
|
|
fsHandle.release(); // devfs now owns the kernel handle
|
|
|
|
std::string suggestion = StringUtils::removeNotAscii(StringUtils::removeAccents(Account::username(title.userId())));
|
|
|
|
io::createDirectory(title.path());
|
|
std::string dst_path = title.path() + "/" + suggestion;
|
|
std::string tmp_path = dst_path + ".tmp";
|
|
|
|
if (io::directoryExists(tmp_path)) {
|
|
io::deleteFolderRecursively(tmp_path + "/");
|
|
}
|
|
io::createDirectory(tmp_path);
|
|
res = io::copyDirectory("save:/", tmp_path + "/");
|
|
if (R_FAILED(res)) {
|
|
FileSystem::unmount();
|
|
io::deleteFolderRecursively(tmp_path + "/");
|
|
nxst::log::error("Failed to copy directory to %s with result 0x%08lX.", tmp_path.c_str(), res);
|
|
return nxst::Result<std::string>::failure("Failed to backup save.");
|
|
}
|
|
|
|
if (io::directoryExists(dst_path)) {
|
|
io::deleteFolderRecursively(dst_path + "/");
|
|
}
|
|
if (rename(tmp_path.c_str(), dst_path.c_str()) != 0) {
|
|
FileSystem::unmount();
|
|
nxst::log::error("Failed to rename temp backup to %s.", dst_path.c_str());
|
|
return nxst::Result<std::string>::failure("Failed to finalise backup.");
|
|
}
|
|
|
|
refreshDirectories(title.id());
|
|
FileSystem::unmount();
|
|
|
|
nxst::log::info("Backup succeeded.");
|
|
return nxst::Result<std::string>::success(dst_path);
|
|
}
|
|
|
|
// Creates the save data filesystem for a title if it doesn't exist yet.
|
|
static void createSaveIfNeeded(u64 title_id, AccountUid uid)
|
|
{
|
|
std::vector<u8> nsacd_buf(sizeof(NsApplicationControlData), 0);
|
|
auto* nsacd = reinterpret_cast<NsApplicationControlData*>(nsacd_buf.data());
|
|
|
|
size_t outsize = 0;
|
|
if (!R_SUCCEEDED(nsGetApplicationControlData(NsApplicationControlSource_Storage,
|
|
title_id, nsacd,
|
|
sizeof(NsApplicationControlData), &outsize))) {
|
|
return;
|
|
}
|
|
|
|
static const FsSaveDataMetaInfo meta = {.size = 0x40060, .type = FsSaveDataMetaType_Thumbnail};
|
|
|
|
FsSaveDataAttribute attr = {};
|
|
attr.application_id = title_id;
|
|
attr.uid = uid;
|
|
attr.save_data_type = FsSaveDataType_Account;
|
|
attr.save_data_rank = FsSaveDataRank_Primary;
|
|
|
|
FsSaveDataCreationInfo create_info = {};
|
|
create_info.save_data_size = (s64)nsacd->nacp.user_account_save_data_size;
|
|
create_info.journal_size = (s64)nsacd->nacp.user_account_save_data_journal_size;
|
|
create_info.available_size = 0x4000;
|
|
create_info.owner_id = nsacd->nacp.save_data_owner_id;
|
|
create_info.save_data_space_id = FsSaveDataSpaceId_User;
|
|
|
|
fsCreateSaveDataFileSystem(&attr, &create_info, &meta);
|
|
}
|
|
|
|
nxst::Result<std::string> io::restore(size_t index, AccountUid uid, size_t cellIndex, const std::string& nameFromCell)
|
|
{
|
|
(void)cellIndex;
|
|
|
|
Title title;
|
|
getTitle(title, uid, index);
|
|
|
|
nxst::log::info("Started restore of %s. Title id: 0x%016lX; User id: 0x%lX%lX.",
|
|
title.name().c_str(), title.id(),
|
|
title.userId().uid[1], title.userId().uid[0]);
|
|
|
|
createSaveIfNeeded(title.id(), uid);
|
|
|
|
nxst::FsFileSystemHandle fsHandle;
|
|
Result res = FileSystem::mount(fsHandle.get(), title.id(), uid);
|
|
if (R_FAILED(res)) {
|
|
nxst::log::error("Failed to mount filesystem during restore with result 0x%08lX. "
|
|
"Title id: 0x%016lX; User id: 0x%lX%lX.",
|
|
res, title.id(), uid.uid[1], uid.uid[0]);
|
|
return nxst::Result<std::string>::failure("Failed to mount save.");
|
|
}
|
|
fsHandle.valid = true;
|
|
|
|
if (FileSystem::mount(*fsHandle.get()) == -1) {
|
|
nxst::log::error("Failed to mount devfs during restore. Title id: 0x%016lX; User id: 0x%lX%lX.",
|
|
title.id(), uid.uid[1], uid.uid[0]);
|
|
FileSystem::unmount();
|
|
return nxst::Result<std::string>::failure("Failed to mount save.");
|
|
}
|
|
fsHandle.release(); // devfs now owns the kernel handle
|
|
|
|
std::string suggestion = StringUtils::removeNotAscii(StringUtils::removeAccents(Account::username(uid)));
|
|
std::string src_path = title.path() + "/" + suggestion + "/";
|
|
const std::string dst_path = "save:/";
|
|
|
|
{
|
|
Directory src_check(src_path);
|
|
if (!src_check.good() || src_check.size() == 0) {
|
|
FileSystem::unmount();
|
|
nxst::log::error("Restore source is empty or missing: %s", src_path.c_str());
|
|
return nxst::Result<std::string>::failure("Restore source is empty or missing.");
|
|
}
|
|
}
|
|
|
|
{
|
|
Directory save_root(dst_path);
|
|
for (size_t i = 0, sz = save_root.size(); i < sz; i++) {
|
|
if (save_root.folder(i)) {
|
|
io::deleteFolderRecursively(dst_path + save_root.entry(i) + "/");
|
|
rmdir((dst_path + save_root.entry(i)).c_str());
|
|
} else {
|
|
std::remove((dst_path + save_root.entry(i)).c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
res = fsdevCommitDevice("save");
|
|
if (R_FAILED(res)) {
|
|
FileSystem::unmount();
|
|
nxst::log::error("Failed to commit save after clearing with result 0x%08lX.", res);
|
|
return nxst::Result<std::string>::failure("Failed to commit save after delete.");
|
|
}
|
|
|
|
res = io::copyDirectory(src_path, dst_path);
|
|
if (R_FAILED(res)) {
|
|
FileSystem::unmount();
|
|
nxst::log::error("Failed to copy %s to save:/ with result 0x%08lX.", src_path.c_str(), res);
|
|
return nxst::Result<std::string>::failure("Failed to restore save.");
|
|
}
|
|
|
|
res = fsdevCommitDevice("save");
|
|
if (R_FAILED(res)) {
|
|
FileSystem::unmount();
|
|
nxst::log::error("Failed to commit save with result 0x%08lX.", res);
|
|
return nxst::Result<std::string>::failure("Failed to commit to save device.");
|
|
}
|
|
|
|
blinkLed(4);
|
|
FileSystem::unmount();
|
|
|
|
nxst::log::info("Restore succeeded.");
|
|
return nxst::Result<std::string>::success(nameFromCell + "\nhas been restored successfully.");
|
|
}
|