// See: Bin2Object/BinaryObjectStream.cs public void PreProcessImage(BinaryObjectStream stream, PluginPreProcessImageEventInfo data) { // This is called once when the user selects an application binary // Example: try to read 64-bit ELF header try { var header = stream.ReadObject <elf_header <ulong> >(); // Check for magic bytes if ((Elf)header.m_dwFormat == Elf.ELFMAG) { // Do something with ELF file Console.WriteLine("ELF image found"); } } catch { } // The file is loaded into memory so you can make edits without affecting the file // BinaryObjectStream provides many useful methods and properties // Set endianness with Endianness property // Set text encoding with Encoding property // Map reading of one primitive type to another (for both reading and writing): // This will cause all uints in the stream to be converted to ulongs when read with ReadObject<uint>() // This allows you to use one execution path to read 32-bit and 64-bit files stream.AddPrimitiveMapping(typeof(ulong), typeof(uint)); // Map reading of one object to another (for both reading and writing): // This will cause all fields that exist in ObjectInMemory to be populated with // the correspondingly named fields in ObjectInStream // This is useful if items have been re-ordered via obfuscation stream.AddObjectMapping(typeof(ObjectInMemory), typeof(ObjectInStream)); // With the above mapping, this will read an ObjectInStream and translate it to an ObjectInMemory try { var obj = stream.ReadObject <ObjectInMemory>(); } catch { } // Mappings will be persisted for the entire lifetime of the stream, // so you can use them to alter the order in which Il2CppInspector reads fields in structs // Clear mappings: stream.Reader.PrimitiveMappings.Clear(); stream.Reader.ObjectMappings.Clear(); stream.Writer.PrimitiveMappings.Clear(); stream.Writer.PrimitiveMappings.Clear(); // Other useful methods: // ReadObject<T>, ReadArray<T>, ReadNullTerminatedString, ReadFixedLengthString // + equivalent write methods // You can also use a number of attributes: // ArrayLengthAttribute, StringAttribute, VersionAttribute, SkipWhenReadingAttribute // Set data.IsStreamModified if you change the stream contents }
public override IFileFormatStream this[uint index] { get { Position = 0x8 + 0x14 * index; // sizeof(FatHeader), sizeof(FatArch) Endianness = Endianness.Big; var arch = ReadObject <FatArch>(); Position = arch.Offset; Endianness = Endianness.Little; using var s = new BinaryObjectStream(ReadBytes((int)arch.Size)); return((IFileFormatStream)MachOReader32.Load(s, LoadOptions, OnStatusUpdate) ?? MachOReader64.Load(s, LoadOptions, OnStatusUpdate)); } }
// See: Bin2Object/BinaryObjectStream.cs public void PreProcessMetadata(BinaryObjectStream stream, PluginPreProcessMetadataEventInfo info) { // Example: check if metadata has correct signature if (stream.ReadUInt32() != Il2CppConstants.MetadataSignature) { Console.Error.WriteLine("Metadata signature is invalid"); } // The file is loaded into memory so you can make edits without affecting the file // See PreProcessImage below for information about using BinaryObjectStream for de-obfuscation // Set info.IsStreamModified if you change the stream contents }
// This executes as soon as the raw global-metadata.dat has been read from storage, // before any attempt is made to analyze its contents public void PreProcessMetadata(BinaryObjectStream stream, PluginPreProcessMetadataEventInfo info) { stream.Position = 21; var key = presetKey.Value == "custom"? customKey.Value : PresetKeys[presetKey.Value]; if (string.IsNullOrEmpty(key)) { throw new ArgumentException("Game version or decryption key must be specified"); } var sig = stream.ReadBytes(4); sig = sig.Select((b, i) => (byte)(b - key[i % key.Length])).ToArray(); if (BitConverter.ToUInt32(sig) != Il2CppConstants.MetadataSignature) { return; } PluginServices.For(this).StatusUpdate("Decrypting metadata"); // Subtract key bytes from metadata bytes, skipping the first bytes, // and cycling through the key bytes repeatedly var bytesToSkipAtStart = 21; var length = stream.Length; var bytes = stream.ToArray(); var keyBytes = Encoding.ASCII.GetBytes(key); for (int index = 0, pos = bytesToSkipAtStart; pos < length; index = (index + 1) % key.Length, pos++) { bytes[pos] -= keyBytes[index]; } // We replace the loaded global-metadata.dat with the newly decrypted version, // allowing Il2CppInspector to analyze it as normal stream.Write(0, bytes.Skip(bytesToSkipAtStart).ToArray()); info.IsStreamModified = true; }
public static PluginPreProcessImageEventInfo PreProcessImage(BinaryObjectStream stream) => PluginManager.Try <ILoadPipeline, PluginPreProcessImageEventInfo>((p, e) => p.PreProcessImage(stream, e));
public static PluginPreProcessMetadataEventInfo PreProcessMetadata(BinaryObjectStream stream) => PluginManager.Try <ILoadPipeline, PluginPreProcessMetadataEventInfo>((p, e) => { stream.Position = 0; p.PreProcessMetadata(stream, e); });
// This executes as soon as the raw global-metadata.dat has been read from storage, // before any attempt is made to analyze its contents // We use it to call UnityPlayer.dll to decrypt the file in memory public void PreProcessMetadata(BinaryObjectStream stream, PluginPreProcessMetadataEventInfo info) { // Assume that your plugin is enabled regardless of what is loading // Therefore, we don't want to process global-metadata.dat files that are not for us! // miHoYo metadata has an invalid signature at the start of the file so we use that as the criteria IsOurs = stream.ReadUInt32(0) != Il2CppConstants.MetadataSignature; if (!IsOurs) { return; } // The DWORD 0x4008 bytes from the end should be an offset to itself var lastBlockPointer = stream.Length - 0x4008; IsOurs = stream.ReadUInt32(lastBlockPointer) == lastBlockPointer; if (!IsOurs) { return; } // Tell the user what is happening in case it takes a while PluginServices.For(this).StatusUpdate("Decrypting metadata"); // Create a delegate which internally is a function pointer to the DecryptMetadata function in the DLL var pDecryptMetadata = (DecryptMetadata)Marshal.GetDelegateForFunctionPointer(ModuleBase + Offsets[game.Value].DecryptMetadata, typeof(DecryptMetadata)); // Call the delegate with the encrypted metadata byte array and length as arguments var decryptedBytesUnmanaged = pDecryptMetadata(stream.ToArray(), (int)stream.Length); // Copy the decrypted data back from unmanaged memory to a byte array metadataBlob = new byte[stream.Length]; Marshal.Copy(decryptedBytesUnmanaged, metadataBlob, 0, (int)stream.Length); // Genshin Impact adds some spice by encrypting 0x10 bytes for every 'step' bytes of the metadata // with a simple XOR key, so we need to apply that too if (game.Value.StartsWith("genshin-impact")) { var key = new byte [] { 0xAD, 0x2F, 0x42, 0x30, 0x67, 0x04, 0xB0, 0x9C, 0x9D, 0x2A, 0xC0, 0xBA, 0x0E, 0xBF, 0xA5, 0x68 }; // The step is based on the file size var step = (int)(stream.Length >> 14) << 6; for (var pos = 0; pos < metadataBlob.Length; pos += step) { for (var b = 0; b < 0x10; b++) { metadataBlob[pos + b] ^= key[b]; } } } // We replace the loaded global-metadata.dat with the newly decrypted version, // allowing Il2CppInspector to analyze it as normal stream.Write(0, metadataBlob); // Some types have reordered fields - these calls tell Il2CppInspector what the correct field order is // See MappedTypes.cs for details stream.AddObjectMapping(typeof(Il2CppInspector.Il2CppGlobalMetadataHeader), typeof(Il2CppGlobalMetadataHeader)); stream.AddObjectMapping(typeof(Il2CppInspector.Il2CppTypeDefinition), typeof(Il2CppTypeDefinition)); stream.AddObjectMapping(typeof(Il2CppInspector.Il2CppMethodDefinition), typeof(Il2CppMethodDefinition)); stream.AddObjectMapping(typeof(Il2CppInspector.Il2CppFieldDefinition), typeof(Il2CppFieldDefinition)); stream.AddObjectMapping(typeof(Il2CppInspector.Il2CppPropertyDefinition), typeof(Il2CppPropertyDefinition)); // We tell Il2CppInspector that we have taken care of the metadata // IsStreamModified marks the original data stream as modified so that the user is able to save the changes // SkipValidation tells Il2CppInspector not to check this global-metadata.dat for validity; // if we don't set this, it will report that the metadata is invalid info.IsStreamModified = true; info.SkipValidation = true; }