Files
2026-05-12 20:33:53 -04:00

137 lines
5.0 KiB
C#

using System;
using System.IO;
using System.Text;
using SabreTools.Data.Models.XDVDFS;
using SabreTools.Numerics.Extensions;
namespace SabreTools.Serialization.Writers
{
public class XDVDFS : IFileWriter<Volume>
{
/// <inheritdoc/>
public bool Debug { get; set; } = false;
/// <inheritdoc/>
public bool SerializeFile(Volume? obj, string? path)
{
if (string.IsNullOrEmpty(path))
return false;
if (obj is null || !ValidateVolume(obj))
return false;
// Create the file stream
using var fs = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None);
fs.Write(obj.ReservedArea);
SerializeVolumeDescriptor(fs, obj.VolumeDescriptor);
if (obj.LayoutDescriptor is not null)
SerializeLayoutDescriptor(fs, obj.LayoutDescriptor);
// Loop over all directory descriptors in order of offset
uint[] keys = new uint[obj.DirectoryDescriptors.Count];
obj.DirectoryDescriptors.Keys.CopyTo(keys, 0);
Array.Sort(keys);
// Write directory descriptors
for (int i = 0; i < keys.Length; i++)
{
uint sectorOffset = keys[i];
fs.Seek(sectorOffset * Constants.SectorSize, SeekOrigin.Begin);
SerializeDirectoryDescriptor(fs, obj.DirectoryDescriptors[sectorOffset]);
}
fs.Flush();
return true;
}
/// <summary>
/// Validate that Volume is writable
/// </summary>
public static bool ValidateVolume(Volume? obj)
{
// If the data is invalid
if (obj?.VolumeDescriptor?.StartSignature is null)
return false;
// If the magic doesn't match
string magic = Encoding.ASCII.GetString(obj.VolumeDescriptor.StartSignature);
if (magic != Constants.VolumeDescriptorSignature)
return false;
// Validate model
if (obj.ReservedArea.Length != 0x10000)
return false;
if (obj.VolumeDescriptor.Reserved.Length != 1991)
return false;
if (obj.VolumeDescriptor.EndSignature.Length != 20)
return false;
if (obj.LayoutDescriptor is not null)
{
if (obj.LayoutDescriptor.Signature.Length != 24)
return false;
if (obj.LayoutDescriptor.Unused8Bytes.Length != 8)
return false;
if (obj.LayoutDescriptor.Reserved.Length != 1968)
return false;
}
return true;
}
public static void SerializeVolumeDescriptor(Stream stream, VolumeDescriptor obj)
{
stream.Write(obj.StartSignature);
stream.WriteLittleEndian(obj.RootOffset);
stream.WriteLittleEndian(obj.RootSize);
stream.WriteLittleEndian(obj.MasteringTimestamp);
stream.Write(obj.UnknownByte);
stream.Write(obj.Reserved);
stream.Write(obj.EndSignature);
}
public static void SerializeLayoutDescriptor(Stream stream, LayoutDescriptor obj)
{
stream.Write(obj.Signature);
stream.Write(obj.Unused8Bytes);
SerializeFourPartVersionType(stream, obj.XBLayoutVersion);
SerializeFourPartVersionType(stream, obj.XBPremasterVersion);
SerializeFourPartVersionType(stream, obj.XBGameDiscVersion);
SerializeFourPartVersionType(stream, obj.XBOther1Version);
SerializeFourPartVersionType(stream, obj.XBOther2Version);
SerializeFourPartVersionType(stream, obj.XBOther3Version);
stream.Write(obj.Reserved);
}
public static void SerializeFourPartVersionType(Stream stream, FourPartVersionType obj)
{
stream.WriteLittleEndian(obj.Major);
stream.WriteLittleEndian(obj.Minor);
stream.WriteLittleEndian(obj.Build);
stream.WriteLittleEndian(obj.Revision);
}
public static void SerializeDirectoryDescriptor(Stream stream, DirectoryDescriptor obj)
{
foreach (var dr in obj.DirectoryRecords)
SerializeDirectoryRecord(stream, dr);
if (obj.Padding is not null && obj.Padding.Length > 0)
stream.Write(obj.Padding);
}
public static void SerializeDirectoryRecord(Stream stream, DirectoryRecord obj)
{
stream.WriteLittleEndian(obj.LeftChildOffset);
stream.WriteLittleEndian(obj.RightChildOffset);
stream.WriteLittleEndian(obj.ExtentOffset);
stream.WriteLittleEndian(obj.ExtentSize);
stream.Write((byte)obj.FileFlags);
stream.Write(obj.FilenameLength);
stream.Write(obj.Filename);
if (obj.Padding is not null && obj.Padding.Length > 0)
stream.Write(obj.Padding);
}
}
}