From a6b7dbf37e331b817d0dd12e936545bc9343f200 Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Thu, 14 Sep 2023 23:33:02 +0200 Subject: [PATCH 01/18] Generator(unstrip): Split unstrip into smaller methods --- .../Utils/UnstripTranslator.cs | 349 ++++++++++-------- 1 file changed, 199 insertions(+), 150 deletions(-) diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index 06df34a0..c0696da3 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -6,179 +6,227 @@ namespace Il2CppInterop.Generator.Utils; -public static class UnstripTranslator +public class UnstripTranslator { public static bool TranslateMethod(MethodDefinition original, MethodDefinition target, TypeRewriteContext typeRewriteContext, RuntimeAssemblyReferences imports) { - if (!original.HasBody) return true; + var translator = new UnstripTranslator(original, target, typeRewriteContext, imports); + return translator.Translate(); + } + + private readonly MethodDefinition _original, _target; + private readonly RuntimeAssemblyReferences _imports; + + private readonly RewriteGlobalContext _globalContext; + private readonly ILProcessor _targetBuilder; + + private UnstripTranslator(MethodDefinition original, MethodDefinition target, + TypeRewriteContext typeRewriteContext, RuntimeAssemblyReferences imports) + { + _original = original; + _target = target; + _imports = imports; - var globalContext = typeRewriteContext.AssemblyContext.GlobalContext; - foreach (var variableDefinition in original.Body.Variables) + _globalContext = typeRewriteContext.AssemblyContext.GlobalContext; + _targetBuilder = target.Body.GetILProcessor(); + } + + private bool Translate() + { + if (!_original.HasBody) return true; + + foreach (var variableDefinition in _original.Body.Variables) { var variableType = - Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, variableDefinition.VariableType, - imports); + Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, variableDefinition.VariableType, + _imports); if (variableType == null) return false; - target.Body.Variables.Add(new VariableDefinition(variableType)); + _target.Body.Variables.Add(new VariableDefinition(variableType)); } - var targetBuilder = target.Body.GetILProcessor(); - foreach (var bodyInstruction in original.Body.Instructions) - if (bodyInstruction.OpCode.OperandType == OperandType.InlineField) + foreach (var bodyInstruction in _original.Body.Instructions) + if (!Translate(bodyInstruction)) + return false; + + return true; + } + + private bool Translate(Instruction ins) + { + return ins.OpCode.OperandType switch + { + OperandType.InlineField => InlineField(ins), + OperandType.InlineMethod => InlineMethod(ins), + OperandType.InlineType => InlineType(ins), + OperandType.InlineSig => InlineSig(ins), + OperandType.InlineTok => InlineTok(ins), + _ => Copy(ins), + }; + } + + private bool InlineField(Instruction ins) + { + var fieldArg = (FieldReference)ins.Operand; + var fieldDeclarer = + Pass80UnstripMethods.ResolveTypeInNewAssembliesRaw(_globalContext, fieldArg.DeclaringType, _imports); + if (fieldDeclarer == null) return false; + + var newField = fieldDeclarer.Resolve().Fields.SingleOrDefault(it => it.Name == fieldArg.Name); + if (newField != null) + { + _targetBuilder.Emit(ins.OpCode, _imports.Module.ImportReference(newField)); + return true; + } + + if (ins.OpCode == OpCodes.Ldfld || ins.OpCode == OpCodes.Ldsfld) + { + var getterMethod = fieldDeclarer.Resolve().Properties + .SingleOrDefault(it => it.Name == fieldArg.Name)?.GetMethod; + if (getterMethod == null) return false; + + _targetBuilder.Emit(OpCodes.Call, _imports.Module.ImportReference(getterMethod)); + return true; + } + + if (ins.OpCode == OpCodes.Stfld || ins.OpCode == OpCodes.Stsfld) + { + var setterMethod = fieldDeclarer.Resolve().Properties + .SingleOrDefault(it => it.Name == fieldArg.Name)?.SetMethod; + if (setterMethod == null) return false; + + _targetBuilder.Emit(OpCodes.Call, _imports.Module.ImportReference(setterMethod)); + return true; + } + + return false; + } + + private bool InlineMethod(Instruction ins) + { + var methodArg = (MethodReference)ins.Operand; + var methodDeclarer = + Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, methodArg.DeclaringType, _imports); + if (methodDeclarer == null) return false; // todo: generic methods + + var newReturnType = + Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, methodArg.ReturnType, _imports); + if (newReturnType == null) return false; + + var newMethod = new MethodReference(methodArg.Name, newReturnType, methodDeclarer); + newMethod.HasThis = methodArg.HasThis; + foreach (var methodArgParameter in methodArg.Parameters) + { + var newParamType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, + methodArgParameter.ParameterType, _imports); + if (newParamType == null) return false; + + var newParam = new ParameterDefinition(methodArgParameter.Name, methodArgParameter.Attributes, + newParamType); + newMethod.Parameters.Add(newParam); + } + + _targetBuilder.Emit(ins.OpCode, _imports.Module.ImportReference(newMethod)); + return true; + } + + private bool InlineType(Instruction ins) + { + var targetType = (TypeReference)ins.Operand; + if (targetType is GenericParameter genericParam) + { + if (genericParam.Owner is TypeReference paramOwner) { - var fieldArg = (FieldReference)bodyInstruction.Operand; - var fieldDeclarer = - Pass80UnstripMethods.ResolveTypeInNewAssembliesRaw(globalContext, fieldArg.DeclaringType, imports); - if (fieldDeclarer == null) return false; - var newField = fieldDeclarer.Resolve().Fields.SingleOrDefault(it => it.Name == fieldArg.Name); - if (newField != null) - { - targetBuilder.Emit(bodyInstruction.OpCode, imports.Module.ImportReference(newField)); - } - else - { - if (bodyInstruction.OpCode == OpCodes.Ldfld || bodyInstruction.OpCode == OpCodes.Ldsfld) - { - var getterMethod = fieldDeclarer.Resolve().Properties - .SingleOrDefault(it => it.Name == fieldArg.Name)?.GetMethod; - if (getterMethod == null) return false; - - targetBuilder.Emit(OpCodes.Call, imports.Module.ImportReference(getterMethod)); - } - else if (bodyInstruction.OpCode == OpCodes.Stfld || bodyInstruction.OpCode == OpCodes.Stsfld) - { - var setterMethod = fieldDeclarer.Resolve().Properties - .SingleOrDefault(it => it.Name == fieldArg.Name)?.SetMethod; - if (setterMethod == null) return false; - - targetBuilder.Emit(OpCodes.Call, imports.Module.ImportReference(setterMethod)); - } - else - { - return false; - } - } + var newTypeOwner = + Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, paramOwner, _imports); + if (newTypeOwner == null) return false; + targetType = newTypeOwner.GenericParameters.Single(it => it.Name == targetType.Name); } - else if (bodyInstruction.OpCode.OperandType == OperandType.InlineMethod) + else { - var methodArg = (MethodReference)bodyInstruction.Operand; - var methodDeclarer = - Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, methodArg.DeclaringType, imports); - if (methodDeclarer == null) return false; // todo: generic methods - - var newReturnType = - Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, methodArg.ReturnType, imports); - if (newReturnType == null) return false; - - var newMethod = new MethodReference(methodArg.Name, newReturnType, methodDeclarer); - newMethod.HasThis = methodArg.HasThis; - foreach (var methodArgParameter in methodArg.Parameters) - { - var newParamType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, - methodArgParameter.ParameterType, imports); - if (newParamType == null) return false; + targetType = _target.GenericParameters.Single(it => it.Name == targetType.Name); + } + } + else + { + targetType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, targetType, _imports); + if (targetType == null) return false; + } - var newParam = new ParameterDefinition(methodArgParameter.Name, methodArgParameter.Attributes, - newParamType); - newMethod.Parameters.Add(newParam); - } + if (ins.OpCode == OpCodes.Castclass && !targetType.IsValueType) + { + _targetBuilder.Emit(OpCodes.Call, + _imports.Module.ImportReference(new GenericInstanceMethod(_imports.Il2CppObjectBase_Cast.Value) + { GenericArguments = { targetType } })); + return true; + } - targetBuilder.Emit(bodyInstruction.OpCode, imports.Module.ImportReference(newMethod)); - } - else if (bodyInstruction.OpCode.OperandType == OperandType.InlineType) - { - var targetType = (TypeReference)bodyInstruction.Operand; - if (targetType is GenericParameter genericParam) - { - if (genericParam.Owner is TypeReference paramOwner) - { - var newTypeOwner = - Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, paramOwner, imports); - if (newTypeOwner == null) return false; - targetType = newTypeOwner.GenericParameters.Single(it => it.Name == targetType.Name); - } - else - { - targetType = target.GenericParameters.Single(it => it.Name == targetType.Name); - } - } - else - { - targetType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, targetType, imports); - if (targetType == null) return false; - } + if (ins.OpCode == OpCodes.Isinst && !targetType.IsValueType) + { + _targetBuilder.Emit(OpCodes.Call, + _imports.Module.ImportReference(new GenericInstanceMethod(_imports.Il2CppObjectBase_TryCast.Value) + { GenericArguments = { targetType } })); + return true; + } - if (bodyInstruction.OpCode == OpCodes.Castclass && !targetType.IsValueType) - { - targetBuilder.Emit(OpCodes.Call, - imports.Module.ImportReference(new GenericInstanceMethod(imports.Il2CppObjectBase_Cast.Value) - { GenericArguments = { targetType } })); - } - else if (bodyInstruction.OpCode == OpCodes.Isinst && !targetType.IsValueType) - { - targetBuilder.Emit(OpCodes.Call, - imports.Module.ImportReference(new GenericInstanceMethod(imports.Il2CppObjectBase_TryCast.Value) - { GenericArguments = { targetType } })); - } - else if (bodyInstruction.OpCode == OpCodes.Newarr && !targetType.IsValueType) - { - targetBuilder.Emit(OpCodes.Conv_I8); - - var il2cppTypeArray = new GenericInstanceType(imports.Il2CppReferenceArray) - { GenericArguments = { targetType } }; - targetBuilder.Emit(OpCodes.Newobj, imports.Module.ImportReference( - new MethodReference(".ctor", imports.Module.Void(), il2cppTypeArray) - { - HasThis = true, - Parameters = { new ParameterDefinition(imports.Module.Long()) } - })); - } - else + if (ins.OpCode == OpCodes.Newarr && !targetType.IsValueType) + { + _targetBuilder.Emit(OpCodes.Conv_I8); + + var il2cppTypeArray = new GenericInstanceType(_imports.Il2CppReferenceArray) + { GenericArguments = { targetType } }; + _targetBuilder.Emit(OpCodes.Newobj, _imports.Module.ImportReference( + new MethodReference(".ctor", _imports.Module.Void(), il2cppTypeArray) { - targetBuilder.Emit(bodyInstruction.OpCode, targetType); - } - } - else if (bodyInstruction.OpCode.OperandType == OperandType.InlineSig) - { - // todo: rewrite sig if this ever happens in unity types - return false; - } - else if (bodyInstruction.OpCode.OperandType == OperandType.InlineTok) + HasThis = true, + Parameters = { new ParameterDefinition(_imports.Module.Long()) } + })); + return true; + } + + _targetBuilder.Emit(ins.OpCode, targetType); + return true; + } + + private bool InlineSig(Instruction ins) + { + // todo: rewrite sig if this ever happens in unity types + return false; + } + + private bool InlineTok(Instruction ins) + { + var targetTok = (TypeReference)ins.Operand; + if (targetTok is GenericParameter genericParam) + { + if (genericParam.Owner is TypeReference paramOwner) { - var targetTok = bodyInstruction.Operand as TypeReference; - if (targetTok == null) - return false; - if (targetTok is GenericParameter genericParam) - { - if (genericParam.Owner is TypeReference paramOwner) - { - var newTypeOwner = - Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, paramOwner, imports); - if (newTypeOwner == null) return false; - targetTok = newTypeOwner.GenericParameters.Single(it => it.Name == targetTok.Name); - } - else - { - targetTok = target.GenericParameters.Single(it => it.Name == targetTok.Name); - } - } - else - { - targetTok = Pass80UnstripMethods.ResolveTypeInNewAssemblies(globalContext, targetTok, imports); - if (targetTok == null) return false; - } - - targetBuilder.Emit(OpCodes.Call, - imports.Module.ImportReference( - new GenericInstanceMethod(imports.Il2CppSystemRuntimeTypeHandleGetRuntimeTypeHandle.Value) - { GenericArguments = { targetTok } })); + var newTypeOwner = + Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, paramOwner, _imports); + if (newTypeOwner == null) return false; + targetTok = newTypeOwner.GenericParameters.Single(it => it.Name == targetTok.Name); } else { - targetBuilder.Append(bodyInstruction); + targetTok = _target.GenericParameters.Single(it => it.Name == targetTok.Name); } + } + else + { + targetTok = Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, targetTok, _imports); + if (targetTok == null) return false; + } + _targetBuilder.Emit(OpCodes.Call, + _imports.Module.ImportReference( + new GenericInstanceMethod(_imports.Il2CppSystemRuntimeTypeHandleGetRuntimeTypeHandle.Value) + { GenericArguments = { targetTok } })); + return true; + } + + private bool Copy(Instruction ins) + { + _targetBuilder.Append(ins); return true; } @@ -194,3 +242,4 @@ public static void ReplaceBodyWithException(MethodDefinition newMethod, RuntimeA processor.Emit(OpCodes.Ret); } } + From fdac66566b49769ae2b357af48a497071ef12953 Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Fri, 15 Sep 2023 18:25:44 +0200 Subject: [PATCH 02/18] Generator(unstrip): Save rich unstrip statistics to disk --- .../Pass81FillUnstrippedMethodBodies.cs | 89 ++++++++- .../Utils/SimpleJsonWriter.cs | 183 ++++++++++++++++++ .../Utils/UnstripTranslator.cs | 121 ++++++++---- 3 files changed, 351 insertions(+), 42 deletions(-) create mode 100644 Il2CppInterop.Generator/Utils/SimpleJsonWriter.cs diff --git a/Il2CppInterop.Generator/Passes/Pass81FillUnstrippedMethodBodies.cs b/Il2CppInterop.Generator/Passes/Pass81FillUnstrippedMethodBodies.cs index eadd4df7..76807429 100644 --- a/Il2CppInterop.Generator/Passes/Pass81FillUnstrippedMethodBodies.cs +++ b/Il2CppInterop.Generator/Passes/Pass81FillUnstrippedMethodBodies.cs @@ -1,9 +1,12 @@ using System.Collections.Generic; +using System.IO.Compression; +using System.Text; using Il2CppInterop.Common; using Il2CppInterop.Generator.Contexts; using Il2CppInterop.Generator.Utils; using Microsoft.Extensions.Logging; using Mono.Cecil; +using Mono.Cecil.Cil; namespace Il2CppInterop.Generator.Passes; @@ -17,14 +20,14 @@ private static readonly public static void DoPass(RewriteGlobalContext context) { var methodsSucceeded = 0; - var methodsFailed = 0; + var methodsFailed = new List<(MethodDefinition method, UnstripTranslator.Result result)>(); foreach (var (unityMethod, newMethod, processedType, imports) in StuffToProcess) { - var success = UnstripTranslator.TranslateMethod(unityMethod, newMethod, processedType, imports); - if (success == false) + var result = UnstripTranslator.TranslateMethod(unityMethod, newMethod, processedType, imports); + if (result.IsError) { - methodsFailed++; + methodsFailed.Add((unityMethod, result)); UnstripTranslator.ReplaceBodyWithException(newMethod, imports); } else @@ -34,7 +37,83 @@ public static void DoPass(RewriteGlobalContext context) } Logger.Instance.LogInformation("IL unstrip statistics: {MethodsSucceeded} successful, {MethodsFailed} failed", methodsSucceeded, - methodsFailed); + methodsFailed.Count); + + SaveResults(context, methodsFailed); + } + + private static void SaveResults(RewriteGlobalContext context, + List<(MethodDefinition method, UnstripTranslator.Result result)> results) + { + var outPath = Path.GetDirectoryName(context.Options.OutputDir); + outPath = Path.Combine(outPath, "unstrip.json.gz"); + outPath = Path.GetFullPath(outPath); + var cwd = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar; + if (!outPath.StartsWith(cwd)) + outPath = "unstrip.json.gz"; + else + outPath = outPath.Substring(cwd.Length); + Logger.Instance.LogInformation("Saving IL unstrip statistics to {OutPath}", outPath); + + results.Sort((left, right) => StringComparer.OrdinalIgnoreCase + .Compare(left.method.MemberFullName(), right.method.MemberFullName())); + using (var gzStream = + new StreamWriter( + new GZipStream( + File.Open(outPath, FileMode.Create), + CompressionLevel.Fastest), + new UTF8Encoding(false))) + { + using (var arr = SimpleJsonWriter.Create(gzStream).Array()) + { + foreach (var (method, result) in results) + { + using (var entry = arr.Object()) + { + entry.Property("name") + .Value(method.MemberFullName()); + entry.Property("fullName") + .Value(method.FullName); + entry.Property("scope") + .Value(method.DeclaringType.Scope.Name); + entry.Property("namespace") + .Value(method.DeclaringType.Namespace); + entry.Property("type") + .Value(method.DeclaringType.Name); + entry.Property("method") + .Value(method.Name); + var insProp = entry.Property("instruction"); + if (result.offendingInstruction == null) + insProp.Value(null); + else + using (var ins = insProp.Object()) + { + ins.Property("description") + .Value(result.offendingInstruction.ToString()); + ins.Property("opCode") + .Value(result.offendingInstruction.OpCode.Name); + ins.Property("operandType") + .Value(Enum.GetName(typeof(OperandType), result.offendingInstruction.OpCode.OperandType)); + ins.Property("operandValueType") + .Value(result.offendingInstruction.Operand?.GetType().Name); + ins.Property("operand") + .Value(result.offendingInstruction.Operand?.ToString()); + } + entry.Property("result") + .Value(Enum.GetName(typeof(UnstripTranslator.ErrorType), result.type)); + entry.Property("reason") + .Value(result.reason); + } + } + } + } + } + + private static string MemberFullName(this MethodDefinition method) + { + if (method.DeclaringType == null) + return method.Name; + return $"{method.DeclaringType.FullName}::{method.Name}"; } public static void PushMethod(MethodDefinition unityMethod, MethodDefinition newMethod, diff --git a/Il2CppInterop.Generator/Utils/SimpleJsonWriter.cs b/Il2CppInterop.Generator/Utils/SimpleJsonWriter.cs new file mode 100644 index 00000000..da6781b9 --- /dev/null +++ b/Il2CppInterop.Generator/Utils/SimpleJsonWriter.cs @@ -0,0 +1,183 @@ +using static Il2CppInterop.Generator.Utils.SimpleJsonWriter; + +namespace Il2CppInterop.Generator.Utils; + +internal class SimpleJsonWriter : JsonValue, JsonObject, JsonArray +{ + public interface JsonValue + { + public JsonArray Array(); + public JsonObject Object(); + public void Value(string value); + } + + public interface JsonObject : IDisposable + { + public JsonValue Property(string name); + + } + + public interface JsonArray : JsonValue, IDisposable { } + + public static JsonValue Create(StreamWriter writer) => Create(writer, 0); + + public static JsonValue Create(StreamWriter writer, int startIndentation) => + new SimpleJsonWriter(writer, startIndentation); + + private readonly StreamWriter _writer; + private int _indent; + private bool _newline = true, _comma = false; + private readonly Stack _closeStack = new(); + + private SimpleJsonWriter(StreamWriter writer, int startIndentation) + { + _writer = writer; + _indent = startIndentation; + } + + public JsonArray Array() + { + Open("[", "]"); + return this; + } + + public JsonObject Object() + { + Open("{", "}"); + return this; + } + + public JsonValue Property(string name) + { + Comma(); + Indent(); + name = Encode(name, true); + _writer.Write(name); + _writer.Write(": "); + return this; + } + + public void Value(string value) + { + if (value == null) + _writer.Write("null"); + else + { + value = Encode(value, true); + _writer.Write(value); + } + _comma = true; + } + + private string Encode(string value, bool addDoubleQuotes = false) + { +#if NETSTANDARD + return System.Web.HttpUtility.JavaScriptStringEncode(value, addDoubleQuotes); +#else + // Stolen from https://github.com/mono/mono/blob/89f1d3cc22fd3b0848ecedbd6215b0bdfeea9477/mcs/class/System.Web/System.Web/HttpUtility.cs#L528 + if (string.IsNullOrEmpty(value)) + return addDoubleQuotes ? "\"\"" : string.Empty; + + var len = value.Length; + var needEncode = false; + char c; + for (var i = 0; i < len; i++) + { + c = value[i]; + + if (c >= 0 && c <= 31 || c == 34 || c == 39 || c == 60 || c == 62 || c == 92) + { + needEncode = true; + break; + } + } + + if (!needEncode) + return addDoubleQuotes ? "\"" + value + "\"" : value; + + var sb = new System.Text.StringBuilder(); + if (addDoubleQuotes) + sb.Append('"'); + + for (var i = 0; i < len; i++) + { + c = value[i]; + if (c >= 0 && c <= 7 || c == 11 || c >= 14 && c <= 31 || c == 39 || c == 60 || c == 62) + sb.AppendFormat("\\u{0:x4}", (int)c); + else switch ((int)c) + { + case 8: + sb.Append("\\b"); + break; + + case 9: + sb.Append("\\t"); + break; + + case 10: + sb.Append("\\n"); + break; + + case 12: + sb.Append("\\f"); + break; + + case 13: + sb.Append("\\r"); + break; + + case 34: + sb.Append("\\\""); + break; + + case 92: + sb.Append("\\\\"); + break; + + default: + sb.Append(c); + break; + } + } + + if (addDoubleQuotes) + sb.Append('"'); + + return sb.ToString(); +#endif + } + + private void Comma() + { + if (!_comma) return; + _comma = false; + _writer.WriteLine(','); + _newline = true; + } + + private void Indent() + { + if (!_newline) return; + _writer.Write(new string(' ', _indent * 2)); + _newline = false; + } + + private void Open(string open, string close) + { + Comma(); + Indent(); + _writer.WriteLine(open); + _newline = true; + _indent++; + _closeStack.Push(close); + } + + public void Dispose() + { + var close = _closeStack.Pop(); + _indent--; + Indent(); + _writer.Write(close); + _comma = true; + } +} diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index c0696da3..2b9d662a 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -8,7 +8,35 @@ namespace Il2CppInterop.Generator.Utils; public class UnstripTranslator { - public static bool TranslateMethod(MethodDefinition original, MethodDefinition target, + + public readonly struct Result + { + public static readonly Result OK = new(ErrorType.None, null, null); + public static Result Unimplemented(Instruction ins) => + new(ErrorType.Unimplemented, ins, null); + + public readonly ErrorType type; + public readonly Instruction offendingInstruction; + public readonly string reason; + public bool IsError => type != ErrorType.None; + + public Result(ErrorType type, Instruction offendingInstruction, string reason) + { + this.type = type; + this.offendingInstruction = offendingInstruction; + this.reason = reason; + } + } + + public enum ErrorType + { + None, + Unimplemented, + Unresolved, + FieldProxy, + } + + public static Result TranslateMethod(MethodDefinition original, MethodDefinition target, TypeRewriteContext typeRewriteContext, RuntimeAssemblyReferences imports) { var translator = new UnstripTranslator(original, target, typeRewriteContext, imports); @@ -32,27 +60,31 @@ public class UnstripTranslator _targetBuilder = target.Body.GetILProcessor(); } - private bool Translate() + private Result Translate() { - if (!_original.HasBody) return true; + if (!_original.HasBody) return Result.OK; foreach (var variableDefinition in _original.Body.Variables) { var variableType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, variableDefinition.VariableType, _imports); - if (variableType == null) return false; + if (variableType == null) + return new(ErrorType.Unresolved, null, $"Could not resolve variable #{variableDefinition.Index} {variableDefinition.VariableType}"); _target.Body.Variables.Add(new VariableDefinition(variableType)); } foreach (var bodyInstruction in _original.Body.Instructions) - if (!Translate(bodyInstruction)) - return false; + { + var result = Translate(bodyInstruction); + if (result.IsError) + return result; + } - return true; + return Result.OK; } - private bool Translate(Instruction ins) + private Result Translate(Instruction ins) { return ins.OpCode.OperandType switch { @@ -65,53 +97,58 @@ private bool Translate(Instruction ins) }; } - private bool InlineField(Instruction ins) + private Result InlineField(Instruction ins) { var fieldArg = (FieldReference)ins.Operand; var fieldDeclarer = Pass80UnstripMethods.ResolveTypeInNewAssembliesRaw(_globalContext, fieldArg.DeclaringType, _imports); - if (fieldDeclarer == null) return false; + if (fieldDeclarer == null) + return new(ErrorType.Unresolved, ins, $"Could not resolve declaring type {fieldArg.DeclaringType}"); var newField = fieldDeclarer.Resolve().Fields.SingleOrDefault(it => it.Name == fieldArg.Name); if (newField != null) { _targetBuilder.Emit(ins.OpCode, _imports.Module.ImportReference(newField)); - return true; + return Result.OK; } if (ins.OpCode == OpCodes.Ldfld || ins.OpCode == OpCodes.Ldsfld) { var getterMethod = fieldDeclarer.Resolve().Properties .SingleOrDefault(it => it.Name == fieldArg.Name)?.GetMethod; - if (getterMethod == null) return false; + if (getterMethod == null) + return new(ErrorType.FieldProxy, ins, $"Could not find getter for proxy property {fieldArg}"); _targetBuilder.Emit(OpCodes.Call, _imports.Module.ImportReference(getterMethod)); - return true; + return Result.OK; } if (ins.OpCode == OpCodes.Stfld || ins.OpCode == OpCodes.Stsfld) { var setterMethod = fieldDeclarer.Resolve().Properties .SingleOrDefault(it => it.Name == fieldArg.Name)?.SetMethod; - if (setterMethod == null) return false; + if (setterMethod == null) + return new(ErrorType.FieldProxy, ins, $"Could not find setter for proxy property {fieldArg}"); _targetBuilder.Emit(OpCodes.Call, _imports.Module.ImportReference(setterMethod)); - return true; + return Result.OK; } - return false; + return Result.Unimplemented(ins); } - private bool InlineMethod(Instruction ins) + private Result InlineMethod(Instruction ins) { var methodArg = (MethodReference)ins.Operand; var methodDeclarer = Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, methodArg.DeclaringType, _imports); - if (methodDeclarer == null) return false; // todo: generic methods + if (methodDeclarer == null) + return new(ErrorType.Unresolved, ins, $"Could not resolve declaring type {methodArg.DeclaringType}"); var newReturnType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, methodArg.ReturnType, _imports); - if (newReturnType == null) return false; + if (newReturnType == null) + return new(ErrorType.Unresolved, ins, $"Could not resolve return type {methodArg.ReturnType}"); var newMethod = new MethodReference(methodArg.Name, newReturnType, methodDeclarer); newMethod.HasThis = methodArg.HasThis; @@ -119,7 +156,8 @@ private bool InlineMethod(Instruction ins) { var newParamType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, methodArgParameter.ParameterType, _imports); - if (newParamType == null) return false; + if (newParamType == null) + return new(ErrorType.Unresolved, ins, $"Could not resolve parameter #{methodArgParameter.Index} {methodArgParameter.ParameterType} {methodArgParameter.Name}"); var newParam = new ParameterDefinition(methodArgParameter.Name, methodArgParameter.Attributes, newParamType); @@ -127,10 +165,10 @@ private bool InlineMethod(Instruction ins) } _targetBuilder.Emit(ins.OpCode, _imports.Module.ImportReference(newMethod)); - return true; + return Result.OK; } - private bool InlineType(Instruction ins) + private Result InlineType(Instruction ins) { var targetType = (TypeReference)ins.Operand; if (targetType is GenericParameter genericParam) @@ -139,7 +177,8 @@ private bool InlineType(Instruction ins) { var newTypeOwner = Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, paramOwner, _imports); - if (newTypeOwner == null) return false; + if (newTypeOwner == null) + return new(ErrorType.Unresolved, ins, $"Could not resolve owner type {paramOwner}"); targetType = newTypeOwner.GenericParameters.Single(it => it.Name == targetType.Name); } else @@ -149,8 +188,10 @@ private bool InlineType(Instruction ins) } else { + var oldTargetType = targetType; targetType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, targetType, _imports); - if (targetType == null) return false; + if (targetType == null) + return new(ErrorType.Unresolved, ins, $"Could not resolve type {oldTargetType}"); } if (ins.OpCode == OpCodes.Castclass && !targetType.IsValueType) @@ -158,7 +199,7 @@ private bool InlineType(Instruction ins) _targetBuilder.Emit(OpCodes.Call, _imports.Module.ImportReference(new GenericInstanceMethod(_imports.Il2CppObjectBase_Cast.Value) { GenericArguments = { targetType } })); - return true; + return Result.OK; } if (ins.OpCode == OpCodes.Isinst && !targetType.IsValueType) @@ -166,7 +207,7 @@ private bool InlineType(Instruction ins) _targetBuilder.Emit(OpCodes.Call, _imports.Module.ImportReference(new GenericInstanceMethod(_imports.Il2CppObjectBase_TryCast.Value) { GenericArguments = { targetType } })); - return true; + return Result.OK; } if (ins.OpCode == OpCodes.Newarr && !targetType.IsValueType) @@ -181,29 +222,33 @@ private bool InlineType(Instruction ins) HasThis = true, Parameters = { new ParameterDefinition(_imports.Module.Long()) } })); - return true; + return Result.OK; } _targetBuilder.Emit(ins.OpCode, targetType); - return true; + return Result.OK; } - private bool InlineSig(Instruction ins) + private Result InlineSig(Instruction ins) { // todo: rewrite sig if this ever happens in unity types - return false; + return Result.Unimplemented(ins); } - private bool InlineTok(Instruction ins) + private Result InlineTok(Instruction ins) { - var targetTok = (TypeReference)ins.Operand; + var targetTok = ins.Operand as TypeReference; + if (targetTok == null) + return Result.Unimplemented(ins); + if (targetTok is GenericParameter genericParam) { if (genericParam.Owner is TypeReference paramOwner) { var newTypeOwner = Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, paramOwner, _imports); - if (newTypeOwner == null) return false; + if (newTypeOwner == null) + return new(ErrorType.Unresolved, ins, $"Could not resolve owner type {paramOwner}"); targetTok = newTypeOwner.GenericParameters.Single(it => it.Name == targetTok.Name); } else @@ -213,21 +258,23 @@ private bool InlineTok(Instruction ins) } else { + var oldTargetTok = targetTok; targetTok = Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, targetTok, _imports); - if (targetTok == null) return false; + if (targetTok == null) + return new(ErrorType.Unresolved, ins, $"Could not resolve type {oldTargetTok}"); } _targetBuilder.Emit(OpCodes.Call, _imports.Module.ImportReference( new GenericInstanceMethod(_imports.Il2CppSystemRuntimeTypeHandleGetRuntimeTypeHandle.Value) { GenericArguments = { targetTok } })); - return true; + return Result.OK; } - private bool Copy(Instruction ins) + private Result Copy(Instruction ins) { _targetBuilder.Append(ins); - return true; + return Result.OK; } public static void ReplaceBodyWithException(MethodDefinition newMethod, RuntimeAssemblyReferences imports) From cabadd71f3a42aa3f3a50972ab57ca462b74b789 Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Fri, 15 Sep 2023 21:50:34 +0200 Subject: [PATCH 03/18] Generator(unstrip): Keep generic parameters in method calls --- .../Utils/UnstripTranslator.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index 2b9d662a..4f137e57 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -145,8 +145,11 @@ private Result InlineMethod(Instruction ins) if (methodDeclarer == null) return new(ErrorType.Unresolved, ins, $"Could not resolve declaring type {methodArg.DeclaringType}"); - var newReturnType = - Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, methodArg.ReturnType, _imports); + var newReturnType = methodArg.ReturnType switch + { + GenericParameter genericParam => genericParam, + _ => Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, methodArg.ReturnType, _imports), + }; if (newReturnType == null) return new(ErrorType.Unresolved, ins, $"Could not resolve return type {methodArg.ReturnType}"); @@ -154,8 +157,12 @@ private Result InlineMethod(Instruction ins) newMethod.HasThis = methodArg.HasThis; foreach (var methodArgParameter in methodArg.Parameters) { - var newParamType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, - methodArgParameter.ParameterType, _imports); + var newParamType = methodArgParameter.ParameterType switch + { + GenericParameter genericParam => genericParam, + _ => Pass80UnstripMethods.ResolveTypeInNewAssemblies( + _globalContext, methodArgParameter.ParameterType, _imports), + }; if (newParamType == null) return new(ErrorType.Unresolved, ins, $"Could not resolve parameter #{methodArgParameter.Index} {methodArgParameter.ParameterType} {methodArgParameter.Name}"); From e7deba27b34d0aa33745ef49a01fbb1d38d84117 Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Fri, 15 Sep 2023 22:17:07 +0200 Subject: [PATCH 04/18] Generator(unstrip): Support Ld(s)flda --- .../Utils/UnstripTranslator.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index 4f137e57..67aacc20 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -112,14 +112,32 @@ private Result InlineField(Instruction ins) return Result.OK; } - if (ins.OpCode == OpCodes.Ldfld || ins.OpCode == OpCodes.Ldsfld) + if (ins.OpCode == OpCodes.Ldfld || ins.OpCode == OpCodes.Ldsfld || + ins.OpCode == OpCodes.Ldflda || ins.OpCode == OpCodes.Ldsflda) { var getterMethod = fieldDeclarer.Resolve().Properties .SingleOrDefault(it => it.Name == fieldArg.Name)?.GetMethod; if (getterMethod == null) return new(ErrorType.FieldProxy, ins, $"Could not find getter for proxy property {fieldArg}"); - _targetBuilder.Emit(OpCodes.Call, _imports.Module.ImportReference(getterMethod)); + + if (ins.OpCode == OpCodes.Ldflda || ins.OpCode == OpCodes.Ldsflda) + { + TypeReference variableType; + if (getterMethod.ReturnType is GenericParameter genReturnType + && genReturnType.Type == GenericParameterType.Type + && getterMethod.MethodReturnType.Method is MemberReference getterMethodRef + && getterMethodRef.DeclaringType is IGenericInstance genGetterMethod) + variableType = genGetterMethod.GenericArguments[genReturnType.Position]; + else + variableType = getterMethod.ReturnType; + + var varDef = new VariableDefinition(variableType); + _target.Body.Variables.Add(varDef); + + _targetBuilder.Emit(OpCodes.Stloc, varDef); + _targetBuilder.Emit(OpCodes.Ldloca, varDef); + } return Result.OK; } From be287c014b4c3bd6627538cecc702756a0227d03 Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Fri, 15 Sep 2023 23:54:53 +0200 Subject: [PATCH 05/18] Generator(unstrip): Translate box to Il2CppSystem.Object::op_Implicit --- .../Utils/RuntimeAssemblyReferences.cs | 9 +++++++++ Il2CppInterop.Generator/Utils/UnstripTranslator.cs | 12 ++++++++++++ 2 files changed, 21 insertions(+) diff --git a/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs b/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs index 6646699a..fc9ac340 100644 --- a/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs +++ b/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs @@ -71,6 +71,8 @@ public RuntimeAssemblyReferences(ModuleDefinition module, RewriteGlobalContext g public Lazy Il2CppSystemDelegateCombine { get; private set; } public Lazy Il2CppSystemDelegateRemove { get; private set; } public Lazy Il2CppSystemRuntimeTypeHandleGetRuntimeTypeHandle { get; private set; } + public Lazy Il2CppObject { get; private set; } + public Memoize Il2CppObject_op_Implicit { get; private set; } public MethodReference WriteFieldWBarrier => globalCtx.HasGcWbarrierFieldWrite ? IL2CPP_il2cpp_gc_wbarrier_set_field.Value @@ -619,5 +621,12 @@ private void InitMethodRefs() methodReference.GenericParameters.Add(new GenericParameter("T", methodReference)); return Module.ImportReference(methodReference); }); + + Il2CppObject = new(() => globalCtx.GetAssemblyByName("mscorlib") + .NewAssembly.MainModule.GetType("Il2CppSystem.Object")); + + Il2CppObject_op_Implicit = new((param) => + Module.ImportReference(Il2CppObject.Value.Methods.Single(m => m.Name == "op_Implicit" && + m.Parameters[0].ParameterType.FullName == param.FullName))); } } diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index 67aacc20..39f07003 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -250,6 +250,18 @@ private Result InlineType(Instruction ins) return Result.OK; } + if (ins.OpCode == OpCodes.Box) + { + if (targetType.IsPrimitive || targetType.FullName == "System.String") + { + _targetBuilder.Emit(OpCodes.Call, _imports.Il2CppObject_op_Implicit.Get(targetType)); + return Result.OK; + } + + // TODO implement (blittable?) struct boxing + return Result.Unimplemented(ins); + } + _targetBuilder.Emit(ins.OpCode, targetType); return Result.OK; } From 1ba04cc5a9214e6aa67de4c9424f76b524a2d49c Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Sun, 17 Sep 2023 22:06:02 +0200 Subject: [PATCH 06/18] Generator(unstrip): Add complete newarr support --- .../Passes/Pass80UnstripMethods.cs | 41 +++++++++++---- .../Utils/UnstripTranslator.cs | 51 ++++++++++++------- 2 files changed, 63 insertions(+), 29 deletions(-) diff --git a/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs b/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs index d64773dc..e6869f2f 100644 --- a/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs +++ b/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs @@ -145,27 +145,42 @@ private static PropertyDefinition GetOrCreateProperty(MethodDefinition unityMeth internal static TypeReference? ResolveTypeInNewAssemblies(RewriteGlobalContext context, TypeReference unityType, RuntimeAssemblyReferences imports) { - var resolved = ResolveTypeInNewAssembliesRaw(context, unityType, imports); + return ResolveTypeInNewAssemblies(context, unityType, imports, out var _); + } + + internal static TypeReference? ResolveTypeInNewAssemblies(RewriteGlobalContext context, TypeReference unityType, + RuntimeAssemblyReferences imports, out TypeRewriteContext rwContext) + { + var resolved = ResolveTypeInNewAssembliesRaw(context, unityType, imports, out rwContext); return resolved != null ? imports.Module.ImportReference(resolved) : null; } internal static TypeReference? ResolveTypeInNewAssembliesRaw(RewriteGlobalContext context, TypeReference unityType, RuntimeAssemblyReferences imports) + { + return ResolveTypeInNewAssembliesRaw(context, unityType, imports, out var _); + } + + internal static TypeReference? ResolveTypeInNewAssembliesRaw(RewriteGlobalContext context, TypeReference unityType, + RuntimeAssemblyReferences imports, out TypeRewriteContext rwContext) { if (unityType is ByReferenceType) { - var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports); + var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports, out rwContext); return resolvedElementType == null ? null : new ByReferenceType(resolvedElementType); } if (unityType is GenericParameter) + { + rwContext = null; return null; + } if (unityType is ArrayType arrayType) { - if (arrayType.Rank != 1) return null; - var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports); - if (resolvedElementType == null) return null; + if (arrayType.Rank != 1) { rwContext = null; return null; } + var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports, out rwContext); + if (resolvedElementType == null) { rwContext = null; return null; } if (resolvedElementType.FullName == "System.String") return imports.Il2CppStringArray; var genericBase = resolvedElementType.IsValueType @@ -176,7 +191,7 @@ private static PropertyDefinition GetOrCreateProperty(MethodDefinition unityMeth if (unityType.DeclaringType != null) { - var enclosingResolvedType = ResolveTypeInNewAssembliesRaw(context, unityType.DeclaringType, imports); + var enclosingResolvedType = ResolveTypeInNewAssembliesRaw(context, unityType.DeclaringType, imports, out rwContext); if (enclosingResolvedType == null) return null; var resolvedNestedType = enclosingResolvedType.Resolve().NestedTypes .FirstOrDefault(it => it.Name == unityType.Name); @@ -186,13 +201,13 @@ private static PropertyDefinition GetOrCreateProperty(MethodDefinition unityMeth if (unityType is PointerType) { - var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports); + var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports, out rwContext); return resolvedElementType == null ? null : new PointerType(resolvedElementType); } if (unityType is GenericInstanceType genericInstance) { - var baseRef = ResolveTypeInNewAssembliesRaw(context, genericInstance.ElementType, imports); + var baseRef = ResolveTypeInNewAssembliesRaw(context, genericInstance.ElementType, imports, out rwContext); if (baseRef == null) return null; var newInstance = new GenericInstanceType(baseRef); foreach (var unityGenericArgument in genericInstance.GenericArguments) @@ -211,21 +226,25 @@ private static PropertyDefinition GetOrCreateProperty(MethodDefinition unityMeth if ((targetAssemblyName == "mscorlib" || targetAssemblyName == "netstandard") && (unityType.IsValueType || unityType.FullName == "System.String" || unityType.FullName == "System.Void") && unityType.FullName != "System.RuntimeTypeHandle") + { + rwContext = null; return imports.Module.ImportCorlibReference(unityType.Namespace, unityType.Name); + } if (targetAssemblyName == "UnityEngine") foreach (var assemblyRewriteContext in context.Assemblies) { if (!assemblyRewriteContext.NewAssembly.Name.Name.StartsWith("UnityEngine")) continue; - var newTypeInAnyUnityAssembly = - assemblyRewriteContext.TryGetTypeByName(unityType.FullName)?.NewType; + rwContext = assemblyRewriteContext.TryGetTypeByName(unityType.FullName); + var newTypeInAnyUnityAssembly = rwContext?.NewType; if (newTypeInAnyUnityAssembly != null) return newTypeInAnyUnityAssembly; } var targetAssembly = context.TryGetAssemblyByName(targetAssemblyName); - var newType = targetAssembly?.TryGetTypeByName(unityType.FullName)?.NewType; + rwContext = targetAssembly?.TryGetTypeByName(unityType.FullName); + var newType = rwContext?.NewType; return newType; } diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index 39f07003..52df6ba7 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -3,6 +3,7 @@ using Il2CppInterop.Generator.Passes; using Mono.Cecil; using Mono.Cecil.Cil; +using static Il2CppInterop.Generator.Contexts.TypeRewriteContext; namespace Il2CppInterop.Generator.Utils; @@ -34,6 +35,7 @@ public enum ErrorType Unimplemented, Unresolved, FieldProxy, + NonBlittableStruct, } public static Result TranslateMethod(MethodDefinition original, MethodDefinition target, @@ -196,25 +198,32 @@ private Result InlineMethod(Instruction ins) private Result InlineType(Instruction ins) { var targetType = (TypeReference)ins.Operand; + TypeRewriteContext rwContext; if (targetType is GenericParameter genericParam) { - if (genericParam.Owner is TypeReference paramOwner) + if (genericParam.Owner is TypeReference paramTypeOwner) { - var newTypeOwner = - Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, paramOwner, _imports); - if (newTypeOwner == null) - return new(ErrorType.Unresolved, ins, $"Could not resolve owner type {paramOwner}"); - targetType = newTypeOwner.GenericParameters.Single(it => it.Name == targetType.Name); + targetType = paramTypeOwner.GenericParameters.Single(it => it.Name == genericParam.Name); + targetType = + Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, targetType, _imports, out rwContext); + if (targetType == null) + return new(ErrorType.Unresolved, ins, $"Could not resolve generic {genericParam.Name} in type owner {paramTypeOwner}"); } - else + else if (genericParam.Owner is MethodDefinition paramMethodOwner) { - targetType = _target.GenericParameters.Single(it => it.Name == targetType.Name); + targetType = paramMethodOwner.GenericParameters.Single(it => it.Name == genericParam.Name); + targetType = + Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, targetType, _imports, out rwContext); + if (targetType == null) + return new(ErrorType.Unresolved, ins, $"Could not resolve generic {genericParam.Name} in method owner {paramMethodOwner}"); } + else + return new(ErrorType.Unresolved, ins, $"Could not resolve generic {genericParam.Name}, unknown owner {genericParam.Owner.GetType().Name}"); } else { var oldTargetType = targetType; - targetType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, targetType, _imports); + targetType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, targetType, _imports, out rwContext); if (targetType == null) return new(ErrorType.Unresolved, ins, $"Could not resolve type {oldTargetType}"); } @@ -235,18 +244,24 @@ private Result InlineType(Instruction ins) return Result.OK; } - if (ins.OpCode == OpCodes.Newarr && !targetType.IsValueType) + if (ins.OpCode == OpCodes.Newarr) { - _targetBuilder.Emit(OpCodes.Conv_I8); + MethodReference il2cppArrayctor_size; + if (targetType.IsValueType) + { + if (rwContext != null && // primitives does not have a rewrite context but are blittable + rwContext.ComputedTypeSpecifics != TypeSpecifics.BlittableStruct) + return new(ErrorType.NonBlittableStruct, ins, $"Expected {nameof(TypeSpecifics.BlittableStruct)} but found {Enum.GetName(typeof(TypeSpecifics), rwContext.ComputedTypeSpecifics)}"); + il2cppArrayctor_size = _imports.Il2CppStructArrayctor_size.Get(targetType); + } + else if (targetType.FullName == "System.String") + il2cppArrayctor_size = _imports.Il2CppStringArrayctor_size.Value; + else + il2cppArrayctor_size = _imports.Il2CppRefrenceArrayctor_size.Get(targetType); - var il2cppTypeArray = new GenericInstanceType(_imports.Il2CppReferenceArray) - { GenericArguments = { targetType } }; + _targetBuilder.Emit(OpCodes.Conv_I8); _targetBuilder.Emit(OpCodes.Newobj, _imports.Module.ImportReference( - new MethodReference(".ctor", _imports.Module.Void(), il2cppTypeArray) - { - HasThis = true, - Parameters = { new ParameterDefinition(_imports.Module.Long()) } - })); + il2cppArrayctor_size)); return Result.OK; } From d8a9c9ab3c965f8b463cdf7d11f026b6c84ff2c3 Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Mon, 18 Sep 2023 20:15:57 +0200 Subject: [PATCH 07/18] Generator(unstrip): Translate Stelem/Ldelem/Ldlen Stelem -> Il2CppArrayBase::set_Item Ldelem -> Il2CppArrayBase::get_Item Ldlen -> Il2CppArrayBase::get_Length --- .../Utils/InstructionExtensions.cs | 154 ++++++++++++++++++ .../Utils/RuntimeAssemblyReferences.cs | 42 +++++ Il2CppInterop.Generator/Utils/StackWalker.cs | 139 ++++++++++++++++ .../Utils/UnstripTranslator.cs | 45 +++++ 4 files changed, 380 insertions(+) create mode 100644 Il2CppInterop.Generator/Utils/InstructionExtensions.cs create mode 100644 Il2CppInterop.Generator/Utils/StackWalker.cs diff --git a/Il2CppInterop.Generator/Utils/InstructionExtensions.cs b/Il2CppInterop.Generator/Utils/InstructionExtensions.cs new file mode 100644 index 00000000..9468c035 --- /dev/null +++ b/Il2CppInterop.Generator/Utils/InstructionExtensions.cs @@ -0,0 +1,154 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace Il2CppInterop.Generator.Utils; + +internal static class InstructionExtensions +{ + public static bool IsStelem(this Code opCode) + { + switch (opCode) + { + case Code.Stelem_I: + case Code.Stelem_I1: + case Code.Stelem_I2: + case Code.Stelem_I4: + case Code.Stelem_I8: + case Code.Stelem_R4: + case Code.Stelem_R8: + case Code.Stelem_Ref: + return true; + default: return false; + } + } + + public static bool IsLdelem(this Code opCode) + { + switch (opCode) + { + case Code.Ldelem_I: + case Code.Ldelem_I1: + case Code.Ldelem_I2: + case Code.Ldelem_I4: + case Code.Ldelem_I8: + case Code.Ldelem_R4: + case Code.Ldelem_R8: + case Code.Ldelem_Ref: + return true; + default: return false; + } + } + + public static bool IsLdind(this Code opCode) + { + switch (opCode) + { + case Code.Ldind_I: + case Code.Ldind_I1: + case Code.Ldind_I2: + case Code.Ldind_I4: + case Code.Ldind_I8: + case Code.Ldind_R4: + case Code.Ldind_R8: + case Code.Ldind_Ref: + case Code.Ldind_U1: + case Code.Ldind_U2: + case Code.Ldind_U4: + return true; + default: return false; + } + } + + public static bool BreaksFlow(this OpCode opCode) + { + if (opCode == OpCodes.Jmp) return true; + switch (opCode.FlowControl) + { + case FlowControl.Return: + case FlowControl.Branch: + case FlowControl.Cond_Branch: + return true; + default: return false; + } + } + + public static int PushAmount(this Instruction ins) + { + return ins.OpCode.StackBehaviourPush switch + { + StackBehaviour.Push0 => 0, + StackBehaviour.Varpush => ((MethodReference)ins.Operand) + .ReturnType.FullName == "System.Void" ? 0 : 1, + StackBehaviour.Push1_push1 => 2, + _ => 1, + }; + } + + public static int PopAmount(this Instruction ins) + { + return ins.OpCode.StackBehaviourPop switch + { + StackBehaviour.Pop0 => 0, + StackBehaviour.Pop1 => 1, + StackBehaviour.Popi => 1, + StackBehaviour.Popref => 1, + StackBehaviour.Pop1_pop1 => 2, + StackBehaviour.Popi_pop1 => 2, + StackBehaviour.Popi_popi => 2, + StackBehaviour.Popi_popi8 => 2, + StackBehaviour.Popi_popi_popi => 3, + StackBehaviour.Popi_popr4 => 2, + StackBehaviour.Popi_popr8 => 2, + StackBehaviour.Popref_pop1 => 2, + StackBehaviour.Popref_popi => 2, + StackBehaviour.Popref_popi_popi => 3, + StackBehaviour.Popref_popi_popi8 => 3, + StackBehaviour.Popref_popi_popr4 => 3, + StackBehaviour.Popref_popi_popr8 => 3, + StackBehaviour.Popref_popi_popref => 3, + StackBehaviour.Varpop => GetParameterCount(ins), + var pop => throw new NotSupportedException( + $"{Enum.GetName(typeof(StackBehaviour), pop)} is not a pop behaviour"), + }; + } + + public static int GetParameterCount(this Instruction ins) + { + if (ins.Operand is not MethodReference method) + throw new ArgumentException("Operand must be a method", nameof(ins)); + if (method.HasThis && ins.OpCode.Code != Code.Newobj) + return method.Parameters.Count + 1; + return method.Parameters.Count; + } + + public static bool TryGetLdlocIndex(this Instruction ins, out int index) + { + index = ins.OpCode.Code switch + { + Code.Ldloc_0 => 0, + Code.Ldloc_1 => 1, + Code.Ldloc_2 => 2, + Code.Ldloc_3 => 3, + Code.Ldloc or + Code.Ldloc_S => ((VariableReference)ins.Operand).Index, + _ => -1, + }; + return index >= 0; + } + + public static bool TryGetLdargIndex(this Instruction ins, bool hasThis, out int index) + { + var thisOffset = hasThis ? -1 : 0; + index = ins.OpCode.Code switch + { + Code.Ldarg_0 => thisOffset + 0, + Code.Ldarg_1 => thisOffset + 1, + Code.Ldarg_2 => thisOffset + 2, + Code.Ldarg_3 => thisOffset + 3, + Code.Ldarg or + Code.Ldarg_S => ((ParameterReference)ins.Operand).Index, + _ => -2, + }; + return index >= -1; + } +} diff --git a/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs b/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs index fc9ac340..3d274dd2 100644 --- a/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs +++ b/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs @@ -24,6 +24,9 @@ public RuntimeAssemblyReferences(ModuleDefinition module, RewriteGlobalContext g public ModuleDefinition Module { get; } + public Memoize Il2CppArrayBase_set_Item { get; private set; } + public Memoize Il2CppArrayBase_get_Item { get; private set; } + public Memoize Il2CppArrayBase_get_Length { get; private set; } public Memoize Il2CppRefrenceArrayctor { get; private set; } public Lazy Il2CppStringArrayctor { get; private set; } public Memoize Il2CppStructArrayctor { get; private set; } @@ -150,6 +153,7 @@ private void InitTypeRefs() allTypes["Il2CppInterop.Runtime.InteropTypes.Il2CppObjectBase"] = Il2CppObjectBase; allTypes["Il2CppInterop.Runtime.Runtime.Il2CppObjectPool"] = Il2CppObjectPool; allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStringArray"] = Il2CppStringArray; + allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppArrayBase"] = Il2CppArrayBase; allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppReferenceArray"] = Il2CppReferenceArray; allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray"] = Il2CppStructArray; allTypes["Il2CppInterop.Runtime.Il2CppException"] = Il2CppException; @@ -158,6 +162,44 @@ private void InitTypeRefs() private void InitMethodRefs() { + Il2CppArrayBase_set_Item = new((param) => + { + var owner = ResolveType("Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppArrayBase"); + var gp = owner.GenericParameters[0]; + var giOwner = new GenericInstanceType(owner); + giOwner.GenericArguments.Add(param); + var mr = new MethodReference("set_Item", ResolveType("System.Void"), + giOwner) + { HasThis = true }; + mr.Parameters.Add(new ParameterDefinition("", ParameterAttributes.None, ResolveType("System.Int32"))); + mr.Parameters.Add(new ParameterDefinition("", ParameterAttributes.None, gp)); + return mr; + }); + + Il2CppArrayBase_get_Item = new((param) => + { + var owner = ResolveType("Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppArrayBase"); + var gp = owner.GenericParameters[0]; + var giOwner = new GenericInstanceType(owner); + giOwner.GenericArguments.Add(param); + var mr = new MethodReference("get_Item", gp, + giOwner) + { HasThis = true }; + mr.Parameters.Add(new ParameterDefinition("", ParameterAttributes.None, ResolveType("System.Int32"))); + return mr; + }); + + Il2CppArrayBase_get_Length = new((param) => + { + var owner = ResolveType("Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppArrayBase"); + var giOwner = new GenericInstanceType(owner); + giOwner.GenericArguments.Add(param); + var mr = new MethodReference("get_Length", ResolveType("System.Int32"), + giOwner) + { HasThis = true }; + return mr; + }); + Il2CppRefrenceArrayctor = new((param) => { var owner = ResolveType("Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppReferenceArray"); diff --git a/Il2CppInterop.Generator/Utils/StackWalker.cs b/Il2CppInterop.Generator/Utils/StackWalker.cs new file mode 100644 index 00000000..40cbc120 --- /dev/null +++ b/Il2CppInterop.Generator/Utils/StackWalker.cs @@ -0,0 +1,139 @@ +using Il2CppInterop.Common; +using Microsoft.Extensions.Logging; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace Il2CppInterop.Generator.Utils; + +internal static class StackWalker +{ + public static bool TryWalkStack(RuntimeAssemblyReferences imports, + MethodDefinition target, int stackTarget, + out TypeReference type) + { + return TryWalkStack(imports, target, stackTarget, out type, out var _); + } + + public static bool TryWalkStack(RuntimeAssemblyReferences imports, + MethodDefinition target, int stackTarget, + out TypeReference type, out Instruction source) + { + if (TryWalkStack(imports, target, + target.Body.Instructions.Count - 1, + new[] { stackTarget }, out var results)) + { + type = results[0].type; + source = results[0].source; + return true; + } + type = null; + source = null; + return false; + } + + public static bool TryWalkStack(RuntimeAssemblyReferences imports, + MethodDefinition target, IEnumerable stackTargets, + out (TypeReference type, Instruction source, int index)[] results) + { + return TryWalkStack(imports, target, + target.Body.Instructions.Count - 1, + stackTargets, out results); + } + + /// + /// Walks the instructions of in reverse order + /// starting from .
+ /// The state of the stack is recreated and the s + /// at the positions described by is returned + /// along with the instructions responsible. + ///
+ /// + /// Method to walk + /// Instruction index of where to start walking + /// + /// Stack positions to resolve
+ /// Note that stackTarget 0 is the last argument to a method, not the first (nor ) + /// + /// Resolved s and their source instructions + /// if all where found, otherwise + /// + public static bool TryWalkStack(RuntimeAssemblyReferences imports, + MethodDefinition target, int startInstruction, + IEnumerable stackTargets, out (TypeReference type, Instruction source, int index)[] results) + { + var _stackTargets = new List(stackTargets); + _stackTargets.Sort(); + + results = new (TypeReference type, Instruction source, int index)[_stackTargets.Count]; + for (var i = 0; i < _stackTargets.Count; i++) + results[i].index = _stackTargets[i]; + + var stackPos = 0; + var stackTargetIdx = 0; + var stackTarget = _stackTargets[0]; + + for (var i = startInstruction; i >= 0; i--) + { + var ins = target.Body.Instructions[i]; + if (ins.OpCode.BreaksFlow()) + { + // TODO follow branches + // Without branch logic there's a possibility that we walk the stack incorrectly + // causing the found TypeReference to be bogus + return false; + } + + var nPush = ins.PushAmount() ; + if (stackPos == stackTarget && nPush > 0) + { + results[stackTargetIdx].source ??= ins; + var code = ins.OpCode.Code; + if (code == Code.Dup || + code.IsLdind()) + { + stackPos = 0; + for (var j = stackTargetIdx; j < _stackTargets.Count; j++) + _stackTargets[j] -= stackTarget; + stackTarget = 0; + continue; + } + else if (code == Code.Call || + code == Code.Callvirt) + results[stackTargetIdx].type = ((MethodReference)ins.Operand).ReturnType; + else if (code == Code.Newobj) + results[stackTargetIdx].type = ((MethodReference)ins.Operand).DeclaringType; + else if (code == Code.Ldfld || + code == Code.Ldsfld) + results[stackTargetIdx].type = ((FieldReference)ins.Operand).FieldType; + else if (ins.TryGetLdlocIndex(out var varArgIdx)) + { + var varArg = target.Body.Variables[varArgIdx]; + results[stackTargetIdx].type = varArg.VariableType; + } + else if (ins.TryGetLdargIndex(target.HasThis, out var paramArgIdx)) + { + var paramArg = paramArgIdx switch + { + -1 => target.Body.ThisParameter, + _ => target.Parameters[paramArgIdx], + }; + results[stackTargetIdx].type = paramArg.ParameterType; + } + else if (code == Code.Ldstr) + results[stackTargetIdx].type = imports.Module.String(); + else + return false; + + if (++stackTargetIdx < _stackTargets.Count) + stackTarget = _stackTargets[stackTargetIdx]; + else + return true; + } + + var nPop = ins.PopAmount(); + stackPos += nPush - nPop; + } + return false; + } +} + diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index 52df6ba7..194fa0de 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -36,6 +36,7 @@ public enum ErrorType Unresolved, FieldProxy, NonBlittableStruct, + Stack, } public static Result TranslateMethod(MethodDefinition original, MethodDefinition target, @@ -95,6 +96,7 @@ private Result Translate(Instruction ins) OperandType.InlineType => InlineType(ins), OperandType.InlineSig => InlineSig(ins), OperandType.InlineTok => InlineTok(ins), + OperandType.InlineNone => InlineNone(ins), _ => Copy(ins), }; } @@ -323,6 +325,49 @@ private Result InlineTok(Instruction ins) return Result.OK; } + private Result InlineNone(Instruction ins) + { + var code = ins.OpCode.Code; + var stackTarget = code switch + { + _ when code.IsStelem() => 2, + _ when code.IsLdelem() => 1, + Code.Ldlen => 0, + _ => -1, + }; + if (stackTarget >= 0) + { + if (!StackWalker.TryWalkStack(_imports, _target, stackTarget, out var il2cppArray)) + { + // TODO follow branches in StackWalker + // We're probably here because of our naive stack walking + return new(ErrorType.Stack, ins, $"Unable to find target of {ins.OpCode.Name}"); + } + + TypeReference genericArg; + if (il2cppArray == _imports.Il2CppStringArray) + genericArg = _imports.Module.String(); + else if (il2cppArray is GenericInstanceType il2cppArrayInstance && ( + il2cppArrayInstance.ElementType == _imports.Il2CppReferenceArray || + il2cppArrayInstance.ElementType == _imports.Il2CppStructArray)) + genericArg = il2cppArrayInstance.GenericArguments[0]; + else + return new(ErrorType.Stack, ins, $"Unexpected target of {ins.OpCode.Name} is {il2cppArray}"); + + var mRef = stackTarget switch + { + 2 => _imports.Il2CppArrayBase_set_Item.Get(genericArg), + 1 => _imports.Il2CppArrayBase_get_Item.Get(genericArg), + _ => _imports.Il2CppArrayBase_get_Length.Get(genericArg), + }; + _targetBuilder.Emit(OpCodes.Callvirt, _imports.Module.ImportReference(mRef)); + return Result.OK; + } + + _targetBuilder.Append(ins); + return Result.OK; + } + private Result Copy(Instruction ins) { _targetBuilder.Append(ins); From 85c2c81bdaf49f6dd4702fd9f431f194d029b497 Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Tue, 19 Sep 2023 20:13:31 +0200 Subject: [PATCH 08/18] Generator(unstrip): Retarget branches while translating --- .../Utils/InstructionExtensions.cs | 31 +++++ .../Utils/RetargetingILProcessor.cs | 110 ++++++++++++++++++ .../Utils/UnstripTranslator.cs | 22 +++- 3 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 Il2CppInterop.Generator/Utils/RetargetingILProcessor.cs diff --git a/Il2CppInterop.Generator/Utils/InstructionExtensions.cs b/Il2CppInterop.Generator/Utils/InstructionExtensions.cs index 9468c035..7612c231 100644 --- a/Il2CppInterop.Generator/Utils/InstructionExtensions.cs +++ b/Il2CppInterop.Generator/Utils/InstructionExtensions.cs @@ -151,4 +151,35 @@ Code.Ldarg or }; return index >= -1; } + + public static OpCode GetLong(this OpCode opCode) + { + return opCode.OperandType switch + { + OperandType.ShortInlineArg => throw new NotImplementedException(opCode.OperandType.ToString()), + OperandType.ShortInlineBrTarget => opCode.Code switch + { + Code.Br_S => OpCodes.Br, + Code.Brfalse_S => OpCodes.Brfalse, + Code.Brtrue_S => OpCodes.Brtrue, + Code.Beq_S => OpCodes.Beq, + Code.Bge_S => OpCodes.Bge, + Code.Bgt_S => OpCodes.Bgt, + Code.Ble_S => OpCodes.Ble, + Code.Blt_S => OpCodes.Blt, + Code.Bne_Un_S => OpCodes.Bne_Un, + Code.Bge_Un_S => OpCodes.Bge_Un, + Code.Bgt_Un_S => OpCodes.Bgt_Un, + Code.Ble_Un_S => OpCodes.Ble_Un, + Code.Blt_Un_S => OpCodes.Blt_Un, + Code.Leave_S => OpCodes.Leave, + _ => throw new NotImplementedException($"{opCode.OperandType} {opCode.Code}"), + }, + OperandType.ShortInlineI => throw new NotImplementedException(opCode.OperandType.ToString()), + OperandType.ShortInlineR => throw new NotImplementedException(opCode.OperandType.ToString()), + OperandType.ShortInlineVar => throw new NotImplementedException(opCode.OperandType.ToString()), + _ => throw new NotSupportedException($"{opCode.OperandType} is not a short version OpCode"), + }; + } + } diff --git a/Il2CppInterop.Generator/Utils/RetargetingILProcessor.cs b/Il2CppInterop.Generator/Utils/RetargetingILProcessor.cs new file mode 100644 index 00000000..14636004 --- /dev/null +++ b/Il2CppInterop.Generator/Utils/RetargetingILProcessor.cs @@ -0,0 +1,110 @@ +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace Il2CppInterop.Generator.Utils; + +internal class RetargetingILProcessor +{ + private readonly MethodDefinition _target; + private readonly Builder _builder; + + // + private readonly Dictionary _replacementBranches = new(); + // + private readonly Dictionary> _originalBranches = new(); + public bool NeedsRetargeting => _originalBranches.Count > 0; + public IReadOnlyDictionary> IncompleteBranches => _originalBranches; + + private Instruction _originalInstruction; + private int _trackedIdx = -1; + + public RetargetingILProcessor(MethodDefinition target) + { + _target = target; + _builder = new(this); + } + + public Builder Track(Instruction originalInstruction) + { + if (_originalInstruction != null) + throw new InvalidOperationException("track called before builder disposed"); + _originalInstruction = originalInstruction; + _trackedIdx = _target.Body.Instructions.Count; + return _builder; + } + + private void TrackBranch(Instruction instruction) + { + var operandType = instruction.OpCode.OperandType; + if (operandType != OperandType.InlineBrTarget && + operandType != OperandType.ShortInlineBrTarget) + return; + + var dst = (Instruction)instruction.Operand; + if (operandType == OperandType.ShortInlineBrTarget) + instruction.OpCode = instruction.OpCode.GetLong(); + + if (_replacementBranches.TryGetValue(dst, out var newDst)) + instruction.Operand = newDst; + else + { + if (!_originalBranches.TryGetValue(dst, out var oldBranches)) + _originalBranches.Add(dst, oldBranches = new()); + oldBranches.Add(instruction); + } + } + + private void RetargetBranches() + { + if (_originalInstruction == null) + throw new InvalidOperationException("builder disposed without calling track"); + + if (_trackedIdx < _target.Body.Instructions.Count) + { + var newDst = _target.Body.Instructions[_trackedIdx]; + _replacementBranches.Add(_originalInstruction, newDst); + if (_originalBranches.TryGetValue(_originalInstruction, out var oldBranches)) + { + foreach (var oldBranch in oldBranches) + oldBranch.Operand = newDst; + _originalBranches.Remove(_originalInstruction); + } + } + + _originalInstruction = null; + _trackedIdx = -1; + } + + public class Builder : IDisposable + { + private readonly RetargetingILProcessor _processor; + private readonly ILProcessor _targetBuilder; + + public Builder(RetargetingILProcessor processor) + { + _processor = processor; + _targetBuilder = processor._target.Body.GetILProcessor(); + } + + public void Dispose() => _processor.RetargetBranches(); + + public void Append(Instruction instruction) + { + _processor.TrackBranch(instruction); + _targetBuilder.Append(instruction); + } + + // Add whatever equivalent _targetBuilder.Emit you need below + + public void Emit(OpCode opCode) => + Append(_targetBuilder.Create(opCode)); + public void Emit(OpCode opCode, FieldReference field) => + Append(_targetBuilder.Create(opCode, field)); + public void Emit(OpCode opCode, MethodReference method) => + Append(_targetBuilder.Create(opCode, method)); + public void Emit(OpCode opCode, TypeReference type) => + Append(_targetBuilder.Create(opCode, type)); + public void Emit(OpCode opCode, VariableDefinition variable) => + Append(_targetBuilder.Create(opCode, variable)); + } +} diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index 194fa0de..10b3579b 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -37,6 +37,7 @@ public enum ErrorType FieldProxy, NonBlittableStruct, Stack, + Retargeting, } public static Result TranslateMethod(MethodDefinition original, MethodDefinition target, @@ -50,7 +51,8 @@ public enum ErrorType private readonly RuntimeAssemblyReferences _imports; private readonly RewriteGlobalContext _globalContext; - private readonly ILProcessor _targetBuilder; + private readonly RetargetingILProcessor _retargeter; + private RetargetingILProcessor.Builder _targetBuilder; private UnstripTranslator(MethodDefinition original, MethodDefinition target, TypeRewriteContext typeRewriteContext, RuntimeAssemblyReferences imports) @@ -60,7 +62,7 @@ public enum ErrorType _imports = imports; _globalContext = typeRewriteContext.AssemblyContext.GlobalContext; - _targetBuilder = target.Body.GetILProcessor(); + _retargeter = new RetargetingILProcessor(target); } private Result Translate() @@ -78,10 +80,20 @@ private Result Translate() } foreach (var bodyInstruction in _original.Body.Instructions) + using (_targetBuilder = _retargeter.Track(bodyInstruction)) + { + var result = Translate(bodyInstruction); + if (result.IsError) + return result; + } + + if (_retargeter.NeedsRetargeting) { - var result = Translate(bodyInstruction); - if (result.IsError) - return result; + // This is most likely due to not translating some instructions, + // i.e. the original instruction results in zero instructions in the new method, + // which doesn't really make sense, so something is probably broken + var branches = string.Join("\n\t", _retargeter.IncompleteBranches.Values.SelectMany(x => x)); + return new(ErrorType.Retargeting, null, $"Incomplete branch retargeting:\n\t{branches}"); } return Result.OK; From 6d157e22341b64521f8bd3d0bc9dbc0d1f30da5a Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Tue, 19 Sep 2023 20:21:39 +0200 Subject: [PATCH 09/18] Generator(unstrip): Optimize new method body Added due to the use of long branches in retargeter --- Il2CppInterop.Generator/Utils/UnstripTranslator.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index 10b3579b..59ae1b89 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -3,6 +3,7 @@ using Il2CppInterop.Generator.Passes; using Mono.Cecil; using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; using static Il2CppInterop.Generator.Contexts.TypeRewriteContext; namespace Il2CppInterop.Generator.Utils; @@ -96,6 +97,9 @@ private Result Translate() return new(ErrorType.Retargeting, null, $"Incomplete branch retargeting:\n\t{branches}"); } + // Retargeter uses long branches everywhere for convenience + // but it is safe to optimize them when everything is translated + _target.Body.Optimize(); return Result.OK; } From 061660abd353bbb73c4ef982694ac2777264b389 Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Tue, 19 Sep 2023 20:28:28 +0200 Subject: [PATCH 10/18] Generator(unstrip): Don't translate virtual enum calls --- Il2CppInterop.Generator/Utils/UnstripTranslator.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index 59ae1b89..4152fb0f 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -178,6 +178,19 @@ private Result InlineField(Instruction ins) private Result InlineMethod(Instruction ins) { var methodArg = (MethodReference)ins.Operand; + if (ins.OpCode == OpCodes.Callvirt && + ins.Previous?.OpCode == OpCodes.Constrained && + ins.Previous.Operand is TypeReference constrainedType && + constrainedType.Resolve().IsEnum) + { + // Keep virtual enum calls as they are, they can't (and don't need to) be translated into il2cpp + // E.g: + // constrained. Some.EnumType + // callvirt instance string System.Object::ToString() + _targetBuilder.Emit(ins.OpCode, _imports.Module.ImportReference(methodArg)); + return Result.OK; + } + var methodDeclarer = Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, methodArg.DeclaringType, _imports); if (methodDeclarer == null) From 0be20b35166a3af6d1e2a1db803b7101ae96d659 Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Tue, 19 Sep 2023 21:00:58 +0200 Subject: [PATCH 11/18] Generator(unstrip): Add retargeting support for InsertAfter --- .../Utils/RetargetingILProcessor.cs | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/Il2CppInterop.Generator/Utils/RetargetingILProcessor.cs b/Il2CppInterop.Generator/Utils/RetargetingILProcessor.cs index 14636004..f3acb6bc 100644 --- a/Il2CppInterop.Generator/Utils/RetargetingILProcessor.cs +++ b/Il2CppInterop.Generator/Utils/RetargetingILProcessor.cs @@ -94,17 +94,42 @@ public void Append(Instruction instruction) _targetBuilder.Append(instruction); } - // Add whatever equivalent _targetBuilder.Emit you need below + public void InsertAfter(Instruction target, Instruction instruction) + { + var index = _targetBuilder.Body.Instructions.IndexOf (target); + _targetBuilder.InsertAfter(index, instruction); + } + + public void InsertAfter(int index, Instruction instruction) + { + _processor.TrackBranch(instruction); + _targetBuilder.InsertAfter(index, instruction); + if (index < _processor._trackedIdx) + _processor._trackedIdx++; + } + + // Add whatever equivalent _targetBuilder.Emit/Create you need below public void Emit(OpCode opCode) => - Append(_targetBuilder.Create(opCode)); + Append(Create(opCode)); public void Emit(OpCode opCode, FieldReference field) => - Append(_targetBuilder.Create(opCode, field)); + Append(Create(opCode, field)); public void Emit(OpCode opCode, MethodReference method) => - Append(_targetBuilder.Create(opCode, method)); + Append(Create(opCode, method)); public void Emit(OpCode opCode, TypeReference type) => - Append(_targetBuilder.Create(opCode, type)); + Append(Create(opCode, type)); public void Emit(OpCode opCode, VariableDefinition variable) => - Append(_targetBuilder.Create(opCode, variable)); + Append(Create(opCode, variable)); + + public Instruction Create(OpCode opCode) => + _targetBuilder.Create(opCode); + public Instruction Create(OpCode opCode, FieldReference field) => + _targetBuilder.Create(opCode, field); + public Instruction Create(OpCode opCode, MethodReference method) => + _targetBuilder.Create(opCode, method); + public Instruction Create(OpCode opCode, TypeReference type) => + _targetBuilder.Create(opCode, type); + public Instruction Create(OpCode opCode, VariableDefinition variable) => + _targetBuilder.Create(opCode, variable); } } From 4f9af8d72db12c938605676e569fd4eb28dd852a Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Tue, 19 Sep 2023 21:02:14 +0200 Subject: [PATCH 12/18] Generator(unstrip): Add implicit conversions for Il2CppSystem.Object parameters --- .../Utils/UnstripTranslator.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index 4152fb0f..27d1d900 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -206,6 +206,7 @@ private Result InlineMethod(Instruction ins) var newMethod = new MethodReference(methodArg.Name, newReturnType, methodDeclarer); newMethod.HasThis = methodArg.HasThis; + List objectParamIndices = null; foreach (var methodArgParameter in methodArg.Parameters) { var newParamType = methodArgParameter.ParameterType switch @@ -217,11 +218,31 @@ private Result InlineMethod(Instruction ins) if (newParamType == null) return new(ErrorType.Unresolved, ins, $"Could not resolve parameter #{methodArgParameter.Index} {methodArgParameter.ParameterType} {methodArgParameter.Name}"); + if (newParamType.FullName == _imports.Il2CppObject.Value.FullName) + (objectParamIndices ??= new()) + .Add(methodArgParameter); + var newParam = new ParameterDefinition(methodArgParameter.Name, methodArgParameter.Attributes, newParamType); newMethod.Parameters.Add(newParam); } + if (objectParamIndices != null) + { + var stackIndices = objectParamIndices.Select(x => methodArg.Parameters.Count - 1 - x.Index); + if (!StackWalker.TryWalkStack(_imports, _target, stackIndices, out var results)) + return new(ErrorType.Stack, ins, $"Unable to find targets of {objectParamIndices.Count} parameters #" + + string.Join(", #", objectParamIndices.Select(x => x.Index)) + + $" in {ins.OpCode.Name} instruction"); + + foreach (var (targetType, targetInstruction, _) in results) + if (targetType.IsPrimitive || targetType.FullName == "System.String") + { + var box = _targetBuilder.Create(OpCodes.Call, _imports.Il2CppObject_op_Implicit.Get(targetType)); + _targetBuilder.InsertAfter(targetInstruction, box); + } + } + _targetBuilder.Emit(ins.OpCode, _imports.Module.ImportReference(newMethod)); return Result.OK; } From 694b06e3b27930a1c52cbaeb6ad48729a51f4ad7 Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Tue, 19 Sep 2023 21:04:47 +0200 Subject: [PATCH 13/18] Generator(unstrip): Add some unbox support --- .../Utils/RuntimeAssemblyReferences.cs | 6 +++++ .../Utils/UnstripTranslator.cs | 26 ++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs b/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs index 3d274dd2..11fa8978 100644 --- a/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs +++ b/Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs @@ -76,6 +76,7 @@ public RuntimeAssemblyReferences(ModuleDefinition module, RewriteGlobalContext g public Lazy Il2CppSystemRuntimeTypeHandleGetRuntimeTypeHandle { get; private set; } public Lazy Il2CppObject { get; private set; } public Memoize Il2CppObject_op_Implicit { get; private set; } + public Lazy Il2CppString_op_Implicit { get; private set; } public MethodReference WriteFieldWBarrier => globalCtx.HasGcWbarrierFieldWrite ? IL2CPP_il2cpp_gc_wbarrier_set_field.Value @@ -670,5 +671,10 @@ private void InitMethodRefs() Il2CppObject_op_Implicit = new((param) => Module.ImportReference(Il2CppObject.Value.Methods.Single(m => m.Name == "op_Implicit" && m.Parameters[0].ParameterType.FullName == param.FullName))); + + Il2CppString_op_Implicit = new(() => + Module.ImportReference(globalCtx.GetAssemblyByName("mscorlib").NewAssembly.MainModule + .GetType("Il2CppSystem.String").Methods.Single(m => m.Name == "op_Implicit" && + m.ReturnType.FullName == "System.String"))); } } diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index 27d1d900..c93e992c 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -280,11 +280,16 @@ private Result InlineType(Instruction ins) return new(ErrorType.Unresolved, ins, $"Could not resolve type {oldTargetType}"); } - if (ins.OpCode == OpCodes.Castclass && !targetType.IsValueType) + if ((ins.OpCode == OpCodes.Castclass || ins.OpCode == OpCodes.Unbox_Any) && + !targetType.IsValueType) { - _targetBuilder.Emit(OpCodes.Call, - _imports.Module.ImportReference(new GenericInstanceMethod(_imports.Il2CppObjectBase_Cast.Value) - { GenericArguments = { targetType } })); + // unbox.any T where T is a reference is equivalent to castclass + if (targetType.FullName == "System.String") + _targetBuilder.Emit(OpCodes.Call, _imports.Il2CppString_op_Implicit.Value); + else + _targetBuilder.Emit(OpCodes.Call, + _imports.Module.ImportReference(new GenericInstanceMethod(_imports.Il2CppObjectBase_Cast.Value) + { GenericArguments = { targetType } })); return Result.OK; } @@ -329,6 +334,19 @@ private Result InlineType(Instruction ins) return Result.Unimplemented(ins); } + if (ins.OpCode == OpCodes.Unbox || ins.OpCode == OpCodes.Unbox_Any) + { + if (targetType.FullName == "System.String") + { + _targetBuilder.Emit(OpCodes.Call, _imports.Il2CppString_op_Implicit.Value); + return Result.OK; + } + + // TODO implement unboxing + // The type on the stack is not System.Object, it is Il2CppSystem.Object + return Result.Unimplemented(ins); + } + _targetBuilder.Emit(ins.OpCode, targetType); return Result.OK; } From a7a60024cd7e48180051ddb67d7cec8bf0ad69c3 Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Tue, 19 Sep 2023 21:45:24 +0200 Subject: [PATCH 14/18] Generator(unstrip): Fail on Ldelema/Ldelem_any --- Il2CppInterop.Generator/Utils/UnstripTranslator.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index c93e992c..c5359d17 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -347,6 +347,15 @@ private Result InlineType(Instruction ins) return Result.Unimplemented(ins); } + if (ins.OpCode == OpCodes.Ldelema || + ins.OpCode == OpCodes.Ldelem_Any) + { + // TODO implement Ldelema with Stloc & Ldloca + // TODO implement Ldelem_Any + // Can probably share code with Stelem/Ldelem/Ldlen + return Result.Unimplemented(ins); + } + _targetBuilder.Emit(ins.OpCode, targetType); return Result.OK; } From e5f6f7089509fee70d3393da308c628e956943e4 Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Tue, 19 Sep 2023 21:51:34 +0200 Subject: [PATCH 15/18] Generator(unstrip): Translate valuetype methods E.g. string::Join becomes Il2CppSystem.String::Join This is correct since we translate string[] to Il2CppStringArray, etc --- .../Passes/Pass80UnstripMethods.cs | 29 ++++++++++--------- .../Utils/UnstripTranslator.cs | 2 +- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs b/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs index e6869f2f..042bec2e 100644 --- a/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs +++ b/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs @@ -143,26 +143,26 @@ private static PropertyDefinition GetOrCreateProperty(MethodDefinition unityMeth } internal static TypeReference? ResolveTypeInNewAssemblies(RewriteGlobalContext context, TypeReference unityType, - RuntimeAssemblyReferences imports) + RuntimeAssemblyReferences imports, bool resolveValueTypes = false) { - return ResolveTypeInNewAssemblies(context, unityType, imports, out var _); + return ResolveTypeInNewAssemblies(context, unityType, imports, out var _, resolveValueTypes); } internal static TypeReference? ResolveTypeInNewAssemblies(RewriteGlobalContext context, TypeReference unityType, - RuntimeAssemblyReferences imports, out TypeRewriteContext rwContext) + RuntimeAssemblyReferences imports, out TypeRewriteContext rwContext, bool resolveValueTypes = false) { - var resolved = ResolveTypeInNewAssembliesRaw(context, unityType, imports, out rwContext); + var resolved = ResolveTypeInNewAssembliesRaw(context, unityType, imports, out rwContext, resolveValueTypes); return resolved != null ? imports.Module.ImportReference(resolved) : null; } internal static TypeReference? ResolveTypeInNewAssembliesRaw(RewriteGlobalContext context, TypeReference unityType, - RuntimeAssemblyReferences imports) + RuntimeAssemblyReferences imports, bool resolveValueTypes = false) { - return ResolveTypeInNewAssembliesRaw(context, unityType, imports, out var _); + return ResolveTypeInNewAssembliesRaw(context, unityType, imports, out var _, resolveValueTypes); } internal static TypeReference? ResolveTypeInNewAssembliesRaw(RewriteGlobalContext context, TypeReference unityType, - RuntimeAssemblyReferences imports, out TypeRewriteContext rwContext) + RuntimeAssemblyReferences imports, out TypeRewriteContext rwContext, bool resolveValueTypes = false) { if (unityType is ByReferenceType) { @@ -179,7 +179,7 @@ private static PropertyDefinition GetOrCreateProperty(MethodDefinition unityMeth if (unityType is ArrayType arrayType) { if (arrayType.Rank != 1) { rwContext = null; return null; } - var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports, out rwContext); + var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports, out rwContext, resolveValueTypes); if (resolvedElementType == null) { rwContext = null; return null; } if (resolvedElementType.FullName == "System.String") return imports.Il2CppStringArray; @@ -191,7 +191,7 @@ private static PropertyDefinition GetOrCreateProperty(MethodDefinition unityMeth if (unityType.DeclaringType != null) { - var enclosingResolvedType = ResolveTypeInNewAssembliesRaw(context, unityType.DeclaringType, imports, out rwContext); + var enclosingResolvedType = ResolveTypeInNewAssembliesRaw(context, unityType.DeclaringType, imports, out rwContext, resolveValueTypes); if (enclosingResolvedType == null) return null; var resolvedNestedType = enclosingResolvedType.Resolve().NestedTypes .FirstOrDefault(it => it.Name == unityType.Name); @@ -201,13 +201,13 @@ private static PropertyDefinition GetOrCreateProperty(MethodDefinition unityMeth if (unityType is PointerType) { - var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports, out rwContext); + var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports, out rwContext, resolveValueTypes); return resolvedElementType == null ? null : new PointerType(resolvedElementType); } if (unityType is GenericInstanceType genericInstance) { - var baseRef = ResolveTypeInNewAssembliesRaw(context, genericInstance.ElementType, imports, out rwContext); + var baseRef = ResolveTypeInNewAssembliesRaw(context, genericInstance.ElementType, imports, out rwContext, resolveValueTypes); if (baseRef == null) return null; var newInstance = new GenericInstanceType(baseRef); foreach (var unityGenericArgument in genericInstance.GenericArguments) @@ -223,9 +223,10 @@ private static PropertyDefinition GetOrCreateProperty(MethodDefinition unityMeth var targetAssemblyName = unityType.Scope.Name; if (targetAssemblyName.EndsWith(".dll")) targetAssemblyName = targetAssemblyName.Substring(0, targetAssemblyName.Length - 4); - if ((targetAssemblyName == "mscorlib" || targetAssemblyName == "netstandard") && - (unityType.IsValueType || unityType.FullName == "System.String" || - unityType.FullName == "System.Void") && unityType.FullName != "System.RuntimeTypeHandle") + if ((targetAssemblyName == "mscorlib" || targetAssemblyName == "netstandard") && ( + (!resolveValueTypes && (unityType.IsValueType || unityType.FullName == "System.String")) || + unityType.FullName == "System.Void" || unityType.IsPrimitive + ) && unityType.FullName != "System.RuntimeTypeHandle") { rwContext = null; return imports.Module.ImportCorlibReference(unityType.Namespace, unityType.Name); diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index c5359d17..3b96a746 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -192,7 +192,7 @@ private Result InlineMethod(Instruction ins) } var methodDeclarer = - Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, methodArg.DeclaringType, _imports); + Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, methodArg.DeclaringType, _imports, resolveValueTypes: true); if (methodDeclarer == null) return new(ErrorType.Unresolved, ins, $"Could not resolve declaring type {methodArg.DeclaringType}"); From 4c1e022d410fd32015a019d8cead2f5abd029e99 Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Tue, 19 Sep 2023 23:13:38 +0200 Subject: [PATCH 16/18] misc: Fix incorrect usage of ByReferenceType ByReferenceType::ElementData is what you want when IsByReference is true. Example: ref int[] is represented as ByReferenceType(ArrayType(typeof(int)) Using TypeReference::GetElementType() on the outer ByReferenceType returns typeof(int), not ArrayType(typeof(int)). ElementData always points to what is ByReference. --- Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs | 10 ++++++---- .../Passes/Pass20GenerateStaticConstructors.cs | 2 +- Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs | 4 ++-- Il2CppInterop.Generator/Utils/UnstripGenerator.cs | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs b/Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs index dd2cc1ae..bfd95646 100644 --- a/Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs +++ b/Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs @@ -172,14 +172,16 @@ public static void EmitLdcI4(this ILProcessor body, int constant) } var imports = enclosingType.AssemblyContext.Imports; - if (originalType is ByReferenceType) + if (originalType is ByReferenceType originalRefType) { - if (newType.GetElementType().IsValueType) + if (newType is not ByReferenceType newRefType) + throw new ArgumentException($"{nameof(newType)} must be {nameof(ByReferenceType)} if {nameof(originalType)} is", nameof(newType)); + if (newRefType.ElementType.IsValueType) { body.Emit(OpCodes.Ldarg, argumentIndex); body.Emit(OpCodes.Conv_I); } - else if (originalType.GetElementType().IsValueType) + else if (originalRefType.ElementType.IsValueType) { body.Emit(OpCodes.Ldarg, argumentIndex); body.Emit(OpCodes.Ldind_Ref); @@ -192,7 +194,7 @@ public static void EmitLdcI4(this ILProcessor body, int constant) body.Body.Variables.Add(pointerVar); body.Emit(OpCodes.Ldarg, argumentIndex); body.Emit(OpCodes.Ldind_Ref); - if (originalType.GetElementType().FullName == "System.String") + if (originalRefType.ElementType.FullName == "System.String") body.Emit(OpCodes.Call, imports.IL2CPP_ManagedStringToIl2Cpp.Value); else body.Emit(OpCodes.Call, imports.IL2CPP_Il2CppObjectBaseToPtr.Value); diff --git a/Il2CppInterop.Generator/Passes/Pass20GenerateStaticConstructors.cs b/Il2CppInterop.Generator/Passes/Pass20GenerateStaticConstructors.cs index 1a81795b..fccadf6a 100644 --- a/Il2CppInterop.Generator/Passes/Pass20GenerateStaticConstructors.cs +++ b/Il2CppInterop.Generator/Passes/Pass20GenerateStaticConstructors.cs @@ -191,7 +191,7 @@ private static void GenerateStaticProxy(AssemblyRewriteContext assemblyContext, imports.Module.ImportReference(new GenericInstanceMethod(imports.IL2CPP_RenderTypeName.Value) { GenericArguments = - {newTypeReference.IsByReference ? newTypeReference.GetElementType() : newTypeReference} + {newTypeReference is ByReferenceType newTypeRefType ? newTypeRefType.ElementType : newTypeReference} })); } } diff --git a/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs b/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs index 042bec2e..d02f3c57 100644 --- a/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs +++ b/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs @@ -164,9 +164,9 @@ private static PropertyDefinition GetOrCreateProperty(MethodDefinition unityMeth internal static TypeReference? ResolveTypeInNewAssembliesRaw(RewriteGlobalContext context, TypeReference unityType, RuntimeAssemblyReferences imports, out TypeRewriteContext rwContext, bool resolveValueTypes = false) { - if (unityType is ByReferenceType) + if (unityType is ByReferenceType unityRefType) { - var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports, out rwContext); + var resolvedElementType = ResolveTypeInNewAssemblies(context, unityRefType.ElementType, imports, out rwContext); return resolvedElementType == null ? null : new ByReferenceType(resolvedElementType); } diff --git a/Il2CppInterop.Generator/Utils/UnstripGenerator.cs b/Il2CppInterop.Generator/Utils/UnstripGenerator.cs index dabd4801..c3a95fde 100644 --- a/Il2CppInterop.Generator/Utils/UnstripGenerator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripGenerator.cs @@ -65,7 +65,7 @@ public static class UnstripGenerator { var param = newMethod.Parameters[i]; var paramType = param.ParameterType; - if (paramType.IsValueType || (paramType.IsByReference && paramType.GetElementType().IsValueType)) + if (paramType.IsValueType || (paramType is ByReferenceType paramRefType && paramRefType.ElementType.IsValueType)) { body.Emit(OpCodes.Ldarg, i + argOffset); } From 46494b307d9bd44fef7baf14af0a04612b018821 Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Tue, 19 Sep 2023 23:18:41 +0200 Subject: [PATCH 17/18] Generator(unstrip): Allow some more generics Until ResolveTypeInNewAssemblies supports generics, shortcircuit generic variables and generic reference parameters --- Il2CppInterop.Generator/Utils/UnstripTranslator.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index 3b96a746..b27039ff 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -72,9 +72,11 @@ private Result Translate() foreach (var variableDefinition in _original.Body.Variables) { - var variableType = - Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, variableDefinition.VariableType, - _imports); + var variableType = variableDefinition.VariableType switch + { + GenericParameter genericParam => genericParam, + _ => Pass80UnstripMethods.ResolveTypeInNewAssemblies(_globalContext, variableDefinition.VariableType, _imports), + }; if (variableType == null) return new(ErrorType.Unresolved, null, $"Could not resolve variable #{variableDefinition.Index} {variableDefinition.VariableType}"); _target.Body.Variables.Add(new VariableDefinition(variableType)); @@ -211,6 +213,7 @@ private Result InlineMethod(Instruction ins) { var newParamType = methodArgParameter.ParameterType switch { + ByReferenceType byRef when byRef.ElementType is GenericParameter => byRef, GenericParameter genericParam => genericParam, _ => Pass80UnstripMethods.ResolveTypeInNewAssemblies( _globalContext, methodArgParameter.ParameterType, _imports), From cbdc8ff5db0ad4a433c0f9acc8bd6b241140baea Mon Sep 17 00:00:00 2001 From: Rafael Mohlin Date: Wed, 20 Sep 2023 17:44:38 +0200 Subject: [PATCH 18/18] misc: Fix formatting errors I blame Visual Studio's AI completion feature --- Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs | 2 +- Il2CppInterop.Generator/Utils/RetargetingILProcessor.cs | 2 +- Il2CppInterop.Generator/Utils/StackWalker.cs | 2 +- Il2CppInterop.Generator/Utils/UnstripTranslator.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs b/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs index d02f3c57..8c883b4b 100644 --- a/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs +++ b/Il2CppInterop.Generator/Passes/Pass80UnstripMethods.cs @@ -178,7 +178,7 @@ private static PropertyDefinition GetOrCreateProperty(MethodDefinition unityMeth if (unityType is ArrayType arrayType) { - if (arrayType.Rank != 1) { rwContext = null; return null; } + if (arrayType.Rank != 1) { rwContext = null; return null; } var resolvedElementType = ResolveTypeInNewAssemblies(context, unityType.GetElementType(), imports, out rwContext, resolveValueTypes); if (resolvedElementType == null) { rwContext = null; return null; } if (resolvedElementType.FullName == "System.String") diff --git a/Il2CppInterop.Generator/Utils/RetargetingILProcessor.cs b/Il2CppInterop.Generator/Utils/RetargetingILProcessor.cs index f3acb6bc..92d0009f 100644 --- a/Il2CppInterop.Generator/Utils/RetargetingILProcessor.cs +++ b/Il2CppInterop.Generator/Utils/RetargetingILProcessor.cs @@ -96,7 +96,7 @@ public void Append(Instruction instruction) public void InsertAfter(Instruction target, Instruction instruction) { - var index = _targetBuilder.Body.Instructions.IndexOf (target); + var index = _targetBuilder.Body.Instructions.IndexOf(target); _targetBuilder.InsertAfter(index, instruction); } diff --git a/Il2CppInterop.Generator/Utils/StackWalker.cs b/Il2CppInterop.Generator/Utils/StackWalker.cs index 40cbc120..082cfeb8 100644 --- a/Il2CppInterop.Generator/Utils/StackWalker.cs +++ b/Il2CppInterop.Generator/Utils/StackWalker.cs @@ -83,7 +83,7 @@ internal static class StackWalker return false; } - var nPush = ins.PushAmount() ; + var nPush = ins.PushAmount(); if (stackPos == stackTarget && nPush > 0) { results[stackTargetIdx].source ??= ins; diff --git a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs index b27039ff..feca2ce1 100644 --- a/Il2CppInterop.Generator/Utils/UnstripTranslator.cs +++ b/Il2CppInterop.Generator/Utils/UnstripTranslator.cs @@ -22,7 +22,7 @@ public class UnstripTranslator public readonly string reason; public bool IsError => type != ErrorType.None; - public Result(ErrorType type, Instruction offendingInstruction, string reason) + public Result(ErrorType type, Instruction offendingInstruction, string reason) { this.type = type; this.offendingInstruction = offendingInstruction;