mirror of
https://github.com/AssetRipper/AssetRipper.git
synced 2025-12-11 20:15:29 +01:00
More efficient UnityGUID. Resolves #720
This commit is contained in:
parent
dbeba151d5
commit
2cad1dec77
@ -1,6 +1,8 @@
|
||||
using AssetRipper.IO.Endian;
|
||||
using AssetRipper.IO.Files.Extensions;
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
@ -8,14 +10,24 @@ namespace AssetRipper.IO.Files
|
||||
{
|
||||
public record struct UnityGUID : IEndianReadable, IEndianWritable
|
||||
{
|
||||
public UnityGUID(Guid guid) : this(ConvertSystemOrUnityBytes(guid.ToByteArray())) { }
|
||||
public UnityGUID(Guid guid)
|
||||
{
|
||||
Span<byte> guidData = stackalloc byte[16];
|
||||
bool success = guid.TryWriteBytes(guidData);
|
||||
Debug.Assert(success);
|
||||
ConvertSystemOrUnityBytes(guidData, guidData);
|
||||
Data0 = ReadUInt32LittleEndian(guidData, 0);
|
||||
Data1 = ReadUInt32LittleEndian(guidData, 1);
|
||||
Data2 = ReadUInt32LittleEndian(guidData, 2);
|
||||
Data3 = ReadUInt32LittleEndian(guidData, 3);
|
||||
}
|
||||
|
||||
public UnityGUID(ReadOnlySpan<byte> guidData)
|
||||
{
|
||||
Data0 = BinaryPrimitives.ReadUInt32LittleEndian(guidData.Slice(0, sizeof(uint)));
|
||||
Data1 = BinaryPrimitives.ReadUInt32LittleEndian(guidData.Slice(4, sizeof(uint)));
|
||||
Data2 = BinaryPrimitives.ReadUInt32LittleEndian(guidData.Slice(8, sizeof(uint)));
|
||||
Data3 = BinaryPrimitives.ReadUInt32LittleEndian(guidData.Slice(12, sizeof(uint)));
|
||||
Data0 = ReadUInt32LittleEndian(guidData, 0);
|
||||
Data1 = ReadUInt32LittleEndian(guidData, 1);
|
||||
Data2 = ReadUInt32LittleEndian(guidData, 2);
|
||||
Data3 = ReadUInt32LittleEndian(guidData, 3);
|
||||
}
|
||||
|
||||
public UnityGUID(uint data0, uint data1, uint data2, uint data3)
|
||||
@ -26,11 +38,26 @@ namespace AssetRipper.IO.Files
|
||||
Data3 = data3;
|
||||
}
|
||||
|
||||
public static UnityGUID NewGuid() => new UnityGUID(Guid.NewGuid().ToByteArray());
|
||||
public static UnityGUID NewGuid()
|
||||
{
|
||||
//This is not an acceptable way to convert between Unity and System Guids.
|
||||
//We only do it this way to efficiently get 16 random bytes.
|
||||
//We don't care about official Guid validity because Unity does not care either.
|
||||
Guid guid = Guid.NewGuid();
|
||||
ReadOnlySpan<Guid> guidSpan = MemoryMarshal.CreateReadOnlySpan(ref guid, 1);
|
||||
ReadOnlySpan<byte> byteSpan = MemoryMarshal.Cast<Guid, byte>(guidSpan);
|
||||
return new UnityGUID(byteSpan);
|
||||
}
|
||||
|
||||
public static explicit operator UnityGUID(Guid systemGuid) => new UnityGUID(systemGuid);
|
||||
|
||||
public static explicit operator Guid(UnityGUID unityGuid) => new Guid(ConvertSystemOrUnityBytes(unityGuid.ToByteArray()));
|
||||
public static explicit operator Guid(UnityGUID unityGuid)
|
||||
{
|
||||
Span<byte> span = stackalloc byte[16];
|
||||
unityGuid.Write(span);
|
||||
ConvertSystemOrUnityBytes(span, span);
|
||||
return new Guid(span);
|
||||
}
|
||||
|
||||
public void Read(EndianReader reader)
|
||||
{
|
||||
@ -48,13 +75,18 @@ namespace AssetRipper.IO.Files
|
||||
writer.Write(Data3);
|
||||
}
|
||||
|
||||
private void Write(Span<byte> span)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(span.Slice(0 * sizeof(uint), sizeof(uint)), Data0);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(span.Slice(1 * sizeof(uint), sizeof(uint)), Data1);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(span.Slice(2 * sizeof(uint), sizeof(uint)), Data2);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(span.Slice(3 * sizeof(uint), sizeof(uint)), Data3);
|
||||
}
|
||||
|
||||
public byte[] ToByteArray()
|
||||
{
|
||||
byte[] result = new byte[16];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(result.AsSpan(0, 4), Data0);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(result.AsSpan(4, 4), Data1);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(result.AsSpan(8, 4), Data2);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(result.AsSpan(12, 4), Data3);
|
||||
Write(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -83,46 +115,45 @@ namespace AssetRipper.IO.Files
|
||||
sb.Append(StringBuilderExtensions.ByteHexRepresentations[unchecked((int)(value >> 20) & 0xF0) | unchecked((int)(value >> 28) & 0xF)]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read little-endian <see cref="uint"/> from <see cref="Span{byte}"/>
|
||||
/// </summary>
|
||||
/// <param name="byteSpan">A span of bytes.</param>
|
||||
/// <param name="index">The ith <see cref="uint"/> in <paramref name="byteSpan"/>.</param>
|
||||
/// <returns></returns>
|
||||
private static uint ReadUInt32LittleEndian(ReadOnlySpan<byte> byteSpan, int index)
|
||||
{
|
||||
return BinaryPrimitives.ReadUInt32LittleEndian(byteSpan.Slice(index * sizeof(uint), sizeof(uint)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts system bytes to unity bytes, or the reverse
|
||||
/// </summary>
|
||||
/// <param name="originalBytes">A 16 byte input array</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException">Array is null</exception>
|
||||
/// <exception cref="ArgumentException">Array doesn't have 16 elements</exception>
|
||||
private static byte[] ConvertSystemOrUnityBytes(byte[] originalBytes)
|
||||
/// <param name="input">A 16 byte input span</param>
|
||||
/// <returns>The same span: <paramref name="input"/></returns>
|
||||
/// <exception cref="ArgumentException">Span doesn't have 16 elements</exception>
|
||||
private static void ConvertSystemOrUnityBytes(scoped ReadOnlySpan<byte> input, scoped Span<byte> output)
|
||||
{
|
||||
if (originalBytes is null)
|
||||
if (input.Length != 16)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(originalBytes));
|
||||
throw new ArgumentException($"Invalid length: {input.Length}", nameof(input));
|
||||
}
|
||||
if (output.Length != 16)
|
||||
{
|
||||
throw new ArgumentException($"Invalid length: {output.Length}", nameof(output));
|
||||
}
|
||||
|
||||
if (originalBytes.Length != 16)
|
||||
{
|
||||
throw new ArgumentException($"Invalid length: {originalBytes.Length}", nameof(originalBytes));
|
||||
}
|
||||
|
||||
byte[] newBytes = new byte[16];
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
newBytes[i] = originalBytes[3 - i];
|
||||
}
|
||||
newBytes[4] = originalBytes[5];
|
||||
newBytes[5] = originalBytes[4];
|
||||
newBytes[6] = originalBytes[7];
|
||||
newBytes[7] = originalBytes[6];
|
||||
for (int i = 8; i < 16; i++)
|
||||
{
|
||||
newBytes[i] = originalBytes[i];
|
||||
}
|
||||
//Unity Guid's are in big endian, so the bytes have to be flipped for multibyte fields
|
||||
(output[0], output[1], output[2], output[3]) = (input[3], input[2], input[1], input[0]);
|
||||
(output[4], output[5]) = (input[5], input[4]);
|
||||
(output[6], output[7]) = (input[7], input[6]);
|
||||
input.Slice(8).CopyTo(output.Slice(8));
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
//AB becomes BA
|
||||
byte value = newBytes[i];
|
||||
newBytes[i] = (byte)(unchecked((int)(value << 4) & 0xF0) | unchecked((int)(value >> 4) & 0xF));
|
||||
uint value = output[i];
|
||||
output[i] = (byte)(unchecked((value << 4) & 0xF0) | unchecked((value >> 4) & 0xF));
|
||||
}
|
||||
|
||||
return newBytes;
|
||||
}
|
||||
|
||||
public static UnityGUID Parse(string guidString) => new UnityGUID(Guid.Parse(guidString));
|
||||
@ -145,13 +176,14 @@ namespace AssetRipper.IO.Files
|
||||
/// </remarks>
|
||||
/// <param name="input">Input data. Can be any length</param>
|
||||
/// <returns>A stable guid corresponding to the <paramref name="input"/>.</returns>
|
||||
public static UnityGUID Md5Hash(ReadOnlySpan<byte> input)
|
||||
public static UnityGUID Md5Hash(scoped ReadOnlySpan<byte> input)
|
||||
{
|
||||
byte[] hashBytes = MD5.HashData(input);
|
||||
return new UnityGUID(ConvertSystemOrUnityBytes(hashBytes));
|
||||
ConvertSystemOrUnityBytes(hashBytes, hashBytes);
|
||||
return new UnityGUID(hashBytes);
|
||||
}
|
||||
|
||||
public static UnityGUID Md5Hash(ReadOnlySpan<byte> assemblyName, ReadOnlySpan<byte> @namespace, ReadOnlySpan<byte> className)
|
||||
public static UnityGUID Md5Hash(scoped ReadOnlySpan<byte> assemblyName, scoped ReadOnlySpan<byte> @namespace, scoped ReadOnlySpan<byte> className)
|
||||
{
|
||||
int length = assemblyName.Length + @namespace.Length + className.Length;
|
||||
Span<byte> input = length < 1024 ? stackalloc byte[length] : GC.AllocateUninitializedArray<byte>(length);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user