core, frontend: Add 'Dump CXI file' option

Right click on an application in the Select Contents dialog.
This commit is contained in:
zhupengfei
2020-05-02 00:06:46 +08:00
parent 2c4dd84d49
commit 24bdf0a156
12 changed files with 275 additions and 31 deletions
+42 -8
View File
@@ -62,7 +62,7 @@ bool SDMCImporter::IsGood() const {
return is_good;
}
void SDMCImporter::Abort() {
void SDMCImporter::AbortImporting() {
decryptor->Abort();
}
@@ -319,10 +319,8 @@ std::vector<ContentSpecifier> SDMCImporter::ListContent() const {
// Regex for half Title IDs
static const std::regex title_regex{"[0-9a-f]{8}"};
std::tuple<std::string, u64, EncryptionType, bool, std::vector<u16>> SDMCImporter::LoadTitleData(
const std::string& path) const {
// Remove trailing '/'
const auto sdmc_path = config.sdmc_path.substr(0, config.sdmc_path.size() - 1);
static bool LoadTMD(const std::string& sdmc_path, const std::string& path, SDMCDecryptor& decryptor,
TitleMetadata& out) {
std::string title_metadata;
const bool ret = FileUtil::ForeachDirectoryEntry(
@@ -344,16 +342,26 @@ std::tuple<std::string, u64, EncryptionType, bool, std::vector<u16>> SDMCImporte
});
if (ret) { // TMD not found
return {};
return false;
}
if (!FileUtil::Exists(sdmc_path + path + title_metadata)) {
// Probably TMD is not directly inside, aborting.
return {};
return false;
}
return out.Load(decryptor.DecryptFile(path + title_metadata)) == ResultStatus::Success;
}
std::tuple<std::string, u64, EncryptionType, bool, std::vector<u16>> SDMCImporter::LoadTitleData(
const std::string& path) const {
// Remove trailing '/'
const auto sdmc_path = config.sdmc_path.substr(0, config.sdmc_path.size() - 1);
TitleMetadata tmd;
tmd.Load(decryptor->DecryptFile(path + title_metadata));
if (!LoadTMD(sdmc_path, path, *decryptor, tmd)) {
return {};
}
const auto boot_content_path = fmt::format("{}{:08x}.app", path, tmd.GetBootContentID());
@@ -390,6 +398,32 @@ std::tuple<std::string, u64, EncryptionType, bool, std::vector<u16>> SDMCImporte
encryption, seed_crypto, smdh.GetIcon(false)};
}
bool SDMCImporter::DumpCXI(const ContentSpecifier& specifier, const std::string& destination,
const ProgressCallback& callback) {
if (specifier.type != ContentType::Application) {
LOG_ERROR(Core, "Unsupported specifier type {}", static_cast<int>(specifier.type));
return false;
}
const auto content_path = fmt::format("/title/{:08x}/{:08x}/content/", specifier.id >> 32,
(specifier.id & 0xFFFFFFFF));
TitleMetadata tmd;
if (!LoadTMD(config.sdmc_path, content_path, *decryptor, tmd)) {
LOG_ERROR(Core, "Could not load tmd");
return false;
}
const auto boot_content_path =
fmt::format("{}{:08x}.app", content_path, tmd.GetBootContentID());
dump_cxi_ncch = std::make_unique<NCCHContainer>(config.sdmc_path, boot_content_path);
return dump_cxi_ncch->DecryptToFile(destination, callback) == ResultStatus::Success;
}
void SDMCImporter::AbortDumpCXI() {
dump_cxi_ncch->AbortDecryptToFile();
}
void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
const auto ProcessDirectory = [this, &out, &sdmc_path = config.sdmc_path](ContentType type,
u64 high_id) {
+30 -11
View File
@@ -52,7 +52,7 @@ struct ContentSpecifier {
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<u16> icon; ///< Optional. The content's icon.
std::vector<u16> icon; ///< Optional. The content's icon.
};
/**
@@ -70,15 +70,17 @@ struct Config {
// 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)
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.
};
class NCCHContainer;
class SDMCImporter {
public:
/// (current_size, total_size)
@@ -93,24 +95,37 @@ public:
~SDMCImporter();
/**
* Aborts a specific content by its specifier.
* Imports a specific content by its specifier.
* Blocks, but can be aborted on another thread if needed.
* @return true on success, false otherwise
*/
bool ImportContent(const ContentSpecifier& specifier,
const ProgressCallback& callback = [](std::size_t, std::size_t) {});
/**
* 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, const std::string& destination,
const ProgressCallback& callback = [](std::size_t, std::size_t) {});
/**
* Aborts current CXI dumping.
*/
void AbortDumpCXI();
/**
* Deletes/Cleans up a content. Used for deleting contents that have
* not been fully imported.
*/
void DeleteContent(const ContentSpecifier& specifier);
/**
* Aborts current importing.
*/
void Abort();
/**
* Gets a list of dumpable content specifiers.
*/
@@ -147,11 +162,15 @@ private:
* Required to end with '/'.
* @return {name, extdata_id, encryption, seed_crypto, icon}
*/
std::tuple<std::string, u64, EncryptionType, bool, std::vector<u16>> LoadTitleData(const std::string& path) const;
std::tuple<std::string, u64, EncryptionType, bool, std::vector<u16>> LoadTitleData(
const std::string& path) const;
bool is_good{};
Config config;
std::unique_ptr<SDMCDecryptor> decryptor;
// The NCCH used to dump CXIs.
std::unique_ptr<NCCHContainer> dump_cxi_ncch;
};
/**
+18 -4
View File
@@ -15,10 +15,10 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/data_container.h"
#include "core/importer.h"
#include "core/key/key.h"
#include "core/ncch/ncch_container.h"
#include "core/ncch/seed_db.h"
#include "core/quick_decryptor.h"
namespace Core {
@@ -440,10 +440,13 @@ ResultStatus NCCHContainer::DecryptToFile(const std::string& destination,
if (!is_encrypted) {
// Simply copy everything
QuickDecryptor<SDMCFile, FileUtil::IOFile> decryptor;
file->Seek(0, SEEK_SET);
const auto size = file->GetSize();
decryptor.Reset(size);
decryptor.DecryptAndWriteFile(file, size, dest_file, callback);
const bool ret = decryptor.DecryptAndWriteFile(file, size, dest_file, callback);
return ret ? ResultStatus::Success : ResultStatus::Error;
}
// Write NCCH header
@@ -463,7 +466,6 @@ ResultStatus NCCHContainer::DecryptToFile(const std::string& destination,
return ResultStatus::Error;
}
QuickDecryptor<SDMCFile, FileUtil::IOFile> decryptor;
const auto total_size =
file->GetSize() - sizeof(NCCH_Header) - sizeof(ExHeader_Header) - sizeof(ExeFs_Header);
decryptor.Reset(total_size);
@@ -477,12 +479,19 @@ ResultStatus NCCHContainer::DecryptToFile(const std::string& destination,
return true;
}
if (aborted.exchange(false)) {
return false;
}
file->Seek(written, SEEK_SET);
ASSERT_MSG(written <= offset, "Offsets are not in increasing order");
if (!decryptor.DecryptAndWriteFile(file, offset - written, dest_file, callback)) {
LOG_ERROR(Core, "Could not write data before {} to {}", name, destination);
return false;
}
if (aborted.exchange(false)) {
return false;
}
if (!decryptor.DecryptAndWriteFile(file, size, dest_file, callback, decrypt, key, ctr,
aes_seek_pos)) {
LOG_ERROR(Core, "Could not write {} to {}", name, destination);
@@ -536,6 +545,11 @@ ResultStatus NCCHContainer::DecryptToFile(const std::string& destination,
return ResultStatus::Success;
}
void NCCHContainer::AbortDecryptToFile() {
aborted = true;
decryptor.Abort();
}
#pragma pack(push, 1)
struct RomFSIVFCHeader {
u32_le magic;
+11 -1
View File
@@ -13,7 +13,6 @@
#include "common/file_util.h"
#include "common/swap.h"
#include "core/decryptor.h"
#include "core/importer.h"
#include "core/result_status.h"
namespace Core {
@@ -196,6 +195,8 @@ struct ExHeader_Header {
static_assert(sizeof(ExHeader_Header) == 0x800, "ExHeader structure size is wrong");
enum class EncryptionType;
/**
* Helper which implements an interface to deal with NCCH containers which can
* contain ExeFS archives or RomFS archives for games or other applications.
@@ -272,6 +273,11 @@ public:
ResultStatus DecryptToFile(const std::string& destination,
const ProgressCallback& callback = [](std::size_t, std::size_t) {});
/**
* Aborts DecryptToFile. Simply aborts the decryptor.
*/
void AbortDecryptToFile();
NCCH_Header ncch_header;
ExHeader_Header exheader_header;
ExeFs_Header exefs_header;
@@ -298,6 +304,10 @@ private:
std::string filepath;
std::shared_ptr<SDMCFile> file;
std::shared_ptr<SDMCFile> exefs_file;
// Used for DecryptToFile
QuickDecryptor<SDMCFile, FileUtil::IOFile> decryptor;
std::atomic_bool aborted{false};
};
/**