From ddb82842bc120352279470a88d353819c661b846 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 14 Dec 2022 22:41:17 -0800 Subject: [PATCH] Add BFPK builder --- BurnOutSharp.Builder/BFPK.cs | 231 +++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 BurnOutSharp.Builder/BFPK.cs 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 + } +}