Preliminary work for a title info dialog

pending DRY fixes, etc
This commit is contained in:
Pengfei
2021-08-08 15:52:00 +08:00
parent e4612770a5
commit 3d68baf55c
9 changed files with 911 additions and 497 deletions
+6 -2
View File
@@ -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 // English short title name, extdata id, encryption, seed, icon
using TitleData = std::tuple<std::string, u64, EncryptionType, bool, std::vector<u16>>; using TitleData = std::tuple<std::string, u64, EncryptionType, bool, std::vector<u16>>;
@@ -659,7 +663,7 @@ void SDMCImporter::AbortDumpCXI() {
} }
bool SDMCImporter::CanBuildLegitCIA(const ContentSpecifier& specifier) const { bool SDMCImporter::CanBuildLegitCIA(const ContentSpecifier& specifier) const {
if (!CanBuildCIA(specifier.type)) { if (!IsTitle(specifier.type)) {
return false; return false;
} }
@@ -683,7 +687,7 @@ bool SDMCImporter::BuildCIA(CIABuildType build_type, const ContentSpecifier& spe
return false; return false;
} }
if (!CanBuildCIA(specifier.type)) { if (!IsTitle(specifier.type)) {
LOG_ERROR(Core, "Unsupported specifier type {}", static_cast<int>(specifier.type)); LOG_ERROR(Core, "Unsupported specifier type {}", static_cast<int>(specifier.type));
return false; return false;
} }
+233 -232
View File
@@ -1,232 +1,233 @@
// Copyright 2019 threeSD Project // Copyright 2019 threeSD Project
// 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.
#pragma once #pragma once
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/progress_callback.h" #include "common/progress_callback.h"
#include "core/file_sys/cia_common.h" #include "core/file_sys/cia_common.h"
namespace Core { namespace Core {
class CIABuilder; class CIABuilder;
class SDMCDecryptor; class SDMCDecryptor;
class TicketDB; class TicketDB;
class TitleDB; class TitleDB;
class TitleMetadata; class TitleMetadata;
/** /**
* Type of an importable content. * Type of an importable content.
* Applications, updates and DLCs are all considered titles. * Applications, updates and DLCs are all considered titles.
*/ */
enum class ContentType { enum class ContentType {
Application, Application,
Update, Update,
DLC, DLC,
Savegame, Savegame,
Extdata, Extdata,
SystemArchive, SystemArchive,
Sysdata, Sysdata,
SystemTitle, SystemTitle,
SystemApplet, // This should belong to System Title, but they cause problems so a new category. SystemApplet, // This should belong to System Title, but they cause problems so a new category.
}; };
constexpr bool CanBuildCIA(ContentType type) { constexpr bool IsTitle(ContentType type) {
return type == ContentType::Application || type == ContentType::Update || return type == ContentType::Application || type == ContentType::Update ||
type == ContentType::DLC || type == ContentType::SystemTitle || type == ContentType::DLC || type == ContentType::SystemTitle ||
type == ContentType::SystemApplet; type == ContentType::SystemApplet;
} }
/** /**
* Encryption type of an importable content. * Encryption type of an importable content.
*/ */
enum class EncryptionType { enum class EncryptionType {
None, None,
FixedKey, FixedKey,
NCCHSecure1, NCCHSecure1,
NCCHSecure2, NCCHSecure2,
NCCHSecure3, NCCHSecure3,
NCCHSecure4, NCCHSecure4,
}; };
/** /**
* Struct that specifies an importable content. * Struct that specifies an importable content.
*/ */
struct ContentSpecifier { struct ContentSpecifier {
ContentType type; ContentType type;
u64 id; u64 id;
bool already_exists; ///< Tells whether a file already exists in target path. 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. 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. std::string name; ///< Optional. The content's preferred display name.
u64 extdata_id; ///< Extdata ID for Applications. u64 extdata_id; ///< Extdata ID for Applications.
EncryptionType encryption = EncryptionType::None; ///< Only for NCCHs. Encryption scheme. EncryptionType encryption = EncryptionType::None; ///< Only for NCCHs. Encryption scheme.
bool seed_crypto = false; ///< Only for NCCHs. Whether seed crypto is used. bool seed_crypto = false; ///< Only for NCCHs. Whether seed crypto is used.
std::vector<u16> icon; ///< Optional. The content's icon. std::vector<u16> icon; ///< Optional. The content's icon.
}; };
/** /**
* A set of values that are used to initialize the importer. * 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) * 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>") std::string sdmc_path; ///< SDMC root path ("Nintendo 3DS/<ID0>/<ID1>")
std::string user_path; ///< Target user path of Citra std::string user_path; ///< Target user path of Citra
// Necessary system files keys are loaded from. // Necessary system files keys are loaded from.
std::string movable_sed_path; ///< Path to movable.sed 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 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. // 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 nand_title_db_path; ///< Path to NAND title.db. Entirely optional.
std::string ticket_db_path; ///< Path to ticket.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. 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 // The following system files are optional for importing and are only copied so that Citra
// will be able to decrypt imported encrypted ROMs. // 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 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 seed_db_path; ///< Path to seeddb.bin (Sysdata 2)
std::string secret_sector_path; ///< Path to secret sector (New3DS only) (Sysdata 3) std::string secret_sector_path; ///< Path to secret sector (New3DS only) (Sysdata 3)
// Note: Sysdata 4 is aes_keys.txt (slot0x25KeyX) // Note: Sysdata 4 is aes_keys.txt (slot0x25KeyX)
std::string config_savegame_path; ///< Path to config savegame (Sysdata 5) std::string config_savegame_path; ///< Path to config savegame (Sysdata 5)
std::string system_archives_path; ///< Path to system archives. std::string system_archives_path; ///< Path to system archives.
std::string system_titles_path; ///< Path to system titles. std::string system_titles_path; ///< Path to system titles.
std::string nand_data_path; ///< Path to NAND data. (Extdata and savedata) std::string nand_data_path; ///< Path to NAND data. (Extdata and savedata)
int version = 0; ///< Version of the dumper used. int version = 0; ///< Version of the dumper used.
}; };
// Version of the current dumper. // Version of the current dumper.
constexpr int CurrentDumperVersion = 4; constexpr int CurrentDumperVersion = 4;
class SDMCFile; class SDMCFile;
class NCCHContainer; class NCCHContainer;
class SDMCImporter { class SDMCImporter {
public: public:
/** /**
* Initializes the importer. * Initializes the importer.
* @param root_folder Path to the "Nintendo 3DS/<ID0>/<ID1>" folder. * @param root_folder Path to the "Nintendo 3DS/<ID0>/<ID1>" folder.
*/ */
explicit SDMCImporter(const Config& config); explicit SDMCImporter(const Config& config);
~SDMCImporter(); ~SDMCImporter();
/** /**
* Imports a specific content by its specifier, deleting it when failed. * Imports a specific content by its specifier, deleting it when failed.
* Blocks, but can be aborted on another thread if needed. * Blocks, but can be aborted on another thread if needed.
* @return true on success, false otherwise * @return true on success, false otherwise
*/ */
bool ImportContent( bool ImportContent(
const ContentSpecifier& specifier, const ContentSpecifier& specifier,
const Common::ProgressCallback& callback = [](u64, u64) {}); const Common::ProgressCallback& callback = [](u64, u64) {});
/** /**
* Aborts current importing. * Aborts current importing.
*/ */
void AbortImporting(); void AbortImporting();
/** /**
* Dumps a content to CXI. * Dumps a content to CXI.
* Blocks, but can be aborted on another thread. * Blocks, but can be aborted on another thread.
* @return true on success, false otherwise * @return true on success, false otherwise
*/ */
bool DumpCXI(const ContentSpecifier& specifier, std::string destination, bool DumpCXI(const ContentSpecifier& specifier, std::string destination,
const Common::ProgressCallback& callback, bool auto_filename = false); const Common::ProgressCallback& callback, bool auto_filename = false);
/** /**
* Aborts current CXI dumping. * Aborts current CXI dumping.
*/ */
void AbortDumpCXI(); void AbortDumpCXI();
/** /**
* Builds a CIA from a content. * Builds a CIA from a content.
* Blocks, but can be aborted on another thread. * Blocks, but can be aborted on another thread.
* @return true on success, false otherwise * @return true on success, false otherwise
*/ */
bool BuildCIA(CIABuildType build_type, const ContentSpecifier& specifier, bool BuildCIA(CIABuildType build_type, const ContentSpecifier& specifier,
std::string destination, const Common::ProgressCallback& callback, std::string destination, const Common::ProgressCallback& callback,
bool auto_filename = false); bool auto_filename = false);
/** /**
* Checks if a content can be built as a legit CIA. * Checks if a content can be built as a legit CIA.
*/ */
bool CanBuildLegitCIA(const ContentSpecifier& specifier) const; bool CanBuildLegitCIA(const ContentSpecifier& specifier) const;
/** /**
* Aborts current CIA building * Aborts current CIA building
*/ */
void AbortBuildCIA(); void AbortBuildCIA();
/** /**
* Gets a list of dumpable content specifiers. * Gets a list of dumpable content specifiers.
*/ */
std::vector<ContentSpecifier> ListContent() const; std::vector<ContentSpecifier> ListContent() const;
/** /**
* Returns whether the importer is in good state. * Returns whether the importer is in good state.
*/ */
bool IsGood() const; bool IsGood() const;
private: bool LoadTMD(ContentType type, u64 id, TitleMetadata& out) const;
bool Init(); bool LoadTMD(const ContentSpecifier& specifier, TitleMetadata& out) const;
// Impl of ImportContent without deleting mechanism. private:
bool ImportContentImpl( bool Init();
const ContentSpecifier& specifier,
const Common::ProgressCallback& callback = [](u64, u64) {}); // Impl of ImportContent without deleting mechanism.
bool ImportTitle(const ContentSpecifier& specifier, const Common::ProgressCallback& callback); bool ImportContentImpl(
bool ImportNandTitle(const ContentSpecifier& specifier, const ContentSpecifier& specifier,
const Common::ProgressCallback& callback); const Common::ProgressCallback& callback = [](u64, u64) {});
bool ImportSavegame(u64 id, const Common::ProgressCallback& callback); bool ImportTitle(const ContentSpecifier& specifier, const Common::ProgressCallback& callback);
bool ImportNandSavegame(u64 id, const Common::ProgressCallback& callback); bool ImportNandTitle(const ContentSpecifier& specifier,
bool ImportExtdata(u64 id, const Common::ProgressCallback& callback); const Common::ProgressCallback& callback);
bool ImportNandExtdata(u64 id, const Common::ProgressCallback& callback); bool ImportSavegame(u64 id, const Common::ProgressCallback& callback);
bool ImportSystemArchive(u64 id, const Common::ProgressCallback& callback); bool ImportNandSavegame(u64 id, const Common::ProgressCallback& callback);
bool ImportSysdata(u64 id, const Common::ProgressCallback& callback); bool ImportExtdata(u64 id, const Common::ProgressCallback& callback);
bool ImportNandExtdata(u64 id, const Common::ProgressCallback& callback);
void ListTitle(std::vector<ContentSpecifier>& out) const; bool ImportSystemArchive(u64 id, const Common::ProgressCallback& callback);
void ListNandTitle(std::vector<ContentSpecifier>& out) const; bool ImportSysdata(u64 id, const Common::ProgressCallback& callback);
void ListNandSavegame(std::vector<ContentSpecifier>& out) const;
void ListExtdata(std::vector<ContentSpecifier>& out) const; void ListTitle(std::vector<ContentSpecifier>& out) const;
void ListSystemArchive(std::vector<ContentSpecifier>& out) const; void ListNandTitle(std::vector<ContentSpecifier>& out) const;
void ListSysdata(std::vector<ContentSpecifier>& out) const; void ListNandSavegame(std::vector<ContentSpecifier>& out) const;
void ListExtdata(std::vector<ContentSpecifier>& out) const;
void DeleteContent(const ContentSpecifier& specifier) const; void ListSystemArchive(std::vector<ContentSpecifier>& out) const;
void DeleteTitle(u64 id) const; void ListSysdata(std::vector<ContentSpecifier>& out) const;
void DeleteNandTitle(u64 id) const;
void DeleteSavegame(u64 id) const; void DeleteContent(const ContentSpecifier& specifier) const;
void DeleteExtdata(u64 id) const; void DeleteTitle(u64 id) const;
void DeleteSystemArchive(u64 id) const; void DeleteNandTitle(u64 id) const;
void DeleteSysdata(u64 id) const; void DeleteSavegame(u64 id) const;
void DeleteExtdata(u64 id) const;
bool LoadTMD(ContentType type, u64 id, TitleMetadata& out) const; void DeleteSystemArchive(u64 id) const;
void DeleteSysdata(u64 id) const;
bool is_good{};
Config config; bool is_good{};
std::unique_ptr<SDMCDecryptor> sdmc_decryptor; Config config;
std::unique_ptr<SDMCDecryptor> sdmc_decryptor;
// Used for CIA building
std::unique_ptr<CIABuilder> cia_builder; // Used for CIA building
std::unique_ptr<CIABuilder> cia_builder;
// The NCCH used to dump CXIs.
std::unique_ptr<NCCHContainer> dump_cxi_ncch; // The NCCH used to dump CXIs.
std::unique_ptr<NCCHContainer> dump_cxi_ncch;
std::unique_ptr<TitleDB> sdmc_title_db{};
std::unique_ptr<TitleDB> nand_title_db{}; std::unique_ptr<TitleDB> sdmc_title_db{};
}; std::unique_ptr<TitleDB> 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 * 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<Config> LoadPresetConfig(std::string mount_point); */
std::vector<Config> LoadPresetConfig(std::string mount_point);
} // namespace Core
} // namespace Core
+82 -79
View File
@@ -1,79 +1,82 @@
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON) set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOUIC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
if (POLICY CMP0071) if (POLICY CMP0071)
cmake_policy(SET CMP0071 NEW) cmake_policy(SET CMP0071 NEW)
endif() endif()
file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/themes/*) file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/themes/*)
add_executable(threeSD add_executable(threeSD
helpers/multi_job.cpp helpers/multi_job.cpp
helpers/multi_job.h helpers/multi_job.h
helpers/simple_job.cpp helpers/simple_job.cpp
helpers/simple_job.h helpers/simple_job.h
cia_build_dialog.cpp cia_build_dialog.cpp
cia_build_dialog.h cia_build_dialog.h
cia_build_dialog.ui cia_build_dialog.ui
import_dialog.cpp import_dialog.cpp
import_dialog.h import_dialog.h
import_dialog.ui import_dialog.ui
main.cpp main.cpp
main.h main.h
main.ui main.ui
select_files_dialog.cpp select_files_dialog.cpp
select_files_dialog.h select_files_dialog.h
select_files_dialog.ui select_files_dialog.ui
utilities.cpp title_info_dialog.cpp
utilities.h title_info_dialog.h
utilities.ui title_info_dialog.ui
${THEMES} utilities.cpp
) utilities.h
utilities.ui
target_link_libraries(threeSD PRIVATE common core) ${THEMES}
target_link_libraries(threeSD PRIVATE Qt5::Widgets) )
target_link_libraries(threeSD PRIVATE qdevicewatcher)
target_link_libraries(threeSD PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) target_link_libraries(threeSD PRIVATE common core)
target_link_libraries(threeSD PRIVATE Qt5::Widgets)
if (APPLE) target_link_libraries(threeSD PRIVATE qdevicewatcher)
# set(MACOSX_ICON "../../dist/citra.icns") target_link_libraries(threeSD PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
# set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
# target_sources(threeSD PRIVATE ${MACOSX_ICON}) if (APPLE)
set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE TRUE) # set(MACOSX_ICON "../../dist/citra.icns")
set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) # set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
elseif(WIN32) # target_sources(threeSD PRIVATE ${MACOSX_ICON})
# compile as a win32 gui application instead of a console application set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE TRUE)
target_link_libraries(threeSD PRIVATE Qt5::WinMain) set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
if(MSVC) elseif(WIN32)
set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") # compile as a win32 gui application instead of a console application
elseif(MINGW) target_link_libraries(threeSD PRIVATE Qt5::WinMain)
set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "-mwindows") if(MSVC)
endif() set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") elseif(MINGW)
# In Ubuntu, the executable would be recognized as a shared library otherwise. set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "-mwindows")
set_target_properties(threeSD PROPERTIES LINK_FLAGS "-no-pie") endif()
endif() elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
# In Ubuntu, the executable would be recognized as a shared library otherwise.
target_compile_definitions(threeSD PRIVATE set_target_properties(threeSD PROPERTIES LINK_FLAGS "-no-pie")
# Use QStringBuilder for string concatenation to reduce endif()
# the overall number of temporary strings created.
-DQT_USE_QSTRINGBUILDER target_compile_definitions(threeSD PRIVATE
# Use QStringBuilder for string concatenation to reduce
# Disable implicit type narrowing in signal/slot connect() calls. # the overall number of temporary strings created.
-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT -DQT_USE_QSTRINGBUILDER
# Disable unsafe overloads of QProcess' start() function. # Disable implicit type narrowing in signal/slot connect() calls.
-DQT_NO_PROCESS_COMBINED_ARGUMENT_START -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
# Disable implicit QString->QUrl conversions to enforce use of proper resolving functions. # Disable unsafe overloads of QProcess' start() function.
-DQT_NO_URL_CAST_FROM_STRING -DQT_NO_PROCESS_COMBINED_ARGUMENT_START
# Disable automatic conversions from 8-bit strings (char *) to unicode QStrings # Disable implicit QString->QUrl conversions to enforce use of proper resolving functions.
-DQT_NO_CAST_FROM_ASCII -DQT_NO_URL_CAST_FROM_STRING
)
# Disable automatic conversions from 8-bit strings (char *) to unicode QStrings
if (MSVC) -DQT_NO_CAST_FROM_ASCII
include(CopyQt5Deps) )
copy_Qt5_deps(threeSD)
endif() if (MSVC)
include(CopyQt5Deps)
copy_Qt5_deps(threeSD)
endif()
+125 -125
View File
@@ -1,125 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>CIABuildDialog</class> <class>CIABuildDialog</class>
<widget class="QDialog" name="CIABuildDialog"> <widget class="QDialog" name="CIABuildDialog">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>510</width> <width>510</width>
<height>260</height> <height>260</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Build CIA</string> <string>Build CIA</string>
</property> </property>
<layout class="QVBoxLayout"> <layout class="QVBoxLayout">
<item> <item>
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">
<string>Destination:</string> <string>Destination:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLineEdit" name="destination"/> <widget class="QLineEdit" name="destination"/>
</item> </item>
<item> <item>
<widget class="QToolButton" name="destinationExplore"> <widget class="QToolButton" name="destinationExplore">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QGroupBox"> <widget class="QGroupBox">
<property name="title"> <property name="title">
<string>Build Type</string> <string>Build Type</string>
</property> </property>
<layout class="QVBoxLayout"> <layout class="QVBoxLayout">
<item> <item>
<widget class="QRadioButton" name="standardButton"> <widget class="QRadioButton" name="standardButton">
<property name="text"> <property name="text">
<string>Standard</string> <string>Standard</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel"> <widget class="QLabel">
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Recommended for general use.&lt;br&gt;Decrypted CIA with decrypted contents and standard ticket.</string> <string>Recommended for general use.&lt;br&gt;Decrypted CIA with decrypted contents and standard ticket.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QRadioButton" name="pirateLegitButton"> <widget class="QRadioButton" name="pirateLegitButton">
<property name="text"> <property name="text">
<string>Legit with standard ticket ("pirate legit")</string> <string>Legit with standard ticket ("pirate legit")</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="pirateLegitLabel"> <widget class="QLabel" name="pirateLegitLabel">
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Encrypted CIA with legit TMD, encrypted contents and standard ticket.</string> <string>Encrypted CIA with legit TMD, encrypted contents and standard ticket.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QRadioButton" name="legitButton"> <widget class="QRadioButton" name="legitButton">
<property name="text"> <property name="text">
<string>Legit</string> <string>Legit</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="legitLabel"> <widget class="QLabel" name="legitLabel">
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Encrypted CIA with legit TMD, encrypted contents and legit ticket.&lt;br&gt;WARNING: Legit ticket may include console identifying information!</string> <string>Encrypted CIA with legit TMD, encrypted contents and legit ticket.&lt;br&gt;WARNING: Legit ticket may include console identifying information!</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item>
<spacer> <spacer>
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel</set> <set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel</set>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>
+8 -2
View File
@@ -27,6 +27,7 @@
#include "frontend/helpers/multi_job.h" #include "frontend/helpers/multi_job.h"
#include "frontend/helpers/simple_job.h" #include "frontend/helpers/simple_job.h"
#include "frontend/import_dialog.h" #include "frontend/import_dialog.h"
#include "frontend/title_info_dialog.h"
#include "ui_import_dialog.h" #include "ui_import_dialog.h"
static QString ReadableByteSize(qulonglong size) { static QString ReadableByteSize(qulonglong size) {
@@ -519,10 +520,15 @@ void ImportDialog::OnContextMenu(const QPoint& point) {
connect(dump_cxi, &QAction::triggered, connect(dump_cxi, &QAction::triggered,
[this, specifier] { StartDumpingCXISingle(specifier); }); [this, specifier] { StartDumpingCXISingle(specifier); });
} }
if (Core::CanBuildCIA(specifier.type)) { if (Core::IsTitle(specifier.type)) {
QAction* build_cia = context_menu.addAction(tr("Build CIA...")); QAction* build_cia = context_menu.addAction(tr("Build CIA..."));
connect(build_cia, &QAction::triggered, connect(build_cia, &QAction::triggered,
[this, specifier] { StartBuildingCIASingle(specifier); }); [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 } else { // Top level
if (!title_view) { if (!title_view) {
@@ -837,7 +843,7 @@ void ImportDialog::StartBatchBuildingCIA() {
const auto removed_iter = std::remove_if( const auto removed_iter = std::remove_if(
to_import.begin(), to_import.end(), 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 if (removed_iter == to_import.begin()) { // No Titles selected
QMessageBox::critical(this, tr("threeSD"), QMessageBox::critical(this, tr("threeSD"),
tr("The contents selected are not supported.<br>You can only build " tr("The contents selected are not supported.<br>You can only build "
+57 -57
View File
@@ -1,57 +1,57 @@
// Copyright 2020 threeSD Project // Copyright 2020 threeSD Project
// 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 <QDesktopWidget> #include <QDesktopWidget>
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
#include "frontend/select_files_dialog.h" #include "frontend/select_files_dialog.h"
#include "ui_select_files_dialog.h" #include "ui_select_files_dialog.h"
SelectFilesDialog::SelectFilesDialog(QWidget* parent, bool source_is_dir_, bool destination_is_dir_) SelectFilesDialog::SelectFilesDialog(QWidget* parent, bool source_is_dir_, bool destination_is_dir_)
: QDialog(parent), ui(std::make_unique<Ui::SelectFilesDialog>()), source_is_dir(source_is_dir_), : QDialog(parent), ui(std::make_unique<Ui::SelectFilesDialog>()), source_is_dir(source_is_dir_),
destination_is_dir(destination_is_dir_) { destination_is_dir(destination_is_dir_) {
ui->setupUi(this); ui->setupUi(this);
const double scale = qApp->desktop()->logicalDpiX() / 96.0; const double scale = qApp->desktop()->logicalDpiX() / 96.0;
resize(static_cast<int>(width() * scale), static_cast<int>(height() * scale)); resize(static_cast<int>(width() * scale), static_cast<int>(height() * scale));
connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] { connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] {
if (ui->source->text().isEmpty() || ui->destination->text().isEmpty()) { if (ui->source->text().isEmpty() || ui->destination->text().isEmpty()) {
QMessageBox::warning(this, tr("Warning"), tr("Please fill in all the fields.")); QMessageBox::warning(this, tr("Warning"), tr("Please fill in all the fields."));
return; return;
} }
accept(); accept();
}); });
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SelectFilesDialog::reject); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SelectFilesDialog::reject);
connect(ui->sourceExplore, &QToolButton::clicked, [this] { connect(ui->sourceExplore, &QToolButton::clicked, [this] {
QString path; QString path;
if (source_is_dir) { if (source_is_dir) {
path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
} else { } else {
path = QFileDialog::getOpenFileName(this, tr("Select File")); path = QFileDialog::getOpenFileName(this, tr("Select File"));
} }
if (!path.isEmpty()) { if (!path.isEmpty()) {
ui->source->setText(path); ui->source->setText(path);
} }
}); });
connect(ui->destinationExplore, &QToolButton::clicked, [this] { connect(ui->destinationExplore, &QToolButton::clicked, [this] {
QString path; QString path;
if (destination_is_dir) { if (destination_is_dir) {
path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
} else { } else {
path = QFileDialog::getSaveFileName(this, tr("Select File")); path = QFileDialog::getSaveFileName(this, tr("Select File"));
} }
if (!path.isEmpty()) { if (!path.isEmpty()) {
ui->destination->setText(path); ui->destination->setText(path);
} }
}); });
} }
SelectFilesDialog::~SelectFilesDialog() = default; SelectFilesDialog::~SelectFilesDialog() = default;
std::pair<QString, QString> SelectFilesDialog::GetResults() const { std::pair<QString, QString> SelectFilesDialog::GetResults() const {
return {ui->source->text(), ui->destination->text()}; return {ui->source->text(), ui->destination->text()};
} }
+149
View File
@@ -0,0 +1,149 @@
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QDesktopWidget>
#include <QMessageBox>
#include <QPixmap>
#include <fmt/format.h>
#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::TitleInfoDialog>()) {
ui->setupUi(this);
const double scale = qApp->desktop()->logicalDpiX() / 96.0;
resize(static_cast<int>(width() * scale), static_cast<int>(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<FileUtil::IOFile>(boot_content_path, "rb"));
} else {
const auto relative_path = boot_content_path.substr(config.sdmc_path.size() - 1);
ncch.OpenFile(std::make_shared<Core::SDMCFile>(config.sdmc_path, relative_path, "rb"));
}
static const std::unordered_map<Core::EncryptionType, const char*> 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<u8> 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<const uchar*>(smdh.GetIcon(true).data()), 48, 48,
QImage::Format::Format_RGB16)));
ui->iconSmallLabel->setPixmap(
QPixmap::fromImage(QImage(reinterpret_cast<const uchar*>(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<const char*, 12> 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<int>(i));
if (i == 1) { // English
ui->languageComboBox->setCurrentIndex(ui->languageComboBox->count() - 1);
}
}
connect(ui->languageComboBox, qOverload<int>(&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)));
}
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include <QDialog>
#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::TitleInfoDialog> ui;
Core::SMDH smdh{};
};
+216
View File
@@ -0,0 +1,216 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TitleInfoDialog</class>
<widget class="QDialog" name="TitleInfoDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>360</height>
</rect>
</property>
<property name="windowTitle">
<string>Title Info</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox">
<property name="title">
<string>Info</string>
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel">
<property name="text">
<string>Version:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="versionLineEdit"/>
</item>
<item row="0" column="2" rowspan="2">
<widget class="QLabel" name="iconLargeLabel">
<property name="minimumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
</widget>
</item>
<item row="0" column="3" rowspan="2">
<widget class="QLabel" name="iconSmallLabel">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel">
<property name="text">
<string>Encryption:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="encryptionLineEdit"/>
</item>
<item row="2" column="0">
<widget class="QLabel">
<property name="text">
<string>Title ID:</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="3">
<widget class="QLineEdit" name="titleIDLineEdit"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="namesGroupBox">
<property name="title">
<string>Names</string>
</property>
<layout class="QGridLayout" columnstretch="0,20,10">
<item row="0" column="0">
<widget class="QLabel">
<property name="text">
<string>Short Title:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="shortTitleLineEdit"/>
</item>
<item row="0" column="2">
<widget class="QComboBox" name="languageComboBox"/>
</item>
<item row="1" column="0">
<widget class="QLabel">
<property name="text">
<string>Long Title:</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QLineEdit" name="longTitleLineEdit"/>
</item>
<item row="2" column="0">
<widget class="QLabel">
<property name="text">
<string>Publisher:</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="publisherLineEdit"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Checks</string>
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel">
<property name="text">
<string>TMD:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="tmdCheckLabel">
<property name="text">
<string>Legit</string>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QLabel">
<property name="text">
<string>Ticket:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="ticketCheckLabel">
<property name="text">
<string>Legit</string>
</property>
</widget>
</item>
<item row="1" column="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item row="2" column="0">
<widget class="QLabel">
<property name="text">
<string>Contents:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="contentsCheckButton">
<property name="text">
<string>Check</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="contentsCheckLabel">
<property name="visible">
<bool>false</bool>
</property>
<property name="text">
<string>Legit</string>
</property>
</widget>
</item>
<item row="2" column="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
</ui>