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