mirror of
https://github.com/SabreTools/NDecrypt.git
synced 2026-02-07 13:52:13 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc68aa5046 | ||
|
|
ff486d8613 | ||
|
|
941f7d5191 |
@@ -49,6 +49,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="Data\Constants.cs" />
|
||||
<Compile Include="Headers\NCCHHeaderFlags.cs" />
|
||||
<Compile Include="Headers\RomFSHeader.cs" />
|
||||
<Compile Include="ThreeDSTool.cs" />
|
||||
<Compile Include="Data\Enums.cs" />
|
||||
<Compile Include="Headers\AccessControlInfo.cs" />
|
||||
|
||||
@@ -2,6 +2,61 @@
|
||||
|
||||
namespace ThreeDS.Data
|
||||
{
|
||||
[Flags]
|
||||
public enum ARM9AccessControlDescriptors : byte
|
||||
{
|
||||
MountNandRoot = 0x01,
|
||||
MountNandroWriteAccess = 0x02,
|
||||
MountTwlnRoot = 0x04,
|
||||
MountWnandRoot = 0x08,
|
||||
MountCardSPI = 0x0F,
|
||||
UseSDIF3 = 0x10,
|
||||
CreateSeed = 0x20,
|
||||
UseCardSPI = 0x40,
|
||||
SDApplication = 0x80,
|
||||
MoundSdmcWriteAccess = 0xF0,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ARM11LSCFlag0 : byte
|
||||
{
|
||||
IdealProcessor = 0x01 | 0x02,
|
||||
AffinityMask = 0x04 | 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// Value Description
|
||||
/// 0 Prod (64MB of usable application memory)
|
||||
/// 1 Undefined (unusable)
|
||||
/// 2 Dev1 (96MB of usable application memory)
|
||||
/// 3 Dev2 (80MB of usable application memory)
|
||||
/// 4 Dev3 (72MB of usable application memory)
|
||||
/// 5 Dev4 (32MB of usable application memory)
|
||||
/// 6-7 Undefined Same as Prod?
|
||||
/// </summary>
|
||||
Old3DSSystemMode = 0x0F | 0x10 | 0x20 | 0x40,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ARM11LSCFlag1 : byte
|
||||
{
|
||||
EnableL2Cache = 0x01,
|
||||
Cpuspeed_804MHz = 0x02,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ARM11LSCFlag2 : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Value Description
|
||||
/// 0 Legacy (use Old3DS system mode)
|
||||
/// 1 Prod (124MB of usable application memory)
|
||||
/// 2 Dev1 (178MB of usable application memory)
|
||||
/// 3 Dev2 (124MB of usable application memory)
|
||||
/// 4-7 Undefined Same as Prod?
|
||||
/// </summary>
|
||||
New3DSSystemMode = 0x01 | 0x02 | 0x04 | 0x08,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum BitMasks : byte
|
||||
{
|
||||
@@ -36,6 +91,33 @@ namespace ThreeDS.Data
|
||||
NineSix = 0x0B,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum FilesystemAccessInfo : ulong
|
||||
{
|
||||
CategorySystemApplication = 0x1,
|
||||
CategoryHardwareCheck = 0x2,
|
||||
CategoryFilesystemTool = 0x4,
|
||||
Debug = 0x8,
|
||||
TWLCardBackup = 0x10,
|
||||
TWLNANDData = 0x20,
|
||||
BOSS = 0x40,
|
||||
sdmcRoot = 0x80,
|
||||
Core = 0x100,
|
||||
nandRootroReadOnly = 0x200,
|
||||
nandRootrw = 0x400,
|
||||
nandrootroWriteAccess = 0x800,
|
||||
CategorySystemSettings = 0x1000,
|
||||
Cardboard = 0x2000,
|
||||
ExportImportIVS = 0x4000,
|
||||
sdmcRootWriteOnly = 0x8000,
|
||||
SwitchCleanup = 0x10000, // Introduced in 3.0.0?
|
||||
SavedataMove = 0x20000, // Introduced in 5.0.0
|
||||
Shop = 0x40000, // Introduced in 5.0.0
|
||||
Shell = 0x80000, // Introduced in 5.0.0
|
||||
CategoryHomeMenu = 0x100000, // Introduced in 6.0.0
|
||||
SeedDB = 0x200000, // Introduced in 9.6.0-X FIRM. Home Menu has this bit set starting with 9.6.0-X.
|
||||
}
|
||||
|
||||
public enum FilesystemType : ulong
|
||||
{
|
||||
None = 0,
|
||||
@@ -82,4 +164,19 @@ namespace ThreeDS.Data
|
||||
MediaUnitSize = 0x06,
|
||||
MediaCardDevice2X = 0x07,
|
||||
}
|
||||
|
||||
public enum ResourceLimitCategory
|
||||
{
|
||||
APPLICATION = 0,
|
||||
SYS_APPLET = 1,
|
||||
LIB_APPLET = 2,
|
||||
OTHER = 3,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum StorageInfoOtherAttributes : byte
|
||||
{
|
||||
NotUseROMFS = 0x01,
|
||||
UseExtendedSavedataAccess = 0x02,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,52 @@ namespace ThreeDS.Headers
|
||||
{
|
||||
public class ARM11KernelCapabilities
|
||||
{
|
||||
public byte[][] Descriptors = new byte[28][];
|
||||
public byte[] Reserved = new byte[0x10];
|
||||
/// <summary>
|
||||
/// Descriptors
|
||||
/// -------------------
|
||||
/// Pattern of bits 20-31 Type Fields
|
||||
/// 0b1110xxxxxxxx Interrupt info
|
||||
/// 0b11110xxxxxxx System call mask Bits 24-26: System call mask table index; Bits 0-23: mask
|
||||
/// 0b1111110xxxxx Kernel release version Bits 8-15: Major version; Bits 0-7: Minor version
|
||||
/// 0b11111110xxxx Handle table size Bits 0-18: size
|
||||
/// 0b111111110xxx Kernel flags
|
||||
/// 0b11111111100x Map address range Describes a memory mapping like the 0b111111111110 descriptor, but an entire range rather than a single page is mapped.Another 0b11111111100x descriptor must follow this one to denote the(exclusive) end of the address range to map.
|
||||
/// 0b111111111110 Map memory page Bits 0-19: page index to map(virtual address >> 12; the physical address is determined per-page according to Memory layout); Bit 20: Map read-only(otherwise read-write)
|
||||
///
|
||||
/// ARM11 Kernel Flags
|
||||
/// -------------------
|
||||
/// Bit Description
|
||||
/// 0 Allow debug
|
||||
/// 1 Force debug
|
||||
/// 2 Allow non-alphanum
|
||||
/// 3 Shared page writing
|
||||
/// 4 Privilege priority
|
||||
/// 5 Allow main() args
|
||||
/// 6 Shared device memory
|
||||
/// 7 Runnable on sleep
|
||||
/// 8-11 Memory type(1: application, 2: system, 3: base)
|
||||
/// 12 Special memory
|
||||
/// 13 Process has access to CPU core 2 (New3DS only)
|
||||
/// </summary>
|
||||
public byte[][] Descriptors { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get ARM11 kernel capabilities, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>ARM11 kernel capabilities object, null on error</returns>
|
||||
public static ARM11KernelCapabilities Read(BinaryReader reader)
|
||||
{
|
||||
ARM11KernelCapabilities kc = new ARM11KernelCapabilities();
|
||||
|
||||
try
|
||||
{
|
||||
kc.Descriptors = new byte[28][];
|
||||
for (int i = 0; i < 28; i++)
|
||||
kc.Descriptors[i] = reader.ReadBytes(4);
|
||||
|
||||
|
||||
@@ -1,22 +1,75 @@
|
||||
using System.IO;
|
||||
using ThreeDS.Data;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class ARM11LocalSystemCapabilities
|
||||
{
|
||||
public byte[] ProgramID = new byte[8];
|
||||
public uint CoreVersion;
|
||||
public byte Flag1;
|
||||
public byte Flag2;
|
||||
public byte Flag0;
|
||||
public byte Priority;
|
||||
public byte[][] ResourceLimitDescriptors = new byte[16][];
|
||||
public StorageInfo StorageInfo;
|
||||
public byte[][] ServiceAccessControl = new byte[32][];
|
||||
public byte[][] ExtendedServiceAccessControl = new byte[2][];
|
||||
public byte[] Reserved = new byte[0xF];
|
||||
public byte ResourceLimitCategory;
|
||||
/// <summary>
|
||||
/// Program ID
|
||||
/// </summary>
|
||||
public byte[] ProgramID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Core version (The Title ID low of the required FIRM)
|
||||
/// </summary>
|
||||
public uint CoreVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag1 (implemented starting from 8.0.0-18).
|
||||
/// </summary>
|
||||
public ARM11LSCFlag1 Flag1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag2 (implemented starting from 8.0.0-18).
|
||||
/// </summary>
|
||||
public ARM11LSCFlag2 Flag2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag0
|
||||
/// </summary>
|
||||
public ARM11LSCFlag0 Flag0 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Priority
|
||||
/// </summary>
|
||||
public byte Priority { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Resource limit descriptors. The first byte here controls the maximum allowed CpuTime.
|
||||
/// </summary>
|
||||
public byte[][] ResourceLimitDescriptors { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Storage info
|
||||
/// </summary>
|
||||
public StorageInfo StorageInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Service access control
|
||||
/// </summary>
|
||||
public byte[][] ServiceAccessControl { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Extended service access control, support for this was implemented with 9.3.0-X.
|
||||
/// </summary>
|
||||
public byte[][] ExtendedServiceAccessControl { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Resource limit category. (0 = APPLICATION, 1 = SYS_APPLET, 2 = LIB_APPLET, 3 = OTHER (sysmodules running under the BASE memregion))
|
||||
/// </summary>
|
||||
public ResourceLimitCategory ResourceLimitCategory { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get ARM11 local system capabilities, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>ARM11 local system capabilities object, null on error</returns>
|
||||
public static ARM11LocalSystemCapabilities Read(BinaryReader reader)
|
||||
{
|
||||
ARM11LocalSystemCapabilities lsc = new ARM11LocalSystemCapabilities();
|
||||
@@ -25,24 +78,27 @@ namespace ThreeDS.Headers
|
||||
{
|
||||
lsc.ProgramID = reader.ReadBytes(8);
|
||||
lsc.CoreVersion = reader.ReadUInt32();
|
||||
lsc.Flag1 = reader.ReadByte();
|
||||
lsc.Flag2 = reader.ReadByte();
|
||||
lsc.Flag0 = reader.ReadByte();
|
||||
lsc.Flag1 = (ARM11LSCFlag1)reader.ReadByte();
|
||||
lsc.Flag2 = (ARM11LSCFlag2)reader.ReadByte();
|
||||
lsc.Flag0 = (ARM11LSCFlag0)reader.ReadByte();
|
||||
lsc.Priority = reader.ReadByte();
|
||||
|
||||
lsc.ResourceLimitDescriptors = new byte[16][];
|
||||
for (int i = 0; i < 16; i++)
|
||||
lsc.ResourceLimitDescriptors[i] = reader.ReadBytes(2);
|
||||
|
||||
lsc.StorageInfo = StorageInfo.Read(reader);
|
||||
|
||||
lsc.ServiceAccessControl = new byte[32][];
|
||||
for (int i = 0; i < 32; i++)
|
||||
lsc.ServiceAccessControl[i] = reader.ReadBytes(8);
|
||||
|
||||
lsc.ExtendedServiceAccessControl = new byte[2][];
|
||||
for (int i = 0; i < 2; i++)
|
||||
lsc.ExtendedServiceAccessControl[i] = reader.ReadBytes(8);
|
||||
|
||||
lsc.Reserved = reader.ReadBytes(0xF);
|
||||
lsc.ResourceLimitCategory = reader.ReadByte();
|
||||
lsc.ResourceLimitCategory = (ResourceLimitCategory)reader.ReadByte();
|
||||
return lsc;
|
||||
}
|
||||
catch
|
||||
|
||||
@@ -1,19 +1,34 @@
|
||||
using System.IO;
|
||||
using ThreeDS.Data;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class ARM9AccessControl
|
||||
{
|
||||
public byte[] Descriptors = new byte[15];
|
||||
public byte DescriptorVersion;
|
||||
/// <summary>
|
||||
/// Descriptors
|
||||
/// </summary>
|
||||
public ARM9AccessControlDescriptors[] Descriptors { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 Descriptor Version. Originally this value had to be ≥ 2. Starting with 9.3.0-X this value has to be either value 2 or value 3.
|
||||
/// </summary>
|
||||
public byte DescriptorVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get ARM9 access control, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>ARM9 access control object, null on error</returns>
|
||||
public static ARM9AccessControl Read(BinaryReader reader)
|
||||
{
|
||||
ARM9AccessControl ac = new ARM9AccessControl();
|
||||
|
||||
try
|
||||
{
|
||||
ac.Descriptors = reader.ReadBytes(15);
|
||||
ac.Descriptors = new ARM9AccessControlDescriptors[15];
|
||||
for (int i = 0; i < 15; i++)
|
||||
ac.Descriptors[i] = (ARM9AccessControlDescriptors)reader.ReadByte();
|
||||
ac.DescriptorVersion = reader.ReadByte();
|
||||
return ac;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,26 @@ namespace ThreeDS.Headers
|
||||
{
|
||||
public class AccessControlInfo
|
||||
{
|
||||
public ARM11LocalSystemCapabilities ARM11LocalSystemCapabilities;
|
||||
public ARM11KernelCapabilities ARM11KernelCapabilities;
|
||||
public ARM9AccessControl ARM9AccessControl;
|
||||
/// <summary>
|
||||
/// ARM11 local system capabilities
|
||||
/// </summary>
|
||||
public ARM11LocalSystemCapabilities ARM11LocalSystemCapabilities { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM11 kernel capabilities
|
||||
/// </summary>
|
||||
public ARM11KernelCapabilities ARM11KernelCapabilities { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ARM9 access control
|
||||
/// </summary>
|
||||
public ARM9AccessControl ARM9AccessControl { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get access control info, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Access control info object, null on error</returns>
|
||||
public static AccessControlInfo Read(BinaryReader reader)
|
||||
{
|
||||
AccessControlInfo aci = new AccessControlInfo();
|
||||
|
||||
@@ -4,12 +4,36 @@ namespace ThreeDS.Headers
|
||||
{
|
||||
public class CXIExtendedHeader
|
||||
{
|
||||
public SystemControlInfo SCI;
|
||||
public AccessControlInfo ACI;
|
||||
public byte[] AccessDescSignature = new byte[0x100];
|
||||
public byte[] NCCHHDRPublicKey = new byte[0x100];
|
||||
public AccessControlInfo ACIForLimitations;
|
||||
/// <summary>
|
||||
/// SCI
|
||||
/// </summary>
|
||||
public SystemControlInfo SCI { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ACI
|
||||
/// </summary>
|
||||
public AccessControlInfo ACI { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// AccessDesc signature (RSA-2048-SHA256)
|
||||
/// </summary>
|
||||
public byte[] AccessDescSignature { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// NCCH HDR RSA-2048 public key
|
||||
/// </summary>
|
||||
public byte[] NCCHHDRPublicKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ACI (for limitation of first ACI)
|
||||
/// </summary>
|
||||
public AccessControlInfo ACIForLimitations { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get a CXI extended header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>CXI extended header object, null on error</returns>
|
||||
public static CXIExtendedHeader Read(BinaryReader reader)
|
||||
{
|
||||
CXIExtendedHeader header = new CXIExtendedHeader();
|
||||
|
||||
@@ -4,10 +4,26 @@ namespace ThreeDS.Headers
|
||||
{
|
||||
public class CodeSetInfo
|
||||
{
|
||||
public byte[] Address = new byte[0x04];
|
||||
public uint PhysicalRegionSizeInPages;
|
||||
public uint SizeInBytes;
|
||||
/// <summary>
|
||||
/// Address
|
||||
/// </summary>
|
||||
public byte[] Address { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Physical region size (in page-multiples)
|
||||
/// </summary>
|
||||
public uint PhysicalRegionSizeInPages { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size (in bytes)
|
||||
/// </summary>
|
||||
public uint SizeInBytes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get code set info, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Code set info object, null on error</returns>
|
||||
public static CodeSetInfo Read(BinaryReader reader)
|
||||
{
|
||||
CodeSetInfo csi = new CodeSetInfo();
|
||||
|
||||
@@ -9,13 +9,33 @@ namespace ThreeDS.Headers
|
||||
private const string codeSegment = ".code\0\0\0";
|
||||
private readonly byte[] codeSegmentBytes = new byte[] { 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x00, 0x00, 0x00 };
|
||||
|
||||
public byte[] FileName = new byte[8];
|
||||
/// <summary>
|
||||
/// File name
|
||||
/// </summary>
|
||||
public byte[] FileName { get; private set; }
|
||||
public string ReadableFileName { get { return Encoding.ASCII.GetString(FileName); } }
|
||||
public bool IsCodeBinary { get { return Enumerable.SequenceEqual(FileName, codeSegmentBytes); } }
|
||||
public uint FileOffset;
|
||||
public uint FileSize;
|
||||
public byte[] FileHash = new byte[0x20];
|
||||
|
||||
/// <summary>
|
||||
/// File offset
|
||||
/// </summary>
|
||||
public uint FileOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File size
|
||||
/// </summary>
|
||||
public uint FileSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SHA256 hash calculated over the entire file contents
|
||||
/// </summary>
|
||||
public byte[] FileHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an ExeFS file header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>ExeFS file header object, null on error</returns>
|
||||
public static ExeFSFileHeader Read(BinaryReader reader)
|
||||
{
|
||||
ExeFSFileHeader header = new ExeFSFileHeader();
|
||||
|
||||
@@ -4,25 +4,35 @@ namespace ThreeDS.Headers
|
||||
{
|
||||
public class ExeFSHeader
|
||||
{
|
||||
public ExeFSFileHeader[] FileHeaders = new ExeFSFileHeader[10];
|
||||
public byte[] Reserved = new byte[0x20];
|
||||
/// <summary>
|
||||
/// File headers (10 headers maximum, 16 bytes each)
|
||||
/// </summary>
|
||||
public ExeFSFileHeader[] FileHeaders { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an ExeFS header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>ExeFS header object, null on error</returns>
|
||||
public static ExeFSHeader Read(BinaryReader reader)
|
||||
{
|
||||
ExeFSHeader header = new ExeFSHeader();
|
||||
|
||||
try
|
||||
{
|
||||
header.FileHeaders = new ExeFSFileHeader[10];
|
||||
for (int i = 0; i < 10; i++)
|
||||
header.FileHeaders[i] = ExeFSFileHeader.Read(reader);
|
||||
|
||||
header.Reserved = reader.ReadBytes(0x20);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
byte[] fileHash = reader.ReadBytes(0x20);
|
||||
header.FileHeaders[9 - i].FileHash = fileHash;
|
||||
}
|
||||
header.FileHeaders[9 - i].FileHash = reader.ReadBytes(0x20);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ThreeDS.Data;
|
||||
|
||||
@@ -9,48 +8,159 @@ namespace ThreeDS.Headers
|
||||
{
|
||||
private const string NCCHMagicNumber = "NCCH";
|
||||
|
||||
/// <summary>
|
||||
/// RSA-2048 signature of the NCCH header, using SHA-256.
|
||||
/// </summary>
|
||||
public byte[] RSA2048Signature = new byte[0x100];
|
||||
public uint ContentSizeInMediaUnits;
|
||||
public uint ContentSizeInBytes { get { return ContentSizeInMediaUnits * 0x200; } }
|
||||
public byte[] PartitionId = new byte[8];
|
||||
public byte[] MakerCode = new byte[2];
|
||||
public byte[] Version = new byte[2];
|
||||
public byte[] VerificationHash = new byte[4];
|
||||
public byte[] ProgramId = new byte[4];
|
||||
public byte[] Reserved1 = new byte[0x10];
|
||||
public byte[] LogoRegionHash = new byte[0x20];
|
||||
public byte[] ProductCode = new byte[0x10];
|
||||
public byte[] ExtendedHeaderHash = new byte[0x20];
|
||||
public uint ExtendedHeaderSizeInBytes;
|
||||
public byte[] Reserved2 = new byte[4];
|
||||
public NCCHHeaderFlags Flags;
|
||||
public uint PlainRegionOffsetInMediaUnits;
|
||||
public uint PlainRegionOffsetInBytes { get { return PlainRegionOffsetInMediaUnits * 0x200; } }
|
||||
public uint PlainRegionSizeInMediaUnits;
|
||||
public uint PlainRegionSizeInBytes { get { return PlainRegionSizeInMediaUnits * 0x200; } }
|
||||
public uint LogoRegionOffsetInMediaUnits;
|
||||
public uint LogoRegionOffsetInBytes { get { return LogoRegionOffsetInMediaUnits * 0x200; } }
|
||||
public uint LogoRegionSizeInMediaUnits;
|
||||
public uint LogoRegionSizeInBytes { get { return LogoRegionSizeInMediaUnits * 0x200; } }
|
||||
public uint ExeFSOffsetInMediaUnits;
|
||||
public uint ExeFSOffsetInBytes { get { return ExeFSOffsetInMediaUnits * 0x200; } }
|
||||
public uint ExeFSSizeInMediaUnits;
|
||||
public uint ExeFSSizeInBytes { get { return ExeFSSizeInMediaUnits * 0x200; } }
|
||||
public uint ExeFSHashRegionOffsetInMediaUnits;
|
||||
public uint ExeFSHashRegionOffsetInBytes { get { return ExeFSHashRegionOffsetInMediaUnits * 0x200; } }
|
||||
public uint ExeFSHashRegionSizeInMediaUnits;
|
||||
public uint ExeFSHashRegionSizeInBytes { get { return ExeFSHashRegionSizeInMediaUnits * 0x200; } }
|
||||
public uint RomFSOffsetInMediaUnits;
|
||||
public uint RomFSOffsetInBytes { get { return RomFSOffsetInMediaUnits * 0x200; } }
|
||||
public uint RomFSSizeInMediaUnits;
|
||||
public uint RomFSSizeInBytes { get { return RomFSSizeInMediaUnits * 0x200; } }
|
||||
public uint RomFSHashRegionOffsetInMediaUnits;
|
||||
public uint RomFSHashRegionOffsetInBytes { get { return RomFSHashRegionOffsetInMediaUnits * 0x200; } }
|
||||
public uint RomFSHashRegionSizeInMediaUnits;
|
||||
public uint RomFSHashRegionSizeInBytes { get { return RomFSHashRegionSizeInMediaUnits * 0x200; } }
|
||||
public byte[] ExeFSSuperblockHash = new byte[0x20];
|
||||
public byte[] RomFSSuperblockHash = new byte[0x20];
|
||||
|
||||
/// <summary>
|
||||
/// Content size, in media units (1 media unit = 0x200 bytes)
|
||||
/// </summary>
|
||||
public uint ContentSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Partition ID
|
||||
/// </summary>
|
||||
public byte[] PartitionId { get; private set; }
|
||||
public byte[] PlainIV { get { return PartitionId.Concat(Constants.PlainCounter).ToArray(); } }
|
||||
public byte[] ExeFSIV { get { return PartitionId.Concat(Constants.ExefsCounter).ToArray(); } }
|
||||
public byte[] RomFSIV { get { return PartitionId.Concat(Constants.RomfsCounter).ToArray(); } }
|
||||
|
||||
/// <summary>
|
||||
/// Maker code
|
||||
/// </summary>
|
||||
public byte[] MakerCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Version
|
||||
/// </summary>
|
||||
public byte[] Version { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// When ncchflag[7] = 0x20 starting with FIRM 9.6.0-X, this is compared with the first output u32 from a
|
||||
/// SHA256 hash. The data used for that hash is 0x18-bytes: [0x10-long title-unique content lock seed]
|
||||
/// [programID from NCCH + 0x118]. This hash is only used for verification of the content lock seed, and
|
||||
/// is not the actual keyY.
|
||||
/// </summary>
|
||||
public byte[] VerificationHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Program ID
|
||||
/// </summary>
|
||||
public byte[] ProgramId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logo Region SHA-256 hash. (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11)
|
||||
/// </summary>
|
||||
public byte[] LogoRegionHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Product code
|
||||
/// </summary>
|
||||
public byte[] ProductCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Extended header SHA-256 hash (SHA256 of 2x Alignment Size, beginning at 0x0 of ExHeader)
|
||||
/// </summary>
|
||||
public byte[] ExtendedHeaderHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Extended header size, in bytes
|
||||
/// </summary>
|
||||
public uint ExtendedHeaderSizeInBytes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flags
|
||||
/// </summary>
|
||||
public NCCHHeaderFlags Flags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plain region offset, in media units
|
||||
/// </summary>
|
||||
public uint PlainRegionOffsetInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plain region size, in media units
|
||||
/// </summary>
|
||||
public uint PlainRegionSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logo Region offset, in media units (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11)
|
||||
/// </summary>
|
||||
public uint LogoRegionOffsetInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logo Region size, in media units (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11)
|
||||
/// </summary>
|
||||
public uint LogoRegionSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS offset, in media units
|
||||
/// </summary>
|
||||
public uint ExeFSOffsetInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS size, in media units
|
||||
/// </summary>
|
||||
public uint ExeFSSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS hash region size, in media units
|
||||
/// </summary>
|
||||
public uint ExeFSHashRegionSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved3 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RomFS offset, in media units
|
||||
/// </summary>
|
||||
public uint RomFSOffsetInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RomFS size, in media units
|
||||
/// </summary>
|
||||
public uint RomFSSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RomFS hash region size, in media units
|
||||
/// </summary>
|
||||
public uint RomFSHashRegionSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved4 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ExeFS superblock SHA-256 hash - (SHA-256 hash, starting at 0x0 of the ExeFS over the number of
|
||||
/// media units specified in the ExeFS hash region size)
|
||||
/// </summary>
|
||||
public byte[] ExeFSSuperblockHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RomFS superblock SHA-256 hash - (SHA-256 hash, starting at 0x0 of the RomFS over the number
|
||||
/// of media units specified in the RomFS hash region size)
|
||||
/// </summary>
|
||||
public byte[] RomFSSuperblockHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an NCCH header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>NCCH header object, null on error</returns>
|
||||
public static NCCHHeader Read(BinaryReader reader)
|
||||
{
|
||||
NCCHHeader header = new NCCHHeader();
|
||||
@@ -81,12 +191,12 @@ namespace ThreeDS.Headers
|
||||
header.LogoRegionSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.ExeFSOffsetInMediaUnits = reader.ReadUInt32();
|
||||
header.ExeFSSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.ExeFSHashRegionOffsetInMediaUnits = reader.ReadUInt32();
|
||||
header.ExeFSHashRegionSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.Reserved3 = reader.ReadBytes(4);
|
||||
header.RomFSOffsetInMediaUnits = reader.ReadUInt32();
|
||||
header.RomFSSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.RomFSHashRegionOffsetInMediaUnits = reader.ReadUInt32();
|
||||
header.RomFSHashRegionSizeInMediaUnits = reader.ReadUInt32();
|
||||
header.Reserved4 = reader.ReadBytes(4);
|
||||
header.ExeFSSuperblockHash = reader.ReadBytes(0x20);
|
||||
header.RomFSSuperblockHash = reader.ReadBytes(0x20);
|
||||
|
||||
|
||||
@@ -6,16 +6,54 @@ namespace ThreeDS.Headers
|
||||
{
|
||||
public class NCCHHeaderFlags
|
||||
{
|
||||
public byte Reserved0;
|
||||
public byte Reserved1;
|
||||
public byte Reserved2;
|
||||
public CryptoMethod CryptoMethod;
|
||||
public ContentPlatform ContentPlatform;
|
||||
public ContentType MediaPlatformIndex;
|
||||
public byte ContentUnitSize;
|
||||
public uint ContentUnitSizeInBytes { get { return (uint)(0x200 * Math.Pow(2, this.ContentUnitSize)); } }
|
||||
public BitMasks BitMasks;
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved0 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Crypto Method: When this is non-zero, a NCCH crypto method using two keyslots is used.
|
||||
/// </summary>
|
||||
public CryptoMethod CryptoMethod { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content Platform: 1 = CTR, 2 = snake (New 3DS).
|
||||
/// </summary>
|
||||
public ContentPlatform ContentPlatform { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content Type Bit-masks: Data = 0x1, Executable = 0x2, SystemUpdate = 0x4, Manual = 0x8,
|
||||
/// Child = (0x4|0x8), Trial = 0x10. When 'Data' is set, but not 'Executable', NCCH is a CFA.
|
||||
/// Otherwise when 'Executable' is set, NCCH is a CXI.
|
||||
/// </summary>
|
||||
public ContentType MediaPlatformIndex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content Unit Size i.e. u32 ContentUnitSize = 0x200*2^flags[6];
|
||||
/// </summary>
|
||||
public byte ContentUnitSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bit-masks: FixedCryptoKey = 0x1, NoMountRomFs = 0x2, NoCrypto = 0x4, using a new keyY
|
||||
/// generator = 0x20(starting with FIRM 9.6.0-X).
|
||||
/// </summary>
|
||||
public BitMasks BitMasks { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get an NCCH header flags, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>NCCH header flags object, null on error</returns>
|
||||
public static NCCHHeaderFlags Read(BinaryReader reader)
|
||||
{
|
||||
NCCHHeaderFlags flags = new NCCHHeaderFlags();
|
||||
@@ -38,6 +76,10 @@ namespace ThreeDS.Headers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write NCCH header flags to stream, if possible
|
||||
/// </summary>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -8,36 +8,117 @@ namespace ThreeDS.Headers
|
||||
{
|
||||
private const string NCSDMagicNumber = "NCSD";
|
||||
|
||||
// Common to all NCSD files
|
||||
public byte[] RSA2048Signature = new byte[0x100];
|
||||
public uint ImageSizeInMediaUnits;
|
||||
public uint ImageSizeInBytes { get { return ImageSizeInMediaUnits * 0x200; } }
|
||||
public byte[] MediaId = new byte[8];
|
||||
public FilesystemType PartitionsFSType;
|
||||
public byte[] PartitionsCryptType = new byte[8];
|
||||
public PartitionTableEntry[] PartitionsTable = new PartitionTableEntry[8];
|
||||
#region Common to all NCSD files
|
||||
|
||||
// For carts
|
||||
public byte[] ExheaderHash = new byte[0x20];
|
||||
public uint AdditionalHeaderSize;
|
||||
public uint SectorZeroOffset;
|
||||
private byte[] partitionFlags = new byte[8];
|
||||
/// <summary>
|
||||
/// RSA-2048 SHA-256 signature of the NCSD header
|
||||
/// </summary>
|
||||
public byte[] RSA2048Signature { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the NCSD image, in media units (1 media unit = 0x200 bytes)
|
||||
/// </summary>
|
||||
public uint ImageSizeInMediaUnits { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Media ID
|
||||
/// </summary>
|
||||
public byte[] MediaId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Partitions FS type (0=None, 1=Normal, 3=FIRM, 4=AGB_FIRM save)
|
||||
/// </summary>
|
||||
public FilesystemType PartitionsFSType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Partitions crypt type (each byte corresponds to a partition in the partition table)
|
||||
/// </summary>
|
||||
public byte[] PartitionsCryptType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset & Length partition table, in media units
|
||||
/// </summary>
|
||||
public PartitionTableEntry[] PartitionsTable { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region For carts
|
||||
|
||||
/// <summary>
|
||||
/// Exheader SHA-256 hash
|
||||
/// </summary>
|
||||
public byte[] ExheaderHash { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional header size
|
||||
/// </summary>
|
||||
public uint AdditionalHeaderSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sector zero offset
|
||||
/// </summary>
|
||||
public uint SectorZeroOffset { get; private set; }
|
||||
|
||||
/// <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[][] PartitionIdTable = new byte[8][];
|
||||
public byte[] ReservedBlock1 = new byte[0x20];
|
||||
public byte[] ReservedBlock2 = new byte[0xE];
|
||||
public byte FirmUpdateByte1;
|
||||
public byte FIrmUpdateByte2;
|
||||
|
||||
// For NAND
|
||||
public byte[] Unknown = new byte[0x5E];
|
||||
public byte[] EncryptedMBR = new byte[0x42];
|
||||
/// <summary>
|
||||
/// Partition ID table
|
||||
/// </summary>
|
||||
public byte[][] PartitionIdTable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved?
|
||||
/// </summary>
|
||||
public byte[] Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Support for this was implemented with 9.6.0-X FIRM. Bit0=1 enables using bits 1-2, it's unknown
|
||||
/// what these two bits are actually used for(the value of these two bits get compared with some other
|
||||
/// value during NCSD verification/loading). This appears to enable a new, likely hardware-based,
|
||||
/// antipiracy check on cartridges.
|
||||
/// </summary>
|
||||
public byte FirmUpdateByte1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Support for this was implemented with 9.6.0-X FIRM, see below regarding save crypto.
|
||||
/// </summary>
|
||||
public byte FIrmUpdateByte2 { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region For NAND
|
||||
|
||||
/// <summary>
|
||||
/// Unknown
|
||||
/// </summary>
|
||||
public byte[] Unknown { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Encrypted MBR partition-table, for the TWL partitions(key-data used for this keyslot is console-unique).
|
||||
/// </summary>
|
||||
public byte[] EncryptedMBR { 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>
|
||||
/// <returns>NCSD header object, null on error</returns>
|
||||
public static NCSDHeader Read(BinaryReader reader)
|
||||
{
|
||||
NCSDHeader header = new NCSDHeader();
|
||||
@@ -54,6 +135,7 @@ namespace ThreeDS.Headers
|
||||
header.PartitionsFSType = (FilesystemType)reader.ReadUInt64();
|
||||
header.PartitionsCryptType = reader.ReadBytes(8);
|
||||
|
||||
header.PartitionsTable = new PartitionTableEntry[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
header.PartitionsTable[i] = PartitionTableEntry.Read(reader);
|
||||
|
||||
@@ -65,11 +147,12 @@ namespace ThreeDS.Headers
|
||||
header.SectorZeroOffset = reader.ReadUInt32();
|
||||
header.partitionFlags = reader.ReadBytes(8);
|
||||
|
||||
header.PartitionIdTable = new byte[8][];
|
||||
for (int i = 0; i < 8; i++)
|
||||
header.PartitionIdTable[i] = reader.ReadBytes(8);
|
||||
|
||||
header.ReservedBlock1 = reader.ReadBytes(0x20);
|
||||
header.ReservedBlock2 = reader.ReadBytes(0xE);
|
||||
header.Reserved1 = reader.ReadBytes(0x20);
|
||||
header.Reserved2 = reader.ReadBytes(0xE);
|
||||
header.FirmUpdateByte1 = reader.ReadByte();
|
||||
header.FIrmUpdateByte2 = reader.ReadByte();
|
||||
}
|
||||
|
||||
@@ -4,9 +4,21 @@ namespace ThreeDS.Headers
|
||||
{
|
||||
public class PartitionTableEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Offset
|
||||
/// </summary>
|
||||
public uint Offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Length
|
||||
/// </summary>
|
||||
public uint Length { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get partition table entry, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Partition table entry object, null on error</returns>
|
||||
public static PartitionTableEntry Read(BinaryReader reader)
|
||||
{
|
||||
PartitionTableEntry entry = new PartitionTableEntry();
|
||||
@@ -23,6 +35,10 @@ namespace ThreeDS.Headers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for a valid partition
|
||||
/// </summary>
|
||||
/// <returns>True if the offset and length are not 0, false otherwise</returns>
|
||||
public bool IsValid()
|
||||
{
|
||||
return Offset != 0 && Length != 0;
|
||||
|
||||
127
3DSDecrypt/Headers/RomFSHeader.cs
Normal file
127
3DSDecrypt/Headers/RomFSHeader.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System.IO;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
// https://www.3dbrew.org/wiki/RomFS
|
||||
public class RomFSHeader
|
||||
{
|
||||
private const string RomFSMagicNumber = "IVFC";
|
||||
private const uint RomFSSecondMagicNumber = 0x10000;
|
||||
|
||||
/// <summary>
|
||||
/// Master hash size
|
||||
/// </summary>
|
||||
public uint MasterHashSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 1 logical offset
|
||||
/// </summary>
|
||||
public ulong Level1LogicalOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 1 hashdata size
|
||||
/// </summary>
|
||||
public ulong Level1HashdataSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 1 block size, in log2
|
||||
/// </summary>
|
||||
public uint Level1BlockSizeLog2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 2 logical offset
|
||||
/// </summary>
|
||||
public ulong Level2LogicalOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 2 hashdata size
|
||||
/// </summary>
|
||||
public ulong Level2HashdataSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 2 block size, in log2
|
||||
/// </summary>
|
||||
public uint Level2BlockSizeLog2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 3 logical offset
|
||||
/// </summary>
|
||||
public ulong Level3LogicalOffset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 3 hashdata size
|
||||
/// </summary>
|
||||
public ulong Level3HashdataSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Level 3 block size, in log2
|
||||
/// </summary>
|
||||
public uint Level3BlockSizeLog2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved3 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved4 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional info size.
|
||||
/// </summary>
|
||||
public uint OptionalInfoSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get a RomFS header, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>RomFS header object, null on error</returns>
|
||||
public static RomFSHeader Read(BinaryReader reader)
|
||||
{
|
||||
RomFSHeader header = new RomFSHeader();
|
||||
|
||||
try
|
||||
{
|
||||
if (new string(reader.ReadChars(4)) != RomFSMagicNumber)
|
||||
return null;
|
||||
|
||||
if (reader.ReadUInt32() != RomFSSecondMagicNumber)
|
||||
return null;
|
||||
|
||||
header.MasterHashSize = reader.ReadUInt32();
|
||||
header.Level1LogicalOffset = reader.ReadUInt64();
|
||||
header.Level1HashdataSize = reader.ReadUInt64();
|
||||
header.Level1BlockSizeLog2 = reader.ReadUInt32();
|
||||
header.Reserved1 = reader.ReadBytes(4);
|
||||
header.Level2LogicalOffset = reader.ReadUInt64();
|
||||
header.Level2HashdataSize = reader.ReadUInt64();
|
||||
header.Level2BlockSizeLog2 = reader.ReadUInt32();
|
||||
header.Reserved2 = reader.ReadBytes(4);
|
||||
header.Level3LogicalOffset = reader.ReadUInt64();
|
||||
header.Level3HashdataSize = reader.ReadUInt64();
|
||||
header.Level3BlockSizeLog2 = reader.ReadUInt32();
|
||||
header.Reserved3 = reader.ReadBytes(4);
|
||||
header.Reserved4 = reader.ReadBytes(4);
|
||||
header.OptionalInfoSize = reader.ReadUInt32();
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,40 @@
|
||||
using System.IO;
|
||||
using ThreeDS.Data;
|
||||
|
||||
namespace ThreeDS.Headers
|
||||
{
|
||||
public class StorageInfo
|
||||
{
|
||||
public byte[] ExtdataID = new byte[8];
|
||||
public byte[] SystemSavedataIDs = new byte[8];
|
||||
public byte[] StorageAccessibleUniqueIDs = new byte[8];
|
||||
public byte[] FilesystemAccessInfo = new byte[7];
|
||||
public byte OtherAttributes;
|
||||
/// <summary>
|
||||
/// Extdata ID
|
||||
/// </summary>
|
||||
public byte[] ExtdataID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// System savedata IDs
|
||||
/// </summary>
|
||||
public byte[] SystemSavedataIDs { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Storage accessible unique IDs
|
||||
/// </summary>
|
||||
public byte[] StorageAccessibleUniqueIDs { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Filesystem access info
|
||||
/// </summary>
|
||||
public byte[] FilesystemAccessInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Other attributes
|
||||
/// </summary>
|
||||
public StorageInfoOtherAttributes OtherAttributes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get storage info, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>Storage info object, null on error</returns>
|
||||
public static StorageInfo Read(BinaryReader reader)
|
||||
{
|
||||
StorageInfo si = new StorageInfo();
|
||||
@@ -20,7 +45,7 @@ namespace ThreeDS.Headers
|
||||
si.SystemSavedataIDs = reader.ReadBytes(8);
|
||||
si.StorageAccessibleUniqueIDs = reader.ReadBytes(8);
|
||||
si.FilesystemAccessInfo = reader.ReadBytes(7);
|
||||
si.OtherAttributes = reader.ReadByte();
|
||||
si.OtherAttributes = (StorageInfoOtherAttributes)reader.ReadByte();
|
||||
return si;
|
||||
}
|
||||
catch
|
||||
|
||||
@@ -4,19 +4,71 @@ namespace ThreeDS.Headers
|
||||
{
|
||||
public class SystemControlInfo
|
||||
{
|
||||
public char[] ApplicationTitle = new char[8];
|
||||
public byte[] Reserved1 = new byte[5];
|
||||
public byte Flag;
|
||||
public byte[] RemasterVersion = new byte[2];
|
||||
public CodeSetInfo TextCodesetInfo;
|
||||
public uint StackSize;
|
||||
public CodeSetInfo ReadOnlyCodeSetInfo;
|
||||
public byte[] Reserved2 = new byte[4];
|
||||
public CodeSetInfo DataCodeSetInfo;
|
||||
public uint BSSSize;
|
||||
public byte[][] DependencyModuleList = new byte[48][];
|
||||
public SystemInfo SystemInfo;
|
||||
/// <summary>
|
||||
/// Application title (default is "CtrApp")
|
||||
/// </summary>
|
||||
public char[] ApplicationTitle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved1 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag (bit 0: CompressExefsCode, bit 1: SDApplication)
|
||||
/// </summary>
|
||||
public byte Flag { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Remaster version
|
||||
/// </summary>
|
||||
public byte[] RemasterVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Text code set info
|
||||
/// </summary>
|
||||
public CodeSetInfo TextCodesetInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stack size
|
||||
/// </summary>
|
||||
public uint StackSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read-only code set info
|
||||
/// </summary>
|
||||
public CodeSetInfo ReadOnlyCodeSetInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Data code set info
|
||||
/// </summary>
|
||||
public CodeSetInfo DataCodeSetInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// BSS size
|
||||
/// </summary>
|
||||
public uint BSSSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dependency module (program ID) list
|
||||
/// </summary>
|
||||
public byte[][] DependencyModuleList { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SystemInfo
|
||||
/// </summary>
|
||||
public SystemInfo SystemInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get system control info, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>System control info object, null on error</returns>
|
||||
public static SystemControlInfo Read(BinaryReader reader)
|
||||
{
|
||||
SystemControlInfo sci = new SystemControlInfo();
|
||||
@@ -34,6 +86,7 @@ namespace ThreeDS.Headers
|
||||
sci.DataCodeSetInfo = CodeSetInfo.Read(reader);
|
||||
sci.BSSSize = reader.ReadUInt32();
|
||||
|
||||
sci.DependencyModuleList = new byte[48][];
|
||||
for (int i = 0; i < 48; i++)
|
||||
sci.DependencyModuleList[i] = reader.ReadBytes(8);
|
||||
|
||||
|
||||
@@ -4,10 +4,26 @@ namespace ThreeDS.Headers
|
||||
{
|
||||
public class SystemInfo
|
||||
{
|
||||
public ulong SaveDataSize;
|
||||
public byte[] JumpID = new byte[8];
|
||||
public byte[] Reserved = new byte[0x30];
|
||||
/// <summary>
|
||||
/// SaveData Size
|
||||
/// </summary>
|
||||
public ulong SaveDataSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Jump ID
|
||||
/// </summary>
|
||||
public byte[] JumpID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved
|
||||
/// </summary>
|
||||
public byte[] Reserved { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read from a stream and get system info, if possible
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <returns>System info object, null on error</returns>
|
||||
public static SystemInfo Read(BinaryReader reader)
|
||||
{
|
||||
SystemInfo si = new SystemInfo();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Security;
|
||||
using ThreeDS.Data;
|
||||
@@ -11,15 +12,50 @@ namespace ThreeDS
|
||||
{
|
||||
public class ThreeDSTool
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the input 3DS file
|
||||
/// </summary>
|
||||
private readonly string filename;
|
||||
|
||||
/// <summary>
|
||||
/// Flag to detrmine if development keys should be used
|
||||
/// </summary>
|
||||
private readonly bool development;
|
||||
|
||||
/// <summary>
|
||||
/// Boot rom key
|
||||
/// </summary>
|
||||
private BigInteger KeyX;
|
||||
|
||||
/// <summary>
|
||||
/// NCCH boot rom key
|
||||
/// </summary>
|
||||
private BigInteger KeyX2C;
|
||||
|
||||
/// <summary>
|
||||
/// Kernel9/Process9 key
|
||||
/// </summary>
|
||||
private BigInteger KeyY;
|
||||
|
||||
/// <summary>
|
||||
/// Normal AES key
|
||||
/// </summary>
|
||||
private BigInteger NormalKey;
|
||||
|
||||
/// <summary>
|
||||
/// NCCH AES key
|
||||
/// </summary>
|
||||
private BigInteger NormalKey2C;
|
||||
|
||||
public ThreeDSTool(string filename, bool development)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.development = development;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to decrypt a 3DS file
|
||||
/// </summary>
|
||||
public void Decrypt()
|
||||
{
|
||||
if (!File.Exists(filename))
|
||||
@@ -42,7 +78,7 @@ namespace ThreeDS
|
||||
{
|
||||
if (!header.PartitionsTable[p].IsValid())
|
||||
{
|
||||
Console.WriteLine("Partition {0} Not found... Skipping...", p);
|
||||
Console.WriteLine($"Partition {p} Not found... Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -52,211 +88,24 @@ namespace ThreeDS
|
||||
NCCHHeader partitionHeader = NCCHHeader.Read(f);
|
||||
if (partitionHeader == null)
|
||||
{
|
||||
Console.WriteLine("Partition {0} Unable to read NCCH header", p);
|
||||
Console.WriteLine($"Partition {p} Unable to read NCCH header");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the 'NoCrypto' bit is set
|
||||
if ((partitionHeader.Flags.BitMasks & BitMasks.NoCrypto) != 0)
|
||||
{
|
||||
Console.WriteLine("Partition {0:d}: Already Decrypted?...", p);
|
||||
Console.WriteLine($"Partition {p}: Already Decrypted?...");
|
||||
continue;
|
||||
}
|
||||
|
||||
// PartitionID is used as IV joined with the content type.
|
||||
byte[] plainIV = partitionHeader.PartitionId.Concat(Constants.PlainCounter).ToArray(); // Get the IV for plain sector (TitleID + Plain Counter)
|
||||
byte[] exefsIV = partitionHeader.PartitionId.Concat(Constants.ExefsCounter).ToArray(); // Get the IV for ExeFS (TitleID + ExeFS Counter)
|
||||
byte[] romfsIV = partitionHeader.PartitionId.Concat(Constants.RomfsCounter).ToArray(); // Get the IV for RomFS (TitleID + RomFS Counter)
|
||||
|
||||
BigInteger KeyX = 0;
|
||||
BigInteger KeyX2C = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
BigInteger KeyY = new BigInteger(partitionHeader.RSA2048Signature.Take(16).Reverse().ToArray()); // KeyY is the first 16 bytes of the partition RSA-2048 SHA-256 signature
|
||||
|
||||
BigInteger NormalKey = 0;
|
||||
BigInteger NormalKey2C = RotateLeft((RotateLeft(KeyX2C, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
|
||||
// Determine the Keys to be used
|
||||
if ((partitionHeader.Flags.BitMasks & BitMasks.FixedCryptoKey) != 0)
|
||||
{
|
||||
NormalKey = 0x00;
|
||||
NormalKey2C = 0x00;
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Zero Key");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (partitionHeader.Flags.CryptoMethod == CryptoMethod.Original)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x2C");
|
||||
}
|
||||
else if (partitionHeader.Flags.CryptoMethod == CryptoMethod.Seven)
|
||||
{
|
||||
KeyX = (development ? Constants.KeyX0x25 : Constants.KeyX0x25);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x25");
|
||||
}
|
||||
else if (partitionHeader.Flags.CryptoMethod == CryptoMethod.NineThree)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x18 : Constants.KeyX0x18);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x18");
|
||||
}
|
||||
else if (partitionHeader.Flags.CryptoMethod == CryptoMethod.NineSix)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x1B : Constants.KeyX0x1B);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x1B");
|
||||
}
|
||||
SetEncryptionKeys(partitionHeader.RSA2048Signature, partitionHeader.Flags.BitMasks, partitionHeader.Flags.CryptoMethod, p);
|
||||
|
||||
NormalKey = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
|
||||
// Decrypted extended header, if it exists
|
||||
if (partitionHeader.ExtendedHeaderSizeInBytes > 0)
|
||||
{
|
||||
// Seek to the partition start and skip first part of the header
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin);
|
||||
|
||||
var str = BitConverter.ToString(plainIV).Replace("-", "");
|
||||
|
||||
var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR");
|
||||
exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), plainIV));
|
||||
|
||||
Console.WriteLine("Partition {0} ExeFS: Decrypting: ExHeader", p);
|
||||
|
||||
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(Constants.CXTExtendedDataHeaderLength)));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
// Decrypt the ExeFS, if it exists
|
||||
if (partitionHeader.ExeFSSizeInBytes > 0)
|
||||
{
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
|
||||
var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR");
|
||||
exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIV));
|
||||
|
||||
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes((int)header.SectorSize)));
|
||||
g.Flush();
|
||||
|
||||
Console.WriteLine("Partition {0} ExeFS: Decrypting: ExeFS Filename Table", p);
|
||||
|
||||
if (partitionHeader.Flags.CryptoMethod != CryptoMethod.Original)
|
||||
{
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
ExeFSHeader exefsHeader = ExeFSHeader.Read(f);
|
||||
if (exefsHeader != null)
|
||||
{
|
||||
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders)
|
||||
{
|
||||
if (!fileHeader.IsCodeBinary)
|
||||
continue;
|
||||
|
||||
uint datalenM = ((fileHeader.FileSize) / (1024 * 1024));
|
||||
uint datalenB = ((fileHeader.FileSize) % (1024 * 1024));
|
||||
uint ctroffset = ((fileHeader.FileOffset + header.SectorSize) / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffsetForHeader = AddToByteArray(exefsIV, (int)ctroffset);
|
||||
|
||||
var exefsctrmode = CipherUtilities.GetCipher("AES/CTR");
|
||||
exefsctrmode.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), exefsIVWithOffsetForHeader));
|
||||
|
||||
exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR");
|
||||
exefsctrmode2C.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffsetForHeader));
|
||||
|
||||
f.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
if (datalenM > 0)
|
||||
{
|
||||
for (int i = 0; i < datalenM; i++)
|
||||
{
|
||||
g.Write(exefsctrmode2C.ProcessBytes(exefsctrmode.ProcessBytes(f.ReadBytes(1024 * 1024))));
|
||||
g.Flush();
|
||||
Console.Write("\rPartition {0} ExeFS: Decrypting: {1}... {2} / {3} mb...", p, fileHeader.ReadableFileName, i, datalenM + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (datalenB > 0)
|
||||
{
|
||||
g.Write(exefsctrmode2C.DoFinal(exefsctrmode.DoFinal(f.ReadBytes((int)datalenB))));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
Console.Write("\rPartition {0} ExeFS: Decrypting: {1}... {2} / {3} mb... Done!\r\n", p, fileHeader.ReadableFileName, datalenM + 1, datalenM + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// decrypt 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);
|
||||
|
||||
byte[] exefsIVWithOffset = AddToByteArray(exefsIV, ctroffsetE);
|
||||
|
||||
exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR");
|
||||
exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffset));
|
||||
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin);
|
||||
if (exefsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < exefsSizeM; i++)
|
||||
{
|
||||
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(1024 * 1024)));
|
||||
g.Flush();
|
||||
Console.Write("\rPartition {0} ExeFS: Decrypting: {1} / {2} mb", p, i, exefsSizeM + 1);
|
||||
}
|
||||
}
|
||||
if (exefsSizeB > 0)
|
||||
{
|
||||
g.Write(exefsctrmode2C.DoFinal(f.ReadBytes(exefsSizeB)));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
Console.Write("\rPartition {0} ExeFS: Decrypting: {1} / {2} mb... Done!\r\n", p, exefsSizeM + 1, exefsSizeM + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Partition {0} ExeFS: No Data... Skipping...", p);
|
||||
}
|
||||
|
||||
if (partitionHeader.RomFSOffsetInMediaUnits != 0)
|
||||
{
|
||||
int romfsSizeM = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (1024 * 1024);
|
||||
int romfsSizeB = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) % (1024 * 1024);
|
||||
|
||||
var romfsctrmode = CipherUtilities.GetCipher("AES/CTR");
|
||||
romfsctrmode.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), romfsIV));
|
||||
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
if (romfsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < romfsSizeM; i++)
|
||||
{
|
||||
g.Write(romfsctrmode.ProcessBytes(f.ReadBytes(1024 * 1024)));
|
||||
g.Flush();
|
||||
Console.Write("\rPartition {0} RomFS: Decrypting: {1} / {2} mb", p, i, romfsSizeM + 1);
|
||||
}
|
||||
}
|
||||
if (romfsSizeB > 0)
|
||||
{
|
||||
g.Write(romfsctrmode.DoFinal(f.ReadBytes(romfsSizeB)));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
Console.Write("\rPartition {0} RomFS: Decrypting: {1} / {2} mb... Done!\r\n", p, romfsSizeM + 1, romfsSizeM + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Partition {0} RomFS: No Data... Skipping...", p);
|
||||
}
|
||||
// Decrypt each of the pieces if they exist
|
||||
ProcessExtendedHeader(f, g, header, p, partitionHeader, false);
|
||||
ProcessExeFS(f, g, header, p, partitionHeader, false);
|
||||
ProcessRomFS(f, g, header, p, partitionHeader, false);
|
||||
|
||||
// Write the new CryptoMethod
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18B, SeekOrigin.Begin);
|
||||
@@ -277,6 +126,9 @@ namespace ThreeDS
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to encrypt a 3DS file
|
||||
/// </summary>
|
||||
public void Encrypt()
|
||||
{
|
||||
if (!File.Exists(filename))
|
||||
@@ -294,12 +146,16 @@ namespace ThreeDS
|
||||
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++)
|
||||
{
|
||||
if (!header.PartitionsTable[p].IsValid())
|
||||
{
|
||||
Console.WriteLine("Partition {0} Not found... Skipping...", p);
|
||||
Console.WriteLine($"Partition {p} Not found... Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -309,231 +165,24 @@ namespace ThreeDS
|
||||
NCCHHeader partitionHeader = NCCHHeader.Read(f);
|
||||
if (partitionHeader == null)
|
||||
{
|
||||
Console.WriteLine("Partition {0} Unable to read NCCH header", p);
|
||||
Console.WriteLine($"Partition {p} Unable to read NCCH header");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the backup flags
|
||||
f.BaseStream.Seek(0x1188, SeekOrigin.Begin);
|
||||
NCCHHeaderFlags backupFlags = NCCHHeaderFlags.Read(f);
|
||||
|
||||
// Check if the 'NoCrypto' bit is not set
|
||||
if ((partitionHeader.Flags.BitMasks & BitMasks.NoCrypto) == 0)
|
||||
{
|
||||
Console.WriteLine("Partition {0:d}: Already Encrypted?...", p);
|
||||
Console.WriteLine($"Partition {p}: Already Encrypted?...");
|
||||
continue;
|
||||
}
|
||||
|
||||
// PartitionID is used as IV joined with the content type.
|
||||
byte[] plainIV = partitionHeader.PartitionId.Concat(Constants.PlainCounter).ToArray(); // Get the IV for plain sector (TitleID + Plain Counter)
|
||||
byte[] exefsIV = partitionHeader.PartitionId.Concat(Constants.ExefsCounter).ToArray(); // Get the IV for ExeFS (TitleID + ExeFS Counter)
|
||||
byte[] romfsIV = partitionHeader.PartitionId.Concat(Constants.RomfsCounter).ToArray(); // Get the IV for RomFS (TitleID + RomFS Counter)
|
||||
|
||||
BigInteger KeyX = 0;
|
||||
BigInteger KeyX2C = Constants.KeyX0x2C;
|
||||
BigInteger KeyY = new BigInteger(partitionHeader.RSA2048Signature.Take(16).Reverse().ToArray()); // KeyY is the first 16 bytes of the partition RSA-2048 SHA-256 signature
|
||||
|
||||
BigInteger NormalKey = 0;
|
||||
BigInteger NormalKey2C = RotateLeft((RotateLeft(KeyX2C, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
|
||||
// Determine the Keys to be used
|
||||
if ((backupFlags.BitMasks & BitMasks.FixedCryptoKey) != 0)
|
||||
{
|
||||
NormalKey = 0x00;
|
||||
NormalKey2C = 0x00;
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Zero Key");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (backupFlags.CryptoMethod == CryptoMethod.Original)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x2C");
|
||||
}
|
||||
else if (backupFlags.CryptoMethod == CryptoMethod.Seven)
|
||||
{
|
||||
KeyX = (development ? Constants.KeyX0x25 : Constants.KeyX0x25);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x25");
|
||||
}
|
||||
else if (backupFlags.CryptoMethod == CryptoMethod.NineThree)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x18 : Constants.KeyX0x18);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x18");
|
||||
}
|
||||
else if (backupFlags.CryptoMethod == CryptoMethod.NineSix)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x1B : Constants.KeyX0x1B);
|
||||
if (p == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x1B");
|
||||
}
|
||||
SetEncryptionKeys(partitionHeader.RSA2048Signature, backupFlags.BitMasks, backupFlags.CryptoMethod, p);
|
||||
|
||||
NormalKey = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
|
||||
// Encrypt extended header, if it exists
|
||||
if (partitionHeader.ExtendedHeaderSizeInBytes > 0)
|
||||
{
|
||||
// Seek to the partition start and skip first part of the header
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x200, SeekOrigin.Begin);
|
||||
|
||||
var str = BitConverter.ToString(plainIV).Replace("-", "");
|
||||
|
||||
var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR");
|
||||
exefsctrmode2C.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), plainIV));
|
||||
|
||||
Console.WriteLine("Partition {0} ExeFS: Encrypting: ExHeader", p);
|
||||
|
||||
g.Write(exefsctrmode2C.ProcessBytes(f.ReadBytes(Constants.CXTExtendedDataHeaderLength)));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
// Encrypt the ExeFS, if it exists
|
||||
if (partitionHeader.ExeFSSizeInBytes > 0)
|
||||
{
|
||||
if (backupFlags.CryptoMethod != CryptoMethod.Original)
|
||||
{
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
ExeFSHeader exefsHeader = ExeFSHeader.Read(f);
|
||||
if (exefsHeader != null)
|
||||
{
|
||||
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders)
|
||||
{
|
||||
if (!fileHeader.IsCodeBinary)
|
||||
continue;
|
||||
|
||||
uint datalenM = ((fileHeader.FileSize) / (1024 * 1024));
|
||||
uint datalenB = ((fileHeader.FileSize) % (1024 * 1024));
|
||||
uint ctroffset = ((fileHeader.FileOffset + header.SectorSize) / 0x10);
|
||||
|
||||
byte[] exefsIVWithOffsetForHeader = AddToByteArray(exefsIV, (int)ctroffset);
|
||||
|
||||
var exefsctrmode = CipherUtilities.GetCipher("AES/CTR");
|
||||
exefsctrmode.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), exefsIVWithOffsetForHeader));
|
||||
|
||||
var exefsctrmode2C = CipherUtilities.GetCipher("AES/CTR");
|
||||
exefsctrmode2C.Init(false, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffsetForHeader));
|
||||
|
||||
f.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) + 1) * header.SectorSize) + fileHeader.FileOffset, SeekOrigin.Begin);
|
||||
|
||||
if (datalenM > 0)
|
||||
{
|
||||
for (int i = 0; i < datalenM; i++)
|
||||
{
|
||||
g.Write(exefsctrmode2C.ProcessBytes(exefsctrmode.ProcessBytes(f.ReadBytes(1024 * 1024))));
|
||||
g.Flush();
|
||||
Console.Write("\rPartition {0} ExeFS: Encrypting: {1}... {2} / {3} mb...", p, fileHeader.ReadableFileName, i, datalenM + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (datalenB > 0)
|
||||
{
|
||||
g.Write(exefsctrmode2C.DoFinal(exefsctrmode.DoFinal(f.ReadBytes((int)datalenB))));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
Console.Write("\rPartition {0} ExeFS: Encrypting: {1}... {2} / {3} mb... Done!\r\n", p, fileHeader.ReadableFileName, datalenM + 1, datalenM + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// encrypt exefs filename table
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
|
||||
var exefsctrmode2C_2 = CipherUtilities.GetCipher("AES/CTR");
|
||||
exefsctrmode2C_2.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIV));
|
||||
|
||||
g.Write(exefsctrmode2C_2.ProcessBytes(f.ReadBytes((int)header.SectorSize)));
|
||||
g.Flush();
|
||||
|
||||
Console.WriteLine("Partition {0} ExeFS: Encrypting: ExeFS Filename Table", p);
|
||||
|
||||
// encrypt 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);
|
||||
|
||||
byte[] exefsIVWithOffset = AddToByteArray(exefsIV, ctroffsetE);
|
||||
|
||||
exefsctrmode2C_2 = CipherUtilities.GetCipher("AES/CTR");
|
||||
exefsctrmode2C_2.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey2C)), exefsIVWithOffset));
|
||||
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.ExeFSOffsetInMediaUnits + 1) * header.SectorSize, SeekOrigin.Begin);
|
||||
if (exefsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < exefsSizeM; i++)
|
||||
{
|
||||
g.Write(exefsctrmode2C_2.ProcessBytes(f.ReadBytes(1024 * 1024)));
|
||||
g.Flush();
|
||||
Console.Write("\rPartition {0} ExeFS: Encrypting: {1} / {2} mb", p, i, exefsSizeM + 1);
|
||||
}
|
||||
}
|
||||
if (exefsSizeB > 0)
|
||||
{
|
||||
g.Write(exefsctrmode2C_2.DoFinal(f.ReadBytes(exefsSizeB)));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
Console.Write("\rPartition {0} ExeFS: Encrypting: {1} / {2} mb... Done!\r\n", p, exefsSizeM + 1, exefsSizeM + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Partition {0} ExeFS: No Data... Skipping...", p);
|
||||
}
|
||||
|
||||
if (partitionHeader.RomFSOffsetInMediaUnits != 0)
|
||||
{
|
||||
int romfsBlockSize = 16; // block size in mb
|
||||
int romfsSizeM = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (romfsBlockSize * (1024 * 1024));
|
||||
int romfsSizeB = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) % (romfsBlockSize * (1024 * 1024));
|
||||
int romfsSizeTotalMb = (int)((partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (1024 * 1024) + 1);
|
||||
|
||||
if (p > 0) // RomFS for partitions 1 and up always use Key0x2C
|
||||
{
|
||||
if ((backupFlags.BitMasks & BitMasks.FixedCryptoKey) != 0) // except if using zero-key
|
||||
{
|
||||
NormalKey = 0x00;
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyX = KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
NormalKey = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
}
|
||||
|
||||
var romfsctrmode = CipherUtilities.GetCipher("AES/CTR");
|
||||
romfsctrmode.Init(true, new ParametersWithIV(new KeyParameter(TakeSixteen(NormalKey)), romfsIV));
|
||||
|
||||
f.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset + partitionHeader.RomFSOffsetInMediaUnits) * header.SectorSize, SeekOrigin.Begin);
|
||||
if (romfsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < romfsSizeM; i++)
|
||||
{
|
||||
g.Write(romfsctrmode.ProcessBytes(f.ReadBytes(romfsBlockSize * 1024 * 1024)));
|
||||
g.Flush();
|
||||
Console.Write("\rPartition {0} RomFS: Encrypting: {1} / {2} mb", p, i * romfsBlockSize, romfsSizeTotalMb);
|
||||
}
|
||||
}
|
||||
if (romfsSizeB > 0)
|
||||
{
|
||||
g.Write(romfsctrmode.DoFinal(f.ReadBytes(romfsSizeB)));
|
||||
g.Flush();
|
||||
}
|
||||
|
||||
Console.Write("\rPartition {0} RomFS: Encrypting: {1} / {2} mb... Done!\r\n", p, romfsSizeTotalMb, romfsSizeTotalMb);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Partition {0} RomFS: No Data... Skipping...", 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);
|
||||
|
||||
// Write the new CryptoMethod
|
||||
g.BaseStream.Seek((header.PartitionsTable[p].Offset * header.SectorSize) + 0x18B, SeekOrigin.Begin);
|
||||
@@ -562,24 +211,79 @@ namespace ThreeDS
|
||||
}
|
||||
}
|
||||
|
||||
private static BigInteger RotateLeft(BigInteger val, int r_bits, int max_bits)
|
||||
/// <summary>
|
||||
/// Perform a rotate left on a BigInteger
|
||||
/// </summary>
|
||||
/// <param name="val">BigInteger value to rotate</param>
|
||||
/// <param name="r_bits">Number of bits to rotate</param>
|
||||
/// <param name="max_bits">Maximum number of bits to rotate on</param>
|
||||
/// <returns>Rotated BigInteger value</returns>
|
||||
private BigInteger RotateLeft(BigInteger val, int r_bits, int max_bits)
|
||||
{
|
||||
return (val << r_bits % max_bits) & (BigInteger.Pow(2, max_bits) - 1) | ((val & (BigInteger.Pow(2, max_bits) - 1)) >> (max_bits - (r_bits % max_bits)));
|
||||
}
|
||||
|
||||
private static string ToBytes(int num)
|
||||
/// <summary>
|
||||
/// Determine the set of keys to be used for encryption or decryption
|
||||
/// </summary>
|
||||
/// <param name="rsaSignature">RSA-2048 signature from a partition header</param>
|
||||
/// <param name="masks">BitMasks value for a partition header or backup header</param>
|
||||
/// <param name="method">CryptoMethod used for the partition</param>
|
||||
/// <param name="partitionNumber">Partition number, only used for logging</param>
|
||||
private void SetEncryptionKeys(byte[] rsaSignature, BitMasks masks, CryptoMethod method, int partitionNumber)
|
||||
{
|
||||
string numstr = string.Empty;
|
||||
while (numstr.Length < 16)
|
||||
{
|
||||
numstr += (char)(num & 0xFF);
|
||||
num >>= 8;
|
||||
}
|
||||
KeyX = 0;
|
||||
KeyX2C = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
KeyY = new BigInteger(rsaSignature.Take(16).Reverse().ToArray()); // KeyY is the first 16 bytes of the partition RSA-2048 SHA-256 signature
|
||||
|
||||
return numstr;
|
||||
NormalKey = 0;
|
||||
NormalKey2C = RotateLeft((RotateLeft(KeyX2C, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
|
||||
if ((masks & BitMasks.FixedCryptoKey) != 0)
|
||||
{
|
||||
NormalKey = 0x00;
|
||||
NormalKey2C = 0x00;
|
||||
if (partitionNumber == 0)
|
||||
Console.WriteLine("Encryption Method: Zero Key");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (method == CryptoMethod.Original)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
if (partitionNumber == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x2C");
|
||||
}
|
||||
else if (method == CryptoMethod.Seven)
|
||||
{
|
||||
KeyX = (development ? Constants.KeyX0x25 : Constants.KeyX0x25);
|
||||
if (partitionNumber == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x25");
|
||||
}
|
||||
else if (method == CryptoMethod.NineThree)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x18 : Constants.KeyX0x18);
|
||||
if (partitionNumber == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x18");
|
||||
}
|
||||
else if (method == CryptoMethod.NineSix)
|
||||
{
|
||||
KeyX = (development ? Constants.DevKeyX0x1B : Constants.KeyX0x1B);
|
||||
if (partitionNumber == 0)
|
||||
Console.WriteLine("Encryption Method: Key 0x1B");
|
||||
}
|
||||
|
||||
NormalKey = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] AddToByteArray(byte[] input, int add)
|
||||
/// <summary>
|
||||
/// Add an integer value to a number represented by a byte array
|
||||
/// </summary>
|
||||
/// <param name="input">Byte array to add to</param>
|
||||
/// <param name="add">Amount to add</param>
|
||||
/// <returns>Byte array representing the new value</returns>
|
||||
private byte[] AddToByteArray(byte[] input, int add)
|
||||
{
|
||||
int len = input.Length;
|
||||
var bigint = new BigInteger(input.Reverse().ToArray());
|
||||
@@ -599,7 +303,26 @@ namespace ThreeDS
|
||||
return arr;
|
||||
}
|
||||
|
||||
private static byte[] TakeSixteen(BigInteger input)
|
||||
/// <summary>
|
||||
/// Create AES cipher and intialize
|
||||
/// </summary>
|
||||
/// <param name="key">BigInteger representation of 128-bit encryption key</param>
|
||||
/// <param name="iv">AES initial value for counter</param>
|
||||
/// <param name="encrypt">True if cipher is created for encryption, false otherwise</param>
|
||||
/// <returns>Initialized AES cipher</returns>
|
||||
private IBufferedCipher CreateAESCipher(BigInteger key, byte[] iv, bool encrypt)
|
||||
{
|
||||
var cipher = CipherUtilities.GetCipher("AES/CTR");
|
||||
cipher.Init(encrypt, new ParametersWithIV(new KeyParameter(TakeSixteen(key)), iv));
|
||||
return cipher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a 16-byte array representation of a BigInteger
|
||||
/// </summary>
|
||||
/// <param name="input">BigInteger value to convert</param>
|
||||
/// <returns>16-byte array representing the BigInteger</returns>
|
||||
private byte[] TakeSixteen(BigInteger input)
|
||||
{
|
||||
var arr = input.ToByteArray().Take(16).Reverse().ToArray();
|
||||
|
||||
@@ -615,5 +338,220 @@ namespace ThreeDS
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the extended header, if it exists
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// <param name="header">File header</param>
|
||||
/// <param name="partitionNumber">Partition number for logging</param>
|
||||
/// <param name="partitionHeader">Partition header</param>
|
||||
/// <param name="encrypt">True if we want to encrypt the extended header, false otherwise</param>
|
||||
private void ProcessExtendedHeader(BinaryReader reader, BinaryWriter writer, NCSDHeader header, int partitionNumber, NCCHHeader partitionHeader, bool encrypt)
|
||||
{
|
||||
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);
|
||||
|
||||
Console.WriteLine($"Partition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + ": ExHeader");
|
||||
|
||||
var cipher = CreateAESCipher(NormalKey2C, partitionHeader.PlainIV, encrypt);
|
||||
writer.Write(cipher.ProcessBytes(reader.ReadBytes(Constants.CXTExtendedDataHeaderLength)));
|
||||
writer.Flush();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Partition {partitionNumber} ExeFS: No Extended Header... Skipping...");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the ExeFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// <param name="header">File header</param>
|
||||
/// <param name="partitionNumber">Partition number for logging</param>
|
||||
/// <param name="partitionHeader">Partition header</param>
|
||||
/// <param name="encrypt">True if we want to encrypt the extended header, false otherwise</param>
|
||||
private void ProcessExeFS(BinaryReader reader, BinaryWriter writer, NCSDHeader header, int partitionNumber, NCCHHeader partitionHeader, bool encrypt)
|
||||
{
|
||||
if (partitionHeader.ExeFSSizeInMediaUnits > 0)
|
||||
{
|
||||
// If we're decrypting, we need to decrypt the filename table first
|
||||
if (!encrypt)
|
||||
ProcessExeFSFilenameTable(reader, writer, header, partitionNumber, partitionHeader, encrypt);
|
||||
|
||||
// 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);
|
||||
ExeFSHeader exefsHeader = ExeFSHeader.Read(reader);
|
||||
if (exefsHeader != null)
|
||||
{
|
||||
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders)
|
||||
{
|
||||
// Only decrypt a file if it's a code binary
|
||||
if (!fileHeader.IsCodeBinary)
|
||||
continue;
|
||||
|
||||
uint datalenM = ((fileHeader.FileSize) / (1024 * 1024));
|
||||
uint datalenB = ((fileHeader.FileSize) % (1024 * 1024));
|
||||
uint ctroffset = ((fileHeader.FileOffset + header.SectorSize) / 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);
|
||||
|
||||
if (datalenM > 0)
|
||||
{
|
||||
for (int i = 0; i < datalenM; i++)
|
||||
{
|
||||
writer.Write(secondCipher.ProcessBytes(firstCipher.ProcessBytes(reader.ReadBytes(1024 * 1024))));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {i} / {datalenM + 1} mb...");
|
||||
}
|
||||
}
|
||||
|
||||
if (datalenB > 0)
|
||||
{
|
||||
writer.Write(secondCipher.DoFinal(firstCipher.DoFinal(reader.ReadBytes((int)datalenB))));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {fileHeader.ReadableFileName}... {datalenM + 1} / {datalenM + 1} mb... Done!\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're encrypting, we need to encrypt the filename table now
|
||||
if (encrypt)
|
||||
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);
|
||||
|
||||
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);
|
||||
if (exefsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < exefsSizeM; i++)
|
||||
{
|
||||
writer.Write(exeFS.ProcessBytes(reader.ReadBytes(1024 * 1024)));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {i} / {exefsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (exefsSizeB > 0)
|
||||
{
|
||||
writer.Write(exeFS.DoFinal(reader.ReadBytes(exefsSizeB)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {partitionNumber} ExeFS: " + (encrypt ? "Encrypting" : "Decrypting") + $": {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Partition {partitionNumber} ExeFS: No Data... Skipping...");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the ExeFS Filename Table
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// <param name="header">File header</param>
|
||||
/// <param name="partitionNumber">Partition number for logging</param>
|
||||
/// <param name="partitionHeader">Partition header</param>
|
||||
/// <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);
|
||||
|
||||
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.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the RomFS, if it exists
|
||||
/// </summary>
|
||||
/// <param name="reader">BinaryReader representing the input stream</param>
|
||||
/// <param name="writer">BinaryWriter representing the output stream</param>
|
||||
/// <param name="header">File header</param>
|
||||
/// <param name="partitionNumber">Partition number for logging</param>
|
||||
/// <param name="partitionHeader">Partition header</param>
|
||||
/// <param name="encrypt">True if we want to encrypt the extended header, false otherwise</param>
|
||||
/// <param name="backupFlags">Optional backup flags, only used for encrypt</param>
|
||||
private void ProcessRomFS(BinaryReader reader, BinaryWriter writer, NCSDHeader header, int partitionNumber, NCCHHeader partitionHeader, bool encrypt, NCCHHeaderFlags backupFlags = null)
|
||||
{
|
||||
if (partitionHeader.RomFSOffsetInMediaUnits != 0)
|
||||
{
|
||||
int romfsSizeM = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) / (1024 * 1024);
|
||||
int romfsSizeB = (int)(partitionHeader.RomFSSizeInMediaUnits * header.SectorSize) % (1024 * 1024);
|
||||
|
||||
// Encrypting RomFS for partitions 1 and up always use Key0x2C
|
||||
if (encrypt && partitionNumber > 0)
|
||||
{
|
||||
// If the backup flags aren't provided and we're encrypting, assume defaults
|
||||
if (backupFlags == null)
|
||||
{
|
||||
KeyX = KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
NormalKey = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
|
||||
if ((backupFlags.BitMasks & BitMasks.FixedCryptoKey) != 0) // except if using zero-key
|
||||
{
|
||||
NormalKey = 0x00;
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyX = KeyX = (development ? Constants.DevKeyX0x2C : Constants.KeyX0x2C);
|
||||
NormalKey = RotateLeft((RotateLeft(KeyX, 2, 128) ^ KeyY) + Constants.AESHardwareConstant, 87, 128);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (romfsSizeM > 0)
|
||||
{
|
||||
for (int i = 0; i < romfsSizeM; i++)
|
||||
{
|
||||
writer.Write(cipher.ProcessBytes(reader.ReadBytes(1024 * 1024)));
|
||||
writer.Flush();
|
||||
Console.Write($"\rPartition {partitionNumber} RomFS: Decrypting: {i} / {romfsSizeM + 1} mb");
|
||||
}
|
||||
}
|
||||
if (romfsSizeB > 0)
|
||||
{
|
||||
writer.Write(cipher.DoFinal(reader.ReadBytes(romfsSizeB)));
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
Console.Write($"\rPartition {partitionNumber} RomFS: Decrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done!\r\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Partition {partitionNumber} RomFS: No Data... Skipping...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user