xamarin.androidcompressionapkassemblies

Compress Xamarin Android APK assemblies


Is it possible to compress Xamarin assemblies into assemblies.blob after decompressing them using decompress-assemblies command line tool included in xamarin-android?


Solution

  • I got familiar with the structure of assemblies.blob file from https://github.com/xamarin/xamarin-android/blob/main/Documentation/project-docs/AssemblyStores.md and used the following to be able to modify a DLL inside that store file:

    1. Decompress the file using decompress-assemblies utility

    2. Patch the DLL file needed

    3. Read the descriptor index for the compressed DLL, to get that, use assembly-store-reader, then write down the offset of the assembly that requires patching, open assemblies.blob in a hex editor, navigate to the offset of the DLL, descriptor index will be inside the uint after the compression magic uint (XALZ) Descriptor index in that example is 29.

    4. Compress the DLL file again using AssemblyCompression class: and use the descriptor index as an input to AssemblyData constructor

    5. Add this method to AssemblyStoreReader to recreate the assemblies.blob with the patched DLL:

      internal void SaveStoreToStream(Stream output)
      {
          EnsureStoreDataAvailable();
      
          // Load patched DLL.
          MemoryStream dllStream;
          using (var fs = File.Open("/location/of/patched.dll.lz4", FileMode.Open, FileAccess.Read))
          {
              dllStream = new MemoryStream();
              fs.CopyTo(dllStream);
          }
      
          // Index of the assembly to patch in assemblies.blob file.
          var patchedAssemblyIndex = 17;
          uint offsetDiff = (uint)(Assemblies[patchedAssemblyIndex].DataSize - dllStream.Length);
      
      
          using (var bw = new BinaryWriter(output))
          {
              bw.Write(ASSEMBLY_STORE_MAGIC);
              bw.Write(ASSEMBLY_STORE_FORMAT_VERSION);
              bw.Write(LocalEntryCount);
              bw.Write(GlobalEntryCount);
              bw.Write(StoreID);
      
              foreach (AssemblyStoreAssembly assembly in Assemblies)
              {
                  if (assembly.RuntimeIndex > patchedAssemblyIndex)
                  {
                      bw.Write(assembly.DataOffset - offsetDiff);
                  } else
                  {
                      bw.Write(assembly.DataOffset);
                  }
      
                  if (assembly.RuntimeIndex == patchedAssemblyIndex)
                  {
                      bw.Write((uint)dllStream.Length);
                  } else
                  {
                      bw.Write(assembly.DataSize);
                  }
      
                  bw.Write(assembly.DebugDataOffset);
                  bw.Write(assembly.DebugDataSize);
                  bw.Write(assembly.ConfigDataOffset);
                  bw.Write(assembly.ConfigDataSize);
              }
      
              foreach (AssemblyStoreHashEntry entry in GlobalIndex32)
              {
                  bw.Write(entry.Hash);
                  bw.Write(entry.MappingIndex);
                  bw.Write(entry.LocalStoreIndex);
                  bw.Write(entry.StoreID);
              }
      
              foreach (AssemblyStoreHashEntry entry in GlobalIndex64)
              {
                  bw.Write(entry.Hash);
                  bw.Write(entry.MappingIndex);
                  bw.Write(entry.LocalStoreIndex);
                  bw.Write(entry.StoreID);
              }
      
              foreach (AssemblyStoreAssembly assembly in Assemblies) {
                  if (assembly.RuntimeIndex == patchedAssemblyIndex)
                  {
                      bw.Write(dllStream.ToArray());
                  }
                  else
                  {
                      using (var stream = new MemoryStream())
                      {
                          assembly.ExtractImage(stream);
                          bw.Write(stream.ToArray());
                      }
                  }
              }
          }
      }
      
    6. Write the stream passed to the above method to a new file and that will be the assemblies.blob with the patched DLL

    7. Use apktool to recreate the APK, align and sign, then it should be working with your patched DLL