mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-02 16:49:04 +00:00
Add Legit CIA building
This commit is contained in:
@@ -649,8 +649,9 @@ void SDMCImporter::AbortDumpCXI() {
|
||||
dump_cxi_ncch->AbortDecryptToFile();
|
||||
}
|
||||
|
||||
bool SDMCImporter::BuildCIA(const ContentSpecifier& specifier, std::string destination,
|
||||
const Common::ProgressCallback& callback, bool auto_filename) {
|
||||
bool SDMCImporter::BuildCIA(CIABuildType type, const ContentSpecifier& specifier,
|
||||
std::string destination, const Common::ProgressCallback& callback,
|
||||
bool auto_filename) {
|
||||
|
||||
if (config.certs_db_path.empty()) {
|
||||
LOG_ERROR(Core, "Missing certs.db");
|
||||
@@ -693,7 +694,7 @@ bool SDMCImporter::BuildCIA(const ContentSpecifier& specifier, std::string desti
|
||||
}
|
||||
}
|
||||
|
||||
const bool ret = cia_builder->Init(CIABuildType::Standard, destination, tmd, config,
|
||||
const bool ret = cia_builder->Init(type, destination, tmd, config,
|
||||
FileUtil::GetDirectoryTreeSize(physical_path), callback);
|
||||
if (!ret) {
|
||||
FileUtil::Delete(destination);
|
||||
|
||||
+2
-1
@@ -10,6 +10,7 @@
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/progress_callback.h"
|
||||
#include "core/ncch/cia_common.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
@@ -143,7 +144,7 @@ public:
|
||||
* Blocks, but can be aborted on another thread.
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
bool BuildCIA(const ContentSpecifier& specifier, std::string destination,
|
||||
bool BuildCIA(CIABuildType type, const ContentSpecifier& specifier, std::string destination,
|
||||
const Common::ProgressCallback& callback, bool auto_filename = false);
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,12 +37,14 @@ public:
|
||||
}
|
||||
|
||||
bool VerifyHash(u8* out) {
|
||||
sha.Verify(out);
|
||||
return sha.Verify(out);
|
||||
}
|
||||
|
||||
std::size_t Write(const char* data, std::size_t length) override {
|
||||
const std::size_t length_written = FileUtil::IOFile::Write(data, length);
|
||||
sha.Update(reinterpret_cast<const CryptoPP::byte*>(data), length_written);
|
||||
if (hash_enabled) {
|
||||
sha.Update(reinterpret_cast<const CryptoPP::byte*>(data), length_written);
|
||||
}
|
||||
return length_written;
|
||||
}
|
||||
|
||||
@@ -80,6 +82,7 @@ bool CIABuilder::Init(CIABuildType type_, const std::string& destination, TitleM
|
||||
// Check for legit TMD
|
||||
if (!tmd.VerifyHashes() || !tmd.ValidateSignature()) {
|
||||
LOG_ERROR(Core, "TMD is not legit");
|
||||
file.reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -188,7 +191,7 @@ static Key::AESKey GetTitleKey(const Ticket& ticket) {
|
||||
Key::AESKey ctr{};
|
||||
std::memcpy(ctr.data(), &ticket.body.title_id, 8);
|
||||
|
||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption aes;
|
||||
CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption aes;
|
||||
aes.SetKeyWithIV(ticket_key.data(), ticket_key.size(), ctr.data());
|
||||
|
||||
Key::AESKey title_key = ticket.body.title_key;
|
||||
@@ -222,44 +225,104 @@ bool CIABuilder::WriteTicket(const std::string& ticket_db_path,
|
||||
return true;
|
||||
}
|
||||
|
||||
class CIAEncryptAndHash final : public CryptoFunc {
|
||||
public:
|
||||
explicit CIAEncryptAndHash(const Key::AESKey& key, const Key::AESKey& iv) {
|
||||
aes.SetKeyWithIV(key.data(), key.size(), iv.data());
|
||||
}
|
||||
|
||||
~CIAEncryptAndHash() override = default;
|
||||
|
||||
void ProcessData(u8* data, std::size_t size) override {
|
||||
sha.Update(data, size);
|
||||
aes.ProcessData(data, data, size);
|
||||
}
|
||||
|
||||
bool VerifyHash(const u8* hash) {
|
||||
return sha.Verify(hash);
|
||||
}
|
||||
|
||||
private:
|
||||
CryptoPP::CBC_Mode<CryptoPP::AES>::Encryption aes;
|
||||
CryptoPP::SHA256 sha;
|
||||
};
|
||||
|
||||
bool CIABuilder::AddContent(u16 content_id, NCCHContainer& ncch) {
|
||||
file->Seek(written, SEEK_SET); // To enforce alignment
|
||||
file->SetHashEnabled(true);
|
||||
|
||||
{
|
||||
std::lock_guard lock{abort_ncch_mutex};
|
||||
abort_ncch = &ncch;
|
||||
}
|
||||
|
||||
const auto ret = ncch.DecryptToFile(file, [this](std::size_t current, std::size_t total) {
|
||||
callback(written + current, total_size);
|
||||
});
|
||||
|
||||
{
|
||||
std::lock_guard lock{abort_ncch_mutex};
|
||||
abort_ncch = nullptr;
|
||||
}
|
||||
|
||||
if (ret != ResultStatus::Success) {
|
||||
file.reset();
|
||||
if (ncch.Load() != ResultStatus::Success) {
|
||||
return false;
|
||||
}
|
||||
written = Common::AlignUp(file->Tell(), CIA_ALIGNMENT);
|
||||
|
||||
header.content_size = written - content_offset;
|
||||
file->Seek(written, SEEK_SET); // To enforce alignment
|
||||
|
||||
auto& tmd_chunk = tmd.GetContentChunkByID(content_id);
|
||||
header.SetContentPresent(tmd_chunk.index);
|
||||
const auto progress_callback = [this](std::size_t current, std::size_t total) {
|
||||
callback(written + current, total_size);
|
||||
};
|
||||
if (type == CIABuildType::Standard) {
|
||||
// Decrypt the NCCH. We created a HashedFile to transparently calculate the hash as there
|
||||
// is no easy way to get decrypted NCCH content otherwise.
|
||||
file->SetHashEnabled(true);
|
||||
{
|
||||
std::lock_guard lock{abort_ncch_mutex};
|
||||
abort_ncch = &ncch;
|
||||
}
|
||||
const auto ret = ncch.DecryptToFile(file, progress_callback);
|
||||
{
|
||||
std::lock_guard lock{abort_ncch_mutex};
|
||||
abort_ncch = nullptr;
|
||||
}
|
||||
|
||||
if (type == CIABuildType::Standard) { // Fix hash
|
||||
if (ret != ResultStatus::Success) {
|
||||
file.reset();
|
||||
return false;
|
||||
}
|
||||
file->GetHash(tmd_chunk.hash.data());
|
||||
} else { // Verify hash
|
||||
if (!file->VerifyHash(tmd_chunk.hash.data())) {
|
||||
file->SetHashEnabled(false);
|
||||
} else {
|
||||
ncch.file->Seek(0, SEEK_SET);
|
||||
|
||||
// Calculate IV
|
||||
Key::AESKey iv{};
|
||||
std::memcpy(iv.data(), &tmd_chunk.index, sizeof(tmd_chunk.index));
|
||||
|
||||
const bool is_encrypted = static_cast<u16>(tmd_chunk.type) & 0x01;
|
||||
|
||||
// For encrypted content, the hashes are calculated before CIA/CDN encryption.
|
||||
// So we have to add hash calculation to the CryptoFunc of the QuickDecryptor.
|
||||
// For unencrypted content, we can just use HashedFile's hashing.
|
||||
std::shared_ptr<CIAEncryptAndHash> crypto;
|
||||
if (is_encrypted) {
|
||||
crypto = std::make_shared<CIAEncryptAndHash>(title_key, iv);
|
||||
} else { // crypto left to be null
|
||||
file->SetHashEnabled(true);
|
||||
}
|
||||
decryptor.SetCrypto(crypto);
|
||||
if (!decryptor.CryptAndWriteFile(ncch.file, ncch.file->GetSize(), file,
|
||||
progress_callback)) {
|
||||
|
||||
file.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify the hash
|
||||
bool verified{};
|
||||
if (is_encrypted) {
|
||||
verified = crypto->VerifyHash(tmd_chunk.hash.data());
|
||||
} else {
|
||||
verified = file->VerifyHash(tmd_chunk.hash.data());
|
||||
file->SetHashEnabled(false);
|
||||
}
|
||||
if (!verified) {
|
||||
LOG_ERROR(Core, "Hash dismatch for content {}", content_id);
|
||||
file.reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
file->SetHashEnabled(false);
|
||||
|
||||
written = Common::AlignUp(file->Tell(), CIA_ALIGNMENT);
|
||||
|
||||
header.content_size = written - content_offset;
|
||||
header.SetContentPresent(tmd_chunk.index);
|
||||
|
||||
// DLCs do not have a meta
|
||||
if (tmd_chunk.index != TMDContentIndex::Main || (tmd.GetTitleID() >> 32) == 0x0004008c) {
|
||||
@@ -322,9 +385,13 @@ bool CIABuilder::Finalize() {
|
||||
}
|
||||
|
||||
void CIABuilder::Abort() {
|
||||
std::lock_guard lock{abort_ncch_mutex};
|
||||
if (abort_ncch) {
|
||||
abort_ncch->AbortDecryptToFile();
|
||||
if (type == CIABuildType::Standard) { // Abort NCCH decryption
|
||||
std::lock_guard lock{abort_ncch_mutex};
|
||||
if (abort_ncch) {
|
||||
abort_ncch->AbortDecryptToFile();
|
||||
}
|
||||
} else { // Abort the decryptor
|
||||
decryptor.Abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "common/progress_callback.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/key/key.h"
|
||||
#include "core/ncch/cia_common.h"
|
||||
#include "core/ncch/ncch_container.h"
|
||||
#include "core/ncch/title_metadata.h"
|
||||
#include "core/quick_decryptor.h"
|
||||
@@ -26,12 +27,6 @@ constexpr std::size_t CIA_METADATA_SIZE = 0x3AC0;
|
||||
struct Config;
|
||||
class HashedFile;
|
||||
|
||||
enum class CIABuildType {
|
||||
Standard, /// Decrypted CIA with generalized ticket
|
||||
PirateLegit, /// Uses legit TMD and encryption, but with generalized ticket
|
||||
Legit, /// Fully legit, with personal ticket containing console ID and eshop account
|
||||
};
|
||||
|
||||
class CIABuilder {
|
||||
public:
|
||||
explicit CIABuilder();
|
||||
@@ -122,6 +117,8 @@ private:
|
||||
// The NCCH to abort on
|
||||
std::mutex abort_ncch_mutex;
|
||||
NCCHContainer* abort_ncch{};
|
||||
|
||||
QuickDecryptor decryptor;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -16,4 +16,10 @@ constexpr std::array<const char*, 3> CIACertNames{{
|
||||
"Root-CA00000003-CP0000000b",
|
||||
}};
|
||||
|
||||
enum class CIABuildType {
|
||||
Standard, /// Decrypted CIA with generalized ticket
|
||||
PirateLegit, /// Uses legit TMD and encryption, but with generalized ticket
|
||||
Legit, /// Fully legit, with personal ticket containing console ID and eshop account
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -318,6 +318,8 @@ private:
|
||||
// Used for DecryptToFile
|
||||
QuickDecryptor decryptor;
|
||||
std::atomic_bool aborted{false};
|
||||
|
||||
friend class CIABuilder;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -798,7 +798,8 @@ void ImportDialog::StartBuildingCIASingle(const Core::ContentSpecifier& specifie
|
||||
auto* job = new SimpleJob(
|
||||
this,
|
||||
[this, specifier, path](const Common::ProgressCallback& callback) {
|
||||
return importer.BuildCIA(specifier, path.toStdString(), callback);
|
||||
return importer.BuildCIA(Core::CIABuildType::Standard, specifier, path.toStdString(),
|
||||
callback);
|
||||
},
|
||||
[this] { importer.AbortBuildCIA(); });
|
||||
RunSimpleJob(job);
|
||||
@@ -854,7 +855,8 @@ void ImportDialog::StartBatchBuildingCIA() {
|
||||
this, importer, std::move(to_import),
|
||||
[path](Core::SDMCImporter& importer, const Core::ContentSpecifier& specifier,
|
||||
const Common::ProgressCallback& callback) {
|
||||
return importer.BuildCIA(specifier, path.toStdString(), callback, true);
|
||||
return importer.BuildCIA(Core::CIABuildType::Standard, specifier, path.toStdString(),
|
||||
callback, true);
|
||||
},
|
||||
&Core::SDMCImporter::AbortBuildCIA);
|
||||
RunMultiJob(job, total_count, total_size);
|
||||
|
||||
Reference in New Issue
Block a user