diff --git a/Il2CppInspector.Common/IL2CPP/Metadata.cs b/Il2CppInspector.Common/IL2CPP/Metadata.cs index c119de5..f4e2317 100644 --- a/Il2CppInspector.Common/IL2CPP/Metadata.cs +++ b/Il2CppInspector.Common/IL2CPP/Metadata.cs @@ -50,41 +50,50 @@ namespace Il2CppInspector public Metadata(MemoryStream stream, EventHandler statusCallback = null) : base(stream) { - // Read magic bytes - if (ReadUInt32() != 0xFAB11BAF) { + // Pre-processing hook + var pluginResult = PluginHooks.PreProcessMetadata(stream); + IsModified = pluginResult.IsStreamModified; + + // Read metadata header + Header = ReadObject(0); + + // Check for correct magic bytes + if (Header.signature != Il2CppConstants.MetadataSignature) { throw new InvalidOperationException("The supplied metadata file is not valid."); } // Set object versioning for Bin2Object from metadata version - Version = ReadInt32(); + Version = Header.version; - // Rewind and read metadata header in full - Header = ReadObject(0); - if (Version < 16 || Version > 27) - { + if (Version < 16 || Version > 27) { throw new InvalidOperationException($"The supplied metadata file is not of a supported version ({Header.version})."); } + // Rewind and read metadata header with the correct version settings + Header = ReadObject(0); + // Sanity checking // Unity.IL2CPP.MetadataCacheWriter.WriteLibIl2CppMetadata always writes the metadata information in the same order it appears in the header, // with each block always coming directly after the previous block, 4-byte aligned. We can use this to check the integrity of the data and // detect sub-versions. - // For metadata v24, the header can either be either 0x110 (24.0, 24.1) or 0x108 (24.2) bytes long. Since 'stringLiteralOffset' is the first thing + // For metadata v24.0, the header can either be either 0x110 (24.0, 24.1) or 0x108 (24.2) bytes long. Since 'stringLiteralOffset' is the first thing // in the header after the sanity and version fields, and since it will always point directly to the first byte after the end of the header, // we can use this value to determine the actual header length and therefore narrow down the metadata version to 24.0/24.1 or 24.2. - var realHeaderLength = Header.stringLiteralOffset; + if (!pluginResult.AdditionalData.SkipValidation) { + var realHeaderLength = Header.stringLiteralOffset; - if (realHeaderLength != Sizeof(typeof(Il2CppGlobalMetadataHeader))) { - if (Version == 24.0) { - Version = 24.2; - Header = ReadObject(0); + if (realHeaderLength != Sizeof(typeof(Il2CppGlobalMetadataHeader))) { + if (Version == 24.0) { + Version = 24.2; + Header = ReadObject(0); + } } - } - if (realHeaderLength != Sizeof(typeof(Il2CppGlobalMetadataHeader))) { - throw new InvalidOperationException("Could not verify the integrity of the metadata file or accurately identify the metadata sub-version"); + if (realHeaderLength != Sizeof(typeof(Il2CppGlobalMetadataHeader))) { + throw new InvalidOperationException("Could not verify the integrity of the metadata file or accurately identify the metadata sub-version"); + } } // Load all the relevant metadata using offsets provided in the header @@ -219,7 +228,7 @@ namespace Il2CppInspector StringLiterals[i] = ReadFixedLengthString(Header.stringLiteralDataOffset + stringLiteralList[i].dataIndex, stringLiteralList[i].length); // Post-processing hook - PluginHooks.PostProcessMetadata(this); + IsModified |= PluginHooks.PostProcessMetadata(this).IsStreamModified; } // Save metadata to file, overwriting if necessary diff --git a/Il2CppInspector.Common/IL2CPP/MetadataClasses.cs b/Il2CppInspector.Common/IL2CPP/MetadataClasses.cs index e7f6f55..3bd29f6 100644 --- a/Il2CppInspector.Common/IL2CPP/MetadataClasses.cs +++ b/Il2CppInspector.Common/IL2CPP/MetadataClasses.cs @@ -38,7 +38,7 @@ namespace Il2CppInspector #pragma warning disable CS0649 public class Il2CppGlobalMetadataHeader { - public uint sanity; + public uint signature; public int version; public int stringLiteralOffset; // string data for managed code public int stringLiteralCount; diff --git a/Il2CppInspector.Common/Plugins/API/V100/Hooks.cs b/Il2CppInspector.Common/Plugins/API/V100/Hooks.cs index 6f90881..fd4e825 100644 --- a/Il2CppInspector.Common/Plugins/API/V100/Hooks.cs +++ b/Il2CppInspector.Common/Plugins/API/V100/Hooks.cs @@ -4,13 +4,25 @@ All rights reserved. */ +using System; +using System.IO; + namespace Il2CppInspector.PluginAPI.V100 { + /// + /// Process global-metadata.dat when it is first opened as a sequence of bytes + /// Seek cursor will be at the start of the file + /// + public interface IPreProcessMetadata + { + void PreProcessMetadata(MemoryStream stream, PluginPreProcessMetadataEventInfo data); + } + /// /// Process global-metadata.dat after it has been loaded into a Metadata object /// public interface IPostProcessMetadata { - void PostProcessMetadata(Metadata metadata); + void PostProcessMetadata(Metadata metadata, PluginPostProcessMetadataEventInfo data); } } diff --git a/Il2CppInspector.Common/Plugins/API/V100/PluginEventInfo.cs b/Il2CppInspector.Common/Plugins/API/V100/PluginEventInfo.cs new file mode 100644 index 0000000..1765c8c --- /dev/null +++ b/Il2CppInspector.Common/Plugins/API/V100/PluginEventInfo.cs @@ -0,0 +1,92 @@ +/* + Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty + + All rights reserved. +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Il2CppInspector.PluginAPI.V100 +{ + public interface IPluginEventInfo + { + public bool IsHandled { get; set; } + public bool IsInvalid { get; set; } + public bool IsDataModified { get; set; } + public bool IsStreamModified { get; set; } + public PluginErrorEventArgs Error { get; set; } + } + + /// + /// Object which allows plugins to report on what has happened during a call + /// Changes made to this object propagate to the next plugin in the call chain until IsHandled is set to true + /// + public class PluginEventInfo : IPluginEventInfo where T : new() + { + /// + /// A plugin should set this if it has processed the supplied data in such a way that no further processing is required by other plugins + /// Generally, this will prevent other plugins from processing the data + /// Note that this should be set even if the processed data was invalid () + /// If this is not set, the same event will be raised on the next available plugin + /// Note that you can still do processing but set IsHandled to false to allow additional processing from other plugins + /// + public bool IsHandled { get; set; } = false; + + /// + /// A plugin should set this when IsHandled = true but the data it processed was invalid, for example if the processing gave an unexpected result + /// + public bool IsInvalid { get; set; } = false; + + /// + /// A plugin should set this when it has directly modified the provided data structure (object) + /// This can be set even if IsHandled = false to indicate that changes have been made but more plugins can still be called + /// Should be set to false if you have only queried (performed reads) on the data without changing it + /// + public bool IsDataModified { get; set; } = false; + + /// + /// A plugin should set this when it has directly modified the supplied or underlying stream for the metadata or binary + /// This can be set even if IsHandled = false to indicate that changes have been made but more plugins can still be called + /// Should be set to false if you have only queried (performed reads) on the stream without changing it + /// + public bool IsStreamModified { get; set; } = false; + + /// + /// Event-specific additional options and controls. See the documentation for each event for more details. + /// + public T AdditionalData { get; } = new T(); + + /// + /// This wiil be set automatically by Il2CppInspector to the last exception thrown by a plugin for the current event + /// + public PluginErrorEventArgs Error { get; set; } = null; + } + + /// + /// Generic event info with no additional paramters + /// + public class PluginEventInfo : PluginEventInfo { } + + /// + /// Event info for PreProcessMetadata + /// + public class PluginPreProcessMetadataEventInfo : PluginEventInfo { } + + /// + /// Event info for PostProcessMetadata + /// + public class PluginPostProcessMetadataEventInfo : PluginEventInfo { } + + /// + /// Additional data for PreProcessMetadata + /// + public class PluginPreProcessMetadataEventData + { + /// + /// Set to true to disable some validation checks by Il2CppInspector that the metadata is valid + /// + public bool SkipValidation { get; set; } + } +} diff --git a/Il2CppInspector.Common/Plugins/PluginHooks.cs b/Il2CppInspector.Common/Plugins/PluginHooks.cs index 7175b20..3cf03d5 100644 --- a/Il2CppInspector.Common/Plugins/PluginHooks.cs +++ b/Il2CppInspector.Common/Plugins/PluginHooks.cs @@ -5,6 +5,7 @@ */ // This is the ONLY line to update when the API version changes +using System.IO; using Il2CppInspector.PluginAPI.V100; namespace Il2CppInspector @@ -12,6 +13,13 @@ namespace Il2CppInspector // Hooks we provide to plugins which can choose whether or not to provide implementations internal static class PluginHooks { - public static void PostProcessMetadata(Metadata metadata) => PluginManager.Try(p => p.PostProcessMetadata(metadata)); + public static PluginPostProcessMetadataEventInfo PostProcessMetadata(Metadata metadata) + => PluginManager.Try((p, e) => p.PostProcessMetadata(metadata, e)); + + public static PluginPreProcessMetadataEventInfo PreProcessMetadata(MemoryStream stream) + => PluginManager.Try((p, e) => { + stream.Position = 0; + p.PreProcessMetadata(stream, e); + }); } } diff --git a/Il2CppInspector.Common/Plugins/PluginManager.cs b/Il2CppInspector.Common/Plugins/PluginManager.cs index da728fb..1236a68 100644 --- a/Il2CppInspector.Common/Plugins/PluginManager.cs +++ b/Il2CppInspector.Common/Plugins/PluginManager.cs @@ -209,15 +209,24 @@ namespace Il2CppInspector // Try to cast each enabled plugin to a specific interface type, and for those supporting the interface, execute the supplied delegate // Errors will be forwarded to the error handler - internal static void Try(Action action) { + internal static E Try(Action action) where E : IPluginEventInfo, new() + { + var eventInfo = new E(); + foreach (var plugin in EnabledPlugins) if (plugin is I p) try { - action(p); + action(p, eventInfo); + + if (eventInfo.IsHandled) + break; } catch (Exception ex) { - ErrorHandler?.Invoke(AsInstance, new PluginErrorEventArgs { Plugin = plugin, Exception = ex, Operation = typeof(I).Name }); + eventInfo.Error = new PluginErrorEventArgs { Plugin = plugin, Exception = ex, Operation = typeof(I).Name }; + ErrorHandler?.Invoke(AsInstance, eventInfo.Error); } + + return eventInfo; } // Process an incoming status update