diff --git a/src/LibProtodec/CilReader.cs b/src/LibProtodec/CilReader.cs new file mode 100644 index 0000000..4c8b8ae --- /dev/null +++ b/src/LibProtodec/CilReader.cs @@ -0,0 +1,67 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using SystemEx.Memory; + +namespace LibProtodec; + +public static class CilReader +{ + // Temporary AOT incompatible method to fill the dictionary, TODO: replace with a source generator + private static readonly Dictionary OpCodeLookup = + typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static) + .Select(field => (OpCode)field.GetValue(null)!) + .ToDictionary(opCode => (int)(ushort)opCode.Value); + + public static OpCode ReadCilOpCode(this ref MemoryReader reader, out int operandLength) + { + byte opCodeByte = reader.ReadByte(); + int opCodeInt = opCodeByte == OpCodes.Prefix1.Value + ? (opCodeByte << 8) | reader.ReadByte() + : opCodeByte; + + OpCode opCode = OpCodeLookup[opCodeInt]; + operandLength = SizeOf(opCode.OperandType); + + return opCode; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int SizeOf(OperandType operandType) + { + switch (operandType) + { + case OperandType.ShortInlineBrTarget: + case OperandType.ShortInlineI: + case OperandType.ShortInlineVar: + return 1; + case OperandType.InlineVar: + return 2; + case OperandType.InlineBrTarget: + case OperandType.InlineField: + case OperandType.InlineI: + case OperandType.InlineMethod: + case OperandType.InlineSig: + case OperandType.InlineString: + case OperandType.InlineSwitch: + case OperandType.InlineTok: + case OperandType.InlineType: + case OperandType.ShortInlineR: + return 4; + case OperandType.InlineI8: + case OperandType.InlineR: + return 8; + case OperandType.InlineNone: + default: + return 0; + } + } +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Clr/ClrMethod.cs b/src/LibProtodec/Models/Cil/Clr/ClrMethod.cs index f29e480..467b29a 100644 --- a/src/LibProtodec/Models/Cil/Clr/ClrMethod.cs +++ b/src/LibProtodec/Models/Cil/Clr/ClrMethod.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Reflection; +using CommunityToolkit.Diagnostics; namespace LibProtodec.Models.Cil.Clr; @@ -35,4 +36,8 @@ public sealed class ClrMethod(MethodInfo clrMethod) : ClrMember(clrMethod), ICil parameter.ParameterType); } } + + public byte[] GetMethodBodyILAsByteArray() => + clrMethod.GetMethodBody()?.GetILAsByteArray() + ?? ThrowHelper.ThrowNotSupportedException(); } \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Clr/ClrModule.cs b/src/LibProtodec/Models/Cil/Clr/ClrModule.cs new file mode 100644 index 0000000..33b2416 --- /dev/null +++ b/src/LibProtodec/Models/Cil/Clr/ClrModule.cs @@ -0,0 +1,27 @@ +using System.Collections.Concurrent; +using System.Reflection; + +namespace LibProtodec.Models.Cil.Clr; + +public sealed class ClrModule : ICilModule +{ + private readonly Module _module; + + private ClrModule(Module module) => + _module = module; + + public string ResolveFieldName(int token) => + _module.ResolveField(token).Name; + + public string ResolveMethodName(int token) => + _module.ResolveMethod(token).Name; + + + private static readonly ConcurrentDictionary ModuleLookup = []; + + public static ICilModule GetOrCreate(Module clrModule) => + ModuleLookup.GetOrAdd( + clrModule.FullyQualifiedName, + static (_, clrModule) => new ClrModule(clrModule), + clrModule); +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Clr/ClrType.cs b/src/LibProtodec/Models/Cil/Clr/ClrType.cs index 62fd807..6c63804 100644 --- a/src/LibProtodec/Models/Cil/Clr/ClrType.cs +++ b/src/LibProtodec/Models/Cil/Clr/ClrType.cs @@ -31,6 +31,10 @@ public sealed class ClrType : ClrMember, ICilType public string DeclaringAssemblyName => _clrType.Assembly.FullName!; + public ICilModule DeclaringModule => + ClrModule.GetOrCreate( + _clrType.Module); + public ICilType? BaseType => _clrType.BaseType is null ? null diff --git a/src/LibProtodec/Models/Cil/ICilMethod.cs b/src/LibProtodec/Models/Cil/ICilMethod.cs index 01dc2fe..a3a7f37 100644 --- a/src/LibProtodec/Models/Cil/ICilMethod.cs +++ b/src/LibProtodec/Models/Cil/ICilMethod.cs @@ -23,4 +23,6 @@ public interface ICilMethod IList CustomAttributes { get; } IEnumerable GetParameterTypes(); + + byte[] GetMethodBodyILAsByteArray(); } \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/ICilModule.cs b/src/LibProtodec/Models/Cil/ICilModule.cs new file mode 100644 index 0000000..f721955 --- /dev/null +++ b/src/LibProtodec/Models/Cil/ICilModule.cs @@ -0,0 +1,14 @@ +// Copyright © 2024 Xpl0itR +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +namespace LibProtodec.Models.Cil; + +public interface ICilModule +{ + string ResolveFieldName(int token); + + string ResolveMethodName(int token); +} \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/ICilType.cs b/src/LibProtodec/Models/Cil/ICilType.cs index a2a490b..913a6fe 100644 --- a/src/LibProtodec/Models/Cil/ICilType.cs +++ b/src/LibProtodec/Models/Cil/ICilType.cs @@ -14,9 +14,10 @@ public interface ICilType string FullName { get; } string? Namespace { get; } - string DeclaringAssemblyName { get; } - ICilType? DeclaringType { get; } - ICilType? BaseType { get; } + string DeclaringAssemblyName { get; } + ICilModule DeclaringModule { get; } + ICilType? DeclaringType { get; } + ICilType? BaseType { get; } bool IsAbstract { get; } bool IsClass { get; } diff --git a/src/LibProtodec/ProtodecContext.cs b/src/LibProtodec/ProtodecContext.cs index 71f7f71..001dc2c 100644 --- a/src/LibProtodec/ProtodecContext.cs +++ b/src/LibProtodec/ProtodecContext.cs @@ -10,7 +10,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection.Emit; using SystemEx; +using SystemEx.Memory; using CommunityToolkit.Diagnostics; using LibProtodec.Models.Cil; using LibProtodec.Models.Protobuf; @@ -69,6 +71,8 @@ public class ProtodecContext Protobuf protobuf = GetProtobuf(messageClass, message, options); + ParseWriteToMethodTesting(messageClass); + List idFields = messageClass.GetFields() .Where(static field => field is { IsPublic: true, IsStatic: true, IsLiteral: true }) .ToList(); @@ -137,6 +141,39 @@ public class ProtodecContext return message; } + private static void ParseWriteToMethodTesting(ICilType messageClass) + { + ICilMethod writeTo = messageClass.GetMethods().Single(static method => method.Name == "WriteTo"); + MemoryReader reader = new(writeTo.GetMethodBodyILAsByteArray()); + + int fieldToken = 0; + while (reader.Remaining > 0) + { + OpCode opCode = reader.ReadCilOpCode(out int operandLength); + if (opCode == OpCodes.Ret) // DummyDLL from il2cppdumper will only have ret in method body + return; + + if (opCode == OpCodes.Ldfld) + { + fieldToken = reader.ReadInt32LittleEndian(); + } + else if (opCode == OpCodes.Call) + { + Guard.IsNotEqualTo(fieldToken, 0); + int methodToken = reader.ReadInt32LittleEndian(); + + string methodName = messageClass.DeclaringModule.ResolveMethodName(methodToken); // System.NotSupportedException: 'Resolving tokens is not supported on assemblies loaded by a MetadataLoadContext.' + string fieldName = messageClass.DeclaringModule.ResolveFieldName(fieldToken); + + //TODO + } + else + { + reader.Position += operandLength; + } + } + } + public virtual Enum ParseEnum(ICilType enumEnum, ParserOptions options = ParserOptions.None) { Guard.IsTrue(enumEnum.IsEnum);