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

882 lines
40 KiB
C#

using AssetRipper.AssemblyDumper.Attributes;
using AssetRipper.AssemblyDumper.InjectedTypes;
using AssetRipper.AssemblyDumper.Methods;
using AssetRipper.AssemblyDumper.Types;
using AssetRipper.Assets;
using AssetRipper.Assets.Generics;
using AssetRipper.IO.Endian;
using System.Diagnostics;
namespace AssetRipper.AssemblyDumper.Passes;
public static class Pass100_FillReadMethods
{
#nullable disable
private static IMethodDefOrRef alignStreamMethod;
private static IMethodDefOrRef readInt32Method;
private static IMethodDefOrRef readBytesMethod;
/// <summary>
/// TypeSignature for <see langword="ref"/> <see cref="EndianSpanReader"/>
/// </summary>
private static TypeSignature endianSpanReaderReference;
private static ITypeDefOrRef assetDictionaryReference;
private static TypeDefinition assetDictionaryDefinition;
private static ITypeDefOrRef assetListReference;
private static TypeDefinition assetListDefinition;
private static ITypeDefOrRef assetPairReference;
private static TypeDefinition assetPairDefinition;
private static MethodDefinition readAssetAlignDefinition;
private static MethodDefinition readAssetListDefinition;
private static MethodDefinition readAssetListAlignDefinition;
private static MethodDefinition readAssetPairDefinition;
private static MethodDefinition readAssetPairAlignDefinition;
private static MethodDefinition readAssetDictionaryDefinition;
private static MethodDefinition readAssetDictionaryAlignDefinition;
#nullable enable
private static readonly Dictionary<ElementType, IMethodDefOrRef> primitiveReadMethods = new();
private static string ReadMethod => emittingRelease ? ReadRelease : ReadEditor;
private const string ReadRelease = nameof(UnityAssetBase.ReadRelease);
private const string ReadEditor = nameof(UnityAssetBase.ReadEditor);
private const int MaxArraySize = 1024;
private static readonly Dictionary<string, IMethodDescriptor> methodDictionary = new();
private static readonly SignatureComparer signatureComparer = new(SignatureComparisonFlags.VersionAgnostic);
private static bool emittingRelease;
public static void DoPass()
{
methodDictionary.Clear();
Initialize();
emittingRelease = true;
readAssetAlignDefinition = MakeGenericAssetAlignMethod();
readAssetListDefinition = MakeGenericListMethod(false);
readAssetListAlignDefinition = MakeGenericListMethod(true);
readAssetPairDefinition = MakeGenericPairMethod(false);
readAssetPairAlignDefinition = MakeGenericPairMethod(true);
readAssetDictionaryDefinition = MakeGenericDictionaryMethod(false);
readAssetDictionaryAlignDefinition = MakeGenericDictionaryMethod(true);
foreach (ClassGroupBase group in SharedState.Instance.AllGroups)
{
foreach (GeneratedClassInstance instance in group.Instances)
{
instance.Type.FillReleaseWriteMethod(instance.Class, instance.VersionRange.Start);
}
}
CreateHelperClassForWriteMethods();
methodDictionary.Clear();
emittingRelease = false;
readAssetAlignDefinition = MakeGenericAssetAlignMethod();
readAssetListDefinition = MakeGenericListMethod(false);
readAssetListAlignDefinition = MakeGenericListMethod(true);
readAssetPairDefinition = MakeGenericPairMethod(false);
readAssetPairAlignDefinition = MakeGenericPairMethod(true);
readAssetDictionaryDefinition = MakeGenericDictionaryMethod(false);
readAssetDictionaryAlignDefinition = MakeGenericDictionaryMethod(true);
foreach (ClassGroupBase group in SharedState.Instance.AllGroups)
{
foreach (GeneratedClassInstance instance in group.Instances)
{
instance.Type.FillEditorWriteMethod(instance.Class, instance.VersionRange.Start);
}
}
CreateHelperClassForWriteMethods();
methodDictionary.Clear();
}
private static void Initialize()
{
primitiveReadMethods.Add(ElementType.Boolean, SharedState.Instance.Importer.ImportMethod(typeof(EndianSpanReader), m => m.Name == nameof(EndianSpanReader.ReadBoolean)));
primitiveReadMethods.Add(ElementType.Char, SharedState.Instance.Importer.ImportMethod(typeof(EndianSpanReader), m => m.Name == nameof(EndianSpanReader.ReadChar)));
primitiveReadMethods.Add(ElementType.I1, SharedState.Instance.Importer.ImportMethod(typeof(EndianSpanReader), m => m.Name == nameof(EndianSpanReader.ReadSByte)));
primitiveReadMethods.Add(ElementType.U1, SharedState.Instance.Importer.ImportMethod(typeof(EndianSpanReader), m => m.Name == nameof(EndianSpanReader.ReadByte)));
primitiveReadMethods.Add(ElementType.I2, SharedState.Instance.Importer.ImportMethod(typeof(EndianSpanReader), m => m.Name == nameof(EndianSpanReader.ReadInt16)));
primitiveReadMethods.Add(ElementType.U2, SharedState.Instance.Importer.ImportMethod(typeof(EndianSpanReader), m => m.Name == nameof(EndianSpanReader.ReadUInt16)));
primitiveReadMethods.Add(ElementType.I4, SharedState.Instance.Importer.ImportMethod(typeof(EndianSpanReader), m => m.Name == nameof(EndianSpanReader.ReadInt32)));
primitiveReadMethods.Add(ElementType.U4, SharedState.Instance.Importer.ImportMethod(typeof(EndianSpanReader), m => m.Name == nameof(EndianSpanReader.ReadUInt32)));
primitiveReadMethods.Add(ElementType.I8, SharedState.Instance.Importer.ImportMethod(typeof(EndianSpanReader), m => m.Name == nameof(EndianSpanReader.ReadInt64)));
primitiveReadMethods.Add(ElementType.U8, SharedState.Instance.Importer.ImportMethod(typeof(EndianSpanReader), m => m.Name == nameof(EndianSpanReader.ReadUInt64)));
primitiveReadMethods.Add(ElementType.R4, SharedState.Instance.Importer.ImportMethod(typeof(EndianSpanReader), m => m.Name == nameof(EndianSpanReader.ReadSingle)));
primitiveReadMethods.Add(ElementType.R8, SharedState.Instance.Importer.ImportMethod(typeof(EndianSpanReader), m => m.Name == nameof(EndianSpanReader.ReadDouble)));
primitiveReadMethods.Add(ElementType.String, SharedState.Instance.Importer.ImportMethod(typeof(EndianSpanReader), m => m.Name == nameof(EndianSpanReader.ReadUtf8String)));
readInt32Method = primitiveReadMethods[ElementType.I4];
alignStreamMethod = SharedState.Instance.Importer.ImportMethod(typeof(EndianSpanReader), m => m.Name == nameof(EndianSpanReader.Align));
endianSpanReaderReference = SharedState.Instance.Importer.ImportTypeSignature(typeof(EndianSpanReader)).MakeByReferenceType();
readBytesMethod = SharedState.Instance.InjectHelperType(typeof(TypelessDataHelper)).Methods.Single();
assetDictionaryReference = SharedState.Instance.Importer.ImportType(typeof(AssetDictionary<,>));
assetListReference = SharedState.Instance.Importer.ImportType(typeof(AssetList<>));
assetPairReference = SharedState.Instance.Importer.ImportType(typeof(AssetPair<,>));
assetDictionaryDefinition = SharedState.Instance.Importer.LookupType(typeof(AssetDictionary<,>));
assetListDefinition = SharedState.Instance.Importer.LookupType(typeof(AssetList<>));
assetPairDefinition = SharedState.Instance.Importer.LookupType(typeof(AssetPair<,>));
}
private static void CreateHelperClassForWriteMethods()
{
TypeDefinition type = StaticClassCreator.CreateEmptyStaticClass(SharedState.Instance.Module, SharedState.HelpersNamespace, $"{ReadMethod}Methods");
type.IsPublic = false;
type.Methods.Add(readAssetAlignDefinition);
type.Methods.Add(readAssetListDefinition);
type.Methods.Add(readAssetListAlignDefinition);
type.Methods.Add(readAssetPairDefinition);
type.Methods.Add(readAssetPairAlignDefinition);
type.Methods.Add(readAssetDictionaryDefinition);
type.Methods.Add(readAssetDictionaryAlignDefinition);
foreach ((string _, IMethodDescriptor method) in methodDictionary.OrderBy(pair => pair.Key))
{
if (method is MethodDefinition methodDefinition && methodDefinition.DeclaringType is null)
{
type.Methods.Add(methodDefinition);
}
}
Console.WriteLine($"\t{type.Methods.Count} {ReadMethod} helper methods");
}
private static void FillEditorWriteMethod(this TypeDefinition type, UniversalClass klass, UnityVersion version)
{
type.FillMethod(ReadEditor, klass.EditorRootNode, version);
}
private static void FillReleaseWriteMethod(this TypeDefinition type, UniversalClass klass, UnityVersion version)
{
type.FillMethod(ReadRelease, klass.ReleaseRootNode, version);
}
private static void FillMethod(this TypeDefinition type, string methodName, UniversalNode? rootNode, UnityVersion version)
{
MethodDefinition method = type.Methods.First(m => m.Name == methodName);
CilInstructionCollection instructions = method.GetInstructions();
if (rootNode is not null)
{
foreach (UniversalNode unityNode in rootNode.SubNodes)
{
FieldDefinition field = type.GetFieldByName(unityNode.Name, true);
IMethodDescriptor fieldReadMethod = GetOrMakeMethod(unityNode, field.Signature!.FieldType, version);
if (field.Signature.FieldType.IsArrayOrPrimitive())
{
instructions.Add(CilOpCodes.Ldarg_0);//this
instructions.Add(CilOpCodes.Ldarg_1);//reader
instructions.AddCall(fieldReadMethod);
instructions.Add(CilOpCodes.Stfld, field);
}
else
{
instructions.Add(CilOpCodes.Ldarg_0);//this
instructions.Add(CilOpCodes.Ldfld, field);
instructions.Add(CilOpCodes.Ldarg_1);//reader
instructions.AddCall(fieldReadMethod);
}
}
}
instructions.Add(CilOpCodes.Ret);
instructions.OptimizeMacros();
}
private static IMethodDescriptor GetOrMakeMethod(UniversalNode node, TypeSignature type, UnityVersion version)
{
string uniqueName = UniqueNameFactory.GetReadWriteName(node, version);
if (methodDictionary.TryGetValue(uniqueName, out IMethodDescriptor? method))
{
return method;
}
if (SharedState.Instance.SubclassGroups.TryGetValue(node.TypeName, out SubclassGroup? subclassGroup))
{
TypeDefinition typeDefinition = subclassGroup.GetTypeForVersion(version);
Debug.Assert(signatureComparer.Equals(typeDefinition.ToTypeSignature(), type));
MethodDefinition typeReadMethod = typeDefinition.GetMethodByName(ReadMethod);
method = node.AlignBytes ? readAssetAlignDefinition.MakeGenericInstanceMethod(type) : typeReadMethod;
methodDictionary.Add(uniqueName, method);
return method;
}
switch (node.NodeType)
{
case NodeType.Vector:
{
UniversalNode arrayNode = node.SubNodes[0];
UniversalNode elementTypeNode = arrayNode.SubNodes[1];
bool align = node.AlignBytes || arrayNode.AlignBytes;
if (type is GenericInstanceTypeSignature genericSignature)
{
Debug.Assert(genericSignature.GenericType.Name == $"{nameof(AssetList<>)}`1");
Debug.Assert(genericSignature.TypeArguments.Count == 1);
method = MakeListMethod(uniqueName, elementTypeNode, genericSignature.TypeArguments[0], version, align);
}
else
{
SzArrayTypeSignature arrayType = (SzArrayTypeSignature)type;
TypeSignature elementType = arrayType.BaseType;
method = MakeArrayMethod(uniqueName, elementTypeNode, elementType, version, align);
}
}
break;
case NodeType.Map:
{
UniversalNode arrayNode = node.SubNodes[0];
UniversalNode pairNode = arrayNode.SubNodes[1];
bool align = node.AlignBytes || arrayNode.AlignBytes;
GenericInstanceTypeSignature genericSignature = (GenericInstanceTypeSignature)type;
Debug.Assert(genericSignature.GenericType.Name == $"{nameof(AssetDictionary<,>)}`2");
Debug.Assert(genericSignature.TypeArguments.Count == 2);
GenericInstanceTypeSignature pairSignature = assetPairReference.MakeGenericInstanceType(genericSignature.TypeArguments[0], genericSignature.TypeArguments[1]);
method = MakeDictionaryMethod(uniqueName, pairNode, pairSignature, version, align);
}
break;
case NodeType.Pair:
{
UniversalNode firstTypeNode = node.SubNodes[0];
UniversalNode secondTypeNode = node.SubNodes[1];
bool align = node.AlignBytes;
GenericInstanceTypeSignature genericSignature = (GenericInstanceTypeSignature)type;
Debug.Assert(genericSignature.GenericType.Name == $"{nameof(AssetPair<,>)}`2");
Debug.Assert(genericSignature.TypeArguments.Count == 2);
method = MakePairMethod(uniqueName, firstTypeNode, genericSignature.TypeArguments[0], secondTypeNode, genericSignature.TypeArguments[1], version, align);
}
break;
case NodeType.TypelessData: //byte array
{
method = MakeTypelessDataMethod(uniqueName, node.AlignBytes);
}
break;
case NodeType.Array:
{
UniversalNode elementTypeNode = node.SubNodes[1];
bool align = node.AlignBytes;
if (type is GenericInstanceTypeSignature genericSignature)
{
Debug.Assert(genericSignature.GenericType.Name == $"{nameof(AssetList<>)}`1");
Debug.Assert(genericSignature.TypeArguments.Count == 1);
method = MakeListMethod(uniqueName, elementTypeNode, genericSignature.TypeArguments[0], version, align);
}
else
{
SzArrayTypeSignature arrayType = (SzArrayTypeSignature)type;
TypeSignature elementType = arrayType.BaseType;
method = MakeArrayMethod(uniqueName, elementTypeNode, elementType, version, align);
}
}
break;
default:
method = MakePrimitiveMethod(uniqueName, node, node.AlignBytes);
break;
}
methodDictionary.Add(uniqueName, method);
return method;
}
private static MethodDefinition MakeGenericAssetAlignMethod()
{
string uniqueName = "AssetAlign";
GenericParameterSignature elementType = new GenericParameterSignature(SharedState.Instance.Module, GenericParameterType.Method, 0);
IMethodDefOrRef readMethod = SharedState.Instance.Importer.ImportMethod<UnityAssetBase>(m => m.Name == ReadMethod && m.Parameters[0].ParameterType is ByReferenceTypeSignature);
MethodDefinition method = NewMethod(uniqueName, elementType);
CilInstructionCollection instructions = method.GetInstructions();
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Callvirt, readMethod);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.AddCall(alignStreamMethod);
instructions.Add(CilOpCodes.Ret);
GenericParameter genericParameter = new GenericParameter("T");
genericParameter.Constraints.Add(new GenericParameterConstraint(SharedState.Instance.Importer.ImportType<UnityAssetBase>()));
method.GenericParameters.Add(genericParameter);
method.Signature!.GenericParameterCount = 1;
method.Signature.IsGeneric = true;
return method;
}
private static IMethodDescriptor MakeDictionaryMethod(string uniqueName, UniversalNode pairNode, GenericInstanceTypeSignature pairSignature, UnityVersion version, bool align)
{
TypeSignature keySignature = pairSignature.TypeArguments[0];
TypeSignature valueSignature = pairSignature.TypeArguments[1];
if (keySignature.IsTypeDefinition() && valueSignature.IsTypeDefinition())
{
return align
? readAssetDictionaryAlignDefinition.MakeGenericInstanceMethod(keySignature, valueSignature)
: readAssetDictionaryDefinition.MakeGenericInstanceMethod(keySignature, valueSignature);
}
else
{
IMethodDescriptor pairReadMethod = GetOrMakeMethod(pairNode, pairSignature, version);
return MakeDictionaryMethod(uniqueName, pairReadMethod, keySignature, valueSignature, align);
}
}
private static MethodDefinition MakeGenericDictionaryMethod(bool align)
{
string uniqueName = align ? "MapAlign_Asset_Asset" : "Map_Asset_Asset";
GenericParameterSignature keyType = new GenericParameterSignature(SharedState.Instance.Module, GenericParameterType.Method, 0);
GenericParameterSignature valueType = new GenericParameterSignature(SharedState.Instance.Module, GenericParameterType.Method, 1);
IMethodDescriptor readMethod = readAssetPairDefinition.MakeGenericInstanceMethod(keyType, valueType);
MethodDefinition method = MakeDictionaryMethod(uniqueName, readMethod, keyType, valueType, align);
GenericParameter keyParameter = new GenericParameter("TKey", GenericParameterAttributes.DefaultConstructorConstraint);
keyParameter.Constraints.Add(new GenericParameterConstraint(SharedState.Instance.Importer.ImportType<UnityAssetBase>()));
method.GenericParameters.Add(keyParameter);
GenericParameter valueParameter = new GenericParameter("TValue", GenericParameterAttributes.DefaultConstructorConstraint);
valueParameter.Constraints.Add(new GenericParameterConstraint(SharedState.Instance.Importer.ImportType<UnityAssetBase>()));
method.GenericParameters.Add(valueParameter);
method.Signature!.GenericParameterCount = 2;
method.Signature.IsGeneric = true;
return method;
}
private static MethodDefinition MakeDictionaryMethod(string uniqueName, IMethodDescriptor pairReadMethod, TypeSignature keySignature, TypeSignature valueSignature, bool align)
{
GenericInstanceTypeSignature genericDictionaryType = assetDictionaryReference.MakeGenericInstanceType(keySignature, valueSignature);
MethodDefinition clearMethodDefinition = SharedState.Instance.Importer.LookupMethod(typeof(AssetDictionary<,>), m => m.Name == nameof(AssetDictionary<,>.Clear));
IMethodDefOrRef clearMethodReference = MethodUtils.MakeMethodOnGenericType(SharedState.Instance.Importer, genericDictionaryType, clearMethodDefinition);
MethodDefinition method = NewMethod(uniqueName, genericDictionaryType);
CilInstructionCollection instructions = method.GetInstructions();
CilLocalVariable countLocal = instructions.AddLocalVariable(SharedState.Instance.Importer.Int32);
CilLocalVariable iLocal = instructions.AddLocalVariable(SharedState.Instance.Importer.Int32);
CilInstructionLabel loopConditionStartList = new();
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Call, clearMethodReference);
//Read count
instructions.Add(CilOpCodes.Ldarg_1);//reader
instructions.AddCall(readInt32Method);
instructions.Add(CilOpCodes.Stloc, countLocal);
instructions.Add(CilOpCodes.Ldc_I4_0); //Load 0 as an int32
instructions.Add(CilOpCodes.Stloc, iLocal); //Store in count
//Create an empty, unconditional branch which will jump down to the loop condition.
//This converts the do..while loop into a for loop.
instructions.Add(CilOpCodes.Br, loopConditionStartList);
//Now we just read pair, increment i, compare against count, and jump back to here if it's less
ICilLabel jumpTargetList = instructions.Add(CilOpCodes.Nop).CreateLabel(); //Create a dummy instruction to jump back to
MethodDefinition addNewMethodDefinition = SharedState.Instance.Importer.LookupMethod(typeof(AssetDictionary<,>), m => m.Name == nameof(AssetDictionary<,>.AddNew));
IMethodDefOrRef addNewMethodReference = MethodUtils.MakeMethodOnGenericType(SharedState.Instance.Importer, genericDictionaryType, addNewMethodDefinition);
//Add new and read pair
instructions.Add(CilOpCodes.Ldarg_0);
instructions.AddCall(addNewMethodReference);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.AddCall(pairReadMethod);
//Increment i
instructions.Add(CilOpCodes.Ldloc, iLocal); //Load i local
instructions.Add(CilOpCodes.Ldc_I4_1); //Load constant 1 as int32
instructions.Add(CilOpCodes.Add); //Add
instructions.Add(CilOpCodes.Stloc, iLocal); //Store in i local
//Jump to start of loop if i < count
loopConditionStartList.Instruction = instructions.Add(CilOpCodes.Ldloc, iLocal); //Load i
instructions.Add(CilOpCodes.Ldloc, countLocal); //Load count
instructions.Add(CilOpCodes.Blt, jumpTargetList); //Jump back up if less than
if (align)
{
instructions.Add(CilOpCodes.Ldarg_1);
instructions.AddCall(alignStreamMethod);
}
instructions.Add(CilOpCodes.Ret);
instructions.OptimizeMacros();
return method;
}
private static IMethodDescriptor MakePairMethod(string uniqueName, UniversalNode keyTypeNode, TypeSignature keySignature, UniversalNode valueTypeNode, TypeSignature valueSignature, UnityVersion version, bool align)
{
if (keySignature.IsTypeDefinition() && valueSignature.IsTypeDefinition())
{
return align
? readAssetPairAlignDefinition.MakeGenericInstanceMethod(keySignature, valueSignature)
: readAssetPairDefinition.MakeGenericInstanceMethod(keySignature, valueSignature);
}
else
{
IMethodDescriptor keyReadMethod = GetOrMakeMethod(keyTypeNode, keySignature, version);
IMethodDescriptor valueReadMethod = GetOrMakeMethod(valueTypeNode, valueSignature, version);
return MakePairMethod(uniqueName, keyReadMethod, keySignature, valueReadMethod, valueSignature, align);
}
}
private static MethodDefinition MakeGenericPairMethod(bool align)
{
string uniqueName = align ? "PairAlign_Asset_Asset" : "Pair_Asset_Asset";
GenericParameterSignature keyType = new GenericParameterSignature(SharedState.Instance.Module, GenericParameterType.Method, 0);
GenericParameterSignature valueType = new GenericParameterSignature(SharedState.Instance.Module, GenericParameterType.Method, 1);
IMethodDefOrRef readMethod = SharedState.Instance.Importer.ImportMethod<UnityAssetBase>(m => m.Name == ReadMethod && m.Parameters[0].ParameterType is ByReferenceTypeSignature);
MethodDefinition method = MakePairMethod(uniqueName, readMethod, keyType, readMethod, valueType, align);
GenericParameter keyParameter = new GenericParameter("TKey", GenericParameterAttributes.DefaultConstructorConstraint);
keyParameter.Constraints.Add(new GenericParameterConstraint(SharedState.Instance.Importer.ImportType<UnityAssetBase>()));
method.GenericParameters.Add(keyParameter);
GenericParameter valueParameter = new GenericParameter("TValue", GenericParameterAttributes.DefaultConstructorConstraint);
valueParameter.Constraints.Add(new GenericParameterConstraint(SharedState.Instance.Importer.ImportType<UnityAssetBase>()));
method.GenericParameters.Add(valueParameter);
method.Signature!.GenericParameterCount = 2;
method.Signature.IsGeneric = true;
return method;
}
private static MethodDefinition MakePairMethod(string uniqueName, IMethodDescriptor keyReadMethod, TypeSignature keySignature, IMethodDescriptor valueReadMethod, TypeSignature valueSignature, bool align)
{
GenericInstanceTypeSignature genericPairType = assetPairReference.MakeGenericInstanceType(keySignature, valueSignature);
MethodDefinition method = NewMethod(uniqueName, genericPairType);
CilInstructionCollection instructions = method.GetInstructions();
if (keySignature.IsArrayOrPrimitive())
{
IMethodDefOrRef setKeyMethod = MethodUtils.MakeMethodOnGenericType(
SharedState.Instance.Importer,
genericPairType,
assetPairDefinition.Methods.Single(m => m.Name == $"set_{nameof(AssetPair<,>.Key)}"));
instructions.Add(CilOpCodes.Ldarg_0);//pair
instructions.Add(CilOpCodes.Ldarg_1);//reader
instructions.AddCall(keyReadMethod);
instructions.AddCall(setKeyMethod);
}
else
{
IMethodDefOrRef getKeyMethod = MethodUtils.MakeMethodOnGenericType(
SharedState.Instance.Importer,
genericPairType,
assetPairDefinition.Methods.Single(m => m.Name == $"get_{nameof(AssetPair<,>.Key)}"));
instructions.Add(CilOpCodes.Ldarg_0);//pair
instructions.AddCall(getKeyMethod);
instructions.Add(CilOpCodes.Ldarg_1);//reader
instructions.AddCall(keyReadMethod);
}
if (valueSignature.IsArrayOrPrimitive())
{
IMethodDefOrRef setValueMethod = MethodUtils.MakeMethodOnGenericType(
SharedState.Instance.Importer,
genericPairType,
assetPairDefinition.Methods.Single(m => m.Name == $"set_{nameof(AssetPair<,>.Value)}"));
instructions.Add(CilOpCodes.Ldarg_0);//pair
instructions.Add(CilOpCodes.Ldarg_1);//reader
instructions.AddCall(valueReadMethod);
instructions.AddCall(setValueMethod);
}
else
{
IMethodDefOrRef getValueMethod = MethodUtils.MakeMethodOnGenericType(
SharedState.Instance.Importer,
genericPairType,
assetPairDefinition.Methods.Single(m => m.Name == $"get_{nameof(AssetPair<,>.Value)}"));
instructions.Add(CilOpCodes.Ldarg_0);//pair
instructions.AddCall(getValueMethod);
instructions.Add(CilOpCodes.Ldarg_1);//reader
instructions.AddCall(valueReadMethod);
}
if (align)
{
instructions.Add(CilOpCodes.Ldarg_1);//reader
instructions.AddCall(alignStreamMethod);
}
instructions.Add(CilOpCodes.Ret);
return method;
}
private static IMethodDescriptor MakeTypelessDataMethod(string uniqueName, bool align)
{
CorLibTypeSignature elementType = SharedState.Instance.Importer.UInt8;
SzArrayTypeSignature arrayType = elementType.MakeSzArrayType();
MethodDefinition method = NewMethod(uniqueName, arrayType);
CilInstructionCollection instructions = method.GetInstructions();
CilLocalVariable countLocal = instructions.AddLocalVariable(SharedState.Instance.Importer.Int32);
CilLocalVariable arrayLocal = instructions.AddLocalVariable(arrayType);
//Read count
instructions.Add(CilOpCodes.Ldarg_0);//reader
instructions.AddCall(readInt32Method);
instructions.Add(CilOpCodes.Stloc, countLocal);
instructions.Add(CilOpCodes.Ldarg_0);//reader
instructions.Add(CilOpCodes.Ldloc, countLocal);
instructions.AddCall(readBytesMethod);
instructions.Add(CilOpCodes.Stloc, arrayLocal);
if (align)
{
instructions.Add(CilOpCodes.Ldarg_0);
instructions.AddCall(alignStreamMethod);
}
instructions.Add(CilOpCodes.Ldloc, arrayLocal);
instructions.Add(CilOpCodes.Ret);
instructions.OptimizeMacros();
return method;
}
private static IMethodDescriptor MakeListMethod(string uniqueName, UniversalNode elementTypeNode, TypeSignature elementType, UnityVersion version, bool align)
{
if (elementType.IsTypeDefinition())
{
return align
? readAssetListAlignDefinition.MakeGenericInstanceMethod(elementType)
: readAssetListDefinition.MakeGenericInstanceMethod(elementType);
}
else
{
IMethodDescriptor elementReadMethod = GetOrMakeMethod(elementTypeNode, elementType, version);
return MakeListMethod(uniqueName, elementType, elementReadMethod, align);
}
}
private static MethodDefinition MakeGenericListMethod(bool align)
{
string uniqueName = align ? "ArrayAlign_Asset" : "Array_Asset";
GenericParameterSignature elementType = new GenericParameterSignature(SharedState.Instance.Module, GenericParameterType.Method, 0);
IMethodDefOrRef readMethod = SharedState.Instance.Importer.ImportMethod<UnityAssetBase>(m => m.Name == ReadMethod && m.Parameters[0].ParameterType is ByReferenceTypeSignature);
MethodDefinition method = MakeListMethod(uniqueName, elementType, readMethod, align);
GenericParameter genericParameter = new GenericParameter("T", GenericParameterAttributes.DefaultConstructorConstraint);
genericParameter.Constraints.Add(new GenericParameterConstraint(SharedState.Instance.Importer.ImportType<UnityAssetBase>()));
method.GenericParameters.Add(genericParameter);
method.Signature!.GenericParameterCount = 1;
method.Signature.IsGeneric = true;
return method;
}
private static MethodDefinition MakeListMethod(string uniqueName, TypeSignature elementType, IMethodDescriptor elementReadMethod, bool align)
{
GenericInstanceTypeSignature genericListType = assetListReference.MakeGenericInstanceType(elementType);
MethodDefinition clearMethodDefinition = SharedState.Instance.Importer.LookupMethod(typeof(AssetList<>), m => m.Name == nameof(AssetList<>.Clear));
IMethodDefOrRef clearMethodReference = MethodUtils.MakeMethodOnGenericType(SharedState.Instance.Importer, genericListType, clearMethodDefinition);
MethodDefinition method = NewMethod(uniqueName, genericListType);
CilInstructionCollection instructions = method.GetInstructions();
CilLocalVariable countLocal = instructions.AddLocalVariable(SharedState.Instance.Importer.Int32);
CilLocalVariable iLocal = instructions.AddLocalVariable(SharedState.Instance.Importer.Int32);
CilInstructionLabel loopConditionStartList = new();
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Call, clearMethodReference);
//Read count
instructions.Add(CilOpCodes.Ldarg_1);//reader
instructions.AddCall(readInt32Method);
instructions.Add(CilOpCodes.Stloc, countLocal);
instructions.Add(CilOpCodes.Ldc_I4_0); //Load 0 as an int32
instructions.Add(CilOpCodes.Stloc, iLocal); //Store in count
//Create an empty, unconditional branch which will jump down to the loop condition.
//This converts the do..while loop into a for loop.
instructions.Add(CilOpCodes.Br, loopConditionStartList);
//Now we just read pair, increment i, compare against count, and jump back to here if it's less
ICilLabel jumpTargetList = instructions.Add(CilOpCodes.Nop).CreateLabel(); //Create a dummy instruction to jump back to
//Read and add to list
if (elementType.IsArrayOrPrimitive())
{
MethodDefinition addMethodDefinition = SharedState.Instance.Importer.LookupMethod(typeof(AssetList<>), m => m.Name == nameof(AssetList<>.Add));
IMethodDefOrRef addMethodReference = MethodUtils.MakeMethodOnGenericType(SharedState.Instance.Importer, genericListType, addMethodDefinition);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.AddCall(elementReadMethod);
instructions.AddCall(addMethodReference);
}
else
{
MethodDefinition addNewMethodDefinition = SharedState.Instance.Importer.LookupMethod(typeof(AssetList<>), m => m.Name == nameof(AssetList<>.AddNew));
IMethodDefOrRef addNewMethodReference = MethodUtils.MakeMethodOnGenericType(SharedState.Instance.Importer, genericListType, addNewMethodDefinition);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.AddCall(addNewMethodReference);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.AddCall(elementReadMethod);
}
//Increment i
instructions.Add(CilOpCodes.Ldloc, iLocal); //Load i local
instructions.Add(CilOpCodes.Ldc_I4_1); //Load constant 1 as int32
instructions.Add(CilOpCodes.Add); //Add
instructions.Add(CilOpCodes.Stloc, iLocal); //Store in i local
//Jump to start of loop if i < count
loopConditionStartList.Instruction = instructions.Add(CilOpCodes.Ldloc, iLocal); //Load i
instructions.Add(CilOpCodes.Ldloc, countLocal); //Load count
instructions.Add(CilOpCodes.Blt, jumpTargetList); //Jump back up if less than
if (align)
{
instructions.Add(CilOpCodes.Ldarg_1);
instructions.AddCall(alignStreamMethod);
}
instructions.Add(CilOpCodes.Ret);
instructions.OptimizeMacros();
return method;
}
private static IMethodDescriptor MakeArrayMethod(string uniqueName, UniversalNode elementTypeNode, TypeSignature elementType, UnityVersion version, bool align)
{
if (elementType is CorLibTypeSignature corLibTypeSignature && corLibTypeSignature.ElementType == ElementType.U1)
{
return MakeTypelessDataMethod(uniqueName, align);
}
bool throwForNonByteArrays = true;
if (throwForNonByteArrays)
{
throw new NotSupportedException();
}
IMethodDescriptor elementReadMethod = GetOrMakeMethod(elementTypeNode, elementType, version);
SzArrayTypeSignature arrayType = elementType.MakeSzArrayType();
GenericInstanceTypeSignature listType = SharedState.Instance.Importer.ImportType(typeof(List<>)).MakeGenericInstanceType(elementType);
MethodDefinition method = NewMethod(uniqueName, arrayType);
CilInstructionCollection instructions = method.GetInstructions();
CilLocalVariable countLocal = instructions.AddLocalVariable(SharedState.Instance.Importer.Int32);
CilLocalVariable iLocal = instructions.AddLocalVariable(SharedState.Instance.Importer.Int32);
CilLocalVariable arrayLocal = instructions.AddLocalVariable(arrayType);
CilLocalVariable listLocal = instructions.AddLocalVariable(listType);
//Read count
instructions.Add(CilOpCodes.Ldarg_0);//reader
instructions.AddCall(readInt32Method);
instructions.Add(CilOpCodes.Stloc, countLocal);
CilInstructionLabel readAsListInstruction = new();
CilInstructionLabel loopConditionStartArray = new();
CilInstructionLabel loopConditionStartList = new();
CilInstructionLabel returnInstruction = new();
//Check size of count
instructions.Add(CilOpCodes.Ldloc, countLocal);
instructions.Add(CilOpCodes.Ldc_I4, MaxArraySize);
instructions.Add(CilOpCodes.Bgt, readAsListInstruction);
//Read into array
instructions.Add(CilOpCodes.Ldloc, countLocal);
instructions.Add(CilOpCodes.Newarr, elementType.ToTypeDefOrRef());
instructions.Add(CilOpCodes.Stloc, arrayLocal);
instructions.Add(CilOpCodes.Ldc_I4_0); //Load 0 as an int32
instructions.Add(CilOpCodes.Stloc, iLocal); //Store in count
//Create an empty, unconditional branch which will jump down to the loop condition.
//This converts the do..while loop into a for loop.
instructions.Add(CilOpCodes.Br, loopConditionStartArray);
//Now we just read pair, increment i, compare against count, and jump back to here if it's less
ICilLabel jumpTargetArray = instructions.Add(CilOpCodes.Nop).CreateLabel(); //Create a dummy instruction to jump back to
//Read and add to array
instructions.Add(CilOpCodes.Ldloc, arrayLocal);
instructions.Add(CilOpCodes.Ldloc, iLocal);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.AddCall(elementReadMethod);
instructions.AddStoreElement(elementType);
//Increment i
instructions.Add(CilOpCodes.Ldloc, iLocal); //Load i local
instructions.Add(CilOpCodes.Ldc_I4_1); //Load constant 1 as int32
instructions.Add(CilOpCodes.Add); //Add
instructions.Add(CilOpCodes.Stloc, iLocal); //Store in i local
//Jump to start of loop if i < count
loopConditionStartArray.Instruction = instructions.Add(CilOpCodes.Ldloc, iLocal); //Load i
instructions.Add(CilOpCodes.Ldloc, countLocal); //Load count
instructions.Add(CilOpCodes.Blt, jumpTargetArray); //Jump back up if less than
instructions.Add(CilOpCodes.Br, returnInstruction);//Jump to return statement
//Read into list (because we don't trust large counts)
MethodDefinition listConstructorDefinition = SharedState.Instance.Importer.LookupMethod(typeof(List<>), m =>
{
return m.IsConstructor
&& m.Parameters.Count == 1
&& m.Parameters[0].ParameterType is CorLibTypeSignature corLibTypeSignature
&& corLibTypeSignature.ElementType == ElementType.I4;
});
IMethodDefOrRef listConstructorReference = MethodUtils.MakeMethodOnGenericType(SharedState.Instance.Importer, listType, listConstructorDefinition);
MethodDefinition addMethodDefinition = SharedState.Instance.Importer.LookupMethod(typeof(List<>), m => m.Name == nameof(List<>.Add));
IMethodDefOrRef addMethodReference = MethodUtils.MakeMethodOnGenericType(SharedState.Instance.Importer, listType, addMethodDefinition);
MethodDefinition toArrayMethodDefinition = SharedState.Instance.Importer.LookupMethod(typeof(List<>), m => m.Name == nameof(List<>.ToArray));
IMethodDefOrRef toArrayMethodReference = MethodUtils.MakeMethodOnGenericType(SharedState.Instance.Importer, listType, toArrayMethodDefinition);
readAsListInstruction.Instruction = instructions.Add(CilOpCodes.Ldc_I4, MaxArraySize);
instructions.Add(CilOpCodes.Newobj, listConstructorReference);
instructions.Add(CilOpCodes.Stloc, listLocal);
instructions.Add(CilOpCodes.Ldc_I4_0); //Load 0 as an int32
instructions.Add(CilOpCodes.Stloc, iLocal); //Store in count
//Create an empty, unconditional branch which will jump down to the loop condition.
//This converts the do..while loop into a for loop.
instructions.Add(CilOpCodes.Br, loopConditionStartList);
//Now we just read pair, increment i, compare against count, and jump back to here if it's less
ICilLabel jumpTargetList = instructions.Add(CilOpCodes.Nop).CreateLabel(); //Create a dummy instruction to jump back to
//Read byte and add to list
instructions.Add(CilOpCodes.Ldloc, listLocal);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.AddCall(elementReadMethod);
instructions.AddCall(addMethodReference);
//Increment i
instructions.Add(CilOpCodes.Ldloc, iLocal); //Load i local
instructions.Add(CilOpCodes.Ldc_I4_1); //Load constant 1 as int32
instructions.Add(CilOpCodes.Add); //Add
instructions.Add(CilOpCodes.Stloc, iLocal); //Store in i local
//Jump to start of loop if i < count
loopConditionStartList.Instruction = instructions.Add(CilOpCodes.Ldloc, iLocal); //Load i
instructions.Add(CilOpCodes.Ldloc, countLocal); //Load count
instructions.Add(CilOpCodes.Blt, jumpTargetList); //Jump back up if less than
instructions.Add(CilOpCodes.Ldloc, listLocal);
instructions.AddCall(toArrayMethodReference);
instructions.Add(CilOpCodes.Stloc, arrayLocal);
returnInstruction.Instruction = instructions.Add(CilOpCodes.Nop);
if (align)
{
instructions.Add(CilOpCodes.Ldarg_0);
instructions.AddCall(alignStreamMethod);
}
instructions.Add(CilOpCodes.Ldloc, arrayLocal);
instructions.Add(CilOpCodes.Ret);
instructions.OptimizeMacros();
return method;
}
private static IMethodDescriptor MakePrimitiveMethod(string uniqueName, UniversalNode node, bool align)
{
IMethodDescriptor primitiveMethod = GetPrimitiveMethod(node);
if (align)
{
MethodDefinition method = NewMethod(uniqueName, primitiveMethod.Signature!.ReturnType);
CilInstructionCollection instructions = method.GetInstructions();
instructions.Add(CilOpCodes.Ldarg_0);
instructions.AddCall(primitiveMethod);
if (align)
{
instructions.Add(CilOpCodes.Ldarg_0);
instructions.AddCall(alignStreamMethod);
}
instructions.Add(CilOpCodes.Ret);
return method;
}
else
{
return primitiveMethod;
}
}
/// <summary>
/// Array and primitive read methods have the Func&lt;AssetReader, T&gt; signature.<br/>
/// Others have the Action&lt;T, AssetReader&gt; signature.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private static bool IsArrayOrPrimitive(this TypeSignature type)
{
return type is SzArrayTypeSignature or CorLibTypeSignature or TypeDefOrRefSignature { Namespace: "AssetRipper.Primitives", Name: nameof(Utf8String) };
}
private static bool IsTypeDefinition(this TypeSignature type)
{
return type is TypeDefOrRefSignature defOrRefSignature && defOrRefSignature.Type is TypeDefinition;
}
private static IMethodDescriptor GetPrimitiveMethod(UniversalNode node)
{
return node.NodeType switch
{
NodeType.Boolean => primitiveReadMethods[ElementType.Boolean],
NodeType.Character => primitiveReadMethods[ElementType.Char],
NodeType.Int8 => primitiveReadMethods[ElementType.I1],
NodeType.UInt8 => primitiveReadMethods[ElementType.U1],
NodeType.Int16 => primitiveReadMethods[ElementType.I2],
NodeType.UInt16 => primitiveReadMethods[ElementType.U2],
NodeType.Int32 => primitiveReadMethods[ElementType.I4],
NodeType.UInt32 => primitiveReadMethods[ElementType.U4],
NodeType.Int64 => primitiveReadMethods[ElementType.I8],
NodeType.UInt64 => primitiveReadMethods[ElementType.U8],
NodeType.Single => primitiveReadMethods[ElementType.R4],
NodeType.Double => primitiveReadMethods[ElementType.R8],
NodeType.String => primitiveReadMethods[ElementType.String],
_ => throw new NotSupportedException(node.TypeName),
};
}
private static MethodDefinition NewMethod(string uniqueName, TypeSignature parameter)
{
if (parameter.IsArrayOrPrimitive())
{
MethodSignature methodSignature = MethodSignature.CreateStatic(parameter);
MethodDefinition method = new MethodDefinition($"{ReadMethod}_{uniqueName}", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, methodSignature);
method.CilMethodBody = new CilMethodBody();
method.AddParameter(endianSpanReaderReference, "reader");
method.AddExtensionAttribute(SharedState.Instance.Importer);
return method;
}
else
{
MethodSignature methodSignature = MethodSignature.CreateStatic(SharedState.Instance.Importer.Void);
MethodDefinition method = new MethodDefinition($"{ReadMethod}_{uniqueName}", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, methodSignature);
method.CilMethodBody = new CilMethodBody();
method.AddParameter(parameter, "value");
method.AddParameter(endianSpanReaderReference, "reader");
method.AddExtensionAttribute(SharedState.Instance.Importer);
return method;
}
}
private static CilInstruction AddCall(this CilInstructionCollection instructions, IMethodDescriptor method)
{
return method is MethodDefinition definition && definition.IsStatic
? instructions.Add(CilOpCodes.Call, method)
: instructions.Add(CilOpCodes.Callvirt, method);
}
private static IMethodDefOrRef GetDefaultConstructor(this TypeSignature type)
{
return type switch
{
TypeDefOrRefSignature typeDefOrRefSignature => typeDefOrRefSignature.Type is TypeDefinition typeDefinition
? typeDefinition.GetDefaultConstructor()
: throw new InvalidOperationException(),
GenericInstanceTypeSignature genericInstanceTypeSignature => MethodUtils.MakeConstructorOnGenericType(SharedState.Instance.Importer, genericInstanceTypeSignature, 0),
_ => throw new NotSupportedException(),
};
}
}