mirror of
https://github.com/mamedev/mame.git
synced 2024-11-16 07:48:32 +01:00
b67b969bf0
* Moved several machine lifecycle callbacks to the notifier/subscriber model. The old callback registration model is still available for them for now, but prints a deprecation warning. * Added pre-save/post-load notifications. * Use a single allocated timer rather than one anonymous timer per waiter. Waiters no longer prevent saved states from being loaded. * Clean up outstanding waiters on stop or state load rather than just leaking them. * Started documenting parts of the emulator interface object that should be relatively stable. -imagedev/avivideo.cpp: Fixed an object leak on unload. Also changed some other media image devices to use smart pointers.
984 lines
26 KiB
Lua
984 lines
26 KiB
Lua
-- license:BSD-3-Clause
|
|
-- copyright-holders:Carl
|
|
--
|
|
-- json cheat file format
|
|
-- [{
|
|
-- "desc": "text",
|
|
-- "parameter": {
|
|
-- "min": "minval(0)",
|
|
-- "max": "maxval(numitems)",
|
|
-- "step": "stepval(1)",
|
|
-- "item" [{
|
|
-- "value": "itemval(index*stepval+minval)",
|
|
-- "text": "text"
|
|
-- },
|
|
-- ... ]
|
|
-- },
|
|
-- "cpu": {
|
|
-- "varname": "tag"
|
|
-- ...
|
|
-- }
|
|
-- "space": {
|
|
-- "varname": {
|
|
-- "tag": "tag",
|
|
-- "type": "program|data|io"
|
|
-- },
|
|
-- ...
|
|
-- },
|
|
-- "screen": {
|
|
-- "varname": "tag",
|
|
-- ...
|
|
-- },
|
|
-- "region": {
|
|
-- "varname": "tag",
|
|
-- ...
|
|
-- },
|
|
-- "ram": {
|
|
-- "varname": "tag",
|
|
-- ...
|
|
-- },
|
|
-- "share": {
|
|
-- "varname": "tag",
|
|
-- ...
|
|
-- },
|
|
-- "script": {
|
|
-- "on|off|run|change": "script",
|
|
-- ...
|
|
-- },
|
|
-- "comment": "text"
|
|
-- },
|
|
-- ... ]
|
|
--
|
|
-- Scripts are lua scripts with a limited api. Most library functions are unavailable.
|
|
-- Like the XML cheats, param is the current parameter value and variables are shared between scripts within a cheat
|
|
-- Differences from XML cheats:
|
|
-- - actions are only one line which include the entire script
|
|
-- - "condexpr" is replaced with lua control statements (if-then-else-end)
|
|
-- - variables are only limited by the limits of the lua interperter, you can have strings and tables
|
|
-- - the address spaces in the "space" blocks are accessible to the script if included,
|
|
-- same with regions (the "m" space in debug expr)
|
|
-- - frame is replaced by screen:frame_number() so if you use frame a screen needs to be in the device section
|
|
-- - output is a function and argindex isn't supported, output args need to be explicit and a screen device
|
|
-- must be provided
|
|
-- - cpu is only used for break and watch points, if it is defined and the debugger is not enabled (-debugger none is enough)
|
|
-- it will disable the cheat only if a point is set, check var for nil first
|
|
-- - watch points require the address space that you want to set the watch on, wptype is "r"-read, "w"-write or "rw"-both
|
|
|
|
local exports = {}
|
|
exports.name = "cheat"
|
|
exports.version = "0.0.1"
|
|
exports.description = "Cheat plugin"
|
|
exports.license = "BSD-3-Clause"
|
|
exports.author = { name = "Carl" }
|
|
|
|
local cheat = exports
|
|
|
|
local reset_subscription, stop_subscription, frame_subscription
|
|
|
|
function cheat.set_folder(path)
|
|
cheat.path = path
|
|
end
|
|
|
|
function cheat.startplugin()
|
|
local cheats = {}
|
|
local output = {}
|
|
local line = 0
|
|
local start_time = 0
|
|
local stop = true
|
|
local cheatname = ""
|
|
local consolelog = nil
|
|
local consolelast = 0
|
|
local perodicset = false
|
|
local watches = {}
|
|
local breaks = {}
|
|
local inputs = {}
|
|
|
|
local function load_cheats()
|
|
local filename = emu.romname()
|
|
local newcheats = {}
|
|
local file = emu.file(manager.machine.options.entries.cheatpath:value():gsub("([^;]+)", "%1;%1/cheat") , 1)
|
|
|
|
for name, image in pairs(manager.machine.images) do
|
|
if image.exists and image.software_list_name ~= "" then
|
|
filename = image.software_list_name .. "/" .. image.filename
|
|
end
|
|
end
|
|
|
|
cheatname = filename
|
|
local function add(addcheats)
|
|
if not next(newcheats) then
|
|
newcheats = addcheats
|
|
else
|
|
for num, cheat in pairs(addcheats) do
|
|
newcheats[#newcheats + 1] = cheat
|
|
end
|
|
end
|
|
end
|
|
for scrfile in lfs.dir(cheat.path) do
|
|
local name = string.match(scrfile, "^(cheat_.*).lua$")
|
|
if name then
|
|
local conv = require("cheat/" .. name)
|
|
if conv then
|
|
local ret = file:open(conv.filename(filename))
|
|
while not ret do
|
|
add(conv.conv_cheat(file:read(file:size())))
|
|
ret = file:open_next()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return newcheats
|
|
end
|
|
|
|
local function load_hotkeys()
|
|
local json = require("json")
|
|
local file = io.open(manager.machine.options.entries.cheatpath:value():match("([^;]+)") .. "/" .. cheatname .. "_hotkeys.json", "r")
|
|
if not file then
|
|
return
|
|
end
|
|
local hotkeys = json.parse(file:read("a"))
|
|
for num, val in ipairs(hotkeys) do
|
|
for num, cheat in pairs(cheats) do
|
|
if val.desc == cheat.desc then
|
|
cheat.hotkeys = {pressed = false, keys = manager.machine.input:seq_from_tokens(val.keys)}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function save_hotkeys()
|
|
local hotkeys = {}
|
|
for num, cheat in ipairs(cheats) do
|
|
if cheat.hotkeys then
|
|
local hotkey = {desc = cheat.desc, keys = manager.machine.input:seq_to_tokens(cheat.hotkeys.keys)}
|
|
if hotkey.keys ~= "" then
|
|
hotkeys[#hotkeys + 1] = hotkey
|
|
end
|
|
end
|
|
end
|
|
local path = manager.machine.options.entries.cheatpath:value():match("([^;]+)")
|
|
local filepath = path .. "/" .. cheatname .. "_hotkeys.json"
|
|
if #hotkeys > 0 then
|
|
local json = require("json")
|
|
local attr = lfs.attributes(path)
|
|
if not attr then
|
|
lfs.mkdir(path)
|
|
elseif attr.mode ~= "directory" then -- uhhh?
|
|
return
|
|
end
|
|
if cheatname:find("/", 1, true) then
|
|
local softpath = path .. "/" .. cheatname:match("([^/]+)")
|
|
attr = lfs.attributes(softpath)
|
|
if not attr then
|
|
lfs.mkdir(softpath)
|
|
elseif attr.mode ~= "directory" then -- uhhh?
|
|
return
|
|
end
|
|
end
|
|
|
|
local file = io.open(filepath, "w+")
|
|
if file then
|
|
file:write(json.stringify(hotkeys, {indent = true}))
|
|
file:close()
|
|
end
|
|
else
|
|
local attr = lfs.attributes(filepath)
|
|
if attr and (attr.mode == "file") then
|
|
local json = require("json")
|
|
local file = io.open(filepath, "w+")
|
|
if file then
|
|
file:write(json.stringify(hotkeys, {indent = true}))
|
|
file:close()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function cheat_error(cheat, msg)
|
|
emu.print_error("error cheat script error: \"" .. cheat.desc .. "\" " .. msg)
|
|
cheat.desc = cheat.desc .. " error"
|
|
cheat.script = nil
|
|
cheat.enabled = nil
|
|
return
|
|
end
|
|
|
|
local function run_if(cheat, func)
|
|
if func then
|
|
local stat, err = pcall(func)
|
|
if not stat then
|
|
cheat_error(cheat, err)
|
|
end
|
|
return func
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function draw_text(screen, x, y, color, form, ...)
|
|
local str = form:format(...)
|
|
if y == "auto" then
|
|
y = line
|
|
line = line + 1
|
|
end
|
|
if not screen then
|
|
emu.print_verbose("draw_text: invalid screen")
|
|
return
|
|
end
|
|
if type(x) == "string" then
|
|
y = y * mame_manager.ui.line_height
|
|
end
|
|
output[#output + 1] = { type = "text", scr = screen, x = x, y = y, str = str, color = color }
|
|
end
|
|
|
|
local function draw_line(screen, x1, y1, x2, y2, color)
|
|
if not screen then
|
|
emu.print_verbose("draw_line: invalid screen")
|
|
return
|
|
end
|
|
output[#output + 1] = { type = "line", scr = screen, x1 = x1, x2 = x2, y1 = y1, y2 = y2, color = color }
|
|
end
|
|
|
|
local function draw_box(screen, x1, y1, x2, y2, bgcolor, linecolor)
|
|
if not screen then
|
|
emu.print_verbose("draw_box: invalid screen")
|
|
return
|
|
end
|
|
output[#output + 1] = { type = "box", scr = screen, x1 = x1, x2 = x2, y1 = y1, y2 = y2, bgcolor = bgcolor, linecolor = linecolor }
|
|
end
|
|
|
|
local function tobcd(val)
|
|
local result = 0
|
|
local shift = 0
|
|
while val ~= 0 do
|
|
result = result + ((val % 10) << shift)
|
|
val = val / 10
|
|
shift = shift + 4
|
|
end
|
|
return result
|
|
end
|
|
|
|
local function frombcd(val)
|
|
local result = 0
|
|
local mul = 1
|
|
while val ~= 0 do
|
|
result = result + ((val % 16) * mul)
|
|
val = val >> 4
|
|
mul = mul * 10
|
|
end
|
|
return result
|
|
end
|
|
|
|
local function time()
|
|
return emu.time() - start_time
|
|
end
|
|
|
|
local function periodiccb()
|
|
local last = consolelast
|
|
local msg = consolelog[#consolelog]
|
|
consolelast = #consolelog
|
|
if #consolelog > last and msg:find("Stopped at", 1, true) then
|
|
local point = tonumber(msg:match("Stopped at breakpoint ([0-9]+)"))
|
|
if not point then
|
|
point = tonumber(msg:match("Stopped at watchpoint ([0-9]+"))
|
|
if not point then
|
|
return -- ??
|
|
end
|
|
local wp = watches[point]
|
|
if wp then
|
|
run_if(wp.cheat, wp.func)
|
|
-- go in case a debugger other than "none" is enabled
|
|
-- don't use an b/wpset action because that will supress the b/wp index
|
|
manager.machine.debugger.execution_state = "run"
|
|
end
|
|
else
|
|
local bp = breaks[point]
|
|
if bp then
|
|
run_if(bp.cheat, bp.func)
|
|
manager.machine.debugger.execution_state = "run"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function bpset(cheat, dev, addr, func)
|
|
if cheat.is_oneshot then
|
|
error("bpset not permitted in oneshot cheat")
|
|
return
|
|
end
|
|
local idx = dev.debug:bpset(addr)
|
|
breaks[idx] = {cheat = cheat, func = func, dev = dev}
|
|
end
|
|
|
|
local function wpset(cheat, dev, space, wptype, addr, len, func)
|
|
if cheat.is_oneshot then
|
|
error("wpset not permitted in oneshot cheat")
|
|
return
|
|
end
|
|
if not space.name then
|
|
error("bad space in wpset")
|
|
return
|
|
end
|
|
local idx = dev.debug:wpset(space, wptype, addr, len)
|
|
watches[idx] = {cheat = cheat, func = func, dev = dev}
|
|
end
|
|
|
|
local function bwpclr(cheat)
|
|
if not manager.machine.debugger then
|
|
return
|
|
end
|
|
for num, bp in pairs(breaks) do
|
|
if cheat == bp.cheat then
|
|
bp.dev.debug:bpclr(num)
|
|
end
|
|
end
|
|
for num, wp in pairs(watches) do
|
|
if cheat == wp.cheat then
|
|
wp.dev.debug:wpclr(num)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function input_trans(list)
|
|
local xlate = { start = {}, stop = {}, last = 0 }
|
|
local function errout(port, field)
|
|
cheat:set_enabled(false)
|
|
error(port .. field .. " not found")
|
|
return
|
|
end
|
|
|
|
for num, entry in ipairs(list) do
|
|
if entry.port:sub(1, 1) ~= ":" then
|
|
entry.port = ":" .. entry.port
|
|
end
|
|
local port = manager.machine.ioport.ports[entry.port]
|
|
if not port then
|
|
errout(entry.port, entry.field)
|
|
end
|
|
local field = port.fields[entry.field]
|
|
if not field then
|
|
errout(entry.port, entry.field)
|
|
end
|
|
if not xlate.start[entry.start] then
|
|
xlate.start[entry.start] = {}
|
|
end
|
|
if not xlate.stop[entry.stop] then
|
|
xlate.stop[entry.stop] = {}
|
|
end
|
|
local start = xlate.start[entry.start]
|
|
local stop = xlate.stop[entry.stop]
|
|
local ent = { port = port, field = field }
|
|
stop[#stop + 1] = ent
|
|
start[#start + 1] = ent
|
|
if entry.stop > xlate.last then
|
|
xlate.last = entry.stop
|
|
end
|
|
end
|
|
return xlate
|
|
end
|
|
|
|
local function input_run(cheat, list)
|
|
if not cheat.is_oneshot then
|
|
cheat.enabled = false
|
|
error("input_run only allowed in one shot cheats")
|
|
return
|
|
end
|
|
local _, screen = next(manager.machine.screens)
|
|
list.begin = screen:frame_number()
|
|
inputs[#inputs + 1] = list
|
|
end
|
|
|
|
local function param_calc(param)
|
|
if param.item then
|
|
if not param.item[param.index] then -- uh oh
|
|
param.index = 1
|
|
end
|
|
param.value = param.item[param.index].value
|
|
return
|
|
end
|
|
param.value = param.min + (param.step * (param.index - 1))
|
|
if param.value > param.max then
|
|
param.value = param.max
|
|
end
|
|
end
|
|
|
|
-- return is current state, ui change
|
|
local function set_enabled(cheat, state)
|
|
if cheat.is_oneshot then
|
|
if state then
|
|
if cheat.parameter and cheat.script.change and cheat.parameter.index ~= 0 then
|
|
param_calc(cheat.parameter)
|
|
cheat.cheat_env.param = cheat.parameter.value
|
|
cheat.script.change()
|
|
elseif not cheat.parameter and cheat.script.on then
|
|
cheat.script.on()
|
|
end
|
|
end
|
|
return false, false
|
|
end
|
|
if cheat.enabled == state then
|
|
return state, false
|
|
end
|
|
if not state then
|
|
cheat.enabled = false
|
|
run_if(cheat, cheat.script.off)
|
|
bwpclr(cheat)
|
|
else
|
|
cheat.enabled = true
|
|
run_if(cheat, cheat.script.on)
|
|
end
|
|
return state, true
|
|
end
|
|
|
|
-- return is current index, ui change
|
|
local function set_index(cheat, index)
|
|
local param = cheat.parameter
|
|
local oldindex = param.index
|
|
if (index < 0) or (index > param.last) or (param.index == index) then
|
|
return param.index, false
|
|
end
|
|
param.index = index
|
|
if index == 0 then
|
|
cheat.cheat_env.param = param.min
|
|
cheat:set_enabled(false)
|
|
else
|
|
if oldindex == 0 then
|
|
cheat:set_enabled(true)
|
|
end
|
|
param_calc(param)
|
|
cheat.cheat_env.param = param.value
|
|
if not cheat.is_oneshot then
|
|
run_if(cheat, cheat.script.change)
|
|
end
|
|
end
|
|
return index, true
|
|
end
|
|
|
|
local function parse_cheat(cheat)
|
|
cheat.cheat_env = { draw_text = draw_text,
|
|
draw_line = draw_line,
|
|
draw_box = draw_box,
|
|
tobcd = tobcd,
|
|
frombcd = frombcd,
|
|
pairs = pairs,
|
|
ipairs = ipairs,
|
|
outputs = manager.machine.output,
|
|
time = time,
|
|
input_trans = input_trans,
|
|
input_run = function(list) input_run(cheat, list) end,
|
|
os = { time = os.time, date = os.date, difftime = os.difftime },
|
|
table =
|
|
{ insert = table.insert,
|
|
remove = table.remove } }
|
|
cheat.enabled = false
|
|
cheat.set_enabled = set_enabled;
|
|
cheat.get_enabled = function(cheat) return cheat.enabled end
|
|
cheat.is_oneshot = cheat.script and not cheat.script.run and not cheat.script.off
|
|
|
|
-- verify scripts are valid first
|
|
if not cheat.script then
|
|
return
|
|
end
|
|
for name, script in pairs(cheat.script) do
|
|
script, err = load(script, cheat.desc .. name, "t", cheat.cheat_env)
|
|
if not script then
|
|
cheat_error(cheat, err)
|
|
return
|
|
end
|
|
cheat.script[name] = script
|
|
end
|
|
-- initialize temp[0-9] for backward compatbility reasons
|
|
for i = 0, 9 do
|
|
cheat.cheat_env["temp" .. i] = 0
|
|
end
|
|
if cheat.cpu then
|
|
cheat.cpudev = {}
|
|
for name, tag in pairs(cheat.cpu) do
|
|
if manager.machine.debugger then
|
|
local dev = manager.machine.devices[tag]
|
|
if not dev or not dev.debug then
|
|
cheat_error(cheat, "missing or invalid device " .. tag)
|
|
return
|
|
end
|
|
cheat.cheat_env[name] = {
|
|
bpset = function(addr, func) bpset(cheat, dev, addr, func) end,
|
|
wpset = function(space, wptype, addr, len, func) wpset(cheat, dev, space, wptype, addr, len, func) end,
|
|
regs = dev.state }
|
|
cheat.bp = {}
|
|
cheat.wp = {}
|
|
if not periodicset then
|
|
emu.register_periodic(periodic_cb)
|
|
periodicset = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if cheat.space then
|
|
for name, space in pairs(cheat.space) do
|
|
local cpu, mem
|
|
cpu = manager.machine.devices[space.tag]
|
|
if not cpu then
|
|
cheat_error(cheat, "missing device " .. space.tag)
|
|
return
|
|
end
|
|
if space.type then
|
|
mem = cpu.spaces[space.type]
|
|
else
|
|
space.type = "program"
|
|
mem = cpu.spaces["program"]
|
|
end
|
|
if not mem then
|
|
cheat_error(cheat, "missing space " .. space.type)
|
|
return
|
|
end
|
|
cheat.cheat_env[name] = mem
|
|
end
|
|
end
|
|
if cheat.screen then
|
|
for name, screen in pairs(cheat.screen) do
|
|
local scr = manager.machine.screens[screen]
|
|
if screen == "ui" then
|
|
scr = manager.machine.render.ui_container
|
|
elseif not scr then
|
|
local tag
|
|
local nxt, coll = manager.machine.screens:pairs()
|
|
tag, scr = nxt(coll) -- get any screen
|
|
end
|
|
cheat.cheat_env[name] = scr
|
|
end
|
|
end
|
|
if cheat.region then
|
|
for name, region in pairs(cheat.region) do
|
|
local mem = manager.machine.memory.regions[region]
|
|
if not mem then
|
|
cheat_error(cheat, "missing region " .. region)
|
|
return
|
|
end
|
|
cheat.cheat_env[name] = mem
|
|
end
|
|
end
|
|
if cheat.ram then
|
|
for name, tag in pairs(cheat.ram) do
|
|
local ram = manager.machine.devices[tag]
|
|
if not ram then
|
|
cheat_error(cheat, "missing ram device " .. tag)
|
|
return
|
|
end
|
|
cheat.cheat_env[name] = emu.item(ram.items["0/m_pointer"])
|
|
end
|
|
end
|
|
if cheat.share then
|
|
for name, tag in pairs(cheat.share) do
|
|
local share = manager.machine.memory.shares[tag]
|
|
if not share then
|
|
cheat_error(cheat, "missing share " .. share)
|
|
return
|
|
end
|
|
cheat.cheat_env[name] = share
|
|
end
|
|
end
|
|
local param = cheat.parameter
|
|
if not param then
|
|
return
|
|
end
|
|
cheat.set_index = set_index;
|
|
cheat.set_value = function(cheat, value)
|
|
local idx = ((value - cheat.parameter.min) / cheat.parameter.step) + 1
|
|
local chg = false
|
|
if math.integer(idx) == idx then
|
|
idx, chg = cheat:set_index(idx)
|
|
end
|
|
return cheat.parameter.value, chg
|
|
end
|
|
cheat.get_index = function(cheat) return cheat.parameter.index end
|
|
cheat.get_value = function(cheat) return cheat.parameter.value end
|
|
param.min = tonumber(param.min) or 0
|
|
param.max = tonumber(param.max) or #param.item
|
|
param.step = tonumber(param.step) or 1
|
|
if param.item then
|
|
for count, item in pairs(param.item) do
|
|
if not item.value then
|
|
item.value = (count * param.step) + param.min
|
|
else
|
|
item.value = tonumber(item.value)
|
|
end
|
|
end
|
|
param.last = #param.item
|
|
else
|
|
param.last = ((param.max - param.min) / param.step) + 1
|
|
end
|
|
param.index = 0
|
|
param.value = param.min
|
|
cheat.cheat_env.param = param.min
|
|
end
|
|
|
|
local hotkeymenu = false
|
|
local hotkeylist = {}
|
|
local commonui
|
|
local poller
|
|
|
|
local function menu_populate()
|
|
local menu = {}
|
|
if hotkeymenu then
|
|
local ioport = manager.machine.ioport
|
|
local input = manager.machine.input
|
|
|
|
menu[1] = {_("Select cheat to set hotkey"), "", "off"}
|
|
menu[2] = {string.format(_("Press %s to clear hotkey"), manager.ui:get_general_input_setting(ioport:token_to_input_type("UI_CLEAR"))), "", "off"}
|
|
menu[3] = {"---", "", "off"}
|
|
hotkeylist = {}
|
|
|
|
local function hkcbfunc(cheat, event)
|
|
if poller then
|
|
if poller:poll() then
|
|
if poller.sequence then
|
|
cheat.hotkeys = { pressed = false, keys = poller.sequence }
|
|
end
|
|
poller = nil
|
|
return true
|
|
end
|
|
elseif event == "clear" then
|
|
cheat.hotkeys = nil
|
|
return true
|
|
elseif event == "select" then
|
|
if not commonui then
|
|
commonui = require('commonui')
|
|
end
|
|
poller = commonui.switch_polling_helper()
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
for num, cheat in ipairs(cheats) do
|
|
if cheat.script then
|
|
local setting = cheat.hotkeys and input:seq_name(cheat.hotkeys.keys) or _("None")
|
|
menu[#menu + 1] = {cheat.desc, setting, ""}
|
|
hotkeylist[#hotkeylist + 1] = function(event) return hkcbfunc(cheat, event) end
|
|
end
|
|
end
|
|
menu[#menu + 1] = {"---", "", ""}
|
|
menu[#menu + 1] = {_("Done"), "", ""}
|
|
if poller then
|
|
return poller:overlay(menu)
|
|
else
|
|
return menu
|
|
end
|
|
end
|
|
for num, cheat in ipairs(cheats) do
|
|
menu[num] = {}
|
|
menu[num][1] = cheat.desc
|
|
if not cheat.parameter then
|
|
if not cheat.script then
|
|
if cheat.desc == "" then
|
|
menu[num][1] = "---"
|
|
end
|
|
menu[num][2] = ""
|
|
menu[num][3] = "off"
|
|
elseif cheat.is_oneshot then
|
|
menu[num][2] = _("Set")
|
|
menu[num][3] = ""
|
|
else
|
|
if cheat.enabled then
|
|
menu[num][2] = _("On")
|
|
menu[num][3] = "l"
|
|
else
|
|
menu[num][2] = _("Off")
|
|
menu[num][3] = "r"
|
|
end
|
|
end
|
|
else
|
|
if cheat.parameter.index == 0 then
|
|
if cheat.is_oneshot then
|
|
menu[num][2] = _("Set")
|
|
else
|
|
menu[num][2] = _("Off")
|
|
end
|
|
menu[num][3] = "r"
|
|
else
|
|
if cheat.parameter.item then
|
|
menu[num][2] = cheat.parameter.item[cheat.parameter.index].text
|
|
else
|
|
menu[num][2] = cheat.parameter.value
|
|
end
|
|
menu[num][3] = "l"
|
|
if cheat.parameter.index < cheat.parameter.last then
|
|
menu[num][3] = "lr"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
menu[#menu + 1] = {"---", "", ""}
|
|
menu[#menu + 1] = {_("Set hotkeys"), "", ""}
|
|
menu[#menu + 1] = {_("Reset All"), "", ""}
|
|
menu[#menu + 1] = {_("Reload All"), "", ""}
|
|
return menu
|
|
end
|
|
|
|
local function menu_callback(index, event)
|
|
manager.machine:popmessage()
|
|
if hotkeymenu then
|
|
if event == "back" then
|
|
hotkeymenu = false
|
|
return true
|
|
else
|
|
index = index - 3
|
|
if index >= 1 and index <= #hotkeylist then
|
|
hotkeylist[index](event)
|
|
return true
|
|
elseif index == #hotkeylist + 2 and event == "select" then
|
|
hotkeymenu = false
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
if index > #cheats and event == "select" then
|
|
index = index - #cheats
|
|
if index == 2 then
|
|
hotkeymenu = true
|
|
elseif index == 3 then
|
|
for num, cheat in pairs(cheats) do
|
|
cheat:set_enabled(false)
|
|
if cheat.parameter then
|
|
cheat:set_index(0)
|
|
end
|
|
end
|
|
elseif index == 4 then
|
|
for num, cheat in pairs(cheats) do
|
|
cheat:set_enabled(false)
|
|
end
|
|
cheats = load_cheats()
|
|
for num, cheat in pairs(cheats) do
|
|
parse_cheat(cheat)
|
|
end
|
|
load_hotkeys()
|
|
end
|
|
return true
|
|
end
|
|
|
|
local cheat = cheats[index]
|
|
if not cheat then
|
|
return false
|
|
end
|
|
if event == "up" or event == "down" or event == "comment" then
|
|
if cheat.comment then
|
|
manager.machine:popmessage(string.format(_("Cheat Comment:\n%s"), cheat.comment))
|
|
end
|
|
elseif event == "left" then
|
|
if cheat.parameter then
|
|
local idx, chg = cheat:set_index(cheat:get_index() - 1)
|
|
return chg
|
|
else
|
|
if not cheat.is_oneshot then
|
|
local state, chg = cheat:set_enabled(false)
|
|
return chg
|
|
end
|
|
return false
|
|
end
|
|
elseif event == "right" then
|
|
if cheat.parameter then
|
|
local idx, chg = cheat:set_index(cheat:get_index() + 1)
|
|
return chg
|
|
else
|
|
if not cheat.is_oneshot then
|
|
local state, chg = cheat:set_enabled(true)
|
|
return chg
|
|
end
|
|
return false
|
|
end
|
|
elseif event == "select" then
|
|
if cheat.is_oneshot then
|
|
cheat:set_enabled(true)
|
|
if cheat.parameter and cheat.script.change and cheat:get_index() ~= 0 then
|
|
local itemtext
|
|
if cheat.parameter.item then
|
|
itemtext = cheat.parameter.item[cheat.parameter.index].text
|
|
else
|
|
itemtext = cheat.parameter.value
|
|
end
|
|
manager.machine:popmessage(string.format(_("Activated: %s = %s"), cheat.desc, itemtext))
|
|
elseif not cheat.parameter and cheat.script.on then
|
|
manager.machine:popmessage(string.format(_("Activated: %s"), cheat.desc))
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
emu.register_menu(function(index, event)
|
|
return menu_callback(index, event)
|
|
end,
|
|
function()
|
|
return menu_populate()
|
|
end, _("Cheat"))
|
|
|
|
reset_subscription = emu.add_machine_reset_notifier(function ()
|
|
if not stop then
|
|
return
|
|
end
|
|
stop = false
|
|
start_time = emu.time()
|
|
cheats = load_cheats()
|
|
local json = require("json")
|
|
local file = io.open(manager.machine.options.entries.cheatpath:value():match("([^;]+)") .. "/output.json", "w")
|
|
if file then
|
|
file:write(json.stringify(cheats, {indent = true}))
|
|
file:close()
|
|
end
|
|
for num, cheat in pairs(cheats) do
|
|
parse_cheat(cheat)
|
|
end
|
|
load_hotkeys()
|
|
if manager.machine.debugger then
|
|
consolelog = manager.machine.debugger.consolelog
|
|
consolelast = 0
|
|
end
|
|
end)
|
|
|
|
stop_subscription = emu.add_machine_stop_notifier(function ()
|
|
stop = true
|
|
consolelog = nil
|
|
save_hotkeys()
|
|
end)
|
|
|
|
frame_subscription = emu.add_machine_frame_notifier(function ()
|
|
if stop then
|
|
return
|
|
end
|
|
for num, cheat in pairs(cheats) do
|
|
if cheat.enabled then
|
|
run_if(cheat, cheat.script.run)
|
|
end
|
|
if cheat.hotkeys and cheat.hotkeys.keys then
|
|
if manager.machine.input:seq_pressed(cheat.hotkeys.keys) then
|
|
if not cheat.hotkeys.pressed then
|
|
if cheat.is_oneshot then
|
|
if not run_if(cheat, cheat.script.change) then
|
|
run_if(cheat, cheat.script.on)
|
|
end
|
|
manager.machine:popmessage(string.format(_("Activated: %s"), cheat.desc))
|
|
elseif not cheat.enabled then
|
|
cheat.enabled = true
|
|
run_if(cheat, cheat.script.on)
|
|
manager.machine:popmessage(string.format(_("Enabled: %s"), cheat.desc))
|
|
else
|
|
cheat.enabled = false
|
|
run_if(cheat, cheat.script.off)
|
|
bwpclr(cheat)
|
|
manager.machine:popmessage(string.format(_("Disabled: %s"), cheat.desc))
|
|
end
|
|
end
|
|
cheat.hotkeys.pressed = true
|
|
else
|
|
cheat.hotkeys.pressed = false
|
|
end
|
|
end
|
|
end
|
|
for num, input in pairs(inputs) do
|
|
local _, screen = next(manager.machine.screens)
|
|
local framenum = screen:frame_number() - input.begin
|
|
local enttab = input.start[framenum]
|
|
if enttab then
|
|
for num, entry in pairs(enttab) do
|
|
entry.field:set_value(1)
|
|
end
|
|
end
|
|
enttab = input.stop[framenum]
|
|
if enttab then
|
|
for num, entry in pairs(enttab) do
|
|
entry.field:set_value(0)
|
|
|
|
end
|
|
end
|
|
if framenum >= input.last then
|
|
table.remove(inputs, num)
|
|
end
|
|
end
|
|
|
|
end)
|
|
|
|
emu.register_frame_done(function()
|
|
if stop then
|
|
return
|
|
end
|
|
line = 0
|
|
for num, draw in pairs(output) do
|
|
if draw.type == "text" then
|
|
if not draw.color then
|
|
draw.scr:draw_text(draw.x, draw.y, draw.str)
|
|
else
|
|
draw.scr:draw_text(draw.x, draw.y, draw.str, draw.color)
|
|
end
|
|
elseif draw.type == "line" then
|
|
draw.scr:draw_line(draw.x1, draw.y1, draw.x2, draw.y2, draw.color)
|
|
elseif draw.type == "box" then
|
|
draw.scr:draw_box(draw.x1, draw.y1, draw.x2, draw.y2, draw.linecolor, draw.bgcolor)
|
|
end
|
|
end
|
|
output = {}
|
|
end)
|
|
|
|
local ce = {}
|
|
|
|
-- interface to script cheat engine
|
|
function ce.inject(newcheat)
|
|
cheats[#cheats + 1] = newcheat
|
|
parse_cheat(newcheat)
|
|
manager.machine:popmessage(string.format(_("%s added"), newcheat.desc))
|
|
end
|
|
|
|
function ce.get(index)
|
|
local cheat = cheats[index]
|
|
if not cheat then
|
|
return nil
|
|
end
|
|
local intf = {
|
|
get_enabled = function() return cheat:get_enabled() end,
|
|
set_enabled = function(status) return cheat:set_enabled(status) end,
|
|
desc = cheat.desc,
|
|
is_oneshot = cheat.is_oneshot,
|
|
comment = cheat.comment,
|
|
get_hotkeys = function() if cheat.hotkeys then return cheat.hotkeys.keys end return nil end,
|
|
set_hotkeys = function(seq) cheat.hotkeys = { pressed = false, keys = manager.machine.input:seq_clean(seq) } end
|
|
}
|
|
if cheat.script then
|
|
intf.script = {}
|
|
if cheat.script.on then intf.script.on = true end
|
|
if cheat.script.off then intf.script.off = true end
|
|
if cheat.script.run then intf.script.run = true end
|
|
if cheat.script.change then intf.script.change = true end
|
|
end
|
|
|
|
if cheat.parameter then
|
|
intf.parameter = {}
|
|
intf.get_value = function() return cheat:get_value() end
|
|
intf.set_value = function(value) return cheat:set_value(value) end
|
|
intf.get_index = function() return cheat:get_index() end
|
|
intf.set_index = function(index) return cheat:set_index(index) end
|
|
intf.parameter.min = cheat.parameter.min
|
|
intf.parameter.max = cheat.parameter.max
|
|
intf.parameter.step = cheat.parameter.step
|
|
if cheat.parameter.item then
|
|
intf.parameter.item = {}
|
|
for idx, item in pairs(cheat.parameter.item) do
|
|
intf.parameter.item[idx] = {}
|
|
intf.parameter.item[idx].text = cheat.parameter.item[idx].text
|
|
intf.parameter.item[idx].value = cheat.parameter.item[idx].value
|
|
end
|
|
end
|
|
end
|
|
return intf
|
|
end
|
|
|
|
function ce.list()
|
|
local list = {}
|
|
for num, cheat in pairs(cheats) do
|
|
list[num] = cheat.desc
|
|
end
|
|
return list
|
|
end
|
|
|
|
_G.emu.plugin.cheat = ce
|
|
|
|
end
|
|
|
|
return exports
|