List <TypeMapModule> LoadMapModules(string filePath)
        {
            ulong moduleCount = (ulong)ELF.GetUInt32(ModuleCountSymbolName);

            // MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModule)
            ulong size;

            size  = 16;                                       // module_uuid
            size += GetPaddedSize <uint> (size);              // entry_count
            size += GetPaddedSize <uint> (size);              // duplicate_count
            size += GetPaddedSize <string> (size);            // map (pointer)
            size += GetPaddedSize <string> (size);            // duplicate_map (pointer)
            size += GetPaddedSize <string> (size);            // assembly_name (pointer)
            size += GetPaddedSize <string> (size);            // image (pointer)
            size += GetPaddedSize <uint> (size);              // java_name_width
            size += GetPaddedSize <string> (size);            // java_map (pointer)

            byte[] moduleData = ELF.GetData(MapModulesSymbolName);
            if (moduleData.Length == 0)
            {
                throw new InvalidOperationException($"{filePath} doesn't have a valid '{MapModulesSymbolName}' symbol");
            }

            ulong calculatedModuleCount = (ulong)moduleData.Length / size;

            if (calculatedModuleCount != moduleCount)
            {
                throw new InvalidOperationException($"{filePath} has invalid '{ModuleCountSymbolName}' symbol value ({moduleCount}), '{MapModulesSymbolName}' size indicates there are {calculatedModuleCount} managedToJava instead");
            }

            var   ret    = new List <TypeMapModule> ();
            ulong offset = 0;

            for (ulong i = 0; i < moduleCount; i++)
            {
                Log.Debug($"Module {i + 1}");
                var module = new TypeMapModule();

                byte[] mvid = new byte[16];
                Array.Copy(moduleData, (int)offset, mvid, 0, mvid.Length);
                module.module_uuid = new Guid(mvid);
                offset            += (ulong)mvid.Length;
                Log.Debug($"  module_uuid == {module.module_uuid}");

                module.entry_count = ReadUInt32(moduleData, ref offset);
                Log.Debug($"  entry_count == {module.entry_count}");

                module.duplicate_count = ReadUInt32(moduleData, ref offset);
                Log.Debug($"  duplicate_count == {module.duplicate_count}");

                // MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
                ulong pointer = ReadPointer(moduleData, ref offset);
                size  = 0;
                size += GetPaddedSize <uint> (size);                // type_token_id
                size += GetPaddedSize <uint> (size);                // java_map_index

                ulong  mapSize = size * module.entry_count;
                byte[] data    = ELF.GetData(pointer, mapSize);

                module.map = new List <TypeMapModuleEntry> ();
                ReadMapEntries(module.map, data, module.entry_count);

                // MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
                pointer = ReadPointer(moduleData, ref offset);
                if (pointer != 0)
                {
                    mapSize = size * module.duplicate_count;
                    data    = ELF.GetData(pointer, mapSize);
                    module.duplicate_map = new List <TypeMapModuleEntry> ();
                    ReadMapEntries(module.duplicate_map, data, module.duplicate_count);
                }

                pointer = ReadPointer(moduleData, ref offset);
                module.assembly_name = ELF.GetASCIIZ(pointer);
                Log.Debug($"  assembly_name == {module.assembly_name}");
                Log.Debug("");

                // Read the values to properly adjust the offset taking padding into account
                ReadPointer(moduleData, ref offset);
                ReadUInt32(moduleData, ref offset);
                ReadPointer(moduleData, ref offset);

                ret.Add(module);
            }

            return(ret);

            void ReadMapEntries(List <TypeMapModuleEntry> map, byte[] inputData, uint entryCount)
            {
                ulong mapOffset = 0;

                for (uint i = 0; i < entryCount; i++)
                {
                    var entry = new TypeMapModuleEntry {
                        type_token_id  = ReadUInt32(inputData, ref mapOffset),
                        java_map_index = ReadUInt32(inputData, ref mapOffset)
                    };

                    map.Add(entry);
                }
            }
        }
        bool DoConvert()
        {
            if (modules == null || javaTypes == null)
            {
                Log.Warning($"{Description}: cannot convert maps, no data");
                return(false);
            }

            string filePath      = ELF.FilePath;
            var    managedToJava = new List <MapEntry> ();
            uint   index         = 0;

            bool somethingFailed = false;

            foreach (TypeMapModule m in modules)
            {
                ConvertManagedMap(m, EnsureMap(m), isDuplicate: false);
                if (m.duplicate_map != null)
                {
                    if (!ConvertManagedMap(m, m.duplicate_map, isDuplicate: true))
                    {
                        somethingFailed = true;
                    }
                }

                index++;
            }

            if (somethingFailed)
            {
                return(false);
            }

            index = 0;
            var javaToManaged = new List <MapEntry> ();

            foreach (TypeMapJava tmj in javaTypes)
            {
                (bool success, TypeMapModule? module, bool isGeneric, bool isDuplicate) = FindManagedType(tmj.module_index, tmj.type_token_id);
                if (!success)
                {
                    somethingFailed = true;
                    continue;
                }

                if (module == null)
                {
                    throw new InvalidOperationException("module must not be null here");
                }

                javaToManaged.Add(
                    new MapEntry(
                        MakeManagedType(module.module_uuid, tmj.type_token_id, module.assembly_name, filePath, isGeneric, isDuplicate),
                        new MapJavaType(tmj.java_name, filePath)
                        )
                    );
                index++;
            }

            map = MakeMap(managedToJava, javaToManaged);
            return(true);

            bool ConvertManagedMap(TypeMapModule module, List <TypeMapModuleEntry> map, bool isDuplicate)
            {
                foreach (TypeMapModuleEntry entry in map)
                {
                    TypeMapJava java;

                    if ((uint)javaTypes.Count <= entry.java_map_index)
                    {
                        Log.Error($"Managed type {entry.type_token_id} in module {module.assembly_name} ({module.module_uuid}) has invalid Java map index {entry.java_map_index}");
                        return(false);
                    }
                    java = javaTypes[(int)entry.java_map_index];
                    managedToJava.Add(
                        new MapEntry(
                            MakeManagedType(module.module_uuid, entry.type_token_id, module.assembly_name, filePath, isGeneric: false, isDuplicate: isDuplicate),
                            new MapJavaType(java.java_name, filePath)
                            )
                        );
                }
                return(true);
            }

            (bool success, TypeMapModule?module, bool isGeneric, bool isDuplicate) FindManagedType(uint moduleIndex, uint tokenID)
            {
                if (moduleIndex >= (uint)modules.Count)
                {
                    Log.Error($"Invalid module index {moduleIndex} for type token ID {tokenID} at Java map index {index}");
                    return(false, null, false, false);
                }

                TypeMapModule m = modules[(int)moduleIndex];

                if (tokenID == 0)
                {
                    return(true, m, true, false);
                }

                foreach (TypeMapModuleEntry entry in EnsureMap(m))
                {
                    if (entry.type_token_id == tokenID)
                    {
                        return(true, m, false, false);
                    }
                }

                if (m.duplicate_map != null)
                {
                    foreach (TypeMapModuleEntry entry in m.duplicate_map)
                    {
                        if (entry.type_token_id == tokenID)
                        {
                            return(true, m, false, true);
                        }
                    }
                }

                Log.Error($"Module {m.assembly_name} ({m.module_uuid}) at index {moduleIndex} doesn't contain an entry for managed type with token ID {tokenID}");
                return(false, null, false, false);
            }

            List <TypeMapModuleEntry> EnsureMap(TypeMapModule m)
            {
                if (m.map == null)
                {
                    throw new InvalidOperationException($"Module {m.module_uuid} ({m.assembly_name}) has no map?");
                }
                return(m.map);
            }
        }