mirror of
https://github.com/holub/mame
synced 2025-04-29 11:30:28 +03:00

osd/modules/input, emu/inpttype.cpp: Made most default joystick assignments supplied by input modules. Input modules take available controls into consideration when generating default assignments. emu/inpttype.ipp: Added a separate "Back" UI input separate from Cancel. You may want an easier to hit combination for moving to the previous menu than for exiting or cancelling input. They both default to Escape. emu/inpttype.ipp: Added a UI Help control. Currently only used by analog inputs menu emu/inpttype.h: Moved I/O port field type enum to its own header and sorted UI controls so they appear in a more logical order. ui: Don't use UI Select to restore defaults - people should be getting used to the UI Clear input by now. UI Select cycles multi-value items instead. ui/inputmap.cpp: Don't use immediate cancel to cycle between clearing and restoring default assignment (use UI Clear instead). osd: Reduced the number of files needing to include the dreaded emu.h. Got some implementation out of headers.
983 lines
26 KiB
Lua
983 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
|
|
|
|
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"))
|
|
|
|
emu.register_start(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)
|
|
|
|
emu.register_stop(function()
|
|
stop = true
|
|
consolelog = nil
|
|
save_hotkeys()
|
|
end)
|
|
|
|
emu.register_frame(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
|