Why the decompiler rejects the file
You extract a file with the .dll extension and the decompiler reports that it is not a managed assembly. The file usually is one, after a decompression step the build applied. Xamarin compresses managed assemblies by default to reduce the installed size, and the stored form is a short header followed by a compressed block rather than a portable executable image.
Any tool that begins by reading the executable header fails at that point, which is why the message describes an invalid file rather than a missing dependency.
What the stored form contains
The stored entry is small and regular. A marker at the start identifies the compression, a field records the uncompressed length, and the rest of the entry is the compressed data. The compression used for these assemblies is LZ4, chosen for the speed of decompression rather than for the ratio.
Two properties make the format easy to handle. The uncompressed length in the header lets you allocate the output buffer exactly, and the compression is a block format with no dictionary to reconstruct, so one pass produces the original assembly.
A build can also turn the compression off, and that case shows up at once: the file opens in the decompiler because it is already an assembly. When the option is on, every managed assembly in the package is stored compressed, so a file that opens and a second file that does not point to a build setting that changed between releases rather than to a damaged assembly.
Decompressing an entry in place
- Read the header before touching the payload. The marker and the length field sit at fixed positions at the start of the entry.
- Decompress the remaining bytes as one LZ4 block. The output length is the value from the header, and the assembly is the whole output.
- Confirm the result before opening it. A managed assembly begins with a recognisable executable signature, and its presence means the decompression succeeded.
- Repeat the step per entry inside an assembly store. The newer store keeps many assemblies in one blob with a manifest that records each entry's offset and size, and every entry carries its own compression state.
The manifest in a store is the index that makes the blob usable. It records how many assemblies the blob holds and where each one begins and ends, so reading a single entry means reading the manifest first and then slicing the blob. A compressed entry carries its own header, which means the store format and the per-assembly format can both appear in one package.
Dumping the loaded image instead
When the header does not match the documented layout, or when the container has been altered, the runtime remains the authority. The loader has to present a valid assembly image to the runtime for the application to start, so placing an observation point at that boundary and writing the buffer to a file gives you the uncompressed assembly without parsing anything. This route also handles a store whose manifest has been transformed, and it produces the exact image the app uses.
The trade is a device and a running app. In exchange the compression question disappears, because what the loader receives has already been decompressed.
The observation point is the call the runtime makes when it hands a buffer or a stream to the assembly loading path. What sits in that buffer is the decompressed image, complete with its header, so writing it out needs no knowledge of the container. Doing this once per assembly as the app starts produces the full set in a single run.
What decompilation gives you afterwards
An assembly that opens in a decompiler returns C# with type and method names intact in most builds. Trimming may have removed code that no path reaches, and an obfuscated build may have renamed types and methods, while the string constants and the shape of the HTTP client generally remain readable. Recovering names is a separate exercise from recovering the request logic, and it can wait until you know which names matter.
From decompiled C# to a client
Find the class that performs the network calls, list its endpoints and the values it sets before each call, then reproduce the flow outside the app. Deliverables are a Python, JavaScript or TypeScript client, an importable Postman collection and documentation, starting at $120 with most projects finished in 24 to 72 hours.
Related work
Reviewed 28 September 2026 · SReverse research desk