Fix installing DLCs with encrypted flags for missing contents (#979)
Some checks failed
citra-transifex / transifex (push) Has been cancelled
citra-format / clang-format (push) Has been cancelled
citra-build / source (push) Has been cancelled
citra-build / linux (appimage) (push) Has been cancelled
citra-build / linux (fresh) (push) Has been cancelled
citra-build / macos (arm64) (push) Has been cancelled
citra-build / macos (x86_64) (push) Has been cancelled
citra-build / windows (msvc) (push) Has been cancelled
citra-build / windows (msys2) (push) Has been cancelled
citra-build / android (push) Has been cancelled
citra-build / ios (push) Has been cancelled
citra-build / macos-universal (push) Has been cancelled

This commit is contained in:
PabloMK7 2025-04-24 21:53:09 +02:00 committed by GitHub
parent 493f59cef5
commit 01d7ff7a08
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 66 additions and 43 deletions

View file

@ -13,11 +13,11 @@
namespace FileSys { namespace FileSys {
Loader::ResultStatus CIAContainer::Load(const FileBackend& backend) { Loader::ResultStatus CIAContainer::Load(const FileBackend& backend) {
std::vector<u8> header_data(sizeof(Header)); std::vector<u8> header_data(sizeof(CIAHeader));
// Load the CIA Header // Load the CIA Header
ResultVal<std::size_t> read_result = backend.Read(0, sizeof(Header), header_data.data()); ResultVal<std::size_t> read_result = backend.Read(0, sizeof(CIAHeader), header_data.data());
if (read_result.Failed() || *read_result != sizeof(Header)) if (read_result.Failed() || *read_result != sizeof(CIAHeader))
return Loader::ResultStatus::Error; return Loader::ResultStatus::Error;
Loader::ResultStatus result = LoadHeader(header_data); Loader::ResultStatus result = LoadHeader(header_data);
@ -65,8 +65,8 @@ Loader::ResultStatus CIAContainer::Load(const std::string& filepath) {
return Loader::ResultStatus::Error; return Loader::ResultStatus::Error;
// Load CIA Header // Load CIA Header
std::vector<u8> header_data(sizeof(Header)); std::vector<u8> header_data(sizeof(CIAHeader));
if (file.ReadBytes(header_data.data(), sizeof(Header)) != sizeof(Header)) if (file.ReadBytes(header_data.data(), sizeof(CIAHeader)) != sizeof(CIAHeader))
return Loader::ResultStatus::Error; return Loader::ResultStatus::Error;
Loader::ResultStatus result = LoadHeader(header_data); Loader::ResultStatus result = LoadHeader(header_data);
@ -134,11 +134,12 @@ Loader::ResultStatus CIAContainer::Load(std::span<const u8> file_data) {
} }
Loader::ResultStatus CIAContainer::LoadHeader(std::span<const u8> header_data, std::size_t offset) { Loader::ResultStatus CIAContainer::LoadHeader(std::span<const u8> header_data, std::size_t offset) {
if (header_data.size() - offset < sizeof(Header)) { if (header_data.size() - offset < sizeof(CIAHeader)) {
return Loader::ResultStatus::Error; return Loader::ResultStatus::Error;
} }
std::memcpy(&cia_header, header_data.data(), sizeof(Header)); std::memcpy(&cia_header, header_data.data(), sizeof(CIAHeader));
has_header = true;
return Loader::ResultStatus::Success; return Loader::ResultStatus::Success;
} }
@ -265,4 +266,7 @@ void CIAContainer::Print() const {
LOG_DEBUG(Service_FS, "Content {:x} Offset: 0x{:08x} bytes", i, GetContentOffset(i)); LOG_DEBUG(Service_FS, "Content {:x} Offset: 0x{:08x} bytes", i, GetContentOffset(i));
} }
} }
const CIAHeader* CIAContainer::GetHeader() {
return has_header ? &cia_header : nullptr;
}
} // namespace FileSys } // namespace FileSys

View file

@ -28,6 +28,30 @@ constexpr std::size_t CIA_DEPENDENCY_SIZE = 0x300;
constexpr std::size_t CIA_METADATA_SIZE = 0x400; constexpr std::size_t CIA_METADATA_SIZE = 0x400;
constexpr u32 CIA_SECTION_ALIGNMENT = 0x40; constexpr u32 CIA_SECTION_ALIGNMENT = 0x40;
struct CIAHeader {
u32_le header_size;
u16_le type;
u16_le version;
u32_le cert_size;
u32_le tik_size;
u32_le tmd_size;
u32_le meta_size;
u64_le content_size;
std::array<u8, CIA_CONTENT_BITS_SIZE> content_present;
bool IsContentPresent(std::size_t index) const {
// The content_present is a bit array which defines which content in the TMD
// is included in the CIA, so check the bit for this index and add if set.
// The bits in the content index are arranged w/ index 0 as the MSB, 7 as the LSB, etc.
return (content_present[index >> 3] & (0x80 >> (index & 7))) != 0;
}
void SetContentPresent(u16 index) {
content_present[index >> 3] |= (0x80 >> (index & 7));
}
};
static_assert(sizeof(CIAHeader) == CIA_HEADER_SIZE, "CIA Header structure size is wrong");
/** /**
* Helper which implements an interface to read and write CTR Installable Archive (CIA) files. * Helper which implements an interface to read and write CTR Installable Archive (CIA) files.
* Data can either be loaded from a FileBackend, a string path, or from a data array. Data can * Data can either be loaded from a FileBackend, a string path, or from a data array. Data can
@ -49,6 +73,7 @@ public:
Loader::ResultStatus LoadTitleMetadata(const TitleMetadata& tmd); Loader::ResultStatus LoadTitleMetadata(const TitleMetadata& tmd);
Loader::ResultStatus LoadMetadata(std::span<const u8> meta_data, std::size_t offset = 0); Loader::ResultStatus LoadMetadata(std::span<const u8> meta_data, std::size_t offset = 0);
const CIAHeader* GetHeader();
Ticket& GetTicket(); Ticket& GetTicket();
const TitleMetadata& GetTitleMetadata() const; const TitleMetadata& GetTitleMetadata() const;
std::array<u64, 0x30>& GetDependencies(); std::array<u64, 0x30>& GetDependencies();
@ -69,30 +94,6 @@ public:
void Print() const; void Print() const;
struct Header {
u32_le header_size;
u16_le type;
u16_le version;
u32_le cert_size;
u32_le tik_size;
u32_le tmd_size;
u32_le meta_size;
u64_le content_size;
std::array<u8, CIA_CONTENT_BITS_SIZE> content_present;
bool IsContentPresent(std::size_t index) const {
// The content_present is a bit array which defines which content in the TMD
// is included in the CIA, so check the bit for this index and add if set.
// The bits in the content index are arranged w/ index 0 as the MSB, 7 as the LSB, etc.
return (content_present[index >> 3] & (0x80 >> (index & 7))) != 0;
}
void SetContentPresent(u16 index) {
content_present[index >> 3] |= (0x80 >> (index & 7));
}
};
static_assert(sizeof(Header) == CIA_HEADER_SIZE, "CIA Header structure size is wrong");
private: private:
struct Metadata { struct Metadata {
std::array<u64_le, 0x30> dependencies; std::array<u64_le, 0x30> dependencies;
@ -103,7 +104,8 @@ private:
static_assert(sizeof(Metadata) == CIA_METADATA_SIZE, "CIA Metadata structure size is wrong"); static_assert(sizeof(Metadata) == CIA_METADATA_SIZE, "CIA Metadata structure size is wrong");
Header cia_header; bool has_header = false;
CIAHeader cia_header;
Metadata cia_metadata; Metadata cia_metadata;
Ticket cia_ticket; Ticket cia_ticket;
TitleMetadata cia_tmd; TitleMetadata cia_tmd;

View file

@ -6,6 +6,7 @@
#include "common/alignment.h" #include "common/alignment.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/file_sys/cia_container.h"
#include "core/file_sys/signature.h" #include "core/file_sys/signature.h"
#include "core/file_sys/title_metadata.h" #include "core/file_sys/title_metadata.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
@ -185,9 +186,14 @@ std::array<u8, 16> TitleMetadata::GetContentCTRByIndex(std::size_t index) const
return ctr; return ctr;
} }
bool TitleMetadata::HasEncryptedContent() const { bool TitleMetadata::HasEncryptedContent(const CIAHeader* header) const {
return std::any_of(tmd_chunks.begin(), tmd_chunks.end(), [](auto& chunk) { return std::any_of(tmd_chunks.begin(), tmd_chunks.end(), [header](auto& chunk) {
return (static_cast<u16>(chunk.type) & FileSys::TMDContentTypeFlag::Encrypted) != 0; bool is_crypted =
(static_cast<u16>(chunk.type) & FileSys::TMDContentTypeFlag::Encrypted) != 0;
if (header) {
is_crypted = is_crypted && header->IsContentPresent(static_cast<u16>(chunk.index));
}
return is_crypted;
}); });
} }

View file

@ -17,6 +17,8 @@ enum class ResultStatus;
namespace FileSys { namespace FileSys {
struct CIAHeader;
enum TMDContentTypeFlag : u16 { enum TMDContentTypeFlag : u16 {
Encrypted = 1 << 0, Encrypted = 1 << 0,
Disc = 1 << 2, Disc = 1 << 2,
@ -99,7 +101,7 @@ public:
u64 GetContentSizeByIndex(std::size_t index) const; u64 GetContentSizeByIndex(std::size_t index) const;
bool GetContentOptional(std::size_t index) const; bool GetContentOptional(std::size_t index) const;
std::array<u8, 16> GetContentCTRByIndex(std::size_t index) const; std::array<u8, 16> GetContentCTRByIndex(std::size_t index) const;
bool HasEncryptedContent() const; bool HasEncryptedContent(const CIAHeader* header = nullptr) const;
void SetTitleID(u64 title_id); void SetTitleID(u64 title_id);
void SetTitleType(u32 type); void SetTitleType(u32 type);

View file

@ -425,8 +425,8 @@ CIAFile::CIAFile(Core::System& system_, Service::FS::MediaType media_type, bool
// If data is being installing from CDN, provide a fake header to the container so that // If data is being installing from CDN, provide a fake header to the container so that
// it's not uninitialized. // it's not uninitialized.
if (from_cdn) { if (from_cdn) {
FileSys::CIAContainer::Header fake_header{ FileSys::CIAHeader fake_header{
.header_size = sizeof(FileSys::CIAContainer::Header), .header_size = sizeof(FileSys::CIAHeader),
.type = 0, .type = 0,
.version = 0, .version = 0,
.cert_size = 0, .cert_size = 0,
@ -548,6 +548,11 @@ ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length,
buffer + (range_min - offset) + available_to_write); buffer + (range_min - offset) + available_to_write);
if ((tmd.GetContentTypeByIndex(i) & FileSys::TMDContentTypeFlag::Encrypted) != 0) { if ((tmd.GetContentTypeByIndex(i) & FileSys::TMDContentTypeFlag::Encrypted) != 0) {
if (!decryption_authorized) {
LOG_ERROR(Service_AM, "Blocked unauthorized encrypted CIA installation.");
return Result(ErrorDescription::NotAuthorized, ErrorModule::AM,
ErrorSummary::InvalidState, ErrorLevel::Permanent);
}
decryption_state->content[i].ProcessData(temp.data(), temp.data(), temp.size()); decryption_state->content[i].ProcessData(temp.data(), temp.data(), temp.size());
} }
@ -663,7 +668,8 @@ Result CIAFile::PrepareToImportContent(const FileSys::TitleMetadata& tmd) {
content_file_paths.emplace_back(path); content_file_paths.emplace_back(path);
} }
if (container.GetTitleMetadata().HasEncryptedContent()) { if (container.GetTitleMetadata().HasEncryptedContent(from_cdn ? nullptr
: container.GetHeader())) {
if (!decryption_authorized) { if (!decryption_authorized) {
LOG_ERROR(Service_AM, "Blocked unauthorized encrypted CIA installation."); LOG_ERROR(Service_AM, "Blocked unauthorized encrypted CIA installation.");
return {ErrorDescription::NotAuthorized, ErrorModule::AM, ErrorSummary::InvalidState, return {ErrorDescription::NotAuthorized, ErrorModule::AM, ErrorSummary::InvalidState,
@ -728,9 +734,7 @@ Result CIAFile::ProvideTMDForAdditionalContent(const FileSys::TitleMetadata& tmd
is_additional_content = true; is_additional_content = true;
PrepareToImportContent(container.GetTitleMetadata()); return PrepareToImportContent(container.GetTitleMetadata());
return ResultSuccess;
} }
const FileSys::TitleMetadata& CIAFile::GetTMD() { const FileSys::TitleMetadata& CIAFile::GetTMD() {
@ -762,6 +766,11 @@ ResultVal<std::size_t> CIAFile::WriteContentDataIndexed(u16 content_index, u64 o
std::vector<u8> temp(buffer, buffer + std::min(static_cast<u64>(length), remaining_to_write)); std::vector<u8> temp(buffer, buffer + std::min(static_cast<u64>(length), remaining_to_write));
if ((tmd.GetContentTypeByIndex(content_index) & FileSys::TMDContentTypeFlag::Encrypted) != 0) { if ((tmd.GetContentTypeByIndex(content_index) & FileSys::TMDContentTypeFlag::Encrypted) != 0) {
if (!decryption_authorized) {
LOG_ERROR(Service_AM, "Blocked unauthorized encrypted CIA installation.");
return Result(ErrorDescription::NotAuthorized, ErrorModule::AM,
ErrorSummary::InvalidState, ErrorLevel::Permanent);
}
decryption_state->content[content_index].ProcessData(temp.data(), temp.data(), temp.size()); decryption_state->content[content_index].ProcessData(temp.data(), temp.data(), temp.size());
} }
@ -1000,7 +1009,7 @@ InstallStatus InstallCIA(const std::string& path,
Core::System::GetInstance(), Core::System::GetInstance(),
Service::AM::GetTitleMediaType(container.GetTitleMetadata().GetTitleID())); Service::AM::GetTitleMediaType(container.GetTitleMetadata().GetTitleID()));
if (container.GetTitleMetadata().HasEncryptedContent()) { if (container.GetTitleMetadata().HasEncryptedContent(container.GetHeader())) {
LOG_ERROR(Service_AM, "File {} is encrypted! Aborting...", path); LOG_ERROR(Service_AM, "File {} is encrypted! Aborting...", path);
return InstallStatus::ErrorEncrypted; return InstallStatus::ErrorEncrypted;
} }