mirror of
https://github.com/AssetRipper/AssetRipper.git
synced 2025-12-11 20:15:29 +01:00
Deterministic Script Guids. Resolves #691
This commit is contained in:
parent
7e4e1591ea
commit
8ff9be22d5
@ -21,13 +21,8 @@ namespace AssetRipper.Export.UnityProjects.Scripts
|
||||
|
||||
// find copies in whole project and skip them
|
||||
Dictionary<MonoScriptInfo, IMonoScript> uniqueDictionary = new();
|
||||
foreach (IUnityObjectBase asset in script.Collection.Bundle.FetchAssetsInHierarchy())
|
||||
foreach (IMonoScript assetScript in script.Collection.Bundle.FetchAssetsInHierarchy().OfType<IMonoScript>())
|
||||
{
|
||||
if (asset is not IMonoScript assetScript)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
MonoScriptInfo info = MonoScriptInfo.From(assetScript);
|
||||
if (uniqueDictionary.TryGetValue(info, out IMonoScript? uniqueScript))
|
||||
{
|
||||
@ -80,7 +75,7 @@ namespace AssetRipper.Export.UnityProjects.Scripts
|
||||
{
|
||||
if (MonoScriptExtensions.HasNamespace(script.Collection.Version))
|
||||
{
|
||||
int fileID = Compute(script.Namespace_C115.String, script.ClassName_C115.String);
|
||||
int fileID = ComputeScriptFileID(script.Namespace_C115.String, script.ClassName_C115.String);
|
||||
return new MetaPtr(fileID, UnityEngineGUID, AssetExporter.ToExportType(asset));
|
||||
}
|
||||
else
|
||||
@ -88,18 +83,37 @@ namespace AssetRipper.Export.UnityProjects.Scripts
|
||||
ScriptIdentifier scriptInfo = script.GetScriptID(AssetExporter.AssemblyManager);
|
||||
if (!scriptInfo.IsDefault)
|
||||
{
|
||||
int fileID = Compute(scriptInfo.Namespace, scriptInfo.Name);
|
||||
int fileID = ComputeScriptFileID(scriptInfo.Namespace, scriptInfo.Name);
|
||||
return new MetaPtr(fileID, UnityEngineGUID, AssetExporter.ToExportType(asset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long exportID = GetExportID(asset);
|
||||
UnityGUID uniqueGUID = script.GUID;
|
||||
UnityGUID uniqueGUID = ComputeScriptGuid(script);
|
||||
return new MetaPtr(exportID, uniqueGUID, AssetExporter.ToExportType(asset));
|
||||
}
|
||||
|
||||
private static int Compute(string @namespace, string name)
|
||||
/// <summary>
|
||||
/// Compute a unique hash of a script and use that as the Guid for the script.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is for consistency. Script guid's are random when created in Unity.
|
||||
/// </remarks>
|
||||
private static UnityGUID ComputeScriptGuid(IMonoScript script)
|
||||
{
|
||||
//The assembly file name without any extension.
|
||||
ReadOnlySpan<byte> assemblyName = Encoding.UTF8.GetBytes(script.GetAssemblyNameFixed());
|
||||
return UnityGUID.Md5Hash(assemblyName, script.Namespace_C115.Data, script.ClassName_C115.Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the FileID of a script inside an assembly.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a Unity algorithm.
|
||||
/// </remarks>
|
||||
private static int ComputeScriptFileID(string @namespace, string name)
|
||||
{
|
||||
string toBeHashed = $"s\0\0\0{@namespace}{name}";
|
||||
using MD4 hash = new();
|
||||
@ -120,7 +134,7 @@ namespace AssetRipper.Export.UnityProjects.Scripts
|
||||
IMonoScript script = (IMonoScript)asset;
|
||||
IMonoImporter importer = MonoImporterFactory.CreateAsset(container.ExportVersion);
|
||||
importer.ExecutionOrder_C1035 = (short)script.ExecutionOrder_C115;
|
||||
Meta meta = new Meta(script.GUID, importer);
|
||||
Meta meta = new Meta(ComputeScriptGuid(script), importer);
|
||||
ExportMeta(container, meta, path);
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
using AssetRipper.IO.Files;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace AssetRipper.Tests
|
||||
namespace AssetRipper.IO.Files.Tests
|
||||
{
|
||||
public class GuidTests
|
||||
{
|
||||
@ -72,6 +72,21 @@ namespace AssetRipper.Tests
|
||||
Assert.That(equivalentGuid, Is.EqualTo(originalGuid));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UnityGuidIsTheSameSizeAsSystemGuid()
|
||||
{
|
||||
Assert.That(Unsafe.SizeOf<UnityGUID>(), Is.EqualTo(Unsafe.SizeOf<Guid>()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToBytesMethod()
|
||||
{
|
||||
Guid originalGuid = Guid.NewGuid();
|
||||
UnityGUID tobyteArrayGuid = new UnityGUID(originalGuid.ToByteArray());
|
||||
UnityGUID memoryMarshallGuid = new UnityGUID(MemoryMarshal.Cast<Guid, byte>(new ReadOnlySpan<Guid>(in originalGuid)));
|
||||
Assert.That(memoryMarshallGuid, Is.EqualTo(tobyteArrayGuid));
|
||||
}
|
||||
|
||||
private static string GetLongRandomString(int numSetsOf32Characters = 4)
|
||||
{
|
||||
StringBuilder sb = new(numSetsOf32Characters * 32);
|
||||
@ -143,14 +143,24 @@ namespace AssetRipper.IO.Files
|
||||
/// <remarks>
|
||||
/// The returned guid is most likely not "valid" by official standards. However, Unity doesn't seem to care.
|
||||
/// </remarks>
|
||||
/// <param name="inputBytes">Input byte array. Can be any length</param>
|
||||
/// <returns>A stable guid corresponding to the input bytes</returns>
|
||||
public static UnityGUID Md5Hash(byte[] inputBytes)
|
||||
/// <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)
|
||||
{
|
||||
byte[] hashBytes = MD5.HashData(inputBytes);
|
||||
byte[] hashBytes = MD5.HashData(input);
|
||||
return new UnityGUID(ConvertSystemOrUnityBytes(hashBytes));
|
||||
}
|
||||
|
||||
public static UnityGUID Md5Hash(ReadOnlySpan<byte> assemblyName, ReadOnlySpan<byte> @namespace, ReadOnlySpan<byte> className)
|
||||
{
|
||||
int length = assemblyName.Length + @namespace.Length + className.Length;
|
||||
Span<byte> input = length < 1024 ? stackalloc byte[length] : GC.AllocateUninitializedArray<byte>(length);
|
||||
assemblyName.CopyTo(input);
|
||||
@namespace.CopyTo(input.Slice(assemblyName.Length));
|
||||
className.CopyTo(input.Slice(assemblyName.Length + @namespace.Length));
|
||||
return Md5Hash(input);
|
||||
}
|
||||
|
||||
public bool IsZero => Data0 == 0 && Data1 == 0 && Data2 == 0 && Data3 == 0;
|
||||
|
||||
public uint Data0 { get; set; }
|
||||
@ -166,7 +176,7 @@ namespace AssetRipper.IO.Files
|
||||
public static UnityGUID Zero => default;
|
||||
|
||||
[ThreadStatic]
|
||||
private static StringBuilder? s_sb = null;
|
||||
private static StringBuilder? s_sb;
|
||||
|
||||
private static StringBuilder GetStringBuilder()
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user