From e9dd10614c4f624dcbcb454765d5312d56eca6ac Mon Sep 17 00:00:00 2001 From: MrUser127 Date: Sat, 8 Feb 2025 00:33:42 +0200 Subject: [PATCH] Gui --- Cpp2IL.Core/Utils/MiscUtils.cs | 4 +- Cpp2IL.Core/Utils/PathUtils.cs | 486 +++++++++++++++++++++++++++ Cpp2IL.Gui/App.axaml | 10 + Cpp2IL.Gui/App.axaml.cs | 23 ++ Cpp2IL.Gui/Cpp2IL.Gui.csproj | 30 ++ Cpp2IL.Gui/Program.cs | 21 ++ Cpp2IL.Gui/Views/MainWindow.axaml | 43 +++ Cpp2IL.Gui/Views/MainWindow.axaml.cs | 186 ++++++++++ Cpp2IL.Gui/Views/MessageBox.axaml | 18 + Cpp2IL.Gui/Views/MessageBox.axaml.cs | 16 + Cpp2IL.Gui/app.manifest | 18 + Cpp2IL.sln | 6 + Cpp2IL/Program.cs | 478 +------------------------- 13 files changed, 862 insertions(+), 477 deletions(-) create mode 100644 Cpp2IL.Core/Utils/PathUtils.cs create mode 100644 Cpp2IL.Gui/App.axaml create mode 100644 Cpp2IL.Gui/App.axaml.cs create mode 100644 Cpp2IL.Gui/Cpp2IL.Gui.csproj create mode 100644 Cpp2IL.Gui/Program.cs create mode 100644 Cpp2IL.Gui/Views/MainWindow.axaml create mode 100644 Cpp2IL.Gui/Views/MainWindow.axaml.cs create mode 100644 Cpp2IL.Gui/Views/MessageBox.axaml create mode 100644 Cpp2IL.Gui/Views/MessageBox.axaml.cs create mode 100644 Cpp2IL.Gui/app.manifest diff --git a/Cpp2IL.Core/Utils/MiscUtils.cs b/Cpp2IL.Core/Utils/MiscUtils.cs index 15e0e4c4..65850bee 100644 --- a/Cpp2IL.Core/Utils/MiscUtils.cs +++ b/Cpp2IL.Core/Utils/MiscUtils.cs @@ -264,7 +264,9 @@ bool F2(T t) "install.exe", "launch.exe", "MelonLoader.Installer.exe", - "crashpad_handler.exe" + "crashpad_handler.exe", + "EOSBootstrapper.exe", + "start_protected_game.exe" ]; public static string AnalyzeStackTracePointers(ulong[] pointers) diff --git a/Cpp2IL.Core/Utils/PathUtils.cs b/Cpp2IL.Core/Utils/PathUtils.cs new file mode 100644 index 00000000..b8d23ac7 --- /dev/null +++ b/Cpp2IL.Core/Utils/PathUtils.cs @@ -0,0 +1,486 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using AssetRipper.Primitives; +using Cpp2IL.Core.Logging; + +namespace Cpp2IL.Core.Utils; + +public static class PathUtils +{ + private static readonly List PathsToDeleteOnExit = []; + + public static void CleanupExtractedFiles() + { + foreach (var p in PathsToDeleteOnExit) + { + try + { + Logger.InfoNewline($"Cleaning up {p}..."); + File.Delete(p); + } + catch (Exception) + { + //Ignore + } + } + } + + public static void ResolvePathsFromCommandLine(string? gamePath, string? inputExeName, ref Cpp2IlRuntimeArgs args) + { + if (string.IsNullOrEmpty(gamePath)) + throw new Exception("No force options provided, and no game path was provided either. Please provide a game path or use the --force- options."); + + //Somehow the above doesn't tell .net that gamePath can't be null on net472, so we do this stupid thing to avoid nullable warnings +#if NET472 + gamePath = gamePath!; +#endif + + Logger.VerboseNewline("Beginning path resolution..."); + + if (Directory.Exists(gamePath) && File.Exists(Path.Combine(gamePath, "Contents/Frameworks/GameAssembly.dylib"))) + HandleMacOSGamePath(gamePath, inputExeName, ref args); + else if (Directory.Exists(gamePath) && File.Exists(Path.Combine(gamePath, "GameAssembly.so"))) + HandleLinuxGamePath(gamePath, inputExeName, ref args); + else if (Directory.Exists(gamePath)) + HandleWindowsGamePath(gamePath, inputExeName, ref args); + else if (File.Exists(gamePath) && Path.GetExtension(gamePath).ToLowerInvariant() == ".apk") + HandleSingleApk(gamePath, ref args); + else if (File.Exists(gamePath) && Path.GetExtension(gamePath).ToLowerInvariant() is ".xapk" or ".apkm") + HandleXapk(gamePath, ref args); + else if (File.Exists(gamePath) && Path.GetExtension(gamePath).ToLowerInvariant() is ".ipa" or ".tipa") + HandleIpa(gamePath, ref args); + else + { + if (!Cpp2IlPluginManager.TryProcessGamePath(gamePath, ref args)) + throw new Exception($"Could not find a valid unity game at {gamePath}"); + } + } + + private static void HandleMacOSGamePath(string gamePath, string? inputExeName, ref Cpp2IlRuntimeArgs args) + { + //macOS game. + args.PathToAssembly = Path.Combine(gamePath, "Contents", "Frameworks", "GameAssembly.dylib"); + var exeName = Path.GetFileName(Directory.GetFiles(Path.Combine(gamePath, "Contents", "MacOS")) + .FirstOrDefault(f => MiscUtils.BlacklistedExecutableFilenames.Any(f.EndsWith))); + + exeName = inputExeName ?? exeName; + + Logger.VerboseNewline($"Trying HandleMacOSGamePath as provided bundle contains GameAssembly.dylib, potential GA is {args.PathToAssembly} and executable {exeName}"); + + if (exeName == null) + throw new Exception("Failed to locate any executable in the provided game directory. Make sure the path is correct, and if you *really* know what you're doing (and know it's not supported), use the force options, documented if you provide --help."); + + var unityPlayerPath = Path.Combine(gamePath, "Contents", "MacOS", exeName); + var gameDataPath = Path.Combine(gamePath, "Contents", "Resources", "Data"); + args.PathToMetadata = Path.Combine(gameDataPath, "il2cpp_data", "Metadata", "global-metadata.dat"); + + if (!File.Exists(args.PathToAssembly) || !File.Exists(unityPlayerPath) || !File.Exists(args.PathToMetadata)) + throw new Exception("Invalid game-path or exe-name specified. Failed to find one of the following:\n" + + $"\t{args.PathToAssembly}\n" + + $"\t{unityPlayerPath}\n" + + $"\t{args.PathToMetadata}\n"); + + Logger.VerboseNewline($"Found probable macOS game at path: {gamePath}. Attempting to get unity version..."); + + var uv = Cpp2IlApi.DetermineUnityVersion(unityPlayerPath, gameDataPath); + Logger.VerboseNewline($"First-attempt unity version detection gave: {uv}"); + + if (uv == default) + { + Logger.Warn("Could not determine unity version, probably due to not running on windows and not having any assets files to determine it from. Enter unity version, if known, in the format of (xxxx.x.x), else nothing to fail: "); + var userInputUv = Console.ReadLine(); + + if (!string.IsNullOrEmpty(userInputUv)) + uv = UnityVersion.Parse(userInputUv); + + if (uv == default) + throw new Exception("Failed to determine unity version. If you're not running on windows, I need a globalgamemanagers file or a data.unity3d file, or you need to use the force options."); + } + + args.UnityVersion = uv; + + if (args.UnityVersion.Major < 4) + { + Logger.WarnNewline($"Fail once: Unity version of provided executable is {args.UnityVersion}. This is probably not the correct version. Retrying with alternative method..."); + + var readUnityVersionFrom = Path.Combine(gameDataPath, "globalgamemanagers"); + if (File.Exists(readUnityVersionFrom)) + args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(File.ReadAllBytes(readUnityVersionFrom)); + else + { + readUnityVersionFrom = Path.Combine(gameDataPath, "data.unity3d"); + using var stream = File.OpenRead(readUnityVersionFrom); + + args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(stream); + } + } + + Logger.InfoNewline($"Determined game's unity version to be {args.UnityVersion}"); + + if (args.UnityVersion.Major <= 4) + throw new Exception($"Unable to determine a valid unity version (got {args.UnityVersion})"); + + args.Valid = true; + } + + private static void HandleLinuxGamePath(string gamePath, string? inputExeName, ref Cpp2IlRuntimeArgs args) + { + //Linux game. + args.PathToAssembly = Path.Combine(gamePath, "GameAssembly.so"); + var exeName = Path.GetFileName(Directory.GetFiles(gamePath) + .FirstOrDefault(f => + (f.EndsWith(".x86_64") || f.EndsWith(".x86")) && + !MiscUtils.BlacklistedExecutableFilenames.Any(f.EndsWith))); + + exeName = inputExeName ?? exeName; + + Logger.VerboseNewline($"Trying HandleLinuxGamePath as provided directory contains a GameAssembly.so, potential GA is {args.PathToAssembly} and executable {exeName}"); + + if (exeName == null) + throw new Exception("Failed to locate any executable in the provided game directory. Make sure the path is correct, and if you *really* know what you're doing (and know it's not supported), use the force options, documented if you provide --help."); + + var exeNameNoExt = exeName.Replace(".x86_64", "").Replace(".x86", ""); + + var unityPlayerPath = Path.Combine(gamePath, exeName); + args.PathToMetadata = Path.Combine(gamePath, $"{exeNameNoExt}_Data", "il2cpp_data", "Metadata", "global-metadata.dat"); + + if (!File.Exists(args.PathToAssembly) || !File.Exists(unityPlayerPath) || !File.Exists(args.PathToMetadata)) + throw new Exception("Invalid game-path or exe-name specified. Failed to find one of the following:\n" + + $"\t{args.PathToAssembly}\n" + + $"\t{unityPlayerPath}\n" + + $"\t{args.PathToMetadata}\n"); + + Logger.VerboseNewline($"Found probable linux game at path: {gamePath}. Attempting to get unity version..."); + var gameDataPath = Path.Combine(gamePath, $"{exeNameNoExt}_Data"); + var uv = Cpp2IlApi.DetermineUnityVersion(unityPlayerPath, gameDataPath); + Logger.VerboseNewline($"First-attempt unity version detection gave: {uv}"); + + if (uv == default) + { + Logger.Warn("Could not determine unity version, probably due to not running on windows and not having any assets files to determine it from. Enter unity version, if known, in the format of (xxxx.x.x), else nothing to fail: "); + var userInputUv = Console.ReadLine(); + + if (!string.IsNullOrEmpty(userInputUv)) + uv = UnityVersion.Parse(userInputUv); + + if (uv == default) + throw new Exception("Failed to determine unity version. If you're not running on windows, I need a globalgamemanagers file or a data.unity3d file, or you need to use the force options."); + } + + args.UnityVersion = uv; + + if (args.UnityVersion.Major < 4) + { + Logger.WarnNewline($"Fail once: Unity version of provided executable is {args.UnityVersion}. This is probably not the correct version. Retrying with alternative method..."); + + var readUnityVersionFrom = Path.Combine(gameDataPath, "globalgamemanagers"); + if (File.Exists(readUnityVersionFrom)) + args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(File.ReadAllBytes(readUnityVersionFrom)); + else + { + readUnityVersionFrom = Path.Combine(gameDataPath, "data.unity3d"); + using var stream = File.OpenRead(readUnityVersionFrom); + + args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(stream); + } + } + + Logger.InfoNewline($"Determined game's unity version to be {args.UnityVersion}"); + + if (args.UnityVersion.Major <= 4) + throw new Exception($"Unable to determine a valid unity version (got {args.UnityVersion})"); + + args.Valid = true; + } + + private static void HandleWindowsGamePath(string gamePath, string? inputExeName, ref Cpp2IlRuntimeArgs args) + { + //Windows game. + args.PathToAssembly = Path.Combine(gamePath, "GameAssembly.dll"); + var exeName = Path.GetFileNameWithoutExtension(Directory.GetFiles(gamePath) + .FirstOrDefault(f => f.EndsWith(".exe") && !MiscUtils.BlacklistedExecutableFilenames.Any(f.EndsWith))); + + exeName = inputExeName ?? exeName; + + Logger.VerboseNewline($"Trying HandleWindowsGamePath as provided path is a directory with no GameAssembly.so, potential GA is {args.PathToAssembly} and executable {exeName}"); + + if (exeName == null) + throw new Exception("Failed to locate any executable in the provided game directory. Make sure the path is correct, and if you *really* know what you're doing (and know it's not supported), use the force options, documented if you provide --help."); + + var unityPlayerPath = Path.Combine(gamePath, $"{exeName}.exe"); + args.PathToMetadata = Path.Combine(gamePath, $"{exeName}_Data", "il2cpp_data", "Metadata", "global-metadata.dat"); + + if (!File.Exists(args.PathToAssembly) || !File.Exists(unityPlayerPath) || !File.Exists(args.PathToMetadata)) + throw new Exception("Invalid game-path or exe-name specified. Failed to find one of the following:\n" + + $"\t{args.PathToAssembly}\n" + + $"\t{unityPlayerPath}\n" + + $"\t{args.PathToMetadata}\n"); + + Logger.VerboseNewline($"Found probable windows game at path: {gamePath}. Attempting to get unity version..."); + var gameDataPath = Path.Combine(gamePath, $"{exeName}_Data"); + var uv = Cpp2IlApi.DetermineUnityVersion(unityPlayerPath, gameDataPath); + Logger.VerboseNewline($"First-attempt unity version detection gave: {uv}"); + + if (uv == default) + { + Logger.Warn("Could not determine unity version, probably due to not running on windows and not having any assets files to determine it from. Enter unity version, if known, in the format of (xxxx.x.x), else nothing to fail: "); + var userInputUv = Console.ReadLine(); + + if (!string.IsNullOrEmpty(userInputUv)) + uv = UnityVersion.Parse(userInputUv); + + if (uv == default) + throw new Exception("Failed to determine unity version. If you're not running on windows, I need a globalgamemanagers file or a data.unity3d file, or you need to use the force options."); + } + + args.UnityVersion = uv; + + if (args.UnityVersion.Major < 4) + { + Logger.WarnNewline($"Fail once: Unity version of provided executable is {args.UnityVersion}. This is probably not the correct version. Retrying with alternative method..."); + + var readUnityVersionFrom = Path.Combine(gameDataPath, "globalgamemanagers"); + if (File.Exists(readUnityVersionFrom)) + args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(File.ReadAllBytes(readUnityVersionFrom)); + else + { + readUnityVersionFrom = Path.Combine(gameDataPath, "data.unity3d"); + using var stream = File.OpenRead(readUnityVersionFrom); + + args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(stream); + } + } + + Logger.InfoNewline($"Determined game's unity version to be {args.UnityVersion}"); + + if (args.UnityVersion.Major <= 4) + throw new Exception($"Unable to determine a valid unity version (got {args.UnityVersion})"); + + args.Valid = true; + } + + private static void HandleSingleApk(string gamePath, ref Cpp2IlRuntimeArgs args) + { + //APK + //Metadata: assets/bin/Data/Managed/Metadata + //Binary: lib/(armeabi-v7a)|(arm64-v8a)/libil2cpp.so + + Logger.VerboseNewline("Trying HandleSingleApk as provided path is an apk file"); + + Logger.InfoNewline($"Attempting to extract required files from APK {gamePath}", "APK"); + + using var stream = File.OpenRead(gamePath); + using var zipArchive = new ZipArchive(stream); + + var globalMetadata = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("assets/bin/Data/Managed/Metadata/global-metadata.dat")); + var binary = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("lib/x86_64/libil2cpp.so")); + binary ??= zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("lib/x86/libil2cpp.so")); + binary ??= zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("lib/arm64-v8a/libil2cpp.so")); + binary ??= zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("lib/armeabi-v7a/libil2cpp.so")); + + var globalgamemanagers = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("assets/bin/Data/globalgamemanagers")); + var dataUnity3d = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("assets/bin/Data/data.unity3d")); + + if (binary == null) + throw new Exception("Could not find libil2cpp.so inside the apk."); + if (globalMetadata == null) + throw new Exception("Could not find global-metadata.dat inside the apk"); + if (globalgamemanagers == null && dataUnity3d == null) + throw new Exception("Could not find globalgamemanagers or data.unity3d inside the apk"); + + var tempFileBinary = Path.GetTempFileName(); + var tempFileMeta = Path.GetTempFileName(); + + PathsToDeleteOnExit.Add(tempFileBinary); + PathsToDeleteOnExit.Add(tempFileMeta); + + Logger.InfoNewline($"Extracting APK/{binary.FullName} to {tempFileBinary}", "APK"); + binary.ExtractToFile(tempFileBinary, true); + Logger.InfoNewline($"Extracting APK/{globalMetadata.FullName} to {tempFileMeta}", "APK"); + globalMetadata.ExtractToFile(tempFileMeta, true); + + args.PathToAssembly = tempFileBinary; + args.PathToMetadata = tempFileMeta; + + if (globalgamemanagers != null) + { + Logger.InfoNewline("Reading globalgamemanagers to determine unity version...", "APK"); + var ggmBytes = new byte[0x40]; + using var ggmStream = globalgamemanagers.Open(); + + // ReSharper disable once MustUseReturnValue + ggmStream.Read(ggmBytes, 0, 0x40); + + args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(ggmBytes); + } + else + { + Logger.InfoNewline("Reading data.unity3d to determine unity version...", "APK"); + using var du3dStream = dataUnity3d!.Open(); + + args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(du3dStream); + } + + Logger.InfoNewline($"Determined game's unity version to be {args.UnityVersion}", "APK"); + + args.Valid = true; + } + + private static void HandleXapk(string gamePath, ref Cpp2IlRuntimeArgs args) + { + //XAPK file + //Contains two APKs - one starting with `config.` and one with the package name + //The config one is architecture-specific and so contains the binary + //The other contains the metadata + + Logger.VerboseNewline("Trying HandleXapk as provided path is an xapk or apkm file"); + + Logger.InfoNewline($"Attempting to extract required files from XAPK {gamePath}", "XAPK"); + + using var xapkStream = File.OpenRead(gamePath); + using var xapkZip = new ZipArchive(xapkStream); + + ZipArchiveEntry? configApk = null; + var configApks = xapkZip.Entries.Where(e => e.FullName.Contains("config.") && e.FullName.EndsWith(".apk")).ToList(); + + var instructionSetPreference = new[] { "arm64_v8a", "arm64", "armeabi_v7a", "arm" }; + foreach (var instructionSet in instructionSetPreference) + { + configApk = configApks.FirstOrDefault(e => e.FullName.Contains(instructionSet)); + if (configApk != null) + break; + } + + //Try for base.apk, else find any apk that isn't the config apk + var mainApk = xapkZip.Entries.FirstOrDefault(e => e.FullName.EndsWith(".apk") && e.FullName.Contains("base.apk")) + ?? xapkZip.Entries.FirstOrDefault(e => e != configApk && e.FullName.EndsWith(".apk")); + + Logger.InfoNewline($"Identified APKs inside XAPK - config: {configApk?.FullName}, mainPackage: {mainApk?.FullName}", "XAPK"); + + if (configApk == null) + throw new Exception("Could not find a config apk inside the XAPK"); + if (mainApk == null) + throw new Exception("Could not find a main apk inside the XAPK"); + + using var configZip = new ZipArchive(configApk.Open()); + using var mainZip = new ZipArchive(mainApk.Open()); + var binary = configZip.Entries.FirstOrDefault(e => e.FullName.EndsWith("libil2cpp.so")); + var globalMetadata = mainZip.Entries.FirstOrDefault(e => e.FullName.EndsWith("global-metadata.dat")); + + var globalgamemanagers = mainZip.Entries.FirstOrDefault(e => e.FullName.EndsWith("globalgamemanagers")); + var dataUnity3d = mainZip.Entries.FirstOrDefault(e => e.FullName.EndsWith("data.unity3d")); + + if (binary == null) + throw new Exception("Could not find libil2cpp.so inside the config APK"); + if (globalMetadata == null) + throw new Exception("Could not find global-metadata.dat inside the main APK"); + if (globalgamemanagers == null && dataUnity3d == null) + throw new Exception("Could not find globalgamemanagers or data.unity3d inside the main APK"); + + var tempFileBinary = Path.GetTempFileName(); + var tempFileMeta = Path.GetTempFileName(); + + PathsToDeleteOnExit.Add(tempFileBinary); + PathsToDeleteOnExit.Add(tempFileMeta); + + Logger.InfoNewline($"Extracting XAPK/{configApk.Name}/{binary.FullName} to {tempFileBinary}", "XAPK"); + binary.ExtractToFile(tempFileBinary, true); + Logger.InfoNewline($"Extracting XAPK{mainApk.Name}/{globalMetadata.FullName} to {tempFileMeta}", "XAPK"); + globalMetadata.ExtractToFile(tempFileMeta, true); + + args.PathToAssembly = tempFileBinary; + args.PathToMetadata = tempFileMeta; + + if (globalgamemanagers != null) + { + Logger.InfoNewline("Reading globalgamemanagers to determine unity version...", "XAPK"); + var ggmBytes = new byte[0x40]; + using var ggmStream = globalgamemanagers.Open(); + + // ReSharper disable once MustUseReturnValue + ggmStream.Read(ggmBytes, 0, 0x40); + + args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(ggmBytes); + } + else + { + Logger.InfoNewline("Reading data.unity3d to determine unity version...", "XAPK"); + using var du3dStream = dataUnity3d!.Open(); + + args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(du3dStream); + } + + Logger.InfoNewline($"Determined game's unity version to be {args.UnityVersion}", "XAPK"); + + args.Valid = true; + } + + private static void HandleIpa(string gamePath, ref Cpp2IlRuntimeArgs args) + { + //IPA + //Metadata: Payload/AppName.app/Data/Managed/Metadata/global-metadata.dat + //Binary: Payload/AppName.app/Frameworks/UnityFramework.framework/UnityFramework + //GlobalGameManager: Payload/AppName.app/Data/globalgamemanagers + //Unity3d: Payload/AppName.app/Data/data.unity3d + + Logger.VerboseNewline("Trying HandleIpa as provided path is an ipa or tipa file"); + + Logger.InfoNewline($"Attempting to extract required files from IPA {gamePath}", "IPA"); + + using var stream = File.OpenRead(gamePath); + using var zipArchive = new ZipArchive(stream); + + var globalMetadata = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("Data/Managed/Metadata/global-metadata.dat")); + var binary = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("Frameworks/UnityFramework.framework/UnityFramework")); + + var globalgamemanagers = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("Data/globalgamemanagers")); + var dataUnity3d = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("Data/data.unity3d")); + + if (binary == null) + throw new Exception("Could not find UnityFramework inside the ipa."); + if (globalMetadata == null) + throw new Exception("Could not find global-metadata.dat inside the ipa."); + if (globalgamemanagers == null && dataUnity3d == null) + throw new Exception("Could not find globalgamemanagers or unity3d inside the ipa."); + + var tempFileBinary = Path.GetTempFileName(); + var tempFileMeta = Path.GetTempFileName(); + + PathsToDeleteOnExit.Add(tempFileBinary); + PathsToDeleteOnExit.Add(tempFileMeta); + + Logger.InfoNewline($"Extracting IPA/{binary.FullName} to {tempFileBinary}", "IPA"); + binary.ExtractToFile(tempFileBinary, true); + Logger.InfoNewline($"Extracting IPA/{globalMetadata.FullName} to {tempFileMeta}", "IPA"); + globalMetadata.ExtractToFile(tempFileMeta, true); + + args.PathToAssembly = tempFileBinary; + args.PathToMetadata = tempFileMeta; + + if (globalgamemanagers != null) + { + Logger.InfoNewline("Reading globalgamemanagers to determine unity version...", "IPA"); + var ggmBytes = new byte[0x40]; + using var ggmStream = globalgamemanagers.Open(); + + // ReSharper disable once MustUseReturnValue + ggmStream.Read(ggmBytes, 0, 0x40); + + args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(ggmBytes); + } + else + { + Logger.InfoNewline("Reading data.unity3d to determine unity version...", "IPA"); + using var du3dStream = dataUnity3d!.Open(); + + args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(du3dStream); + } + + Logger.InfoNewline($"Determined game's unity version to be {args.UnityVersion}", "IPA"); + + args.Valid = true; + } +} diff --git a/Cpp2IL.Gui/App.axaml b/Cpp2IL.Gui/App.axaml new file mode 100644 index 00000000..846261de --- /dev/null +++ b/Cpp2IL.Gui/App.axaml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/Cpp2IL.Gui/App.axaml.cs b/Cpp2IL.Gui/App.axaml.cs new file mode 100644 index 00000000..b8d39828 --- /dev/null +++ b/Cpp2IL.Gui/App.axaml.cs @@ -0,0 +1,23 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; + +namespace Cpp2IL.Gui; + +public partial class App : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + } +} diff --git a/Cpp2IL.Gui/Cpp2IL.Gui.csproj b/Cpp2IL.Gui/Cpp2IL.Gui.csproj new file mode 100644 index 00000000..d59a4436 --- /dev/null +++ b/Cpp2IL.Gui/Cpp2IL.Gui.csproj @@ -0,0 +1,30 @@ + + + WinExe + net8.0 + enable + true + app.manifest + true + + + + + + + + + + + + + + + + + + MainWindow.axaml + Code + + + diff --git a/Cpp2IL.Gui/Program.cs b/Cpp2IL.Gui/Program.cs new file mode 100644 index 00000000..b7178480 --- /dev/null +++ b/Cpp2IL.Gui/Program.cs @@ -0,0 +1,21 @@ +using Avalonia; +using System; + +namespace Cpp2IL.Gui; + +class Program +{ + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace(); +} diff --git a/Cpp2IL.Gui/Views/MainWindow.axaml b/Cpp2IL.Gui/Views/MainWindow.axaml new file mode 100644 index 00000000..d6f1253b --- /dev/null +++ b/Cpp2IL.Gui/Views/MainWindow.axaml @@ -0,0 +1,43 @@ + + + + + + Cpp2IL + Select game + + + + + + + + + + Output format + + + + + + + + + + diff --git a/Cpp2IL.Gui/Views/MainWindow.axaml.cs b/Cpp2IL.Gui/Views/MainWindow.axaml.cs new file mode 100644 index 00000000..2bb55a17 --- /dev/null +++ b/Cpp2IL.Gui/Views/MainWindow.axaml.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime; +using System.Threading.Tasks; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Platform.Storage; +using Cpp2IL.Core; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.Extensions; +using Cpp2IL.Core.Logging; +using Cpp2IL.Core.Utils; + +namespace Cpp2IL.Gui; + +public partial class MainWindow : Window +{ + private Cpp2IlRuntimeArgs _runtimeArgs; + + public MainWindow() + { + InitializeComponent(); + + _runtimeArgs = new Cpp2IlRuntimeArgs(); + + Cpp2IlApi.Init(); + + foreach (var outputFormat in OutputFormatRegistry.AllOutputFormats) + { + var comboBoxItem = new ComboBoxItem { Content = outputFormat.OutputFormatName }; + OutputFormat.Items.Add(comboBoxItem); + } + + OutputFormat.SelectedIndex = 0; + } + + private void Run(object? sender, RoutedEventArgs e) + { + Cpp2IlApi.ConfigureLib(false); + Cpp2IlApi.RuntimeOptions = _runtimeArgs; + _runtimeArgs.OutputFormat?.OnOutputFormatSelected(); + GCSettings.LatencyMode = + _runtimeArgs.LowMemoryMode ? GCLatencyMode.Interactive : GCLatencyMode.SustainedLowLatency; + Cpp2IlApi.InitializeLibCpp2Il(_runtimeArgs.PathToAssembly, _runtimeArgs.PathToMetadata, + _runtimeArgs.UnityVersion); + + if (_runtimeArgs.LowMemoryMode) + GC.Collect(); + + foreach (var (key, value) in _runtimeArgs.ProcessingLayerConfigurationOptions) + Cpp2IlApi.CurrentAppContext.PutExtraData(key, value); + + var layers = _runtimeArgs.ProcessingLayersToRun.Clone(); + RunProcessingLayers(_runtimeArgs, + processingLayer => processingLayer.PreProcess(Cpp2IlApi.CurrentAppContext, layers)); + _runtimeArgs.ProcessingLayersToRun = layers; + + RunProcessingLayers(_runtimeArgs, processingLayer => processingLayer.Process(Cpp2IlApi.CurrentAppContext)); + + if (_runtimeArgs.LowMemoryMode) + GC.Collect(); + + _runtimeArgs.OutputFormat.DoOutput(Cpp2IlApi.CurrentAppContext, _runtimeArgs.OutputRootDirectory); + + PathUtils.CleanupExtractedFiles(); + Cpp2IlPluginManager.CallOnFinish(); + } + + private static void RunProcessingLayers(Cpp2IlRuntimeArgs runtimeArgs, Action run) + { + foreach (var processingLayer in runtimeArgs.ProcessingLayersToRun) + { +#if !DEBUG + try + { +#endif + run(processingLayer); +#if !DEBUG + } + catch (Exception e) + { + Logger.ErrorNewline($"Processing layer {processingLayer.Id} threw an exception: {e}"); + Environment.Exit(1); + } +#endif + + if (runtimeArgs.LowMemoryMode) + GC.Collect(); + } + } + + private async void ShowOutputFolderBrowser(object? sender, RoutedEventArgs e) + { + var path = await ShowFileOrFolderPicker(true, "Select output folder"); + + if (string.IsNullOrEmpty(path)) + return; + + _runtimeArgs.OutputRootDirectory = path; + } + + private void SetOutputFormat(object? sender, AvaloniaPropertyChangedEventArgs avaloniaPropertyChangedEventArgs) + { + if (OutputFormat == null || OutputFormat.SelectedIndex < 0) + return; + + _runtimeArgs.OutputFormat = OutputFormatRegistry.AllOutputFormats[OutputFormat.SelectedIndex]; + } + + private async void ShowFileBrowser(object? sender, RoutedEventArgs e) + { + var path = await ShowFileOrFolderPicker(false, "Select game"); + if (string.IsNullOrEmpty(path)) + return; + + try + { + PathUtils.ResolvePathsFromCommandLine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path), + ref _runtimeArgs); + } + catch (Exception exception) + { + Logger.ErrorNewline(exception.ToString()); + ShowPathResolveError(exception); + return; + } + + ShowSettingsTab(); + } + + private async void ShowFolderBrowser(object? sender, RoutedEventArgs e) + { + var path = await ShowFileOrFolderPicker(true, "Select game"); + if (string.IsNullOrEmpty(path)) + return; + try + { + PathUtils.ResolvePathsFromCommandLine(path, null, ref _runtimeArgs); + } + catch (Exception exception) + { + Logger.ErrorNewline(exception.ToString()); + ShowPathResolveError(exception); + return; + } + + ShowSettingsTab(); + } + + private void ShowSettingsTab() => Tabs.SelectedIndex = 1; + + private void ShowPathResolveError(Exception exception) + { + new MessageBox("Failed to load game! Try selecting exe instead of folder.", exception.ToString()) + .ShowDialog(this); + } + + private async Task ShowFileOrFolderPicker(bool folderPicker, string name) + { + var topLevel = GetTopLevel(this); + if (topLevel == null) + throw new Exception("GetTopLevel(this) returned null"); + + IReadOnlyList item; + + if (folderPicker) + { + item = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions() + { + AllowMultiple = false, Title = name + }); + } + else + { + item = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions() + { + AllowMultiple = false, Title = name + }); + } + + return item.Count == 1 ? item[0].Path.LocalPath : string.Empty; + } +} diff --git a/Cpp2IL.Gui/Views/MessageBox.axaml b/Cpp2IL.Gui/Views/MessageBox.axaml new file mode 100644 index 00000000..eff6533b --- /dev/null +++ b/Cpp2IL.Gui/Views/MessageBox.axaml @@ -0,0 +1,18 @@ + + + Error + + + + + + diff --git a/Cpp2IL.Gui/Views/MessageBox.axaml.cs b/Cpp2IL.Gui/Views/MessageBox.axaml.cs new file mode 100644 index 00000000..7cf68275 --- /dev/null +++ b/Cpp2IL.Gui/Views/MessageBox.axaml.cs @@ -0,0 +1,16 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; + +namespace Cpp2IL.Gui; + +public partial class MessageBox : Window +{ + public MessageBox(string error, string exception) + { + InitializeComponent(); + ErrorText.Text = error; + ExceptionText.Text = exception; + } + + private void Close(object? sender, RoutedEventArgs e) => Close(); +} diff --git a/Cpp2IL.Gui/app.manifest b/Cpp2IL.Gui/app.manifest new file mode 100644 index 00000000..4e2383b4 --- /dev/null +++ b/Cpp2IL.Gui/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/Cpp2IL.sln b/Cpp2IL.sln index c45c3e39..c8cd4dd2 100644 --- a/Cpp2IL.sln +++ b/Cpp2IL.sln @@ -47,6 +47,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cpp2IL.Plugin.ControlFlowGr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cpp2IL.Plugin.Pdb", "Cpp2IL.Plugin.Pdb\Cpp2IL.Plugin.Pdb.csproj", "{ED407C97-534B-476C-8CCC-BF9D2FB5458A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cpp2IL.Gui", "Cpp2IL.Gui\Cpp2IL.Gui.csproj", "{68456481-F57E-4B82-B5AB-772712501AE0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -101,6 +103,10 @@ Global {ED407C97-534B-476C-8CCC-BF9D2FB5458A}.Debug|Any CPU.Build.0 = Debug|Any CPU {ED407C97-534B-476C-8CCC-BF9D2FB5458A}.Release|Any CPU.ActiveCfg = Release|Any CPU {ED407C97-534B-476C-8CCC-BF9D2FB5458A}.Release|Any CPU.Build.0 = Release|Any CPU + {68456481-F57E-4B82-B5AB-772712501AE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68456481-F57E-4B82-B5AB-772712501AE0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68456481-F57E-4B82-B5AB-772712501AE0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68456481-F57E-4B82-B5AB-772712501AE0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Cpp2IL/Program.cs b/Cpp2IL/Program.cs index 53251941..ec1eabb4 100644 --- a/Cpp2IL/Program.cs +++ b/Cpp2IL/Program.cs @@ -27,466 +27,8 @@ namespace Cpp2IL; [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] internal class Program { - private static readonly List PathsToDeleteOnExit = []; - public static readonly string Cpp2IlVersionString = typeof(Program).Assembly.GetCustomAttribute()!.InformationalVersion; - private static void ResolvePathsFromCommandLine(string? gamePath, string? inputExeName, ref Cpp2IlRuntimeArgs args) - { - if (string.IsNullOrEmpty(gamePath)) - throw new SoftException("No force options provided, and no game path was provided either. Please provide a game path or use the --force- options."); - - //Somehow the above doesn't tell .net that gamePath can't be null on net472, so we do this stupid thing to avoid nullable warnings -#if NET472 - gamePath = gamePath!; -#endif - - Logger.VerboseNewline("Beginning path resolution..."); - - if (Directory.Exists(gamePath) && File.Exists(Path.Combine(gamePath, "Contents/Frameworks/GameAssembly.dylib"))) - HandleMacOSGamePath(gamePath, inputExeName, ref args); - else if (Directory.Exists(gamePath) && File.Exists(Path.Combine(gamePath, "GameAssembly.so"))) - HandleLinuxGamePath(gamePath, inputExeName, ref args); - else if (Directory.Exists(gamePath)) - HandleWindowsGamePath(gamePath, inputExeName, ref args); - else if (File.Exists(gamePath) && Path.GetExtension(gamePath).ToLowerInvariant() == ".apk") - HandleSingleApk(gamePath, ref args); - else if (File.Exists(gamePath) && Path.GetExtension(gamePath).ToLowerInvariant() is ".xapk" or ".apkm") - HandleXapk(gamePath, ref args); - else if (File.Exists(gamePath) && Path.GetExtension(gamePath).ToLowerInvariant() is ".ipa" or ".tipa") - HandleIpa(gamePath, ref args); - else - { - if (!Cpp2IlPluginManager.TryProcessGamePath(gamePath, ref args)) - throw new SoftException($"Could not find a valid unity game at {gamePath}"); - } - } - - private static void HandleMacOSGamePath(string gamePath, string? inputExeName, ref Cpp2IlRuntimeArgs args) - { - //macOS game. - args.PathToAssembly = Path.Combine(gamePath, "Contents", "Frameworks", "GameAssembly.dylib"); - var exeName = Path.GetFileName(Directory.GetFiles(Path.Combine(gamePath, "Contents", "MacOS")) - .FirstOrDefault(f => MiscUtils.BlacklistedExecutableFilenames.Any(f.EndsWith))); - - exeName = inputExeName ?? exeName; - - Logger.VerboseNewline($"Trying HandleMacOSGamePath as provided bundle contains GameAssembly.dylib, potential GA is {args.PathToAssembly} and executable {exeName}"); - - if (exeName == null) - throw new SoftException("Failed to locate any executable in the provided game directory. Make sure the path is correct, and if you *really* know what you're doing (and know it's not supported), use the force options, documented if you provide --help."); - - var unityPlayerPath = Path.Combine(gamePath, "Contents", "MacOS", exeName); - var gameDataPath = Path.Combine(gamePath, "Contents", "Resources", "Data"); - args.PathToMetadata = Path.Combine(gameDataPath, "il2cpp_data", "Metadata", "global-metadata.dat"); - - if (!File.Exists(args.PathToAssembly) || !File.Exists(unityPlayerPath) || !File.Exists(args.PathToMetadata)) - throw new SoftException("Invalid game-path or exe-name specified. Failed to find one of the following:\n" + - $"\t{args.PathToAssembly}\n" + - $"\t{unityPlayerPath}\n" + - $"\t{args.PathToMetadata}\n"); - - Logger.VerboseNewline($"Found probable macOS game at path: {gamePath}. Attempting to get unity version..."); - - var uv = Cpp2IlApi.DetermineUnityVersion(unityPlayerPath, gameDataPath); - Logger.VerboseNewline($"First-attempt unity version detection gave: {uv}"); - - if (uv == default) - { - Logger.Warn("Could not determine unity version, probably due to not running on windows and not having any assets files to determine it from. Enter unity version, if known, in the format of (xxxx.x.x), else nothing to fail: "); - var userInputUv = Console.ReadLine(); - - if (!string.IsNullOrEmpty(userInputUv)) - uv = UnityVersion.Parse(userInputUv); - - if (uv == default) - throw new SoftException("Failed to determine unity version. If you're not running on windows, I need a globalgamemanagers file or a data.unity3d file, or you need to use the force options."); - } - - args.UnityVersion = uv; - - if (args.UnityVersion.Major < 4) - { - Logger.WarnNewline($"Fail once: Unity version of provided executable is {args.UnityVersion}. This is probably not the correct version. Retrying with alternative method..."); - - var readUnityVersionFrom = Path.Combine(gameDataPath, "globalgamemanagers"); - if (File.Exists(readUnityVersionFrom)) - args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(File.ReadAllBytes(readUnityVersionFrom)); - else - { - readUnityVersionFrom = Path.Combine(gameDataPath, "data.unity3d"); - using var stream = File.OpenRead(readUnityVersionFrom); - - args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(stream); - } - } - - Logger.InfoNewline($"Determined game's unity version to be {args.UnityVersion}"); - - if (args.UnityVersion.Major <= 4) - throw new SoftException($"Unable to determine a valid unity version (got {args.UnityVersion})"); - - args.Valid = true; - } - - private static void HandleLinuxGamePath(string gamePath, string? inputExeName, ref Cpp2IlRuntimeArgs args) - { - //Linux game. - args.PathToAssembly = Path.Combine(gamePath, "GameAssembly.so"); - var exeName = Path.GetFileName(Directory.GetFiles(gamePath) - .FirstOrDefault(f => - (f.EndsWith(".x86_64") || f.EndsWith(".x86")) && - !MiscUtils.BlacklistedExecutableFilenames.Any(f.EndsWith))); - - exeName = inputExeName ?? exeName; - - Logger.VerboseNewline($"Trying HandleLinuxGamePath as provided directory contains a GameAssembly.so, potential GA is {args.PathToAssembly} and executable {exeName}"); - - if (exeName == null) - throw new SoftException("Failed to locate any executable in the provided game directory. Make sure the path is correct, and if you *really* know what you're doing (and know it's not supported), use the force options, documented if you provide --help."); - - var exeNameNoExt = exeName.Replace(".x86_64", "").Replace(".x86", ""); - - var unityPlayerPath = Path.Combine(gamePath, exeName); - args.PathToMetadata = Path.Combine(gamePath, $"{exeNameNoExt}_Data", "il2cpp_data", "Metadata", "global-metadata.dat"); - - if (!File.Exists(args.PathToAssembly) || !File.Exists(unityPlayerPath) || !File.Exists(args.PathToMetadata)) - throw new SoftException("Invalid game-path or exe-name specified. Failed to find one of the following:\n" + - $"\t{args.PathToAssembly}\n" + - $"\t{unityPlayerPath}\n" + - $"\t{args.PathToMetadata}\n"); - - Logger.VerboseNewline($"Found probable linux game at path: {gamePath}. Attempting to get unity version..."); - var gameDataPath = Path.Combine(gamePath, $"{exeNameNoExt}_Data"); - var uv = Cpp2IlApi.DetermineUnityVersion(unityPlayerPath, gameDataPath); - Logger.VerboseNewline($"First-attempt unity version detection gave: {uv}"); - - if (uv == default) - { - Logger.Warn("Could not determine unity version, probably due to not running on windows and not having any assets files to determine it from. Enter unity version, if known, in the format of (xxxx.x.x), else nothing to fail: "); - var userInputUv = Console.ReadLine(); - - if (!string.IsNullOrEmpty(userInputUv)) - uv = UnityVersion.Parse(userInputUv); - - if (uv == default) - throw new SoftException("Failed to determine unity version. If you're not running on windows, I need a globalgamemanagers file or a data.unity3d file, or you need to use the force options."); - } - - args.UnityVersion = uv; - - if (args.UnityVersion.Major < 4) - { - Logger.WarnNewline($"Fail once: Unity version of provided executable is {args.UnityVersion}. This is probably not the correct version. Retrying with alternative method..."); - - var readUnityVersionFrom = Path.Combine(gameDataPath, "globalgamemanagers"); - if (File.Exists(readUnityVersionFrom)) - args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(File.ReadAllBytes(readUnityVersionFrom)); - else - { - readUnityVersionFrom = Path.Combine(gameDataPath, "data.unity3d"); - using var stream = File.OpenRead(readUnityVersionFrom); - - args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(stream); - } - } - - Logger.InfoNewline($"Determined game's unity version to be {args.UnityVersion}"); - - if (args.UnityVersion.Major <= 4) - throw new SoftException($"Unable to determine a valid unity version (got {args.UnityVersion})"); - - args.Valid = true; - } - - private static void HandleWindowsGamePath(string gamePath, string? inputExeName, ref Cpp2IlRuntimeArgs args) - { - //Windows game. - args.PathToAssembly = Path.Combine(gamePath, "GameAssembly.dll"); - var exeName = Path.GetFileNameWithoutExtension(Directory.GetFiles(gamePath) - .FirstOrDefault(f => f.EndsWith(".exe") && !MiscUtils.BlacklistedExecutableFilenames.Any(f.EndsWith))); - - exeName = inputExeName ?? exeName; - - Logger.VerboseNewline($"Trying HandleWindowsGamePath as provided path is a directory with no GameAssembly.so, potential GA is {args.PathToAssembly} and executable {exeName}"); - - if (exeName == null) - throw new SoftException("Failed to locate any executable in the provided game directory. Make sure the path is correct, and if you *really* know what you're doing (and know it's not supported), use the force options, documented if you provide --help."); - - var unityPlayerPath = Path.Combine(gamePath, $"{exeName}.exe"); - args.PathToMetadata = Path.Combine(gamePath, $"{exeName}_Data", "il2cpp_data", "Metadata", "global-metadata.dat"); - - if (!File.Exists(args.PathToAssembly) || !File.Exists(unityPlayerPath) || !File.Exists(args.PathToMetadata)) - throw new SoftException("Invalid game-path or exe-name specified. Failed to find one of the following:\n" + - $"\t{args.PathToAssembly}\n" + - $"\t{unityPlayerPath}\n" + - $"\t{args.PathToMetadata}\n"); - - Logger.VerboseNewline($"Found probable windows game at path: {gamePath}. Attempting to get unity version..."); - var gameDataPath = Path.Combine(gamePath, $"{exeName}_Data"); - var uv = Cpp2IlApi.DetermineUnityVersion(unityPlayerPath, gameDataPath); - Logger.VerboseNewline($"First-attempt unity version detection gave: {uv}"); - - if (uv == default) - { - Logger.Warn("Could not determine unity version, probably due to not running on windows and not having any assets files to determine it from. Enter unity version, if known, in the format of (xxxx.x.x), else nothing to fail: "); - var userInputUv = Console.ReadLine(); - - if (!string.IsNullOrEmpty(userInputUv)) - uv = UnityVersion.Parse(userInputUv); - - if (uv == default) - throw new SoftException("Failed to determine unity version. If you're not running on windows, I need a globalgamemanagers file or a data.unity3d file, or you need to use the force options."); - } - - args.UnityVersion = uv; - - if (args.UnityVersion.Major < 4) - { - Logger.WarnNewline($"Fail once: Unity version of provided executable is {args.UnityVersion}. This is probably not the correct version. Retrying with alternative method..."); - - var readUnityVersionFrom = Path.Combine(gameDataPath, "globalgamemanagers"); - if (File.Exists(readUnityVersionFrom)) - args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(File.ReadAllBytes(readUnityVersionFrom)); - else - { - readUnityVersionFrom = Path.Combine(gameDataPath, "data.unity3d"); - using var stream = File.OpenRead(readUnityVersionFrom); - - args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(stream); - } - } - - Logger.InfoNewline($"Determined game's unity version to be {args.UnityVersion}"); - - if (args.UnityVersion.Major <= 4) - throw new SoftException($"Unable to determine a valid unity version (got {args.UnityVersion})"); - - args.Valid = true; - } - - private static void HandleSingleApk(string gamePath, ref Cpp2IlRuntimeArgs args) - { - //APK - //Metadata: assets/bin/Data/Managed/Metadata - //Binary: lib/(armeabi-v7a)|(arm64-v8a)/libil2cpp.so - - Logger.VerboseNewline("Trying HandleSingleApk as provided path is an apk file"); - - Logger.InfoNewline($"Attempting to extract required files from APK {gamePath}", "APK"); - - using var stream = File.OpenRead(gamePath); - using var zipArchive = new ZipArchive(stream); - - var globalMetadata = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("assets/bin/Data/Managed/Metadata/global-metadata.dat")); - var binary = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("lib/x86_64/libil2cpp.so")); - binary ??= zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("lib/x86/libil2cpp.so")); - binary ??= zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("lib/arm64-v8a/libil2cpp.so")); - binary ??= zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("lib/armeabi-v7a/libil2cpp.so")); - - var globalgamemanagers = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("assets/bin/Data/globalgamemanagers")); - var dataUnity3d = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("assets/bin/Data/data.unity3d")); - - if (binary == null) - throw new SoftException("Could not find libil2cpp.so inside the apk."); - if (globalMetadata == null) - throw new SoftException("Could not find global-metadata.dat inside the apk"); - if (globalgamemanagers == null && dataUnity3d == null) - throw new SoftException("Could not find globalgamemanagers or data.unity3d inside the apk"); - - var tempFileBinary = Path.GetTempFileName(); - var tempFileMeta = Path.GetTempFileName(); - - PathsToDeleteOnExit.Add(tempFileBinary); - PathsToDeleteOnExit.Add(tempFileMeta); - - Logger.InfoNewline($"Extracting APK/{binary.FullName} to {tempFileBinary}", "APK"); - binary.ExtractToFile(tempFileBinary, true); - Logger.InfoNewline($"Extracting APK/{globalMetadata.FullName} to {tempFileMeta}", "APK"); - globalMetadata.ExtractToFile(tempFileMeta, true); - - args.PathToAssembly = tempFileBinary; - args.PathToMetadata = tempFileMeta; - - if (globalgamemanagers != null) - { - Logger.InfoNewline("Reading globalgamemanagers to determine unity version...", "APK"); - var ggmBytes = new byte[0x40]; - using var ggmStream = globalgamemanagers.Open(); - - // ReSharper disable once MustUseReturnValue - ggmStream.Read(ggmBytes, 0, 0x40); - - args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(ggmBytes); - } - else - { - Logger.InfoNewline("Reading data.unity3d to determine unity version...", "APK"); - using var du3dStream = dataUnity3d!.Open(); - - args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(du3dStream); - } - - Logger.InfoNewline($"Determined game's unity version to be {args.UnityVersion}", "APK"); - - args.Valid = true; - } - - private static void HandleXapk(string gamePath, ref Cpp2IlRuntimeArgs args) - { - //XAPK file - //Contains two APKs - one starting with `config.` and one with the package name - //The config one is architecture-specific and so contains the binary - //The other contains the metadata - - Logger.VerboseNewline("Trying HandleXapk as provided path is an xapk or apkm file"); - - Logger.InfoNewline($"Attempting to extract required files from XAPK {gamePath}", "XAPK"); - - using var xapkStream = File.OpenRead(gamePath); - using var xapkZip = new ZipArchive(xapkStream); - - ZipArchiveEntry? configApk = null; - var configApks = xapkZip.Entries.Where(e => e.FullName.Contains("config.") && e.FullName.EndsWith(".apk")).ToList(); - - var instructionSetPreference = new[] { "arm64_v8a", "arm64", "armeabi_v7a", "arm" }; - foreach (var instructionSet in instructionSetPreference) - { - configApk = configApks.FirstOrDefault(e => e.FullName.Contains(instructionSet)); - if (configApk != null) - break; - } - - //Try for base.apk, else find any apk that isn't the config apk - var mainApk = xapkZip.Entries.FirstOrDefault(e => e.FullName.EndsWith(".apk") && e.FullName.Contains("base.apk")) - ?? xapkZip.Entries.FirstOrDefault(e => e != configApk && e.FullName.EndsWith(".apk")); - - Logger.InfoNewline($"Identified APKs inside XAPK - config: {configApk?.FullName}, mainPackage: {mainApk?.FullName}", "XAPK"); - - if (configApk == null) - throw new SoftException("Could not find a config apk inside the XAPK"); - if (mainApk == null) - throw new SoftException("Could not find a main apk inside the XAPK"); - - using var configZip = new ZipArchive(configApk.Open()); - using var mainZip = new ZipArchive(mainApk.Open()); - var binary = configZip.Entries.FirstOrDefault(e => e.FullName.EndsWith("libil2cpp.so")); - var globalMetadata = mainZip.Entries.FirstOrDefault(e => e.FullName.EndsWith("global-metadata.dat")); - - var globalgamemanagers = mainZip.Entries.FirstOrDefault(e => e.FullName.EndsWith("globalgamemanagers")); - var dataUnity3d = mainZip.Entries.FirstOrDefault(e => e.FullName.EndsWith("data.unity3d")); - - if (binary == null) - throw new SoftException("Could not find libil2cpp.so inside the config APK"); - if (globalMetadata == null) - throw new SoftException("Could not find global-metadata.dat inside the main APK"); - if (globalgamemanagers == null && dataUnity3d == null) - throw new SoftException("Could not find globalgamemanagers or data.unity3d inside the main APK"); - - var tempFileBinary = Path.GetTempFileName(); - var tempFileMeta = Path.GetTempFileName(); - - PathsToDeleteOnExit.Add(tempFileBinary); - PathsToDeleteOnExit.Add(tempFileMeta); - - Logger.InfoNewline($"Extracting XAPK/{configApk.Name}/{binary.FullName} to {tempFileBinary}", "XAPK"); - binary.ExtractToFile(tempFileBinary, true); - Logger.InfoNewline($"Extracting XAPK{mainApk.Name}/{globalMetadata.FullName} to {tempFileMeta}", "XAPK"); - globalMetadata.ExtractToFile(tempFileMeta, true); - - args.PathToAssembly = tempFileBinary; - args.PathToMetadata = tempFileMeta; - - if (globalgamemanagers != null) - { - Logger.InfoNewline("Reading globalgamemanagers to determine unity version...", "XAPK"); - var ggmBytes = new byte[0x40]; - using var ggmStream = globalgamemanagers.Open(); - - // ReSharper disable once MustUseReturnValue - ggmStream.Read(ggmBytes, 0, 0x40); - - args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(ggmBytes); - } - else - { - Logger.InfoNewline("Reading data.unity3d to determine unity version...", "XAPK"); - using var du3dStream = dataUnity3d!.Open(); - - args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(du3dStream); - } - - Logger.InfoNewline($"Determined game's unity version to be {args.UnityVersion}", "XAPK"); - - args.Valid = true; - } - - private static void HandleIpa(string gamePath, ref Cpp2IlRuntimeArgs args) - { - //IPA - //Metadata: Payload/AppName.app/Data/Managed/Metadata/global-metadata.dat - //Binary: Payload/AppName.app/Frameworks/UnityFramework.framework/UnityFramework - //GlobalGameManager: Payload/AppName.app/Data/globalgamemanagers - //Unity3d: Payload/AppName.app/Data/data.unity3d - - Logger.VerboseNewline("Trying HandleIpa as provided path is an ipa or tipa file"); - - Logger.InfoNewline($"Attempting to extract required files from IPA {gamePath}", "IPA"); - - using var stream = File.OpenRead(gamePath); - using var zipArchive = new ZipArchive(stream); - - var globalMetadata = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("Data/Managed/Metadata/global-metadata.dat")); - var binary = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("Frameworks/UnityFramework.framework/UnityFramework")); - - var globalgamemanagers = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("Data/globalgamemanagers")); - var dataUnity3d = zipArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith("Data/data.unity3d")); - - if (binary == null) - throw new SoftException("Could not find UnityFramework inside the ipa."); - if (globalMetadata == null) - throw new SoftException("Could not find global-metadata.dat inside the ipa."); - if (globalgamemanagers == null && dataUnity3d == null) - throw new SoftException("Could not find globalgamemanagers or unity3d inside the ipa."); - - var tempFileBinary = Path.GetTempFileName(); - var tempFileMeta = Path.GetTempFileName(); - - PathsToDeleteOnExit.Add(tempFileBinary); - PathsToDeleteOnExit.Add(tempFileMeta); - - Logger.InfoNewline($"Extracting IPA/{binary.FullName} to {tempFileBinary}", "IPA"); - binary.ExtractToFile(tempFileBinary, true); - Logger.InfoNewline($"Extracting IPA/{globalMetadata.FullName} to {tempFileMeta}", "IPA"); - globalMetadata.ExtractToFile(tempFileMeta, true); - - args.PathToAssembly = tempFileBinary; - args.PathToMetadata = tempFileMeta; - - if (globalgamemanagers != null) - { - Logger.InfoNewline("Reading globalgamemanagers to determine unity version...", "IPA"); - var ggmBytes = new byte[0x40]; - using var ggmStream = globalgamemanagers.Open(); - - // ReSharper disable once MustUseReturnValue - ggmStream.Read(ggmBytes, 0, 0x40); - - args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(ggmBytes); - } - else - { - Logger.InfoNewline("Reading data.unity3d to determine unity version...", "IPA"); - using var du3dStream = dataUnity3d!.Open(); - - args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(du3dStream); - } - - Logger.InfoNewline($"Determined game's unity version to be {args.UnityVersion}", "IPA"); - - args.Valid = true; - } - #if !NETFRAMEWORK [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Cpp2IL.CommandLineArgs", "Cpp2IL")] #endif @@ -540,7 +82,7 @@ private static Cpp2IlRuntimeArgs GetRuntimeOptionsFromCommandLine(string[] comma options.GamePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + options.GamePath[1..]; #endif - ResolvePathsFromCommandLine(options.GamePath, options.ExeName, ref result); + PathUtils.ResolvePathsFromCommandLine(options.GamePath, options.ExeName, ref result); } else { @@ -716,7 +258,7 @@ public static int MainWithArgs(Cpp2IlRuntimeArgs runtimeArgs) // if (runtimeArgs.EnableAnalysis) // Cpp2IlApi.PopulateConcreteImplementations(); - CleanupExtractedFiles(); + PathUtils.CleanupExtractedFiles(); Cpp2IlPluginManager.CallOnFinish(); @@ -752,20 +294,4 @@ private static void RunProcessingLayers(Cpp2IlRuntimeArgs runtimeArgs, Action