mirror of
https://github.com/Abdess/retrobios.git
synced 2026-06-25 12:32:49 +00:00
expand bios collection, retrobat at 93% coverage
This commit is contained in:
parent
851a14e49a
commit
e6ea0484a8
3946 changed files with 8119839 additions and 2930936 deletions
376
bios/Arcade/MAME/plugins/hiscore/init.lua
Normal file
376
bios/Arcade/MAME/plugins/hiscore/init.lua
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
-- hiscore.lua
|
||||
-- by borgar@borgar.net, CC0 license
|
||||
--
|
||||
-- This uses MAME's built-in Lua scripting to implement
|
||||
-- high-score saving with hiscore.dat infom just as older
|
||||
-- builds did in the past.
|
||||
--
|
||||
local exports = {
|
||||
name = 'hiscore',
|
||||
version = '1.0.1',
|
||||
description = 'Hiscore',
|
||||
license = 'CC0',
|
||||
author = { name = 'borgar@borgar.net' } }
|
||||
|
||||
local hiscore = exports
|
||||
|
||||
local hiscore_plugin_path
|
||||
local reset_subscription, frame_subscription, stop_subscription
|
||||
|
||||
function hiscore.set_folder(path)
|
||||
hiscore_plugin_path = path
|
||||
end
|
||||
|
||||
function hiscore.startplugin()
|
||||
|
||||
local function get_data_path()
|
||||
return manager.machine.options.entries.homepath:value():match('([^;]+)') .. '/hiscore'
|
||||
end
|
||||
|
||||
-- configuration
|
||||
local config_read = false
|
||||
local timed_save = true
|
||||
|
||||
-- read configuration file from data directory
|
||||
local function read_config()
|
||||
if config_read then
|
||||
return true
|
||||
end
|
||||
local filename = get_data_path() .. '/plugin.cfg'
|
||||
local file = io.open(filename, 'r')
|
||||
if file then
|
||||
local json = require('json')
|
||||
local parsed_settings = json.parse(file:read('a'))
|
||||
file:close()
|
||||
if parsed_settings then
|
||||
if parsed_settings.only_save_at_exit and (parsed_settings.only_save_at_exit ~= 0) then
|
||||
timed_save = false
|
||||
end
|
||||
-- TODO: other settings? maybe path overrides for hiscore.dat or the hiscore data?
|
||||
config_read = true
|
||||
return true
|
||||
else
|
||||
emu.print_error(string.format('Error loading hiscore plugin settings: error parsing file "%s" as JSON', filename))
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- save configuration file
|
||||
local function save_config()
|
||||
local path = get_data_path()
|
||||
local attr = lfs.attributes(path)
|
||||
if not attr then
|
||||
lfs.mkdir(path)
|
||||
elseif attr.mode ~= 'directory' then
|
||||
emu.print_error(string.format('Error saving hiscore plugin settings: "%s" is not a directory', path))
|
||||
return
|
||||
end
|
||||
local settings = { only_save_at_exit = not timed_save }
|
||||
-- TODO: other settings?
|
||||
local filename = path .. '/plugin.cfg'
|
||||
local json = require('json')
|
||||
local data = json.stringify(settings, { indent = true })
|
||||
local file = io.open(filename, 'w')
|
||||
if not file then
|
||||
emu.print_error(string.format('Error saving hiscore plugin settings: error opening file "%s" for writing', filename))
|
||||
return
|
||||
end
|
||||
file:write(data)
|
||||
file:close()
|
||||
end
|
||||
|
||||
-- build menu
|
||||
local function populate_menu()
|
||||
local items = { }
|
||||
local setting = timed_save and _p('plugin-hiscore', 'When updated') or _p('plugin-hiscore', 'On exit')
|
||||
table.insert(items, { _p('plugin-hiscore', 'Hiscore Support Options'), '', 'off' })
|
||||
table.insert(items, { '---', '', '' })
|
||||
table.insert(items, { _p('plugin-hiscore', 'Save scores'), setting, timed_save and 'l' or 'r' })
|
||||
return items
|
||||
end
|
||||
|
||||
-- handle menu events
|
||||
local function handle_menu(index, event)
|
||||
if event == 'left' then
|
||||
timed_save = false
|
||||
return true
|
||||
elseif event == 'right' then
|
||||
timed_save = true
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local hiscoredata_path = "hiscore.dat";
|
||||
|
||||
local current_checksum = 0;
|
||||
local default_checksum = 0;
|
||||
|
||||
local scores_have_been_read = false;
|
||||
local mem_check_passed = false;
|
||||
local found_hiscore_entry = false;
|
||||
local delaytime = 0;
|
||||
|
||||
local function parse_table ( dsting )
|
||||
local _table = {}
|
||||
for line in string.gmatch(dsting, '([^\n]+)') do
|
||||
local delay = line:match('^@delay=([.%d]*)')
|
||||
if delay and #delay > 0 then
|
||||
delaytime = emu.time() + tonumber(delay)
|
||||
else
|
||||
local cpu, mem;
|
||||
local cputag, space, offs, len, chk_st, chk_ed, fill = string.match(line, '^@([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),?(%x?%x?)');
|
||||
cpu = manager.machine.devices[cputag];
|
||||
if not cpu then
|
||||
error(cputag .. " device not found")
|
||||
end
|
||||
local rgnname, rgntype = space:match("([^/]*)/?([^/]*)")
|
||||
if rgntype == "share" then
|
||||
mem = manager.machine.memory.shares[rgnname]
|
||||
else
|
||||
mem = cpu.spaces[space]
|
||||
end
|
||||
if not mem then
|
||||
error(space .. " space not found")
|
||||
end
|
||||
_table[ #_table + 1 ] = {
|
||||
mem = mem,
|
||||
addr = tonumber(offs, 16),
|
||||
size = tonumber(len, 16),
|
||||
c_start = tonumber(chk_st, 16),
|
||||
c_end = tonumber(chk_ed, 16),
|
||||
fill = tonumber(fill, 16)
|
||||
};
|
||||
end
|
||||
end
|
||||
return _table;
|
||||
end
|
||||
|
||||
|
||||
local function read_hiscore_dat ()
|
||||
local file = io.open( hiscoredata_path, "r" );
|
||||
local rm_match;
|
||||
if not file then
|
||||
file = io.open( hiscore_plugin_path .. "/hiscore.dat", "r" );
|
||||
end
|
||||
if emu.softname() ~= "" then
|
||||
local soft = emu.softname():match("([^:]*)$")
|
||||
rm_match = '^' .. emu.romname() .. ',' .. soft .. ':';
|
||||
else
|
||||
rm_match = '^' .. emu.romname() .. ':';
|
||||
end
|
||||
local cluster = "";
|
||||
local current_is_match = false;
|
||||
if file then
|
||||
repeat
|
||||
line = file:read("*l");
|
||||
if line then
|
||||
-- remove comments
|
||||
line = line:gsub( '[ \t\r\n]*;.+$', '' );
|
||||
-- handle lines
|
||||
if string.find(line, '^@') then -- data line
|
||||
if current_is_match then
|
||||
cluster = cluster .. "\n" .. line;
|
||||
end
|
||||
elseif string.find(line, rm_match) then --- match this game
|
||||
current_is_match = true;
|
||||
elseif string.find(line, '^[a-z0-9_,]+:') then --- some game
|
||||
if current_is_match and string.len(cluster) > 0 then
|
||||
break; -- we're done
|
||||
end
|
||||
else --- empty line or garbage
|
||||
-- noop
|
||||
end
|
||||
end
|
||||
until not line;
|
||||
file:close();
|
||||
end
|
||||
return cluster;
|
||||
end
|
||||
|
||||
|
||||
local function check_mem ( posdata )
|
||||
if #posdata < 1 then
|
||||
return false;
|
||||
end
|
||||
for ri,row in ipairs(posdata) do
|
||||
-- must pass mem check
|
||||
if row["c_start"] ~= row["mem"]:read_u8(row["addr"]) then
|
||||
return false;
|
||||
end
|
||||
if row["c_end"] ~= row["mem"]:read_u8(row["addr"]+row["size"]-1) then
|
||||
return false;
|
||||
end
|
||||
end
|
||||
return true;
|
||||
end
|
||||
|
||||
|
||||
local function get_file_name()
|
||||
local r;
|
||||
if emu.softname() ~= "" then
|
||||
local soft = emu.softname():match("([^:]*)$")
|
||||
r = get_data_path() .. '/' .. emu.romname() .. "_" .. soft .. ".hi";
|
||||
else
|
||||
r = get_data_path() .. '/' .. emu.romname() .. ".hi";
|
||||
end
|
||||
return r;
|
||||
end
|
||||
|
||||
|
||||
local function write_scores ( posdata )
|
||||
emu.print_verbose("hiscore: write_scores")
|
||||
local output = io.open(get_file_name(), "wb");
|
||||
if not output then
|
||||
-- attempt to create the directory, and try again
|
||||
lfs.mkdir(get_data_path());
|
||||
output = io.open(get_file_name(), "wb");
|
||||
end
|
||||
emu.print_verbose("hiscore: write_scores output")
|
||||
if output then
|
||||
for ri,row in ipairs(posdata) do
|
||||
t = {}
|
||||
for i=0,row["size"]-1 do
|
||||
t[i+1] = row["mem"]:read_u8(row["addr"] + i)
|
||||
end
|
||||
output:write(string.char(table.unpack(t)));
|
||||
end
|
||||
output:close();
|
||||
end
|
||||
emu.print_verbose("hiscore: write_scores end")
|
||||
end
|
||||
|
||||
|
||||
local function read_scores ( posdata )
|
||||
local input = io.open(get_file_name(), "rb");
|
||||
if input then
|
||||
for ri,row in ipairs(posdata) do
|
||||
local str = input:read(row["size"]);
|
||||
for i=0,row["size"]-1 do
|
||||
local b = str:sub(i+1,i+1):byte();
|
||||
row["mem"]:write_u8( row["addr"] + i, b );
|
||||
end
|
||||
end
|
||||
input:close();
|
||||
return true;
|
||||
end
|
||||
return false;
|
||||
end
|
||||
|
||||
|
||||
local function check_scores ( posdata )
|
||||
local r = 0;
|
||||
for ri,row in ipairs(posdata) do
|
||||
for i=0,row["size"]-1 do
|
||||
r = r + row["mem"]:read_u8( row["addr"] + i );
|
||||
end
|
||||
end
|
||||
return r;
|
||||
end
|
||||
|
||||
|
||||
local function init ()
|
||||
if not scores_have_been_read then
|
||||
if (delaytime <= emu.time()) and check_mem( positions ) then
|
||||
default_checksum = check_scores( positions );
|
||||
if read_scores( positions ) then
|
||||
emu.print_verbose( "hiscore: scores read OK" );
|
||||
else
|
||||
-- likely there simply isn't a .hi file around yet
|
||||
emu.print_verbose( "hiscore: scores read FAIL" );
|
||||
end
|
||||
scores_have_been_read = true;
|
||||
current_checksum = check_scores( positions );
|
||||
mem_check_passed = true;
|
||||
else
|
||||
-- memory check can fail while the game is still warming up
|
||||
-- TODO: only allow it to fail N many times
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local last_write_time = -10;
|
||||
local function tick ()
|
||||
-- set up scores if they have been
|
||||
init();
|
||||
-- only allow save check to run when
|
||||
if mem_check_passed and timed_save then
|
||||
-- The reason for this complicated mess is that
|
||||
-- MAME does expose a hook for "exit". Once it does,
|
||||
-- this should obviously just be done when the emulator
|
||||
-- shuts down (or reboots).
|
||||
local checksum = check_scores( positions );
|
||||
if checksum ~= current_checksum and checksum ~= default_checksum then
|
||||
-- 5 sec grace time so we don't clobber io and cause
|
||||
-- latency. This would be bad as it would only ever happen
|
||||
-- to players currently reaching a new highscore
|
||||
if emu.time() > last_write_time + 5 then
|
||||
write_scores( positions );
|
||||
current_checksum = checksum;
|
||||
last_write_time = emu.time();
|
||||
-- emu.print_verbose( "SAVE SCORES EVENT!", last_write_time );
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function reset()
|
||||
-- the notifier will still be attached even if the running game has no hiscore.dat entry
|
||||
if mem_check_passed and found_hiscore_entry then
|
||||
local checksum = check_scores(positions)
|
||||
if checksum ~= current_checksum and checksum ~= default_checksum then
|
||||
write_scores(positions)
|
||||
end
|
||||
end
|
||||
found_hiscore_entry = false
|
||||
mem_check_passed = false
|
||||
scores_have_been_read = false;
|
||||
end
|
||||
|
||||
reset_subscription = emu.add_machine_reset_notifier(function ()
|
||||
found_hiscore_entry = false
|
||||
mem_check_passed = false
|
||||
scores_have_been_read = false;
|
||||
last_write_time = -10
|
||||
emu.print_verbose("Starting " .. emu.gamename())
|
||||
read_config();
|
||||
local dat = read_hiscore_dat()
|
||||
if dat and dat ~= "" then
|
||||
emu.print_verbose( "hiscore: found hiscore.dat entry for " .. emu.romname() );
|
||||
res, positions = pcall(parse_table, dat);
|
||||
if not res then
|
||||
emu.print_error("hiscore: hiscore.dat parse error " .. positions);
|
||||
return;
|
||||
end
|
||||
for i, row in pairs(positions) do
|
||||
if row.fill then
|
||||
for i=0,row["size"]-1 do
|
||||
row["mem"]:write_u8(row["addr"] + i, row.fill)
|
||||
end
|
||||
end
|
||||
end
|
||||
found_hiscore_entry = true
|
||||
end
|
||||
end)
|
||||
|
||||
frame_subscription = emu.add_machine_frame_notifier(function ()
|
||||
if found_hiscore_entry then
|
||||
tick()
|
||||
end
|
||||
end)
|
||||
|
||||
stop_subscription = emu.add_machine_stop_notifier(function ()
|
||||
reset()
|
||||
save_config()
|
||||
end)
|
||||
|
||||
emu.register_prestart(function ()
|
||||
reset()
|
||||
end)
|
||||
|
||||
emu.register_menu(handle_menu, populate_menu, _p('plugin-hiscore', 'Hiscore Support'))
|
||||
end
|
||||
|
||||
return exports
|
||||
Loading…
Add table
Add a link
Reference in a new issue