diff options
| author | 2016-03-22 16:19:15 +0100 | |
|---|---|---|
| committer | 2016-03-22 16:19:15 +0100 | |
| commit | ef86ee6cec89be8141a23afd1f98744632828dfb (patch) | |
| tree | f4d200f87b32e03bee0fe0d5af4a1bf929081e1e /plugins/hiscore/init.lua | |
| parent | 2937ec37b8608a7932e01d040d09e8f6901bde10 (diff) | |
added hiscore plugin (disabled for now) (nw)
Diffstat (limited to 'plugins/hiscore/init.lua')
| -rw-r--r-- | plugins/hiscore/init.lua | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/plugins/hiscore/init.lua b/plugins/hiscore/init.lua new file mode 100644 index 00000000000..5c54a53912b --- /dev/null +++ b/plugins/hiscore/init.lua @@ -0,0 +1,257 @@ +-- hiscore.lua +-- by borgar@borgar.net, WTFPL license +-- +-- This uses MAME's built-in Lua scripting to implment +-- high-score saving with hiscore.dat infom just as older +-- builds did in the past. +-- + +local exports = {} +exports.name = "hiscore" +exports.version = "1.0.0" +exports.description = "Hiscore" +exports.license = "WTFPL license" +exports.author = { name = "borgar@borgar.net" } +local hiscore = exports + +local hiscore_plugin_path = "" + +function hiscore.set_folder(path) + hiscore_plugin_path = path +end + +function hiscore.startplugin() + + hiscoredata_path = "hiscore.dat"; + hiscore_path = "hi"; + + current_checksum = 0; + default_checksum = 0; + + scores_have_been_read = false; + mem_check_passed = false; + current_game = ""; + + positions = {}; + + function parse_table ( dsting ) + local _table = {}; + for line in string.gmatch(dsting, '([^\n]+)') do + local cpu, mem; + cputag, space, offs, len, chk_st, chk_ed = string.match(line, '^@([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+)'); + cpu = manager:machine().devices[cputag]; + if not cpu then + return nil; + end + mem = cpu.spaces[space]; + if not mem then + return nil; + 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), + }; + end + return _table; + end + + + function read_hiscore_dat () + file = io.open( hiscoredata_path, "r" ); + if not file then + file = io.open( hiscore_plugin_path .. "/hiscore.dat", "r" ); + end + rm_match = '^' .. emu.romname() .. ':'; + cluster = ""; + 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 + + + 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 + + + function get_file_name () + r = hiscore_path .. '/' .. emu.romname() .. ".hi"; + return r; + end + + + function write_scores ( posdata ) + print("write_scores") + local output = io.open(get_file_name(), "wb"); + if not output then + -- attempt to create the directory, and try again + os.execute( "mkdir " .. hiscore_path ); + output = io.open(get_file_name(), "wb"); + end + print("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 + print("write_scores end") + end + + + 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 + + + function check_scores ( posdata ) + local r = 0; + -- commonly the first entry will be for the entire table + -- so it will only trigger a write once a player enters + -- his/her name in. + row = positions[1]; + for i=0,row["size"]-1 do + r = r + row["mem"]:read_u8( row["addr"] + i ); + end + return r; + end + + + function init () + if not scores_have_been_read then + if check_mem( positions ) then + default_checksum = check_scores( positions ); + if read_scores( positions ) then + print( "scores read", "OK" ); + else + -- likely there simply isn't a .hi file around yet + print( "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; + function tick () + -- set up scores if they have been + init(); + -- only allow save check to run when + if mem_check_passed 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(); + -- print( "SAVE SCORES EVENT!", last_write_time ); + end + end + end + end + + function reset() + -- the notifier will still be attached even if the running game has no hiscore.dat entry + if mem_check_passed and (emu.romname() == current_game) then + local checksum = check_scores(positions) + if checksum ~= current_checksum and checksum ~= default_checksum then + write_scores(positions) + end + end + current_game = nil + mem_check_passed = false + end + + emu.register_start(function() + print("Starting " .. emu.gamename()) + -- check if we've just soft reset + if reset then + reset() + end + current_game = emu.romname() + dat = read_hiscore_dat() + if dat then + print( "found hiscore.dat entry for " .. emu.romname() ); + positions = parse_table( dat ); + if not positions then + print("hiscore.dat parse error"); + return; + end + emu.sethook( tick, "frame" ); + emu.register_stop(function() + reset() + end) + end + + end) + +end + +return exports |
