| 1 | + | --- @diagnostic disable: param-type-mismatch, undefined-field, cast-local-type |
| 2 | + | -- memAPI v.1.0a (by @asdhuman) |
| 3 | + | -- Read docs at https://codedune.tech/docs/memapi |
| 4 | + | -- lua require docs at https://www.lua.org/pil/8.1.html |
| 5 | + | -- Import this library with `` local memapi = require "memapi" `` |
| 6 | + | |
| 7 | + | local memapi = {} |
| 8 | + | |
| 9 | + | -- Save copied tables in `copies`, indexed by original table. |
| 10 | + | local function deepcopy(orig) |
| 11 | + | local orig_type = type(orig) |
| 12 | + | local copy |
| 13 | + | if orig_type == 'table' then |
| 14 | + | copy = {} |
| 15 | + | for orig_key, orig_value in next, orig, nil do |
| 16 | + | copy[deepcopy(orig_key)] = deepcopy(orig_value) |
| 17 | + | end |
| 18 | + | setmetatable(copy, deepcopy(getmetatable(orig))) |
| 19 | + | else -- number, string, boolean, etc |
| 20 | + | copy = orig |
| 21 | + | end |
| 22 | + | return copy |
| 23 | + | end |
| 24 | + | |
| 25 | + | memapi.convertToHexString = function(number, digits) |
| 26 | + | if digits < 1 or digits > 16 then |
| 27 | + | error("Number of hexadecimal digits in the resulting string has to be in range [1; 16]") |
| 28 | + | return |
| 29 | + | end |
| 30 | + | |
| 31 | + | if _VERSION:match("5.2") ~= nil then -- Lua 5.2 |
| 32 | + | local mask = load[[return function(digits) return (1 << (digits * 4)) - 1 end]]()(digits) |
| 33 | + | local format = "%0" .. digits .. "X" |
| 34 | + | return load[[return function(format, number, mask) |
| 35 | + | return string.format(format, number & mask) |
| 36 | + | end]]()(format, number, mask) |
| 37 | + | else -- Lua 5.3 |
| 38 | + | local mask = (1 << (digits * 4)) - 1 |
| 39 | + | local format = "%0" .. digits .. "X" |
| 40 | + | return string.format(format, number & mask) |
| 41 | + | end |
| 42 | + | end |
| 43 | + | |
| 44 | + | memapi.get_libbase = function(libname) |
| 45 | + | local addresses = gg.getRangesList("*" .. libname .. "*.so") |
| 46 | + | local xa_libs = {} |
| 47 | + | for k, v in pairs(addresses) do |
| 48 | + | if v.state == 'Xa' then table.insert(xa_libs, v) end -- get all in Xa |
| 49 | + | end |
| 50 | + | if #xa_libs == 1 then |
| 51 | + | local ret = {} |
| 52 | + | ret.base_offset = xa_libs[1].start |
| 53 | + | ret.end_offset = xa_libs[1]["end"] -- "end" is lua keyword |
| 54 | + | ret.base_libname = xa_libs[1].name |
| 55 | + | return ret |
| 56 | + | elseif #xa_libs > 1 then return -1 |
| 57 | + | else return nil end |
| 58 | + | end |
| 59 | + | |
| 60 | + | memapi.__attach = function (self) |
| 61 | + | local libs = memapi.get_libbase(self.user_libname) |
| 62 | + | if type(libs) == "table" then |
| 63 | + | self.is_hooked = true |
| 64 | + | self.base_offset = libs.base_offset |
| 65 | + | self.end_offset = libs.end_offset |
| 66 | + | self.libname = libs.base_libname |
| 67 | + | return true |
| 68 | + | elseif libs == -1 then |
| 69 | + | gg.alert("[memAPI] Found more then one library with this string, try to specify more precise") |
| 70 | + | return false |
| 71 | + | else |
| 72 | + | return false |
| 73 | + | end |
| 74 | + | end |
| 75 | + | |
| 76 | + | memapi.__arch = function(self, ...) |
| 77 | + | local args = {...} |
| 78 | + | if #args == 1 then |
| 79 | + | if args[1] == "x32" or args[1] == "32" or args[1] == "armv7" then |
| 80 | + | return not self.target_info.x64 |
| 81 | + | elseif args[1] == "x64" or args[1] == '64' or args[1] == "armv8" then |
| 82 | + | return self.target_info.x64 |
| 83 | + | end |
| 84 | + | else |
| 85 | + | if self.target_info.x64 then |
| 86 | + | return "x64" |
| 87 | + | else |
| 88 | + | return "x32" |
| 89 | + | end |
| 90 | + | end |
| 91 | + | end |
| 92 | + | |
| 93 | + | memapi.__read = function(self, addr, size) -- size in bytes |
| 94 | + | local start_clock = os.clock() |
| 95 | + | local offset |
| 96 | + | if type(addr) == "string" then |
| 97 | + | offset = self.base_offset + tonumber(tostring(addr), 16) |
| 98 | + | elseif type(addr) == "number" then |
| 99 | + | offset = self.base_offset + addr |
| 100 | + | end |
| 101 | + | local t = {} |
| 102 | + | local data = "" |
| 103 | + | for i=1, size do |
| 104 | + | t[i] = {} |
| 105 | + | t[i].address = string.format("0x%X", tostring(offset + (i-1))) |
| 106 | + | t[i].flags = gg.TYPE_BYTE |
| 107 | + | end |
| 108 | + | local d = gg.getValues(t) |
| 109 | + | for _, v in pairs(d) do |
| 110 | + | data = data .. memapi.convertToHexString(v["value"], 2) |
| 111 | + | end |
| 112 | + | if self.debug then |
| 113 | + | print(string.format("[memAPI] Read %d bytes from 0x%X in %f s", size, offset, os.clock() - start_clock)) |
| 114 | + | end |
| 115 | + | return data |
| 116 | + | end |
| 117 | + | |
| 118 | + | memapi.__push = function (self, ...) |
| 119 | + | local start_clock = os.clock() |
| 120 | + | local args = {...} |
| 121 | + | if #self.write_buffer < 1 then return end |
| 122 | + | |
| 123 | + | if #self.op_buffer > 10 then |
| 124 | + | table.remove(self.op_buffer, 1) |
| 125 | + | end |
| 126 | + | |
| 127 | + | local oldvals = {} |
| 128 | + | |
| 129 | + | for _, v in pairs(self.write_buffer) do |
| 130 | + | if self.rollbacking then -- can make push slower |
| 131 | + | table.insert(oldvals, gg.getValues(v)) |
| 132 | + | end |
| 133 | + | gg.setValues(v) |
| 134 | + | end |
| 135 | + | |
| 136 | + | if self.rollbacking then |
| 137 | + | table.insert(self.op_buffer, oldvals) |
| 138 | + | end |
| 139 | + | |
| 140 | + | if self.debug then |
| 141 | + | print(string.format("[memAPI] Pushed %d operations in %f s", #self.write_buffer, os.clock() - start_clock)) |
| 142 | + | end |
| 143 | + | |
| 144 | + | self.write_buffer = {} |
| 145 | + | end |
| 146 | + | |
| 147 | + | memapi.__rollback = function (self) |
| 148 | + | if self.rollbacking then |
| 149 | + | -- get last item from write_buffer |
| 150 | + | local start_clock = os.clock() |
| 151 | + | local last = self.op_buffer[#self.op_buffer] |
| 152 | + | |
| 153 | + | for _, v in pairs(last) do |
| 154 | + | gg.setValues(v) |
| 155 | + | end |
| 156 | + | |
| 157 | + | table.remove(self.op_buffer, #self.op_buffer) |
| 158 | + | |
| 159 | + | if self.debug then |
| 160 | + | print(string.format("[memAPI] Rollbacked %d operations in %f s", #last, os.clock() - start_clock)) |
| 161 | + | end |
| 162 | + | else |
| 163 | + | error("Rollbacking not enabled. Use ``hook.rollbacking = true``") |
| 164 | + | return |
| 165 | + | end |
| 166 | + | end |
| 167 | + | |
| 168 | + | memapi.__write32 = function (self, offset, val) |
| 169 | + | local start_clock = os.clock() |
| 170 | + | local t = memapi.__bytesToTable32(val, offset) |
| 171 | + | if t ~= false then |
| 172 | + | table.insert(self.write_buffer, t) |
| 173 | + | if self.debug then |
| 174 | + | print(string.format("[memAPI] __write32() %d bytes in %f s", #t, os.clock() - start_clock)) |
| 175 | + | end |
| 176 | + | return true |
| 177 | + | else |
| 178 | + | return false |
| 179 | + | end |
| 180 | + | end |
| 181 | + | |
| 182 | + | memapi.__write64 = function (self, offset, val) |
| 183 | + | local start_clock = os.clock() |
| 184 | + | local t = memapi.__bytesToTable64(val, offset) |
| 185 | + | if t ~= false then |
| 186 | + | table.insert(self.write_buffer, t) |
| 187 | + | if self.debug then |
| 188 | + | print(string.format("[memAPI] __write64() %d bytes in %f s", #t, os.clock() - start_clock)) |
| 189 | + | end |
| 190 | + | return true |
| 191 | + | else |
| 192 | + | return false |
| 193 | + | end |
| 194 | + | end |
| 195 | + | |
| 196 | + | memapi.__write = function(self, addr, ...) |
| 197 | + | local start_clock = os.clock() |
| 198 | + | local args = {...} |
| 199 | + | local offset |
| 200 | + | if type(addr) == "string" then |
| 201 | + | offset = self.base_offset + tonumber(tostring(addr):gsub("0x", ""), 16) |
| 202 | + | elseif type(addr) == "number" then |
| 203 | + | offset = self.base_offset + addr |
| 204 | + | else |
| 205 | + | error("[memAPI] Invalid offset type (string or number expected)") |
| 206 | + | return |
| 207 | + | end |
| 208 | + | |
| 209 | + | if #args == 1 then |
| 210 | + | if self.is_hooked then |
| 211 | + | if self:arch("x32") then |
| 212 | + | self:__write32(offset, args[1]) |
| 213 | + | else |
| 214 | + | self:__write64(offset, args[1]) |
| 215 | + | end |
| 216 | + | else |
| 217 | + | gg.alert("[memAPI] You have to attach to library first") |
| 218 | + | end |
| 219 | + | elseif #args > 1 then |
| 220 | + | if self.is_hooked then |
| 221 | + | if self:arch("x32") then |
| 222 | + | local stf = 0 |
| 223 | + | for i=1, #args do |
| 224 | + | self:__write32(offset + stf, args[i]) |
| 225 | + | stf = stf + memapi.byteslenght(args[i]) |
| 226 | + | end |
| 227 | + | else |
| 228 | + | local stf = 0 |
| 229 | + | for i=1, #args do |
| 230 | + | self:__write64(offset + stf, args[i]) |
| 231 | + | stf = stf + memapi.byteslenght(args[i]) |
| 232 | + | end |
| 233 | + | end |
| 234 | + | else |
| 235 | + | gg.alert("[memAPI] You have to attach to library first") |
| 236 | + | end |
| 237 | + | end |
| 238 | + | if self.debug then |
| 239 | + | print(string.format("[memAPI] __write %d ops in %f s", #args, os.clock() - start_clock)) |
| 240 | + | end |
| 241 | + | end |
| 242 | + | |
| 243 | + | memapi.__disasm32 = function(addr, opcode) |
| 244 | + | local thumb = gg.disasm(gg.ASM_THUMB, addr, opcode) |
| 245 | + | local arm = gg.disasm(gg.ASM_ARM, addr, opcode) |
| 246 | + | local t = {} |
| 247 | + | t.thumb = thumb |
| 248 | + | t.arm = arm |
| 249 | + | return t |
| 250 | + | end |
| 251 | + | |
| 252 | + | memapi.__disasm64 = function(addr, opcode) |
| 253 | + | local thumb = gg.disasm(gg.ASM_THUMB, addr, opcode) |
| 254 | + | local arm = gg.disasm(gg.ASM_ARM64, addr, opcode) |
| 255 | + | local t = {} |
| 256 | + | t.thumb = thumb |
| 257 | + | t.arm = arm |
| 258 | + | return t |
| 259 | + | end |
| 260 | + | |
| 261 | + | memapi.disasm = function(arch, addr, opcode) |
| 262 | + | if type(opcode) == "string" then |
| 263 | + | -- remove all spaces, conver to hex |
| 264 | + | opcode = string.gsub(opcode, "%s+", "") |
| 265 | + | opcode = string.gsub(opcode, " ", "") |
| 266 | + | opcode = string.gsub(opcode, "0x", "") |
| 267 | + | opcode = string.gsub(opcode, "0X", "") |
| 268 | + | |
| 269 | + | -- check if opcode is valid |
| 270 | + | if #opcode % 8 ~= 0 then |
| 271 | + | gg.alert("4 bytes opcode size required!") |
| 272 | + | end |
| 273 | + | |
| 274 | + | -- convert to number |
| 275 | + | opcode = tonumber(opcode, 16) |
| 276 | + | opcode = string.unpack('>I4', string.pack('<I4', opcode)) |
| 277 | + | end |
| 278 | + | |
| 279 | + | if addr == nil then addr = 0x0 end |
| 280 | + | if arch == "x32" then |
| 281 | + | return memapi.__disasm32(addr, opcode) |
| 282 | + | else |
| 283 | + | return memapi.__disasm64(addr, opcode) |
| 284 | + | end |
| 285 | + | end |
| 286 | + | |
| 287 | + | memapi.asm = function(code, op_addr) |
| 288 | + | if op_addr == nil then op_addr = 0x0 end |
| 289 | + | local arm = code |
| 290 | + | local addr = gg.getRangesList('libc.so') |
| 291 | + | for i, v in ipairs(addr) do |
| 292 | + | if v.type:sub(2,2) == 'w' then |
| 293 | + | addr = {{address = v.start, flags = gg.TYPE_DWORD}} |
| 294 | + | end |
| 295 | + | end |
| 296 | + | |
| 297 | + | if not addr[1].address then |
| 298 | + | gg.alert('Failed to get addr (memapi.asm issues)\n' .. tostring(addr)) |
| 299 | + | do return end |
| 300 | + | end |
| 301 | + | do |
| 302 | + | local addr = tonumber(op_addr, 16) |
| 303 | + | if addr then |
| 304 | + | addr = addr & 0xFFFFFFFF |
| 305 | + | arm = arm:gsub('( 0[xX](%x+))', function (m, n) |
| 306 | + | local num = tonumber(n, 16) |
| 307 | + | if not num then return m end |
| 308 | + | num = num & 0xFFFFFFFF |
| 309 | + | num = num - addr; |
| 310 | + | local sign = '+' |
| 311 | + | if num < 0 then |
| 312 | + | num = -num |
| 313 | + | sign = '-' |
| 314 | + | end |
| 315 | + | return string.format(' %s0x%X', sign, num) |
| 316 | + | end) |
| 317 | + | end |
| 318 | + | end |
| 319 | + | |
| 320 | + | local old = gg.getValues(addr) |
| 321 | + | addr[1].value = '~A '..arm |
| 322 | + | local ok, err = pcall(gg.setValues, addr) |
| 323 | + | local out |
| 324 | + | if not ok then |
| 325 | + | err = err:gsub("^.* '1': ", ''):gsub('\nlevel = 1.*$', '') |
| 326 | + | error("-----------------------\n[memAPI] Error: \n" .. tostring(err) .. "\n-----------------------\n") |
| 327 | + | out = 'ERR!' |
| 328 | + | else |
| 329 | + | out = gg.getValues(addr) |
| 330 | + | out = out[1].value & 0xFFFFFFFF |
| 331 | + | |
| 332 | + | gg.setValues(old) |
| 333 | + | out = string.unpack('>I4', string.pack('<I4', out)) |
| 334 | + | out = string.format('%08X', out) |
| 335 | + | return out |
| 336 | + | end |
| 337 | + | end |
| 338 | + | |
| 339 | + | memapi.__formatpatch = function(val) |
| 340 | + | local ret = "" |
| 341 | + | if type(val) == "string" then |
| 342 | + | if val == "nop" then |
| 343 | + | return "00F020E3" -- nop is 4 bytes "00 F0 20 E3" |
| 344 | + | else |
| 345 | + | local v = val:gsub("[h]", "") |
| 346 | + | v = v:gsub("%s+", "") |
| 347 | + | if string.match(v, "^[0-9A-Fa-f]+$") then |
| 348 | + | val = val:gsub("[h]", ""); val = val:gsub("%s+", "") |
| 349 | + | if string.len(val) % 2 ~= 0 then val = val .. "0" end |
| 350 | + | local i2 = 1 |
| 351 | + | for i=1, string.len(val)/2 do |
| 352 | + | ret = ret .. string.sub(val, i2, i2+1) |
| 353 | + | i2 = i2 + 2 |
| 354 | + | end |
| 355 | + | else |
| 356 | + | local s = memapi.asm(val) -- asm parse |
| 357 | + | if s == nil then return false |
| 358 | + | else |
| 359 | + | for i=1, #s/2 do |
| 360 | + | ret = ret .. string.sub(s, (i-1)*2+1, (i-1)*2+2) |
| 361 | + | end |
| 362 | + | end |
| 363 | + | end |
| 364 | + | end |
| 365 | + | elseif type(val) == "boolean" then |
| 366 | + | if val then ret = "h01" |
| 367 | + | else ret ="h00" end |
| 368 | + | ret = ret .. "00A0E31EFF2FE1" |
| 369 | + | elseif type(val) == "number" then -- 0-255 currently |
| 370 | + | if val >= 0 then |
| 371 | + | if val >= 255 then |
| 372 | + | ret = memapi.asm("mov r0, #" .. tostring(val)) -- somekind slower |
| 373 | + | ret = ret .. "1EFF2FE1" |
| 374 | + | else |
| 375 | + | ret = memapi.convertToHexString(val, 2) .."00A0E31EFF2FE1" |
| 376 | + | end |
| 377 | + | else |
| 378 | + | ret = memapi.asm("mov r0, #" .. tostring(val)) |
| 379 | + | ret = ret .. "1EFF2FE1" |
| 380 | + | error("value <0, not implemented") |
| 381 | + | return |
| 382 | + | -- ret = memapi.convertToHexString(val, 2) -- maybe i will do that later... |
| 383 | + | end |
| 384 | + | end |
| 385 | + | return ret |
| 386 | + | end |
| 387 | + | |
| 388 | + | memapi.__bytesToTable64 = function(val, offset) |
| 389 | + | local t = {} |
| 390 | + | if type(val) == "string" then |
| 391 | + | if val == "nop" then |
| 392 | + | local np = "1F2003D5" -- nop is 4 bytes "1F 20 03 D5" |
| 393 | + | for i=1, 4 do |
| 394 | + | t[i] = {} |
| 395 | + | t[i].address = offset + i - 1 |
| 396 | + | t[i].flags = gg.TYPE_BYTE |
| 397 | + | t[i].value = "h" .. string.sub(np, (i-1)*2+1, (i-1)*2+2) |
| 398 | + | end |
| 399 | + | else |
| 400 | + | local v = val:gsub("[h]", "") |
| 401 | + | v = v:gsub("%s+", "") |
| 402 | + | if string.match(v, "^[0-9A-Fa-f]+$") then |
| 403 | + | val = val:gsub("[h]", ""); val = val:gsub("%s+", "") |
| 404 | + | if string.len(val) % 2 ~= 0 then val = val .. "0" end |
| 405 | + | local i2 = 1 |
| 406 | + | for i=1, string.len(val)/2 do |
| 407 | + | t[i] = {} |
| 408 | + | t[i].address = string.format("0x%X", tostring(offset + (i-1))) |
| 409 | + | t[i].flags = gg.TYPE_BYTE |
| 410 | + | local bb = "h" .. string.sub(val, i2, i2+1) |
| 411 | + | t[i].value = bb |
| 412 | + | i2 = i2 + 2 |
| 413 | + | end |
| 414 | + | else |
| 415 | + | local s = memapi.asm(val) -- asm parse |
| 416 | + | if s == nil then return false |
| 417 | + | else |
| 418 | + | for i=1, #s/2 do |
| 419 | + | t[i] = {} |
| 420 | + | t[i].address = string.format("0x%X", tostring(offset + (i-1))) |
| 421 | + | t[i].flags = gg.TYPE_BYTE |
| 422 | + | t[i].value = "h" .. string.sub(s, (i-1)*2+1, (i-1)*2+2) |
| 423 | + | end |
| 424 | + | end |
| 425 | + | end |
| 426 | + | end |
| 427 | + | elseif type(val) == "boolean" then |
| 428 | + | local s |
| 429 | + | if val then s = "200080D2" |
| 430 | + | else s = "000080D2" end |
| 431 | + | s = s .. "C0035FD6" -- ret |
| 432 | + | for i=1, 8 do |
| 433 | + | t[i] = {} |
| 434 | + | t[i].address = offset + i - 1 |
| 435 | + | t[i].flags = gg.TYPE_BYTE |
| 436 | + | t[i].value = "h" .. string.sub(s, (i-1)*2+1, (i-1)*2+2) |
| 437 | + | end |
| 438 | + | elseif type(val) == "number" then |
| 439 | + | local s |
| 440 | + | s = memapi.asm("mov x0, #" .. tostring(val)) |
| 441 | + | s = s .. "C0035FD6" -- ret |
| 442 | + | for i=1, 8 do |
| 443 | + | t[i] = {} |
| 444 | + | t[i].address = offset + i - 1 |
| 445 | + | t[i].flags = gg.TYPE_BYTE |
| 446 | + | t[i].value = "h" .. string.sub(s, (i-1)*2+1, (i-1)*2+2) |
| 447 | + | end |
| 448 | + | end |
| 449 | + | |
| 450 | + | if #t > 0 then |
| 451 | + | return t |
| 452 | + | else |
| 453 | + | return false |
| 454 | + | end |
| 455 | + | end |
| 456 | + | |
| 457 | + | memapi.__bytesToTable32 = function(val, offset) |
| 458 | + | local t = {} |
| 459 | + | if type(val) == "string" then |
| 460 | + | if val == "nop" then |
| 461 | + | local np = "00F020E3" -- nop is 4 bytes "00 F0 20 E3" |
| 462 | + | for i=1, 4 do |
| 463 | + | t[i] = {} |
| 464 | + | t[i].address = offset + i - 1 |
| 465 | + | t[i].flags = gg.TYPE_BYTE |
| 466 | + | t[i].value = "h" .. string.sub(np, (i-1)*2+1, (i-1)*2+2) |
| 467 | + | end |
| 468 | + | else |
| 469 | + | local v = val:gsub("[h]", "") |
| 470 | + | v = v:gsub("%s+", "") |
| 471 | + | if string.match(v, "^[0-9A-Fa-f]+$") then |
| 472 | + | val = val:gsub("[h]", ""); val = val:gsub("%s+", "") |
| 473 | + | if string.len(val) % 2 ~= 0 then val = val .. "0" end |
| 474 | + | local i2 = 1 |
| 475 | + | for i=1, string.len(val)/2 do |
| 476 | + | t[i] = {} |
| 477 | + | t[i].address = string.format("0x%X", tostring(offset + (i-1))) |
| 478 | + | t[i].flags = gg.TYPE_BYTE |
| 479 | + | local bb = "h" .. string.sub(val, i2, i2+1) |
| 480 | + | t[i].value = bb |
| 481 | + | i2 = i2 + 2 |
| 482 | + | end |
| 483 | + | else |
| 484 | + | local s = memapi.asm(val) -- asm parse |
| 485 | + | if s == nil then return false |
| 486 | + | else |
| 487 | + | for i=1, #s/2 do |
| 488 | + | t[i] = {} |
| 489 | + | t[i].address = string.format("0x%X", tostring(offset + (i-1))) |
| 490 | + | t[i].flags = gg.TYPE_BYTE |
| 491 | + | t[i].value = "h" .. string.sub(s, (i-1)*2+1, (i-1)*2+2) |
| 492 | + | end |
| 493 | + | end |
| 494 | + | end |
| 495 | + | end |
| 496 | + | elseif type(val) == "boolean" then |
| 497 | + | for i=1, 8 do |
| 498 | + | t[i] = {} |
| 499 | + | t[i].address = string.format("0x%X", tostring(offset + (i-1))) |
| 500 | + | t[i].flags = gg.TYPE_BYTE |
| 501 | + | end |
| 502 | + | if val then t[1].value = "h01" else t[1].value = "h00" end |
| 503 | + | t[2].value = "h00"; t[3].value = "hA0" |
| 504 | + | t[4].value = "hE3"; t[5].value = "h1E" |
| 505 | + | t[6].value = "hFF"; t[7].value = "h2F" |
| 506 | + | t[8].value = "hE1" |
| 507 | + | elseif type(val) == "number" then |
| 508 | + | for i=1, 8 do |
| 509 | + | t[i] = {} |
| 510 | + | t[i].address = string.format("0x%X", tostring(offset + (i-1))) |
| 511 | + | t[i].flags = gg.TYPE_BYTE |
| 512 | + | end |
| 513 | + | if val >= 0 then |
| 514 | + | t[1].value = "h" .. memapi.convertToHexString(val, 2) |
| 515 | + | else |
| 516 | + | t[1].value = "h" .. memapi.convertToHexString(val, 2) -- maybe i will do that later... |
| 517 | + | end |
| 518 | + | t[2].value = "h00"; t[3].value = "hA0" |
| 519 | + | t[4].value = "hE3"; t[5].value = "h1E" |
| 520 | + | t[6].value = "hFF"; t[7].value = "h2F" |
| 521 | + | t[8].value = "hE1" |
| 522 | + | end |
| 523 | + | |
| 524 | + | if #t > 0 then |
| 525 | + | return t |
| 526 | + | else |
| 527 | + | return false |
| 528 | + | end |
| 529 | + | end |
| 530 | + | |
| 531 | + | memapi.byteslenght = function(val) |
| 532 | + | if type(val) == "string" then |
| 533 | + | if val == "nop" then |
| 534 | + | return 4 -- nop is 4 bytes "00 F0 20 E3" |
| 535 | + | else |
| 536 | + | local v = val:gsub("[h]", "") |
| 537 | + | v = v:gsub("%s+", "") |
| 538 | + | if string.match(v, "^[0-9A-Fa-f]+$") then |
| 539 | + | val = val:gsub("[h]", ""); val = val:gsub("%s+", "") |
| 540 | + | if string.len(val) % 2 ~= 0 then val = val .. "0" end |
| 541 | + | return string.len(val)/2 |
| 542 | + | else |
| 543 | + | local s = memapi.asm(val) -- asm parse |
| 544 | + | if s == nil then return false |
| 545 | + | else return #s/2 end |
| 546 | + | end |
| 547 | + | end |
| 548 | + | elseif type(val) == "boolean" then |
| 549 | + | return 8 -- "mov r0, #0/1; bx lr" |
| 550 | + | elseif type(val) == "number" then |
| 551 | + | return 8 -- same as bool, #? |
| 552 | + | else |
| 553 | + | return false |
| 554 | + | end |
| 555 | + | end |
| 556 | + | |
| 557 | + | memapi.__switchconstr = function(hook, offset, patch_to, orig_bytes) |
| 558 | + | local handler = {} |
| 559 | + | |
| 560 | + | if type(offset) == "string" then |
| 561 | + | offset = tonumber(tostring(offset):gsub("0x", ""), 16) |
| 562 | + | elseif type(offset) == "number" then |
| 563 | + | -- nothing |
| 564 | + | else |
| 565 | + | error("[memAPI] Invalid offset type (string or number expected)") |
| 566 | + | return |
| 567 | + | end |
| 568 | + | |
| 569 | + | handler.hook = deepcopy(hook) -- memapi obj |
| 570 | + | handler.hook.op_buffer = {} -- cleanup |
| 571 | + | handler.hook.write_buffer = {} -- cleanup |
| 572 | + | handler.hook.rollbacking = false -- we dont need that here |
| 573 | + | |
| 574 | + | handler.offset = offset |
| 575 | + | handler.patch_to = memapi.__formatpatch(patch_to) -- A0B1C2D3... |
| 576 | + | handler.byteslenght = memapi.byteslenght(handler.patch_to) |
| 577 | + | |
| 578 | + | local membytes = handler.hook:read(offset, handler.byteslenght) -- to get state |
| 579 | + | |
| 580 | + | if orig_bytes then |
| 581 | + | if handler.byteslenght ~= memapi.byteslenght(orig_bytes) then |
| 582 | + | error("Lenght of patch_to and orig_bytes not equal") |
| 583 | + | return |
| 584 | + | end |
| 585 | + | end |
| 586 | + | |
| 587 | + | if membytes == handler.patch_to then |
| 588 | + | handler.state = true -- already enabled |
| 589 | + | if orig_bytes then |
| 590 | + | handler.original = orig_bytes -- switch impl |
| 591 | + | else |
| 592 | + | handler.original = "false" -- bswitch impl |
| 593 | + | end |
| 594 | + | else |
| 595 | + | handler.state = false -- ok |
| 596 | + | if orig_bytes then |
| 597 | + | handler.original = orig_bytes -- switch |
| 598 | + | else |
| 599 | + | handler.original = membytes -- bswitch |
| 600 | + | end |
| 601 | + | end |
| 602 | + | |
| 603 | + | handler.enable = function() |
| 604 | + | handler.state = true |
| 605 | + | handler.hook:write(handler.offset, handler.patch_to) |
| 606 | + | handler.hook:push() |
| 607 | + | gg.alert("enabled") |
| 608 | + | end |
| 609 | + | |
| 610 | + | handler.disable = function() |
| 611 | + | handler.state = false |
| 612 | + | handler.hook:write(handler.offset, handler.original) |
| 613 | + | handler.hook:push() |
| 614 | + | gg.alert("disabled") |
| 615 | + | end |
| 616 | + | |
| 617 | + | handler.toggle = function() |
| 618 | + | if handler.state then |
| 619 | + | handler.disable(); return false; |
| 620 | + | else |
| 621 | + | handler.enable(); return true; |
| 622 | + | end |
| 623 | + | end |
| 624 | + | |
| 625 | + | return handler |
| 626 | + | end |
| 627 | + | |
| 628 | + | memapi.__bswitch = function(self, offset, patch_to) |
| 629 | + | return memapi.__switchconstr(self, offset, patch_to, nil) -- how do we know orig bytes, if patched, lol? |
| 630 | + | end |
| 631 | + | |
| 632 | + | memapi.__switch = function(self, offset, patch_to, orig_bytes) |
| 633 | + | return memapi.__switchconstr(self, offset, patch_to, orig_bytes) |
| 634 | + | end |
| 635 | + | |
| 636 | + | memapi.__patchconstr = function (hook, offset, ...) |
| 637 | + | local args = {...} |
| 638 | + | if #args < 1 then |
| 639 | + | error("No data specified") |
| 640 | + | return |
| 641 | + | end |
| 642 | + | |
| 643 | + | local handler = {} |
| 644 | + | handler.__hook = deepcopy(hook) -- memapi obj |
| 645 | + | handler.__hook.op_buffer = {} -- cleanup |
| 646 | + | handler.__hook.write_buffer = {} -- cleanup |
| 647 | + | |
| 648 | + | handler.offset = offset |
| 649 | + | |
| 650 | + | handler.__hook:write(offset, ...) |
| 651 | + | handler.__hook:push() |
| 652 | + | |
| 653 | + | local mt = {} |
| 654 | + | mt.__index = function(self, key) |
| 655 | + | if key == "rollbacking" then |
| 656 | + | return handler.__hook.rollbacking |
| 657 | + | end |
| 658 | + | end |
| 659 | + | |
| 660 | + | mt.__newindex = function (self, key, value) |
| 661 | + | if key == "rollbacking" then |
| 662 | + | handler.__hook.rollbacking = value |
| 663 | + | else |
| 664 | + | handler[key] = value |
| 665 | + | end |
| 666 | + | end |
| 667 | + | |
| 668 | + | handler.rollback = function () |
| 669 | + | handler.__hook:rollback() |
| 670 | + | end |
| 671 | + | |
| 672 | + | handler.patch = function (...) |
| 673 | + | handler.__hook:write(handler.offset, ...) |
| 674 | + | handler.__hook:push() |
| 675 | + | end |
| 676 | + | |
| 677 | + | handler.read = function (num) |
| 678 | + | return handler.__hook:read(handler.offset, num) |
| 679 | + | end |
| 680 | + | |
| 681 | + | setmetatable(handler, mt) |
| 682 | + | |
| 683 | + | return handler |
| 684 | + | end |
| 685 | + | |
| 686 | + | memapi.__patch = function (self, offset, ...) |
| 687 | + | return memapi.__patchconstr(self, offset, ...) |
| 688 | + | end |
| 689 | + | |
| 690 | + | memapi.hook = function(libname) -- you can use shortname, like "l2cpp" instead of "libil2cpp" |
| 691 | + | local handler = {} |
| 692 | + | handler.target_info = gg.getTargetInfo() |
| 693 | + | handler.user_libname = libname |
| 694 | + | handler.is_hooked = false |
| 695 | + | handler.base_offset = nil |
| 696 | + | handler.end_offset = nil |
| 697 | + | handler.libname = nil |
| 698 | + | handler.op_buffer = {} -- we keep old memory values here (10 last operations) for rollbacking |
| 699 | + | handler.write_buffer = {} -- we keep all write operations here, then writing it on :push() |
| 700 | + | handler.debug = false |
| 701 | + | handler.rollbacking = false |
| 702 | + | |
| 703 | + | handler.arch = memapi.__arch |
| 704 | + | handler.attach = memapi.__attach |
| 705 | + | |
| 706 | + | handler.read = memapi.__read |
| 707 | + | handler.write = memapi.__write |
| 708 | + | handler.push = memapi.__push |
| 709 | + | handler.rollback = memapi.__rollback |
| 710 | + | handler.__write32 = memapi.__write32 |
| 711 | + | handler.__write64 = memapi.__write64 |
| 712 | + | |
| 713 | + | handler.asm = memapi.asm |
| 714 | + | handler.disasm = memapi.disasm |
| 715 | + | |
| 716 | + | handler.bswitch = memapi.__bswitch |
| 717 | + | handler.switch = memapi.__bswitch |
| 718 | + | handler.patch = memapi.__patch |
| 719 | + | |
| 720 | + | return handler |
| 721 | + | end |
| 722 | + | |
| 723 | + | return memapi |