diff --git a/BurnOutSharp.Builder/BFPK.cs b/BurnOutSharp.Builder/BFPK.cs
new file mode 100644
index 00000000..30fccbed
--- /dev/null
+++ b/BurnOutSharp.Builder/BFPK.cs
@@ -0,0 +1,231 @@
+using System.IO;
+using System.Linq;
+using BurnOutSharp.Models.BFPK;
+
+namespace BurnOutSharp.Builder
+{
+ // TODO: Make Stream Data rely on Byte Data
+ public class BFPK
+ {
+ #region Byte Data
+
+ ///
+ /// Parse a byte array into a MoPaQ archive
+ ///
+ /// Byte array to parse
+ /// Offset into the byte array
+ /// Filled archive on success, null on error
+ public static Archive ParseArchive(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 archive to fill
+ var archive = new Archive();
+
+ #region Header
+
+ // Try to parse the header
+ var header = ParseHeader(data, ref offset);
+ if (header == null)
+ return null;
+
+ // Set the archive header
+ archive.Header = header;
+
+ #endregion
+
+ #region Files
+
+ // If we have any files
+ if (header.Files > 0)
+ {
+ var files = new FileEntry[header.Files];
+
+ // Read all entries in turn
+ for (int i = 0; i < header.Files; i++)
+ {
+ var file = ParseFileEntry(data, ref offset);
+ if (file == null)
+ return null;
+
+ files[i] = file;
+ }
+
+ // Set the files
+ archive.Files = files;
+ }
+
+ #endregion
+
+ return archive;
+ }
+
+ ///
+ /// Parse a byte array into a header
+ ///
+ /// Byte array to parse
+ /// Offset into the byte array
+ /// Filled header on success, null on error
+ private static Header ParseHeader(byte[] data, ref int offset)
+ {
+ // TODO: Use marshalling here instead of building
+ Header header = new Header();
+
+ header.Magic = data.ReadUInt32(ref offset);
+ if (header.Magic != 0x4b504642)
+ return null;
+
+ header.Version = data.ReadInt32(ref offset);
+ header.Files = data.ReadInt32(ref offset);
+
+ return header;
+ }
+
+ ///
+ /// Parse a byte array into a file entry
+ ///
+ /// Byte array to parse
+ /// Offset into the byte array
+ /// Filled file entry on success, null on error
+ private static FileEntry ParseFileEntry(byte[] data, ref int offset)
+ {
+ // TODO: Use marshalling here instead of building
+ FileEntry fileEntry = new FileEntry();
+
+ fileEntry.NameSize = data.ReadInt32(ref offset);
+ if (fileEntry.NameSize > 0)
+ fileEntry.Name = new string(data.ReadBytes(ref offset, fileEntry.NameSize).Select(b => (char)b).ToArray());
+
+ fileEntry.UncompressedSize = data.ReadInt32(ref offset);
+ fileEntry.Offset = data.ReadInt32(ref offset);
+ if (fileEntry.Offset > 0)
+ {
+ int entryOffset = fileEntry.Offset;
+ fileEntry.CompressedSize = data.ReadInt32(ref entryOffset);
+ }
+
+ return fileEntry;
+ }
+
+ #endregion
+
+ #region Stream Data
+
+ ///
+ /// Parse a Stream into a MoPaQ archive
+ ///
+ /// Stream to parse
+ /// Filled archive on success, null on error
+ public static Archive ParseArchive(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 archive to fill
+ var archive = new Archive();
+
+ #region Header
+
+ // Try to parse the header
+ var header = ParseHeader(data);
+ if (header == null)
+ return null;
+
+ // Set the archive header
+ archive.Header = header;
+
+ #endregion
+
+ #region Files
+
+ // If we have any files
+ if (header.Files > 0)
+ {
+ var files = new FileEntry[header.Files];
+
+ // Read all entries in turn
+ for (int i = 0; i < header.Files; i++)
+ {
+ var file = ParseFileEntry(data);
+ if (file == null)
+ return null;
+
+ files[i] = file;
+ }
+
+ // Set the files
+ archive.Files = files;
+ }
+
+ #endregion
+
+ return archive;
+ }
+
+ ///
+ /// Parse a Stream into a header
+ ///
+ /// Stream to parse
+ /// Filled header on success, null on error
+ private static Header ParseHeader(Stream data)
+ {
+ // TODO: Use marshalling here instead of building
+ Header header = new Header();
+
+ header.Magic = data.ReadUInt32();
+ if (header.Magic != 0x4b504642)
+ return null;
+
+ header.Version = data.ReadInt32();
+ header.Files = data.ReadInt32();
+
+ return header;
+ }
+
+ ///
+ /// Parse a Stream into a file entry
+ ///
+ /// Stream to parse
+ /// Filled file entry on success, null on error
+ private static FileEntry ParseFileEntry(Stream data)
+ {
+ // TODO: Use marshalling here instead of building
+ FileEntry fileEntry = new FileEntry();
+
+ fileEntry.NameSize = data.ReadInt32();
+ if (fileEntry.NameSize > 0)
+ fileEntry.Name = new string(data.ReadBytes(fileEntry.NameSize).Select(b => (char)b).ToArray());
+
+ fileEntry.UncompressedSize = data.ReadInt32();
+ fileEntry.Offset = data.ReadInt32();
+ if (fileEntry.Offset > 0)
+ {
+ long currentOffset = data.Position;
+ data.Seek(fileEntry.Offset, SeekOrigin.Begin);
+ fileEntry.CompressedSize = data.ReadInt32();
+ data.Seek(currentOffset, SeekOrigin.Begin);
+ }
+
+ return fileEntry;
+ }
+
+ #endregion
+ }
+}