diff --git a/Il2CppInspector.CLI/Program.cs b/Il2CppInspector.CLI/Program.cs index 115907b..e8e23bc 100644 --- a/Il2CppInspector.CLI/Program.cs +++ b/Il2CppInspector.CLI/Program.cs @@ -38,6 +38,12 @@ namespace Il2CppInspector.CLI [Option('o', "json-out", Required = false, HelpText = "JSON metadata output file", Default = "metadata.json")] public string JsonOutPath { get; set; } + [Option("metadata-out", Required = false, HelpText = "IL2CPP metadata file output (for extracted or decrypted metadata; ignored otherwise)")] + public string MetadataFileOut { get; set; } + + [Option("binary-out", Required = false, HelpText = "IL2CPP binary file output (for extracted or decrypted binaries; ignored otherwise; suffixes will be appended for multiple files)")] + public string BinaryFileOut { get; set; } + [Option('e', "exclude-namespaces", Required = false, Separator = ',', HelpText = "Comma-separated list of namespaces to suppress in C# output, or 'none' to include all namespaces", Default = new [] { "System", @@ -179,11 +185,13 @@ namespace Il2CppInspector.CLI } // Check files exist and determine whether they're archives or not + bool isExtractedFromPackage = false; List il2cppInspectors; using (new Benchmark("Analyze IL2CPP data")) { try { il2cppInspectors = Il2CppInspector.LoadFromPackage(options.BinaryFiles); + isExtractedFromPackage = true; } catch (Exception ex) { Console.Error.WriteLine(ex.Message); @@ -191,6 +199,8 @@ namespace Il2CppInspector.CLI } if (il2cppInspectors == null) { + isExtractedFromPackage = false; + if (!File.Exists(options.MetadataFile)) { Console.Error.WriteLine($"File {options.MetadataFile} does not exist"); return 1; @@ -209,6 +219,36 @@ namespace Il2CppInspector.CLI if (il2cppInspectors == null) Environment.Exit(1); + // Save metadata and binary if extracted or modified and save requested + if (!string.IsNullOrEmpty(options.MetadataFileOut)) { + if (isExtractedFromPackage || il2cppInspectors[0].Metadata.IsModified) { + Console.WriteLine($"Saving metadata file to {options.MetadataFileOut}"); + + il2cppInspectors[0].SaveMetadataToFile(options.MetadataFileOut); + } else + Console.WriteLine("Metadata file was not modified - skipping save"); + } + + if (!string.IsNullOrEmpty(options.BinaryFileOut)) { + var outputIndex = 0; + foreach (var il2cpp in il2cppInspectors) { + // If there's an extension, strip the leading period + var ext = Path.GetExtension(options.BinaryFileOut); + if (ext.Length > 0) + ext = ext.Substring(1); + var outPath = getOutputPath(options.BinaryFileOut, ext, outputIndex); + + if (isExtractedFromPackage || il2cpp.Binary.IsModified) { + Console.WriteLine($"Saving binary file to {outPath}"); + + il2cpp.SaveBinaryToFile(outPath); + } else + Console.WriteLine("Binary file was not modified - skipping save"); + + outputIndex++; + } + } + // Write output files for each binary int imageIndex = 0; foreach (var il2cpp in il2cppInspectors) { diff --git a/Il2CppInspector.GUI/App.xaml.cs b/Il2CppInspector.GUI/App.xaml.cs index 55a4a45..ea0d86b 100644 --- a/Il2CppInspector.GUI/App.xaml.cs +++ b/Il2CppInspector.GUI/App.xaml.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using System.Windows; using Il2CppInspector; @@ -17,10 +19,21 @@ namespace Il2CppInspectorGUI /// /// Interaction logic for App.xaml /// - public partial class App : Application + public partial class App : Application, INotifyPropertyChanged { private Metadata metadata; + // True if we extracted from an APK, IPA, zip file etc. + private bool isExtractedFromPackage; + public bool IsExtractedFromPackage { + get => isExtractedFromPackage; + set { + if (value == isExtractedFromPackage) return; + isExtractedFromPackage = value; + OnPropertyChanged(); + } + } + public List AppModels { get; } = new List(); public Exception LastException { get; private set; } @@ -32,6 +45,8 @@ namespace Il2CppInspectorGUI // Attempt to load an IL2CPP application package (APK or IPA) public async Task LoadPackageAsync(IEnumerable packageFiles) { + IsExtractedFromPackage = false; + try { OnStatusUpdate?.Invoke(this, "Extracting package"); @@ -39,7 +54,8 @@ namespace Il2CppInspectorGUI 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); + IsExtractedFromPackage = await LoadMetadataAsync(streams.Value.Metadata) && await LoadBinaryAsync(streams.Value.Binary); + return IsExtractedFromPackage; } catch (Exception ex) { LastException = ex; @@ -49,6 +65,7 @@ namespace Il2CppInspectorGUI // Attempt to load an IL2CPP metadata file public async Task LoadMetadataAsync(string metadataFile) { + IsExtractedFromPackage = false; var stream = new MemoryStream(await File.ReadAllBytesAsync(metadataFile)); return await LoadMetadataAsync(stream); } @@ -120,5 +137,11 @@ namespace Il2CppInspectorGUI return false; } }); + + // Property change notifier for IsExtractedFromPackage binding + public event PropertyChangedEventHandler PropertyChanged; + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } } } diff --git a/Il2CppInspector.GUI/MainWindow.xaml b/Il2CppInspector.GUI/MainWindow.xaml index e266071..feecb5d 100644 --- a/Il2CppInspector.GUI/MainWindow.xaml +++ b/Il2CppInspector.GUI/MainWindow.xaml @@ -175,6 +175,45 @@ + + + + + diff --git a/Il2CppInspector.GUI/MainWindow.xaml.cs b/Il2CppInspector.GUI/MainWindow.xaml.cs index 8b62a9b..5783b94 100644 --- a/Il2CppInspector.GUI/MainWindow.xaml.cs +++ b/Il2CppInspector.GUI/MainWindow.xaml.cs @@ -31,6 +31,7 @@ using Il2CppInspector.Reflection; using Ookii.Dialogs.Wpf; using Path = System.IO.Path; using Il2CppInspector.Cpp.UnityHeaders; +using System.IO.Packaging; namespace Il2CppInspectorGUI { @@ -42,6 +43,9 @@ namespace Il2CppInspectorGUI public MainWindow() { InitializeComponent(); + // Allow XAML to access properties in the App class + DataContext = ((App) Application.Current); + // Subscribe to status update events ((App) Application.Current).OnStatusUpdate += OnStatusUpdate; @@ -314,6 +318,43 @@ namespace Il2CppInspectorGUI return false; } + /// + /// Save extracted or decrypted files + /// + private async void BtnSaveMetadata_OnClick(object sender, RoutedEventArgs e) { + var package = ((AppModel) lstImages.SelectedItem).TypeModel.Package; + var saveFileDialog = new SaveFileDialog { + Filter = "IL2CPP metadata files (*.dat)|*.dat|All files (*.*)|*.*", + FileName = "global-metadata.dat", + CheckFileExists = false, + OverwritePrompt = true + }; + + if (saveFileDialog.ShowDialog() == false) + return; + + var outPath = saveFileDialog.FileName; + await Task.Run(() => package.SaveMetadataToFile(outPath)); + } + + private async void BtnSaveBinary_OnClick(object sender, RoutedEventArgs e) { + var package = ((AppModel) lstImages.SelectedItem).TypeModel.Package; + var binaryName = package.BinaryImage.DefaultFilename; + + var saveFileDialog = new SaveFileDialog { + Filter = "All files (*.*)|*.*", + FileName = binaryName, + CheckFileExists = false, + OverwritePrompt = true + }; + + if (saveFileDialog.ShowDialog() == false) + return; + + var outPath = saveFileDialog.FileName; + await Task.Run(() => package.SaveBinaryToFile(outPath)); + } + /// /// Perform export ///