XDVDFS FileWriter (#83)

This commit is contained in:
Deterous
2026-04-15 21:41:55 +09:00
committed by GitHub
parent c646141bdf
commit dab850103b
7 changed files with 193 additions and 7 deletions

View File

@@ -19,7 +19,7 @@ namespace SabreTools.Data.Models.XDVDFS
/// Seemingly unused 8 bytes after the signature, should be zeroed
/// </summary>
/// <remarks>8 bytes</remarks>
public byte[] Unusued8Bytes { get; set; } = new byte[8];
public byte[] Unused8Bytes { get; set; } = new byte[8];
/// <summary>
/// Version number of xblayout(?) tool used to master the filesystem

View File

@@ -23,6 +23,7 @@ namespace SabreTools.Data.Models.XDVDFS
/// <summary>
/// UInt32 size of the root directory descriptor in bytes
/// </summary>
/// <remarks>Little-endian</remarks>
public uint RootSize { get; set; }
/// <summary>

View File

@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.IO;
using SabreTools.Data.Models.XDVDFS;
using SabreTools.IO.Extensions;
using SabreTools.Matching;
using SabreTools.Numerics.Extensions;
#pragma warning disable IDE0017 // Simplify object initialization
@@ -94,7 +95,7 @@ namespace SabreTools.Serialization.Readers
var signature = System.Text.Encoding.ASCII.GetString(obj.Signature);
if (!signature.Equals(Constants.LayoutDescriptorSignature))
return null;
obj.Unusued8Bytes = data.ReadBytes(8);
obj.Unused8Bytes = data.ReadBytes(8);
obj.XBLayoutVersion = ParseFourPartVersionType(data);
obj.XBPremasterVersion = ParseFourPartVersionType(data);
@@ -208,7 +209,8 @@ namespace SabreTools.Serialization.Readers
// If invalid record read or next descriptor cannot fit in the current sector, skip ahead
if (dr is null || (data.Position - initialOffset) % Constants.SectorSize > (Constants.SectorSize - Constants.MinimumRecordLength))
{
data.SeekIfPossible(Constants.SectorSize - (int)((data.Position - initialOffset) % Constants.SectorSize), SeekOrigin.Current);
int padding = Constants.SectorSize - (int)((data.Position - initialOffset) % Constants.SectorSize);
obj.Padding = data.ReadBytes(padding);
continue;
}
@@ -219,6 +221,7 @@ namespace SabreTools.Serialization.Readers
obj.DirectoryRecords = [.. records];
// Previously set padding assumed to be all 0xFF up to sector boundaries
int remainder = Constants.SectorSize - (int)(size % Constants.SectorSize);
if (remainder > 0 && remainder < Constants.SectorSize)
obj.Padding = data.ReadBytes(remainder);
@@ -235,11 +238,12 @@ namespace SabreTools.Serialization.Readers
{
var obj = new DirectoryRecord();
obj.LeftChildOffset = data.ReadUInt16LittleEndian();
obj.RightChildOffset = data.ReadUInt16LittleEndian();
if (obj.LeftChildOffset == 0xFFFF && obj.RightChildOffset == 0xFFFF)
byte[] start = data.PeekBytes(4);
if (start.EqualsExactly([0xFF, 0xFF, 0xFF, 0xFF]))
return null;
obj.LeftChildOffset = data.ReadUInt16LittleEndian();
obj.RightChildOffset = data.ReadUInt16LittleEndian();
obj.ExtentOffset = data.ReadUInt32LittleEndian();
obj.ExtentSize = data.ReadUInt32LittleEndian();
obj.FileFlags = (FileFlags)data.ReadByteValue();

View File

@@ -0,0 +1,16 @@
using System.IO;
using Xunit;
namespace SabreTools.Serialization.Writers.Test
{
public class XDVDFSTests
{
[Fact]
public void SerializeFile_Null_False()
{
var serializer = new XDVDFS();
bool actual = serializer.SerializeFile(null, null);
Assert.False(actual);
}
}
}

View File

@@ -0,0 +1,135 @@
using System;
using System.IO;
using System.Text;
using SabreTools.Data.Models.XDVDFS;
using SabreTools.IO;
using SabreTools.IO.Extensions;
using SabreTools.Numerics;
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);
SerializeHeader(fs, obj);
// 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;
}
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 SerializeHeader(Stream stream, Volume obj)
{
stream.Write(obj.ReservedArea, 0, obj.ReservedArea.Length);
stream.Write(obj.VolumeDescriptor.StartSignature, 0, obj.VolumeDescriptor.StartSignature.Length);
stream.WriteLittleEndian(obj.VolumeDescriptor.RootOffset);
stream.WriteLittleEndian(obj.VolumeDescriptor.RootSize);
stream.WriteLittleEndian(obj.VolumeDescriptor.MasteringTimestamp);
stream.WriteByte(obj.VolumeDescriptor.UnknownByte);
stream.Write(obj.VolumeDescriptor.Reserved, 0, obj.VolumeDescriptor.Reserved.Length);
stream.Write(obj.VolumeDescriptor.EndSignature, 0, obj.VolumeDescriptor.EndSignature.Length);
if (obj.LayoutDescriptor is not null)
{
stream.Write(obj.LayoutDescriptor.Signature, 0, obj.LayoutDescriptor.Signature.Length);
stream.Write(obj.LayoutDescriptor.Unused8Bytes, 0, obj.LayoutDescriptor.Unused8Bytes.Length);
SerializeFourPartVersionType(stream, obj.LayoutDescriptor.XBLayoutVersion);
SerializeFourPartVersionType(stream, obj.LayoutDescriptor.XBPremasterVersion);
SerializeFourPartVersionType(stream, obj.LayoutDescriptor.XBGameDiscVersion);
SerializeFourPartVersionType(stream, obj.LayoutDescriptor.XBOther1Version);
SerializeFourPartVersionType(stream, obj.LayoutDescriptor.XBOther2Version);
SerializeFourPartVersionType(stream, obj.LayoutDescriptor.XBOther3Version);
stream.Write(obj.LayoutDescriptor.Reserved, 0, obj.LayoutDescriptor.Reserved.Length);
}
}
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, 0, obj.Padding.Length);
}
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.WriteByte((byte)obj.FileFlags);
stream.WriteByte(obj.FilenameLength);
stream.Write(obj.Filename, 0, obj.Filename.Length);
if (obj.Padding is not null && obj.Padding.Length > 0)
stream.Write(obj.Padding, 0, obj.Padding.Length);
}
}
}

View File

@@ -68,7 +68,7 @@ namespace SabreTools.Wrappers
builder.AppendLine(" -------------------------");
builder.AppendLine(Encoding.ASCII.GetString(ld.Signature), " Signature");
builder.AppendLine(ld.Unusued8Bytes, " Unusued 8 Bytes");
builder.AppendLine(ld.Unused8Bytes, " Unusued 8 Bytes");
builder.AppendLine(GetVersionString(ld.XBLayoutVersion), " xblayout Version");
builder.AppendLine(GetVersionString(ld.XBPremasterVersion), " xbpremaster Version");
builder.AppendLine(GetVersionString(ld.XBGameDiscVersion), " xbgamedisc Version");

View File

@@ -0,0 +1,30 @@
using System;
using System.IO;
using SabreTools.Data.Models.XDVDFS;
using SabreTools.Numerics.Extensions;
namespace SabreTools.Wrappers
{
public partial class XDVDFS : IWritable
{
/// <inheritdoc/>
public bool Write(string outputDirectory, bool includeDebug)
{
// Get the base path
string outputFilename = Filename is null
? Guid.NewGuid().ToString()
: Path.GetFileName(Filename);
outputFilename += ".xiso";
string outputPath = Path.Combine(outputDirectory, outputFilename);
var writer = new SabreTools.Serialization.Writers.XDVDFS();
if (!writer.SerializeFile(Model, outputPath))
{
if (includeDebug) Console.WriteLine("Model was invalid, cannot write!");
return false;
}
return true;
}
}
}