diff --git a/Il2CppInspector.Common/IL2CPP/Metadata.cs b/Il2CppInspector.Common/IL2CPP/Metadata.cs index 116d5b7..e6720d3 100644 --- a/Il2CppInspector.Common/IL2CPP/Metadata.cs +++ b/Il2CppInspector.Common/IL2CPP/Metadata.cs @@ -43,7 +43,7 @@ namespace Il2CppInspector public uint[] VTableMethodIndices { get; set; } public string[] StringLiterals { get; set; } - public Dictionary Strings { get; } = new Dictionary(); + public Dictionary Strings { get; private set; } = new Dictionary(); // Set if something in the metadata has been modified / decrypted public bool IsModified { get; private set; } = false; @@ -97,7 +97,7 @@ namespace Il2CppInspector // 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. - if (!pluginResult.AdditionalData.SkipValidation) { + if (!pluginResult.SkipValidation) { var realHeaderLength = Header.stringLiteralOffset; if (realHeaderLength != Sizeof(typeof(Il2CppGlobalMetadataHeader))) { @@ -169,15 +169,20 @@ namespace Il2CppInspector AttributeTypeRanges = ReadArray(Header.attributesInfoOffset, Header.attributesInfoCount / Sizeof(typeof(Il2CppCustomAttributeTypeRange))); } - // Get all metadata string literals - Position = Header.stringOffset; + // Get all metadata strings + var pluginGetStringsResult = PluginHooks.GetStrings(this); + if (pluginGetStringsResult.IsHandled) + Strings = pluginGetStringsResult.Strings; - // Naive implementation: this works for normal IL2CPP metadata but isn't good enough when the strings are encrypted - while (Position < Header.stringOffset + Header.stringCount) - Strings.Add((int)Position - Header.stringOffset, ReadNullTerminatedString()); + else { + Position = Header.stringOffset; - // To check for encryption, find every single string start position by scanning all of the definitions - var stringOffsets = + // Naive implementation: this works for normal IL2CPP metadata but isn't good enough when the strings are encrypted + while (Position < Header.stringOffset + Header.stringCount) + Strings.Add((int) Position - Header.stringOffset, ReadNullTerminatedString()); + + // To check for encryption, find every single string start position by scanning all of the definitions + var stringOffsets = Images.Select(x => x.nameIndex) .Concat(Assemblies.Select(x => x.aname.nameIndex)) .Concat(Assemblies.Select(x => x.aname.cultureIndex)) @@ -195,50 +200,57 @@ namespace Il2CppInspector .Distinct() .ToList(); - // Now confirm that all the keys are present - // If they aren't, that means one or more of the null terminators wasn't null, indicating potential encryption - // Only do this if we need to because it's very slow - if (Header.stringCount > 0 && stringOffsets.Except(Strings.Keys).Any()) { + // Now confirm that all the keys are present + // If they aren't, that means one or more of the null terminators wasn't null, indicating potential encryption + // Only do this if we need to because it's very slow + if (Header.stringCount > 0 && stringOffsets.Except(Strings.Keys).Any()) { - Console.WriteLine("Decrypting strings..."); - StatusUpdate("Decrypting strings"); + Console.WriteLine("Decrypting strings..."); + StatusUpdate("Decrypting strings"); - // There may be zero-padding at the end of the last string since counts seem to be word-aligned - // Find the true location one byte after the final character of the final string - var endOfStrings = Header.stringCount; - while (ReadByte(Header.stringOffset + endOfStrings - 1) == 0) - endOfStrings--; + // There may be zero-padding at the end of the last string since counts seem to be word-aligned + // Find the true location one byte after the final character of the final string + var endOfStrings = Header.stringCount; + while (ReadByte(Header.stringOffset + endOfStrings - 1) == 0) + endOfStrings--; - // Start again - Strings.Clear(); - Position = Header.stringOffset; + // Start again + Strings.Clear(); + Position = Header.stringOffset; - // Read in all of the strings as if they are fixed length rather than null-terminated - foreach (var offset in stringOffsets.Zip(stringOffsets.Skip(1).Append(endOfStrings), (a, b) => (current: a, next: b))) { - var encryptedString = ReadBytes(offset.next - offset.current - 1); + // Read in all of the strings as if they are fixed length rather than null-terminated + foreach (var offset in stringOffsets.Zip(stringOffsets.Skip(1).Append(endOfStrings), (a, b) => (current: a, next: b))) { + var encryptedString = ReadBytes(offset.next - offset.current - 1); - // The null terminator is the XOR key - var xorKey = ReadByte(); + // The null terminator is the XOR key + var xorKey = ReadByte(); - var decryptedString = Encoding.GetString(encryptedString.Select(b => (byte) (b ^ xorKey)).ToArray()); - Strings.Add(offset.current, decryptedString); + var decryptedString = Encoding.GetString(encryptedString.Select(b => (byte) (b ^ xorKey)).ToArray()); + Strings.Add(offset.current, decryptedString); + } + + // Write changes back in case the user wants to save the metadata file + Position = Header.stringOffset; + foreach (var str in Strings.OrderBy(s => s.Key)) + WriteNullTerminatedString(str.Value); + Flush(); + + IsModified = true; } - - // Write changes back in case the user wants to save the metadata file - Position = Header.stringOffset; - foreach (var str in Strings.OrderBy(s => s.Key)) - WriteNullTerminatedString(str.Value); - Flush(); - - IsModified = true; } - // Get all managed code string literals - var stringLiteralList = ReadArray(Header.stringLiteralOffset, Header.stringLiteralCount / Sizeof(typeof(Il2CppStringLiteral))); + // Get all string literals + var pluginGetStringLiteralsResult = PluginHooks.GetStringLiterals(this); + if (pluginGetStringLiteralsResult.IsHandled) + StringLiterals = pluginGetStringLiteralsResult.StringLiterals.ToArray(); - StringLiterals = new string[stringLiteralList.Length]; - for (var i = 0; i < stringLiteralList.Length; i++) - StringLiterals[i] = ReadFixedLengthString(Header.stringLiteralDataOffset + stringLiteralList[i].dataIndex, stringLiteralList[i].length); + else { + var stringLiteralList = ReadArray(Header.stringLiteralOffset, Header.stringLiteralCount / Sizeof(typeof(Il2CppStringLiteral))); + + StringLiterals = new string[stringLiteralList.Length]; + for (var i = 0; i < stringLiteralList.Length; i++) + StringLiterals[i] = ReadFixedLengthString(Header.stringLiteralDataOffset + stringLiteralList[i].dataIndex, stringLiteralList[i].length); + } // Post-processing hook IsModified |= PluginHooks.PostProcessMetadata(this).IsStreamModified; diff --git a/Il2CppInspector.Common/Plugins/API/V100/Hooks.cs b/Il2CppInspector.Common/Plugins/API/V100/Hooks.cs index 91686b9..35b66ea 100644 --- a/Il2CppInspector.Common/Plugins/API/V100/Hooks.cs +++ b/Il2CppInspector.Common/Plugins/API/V100/Hooks.cs @@ -28,6 +28,22 @@ namespace Il2CppInspector.PluginAPI.V100 void PostProcessMetadata(Metadata metadata, PluginPostProcessMetadataEventInfo data); } + /// + /// Fetch all of the .NET identifier strings + /// + public interface IGetStrings + { + void GetStrings(Metadata metadata, PluginGetStringsEventInfo data); + } + + /// + /// Fetch all of the (constant) string literals + /// + public interface IGetStringLiterals + { + void GetStringLiterals(Metadata metadata, PluginGetStringLiteralsEventInfo data); + } + /// /// Post-process the .NET type model to make changes after it has been fully created /// diff --git a/Il2CppInspector.Common/Plugins/API/V100/PluginEventInfo.cs b/Il2CppInspector.Common/Plugins/API/V100/PluginEventInfo.cs index 2d88551..b0c2ae6 100644 --- a/Il2CppInspector.Common/Plugins/API/V100/PluginEventInfo.cs +++ b/Il2CppInspector.Common/Plugins/API/V100/PluginEventInfo.cs @@ -10,20 +10,11 @@ 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() + public class PluginEventInfo { /// /// 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 @@ -53,45 +44,52 @@ namespace Il2CppInspector.PluginAPI.V100 /// 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 { } + public class PluginPreProcessMetadataEventInfo : PluginEventInfo + { + /// + /// Set to true to disable some validation checks by Il2CppInspector that the metadata is valid + /// + public bool SkipValidation { get; set; } + } /// /// Event info for PostProcessMetadata /// public class PluginPostProcessMetadataEventInfo : PluginEventInfo { } + /// + /// Event info for GetStrings + /// + public class PluginGetStringsEventInfo : PluginEventInfo + { + /// + /// All of the fetched strings to be returned + /// + public Dictionary Strings { get; set; } = new Dictionary(); + } + + /// + /// Event info for GetStringLiterals + /// + public class PluginGetStringLiteralsEventInfo : PluginEventInfo + { + /// + /// All of the fetched string literals to be returned + /// + public List StringLiterals { get; set; } = new List(); + } + /// /// Event info for PostProcessTypeModel /// public class PluginPostProcessTypeModelEventInfo : 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 ea32e18..04ceaca 100644 --- a/Il2CppInspector.Common/Plugins/PluginHooks.cs +++ b/Il2CppInspector.Common/Plugins/PluginHooks.cs @@ -15,16 +15,23 @@ namespace Il2CppInspector // Hooks we provide to plugins which can choose whether or not to provide implementations internal static class PluginHooks { - public static PluginPostProcessMetadataEventInfo PostProcessMetadata(Metadata metadata) - => PluginManager.Try((p, e) => p.PostProcessMetadata(metadata, e)); - public static PluginPreProcessMetadataEventInfo PreProcessMetadata(BinaryObjectStream stream) => PluginManager.Try((p, e) => { stream.Position = 0; p.PreProcessMetadata(stream, e); }); + public static PluginPostProcessMetadataEventInfo PostProcessMetadata(Metadata metadata) + => PluginManager.Try((p, e) => p.PostProcessMetadata(metadata, e)); + + public static PluginGetStringsEventInfo GetStrings(Metadata metadata) + => PluginManager.Try((p, e) => p.GetStrings(metadata, e)); + + public static PluginGetStringLiteralsEventInfo GetStringLiterals(Metadata metadata) + => PluginManager.Try((p, e) => p.GetStringLiterals(metadata, e)); + public static PluginPostProcessTypeModelEventInfo PostProcessTypeModel(TypeModel typeModel) => PluginManager.Try((p, e) => p.PostProcessTypeModel(typeModel, e)); + } } diff --git a/Il2CppInspector.Common/Plugins/PluginManager.cs b/Il2CppInspector.Common/Plugins/PluginManager.cs index 1236a68..f4a6e7e 100644 --- a/Il2CppInspector.Common/Plugins/PluginManager.cs +++ b/Il2CppInspector.Common/Plugins/PluginManager.cs @@ -209,7 +209,7 @@ 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 E Try(Action action) where E : IPluginEventInfo, new() + internal static E Try(Action action) where E : PluginEventInfo, new() { var eventInfo = new E();