public NCCHRegion(NCCHSection type, long offset, long size, long tid = 0) { this.Type = type; this.Offset = offset; this.Size = size; if ((int)type < 4 && tid != 0) { BigInteger ctrAsBigInt = ((BigInteger)tid << 64 | (BigInteger)(int)type << 56); this.CTRInt = ctrAsBigInt; this.CTR = ctrAsBigInt.ToCTRBytes(); } }
private byte[] getNcchAesCounter(NCCHHeader header, NCCHSection type) //Function based on code from ctrtool's source: https://github.com/Relys/Project_CTR { byte[] counter = new byte[16]; if (header.formatVersion == 2 || header.formatVersion == 0) { for (int i = 0; i < 8; ++i) { counter[i] = header.titleId[header.titleId.Length - 1 - i]; } counter[8] = (byte)type; } else if (header.formatVersion == 1) { UInt32 x = 0; switch (type) { case NCCHSection.ExHeader: x = 0x200; //ExHeader is always 0x200 bytes into the NCCH break; case NCCHSection.ExeFS: x = header.exefsOffset * MEDIAUNITSIZE; break; case NCCHSection.RomFS: x = header.romfsOffset * MEDIAUNITSIZE; break; } for (int i = 0; i < 8; ++i) { counter[i] = header.titleId[i]; } counter[12] = (byte)((x >> 24) & 0xFF); counter[13] = (byte)((x >> 16) & 0xFF); counter[14] = (byte)((x >> 8) & 0xFF); counter[15] = (byte)(x & 0xFF); } return(counter); }
//Function based on code from ctrtool's source: https://github.com/Relys/Project_CTR private byte[] getNcchAesCounter(NCCHHeader header, NCCHSection type) { byte[] counter = new byte[16]; if(header.formatVersion == 2 || header.formatVersion == 0) { for(int i = 0; i < 8; ++i) { counter[i] = header.titleId[header.titleId.Length - 1 - i]; } counter[8] = (byte) type; } else if(header.formatVersion == 1) { UInt32 x = 0; switch(type) { case NCCHSection.ExHeader: x = 0x200; //ExHeader is always 0x200 bytes into the NCCH break; case NCCHSection.ExeFS: x = header.exefsOffset * MEDIAUNITSIZE; break; case NCCHSection.RomFS: x = header.romfsOffset * MEDIAUNITSIZE; break; } for(int i = 0; i < 8; ++i) { counter[i] = header.titleId[i]; } counter[12] = (byte) ((x >> 24) & 0xFF); counter[13] = (byte) ((x >> 16) & 0xFF); counter[14] = (byte) ((x >> 8) & 0xFF); counter[15] = (byte) (x & 0xFF); } return counter; }
private NCCHInfoEntry parseNCCHSection(NCCHHeader header, NCCHSection type, byte[] keyY, bool uses7xCrypto, bool usesSeedCrypto, bool print, string indent) { NCCHInfoEntry entry = new NCCHInfoEntry(); entry.keyY = keyY; entry.reserved = 0x00000000; entry.uses7xcrypto = (UInt32) (uses7xCrypto ? 0x00000001 : 0x00000000); entry.uses9xcrypto = (UInt32) (usesSeedCrypto ? 0x00000001 : 0x00000000); entry.outputname = new byte[112]; string sectionname; uint offset; uint sectionsize; switch(type) { case NCCHSection.ExHeader: sectionname = "ExHeader"; offset = 0x200; //Always 0x200 sectionsize = header.exhdrSize * MEDIAUNITSIZE; break; case NCCHSection.ExeFS: sectionname = "ExeFS"; offset = header.exefsOffset * MEDIAUNITSIZE; sectionsize = header.exefsSize * MEDIAUNITSIZE; break; case NCCHSection.RomFS: sectionname = "RomFS"; offset = header.romfsOffset * MEDIAUNITSIZE; sectionsize = header.romfsSize * MEDIAUNITSIZE; break; default: Console.Error.WriteLine("Illegal NCCH Section type provided!"); Environment.Exit(1); return entry; //Needed to compile } entry.counter = getNcchAesCounter(header, type); entry.titleID = new byte[8]; Array.Copy(header.programId, 0, entry.titleID, 0, 8); //Compute section size in MB, rounding up to the next MB uint sectionMb = sectionsize / BYTES_PER_MEGABYTE; //Rounding down in this step uint remainder = sectionsize % BYTES_PER_MEGABYTE; if(remainder != 0) { ++sectionMb; } entry.size = sectionMb; if(print) { Console.WriteLine(String.Format(indent + "{0} offset: {1:X8}", sectionname, offset)); Console.WriteLine(String.Format(indent + "{0} counter: {1}", sectionname, ToHexString(entry.counter))); Console.WriteLine(String.Format(indent + "{0} Megabytes(rounded up): {1}", sectionname, sectionMb)); } //TODO //return struct.pack('<16s16sIIIIQ', str(counter), str(keyY), sectionMb, 0, usesSeedCrypto, uses7xCrypto, titleId) return entry; }
public void ExtractSection(NCCHSection section, Stream outputStream, bool decrypt = true, bool close = true) { if (this.Info.Flags.UsesEncryption && this.Cryptor == null && decrypt == true) { throw new ArgumentException("This NCCH is encrypted, and no Crypto Engine was provided to perform decryption."); } if (!this.Regions.Any(region => region.Type == section)) { throw new ArgumentException($"The requested section ({Enum.GetName(typeof(NCCHSection), section)}) was not found inside this NCCH."); } NCCHRegion region = this.Regions.Find(region => region.Type == section); if (this.Info.Flags.UsesEncryption && decrypt && !NoCryptoSections.Contains(region.Type)) //if encrypted, user wants to decrypt, and the selected region is known to use encryption { Keyslot primaryKeyslot; Keyslot secondaryKeyslot; byte[] secondaryKeyY = new byte[16]; if (this.Info.Flags.UsesFixedKey) { primaryKeyslot = ((this.Info.TitleID & (0x10 << 32)) > 0) ? Keyslot.FixedSystemKey : Keyslot.ZeroKey; secondaryKeyslot = primaryKeyslot; } else { primaryKeyslot = Keyslot.NCCH; secondaryKeyslot = secondaryKeyslot = this.Info.Flags.CryptoMethod switch { 0x00 => Keyslot.NCCH, 0x01 => Keyslot.NCCH70, 0x0A => Keyslot.NCCH93, 0x0B => Keyslot.NCCH96, _ => throw new Exception("Could not determine crypto type. This should NOT happen.") }; if (this.Info.Flags.UsesSeed) { this.Info.LoadSeed(this.SeedDB); secondaryKeyY = this.Info.SeededKeyY; } else { secondaryKeyY = this.Info.KeyY; } } this.Cryptor.SetKeyslot("y", (int)primaryKeyslot, this.Info.KeyY.ToUnsignedBigInt()); //load primary key into keyslot this.Cryptor.SetKeyslot("y", (int)secondaryKeyslot, secondaryKeyY.ToUnsignedBigInt()); //load secondary key into keyslot //decrypts based on section switch (region.Type) { case NCCHSection.ExHeader: Tools.CryptFileStreamPart(this.NCCHMemoryMappedFile, outputStream, new AesCtrCryptoTransform(this.Cryptor.NormalKey[(int)primaryKeyslot], region.CTR), region.Offset, region.Size, close); break; case NCCHSection.RomFS: //uses secondary keyslot for it's entirety Tools.CryptFileStreamPart(this.NCCHMemoryMappedFile, outputStream, new AesCtrCryptoTransform(this.Cryptor.NormalKey[(int)secondaryKeyslot], region.CTR), region.Offset, region.Size, close); break; case NCCHSection.ExeFS: //can use two keyslots. because of this, I separated the ExeFS decryption into another method ExtractExeFS(outputStream, region, secondaryKeyslot, close); break; } } else //just extract the raw section, because no crypto is used or the user did not request decryption { Tools.ExtractFileStreamPart(this.NCCHMemoryMappedFile, outputStream, region.Offset, region.Size, close); } }
private NCCHInfoEntry parseNCCHSection(NCCHHeader header, NCCHSection type, byte[] keyY, bool uses7xCrypto, bool usesSeedCrypto, bool print, string indent) { NCCHInfoEntry entry = new NCCHInfoEntry(); entry.keyY = keyY; entry.reserved = 0x00000000; entry.uses7xcrypto = (UInt32)(uses7xCrypto ? 0x00000001 : 0x00000000); entry.uses9xcrypto = (UInt32)(usesSeedCrypto ? 0x00000001 : 0x00000000); entry.outputname = new byte[112]; string sectionname; uint offset; uint sectionsize; switch (type) { case NCCHSection.ExHeader: sectionname = "ExHeader"; offset = 0x200; //Always 0x200 sectionsize = header.exhdrSize * MEDIAUNITSIZE; break; case NCCHSection.ExeFS: sectionname = "ExeFS"; offset = header.exefsOffset * MEDIAUNITSIZE; sectionsize = header.exefsSize * MEDIAUNITSIZE; break; case NCCHSection.RomFS: sectionname = "RomFS"; offset = header.romfsOffset * MEDIAUNITSIZE; sectionsize = header.romfsSize * MEDIAUNITSIZE; break; default: Console.Error.WriteLine("Illegal NCCH Section type provided!"); Environment.Exit(1); return(entry); //Needed to compile } entry.counter = getNcchAesCounter(header, type); entry.titleID = new byte[8]; Array.Copy(header.programId, 0, entry.titleID, 0, 8); //Compute section size in MB, rounding up to the next MB uint sectionMb = sectionsize / BYTES_PER_MEGABYTE; //Rounding down in this step uint remainder = sectionsize % BYTES_PER_MEGABYTE; if (remainder != 0) { ++sectionMb; } entry.size = sectionMb; if (print) { Console.WriteLine(String.Format(indent + "{0} offset: {1:X8}", sectionname, offset)); Console.WriteLine(String.Format(indent + "{0} counter: {1}", sectionname, ToHexString(entry.counter))); Console.WriteLine(String.Format(indent + "{0} Megabytes(rounded up): {1}", sectionname, sectionMb)); } //TODO //return struct.pack('<16s16sIIIIQ', str(counter), str(keyY), sectionMb, 0, usesSeedCrypto, uses7xCrypto, titleId) return(entry); }