using System.IO; using System.Text; namespace BinaryObjectScanner.Wrappers { public class PFF : WrapperBase { #region Descriptive Properties /// public override string DescriptionString => "NovaLogic Game Archive Format (PFF)"; #endregion #region Pass-Through Properties #region Header /// #if NET48 public uint HeaderSize => _model.Header.HeaderSize; #else public uint? HeaderSize => _model.Header?.HeaderSize; #endif /// #if NET48 public string Signature => _model.Header.Signature; #else public string? Signature => _model.Header?.Signature; #endif /// #if NET48 public uint NumberOfFiles => _model.Header.NumberOfFiles; #else public uint? NumberOfFiles => _model.Header?.NumberOfFiles; #endif /// #if NET48 public uint FileSegmentSize => _model.Header.FileSegmentSize; #else public uint? FileSegmentSize => _model.Header?.FileSegmentSize; #endif /// #if NET48 public uint FileListOffset => _model.Header.FileListOffset; #else public uint? FileListOffset => _model.Header?.FileListOffset; #endif #endregion #region Segments /// #if NET48 public SabreTools.Models.PFF.Segment[] Segments => _model.Segments; #else public SabreTools.Models.PFF.Segment?[]? Segments => _model.Segments; #endif #endregion #region Footer /// #if NET48 public uint SystemIP => _model.Footer.SystemIP; #else public uint? SystemIP => _model.Footer?.SystemIP; #endif /// #if NET48 public uint Reserved => _model.Footer.Reserved; #else public uint? Reserved => _model.Footer?.Reserved; #endif /// #if NET48 public string KingTag => _model.Footer.KingTag; #else public string? KingTag => _model.Footer?.KingTag; #endif #endregion #endregion #region Constructors /// #if NET48 public PFF(SabreTools.Models.PFF.Archive model, byte[] data, int offset) #else public PFF(SabreTools.Models.PFF.Archive? model, byte[]? data, int offset) #endif : base(model, data, offset) { // All logic is handled by the base class } /// #if NET48 public PFF(SabreTools.Models.PFF.Archive model, Stream data) #else public PFF(SabreTools.Models.PFF.Archive? model, Stream? data) #endif : base(model, data) { // All logic is handled by the base class }/// /// Create a PFF archive from a byte array and offset /// /// Byte array representing the archive /// Offset within the array to parse /// A PFF archive wrapper on success, null on failure #if NET48 public static PFF Create(byte[] data, int offset) #else public static PFF? Create(byte[]? data, int offset) #endif { // 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 a PFF archive from a Stream /// /// Stream representing the archive /// A PFF archive wrapper on success, null on failure #if NET48 public static PFF Create(Stream data) #else public static PFF? Create(Stream? data) #endif { // If the data is invalid if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead) return null; var archive = new SabreTools.Serialization.Streams.PFF().Deserialize(data); if (archive == null) return null; try { return new PFF(archive, data); } catch { return null; } } #endregion #region Data /// /// Extract all segments from the PFF to an output directory /// /// Output directory to write to /// True if all segments extracted, false otherwise public bool ExtractAll(string outputDirectory) { // If we have no segments if (Segments == null || Segments.Length == 0) return false; // Loop through and extract all files to the output bool allExtracted = true; for (int i = 0; i < Segments.Length; i++) { allExtracted &= ExtractSegment(i, outputDirectory); } return allExtracted; } /// /// Extract a segment from the PFF to an output directory by index /// /// Segment index to extract /// Output directory to write to /// True if the segment extracted, false otherwise public bool ExtractSegment(int index, string outputDirectory) { // If we have no segments if (NumberOfFiles == 0 || Segments == null || Segments.Length == 0) return false; // If we have an invalid index if (index < 0 || index >= Segments.Length) return false; // Get the segment information var file = Segments[index]; if (file == null) return false; // Get the read index and length int offset = (int)file.FileLocation; int size = (int)file.FileSize; try { // Ensure the output directory exists Directory.CreateDirectory(outputDirectory); // Create the output path string filePath = Path.Combine(outputDirectory, file.FileName ?? $"file{index}"); using (FileStream fs = File.OpenWrite(filePath)) { // Read the data block #if NET48 byte[] data = ReadFromDataSource(offset, size); #else byte[]? data = ReadFromDataSource(offset, size); #endif if (data == null) return false; // Write the data -- TODO: Compressed data? fs.Write(data, 0, size); } return true; } catch { return false; } } #endregion #region Printing /// public override StringBuilder PrettyPrint() { StringBuilder builder = new StringBuilder(); Printing.PFF.Print(builder, _model); return builder; } #if NET6_0_OR_GREATER /// public override string ExportJSON() => System.Text.Json.JsonSerializer.Serialize(_model, _jsonSerializerOptions); #endif #endregion } }