From 6015d9e0ab2231ddb194a90278e29b527f35c59e Mon Sep 17 00:00:00 2001 From: Katy Coe Date: Thu, 9 Jul 2020 21:25:19 +0200 Subject: [PATCH] AppModel: Add initial handling of methods (AppMethod) --- Il2CppInspector.Common/Model/AppMethod.cs | 35 ++ Il2CppInspector.Common/Model/AppModel.cs | 30 +- Il2CppInspector.Common/MultiKeyDictionary.cs | 327 +++++++++++++++++++ README.md | 2 + 4 files changed, 378 insertions(+), 16 deletions(-) create mode 100644 Il2CppInspector.Common/Model/AppMethod.cs create mode 100644 Il2CppInspector.Common/MultiKeyDictionary.cs diff --git a/Il2CppInspector.Common/Model/AppMethod.cs b/Il2CppInspector.Common/Model/AppMethod.cs new file mode 100644 index 0000000..d5a0a51 --- /dev/null +++ b/Il2CppInspector.Common/Model/AppMethod.cs @@ -0,0 +1,35 @@ +/* + Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty + + All rights reserved. +*/ + +using Il2CppInspector.Cpp; +using Il2CppInspector.Reflection; + +namespace Il2CppInspector.Model +{ + // Class that represents a composite IL/C++ method + public class AppMethod + { + // The corresponding C++ function pointer type + public CppFnPtrType CppFnPtrType { get; internal set; } + + // The corresponding .NET method + public MethodBase Method { get; internal set; } + + // The VA of the MethodInfo* (VA of the pointer to the MethodInfo) object which defines this method + public ulong MethodInfoPtrAddress { get; internal set; } + + // The VA of the method code itself, or 0 if unknown/not compiled + public ulong MethodCodeAddress => Method.VirtualAddress?.Start ?? 0; + + public AppMethod(MethodBase method, CppFnPtrType cppMethod, ulong methodInfoPtr = 0xffffffff_ffffffff) { + Method = method; + CppFnPtrType = cppMethod; + MethodInfoPtrAddress = methodInfoPtr; + } + + public override string ToString() => CppFnPtrType.ToSignatureString(); + } +} diff --git a/Il2CppInspector.Common/Model/AppModel.cs b/Il2CppInspector.Common/Model/AppModel.cs index 3b5757b..efacd33 100644 --- a/Il2CppInspector.Common/Model/AppModel.cs +++ b/Il2CppInspector.Common/Model/AppModel.cs @@ -8,6 +8,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using Aron.Weiler; using Il2CppInspector.Cpp; using Il2CppInspector.Cpp.UnityHeaders; using Il2CppInspector.Reflection; @@ -32,22 +33,6 @@ namespace Il2CppInspector.Model public ulong VirtualAddress { get; internal set; } } - // Class that represents a composite IL/C++ method - public class AppMethod - { - // The corresponding C++ function pointer type - public CppFnPtrType CppFnPtrType { get; internal set; } - - // The corresponding .NET method - public MethodBase ILMethod { get; internal set; } - - // The VA of the MethodInfo* (VA of the pointer to the MethodInfo) object which defines this method - public ulong MethodInfoPtrAddress { get; internal set; } - - // The VA of the method code itself, or 0 if unknown/not compiled - public ulong MethodCodeAddress => ILMethod.VirtualAddress?.Start ?? 0; - } - // Class that represents the entire structure of the IL2CPP binary realized as C++ types and code, // correlated with .NET types where applicable. Primarily designed to enable automated static analysis of disassembly code. public class AppModel : IEnumerable @@ -71,6 +56,9 @@ namespace Il2CppInspector.Model // The types are ordered to enable the production of code output without forward dependencies public List DependencyOrderedTypes { get; private set; } + // Composite mapping of all the .NET methods in the IL2CPP binary + public MultiKeyDictionary Methods = new MultiKeyDictionary(); + // The .NET type model for the application public TypeModel ILModel { get; } @@ -143,6 +131,9 @@ namespace Il2CppInspector.Model foreach (var method in ILModel.MethodsByDefinitionIndex.Where(m => m.VirtualAddress.HasValue)) { declarationGenerator.IncludeMethod(method); DependencyOrderedTypes.AddRange(declarationGenerator.GenerateRemainingTypeDeclarations()); + + var fnPtr = declarationGenerator.GenerateMethodDeclaration(method); + Methods.Add(method, fnPtr, new AppMethod(method, fnPtr)); } // Add generic methods to C++ type model @@ -151,6 +142,9 @@ namespace Il2CppInspector.Model foreach (var method in ILModel.GenericMethods.Values.Where(m => m.VirtualAddress.HasValue)) { declarationGenerator.IncludeMethod(method); DependencyOrderedTypes.AddRange(declarationGenerator.GenerateRemainingTypeDeclarations()); + + var fnPtr = declarationGenerator.GenerateMethodDeclaration(method); + Methods.Add(method, fnPtr, new AppMethod(method, fnPtr)); } // Add metadata usage types to C++ type model @@ -159,6 +153,8 @@ namespace Il2CppInspector.Model if (Package.MetadataUsages != null) foreach (var usage in Package.MetadataUsages) { + var address = usage.VirtualAddress; + switch (usage.Type) { case MetadataUsageType.Type: case MetadataUsageType.TypeInfo: @@ -171,6 +167,8 @@ namespace Il2CppInspector.Model var method = ILModel.GetMetadataUsageMethod(usage); declarationGenerator.IncludeMethod(method); DependencyOrderedTypes.AddRange(declarationGenerator.GenerateRemainingTypeDeclarations()); + + Methods[method].MethodInfoPtrAddress = address; break; } } diff --git a/Il2CppInspector.Common/MultiKeyDictionary.cs b/Il2CppInspector.Common/MultiKeyDictionary.cs new file mode 100644 index 0000000..fa6eeca --- /dev/null +++ b/Il2CppInspector.Common/MultiKeyDictionary.cs @@ -0,0 +1,327 @@ +// ************************************************* +// Created by Aron Weiler +// Feel free to use this code in any way you like, +// just don't blame me when your coworkers think you're awesome. +// Comments? Email aronweiler@gmail.com +// Revision 1.6 +// Revised locking strategy based on the some bugs found with the existing lock objects. +// Possible deadlock and race conditions resolved by swapping out the two lock objects for a ReaderWriterLockSlim. +// Performance takes a very small hit, but correctness is guaranteed. +// ************************************************* + +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Aron.Weiler +{ + /// + /// Multi-Key Dictionary Class + /// + /// Primary Key Type + /// Sub Key Type + /// Value Type + public class MultiKeyDictionary + { + internal readonly Dictionary baseDictionary = new Dictionary(); + internal readonly Dictionary subDictionary = new Dictionary(); + internal readonly Dictionary primaryToSubkeyMapping = new Dictionary(); + + ReaderWriterLockSlim readerWriterLock = new ReaderWriterLockSlim(); + + public V this[L subKey] + { + get + { + V item; + if (TryGetValue(subKey, out item)) + return item; + + throw new KeyNotFoundException("sub key not found: " + subKey.ToString()); + } + } + + public V this[K primaryKey] + { + get + { + V item; + if (TryGetValue(primaryKey, out item)) + return item; + + throw new KeyNotFoundException("primary key not found: " + primaryKey.ToString()); + } + } + + public void Associate(L subKey, K primaryKey) + { + readerWriterLock.EnterUpgradeableReadLock(); + + try + { + if (!baseDictionary.ContainsKey(primaryKey)) + throw new KeyNotFoundException(string.Format("The base dictionary does not contain the key '{0}'", primaryKey)); + + if (primaryToSubkeyMapping.ContainsKey(primaryKey)) // Remove the old mapping first + { + readerWriterLock.EnterWriteLock(); + + try + { + if (subDictionary.ContainsKey(primaryToSubkeyMapping[primaryKey])) + { + subDictionary.Remove(primaryToSubkeyMapping[primaryKey]); + } + + primaryToSubkeyMapping.Remove(primaryKey); + } + finally + { + readerWriterLock.ExitWriteLock(); + } + } + + subDictionary[subKey] = primaryKey; + primaryToSubkeyMapping[primaryKey] = subKey; + } + finally + { + readerWriterLock.ExitUpgradeableReadLock(); + } + } + + public bool TryGetValue(L subKey, out V val) + { + val = default(V); + + K primaryKey; + + readerWriterLock.EnterReadLock(); + + try + { + if (subDictionary.TryGetValue(subKey, out primaryKey)) + { + return baseDictionary.TryGetValue(primaryKey, out val); + } + } + finally + { + readerWriterLock.ExitReadLock(); + } + + return false; + } + + public bool TryGetValue(K primaryKey, out V val) + { + readerWriterLock.EnterReadLock(); + + try + { + return baseDictionary.TryGetValue(primaryKey, out val); + } + finally + { + readerWriterLock.ExitReadLock(); + } + } + + public bool ContainsKey(L subKey) + { + V val; + + return TryGetValue(subKey, out val); + } + + public bool ContainsKey(K primaryKey) + { + V val; + + return TryGetValue(primaryKey, out val); + } + + public void Remove(K primaryKey) + { + readerWriterLock.EnterWriteLock(); + + try + { + if (primaryToSubkeyMapping.ContainsKey(primaryKey)) + { + if (subDictionary.ContainsKey(primaryToSubkeyMapping[primaryKey])) + { + subDictionary.Remove(primaryToSubkeyMapping[primaryKey]); + } + + primaryToSubkeyMapping.Remove(primaryKey); + } + + baseDictionary.Remove(primaryKey); + } + finally + { + readerWriterLock.ExitWriteLock(); + } + } + + public void Remove(L subKey) + { + readerWriterLock.EnterWriteLock(); + + try + { + baseDictionary.Remove(subDictionary[subKey]); + + primaryToSubkeyMapping.Remove(subDictionary[subKey]); + + subDictionary.Remove(subKey); + } + finally + { + readerWriterLock.ExitWriteLock(); + } + } + + public void Add(K primaryKey, V val) + { + readerWriterLock.EnterWriteLock(); + + try + { + baseDictionary.Add(primaryKey, val); + } + finally + { + readerWriterLock.ExitWriteLock(); + } + } + + public void Add(K primaryKey, L subKey, V val) + { + Add(primaryKey, val); + + Associate(subKey, primaryKey); + } + + public V[] CloneValues() + { + readerWriterLock.EnterReadLock(); + + try + { + V[] values = new V[baseDictionary.Values.Count]; + + baseDictionary.Values.CopyTo(values, 0); + + return values; + } + finally + { + readerWriterLock.ExitReadLock(); + } + } + + public List Values + { + get + { + readerWriterLock.EnterReadLock(); + + try + { + return baseDictionary.Values.ToList(); + } + finally + { + readerWriterLock.ExitReadLock(); + } + } + } + + public K[] ClonePrimaryKeys() + { + readerWriterLock.EnterReadLock(); + + try + { + K[] values = new K[baseDictionary.Keys.Count]; + + baseDictionary.Keys.CopyTo(values, 0); + + return values; + } + finally + { + readerWriterLock.ExitReadLock(); + } + } + + public L[] CloneSubKeys() + { + readerWriterLock.EnterReadLock(); + + try + { + L[] values = new L[subDictionary.Keys.Count]; + + subDictionary.Keys.CopyTo(values, 0); + + return values; + } + finally + { + readerWriterLock.ExitReadLock(); + } + } + + public void Clear() + { + readerWriterLock.EnterWriteLock(); + + try + { + baseDictionary.Clear(); + + subDictionary.Clear(); + + primaryToSubkeyMapping.Clear(); + } + finally + { + readerWriterLock.ExitWriteLock(); + } + } + + public int Count + { + get + { + readerWriterLock.EnterReadLock(); + + try + { + return baseDictionary.Count; + } + finally + { + readerWriterLock.ExitReadLock(); + } + } + } + + public IEnumerator> GetEnumerator() + { + readerWriterLock.EnterReadLock(); + + try + { + return baseDictionary.GetEnumerator(); + } + finally + { + readerWriterLock.ExitReadLock(); + } + } + } +} diff --git a/README.md b/README.md index 2f967a7..3bf4656 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,8 @@ Thanks to the following major contributors! - nneonneo - https://github.com/nneonneo (huge overhaul of generics and script generation) - carterbush - https://github.com/carterbush (IDA script generation) +This project uses [MultiKeyDictionary](https://www.codeproject.com/Articles/32894/C-Multi-key-Generic-Dictionary) by Aron Weiler. + Thanks to the following individuals whose code and research helped me develop this tool: - Perfare - https://github.com/Perfare/Il2CppDumper