Files

285 lines
9.4 KiB
C#
Raw Permalink Normal View History

2024-11-19 01:04:50 -05:00
using System.IO;
using System.Text;
2025-09-26 13:06:18 -04:00
using SabreTools.Data.Models.WAD3;
2024-11-19 01:04:50 -05:00
using SabreTools.IO.Extensions;
2026-03-24 19:17:25 -04:00
using SabreTools.Numerics.Extensions;
2025-09-26 13:06:18 -04:00
using static SabreTools.Data.Models.WAD3.Constants;
2024-11-19 01:04:50 -05:00
2026-01-27 12:03:01 -05:00
#pragma warning disable IDE0017 // Simplify object initialization
2025-09-26 14:57:20 -04:00
namespace SabreTools.Serialization.Readers
2024-11-19 01:04:50 -05:00
{
public class WAD3 : BaseBinaryReader<Data.Models.WAD3.File>
2024-11-19 01:04:50 -05:00
{
/// <inheritdoc/>
2025-09-26 13:06:18 -04:00
public override Data.Models.WAD3.File? Deserialize(Stream? data)
2024-11-19 01:04:50 -05:00
{
// If the data is invalid
2026-01-25 14:30:18 -05:00
if (data is null || !data.CanRead)
2024-11-19 01:04:50 -05:00
return null;
try
{
2025-08-19 09:09:33 -04:00
// Cache the current offset
long initialOffset = data.Position;
// Create a new Half-Life Texture Package to fill
2025-09-26 13:06:18 -04:00
var file = new Data.Models.WAD3.File();
2024-11-19 01:04:50 -05:00
#region Header
2024-11-19 01:04:50 -05:00
// Try to parse the header
2024-12-16 23:08:45 -05:00
var header = ParseHeader(data);
if (header.Signature != SignatureString)
return null;
2024-11-19 01:04:50 -05:00
// Set the package header
file.Header = header;
2024-11-19 01:04:50 -05:00
#endregion
2024-11-19 01:04:50 -05:00
#region Directory Entries
2024-11-19 01:04:50 -05:00
// Get the directory offset
2025-08-19 09:09:33 -04:00
long dirOffset = initialOffset + header.DirOffset;
if (dirOffset < initialOffset || dirOffset >= data.Length)
return null;
2024-11-19 01:04:50 -05:00
// Seek to the lump offset
2025-10-27 22:43:56 -04:00
data.SeekIfPossible(dirOffset, SeekOrigin.Begin);
2024-11-19 01:04:50 -05:00
// Create the lump array
file.DirEntries = new DirEntry[header.NumDirs];
for (int i = 0; i < header.NumDirs; i++)
{
2024-12-16 23:08:45 -05:00
file.DirEntries[i] = ParseDirEntry(data);
}
2024-11-19 01:04:50 -05:00
#endregion
2024-11-19 01:04:50 -05:00
#region File Entries
2024-11-19 01:04:50 -05:00
// Create the file entry array
2024-11-29 20:48:50 -05:00
file.FileEntries = new FileEntry[header.NumDirs];
for (int i = 0; i < header.NumDirs; i++)
{
var dirEntry = file.DirEntries[i];
2026-01-25 14:30:18 -05:00
if (dirEntry is null)
continue;
2024-11-19 01:04:50 -05:00
// TODO: Handle compressed entries
if (dirEntry.Compression != 0)
continue;
// Get the file entry offset
2025-08-19 09:09:33 -04:00
long fileEntryOffset = initialOffset + dirEntry.Offset;
if (fileEntryOffset < initialOffset || fileEntryOffset >= data.Length)
continue;
// Seek to the file entry offset
2025-10-27 22:43:56 -04:00
data.SeekIfPossible(fileEntryOffset, SeekOrigin.Begin);
// Try to parse the file entry
var fileEntry = ParseFileEntry(data, dirEntry.Type);
2026-01-25 14:32:49 -05:00
if (fileEntry is not null)
file.FileEntries[i] = fileEntry;
}
2024-11-19 01:04:50 -05:00
#endregion
2024-11-19 01:04:50 -05:00
return file;
}
catch
{
// Ignore the actual error
return null;
}
2024-11-19 01:04:50 -05:00
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a CharInfo
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled CharInfo on success, null on error</returns>
public static CharInfo ParseCharInfo(Stream data)
{
var obj = new CharInfo();
obj.StartOffset = data.ReadUInt16LittleEndian();
obj.CharWidth = data.ReadUInt16LittleEndian();
return obj;
}
/// <summary>
/// Parse a Stream into a DirEntry
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled DirEntry on success, null on error</returns>
public static DirEntry ParseDirEntry(Stream data)
{
var obj = new DirEntry();
obj.Offset = data.ReadUInt32LittleEndian();
obj.DiskLength = data.ReadUInt32LittleEndian();
obj.Length = data.ReadUInt32LittleEndian();
obj.Type = (FileType)data.ReadByteValue();
obj.Compression = data.ReadByteValue();
obj.Padding = data.ReadUInt16LittleEndian();
byte[] name = data.ReadBytes(16);
obj.Name = Encoding.ASCII.GetString(name).TrimEnd('\0');
return obj;
}
/// <summary>
/// Parse a Stream into a FileEntry
2024-11-19 01:04:50 -05:00
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="type">File entry type</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled FileEntry on success, null on error</returns>
public static FileEntry? ParseFileEntry(Stream data, FileType type)
2024-11-19 01:04:50 -05:00
{
return type switch
{
FileType.Spraydecal
or FileType.Miptex => ParseMipTex(data),
FileType.Qpic => ParseQpicImage(data),
FileType.Font => ParseFont(data),
_ => null,
};
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a Font
2024-11-19 01:04:50 -05:00
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled Font on success, null on error</returns>
public static Font ParseFont(Stream data)
2024-11-19 01:04:50 -05:00
{
2024-12-16 23:08:45 -05:00
var obj = new Font();
obj.Width = data.ReadUInt32LittleEndian();
obj.Height = data.ReadUInt32LittleEndian();
obj.RowCount = data.ReadUInt32LittleEndian();
obj.RowHeight = data.ReadUInt32LittleEndian();
obj.FontInfo = new CharInfo[256];
for (int i = 0; i < obj.FontInfo.Length; i++)
2024-11-19 01:04:50 -05:00
{
2024-12-16 23:08:45 -05:00
obj.FontInfo[i] = ParseCharInfo(data);
2024-11-19 01:04:50 -05:00
}
2026-01-25 16:15:05 -05:00
2024-12-16 23:08:45 -05:00
obj.Data = new byte[obj.Height][];
for (int i = 0; i < obj.Height; i++)
2024-11-19 01:04:50 -05:00
{
2024-12-16 23:08:45 -05:00
obj.Data[i] = data.ReadBytes((int)obj.Width);
2024-11-19 01:04:50 -05:00
}
2026-01-25 16:15:05 -05:00
2024-12-16 23:08:45 -05:00
obj.ColorsUsed = data.ReadUInt16LittleEndian();
obj.Palette = new byte[obj.ColorsUsed][];
for (int i = 0; i < obj.ColorsUsed; i++)
2024-11-19 01:04:50 -05:00
{
2024-12-16 23:08:45 -05:00
obj.Palette[i] = data.ReadBytes(3);
2024-11-19 01:04:50 -05:00
}
2024-12-16 23:08:45 -05:00
return obj;
2024-11-19 01:04:50 -05:00
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a Header
2024-11-19 01:04:50 -05:00
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled Header on success, null on error</returns>
public static Header ParseHeader(Stream data)
2024-11-19 01:04:50 -05:00
{
2024-12-16 23:08:45 -05:00
var obj = new Header();
byte[] signature = data.ReadBytes(4);
obj.Signature = Encoding.ASCII.GetString(signature);
obj.NumDirs = data.ReadUInt32LittleEndian();
obj.DirOffset = data.ReadUInt32LittleEndian();
return obj;
}
2024-11-19 01:04:50 -05:00
2024-12-16 23:08:45 -05:00
/// <summary>
/// Parse a Stream into a MipMap
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled MipMap on success, null on error</returns>
public static MipMap ParseMipMap(Stream data, uint width, uint height)
{
var obj = new MipMap();
obj.Data = new byte[width][];
2024-11-19 01:04:50 -05:00
for (int i = 0; i < width; i++)
{
2024-12-16 23:08:45 -05:00
obj.Data[i] = data.ReadBytes((int)height);
2024-11-19 01:04:50 -05:00
}
2024-12-16 23:08:45 -05:00
return obj;
2024-11-19 01:04:50 -05:00
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a MipTex
2024-11-19 01:04:50 -05:00
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled MipTex on success, null on error</returns>
public static MipTex ParseMipTex(Stream data)
2024-11-19 01:04:50 -05:00
{
2024-12-16 23:08:45 -05:00
var obj = new MipTex();
2024-11-19 01:04:50 -05:00
2024-12-16 23:08:45 -05:00
byte[] nameBytes = data.ReadBytes(16);
obj.Name = Encoding.ASCII.GetString(nameBytes).TrimEnd('\0');
obj.Width = data.ReadUInt32LittleEndian();
obj.Height = data.ReadUInt32LittleEndian();
obj.MipOffsets = new uint[4];
for (int i = 0; i < obj.MipOffsets.Length; i++)
{
obj.MipOffsets[i] = data.ReadUInt32LittleEndian();
}
2026-01-25 16:15:05 -05:00
2024-12-16 23:08:45 -05:00
obj.MipImages = new MipMap[4];
for (int i = 0; i < obj.MipImages.Length; i++)
2024-11-19 01:04:50 -05:00
{
2024-12-16 23:08:45 -05:00
obj.MipImages[i] = ParseMipMap(data, obj.Width, obj.Height);
2024-11-19 01:04:50 -05:00
}
2026-01-25 16:15:05 -05:00
2024-12-16 23:08:45 -05:00
obj.ColorsUsed = data.ReadUInt16LittleEndian();
obj.Palette = new byte[obj.ColorsUsed][];
for (int i = 0; i < obj.ColorsUsed; i++)
2024-11-19 01:04:50 -05:00
{
2024-12-16 23:08:45 -05:00
obj.Palette[i] = data.ReadBytes(3);
2024-11-19 01:04:50 -05:00
}
2024-12-16 23:08:45 -05:00
return obj;
2024-11-19 01:04:50 -05:00
}
/// <summary>
2024-12-16 23:08:45 -05:00
/// Parse a Stream into a QpicImage
2024-11-19 01:04:50 -05:00
/// </summary>
/// <param name="data">Stream to parse</param>
2024-12-16 23:08:45 -05:00
/// <returns>Filled QpicImage on success, null on error</returns>
public static QpicImage ParseQpicImage(Stream data)
2024-11-19 01:04:50 -05:00
{
2024-12-16 23:08:45 -05:00
var obj = new QpicImage();
obj.Width = data.ReadUInt32LittleEndian();
obj.Height = data.ReadUInt32LittleEndian();
obj.Data = new byte[obj.Height][];
for (int i = 0; i < obj.Height; i++)
2024-11-19 01:04:50 -05:00
{
2024-12-16 23:08:45 -05:00
obj.Data[i] = data.ReadBytes((int)obj.Width);
2024-11-19 01:04:50 -05:00
}
2026-01-25 16:15:05 -05:00
2024-12-16 23:08:45 -05:00
obj.ColorsUsed = data.ReadUInt16LittleEndian();
obj.Palette = new byte[obj.ColorsUsed][];
for (int i = 0; i < obj.ColorsUsed; i++)
2024-11-19 01:04:50 -05:00
{
2024-12-16 23:08:45 -05:00
obj.Palette[i] = data.ReadBytes(3);
2024-11-19 01:04:50 -05:00
}
2024-12-16 23:08:45 -05:00
return obj;
2024-11-19 01:04:50 -05:00
}
}
}