diff --git a/src/core/ncch/title_metadata.cpp b/src/core/ncch/title_metadata.cpp index d0b4a14..5e48239 100644 --- a/src/core/ncch/title_metadata.cpp +++ b/src/core/ncch/title_metadata.cpp @@ -55,6 +55,62 @@ ResultStatus TitleMetadata::Load(const std::vector file_data, std::size_t of return ResultStatus::Success; } +ResultStatus TitleMetadata::Save(const std::string& file_path) { + FileUtil::IOFile file(file_path, "wb"); + if (!file.IsOpen()) + return ResultStatus::Error; + + if (!file.WriteBytes(&signature_type, sizeof(u32_be))) + return ResultStatus::Error; + + // Signature lengths are variable, and the body follows the signature + u32 signature_size = GetSignatureSize(signature_type); + if (signature_size == 0) { + return ResultStatus::Error; + } + + if (!file.WriteBytes(tmd_signature.data(), signature_size)) + return ResultStatus::Error; + + // The TMD body start position is rounded to the nearest 0x40 after the signature + std::size_t body_start = Common::AlignUp(signature_size + sizeof(u32), 0x40); + file.Seek(body_start, SEEK_SET); + + // Update our TMD body values and hashes + tmd_body.content_count = static_cast(tmd_chunks.size()); + + // TODO(shinyquagsire23): Do TMDs with more than one contentinfo exist? + // For now we'll just adjust the first index to hold all content chunks + // and ensure that no further content info data exists. + tmd_body.contentinfo = {}; + tmd_body.contentinfo[0].index = 0; + tmd_body.contentinfo[0].command_count = static_cast(tmd_chunks.size()); + + CryptoPP::SHA256 chunk_hash; + for (u16 i = 0; i < tmd_body.content_count; i++) { + chunk_hash.Update(reinterpret_cast(&tmd_chunks[i]), sizeof(ContentChunk)); + } + chunk_hash.Final(tmd_body.contentinfo[0].hash.data()); + + CryptoPP::SHA256 contentinfo_hash; + for (std::size_t i = 0; i < tmd_body.contentinfo.size(); i++) { + chunk_hash.Update(reinterpret_cast(&tmd_body.contentinfo[i]), sizeof(ContentInfo)); + } + chunk_hash.Final(tmd_body.contentinfo_hash.data()); + + // Write our TMD body, then write each of our ContentChunks + if (file.WriteBytes(&tmd_body, sizeof(TitleMetadata::Body)) != sizeof(TitleMetadata::Body)) + return ResultStatus::Error; + + for (u16 i = 0; i < tmd_body.content_count; i++) { + ContentChunk chunk = tmd_chunks[i]; + if (file.WriteBytes(&chunk, sizeof(ContentChunk)) != sizeof(ContentChunk)) + return ResultStatus::Error; + } + + return ResultStatus::Success; +} + u64 TitleMetadata::GetTitleID() const { return tmd_body.title_id; } @@ -105,6 +161,26 @@ std::array TitleMetadata::GetContentCTRByIndex(u16 index) const { return ctr; } +void TitleMetadata::SetTitleID(u64 title_id) { + tmd_body.title_id = title_id; +} + +void TitleMetadata::SetTitleType(u32 type) { + tmd_body.title_type = type; +} + +void TitleMetadata::SetTitleVersion(u16 version) { + tmd_body.title_version = version; +} + +void TitleMetadata::SetSystemVersion(u64 version) { + tmd_body.system_version = version; +} + +void TitleMetadata::AddContentChunk(const ContentChunk& chunk) { + tmd_chunks.push_back(chunk); +} + void TitleMetadata::Print() const { LOG_DEBUG(Service_FS, "{} chunks", static_cast(tmd_body.content_count)); diff --git a/src/core/ncch/title_metadata.h b/src/core/ncch/title_metadata.h index 0419804..b9d163b 100644 --- a/src/core/ncch/title_metadata.h +++ b/src/core/ncch/title_metadata.h @@ -110,6 +110,7 @@ public: #pragma pack(pop) ResultStatus Load(const std::vector file_data, std::size_t offset = 0); + ResultStatus Save(const std::string& file_path); u64 GetTitleID() const; u32 GetTitleType() const; @@ -124,6 +125,12 @@ public: u64 GetContentSizeByIndex(u16 index) const; std::array GetContentCTRByIndex(u16 index) const; + void SetTitleID(u64 title_id); + void SetTitleType(u32 type); + void SetTitleVersion(u16 version); + void SetSystemVersion(u64 version); + void AddContentChunk(const ContentChunk& chunk); + void Print() const; private: