diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a44e9e5..8765367 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -13,6 +13,8 @@ add_library(core STATIC key/key.h ncch/ncch_container.cpp ncch/ncch_container.h + ncch/seed_db.cpp + ncch/seed_db.h ncch/smdh.cpp ncch/smdh.h ncch/title_metadata.cpp diff --git a/src/core/importer.cpp b/src/core/importer.cpp index f28d4e3..b6bcd6a 100644 --- a/src/core/importer.cpp +++ b/src/core/importer.cpp @@ -13,6 +13,7 @@ #include "core/inner_fat.h" #include "core/key/key.h" #include "core/ncch/ncch_container.h" +#include "core/ncch/seed_db.h" #include "core/ncch/smdh.h" #include "core/ncch/title_metadata.h" @@ -222,11 +223,27 @@ bool SDMCImporter::ImportSysdata(u64 id, [[maybe_unused]] const ProgressCallback } case 2: { // seed db const auto target_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SEED_DB; - LOG_INFO(Core, "Copying {} from {} to {}", SEED_DB, config.seed_db_path, target_path); - if (!FileUtil::CreateFullPath(target_path)) { + LOG_INFO(Core, "Dumping SeedDB from {} to {}", SEED_DB, config.seed_db_path, target_path); + + SeedDB target; + if (!target.Load(target_path)) { + LOG_ERROR(Core, "Could not load seeddb from {}", target_path); return false; } - return FileUtil::Copy(config.seed_db_path, target_path); + + SeedDB source; + if (!source.Load(config.seed_db_path)) { + LOG_ERROR(Core, "Could not load seeddb from {}", config.seed_db_path); + return false; + } + + for (const auto& seed : source) { + if (!target.Get(seed.title_id)) { + LOG_INFO(Core, "Adding seed for {:16X}", seed.title_id); + target.Add(seed); + } + } + return target.Save(target_path); } case 3: { // secret sector const auto target_path = @@ -485,7 +502,6 @@ void SDMCImporter::ListSysdata(std::vector& out) const { { const auto sysdata_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir); CHECK_CONTENT(0, config.bootrom_path, sysdata_path + BOOTROM9, BOOTROM9); - CHECK_CONTENT(2, config.seed_db_path, sysdata_path + SEED_DB, SEED_DB); CHECK_CONTENT(3, config.secret_sector_path, sysdata_path + SECRET_SECTOR, SECRET_SECTOR); if (!config.bootrom_path.empty()) { // 47 bytes = "slot0x26KeyX=<32>\r\n" is only for Windows, @@ -524,6 +540,34 @@ void SDMCImporter::ListSysdata(std::vector& out) const { "Safe mode firm"}); } } while (0); + + // Check for seeddb + if (config.seed_db_path.empty()) { + return; + } + + const auto target_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SEED_DB; + SeedDB target; + if (!target.Load(target_path)) { + LOG_ERROR(Core, "Could not load seeddb from {}", target_path); + return; + } + + SeedDB source; + if (!source.Load(config.seed_db_path)) { + LOG_ERROR(Core, "Could not load seeddb from {}", config.seed_db_path); + return; + } + + bool exists = true; // Whether the DB already 'exists', i.e. no new seeds can be found + for (const auto& seed : source) { + if (!target.Get(seed.title_id)) { + exists = false; + break; + } + } + out.push_back( + {ContentType::Sysdata, 2, exists, FileUtil::GetSize(config.seed_db_path), SEED_DB}); } void SDMCImporter::DeleteContent(const ContentSpecifier& specifier) { diff --git a/src/core/ncch/seed_db.cpp b/src/core/ncch/seed_db.cpp new file mode 100644 index 0000000..af04432 --- /dev/null +++ b/src/core/ncch/seed_db.cpp @@ -0,0 +1,117 @@ +// Copyright 2018 Citra Emulator Project / 2020 Pengfei Zhu +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// Modified from Citra's implementation to allow multiple instances + +#include +#include "common/file_util.h" +#include "common/logging/log.h" +#include "core/ncch/seed_db.h" + +namespace Core { + +bool SeedDB::Load(const std::string& path) { + seeds.clear(); + if (!FileUtil::Exists(path)) { + if (!FileUtil::CreateFullPath(path)) { + LOG_ERROR(Service_FS, "Failed to create seed database"); + return false; + } + if (!Save(path)) { + LOG_ERROR(Service_FS, "Failed to save seed database"); + return false; + } + return true; + } + FileUtil::IOFile file{path, "rb"}; + if (!file.IsOpen()) { + LOG_ERROR(Service_FS, "Failed to open seed database"); + return false; + } + u32 count; + if (!file.ReadBytes(&count, sizeof(count))) { + LOG_ERROR(Service_FS, "Failed to read seed database count fully"); + return false; + } + if (!file.Seek(SEEDDB_PADDING_BYTES, SEEK_CUR)) { + LOG_ERROR(Service_FS, "Failed to skip seed database padding"); + return false; + } + for (u32 i = 0; i < count; ++i) { + Seed seed; + if (!file.ReadBytes(&seed.title_id, sizeof(seed.title_id))) { + LOG_ERROR(Service_FS, "Failed to read seed {} title ID", i); + return false; + } + if (!file.ReadBytes(seed.data.data(), seed.data.size())) { + LOG_ERROR(Service_FS, "Failed to read seed {} data", i); + return false; + } + if (!file.ReadBytes(seed.reserved.data(), seed.reserved.size())) { + LOG_ERROR(Service_FS, "Failed to read seed {} reserved data", i); + return false; + } + seeds.push_back(seed); + } + return true; +} + +bool SeedDB::Save(const std::string& path) { + if (!FileUtil::CreateFullPath(path)) { + LOG_ERROR(Service_FS, "Failed to create seed database"); + return false; + } + FileUtil::IOFile file{path, "wb"}; + if (!file.IsOpen()) { + LOG_ERROR(Service_FS, "Failed to open seed database"); + return false; + } + u32 count{static_cast(seeds.size())}; + if (file.WriteBytes(&count, sizeof(count)) != sizeof(count)) { + LOG_ERROR(Service_FS, "Failed to write seed database count fully"); + return false; + } + std::array padding{}; + if (file.WriteBytes(padding.data(), padding.size()) != padding.size()) { + LOG_ERROR(Service_FS, "Failed to write seed database padding fully"); + return false; + } + for (std::size_t i = 0; i < count; ++i) { + if (file.WriteBytes(&seeds[i].title_id, sizeof(seeds[i].title_id)) != + sizeof(seeds[i].title_id)) { + LOG_ERROR(Service_FS, "Failed to write seed {} title ID fully", i); + return false; + } + if (file.WriteBytes(seeds[i].data.data(), seeds[i].data.size()) != seeds[i].data.size()) { + LOG_ERROR(Service_FS, "Failed to write seed {} data fully", i); + return false; + } + if (file.WriteBytes(seeds[i].reserved.data(), seeds[i].reserved.size()) != + seeds[i].reserved.size()) { + LOG_ERROR(Service_FS, "Failed to write seed {} reserved data fully", i); + return false; + } + } + return true; +} + +void SeedDB::Add(const Seed& seed) { + seeds.push_back(seed); +} + +std::size_t SeedDB::Size() const { + return seeds.size(); +} + +std::optional SeedDB::Get(u64 title_id) const { + const auto found_seed_iter = + std::find_if(seeds.begin(), seeds.end(), + [title_id](const auto& seed) { return seed.title_id == title_id; }); + if (found_seed_iter != seeds.end()) { + return found_seed_iter->data; + } + return std::nullopt; +} + +} // namespace Core diff --git a/src/core/ncch/seed_db.h b/src/core/ncch/seed_db.h new file mode 100644 index 0000000..c4ddafe --- /dev/null +++ b/src/core/ncch/seed_db.h @@ -0,0 +1,54 @@ +// Copyright 2018 Citra Emulator Project / 2020 Pengfei Zhu +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// Modified from Citra's implementation to allow multiple instances + +#include +#include +#include +#include "common/common_types.h" +#include "common/swap.h" + +namespace Core { + +constexpr std::size_t SEEDDB_PADDING_BYTES{12}; + +struct Seed { + using Data = std::array; + + u64_le title_id; + Data data; + std::array reserved; +}; + +class SeedDB { +public: + bool Load(const std::string& path); + bool Save(const std::string& path); + + void Add(const Seed& seed); + std::size_t Size() const; + std::optional Get(u64 title_id) const; + + auto begin() { + return seeds.begin(); + } + + auto begin() const { + return seeds.begin(); + } + + auto end() { + return seeds.end(); + } + + auto end() const { + return seeds.end(); + } + +private: + std::vector seeds; +}; + +} // namespace Core