From 39d230f0221c7a02f4ec77a67576182621853047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Pr=C3=A4hauser?= Date: Sun, 1 Aug 2021 19:28:21 +0200 Subject: [PATCH 1/5] Support loading compressed Xamarin assemblies, see #2137 Extend class LoadedAssembly to detect and load compressed Xamarin assemblies if direct loading of assembly fails. Requires Nuget pkg K4os.Compression.LZ4 for LZ4 decompression. --- ILSpy/ILSpy.csproj | 1 + ILSpy/LoadedAssembly.cs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 4757e9ffe..3d09cdfe4 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -54,6 +54,7 @@ + diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index ad1dae9dc..02b342f59 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -35,6 +35,8 @@ using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.Util; using ICSharpCode.ILSpy.Options; +using K4os.Compression.LZ4; + #nullable enable namespace ICSharpCode.ILSpy @@ -323,6 +325,15 @@ namespace ICSharpCode.ILSpy { loadAssemblyException = ex; } + // Maybe its a compressed Xamarin/Mono assembly, see https://github.com/xamarin/xamarin-android/pull/4686 + try + { + return LoadCompressedAssembly(fileName); + } + catch (InvalidDataException) + { + // Not a compressed module, try other options below + } // If it's not a .NET module, maybe it's a single-file bundle var bundle = LoadedPackage.FromBundle(fileName); if (bundle != null) @@ -365,6 +376,31 @@ namespace ICSharpCode.ILSpy return new LoadResult(module); } + LoadResult LoadCompressedAssembly(string filename) + { + const uint CompressedDataMagic = 0x5A4C4158; // Magic used for Xamarin compressed module header ('XALZ', little-endian) + using (var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read)) + using (var fileReader = new BinaryReader(fileStream)) + { + // Read compressed file header + var magic = fileReader.ReadUInt32(); + if (magic != CompressedDataMagic) + throw new InvalidDataException($"Xamarin compressed module header magic {magic} does not match expected {CompressedDataMagic}"); + var descIdx = fileReader.ReadUInt32(); + var uncLen = fileReader.ReadUInt32(); + // fileReader stream position is now at compressed module data + var src = fileReader.ReadBytes((int)uncLen); // compressed length must be smaller than uncompressed length + var dst = new byte[(int)uncLen]; + // Decompress + LZ4Codec.Decode(src, 0, src.Length, dst, 0, dst.Length); + // Load module from decompressed data buffer + using (var modData = new MemoryStream(dst, writable: false)) + { + return LoadAssembly(modData, PEStreamOptions.PrefetchEntireImage); + } + } + } + IDebugInfoProvider? LoadDebugInfo(PEFile module) { if (DecompilerSettingsPanel.CurrentDecompilerSettings.UseDebugSymbols) From d04c7fd3729b63e1a7bd2b8284fcf48619f253eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Pr=C3=A4hauser?= Date: Wed, 4 Aug 2021 20:04:31 +0200 Subject: [PATCH 2/5] Add license notice for pkg K4os.Compression.LZ4 --- doc/third-party-notices.txt | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/doc/third-party-notices.txt b/doc/third-party-notices.txt index 39961ecc9..3f54b3b86 100644 --- a/doc/third-party-notices.txt +++ b/doc/third-party-notices.txt @@ -415,6 +415,34 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +License Notice for K4os.Compression.LZ4 (part of ILSpy) +--------------------------- + +https://github.com/MiloszKrajewski/K4os.Compression.LZ4/blob/master/LICENSE + +MIT License + +Copyright (c) 2017 Milosz Krajewski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + License Notice for LightJson (part of ICSharpCode.Decompiler) --------------------------- From 95f9908823023f7266eb8abc5812486d56598fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Pr=C3=A4hauser?= Date: Wed, 4 Aug 2021 20:05:26 +0200 Subject: [PATCH 3/5] Ensure we read all of compressed data in LoadCompressedAssembly --- ILSpy/LoadedAssembly.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index 02b342f59..42361ed75 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -389,7 +389,7 @@ namespace ICSharpCode.ILSpy var descIdx = fileReader.ReadUInt32(); var uncLen = fileReader.ReadUInt32(); // fileReader stream position is now at compressed module data - var src = fileReader.ReadBytes((int)uncLen); // compressed length must be smaller than uncompressed length + var src = fileReader.ReadBytes((int)fileStream.Length); // Ensure we read all of compressed data var dst = new byte[(int)uncLen]; // Decompress LZ4Codec.Decode(src, 0, src.Length, dst, 0, dst.Length); From 6cda7cceb1b9ec21c0efe4a87984d228b8dcaaab Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 4 Aug 2021 20:13:56 +0200 Subject: [PATCH 4/5] Use FileName parameter instead of LoadedAssembly.fileName --- ILSpy/LoadedAssembly.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index 42361ed75..045b3816a 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -376,7 +376,7 @@ namespace ICSharpCode.ILSpy return new LoadResult(module); } - LoadResult LoadCompressedAssembly(string filename) + LoadResult LoadCompressedAssembly(string fileName) { const uint CompressedDataMagic = 0x5A4C4158; // Magic used for Xamarin compressed module header ('XALZ', little-endian) using (var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read)) From 27f56a9253b4b7f5934e9e1b15e451dc809da87b Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 7 Aug 2021 20:22:03 +0200 Subject: [PATCH 5/5] Use ArrayPool.Shared for decompression. --- ILSpy/ILSpy.csproj | 1 + ILSpy/LoadedAssembly.cs | 32 ++++++++++++++++++++++---------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 3d09cdfe4..66bbb2c0d 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -55,6 +55,7 @@ + diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs index 045b3816a..320a7f581 100644 --- a/ILSpy/LoadedAssembly.cs +++ b/ILSpy/LoadedAssembly.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -386,17 +387,28 @@ namespace ICSharpCode.ILSpy var magic = fileReader.ReadUInt32(); if (magic != CompressedDataMagic) throw new InvalidDataException($"Xamarin compressed module header magic {magic} does not match expected {CompressedDataMagic}"); - var descIdx = fileReader.ReadUInt32(); - var uncLen = fileReader.ReadUInt32(); - // fileReader stream position is now at compressed module data - var src = fileReader.ReadBytes((int)fileStream.Length); // Ensure we read all of compressed data - var dst = new byte[(int)uncLen]; - // Decompress - LZ4Codec.Decode(src, 0, src.Length, dst, 0, dst.Length); - // Load module from decompressed data buffer - using (var modData = new MemoryStream(dst, writable: false)) + _ = fileReader.ReadUInt32(); // skip index into descriptor table, unused + int uncompressedLength = (int)fileReader.ReadUInt32(); + int compressedLength = (int)fileStream.Length; // Ensure we read all of compressed data + ArrayPool pool = ArrayPool.Shared; + var src = pool.Rent(compressedLength); + var dst = pool.Rent(uncompressedLength); + try + { + // fileReader stream position is now at compressed module data + fileStream.Read(src, 0, compressedLength); + // Decompress + LZ4Codec.Decode(src, 0, compressedLength, dst, 0, uncompressedLength); + // Load module from decompressed data buffer + using (var uncompressedStream = new MemoryStream(dst, writable: false)) + { + return LoadAssembly(uncompressedStream, PEStreamOptions.PrefetchEntireImage); + } + } + finally { - return LoadAssembly(modData, PEStreamOptions.PrefetchEntireImage); + pool.Return(dst); + pool.Return(src); } } }