917 lines
41 KiB
C#

using AssetRipper.AssemblyDumper.InjectedTypes;
using AssetRipper.AssemblyDumper.Methods;
using AssetRipper.AssemblyDumper.Types;
using AssetRipper.Assets;
using AssetRipper.Assets.Cloning;
using AssetRipper.Assets.Generics;
using AssetRipper.Assets.Metadata;
using System.Diagnostics;
namespace AssetRipper.AssemblyDumper.Passes;
internal static partial class Pass105_CopyValuesMethods
{
private const string CopyValuesName = nameof(IUnityAssetBase.CopyValues);
private const string DeepCloneName = "DeepClone";
private static readonly Dictionary<TypeSignatureStruct, (IMethodDescriptor, CopyMethodType)> singleTypeDictionary = new();
private static readonly Dictionary<(TypeSignatureStruct, TypeSignatureStruct), (IMethodDescriptor, CopyMethodType)> doubleTypeDictionary = new();
private static readonly HashSet<ClassGroupBase> processedGroups = new();
#nullable disable
private static TypeSignature pptrCommonType;
private static IMethodDefOrRef pptrCommonGetFileIDMethod;
private static IMethodDefOrRef pptrCommonGetPathIDMethod;
private static IMethodDefOrRef ipptrGetFileIDMethod;
private static IMethodDefOrRef ipptrGetPathIDMethod;
private static ITypeDefOrRef pptrConverterType;
private static IMethodDefOrRef pptrConverterGetSourceCollectionMethod;
private static IMethodDefOrRef pptrConverterGetTargetCollectionMethod;
private static IMethodDefOrRef utf8StringGetEmpty;
private static TypeDefinition helperType;
private static MethodDefinition duplicateArrayMethod;
private static MethodDefinition duplicateArrayArrayMethod;
private static MethodDefinition copyCapacityFromDictionary;
private static MethodDefinition copyCapacityFromList;
private static IMethodDefOrRef pptrConvertMethod;
private static ITypeDefOrRef accessPairBase;
private static IMethodDefOrRef accessPairBaseGetKey;
private static IMethodDefOrRef accessPairBaseSetKey;
private static IMethodDefOrRef accessPairBaseGetValue;
private static IMethodDefOrRef accessPairBaseSetValue;
private static ITypeDefOrRef accessListBase;
private static IMethodDefOrRef accessListBaseGetCount;
private static IMethodDefOrRef accessListBaseGetItem;
private static IMethodDefOrRef accessListBaseSetItem;
private static ITypeDefOrRef accessDictionaryBase;
private static IMethodDefOrRef accessDictionaryBaseGetCount;
private static IMethodDefOrRef accessDictionaryBaseGetPair;
private static ITypeDefOrRef assetList;
private static IMethodDefOrRef assetListSetItem;
private static ITypeDefOrRef assetDictionary;
private static IMethodDefOrRef assetDictionaryGetPair;
private static ITypeDefOrRef assetPair;
#nullable enable
public static void DoPass()
{
pptrCommonType = SharedState.Instance.Importer.ImportType<PPtr>().ToTypeSignature();
pptrCommonGetFileIDMethod = SharedState.Instance.Importer.ImportMethod<PPtr>(m => m.Name == $"get_{nameof(PPtr.FileID)}");
pptrCommonGetPathIDMethod = SharedState.Instance.Importer.ImportMethod<PPtr>(m => m.Name == $"get_{nameof(PPtr.PathID)}");
ipptrGetFileIDMethod = SharedState.Instance.Importer.ImportMethod<IPPtr>(m => m.Name == $"get_{nameof(IPPtr.FileID)}");
ipptrGetPathIDMethod = SharedState.Instance.Importer.ImportMethod<IPPtr>(m => m.Name == $"get_{nameof(IPPtr.PathID)}");
utf8StringGetEmpty = SharedState.Instance.Importer.ImportMethod<Utf8String>(m => m.Name == "get_" + nameof(Utf8String.Empty));
pptrConverterType = SharedState.Instance.Importer.ImportType<PPtrConverter>();
pptrConverterGetSourceCollectionMethod = SharedState.Instance.Importer.ImportMethod<PPtrConverter>(m => m.Name == "get_" + nameof(PPtrConverter.SourceCollection));
pptrConverterGetTargetCollectionMethod = SharedState.Instance.Importer.ImportMethod<PPtrConverter>(m => m.Name == "get_" + nameof(PPtrConverter.TargetCollection));
helperType = InjectHelper();
accessPairBase = SharedState.Instance.Importer.ImportType(typeof(AccessPairBase<,>));
accessPairBaseGetKey = SharedState.Instance.Importer.ImportMethod(typeof(AccessPairBase<,>), m => m.Name == $"get_{nameof(AccessPairBase<,>.Key)}");
accessPairBaseSetKey = SharedState.Instance.Importer.ImportMethod(typeof(AccessPairBase<,>), m => m.Name == $"set_{nameof(AccessPairBase<,>.Key)}");
accessPairBaseGetValue = SharedState.Instance.Importer.ImportMethod(typeof(AccessPairBase<,>), m => m.Name == $"get_{nameof(AccessPairBase<,>.Value)}");
accessPairBaseSetValue = SharedState.Instance.Importer.ImportMethod(typeof(AccessPairBase<,>), m => m.Name == $"set_{nameof(AccessPairBase<,>.Value)}");
accessListBase = SharedState.Instance.Importer.ImportType(typeof(AccessListBase<>));
accessListBaseGetCount = SharedState.Instance.Importer.ImportMethod(typeof(AccessListBase<>), m => m.Name == $"get_{nameof(AccessListBase<>.Count)}");
accessListBaseGetItem = SharedState.Instance.Importer.ImportMethod(typeof(AccessListBase<>), m => m.Name == "get_Item");
accessDictionaryBase = SharedState.Instance.Importer.ImportType(typeof(AccessDictionaryBase<,>));
accessDictionaryBaseGetCount = SharedState.Instance.Importer.ImportMethod(typeof(AccessDictionaryBase<,>), m => m.Name == $"get_{nameof(AccessDictionaryBase<,>.Count)}");
accessDictionaryBaseGetPair = SharedState.Instance.Importer.ImportMethod(typeof(AccessDictionaryBase<,>), m => m.Name == nameof(AccessDictionaryBase<,>.GetPair));
assetList = SharedState.Instance.Importer.ImportType(typeof(AssetList<>));
assetListSetItem = SharedState.Instance.Importer.ImportMethod(typeof(AssetList<>), m => m.Name == "set_Item");
assetDictionary = SharedState.Instance.Importer.ImportType(typeof(AssetDictionary<,>));
assetDictionaryGetPair = SharedState.Instance.Importer.ImportMethod(typeof(AssetDictionary<,>), m => m.Name == nameof(AssetDictionary<,>.GetPair));
assetPair = SharedState.Instance.Importer.ImportType(typeof(AssetPair<,>));
foreach (ClassGroupBase group in SharedState.Instance.AllGroups)
{
EnsureGroupProcessed(group);
}
foreach (SubclassGroup group in SharedState.Instance.SubclassGroups.Values)
{
bool needsConverter = GetPrimaryCopyValuesMethod(group.Interface).Parameters.Count == 2;
{
MethodDefinition method = group.Interface.AddMethod(DeepCloneName, InterfaceUtils.InterfaceMethodDeclaration, group.Interface.ToTypeSignature());
if (needsConverter)
{
method.AddParameter(pptrConverterType.ToTypeSignature(), "converter");
}
}
foreach (TypeDefinition type in group.Types)
{
MethodDefinition copyValuesMethod = GetPrimaryCopyValuesMethod(type);
MethodDefinition method = type.AddMethod(DeepCloneName, InterfaceUtils.InterfaceMethodImplementation, group.Interface.ToTypeSignature());
CilInstructionCollection instructions = method.GetInstructions();
instructions.Add(CilOpCodes.Newobj, type.GetDefaultConstructor());
instructions.Add(CilOpCodes.Dup);
instructions.Add(CilOpCodes.Ldarg_0);
if (needsConverter)
{
method.AddParameter(pptrConverterType.ToTypeSignature(), "converter");
instructions.Add(CilOpCodes.Ldarg_1);
}
instructions.Add(CilOpCodes.Call, copyValuesMethod);
instructions.Add(CilOpCodes.Ret);
}
}
IMethodDefOrRef pptrConverterConstructor = SharedState.Instance.Importer.ImportMethod<PPtrConverter>(m =>
{
return m.IsConstructor && m.Parameters.Count == 2 && m.Parameters[0].ParameterType.Name == nameof(IUnityObjectBase);
});
foreach (ClassGroup group in SharedState.Instance.ClassGroups.Values)
{
if (GetPrimaryCopyValuesMethod(group.Interface).Parameters.Count == 2)//Has converter
{
{
MethodDefinition method = group.Interface.AddMethod(CopyValuesName, InterfaceUtils.InterfaceMethodDeclaration, SharedState.Instance.Importer.Void);
method.AddParameter(group.Interface.ToTypeSignature(), "source");
}
foreach (TypeDefinition type in group.Types)
{
MethodDefinition originalCopyValuesMethod = GetPrimaryCopyValuesMethod(type);
MethodDefinition method = type.AddMethod(CopyValuesName, InterfaceUtils.InterfaceMethodImplementation, SharedState.Instance.Importer.Void);
method.AddParameter(group.Interface.ToTypeSignature(), "source");
CilInstructionCollection instructions = method.GetInstructions();
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Newobj, pptrConverterConstructor);
instructions.Add(CilOpCodes.Call, originalCopyValuesMethod);
instructions.Add(CilOpCodes.Ret);
}
}
}
TypeSignature unityAssetBaseInterfaceRef = SharedState.Instance.Importer.ImportTypeSignature<IUnityAssetBase>();
Dictionary<TypeDefinition, MethodDefinition> overridenMethods = new();
foreach (ClassGroupBase group in SharedState.Instance.AllGroups)
{
foreach (TypeDefinition type in group.Types)
{
MethodDefinition copyValuesMethod = type.AddMethod(
nameof(UnityAssetBase.CopyValues),
Pass063_CreateEmptyMethods.OverrideMethodAttributes,
SharedState.Instance.Importer.Void);
copyValuesMethod.AddParameter(unityAssetBaseInterfaceRef, "source");
copyValuesMethod.AddParameter(pptrConverterType.ToTypeSignature(), "converter");
copyValuesMethod.AddNullableContextAttribute(NullableAnnotation.MaybeNull);
overridenMethods.Add(type, copyValuesMethod);
}
}
foreach (ClassGroupBase group in SharedState.Instance.AllGroups)
{
foreach (GeneratedClassInstance instance in group.Instances)
{
MethodDefinition primaryMethod = GetPrimaryCopyValuesMethod(instance.Type);
MethodDefinition thisMethod = overridenMethods[instance.Type];
MethodDefinition? baseMethod = instance.Base is null ? null : overridenMethods[instance.Base.Type];
CilInstructionCollection instructions = thisMethod.GetInstructions();
if (group is SubclassGroup)//Optimization for subclasses since 2 null checks is unnecessary
{
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Isinst, group.Interface);
if (primaryMethod.Parameters.Count == 2)
{
instructions.Add(CilOpCodes.Ldarg_2);//Converter is needed
}
instructions.Add(CilOpCodes.Callvirt, primaryMethod);
instructions.Add(CilOpCodes.Ret);
}
else
{
CilInstructionLabel returnLabel = new();
CilInstructionLabel isNullLabel = new();
CilLocalVariable castedArgumentLocal = instructions.AddLocalVariable(group.Interface.ToTypeSignature());
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Isinst, group.Interface);
instructions.Add(CilOpCodes.Stloc, castedArgumentLocal);
instructions.Add(CilOpCodes.Ldloc, castedArgumentLocal);
instructions.Add(CilOpCodes.Ldnull);
instructions.Add(CilOpCodes.Cgt_Un);
instructions.Add(CilOpCodes.Brfalse, isNullLabel);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldloc, castedArgumentLocal);
if (primaryMethod.Parameters.Count == 2)
{
instructions.Add(CilOpCodes.Ldarg_2);//Converter is needed
}
instructions.Add(CilOpCodes.Callvirt, primaryMethod);
instructions.Add(CilOpCodes.Br, returnLabel);
isNullLabel.Instruction = instructions.Add(CilOpCodes.Nop);
if (baseMethod is null)//Object
{
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Callvirt, instance.Type.GetMethodByName(nameof(IUnityAssetBase.Reset)));
}
else
{
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Ldarg_2);
instructions.Add(CilOpCodes.Call, baseMethod);
}
returnLabel.Instruction = instructions.Add(CilOpCodes.Ret);
}
}
}
singleTypeDictionary.Clear();
doubleTypeDictionary.Clear();
processedGroups.Clear();
overridenMethods.Clear();
}
private static TypeDefinition InjectHelper()
{
TypeDefinition clonedType = SharedState.Instance.InjectHelperType(typeof(CopyValuesHelper));
duplicateArrayMethod = clonedType.GetMethodByName(nameof(CopyValuesHelper.DuplicateArray));
duplicateArrayArrayMethod = clonedType.GetMethodByName(nameof(CopyValuesHelper.DuplicateArrayArray));
pptrConvertMethod = clonedType.GetMethodByName(nameof(CopyValuesHelper.ConvertPPtr));
copyCapacityFromDictionary = clonedType.GetMethodByName(nameof(CopyValuesHelper.CopyCapacityFrom_Dictionary));
copyCapacityFromList = clonedType.GetMethodByName(nameof(CopyValuesHelper.CopyCapacityFrom_List));
return clonedType;
}
private static void EnsureGroupProcessed(ClassGroupBase group)
{
if (!processedGroups.Add(group))
{
return;
}
if (group.IsPPtr)
{
{
MethodDefinition method = group.Interface.AddMethod(CopyValuesName, InterfaceUtils.InterfaceMethodDeclaration, SharedState.Instance.Importer.Void);
method.AddParameter(group.Interface.ToTypeSignature(), "source");
method.AddParameter(pptrConverterType.ToTypeSignature(), "converter");
method.AddNullableContextAttribute(NullableAnnotation.MaybeNull);
singleTypeDictionary.Add(group.Interface.ToTypeSignature(), (method, CopyMethodType.Callvirt | CopyMethodType.HasConverter));
}
foreach (TypeDefinition type in group.Types)
{
MethodDefinition method = type.AddMethod(CopyValuesName, InterfaceUtils.InterfaceMethodImplementation, SharedState.Instance.Importer.Void);
method.AddParameter(group.Interface.ToTypeSignature(), "source");
Parameter converterParam = method.AddParameter(pptrConverterType.ToTypeSignature(), "converter");
method.AddNullableContextAttribute(NullableAnnotation.MaybeNull);
CilInstructionCollection instructions = method.GetInstructions();
CilInstructionLabel returnLabel = new();
CilInstructionLabel isNullLabel = new();
//If other is null
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Brfalse, isNullLabel);
//Not null
{
//Convert PPtr
CilLocalVariable convertedPPtr = instructions.AddLocalVariable(pptrCommonType);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Ldarg_2);
instructions.Add(CilOpCodes.Call, pptrConvertMethod.MakeGenericInstanceMethod(GetPPtrTypeArgument(type, group.Interface)));
instructions.Add(CilOpCodes.Stloc, convertedPPtr);
//Store FileID
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldloca, convertedPPtr);
instructions.Add(CilOpCodes.Call, pptrCommonGetFileIDMethod);
instructions.Add(CilOpCodes.Stfld, type.GetFieldByName("m_FileID_"));
//Store PathID
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldloca, convertedPPtr);
instructions.Add(CilOpCodes.Call, pptrCommonGetPathIDMethod);
FieldDefinition pathIDField = type.GetFieldByName("m_PathID_");
if (pathIDField.Signature!.FieldType is CorLibTypeSignature { ElementType: ElementType.I4 })
{
instructions.Add(CilOpCodes.Conv_Ovf_I4);//Convert I8 to I4
}
instructions.Add(CilOpCodes.Stfld, pathIDField);
instructions.Add(CilOpCodes.Br, returnLabel);
}
isNullLabel.Instruction = instructions.Add(CilOpCodes.Nop);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Callvirt, type.GetMethodByName(nameof(IUnityAssetBase.Reset)));
returnLabel.Instruction = instructions.Add(CilOpCodes.Ret);
singleTypeDictionary.Add(type.ToTypeSignature(), (method, CopyMethodType.HasConverter));
}
}
else
{
bool needsConverter = false;
bool needsNullCheck = group is SubclassGroup;
Dictionary<TypeDefinition, MethodDefinition> instanceMethods = new();
foreach (GeneratedClassInstance instance in group.Instances)
{
MethodDefinition method = instance.Type.AddMethod(CopyValuesName, InterfaceUtils.InterfaceMethodImplementation, SharedState.Instance.Importer.Void);
method.AddParameter(group.Interface.ToTypeSignature(), "source");
CilInstructionCollection instructions = method.GetInstructions();
CilInstructionLabel returnLabel = new();
CilInstructionLabel isNullLabel = new();
if (needsNullCheck)
{
method.AddNullableContextAttribute(NullableAnnotation.MaybeNull);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Ldnull);
instructions.Add(CilOpCodes.Cgt_Un);
instructions.Add(CilOpCodes.Brfalse, isNullLabel);
}
foreach (ClassProperty classProperty in instance.Properties)
{
if (classProperty.BackingField is not null)
{
TypeSignature fieldTypeSignature = classProperty.BackingField.Signature!.FieldType;
if (fieldTypeSignature is CorLibTypeSignature)
{
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Callvirt, classProperty.Base.Definition.GetMethod!);
instructions.Add(CilOpCodes.Stfld, classProperty.BackingField);
}
else if (fieldTypeSignature is TypeDefOrRefSignature { Namespace: "AssetRipper.Primitives", Name: nameof(Utf8String) })
{
if (classProperty.Base.AbsentRange.IsEmpty())
{
// m_Field = source.Property;
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Callvirt, classProperty.Base.Definition.GetMethod!);
instructions.Add(CilOpCodes.Stfld, classProperty.BackingField);
}
else
{
// m_Field = source.Property ?? Utf8String.Empty;
CilInstructionLabel stfldLabel = new();
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Callvirt, classProperty.Base.Definition.GetMethod!);
instructions.Add(CilOpCodes.Dup);
instructions.Add(CilOpCodes.Brtrue, stfldLabel);
instructions.Add(CilOpCodes.Pop);
instructions.Add(CilOpCodes.Call, utf8StringGetEmpty);
stfldLabel.Instruction = instructions.Add(CilOpCodes.Stfld, classProperty.BackingField);
}
}
else if (fieldTypeSignature is SzArrayTypeSignature arrayTypeSignature)
{
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Callvirt, classProperty.Base.Definition.GetMethod!);
instructions.Add(CilOpCodes.Call, MakeDuplicateArrayMethod(arrayTypeSignature));
instructions.Add(CilOpCodes.Stfld, classProperty.BackingField);
}
else
{
(IMethodDescriptor fieldCopyMethod, CopyMethodType copyMethodType) = GetOrMakeMethod(
fieldTypeSignature,
classProperty.Base.Definition.Signature!.ReturnType);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldfld, classProperty.BackingField);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Callvirt, classProperty.Base.Definition.GetMethod!);
if (HasConverter(copyMethodType))
{
instructions.Add(CilOpCodes.Ldarg_2);
needsConverter = true;
}
instructions.Add(GetCallOpCode(copyMethodType), fieldCopyMethod);
}
}
}
if (needsNullCheck)
{
instructions.Add(CilOpCodes.Br, returnLabel);
isNullLabel.Instruction = instructions.Add(CilOpCodes.Nop);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Callvirt, instance.Type.GetMethodByName(nameof(IUnityAssetBase.Reset)));
}
returnLabel.Instruction = instructions.Add(CilOpCodes.Ret);
instanceMethods.Add(instance.Type, method);
}
foreach ((TypeDefinition type, MethodDefinition method) in instanceMethods)
{
if (needsConverter)
{
method.AddParameter(pptrConverterType.ToTypeSignature(), "converter");
singleTypeDictionary.Add(type.ToTypeSignature(), (method, CopyMethodType.HasConverter));
}
else
{
singleTypeDictionary.Add(type.ToTypeSignature(), (method, CopyMethodType.None));
}
}
{
MethodDefinition method = group.Interface.AddMethod(CopyValuesName, InterfaceUtils.InterfaceMethodDeclaration, SharedState.Instance.Importer.Void);
method.AddParameter(group.Interface.ToTypeSignature(), "source");
if (needsNullCheck)
{
method.AddNullableContextAttribute(NullableAnnotation.MaybeNull);
}
if (needsConverter)
{
method.AddParameter(pptrConverterType.ToTypeSignature(), "converter");
singleTypeDictionary.Add(group.Interface.ToTypeSignature(), (method, CopyMethodType.Callvirt | CopyMethodType.HasConverter));
}
else
{
singleTypeDictionary.Add(group.Interface.ToTypeSignature(), (method, CopyMethodType.Callvirt));
}
}
}
}
private static (IMethodDescriptor, CopyMethodType) GetOrMakeMethod(TypeSignature targetSignature, TypeSignature sourceSignature)
{
if (singleTypeDictionary.TryGetValue(targetSignature, out (IMethodDescriptor, CopyMethodType) pair))
{
return pair;
}
else if (doubleTypeDictionary.TryGetValue((targetSignature, sourceSignature), out pair))
{
return pair;
}
switch (targetSignature)
{
case TypeDefOrRefSignature typeDefOrRefSignature:
TypeDefinition type = (TypeDefinition)typeDefOrRefSignature.Type;
EnsureGroupProcessed(SharedState.Instance.TypesToGroups[type]);
return singleTypeDictionary[type.ToTypeSignature()];
case GenericInstanceTypeSignature targetGenericSignature:
{
bool needsConverter = false;
GenericInstanceTypeSignature sourceGenericSignature = (GenericInstanceTypeSignature)sourceSignature;
MethodDefinition method = helperType.AddMethod(
MakeUniqueCopyValuesName(targetSignature, sourceSignature),
StaticClassCreator.StaticMethodAttributes,
SharedState.Instance.Importer.Void);
method.AddParameter(targetSignature, "target");
method.AddParameter(sourceSignature, "source");
CilInstructionCollection instructions = method.GetInstructions();
switch (targetGenericSignature.GenericType.Name?.ToString())
{
case $"{nameof(AssetDictionary<,>)}`2":
{
//Argument 0 (target) is AssetDictionary`2. Argument 1 (source) is AccessDictionaryBase`2.
TypeSignature targetKeyTypeSignature = targetGenericSignature.TypeArguments[0];
TypeSignature targetValueTypeSignature = targetGenericSignature.TypeArguments[1];
TypeSignature targetPairTypeSignature = assetPair.MakeGenericInstanceType(targetKeyTypeSignature, targetValueTypeSignature);
TypeSignature sourceKeyTypeSignature = sourceGenericSignature.TypeArguments[0];
TypeSignature sourceValueTypeSignature = sourceGenericSignature.TypeArguments[1];
TypeSignature sourcePairTypeSignature = accessPairBase.MakeGenericInstanceType(sourceKeyTypeSignature, sourceValueTypeSignature);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Call, copyCapacityFromDictionary.MakeGenericInstanceMethod(targetKeyTypeSignature, targetValueTypeSignature, sourceKeyTypeSignature, sourceValueTypeSignature));
CilLocalVariable iLocal = instructions.AddLocalVariable(SharedState.Instance.Importer.Int32);
instructions.Add(CilOpCodes.Ldc_I4_0);
instructions.Add(CilOpCodes.Stloc, iLocal);
CilInstructionLabel conditionLabel = new();
instructions.Add(CilOpCodes.Br, conditionLabel);
CilInstructionLabel forStartLabel = new();
forStartLabel.Instruction = instructions.Add(CilOpCodes.Nop);
(IMethodDescriptor copyMethod, CopyMethodType copyMethodType) = GetOrMakeMethod(targetPairTypeSignature, sourcePairTypeSignature);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldloc, iLocal);
instructions.Add(CilOpCodes.Callvirt, MakeAssetDictionaryGetPairMethod(targetKeyTypeSignature, targetValueTypeSignature));
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Ldloc, iLocal);
instructions.Add(CilOpCodes.Callvirt, MakeDictionaryGetPairMethod(sourceKeyTypeSignature, sourceValueTypeSignature));
if (HasConverter(copyMethodType))
{
instructions.Add(CilOpCodes.Ldarg_2);
needsConverter = true;
}
instructions.Add(GetCallOpCode(copyMethodType), copyMethod);
instructions.Add(CilOpCodes.Ldloc, iLocal);
instructions.Add(CilOpCodes.Ldc_I4_1);
instructions.Add(CilOpCodes.Add);
instructions.Add(CilOpCodes.Stloc, iLocal);
conditionLabel.Instruction = instructions.Add(CilOpCodes.Nop);
instructions.Add(CilOpCodes.Ldloc, iLocal);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Callvirt, MakeDictionaryGetCountMethod(targetKeyTypeSignature, targetValueTypeSignature));
instructions.Add(CilOpCodes.Clt);
instructions.Add(CilOpCodes.Brtrue, forStartLabel);
instructions.Add(CilOpCodes.Ret);
}
break;
case $"{nameof(AssetList<>)}`1":
{
//Argument 0 (target) is AssetList`1. Argument 1 (source) is AccessListBase`1.
TypeSignature targetElementTypeSignature = targetGenericSignature.TypeArguments[0];
TypeSignature sourceElementTypeSignature = sourceGenericSignature.TypeArguments[0];
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Call, copyCapacityFromList.MakeGenericInstanceMethod(targetElementTypeSignature, sourceElementTypeSignature));
CilLocalVariable iLocal = instructions.AddLocalVariable(SharedState.Instance.Importer.Int32);
instructions.Add(CilOpCodes.Ldc_I4_0);
instructions.Add(CilOpCodes.Stloc, iLocal);
CilInstructionLabel conditionLabel = new();
instructions.Add(CilOpCodes.Br, conditionLabel);
CilInstructionLabel forStartLabel = new();
forStartLabel.Instruction = instructions.Add(CilOpCodes.Nop);
if (targetElementTypeSignature is CorLibTypeSignature or TypeDefOrRefSignature { Namespace: "AssetRipper.Primitives", Name: nameof(Utf8String) })
{
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldloc, iLocal);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Ldloc, iLocal);
instructions.Add(CilOpCodes.Callvirt, MakeListGetItemMethod(sourceElementTypeSignature));
instructions.Add(CilOpCodes.Callvirt, MakeAssetListSetItemMethod(targetElementTypeSignature));
}
else if (targetElementTypeSignature is SzArrayTypeSignature keyArrayTypeSignature)
{
throw new NotSupportedException();
}
else
{
(IMethodDescriptor copyMethod, CopyMethodType copyMethodType) = GetOrMakeMethod(targetElementTypeSignature, sourceElementTypeSignature);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldloc, iLocal);
instructions.Add(CilOpCodes.Callvirt, MakeListGetItemMethod(targetElementTypeSignature));
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Ldloc, iLocal);
instructions.Add(CilOpCodes.Callvirt, MakeListGetItemMethod(sourceElementTypeSignature));
if (HasConverter(copyMethodType))
{
instructions.Add(CilOpCodes.Ldarg_2);
needsConverter = true;
}
instructions.Add(GetCallOpCode(copyMethodType), copyMethod);
}
instructions.Add(CilOpCodes.Ldloc, iLocal);
instructions.Add(CilOpCodes.Ldc_I4_1);
instructions.Add(CilOpCodes.Add);
instructions.Add(CilOpCodes.Stloc, iLocal);
conditionLabel.Instruction = instructions.Add(CilOpCodes.Nop);
instructions.Add(CilOpCodes.Ldloc, iLocal);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Callvirt, MakeListGetCountMethod(targetElementTypeSignature));
instructions.Add(CilOpCodes.Clt);
instructions.Add(CilOpCodes.Brtrue, forStartLabel);
instructions.Add(CilOpCodes.Ret);
}
break;
case $"{nameof(AssetPair<,>)}`2" or $"{nameof(AccessPairBase<,>)}`2":
{
TypeSignature targetKeyTypeSignature = targetGenericSignature.TypeArguments[0];
TypeSignature sourceKeyTypeSignature = sourceGenericSignature.TypeArguments[0];
TypeSignature targetValueTypeSignature = targetGenericSignature.TypeArguments[1];
TypeSignature sourceValueTypeSignature = sourceGenericSignature.TypeArguments[1];
if (targetKeyTypeSignature is CorLibTypeSignature)
{
CilInstructionLabel notNullLabel = new();
CilInstructionLabel finishLabel = new();
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Brtrue, notNullLabel);
instructions.AddDefaultValue(sourceKeyTypeSignature);
instructions.Add(CilOpCodes.Br, finishLabel);
notNullLabel.Instruction = instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Callvirt, MakePairGetKeyMethod(sourceKeyTypeSignature, sourceValueTypeSignature));
finishLabel.Instruction = instructions.Add(CilOpCodes.Callvirt, MakePairSetKeyMethod(targetKeyTypeSignature, targetValueTypeSignature));
}
else if (targetKeyTypeSignature is TypeDefOrRefSignature { Namespace: "AssetRipper.Primitives", Name: nameof(Utf8String) })
{
CilInstructionLabel notNullLabel = new();
CilInstructionLabel finishLabel = new();
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Brtrue, notNullLabel);
instructions.Add(CilOpCodes.Call, utf8StringGetEmpty);
instructions.Add(CilOpCodes.Br, finishLabel);
notNullLabel.Instruction = instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Callvirt, MakePairGetKeyMethod(sourceKeyTypeSignature, sourceValueTypeSignature));
finishLabel.Instruction = instructions.Add(CilOpCodes.Callvirt, MakePairSetKeyMethod(targetKeyTypeSignature, targetValueTypeSignature));
}
else if (targetKeyTypeSignature is SzArrayTypeSignature keyArrayTypeSignature)
{
CilInstructionLabel notNullLabel = new();
CilInstructionLabel finishLabel = new();
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Brtrue, notNullLabel);
instructions.Add(CilOpCodes.Ldnull);
instructions.Add(CilOpCodes.Br, finishLabel);
notNullLabel.Instruction = instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Callvirt, MakePairGetKeyMethod(sourceKeyTypeSignature, sourceValueTypeSignature));
finishLabel.Instruction = instructions.Add(CilOpCodes.Call, MakeDuplicateArrayMethod(keyArrayTypeSignature));
instructions.Add(CilOpCodes.Callvirt, MakePairSetKeyMethod(targetKeyTypeSignature, targetValueTypeSignature));
}
else
{
CilInstructionLabel notNullLabel = new();
CilInstructionLabel finishLabel = new();
(IMethodDescriptor keyCopyMethod, CopyMethodType keyCopyMethodType) = GetOrMakeMethod(targetKeyTypeSignature, sourceKeyTypeSignature);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Callvirt, MakePairGetKeyMethod(targetKeyTypeSignature, targetValueTypeSignature));
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Brtrue, notNullLabel);
instructions.Add(CilOpCodes.Ldnull);
instructions.Add(CilOpCodes.Br, finishLabel);
notNullLabel.Instruction = instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Callvirt, MakePairGetKeyMethod(sourceKeyTypeSignature, sourceValueTypeSignature));
int finishIndex = instructions.Count;
if (HasConverter(keyCopyMethodType))
{
instructions.Add(CilOpCodes.Ldarg_2);
needsConverter = true;
}
instructions.Add(GetCallOpCode(keyCopyMethodType), keyCopyMethod);
finishLabel.Instruction = instructions[finishIndex];
}
if (targetValueTypeSignature is CorLibTypeSignature)
{
CilInstructionLabel notNullLabel = new();
CilInstructionLabel finishLabel = new();
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Brtrue, notNullLabel);
instructions.AddDefaultValue(sourceValueTypeSignature);
instructions.Add(CilOpCodes.Br, finishLabel);
notNullLabel.Instruction = instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Callvirt, MakePairGetValueMethod(sourceKeyTypeSignature, sourceValueTypeSignature));
finishLabel.Instruction = instructions.Add(CilOpCodes.Callvirt, MakePairSetValueMethod(targetKeyTypeSignature, targetValueTypeSignature));
}
else if (targetValueTypeSignature is TypeDefOrRefSignature { Namespace: "AssetRipper.Primitives", Name: nameof(Utf8String) })
{
CilInstructionLabel notNullLabel = new();
CilInstructionLabel finishLabel = new();
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Brtrue, notNullLabel);
instructions.Add(CilOpCodes.Call, utf8StringGetEmpty);
instructions.Add(CilOpCodes.Br, finishLabel);
notNullLabel.Instruction = instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Callvirt, MakePairGetValueMethod(sourceKeyTypeSignature, sourceValueTypeSignature));
finishLabel.Instruction = instructions.Add(CilOpCodes.Callvirt, MakePairSetValueMethod(targetKeyTypeSignature, targetValueTypeSignature));
}
else if (targetValueTypeSignature is SzArrayTypeSignature valueArrayTypeSignature)
{
CilInstructionLabel notNullLabel = new();
CilInstructionLabel finishLabel = new();
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Brtrue, notNullLabel);
instructions.Add(CilOpCodes.Ldnull);
instructions.Add(CilOpCodes.Br, finishLabel);
notNullLabel.Instruction = instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Callvirt, MakePairGetValueMethod(sourceKeyTypeSignature, sourceValueTypeSignature));
finishLabel.Instruction = instructions.Add(CilOpCodes.Call, MakeDuplicateArrayMethod(valueArrayTypeSignature));
instructions.Add(CilOpCodes.Callvirt, MakePairSetValueMethod(targetKeyTypeSignature, targetValueTypeSignature));
}
else
{
CilInstructionLabel notNullLabel = new();
CilInstructionLabel finishLabel = new();
(IMethodDescriptor valueCopyMethod, CopyMethodType valueCopyMethodType) = GetOrMakeMethod(targetValueTypeSignature, sourceValueTypeSignature);
instructions.Add(CilOpCodes.Ldarg_0);
instructions.Add(CilOpCodes.Callvirt, MakePairGetValueMethod(targetKeyTypeSignature, targetValueTypeSignature));
instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Brtrue, notNullLabel);
instructions.Add(CilOpCodes.Ldnull);
instructions.Add(CilOpCodes.Br, finishLabel);
notNullLabel.Instruction = instructions.Add(CilOpCodes.Ldarg_1);
instructions.Add(CilOpCodes.Callvirt, MakePairGetValueMethod(sourceKeyTypeSignature, sourceValueTypeSignature));
int finishIndex = instructions.Count;
if (HasConverter(valueCopyMethodType))
{
instructions.Add(CilOpCodes.Ldarg_2);
needsConverter = true;
}
instructions.Add(GetCallOpCode(valueCopyMethodType), valueCopyMethod);
finishLabel.Instruction = instructions[finishIndex];
}
instructions.Add(CilOpCodes.Ret);
}
break;
default:
throw new NotSupportedException();
}
(IMethodDescriptor, CopyMethodType) result;
if (needsConverter)
{
method.AddParameter(pptrConverterType.ToTypeSignature(), "converter");
result = (method, CopyMethodType.HasConverter);
}
else
{
result = (method, CopyMethodType.None);
}
doubleTypeDictionary.Add((targetSignature, sourceSignature), result);
return result;
}
default:
throw new NotSupportedException();
}
}
private static MethodDefinition GetPrimaryCopyValuesMethod(this TypeDefinition type)
{
return (MethodDefinition)singleTypeDictionary[type.ToTypeSignature()].Item1;
}
private static IMethodDefOrRef MakeDictionaryGetCountMethod(TypeSignature keyTypeSignature, TypeSignature valueTypeSignature)
{
return MethodUtils.MakeMethodOnGenericType(
SharedState.Instance.Importer,
accessDictionaryBase.MakeGenericInstanceType(keyTypeSignature, valueTypeSignature),
accessDictionaryBaseGetCount);
}
private static IMethodDefOrRef MakeDictionaryGetPairMethod(TypeSignature keyTypeSignature, TypeSignature valueTypeSignature)
{
return MethodUtils.MakeMethodOnGenericType(
SharedState.Instance.Importer,
accessDictionaryBase.MakeGenericInstanceType(keyTypeSignature, valueTypeSignature),
accessDictionaryBaseGetPair);
}
private static IMethodDefOrRef MakeAssetDictionaryGetPairMethod(TypeSignature keyTypeSignature, TypeSignature valueTypeSignature)
{
return MethodUtils.MakeMethodOnGenericType(
SharedState.Instance.Importer,
assetDictionary.MakeGenericInstanceType(keyTypeSignature, valueTypeSignature),
assetDictionaryGetPair);
}
private static IMethodDefOrRef MakeListGetCountMethod(TypeSignature elementTypeSignature)
{
return MethodUtils.MakeMethodOnGenericType(
SharedState.Instance.Importer,
accessListBase.MakeGenericInstanceType(elementTypeSignature),
accessListBaseGetCount);
}
private static IMethodDefOrRef MakeListGetItemMethod(TypeSignature elementTypeSignature)
{
return MethodUtils.MakeMethodOnGenericType(
SharedState.Instance.Importer,
accessListBase.MakeGenericInstanceType(elementTypeSignature),
accessListBaseGetItem);
}
private static IMethodDefOrRef MakeAssetListSetItemMethod(TypeSignature elementTypeSignature)
{
return MethodUtils.MakeMethodOnGenericType(
SharedState.Instance.Importer,
assetList.MakeGenericInstanceType(elementTypeSignature),
assetListSetItem);
}
private static IMethodDefOrRef MakePairGetKeyMethod(TypeSignature keyTypeSignature, TypeSignature valueTypeSignature)
{
return MethodUtils.MakeMethodOnGenericType(
SharedState.Instance.Importer,
accessPairBase.MakeGenericInstanceType(keyTypeSignature, valueTypeSignature),
accessPairBaseGetKey);
}
private static IMethodDefOrRef MakePairSetKeyMethod(TypeSignature keyTypeSignature, TypeSignature valueTypeSignature)
{
return MethodUtils.MakeMethodOnGenericType(
SharedState.Instance.Importer,
accessPairBase.MakeGenericInstanceType(keyTypeSignature, valueTypeSignature),
accessPairBaseSetKey);
}
private static IMethodDefOrRef MakePairGetValueMethod(TypeSignature keyTypeSignature, TypeSignature valueTypeSignature)
{
return MethodUtils.MakeMethodOnGenericType(
SharedState.Instance.Importer,
accessPairBase.MakeGenericInstanceType(keyTypeSignature, valueTypeSignature),
accessPairBaseGetValue);
}
private static IMethodDefOrRef MakePairSetValueMethod(TypeSignature keyTypeSignature, TypeSignature valueTypeSignature)
{
return MethodUtils.MakeMethodOnGenericType(
SharedState.Instance.Importer,
accessPairBase.MakeGenericInstanceType(keyTypeSignature, valueTypeSignature),
accessPairBaseSetValue);
}
private static string MakeUniqueCopyValuesName(TypeSignature target, TypeSignature source)
{
return $"{CopyValuesName}__{UniqueNameFactory.MakeUniqueName(target)}__{UniqueNameFactory.MakeUniqueName(source)}";
}
private static IMethodDescriptor MakeDuplicateArrayMethod(SzArrayTypeSignature arrayTypeSignature)
{
TypeSignature elementType = arrayTypeSignature.BaseType;
if (elementType is SzArrayTypeSignature nestedArray)
{
Debug.Assert(nestedArray.BaseType is CorLibTypeSignature or TypeDefOrRefSignature { Namespace: "AssetRipper.Primitives", Name: nameof(Utf8String) });
return duplicateArrayArrayMethod.MakeGenericInstanceMethod(nestedArray.BaseType);
}
else
{
Debug.Assert(elementType is CorLibTypeSignature or TypeDefOrRefSignature { Namespace: "AssetRipper.Primitives", Name: nameof(Utf8String) });
return duplicateArrayMethod.MakeGenericInstanceMethod(elementType);
}
}
private static TypeSignature GetPPtrTypeArgument(TypeDefinition type, TypeDefinition groupInterface)
{
return TryGetPPtrTypeArgument(type)
?? TryGetPPtrTypeArgument(groupInterface)
?? throw new Exception("Could not get PPtr type argument.");
}
private static TypeSignature? TryGetPPtrTypeArgument(TypeDefinition type)
{
foreach (InterfaceImplementation implem in type.Interfaces)
{
if (implem.Interface is TypeSpecification specification
&& specification.Signature is GenericInstanceTypeSignature genericInstanceTypeSignature
&& genericInstanceTypeSignature.GenericType.Name == $"{nameof(IPPtr<>)}`1")
{
return genericInstanceTypeSignature.TypeArguments[0];
}
}
return null;
}
private static bool HasConverter(CopyMethodType copyMethodType)
{
return (copyMethodType & CopyMethodType.HasConverter) != 0;
}
private static CilOpCode GetCallOpCode(CopyMethodType copyMethodType)
{
return (copyMethodType & CopyMethodType.Callvirt) != 0 ? CilOpCodes.Callvirt : CilOpCodes.Call;
}
}