mirror of
https://github.com/holub/mame
synced 2025-05-04 21:43:05 +03:00
499 lines
14 KiB
Lua
499 lines
14 KiB
Lua
-- license:BSD-3-Clause
|
|
-- copyright-holders:Carl
|
|
-- This includes a library of functions to be used at the Lua console as cf.getspaces() etc...
|
|
local exports = {}
|
|
exports.name = "cheatfind"
|
|
exports.version = "0.0.1"
|
|
exports.description = "Cheat finder helper library"
|
|
exports.license = "The BSD 3-Clause License"
|
|
exports.author = { name = "Carl" }
|
|
|
|
local cheatfind = exports
|
|
|
|
function cheatfind.startplugin()
|
|
local cheat = {}
|
|
|
|
-- return table of devices and spaces
|
|
function cheat.getspaces()
|
|
local spaces = {}
|
|
for tag, device in pairs(manager:machine().devices) do
|
|
if device.spaces then
|
|
spaces[tag] = {}
|
|
for name, space in pairs(device.spaces) do
|
|
spaces[tag][name] = space
|
|
end
|
|
end
|
|
end
|
|
return spaces
|
|
end
|
|
|
|
-- return table of ram devices
|
|
function cheat.getram()
|
|
local ram = {}
|
|
for tag, device in pairs(manager:machine().devices) do
|
|
if device:shortname() == "ram" then
|
|
ram[tag] = {}
|
|
ram[tag].dev = device
|
|
ram[tag].size = emu.item(device.items["0/m_size"]):read(0)
|
|
end
|
|
end
|
|
return ram
|
|
end
|
|
|
|
-- save data block
|
|
function cheat.save(space, start, size)
|
|
local data = { block = "", start = start, size = size, space = space }
|
|
if space.shortname then
|
|
if space:shortname() == "ram" then
|
|
data.block = emu.item(space.items["0/m_pointer"]):read_block(start, size)
|
|
if not data.block then
|
|
return nil
|
|
end
|
|
end
|
|
else
|
|
local block = ""
|
|
for i = start, start + size do
|
|
block = block .. string.pack("B", space:read_u8(i))
|
|
end
|
|
data.block = block
|
|
end
|
|
return data
|
|
end
|
|
|
|
-- compare two data blocks
|
|
function cheat.comp(olddata, newdata, oper, val, signed, width, endian)
|
|
local ret = {}
|
|
local ref = {} -- this is a helper for comparing two match lists
|
|
local format = ""
|
|
local size = olddata.size
|
|
|
|
if endian == 1 then
|
|
format = format .. ">"
|
|
else
|
|
format = format .. "<"
|
|
end
|
|
if width == 16 then
|
|
size = size & 0xfffe
|
|
if signed == 1 then
|
|
format = format .. "h"
|
|
else
|
|
format = format .. "H"
|
|
end
|
|
elseif width == 32 then
|
|
size = size & 0xfffc
|
|
if signed == 1 then
|
|
format = format .. "l"
|
|
else
|
|
format = format .. "L"
|
|
end
|
|
elseif width == 64 then
|
|
size = size & 0xfff8
|
|
if signed == 1 then
|
|
format = format .. "j"
|
|
else
|
|
format = format .. "J"
|
|
end
|
|
else
|
|
if signed == 1 then
|
|
format = format .. "b"
|
|
else
|
|
format = format .. "B"
|
|
end
|
|
end
|
|
|
|
if olddata.start ~= newdata.start or olddata.size ~= newdata.size then
|
|
return {}
|
|
end
|
|
if not val then
|
|
val = 0
|
|
end
|
|
if oper == "+" or oper == "inc" or oper == "<" or oper == "lt" then
|
|
for i = 1, size do
|
|
local old = string.unpack(format, olddata.block, i)
|
|
local new = string.unpack(format, newdata.block, i)
|
|
if old < new then
|
|
if (val > 0 and (old + val) == new) or val == 0 then
|
|
ret[#ret + 1] = { addr = olddata.start + i - 1,
|
|
oldval = old,
|
|
newval = new}
|
|
ref[ret[#ret].addr] = #ret
|
|
end
|
|
end
|
|
end
|
|
elseif oper == "-" or oper == "dec" or oper == ">" or oper == "gt" then
|
|
for i = 1, size do
|
|
local old = string.unpack(format, olddata.block, i)
|
|
local new = string.unpack(format, newdata.block, i)
|
|
if old > new then
|
|
if (val > 0 and (old - val) == new) or val == 0 then
|
|
ret[#ret + 1] = { addr = olddata.start + i - 1,
|
|
oldval = old,
|
|
newval = new}
|
|
ref[ret[#ret].addr] = #ret
|
|
end
|
|
end
|
|
end
|
|
elseif oper == "=" or oper == "eq" then
|
|
for i = 1, size do
|
|
local old = string.unpack(format, olddata.block, i)
|
|
local new = string.unpack(format, newdata.block, i)
|
|
if old == new then
|
|
ret[#ret + 1] = { addr = olddata.start + i - 1,
|
|
oldval = old,
|
|
newval = new}
|
|
ref[ret[#ret].addr] = #ret
|
|
end
|
|
end
|
|
elseif oper == "!" or oper == "ne" then
|
|
for i = 1, size do
|
|
local old = string.unpack(format, olddata.block, i)
|
|
local new = string.unpack(format, newdata.block, i)
|
|
if old ~= new then
|
|
ret[#ret + 1] = { addr = olddata.start + i - 1,
|
|
oldval = old,
|
|
newval = new}
|
|
ref[ret[#ret].addr] = #ret
|
|
end
|
|
end
|
|
elseif oper == "^" or oper == "xor" then
|
|
if val >= width then
|
|
return {}
|
|
end
|
|
for i = 1, size do
|
|
local old = string.unpack(format, olddata.block, i)
|
|
local new = string.unpack(format, newdata.block, i)
|
|
if ((old ~ new) & (1 << val)) ~= 0 then
|
|
ret[#ret + 1] = { addr = olddata.start + i - 1,
|
|
oldval = old,
|
|
newval = new}
|
|
ref[ret[#ret].addr] = #ret
|
|
end
|
|
end
|
|
end
|
|
return ret, ref
|
|
end
|
|
|
|
-- compare successive pairs of blocks from a table returning only addresses that match every comparison
|
|
function cheat.compall(dattable, oper, val, signed, width, endian)
|
|
if #dattable < 2 then
|
|
return {}
|
|
end
|
|
local matches, refs = cheat.comp(dattable[1], dattable[2], oper, val, signed, width, endian)
|
|
local nonmatch = {}
|
|
for i = 2, #dattable - 1 do
|
|
local matchnext, refsnext = cheat.comp(dattable[i], dattable[i+1], oper, val, signed, width, endian)
|
|
for addr, ref in pairs(refs) do
|
|
if not refsnext[addr] then
|
|
nonmatch[ref] = true
|
|
refs[addr] = nil
|
|
else
|
|
matches[ref].newval = matchnext[refsnext[addr]].newval
|
|
end
|
|
end
|
|
end
|
|
local resort = {}
|
|
for num, match in pairs(matches) do
|
|
if not nonmatch[num] then
|
|
resort[#resort + 1] = match
|
|
end
|
|
end
|
|
return resort
|
|
end
|
|
|
|
|
|
-- compare a data block to the current state
|
|
function cheat.compcur(olddata, oper, val, signed, width, endian)
|
|
local newdata = cheat.save(olddata.space, olddata.start, olddata.size, olddata.space)
|
|
return cheat.comp(olddata, newdata, oper, val, signed, width, endian)
|
|
end
|
|
|
|
_G.cf = cheat
|
|
|
|
local devtable = {}
|
|
local devsel = 1
|
|
local devcur = 1
|
|
local bitwidth = 3
|
|
local signed = 0
|
|
local endian = 0
|
|
local optable = { "<", ">", "=", "!", "^" }
|
|
local opsel = 1
|
|
local bit = 0
|
|
local value = 0
|
|
local leftop = 1
|
|
local rightop = 0
|
|
local matches = {}
|
|
local matchsel = 1
|
|
local menu_blocks = {}
|
|
local midx = { region = 1, init = 2, save = 3, width = 5, signed = 6, endian = 7, op = 8, val = 9,
|
|
lop = 10, rop = 11, comp = 12, match = 14 }
|
|
|
|
local function start()
|
|
devtable = {}
|
|
devsel = 1
|
|
devcur = 1
|
|
bitwidth = 3
|
|
signed = 0
|
|
endian = 0
|
|
opsel = 1
|
|
bit = 0
|
|
value = 0
|
|
leftop = 1
|
|
rightop = 1
|
|
matches = {}
|
|
matchsel = 1
|
|
menu_blocks = {}
|
|
|
|
table = cheat.getspaces()
|
|
for tag, list in pairs(table) do
|
|
if list.program then
|
|
local ram = {}
|
|
for num, entry in pairs(list.program.map) do
|
|
if entry.writetype == "ram" then
|
|
ram[#ram + 1] = { offset = entry.offset, size = entry.endoff - entry.offset }
|
|
end
|
|
end
|
|
if next(ram) then
|
|
devtable[#devtable + 1] = { tag = tag, space = list.program, ram = ram }
|
|
end
|
|
end
|
|
end
|
|
table = cheat.getram()
|
|
for tag, ram in pairs(table) do
|
|
devtable[#devtable + 1] = { tag = tag, space = ram.dev, ram = {{ offset = 0, size = ram.size }} }
|
|
end
|
|
end
|
|
|
|
emu.register_start(start)
|
|
|
|
local function menu_populate()
|
|
local menu = {}
|
|
|
|
local function menu_lim(val, min, max, menuitem)
|
|
if val == min then
|
|
menuitem[3] = "r"
|
|
elseif val == max then
|
|
menuitem[3] = "l"
|
|
else
|
|
menuitem[3] = "lr"
|
|
end
|
|
end
|
|
|
|
menu[midx.region] = { "CPU or RAM", devtable[devsel].tag, 0 }
|
|
if #devtable == 1 then
|
|
menu[midx.region][3] = 0
|
|
else
|
|
menu_lim(devsel, 1, #devtable, menu[midx.region])
|
|
end
|
|
menu[midx.init] = { "Init", "", 0 }
|
|
if #menu_blocks ~= 0 then
|
|
menu[midx.save] = { "Save current", "", 0 }
|
|
menu[midx.save + 1] = { "---", "", "off" }
|
|
menu[midx.width] = { "Bit Width", 1 << bitwidth , 0 }
|
|
menu_lim(bitwidth, 3, 6, menu[midx.width])
|
|
menu[midx.signed] = { "Signed", "false", 0 }
|
|
menu_lim(signed, 0, 1, menu[midx.signed])
|
|
if signed == 1 then
|
|
menu[midx.signed][2] = "true"
|
|
end
|
|
menu[midx.endian] = { "Endian", "little", 0 }
|
|
menu_lim(endian, 0, 1, menu[midx.endian])
|
|
if endian == 1 then
|
|
menu[midx.endian][2] = "big"
|
|
end
|
|
menu[midx.op] = { "Operator", optable[opsel], "" }
|
|
menu_lim(opsel, 1, #optable, menu[midx.op])
|
|
if optable[opsel] == "^" then
|
|
menu[midx.val] = { "Bit", bit, "" }
|
|
menu_lim(bit, 0, (1 << bitwidth), menu[midx.val])
|
|
else
|
|
menu[midx.val] = { "Value", value, "" }
|
|
menu_lim(value, 0, 100, menu[midx.val]) -- max value?
|
|
if value == 0 then
|
|
menu[midx.val][2] = "Any"
|
|
end
|
|
end
|
|
menu[midx.lop] = { "Left operand", leftop, "" }
|
|
menu_lim(leftop, 1, #menu_blocks[1] + 1, menu[midx.lop])
|
|
if leftop == #menu_blocks[1] + 1 then
|
|
menu[midx.lop][2] = "All"
|
|
end
|
|
menu[midx.rop] = { "Right operand", rightop, "" }
|
|
menu_lim(rightop, 1, #menu_blocks[1] + 1, menu[midx.rop])
|
|
if rightop == #menu_blocks[1] + 1 then
|
|
menu[midx.rop][2] = "Current"
|
|
end
|
|
menu[midx.comp] = { "Compare", "", 0 }
|
|
if #matches ~= 0 then
|
|
menu[midx.comp + 1] = { "---", "", "off" }
|
|
menu[midx.match] = { "Match block", matchsel, "" }
|
|
if #matches == 1 then
|
|
menu[midx.match][3] = 0
|
|
else
|
|
menu_lim(matchsel, 1, #matches, menu[midx.match])
|
|
end
|
|
for num2, match in ipairs(matches[matchsel]) do
|
|
if #menu > 50 then
|
|
break
|
|
end
|
|
local numform = ""
|
|
if bitwidth == 4 then
|
|
numform = " %04x"
|
|
elseif bitwidth == 5 then
|
|
numform = " %08x"
|
|
elseif bitwidth == 6 then
|
|
numform = " %016x"
|
|
else
|
|
numform = " %02x"
|
|
end
|
|
menu[#menu + 1] = { string.format("%08x" .. numform .. numform, match.addr, match.oldval,
|
|
match.newval), "", 0 }
|
|
if not match.mode then
|
|
match.mode = 1
|
|
end
|
|
if match.mode == 1 then
|
|
menu[#menu][2] = "Test"
|
|
else
|
|
menu[#menu][2] = "Write"
|
|
end
|
|
menu_lim(match.mode, 1, 2, menu[#menu])
|
|
end
|
|
end
|
|
end
|
|
return menu
|
|
end
|
|
|
|
local function menu_callback(index, event)
|
|
local ret = false
|
|
|
|
local function incdec(val, min, max)
|
|
if event == "left" and val ~= min then
|
|
val = val - 1
|
|
ret = true
|
|
elseif event == "right" and val ~= max then
|
|
val = val + 1
|
|
ret = true
|
|
end
|
|
return val
|
|
end
|
|
|
|
if index == midx.region then
|
|
if (event == "left" or event == "right") and #menu_blocks ~= 0 then
|
|
manager:machine():popmessage("Changes to this only take effect when Init is selected")
|
|
end
|
|
devsel = incdec(devsel, 1, #devtable)
|
|
return true
|
|
elseif index == midx.init then
|
|
if event == "select" then
|
|
menu_blocks = {}
|
|
matches = {}
|
|
for num, region in ipairs(devtable[devcur].ram) do
|
|
menu_blocks[num] = {}
|
|
menu_blocks[num][1] = cheat.save(devtable[devcur].space, region.offset, region.size)
|
|
end
|
|
manager:machine():popmessage("Data cleared and current state saved")
|
|
ret = true
|
|
end
|
|
devcur = devsel
|
|
elseif index == midx.save then
|
|
if event == "select" then
|
|
for num, region in ipairs(devtable[devcur].ram) do
|
|
menu_blocks[num][#menu_blocks[num] + 1] = cheat.save(devtable[devcur].space, region.offset, region.size)
|
|
end
|
|
manager:machine():popmessage("Current state saved")
|
|
ret = true
|
|
end
|
|
elseif index == midx.width then
|
|
bitwidth = incdec(bitwidth, 3, 6)
|
|
if event == "left" or event == "right" then
|
|
bit = 0
|
|
end
|
|
elseif index == midx.signed then
|
|
signed = incdec(signed, 0, 1)
|
|
elseif index == midx.endian then
|
|
endian = incdec(endian, 0, 1)
|
|
elseif index == midx.op then
|
|
if event == "left" or event == "right" or event == "comment" then
|
|
if optable[opsel] == "<" then
|
|
manager:machine():popmessage("Left less than right, value is difference")
|
|
elseif optable[opsel] == ">" then
|
|
manager:machine():popmessage("Left greater than right, value is difference")
|
|
elseif optable[opsel] == "=" then
|
|
manager:machine():popmessage("Left equal to right, value is ignored")
|
|
elseif optable[opsel] == "!" then
|
|
manager:machine():popmessage("Left not equal to right, value is ignored")
|
|
elseif optable[opsel] == "^" then
|
|
manager:machine():popmessage("Left bit different than right")
|
|
end
|
|
end
|
|
opsel = incdec(opsel, 1, #optable)
|
|
elseif index == midx.val then
|
|
if optable[opsel] == "^" then
|
|
bit = incdec(bit, 0, (1 << bitwidth))
|
|
else
|
|
value = incdec(value, 0, 100)
|
|
end
|
|
elseif index == midx.lop then
|
|
leftop = incdec(leftop, 1, #menu_blocks[1] + 1)
|
|
if (event == "left" or event == "right" or event == "comment") and leftop == #menu_blocks[1] + 1 then
|
|
manager:machine():popmessage("Any compares every saved data pair and returns only matches that are true for all. Right operand is ignored.")
|
|
end
|
|
elseif index == midx.rop then
|
|
rightop = incdec(rightop, 1, #menu_blocks[1] + 1)
|
|
elseif index == midx.comp then
|
|
if event == "select" then
|
|
matches = {}
|
|
local val
|
|
if optable[opsel] == "^" then
|
|
val = bit
|
|
else
|
|
val = value
|
|
end
|
|
for num = 1, #menu_blocks do
|
|
if leftop == #menu_blocks[1] + 1 then
|
|
matches[#matches + 1] = cheat.compall(menu_blocks[num], optable[opsel], val, signed,
|
|
1 << bitwidth, endian)
|
|
elseif rightop == #menu_blocks[1] + 1 then
|
|
matches[#matches + 1] = cheat.compcur(menu_blocks[num][leftop], optable[opsel], val,
|
|
signed, 1 << bitwidth, endian)
|
|
else
|
|
matches[#matches + 1] = cheat.comp(menu_blocks[num][leftop], menu_blocks[num][rightop],
|
|
optable[opsel], val, signed, 1 << bitwidth, endian)
|
|
end
|
|
end
|
|
ret = true
|
|
end
|
|
elseif index == midx.match then
|
|
matchsel = incdec(matchsel, 0, #matches)
|
|
elseif index > midx.match then
|
|
local match = matches[matchsel][index - midx.match]
|
|
match.mode = incdec(match.mode, 1, 2)
|
|
if event == "select" then
|
|
local dev = devtable[devcur]
|
|
local cheat = { desc = string.format("Test cheat at addr %08x", match.addr), script = {} }
|
|
if dev.space.shortname then
|
|
cheat.ram = dev.tag
|
|
cheat.script.on = "ram:write(" .. match.addr .. "," .. match.newval .. ")"
|
|
else
|
|
cheat.space = { cpu = { tag = dev.tag, type = "program" } }
|
|
cheat.script.on = "cpu:write_u8(" .. match.addr .. "," .. match.newval .. ")"
|
|
end
|
|
if match.mode == 1 then
|
|
if not _G.ce then
|
|
manager:machine():popmessage("Cheat engine not available")
|
|
else
|
|
ce.inject(cheat)
|
|
end
|
|
else
|
|
|
|
end
|
|
|
|
end
|
|
end
|
|
devsel = devcur
|
|
return ret
|
|
end
|
|
emu.register_menu(menu_callback, menu_populate, "Cheat Finder")
|
|
end
|
|
|
|
return exports
|