diff --git a/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs b/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs index 4b21b7bb..122c19c8 100644 --- a/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs +++ b/Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Cpp2IL.Core.Api; -using Cpp2IL.Core.Graphs; using Cpp2IL.Core.Il2CppApiFunctions; using Cpp2IL.Core.ISIL; using Cpp2IL.Core.Logging; @@ -17,16 +16,16 @@ public override Memory GetRawBytesForMethod(MethodAnalysisContext context, { if (context.Definition is { } methodDefinition) { - var wasmDef = WasmUtils.TryGetWasmDefinition(methodDefinition); + var wasmDef = WasmUtils.TryGetWasmDefinition(context); if (wasmDef == null) { - Logger.WarnNewline($"Could not find WASM definition for method {methodDefinition.DeclaringType?.FullName}::{methodDefinition.Name}, probably incorrect signature calculation (signature was {WasmUtils.BuildSignature(methodDefinition)})", "WasmInstructionSet"); + Logger.WarnNewline($"Could not find WASM definition for method {methodDefinition.HumanReadableSignature} in {methodDefinition.DeclaringType?.FullName}, probably incorrect signature calculation (signature was {WasmUtils.BuildSignature(context)})", "WasmInstructionSet"); return Array.Empty(); } if (wasmDef.AssociatedFunctionBody == null) - throw new($"WASM definition {wasmDef}, resolved from MethodAnalysisContext {context} has no associated function body"); + throw new($"WASM definition {wasmDef}, resolved from MethodAnalysisContext {context.Definition.HumanReadableSignature} in {context.DeclaringType?.FullName} has no associated function body (signature was {WasmUtils.BuildSignature(context)})"); return wasmDef.AssociatedFunctionBody.Instructions; } @@ -46,10 +45,10 @@ public override BaseKeyFunctionAddresses CreateKeyFunctionAddressesInstance() public override string PrintAssembly(MethodAnalysisContext context) { - if (context.Definition is not { } methodDefinition) + if (context.Definition == null) return string.Empty; - var def = WasmUtils.GetWasmDefinition(methodDefinition); + var def = WasmUtils.GetWasmDefinition(context); var disassembled = Disassembler.Disassemble(def.AssociatedFunctionBody!.Instructions, (uint)context.UnderlyingPointer); return string.Join("\n", disassembled); diff --git a/Cpp2IL.Core/Model/Contexts/ApplicationAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/ApplicationAnalysisContext.cs index 199eea85..9af5d2ef 100644 --- a/Cpp2IL.Core/Model/Contexts/ApplicationAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/ApplicationAnalysisContext.cs @@ -109,6 +109,7 @@ private void PopulateMethodsByAddressTable() { Assemblies.SelectMany(a => a.Types).SelectMany(t => t.Methods).ToList().ForEach(m => { + m.EnsureRawBytes(); var ptr = InstructionSet.GetPointerForMethod(m); if (!MethodsByAddress.ContainsKey(ptr)) diff --git a/Cpp2IL.Core/Model/Contexts/AttributeGeneratorMethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/AttributeGeneratorMethodAnalysisContext.cs index 56c46c80..b09a9cac 100644 --- a/Cpp2IL.Core/Model/Contexts/AttributeGeneratorMethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/AttributeGeneratorMethodAnalysisContext.cs @@ -12,6 +12,6 @@ public AttributeGeneratorMethodAnalysisContext(ulong pointer, ApplicationAnalysi { UnderlyingPointer = pointer; AssociatedMember = associatedMember; - RawBytes = AppContext.InstructionSet.GetRawBytesForMethod(this, true); + rawMethodBody = AppContext.InstructionSet.GetRawBytesForMethod(this, true); } } diff --git a/Cpp2IL.Core/Model/Contexts/ConcreteGenericMethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/ConcreteGenericMethodAnalysisContext.cs index 41e58bf3..48ebf466 100644 --- a/Cpp2IL.Core/Model/Contexts/ConcreteGenericMethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/ConcreteGenericMethodAnalysisContext.cs @@ -50,7 +50,7 @@ private ConcreteGenericMethodAnalysisContext(Cpp2IlMethodRef methodRef, Assembly } if (UnderlyingPointer != 0) - RawBytes = AppContext.InstructionSet.GetRawBytesForMethod(this, false); + rawMethodBody = AppContext.InstructionSet.GetRawBytesForMethod(this, false); } private static AssemblyAnalysisContext ResolveDeclaringAssembly(Cpp2IlMethodRef methodRef, ApplicationAnalysisContext context) diff --git a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs index 8a1c7d6e..7f5dc01a 100644 --- a/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs @@ -40,7 +40,7 @@ public class MethodAnalysisContext : HasCustomAttributesAndName, IMethodInfoProv /// /// The raw method body as machine code in the active instruction set. /// - public Memory RawBytes; + public Memory RawBytes => rawMethodBody ??= InitRawBytes(); /// /// The first-stage-analyzed Instruction-Set-Independent Language Instructions. @@ -75,6 +75,8 @@ public class MethodAnalysisContext : HasCustomAttributesAndName, IMethodInfoProv //TODO Support custom attributes on return types (v31 feature) public TypeAnalysisContext ReturnTypeContext => InjectedReturnType ?? DeclaringType!.DeclaringAssembly.ResolveIl2CppType(Definition!.RawReturnType!); + + protected Memory? rawMethodBody; private static List blockProcessors = @@ -92,20 +94,6 @@ public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisCon { InitCustomAttributeData(); - //Some abstract methods (on interfaces, no less) apparently have a body? Unity doesn't support default interface methods so idk what's going on here. - //E.g. UnityEngine.Purchasing.AppleCore.dll: UnityEngine.Purchasing.INativeAppleStore::SetUnityPurchasingCallback on among us (itch.io build) - if (Definition.MethodPointer != 0 && !Definition.Attributes.HasFlag(MethodAttributes.Abstract)) - { - RawBytes = AppContext.InstructionSet.GetRawBytesForMethod(this, false); - - if (RawBytes.Length == 0) - { - Logger.VerboseNewline("\t\t\tUnexpectedly got 0-byte method body for " + this + $". Pointer was 0x{Definition.MethodPointer:X}", "MAC"); - } - } - else - RawBytes = Array.Empty(); - for (var i = 0; i < Definition.InternalParameterData!.Length; i++) { var parameterDefinition = Definition.InternalParameterData![i]; @@ -113,12 +101,36 @@ public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisCon } } else - RawBytes = Array.Empty(); + rawMethodBody = Array.Empty(); + } + + public void EnsureRawBytes() + { + rawMethodBody ??= InitRawBytes(); + } + + private Memory InitRawBytes() + { + //Some abstract methods (on interfaces, no less) apparently have a body? Unity doesn't support default interface methods so idk what's going on here. + //E.g. UnityEngine.Purchasing.AppleCore.dll: UnityEngine.Purchasing.INativeAppleStore::SetUnityPurchasingCallback on among us (itch.io build) + if (Definition != null && Definition.MethodPointer != 0 && !Definition.Attributes.HasFlag(MethodAttributes.Abstract)) + { + var ret = AppContext.InstructionSet.GetRawBytesForMethod(this, false); + + if (ret.Length == 0) + { + Logger.VerboseNewline("\t\t\tUnexpectedly got 0-byte method body for " + this + $". Pointer was 0x{Definition.MethodPointer:X}", "MAC"); + } + + return ret; + } + else + return Array.Empty(); } protected MethodAnalysisContext(ApplicationAnalysisContext context) : base(0, context) { - RawBytes = Array.Empty(); + rawMethodBody = Array.Empty(); } [MemberNotNull(nameof(ConvertedIsil))] diff --git a/Cpp2IL.Core/Model/Contexts/NativeMethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/NativeMethodAnalysisContext.cs index 00b5b525..24fc6542 100644 --- a/Cpp2IL.Core/Model/Contexts/NativeMethodAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/NativeMethodAnalysisContext.cs @@ -34,6 +34,6 @@ public NativeMethodAnalysisContext(TypeAnalysisContext parent, ulong address, bo DefaultName = $"NativeMethod_0x{UnderlyingPointer:X}"; } - RawBytes = AppContext.InstructionSet.GetRawBytesForMethod(this, false); + rawMethodBody = AppContext.InstructionSet.GetRawBytesForMethod(this, false); } } diff --git a/Cpp2IL.Core/Model/Contexts/SystemTypesContext.cs b/Cpp2IL.Core/Model/Contexts/SystemTypesContext.cs index 232a860f..c5745b9c 100644 --- a/Cpp2IL.Core/Model/Contexts/SystemTypesContext.cs +++ b/Cpp2IL.Core/Model/Contexts/SystemTypesContext.cs @@ -66,4 +66,22 @@ public SystemTypesContext(ApplicationAnalysisContext appContext) UnmanagedCallersOnlyAttributeType = systemAssembly.GetTypeByFullName("System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute"); } + + public bool IsPrimitive(TypeAnalysisContext context) + { + return context == SystemBooleanType || + context == SystemCharType || + context == SystemSByteType || + context == SystemByteType || + context == SystemInt16Type || + context == SystemUInt16Type || + context == SystemInt32Type || + context == SystemUInt32Type || + context == SystemInt64Type || + context == SystemUInt64Type || + context == SystemSingleType || + context == SystemDoubleType || + context == SystemIntPtrType || + context == SystemUIntPtrType; + } } diff --git a/Cpp2IL.Core/Model/Contexts/TypeAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/TypeAnalysisContext.cs index 1ba5b4e1..a4e2ed81 100644 --- a/Cpp2IL.Core/Model/Contexts/TypeAnalysisContext.cs +++ b/Cpp2IL.Core/Model/Contexts/TypeAnalysisContext.cs @@ -71,9 +71,26 @@ public class TypeAnalysisContext : HasCustomAttributesAndName, ITypeInfoProvider public TypeAnalysisContext? DeclaringType { get; protected internal set; } + public TypeAnalysisContext? EnumUnderlyingType => Definition == null ? null : DeclaringAssembly.ResolveIl2CppType(Definition.EnumUnderlyingType); + public TypeAnalysisContext? BaseType => OverrideBaseType ?? (Definition == null ? null : DeclaringAssembly.ResolveIl2CppType(Definition.RawBaseType)); public TypeAnalysisContext[] InterfaceContexts => (Definition?.RawInterfaces.Select(DeclaringAssembly.ResolveIl2CppType).ToArray() ?? [])!; + + public bool IsPrimitive + { + get + { + if (Definition == null) + return false; + + if (Definition.RawBaseType?.Type.IsIl2CppPrimitive() == true) + return true; + + //Might still be TYPE_CLASS but yet int or something, so check it directly + return AppContext.SystemTypes.IsPrimitive(this); + } + } public string FullName { diff --git a/Cpp2IL.Core/OutputFormats/WasmMappingOutputFormat.cs b/Cpp2IL.Core/OutputFormats/WasmMappingOutputFormat.cs index a9bf8bad..a733e47c 100644 --- a/Cpp2IL.Core/OutputFormats/WasmMappingOutputFormat.cs +++ b/Cpp2IL.Core/OutputFormats/WasmMappingOutputFormat.cs @@ -44,7 +44,7 @@ public override void DoOutput(ApplicationAnalysisContext context, string outputR try { - var wasmDef = WasmUtils.GetWasmDefinition(methodAnalysisContext.Definition); + var wasmDef = WasmUtils.GetWasmDefinition(methodAnalysisContext); var ghidraName = WasmUtils.GetGhidraFunctionName(wasmDef); output.AppendLine(ghidraName); diff --git a/Cpp2IL.Core/Utils/WasmUtils.cs b/Cpp2IL.Core/Utils/WasmUtils.cs index 7cb6e08e..8c215f6c 100644 --- a/Cpp2IL.Core/Utils/WasmUtils.cs +++ b/Cpp2IL.Core/Utils/WasmUtils.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Reflection; using System.Text.RegularExpressions; +using Cpp2IL.Core.Model.Contexts; using LibCpp2IL; using LibCpp2IL.Metadata; using LibCpp2IL.Reflection; @@ -15,34 +16,52 @@ public static class WasmUtils internal static readonly Dictionary> MethodDefinitionIndices = new(); private static Regex DynCallRemappingRegex = new(@"Module\[\s*[""'](dynCall_[^""']+)[""']\s*\]\s*=\s*Module\[\s*[""']asm[""']\s*\]\[\s*[""']([^""']+)[""']\s*\]\s*\)\.apply", RegexOptions.Compiled); - public static string BuildSignature(Il2CppMethodDefinition definition) + public static string BuildSignature(MethodAnalysisContext definition) { var instanceParam = definition.IsStatic ? "" : "i"; //Something still off about p/invoke functions. They do have methodinfo args, but something is wrong somewhere. + + //Also, this is STILL wrong for a lot of methods in DateTimeFormat and TimeZoneInfo. + //It feels like it's something to do with when DateTime is considered a struct and when it's considered a class. + //But I can find no rhyme nor reason to it. - return $"{GetSignatureLetter(definition.ReturnType!)}{instanceParam}{string.Join("", definition.Parameters!.Select(p => GetSignatureLetter(p.Type, p.IsRefOrOut)))}i"; //Add an extra i on the end for the method info param + var returnTypeSignature = definition.ReturnTypeContext switch + { + { Namespace: nameof(System), Name: "Void" } => "v", + { IsValueType: true, IsPrimitive: false, Definition: null or { Size: < 0 or > 8 } } => "vi", //Large or Generic Struct returns have a void return type, but the actual return value is the first parameter. + { IsValueType: true, IsPrimitive: false, Definition.Size: > 4 } => "j", //Medium structs are returned as longs + { IsValueType: true, IsPrimitive: false, Definition.Size: <= 4 } => "i", //Small structs are returned as ints + _ => GetSignatureLetter(definition.ReturnTypeContext!) + }; + + return $"{returnTypeSignature}{instanceParam}{string.Join("", definition.Parameters!.Select(p => GetSignatureLetter(p.ParameterTypeContext, p.IsRef)))}i"; //Add an extra i on the end for the method info param } - private static char GetSignatureLetter(Il2CppTypeReflectionData type, bool isRefOrOut = false) + private static string GetSignatureLetter(TypeAnalysisContext type, bool isRefOrOut = false) { if (isRefOrOut) //ref/out params are passed as pointers - return 'i'; + return "i"; + + if (type is WrappedTypeAnalysisContext) + //Pointers, arrays, etc are ints + return "i"; - if (type.isPointer) - //Pointers are ints - return 'i'; + if (type.IsEnumType) + type = type.EnumUnderlyingType ?? throw new($"Enum type {type} has no underlying type"); - var typeDefinition = type.baseType ?? LibCpp2IlReflection.GetType("Int32", "System")!; + // var typeDefinition = type.BaseType ?? type.AppContext.SystemTypes.SystemInt32Type; - return typeDefinition.Name switch + return type.Name switch { - "Void" => 'v', - "Int64" => 'j', - "Single" => 'f', - "Double" => 'd', - _ => 'i' //Including Int32 + "Void" => "v", + "Int64" => "j", + "Single" => "f", + "Double" => "d", + "Int32" => "i", + _ when type is { IsValueType: true, IsPrimitive: false, IsEnumType: false, Definition.Size: <= 8 and > 0 } => "j", //TODO check - value types < 16 bytes (including base object header which is irrelevant here) are passed directly as long? + _ => "i" }; } @@ -55,7 +74,7 @@ public static string GetGhidraFunctionName(WasmFunctionDefinition functionDefini return $"unnamed_function_{index}"; } - public static WasmFunctionDefinition? TryGetWasmDefinition(Il2CppMethodDefinition definition) + public static WasmFunctionDefinition? TryGetWasmDefinition(MethodAnalysisContext definition) { try { @@ -67,53 +86,56 @@ public static string GetGhidraFunctionName(WasmFunctionDefinition functionDefini } } - public static WasmFunctionDefinition GetWasmDefinition(Il2CppMethodDefinition definition) + public static WasmFunctionDefinition GetWasmDefinition(MethodAnalysisContext context) { + if (context.Definition == null) + throw new($"Attempted to get wasm definition for probably-injected method context: {context}"); + //First, we have to calculate the signature - var signature = BuildSignature(definition); + var signature = BuildSignature(context); try { - return ((WasmFile)LibCpp2IlMain.Binary!).GetFunctionFromIndexAndSignature(definition.MethodPointer, signature); + return ((WasmFile)LibCpp2IlMain.Binary!).GetFunctionFromIndexAndSignature(context.Definition.MethodPointer, signature); } catch (Exception e) { - throw new($"Failed to find wasm definition for {definition}\nwhich has params {definition.Parameters?.ToStringEnumerable()}", e); + throw new($"Failed to find wasm definition for {context}\nwhich has params {context.Parameters?.ToStringEnumerable()}", e); } } - private static void CalculateAllMethodDefinitionIndices() - { - foreach (var il2CppMethodDefinition in LibCpp2IlMain.TheMetadata!.methodDefs) - { - var methodDefinition = il2CppMethodDefinition; - - try - { - var wasmDef = GetWasmDefinition(methodDefinition); - var index = ((WasmFile)LibCpp2IlMain.Binary!).FunctionTable.IndexOf(wasmDef); - - if (!MethodDefinitionIndices.TryGetValue(index, out var mDefs)) - MethodDefinitionIndices[index] = mDefs = []; - - mDefs.Add(methodDefinition); - } - catch (Exception) - { - //Ignore - } - } - } - - public static List? GetMethodDefinitionsAtIndex(int index) - { - if (MethodDefinitionIndices.Count == 0) - CalculateAllMethodDefinitionIndices(); - - if (MethodDefinitionIndices.TryGetValue(index, out var methodDefinitions)) - return methodDefinitions; - - return null; - } + // private static void CalculateAllMethodDefinitionIndices() + // { + // foreach (var il2CppMethodDefinition in LibCpp2IlMain.TheMetadata!.methodDefs) + // { + // var methodDefinition = il2CppMethodDefinition; + // + // try + // { + // var wasmDef = GetWasmDefinition(methodDefinition); + // var index = ((WasmFile)LibCpp2IlMain.Binary!).FunctionTable.IndexOf(wasmDef); + // + // if (!MethodDefinitionIndices.TryGetValue(index, out var mDefs)) + // MethodDefinitionIndices[index] = mDefs = []; + // + // mDefs.Add(methodDefinition); + // } + // catch (Exception) + // { + // //Ignore + // } + // } + // } + // + // public static List? GetMethodDefinitionsAtIndex(int index) + // { + // if (MethodDefinitionIndices.Count == 0) + // CalculateAllMethodDefinitionIndices(); + // + // if (MethodDefinitionIndices.TryGetValue(index, out var methodDefinitions)) + // return methodDefinitions; + // + // return null; + // } public static Dictionary ExtractAndParseDynCallRemaps(string frameworkJsFile) { diff --git a/LibCpp2IL/Metadata/Il2CppTypeDefinition.cs b/LibCpp2IL/Metadata/Il2CppTypeDefinition.cs index aa93eb5c..590e52b2 100644 --- a/LibCpp2IL/Metadata/Il2CppTypeDefinition.cs +++ b/LibCpp2IL/Metadata/Il2CppTypeDefinition.cs @@ -338,6 +338,8 @@ public Il2CppPropertyDefinition[]? Properties .ToArray(); public Il2CppTypeDefinition? DeclaringType => LibCpp2IlMain.TheMetadata == null || LibCpp2IlMain.Binary == null || DeclaringTypeIndex < 0 ? null : LibCpp2IlMain.TheMetadata.typeDefs[LibCpp2IlMain.Binary.GetType(DeclaringTypeIndex).Data.ClassIndex]; + + public Il2CppTypeDefinition? ElementType => LibCpp2IlMain.TheMetadata == null || LibCpp2IlMain.Binary == null || ElementTypeIndex < 0 ? null : LibCpp2IlMain.TheMetadata.typeDefs[LibCpp2IlMain.Binary.GetType(ElementTypeIndex).Data.ClassIndex]; public Il2CppGenericContainer? GenericContainer => GenericContainerIndex < 0 ? null : LibCpp2IlMain.TheMetadata?.genericContainers[GenericContainerIndex]; diff --git a/LibCpp2IL/Wasm/WasmDynCallCoefficients.cs b/LibCpp2IL/Wasm/WasmDynCallCoefficients.cs index 40b30f38..fa71e146 100644 --- a/LibCpp2IL/Wasm/WasmDynCallCoefficients.cs +++ b/LibCpp2IL/Wasm/WasmDynCallCoefficients.cs @@ -3,5 +3,5 @@ namespace LibCpp2IL.Wasm; public struct WasmDynCallCoefficients { public ulong andWith; - public ulong addConstant; + public long addConstant; } diff --git a/LibCpp2IL/Wasm/WasmFile.cs b/LibCpp2IL/Wasm/WasmFile.cs index 2fab4e34..6aff15f5 100644 --- a/LibCpp2IL/Wasm/WasmFile.cs +++ b/LibCpp2IL/Wasm/WasmFile.cs @@ -80,13 +80,16 @@ public WasmFile(MemoryStream input) : base(input) public WasmFunctionDefinition GetFunctionFromIndexAndSignature(ulong index, string signature) { if (!DynCallCoefficients.TryGetValue(signature, out var coefficients)) + { + // LibLogger.WarnNewline($"Can't get function with signature {signature}, as it's not defined in the binary"); throw new($"Can't get function with signature {signature}, as it's not defined in the binary"); + } //Calculate adjusted dyncall index - index = (index & coefficients.andWith) + coefficients.addConstant; + index = (ulong)((long)(index & coefficients.andWith) + coefficients.addConstant); //Use element section to look up real index - var realIndex = ElementSection.Elements[0].FunctionIndices![(int)index - 1]; //Minus 1 because the first element in the actual memory layout is FFFFFFFF + var realIndex = ElementSection.Elements[0].FunctionIndices![(int)index]; //Minus 1 because the first element in the actual memory layout is FFFFFFFF //Look up real index in function table return FunctionTable[(int)realIndex]; @@ -130,21 +133,21 @@ private void CalculateDynCallOffsets() var disassembled = Disassembler.Disassemble(funcBody, (uint)function.InstructionsOffset); //Find the consts, ands, and adds - var relevantInstructions = disassembled.Where(i => i.Mnemonic is WasmMnemonic.I32Const or WasmMnemonic.I32And or WasmMnemonic.I32Add).ToArray(); + var relevantInstructions = disassembled.Where(i => i.Mnemonic is WasmMnemonic.I32Const or WasmMnemonic.I32And or WasmMnemonic.I32Add or WasmMnemonic.I32Sub).ToArray(); ulong andWith; - ulong add; + long add; if (relevantInstructions.Length == 2) { if (relevantInstructions[^1].Mnemonic == WasmMnemonic.I32And) { - andWith = (ulong)relevantInstructions[0].Operands[0]; + andWith = (ulong)(long)relevantInstructions[0].Operands[0]; add = 0; } else if (relevantInstructions[^1].Mnemonic == WasmMnemonic.I32Add) { - add = (ulong)relevantInstructions[0].Operands[0]; + add = (long)relevantInstructions[0].Operands[0]; andWith = int.MaxValue; } else @@ -156,14 +159,23 @@ private void CalculateDynCallOffsets() else if (relevantInstructions.Length == 4) { //Should be const, and, const, add - if (!relevantInstructions.Select(i => i.Mnemonic).SequenceEqual(new[] { WasmMnemonic.I32Const, WasmMnemonic.I32And, WasmMnemonic.I32Const, WasmMnemonic.I32Add })) + var mnemonics = relevantInstructions.Select(i => i.Mnemonic).ToArray(); + var isAddVariant = mnemonics.SequenceEqual([WasmMnemonic.I32Const, WasmMnemonic.I32And, WasmMnemonic.I32Const, WasmMnemonic.I32Add]); + var isSubVariant = mnemonics.SequenceEqual([WasmMnemonic.I32Const, WasmMnemonic.I32And, WasmMnemonic.I32Const, WasmMnemonic.I32Sub]); + if (!isAddVariant && !isSubVariant) { - LibLogger.WarnNewline($"\t\tCouldn't calculate coefficients for {signature}, got mnemonics {string.Join(", ", relevantInstructions.Select(i => i.Mnemonic))}, expecting I32Const, I32And, I32Const, I32Add"); + LibLogger.WarnNewline($"\t\tCouldn't calculate coefficients for {signature}, got mnemonics {string.Join(", ", relevantInstructions.Select(i => i.Mnemonic))}, expecting I32Const, I32And, I32Const, (I32Add|I32Sub)"); continue; } - andWith = (ulong)relevantInstructions[0].Operands[0]; - add = (ulong)relevantInstructions[2].Operands[0]; + andWith = (ulong)(long)relevantInstructions[0].Operands[0]; + add = (long)relevantInstructions[2].Operands[0]; + + if (isSubVariant) + { + add = -add; + LibLogger.WarnNewline($"\t\tCoefficient for {signature} using I32Sub not I32Add, this may not work!"); + } } else if (disassembled.All(d => d.Mnemonic is WasmMnemonic.LocalGet or WasmMnemonic.CallIndirect or WasmMnemonic.End)) { diff --git a/WasmDisassembler/Disassembler.cs b/WasmDisassembler/Disassembler.cs index d37193fc..3bddaabd 100644 --- a/WasmDisassembler/Disassembler.cs +++ b/WasmDisassembler/Disassembler.cs @@ -115,7 +115,7 @@ internal static object ReadPrimitive(this BinaryReader reader, Type type) return reader.ReadDouble(); if (type == typeof(LEB128)) - return reader.BaseStream.ReadLEB128Unsigned(); + return reader.BaseStream.ReadLEB128Signed(); throw new($"Bad primitive type: {type}"); }