public static void Main(string[] args) { Console.WriteLine("===Cpp2IL by Samboy063==="); Console.WriteLine("A Tool to Reverse Unity's \"il2cpp\" Build Process."); Console.WriteLine("Running on " + Environment.OSVersion.Platform); #region Command Line Parsing CommandLineOptions = null; Parser.Default.ParseArguments <Options>(args).WithParsed(options => { CommandLineOptions = options; }); if (CommandLineOptions == null) { Console.WriteLine("Invalid command line. Exiting."); return; } var baseGamePath = CommandLineOptions.GamePath; Console.WriteLine("Using path: " + baseGamePath); if (!Directory.Exists(baseGamePath)) { Console.WriteLine("Specified game-path does not exist: " + baseGamePath); return; } var assemblyPath = Path.Combine(baseGamePath, "GameAssembly.dll"); var exeName = Path.GetFileNameWithoutExtension(Directory.GetFiles(baseGamePath) .First(f => f.EndsWith(".exe") && !BlacklistedExecutableFilenames.Any(bl => f.EndsWith(bl)))); if (CommandLineOptions.ExeName != null) { exeName = CommandLineOptions.ExeName; Console.WriteLine($"Using OVERRIDDEN game name: {exeName}"); } else { Console.WriteLine($"Auto-detected game name: {exeName}"); } var unityPlayerPath = Path.Combine(baseGamePath, $"{exeName}.exe"); var metadataPath = Path.Combine(baseGamePath, $"{exeName}_Data", "il2cpp_data", "Metadata", "global-metadata.dat"); if (!File.Exists(assemblyPath) || !File.Exists(unityPlayerPath) || !File.Exists(metadataPath)) { Console.WriteLine("Invalid game-path or exe-name specified. Failed to find one of the following:\n" + $"\t{assemblyPath}\n" + $"\t{unityPlayerPath}\n" + $"\t{metadataPath}\n"); return; } #endregion Console.WriteLine($"Located game EXE: {unityPlayerPath}"); Console.WriteLine($"Located global-metadata: {metadataPath}"); #region Unity Version Determination Console.WriteLine("\nAttempting to determine Unity version..."); int[] unityVerUseful; if (Environment.OSVersion.Platform == PlatformID.Win32NT) { var unityVer = FileVersionInfo.GetVersionInfo(unityPlayerPath); unityVerUseful = new[] { unityVer.FileMajorPart, unityVer.FileMinorPart, unityVer.FileBuildPart }; } else { //Globalgamemanagers var globalgamemanagersPath = Path.Combine(baseGamePath, $"{exeName}_Data", "globalgamemanagers"); var ggmBytes = File.ReadAllBytes(globalgamemanagersPath); var verString = new StringBuilder(); var idx = 0x14; while (ggmBytes[idx] != 0) { verString.Append(Convert.ToChar(ggmBytes[idx])); idx++; } var unityVer = verString.ToString(); unityVer = unityVer.Substring(0, unityVer.IndexOf("f", StringComparison.Ordinal)); Console.WriteLine("Read version string from globalgamemanagers: " + unityVer); unityVerUseful = unityVer.Split(".").Select(int.Parse).ToArray(); } Console.WriteLine("This game is built with Unity version " + string.Join(".", unityVerUseful)); if (unityVerUseful[0] <= 4) { Console.WriteLine("Unable to determine a valid unity version. Aborting."); return; } #endregion LibCpp2IlMain.Settings.AllowManualMetadataAndCodeRegInput = true; if (!LibCpp2IlMain.LoadFromFile(assemblyPath, metadataPath, unityVerUseful)) { Console.WriteLine("Initialization with LibCpp2IL failed."); return; } Console.WriteLine(LibCpp2IlReflection.GetType("String", "System").DeclaringAssembly.Name); //Dump DLLs #region Assembly Generation var resolver = new RegistryAssemblyResolver(); var moduleParams = new ModuleParameters { Kind = ModuleKind.Dll, AssemblyResolver = resolver, MetadataResolver = new MetadataResolver(resolver) }; Console.WriteLine("Building assemblies..."); Console.WriteLine("\tPass 1: Creating empty types..."); Assemblies = AssemblyBuilder.CreateAssemblies(LibCpp2IlMain.TheMetadata !, resolver, moduleParams); Utils.BuildPrimitiveMappings(); Console.WriteLine("\tPass 2: Setting parents and handling inheritance..."); //Stateful method, no return value AssemblyBuilder.ConfigureHierarchy(LibCpp2IlMain.TheMetadata, LibCpp2IlMain.ThePe !); Console.WriteLine("\tPass 3: Populating types..."); var methods = new List <(TypeDefinition type, List <CppMethodData> methods)>(); //Create out dirs if needed var outputPath = Path.GetFullPath("cpp2il_out"); if (!Directory.Exists(outputPath)) { Directory.CreateDirectory(outputPath); } var methodOutputDir = Path.Combine(outputPath, "types"); if (!(CommandLineOptions.SkipAnalysis && CommandLineOptions.SkipMetadataTextFiles) && !Directory.Exists(methodOutputDir)) { Directory.CreateDirectory(methodOutputDir); } for (var imageIndex = 0; imageIndex < LibCpp2IlMain.TheMetadata.assemblyDefinitions.Length; imageIndex++) { var imageDef = LibCpp2IlMain.TheMetadata.assemblyDefinitions[imageIndex]; Console.WriteLine($"\t\tPopulating {imageDef.typeCount} types in assembly {imageIndex + 1} of {LibCpp2IlMain.TheMetadata.assemblyDefinitions.Length}: {imageDef.Name}..."); var assemblySpecificPath = Path.Combine(methodOutputDir, imageDef.Name.Replace(".dll", "")); if (!(CommandLineOptions.SkipMetadataTextFiles && CommandLineOptions.SkipAnalysis) && !Directory.Exists(assemblySpecificPath)) { Directory.CreateDirectory(assemblySpecificPath); } methods.AddRange(AssemblyBuilder.ProcessAssemblyTypes(LibCpp2IlMain.TheMetadata, LibCpp2IlMain.ThePe, imageDef)); } //Invert dict for CppToMono SharedState.CppToMonoTypeDefs = SharedState.MonoToCppTypeDefs.ToDictionary(i => i.Value, i => i.Key); Console.WriteLine("\tPass 4: Handling SerializeFields..."); //Add serializefield to monobehaviors #region SerializeFields var unityEngineAssembly = Assemblies.Find(x => x.MainModule.Types.Any(t => t.Namespace == "UnityEngine" && t.Name == "SerializeField")); if (unityEngineAssembly != null) { var serializeFieldMethod = unityEngineAssembly.MainModule.Types.First(x => x.Name == "SerializeField").Methods.First(); foreach (var imageDef in LibCpp2IlMain.TheMetadata.assemblyDefinitions) { var lastTypeIndex = imageDef.firstTypeIndex + imageDef.typeCount; for (var typeIndex = imageDef.firstTypeIndex; typeIndex < lastTypeIndex; typeIndex++) { var typeDef = LibCpp2IlMain.TheMetadata.typeDefs[typeIndex]; var typeDefinition = SharedState.TypeDefsByIndex[typeIndex]; //Fields var lastFieldIdx = typeDef.firstFieldIdx + typeDef.field_count; for (var fieldIdx = typeDef.firstFieldIdx; fieldIdx < lastFieldIdx; ++fieldIdx) { var fieldDef = LibCpp2IlMain.TheMetadata.fieldDefs[fieldIdx]; var fieldName = LibCpp2IlMain.TheMetadata.GetStringFromIndex(fieldDef.nameIndex); var fieldDefinition = typeDefinition.Fields.First(x => x.Name == fieldName); //Get attributes and look for the serialize field attribute. var attributeIndex = LibCpp2IlMain.TheMetadata.GetCustomAttributeIndex(imageDef, fieldDef.customAttributeIndex, fieldDef.token); if (attributeIndex < 0) { continue; } var attributeTypeRange = LibCpp2IlMain.TheMetadata.attributeTypeRanges[attributeIndex]; for (var attributeIdxIdx = 0; attributeIdxIdx < attributeTypeRange.count; attributeIdxIdx++) { var attributeTypeIndex = LibCpp2IlMain.TheMetadata.attributeTypes[attributeTypeRange.start + attributeIdxIdx]; var attributeType = LibCpp2IlMain.ThePe.types[attributeTypeIndex]; if (attributeType.type != Il2CppTypeEnum.IL2CPP_TYPE_CLASS) { continue; } var cppAttribType = LibCpp2IlMain.TheMetadata.typeDefs[attributeType.data.classIndex]; var attributeName = LibCpp2IlMain.TheMetadata.GetStringFromIndex(cppAttribType.nameIndex); if (attributeName != "SerializeField") { continue; } var customAttribute = new CustomAttribute(typeDefinition.Module.ImportReference(serializeFieldMethod)); fieldDefinition.CustomAttributes.Add(customAttribute); } } } } } #endregion KeyFunctionAddresses keyFunctionAddresses = null; if (!CommandLineOptions.SkipAnalysis) { Console.WriteLine("\tPass 5: Locating Globals..."); Console.WriteLine($"\t\tFound {LibCpp2IlGlobalMapper.TypeRefs.Count} type globals"); Console.WriteLine($"\t\tFound {LibCpp2IlGlobalMapper.MethodRefs.Count} method globals"); Console.WriteLine($"\t\tFound {LibCpp2IlGlobalMapper.FieldRefs.Count} field globals"); Console.WriteLine($"\t\tFound {LibCpp2IlGlobalMapper.Literals.Count} string literals"); //TODO: Don't do this. Rework everything to use the API surface. SharedState.Globals.AddRange(LibCpp2IlGlobalMapper.TypeRefs); SharedState.Globals.AddRange(LibCpp2IlGlobalMapper.MethodRefs); SharedState.Globals.AddRange(LibCpp2IlGlobalMapper.FieldRefs); SharedState.Globals.AddRange(LibCpp2IlGlobalMapper.Literals); foreach (var globalIdentifier in SharedState.Globals) { SharedState.GlobalsByOffset[globalIdentifier.Offset] = globalIdentifier; } Console.WriteLine("\tPass 6: Looking for key functions..."); //This part involves decompiling known functions to search for other function calls Disassembler.Translator.IncludeAddress = true; Disassembler.Translator.IncludeBinary = true; keyFunctionAddresses = KeyFunctionAddresses.Find(methods, LibCpp2IlMain.ThePe); } #endregion Console.WriteLine("Saving Header DLLs to " + outputPath + "..."); GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency; foreach (var assembly in Assemblies) { var dllPath = Path.Combine(outputPath, assembly.MainModule.Name); var reference = assembly.MainModule.AssemblyReferences.FirstOrDefault(a => a.Name == "System.Private.CoreLib"); if (reference != null) { assembly.MainModule.AssemblyReferences.Remove(reference); } assembly.Write(dllPath); if (assembly.Name.Name != "Assembly-CSharp" || CommandLineOptions.SkipAnalysis) { continue; } Console.WriteLine("Dumping method bytes to " + methodOutputDir); Directory.CreateDirectory(Path.Combine(methodOutputDir, assembly.Name.Name)); //Write methods var allUsedMnemonics = new List <ud_mnemonic_code>(); var counter = 0; var toProcess = methods.Where(tuple => tuple.type.Module.Assembly == assembly).ToList(); //Sort alphabetically by type. toProcess.Sort((a, b) => String.Compare(a.type.FullName, b.type.FullName, StringComparison.Ordinal)); var thresholds = new[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }.ToList(); var nextThreshold = thresholds.First(); var successfullyProcessed = 0; var failedProcess = 0; var startTime = DateTime.Now; var methodTaintDict = new ConcurrentDictionary <string, AsmDumper.TaintReason>(); thresholds.RemoveAt(0); Action <(TypeDefinition type, List <CppMethodData> methods)> action = tuple => { var(type, methodData) = tuple; counter++; var pct = 100 * ((decimal)counter / toProcess.Count); if (pct > nextThreshold) { lock (thresholds) { //Check again to prevent races if (pct > nextThreshold) { var elapsedSoFar = DateTime.Now - startTime; var rate = counter / elapsedSoFar.TotalSeconds; var remaining = toProcess.Count - counter; Console.WriteLine($"{nextThreshold}% ({counter} classes in {Math.Round(elapsedSoFar.TotalSeconds)} sec, ~{Math.Round(rate)} classes / sec, {remaining} classes remaining, approx {Math.Round(remaining / rate + 5)} sec remaining)"); nextThreshold = thresholds.First(); thresholds.RemoveAt(0); } } } // Console.WriteLine($"\t-Dumping methods in type {counter}/{methodBytes.Count}: {type.Key}"); try { var filename = Path.Combine(methodOutputDir, assembly.Name.Name, type.Name.Replace("<", "_").Replace(">", "_").Replace("|", "_") + "_methods.txt"); var typeDump = new StringBuilder("Type: " + type.Name + "\n\n"); foreach (var method in methodData) { var methodStart = method.MethodOffsetRam; if (methodStart == 0) { continue; } var methodDefinition = SharedState.MethodsByIndex[method.MethodId]; var taintResult = new AsmDumper(methodDefinition, method, methodStart, keyFunctionAddresses !, LibCpp2IlMain.ThePe) .AnalyzeMethod(typeDump, ref allUsedMnemonics); var key = new StringBuilder(); key.Append(methodDefinition.DeclaringType.FullName).Append("::").Append(methodDefinition.Name); methodDefinition.MethodSignatureFullName(key); methodTaintDict[key.ToString()] = taintResult; if (taintResult != AsmDumper.TaintReason.UNTAINTED) { Interlocked.Increment(ref failedProcess); } else { Interlocked.Increment(ref successfullyProcessed); } } lock (type) File.WriteAllText(filename, typeDump.ToString()); } catch (Exception e) { Console.WriteLine("Failed to dump methods for type " + type.Name + " " + e); } }; var parallel = false; if (parallel) { toProcess.AsParallel().ForAll(action); } else { toProcess.ForEach(action); } var total = successfullyProcessed + failedProcess; var elapsed = DateTime.Now - startTime; Console.WriteLine($"Finished method processing in {elapsed.Ticks} ticks (about {Math.Round(elapsed.TotalSeconds, 1)} seconds), at an overall rate of about {Math.Round(toProcess.Count / elapsed.TotalSeconds)} methods/sec"); Console.WriteLine($"Processed {total} methods, {successfullyProcessed} ({Math.Round(successfullyProcessed * 100.0 / total, 2)}%) successfully, {failedProcess} ({Math.Round(failedProcess * 100.0 / total, 2)}%) with errors."); Console.WriteLine("Breakdown By Taint Reason:"); foreach (var reason in Enum.GetValues(typeof(AsmDumper.TaintReason))) { var count = (decimal)methodTaintDict.Values.Count(v => v == (AsmDumper.TaintReason)reason); Console.WriteLine($"{reason}: {count} (about {Math.Round(count * 100 / total, 1)}%)"); } var summary = new StringBuilder(); foreach (var keyValuePair in methodTaintDict) { summary.Append("\t") .Append(keyValuePair.Key) .Append(Utils.Repeat(" ", 250 - keyValuePair.Key.Length)) .Append(keyValuePair.Value) .Append(" (") .Append((int)keyValuePair.Value) .Append(")") .Append("\n"); } #if DUMP_PACKAGE_SUCCESS_DATA Console.WriteLine("By Package:"); var keys = methodTaintDict .Select(kvp => kvp.Key) .GroupBy( GetPackageName, className => className, (packageName, classEnumerable) => new { package = packageName, classes = classEnumerable.ToList() }) .ToList(); foreach (var key in keys) { var resultLine = new StringBuilder(); var totalClassCount = key.classes.Count; resultLine.Append($"\tIn package {key.package} ({totalClassCount} classes): "); foreach (var reason in Enum.GetValues(typeof(AsmDumper.TaintReason))) { var count = (decimal)methodTaintDict.Where(kvp => key.classes.Contains(kvp.Key)).Count(v => v.Value == (AsmDumper.TaintReason)reason); resultLine.Append(reason).Append(":").Append(count).Append($" ({Math.Round(count * 100 / totalClassCount, 1)}%) "); } Console.WriteLine(resultLine.ToString()); } #endif File.WriteAllText(Path.Combine(outputPath, "method_statuses.txt"), summary.ToString()); Console.WriteLine($"Wrote file: {Path.Combine(outputPath, "method_statuses.txt")}"); // Console.WriteLine("Assembly uses " + allUsedMnemonics.Count + " mnemonics"); } // Console.WriteLine("[Finished. Press enter to exit]"); // Console.ReadLine(); }
public static int Main(string[] args) { Console.WriteLine("===Cpp2IL by Samboy063==="); Console.WriteLine("A Tool to Reverse Unity's \"il2cpp\" Build Process."); Console.WriteLine("Running on " + Environment.OSVersion.Platform); #region Command Line Parsing CommandLineOptions = null; Parser.Default.ParseArguments <Options>(args).WithParsed(options => { CommandLineOptions = options; }); if (CommandLineOptions == null) { Console.WriteLine("Invalid command line. Exiting."); return(1); } if (!CheckForceOptionsAreValid()) { Console.WriteLine("Read the help."); return(1); } int[] unityVerUseful; string assemblyPath; string metadataPath; if (CommandLineOptions.ForcedBinaryPath == null) { var baseGamePath = CommandLineOptions.GamePath; Console.WriteLine("Using path: " + baseGamePath); if (!Directory.Exists(baseGamePath)) { Console.WriteLine("Specified game-path does not exist: " + baseGamePath); return(2); } assemblyPath = Path.Combine(baseGamePath, "GameAssembly.dll"); var exeName = Path.GetFileNameWithoutExtension(Directory.GetFiles(baseGamePath) .First(f => f.EndsWith(".exe") && !BlacklistedExecutableFilenames.Any(bl => f.EndsWith(bl)))); if (CommandLineOptions.ExeName != null) { exeName = CommandLineOptions.ExeName; Console.WriteLine($"Using OVERRIDDEN game name: {exeName}"); } else { Console.WriteLine($"Auto-detected game name: {exeName}"); } var unityPlayerPath = Path.Combine(baseGamePath, $"{exeName}.exe"); metadataPath = Path.Combine(baseGamePath, $"{exeName}_Data", "il2cpp_data", "Metadata", "global-metadata.dat"); if (!File.Exists(assemblyPath) || !File.Exists(unityPlayerPath) || !File.Exists(metadataPath)) { Console.WriteLine("Invalid game-path or exe-name specified. Failed to find one of the following:\n" + $"\t{assemblyPath}\n" + $"\t{unityPlayerPath}\n" + $"\t{metadataPath}\n"); return(2); } #endregion Console.WriteLine($"Located game EXE: {unityPlayerPath}"); Console.WriteLine($"Located global-metadata: {metadataPath}"); #region Unity Version Determination Console.WriteLine("\nAttempting to determine Unity version..."); if (Environment.OSVersion.Platform == PlatformID.Win32NT) { var unityVer = FileVersionInfo.GetVersionInfo(unityPlayerPath); unityVerUseful = new[] { unityVer.FileMajorPart, unityVer.FileMinorPart, unityVer.FileBuildPart }; } else { //Globalgamemanagers var globalgamemanagersPath = Path.Combine(baseGamePath, $"{exeName}_Data", "globalgamemanagers"); var ggmBytes = File.ReadAllBytes(globalgamemanagersPath); var verString = new StringBuilder(); var idx = 0x14; while (ggmBytes[idx] != 0) { verString.Append(Convert.ToChar(ggmBytes[idx])); idx++; } var unityVer = verString.ToString(); unityVer = unityVer.Substring(0, unityVer.IndexOf("f", StringComparison.Ordinal)); Console.WriteLine("Read version string from globalgamemanagers: " + unityVer); unityVerUseful = unityVer.Split(".").Select(int.Parse).ToArray(); } Console.WriteLine("This game is built with Unity version " + string.Join(".", unityVerUseful)); if (unityVerUseful[0] <= 4) { Console.WriteLine("Unable to determine a valid unity version. Aborting."); return(1); } #endregion } else { Console.WriteLine("Warning: Using force options, I sure hope you know what you're doing!"); assemblyPath = CommandLineOptions.ForcedBinaryPath !; metadataPath = CommandLineOptions.ForcedMetadataPath !; unityVerUseful = CommandLineOptions.ForcedUnityVersion !.Split('.').Select(int.Parse).ToArray(); } //Set this flag from command line options LibCpp2IlMain.Settings.AllowManualMetadataAndCodeRegInput = !CommandLineOptions.DisableRegistrationPrompts; //Disable Method Ptr Mapping and Global Resolving if skipping analysis LibCpp2IlMain.Settings.DisableMethodPointerMapping = LibCpp2IlMain.Settings.DisableGlobalResolving = CommandLineOptions.SkipAnalysis; if (!LibCpp2IlMain.LoadFromFile(assemblyPath, metadataPath, unityVerUseful)) { Console.WriteLine("Initialization with LibCpp2IL failed."); return(1); } //Dump DLLs #region Assembly Generation var resolver = new RegistryAssemblyResolver(); var moduleParams = new ModuleParameters { Kind = ModuleKind.Dll, AssemblyResolver = resolver, MetadataResolver = new MetadataResolver(resolver) }; Console.WriteLine("Building assemblies..."); Console.WriteLine("\tPass 1: Creating empty types..."); Assemblies = AssemblyBuilder.CreateAssemblies(LibCpp2IlMain.TheMetadata !, resolver, moduleParams); Utils.BuildPrimitiveMappings(); Console.WriteLine("\tPass 2: Setting parents and handling inheritance..."); //Stateful method, no return value AssemblyBuilder.ConfigureHierarchy(LibCpp2IlMain.TheMetadata, LibCpp2IlMain.ThePe !); Console.WriteLine("\tPass 3: Populating types..."); var methods = new List <(TypeDefinition type, List <CppMethodData> methods)>(); //Create out dirs if needed var outputPath = Path.GetFullPath("cpp2il_out"); if (!Directory.Exists(outputPath)) { Directory.CreateDirectory(outputPath); } var methodOutputDir = Path.Combine(outputPath, "types"); if (!(CommandLineOptions.SkipAnalysis && CommandLineOptions.SkipMetadataTextFiles) && !Directory.Exists(methodOutputDir)) { Directory.CreateDirectory(methodOutputDir); } for (var imageIndex = 0; imageIndex < LibCpp2IlMain.TheMetadata.imageDefinitions.Length; imageIndex++) { var imageDef = LibCpp2IlMain.TheMetadata.imageDefinitions[imageIndex]; Console.WriteLine($"\t\tPopulating {imageDef.typeCount} types in assembly {imageIndex + 1} of {LibCpp2IlMain.TheMetadata.imageDefinitions.Length}: {imageDef.Name}..."); var assemblySpecificPath = Path.Combine(methodOutputDir, imageDef.Name.Replace(".dll", "")); if (!(CommandLineOptions.SkipMetadataTextFiles && CommandLineOptions.SkipAnalysis) && !Directory.Exists(assemblySpecificPath)) { Directory.CreateDirectory(assemblySpecificPath); } methods.AddRange(AssemblyBuilder.ProcessAssemblyTypes(LibCpp2IlMain.TheMetadata, LibCpp2IlMain.ThePe, imageDef)); } //Invert dict for CppToMono SharedState.UnmanagedToManagedTypes = SharedState.MonoToCppTypeDefs.ToDictionary(i => i.Value, i => i.Key); Console.WriteLine("\tPass 4: Applying type, method, and field attributes..."); #region Attributes var unityEngineAssembly = Assemblies.Find(x => x.MainModule.Types.Any(t => t.Namespace == "UnityEngine" && t.Name == "SerializeField")); if (unityEngineAssembly != null) { foreach (var imageDef in LibCpp2IlMain.TheMetadata.imageDefinitions) { //Cache these per-module. var attributeCtorsByClassIndex = new Dictionary <long, MethodReference>(); var lastTypeIndex = imageDef.firstTypeIndex + imageDef.typeCount; for (var typeIndex = imageDef.firstTypeIndex; typeIndex < lastTypeIndex; typeIndex++) { var typeDef = LibCpp2IlMain.TheMetadata.typeDefs[typeIndex]; var typeDefinition = SharedState.UnmanagedToManagedTypes[typeDef !];