using System.IO; using System.Linq; using BurnOutSharp.Models.BFPK; using BurnOutSharp.Utilities; namespace BurnOutSharp.Builders { 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; // Create a memory stream and parse that MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset); return ParseArchive(dataStream); } #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 || data.Length == 0 || !data.CanSeek || !data.CanRead) 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 } }