mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-03 08:39:04 +00:00
core, frontend: Add 'Dump CXI file' option
Right click on an application in the Select Contents dialog.
This commit is contained in:
+42
-8
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user