Jump to content


  • Posts

  • Joined

  • Last visited

  • Days Won


Everything posted by CmP

  1. CmP


    When GG reinstalls itself, version of the apk is set to a random one. That's where version "239.0" came from in your case.
  2. Explain the algorithm that you need to implement. First step is, for example, new search for values of dword type that are equal to 12345. What are the next steps? To get address of first result and do something with it? Do you need to search for values that are equal to address of first result or what?
  3. In the description of the video there is the following: So at the very least author of the video has specified who and where originally posted the method. Of course it would be more reasonable to include link to this thread, but at least there is some mention at all.
  4. Text search finds only exact matches of searched string, so yes, it is case-sensitive and it means that "ExAmple" won't be found by searching "example".
  5. Disassembler and assembler in GG have various differences from regular ones, some things are implemented in a simple way that is not as convenient for the user. In this case it is modified immediate constants in which 8-bit constant and rotation fields are just exposed to the user. General approach to handling those differences is to assemble the instruction using any other assembler that works and then to check how GG disassembles the instruction. The format of GG's disassembler output is the one that is accepted by GG's assembler. For example, your case, instruction "ADD R12, R12, #0xFC0": 1) assembling the instruction using armconverter results in "3FCD8CE2"; 2) editing a value to "3FCD8CE2r" in GG shows that GG disassembles the instruction as "ADD R12, R12, #63, 26"; 3) trying "~A ADD R12, R12, #63, 26" as input for GG assembler results in confirming that it works as expected, instruction gets assembled correctly. "#63, 26" are parts of ARM 12-bit modified immediate constant. 63 is 8-bit constant, 26 is amount of bits for right rotation (ROR) of the 8-bit constant to get the result. And indeed, 63 ROR 26 = 4032 (0xFC0) for 32-bit values as can be checked with the following tool: https://onlinetoolz.net/bitshift#base=10&value=63&bits=32&steps=26&dir=r&type=circ&allsteps=0
  6. refineNumber function does nothing when search results list is empty as specified in the description of the function in the documentation: To start new search use searchNumber function that works like described below:
  7. Yes, automation of searching for tagged pointers to an address or a range of addresses is pretty straightforward. Here is an example of the code for adding tag to a pointer: address = 0x1122334455 taggedAddress = address | 0xB400000000000000 Next example shows how code for tagging pointers can be organized to follow good practices: local POINTER_TAG = 0xB4 local TAG_SHIFT = 56 local function addTag(pointer) return pointer | (POINTER_TAG << TAG_SHIFT) end The usage of the code above can be illustrated with modified version of Pointer scan script to search for tagged pointers: -- Modified version of the following script to search for tagged pointers -- https://gameguardian.net/forum/files/file/30-pointer-scan/ local POINTER_TAG = 0xB4 local TAG_SHIFT = 56 local function addTag(pointer) return pointer | (POINTER_TAG << TAG_SHIFT) end local d = {'', '512'} while true do d = gg.prompt({'Address of value in hex. E.g. BAADBEEF', 'Maximal possible offset. E.g. "100" or "100h" for hex'}, d, {'number', 'number'}) if d == nil then break end local address = tonumber(d[1], 16) if address == nil then gg.alert('Address must be hex number') goto continue_1 end address = addTag(address) local offset, hex = string.gsub(d[2], 'h', '') if hex == 0 then offset = tonumber(offset) else offset = tonumber(offset, 16) end if offset == nil then gg.alert('Offset must be decimal or hex number') goto continue_1 end local search = (address - offset)..'~'..address gg.searchNumber(search, gg.TYPE_QWORD) break; ::continue_1:: end tagged_pointer_scan.lua
  8. CmP

    gg has error

  9. He means the topic itself, I think. That it shouldn't have passed moderation. But then there is a question of whether this particular topic was delayed by the system for approval in the first place.
  10. Use "getValues" instead of "loadResults" followed with "getResults". And more importantly, instead of calling the function for each value, construct table with all values and call the function once for that table. Example of how it should not be done: local results = gg.getResults(gg.getResultsCount()) for _, result in ipairs(results) do local checkedValue = {{address = result.address + 8, flags = result.flags}} checkedValue = gg.getValues(checkedValue) local value = checkedValue[1].value -- some further processing according to value end Example of what should be done instead: local results = gg.getResults(gg.getResultsCount()) local checkedValues = {} for index, result in ipairs(results) do checkedValues[index] = {address = result.address + 8, flags = result.flags} end checkedValues = gg.getValues(checkedValues) for i, v in ipairs(checkedValues) do local value = v.value -- some further processing according to value end
  11. CmP

    how to do it on the left?

    Direct answer to the question is present in Lua reference manual: https://www.lua.org/manual/5.3/manual.html#pdf-string.sub
  12. It is enough to change the following in the code of the script to make it work for ARM64: - "gg.ASM_ARM" in line 43 to "gg.ASM_ARM64"; - "~A" in line 66 to "~A8". The script below includes listed changes and few other ones (like "ARM" to "ARM64" in output). See if it works for you. arm64_converter.lua
  13. CmP

    Can the administrator help

    GG is not the right tool for that, but it can still be accomplished with GG by developing a script to do that. One of the simplest options for such script is to use disasm API function for disassembling instructions and then process function's output accordingly. An example of script that implements this approach is included below. Note that for the script to process, for example, 10-20+ MBs of instructions more than a minute may be needed. local jumpDestinationAddress = 0x7001234560 -- target address to find BL instructions that jump to it local startAddress = 0x7001000000 -- address starting from which to process instructions local endAddress = 0x7001FFFFFF -- address to which to process instructions (address of last byte of last instruction to process) local instructionsInBlock = 100000 -- amount of instructions in processed blocks, too high amounts will cause OutOfMemoryError local blockSize = instructionsInBlock * 4 -- block size in bytes local getValues = gg.getValues local dwordType = gg.TYPE_DWORD local function readDwordValues(startAddress, endAddress) local values = {} local index = 1 for address = startAddress, endAddress, 4 do values[index] = {address = address, flags = dwordType} index = index + 1 end return getValues(values) end local disasm = gg.disasm local asmType = gg.ASM_ARM64 local string_sub = string.sub local string_find = string.find local function processInstructions(instructions, targetAddress) local destinationAddressString = string.format("%X", targetAddress) local targetPattern = ";[^%x]*" .. destinationAddressString .. "[^%x]" -- pattern for comment to BL instructions that includes target address exactly local targetInstructions = {} local index = 1 for i = 1, #instructions do local instruction = instructions[i] local disassembled = disasm(asmType, instruction.address, instruction.value) -- Discarding all results that don't start with "BL" to avoid checking the pattern each time if string_sub(disassembled, 1, 2) == "BL" and string_find(disassembled, targetPattern) ~= nil then targetInstructions[index] = instruction index = index + 1 end end return targetInstructions end local function appendTable(destination, source) local index = #destination + 1 for i, v in ipairs(source) do destination[index] = v index = index + 1 end end local targetInstructions = {} for blockStartAddress = startAddress, endAddress, blockSize do local blockEndAddress = blockStartAddress + blockSize - 1 if blockEndAddress > endAddress then blockEndAddress = endAddress end local instructions = readDwordValues(blockStartAddress, blockEndAddress) local partialResults = processInstructions(instructions, jumpDestinationAddress) appendTable(targetInstructions, partialResults) end gg.loadResults(targetInstructions) BL_specified_destination_finder.lua
  14. "memoryFrom" and "memoryTo" parameters of searchNumber API function can be used to set search bounds. To perform nearby search just set "memoryFrom" parameter to address - distance and "memoryTo" parameter to address + distance. Example: local address = 0x10001000 local minAddress = address - 200 -- equivalent of "Before" checkbox and distance of 200 (0xC8) bytes local maxAddress = address + 400 -- equivalent of "After" checkbox and distance of 400 (0x190) bytes -- Search for any value of type "Byte" in range of addresses from 0x10000F38 to 0x10001190 gg.searchNumber("0~~0", gg.TYPE_BYTE, false, gg.SIGN_EQUAL, minAddress, maxAddress)
  15. As others mentioned above, this is not something that needs a fix from user side. The regions that were identified by GG as "C++ alloc" or "C++ heap" on Android 10 and earlier versions are (in some, but not all, cases) identified as "Other" on Android 11 and higher versions. So the solution is do the following:
  16. The idea is to read enough bytes at once and then trim the string to first occurrence of quote character instead of reading one byte at a time until quote character is encountered. Generally, first approach should be better in terms of performance, because each read from memory takes significantly more time than several Lua operations, even if just one byte needs to be read. I thought to add 12 to get position of first byte of the answer that is the next one after last quote character in searched string. I don't immediately see why it doesn't work as expected, but it seems that there is a mistake somewhere and it's fixed by your correction to use "+ 11" instead of "+ 12".
  17. You should also consider refactoring the code to make it simpler and cleaner. For example, your "MENU1" function can be refactored to something like the following: function MENU1() gg.setRanges(gg.REGION_ANONYMOUS) gg.clearResults() gg.searchNumber(':","answer":"', gg.TYPE_BYTE) local count = gg.getResultsCount() local results = gg.getResults(count) local answers = {} for i = 1, count, 12 do local address = results[i].address + 12 local maxLength = 1024 local answer = readBytesToString(address, maxLength) answer = answer:match('[^"]*') -- match part up to first quote character table.insert(answers, answer) end for number, answer in ipairs(answers) do print(string.format("Answer #%d:", number), answer) end gg.alert("Answer See [ON]") end Let me know, if the code above works for you.
  18. To output a string Lua implementation in GG treats it as UTF-8 (by themselves Lua strings are just byte arrays), so reading bytes from memory to Lua string and passing the string to a function that outputs it ("print", "gg.alert", "gg.toast", etc) should be enough to achieve desired result in your case. Here is an example of function for reading bytes from memory and constructing string from them: function readBytesToString(address, length) local values = {} for i = 0, length - 1 do values[i] = {address = address + i, flags = gg.TYPE_BYTE} end values = gg.getValues(values) local bytes = {} for i, v in ipairs(values) do bytes[i] = v.value & 0xFF end return string.char(table.unpack(bytes)) end Call it with address to read from as first argument and amount of bytes to read as second argument, for example: local startAddress = 0x12340000 local bytesToRead = 20 local result = readBytesToString(startAddress, bytesToRead) print(result)
  19. CmP

    Edit only one final value

    You can use second parameter of "getResults" function to get (only) the last result: local count = gg.getResultsCount() local result = gg.getResults(1, count - 1) result[1].value = 123 gg.setValues(result)
  20. Preserving selection state only of some menu items can be done by adding a table for specifying menu items whose selection state needs to be preserved and updating "selection" table after the call to "multiChoice" function according to the added table and user's selection of items. Here is an example of how it can be implemented based on your code: function updateSelection(selection, keysToUpdate, newSelection) newSelection = newSelection or {} for i = 1, #selection do if keysToUpdate[i] then -- if item's selection state needs to be updated selection[i] = newSelection[i] or false -- update item's selection state and ensure that new value is not nil else selection[i] = false -- reset item's selection state to unselected end end end local options = {'Skip Game', 'Infinite Ammo', 'Exit'} local selection = {false, false, false} local remember = {[2] = true} function start() local choices = gg.multiChoice(options, selection) updateSelection(selection, remember, choices) if choices == nil then return end if choices[3] then print('Exited') os.exit() end if choices[1] then gg.toast('GAME SKIPPED') end if choices[2] then gg.toast('ON') else gg.toast('OFF') end end while true do if gg.isVisible() then gg.setVisible(false) start() end gg.sleep(100) end
  21. I can confirm the behavior of fuzzy search that is shown in the video. Some values of floating-point types (Float and Double), despite not changing, pass both "Increased" and "Decreased" conditions (but not "Changed", it correctly filters most of them out). It's not absolutely clear whether such behavior is intentional or not, but from user's side it's fairly easy to account for it. First time after making target value change in the game, search for values that have "changed". After that "increased" and "decreased" modes can be used as usual to find values that have increased or decreased correspondingly. Search tab toolbar contains "Remove" item (). Tapping on it causes dialog to be displayed with it's first option being "Remove all". Choosing the option clears results list allowing to start new fuzzy search.
  22. Relevant explanation from StackOverflow answer: https://stackoverflow.com/a/6389082
  23. It's because of fundamentals of how Lua is implemented. The implementations (official one and LuaJ) are single-threaded, therefore only one operation can be performed at a time. Nothing can be done about it without changing the implementation.
  24. How does that achieve the goal though? Original "searchNumber" function needs to be called anyway to actually perform the search. When it's called, it will only return after the search is finished (successfully or by cancellation). So how would a coroutine do anything when Lua executes native (i.e. not Lua one) function?
  25. Can you explain how would coroutines make that possible? How would you pass execution to a coroutine after calling "searchNumber", when it is doing it's job? Coroutines don't make it possible to execute code in parallel.
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.