Read and use full headers

This commit is contained in:
Matt Nadareski
2019-04-08 00:09:18 -07:00
parent dc68aa5046
commit 26c7a34e98
3 changed files with 202 additions and 51 deletions

View File

@@ -160,14 +160,16 @@ namespace ThreeDS.Headers
/// Read from a stream and get an NCCH header, if possible
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <param name="readSignature">True if the RSA signature is read, false otherwise</param>
/// <returns>NCCH header object, null on error</returns>
public static NCCHHeader Read(BinaryReader reader)
public static NCCHHeader Read(BinaryReader reader, bool readSignature)
{
NCCHHeader header = new NCCHHeader();
try
{
header.RSA2048Signature = reader.ReadBytes(0x100);
if (readSignature)
header.RSA2048Signature = reader.ReadBytes(0x100);
if (new string(reader.ReadChars(4)) != NCCHMagicNumber)
return null;

View File

@@ -40,9 +40,34 @@ namespace ThreeDS.Headers
/// </summary>
public PartitionTableEntry[] PartitionsTable { get; private set; }
/// <summary>
/// Partition table entry for Executable Content (CXI)
/// </summary>
public PartitionTableEntry ExecutableContent { get { return PartitionsTable[0]; } }
/// <summary>
/// Partition table entry for E-Manual (CFA)
/// </summary>
public PartitionTableEntry EManual { get { return PartitionsTable[1]; } }
/// <summary>
/// Partition table entry for Download Play Child container (CFA)
/// </summary>
public PartitionTableEntry DownloadPlayChildContainer { get { return PartitionsTable[2]; } }
/// <summary>
/// Partition table entry for New3DS Update Data (CFA)
/// </summary>
public PartitionTableEntry New3DSUpdateData { get { return PartitionsTable[6]; } }
/// <summary>
/// Partition table entry for Update Data (CFA)
/// </summary>
public PartitionTableEntry UpdateData { get { return PartitionsTable[7]; } }
#endregion
#region For carts
#region CTR Cart Image (CCI) Specific
/// <summary>
/// Exheader SHA-256 hash
@@ -62,13 +87,38 @@ namespace ThreeDS.Headers
/// <summary>
/// Partition Flags
/// </summary>
private byte[] partitionFlags;
public byte BackupWriteWaitTime { get { return partitionFlags[(int)NCSDFlags.BackupWriteWaitTime]; } }
public MediaCardDeviceType MediaCardDevice3X { get { return (MediaCardDeviceType)partitionFlags[(int)NCSDFlags.MediaCardDevice3X]; } }
public MediaPlatformIndex MediaPlatformIndex { get { return (MediaPlatformIndex)partitionFlags[(int)NCSDFlags.MediaPlatformIndex]; } }
public MediaTypeIndex MediaTypeIndex { get { return (MediaTypeIndex)partitionFlags[(int)NCSDFlags.MediaTypeIndex]; } }
public uint SectorSize { get { return (uint)(0x200 * Math.Pow(2, partitionFlags[(int)NCSDFlags.MediaUnitSize])); } }
public MediaCardDeviceType MediaCardDevice2X { get { return (MediaCardDeviceType)partitionFlags[(int)NCSDFlags.MediaCardDevice2X]; } }
public byte[] PartitionFlags { get; private set; }
/// <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 { return PartitionFlags[(int)NCSDFlags.BackupWriteWaitTime]; } }
/// <summary>
/// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (SDK 3.X+)
/// </summary>
public MediaCardDeviceType MediaCardDevice3X { get { return (MediaCardDeviceType)PartitionFlags[(int)NCSDFlags.MediaCardDevice3X]; } }
/// <summary>
/// Media Platform Index (1 = CTR)
/// </summary>
public MediaPlatformIndex MediaPlatformIndex { get { 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 { return (MediaTypeIndex)PartitionFlags[(int)NCSDFlags.MediaTypeIndex]; } }
/// <summary>
/// Media Unit Size i.e. u32 MediaUnitSize = 0x200*2^flags[6];
/// </summary>
public uint MediaUnitSize { get { return (uint)(0x200 * Math.Pow(2, PartitionFlags[(int)NCSDFlags.MediaUnitSize])); } }
/// <summary>
/// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (Only SDK 2.X)
/// </summary>
public MediaCardDeviceType MediaCardDevice2X { get { return (MediaCardDeviceType)PartitionFlags[(int)NCSDFlags.MediaCardDevice2X]; } }
/// <summary>
/// Partition ID table
@@ -96,11 +146,11 @@ namespace ThreeDS.Headers
/// <summary>
/// Support for this was implemented with 9.6.0-X FIRM, see below regarding save crypto.
/// </summary>
public byte FIrmUpdateByte2 { get; private set; }
public byte FirmUpdateByte2 { get; private set; }
#endregion
#region For NAND
#region Raw NAND Format Specific
/// <summary>
/// Unknown
@@ -114,12 +164,95 @@ namespace ThreeDS.Headers
#endregion
#region Card Info Header
/// <summary>
/// CARD2: Writable Address In Media Units (For 'On-Chip' Savedata). CARD1: Always 0xFFFFFFFF.
/// </summary>
public byte[] CARD2WritableAddressMediaUnits { get; private set; }
/// <summary>
/// Card Info Bitmask
/// </summary>
public byte[] CardInfoBytemask { get; private set; }
/// <summary>
/// Reserved1
/// </summary>
public byte[] Reserved3 { get; private set; }
/// <summary>
/// Title version
/// </summary>
public ushort TitleVersion { get; private set; }
/// <summary>
/// Card revision
/// </summary>
public ushort CardRevision { get; private set; }
/// <summary>
/// Reserved2
/// </summary>
public byte[] Reserved4 { get; private set; }
/// <summary>
/// Card seed keyY (first u64 is Media ID (same as first NCCH partitionId))
/// </summary>
public byte[] CardSeedKeyY { get; private set; }
/// <summary>
/// Encrypted card seed (AES-CCM, keyslot 0x3B for retail cards, see CTRCARD_SECSEED) /// </summary>
public byte[] EncryptedCardSeed { get; private set; }
/// <summary>
/// Card seed AES-MAC
/// </summary>
public byte[] CardSeedAESMAC { get; private set; }
/// <summary>
/// Card seed nonce
/// </summary>
public byte[] CardSeedNonce { get; private set; }
/// <summary>
/// Reserved3
/// </summary>
public byte[] Reserved5 { get; private set; }
/// <summary>
/// Copy of first NCCH header (excluding RSA signature)
/// </summary>
public NCCHHeader BackupHeader { get; private set; }
#endregion
#region Development Card Info Header Extension
/// <summary>
/// CardDeviceReserved1
/// </summary>
public byte[] CardDeviceReserved1 { get; private set; }
/// <summary>
/// TitleKey
/// </summary>
public byte[] TitleKey { get; private set; }
/// <summary>
/// CardDeviceReserved2
/// </summary>
public byte[] CardDeviceReserved2 { get; private set; }
#endregion
/// <summary>
/// Read from a stream and get an NCSD header, if possible
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <param name="development">True if development cart, false otherwise</param>
/// <returns>NCSD header object, null on error</returns>
public static NCSDHeader Read(BinaryReader reader)
public static NCSDHeader Read(BinaryReader reader, bool development)
{
NCSDHeader header = new NCSDHeader();
@@ -145,7 +278,7 @@ namespace ThreeDS.Headers
header.ExheaderHash = reader.ReadBytes(0x20);
header.AdditionalHeaderSize = reader.ReadUInt32();
header.SectorZeroOffset = reader.ReadUInt32();
header.partitionFlags = reader.ReadBytes(8);
header.PartitionFlags = reader.ReadBytes(8);
header.PartitionIdTable = new byte[8][];
for (int i = 0; i < 8; i++)
@@ -154,7 +287,27 @@ namespace ThreeDS.Headers
header.Reserved1 = reader.ReadBytes(0x20);
header.Reserved2 = reader.ReadBytes(0xE);
header.FirmUpdateByte1 = reader.ReadByte();
header.FIrmUpdateByte2 = reader.ReadByte();
header.FirmUpdateByte2 = reader.ReadByte();
header.CARD2WritableAddressMediaUnits = reader.ReadBytes(4);
header.CardInfoBytemask = reader.ReadBytes(4);
header.Reserved3 = reader.ReadBytes(0x108);
header.TitleVersion = reader.ReadUInt16();
header.CardRevision = reader.ReadUInt16();
header.Reserved4 = reader.ReadBytes(0xCEE);
header.CardSeedKeyY = reader.ReadBytes(0x10);
header.EncryptedCardSeed = reader.ReadBytes(0x10);
header.CardSeedAESMAC = reader.ReadBytes(0x10);
header.CardSeedNonce = reader.ReadBytes(0x10);
header.Reserved5 = reader.ReadBytes(0xC4);
header.BackupHeader = NCCHHeader.Read(reader, false);
if (development)
{
header.CardDeviceReserved1 = reader.ReadBytes(0x200);
header.TitleKey = reader.ReadBytes(0x10);
header.CardDeviceReserved2 = reader.ReadBytes(0xF0);
}
}
else if (header.PartitionsFSType == FilesystemType.FIRM)
{

View File

@@ -66,7 +66,7 @@ namespace ThreeDS
using (BinaryReader f = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
using (BinaryWriter g = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)))
{
NCSDHeader header = NCSDHeader.Read(f);
NCSDHeader header = NCSDHeader.Read(f, development);
if (header == null)
{
Console.WriteLine("Error: Not a 3DS Rom!");
@@ -83,9 +83,9 @@ namespace ThreeDS
}
// Seek to the beginning of the NCCH partition
f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize), SeekOrigin.Begin);
f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.MediaUnitSize), SeekOrigin.Begin);
NCCHHeader partitionHeader = NCCHHeader.Read(f);
NCCHHeader partitionHeader = NCCHHeader.Read(f, true);
if (partitionHeader == null)
{
Console.WriteLine($"Partition {p} Unable to read NCCH header");
@@ -108,12 +108,12 @@ namespace ThreeDS
ProcessRomFS(f, g, header, p, partitionHeader, false);
// Write the new CryptoMethod
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18B, SeekOrigin.Begin);
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.MediaUnitSize) + 0x18B, SeekOrigin.Begin);
g.Write((byte)CryptoMethod.Original);
g.Flush();
// Write the new BitMasks flag
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18F, SeekOrigin.Begin);
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.MediaUnitSize) + 0x18F, SeekOrigin.Begin);
BitMasks flag = partitionHeader.Flags.BitMasks;
flag = flag & (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
flag = (flag | BitMasks.NoCrypto);
@@ -139,17 +139,13 @@ namespace ThreeDS
using (BinaryReader f = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
using (BinaryWriter g = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)))
{
NCSDHeader header = NCSDHeader.Read(f);
NCSDHeader header = NCSDHeader.Read(f, development);
if (header == null)
{
Console.WriteLine("Error: Not a 3DS Rom!");
return;
}
// Get the backup flags
f.BaseStream.Seek(0x1188, SeekOrigin.Begin);
NCCHHeaderFlags backupFlags = NCCHHeaderFlags.Read(f);
// Iterate over all 8 NCCH partitions
for (int p = 0; p < 8; p++)
{
@@ -160,9 +156,9 @@ namespace ThreeDS
}
// Seek to the beginning of the NCCH partition
f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize), SeekOrigin.Begin);
f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.MediaUnitSize), SeekOrigin.Begin);
NCCHHeader partitionHeader = NCCHHeader.Read(f);
NCCHHeader partitionHeader = NCCHHeader.Read(f, true);
if (partitionHeader == null)
{
Console.WriteLine($"Partition {p} Unable to read NCCH header");
@@ -177,15 +173,15 @@ namespace ThreeDS
}
// Determine the Keys to be used
SetEncryptionKeys(partitionHeader.RSA2048Signature, backupFlags.BitMasks, backupFlags.CryptoMethod, p);
SetEncryptionKeys(partitionHeader.RSA2048Signature, header.BackupHeader.Flags.BitMasks, header.BackupHeader.Flags.CryptoMethod, p);
// Encrypt each of the pieces if they exist
ProcessExtendedHeader(f, g, header, p, partitionHeader, true);
ProcessExeFS(f, g, header, p, partitionHeader, true);
ProcessRomFS(f, g, header, p, partitionHeader, true, backupFlags);
ProcessRomFS(f, g, header, p, partitionHeader, true, header.BackupHeader.Flags);
// Write the new CryptoMethod
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18B, SeekOrigin.Begin);
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.MediaUnitSize) + 0x18B, SeekOrigin.Begin);
if (p > 0)
{
g.Write((byte)CryptoMethod.Original); // For partitions 1 and up, set crypto-method to 0x00
@@ -193,15 +189,15 @@ namespace ThreeDS
}
else
{
g.Write((byte)backupFlags.CryptoMethod); // If partition 0, restore crypto-method from backup flags
g.Write((byte)header.BackupHeader.Flags.CryptoMethod); // If partition 0, restore crypto-method from backup flags
g.Flush();
}
// Write the new BitMasks flag
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18F, SeekOrigin.Begin);
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.MediaUnitSize) + 0x18F, SeekOrigin.Begin);
BitMasks flag = partitionHeader.Flags.BitMasks;
flag = (flag & ((BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF));
flag = (flag | (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & backupFlags.BitMasks);
flag = (flag | (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & header.BackupHeader.Flags.BitMasks);
g.Write((byte)flag);
g.Flush();
}
@@ -352,8 +348,8 @@ namespace ThreeDS
{
if (partitionHeader.ExtendedHeaderSizeInBytes > 0)
{
reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin);
writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin);
reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset * header.MediaUnitSize) + 0x200, SeekOrigin.Begin);
writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset * header.MediaUnitSize) + 0x200, SeekOrigin.Begin);
Console.WriteLine($"Partition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + ": ExHeader");
@@ -387,7 +383,7 @@ namespace ThreeDS
// For all but the original crypto method, process each of the files in the table
if (partitionHeader.Flags.CryptoMethod != CryptoMethod.Original)
{
reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.MediaUnitSize, SeekOrigin.Begin);
ExeFSHeader exefsHeader = ExeFSHeader.Read(reader);
if (exefsHeader != null)
{
@@ -399,15 +395,15 @@ namespace ThreeDS
uint datalenM = ((fileHeader.FileSize) / (1024 * 1024));
uint datalenB = ((fileHeader.FileSize) % (1024 * 1024));
uint ctroffset = ((fileHeader.FileOffset + header.SectorSize) / 0x10);
uint ctroffset = ((fileHeader.FileOffset + header.MediaUnitSize) / 0x10);
byte[] exefsIVWithOffsetForHeader = AddToByteArray(partitionHeader.ExeFSIV, (int)ctroffset);
var firstCipher = CreateAESCipher(NormalKey, exefsIVWithOffsetForHeader, encrypt);
var secondCipher = CreateAESCipher(NormalKey2C, exefsIVWithOffsetForHeader, !encrypt);
reader.BaseStream.Seek((((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin);
writer.BaseStream.Seek((((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin);
reader.BaseStream.Seek((((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.MediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin);
writer.BaseStream.Seek((((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.MediaUnitSize) + fileHeader.FileOffset, SeekOrigin.Begin);
if (datalenM > 0)
{
@@ -435,16 +431,16 @@ namespace ThreeDS
ProcessExeFSFilenameTable(reader, writer, header, partitionNumber, partitionHeader, encrypt);
// Process the ExeFS
int exefsSizeM = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) / (1024 * 1024);
int exefsSizeB = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.SectorSize) % (1024 * 1024);
int ctroffsetE = (int)(header.SectorSize / 0x10);
int exefsSizeM = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.MediaUnitSize) / (1024 * 1024);
int exefsSizeB = (int)((partitionHeader.ExeFSSizeInMediaUnits - 1) * header.MediaUnitSize) % (1024 * 1024);
int ctroffsetE = (int)(header.MediaUnitSize / 0x10);
byte[] exefsIVWithOffset = AddToByteArray(partitionHeader.ExeFSIV, ctroffsetE);
var exeFS = CreateAESCipher(NormalKey2C, exefsIVWithOffset, encrypt);
reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin);
writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin);
reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.MediaUnitSize, SeekOrigin.Begin);
writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.MediaUnitSize, SeekOrigin.Begin);
if (exefsSizeM > 0)
{
for (int i = 0; i < exefsSizeM; i++)
@@ -479,13 +475,13 @@ namespace ThreeDS
/// <param name="encrypt">True if we want to encrypt the extended header, false otherwise</param>
private void ProcessExeFSFilenameTable(BinaryReader reader, BinaryWriter writer, NCSDHeader header, int partitionNumber, NCCHHeader partitionHeader, bool encrypt)
{
reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.MediaUnitSize, SeekOrigin.Begin);
writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.MediaUnitSize, SeekOrigin.Begin);
Console.WriteLine($"Partition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table");
var exeFSFilenameTable = CreateAESCipher(NormalKey2C, partitionHeader.ExeFSIV, encrypt);
writer.Write(exeFSFilenameTable.ProcessBytes(reader.ReadBytes((int)header.SectorSize)));
writer.Write(exeFSFilenameTable.ProcessBytes(reader.ReadBytes((int)header.MediaUnitSize)));
writer.Flush();
}
@@ -503,8 +499,8 @@ namespace ThreeDS
{
if (partitionHeader.RomFSOffsetInMediaUnits != 0)
{
int romfsSizeM = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (1024 * 1024);
int romfsSizeB = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) % (1024 * 1024);
int romfsSizeM = (int)(partitionHeader.RomFSSizeInMediaUnits * header.MediaUnitSize) / (1024 * 1024);
int romfsSizeB = (int)(partitionHeader.RomFSSizeInMediaUnits * header.MediaUnitSize) % (1024 * 1024);
// Encrypting RomFS for partitions 1 and up always use Key0x2C
if (encrypt && partitionNumber > 0)
@@ -529,8 +525,8 @@ namespace ThreeDS
var cipher = CreateAESCipher(NormalKey, partitionHeader.RomFSIV, encrypt);
reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
reader.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.MediaUnitSize, SeekOrigin.Begin);
writer.BaseStream.Seek((header.PartitionsTable[partitionNumber].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.MediaUnitSize, SeekOrigin.Begin);
if (romfsSizeM > 0)
{
for (int i = 0; i < romfsSizeM; i++)