diff --git a/Source/AssetRipper.IO.Files.Tests/SerializedFileTests.cs b/Source/AssetRipper.IO.Files.Tests/SerializedFileTests.cs
new file mode 100644
index 000000000..774c6a6cd
--- /dev/null
+++ b/Source/AssetRipper.IO.Files.Tests/SerializedFileTests.cs
@@ -0,0 +1,93 @@
+using AssetRipper.IO.Endian;
+using AssetRipper.IO.Files.SerializedFiles;
+using AssetRipper.IO.Files.SerializedFiles.Parser;
+using AssetRipper.IO.Files.Streams.Smart;
+
+namespace AssetRipper.IO.Files.Tests;
+
+internal class SerializedFileTests
+{
+ [Theory]
+ public void WritingSerializedFileDoesNotThrow(FormatVersion generation)
+ {
+ Assert.DoesNotThrow(() =>
+ {
+ SerializedFileBuilder builder = new() { Generation = generation };
+ SerializedFile file = builder.Build();
+ using SmartStream stream = SmartStream.CreateMemory();
+ file.Write(stream);
+ });
+ }
+
+ [Theory]
+ public void WrittenSerializedFileCanBeRead(FormatVersion generation)
+ {
+ SerializedFileBuilder builder = new() { Generation = generation };
+ SerializedFile file = builder.Build();
+ using SmartStream stream = SmartStream.CreateMemory();
+ file.Write(stream);
+ stream.Flush();
+ stream.Position = 0;
+ Assert.That(SerializedFileScheme.Default.CanRead(stream));
+ }
+
+ [Theory]
+ public void ReadingAndWritingAreConsistent(FormatVersion generation)
+ {
+ SerializedFileBuilder builder = new() { Generation = generation };
+ SerializedFile original = builder.Build();
+ AssertReadingAndWritingAreConsistent(original);
+ }
+
+ private static void AssertReadingAndWritingAreConsistent(SerializedFile original)
+ {
+ SerializedFile read;
+ {
+ using SmartStream stream = SmartStream.CreateMemory();
+ original.Write(stream);
+ stream.Flush();
+ stream.Position = 0;
+ read = SerializedFileScheme.Default.Read(stream, original.FilePath, original.Name);
+ }
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(read.Generation, Is.EqualTo(original.Generation));
+ Assert.That(read.Version, Is.EqualTo(original.Version));
+ Assert.That(read.Platform, Is.EqualTo(original.Platform));
+ Assert.That(read.EndianType, Is.EqualTo(original.EndianType));
+ Assert.That(read.Flags, Is.EqualTo(original.Flags));
+ Assert.That(read.Dependencies.ToArray(), Is.EqualTo(original.Dependencies.ToArray()));
+ Assert.That(read.Objects.ToArray(), Is.EqualTo(original.Objects.ToArray()));
+ });
+ }
+
+ [Theory]
+ public void SerializeFileHeaderReadingMatchesWriting(FormatVersion generation)
+ {
+ SerializedFileHeader header = new()
+ {
+ Version = generation,
+ MetadataSize = 256,//arbitrary number greater than 0
+ };
+ using MemoryStream stream = new();
+ using (EndianWriter writer = new(stream, EndianType.BigEndian))
+ {
+ header.Write(writer);
+ }
+ stream.Flush();
+ Assert.That(stream.Position, Is.GreaterThan(0));
+ stream.Position = 0;
+ SerializedFileHeader readHeader = new();
+ using (EndianReader reader = new(stream, EndianType.BigEndian))
+ {
+ readHeader.Read(reader);
+ }
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(stream.Position, Is.EqualTo(stream.Length));
+ Assert.That(readHeader, Is.EqualTo(header));
+ });
+ }
+}
diff --git a/Source/AssetRipper.IO.Files/SerializedFiles/Parser/FileIdentifier.cs b/Source/AssetRipper.IO.Files/SerializedFiles/Parser/FileIdentifier.cs
index 527927208..c8361e5ca 100644
--- a/Source/AssetRipper.IO.Files/SerializedFiles/Parser/FileIdentifier.cs
+++ b/Source/AssetRipper.IO.Files/SerializedFiles/Parser/FileIdentifier.cs
@@ -39,7 +39,7 @@ namespace AssetRipper.IO.Files.SerializedFiles.Parser
PathName = FilenameUtils.FixFileIdentifier(PathNameOrigin);
}
- public void Write(SerializedWriter writer)
+ public readonly void Write(SerializedWriter writer)
{
if (HasAssetPath(writer.Generation))
{
@@ -53,7 +53,7 @@ namespace AssetRipper.IO.Files.SerializedFiles.Parser
writer.WriteStringZeroTerm(PathNameOrigin);
}
- public string GetFilePath()
+ public readonly string GetFilePath()
{
if (Type == AssetType.Meta)
{
@@ -62,7 +62,7 @@ namespace AssetRipper.IO.Files.SerializedFiles.Parser
return PathName;
}
- public override string? ToString()
+ public override readonly string? ToString()
{
if (Type == AssetType.Meta)
{
diff --git a/Source/AssetRipper.IO.Files/SerializedFiles/Parser/SerializedFileHeader.cs b/Source/AssetRipper.IO.Files/SerializedFiles/Parser/SerializedFileHeader.cs
index c49c94c67..dc6f3cf0e 100644
--- a/Source/AssetRipper.IO.Files/SerializedFiles/Parser/SerializedFileHeader.cs
+++ b/Source/AssetRipper.IO.Files/SerializedFiles/Parser/SerializedFileHeader.cs
@@ -5,7 +5,7 @@ namespace AssetRipper.IO.Files.SerializedFiles.Parser
///
/// The file header is found at the beginning of an asset file. The header is always using big endian byte order.
///
- public sealed class SerializedFileHeader
+ public sealed record class SerializedFileHeader
{
///
/// Size of the metadata parts of the file
@@ -129,7 +129,7 @@ namespace AssetRipper.IO.Files.SerializedFiles.Parser
if (HasLargeFilesSupport(Version))
{
writer.Write(0);
- writer.Write(0);
+ writer.Write(0u);
}
else
{
@@ -143,7 +143,7 @@ namespace AssetRipper.IO.Files.SerializedFiles.Parser
//0x0c
if (HasLargeFilesSupport(Version))
{
- writer.Write(0);
+ writer.Write(0u);
}
else
{
@@ -163,7 +163,7 @@ namespace AssetRipper.IO.Files.SerializedFiles.Parser
writer.Write((uint)MetadataSize);
writer.Write(FileSize);
writer.Write(DataOffset);
- writer.Write((long)0);
+ writer.Write(0L);
}
}
}
diff --git a/Source/AssetRipper.IO.Files/SerializedFiles/SerializedFile.cs b/Source/AssetRipper.IO.Files/SerializedFiles/SerializedFile.cs
index 9228e3379..74d3c4391 100644
--- a/Source/AssetRipper.IO.Files/SerializedFiles/SerializedFile.cs
+++ b/Source/AssetRipper.IO.Files/SerializedFiles/SerializedFile.cs
@@ -21,7 +21,38 @@ namespace AssetRipper.IO.Files.SerializedFiles
public FormatVersion Generation { get; private set; }
public UnityVersion Version { get; private set; }
public BuildTarget Platform { get; private set; }
- public TransferInstructionFlags Flags { get; private set; }
+ public TransferInstructionFlags Flags
+ {
+ get
+ {
+ TransferInstructionFlags flags;
+ if (SerializedFileMetadata.HasPlatform(Generation) && Platform == BuildTarget.NoTarget)
+ {
+ if (FilePath.EndsWith(".unity", StringComparison.Ordinal))
+ {
+ flags = TransferInstructionFlags.SerializeEditorMinimalScene;
+ }
+ else
+ {
+ flags = TransferInstructionFlags.NoTransferInstructionFlags;
+ }
+ }
+ else
+ {
+ flags = TransferInstructionFlags.SerializeGameRelease;
+ }
+
+ if (FilenameUtils.IsEngineResource(Name) || (Generation < FormatVersion.Unknown_10 && FilenameUtils.IsBuiltinExtra(Name)))
+ {
+ flags |= TransferInstructionFlags.IsBuiltinResourcesFile;
+ }
+ if (EndianType is EndianType.BigEndian)
+ {
+ flags |= TransferInstructionFlags.SwapEndianess;
+ }
+ return flags;
+ }
+ }
public EndianType EndianType { get; private set; }
public ReadOnlySpan Dependencies => m_dependencies;
public ReadOnlySpan Objects => m_objects;
@@ -29,36 +60,6 @@ namespace AssetRipper.IO.Files.SerializedFiles
public ReadOnlySpan RefTypes => m_refTypes;
public bool HasTypeTree { get; private set; }
- private static TransferInstructionFlags GetFlags(SerializedFileHeader header, SerializedFileMetadata metadata, string name, string filePath)
- {
- TransferInstructionFlags flags;
- if (SerializedFileMetadata.HasPlatform(header.Version) && metadata.TargetPlatform == BuildTarget.NoTarget)
- {
- if (filePath.EndsWith(".unity", StringComparison.Ordinal))
- {
- flags = TransferInstructionFlags.SerializeEditorMinimalScene;
- }
- else
- {
- flags = TransferInstructionFlags.NoTransferInstructionFlags;
- }
- }
- else
- {
- flags = TransferInstructionFlags.SerializeGameRelease;
- }
-
- if (FilenameUtils.IsEngineResource(name) || (header.Version < FormatVersion.Unknown_10 && FilenameUtils.IsBuiltinExtra(name)))
- {
- flags |= TransferInstructionFlags.IsBuiltinResourcesFile;
- }
- if (header.Endianess || metadata.SwapEndianess)
- {
- flags |= TransferInstructionFlags.SwapEndianess;
- }
- return flags;
- }
-
private static EndianType GetEndianType(SerializedFileHeader header, SerializedFileMetadata metadata)
{
bool swapEndianess = SerializedFileHeader.HasEndianess(header.Version) ? header.Endianess : metadata.SwapEndianess;
@@ -109,7 +110,6 @@ namespace AssetRipper.IO.Files.SerializedFiles
Generation = header.Version;
Version = metadata.UnityVersion;
Platform = metadata.TargetPlatform;
- Flags = GetFlags(header, metadata, Name, FilePath);
EndianType = GetEndianType(header, metadata);
m_dependencies = metadata.Externals;
m_objects = metadata.Object;
@@ -121,14 +121,14 @@ namespace AssetRipper.IO.Files.SerializedFiles
public override void Write(Stream stream)
{
long initialPosition = stream.Position;
- using SerializedWriter writer = new(stream, EndianType, Generation, Version);
SerializedFileHeader header = new()
{
Version = Generation,
Endianess = EndianType == EndianType.BigEndian,
};
- header.Write(writer);
+ header.Write(new EndianWriter(stream, EndianType.BigEndian));
+ using SerializedWriter writer = new(stream, EndianType, Generation, Version);
SerializedFileMetadata metadata = new()
{
UnityVersion = Version,
@@ -144,30 +144,30 @@ namespace AssetRipper.IO.Files.SerializedFiles
long objectDataPosition;
if (SerializedFileMetadata.IsMetadataAtTheEnd(Generation))
{
- metadataPosition = stream.Position;
- metadata.Write(writer);
- metadataSize = stream.Position - metadataPosition;
AlignStream(writer);//Object data must always be aligned.
objectDataPosition = stream.Position;
WriteObjectData(writer, metadata.Object);
+ metadataPosition = stream.Position;
+ metadata.Write(writer);
+ metadataSize = stream.Position - metadataPosition;
}
else
{
- AlignStream(writer);//Object data must always be aligned.
- objectDataPosition = stream.Position;
- WriteObjectData(writer, metadata.Object);
metadataPosition = stream.Position;
metadata.Write(writer);
metadataSize = stream.Position - metadataPosition;
+ AlignStream(writer);//Object data must always be aligned.
+ objectDataPosition = stream.Position;
+ WriteObjectData(writer, metadata.Object);
}
-
+
long finalPosition = stream.Position;
stream.Position = initialPosition;
header.FileSize = finalPosition - initialPosition;
header.MetadataSize = metadataSize;
header.DataOffset = objectDataPosition - initialPosition;
- header.Write(writer);
+ header.Write(new EndianWriter(stream, EndianType.BigEndian));
stream.Position = finalPosition;
@@ -177,7 +177,7 @@ namespace AssetRipper.IO.Files.SerializedFiles
{
if (objectInfo.ObjectData is not null)
{
- writer.Write(objectInfo.ObjectData);
+ writer.Write(objectInfo.ObjectData);
}
AlignStream(writer);
@@ -234,5 +234,21 @@ namespace AssetRipper.IO.Files.SerializedFiles
SmartStream stream = SmartStream.OpenRead(filePath);
return SerializedFileScheme.Default.Read(stream, filePath, fileName);
}
+
+ public static SerializedFile FromBuilder(SerializedFileBuilder builder)
+ {
+ return new()
+ {
+ Generation = builder.Generation,
+ Version = builder.Version,
+ Platform = builder.Platform,
+ EndianType = builder.EndianType,
+ m_dependencies = builder.Dependencies.ToArray(),
+ m_objects = builder.Objects.ToArray(),
+ m_types = builder.Types.ToArray(),
+ m_refTypes = builder.RefTypes.ToArray(),
+ HasTypeTree = builder.HasTypeTree,
+ };
+ }
}
}
diff --git a/Source/AssetRipper.IO.Files/SerializedFiles/SerializedFileBuilder.cs b/Source/AssetRipper.IO.Files/SerializedFiles/SerializedFileBuilder.cs
new file mode 100644
index 000000000..4dc3dae13
--- /dev/null
+++ b/Source/AssetRipper.IO.Files/SerializedFiles/SerializedFileBuilder.cs
@@ -0,0 +1,22 @@
+using AssetRipper.IO.Endian;
+using AssetRipper.IO.Files.SerializedFiles.Parser;
+
+namespace AssetRipper.IO.Files.SerializedFiles;
+
+public sealed class SerializedFileBuilder
+{
+ public FormatVersion Generation { get; set; }
+ public UnityVersion Version { get; set; }
+ public BuildTarget Platform { get; set; }
+ public EndianType EndianType { get; set; }
+ public List Dependencies { get; } = new();
+ public List Objects { get; } = new();
+ public List Types { get; } = new();
+ public List RefTypes { get; } = new();
+ public bool HasTypeTree { get; set; }
+
+ public SerializedFile Build()
+ {
+ return SerializedFile.FromBuilder(this);
+ }
+}