From 4b3548b6ab40321839785a652d79f539d663cdfe Mon Sep 17 00:00:00 2001 From: Gericom Date: Mon, 12 Jun 2017 12:18:17 +0200 Subject: [PATCH] Add project files. --- dspatch.sln | 34 ++ dspatch/App.config | 6 + dspatch/DS/DemoMenu.cs | 89 ++++ dspatch/IO/EndianBinaryReader.cs | 374 +++++++++++++++ dspatch/IO/EndianBinaryWriter.cs | 344 ++++++++++++++ dspatch/IO/IOUtil.cs | 119 +++++ dspatch/IO/SFSDirectory.cs | 235 ++++++++++ dspatch/IO/SFSFile.cs | 27 ++ dspatch/Nitro/ARM9.cs | 171 +++++++ dspatch/Nitro/CRC16.cs | 62 +++ dspatch/Nitro/CRT0.cs | 110 +++++ dspatch/Nitro/NDS.cs | 719 +++++++++++++++++++++++++++++ dspatch/Nitro/NitroFS.cs | 165 +++++++ dspatch/Program.cs | 360 +++++++++++++++ dspatch/Properties/AssemblyInfo.cs | 36 ++ dspatch/dspatch.csproj | 75 +++ 16 files changed, 2926 insertions(+) create mode 100644 dspatch.sln create mode 100644 dspatch/App.config create mode 100644 dspatch/DS/DemoMenu.cs create mode 100644 dspatch/IO/EndianBinaryReader.cs create mode 100644 dspatch/IO/EndianBinaryWriter.cs create mode 100644 dspatch/IO/IOUtil.cs create mode 100644 dspatch/IO/SFSDirectory.cs create mode 100644 dspatch/IO/SFSFile.cs create mode 100644 dspatch/Nitro/ARM9.cs create mode 100644 dspatch/Nitro/CRC16.cs create mode 100644 dspatch/Nitro/CRT0.cs create mode 100644 dspatch/Nitro/NDS.cs create mode 100644 dspatch/Nitro/NitroFS.cs create mode 100644 dspatch/Program.cs create mode 100644 dspatch/Properties/AssemblyInfo.cs create mode 100644 dspatch/dspatch.csproj diff --git a/dspatch.sln b/dspatch.sln new file mode 100644 index 0000000..116b0c5 --- /dev/null +++ b/dspatch.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dspatch", "dspatch\dspatch.csproj", "{C8CAE5E5-40A9-4840-AE8A-ABBD2AE50749}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C8CAE5E5-40A9-4840-AE8A-ABBD2AE50749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C8CAE5E5-40A9-4840-AE8A-ABBD2AE50749}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8CAE5E5-40A9-4840-AE8A-ABBD2AE50749}.Debug|x64.ActiveCfg = Debug|Any CPU + {C8CAE5E5-40A9-4840-AE8A-ABBD2AE50749}.Debug|x64.Build.0 = Debug|Any CPU + {C8CAE5E5-40A9-4840-AE8A-ABBD2AE50749}.Debug|x86.ActiveCfg = Debug|Any CPU + {C8CAE5E5-40A9-4840-AE8A-ABBD2AE50749}.Debug|x86.Build.0 = Debug|Any CPU + {C8CAE5E5-40A9-4840-AE8A-ABBD2AE50749}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C8CAE5E5-40A9-4840-AE8A-ABBD2AE50749}.Release|Any CPU.Build.0 = Release|Any CPU + {C8CAE5E5-40A9-4840-AE8A-ABBD2AE50749}.Release|x64.ActiveCfg = Release|Any CPU + {C8CAE5E5-40A9-4840-AE8A-ABBD2AE50749}.Release|x64.Build.0 = Release|Any CPU + {C8CAE5E5-40A9-4840-AE8A-ABBD2AE50749}.Release|x86.ActiveCfg = Release|Any CPU + {C8CAE5E5-40A9-4840-AE8A-ABBD2AE50749}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/dspatch/App.config b/dspatch/App.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/dspatch/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/dspatch/DS/DemoMenu.cs b/dspatch/DS/DemoMenu.cs new file mode 100644 index 0000000..00f7528 --- /dev/null +++ b/dspatch/DS/DemoMenu.cs @@ -0,0 +1,89 @@ +using dspatch.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace dspatch.DS +{ + public class DemoMenu + { + public byte[] Write() + { + MemoryStream m = new MemoryStream(); + EndianBinaryWriter er = new EndianBinaryWriter(m); + er.Write((byte)entries.Length); + er.Write((byte)0); + er.Write((byte)0); + er.Write((byte)0); + foreach (var e in entries) + e.Write(er); + er.Write((uint)0); + byte[] result = m.ToArray(); + er.Close(); + return result; + } + public DemoMenuEntry[] entries; + public class DemoMenuEntry + { + private string createString(string text, int length, bool nullTerminated = true) + { + if (text == null) + text = ""; + if (text.Length > (length - (nullTerminated ? 1 : 0))) + return text.Substring(0, length - (nullTerminated ? 1 : 0)); + return text.PadRight(length, '\0'); + } + + public void Write(EndianBinaryWriter er) + { + er.Write(bannerImage, 0, 512); + er.Write(bannerPalette, 0, 32); + er.Write(createString(bannerText1, 0x80), Encoding.ASCII, false); + er.Write(createString(bannerText2, 0x80), Encoding.ASCII, false); + er.Write(rating); + er.Write(guideMode); + er.Write(createString(selectButtonText, 0x20), Encoding.ASCII, false); + er.Write(createString(startButtonText, 0x20), Encoding.ASCII, false); + er.Write(createString(aButtonText, 0x20), Encoding.ASCII, false); + er.Write(createString(bButtonText, 0x20), Encoding.ASCII, false); + er.Write(createString(xButtonText, 0x20), Encoding.ASCII, false); + er.Write(createString(yButtonText, 0x20), Encoding.ASCII, false); + er.Write(createString(lButtonText, 0x20), Encoding.ASCII, false); + er.Write(createString(rButtonText, 0x20), Encoding.ASCII, false); + er.Write(createString(dpadText, 0x20), Encoding.ASCII, false); + er.Write(createString(unknown1Text, 0x20), Encoding.ASCII, false); + er.Write(createString(unknown2Text, 0x20), Encoding.ASCII, false); + er.Write(createString(unknown3Text, 0x20), Encoding.ASCII, false); + er.Write(createString(touchText1, 0x20), Encoding.ASCII, false); + er.Write(createString(touchText2, 0x20), Encoding.ASCII, false); + er.Write(padding, 0, 0x40); + er.Write(createString(internalName, 0xA, false), Encoding.ASCII, false); + } + public byte[] bannerImage; + public byte[] bannerPalette; + public string bannerText1; + public string bannerText2; + public byte rating = 0x1;//0x1 = Everyone + public byte guideMode = 0x11;//0x11 = nothing, 0x13 = only buttons, 0x15 = only touch, 0x17 = buttons + touch + public string selectButtonText; + public string startButtonText; + public string aButtonText; + public string bButtonText; + public string xButtonText; + public string yButtonText; + public string lButtonText; + public string rButtonText; + public string dpadText; + public string unknown1Text; + public string unknown2Text; + public string unknown3Text; + public string touchText1; + public string touchText2; + public byte[] padding = new byte[0x40];//? + public string internalName; + } + } +} diff --git a/dspatch/IO/EndianBinaryReader.cs b/dspatch/IO/EndianBinaryReader.cs new file mode 100644 index 0000000..49be331 --- /dev/null +++ b/dspatch/IO/EndianBinaryReader.cs @@ -0,0 +1,374 @@ +using System.Text; +using System.Runtime.InteropServices; +using System; +using System.IO; + +namespace dspatch.IO +{ + public class EndianBinaryReader : IDisposable + { + private bool disposed; + private byte[] buffer; + + public Stream BaseStream { get; private set; } + public Endianness Endianness { get; set; } + public Endianness SystemEndianness { get { return BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian; } } + + private bool Reverse { get { return SystemEndianness != Endianness; } } + + public EndianBinaryReader(Stream baseStream) + : this(baseStream, Endianness.BigEndian) + { } + + public EndianBinaryReader(Stream baseStream, Endianness endianness) + { + if (baseStream == null) throw new ArgumentNullException("baseStream"); + if (!baseStream.CanRead) throw new ArgumentException("baseStream"); + + BaseStream = baseStream; + Endianness = endianness; + } + + ~EndianBinaryReader() + { + Dispose(false); + } + + private void FillBuffer(int bytes, int stride) + { + if (buffer == null || buffer.Length < bytes) + buffer = new byte[bytes]; + + BaseStream.Read(buffer, 0, bytes); + + if (Reverse) + for (int i = 0; i < bytes; i += stride) + { + Array.Reverse(buffer, i, stride); + } + } + + public byte ReadByte() + { + FillBuffer(1, 1); + + return buffer[0]; + } + + public byte[] ReadBytes(int count) + { + byte[] temp; + + FillBuffer(count, 1); + temp = new byte[count]; + Array.Copy(buffer, 0, temp, 0, count); + return temp; + } + + public sbyte ReadSByte() + { + FillBuffer(1, 1); + + unchecked + { + return (sbyte)buffer[0]; + } + } + + public sbyte[] ReadSBytes(int count) + { + sbyte[] temp; + + temp = new sbyte[count]; + FillBuffer(count, 1); + + unchecked + { + for (int i = 0; i < count; i++) + { + temp[i] = (sbyte)buffer[i]; + } + } + + return temp; + } + + public char ReadChar(Encoding encoding) + { + int size; + + size = GetEncodingSize(encoding); + FillBuffer(size, size); + return encoding.GetChars(buffer, 0, size)[0]; + } + + public char[] ReadChars(Encoding encoding, int count) + { + int size; + + size = GetEncodingSize(encoding); + FillBuffer(size * count, size); + return encoding.GetChars(buffer, 0, size * count); + } + + private static int GetEncodingSize(Encoding encoding) + { + if (encoding == Encoding.UTF8 || encoding == Encoding.ASCII) + return 1; + else if (encoding == Encoding.Unicode || encoding == Encoding.BigEndianUnicode) + return 2; + + return 1; + } + + public string ReadStringNT(Encoding encoding) + { + string text; + + text = ""; + + do + { + text += ReadChar(encoding); + } while (!text.EndsWith("\0", StringComparison.Ordinal)); + + return text.Remove(text.Length - 1); + } + + public string ReadString(Encoding encoding, int count) + { + return new string(ReadChars(encoding, count)); + } + + public double ReadDouble() + { + const int size = sizeof(double); + FillBuffer(size, size); + return BitConverter.ToDouble(buffer, 0); + } + + public double[] ReadDoubles(int count) + { + const int size = sizeof(double); + double[] temp; + + temp = new double[count]; + FillBuffer(size * count, size); + + for (int i = 0; i < count; i++) + { + temp[i] = BitConverter.ToDouble(buffer, size * i); + } + return temp; + } + + public Single ReadSingle() + { + const int size = sizeof(Single); + FillBuffer(size, size); + return BitConverter.ToSingle(buffer, 0); + } + + public Single[] ReadSingles(int count) + { + const int size = sizeof(Single); + Single[] temp; + + temp = new Single[count]; + FillBuffer(size * count, size); + + for (int i = 0; i < count; i++) + { + temp[i] = BitConverter.ToSingle(buffer, size * i); + } + return temp; + } + + public Single ReadFx16() + { + return ReadInt16() / 4096f; + } + + public Single ReadFx32() + { + return ReadInt32() / 4096f; + } + + public Single[] ReadFx32s(int count) + { + Single[] result = new float[count]; + for (int i = 0; i < count; i++) + { + result[i] = ReadInt32() / 4096f; + } + return result; + } + + public Int32 ReadInt32() + { + const int size = sizeof(Int32); + FillBuffer(size, size); + return BitConverter.ToInt32(buffer, 0); + } + + public Int32[] ReadInt32s(int count) + { + const int size = sizeof(Int32); + Int32[] temp; + + temp = new Int32[count]; + FillBuffer(size * count, size); + + for (int i = 0; i < count; i++) + { + temp[i] = BitConverter.ToInt32(buffer, size * i); + } + return temp; + } + + public Int64 ReadInt64() + { + const int size = sizeof(Int64); + FillBuffer(size, size); + return BitConverter.ToInt64(buffer, 0); + } + + public Int64[] ReadInt64s(int count) + { + const int size = sizeof(Int64); + Int64[] temp; + + temp = new Int64[count]; + FillBuffer(size * count, size); + + for (int i = 0; i < count; i++) + { + temp[i] = BitConverter.ToInt64(buffer, size * i); + } + return temp; + } + + public Int16 ReadInt16() + { + const int size = sizeof(Int16); + FillBuffer(size, size); + return BitConverter.ToInt16(buffer, 0); + } + + public Int16[] ReadInt16s(int count) + { + const int size = sizeof(Int16); + Int16[] temp; + + temp = new Int16[count]; + FillBuffer(size * count, size); + + for (int i = 0; i < count; i++) + { + temp[i] = BitConverter.ToInt16(buffer, size * i); + } + return temp; + } + + public UInt16 ReadUInt16() + { + const int size = sizeof(UInt16); + FillBuffer(size, size); + return BitConverter.ToUInt16(buffer, 0); + } + + public UInt16[] ReadUInt16s(int count) + { + const int size = sizeof(UInt16); + UInt16[] temp; + + temp = new UInt16[count]; + FillBuffer(size * count, size); + + for (int i = 0; i < count; i++) + { + temp[i] = BitConverter.ToUInt16(buffer, size * i); + } + return temp; + } + + public UInt32 ReadUInt32() + { + const int size = sizeof(UInt32); + FillBuffer(size, size); + return BitConverter.ToUInt32(buffer, 0); + } + + public UInt32[] ReadUInt32s(int count) + { + const int size = sizeof(UInt32); + UInt32[] temp; + + temp = new UInt32[count]; + FillBuffer(size * count, size); + + for (int i = 0; i < count; i++) + { + temp[i] = BitConverter.ToUInt32(buffer, size * i); + } + return temp; + } + + public UInt64 ReadUInt64() + { + const int size = sizeof(UInt64); + FillBuffer(size, size); + return BitConverter.ToUInt64(buffer, 0); + } + + public UInt64[] ReadUInt64s(int count) + { + const int size = sizeof(UInt64); + UInt64[] temp; + + temp = new UInt64[count]; + FillBuffer(size * count, size); + + for (int i = 0; i < count; i++) + { + temp[i] = BitConverter.ToUInt64(buffer, size * i); + } + return temp; + } + + public void Close() + { + Dispose(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + if (BaseStream != null) + { + BaseStream.Close(); + } + } + + buffer = null; + + disposed = true; + } + } + } + + public enum Endianness + { + BigEndian, + LittleEndian, + } +} diff --git a/dspatch/IO/EndianBinaryWriter.cs b/dspatch/IO/EndianBinaryWriter.cs new file mode 100644 index 0000000..4f46578 --- /dev/null +++ b/dspatch/IO/EndianBinaryWriter.cs @@ -0,0 +1,344 @@ +using System.Text; +using System.IO; +using System; + +namespace dspatch.IO +{ + public class EndianBinaryWriter : IDisposable + { + private bool disposed; + private byte[] buffer; + + public Stream BaseStream { get; private set; } + public Endianness Endianness { get; set; } + public Endianness SystemEndianness { get { return BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian; } } + + private bool Reverse { get { return SystemEndianness != Endianness; } } + + public EndianBinaryWriter(Stream baseStream) + : this(baseStream, Endianness.BigEndian) + { } + + public EndianBinaryWriter(Stream baseStream, Endianness endianness) + { + if (baseStream == null) throw new ArgumentNullException("baseStream"); + if (!baseStream.CanWrite) throw new ArgumentException("baseStream"); + + BaseStream = baseStream; + Endianness = endianness; + } + + ~EndianBinaryWriter() + { + Dispose(false); + } + + private void WriteBuffer(int bytes, int stride) + { + if (Reverse) + for (int i = 0; i < bytes; i += stride) + { + Array.Reverse(buffer, i, stride); + } + + BaseStream.Write(buffer, 0, bytes); + } + + private void CreateBuffer(int size) + { + if (buffer == null || buffer.Length < size) + buffer = new byte[size]; + } + + public void Write(byte value) + { + CreateBuffer(1); + buffer[0] = value; + WriteBuffer(1, 1); + } + + public void Write(byte[] value, int offset, int count) + { + CreateBuffer(count); + Array.Copy(value, offset, buffer, 0, count); + WriteBuffer(count, 1); + } + + public void Write(sbyte value) + { + CreateBuffer(1); + unchecked + { + buffer[0] = (byte)value; + } + WriteBuffer(1, 1); + } + + public void Write(sbyte[] value, int offset, int count) + { + CreateBuffer(count); + + unchecked + { + for (int i = 0; i < count; i++) + { + buffer[i] = (byte)value[i + offset]; + } + } + + WriteBuffer(count, 1); + } + + public void Write(char value, Encoding encoding) + { + int size; + + size = GetEncodingSize(encoding); + CreateBuffer(size); + Array.Copy(encoding.GetBytes(new string(value, 1)), 0, buffer, 0, size); + WriteBuffer(size, size); + } + + public void Write(char[] value, int offset, int count, Encoding encoding) + { + int size; + + size = GetEncodingSize(encoding); + CreateBuffer(size * count); + Array.Copy(encoding.GetBytes(value, offset, count), 0, buffer, 0, count * size); + WriteBuffer(size * count, size); + } + + private static int GetEncodingSize(Encoding encoding) + { + if (encoding == Encoding.UTF8 || encoding == Encoding.ASCII) + return 1; + else if (encoding == Encoding.Unicode || encoding == Encoding.BigEndianUnicode) + return 2; + + return 1; + } + + public void Write(string value,Encoding encoding, bool nullTerminated) + { + Write(value.ToCharArray(), 0, value.Length, encoding); + if (nullTerminated) + Write('\0', encoding); + } + + public void Write(double value) + { + const int size = sizeof(double); + + CreateBuffer(size); + Array.Copy(BitConverter.GetBytes(value), 0, buffer, 0, size); + WriteBuffer(size, size); + } + + public void Write(double[] value, int offset, int count) + { + const int size = sizeof(double); + + CreateBuffer(size * count); + for (int i = 0; i < count; i++) + { + Array.Copy(BitConverter.GetBytes(value[i + offset]), 0, buffer, i * size, size); + } + + WriteBuffer(size * count, size); + } + + public void Write(Single value) + { + const int size = sizeof(Single); + + CreateBuffer(size); + Array.Copy(BitConverter.GetBytes(value), 0, buffer, 0, size); + WriteBuffer(size, size); + } + + public void Write(Single[] value, int offset, int count) + { + const int size = sizeof(Single); + + CreateBuffer(size * count); + for (int i = 0; i < count; i++) + { + Array.Copy(BitConverter.GetBytes(value[i + offset]), 0, buffer, i * size, size); + } + + WriteBuffer(size * count, size); + } + + public void Write(Int32 value) + { + const int size = sizeof(Int32); + + CreateBuffer(size); + Array.Copy(BitConverter.GetBytes(value), 0, buffer, 0, size); + WriteBuffer(size, size); + } + + public void Write(Int32[] value, int offset, int count) + { + const int size = sizeof(Int32); + + CreateBuffer(size * count); + for (int i = 0; i < count; i++) + { + Array.Copy(BitConverter.GetBytes(value[i + offset]), 0, buffer, i * size, size); + } + + WriteBuffer(size * count, size); + } + + public void Write(Int64 value) + { + const int size = sizeof(Int64); + + CreateBuffer(size); + Array.Copy(BitConverter.GetBytes(value), 0, buffer, 0, size); + WriteBuffer(size, size); + } + + public void Write(Int64[] value, int offset, int count) + { + const int size = sizeof(Int64); + + CreateBuffer(size * count); + for (int i = 0; i < count; i++) + { + Array.Copy(BitConverter.GetBytes(value[i + offset]), 0, buffer, i * size, size); + } + + WriteBuffer(size * count, size); + } + + public void Write(Int16 value) + { + const int size = sizeof(Int16); + + CreateBuffer(size); + Array.Copy(BitConverter.GetBytes(value), 0, buffer, 0, size); + WriteBuffer(size, size); + } + + public void Write(Int16[] value, int offset, int count) + { + const int size = sizeof(Int16); + + CreateBuffer(size * count); + for (int i = 0; i < count; i++) + { + Array.Copy(BitConverter.GetBytes(value[i + offset]), 0, buffer, i * size, size); + } + + WriteBuffer(size * count, size); + } + + public void Write(UInt16 value) + { + const int size = sizeof(UInt16); + + CreateBuffer(size); + Array.Copy(BitConverter.GetBytes(value), 0, buffer, 0, size); + WriteBuffer(size, size); + } + + public void Write(UInt16[] value, int offset, int count) + { + const int size = sizeof(UInt16); + + CreateBuffer(size * count); + for (int i = 0; i < count; i++) + { + Array.Copy(BitConverter.GetBytes(value[i + offset]), 0, buffer, i * size, size); + } + + WriteBuffer(size * count, size); + } + + public void Write(UInt32 value) + { + const int size = sizeof(UInt32); + + CreateBuffer(size); + Array.Copy(BitConverter.GetBytes(value), 0, buffer, 0, size); + WriteBuffer(size, size); + } + + public void Write(UInt32[] value, int offset, int count) + { + const int size = sizeof(UInt32); + + CreateBuffer(size * count); + for (int i = 0; i < count; i++) + { + Array.Copy(BitConverter.GetBytes(value[i + offset]), 0, buffer, i * size, size); + } + + WriteBuffer(size * count, size); + } + + public void Write(UInt64 value) + { + const int size = sizeof(UInt64); + + CreateBuffer(size); + Array.Copy(BitConverter.GetBytes(value), 0, buffer, 0, size); + WriteBuffer(size, size); + } + + public void Write(UInt64[] value, int offset, int count) + { + const int size = sizeof(UInt64); + + CreateBuffer(size * count); + for (int i = 0; i < count; i++) + { + Array.Copy(BitConverter.GetBytes(value[i + offset]), 0, buffer, i * size, size); + } + + WriteBuffer(size * count, size); + } + + public void WriteFx16(Single Value) + { + Write((Int16)(Value * 4096f)); + } + + public void WriteFx32(Single Value) + { + Write((Int32)(Value * 4096f)); + } + + public void Close() + { + Dispose(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + if (BaseStream != null) + { + BaseStream.Close(); + } + } + + buffer = null; + + disposed = true; + } + } + } +} diff --git a/dspatch/IO/IOUtil.cs b/dspatch/IO/IOUtil.cs new file mode 100644 index 0000000..46a4446 --- /dev/null +++ b/dspatch/IO/IOUtil.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace dspatch.IO +{ + public class IOUtil + { + public static short ReadS16LE(byte[] Data, int Offset) + { + return (short)(Data[Offset] | (Data[Offset + 1] << 8)); + } + + public static short[] ReadS16sLE(byte[] Data, int Offset, int Count) + { + short[] res = new short[Count]; + for (int i = 0; i < Count; i++) res[i] = ReadS16LE(Data, Offset + i * 2); + return res; + } + + public static short ReadS16BE(byte[] Data, int Offset) + { + return (short)((Data[Offset] << 8) | Data[Offset + 1]); + } + + public static void WriteS16LE(byte[] Data, int Offset, short Value) + { + Data[Offset] = (byte)(Value & 0xFF); + Data[Offset + 1] = (byte)((Value >> 8) & 0xFF); + } + + public static void WriteS16sLE(byte[] Data, int Offset, short[] Values) + { + for (int i = 0; i < Values.Length; i++) + { + WriteS16LE(Data, Offset + i * 2, Values[i]); + } + } + + public static ushort ReadU16LE(byte[] Data, int Offset) + { + return (ushort)(Data[Offset] | (Data[Offset + 1] << 8)); + } + + public static ushort ReadU16BE(byte[] Data, int Offset) + { + return (ushort)((Data[Offset] << 8) | Data[Offset + 1]); + } + + public static void WriteU16LE(byte[] Data, int Offset, ushort Value) + { + Data[Offset] = (byte)(Value & 0xFF); + Data[Offset + 1] = (byte)((Value >> 8) & 0xFF); + } + + public static uint ReadU24LE(byte[] Data, int Offset) + { + return (uint)(Data[Offset] | (Data[Offset + 1] << 8) | (Data[Offset + 2] << 16)); + } + + public static uint ReadU32LE(byte[] Data, int Offset) + { + return (uint)(Data[Offset] | (Data[Offset + 1] << 8) | (Data[Offset + 2] << 16) | (Data[Offset + 3] << 24)); + } + + public static uint ReadU32BE(byte[] Data, int Offset) + { + return (uint)((Data[Offset] << 24) | (Data[Offset + 1] << 16) | (Data[Offset + 2] << 8) | Data[Offset + 3]); + } + + public static void WriteU32LE(byte[] Data, int Offset, uint Value) + { + Data[Offset] = (byte)(Value & 0xFF); + Data[Offset + 1] = (byte)((Value >> 8) & 0xFF); + Data[Offset + 2] = (byte)((Value >> 16) & 0xFF); + Data[Offset + 3] = (byte)((Value >> 24) & 0xFF); + } + + public static void WriteU32BE(byte[] Data, int Offset, uint Value) + { + Data[Offset + 0] = (byte)((Value >> 24) & 0xFF); + Data[Offset + 1] = (byte)((Value >> 16) & 0xFF); + Data[Offset + 2] = (byte)((Value >> 8) & 0xFF); + Data[Offset + 3] = (byte)(Value & 0xFF); + } + + public static ulong ReadU64LE(byte[] Data, int Offset) + { + return (ulong)Data[Offset] | ((ulong)Data[Offset + 1] << 8) | ((ulong)Data[Offset + 2] << 16) | ((ulong)Data[Offset + 3] << 24) | ((ulong)Data[Offset + 4] << 32) | ((ulong)Data[Offset + 5] << 40) | ((ulong)Data[Offset + 6] << 48) | ((ulong)Data[Offset + 7] << 56); + } + + public static ulong ReadU64BE(byte[] Data, int Offset) + { + return ((ulong)Data[Offset] << 56) | ((ulong)Data[Offset + 1] << 48) | ((ulong)Data[Offset + 2] << 40) | ((ulong)Data[Offset + 3] << 32) | ((ulong)Data[Offset + 4] << 24) | ((ulong)Data[Offset + 5] << 16) | ((ulong)Data[Offset + 6] << 8) | ((ulong)Data[Offset + 7] << 0); + } + + public static void WriteU64LE(byte[] Data, int Offset, ulong Value) + { + Data[Offset] = (byte)(Value & 0xFF); + Data[Offset + 1] = (byte)((Value >> 8) & 0xFF); + Data[Offset + 2] = (byte)((Value >> 16) & 0xFF); + Data[Offset + 3] = (byte)((Value >> 24) & 0xFF); + Data[Offset + 4] = (byte)((Value >> 32) & 0xFF); + Data[Offset + 5] = (byte)((Value >> 40) & 0xFF); + Data[Offset + 6] = (byte)((Value >> 48) & 0xFF); + Data[Offset + 7] = (byte)((Value >> 56) & 0xFF); + } + + public static void WriteSingleLE(byte[] Data, int Offset, float Value) + { + byte[] a = BitConverter.GetBytes(Value); + Data[0 + Offset] = a[0]; + Data[1 + Offset] = a[1]; + Data[2 + Offset] = a[2]; + Data[3 + Offset] = a[3]; + } + } +} diff --git a/dspatch/IO/SFSDirectory.cs b/dspatch/IO/SFSDirectory.cs new file mode 100644 index 0000000..5a5f665 --- /dev/null +++ b/dspatch/IO/SFSDirectory.cs @@ -0,0 +1,235 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +namespace dspatch.IO +{ + public class SFSDirectory + { + public SFSDirectory(string Name, bool Root) + { + DirectoryName = Name; + IsRoot = Root; + SubDirectories = new List(); + Files = new List(); + } + public SFSDirectory(UInt16 Id) + { + DirectoryID = Id; + IsRoot = false; + SubDirectories = new List(); + Files = new List(); + } + + public static SFSDirectory FromDirectory(String Path) + { + SFSDirectory Root = new SFSDirectory("/", true); + FillSFSDirFromDisk(new DirectoryInfo(Path), Root); + return Root; + } + + private static void FillSFSDirFromDisk(DirectoryInfo Dir, SFSDirectory Dst) + { + foreach (var v in Dir.EnumerateFiles()) + { + Dst.Files.Add(new SFSFile(-1, v.Name, Dst) { Data = File.ReadAllBytes(v.FullName) }); + } + foreach (var v in Dir.EnumerateDirectories()) + { + SFSDirectory d = new SFSDirectory(v.Name, false) { Parent = Dst }; + Dst.SubDirectories.Add(d); + FillSFSDirFromDisk(v, d); + } + } + + public String DirectoryName; + public UInt16 DirectoryID; + + public SFSDirectory Parent; + + public List SubDirectories; + public List Files; + + public Boolean IsRoot { get; set; } + + public SFSFile this[string index] + { + get + { + foreach (SFSFile f in Files) + { + if (f.FileName == index) return f; + } + return null; + //throw new FileNotFoundException("The file " + index + " does not exist in this directory."); + } + set + { + for (int i = 0; i < Files.Count; i++) + { + if (Files[i].FileName == index) + { + Files[i] = value; + return; + } + } + throw new System.IO.FileNotFoundException("The file " + index + " does not exist in this directory."); + } + } + + public UInt32 TotalNrSubDirectories + { + get + { + UInt32 nr = (UInt32)SubDirectories.Count; + foreach (SFSDirectory d in SubDirectories) nr += d.TotalNrSubDirectories; + return nr; + } + } + + public UInt32 TotalNrSubFiles + { + get + { + UInt32 nr = (UInt32)Files.Count; + foreach (SFSDirectory d in SubDirectories) nr += d.TotalNrSubFiles; + return nr; + } + } + + public SFSFile GetFileByID(int Id) + { + foreach (SFSFile f in Files) + { + if (f.FileID == Id) return f; + } + foreach (SFSDirectory d in SubDirectories) + { + SFSFile f = d.GetFileByID(Id); + if (f != null) return f; + } + return null; + } + + public SFSDirectory GetDirectoryByPath(String Path) + { + if (Path.TrimStart('/').StartsWith("../")) + { + if (Parent == null) return null; + return Parent.GetDirectoryByPath("/" + Path.TrimStart('/').Substring(2)); + } + if (!Path.StartsWith(DirectoryName)) return null; + if (Path == DirectoryName) return this; + Path = Path.Substring(DirectoryName.Length); + if (Path.Length == 0 || !Path.StartsWith("/")) return null; + String dir = Path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)[0]; + foreach(var v in SubDirectories) + { + if(v.DirectoryName == dir) return v.GetDirectoryByPath(Path.Substring(1)); + } + return null; + } + + public SFSFile GetFileByPath(String Path) + { + if (Path.TrimStart('/').StartsWith("../")) + { + if (Parent == null) return null; + return Parent.GetFileByPath("/" + Path.TrimStart('/').Substring(2)); + } + if (!Path.StartsWith(DirectoryName)) return null; + Path = Path.Substring(DirectoryName.Length); + if (Path.Length == 0 || !Path.StartsWith("/")) return null; + //Sarc files may use slashes in their names (since they are just hashes, and do not have a real directory structure) + var vvv = this[Path.Substring(1)]; + if (vvv != null) return vvv; + + string[] parts = Path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length > 1) + { + String dir = parts[0]; + foreach (var v in SubDirectories) + { + if (v.DirectoryName == dir) return v.GetFileByPath(Path.Substring(1)); + } + } + else if(parts.Length == 1) + { + return this[parts[0]]; + } + return null; + } + + public void UpdateIDs(ref int DirId, ref int FileID) + { + this.DirectoryID = (ushort)(0xF000 | DirId++); + foreach (var v in Files) + { + v.FileID = FileID++; + } + foreach (var v in SubDirectories) + { + v.UpdateIDs(ref DirId, ref FileID); + } + } + + public bool IsValidName(String Name) + { + char[] invalid = System.IO.Path.GetInvalidFileNameChars(); + foreach (char c in invalid) + { + if (Name.Contains(c)) return false; + } + foreach (var v in SubDirectories) + { + if (v.DirectoryName == Name) return false; + } + foreach (var v in Files) + { + if (v.FileName == Name) return false; + } + return true; + } + + public void PrintTree(ref int indent) + { + Console.WriteLine(new string(' ', indent * 4) + DirectoryName + " (" + DirectoryID.ToString("X") + ")"); + indent++; + foreach (var v in Files) + { + Console.WriteLine(new string(' ', indent * 4) + v.FileName + " (" + v.FileID.ToString("X") + ")"); + } + foreach (var v in SubDirectories) + { + v.PrintTree(ref indent); + } + indent--; + } + + public void Export(String Path) + { + foreach (var v in Files) + { + System.IO.File.Create(Path + "\\" + v.FileName).Close(); + System.IO.File.WriteAllBytes(Path + "\\" + v.FileName, v.Data); + } + foreach (var v in SubDirectories) + { + System.IO.Directory.CreateDirectory(Path + "\\" + v.DirectoryName); + v.Export(Path + "\\" + v.DirectoryName); + } + } + + public override string ToString() + { + if (Parent != this) + { + if (!IsRoot) return Parent.ToString() + DirectoryName + "/"; + else return "/"; + } + else return DirectoryName; + } + } +} diff --git a/dspatch/IO/SFSFile.cs b/dspatch/IO/SFSFile.cs new file mode 100644 index 0000000..528efbe --- /dev/null +++ b/dspatch/IO/SFSFile.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace dspatch.IO +{ + public class SFSFile + { + public SFSFile(Int32 Id, String Name, SFSDirectory Parent) + { + FileID = Id; + FileName = Name; + this.Parent = Parent; + } + public String FileName; + public Int32 FileID; + public Byte[] Data; + + public SFSDirectory Parent; + + public override string ToString() + { + return Parent.ToString() + FileName; + } + } +} diff --git a/dspatch/Nitro/ARM9.cs b/dspatch/Nitro/ARM9.cs new file mode 100644 index 0000000..3301ba8 --- /dev/null +++ b/dspatch/Nitro/ARM9.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using dspatch.IO; + +namespace dspatch.Nitro +{ + public class ARM9 + { + private UInt32 RamAddress; + + private byte[] StaticData; + + private UInt32 _start_ModuleParamsOffset; + private CRT0.ModuleParams _start_ModuleParams; + + private List AutoLoadList; + + public ARM9(byte[] Data, UInt32 RamAddress) + : this(Data, RamAddress, FindModuleParams(Data)) { } + + public ARM9(byte[] Data, UInt32 RamAddress, UInt32 _start_ModuleParamsOffset) + { + //Unimportant static footer! Use it for _start_ModuleParamsOffset and remove it. + if (IOUtil.ReadU32LE(Data, Data.Length - 12) == 0xDEC00621) + { + _start_ModuleParamsOffset = IOUtil.ReadU32LE(Data, Data.Length - 8); + byte[] data_tmp = new byte[Data.Length - 12]; + Array.Copy(Data, data_tmp, Data.Length - 12); + Data = data_tmp; + } + + this.RamAddress = RamAddress; + this._start_ModuleParamsOffset = _start_ModuleParamsOffset; + _start_ModuleParams = new CRT0.ModuleParams(Data, _start_ModuleParamsOffset); + if (_start_ModuleParams.CompressedStaticEnd != 0) + { + Data = Decompress(Data, _start_ModuleParamsOffset); + _start_ModuleParams = new CRT0.ModuleParams(Data, _start_ModuleParamsOffset); + } + + StaticData = new byte[_start_ModuleParams.AutoLoadStart - RamAddress]; + Array.Copy(Data, StaticData, _start_ModuleParams.AutoLoadStart - RamAddress); + + AutoLoadList = new List(); + uint nr = (_start_ModuleParams.AutoLoadListEnd - _start_ModuleParams.AutoLoadListOffset) / 0xC; + uint Offset = _start_ModuleParams.AutoLoadStart - RamAddress; + for (int i = 0; i < nr; i++) + { + var entry = new CRT0.AutoLoadEntry(Data, _start_ModuleParams.AutoLoadListOffset - RamAddress + (uint)i * 0xC); + entry.Data = new byte[entry.Size]; + Array.Copy(Data, Offset, entry.Data, 0, entry.Size); + AutoLoadList.Add(entry); + Offset += entry.Size; + } + } + + public byte[] Write() + { + MemoryStream m = new MemoryStream(); + EndianBinaryWriter er = new EndianBinaryWriter(m, Endianness.LittleEndian); + er.Write(StaticData, 0, StaticData.Length); + _start_ModuleParams.AutoLoadStart = (uint)er.BaseStream.Position + RamAddress; + foreach (var v in AutoLoadList) er.Write(v.Data, 0, v.Data.Length); + _start_ModuleParams.AutoLoadListOffset = (uint)er.BaseStream.Position + RamAddress; + foreach (var v in AutoLoadList) v.Write(er); + _start_ModuleParams.AutoLoadListEnd = (uint)er.BaseStream.Position + RamAddress; + long curpos = er.BaseStream.Position; + er.BaseStream.Position = _start_ModuleParamsOffset; + _start_ModuleParams.Write(er); + er.BaseStream.Position = curpos; + byte[] data = m.ToArray(); + er.Close(); + return data; + } + + public void AddAutoLoadEntry(UInt32 Address, byte[] Data) + { + AutoLoadList.Add(new CRT0.AutoLoadEntry(Address, Data)); + } + + public bool WriteU16LE(UInt32 Address, UInt16 Value) + { + if (Address > RamAddress && Address < _start_ModuleParams.AutoLoadStart) + { + IOUtil.WriteU16LE(StaticData, (int)(Address - RamAddress), Value); + return true; + } + foreach (var v in AutoLoadList) + { + if (Address > v.Address && Address < (v.Address + v.Size)) + { + IOUtil.WriteU16LE(v.Data, (int)(Address - v.Address), Value); + return true; + } + } + return false; + } + + public UInt32 ReadU32LE(UInt32 Address) + { + if (Address > RamAddress && Address < _start_ModuleParams.AutoLoadStart) + { + return IOUtil.ReadU32LE(StaticData, (int)(Address - RamAddress)); + } + foreach (var v in AutoLoadList) + { + if (Address > v.Address && Address < (v.Address + v.Size)) + { + return IOUtil.ReadU32LE(v.Data, (int)(Address - v.Address)); + } + } + return 0xFFFFFFFF; + } + + public bool WriteU32LE(UInt32 Address, UInt32 Value) + { + if (Address > RamAddress && Address < _start_ModuleParams.AutoLoadStart) + { + IOUtil.WriteU32LE(StaticData, (int)(Address - RamAddress), Value); + return true; + } + foreach (var v in AutoLoadList) + { + if (Address > v.Address && Address < (v.Address + v.Size)) + { + IOUtil.WriteU32LE(v.Data, (int)(Address - v.Address), Value); + return true; + } + } + return false; + } + + public static byte[] Decompress(byte[] Data) + { + uint offset = FindModuleParams(Data); + if (offset == 0xffffffe3) return Data;//no moduleparams, so it must be uncompressed + return Decompress(Data, offset); + } + + public static byte[] Decompress(byte[] Data, UInt32 _start_ModuleParamsOffset) + { + if (IOUtil.ReadU32LE(Data, (int)_start_ModuleParamsOffset + 0x14) == 0) return Data;//Not Compressed! + byte[] Result = CRT0.MIi_UncompressBackward(Data); + IOUtil.WriteU32LE(Result, (int)_start_ModuleParamsOffset + 0x14, 0); + return Result; + } + + private static uint FindModuleParams(byte[] Data) + { + return (uint)IndexOf(Data, new byte[] { 0x21, 0x06, 0xC0, 0xDE, 0xDE, 0xC0, 0x06, 0x21 }) - 0x1C; + } + + private static unsafe long IndexOf(byte[] Data, byte[] Search) + { + fixed (byte* H = Data) fixed (byte* N = Search) + { + long i = 0; + for (byte* hNext = H, hEnd = H + Data.LongLength; hNext < hEnd; i++, hNext++) + { + bool Found = true; + for (byte* hInc = hNext, nInc = N, nEnd = N + Search.LongLength; Found && nInc < nEnd; Found = *nInc == *hInc, nInc++, hInc++) ; + if (Found) return i; + } + return -1; + } + } + } +} diff --git a/dspatch/Nitro/CRC16.cs b/dspatch/Nitro/CRC16.cs new file mode 100644 index 0000000..992295f --- /dev/null +++ b/dspatch/Nitro/CRC16.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace dspatch.Nitro +{ + public class CRC16 + { + private readonly static ushort[] CRC16Table = + { + 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, + 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, + 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, + 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, + 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, + 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, + 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, + 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, + 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, + 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, + 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, + 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, + 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, + 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, + 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, + 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, + 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, + 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, + 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, + 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, + 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, + 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, + 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, + 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, + 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, + 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, + 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, + 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, + 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, + 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, + 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, + 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 + }; + + public static ushort GetCRC16(byte[] Data) + { + return GetCRC16(Data, 0, Data.Length); + } + + + public static ushort GetCRC16(byte[] Data, int start, int length) + { + ushort result = 0xFFFF; + for (int i = start; i < start + length; i++) + { + result = (ushort)((result >> 8) ^ CRC16Table[(result ^ Data[i]) & 0xFF]); + } + return (ushort)result; + } + } +} diff --git a/dspatch/Nitro/CRT0.cs b/dspatch/Nitro/CRT0.cs new file mode 100644 index 0000000..5873a9d --- /dev/null +++ b/dspatch/Nitro/CRT0.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using dspatch.IO; + +namespace dspatch.Nitro +{ + public class CRT0 + { + public class ModuleParams + { + public ModuleParams(byte[] Data, uint Offset) + { + AutoLoadListOffset = IOUtil.ReadU32LE(Data, (int)Offset + 0); + AutoLoadListEnd = IOUtil.ReadU32LE(Data, (int)Offset + 4); + AutoLoadStart = IOUtil.ReadU32LE(Data, (int)Offset + 8); + StaticBssStart = IOUtil.ReadU32LE(Data, (int)Offset + 12); + StaticBssEnd = IOUtil.ReadU32LE(Data, (int)Offset + 16); + CompressedStaticEnd = IOUtil.ReadU32LE(Data, (int)Offset + 20); + SDKVersion = IOUtil.ReadU32LE(Data, (int)Offset + 24); + NitroCodeBE = IOUtil.ReadU32LE(Data, (int)Offset + 28); + NitroCodeLE = IOUtil.ReadU32LE(Data, (int)Offset + 32); + } + public void Write(EndianBinaryWriter er) + { + er.Write(AutoLoadListOffset); + er.Write(AutoLoadListEnd); + er.Write(AutoLoadStart); + er.Write(StaticBssStart); + er.Write(StaticBssEnd); + er.Write(CompressedStaticEnd); + er.Write(SDKVersion); + er.Write(NitroCodeBE); + er.Write(NitroCodeLE); + } + public UInt32 AutoLoadListOffset; + public UInt32 AutoLoadListEnd; + public UInt32 AutoLoadStart; + public UInt32 StaticBssStart; + public UInt32 StaticBssEnd; + public UInt32 CompressedStaticEnd; + public UInt32 SDKVersion; + public UInt32 NitroCodeBE; + public UInt32 NitroCodeLE; + } + + public class AutoLoadEntry + { + public AutoLoadEntry(UInt32 Address, byte[] Data) + { + this.Address = Address; + this.Data = Data; + Size = (uint)Data.Length; + BssSize = 0; + } + public AutoLoadEntry(byte[] Data, uint Offset) + { + Address = IOUtil.ReadU32LE(Data, (int)Offset + 0); + Size = IOUtil.ReadU32LE(Data, (int)Offset + 4); + BssSize = IOUtil.ReadU32LE(Data, (int)Offset + 8); + } + public void Write(EndianBinaryWriter er) + { + er.Write(Address); + er.Write(Size); + er.Write(BssSize); + } + public UInt32 Address; + public UInt32 Size; + public UInt32 BssSize; + + public byte[] Data; + } + + public static byte[] MIi_UncompressBackward(byte[] Data) + { + UInt32 leng = IOUtil.ReadU32LE(Data, Data.Length - 4) + (uint)Data.Length; + byte[] Result = new byte[leng]; + Array.Copy(Data, Result, Data.Length); + int Offs = (int)(Data.Length - (IOUtil.ReadU32LE(Data, Data.Length - 8) >> 24)); + int dstoffs = (int)leng; + while (true) + { + byte header = Result[--Offs]; + for (int i = 0; i < 8; i++) + { + if ((header & 0x80) == 0) Result[--dstoffs] = Result[--Offs]; + else + { + byte a = Result[--Offs]; + byte b = Result[--Offs]; + int offs = (((a & 0xF) << 8) | b) + 2;//+ 1; + int length = (a >> 4) + 2; + do + { + Result[dstoffs - 1] = Result[dstoffs + offs]; + dstoffs--; + length--; + } + while (length >= 0); + } + if (Offs <= (Data.Length - (IOUtil.ReadU32LE(Data, Data.Length - 8) & 0xFFFFFF))) return Result; + header <<= 1; + } + } + } + } +} diff --git a/dspatch/Nitro/NDS.cs b/dspatch/Nitro/NDS.cs new file mode 100644 index 0000000..fad8bab --- /dev/null +++ b/dspatch/Nitro/NDS.cs @@ -0,0 +1,719 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using dspatch.IO; +using System.Xml.Serialization; +using System.Xml; + +namespace dspatch.Nitro +{ + public class NDS + { + public NDS() { } + public NDS(byte[] data) + { + EndianBinaryReader er = new EndianBinaryReader(new MemoryStream(data), Endianness.LittleEndian); + Header = new RomHeader(er); + + er.BaseStream.Position = Header.MainRomOffset; + MainRom = er.ReadBytes((int)Header.MainSize); + if (er.ReadUInt32() == 0xDEC00621)//Nitro Footer! + { + er.BaseStream.Position -= 4; + StaticFooter = new NitroFooter(er); + } + + er.BaseStream.Position = Header.SubRomOffset; + SubRom = er.ReadBytes((int)Header.SubSize); + + er.BaseStream.Position = Header.FntOffset; + Fnt = new RomFNT(er); + + er.BaseStream.Position = Header.MainOvtOffset; + MainOvt = new RomOVT[Header.MainOvtSize / 32]; + for (int i = 0; i < Header.MainOvtSize / 32; i++) MainOvt[i] = new RomOVT(er); + + er.BaseStream.Position = Header.SubOvtOffset; + SubOvt = new RomOVT[Header.SubOvtSize / 32]; + for (int i = 0; i < Header.SubOvtSize / 32; i++) SubOvt[i] = new RomOVT(er); + + er.BaseStream.Position = Header.FatOffset; + Fat = new FileAllocationEntry[Header.FatSize / 8]; + for (int i = 0; i < Header.FatSize / 8; i++) Fat[i] = new FileAllocationEntry(er); + + if (Header.BannerOffset != 0) + { + er.BaseStream.Position = Header.BannerOffset; + Banner = new RomBanner(er); + } + + FileData = new byte[Header.FatSize / 8][]; + for (int i = 0; i < Header.FatSize / 8; i++) + { + er.BaseStream.Position = Fat[i].fileTop; + FileData[i] = er.ReadBytes((int)Fat[i].fileSize); + } + //RSA Signature + if (Header.RomSize + 0x88 <= er.BaseStream.Length) + { + er.BaseStream.Position = Header.RomSize; + byte[] RSASig = er.ReadBytes(0x88); + for (int i = 0; i < RSASig.Length; i++) + { + //It could be padding, so check if there is something other than 0xFF or 0x00 + if (RSASig[i] != 0xFF || RSASig[i] != 0x00) + { + RSASignature = RSASig; + break; + } + } + } + er.Close(); + } + + public byte[] Write(bool multiboot = false) + { + MemoryStream m = new MemoryStream(); + EndianBinaryWriter er = new EndianBinaryWriter(m, Endianness.LittleEndian); + //Header + //skip the header, and write it afterwards + er.BaseStream.Position = 16384; + Header.HeaderSize = (uint)er.BaseStream.Position; + //MainRom + Header.MainRomOffset = (uint)er.BaseStream.Position; + Header.MainSize = (uint)MainRom.Length; + er.Write(MainRom, 0, MainRom.Length); + //Static Footer + if (StaticFooter != null) StaticFooter.Write(er); + if (MainOvt.Length != 0) + { + while ((er.BaseStream.Position % 0x200) != 0) er.Write((byte)0); + //Main Ovt + Header.MainOvtOffset = (uint)er.BaseStream.Position; + Header.MainOvtSize = (uint)MainOvt.Length * 0x20; + foreach (var v in MainOvt) v.Write(er); + foreach (var v in MainOvt) + { + while ((er.BaseStream.Position % 0x200) != 0) er.Write((byte)0); + Fat[v.FileId].fileTop = (uint)er.BaseStream.Position; + Fat[v.FileId].fileBottom = (uint)er.BaseStream.Position + (uint)FileData[v.FileId].Length; + er.Write(FileData[v.FileId], 0, FileData[v.FileId].Length); + } + } + else + { + Header.MainOvtOffset = 0; + Header.MainOvtSize = 0; + } + while ((er.BaseStream.Position % 0x200) != 0) er.Write((byte)0xFF); + //SubRom + Header.SubRomOffset = (uint)er.BaseStream.Position; + Header.SubSize = (uint)SubRom.Length; + er.Write(SubRom, 0, SubRom.Length); + //I assume this works the same as the main ovt? + if (SubOvt.Length != 0) + { + while ((er.BaseStream.Position % 0x200) != 0) er.Write((byte)0); + //Sub Ovt + Header.SubOvtOffset = (uint)er.BaseStream.Position; + Header.SubOvtSize = (uint)SubOvt.Length * 0x20; + foreach (var v in SubOvt) v.Write(er); + foreach (var v in SubOvt) + { + while ((er.BaseStream.Position % 0x200) != 0) er.Write((byte)0); + Fat[v.FileId].fileTop = (uint)er.BaseStream.Position; + Fat[v.FileId].fileBottom = (uint)er.BaseStream.Position + (uint)FileData[v.FileId].Length; + er.Write(FileData[v.FileId], 0, FileData[v.FileId].Length); + } + + } + else + { + Header.SubOvtOffset = 0; + Header.SubOvtSize = 0; + } + while ((er.BaseStream.Position % 0x200) != 0) er.Write((byte)0xFF); + //FNT + Header.FntOffset = (uint)er.BaseStream.Position; + Fnt.Write(er); + Header.FntSize = (uint)er.BaseStream.Position - Header.FntOffset; + while ((er.BaseStream.Position % 0x200) != 0) er.Write((byte)0xFF); + //FAT + Header.FatOffset = (uint)er.BaseStream.Position; + Header.FatSize = (uint)Fat.Length * 8; + //Skip the fat, and write it after writing the data itself + er.BaseStream.Position += Header.FatSize; + //Banner + if (Banner != null) + { + while ((er.BaseStream.Position % 0x200) != 0) er.Write((byte)0xFF); + Header.BannerOffset = (uint)er.BaseStream.Position; + Banner.Write(er); + } + else Header.BannerOffset = 0; + //Files + for (int i = (int)(Header.MainOvtSize / 32 + Header.SubOvtSize / 32); i < FileData.Length; i++) + { + while ((er.BaseStream.Position % 0x200) != 0) er.Write((byte)0xFF); + Fat[i].fileTop = (uint)er.BaseStream.Position; + Fat[i].fileBottom = (uint)er.BaseStream.Position + (uint)FileData[i].Length; + er.Write(FileData[i], 0, FileData[i].Length); + } + while ((er.BaseStream.Position % 4/*0x200*/) != 0) er.Write((byte)0); + long curpos = er.BaseStream.Position; + Header.RomSize = (uint)er.BaseStream.Position; + if (!multiboot) + { + uint CapacitySize = Header.RomSize; + CapacitySize |= CapacitySize >> 16; + CapacitySize |= CapacitySize >> 8; + CapacitySize |= CapacitySize >> 4; + CapacitySize |= CapacitySize >> 2; + CapacitySize |= CapacitySize >> 1; + CapacitySize++; + if (CapacitySize <= 0x20000) CapacitySize = 0x20000; + int Capacity = -18; + while (CapacitySize != 0) { CapacitySize >>= 1; Capacity++; } + Header.DeviceSize = (byte)((Capacity < 0) ? 0 : Capacity); + } + else + Header.DeviceSize = 0x0B; + //RSA! + if (RSASignature != null) er.Write(RSASignature, 0, 0x88); + //Fat + er.BaseStream.Position = Header.FatOffset; + foreach (var v in Fat) v.Write(er); + //Header + er.BaseStream.Position = 0; + Header.Write(er); + byte[] result = m.ToArray(); + er.Close(); + return result; + } + + public RomHeader Header; + [Serializable] + public class RomHeader + { + public RomHeader() { } + public RomHeader(EndianBinaryReader er) + { + GameName = er.ReadString(Encoding.ASCII, 12).Replace("\0", ""); + GameCode = er.ReadString(Encoding.ASCII, 4).Replace("\0", ""); + MakerCode = er.ReadString(Encoding.ASCII, 2).Replace("\0", ""); + ProductId = er.ReadByte(); + DeviceType = er.ReadByte(); + DeviceSize = er.ReadByte(); + ReservedA = er.ReadBytes(9); + GameVersion = er.ReadByte(); + Property = er.ReadByte(); + + MainRomOffset = er.ReadUInt32(); + MainEntryAddress = er.ReadUInt32(); + MainRamAddress = er.ReadUInt32(); + MainSize = er.ReadUInt32(); + SubRomOffset = er.ReadUInt32(); + SubEntryAddress = er.ReadUInt32(); + SubRamAddress = er.ReadUInt32(); + SubSize = er.ReadUInt32(); + + FntOffset = er.ReadUInt32(); + FntSize = er.ReadUInt32(); + + FatOffset = er.ReadUInt32(); + FatSize = er.ReadUInt32(); + + MainOvtOffset = er.ReadUInt32(); + MainOvtSize = er.ReadUInt32(); + + SubOvtOffset = er.ReadUInt32(); + SubOvtSize = er.ReadUInt32(); + + RomParamA = er.ReadBytes(8); + BannerOffset = er.ReadUInt32(); + SecureCRC = er.ReadUInt16(); + RomParamB = er.ReadBytes(2); + + MainAutoloadDone = er.ReadUInt32(); + SubAutoloadDone = er.ReadUInt32(); + + RomParamC = er.ReadBytes(8); + RomSize = er.ReadUInt32(); + HeaderSize = er.ReadUInt32(); + ReservedB = er.ReadBytes(0x38); + + LogoData = er.ReadBytes(0x9C); + LogoCRC = er.ReadUInt16(); + HeaderCRC = er.ReadUInt16(); + } + public void Write(EndianBinaryWriter er) + { + MemoryStream m = new MemoryStream(); + EndianBinaryWriter ew = new EndianBinaryWriter(m, Endianness.LittleEndian); + ew.Write(GameName.PadRight(12, '\0'), Encoding.ASCII, false); + ew.Write(GameCode.PadRight(4, '\0'), Encoding.ASCII, false); + ew.Write(MakerCode.PadRight(2, '\0'), Encoding.ASCII, false); + ew.Write(ProductId); + ew.Write(DeviceType); + ew.Write(DeviceSize); + ew.Write(ReservedA, 0, 9); + ew.Write(GameVersion); + ew.Write(Property); + + ew.Write(MainRomOffset); + ew.Write(MainEntryAddress); + ew.Write(MainRamAddress); + ew.Write(MainSize); + ew.Write(SubRomOffset); + ew.Write(SubEntryAddress); + ew.Write(SubRamAddress); + ew.Write(SubSize); + + ew.Write(FntOffset); + ew.Write(FntSize); + + ew.Write(FatOffset); + ew.Write(FatSize); + + ew.Write(MainOvtOffset); + ew.Write(MainOvtSize); + + ew.Write(SubOvtOffset); + ew.Write(SubOvtSize); + + ew.Write(RomParamA, 0, 8); + ew.Write(BannerOffset); + ew.Write(SecureCRC); + ew.Write(RomParamB, 0, 2); + + ew.Write(MainAutoloadDone); + ew.Write(SubAutoloadDone); + + ew.Write(RomParamC, 0, 8); + ew.Write(RomSize); + ew.Write(HeaderSize); + ew.Write(ReservedB, 0, 0x38); + + ew.Write(LogoData, 0, 0x9C); + LogoCRC = CRC16.GetCRC16(LogoData); + ew.Write(LogoCRC); + + byte[] header = m.ToArray(); + ew.Close(); + + HeaderCRC = CRC16.GetCRC16(header); + + er.Write(header, 0, header.Length); + er.Write(HeaderCRC); + } + public String GameName;//12 + public String GameCode;//4 + public String MakerCode;//2 + public Byte ProductId; + public Byte DeviceType; + public Byte DeviceSize; + public byte[] ReservedA;//9 + public Byte GameVersion; + public Byte Property; + + [XmlIgnore] + public UInt32 MainRomOffset; + public UInt32 MainEntryAddress; + public UInt32 MainRamAddress; + [XmlIgnore] + public UInt32 MainSize; + [XmlIgnore] + public UInt32 SubRomOffset; + public UInt32 SubEntryAddress; + public UInt32 SubRamAddress; + [XmlIgnore] + public UInt32 SubSize; + + [XmlIgnore] + public UInt32 FntOffset; + [XmlIgnore] + public UInt32 FntSize; + + [XmlIgnore] + public UInt32 FatOffset; + [XmlIgnore] + public UInt32 FatSize; + + [XmlIgnore] + public UInt32 MainOvtOffset; + [XmlIgnore] + public UInt32 MainOvtSize; + + [XmlIgnore] + public UInt32 SubOvtOffset; + [XmlIgnore] + public UInt32 SubOvtSize; + + public byte[] RomParamA;//8 + [XmlIgnore] + public UInt32 BannerOffset; + public UInt16 SecureCRC; + public byte[] RomParamB;//2 + + public UInt32 MainAutoloadDone; + public UInt32 SubAutoloadDone; + + public byte[] RomParamC;//8 + [XmlIgnore] + public UInt32 RomSize; + [XmlIgnore] + public UInt32 HeaderSize; + public byte[] ReservedB;//0x38 + + public byte[] LogoData;//0x9C + [XmlIgnore] + public UInt16 LogoCRC; + [XmlIgnore] + public UInt16 HeaderCRC; + } + public Byte[] MainRom; + public NitroFooter StaticFooter; + [Serializable] + public class NitroFooter + { + public NitroFooter() { } + public NitroFooter(EndianBinaryReader er) + { + NitroCode = er.ReadUInt32(); + _start_ModuleParamsOffset = er.ReadUInt32(); + Unknown = er.ReadUInt32(); + } + public void Write(EndianBinaryWriter er) + { + er.Write(NitroCode); + er.Write(_start_ModuleParamsOffset); + er.Write(Unknown); + } + public UInt32 NitroCode; + public UInt32 _start_ModuleParamsOffset; + public UInt32 Unknown; + } + + + public Byte[] SubRom; + public RomFNT Fnt; + public class RomFNT + { + public RomFNT() + { + DirectoryTable = new List(); + EntryNameTable = new List(); + } + public RomFNT(EndianBinaryReader er) + { + DirectoryTable = new List(); + DirectoryTable.Add(new DirectoryTableEntry(er)); + for (int i = 0; i < DirectoryTable[0].dirParentID - 1; i++) + { + DirectoryTable.Add(new DirectoryTableEntry(er)); + } + EntryNameTable = new List(); + int dirend = 0; + while (dirend < DirectoryTable[0].dirParentID) + { + byte entryNameLength = er.ReadByte(); + er.BaseStream.Position--; + if (entryNameLength == 0) + { + EntryNameTable.Add(new EntryNameTableEndOfDirectoryEntry(er)); + dirend++; + } + else if (entryNameLength < 0x80) EntryNameTable.Add(new EntryNameTableFileEntry(er)); + else EntryNameTable.Add(new EntryNameTableDirectoryEntry(er)); + } + } + public void Write(EndianBinaryWriter er) + { + foreach (DirectoryTableEntry e in DirectoryTable) e.Write(er); + foreach (EntryNameTableEntry e in EntryNameTable) e.Write(er); + } + public List DirectoryTable; + public List EntryNameTable; + } + public RomOVT[] MainOvt; + public RomOVT[] SubOvt; + [Serializable] + public class RomOVT + { + [Flags] + public enum OVTFlag : byte + { + Compressed = 1, + AuthenticationCode = 2 + } + public RomOVT() { } + public RomOVT(EndianBinaryReader er) + { + Id = er.ReadUInt32(); + RamAddress = er.ReadUInt32(); + RamSize = er.ReadUInt32(); + BssSize = er.ReadUInt32(); + SinitInit = er.ReadUInt32(); + SinitInitEnd = er.ReadUInt32(); + FileId = er.ReadUInt32(); + UInt32 tmp = er.ReadUInt32(); + Compressed = tmp & 0xFFFFFF; + Flag = (OVTFlag)(tmp >> 24); + } + public void Write(EndianBinaryWriter er) + { + er.Write(Id); + er.Write(RamAddress); + er.Write(RamSize); + er.Write(BssSize); + er.Write(SinitInit); + er.Write(SinitInitEnd); + er.Write(FileId); + er.Write((uint)((((uint)Flag) & 0xFF) << 24 | (Compressed & 0xFFFFFF))); + } + [XmlAttribute] + public UInt32 Id; + public UInt32 RamAddress; + public UInt32 RamSize; + public UInt32 BssSize; + public UInt32 SinitInit; + public UInt32 SinitInitEnd; + [XmlIgnore] + public UInt32 FileId; + + public UInt32 Compressed;//:24; + [XmlAttribute] + public OVTFlag Flag;// :8; + } + public FileAllocationEntry[] Fat; + public RomBanner Banner; + [Serializable] + public class RomBanner + { + public RomBanner() { } + public RomBanner(EndianBinaryReader er) + { + Header = new BannerHeader(er); + Banner = new BannerV1(er); + } + public void Write(EndianBinaryWriter er) + { + Header.CRC16_v1 = Banner.GetCRC(); + Header.Write(er); + Banner.Write(er); + } + public BannerHeader Header; + [Serializable] + public class BannerHeader + { + public BannerHeader() { } + public BannerHeader(EndianBinaryReader er) + { + Version = er.ReadByte(); + ReservedA = er.ReadByte(); + CRC16_v1 = er.ReadUInt16(); + ReservedB = er.ReadBytes(28); + } + public void Write(EndianBinaryWriter er) + { + er.Write(Version); + er.Write(ReservedA); + er.Write(CRC16_v1); + er.Write(ReservedB, 0, 28); + } + public Byte Version; + public Byte ReservedA; + [XmlIgnore] + public UInt16 CRC16_v1; + public Byte[] ReservedB;//28 + } + public BannerV1 Banner; + [Serializable] + public class BannerV1 + { + public BannerV1() { } + public BannerV1(EndianBinaryReader er) + { + Image = er.ReadBytes(32 * 32 / 2); + Pltt = er.ReadBytes(16 * 2); + GameName = new string[6]; + GameName[0] = er.ReadString(Encoding.Unicode, 128).Replace("\0", ""); + GameName[1] = er.ReadString(Encoding.Unicode, 128).Replace("\0", ""); + GameName[2] = er.ReadString(Encoding.Unicode, 128).Replace("\0", ""); + GameName[3] = er.ReadString(Encoding.Unicode, 128).Replace("\0", ""); + GameName[4] = er.ReadString(Encoding.Unicode, 128).Replace("\0", ""); + GameName[5] = er.ReadString(Encoding.Unicode, 128).Replace("\0", ""); + } + public void Write(EndianBinaryWriter er) + { + er.Write(Image, 0, 32 * 32 / 2); + er.Write(Pltt, 0, 16 * 2); + foreach (string s in GameName) er.Write(GameName[0].PadRight(128, '\0'), Encoding.Unicode, false); + } + public Byte[] Image;//32*32/2 + public Byte[] Pltt;//16*2 + + [XmlIgnore] + public String[] GameName;//6, 128 chars (UTF16-LE) + + [XmlElement("GameName")] + public String[] Base64GameName + { + get + { + String[] b = new String[6]; + for (int i = 0; i < 6; i++) + { + b[i] = Convert.ToBase64String(Encoding.Unicode.GetBytes(GameName[i])); + } + return b; + } + set + { + GameName = new string[6]; + for (int i = 0; i < 6; i++) + { + GameName[i] = Encoding.Unicode.GetString(Convert.FromBase64String(value[i])); + } + } + } + + public ushort GetCRC() + { + byte[] Data = new byte[2080]; + Array.Copy(Image, Data, 512); + Array.Copy(Pltt, 0, Data, 512, 32); + Array.Copy(Encoding.Unicode.GetBytes(GameName[0].PadRight(128, '\0')), 0, Data, 544, 256); + Array.Copy(Encoding.Unicode.GetBytes(GameName[1].PadRight(128, '\0')), 0, Data, 544 + 256, 256); + Array.Copy(Encoding.Unicode.GetBytes(GameName[2].PadRight(128, '\0')), 0, Data, 544 + 256 * 2, 256); + Array.Copy(Encoding.Unicode.GetBytes(GameName[3].PadRight(128, '\0')), 0, Data, 544 + 256 * 3, 256); + Array.Copy(Encoding.Unicode.GetBytes(GameName[4].PadRight(128, '\0')), 0, Data, 544 + 256 * 4, 256); + Array.Copy(Encoding.Unicode.GetBytes(GameName[5].PadRight(128, '\0')), 0, Data, 544 + 256 * 5, 256); + return CRC16.GetCRC16(Data); + } + } + } + public Byte[][] FileData; + + public Byte[] RSASignature; + + public void FromFileSystem(SFSDirectory Root) + { + int did = 0; + int fid = MainOvt.Length + SubOvt.Length; + Root.UpdateIDs(ref did, ref fid); + //FATB.numFiles = (ushort)Root.TotalNrSubFiles; + //List Data = new List(); + uint nrfiles = Root.TotalNrSubFiles; + FileAllocationEntry[] overlays = new FileAllocationEntry[MainOvt.Length + SubOvt.Length]; + Array.Copy(Fat, overlays, MainOvt.Length + SubOvt.Length); + Fat = new FileAllocationEntry[(MainOvt.Length + SubOvt.Length) + nrfiles]; + Array.Copy(overlays, Fat, MainOvt.Length + SubOvt.Length); + byte[][] overlaydata = new byte[MainOvt.Length + SubOvt.Length][]; + Array.Copy(FileData, overlaydata, MainOvt.Length + SubOvt.Length); + FileData = new byte[(MainOvt.Length + SubOvt.Length) + nrfiles][]; + Array.Copy(overlaydata, FileData, MainOvt.Length + SubOvt.Length); + //FATB.allocationTable.Clear(); + for (ushort i = (ushort)(MainOvt.Length + SubOvt.Length); i < nrfiles + MainOvt.Length + SubOvt.Length; i++) + { + var f = Root.GetFileByID(i); + Fat[i] = new FileAllocationEntry(0, 0); + FileData[i] = f.Data; + } + Fnt.DirectoryTable.Clear(); + NitroFSUtil.GenerateDirectoryTable(Fnt.DirectoryTable, Root); + uint offset2 = Fnt.DirectoryTable[0].dirEntryStart; + ushort fileId = (ushort)(MainOvt.Length + SubOvt.Length);//0; + Fnt.EntryNameTable.Clear(); + NitroFSUtil.GenerateEntryNameTable(Fnt.DirectoryTable, Fnt.EntryNameTable, Root, ref offset2, ref fileId); + } + + public SFSDirectory ToFileSystem() + { + bool treereconstruct = false;//Some programs do not write the Directory Table well, so sometimes I need to reconstruct the tree based on the fnt, which is bad! + List dirs = new List(); + dirs.Add(new SFSDirectory("/", true)); + dirs[0].DirectoryID = 0xF000; + + uint nrdirs = Fnt.DirectoryTable[0].dirParentID; + for (int i = 1; i < nrdirs; i++) + { + dirs.Add(new SFSDirectory((ushort)(0xF000 + i))); + } + for (int i = 1; i < nrdirs; i++) + { + if (Fnt.DirectoryTable[i].dirParentID - 0xF000 == i) + { + treereconstruct = true; + foreach (var v in dirs) + { + v.Parent = null; + } + break; + } + dirs[i].Parent = dirs[Fnt.DirectoryTable[i].dirParentID - 0xF000]; + } + if (!treereconstruct) + { + for (int i = 0; i < nrdirs; i++) + { + for (int j = 0; j < nrdirs; j++) + { + if (dirs[i] == dirs[j].Parent) + { + dirs[i].SubDirectories.Add(dirs[j]); + } + } + } + } + uint offset = nrdirs * 8; + ushort fileid = Fnt.DirectoryTable[0].dirEntryFileID; + SFSDirectory curdir = null; + foreach (EntryNameTableEntry e in Fnt.EntryNameTable) + { + for (int i = 0; i < nrdirs; i++) + { + if (offset == Fnt.DirectoryTable[i].dirEntryStart) + { + curdir = dirs[i]; + break; + } + } + if (e is EntryNameTableEndOfDirectoryEntry) + { + curdir = null; + offset++; + } + else if (e is EntryNameTableFileEntry) + { + curdir.Files.Add(new SFSFile(fileid++, ((EntryNameTableFileEntry)e).entryName, curdir)); + offset += 1u + e.entryNameLength; + } + else if (e is EntryNameTableDirectoryEntry) + { + if (treereconstruct) + { + dirs[((EntryNameTableDirectoryEntry)e).directoryID - 0xF000].Parent = curdir; + curdir.SubDirectories.Add(dirs[((EntryNameTableDirectoryEntry)e).directoryID - 0xF000]); + } + dirs[((EntryNameTableDirectoryEntry)e).directoryID - 0xF000].DirectoryName = ((EntryNameTableDirectoryEntry)e).entryName; + offset += 3u + (e.entryNameLength & 0x7Fu); + } + } + for (int i = (MainOvt.Length + SubOvt.Length); i < Fat.Length; i++) + { + //byte[] data = new byte[fat[i].fileSize]; + //Array.Copy(FileData FIMG.fileImage, fat[i].fileTop, data, 0, data.Length); + dirs[0].GetFileByID((ushort)i).Data = FileData[i];//data; + } + return dirs[0]; + } + + public byte[] GetDecompressedARM9() + { + if (StaticFooter != null) return ARM9.Decompress(MainRom, StaticFooter._start_ModuleParamsOffset); + else return ARM9.Decompress(MainRom); + } + } +} diff --git a/dspatch/Nitro/NitroFS.cs b/dspatch/Nitro/NitroFS.cs new file mode 100644 index 0000000..e8345b7 --- /dev/null +++ b/dspatch/Nitro/NitroFS.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using dspatch.IO; + +namespace dspatch.Nitro +{ + public class FileAllocationEntry + { + public FileAllocationEntry(UInt32 Offset, UInt32 Size) + { + fileTop = Offset; + fileBottom = Offset + Size; + } + public FileAllocationEntry(EndianBinaryReader er) + { + fileTop = er.ReadUInt32(); + fileBottom = er.ReadUInt32(); + } + public void Write(EndianBinaryWriter er) + { + er.Write(fileTop); + er.Write(fileBottom); + } + public UInt32 fileTop; + public UInt32 fileBottom; + + public UInt32 fileSize + { + get { return fileBottom - fileTop; } + } + } + public class DirectoryTableEntry + { + public DirectoryTableEntry() { } + public DirectoryTableEntry(EndianBinaryReader er) + { + dirEntryStart = er.ReadUInt32(); + dirEntryFileID = er.ReadUInt16(); + dirParentID = er.ReadUInt16(); + } + public void Write(EndianBinaryWriter er) + { + er.Write(dirEntryStart); + er.Write(dirEntryFileID); + er.Write(dirParentID); + } + public UInt32 dirEntryStart; + public UInt16 dirEntryFileID; + public UInt16 dirParentID; + } + public class EntryNameTableEntry + { + protected EntryNameTableEntry() { } + public EntryNameTableEntry(EndianBinaryReader er) + { + entryNameLength = er.ReadByte(); + } + public virtual void Write(EndianBinaryWriter er) + { + er.Write(entryNameLength); + } + public Byte entryNameLength; + } + public class EntryNameTableEndOfDirectoryEntry : EntryNameTableEntry + { + public EntryNameTableEndOfDirectoryEntry() { } + public EntryNameTableEndOfDirectoryEntry(EndianBinaryReader er) + : base(er) { } + public override void Write(EndianBinaryWriter er) + { + base.Write(er); + } + } + public class EntryNameTableFileEntry : EntryNameTableEntry + { + public EntryNameTableFileEntry(String Name) + { + entryNameLength = (byte)Name.Length; + entryName = Name; + } + public EntryNameTableFileEntry(EndianBinaryReader er) + : base(er) + { + entryName = er.ReadString(Encoding.ASCII, entryNameLength); + } + public override void Write(EndianBinaryWriter er) + { + base.Write(er); + er.Write(entryName, Encoding.ASCII, false); + } + public String entryName; + } + public class EntryNameTableDirectoryEntry : EntryNameTableEntry + { + public EntryNameTableDirectoryEntry(String Name, UInt16 DirectoryID) + { + entryNameLength = (byte)(Name.Length | 0x80); + entryName = Name; + directoryID = DirectoryID; + } + public EntryNameTableDirectoryEntry(EndianBinaryReader er) + : base(er) + { + entryName = er.ReadString(Encoding.ASCII, entryNameLength & 0x7F); + directoryID = er.ReadUInt16(); + } + public override void Write(EndianBinaryWriter er) + { + base.Write(er); + er.Write(entryName, Encoding.ASCII, false); + er.Write(directoryID); + } + public String entryName; + public UInt16 directoryID; + } + + public class NitroFSUtil + { + public static void GenerateDirectoryTable(List directoryTable, SFSDirectory dir) + { + DirectoryTableEntry cur = new DirectoryTableEntry(); + if (dir.IsRoot) + { + cur.dirParentID = (ushort)(dir.TotalNrSubDirectories + 1); + cur.dirEntryStart = cur.dirParentID * 8u; + } + else cur.dirParentID = dir.Parent.DirectoryID; + dir.DirectoryID = (ushort)(0xF000 + directoryTable.Count); + directoryTable.Add(cur); + foreach (SFSDirectory d in dir.SubDirectories) + { + GenerateDirectoryTable(directoryTable, d); + } + } + + public static void GenerateEntryNameTable(List directoryTable, List entryNameTable, SFSDirectory dir, ref uint Offset, ref ushort FileId) + { + directoryTable[dir.DirectoryID - 0xF000].dirEntryStart = Offset; + directoryTable[dir.DirectoryID - 0xF000].dirEntryFileID = FileId; + + foreach (SFSDirectory d in dir.SubDirectories) + { + entryNameTable.Add(new EntryNameTableDirectoryEntry(d.DirectoryName, d.DirectoryID)); + Offset += (uint)d.DirectoryName.Length + 3u; + } + foreach (SFSFile f in dir.Files) + { + f.FileID = FileId; + entryNameTable.Add(new EntryNameTableFileEntry(f.FileName)); + Offset += (uint)f.FileName.Length + 1u; + FileId++; + } + entryNameTable.Add(new EntryNameTableEndOfDirectoryEntry()); + Offset++; + + foreach (SFSDirectory d in dir.SubDirectories) + { + GenerateEntryNameTable(directoryTable, entryNameTable, d, ref Offset, ref FileId); + } + } + } +} diff --git a/dspatch/Program.cs b/dspatch/Program.cs new file mode 100644 index 0000000..31a39ad --- /dev/null +++ b/dspatch/Program.cs @@ -0,0 +1,360 @@ +using dspatch.DS; +using dspatch.IO; +using dspatch.Nitro; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace dspatch +{ + class Program + { + static byte[] lockIconImage = + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x8A, + 0x00, 0xA0, 0x88, 0x88, 0x00, 0x88, 0x57, 0x34, 0x80, 0x58, 0x33, 0x33, + 0x8A, 0x34, 0x04, 0x00, 0xA0, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xA8, 0x0A, 0x00, 0x00, 0x88, 0x88, 0x0A, 0x00, + 0x43, 0x75, 0x88, 0x00, 0x33, 0x33, 0x85, 0x08, 0x00, 0x40, 0x43, 0x88, + 0x00, 0x00, 0x37, 0x85, 0x00, 0x00, 0x40, 0x73, 0x00, 0x00, 0x00, 0x53, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, + 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, + 0x00, 0x00, 0x00, 0x33, 0xDD, 0xDD, 0xDD, 0xDD, 0xA8, 0x00, 0x00, 0x00, + 0x88, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, + 0x88, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, + 0xDD, 0xED, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x29, 0xFF, 0xFF, 0xBF, 0x11, 0xFF, 0xFF, 0x9F, 0x11, + 0xFF, 0xFF, 0xBF, 0x11, 0xFF, 0xFF, 0xFF, 0x16, 0xDD, 0xDD, 0xDD, 0xDD, + 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0x92, 0xDD, 0xDD, 0xDD, + 0x11, 0xDB, 0xDD, 0xDD, 0x11, 0xD9, 0xDD, 0xDD, 0x11, 0xDB, 0xDD, 0xDD, + 0x61, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0x00, 0x00, 0xDD, 0xDD, 0x00, 0x00, + 0xDD, 0xDD, 0x00, 0x00, 0xDD, 0xDD, 0x00, 0x00, 0xDD, 0xDD, 0x00, 0x00, + 0xDD, 0xDD, 0x00, 0x00, 0xDD, 0xDD, 0x00, 0x00, 0xDD, 0xDD, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x16, + 0xFF, 0xFF, 0xFF, 0x12, 0xFF, 0xFF, 0xCF, 0x11, 0xFF, 0xFF, 0x9F, 0x11, + 0xFF, 0xFF, 0x9F, 0x11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x61, 0xDD, 0xDD, 0xDD, 0x21, 0xDD, 0xDD, 0xDD, + 0x11, 0xDB, 0xDD, 0xDD, 0x11, 0xD9, 0xDD, 0xDD, 0x11, 0xD9, 0xDD, 0xDD, + 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, + 0xDD, 0xDD, 0x00, 0x00, 0xDD, 0xDD, 0x00, 0x00, 0xDD, 0xDD, 0x00, 0x00, + 0xDD, 0xDD, 0x00, 0x00, 0xDD, 0xDD, 0x00, 0x00, 0xDD, 0xDD, 0x00, 0x00, + 0xDD, 0xDD, 0x00, 0x00, 0xDD, 0xED, 0x00, 0x00 + }; + + static byte[] lockIconPltt = + { + 0x1C, 0x3C, 0x07, 0x25, 0x49, 0x25, 0xAA, 0x3D, 0xCB, 0x3D, 0xEC, 0x41, + 0xEF, 0x25, 0x0E, 0x4A, 0x2F, 0x4E, 0x74, 0x26, 0x71, 0x56, 0xF9, 0x2A, + 0x5B, 0x2F, 0x5E, 0x1B, 0x7E, 0x37, 0xBF, 0x2F + }; + + static byte[] haxxStationIconImage = + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x90, 0x61, 0x66, 0x66, 0x69, 0x66, 0x66, 0x66, 0x61, 0xC8, 0xCC, 0xCC, + 0x66, 0xCC, 0xCC, 0x6C, 0x66, 0xCC, 0xCC, 0x6C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0xCC, 0xCC, 0xCC, 0xCC, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x16, 0x09, 0x66, 0x66, 0x66, 0x96, + 0xCC, 0xCC, 0x8C, 0x16, 0xC6, 0xCC, 0xCC, 0x66, 0xC6, 0xCC, 0xCC, 0x66, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0xCC, 0xCC, 0x6C, + 0x66, 0xCC, 0xCC, 0x6C, 0x66, 0xCC, 0xCC, 0x6C, 0x66, 0xCC, 0xCC, 0x6C, + 0x66, 0xCC, 0xCC, 0x6C, 0x66, 0x8C, 0xCC, 0x6C, 0x45, 0x65, 0x41, 0x85, + 0x89, 0xE2, 0xC0, 0x27, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x27, 0x16, 0x54, 0xB7, 0xD5, 0xE0, 0x9D, 0x14, + 0xC6, 0xCC, 0xCC, 0x66, 0xC6, 0xCC, 0xCC, 0x66, 0xC6, 0xCC, 0xCC, 0x66, + 0xC6, 0xCC, 0xCC, 0x66, 0xC6, 0xCC, 0xCC, 0x66, 0xC6, 0x99, 0xCC, 0x66, + 0x5C, 0x11, 0xC5, 0x66, 0x42, 0xEE, 0x24, 0x44, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x20, 0x0E, 0x00, + 0x00, 0x79, 0x00, 0x0B, 0x00, 0x01, 0x70, 0x08, 0xD0, 0x04, 0x2E, 0x00, + 0xB0, 0x07, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD9, 0x00, 0x77, 0x0C, + 0x24, 0x87, 0x52, 0x14, 0x66, 0x14, 0xC9, 0x6C, 0x66, 0xAC, 0xCC, 0x6C, + 0x66, 0xCC, 0xC1, 0x6C, 0x66, 0x1C, 0x16, 0x6C, 0x66, 0xCC, 0xC1, 0x6C, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x21, 0xA7, 0x00, 0x41, 0x88, 0x47, 0x41, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0xF3, 0x63, 0x66, 0x66, + 0x36, 0x3F, 0x66, 0x66, 0x36, 0x3F, 0x66, 0x66, 0x0D, 0x00, 0xD0, 0x99, + 0x00, 0x88, 0x00, 0x00, 0x18, 0x44, 0x81, 0x99, 0x76, 0xCC, 0x57, 0x44, + 0xC6, 0xCC, 0xCC, 0x66, 0xC6, 0x6C, 0xCC, 0x66, 0xC6, 0xC6, 0xC6, 0x66, + 0xC6, 0x6C, 0xCC, 0x66, 0x90, 0x09, 0x89, 0x00, 0xB0, 0x07, 0x4D, 0x00, + 0xD0, 0x04, 0x10, 0x00, 0x00, 0x01, 0x80, 0x05, 0x00, 0x78, 0x00, 0x0E, + 0x00, 0x10, 0x0D, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x66, 0xCC, 0xCC, 0x6C, 0x66, 0xCC, 0xCC, 0x6C, 0x66, 0xCC, 0xCC, 0x6C, + 0x61, 0xC8, 0xCC, 0xCC, 0x68, 0x66, 0x66, 0x66, 0x80, 0x61, 0x66, 0x66, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3, 0x63, 0xFF, 0x6F, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0xCC, 0xCC, 0xCC, 0xCC, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC6, 0xCC, 0xCC, 0x66, 0xC6, 0xCC, 0xCC, 0x66, + 0xC6, 0xCC, 0xCC, 0x66, 0xCC, 0xCC, 0x8C, 0x16, 0x66, 0x66, 0x66, 0x86, + 0x66, 0x66, 0x16, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + static byte[] haxxStationIconPltt = + { + 0x1F, 0x7C, 0x42, 0x04, 0x83, 0x0C, 0xC5, 0x08, 0xC6, 0x18, 0xE7, 0x1C, + 0x00, 0x00, 0x29, 0x25, 0x8C, 0x2D, 0xAD, 0x35, 0xCE, 0x39, 0x0F, 0x3E, + 0x10, 0x42, 0x51, 0x46, 0x94, 0x52, 0x35, 0x23 + }; + + static byte[] haxxStationServer = + { + 0x52, 0x43, 0x31, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x30, 0x31, 0x20, + 0x32, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x65, 0x72, 0x69, + 0x63, 0x6F, 0x6D, 0x00, 0x00, 0x00, 0x48, 0x61, 0x78, 0x78, 0x53, 0x74, + 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x79, 0x20, 0x47, 0x65, 0x72, + 0x69, 0x63, 0x6F, 0x6D, 0x2C, 0x20, 0x73, 0x68, 0x75, 0x74, 0x74, 0x65, + 0x72, 0x62, 0x75, 0x67, 0x32, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x6E, 0x64, 0x20, 0x41, 0x70, + 0x61, 0x63, 0x68, 0x65, 0x20, 0x54, 0x68, 0x75, 0x6E, 0x64, 0x65, 0x72, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + static byte[] dsHash = + { + 0xF1, 0x8B, 0x55, 0xF3, 0xE1, 0x25, 0x9C, 0x03, 0xE1, 0x0D, 0x0E, 0xCB, + 0x54, 0x96, 0x93, 0xB4, 0x29, 0x05, 0xCE, 0xB5 + }; + + static byte[] exploitData = + { + 0x44, 0x30, 0x9F, 0xE5, 0x2C, 0x00, 0x93, 0xE5, 0x2C, 0x10, 0x9F, 0xE5, + 0x01, 0x00, 0x40, 0xE0, 0x28, 0x10, 0x9F, 0xE5, 0x01, 0x00, 0x80, 0xE0, + 0x24, 0x10, 0x9F, 0xE5, 0x24, 0x20, 0x9F, 0xE5, 0x28, 0xE0, 0x9F, 0xE5, + 0x3E, 0xFF, 0x2F, 0xE1, 0x24, 0xE0, 0x9F, 0xE5, 0x3E, 0xFF, 0x2F, 0xE1, + 0x01, 0x00, 0xA0, 0xE3, 0x1C, 0xE0, 0x9F, 0xE5, 0x1E, 0xFF, 0x2F, 0xE1, + 0x00, 0xAE, 0x11, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x20, 0x6D, 0x11, 0x00, 0x40, 0xE8, 0x3F, 0x02, 0x08, 0xBB, 0x32, 0x02, + 0x24, 0xAF, 0x32, 0x02, 0x78, 0x3A, 0x32, 0x02 + }; + + static byte[] arm7Fix = + { + 0x2C, 0x00, 0x9F, 0xE5, 0x8E, 0x07, 0x80, 0xE2, 0x1C, 0x10, 0x9F, 0xE5, + 0x1C, 0x20, 0x9F, 0xE5, 0x01, 0x30, 0xD0, 0xE4, 0x01, 0x30, 0xC1, 0xE4, + 0x01, 0x20, 0x52, 0xE2, 0xFB, 0xFF, 0xFF, 0xCA, 0x00, 0x00, 0x9F, 0xE5, + 0x10, 0xFF, 0x2F, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00 + }; + + static void PrintUsage() + { + Console.WriteLine("Usage: dspatch download_station.nds rom_to_boot.nds result.nds"); + } + + static void Main(string[] args) + { + Console.WriteLine("== DS Download Station Patcher v1.0 =="); + Console.WriteLine("Exploit by Gericom, shutterbug2000 and Apache Thunder\n"); + if (args.Length != 3) + PrintUsage(); + else + { + byte[] dsdata = File.ReadAllBytes(args[0]); + byte[] sha1 = SHA1.Create().ComputeHash(dsdata); + for (int i = 0; i < 20; i++) + { + if (sha1[i] != dsHash[i]) + { + Console.WriteLine("Error: Invalid download station rom!"); + Console.WriteLine("The patcher is only compatible with:"); + Console.WriteLine("xxxx - DS Download Station - Volume 1 (Kiosk WiFi Demo Cart) (U)(Independent).nds"); + Console.WriteLine("SHA1: F18B55F3E1259C03E10D0ECB549693B42905CEB5"); + return; + } + } + NDS downloadStation = new NDS(dsdata); + var fs = downloadStation.ToFileSystem(); + byte[] newRom = File.ReadAllBytes(args[1]); + NDS newRom2 = new NDS(newRom); + if (newRom2.Header.SubRamAddress >= 0x03000000) + { + byte[] newArm7 = new byte[newRom2.SubRom.Length + arm7Fix.Length]; + Array.Copy(arm7Fix, newArm7, arm7Fix.Length); + Array.Copy(newRom2.SubRom, 0, newArm7, arm7Fix.Length, newRom2.SubRom.Length); + IOUtil.WriteU32LE(newArm7, 0x28, newRom2.Header.SubEntryAddress); + IOUtil.WriteU32LE(newArm7, 0x2C, newRom2.Header.SubRamAddress); + IOUtil.WriteU32LE(newArm7, 0x30, newRom2.Header.SubSize); + newRom2.SubRom = newArm7; + newRom2.Header.SubSize = (uint)newArm7.Length; + newRom2.Header.SubRamAddress = 0x02380000; + newRom2.Header.SubEntryAddress = 0x02380000; + } + byte[] newRomFixed = newRom2.Write(true); + { + uint arm9offset = (uint)(newRomFixed[0x20] | (newRomFixed[0x21] << 8) | (newRomFixed[0x22] << 16) | (newRomFixed[0x23] << 24)); + uint arm9loadaddr = (uint)(newRomFixed[0x28] | (newRomFixed[0x29] << 8) | (newRomFixed[0x2A] << 16) | (newRomFixed[0x2B] << 24)); + uint arm9size = (uint)(newRomFixed[0x2C] | (newRomFixed[0x2D] << 8) | (newRomFixed[0x2E] << 16) | (newRomFixed[0x2F] << 24)); + uint arm7offset = (uint)(newRomFixed[0x30] | (newRomFixed[0x31] << 8) | (newRomFixed[0x32] << 16) | (newRomFixed[0x33] << 24)); + + //arm9 offset becomes 0x180 + newRomFixed[0x20] = 0x80; + newRomFixed[0x21] = 0x01; + newRomFixed[0x22] = 0x00; + newRomFixed[0x23] = 0x00; + //arm9 load becomes 0x02332C40 (rsa_GetDecodedHash) + newRomFixed[0x28] = 0x40; + newRomFixed[0x29] = 0x2C; + newRomFixed[0x2A] = 0x33; + newRomFixed[0x2B] = 0x02; + //arm9 size becomes 0x100 + newRomFixed[0x2C] = 0x00; + newRomFixed[0x2D] = 0x01; + newRomFixed[0x2E] = 0x00; + newRomFixed[0x2F] = 0x00; + ushort newcrc = CRC16.GetCRC16(newRomFixed, 0, 0x15E); + newRomFixed[0x15E] = (byte)(newcrc & 0xFF); + newRomFixed[0x15F] = (byte)(newcrc >> 8); + + exploitData[0x3C] = (byte)(arm7offset & 0xFF); + exploitData[0x3D] = (byte)((arm7offset >> 8) & 0xFF); + exploitData[0x3E] = (byte)((arm7offset >> 16) & 0xFF); + exploitData[0x3F] = (byte)((arm7offset >> 24) & 0xFF); + + exploitData[0x40] = (byte)(arm9offset & 0xFF); + exploitData[0x41] = (byte)((arm9offset >> 8) & 0xFF); + exploitData[0x42] = (byte)((arm9offset >> 16) & 0xFF); + exploitData[0x43] = (byte)((arm9offset >> 24) & 0xFF); + + exploitData[0x44] = (byte)(arm9loadaddr & 0xFF); + exploitData[0x45] = (byte)((arm9loadaddr >> 8) & 0xFF); + exploitData[0x46] = (byte)((arm9loadaddr >> 16) & 0xFF); + exploitData[0x47] = (byte)((arm9loadaddr >> 24) & 0xFF); + + exploitData[0x48] = (byte)(arm9size & 0xFF); + exploitData[0x49] = (byte)((arm9size >> 8) & 0xFF); + exploitData[0x4A] = (byte)((arm9size >> 16) & 0xFF); + exploitData[0x4B] = (byte)((arm9size >> 24) & 0xFF); + + Array.Copy(exploitData, 0, newRomFixed, 0x180, exploitData.Length); + } + SFSDirectory d = fs.GetDirectoryByPath("//ds_demo"); + d.Files.Add(new SFSFile(-1, "rom1.nds", d) { Data = newRomFixed }); + //this is shitty... + d.Files.Remove(d.GetFileByPath("ds_demo/ANDEdemo")); + d.Files.Remove(d.GetFileByPath("ds_demo/AGFEdemo")); + d.Files.Remove(d.GetFileByPath("ds_demo/AMFEclip")); + d.Files.Remove(d.GetFileByPath("ds_demo/AMTEdemo")); + d.Files.Remove(d.GetFileByPath("ds_demo/APTEdemo")); + d.Files.Remove(d.GetFileByPath("ds_demo/ATTEdemo")); + d.Files.Remove(d.GetFileByPath("ds_demo/ATTEpush")); + d.Files.Remove(d.GetFileByPath("ds_demo/testcode")); + d.Files.Remove(d.GetFileByPath("ds_demo/AMCEdemo")); + DemoMenu menu = new DemoMenu(); + menu.entries = new DemoMenu.DemoMenuEntry[] + { + new DemoMenu.DemoMenuEntry() + { + rating = 1, + guideMode = 0x15, + touchText1 = "HaxxStation by Gericom,", + touchText2 = "shutterbug2000, Apache Thunder", + internalName = "rom1.nds" + }, + new DemoMenu.DemoMenuEntry() + { + bannerImage = new byte[512], + bannerPalette = new byte[32], + bannerText1 = "", + bannerText2 = "", + rating = 1, + guideMode = 0x15, + touchText1 = "HaxxStation by Gericom,", + touchText2 = "shutterbug2000, Apache Thunder", + internalName = "rom1.nds" + }, + new DemoMenu.DemoMenuEntry() + { + bannerImage = new byte[512], + bannerPalette = new byte[32], + bannerText1 = "", + bannerText2 = "", + rating = 1, + guideMode = 0x15, + touchText1 = "HaxxStation by Gericom,", + touchText2 = "shutterbug2000, Apache Thunder", + internalName = "rom1.nds" + } + }; + if (newRom2.Banner != null) + { + string[] lines = newRom2.Banner.Banner.GameName[1].Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + if (lines.Length > 0) + menu.entries[0].bannerText1 = lines[0]; + else + menu.entries[0].bannerText1 = "Bannerless Homebrew"; + if (lines.Length > 1) + menu.entries[0].bannerText2 = lines[1]; + else + menu.entries[0].bannerText2 = ""; + menu.entries[0].bannerImage = newRom2.Banner.Banner.Image; + menu.entries[0].bannerPalette = newRom2.Banner.Banner.Pltt; + } + else + { + menu.entries[0].bannerText1 = "Bannerless Homebrew"; + menu.entries[0].bannerText2 = ""; + menu.entries[0].bannerImage = new byte[512]; + menu.entries[0].bannerPalette = new byte[32]; + } + byte[] demoMenu = menu.Write(); + fs.GetFileByPath("//ds_demo/demomenu").Data = demoMenu; + fs.GetFileByPath("//mb/server").Data = haxxStationServer; + fs.GetFileByPath("//mb/icon.nbfc").Data = haxxStationIconImage; + fs.GetFileByPath("//mb/icon.nbfp").Data = haxxStationIconPltt; + downloadStation.FromFileSystem(fs); + //change banner + downloadStation.Banner.Banner.Image = haxxStationIconImage; + downloadStation.Banner.Banner.Pltt = haxxStationIconPltt; + for(int i = 0; i < 6; i++) + downloadStation.Banner.Banner.GameName[i] = "HaxxStation\nBy Gericom, shutterbug2000\nand Apache Thunder"; + byte[] finalResult = downloadStation.Write(); + File.Create(args[2]).Close(); + File.WriteAllBytes(args[2], finalResult); + } + } + } +} diff --git a/dspatch/Properties/AssemblyInfo.cs b/dspatch/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2b10bc7 --- /dev/null +++ b/dspatch/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DS Download Station Patcher")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DS Download Station Patcher")] +[assembly: AssemblyCopyright("Copyright © 2017 Gericom, shutterbug2000 and Apache Thunder")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c8cae5e5-40a9-4840-ae8a-abbd2ae50749")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/dspatch/dspatch.csproj b/dspatch/dspatch.csproj new file mode 100644 index 0000000..5ac8735 --- /dev/null +++ b/dspatch/dspatch.csproj @@ -0,0 +1,75 @@ + + + + + Debug + AnyCPU + {C8CAE5E5-40A9-4840-AE8A-ABBD2AE50749} + Exe + Properties + dspatch + dspatch + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file