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 {
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
ResultVal<std::size_t> read_result = backend.Read(0, sizeof(Header), header_data.data());
if (read_result.Failed() || *read_result != sizeof(Header))
ResultVal<std::size_t> read_result = backend.Read(0, sizeof(CIAHeader), header_data.data());
if (read_result.Failed() || *read_result != sizeof(CIAHeader))
return Loader::ResultStatus::Error;
Loader::ResultStatus result = LoadHeader(header_data);
@ -65,8 +65,8 @@ Loader::ResultStatus CIAContainer::Load(const std::string& filepath) {
return Loader::ResultStatus::Error;
// Load CIA Header
std::vector<u8> header_data(sizeof(Header));
if (file.ReadBytes(header_data.data(), sizeof(Header)) != sizeof(Header))
std::vector<u8> header_data(sizeof(CIAHeader));
if (file.ReadBytes(header_data.data(), sizeof(CIAHeader)) != sizeof(CIAHeader))
return Loader::ResultStatus::Error;
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) {
if (header_data.size() - offset < sizeof(Header)) {
if (header_data.size() - offset < sizeof(CIAHeader)) {
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;
}
@ -265,4 +266,7 @@ void CIAContainer::Print() const {
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

View file

@ -28,48 +28,7 @@ constexpr std::size_t CIA_DEPENDENCY_SIZE = 0x300;
constexpr std::size_t CIA_METADATA_SIZE = 0x400;
constexpr u32 CIA_SECTION_ALIGNMENT = 0x40;
/**
* 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
* also be partially loaded for CIAs which are downloading/streamed in and need some metadata
* read out.
*/
class CIAContainer {
public:
// Load whole CIAs outright
Loader::ResultStatus Load(const FileBackend& backend);
Loader::ResultStatus Load(const std::string& filepath);
Loader::ResultStatus Load(std::span<const u8> header_data);
// Load parts of CIAs (for CIAs streamed in)
Loader::ResultStatus LoadHeader(std::span<const u8> header_data, std::size_t offset = 0);
Loader::ResultStatus LoadTicket(std::span<const u8> ticket_data, std::size_t offset = 0);
Loader::ResultStatus LoadTicket(const Ticket& ticket);
Loader::ResultStatus LoadTitleMetadata(std::span<const u8> tmd_data, std::size_t offset = 0);
Loader::ResultStatus LoadTitleMetadata(const TitleMetadata& tmd);
Loader::ResultStatus LoadMetadata(std::span<const u8> meta_data, std::size_t offset = 0);
Ticket& GetTicket();
const TitleMetadata& GetTitleMetadata() const;
std::array<u64, 0x30>& GetDependencies();
u32 GetCoreVersion() const;
u64 GetCertificateOffset() const;
u64 GetTicketOffset() const;
u64 GetTitleMetadataOffset() const;
u64 GetMetadataOffset() const;
u64 GetContentOffset(std::size_t index = 0) const;
u32 GetCertificateSize() const;
u32 GetTicketSize() const;
u32 GetTitleMetadataSize() const;
u32 GetMetadataSize() const;
u64 GetTotalContentSize() const;
u64 GetContentSize(std::size_t index = 0) const;
void Print() const;
struct Header {
struct CIAHeader {
u32_le header_size;
u16_le type;
u16_le version;
@ -91,7 +50,49 @@ public:
}
};
static_assert(sizeof(Header) == CIA_HEADER_SIZE, "CIA Header structure size is wrong");
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.
* Data can either be loaded from a FileBackend, a string path, or from a data array. Data can
* also be partially loaded for CIAs which are downloading/streamed in and need some metadata
* read out.
*/
class CIAContainer {
public:
// Load whole CIAs outright
Loader::ResultStatus Load(const FileBackend& backend);
Loader::ResultStatus Load(const std::string& filepath);
Loader::ResultStatus Load(std::span<const u8> header_data);
// Load parts of CIAs (for CIAs streamed in)
Loader::ResultStatus LoadHeader(std::span<const u8> header_data, std::size_t offset = 0);
Loader::ResultStatus LoadTicket(std::span<const u8> ticket_data, std::size_t offset = 0);
Loader::ResultStatus LoadTicket(const Ticket& ticket);
Loader::ResultStatus LoadTitleMetadata(std::span<const u8> tmd_data, std::size_t offset = 0);
Loader::ResultStatus LoadTitleMetadata(const TitleMetadata& tmd);
Loader::ResultStatus LoadMetadata(std::span<const u8> meta_data, std::size_t offset = 0);
const CIAHeader* GetHeader();
Ticket& GetTicket();
const TitleMetadata& GetTitleMetadata() const;
std::array<u64, 0x30>& GetDependencies();
u32 GetCoreVersion() const;
u64 GetCertificateOffset() const;
u64 GetTicketOffset() const;
u64 GetTitleMetadataOffset() const;
u64 GetMetadataOffset() const;
u64 GetContentOffset(std::size_t index = 0) const;
u32 GetCertificateSize() const;
u32 GetTicketSize() const;
u32 GetTitleMetadataSize() const;
u32 GetMetadataSize() const;
u64 GetTotalContentSize() const;
u64 GetContentSize(std::size_t index = 0) const;
void Print() const;
private:
struct Metadata {
@ -103,7 +104,8 @@ private:
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;
Ticket cia_ticket;
TitleMetadata cia_tmd;

View file

@ -6,6 +6,7 @@
#include "common/alignment.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/file_sys/cia_container.h"
#include "core/file_sys/signature.h"
#include "core/file_sys/title_metadata.h"
#include "core/loader/loader.h"
@ -185,9 +186,14 @@ std::array<u8, 16> TitleMetadata::GetContentCTRByIndex(std::size_t index) const
return ctr;
}
bool TitleMetadata::HasEncryptedContent() const {
return std::any_of(tmd_chunks.begin(), tmd_chunks.end(), [](auto& chunk) {
return (static_cast<u16>(chunk.type) & FileSys::TMDContentTypeFlag::Encrypted) != 0;
bool TitleMetadata::HasEncryptedContent(const CIAHeader* header) const {
return std::any_of(tmd_chunks.begin(), tmd_chunks.end(), [header](auto& chunk) {
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 {
struct CIAHeader;
enum TMDContentTypeFlag : u16 {
Encrypted = 1 << 0,
Disc = 1 << 2,
@ -99,7 +101,7 @@ public:
u64 GetContentSizeByIndex(std::size_t index) const;
bool GetContentOptional(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 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
// it's not uninitialized.
if (from_cdn) {
FileSys::CIAContainer::Header fake_header{
.header_size = sizeof(FileSys::CIAContainer::Header),
FileSys::CIAHeader fake_header{
.header_size = sizeof(FileSys::CIAHeader),
.type = 0,
.version = 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);
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());
}
@ -663,7 +668,8 @@ Result CIAFile::PrepareToImportContent(const FileSys::TitleMetadata& tmd) {
content_file_paths.emplace_back(path);
}
if (container.GetTitleMetadata().HasEncryptedContent()) {
if (container.GetTitleMetadata().HasEncryptedContent(from_cdn ? nullptr
: container.GetHeader())) {
if (!decryption_authorized) {
LOG_ERROR(Service_AM, "Blocked unauthorized encrypted CIA installation.");
return {ErrorDescription::NotAuthorized, ErrorModule::AM, ErrorSummary::InvalidState,
@ -728,9 +734,7 @@ Result CIAFile::ProvideTMDForAdditionalContent(const FileSys::TitleMetadata& tmd
is_additional_content = true;
PrepareToImportContent(container.GetTitleMetadata());
return ResultSuccess;
return PrepareToImportContent(container.GetTitleMetadata());
}
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));
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());
}
@ -1000,7 +1009,7 @@ InstallStatus InstallCIA(const std::string& path,
Core::System::GetInstance(),
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);
return InstallStatus::ErrorEncrypted;
}