Compare commits

...

8 Commits
1.7.2 ... 1.7.4

Author SHA1 Message Date
Matt Nadareski
695309bc32 Bump version 2024-11-14 13:32:19 -05:00
Matt Nadareski
97b2f68ec7 Use offsets instead of guessing... 2024-11-14 13:27:02 -05:00
Matt Nadareski
593044dbf3 Fix code binary check in N3DS 2024-11-14 12:49:16 -05:00
Matt Nadareski
1fcf44fb8d Bump version 2024-11-14 11:32:17 -05:00
Matt Nadareski
a2a472baf9 Fix top level printing issue 2024-11-14 11:24:08 -05:00
Matt Nadareski
b5b4a50d94 Fix deserialization of NCCH extended header
The actual fix to this is somewhere in the conversion code where an array of Enum values somehow just... fails? I'm not totally sure how that's happening but this is the easiest way around it until that auto stuff can be fixed.
2024-11-14 11:20:46 -05:00
Matt Nadareski
f1b5464052 Extend N3DS wrapper further 2024-11-14 03:17:11 -05:00
Matt Nadareski
2c0224db22 Fix byte order for N3DS IV 2024-11-14 00:12:35 -05:00
5 changed files with 256 additions and 166 deletions

View File

@@ -9,7 +9,7 @@
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.7.0</Version>
<Version>1.7.4</Version>
</PropertyGroup>
<!-- Support All Frameworks -->

View File

@@ -78,8 +78,14 @@ namespace SabreTools.Serialization.Deserializers
// Iterate and build the partitions
for (int i = 0; i < 8; i++)
{
// Cache the offset of the partition
initialOffset = data.Position;
// Find the offset to the partition
long partitionOffset = cart.Header.PartitionsTable?[i]?.Offset ?? 0;
partitionOffset *= mediaUnitSize;
if (partitionOffset == 0)
continue;
// Seek to the start of the partition
data.Seek(partitionOffset, SeekOrigin.Begin);
// Handle the normal header
var partition = ParseNCCHHeader(data);
@@ -101,7 +107,7 @@ namespace SabreTools.Serialization.Deserializers
if (partition.ExeFSSizeInMediaUnits > 0)
{
long offset = partition.ExeFSOffsetInMediaUnits * mediaUnitSize;
data.Seek(initialOffset + offset, SeekOrigin.Begin);
data.Seek(partitionOffset + offset, SeekOrigin.Begin);
var exeFsHeader = ParseExeFSHeader(data);
if (exeFsHeader == null)
@@ -114,7 +120,7 @@ namespace SabreTools.Serialization.Deserializers
if (partition.RomFSSizeInMediaUnits > 0)
{
long offset = partition.RomFSOffsetInMediaUnits * mediaUnitSize;
data.Seek(initialOffset + offset, SeekOrigin.Begin);
data.Seek(partitionOffset + offset, SeekOrigin.Begin);
var romFsHeader = ParseRomFSHeader(data);
if (romFsHeader == null)
@@ -124,10 +130,6 @@ namespace SabreTools.Serialization.Deserializers
cart.RomFSHeaders[i] = romFsHeader;
}
// Skip past other data
long partitionSize = partition.ContentSizeInMediaUnits * mediaUnitSize;
data.Seek(initialOffset + partitionSize, SeekOrigin.Begin);
}
#endregion
@@ -329,7 +331,51 @@ namespace SabreTools.Serialization.Deserializers
/// <returns>Filled NCCH extended header on success, null on error</returns>
public static NCCHExtendedHeader? ParseNCCHExtendedHeader(Stream data)
{
return data.ReadType<NCCHExtendedHeader>();
// TODO: Replace with `data.ReadType<NCCHExtendedHeader>();` when enum serialization fixed
var header = new NCCHExtendedHeader();
header.SCI = data.ReadType<SystemControlInfo>();
header.ACI = ParseAccessControlInfo(data);
header.AccessDescSignature = data.ReadBytes(0x100);
header.NCCHHDRPublicKey = data.ReadBytes(0x100);
header.ACIForLimitations = ParseAccessControlInfo(data);
return header;
}
/// <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>
public static AccessControlInfo? ParseAccessControlInfo(Stream data)
{
var aci = new AccessControlInfo();
aci.ARM11LocalSystemCapabilities = data.ReadType<ARM11LocalSystemCapabilities>();
aci.ARM11KernelCapabilities = data.ReadType<ARM11KernelCapabilities>();
aci.ARM9AccessControl = ParseARM9AccessControl(data);
return aci;
}
/// <summary>
/// Parse a Stream into an ARM9 access control
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled ARM9 access control on success, null on error</returns>
public static ARM9AccessControl? ParseARM9AccessControl(Stream data)
{
var a9ac = new ARM9AccessControl();
a9ac.Descriptors = new ARM9AccessControlDescriptors[15];
for (int i = 0; i < a9ac.Descriptors.Length; i++)
{
a9ac.Descriptors[i] = (ARM9AccessControlDescriptors)data.ReadByteValue();
}
a9ac.DescriptorVersion = data.ReadByteValue();
return a9ac;
}
/// <summary>

View File

@@ -1,3 +1,4 @@
using System;
using System.Text;
using SabreTools.Models.N3DS;
using SabreTools.Serialization.Interfaces;
@@ -491,14 +492,20 @@ namespace SabreTools.Serialization.Printers
}
else
{
builder.AppendLine($" Descriptors: {entry.ACI.ARM9AccessControl.Descriptors} (0x{entry.ACI.ARM9AccessControl.Descriptors:X})");
string descriptorsStr = "[NULL]";
if (entry.ACI.ARM9AccessControl.Descriptors != null)
{
var descriptors = Array.ConvertAll(entry.ACI.ARM9AccessControl.Descriptors, d => d.ToString());
descriptorsStr = string.Join(", ", descriptors);
}
builder.AppendLine(descriptorsStr, " Descriptors");
builder.AppendLine(entry.ACI.ARM9AccessControl.DescriptorVersion, " Descriptor version");
}
builder.AppendLine(entry.AccessDescSignature, " AccessDec signature (RSA-2048-SHA256)");
builder.AppendLine(entry.NCCHHDRPublicKey, " NCCH HDR RSA-2048 public key");
}
builder.AppendLine(entry.AccessDescSignature, " AccessDec signature (RSA-2048-SHA256)");
builder.AppendLine(entry.NCCHHDRPublicKey, " NCCH HDR RSA-2048 public key");
builder.AppendLine(" Access control info (for limitations of first ACI):");
if (entry.ACIForLimitations == null)
{
@@ -559,7 +566,13 @@ namespace SabreTools.Serialization.Printers
}
else
{
builder.AppendLine($" Descriptors: {entry.ACIForLimitations.ARM9AccessControl.Descriptors} (0x{entry.ACIForLimitations.ARM9AccessControl.Descriptors:X})");
string descriptorsStr = "[NULL]";
if (entry.ACIForLimitations.ARM9AccessControl.Descriptors != null)
{
var descriptors = Array.ConvertAll(entry.ACIForLimitations.ARM9AccessControl.Descriptors, d => d.ToString());
descriptorsStr = string.Join(", ", descriptors);
}
builder.AppendLine(descriptorsStr, " Descriptors");
builder.AppendLine(entry.ACIForLimitations.ARM9AccessControl.DescriptorVersion, " Descriptor version");
}
}

View File

@@ -10,7 +10,7 @@
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.7.2</Version>
<Version>1.7.4</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>

View File

@@ -22,76 +22,10 @@ namespace SabreTools.Serialization.Wrappers
public NCCHHeader? BackupHeader => Model.CardInfoHeader?.InitialData?.BackupHeader;
/// <summary>
/// Backup Write Wait Time (The time to wait to write save to backup after the card is recognized (0-255
/// seconds)). NATIVE_FIRM loads this flag from the gamecard NCSD header starting with 6.0.0-11.
/// ExeFS headers
/// </summary>
public byte BackupWriteWaitTime
{
get
{
if (Model.Header?.PartitionFlags == null)
return default;
return Model.Header.PartitionFlags[(int)NCSDFlags.BackupWriteWaitTime];
}
}
/// <summary>
/// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (Only SDK 2.X)
/// </summary>
public MediaCardDeviceType MediaCardDevice2X
{
get
{
if (Model.Header?.PartitionFlags == null)
return default;
return (MediaCardDeviceType)Model.Header.PartitionFlags[(int)NCSDFlags.MediaCardDevice2X];
}
}
/// <summary>
/// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (SDK 3.X+)
/// </summary>
public MediaCardDeviceType MediaCardDevice3X
{
get
{
if (Model.Header?.PartitionFlags == null)
return default;
return (MediaCardDeviceType)Model.Header.PartitionFlags[(int)NCSDFlags.MediaCardDevice3X];
}
}
/// <summary>
/// Media Platform Index (1 = CTR)
/// </summary>
public MediaPlatformIndex MediaPlatformIndex
{
get
{
if (Model.Header?.PartitionFlags == null)
return default;
return (MediaPlatformIndex)Model.Header.PartitionFlags[(int)NCSDFlags.MediaPlatformIndex];
}
}
/// <summary>
/// Media Type Index (0 = Inner Device, 1 = Card1, 2 = Card2, 3 = Extended Device)
/// </summary>
public MediaTypeIndex MediaTypeIndex
{
get
{
if (Model.Header?.PartitionFlags == null)
return default;
return (MediaTypeIndex)Model.Header.PartitionFlags[(int)NCSDFlags.MediaTypeIndex];
}
}
public ExeFSHeader?[] ExeFSHeaders => Model.ExeFSHeaders ?? [];
/// <summary>
/// Media unit size in bytes
/// </summary>
@@ -106,7 +40,17 @@ namespace SabreTools.Serialization.Wrappers
}
}
#region Partition Entries
/// <summary>
/// Partitions data table
/// </summary>
public NCCHHeader?[] Partitions => Model.Partitions ?? [];
/// <summary>
/// Partitions header table
/// </summary>
public PartitionTableEntry?[] PartitionsTable => Model.Header?.PartitionsTable ?? [];
#region Named Partition Entries
/// <summary>
/// Partition table entry for Executable Content (CXI)
@@ -115,10 +59,10 @@ namespace SabreTools.Serialization.Wrappers
{
get
{
if (Model.Header?.PartitionsTable == null)
if (PartitionsTable == null || PartitionsTable.Length == 0)
return null;
return Model.Header.PartitionsTable[0];
return PartitionsTable[0];
}
}
@@ -129,10 +73,10 @@ namespace SabreTools.Serialization.Wrappers
{
get
{
if (Model.Header?.PartitionsTable == null)
if (PartitionsTable == null || PartitionsTable.Length == 0)
return null;
return Model.Header.PartitionsTable[1];
return PartitionsTable[1];
}
}
@@ -143,10 +87,10 @@ namespace SabreTools.Serialization.Wrappers
{
get
{
if (Model.Header?.PartitionsTable == null)
if (PartitionsTable == null || PartitionsTable.Length == 0)
return null;
return Model.Header.PartitionsTable[2];
return PartitionsTable[2];
}
}
@@ -157,10 +101,10 @@ namespace SabreTools.Serialization.Wrappers
{
get
{
if (Model.Header?.PartitionsTable == null)
if (PartitionsTable == null || PartitionsTable.Length == 0)
return null;
return Model.Header.PartitionsTable[6];
return PartitionsTable[6];
}
}
@@ -171,10 +115,90 @@ namespace SabreTools.Serialization.Wrappers
{
get
{
if (Model.Header?.PartitionsTable == null)
if (PartitionsTable == null || PartitionsTable.Length == 0)
return null;
return Model.Header.PartitionsTable[7];
return PartitionsTable[7];
}
}
#endregion
/// <summary>
/// Partitions flags
/// </summary>
public byte[] PartitionFlags => Model.Header?.PartitionFlags ?? [];
#region Partition Flags
/// <summary>
/// Backup Write Wait Time (The time to wait to write save to backup after the card is recognized (0-255
/// seconds)). NATIVE_FIRM loads this flag from the gamecard NCSD header starting with 6.0.0-11.
/// </summary>
public byte BackupWriteWaitTime
{
get
{
if (PartitionFlags == null || PartitionFlags.Length == 0)
return default;
return PartitionFlags[(int)NCSDFlags.BackupWriteWaitTime];
}
}
/// <summary>
/// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (Only SDK 2.X)
/// </summary>
public MediaCardDeviceType MediaCardDevice2X
{
get
{
if (PartitionFlags == null || PartitionFlags.Length == 0)
return default;
return (MediaCardDeviceType)PartitionFlags[(int)NCSDFlags.MediaCardDevice2X];
}
}
/// <summary>
/// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (SDK 3.X+)
/// </summary>
public MediaCardDeviceType MediaCardDevice3X
{
get
{
if (PartitionFlags == null || PartitionFlags.Length == 0)
return default;
return (MediaCardDeviceType)PartitionFlags[(int)NCSDFlags.MediaCardDevice3X];
}
}
/// <summary>
/// Media Platform Index (1 = CTR)
/// </summary>
public MediaPlatformIndex MediaPlatformIndex
{
get
{
if (PartitionFlags == null || PartitionFlags.Length == 0)
return default;
return (MediaPlatformIndex)PartitionFlags[(int)NCSDFlags.MediaPlatformIndex];
}
}
/// <summary>
/// Media Type Index (0 = Inner Device, 1 = Card1, 2 = Card2, 3 = Extended Device)
/// </summary>
public MediaTypeIndex MediaTypeIndex
{
get
{
if (PartitionFlags == null || PartitionFlags.Length == 0)
return default;
return (MediaTypeIndex)PartitionFlags[(int)NCSDFlags.MediaTypeIndex];
}
}
@@ -251,31 +275,48 @@ namespace SabreTools.Serialization.Wrappers
/// <summary>
/// Get the bit masks for a partition
/// </summary>
public BitMasks GetBitMasks(int partitionIndex)
public BitMasks GetBitMasks(int index)
{
if (Model.Partitions == null)
if (Partitions == null)
return 0;
if (partitionIndex < 0 || partitionIndex >= Model.Partitions.Length)
if (index < 0 || index >= Partitions.Length)
return 0;
var partition = Model.Partitions[partitionIndex];
var partition = Partitions[index];
if (partition?.Flags == null)
return 0;
return partition.Flags.BitMasks;
}
/// <summary>
/// Get the crypto method for a partition
/// </summary>
public CryptoMethod GetCryptoMethod(int index)
{
if (Partitions == null)
return 0;
if (index < 0 || index >= Partitions.Length)
return 0;
var partition = Partitions[index];
if (partition?.Flags == null)
return 0;
return partition.Flags.CryptoMethod;
}
/// <summary>
/// Determines if a file header represents a CODE block
/// </summary>
public bool IsCodeBinary(int fsIndex, int headerIndex)
{
if (Model.ExeFSHeaders == null)
if (ExeFSHeaders == null)
return false;
if (fsIndex < 0 || fsIndex >= Model.ExeFSHeaders.Length)
if (fsIndex < 0 || fsIndex >= ExeFSHeaders.Length)
return false;
var fsHeader = Model.ExeFSHeaders[fsIndex];
var fsHeader = ExeFSHeaders[fsIndex];
if (fsHeader?.FileHeaders == null)
return false;
@@ -286,60 +327,63 @@ namespace SabreTools.Serialization.Wrappers
if (fileHeader == null)
return false;
return fileHeader.FileName == ".code\0\0\0";
return fileHeader.FileName == ".code";
}
/// <summary>
/// Get the initial value for the plain counter
/// </summary>
public byte[] PlainIV(int partitionIndex)
public byte[] PlainIV(int index)
{
if (Model.Partitions == null)
if (Partitions == null)
return [];
if (partitionIndex < 0 || partitionIndex >= Model.Partitions.Length)
if (index < 0 || index >= Partitions.Length)
return [];
var header = Model.Partitions[partitionIndex];
if (header == null)
var header = Partitions[index];
if (header == null || header.MagicID != NCCHMagicNumber)
return [];
byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId);
Array.Reverse(partitionIdBytes);
return [.. partitionIdBytes, .. PlainCounter];
}
/// <summary>
/// Get the initial value for the ExeFS counter
/// </summary>
public byte[] ExeFSIV(int partitionIndex)
public byte[] ExeFSIV(int index)
{
if (Model.Partitions == null)
if (Partitions == null)
return [];
if (partitionIndex < 0 || partitionIndex >= Model.Partitions.Length)
if (index < 0 || index >= Partitions.Length)
return [];
var header = Model.Partitions[partitionIndex];
if (header == null)
var header = Partitions[index];
if (header == null || header.MagicID != NCCHMagicNumber)
return [];
byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId);
Array.Reverse(partitionIdBytes);
return [.. partitionIdBytes, .. ExefsCounter];
}
/// <summary>
/// Get the initial value for the RomFS counter
/// </summary>
public byte[] RomFSIV(int partitionIndex)
public byte[] RomFSIV(int index)
{
if (Model.Partitions == null)
if (Partitions == null)
return [];
if (partitionIndex < 0 || partitionIndex >= Model.Partitions.Length)
if (index < 0 || index >= Partitions.Length)
return [];
var header = Model.Partitions[partitionIndex];
if (header == null)
var header = Partitions[index];
if (header == null || header.MagicID != NCCHMagicNumber)
return [];
byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId);
Array.Reverse(partitionIdBytes);
return [.. partitionIdBytes, .. RomfsCounter];
}
@@ -348,20 +392,11 @@ namespace SabreTools.Serialization.Wrappers
/// </summary>
public bool PossiblyDecrypted(int index)
{
if (Model.Partitions == null)
return false;
if (index < 0 || index >= Model.Partitions.Length)
return false;
var partition = Model.Partitions[index];
if (partition?.Flags == null)
return false;
var bitMasks = GetBitMasks(index);
#if NET20 || NET35
return (partition.Flags.BitMasks & BitMasks.NoCrypto) != 0;
return (bitMasks & BitMasks.NoCrypto) != 0;
#else
return partition.Flags.BitMasks.HasFlag(BitMasks.NoCrypto);
return bitMasks.HasFlag(BitMasks.NoCrypto);
#endif
}
@@ -375,24 +410,20 @@ namespace SabreTools.Serialization.Wrappers
/// <returns>Offset to the ExeFS of the partition, 0 on error</returns>
public uint GetExeFSOffset(int index)
{
// Empty partitions table means no size is available
var partitionsTable = Model.Header?.PartitionsTable;
if (partitionsTable == null)
// No partitions means no size is available
if (PartitionsTable == null || Partitions == null)
return 0;
if (index < 0 || index >= Partitions.Length)
return 0;
// Invalid partition table entry means no size is available
var entry = partitionsTable[index];
var entry = PartitionsTable[index];
if (entry == null)
return 0;
// Empty partitions array means no size is available
var partitions = Model.Partitions;
if (partitions == null)
return 0;
// Invalid partition means no size is available
var header = partitions[index];
if (header == null)
var header = Partitions[index];
if (header == null || header.MagicID != NCCHMagicNumber)
return 0;
// If the offset is 0, return 0
@@ -411,13 +442,14 @@ namespace SabreTools.Serialization.Wrappers
/// <returns>Offset to the partition, 0 on error</returns>
public uint GetPartitionOffset(int index)
{
// Empty partitions table means no size is available
var partitionsTable = Model.Header?.PartitionsTable;
if (partitionsTable == null)
// No partitions means no size is available
if (PartitionsTable == null)
return 0;
if (index < 0 || index >= PartitionsTable.Length)
return 0;
// Invalid partition table entry means no size is available
var entry = partitionsTable[index];
var entry = PartitionsTable[index];
if (entry == null)
return 0;
@@ -436,24 +468,20 @@ namespace SabreTools.Serialization.Wrappers
/// <returns>Offset to the RomFS of the partition, 0 on error</returns>
public uint GetRomFSOffset(int index)
{
// Empty partitions table means no size is available
var partitionsTable = Model.Header?.PartitionsTable;
if (partitionsTable == null)
// No partitions means no size is available
if (PartitionsTable == null || Partitions == null)
return 0;
if (index < 0 || index >= Partitions.Length)
return 0;
// Invalid partition table entry means no size is available
var entry = partitionsTable[index];
var entry = PartitionsTable[index];
if (entry == null)
return 0;
// Empty partitions array means no size is available
var partitions = Model.Partitions;
if (partitions == null)
return 0;
// Invalid partition means no size is available
var header = partitions[index];
if (header == null)
var header = Partitions[index];
if (header == null || header.MagicID != NCCHMagicNumber)
return 0;
// If the offset is 0, return 0
@@ -477,12 +505,13 @@ namespace SabreTools.Serialization.Wrappers
public uint GetExeFSSize(int index)
{
// Empty partitions array means no size is available
var partitions = Model.Partitions;
if (partitions == null)
if (Partitions == null)
return 0;
if (index < 0 || index >= Partitions.Length)
return 0;
// Invalid partition header means no size is available
var header = partitions[index];
var header = Partitions[index];
if (header == null)
return 0;
@@ -497,12 +526,13 @@ namespace SabreTools.Serialization.Wrappers
public uint GetExtendedHeaderSize(int index)
{
// Empty partitions array means no size is available
var partitions = Model.Partitions;
if (partitions == null)
if (Partitions == null)
return 0;
if (index < 0 || index >= Partitions.Length)
return 0;
// Invalid partition header means no size is available
var header = partitions[index];
var header = Partitions[index];
if (header == null)
return 0;
@@ -517,12 +547,13 @@ namespace SabreTools.Serialization.Wrappers
public uint GetRomFSSize(int index)
{
// Empty partitions array means no size is available
var partitions = Model.Partitions;
if (partitions == null)
if (Partitions == null)
return 0;
if (index < 0 || index >= Partitions.Length)
return 0;
// Invalid partition header means no size is available
var header = partitions[index];
var header = Partitions[index];
if (header == null)
return 0;