/* * 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 . * * 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 #include #include #include #include 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 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 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::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::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::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::failure("Failed to finalise backup."); } refreshDirectories(title.id()); FileSystem::unmount(); nxst::log::info("Backup succeeded."); return nxst::Result::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 nsacd_buf(sizeof(NsApplicationControlData), 0); auto* nsacd = reinterpret_cast(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 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::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::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::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::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::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::failure("Failed to commit to save device."); } blinkLed(4); FileSystem::unmount(); nxst::log::info("Restore succeeded."); return nxst::Result::success(nameFromCell + "\nhas been restored successfully."); }