Skip to content

Commit

Permalink
Core: A bunch of changes to WASM sig calculation.
Browse files Browse the repository at this point in the history
This still doesn't work properly. Make it make sense. Please.
  • Loading branch information
SamboyCoding committed Sep 24, 2024
1 parent af5217c commit 2f8e5b3
Show file tree
Hide file tree
Showing 14 changed files with 174 additions and 91 deletions.
11 changes: 5 additions & 6 deletions Cpp2IL.Core/InstructionSets/WasmInstructionSet.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -17,16 +16,16 @@ public override Memory<byte> 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<byte>();
}

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;
}
Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions Cpp2IL.Core/Model/Contexts/ApplicationAnalysisContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
46 changes: 29 additions & 17 deletions Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class MethodAnalysisContext : HasCustomAttributesAndName, IMethodInfoProv
/// <summary>
/// The raw method body as machine code in the active instruction set.
/// </summary>
public Memory<byte> RawBytes;
public Memory<byte> RawBytes => rawMethodBody ??= InitRawBytes();

/// <summary>
/// The first-stage-analyzed Instruction-Set-Independent Language Instructions.
Expand Down Expand Up @@ -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<byte>? rawMethodBody;


private static List<IBlockProcessor> blockProcessors =
Expand All @@ -92,33 +94,43 @@ 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<byte>();

for (var i = 0; i < Definition.InternalParameterData!.Length; i++)
{
var parameterDefinition = Definition.InternalParameterData![i];
Parameters.Add(new(parameterDefinition, i, this));
}
}
else
RawBytes = Array.Empty<byte>();
rawMethodBody = Array.Empty<byte>();
}

public void EnsureRawBytes()
{
rawMethodBody ??= InitRawBytes();
}

private Memory<byte> 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<byte>();
}

protected MethodAnalysisContext(ApplicationAnalysisContext context) : base(0, context)
{
RawBytes = Array.Empty<byte>();
rawMethodBody = Array.Empty<byte>();
}

[MemberNotNull(nameof(ConvertedIsil))]
Expand Down
2 changes: 1 addition & 1 deletion Cpp2IL.Core/Model/Contexts/NativeMethodAnalysisContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
18 changes: 18 additions & 0 deletions Cpp2IL.Core/Model/Contexts/SystemTypesContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
17 changes: 17 additions & 0 deletions Cpp2IL.Core/Model/Contexts/TypeAnalysisContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
2 changes: 1 addition & 1 deletion Cpp2IL.Core/OutputFormats/WasmMappingOutputFormat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
126 changes: 74 additions & 52 deletions Cpp2IL.Core/Utils/WasmUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,34 +16,52 @@ public static class WasmUtils
internal static readonly Dictionary<int, List<Il2CppMethodDefinition>> 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"
};
}

Expand All @@ -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
{
Expand All @@ -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<Il2CppMethodDefinition>? 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<Il2CppMethodDefinition>? GetMethodDefinitionsAtIndex(int index)
// {
// if (MethodDefinitionIndices.Count == 0)
// CalculateAllMethodDefinitionIndices();
//
// if (MethodDefinitionIndices.TryGetValue(index, out var methodDefinitions))
// return methodDefinitions;
//
// return null;
// }

public static Dictionary<string, string> ExtractAndParseDynCallRemaps(string frameworkJsFile)
{
Expand Down
2 changes: 2 additions & 0 deletions LibCpp2IL/Metadata/Il2CppTypeDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down
Loading

0 comments on commit 2f8e5b3

Please sign in to comment.