diff --git a/BurnOutSharp.Builders/BFPK.cs b/BurnOutSharp.Builders/BFPK.cs index 10e7b5f0..48f6efe6 100644 --- a/BurnOutSharp.Builders/BFPK.cs +++ b/BurnOutSharp.Builders/BFPK.cs @@ -1,5 +1,4 @@ using System.IO; -using System.Linq; using System.Text; using BurnOutSharp.Models.BFPK; using BurnOutSharp.Utilities; diff --git a/BurnOutSharp.Builders/Nitro.cs b/BurnOutSharp.Builders/Nitro.cs new file mode 100644 index 00000000..242a81d2 --- /dev/null +++ b/BurnOutSharp.Builders/Nitro.cs @@ -0,0 +1,192 @@ +using System.IO; +using System.Text; +using BurnOutSharp.Models.Nitro; +using BurnOutSharp.Utilities; + +namespace BurnOutSharp.Builders +{ + public class Nitro + { + #region Byte Data + + /// + /// Parse a byte array into a NDS cart image + /// + /// Byte array to parse + /// Offset into the byte array + /// Filled cart image on success, null on error + public static Cart ParseCart(byte[] data, int offset) + { + // If the data is invalid + if (data == null) + return null; + + // If the offset is out of bounds + if (offset < 0 || offset >= data.Length) + return null; + + // Create a memory stream and parse that + MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset); + return ParseCart(dataStream); + } + + #endregion + + #region Stream Data + + /// + /// Parse a Stream into a NDS cart image + /// + /// Stream to parse + /// Filled cart image on success, null on error + public static Cart ParseCart(Stream data) + { + // If the data is invalid + if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead) + return null; + + // If the offset is out of bounds + if (data.Position < 0 || data.Position >= data.Length) + return null; + + // Cache the current offset + int initialOffset = (int)data.Position; + + // Create a new cart image to fill + var cart = new Cart(); + + #region Header + + // Try to parse the header + var header = ParseHeader(data); + if (header == null) + return null; + + // Set the cart image header + cart.Header = header; + + #endregion + + #region Secure Area + + // Try to get the secure area offset + int secureAreaOffset = 0x4000; + if (secureAreaOffset > data.Length) + return null; + + // Seek to the secure area + data.Seek(secureAreaOffset, SeekOrigin.Begin); + + // Read the secure area without processing + cart.SecureArea = data.ReadBytes(0x800); + + #endregion + + return cart; + } + + /// + /// Parse a Stream into a header + /// + /// Stream to parse + /// Filled header on success, null on error + private static Header ParseHeader(Stream data) + { + // TODO: Use marshalling here instead of building + Header header = new Header(); + + byte[] gameTitle = data.ReadBytes(0x0C); + header.GameTitle = Encoding.ASCII.GetString(gameTitle); + header.Gamecode = data.ReadUInt32(); + byte[] makerCode = data.ReadBytes(2); + header.Makercode = Encoding.ASCII.GetString(bytes: makerCode); + header.Unitcode = (Unitcode)data.ReadByteValue(); + header.EncryptionSeedSelect = data.ReadByteValue(); + header.Devicecapacity = data.ReadByteValue(); + header.Reserved1 = data.ReadBytes(7); + header.GameRevision = data.ReadUInt16(); + header.RomVersion = data.ReadByteValue(); + header.InternalFlags = data.ReadByteValue(); + header.ARM9RomOffset = data.ReadUInt32(); + header.ARM9EntryAddress = data.ReadUInt32(); + header.ARM9LoadAddress = data.ReadUInt32(); + header.ARM9Size = data.ReadUInt32(); + header.ARM7RomOffset = data.ReadUInt32(); + header.ARM7EntryAddress = data.ReadUInt32(); + header.ARM7LoadAddress = data.ReadUInt32(); + header.ARM7Size = data.ReadUInt32(); + header.FileNameTableOffset = data.ReadUInt32(); + header.FileNameTableLength = data.ReadUInt32(); + header.FileAllocationTableOffset = data.ReadUInt32(); + header.FileAllocationTableLength = data.ReadUInt32(); + header.ARM9OverlayOffset = data.ReadUInt32(); + header.ARM9OverlayLength = data.ReadUInt32(); + header.ARM7OverlayOffset = data.ReadUInt32(); + header.ARM7OverlayLength = data.ReadUInt32(); + header.SecureDisable = data.ReadBytes(8); + header.NTRRegionRomSize = data.ReadUInt32(); + header.HeaderSize = data.ReadUInt32(); + header.Reserved2 = data.ReadBytes(56); + header.NintendoLogo = data.ReadBytes(156); + header.NintendoLogoCRC = data.ReadUInt16(); + header.DebuggerReserved = data.ReadBytes(0x20); + + // If we have a DSi compatible title + if (header.Unitcode == Unitcode.NDSPlusDSi || header.Unitcode == Unitcode.DSi) + { + header.GlobalMBK15Settings = data.ReadBytes(20); + header.LocalMBK68SettingsARM9 = data.ReadBytes(12); + header.LocalMBK68SettingsARM7 = data.ReadBytes(12); + header.GlobalMBK9Setting = data.ReadBytes(4); + header.RegionFlags = data.ReadBytes(4); + header.AccessControl = data.ReadBytes(4); + header.ARM7SCFGEXTMask = data.ReadBytes(4); + header.ReservedFlags = data.ReadBytes(4); + header.ARM9iRomOffset = data.ReadUInt32(); + header.Reserved3 = data.ReadBytes(4); + header.ARM9iLoadAddress = data.ReadUInt32(); + header.ARM9iSize = data.ReadUInt32(); + header.ARM7iRomOffset = data.ReadUInt32(); + header.Reserved4 = data.ReadBytes(4); + header.ARM7iLoadAddress = data.ReadUInt32(); + header.ARM7iSize = data.ReadUInt32(); + header.DigestNTRRegionOffset = data.ReadUInt32(); + header.DigestNTRRegionLength = data.ReadUInt32(); + header.DigestTWLRegionOffset = data.ReadUInt32(); + header.DigestTWLRegionLength = data.ReadUInt32(); + header.DigestSectorHashtableRegionOffset = data.ReadUInt32(); + header.DigestSectorHashtableRegionLength = data.ReadUInt32(); + header.DigestBlockHashtableRegionOffset = data.ReadUInt32(); + header.DigestBlockHashtableRegionLength = data.ReadUInt32(); + header.DigestSectorSize = data.ReadUInt32(); + header.DigestBlockSectorCount = data.ReadUInt32(); + header.IconBannerSize = data.ReadUInt32(); + header.Unknown1 = data.ReadBytes(4); + header.ModcryptArea1Offset = data.ReadUInt32(); + header.ModcryptArea1Size = data.ReadUInt32(); + header.ModcryptArea2Offset = data.ReadUInt32(); + header.ModcryptArea2Size = data.ReadUInt32(); + header.TitleID = data.ReadBytes(8); + header.DSiWarePublicSavSize = data.ReadUInt32(); + header.DSiWarePrivateSavSize = data.ReadUInt32(); + header.ReservedZero = data.ReadBytes(176); + header.Unknown2 = data.ReadBytes(0x10); + header.ARM9WithSecureAreaSHA1HMACHash = data.ReadBytes(20); + header.ARM7SHA1HMACHash = data.ReadBytes(20); + header.DigestMasterSHA1HMACHash = data.ReadBytes(20); + header.BannerSHA1HMACHash = data.ReadBytes(20); + header.ARM9iDecryptedSHA1HMACHash = data.ReadBytes(20); + header.ARM7iDecryptedSHA1HMACHash = data.ReadBytes(20); + header.Reserved5 = data.ReadBytes(40); + header.ARM9NoSecureAreaSHA1HMACHash = data.ReadBytes(20); + header.Reserved6 = data.ReadBytes(2636); + header.ReservedAndUnchecked = data.ReadBytes(0x180); + header.RSASignature = data.ReadBytes(0x80); + } + + return header; + } + + #endregion + } +} diff --git a/BurnOutSharp.Models/Nitro/Cart.cs b/BurnOutSharp.Models/Nitro/Cart.cs new file mode 100644 index 00000000..18ae46ae --- /dev/null +++ b/BurnOutSharp.Models/Nitro/Cart.cs @@ -0,0 +1,18 @@ +namespace BurnOutSharp.Models.Nitro +{ + /// + /// Represents a DS cart image + /// + public class Cart + { + /// + /// DS cart header + /// + public Header Header { get; set; } + + /// + /// Secure area, may be encrypted or decrypted + /// + public byte[] SecureArea { get; set; } + } +} \ No newline at end of file