mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-02 16:49:04 +00:00
Support for multiple NANDs
Preliminary support is added for multiple NANDs. ticket.db, title.db and seed.db will be merged, while the first NAND will be used for system titles and data. Future changes to the frontend will allow selecting NANDs.
This commit is contained in:
Vendored
+73
-88
@@ -5,124 +5,109 @@
|
|||||||
# GM9 Script for dumping necessary files automatically.
|
# GM9 Script for dumping necessary files automatically.
|
||||||
|
|
||||||
set PREVIEW_MODE "threeSD Dumper\nby zhaowenlan1779"
|
set PREVIEW_MODE "threeSD Dumper\nby zhaowenlan1779"
|
||||||
set OUT "0:/threeSD"
|
if not ask "Execute threeSD Dumper?\n \nRequires GodMode9 v2.0.0\nYou are on $[GM9VER]\n \nRequired Space: ~400MB for each NAND"
|
||||||
if not find $[OUT] NULL
|
|
||||||
mkdir $[OUT]
|
|
||||||
end
|
|
||||||
|
|
||||||
if not ask "Execute threeSD Dumper?"
|
|
||||||
goto Exit
|
goto Exit
|
||||||
end
|
end
|
||||||
|
|
||||||
set PREVIEW_MODE "threeSD Dumper\nby zhaowenlan1779\n \nWorking..."
|
set PREVIEW_MODE "threeSD Dumper\nby zhaowenlan1779\n \nWorking..."
|
||||||
|
|
||||||
# === movable.sed
|
set OUT "0:/threeSD"
|
||||||
cp -w -n "1:/private/movable.sed" $[OUT]/movable.sed
|
if exist $[OUT]
|
||||||
|
rm $[OUT]
|
||||||
|
end
|
||||||
|
mkdir $[OUT]
|
||||||
|
|
||||||
# === bootrom
|
# === General data (independent of NANDs)
|
||||||
if find "M:/boot9.bin" NULL
|
|
||||||
|
# Version
|
||||||
|
dumptxt $[OUT]/version.txt 4
|
||||||
|
|
||||||
|
# bootrom
|
||||||
|
if exist "M:/boot9.bin"
|
||||||
cp -w -n "M:/boot9.bin" $[OUT]/boot9.bin
|
cp -w -n "M:/boot9.bin" $[OUT]/boot9.bin
|
||||||
elif find "0:/3DS/boot9.bin" NULL
|
elif exist "0:/3DS/boot9.bin"
|
||||||
cp -w -n "0:/3DS/boot9.bin" $[OUT]/boot9.bin
|
cp -w -n "0:/3DS/boot9.bin" $[OUT]/boot9.bin
|
||||||
else
|
else
|
||||||
echo "ERROR: \nboot9.bin not found. \nIf you use fastboot3ds, hold HOME while booting, \nand go to Miscellaneous... > Dump bootroms & OTP. \nWhen finished, simply execute this script again."
|
echo "ERROR: \nboot9.bin not found. \nIf you use fastboot3ds, hold HOME while booting, \nand go to Miscellaneous... > Dump bootroms & OTP. \nWhen finished, simply execute this script again."
|
||||||
goto Exit
|
goto Exit
|
||||||
end
|
end
|
||||||
|
|
||||||
# === certs.db
|
# Secret sector (N3DS only)
|
||||||
if chk $[RDTYPE] "devkit"
|
|
||||||
echo "WARNING: \nDev kit detected. \nCIA building will not be usable."
|
|
||||||
else
|
|
||||||
cp -w -n "1:/dbs/certs.db" $[OUT]/certs.db
|
|
||||||
end
|
|
||||||
|
|
||||||
# === ticket.db
|
|
||||||
cp -w -n "1:/dbs/ticket.db" $[OUT]/ticket.db
|
|
||||||
|
|
||||||
# === title.db
|
|
||||||
cp -w -n "1:/dbs/title.db" $[OUT]/title.db
|
|
||||||
|
|
||||||
# === Secret sector (N3DS only)
|
|
||||||
if chk $[ONTYPE] "N3DS"
|
if chk $[ONTYPE] "N3DS"
|
||||||
cp -w -n "S:/sector0x96.bin" $[OUT]/sector0x96.bin
|
cp -w -n "S:/sector0x96.bin" $[OUT]/sector0x96.bin
|
||||||
end
|
end
|
||||||
|
|
||||||
# === NAND data
|
# === NANDs
|
||||||
if not find $[OUT]/data NULL
|
|
||||||
mkdir $[OUT]/data
|
# Start with SysNAND
|
||||||
|
set NAND "1:"
|
||||||
|
set NAND_NAME "Sys"
|
||||||
|
set ID0 $[SYSID0]
|
||||||
|
|
||||||
|
@Loop
|
||||||
|
set PREVIEW_MODE "threeSD Dumper\nby zhaowenlan1779\n \nWorking ($[NAND_NAME])..."
|
||||||
|
set OUT "0:/threeSD/$[NAND_NAME]"
|
||||||
|
mkdir $[OUT]
|
||||||
|
|
||||||
|
# movable.sed
|
||||||
|
cp -w -n $[NAND]/private/movable.sed $[OUT]/movable.sed
|
||||||
|
|
||||||
|
# certs.db
|
||||||
|
if chk $[RDTYPE] "devkit"
|
||||||
|
echo "WARNING: \nDev kit detected. \nCIA building will not be usable."
|
||||||
|
else
|
||||||
|
cp -w -n $[NAND]/dbs/certs.db $[OUT]/certs.db
|
||||||
end
|
end
|
||||||
|
|
||||||
if not find $[OUT]/data/extdata NULL
|
# ticket.db
|
||||||
mkdir $[OUT]/data/extdata
|
cp -w -n $[NAND]/dbs/ticket.db $[OUT]/ticket.db
|
||||||
end
|
|
||||||
cp -w -n "1:/data/$[SYSID0]/extdata" $[OUT]/data/extdata
|
|
||||||
|
|
||||||
if not find $[OUT]/data/sysdata NULL
|
# title.db
|
||||||
mkdir $[OUT]/data/sysdata
|
cp -w -n $[NAND]/dbs/title.db $[OUT]/title.db
|
||||||
end
|
|
||||||
cp -w -n "1:/data/$[SYSID0]/sysdata" $[OUT]/data/sysdata
|
|
||||||
|
|
||||||
# === Other system titles
|
# seeddb.bin
|
||||||
if find $[OUT]/title NULL
|
# Note: this contains both SysNAND and EmuNAND seeds when built, but only the current EmuNAND
|
||||||
rm $[OUT]/title
|
if exist 0:/gm9/out/seedd.bin
|
||||||
|
rm 0:/gm9/out/seeddb.bin
|
||||||
end
|
end
|
||||||
mkdir $[OUT]/title
|
|
||||||
|
|
||||||
# System Applications
|
|
||||||
if not find $[OUT]/title/00040010 NULL
|
|
||||||
mkdir $[OUT]/title/00040010
|
|
||||||
end
|
|
||||||
cp -w -n "1:/title/00040010" $[OUT]/title/00040010
|
|
||||||
|
|
||||||
# System Data Archives
|
|
||||||
if not find $[OUT]/title/0004001b NULL
|
|
||||||
mkdir $[OUT]/title/0004001b
|
|
||||||
end
|
|
||||||
cp -w -n "1:/title/0004001b" $[OUT]/title/0004001b
|
|
||||||
|
|
||||||
# System Applets
|
|
||||||
if not find $[OUT]/title/00040030 NULL
|
|
||||||
mkdir $[OUT]/title/00040030
|
|
||||||
end
|
|
||||||
cp -w -n "1:/title/00040030" $[OUT]/title/00040030
|
|
||||||
|
|
||||||
# Shared Data Archives
|
|
||||||
if not find $[OUT]/title/0004009b NULL
|
|
||||||
mkdir $[OUT]/title/0004009b
|
|
||||||
end
|
|
||||||
cp -w -n "1:/title/0004009b" $[OUT]/title/0004009b
|
|
||||||
|
|
||||||
# System Data Archives
|
|
||||||
if not find $[OUT]/title/000400db NULL
|
|
||||||
mkdir $[OUT]/title/000400db
|
|
||||||
end
|
|
||||||
cp -w -n "1:/title/000400db" $[OUT]/title/000400db
|
|
||||||
|
|
||||||
# System Modules
|
|
||||||
if not find $[OUT]/title/00040130 NULL
|
|
||||||
mkdir $[OUT]/title/00040130
|
|
||||||
end
|
|
||||||
cp -w -n "1:/title/00040130" $[OUT]/title/00040130
|
|
||||||
|
|
||||||
# System Firmware
|
|
||||||
if not find $[OUT]/title/00040138 NULL
|
|
||||||
mkdir $[OUT]/title/00040138
|
|
||||||
end
|
|
||||||
cp -w -n "1:/title/00040138" $[OUT]/title/00040138
|
|
||||||
|
|
||||||
# === seeddb.bin
|
|
||||||
sdump -o -s -w seeddb.bin
|
sdump -o -s -w seeddb.bin
|
||||||
if not find 0:/gm9/out/seeddb.bin NULL
|
if not exist 0:/gm9/out/seeddb.bin
|
||||||
echo "WARNING: \nseeddb.bin couldn't be built. \nThis may be because your system \ndoes not have any seeds. \nOtherwise, imported games may fail \nto run if they use seed encryption."
|
echo "WARNING: \nseeddb.bin couldn't be built. \nThis may be because your system \ndoes not have any seeds. \nOtherwise, imported games may fail \nto run if they use seed encryption."
|
||||||
else
|
else
|
||||||
cp -w -n "0:/gm9/out/seeddb.bin" $[OUT]/seeddb.bin
|
cp -w -n "0:/gm9/out/seeddb.bin" $[OUT]/seeddb.bin
|
||||||
rm "0:/gm9/out/seeddb.bin"
|
rm "0:/gm9/out/seeddb.bin"
|
||||||
end
|
end
|
||||||
|
|
||||||
# === Write version
|
# data
|
||||||
dumptxt $[OUT]/version.txt 4
|
cp -w -n $[NAND]/data/$[ID0] $[OUT]/data
|
||||||
|
|
||||||
|
# title
|
||||||
|
cp -w -n $[NAND]/title $[OUT]/title
|
||||||
|
|
||||||
|
# Loop Control
|
||||||
|
if chk $[NAND] "1:"
|
||||||
|
# Start EmuNAND
|
||||||
|
if not exist "4:/title"
|
||||||
|
goto Finish
|
||||||
|
end
|
||||||
|
set NAND "4:"
|
||||||
|
else
|
||||||
|
# Next EmuNAND
|
||||||
|
set LASTEMU $[EMUBASE]
|
||||||
|
nextemu
|
||||||
|
if chk $[EMUBASE] $[LASTEMU]
|
||||||
|
# All EmuNANDs done
|
||||||
|
goto Finish
|
||||||
|
end
|
||||||
|
end
|
||||||
|
set NAND_NAME "Emu$[EMUBASE]"
|
||||||
|
set ID0 $[EMUID0]
|
||||||
|
goto Loop
|
||||||
|
|
||||||
|
@Finish
|
||||||
set PREVIEW_MODE "threeSD Dumper\nby zhaowenlan1779\n \nSuccess!"
|
set PREVIEW_MODE "threeSD Dumper\nby zhaowenlan1779\n \nSuccess!"
|
||||||
echo "Successfully dumped necessary\nfiles for threeSD."
|
if ask "Successfully dumped necessary\nfiles for threeSD.\n \nPower off now?"
|
||||||
|
poweroff
|
||||||
|
end
|
||||||
|
|
||||||
@Exit
|
@Exit
|
||||||
|
|||||||
+164
-130
@@ -2,6 +2,7 @@
|
|||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <cryptopp/sha.h>
|
#include <cryptopp/sha.h>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
@@ -32,13 +33,12 @@ SDMCImporter::SDMCImporter(const Config& config_) : config(config_) {
|
|||||||
SDMCImporter::~SDMCImporter() {
|
SDMCImporter::~SDMCImporter() {
|
||||||
// Unload global DBs
|
// Unload global DBs
|
||||||
Certs::Clear();
|
Certs::Clear();
|
||||||
Seeds::Clear();
|
g_seed_db.seeds.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SDMCImporter::Init() {
|
bool SDMCImporter::Init() {
|
||||||
ASSERT_MSG(!config.sdmc_path.empty() && !config.user_path.empty() &&
|
ASSERT_MSG(IsConfigGood(config), "Config is not good");
|
||||||
!config.bootrom_path.empty() && !config.movable_sed_path.empty(),
|
nand_config = config.nands[0];
|
||||||
"Config is not good");
|
|
||||||
|
|
||||||
// Fix paths
|
// Fix paths
|
||||||
if (config.sdmc_path.back() != '/' && config.sdmc_path.back() != '\\') {
|
if (config.sdmc_path.back() != '/' && config.sdmc_path.back() != '\\') {
|
||||||
@@ -51,28 +51,29 @@ bool SDMCImporter::Init() {
|
|||||||
|
|
||||||
Key::ClearKeys();
|
Key::ClearKeys();
|
||||||
Key::LoadBootromKeys(config.bootrom_path);
|
Key::LoadBootromKeys(config.bootrom_path);
|
||||||
Key::LoadMovableSedKeys(config.movable_sed_path);
|
Key::LoadMovableSedKeys(nand_config.movable_sed_path);
|
||||||
|
|
||||||
if (!Key::IsNormalKeyAvailable(Key::SDKey)) {
|
if (!Key::IsNormalKeyAvailable(Key::SDKey)) {
|
||||||
LOG_ERROR(Core, "SDKey is not available");
|
LOG_ERROR(Core, "SDKey is not available");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load global DBs
|
// Load and merge global DBs
|
||||||
if (!config.seed_db_path.empty()) {
|
ticket_db = std::make_shared<TicketDB>();
|
||||||
Seeds::Load(config.seed_db_path);
|
nand_title_db = std::make_unique<TitleDB>();
|
||||||
|
for (const auto& nand : config.nands) {
|
||||||
|
if (!nand.certs_db_path.empty()) {
|
||||||
|
TRY(Certs::Load(nand.certs_db_path));
|
||||||
}
|
}
|
||||||
if (!config.certs_db_path.empty()) {
|
if (!nand.ticket_db_path.empty()) {
|
||||||
Certs::Load(config.certs_db_path);
|
TRY(ticket_db->AddFromFile(nand.ticket_db_path));
|
||||||
}
|
}
|
||||||
|
if (!nand.title_db_path.empty()) {
|
||||||
// Load Ticket DB
|
TRY(nand_title_db->AddFromFile(nand.title_db_path));
|
||||||
if (!config.ticket_db_path.empty()) {
|
}
|
||||||
ticket_db = std::make_shared<TicketDB>(config.ticket_db_path);
|
if (!nand.seed_db_path.empty()) {
|
||||||
|
TRY(g_seed_db.AddFromFile(nand.seed_db_path));
|
||||||
}
|
}
|
||||||
if (!ticket_db || !ticket_db->IsGood()) {
|
|
||||||
LOG_WARNING(Core, "ticket.db not present or is invalid");
|
|
||||||
ticket_db.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create children
|
// Create children
|
||||||
@@ -84,22 +85,10 @@ bool SDMCImporter::Init() {
|
|||||||
DataContainer container(sdmc_decryptor->DecryptFile("/dbs/title.db"));
|
DataContainer container(sdmc_decryptor->DecryptFile("/dbs/title.db"));
|
||||||
std::vector<std::vector<u8>> data;
|
std::vector<std::vector<u8>> data;
|
||||||
if (container.IsGood() && container.GetIVFCLevel4Data(data)) {
|
if (container.IsGood() && container.GetIVFCLevel4Data(data)) {
|
||||||
sdmc_title_db = std::make_unique<TitleDB>(std::move(data[0]));
|
sdmc_title_db = std::make_unique<TitleDB>();
|
||||||
|
TRY(sdmc_title_db->AddFromData(std::move(data[0])));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!sdmc_title_db || !sdmc_title_db->IsGood()) {
|
|
||||||
LOG_WARNING(Core, "SDMC title.db invalid");
|
|
||||||
sdmc_title_db.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load NAND Title DB
|
|
||||||
if (!config.nand_title_db_path.empty()) {
|
|
||||||
nand_title_db = std::make_unique<TitleDB>(config.nand_title_db_path);
|
|
||||||
}
|
|
||||||
if (!nand_title_db || !nand_title_db->IsGood()) {
|
|
||||||
LOG_WARNING(Core, "NAND title.db invalid");
|
|
||||||
nand_title_db.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
FileUtil::SetUserPath(config.user_path);
|
FileUtil::SetUserPath(config.user_path);
|
||||||
return true;
|
return true;
|
||||||
@@ -193,8 +182,7 @@ bool SDMCImporter::ImportTitle(const ContentSpecifier& specifier,
|
|||||||
bool SDMCImporter::ImportNandTitle(const ContentSpecifier& specifier,
|
bool SDMCImporter::ImportNandTitle(const ContentSpecifier& specifier,
|
||||||
const Common::ProgressCallback& callback) {
|
const Common::ProgressCallback& callback) {
|
||||||
|
|
||||||
const auto base_path =
|
const auto base_path = nand_config.title_path.substr(0, nand_config.title_path.size() - 6);
|
||||||
config.system_titles_path.substr(0, config.system_titles_path.size() - 6);
|
|
||||||
return ImportTitleGeneric(
|
return ImportTitleGeneric(
|
||||||
base_path, specifier, callback,
|
base_path, specifier, callback,
|
||||||
[this, &base_path](const std::string& filepath,
|
[this, &base_path](const std::string& filepath,
|
||||||
@@ -242,7 +230,7 @@ bool SDMCImporter::ImportNandSavegame(u64 id,
|
|||||||
[[maybe_unused]] const Common::ProgressCallback& callback) {
|
[[maybe_unused]] const Common::ProgressCallback& callback) {
|
||||||
const auto path = fmt::format("sysdata/{:08x}/00000000", (id & 0xFFFFFFFF));
|
const auto path = fmt::format("sysdata/{:08x}/00000000", (id & 0xFFFFFFFF));
|
||||||
|
|
||||||
FileUtil::IOFile file(config.nand_data_path + path, "rb");
|
FileUtil::IOFile file(nand_config.data_path + path, "rb");
|
||||||
std::vector<u8> data = file.GetData();
|
std::vector<u8> data = file.GetData();
|
||||||
if (data.empty()) {
|
if (data.empty()) {
|
||||||
LOG_ERROR(Core, "Failed to read from {}", path);
|
LOG_ERROR(Core, "Failed to read from {}", path);
|
||||||
@@ -281,7 +269,7 @@ bool SDMCImporter::ImportExtdata(u64 id,
|
|||||||
bool SDMCImporter::ImportNandExtdata(u64 id,
|
bool SDMCImporter::ImportNandExtdata(u64 id,
|
||||||
[[maybe_unused]] const Common::ProgressCallback& callback) {
|
[[maybe_unused]] const Common::ProgressCallback& callback) {
|
||||||
const auto path = fmt::format("extdata/{:08x}/{:08x}/", (id >> 32), (id & 0xFFFFFFFF));
|
const auto path = fmt::format("extdata/{:08x}/{:08x}/", (id >> 32), (id & 0xFFFFFFFF));
|
||||||
Extdata extdata(config.nand_data_path + path);
|
Extdata extdata(nand_config.data_path + path);
|
||||||
if (!extdata.IsGood()) {
|
if (!extdata.IsGood()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -303,27 +291,11 @@ bool SDMCImporter::ImportSysdata(u64 id,
|
|||||||
}
|
}
|
||||||
case 1: { // seed db
|
case 1: { // seed db
|
||||||
const auto target_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SEED_DB;
|
const auto target_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SEED_DB;
|
||||||
LOG_INFO(Core, "Dumping SeedDB from {} to {}", SEED_DB, config.seed_db_path, target_path);
|
LOG_INFO(Core, "Dumping SeedDB to {}", SEED_DB, target_path);
|
||||||
|
|
||||||
SeedDB target;
|
SeedDB merged_seed_db{g_seed_db};
|
||||||
if (!target.Load(target_path)) {
|
merged_seed_db.AddFromFile(target_path);
|
||||||
LOG_ERROR(Core, "Could not load seeddb from {}", target_path);
|
return merged_seed_db.Save(target_path);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 2: { // secret sector
|
case 2: { // secret sector
|
||||||
const auto target_path =
|
const auto target_path =
|
||||||
@@ -407,11 +379,11 @@ bool SDMCImporter::LoadTMD(ContentType type, u64 id, TitleMetadata& out) const {
|
|||||||
const bool is_nand = type == ContentType::NandTitle;
|
const bool is_nand = type == ContentType::NandTitle;
|
||||||
|
|
||||||
auto& title_db = is_nand ? nand_title_db : sdmc_title_db;
|
auto& title_db = is_nand ? nand_title_db : sdmc_title_db;
|
||||||
const auto physical_path =
|
const auto physical_path = is_nand
|
||||||
is_nand ? fmt::format("{}{:08x}/{:08x}/content/", config.system_titles_path, (id >> 32),
|
? fmt::format("{}{:08x}/{:08x}/content/", nand_config.title_path,
|
||||||
(id & 0xFFFFFFFF))
|
(id >> 32), (id & 0xFFFFFFFF))
|
||||||
: fmt::format("{}title/{:08x}/{:08x}/content/", config.sdmc_path, (id >> 32),
|
: fmt::format("{}title/{:08x}/{:08x}/content/", config.sdmc_path,
|
||||||
(id & 0xFFFFFFFF));
|
(id >> 32), (id & 0xFFFFFFFF));
|
||||||
|
|
||||||
std::string tmd_path;
|
std::string tmd_path;
|
||||||
if (title_db && title_db->titles.count(id)) {
|
if (title_db && title_db->titles.count(id)) {
|
||||||
@@ -445,7 +417,7 @@ std::shared_ptr<FileUtil::IOFile> SDMCImporter::OpenContent(const ContentSpecifi
|
|||||||
u32 content_id) const {
|
u32 content_id) const {
|
||||||
if (specifier.type == ContentType::NandTitle) {
|
if (specifier.type == ContentType::NandTitle) {
|
||||||
const auto path =
|
const auto path =
|
||||||
fmt::format("{}{:08x}/{:08x}/content/{:08x}.app", config.system_titles_path,
|
fmt::format("{}{:08x}/{:08x}/content/{:08x}.app", nand_config.title_path,
|
||||||
(specifier.id >> 32), (specifier.id & 0xFFFFFFFF), content_id);
|
(specifier.id >> 32), (specifier.id & 0xFFFFFFFF), content_id);
|
||||||
return std::make_shared<FileUtil::IOFile>(path, "rb");
|
return std::make_shared<FileUtil::IOFile>(path, "rb");
|
||||||
} else {
|
} else {
|
||||||
@@ -868,10 +840,9 @@ void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
|
|||||||
|
|
||||||
// TODO: Simplify.
|
// TODO: Simplify.
|
||||||
void SDMCImporter::ListNandTitle(std::vector<ContentSpecifier>& out) const {
|
void SDMCImporter::ListNandTitle(std::vector<ContentSpecifier>& out) const {
|
||||||
const auto ProcessDirectory = [this, &out,
|
const auto ProcessDirectory = [this, &out, &title_path = nand_config.title_path](u64 high_id) {
|
||||||
&system_titles_path = config.system_titles_path](u64 high_id) {
|
|
||||||
FileUtil::ForeachDirectoryEntry(
|
FileUtil::ForeachDirectoryEntry(
|
||||||
nullptr, fmt::format("{}{:08x}/", system_titles_path, high_id),
|
nullptr, fmt::format("{}{:08x}/", title_path, high_id),
|
||||||
[this, high_id, &out](u64* /*num_entries_out*/, const std::string& directory,
|
[this, high_id, &out](u64* /*num_entries_out*/, const std::string& directory,
|
||||||
const std::string& virtual_name) {
|
const std::string& virtual_name) {
|
||||||
if (!FileUtil::IsDirectory(directory + virtual_name + "/")) {
|
if (!FileUtil::IsDirectory(directory + virtual_name + "/")) {
|
||||||
@@ -931,7 +902,7 @@ void SDMCImporter::ListNandTitle(std::vector<ContentSpecifier>& out) const {
|
|||||||
|
|
||||||
void SDMCImporter::ListNandSavegame(std::vector<ContentSpecifier>& out) const {
|
void SDMCImporter::ListNandSavegame(std::vector<ContentSpecifier>& out) const {
|
||||||
FileUtil::ForeachDirectoryEntry(
|
FileUtil::ForeachDirectoryEntry(
|
||||||
nullptr, fmt::format("{}sysdata/", config.nand_data_path),
|
nullptr, fmt::format("{}sysdata/", nand_config.data_path),
|
||||||
[&out](u64* /*num_entries_out*/, const std::string& directory,
|
[&out](u64* /*num_entries_out*/, const std::string& directory,
|
||||||
const std::string& virtual_name) {
|
const std::string& virtual_name) {
|
||||||
if (!FileUtil::IsDirectory(directory + virtual_name + "/")) {
|
if (!FileUtil::IsDirectory(directory + virtual_name + "/")) {
|
||||||
@@ -996,7 +967,7 @@ void SDMCImporter::ListExtdata(std::vector<ContentSpecifier>& out) const {
|
|||||||
"3DS/00000000000000000000000000000000/00000000000000000000000000000000/"
|
"3DS/00000000000000000000000000000000/00000000000000000000000000000000/"
|
||||||
"extdata/00000000/{}");
|
"extdata/00000000/{}");
|
||||||
ProcessDirectory(0x00048000, ContentType::NandExtdata,
|
ProcessDirectory(0x00048000, ContentType::NandExtdata,
|
||||||
fmt::format("{}extdata/00048000/", config.nand_data_path),
|
fmt::format("{}extdata/00048000/", nand_config.data_path),
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||||
"data/00000000000000000000000000000000/extdata/00048000/{}");
|
"data/00000000000000000000000000000000/extdata/00048000/{}");
|
||||||
}
|
}
|
||||||
@@ -1026,32 +997,25 @@ void SDMCImporter::ListSysdata(std::vector<ContentSpecifier>& out) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for seeddb
|
// Check for seeddb
|
||||||
if (config.seed_db_path.empty()) {
|
if (g_seed_db.seeds.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto target_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SEED_DB;
|
const auto target_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SEED_DB;
|
||||||
SeedDB target;
|
SeedDB target;
|
||||||
if (!target.Load(target_path)) {
|
if (!target.AddFromFile(target_path)) {
|
||||||
LOG_ERROR(Core, "Could not load seeddb from {}", target_path);
|
LOG_ERROR(Core, "Could not load seeddb from {}", target_path);
|
||||||
return;
|
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
|
bool exists = true; // Whether the DB already 'exists', i.e. no new seeds can be found
|
||||||
for (const auto& seed : source) {
|
for (const auto& [title_id, seed] : g_seed_db.seeds) {
|
||||||
if (!target.Get(seed.title_id)) {
|
if (!target.seeds.count(title_id)) {
|
||||||
exists = false;
|
exists = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.push_back(
|
out.push_back({ContentType::Sysdata, 1, exists, g_seed_db.GetSize(), SEED_DB});
|
||||||
{ContentType::Sysdata, 1, exists, FileUtil::GetSize(config.seed_db_path), SEED_DB});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDMCImporter::DeleteContent(const ContentSpecifier& specifier) const {
|
void SDMCImporter::DeleteContent(const ContentSpecifier& specifier) const {
|
||||||
@@ -1136,6 +1100,67 @@ void SDMCImporter::DeleteSysdata(u64 id) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string GetID0(const std::string& movable_sed_path) {
|
||||||
|
FileUtil::IOFile file(movable_sed_path, "rb");
|
||||||
|
if (!file || file.GetSize() < 0x120) {
|
||||||
|
LOG_ERROR(Core, "Couldn't open file {}, or file too small", movable_sed_path);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check magic
|
||||||
|
u32_le magic{};
|
||||||
|
if (file.ReadBytes(&magic, sizeof(magic)) != sizeof(magic) ||
|
||||||
|
magic != MakeMagic('S', 'E', 'E', 'D')) {
|
||||||
|
|
||||||
|
LOG_ERROR(Core, "File {} is invalid", movable_sed_path);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate ID0
|
||||||
|
file.Seek(0x110, SEEK_SET); // KeyY offset
|
||||||
|
std::array<u8, 0x10> keyY;
|
||||||
|
if (file.ReadBytes(keyY.data(), keyY.size()) != keyY.size()) {
|
||||||
|
LOG_ERROR(Core, "Could not read keyY from {}", movable_sed_path);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
CryptoPP::SHA256 sha;
|
||||||
|
sha.Update(keyY.data(), keyY.size());
|
||||||
|
|
||||||
|
std::array<u32_le, CryptoPP::SHA256::DIGESTSIZE / sizeof(u32_le)> hash;
|
||||||
|
sha.Final(reinterpret_cast<CryptoPP::byte*>(hash.data()));
|
||||||
|
|
||||||
|
// ID0 is generated from the first half of the hash, with the four u32s byte-flipped
|
||||||
|
return fmt::format("{:08x}{:08x}{:08x}{:08x}", hash[0], hash[1], hash[2], hash[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets SDMC path (Nintendo 3DS/<ID0>/<ID1>) from ID0 folder. Basically just takes the first
|
||||||
|
// folder contained within, that matches the ID regex.
|
||||||
|
static std::string GetSDMCPath(const std::string& id0_folder) {
|
||||||
|
static const std::regex IdRegex{"[0-9a-f]{32}"};
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
FileUtil::ForeachDirectoryEntry(
|
||||||
|
nullptr, id0_folder,
|
||||||
|
[&result](u64* /*num_entries_out*/, const std::string& directory,
|
||||||
|
const std::string& virtual_name) {
|
||||||
|
if (!FileUtil::IsDirectory(directory + virtual_name + "/")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!std::regex_match(virtual_name, IdRegex)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
result = virtual_name;
|
||||||
|
return false; // halt searching
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.empty()) {
|
||||||
|
LOG_ERROR(Core, "Could not find ID1 folder in {}", id0_folder);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return id0_folder + result + "/";
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Config> LoadPresetConfig(std::string mount_point) {
|
std::vector<Config> LoadPresetConfig(std::string mount_point) {
|
||||||
if (mount_point.back() != '/' && mount_point.back() != '\\') {
|
if (mount_point.back() != '/' && mount_point.back() != '\\') {
|
||||||
mount_point += '/';
|
mount_point += '/';
|
||||||
@@ -1148,80 +1173,89 @@ std::vector<Config> LoadPresetConfig(std::string mount_point) {
|
|||||||
|
|
||||||
Config config_template{};
|
Config config_template{};
|
||||||
config_template.user_path = FileUtil::GetUserPath(FileUtil::UserPath::UserDir);
|
config_template.user_path = FileUtil::GetUserPath(FileUtil::UserPath::UserDir);
|
||||||
|
if (!FileUtil::Exists(mount_point + "threeSD/")) {
|
||||||
// Load dumped data paths if using our dumper
|
// Still return the config for display in frontend to notify the user
|
||||||
if (FileUtil::Exists(mount_point + "threeSD/")) {
|
return {config_template};
|
||||||
#define LOAD_DATA(var, path) \
|
|
||||||
if (FileUtil::Exists(mount_point + "threeSD/" + path)) { \
|
|
||||||
config_template.var = mount_point + "threeSD/" + path; \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LOAD_DATA(movable_sed_path, MOVABLE_SED);
|
// Check version first
|
||||||
LOAD_DATA(bootrom_path, BOOTROM9);
|
|
||||||
LOAD_DATA(certs_db_path, CERTS_DB);
|
|
||||||
LOAD_DATA(nand_title_db_path, TITLE_DB);
|
|
||||||
LOAD_DATA(ticket_db_path, TICKET_DB);
|
|
||||||
LOAD_DATA(seed_db_path, SEED_DB);
|
|
||||||
LOAD_DATA(secret_sector_path, SECRET_SECTOR);
|
|
||||||
LOAD_DATA(system_titles_path, "title/");
|
|
||||||
LOAD_DATA(nand_data_path, "data/");
|
|
||||||
#undef LOAD_DATA
|
|
||||||
|
|
||||||
// encTitleKeys.bin
|
|
||||||
if (FileUtil::Exists(mount_point + "gm9/support/" ENC_TITLE_KEYS_BIN)) {
|
|
||||||
config_template.enc_title_keys_bin_path =
|
|
||||||
mount_point + "gm9/support/" ENC_TITLE_KEYS_BIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load version
|
|
||||||
if (FileUtil::Exists(mount_point + "threeSD/version.txt")) {
|
if (FileUtil::Exists(mount_point + "threeSD/version.txt")) {
|
||||||
std::ifstream stream;
|
std::ifstream stream;
|
||||||
OpenFStream(stream, mount_point + "threeSD/version.txt", std::ios::in);
|
OpenFStream(stream, mount_point + "threeSD/version.txt", std::ios::in);
|
||||||
stream >> config_template.version;
|
stream >> config_template.version;
|
||||||
}
|
}
|
||||||
|
if (config_template.version != CurrentDumperVersion) {
|
||||||
|
return {config_template}; // Notify the user
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regex for 3DS ID0 and ID1
|
#define LOAD_DATA(var, path) \
|
||||||
const std::regex id_regex{"[0-9a-f]{32}"};
|
if (FileUtil::Exists(mount_point + path)) { \
|
||||||
|
config_template.var = mount_point + path; \
|
||||||
|
}
|
||||||
|
|
||||||
// Load SDMC dir
|
// General data
|
||||||
std::vector<Config> out;
|
LOAD_DATA(bootrom_path, "threeSD/" BOOTROM9);
|
||||||
const auto ProcessDirectory = [&id_regex, &config_template, &out](const std::string& path) {
|
LOAD_DATA(secret_sector_path, "threeSD/" SECRET_SECTOR);
|
||||||
return FileUtil::ForeachDirectoryEntry(
|
LOAD_DATA(enc_title_keys_bin_path, "gm9/support/" ENC_TITLE_KEYS_BIN);
|
||||||
nullptr, path,
|
#undef LOAD_DATA
|
||||||
[&id_regex, &config_template, &out](u64* /*num_entries_out*/,
|
|
||||||
const std::string& directory,
|
// Load NANDs
|
||||||
|
std::vector<Config::NandConfig> nands;
|
||||||
|
const auto Callback = [&nands](u64* /*num_entries_out*/, const std::string& directory,
|
||||||
const std::string& virtual_name) {
|
const std::string& virtual_name) {
|
||||||
if (!FileUtil::IsDirectory(directory + virtual_name + "/")) {
|
const std::string nand_dir = directory + virtual_name + "/";
|
||||||
|
if (!FileUtil::IsDirectory(nand_dir)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!std::regex_match(virtual_name, id_regex)) {
|
Config::NandConfig config;
|
||||||
return true;
|
config.nand_name = virtual_name;
|
||||||
}
|
|
||||||
|
|
||||||
Config config = config_template;
|
#define LOAD_DATA(var, path) \
|
||||||
config.sdmc_path = directory + virtual_name + "/";
|
if (FileUtil::Exists(nand_dir + path)) { \
|
||||||
out.push_back(config);
|
config.var = nand_dir + path; \
|
||||||
|
}
|
||||||
|
LOAD_DATA(movable_sed_path, MOVABLE_SED);
|
||||||
|
LOAD_DATA(certs_db_path, CERTS_DB);
|
||||||
|
LOAD_DATA(ticket_db_path, TICKET_DB);
|
||||||
|
LOAD_DATA(title_db_path, TITLE_DB);
|
||||||
|
LOAD_DATA(seed_db_path, SEED_DB);
|
||||||
|
LOAD_DATA(title_path, "title/");
|
||||||
|
LOAD_DATA(data_path, "data/");
|
||||||
|
#undef LOAD_DATA
|
||||||
|
|
||||||
|
nands.emplace_back(std::move(config));
|
||||||
return true;
|
return true;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
FileUtil::ForeachDirectoryEntry(nullptr, mount_point + "threeSD/", Callback);
|
||||||
|
|
||||||
FileUtil::ForeachDirectoryEntry(
|
// Group NAND configs by ID0 to generate configs
|
||||||
nullptr, mount_point + "Nintendo 3DS/",
|
std::map<std::string, Config> config_map; // id0 -> config
|
||||||
[&id_regex, &ProcessDirectory](u64* /*num_entries_out*/, const std::string& directory,
|
for (const auto& nand : nands) {
|
||||||
const std::string& virtual_name) {
|
const auto id0 = GetID0(nand.movable_sed_path);
|
||||||
if (!FileUtil::IsDirectory(directory + virtual_name + "/")) {
|
if (id0.empty()) {
|
||||||
return true;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!std::regex_match(virtual_name, id_regex)) {
|
if (!config_map.count(id0)) {
|
||||||
return true;
|
// Create config for this ID0
|
||||||
|
config_map[id0] = config_template;
|
||||||
|
config_map[id0].id0 = id0;
|
||||||
|
|
||||||
|
auto sdmc_path = GetSDMCPath(fmt::format("{}Nintendo 3DS/{}/", mount_point, id0));
|
||||||
|
if (sdmc_path.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
config_map[id0].sdmc_path = std::move(sdmc_path);
|
||||||
|
}
|
||||||
|
config_map[id0].nands.emplace_back(nand);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ProcessDirectory(directory + virtual_name + "/");
|
// Convert to a vector
|
||||||
});
|
std::vector<Config> out;
|
||||||
|
for (auto& [id0, config] : config_map) {
|
||||||
|
out.emplace_back(std::move(config));
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+36
-18
@@ -58,35 +58,50 @@ struct ContentSpecifier {
|
|||||||
* All paths to directories shall end with a '/' (will be automatically added when not present)
|
* All paths to directories shall end with a '/' (will be automatically added when not present)
|
||||||
*/
|
*/
|
||||||
struct Config {
|
struct Config {
|
||||||
std::string sdmc_path; ///< SDMC root path ("Nintendo 3DS/<ID0>/<ID1>")
|
int version = 0; ///< Version of the dumper used.
|
||||||
|
|
||||||
std::string user_path; ///< Target user path of Citra
|
std::string user_path; ///< Target user path of Citra
|
||||||
|
std::string sdmc_path; ///< SDMC root path ("Nintendo 3DS/<ID0>/<ID1>")
|
||||||
|
std::string id0; ///< ID0 of the SDMC used in this configuration.
|
||||||
|
|
||||||
// Necessary system files keys are loaded from.
|
// Necessary system files
|
||||||
std::string movable_sed_path; ///< Path to movable.sed
|
|
||||||
std::string bootrom_path; ///< Path to bootrom (boot9.bin) (Sysdata 0)
|
std::string bootrom_path; ///< Path to bootrom (boot9.bin) (Sysdata 0)
|
||||||
std::string certs_db_path; ///< Path to certs.db. Used while building CIA.
|
std::string secret_sector_path; ///< Path to secret sector (New3DS only) (Sysdata 2)
|
||||||
|
|
||||||
// Optional, used while building CIA, but usually missing these files won't hinder CIA building.
|
|
||||||
std::string nand_title_db_path; ///< Path to NAND title.db. Entirely optional.
|
|
||||||
std::string ticket_db_path; ///< Path to ticket.db. Entirely optional.
|
|
||||||
std::string enc_title_keys_bin_path; ///< Path to encTitleKeys.bin. Entirely optional.
|
std::string enc_title_keys_bin_path; ///< Path to encTitleKeys.bin. Entirely optional.
|
||||||
|
|
||||||
// The following system files are optional for importing and are only copied so that Citra
|
struct NandConfig {
|
||||||
// will be able to decrypt imported encrypted ROMs.
|
std::string nand_name; ///< Name of the NAND used in this configuration.
|
||||||
|
std::string movable_sed_path; ///< Path to movable.sed
|
||||||
|
|
||||||
std::string seed_db_path; ///< Path to seeddb.bin (Sysdata 1)
|
std::string certs_db_path; ///< Path to certs.db. Used while building CIA.
|
||||||
std::string secret_sector_path; ///< Path to secret sector (New3DS only) (Sysdata 2)
|
std::string ticket_db_path; ///< Path to ticket.db. Entirely optional.
|
||||||
// Note: Sysdata 3 is aes_keys.txt (slot0x25KeyX)
|
std::string title_db_path; ///< Path to NAND title.db. Entirely optional.
|
||||||
|
std::string seed_db_path; ///< Path to seeddb.bin
|
||||||
|
|
||||||
std::string system_titles_path; ///< Path to system titles.
|
std::string title_path; ///< Path to system titles.
|
||||||
std::string nand_data_path; ///< Path to NAND data. (Extdata and savedata)
|
std::string data_path; ///< Path to NAND data. (Extdata and savedata)
|
||||||
|
};
|
||||||
int version = 0; ///< Version of the dumper used.
|
/// A list of NandConfigs with the same ID0 (linked NANDs).
|
||||||
|
/// The order of the NANDs matter: the importer will merge the dbs, but will only load NAND
|
||||||
|
/// titles and data from the *first* NAND.
|
||||||
|
std::vector<NandConfig> nands;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Version of the current dumper.
|
// Version of the current dumper.
|
||||||
constexpr int CurrentDumperVersion = 4;
|
constexpr int CurrentDumperVersion = 4;
|
||||||
|
|
||||||
|
constexpr bool IsConfigGood(const Config& config) {
|
||||||
|
return config.version == CurrentDumperVersion && !config.user_path.empty() &&
|
||||||
|
!config.sdmc_path.empty() && !config.bootrom_path.empty() && !config.nands.empty() &&
|
||||||
|
!config.nands[0].movable_sed_path.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool IsConfigComplete(const Config& config) {
|
||||||
|
// We are skipping the DBs here. Need more work regarding that
|
||||||
|
return IsConfigGood(config) && !config.nands[0].title_path.empty() &&
|
||||||
|
!config.nands[0].data_path.empty();
|
||||||
|
}
|
||||||
|
|
||||||
class SDMCFile;
|
class SDMCFile;
|
||||||
class NCCHContainer;
|
class NCCHContainer;
|
||||||
|
|
||||||
@@ -94,7 +109,6 @@ class SDMCImporter {
|
|||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Initializes the importer.
|
* Initializes the importer.
|
||||||
* @param root_folder Path to the "Nintendo 3DS/<ID0>/<ID1>" folder.
|
|
||||||
*/
|
*/
|
||||||
explicit SDMCImporter(const Config& config);
|
explicit SDMCImporter(const Config& config);
|
||||||
|
|
||||||
@@ -211,6 +225,7 @@ private:
|
|||||||
|
|
||||||
bool is_good{};
|
bool is_good{};
|
||||||
Config config;
|
Config config;
|
||||||
|
Config::NandConfig nand_config; // Main NAND config
|
||||||
std::unique_ptr<SDMCDecryptor> sdmc_decryptor;
|
std::unique_ptr<SDMCDecryptor> sdmc_decryptor;
|
||||||
FileDecryptor file_decryptor;
|
FileDecryptor file_decryptor;
|
||||||
|
|
||||||
@@ -227,6 +242,9 @@ private:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Look for and load preset config for a SD card mounted at mount_point.
|
* Look for and load preset config for a SD card mounted at mount_point.
|
||||||
|
* Note: This returns only one config per ID0.
|
||||||
|
* The frontend should allow the user to change the order of the NANDs.
|
||||||
|
*
|
||||||
* @return a list of preset config available. can be empty
|
* @return a list of preset config available. can be empty
|
||||||
*/
|
*/
|
||||||
std::vector<Config> LoadPresetConfig(std::string mount_point);
|
std::vector<Config> LoadPresetConfig(std::string mount_point);
|
||||||
|
|||||||
+12
-40
@@ -31,17 +31,6 @@ Q_IMPORT_PLUGIN(QWindowsVistaStylePlugin)
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool IsConfigGood(const Core::Config& config) {
|
|
||||||
return !config.sdmc_path.empty() && !config.user_path.empty() &&
|
|
||||||
!config.movable_sed_path.empty() && !config.bootrom_path.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsConfigComplete(const Core::Config& config) {
|
|
||||||
return IsConfigGood(config) && !config.certs_db_path.empty() &&
|
|
||||||
!config.nand_title_db_path.empty() && !config.ticket_db_path.empty() &&
|
|
||||||
!config.system_titles_path.empty() && !config.nand_data_path.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
MainDialog::MainDialog(QWidget* parent)
|
MainDialog::MainDialog(QWidget* parent)
|
||||||
: DPIAwareDialog(parent, 640, 256), ui(std::make_unique<Ui::MainDialog>()) {
|
: DPIAwareDialog(parent, 640, 256), ui(std::make_unique<Ui::MainDialog>()) {
|
||||||
|
|
||||||
@@ -116,8 +105,6 @@ void MainDialog::SetContentSizes(int previous_width, int previous_height) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const std::regex sdmc_path_regex{"(.+)([/\\\\])Nintendo 3DS/([0-9a-f]{32})/([0-9a-f]{32})/"};
|
|
||||||
|
|
||||||
void MainDialog::LoadPresetConfig() {
|
void MainDialog::LoadPresetConfig() {
|
||||||
ui->main->clear();
|
ui->main->clear();
|
||||||
preset_config_list.clear();
|
preset_config_list.clear();
|
||||||
@@ -137,24 +124,16 @@ void MainDialog::LoadPresetConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get ID0
|
// Get ID0
|
||||||
QString id0 = tr("Unknown");
|
QString id0 = QString::fromStdString(list[i].id0);
|
||||||
std::smatch match;
|
|
||||||
if (std::regex_match(list[i].sdmc_path, match, sdmc_path_regex)) {
|
|
||||||
if (match.size() >= 5) {
|
|
||||||
id0 = QString::fromStdString(match[3].str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get status
|
// Get status
|
||||||
QString status = tr("Good");
|
QString status = tr("Good");
|
||||||
if (!IsConfigGood(list[i])) {
|
if (list[i].version != Core::CurrentDumperVersion) {
|
||||||
status = tr("No Configuration Found");
|
|
||||||
} else if (list[i].version != Core::CurrentDumperVersion) {
|
|
||||||
status = tr("Version Dismatch");
|
status = tr("Version Dismatch");
|
||||||
|
} else if (!IsConfigGood(list[i])) {
|
||||||
|
status = tr("No Configuration Found");
|
||||||
} else if (!IsConfigComplete(list[i])) {
|
} else if (!IsConfigComplete(list[i])) {
|
||||||
status = tr("Missing System Files");
|
status = tr("Missing System Files");
|
||||||
} else if (list[i].seed_db_path.empty()) {
|
|
||||||
status = tr("Good, Missing Seeds");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* item = new QTreeWidgetItem{{path, id0, status}};
|
auto* item = new QTreeWidgetItem{{path, id0, status}};
|
||||||
@@ -201,7 +180,14 @@ void MainDialog::LaunchImportDialog() {
|
|||||||
config = preset_config_list.at(index);
|
config = preset_config_list.at(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check config integrity
|
// Display info regarding status
|
||||||
|
if (config.version != Core::CurrentDumperVersion) {
|
||||||
|
QMessageBox::critical(this, tr("Version Dismatch"),
|
||||||
|
tr("You are using an unsupported version of threeSDumper.<br>Please "
|
||||||
|
"ensure that you are using the most recent version of both "
|
||||||
|
"threeSD and threeSDumper and try again."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!IsConfigGood(config)) {
|
if (!IsConfigGood(config)) {
|
||||||
QMessageBox::critical(
|
QMessageBox::critical(
|
||||||
this, tr("Error"),
|
this, tr("Error"),
|
||||||
@@ -211,15 +197,6 @@ void MainDialog::LaunchImportDialog() {
|
|||||||
"guide</a> correctly."));
|
"guide</a> correctly."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.version != Core::CurrentDumperVersion) {
|
|
||||||
QMessageBox::critical(this, tr("Version Dismatch"),
|
|
||||||
tr("You are using an unsupported version of threeSDumper.<br>Please "
|
|
||||||
"ensure that you are using the most recent version of both "
|
|
||||||
"threeSD and threeSDumper and try again."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsConfigComplete(config)) {
|
if (!IsConfigComplete(config)) {
|
||||||
QMessageBox::warning(
|
QMessageBox::warning(
|
||||||
this, tr("Warning"),
|
this, tr("Warning"),
|
||||||
@@ -227,11 +204,6 @@ void MainDialog::LaunchImportDialog() {
|
|||||||
"may not be importable, or may not run.<br>Please check if you have followed the <a "
|
"may not be importable, or may not run.<br>Please check if you have followed the <a "
|
||||||
"href='https://github.com/zhaowenlan1779/threeSD/wiki/Quickstart-Guide'>guide</a> "
|
"href='https://github.com/zhaowenlan1779/threeSD/wiki/Quickstart-Guide'>guide</a> "
|
||||||
"correctly."));
|
"correctly."));
|
||||||
} else if (config.seed_db_path.empty()) {
|
|
||||||
QMessageBox::warning(this, tr("Warning"),
|
|
||||||
tr("Seed database is missing from your configuration.<br>Your system "
|
|
||||||
"likely does not have any seeds.<br>However, if it does have any, "
|
|
||||||
"imported games using seed encryption may not work."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImportDialog dialog(this, config);
|
ImportDialog dialog(this, config);
|
||||||
|
|||||||
Reference in New Issue
Block a user