Fix #2362: resolve Xamarin compressed references in ilspycmd - #4107
Open
christophwille wants to merge 1 commit into
Open
Fix #2362: resolve Xamarin compressed references in ilspycmd#4107christophwille wants to merge 1 commit into
christophwille wants to merge 1 commit into
Conversation
Since the input file goes through ILSpyX's FileLoaderRegistry, an XALZ module passed directly to ilspycmd already decompresses on the fly. The crash in the issue is in reference resolution: a Xamarin app folder holds every assembly compressed, and UniversalAssemblyResolver opened the referenced sibling as a plain PE image. In the CLI that failure was swallowed, so the reference resolved to null and the output degraded (enum members printed as casts of raw values, and so on). The UI does not have this problem because its resolver only asks the universal resolver for the file name and then loads it through the loaders. Rather than re-implementing the resolver in the CLI, the universal resolver gains one overridable step - turning a found file into a module - and the CLI overrides it to run the same loader loop the input file uses, falling back to the plain PE path for anything no loader claims. Assisted-by: Claude:claude-fable-5-1:Claude Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2362.
What was actually broken
#2362 stayed open with the note that
ilspycmdstill lacked the Xamarin compressed-DLL ("XALZ") support the UI has had since #2137. Looking at it after #4101, there are two separate things in play:Passing an XALZ file as the input already works. Fix #3154: accept single-file bundles as ilspycmd input #4101 made every CLI mode load its input through ILSpyX's
FileLoaderRegistry, andXamarinCompressedFileLoaderis the first loader in that registry. Soilspycmd Foo.dllwith a compressed module decompresses on the fly today. Nothing pinned that with a test, so this PR adds one.References were the real gap. The stack trace in the issue is inside
UniversalAssemblyResolver.Resolve: the referenced sibling DLL was still compressed. A Xamarin app folder holds every assembly compressed, and the CLI buildsUniversalAssemblyResolverdirectly, whose private load step doesnew PEFile(fileName, stream). WiththrowOnError: falsethe resultingBadImageFormatExceptionis swallowed, the reference resolves to null, and the output degrades silently. For example, an enum from the referenced assembly prints as a cast of the raw value:The UI does not have this problem because
LoadedAssembly.MyAssemblyResolveronly asks the universal resolver for the file name and then loads that file through the registry.Approach
Constraints from the request: keep the XALZ code in ILSpyX (no duplication), and do not add detection logic to
ilspycmd(the registry order already handles it).UniversalAssemblyResolvergains oneprotected virtual MetadataFile LoadModuleFromFile(string fileName), extracted from the existing privateCreatePEFileFromFileName. BothResolveandResolveModulealready funnel through that method, so the hook covers assemblies and netmodules. This is an additive public-API change on a non-sealed class; no existing caller changes.ilspycmdaddsFileLoaderAssemblyResolver : UniversalAssemblyResolver(15 lines, inInputFileLoader.cs) that overrides the hook to run the same loader loop the input file uses, and falls back to the base PE path for anything no loader claims. A corrupt XALZ or a package (bundle/zip) therefore ends up exactly where it did before: an unresolved reference.IlspyCmdProgram.cs(GetDecompiler,ExtractResource,DecompileAsProject) collapse into oneCreateResolverhelper. Net fewer lines there.No new CLI option, no README regeneration,
-d|--dump-packageuntouched, nopackages.lock.jsonchanges (K4os.Compression.LZ4reaches the test project transitively via ILSpyX, as it already does forICSharpCode.Decompiler.Tests).Rejected alternatives
UniversalAssemblyResolverin a CLI-sideIAssemblyResolverbuilt on the publicFindAssemblyFile. Avoids touching the Decompiler, but means re-implementingResolve,ResolveModule, and both async variants, plus delegatingIsGacAssembly/IsSharedAssembly, becauseWholeProjectDecompilerandBamlAwareWholeProjectDecompilertake the resolver as theAssemblyReferenceClassifiertoo. Roughly three times the code of the hook, all of it duplicating logic that already exists.AssemblyList/LoadedAssemblystack inilspycmd. Gives full parity with the UI (including bundle-sibling resolution andParentBundlecontext), but drags inAssemblyListManager, a settings provider, and event plumbing, and changes error semantics for every CLI mode. Out of proportion for the problem.ilspycmdand decompress there. DuplicatesXamarinCompressedFileLoader, and does nothing for references, which is where the issue actually fails.Func<string, MetadataFile?>into the resolver constructor instead of a virtual method. Same effect, more surface: another constructor overload on a class that already has six optional parameters.Tests
ICSharpCode.ILSpyCmd.Tests/XamarinCompressedInputTests.csbuilds a temp folder the way a Xamarin app ships: the test assembly and theICSharpCode.Decompiler.dllit references, both LZ4-compressed behind the 12-byte XALZ header (same synthesis as the existingXamarinCompressedFileLoaderTests, no binary fixture).CompressedInputIsDecompiled: pins the input path from Fix #3154: accept single-file bundles as ilspycmd input #4101.CompressedReferenceIsResolved: red before this change (return (int)id == 1;), green after.All 44
ilspycmdtests pass; the resolver, error-recovery, XALZ loader, and isolated-decompilation fixtures inICSharpCode.Decompiler.Testspass unchanged.Not in this PR
ICSharpCode.Decompiler.PowerShellalso constructsUniversalAssemblyResolverdirectly and keeps the old behavior for compressed references. Same fix would apply if wanted.🤖 Generated with Claude Code