From 53dd1e9aa5a51d584c4dd457ca6e03c2b3ce6e54 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 9 Jan 2023 11:33:54 -0800 Subject: [PATCH] Add alignment, partition parsing, fix BE --- BurnOutSharp.Builders/N3DS.cs | 92 ++++++++++++++++++----- BurnOutSharp.Models/N3DS/CIA.cs | 5 ++ BurnOutSharp.Models/N3DS/TitleMetadata.cs | 2 +- BurnOutSharp.Wrappers/CIA.cs | 79 ++++++++++++++++++- 4 files changed, 156 insertions(+), 22 deletions(-) diff --git a/BurnOutSharp.Builders/N3DS.cs b/BurnOutSharp.Builders/N3DS.cs index 526f4f97..e12d625a 100644 --- a/BurnOutSharp.Builders/N3DS.cs +++ b/BurnOutSharp.Builders/N3DS.cs @@ -244,6 +244,12 @@ namespace BurnOutSharp.Builders #endregion + // Align to 64-byte boundary, if needed + while (data.Position < data.Length - 1 && data.Position % 64 != 0) + { + _ = data.ReadByteValue(); + } + #region Certificate Chain // Create the certificate chain @@ -261,6 +267,12 @@ namespace BurnOutSharp.Builders #endregion + // Align to 64-byte boundary, if needed + while (data.Position < data.Length - 1 && data.Position % 64 != 0) + { + _ = data.ReadByteValue(); + } + #region Ticket // Try to parse the ticket @@ -273,6 +285,12 @@ namespace BurnOutSharp.Builders #endregion + // Align to 64-byte boundary, if needed + while (data.Position < data.Length - 1 && data.Position % 64 != 0) + { + _ = data.ReadByteValue(); + } + #region Title Metadata // Try to parse the title metadata @@ -285,14 +303,31 @@ namespace BurnOutSharp.Builders #endregion + // Align to 64-byte boundary, if needed + while (data.Position < data.Length - 1 && data.Position % 64 != 0) + { + _ = data.ReadByteValue(); + } + #region Content File Data - // If we have content - if (header.ContentSize > 0) - cia.ContentFileData = data.ReadBytes((int)header.ContentSize); + // Create the partition table + cia.Partitions = new NCCHHeader[8]; + + // Iterate and build the partitions + for (int i = 0; i < 8; i++) + { + cia.Partitions[i] = ParseNCCHHeader(data); + } #endregion + // Align to 64-byte boundary, if needed + while (data.Position < data.Length - 1 && data.Position % 64 != 0) + { + _ = data.ReadByteValue(); + } + #region Meta Data // If we have a meta data @@ -933,8 +968,9 @@ namespace BurnOutSharp.Builders /// Parse a Stream into a ticket /// /// Stream to parse + /// Indicates if the ticket is from CDN /// Filled ticket on success, null on error - private static Ticket ParseTicket(Stream data) + private static Ticket ParseTicket(Stream data, bool fromCdn = false) { // TODO: Use marshalling here instead of building Ticket ticket = new Ticket(); @@ -1011,14 +1047,19 @@ namespace BurnOutSharp.Builders data.Seek(-8, SeekOrigin.Current); ticket.ContentIndex = data.ReadBytes((int)ticket.ContentIndexSize); - ticket.CertificateChain = new Certificate[2]; - for (int i = 0; i < 2; i++) - { - var certificate = ParseCertificate(data); - if (certificate == null) - return null; - ticket.CertificateChain[i] = certificate; + // Certificates only exist in standalone CETK files + if (fromCdn) + { + ticket.CertificateChain = new Certificate[2]; + for (int i = 0; i < 2; i++) + { + var certificate = ParseCertificate(data); + if (certificate == null) + return null; + + ticket.CertificateChain[i] = certificate; + } } return ticket; @@ -1028,8 +1069,9 @@ namespace BurnOutSharp.Builders /// Parse a Stream into a title metadata /// /// Stream to parse + /// Indicates if the ticket is from CDN /// Filled title metadata on success, null on error - private static TitleMetadata ParseTitleMetadata(Stream data) + private static TitleMetadata ParseTitleMetadata(Stream data, bool fromCdn = false) { // TODO: Use marshalling here instead of building TitleMetadata titleMetadata = new TitleMetadata(); @@ -1084,7 +1126,12 @@ namespace BurnOutSharp.Builders titleMetadata.Reserved3 = data.ReadBytes(0x31); titleMetadata.AccessRights = data.ReadUInt32(); titleMetadata.TitleVersion = data.ReadUInt16(); - titleMetadata.ContentCount = data.ReadUInt16(); + + // Read the content count (big-endian) + byte[] contentCount = data.ReadBytes(2); + Array.Reverse(contentCount); + titleMetadata.ContentCount = BitConverter.ToUInt16(contentCount, 0); + titleMetadata.BootContent = data.ReadUInt16(); titleMetadata.Padding2 = data.ReadBytes(2); titleMetadata.SHA256HashContentInfoRecords = data.ReadBytes(0x20); @@ -1098,14 +1145,19 @@ namespace BurnOutSharp.Builders { titleMetadata.ContentChunkRecords[i] = ParseContentChunkRecord(data); } - titleMetadata.CertificateChain = new Certificate[2]; - for (int i = 0; i < 2; i++) - { - var certificate = ParseCertificate(data); - if (certificate == null) - return null; - titleMetadata.CertificateChain[i] = certificate; + // Certificates only exist in standalone TMD files + if (fromCdn) + { + titleMetadata.CertificateChain = new Certificate[2]; + for (int i = 0; i < 2; i++) + { + var certificate = ParseCertificate(data); + if (certificate == null) + return null; + + titleMetadata.CertificateChain[i] = certificate; + } } return titleMetadata; diff --git a/BurnOutSharp.Models/N3DS/CIA.cs b/BurnOutSharp.Models/N3DS/CIA.cs index 484cb746..b1983e68 100644 --- a/BurnOutSharp.Models/N3DS/CIA.cs +++ b/BurnOutSharp.Models/N3DS/CIA.cs @@ -37,6 +37,11 @@ namespace BurnOutSharp.Models.N3DS /// public TitleMetadata TMDFileData { get; set; } + /// + /// Content file data + /// + public NCCHHeader[] Partitions { get; set; } + /// /// Content file data /// diff --git a/BurnOutSharp.Models/N3DS/TitleMetadata.cs b/BurnOutSharp.Models/N3DS/TitleMetadata.cs index cb56cc0c..31d8956a 100644 --- a/BurnOutSharp.Models/N3DS/TitleMetadata.cs +++ b/BurnOutSharp.Models/N3DS/TitleMetadata.cs @@ -114,7 +114,7 @@ namespace BurnOutSharp.Models.N3DS public ushort TitleVersion; /// - /// Content Count + /// Content Count (big-endian) /// public ushort ContentCount; diff --git a/BurnOutSharp.Wrappers/CIA.cs b/BurnOutSharp.Wrappers/CIA.cs index c711fe18..b278769f 100644 --- a/BurnOutSharp.Wrappers/CIA.cs +++ b/BurnOutSharp.Wrappers/CIA.cs @@ -224,6 +224,13 @@ namespace BurnOutSharp.Wrappers #endregion + #region Partitions + + /// + public Models.N3DS.NCCHHeader[] Partitions => _cia.Partitions; + + #endregion + #region Meta Data /// @@ -321,7 +328,7 @@ namespace BurnOutSharp.Wrappers PrintCertificateChain(); PrintTicket(); PrintTitleMetadata(); - // TODO: Print the content file data + PrintPartitions(); PrintMetaData(); } @@ -602,6 +609,76 @@ namespace BurnOutSharp.Wrappers Console.WriteLine(); } + /// + /// Print NCCH partition header information + /// + private void PrintPartitions() + { + Console.WriteLine(" NCCH Partition Header Information:"); + Console.WriteLine(" -------------------------"); + if (Partitions == null || Partitions.Length == 0) + { + Console.WriteLine(" No NCCH partition headers"); + } + else + { + for (int i = 0; i < Partitions.Length; i++) + { + var partitionHeader = Partitions[i]; + Console.WriteLine($" NCCH Partition Header {i}"); + if (partitionHeader.MagicID == string.Empty) + { + Console.WriteLine($" Empty partition, no data can be parsed"); + } + else if (partitionHeader.MagicID != Models.N3DS.Constants.NCCHMagicNumber) + { + Console.WriteLine($" Unrecognized partition data, no data can be parsed"); + } + else + { + Console.WriteLine($" RSA-2048 SHA-256 signature: {BitConverter.ToString(partitionHeader.RSA2048Signature).Replace('-', ' ')}"); + Console.WriteLine($" Magic ID: {partitionHeader.MagicID}"); + Console.WriteLine($" Content size in media units: {partitionHeader.ContentSizeInMediaUnits}"); + Console.WriteLine($" Partition ID: {partitionHeader.PartitionId}"); + Console.WriteLine($" Maker code: {partitionHeader.MakerCode}"); + Console.WriteLine($" Version: {partitionHeader.Version}"); + Console.WriteLine($" Verification hash: {partitionHeader.VerificationHash}"); + Console.WriteLine($" Program ID: {BitConverter.ToString(partitionHeader.ProgramId).Replace('-', ' ')}"); + Console.WriteLine($" Reserved 1: {BitConverter.ToString(partitionHeader.Reserved1).Replace('-', ' ')}"); + Console.WriteLine($" Logo region SHA-256 hash: {BitConverter.ToString(partitionHeader.LogoRegionHash).Replace('-', ' ')}"); + Console.WriteLine($" Product code: {partitionHeader.ProductCode}"); + Console.WriteLine($" Extended header SHA-256 hash: {BitConverter.ToString(partitionHeader.ExtendedHeaderHash).Replace('-', ' ')}"); + Console.WriteLine($" Extended header size in bytes: {partitionHeader.ExtendedHeaderSizeInBytes}"); + Console.WriteLine($" Reserved 2: {BitConverter.ToString(partitionHeader.Reserved2).Replace('-', ' ')}"); + Console.WriteLine(" Flags:"); + Console.WriteLine($" Reserved 0: {partitionHeader.Flags.Reserved0}"); + Console.WriteLine($" Reserved 1: {partitionHeader.Flags.Reserved1}"); + Console.WriteLine($" Reserved 2: {partitionHeader.Flags.Reserved2}"); + Console.WriteLine($" Crypto method: {partitionHeader.Flags.CryptoMethod}"); + Console.WriteLine($" Content platform: {partitionHeader.Flags.ContentPlatform}"); + Console.WriteLine($" Content type: {partitionHeader.Flags.MediaPlatformIndex}"); + Console.WriteLine($" Content unit size: {partitionHeader.Flags.ContentUnitSize}"); + Console.WriteLine($" Bitmasks: {partitionHeader.Flags.BitMasks}"); + Console.WriteLine($" Plain region offset, in media units: {partitionHeader.PlainRegionOffsetInMediaUnits}"); + Console.WriteLine($" Plain region size, in media units: {partitionHeader.PlainRegionSizeInMediaUnits}"); + Console.WriteLine($" Logo region offset, in media units: {partitionHeader.LogoRegionOffsetInMediaUnits}"); + Console.WriteLine($" Logo region size, in media units: {partitionHeader.LogoRegionSizeInMediaUnits}"); + Console.WriteLine($" ExeFS offset, in media units: {partitionHeader.ExeFSOffsetInMediaUnits}"); + Console.WriteLine($" ExeFS size, in media units: {partitionHeader.ExeFSSizeInMediaUnits}"); + Console.WriteLine($" ExeFS hash region size, in media units: {partitionHeader.ExeFSHashRegionSizeInMediaUnits}"); + Console.WriteLine($" Reserved 3: {BitConverter.ToString(partitionHeader.Reserved3).Replace('-', ' ')}"); + Console.WriteLine($" RomFS offset, in media units: {partitionHeader.RomFSOffsetInMediaUnits}"); + Console.WriteLine($" RomFS size, in media units: {partitionHeader.RomFSSizeInMediaUnits}"); + Console.WriteLine($" RomFS hash region size, in media units: {partitionHeader.RomFSHashRegionSizeInMediaUnits}"); + Console.WriteLine($" Reserved 4: {BitConverter.ToString(partitionHeader.Reserved4).Replace('-', ' ')}"); + Console.WriteLine($" ExeFS superblock SHA-256 hash: {BitConverter.ToString(partitionHeader.ExeFSSuperblockHash).Replace('-', ' ')}"); + Console.WriteLine($" RomFS superblock SHA-256 hash: {BitConverter.ToString(partitionHeader.RomFSSuperblockHash).Replace('-', ' ')}"); + } + } + } + Console.WriteLine(); + } + /// /// Print metadata information ///