Add extended header parsing for N3DS

This commit is contained in:
Matt Nadareski
2023-01-07 22:12:58 -08:00
parent 1fec5c15d4
commit b99b2a53cf
11 changed files with 256 additions and 17 deletions

View File

@@ -1,3 +1,4 @@
using System;
using System.IO;
using System.Text;
using BurnOutSharp.Models.N3DS;
@@ -105,7 +106,33 @@ namespace BurnOutSharp.Builders
#endregion
// TODO: Parse partition extended headers (decrypted only)
#region Extended Headers
// Create the extended header table
cart.NCCHExtendedHeaders = new NCCHExtendedHeader[8];
// Iterate and build the extended headers
for (int i = 0; i < 8; i++)
{
// If we have an encrypted or invalid partition
if (cart.Partitions[i].MagicID != NCCHMagicNumber)
continue;
// Get the extended header offset
long mediaUnitSize = (uint)(0x200 * Math.Pow(2, header.PartitionFlags[(int)NCSDFlags.MediaUnitSize]));
long offset = (cart.Header.PartitionsTable[i].Offset * mediaUnitSize) + 0x200;
if (offset < 0 || offset >= data.Length)
continue;
// Seek to the extended header
data.Seek(offset, SeekOrigin.Begin);
// Parse the extended header
cart.NCCHExtendedHeaders[i] = ParseNCCHExtendedHeader(data);
}
#endregion
// TODO: Parse ExeFS file entries (decrypted only)
// TODO: Parse ExeFS filename table (decrypted only)
@@ -349,6 +376,209 @@ namespace BurnOutSharp.Builders
return testData;
}
/// <summary>
/// Parse a Stream into an NCCH extended header
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled NCCH extended header on success, null on error</returns>
private static NCCHExtendedHeader ParseNCCHExtendedHeader(Stream data)
{
// TODO: Use marshalling here instead of building
NCCHExtendedHeader extendedHeader = new NCCHExtendedHeader();
extendedHeader.SCI = ParseSystemControlInfo(data);
if (extendedHeader.SCI == null)
return null;
extendedHeader.ACI = ParseAccessControlInfo(data);
if (extendedHeader.ACI == null)
return null;
extendedHeader.AccessDescSignature = data.ReadBytes(0x100);
extendedHeader.NCCHHDRPublicKey = data.ReadBytes(0x100);
extendedHeader.ACIForLimitations = ParseAccessControlInfo(data);
if (extendedHeader.ACI == null)
return null;
return extendedHeader;
}
/// <summary>
/// Parse a Stream into a system control info
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled system control info on success, null on error</returns>
private static SystemControlInfo ParseSystemControlInfo(Stream data)
{
// TODO: Use marshalling here instead of building
SystemControlInfo systemControlInfo = new SystemControlInfo();
byte[] applicationTitle = data.ReadBytes(8);
systemControlInfo.ApplicationTitle = Encoding.ASCII.GetString(applicationTitle).TrimEnd('\0');
systemControlInfo.Reserved1 = data.ReadBytes(5);
systemControlInfo.Flag = data.ReadByteValue();
systemControlInfo.RemasterVersion = data.ReadUInt16();
systemControlInfo.TextCodeSetInfo = ParseCodeSetInfo(data);
systemControlInfo.StackSize = data.ReadUInt32();
systemControlInfo.ReadOnlyCodeSetInfo = ParseCodeSetInfo(data);
systemControlInfo.Reserved2 = data.ReadBytes(4);
systemControlInfo.DataCodeSetInfo = ParseCodeSetInfo(data);
systemControlInfo.BSSSize = data.ReadUInt32();
systemControlInfo.DependencyModuleList = new ulong[48];
for (int i = 0; i < 48; i++)
{
systemControlInfo.DependencyModuleList[i] = data.ReadUInt64();
}
systemControlInfo.SystemInfo = ParseSystemInfo(data);
return systemControlInfo;
}
/// <summary>
/// Parse a Stream into a code set info
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled code set info on success, null on error</returns>
private static CodeSetInfo ParseCodeSetInfo(Stream data)
{
// TODO: Use marshalling here instead of building
CodeSetInfo codeSetInfo = new CodeSetInfo();
codeSetInfo.Address = data.ReadUInt32();
codeSetInfo.PhysicalRegionSizeInPages = data.ReadUInt32();
codeSetInfo.SizeInBytes = data.ReadUInt32();
return codeSetInfo;
}
/// <summary>
/// Parse a Stream into a system info
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled system info on success, null on error</returns>
private static SystemInfo ParseSystemInfo(Stream data)
{
// TODO: Use marshalling here instead of building
SystemInfo systemInfo = new SystemInfo();
systemInfo.SaveDataSize = data.ReadUInt64();
systemInfo.JumpID = data.ReadUInt64();
systemInfo.Reserved = data.ReadBytes(0x30);
return systemInfo;
}
/// <summary>
/// Parse a Stream into an access control info
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled access control info on success, null on error</returns>
private static AccessControlInfo ParseAccessControlInfo(Stream data)
{
// TODO: Use marshalling here instead of building
AccessControlInfo accessControlInfo = new AccessControlInfo();
accessControlInfo.ARM11LocalSystemCapabilities = ParseARM11LocalSystemCapabilities(data);
accessControlInfo.ARM11KernelCapabilities = ParseARM11KernelCapabilities(data);
accessControlInfo.ARM9AccessControl = ParseARM9AccessControl(data);
return accessControlInfo;
}
/// <summary>
/// Parse a Stream into an ARM11 local system capabilities
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled ARM11 local system capabilities on success, null on error</returns>
private static ARM11LocalSystemCapabilities ParseARM11LocalSystemCapabilities(Stream data)
{
// TODO: Use marshalling here instead of building
ARM11LocalSystemCapabilities arm11LocalSystemCapabilities = new ARM11LocalSystemCapabilities();
arm11LocalSystemCapabilities.ProgramID = data.ReadUInt64();
arm11LocalSystemCapabilities.CoreVersion = data.ReadUInt32();
arm11LocalSystemCapabilities.Flag1 = (ARM11LSCFlag1)data.ReadByteValue();
arm11LocalSystemCapabilities.Flag2 = (ARM11LSCFlag2)data.ReadByteValue();
arm11LocalSystemCapabilities.Flag0 = (ARM11LSCFlag0)data.ReadByteValue();
arm11LocalSystemCapabilities.Priority = data.ReadByteValue();
arm11LocalSystemCapabilities.ResourceLimitDescriptors = new ushort[16];
for (int i = 0; i < 16; i++)
{
arm11LocalSystemCapabilities.ResourceLimitDescriptors[i] = data.ReadUInt16();
}
arm11LocalSystemCapabilities.StorageInfo = ParseStorageInfo(data);
arm11LocalSystemCapabilities.ServiceAccessControl = new ulong[32];
for (int i = 0; i < 32; i++)
{
arm11LocalSystemCapabilities.ServiceAccessControl[i] = data.ReadUInt64();
}
arm11LocalSystemCapabilities.ExtendedServiceAccessControl = new ulong[2];
for (int i = 0; i < 2; i++)
{
arm11LocalSystemCapabilities.ExtendedServiceAccessControl[i] = data.ReadUInt64();
}
arm11LocalSystemCapabilities.Reserved = data.ReadBytes(0x0F);
arm11LocalSystemCapabilities.ResourceLimitCategory = (ResourceLimitCategory)data.ReadByteValue();
return arm11LocalSystemCapabilities;
}
/// <summary>
/// Parse a Stream into a storage info
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled storage info on success, null on error</returns>
private static StorageInfo ParseStorageInfo(Stream data)
{
// TODO: Use marshalling here instead of building
StorageInfo storageInfo = new StorageInfo();
storageInfo.ExtdataID = data.ReadUInt64();
storageInfo.SystemSavedataIDs = data.ReadBytes(8);
storageInfo.StorageAccessibleUniqueIDs = data.ReadBytes(8);
storageInfo.FileSystemAccessInfo = data.ReadBytes(7);
storageInfo.OtherAttributes = (StorageInfoOtherAttributes)data.ReadByteValue();
return storageInfo;
}
/// <summary>
/// Parse a Stream into an ARM11 kernel capabilities
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled ARM11 kernel capabilities on success, null on error</returns>
private static ARM11KernelCapabilities ParseARM11KernelCapabilities(Stream data)
{
// TODO: Use marshalling here instead of building
ARM11KernelCapabilities arm11KernelCapabilities = new ARM11KernelCapabilities();
arm11KernelCapabilities.Descriptors = new uint[28];
for (int i = 0; i < 28; i++)
{
arm11KernelCapabilities.Descriptors[i] = data.ReadUInt32();
}
arm11KernelCapabilities.Reserved = data.ReadBytes(0x10);
return arm11KernelCapabilities;
}
/// <summary>
/// Parse a Stream into an ARM11 access control
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled ARM11 access control on success, null on error</returns>
private static ARM9AccessControl ParseARM9AccessControl(Stream data)
{
// TODO: Use marshalling here instead of building
ARM9AccessControl arm9AccessControl = new ARM9AccessControl();
arm9AccessControl.Descriptors = data.ReadBytes(15);
arm9AccessControl.DescriptorVersion = data.ReadByteValue();
return arm9AccessControl;
}
#endregion
}
}

View File

@@ -33,7 +33,8 @@
/// 12 Special memory
/// 13 Process has access to CPU core 2 (New3DS only)
/// </summary>
public byte[][] Descriptors;
/// TODO: Make enum for flag values
public uint[] Descriptors;
/// <summary>
/// Reserved

View File

@@ -6,7 +6,7 @@
/// <summary>
/// Program ID
/// </summary>
public byte[] ProgramID;
public ulong ProgramID;
/// <summary>
/// Core version (The Title ID low of the required FIRM)
@@ -36,7 +36,7 @@
/// <summary>
/// Resource limit descriptors. The first byte here controls the maximum allowed CpuTime.
/// </summary>
public byte[][] ResourceLimitDescriptors;
public ushort[] ResourceLimitDescriptors;
/// <summary>
/// Storage info
@@ -46,12 +46,12 @@
/// <summary>
/// Service access control
/// </summary>
public byte[][] ServiceAccessControl;
public ulong[] ServiceAccessControl;
/// <summary>
/// Extended service access control, support for this was implemented with 9.3.0-X.
/// </summary>
public byte[][] ExtendedServiceAccessControl;
public ulong[] ExtendedServiceAccessControl;
/// <summary>
/// Reserved

View File

@@ -6,7 +6,7 @@
/// <summary>
/// Descriptors
/// </summary>
public ARM9AccessControlDescriptors[] Descriptors;
public byte[] Descriptors;
/// <summary>
/// ARM9 Descriptor Version. Originally this value had to be ≥ 2.

View File

@@ -24,5 +24,10 @@ namespace BurnOutSharp.Models.N3DS
/// NCCH partitions
/// </summary>
public NCCHHeader[] Partitions { get; set; }
/// <summary>
/// NCCH extended headers
/// </summary>
public NCCHExtendedHeader[] NCCHExtendedHeaders { get; set; }
}
}

View File

@@ -6,7 +6,7 @@
/// <summary>
/// Address
/// </summary>
public byte[] Address;
public uint Address;
/// <summary>
/// Physical region size (in page-multiples)

View File

@@ -2,6 +2,7 @@
namespace BurnOutSharp.Models.N3DS
{
// TODO: Fix this, I don't think it's correct
[Flags]
public enum ARM9AccessControlDescriptors : byte
{
@@ -14,7 +15,7 @@ namespace BurnOutSharp.Models.N3DS
CreateSeed = 0x20,
UseCardSPI = 0x40,
SDApplication = 0x80,
MoundSdmcWriteAccess = 0xF0,
MountSdmcWriteAccess = 0xF0,
}
[Flags]

View File

@@ -6,7 +6,7 @@
/// - A signed copy of NCCH HDR public key, and exheader ACI. This version of the ACI is used as limitation to the actual ACI.
/// </summary>
/// <see href="https://www.3dbrew.org/wiki/NCCH/Extended_Header"/>
public sealed class CXIExtendedHeader
public sealed class NCCHExtendedHeader
{
/// <summary>
/// SCI

View File

@@ -9,7 +9,7 @@
/// <summary>
/// Extdata ID
/// </summary>
public byte[] ExtdataID;
public ulong ExtdataID;
/// <summary>
/// System savedata IDs
@@ -24,7 +24,9 @@
/// <summary>
/// Filesystem access info
/// </summary>
public byte[] FilesystemAccessInfo;
/// TODO: Create enum for the flag values
/// TODO: Combine with "other attributes"
public byte[] FileSystemAccessInfo;
/// <summary>
/// Other attributes

View File

@@ -6,7 +6,7 @@
/// <summary>
/// Application title (default is "CtrApp")
/// </summary>
public char[] ApplicationTitle;
public string ApplicationTitle;
/// <summary>
/// Reserved
@@ -21,12 +21,12 @@
/// <summary>
/// Remaster version
/// </summary>
public byte[] RemasterVersion;
public ushort RemasterVersion;
/// <summary>
/// Text code set info
/// </summary>
public CodeSetInfo TextCodesetInfo;
public CodeSetInfo TextCodeSetInfo;
/// <summary>
/// Stack size
@@ -56,7 +56,7 @@
/// <summary>
/// Dependency module (program ID) list
/// </summary>
public byte[][] DependencyModuleList;
public ulong[] DependencyModuleList;
/// <summary>
/// SystemInfo

View File

@@ -11,7 +11,7 @@
/// <summary>
/// Jump ID
/// </summary>
public byte[] JumpID;
public ulong JumpID;
/// <summary>
/// Reserved