mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-07-08 18:06:41 +00:00
Start implementing Advanced Installer reader
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SabreTools.Serialization.Readers;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Serialization.Test.Readers
|
||||
{
|
||||
public class AdvancedInstallerTests
|
||||
{
|
||||
[Fact]
|
||||
public void NullArray_Null()
|
||||
{
|
||||
byte[]? data = null;
|
||||
int offset = 0;
|
||||
var deserializer = new AdvancedInstaller();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_Null()
|
||||
{
|
||||
byte[]? data = [];
|
||||
int offset = 0;
|
||||
var deserializer = new AdvancedInstaller();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidArray_Null()
|
||||
{
|
||||
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
|
||||
int offset = 0;
|
||||
var deserializer = new AdvancedInstaller();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullStream_Null()
|
||||
{
|
||||
Stream? data = null;
|
||||
var deserializer = new AdvancedInstaller();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([]);
|
||||
var deserializer = new AdvancedInstaller();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
|
||||
var deserializer = new AdvancedInstaller();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace SabreTools.Data.Models.AdvancedInstaller
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public static readonly byte[] SignatureBytes = [0x41, 0x44, 0x56, 0x49, 0x4E, 0x53, 0x54, 0x53, 0x46, 0x58];
|
||||
|
||||
public const string SignatureString = "ADVINSTSFX";
|
||||
}
|
||||
}
|
||||
180
SabreTools.Serialization/Readers/AdvancedInstaller.cs
Normal file
180
SabreTools.Serialization/Readers/AdvancedInstaller.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.Data.Models.AdvancedInstaller;
|
||||
using SabreTools.IO.Extensions;
|
||||
using static SabreTools.Data.Models.AdvancedInstaller.Constants;
|
||||
|
||||
namespace SabreTools.Serialization.Readers
|
||||
{
|
||||
public class AdvancedInstaller : BaseBinaryReader<SFX>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override SFX? Deserialize(Stream? data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Cache the current offset
|
||||
long initialOffset = data.Position;
|
||||
|
||||
// Create a new SFX to populate
|
||||
var sfx = new SFX();
|
||||
|
||||
// Try to parse the footer from the data end
|
||||
var footer = ParseFooter(data, initialOffset);
|
||||
if (footer == null)
|
||||
return null;
|
||||
if (footer.Signature != SignatureString)
|
||||
return null;
|
||||
|
||||
// Set the footer
|
||||
sfx.Footer = footer;
|
||||
|
||||
// Seek to the entry table offset
|
||||
long tableOffset = initialOffset + footer.TablePointer;
|
||||
if (tableOffset < initialOffset || tableOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Try to parse the entry table
|
||||
var table = ParseTable(data, footer.EntryCount);
|
||||
if (table.Length != footer.EntryCount)
|
||||
return null;
|
||||
|
||||
// Set the entry table
|
||||
sfx.Entries = table;
|
||||
|
||||
return sfx;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a Footer
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="initialOffset">Initial offset to use in address comparisons</param>
|
||||
/// <returns>Filled Footer on success, null on error</returns>
|
||||
public static Footer? ParseFooter(Stream data, long initialOffset)
|
||||
{
|
||||
// Seek to the end of the end of the data
|
||||
data.Seek(0, SeekOrigin.End);
|
||||
|
||||
// Cache the current offset
|
||||
long endOffset = data.Position;
|
||||
|
||||
// Set a maximum of 10 iterations to find the signature
|
||||
int iterations = 10;
|
||||
|
||||
// Search backward for the signature
|
||||
bool signatureFound = false;
|
||||
do
|
||||
{
|
||||
byte[] tempBytes = data.ReadBytes(10);
|
||||
string tempStr = Encoding.ASCII.GetString(tempBytes);
|
||||
if (tempStr == SignatureString)
|
||||
{
|
||||
signatureFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
iterations--;
|
||||
|
||||
} while (iterations > 0);
|
||||
|
||||
// If no signature was found
|
||||
if (!signatureFound)
|
||||
return null;
|
||||
|
||||
// Seek to the first footer offset field
|
||||
data.Seek(-70, SeekOrigin.Current);
|
||||
|
||||
// Find the actual offset of the start of the footer
|
||||
uint footerStart = data.ReadUInt32LittleEndian();
|
||||
if (footerStart > endOffset)
|
||||
return null;
|
||||
|
||||
// If the offset is immediately prior, no name exists
|
||||
bool shortFooter = footerStart == data.Position - 8;
|
||||
|
||||
// Seek to the start of the footer
|
||||
data.Seek(initialOffset + footerStart, SeekOrigin.Begin);
|
||||
|
||||
var obj = new Footer();
|
||||
|
||||
obj.Unknown0 = data.ReadUInt32LittleEndian();
|
||||
|
||||
// TODO: Make this block safer
|
||||
if (!shortFooter)
|
||||
{
|
||||
obj.OriginalFilenameSize = data.ReadUInt32LittleEndian();
|
||||
byte[] filenameBytes = data.ReadBytes((int)obj.OriginalFilenameSize * 2);
|
||||
obj.OriginalFilename = Encoding.Unicode.GetString(filenameBytes);
|
||||
obj.Unknown1 = data.ReadUInt32LittleEndian();
|
||||
}
|
||||
|
||||
obj.FooterOffset = data.ReadUInt32LittleEndian();
|
||||
obj.EntryCount = data.ReadUInt32LittleEndian();
|
||||
obj.Unknown2 = data.ReadUInt32LittleEndian();
|
||||
obj.UnknownOffset = data.ReadUInt32LittleEndian();
|
||||
obj.TablePointer = data.ReadUInt32LittleEndian();
|
||||
obj.FileDataStart = data.ReadUInt32LittleEndian();
|
||||
byte[] hexStringBytes = data.ReadBytes(32);
|
||||
obj.HexString = Encoding.ASCII.GetString(hexStringBytes);
|
||||
obj.Unknown3 = data.ReadUInt32LittleEndian();
|
||||
byte[] signatureBytes = data.ReadBytes(10);
|
||||
obj.Signature = Encoding.ASCII.GetString(signatureBytes);
|
||||
// TODO: Handle padding?
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an entry table
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="count">Number of entries to parse</param>
|
||||
/// <returns>Filled entry table on success, null on error</returns>
|
||||
public static FileEntry[] ParseTable(Stream data, uint count)
|
||||
{
|
||||
if (count == 0)
|
||||
return [];
|
||||
|
||||
var obj = new FileEntry[count];
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
obj[i] = ParseFileEntry(data);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a Footer
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="count">Number of entries to parse</param>
|
||||
/// <returns>Filled Footer on success, null on error</returns>
|
||||
public static FileEntry ParseFileEntry(Stream data)
|
||||
{
|
||||
var obj = new FileEntry();
|
||||
|
||||
obj.Unknown0 = data.ReadUInt32LittleEndian();
|
||||
obj.Unknown1 = data.ReadUInt32LittleEndian();
|
||||
obj.Unknown2 = data.ReadUInt32LittleEndian();
|
||||
obj.FileSize = data.ReadUInt32LittleEndian();
|
||||
obj.FileOffset = data.ReadUInt32LittleEndian();
|
||||
obj.NameSize = data.ReadUInt32LittleEndian();
|
||||
byte[] nameBytes = data.ReadBytes((int)obj.NameSize * 2);
|
||||
obj.Name = Encoding.Unicode.GetString(nameBytes);
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user