diff --git a/BurnOutSharp.Wrappers/VPK.cs b/BurnOutSharp.Wrappers/VPK.cs
new file mode 100644
index 00000000..490a52e5
--- /dev/null
+++ b/BurnOutSharp.Wrappers/VPK.cs
@@ -0,0 +1,323 @@
+using System;
+using System.IO;
+
+namespace BurnOutSharp.Wrappers
+{
+ public class VPK : WrapperBase
+ {
+ #region Pass-Through Properties
+
+ #region Header
+
+ ///
+ public uint Signature => _file.Header.Signature;
+
+ ///
+ public uint Version => _file.Header.Version;
+
+ ///
+ public uint DirectoryLength => _file.Header.DirectoryLength;
+
+ #endregion
+
+ #region Extended Header
+
+ ///
+ public uint? Dummy0 => _file.ExtendedHeader?.Dummy0;
+
+ ///
+ public uint? ArchiveHashLength => _file.ExtendedHeader?.ArchiveHashLength;
+
+ ///
+ public uint? ExtraLength => _file.ExtendedHeader?.ExtraLength;
+
+ ///
+ public uint? Dummy1 => _file.ExtendedHeader?.Dummy1;
+
+ #endregion
+
+ #region Archive Hashes
+
+ ///
+ public Models.VPK.ArchiveHash[] ArchiveHashes => _file.ArchiveHashes;
+
+ #endregion
+
+ #region Directory Items
+
+ ///
+ public Models.VPK.DirectoryItem[] DirectoryItems => _file.DirectoryItems;
+
+ #endregion
+
+ #endregion
+
+ #region Extension Properties
+
+ // TODO: Determine what extension properties are needed
+
+ #endregion
+
+ #region Instance Variables
+
+ ///
+ /// Internal representation of the executable
+ ///
+ private Models.VPK.File _file;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Private constructor
+ ///
+ private VPK() { }
+
+ ///
+ /// Create an VPK from a byte array and offset
+ ///
+ /// Byte array representing the executable
+ /// Offset within the array to parse
+ /// An VPK wrapper on success, null on failure
+ public static VPK Create(byte[] data, int offset)
+ {
+ // If the data is invalid
+ if (data == null)
+ return null;
+
+ // If the offset is out of bounds
+ if (offset < 0 || offset >= data.Length)
+ return null;
+
+ // Create a memory stream and use that
+ MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset);
+ return Create(dataStream);
+ }
+
+ ///
+ /// Create anVPK from a Stream
+ ///
+ /// Stream representing the executable
+ /// An VPK wrapper on success, null on failure
+ public static VPK Create(Stream data)
+ {
+ // If the data is invalid
+ if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
+ return null;
+
+ var file = Builders.VPK.ParseFile(data);
+ if (file == null)
+ return null;
+
+ var wrapper = new VPK
+ {
+ _file = file,
+ _dataSource = DataSource.Stream,
+ _streamData = data,
+ };
+ return wrapper;
+ }
+
+ #endregion
+
+ #region Printing
+
+ ///
+ public override void Print()
+ {
+ Console.WriteLine("VPK Information:");
+ Console.WriteLine("-------------------------");
+ Console.WriteLine();
+
+ PrintHeader();
+ PrintExtendedHeader();
+ PrintArchiveHashes();
+ PrintDirectoryItems();
+ }
+
+ ///
+ /// Print header information
+ ///
+ private void PrintHeader()
+ {
+ Console.WriteLine(" Header Information:");
+ Console.WriteLine(" -------------------------");
+ Console.WriteLine($" Signature: {Signature}");
+ Console.WriteLine($" Version: {Version}");
+ Console.WriteLine($" Directory length: {DirectoryLength}");
+ Console.WriteLine();
+ }
+
+ ///
+ /// Print extended header information
+ ///
+ private void PrintExtendedHeader()
+ {
+ Console.WriteLine(" Extended Header Information:");
+ Console.WriteLine(" -------------------------");
+ if (_file.ExtendedHeader == null)
+ {
+ Console.WriteLine(" No extended header");
+ }
+ else
+ {
+ Console.WriteLine($" Dummy 0: {Dummy0}");
+ Console.WriteLine($" Archive hash length: {ArchiveHashLength}");
+ Console.WriteLine($" Extra length: {ExtraLength}");
+ Console.WriteLine($" Dummy 1: {Dummy1}");
+ Console.WriteLine();
+ }
+ }
+
+ ///
+ /// Print archive hashes information
+ ///
+ private void PrintArchiveHashes()
+ {
+ Console.WriteLine(" Archive Hashes Information:");
+ Console.WriteLine(" -------------------------");
+ if (ArchiveHashes == null || ArchiveHashes.Length == 0)
+ {
+ Console.WriteLine(" No archive hashes");
+ }
+ else
+ {
+ for (int i = 0; i < ArchiveHashes.Length; i++)
+ {
+ var archiveHash = ArchiveHashes[i];
+ Console.WriteLine($" Archive Hash {i}");
+ Console.WriteLine($" Archive index: {archiveHash.ArchiveIndex}");
+ Console.WriteLine($" Archive offset: {archiveHash.ArchiveOffset}");
+ Console.WriteLine($" Length: {archiveHash.Length}");
+ Console.WriteLine($" Hash: {BitConverter.ToString(archiveHash.Hash).Replace("-", string.Empty)}");
+ }
+ }
+ Console.WriteLine();
+ }
+
+ ///
+ /// Print directory items information
+ ///
+ private void PrintDirectoryItems()
+ {
+ Console.WriteLine(" Directory Items Information:");
+ Console.WriteLine(" -------------------------");
+ if (DirectoryItems == null || DirectoryItems.Length == 0)
+ {
+ Console.WriteLine(" No directory items");
+ }
+ else
+ {
+ for (int i = 0; i < DirectoryItems.Length; i++)
+ {
+ var directoryItem = DirectoryItems[i];
+ Console.WriteLine($" Directory Item {i}");
+ Console.WriteLine($" Extension: {directoryItem.Extension}");
+ Console.WriteLine($" Path: {directoryItem.Path}");
+ Console.WriteLine($" Name: {directoryItem.Name}");
+ PrintDirectoryEntry(directoryItem.DirectoryEntry);
+ // TODO: Print out preload data?
+ }
+ }
+ Console.WriteLine();
+ }
+
+ ///
+ /// Print directory entry information
+ ///
+ private void PrintDirectoryEntry(Models.VPK.DirectoryEntry directoryEntry)
+ {
+ if (directoryEntry == null)
+ {
+ Console.WriteLine(" Directory entry: [NULL]");
+ }
+ else
+ {
+ Console.WriteLine($" Directory entry CRC: {directoryEntry.CRC}");
+ Console.WriteLine($" Directory entry preload bytes: {directoryEntry.PreloadBytes}");
+ Console.WriteLine($" Directory entry archive index: {directoryEntry.ArchiveIndex}");
+ Console.WriteLine($" Directory entry entry offset: {directoryEntry.EntryOffset}");
+ Console.WriteLine($" Directory entry entry length: {directoryEntry.EntryLength}");
+ Console.WriteLine($" Directory entry dummy 0: {directoryEntry.Dummy0}");
+ }
+ }
+
+ #endregion
+
+ #region Extraction
+
+ ///
+ /// Extract all files from the VPK to an output directory
+ ///
+ /// Output directory to write to
+ /// True if all files extracted, false otherwise
+ public bool ExtractAll(string outputDirectory)
+ {
+ // If we have no directory items
+ if (DirectoryItems == null || DirectoryItems.Length == 0)
+ return false;
+
+ // Loop through and extract all files to the output
+ bool allExtracted = true;
+ for (int i = 0; i < DirectoryItems.Length; i++)
+ {
+ allExtracted &= ExtractFile(i, outputDirectory);
+ }
+
+ return allExtracted;
+ }
+
+ ///
+ /// Extract a file from the VPK to an output directory by index
+ ///
+ /// File index to extract
+ /// Output directory to write to
+ /// True if the file extracted, false otherwise
+ public bool ExtractFile(int index, string outputDirectory)
+ {
+ // If we have no directory items
+ if (DirectoryItems == null || DirectoryItems.Length == 0)
+ return false;
+
+ // If the directory item index is invalid
+ if (index < 0 || index >= DirectoryItems.Length)
+ return false;
+
+ // Get the directory item
+ var directoryItem = DirectoryItems[index];
+ if (directoryItem?.DirectoryEntry == null)
+ return false;
+
+ // Create the filename
+ string filename = $"{directoryItem.Name}.{directoryItem.Extension}";
+ if (!string.IsNullOrEmpty(directoryItem.Path))
+ filename = Path.Combine(directoryItem.Path, filename);
+
+ // If we have an invalid output directory
+ if (string.IsNullOrWhiteSpace(outputDirectory))
+ return false;
+
+ // Ensure the output directory is created
+ Directory.CreateDirectory(outputDirectory);
+
+ // Create the full output path
+ filename = Path.Combine(outputDirectory, filename);
+
+ // Read the directory item bytes
+ int offset = (int)directoryItem.DirectoryEntry.EntryOffset;
+ int length = (int)directoryItem.DirectoryEntry.EntryLength;
+ byte[] data = ReadFromDataSource(offset, length);
+
+ // Open the output file for writing
+ using (Stream fs = File.OpenWrite(filename))
+ {
+ fs.Write(data, 0, data.Length);
+ }
+
+ return true;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file