public void TestAppModelQueries() { // Arrange // We're currently in IlCppTests\bin\Debug\netcoreapp3.0 or similar var testPath = Path.GetFullPath(Directory.GetCurrentDirectory() + @"\..\..\..\TestBinaries\ArraysAndPointers-ARM64"); // Act var inspectors = Il2CppInspector.LoadFromFile(testPath + @"\ArraysAndPointers-ARM64.so", testPath + @"\global-metadata.dat"); var model = new TypeModel(inspectors[0]); var app = new AppModel(model).Build(); // Assert // Check VTable offset is accurate Assert.AreEqual(0x130, app.GetVTableOffset()); // BufferedStream.Flush() Assert.AreEqual(11, app.GetVTableIndexFromClassOffset(0x1E0)); var vtable = model.GetType("System.IO.BufferedStream").GetVTable(); Assert.AreEqual("get_CanWrite", vtable[app.GetVTableIndexFromClassOffset(0x1E0)].Name); var method = app.Methods.Values.First(m => m.MethodCodeAddress == 0x7C94D4); Assert.AreEqual("Flush", method.Method.Name); Assert.AreEqual("System.IO.BufferedStream", method.Method.DeclaringType.FullName); }
public void TestGenericTypes() { // Arrange // We're currently in IlCppTests\bin\Debug\netcoreapp3.0 or similar var testPath = Path.GetFullPath(Directory.GetCurrentDirectory() + @"\..\..\..\TestBinaries\GenericTypes"); // Build model var inspectors = Il2CppInspector.LoadFromFile(testPath + @"\GenericTypes.so", testPath + @"\global-metadata.dat"); var model = new Il2CppModel(inspectors[0]); var asm = model.GetAssembly("GenericTypes.dll"); // Act TypeInfo tDerived = asm.GetType("Il2CppTests.TestSources.Derived`1"); TypeInfo tDerivedBase = tDerived.BaseType; // TODO: array of Derived<int> // TypeInfo tDerivedArray TypeInfo tT = asm.GetType("Il2CppTests.TestSources.Base`2").GenericTypeParameters[0]; TypeInfo tF = tDerived.GetField("F").FieldType; TypeInfo tNested = asm.GetType("Il2CppTests.TestSources.Derived`1+Nested"); DisplayGenericType(tDerived, "Derived<V>"); DisplayGenericType(tDerivedBase, "Base type of Derived<V>"); //DisplayGenericType(tDerivedArray, "Array of Derived<int>"); DisplayGenericType(tT, "Type parameter T from Base<T>"); DisplayGenericType(tF, "Field type, G<Derived<V>>"); DisplayGenericType(tNested, "Nested type in Derived<V>"); // Assert var checks = new[] {
static void Main(string[] args) { // Command-line usage: dotnet run [<binary-file> [<metadata-file> [<output-file>]]] // Defaults to libil2cpp.so or GameAssembly.dll if binary file not specified string imageFile = "libil2cpp.so"; string metaFile = "global-metadata.dat"; string outFile = "types.cs"; if (args.Length == 0) { if (!File.Exists(imageFile)) { imageFile = "GameAssembly.dll"; } } if (args.Length >= 1) { imageFile = args[0]; } if (args.Length >= 2) { metaFile = args[1]; } if (args.Length >= 3) { outFile = args[2]; } // Check files if (!File.Exists(imageFile)) { Console.Error.WriteLine($"File {imageFile} does not exist"); Environment.Exit(1); } if (!File.Exists(metaFile)) { Console.Error.WriteLine($"File {metaFile} does not exist"); Environment.Exit(1); } // Analyze data var il2cppInspectors = Il2CppInspector.LoadFromFile(imageFile, metaFile); if (il2cppInspectors == null) { Environment.Exit(1); } // Write output file int i = 0; foreach (var il2cpp in il2cppInspectors) { new Il2CppDumper(il2cpp).WriteFile(outFile + (i++ > 0 ? "-" + (i - 1) : "")); } }
// See: IL2CPP/Il2CppInspector.cs public void PostProcessPackage(Il2CppInspector.Il2CppInspector package, PluginPostProcessPackageEventInfo data) { // This is called once per IL2CPP binary after it has been merged with global-metadata.dat // and all of the data linked together // It contains surrogate properties to everything in Metadata and Il2CppBinary // plus all calculated default field values (FieldDefaultValue), default parameter values (ParameterDefaultValue), // field offsets (FieldOffsets), custom attribute generators (CustomAttributeGenerators), // function addresses (FunctionAddresses) and metadata usages (MetadataUsages) etc. // Set data.IsDataModified if you modify the Il2CppInspector }
public void TestGenericTypes() { // Arrange // We're currently in IlCppTests\bin\Debug\netcoreapp3.0 or similar var testPath = Path.GetFullPath(Directory.GetCurrentDirectory() + @"\..\..\..\TestBinaries\GenericTypes"); // Build model var inspectors = Il2CppInspector.LoadFromFile(testPath + @"\GenericTypes.so", testPath + @"\global-metadata.dat"); var model = new Il2CppModel(inspectors[0]); var asm = model.GetAssembly("GenericTypes.dll"); // Act TypeInfo tBase = asm.GetType("Il2CppTests.TestSources.Base`2"); TypeInfo tDerived = asm.GetType("Il2CppTests.TestSources.Derived`1"); TypeInfo tDerivedBase = tDerived.BaseType; TypeInfo tDerivedArray = model.GetType("Il2CppTests.TestSources.Derived`1[System.Int32][]"); TypeInfo tT = tBase.GenericTypeParameters[0]; TypeInfo tU = tBase.GenericTypeParameters[1]; TypeInfo tF = tDerived.GetField("F").FieldType; TypeInfo tNested = asm.GetType("Il2CppTests.TestSources.Derived`1+Nested"); TypeInfo tNG = asm.GetType("Il2CppTests.TestSources.NonGeneric"); TypeInfo tGCWM = asm.GetType("Il2CppTests.TestSources.GenericClassWithMethods`1"); TypeInfo tCGM = asm.GetType("Il2CppTests.TestSources.CallGenericMethods"); MethodInfo mGMDINGC = tNG.GetMethod("GenericMethodDefinitionInNonGenericClass"); MethodInfo mNGMIGC = tGCWM.GetMethod("NonGenericMethodInGenericClass"); MethodInfo mNGMIGC2 = tGCWM.GetMethod("NonGenericMethodInGenericClass2"); MethodInfo mGMDIGC = tGCWM.GetMethod("GenericMethodDefinitionInGenericClass"); MethodInfo mGMDIGC2 = tGCWM.GetMethod("GenericMethodDefinitionInGenericClass2"); MethodBase mGMDINGC_closed = model.GetGenericMethod( "Il2CppTests.TestSources.NonGeneric.GenericMethodDefinitionInNonGenericClass", model.GetType("System.Single")); MethodBase mGMDIGC_closed = model.GetGenericMethod( "Il2CppTests.TestSources.GenericClassWithMethods`1.GenericMethodDefinitionInGenericClass", model.GetType("System.Int32")); MethodBase mGMDIGC2_closed = model.GetGenericMethod( "Il2CppTests.TestSources.GenericClassWithMethods`1.GenericMethodDefinitionInGenericClass2", model.GetType("System.String")); DisplayGenericType(tBase, "Generic type definition Base<T, U>"); DisplayGenericType(tDerived, "Derived<V>"); DisplayGenericType(tDerivedBase, "Base type of Derived<V>"); DisplayGenericType(tDerivedArray, "Array of Derived<int>"); DisplayGenericType(tT, "Type parameter T from Base<T,U>"); DisplayGenericType(tU, "Type parameter U from Base<T,U>"); DisplayGenericType(tF, "Field type, G<Derived<V>>"); DisplayGenericType(tNested, "Nested type in Derived<V>"); // Assert var typeChecks = new[] {
private void runTest(string testPath) { // Android var testFile = testPath + @"\" + Path.GetFileName(testPath) + ".so"; // Windows if (!File.Exists(testFile)) { testFile = testPath + @"\" + Path.GetFileName(testPath) + ".dll"; } if (!File.Exists(testFile)) { testFile = testPath + @"\GameAssembly.dll"; } // iOS if (!File.Exists(testFile)) { testFile = testPath + @"\" + Path.GetFileName(testPath); } // Android if (!File.Exists(testFile)) { testFile = testPath + @"\libil2cpp.so"; } var inspectors = Il2CppInspector.LoadFromFile(testFile, testPath + @"\global-metadata.dat"); // If null here, there was a problem parsing the files if (inspectors == null) { throw new Exception("Could not understand IL2CPP binary or metadata"); } if (inspectors.Count == 0) { throw new Exception("Could not find any images in the IL2CPP binary"); } // Dump each image in the binary separately int i = 0; foreach (var il2cpp in inspectors) { new CSharpCodeStubs(new Il2CppModel(il2cpp)) { ExcludedNamespaces = Constants.DefaultExcludedNamespaces, SuppressMetadata = false, MustCompile = true } }
public void TestAppModelQueries() { // Arrange // We're currently in IlCppTests\bin\Debug\netcoreapp3.0 or similar var testPath = Path.GetFullPath(Directory.GetCurrentDirectory() + @"\..\..\..\TestBinaries\ArraysAndPointers-ARM64"); // Act var inspectors = Il2CppInspector.LoadFromFile(testPath + @"\ArraysAndPointers-ARM64.so", testPath + @"\global-metadata.dat"); var model = new TypeModel(inspectors[0]); var app = new AppModel(model); // Assert // Check VTable offset is accurate // Note: this is for 2020.1 and below; it changed to 0x138 in 2020.2 Assert.AreEqual(0x130, app.GetVTableOffset()); // BufferedStream.Flush() Assert.AreEqual(11, app.GetVTableIndexFromClassOffset(0x1E0)); var vtable = model.GetType("System.IO.BufferedStream").GetVTable(); // Check vtable calculations are correct Assert.AreEqual("get_CanWrite", vtable[app.GetVTableIndexFromClassOffset(0x1E0)].Name); // Check method lookup is correct var method = app.Methods.Values.First(m => m.MethodCodeAddress == 0x7C94D4); Assert.AreEqual("Flush", method.Method.Name); Assert.AreEqual("System.IO.BufferedStream", method.Method.DeclaringType.FullName); // AsyncStateMachineAttribute CAG - 0x3B7C58 - Type from Il2CppType** // adrp x9,0xfca000 - ldr x9,[x9, #0x90] - ldr x0,[x9] // Check Il2CppType * lookup is correct via AppModel var typeRefPtr = (ulong)app.Image.ReadMappedWord(0xFCA090); var typeFromRef = app.Types.Values.First(t => t.TypeRefPtrAddress == typeRefPtr).Type; Assert.AreEqual("System.IO.StreamReader+<ReadAsyncInternal>d__65", typeFromRef.FullName); // Check Il2CppType * lookup is correct via AddressMap var map = app.GetAddressMap(); var appTypeReference = map[typeRefPtr]; Assert.AreEqual(typeof(AppTypeReference), appTypeReference.GetType()); Assert.AreEqual(typeFromRef, ((AppTypeReference)appTypeReference).Type.Type); Assert.AreEqual(typeRefPtr, ((AppTypeReference)appTypeReference).Type.TypeRefPtrAddress); }
// Attempt to load an IL2CPP application package (APK or IPA) public async Task <bool> LoadPackageAsync(string packageFile) { try { var streams = Inspector.GetStreamsFromPackage(packageFile); if (streams == null) { throw new InvalidOperationException("The supplied package is not an APK or IPA file, or does not contain an IL2CPP application"); } return(await LoadMetadataAsync(streams.Value.Metadata) && await LoadBinaryAsync(streams.Value.Binary)); } catch (Exception ex) { LastException = ex; return(false); } }
// A "package" is the combination of global-metadata.dat, the application binary, // and some analysis which links them both together into a single unit, the Il2CppInspector object. // This executes after all the low-level processing and analysis of the application is completed, // but before any higher-level abstractions are created, such as the .NET type model or C++ application model. // Therefore this is a good place to make any final changes to the data that the high level models and output modules will rely on // In this case, we are going to acquire all of the string literals that we deferred earlier public void PostProcessPackage(Il2CppInspector.Il2CppInspector package, PluginPostProcessPackageEventInfo data) { // Don't do anything if this isn't for us if (!IsOurs) { return; } // Tell the user what is happening in case it takes a while PluginServices.For(this).StatusUpdate("Decrypting string literals"); // Calculate the number of string literals // This calculation depends on being able to scan MetadataUsages for all of the StringLiteral uses // and finding the one with the highest index. The creation of MetadataUsages requires data // from both global-metadata.dat and the application binary; this data is merged together // when the Il2CppInspector object is initialized, so this is the earliest opportunity we have to examine it var stringLiteralCount = package.MetadataUsages.Where(u => u.Type == MetadataUsageType.StringLiteral).Max(u => u.SourceIndex) + 1; // Create a delegate which internally is a function pointer to the GetStringLiteralFromIndex function in the DLL var pGetStringLiteralFromIndex = (GetStringLiteralFromIndex) Marshal.GetDelegateForFunctionPointer(ModuleBase + Offsets[game.Value].GetStringLiteralFromIndex, typeof(GetStringLiteralFromIndex)); var stringLiterals = new List <string>(); var length = 0; // For each index, call the delegate with the decrypted metadata byte array, index and a pointer as arguments // In this case, the function returns an array of UTF8-encoded characters, // and populates 'length' with the number of bytes returned for (uint index = 0; index < stringLiteralCount; index++) { var decryptedBytesUnmanaged = pGetStringLiteralFromIndex(metadataBlob, index, ref length); var str = new byte[length]; Marshal.Copy(decryptedBytesUnmanaged, str, 0, length); stringLiterals.Add(Encoding.UTF8.GetString(str)); } // If we had used IGetStringLiterals above, we would have set data.StringLiterals, // but here we modify the package (the Il2CppInspector object) directly instead package.Metadata.StringLiterals = stringLiterals.ToArray(); // We don't set FullyProcessed so that other plugins can perform further post-processing modifications // IsDataModified tells Il2CppInspector that the contents of its internal data structures have been changed // Note this is different from IsStreamModified; changing the data in memory // does not automatically rewrite the stream. data.IsDataModified = true; }
// Attempt to load an IL2CPP application package (APK or IPA) public async Task <bool> LoadPackageAsync(string packageFile) { try { OnStatusUpdate?.Invoke(this, "Extracting package"); // TODO: Accept multiple APKs var streams = Inspector.GetStreamsFromPackage(new string[] { packageFile }); if (streams == null) { throw new InvalidOperationException("The supplied package is not an APK or IPA file, or does not contain an IL2CPP application"); } return(await LoadMetadataAsync(streams.Value.Metadata) && await LoadBinaryAsync(streams.Value.Binary)); } catch (Exception ex) { LastException = ex; return(false); } }
// Attempt to load an IL2CPP application package (APK or IPA) public async Task <bool> LoadPackageAsync(IEnumerable <string> packageFiles) { try { OnStatusUpdate?.Invoke(this, "Extracting package"); var streams = await Task.Run(() => Inspector.GetStreamsFromPackage(packageFiles)); if (streams == null) { throw new InvalidOperationException("The supplied package is not an APK or IPA file, or does not contain a complete IL2CPP application"); } return(await LoadMetadataAsync(streams.Value.Metadata) && await LoadBinaryAsync(streams.Value.Binary)); } catch (Exception ex) { LastException = ex; return(false); } }
public void TestNames() { // Arrange // We're currently in IlCppTests\bin\Debug\netcoreapp3.0 or similar var testPath = Path.GetFullPath(Directory.GetCurrentDirectory() + @"\..\..\..\TestBinaries\References-ARMv7"); // Build model var inspectors = Il2CppInspector.LoadFromFile(testPath + @"\References-ARMv7.so", testPath + @"\global-metadata.dat"); var model = new Il2CppModel(inspectors[0]); var asm = model.GetAssembly("References.dll"); // Act var t = asm.GetType("Il2CppTests.TestSources.Test"); var m1 = t.GetMethod("MethodWithGenericAndClassRefs"); var m2 = t.GetMethod("MethodWithInRefOut"); var p1 = m1.DeclaredParameters; var p2 = m2.DeclaredParameters; // Assert var checks = new[] {
private void LoadIlAppModel() { // Lazily create an Il2CppInspector model. Copying the ELF data array is required since the library // seems to mess with the data, which crashes the game. // TODO: Consider caching the result in a file. This is horribly slow. var binaryStream = new MemoryStream(Data.ToArray()); var metadataStream = new MemoryStream(Il2CppMetadata); var oldOut = Console.Out; // Disable Console.WriteLine() since Il2CppInspector logs text Console.SetOut(TextWriter.Null); try { // The internal class Il2CppInspector.ElfReader64 can be used to retrieve the offsets of sections // in the ELF file. These are relevant since Il2CppInspector saves symbol offsets relative to the // code section of the ELF so this offset needs to be considered when editing the ELF. var elfReader = CreateAndInitElfReader(binaryStream); ReadSectionOffsets(elfReader); // Create the inspector manually instead of using Il2CppInspector.LoadFromStream to avoid // reading the ELF a second time. var metadata = new Metadata(metadataStream); var binary = Il2CppBinary.Load(elfReader, metadata); var inspector = new Il2CppInspector.Il2CppInspector(binary, metadata); if (inspector == null) { throw new Exception("Couldn't extract Il2Cpp metadata."); } ilAppModel = new AppModel(new TypeModel(inspector)); } finally { Console.SetOut(oldOut); } }
private void runTest(string testPath) { // Android var testFile = testPath + @"\" + Path.GetFileName(testPath) + ".so"; // Windows if (!File.Exists(testFile)) { testFile = testPath + @"\" + Path.GetFileName(testPath) + ".dll"; } if (!File.Exists(testFile)) { testFile = testPath + @"\GameAssembly.dll"; } // iOS if (!File.Exists(testFile)) { testFile = testPath + @"\" + Path.GetFileName(testPath); } // Android if (!File.Exists(testFile)) { testFile = testPath + @"\libil2cpp.so"; } var inspectors = Il2CppInspector.LoadFromFile(testFile, testPath + @"\global-metadata.dat"); // If null here, there was a problem parsing the files if (inspectors == null) { throw new Exception("Could not understand IL2CPP binary or metadata"); } if (inspectors.Count == 0) { throw new Exception("Could not find any images in the IL2CPP binary"); } // Exclusions var excludedNamespaces = new List <string> { "System", "Mono", "Microsoft.Win32", "Unity", "UnityEditor", "UnityEngine", "UnityEngineInternal", "AOT", "JetBrains.Annotations" }; // Dump each image in the binary separately int i = 0; foreach (var il2cpp in inspectors) { new CSharpCodeStubs(new Il2CppModel(il2cpp)) { ExcludedNamespaces = excludedNamespaces, SuppressMetadata = false, MustCompile = true } }
public static PluginPostProcessPackageEventInfo PostProcessPackage(Il2CppInspector package) => PluginManager.Try <ILoadPipeline, PluginPostProcessPackageEventInfo>((p, e) => p.PostProcessPackage(package, e));
public Il2CppDumper(Il2CppInspector proc) { model = new Il2CppReflector(proc); }
private void runTest(string testPath) { // Android var testFile = testPath + @"\" + Path.GetFileName(testPath) + ".so"; // Windows if (!File.Exists(testFile)) { testFile = testPath + @"\" + Path.GetFileName(testPath) + ".dll"; } if (!File.Exists(testFile)) { testFile = testPath + @"\GameAssembly.dll"; } // iOS if (!File.Exists(testFile)) { testFile = testPath + @"\" + Path.GetFileName(testPath); } // Android if (!File.Exists(testFile)) { testFile = testPath + @"\libil2cpp.so"; } var inspectors = Il2CppInspector.LoadFromFile(testFile, testPath + @"\global-metadata.dat"); // If null here, there was a problem parsing the files if (inspectors == null) { throw new Exception("Could not understand IL2CPP binary or metadata"); } if (inspectors.Count == 0) { throw new Exception("Could not find any images in the IL2CPP binary"); } // Dump each image in the binary separately int i = 0; foreach (var il2cpp in inspectors) { var model = new Il2CppModel(il2cpp); var nameSuffix = i++ > 0 ? "-" + (i - 1) : ""; new CSharpCodeStubs(model) { ExcludedNamespaces = Constants.DefaultExcludedNamespaces, SuppressMetadata = false, MustCompile = true }.WriteSingleFile(testPath + $@"\test-result{nameSuffix}.cs"); new IDAPythonScript(model) .WriteScriptToFile(testPath + $@"\test-ida-result{nameSuffix}.py"); new CppScaffolding(model) .WriteCppToFile(testPath + $@"\test-result{nameSuffix}.h"); } // Compare test result with expected result for (i = 0; i < inspectors.Count; i++) { var expected = File.ReadAllLines(testPath + @"\..\..\TestExpectedResults\" + Path.GetFileName(testPath) + (i > 0 ? "-" + i : "") + ".cs"); var actual = File.ReadAllLines(testPath + @"\test-result" + (i > 0 ? "-" + i : "") + ".cs"); // Get rid of blank lines and trim the remaining lines expected = (from l in expected where !string.IsNullOrWhiteSpace(l) select l.Trim()).ToArray(); actual = (from l in actual where !string.IsNullOrWhiteSpace(l) select l.Trim()).ToArray(); CollectionAssert.AreEqual(expected, actual); } }
private static int Run(Options options) { // Banner var asmInfo = FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetEntryAssembly().Location); Console.WriteLine(asmInfo.ProductName); Console.WriteLine("Version " + asmInfo.ProductVersion); Console.WriteLine(asmInfo.LegalCopyright); Console.WriteLine(""); // Check excluded namespaces if (options.ExcludedNamespaces.Count() == 1 && options.ExcludedNamespaces.First().ToLower() == "none") { options.ExcludedNamespaces = new List <string>(); } // Check files if (!File.Exists(options.BinaryFile)) { Console.Error.WriteLine($"File {options.BinaryFile} does not exist"); return(1); } if (!File.Exists(options.MetadataFile)) { Console.Error.WriteLine($"File {options.MetadataFile} does not exist"); return(1); } // Creating a Visual Studio solution requires Unity assembly references var unityPath = string.Empty; var unityAssembliesPath = string.Empty; if (options.CreateSolution) { unityPath = FindPath(options.UnityPath); unityAssembliesPath = FindPath(options.UnityAssembliesPath); if (!Directory.Exists(unityPath)) { Console.Error.WriteLine($"Unity path {unityPath} does not exist"); return(1); } if (!File.Exists(unityPath + @"\Editor\Data\Managed\UnityEditor.dll")) { Console.Error.WriteLine($"No Unity installation found at {unityPath}"); return(1); } if (!Directory.Exists(unityAssembliesPath)) { Console.Error.WriteLine($"Unity assemblies path {unityAssembliesPath} does not exist"); return(1); } if (!File.Exists(unityAssembliesPath + @"\UnityEngine.UI.dll")) { Console.Error.WriteLine($"No Unity assemblies found at {unityAssembliesPath}"); return(1); } Console.WriteLine("Using Unity editor at " + unityPath); Console.WriteLine("Using Unity assemblies at " + unityAssembliesPath); } // Analyze data List <Il2CppInspector> il2cppInspectors; using (var il2cppTimer = new Benchmark("Analyze IL2CPP data")) il2cppInspectors = Il2CppInspector.LoadFromFile(options.BinaryFile, options.MetadataFile); if (il2cppInspectors == null) { Environment.Exit(1); } // Write output file int i = 0; foreach (var il2cpp in il2cppInspectors) { // Create model Il2CppModel model; using (var modelTimer = new Benchmark("Create type model")) model = new Il2CppModel(il2cpp); // C# signatures output using (var signaturesDumperTimer = new Benchmark("Generate C# code")) { var writer = new Il2CppCSharpDumper(model) { ExcludedNamespaces = options.ExcludedNamespaces.ToList(), SuppressMetadata = options.SuppressMetadata, MustCompile = options.MustCompile }; var imageSuffix = i++ > 0 ? "-" + (i - 1) : ""; var csOut = options.CSharpOutPath; if (csOut.ToLower().EndsWith(".cs")) { csOut = csOut.Insert(csOut.Length - 3, imageSuffix); } else { csOut += imageSuffix; } if (options.CreateSolution) { writer.WriteSolution(csOut, unityPath, unityAssembliesPath); } else { switch (options.LayoutSchema.ToLower(), options.SortOrder.ToLower()) {
private static int Run(Options options) { // Check excluded namespaces if (options.ExcludedNamespaces.Count() == 1 && options.ExcludedNamespaces.First().ToLower() == "none") { options.ExcludedNamespaces = new List <string>(); } // Check files if (!File.Exists(options.BinaryFile)) { Console.Error.WriteLine($"File {options.BinaryFile} does not exist"); return(1); } if (!File.Exists(options.MetadataFile)) { Console.Error.WriteLine($"File {options.MetadataFile} does not exist"); return(1); } // Analyze data List <Il2CppInspector> il2cppInspectors; using (var timer = new Benchmark("Analyze IL2CPP data")) il2cppInspectors = Il2CppInspector.LoadFromFile(options.BinaryFile, options.MetadataFile); if (il2cppInspectors == null) { Environment.Exit(1); } // Write output file int i = 0; foreach (var il2cpp in il2cppInspectors) { // Create model Il2CppModel model; using (var timer1 = new Benchmark("Create type model")) model = new Il2CppModel(il2cpp); // C# signatures output using var timer2 = new Benchmark("Generate C# code"); var writer = new Il2CppCSharpDumper(model) { ExcludedNamespaces = options.ExcludedNamespaces.ToList(), SuppressMetadata = options.SuppressMetadata, MustCompile = options.MustCompile }; var imageSuffix = i++ > 0 ? "-" + (i - 1) : ""; var csOut = options.CSharpOutPath; if (csOut.ToLower().EndsWith(".cs")) { csOut = csOut.Insert(csOut.Length - 3, imageSuffix); } else { csOut += imageSuffix; } switch (options.LayoutSchema.ToLower(), options.SortOrder.ToLower()) {
private void runTest(string testPath) { // Android var testFile = testPath + @"\" + Path.GetFileName(testPath) + ".so"; if (!File.Exists(testFile)) { testFile = testPath + @"\libil2cpp.so"; } // Windows if (!File.Exists(testFile)) { testFile = testPath + @"\" + Path.GetFileName(testPath) + ".dll"; } if (!File.Exists(testFile)) { testFile = testPath + @"\GameAssembly.dll"; } // iOS if (!File.Exists(testFile)) { testFile = testPath + @"\" + Path.GetFileName(testPath); } var inspectors = Il2CppInspector.LoadFromFile(testFile, testPath + @"\global-metadata.dat"); // If null here, there was a problem parsing the files if (inspectors == null) { throw new Exception("Could not understand IL2CPP binary or metadata"); } if (inspectors.Count == 0) { throw new Exception("Could not find any images in the IL2CPP binary"); } // Dump each image in the binary separately int i = 0; foreach (var il2cpp in inspectors) { var model = new TypeModel(il2cpp); var appModel = new AppModel(model, makeDefaultBuild: false).Build(compiler: CppCompilerType.MSVC); var nameSuffix = i++ > 0 ? "-" + (i - 1) : ""; new CSharpCodeStubs(model) { ExcludedNamespaces = Constants.DefaultExcludedNamespaces, SuppressMetadata = false, MustCompile = true }.WriteSingleFile(testPath + $@"\test-result{nameSuffix}.cs"); new JSONMetadata(appModel) .Write(testPath + $@"\test-result{nameSuffix}.json"); new CppScaffolding(appModel) .Write(testPath + $@"\test-cpp-result{nameSuffix}"); new PythonScript(appModel) .WriteScriptToFile(testPath + $@"\test-ida{nameSuffix}.py", "IDA", testPath + $@"\test-cpp-result{nameSuffix}\appdata\il2cpp-types.h", testPath + $@"\test-result{nameSuffix}.json"); } // Compare test results with expected results for (i = 0; i < inspectors.Count; i++) { var suffix = (i > 0 ? "-" + i : ""); compareFiles(testPath, suffix + ".cs", $"test-result{suffix}.cs"); compareFiles(testPath, suffix + ".json", $"test-result{suffix}.json"); compareFiles(testPath, suffix + ".h", $@"test-cpp-result{suffix}\appdata\il2cpp-types.h"); } }
// Test runner private async Task runTest(string testPath, LoadOptions loadOptions = null) { // Android var testFile = testPath + @"\" + Path.GetFileName(testPath) + ".so"; if (!File.Exists(testFile)) { testFile = testPath + @"\libil2cpp.so"; } // Windows if (!File.Exists(testFile)) { testFile = testPath + @"\" + Path.GetFileName(testPath) + ".dll"; } if (!File.Exists(testFile)) { testFile = testPath + @"\GameAssembly.dll"; } // iOS if (!File.Exists(testFile)) { testFile = testPath + @"\" + Path.GetFileName(testPath); } // Linux process map if (!File.Exists(testFile)) { testFile = Directory.GetFiles(testPath, "*-maps.txt").FirstOrDefault(); } // XAPK (selects latest version assuming lexical order) if (testFile == null) { testFile = Directory.GetFiles(testPath, "*.xapk").LastOrDefault(); } // APK (selects latest version assuming lexical order) (prefer XAPKs) if (testFile == null) { testFile = Directory.GetFiles(testPath, "*.apk").LastOrDefault(); } // Set load options if (loadOptions == null) { loadOptions = new LoadOptions(); } loadOptions.BinaryFilePath = testFile; // Load core plugins PluginManager.Reload(testPath + @"\..\plugins", coreOnly: true); // Set up additional plugins - place desired plugins in 'plugins' sub-folder of test folder try { PluginManager.Reload(testPath + @"\plugins", reset: false); } catch (DirectoryNotFoundException) { } // Handlers for debugging output PluginManager.StatusHandler += (s, e) => { Console.WriteLine("Plugin " + e.Plugin.Name + ": " + e.Text); }; PluginManager.ErrorHandler += (s, e) => { Assert.Fail($"{e.Error.Plugin.Name} throw an exception during {e.Error.Operation}: {e.Error.Exception.Message}.", e); }; // Get plugin options - place desired options in <plugins-id>.options.txt for each plugin in test folder // in the format "key=value", one per line // Use %TESTDIR% to refer to the directory containing the test files foreach (var plugin in PluginManager.AvailablePlugins) { Console.WriteLine("Using plugin: " + plugin.Name); // Enable plugin var ourPlugin = PluginManager.Plugins[plugin.Id]; ourPlugin.Enabled = true; // Look for options var optionsFile = $@"{testPath}\{plugin.Id}-options.txt"; if (File.Exists(optionsFile)) { var options = File.ReadAllLines(optionsFile); // Parse options foreach (var option in options) { var kv = option.Split('=', 2); if (kv.Length == 2) { var key = kv[0].Trim(); var value = kv[1].Trim().Replace("%TESTDIR%", testPath); Console.WriteLine($"Setting option: {key} = {value}"); // Null default values must be castable to object ourPlugin.Plugin.Options.Single(o => o.Name == key).SetFromString(value); } } } PluginManager.OptionsChanged(plugin); } List <Il2CppInspector> inspectors; using (new Benchmark("Load IL2CPP metadata and binary")) try { inspectors = Il2CppInspector.LoadFromFile(testFile, testPath + @"\global-metadata.dat", loadOptions); } catch (FileNotFoundException) { inspectors = Il2CppInspector.LoadFromPackage(new[] { testFile }, loadOptions); } // If null here, there was a problem parsing the files if (inspectors == null) { throw new Exception("Could not understand IL2CPP binary or metadata"); } if (inspectors.Count == 0) { throw new Exception("Could not find any images in the IL2CPP binary"); } // End if we were only testing file load if (!GenerateCpp && !GenerateCS && !GenerateDLL && !GenerateJSON && !GeneratePython) { return; } // Dump each image in the binary separately var imageTasks = inspectors.Select((il2cpp, i) => Task.Run(async() => { TypeModel model; using (new Benchmark("Create .NET type model")) model = new TypeModel(il2cpp); Task <AppModel> appModelTask = null; if (GenerateCpp || GenerateJSON || GeneratePython) { appModelTask = Task.Run(() => { using (new Benchmark("Create application model")) return(new AppModel(model, makeDefaultBuild: false).Build(compiler: CppCompilerType.MSVC)); }); } var nameSuffix = i++ > 0 ? "-" + (i - 1) : ""; var generateTasks = new List <Task>(); var compareTasks = new List <Task>(); if (GenerateCS) { generateTasks.Add(Task.Run(() => { using (new Benchmark("Create C# code stubs")) new CSharpCodeStubs(model) { ExcludedNamespaces = Constants.DefaultExcludedNamespaces, SuppressMetadata = false, MustCompile = true }.WriteSingleFile(testPath + $@"\test-result{nameSuffix}.cs"); compareTasks.Add(Task.Run(() => compareFiles(testPath, nameSuffix + ".cs", $"test-result{nameSuffix}.cs"))); })); } if (GenerateDLL) { generateTasks.Add(Task.Run(() => { using (new Benchmark("Create .NET assembly shims")) new AssemblyShims(model).Write(testPath + $@"\test-dll-result{nameSuffix}"); compareTasks.Add(Task.Run(() => compareBinaryFiles(testPath + $@"\test-dll-result{nameSuffix}", testPath + @"\..\..\TestExpectedResults\dll-" + Path.GetFileName(testPath) + nameSuffix))); })); } AppModel appModel = null; if (appModelTask != null) { appModel = await appModelTask; } if (GenerateJSON || GeneratePython) { generateTasks.Add(Task.Run(() => { using (new Benchmark("Create JSON metadata")) new JSONMetadata(appModel) .Write(testPath + $@"\test-result{nameSuffix}.json"); compareTasks.Add(Task.Run(() => compareFiles(testPath, nameSuffix + ".json", $"test-result{nameSuffix}.json"))); })); } if (GenerateCpp || GeneratePython) { generateTasks.Add(Task.Run(() => { using (new Benchmark("Create C++ scaffolding")) new CppScaffolding(appModel) .Write(testPath + $@"\test-cpp-result{nameSuffix}"); compareTasks.Add(Task.Run(() => compareFiles(testPath, nameSuffix + ".h", $@"test-cpp-result{nameSuffix}\appdata\il2cpp-types.h"))); })); } if (GeneratePython) { generateTasks.Add(Task.Run(() => { var python = new PythonScript(appModel); foreach (var target in PythonScript.GetAvailableTargets()) { python.WriteScriptToFile(testPath + $@"\test-{target.ToLower()}{nameSuffix}.py", target, testPath + $@"\test-cpp-result{nameSuffix}\appdata\il2cpp-types.h", testPath + $@"\test-result{nameSuffix}.json"); } })); } await Task.WhenAll(generateTasks); await Task.WhenAll(compareTasks); })); await Task.WhenAll(imageTasks); }