diff --git a/src/core/importer.cpp b/src/core/importer.cpp index 71426b4..e8fb146 100644 --- a/src/core/importer.cpp +++ b/src/core/importer.cpp @@ -561,8 +561,53 @@ TitleData LoadTitleData(NCCHContainer& ncch) { encryption, seed_crypto, smdh.GetIcon(false)}; } -bool SDMCImporter::DumpCXI(const ContentSpecifier& specifier, const std::string& destination, - const Common::ProgressCallback& callback) { +static std::string NormalizeFilename(std::string filename) { + static constexpr std::array IllegalCharacters{ + {':', '/', '\\', '"', '*', '?', '\n', '\r'}}; + + const auto pred = [](char c) { + return std::ranges::find(IllegalCharacters, c) != IllegalCharacters.end(); + }; + std::ranges::replace_if(filename, pred, ' '); + + std::string result; + for (std::size_t i = 0; i < filename.size(); ++i) { + if (i < filename.size() - 1 && filename[i] == ' ' && filename[i + 1] == ' ') { + continue; + } + result.push_back(filename[i]); + } + return result; +} + +static std::string GetTitleFileName(NCCHContainer& ncch) { + std::string codeset_name; + ncch.ReadCodesetName(codeset_name); + + std::string product_code; + ncch.ReadProductCode(product_code); + + u64 program_id{}; + ncch.ReadProgramId(program_id); + + std::vector smdh_buffer; + if (ncch.LoadSectionExeFS("icon", smdh_buffer) != ResultStatus::Success || + smdh_buffer.size() != sizeof(SMDH)) { + LOG_WARNING(Core, "Failed to load icon in ExeFS or size incorrect"); + return NormalizeFilename( + fmt::format("{:016x} {} ({})", program_id, codeset_name, product_code)); + } else { + SMDH smdh; + std::memcpy(&smdh, smdh_buffer.data(), smdh_buffer.size()); + const auto short_title = + Common::UTF16BufferToUTF8(smdh.GetShortTitle(SMDH::TitleLanguage::English)); + return NormalizeFilename(fmt::format("{:016x} {} ({}) ({})", program_id, short_title, + product_code, smdh.GetRegionString())); + } +} + +bool SDMCImporter::DumpCXI(const ContentSpecifier& specifier, std::string destination, + const Common::ProgressCallback& callback, bool auto_filename) { if (specifier.type != ContentType::Application) { LOG_ERROR(Core, "Unsupported specifier type {}", static_cast(specifier.type)); @@ -580,6 +625,13 @@ bool SDMCImporter::DumpCXI(const ContentSpecifier& specifier, const std::string& dump_cxi_ncch = std::make_unique( std::make_shared(config.sdmc_path, boot_content_path, "rb")); + if (auto_filename) { + if (destination.back() != '/' && destination.back() != '\\') { + destination.push_back('/'); + } + destination.append(GetTitleFileName(*dump_cxi_ncch)).append(".cxi"); + } + if (!FileUtil::CreateFullPath(destination)) { LOG_ERROR(Core, "Failed to create path {}", destination); return false; @@ -593,8 +645,8 @@ void SDMCImporter::AbortDumpCXI() { dump_cxi_ncch->AbortDecryptToFile(); } -bool SDMCImporter::BuildCIA(const ContentSpecifier& specifier, const std::string& destination, - const Common::ProgressCallback& callback) { +bool SDMCImporter::BuildCIA(const ContentSpecifier& specifier, std::string destination, + const Common::ProgressCallback& callback, bool auto_filename) { if (config.certs_db_path.empty()) { LOG_ERROR(Core, "Missing certs.db"); @@ -621,6 +673,22 @@ bool SDMCImporter::BuildCIA(const ContentSpecifier& specifier, const std::string : fmt::format("{}title/{:08x}/{:08x}/content/", config.sdmc_path, (specifier.id >> 32), (specifier.id & 0xFFFFFFFF)); + if (auto_filename) { + if (destination.back() != '/' && destination.back() != '\\') { + destination.push_back('/'); + } + const auto boot_content_path = + fmt::format("{}{:08x}.app", physical_path, tmd.GetBootContentID()); + if (is_nand) { + NCCHContainer ncch(std::make_shared(boot_content_path, "rb")); + destination.append(GetTitleFileName(ncch)).append(".cia"); + } else { + const auto relative_path = boot_content_path.substr(config.sdmc_path.size() - 1); + NCCHContainer ncch(std::make_shared(config.sdmc_path, relative_path, "rb")); + destination.append(GetTitleFileName(ncch)).append(".cia"); + } + } + bool ret = cia_builder->Init(destination, tmd, config.certs_db_path, FileUtil::GetDirectoryTreeSize(physical_path), callback); if (!ret) { diff --git a/src/core/importer.h b/src/core/importer.h index ae2535d..63ffe4c 100644 --- a/src/core/importer.h +++ b/src/core/importer.h @@ -127,9 +127,8 @@ public: * Blocks, but can be aborted on another thread. * @return true on success, false otherwise */ - bool DumpCXI( - const ContentSpecifier& specifier, const std::string& destination, - const Common::ProgressCallback& callback = [](std::size_t, std::size_t) {}); + bool DumpCXI(const ContentSpecifier& specifier, std::string destination, + const Common::ProgressCallback& callback, bool auto_filename = false); /** * Aborts current CXI dumping. @@ -141,9 +140,8 @@ public: * Blocks, but can be aborted on another thread. * @return true on success, false otherwise */ - bool BuildCIA( - const ContentSpecifier& specifier, const std::string& destination, - const Common::ProgressCallback& callback = [](std::size_t, std::size_t) {}); + bool BuildCIA(const ContentSpecifier& specifier, std::string destination, + const Common::ProgressCallback& callback, bool auto_filename = false); /** * Aborts current CIA building diff --git a/src/core/ncch/ncch_container.cpp b/src/core/ncch/ncch_container.cpp index e2d6c63..134548b 100644 --- a/src/core/ncch/ncch_container.cpp +++ b/src/core/ncch/ncch_container.cpp @@ -371,6 +371,17 @@ ResultStatus NCCHContainer::ReadCodesetName(std::string& name) { return ResultStatus::Success; } +ResultStatus NCCHContainer::ReadProductCode(std::string& product_code) { + ResultStatus result = Load(); + if (result != ResultStatus::Success) + return result; + + std::array data{}; + std::memcpy(data.data(), ncch_header.product_code, 16); + product_code = data.data(); + return ResultStatus::Success; +} + ResultStatus NCCHContainer::ReadEncryptionType(EncryptionType& encryption) { ResultStatus result = Load(); if (result != ResultStatus::Success) diff --git a/src/core/ncch/ncch_container.h b/src/core/ncch/ncch_container.h index b145c0a..ab8fa2b 100644 --- a/src/core/ncch/ncch_container.h +++ b/src/core/ncch/ncch_container.h @@ -257,6 +257,12 @@ public: */ ResultStatus ReadCodesetName(std::string& name); + /** + * Reads the product code. + * @return ResultStatus result of function. + */ + ResultStatus ReadProductCode(std::string& name); + /** * Gets encryption type (which key is used). * @return ResultStatus result of function. diff --git a/src/core/ncch/smdh.cpp b/src/core/ncch/smdh.cpp index 011799a..5354e7f 100644 --- a/src/core/ncch/smdh.cpp +++ b/src/core/ncch/smdh.cpp @@ -88,20 +88,25 @@ std::array SMDH::GetShortTitle(Core::SMDH::TitleLanguage language) co return titles[static_cast(language)].short_title; } -std::vector SMDH::GetRegions() const { - if (region_lockout == 0x7fffffff) { - return std::vector{GameRegion::RegionFree}; - } - +std::string SMDH::GetRegionString() const { constexpr u32 REGION_COUNT = 7; - std::vector result; + + // JPN/USA/EUR/Australia/CHN/KOR/TWN + // Australia does not have a symbol because it's practically the same as Europe + static const std::array RegionSymbols{ + {"J", "U", "E", "", "C", "K", "T"}}; + + std::string region_string; for (u32 region = 0; region < REGION_COUNT; ++region) { if (region_lockout & (1 << region)) { - result.push_back(static_cast(region)); + region_string.append(RegionSymbols[region]); } } - return result; + if (region_string == "JUECKT") { + return "W"; + } + return region_string; } } // namespace Core diff --git a/src/core/ncch/smdh.h b/src/core/ncch/smdh.h index 606428a..7c72932 100644 --- a/src/core/ncch/smdh.h +++ b/src/core/ncch/smdh.h @@ -62,17 +62,6 @@ struct SMDH { TraditionalChinese = 11 }; - enum class GameRegion { - Japan = 0, - NorthAmerica = 1, - Europe = 2, - Australia = 3, - China = 4, - Korea = 5, - Taiwan = 6, - RegionFree = 7, - }; - /** * Gets game icon from SMDH * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) @@ -87,7 +76,8 @@ struct SMDH { */ std::array GetShortTitle(Core::SMDH::TitleLanguage language) const; - std::vector GetRegions() const; + /// Gets a string representing the supported regions. + std::string GetRegionString() const; }; static_assert(sizeof(SMDH) == 0x36C0, "SMDH structure size is wrong"); diff --git a/src/frontend/import_dialog.cpp b/src/frontend/import_dialog.cpp index d87c7b5..5e29d65 100644 --- a/src/frontend/import_dialog.cpp +++ b/src/frontend/import_dialog.cpp @@ -738,13 +738,6 @@ void ImportDialog::StartDumpingCXISingle(const Core::ContentSpecifier& specifier RunSimpleJob(job); } -static std::string GetCXIFileName(const Core::ContentSpecifier& specifier) { - return QStringLiteral("%1 (%2).cxi") - .arg(QString::fromStdString(specifier.name)) - .arg(specifier.id, 16, 16, QLatin1Char('0')) - .toStdString(); -} - void ImportDialog::StartBatchDumpingCXI() { auto to_import = GetSelectedContentList(); if (to_import.empty()) { @@ -791,11 +784,10 @@ void ImportDialog::StartBatchDumpingCXI() { this, importer, std::move(to_import), [path](Core::SDMCImporter& importer, const Core::ContentSpecifier& specifier, const Common::ProgressCallback& callback) { - return importer.DumpCXI(specifier, path.toStdString() + GetCXIFileName(specifier), - callback); + return importer.DumpCXI(specifier, path.toStdString(), callback, true); }, [path](Core::SDMCImporter& /*importer*/, const Core::ContentSpecifier& specifier) { - FileUtil::Delete(path.toStdString() + GetCXIFileName(specifier)); + // TODO: FileUtil::Delete(path.toStdString() + GetCXIFileName(specifier)); }, &Core::SDMCImporter::AbortDumpCXI); RunMultiJob(job, total_count, total_size); @@ -824,13 +816,6 @@ void ImportDialog::StartBuildingCIASingle(const Core::ContentSpecifier& specifie RunSimpleJob(job); } -static std::string GetCIAFileName(const Core::ContentSpecifier& specifier) { - return QStringLiteral("%1 (%2).cia") - .arg(QString::fromStdString(specifier.name)) - .arg(specifier.id, 16, 16, QLatin1Char('0')) - .toStdString(); -} - void ImportDialog::StartBatchBuildingCIA() { auto to_import = GetSelectedContentList(); if (to_import.empty()) { @@ -881,11 +866,10 @@ void ImportDialog::StartBatchBuildingCIA() { this, importer, std::move(to_import), [path](Core::SDMCImporter& importer, const Core::ContentSpecifier& specifier, const Common::ProgressCallback& callback) { - return importer.BuildCIA(specifier, path.toStdString() + GetCIAFileName(specifier), - callback); + return importer.BuildCIA(specifier, path.toStdString(), callback, true); }, [path](Core::SDMCImporter& /*importer*/, const Core::ContentSpecifier& specifier) { - FileUtil::Delete(path.toStdString() + GetCIAFileName(specifier)); + // TODO: FileUtil::Delete(path.toStdString() + GetCIAFileName(specifier)); }, &Core::SDMCImporter::AbortBuildCIA); RunMultiJob(job, total_count, total_size);