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; /// /// TypeSignature for /// 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 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 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(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())); 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())); method.GenericParameters.Add(keyParameter); GenericParameter valueParameter = new GenericParameter("TValue", GenericParameterAttributes.DefaultConstructorConstraint); valueParameter.Constraints.Add(new GenericParameterConstraint(SharedState.Instance.Importer.ImportType())); 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(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())); method.GenericParameters.Add(keyParameter); GenericParameter valueParameter = new GenericParameter("TValue", GenericParameterAttributes.DefaultConstructorConstraint); valueParameter.Constraints.Add(new GenericParameterConstraint(SharedState.Instance.Importer.ImportType())); 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(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())); 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; } } /// /// Array and primitive read methods have the Func<AssetReader, T> signature.
/// Others have the Action<T, AssetReader> signature. ///
/// /// 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(), }; } }