mirror of
https://github.com/AssetRipper/AssetRipper.git
synced 2025-12-11 20:15:29 +01:00
219 lines
9.9 KiB
C#
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;
|
|
}
|
|
}
|