Files
SabreTools.Serialization/SabreTools.Serialization.Readers/PFF.cs

171 lines
5.7 KiB
C#
Raw Normal View History

2023-09-08 22:55:06 -04:00
using System.IO;
using System.Text;
2025-09-26 13:06:18 -04:00
using SabreTools.Data.Models.PFF;
2024-04-17 11:52:22 -04:00
using SabreTools.IO.Extensions;
2026-03-24 19:17:25 -04:00
using SabreTools.Numerics.Extensions;
2025-09-26 13:06:18 -04:00
using static SabreTools.Data.Models.PFF.Constants;
2023-09-08 22:55:06 -04:00
2026-01-27 12:03:01 -05:00
#pragma warning disable IDE0017 // Simplify object initialization
2025-09-26 14:57:20 -04:00
namespace SabreTools.Serialization.Readers
2023-09-08 22:55:06 -04:00
{
public class PFF : BaseBinaryReader<Archive>
2023-09-08 22:55:06 -04:00
{
/// <inheritdoc/>
public override Archive? Deserialize(Stream? data)
{
// If the data is invalid
2026-01-25 14:30:18 -05:00
if (data is null || !data.CanRead)
return null;
try
{
2025-08-19 09:09:33 -04:00
// Cache the current offset
long initialOffset = data.Position;
// Create a new archive to fill
var archive = new Archive();
#region Header
// Try to parse the header
var header = ParseHeader(data);
2024-12-16 23:08:45 -05:00
if (header.Signature == Version0SignatureString)
{
if (header.FileSegmentSize != Version0HSegmentSize)
return null;
}
else if (header.Signature == Version2SignatureString)
{
if (header.FileSegmentSize != Version2SegmentSize)
return null;
}
else if (header.Signature == Version3SignatureString)
{
if (header.FileSegmentSize != Version2SegmentSize
&& header.FileSegmentSize != Version3SegmentSize)
{
return null;
}
}
else if (header.Signature == Version4SignatureString)
{
if (header.FileSegmentSize != Version4SegmentSize)
return null;
}
else
{
return null;
2024-12-16 23:08:45 -05:00
}
// Set the archive header
archive.Header = header;
#endregion
#region Segments
// Get the segments
2025-08-19 09:09:33 -04:00
long offset = initialOffset + header.FileListOffset;
if (offset < initialOffset || offset >= data.Length)
return null;
// Seek to the segments
2025-10-27 22:43:56 -04:00
data.SeekIfPossible(offset, SeekOrigin.Begin);
// Create the segments array
archive.Segments = new Segment[header.NumberOfFiles];
// Read all segments in turn
for (int i = 0; i < header.NumberOfFiles; i++)
{
2024-12-17 15:43:22 -05:00
archive.Segments[i] = ParseSegment(data, header.FileSegmentSize);
}
#endregion
#region Footer
// Get the footer offset
2025-08-19 09:09:33 -04:00
offset = initialOffset + header.FileListOffset + (header.FileSegmentSize * header.NumberOfFiles);
if (offset < initialOffset || offset >= data.Length)
return null;
// Seek to the footer
2025-10-27 22:43:56 -04:00
data.SeekIfPossible(offset, SeekOrigin.Begin);
// Set the archive footer
2024-12-16 23:08:45 -05:00
archive.Footer = ParseFooter(data);
#endregion
return archive;
}
catch
{
// Ignore the actual error
return null;
}
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a Footer
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled Footer on success, null on error</returns>
public static Footer ParseFooter(Stream data)
{
2024-12-16 23:08:45 -05:00
var obj = new Footer();
obj.SystemIP = data.ReadUInt32LittleEndian();
obj.Reserved = data.ReadUInt32LittleEndian();
byte[] kingTag = data.ReadBytes(4);
obj.KingTag = Encoding.ASCII.GetString(kingTag);
2024-12-16 23:08:45 -05:00
return obj;
}
2024-12-16 23:08:45 -05:00
/// <summary>
/// Parse a Stream into a Header
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled Header on success, null on error</returns>
public static Header ParseHeader(Stream data)
{
var obj = new Header();
2024-04-23 21:00:10 -04:00
2024-12-16 23:08:45 -05:00
obj.HeaderSize = data.ReadUInt32LittleEndian();
byte[] signature = data.ReadBytes(4);
obj.Signature = Encoding.ASCII.GetString(signature);
obj.NumberOfFiles = data.ReadUInt32LittleEndian();
obj.FileSegmentSize = data.ReadUInt32LittleEndian();
obj.FileListOffset = data.ReadUInt32LittleEndian();
2024-04-23 21:00:10 -04:00
2024-12-16 23:08:45 -05:00
return obj;
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a Segment
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="segmentSize">PFF segment size</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled Segment on success, null on error</returns>
public static Segment ParseSegment(Stream data, uint segmentSize)
{
2024-12-16 23:08:45 -05:00
var obj = new Segment();
2024-12-16 23:08:45 -05:00
obj.Deleted = data.ReadUInt32LittleEndian();
obj.FileLocation = data.ReadUInt32LittleEndian();
obj.FileSize = data.ReadUInt32LittleEndian();
obj.PackedDate = data.ReadUInt32LittleEndian();
2024-11-27 13:47:07 -05:00
byte[] fileName = data.ReadBytes(0x10);
2024-12-16 23:08:45 -05:00
obj.FileName = Encoding.ASCII.GetString(fileName).TrimEnd('\0');
if (segmentSize > Version2SegmentSize)
2024-12-16 23:08:45 -05:00
obj.ModifiedDate = data.ReadUInt32LittleEndian();
if (segmentSize > Version3SegmentSize)
2024-12-16 23:08:45 -05:00
obj.CompressionLevel = data.ReadUInt32LittleEndian();
2024-12-16 23:08:45 -05:00
return obj;
}
2023-09-08 22:55:06 -04:00
}
}