SerializedFile write support

This commit is contained in:
Jeremy Pritts 2023-09-02 02:01:10 -04:00
parent 546bd80a72
commit 15f53a27ec
5 changed files with 181 additions and 50 deletions

View File

@ -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));
});
}
}

View File

@ -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)
{

View File

@ -5,7 +5,7 @@ namespace AssetRipper.IO.Files.SerializedFiles.Parser
/// <summary>
/// The file header is found at the beginning of an asset file. The header is always using big endian byte order.
/// </summary>
public sealed class SerializedFileHeader
public sealed record class SerializedFileHeader
{
/// <summary>
/// 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);
}
}
}

View File

@ -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<FileIdentifier> Dependencies => m_dependencies;
public ReadOnlySpan<ObjectInfo> Objects => m_objects;
@ -29,36 +60,6 @@ namespace AssetRipper.IO.Files.SerializedFiles
public ReadOnlySpan<SerializedTypeReference> 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,
};
}
}
}

View File

@ -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<FileIdentifier> Dependencies { get; } = new();
public List<ObjectInfo> Objects { get; } = new();
public List<SerializedType> Types { get; } = new();
public List<SerializedTypeReference> RefTypes { get; } = new();
public bool HasTypeTree { get; set; }
public SerializedFile Build()
{
return SerializedFile.FromBuilder(this);
}
}