From 3d68baf55ce369883012fbf78666588ff879fbf1 Mon Sep 17 00:00:00 2001 From: Pengfei Date: Sun, 8 Aug 2021 15:52:00 +0800 Subject: [PATCH] Preliminary work for a title info dialog pending DRY fixes, etc --- src/core/importer.cpp | 8 +- src/core/importer.h | 465 ++++++++++++++------------- src/frontend/CMakeLists.txt | 161 +++++----- src/frontend/cia_build_dialog.ui | 250 +++++++------- src/frontend/import_dialog.cpp | 10 +- src/frontend/select_files_dialog.cpp | 114 +++---- src/frontend/title_info_dialog.cpp | 149 +++++++++ src/frontend/title_info_dialog.h | 35 ++ src/frontend/title_info_dialog.ui | 216 +++++++++++++ 9 files changed, 911 insertions(+), 497 deletions(-) create mode 100644 src/frontend/title_info_dialog.cpp create mode 100644 src/frontend/title_info_dialog.h create mode 100644 src/frontend/title_info_dialog.ui diff --git a/src/core/importer.cpp b/src/core/importer.cpp index c2275f5..55e765a 100644 --- a/src/core/importer.cpp +++ b/src/core/importer.cpp @@ -521,6 +521,10 @@ bool SDMCImporter::LoadTMD(ContentType type, u64 id, TitleMetadata& out) const { } } +bool SDMCImporter::LoadTMD(const ContentSpecifier& specifier, TitleMetadata& out) const { + return LoadTMD(specifier.type, specifier.id, out); +} + // English short title name, extdata id, encryption, seed, icon using TitleData = std::tuple>; @@ -659,7 +663,7 @@ void SDMCImporter::AbortDumpCXI() { } bool SDMCImporter::CanBuildLegitCIA(const ContentSpecifier& specifier) const { - if (!CanBuildCIA(specifier.type)) { + if (!IsTitle(specifier.type)) { return false; } @@ -683,7 +687,7 @@ bool SDMCImporter::BuildCIA(CIABuildType build_type, const ContentSpecifier& spe return false; } - if (!CanBuildCIA(specifier.type)) { + if (!IsTitle(specifier.type)) { LOG_ERROR(Core, "Unsupported specifier type {}", static_cast(specifier.type)); return false; } diff --git a/src/core/importer.h b/src/core/importer.h index 0b53a66..15a1b4d 100644 --- a/src/core/importer.h +++ b/src/core/importer.h @@ -1,232 +1,233 @@ -// Copyright 2019 threeSD Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include "common/common_types.h" -#include "common/progress_callback.h" -#include "core/file_sys/cia_common.h" - -namespace Core { - -class CIABuilder; -class SDMCDecryptor; -class TicketDB; -class TitleDB; -class TitleMetadata; - -/** - * Type of an importable content. - * Applications, updates and DLCs are all considered titles. - */ -enum class ContentType { - Application, - Update, - DLC, - Savegame, - Extdata, - SystemArchive, - Sysdata, - SystemTitle, - SystemApplet, // This should belong to System Title, but they cause problems so a new category. -}; - -constexpr bool CanBuildCIA(ContentType type) { - return type == ContentType::Application || type == ContentType::Update || - type == ContentType::DLC || type == ContentType::SystemTitle || - type == ContentType::SystemApplet; -} - -/** - * Encryption type of an importable content. - */ -enum class EncryptionType { - None, - FixedKey, - NCCHSecure1, - NCCHSecure2, - NCCHSecure3, - NCCHSecure4, -}; - -/** - * Struct that specifies an importable content. - */ -struct ContentSpecifier { - ContentType type; - u64 id; - bool already_exists; ///< Tells whether a file already exists in target path. - u64 maximum_size; ///< The maximum size of the content. May be slightly bigger than real size. - std::string name; ///< Optional. The content's preferred display name. - u64 extdata_id; ///< Extdata ID for Applications. - EncryptionType encryption = EncryptionType::None; ///< Only for NCCHs. Encryption scheme. - bool seed_crypto = false; ///< Only for NCCHs. Whether seed crypto is used. - std::vector icon; ///< Optional. The content's icon. -}; - -/** - * A set of values that are used to initialize the importer. - * All paths to directories shall end with a '/' (will be automatically added when not present) - */ -struct Config { - std::string sdmc_path; ///< SDMC root path ("Nintendo 3DS//") - std::string user_path; ///< Target user path of Citra - - // Necessary system files keys are loaded from. - std::string movable_sed_path; ///< Path to movable.sed - std::string bootrom_path; ///< Path to bootrom (boot9.bin) (Sysdata 0) - std::string certs_db_path; ///< Path to certs.db. Used while building CIA. - - // 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. Entireley optional. - - // The following system files are optional for importing and are only copied so that Citra - // will be able to decrypt imported encrypted ROMs. - - std::string safe_mode_firm_path; ///< Path to safe mode firm (A folder) (Sysdata 1) - std::string seed_db_path; ///< Path to seeddb.bin (Sysdata 2) - std::string secret_sector_path; ///< Path to secret sector (New3DS only) (Sysdata 3) - // Note: Sysdata 4 is aes_keys.txt (slot0x25KeyX) - std::string config_savegame_path; ///< Path to config savegame (Sysdata 5) - - std::string system_archives_path; ///< Path to system archives. - std::string system_titles_path; ///< Path to system titles. - std::string nand_data_path; ///< Path to NAND data. (Extdata and savedata) - - int version = 0; ///< Version of the dumper used. -}; - -// Version of the current dumper. -constexpr int CurrentDumperVersion = 4; - -class SDMCFile; -class NCCHContainer; - -class SDMCImporter { -public: - /** - * Initializes the importer. - * @param root_folder Path to the "Nintendo 3DS//" folder. - */ - explicit SDMCImporter(const Config& config); - - ~SDMCImporter(); - - /** - * Imports a specific content by its specifier, deleting it when failed. - * Blocks, but can be aborted on another thread if needed. - * @return true on success, false otherwise - */ - bool ImportContent( - const ContentSpecifier& specifier, - const Common::ProgressCallback& callback = [](u64, u64) {}); - - /** - * Aborts current importing. - */ - void AbortImporting(); - - /** - * Dumps a content to CXI. - * Blocks, but can be aborted on another thread. - * @return true on success, false otherwise - */ - bool DumpCXI(const ContentSpecifier& specifier, std::string destination, - const Common::ProgressCallback& callback, bool auto_filename = false); - - /** - * Aborts current CXI dumping. - */ - void AbortDumpCXI(); - - /** - * Builds a CIA from a content. - * Blocks, but can be aborted on another thread. - * @return true on success, false otherwise - */ - bool BuildCIA(CIABuildType build_type, const ContentSpecifier& specifier, - std::string destination, const Common::ProgressCallback& callback, - bool auto_filename = false); - - /** - * Checks if a content can be built as a legit CIA. - */ - bool CanBuildLegitCIA(const ContentSpecifier& specifier) const; - - /** - * Aborts current CIA building - */ - void AbortBuildCIA(); - - /** - * Gets a list of dumpable content specifiers. - */ - std::vector ListContent() const; - - /** - * Returns whether the importer is in good state. - */ - bool IsGood() const; - -private: - bool Init(); - - // Impl of ImportContent without deleting mechanism. - bool ImportContentImpl( - const ContentSpecifier& specifier, - const Common::ProgressCallback& callback = [](u64, u64) {}); - bool ImportTitle(const ContentSpecifier& specifier, const Common::ProgressCallback& callback); - bool ImportNandTitle(const ContentSpecifier& specifier, - const Common::ProgressCallback& callback); - bool ImportSavegame(u64 id, const Common::ProgressCallback& callback); - bool ImportNandSavegame(u64 id, const Common::ProgressCallback& callback); - bool ImportExtdata(u64 id, const Common::ProgressCallback& callback); - bool ImportNandExtdata(u64 id, const Common::ProgressCallback& callback); - bool ImportSystemArchive(u64 id, const Common::ProgressCallback& callback); - bool ImportSysdata(u64 id, const Common::ProgressCallback& callback); - - void ListTitle(std::vector& out) const; - void ListNandTitle(std::vector& out) const; - void ListNandSavegame(std::vector& out) const; - void ListExtdata(std::vector& out) const; - void ListSystemArchive(std::vector& out) const; - void ListSysdata(std::vector& out) const; - - void DeleteContent(const ContentSpecifier& specifier) const; - void DeleteTitle(u64 id) const; - void DeleteNandTitle(u64 id) const; - void DeleteSavegame(u64 id) const; - void DeleteExtdata(u64 id) const; - void DeleteSystemArchive(u64 id) const; - void DeleteSysdata(u64 id) const; - - bool LoadTMD(ContentType type, u64 id, TitleMetadata& out) const; - - bool is_good{}; - Config config; - std::unique_ptr sdmc_decryptor; - - // Used for CIA building - std::unique_ptr cia_builder; - - // The NCCH used to dump CXIs. - std::unique_ptr dump_cxi_ncch; - - std::unique_ptr sdmc_title_db{}; - std::unique_ptr nand_title_db{}; -}; - -/** - * Look for and load preset config for a SD card mounted at mount_point. - * @return a list of preset config available. can be empty - */ -std::vector LoadPresetConfig(std::string mount_point); - -} // namespace Core +// Copyright 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include "common/common_types.h" +#include "common/progress_callback.h" +#include "core/file_sys/cia_common.h" + +namespace Core { + +class CIABuilder; +class SDMCDecryptor; +class TicketDB; +class TitleDB; +class TitleMetadata; + +/** + * Type of an importable content. + * Applications, updates and DLCs are all considered titles. + */ +enum class ContentType { + Application, + Update, + DLC, + Savegame, + Extdata, + SystemArchive, + Sysdata, + SystemTitle, + SystemApplet, // This should belong to System Title, but they cause problems so a new category. +}; + +constexpr bool IsTitle(ContentType type) { + return type == ContentType::Application || type == ContentType::Update || + type == ContentType::DLC || type == ContentType::SystemTitle || + type == ContentType::SystemApplet; +} + +/** + * Encryption type of an importable content. + */ +enum class EncryptionType { + None, + FixedKey, + NCCHSecure1, + NCCHSecure2, + NCCHSecure3, + NCCHSecure4, +}; + +/** + * Struct that specifies an importable content. + */ +struct ContentSpecifier { + ContentType type; + u64 id; + bool already_exists; ///< Tells whether a file already exists in target path. + u64 maximum_size; ///< The maximum size of the content. May be slightly bigger than real size. + std::string name; ///< Optional. The content's preferred display name. + u64 extdata_id; ///< Extdata ID for Applications. + EncryptionType encryption = EncryptionType::None; ///< Only for NCCHs. Encryption scheme. + bool seed_crypto = false; ///< Only for NCCHs. Whether seed crypto is used. + std::vector icon; ///< Optional. The content's icon. +}; + +/** + * A set of values that are used to initialize the importer. + * All paths to directories shall end with a '/' (will be automatically added when not present) + */ +struct Config { + std::string sdmc_path; ///< SDMC root path ("Nintendo 3DS//") + std::string user_path; ///< Target user path of Citra + + // Necessary system files keys are loaded from. + std::string movable_sed_path; ///< Path to movable.sed + std::string bootrom_path; ///< Path to bootrom (boot9.bin) (Sysdata 0) + std::string certs_db_path; ///< Path to certs.db. Used while building CIA. + + // 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. Entireley optional. + + // The following system files are optional for importing and are only copied so that Citra + // will be able to decrypt imported encrypted ROMs. + + std::string safe_mode_firm_path; ///< Path to safe mode firm (A folder) (Sysdata 1) + std::string seed_db_path; ///< Path to seeddb.bin (Sysdata 2) + std::string secret_sector_path; ///< Path to secret sector (New3DS only) (Sysdata 3) + // Note: Sysdata 4 is aes_keys.txt (slot0x25KeyX) + std::string config_savegame_path; ///< Path to config savegame (Sysdata 5) + + std::string system_archives_path; ///< Path to system archives. + std::string system_titles_path; ///< Path to system titles. + std::string nand_data_path; ///< Path to NAND data. (Extdata and savedata) + + int version = 0; ///< Version of the dumper used. +}; + +// Version of the current dumper. +constexpr int CurrentDumperVersion = 4; + +class SDMCFile; +class NCCHContainer; + +class SDMCImporter { +public: + /** + * Initializes the importer. + * @param root_folder Path to the "Nintendo 3DS//" folder. + */ + explicit SDMCImporter(const Config& config); + + ~SDMCImporter(); + + /** + * Imports a specific content by its specifier, deleting it when failed. + * Blocks, but can be aborted on another thread if needed. + * @return true on success, false otherwise + */ + bool ImportContent( + const ContentSpecifier& specifier, + const Common::ProgressCallback& callback = [](u64, u64) {}); + + /** + * Aborts current importing. + */ + void AbortImporting(); + + /** + * Dumps a content to CXI. + * Blocks, but can be aborted on another thread. + * @return true on success, false otherwise + */ + bool DumpCXI(const ContentSpecifier& specifier, std::string destination, + const Common::ProgressCallback& callback, bool auto_filename = false); + + /** + * Aborts current CXI dumping. + */ + void AbortDumpCXI(); + + /** + * Builds a CIA from a content. + * Blocks, but can be aborted on another thread. + * @return true on success, false otherwise + */ + bool BuildCIA(CIABuildType build_type, const ContentSpecifier& specifier, + std::string destination, const Common::ProgressCallback& callback, + bool auto_filename = false); + + /** + * Checks if a content can be built as a legit CIA. + */ + bool CanBuildLegitCIA(const ContentSpecifier& specifier) const; + + /** + * Aborts current CIA building + */ + void AbortBuildCIA(); + + /** + * Gets a list of dumpable content specifiers. + */ + std::vector ListContent() const; + + /** + * Returns whether the importer is in good state. + */ + bool IsGood() const; + + bool LoadTMD(ContentType type, u64 id, TitleMetadata& out) const; + bool LoadTMD(const ContentSpecifier& specifier, TitleMetadata& out) const; + +private: + bool Init(); + + // Impl of ImportContent without deleting mechanism. + bool ImportContentImpl( + const ContentSpecifier& specifier, + const Common::ProgressCallback& callback = [](u64, u64) {}); + bool ImportTitle(const ContentSpecifier& specifier, const Common::ProgressCallback& callback); + bool ImportNandTitle(const ContentSpecifier& specifier, + const Common::ProgressCallback& callback); + bool ImportSavegame(u64 id, const Common::ProgressCallback& callback); + bool ImportNandSavegame(u64 id, const Common::ProgressCallback& callback); + bool ImportExtdata(u64 id, const Common::ProgressCallback& callback); + bool ImportNandExtdata(u64 id, const Common::ProgressCallback& callback); + bool ImportSystemArchive(u64 id, const Common::ProgressCallback& callback); + bool ImportSysdata(u64 id, const Common::ProgressCallback& callback); + + void ListTitle(std::vector& out) const; + void ListNandTitle(std::vector& out) const; + void ListNandSavegame(std::vector& out) const; + void ListExtdata(std::vector& out) const; + void ListSystemArchive(std::vector& out) const; + void ListSysdata(std::vector& out) const; + + void DeleteContent(const ContentSpecifier& specifier) const; + void DeleteTitle(u64 id) const; + void DeleteNandTitle(u64 id) const; + void DeleteSavegame(u64 id) const; + void DeleteExtdata(u64 id) const; + void DeleteSystemArchive(u64 id) const; + void DeleteSysdata(u64 id) const; + + bool is_good{}; + Config config; + std::unique_ptr sdmc_decryptor; + + // Used for CIA building + std::unique_ptr cia_builder; + + // The NCCH used to dump CXIs. + std::unique_ptr dump_cxi_ncch; + + std::unique_ptr sdmc_title_db{}; + std::unique_ptr nand_title_db{}; +}; + +/** + * Look for and load preset config for a SD card mounted at mount_point. + * @return a list of preset config available. can be empty + */ +std::vector LoadPresetConfig(std::string mount_point); + +} // namespace Core diff --git a/src/frontend/CMakeLists.txt b/src/frontend/CMakeLists.txt index 2c600cc..78d73d5 100644 --- a/src/frontend/CMakeLists.txt +++ b/src/frontend/CMakeLists.txt @@ -1,79 +1,82 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_INCLUDE_CURRENT_DIR ON) -if (POLICY CMP0071) - cmake_policy(SET CMP0071 NEW) -endif() - -file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/themes/*) - -add_executable(threeSD - helpers/multi_job.cpp - helpers/multi_job.h - helpers/simple_job.cpp - helpers/simple_job.h - cia_build_dialog.cpp - cia_build_dialog.h - cia_build_dialog.ui - import_dialog.cpp - import_dialog.h - import_dialog.ui - main.cpp - main.h - main.ui - select_files_dialog.cpp - select_files_dialog.h - select_files_dialog.ui - utilities.cpp - utilities.h - utilities.ui - ${THEMES} -) - -target_link_libraries(threeSD PRIVATE common core) -target_link_libraries(threeSD PRIVATE Qt5::Widgets) -target_link_libraries(threeSD PRIVATE qdevicewatcher) -target_link_libraries(threeSD PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) - -if (APPLE) - # set(MACOSX_ICON "../../dist/citra.icns") - # set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - # target_sources(threeSD PRIVATE ${MACOSX_ICON}) - set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE TRUE) - set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) -elseif(WIN32) - # compile as a win32 gui application instead of a console application - target_link_libraries(threeSD PRIVATE Qt5::WinMain) - if(MSVC) - set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") - elseif(MINGW) - set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "-mwindows") - endif() -elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") - # In Ubuntu, the executable would be recognized as a shared library otherwise. - set_target_properties(threeSD PROPERTIES LINK_FLAGS "-no-pie") -endif() - -target_compile_definitions(threeSD PRIVATE - # Use QStringBuilder for string concatenation to reduce - # the overall number of temporary strings created. - -DQT_USE_QSTRINGBUILDER - - # Disable implicit type narrowing in signal/slot connect() calls. - -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT - - # Disable unsafe overloads of QProcess' start() function. - -DQT_NO_PROCESS_COMBINED_ARGUMENT_START - - # Disable implicit QString->QUrl conversions to enforce use of proper resolving functions. - -DQT_NO_URL_CAST_FROM_STRING - - # Disable automatic conversions from 8-bit strings (char *) to unicode QStrings - -DQT_NO_CAST_FROM_ASCII -) - -if (MSVC) - include(CopyQt5Deps) - copy_Qt5_deps(threeSD) -endif() +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +if (POLICY CMP0071) + cmake_policy(SET CMP0071 NEW) +endif() + +file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/themes/*) + +add_executable(threeSD + helpers/multi_job.cpp + helpers/multi_job.h + helpers/simple_job.cpp + helpers/simple_job.h + cia_build_dialog.cpp + cia_build_dialog.h + cia_build_dialog.ui + import_dialog.cpp + import_dialog.h + import_dialog.ui + main.cpp + main.h + main.ui + select_files_dialog.cpp + select_files_dialog.h + select_files_dialog.ui + title_info_dialog.cpp + title_info_dialog.h + title_info_dialog.ui + utilities.cpp + utilities.h + utilities.ui + ${THEMES} +) + +target_link_libraries(threeSD PRIVATE common core) +target_link_libraries(threeSD PRIVATE Qt5::Widgets) +target_link_libraries(threeSD PRIVATE qdevicewatcher) +target_link_libraries(threeSD PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) + +if (APPLE) + # set(MACOSX_ICON "../../dist/citra.icns") + # set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + # target_sources(threeSD PRIVATE ${MACOSX_ICON}) + set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE TRUE) + set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) +elseif(WIN32) + # compile as a win32 gui application instead of a console application + target_link_libraries(threeSD PRIVATE Qt5::WinMain) + if(MSVC) + set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") + elseif(MINGW) + set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "-mwindows") + endif() +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + # In Ubuntu, the executable would be recognized as a shared library otherwise. + set_target_properties(threeSD PROPERTIES LINK_FLAGS "-no-pie") +endif() + +target_compile_definitions(threeSD PRIVATE + # Use QStringBuilder for string concatenation to reduce + # the overall number of temporary strings created. + -DQT_USE_QSTRINGBUILDER + + # Disable implicit type narrowing in signal/slot connect() calls. + -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT + + # Disable unsafe overloads of QProcess' start() function. + -DQT_NO_PROCESS_COMBINED_ARGUMENT_START + + # Disable implicit QString->QUrl conversions to enforce use of proper resolving functions. + -DQT_NO_URL_CAST_FROM_STRING + + # Disable automatic conversions from 8-bit strings (char *) to unicode QStrings + -DQT_NO_CAST_FROM_ASCII +) + +if (MSVC) + include(CopyQt5Deps) + copy_Qt5_deps(threeSD) +endif() diff --git a/src/frontend/cia_build_dialog.ui b/src/frontend/cia_build_dialog.ui index 22f7947..c8da2b6 100644 --- a/src/frontend/cia_build_dialog.ui +++ b/src/frontend/cia_build_dialog.ui @@ -1,125 +1,125 @@ - - - CIABuildDialog - - - - 0 - 0 - 510 - 260 - - - - Build CIA - - - - - - - - Destination: - - - - - - - - - - - 0 - 0 - - - - ... - - - - - - - - - Build Type - - - - - - Standard - - - true - - - - - - - true - - - Recommended for general use.<br>Decrypted CIA with decrypted contents and standard ticket. - - - - - - - Legit with standard ticket ("pirate legit") - - - - - - - true - - - Encrypted CIA with legit TMD, encrypted contents and standard ticket. - - - - - - - Legit - - - - - - - true - - - Encrypted CIA with legit TMD, encrypted contents and legit ticket.<br>WARNING: Legit ticket may include console identifying information! - - - - - - - - - - Qt::Vertical - - - - - - - QDialogButtonBox::Ok|QDialogButtonBox::Cancel - - - - - - - - + + + CIABuildDialog + + + + 0 + 0 + 510 + 260 + + + + Build CIA + + + + + + + + Destination: + + + + + + + + + + + 0 + 0 + + + + ... + + + + + + + + + Build Type + + + + + + Standard + + + true + + + + + + + true + + + Recommended for general use.<br>Decrypted CIA with decrypted contents and standard ticket. + + + + + + + Legit with standard ticket ("pirate legit") + + + + + + + true + + + Encrypted CIA with legit TMD, encrypted contents and standard ticket. + + + + + + + Legit + + + + + + + true + + + Encrypted CIA with legit TMD, encrypted contents and legit ticket.<br>WARNING: Legit ticket may include console identifying information! + + + + + + + + + + Qt::Vertical + + + + + + + QDialogButtonBox::Ok|QDialogButtonBox::Cancel + + + + + + + + diff --git a/src/frontend/import_dialog.cpp b/src/frontend/import_dialog.cpp index 319076f..954bc20 100644 --- a/src/frontend/import_dialog.cpp +++ b/src/frontend/import_dialog.cpp @@ -27,6 +27,7 @@ #include "frontend/helpers/multi_job.h" #include "frontend/helpers/simple_job.h" #include "frontend/import_dialog.h" +#include "frontend/title_info_dialog.h" #include "ui_import_dialog.h" static QString ReadableByteSize(qulonglong size) { @@ -519,10 +520,15 @@ void ImportDialog::OnContextMenu(const QPoint& point) { connect(dump_cxi, &QAction::triggered, [this, specifier] { StartDumpingCXISingle(specifier); }); } - if (Core::CanBuildCIA(specifier.type)) { + if (Core::IsTitle(specifier.type)) { QAction* build_cia = context_menu.addAction(tr("Build CIA...")); connect(build_cia, &QAction::triggered, [this, specifier] { StartBuildingCIASingle(specifier); }); + QAction* show_title_info = context_menu.addAction(tr("Show Title Info")); + connect(show_title_info, &QAction::triggered, [this, specifier] { + TitleInfoDialog dialog(this, config, *importer, specifier); + dialog.exec(); + }); } } else { // Top level if (!title_view) { @@ -837,7 +843,7 @@ void ImportDialog::StartBatchBuildingCIA() { const auto removed_iter = std::remove_if( to_import.begin(), to_import.end(), - [](const Core::ContentSpecifier& specifier) { return !Core::CanBuildCIA(specifier.type); }); + [](const Core::ContentSpecifier& specifier) { return !Core::IsTitle(specifier.type); }); if (removed_iter == to_import.begin()) { // No Titles selected QMessageBox::critical(this, tr("threeSD"), tr("The contents selected are not supported.
You can only build " diff --git a/src/frontend/select_files_dialog.cpp b/src/frontend/select_files_dialog.cpp index c32d88a..c232606 100644 --- a/src/frontend/select_files_dialog.cpp +++ b/src/frontend/select_files_dialog.cpp @@ -1,57 +1,57 @@ -// Copyright 2020 threeSD Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include "frontend/select_files_dialog.h" -#include "ui_select_files_dialog.h" - -SelectFilesDialog::SelectFilesDialog(QWidget* parent, bool source_is_dir_, bool destination_is_dir_) - : QDialog(parent), ui(std::make_unique()), source_is_dir(source_is_dir_), - destination_is_dir(destination_is_dir_) { - - ui->setupUi(this); - - const double scale = qApp->desktop()->logicalDpiX() / 96.0; - resize(static_cast(width() * scale), static_cast(height() * scale)); - - connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] { - if (ui->source->text().isEmpty() || ui->destination->text().isEmpty()) { - QMessageBox::warning(this, tr("Warning"), tr("Please fill in all the fields.")); - return; - } - accept(); - }); - connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SelectFilesDialog::reject); - - connect(ui->sourceExplore, &QToolButton::clicked, [this] { - QString path; - if (source_is_dir) { - path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); - } else { - path = QFileDialog::getOpenFileName(this, tr("Select File")); - } - if (!path.isEmpty()) { - ui->source->setText(path); - } - }); - connect(ui->destinationExplore, &QToolButton::clicked, [this] { - QString path; - if (destination_is_dir) { - path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); - } else { - path = QFileDialog::getSaveFileName(this, tr("Select File")); - } - if (!path.isEmpty()) { - ui->destination->setText(path); - } - }); -} - -SelectFilesDialog::~SelectFilesDialog() = default; - -std::pair SelectFilesDialog::GetResults() const { - return {ui->source->text(), ui->destination->text()}; -} +// Copyright 2020 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "frontend/select_files_dialog.h" +#include "ui_select_files_dialog.h" + +SelectFilesDialog::SelectFilesDialog(QWidget* parent, bool source_is_dir_, bool destination_is_dir_) + : QDialog(parent), ui(std::make_unique()), source_is_dir(source_is_dir_), + destination_is_dir(destination_is_dir_) { + + ui->setupUi(this); + + const double scale = qApp->desktop()->logicalDpiX() / 96.0; + resize(static_cast(width() * scale), static_cast(height() * scale)); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] { + if (ui->source->text().isEmpty() || ui->destination->text().isEmpty()) { + QMessageBox::warning(this, tr("Warning"), tr("Please fill in all the fields.")); + return; + } + accept(); + }); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SelectFilesDialog::reject); + + connect(ui->sourceExplore, &QToolButton::clicked, [this] { + QString path; + if (source_is_dir) { + path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); + } else { + path = QFileDialog::getOpenFileName(this, tr("Select File")); + } + if (!path.isEmpty()) { + ui->source->setText(path); + } + }); + connect(ui->destinationExplore, &QToolButton::clicked, [this] { + QString path; + if (destination_is_dir) { + path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); + } else { + path = QFileDialog::getSaveFileName(this, tr("Select File")); + } + if (!path.isEmpty()) { + ui->destination->setText(path); + } + }); +} + +SelectFilesDialog::~SelectFilesDialog() = default; + +std::pair SelectFilesDialog::GetResults() const { + return {ui->source->text(), ui->destination->text()}; +} diff --git a/src/frontend/title_info_dialog.cpp b/src/frontend/title_info_dialog.cpp new file mode 100644 index 0000000..c8c518d --- /dev/null +++ b/src/frontend/title_info_dialog.cpp @@ -0,0 +1,149 @@ +// Copyright 2021 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include "common/string_util.h" +#include "core/file_sys/ncch_container.h" +#include "core/file_sys/title_metadata.h" +#include "core/importer.h" +#include "frontend/title_info_dialog.h" +#include "ui_title_info_dialog.h" + +TitleInfoDialog::TitleInfoDialog(QWidget* parent, const Core::Config& config, + Core::SDMCImporter& importer, + const Core::ContentSpecifier& specifier) + : QDialog(parent), ui(std::make_unique()) { + + ui->setupUi(this); + + const double scale = qApp->desktop()->logicalDpiX() / 96.0; + resize(static_cast(width() * scale), static_cast(height() * scale)); + + InitializeInfo(config, importer, specifier); + InitializeLanguageComboBox(); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &TitleInfoDialog::accept); +} + +TitleInfoDialog::~TitleInfoDialog() = default; + +void TitleInfoDialog::InitializeInfo(const Core::Config& config, Core::SDMCImporter& importer, + const Core::ContentSpecifier& specifier) { + Core::TitleMetadata tmd; + if (!importer.LoadTMD(specifier, tmd)) { + QMessageBox::warning(this, tr("threeSD"), tr("Could not load title information.")); + reject(); + return; + } + ui->versionLineEdit->setText(QString::fromStdString(tmd.GetTitleVersionString())); + ui->titleIDLineEdit->setText(QStringLiteral("%1").arg(specifier.id, 16, 16, QLatin1Char{'0'})); + + const bool is_nand = specifier.type == Core::ContentType::SystemTitle || + specifier.type == Core::ContentType::SystemApplet; + const auto physical_path = + is_nand ? fmt::format("{}{:08x}/{:08x}/content/", config.system_titles_path, + (specifier.id >> 32), (specifier.id & 0xFFFFFFFF)) + : fmt::format("{}title/{:08x}/{:08x}/content/", config.sdmc_path, + (specifier.id >> 32), (specifier.id & 0xFFFFFFFF)); + const auto boot_content_path = + fmt::format("{}{:08x}.app", physical_path, tmd.GetBootContentID()); + Core::NCCHContainer ncch; + if (is_nand) { + ncch.OpenFile(std::make_shared(boot_content_path, "rb")); + } else { + const auto relative_path = boot_content_path.substr(config.sdmc_path.size() - 1); + ncch.OpenFile(std::make_shared(config.sdmc_path, relative_path, "rb")); + } + + static const std::unordered_map EncryptionTypeMap{{ + {Core::EncryptionType::None, QT_TR_NOOP("None")}, + {Core::EncryptionType::FixedKey, QT_TR_NOOP("FixedKey")}, + {Core::EncryptionType::NCCHSecure1, QT_TR_NOOP("Secure1")}, + {Core::EncryptionType::NCCHSecure2, QT_TR_NOOP("Secure2")}, + {Core::EncryptionType::NCCHSecure3, QT_TR_NOOP("Secure3")}, + {Core::EncryptionType::NCCHSecure4, QT_TR_NOOP("Secure4")}, + }}; + + Core::EncryptionType encryption = Core::EncryptionType::None; + ncch.ReadEncryptionType(encryption); + + bool seed_crypto = false; + ncch.ReadSeedCrypto(seed_crypto); + + QString encryption_text = tr(EncryptionTypeMap.at(encryption)); + if (seed_crypto) { + encryption_text.append(tr(" (Seed)")); + } + ui->encryptionLineEdit->setText(encryption_text); + + // Load SMDH + std::vector smdh_buffer; + if (!ncch.LoadSectionExeFS("icon", smdh_buffer) || smdh_buffer.size() != sizeof(Core::SMDH) || + !Core::IsValidSMDH(smdh_buffer)) { + + LOG_WARNING(Core, "Failed to load SMDH"); + ui->namesGroupBox->setEnabled(false); + return; + } + + std::memcpy(&smdh, smdh_buffer.data(), smdh_buffer.size()); + // Load icon + ui->iconLargeLabel->setPixmap( + QPixmap::fromImage(QImage(reinterpret_cast(smdh.GetIcon(true).data()), 48, 48, + QImage::Format::Format_RGB16))); + ui->iconSmallLabel->setPixmap( + QPixmap::fromImage(QImage(reinterpret_cast(smdh.GetIcon(false).data()), 24, + 24, QImage::Format::Format_RGB16))); +} + +void TitleInfoDialog::InitializeLanguageComboBox() { + if (!ui->namesGroupBox->isEnabled()) { // SMDH not available + return; + } + // Corresponds to the indices of the languages defined in SMDH + static constexpr std::array LanguageNames{{ + QT_TR_NOOP("Japanese"), + QT_TR_NOOP("English"), + QT_TR_NOOP("French"), + QT_TR_NOOP("German"), + QT_TR_NOOP("Italian"), + QT_TR_NOOP("Spanish"), + QT_TR_NOOP("Chinese (Simplified)"), + QT_TR_NOOP("Korean"), + QT_TR_NOOP("Dutch"), + QT_TR_NOOP("Portuguese"), + QT_TR_NOOP("Russian"), + QT_TR_NOOP("Chinese (Traditional)"), + }}; + for (std::size_t i = 0; i < LanguageNames.size(); ++i) { + const auto& title = smdh.titles.at(i); + if (Common::UTF16BufferToUTF8(title.short_title).empty() && + Common::UTF16BufferToUTF8(title.long_title).empty() && + Common::UTF16BufferToUTF8(title.publisher).empty()) { + // All empty, ignore this language + continue; + } + + ui->languageComboBox->addItem(tr(LanguageNames.at(i)), static_cast(i)); + if (i == 1) { // English + ui->languageComboBox->setCurrentIndex(ui->languageComboBox->count() - 1); + } + } + connect(ui->languageComboBox, qOverload(&QComboBox::currentIndexChanged), this, + &TitleInfoDialog::UpdateNames); + UpdateNames(); +} + +void TitleInfoDialog::UpdateNames() { + const auto& title = smdh.titles.at(ui->languageComboBox->currentData().toInt()); + ui->shortTitleLineEdit->setText( + QString::fromStdString(Common::UTF16BufferToUTF8(title.short_title))); + ui->longTitleLineEdit->setText( + QString::fromStdString(Common::UTF16BufferToUTF8(title.long_title))); + ui->publisherLineEdit->setText( + QString::fromStdString(Common::UTF16BufferToUTF8(title.publisher))); +} diff --git a/src/frontend/title_info_dialog.h b/src/frontend/title_info_dialog.h new file mode 100644 index 0000000..2468d59 --- /dev/null +++ b/src/frontend/title_info_dialog.h @@ -0,0 +1,35 @@ +// Copyright 2021 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "core/file_sys/smdh.h" + +namespace Core { +class Config; +class ContentSpecifier; +class SDMCImporter; +} // namespace Core + +namespace Ui { +class TitleInfoDialog; +} + +class TitleInfoDialog : public QDialog { + Q_OBJECT + +public: + explicit TitleInfoDialog(QWidget* parent, const Core::Config& config, + Core::SDMCImporter& importer, const Core::ContentSpecifier& specifier); + ~TitleInfoDialog(); + +private: + void InitializeInfo(const Core::Config& config, Core::SDMCImporter& importer, + const Core::ContentSpecifier& specifier); + void InitializeLanguageComboBox(); + void UpdateNames(); + + std::unique_ptr ui; + Core::SMDH smdh{}; +}; diff --git a/src/frontend/title_info_dialog.ui b/src/frontend/title_info_dialog.ui new file mode 100644 index 0000000..4f97d84 --- /dev/null +++ b/src/frontend/title_info_dialog.ui @@ -0,0 +1,216 @@ + + + TitleInfoDialog + + + + 0 + 0 + 500 + 360 + + + + Title Info + + + + + + Info + + + + + + Version: + + + + + + + + + + + 48 + 48 + + + + + + + + + 24 + 24 + + + + + + + + Encryption: + + + + + + + + + + Title ID: + + + + + + + + + + + + + Names + + + + + + Short Title: + + + + + + + + + + + + + Long Title: + + + + + + + + + + Publisher: + + + + + + + + + + + + + Checks + + + + + + TMD: + + + + + + + Legit + + + + + + + Qt::Horizontal + + + + + + + Ticket: + + + + + + + Legit + + + + + + + Qt::Horizontal + + + + + + + Contents: + + + + + + + Check + + + + + + + false + + + Legit + + + + + + + Qt::Horizontal + + + + + + + + + + Qt::Vertical + + + + + + + QDialogButtonBox::Ok + + + + + +