mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-03 00:38:58 +00:00
Add Legit CIA building
This commit is contained in:
@@ -649,8 +649,9 @@ void SDMCImporter::AbortDumpCXI() {
|
|||||||
dump_cxi_ncch->AbortDecryptToFile();
|
dump_cxi_ncch->AbortDecryptToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SDMCImporter::BuildCIA(const ContentSpecifier& specifier, std::string destination,
|
bool SDMCImporter::BuildCIA(CIABuildType type, const ContentSpecifier& specifier,
|
||||||
const Common::ProgressCallback& callback, bool auto_filename) {
|
std::string destination, const Common::ProgressCallback& callback,
|
||||||
|
bool auto_filename) {
|
||||||
|
|
||||||
if (config.certs_db_path.empty()) {
|
if (config.certs_db_path.empty()) {
|
||||||
LOG_ERROR(Core, "Missing certs.db");
|
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);
|
FileUtil::GetDirectoryTreeSize(physical_path), callback);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
FileUtil::Delete(destination);
|
FileUtil::Delete(destination);
|
||||||
|
|||||||
+2
-1
@@ -10,6 +10,7 @@
|
|||||||
#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/ncch/cia_common.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
@@ -143,7 +144,7 @@ public:
|
|||||||
* 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(const ContentSpecifier& specifier, std::string destination,
|
bool BuildCIA(CIABuildType type, const ContentSpecifier& specifier, std::string destination,
|
||||||
const Common::ProgressCallback& callback, bool auto_filename = false);
|
const Common::ProgressCallback& callback, bool auto_filename = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -37,12 +37,14 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool VerifyHash(u8* out) {
|
bool VerifyHash(u8* out) {
|
||||||
sha.Verify(out);
|
return sha.Verify(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t Write(const char* data, std::size_t length) override {
|
std::size_t Write(const char* data, std::size_t length) override {
|
||||||
const std::size_t length_written = FileUtil::IOFile::Write(data, length);
|
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;
|
return length_written;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +82,7 @@ bool CIABuilder::Init(CIABuildType type_, const std::string& destination, TitleM
|
|||||||
// Check for legit TMD
|
// Check for legit TMD
|
||||||
if (!tmd.VerifyHashes() || !tmd.ValidateSignature()) {
|
if (!tmd.VerifyHashes() || !tmd.ValidateSignature()) {
|
||||||
LOG_ERROR(Core, "TMD is not legit");
|
LOG_ERROR(Core, "TMD is not legit");
|
||||||
|
file.reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,7 +191,7 @@ static Key::AESKey GetTitleKey(const Ticket& ticket) {
|
|||||||
Key::AESKey ctr{};
|
Key::AESKey ctr{};
|
||||||
std::memcpy(ctr.data(), &ticket.body.title_id, 8);
|
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());
|
aes.SetKeyWithIV(ticket_key.data(), ticket_key.size(), ctr.data());
|
||||||
|
|
||||||
Key::AESKey title_key = ticket.body.title_key;
|
Key::AESKey title_key = ticket.body.title_key;
|
||||||
@@ -222,44 +225,104 @@ bool CIABuilder::WriteTicket(const std::string& ticket_db_path,
|
|||||||
return true;
|
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) {
|
bool CIABuilder::AddContent(u16 content_id, NCCHContainer& ncch) {
|
||||||
file->Seek(written, SEEK_SET); // To enforce alignment
|
if (ncch.Load() != ResultStatus::Success) {
|
||||||
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();
|
|
||||||
return false;
|
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);
|
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());
|
file->GetHash(tmd_chunk.hash.data());
|
||||||
} else { // Verify hash
|
file->SetHashEnabled(false);
|
||||||
if (!file->VerifyHash(tmd_chunk.hash.data())) {
|
} 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);
|
LOG_ERROR(Core, "Hash dismatch for content {}", content_id);
|
||||||
|
file.reset();
|
||||||
return false;
|
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
|
// DLCs do not have a meta
|
||||||
if (tmd_chunk.index != TMDContentIndex::Main || (tmd.GetTitleID() >> 32) == 0x0004008c) {
|
if (tmd_chunk.index != TMDContentIndex::Main || (tmd.GetTitleID() >> 32) == 0x0004008c) {
|
||||||
@@ -322,9 +385,13 @@ bool CIABuilder::Finalize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CIABuilder::Abort() {
|
void CIABuilder::Abort() {
|
||||||
std::lock_guard lock{abort_ncch_mutex};
|
if (type == CIABuildType::Standard) { // Abort NCCH decryption
|
||||||
if (abort_ncch) {
|
std::lock_guard lock{abort_ncch_mutex};
|
||||||
abort_ncch->AbortDecryptToFile();
|
if (abort_ncch) {
|
||||||
|
abort_ncch->AbortDecryptToFile();
|
||||||
|
}
|
||||||
|
} else { // Abort the decryptor
|
||||||
|
decryptor.Abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "common/progress_callback.h"
|
#include "common/progress_callback.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
#include "core/key/key.h"
|
#include "core/key/key.h"
|
||||||
|
#include "core/ncch/cia_common.h"
|
||||||
#include "core/ncch/ncch_container.h"
|
#include "core/ncch/ncch_container.h"
|
||||||
#include "core/ncch/title_metadata.h"
|
#include "core/ncch/title_metadata.h"
|
||||||
#include "core/quick_decryptor.h"
|
#include "core/quick_decryptor.h"
|
||||||
@@ -26,12 +27,6 @@ constexpr std::size_t CIA_METADATA_SIZE = 0x3AC0;
|
|||||||
struct Config;
|
struct Config;
|
||||||
class HashedFile;
|
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 {
|
class CIABuilder {
|
||||||
public:
|
public:
|
||||||
explicit CIABuilder();
|
explicit CIABuilder();
|
||||||
@@ -122,6 +117,8 @@ private:
|
|||||||
// The NCCH to abort on
|
// The NCCH to abort on
|
||||||
std::mutex abort_ncch_mutex;
|
std::mutex abort_ncch_mutex;
|
||||||
NCCHContainer* abort_ncch{};
|
NCCHContainer* abort_ncch{};
|
||||||
|
|
||||||
|
QuickDecryptor decryptor;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|||||||
@@ -16,4 +16,10 @@ constexpr std::array<const char*, 3> CIACertNames{{
|
|||||||
"Root-CA00000003-CP0000000b",
|
"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
|
} // namespace Core
|
||||||
|
|||||||
@@ -318,6 +318,8 @@ private:
|
|||||||
// Used for DecryptToFile
|
// Used for DecryptToFile
|
||||||
QuickDecryptor decryptor;
|
QuickDecryptor decryptor;
|
||||||
std::atomic_bool aborted{false};
|
std::atomic_bool aborted{false};
|
||||||
|
|
||||||
|
friend class CIABuilder;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -798,7 +798,8 @@ void ImportDialog::StartBuildingCIASingle(const Core::ContentSpecifier& specifie
|
|||||||
auto* job = new SimpleJob(
|
auto* job = new SimpleJob(
|
||||||
this,
|
this,
|
||||||
[this, specifier, path](const Common::ProgressCallback& callback) {
|
[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(); });
|
[this] { importer.AbortBuildCIA(); });
|
||||||
RunSimpleJob(job);
|
RunSimpleJob(job);
|
||||||
@@ -854,7 +855,8 @@ void ImportDialog::StartBatchBuildingCIA() {
|
|||||||
this, importer, std::move(to_import),
|
this, importer, std::move(to_import),
|
||||||
[path](Core::SDMCImporter& importer, const Core::ContentSpecifier& specifier,
|
[path](Core::SDMCImporter& importer, const Core::ContentSpecifier& specifier,
|
||||||
const Common::ProgressCallback& callback) {
|
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);
|
&Core::SDMCImporter::AbortBuildCIA);
|
||||||
RunMultiJob(job, total_count, total_size);
|
RunMultiJob(job, total_count, total_size);
|
||||||
|
|||||||
Reference in New Issue
Block a user