using System; using System.IO; using System.Text; using SabreTools.Data.Models.N3DS; using SabreTools.IO.Extensions; using SabreTools.Numerics.Extensions; using static SabreTools.Data.Models.N3DS.Constants; #pragma warning disable IDE0017 // Simplify object initialization namespace SabreTools.Serialization.Readers { public class N3DS : BaseBinaryReader { /// public override Cart? Deserialize(Stream? data) { // If the data is invalid if (data is null || !data.CanRead) return null; try { // Cache the current offset long initialOffset = data.Position; // Create a new cart image to fill var cart = new Cart(); #region NCSD Header // Try to parse the header var header = ParseNCSDHeader(data); if (header.MagicNumber != NCSDMagicNumber) return null; // Set the cart image header cart.Header = header; #endregion #region Card Info Header // Set the card info header cart.CardInfoHeader = ParseCardInfoHeader(data); #endregion #region Development Card Info Header // Set the development card info header cart.DevelopmentCardInfoHeader = ParseDevelopmentCardInfoHeader(data); #endregion // Cache the media unit size for further use long mediaUnitSize = 0; if (header.PartitionFlags is not null) mediaUnitSize = (uint)(0x200 * Math.Pow(2, header.PartitionFlags[(int)NCSDFlags.MediaUnitSize])); #region Partitions // Create the tables cart.Partitions = new NCCHHeader[8]; cart.ExtendedHeaders = new NCCHExtendedHeader[8]; cart.ExeFSHeaders = new ExeFSHeader[8]; cart.RomFSHeaders = new RomFSHeader[8]; // Iterate and build the partitions for (int i = 0; i < 8; i++) { // Find the offset to the partition long partitionOffset = initialOffset + cart.Header.PartitionsTable[i].Offset; partitionOffset *= mediaUnitSize; if (partitionOffset <= initialOffset) continue; // Seek to the start of the partition data.SeekIfPossible(partitionOffset, SeekOrigin.Begin); // Handle the normal header var partition = ParseNCCHHeader(data); if (partition is null || partition.MagicID != NCCHMagicNumber) continue; // Set the normal header cart.Partitions[i] = partition; // Handle the extended header, if it exists if (partition.ExtendedHeaderSizeInBytes > 0) cart.ExtendedHeaders[i] = ParseNCCHExtendedHeader(data); // Handle the ExeFS, if it exists if (partition.ExeFSSizeInMediaUnits > 0) { long offset = partition.ExeFSOffsetInMediaUnits * mediaUnitSize; data.SeekIfPossible(partitionOffset + offset, SeekOrigin.Begin); var exeFsHeader = ParseExeFSHeader(data); if (exeFsHeader is null) return null; cart.ExeFSHeaders[i] = exeFsHeader; } // Handle the RomFS, if it exists if (partition.RomFSSizeInMediaUnits > 0) { long offset = partition.RomFSOffsetInMediaUnits * mediaUnitSize; data.SeekIfPossible(partitionOffset + offset, SeekOrigin.Begin); var romFsHeader = ParseRomFSHeader(data); if (romFsHeader.MagicString != RomFSMagicNumber) continue; if (romFsHeader.MagicNumber != RomFSSecondMagicNumber) continue; cart.RomFSHeaders[i] = romFsHeader; } } #endregion return cart; } catch { // Ignore the actual error return null; } } /// /// Parse a Stream into a AccessControlInfo /// /// Stream to parse /// Filled AccessControlInfo on success, null on error public static AccessControlInfo ParseAccessControlInfo(Stream data) { var obj = new AccessControlInfo(); obj.ARM11LocalSystemCapabilities = ParseARM11LocalSystemCapabilities(data); obj.ARM11KernelCapabilities = ParseARM11KernelCapabilities(data); obj.ARM9AccessControl = ParseARM9AccessControl(data); return obj; } /// /// Parse a Stream into a ARM9AccessControl /// /// Stream to parse /// Filled ARM9AccessControl on success, null on error public static ARM9AccessControl ParseARM9AccessControl(Stream data) { var obj = new ARM9AccessControl(); obj.Descriptors = new ARM9AccessControlDescriptors[15]; for (int i = 0; i < 15; i++) { obj.Descriptors[i] = (ARM9AccessControlDescriptors)data.ReadByteValue(); } obj.DescriptorVersion = data.ReadByteValue(); return obj; } /// /// Parse a Stream into a ARM11KernelCapabilities /// /// Stream to parse /// Filled ARM11KernelCapabilities on success, null on error public static ARM11KernelCapabilities ParseARM11KernelCapabilities(Stream data) { var obj = new ARM11KernelCapabilities(); obj.Descriptors = new uint[28]; for (int i = 0; i < 28; i++) { obj.Descriptors[i] = data.ReadUInt32LittleEndian(); } obj.Reserved = data.ReadBytes(0x10); return obj; } /// /// Parse a Stream into a ARM11LocalSystemCapabilities /// /// Stream to parse /// Filled ARM11LocalSystemCapabilities on success, null on error public static ARM11LocalSystemCapabilities ParseARM11LocalSystemCapabilities(Stream data) { var obj = new ARM11LocalSystemCapabilities(); obj.ProgramID = data.ReadUInt64LittleEndian(); obj.CoreVersion = data.ReadUInt32LittleEndian(); obj.Flag1 = (ARM11LSCFlag1)data.ReadByteValue(); obj.Flag2 = (ARM11LSCFlag2)data.ReadByteValue(); obj.Flag0 = (ARM11LSCFlag0)data.ReadByteValue(); obj.Priority = data.ReadByteValue(); obj.ResourceLimitDescriptors = new ushort[16]; for (int i = 0; i < 16; i++) { obj.ResourceLimitDescriptors[i] = data.ReadUInt16LittleEndian(); } obj.StorageInfo = ParseStorageInfo(data); obj.ServiceAccessControl = new ulong[32]; for (int i = 0; i < 32; i++) { obj.ServiceAccessControl[i] = data.ReadUInt64LittleEndian(); } obj.ExtendedServiceAccessControl = new ulong[2]; for (int i = 0; i < 2; i++) { obj.ExtendedServiceAccessControl[i] = data.ReadUInt64LittleEndian(); } obj.Reserved = data.ReadBytes(0x0F); obj.ResourceLimitCategory = (ResourceLimitCategory)data.ReadByteValue(); return obj; } /// /// Parse a Stream into a CardInfoHeader /// /// Stream to parse /// Filled CardInfoHeader on success, null on error public static CardInfoHeader ParseCardInfoHeader(Stream data) { var obj = new CardInfoHeader(); obj.WritableAddressMediaUnits = data.ReadUInt32LittleEndian(); obj.CardInfoBitmask = data.ReadUInt32LittleEndian(); obj.Reserved1 = data.ReadBytes(0xF8); obj.FilledSize = data.ReadUInt32LittleEndian(); obj.Reserved2 = data.ReadBytes(0x0C); obj.TitleVersion = data.ReadUInt16LittleEndian(); obj.CardRevision = data.ReadUInt16LittleEndian(); obj.Reserved3 = data.ReadBytes(0x0C); obj.CVerTitleID = data.ReadBytes(0x08); obj.CVerVersionNumber = data.ReadUInt16LittleEndian(); obj.Reserved4 = data.ReadBytes(0xCD6); obj.InitialData = ParseInitialData(data); return obj; } /// /// Parse a Stream into a CodeSetInfo /// /// Stream to parse /// Filled CodeSetInfo on success, null on error public static CodeSetInfo ParseCodeSetInfo(Stream data) { var obj = new CodeSetInfo(); obj.Address = data.ReadUInt32LittleEndian(); obj.PhysicalRegionSizeInPages = data.ReadUInt32LittleEndian(); obj.SizeInBytes = data.ReadUInt32LittleEndian(); return obj; } /// /// Parse a Stream into a DevelopmentCardInfoHeader /// /// Stream to parse /// Filled DevelopmentCardInfoHeader on success, null on error public static DevelopmentCardInfoHeader ParseDevelopmentCardInfoHeader(Stream data) { var obj = new DevelopmentCardInfoHeader(); obj.CardDeviceReserved1 = data.ReadBytes(0x200); obj.TitleKey = data.ReadBytes(0x10); obj.CardDeviceReserved2 = data.ReadBytes(0x1BF0); obj.TestData = ParseTestData(data); return obj; } /// /// Parse a Stream into a ExeFSFileHeader /// /// Stream to parse /// Filled ExeFSFileHeader on success, null on error public static ExeFSFileHeader ParseExeFSFileHeader(Stream data) { var obj = new ExeFSFileHeader(); byte[] fileName = data.ReadBytes(8); obj.FileName = Encoding.ASCII.GetString(fileName).TrimEnd('\0'); obj.FileOffset = data.ReadUInt32LittleEndian(); obj.FileSize = data.ReadUInt32LittleEndian(); return obj; } /// /// Parse a Stream into an ExeFSHeader /// /// Stream to parse /// Filled ExeFSHeader on success, null on error public static ExeFSHeader ParseExeFSHeader(Stream data) { var obj = new ExeFSHeader(); obj.FileHeaders = new ExeFSFileHeader[10]; for (int i = 0; i < 10; i++) { obj.FileHeaders[i] = ParseExeFSFileHeader(data); } obj.Reserved = data.ReadBytes(0x20); obj.FileHashes = new byte[10][]; for (int i = 0; i < 10; i++) { obj.FileHashes[i] = data.ReadBytes(0x20); } return obj; } /// /// Parse a Stream into initial data /// /// Stream to parse /// Filled initial data on success, null on error public static InitialData ParseInitialData(Stream data) { var obj = new InitialData(); obj.CardSeedKeyY = data.ReadBytes(0x10); obj.EncryptedCardSeed = data.ReadBytes(0x10); obj.CardSeedAESMAC = data.ReadBytes(0x10); obj.CardSeedNonce = data.ReadBytes(0x0C); obj.Reserved = data.ReadBytes(0xC4); obj.BackupHeader = ParseNCCHHeader(data, skipSignature: true); return obj; } /// /// Parse a Stream into a NCCHExtendedHeader /// /// Stream to parse /// Filled NCCHExtendedHeader on success, null on error public static NCCHExtendedHeader ParseNCCHExtendedHeader(Stream data) { var obj = new NCCHExtendedHeader(); obj.SCI = ParseSystemControlInfo(data); obj.ACI = ParseAccessControlInfo(data); obj.AccessDescSignature = data.ReadBytes(0x100); obj.NCCHHDRPublicKey = data.ReadBytes(0x100); obj.ACIForLimitations = ParseAccessControlInfo(data); return obj; } /// /// Parse a Stream into an NCCHHeader /// /// Stream to parse /// Indicates if the signature should be skipped /// Filled NCCHHeader on success, null on error public static NCCHHeader ParseNCCHHeader(Stream data, bool skipSignature = false) { var obj = new NCCHHeader(); if (!skipSignature) obj.RSA2048Signature = data.ReadBytes(0x100); byte[] magicId = data.ReadBytes(4); obj.MagicID = Encoding.ASCII.GetString(magicId).TrimEnd('\0'); obj.ContentSizeInMediaUnits = data.ReadUInt32LittleEndian(); obj.PartitionId = data.ReadUInt64LittleEndian(); obj.MakerCode = data.ReadUInt16LittleEndian(); obj.Version = data.ReadUInt16LittleEndian(); obj.VerificationHash = data.ReadUInt32LittleEndian(); obj.ProgramId = data.ReadBytes(8); obj.Reserved1 = data.ReadBytes(0x10); obj.LogoRegionHash = data.ReadBytes(0x20); byte[] productCode = data.ReadBytes(0x10); obj.ProductCode = Encoding.ASCII.GetString(productCode).TrimEnd('\0'); obj.ExtendedHeaderHash = data.ReadBytes(0x20); obj.ExtendedHeaderSizeInBytes = data.ReadUInt32LittleEndian(); obj.Reserved2 = data.ReadUInt32LittleEndian(); obj.Flags = ParseNCCHHeaderFlags(data); obj.PlainRegionOffsetInMediaUnits = data.ReadUInt32LittleEndian(); obj.PlainRegionSizeInMediaUnits = data.ReadUInt32LittleEndian(); obj.LogoRegionOffsetInMediaUnits = data.ReadUInt32LittleEndian(); obj.LogoRegionSizeInMediaUnits = data.ReadUInt32LittleEndian(); obj.ExeFSOffsetInMediaUnits = data.ReadUInt32LittleEndian(); obj.ExeFSSizeInMediaUnits = data.ReadUInt32LittleEndian(); obj.ExeFSHashRegionSizeInMediaUnits = data.ReadUInt32LittleEndian(); obj.Reserved3 = data.ReadUInt32LittleEndian(); obj.RomFSOffsetInMediaUnits = data.ReadUInt32LittleEndian(); obj.RomFSSizeInMediaUnits = data.ReadUInt32LittleEndian(); obj.RomFSHashRegionSizeInMediaUnits = data.ReadUInt32LittleEndian(); obj.Reserved4 = data.ReadUInt32LittleEndian(); obj.ExeFSSuperblockHash = data.ReadBytes(0x20); obj.RomFSSuperblockHash = data.ReadBytes(0x20); return obj; } /// /// Parse a Stream into an NCCHHeaderFlags /// /// Stream to parse /// Filled NCCHHeaderFlags on success, null on error public static NCCHHeaderFlags ParseNCCHHeaderFlags(Stream data) { var obj = new NCCHHeaderFlags(); obj.Reserved0 = data.ReadByteValue(); obj.Reserved1 = data.ReadByteValue(); obj.Reserved2 = data.ReadByteValue(); obj.CryptoMethod = (CryptoMethod)data.ReadByteValue(); obj.ContentPlatform = (ContentPlatform)data.ReadByteValue(); obj.MediaPlatformIndex = (ContentType)data.ReadByteValue(); obj.ContentUnitSize = data.ReadByteValue(); obj.BitMasks = (BitMasks)data.ReadByteValue(); return obj; } /// /// Parse a Stream into an NCSDHeader /// /// Stream to parse /// Filled NCSDHeader on success, null on error public static NCSDHeader ParseNCSDHeader(Stream data) { var obj = new NCSDHeader(); obj.RSA2048Signature = data.ReadBytes(0x100); byte[] magicNumber = data.ReadBytes(4); obj.MagicNumber = Encoding.ASCII.GetString(magicNumber).TrimEnd('\0'); obj.ImageSizeInMediaUnits = data.ReadUInt32LittleEndian(); obj.MediaId = data.ReadBytes(8); obj.PartitionsFSType = (FilesystemType)data.ReadUInt64LittleEndian(); obj.PartitionsCryptType = data.ReadBytes(8); obj.PartitionsTable = new PartitionTableEntry[8]; for (int i = 0; i < 8; i++) { obj.PartitionsTable[i] = ParsePartitionTableEntry(data); } if (obj.PartitionsFSType == FilesystemType.Normal || obj.PartitionsFSType == FilesystemType.None) { obj.ExheaderHash = data.ReadBytes(0x20); obj.AdditionalHeaderSize = data.ReadUInt32LittleEndian(); obj.SectorZeroOffset = data.ReadUInt32LittleEndian(); obj.PartitionFlags = data.ReadBytes(8); obj.PartitionIdTable = new ulong[8]; for (int i = 0; i < 8; i++) { obj.PartitionIdTable[i] = data.ReadUInt64LittleEndian(); } obj.Reserved1 = data.ReadBytes(0x20); obj.Reserved2 = data.ReadBytes(0x0E); obj.FirmUpdateByte1 = data.ReadByteValue(); obj.FirmUpdateByte2 = data.ReadByteValue(); } else if (obj.PartitionsFSType == FilesystemType.FIRM) { obj.Unknown = data.ReadBytes(0x5E); obj.EncryptedMBR = data.ReadBytes(0x42); } return obj; } /// /// Parse a Stream into an PartitionTableEntry /// /// Stream to parse /// Filled PartitionTableEntry on success, null on error public static PartitionTableEntry ParsePartitionTableEntry(Stream data) { var obj = new PartitionTableEntry(); obj.Offset = data.ReadUInt32LittleEndian(); obj.Length = data.ReadUInt32LittleEndian(); return obj; } /// /// Parse a Stream into an RomFSHeader /// /// Stream to parse /// Filled RomFSHeader on success, null on error public static RomFSHeader ParseRomFSHeader(Stream data) { var obj = new RomFSHeader(); byte[] magicString = data.ReadBytes(4); obj.MagicString = Encoding.ASCII.GetString(magicString); obj.MagicNumber = data.ReadUInt32LittleEndian(); obj.MasterHashSize = data.ReadUInt32LittleEndian(); obj.Level1LogicalOffset = data.ReadUInt64LittleEndian(); obj.Level1HashdataSize = data.ReadUInt64LittleEndian(); obj.Level1BlockSizeLog2 = data.ReadUInt32LittleEndian(); obj.Reserved1 = data.ReadUInt32LittleEndian(); obj.Level2LogicalOffset = data.ReadUInt64LittleEndian(); obj.Level2HashdataSize = data.ReadUInt64LittleEndian(); obj.Level2BlockSizeLog2 = data.ReadUInt32LittleEndian(); obj.Reserved2 = data.ReadUInt32LittleEndian(); obj.Level3LogicalOffset = data.ReadUInt64LittleEndian(); obj.Level3HashdataSize = data.ReadUInt64LittleEndian(); obj.Level3BlockSizeLog2 = data.ReadUInt32LittleEndian(); obj.Reserved3 = data.ReadUInt32LittleEndian(); obj.Reserved4 = data.ReadUInt32LittleEndian(); obj.OptionalInfoSize = data.ReadUInt32LittleEndian(); return obj; } /// /// Parse a Stream into an StorageInfo /// /// Stream to parse /// Filled StorageInfo on success, null on error public static StorageInfo ParseStorageInfo(Stream data) { var obj = new StorageInfo(); obj.ExtdataID = data.ReadUInt64LittleEndian(); obj.SystemSavedataIDs = data.ReadBytes(8); obj.StorageAccessibleUniqueIDs = data.ReadBytes(8); obj.FileSystemAccessInfo = data.ReadBytes(7); obj.OtherAttributes = (StorageInfoOtherAttributes)data.ReadByteValue(); return obj; } /// /// Parse a Stream into an SystemControlInfo /// /// Stream to parse /// Filled SystemControlInfo on success, null on error public static SystemControlInfo ParseSystemControlInfo(Stream data) { var obj = new SystemControlInfo(); byte[] applicationTitle = data.ReadBytes(8); obj.ApplicationTitle = Encoding.ASCII.GetString(applicationTitle).TrimEnd('\0'); obj.Reserved1 = data.ReadBytes(5); obj.Flag = data.ReadByteValue(); obj.RemasterVersion = data.ReadUInt16LittleEndian(); obj.TextCodeSetInfo = ParseCodeSetInfo(data); obj.StackSize = data.ReadUInt32LittleEndian(); obj.ReadOnlyCodeSetInfo = ParseCodeSetInfo(data); obj.Reserved2 = data.ReadUInt32LittleEndian(); obj.DataCodeSetInfo = ParseCodeSetInfo(data); obj.BSSSize = data.ReadUInt32LittleEndian(); obj.DependencyModuleList = new ulong[48]; for (int i = 0; i < 48; i++) { obj.DependencyModuleList[i] = data.ReadUInt64LittleEndian(); } obj.SystemInfo = ParseSystemInfo(data); return obj; } /// /// Parse a Stream into an SystemInfo /// /// Stream to parse /// Filled SystemInfo on success, null on error public static SystemInfo ParseSystemInfo(Stream data) { var obj = new SystemInfo(); obj.SaveDataSize = data.ReadUInt64LittleEndian(); obj.JumpID = data.ReadUInt64LittleEndian(); obj.Reserved = data.ReadBytes(0x30); return obj; } /// /// Parse a Stream into an TestData /// /// Stream to parse /// Filled TestData on success, null on error public static TestData ParseTestData(Stream data) { var obj = new TestData(); obj.Signature = data.ReadBytes(8); obj.AscendingByteSequence = data.ReadBytes(0x1F8); obj.DescendingByteSequence = data.ReadBytes(0x200); obj.Filled00 = data.ReadBytes(0x200); obj.FilledFF = data.ReadBytes(0x200); obj.Filled0F = data.ReadBytes(0x200); obj.FilledF0 = data.ReadBytes(0x200); obj.Filled55 = data.ReadBytes(0x200); obj.FilledAA = data.ReadBytes(0x1FF); obj.FinalByte = data.ReadByteValue(); return obj; } } }