diff --git a/BurnOutSharp.Builder/BurnOutSharp.Builder.csproj b/BurnOutSharp.Builder/BurnOutSharp.Builder.csproj
new file mode 100644
index 00000000..681fc8a2
--- /dev/null
+++ b/BurnOutSharp.Builder/BurnOutSharp.Builder.csproj
@@ -0,0 +1,27 @@
+
+
+
+ netstandard2.0
+ BurnOutSharp.Executable
+ BurnOutSharp.Executable
+ Matt Nadareski;Gernot Knippen
+ BurnOutSharp
+ Copyright (c)2005-2010 Gernot Knippen, Copyright (c)2018-2022 Matt Nadareski
+ LICENSE.txt
+ https://github.com/mnadareski/BurnOutSharp
+ 2.3.4
+ 2.3.4
+ 2.3.4
+ true
+ true
+
+
+
+ true
+
+
+
+
+
+
+
diff --git a/BurnOutSharp.Builder/Extensions.cs b/BurnOutSharp.Builder/Extensions.cs
new file mode 100644
index 00000000..d0416883
--- /dev/null
+++ b/BurnOutSharp.Builder/Extensions.cs
@@ -0,0 +1,276 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace BurnOutSharp.Builder
+{
+ internal static class Extensions
+ {
+ #region Byte Arrays
+
+ ///
+ /// Read a byte and increment the pointer to an array
+ ///
+ public static byte ReadByte(this byte[] content, ref int offset)
+ {
+ return content[offset++];
+ }
+
+ ///
+ /// Read a byte array and increment the pointer to an array
+ ///
+ public static byte[] ReadBytes(this byte[] content, ref int offset, int count)
+ {
+ byte[] buffer = new byte[count];
+ Array.Copy(content, offset, buffer, 0, Math.Min(count, content.Length - offset));
+ offset += count;
+ return buffer;
+ }
+
+ ///
+ /// Read a char and increment the pointer to an array
+ ///
+ public static char ReadChar(this byte[] content, ref int offset)
+ {
+ return (char)content[offset++];
+ }
+
+ ///
+ /// Read a character array and increment the pointer to an array
+ ///
+ public static char[] ReadChars(this byte[] content, ref int offset, int count) => content.ReadChars(ref offset, count, Encoding.Default);
+
+ ///
+ /// Read a character array and increment the pointer to an array
+ ///
+ public static char[] ReadChars(this byte[] content, ref int offset, int count, Encoding encoding)
+ {
+ // TODO: Fix the code below to make it work with byte arrays and not streams
+ return null;
+
+ // byte[] buffer = new byte[count];
+ // stream.Read(buffer, 0, count);
+ // return encoding.GetString(buffer).ToCharArray();
+ }
+
+ ///
+ /// Read a short and increment the pointer to an array
+ ///
+ public static short ReadInt16(this byte[] content, ref int offset)
+ {
+ short value = BitConverter.ToInt16(content, offset);
+ offset += 2;
+ return value;
+ }
+
+ ///
+ /// Read a ushort and increment the pointer to an array
+ ///
+ public static ushort ReadUInt16(this byte[] content, ref int offset)
+ {
+ ushort value = BitConverter.ToUInt16(content, offset);
+ offset += 2;
+ return value;
+ }
+
+ ///
+ /// Read a int and increment the pointer to an array
+ ///
+ public static int ReadInt32(this byte[] content, ref int offset)
+ {
+ int value = BitConverter.ToInt32(content, offset);
+ offset += 4;
+ return value;
+ }
+
+ ///
+ /// Read a uint and increment the pointer to an array
+ ///
+ public static uint ReadUInt32(this byte[] content, ref int offset)
+ {
+ uint value = BitConverter.ToUInt32(content, offset);
+ offset += 4;
+ return value;
+ }
+
+ ///
+ /// Read a long and increment the pointer to an array
+ ///
+ public static long ReadInt64(this byte[] content, ref int offset)
+ {
+ long value = BitConverter.ToInt64(content, offset);
+ offset += 8;
+ return value;
+ }
+
+ ///
+ /// Read a ulong and increment the pointer to an array
+ ///
+ public static ulong ReadUInt64(this byte[] content, ref int offset)
+ {
+ ulong value = BitConverter.ToUInt64(content, offset);
+ offset += 8;
+ return value;
+ }
+
+ ///
+ /// Read a null-terminated string from the stream
+ ///
+ public static string ReadString(this byte[] content, ref int offset) => content.ReadString(ref offset, Encoding.Default);
+
+ ///
+ /// Read a null-terminated string from the stream
+ ///
+ public static string ReadString(this byte[] content, ref int offset, Encoding encoding)
+ {
+ byte[] nullTerminator = encoding.GetBytes(new char[] { '\0' });
+ int charWidth = nullTerminator.Length;
+
+ List keyChars = new List();
+ while (BitConverter.ToUInt16(content, offset) != 0x0000)
+ {
+ keyChars.Add(encoding.GetChars(content, offset, charWidth)[0]); offset += charWidth;
+ }
+ offset += 2;
+
+ return new string(keyChars.ToArray());
+ }
+
+ #endregion
+
+ #region Streams
+
+ ///
+ /// Read a byte from the stream
+ ///
+ public static byte ReadByteValue(this Stream stream)
+ {
+ byte[] buffer = new byte[1];
+ stream.Read(buffer, 0, 1);
+ return buffer[0];
+ }
+
+ ///
+ /// Read a byte array from the stream
+ ///
+ public static byte[] ReadBytes(this Stream stream, int count)
+ {
+ byte[] buffer = new byte[count];
+ stream.Read(buffer, 0, count);
+ return buffer;
+ }
+
+ ///
+ /// Read a character from the stream
+ ///
+ public static char ReadChar(this Stream stream)
+ {
+ byte[] buffer = new byte[1];
+ stream.Read(buffer, 0, 1);
+ return (char)buffer[0];
+ }
+
+ ///
+ /// Read a character array from the stream
+ ///
+ public static char[] ReadChars(this Stream stream, int count) => stream.ReadChars(count, Encoding.Default);
+
+ ///
+ /// Read a character array from the stream
+ ///
+ public static char[] ReadChars(this Stream stream, int count, Encoding encoding)
+ {
+ byte[] buffer = new byte[count];
+ stream.Read(buffer, 0, count);
+ return encoding.GetString(buffer).ToCharArray();
+ }
+
+ ///
+ /// Read a short from the stream
+ ///
+ public static short ReadInt16(this Stream stream)
+ {
+ byte[] buffer = new byte[2];
+ stream.Read(buffer, 0, 2);
+ return BitConverter.ToInt16(buffer, 0);
+ }
+
+ ///
+ /// Read a ushort from the stream
+ ///
+ public static ushort ReadUInt16(this Stream stream)
+ {
+ byte[] buffer = new byte[2];
+ stream.Read(buffer, 0, 2);
+ return BitConverter.ToUInt16(buffer, 0);
+ }
+
+ ///
+ /// Read an int from the stream
+ ///
+ public static int ReadInt32(this Stream stream)
+ {
+ byte[] buffer = new byte[4];
+ stream.Read(buffer, 0, 4);
+ return BitConverter.ToInt32(buffer, 0);
+ }
+
+ ///
+ /// Read a uint from the stream
+ ///
+ public static uint ReadUInt32(this Stream stream)
+ {
+ byte[] buffer = new byte[4];
+ stream.Read(buffer, 0, 4);
+ return BitConverter.ToUInt32(buffer, 0);
+ }
+
+ ///
+ /// Read a long from the stream
+ ///
+ public static long ReadInt64(this Stream stream)
+ {
+ byte[] buffer = new byte[8];
+ stream.Read(buffer, 0, 8);
+ return BitConverter.ToInt64(buffer, 0);
+ }
+
+ ///
+ /// Read a ulong from the stream
+ ///
+ public static ulong ReadUInt64(this Stream stream)
+ {
+ byte[] buffer = new byte[8];
+ stream.Read(buffer, 0, 8);
+ return BitConverter.ToUInt64(buffer, 0);
+ }
+
+ ///
+ /// Read a null-terminated string from the stream
+ ///
+ public static string ReadString(this Stream stream) => stream.ReadString(Encoding.Default);
+
+ ///
+ /// Read a null-terminated string from the stream
+ ///
+ public static string ReadString(this Stream stream, Encoding encoding)
+ {
+ byte[] nullTerminator = encoding.GetBytes(new char[] { '\0' });
+ int charWidth = nullTerminator.Length;
+
+ List tempBuffer = new List();
+
+ byte[] buffer = new byte[charWidth];
+ while (stream.Read(buffer, 0, charWidth) != 0 && buffer.SequenceEqual(nullTerminator))
+ {
+ tempBuffer.AddRange(buffer);
+ }
+
+ return encoding.GetString(tempBuffer.ToArray());
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/BurnOutSharp.Builder/MSDOS.cs b/BurnOutSharp.Builder/MSDOS.cs
new file mode 100644
index 00000000..969cdd45
--- /dev/null
+++ b/BurnOutSharp.Builder/MSDOS.cs
@@ -0,0 +1,309 @@
+using System.IO;
+using BurnOutSharp.Models.MSDOS;
+
+namespace BurnOutSharp.Builder
+{
+ // TODO: Make Stream Data rely on Byte Data
+ public static class MSDOS
+ {
+ #region Byte Data
+
+ ///
+ /// Parse a byte array into an MS-DOS executable
+ ///
+ /// Byte array to parse
+ /// Offset into the byte array
+ /// Filled executable on success, null on error
+ public static Executable ParseExecutable(byte[] data, int offset)
+ {
+ // If the data is invalid
+ if (data == null)
+ return null;
+
+ // If the offset is out of bounds
+ if (offset < 0 || offset >= data.Length)
+ return null;
+
+ // Cache the current offset
+ int initialOffset = offset;
+
+ // Create a new executable to fill
+ var executable = new Executable();
+
+ // Try to parse the executable header
+ var executableHeader = ParseExecutableHeader(data, offset);
+ if (executableHeader == null)
+ return null;
+
+ // Set the executable header
+ executable.Header = executableHeader;
+
+ // If the offset for the relocation table doesn't exist
+ int tableAddress = initialOffset + executableHeader.RelocationTableAddr;
+ if (tableAddress >= data.Length)
+ return executable;
+
+ // Try to parse the relocation table
+ var relocationTable = ParseRelocationTable(data, tableAddress, executableHeader.RelocationItems);
+ if (relocationTable == null)
+ return null;
+
+ // Set the relocation table
+ executable.RelocationTable = relocationTable;
+
+ // Return the executable
+ return executable;
+ }
+
+ ///
+ /// Parse a byte array into an MS-DOS executable header
+ ///
+ /// Byte array to parse
+ /// Offset into the byte array
+ /// Filled executable header on success, null on error
+ private static ExecutableHeader ParseExecutableHeader(byte[] data, int offset)
+ {
+ // If we don't have enough data
+ if (data.Length < 28)
+ return null;
+
+ // If the offset means we don't have enough data
+ if (data.Length - offset < 28)
+ return null;
+
+ // TODO: Use marshalling here instead of building
+ var header = new ExecutableHeader();
+
+ #region Standard Fields
+
+ header.Magic = new char[2];
+ for (int i = 0; i < header.Magic.Length; i++)
+ {
+ header.Magic[i] = (char)data[offset]; offset++;
+ }
+ if (header.Magic[0] != 'M' || header.Magic[1] != 'Z')
+ return null;
+
+ header.LastPageBytes = data.ReadUInt16(ref offset);
+ header.Pages = data.ReadUInt16(ref offset);
+ header.RelocationItems = data.ReadUInt16(ref offset);
+ header.HeaderParagraphSize = data.ReadUInt16(ref offset);
+ header.MinimumExtraParagraphs = data.ReadUInt16(ref offset);
+ header.MaximumExtraParagraphs = data.ReadUInt16(ref offset);
+ header.InitialSSValue = data.ReadUInt16(ref offset);
+ header.InitialSPValue = data.ReadUInt16(ref offset);
+ header.Checksum = data.ReadUInt16(ref offset);
+ header.InitialIPValue = data.ReadUInt16(ref offset);
+ header.InitialCSValue = data.ReadUInt16(ref offset);
+ header.RelocationTableAddr = data.ReadUInt16(ref offset);
+ header.OverlayNumber = data.ReadUInt16(ref offset);
+
+ #endregion
+
+ // If we don't have enough data for PE extensions
+ if (offset >= data.Length || data.Length - offset < 36)
+ return header;
+
+ #region PE Extensions
+
+ header.Reserved1 = new ushort[4];
+ for (int i = 0; i < header.Reserved1.Length; i++)
+ {
+ header.Reserved1[i] = data.ReadUInt16(ref offset);
+ }
+ header.OEMIdentifier = data.ReadUInt16(ref offset);
+ header.OEMInformation = data.ReadUInt16(ref offset);
+ header.Reserved2 = new ushort[10];
+ for (int i = 0; i < header.Reserved1.Length; i++)
+ {
+ header.Reserved2[i] = data.ReadUInt16(ref offset);
+ }
+ header.NewExeHeaderAddr = data.ReadUInt32(ref offset);
+
+ #endregion
+
+ return header;
+ }
+
+ ///
+ /// Parse a byte array into a relocation table
+ ///
+ /// Byte array to parse
+ /// Offset into the byte array
+ /// Filled executable header on success, null on error
+ private static RelocationEntry[] ParseRelocationTable(byte[] data, int offset, int count)
+ {
+ // If we don't have enough data
+ if (data.Length < (count * 4))
+ return null;
+
+ // If the offset means we don't have enough data
+ if (data.Length - offset < (count * 4))
+ return null;
+
+ // TODO: Use marshalling here instead of building
+ var relocationTable = new RelocationEntry[count];
+
+ for (int i = 0; i < count; i++)
+ {
+ var entry = new RelocationEntry();
+ entry.Offset = data.ReadUInt16(ref offset);
+ entry.Segment = data.ReadUInt16(ref offset);
+ relocationTable[i] = entry;
+ }
+
+ return relocationTable;
+ }
+
+ #endregion
+
+ #region Stream Data
+
+ ///
+ /// Parse a byte array into an MS-DOS executable
+ ///
+ /// Stream to parse
+ /// Filled executable on success, null on error
+ public static Executable ParseExecutable(Stream data)
+ {
+ // If the data is invalid
+ if (data == null)
+ return null;
+
+ // If the offset is out of bounds
+ if (data.Position < 0 || data.Position >= data.Length)
+ return null;
+
+ // Cache the current offset
+ int initialOffset = (int)data.Position;
+
+ // Create a new executable to fill
+ var executable = new Executable();
+
+ // Try to parse the executable header
+ var executableHeader = ParseExecutableHeader(data);
+ if (executableHeader == null)
+ return null;
+
+ // Set the executable header
+ executable.Header = executableHeader;
+
+ // If the offset for the relocation table doesn't exist
+ int tableAddress = initialOffset + executableHeader.RelocationTableAddr;
+ if (tableAddress >= data.Length)
+ return executable;
+
+ // Try to parse the relocation table
+ data.Seek(tableAddress, SeekOrigin.Begin);
+ var relocationTable = ParseRelocationTable(data, executableHeader.RelocationItems);
+ if (relocationTable == null)
+ return null;
+
+ // Set the relocation table
+ executable.RelocationTable = relocationTable;
+
+ // Return the executable
+ return executable;
+ }
+
+ ///
+ /// Parse a byte array into an MS-DOS executable header
+ ///
+ /// Stream to parse
+ /// Filled executable header on success, null on error
+ private static ExecutableHeader ParseExecutableHeader(Stream data)
+ {
+ // If we don't have enough data
+ if (data.Length < 28)
+ return null;
+
+ // If the offset means we don't have enough data
+ if (data.Length - data.Position < 28)
+ return null;
+
+ // TODO: Use marshalling here instead of building
+ var header = new ExecutableHeader();
+
+ #region Standard Fields
+
+ header.Magic = new char[2];
+ for (int i = 0; i < header.Magic.Length; i++)
+ {
+ header.Magic[i] = data.ReadChar();
+ }
+ if (header.Magic[0] != 'M' || header.Magic[1] != 'Z')
+ return null;
+
+ header.LastPageBytes = data.ReadUInt16();
+ header.Pages = data.ReadUInt16();
+ header.RelocationItems = data.ReadUInt16();
+ header.HeaderParagraphSize = data.ReadUInt16();
+ header.MinimumExtraParagraphs = data.ReadUInt16();
+ header.MaximumExtraParagraphs = data.ReadUInt16();
+ header.InitialSSValue = data.ReadUInt16();
+ header.InitialSPValue = data.ReadUInt16();
+ header.Checksum = data.ReadUInt16();
+ header.InitialIPValue = data.ReadUInt16();
+ header.InitialCSValue = data.ReadUInt16();
+ header.RelocationTableAddr = data.ReadUInt16();
+ header.OverlayNumber = data.ReadUInt16();
+
+ #endregion
+
+ // If we don't have enough data for PE extensions
+ if (data.Position >= data.Length || data.Length - data.Position < 36)
+ return header;
+
+ #region PE Extensions
+
+ header.Reserved1 = new ushort[4];
+ for (int i = 0; i < header.Reserved1.Length; i++)
+ {
+ header.Reserved1[i] = data.ReadUInt16();
+ }
+ header.OEMIdentifier = data.ReadUInt16();
+ header.OEMInformation = data.ReadUInt16();
+ header.Reserved2 = new ushort[10];
+ for (int i = 0; i < header.Reserved1.Length; i++)
+ {
+ header.Reserved2[i] = data.ReadUInt16();
+ }
+ header.NewExeHeaderAddr = data.ReadUInt32();
+
+ #endregion
+
+ return header;
+ }
+
+ ///
+ /// Parse a byte array into a relocation table
+ ///
+ /// Stream to parse
+ /// Filled executable header on success, null on error
+ private static RelocationEntry[] ParseRelocationTable(Stream data, int count)
+ {
+ // If we don't have enough data
+ if (data.Length < (count * 4))
+ return null;
+
+ // If the offset means we don't have enough data
+ if (data.Length - data.Position < (count * 4))
+ return null;
+
+ // TODO: Use marshalling here instead of building
+ var relocationTable = new RelocationEntry[count];
+
+ for (int i = 0; i < count; i++)
+ {
+ var entry = new RelocationEntry();
+ entry.Offset = data.ReadUInt16();
+ entry.Segment = data.ReadUInt16();
+ relocationTable[i] = entry;
+ }
+
+ return relocationTable;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/BurnOutSharp.Models/MSDOS/ExecutableHeader.cs b/BurnOutSharp.Models/MSDOS/ExecutableHeader.cs
index bbdbd37d..fdb56562 100644
--- a/BurnOutSharp.Models/MSDOS/ExecutableHeader.cs
+++ b/BurnOutSharp.Models/MSDOS/ExecutableHeader.cs
@@ -123,7 +123,7 @@ namespace BurnOutSharp.Models.MSDOS
///
/// Starting address of the PE header
///
- public int NewExeHeaderAddr;
+ public uint NewExeHeaderAddr;
#endregion
}
diff --git a/BurnOutSharp.sln b/BurnOutSharp.sln
index e1dc5e0a..05dd7a56 100644
--- a/BurnOutSharp.sln
+++ b/BurnOutSharp.sln
@@ -22,7 +22,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.Compression.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.Compression", "Dtf\src\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj", "{3065BE52-CB56-4557-80CD-E3AC57C242F2}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BurnOutSharp.Models", "BurnOutSharp.Models\BurnOutSharp.Models.csproj", "{44939210-BDC2-4250-BC0F-0AB8F316F09D}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BurnOutSharp.Models", "BurnOutSharp.Models\BurnOutSharp.Models.csproj", "{44939210-BDC2-4250-BC0F-0AB8F316F09D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BurnOutSharp.Builder", "BurnOutSharp.Builder\BurnOutSharp.Builder.csproj", "{7577733A-CC8D-4E7C-8B6D-FFC7EC1B3D07}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -58,6 +60,10 @@ Global
{44939210-BDC2-4250-BC0F-0AB8F316F09D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44939210-BDC2-4250-BC0F-0AB8F316F09D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44939210-BDC2-4250-BC0F-0AB8F316F09D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7577733A-CC8D-4E7C-8B6D-FFC7EC1B3D07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7577733A-CC8D-4E7C-8B6D-FFC7EC1B3D07}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7577733A-CC8D-4E7C-8B6D-FFC7EC1B3D07}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7577733A-CC8D-4E7C-8B6D-FFC7EC1B3D07}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE