mirror of
https://github.com/azahar-emu/azahar
synced 2025-11-06 23:19:57 +01:00
Z3DS: Add title info and smdh to metadata
This commit is contained in:
parent
db8aaacb35
commit
00c0f01e73
@ -3076,12 +3076,24 @@ void GMainWindow::OnCompressFile() {
|
||||
bool is_compressed = false;
|
||||
if (Service::AM::CheckCIAToInstall(in_path, is_compressed, true) ==
|
||||
Service::AM::InstallStatus::Success) {
|
||||
auto meta_info = Service::AM::GetCIAInfos(in_path);
|
||||
compress_info.is_supported = true;
|
||||
compress_info.is_compressed = is_compressed;
|
||||
compress_info.recommended_compressed_extension = "zcia";
|
||||
compress_info.recommended_uncompressed_extension = "cia";
|
||||
compress_info.underlying_magic = std::array<u8, 4>({'C', 'I', 'A', '\0'});
|
||||
frame_size = FileUtil::Z3DSWriteIOFile::DEFAULT_CIA_FRAME_SIZE;
|
||||
if (meta_info.Succeeded()) {
|
||||
const auto& meta_info_val = meta_info.Unwrap();
|
||||
std::vector<u8> value(sizeof(Service::AM::TitleInfo));
|
||||
memcpy(value.data(), &meta_info_val.first, sizeof(Service::AM::TitleInfo));
|
||||
compress_info.default_metadata.emplace("titleinfo", value);
|
||||
if (meta_info_val.second) {
|
||||
value.resize(sizeof(Loader::SMDH));
|
||||
memcpy(value.data(), meta_info_val.second.get(), sizeof(Loader::SMDH));
|
||||
compress_info.default_metadata.emplace("smdh", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3121,8 +3133,9 @@ void GMainWindow::OnCompressFile() {
|
||||
const auto progress = [&](std::size_t written, std::size_t total) {
|
||||
emit UpdateProgress(written, total);
|
||||
};
|
||||
bool success = FileUtil::CompressZ3DSFile(in_path, out_path, compress_info.underlying_magic,
|
||||
frame_size, progress);
|
||||
bool success =
|
||||
FileUtil::CompressZ3DSFile(in_path, out_path, compress_info.underlying_magic,
|
||||
frame_size, progress, compress_info.default_metadata);
|
||||
if (!success) {
|
||||
FileUtil::Delete(out_path);
|
||||
}
|
||||
|
||||
@ -121,6 +121,7 @@ Z3DSMetadata::Z3DSMetadata(const std::span<u8>& source_data) {
|
||||
}
|
||||
// Only binary type supported for now
|
||||
if (item.type != Item::TYPE_BINARY) {
|
||||
in.ignore(static_cast<std::streamsize>(item.name_len) + item.data_len);
|
||||
continue;
|
||||
}
|
||||
std::string name(item.name_len, '\0');
|
||||
@ -665,7 +666,8 @@ void Z3DSReadIOFile::serialize(Archive& ar, const unsigned int) {
|
||||
|
||||
bool CompressZ3DSFile(const std::string& src_file_name, const std::string& dst_file_name,
|
||||
const std::array<u8, 4>& underlying_magic, size_t frame_size,
|
||||
std::function<ProgressCallback>&& update_callback) {
|
||||
std::function<ProgressCallback>&& update_callback,
|
||||
std::unordered_map<std::string, std::vector<u8>> metadata) {
|
||||
|
||||
IOFile in_file(src_file_name, "rb");
|
||||
if (!in_file.IsOpen()) {
|
||||
@ -687,6 +689,12 @@ bool CompressZ3DSFile(const std::string& src_file_name, const std::string& dst_f
|
||||
|
||||
Z3DSWriteIOFile out_compress_file(std::move(out_file), underlying_magic, frame_size);
|
||||
|
||||
for (auto& it : metadata) {
|
||||
std::string val_str(it.second.size(), '\0');
|
||||
memcpy(val_str.data(), it.second.data(), val_str.size());
|
||||
out_compress_file.Metadata().Add(it.first, val_str);
|
||||
}
|
||||
|
||||
size_t next_chunk = out_compress_file.GetNextWriteHint();
|
||||
std::vector<u8> buffer(next_chunk);
|
||||
size_t in_size = in_file.GetSize();
|
||||
|
||||
@ -261,7 +261,8 @@ using ProgressCallback = void(std::size_t, std::size_t);
|
||||
|
||||
bool CompressZ3DSFile(const std::string& src_file, const std::string& dst_file,
|
||||
const std::array<u8, 4>& underlying_magic, size_t frame_size,
|
||||
std::function<ProgressCallback>&& update_callback = nullptr);
|
||||
std::function<ProgressCallback>&& update_callback = nullptr,
|
||||
std::unordered_map<std::string, std::vector<u8>> metadata = {});
|
||||
|
||||
bool DeCompressZ3DSFile(const std::string& src_file, const std::string& dst_file,
|
||||
std::function<ProgressCallback>&& update_callback = nullptr);
|
||||
|
||||
@ -54,6 +54,17 @@ Loader::ResultStatus CIAContainer::Load(const FileBackend& backend) {
|
||||
result = LoadMetadata(meta_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
if (cia_header.meta_size >= sizeof(Metadata) + sizeof(Loader::SMDH)) {
|
||||
std::vector<u8> smdh_data(sizeof(Loader::SMDH));
|
||||
read_result = backend.Read(GetMetadataOffset() + CIA_METADATA_SIZE,
|
||||
sizeof(Loader::SMDH), smdh_data.data());
|
||||
if (read_result.Failed() || *read_result != sizeof(Loader::SMDH))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
result = LoadSMDH(smdh_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
@ -102,6 +113,16 @@ Loader::ResultStatus CIAContainer::Load(FileUtil::IOFile* file) {
|
||||
result = LoadMetadata(meta_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
if (cia_header.meta_size >= sizeof(Metadata) + sizeof(Loader::SMDH)) {
|
||||
std::vector<u8> smdh_data(sizeof(Loader::SMDH));
|
||||
file->Seek(GetMetadataOffset() + CIA_METADATA_SIZE, SEEK_SET);
|
||||
if (file->ReadBytes(smdh_data.data(), sizeof(Loader::SMDH)) != sizeof(Loader::SMDH))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
result = LoadSMDH(smdh_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
@ -127,6 +148,11 @@ Loader::ResultStatus CIAContainer::Load(std::span<const u8> file_data) {
|
||||
result = LoadMetadata(file_data, GetMetadataOffset());
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
if (cia_header.meta_size >= sizeof(Metadata) + sizeof(Loader::SMDH)) {
|
||||
result = LoadSMDH(file_data, GetMetadataOffset() + CIA_METADATA_SIZE);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
@ -172,6 +198,18 @@ Loader::ResultStatus CIAContainer::LoadMetadata(std::span<const u8> meta_data, s
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus CIAContainer::LoadSMDH(std::span<const u8> smdh_data, std::size_t offset) {
|
||||
if (smdh_data.size() - offset < sizeof(Loader::SMDH)) {
|
||||
return Loader::ResultStatus::Error;
|
||||
}
|
||||
|
||||
cia_smdh = std::make_unique<Loader::SMDH>();
|
||||
|
||||
std::memcpy(cia_smdh.get(), smdh_data.data(), sizeof(Loader::SMDH));
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Ticket& CIAContainer::GetTicket() {
|
||||
return cia_ticket;
|
||||
}
|
||||
@ -188,6 +226,10 @@ u32 CIAContainer::GetCoreVersion() const {
|
||||
return cia_metadata.core_version;
|
||||
}
|
||||
|
||||
const std::unique_ptr<Loader::SMDH>& CIAContainer::GetSMDH() const {
|
||||
return cia_smdh;
|
||||
}
|
||||
|
||||
u64 CIAContainer::GetCertificateOffset() const {
|
||||
return Common::AlignUp(cia_header.header_size, CIA_SECTION_ALIGNMENT);
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/ticket.h"
|
||||
#include "core/file_sys/title_metadata.h"
|
||||
#include "core/loader/smdh.h"
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus;
|
||||
@ -73,12 +74,14 @@ public:
|
||||
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);
|
||||
Loader::ResultStatus LoadSMDH(std::span<const u8> smdh_data, std::size_t offset = 0);
|
||||
|
||||
const CIAHeader* GetHeader();
|
||||
Ticket& GetTicket();
|
||||
const TitleMetadata& GetTitleMetadata() const;
|
||||
std::array<u64, 0x30>& GetDependencies();
|
||||
u32 GetCoreVersion() const;
|
||||
const std::unique_ptr<Loader::SMDH>& GetSMDH() const;
|
||||
|
||||
u64 GetCertificateOffset() const;
|
||||
u64 GetTicketOffset() const;
|
||||
@ -108,6 +111,7 @@ private:
|
||||
bool has_header = false;
|
||||
CIAHeader cia_header;
|
||||
Metadata cia_metadata;
|
||||
std::unique_ptr<Loader::SMDH> cia_smdh;
|
||||
Ticket cia_ticket;
|
||||
TitleMetadata cia_tmd;
|
||||
};
|
||||
|
||||
@ -91,6 +91,10 @@ struct NCCH_Header {
|
||||
u8 reserved_4[4];
|
||||
u8 exefs_super_block_hash[0x20];
|
||||
u8 romfs_super_block_hash[0x20];
|
||||
|
||||
u32 GetContentUnitSize() {
|
||||
return 0x200u * (1u << content_unit_size);
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(NCCH_Header) == 0x200, "NCCH header structure size is wrong");
|
||||
|
||||
@ -176,6 +176,17 @@ u64 TitleMetadata::GetContentSizeByIndex(std::size_t index) const {
|
||||
return tmd_chunks[index].size;
|
||||
}
|
||||
|
||||
u64 TitleMetadata::GetCombinedContentSize(const CIAHeader* header) const {
|
||||
u64 total_size = 0;
|
||||
for (auto& chunk : tmd_chunks) {
|
||||
if (header && !header->IsContentPresent(static_cast<u16>(chunk.index))) {
|
||||
continue;
|
||||
}
|
||||
total_size += chunk.size;
|
||||
}
|
||||
return total_size;
|
||||
}
|
||||
|
||||
bool TitleMetadata::GetContentOptional(std::size_t index) const {
|
||||
return (static_cast<u16>(tmd_chunks[index].type) & FileSys::TMDContentTypeFlag::Optional) != 0;
|
||||
}
|
||||
|
||||
@ -99,6 +99,7 @@ public:
|
||||
u32 GetContentIDByIndex(std::size_t index) const;
|
||||
u16 GetContentTypeByIndex(std::size_t index) const;
|
||||
u64 GetContentSizeByIndex(std::size_t index) const;
|
||||
u64 GetCombinedContentSize(const CIAHeader* header) const;
|
||||
bool GetContentOptional(std::size_t index) const;
|
||||
std::array<u8, 16> GetContentCTRByIndex(std::size_t index) const;
|
||||
bool HasEncryptedContent(const CIAHeader* header = nullptr) const;
|
||||
|
||||
@ -56,16 +56,6 @@ constexpr u8 VARIATION_SYSTEM = 0x02;
|
||||
constexpr u32 TID_HIGH_UPDATE = 0x0004000E;
|
||||
constexpr u32 TID_HIGH_DLC = 0x0004008C;
|
||||
|
||||
struct TitleInfo {
|
||||
u64_le tid;
|
||||
u64_le size;
|
||||
u16_le version;
|
||||
u16_le unused;
|
||||
u32_le type;
|
||||
};
|
||||
|
||||
static_assert(sizeof(TitleInfo) == 0x18, "Title info structure size is wrong");
|
||||
|
||||
constexpr u8 OWNERSHIP_DOWNLOADED = 0x01;
|
||||
constexpr u8 OWNERSHIP_OWNED = 0x02;
|
||||
|
||||
@ -1183,6 +1173,37 @@ InstallStatus CheckCIAToInstall(const std::string& path, bool& is_compressed,
|
||||
return InstallStatus::ErrorInvalid;
|
||||
}
|
||||
|
||||
ResultVal<std::pair<TitleInfo, std::unique_ptr<Loader::SMDH>>> GetCIAInfos(
|
||||
const std::string& path) {
|
||||
if (!FileUtil::Exists(path)) {
|
||||
LOG_ERROR(Service_AM, "File {} does not exist!", path);
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
std::unique_ptr<FileUtil::IOFile> in_file = std::make_unique<FileUtil::IOFile>(path, "rb");
|
||||
FileSys::CIAContainer container;
|
||||
if (container.Load(in_file.get()) == Loader::ResultStatus::Success) {
|
||||
in_file->Seek(0, SEEK_SET);
|
||||
const FileSys::TitleMetadata& tmd = container.GetTitleMetadata();
|
||||
|
||||
TitleInfo info{};
|
||||
info.tid = tmd.GetTitleID();
|
||||
info.version = tmd.GetTitleVersion();
|
||||
info.size = tmd.GetCombinedContentSize(container.GetHeader());
|
||||
info.type = tmd.GetTitleType();
|
||||
|
||||
const auto& cia_smdh = container.GetSMDH();
|
||||
std::unique_ptr<Loader::SMDH> smdh{};
|
||||
if (cia_smdh) {
|
||||
smdh = std::make_unique<Loader::SMDH>(*cia_smdh);
|
||||
}
|
||||
|
||||
return std::pair<TitleInfo, std::unique_ptr<Loader::SMDH>>(info, std::move(smdh));
|
||||
}
|
||||
|
||||
return ResultUnknown;
|
||||
}
|
||||
|
||||
u64 GetTitleUpdateId(u64 title_id) {
|
||||
// Real services seem to just discard and replace the whole high word.
|
||||
return (title_id & 0xFFFFFFFF) | (static_cast<u64>(TID_HIGH_UPDATE) << 32);
|
||||
|
||||
@ -105,6 +105,15 @@ struct ImportContentContext {
|
||||
};
|
||||
static_assert(sizeof(ImportContentContext) == 0x18, "Invalid ImportContentContext size");
|
||||
|
||||
struct TitleInfo {
|
||||
u64_le tid;
|
||||
u64_le size;
|
||||
u16_le version;
|
||||
u16_le unused;
|
||||
u32_le type;
|
||||
};
|
||||
static_assert(sizeof(TitleInfo) == 0x18, "Title info structure size is wrong");
|
||||
|
||||
// Title ID valid length
|
||||
constexpr std::size_t TITLE_ID_VALID_LENGTH = 16;
|
||||
|
||||
@ -367,6 +376,11 @@ InstallStatus InstallCIA(const std::string& path,
|
||||
InstallStatus CheckCIAToInstall(const std::string& path, bool& is_compressed,
|
||||
bool check_encryption);
|
||||
|
||||
/**
|
||||
* Get CIA metadata information from file.
|
||||
*/
|
||||
ResultVal<std::pair<TitleInfo, std::unique_ptr<Loader::SMDH>>> GetCIAInfos(const std::string& path);
|
||||
|
||||
/**
|
||||
* Get the update title ID for a title
|
||||
* @param titleId the title ID
|
||||
|
||||
@ -91,6 +91,7 @@ public:
|
||||
std::array<u8, 4> underlying_magic{};
|
||||
std::string recommended_compressed_extension;
|
||||
std::string recommended_uncompressed_extension;
|
||||
std::unordered_map<std::string, std::vector<u8>> default_metadata;
|
||||
};
|
||||
|
||||
explicit AppLoader(Core::System& system_, FileUtil::IOFile&& file)
|
||||
|
||||
@ -424,6 +424,16 @@ AppLoader::CompressFileInfo AppLoader_NCCH::GetCompressFileInfo() {
|
||||
info.recommended_compressed_extension = "zcxi";
|
||||
info.recommended_uncompressed_extension = "cxi";
|
||||
}
|
||||
std::vector<u8> title_info_vec(sizeof(Service::AM::TitleInfo));
|
||||
Service::AM::TitleInfo* title_info =
|
||||
reinterpret_cast<Service::AM::TitleInfo*>(title_info_vec.data());
|
||||
title_info->tid = base_ncch.ncch_header.program_id;
|
||||
title_info->version = base_ncch.ncch_header.version;
|
||||
title_info->size =
|
||||
base_ncch.ncch_header.content_size * base_ncch.ncch_header.GetContentUnitSize();
|
||||
title_info->unused = title_info->type = 0;
|
||||
info.default_metadata.emplace("titleinfo", title_info_vec);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -22,6 +22,10 @@ bool IsValidSMDH(std::span<const u8> smdh_data) {
|
||||
return Loader::MakeMagic('S', 'M', 'D', 'H') == magic;
|
||||
}
|
||||
|
||||
bool SMDH::IsValid() const {
|
||||
return Loader::MakeMagic('S', 'M', 'D', 'H') == magic;
|
||||
}
|
||||
|
||||
std::vector<u16> SMDH::GetIcon(bool large) const {
|
||||
u32 size;
|
||||
const u8* icon_data;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -77,6 +77,11 @@ struct SMDH {
|
||||
Visible = 1 << 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if SMDH is valid.
|
||||
*/
|
||||
bool IsValid() const;
|
||||
|
||||
/**
|
||||
* Gets game icon from SMDH
|
||||
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user