From a46a8dd378385398392fc00f3f9df1719f801ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 27 May 2018 01:59:23 +0200 Subject: [PATCH 1/7] WiiSave: Refactor import/export code The current WiiSave code is extremely messy, as it exposes all kinds of implementation details in the header (including internal struct definitions and magic numbers that don't have to be). The read/write code is intermingled, so it's hard to tell which members are used, or when/where they are set at all. It also implicitly relies on some functions being called in a specific order since it doesn't seek manually every time, which makes the code even more fragile. The logic is also hardcoded to only support bin->nand or nand->bin, even though it would be useful to support nand->nand (for the Movie save copying code, for example). This commit attempts to solve these problems by getting rid of the WiiSave class: * Read/write code is moved to new Storage classes (NandStorage and DataBinStorage) with small, clear functions that do one and only one thing. * The import/export logic was refactored into a generic Copy function that takes two storages as parameters. * The existing import and export functions are now just small wrappers that call Copy with the appropriate storages. --- Source/Core/Core/HW/WiiSave.cpp | 1004 +++++++++++++++---------------- Source/Core/Core/HW/WiiSave.h | 148 +---- 2 files changed, 522 insertions(+), 630 deletions(-) diff --git a/Source/Core/Core/HW/WiiSave.cpp b/Source/Core/Core/HW/WiiSave.cpp index cf376cb501..b75d4c97b2 100644 --- a/Source/Core/Core/HW/WiiSave.cpp +++ b/Source/Core/Core/HW/WiiSave.cpp @@ -9,13 +9,16 @@ #include "Core/HW/WiiSave.h" +#include +#include #include -#include #include #include #include #include +#include #include +#include #include #include "Common/Align.h" @@ -23,16 +26,20 @@ #include "Common/Crypto/ec.h" #include "Common/File.h" #include "Common/FileUtil.h" +#include "Common/Lazy.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/NandPaths.h" #include "Common/StringUtil.h" +#include "Common/Swap.h" #include "Core/CommonTitles.h" #include "Core/IOS/ES/ES.h" #include "Core/IOS/IOS.h" #include "Core/IOS/IOSC.h" #include "Core/IOS/Uids.h" +namespace WiiSave +{ using Md5 = std::array; constexpr std::array s_sd_initial_iv{{0x21, 0x67, 0x12, 0xE6, 0xAA, 0x1F, 0x68, 0x9F, @@ -41,525 +48,506 @@ constexpr Md5 s_md5_blanker{{0x0E, 0x65, 0x37, 0x81, 0x99, 0xBE, 0x45, 0x17, 0xA 0x45, 0x1A, 0x57, 0x93}}; constexpr u32 s_ng_id = 0x0403AC68; -bool WiiSave::Import(std::string filename) +enum { - IOS::HLE::Kernel ios; - WiiSave save_file{ios, std::move(filename)}; - return save_file.Import(); + BLOCK_SZ = 0x40, + ICON_SZ = 0x1200, + BNR_SZ = 0x60a0, + FULL_BNR_MIN = 0x72a0, // BNR_SZ + 1*ICON_SZ + FULL_BNR_MAX = 0xF0A0, // BNR_SZ + 8*ICON_SZ + BK_LISTED_SZ = 0x70, // Size before rounding to nearest block + SIG_SZ = 0x40, + FULL_CERT_SZ = 0x3C0, // SIG_SZ + NG_CERT_SZ + AP_CERT_SZ + 0x80? + + BK_HDR_MAGIC = 0x426B0001, + FILE_HDR_MAGIC = 0x03adf17e +}; + +#pragma pack(push, 1) +struct DataBinHeader +{ + Common::BigEndianValue tid; + Common::BigEndianValue banner_size; // (0x72A0 or 0xF0A0, also seen 0xBAA0) + u8 permissions; + u8 unk1; // maybe permissions is a be16 + std::array md5; // md5 of plaintext header with md5 blanker applied + Common::BigEndianValue unk2; +}; + +struct Header +{ + DataBinHeader hdr; + u8 banner[FULL_BNR_MAX]; +}; + +struct BkHeader +{ + Common::BigEndianValue size; // 0x00000070 + // u16 magic; // 'Bk' + // u16 magic2; // or version (0x0001) + Common::BigEndianValue magic; // 0x426B0001 + Common::BigEndianValue ngid; + Common::BigEndianValue number_of_files; + Common::BigEndianValue size_of_files; + Common::BigEndianValue unk1; + Common::BigEndianValue unk2; + Common::BigEndianValue total_size; + std::array unk3; + Common::BigEndianValue tid; + std::array mac_address; + std::array padding; +}; + +struct FileHDR +{ + Common::BigEndianValue magic; // 0x03adf17e + Common::BigEndianValue size; + u8 permissions; + u8 attrib; + u8 type; // (1=file, 2=directory) + std::array name; + std::array iv; + std::array unk; +}; +#pragma pack(pop) + +class Storage +{ +public: + struct SaveFile + { + u8 mode, attributes, type; + std::string path; + // Only valid for regular (i.e. non-directory) files. + Common::Lazy>> data; + }; + + virtual ~Storage() = default; + virtual std::optional
ReadHeader() = 0; + virtual std::optional ReadBkHeader() = 0; + virtual std::optional> ReadFiles() = 0; + virtual bool WriteHeader(const Header& header) = 0; + virtual bool WriteBkHeader(const BkHeader& bk_header) = 0; + virtual bool WriteFiles(const std::vector& files) = 0; +}; + +void StorageDeleter::operator()(Storage* p) const +{ + delete p; } -bool WiiSave::Export(u64 title_id, std::string export_path) +class NandStorage final : public Storage { - IOS::HLE::Kernel ios; - WiiSave export_save{ios, title_id, std::move(export_path)}; - return export_save.Export(); +public: + explicit NandStorage(u64 tid) : m_tid{tid} + { + m_wii_title_path = Common::GetTitleDataPath(tid, Common::FromWhichRoot::FROM_CONFIGURED_ROOT); + File::CreateFullPath(m_wii_title_path); + ScanForFiles(); + } + + std::optional
ReadHeader() override + { + Header header{}; + std::string banner_file_path = m_wii_title_path + "/banner.bin"; + u32 banner_size = static_cast(File::GetSize(banner_file_path)); + header.hdr.banner_size = banner_size; + header.hdr.tid = m_tid; + header.hdr.md5 = s_md5_blanker; + header.hdr.permissions = 0x3C; + + File::IOFile banner_file(banner_file_path, "rb"); + if (!banner_file.ReadBytes(header.banner, banner_size)) + return {}; + // remove nocopy flag + header.banner[7] &= ~1; + + Md5 md5_calc; + mbedtls_md5(reinterpret_cast(&header), sizeof(Header), md5_calc.data()); + header.hdr.md5 = std::move(md5_calc); + return header; + } + + std::optional ReadBkHeader() override + { + BkHeader bk_hdr{}; + bk_hdr.size = BK_LISTED_SZ; + bk_hdr.magic = BK_HDR_MAGIC; + bk_hdr.ngid = s_ng_id; + bk_hdr.number_of_files = static_cast(m_files_list.size()); + bk_hdr.size_of_files = m_files_size; + bk_hdr.total_size = m_files_size + FULL_CERT_SZ; + bk_hdr.tid = m_tid; + return bk_hdr; + } + + std::optional> ReadFiles() override + { + std::vector ret(m_files_list.size()); + std::transform(m_files_list.begin(), m_files_list.end(), ret.begin(), [this](const auto& path) { + const File::FileInfo file_info{path}; + SaveFile save_file; + save_file.mode = 0x3c; + save_file.attributes = 0; + save_file.type = file_info.IsDirectory() ? 2 : 1; + save_file.path = Common::UnescapeFileName(path.substr(m_wii_title_path.length() + 1)); + save_file.data = [path]() -> std::optional> { + File::IOFile file{path, "rb"}; + std::vector data(file.GetSize()); + if (!file || !file.ReadBytes(data.data(), data.size())) + return std::nullopt; + return data; + }; + return save_file; + }); + return ret; + } + + bool WriteHeader(const Header& header) override + { + const std::string banner_file_path = m_wii_title_path + "/banner.bin"; + if (!File::Exists(banner_file_path) || + AskYesNoT("%s already exists. Consider making a backup of the current save files before " + "overwriting.\nOverwrite now?", + banner_file_path.c_str())) + { + File::IOFile banner_file(banner_file_path, "wb"); + banner_file.WriteBytes(header.banner, header.hdr.banner_size); + return true; + } + return false; + } + + bool WriteBkHeader(const BkHeader& bk_header) override { return true; } + + bool WriteFiles(const std::vector& files) override + { + for (const SaveFile& file : files) + { + // Allows files in subfolders to be escaped properly (ex: "nocopy/data00") + // Special characters in path components will be escaped such as /../ + std::string file_path = Common::EscapePath(file.path); + std::string file_path_full = m_wii_title_path + '/' + file_path; + File::CreateFullPath(file_path_full); + + if (file.type == 1) + { + File::IOFile raw_save_file(file_path_full, "wb"); + const std::optional>& data = *file.data; + if (!data) + return false; + raw_save_file.WriteBytes(data->data(), data->size()); + } + else if (file.type == 2) + { + File::CreateDir(file_path_full); + if (!File::IsDirectory(file_path_full)) + return false; + } + } + return true; + } + +private: + void ScanForFiles() + { + std::vector directories; + directories.push_back(m_wii_title_path); + u32 size = 0; + + for (u32 i = 0; i < directories.size(); ++i) + { + if (i != 0) + { + // add dir to fst + m_files_list.push_back(directories[i]); + } + + File::FSTEntry fst_tmp = File::ScanDirectoryTree(directories[i], false); + for (const File::FSTEntry& elem : fst_tmp.children) + { + if (elem.virtualName != "banner.bin") + { + size += sizeof(FileHDR); + if (elem.isDirectory) + { + if (elem.virtualName == "nocopy" || elem.virtualName == "nomove") + { + NOTICE_LOG(CONSOLE, + "This save will likely require homebrew tools to copy to a real Wii."); + } + + directories.push_back(elem.physicalName); + } + else + { + m_files_list.push_back(elem.physicalName); + size += static_cast(Common::AlignUp(elem.size, BLOCK_SZ)); + } + } + } + } + m_files_size = size; + } + + u64 m_tid = 0; + std::string m_wii_title_path; + std::vector m_files_list; + u32 m_files_size = 0; +}; + +class DataBinStorage final : public Storage +{ +public: + explicit DataBinStorage(IOS::HLE::IOSC* iosc, const std::string& path, const char* mode) + : m_iosc{*iosc} + { + File::CreateFullPath(path); + m_file = File::IOFile{path, mode}; + } + + std::optional
ReadHeader() override + { + Header header; + if (!m_file.Seek(0, SEEK_SET) || !m_file.ReadArray(&header, 1)) + return {}; + + std::array iv = s_sd_initial_iv; + m_iosc.Decrypt(IOS::HLE::IOSC::HANDLE_SD_KEY, iv.data(), reinterpret_cast(&header), + sizeof(Header), reinterpret_cast(&header), IOS::PID_ES); + u32 banner_size = header.hdr.banner_size; + if ((banner_size < FULL_BNR_MIN) || (banner_size > FULL_BNR_MAX) || + (((banner_size - BNR_SZ) % ICON_SZ) != 0)) + { + ERROR_LOG(CONSOLE, "Not a Wii save or read failure for file header size %x", banner_size); + return {}; + } + + Md5 md5_file = header.hdr.md5; + header.hdr.md5 = s_md5_blanker; + Md5 md5_calc; + mbedtls_md5(reinterpret_cast(&header), sizeof(Header), md5_calc.data()); + if (md5_file != md5_calc) + { + ERROR_LOG(CONSOLE, "MD5 mismatch\n %016" PRIx64 "%016" PRIx64 " != %016" PRIx64 "%016" PRIx64, + Common::swap64(md5_file.data()), Common::swap64(md5_file.data() + 8), + Common::swap64(md5_calc.data()), Common::swap64(md5_calc.data() + 8)); + return {}; + } + return header; + } + + std::optional ReadBkHeader() override + { + BkHeader bk_header; + m_file.Seek(sizeof(Header), SEEK_SET); + if (!m_file.ReadArray(&bk_header, 1)) + return {}; + if (bk_header.size != BK_LISTED_SZ || bk_header.magic != BK_HDR_MAGIC) + return {}; + if (bk_header.size_of_files + FULL_CERT_SZ != bk_header.total_size) + return {}; + return bk_header; + } + + std::optional> ReadFiles() override + { + const std::optional bk_header = ReadBkHeader(); + if (!bk_header || !m_file.Seek(sizeof(Header) + sizeof(BkHeader), SEEK_SET)) + return {}; + + std::vector files; + for (u32 i = 0; i < bk_header->number_of_files; ++i) + { + SaveFile save_file; + FileHDR file_hdr; + if (!m_file.ReadArray(&file_hdr, 1) || file_hdr.magic != FILE_HDR_MAGIC) + return {}; + + save_file.mode = file_hdr.permissions; + save_file.attributes = file_hdr.attrib; + save_file.type = file_hdr.type; + save_file.path = file_hdr.name.data(); + if (file_hdr.type == 1) + { + const u32 rounded_size = Common::AlignUp(file_hdr.size, BLOCK_SZ); + const u64 pos = m_file.Tell(); + std::array iv = file_hdr.iv; + + save_file.data = [this, rounded_size, iv, pos]() mutable -> std::optional> { + std::vector file_data(rounded_size); + if (!m_file.Seek(pos, SEEK_SET) || !m_file.ReadBytes(file_data.data(), rounded_size)) + return {}; + + m_iosc.Decrypt(IOS::HLE::IOSC::HANDLE_SD_KEY, iv.data(), file_data.data(), rounded_size, + file_data.data(), IOS::PID_ES); + return file_data; + }; + m_file.Seek(pos + rounded_size, SEEK_SET); + } + files.emplace_back(std::move(save_file)); + } + return files; + } + + bool WriteHeader(const Header& header) override + { + Header encrypted_header; + std::array iv = s_sd_initial_iv; + m_iosc.Encrypt(IOS::HLE::IOSC::HANDLE_SD_KEY, iv.data(), reinterpret_cast(&header), + sizeof(Header), reinterpret_cast(&encrypted_header), IOS::PID_ES); + return m_file.Seek(0, SEEK_SET) && m_file.WriteArray(&encrypted_header, 1); + } + + bool WriteBkHeader(const BkHeader& bk_header) override + { + return m_file.Seek(sizeof(Header), SEEK_SET) && m_file.WriteArray(&bk_header, 1); + } + + bool WriteFiles(const std::vector& files) override + { + if (!m_file.Seek(sizeof(Header) + sizeof(BkHeader), SEEK_SET)) + return false; + + for (const SaveFile& save_file : files) + { + FileHDR file_hdr{}; + file_hdr.magic = FILE_HDR_MAGIC; + file_hdr.permissions = save_file.mode; + file_hdr.attrib = save_file.attributes; + file_hdr.type = save_file.type; + if (save_file.path.length() > 0x44) + return false; + std::strncpy(file_hdr.name.data(), save_file.path.data(), file_hdr.name.size()); + + std::optional> data; + if (file_hdr.type == 1) + { + data = *save_file.data; + if (!data) + return false; + file_hdr.size = static_cast(data->size()); + } + + if (!m_file.WriteArray(&file_hdr, 1)) + return false; + + if (data) + { + std::vector file_data_enc(Common::AlignUp(data->size(), BLOCK_SZ)); + std::copy(data->cbegin(), data->cend(), file_data_enc.begin()); + m_iosc.Encrypt(IOS::HLE::IOSC::HANDLE_SD_KEY, file_hdr.iv.data(), file_data_enc.data(), + file_data_enc.size(), file_data_enc.data(), IOS::PID_ES); + if (!m_file.WriteBytes(file_data_enc.data(), file_data_enc.size())) + return false; + } + } + + if (!WriteSignatures()) + { + ERROR_LOG(CORE, "WiiSave::WriteFiles: Failed to write signatures"); + return false; + } + return true; + } + +private: + bool WriteSignatures() + { + const std::optional bk_header = ReadBkHeader(); + if (!bk_header) + return false; + + // Read data to sign. + const u32 data_size = bk_header->size_of_files + 0x80; + auto data = std::make_unique(data_size); + m_file.Seek(0xf0c0, SEEK_SET); + if (!m_file.ReadBytes(data.get(), data_size)) + return false; + + // Sign the data. + IOS::CertECC ap_cert; + Common::ec::Signature ap_sig; + m_iosc.Sign(ap_sig.data(), reinterpret_cast(&ap_cert), Titles::SYSTEM_MENU, data.get(), + data_size); + + // Write signatures. + if (!m_file.Seek(0, SEEK_END)) + return false; + const u32 SIGNATURE_END_MAGIC = Common::swap32(0x2f536969); + const IOS::CertECC device_certificate = m_iosc.GetDeviceCertificate(); + return m_file.WriteArray(ap_sig.data(), ap_sig.size()) && + m_file.WriteArray(&SIGNATURE_END_MAGIC, 1) && + m_file.WriteArray(&device_certificate, 1) && m_file.WriteArray(&ap_cert, 1); + } + + IOS::HLE::IOSC& m_iosc; + File::IOFile m_file; +}; + +StoragePointer MakeNandStorage(IOS::HLE::FS::FileSystem* fs, u64 tid) +{ + // fs parameter is not used yet but will be after WiiSave is migrated to the new FS interface. + return StoragePointer{new NandStorage{tid}}; } -size_t WiiSave::ExportAll(std::string export_path) +StoragePointer MakeDataBinStorage(IOS::HLE::IOSC* iosc, const std::string& path, const char* mode) +{ + return StoragePointer{new DataBinStorage{iosc, path, mode}}; +} + +template +static bool Copy(const char* description, Storage* source, std::optional (Storage::*read_fn)(), + Storage* dest, bool (Storage::*write_fn)(const T&)) +{ + const std::optional data = (source->*read_fn)(); + if (data && (dest->*write_fn)(*data)) + return true; + ERROR_LOG(CORE, "WiiSave::Copy: Failed to %s %s", !data ? "read" : "write", description); + return false; +} + +bool Copy(Storage* source, Storage* dest) +{ + return Copy("header", source, &Storage::ReadHeader, dest, &Storage::WriteHeader) && + Copy("bk header", source, &Storage::ReadBkHeader, dest, &Storage::WriteBkHeader) && + Copy("files", source, &Storage::ReadFiles, dest, &Storage::WriteFiles); +} + +bool Import(const std::string& data_bin_path) +{ + IOS::HLE::Kernel ios; + const auto data_bin = MakeDataBinStorage(&ios.GetIOSC(), data_bin_path, "rb"); + if (const std::optional
header = data_bin->ReadHeader()) + return Copy(data_bin.get(), MakeNandStorage(ios.GetFS().get(), header->hdr.tid).get()); + ERROR_LOG(CORE, "WiiSave::Import: Failed to read header"); + return false; +} + +static bool Export(u64 tid, const std::string& export_path, IOS::HLE::Kernel* ios) +{ + std::string path = StringFromFormat("%s/private/wii/title/%c%c%c%c/data.bin", export_path.c_str(), + static_cast(tid >> 24), static_cast(tid >> 16), + static_cast(tid >> 8), static_cast(tid)); + return Copy(MakeNandStorage(ios->GetFS().get(), tid).get(), + MakeDataBinStorage(&ios->GetIOSC(), path, "w+b").get()); +} + +bool Export(u64 tid, const std::string& export_path) +{ + IOS::HLE::Kernel ios; + return Export(tid, export_path, &ios); +} + +size_t ExportAll(const std::string& export_path) { IOS::HLE::Kernel ios; size_t exported_save_count = 0; for (const u64 title : ios.GetES()->GetInstalledTitles()) { - WiiSave export_save{ios, title, export_path}; - if (export_save.Export()) + if (Export(title, export_path, &ios)) ++exported_save_count; } return exported_save_count; } - -WiiSave::WiiSave(IOS::HLE::Kernel& ios, std::string filename) - : m_ios{ios}, m_sd_iv{s_sd_initial_iv}, - m_encrypted_save_path(std::move(filename)), m_valid{true} -{ -} - -bool WiiSave::Import() -{ - ReadHDR(); - ReadBKHDR(); - ImportWiiSaveFiles(); - // TODO: check_sig() - return m_valid; -} - -WiiSave::WiiSave(IOS::HLE::Kernel& ios, u64 title_id, std::string export_path) - : m_ios{ios}, m_sd_iv{s_sd_initial_iv}, - m_encrypted_save_path(std::move(export_path)), m_title_id{title_id} -{ - if (getPaths(true)) - m_valid = true; -} - -bool WiiSave::Export() -{ - WriteHDR(); - WriteBKHDR(); - ExportWiiSaveFiles(); - do_sig(); - return m_valid; -} - -void WiiSave::ReadHDR() -{ - File::IOFile data_file(m_encrypted_save_path, "rb"); - if (!data_file) - { - ERROR_LOG(CONSOLE, "Cannot open %s", m_encrypted_save_path.c_str()); - m_valid = false; - return; - } - if (!data_file.ReadBytes(&m_encrypted_header, HEADER_SZ)) - { - ERROR_LOG(CONSOLE, "Failed to read header"); - m_valid = false; - return; - } - data_file.Close(); - - m_ios.GetIOSC().Decrypt(IOS::HLE::IOSC::HANDLE_SD_KEY, m_sd_iv.data(), - reinterpret_cast(&m_encrypted_header), HEADER_SZ, - reinterpret_cast(&m_header), IOS::PID_ES); - u32 banner_size = m_header.hdr.banner_size; - if ((banner_size < FULL_BNR_MIN) || (banner_size > FULL_BNR_MAX) || - (((banner_size - BNR_SZ) % ICON_SZ) != 0)) - { - ERROR_LOG(CONSOLE, "Not a Wii save or read failure for file header size %x", banner_size); - m_valid = false; - return; - } - m_title_id = m_header.hdr.save_game_title; - - Md5 md5_file = m_header.hdr.md5; - m_header.hdr.md5 = s_md5_blanker; - Md5 md5_calc; - mbedtls_md5((u8*)&m_header, HEADER_SZ, md5_calc.data()); - if (md5_file != md5_calc) - { - ERROR_LOG(CONSOLE, "MD5 mismatch\n %016" PRIx64 "%016" PRIx64 " != %016" PRIx64 "%016" PRIx64, - Common::swap64(md5_file.data()), Common::swap64(md5_file.data() + 8), - Common::swap64(md5_calc.data()), Common::swap64(md5_calc.data() + 8)); - m_valid = false; - } - - if (!getPaths()) - { - m_valid = false; - return; - } - std::string banner_file_path = m_wii_title_path + "/banner.bin"; - if (!File::Exists(banner_file_path) || - AskYesNoT("%s already exists. Consider making a backup of the current save files before " - "overwriting.\nOverwrite now?", - banner_file_path.c_str())) - { - INFO_LOG(CONSOLE, "Creating file %s", banner_file_path.c_str()); - File::IOFile banner_file(banner_file_path, "wb"); - banner_file.WriteBytes(m_header.banner, banner_size); - } - else - { - m_valid = false; - } -} - -void WiiSave::WriteHDR() -{ - if (!m_valid) - return; - memset(&m_header, 0, HEADER_SZ); - - std::string banner_file_path = m_wii_title_path + "/banner.bin"; - u32 banner_size = static_cast(File::GetSize(banner_file_path)); - m_header.hdr.banner_size = banner_size; - - m_header.hdr.save_game_title = m_title_id; - m_header.hdr.md5 = s_md5_blanker; - m_header.hdr.permissions = 0x3C; - - File::IOFile banner_file(banner_file_path, "rb"); - if (!banner_file.ReadBytes(m_header.banner, banner_size)) - { - ERROR_LOG(CONSOLE, "Failed to read banner.bin"); - m_valid = false; - return; - } - // remove nocopy flag - m_header.banner[7] &= ~1; - - Md5 md5_calc; - mbedtls_md5((u8*)&m_header, HEADER_SZ, md5_calc.data()); - m_header.hdr.md5 = std::move(md5_calc); - - m_ios.GetIOSC().Encrypt(IOS::HLE::IOSC::HANDLE_SD_KEY, m_sd_iv.data(), - reinterpret_cast(&m_header), HEADER_SZ, - reinterpret_cast(&m_encrypted_header), IOS::PID_ES); - - File::IOFile data_file(m_encrypted_save_path, "wb"); - if (!data_file.WriteBytes(&m_encrypted_header, HEADER_SZ)) - { - ERROR_LOG(CONSOLE, "Failed to write header for %s", m_encrypted_save_path.c_str()); - m_valid = false; - } -} - -void WiiSave::ReadBKHDR() -{ - if (!m_valid) - return; - - File::IOFile fpData_bin(m_encrypted_save_path, "rb"); - if (!fpData_bin) - { - ERROR_LOG(CONSOLE, "Cannot open %s", m_encrypted_save_path.c_str()); - m_valid = false; - return; - } - fpData_bin.Seek(HEADER_SZ, SEEK_SET); - if (!fpData_bin.ReadBytes(&m_bk_hdr, BK_SZ)) - { - ERROR_LOG(CONSOLE, "Failed to read bk header"); - m_valid = false; - return; - } - fpData_bin.Close(); - - if (m_bk_hdr.size != BK_LISTED_SZ || m_bk_hdr.magic != BK_HDR_MAGIC) - { - ERROR_LOG(CONSOLE, "Invalid Size(%x) or Magic word (%x)", u32(m_bk_hdr.size), - u32(m_bk_hdr.magic)); - m_valid = false; - return; - } - - if (m_bk_hdr.size_of_files + FULL_CERT_SZ != m_bk_hdr.total_size) - { - WARN_LOG(CONSOLE, "Size(%x) + cert(%x) does not equal totalsize(%x)", - u32(m_bk_hdr.size_of_files), FULL_CERT_SZ, u32(m_bk_hdr.total_size)); - } - if (m_title_id != m_bk_hdr.save_game_title) - { - WARN_LOG(CONSOLE, - "Encrypted title (%" PRIx64 ") does not match unencrypted title (%" PRIx64 ")", - m_title_id, u64(m_bk_hdr.save_game_title)); - } -} - -void WiiSave::WriteBKHDR() -{ - if (!m_valid) - return; - u32 number_of_files = 0, size_of_files = 0; - ScanForFiles(m_wii_title_path, m_files_list, &number_of_files, &size_of_files); - memset(&m_bk_hdr, 0, BK_SZ); - m_bk_hdr.size = BK_LISTED_SZ; - m_bk_hdr.magic = BK_HDR_MAGIC; - m_bk_hdr.ngid = s_ng_id; - m_bk_hdr.number_of_files = number_of_files; - m_bk_hdr.size_of_files = size_of_files; - m_bk_hdr.total_size = size_of_files + FULL_CERT_SZ; - m_bk_hdr.save_game_title = m_title_id; - - File::IOFile data_file(m_encrypted_save_path, "ab"); - if (!data_file.WriteBytes(&m_bk_hdr, BK_SZ)) - { - ERROR_LOG(CONSOLE, "Failed to write bkhdr"); - m_valid = false; - } -} - -void WiiSave::ImportWiiSaveFiles() -{ - if (!m_valid) - return; - - File::IOFile data_file(m_encrypted_save_path, "rb"); - if (!data_file) - { - ERROR_LOG(CONSOLE, "Cannot open %s", m_encrypted_save_path.c_str()); - m_valid = false; - return; - } - - data_file.Seek(HEADER_SZ + BK_SZ, SEEK_SET); - - FileHDR file_hdr_tmp; - - for (u32 i = 0; i < m_bk_hdr.number_of_files; ++i) - { - memset(&file_hdr_tmp, 0, FILE_HDR_SZ); - m_iv.fill(0); - - if (!data_file.ReadBytes(&file_hdr_tmp, FILE_HDR_SZ)) - { - ERROR_LOG(CONSOLE, "Failed to read header for file %d", i); - m_valid = false; - } - - if (file_hdr_tmp.magic != FILE_HDR_MAGIC) - { - ERROR_LOG(CONSOLE, "Bad File Header"); - break; - } - else - { - // Allows files in subfolders to be escaped properly (ex: "nocopy/data00") - // Special characters in path components will be escaped such as /../ - std::string file_path = Common::EscapePath(file_hdr_tmp.name.data()); - - std::string file_path_full = m_wii_title_path + '/' + file_path; - File::CreateFullPath(file_path_full); - const File::FileInfo file_info(file_path_full); - if (file_hdr_tmp.type == 1) - { - u32 file_size_rounded = Common::AlignUp(file_hdr_tmp.size, BLOCK_SZ); - std::vector file_data(file_size_rounded); - std::vector file_data_enc(file_size_rounded); - - if (!data_file.ReadBytes(file_data_enc.data(), file_size_rounded)) - { - ERROR_LOG(CONSOLE, "Failed to read data from file %d", i); - m_valid = false; - break; - } - - m_iv = file_hdr_tmp.iv; - m_ios.GetIOSC().Decrypt(IOS::HLE::IOSC::HANDLE_SD_KEY, m_iv.data(), file_data_enc.data(), - file_size_rounded, file_data.data(), IOS::PID_ES); - - INFO_LOG(CONSOLE, "Creating file %s", file_path_full.c_str()); - - File::IOFile raw_save_file(file_path_full, "wb"); - raw_save_file.WriteBytes(file_data.data(), file_hdr_tmp.size); - } - else if (file_hdr_tmp.type == 2) - { - if (!file_info.Exists()) - { - if (!File::CreateDir(file_path_full)) - ERROR_LOG(CONSOLE, "Failed to create directory %s", file_path_full.c_str()); - } - else if (!file_info.IsDirectory()) - { - ERROR_LOG(CONSOLE, - "Failed to create directory %s because a file with the same name exists", - file_path_full.c_str()); - } - } - } - } -} - -void WiiSave::ExportWiiSaveFiles() -{ - if (!m_valid) - return; - - for (u32 i = 0; i < m_bk_hdr.number_of_files; i++) - { - FileHDR file_hdr_tmp; - memset(&file_hdr_tmp, 0, FILE_HDR_SZ); - - u32 file_size = 0; - const File::FileInfo file_info(m_files_list[i]); - if (file_info.IsDirectory()) - { - file_hdr_tmp.type = 2; - } - else - { - file_size = static_cast(file_info.GetSize()); - file_hdr_tmp.type = 1; - } - - u32 file_size_rounded = Common::AlignUp(file_size, BLOCK_SZ); - file_hdr_tmp.magic = FILE_HDR_MAGIC; - file_hdr_tmp.size = file_size; - file_hdr_tmp.permissions = 0x3c; - - std::string name = - Common::UnescapeFileName(m_files_list[i].substr(m_wii_title_path.length() + 1)); - - if (name.length() > 0x44) - { - ERROR_LOG(CONSOLE, "\"%s\" is too long for the filename, max length is 0x44 + \\0", - name.c_str()); - m_valid = false; - return; - } - std::strncpy(file_hdr_tmp.name.data(), name.c_str(), file_hdr_tmp.name.size()); - - { - File::IOFile fpData_bin(m_encrypted_save_path, "ab"); - fpData_bin.WriteBytes(&file_hdr_tmp, FILE_HDR_SZ); - } - - if (file_hdr_tmp.type == 1) - { - if (file_size == 0) - { - ERROR_LOG(CONSOLE, "%s is a 0 byte file", m_files_list[i].c_str()); - m_valid = false; - return; - } - File::IOFile raw_save_file(m_files_list[i], "rb"); - if (!raw_save_file) - { - ERROR_LOG(CONSOLE, "%s failed to open", m_files_list[i].c_str()); - m_valid = false; - } - - std::vector file_data(file_size_rounded); - std::vector file_data_enc(file_size_rounded); - - if (!raw_save_file.ReadBytes(file_data.data(), file_size)) - { - ERROR_LOG(CONSOLE, "Failed to read data from file: %s", m_files_list[i].c_str()); - m_valid = false; - } - - m_ios.GetIOSC().Encrypt(IOS::HLE::IOSC::HANDLE_SD_KEY, file_hdr_tmp.iv.data(), - file_data.data(), file_size_rounded, file_data_enc.data(), - IOS::PID_ES); - - File::IOFile fpData_bin(m_encrypted_save_path, "ab"); - if (!fpData_bin.WriteBytes(file_data_enc.data(), file_size_rounded)) - { - ERROR_LOG(CONSOLE, "Failed to write data to file: %s", m_encrypted_save_path.c_str()); - } - } - } -} - -void WiiSave::do_sig() -{ - if (!m_valid) - return; - - File::IOFile data_file(m_encrypted_save_path, "rb"); - if (!data_file) - { - m_valid = false; - return; - } - - // Read data to sign. - const u32 data_size = m_bk_hdr.size_of_files + 0x80; - auto data = std::make_unique(data_size); - data_file.Seek(0xf0c0, SEEK_SET); - if (!data_file.ReadBytes(data.get(), data_size)) - { - m_valid = false; - return; - } - - // Sign the data. - IOS::CertECC ap_cert; - Common::ec::Signature ap_sig; - m_ios.GetIOSC().Sign(ap_sig.data(), reinterpret_cast(&ap_cert), Titles::SYSTEM_MENU, - data.get(), data_size); - - // Write signatures. - data_file.Open(m_encrypted_save_path, "ab"); - if (!data_file) - { - m_valid = false; - return; - } - - data_file.WriteArray(ap_sig.data(), ap_sig.size()); - const u32 SIGNATURE_END_MAGIC = Common::swap32(0x2f536969); - data_file.WriteArray(&SIGNATURE_END_MAGIC, 1); - const IOS::CertECC device_certificate = m_ios.GetIOSC().GetDeviceCertificate(); - data_file.WriteArray(&device_certificate, 1); - data_file.WriteArray(&ap_cert, 1); - - m_valid = data_file.IsGood(); -} - -bool WiiSave::getPaths(bool for_export) -{ - if (m_title_id) - { - // CONFIGURED because this whole class is only used from the GUI, not directly by games. - m_wii_title_path = Common::GetTitleDataPath(m_title_id, Common::FROM_CONFIGURED_ROOT); - } - - if (for_export) - { - char game_id[5]; - sprintf(game_id, "%c%c%c%c", (u8)(m_title_id >> 24) & 0xFF, (u8)(m_title_id >> 16) & 0xFF, - (u8)(m_title_id >> 8) & 0xFF, (u8)m_title_id & 0xFF); - - if (!File::IsDirectory(m_wii_title_path)) - { - m_valid = false; - ERROR_LOG(CONSOLE, "No save folder found for title %s", game_id); - return false; - } - - if (!File::Exists(m_wii_title_path + "/banner.bin")) - { - m_valid = false; - ERROR_LOG(CONSOLE, "No banner file found for title %s", game_id); - return false; - } - m_encrypted_save_path += StringFromFormat("/private/wii/title/%s/data.bin", game_id); - File::CreateFullPath(m_encrypted_save_path); - } - else - { - File::CreateFullPath(m_wii_title_path); - } - return true; -} - -void WiiSave::ScanForFiles(const std::string& save_directory, std::vector& file_list, - u32* num_files, u32* size_files) -{ - std::vector directories; - directories.push_back(save_directory); - u32 num = 0; - u32 size = 0; - - for (u32 i = 0; i < directories.size(); ++i) - { - if (i != 0) - { - // add dir to fst - file_list.push_back(directories[i]); - } - - File::FSTEntry fst_tmp = File::ScanDirectoryTree(directories[i], false); - for (const File::FSTEntry& elem : fst_tmp.children) - { - if (elem.virtualName != "banner.bin") - { - num++; - size += FILE_HDR_SZ; - if (elem.isDirectory) - { - if (elem.virtualName == "nocopy" || elem.virtualName == "nomove") - { - NOTICE_LOG(CONSOLE, - "This save will likely require homebrew tools to copy to a real Wii."); - } - - directories.push_back(elem.physicalName); - } - else - { - file_list.push_back(elem.physicalName); - size += static_cast(Common::AlignUp(elem.size, BLOCK_SZ)); - } - } - } - } - - *num_files = num; - *size_files = size; -} - -WiiSave::~WiiSave() -{ -} +} // namespace WiiSave diff --git a/Source/Core/Core/HW/WiiSave.h b/Source/Core/Core/HW/WiiSave.h index c729409a1e..1ae03ed46a 100644 --- a/Source/Core/Core/HW/WiiSave.h +++ b/Source/Core/Core/HW/WiiSave.h @@ -4,138 +4,42 @@ #pragma once -#include +#include +#include #include -#include -#include #include "Common/CommonTypes.h" -#include "Common/Swap.h" namespace IOS { namespace HLE { -class Kernel; +namespace FS +{ +class FileSystem; +} +class IOSC; } } // namespace IOS::HLE -class WiiSave +namespace WiiSave { -public: - /// Import a save into the NAND from a .bin file. - static bool Import(std::string filename); - /// Export a save to a .bin file. - static bool Export(u64 title_id, std::string export_path); - /// Export all saves that are in the NAND. Returns the number of exported saves. - static size_t ExportAll(std::string export_path); - -private: - WiiSave(IOS::HLE::Kernel& ios, std::string filename); - WiiSave(IOS::HLE::Kernel& ios, u64 title_id, std::string export_path); - ~WiiSave(); - - bool Import(); - bool Export(); - - void ReadHDR(); - void ReadBKHDR(); - void WriteHDR(); - void WriteBKHDR(); - void ImportWiiSaveFiles(); - void ExportWiiSaveFiles(); - void do_sig(); - bool getPaths(bool for_export = false); - void ScanForFiles(const std::string& save_directory, std::vector& file_list, - u32* num_files, u32* size_files); - - IOS::HLE::Kernel& m_ios; - - std::array m_sd_iv; - std::vector m_files_list; - - std::string m_encrypted_save_path; - - std::string m_wii_title_path; - - std::array m_iv; - - u64 m_title_id; - - bool m_valid; - - enum - { - BLOCK_SZ = 0x40, - HDR_SZ = 0x20, - ICON_SZ = 0x1200, - BNR_SZ = 0x60a0, - FULL_BNR_MIN = 0x72a0, // BNR_SZ + 1*ICON_SZ - FULL_BNR_MAX = 0xF0A0, // BNR_SZ + 8*ICON_SZ - HEADER_SZ = 0xF0C0, // HDR_SZ + FULL_BNR_MAX - BK_LISTED_SZ = 0x70, // Size before rounding to nearest block - BK_SZ = 0x80, - FILE_HDR_SZ = 0x80, - - SIG_SZ = 0x40, - NG_CERT_SZ = 0x180, - AP_CERT_SZ = 0x180, - FULL_CERT_SZ = 0x3C0, // SIG_SZ + NG_CERT_SZ + AP_CERT_SZ + 0x80? - - BK_HDR_MAGIC = 0x426B0001, - FILE_HDR_MAGIC = 0x03adf17e - }; - -#pragma pack(push, 1) - - struct DataBinHeader // encrypted - { - Common::BigEndianValue save_game_title; - Common::BigEndianValue banner_size; // (0x72A0 or 0xF0A0, also seen 0xBAA0) - u8 permissions; - u8 unk1; // maybe permissions is a be16 - std::array md5; // md5 of plaintext header with md5 blanker applied - Common::BigEndianValue unk2; - }; - - struct Header - { - DataBinHeader hdr; - u8 banner[FULL_BNR_MAX]; - }; - - struct BkHeader // Not encrypted - { - Common::BigEndianValue size; // 0x00000070 - // u16 magic; // 'Bk' - // u16 magic2; // or version (0x0001) - Common::BigEndianValue magic; // 0x426B0001 - Common::BigEndianValue ngid; - Common::BigEndianValue number_of_files; - Common::BigEndianValue size_of_files; - Common::BigEndianValue unk1; - Common::BigEndianValue unk2; - Common::BigEndianValue total_size; - std::array unk3; - Common::BigEndianValue save_game_title; - std::array mac_address; - std::array padding; - }; - - struct FileHDR // encrypted - { - Common::BigEndianValue magic; // 0x03adf17e - Common::BigEndianValue size; - u8 permissions; - u8 attrib; - u8 type; // (1=file, 2=directory) - std::array name; - std::array iv; - std::array unk; - }; -#pragma pack(pop) - - Header m_header; - Header m_encrypted_header; - BkHeader m_bk_hdr; +class Storage; +struct StorageDeleter +{ + void operator()(Storage* p) const; }; + +using StoragePointer = std::unique_ptr; +StoragePointer MakeNandStorage(IOS::HLE::FS::FileSystem* fs, u64 tid); +StoragePointer MakeDataBinStorage(IOS::HLE::IOSC* iosc, const std::string& path, const char* mode); + +bool Copy(Storage* source, Storage* destination); + +/// Import a save into the NAND from a .bin file. +bool Import(const std::string& data_bin_path); +/// Export a save to a .bin file. +bool Export(u64 tid, const std::string& export_path); +/// Export all saves that are in the NAND. Returns the number of exported saves. +size_t ExportAll(const std::string& export_path); +} // namespace WiiSave From 94953670f2936c28347c09ca49d3489f1d365be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 1 Jun 2018 00:10:55 +0200 Subject: [PATCH 2/7] WiiSave: Move overwrite prompt to Import WriteHeader should just write the header and not do anything else. --- Source/Core/Core/HW/WiiSave.cpp | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Source/Core/Core/HW/WiiSave.cpp b/Source/Core/Core/HW/WiiSave.cpp index b75d4c97b2..e274c222bd 100644 --- a/Source/Core/Core/HW/WiiSave.cpp +++ b/Source/Core/Core/HW/WiiSave.cpp @@ -123,6 +123,7 @@ public: }; virtual ~Storage() = default; + virtual bool SaveExists() { return true; } virtual std::optional
ReadHeader() = 0; virtual std::optional ReadBkHeader() = 0; virtual std::optional> ReadFiles() = 0; @@ -146,6 +147,8 @@ public: ScanForFiles(); } + bool SaveExists() override { return File::Exists(m_wii_title_path + "/banner.bin"); } + std::optional
ReadHeader() override { Header header{}; @@ -205,17 +208,8 @@ public: bool WriteHeader(const Header& header) override { - const std::string banner_file_path = m_wii_title_path + "/banner.bin"; - if (!File::Exists(banner_file_path) || - AskYesNoT("%s already exists. Consider making a backup of the current save files before " - "overwriting.\nOverwrite now?", - banner_file_path.c_str())) - { - File::IOFile banner_file(banner_file_path, "wb"); - banner_file.WriteBytes(header.banner, header.hdr.banner_size); - return true; - } - return false; + File::IOFile banner_file(m_wii_title_path + "/banner.bin", "wb"); + return banner_file.WriteBytes(header.banner, header.hdr.banner_size); } bool WriteBkHeader(const BkHeader& bk_header) override { return true; } @@ -518,10 +512,19 @@ bool Import(const std::string& data_bin_path) { IOS::HLE::Kernel ios; const auto data_bin = MakeDataBinStorage(&ios.GetIOSC(), data_bin_path, "rb"); - if (const std::optional
header = data_bin->ReadHeader()) - return Copy(data_bin.get(), MakeNandStorage(ios.GetFS().get(), header->hdr.tid).get()); - ERROR_LOG(CORE, "WiiSave::Import: Failed to read header"); - return false; + const std::optional
header = data_bin->ReadHeader(); + if (!header) + { + ERROR_LOG(CORE, "WiiSave::Import: Failed to read header"); + return false; + } + const auto nand = MakeNandStorage(ios.GetFS().get(), header->hdr.tid); + if (nand->SaveExists() && !AskYesNoT("Save data for this title already exists. Consider backing " + "up the current data before overwriting.\nOverwrite now?")) + { + return false; + } + return Copy(data_bin.get(), nand.get()); } static bool Export(u64 tid, const std::string& export_path, IOS::HLE::Kernel* ios) From 8af16fdd2849023a6545b1b79b5f12077f2dee46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 1 Jun 2018 18:58:34 +0200 Subject: [PATCH 3/7] WiiSave: Use an enum class for save file type --- Source/Core/Core/HW/WiiSave.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Source/Core/Core/HW/WiiSave.cpp b/Source/Core/Core/HW/WiiSave.cpp index e274c222bd..0814448df0 100644 --- a/Source/Core/Core/HW/WiiSave.cpp +++ b/Source/Core/Core/HW/WiiSave.cpp @@ -116,7 +116,13 @@ class Storage public: struct SaveFile { - u8 mode, attributes, type; + enum class Type : u8 + { + File = 1, + Directory = 2, + }; + u8 mode, attributes; + Type type; std::string path; // Only valid for regular (i.e. non-directory) files. Common::Lazy>> data; @@ -192,7 +198,7 @@ public: SaveFile save_file; save_file.mode = 0x3c; save_file.attributes = 0; - save_file.type = file_info.IsDirectory() ? 2 : 1; + save_file.type = file_info.IsDirectory() ? SaveFile::Type::Directory : SaveFile::Type::File; save_file.path = Common::UnescapeFileName(path.substr(m_wii_title_path.length() + 1)); save_file.data = [path]() -> std::optional> { File::IOFile file{path, "rb"}; @@ -224,7 +230,7 @@ public: std::string file_path_full = m_wii_title_path + '/' + file_path; File::CreateFullPath(file_path_full); - if (file.type == 1) + if (file.type == SaveFile::Type::File) { File::IOFile raw_save_file(file_path_full, "wb"); const std::optional>& data = *file.data; @@ -232,7 +238,7 @@ public: return false; raw_save_file.WriteBytes(data->data(), data->size()); } - else if (file.type == 2) + else if (file.type == SaveFile::Type::Directory) { File::CreateDir(file_path_full); if (!File::IsDirectory(file_path_full)) @@ -360,9 +366,12 @@ public: save_file.mode = file_hdr.permissions; save_file.attributes = file_hdr.attrib; - save_file.type = file_hdr.type; + const SaveFile::Type type = static_cast(file_hdr.type); + if (type != SaveFile::Type::Directory && type != SaveFile::Type::File) + return {}; + save_file.type = type; save_file.path = file_hdr.name.data(); - if (file_hdr.type == 1) + if (type == SaveFile::Type::File) { const u32 rounded_size = Common::AlignUp(file_hdr.size, BLOCK_SZ); const u64 pos = m_file.Tell(); @@ -409,7 +418,7 @@ public: file_hdr.magic = FILE_HDR_MAGIC; file_hdr.permissions = save_file.mode; file_hdr.attrib = save_file.attributes; - file_hdr.type = save_file.type; + file_hdr.type = static_cast(save_file.type); if (save_file.path.length() > 0x44) return false; std::strncpy(file_hdr.name.data(), save_file.path.data(), file_hdr.name.size()); From 4df266f943c5a7fada40d5aaf0816e5f938d836e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 1 Jun 2018 20:25:56 +0200 Subject: [PATCH 4/7] WiiSave: Use the correct length for paths Paths can never exceed 0x40 characters as this is the maximum length that is allowed by IOS (and probably the common FS library too). --- Source/Core/Core/HW/WiiSave.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Source/Core/Core/HW/WiiSave.cpp b/Source/Core/Core/HW/WiiSave.cpp index 0814448df0..bb7494ef5e 100644 --- a/Source/Core/Core/HW/WiiSave.cpp +++ b/Source/Core/Core/HW/WiiSave.cpp @@ -105,10 +105,12 @@ struct FileHDR u8 permissions; u8 attrib; u8 type; // (1=file, 2=directory) - std::array name; + std::array name; + std::array padding; std::array iv; std::array unk; }; +static_assert(sizeof(FileHDR) == 0x80, "FileHDR has an incorrect size"); #pragma pack(pop) class Storage @@ -370,7 +372,8 @@ public: if (type != SaveFile::Type::Directory && type != SaveFile::Type::File) return {}; save_file.type = type; - save_file.path = file_hdr.name.data(); + save_file.path = + std::string{file_hdr.name.data(), strnlen(file_hdr.name.data(), file_hdr.name.size())}; if (type == SaveFile::Type::File) { const u32 rounded_size = Common::AlignUp(file_hdr.size, BLOCK_SZ); @@ -419,7 +422,7 @@ public: file_hdr.permissions = save_file.mode; file_hdr.attrib = save_file.attributes; file_hdr.type = static_cast(save_file.type); - if (save_file.path.length() > 0x44) + if (save_file.path.length() > file_hdr.name.size()) return false; std::strncpy(file_hdr.name.data(), save_file.path.data(), file_hdr.name.size()); From 8eafd1928ef08e95a6001e78420393fb63167e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 1 Jun 2018 20:41:52 +0200 Subject: [PATCH 5/7] WiiSave: Move user interaction to UI frontends --- Source/Core/Core/HW/WiiSave.cpp | 8 ++------ Source/Core/Core/HW/WiiSave.h | 3 ++- Source/Core/DolphinQt2/MenuBar.cpp | 13 +++++++++++-- Source/Core/DolphinWX/FrameTools.cpp | 8 +++++++- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Source/Core/Core/HW/WiiSave.cpp b/Source/Core/Core/HW/WiiSave.cpp index bb7494ef5e..1ffe9d36f2 100644 --- a/Source/Core/Core/HW/WiiSave.cpp +++ b/Source/Core/Core/HW/WiiSave.cpp @@ -28,7 +28,6 @@ #include "Common/FileUtil.h" #include "Common/Lazy.h" #include "Common/Logging/Log.h" -#include "Common/MsgHandler.h" #include "Common/NandPaths.h" #include "Common/StringUtil.h" #include "Common/Swap.h" @@ -520,7 +519,7 @@ bool Copy(Storage* source, Storage* dest) Copy("files", source, &Storage::ReadFiles, dest, &Storage::WriteFiles); } -bool Import(const std::string& data_bin_path) +bool Import(const std::string& data_bin_path, std::function can_overwrite) { IOS::HLE::Kernel ios; const auto data_bin = MakeDataBinStorage(&ios.GetIOSC(), data_bin_path, "rb"); @@ -531,11 +530,8 @@ bool Import(const std::string& data_bin_path) return false; } const auto nand = MakeNandStorage(ios.GetFS().get(), header->hdr.tid); - if (nand->SaveExists() && !AskYesNoT("Save data for this title already exists. Consider backing " - "up the current data before overwriting.\nOverwrite now?")) - { + if (nand->SaveExists() && !can_overwrite()) return false; - } return Copy(data_bin.get(), nand.get()); } diff --git a/Source/Core/Core/HW/WiiSave.h b/Source/Core/Core/HW/WiiSave.h index 1ae03ed46a..cace7ba896 100644 --- a/Source/Core/Core/HW/WiiSave.h +++ b/Source/Core/Core/HW/WiiSave.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include @@ -37,7 +38,7 @@ StoragePointer MakeDataBinStorage(IOS::HLE::IOSC* iosc, const std::string& path, bool Copy(Storage* source, Storage* destination); /// Import a save into the NAND from a .bin file. -bool Import(const std::string& data_bin_path); +bool Import(const std::string& data_bin_path, std::function can_overwrite); /// Export a save to a .bin file. bool Export(u64 tid, const std::string& export_path); /// Export all saves that are in the NAND. Returns the number of exported saves. diff --git a/Source/Core/DolphinQt2/MenuBar.cpp b/Source/Core/DolphinQt2/MenuBar.cpp index 43ecf794f3..f7f4c047b0 100644 --- a/Source/Core/DolphinQt2/MenuBar.cpp +++ b/Source/Core/DolphinQt2/MenuBar.cpp @@ -958,9 +958,18 @@ void MenuBar::ImportWiiSave() if (file.isEmpty()) return; - if (WiiSave::Import(file.toStdString())) + bool cancelled = false; + auto can_overwrite = [&] { + bool yes = QMessageBox::question( + this, tr("Save Import"), + tr("Save data for this title already exists in the NAND. Consider backing up " + "the current data before overwriting.\nOverwrite now?")) == QMessageBox::Yes; + cancelled = !yes; + return yes; + }; + if (WiiSave::Import(file.toStdString(), can_overwrite)) QMessageBox::information(this, tr("Save Import"), tr("Successfully imported save files.")); - else + else if (!cancelled) QMessageBox::critical(this, tr("Save Import"), tr("Failed to import save files.")); } diff --git a/Source/Core/DolphinWX/FrameTools.cpp b/Source/Core/DolphinWX/FrameTools.cpp index ea6f504e7c..d6d53af3a4 100644 --- a/Source/Core/DolphinWX/FrameTools.cpp +++ b/Source/Core/DolphinWX/FrameTools.cpp @@ -1214,8 +1214,14 @@ void CFrame::OnImportSave(wxCommandEvent& WXUNUSED(event)) _("Wii save files (*.bin)") + "|*.bin|" + wxGetTranslation(wxALL_FILES), wxFD_OPEN | wxFD_PREVIEW | wxFD_FILE_MUST_EXIST, this); + auto can_overwrite = [this] { + return wxMessageBox(_("Save data for this title already exists in the NAND. Consider backing " + "up the current data before overwriting.\nOverwrite now?"), + _("Save Import"), wxYES_NO, this) == wxYES; + }; + if (!path.IsEmpty()) - WiiSave::Import(WxStrToStr(path)); + WiiSave::Import(WxStrToStr(path), can_overwrite); } void CFrame::OnShowCheatsWindow(wxCommandEvent& WXUNUSED(event)) From 210377816d4cb86f9455a6b3040c393886f470b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 1 Jun 2018 23:44:24 +0200 Subject: [PATCH 6/7] WiiSave: Get rid of some magic numbers It would make sense for 0x80 and 0xf0c0 to be respectively sizeof(BkHeader) and sizeof(Header) as Nintendo is signing anything that comes after the header, including the BkHeader. --- Source/Core/Core/HW/WiiSave.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Core/Core/HW/WiiSave.cpp b/Source/Core/Core/HW/WiiSave.cpp index 1ffe9d36f2..47cfdac4cc 100644 --- a/Source/Core/Core/HW/WiiSave.cpp +++ b/Source/Core/Core/HW/WiiSave.cpp @@ -78,6 +78,7 @@ struct Header DataBinHeader hdr; u8 banner[FULL_BNR_MAX]; }; +static_assert(sizeof(Header) == 0xf0c0, "Header has an incorrect size"); struct BkHeader { @@ -96,6 +97,7 @@ struct BkHeader std::array mac_address; std::array padding; }; +static_assert(sizeof(BkHeader) == 0x80, "BkHeader has an incorrect size"); struct FileHDR { @@ -464,9 +466,9 @@ private: return false; // Read data to sign. - const u32 data_size = bk_header->size_of_files + 0x80; + const u32 data_size = bk_header->size_of_files + sizeof(BkHeader); auto data = std::make_unique(data_size); - m_file.Seek(0xf0c0, SEEK_SET); + m_file.Seek(sizeof(Header), SEEK_SET); if (!m_file.ReadBytes(data.get(), data_size)) return false; From 69abaf3ec4cd902cf3e51a0a6c8a4c117a8bf059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 1 Jun 2018 23:45:58 +0200 Subject: [PATCH 7/7] WiiSave: Merge Header and DataBinHeader to reduce noise DataBinHeader is not used anywhere in the code other than via Header, so let's merge them to reduce noise when accessing header fields (currently we have to do header.hdr which looks silly). --- Source/Core/Core/HW/WiiSave.cpp | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/Source/Core/Core/HW/WiiSave.cpp b/Source/Core/Core/HW/WiiSave.cpp index 47cfdac4cc..c87130c190 100644 --- a/Source/Core/Core/HW/WiiSave.cpp +++ b/Source/Core/Core/HW/WiiSave.cpp @@ -63,7 +63,7 @@ enum }; #pragma pack(push, 1) -struct DataBinHeader +struct Header { Common::BigEndianValue tid; Common::BigEndianValue banner_size; // (0x72A0 or 0xF0A0, also seen 0xBAA0) @@ -71,11 +71,6 @@ struct DataBinHeader u8 unk1; // maybe permissions is a be16 std::array md5; // md5 of plaintext header with md5 blanker applied Common::BigEndianValue unk2; -}; - -struct Header -{ - DataBinHeader hdr; u8 banner[FULL_BNR_MAX]; }; static_assert(sizeof(Header) == 0xf0c0, "Header has an incorrect size"); @@ -163,10 +158,10 @@ public: Header header{}; std::string banner_file_path = m_wii_title_path + "/banner.bin"; u32 banner_size = static_cast(File::GetSize(banner_file_path)); - header.hdr.banner_size = banner_size; - header.hdr.tid = m_tid; - header.hdr.md5 = s_md5_blanker; - header.hdr.permissions = 0x3C; + header.banner_size = banner_size; + header.tid = m_tid; + header.md5 = s_md5_blanker; + header.permissions = 0x3C; File::IOFile banner_file(banner_file_path, "rb"); if (!banner_file.ReadBytes(header.banner, banner_size)) @@ -176,7 +171,7 @@ public: Md5 md5_calc; mbedtls_md5(reinterpret_cast(&header), sizeof(Header), md5_calc.data()); - header.hdr.md5 = std::move(md5_calc); + header.md5 = std::move(md5_calc); return header; } @@ -218,7 +213,7 @@ public: bool WriteHeader(const Header& header) override { File::IOFile banner_file(m_wii_title_path + "/banner.bin", "wb"); - return banner_file.WriteBytes(header.banner, header.hdr.banner_size); + return banner_file.WriteBytes(header.banner, header.banner_size); } bool WriteBkHeader(const BkHeader& bk_header) override { return true; } @@ -318,7 +313,7 @@ public: std::array iv = s_sd_initial_iv; m_iosc.Decrypt(IOS::HLE::IOSC::HANDLE_SD_KEY, iv.data(), reinterpret_cast(&header), sizeof(Header), reinterpret_cast(&header), IOS::PID_ES); - u32 banner_size = header.hdr.banner_size; + u32 banner_size = header.banner_size; if ((banner_size < FULL_BNR_MIN) || (banner_size > FULL_BNR_MAX) || (((banner_size - BNR_SZ) % ICON_SZ) != 0)) { @@ -326,8 +321,8 @@ public: return {}; } - Md5 md5_file = header.hdr.md5; - header.hdr.md5 = s_md5_blanker; + Md5 md5_file = header.md5; + header.md5 = s_md5_blanker; Md5 md5_calc; mbedtls_md5(reinterpret_cast(&header), sizeof(Header), md5_calc.data()); if (md5_file != md5_calc) @@ -531,7 +526,7 @@ bool Import(const std::string& data_bin_path, std::function can_overwrit ERROR_LOG(CORE, "WiiSave::Import: Failed to read header"); return false; } - const auto nand = MakeNandStorage(ios.GetFS().get(), header->hdr.tid); + const auto nand = MakeNandStorage(ios.GetFS().get(), header->tid); if (nand->SaveExists() && !can_overwrite()) return false; return Copy(data_bin.get(), nand.get());