mirror of
https://github.com/AssetRipper/AssetRipper.git
synced 2025-12-11 20:15:29 +01:00
SerializedFile write support
This commit is contained in:
parent
546bd80a72
commit
15f53a27ec
93
Source/AssetRipper.IO.Files.Tests/SerializedFileTests.cs
Normal file
93
Source/AssetRipper.IO.Files.Tests/SerializedFileTests.cs
Normal 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));
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user