local AndroidInfo = require "Androidinfo"
local MainType = AndroidInfo.platform and gg.TYPE_QWORD or gg.TYPE_DWORD
local pointSize = AndroidInfo.platform and 8 or 4

---@class Searcher
---Universal searcher module for locating Il2Cpp and metadata components in memory
local Searcher = {
    searchWord = ":EnsureCapacity",

    ---Find global metadata in memory using various search strategies
    -- @param self Searcher The Searcher instance
    -- @return number Start address of global metadata
    -- @return number End address of global metadata
    FindGlobalMetaData = function(self)
        gg.clearResults()
        gg.setRanges(gg.REGION_C_ALLOC | gg.REGION_ANONYMOUS |
                         gg.REGION_OTHER)
        local globalMetadata = gg.getRangesList('global-metadata.dat')
        if not self:IsValidData(globalMetadata) then
            globalMetadata = gg.getRangesList("dev/zero")
        end
        if not self:IsValidData(globalMetadata) then
            globalMetadata = {}
            gg.clearResults()
            gg.searchNumber(self.searchWord, gg.TYPE_BYTE)
            gg.refineNumber(self.searchWord:sub(1, 2), gg.TYPE_BYTE)
            local EnsureCapacity = gg.getResults(gg.getResultsCount())
            gg.clearResults()
            for k, v in ipairs(gg.getRangesList()) do
                if (v.state == 'Ca' or v.state == 'A' or v.state == 'Cd' or v.state == 'Cb' or v.state == 'Ch' or
                    v.state == 'O') then
                    for key, val in ipairs(EnsureCapacity) do
                        globalMetadata[#globalMetadata + 1] =
                            (Il2Cpp.FixValue(v.start) <= Il2Cpp.FixValue(val.address) and Il2Cpp.FixValue(val.address) <
                                Il2Cpp.FixValue(v['end'])) and v or nil
                    end
                end
            end
        end
        local value = -89056337
        if gg.getValues({{address = globalMetadata[1].start, flags = 4}})[1].value ~= value then
            gg.searchNumber(value, 4, false, gg.SIGN_EQUAL, globalMetadata[1].start, globalMetadata[#globalMetadata]['end'])
            if gg.getResultsCount() > 0 then
                globalMetadata[1].start = gg.getResults(1)[1].address
            end
        end
        return type(globalMetadata) == "table" and globalMetadata[1].start, globalMetadata[#globalMetadata]['end'] or 0, 0
    end,

    ---Check if global metadata contains valid data by searching for the signature
    -- @param self Searcher The Searcher instance
    -- @param globalMetadata table Table of memory ranges to check
    -- @return boolean True if valid data is found, false otherwise
    IsValidData = function(self, globalMetadata)
        if #globalMetadata ~= 0 then
            gg.searchNumber(self.searchWord, gg.TYPE_BYTE, false, gg.SIGN_EQUAL, globalMetadata[1].start,
                globalMetadata[#globalMetadata]['end'])
            if gg.getResultsCount() > 0 then
                gg.clearResults()
                return true
            end
        end
        return false
    end,

    ---Find Il2Cpp library in memory using various search strategies
    -- @return number Start address of Il2Cpp library
    -- @return number End address of Il2Cpp library
    FindIl2cpp = function()
        local il2cpp = gg.getRangesList('libil2cpp.so')
        if #il2cpp == 0 then
            il2cpp = gg.getRangesList('split_config.')
            local _il2cpp = {}
            gg.setRanges(gg.REGION_CODE_APP)
            for k, v in ipairs(il2cpp) do
                if (v.state == 'Xa') then
                    gg.searchNumber(':il2cpp', gg.TYPE_BYTE, false, gg.SIGN_EQUAL, v.start, v['end'])
                    if (gg.getResultsCount() > 0) then
                        _il2cpp[#_il2cpp + 1] = v
                        gg.clearResults()
                    end
                end
            end
            il2cpp = _il2cpp
        else
            local _il2cpp = {}
            for k,v in ipairs(il2cpp) do
                local Value = gg.getValues({{address = v.start, flags = 4}})[1].value
                if Value==0x464C457F or Value==263434879 then
                --if (string.find(v.type, "..x.") or v.state == "Xa") then
                    _il2cpp[#_il2cpp + 1] = v
                end
            end
            il2cpp[1] = _il2cpp[#_il2cpp]
            --il2cpp = _il2cpp
        end       
        return il2cpp[1].start, il2cpp[#il2cpp]['end']
    end,

    ---Locate and initialize Il2Cpp metadata registration structures
    -- @return table Table containing metadata registration information
    Il2CppMetadataRegistration = function()
        ---Check if an address points to a valid image name
        -- @param addr number Memory address to check
        -- @return string|boolean Image name if valid, false otherwise
        local function isImage(addr)
            local imageStr = Il2Cpp.Utf8ToString(Il2Cpp.GetPtr(addr))
            local check = string.find(imageStr, ".-%.dll") or string.find(imageStr, "__Generated")
            return check and imageStr
        end
        
        -- Set pointer sizes based on version and platform
        Il2Cpp.classPointer = Il2Cpp.Version < 27 and (AndroidInfo.platform and 24 or 12) or (AndroidInfo.platform and 40 or 20);
        Il2Cpp.imagePointer = Il2Cpp.Version < 27 and (AndroidInfo.platform and 72 or 36) or (AndroidInfo.platform and 24 or 12);
        
        -- Get global metadata range
        local gmt =Il2Cpp.Meta.metaStart

        gg.clearResults();
	    gg.setRanges(gg.REGION_C_BSS | gg.REGION_ANONYMOUS | gg.REGION_OTHER);
	    gg.searchNumber(gmt, Il2Cpp.MainType, nil, nil, Il2Cpp.il2cppStart, -1, 1);
	    if gg.getResultsCount() == 0 and AndroidInfo.platform and AndroidInfo.sdk >= 30 then
            gg.searchNumber(tostring(gmt | 0xB400000000000000), Il2Cpp.MainType, nil, nil, Il2Cpp.il2cppStart, -1, 1);
        end
        local t = gg.getResults(1)
        gg.clearResults();
        local Range, a = {}, t[1].address - (10 * Il2Cpp.pointSize)
        for i = 1, 20 do
            Range[i] = {address = a + (i * Il2Cpp.pointSize), flags = Il2Cpp.MainType}
        end
        local res = {}
        for i, v in ipairs(gg.getValues(Range)) do
            local addr = Il2Cpp.FixValue(v.value)
            if addr ~= gmt then
                res[#res+1] = {address = addr}
            end
        end
        for i, v in ipairs(gg.getValuesRange(res)) do
            if v == "Cd" or v == "O" then
                Il2Cpp.il2cppReg = res[i].address
                Il2Cpp.metaReg = res[i+1].address
                Il2Cpp.typeCount = gg.getValues({{address = res[i+1].address + Il2Cpp.pointSize * 12, flags = Il2Cpp.MainType}})[1].value
                break
            end
        end

        local imgAddr = t[1].address + Il2Cpp.imagePointer
        local results = gg.getValues({
            {address=(Il2Cpp.GetPtr(imgAddr) + 16),flags=Il2Cpp.MainType},
            {address=Il2Cpp.GetPtr(t[1].address + Il2Cpp.classPointer),flags=Il2Cpp.MainType}});
        if Il2Cpp.GetPtr(results[1].value) == 0 then
            results[1] = gg.getValues({{address=(Il2Cpp.GetPtr(imgAddr) + 16 + 8),flags=Il2Cpp.MainType}})[1];
        end
        if Il2Cpp.GetPtr(results[2].value) == 0 then
            results[2].address = Il2Cpp.GetPtr(t[1].address + Il2Cpp.classPointer + (4 * Il2Cpp.pointSize))
        end
        local addr = results[1].address
        Il2Cpp.typeDef = results[2].address

        for i = 1, 100 do
            if not Il2Cpp.imageCount then
                local count = Il2Cpp.GetPtr(t[1].address + (i * Il2Cpp.pointSize))
                if count < 1000 then
                    Il2Cpp.imageCount = count
                end
            end
            if not Il2Cpp.imageDef then
                local addr = Il2Cpp.GetPtr(Il2Cpp.GetPtr(Il2Cpp.typeDef + (i * Il2Cpp.pointSize)))
                local image = isImage(addr)
                if image then
                    Il2Cpp.imageDef = addr
                end
            end
            if Il2Cpp.imageDef then
                local addr = Il2Cpp.imageDef + (i * Il2Cpp.pointSize)
                if isImage(addr) then
                    Il2Cpp.imageSize = addr - Il2Cpp.imageDef
                    break
                end
            end
        end
        
        Il2Cpp.pMetadataRegistration = Il2Cpp.Il2CppMetadataRegistration(Il2Cpp.metaReg)
        Il2Cpp.pCodeRegistration = Il2Cpp.Il2CppCodeRegistration(Il2Cpp.il2cppReg)
        Il2Cpp.stringDef = Il2Cpp.Meta.Header.stringOffset
        
        --[[
        if Il2Cpp.Utf8ToString(Il2Cpp.Meta.Header.stringOffset, 100):find(".dll") then
            Il2Cpp.stringDef = Il2Cpp.Meta.Header.stringOffset
            return
        end
        if (Il2Cpp.Version < 27) then
            Il2Cpp.stringDef = Il2Cpp.FixValue(Il2Cpp.GetPtr(Il2Cpp.imageDef + ((AndroidInfo.platform and 8) or 0)));
            return
        else
            address = Il2Cpp.GetPtr(Il2Cpp.GetPtr(Il2Cpp.imageDef) + (AndroidInfo.platform and 16 or 8)) + (AndroidInfo.platform and 24 or 16);
        end
        Il2Cpp.stringDef = Il2Cpp.GetPtr(address);
        ]]
    end
}

return Searcher