mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-09-22 06:45:11 +00:00
Deserializers should have their own guards
This commit is contained in:
@@ -15,51 +15,55 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new media key block to fill
|
||||
var mediaKeyBlock = new MediaKeyBlock();
|
||||
|
||||
#region Records
|
||||
|
||||
// Create the records list
|
||||
var records = new List<Record>();
|
||||
|
||||
// Try to parse the records
|
||||
while (data.Position < data.Length)
|
||||
try
|
||||
{
|
||||
// Try to parse the record
|
||||
var record = ParseRecord(data);
|
||||
if (record == null)
|
||||
return null;
|
||||
// Create a new media key block to fill
|
||||
var mediaKeyBlock = new MediaKeyBlock();
|
||||
|
||||
// Add the record
|
||||
records.Add(record);
|
||||
#region Records
|
||||
|
||||
// If we have an end of media key block record
|
||||
if (record.RecordType == RecordType.EndOfMediaKeyBlock)
|
||||
break;
|
||||
// Create the records list
|
||||
var records = new List<Record>();
|
||||
|
||||
// Align to the 4-byte boundary if we're not at the end
|
||||
if (data.Position < data.Length)
|
||||
// Try to parse the records
|
||||
while (data.Position < data.Length)
|
||||
{
|
||||
while (data.Position < data.Length && (data.Position % 4) != 0)
|
||||
_ = data.ReadByteValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
// Try to parse the record
|
||||
var record = ParseRecord(data);
|
||||
if (record == null)
|
||||
return null;
|
||||
|
||||
// Add the record
|
||||
records.Add(record);
|
||||
|
||||
// If we have an end of media key block record
|
||||
if (record.RecordType == RecordType.EndOfMediaKeyBlock)
|
||||
break;
|
||||
|
||||
// Align to the 4-byte boundary if we're not at the end
|
||||
if (data.Position < data.Length)
|
||||
{
|
||||
while (data.Position < data.Length && (data.Position % 4) != 0)
|
||||
_ = data.ReadByteValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the records
|
||||
mediaKeyBlock.Records = [.. records];
|
||||
|
||||
#endregion
|
||||
|
||||
return mediaKeyBlock;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set the records
|
||||
mediaKeyBlock.Records = [.. records];
|
||||
|
||||
#endregion
|
||||
|
||||
return mediaKeyBlock;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -86,7 +90,7 @@ namespace SabreTools.Serialization.Deserializers
|
||||
RecordType.HostRevocationList => ParseHostRevocationListRecord(data, type, length),
|
||||
RecordType.VerifyMediaKey => ParseVerifyMediaKeyRecord(data, type, length),
|
||||
RecordType.Copyright => ParseCopyrightRecord(data, type, length),
|
||||
|
||||
|
||||
// Unknown record type
|
||||
_ => null,
|
||||
};
|
||||
|
||||
@@ -21,103 +21,100 @@ namespace SabreTools.Serialization.Deserializers
|
||||
/// <inheritdoc/>
|
||||
public override MetadataFile? Deserialize(Stream? data)
|
||||
{
|
||||
// If the stream is null
|
||||
if (data == null)
|
||||
// If tthe data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return default;
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
// Setup the reader and output
|
||||
var reader = new SeparatedValueReader(data, Encoding.UTF8)
|
||||
{
|
||||
Separator = ';',
|
||||
VerifyFieldCount = false,
|
||||
};
|
||||
var dat = new MetadataFile();
|
||||
|
||||
// Read the header values first
|
||||
if (!reader.ReadHeader() || reader.HeaderValues == null)
|
||||
return null;
|
||||
|
||||
dat.Header = [.. reader.HeaderValues];
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
var rows = new List<Row>();
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// If we have no next line
|
||||
if (!reader.ReadNextLine() || reader.Line == null)
|
||||
break;
|
||||
|
||||
// Parse the line into a row
|
||||
Row row;
|
||||
if (reader.Line.Count < HeaderWithRomnameCount)
|
||||
{
|
||||
row = new Row
|
||||
{
|
||||
Name = reader.Line[0],
|
||||
Title = reader.Line[1],
|
||||
Emulator = reader.Line[2],
|
||||
CloneOf = reader.Line[3],
|
||||
Year = reader.Line[4],
|
||||
Manufacturer = reader.Line[5],
|
||||
Category = reader.Line[6],
|
||||
Players = reader.Line[7],
|
||||
Rotation = reader.Line[8],
|
||||
Control = reader.Line[9],
|
||||
Status = reader.Line[10],
|
||||
DisplayCount = reader.Line[11],
|
||||
DisplayType = reader.Line[12],
|
||||
AltRomname = reader.Line[13],
|
||||
AltTitle = reader.Line[14],
|
||||
Extra = reader.Line[15],
|
||||
Buttons = reader.Line[16],
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
row = new Row
|
||||
{
|
||||
Name = reader.Line[0],
|
||||
Title = reader.Line[1],
|
||||
Emulator = reader.Line[2],
|
||||
CloneOf = reader.Line[3],
|
||||
Year = reader.Line[4],
|
||||
Manufacturer = reader.Line[5],
|
||||
Category = reader.Line[6],
|
||||
Players = reader.Line[7],
|
||||
Rotation = reader.Line[8],
|
||||
Control = reader.Line[9],
|
||||
Status = reader.Line[10],
|
||||
DisplayCount = reader.Line[11],
|
||||
DisplayType = reader.Line[12],
|
||||
AltRomname = reader.Line[13],
|
||||
AltTitle = reader.Line[14],
|
||||
Extra = reader.Line[15],
|
||||
Buttons = reader.Line[16],
|
||||
};
|
||||
}
|
||||
|
||||
rows.Add(row);
|
||||
}
|
||||
|
||||
// Assign the rows to the Dat and return
|
||||
if (rows.Count > 0)
|
||||
{
|
||||
dat.Row = [.. rows];
|
||||
return dat;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
}
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new SeparatedValueReader(data, Encoding.UTF8)
|
||||
{
|
||||
Separator = ';',
|
||||
VerifyFieldCount = false,
|
||||
};
|
||||
var dat = new MetadataFile();
|
||||
|
||||
// Read the header values first
|
||||
if (!reader.ReadHeader() || reader.HeaderValues == null)
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
|
||||
dat.Header = [.. reader.HeaderValues];
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
var rows = new List<Row>();
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// If we have no next line
|
||||
if (!reader.ReadNextLine() || reader.Line == null)
|
||||
break;
|
||||
|
||||
// Parse the line into a row
|
||||
Row row;
|
||||
if (reader.Line.Count < HeaderWithRomnameCount)
|
||||
{
|
||||
row = new Row
|
||||
{
|
||||
Name = reader.Line[0],
|
||||
Title = reader.Line[1],
|
||||
Emulator = reader.Line[2],
|
||||
CloneOf = reader.Line[3],
|
||||
Year = reader.Line[4],
|
||||
Manufacturer = reader.Line[5],
|
||||
Category = reader.Line[6],
|
||||
Players = reader.Line[7],
|
||||
Rotation = reader.Line[8],
|
||||
Control = reader.Line[9],
|
||||
Status = reader.Line[10],
|
||||
DisplayCount = reader.Line[11],
|
||||
DisplayType = reader.Line[12],
|
||||
AltRomname = reader.Line[13],
|
||||
AltTitle = reader.Line[14],
|
||||
Extra = reader.Line[15],
|
||||
Buttons = reader.Line[16],
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
row = new Row
|
||||
{
|
||||
Name = reader.Line[0],
|
||||
Title = reader.Line[1],
|
||||
Emulator = reader.Line[2],
|
||||
CloneOf = reader.Line[3],
|
||||
Year = reader.Line[4],
|
||||
Manufacturer = reader.Line[5],
|
||||
Category = reader.Line[6],
|
||||
Players = reader.Line[7],
|
||||
Rotation = reader.Line[8],
|
||||
Control = reader.Line[9],
|
||||
Status = reader.Line[10],
|
||||
DisplayCount = reader.Line[11],
|
||||
DisplayType = reader.Line[12],
|
||||
AltRomname = reader.Line[13],
|
||||
AltTitle = reader.Line[14],
|
||||
Extra = reader.Line[15],
|
||||
Buttons = reader.Line[16],
|
||||
};
|
||||
}
|
||||
|
||||
rows.Add(row);
|
||||
}
|
||||
|
||||
// Assign the rows to the Dat and return
|
||||
if (rows.Count > 0)
|
||||
{
|
||||
dat.Row = [.. rows];
|
||||
return dat;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -15,44 +15,38 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
try
|
||||
{
|
||||
// Try to parse the SVM
|
||||
var svm = new SVM();
|
||||
|
||||
byte[] signature = data.ReadBytes(8);
|
||||
svm.Signature = Encoding.ASCII.GetString(signature);
|
||||
if (svm.Signature != SignatureString)
|
||||
return null;
|
||||
|
||||
svm.Unknown1 = data.ReadBytes(5);
|
||||
svm.Year = data.ReadUInt16BigEndian();
|
||||
svm.Month = data.ReadByteValue();
|
||||
if (svm.Month < 1 || svm.Month > 12)
|
||||
return null;
|
||||
|
||||
svm.Day = data.ReadByteValue();
|
||||
if (svm.Day < 1 || svm.Day > 31)
|
||||
return null;
|
||||
|
||||
svm.Unknown2 = data.ReadUInt32();
|
||||
svm.Length = data.ReadUInt32();
|
||||
if (svm.Length > 0)
|
||||
svm.Data = data.ReadBytes((int)svm.Length);
|
||||
|
||||
return svm;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
|
||||
// Try to parse the SVM
|
||||
return ParseSVMData(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an SVM
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled SVM on success, null on error</returns>
|
||||
private static SVM? ParseSVMData(Stream data)
|
||||
{
|
||||
var svm = new SVM();
|
||||
|
||||
byte[] signature = data.ReadBytes(8);
|
||||
svm.Signature = Encoding.ASCII.GetString(signature);
|
||||
if (svm.Signature != SignatureString)
|
||||
return null;
|
||||
|
||||
svm.Unknown1 = data.ReadBytes(5);
|
||||
svm.Year = data.ReadUInt16BigEndian();
|
||||
svm.Month = data.ReadByteValue();
|
||||
if (svm.Month < 1 || svm.Month > 12)
|
||||
return null;
|
||||
|
||||
svm.Day = data.ReadByteValue();
|
||||
if (svm.Day < 1 || svm.Day > 31)
|
||||
return null;
|
||||
|
||||
svm.Unknown2 = data.ReadUInt32();
|
||||
svm.Length = data.ReadUInt32();
|
||||
if (svm.Length > 0)
|
||||
svm.Data = data.ReadBytes((int)svm.Length);
|
||||
|
||||
return svm;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,42 +15,46 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new archive to fill
|
||||
var archive = new Archive();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Magic != SignatureString)
|
||||
return null;
|
||||
|
||||
// Set the archive header
|
||||
archive.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Files
|
||||
|
||||
// If we have any files
|
||||
var files = new FileEntry[header.Files];
|
||||
|
||||
// Read all entries in turn
|
||||
for (int i = 0; i < header.Files; i++)
|
||||
try
|
||||
{
|
||||
files[i] = ParseFileEntry(data);
|
||||
// Create a new archive to fill
|
||||
var archive = new Archive();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Magic != SignatureString)
|
||||
return null;
|
||||
|
||||
// Set the archive header
|
||||
archive.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Files
|
||||
|
||||
// If we have any files
|
||||
var files = new FileEntry[header.Files];
|
||||
|
||||
// Read all entries in turn
|
||||
for (int i = 0; i < header.Files; i++)
|
||||
{
|
||||
files[i] = ParseFileEntry(data);
|
||||
}
|
||||
|
||||
// Set the files
|
||||
archive.Files = files;
|
||||
|
||||
#endregion
|
||||
|
||||
return archive;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set the files
|
||||
archive.Files = files;
|
||||
|
||||
#endregion
|
||||
|
||||
return archive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -17,98 +17,102 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new Half-Life Level to fill
|
||||
var file = new BspFile();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<BspHeader>();
|
||||
if (header?.Lumps == null || header.Lumps.Length != BSP_HEADER_LUMPS)
|
||||
return null;
|
||||
if (header.Version < 29 || header.Version > 30)
|
||||
return null;
|
||||
|
||||
// Set the level header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lumps
|
||||
|
||||
for (int l = 0; l < BSP_HEADER_LUMPS; l++)
|
||||
try
|
||||
{
|
||||
// Get the next lump entry
|
||||
var lumpEntry = header.Lumps[l];
|
||||
if (lumpEntry == null)
|
||||
continue;
|
||||
if (lumpEntry.Offset == 0 || lumpEntry.Length == 0)
|
||||
continue;
|
||||
// Create a new Half-Life Level to fill
|
||||
var file = new BspFile();
|
||||
|
||||
// Seek to the lump offset
|
||||
data.Seek(lumpEntry.Offset, SeekOrigin.Begin);
|
||||
#region Header
|
||||
|
||||
// Read according to the lump type
|
||||
switch ((LumpType)l)
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<BspHeader>();
|
||||
if (header?.Lumps == null || header.Lumps.Length != BSP_HEADER_LUMPS)
|
||||
return null;
|
||||
if (header.Version < 29 || header.Version > 30)
|
||||
return null;
|
||||
|
||||
// Set the level header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lumps
|
||||
|
||||
for (int l = 0; l < BSP_HEADER_LUMPS; l++)
|
||||
{
|
||||
case LumpType.LUMP_ENTITIES:
|
||||
file.Entities = ParseEntitiesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_PLANES:
|
||||
file.PlanesLump = ParsePlanesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_TEXTURES:
|
||||
file.TextureLump = ParseTextureLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_VERTICES:
|
||||
file.VerticesLump = ParseVerticesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_VISIBILITY:
|
||||
file.VisibilityLump = ParseVisibilityLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_NODES:
|
||||
file.NodesLump = ParseNodesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_TEXINFO:
|
||||
file.TexinfoLump = ParseTexinfoLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_FACES:
|
||||
file.FacesLump = ParseFacesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LIGHTING:
|
||||
file.LightmapLump = ParseLightmapLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_CLIPNODES:
|
||||
file.ClipnodesLump = ParseClipnodesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAVES:
|
||||
file.LeavesLump = ParseLeavesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_MARKSURFACES:
|
||||
file.MarksurfacesLump = ParseMarksurfacesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_EDGES:
|
||||
file.EdgesLump = ParseEdgesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_SURFEDGES:
|
||||
file.SurfedgesLump = ParseSurfedgesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_MODELS:
|
||||
file.ModelsLump = ParseModelsLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
default:
|
||||
// Unsupported LumpType value, ignore
|
||||
break;
|
||||
// Get the next lump entry
|
||||
var lumpEntry = header.Lumps[l];
|
||||
if (lumpEntry == null)
|
||||
continue;
|
||||
if (lumpEntry.Offset == 0 || lumpEntry.Length == 0)
|
||||
continue;
|
||||
|
||||
// Seek to the lump offset
|
||||
data.Seek(lumpEntry.Offset, SeekOrigin.Begin);
|
||||
|
||||
// Read according to the lump type
|
||||
switch ((LumpType)l)
|
||||
{
|
||||
case LumpType.LUMP_ENTITIES:
|
||||
file.Entities = ParseEntitiesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_PLANES:
|
||||
file.PlanesLump = ParsePlanesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_TEXTURES:
|
||||
file.TextureLump = ParseTextureLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_VERTICES:
|
||||
file.VerticesLump = ParseVerticesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_VISIBILITY:
|
||||
file.VisibilityLump = ParseVisibilityLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_NODES:
|
||||
file.NodesLump = ParseNodesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_TEXINFO:
|
||||
file.TexinfoLump = ParseTexinfoLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_FACES:
|
||||
file.FacesLump = ParseFacesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LIGHTING:
|
||||
file.LightmapLump = ParseLightmapLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_CLIPNODES:
|
||||
file.ClipnodesLump = ParseClipnodesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAVES:
|
||||
file.LeavesLump = ParseLeavesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_MARKSURFACES:
|
||||
file.MarksurfacesLump = ParseMarksurfacesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_EDGES:
|
||||
file.EdgesLump = ParseEdgesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_SURFEDGES:
|
||||
file.SurfedgesLump = ParseSurfedgesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_MODELS:
|
||||
file.ModelsLump = ParseModelsLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
default:
|
||||
// Unsupported LumpType value, ignore
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace SabreTools.Serialization.Deserializers
|
||||
public virtual TModel? Deserialize(byte[]? data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null)
|
||||
if (data == null || data.Length == 0)
|
||||
return default;
|
||||
|
||||
// If the offset is out of bounds
|
||||
|
||||
@@ -17,210 +17,214 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
try
|
||||
{
|
||||
// Create a new binary to fill
|
||||
var binary = new Binary();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the file header
|
||||
var fileHeader = ParseFileHeader(data);
|
||||
if (fileHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the file header
|
||||
binary.Header = fileHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region DIFAT Sector Numbers
|
||||
|
||||
// Create a DIFAT sector table
|
||||
var difatSectors = new List<SectorNumber>();
|
||||
|
||||
// Add the sectors from the header
|
||||
if (fileHeader.DIFAT != null)
|
||||
difatSectors.AddRange(fileHeader.DIFAT);
|
||||
|
||||
// Loop through and add the DIFAT sectors
|
||||
var currentSector = (SectorNumber?)fileHeader.FirstDIFATSectorLocation;
|
||||
for (int i = 0; i < fileHeader.NumberOfDIFATSectors; i++)
|
||||
{
|
||||
// If we have a readable sector
|
||||
if (currentSector <= SectorNumber.MAXREGSECT)
|
||||
{
|
||||
// Get the new next sector information
|
||||
long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
|
||||
if (sectorOffset < 0 || sectorOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the next sector
|
||||
data.Seek(sectorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the sectors
|
||||
var sectorNumbers = ParseSectorNumbers(data, fileHeader.SectorShift);
|
||||
if (sectorNumbers == null)
|
||||
return null;
|
||||
|
||||
// Add the sector shifts
|
||||
difatSectors.AddRange(sectorNumbers);
|
||||
}
|
||||
|
||||
// Get the next sector from the DIFAT
|
||||
currentSector = difatSectors[i];
|
||||
}
|
||||
|
||||
// Assign the DIFAT sectors table
|
||||
binary.DIFATSectorNumbers = [.. difatSectors];
|
||||
|
||||
#endregion
|
||||
|
||||
#region FAT Sector Numbers
|
||||
|
||||
// Create a FAT sector table
|
||||
var fatSectors = new List<SectorNumber>();
|
||||
|
||||
// Loop through and add the FAT sectors
|
||||
currentSector = binary.DIFATSectorNumbers[0];
|
||||
for (int i = 0; i < fileHeader.NumberOfFATSectors; i++)
|
||||
{
|
||||
// If we have a readable sector
|
||||
if (currentSector <= SectorNumber.MAXREGSECT)
|
||||
{
|
||||
// Get the new next sector information
|
||||
long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
|
||||
if (sectorOffset < 0 || sectorOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the next sector
|
||||
data.Seek(sectorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the sectors
|
||||
var sectorNumbers = ParseSectorNumbers(data, fileHeader.SectorShift);
|
||||
if (sectorNumbers == null)
|
||||
return null;
|
||||
|
||||
// Add the sector shifts
|
||||
fatSectors.AddRange(sectorNumbers);
|
||||
}
|
||||
|
||||
// Get the next sector from the DIFAT
|
||||
currentSector = binary.DIFATSectorNumbers[i];
|
||||
}
|
||||
|
||||
// Assign the FAT sectors table
|
||||
binary.FATSectorNumbers = [.. fatSectors];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Mini FAT Sector Numbers
|
||||
|
||||
// Create a mini FAT sector table
|
||||
var miniFatSectors = new List<SectorNumber>();
|
||||
|
||||
// Loop through and add the mini FAT sectors
|
||||
currentSector = (SectorNumber)fileHeader.FirstMiniFATSectorLocation;
|
||||
for (int i = 0; i < fileHeader.NumberOfMiniFATSectors; i++)
|
||||
{
|
||||
// If we have a readable sector
|
||||
if (currentSector <= SectorNumber.MAXREGSECT)
|
||||
{
|
||||
// Get the new next sector information
|
||||
long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
|
||||
if (sectorOffset < 0 || sectorOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the next sector
|
||||
data.Seek(sectorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the sectors
|
||||
var sectorNumbers = ParseSectorNumbers(data, fileHeader.SectorShift);
|
||||
if (sectorNumbers == null)
|
||||
return null;
|
||||
|
||||
// Add the sector shifts
|
||||
miniFatSectors.AddRange(sectorNumbers);
|
||||
}
|
||||
|
||||
// Get the next sector from the DIFAT
|
||||
currentSector = binary.DIFATSectorNumbers[i];
|
||||
}
|
||||
|
||||
// Assign the mini FAT sectors table
|
||||
binary.MiniFATSectorNumbers = [.. miniFatSectors];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Entries
|
||||
|
||||
// Get the offset of the first directory sector
|
||||
long firstDirectoryOffset = (long)(fileHeader.FirstDirectorySectorLocation * Math.Pow(2, fileHeader.SectorShift));
|
||||
if (firstDirectoryOffset < 0 || firstDirectoryOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the first directory sector
|
||||
data.Seek(firstDirectoryOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create a directory sector table
|
||||
var directorySectors = new List<DirectoryEntry>();
|
||||
|
||||
// Get the number of directory sectors
|
||||
uint directorySectorCount = 0;
|
||||
switch (fileHeader.MajorVersion)
|
||||
{
|
||||
case 3:
|
||||
directorySectorCount = int.MaxValue;
|
||||
break;
|
||||
case 4:
|
||||
directorySectorCount = fileHeader.NumberOfDirectorySectors;
|
||||
break;
|
||||
}
|
||||
|
||||
// Loop through and add the directory sectors
|
||||
currentSector = (SectorNumber)fileHeader.FirstDirectorySectorLocation;
|
||||
for (int i = 0; i < directorySectorCount; i++)
|
||||
{
|
||||
// If we have an end of chain
|
||||
if (currentSector == SectorNumber.ENDOFCHAIN)
|
||||
break;
|
||||
|
||||
// If we have a free sector for a version 3 filie
|
||||
if (directorySectorCount == int.MaxValue && currentSector == SectorNumber.FREESECT)
|
||||
break;
|
||||
|
||||
// If we have a readable sector
|
||||
if (currentSector <= SectorNumber.MAXREGSECT)
|
||||
{
|
||||
// Get the new next sector information
|
||||
long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
|
||||
if (sectorOffset < 0 || sectorOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the next sector
|
||||
data.Seek(sectorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the sectors
|
||||
var directoryEntries = ParseDirectoryEntries(data, fileHeader.SectorShift, fileHeader.MajorVersion);
|
||||
if (directoryEntries == null)
|
||||
return null;
|
||||
|
||||
// Add the sector shifts
|
||||
directorySectors.AddRange(directoryEntries);
|
||||
}
|
||||
|
||||
// Get the next sector from the DIFAT
|
||||
currentSector = binary.DIFATSectorNumbers[i];
|
||||
}
|
||||
|
||||
// Assign the Directory sectors table
|
||||
binary.DirectoryEntries = [.. directorySectors];
|
||||
|
||||
#endregion
|
||||
|
||||
return binary;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
|
||||
// Create a new binary to fill
|
||||
var binary = new Binary();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the file header
|
||||
var fileHeader = ParseFileHeader(data);
|
||||
if (fileHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the file header
|
||||
binary.Header = fileHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region DIFAT Sector Numbers
|
||||
|
||||
// Create a DIFAT sector table
|
||||
var difatSectors = new List<SectorNumber>();
|
||||
|
||||
// Add the sectors from the header
|
||||
if (fileHeader.DIFAT != null)
|
||||
difatSectors.AddRange(fileHeader.DIFAT);
|
||||
|
||||
// Loop through and add the DIFAT sectors
|
||||
var currentSector = (SectorNumber?)fileHeader.FirstDIFATSectorLocation;
|
||||
for (int i = 0; i < fileHeader.NumberOfDIFATSectors; i++)
|
||||
{
|
||||
// If we have a readable sector
|
||||
if (currentSector <= SectorNumber.MAXREGSECT)
|
||||
{
|
||||
// Get the new next sector information
|
||||
long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
|
||||
if (sectorOffset < 0 || sectorOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the next sector
|
||||
data.Seek(sectorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the sectors
|
||||
var sectorNumbers = ParseSectorNumbers(data, fileHeader.SectorShift);
|
||||
if (sectorNumbers == null)
|
||||
return null;
|
||||
|
||||
// Add the sector shifts
|
||||
difatSectors.AddRange(sectorNumbers);
|
||||
}
|
||||
|
||||
// Get the next sector from the DIFAT
|
||||
currentSector = difatSectors[i];
|
||||
}
|
||||
|
||||
// Assign the DIFAT sectors table
|
||||
binary.DIFATSectorNumbers = [.. difatSectors];
|
||||
|
||||
#endregion
|
||||
|
||||
#region FAT Sector Numbers
|
||||
|
||||
// Create a FAT sector table
|
||||
var fatSectors = new List<SectorNumber>();
|
||||
|
||||
// Loop through and add the FAT sectors
|
||||
currentSector = binary.DIFATSectorNumbers[0];
|
||||
for (int i = 0; i < fileHeader.NumberOfFATSectors; i++)
|
||||
{
|
||||
// If we have a readable sector
|
||||
if (currentSector <= SectorNumber.MAXREGSECT)
|
||||
{
|
||||
// Get the new next sector information
|
||||
long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
|
||||
if (sectorOffset < 0 || sectorOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the next sector
|
||||
data.Seek(sectorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the sectors
|
||||
var sectorNumbers = ParseSectorNumbers(data, fileHeader.SectorShift);
|
||||
if (sectorNumbers == null)
|
||||
return null;
|
||||
|
||||
// Add the sector shifts
|
||||
fatSectors.AddRange(sectorNumbers);
|
||||
}
|
||||
|
||||
// Get the next sector from the DIFAT
|
||||
currentSector = binary.DIFATSectorNumbers[i];
|
||||
}
|
||||
|
||||
// Assign the FAT sectors table
|
||||
binary.FATSectorNumbers = [.. fatSectors];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Mini FAT Sector Numbers
|
||||
|
||||
// Create a mini FAT sector table
|
||||
var miniFatSectors = new List<SectorNumber>();
|
||||
|
||||
// Loop through and add the mini FAT sectors
|
||||
currentSector = (SectorNumber)fileHeader.FirstMiniFATSectorLocation;
|
||||
for (int i = 0; i < fileHeader.NumberOfMiniFATSectors; i++)
|
||||
{
|
||||
// If we have a readable sector
|
||||
if (currentSector <= SectorNumber.MAXREGSECT)
|
||||
{
|
||||
// Get the new next sector information
|
||||
long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
|
||||
if (sectorOffset < 0 || sectorOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the next sector
|
||||
data.Seek(sectorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the sectors
|
||||
var sectorNumbers = ParseSectorNumbers(data, fileHeader.SectorShift);
|
||||
if (sectorNumbers == null)
|
||||
return null;
|
||||
|
||||
// Add the sector shifts
|
||||
miniFatSectors.AddRange(sectorNumbers);
|
||||
}
|
||||
|
||||
// Get the next sector from the DIFAT
|
||||
currentSector = binary.DIFATSectorNumbers[i];
|
||||
}
|
||||
|
||||
// Assign the mini FAT sectors table
|
||||
binary.MiniFATSectorNumbers = [.. miniFatSectors];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Entries
|
||||
|
||||
// Get the offset of the first directory sector
|
||||
long firstDirectoryOffset = (long)(fileHeader.FirstDirectorySectorLocation * Math.Pow(2, fileHeader.SectorShift));
|
||||
if (firstDirectoryOffset < 0 || firstDirectoryOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the first directory sector
|
||||
data.Seek(firstDirectoryOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create a directory sector table
|
||||
var directorySectors = new List<DirectoryEntry>();
|
||||
|
||||
// Get the number of directory sectors
|
||||
uint directorySectorCount = 0;
|
||||
switch (fileHeader.MajorVersion)
|
||||
{
|
||||
case 3:
|
||||
directorySectorCount = int.MaxValue;
|
||||
break;
|
||||
case 4:
|
||||
directorySectorCount = fileHeader.NumberOfDirectorySectors;
|
||||
break;
|
||||
}
|
||||
|
||||
// Loop through and add the directory sectors
|
||||
currentSector = (SectorNumber)fileHeader.FirstDirectorySectorLocation;
|
||||
for (int i = 0; i < directorySectorCount; i++)
|
||||
{
|
||||
// If we have an end of chain
|
||||
if (currentSector == SectorNumber.ENDOFCHAIN)
|
||||
break;
|
||||
|
||||
// If we have a free sector for a version 3 filie
|
||||
if (directorySectorCount == int.MaxValue && currentSector == SectorNumber.FREESECT)
|
||||
break;
|
||||
|
||||
// If we have a readable sector
|
||||
if (currentSector <= SectorNumber.MAXREGSECT)
|
||||
{
|
||||
// Get the new next sector information
|
||||
long sectorOffset = (long)((long)(currentSector + 1) * Math.Pow(2, fileHeader.SectorShift));
|
||||
if (sectorOffset < 0 || sectorOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the next sector
|
||||
data.Seek(sectorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the sectors
|
||||
var directoryEntries = ParseDirectoryEntries(data, fileHeader.SectorShift, fileHeader.MajorVersion);
|
||||
if (directoryEntries == null)
|
||||
return null;
|
||||
|
||||
// Add the sector shifts
|
||||
directorySectors.AddRange(directoryEntries);
|
||||
}
|
||||
|
||||
// Get the next sector from the DIFAT
|
||||
currentSector = binary.DIFATSectorNumbers[i];
|
||||
}
|
||||
|
||||
// Assign the Directory sectors table
|
||||
binary.DirectoryEntries = [.. directorySectors];
|
||||
|
||||
#endregion
|
||||
|
||||
return binary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -16,23 +16,27 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Determine the header version
|
||||
uint version = GetVersion(data);
|
||||
|
||||
// Read and return the current CHD
|
||||
return version switch
|
||||
try
|
||||
{
|
||||
1 => ParseHeaderV1(data),
|
||||
2 => ParseHeaderV2(data),
|
||||
3 => ParseHeaderV3(data),
|
||||
4 => ParseHeaderV4(data),
|
||||
5 => ParseHeaderV5(data),
|
||||
_ => null,
|
||||
};
|
||||
// Determine the header version
|
||||
uint version = GetVersion(data);
|
||||
|
||||
// Read and return the current CHD
|
||||
return version switch
|
||||
{
|
||||
1 => ParseHeaderV1(data),
|
||||
2 => ParseHeaderV2(data),
|
||||
3 => ParseHeaderV3(data),
|
||||
4 => ParseHeaderV4(data),
|
||||
5 => ParseHeaderV5(data),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -14,136 +14,140 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new CIA archive to fill
|
||||
var cia = new Models.N3DS.CIA();
|
||||
|
||||
#region CIA Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<CIAHeader>();
|
||||
if (header == null)
|
||||
return null;
|
||||
if (header.CertificateChainSize > data.Length)
|
||||
return null;
|
||||
if (header.TicketSize > data.Length)
|
||||
return null;
|
||||
if (header.TMDFileSize > data.Length)
|
||||
return null;
|
||||
if (header.MetaSize > data.Length)
|
||||
return null;
|
||||
if ((long)header.ContentSize > data.Length)
|
||||
return null;
|
||||
|
||||
// Set the CIA archive header
|
||||
cia.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
// Align to 64-byte boundary, if needed
|
||||
while (data.Position < data.Length - 1 && data.Position % 64 != 0)
|
||||
try
|
||||
{
|
||||
_ = data.ReadByteValue();
|
||||
}
|
||||
// Create a new CIA archive to fill
|
||||
var cia = new Models.N3DS.CIA();
|
||||
|
||||
#region Certificate Chain
|
||||
#region CIA Header
|
||||
|
||||
// Create the certificate chain
|
||||
cia.CertificateChain = new Certificate[3];
|
||||
|
||||
// Try to parse the certificates
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var certificate = ParseCertificate(data);
|
||||
if (certificate == null)
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<CIAHeader>();
|
||||
if (header == null)
|
||||
return null;
|
||||
if (header.CertificateChainSize > data.Length)
|
||||
return null;
|
||||
if (header.TicketSize > data.Length)
|
||||
return null;
|
||||
if (header.TMDFileSize > data.Length)
|
||||
return null;
|
||||
if (header.MetaSize > data.Length)
|
||||
return null;
|
||||
if ((long)header.ContentSize > data.Length)
|
||||
return null;
|
||||
|
||||
cia.CertificateChain[i] = certificate;
|
||||
}
|
||||
// Set the CIA archive header
|
||||
cia.Header = header;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
// Align to 64-byte boundary, if needed
|
||||
while (data.Position < data.Length - 1 && data.Position % 64 != 0)
|
||||
{
|
||||
_ = data.ReadByteValue();
|
||||
}
|
||||
// Align to 64-byte boundary, if needed
|
||||
while (data.Position < data.Length - 1 && data.Position % 64 != 0)
|
||||
{
|
||||
_ = data.ReadByteValue();
|
||||
}
|
||||
|
||||
#region Ticket
|
||||
#region Certificate Chain
|
||||
|
||||
// Try to parse the ticket
|
||||
var ticket = ParseTicket(data);
|
||||
if (ticket == null)
|
||||
return null;
|
||||
// Create the certificate chain
|
||||
cia.CertificateChain = new Certificate[3];
|
||||
|
||||
// Set the ticket
|
||||
cia.Ticket = ticket;
|
||||
// Try to parse the certificates
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var certificate = ParseCertificate(data);
|
||||
if (certificate == null)
|
||||
return null;
|
||||
|
||||
#endregion
|
||||
cia.CertificateChain[i] = certificate;
|
||||
}
|
||||
|
||||
// Align to 64-byte boundary, if needed
|
||||
while (data.Position < data.Length - 1 && data.Position % 64 != 0)
|
||||
{
|
||||
_ = data.ReadByteValue();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Title Metadata
|
||||
// Align to 64-byte boundary, if needed
|
||||
while (data.Position < data.Length - 1 && data.Position % 64 != 0)
|
||||
{
|
||||
_ = data.ReadByteValue();
|
||||
}
|
||||
|
||||
// Try to parse the title metadata
|
||||
var titleMetadata = ParseTitleMetadata(data);
|
||||
if (titleMetadata == null)
|
||||
return null;
|
||||
#region Ticket
|
||||
|
||||
// Set the title metadata
|
||||
cia.TMDFileData = titleMetadata;
|
||||
|
||||
#endregion
|
||||
|
||||
// Align to 64-byte boundary, if needed
|
||||
while (data.Position < data.Length - 1 && data.Position % 64 != 0)
|
||||
{
|
||||
_ = data.ReadByteValue();
|
||||
}
|
||||
|
||||
#region Content File Data
|
||||
|
||||
// Create the partition table
|
||||
cia.Partitions = new NCCHHeader[8];
|
||||
|
||||
// Iterate and build the partitions
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
cia.Partitions[i] = N3DS.ParseNCCHHeader(data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Align to 64-byte boundary, if needed
|
||||
while (data.Position < data.Length - 1 && data.Position % 64 != 0)
|
||||
{
|
||||
_ = data.ReadByteValue();
|
||||
}
|
||||
|
||||
#region Meta Data
|
||||
|
||||
// If we have a meta data
|
||||
if (header.MetaSize > 0)
|
||||
{
|
||||
// Try to parse the meta
|
||||
var meta = data.ReadType<MetaData>();
|
||||
if (meta == null)
|
||||
// Try to parse the ticket
|
||||
var ticket = ParseTicket(data);
|
||||
if (ticket == null)
|
||||
return null;
|
||||
|
||||
// Set the meta
|
||||
cia.MetaData = meta;
|
||||
// Set the ticket
|
||||
cia.Ticket = ticket;
|
||||
|
||||
#endregion
|
||||
|
||||
// Align to 64-byte boundary, if needed
|
||||
while (data.Position < data.Length - 1 && data.Position % 64 != 0)
|
||||
{
|
||||
_ = data.ReadByteValue();
|
||||
}
|
||||
|
||||
#region Title Metadata
|
||||
|
||||
// Try to parse the title metadata
|
||||
var titleMetadata = ParseTitleMetadata(data);
|
||||
if (titleMetadata == null)
|
||||
return null;
|
||||
|
||||
// Set the title metadata
|
||||
cia.TMDFileData = titleMetadata;
|
||||
|
||||
#endregion
|
||||
|
||||
// Align to 64-byte boundary, if needed
|
||||
while (data.Position < data.Length - 1 && data.Position % 64 != 0)
|
||||
{
|
||||
_ = data.ReadByteValue();
|
||||
}
|
||||
|
||||
#region Content File Data
|
||||
|
||||
// Create the partition table
|
||||
cia.Partitions = new NCCHHeader[8];
|
||||
|
||||
// Iterate and build the partitions
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
cia.Partitions[i] = N3DS.ParseNCCHHeader(data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Align to 64-byte boundary, if needed
|
||||
while (data.Position < data.Length - 1 && data.Position % 64 != 0)
|
||||
{
|
||||
_ = data.ReadByteValue();
|
||||
}
|
||||
|
||||
#region Meta Data
|
||||
|
||||
// If we have a meta data
|
||||
if (header.MetaSize > 0)
|
||||
{
|
||||
// Try to parse the meta
|
||||
var meta = data.ReadType<MetaData>();
|
||||
if (meta == null)
|
||||
return null;
|
||||
|
||||
// Set the meta
|
||||
cia.MetaData = meta;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return cia;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return cia;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -78,316 +78,313 @@ namespace SabreTools.Serialization.Deserializers
|
||||
/// <inheritdoc cref="Deserialize(Stream)"/>
|
||||
public MetadataFile? Deserialize(Stream? data, bool quotes)
|
||||
{
|
||||
// If the stream is null
|
||||
if (data == null)
|
||||
return default;
|
||||
// If tthe data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
// Setup the reader and output
|
||||
var reader = new ClrMameProReader(data, Encoding.UTF8) { Quotes = quotes };
|
||||
var dat = new MetadataFile();
|
||||
|
||||
// Loop through and parse out the values
|
||||
string? lastTopLevel = reader.TopLevel;
|
||||
|
||||
GameBase? game = null;
|
||||
var games = new List<GameBase>();
|
||||
var releases = new List<Release>();
|
||||
var biosSets = new List<BiosSet>();
|
||||
var roms = new List<Rom>();
|
||||
var disks = new List<Disk>();
|
||||
var medias = new List<Media>();
|
||||
var samples = new List<Sample>();
|
||||
var archives = new List<Archive>();
|
||||
var chips = new List<Chip>();
|
||||
var videos = new List<Video>();
|
||||
var dipSwitches = new List<DipSwitch>();
|
||||
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// If we have no next line
|
||||
if (!reader.ReadNextLine())
|
||||
break;
|
||||
|
||||
// Ignore certain row types
|
||||
switch (reader.RowType)
|
||||
{
|
||||
case CmpRowType.None:
|
||||
case CmpRowType.Comment:
|
||||
continue;
|
||||
case CmpRowType.EndTopLevel:
|
||||
switch (lastTopLevel)
|
||||
{
|
||||
case "game":
|
||||
case "machine":
|
||||
case "resource":
|
||||
case "set":
|
||||
if (game != null)
|
||||
{
|
||||
game.Release = [.. releases];
|
||||
game.BiosSet = [.. biosSets];
|
||||
game.Rom = [.. roms];
|
||||
game.Disk = [.. disks];
|
||||
game.Media = [.. medias];
|
||||
game.Sample = [.. samples];
|
||||
game.Archive = [.. archives];
|
||||
game.Chip = [.. chips];
|
||||
game.Video = [.. videos];
|
||||
game.DipSwitch = [.. dipSwitches];
|
||||
|
||||
games.Add(game);
|
||||
game = null;
|
||||
}
|
||||
|
||||
releases.Clear();
|
||||
biosSets.Clear();
|
||||
roms.Clear();
|
||||
disks.Clear();
|
||||
medias.Clear();
|
||||
samples.Clear();
|
||||
archives.Clear();
|
||||
chips.Clear();
|
||||
videos.Clear();
|
||||
dipSwitches.Clear();
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're at the root
|
||||
if (reader.RowType == CmpRowType.TopLevel)
|
||||
{
|
||||
lastTopLevel = reader.TopLevel;
|
||||
switch (reader.TopLevel)
|
||||
{
|
||||
case "clrmamepro":
|
||||
dat.ClrMamePro = new Models.ClrMamePro.ClrMamePro();
|
||||
break;
|
||||
case "game":
|
||||
game = new Game();
|
||||
break;
|
||||
case "machine":
|
||||
game = new Machine();
|
||||
break;
|
||||
case "resource":
|
||||
game = new Resource();
|
||||
break;
|
||||
case "set":
|
||||
game = new Set();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in the doscenter block
|
||||
else if (reader.TopLevel == "clrmamepro"
|
||||
&& reader.RowType == CmpRowType.Standalone)
|
||||
{
|
||||
// Create the block if we haven't already
|
||||
dat.ClrMamePro ??= new Models.ClrMamePro.ClrMamePro();
|
||||
|
||||
switch (reader.Standalone?.Key?.ToLowerInvariant())
|
||||
{
|
||||
case "name":
|
||||
dat.ClrMamePro.Name = reader.Standalone?.Value;
|
||||
break;
|
||||
case "description":
|
||||
dat.ClrMamePro.Description = reader.Standalone?.Value;
|
||||
break;
|
||||
case "rootdir":
|
||||
dat.ClrMamePro.RootDir = reader.Standalone?.Value;
|
||||
break;
|
||||
case "category":
|
||||
dat.ClrMamePro.Category = reader.Standalone?.Value;
|
||||
break;
|
||||
case "version":
|
||||
dat.ClrMamePro.Version = reader.Standalone?.Value;
|
||||
break;
|
||||
case "date":
|
||||
dat.ClrMamePro.Date = reader.Standalone?.Value;
|
||||
break;
|
||||
case "author":
|
||||
dat.ClrMamePro.Author = reader.Standalone?.Value;
|
||||
break;
|
||||
case "homepage":
|
||||
dat.ClrMamePro.Homepage = reader.Standalone?.Value;
|
||||
break;
|
||||
case "url":
|
||||
dat.ClrMamePro.Url = reader.Standalone?.Value;
|
||||
break;
|
||||
case "comment":
|
||||
dat.ClrMamePro.Comment = reader.Standalone?.Value;
|
||||
break;
|
||||
case "header":
|
||||
dat.ClrMamePro.Header = reader.Standalone?.Value;
|
||||
break;
|
||||
case "type":
|
||||
dat.ClrMamePro.Type = reader.Standalone?.Value;
|
||||
break;
|
||||
case "forcemerging":
|
||||
dat.ClrMamePro.ForceMerging = reader.Standalone?.Value;
|
||||
break;
|
||||
case "forcezipping":
|
||||
dat.ClrMamePro.ForceZipping = reader.Standalone?.Value;
|
||||
break;
|
||||
case "forcepacking":
|
||||
dat.ClrMamePro.ForcePacking = reader.Standalone?.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in a game, machine, resource, or set block
|
||||
else if ((reader.TopLevel == "game"
|
||||
|| reader.TopLevel == "machine"
|
||||
|| reader.TopLevel == "resource"
|
||||
|| reader.TopLevel == "set")
|
||||
&& reader.RowType == CmpRowType.Standalone)
|
||||
{
|
||||
// Create the block if we haven't already
|
||||
game ??= reader.TopLevel switch
|
||||
{
|
||||
"game" => new Game(),
|
||||
"machine" => new Machine(),
|
||||
"resource" => new Resource(),
|
||||
"set" => new Set(),
|
||||
_ => throw new FormatException($"Unknown top-level block: {reader.TopLevel}"),
|
||||
};
|
||||
|
||||
switch (reader.Standalone?.Key?.ToLowerInvariant())
|
||||
{
|
||||
case "name":
|
||||
game.Name = reader.Standalone?.Value;
|
||||
break;
|
||||
case "description":
|
||||
game.Description = reader.Standalone?.Value;
|
||||
break;
|
||||
case "year":
|
||||
game.Year = reader.Standalone?.Value;
|
||||
break;
|
||||
case "manufacturer":
|
||||
game.Manufacturer = reader.Standalone?.Value;
|
||||
break;
|
||||
case "category":
|
||||
game.Category = reader.Standalone?.Value;
|
||||
break;
|
||||
case "cloneof":
|
||||
game.CloneOf = reader.Standalone?.Value;
|
||||
break;
|
||||
case "romof":
|
||||
game.RomOf = reader.Standalone?.Value;
|
||||
break;
|
||||
case "sampleof":
|
||||
game.SampleOf = reader.Standalone?.Value;
|
||||
break;
|
||||
case "sample":
|
||||
var sample = new Sample
|
||||
{
|
||||
Name = reader.Standalone?.Value ?? string.Empty,
|
||||
};
|
||||
samples.Add(sample);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in an item block
|
||||
else if ((reader.TopLevel == "game"
|
||||
|| reader.TopLevel == "machine"
|
||||
|| reader.TopLevel == "resource"
|
||||
|| reader.TopLevel == "set")
|
||||
&& game != null
|
||||
&& reader.RowType == CmpRowType.Internal)
|
||||
{
|
||||
// Create the block
|
||||
switch (reader.InternalName)
|
||||
{
|
||||
case "release":
|
||||
var release = CreateRelease(reader);
|
||||
if (release != null)
|
||||
releases.Add(release);
|
||||
break;
|
||||
case "biosset":
|
||||
var biosSet = CreateBiosSet(reader);
|
||||
if (biosSet != null)
|
||||
biosSets.Add(biosSet);
|
||||
break;
|
||||
case "rom":
|
||||
var rom = CreateRom(reader);
|
||||
if (rom != null)
|
||||
roms.Add(rom);
|
||||
break;
|
||||
case "disk":
|
||||
var disk = CreateDisk(reader);
|
||||
if (disk != null)
|
||||
disks.Add(disk);
|
||||
break;
|
||||
case "media":
|
||||
var media = CreateMedia(reader);
|
||||
if (media != null)
|
||||
medias.Add(media);
|
||||
break;
|
||||
case "sample":
|
||||
var sample = CreateSample(reader);
|
||||
if (sample != null)
|
||||
samples.Add(sample);
|
||||
break;
|
||||
case "archive":
|
||||
var archive = CreateArchive(reader);
|
||||
if (archive != null)
|
||||
archives.Add(archive);
|
||||
break;
|
||||
case "chip":
|
||||
var chip = CreateChip(reader);
|
||||
if (chip != null)
|
||||
chips.Add(chip);
|
||||
break;
|
||||
case "video":
|
||||
var video = CreateVideo(reader);
|
||||
if (video != null)
|
||||
videos.Add(video);
|
||||
break;
|
||||
case "sound":
|
||||
var sound = CreateSound(reader);
|
||||
if (sound != null)
|
||||
game.Sound = sound;
|
||||
break;
|
||||
case "input":
|
||||
var input = CreateInput(reader);
|
||||
if (input != null)
|
||||
game.Input = input;
|
||||
break;
|
||||
case "dipswitch":
|
||||
var dipSwitch = CreateDipSwitch(reader);
|
||||
if (dipSwitch != null)
|
||||
dipSwitches.Add(dipSwitch);
|
||||
break;
|
||||
case "driver":
|
||||
var driver = CreateDriver(reader);
|
||||
if (driver != null)
|
||||
game.Driver = driver;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add extra pieces and return
|
||||
if (games.Count > 0)
|
||||
{
|
||||
dat.Game = [.. games];
|
||||
return dat;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new ClrMameProReader(data, Encoding.UTF8) { Quotes = quotes };
|
||||
var dat = new MetadataFile();
|
||||
|
||||
// Loop through and parse out the values
|
||||
string? lastTopLevel = reader.TopLevel;
|
||||
|
||||
GameBase? game = null;
|
||||
var games = new List<GameBase>();
|
||||
var releases = new List<Release>();
|
||||
var biosSets = new List<BiosSet>();
|
||||
var roms = new List<Rom>();
|
||||
var disks = new List<Disk>();
|
||||
var medias = new List<Media>();
|
||||
var samples = new List<Sample>();
|
||||
var archives = new List<Archive>();
|
||||
var chips = new List<Chip>();
|
||||
var videos = new List<Video>();
|
||||
var dipSwitches = new List<DipSwitch>();
|
||||
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// If we have no next line
|
||||
if (!reader.ReadNextLine())
|
||||
break;
|
||||
|
||||
// Ignore certain row types
|
||||
switch (reader.RowType)
|
||||
{
|
||||
case CmpRowType.None:
|
||||
case CmpRowType.Comment:
|
||||
continue;
|
||||
case CmpRowType.EndTopLevel:
|
||||
switch (lastTopLevel)
|
||||
{
|
||||
case "game":
|
||||
case "machine":
|
||||
case "resource":
|
||||
case "set":
|
||||
if (game != null)
|
||||
{
|
||||
game.Release = [.. releases];
|
||||
game.BiosSet = [.. biosSets];
|
||||
game.Rom = [.. roms];
|
||||
game.Disk = [.. disks];
|
||||
game.Media = [.. medias];
|
||||
game.Sample = [.. samples];
|
||||
game.Archive = [.. archives];
|
||||
game.Chip = [.. chips];
|
||||
game.Video = [.. videos];
|
||||
game.DipSwitch = [.. dipSwitches];
|
||||
|
||||
games.Add(game);
|
||||
game = null;
|
||||
}
|
||||
|
||||
releases.Clear();
|
||||
biosSets.Clear();
|
||||
roms.Clear();
|
||||
disks.Clear();
|
||||
medias.Clear();
|
||||
samples.Clear();
|
||||
archives.Clear();
|
||||
chips.Clear();
|
||||
videos.Clear();
|
||||
dipSwitches.Clear();
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're at the root
|
||||
if (reader.RowType == CmpRowType.TopLevel)
|
||||
{
|
||||
lastTopLevel = reader.TopLevel;
|
||||
switch (reader.TopLevel)
|
||||
{
|
||||
case "clrmamepro":
|
||||
dat.ClrMamePro = new Models.ClrMamePro.ClrMamePro();
|
||||
break;
|
||||
case "game":
|
||||
game = new Game();
|
||||
break;
|
||||
case "machine":
|
||||
game = new Machine();
|
||||
break;
|
||||
case "resource":
|
||||
game = new Resource();
|
||||
break;
|
||||
case "set":
|
||||
game = new Set();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in the doscenter block
|
||||
else if (reader.TopLevel == "clrmamepro"
|
||||
&& reader.RowType == CmpRowType.Standalone)
|
||||
{
|
||||
// Create the block if we haven't already
|
||||
dat.ClrMamePro ??= new Models.ClrMamePro.ClrMamePro();
|
||||
|
||||
switch (reader.Standalone?.Key?.ToLowerInvariant())
|
||||
{
|
||||
case "name":
|
||||
dat.ClrMamePro.Name = reader.Standalone?.Value;
|
||||
break;
|
||||
case "description":
|
||||
dat.ClrMamePro.Description = reader.Standalone?.Value;
|
||||
break;
|
||||
case "rootdir":
|
||||
dat.ClrMamePro.RootDir = reader.Standalone?.Value;
|
||||
break;
|
||||
case "category":
|
||||
dat.ClrMamePro.Category = reader.Standalone?.Value;
|
||||
break;
|
||||
case "version":
|
||||
dat.ClrMamePro.Version = reader.Standalone?.Value;
|
||||
break;
|
||||
case "date":
|
||||
dat.ClrMamePro.Date = reader.Standalone?.Value;
|
||||
break;
|
||||
case "author":
|
||||
dat.ClrMamePro.Author = reader.Standalone?.Value;
|
||||
break;
|
||||
case "homepage":
|
||||
dat.ClrMamePro.Homepage = reader.Standalone?.Value;
|
||||
break;
|
||||
case "url":
|
||||
dat.ClrMamePro.Url = reader.Standalone?.Value;
|
||||
break;
|
||||
case "comment":
|
||||
dat.ClrMamePro.Comment = reader.Standalone?.Value;
|
||||
break;
|
||||
case "header":
|
||||
dat.ClrMamePro.Header = reader.Standalone?.Value;
|
||||
break;
|
||||
case "type":
|
||||
dat.ClrMamePro.Type = reader.Standalone?.Value;
|
||||
break;
|
||||
case "forcemerging":
|
||||
dat.ClrMamePro.ForceMerging = reader.Standalone?.Value;
|
||||
break;
|
||||
case "forcezipping":
|
||||
dat.ClrMamePro.ForceZipping = reader.Standalone?.Value;
|
||||
break;
|
||||
case "forcepacking":
|
||||
dat.ClrMamePro.ForcePacking = reader.Standalone?.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in a game, machine, resource, or set block
|
||||
else if ((reader.TopLevel == "game"
|
||||
|| reader.TopLevel == "machine"
|
||||
|| reader.TopLevel == "resource"
|
||||
|| reader.TopLevel == "set")
|
||||
&& reader.RowType == CmpRowType.Standalone)
|
||||
{
|
||||
// Create the block if we haven't already
|
||||
game ??= reader.TopLevel switch
|
||||
{
|
||||
"game" => new Game(),
|
||||
"machine" => new Machine(),
|
||||
"resource" => new Resource(),
|
||||
"set" => new Set(),
|
||||
_ => throw new FormatException($"Unknown top-level block: {reader.TopLevel}"),
|
||||
};
|
||||
|
||||
switch (reader.Standalone?.Key?.ToLowerInvariant())
|
||||
{
|
||||
case "name":
|
||||
game.Name = reader.Standalone?.Value;
|
||||
break;
|
||||
case "description":
|
||||
game.Description = reader.Standalone?.Value;
|
||||
break;
|
||||
case "year":
|
||||
game.Year = reader.Standalone?.Value;
|
||||
break;
|
||||
case "manufacturer":
|
||||
game.Manufacturer = reader.Standalone?.Value;
|
||||
break;
|
||||
case "category":
|
||||
game.Category = reader.Standalone?.Value;
|
||||
break;
|
||||
case "cloneof":
|
||||
game.CloneOf = reader.Standalone?.Value;
|
||||
break;
|
||||
case "romof":
|
||||
game.RomOf = reader.Standalone?.Value;
|
||||
break;
|
||||
case "sampleof":
|
||||
game.SampleOf = reader.Standalone?.Value;
|
||||
break;
|
||||
case "sample":
|
||||
var sample = new Sample
|
||||
{
|
||||
Name = reader.Standalone?.Value ?? string.Empty,
|
||||
};
|
||||
samples.Add(sample);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in an item block
|
||||
else if ((reader.TopLevel == "game"
|
||||
|| reader.TopLevel == "machine"
|
||||
|| reader.TopLevel == "resource"
|
||||
|| reader.TopLevel == "set")
|
||||
&& game != null
|
||||
&& reader.RowType == CmpRowType.Internal)
|
||||
{
|
||||
// Create the block
|
||||
switch (reader.InternalName)
|
||||
{
|
||||
case "release":
|
||||
var release = CreateRelease(reader);
|
||||
if (release != null)
|
||||
releases.Add(release);
|
||||
break;
|
||||
case "biosset":
|
||||
var biosSet = CreateBiosSet(reader);
|
||||
if (biosSet != null)
|
||||
biosSets.Add(biosSet);
|
||||
break;
|
||||
case "rom":
|
||||
var rom = CreateRom(reader);
|
||||
if (rom != null)
|
||||
roms.Add(rom);
|
||||
break;
|
||||
case "disk":
|
||||
var disk = CreateDisk(reader);
|
||||
if (disk != null)
|
||||
disks.Add(disk);
|
||||
break;
|
||||
case "media":
|
||||
var media = CreateMedia(reader);
|
||||
if (media != null)
|
||||
medias.Add(media);
|
||||
break;
|
||||
case "sample":
|
||||
var sample = CreateSample(reader);
|
||||
if (sample != null)
|
||||
samples.Add(sample);
|
||||
break;
|
||||
case "archive":
|
||||
var archive = CreateArchive(reader);
|
||||
if (archive != null)
|
||||
archives.Add(archive);
|
||||
break;
|
||||
case "chip":
|
||||
var chip = CreateChip(reader);
|
||||
if (chip != null)
|
||||
chips.Add(chip);
|
||||
break;
|
||||
case "video":
|
||||
var video = CreateVideo(reader);
|
||||
if (video != null)
|
||||
videos.Add(video);
|
||||
break;
|
||||
case "sound":
|
||||
var sound = CreateSound(reader);
|
||||
if (sound != null)
|
||||
game.Sound = sound;
|
||||
break;
|
||||
case "input":
|
||||
var input = CreateInput(reader);
|
||||
if (input != null)
|
||||
game.Input = input;
|
||||
break;
|
||||
case "dipswitch":
|
||||
var dipSwitch = CreateDipSwitch(reader);
|
||||
if (dipSwitch != null)
|
||||
dipSwitches.Add(dipSwitch);
|
||||
break;
|
||||
case "driver":
|
||||
var driver = CreateDriver(reader);
|
||||
if (driver != null)
|
||||
game.Driver = driver;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add extra pieces and return
|
||||
if (games.Count > 0)
|
||||
{
|
||||
dat.Game = [.. games];
|
||||
return dat;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -16,105 +16,109 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var cueSheet = new Models.CueSheets.CueSheet();
|
||||
var cueFiles = new List<CueFile>();
|
||||
|
||||
// Read the next line from the input
|
||||
string? lastLine = null;
|
||||
while (true)
|
||||
try
|
||||
{
|
||||
string? line = lastLine ?? reader.ReadLine();
|
||||
lastLine = null;
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var cueSheet = new Models.CueSheets.CueSheet();
|
||||
var cueFiles = new List<CueFile>();
|
||||
|
||||
// If we have a null line, break from the loop
|
||||
if (line == null)
|
||||
break;
|
||||
|
||||
// If we have an empty line, we skip
|
||||
if (string.IsNullOrEmpty(line))
|
||||
continue;
|
||||
|
||||
// http://stackoverflow.com/questions/554013/regular-expression-to-split-on-spaces-unless-in-quotes
|
||||
var matchCol = Regex.Matches(line, @"[^\s""]+|""[^""]*""");
|
||||
var splitLine = new List<string>();
|
||||
foreach (Match? match in matchCol)
|
||||
// Read the next line from the input
|
||||
string? lastLine = null;
|
||||
while (true)
|
||||
{
|
||||
if (match != null)
|
||||
splitLine.Add(match.Groups[0].Value);
|
||||
string? line = lastLine ?? reader.ReadLine();
|
||||
lastLine = null;
|
||||
|
||||
// If we have a null line, break from the loop
|
||||
if (line == null)
|
||||
break;
|
||||
|
||||
// If we have an empty line, we skip
|
||||
if (string.IsNullOrEmpty(line))
|
||||
continue;
|
||||
|
||||
// http://stackoverflow.com/questions/554013/regular-expression-to-split-on-spaces-unless-in-quotes
|
||||
var matchCol = Regex.Matches(line, @"[^\s""]+|""[^""]*""");
|
||||
var splitLine = new List<string>();
|
||||
foreach (Match? match in matchCol)
|
||||
{
|
||||
if (match != null)
|
||||
splitLine.Add(match.Groups[0].Value);
|
||||
}
|
||||
|
||||
switch (splitLine[0])
|
||||
{
|
||||
// Read comments
|
||||
case "REM":
|
||||
// We ignore all comments for now
|
||||
break;
|
||||
|
||||
// Read MCN
|
||||
case "CATALOG":
|
||||
if (splitLine.Count < 2)
|
||||
throw new FormatException($"CATALOG line malformed: {line}");
|
||||
|
||||
cueSheet.Catalog = splitLine[1].Trim('"');
|
||||
break;
|
||||
|
||||
// Read external CD-Text file path
|
||||
case "CDTEXTFILE":
|
||||
if (splitLine.Count < 2)
|
||||
throw new FormatException($"CDTEXTFILE line malformed: {line}");
|
||||
|
||||
cueSheet.CdTextFile = splitLine[1].Trim('"');
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced performer
|
||||
case "PERFORMER":
|
||||
if (splitLine.Count < 2)
|
||||
throw new FormatException($"PERFORMER line malformed: {line}");
|
||||
|
||||
cueSheet.Performer = splitLine[1].Trim('"');
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced songwriter
|
||||
case "SONGWRITER":
|
||||
if (splitLine.Count < 2)
|
||||
throw new FormatException($"SONGWRITER line malformed: {line}");
|
||||
|
||||
cueSheet.Songwriter = splitLine[1].Trim('"');
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced title
|
||||
case "TITLE":
|
||||
if (splitLine.Count < 2)
|
||||
throw new FormatException($"TITLE line malformed: {line}");
|
||||
|
||||
cueSheet.Title = splitLine[1].Trim('"');
|
||||
break;
|
||||
|
||||
// Read file information
|
||||
case "FILE":
|
||||
if (splitLine.Count < 3)
|
||||
throw new FormatException($"FILE line malformed: {line}");
|
||||
|
||||
var file = CreateCueFile(splitLine[1], splitLine[2], reader, out lastLine);
|
||||
if (file == default)
|
||||
throw new FormatException($"FILE line malformed: {line}");
|
||||
|
||||
cueFiles.Add(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (splitLine[0])
|
||||
{
|
||||
// Read comments
|
||||
case "REM":
|
||||
// We ignore all comments for now
|
||||
break;
|
||||
if (cueFiles.Count == 0)
|
||||
return null;
|
||||
|
||||
// Read MCN
|
||||
case "CATALOG":
|
||||
if (splitLine.Count < 2)
|
||||
throw new FormatException($"CATALOG line malformed: {line}");
|
||||
|
||||
cueSheet.Catalog = splitLine[1].Trim('"');
|
||||
break;
|
||||
|
||||
// Read external CD-Text file path
|
||||
case "CDTEXTFILE":
|
||||
if (splitLine.Count < 2)
|
||||
throw new FormatException($"CDTEXTFILE line malformed: {line}");
|
||||
|
||||
cueSheet.CdTextFile = splitLine[1].Trim('"');
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced performer
|
||||
case "PERFORMER":
|
||||
if (splitLine.Count < 2)
|
||||
throw new FormatException($"PERFORMER line malformed: {line}");
|
||||
|
||||
cueSheet.Performer = splitLine[1].Trim('"');
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced songwriter
|
||||
case "SONGWRITER":
|
||||
if (splitLine.Count < 2)
|
||||
throw new FormatException($"SONGWRITER line malformed: {line}");
|
||||
|
||||
cueSheet.Songwriter = splitLine[1].Trim('"');
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced title
|
||||
case "TITLE":
|
||||
if (splitLine.Count < 2)
|
||||
throw new FormatException($"TITLE line malformed: {line}");
|
||||
|
||||
cueSheet.Title = splitLine[1].Trim('"');
|
||||
break;
|
||||
|
||||
// Read file information
|
||||
case "FILE":
|
||||
if (splitLine.Count < 3)
|
||||
throw new FormatException($"FILE line malformed: {line}");
|
||||
|
||||
var file = CreateCueFile(splitLine[1], splitLine[2], reader, out lastLine);
|
||||
if (file == default)
|
||||
throw new FormatException($"FILE line malformed: {line}");
|
||||
|
||||
cueFiles.Add(file);
|
||||
break;
|
||||
}
|
||||
cueSheet.Files = [.. cueFiles];
|
||||
return cueSheet;
|
||||
}
|
||||
|
||||
if (cueFiles.Count == 0)
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
|
||||
cueSheet.Files = [.. cueFiles];
|
||||
return cueSheet;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -11,144 +11,141 @@ namespace SabreTools.Serialization.Deserializers
|
||||
/// <inheritdoc/>
|
||||
public override MetadataFile? Deserialize(Stream? data)
|
||||
{
|
||||
// If the stream is null
|
||||
if (data == null)
|
||||
return default;
|
||||
// If tthe data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
// Setup the reader and output
|
||||
var reader = new ClrMameProReader(data, Encoding.UTF8) { DosCenter = true };
|
||||
var dat = new MetadataFile();
|
||||
|
||||
// Loop through and parse out the values
|
||||
string? lastTopLevel = reader.TopLevel;
|
||||
|
||||
Game? game = null;
|
||||
var games = new List<Game>();
|
||||
var files = new List<Models.DosCenter.File>();
|
||||
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// If we have no next line
|
||||
if (!reader.ReadNextLine())
|
||||
break;
|
||||
|
||||
// Ignore certain row types
|
||||
switch (reader.RowType)
|
||||
{
|
||||
case CmpRowType.None:
|
||||
case CmpRowType.Comment:
|
||||
continue;
|
||||
case CmpRowType.EndTopLevel:
|
||||
switch (lastTopLevel)
|
||||
{
|
||||
case "game":
|
||||
if (game != null)
|
||||
{
|
||||
game.File = [.. files];
|
||||
games.Add(game);
|
||||
}
|
||||
|
||||
game = null;
|
||||
files.Clear();
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're at the root
|
||||
if (reader.RowType == CmpRowType.TopLevel)
|
||||
{
|
||||
lastTopLevel = reader.TopLevel;
|
||||
switch (reader.TopLevel)
|
||||
{
|
||||
case "doscenter":
|
||||
dat.DosCenter = new Models.DosCenter.DosCenter();
|
||||
break;
|
||||
case "game":
|
||||
game = new Game();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in the doscenter block
|
||||
else if (reader.TopLevel == "doscenter" && reader.RowType == CmpRowType.Standalone)
|
||||
{
|
||||
// Create the block if we haven't already
|
||||
dat.DosCenter ??= new Models.DosCenter.DosCenter();
|
||||
|
||||
switch (reader.Standalone?.Key?.ToLowerInvariant())
|
||||
{
|
||||
case "name:":
|
||||
dat.DosCenter.Name = reader.Standalone?.Value;
|
||||
break;
|
||||
case "description:":
|
||||
dat.DosCenter.Description = reader.Standalone?.Value;
|
||||
break;
|
||||
case "version:":
|
||||
dat.DosCenter.Version = reader.Standalone?.Value;
|
||||
break;
|
||||
case "date:":
|
||||
dat.DosCenter.Date = reader.Standalone?.Value;
|
||||
break;
|
||||
case "author:":
|
||||
dat.DosCenter.Author = reader.Standalone?.Value;
|
||||
break;
|
||||
case "homepage:":
|
||||
dat.DosCenter.Homepage = reader.Standalone?.Value;
|
||||
break;
|
||||
case "comment:":
|
||||
dat.DosCenter.Comment = reader.Standalone?.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in a game block
|
||||
else if (reader.TopLevel == "game" && reader.RowType == CmpRowType.Standalone)
|
||||
{
|
||||
// Create the block if we haven't already
|
||||
game ??= new Game();
|
||||
|
||||
switch (reader.Standalone?.Key?.ToLowerInvariant())
|
||||
{
|
||||
case "name":
|
||||
game.Name = reader.Standalone?.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in a file block
|
||||
else if (reader.TopLevel == "game" && reader.RowType == CmpRowType.Internal)
|
||||
{
|
||||
// If we have an unknown type, log it
|
||||
if (reader.InternalName != "file")
|
||||
continue;
|
||||
|
||||
// Create the file and add to the list
|
||||
var file = CreateFile(reader);
|
||||
if (file != null)
|
||||
files.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
// Add extra pieces and return
|
||||
if (games.Count > 0)
|
||||
{
|
||||
dat.Game = [.. games];
|
||||
return dat;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new ClrMameProReader(data, Encoding.UTF8) { DosCenter = true };
|
||||
var dat = new MetadataFile();
|
||||
|
||||
// Loop through and parse out the values
|
||||
string? lastTopLevel = reader.TopLevel;
|
||||
|
||||
Game? game = null;
|
||||
var games = new List<Game>();
|
||||
var files = new List<Models.DosCenter.File>();
|
||||
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// If we have no next line
|
||||
if (!reader.ReadNextLine())
|
||||
break;
|
||||
|
||||
// Ignore certain row types
|
||||
switch (reader.RowType)
|
||||
{
|
||||
case CmpRowType.None:
|
||||
case CmpRowType.Comment:
|
||||
continue;
|
||||
case CmpRowType.EndTopLevel:
|
||||
switch (lastTopLevel)
|
||||
{
|
||||
case "game":
|
||||
if (game != null)
|
||||
{
|
||||
game.File = [.. files];
|
||||
games.Add(game);
|
||||
}
|
||||
|
||||
game = null;
|
||||
files.Clear();
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're at the root
|
||||
if (reader.RowType == CmpRowType.TopLevel)
|
||||
{
|
||||
lastTopLevel = reader.TopLevel;
|
||||
switch (reader.TopLevel)
|
||||
{
|
||||
case "doscenter":
|
||||
dat.DosCenter = new Models.DosCenter.DosCenter();
|
||||
break;
|
||||
case "game":
|
||||
game = new Game();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in the doscenter block
|
||||
else if (reader.TopLevel == "doscenter" && reader.RowType == CmpRowType.Standalone)
|
||||
{
|
||||
// Create the block if we haven't already
|
||||
dat.DosCenter ??= new Models.DosCenter.DosCenter();
|
||||
|
||||
switch (reader.Standalone?.Key?.ToLowerInvariant())
|
||||
{
|
||||
case "name:":
|
||||
dat.DosCenter.Name = reader.Standalone?.Value;
|
||||
break;
|
||||
case "description:":
|
||||
dat.DosCenter.Description = reader.Standalone?.Value;
|
||||
break;
|
||||
case "version:":
|
||||
dat.DosCenter.Version = reader.Standalone?.Value;
|
||||
break;
|
||||
case "date:":
|
||||
dat.DosCenter.Date = reader.Standalone?.Value;
|
||||
break;
|
||||
case "author:":
|
||||
dat.DosCenter.Author = reader.Standalone?.Value;
|
||||
break;
|
||||
case "homepage:":
|
||||
dat.DosCenter.Homepage = reader.Standalone?.Value;
|
||||
break;
|
||||
case "comment:":
|
||||
dat.DosCenter.Comment = reader.Standalone?.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in a game block
|
||||
else if (reader.TopLevel == "game" && reader.RowType == CmpRowType.Standalone)
|
||||
{
|
||||
// Create the block if we haven't already
|
||||
game ??= new Game();
|
||||
|
||||
switch (reader.Standalone?.Key?.ToLowerInvariant())
|
||||
{
|
||||
case "name":
|
||||
game.Name = reader.Standalone?.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in a file block
|
||||
else if (reader.TopLevel == "game" && reader.RowType == CmpRowType.Internal)
|
||||
{
|
||||
// If we have an unknown type, log it
|
||||
if (reader.InternalName != "file")
|
||||
continue;
|
||||
|
||||
// Create the file and add to the list
|
||||
var file = CreateFile(reader);
|
||||
if (file != null)
|
||||
files.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
// Add extra pieces and return
|
||||
if (games.Count > 0)
|
||||
{
|
||||
dat.Game = [.. games];
|
||||
return dat;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,56 +15,60 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new SeparatedValueReader(data, Encoding.UTF8)
|
||||
try
|
||||
{
|
||||
Header = false,
|
||||
Separator = '\t',
|
||||
VerifyFieldCount = false,
|
||||
};
|
||||
var dat = new MetadataFile();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
var rows = new List<Row>();
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// If we have no next line
|
||||
if (!reader.ReadNextLine() || reader.Line == null)
|
||||
break;
|
||||
|
||||
// If the next line has an invalid count
|
||||
if (reader.Line.Count < 5)
|
||||
break;
|
||||
|
||||
// Parse the line into a row
|
||||
var row = new Row
|
||||
// Setup the reader and output
|
||||
var reader = new SeparatedValueReader(data, Encoding.UTF8)
|
||||
{
|
||||
SHA256 = reader.Line[0],
|
||||
Name = reader.Line[1],
|
||||
SHA1 = reader.Line[2],
|
||||
MD5 = reader.Line[3],
|
||||
CRC32 = reader.Line[4],
|
||||
Header = false,
|
||||
Separator = '\t',
|
||||
VerifyFieldCount = false,
|
||||
};
|
||||
var dat = new MetadataFile();
|
||||
|
||||
// If we have the size field
|
||||
if (reader.Line.Count > 5)
|
||||
row.Size = reader.Line[5];
|
||||
// Loop through the rows and parse out values
|
||||
var rows = new List<Row>();
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// If we have no next line
|
||||
if (!reader.ReadNextLine() || reader.Line == null)
|
||||
break;
|
||||
|
||||
rows.Add(row);
|
||||
// If the next line has an invalid count
|
||||
if (reader.Line.Count < 5)
|
||||
break;
|
||||
|
||||
// Parse the line into a row
|
||||
var row = new Row
|
||||
{
|
||||
SHA256 = reader.Line[0],
|
||||
Name = reader.Line[1],
|
||||
SHA1 = reader.Line[2],
|
||||
MD5 = reader.Line[3],
|
||||
CRC32 = reader.Line[4],
|
||||
};
|
||||
|
||||
// If we have the size field
|
||||
if (reader.Line.Count > 5)
|
||||
row.Size = reader.Line[5];
|
||||
|
||||
rows.Add(row);
|
||||
}
|
||||
|
||||
// Assign the rows to the Dat and return
|
||||
if (rows.Count > 0)
|
||||
{
|
||||
dat.Row = [.. rows];
|
||||
return dat;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Assign the rows to the Dat and return
|
||||
if (rows.Count > 0)
|
||||
catch
|
||||
{
|
||||
dat.Row = [.. rows];
|
||||
return dat;
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,369 +14,373 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new Half-Life Game Cache to fill
|
||||
var file = new Models.GCF.File();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Dummy0 != 0x00000001)
|
||||
return null;
|
||||
if (header?.MajorVersion != 0x00000001)
|
||||
return null;
|
||||
if (header.MinorVersion != 3 && header.MinorVersion != 5 && header.MinorVersion != 6)
|
||||
return null;
|
||||
|
||||
// Set the game cache header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Block Entry Header
|
||||
|
||||
// Try to parse the block entry header
|
||||
var blockEntryHeader = data.ReadType<BlockEntryHeader>();
|
||||
if (blockEntryHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the game cache block entry header
|
||||
file.BlockEntryHeader = blockEntryHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Block Entries
|
||||
|
||||
// Create the block entry array
|
||||
file.BlockEntries = new BlockEntry[blockEntryHeader.BlockCount];
|
||||
|
||||
// Try to parse the block entries
|
||||
for (int i = 0; i < blockEntryHeader.BlockCount; i++)
|
||||
try
|
||||
{
|
||||
var blockEntry = data.ReadType<BlockEntry>();
|
||||
if (blockEntry == null)
|
||||
// Create a new Half-Life Game Cache to fill
|
||||
var file = new Models.GCF.File();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Dummy0 != 0x00000001)
|
||||
return null;
|
||||
if (header?.MajorVersion != 0x00000001)
|
||||
return null;
|
||||
if (header.MinorVersion != 3 && header.MinorVersion != 5 && header.MinorVersion != 6)
|
||||
return null;
|
||||
|
||||
file.BlockEntries[i] = blockEntry;
|
||||
}
|
||||
// Set the game cache header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Fragmentation Map Header
|
||||
#region Block Entry Header
|
||||
|
||||
// Try to parse the fragmentation map header
|
||||
var fragmentationMapHeader = data.ReadType<FragmentationMapHeader>();
|
||||
if (fragmentationMapHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the game cache fragmentation map header
|
||||
file.FragmentationMapHeader = fragmentationMapHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fragmentation Maps
|
||||
|
||||
// Create the fragmentation map array
|
||||
file.FragmentationMaps = new FragmentationMap[fragmentationMapHeader.BlockCount];
|
||||
|
||||
// Try to parse the fragmentation maps
|
||||
for (int i = 0; i < fragmentationMapHeader.BlockCount; i++)
|
||||
{
|
||||
var fragmentationMap = data.ReadType<FragmentationMap>();
|
||||
if (fragmentationMap == null)
|
||||
// Try to parse the block entry header
|
||||
var blockEntryHeader = data.ReadType<BlockEntryHeader>();
|
||||
if (blockEntryHeader == null)
|
||||
return null;
|
||||
|
||||
file.FragmentationMaps[i] = fragmentationMap;
|
||||
}
|
||||
// Set the game cache block entry header
|
||||
file.BlockEntryHeader = blockEntryHeader;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Block Entry Map Header
|
||||
#region Block Entries
|
||||
|
||||
if (header.MinorVersion < 6)
|
||||
{
|
||||
// Try to parse the block entry map header
|
||||
var blockEntryMapHeader = data.ReadType<BlockEntryMapHeader>();
|
||||
if (blockEntryMapHeader == null)
|
||||
return null;
|
||||
// Create the block entry array
|
||||
file.BlockEntries = new BlockEntry[blockEntryHeader.BlockCount];
|
||||
|
||||
// Set the game cache block entry map header
|
||||
file.BlockEntryMapHeader = blockEntryMapHeader;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Block Entry Maps
|
||||
|
||||
if (header.MinorVersion < 6)
|
||||
{
|
||||
// Create the block entry map array
|
||||
file.BlockEntryMaps = new BlockEntryMap[file.BlockEntryMapHeader!.BlockCount];
|
||||
|
||||
// Try to parse the block entry maps
|
||||
for (int i = 0; i < file.BlockEntryMapHeader.BlockCount; i++)
|
||||
// Try to parse the block entries
|
||||
for (int i = 0; i < blockEntryHeader.BlockCount; i++)
|
||||
{
|
||||
var blockEntryMap = data.ReadType<BlockEntryMap>();
|
||||
if (blockEntryMap == null)
|
||||
var blockEntry = data.ReadType<BlockEntry>();
|
||||
if (blockEntry == null)
|
||||
return null;
|
||||
|
||||
file.BlockEntryMaps[i] = blockEntryMap;
|
||||
file.BlockEntries[i] = blockEntry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
// Cache the current offset
|
||||
long initialOffset = data.Position;
|
||||
#region Fragmentation Map Header
|
||||
|
||||
#region Directory Header
|
||||
|
||||
// Try to parse the directory header
|
||||
var directoryHeader = data.ReadType<DirectoryHeader>();
|
||||
if (directoryHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the game cache directory header
|
||||
file.DirectoryHeader = directoryHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Entries
|
||||
|
||||
// Create the directory entry array
|
||||
file.DirectoryEntries = new DirectoryEntry[directoryHeader.ItemCount];
|
||||
|
||||
// Try to parse the directory entries
|
||||
for (int i = 0; i < directoryHeader.ItemCount; i++)
|
||||
{
|
||||
var directoryEntry = data.ReadType<DirectoryEntry>();
|
||||
if (directoryEntry == null)
|
||||
// Try to parse the fragmentation map header
|
||||
var fragmentationMapHeader = data.ReadType<FragmentationMapHeader>();
|
||||
if (fragmentationMapHeader == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryEntries[i] = directoryEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
// Set the game cache fragmentation map header
|
||||
file.FragmentationMapHeader = fragmentationMapHeader;
|
||||
|
||||
#region Directory Names
|
||||
#endregion
|
||||
|
||||
if (directoryHeader.NameSize > 0)
|
||||
{
|
||||
// Get the current offset for adjustment
|
||||
long directoryNamesStart = data.Position;
|
||||
#region Fragmentation Maps
|
||||
|
||||
// Get the ending offset
|
||||
long directoryNamesEnd = data.Position + directoryHeader.NameSize;
|
||||
// Create the fragmentation map array
|
||||
file.FragmentationMaps = new FragmentationMap[fragmentationMapHeader.BlockCount];
|
||||
|
||||
// Create the string dictionary
|
||||
file.DirectoryNames = [];
|
||||
|
||||
// Loop and read the null-terminated strings
|
||||
while (data.Position < directoryNamesEnd)
|
||||
// Try to parse the fragmentation maps
|
||||
for (int i = 0; i < fragmentationMapHeader.BlockCount; i++)
|
||||
{
|
||||
long nameOffset = data.Position - directoryNamesStart;
|
||||
string? directoryName = data.ReadNullTerminatedAnsiString();
|
||||
if (data.Position > directoryNamesEnd)
|
||||
{
|
||||
data.Seek(-directoryName?.Length ?? 0, SeekOrigin.Current);
|
||||
byte[] endingData = data.ReadBytes((int)(directoryNamesEnd - data.Position));
|
||||
directoryName = Encoding.ASCII.GetString(endingData);
|
||||
}
|
||||
var fragmentationMap = data.ReadType<FragmentationMap>();
|
||||
if (fragmentationMap == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryNames[nameOffset] = directoryName;
|
||||
file.FragmentationMaps[i] = fragmentationMap;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Directory Info 1 Entries
|
||||
#region Block Entry Map Header
|
||||
|
||||
// Create the directory info 1 entry array
|
||||
file.DirectoryInfo1Entries = new DirectoryInfo1Entry[directoryHeader.Info1Count];
|
||||
if (header.MinorVersion < 6)
|
||||
{
|
||||
// Try to parse the block entry map header
|
||||
var blockEntryMapHeader = data.ReadType<BlockEntryMapHeader>();
|
||||
if (blockEntryMapHeader == null)
|
||||
return null;
|
||||
|
||||
// Try to parse the directory info 1 entries
|
||||
for (int i = 0; i < directoryHeader.Info1Count; i++)
|
||||
{
|
||||
var directoryInfo1Entry = data.ReadType<DirectoryInfo1Entry>();
|
||||
if (directoryInfo1Entry == null)
|
||||
// Set the game cache block entry map header
|
||||
file.BlockEntryMapHeader = blockEntryMapHeader;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Block Entry Maps
|
||||
|
||||
if (header.MinorVersion < 6)
|
||||
{
|
||||
// Create the block entry map array
|
||||
file.BlockEntryMaps = new BlockEntryMap[file.BlockEntryMapHeader!.BlockCount];
|
||||
|
||||
// Try to parse the block entry maps
|
||||
for (int i = 0; i < file.BlockEntryMapHeader.BlockCount; i++)
|
||||
{
|
||||
var blockEntryMap = data.ReadType<BlockEntryMap>();
|
||||
if (blockEntryMap == null)
|
||||
return null;
|
||||
|
||||
file.BlockEntryMaps[i] = blockEntryMap;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Cache the current offset
|
||||
long initialOffset = data.Position;
|
||||
|
||||
#region Directory Header
|
||||
|
||||
// Try to parse the directory header
|
||||
var directoryHeader = data.ReadType<DirectoryHeader>();
|
||||
if (directoryHeader == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryInfo1Entries[i] = directoryInfo1Entry;
|
||||
}
|
||||
// Set the game cache directory header
|
||||
file.DirectoryHeader = directoryHeader;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Directory Info 2 Entries
|
||||
#region Directory Entries
|
||||
|
||||
// Create the directory info 2 entry array
|
||||
file.DirectoryInfo2Entries = new DirectoryInfo2Entry[directoryHeader.ItemCount];
|
||||
// Create the directory entry array
|
||||
file.DirectoryEntries = new DirectoryEntry[directoryHeader.ItemCount];
|
||||
|
||||
// Try to parse the directory info 2 entries
|
||||
for (int i = 0; i < directoryHeader.ItemCount; i++)
|
||||
{
|
||||
var directoryInfo2Entry = data.ReadType<DirectoryInfo2Entry>();
|
||||
if (directoryInfo2Entry == null)
|
||||
// Try to parse the directory entries
|
||||
for (int i = 0; i < directoryHeader.ItemCount; i++)
|
||||
{
|
||||
var directoryEntry = data.ReadType<DirectoryEntry>();
|
||||
if (directoryEntry == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryEntries[i] = directoryEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Names
|
||||
|
||||
if (directoryHeader.NameSize > 0)
|
||||
{
|
||||
// Get the current offset for adjustment
|
||||
long directoryNamesStart = data.Position;
|
||||
|
||||
// Get the ending offset
|
||||
long directoryNamesEnd = data.Position + directoryHeader.NameSize;
|
||||
|
||||
// Create the string dictionary
|
||||
file.DirectoryNames = [];
|
||||
|
||||
// Loop and read the null-terminated strings
|
||||
while (data.Position < directoryNamesEnd)
|
||||
{
|
||||
long nameOffset = data.Position - directoryNamesStart;
|
||||
string? directoryName = data.ReadNullTerminatedAnsiString();
|
||||
if (data.Position > directoryNamesEnd)
|
||||
{
|
||||
data.Seek(-directoryName?.Length ?? 0, SeekOrigin.Current);
|
||||
byte[] endingData = data.ReadBytes((int)(directoryNamesEnd - data.Position));
|
||||
directoryName = Encoding.ASCII.GetString(endingData);
|
||||
}
|
||||
|
||||
file.DirectoryNames[nameOffset] = directoryName;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Info 1 Entries
|
||||
|
||||
// Create the directory info 1 entry array
|
||||
file.DirectoryInfo1Entries = new DirectoryInfo1Entry[directoryHeader.Info1Count];
|
||||
|
||||
// Try to parse the directory info 1 entries
|
||||
for (int i = 0; i < directoryHeader.Info1Count; i++)
|
||||
{
|
||||
var directoryInfo1Entry = data.ReadType<DirectoryInfo1Entry>();
|
||||
if (directoryInfo1Entry == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryInfo1Entries[i] = directoryInfo1Entry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Info 2 Entries
|
||||
|
||||
// Create the directory info 2 entry array
|
||||
file.DirectoryInfo2Entries = new DirectoryInfo2Entry[directoryHeader.ItemCount];
|
||||
|
||||
// Try to parse the directory info 2 entries
|
||||
for (int i = 0; i < directoryHeader.ItemCount; i++)
|
||||
{
|
||||
var directoryInfo2Entry = data.ReadType<DirectoryInfo2Entry>();
|
||||
if (directoryInfo2Entry == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryInfo2Entries[i] = directoryInfo2Entry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Copy Entries
|
||||
|
||||
// Create the directory copy entry array
|
||||
file.DirectoryCopyEntries = new DirectoryCopyEntry[directoryHeader.CopyCount];
|
||||
|
||||
// Try to parse the directory copy entries
|
||||
for (int i = 0; i < directoryHeader.CopyCount; i++)
|
||||
{
|
||||
var directoryCopyEntry = data.ReadType<DirectoryCopyEntry>();
|
||||
if (directoryCopyEntry == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryCopyEntries[i] = directoryCopyEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Local Entries
|
||||
|
||||
// Create the directory local entry array
|
||||
file.DirectoryLocalEntries = new DirectoryLocalEntry[directoryHeader.LocalCount];
|
||||
|
||||
// Try to parse the directory local entries
|
||||
for (int i = 0; i < directoryHeader.LocalCount; i++)
|
||||
{
|
||||
var directoryLocalEntry = data.ReadType<DirectoryLocalEntry>();
|
||||
if (directoryLocalEntry == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryLocalEntries[i] = directoryLocalEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Seek to end of directory section, just in case
|
||||
data.Seek(initialOffset + directoryHeader.DirectorySize, SeekOrigin.Begin);
|
||||
|
||||
#region Directory Map Header
|
||||
|
||||
if (header.MinorVersion >= 5)
|
||||
{
|
||||
// Try to parse the directory map header
|
||||
var directoryMapHeader = data.ReadType<DirectoryMapHeader>();
|
||||
if (directoryMapHeader?.Dummy0 != 0x00000001)
|
||||
return null;
|
||||
if (directoryMapHeader?.Dummy1 != 0x00000000)
|
||||
return null;
|
||||
|
||||
// Set the game cache directory map header
|
||||
file.DirectoryMapHeader = directoryMapHeader;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Map Entries
|
||||
|
||||
// Create the directory map entry array
|
||||
file.DirectoryMapEntries = new DirectoryMapEntry[directoryHeader.ItemCount];
|
||||
|
||||
// Try to parse the directory map entries
|
||||
for (int i = 0; i < directoryHeader.ItemCount; i++)
|
||||
{
|
||||
var directoryMapEntry = data.ReadType<DirectoryMapEntry>();
|
||||
if (directoryMapEntry == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryMapEntries[i] = directoryMapEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Checksum Header
|
||||
|
||||
// Try to parse the checksum header
|
||||
var checksumHeader = data.ReadType<ChecksumHeader>();
|
||||
if (checksumHeader?.Dummy0 != 0x00000001)
|
||||
return null;
|
||||
|
||||
file.DirectoryInfo2Entries[i] = directoryInfo2Entry;
|
||||
}
|
||||
// Set the game cache checksum header
|
||||
file.ChecksumHeader = checksumHeader;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Directory Copy Entries
|
||||
// Cache the current offset
|
||||
initialOffset = data.Position;
|
||||
|
||||
// Create the directory copy entry array
|
||||
file.DirectoryCopyEntries = new DirectoryCopyEntry[directoryHeader.CopyCount];
|
||||
#region Checksum Map Header
|
||||
|
||||
// Try to parse the directory copy entries
|
||||
for (int i = 0; i < directoryHeader.CopyCount; i++)
|
||||
{
|
||||
var directoryCopyEntry = data.ReadType<DirectoryCopyEntry>();
|
||||
if (directoryCopyEntry == null)
|
||||
// Try to parse the checksum map header
|
||||
var checksumMapHeader = data.ReadType<ChecksumMapHeader>();
|
||||
if (checksumMapHeader?.Dummy0 != 0x14893721)
|
||||
return null;
|
||||
if (checksumMapHeader?.Dummy1 != 0x00000001)
|
||||
return null;
|
||||
|
||||
file.DirectoryCopyEntries[i] = directoryCopyEntry;
|
||||
}
|
||||
// Set the game cache checksum map header
|
||||
file.ChecksumMapHeader = checksumMapHeader;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Directory Local Entries
|
||||
#region Checksum Map Entries
|
||||
|
||||
// Create the directory local entry array
|
||||
file.DirectoryLocalEntries = new DirectoryLocalEntry[directoryHeader.LocalCount];
|
||||
// Create the checksum map entry array
|
||||
file.ChecksumMapEntries = new ChecksumMapEntry[checksumMapHeader.ItemCount];
|
||||
|
||||
// Try to parse the directory local entries
|
||||
for (int i = 0; i < directoryHeader.LocalCount; i++)
|
||||
{
|
||||
var directoryLocalEntry = data.ReadType<DirectoryLocalEntry>();
|
||||
if (directoryLocalEntry == null)
|
||||
// Try to parse the checksum map entries
|
||||
for (int i = 0; i < checksumMapHeader.ItemCount; i++)
|
||||
{
|
||||
var checksumMapEntry = data.ReadType<ChecksumMapEntry>();
|
||||
if (checksumMapEntry == null)
|
||||
return null;
|
||||
|
||||
file.ChecksumMapEntries[i] = checksumMapEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Checksum Entries
|
||||
|
||||
// Create the checksum entry array
|
||||
file.ChecksumEntries = new ChecksumEntry[checksumMapHeader.ChecksumCount];
|
||||
|
||||
// Try to parse the checksum entries
|
||||
for (int i = 0; i < checksumMapHeader.ChecksumCount; i++)
|
||||
{
|
||||
var checksumEntry = data.ReadType<ChecksumEntry>();
|
||||
if (checksumEntry == null)
|
||||
return null;
|
||||
|
||||
file.ChecksumEntries[i] = checksumEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Seek to end of checksum section, just in case
|
||||
data.Seek(initialOffset + checksumHeader.ChecksumSize, SeekOrigin.Begin);
|
||||
|
||||
#region Data Block Header
|
||||
|
||||
// Try to parse the data block header
|
||||
var dataBlockHeader = ParseDataBlockHeader(data, header.MinorVersion);
|
||||
if (dataBlockHeader == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryLocalEntries[i] = directoryLocalEntry;
|
||||
// Set the game cache data block header
|
||||
file.DataBlockHeader = dataBlockHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Seek to end of directory section, just in case
|
||||
data.Seek(initialOffset + directoryHeader.DirectorySize, SeekOrigin.Begin);
|
||||
|
||||
#region Directory Map Header
|
||||
|
||||
if (header.MinorVersion >= 5)
|
||||
catch
|
||||
{
|
||||
// Try to parse the directory map header
|
||||
var directoryMapHeader = data.ReadType<DirectoryMapHeader>();
|
||||
if (directoryMapHeader?.Dummy0 != 0x00000001)
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
if (directoryMapHeader?.Dummy1 != 0x00000000)
|
||||
return null;
|
||||
|
||||
// Set the game cache directory map header
|
||||
file.DirectoryMapHeader = directoryMapHeader;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Map Entries
|
||||
|
||||
// Create the directory map entry array
|
||||
file.DirectoryMapEntries = new DirectoryMapEntry[directoryHeader.ItemCount];
|
||||
|
||||
// Try to parse the directory map entries
|
||||
for (int i = 0; i < directoryHeader.ItemCount; i++)
|
||||
{
|
||||
var directoryMapEntry = data.ReadType<DirectoryMapEntry>();
|
||||
if (directoryMapEntry == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryMapEntries[i] = directoryMapEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Checksum Header
|
||||
|
||||
// Try to parse the checksum header
|
||||
var checksumHeader = data.ReadType<ChecksumHeader>();
|
||||
if (checksumHeader?.Dummy0 != 0x00000001)
|
||||
return null;
|
||||
|
||||
// Set the game cache checksum header
|
||||
file.ChecksumHeader = checksumHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
// Cache the current offset
|
||||
initialOffset = data.Position;
|
||||
|
||||
#region Checksum Map Header
|
||||
|
||||
// Try to parse the checksum map header
|
||||
var checksumMapHeader = data.ReadType<ChecksumMapHeader>();
|
||||
if (checksumMapHeader?.Dummy0 != 0x14893721)
|
||||
return null;
|
||||
if (checksumMapHeader?.Dummy1 != 0x00000001)
|
||||
return null;
|
||||
|
||||
// Set the game cache checksum map header
|
||||
file.ChecksumMapHeader = checksumMapHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Checksum Map Entries
|
||||
|
||||
// Create the checksum map entry array
|
||||
file.ChecksumMapEntries = new ChecksumMapEntry[checksumMapHeader.ItemCount];
|
||||
|
||||
// Try to parse the checksum map entries
|
||||
for (int i = 0; i < checksumMapHeader.ItemCount; i++)
|
||||
{
|
||||
var checksumMapEntry = data.ReadType<ChecksumMapEntry>();
|
||||
if (checksumMapEntry == null)
|
||||
return null;
|
||||
|
||||
file.ChecksumMapEntries[i] = checksumMapEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Checksum Entries
|
||||
|
||||
// Create the checksum entry array
|
||||
file.ChecksumEntries = new ChecksumEntry[checksumMapHeader.ChecksumCount];
|
||||
|
||||
// Try to parse the checksum entries
|
||||
for (int i = 0; i < checksumMapHeader.ChecksumCount; i++)
|
||||
{
|
||||
var checksumEntry = data.ReadType<ChecksumEntry>();
|
||||
if (checksumEntry == null)
|
||||
return null;
|
||||
|
||||
file.ChecksumEntries[i] = checksumEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Seek to end of checksum section, just in case
|
||||
data.Seek(initialOffset + checksumHeader.ChecksumSize, SeekOrigin.Begin);
|
||||
|
||||
#region Data Block Header
|
||||
|
||||
// Try to parse the data block header
|
||||
var dataBlockHeader = ParseDataBlockHeader(data, header.MinorVersion);
|
||||
if (dataBlockHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the game cache data block header
|
||||
file.DataBlockHeader = dataBlockHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -96,160 +96,140 @@ namespace SabreTools.Serialization.Deserializers
|
||||
/// <inheritdoc cref="Deserialize(Stream)"/>
|
||||
public Models.Hashfile.Hashfile? DeserializeSFV(Stream? data)
|
||||
{
|
||||
// If the stream is null
|
||||
if (data == null)
|
||||
return default;
|
||||
// If tthe data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var sfvList = new List<SFV>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
var sfv = new SFV
|
||||
{
|
||||
File = string.Join(" ", lineParts, 0, lineParts.Length - 1),
|
||||
Hash = lineParts[lineParts.Length - 1],
|
||||
};
|
||||
sfvList.Add(sfv);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (sfvList.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { SFV = [.. sfvList] };
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var sfvList = new List<SFV>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
var sfv = new SFV
|
||||
{
|
||||
File = string.Join(" ", lineParts, 0, lineParts.Length - 1),
|
||||
Hash = lineParts[lineParts.Length - 1],
|
||||
};
|
||||
sfvList.Add(sfv);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (sfvList.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { SFV = [.. sfvList] };
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Deserialize(Stream)"/>
|
||||
public Models.Hashfile.Hashfile? DeserializeMD2(Stream? data)
|
||||
{
|
||||
// If the stream is null
|
||||
if (data == null)
|
||||
return default;
|
||||
// If tthe data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var md2List = new List<MD2>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
// Parse the line into a hash
|
||||
var md2 = new MD2
|
||||
{
|
||||
Hash = lineParts[0],
|
||||
File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
|
||||
};
|
||||
md2List.Add(md2);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (md2List.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { MD2 = [.. md2List] };
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var md2List = new List<MD2>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
// Parse the line into a hash
|
||||
var md2 = new MD2
|
||||
{
|
||||
Hash = lineParts[0],
|
||||
File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
|
||||
};
|
||||
md2List.Add(md2);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (md2List.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { MD2 = [.. md2List] };
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Deserialize(Stream)"/>
|
||||
public Models.Hashfile.Hashfile? DeserializeMD4(Stream? data)
|
||||
{
|
||||
// If the stream is null
|
||||
if (data == null)
|
||||
return default;
|
||||
// If tthe data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var md4List = new List<MD4>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
// Parse the line into a hash
|
||||
var md4 = new MD4
|
||||
{
|
||||
Hash = lineParts[0],
|
||||
File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
|
||||
};
|
||||
md4List.Add(md4);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (md4List.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { MD4 = [.. md4List] };
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var md4List = new List<MD4>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
// Parse the line into a hash
|
||||
var md4 = new MD4
|
||||
{
|
||||
Hash = lineParts[0],
|
||||
File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
|
||||
};
|
||||
md4List.Add(md4);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (md4List.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { MD4 = [.. md4List] };
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Deserialize(Stream)"/>
|
||||
public Models.Hashfile.Hashfile? DeserializeMD5(Stream? data)
|
||||
{
|
||||
// If the stream is null
|
||||
if (data == null)
|
||||
return default;
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
}
|
||||
// If tthe data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
@@ -283,236 +263,221 @@ namespace SabreTools.Serialization.Deserializers
|
||||
/// <inheritdoc cref="Deserialize(Stream)"/>
|
||||
public Models.Hashfile.Hashfile? DeserializeSHA1(Stream? data)
|
||||
{
|
||||
// If the stream is null
|
||||
if (data == null)
|
||||
return default;
|
||||
// If tthe data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var sha1List = new List<SHA1>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
// Parse the line into a hash
|
||||
var sha1 = new SHA1
|
||||
{
|
||||
Hash = lineParts[0],
|
||||
File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
|
||||
};
|
||||
sha1List.Add(sha1);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (sha1List.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { SHA1 = [.. sha1List] };
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var sha1List = new List<SHA1>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
// Parse the line into a hash
|
||||
var sha1 = new SHA1
|
||||
{
|
||||
Hash = lineParts[0],
|
||||
File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
|
||||
};
|
||||
sha1List.Add(sha1);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (sha1List.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { SHA1 = [.. sha1List] };
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Deserialize(Stream)"/>
|
||||
public Models.Hashfile.Hashfile? DeserializeSHA256(Stream? data)
|
||||
{
|
||||
// If the stream is null
|
||||
if (data == null)
|
||||
return default;
|
||||
// If tthe data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var sha256List = new List<SHA256>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
// Parse the line into a hash
|
||||
var sha256 = new SHA256
|
||||
{
|
||||
Hash = lineParts[0],
|
||||
File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
|
||||
};
|
||||
sha256List.Add(sha256);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (sha256List.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { SHA256 = [.. sha256List] };
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var sha256List = new List<SHA256>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
// Parse the line into a hash
|
||||
var sha256 = new SHA256
|
||||
{
|
||||
Hash = lineParts[0],
|
||||
File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
|
||||
};
|
||||
sha256List.Add(sha256);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (sha256List.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { SHA256 = [.. sha256List] };
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Deserialize(Stream)"/>
|
||||
public Models.Hashfile.Hashfile? DeserializeSHA384(Stream? data)
|
||||
{
|
||||
// If the stream is null
|
||||
if (data == null)
|
||||
return default;
|
||||
// If tthe data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var sha384List = new List<SHA384>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
// Parse the line into a hash
|
||||
var sha384 = new SHA384
|
||||
{
|
||||
Hash = lineParts[0],
|
||||
File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
|
||||
};
|
||||
sha384List.Add(sha384);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (sha384List.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { SHA384 = [.. sha384List] };
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var sha384List = new List<SHA384>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
// Parse the line into a hash
|
||||
var sha384 = new SHA384
|
||||
{
|
||||
Hash = lineParts[0],
|
||||
File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
|
||||
};
|
||||
sha384List.Add(sha384);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (sha384List.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { SHA384 = [.. sha384List] };
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Deserialize(Stream)"/>
|
||||
public Models.Hashfile.Hashfile? DeserializeSHA512(Stream? data)
|
||||
{
|
||||
// If the stream is null
|
||||
if (data == null)
|
||||
return default;
|
||||
// If tthe data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var sha512List = new List<SHA512>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
// Parse the line into a hash
|
||||
var sha512 = new SHA512
|
||||
{
|
||||
Hash = lineParts[0],
|
||||
File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
|
||||
};
|
||||
sha512List.Add(sha512);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (sha512List.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { SHA512 = [.. sha512List] };
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var sha512List = new List<SHA512>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
// Parse the line into a hash
|
||||
var sha512 = new SHA512
|
||||
{
|
||||
Hash = lineParts[0],
|
||||
File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
|
||||
};
|
||||
sha512List.Add(sha512);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (sha512List.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { SHA512 = [.. sha512List] };
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Deserialize(Stream)"/>
|
||||
public Models.Hashfile.Hashfile? DeserializeSpamSum(Stream? data)
|
||||
{
|
||||
// If the stream is null
|
||||
if (data == null)
|
||||
// If tthe data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return default;
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var spamsumList = new List<SpamSum>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
// Parse the line into a hash
|
||||
var spamSum = new SpamSum
|
||||
{
|
||||
Hash = lineParts[0],
|
||||
File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
|
||||
};
|
||||
spamsumList.Add(spamSum);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (spamsumList.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { SpamSum = [.. spamsumList] };
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data);
|
||||
var spamsumList = new List<SpamSum>();
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read and split the line
|
||||
string? line = reader.ReadLine();
|
||||
string[]? lineParts = line?.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts == null || lineParts.Length < 2)
|
||||
continue;
|
||||
|
||||
// Parse the line into a hash
|
||||
var spamSum = new SpamSum
|
||||
{
|
||||
Hash = lineParts[0],
|
||||
File = string.Join(" ", lineParts, 1, lineParts.Length - 1),
|
||||
};
|
||||
spamsumList.Add(spamSum);
|
||||
}
|
||||
|
||||
// Assign the hashes to the hashfile and return
|
||||
if (spamsumList.Count > 0)
|
||||
return new Models.Hashfile.Hashfile { SpamSum = [.. spamsumList] };
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -13,80 +13,84 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new IRD to fill
|
||||
var ird = new Models.IRD.File();
|
||||
|
||||
ird.Magic = data.ReadBytes(4);
|
||||
string magic = Encoding.ASCII.GetString(ird.Magic);
|
||||
if (magic != "3IRD")
|
||||
return null;
|
||||
|
||||
ird.Version = data.ReadByteValue();
|
||||
if (ird.Version < 6)
|
||||
return null;
|
||||
|
||||
byte[] titleId = data.ReadBytes(9);
|
||||
ird.TitleID = Encoding.ASCII.GetString(titleId);
|
||||
|
||||
ird.TitleLength = data.ReadByteValue();
|
||||
byte[] title = data.ReadBytes(ird.TitleLength);
|
||||
ird.Title = Encoding.ASCII.GetString(title);
|
||||
|
||||
byte[] systemVersion = data.ReadBytes(4);
|
||||
ird.SystemVersion = Encoding.ASCII.GetString(systemVersion);
|
||||
|
||||
byte[] gameVersion = data.ReadBytes(5);
|
||||
ird.GameVersion = Encoding.ASCII.GetString(gameVersion);
|
||||
|
||||
byte[] appVersion = data.ReadBytes(5);
|
||||
ird.AppVersion = Encoding.ASCII.GetString(appVersion);
|
||||
|
||||
if (ird.Version == 7)
|
||||
ird.UID = data.ReadUInt32();
|
||||
|
||||
ird.HeaderLength = data.ReadByteValue();
|
||||
ird.Header = data.ReadBytes((int)ird.HeaderLength);
|
||||
ird.FooterLength = data.ReadByteValue();
|
||||
ird.Footer = data.ReadBytes((int)ird.FooterLength);
|
||||
|
||||
ird.RegionCount = data.ReadByteValue();
|
||||
ird.RegionHashes = new byte[ird.RegionCount][];
|
||||
for (int i = 0; i < ird.RegionCount; i++)
|
||||
try
|
||||
{
|
||||
ird.RegionHashes[i] = data.ReadBytes(16) ?? [];
|
||||
}
|
||||
// Create a new IRD to fill
|
||||
var ird = new Models.IRD.File();
|
||||
|
||||
ird.FileCount = data.ReadByteValue();
|
||||
ird.FileKeys = new ulong[ird.FileCount];
|
||||
ird.FileHashes = new byte[ird.FileCount][];
|
||||
for (int i = 0; i < ird.FileCount; i++)
|
||||
ird.Magic = data.ReadBytes(4);
|
||||
string magic = Encoding.ASCII.GetString(ird.Magic);
|
||||
if (magic != "3IRD")
|
||||
return null;
|
||||
|
||||
ird.Version = data.ReadByteValue();
|
||||
if (ird.Version < 6)
|
||||
return null;
|
||||
|
||||
byte[] titleId = data.ReadBytes(9);
|
||||
ird.TitleID = Encoding.ASCII.GetString(titleId);
|
||||
|
||||
ird.TitleLength = data.ReadByteValue();
|
||||
byte[] title = data.ReadBytes(ird.TitleLength);
|
||||
ird.Title = Encoding.ASCII.GetString(title);
|
||||
|
||||
byte[] systemVersion = data.ReadBytes(4);
|
||||
ird.SystemVersion = Encoding.ASCII.GetString(systemVersion);
|
||||
|
||||
byte[] gameVersion = data.ReadBytes(5);
|
||||
ird.GameVersion = Encoding.ASCII.GetString(gameVersion);
|
||||
|
||||
byte[] appVersion = data.ReadBytes(5);
|
||||
ird.AppVersion = Encoding.ASCII.GetString(appVersion);
|
||||
|
||||
if (ird.Version == 7)
|
||||
ird.UID = data.ReadUInt32();
|
||||
|
||||
ird.HeaderLength = data.ReadByteValue();
|
||||
ird.Header = data.ReadBytes((int)ird.HeaderLength);
|
||||
ird.FooterLength = data.ReadByteValue();
|
||||
ird.Footer = data.ReadBytes((int)ird.FooterLength);
|
||||
|
||||
ird.RegionCount = data.ReadByteValue();
|
||||
ird.RegionHashes = new byte[ird.RegionCount][];
|
||||
for (int i = 0; i < ird.RegionCount; i++)
|
||||
{
|
||||
ird.RegionHashes[i] = data.ReadBytes(16) ?? [];
|
||||
}
|
||||
|
||||
ird.FileCount = data.ReadByteValue();
|
||||
ird.FileKeys = new ulong[ird.FileCount];
|
||||
ird.FileHashes = new byte[ird.FileCount][];
|
||||
for (int i = 0; i < ird.FileCount; i++)
|
||||
{
|
||||
ird.FileKeys[i] = data.ReadUInt64();
|
||||
ird.FileHashes[i] = data.ReadBytes(16) ?? [];
|
||||
}
|
||||
|
||||
ird.ExtraConfig = data.ReadUInt16();
|
||||
ird.Attachments = data.ReadUInt16();
|
||||
|
||||
if (ird.Version >= 9)
|
||||
ird.PIC = data.ReadBytes(115);
|
||||
|
||||
ird.Data1Key = data.ReadBytes(16);
|
||||
ird.Data2Key = data.ReadBytes(16);
|
||||
|
||||
if (ird.Version < 9)
|
||||
ird.PIC = data.ReadBytes(115);
|
||||
|
||||
if (ird.Version > 7)
|
||||
ird.UID = data.ReadUInt32();
|
||||
|
||||
ird.CRC = data.ReadUInt32();
|
||||
|
||||
return ird;
|
||||
}
|
||||
catch
|
||||
{
|
||||
ird.FileKeys[i] = data.ReadUInt64();
|
||||
ird.FileHashes[i] = data.ReadBytes(16) ?? [];
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
ird.ExtraConfig = data.ReadUInt16();
|
||||
ird.Attachments = data.ReadUInt16();
|
||||
|
||||
if (ird.Version >= 9)
|
||||
ird.PIC = data.ReadBytes(115);
|
||||
|
||||
ird.Data1Key = data.ReadBytes(16);
|
||||
ird.Data2Key = data.ReadBytes(16);
|
||||
|
||||
if (ird.Version < 9)
|
||||
ird.PIC = data.ReadBytes(115);
|
||||
|
||||
if (ird.Version > 7)
|
||||
ird.UID = data.ReadUInt32();
|
||||
|
||||
ird.CRC = data.ReadUInt32();
|
||||
|
||||
return ird;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,78 +14,82 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new archive to fill
|
||||
var archive = new Archive();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Signature1 != Constants.HeaderSignature)
|
||||
return null;
|
||||
if (header.TocAddress >= data.Length)
|
||||
return null;
|
||||
|
||||
// Set the archive header
|
||||
archive.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directories
|
||||
|
||||
// Get the directories offset
|
||||
uint directoriesOffset = header.TocAddress;
|
||||
if (directoriesOffset < 0 || directoriesOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the directories
|
||||
data.Seek(directoriesOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the directories
|
||||
var directories = new List<Models.InstallShieldArchiveV3.Directory>();
|
||||
for (int i = 0; i < header.DirCount; i++)
|
||||
try
|
||||
{
|
||||
var directory = ParseDirectory(data);
|
||||
if (directory?.Name == null)
|
||||
// Create a new archive to fill
|
||||
var archive = new Archive();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Signature1 != Constants.HeaderSignature)
|
||||
return null;
|
||||
if (header.TocAddress >= data.Length)
|
||||
return null;
|
||||
|
||||
directories.Add(directory);
|
||||
data.Seek(directory.ChunkSize - directory.Name.Length - 6, SeekOrigin.Current);
|
||||
}
|
||||
// Set the archive header
|
||||
archive.Header = header;
|
||||
|
||||
// Set the directories
|
||||
archive.Directories = [.. directories];
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
#region Directories
|
||||
|
||||
#region Files
|
||||
// Get the directories offset
|
||||
uint directoriesOffset = header.TocAddress;
|
||||
if (directoriesOffset < 0 || directoriesOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Try to parse the files
|
||||
var files = new List<Models.InstallShieldArchiveV3.File>();
|
||||
for (int i = 0; i < archive.Directories.Length; i++)
|
||||
{
|
||||
var directory = archive.Directories[i];
|
||||
for (int j = 0; j < directory.FileCount; j++)
|
||||
// Seek to the directories
|
||||
data.Seek(directoriesOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the directories
|
||||
var directories = new List<Models.InstallShieldArchiveV3.Directory>();
|
||||
for (int i = 0; i < header.DirCount; i++)
|
||||
{
|
||||
var file = data.ReadType<Models.InstallShieldArchiveV3.File>();
|
||||
if (file?.Name == null)
|
||||
var directory = ParseDirectory(data);
|
||||
if (directory?.Name == null)
|
||||
return null;
|
||||
|
||||
files.Add(file);
|
||||
data.Seek(file.ChunkSize - file.Name.Length - 30, SeekOrigin.Current);
|
||||
directories.Add(directory);
|
||||
data.Seek(directory.ChunkSize - directory.Name.Length - 6, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
// Set the directories
|
||||
archive.Directories = [.. directories];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Files
|
||||
|
||||
// Try to parse the files
|
||||
var files = new List<Models.InstallShieldArchiveV3.File>();
|
||||
for (int i = 0; i < archive.Directories.Length; i++)
|
||||
{
|
||||
var directory = archive.Directories[i];
|
||||
for (int j = 0; j < directory.FileCount; j++)
|
||||
{
|
||||
var file = data.ReadType<Models.InstallShieldArchiveV3.File>();
|
||||
if (file?.Name == null)
|
||||
return null;
|
||||
|
||||
files.Add(file);
|
||||
data.Seek(file.ChunkSize - file.Name.Length - 30, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the files
|
||||
archive.Files = [.. files];
|
||||
|
||||
#endregion
|
||||
|
||||
return archive;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set the files
|
||||
archive.Files = [.. files];
|
||||
|
||||
#endregion
|
||||
|
||||
return archive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -17,312 +17,316 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new cabinet to fill
|
||||
var cabinet = new Cabinet();
|
||||
|
||||
#region Common Header
|
||||
|
||||
// Try to parse the cabinet header
|
||||
var commonHeader = data.ReadType<CommonHeader>();
|
||||
if (commonHeader?.Signature != SignatureString)
|
||||
return null;
|
||||
|
||||
// Set the cabinet header
|
||||
cabinet.CommonHeader = commonHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Volume Header
|
||||
|
||||
// Try to parse the volume header
|
||||
var volumeHeader = ParseVolumeHeader(data, GetMajorVersion(commonHeader));
|
||||
if (volumeHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the volume header
|
||||
cabinet.VolumeHeader = volumeHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Descriptor
|
||||
|
||||
// Get the descriptor offset
|
||||
uint descriptorOffset = commonHeader.DescriptorOffset;
|
||||
if (descriptorOffset < 0 || descriptorOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the descriptor
|
||||
data.Seek(descriptorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the descriptor
|
||||
var descriptor = data.ReadType<Descriptor>();
|
||||
if (descriptor == null)
|
||||
return null;
|
||||
|
||||
// Set the descriptor
|
||||
cabinet.Descriptor = descriptor;
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Descriptor Offsets
|
||||
|
||||
// Get the file table offset
|
||||
uint fileTableOffset = commonHeader.DescriptorOffset + descriptor.FileTableOffset;
|
||||
if (fileTableOffset < 0 || fileTableOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the file table
|
||||
data.Seek(fileTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Get the number of file table items
|
||||
uint fileTableItems;
|
||||
if (GetMajorVersion(commonHeader) <= 5)
|
||||
fileTableItems = descriptor.DirectoryCount + descriptor.FileCount;
|
||||
else
|
||||
fileTableItems = descriptor.DirectoryCount;
|
||||
|
||||
// Create and fill the file table
|
||||
cabinet.FileDescriptorOffsets = new uint[fileTableItems];
|
||||
for (int i = 0; i < cabinet.FileDescriptorOffsets.Length; i++)
|
||||
try
|
||||
{
|
||||
cabinet.FileDescriptorOffsets[i] = data.ReadUInt32();
|
||||
}
|
||||
// Create a new cabinet to fill
|
||||
var cabinet = new Cabinet();
|
||||
|
||||
#endregion
|
||||
#region Common Header
|
||||
|
||||
#region Directory Descriptors
|
||||
// Try to parse the cabinet header
|
||||
var commonHeader = data.ReadType<CommonHeader>();
|
||||
if (commonHeader?.Signature != SignatureString)
|
||||
return null;
|
||||
|
||||
// Create and fill the directory descriptors
|
||||
cabinet.DirectoryNames = new string[descriptor.DirectoryCount];
|
||||
for (int i = 0; i < descriptor.DirectoryCount; i++)
|
||||
{
|
||||
// Get the directory descriptor offset
|
||||
uint offset = descriptorOffset
|
||||
+ descriptor.FileTableOffset
|
||||
+ cabinet.FileDescriptorOffsets[i];
|
||||
// Set the cabinet header
|
||||
cabinet.CommonHeader = commonHeader;
|
||||
|
||||
// If we have an invalid offset
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
continue;
|
||||
#endregion
|
||||
|
||||
// Seek to the file descriptor offset
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
#region Volume Header
|
||||
|
||||
// Create and add the file descriptor
|
||||
string? directoryName = ParseDirectoryName(data, GetMajorVersion(commonHeader));
|
||||
if (directoryName != null)
|
||||
cabinet.DirectoryNames[i] = directoryName;
|
||||
}
|
||||
// Try to parse the volume header
|
||||
var volumeHeader = ParseVolumeHeader(data, GetMajorVersion(commonHeader));
|
||||
if (volumeHeader == null)
|
||||
return null;
|
||||
|
||||
#endregion
|
||||
// Set the volume header
|
||||
cabinet.VolumeHeader = volumeHeader;
|
||||
|
||||
#region File Descriptors
|
||||
#endregion
|
||||
|
||||
// Create and fill the file descriptors
|
||||
cabinet.FileDescriptors = new FileDescriptor[descriptor.FileCount];
|
||||
for (int i = 0; i < descriptor.FileCount; i++)
|
||||
{
|
||||
// Get the file descriptor offset
|
||||
uint offset;
|
||||
#region Descriptor
|
||||
|
||||
// Get the descriptor offset
|
||||
uint descriptorOffset = commonHeader.DescriptorOffset;
|
||||
if (descriptorOffset < 0 || descriptorOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the descriptor
|
||||
data.Seek(descriptorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the descriptor
|
||||
var descriptor = data.ReadType<Descriptor>();
|
||||
if (descriptor == null)
|
||||
return null;
|
||||
|
||||
// Set the descriptor
|
||||
cabinet.Descriptor = descriptor;
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Descriptor Offsets
|
||||
|
||||
// Get the file table offset
|
||||
uint fileTableOffset = commonHeader.DescriptorOffset + descriptor.FileTableOffset;
|
||||
if (fileTableOffset < 0 || fileTableOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the file table
|
||||
data.Seek(fileTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Get the number of file table items
|
||||
uint fileTableItems;
|
||||
if (GetMajorVersion(commonHeader) <= 5)
|
||||
{
|
||||
offset = descriptorOffset
|
||||
+ descriptor.FileTableOffset
|
||||
+ cabinet.FileDescriptorOffsets[descriptor.DirectoryCount + i];
|
||||
}
|
||||
fileTableItems = descriptor.DirectoryCount + descriptor.FileCount;
|
||||
else
|
||||
fileTableItems = descriptor.DirectoryCount;
|
||||
|
||||
// Create and fill the file table
|
||||
cabinet.FileDescriptorOffsets = new uint[fileTableItems];
|
||||
for (int i = 0; i < cabinet.FileDescriptorOffsets.Length; i++)
|
||||
{
|
||||
offset = descriptorOffset
|
||||
cabinet.FileDescriptorOffsets[i] = data.ReadUInt32();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Descriptors
|
||||
|
||||
// Create and fill the directory descriptors
|
||||
cabinet.DirectoryNames = new string[descriptor.DirectoryCount];
|
||||
for (int i = 0; i < descriptor.DirectoryCount; i++)
|
||||
{
|
||||
// Get the directory descriptor offset
|
||||
uint offset = descriptorOffset
|
||||
+ descriptor.FileTableOffset
|
||||
+ descriptor.FileTableOffset2
|
||||
+ (uint)(i * 0x57);
|
||||
+ cabinet.FileDescriptorOffsets[i];
|
||||
|
||||
// If we have an invalid offset
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
continue;
|
||||
|
||||
// Seek to the file descriptor offset
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create and add the file descriptor
|
||||
string? directoryName = ParseDirectoryName(data, GetMajorVersion(commonHeader));
|
||||
if (directoryName != null)
|
||||
cabinet.DirectoryNames[i] = directoryName;
|
||||
}
|
||||
|
||||
// If we have an invalid offset
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
continue;
|
||||
#endregion
|
||||
|
||||
// Seek to the file descriptor offset
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
#region File Descriptors
|
||||
|
||||
// Create and add the file descriptor
|
||||
FileDescriptor fileDescriptor = ParseFileDescriptor(data, GetMajorVersion(commonHeader), descriptorOffset + descriptor.FileTableOffset);
|
||||
cabinet.FileDescriptors[i] = fileDescriptor;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Group Offsets
|
||||
|
||||
// Create and fill the file group offsets
|
||||
cabinet.FileGroupOffsets = new Dictionary<long, OffsetList?>();
|
||||
for (int i = 0; i < (descriptor.FileGroupOffsets?.Length ?? 0); i++)
|
||||
{
|
||||
// Get the file group offset
|
||||
uint offset = descriptor.FileGroupOffsets![i];
|
||||
if (offset == 0)
|
||||
continue;
|
||||
|
||||
// Adjust the file group offset
|
||||
offset += commonHeader.DescriptorOffset;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
continue;
|
||||
|
||||
// Seek to the file group offset
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create and add the offset
|
||||
OffsetList offsetList = ParseOffsetList(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
cabinet.FileGroupOffsets[descriptor.FileGroupOffsets[i]] = offsetList;
|
||||
|
||||
// If we have a nonzero next offset
|
||||
uint nextOffset = offsetList.NextOffset;
|
||||
while (nextOffset != 0)
|
||||
// Create and fill the file descriptors
|
||||
cabinet.FileDescriptors = new FileDescriptor[descriptor.FileCount];
|
||||
for (int i = 0; i < descriptor.FileCount; i++)
|
||||
{
|
||||
// Get the next offset to read
|
||||
uint internalOffset = nextOffset + commonHeader.DescriptorOffset;
|
||||
// Get the file descriptor offset
|
||||
uint offset;
|
||||
if (GetMajorVersion(commonHeader) <= 5)
|
||||
{
|
||||
offset = descriptorOffset
|
||||
+ descriptor.FileTableOffset
|
||||
+ cabinet.FileDescriptorOffsets[descriptor.DirectoryCount + i];
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = descriptorOffset
|
||||
+ descriptor.FileTableOffset
|
||||
+ descriptor.FileTableOffset2
|
||||
+ (uint)(i * 0x57);
|
||||
}
|
||||
|
||||
// If we have an invalid offset
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
continue;
|
||||
|
||||
// Seek to the file descriptor offset
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create and add the file descriptor
|
||||
FileDescriptor fileDescriptor = ParseFileDescriptor(data, GetMajorVersion(commonHeader), descriptorOffset + descriptor.FileTableOffset);
|
||||
cabinet.FileDescriptors[i] = fileDescriptor;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Group Offsets
|
||||
|
||||
// Create and fill the file group offsets
|
||||
cabinet.FileGroupOffsets = new Dictionary<long, OffsetList?>();
|
||||
for (int i = 0; i < (descriptor.FileGroupOffsets?.Length ?? 0); i++)
|
||||
{
|
||||
// Get the file group offset
|
||||
uint offset = descriptor.FileGroupOffsets![i];
|
||||
if (offset == 0)
|
||||
continue;
|
||||
|
||||
// Adjust the file group offset
|
||||
offset += commonHeader.DescriptorOffset;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
continue;
|
||||
|
||||
// Seek to the file group offset
|
||||
data.Seek(internalOffset, SeekOrigin.Begin);
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create and add the offset
|
||||
offsetList = ParseOffsetList(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
cabinet.FileGroupOffsets[nextOffset] = offsetList;
|
||||
OffsetList offsetList = ParseOffsetList(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
cabinet.FileGroupOffsets[descriptor.FileGroupOffsets[i]] = offsetList;
|
||||
|
||||
// Set the next offset
|
||||
nextOffset = offsetList.NextOffset;
|
||||
}
|
||||
}
|
||||
// If we have a nonzero next offset
|
||||
uint nextOffset = offsetList.NextOffset;
|
||||
while (nextOffset != 0)
|
||||
{
|
||||
// Get the next offset to read
|
||||
uint internalOffset = nextOffset + commonHeader.DescriptorOffset;
|
||||
|
||||
#endregion
|
||||
// Seek to the file group offset
|
||||
data.Seek(internalOffset, SeekOrigin.Begin);
|
||||
|
||||
#region File Groups
|
||||
// Create and add the offset
|
||||
offsetList = ParseOffsetList(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
cabinet.FileGroupOffsets[nextOffset] = offsetList;
|
||||
|
||||
// Create the file groups array
|
||||
cabinet.FileGroups = new FileGroup[cabinet.FileGroupOffsets.Count];
|
||||
|
||||
// Create and fill the file groups
|
||||
int fileGroupId = 0;
|
||||
foreach (var kvp in cabinet.FileGroupOffsets)
|
||||
{
|
||||
// Get the offset
|
||||
OffsetList? list = kvp.Value;
|
||||
if (list == null)
|
||||
{
|
||||
fileGroupId++;
|
||||
continue;
|
||||
// Set the next offset
|
||||
nextOffset = offsetList.NextOffset;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have an invalid offset
|
||||
if (list.DescriptorOffset <= 0)
|
||||
#endregion
|
||||
|
||||
#region File Groups
|
||||
|
||||
// Create the file groups array
|
||||
cabinet.FileGroups = new FileGroup[cabinet.FileGroupOffsets.Count];
|
||||
|
||||
// Create and fill the file groups
|
||||
int fileGroupId = 0;
|
||||
foreach (var kvp in cabinet.FileGroupOffsets)
|
||||
{
|
||||
fileGroupId++;
|
||||
continue;
|
||||
// Get the offset
|
||||
OffsetList? list = kvp.Value;
|
||||
if (list == null)
|
||||
{
|
||||
fileGroupId++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we have an invalid offset
|
||||
if (list.DescriptorOffset <= 0)
|
||||
{
|
||||
fileGroupId++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/// Seek to the file group
|
||||
data.Seek(list.DescriptorOffset + descriptorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the file group
|
||||
var fileGroup = ParseFileGroup(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
if (fileGroup == null)
|
||||
return null;
|
||||
|
||||
// Add the file group
|
||||
cabinet.FileGroups[fileGroupId++] = fileGroup;
|
||||
}
|
||||
|
||||
/// Seek to the file group
|
||||
data.Seek(list.DescriptorOffset + descriptorOffset, SeekOrigin.Begin);
|
||||
#endregion
|
||||
|
||||
// Try to parse the file group
|
||||
var fileGroup = ParseFileGroup(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
if (fileGroup == null)
|
||||
return null;
|
||||
#region Component Offsets
|
||||
|
||||
// Add the file group
|
||||
cabinet.FileGroups[fileGroupId++] = fileGroup;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Component Offsets
|
||||
|
||||
// Create and fill the component offsets
|
||||
cabinet.ComponentOffsets = new Dictionary<long, OffsetList?>();
|
||||
for (int i = 0; i < (descriptor.ComponentOffsets?.Length ?? 0); i++)
|
||||
{
|
||||
// Get the component offset
|
||||
uint offset = descriptor.ComponentOffsets![i];
|
||||
if (offset == 0)
|
||||
continue;
|
||||
|
||||
// Adjust the component offset
|
||||
offset += commonHeader.DescriptorOffset;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
continue;
|
||||
|
||||
// Seek to the component offset
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create and add the offset
|
||||
OffsetList offsetList = ParseOffsetList(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
cabinet.ComponentOffsets[descriptor.ComponentOffsets[i]] = offsetList;
|
||||
|
||||
// If we have a nonzero next offset
|
||||
uint nextOffset = offsetList.NextOffset;
|
||||
while (nextOffset != 0)
|
||||
// Create and fill the component offsets
|
||||
cabinet.ComponentOffsets = new Dictionary<long, OffsetList?>();
|
||||
for (int i = 0; i < (descriptor.ComponentOffsets?.Length ?? 0); i++)
|
||||
{
|
||||
// Get the next offset to read
|
||||
uint internalOffset = nextOffset + commonHeader.DescriptorOffset;
|
||||
// Get the component offset
|
||||
uint offset = descriptor.ComponentOffsets![i];
|
||||
if (offset == 0)
|
||||
continue;
|
||||
|
||||
// Seek to the file group offset
|
||||
data.Seek(internalOffset, SeekOrigin.Begin);
|
||||
// Adjust the component offset
|
||||
offset += commonHeader.DescriptorOffset;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
continue;
|
||||
|
||||
// Seek to the component offset
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create and add the offset
|
||||
offsetList = ParseOffsetList(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
cabinet.ComponentOffsets[nextOffset] = offsetList;
|
||||
OffsetList offsetList = ParseOffsetList(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
cabinet.ComponentOffsets[descriptor.ComponentOffsets[i]] = offsetList;
|
||||
|
||||
// Set the next offset
|
||||
nextOffset = offsetList.NextOffset;
|
||||
// If we have a nonzero next offset
|
||||
uint nextOffset = offsetList.NextOffset;
|
||||
while (nextOffset != 0)
|
||||
{
|
||||
// Get the next offset to read
|
||||
uint internalOffset = nextOffset + commonHeader.DescriptorOffset;
|
||||
|
||||
// Seek to the file group offset
|
||||
data.Seek(internalOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create and add the offset
|
||||
offsetList = ParseOffsetList(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
cabinet.ComponentOffsets[nextOffset] = offsetList;
|
||||
|
||||
// Set the next offset
|
||||
nextOffset = offsetList.NextOffset;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Components
|
||||
|
||||
// Create the components array
|
||||
cabinet.Components = new Component[cabinet.ComponentOffsets.Count];
|
||||
|
||||
// Create and fill the components
|
||||
int componentId = 0;
|
||||
foreach (KeyValuePair<long, OffsetList?> kvp in cabinet.ComponentOffsets)
|
||||
{
|
||||
// Get the offset
|
||||
OffsetList? list = kvp.Value;
|
||||
if (list == null)
|
||||
{
|
||||
componentId++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we have an invalid offset
|
||||
if (list.DescriptorOffset <= 0)
|
||||
{
|
||||
componentId++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Seek to the component
|
||||
data.Seek(list.DescriptorOffset + descriptorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the component
|
||||
var component = ParseComponent(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
if (component == null)
|
||||
return null;
|
||||
|
||||
// Add the component
|
||||
cabinet.Components[componentId++] = component;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Parse setup types
|
||||
|
||||
return cabinet;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Components
|
||||
|
||||
// Create the components array
|
||||
cabinet.Components = new Component[cabinet.ComponentOffsets.Count];
|
||||
|
||||
// Create and fill the components
|
||||
int componentId = 0;
|
||||
foreach (KeyValuePair<long, OffsetList?> kvp in cabinet.ComponentOffsets)
|
||||
catch
|
||||
{
|
||||
// Get the offset
|
||||
OffsetList? list = kvp.Value;
|
||||
if (list == null)
|
||||
{
|
||||
componentId++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we have an invalid offset
|
||||
if (list.DescriptorOffset <= 0)
|
||||
{
|
||||
componentId++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Seek to the component
|
||||
data.Seek(list.DescriptorOffset + descriptorOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the component
|
||||
var component = ParseComponent(data, GetMajorVersion(commonHeader), descriptorOffset);
|
||||
if (component == null)
|
||||
return null;
|
||||
|
||||
// Add the component
|
||||
cabinet.Components[componentId++] = component;
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Parse setup types
|
||||
|
||||
return cabinet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace SabreTools.Serialization.Deserializers
|
||||
public T? Deserialize(byte[]? data, int offset, Encoding encoding)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null)
|
||||
if (data == null || data.Length == 0)
|
||||
return default;
|
||||
|
||||
// If the offset is out of bounds
|
||||
@@ -83,28 +83,17 @@ namespace SabreTools.Serialization.Deserializers
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
}
|
||||
// Setup the serializer and the reader
|
||||
var serializer = JsonSerializer.Create();
|
||||
var streamReader = new StreamReader(data, encoding);
|
||||
var jsonReader = new JsonTextReader(streamReader);
|
||||
|
||||
// Setup the serializer and the reader
|
||||
var serializer = JsonSerializer.Create();
|
||||
var streamReader = new StreamReader(data, encoding);
|
||||
var jsonReader = new JsonTextReader(streamReader);
|
||||
|
||||
// Perform the deserialization and return
|
||||
try
|
||||
{
|
||||
// Perform the deserialization and return
|
||||
return serializer.Deserialize<T>(jsonReader);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Absorb all exceptions
|
||||
// Ignore the actual error
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,406 +16,410 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !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 executable to fill
|
||||
var executable = new Executable();
|
||||
|
||||
#region MS-DOS Stub
|
||||
|
||||
// Parse the MS-DOS stub
|
||||
var stub = new MSDOS().Deserialize(data);
|
||||
if (stub?.Header == null || stub.Header.NewExeHeaderAddr == 0)
|
||||
return null;
|
||||
|
||||
// Set the MS-DOS stub
|
||||
executable.Stub = stub;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Information Block
|
||||
|
||||
// Try to parse the executable header
|
||||
data.Seek(initialOffset + stub.Header.NewExeHeaderAddr, SeekOrigin.Begin);
|
||||
var informationBlock = data.ReadType<InformationBlock>();
|
||||
if (informationBlock?.Signature != LESignatureString && informationBlock?.Signature != LXSignatureString)
|
||||
return null;
|
||||
|
||||
// Set the executable header
|
||||
executable.InformationBlock = informationBlock;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Table
|
||||
|
||||
// Get the object table offset
|
||||
long offset = informationBlock.ObjectTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
try
|
||||
{
|
||||
// Seek to the object table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Create the object table
|
||||
executable.ObjectTable = new ObjectTableEntry[informationBlock.ObjectTableCount];
|
||||
// Create a new executable to fill
|
||||
var executable = new Executable();
|
||||
|
||||
// Try to parse the object table
|
||||
for (int i = 0; i < executable.ObjectTable.Length; i++)
|
||||
{
|
||||
var entry = data.ReadType<ObjectTableEntry>();
|
||||
if (entry == null)
|
||||
return null;
|
||||
#region MS-DOS Stub
|
||||
|
||||
executable.ObjectTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Page Map
|
||||
|
||||
// Get the object page map offset
|
||||
offset = informationBlock.ObjectPageMapOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the object page map
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the object page map
|
||||
executable.ObjectPageMap = new ObjectPageMapEntry[informationBlock.ObjectTableCount];
|
||||
|
||||
// Try to parse the object page map
|
||||
for (int i = 0; i < executable.ObjectPageMap.Length; i++)
|
||||
{
|
||||
var entry = data.ReadType<ObjectPageMapEntry>();
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.ObjectPageMap[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Iterate Data Map
|
||||
|
||||
offset = informationBlock.ObjectIterateDataMapOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the object page map
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// TODO: Implement when model found
|
||||
// No model has been found in the documentation about what
|
||||
// each of the entries looks like for this map.
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resource Table
|
||||
|
||||
// Get the resource table offset
|
||||
offset = informationBlock.ResourceTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the resource table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the resource table
|
||||
executable.ResourceTable = new ResourceTableEntry[informationBlock.ResourceTableCount];
|
||||
|
||||
// Try to parse the resource table
|
||||
for (int i = 0; i < executable.ResourceTable.Length; i++)
|
||||
{
|
||||
var entry = data.ReadType<ResourceTableEntry>();
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.ResourceTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resident Names Table
|
||||
|
||||
// Get the resident names table offset
|
||||
offset = informationBlock.ResidentNamesTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the resident names table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the resident names table
|
||||
var residentNamesTable = new List<ResidentNamesTableEntry>();
|
||||
|
||||
// Try to parse the resident names table
|
||||
while (true)
|
||||
{
|
||||
var entry = ParseResidentNamesTableEntry(data);
|
||||
residentNamesTable.Add(entry);
|
||||
|
||||
// If we have a 0-length entry
|
||||
if (entry.Length == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Assign the resident names table
|
||||
executable.ResidentNamesTable = [.. residentNamesTable];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Entry Table
|
||||
|
||||
// Get the entry table offset
|
||||
offset = informationBlock.EntryTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the entry table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the entry table
|
||||
var entryTable = new List<EntryTableBundle>();
|
||||
|
||||
// Try to parse the entry table
|
||||
while (true)
|
||||
{
|
||||
var bundle = ParseEntryTableBundle(data);
|
||||
if (bundle != null)
|
||||
entryTable.Add(bundle);
|
||||
|
||||
// If we have a 0-length entry
|
||||
if (bundle == null || bundle.Entries == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Assign the entry table
|
||||
executable.EntryTable = [.. entryTable];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Module Format Directives Table
|
||||
|
||||
// Get the module format directives table offset
|
||||
offset = informationBlock.ModuleDirectivesTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the module format directives table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the module format directives table
|
||||
executable.ModuleFormatDirectivesTable = new ModuleFormatDirectivesTableEntry[informationBlock.ModuleDirectivesCount];
|
||||
|
||||
// Try to parse the module format directives table
|
||||
for (int i = 0; i < executable.ModuleFormatDirectivesTable.Length; i++)
|
||||
{
|
||||
var entry = data.ReadType<ModuleFormatDirectivesTableEntry>();
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.ModuleFormatDirectivesTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Verify Record Directive Table
|
||||
|
||||
// TODO: Figure out where the offset to this table is stored
|
||||
// The documentation suggests it's either part of or immediately following
|
||||
// the Module Format Directives Table
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fix-up Page Table
|
||||
|
||||
// Get the fix-up page table offset
|
||||
offset = informationBlock.FixupPageTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the fix-up page table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the fix-up page table
|
||||
executable.FixupPageTable = new FixupPageTableEntry[executable.ObjectPageMap?.Length ?? 0 + 1];
|
||||
|
||||
// Try to parse the fix-up page table
|
||||
for (int i = 0; i < executable.FixupPageTable.Length; i++)
|
||||
{
|
||||
var entry = data.ReadType<FixupPageTableEntry>();
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.FixupPageTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fix-up Record Table
|
||||
|
||||
// Get the fix-up record table offset
|
||||
offset = informationBlock.FixupRecordTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the fix-up record table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the fix-up record table
|
||||
executable.FixupRecordTable = new FixupRecordTableEntry[executable.ObjectPageMap?.Length ?? 0 + 1];
|
||||
|
||||
// Try to parse the fix-up record table
|
||||
for (int i = 0; i < executable.FixupRecordTable.Length; i++)
|
||||
{
|
||||
var entry = ParseFixupRecordTableEntry(data);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.FixupRecordTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Imported Module Name Table
|
||||
|
||||
// Get the imported module name table offset
|
||||
offset = informationBlock.ImportedModulesNameTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the imported module name table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the imported module name table
|
||||
executable.ImportModuleNameTable = new ImportModuleNameTableEntry[informationBlock.ImportedModulesCount];
|
||||
|
||||
// Try to parse the imported module name table
|
||||
for (int i = 0; i < executable.ImportModuleNameTable.Length; i++)
|
||||
{
|
||||
var entry = ParseImportModuleNameTableEntry(data);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.ImportModuleNameTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Imported Module Procedure Name Table
|
||||
|
||||
// Get the imported module procedure name table offset
|
||||
offset = informationBlock.ImportProcedureNameTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the imported module procedure name table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Get the size of the imported module procedure name table
|
||||
long tableSize = informationBlock.FixupPageTableOffset
|
||||
+ informationBlock.FixupSectionSize
|
||||
- informationBlock.ImportProcedureNameTableOffset;
|
||||
|
||||
// Create the imported module procedure name table
|
||||
var importModuleProcedureNameTable = new List<ImportModuleProcedureNameTableEntry>();
|
||||
|
||||
// Try to parse the imported module procedure name table
|
||||
while (data.Position < offset + tableSize)
|
||||
{
|
||||
var entry = ParseImportModuleProcedureNameTableEntry(data);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
importModuleProcedureNameTable.Add(entry);
|
||||
}
|
||||
|
||||
// Assign the resident names table
|
||||
executable.ImportModuleProcedureNameTable = [.. importModuleProcedureNameTable];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Per-Page Checksum Table
|
||||
|
||||
// Get the per-page checksum table offset
|
||||
offset = informationBlock.PerPageChecksumTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the per-page checksum name table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the per-page checksum name table
|
||||
executable.PerPageChecksumTable = new PerPageChecksumTableEntry[informationBlock.ModuleNumberPages];
|
||||
|
||||
// Try to parse the per-page checksum name table
|
||||
for (int i = 0; i < executable.PerPageChecksumTable.Length; i++)
|
||||
{
|
||||
var entry = data.ReadType<PerPageChecksumTableEntry>();
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.PerPageChecksumTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Non-Resident Names Table
|
||||
|
||||
// Get the non-resident names table offset
|
||||
offset = informationBlock.NonResidentNamesTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the non-resident names table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the non-resident names table
|
||||
var nonResidentNamesTable = new List<NonResidentNamesTableEntry>();
|
||||
|
||||
// Try to parse the non-resident names table
|
||||
while (true)
|
||||
{
|
||||
var entry = ParseNonResidentNameTableEntry(data);
|
||||
nonResidentNamesTable.Add(entry);
|
||||
|
||||
// If we have a 0-length entry
|
||||
if (entry.Length == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Assign the non-resident names table
|
||||
executable.NonResidentNamesTable = [.. nonResidentNamesTable];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Debug Information
|
||||
|
||||
// Get the debug information offset
|
||||
offset = informationBlock.DebugInformationOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the debug information
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the debug information
|
||||
var debugInformation = ParseDebugInformation(data, informationBlock.DebugInformationLength);
|
||||
if (debugInformation == null)
|
||||
// Parse the MS-DOS stub
|
||||
var stub = new MSDOS().Deserialize(data);
|
||||
if (stub?.Header == null || stub.Header.NewExeHeaderAddr == 0)
|
||||
return null;
|
||||
|
||||
// Set the debug information
|
||||
executable.DebugInformation = debugInformation;
|
||||
// Set the MS-DOS stub
|
||||
executable.Stub = stub;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Information Block
|
||||
|
||||
// Try to parse the executable header
|
||||
data.Seek(initialOffset + stub.Header.NewExeHeaderAddr, SeekOrigin.Begin);
|
||||
var informationBlock = data.ReadType<InformationBlock>();
|
||||
if (informationBlock?.Signature != LESignatureString && informationBlock?.Signature != LXSignatureString)
|
||||
return null;
|
||||
|
||||
// Set the executable header
|
||||
executable.InformationBlock = informationBlock;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Table
|
||||
|
||||
// Get the object table offset
|
||||
long offset = informationBlock.ObjectTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the object table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the object table
|
||||
executable.ObjectTable = new ObjectTableEntry[informationBlock.ObjectTableCount];
|
||||
|
||||
// Try to parse the object table
|
||||
for (int i = 0; i < executable.ObjectTable.Length; i++)
|
||||
{
|
||||
var entry = data.ReadType<ObjectTableEntry>();
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.ObjectTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Page Map
|
||||
|
||||
// Get the object page map offset
|
||||
offset = informationBlock.ObjectPageMapOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the object page map
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the object page map
|
||||
executable.ObjectPageMap = new ObjectPageMapEntry[informationBlock.ObjectTableCount];
|
||||
|
||||
// Try to parse the object page map
|
||||
for (int i = 0; i < executable.ObjectPageMap.Length; i++)
|
||||
{
|
||||
var entry = data.ReadType<ObjectPageMapEntry>();
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.ObjectPageMap[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Iterate Data Map
|
||||
|
||||
offset = informationBlock.ObjectIterateDataMapOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the object page map
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// TODO: Implement when model found
|
||||
// No model has been found in the documentation about what
|
||||
// each of the entries looks like for this map.
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resource Table
|
||||
|
||||
// Get the resource table offset
|
||||
offset = informationBlock.ResourceTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the resource table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the resource table
|
||||
executable.ResourceTable = new ResourceTableEntry[informationBlock.ResourceTableCount];
|
||||
|
||||
// Try to parse the resource table
|
||||
for (int i = 0; i < executable.ResourceTable.Length; i++)
|
||||
{
|
||||
var entry = data.ReadType<ResourceTableEntry>();
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.ResourceTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resident Names Table
|
||||
|
||||
// Get the resident names table offset
|
||||
offset = informationBlock.ResidentNamesTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the resident names table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the resident names table
|
||||
var residentNamesTable = new List<ResidentNamesTableEntry>();
|
||||
|
||||
// Try to parse the resident names table
|
||||
while (true)
|
||||
{
|
||||
var entry = ParseResidentNamesTableEntry(data);
|
||||
residentNamesTable.Add(entry);
|
||||
|
||||
// If we have a 0-length entry
|
||||
if (entry.Length == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Assign the resident names table
|
||||
executable.ResidentNamesTable = [.. residentNamesTable];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Entry Table
|
||||
|
||||
// Get the entry table offset
|
||||
offset = informationBlock.EntryTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the entry table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the entry table
|
||||
var entryTable = new List<EntryTableBundle>();
|
||||
|
||||
// Try to parse the entry table
|
||||
while (true)
|
||||
{
|
||||
var bundle = ParseEntryTableBundle(data);
|
||||
if (bundle != null)
|
||||
entryTable.Add(bundle);
|
||||
|
||||
// If we have a 0-length entry
|
||||
if (bundle == null || bundle.Entries == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Assign the entry table
|
||||
executable.EntryTable = [.. entryTable];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Module Format Directives Table
|
||||
|
||||
// Get the module format directives table offset
|
||||
offset = informationBlock.ModuleDirectivesTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the module format directives table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the module format directives table
|
||||
executable.ModuleFormatDirectivesTable = new ModuleFormatDirectivesTableEntry[informationBlock.ModuleDirectivesCount];
|
||||
|
||||
// Try to parse the module format directives table
|
||||
for (int i = 0; i < executable.ModuleFormatDirectivesTable.Length; i++)
|
||||
{
|
||||
var entry = data.ReadType<ModuleFormatDirectivesTableEntry>();
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.ModuleFormatDirectivesTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Verify Record Directive Table
|
||||
|
||||
// TODO: Figure out where the offset to this table is stored
|
||||
// The documentation suggests it's either part of or immediately following
|
||||
// the Module Format Directives Table
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fix-up Page Table
|
||||
|
||||
// Get the fix-up page table offset
|
||||
offset = informationBlock.FixupPageTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the fix-up page table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the fix-up page table
|
||||
executable.FixupPageTable = new FixupPageTableEntry[executable.ObjectPageMap?.Length ?? 0 + 1];
|
||||
|
||||
// Try to parse the fix-up page table
|
||||
for (int i = 0; i < executable.FixupPageTable.Length; i++)
|
||||
{
|
||||
var entry = data.ReadType<FixupPageTableEntry>();
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.FixupPageTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fix-up Record Table
|
||||
|
||||
// Get the fix-up record table offset
|
||||
offset = informationBlock.FixupRecordTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the fix-up record table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the fix-up record table
|
||||
executable.FixupRecordTable = new FixupRecordTableEntry[executable.ObjectPageMap?.Length ?? 0 + 1];
|
||||
|
||||
// Try to parse the fix-up record table
|
||||
for (int i = 0; i < executable.FixupRecordTable.Length; i++)
|
||||
{
|
||||
var entry = ParseFixupRecordTableEntry(data);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.FixupRecordTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Imported Module Name Table
|
||||
|
||||
// Get the imported module name table offset
|
||||
offset = informationBlock.ImportedModulesNameTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the imported module name table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the imported module name table
|
||||
executable.ImportModuleNameTable = new ImportModuleNameTableEntry[informationBlock.ImportedModulesCount];
|
||||
|
||||
// Try to parse the imported module name table
|
||||
for (int i = 0; i < executable.ImportModuleNameTable.Length; i++)
|
||||
{
|
||||
var entry = ParseImportModuleNameTableEntry(data);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.ImportModuleNameTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Imported Module Procedure Name Table
|
||||
|
||||
// Get the imported module procedure name table offset
|
||||
offset = informationBlock.ImportProcedureNameTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the imported module procedure name table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Get the size of the imported module procedure name table
|
||||
long tableSize = informationBlock.FixupPageTableOffset
|
||||
+ informationBlock.FixupSectionSize
|
||||
- informationBlock.ImportProcedureNameTableOffset;
|
||||
|
||||
// Create the imported module procedure name table
|
||||
var importModuleProcedureNameTable = new List<ImportModuleProcedureNameTableEntry>();
|
||||
|
||||
// Try to parse the imported module procedure name table
|
||||
while (data.Position < offset + tableSize)
|
||||
{
|
||||
var entry = ParseImportModuleProcedureNameTableEntry(data);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
importModuleProcedureNameTable.Add(entry);
|
||||
}
|
||||
|
||||
// Assign the resident names table
|
||||
executable.ImportModuleProcedureNameTable = [.. importModuleProcedureNameTable];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Per-Page Checksum Table
|
||||
|
||||
// Get the per-page checksum table offset
|
||||
offset = informationBlock.PerPageChecksumTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the per-page checksum name table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the per-page checksum name table
|
||||
executable.PerPageChecksumTable = new PerPageChecksumTableEntry[informationBlock.ModuleNumberPages];
|
||||
|
||||
// Try to parse the per-page checksum name table
|
||||
for (int i = 0; i < executable.PerPageChecksumTable.Length; i++)
|
||||
{
|
||||
var entry = data.ReadType<PerPageChecksumTableEntry>();
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
executable.PerPageChecksumTable[i] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Non-Resident Names Table
|
||||
|
||||
// Get the non-resident names table offset
|
||||
offset = informationBlock.NonResidentNamesTableOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the non-resident names table
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Create the non-resident names table
|
||||
var nonResidentNamesTable = new List<NonResidentNamesTableEntry>();
|
||||
|
||||
// Try to parse the non-resident names table
|
||||
while (true)
|
||||
{
|
||||
var entry = ParseNonResidentNameTableEntry(data);
|
||||
nonResidentNamesTable.Add(entry);
|
||||
|
||||
// If we have a 0-length entry
|
||||
if (entry.Length == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Assign the non-resident names table
|
||||
executable.NonResidentNamesTable = [.. nonResidentNamesTable];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Debug Information
|
||||
|
||||
// Get the debug information offset
|
||||
offset = informationBlock.DebugInformationOffset + stub.Header.NewExeHeaderAddr;
|
||||
if (offset > stub.Header.NewExeHeaderAddr && offset < data.Length)
|
||||
{
|
||||
// Seek to the debug information
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the debug information
|
||||
var debugInformation = ParseDebugInformation(data, informationBlock.DebugInformationLength);
|
||||
if (debugInformation == null)
|
||||
return null;
|
||||
|
||||
// Set the debug information
|
||||
executable.DebugInformation = debugInformation;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return executable;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return executable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -11,79 +11,70 @@ namespace SabreTools.Serialization.Deserializers
|
||||
/// <inheritdoc/>
|
||||
public override MetadataFile? Deserialize(Stream? data)
|
||||
{
|
||||
// If the stream is null
|
||||
if (data == null)
|
||||
// If the data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return default;
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
}
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data, Encoding.UTF8);
|
||||
var dat = new MetadataFile();
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new StreamReader(data, Encoding.UTF8);
|
||||
var dat = new MetadataFile();
|
||||
Set? set = null;
|
||||
var sets = new List<Set>();
|
||||
var rows = new List<Row>();
|
||||
|
||||
Set? set = null;
|
||||
var sets = new List<Set>();
|
||||
var rows = new List<Row>();
|
||||
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// Read the line and don't split yet
|
||||
string? line = reader.ReadLine();
|
||||
if (string.IsNullOrEmpty(line))
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// If we have a set to process
|
||||
if (set != null)
|
||||
// Read the line and don't split yet
|
||||
string? line = reader.ReadLine();
|
||||
if (string.IsNullOrEmpty(line))
|
||||
{
|
||||
set.Row = [.. rows];
|
||||
sets.Add(set);
|
||||
set = null;
|
||||
rows.Clear();
|
||||
// If we have a set to process
|
||||
if (set != null)
|
||||
{
|
||||
set.Row = [.. rows];
|
||||
sets.Add(set);
|
||||
set = null;
|
||||
rows.Clear();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
// Set lines are unique
|
||||
if (line.StartsWith("ROMs required for driver"))
|
||||
{
|
||||
string driver = line.Substring("ROMs required for driver".Length).Trim('"', ' ', '.');
|
||||
set = new Set { Driver = driver };
|
||||
continue;
|
||||
}
|
||||
else if (line.StartsWith("No ROMs required for driver"))
|
||||
{
|
||||
string driver = line.Substring("No ROMs required for driver".Length).Trim('"', ' ', '.');
|
||||
set = new Set { Driver = driver };
|
||||
continue;
|
||||
}
|
||||
else if (line.StartsWith("ROMs required for device"))
|
||||
{
|
||||
string device = line.Substring("ROMs required for device".Length).Trim('"', ' ', '.');
|
||||
set = new Set { Device = device };
|
||||
continue;
|
||||
}
|
||||
else if (line.StartsWith("No ROMs required for device"))
|
||||
{
|
||||
string device = line.Substring("No ROMs required for device".Length).Trim('"', ' ', '.');
|
||||
set = new Set { Device = device };
|
||||
continue;
|
||||
}
|
||||
else if (line.Equals("Name Size Checksum", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// No-op
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set lines are unique
|
||||
if (line.StartsWith("ROMs required for driver"))
|
||||
{
|
||||
string driver = line.Substring("ROMs required for driver".Length).Trim('"', ' ', '.');
|
||||
set = new Set { Driver = driver };
|
||||
continue;
|
||||
}
|
||||
else if (line.StartsWith("No ROMs required for driver"))
|
||||
{
|
||||
string driver = line.Substring("No ROMs required for driver".Length).Trim('"', ' ', '.');
|
||||
set = new Set { Driver = driver };
|
||||
continue;
|
||||
}
|
||||
else if (line.StartsWith("ROMs required for device"))
|
||||
{
|
||||
string device = line.Substring("ROMs required for device".Length).Trim('"', ' ', '.');
|
||||
set = new Set { Device = device };
|
||||
continue;
|
||||
}
|
||||
else if (line.StartsWith("No ROMs required for device"))
|
||||
{
|
||||
string device = line.Substring("No ROMs required for device".Length).Trim('"', ' ', '.');
|
||||
set = new Set { Device = device };
|
||||
continue;
|
||||
}
|
||||
else if (line.Equals("Name Size Checksum", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// No-op
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split the line for the name iteratively
|
||||
// Split the line for the name iteratively
|
||||
#if NETFRAMEWORK || NETCOREAPP3_1
|
||||
string[] lineParts = line.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lineParts.Length == 1)
|
||||
@@ -93,112 +84,118 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (lineParts.Length == 1)
|
||||
lineParts = line.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
|
||||
#else
|
||||
string[] lineParts = line.Split(" ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (lineParts.Length == 1)
|
||||
lineParts = line.Split(" ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (lineParts.Length == 1)
|
||||
lineParts = line.Split(" ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (lineParts.Length == 1)
|
||||
lineParts = line.Split(" ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
string[] lineParts = line.Split(" ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (lineParts.Length == 1)
|
||||
lineParts = line.Split(" ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (lineParts.Length == 1)
|
||||
lineParts = line.Split(" ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (lineParts.Length == 1)
|
||||
lineParts = line.Split(" ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
#endif
|
||||
|
||||
// Read the name and set the rest of the line for processing
|
||||
string name = lineParts[0];
|
||||
string trimmedLine = line.Substring(name.Length);
|
||||
if (trimmedLine == null)
|
||||
continue;
|
||||
// Read the name and set the rest of the line for processing
|
||||
string name = lineParts[0];
|
||||
string trimmedLine = line.Substring(name.Length);
|
||||
if (trimmedLine == null)
|
||||
continue;
|
||||
|
||||
#if NETFRAMEWORK || NETCOREAPP3_1
|
||||
lineParts = trimmedLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
#else
|
||||
lineParts = trimmedLine.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
lineParts = trimmedLine.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
#endif
|
||||
|
||||
// The number of items in the row explains what type of row it is
|
||||
var row = new Row();
|
||||
switch (lineParts.Length)
|
||||
{
|
||||
// Normal CHD (Name, MD5/SHA1)
|
||||
case 1:
|
||||
row.Name = name;
|
||||
if (line.Contains("MD5("))
|
||||
row.MD5 = lineParts[0].Substring("MD5".Length).Trim('(', ')');
|
||||
else
|
||||
row.SHA1 = lineParts[0].Substring("SHA1".Length).Trim('(', ')');
|
||||
break;
|
||||
// The number of items in the row explains what type of row it is
|
||||
var row = new Row();
|
||||
switch (lineParts.Length)
|
||||
{
|
||||
// Normal CHD (Name, MD5/SHA1)
|
||||
case 1:
|
||||
row.Name = name;
|
||||
if (line.Contains("MD5("))
|
||||
row.MD5 = lineParts[0].Substring("MD5".Length).Trim('(', ')');
|
||||
else
|
||||
row.SHA1 = lineParts[0].Substring("SHA1".Length).Trim('(', ')');
|
||||
break;
|
||||
|
||||
// Normal ROM (Name, Size, CRC, MD5/SHA1)
|
||||
case 3 when line.Contains("CRC"):
|
||||
row.Name = name;
|
||||
row.Size = lineParts[0];
|
||||
row.CRC = lineParts[1].Substring("CRC".Length).Trim('(', ')');
|
||||
if (line.Contains("MD5("))
|
||||
row.MD5 = lineParts[2].Substring("MD5".Length).Trim('(', ')');
|
||||
else
|
||||
row.SHA1 = lineParts[2].Substring("SHA1".Length).Trim('(', ')');
|
||||
break;
|
||||
// Normal ROM (Name, Size, CRC, MD5/SHA1)
|
||||
case 3 when line.Contains("CRC"):
|
||||
row.Name = name;
|
||||
row.Size = lineParts[0];
|
||||
row.CRC = lineParts[1].Substring("CRC".Length).Trim('(', ')');
|
||||
if (line.Contains("MD5("))
|
||||
row.MD5 = lineParts[2].Substring("MD5".Length).Trim('(', ')');
|
||||
else
|
||||
row.SHA1 = lineParts[2].Substring("SHA1".Length).Trim('(', ')');
|
||||
break;
|
||||
|
||||
// Bad CHD (Name, BAD, SHA1, BAD_DUMP)
|
||||
case 3 when line.Contains("BAD_DUMP"):
|
||||
row.Name = name;
|
||||
row.Bad = true;
|
||||
if (line.Contains("MD5("))
|
||||
row.MD5 = lineParts[1].Substring("MD5".Length).Trim('(', ')');
|
||||
else
|
||||
row.SHA1 = lineParts[1].Substring("SHA1".Length).Trim('(', ')');
|
||||
break;
|
||||
// Bad CHD (Name, BAD, SHA1, BAD_DUMP)
|
||||
case 3 when line.Contains("BAD_DUMP"):
|
||||
row.Name = name;
|
||||
row.Bad = true;
|
||||
if (line.Contains("MD5("))
|
||||
row.MD5 = lineParts[1].Substring("MD5".Length).Trim('(', ')');
|
||||
else
|
||||
row.SHA1 = lineParts[1].Substring("SHA1".Length).Trim('(', ')');
|
||||
break;
|
||||
|
||||
// Nodump CHD (Name, NO GOOD DUMP KNOWN)
|
||||
case 4 when line.Contains("NO GOOD DUMP KNOWN"):
|
||||
row.Name = name;
|
||||
row.NoGoodDumpKnown = true;
|
||||
break;
|
||||
// Nodump CHD (Name, NO GOOD DUMP KNOWN)
|
||||
case 4 when line.Contains("NO GOOD DUMP KNOWN"):
|
||||
row.Name = name;
|
||||
row.NoGoodDumpKnown = true;
|
||||
break;
|
||||
|
||||
// Bad ROM (Name, Size, BAD, CRC, MD5/SHA1, BAD_DUMP)
|
||||
case 5 when line.Contains("BAD_DUMP"):
|
||||
row.Name = name;
|
||||
row.Size = lineParts[0];
|
||||
row.Bad = true;
|
||||
row.CRC = lineParts[2].Substring("CRC".Length).Trim('(', ')');
|
||||
if (line.Contains("MD5("))
|
||||
row.MD5 = lineParts[3].Substring("MD5".Length).Trim('(', ')');
|
||||
else
|
||||
row.SHA1 = lineParts[3].Substring("SHA1".Length).Trim('(', ')');
|
||||
break;
|
||||
// Bad ROM (Name, Size, BAD, CRC, MD5/SHA1, BAD_DUMP)
|
||||
case 5 when line.Contains("BAD_DUMP"):
|
||||
row.Name = name;
|
||||
row.Size = lineParts[0];
|
||||
row.Bad = true;
|
||||
row.CRC = lineParts[2].Substring("CRC".Length).Trim('(', ')');
|
||||
if (line.Contains("MD5("))
|
||||
row.MD5 = lineParts[3].Substring("MD5".Length).Trim('(', ')');
|
||||
else
|
||||
row.SHA1 = lineParts[3].Substring("SHA1".Length).Trim('(', ')');
|
||||
break;
|
||||
|
||||
// Nodump ROM (Name, Size, NO GOOD DUMP KNOWN)
|
||||
case 5 when line.Contains("NO GOOD DUMP KNOWN"):
|
||||
row.Name = name;
|
||||
row.Size = lineParts[0];
|
||||
row.NoGoodDumpKnown = true;
|
||||
break;
|
||||
// Nodump ROM (Name, Size, NO GOOD DUMP KNOWN)
|
||||
case 5 when line.Contains("NO GOOD DUMP KNOWN"):
|
||||
row.Name = name;
|
||||
row.Size = lineParts[0];
|
||||
row.NoGoodDumpKnown = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
row = null;
|
||||
break;
|
||||
default:
|
||||
row = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (row != null)
|
||||
rows.Add(row);
|
||||
}
|
||||
|
||||
if (row != null)
|
||||
rows.Add(row);
|
||||
}
|
||||
// If we have a set to process
|
||||
if (set != null)
|
||||
{
|
||||
set.Row = [.. rows];
|
||||
sets.Add(set);
|
||||
set = null;
|
||||
rows.Clear();
|
||||
}
|
||||
|
||||
// If we have a set to process
|
||||
if (set != null)
|
||||
// Add extra pieces and return
|
||||
if (sets.Count > 0)
|
||||
{
|
||||
dat.Set = [.. sets];
|
||||
return dat;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
set.Row = [.. rows];
|
||||
sets.Add(set);
|
||||
set = null;
|
||||
rows.Clear();
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add extra pieces and return
|
||||
if (sets.Count > 0)
|
||||
{
|
||||
dat.Set = [.. sets];
|
||||
return dat;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,48 +15,52 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
try
|
||||
{
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
// Create a new executable to fill
|
||||
var executable = new Executable();
|
||||
|
||||
// Create a new executable to fill
|
||||
var executable = new Executable();
|
||||
#region Executable Header
|
||||
|
||||
#region Executable Header
|
||||
// Try to parse the executable header
|
||||
var executableHeader = ParseExecutableHeader(data);
|
||||
if (executableHeader == null)
|
||||
return null;
|
||||
|
||||
// Try to parse the executable header
|
||||
var executableHeader = ParseExecutableHeader(data);
|
||||
if (executableHeader == null)
|
||||
return null;
|
||||
// Set the executable header
|
||||
executable.Header = executableHeader;
|
||||
|
||||
// Set the executable header
|
||||
executable.Header = executableHeader;
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
#region Relocation Table
|
||||
|
||||
#region Relocation Table
|
||||
// If the offset for the relocation table doesn't exist
|
||||
int tableAddress = initialOffset + executableHeader.RelocationTableAddr;
|
||||
if (tableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// If the offset for the relocation table doesn't exist
|
||||
int tableAddress = initialOffset + executableHeader.RelocationTableAddr;
|
||||
if (tableAddress >= data.Length)
|
||||
// Try to parse the relocation table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var relocationTable = ParseRelocationTable(data, executableHeader.RelocationItems);
|
||||
if (relocationTable == null)
|
||||
return null;
|
||||
|
||||
// Set the relocation table
|
||||
executable.RelocationTable = relocationTable;
|
||||
|
||||
#endregion
|
||||
|
||||
// Return the executable
|
||||
return executable;
|
||||
|
||||
// Try to parse the relocation table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var relocationTable = ParseRelocationTable(data, executableHeader.RelocationItems);
|
||||
if (relocationTable == null)
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
|
||||
// Set the relocation table
|
||||
executable.RelocationTable = relocationTable;
|
||||
|
||||
#endregion
|
||||
|
||||
// Return the executable
|
||||
return executable;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -16,73 +16,77 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !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 cabinet to fill
|
||||
var cabinet = new Cabinet();
|
||||
|
||||
#region Cabinet Header
|
||||
|
||||
// Try to parse the cabinet header
|
||||
var cabinetHeader = ParseCabinetHeader(data);
|
||||
if (cabinetHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the cabinet header
|
||||
cabinet.Header = cabinetHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Folders
|
||||
|
||||
// Set the folder array
|
||||
cabinet.Folders = new CFFOLDER[cabinetHeader.FolderCount];
|
||||
|
||||
// Try to parse each folder, if we have any
|
||||
for (int i = 0; i < cabinetHeader.FolderCount; i++)
|
||||
try
|
||||
{
|
||||
var folder = ParseFolder(data, cabinetHeader);
|
||||
if (folder == null)
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Create a new cabinet to fill
|
||||
var cabinet = new Cabinet();
|
||||
|
||||
#region Cabinet Header
|
||||
|
||||
// Try to parse the cabinet header
|
||||
var cabinetHeader = ParseCabinetHeader(data);
|
||||
if (cabinetHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the folder
|
||||
cabinet.Folders[i] = folder;
|
||||
}
|
||||
// Set the cabinet header
|
||||
cabinet.Header = cabinetHeader;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Files
|
||||
#region Folders
|
||||
|
||||
// Get the files offset
|
||||
int filesOffset = (int)cabinetHeader.FilesOffset + initialOffset;
|
||||
if (filesOffset > data.Length)
|
||||
return null;
|
||||
// Set the folder array
|
||||
cabinet.Folders = new CFFOLDER[cabinetHeader.FolderCount];
|
||||
|
||||
// Seek to the offset
|
||||
data.Seek(filesOffset, SeekOrigin.Begin);
|
||||
// Try to parse each folder, if we have any
|
||||
for (int i = 0; i < cabinetHeader.FolderCount; i++)
|
||||
{
|
||||
var folder = ParseFolder(data, cabinetHeader);
|
||||
if (folder == null)
|
||||
return null;
|
||||
|
||||
// Set the file array
|
||||
cabinet.Files = new CFFILE[cabinetHeader.FileCount];
|
||||
// Set the folder
|
||||
cabinet.Folders[i] = folder;
|
||||
}
|
||||
|
||||
// Try to parse each file, if we have any
|
||||
for (int i = 0; i < cabinetHeader.FileCount; i++)
|
||||
{
|
||||
var file = ParseFile(data);
|
||||
if (file == null)
|
||||
#endregion
|
||||
|
||||
#region Files
|
||||
|
||||
// Get the files offset
|
||||
int filesOffset = (int)cabinetHeader.FilesOffset + initialOffset;
|
||||
if (filesOffset > data.Length)
|
||||
return null;
|
||||
|
||||
// Set the file
|
||||
cabinet.Files[i] = file;
|
||||
// Seek to the offset
|
||||
data.Seek(filesOffset, SeekOrigin.Begin);
|
||||
|
||||
// Set the file array
|
||||
cabinet.Files = new CFFILE[cabinetHeader.FileCount];
|
||||
|
||||
// Try to parse each file, if we have any
|
||||
for (int i = 0; i < cabinetHeader.FileCount; i++)
|
||||
{
|
||||
var file = ParseFile(data);
|
||||
if (file == null)
|
||||
return null;
|
||||
|
||||
// Set the file
|
||||
cabinet.Files[i] = file;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return cabinet;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return cabinet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -17,319 +17,323 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new archive to fill
|
||||
var archive = new Archive();
|
||||
|
||||
#region User Data
|
||||
|
||||
// Check for User Data
|
||||
uint possibleSignature = data.ReadUInt32();
|
||||
data.Seek(-4, SeekOrigin.Current);
|
||||
if (possibleSignature == UserDataSignatureUInt32)
|
||||
try
|
||||
{
|
||||
// Save the current position for offset correction
|
||||
long basePtr = data.Position;
|
||||
// Create a new archive to fill
|
||||
var archive = new Archive();
|
||||
|
||||
// Deserialize the user data, returning null if invalid
|
||||
var userData = data.ReadType<UserData>();
|
||||
if (userData?.Signature != UserDataSignatureString)
|
||||
return null;
|
||||
#region User Data
|
||||
|
||||
// Set the user data
|
||||
archive.UserData = userData;
|
||||
|
||||
// Set the starting position according to the header offset
|
||||
data.Seek(basePtr + (int)archive.UserData.HeaderOffset, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Archive Header
|
||||
|
||||
// Check for the Header
|
||||
possibleSignature = data.ReadUInt32();
|
||||
data.Seek(-4, SeekOrigin.Current);
|
||||
if (possibleSignature == ArchiveHeaderSignatureUInt32)
|
||||
{
|
||||
// Try to parse the archive header
|
||||
var archiveHeader = ParseArchiveHeader(data);
|
||||
if (archiveHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the archive header
|
||||
archive.ArchiveHeader = archiveHeader;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hash Table
|
||||
|
||||
// TODO: The hash table has to be be decrypted before reading
|
||||
|
||||
// Version 1
|
||||
if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format1)
|
||||
{
|
||||
// If we have a hash table
|
||||
long hashTableOffset = archive.ArchiveHeader.HashTablePosition;
|
||||
if (hashTableOffset != 0)
|
||||
// Check for User Data
|
||||
uint possibleSignature = data.ReadUInt32();
|
||||
data.Seek(-4, SeekOrigin.Current);
|
||||
if (possibleSignature == UserDataSignatureUInt32)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(hashTableOffset, SeekOrigin.Begin);
|
||||
// Save the current position for offset correction
|
||||
long basePtr = data.Position;
|
||||
|
||||
// Find the ending offset based on size
|
||||
long hashTableEnd = hashTableOffset + archive.ArchiveHeader.HashTableSize;
|
||||
|
||||
// Read in the hash table
|
||||
var hashTable = new List<HashEntry>();
|
||||
|
||||
while (data.Position < hashTableEnd)
|
||||
{
|
||||
var hashEntry = data.ReadType<HashEntry>();
|
||||
if (hashEntry == null)
|
||||
return null;
|
||||
|
||||
hashTable.Add(hashEntry);
|
||||
}
|
||||
|
||||
archive.HashTable = [.. hashTable];
|
||||
}
|
||||
}
|
||||
|
||||
// Version 2 and 3
|
||||
else if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format2
|
||||
|| archive.ArchiveHeader.FormatVersion == FormatVersion.Format3)
|
||||
{
|
||||
// If we have a hash table
|
||||
long hashTableOffset = ((uint)archive.ArchiveHeader.HashTablePositionHi << 23) | archive.ArchiveHeader.HashTablePosition;
|
||||
if (hashTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(hashTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Find the ending offset based on size
|
||||
long hashTableEnd = hashTableOffset + archive.ArchiveHeader.HashTableSize;
|
||||
|
||||
// Read in the hash table
|
||||
var hashTable = new List<HashEntry>();
|
||||
|
||||
while (data.Position < hashTableEnd)
|
||||
{
|
||||
var hashEntry = data.ReadType<HashEntry>();
|
||||
if (hashEntry == null)
|
||||
return null;
|
||||
|
||||
hashTable.Add(hashEntry);
|
||||
}
|
||||
|
||||
archive.HashTable = [.. hashTable];
|
||||
}
|
||||
}
|
||||
|
||||
// Version 4
|
||||
else if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format4)
|
||||
{
|
||||
// If we have a hash table
|
||||
long hashTableOffset = ((uint)archive.ArchiveHeader.HashTablePositionHi << 23) | archive.ArchiveHeader.HashTablePosition;
|
||||
if (hashTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(hashTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Find the ending offset based on size
|
||||
long hashTableEnd = hashTableOffset + (long)archive.ArchiveHeader.HashTableSizeLong;
|
||||
|
||||
// Read in the hash table
|
||||
var hashTable = new List<HashEntry>();
|
||||
|
||||
while (data.Position < hashTableEnd)
|
||||
{
|
||||
var hashEntry = data.ReadType<HashEntry>();
|
||||
if (hashEntry == null)
|
||||
return null;
|
||||
|
||||
hashTable.Add(hashEntry);
|
||||
}
|
||||
|
||||
archive.HashTable = [.. hashTable];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Block Table
|
||||
|
||||
// Version 1
|
||||
if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format1)
|
||||
{
|
||||
// If we have a block table
|
||||
long blockTableOffset = archive.ArchiveHeader.BlockTablePosition;
|
||||
if (blockTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(blockTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Find the ending offset based on size
|
||||
long blockTableEnd = blockTableOffset + archive.ArchiveHeader.BlockTableSize;
|
||||
|
||||
// Read in the block table
|
||||
var blockTable = new List<BlockEntry>();
|
||||
|
||||
while (data.Position < blockTableEnd)
|
||||
{
|
||||
var blockEntry = data.ReadType<BlockEntry>();
|
||||
if (blockEntry == null)
|
||||
return null;
|
||||
|
||||
blockTable.Add(blockEntry);
|
||||
}
|
||||
|
||||
archive.BlockTable = [.. blockTable];
|
||||
}
|
||||
}
|
||||
|
||||
// Version 2 and 3
|
||||
else if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format2
|
||||
|| archive.ArchiveHeader.FormatVersion == FormatVersion.Format3)
|
||||
{
|
||||
// If we have a block table
|
||||
long blockTableOffset = ((uint)archive.ArchiveHeader.BlockTablePositionHi << 23) | archive.ArchiveHeader.BlockTablePosition;
|
||||
if (blockTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(blockTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Find the ending offset based on size
|
||||
long blockTableEnd = blockTableOffset + archive.ArchiveHeader.BlockTableSize;
|
||||
|
||||
// Read in the block table
|
||||
var blockTable = new List<BlockEntry>();
|
||||
|
||||
while (data.Position < blockTableEnd)
|
||||
{
|
||||
var blockEntry = data.ReadType<BlockEntry>();
|
||||
if (blockEntry == null)
|
||||
return null;
|
||||
|
||||
blockTable.Add(blockEntry);
|
||||
}
|
||||
|
||||
archive.BlockTable = [.. blockTable];
|
||||
}
|
||||
}
|
||||
|
||||
// Version 4
|
||||
else if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format4)
|
||||
{
|
||||
// If we have a block table
|
||||
long blockTableOffset = ((uint)archive.ArchiveHeader.BlockTablePositionHi << 23) | archive.ArchiveHeader.BlockTablePosition;
|
||||
if (blockTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(blockTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Find the ending offset based on size
|
||||
long blockTableEnd = blockTableOffset + (long)archive.ArchiveHeader.BlockTableSizeLong;
|
||||
|
||||
// Read in the block table
|
||||
var blockTable = new List<BlockEntry>();
|
||||
|
||||
while (data.Position < blockTableEnd)
|
||||
{
|
||||
var blockEntry = data.ReadType<BlockEntry>();
|
||||
if (blockEntry == null)
|
||||
return null;
|
||||
|
||||
blockTable.Add(blockEntry);
|
||||
}
|
||||
|
||||
archive.BlockTable = [.. blockTable];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hi-Block Table
|
||||
|
||||
// Version 2, 3, and 4
|
||||
if (archive.ArchiveHeader.FormatVersion >= FormatVersion.Format2)
|
||||
{
|
||||
// If we have a hi-block table
|
||||
long hiBlockTableOffset = (long)archive.ArchiveHeader.HiBlockTablePosition;
|
||||
if (hiBlockTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(hiBlockTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read in the hi-block table
|
||||
var hiBlockTable = new List<short>();
|
||||
|
||||
for (int i = 0; i < (archive.BlockTable?.Length ?? 0); i++)
|
||||
{
|
||||
short hiBlockEntry = data.ReadInt16();
|
||||
hiBlockTable.Add(hiBlockEntry);
|
||||
}
|
||||
|
||||
archive.HiBlockTable = [.. hiBlockTable];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BET Table
|
||||
|
||||
// Version 3 and 4
|
||||
if (archive.ArchiveHeader.FormatVersion >= FormatVersion.Format3)
|
||||
{
|
||||
// If we have a BET table
|
||||
long betTableOffset = (long)archive.ArchiveHeader.BetTablePosition;
|
||||
if (betTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(betTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read in the BET table
|
||||
var betTable = ParseBetTable(data);
|
||||
if (betTable != null)
|
||||
// Deserialize the user data, returning null if invalid
|
||||
var userData = data.ReadType<UserData>();
|
||||
if (userData?.Signature != UserDataSignatureString)
|
||||
return null;
|
||||
|
||||
archive.BetTable = betTable;
|
||||
// Set the user data
|
||||
archive.UserData = userData;
|
||||
|
||||
// Set the starting position according to the header offset
|
||||
data.Seek(basePtr + (int)archive.UserData.HeaderOffset, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region HET Table
|
||||
#region Archive Header
|
||||
|
||||
// Version 3 and 4
|
||||
if (archive.ArchiveHeader.FormatVersion >= FormatVersion.Format3)
|
||||
{
|
||||
// If we have a HET table
|
||||
long hetTableOffset = (long)archive.ArchiveHeader.HetTablePosition;
|
||||
if (hetTableOffset != 0)
|
||||
// Check for the Header
|
||||
possibleSignature = data.ReadUInt32();
|
||||
data.Seek(-4, SeekOrigin.Current);
|
||||
if (possibleSignature == ArchiveHeaderSignatureUInt32)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(hetTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read in the HET table
|
||||
var hetTable = ParseHetTable(data);
|
||||
if (hetTable != null)
|
||||
// Try to parse the archive header
|
||||
var archiveHeader = ParseArchiveHeader(data);
|
||||
if (archiveHeader == null)
|
||||
return null;
|
||||
|
||||
archive.HetTable = hetTable;
|
||||
// Set the archive header
|
||||
archive.ArchiveHeader = archiveHeader;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hash Table
|
||||
|
||||
// TODO: The hash table has to be be decrypted before reading
|
||||
|
||||
// Version 1
|
||||
if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format1)
|
||||
{
|
||||
// If we have a hash table
|
||||
long hashTableOffset = archive.ArchiveHeader.HashTablePosition;
|
||||
if (hashTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(hashTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Find the ending offset based on size
|
||||
long hashTableEnd = hashTableOffset + archive.ArchiveHeader.HashTableSize;
|
||||
|
||||
// Read in the hash table
|
||||
var hashTable = new List<HashEntry>();
|
||||
|
||||
while (data.Position < hashTableEnd)
|
||||
{
|
||||
var hashEntry = data.ReadType<HashEntry>();
|
||||
if (hashEntry == null)
|
||||
return null;
|
||||
|
||||
hashTable.Add(hashEntry);
|
||||
}
|
||||
|
||||
archive.HashTable = [.. hashTable];
|
||||
}
|
||||
}
|
||||
|
||||
// Version 2 and 3
|
||||
else if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format2
|
||||
|| archive.ArchiveHeader.FormatVersion == FormatVersion.Format3)
|
||||
{
|
||||
// If we have a hash table
|
||||
long hashTableOffset = ((uint)archive.ArchiveHeader.HashTablePositionHi << 23) | archive.ArchiveHeader.HashTablePosition;
|
||||
if (hashTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(hashTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Find the ending offset based on size
|
||||
long hashTableEnd = hashTableOffset + archive.ArchiveHeader.HashTableSize;
|
||||
|
||||
// Read in the hash table
|
||||
var hashTable = new List<HashEntry>();
|
||||
|
||||
while (data.Position < hashTableEnd)
|
||||
{
|
||||
var hashEntry = data.ReadType<HashEntry>();
|
||||
if (hashEntry == null)
|
||||
return null;
|
||||
|
||||
hashTable.Add(hashEntry);
|
||||
}
|
||||
|
||||
archive.HashTable = [.. hashTable];
|
||||
}
|
||||
}
|
||||
|
||||
// Version 4
|
||||
else if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format4)
|
||||
{
|
||||
// If we have a hash table
|
||||
long hashTableOffset = ((uint)archive.ArchiveHeader.HashTablePositionHi << 23) | archive.ArchiveHeader.HashTablePosition;
|
||||
if (hashTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(hashTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Find the ending offset based on size
|
||||
long hashTableEnd = hashTableOffset + (long)archive.ArchiveHeader.HashTableSizeLong;
|
||||
|
||||
// Read in the hash table
|
||||
var hashTable = new List<HashEntry>();
|
||||
|
||||
while (data.Position < hashTableEnd)
|
||||
{
|
||||
var hashEntry = data.ReadType<HashEntry>();
|
||||
if (hashEntry == null)
|
||||
return null;
|
||||
|
||||
hashTable.Add(hashEntry);
|
||||
}
|
||||
|
||||
archive.HashTable = [.. hashTable];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Block Table
|
||||
|
||||
// Version 1
|
||||
if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format1)
|
||||
{
|
||||
// If we have a block table
|
||||
long blockTableOffset = archive.ArchiveHeader.BlockTablePosition;
|
||||
if (blockTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(blockTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Find the ending offset based on size
|
||||
long blockTableEnd = blockTableOffset + archive.ArchiveHeader.BlockTableSize;
|
||||
|
||||
// Read in the block table
|
||||
var blockTable = new List<BlockEntry>();
|
||||
|
||||
while (data.Position < blockTableEnd)
|
||||
{
|
||||
var blockEntry = data.ReadType<BlockEntry>();
|
||||
if (blockEntry == null)
|
||||
return null;
|
||||
|
||||
blockTable.Add(blockEntry);
|
||||
}
|
||||
|
||||
archive.BlockTable = [.. blockTable];
|
||||
}
|
||||
}
|
||||
|
||||
// Version 2 and 3
|
||||
else if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format2
|
||||
|| archive.ArchiveHeader.FormatVersion == FormatVersion.Format3)
|
||||
{
|
||||
// If we have a block table
|
||||
long blockTableOffset = ((uint)archive.ArchiveHeader.BlockTablePositionHi << 23) | archive.ArchiveHeader.BlockTablePosition;
|
||||
if (blockTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(blockTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Find the ending offset based on size
|
||||
long blockTableEnd = blockTableOffset + archive.ArchiveHeader.BlockTableSize;
|
||||
|
||||
// Read in the block table
|
||||
var blockTable = new List<BlockEntry>();
|
||||
|
||||
while (data.Position < blockTableEnd)
|
||||
{
|
||||
var blockEntry = data.ReadType<BlockEntry>();
|
||||
if (blockEntry == null)
|
||||
return null;
|
||||
|
||||
blockTable.Add(blockEntry);
|
||||
}
|
||||
|
||||
archive.BlockTable = [.. blockTable];
|
||||
}
|
||||
}
|
||||
|
||||
// Version 4
|
||||
else if (archive.ArchiveHeader.FormatVersion == FormatVersion.Format4)
|
||||
{
|
||||
// If we have a block table
|
||||
long blockTableOffset = ((uint)archive.ArchiveHeader.BlockTablePositionHi << 23) | archive.ArchiveHeader.BlockTablePosition;
|
||||
if (blockTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(blockTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Find the ending offset based on size
|
||||
long blockTableEnd = blockTableOffset + (long)archive.ArchiveHeader.BlockTableSizeLong;
|
||||
|
||||
// Read in the block table
|
||||
var blockTable = new List<BlockEntry>();
|
||||
|
||||
while (data.Position < blockTableEnd)
|
||||
{
|
||||
var blockEntry = data.ReadType<BlockEntry>();
|
||||
if (blockEntry == null)
|
||||
return null;
|
||||
|
||||
blockTable.Add(blockEntry);
|
||||
}
|
||||
|
||||
archive.BlockTable = [.. blockTable];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hi-Block Table
|
||||
|
||||
// Version 2, 3, and 4
|
||||
if (archive.ArchiveHeader.FormatVersion >= FormatVersion.Format2)
|
||||
{
|
||||
// If we have a hi-block table
|
||||
long hiBlockTableOffset = (long)archive.ArchiveHeader.HiBlockTablePosition;
|
||||
if (hiBlockTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(hiBlockTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read in the hi-block table
|
||||
var hiBlockTable = new List<short>();
|
||||
|
||||
for (int i = 0; i < (archive.BlockTable?.Length ?? 0); i++)
|
||||
{
|
||||
short hiBlockEntry = data.ReadInt16();
|
||||
hiBlockTable.Add(hiBlockEntry);
|
||||
}
|
||||
|
||||
archive.HiBlockTable = [.. hiBlockTable];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BET Table
|
||||
|
||||
// Version 3 and 4
|
||||
if (archive.ArchiveHeader.FormatVersion >= FormatVersion.Format3)
|
||||
{
|
||||
// If we have a BET table
|
||||
long betTableOffset = (long)archive.ArchiveHeader.BetTablePosition;
|
||||
if (betTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(betTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read in the BET table
|
||||
var betTable = ParseBetTable(data);
|
||||
if (betTable != null)
|
||||
return null;
|
||||
|
||||
archive.BetTable = betTable;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region HET Table
|
||||
|
||||
// Version 3 and 4
|
||||
if (archive.ArchiveHeader.FormatVersion >= FormatVersion.Format3)
|
||||
{
|
||||
// If we have a HET table
|
||||
long hetTableOffset = (long)archive.ArchiveHeader.HetTablePosition;
|
||||
if (hetTableOffset != 0)
|
||||
{
|
||||
// Seek to the offset
|
||||
data.Seek(hetTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read in the HET table
|
||||
var hetTable = ParseHetTable(data);
|
||||
if (hetTable != null)
|
||||
return null;
|
||||
|
||||
archive.HetTable = hetTable;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return archive;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return archive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -16,122 +16,126 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new cart image to fill
|
||||
var cart = new Cart();
|
||||
|
||||
#region NCSD Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = ParseNCSDHeader(data);
|
||||
if (header == null)
|
||||
return null;
|
||||
|
||||
// Set the cart image header
|
||||
cart.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Card Info Header
|
||||
|
||||
// Try to parse the card info header
|
||||
var cardInfoHeader = ParseCardInfoHeader(data);
|
||||
if (cardInfoHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the card info header
|
||||
cart.CardInfoHeader = cardInfoHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Development Card Info Header
|
||||
|
||||
// Try to parse the development card info header
|
||||
var developmentCardInfoHeader = data.ReadType<DevelopmentCardInfoHeader>();
|
||||
if (developmentCardInfoHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the development card info header
|
||||
cart.DevelopmentCardInfoHeader = developmentCardInfoHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
// Cache the media unit size for further use
|
||||
long mediaUnitSize = 0;
|
||||
if (header.PartitionFlags != null)
|
||||
mediaUnitSize = (uint)(0x200 * Math.Pow(2, header.PartitionFlags[(int)NCSDFlags.MediaUnitSize]));
|
||||
|
||||
#region Partitions
|
||||
|
||||
// Create the tables
|
||||
cart.Partitions = new NCCHHeader[8];
|
||||
cart.ExtendedHeaders = new NCCHExtendedHeader?[8];
|
||||
cart.ExeFSHeaders = new ExeFSHeader?[8];
|
||||
cart.RomFSHeaders = new RomFSHeader?[8];
|
||||
|
||||
// Iterate and build the partitions
|
||||
for (int i = 0; i < 8; i++)
|
||||
try
|
||||
{
|
||||
// Find the offset to the partition
|
||||
long partitionOffset = cart.Header.PartitionsTable?[i]?.Offset ?? 0;
|
||||
partitionOffset *= mediaUnitSize;
|
||||
if (partitionOffset == 0)
|
||||
continue;
|
||||
// Create a new cart image to fill
|
||||
var cart = new Cart();
|
||||
|
||||
// Seek to the start of the partition
|
||||
data.Seek(partitionOffset, SeekOrigin.Begin);
|
||||
#region NCSD Header
|
||||
|
||||
// Handle the normal header
|
||||
var partition = ParseNCCHHeader(data);
|
||||
if (partition == null || partition.MagicID != NCCHMagicNumber)
|
||||
continue;
|
||||
// Try to parse the header
|
||||
var header = ParseNCSDHeader(data);
|
||||
if (header == null)
|
||||
return null;
|
||||
|
||||
// Set the normal header
|
||||
cart.Partitions[i] = partition;
|
||||
// Set the cart image header
|
||||
cart.Header = header;
|
||||
|
||||
// Handle the extended header, if it exists
|
||||
if (partition.ExtendedHeaderSizeInBytes > 0)
|
||||
#endregion
|
||||
|
||||
#region Card Info Header
|
||||
|
||||
// Try to parse the card info header
|
||||
var cardInfoHeader = ParseCardInfoHeader(data);
|
||||
if (cardInfoHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the card info header
|
||||
cart.CardInfoHeader = cardInfoHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Development Card Info Header
|
||||
|
||||
// Try to parse the development card info header
|
||||
var developmentCardInfoHeader = data.ReadType<DevelopmentCardInfoHeader>();
|
||||
if (developmentCardInfoHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the development card info header
|
||||
cart.DevelopmentCardInfoHeader = developmentCardInfoHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
// Cache the media unit size for further use
|
||||
long mediaUnitSize = 0;
|
||||
if (header.PartitionFlags != null)
|
||||
mediaUnitSize = (uint)(0x200 * Math.Pow(2, header.PartitionFlags[(int)NCSDFlags.MediaUnitSize]));
|
||||
|
||||
#region Partitions
|
||||
|
||||
// Create the tables
|
||||
cart.Partitions = new NCCHHeader[8];
|
||||
cart.ExtendedHeaders = new NCCHExtendedHeader?[8];
|
||||
cart.ExeFSHeaders = new ExeFSHeader?[8];
|
||||
cart.RomFSHeaders = new RomFSHeader?[8];
|
||||
|
||||
// Iterate and build the partitions
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
var extendedHeader = data.ReadType<NCCHExtendedHeader>();
|
||||
if (extendedHeader != null)
|
||||
cart.ExtendedHeaders[i] = extendedHeader;
|
||||
}
|
||||
|
||||
// Handle the ExeFS, if it exists
|
||||
if (partition.ExeFSSizeInMediaUnits > 0)
|
||||
{
|
||||
long offset = partition.ExeFSOffsetInMediaUnits * mediaUnitSize;
|
||||
data.Seek(partitionOffset + offset, SeekOrigin.Begin);
|
||||
|
||||
var exeFsHeader = ParseExeFSHeader(data);
|
||||
if (exeFsHeader == null)
|
||||
return null;
|
||||
|
||||
cart.ExeFSHeaders[i] = exeFsHeader;
|
||||
}
|
||||
|
||||
// Handle the RomFS, if it exists
|
||||
if (partition.RomFSSizeInMediaUnits > 0)
|
||||
{
|
||||
long offset = partition.RomFSOffsetInMediaUnits * mediaUnitSize;
|
||||
data.Seek(partitionOffset + offset, SeekOrigin.Begin);
|
||||
|
||||
var romFsHeader = data.ReadType<RomFSHeader>();
|
||||
if (romFsHeader?.MagicString != RomFSMagicNumber)
|
||||
continue;
|
||||
if (romFsHeader?.MagicNumber != RomFSSecondMagicNumber)
|
||||
// Find the offset to the partition
|
||||
long partitionOffset = cart.Header.PartitionsTable?[i]?.Offset ?? 0;
|
||||
partitionOffset *= mediaUnitSize;
|
||||
if (partitionOffset == 0)
|
||||
continue;
|
||||
|
||||
cart.RomFSHeaders[i] = romFsHeader;
|
||||
// Seek to the start of the partition
|
||||
data.Seek(partitionOffset, SeekOrigin.Begin);
|
||||
|
||||
// Handle the normal header
|
||||
var partition = ParseNCCHHeader(data);
|
||||
if (partition == null || partition.MagicID != NCCHMagicNumber)
|
||||
continue;
|
||||
|
||||
// Set the normal header
|
||||
cart.Partitions[i] = partition;
|
||||
|
||||
// Handle the extended header, if it exists
|
||||
if (partition.ExtendedHeaderSizeInBytes > 0)
|
||||
{
|
||||
var extendedHeader = data.ReadType<NCCHExtendedHeader>();
|
||||
if (extendedHeader != null)
|
||||
cart.ExtendedHeaders[i] = extendedHeader;
|
||||
}
|
||||
|
||||
// Handle the ExeFS, if it exists
|
||||
if (partition.ExeFSSizeInMediaUnits > 0)
|
||||
{
|
||||
long offset = partition.ExeFSOffsetInMediaUnits * mediaUnitSize;
|
||||
data.Seek(partitionOffset + offset, SeekOrigin.Begin);
|
||||
|
||||
var exeFsHeader = ParseExeFSHeader(data);
|
||||
if (exeFsHeader == null)
|
||||
return null;
|
||||
|
||||
cart.ExeFSHeaders[i] = exeFsHeader;
|
||||
}
|
||||
|
||||
// Handle the RomFS, if it exists
|
||||
if (partition.RomFSSizeInMediaUnits > 0)
|
||||
{
|
||||
long offset = partition.RomFSOffsetInMediaUnits * mediaUnitSize;
|
||||
data.Seek(partitionOffset + offset, SeekOrigin.Begin);
|
||||
|
||||
var romFsHeader = data.ReadType<RomFSHeader>();
|
||||
if (romFsHeader?.MagicString != RomFSMagicNumber)
|
||||
continue;
|
||||
if (romFsHeader?.MagicNumber != RomFSSecondMagicNumber)
|
||||
continue;
|
||||
|
||||
cart.RomFSHeaders[i] = romFsHeader;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return cart;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return cart;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,261 +15,265 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new Half-Life No Cache to fill
|
||||
var file = new Models.NCF.File();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Dummy0 != 0x00000001)
|
||||
return null;
|
||||
if (header?.MajorVersion != 0x00000002)
|
||||
return null;
|
||||
if (header?.MinorVersion != 1)
|
||||
return null;
|
||||
|
||||
// Set the no cache header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
// Cache the current offset
|
||||
long initialOffset = data.Position;
|
||||
|
||||
#region Directory Header
|
||||
|
||||
// Try to parse the directory header
|
||||
var directoryHeader = data.ReadType<DirectoryHeader>();
|
||||
if (directoryHeader?.Dummy0 != 0x00000004)
|
||||
return null;
|
||||
|
||||
// Set the game cache directory header
|
||||
file.DirectoryHeader = directoryHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Entries
|
||||
|
||||
// Create the directory entry array
|
||||
file.DirectoryEntries = new DirectoryEntry[directoryHeader.ItemCount];
|
||||
|
||||
// Try to parse the directory entries
|
||||
for (int i = 0; i < directoryHeader.ItemCount; i++)
|
||||
try
|
||||
{
|
||||
var directoryEntry = data.ReadType<DirectoryEntry>();
|
||||
if (directoryEntry == null)
|
||||
// Create a new Half-Life No Cache to fill
|
||||
var file = new Models.NCF.File();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Dummy0 != 0x00000001)
|
||||
return null;
|
||||
if (header?.MajorVersion != 0x00000002)
|
||||
return null;
|
||||
if (header?.MinorVersion != 1)
|
||||
return null;
|
||||
|
||||
file.DirectoryEntries[i] = directoryEntry;
|
||||
}
|
||||
// Set the no cache header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Directory Names
|
||||
// Cache the current offset
|
||||
long initialOffset = data.Position;
|
||||
|
||||
if (directoryHeader.NameSize > 0)
|
||||
{
|
||||
// Get the current offset for adjustment
|
||||
long directoryNamesStart = data.Position;
|
||||
#region Directory Header
|
||||
|
||||
// Get the ending offset
|
||||
long directoryNamesEnd = data.Position + directoryHeader.NameSize;
|
||||
// Try to parse the directory header
|
||||
var directoryHeader = data.ReadType<DirectoryHeader>();
|
||||
if (directoryHeader?.Dummy0 != 0x00000004)
|
||||
return null;
|
||||
|
||||
// Create the string dictionary
|
||||
file.DirectoryNames = new Dictionary<long, string?>();
|
||||
// Set the game cache directory header
|
||||
file.DirectoryHeader = directoryHeader;
|
||||
|
||||
// Loop and read the null-terminated strings
|
||||
while (data.Position < directoryNamesEnd)
|
||||
#endregion
|
||||
|
||||
#region Directory Entries
|
||||
|
||||
// Create the directory entry array
|
||||
file.DirectoryEntries = new DirectoryEntry[directoryHeader.ItemCount];
|
||||
|
||||
// Try to parse the directory entries
|
||||
for (int i = 0; i < directoryHeader.ItemCount; i++)
|
||||
{
|
||||
long nameOffset = data.Position - directoryNamesStart;
|
||||
string? directoryName = data.ReadNullTerminatedAnsiString();
|
||||
if (data.Position > directoryNamesEnd)
|
||||
{
|
||||
data.Seek(-directoryName?.Length ?? 0, SeekOrigin.Current);
|
||||
byte[] endingData = data.ReadBytes((int)(directoryNamesEnd - data.Position));
|
||||
directoryName = Encoding.ASCII.GetString(endingData);
|
||||
}
|
||||
var directoryEntry = data.ReadType<DirectoryEntry>();
|
||||
if (directoryEntry == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryNames[nameOffset] = directoryName;
|
||||
file.DirectoryEntries[i] = directoryEntry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Directory Info 1 Entries
|
||||
#region Directory Names
|
||||
|
||||
// Create the directory info 1 entry array
|
||||
file.DirectoryInfo1Entries = new DirectoryInfo1Entry[directoryHeader.Info1Count];
|
||||
if (directoryHeader.NameSize > 0)
|
||||
{
|
||||
// Get the current offset for adjustment
|
||||
long directoryNamesStart = data.Position;
|
||||
|
||||
// Try to parse the directory info 1 entries
|
||||
for (int i = 0; i < directoryHeader.Info1Count; i++)
|
||||
{
|
||||
var directoryInfo1Entry = data.ReadType<DirectoryInfo1Entry>();
|
||||
if (directoryInfo1Entry == null)
|
||||
// Get the ending offset
|
||||
long directoryNamesEnd = data.Position + directoryHeader.NameSize;
|
||||
|
||||
// Create the string dictionary
|
||||
file.DirectoryNames = new Dictionary<long, string?>();
|
||||
|
||||
// Loop and read the null-terminated strings
|
||||
while (data.Position < directoryNamesEnd)
|
||||
{
|
||||
long nameOffset = data.Position - directoryNamesStart;
|
||||
string? directoryName = data.ReadNullTerminatedAnsiString();
|
||||
if (data.Position > directoryNamesEnd)
|
||||
{
|
||||
data.Seek(-directoryName?.Length ?? 0, SeekOrigin.Current);
|
||||
byte[] endingData = data.ReadBytes((int)(directoryNamesEnd - data.Position));
|
||||
directoryName = Encoding.ASCII.GetString(endingData);
|
||||
}
|
||||
|
||||
file.DirectoryNames[nameOffset] = directoryName;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Info 1 Entries
|
||||
|
||||
// Create the directory info 1 entry array
|
||||
file.DirectoryInfo1Entries = new DirectoryInfo1Entry[directoryHeader.Info1Count];
|
||||
|
||||
// Try to parse the directory info 1 entries
|
||||
for (int i = 0; i < directoryHeader.Info1Count; i++)
|
||||
{
|
||||
var directoryInfo1Entry = data.ReadType<DirectoryInfo1Entry>();
|
||||
if (directoryInfo1Entry == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryInfo1Entries[i] = directoryInfo1Entry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Info 2 Entries
|
||||
|
||||
// Create the directory info 2 entry array
|
||||
file.DirectoryInfo2Entries = new DirectoryInfo2Entry[directoryHeader.ItemCount];
|
||||
|
||||
// Try to parse the directory info 2 entries
|
||||
for (int i = 0; i < directoryHeader.ItemCount; i++)
|
||||
{
|
||||
var directoryInfo2Entry = data.ReadType<DirectoryInfo2Entry>();
|
||||
if (directoryInfo2Entry == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryInfo2Entries[i] = directoryInfo2Entry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Copy Entries
|
||||
|
||||
// Create the directory copy entry array
|
||||
file.DirectoryCopyEntries = new DirectoryCopyEntry[directoryHeader.CopyCount];
|
||||
|
||||
// Try to parse the directory copy entries
|
||||
for (int i = 0; i < directoryHeader.CopyCount; i++)
|
||||
{
|
||||
var directoryCopyEntry = data.ReadType<DirectoryCopyEntry>();
|
||||
if (directoryCopyEntry == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryCopyEntries[i] = directoryCopyEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Local Entries
|
||||
|
||||
// Create the directory local entry array
|
||||
file.DirectoryLocalEntries = new DirectoryLocalEntry[directoryHeader.LocalCount];
|
||||
|
||||
// Try to parse the directory local entries
|
||||
for (int i = 0; i < directoryHeader.LocalCount; i++)
|
||||
{
|
||||
var directoryLocalEntry = data.ReadType<DirectoryLocalEntry>();
|
||||
if (directoryLocalEntry == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryLocalEntries[i] = directoryLocalEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Seek to end of directory section, just in case
|
||||
data.Seek(initialOffset + directoryHeader.DirectorySize, SeekOrigin.Begin);
|
||||
|
||||
#region Unknown Header
|
||||
|
||||
// Try to parse the unknown header
|
||||
var unknownHeader = data.ReadType<UnknownHeader>();
|
||||
if (unknownHeader?.Dummy0 != 0x00000001)
|
||||
return null;
|
||||
if (unknownHeader?.Dummy1 != 0x00000000)
|
||||
return null;
|
||||
|
||||
file.DirectoryInfo1Entries[i] = directoryInfo1Entry;
|
||||
}
|
||||
// Set the game cache unknown header
|
||||
file.UnknownHeader = unknownHeader;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Directory Info 2 Entries
|
||||
#region Unknown Entries
|
||||
|
||||
// Create the directory info 2 entry array
|
||||
file.DirectoryInfo2Entries = new DirectoryInfo2Entry[directoryHeader.ItemCount];
|
||||
// Create the unknown entry array
|
||||
file.UnknownEntries = new UnknownEntry[directoryHeader.ItemCount];
|
||||
|
||||
// Try to parse the directory info 2 entries
|
||||
for (int i = 0; i < directoryHeader.ItemCount; i++)
|
||||
{
|
||||
var directoryInfo2Entry = data.ReadType<DirectoryInfo2Entry>();
|
||||
if (directoryInfo2Entry == null)
|
||||
// Try to parse the unknown entries
|
||||
for (int i = 0; i < directoryHeader.ItemCount; i++)
|
||||
{
|
||||
var unknownEntry = data.ReadType<UnknownEntry>();
|
||||
if (unknownEntry == null)
|
||||
return null;
|
||||
|
||||
file.UnknownEntries[i] = unknownEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Checksum Header
|
||||
|
||||
// Try to parse the checksum header
|
||||
var checksumHeader = data.ReadType<ChecksumHeader>();
|
||||
if (checksumHeader?.Dummy0 != 0x00000001)
|
||||
return null;
|
||||
|
||||
file.DirectoryInfo2Entries[i] = directoryInfo2Entry;
|
||||
}
|
||||
// Set the game cache checksum header
|
||||
file.ChecksumHeader = checksumHeader;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Directory Copy Entries
|
||||
// Cache the current offset
|
||||
initialOffset = data.Position;
|
||||
|
||||
// Create the directory copy entry array
|
||||
file.DirectoryCopyEntries = new DirectoryCopyEntry[directoryHeader.CopyCount];
|
||||
#region Checksum Map Header
|
||||
|
||||
// Try to parse the directory copy entries
|
||||
for (int i = 0; i < directoryHeader.CopyCount; i++)
|
||||
{
|
||||
var directoryCopyEntry = data.ReadType<DirectoryCopyEntry>();
|
||||
if (directoryCopyEntry == null)
|
||||
// Try to parse the checksum map header
|
||||
var checksumMapHeader = data.ReadType<ChecksumMapHeader>();
|
||||
if (checksumMapHeader?.Dummy0 != 0x14893721)
|
||||
return null;
|
||||
if (checksumMapHeader?.Dummy1 != 0x00000001)
|
||||
return null;
|
||||
|
||||
file.DirectoryCopyEntries[i] = directoryCopyEntry;
|
||||
// Set the game cache checksum map header
|
||||
file.ChecksumMapHeader = checksumMapHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Checksum Map Entries
|
||||
|
||||
// Create the checksum map entry array
|
||||
file.ChecksumMapEntries = new ChecksumMapEntry[checksumMapHeader.ItemCount];
|
||||
|
||||
// Try to parse the checksum map entries
|
||||
for (int i = 0; i < checksumMapHeader.ItemCount; i++)
|
||||
{
|
||||
var checksumMapEntry = data.ReadType<ChecksumMapEntry>();
|
||||
if (checksumMapEntry == null)
|
||||
return null;
|
||||
|
||||
file.ChecksumMapEntries[i] = checksumMapEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Checksum Entries
|
||||
|
||||
// Create the checksum entry array
|
||||
file.ChecksumEntries = new ChecksumEntry[checksumMapHeader.ChecksumCount];
|
||||
|
||||
// Try to parse the checksum entries
|
||||
for (int i = 0; i < checksumMapHeader.ChecksumCount; i++)
|
||||
{
|
||||
var checksumEntry = data.ReadType<ChecksumEntry>();
|
||||
if (checksumEntry == null)
|
||||
return null;
|
||||
|
||||
file.ChecksumEntries[i] = checksumEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Seek to end of checksum section, just in case
|
||||
data.Seek(initialOffset + checksumHeader.ChecksumSize, SeekOrigin.Begin);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Local Entries
|
||||
|
||||
// Create the directory local entry array
|
||||
file.DirectoryLocalEntries = new DirectoryLocalEntry[directoryHeader.LocalCount];
|
||||
|
||||
// Try to parse the directory local entries
|
||||
for (int i = 0; i < directoryHeader.LocalCount; i++)
|
||||
catch
|
||||
{
|
||||
var directoryLocalEntry = data.ReadType<DirectoryLocalEntry>();
|
||||
if (directoryLocalEntry == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryLocalEntries[i] = directoryLocalEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Seek to end of directory section, just in case
|
||||
data.Seek(initialOffset + directoryHeader.DirectorySize, SeekOrigin.Begin);
|
||||
|
||||
#region Unknown Header
|
||||
|
||||
// Try to parse the unknown header
|
||||
var unknownHeader = data.ReadType<UnknownHeader>();
|
||||
if (unknownHeader?.Dummy0 != 0x00000001)
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
if (unknownHeader?.Dummy1 != 0x00000000)
|
||||
return null;
|
||||
|
||||
// Set the game cache unknown header
|
||||
file.UnknownHeader = unknownHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unknown Entries
|
||||
|
||||
// Create the unknown entry array
|
||||
file.UnknownEntries = new UnknownEntry[directoryHeader.ItemCount];
|
||||
|
||||
// Try to parse the unknown entries
|
||||
for (int i = 0; i < directoryHeader.ItemCount; i++)
|
||||
{
|
||||
var unknownEntry = data.ReadType<UnknownEntry>();
|
||||
if (unknownEntry == null)
|
||||
return null;
|
||||
|
||||
file.UnknownEntries[i] = unknownEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Checksum Header
|
||||
|
||||
// Try to parse the checksum header
|
||||
var checksumHeader = data.ReadType<ChecksumHeader>();
|
||||
if (checksumHeader?.Dummy0 != 0x00000001)
|
||||
return null;
|
||||
|
||||
// Set the game cache checksum header
|
||||
file.ChecksumHeader = checksumHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
// Cache the current offset
|
||||
initialOffset = data.Position;
|
||||
|
||||
#region Checksum Map Header
|
||||
|
||||
// Try to parse the checksum map header
|
||||
var checksumMapHeader = data.ReadType<ChecksumMapHeader>();
|
||||
if (checksumMapHeader?.Dummy0 != 0x14893721)
|
||||
return null;
|
||||
if (checksumMapHeader?.Dummy1 != 0x00000001)
|
||||
return null;
|
||||
|
||||
// Set the game cache checksum map header
|
||||
file.ChecksumMapHeader = checksumMapHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Checksum Map Entries
|
||||
|
||||
// Create the checksum map entry array
|
||||
file.ChecksumMapEntries = new ChecksumMapEntry[checksumMapHeader.ItemCount];
|
||||
|
||||
// Try to parse the checksum map entries
|
||||
for (int i = 0; i < checksumMapHeader.ItemCount; i++)
|
||||
{
|
||||
var checksumMapEntry = data.ReadType<ChecksumMapEntry>();
|
||||
if (checksumMapEntry == null)
|
||||
return null;
|
||||
|
||||
file.ChecksumMapEntries[i] = checksumMapEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Checksum Entries
|
||||
|
||||
// Create the checksum entry array
|
||||
file.ChecksumEntries = new ChecksumEntry[checksumMapHeader.ChecksumCount];
|
||||
|
||||
// Try to parse the checksum entries
|
||||
for (int i = 0; i < checksumMapHeader.ChecksumCount; i++)
|
||||
{
|
||||
var checksumEntry = data.ReadType<ChecksumEntry>();
|
||||
if (checksumEntry == null)
|
||||
return null;
|
||||
|
||||
file.ChecksumEntries[i] = checksumEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Seek to end of checksum section, just in case
|
||||
data.Seek(initialOffset + checksumHeader.ChecksumSize, SeekOrigin.Begin);
|
||||
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,194 +15,198 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
try
|
||||
{
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
// Create a new executable to fill
|
||||
var executable = new Executable();
|
||||
|
||||
// Create a new executable to fill
|
||||
var executable = new Executable();
|
||||
#region MS-DOS Stub
|
||||
|
||||
#region MS-DOS Stub
|
||||
// Parse the MS-DOS stub
|
||||
var stub = new MSDOS().Deserialize(data);
|
||||
if (stub?.Header == null || stub.Header.NewExeHeaderAddr == 0)
|
||||
return null;
|
||||
|
||||
// Parse the MS-DOS stub
|
||||
var stub = new MSDOS().Deserialize(data);
|
||||
if (stub?.Header == null || stub.Header.NewExeHeaderAddr == 0)
|
||||
return null;
|
||||
// Set the MS-DOS stub
|
||||
executable.Stub = stub;
|
||||
|
||||
// Set the MS-DOS stub
|
||||
executable.Stub = stub;
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
#region Executable Header
|
||||
|
||||
#region Executable Header
|
||||
// Try to parse the executable header
|
||||
data.Seek(initialOffset + stub.Header.NewExeHeaderAddr, SeekOrigin.Begin);
|
||||
var header = data.ReadType<ExecutableHeader>();
|
||||
if (header?.Magic != SignatureString)
|
||||
return null;
|
||||
|
||||
// Try to parse the executable header
|
||||
data.Seek(initialOffset + stub.Header.NewExeHeaderAddr, SeekOrigin.Begin);
|
||||
var header = data.ReadType<ExecutableHeader>();
|
||||
if (header?.Magic != SignatureString)
|
||||
return null;
|
||||
// Set the executable header
|
||||
executable.Header = header;
|
||||
|
||||
// Set the executable header
|
||||
executable.Header = header;
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
#region Segment Table
|
||||
|
||||
#region Segment Table
|
||||
// If the offset for the segment table doesn't exist
|
||||
int tableAddress = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.SegmentTableOffset;
|
||||
if (tableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the segment table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var segmentTable = ParseSegmentTable(data, header.FileSegmentCount);
|
||||
if (segmentTable == null)
|
||||
return null;
|
||||
|
||||
// Set the segment table
|
||||
executable.SegmentTable = segmentTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resource Table
|
||||
|
||||
// If the offset for the segment table doesn't exist
|
||||
tableAddress = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.ResourceTableOffset;
|
||||
if (tableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the resource table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var resourceTable = ParseResourceTable(data, header.ResourceEntriesCount);
|
||||
if (resourceTable == null)
|
||||
return null;
|
||||
|
||||
// Set the resource table
|
||||
executable.ResourceTable = resourceTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resident-Name Table
|
||||
|
||||
// If the offset for the resident-name table doesn't exist
|
||||
tableAddress = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.ResidentNameTableOffset;
|
||||
int endOffset = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.ModuleReferenceTableOffset;
|
||||
if (tableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the resident-name table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var residentNameTable = ParseResidentNameTable(data, endOffset);
|
||||
if (residentNameTable == null)
|
||||
return null;
|
||||
|
||||
// Set the resident-name table
|
||||
executable.ResidentNameTable = residentNameTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Module-Reference Table
|
||||
|
||||
// If the offset for the module-reference table doesn't exist
|
||||
tableAddress = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.ModuleReferenceTableOffset;
|
||||
if (tableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the module-reference table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var moduleReferenceTable = ParseModuleReferenceTable(data, header.ModuleReferenceTableSize);
|
||||
if (moduleReferenceTable == null)
|
||||
return null;
|
||||
|
||||
// Set the module-reference table
|
||||
executable.ModuleReferenceTable = moduleReferenceTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Imported-Name Table
|
||||
|
||||
// If the offset for the imported-name table doesn't exist
|
||||
tableAddress = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.ImportedNamesTableOffset;
|
||||
endOffset = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.EntryTableOffset;
|
||||
if (tableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the imported-name table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var importedNameTable = ParseImportedNameTable(data, endOffset);
|
||||
if (importedNameTable == null)
|
||||
return null;
|
||||
|
||||
// Set the imported-name table
|
||||
executable.ImportedNameTable = importedNameTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Entry Table
|
||||
|
||||
// If the offset for the imported-name table doesn't exist
|
||||
tableAddress = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.EntryTableOffset;
|
||||
endOffset = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.EntryTableOffset
|
||||
+ header.EntryTableSize;
|
||||
if (tableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the imported-name table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var entryTable = ParseEntryTable(data, endOffset);
|
||||
if (entryTable == null)
|
||||
return null;
|
||||
|
||||
// Set the entry table
|
||||
executable.EntryTable = entryTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nonresident-Name Table
|
||||
|
||||
// If the offset for the nonresident-name table doesn't exist
|
||||
tableAddress = initialOffset
|
||||
+ (int)header.NonResidentNamesTableOffset;
|
||||
endOffset = initialOffset
|
||||
+ (int)header.NonResidentNamesTableOffset
|
||||
+ header.NonResidentNameTableSize;
|
||||
if (tableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the nonresident-name table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var nonResidentNameTable = ParseNonResidentNameTable(data, endOffset);
|
||||
if (nonResidentNameTable == null)
|
||||
return null;
|
||||
|
||||
// Set the nonresident-name table
|
||||
executable.NonResidentNameTable = nonResidentNameTable;
|
||||
|
||||
#endregion
|
||||
|
||||
// If the offset for the segment table doesn't exist
|
||||
int tableAddress = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.SegmentTableOffset;
|
||||
if (tableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the segment table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var segmentTable = ParseSegmentTable(data, header.FileSegmentCount);
|
||||
if (segmentTable == null)
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
|
||||
// Set the segment table
|
||||
executable.SegmentTable = segmentTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resource Table
|
||||
|
||||
// If the offset for the segment table doesn't exist
|
||||
tableAddress = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.ResourceTableOffset;
|
||||
if (tableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the resource table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var resourceTable = ParseResourceTable(data, header.ResourceEntriesCount);
|
||||
if (resourceTable == null)
|
||||
return null;
|
||||
|
||||
// Set the resource table
|
||||
executable.ResourceTable = resourceTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resident-Name Table
|
||||
|
||||
// If the offset for the resident-name table doesn't exist
|
||||
tableAddress = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.ResidentNameTableOffset;
|
||||
int endOffset = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.ModuleReferenceTableOffset;
|
||||
if (tableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the resident-name table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var residentNameTable = ParseResidentNameTable(data, endOffset);
|
||||
if (residentNameTable == null)
|
||||
return null;
|
||||
|
||||
// Set the resident-name table
|
||||
executable.ResidentNameTable = residentNameTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Module-Reference Table
|
||||
|
||||
// If the offset for the module-reference table doesn't exist
|
||||
tableAddress = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.ModuleReferenceTableOffset;
|
||||
if (tableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the module-reference table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var moduleReferenceTable = ParseModuleReferenceTable(data, header.ModuleReferenceTableSize);
|
||||
if (moduleReferenceTable == null)
|
||||
return null;
|
||||
|
||||
// Set the module-reference table
|
||||
executable.ModuleReferenceTable = moduleReferenceTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Imported-Name Table
|
||||
|
||||
// If the offset for the imported-name table doesn't exist
|
||||
tableAddress = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.ImportedNamesTableOffset;
|
||||
endOffset = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.EntryTableOffset;
|
||||
if (tableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the imported-name table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var importedNameTable = ParseImportedNameTable(data, endOffset);
|
||||
if (importedNameTable == null)
|
||||
return null;
|
||||
|
||||
// Set the imported-name table
|
||||
executable.ImportedNameTable = importedNameTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Entry Table
|
||||
|
||||
// If the offset for the imported-name table doesn't exist
|
||||
tableAddress = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.EntryTableOffset;
|
||||
endOffset = initialOffset
|
||||
+ (int)stub.Header.NewExeHeaderAddr
|
||||
+ header.EntryTableOffset
|
||||
+ header.EntryTableSize;
|
||||
if (tableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the imported-name table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var entryTable = ParseEntryTable(data, endOffset);
|
||||
if (entryTable == null)
|
||||
return null;
|
||||
|
||||
// Set the entry table
|
||||
executable.EntryTable = entryTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nonresident-Name Table
|
||||
|
||||
// If the offset for the nonresident-name table doesn't exist
|
||||
tableAddress = initialOffset
|
||||
+ (int)header.NonResidentNamesTableOffset;
|
||||
endOffset = initialOffset
|
||||
+ (int)header.NonResidentNamesTableOffset
|
||||
+ header.NonResidentNameTableSize;
|
||||
if (tableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the nonresident-name table
|
||||
data.Seek(tableAddress, SeekOrigin.Begin);
|
||||
var nonResidentNameTable = ParseNonResidentNameTable(data, endOffset);
|
||||
if (nonResidentNameTable == null)
|
||||
return null;
|
||||
|
||||
// Set the nonresident-name table
|
||||
executable.NonResidentNameTable = nonResidentNameTable;
|
||||
|
||||
#endregion
|
||||
|
||||
return executable;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,106 +15,110 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new cart image to fill
|
||||
var cart = new Cart();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<CommonHeader>();
|
||||
if (header == null)
|
||||
return null;
|
||||
|
||||
// Set the cart image header
|
||||
cart.CommonHeader = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extended DSi Header
|
||||
|
||||
// If we have a DSi-compatible cartridge
|
||||
if (header.UnitCode == Unitcode.NDSPlusDSi || header.UnitCode == Unitcode.DSi)
|
||||
try
|
||||
{
|
||||
var extendedDSiHeader = data.ReadType<ExtendedDSiHeader>();
|
||||
if (extendedDSiHeader == null)
|
||||
// Create a new cart image to fill
|
||||
var cart = new Cart();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<CommonHeader>();
|
||||
if (header == null)
|
||||
return null;
|
||||
|
||||
cart.ExtendedDSiHeader = extendedDSiHeader;
|
||||
}
|
||||
// Set the cart image header
|
||||
cart.CommonHeader = header;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Secure Area
|
||||
#region Extended DSi Header
|
||||
|
||||
// Try to get the secure area offset
|
||||
long secureAreaOffset = 0x4000;
|
||||
if (secureAreaOffset > data.Length)
|
||||
return null;
|
||||
// If we have a DSi-compatible cartridge
|
||||
if (header.UnitCode == Unitcode.NDSPlusDSi || header.UnitCode == Unitcode.DSi)
|
||||
{
|
||||
var extendedDSiHeader = data.ReadType<ExtendedDSiHeader>();
|
||||
if (extendedDSiHeader == null)
|
||||
return null;
|
||||
|
||||
// Seek to the secure area
|
||||
data.Seek(secureAreaOffset, SeekOrigin.Begin);
|
||||
cart.ExtendedDSiHeader = extendedDSiHeader;
|
||||
}
|
||||
|
||||
// Read the secure area without processing
|
||||
cart.SecureArea = data.ReadBytes(0x800);
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
#region Secure Area
|
||||
|
||||
#region Name Table
|
||||
|
||||
// Try to get the name table offset
|
||||
long nameTableOffset = header.FileNameTableOffset;
|
||||
if (nameTableOffset < 0 || nameTableOffset > data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the name table
|
||||
data.Seek(nameTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the name table
|
||||
var nameTable = ParseNameTable(data);
|
||||
if (nameTable == null)
|
||||
return null;
|
||||
|
||||
// Set the name table
|
||||
cart.NameTable = nameTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Allocation Table
|
||||
|
||||
// Try to get the file allocation table offset
|
||||
long fileAllocationTableOffset = header.FileAllocationTableOffset;
|
||||
if (fileAllocationTableOffset < 0 || fileAllocationTableOffset > data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the file allocation table
|
||||
data.Seek(fileAllocationTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the file allocation table
|
||||
var fileAllocationTable = new List<FileAllocationTableEntry>();
|
||||
|
||||
// Try to parse the file allocation table
|
||||
while (data.Position - fileAllocationTableOffset < header.FileAllocationTableLength)
|
||||
{
|
||||
var entry = data.ReadType<FileAllocationTableEntry>();
|
||||
if (entry == null)
|
||||
// Try to get the secure area offset
|
||||
long secureAreaOffset = 0x4000;
|
||||
if (secureAreaOffset > data.Length)
|
||||
return null;
|
||||
|
||||
fileAllocationTable.Add(entry);
|
||||
|
||||
// Seek to the secure area
|
||||
data.Seek(secureAreaOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read the secure area without processing
|
||||
cart.SecureArea = data.ReadBytes(0x800);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Name Table
|
||||
|
||||
// Try to get the name table offset
|
||||
long nameTableOffset = header.FileNameTableOffset;
|
||||
if (nameTableOffset < 0 || nameTableOffset > data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the name table
|
||||
data.Seek(nameTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the name table
|
||||
var nameTable = ParseNameTable(data);
|
||||
if (nameTable == null)
|
||||
return null;
|
||||
|
||||
// Set the name table
|
||||
cart.NameTable = nameTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Allocation Table
|
||||
|
||||
// Try to get the file allocation table offset
|
||||
long fileAllocationTableOffset = header.FileAllocationTableOffset;
|
||||
if (fileAllocationTableOffset < 0 || fileAllocationTableOffset > data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the file allocation table
|
||||
data.Seek(fileAllocationTableOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the file allocation table
|
||||
var fileAllocationTable = new List<FileAllocationTableEntry>();
|
||||
|
||||
// Try to parse the file allocation table
|
||||
while (data.Position - fileAllocationTableOffset < header.FileAllocationTableLength)
|
||||
{
|
||||
var entry = data.ReadType<FileAllocationTableEntry>();
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
fileAllocationTable.Add(entry);
|
||||
}
|
||||
|
||||
// Set the file allocation table
|
||||
cart.FileAllocationTable = [.. fileAllocationTable];
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Read and optionally parse out the other areas
|
||||
// Look for offsets and lengths in the header pieces
|
||||
|
||||
return cart;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set the file allocation table
|
||||
cart.FileAllocationTable = [.. fileAllocationTable];
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Read and optionally parse out the other areas
|
||||
// Look for offsets and lengths in the header pieces
|
||||
|
||||
return cart;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -134,7 +138,7 @@ namespace SabreTools.Serialization.Deserializers
|
||||
var entry = data.ReadType<FolderAllocationTableEntry>();
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
|
||||
folderAllocationTable.Add(entry);
|
||||
|
||||
// If we have the root entry
|
||||
|
||||
@@ -14,51 +14,55 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new Half-Life Package to fill
|
||||
var file = new Models.PAK.File();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Signature != SignatureString)
|
||||
return null;
|
||||
|
||||
// Set the package header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Items
|
||||
|
||||
// Get the directory items offset
|
||||
uint directoryItemsOffset = header.DirectoryOffset;
|
||||
if (directoryItemsOffset < 0 || directoryItemsOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the directory items
|
||||
data.Seek(directoryItemsOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the directory item array
|
||||
file.DirectoryItems = new DirectoryItem[header.DirectoryLength / 64];
|
||||
|
||||
// Try to parse the directory items
|
||||
for (int i = 0; i < file.DirectoryItems.Length; i++)
|
||||
try
|
||||
{
|
||||
var directoryItem = data.ReadType<DirectoryItem>();
|
||||
if (directoryItem == null)
|
||||
// Create a new Half-Life Package to fill
|
||||
var file = new Models.PAK.File();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Signature != SignatureString)
|
||||
return null;
|
||||
|
||||
file.DirectoryItems[i] = directoryItem;
|
||||
// Set the package header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Items
|
||||
|
||||
// Get the directory items offset
|
||||
uint directoryItemsOffset = header.DirectoryOffset;
|
||||
if (directoryItemsOffset < 0 || directoryItemsOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the directory items
|
||||
data.Seek(directoryItemsOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the directory item array
|
||||
file.DirectoryItems = new DirectoryItem[header.DirectoryLength / 64];
|
||||
|
||||
// Try to parse the directory items
|
||||
for (int i = 0; i < file.DirectoryItems.Length; i++)
|
||||
{
|
||||
var directoryItem = data.ReadType<DirectoryItem>();
|
||||
if (directoryItem == null)
|
||||
return null;
|
||||
|
||||
file.DirectoryItems[i] = directoryItem;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,71 +15,75 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// 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 Segments
|
||||
|
||||
// Get the segments
|
||||
long offset = header.FileListOffset;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the segments
|
||||
data.Seek(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++)
|
||||
try
|
||||
{
|
||||
var file = ParseSegment(data, header.FileSegmentSize);
|
||||
if (file == null)
|
||||
continue;
|
||||
// Create a new archive to fill
|
||||
var archive = new Archive();
|
||||
|
||||
archive.Segments[i] = file;
|
||||
#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 Segments
|
||||
|
||||
// Get the segments
|
||||
long offset = header.FileListOffset;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the segments
|
||||
data.Seek(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++)
|
||||
{
|
||||
var file = ParseSegment(data, header.FileSegmentSize);
|
||||
if (file == null)
|
||||
continue;
|
||||
|
||||
archive.Segments[i] = file;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Footer
|
||||
|
||||
// Get the footer offset
|
||||
offset = header.FileListOffset + (header.FileSegmentSize * header.NumberOfFiles);
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the footer
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the footer
|
||||
var footer = data.ReadType<Footer>();
|
||||
if (footer == null)
|
||||
return null;
|
||||
|
||||
// Set the archive footer
|
||||
archive.Footer = footer;
|
||||
|
||||
#endregion
|
||||
|
||||
return archive;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Footer
|
||||
|
||||
// Get the footer offset
|
||||
offset = header.FileListOffset + (header.FileSegmentSize * header.NumberOfFiles);
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
|
||||
// Seek to the footer
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the footer
|
||||
var footer = data.ReadType<Footer>();
|
||||
if (footer == null)
|
||||
return null;
|
||||
|
||||
// Set the archive footer
|
||||
archive.Footer = footer;
|
||||
|
||||
#endregion
|
||||
|
||||
return archive;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -18,36 +18,40 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
var di = new DiscInformation();
|
||||
|
||||
// Read the initial disc information
|
||||
di.DataStructureLength = data.ReadUInt16BigEndian();
|
||||
if (di.DataStructureLength > data.Length)
|
||||
return null;
|
||||
|
||||
di.Reserved0 = data.ReadByteValue();
|
||||
di.Reserved1 = data.ReadByteValue();
|
||||
|
||||
// Create a list for the units
|
||||
var diUnits = new List<DiscInformationUnit>();
|
||||
|
||||
// Loop and read all available units
|
||||
for (int i = 0; i < 32; i++)
|
||||
try
|
||||
{
|
||||
var unit = ParseDiscInformationUnit(data);
|
||||
if (unit == null)
|
||||
continue;
|
||||
var di = new DiscInformation();
|
||||
|
||||
diUnits.Add(unit);
|
||||
// Read the initial disc information
|
||||
di.DataStructureLength = data.ReadUInt16BigEndian();
|
||||
if (di.DataStructureLength > data.Length)
|
||||
return null;
|
||||
|
||||
di.Reserved0 = data.ReadByteValue();
|
||||
di.Reserved1 = data.ReadByteValue();
|
||||
|
||||
// Create a list for the units
|
||||
var diUnits = new List<DiscInformationUnit>();
|
||||
|
||||
// Loop and read all available units
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
var unit = ParseDiscInformationUnit(data);
|
||||
if (unit == null)
|
||||
continue;
|
||||
|
||||
diUnits.Add(unit);
|
||||
}
|
||||
|
||||
// Assign the units and return
|
||||
di.Units = [.. diUnits];
|
||||
return di;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Assign the units and return
|
||||
di.Units = [.. diUnits];
|
||||
return di;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -20,283 +20,287 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
var archive = new Archive();
|
||||
|
||||
#region End of Central Directory Record
|
||||
|
||||
// Find the end of central directory record
|
||||
long eocdrOffset = SearchForEndOfCentralDirectoryRecord(data);
|
||||
if (eocdrOffset < 0 || eocdrOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the end of central directory record
|
||||
data.Seek(eocdrOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read the end of central directory record
|
||||
var eocdr = ParseEndOfCentralDirectoryRecord(data);
|
||||
if (eocdr == null)
|
||||
return null;
|
||||
|
||||
// Assign the end of central directory record
|
||||
archive.EndOfCentralDirectoryRecord = eocdr;
|
||||
|
||||
#endregion
|
||||
|
||||
#region ZIP64 End of Central Directory Locator and Record
|
||||
|
||||
// Set a flag for ZIP64 not found by default
|
||||
bool zip64 = false;
|
||||
|
||||
// Process ZIP64 if any fields are set to max value
|
||||
if (eocdr.DiskNumber == 0xFFFF
|
||||
|| eocdr.StartDiskNumber == 0xFFFF
|
||||
|| eocdr.TotalEntriesOnDisk == 0xFFFF
|
||||
|| eocdr.TotalEntries == 0xFFFF
|
||||
|| eocdr.CentralDirectorySize == 0xFFFFFFFF
|
||||
|| eocdr.CentralDirectoryOffset == 0xFFFFFFFF)
|
||||
try
|
||||
{
|
||||
// Set the ZIP64 flag
|
||||
zip64 = true;
|
||||
var archive = new Archive();
|
||||
|
||||
// Find the ZIP64 end of central directory locator
|
||||
long eocdlOffset = SearchForZIP64EndOfCentralDirectoryLocator(data);
|
||||
if (eocdlOffset < 0 || eocdlOffset >= data.Length)
|
||||
#region End of Central Directory Record
|
||||
|
||||
// Find the end of central directory record
|
||||
long eocdrOffset = SearchForEndOfCentralDirectoryRecord(data);
|
||||
if (eocdrOffset < 0 || eocdrOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the ZIP64 end of central directory locator
|
||||
data.Seek(eocdlOffset, SeekOrigin.Begin);
|
||||
// Seek to the end of central directory record
|
||||
data.Seek(eocdrOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read the ZIP64 end of central directory locator
|
||||
var eocdl64 = data.ReadType<EndOfCentralDirectoryLocator64>();
|
||||
if (eocdl64 == null)
|
||||
// Read the end of central directory record
|
||||
var eocdr = ParseEndOfCentralDirectoryRecord(data);
|
||||
if (eocdr == null)
|
||||
return null;
|
||||
|
||||
// Assign the ZIP64 end of central directory record
|
||||
archive.ZIP64EndOfCentralDirectoryLocator = eocdl64;
|
||||
// Assign the end of central directory record
|
||||
archive.EndOfCentralDirectoryRecord = eocdr;
|
||||
|
||||
// Try to get the ZIP64 end of central directory record offset
|
||||
if ((long)eocdl64.CentralDirectoryOffset < 0 || (long)eocdl64.CentralDirectoryOffset >= data.Length)
|
||||
return null;
|
||||
#endregion
|
||||
|
||||
// Seek to the ZIP64 end of central directory record
|
||||
data.Seek((long)eocdl64.CentralDirectoryOffset, SeekOrigin.Begin);
|
||||
#region ZIP64 End of Central Directory Locator and Record
|
||||
|
||||
// Read the ZIP64 end of central directory record
|
||||
var eocdr64 = ParseEndOfCentralDirectoryRecord64(data);
|
||||
if (eocdr64 == null)
|
||||
return null;
|
||||
// Set a flag for ZIP64 not found by default
|
||||
bool zip64 = false;
|
||||
|
||||
// Assign the ZIP64 end of central directory record
|
||||
archive.ZIP64EndOfCentralDirectoryRecord = eocdr64;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Central Directory Records
|
||||
|
||||
// Try to get the central directory record offset
|
||||
long cdrOffset, cdrSize;
|
||||
if (zip64 && archive.ZIP64EndOfCentralDirectoryRecord != null)
|
||||
{
|
||||
cdrOffset = (long)archive.ZIP64EndOfCentralDirectoryRecord.CentralDirectoryOffset;
|
||||
cdrSize = (long)archive.ZIP64EndOfCentralDirectoryRecord.CentralDirectorySize;
|
||||
}
|
||||
else if (archive.EndOfCentralDirectoryRecord != null)
|
||||
{
|
||||
cdrOffset = archive.EndOfCentralDirectoryRecord.CentralDirectoryOffset;
|
||||
cdrSize = archive.EndOfCentralDirectoryRecord.CentralDirectorySize;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to get the central directory record offset
|
||||
if (cdrOffset < 0 || cdrOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the first central directory record
|
||||
data.Seek(cdrOffset, SeekOrigin.Begin);
|
||||
|
||||
// Cache the current offset
|
||||
long currentOffset = data.Position;
|
||||
|
||||
// Read the central directory records
|
||||
var cdrs = new List<CentralDirectoryFileHeader>();
|
||||
while (data.Position < currentOffset + cdrSize)
|
||||
{
|
||||
// Read the central directory record
|
||||
var cdr = ParseCentralDirectoryFileHeader(data);
|
||||
if (cdr == null)
|
||||
return null;
|
||||
|
||||
// Add the central directory record
|
||||
cdrs.Add(cdr);
|
||||
}
|
||||
|
||||
// Assign the central directory records
|
||||
archive.CentralDirectoryHeaders = [.. cdrs];
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Handle digital signature -- immediately following central directory records
|
||||
|
||||
#region Archive Extra Data Record
|
||||
|
||||
// Find the archive extra data record
|
||||
long aedrOffset = SearchForArchiveExtraDataRecord(data, cdrOffset);
|
||||
if (aedrOffset >= 0 && aedrOffset < data.Length)
|
||||
{
|
||||
// Seek to the archive extra data record
|
||||
data.Seek(aedrOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read the archive extra data record
|
||||
var aedr = ParseArchiveExtraDataRecord(data);
|
||||
if (aedr == null)
|
||||
return null;
|
||||
|
||||
// Assign the archive extra data record
|
||||
archive.ArchiveExtraDataRecord = aedr;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Local File
|
||||
|
||||
// Setup all of the collections
|
||||
var localFileHeaders = new List<LocalFileHeader>();
|
||||
var encryptionHeaders = new List<byte[]?>();
|
||||
var fileData = new List<byte[]>(); // TODO: Should this data be read here?
|
||||
var dataDescriptors = new List<DataDescriptor?>();
|
||||
var zip64DataDescriptors = new List<DataDescriptor64?>();
|
||||
|
||||
// Read the local file headers
|
||||
for (int i = 0; i < archive.CentralDirectoryHeaders.Length; i++)
|
||||
{
|
||||
var header = archive.CentralDirectoryHeaders[i];
|
||||
|
||||
// Get the local file header offset
|
||||
long headerOffset = header.RelativeOffsetOfLocalHeader;
|
||||
if (headerOffset == 0xFFFFFFFF && header.ExtraField != null)
|
||||
// Process ZIP64 if any fields are set to max value
|
||||
if (eocdr.DiskNumber == 0xFFFF
|
||||
|| eocdr.StartDiskNumber == 0xFFFF
|
||||
|| eocdr.TotalEntriesOnDisk == 0xFFFF
|
||||
|| eocdr.TotalEntries == 0xFFFF
|
||||
|| eocdr.CentralDirectorySize == 0xFFFFFFFF
|
||||
|| eocdr.CentralDirectoryOffset == 0xFFFFFFFF)
|
||||
{
|
||||
// TODO: Parse into a proper structure instead of this
|
||||
byte[] extraData = header.ExtraField;
|
||||
if (BitConverter.ToUInt16(extraData, 0) == 0x0001)
|
||||
headerOffset = BitConverter.ToInt64(extraData, 4);
|
||||
// Set the ZIP64 flag
|
||||
zip64 = true;
|
||||
|
||||
// Find the ZIP64 end of central directory locator
|
||||
long eocdlOffset = SearchForZIP64EndOfCentralDirectoryLocator(data);
|
||||
if (eocdlOffset < 0 || eocdlOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the ZIP64 end of central directory locator
|
||||
data.Seek(eocdlOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read the ZIP64 end of central directory locator
|
||||
var eocdl64 = data.ReadType<EndOfCentralDirectoryLocator64>();
|
||||
if (eocdl64 == null)
|
||||
return null;
|
||||
|
||||
// Assign the ZIP64 end of central directory record
|
||||
archive.ZIP64EndOfCentralDirectoryLocator = eocdl64;
|
||||
|
||||
// Try to get the ZIP64 end of central directory record offset
|
||||
if ((long)eocdl64.CentralDirectoryOffset < 0 || (long)eocdl64.CentralDirectoryOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the ZIP64 end of central directory record
|
||||
data.Seek((long)eocdl64.CentralDirectoryOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read the ZIP64 end of central directory record
|
||||
var eocdr64 = ParseEndOfCentralDirectoryRecord64(data);
|
||||
if (eocdr64 == null)
|
||||
return null;
|
||||
|
||||
// Assign the ZIP64 end of central directory record
|
||||
archive.ZIP64EndOfCentralDirectoryRecord = eocdr64;
|
||||
}
|
||||
|
||||
if (headerOffset < 0 || headerOffset >= data.Length)
|
||||
return null;
|
||||
#endregion
|
||||
|
||||
// Seek to the local file header
|
||||
data.Seek(headerOffset, SeekOrigin.Begin);
|
||||
#region Central Directory Records
|
||||
|
||||
// Try to parse the local header
|
||||
var localFileHeader = ParseLocalFileHeader(data);
|
||||
if (localFileHeader == null)
|
||||
// Try to get the central directory record offset
|
||||
long cdrOffset, cdrSize;
|
||||
if (zip64 && archive.ZIP64EndOfCentralDirectoryRecord != null)
|
||||
{
|
||||
// Add a placeholder null item
|
||||
localFileHeaders.Add(new LocalFileHeader());
|
||||
encryptionHeaders.Add(null);
|
||||
fileData.Add([]);
|
||||
dataDescriptors.Add(null);
|
||||
zip64DataDescriptors.Add(null);
|
||||
continue;
|
||||
cdrOffset = (long)archive.ZIP64EndOfCentralDirectoryRecord.CentralDirectoryOffset;
|
||||
cdrSize = (long)archive.ZIP64EndOfCentralDirectoryRecord.CentralDirectorySize;
|
||||
}
|
||||
else if (archive.EndOfCentralDirectoryRecord != null)
|
||||
{
|
||||
cdrOffset = archive.EndOfCentralDirectoryRecord.CentralDirectoryOffset;
|
||||
cdrSize = archive.EndOfCentralDirectoryRecord.CentralDirectorySize;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add the local file header
|
||||
localFileHeaders.Add(localFileHeader);
|
||||
// Try to get the central directory record offset
|
||||
if (cdrOffset < 0 || cdrOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Only read the encryption header if necessary
|
||||
// Seek to the first central directory record
|
||||
data.Seek(cdrOffset, SeekOrigin.Begin);
|
||||
|
||||
// Cache the current offset
|
||||
long currentOffset = data.Position;
|
||||
|
||||
// Read the central directory records
|
||||
var cdrs = new List<CentralDirectoryFileHeader>();
|
||||
while (data.Position < currentOffset + cdrSize)
|
||||
{
|
||||
// Read the central directory record
|
||||
var cdr = ParseCentralDirectoryFileHeader(data);
|
||||
if (cdr == null)
|
||||
return null;
|
||||
|
||||
// Add the central directory record
|
||||
cdrs.Add(cdr);
|
||||
}
|
||||
|
||||
// Assign the central directory records
|
||||
archive.CentralDirectoryHeaders = [.. cdrs];
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Handle digital signature -- immediately following central directory records
|
||||
|
||||
#region Archive Extra Data Record
|
||||
|
||||
// Find the archive extra data record
|
||||
long aedrOffset = SearchForArchiveExtraDataRecord(data, cdrOffset);
|
||||
if (aedrOffset >= 0 && aedrOffset < data.Length)
|
||||
{
|
||||
// Seek to the archive extra data record
|
||||
data.Seek(aedrOffset, SeekOrigin.Begin);
|
||||
|
||||
// Read the archive extra data record
|
||||
var aedr = ParseArchiveExtraDataRecord(data);
|
||||
if (aedr == null)
|
||||
return null;
|
||||
|
||||
// Assign the archive extra data record
|
||||
archive.ArchiveExtraDataRecord = aedr;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Local File
|
||||
|
||||
// Setup all of the collections
|
||||
var localFileHeaders = new List<LocalFileHeader>();
|
||||
var encryptionHeaders = new List<byte[]?>();
|
||||
var fileData = new List<byte[]>(); // TODO: Should this data be read here?
|
||||
var dataDescriptors = new List<DataDescriptor?>();
|
||||
var zip64DataDescriptors = new List<DataDescriptor64?>();
|
||||
|
||||
// Read the local file headers
|
||||
for (int i = 0; i < archive.CentralDirectoryHeaders.Length; i++)
|
||||
{
|
||||
var header = archive.CentralDirectoryHeaders[i];
|
||||
|
||||
// Get the local file header offset
|
||||
long headerOffset = header.RelativeOffsetOfLocalHeader;
|
||||
if (headerOffset == 0xFFFFFFFF && header.ExtraField != null)
|
||||
{
|
||||
// TODO: Parse into a proper structure instead of this
|
||||
byte[] extraData = header.ExtraField;
|
||||
if (BitConverter.ToUInt16(extraData, 0) == 0x0001)
|
||||
headerOffset = BitConverter.ToInt64(extraData, 4);
|
||||
}
|
||||
|
||||
if (headerOffset < 0 || headerOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the local file header
|
||||
data.Seek(headerOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the local header
|
||||
var localFileHeader = ParseLocalFileHeader(data);
|
||||
if (localFileHeader == null)
|
||||
{
|
||||
// Add a placeholder null item
|
||||
localFileHeaders.Add(new LocalFileHeader());
|
||||
encryptionHeaders.Add(null);
|
||||
fileData.Add([]);
|
||||
dataDescriptors.Add(null);
|
||||
zip64DataDescriptors.Add(null);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add the local file header
|
||||
localFileHeaders.Add(localFileHeader);
|
||||
|
||||
// Only read the encryption header if necessary
|
||||
#if NET20 || NET35
|
||||
if ((header.Flags & GeneralPurposeBitFlags.FileEncrypted) != 0)
|
||||
#else
|
||||
if (header.Flags.HasFlag(GeneralPurposeBitFlags.FileEncrypted))
|
||||
if (header.Flags.HasFlag(GeneralPurposeBitFlags.FileEncrypted))
|
||||
#endif
|
||||
{
|
||||
// Try to read the encryption header data -- TODO: Verify amount to read
|
||||
byte[] encryptionHeader = data.ReadBytes(12);
|
||||
if (encryptionHeader.Length != 12)
|
||||
return null;
|
||||
|
||||
// Add the encryption header
|
||||
encryptionHeaders.Add(encryptionHeader);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the null encryption header
|
||||
encryptionHeaders.Add(null);
|
||||
}
|
||||
|
||||
// Try to read the file data
|
||||
byte[] fileDatum = data.ReadBytes((int)header.CompressedSize);
|
||||
if (fileDatum.Length < header.CompressedSize)
|
||||
return null;
|
||||
|
||||
// Add the file data
|
||||
fileData.Add(fileDatum);
|
||||
|
||||
// Only read the data descriptor if necessary
|
||||
#if NET20 || NET35
|
||||
if ((header.Flags & GeneralPurposeBitFlags.NoCRC) != 0)
|
||||
#else
|
||||
if (header.Flags.HasFlag(GeneralPurposeBitFlags.NoCRC))
|
||||
#endif
|
||||
{
|
||||
// Select the data descriptor that is being used
|
||||
if (zip64)
|
||||
{
|
||||
// Try to parse the data descriptor
|
||||
var dataDescriptor64 = ParseDataDescriptor64(data);
|
||||
if (dataDescriptor64 == null)
|
||||
// Try to read the encryption header data -- TODO: Verify amount to read
|
||||
byte[] encryptionHeader = data.ReadBytes(12);
|
||||
if (encryptionHeader.Length != 12)
|
||||
return null;
|
||||
|
||||
// Add the data descriptor
|
||||
dataDescriptors.Add(null);
|
||||
zip64DataDescriptors.Add(dataDescriptor64);
|
||||
// Add the encryption header
|
||||
encryptionHeaders.Add(encryptionHeader);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try to parse the data descriptor
|
||||
var dataDescriptor = ParseDataDescriptor(data);
|
||||
if (dataDescriptor == null)
|
||||
return null;
|
||||
// Add the null encryption header
|
||||
encryptionHeaders.Add(null);
|
||||
}
|
||||
|
||||
// Add the data descriptor
|
||||
dataDescriptors.Add(dataDescriptor);
|
||||
// Try to read the file data
|
||||
byte[] fileDatum = data.ReadBytes((int)header.CompressedSize);
|
||||
if (fileDatum.Length < header.CompressedSize)
|
||||
return null;
|
||||
|
||||
// Add the file data
|
||||
fileData.Add(fileDatum);
|
||||
|
||||
// Only read the data descriptor if necessary
|
||||
#if NET20 || NET35
|
||||
if ((header.Flags & GeneralPurposeBitFlags.NoCRC) != 0)
|
||||
#else
|
||||
if (header.Flags.HasFlag(GeneralPurposeBitFlags.NoCRC))
|
||||
#endif
|
||||
{
|
||||
// Select the data descriptor that is being used
|
||||
if (zip64)
|
||||
{
|
||||
// Try to parse the data descriptor
|
||||
var dataDescriptor64 = ParseDataDescriptor64(data);
|
||||
if (dataDescriptor64 == null)
|
||||
return null;
|
||||
|
||||
// Add the data descriptor
|
||||
dataDescriptors.Add(null);
|
||||
zip64DataDescriptors.Add(dataDescriptor64);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try to parse the data descriptor
|
||||
var dataDescriptor = ParseDataDescriptor(data);
|
||||
if (dataDescriptor == null)
|
||||
return null;
|
||||
|
||||
// Add the data descriptor
|
||||
dataDescriptors.Add(dataDescriptor);
|
||||
zip64DataDescriptors.Add(null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the null data descriptor
|
||||
dataDescriptors.Add(null);
|
||||
zip64DataDescriptors.Add(null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the null data descriptor
|
||||
dataDescriptors.Add(null);
|
||||
zip64DataDescriptors.Add(null);
|
||||
}
|
||||
|
||||
// Assign the local file headers
|
||||
archive.LocalFileHeaders = [.. localFileHeaders];
|
||||
|
||||
// Assign the encryption headers
|
||||
archive.EncryptionHeaders = [.. encryptionHeaders];
|
||||
|
||||
// Assign the file data
|
||||
archive.FileData = [.. fileData];
|
||||
|
||||
// Assign the data descriptors
|
||||
archive.DataDescriptors = [.. dataDescriptors];
|
||||
archive.ZIP64DataDescriptors = [.. zip64DataDescriptors];
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Handle archive decryption header
|
||||
|
||||
return archive;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Assign the local file headers
|
||||
archive.LocalFileHeaders = [.. localFileHeaders];
|
||||
|
||||
// Assign the encryption headers
|
||||
archive.EncryptionHeaders = [.. encryptionHeaders];
|
||||
|
||||
// Assign the file data
|
||||
archive.FileData = [.. fileData];
|
||||
|
||||
// Assign the data descriptors
|
||||
archive.DataDescriptors = [.. dataDescriptors];
|
||||
archive.ZIP64DataDescriptors = [.. zip64DataDescriptors];
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Handle archive decryption header
|
||||
|
||||
return archive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace SabreTools.Serialization.Deserializers
|
||||
var deserializer = new PlayJAudio();
|
||||
return deserializer.Deserialize(data, adjust);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AudioFile? Deserialize(Stream? data)
|
||||
=> Deserialize(data, 0);
|
||||
@@ -27,147 +27,147 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !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 audio file to fill
|
||||
var audioFile = new AudioFile();
|
||||
|
||||
#region Audio Header
|
||||
|
||||
// Try to parse the audio header
|
||||
var audioHeader = ParseAudioHeader(data);
|
||||
if (audioHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the audio header
|
||||
audioFile.Header = audioHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unknown Block 1
|
||||
|
||||
uint unknownOffset1 = (audioHeader.Version == 0x00000000)
|
||||
? (audioHeader as AudioHeaderV1)?.UnknownOffset1 ?? 0
|
||||
: ((audioHeader as AudioHeaderV2)?.UnknownOffset1 ?? 0) + 0x54;
|
||||
|
||||
// If we have an unknown block 1 offset
|
||||
if (unknownOffset1 > 0)
|
||||
try
|
||||
{
|
||||
// Get the unknown block 1 offset
|
||||
long offset = unknownOffset1 + adjust;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
// Create a new audio file to fill
|
||||
var audioFile = new AudioFile();
|
||||
|
||||
#region Audio Header
|
||||
|
||||
// Try to parse the audio header
|
||||
var audioHeader = ParseAudioHeader(data);
|
||||
if (audioHeader == null)
|
||||
return null;
|
||||
|
||||
// Seek to the unknown block 1
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
}
|
||||
// Set the audio header
|
||||
audioFile.Header = audioHeader;
|
||||
|
||||
// Try to parse the unknown block 1
|
||||
var unknownBlock1 = ParseUnknownBlock1(data);
|
||||
if (unknownBlock1 == null)
|
||||
return null;
|
||||
#endregion
|
||||
|
||||
// Set the unknown block 1
|
||||
audioFile.UnknownBlock1 = unknownBlock1;
|
||||
#region Unknown Block 1
|
||||
|
||||
#endregion
|
||||
uint unknownOffset1 = (audioHeader.Version == 0x00000000)
|
||||
? (audioHeader as AudioHeaderV1)?.UnknownOffset1 ?? 0
|
||||
: ((audioHeader as AudioHeaderV2)?.UnknownOffset1 ?? 0) + 0x54;
|
||||
|
||||
#region V1 Only
|
||||
|
||||
// If we have a V1 file
|
||||
if (audioHeader.Version == 0x00000000)
|
||||
{
|
||||
#region Unknown Value 2
|
||||
|
||||
// Get the V1 unknown offset 2
|
||||
uint? unknownOffset2 = (audioHeader as AudioHeaderV1)?.UnknownOffset2;
|
||||
|
||||
// If we have an unknown value 2 offset
|
||||
if (unknownOffset2 != null && unknownOffset2 > 0)
|
||||
// If we have an unknown block 1 offset
|
||||
if (unknownOffset1 > 0)
|
||||
{
|
||||
// Get the unknown value 2 offset
|
||||
long offset = unknownOffset2.Value + adjust;
|
||||
// Get the unknown block 1 offset
|
||||
long offset = unknownOffset1 + adjust;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the unknown value 2
|
||||
// Seek to the unknown block 1
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
// Set the unknown value 2
|
||||
audioFile.UnknownValue2 = data.ReadUInt32();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unknown Block 3
|
||||
|
||||
// Get the V1 unknown offset 3
|
||||
uint? unknownOffset3 = (audioHeader as AudioHeaderV1)?.UnknownOffset3;
|
||||
|
||||
// If we have an unknown block 3 offset
|
||||
if (unknownOffset3 != null && unknownOffset3 > 0)
|
||||
{
|
||||
// Get the unknown block 3 offset
|
||||
long offset = unknownOffset3.Value + adjust;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the unknown block 3
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
// Try to parse the unknown block 3
|
||||
var unknownBlock3 = ParseUnknownBlock3(data);
|
||||
if (unknownBlock3 == null)
|
||||
// Try to parse the unknown block 1
|
||||
var unknownBlock1 = ParseUnknownBlock1(data);
|
||||
if (unknownBlock1 == null)
|
||||
return null;
|
||||
|
||||
// Set the unknown block 3
|
||||
audioFile.UnknownBlock3 = unknownBlock3;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region V2 Only
|
||||
|
||||
// If we have a V2 file
|
||||
if (audioHeader.Version == 0x0000000A)
|
||||
{
|
||||
#region Data Files Count
|
||||
|
||||
// Set the data files count
|
||||
audioFile.DataFilesCount = data.ReadUInt32();
|
||||
// Set the unknown block 1
|
||||
audioFile.UnknownBlock1 = unknownBlock1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data Files
|
||||
#region V1 Only
|
||||
|
||||
// Create the data files array
|
||||
audioFile.DataFiles = new DataFile[audioFile.DataFilesCount];
|
||||
|
||||
// Try to parse the data files
|
||||
for (int i = 0; i < audioFile.DataFiles.Length; i++)
|
||||
// If we have a V1 file
|
||||
if (audioHeader.Version == 0x00000000)
|
||||
{
|
||||
var dataFile = ParseDataFile(data);
|
||||
if (dataFile == null)
|
||||
#region Unknown Value 2
|
||||
|
||||
// Get the V1 unknown offset 2
|
||||
uint? unknownOffset2 = (audioHeader as AudioHeaderV1)?.UnknownOffset2;
|
||||
|
||||
// If we have an unknown value 2 offset
|
||||
if (unknownOffset2 != null && unknownOffset2 > 0)
|
||||
{
|
||||
// Get the unknown value 2 offset
|
||||
long offset = unknownOffset2.Value + adjust;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the unknown value 2
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
// Set the unknown value 2
|
||||
audioFile.UnknownValue2 = data.ReadUInt32();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unknown Block 3
|
||||
|
||||
// Get the V1 unknown offset 3
|
||||
uint? unknownOffset3 = (audioHeader as AudioHeaderV1)?.UnknownOffset3;
|
||||
|
||||
// If we have an unknown block 3 offset
|
||||
if (unknownOffset3 != null && unknownOffset3 > 0)
|
||||
{
|
||||
// Get the unknown block 3 offset
|
||||
long offset = unknownOffset3.Value + adjust;
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the unknown block 3
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
// Try to parse the unknown block 3
|
||||
var unknownBlock3 = ParseUnknownBlock3(data);
|
||||
if (unknownBlock3 == null)
|
||||
return null;
|
||||
|
||||
audioFile.DataFiles[i] = dataFile;
|
||||
// Set the unknown block 3
|
||||
audioFile.UnknownBlock3 = unknownBlock3;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region V2 Only
|
||||
|
||||
// If we have a V2 file
|
||||
if (audioHeader.Version == 0x0000000A)
|
||||
{
|
||||
#region Data Files Count
|
||||
|
||||
// Set the data files count
|
||||
audioFile.DataFilesCount = data.ReadUInt32();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data Files
|
||||
|
||||
// Create the data files array
|
||||
audioFile.DataFiles = new DataFile[audioFile.DataFilesCount];
|
||||
|
||||
// Try to parse the data files
|
||||
for (int i = 0; i < audioFile.DataFiles.Length; i++)
|
||||
{
|
||||
var dataFile = ParseDataFile(data);
|
||||
if (dataFile == null)
|
||||
return null;
|
||||
|
||||
audioFile.DataFiles[i] = dataFile;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return audioFile;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return audioFile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -13,44 +13,48 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new playlist to fill
|
||||
var playlist = new Playlist();
|
||||
|
||||
#region Playlist Header
|
||||
|
||||
// Try to parse the playlist header
|
||||
var playlistHeader = ParsePlaylistHeader(data);
|
||||
if (playlistHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the playlist header
|
||||
playlist.Header = playlistHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Audio Files
|
||||
|
||||
// Create the audio files array
|
||||
playlist.AudioFiles = new AudioFile[playlistHeader.TrackCount];
|
||||
|
||||
// Try to parse the audio files
|
||||
for (int i = 0; i < playlist.AudioFiles.Length; i++)
|
||||
try
|
||||
{
|
||||
long currentOffset = data.Position;
|
||||
var entryHeader = PlayJAudio.DeserializeStream(data, currentOffset);
|
||||
if (entryHeader == null)
|
||||
continue;
|
||||
// Create a new playlist to fill
|
||||
var playlist = new Playlist();
|
||||
|
||||
playlist.AudioFiles[i] = entryHeader;
|
||||
#region Playlist Header
|
||||
|
||||
// Try to parse the playlist header
|
||||
var playlistHeader = ParsePlaylistHeader(data);
|
||||
if (playlistHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the playlist header
|
||||
playlist.Header = playlistHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Audio Files
|
||||
|
||||
// Create the audio files array
|
||||
playlist.AudioFiles = new AudioFile[playlistHeader.TrackCount];
|
||||
|
||||
// Try to parse the audio files
|
||||
for (int i = 0; i < playlist.AudioFiles.Length; i++)
|
||||
{
|
||||
long currentOffset = data.Position;
|
||||
var entryHeader = PlayJAudio.DeserializeStream(data, currentOffset);
|
||||
if (entryHeader == null)
|
||||
continue;
|
||||
|
||||
playlist.AudioFiles[i] = entryHeader;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return playlist;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return playlist;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -20,269 +20,273 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
try
|
||||
{
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Create a new executable to fill
|
||||
var executable = new Executable();
|
||||
|
||||
#region MS-DOS Stub
|
||||
|
||||
// Parse the MS-DOS stub
|
||||
var stub = new MSDOS().Deserialize(data);
|
||||
if (stub?.Header == null || stub.Header.NewExeHeaderAddr == 0)
|
||||
return null;
|
||||
|
||||
// Set the MS-DOS stub
|
||||
executable.Stub = stub;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Signature
|
||||
|
||||
data.Seek(initialOffset + stub.Header.NewExeHeaderAddr, SeekOrigin.Begin);
|
||||
byte[] signature = data.ReadBytes(4);
|
||||
executable.Signature = Encoding.ASCII.GetString(signature);
|
||||
if (executable.Signature != SignatureString)
|
||||
return null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region COFF File Header
|
||||
|
||||
// Try to parse the COFF file header
|
||||
var coffFileHeader = data.ReadType<COFFFileHeader>();
|
||||
if (coffFileHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the COFF file header
|
||||
executable.COFFFileHeader = coffFileHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Optional Header
|
||||
|
||||
// Try to parse the optional header
|
||||
var optionalHeader = ParseOptionalHeader(data, coffFileHeader.SizeOfOptionalHeader);
|
||||
if (optionalHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the optional header
|
||||
executable.OptionalHeader = optionalHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Section Table
|
||||
|
||||
// Try to parse the section table
|
||||
var sectionTable = ParseSectionTable(data, coffFileHeader.NumberOfSections);
|
||||
if (sectionTable == null)
|
||||
return null;
|
||||
|
||||
// Set the section table
|
||||
executable.SectionTable = sectionTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region COFF Symbol Table and COFF String Table
|
||||
|
||||
// TODO: Validate that this is correct with an "old" PE
|
||||
if (coffFileHeader.PointerToSymbolTable.ConvertVirtualAddress(executable.SectionTable) != 0)
|
||||
{
|
||||
// If the offset for the COFF symbol table doesn't exist
|
||||
int symbolTableAddress = initialOffset
|
||||
+ (int)coffFileHeader.PointerToSymbolTable.ConvertVirtualAddress(executable.SectionTable);
|
||||
if (symbolTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the COFF symbol table
|
||||
data.Seek(symbolTableAddress, SeekOrigin.Begin);
|
||||
var coffSymbolTable = ParseCOFFSymbolTable(data, coffFileHeader.NumberOfSymbols);
|
||||
if (coffSymbolTable == null)
|
||||
return null;
|
||||
|
||||
// Set the COFF symbol table
|
||||
executable.COFFSymbolTable = coffSymbolTable;
|
||||
|
||||
// Try to parse the COFF string table
|
||||
var coffStringTable = ParseCOFFStringTable(data);
|
||||
if (coffStringTable == null)
|
||||
return null;
|
||||
|
||||
// Set the COFF string table
|
||||
executable.COFFStringTable = coffStringTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Attribute Certificate Table
|
||||
|
||||
if (optionalHeader.CertificateTable != null && optionalHeader.CertificateTable.VirtualAddress != 0)
|
||||
{
|
||||
// If the offset for the attribute certificate table doesn't exist
|
||||
int certificateTableAddress = initialOffset
|
||||
+ (int)optionalHeader.CertificateTable.VirtualAddress;
|
||||
if (certificateTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the attribute certificate table
|
||||
data.Seek(certificateTableAddress, SeekOrigin.Begin);
|
||||
int endOffset = (int)(certificateTableAddress + optionalHeader.CertificateTable.Size);
|
||||
var attributeCertificateTable = ParseAttributeCertificateTable(data, endOffset);
|
||||
if (attributeCertificateTable == null)
|
||||
return null;
|
||||
|
||||
// Set the attribute certificate table
|
||||
executable.AttributeCertificateTable = attributeCertificateTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delay-Load Directory Table
|
||||
|
||||
if (optionalHeader.DelayImportDescriptor != null && optionalHeader.DelayImportDescriptor.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0)
|
||||
{
|
||||
// If the offset for the delay-load directory table doesn't exist
|
||||
int delayLoadDirectoryTableAddress = initialOffset
|
||||
+ (int)optionalHeader.DelayImportDescriptor.VirtualAddress.ConvertVirtualAddress(executable.SectionTable);
|
||||
if (delayLoadDirectoryTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the delay-load directory table
|
||||
data.Seek(delayLoadDirectoryTableAddress, SeekOrigin.Begin);
|
||||
var delayLoadDirectoryTable = data.ReadType<DelayLoadDirectoryTable>();
|
||||
if (delayLoadDirectoryTable == null)
|
||||
return null;
|
||||
|
||||
// Set the delay-load directory table
|
||||
executable.DelayLoadDirectoryTable = delayLoadDirectoryTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Base Relocation Table
|
||||
|
||||
// Should also be in a '.reloc' section
|
||||
if (optionalHeader.BaseRelocationTable != null && optionalHeader.BaseRelocationTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0)
|
||||
{
|
||||
// If the offset for the base relocation table doesn't exist
|
||||
int baseRelocationTableAddress = initialOffset
|
||||
+ (int)optionalHeader.BaseRelocationTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable);
|
||||
if (baseRelocationTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the base relocation table
|
||||
data.Seek(baseRelocationTableAddress, SeekOrigin.Begin);
|
||||
int endOffset = (int)(baseRelocationTableAddress + optionalHeader.BaseRelocationTable.Size);
|
||||
var baseRelocationTable = ParseBaseRelocationTable(data, endOffset, executable.SectionTable);
|
||||
if (baseRelocationTable == null)
|
||||
return null;
|
||||
|
||||
// Set the base relocation table
|
||||
executable.BaseRelocationTable = baseRelocationTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Debug Table
|
||||
|
||||
// Should also be in a '.debug' section
|
||||
if (optionalHeader.Debug != null && optionalHeader.Debug.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0)
|
||||
{
|
||||
// If the offset for the debug table doesn't exist
|
||||
int debugTableAddress = initialOffset
|
||||
+ (int)optionalHeader.Debug.VirtualAddress.ConvertVirtualAddress(executable.SectionTable);
|
||||
if (debugTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the debug table
|
||||
data.Seek(debugTableAddress, SeekOrigin.Begin);
|
||||
int endOffset = (int)(debugTableAddress + optionalHeader.Debug.Size);
|
||||
var debugTable = ParseDebugTable(data, endOffset);
|
||||
if (debugTable == null)
|
||||
return null;
|
||||
|
||||
// Set the debug table
|
||||
executable.DebugTable = debugTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Export Table
|
||||
|
||||
// Should also be in a '.edata' section
|
||||
if (optionalHeader.ExportTable != null && optionalHeader.ExportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0)
|
||||
{
|
||||
// If the offset for the export table doesn't exist
|
||||
int exportTableAddress = initialOffset
|
||||
+ (int)optionalHeader.ExportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable);
|
||||
if (exportTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the export table
|
||||
data.Seek(exportTableAddress, SeekOrigin.Begin);
|
||||
var exportTable = ParseExportTable(data, executable.SectionTable);
|
||||
if (exportTable == null)
|
||||
return null;
|
||||
|
||||
// Set the export table
|
||||
executable.ExportTable = exportTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Import Table
|
||||
|
||||
// Should also be in a '.idata' section
|
||||
if (optionalHeader.ImportTable != null && optionalHeader.ImportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0)
|
||||
{
|
||||
// If the offset for the import table doesn't exist
|
||||
int importTableAddress = initialOffset
|
||||
+ (int)optionalHeader.ImportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable);
|
||||
if (importTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the import table
|
||||
data.Seek(importTableAddress, SeekOrigin.Begin);
|
||||
var importTable = ParseImportTable(data, optionalHeader.Magic, executable.SectionTable);
|
||||
if (importTable == null)
|
||||
return null;
|
||||
|
||||
// Set the import table
|
||||
executable.ImportTable = importTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resource Directory Table
|
||||
|
||||
// Should also be in a '.rsrc' section
|
||||
if (optionalHeader.ResourceTable != null && optionalHeader.ResourceTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0)
|
||||
{
|
||||
// If the offset for the resource directory table doesn't exist
|
||||
int resourceTableAddress = initialOffset
|
||||
+ (int)optionalHeader.ResourceTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable);
|
||||
if (resourceTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the resource directory table
|
||||
data.Seek(resourceTableAddress, SeekOrigin.Begin);
|
||||
var resourceDirectoryTable = ParseResourceDirectoryTable(data, data.Position, executable.SectionTable, true);
|
||||
if (resourceDirectoryTable == null)
|
||||
return null;
|
||||
|
||||
// Set the resource directory table
|
||||
executable.ResourceDirectoryTable = resourceDirectoryTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Finish implementing PE parsing
|
||||
return executable;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Create a new executable to fill
|
||||
var executable = new Executable();
|
||||
|
||||
#region MS-DOS Stub
|
||||
|
||||
// Parse the MS-DOS stub
|
||||
var stub = new MSDOS().Deserialize(data);
|
||||
if (stub?.Header == null || stub.Header.NewExeHeaderAddr == 0)
|
||||
return null;
|
||||
|
||||
// Set the MS-DOS stub
|
||||
executable.Stub = stub;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Signature
|
||||
|
||||
data.Seek(initialOffset + stub.Header.NewExeHeaderAddr, SeekOrigin.Begin);
|
||||
byte[] signature = data.ReadBytes(4);
|
||||
executable.Signature = Encoding.ASCII.GetString(signature);
|
||||
if (executable.Signature != SignatureString)
|
||||
return null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region COFF File Header
|
||||
|
||||
// Try to parse the COFF file header
|
||||
var coffFileHeader = data.ReadType<COFFFileHeader>();
|
||||
if (coffFileHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the COFF file header
|
||||
executable.COFFFileHeader = coffFileHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Optional Header
|
||||
|
||||
// Try to parse the optional header
|
||||
var optionalHeader = ParseOptionalHeader(data, coffFileHeader.SizeOfOptionalHeader);
|
||||
if (optionalHeader == null)
|
||||
return null;
|
||||
|
||||
// Set the optional header
|
||||
executable.OptionalHeader = optionalHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Section Table
|
||||
|
||||
// Try to parse the section table
|
||||
var sectionTable = ParseSectionTable(data, coffFileHeader.NumberOfSections);
|
||||
if (sectionTable == null)
|
||||
return null;
|
||||
|
||||
// Set the section table
|
||||
executable.SectionTable = sectionTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region COFF Symbol Table and COFF String Table
|
||||
|
||||
// TODO: Validate that this is correct with an "old" PE
|
||||
if (coffFileHeader.PointerToSymbolTable.ConvertVirtualAddress(executable.SectionTable) != 0)
|
||||
{
|
||||
// If the offset for the COFF symbol table doesn't exist
|
||||
int symbolTableAddress = initialOffset
|
||||
+ (int)coffFileHeader.PointerToSymbolTable.ConvertVirtualAddress(executable.SectionTable);
|
||||
if (symbolTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the COFF symbol table
|
||||
data.Seek(symbolTableAddress, SeekOrigin.Begin);
|
||||
var coffSymbolTable = ParseCOFFSymbolTable(data, coffFileHeader.NumberOfSymbols);
|
||||
if (coffSymbolTable == null)
|
||||
return null;
|
||||
|
||||
// Set the COFF symbol table
|
||||
executable.COFFSymbolTable = coffSymbolTable;
|
||||
|
||||
// Try to parse the COFF string table
|
||||
var coffStringTable = ParseCOFFStringTable(data);
|
||||
if (coffStringTable == null)
|
||||
return null;
|
||||
|
||||
// Set the COFF string table
|
||||
executable.COFFStringTable = coffStringTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Attribute Certificate Table
|
||||
|
||||
if (optionalHeader.CertificateTable != null && optionalHeader.CertificateTable.VirtualAddress != 0)
|
||||
{
|
||||
// If the offset for the attribute certificate table doesn't exist
|
||||
int certificateTableAddress = initialOffset
|
||||
+ (int)optionalHeader.CertificateTable.VirtualAddress;
|
||||
if (certificateTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the attribute certificate table
|
||||
data.Seek(certificateTableAddress, SeekOrigin.Begin);
|
||||
int endOffset = (int)(certificateTableAddress + optionalHeader.CertificateTable.Size);
|
||||
var attributeCertificateTable = ParseAttributeCertificateTable(data, endOffset);
|
||||
if (attributeCertificateTable == null)
|
||||
return null;
|
||||
|
||||
// Set the attribute certificate table
|
||||
executable.AttributeCertificateTable = attributeCertificateTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delay-Load Directory Table
|
||||
|
||||
if (optionalHeader.DelayImportDescriptor != null && optionalHeader.DelayImportDescriptor.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0)
|
||||
{
|
||||
// If the offset for the delay-load directory table doesn't exist
|
||||
int delayLoadDirectoryTableAddress = initialOffset
|
||||
+ (int)optionalHeader.DelayImportDescriptor.VirtualAddress.ConvertVirtualAddress(executable.SectionTable);
|
||||
if (delayLoadDirectoryTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the delay-load directory table
|
||||
data.Seek(delayLoadDirectoryTableAddress, SeekOrigin.Begin);
|
||||
var delayLoadDirectoryTable = data.ReadType<DelayLoadDirectoryTable>();
|
||||
if (delayLoadDirectoryTable == null)
|
||||
return null;
|
||||
|
||||
// Set the delay-load directory table
|
||||
executable.DelayLoadDirectoryTable = delayLoadDirectoryTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Base Relocation Table
|
||||
|
||||
// Should also be in a '.reloc' section
|
||||
if (optionalHeader.BaseRelocationTable != null && optionalHeader.BaseRelocationTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0)
|
||||
{
|
||||
// If the offset for the base relocation table doesn't exist
|
||||
int baseRelocationTableAddress = initialOffset
|
||||
+ (int)optionalHeader.BaseRelocationTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable);
|
||||
if (baseRelocationTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the base relocation table
|
||||
data.Seek(baseRelocationTableAddress, SeekOrigin.Begin);
|
||||
int endOffset = (int)(baseRelocationTableAddress + optionalHeader.BaseRelocationTable.Size);
|
||||
var baseRelocationTable = ParseBaseRelocationTable(data, endOffset, executable.SectionTable);
|
||||
if (baseRelocationTable == null)
|
||||
return null;
|
||||
|
||||
// Set the base relocation table
|
||||
executable.BaseRelocationTable = baseRelocationTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Debug Table
|
||||
|
||||
// Should also be in a '.debug' section
|
||||
if (optionalHeader.Debug != null && optionalHeader.Debug.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0)
|
||||
{
|
||||
// If the offset for the debug table doesn't exist
|
||||
int debugTableAddress = initialOffset
|
||||
+ (int)optionalHeader.Debug.VirtualAddress.ConvertVirtualAddress(executable.SectionTable);
|
||||
if (debugTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the debug table
|
||||
data.Seek(debugTableAddress, SeekOrigin.Begin);
|
||||
int endOffset = (int)(debugTableAddress + optionalHeader.Debug.Size);
|
||||
var debugTable = ParseDebugTable(data, endOffset);
|
||||
if (debugTable == null)
|
||||
return null;
|
||||
|
||||
// Set the debug table
|
||||
executable.DebugTable = debugTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Export Table
|
||||
|
||||
// Should also be in a '.edata' section
|
||||
if (optionalHeader.ExportTable != null && optionalHeader.ExportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0)
|
||||
{
|
||||
// If the offset for the export table doesn't exist
|
||||
int exportTableAddress = initialOffset
|
||||
+ (int)optionalHeader.ExportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable);
|
||||
if (exportTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the export table
|
||||
data.Seek(exportTableAddress, SeekOrigin.Begin);
|
||||
var exportTable = ParseExportTable(data, executable.SectionTable);
|
||||
if (exportTable == null)
|
||||
return null;
|
||||
|
||||
// Set the export table
|
||||
executable.ExportTable = exportTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Import Table
|
||||
|
||||
// Should also be in a '.idata' section
|
||||
if (optionalHeader.ImportTable != null && optionalHeader.ImportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0)
|
||||
{
|
||||
// If the offset for the import table doesn't exist
|
||||
int importTableAddress = initialOffset
|
||||
+ (int)optionalHeader.ImportTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable);
|
||||
if (importTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the import table
|
||||
data.Seek(importTableAddress, SeekOrigin.Begin);
|
||||
var importTable = ParseImportTable(data, optionalHeader.Magic, executable.SectionTable);
|
||||
if (importTable == null)
|
||||
return null;
|
||||
|
||||
// Set the import table
|
||||
executable.ImportTable = importTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resource Directory Table
|
||||
|
||||
// Should also be in a '.rsrc' section
|
||||
if (optionalHeader.ResourceTable != null && optionalHeader.ResourceTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable) != 0)
|
||||
{
|
||||
// If the offset for the resource directory table doesn't exist
|
||||
int resourceTableAddress = initialOffset
|
||||
+ (int)optionalHeader.ResourceTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable);
|
||||
if (resourceTableAddress >= data.Length)
|
||||
return executable;
|
||||
|
||||
// Try to parse the resource directory table
|
||||
data.Seek(resourceTableAddress, SeekOrigin.Begin);
|
||||
var resourceDirectoryTable = ParseResourceDirectoryTable(data, data.Position, executable.SectionTable, true);
|
||||
if (resourceDirectoryTable == null)
|
||||
return null;
|
||||
|
||||
// Set the resource directory table
|
||||
executable.ResourceDirectoryTable = resourceDirectoryTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Finish implementing PE parsing
|
||||
return executable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,49 +15,53 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new archive to fill
|
||||
var archive = new Archive();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Signature != SignatureString)
|
||||
return null;
|
||||
|
||||
// Set the archive header
|
||||
archive.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region File List
|
||||
|
||||
// If we have any files
|
||||
var fileDescriptors = new FileDescriptor[header.FileCount];
|
||||
|
||||
// Read all entries in turn
|
||||
for (int i = 0; i < header.FileCount; i++)
|
||||
try
|
||||
{
|
||||
var file = ParseFileDescriptor(data, header.MinorVersion);
|
||||
if (file == null)
|
||||
// Create a new archive to fill
|
||||
var archive = new Archive();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Signature != SignatureString)
|
||||
return null;
|
||||
|
||||
fileDescriptors[i] = file;
|
||||
// Set the archive header
|
||||
archive.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region File List
|
||||
|
||||
// If we have any files
|
||||
var fileDescriptors = new FileDescriptor[header.FileCount];
|
||||
|
||||
// Read all entries in turn
|
||||
for (int i = 0; i < header.FileCount; i++)
|
||||
{
|
||||
var file = ParseFileDescriptor(data, header.MinorVersion);
|
||||
if (file == null)
|
||||
return null;
|
||||
|
||||
fileDescriptors[i] = file;
|
||||
}
|
||||
|
||||
// Set the file list
|
||||
archive.FileList = fileDescriptors;
|
||||
|
||||
#endregion
|
||||
|
||||
// Cache the compressed data offset
|
||||
archive.CompressedDataOffset = data.Position;
|
||||
|
||||
return archive;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set the file list
|
||||
archive.FileList = fileDescriptors;
|
||||
|
||||
#endregion
|
||||
|
||||
// Cache the compressed data offset
|
||||
archive.CompressedDataOffset = data.Position;
|
||||
|
||||
return archive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -11,176 +11,173 @@ namespace SabreTools.Serialization.Deserializers
|
||||
/// <inheritdoc/>
|
||||
public override MetadataFile? Deserialize(Stream? data)
|
||||
{
|
||||
// If the stream is null
|
||||
if (data == null)
|
||||
return default;
|
||||
// If tthe data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
}
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new IniReader(data, Encoding.UTF8)
|
||||
{
|
||||
ValidateRows = false,
|
||||
};
|
||||
var dat = new MetadataFile();
|
||||
|
||||
// Loop through and parse out the values
|
||||
var roms = new List<Rom>();
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// If we have no next line
|
||||
if (!reader.ReadNextLine())
|
||||
break;
|
||||
|
||||
// Ignore certain row types
|
||||
switch (reader.RowType)
|
||||
// Setup the reader and output
|
||||
var reader = new IniReader(data, Encoding.UTF8)
|
||||
{
|
||||
case IniRowType.None:
|
||||
case IniRowType.Comment:
|
||||
continue;
|
||||
case IniRowType.SectionHeader:
|
||||
switch (reader.Section?.ToLowerInvariant())
|
||||
ValidateRows = false,
|
||||
};
|
||||
var dat = new MetadataFile();
|
||||
|
||||
// Loop through and parse out the values
|
||||
var roms = new List<Rom>();
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// If we have no next line
|
||||
if (!reader.ReadNextLine())
|
||||
break;
|
||||
|
||||
// Ignore certain row types
|
||||
switch (reader.RowType)
|
||||
{
|
||||
case IniRowType.None:
|
||||
case IniRowType.Comment:
|
||||
continue;
|
||||
case IniRowType.SectionHeader:
|
||||
switch (reader.Section?.ToLowerInvariant())
|
||||
{
|
||||
case "credits":
|
||||
dat.Credits ??= new Credits();
|
||||
break;
|
||||
case "dat":
|
||||
dat.Dat ??= new Dat();
|
||||
break;
|
||||
case "emulator":
|
||||
dat.Emulator ??= new Emulator();
|
||||
break;
|
||||
case "games":
|
||||
dat.Games ??= new Games();
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're in credits
|
||||
if (reader.Section?.ToLowerInvariant() == "credits")
|
||||
{
|
||||
// Create the section if we haven't already
|
||||
dat.Credits ??= new Credits();
|
||||
|
||||
switch (reader.KeyValuePair?.Key?.ToLowerInvariant())
|
||||
{
|
||||
case "credits":
|
||||
dat.Credits ??= new Credits();
|
||||
case "author":
|
||||
dat.Credits.Author = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "dat":
|
||||
dat.Dat ??= new Dat();
|
||||
case "version":
|
||||
dat.Credits.Version = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "emulator":
|
||||
dat.Emulator ??= new Emulator();
|
||||
case "email":
|
||||
dat.Credits.Email = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "games":
|
||||
dat.Games ??= new Games();
|
||||
case "homepage":
|
||||
dat.Credits.Homepage = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "url":
|
||||
dat.Credits.Url = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "date":
|
||||
dat.Credits.Date = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "comment":
|
||||
dat.Credits.Comment = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're in credits
|
||||
if (reader.Section?.ToLowerInvariant() == "credits")
|
||||
{
|
||||
// Create the section if we haven't already
|
||||
dat.Credits ??= new Credits();
|
||||
|
||||
switch (reader.KeyValuePair?.Key?.ToLowerInvariant())
|
||||
{
|
||||
case "author":
|
||||
dat.Credits.Author = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "version":
|
||||
dat.Credits.Version = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "email":
|
||||
dat.Credits.Email = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "homepage":
|
||||
dat.Credits.Homepage = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "url":
|
||||
dat.Credits.Url = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "date":
|
||||
dat.Credits.Date = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "comment":
|
||||
dat.Credits.Comment = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in dat
|
||||
else if (reader.Section?.ToLowerInvariant() == "dat")
|
||||
{
|
||||
// Create the section if we haven't already
|
||||
dat.Dat ??= new Dat();
|
||||
|
||||
switch (reader.KeyValuePair?.Key?.ToLowerInvariant())
|
||||
// If we're in dat
|
||||
else if (reader.Section?.ToLowerInvariant() == "dat")
|
||||
{
|
||||
case "version":
|
||||
dat.Dat.Version = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "plugin":
|
||||
dat.Dat.Plugin = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "split":
|
||||
dat.Dat.Split = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "merge":
|
||||
dat.Dat.Merge = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
// Create the section if we haven't already
|
||||
dat.Dat ??= new Dat();
|
||||
|
||||
switch (reader.KeyValuePair?.Key?.ToLowerInvariant())
|
||||
{
|
||||
case "version":
|
||||
dat.Dat.Version = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "plugin":
|
||||
dat.Dat.Plugin = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "split":
|
||||
dat.Dat.Split = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "merge":
|
||||
dat.Dat.Merge = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in emulator
|
||||
else if (reader.Section?.ToLowerInvariant() == "emulator")
|
||||
{
|
||||
// Create the section if we haven't already
|
||||
dat.Emulator ??= new Emulator();
|
||||
|
||||
switch (reader.KeyValuePair?.Key?.ToLowerInvariant())
|
||||
// If we're in emulator
|
||||
else if (reader.Section?.ToLowerInvariant() == "emulator")
|
||||
{
|
||||
case "refname":
|
||||
dat.Emulator.RefName = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "version":
|
||||
dat.Emulator.Version = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
// Create the section if we haven't already
|
||||
dat.Emulator ??= new Emulator();
|
||||
|
||||
switch (reader.KeyValuePair?.Key?.ToLowerInvariant())
|
||||
{
|
||||
case "refname":
|
||||
dat.Emulator.RefName = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
case "version":
|
||||
dat.Emulator.Version = reader.KeyValuePair?.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in games
|
||||
else if (reader.Section?.ToLowerInvariant() == "games")
|
||||
{
|
||||
// Create the section if we haven't already
|
||||
dat.Games ??= new Games();
|
||||
// If we're in games
|
||||
else if (reader.Section?.ToLowerInvariant() == "games")
|
||||
{
|
||||
// Create the section if we haven't already
|
||||
dat.Games ??= new Games();
|
||||
|
||||
// If the line doesn't contain the delimiter
|
||||
// If the line doesn't contain the delimiter
|
||||
#if NETFRAMEWORK
|
||||
if (!(reader.CurrentLine?.Contains("¬") ?? false))
|
||||
#else
|
||||
if (!(reader.CurrentLine?.Contains('¬') ?? false))
|
||||
if (!(reader.CurrentLine?.Contains('¬') ?? false))
|
||||
#endif
|
||||
continue;
|
||||
continue;
|
||||
|
||||
// Otherwise, separate out the line
|
||||
string[] splitLine = reader.CurrentLine.Split('¬');
|
||||
var rom = new Rom
|
||||
{
|
||||
// EMPTY = splitLine[0]
|
||||
ParentName = splitLine[1],
|
||||
ParentDescription = splitLine[2],
|
||||
GameName = splitLine[3],
|
||||
GameDescription = splitLine[4],
|
||||
RomName = splitLine[5],
|
||||
RomCRC = splitLine[6],
|
||||
RomSize = splitLine[7],
|
||||
RomOf = splitLine[8],
|
||||
MergeName = splitLine[9],
|
||||
// EMPTY = splitLine[10]
|
||||
};
|
||||
// Otherwise, separate out the line
|
||||
string[] splitLine = reader.CurrentLine.Split('¬');
|
||||
var rom = new Rom
|
||||
{
|
||||
// EMPTY = splitLine[0]
|
||||
ParentName = splitLine[1],
|
||||
ParentDescription = splitLine[2],
|
||||
GameName = splitLine[3],
|
||||
GameDescription = splitLine[4],
|
||||
RomName = splitLine[5],
|
||||
RomCRC = splitLine[6],
|
||||
RomSize = splitLine[7],
|
||||
RomOf = splitLine[8],
|
||||
MergeName = splitLine[9],
|
||||
// EMPTY = splitLine[10]
|
||||
};
|
||||
|
||||
roms.Add(rom);
|
||||
roms.Add(rom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add extra pieces and return
|
||||
if (dat.Games != null && roms.Count > 0)
|
||||
// Add extra pieces and return
|
||||
if (dat.Games != null && roms.Count > 0)
|
||||
{
|
||||
dat.Games.Rom = [.. roms];
|
||||
return dat;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
dat.Games.Rom = [.. roms];
|
||||
return dat;
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,20 +13,24 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
try
|
||||
{
|
||||
// Deserialize the SFB
|
||||
var sfb = data.ReadType<Models.PlayStation3.SFB>();
|
||||
if (sfb?.Magic == null)
|
||||
return null;
|
||||
|
||||
// Deserialize the SFB
|
||||
var sfb = data.ReadType<Models.PlayStation3.SFB>();
|
||||
if (sfb?.Magic == null)
|
||||
return null;
|
||||
string magic = Encoding.ASCII.GetString(sfb.Magic);
|
||||
if (magic != ".SFB")
|
||||
return null;
|
||||
|
||||
string magic = Encoding.ASCII.GetString(sfb.Magic);
|
||||
if (magic != ".SFB")
|
||||
return sfb;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
|
||||
return sfb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,47 +13,51 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
try
|
||||
{
|
||||
// Create a new SFO to fill
|
||||
var sfo = new Models.PlayStation3.SFO();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = ParseHeader(data);
|
||||
if (header == null)
|
||||
return null;
|
||||
|
||||
// Assign the header
|
||||
sfo.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Index Table
|
||||
|
||||
// TODO: Determine how many entries are in the index table
|
||||
|
||||
#endregion
|
||||
|
||||
#region Key Table
|
||||
|
||||
// TODO: Finish implementation
|
||||
|
||||
#endregion
|
||||
|
||||
// Padding
|
||||
// TODO: Finish implementation
|
||||
|
||||
#region Data Table
|
||||
|
||||
// TODO: Finish implementation
|
||||
|
||||
#endregion
|
||||
|
||||
return sfo;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
|
||||
// Create a new SFO to fill
|
||||
var sfo = new Models.PlayStation3.SFO();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = ParseHeader(data);
|
||||
if (header == null)
|
||||
return null;
|
||||
|
||||
// Assign the header
|
||||
sfo.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Index Table
|
||||
|
||||
// TODO: Determine how many entries are in the index table
|
||||
|
||||
#endregion
|
||||
|
||||
#region Key Table
|
||||
|
||||
// TODO: Finish implementation
|
||||
|
||||
#endregion
|
||||
|
||||
// Padding
|
||||
// TODO: Finish implementation
|
||||
|
||||
#region Data Table
|
||||
|
||||
// TODO: Finish implementation
|
||||
|
||||
#endregion
|
||||
|
||||
return sfo;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -61,10 +65,9 @@ namespace SabreTools.Serialization.Deserializers
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled SFO header on success, null on error</returns>
|
||||
public Models.PlayStation3.SFOHeader? ParseHeader(Stream data)
|
||||
public static Models.PlayStation3.SFOHeader? ParseHeader(Stream data)
|
||||
{
|
||||
var sfoHeader = data.ReadType<Models.PlayStation3.SFOHeader>();
|
||||
|
||||
if (sfoHeader == null)
|
||||
return null;
|
||||
|
||||
@@ -74,13 +77,13 @@ namespace SabreTools.Serialization.Deserializers
|
||||
|
||||
return sfoHeader;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into an SFO index table entry
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled SFO index table entry on success, null on error</returns>
|
||||
public Models.PlayStation3.SFOIndexTableEntry? ParseIndexTableEntry(Stream data)
|
||||
public static Models.PlayStation3.SFOIndexTableEntry? ParseIndexTableEntry(Stream data)
|
||||
{
|
||||
return data.ReadType<Models.PlayStation3.SFOIndexTableEntry>();
|
||||
}
|
||||
|
||||
@@ -16,37 +16,42 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
try
|
||||
{
|
||||
// Create a new SGA to fill
|
||||
var file = new Models.SGA.File();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = ParseHeader(data);
|
||||
if (header == null)
|
||||
return null;
|
||||
|
||||
// Set the SGA header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory
|
||||
|
||||
// Try to parse the directory
|
||||
var directory = ParseDirectory(data, header.MajorVersion);
|
||||
if (directory == null)
|
||||
return null;
|
||||
|
||||
// Set the SGA directory
|
||||
file.Directory = directory;
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
|
||||
// Create a new SGA to fill
|
||||
var file = new Models.SGA.File();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = ParseHeader(data);
|
||||
if (header == null)
|
||||
return null;
|
||||
|
||||
// Set the SGA header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory
|
||||
|
||||
// Try to parse the directory
|
||||
var directory = ParseDirectory(data, header.MajorVersion);
|
||||
if (directory == null)
|
||||
return null;
|
||||
|
||||
// Set the SGA directory
|
||||
file.Directory = directory;
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -85,100 +85,97 @@ namespace SabreTools.Serialization.Deserializers
|
||||
/// <inheritdoc cref="Deserialize(Stream)"/>
|
||||
public MetadataFile? Deserialize(Stream? data, char delim)
|
||||
{
|
||||
// If the stream is null
|
||||
if (data == null)
|
||||
return default;
|
||||
// If tthe data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
// Setup the reader and output
|
||||
var reader = new SeparatedValueReader(data, Encoding.UTF8)
|
||||
{
|
||||
Header = true,
|
||||
Separator = delim,
|
||||
VerifyFieldCount = false,
|
||||
};
|
||||
var dat = new MetadataFile();
|
||||
|
||||
// Read the header values first
|
||||
if (!reader.ReadHeader() || reader.HeaderValues == null)
|
||||
return null;
|
||||
|
||||
dat.Header = [.. reader.HeaderValues];
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
var rows = new List<Row>();
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// If we have no next line
|
||||
if (!reader.ReadNextLine() || reader.Line == null)
|
||||
break;
|
||||
|
||||
// Parse the line into a row
|
||||
Row row;
|
||||
if (reader.Line.Count < HeaderWithExtendedHashesCount)
|
||||
{
|
||||
row = new Row
|
||||
{
|
||||
FileName = reader.Line[0],
|
||||
InternalName = reader.Line[1],
|
||||
Description = reader.Line[2],
|
||||
GameName = reader.Line[3],
|
||||
GameDescription = reader.Line[4],
|
||||
Type = reader.Line[5],
|
||||
RomName = reader.Line[6],
|
||||
DiskName = reader.Line[7],
|
||||
Size = reader.Line[8],
|
||||
CRC = reader.Line[9],
|
||||
MD5 = reader.Line[10],
|
||||
SHA1 = reader.Line[11],
|
||||
SHA256 = reader.Line[12],
|
||||
Status = reader.Line[13],
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
row = new Row
|
||||
{
|
||||
FileName = reader.Line[0],
|
||||
InternalName = reader.Line[1],
|
||||
Description = reader.Line[2],
|
||||
GameName = reader.Line[3],
|
||||
GameDescription = reader.Line[4],
|
||||
Type = reader.Line[5],
|
||||
RomName = reader.Line[6],
|
||||
DiskName = reader.Line[7],
|
||||
Size = reader.Line[8],
|
||||
CRC = reader.Line[9],
|
||||
MD5 = reader.Line[10],
|
||||
SHA1 = reader.Line[11],
|
||||
SHA256 = reader.Line[12],
|
||||
SHA384 = reader.Line[13],
|
||||
SHA512 = reader.Line[14],
|
||||
SpamSum = reader.Line[15],
|
||||
Status = reader.Line[16],
|
||||
};
|
||||
}
|
||||
rows.Add(row);
|
||||
}
|
||||
|
||||
// Assign the rows to the Dat and return
|
||||
if (rows.Count > 0)
|
||||
{
|
||||
dat.Row = [.. rows];
|
||||
return dat;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
}
|
||||
|
||||
// Setup the reader and output
|
||||
var reader = new SeparatedValueReader(data, Encoding.UTF8)
|
||||
{
|
||||
Header = true,
|
||||
Separator = delim,
|
||||
VerifyFieldCount = false,
|
||||
};
|
||||
var dat = new MetadataFile();
|
||||
|
||||
// Read the header values first
|
||||
if (!reader.ReadHeader() || reader.HeaderValues == null)
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
|
||||
dat.Header = [.. reader.HeaderValues];
|
||||
|
||||
// Loop through the rows and parse out values
|
||||
var rows = new List<Row>();
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
// If we have no next line
|
||||
if (!reader.ReadNextLine() || reader.Line == null)
|
||||
break;
|
||||
|
||||
// Parse the line into a row
|
||||
Row row;
|
||||
if (reader.Line.Count < HeaderWithExtendedHashesCount)
|
||||
{
|
||||
row = new Row
|
||||
{
|
||||
FileName = reader.Line[0],
|
||||
InternalName = reader.Line[1],
|
||||
Description = reader.Line[2],
|
||||
GameName = reader.Line[3],
|
||||
GameDescription = reader.Line[4],
|
||||
Type = reader.Line[5],
|
||||
RomName = reader.Line[6],
|
||||
DiskName = reader.Line[7],
|
||||
Size = reader.Line[8],
|
||||
CRC = reader.Line[9],
|
||||
MD5 = reader.Line[10],
|
||||
SHA1 = reader.Line[11],
|
||||
SHA256 = reader.Line[12],
|
||||
Status = reader.Line[13],
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
row = new Row
|
||||
{
|
||||
FileName = reader.Line[0],
|
||||
InternalName = reader.Line[1],
|
||||
Description = reader.Line[2],
|
||||
GameName = reader.Line[3],
|
||||
GameDescription = reader.Line[4],
|
||||
Type = reader.Line[5],
|
||||
RomName = reader.Line[6],
|
||||
DiskName = reader.Line[7],
|
||||
Size = reader.Line[8],
|
||||
CRC = reader.Line[9],
|
||||
MD5 = reader.Line[10],
|
||||
SHA1 = reader.Line[11],
|
||||
SHA256 = reader.Line[12],
|
||||
SHA384 = reader.Line[13],
|
||||
SHA512 = reader.Line[14],
|
||||
SpamSum = reader.Line[15],
|
||||
Status = reader.Line[16],
|
||||
};
|
||||
}
|
||||
rows.Add(row);
|
||||
}
|
||||
|
||||
// Assign the rows to the Dat and return
|
||||
if (rows.Count > 0)
|
||||
{
|
||||
dat.Row = [.. rows];
|
||||
return dat;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -17,248 +17,252 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new Half-Life 2 Level to fill
|
||||
var file = new VbspFile();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<VbspHeader>();
|
||||
if (header?.Signature != SignatureString)
|
||||
return null;
|
||||
if (Array.IndexOf([17, 18, 19, 20, 21, 22, 23, 25, 27, 29, 0x00040014], header.Version) > -1)
|
||||
return null;
|
||||
if (header.Lumps == null || header.Lumps.Length != VBSP_HEADER_LUMPS)
|
||||
return null;
|
||||
|
||||
// Set the package header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lumps
|
||||
|
||||
for (int l = 0; l < VBSP_HEADER_LUMPS; l++)
|
||||
try
|
||||
{
|
||||
// Get the next lump entry
|
||||
var lumpEntry = header.Lumps[l];
|
||||
if (lumpEntry == null)
|
||||
continue;
|
||||
if (lumpEntry.Offset == 0 || lumpEntry.Length == 0)
|
||||
continue;
|
||||
// Create a new Half-Life 2 Level to fill
|
||||
var file = new VbspFile();
|
||||
|
||||
// Seek to the lump offset
|
||||
data.Seek(lumpEntry.Offset, SeekOrigin.Begin);
|
||||
#region Header
|
||||
|
||||
// Read according to the lump type
|
||||
switch ((LumpType)l)
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<VbspHeader>();
|
||||
if (header?.Signature != SignatureString)
|
||||
return null;
|
||||
if (Array.IndexOf([17, 18, 19, 20, 21, 22, 23, 25, 27, 29, 0x00040014], header.Version) > -1)
|
||||
return null;
|
||||
if (header.Lumps == null || header.Lumps.Length != VBSP_HEADER_LUMPS)
|
||||
return null;
|
||||
|
||||
// Set the package header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lumps
|
||||
|
||||
for (int l = 0; l < VBSP_HEADER_LUMPS; l++)
|
||||
{
|
||||
case LumpType.LUMP_ENTITIES:
|
||||
file.Entities = ParseEntitiesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_PLANES:
|
||||
file.PlanesLump = ParsePlanesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_TEXTURES:
|
||||
file.TexdataLump = ParseTexdataLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_VERTICES:
|
||||
file.VerticesLump = ParseVerticesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_VISIBILITY:
|
||||
file.VisibilityLump = ParseVisibilityLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_NODES:
|
||||
file.NodesLump = ParseNodesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_TEXINFO:
|
||||
file.TexinfoLump = ParseTexinfoLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_FACES:
|
||||
file.FacesLump = ParseFacesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LIGHTING:
|
||||
file.LightmapLump = ParseLightmapLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_CLIPNODES:
|
||||
file.OcclusionLump = ParseOcclusionLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAVES:
|
||||
file.LeavesLump = ParseLeavesLump(data, lumpEntry.Version, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_MARKSURFACES:
|
||||
file.MarksurfacesLump = ParseMarksurfacesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_EDGES:
|
||||
file.EdgesLump = ParseEdgesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_SURFEDGES:
|
||||
file.SurfedgesLump = ParseSurfedgesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_MODELS:
|
||||
file.ModelsLump = ParseModelsLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_WORLDLIGHTS:
|
||||
file.LDRWorldLightsLump = ParseWorldLightsLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAFFACES:
|
||||
file.LeafFacesLump = ParseLeafFacesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAFBRUSHES:
|
||||
file.LeafBrushesLump = ParseLeafBrushesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_BRUSHES:
|
||||
file.BrushesLump = ParseBrushesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_BRUSHSIDES:
|
||||
file.BrushsidesLump = ParseBrushsidesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_AREAS:
|
||||
// TODO: Support LUMP_AREAS [20] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_AREAPORTALS:
|
||||
// TODO: Support LUMP_AREAPORTALS [21] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PORTALS:
|
||||
// TODO: Support LUMP_PORTALS / LUMP_UNUSED0 / LUMP_PROPCOLLISION [22] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_CLUSTERS:
|
||||
// TODO: Support LUMP_CLUSTERS / LUMP_UNUSED1 / LUMP_PROPHULLS [23] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PORTALVERTS:
|
||||
// TODO: Support LUMP_PORTALVERTS / LUMP_UNUSED2 / LUMP_FAKEENTITIES / LUMP_PROPHULLVERTS [24] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_CLUSTERPORTALS:
|
||||
// TODO: Support LUMP_CLUSTERPORTALS / LUMP_UNUSED3 / LUMP_PROPTRIS [25] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_DISPINFO:
|
||||
file.DispInfosLump = ParseDispInfosLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_ORIGINALFACES:
|
||||
file.OriginalFacesLump = ParseFacesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_PHYSDISP:
|
||||
// TODO: Support LUMP_PHYSDISP [28] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PHYSCOLLIDE:
|
||||
file.PhysCollideLump = ParsePhysCollideLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_VERTNORMALS:
|
||||
// TODO: Support LUMP_VERTNORMALS [30] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_VERTNORMALINDICES:
|
||||
// TODO: Support LUMP_VERTNORMALINDICES [31] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_DISP_LIGHTMAP_ALPHAS:
|
||||
// TODO: Support LUMP_DISP_LIGHTMAP_ALPHAS [32] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_DISP_VERTS:
|
||||
file.DispVertsLump = ParseDispVertsLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS:
|
||||
// TODO: Support LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS [34] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_GAME_LUMP:
|
||||
file.GameLump = ParseGameLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAFWATERDATA:
|
||||
// TODO: Support LUMP_LEAFWATERDATA [36] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PRIMITIVES:
|
||||
// TODO: Support LUMP_PRIMITIVES [37] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PRIMVERTS:
|
||||
// TODO: Support LUMP_PRIMVERTS [38] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PRIMINDICES:
|
||||
// TODO: Support LUMP_PRIMINDICES [39] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PAKFILE:
|
||||
file.PakfileLump = ParsePakfileLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_CLIPPORTALVERTS:
|
||||
// TODO: Support LUMP_CLIPPORTALVERTS [41] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_CUBEMAPS:
|
||||
file.CubemapsLump = ParseCubemapsLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_TEXDATA_STRING_DATA:
|
||||
file.TexdataStringData = ParseTexdataStringData(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_TEXDATA_STRING_TABLE:
|
||||
file.TexdataStringTable = ParseTexdataStringTable(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_OVERLAYS:
|
||||
file.OverlaysLump = ParseOverlaysLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAFMINDISTTOWATER:
|
||||
// TODO: Support LUMP_LEAFMINDISTTOWATER [46] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_FACE_MACRO_TEXTURE_INFO:
|
||||
// TODO: Support LUMP_FACE_MACRO_TEXTURE_INFO [47] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_DISP_TRIS:
|
||||
file.DispTrisLump = ParseDispTrisLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_PHYSCOLLIDESURFACE:
|
||||
// TODO: Support LUMP_PHYSCOLLIDESURFACE / LUMP_PROP_BLOB [49] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_WATEROVERLAYS:
|
||||
// TODO: Support LUMP_WATEROVERLAYS [50] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_LIGHTMAPPAGES:
|
||||
file.HDRAmbientIndexLump = ParseAmbientIndexLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LIGHTMAPPAGEINFOS:
|
||||
file.LDRAmbientIndexLump = ParseAmbientIndexLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LIGHTING_HDR:
|
||||
// TODO: Support LUMP_LIGHTING_HDR [53] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_WORLDLIGHTS_HDR:
|
||||
file.HDRWorldLightsLump = ParseWorldLightsLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAF_AMBIENT_LIGHTING_HDR:
|
||||
file.HDRAmbientLightingLump = ParseAmbientLightingLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAF_AMBIENT_LIGHTING:
|
||||
file.LDRAmbientLightingLump = ParseAmbientLightingLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_XZIPPAKFILE:
|
||||
// TODO: Support LUMP_XZIPPAKFILE [57] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_FACES_HDR:
|
||||
// TODO: Support LUMP_FACES_HDR [58] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_MAP_FLAGS:
|
||||
// TODO: Support LUMP_MAP_FLAGS [59] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_OVERLAY_FADES:
|
||||
// TODO: Support LUMP_OVERLAY_FADES [60] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_OVERLAY_SYSTEM_LEVELS:
|
||||
// TODO: Support LUMP_OVERLAY_SYSTEM_LEVELS [61] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PHYSLEVEL:
|
||||
// TODO: Support LUMP_PHYSLEVEL [62] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_DISP_MULTIBLEND:
|
||||
// TODO: Support LUMP_DISP_MULTIBLEND [63] when in Models
|
||||
break;
|
||||
// Get the next lump entry
|
||||
var lumpEntry = header.Lumps[l];
|
||||
if (lumpEntry == null)
|
||||
continue;
|
||||
if (lumpEntry.Offset == 0 || lumpEntry.Length == 0)
|
||||
continue;
|
||||
|
||||
default:
|
||||
// Unsupported LumpType value, ignore
|
||||
break;
|
||||
// Seek to the lump offset
|
||||
data.Seek(lumpEntry.Offset, SeekOrigin.Begin);
|
||||
|
||||
// Read according to the lump type
|
||||
switch ((LumpType)l)
|
||||
{
|
||||
case LumpType.LUMP_ENTITIES:
|
||||
file.Entities = ParseEntitiesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_PLANES:
|
||||
file.PlanesLump = ParsePlanesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_TEXTURES:
|
||||
file.TexdataLump = ParseTexdataLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_VERTICES:
|
||||
file.VerticesLump = ParseVerticesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_VISIBILITY:
|
||||
file.VisibilityLump = ParseVisibilityLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_NODES:
|
||||
file.NodesLump = ParseNodesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_TEXINFO:
|
||||
file.TexinfoLump = ParseTexinfoLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_FACES:
|
||||
file.FacesLump = ParseFacesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LIGHTING:
|
||||
file.LightmapLump = ParseLightmapLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_CLIPNODES:
|
||||
file.OcclusionLump = ParseOcclusionLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAVES:
|
||||
file.LeavesLump = ParseLeavesLump(data, lumpEntry.Version, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_MARKSURFACES:
|
||||
file.MarksurfacesLump = ParseMarksurfacesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_EDGES:
|
||||
file.EdgesLump = ParseEdgesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_SURFEDGES:
|
||||
file.SurfedgesLump = ParseSurfedgesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_MODELS:
|
||||
file.ModelsLump = ParseModelsLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_WORLDLIGHTS:
|
||||
file.LDRWorldLightsLump = ParseWorldLightsLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAFFACES:
|
||||
file.LeafFacesLump = ParseLeafFacesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAFBRUSHES:
|
||||
file.LeafBrushesLump = ParseLeafBrushesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_BRUSHES:
|
||||
file.BrushesLump = ParseBrushesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_BRUSHSIDES:
|
||||
file.BrushsidesLump = ParseBrushsidesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_AREAS:
|
||||
// TODO: Support LUMP_AREAS [20] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_AREAPORTALS:
|
||||
// TODO: Support LUMP_AREAPORTALS [21] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PORTALS:
|
||||
// TODO: Support LUMP_PORTALS / LUMP_UNUSED0 / LUMP_PROPCOLLISION [22] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_CLUSTERS:
|
||||
// TODO: Support LUMP_CLUSTERS / LUMP_UNUSED1 / LUMP_PROPHULLS [23] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PORTALVERTS:
|
||||
// TODO: Support LUMP_PORTALVERTS / LUMP_UNUSED2 / LUMP_FAKEENTITIES / LUMP_PROPHULLVERTS [24] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_CLUSTERPORTALS:
|
||||
// TODO: Support LUMP_CLUSTERPORTALS / LUMP_UNUSED3 / LUMP_PROPTRIS [25] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_DISPINFO:
|
||||
file.DispInfosLump = ParseDispInfosLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_ORIGINALFACES:
|
||||
file.OriginalFacesLump = ParseFacesLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_PHYSDISP:
|
||||
// TODO: Support LUMP_PHYSDISP [28] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PHYSCOLLIDE:
|
||||
file.PhysCollideLump = ParsePhysCollideLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_VERTNORMALS:
|
||||
// TODO: Support LUMP_VERTNORMALS [30] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_VERTNORMALINDICES:
|
||||
// TODO: Support LUMP_VERTNORMALINDICES [31] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_DISP_LIGHTMAP_ALPHAS:
|
||||
// TODO: Support LUMP_DISP_LIGHTMAP_ALPHAS [32] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_DISP_VERTS:
|
||||
file.DispVertsLump = ParseDispVertsLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS:
|
||||
// TODO: Support LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS [34] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_GAME_LUMP:
|
||||
file.GameLump = ParseGameLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAFWATERDATA:
|
||||
// TODO: Support LUMP_LEAFWATERDATA [36] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PRIMITIVES:
|
||||
// TODO: Support LUMP_PRIMITIVES [37] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PRIMVERTS:
|
||||
// TODO: Support LUMP_PRIMVERTS [38] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PRIMINDICES:
|
||||
// TODO: Support LUMP_PRIMINDICES [39] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PAKFILE:
|
||||
file.PakfileLump = ParsePakfileLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_CLIPPORTALVERTS:
|
||||
// TODO: Support LUMP_CLIPPORTALVERTS [41] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_CUBEMAPS:
|
||||
file.CubemapsLump = ParseCubemapsLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_TEXDATA_STRING_DATA:
|
||||
file.TexdataStringData = ParseTexdataStringData(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_TEXDATA_STRING_TABLE:
|
||||
file.TexdataStringTable = ParseTexdataStringTable(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_OVERLAYS:
|
||||
file.OverlaysLump = ParseOverlaysLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAFMINDISTTOWATER:
|
||||
// TODO: Support LUMP_LEAFMINDISTTOWATER [46] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_FACE_MACRO_TEXTURE_INFO:
|
||||
// TODO: Support LUMP_FACE_MACRO_TEXTURE_INFO [47] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_DISP_TRIS:
|
||||
file.DispTrisLump = ParseDispTrisLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_PHYSCOLLIDESURFACE:
|
||||
// TODO: Support LUMP_PHYSCOLLIDESURFACE / LUMP_PROP_BLOB [49] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_WATEROVERLAYS:
|
||||
// TODO: Support LUMP_WATEROVERLAYS [50] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_LIGHTMAPPAGES:
|
||||
file.HDRAmbientIndexLump = ParseAmbientIndexLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LIGHTMAPPAGEINFOS:
|
||||
file.LDRAmbientIndexLump = ParseAmbientIndexLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LIGHTING_HDR:
|
||||
// TODO: Support LUMP_LIGHTING_HDR [53] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_WORLDLIGHTS_HDR:
|
||||
file.HDRWorldLightsLump = ParseWorldLightsLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAF_AMBIENT_LIGHTING_HDR:
|
||||
file.HDRAmbientLightingLump = ParseAmbientLightingLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_LEAF_AMBIENT_LIGHTING:
|
||||
file.LDRAmbientLightingLump = ParseAmbientLightingLump(data, lumpEntry.Offset, lumpEntry.Length);
|
||||
break;
|
||||
case LumpType.LUMP_XZIPPAKFILE:
|
||||
// TODO: Support LUMP_XZIPPAKFILE [57] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_FACES_HDR:
|
||||
// TODO: Support LUMP_FACES_HDR [58] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_MAP_FLAGS:
|
||||
// TODO: Support LUMP_MAP_FLAGS [59] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_OVERLAY_FADES:
|
||||
// TODO: Support LUMP_OVERLAY_FADES [60] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_OVERLAY_SYSTEM_LEVELS:
|
||||
// TODO: Support LUMP_OVERLAY_SYSTEM_LEVELS [61] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_PHYSLEVEL:
|
||||
// TODO: Support LUMP_PHYSLEVEL [62] when in Models
|
||||
break;
|
||||
case LumpType.LUMP_DISP_MULTIBLEND:
|
||||
// TODO: Support LUMP_DISP_MULTIBLEND [63] when in Models
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unsupported LumpType value, ignore
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,84 +15,87 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new Valve Package to fill
|
||||
var file = new Models.VPK.File();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
// The original version had no signature.
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Signature != SignatureUInt32)
|
||||
return null;
|
||||
if (header.Version > 2)
|
||||
return null;
|
||||
|
||||
// Set the package header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extended Header
|
||||
|
||||
if (header.Version == 2)
|
||||
try
|
||||
{
|
||||
// Try to parse the extended header
|
||||
var extendedHeader = data.ReadType<ExtendedHeader>();
|
||||
if (extendedHeader == null)
|
||||
// Create a new Valve Package to fill
|
||||
var file = new Models.VPK.File();
|
||||
|
||||
#region Header
|
||||
|
||||
// The original version had no signature.
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Signature != SignatureUInt32)
|
||||
return null;
|
||||
if (header.Version > 2)
|
||||
return null;
|
||||
|
||||
// Set the package extended header
|
||||
file.ExtendedHeader = extendedHeader;
|
||||
}
|
||||
// Set the package header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Directory Items
|
||||
#region Extended Header
|
||||
|
||||
// Create the directory items tree
|
||||
var directoryItems = ParseDirectoryItemTree(data);
|
||||
if (directoryItems == null)
|
||||
return null;
|
||||
|
||||
// Set the directory items
|
||||
file.DirectoryItems = directoryItems;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Archive Hashes
|
||||
|
||||
if (header?.Version == 2
|
||||
&& file.ExtendedHeader != null
|
||||
&& file.ExtendedHeader.ArchiveMD5SectionSize > 0
|
||||
&& data.Position + file.ExtendedHeader.ArchiveMD5SectionSize <= data.Length)
|
||||
{
|
||||
// Create the archive hashes list
|
||||
var archiveHashes = new List<ArchiveHash>();
|
||||
|
||||
// Cache the current offset
|
||||
long initialOffset = data.Position;
|
||||
|
||||
// Try to parse the directory items
|
||||
while (data.Position < initialOffset + file.ExtendedHeader.ArchiveMD5SectionSize)
|
||||
if (header.Version == 2)
|
||||
{
|
||||
var archiveHash = data.ReadType<ArchiveHash>();
|
||||
if (archiveHash == null)
|
||||
// Try to parse the extended header
|
||||
var extendedHeader = data.ReadType<ExtendedHeader>();
|
||||
if (extendedHeader == null)
|
||||
return null;
|
||||
|
||||
archiveHashes.Add(archiveHash);
|
||||
|
||||
// Set the package extended header
|
||||
file.ExtendedHeader = extendedHeader;
|
||||
}
|
||||
|
||||
file.ArchiveHashes = [.. archiveHashes];
|
||||
#endregion
|
||||
|
||||
#region Directory Items
|
||||
|
||||
// Create the directory items tree
|
||||
var directoryItems = ParseDirectoryItemTree(data);
|
||||
if (directoryItems == null)
|
||||
return null;
|
||||
|
||||
// Set the directory items
|
||||
file.DirectoryItems = directoryItems;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Archive Hashes
|
||||
|
||||
if (header?.Version == 2
|
||||
&& file.ExtendedHeader != null
|
||||
&& file.ExtendedHeader.ArchiveMD5SectionSize > 0
|
||||
&& data.Position + file.ExtendedHeader.ArchiveMD5SectionSize <= data.Length)
|
||||
{
|
||||
// Create the archive hashes list
|
||||
var archiveHashes = new List<ArchiveHash>();
|
||||
|
||||
// Cache the current offset
|
||||
long initialOffset = data.Position;
|
||||
|
||||
// Try to parse the directory items
|
||||
while (data.Position < initialOffset + file.ExtendedHeader.ArchiveMD5SectionSize)
|
||||
{
|
||||
var archiveHash = data.ReadType<ArchiveHash>();
|
||||
if (archiveHash == null)
|
||||
return null;
|
||||
|
||||
archiveHashes.Add(archiveHash);
|
||||
}
|
||||
|
||||
file.ArchiveHashes = [.. archiveHashes];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,79 +15,83 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new Half-Life Texture Package to fill
|
||||
var file = new Models.WAD3.File();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Signature != SignatureString)
|
||||
return null;
|
||||
|
||||
// Set the package header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Entries
|
||||
|
||||
// Get the directory offset
|
||||
uint dirOffset = header.DirOffset;
|
||||
if (dirOffset < 0 || dirOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the lump offset
|
||||
data.Seek(dirOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the lump array
|
||||
file.DirEntries = new DirEntry[header.NumDirs];
|
||||
for (int i = 0; i < header.NumDirs; i++)
|
||||
try
|
||||
{
|
||||
var lump = data.ReadType<DirEntry>();
|
||||
if (lump == null)
|
||||
// Create a new Half-Life Texture Package to fill
|
||||
var file = new Models.WAD3.File();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Signature != SignatureString)
|
||||
return null;
|
||||
|
||||
file.DirEntries[i] = lump;
|
||||
// Set the package header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Entries
|
||||
|
||||
// Get the directory offset
|
||||
uint dirOffset = header.DirOffset;
|
||||
if (dirOffset < 0 || dirOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the lump offset
|
||||
data.Seek(dirOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the lump array
|
||||
file.DirEntries = new DirEntry[header.NumDirs];
|
||||
for (int i = 0; i < header.NumDirs; i++)
|
||||
{
|
||||
var lump = data.ReadType<DirEntry>();
|
||||
if (lump == null)
|
||||
return null;
|
||||
|
||||
file.DirEntries[i] = lump;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Entries
|
||||
|
||||
// Create the file entry array
|
||||
file.FileEntries = new FileEntry?[header.NumDirs];
|
||||
for (int i = 0; i < header.NumDirs; i++)
|
||||
{
|
||||
var dirEntry = file.DirEntries[i];
|
||||
if (dirEntry == null)
|
||||
continue;
|
||||
|
||||
// TODO: Handle compressed entries
|
||||
if (dirEntry.Compression != 0)
|
||||
continue;
|
||||
|
||||
// Get the file entry offset
|
||||
uint fileEntryOffset = dirEntry.Offset;
|
||||
if (fileEntryOffset < 0 || fileEntryOffset >= data.Length)
|
||||
continue;
|
||||
|
||||
// Seek to the file entry offset
|
||||
data.Seek(fileEntryOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the file entry
|
||||
var fileEntry = ParseFileEntry(data, dirEntry.Type);
|
||||
if (fileEntry != null)
|
||||
file.FileEntries[i] = fileEntry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Entries
|
||||
|
||||
// Create the file entry array
|
||||
file.FileEntries = new FileEntry?[header.NumDirs];
|
||||
for (int i = 0; i < header.NumDirs; i++)
|
||||
catch
|
||||
{
|
||||
var dirEntry = file.DirEntries[i];
|
||||
if (dirEntry == null)
|
||||
continue;
|
||||
|
||||
// TODO: Handle compressed entries
|
||||
if (dirEntry.Compression != 0)
|
||||
continue;
|
||||
|
||||
// Get the file entry offset
|
||||
uint fileEntryOffset = dirEntry.Offset;
|
||||
if (fileEntryOffset < 0 || fileEntryOffset >= data.Length)
|
||||
continue;
|
||||
|
||||
// Seek to the file entry offset
|
||||
data.Seek(fileEntryOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to parse the file entry
|
||||
var fileEntry = ParseFileEntry(data, dirEntry.Type);
|
||||
if (fileEntry != null)
|
||||
file.FileEntries[i] = fileEntry;
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -14,125 +14,129 @@ namespace SabreTools.Serialization.Deserializers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a new XBox Package File to fill
|
||||
var file = new Models.XZP.File();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Signature != HeaderSignatureString)
|
||||
return null;
|
||||
if (header.Version != 6)
|
||||
return null;
|
||||
|
||||
// Set the package header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Entries
|
||||
|
||||
// Create the directory entry array
|
||||
file.DirectoryEntries = new DirectoryEntry[header.DirectoryEntryCount];
|
||||
|
||||
// Try to parse the directory entries
|
||||
for (int i = 0; i < file.DirectoryEntries.Length; i++)
|
||||
try
|
||||
{
|
||||
var directoryEntry = data.ReadType<DirectoryEntry>();
|
||||
if (directoryEntry == null)
|
||||
continue;
|
||||
// Create a new XBox Package File to fill
|
||||
var file = new Models.XZP.File();
|
||||
|
||||
file.DirectoryEntries[i] = directoryEntry;
|
||||
}
|
||||
#region Header
|
||||
|
||||
#endregion
|
||||
// Try to parse the header
|
||||
var header = data.ReadType<Header>();
|
||||
if (header?.Signature != HeaderSignatureString)
|
||||
return null;
|
||||
if (header.Version != 6)
|
||||
return null;
|
||||
|
||||
#region Preload Directory Entries
|
||||
// Set the package header
|
||||
file.Header = header;
|
||||
|
||||
if (header.PreloadBytes > 0)
|
||||
{
|
||||
// Create the preload directory entry array
|
||||
file.PreloadDirectoryEntries = new DirectoryEntry[header.PreloadDirectoryEntryCount];
|
||||
#endregion
|
||||
|
||||
// Try to parse the preload directory entries
|
||||
for (int i = 0; i < file.PreloadDirectoryEntries.Length; i++)
|
||||
#region Directory Entries
|
||||
|
||||
// Create the directory entry array
|
||||
file.DirectoryEntries = new DirectoryEntry[header.DirectoryEntryCount];
|
||||
|
||||
// Try to parse the directory entries
|
||||
for (int i = 0; i < file.DirectoryEntries.Length; i++)
|
||||
{
|
||||
var directoryEntry = data.ReadType<DirectoryEntry>();
|
||||
if (directoryEntry == null)
|
||||
continue;
|
||||
|
||||
file.PreloadDirectoryEntries[i] = directoryEntry;
|
||||
file.DirectoryEntries[i] = directoryEntry;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Preload Directory Mappings
|
||||
#region Preload Directory Entries
|
||||
|
||||
if (header.PreloadBytes > 0)
|
||||
{
|
||||
// Create the preload directory mapping array
|
||||
file.PreloadDirectoryMappings = new DirectoryMapping[header.PreloadDirectoryEntryCount];
|
||||
|
||||
// Try to parse the preload directory mappings
|
||||
for (int i = 0; i < file.PreloadDirectoryMappings.Length; i++)
|
||||
if (header.PreloadBytes > 0)
|
||||
{
|
||||
var directoryMapping = data.ReadType<DirectoryMapping>();
|
||||
if (directoryMapping == null)
|
||||
continue;
|
||||
// Create the preload directory entry array
|
||||
file.PreloadDirectoryEntries = new DirectoryEntry[header.PreloadDirectoryEntryCount];
|
||||
|
||||
file.PreloadDirectoryMappings[i] = directoryMapping;
|
||||
// Try to parse the preload directory entries
|
||||
for (int i = 0; i < file.PreloadDirectoryEntries.Length; i++)
|
||||
{
|
||||
var directoryEntry = data.ReadType<DirectoryEntry>();
|
||||
if (directoryEntry == null)
|
||||
continue;
|
||||
|
||||
file.PreloadDirectoryEntries[i] = directoryEntry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Directory Items
|
||||
#region Preload Directory Mappings
|
||||
|
||||
if (header.DirectoryItemCount > 0)
|
||||
{
|
||||
// Get the directory item offset
|
||||
uint directoryItemOffset = header.DirectoryItemOffset;
|
||||
if (directoryItemOffset < 0 || directoryItemOffset >= data.Length)
|
||||
if (header.PreloadBytes > 0)
|
||||
{
|
||||
// Create the preload directory mapping array
|
||||
file.PreloadDirectoryMappings = new DirectoryMapping[header.PreloadDirectoryEntryCount];
|
||||
|
||||
// Try to parse the preload directory mappings
|
||||
for (int i = 0; i < file.PreloadDirectoryMappings.Length; i++)
|
||||
{
|
||||
var directoryMapping = data.ReadType<DirectoryMapping>();
|
||||
if (directoryMapping == null)
|
||||
continue;
|
||||
|
||||
file.PreloadDirectoryMappings[i] = directoryMapping;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Directory Items
|
||||
|
||||
if (header.DirectoryItemCount > 0)
|
||||
{
|
||||
// Get the directory item offset
|
||||
uint directoryItemOffset = header.DirectoryItemOffset;
|
||||
if (directoryItemOffset < 0 || directoryItemOffset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Seek to the directory items
|
||||
data.Seek(directoryItemOffset, SeekOrigin.Begin);
|
||||
|
||||
// Create the directory item array
|
||||
file.DirectoryItems = new DirectoryItem[header.DirectoryItemCount];
|
||||
|
||||
// Try to parse the directory items
|
||||
for (int i = 0; i < file.DirectoryItems.Length; i++)
|
||||
{
|
||||
var directoryItem = ParseDirectoryItem(data);
|
||||
file.DirectoryItems[i] = directoryItem;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Footer
|
||||
|
||||
// Seek to the footer
|
||||
data.Seek(-8, SeekOrigin.End);
|
||||
|
||||
// Try to parse the footer
|
||||
var footer = data.ReadType<Footer>();
|
||||
if (footer?.Signature != FooterSignatureString)
|
||||
return null;
|
||||
|
||||
// Seek to the directory items
|
||||
data.Seek(directoryItemOffset, SeekOrigin.Begin);
|
||||
// Set the package footer
|
||||
file.Footer = footer;
|
||||
|
||||
// Create the directory item array
|
||||
file.DirectoryItems = new DirectoryItem[header.DirectoryItemCount];
|
||||
#endregion
|
||||
|
||||
// Try to parse the directory items
|
||||
for (int i = 0; i < file.DirectoryItems.Length; i++)
|
||||
{
|
||||
var directoryItem = ParseDirectoryItem(data);
|
||||
file.DirectoryItems[i] = directoryItem;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Footer
|
||||
|
||||
// Seek to the footer
|
||||
data.Seek(-8, SeekOrigin.End);
|
||||
|
||||
// Try to parse the footer
|
||||
var footer = data.ReadType<Footer>();
|
||||
if (footer?.Signature != FooterSignatureString)
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
|
||||
// Set the package footer
|
||||
file.Footer = footer;
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -20,37 +20,26 @@ namespace SabreTools.Serialization.Deserializers
|
||||
|
||||
try
|
||||
{
|
||||
// If the stream length and offset are invalid
|
||||
if (data.Length == 0 || data.Position < 0 || data.Position >= data.Length)
|
||||
return default;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors in getting position for compressed streams
|
||||
}
|
||||
|
||||
// Setup the serializer and the reader
|
||||
var serializer = new XmlSerializer(typeof(T));
|
||||
var settings = new XmlReaderSettings
|
||||
{
|
||||
CheckCharacters = false,
|
||||
// Setup the serializer and the reader
|
||||
var serializer = new XmlSerializer(typeof(T));
|
||||
var settings = new XmlReaderSettings
|
||||
{
|
||||
CheckCharacters = false,
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
DtdProcessing = DtdProcessing.Ignore,
|
||||
DtdProcessing = DtdProcessing.Ignore,
|
||||
#endif
|
||||
ValidationFlags = XmlSchemaValidationFlags.None,
|
||||
ValidationType = ValidationType.None,
|
||||
};
|
||||
var streamReader = new StreamReader(data);
|
||||
var xmlReader = XmlReader.Create(streamReader, settings);
|
||||
ValidationFlags = XmlSchemaValidationFlags.None,
|
||||
ValidationType = ValidationType.None,
|
||||
};
|
||||
var streamReader = new StreamReader(data);
|
||||
var xmlReader = XmlReader.Create(streamReader, settings);
|
||||
|
||||
// Perform the deserialization and return
|
||||
try
|
||||
{
|
||||
// Perform the deserialization and return
|
||||
return (T?)serializer.Deserialize(xmlReader);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Absorb all exceptions
|
||||
// Ignore the actual error
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user