2025-10-12 13:03:53 -07:00

219 lines
9.9 KiB
C#

using AsmResolver;
using AssetRipper.AssemblyDumper.Attributes;
using AssetRipper.AssemblyDumper.Documentation;
using AssetRipper.AssemblyDumper.Methods;
using AssetRipper.AssemblyDumper.Types;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
namespace AssetRipper.AssemblyDumper.Passes;
internal static class Pass557_CreateSourceTpkClass
{
public static void DoPass()
{
//Type Tree
{
TypeDefinition type = StaticClassCreator.CreateEmptyStaticClass(SharedState.Instance.Module, SharedState.RootNamespace, "SourceTpk");
FieldDefinition field = CreateInternalStorageClass("SourceTpkData", SharedState.Instance.TpkData);
AddGetStreamMethod(type, field);
AddDataProperty(type, field);
AddVersionsProperty(type, SharedState.Instance.SourceVersions);
DocumentationHandler.AddTypeDefinitionLine(type, "Type Trees are used in the AssetRipper source generation.");
DocumentationHandler.AddTypeDefinitionLine(type, $"This data is sourced from {SharedState.Instance.SourceVersions.Length} versions of Unity.");
DocumentationHandler.AddTypeDefinitionLine(type, $"See: {SeeXmlTagGenerator.MakeHRef(@"https://github.com/AssetRipper/TypeTreeDumps")}");
}
//Engine Assets
{
TypeDefinition type = StaticClassCreator.CreateEmptyStaticClass(SharedState.Instance.Module, SharedState.RootNamespace, "EngineAssetsTpk");
FieldDefinition field = CreateInternalStorageClass("EngineAssetsTpkData", File.ReadAllBytes("engine_assets.tpk"));
AddGetStreamMethod(type, field);
AddDataProperty(type, field);
DocumentationHandler.AddTypeDefinitionLine(type, "Engine Assets are embedded during the AssetRipper source generation.");
DocumentationHandler.AddTypeDefinitionLine(type, $"This data is sourced from many versions of Unity.");
DocumentationHandler.AddTypeDefinitionLine(type, $"See: {SeeXmlTagGenerator.MakeHRef(@"https://github.com/AssetRipper/DocumentationDumps")}");
}
//Assemblies
{
TypeDefinition type = StaticClassCreator.CreateEmptyStaticClass(SharedState.Instance.Module, SharedState.RootNamespace, "ReferenceAssembliesJson");
FieldDefinition field = CreateInternalStorageClass("ReferenceAssembliesJsonData", File.ReadAllBytes("assemblies.json"));
AddGetStreamMethod(type, field);
AddDataProperty(type, field);
}
}
private static void AddVersionsProperty(TypeDefinition type, IReadOnlyCollection<UnityVersion> versions)
{
GenericInstanceTypeSignature readOnlySet = SharedState.Instance.Importer.ImportType(typeof(IReadOnlySet<>))
.MakeGenericInstanceType(SharedState.Instance.Importer.ImportTypeSignature<UnityVersion>());
GenericInstanceTypeSignature unityVersionHashSet = SharedState.Instance.Importer.ImportType(typeof(HashSet<>))
.MakeGenericInstanceType(SharedState.Instance.Importer.ImportTypeSignature<UnityVersion>());
IMethodDefOrRef hashsetConstructor = MethodUtils.MakeConstructorOnGenericType(SharedState.Instance.Importer, unityVersionHashSet, 0);
IMethodDefOrRef addMethod = MethodUtils.MakeMethodOnGenericType(
SharedState.Instance.Importer,
unityVersionHashSet,
SharedState.Instance.Importer.LookupMethod(typeof(HashSet<>), m => m.Name == nameof(HashSet<>.Add)));
IMethodDefOrRef unityVersionConstructor = SharedState.Instance.Importer.ImportConstructor<UnityVersion>(5);
const string propertyName = "Versions";
FieldDefinition field = type.AddField($"<{propertyName}>k__BackingField", readOnlySet, true, Visibility.Private);
field.Attributes |= FieldAttributes.InitOnly;
field.AddCompilerGeneratedAttribute(SharedState.Instance.Importer);
MethodDefinition staticConstructor = type.GetOrCreateStaticConstructor();
CilInstructionCollection instructions = staticConstructor.CilMethodBody!.Instructions;
instructions.Pop();//pop the ret
instructions.Add(CilOpCodes.Newobj, hashsetConstructor);
foreach (UnityVersion version in versions)
{
instructions.Add(CilOpCodes.Dup);
instructions.Add(CilOpCodes.Ldc_I4, (int)version.Major);
instructions.Add(CilOpCodes.Ldc_I4, (int)version.Minor);
instructions.Add(CilOpCodes.Ldc_I4, (int)version.Build);
instructions.Add(CilOpCodes.Ldc_I4, (int)version.Type);
instructions.Add(CilOpCodes.Ldc_I4, (int)version.TypeNumber);
instructions.Add(CilOpCodes.Newobj, unityVersionConstructor);
instructions.Add(CilOpCodes.Call, addMethod);
instructions.Add(CilOpCodes.Pop);
}
instructions.Add(CilOpCodes.Stsfld, field);
instructions.Add(CilOpCodes.Ret);
instructions.OptimizeMacros();
type.ImplementGetterProperty(
propertyName,
MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.SpecialName,
readOnlySet,
field)
.GetMethod!.AddCompilerGeneratedAttribute(SharedState.Instance.Importer);
Console.WriteLine($"\t{versions.Count} source versions.");
}
private static void AddDataProperty(TypeDefinition type, FieldDefinition field)
{
TypeSignature propertySignature = SharedState.Instance.Importer.ImportType(typeof(ReadOnlySpan<>))
.MakeGenericInstanceType(SharedState.Instance.Importer.UInt8);
PropertyDefinition property = type.AddGetterProperty("Data", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, propertySignature);
MethodDefinition method = property.GetMethod!;
CilInstructionCollection instructions = method.GetInstructions();
MemberReference reference = new MemberReference(propertySignature.ToTypeDefOrRef(), ".ctor", SharedState.Instance.Importer.ImportMethod(typeof(ReadOnlySpan<>),
m => m.IsConstructor && m.Parameters.Count == 1 && m.Parameters[0].ParameterType is SzArrayTypeSignature).Signature);
instructions.Add(CilOpCodes.Ldsfld, field);
instructions.Add(CilOpCodes.Newobj, reference);
instructions.Add(CilOpCodes.Ret);
}
private static void AddGetStreamMethod(TypeDefinition type, FieldDefinition field)
{
ITypeDefOrRef returnType = SharedState.Instance.Importer.ImportType(typeof(MemoryStream));
MethodDefinition method = type.AddMethod("GetStream", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, returnType.ToTypeSignature());
CilInstructionCollection instructions = method.GetInstructions();
MemberReference reference = new MemberReference(returnType, ".ctor", SharedState.Instance.Importer.ImportMethod(typeof(MemoryStream), m =>
{
return m.IsConstructor
&& m.Parameters.Count == 2
&& m.Parameters[0].ParameterType is SzArrayTypeSignature
&& m.Parameters[1].ParameterType is CorLibTypeSignature;
})
.Signature);
instructions.Add(CilOpCodes.Ldsfld, field);
instructions.Add(CilOpCodes.Ldc_I4_0);//Not writable
instructions.Add(CilOpCodes.Newobj, reference);
instructions.Add(CilOpCodes.Ret);
}
private static FieldDefinition CreateInternalStorageClass(string className, byte[] data)
{
FieldDefinition field;
TypeDefinition internalType = StaticClassCreator.CreateEmptyStaticClass(SharedState.Instance.Module, SharedState.RootNamespace, className);
internalType.IsPublic = false;
FieldDefinition privateImplementationField = AddStoredDataField(data);
field = internalType.AddField("data", SharedState.Instance.Importer.UInt8.MakeSzArrayType(), true, Visibility.Internal);
field.IsInitOnly = true;
//Static Constructor
{
MethodDefinition staticConstructor = internalType.GetOrCreateStaticConstructor();
CilInstructionCollection instructions = staticConstructor.CilMethodBody!.Instructions;
instructions.Pop();//pop the ret
instructions.Add(CilOpCodes.Ldc_I4, data.Length);
instructions.Add(CilOpCodes.Newarr, SharedState.Instance.Importer.UInt8.ToTypeDefOrRef());
instructions.Add(CilOpCodes.Dup);
instructions.Add(CilOpCodes.Ldtoken, privateImplementationField);
instructions.Add(CilOpCodes.Call, SharedState.Instance.Importer.ImportMethod(typeof(RuntimeHelpers), m => m.Name == nameof(RuntimeHelpers.InitializeArray)));
instructions.Add(CilOpCodes.Stsfld, field);
instructions.Add(CilOpCodes.Ret);
}
return field;
}
/// <summary>
/// Adds a byte array field to the PrivateImplementationDetails class.
/// </summary>
/// <param name="fieldName">The name of the field.</param>
/// <param name="data">The data contained within the field.</param>
/// <returns>The field's <see cref="FieldDefinition"/>.</returns>
private static FieldDefinition AddStoredDataField(byte[] data)
{
TypeDefinition nestedType = GetOrCreateStaticArrayInitType(data.Length);
FieldDefinition privateImplementationField = SharedState.Instance.PrivateImplementationDetails.AddField(HashDataToBase64(data), nestedType.ToTypeSignature(), true, Visibility.Internal);
privateImplementationField.IsInitOnly = true;
privateImplementationField.FieldRva = new DataSegment(data);
privateImplementationField.HasFieldRva = true;
privateImplementationField.AddCompilerGeneratedAttribute(SharedState.Instance.Importer);
return privateImplementationField;
//This might not be the correct way to choose a field name, but I think the specification allows it.
//In any case, ILSpy handles it the way we want, which is all that matters.
static string HashDataToBase64(byte[] data)
{
byte[] hash = SHA256.HashData(data);
return Convert.ToBase64String(hash, Base64FormattingOptions.None);
}
}
private static TypeDefinition GetOrCreateStaticArrayInitType(int length)
{
string name = $"__StaticArrayInitTypeSize={length}";
foreach (TypeDefinition nestedType in SharedState.Instance.PrivateImplementationDetails.NestedTypes)
{
if (nestedType.Name == name)
{
return nestedType;
}
}
TypeDefinition result = new TypeDefinition(null, name,
TypeAttributes.NestedPrivate |
TypeAttributes.ExplicitLayout |
TypeAttributes.AnsiClass |
TypeAttributes.Sealed);
SharedState.Instance.PrivateImplementationDetails.NestedTypes.Add(result);
result.BaseType = SharedState.Instance.Importer.ImportType(typeof(ValueType));
result.ClassLayout = new ClassLayout(1, (uint)length);
result.AddCompilerGeneratedAttribute(SharedState.Instance.Importer);
return result;
}
}