mirror of
https://github.com/holub/mame
synced 2025-04-16 05:24:54 +03:00
-plugins.data: Reworked the code substantialy.
* Use the plugin data folder for storing the cache. The history folder may be read-only or shared with different configurations. * Don't create the cache database or surrounding folder if there's nothing to store in it. * Actually use prepared queries multiple times rather than always destroying them after a single use. * Added proper error checking for most database operations. * Improved query performance by avoiding outer joins and table scans. -bus/nubus: Made the Macintosh Display Cards map the blue channel to white with monochrome monitors. Also added logging for PLL configuration to help debug how CRTC and RAMDAC clocks work in the future.
This commit is contained in:
parent
2f453da00c
commit
923ef2c25d
@ -1,13 +1,16 @@
|
||||
local dat = {}
|
||||
|
||||
local info, ver
|
||||
local datread = require("data/load_dat")
|
||||
local datread = require('data/load_dat')
|
||||
do
|
||||
local buttonchar
|
||||
local function convert(str)
|
||||
if not buttonchar then buttonchar = require("data/button_char") end
|
||||
if not buttonchar then
|
||||
buttonchar = require("data/button_char")
|
||||
end
|
||||
return buttonchar(str)
|
||||
end
|
||||
datread, ver = datread.open("command.dat", "#[^V]*Ver[^.:]*[.:]", convert)
|
||||
datread, ver = datread.open('command.dat', '#[^V]*Ver[^.:]*[.:]', convert)
|
||||
end
|
||||
|
||||
function dat.check(set, softlist)
|
||||
@ -15,11 +18,11 @@ function dat.check(set, softlist)
|
||||
return nil
|
||||
end
|
||||
local status
|
||||
status, info = pcall(datread, "cmd", "info", set)
|
||||
status, info = pcall(datread, 'cmd', 'info', set)
|
||||
if not status or not info then
|
||||
return nil
|
||||
end
|
||||
return _p("plugin-data", "Command")
|
||||
return _p('plugin-data', 'Command')
|
||||
end
|
||||
|
||||
function dat.get()
|
||||
|
@ -1,19 +1,19 @@
|
||||
local dat = {}
|
||||
local ver, info
|
||||
|
||||
local datread = require("data/load_dat")
|
||||
datread, ver = datread.open("gameinit.dat", "# .-GAMEINIT.DAT")
|
||||
local ver, info
|
||||
local datread = require('data/load_dat')
|
||||
datread, ver = datread.open('gameinit.dat', '# .-GAMEINIT.DAT')
|
||||
|
||||
function dat.check(set, softlist)
|
||||
if softlist or not datread then
|
||||
return nil
|
||||
end
|
||||
local status
|
||||
status, info = pcall(datread, "mame", "info", set)
|
||||
status, info = pcall(datread, 'mame', 'info', set)
|
||||
if not status or not info then
|
||||
return nil
|
||||
end
|
||||
return _p("plugin-data", "Gameinit")
|
||||
return _p('plugin-data', 'Gameinit')
|
||||
end
|
||||
|
||||
function dat.get()
|
||||
|
@ -1,149 +1,178 @@
|
||||
local dat = {}
|
||||
local db = require("data/database")
|
||||
|
||||
local db = require('data/database')
|
||||
local ver, info
|
||||
local file = "history.xml"
|
||||
local file = 'history.xml'
|
||||
local tablename
|
||||
|
||||
local function init()
|
||||
local filepath
|
||||
local dbver
|
||||
local fh
|
||||
|
||||
for path in mame_manager.ui.options.entries.historypath:value():gmatch("([^;]+)") do
|
||||
filepath = emu.subst_env(path) .. "/" .. file
|
||||
fh = io.open(filepath, "r")
|
||||
if fh then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- check for old history table
|
||||
local stmt = db.prepare("SELECT version FROM version WHERE datfile = ?")
|
||||
stmt:bind_values("history.dat")
|
||||
if stmt:step() == db.ROW then
|
||||
stmt:finalize()
|
||||
db.exec("DROP TABLE \"history.dat\"")
|
||||
db.exec("DROP TABLE \"history.dat_idx\"")
|
||||
stmt = db.prepare("DELETE FROM version WHERE datfile = ?")
|
||||
stmt:bind_values("history.dat")
|
||||
stmt:step()
|
||||
if db.get_version('history.dat') then
|
||||
db.exec([[DROP TABLE "history.dat";]])
|
||||
db.exec([[DROP TABLE "history.dat_idx";]])
|
||||
db.set_version('history.dat', nil)
|
||||
end
|
||||
stmt:finalize()
|
||||
|
||||
|
||||
local stmt = db.prepare("SELECT version FROM version WHERE datfile = ?")
|
||||
db.check("reading history version")
|
||||
stmt:bind_values(file)
|
||||
if stmt:step() == db.ROW then
|
||||
dbver = stmt:get_value(0)
|
||||
end
|
||||
stmt:finalize()
|
||||
|
||||
if not fh and dbver then
|
||||
-- data in database but missing file, just use what we have
|
||||
ver = dbver
|
||||
local fh, filepath, dbver
|
||||
fh, filepath, tablename, dbver = db.open_data_file(file)
|
||||
if not fh then
|
||||
if dbver then
|
||||
-- data in database but missing file, just use what we have
|
||||
ver = dbver
|
||||
end
|
||||
return
|
||||
elseif not fh then
|
||||
return
|
||||
elseif not dbver then
|
||||
db.exec("CREATE TABLE \"" .. file .. [[_idx" (
|
||||
name VARCHAR NOT NULL,
|
||||
list VARCHAR,
|
||||
data INTEGER NOT NULL)]])
|
||||
db.check("creating index")
|
||||
db.exec("CREATE TABLE \"" .. file .. "\" (data CLOB NOT NULL)")
|
||||
db.check("creating table")
|
||||
db.exec("CREATE INDEX \"name_" .. file .. "\" ON \"" .. file .. "_idx\"(name)")
|
||||
db.check("creating system index")
|
||||
end
|
||||
|
||||
-- scan file for version
|
||||
for line in fh:lines() do
|
||||
local match = line:match("<history([^>]*)>")
|
||||
local match = line:match('<history([^>]*)>')
|
||||
if match then
|
||||
match = match:match("version=\"([^\"]*)\"")
|
||||
match = match:match('version="([^"]*)"')
|
||||
if match then
|
||||
ver = match
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not ver then
|
||||
if (not ver) or (ver == dbver) then
|
||||
fh:close()
|
||||
ver = dbver
|
||||
return
|
||||
end
|
||||
|
||||
if ver == dbver then
|
||||
if not dbver then
|
||||
db.exec(
|
||||
string.format(
|
||||
[[CREATE TABLE "%s_idx" (
|
||||
name VARCHAR NOT NULL,
|
||||
list VARCHAR NOT NULL,
|
||||
data INTEGER NOT NULL);]],
|
||||
tablename))
|
||||
db.check(string.format('creating %s index table', file))
|
||||
db.exec(string.format([[CREATE TABLE "%s" (data CLOB NOT NULL);]], tablename))
|
||||
db.check(string.format('creating %s data table', file))
|
||||
db.exec(
|
||||
string.format(
|
||||
[[CREATE INDEX "namelist_%s" ON "%s_idx" (name, list);]],
|
||||
tablename, tablename))
|
||||
db.check(string.format('creating %s name/list index', file))
|
||||
end
|
||||
|
||||
local slaxml = require('xml')
|
||||
|
||||
db.exec([[BEGIN TRANSACTION;]])
|
||||
if not db.check(string.format('starting %s transaction', file)) then
|
||||
fh:close()
|
||||
ver = dbver
|
||||
return
|
||||
end
|
||||
|
||||
-- clean out previous data and update the version
|
||||
if dbver then
|
||||
db.exec("DELETE FROM \"" .. file .. "\"")
|
||||
db.check("deleting history")
|
||||
db.exec("DELETE FROM \"" .. file .. "_idx\"")
|
||||
db.check("deleting index")
|
||||
stmt = db.prepare("UPDATE version SET version = ? WHERE datfile = ?")
|
||||
db.check("updating history version")
|
||||
else
|
||||
stmt = db.prepare("INSERT INTO version VALUES (?, ?)")
|
||||
db.check("inserting history version")
|
||||
db.exec(string.format([[DELETE FROM "%s";]], tablename))
|
||||
if not db.check(string.format('deleting previous %s data', file)) then
|
||||
db.exec([[ROLLBACK TRANSACTION;]])
|
||||
fh:close()
|
||||
ver = dbver
|
||||
return
|
||||
end
|
||||
db.exec(string.format([[DELETE FROM "%s_idx";]], tablename))
|
||||
if not db.check(string.format('deleting previous %s data', file)) then
|
||||
db.exec([[ROLLBACK TRANSACTION;]])
|
||||
fh:close()
|
||||
ver = dbver
|
||||
return
|
||||
end
|
||||
end
|
||||
db.set_version(file, ver)
|
||||
if not db.check(string.format('updating %s version', file)) then
|
||||
db.exec([[ROLLBACK TRANSACTION;]])
|
||||
fh:close()
|
||||
ver = dbver
|
||||
return
|
||||
end
|
||||
stmt:bind_values(ver, file)
|
||||
stmt:step()
|
||||
stmt:finalize()
|
||||
|
||||
fh:seek("set")
|
||||
local buffer = fh:read("a")
|
||||
db.exec("BEGIN TRANSACTION")
|
||||
db.check("beginning history transation")
|
||||
fh:seek('set')
|
||||
local buffer = fh:read('a')
|
||||
|
||||
local lasttag
|
||||
local entry = {}
|
||||
local rowid
|
||||
local slaxml = require("xml")
|
||||
|
||||
local dataquery = db.prepare(
|
||||
string.format([[INSERT INTO "%s" (data) VALUES (?);]], tablename))
|
||||
local indexquery = db.prepare(
|
||||
string.format([[INSERT INTO "%s_idx" (name, list, data) VALUES (?, ?, ?);]], tablename))
|
||||
|
||||
local parser = slaxml:parser{
|
||||
startElement = function(name)
|
||||
lasttag = name
|
||||
if name == "entry" then
|
||||
if name == 'entry' then
|
||||
entry = {}
|
||||
rowid = nil
|
||||
elseif (name == "system") or (name == "item") then
|
||||
entry[#entry + 1] = {}
|
||||
elseif (name == 'system') or (name == 'item') then
|
||||
table.insert(entry, {})
|
||||
end
|
||||
end,
|
||||
attribute = function(name, value)
|
||||
if (name == "name") or (name == "list") then
|
||||
if (name == 'name') or (name == 'list') then
|
||||
entry[#entry][name] = value
|
||||
end
|
||||
end,
|
||||
text = function(text, cdata)
|
||||
if lasttag == "text" then
|
||||
text = text:gsub("\r", "") -- strip crs
|
||||
stmt = db.prepare("INSERT INTO \"" .. file .. "\" VALUES (?)")
|
||||
db.check("inserting values")
|
||||
stmt:bind_values(text)
|
||||
stmt:step()
|
||||
rowid = stmt:last_insert_rowid()
|
||||
stmt:finalize()
|
||||
if lasttag == 'text' then
|
||||
text = text:gsub('\r', '') -- strip carriage returns
|
||||
dataquery:bind_values(text)
|
||||
while true do
|
||||
local status = dataquery:step()
|
||||
if status == db.DONE then
|
||||
rowid = dataquery:last_insert_rowid();
|
||||
break
|
||||
elseif result == db.BUSY then
|
||||
emu.print_error(string.format('Database busy: inserting %s data', file))
|
||||
-- FIXME: how to abort parse and roll back?
|
||||
break
|
||||
elseif result ~= db.ROW then
|
||||
db.check(string.format('inserting %s data', file))
|
||||
break
|
||||
end
|
||||
end
|
||||
dataquery:reset()
|
||||
end
|
||||
end,
|
||||
closeElement = function(name)
|
||||
if name == "entry" then
|
||||
if (name == 'entry') and rowid then
|
||||
for num, entry in pairs(entry) do
|
||||
stmt = db.prepare("INSERT INTO \"" .. file .. "_idx\" VALUES (?, ?, ?)")
|
||||
db.check("inserting into index")
|
||||
stmt:bind_values(entry.name, entry.list, rowid)
|
||||
stmt:step()
|
||||
stmt:finalize()
|
||||
indexquery:bind_values(entry.name, entry.list or '', rowid)
|
||||
while true do
|
||||
local status = indexquery:step()
|
||||
if status == db.DONE then
|
||||
break
|
||||
elseif status == db.BUSY then
|
||||
emu.print_error(string.format('Database busy: inserting %s data', file))
|
||||
-- FIXME: how to abort parse and roll back?
|
||||
break
|
||||
elseif result ~= db.ROW then
|
||||
db.check(string.format('inserting %s data', file))
|
||||
break
|
||||
end
|
||||
end
|
||||
indexquery:reset()
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
parser:parse(buffer, {stripWhitespace=true})
|
||||
|
||||
parser:parse(buffer, { stripWhitespace = true })
|
||||
dataquery:finalize()
|
||||
indexquery:finalize()
|
||||
fh:close()
|
||||
db.exec("END TRANSACTION")
|
||||
db.check("ending history transation")
|
||||
|
||||
db.exec([[COMMIT TRANSACTION;]])
|
||||
if not db.check(string.format('committing %s transaction', file)) then
|
||||
db.exec([[ROLLBACK TRANSACTION;]])
|
||||
ver = dbver
|
||||
end
|
||||
end
|
||||
|
||||
if db then
|
||||
@ -151,25 +180,31 @@ if db then
|
||||
end
|
||||
|
||||
function dat.check(set, softlist)
|
||||
if not ver or not db then
|
||||
if not ver then
|
||||
return nil
|
||||
end
|
||||
|
||||
info = nil
|
||||
local stmt
|
||||
if softlist then
|
||||
stmt = db.prepare("SELECT f.data FROM \"" .. file .. "_idx\" AS fi, \"" .. file .. [["
|
||||
AS f WHERE fi.name = ? AND fi.list = ? AND f.rowid = fi.data]])
|
||||
stmt:bind_values(set, softlist)
|
||||
else
|
||||
stmt = db.prepare("SELECT f.data FROM \"" .. file .. "_idx\" AS fi, \"" .. file .. [["
|
||||
AS f WHERE fi.name = ? AND fi.list ISNULL AND f.rowid = fi.data]])
|
||||
stmt:bind_values(set)
|
||||
local query = db.prepare(
|
||||
string.format(
|
||||
[[SELECT f.data
|
||||
FROM "%s_idx" AS fi LEFT JOIN "%s" AS f ON fi.data = f.rowid
|
||||
WHERE fi.name = ? AND fi.list = ?;]],
|
||||
tablename, tablename))
|
||||
query:bind_values(set, softlist or '')
|
||||
while not info do
|
||||
local status = query:step()
|
||||
if status == db.ROW then
|
||||
info = query:get_value(0)
|
||||
elseif status == db.DONE then
|
||||
break
|
||||
elseif status ~= db.BUSY then
|
||||
db.check(string.format('reading %s data', file))
|
||||
break
|
||||
end
|
||||
end
|
||||
if stmt:step() == db.ROW then
|
||||
info = stmt:get_value(0)
|
||||
end
|
||||
stmt:finalize()
|
||||
return info and _p("plugin-data", "History") or nil
|
||||
query:finalize()
|
||||
return info and _p('plugin-data', 'History') or nil
|
||||
end
|
||||
|
||||
function dat.get()
|
||||
|
@ -1,23 +1,27 @@
|
||||
local dat = {}
|
||||
local ver, info
|
||||
local datread = require("data/load_dat")
|
||||
datread, ver = datread.open("mameinfo.dat", "# MAMEINFO.DAT", function(str) return str:gsub("\n\n", "\n") end)
|
||||
|
||||
local info, ver
|
||||
local datread = require('data/load_dat')
|
||||
datread, ver = datread.open(
|
||||
'mameinfo.dat',
|
||||
'# MAMEINFO.DAT',
|
||||
function (str) return str:gsub('\n\n', '\n') end)
|
||||
|
||||
function dat.check(set, softlist)
|
||||
if softlist or not datread then
|
||||
return nil
|
||||
end
|
||||
local status, drvinfo
|
||||
status, info = pcall(datread, "mame", "info", set)
|
||||
status, info = pcall(datread, 'mame', 'info', set)
|
||||
if not status or not info then
|
||||
return nil
|
||||
end
|
||||
local sourcefile = emu.driver_find(set).source_file:match("[^/\\]*$")
|
||||
status, drvinfo = pcall(datread, "drv", "info", sourcefile)
|
||||
local sourcefile = emu.driver_find(set).source_file:match('[^/\\]*$')
|
||||
status, drvinfo = pcall(datread, 'drv', 'info', sourcefile)
|
||||
if drvinfo then
|
||||
info = info .. _p("plugin-data", "\n\n--- DRIVER INFO ---\nDriver: ") .. sourcefile .. "\n\n" .. drvinfo
|
||||
info = info .. _p('plugin-data', '\n\n--- DRIVER INFO ---\nDriver: ') .. sourcefile .. '\n\n' .. drvinfo
|
||||
end
|
||||
return _p("plugin-data", "MAMEinfo")
|
||||
return _p('plugin-data', 'MAMEinfo')
|
||||
end
|
||||
|
||||
function dat.get()
|
||||
|
@ -1,120 +1,144 @@
|
||||
-- get marp high score file from http://replay.marpirc.net/txt/scores3.htm
|
||||
local dat = {}
|
||||
|
||||
local db = require("data/database")
|
||||
local ver, info
|
||||
|
||||
local function init()
|
||||
local filepath
|
||||
local dbver
|
||||
local fh
|
||||
local file = "scores3.htm"
|
||||
local file = 'scores3.htm'
|
||||
|
||||
for path in mame_manager.ui.options.entries.historypath:value():gmatch("([^;]+)") do
|
||||
filepath = emu.subst_env(path) .. "/" .. file
|
||||
fh = io.open(filepath, "r")
|
||||
if fh then
|
||||
break
|
||||
local fh, filepath, tablename, dbver = db.open_data_file(file)
|
||||
if not fh then
|
||||
if dbver then
|
||||
-- data in database but missing file, just use what we have
|
||||
ver = dbver
|
||||
end
|
||||
end
|
||||
|
||||
local stmt = db.prepare("SELECT version FROM version WHERE datfile = ?")
|
||||
db.check("reading marp version")
|
||||
stmt:bind_values(file)
|
||||
if stmt:step() == db.ROW then
|
||||
dbver = stmt:get_value(0)
|
||||
end
|
||||
stmt:finalize()
|
||||
|
||||
if not fh and dbver then
|
||||
-- data in database but missing file, just use what we have
|
||||
ver = dbver
|
||||
return
|
||||
elseif not fh then
|
||||
return
|
||||
elseif not dbver then
|
||||
db.exec("CREATE TABLE \"" .. file .. [[" (
|
||||
romset VARCHAR NOT NULL,
|
||||
data CLOB NOT NULL)]])
|
||||
db.check("creating marp table")
|
||||
end
|
||||
|
||||
-- scan file for version
|
||||
for line in fh:lines() do
|
||||
local match = line:match("Top Scores from the MAME Action Replay Page %(([%w :]+)%)")
|
||||
local match = line:match('Top Scores from the MAME Action Replay Page %(([%w :]+)%)')
|
||||
if match then
|
||||
ver = match
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not ver then
|
||||
if (not ver) or (ver == dbver) then
|
||||
fh:close()
|
||||
ver = dbver
|
||||
return
|
||||
end
|
||||
|
||||
if ver == dbver then
|
||||
if not dbver then
|
||||
db.exec(
|
||||
string.format(
|
||||
[[CREATE TABLE "%s" (
|
||||
romset VARCHAR NOT NULL,
|
||||
data CLOB NOT NULL,
|
||||
PRIMARY KEY(romset));]],
|
||||
tablename))
|
||||
db.check('creating MARP table')
|
||||
end
|
||||
|
||||
db.exec([[BEGIN TRANSACTION;]])
|
||||
if not db.check('starting MARP transaction') then
|
||||
fh:close()
|
||||
ver = dbver
|
||||
return
|
||||
end
|
||||
|
||||
-- clean out previous data and update the version
|
||||
if dbver then
|
||||
db.exec("DELETE FROM \"" .. file .. "\"")
|
||||
db.check("deleting marp")
|
||||
stmt = db.prepare("UPDATE version SET version = ? WHERE datfile = ?")
|
||||
db.check("updating marp version")
|
||||
else
|
||||
stmt = db.prepare("INSERT INTO version VALUES (?, ?)")
|
||||
db.check("inserting marp version")
|
||||
db.exec(string.format([[DELETE FROM "%s";]], tablename))
|
||||
if not db.check('deleting previous MARP data') then
|
||||
db.exec([[ROLLBACK TRANSACTION;]])
|
||||
fh:close()
|
||||
ver = dbver
|
||||
return
|
||||
end
|
||||
end
|
||||
if not db.set_version(file, ver) then
|
||||
db.check('updating MARP version')
|
||||
db.exec([[ROLLBACK TRANSACTION;]])
|
||||
fh:close()
|
||||
ver = dbver
|
||||
return
|
||||
end
|
||||
stmt:bind_values(ver, file)
|
||||
stmt:step()
|
||||
stmt:finalize()
|
||||
|
||||
fh:seek("set")
|
||||
local buffer = fh:read("a")
|
||||
db.exec("BEGIN TRANSACTION")
|
||||
db.check("beginning marp transation")
|
||||
fh:seek('set')
|
||||
local buffer = fh:read('a')
|
||||
|
||||
local function gmatchpos()
|
||||
local pos = 1
|
||||
local set, data = ""
|
||||
local set = ''
|
||||
local data = ''
|
||||
local function iter()
|
||||
local lastset = set
|
||||
while true do
|
||||
local spos, scr, plyr, stype, ltype
|
||||
local url = ""
|
||||
spos, pos, set, stype, scr, plyr, ltype = buffer:find("\n%s*([%w_]*)%-?(%w-) :%s*(%d+) [;:] ([^:]+): [^%[\n]*%[?([%w ]*)[^\n]*", pos)
|
||||
spos, pos, set, stype, scr, plyr, ltype = buffer:find('\n%s*([%w_]*)%-?(%w-) :%s*(%d+) [;:] ([^:]+): [^%[\n]*%[?([%w ]*)[^\n]*', pos)
|
||||
if not spos then
|
||||
return nil
|
||||
end
|
||||
if set ~= "" then
|
||||
if ltype ~= "" then
|
||||
url = ltype .. "\t\n"
|
||||
else
|
||||
url = ""
|
||||
|
||||
local url = ''
|
||||
if set ~= '' then
|
||||
if ltype ~= '' then
|
||||
url = ltype .. '\t\n'
|
||||
end
|
||||
url = url .. "http://replay.marpirc.net/r/" .. set .. ((stype ~= "") and ("-" .. stype) or "") .. "\t\n"
|
||||
url = url .. 'http://replay.marpirc.net/r/' .. set
|
||||
if stype ~= '' then
|
||||
url = url .. '-' .. stype
|
||||
end
|
||||
url = url .. '\t\n'
|
||||
end
|
||||
if set ~= "" and lastset and lastset ~= set then
|
||||
|
||||
if (set ~= '') and (lastset ~= set) then
|
||||
local lastdata = data
|
||||
data = url .. plyr .. "\t" .. scr .. "\n"
|
||||
data = url .. plyr .. '\t' .. scr .. '\n'
|
||||
return lastset, lastdata
|
||||
end
|
||||
data = data .. ((url ~= "") and ("\n" .. url) or "") .. plyr .. "\t" .. scr .. "\n"
|
||||
|
||||
if url ~= '' then
|
||||
data = data .. '\n' .. url
|
||||
end
|
||||
data = data .. plyr .. '\t' .. scr .. '\n'
|
||||
end
|
||||
end
|
||||
return iter
|
||||
end
|
||||
|
||||
local query = db.prepare(
|
||||
string.format([[INSERT INTO "%s" (romset, data) VALUES (?, ?)]], tablename))
|
||||
for set, data in gmatchpos() do
|
||||
stmt = db.prepare("INSERT INTO \"" .. file .. "\" VALUES (?, ?)")
|
||||
db.check("inserting marp values")
|
||||
stmt:bind_values(set, data)
|
||||
stmt:step()
|
||||
stmt:finalize()
|
||||
query:bind_values(set, data)
|
||||
while true do
|
||||
local status = query:step()
|
||||
if status == db.DONE then
|
||||
break
|
||||
elseif status == db.BUSY then
|
||||
emu.print_error('Database busy: inserting MARP data')
|
||||
query:finalize()
|
||||
db.exec([[ROLLBACK TRANSACTION;]])
|
||||
fh:close()
|
||||
ver = dbver
|
||||
return
|
||||
elseif result ~= db.ROW then
|
||||
db.check('inserting MARP data')
|
||||
break
|
||||
end
|
||||
end
|
||||
query:reset()
|
||||
end
|
||||
query:finalize()
|
||||
|
||||
fh:close()
|
||||
db.exec("END TRANSACTION")
|
||||
db.check("ending marp transation")
|
||||
db.exec([[COMMIT TRANSACTION;]])
|
||||
if not db.check('committing MARP transaction') then
|
||||
db.exec([[ROLLBACK TRANSACTION;]])
|
||||
ver = dbver
|
||||
end
|
||||
end
|
||||
|
||||
if db then
|
||||
@ -122,18 +146,26 @@ if db then
|
||||
end
|
||||
|
||||
function dat.check(set, softlist)
|
||||
if softlist or not ver or not db then
|
||||
if softlist or (not ver) then
|
||||
return nil
|
||||
end
|
||||
|
||||
info = nil
|
||||
local stmt = db.prepare("SELECT data FROM \"scores3.htm\" AS f WHERE romset = ?")
|
||||
db.check("reading marp data")
|
||||
stmt:bind_values(set)
|
||||
if stmt:step() == db.ROW then
|
||||
info = "#j2\n" .. stmt:get_value(0)
|
||||
local query = db.prepare([[SELECT data FROM "scores3.htm" WHERE romset = ?;]])
|
||||
query:bind_values(set)
|
||||
while not info do
|
||||
local status = query:step()
|
||||
if status == db.ROW then
|
||||
info = '#j2\n' .. query:get_value(0)
|
||||
elseif status == db.DONE then
|
||||
break
|
||||
elseif status ~= db.BUSY then
|
||||
db.check('reading MARP data')
|
||||
break
|
||||
end
|
||||
end
|
||||
stmt:finalize()
|
||||
return info and _p("plugin-data", "MARPScore") or nil
|
||||
query:finalize()
|
||||
return info and _p('plugin-data', 'MARPScore') or nil
|
||||
end
|
||||
|
||||
function dat.get()
|
||||
|
@ -1,24 +1,27 @@
|
||||
local dat = {}
|
||||
local ver, info
|
||||
|
||||
local datread = require("data/load_dat")
|
||||
datread, ver = datread.open("messinfo.dat", "# MESSINFO.DAT", function(str) return str:gsub("\n\n", "\n") end)
|
||||
local ver, info
|
||||
local datread = require('data/load_dat')
|
||||
datread, ver = datread.open(
|
||||
'messinfo.dat',
|
||||
'# MESSINFO.DAT',
|
||||
function (str) return str:gsub('\n\n', '\n') end)
|
||||
|
||||
function dat.check(set, softlist)
|
||||
if softlist or not datread then
|
||||
return nil
|
||||
end
|
||||
local status, drvinfo
|
||||
status, info = pcall(datread, "mame", "info", set)
|
||||
status, info = pcall(datread, 'mame', 'info', set)
|
||||
if not status or not info then
|
||||
return nil
|
||||
end
|
||||
local sourcefile = emu.driver_find(set).source_file:match("[^/\\]*$")
|
||||
status, drvinfo = pcall(datread, "drv", "info", sourcefile)
|
||||
local sourcefile = emu.driver_find(set).source_file:match('[^/\\]*$')
|
||||
status, drvinfo = pcall(datread, 'drv', 'info', sourcefile)
|
||||
if drvinfo then
|
||||
info = info .. _p("plugin-data", "\n\n--- DRIVER INFO ---\nDriver: ") .. sourcefile .. "\n\n" .. drvinfo
|
||||
info = info .. _p('plugin-data', '\n\n--- DRIVER INFO ---\nDriver: ') .. sourcefile .. '\n\n' .. drvinfo
|
||||
end
|
||||
return _p("plugin-data", "MESSinfo")
|
||||
return _p('plugin-data', 'MESSinfo')
|
||||
end
|
||||
|
||||
function dat.get()
|
||||
|
@ -1,25 +1,26 @@
|
||||
local dat = {}
|
||||
|
||||
local ver, info
|
||||
local datread = require("data/load_dat")
|
||||
datread, ver = datread.open("story.dat", "# version")
|
||||
local datread = require('data/load_dat')
|
||||
datread, ver = datread.open('story.dat', '# version')
|
||||
|
||||
function dat.check(set, softlist)
|
||||
if softlist or not datread then
|
||||
return nil
|
||||
end
|
||||
local status, data = pcall(datread, "story", "info", set)
|
||||
local status, data = pcall(datread, 'story', 'info', set)
|
||||
if not status or not data then
|
||||
return nil
|
||||
end
|
||||
local lines = {}
|
||||
data = data:gsub("MAMESCORE records : ([^\n]+)", "MAMESCORE records :\t\n%1", 1)
|
||||
for line in data:gmatch("[^\n]*") do
|
||||
if not (#lines == 0 and line == "") and not (lines[#lines] == "" and line == "") then
|
||||
lines[#lines + 1] = line:gsub("^(.-)_+([0-9.]+)$", "%1\t%2")
|
||||
data = data:gsub('MAMESCORE records : ([^\n]+)', 'MAMESCORE records :\t\n%1', 1)
|
||||
for line in data:gmatch('[^\n]*') do
|
||||
if (line ~= '') or ((#lines ~= 0) and (lines[#lines] ~= '')) then
|
||||
table.insert(lines, line:gsub('^(.-)_+([0-9.]+)$', '%1\t%2'))
|
||||
end
|
||||
end
|
||||
info = "#j2\n" .. table.concat(lines, "\n")
|
||||
return _p("plugin-data", "Mamescore")
|
||||
info = '#j2\n' .. table.concat(lines, '\n')
|
||||
return _p('plugin-data', 'Mamescore')
|
||||
end
|
||||
|
||||
function dat.get()
|
||||
|
@ -1,18 +1,19 @@
|
||||
local dat = {}
|
||||
|
||||
local ver, info
|
||||
local datread = require("data/load_dat")
|
||||
datread, ver = datread.open("sysinfo.dat", "# This file was generated on")
|
||||
local datread = require('data/load_dat')
|
||||
datread, ver = datread.open('sysinfo.dat', '# This file was generated on')
|
||||
|
||||
function dat.check(set, softlist)
|
||||
if softlist or not datread then
|
||||
return nil
|
||||
end
|
||||
local status
|
||||
status, info = pcall(datread, "bio", "info", set)
|
||||
status, info = pcall(datread, 'bio', 'info', set)
|
||||
if not status or not info then
|
||||
return nil
|
||||
end
|
||||
return _p("plugin-data", "Sysinfo")
|
||||
return _p('plugin-data', 'Sysinfo')
|
||||
end
|
||||
|
||||
function dat.get()
|
||||
|
@ -1,41 +1,159 @@
|
||||
local sql = require("lsqlite3")
|
||||
local datfile = {}
|
||||
local sql = require('lsqlite3')
|
||||
local db
|
||||
|
||||
local function check_db(msg)
|
||||
local function check_db_result(msg)
|
||||
if db:errcode() > sql.OK then
|
||||
emu.print_error(string.format("Error: %s (%s - %s)", msg, db:errcode(), db:errmsg()))
|
||||
emu.print_error(string.format('Error: %s (%s - %s)', msg, db:errcode(), db:errmsg()))
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
do
|
||||
local dbpath = emu.subst_env(mame_manager.ui.options.entries.historypath:value():match("([^;]+)"))
|
||||
db = sql.open(dbpath .. "/history.db")
|
||||
if not db then
|
||||
lfs.mkdir(dbpath)
|
||||
db = sql.open(dbpath .. "/history.db")
|
||||
if not db then
|
||||
emu.print_error("Unable to create history.db")
|
||||
return false
|
||||
end
|
||||
check_db("opening database")
|
||||
end
|
||||
local function settings_path()
|
||||
return emu.subst_env(manager.machine.options.entries.homepath:value():match('([^;]+)')) .. '/data'
|
||||
end
|
||||
|
||||
if db then
|
||||
local function check_version_table()
|
||||
local found = false
|
||||
db:exec("select * from sqlite_master where name = 'version'", function() found = true return 0 end)
|
||||
check_db("checking for 'version' table")
|
||||
db:exec(
|
||||
[[SELECT COUNT(*) FROM sqlite_master WHERE name = 'version' AND type = 'table';]],
|
||||
function (udata, cols, values, names)
|
||||
found = tonumber(values[1]) > 0
|
||||
return 0
|
||||
end)
|
||||
if check_db_result('checking for "version" table') and (not found) then
|
||||
db:exec(
|
||||
[[CREATE TABLE version (
|
||||
datfile VARCHAR NOT NULL,
|
||||
version VARCHAR NOT NULL,
|
||||
PRIMARY KEY (datfile));]])
|
||||
found = check_db_result('creating "version" table')
|
||||
end
|
||||
if not found then
|
||||
db:exec([[
|
||||
CREATE TABLE version (
|
||||
version VARCHAR NOT NULL,
|
||||
datfile VARCHAR UNIQUE NOT NULL)]])
|
||||
check_db("creating 'version' table")
|
||||
db:close()
|
||||
db = nil
|
||||
end
|
||||
end
|
||||
|
||||
local dbtable = { prepare = function(...) return db:prepare(...) end,
|
||||
exec = function(...) return db:exec(...) end, ROW = sql.ROW, check = check_db }
|
||||
local function open_existing()
|
||||
db = sql.open(settings_path() .. '/history.db', sql.OPEN_READWRITE)
|
||||
if db then
|
||||
check_version_table()
|
||||
end
|
||||
return db
|
||||
end
|
||||
|
||||
return db and dbtable or false
|
||||
local function open_create()
|
||||
-- first try to create settings directory
|
||||
local dir = settings_path()
|
||||
local attr = lfs.attributes(dir)
|
||||
if (not attr) and (not lfs.mkdir(dir)) then
|
||||
emu.print_error(string.format('Error creating data plugin settings directory "%s"', dir))
|
||||
elseif attr and (attr.mode ~= 'directory') then
|
||||
emu.print_error(string.format('Error opening data plugin database: "%s" is not a directory', dir))
|
||||
else
|
||||
-- now try to open the database
|
||||
local dbpath = dir .. '/history.db'
|
||||
db = sql.open(dbpath, sql.OPEN_READWRITE | sql.OPEN_CREATE)
|
||||
if not db then
|
||||
emu.print_error(string.format('Error opening data plugin database "%s"', dbpath))
|
||||
else
|
||||
check_version_table()
|
||||
end
|
||||
end
|
||||
return db
|
||||
end
|
||||
|
||||
|
||||
local dbtable = {
|
||||
ROW = sql.ROW,
|
||||
BUSY = sql.BUSY,
|
||||
DONE = sql.DONE,
|
||||
check = check_db_result }
|
||||
|
||||
function dbtable.sanitize_name(name)
|
||||
return name:gsub('[^%w%."]', '_')
|
||||
end
|
||||
|
||||
function dbtable.get_version(filename)
|
||||
-- don't try to create the database here, just return nil if it doesn't exist
|
||||
if (not db) and (not open_existing()) then
|
||||
return nil
|
||||
end
|
||||
local query = db:prepare([[SELECT version FROM version WHERE datfile = ?;]])
|
||||
query:bind_values(filename)
|
||||
local result
|
||||
while result == nil do
|
||||
local status = query:step()
|
||||
if status == sql.ROW then
|
||||
result = query:get_value(0)
|
||||
elseif status ~= sql.BUSY then
|
||||
break
|
||||
end
|
||||
end
|
||||
query:finalize()
|
||||
return result
|
||||
end
|
||||
|
||||
function dbtable.set_version(filename, version)
|
||||
if (not db) and (not open_create()) then
|
||||
return nil
|
||||
end
|
||||
local query
|
||||
if version ~= nil then
|
||||
query = db:prepare(
|
||||
[[INSERT INTO version (datfile, version) VALUES (?1, ?2)
|
||||
ON CONFLICT(datfile) DO UPDATE set version = ?2;]])
|
||||
query:bind_values(filename, version)
|
||||
else
|
||||
query = db:prepare([[DELETE FROM version WHERE datfile = ?1;]])
|
||||
query:bind_values(filename)
|
||||
end
|
||||
local result
|
||||
while result == nil do
|
||||
local status = query:step()
|
||||
if status == sqlite3.DONE then
|
||||
result = true
|
||||
elseif result ~= sqlite3.ROW then
|
||||
result = false
|
||||
end
|
||||
end
|
||||
query:finalize()
|
||||
return result
|
||||
end
|
||||
|
||||
function dbtable.prepare(...)
|
||||
if (not db) and open_create() then
|
||||
dbtable.prepare = function (...) return db:prepare(...) end
|
||||
end
|
||||
if db then
|
||||
return db:prepare(...)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function dbtable.exec(...)
|
||||
if (not db) and open_create() then
|
||||
dbtable.exec = function (...) return db:exec(...) end
|
||||
end
|
||||
if db then
|
||||
return db:exec(...)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function dbtable.open_data_file(file)
|
||||
local fh, filepath
|
||||
for path in mame_manager.ui.options.entries.historypath:value():gmatch('([^;]+)') do
|
||||
filepath = emu.subst_env(path) .. '/' .. file
|
||||
fh = io.open(filepath, 'r')
|
||||
if fh then
|
||||
break
|
||||
end
|
||||
end
|
||||
return fh, filepath, dbtable.sanitize_name(file), dbtable.get_version(file)
|
||||
end
|
||||
|
||||
return dbtable
|
||||
|
@ -4,12 +4,12 @@
|
||||
-- heading if it supports the set otherwise nil and get which returns the data
|
||||
-- the script should be named data_<name>.lua
|
||||
-- this is set default on in the plugin.json
|
||||
local exports = {}
|
||||
exports.name = "data"
|
||||
exports.version = "0.0.1"
|
||||
exports.description = "Data plugin"
|
||||
exports.license = "BSD-3-Clause"
|
||||
exports.author = { name = "Carl" }
|
||||
local exports = {
|
||||
name = 'data',
|
||||
version = '0.0.2',
|
||||
description = 'Data plugin',
|
||||
license = 'BSD-3-Clause',
|
||||
author = { name = 'Carl' } }
|
||||
|
||||
local data = exports
|
||||
|
||||
@ -26,7 +26,7 @@ function data.startplugin()
|
||||
local cur_list
|
||||
|
||||
emu.register_start(
|
||||
function()
|
||||
function ()
|
||||
data_scr = {}
|
||||
for file in lfs.dir(plugindir) do
|
||||
local name = string.match(file, '^(data_.*).lua$')
|
||||
@ -40,7 +40,7 @@ function data.startplugin()
|
||||
end)
|
||||
|
||||
emu.register_callback(
|
||||
function(set)
|
||||
function (set)
|
||||
local ret = {}
|
||||
if set == '' then
|
||||
set = emu.romname()
|
||||
@ -70,13 +70,13 @@ function data.startplugin()
|
||||
'data_list')
|
||||
|
||||
emu.register_callback(
|
||||
function(num)
|
||||
function (num)
|
||||
return valid_lst[num + 1].get()
|
||||
end,
|
||||
'data')
|
||||
|
||||
emu.register_callback(
|
||||
function(num)
|
||||
function (num)
|
||||
local ver
|
||||
if valid_lst[num + 1].ver then
|
||||
ver = valid_lst[num + 1].ver()
|
||||
|
@ -1,17 +1,29 @@
|
||||
local datfile = {}
|
||||
local db = require("data/database")
|
||||
|
||||
local function readret(file)
|
||||
local function read(tag1, tag2, set)
|
||||
local db = require('data/database')
|
||||
|
||||
local function readret(file, tablename)
|
||||
local query = db.prepare(
|
||||
string.format(
|
||||
[[SELECT f.data
|
||||
FROM "%s_idx" AS fi LEFT JOIN "%s" AS f ON fi.data = f.rowid
|
||||
WHERE fi.type = ? AND fi.val = ? AND fi.romset = ?;]],
|
||||
tablename, tablename))
|
||||
local function read(tag, val, set)
|
||||
query:bind_values(tag, val, set)
|
||||
local data
|
||||
local stmt = db.prepare("SELECT f.data FROM \"" .. file .. "_idx\" AS fi, \"" .. file .. [["
|
||||
AS f WHERE fi.type = ? AND fi.val = ? AND fi.romset = ? AND f.rowid = fi.data]])
|
||||
db.check("reading " .. tag1 .. " - " .. tag2 .. " - " .. set)
|
||||
stmt:bind_values(tag1, tag2, set)
|
||||
if stmt:step() == db.ROW then
|
||||
data = stmt:get_value(0)
|
||||
while not data do
|
||||
local status = query:step()
|
||||
if status == db.ROW then
|
||||
data = query:get_value(0)
|
||||
elseif status == db.DONE then
|
||||
break
|
||||
elseif status ~= db.BUSY then
|
||||
db.check(string.format('reading %s data', file))
|
||||
break
|
||||
end
|
||||
end
|
||||
stmt:finalize()
|
||||
query:reset()
|
||||
return data
|
||||
end
|
||||
return read
|
||||
@ -22,49 +34,22 @@ function datfile.open(file, vertag, fixupcb)
|
||||
if not db then
|
||||
return nil
|
||||
end
|
||||
local ver, dbver
|
||||
local filepath
|
||||
local fh
|
||||
|
||||
for path in mame_manager.ui.options.entries.historypath:value():gmatch("([^;]+)") do
|
||||
filepath = emu.subst_env(path) .. "/" .. file
|
||||
fh = io.open(filepath, "r")
|
||||
if fh then
|
||||
break
|
||||
local fh, filepath, tablename, dbver = db.open_data_file(file)
|
||||
if not fh then
|
||||
if dbver then
|
||||
-- data in database but missing file, just use what we have
|
||||
return readret(file, tablename), dbver
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
-- remove unsafe chars from file for use in sql statement
|
||||
file = file:gsub("[^%w%._]", "")
|
||||
|
||||
local stmt = db.prepare("SELECT version FROM version WHERE datfile = ?")
|
||||
db.check("reading version")
|
||||
stmt:bind_values(file)
|
||||
if stmt:step() == db.ROW then
|
||||
dbver = stmt:get_value(0)
|
||||
end
|
||||
stmt:finalize()
|
||||
|
||||
if not fh and dbver then
|
||||
-- data in database but missing file, just use what we have
|
||||
return readret(file), dbver
|
||||
elseif not fh then
|
||||
return nil
|
||||
elseif not dbver then
|
||||
db.exec("CREATE TABLE \"" .. file .. [[_idx" (
|
||||
type VARCHAR NOT NULL,
|
||||
val VARCHAR NOT NULL,
|
||||
romset VARCHAR NOT NULL,
|
||||
data INTEGER NOT NULL)]])
|
||||
db.check("creating index")
|
||||
db.exec("CREATE TABLE \"" .. file .. "\" (data CLOB NOT NULL)")
|
||||
db.check("creating table")
|
||||
db.exec("CREATE INDEX \"typeval_" .. file .. "\" ON \"" .. file .. "_idx\"(type, val)")
|
||||
db.check("creating typeval index")
|
||||
end
|
||||
|
||||
local ver
|
||||
if vertag then
|
||||
-- scan file for version
|
||||
for line in fh:lines() do
|
||||
local match = line:match(vertag .. "%s*(%S+)")
|
||||
local match = line:match(vertag .. '%s*(%S+)')
|
||||
if match then
|
||||
ver = match
|
||||
break
|
||||
@ -72,109 +57,204 @@ function datfile.open(file, vertag, fixupcb)
|
||||
end
|
||||
end
|
||||
if not ver then
|
||||
-- use file ctime for version
|
||||
ver = tostring(lfs.attributes(filepath, "change"))
|
||||
-- fall back to file modification time for version
|
||||
ver = tostring(lfs.attributes(filepath, 'change'))
|
||||
end
|
||||
if ver == dbver then
|
||||
fh:close()
|
||||
return readret(file), dbver
|
||||
return readret(file, tablename), dbver
|
||||
end
|
||||
|
||||
if dbver then
|
||||
db.exec("DELETE FROM \"" .. file .. "\"")
|
||||
db.check("deleting")
|
||||
db.exec("DELETE FROM \"" .. file .. "_idx\"")
|
||||
db.check("deleting index")
|
||||
stmt = db.prepare("UPDATE version SET version = ? WHERE datfile = ?")
|
||||
db.check("updating version")
|
||||
else
|
||||
stmt = db.prepare("INSERT INTO version VALUES (?, ?)")
|
||||
db.check("inserting version")
|
||||
if not dbver then
|
||||
db.exec(
|
||||
string.format(
|
||||
[[CREATE TABLE "%s_idx" (
|
||||
type VARCHAR NOT NULL,
|
||||
val VARCHAR NOT NULL,
|
||||
romset VARCHAR NOT NULL,
|
||||
data INTEGER NOT NULL);]],
|
||||
tablename))
|
||||
db.check(string.format('creating %s index table', file))
|
||||
db.exec(string.format([[CREATE TABLE "%s" (data CLOB NOT NULL);]], tablename))
|
||||
db.check(string.format('creating %s data table', file))
|
||||
db.exec(
|
||||
string.format(
|
||||
[[CREATE INDEX "typeval_%s" ON "%s_idx" (type, val, romset);]],
|
||||
tablename, tablename))
|
||||
db.check(string.format('creating %s type/value index', file))
|
||||
end
|
||||
stmt:bind_values(ver, file)
|
||||
stmt:step()
|
||||
stmt:finalize()
|
||||
|
||||
do
|
||||
fh:seek("set")
|
||||
local buffer = fh:read("a")
|
||||
db.exec("BEGIN TRANSACTION")
|
||||
db.check("beginning transaction")
|
||||
local function gmatchpos()
|
||||
local pos = 1
|
||||
local function iter()
|
||||
local tags, data
|
||||
while not data do
|
||||
local npos
|
||||
local spos, epos = buffer:find("[\n\r]$[^=\n\r]*=[^\n\r]*", pos)
|
||||
if not spos then
|
||||
return nil
|
||||
end
|
||||
npos, epos = buffer:find("[\n\r]$%w+%s*[\n\r]+", epos)
|
||||
if not npos then
|
||||
return nil
|
||||
end
|
||||
tags = buffer:sub(spos, epos)
|
||||
spos, npos = buffer:find("[\n\r]$[^=\n\r]*=[^\n\r]*", epos)
|
||||
if not spos then
|
||||
return nil
|
||||
end
|
||||
data = buffer:sub(epos, spos)
|
||||
pos = spos
|
||||
end
|
||||
return tags, data
|
||||
end
|
||||
return iter
|
||||
db.exec([[BEGIN TRANSACTION;]])
|
||||
if not db.check(string.format('starting %s transaction', file)) then
|
||||
fh:close()
|
||||
if dbver then
|
||||
return readret(file, tablename), dbver
|
||||
else
|
||||
return nil
|
||||
end
|
||||
for info, data in gmatchpos() do
|
||||
local tags = {}
|
||||
local infotype
|
||||
info = info:gsub(utf8.char(0xfeff), "") --remove boms
|
||||
data = data:gsub(utf8.char(0xfeff), "")
|
||||
for s in info:gmatch("[\n\r]$([^\n\r]*)") do
|
||||
if s:find("=", 1, true) then
|
||||
local m1, m2 = s:match("([^=]*)=(.*)")
|
||||
for tag in m1:gmatch("[^,]+") do
|
||||
for set in m2:gmatch("[^,]+") do
|
||||
tags[#tags + 1] = { tag = tag, set = set }
|
||||
end
|
||||
end
|
||||
|
||||
-- clean out previous data and update the version
|
||||
if dbver then
|
||||
db.exec(string.format([[DELETE FROM "%s";]], tablename))
|
||||
if not db.check(string.format('deleting previous %s data', file)) then
|
||||
db.exec([[ROLLBACK TRANSACTION;]])
|
||||
fh:close()
|
||||
return readret(file, tablename), dbver
|
||||
end
|
||||
db.exec(string.format([[DELETE FROM "%s_idx";]], tablename))
|
||||
if not db.check(string.format('deleting previous %s data', file)) then
|
||||
db.exec([[ROLLBACK TRANSACTION;]])
|
||||
fh:close()
|
||||
return readret(file, tablename), dbver
|
||||
end
|
||||
end
|
||||
db.set_version(file, ver)
|
||||
if not db.check(string.format('updating %s version', file)) then
|
||||
db.exec([[ROLLBACK TRANSACTION;]])
|
||||
fh:close()
|
||||
if dbver then
|
||||
return readret(file, tablename), dbver
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local dataquery = db.prepare(
|
||||
string.format([[INSERT INTO "%s" (data) VALUES (?);]], tablename))
|
||||
local indexquery = db.prepare(
|
||||
string.format(
|
||||
[[INSERT INTO "%s_idx" (type, val, romset, data) VALUES (?, ?, ?, ?)]],
|
||||
tablename))
|
||||
|
||||
fh:seek('set')
|
||||
local buffer = fh:read('a')
|
||||
|
||||
local function gmatchpos()
|
||||
local pos = 1
|
||||
local function iter()
|
||||
local tags, data
|
||||
while not data do
|
||||
local npos
|
||||
local spos, epos = buffer:find('[\n\r]$[^=\n\r]*=[^\n\r]*', pos)
|
||||
if not spos then
|
||||
return nil
|
||||
end
|
||||
npos, epos = buffer:find('[\n\r]$%w+%s*[\n\r]+', epos)
|
||||
if not npos then
|
||||
return nil
|
||||
end
|
||||
tags = buffer:sub(spos, epos)
|
||||
spos, npos = buffer:find('[\n\r]$[^=\n\r]*=[^\n\r]*', epos)
|
||||
if not spos then
|
||||
return nil
|
||||
end
|
||||
data = buffer:sub(epos, spos)
|
||||
pos = spos
|
||||
end
|
||||
return tags, data
|
||||
end
|
||||
return iter
|
||||
end
|
||||
|
||||
for info, data in gmatchpos() do
|
||||
local tags = {}
|
||||
local infotype
|
||||
info = info:gsub(utf8.char(0xfeff), '') -- remove byte order marks
|
||||
data = data:gsub(utf8.char(0xfeff), '')
|
||||
for s in info:gmatch('[\n\r]$([^\n\r]*)') do
|
||||
if s:find('=', 1, true) then
|
||||
local m1, m2 = s:match('([^=]*)=(.*)')
|
||||
for tag in m1:gmatch('[^,]+') do
|
||||
for set in m2:gmatch('[^,]+') do
|
||||
table.insert(tags, { tag = tag, set = set })
|
||||
end
|
||||
else
|
||||
infotype = s
|
||||
end
|
||||
else
|
||||
infotype = s
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
data = data:gsub('[\n\r]$end%s*[\n\r]$%w+%s*[\n\r]', '\n')
|
||||
data = data:gsub('[\n\r]$end%s*[\n\r].-[\n\r]$%w+%s*[\n\r]', '\n')
|
||||
data = data:gsub('[\n\r]$end%s*[\n\r].*', '')
|
||||
|
||||
if (#tags > 0) and infotype then
|
||||
data = data:gsub('\r', '') -- strip carriage returns
|
||||
if fixupcb then
|
||||
data = fixupcb(data)
|
||||
end
|
||||
|
||||
dataquery:bind_values(data)
|
||||
local row
|
||||
while true do
|
||||
local status = dataquery:step()
|
||||
if status == db.DONE then
|
||||
row = dataquery:last_insert_rowid();
|
||||
break
|
||||
elseif status == db.BUSY then
|
||||
emu.print_error(string.format('Database busy: inserting %s data', file))
|
||||
dataquery:finalize()
|
||||
indexquery:finalize()
|
||||
db.exec([[ROLLBACK TRANSACTION;]])
|
||||
fh:close()
|
||||
if dbver then
|
||||
return readret(file, tablename), dbver
|
||||
else
|
||||
return nil
|
||||
end
|
||||
elseif result ~= db.ROW then
|
||||
db.check(string.format('inserting %s data', file))
|
||||
break
|
||||
end
|
||||
end
|
||||
dataquery:reset()
|
||||
|
||||
data = data:gsub("[\n\r]$end%s*[\n\r]$%w+%s*[\n\r]", "\n")
|
||||
data = data:gsub("[\n\r]$end%s*[\n\r].-[\n\r]$%w+%s*[\n\r]", "\n")
|
||||
data = data:gsub("[\n\r]$end%s*[\n\r].*", "")
|
||||
|
||||
if #tags > 0 and infotype then
|
||||
data = data:gsub("\r", "") -- strip crs
|
||||
if fixupcb then
|
||||
data = fixupcb(data)
|
||||
end
|
||||
stmt = db.prepare("INSERT INTO \"" .. file .. "\" VALUES (?)")
|
||||
db.check("inserting values")
|
||||
stmt:bind_values(data)
|
||||
stmt:step()
|
||||
local row = stmt:last_insert_rowid()
|
||||
stmt:finalize()
|
||||
if row then
|
||||
for num, tag in pairs(tags) do
|
||||
stmt = db.prepare("INSERT INTO \"" .. file .. "_idx\" VALUES (?, ?, ?, ?)")
|
||||
db.check("inserting into index")
|
||||
stmt:bind_values(infotype, tag.tag, tag.set, row)
|
||||
stmt:step()
|
||||
stmt:finalize()
|
||||
indexquery:bind_values(infotype, tag.tag, tag.set, row)
|
||||
while true do
|
||||
local status = indexquery:step()
|
||||
if status == db.DONE then
|
||||
break
|
||||
elseif status == db.BUSY then
|
||||
emu.print_error(string.format('Database busy: inserting %s data', file))
|
||||
dataquery:finalize()
|
||||
indexquery:finalize()
|
||||
db.exec([[ROLLBACK TRANSACTION;]])
|
||||
fh:close()
|
||||
if dbver then
|
||||
return readret(file, tablename), dbver
|
||||
else
|
||||
return nil
|
||||
end
|
||||
elseif result ~= db.ROW then
|
||||
db.check(string.format('inserting %s data', file))
|
||||
break
|
||||
end
|
||||
end
|
||||
indexquery:reset()
|
||||
end
|
||||
end
|
||||
end
|
||||
db.exec("END TRANSACTION")
|
||||
db.check("ending transaction")
|
||||
end
|
||||
fh:close()
|
||||
|
||||
return readret(file), ver
|
||||
dataquery:finalize()
|
||||
indexquery:finalize()
|
||||
|
||||
fh:close()
|
||||
db.exec([[COMMIT TRANSACTION;]])
|
||||
if not db.check(string.format('committing %s transaction', file)) then
|
||||
db.exec([[ROLLBACK TRANSACTION;]])
|
||||
if dbver then
|
||||
return readret(file, tablename), dbver
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
return readret(file, tablename), ver
|
||||
end
|
||||
|
||||
return datfile
|
||||
|
@ -8,10 +8,8 @@
|
||||
|
||||
TODO:
|
||||
* Work out why some monitors need magic multiply or divide by two to
|
||||
get the right RAMDAC clock - inferring it from the reference clock
|
||||
modulus is definitely wrong.
|
||||
* When a monochrome monitor is connected, it sends the intensity to the
|
||||
blue channel - maybe detect this and map blue channel to white?
|
||||
get the right RAMDAC/CRTC clocks - inferring it from the reference
|
||||
clock modulus is definitely wrong.
|
||||
* The 8•24 card uses a strange off-white palette with some monitors,
|
||||
including the 21" and 16" RGB displays.
|
||||
* Interlaced modes.
|
||||
@ -38,7 +36,7 @@
|
||||
|
||||
static INPUT_PORTS_START( 48gc )
|
||||
PORT_START("MONITOR")
|
||||
PORT_CONFNAME(0x07, 0x06, u8"Attached monitor")
|
||||
PORT_CONFNAME(0x0f, 0x06, u8"Attached monitor")
|
||||
PORT_CONFSETTING( 0x00, u8"Macintosh Two-Page Monitor (1152\u00d7870)")
|
||||
PORT_CONFSETTING( 0x01, u8"Macintosh Portrait Display (B&W 15\" 640\u00d7870)")
|
||||
PORT_CONFSETTING( 0x02, u8"Macintosh RGB Display (12\" 512\u00d7384)")
|
||||
@ -51,14 +49,14 @@ INPUT_PORTS_END
|
||||
|
||||
static INPUT_PORTS_START( 824gc )
|
||||
PORT_START("MONITOR")
|
||||
PORT_CONFNAME(0x07, 0x06, u8"Attached monitor")
|
||||
PORT_CONFNAME(0x0f, 0x06, u8"Attached monitor")
|
||||
PORT_CONFSETTING( 0x00, u8"Mac 21\" Color Display (1152\u00d7870)")
|
||||
PORT_CONFSETTING( 0x01, u8"Mac Portrait Display (B&W 15\" 640\u00d7870)")
|
||||
PORT_CONFSETTING( 0x02, u8"Mac RGB Display (12\" 512\u00d7384)")
|
||||
PORT_CONFSETTING( 0x03, u8"Mac Two-Page Display (B&W 21\" 1152\u00d7870)")
|
||||
//PORT_CONFSETTING( 0x04, u8"NTSC Monitor") requires implementing interlace modes
|
||||
PORT_CONFSETTING( 0x05, u8"Mac 16\" Color Display (832\u00d7624)")
|
||||
PORT_CONFSETTING( 0x06, u8"Mac Hi-Res Display (12-14\" 640\u00d7480)")
|
||||
PORT_CONFSETTING( 0x0d, u8"Mac 16\" Color Display (832\u00d7624)")
|
||||
INPUT_PORTS_END
|
||||
|
||||
|
||||
@ -80,6 +78,27 @@ DEFINE_DEVICE_TYPE(NUBUS_48GC, nubus_48gc_device, "nb_48gc", "Apple Macintosh
|
||||
DEFINE_DEVICE_TYPE(NUBUS_824GC, nubus_824gc_device, "nb_824gc", "Apple Macintosh Display Card 8*24")
|
||||
|
||||
|
||||
// TODO: find a better place for this table to live
|
||||
struct mac_monitor_info { bool mono; unsigned sense[4]; };
|
||||
static mac_monitor_info const f_monitors[] = {
|
||||
{ false, { 0, 0, 0, 0 } }, // 0: RGB 21"
|
||||
{ true, { 1, 1, 1, 0 } }, // 1: Full-Page (B&W 15")
|
||||
{ false, { 2, 2, 0, 2 } }, // 2: RGB 12"
|
||||
{ true, { 3, 3, 1, 2 } }, // 3: Two-Page (B&W 21")
|
||||
{ false, { 4, 0, 4, 4 } }, // 4: NTSC Monitor
|
||||
{ false, { 5, 1, 5, 4 } }, // 5: RGB 15"
|
||||
{ false, { 6, 2, 4, 6 } }, // 6: Hi-Res (12-14")
|
||||
{ false, { 6, 0, 0, 6 } }, // 7: Multiple Scan 14"
|
||||
{ false, { 6, 0, 4, 6 } }, // 8: Multiple Scan 16"
|
||||
{ false, { 6, 2, 0, 6 } }, // 9: Multiple Scan 21"
|
||||
{ false, { 7, 0, 0, 0 } }, // 10: PAL Encoder
|
||||
{ false, { 7, 1, 1, 0 } }, // 11: NTSC Encoder
|
||||
{ false, { 7, 1, 1, 6 } }, // 12: VGA/Super VGA
|
||||
{ false, { 7, 2, 5, 2 } }, // 13: RGB 16"
|
||||
{ false, { 7, 3, 0, 0 } }, // 14: PAL Monitor
|
||||
{ false, { 7, 3, 4, 4 } } }; // 15: RGB 19"
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// device_add_mconfig - add device configuration
|
||||
//-------------------------------------------------
|
||||
@ -149,7 +168,7 @@ jmfb_device::jmfb_device(const machine_config &mconfig, device_type type, const
|
||||
m_monitor(*this, "MONITOR"),
|
||||
m_vram_view(*this, "vram"),
|
||||
m_timer(nullptr),
|
||||
m_vbl_disable(0), m_toggle(0), m_stride(0), m_base(0),
|
||||
m_vbl_disable(0), m_toggle(0),
|
||||
m_count(0), m_clutoffs(0), m_mode(0)
|
||||
{
|
||||
set_screen(*this, GC48_SCREEN_NAME);
|
||||
@ -191,13 +210,18 @@ void jmfb_device::device_start()
|
||||
|
||||
m_timer = timer_alloc(FUNC(jmfb_device::vbl_tick), this);
|
||||
|
||||
m_monitor_type = 0;
|
||||
m_mode = 0;
|
||||
|
||||
save_item(NAME(m_monitor_type));
|
||||
save_item(NAME(m_vram));
|
||||
save_item(NAME(m_vbl_disable));
|
||||
save_item(NAME(m_toggle));
|
||||
save_item(NAME(m_stride));
|
||||
save_item(NAME(m_base));
|
||||
save_item(NAME(m_registers));
|
||||
save_item(NAME(m_sense));
|
||||
save_item(NAME(m_preload));
|
||||
save_item(NAME(m_base));
|
||||
save_item(NAME(m_stride));
|
||||
save_item(NAME(m_colors));
|
||||
save_item(NAME(m_count));
|
||||
save_item(NAME(m_clutoffs));
|
||||
@ -243,12 +267,19 @@ void jmfb_device::device_reset()
|
||||
{
|
||||
m_vram_view.select(0);
|
||||
|
||||
m_monitor_type = m_monitor->read();
|
||||
if (m_monitor_type > std::size(f_monitors))
|
||||
{
|
||||
throw emu_fatalerror("%s: Invalid monitor selection %x\n", m_monitor_type);
|
||||
}
|
||||
|
||||
std::fill(m_vram.begin(), m_vram.end(), 0);
|
||||
m_vbl_disable = 1;
|
||||
m_toggle = 0;
|
||||
m_stride = 80 / 4;
|
||||
m_base = 0;
|
||||
m_sense = 0;
|
||||
m_preload = 256 - 8;
|
||||
m_base = 0;
|
||||
m_stride = 80 / 4;
|
||||
|
||||
m_clutoffs = 0;
|
||||
m_count = 0;
|
||||
@ -287,6 +318,11 @@ TIMER_CALLBACK_MEMBER(jmfb_device::vbl_tick)
|
||||
|
||||
uint32_t jmfb_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
|
||||
{
|
||||
auto const trans =
|
||||
[mono = f_monitors[m_monitor_type].mono] (rgb_t color)
|
||||
{
|
||||
return !mono ? color : rgb_t(color.b(), color.b(), color.b());
|
||||
};
|
||||
auto const screenbase = util::big_endian_cast<uint8_t const>(&m_vram[0]) + (m_base << 5);
|
||||
int const xres = screen.visible_area().right();
|
||||
|
||||
@ -301,14 +337,14 @@ uint32_t jmfb_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap,
|
||||
{
|
||||
uint8_t const pixels = rowbase[x];
|
||||
|
||||
*scanline++ = pen_color(BIT(pixels, 7));
|
||||
*scanline++ = pen_color(BIT(pixels, 6));
|
||||
*scanline++ = pen_color(BIT(pixels, 5));
|
||||
*scanline++ = pen_color(BIT(pixels, 4));
|
||||
*scanline++ = pen_color(BIT(pixels, 3));
|
||||
*scanline++ = pen_color(BIT(pixels, 2));
|
||||
*scanline++ = pen_color(BIT(pixels, 1));
|
||||
*scanline++ = pen_color(BIT(pixels, 0));
|
||||
*scanline++ = trans(pen_color(BIT(pixels, 7)));
|
||||
*scanline++ = trans(pen_color(BIT(pixels, 6)));
|
||||
*scanline++ = trans(pen_color(BIT(pixels, 5)));
|
||||
*scanline++ = trans(pen_color(BIT(pixels, 4)));
|
||||
*scanline++ = trans(pen_color(BIT(pixels, 3)));
|
||||
*scanline++ = trans(pen_color(BIT(pixels, 2)));
|
||||
*scanline++ = trans(pen_color(BIT(pixels, 1)));
|
||||
*scanline++ = trans(pen_color(BIT(pixels, 0)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -322,10 +358,10 @@ uint32_t jmfb_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap,
|
||||
{
|
||||
uint8_t const pixels = rowbase[x];
|
||||
|
||||
*scanline++ = pen_color(BIT(pixels, 6, 2));
|
||||
*scanline++ = pen_color(BIT(pixels, 4, 2));
|
||||
*scanline++ = pen_color(BIT(pixels, 2, 2));
|
||||
*scanline++ = pen_color(BIT(pixels, 0, 2));
|
||||
*scanline++ = trans(pen_color(BIT(pixels, 6, 2)));
|
||||
*scanline++ = trans(pen_color(BIT(pixels, 4, 2)));
|
||||
*scanline++ = trans(pen_color(BIT(pixels, 2, 2)));
|
||||
*scanline++ = trans(pen_color(BIT(pixels, 0, 2)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -339,8 +375,8 @@ uint32_t jmfb_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap,
|
||||
{
|
||||
uint8_t const pixels = rowbase[x];
|
||||
|
||||
*scanline++ = pen_color(BIT(pixels, 4, 4));
|
||||
*scanline++ = pen_color(BIT(pixels, 0, 4));
|
||||
*scanline++ = trans(pen_color(BIT(pixels, 4, 4)));
|
||||
*scanline++ = trans(pen_color(BIT(pixels, 0, 4)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -352,7 +388,7 @@ uint32_t jmfb_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap,
|
||||
uint32_t *scanline = &bitmap.pix(y);
|
||||
for (int x = 0; x <= xres; x++)
|
||||
{
|
||||
*scanline++ = pen_color(rowbase[x]);
|
||||
*scanline++ = trans(pen_color(rowbase[x]));
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -364,7 +400,10 @@ uint32_t jmfb_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap,
|
||||
uint32_t *scanline = &bitmap.pix(y);
|
||||
for (int x = 0; x <= xres; x++)
|
||||
{
|
||||
*scanline++ = rgb_t(source[0], source[1], source[2]);
|
||||
if (!f_monitors[m_monitor_type].mono)
|
||||
*scanline++ = rgb_t(source[0], source[1], source[2]);
|
||||
else
|
||||
*scanline++ = rgb_t(source[2], source[2], source[2]);
|
||||
source += 3;
|
||||
}
|
||||
}
|
||||
@ -378,8 +417,20 @@ void jmfb_device::update_crtc()
|
||||
{
|
||||
int const vtotal = (m_vactive + m_vbporch + m_vsync + m_vfporch) / 2;
|
||||
int const height = m_vactive / 2;
|
||||
if (vtotal && height && m_multiplier)
|
||||
if (vtotal && height && m_multiplier && m_modulus)
|
||||
{
|
||||
int const divider = 256 - m_preload;
|
||||
XTAL const refclk = 20_MHz_XTAL / m_modulus;
|
||||
XTAL const vcoout = refclk * m_multiplier;
|
||||
XTAL const pixclk = vcoout / (1 << m_pdiv);
|
||||
XTAL const dacclk = pixclk / divider;
|
||||
LOG("reference clock %d VCO output %d pixel clock %d RAMDAC clock %d\n",
|
||||
refclk.value(), vcoout.value(), pixclk.value(), dacclk.value());
|
||||
|
||||
int htotal = m_hactive + m_hbporch + m_hsync + m_hfporch + 8;
|
||||
int width = m_hactive + 2;
|
||||
LOG("horizontal total %d active %d\n", htotal, width);
|
||||
|
||||
// FIXME: where does this multiply/divide by 2 come from?
|
||||
// This is obviously not correct by any definition.
|
||||
int scale = 0;
|
||||
@ -398,9 +449,8 @@ void jmfb_device::update_crtc()
|
||||
default:
|
||||
throw emu_fatalerror("%s: Unknown clock modulus %d\n", tag(), m_modulus);
|
||||
}
|
||||
int const divider = 256 - unsigned(m_preload);
|
||||
int htotal = ((m_hactive + m_hbporch + m_hsync + m_hfporch + 8) << (m_pdiv + scale)) / divider;
|
||||
int width = ((m_hactive + 2) << (m_pdiv + scale)) / divider;
|
||||
htotal = ((m_hactive + m_hbporch + m_hsync + m_hfporch + 8) << (m_pdiv + scale)) / divider;
|
||||
width = ((m_hactive + 2) << (m_pdiv + scale)) / divider;
|
||||
switch (m_mode)
|
||||
{
|
||||
case 0: // 1bpp:
|
||||
@ -440,7 +490,16 @@ uint32_t jmfb_device::jmfb_r(offs_t offset, uint32_t mem_mask)
|
||||
switch (offset)
|
||||
{
|
||||
case 0x000/4:
|
||||
return m_monitor->read() << 9;
|
||||
{
|
||||
uint32_t result = f_monitors[m_monitor_type].sense[0];
|
||||
if (BIT(m_sense, 2))
|
||||
result &= f_monitors[m_monitor_type].sense[1];
|
||||
if (BIT(m_sense, 1))
|
||||
result &= f_monitors[m_monitor_type].sense[2];
|
||||
if (BIT(m_sense, 0))
|
||||
result &= f_monitors[m_monitor_type].sense[3];
|
||||
return result << 9;
|
||||
}
|
||||
|
||||
case 0x1c0/4:
|
||||
m_toggle ^= 0xffffffff;
|
||||
@ -462,23 +521,24 @@ void jmfb_device::jmfb_w(offs_t offset, uint32_t data, uint32_t mem_mask)
|
||||
{
|
||||
m_vram_view.select(BIT(data, 2)); // packed RGB mode
|
||||
}
|
||||
m_sense = (data >> 9) & 0x07;
|
||||
break;
|
||||
|
||||
case 0x004/4:
|
||||
LOG("%s: %02x to preload\n", machine().describe_context(), data);
|
||||
m_preload = data;
|
||||
m_preload = data & 0xff;
|
||||
update_crtc();
|
||||
break;
|
||||
|
||||
case 0x008/4: // base
|
||||
LOG("%s: %x to base\n", machine().describe_context(), data);
|
||||
m_base = data;
|
||||
m_base = data & 0xffff;
|
||||
break;
|
||||
|
||||
case 0x00c/4: // stride
|
||||
LOG("%s: %x to stride\n", machine().describe_context(), data);
|
||||
// this value is in DWORDs for 1-8 bpp and, uhh, strange for 24bpp
|
||||
m_stride = data;
|
||||
m_stride = data & 0xffff;
|
||||
break;
|
||||
|
||||
case 0x10c/4: // active pixel cells - 2
|
||||
|
@ -53,10 +53,14 @@ private:
|
||||
memory_view m_vram_view;
|
||||
emu_timer *m_timer;
|
||||
|
||||
uint8_t m_monitor_type;
|
||||
|
||||
std::vector<uint32_t> m_vram;
|
||||
uint32_t m_vbl_disable, m_toggle, m_stride, m_base;
|
||||
uint32_t m_vbl_disable, m_toggle;
|
||||
uint32_t m_registers[0x100];
|
||||
uint8_t m_preload;
|
||||
uint8_t m_sense;
|
||||
uint16_t m_preload;
|
||||
uint32_t m_base, m_stride;
|
||||
|
||||
uint8_t m_colors[3], m_count, m_clutoffs, m_mode;
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
06 = ?
|
||||
07 = command - 002 = pattern fill, 100/101 = copy forward/backward
|
||||
08 = pattern offset - X in bits 0-2, Y in bits 3-6
|
||||
08 = pattern Y offset * 8
|
||||
09 = VRAM destination * 4
|
||||
0a = VRAM source * 4
|
||||
0b = height (inclusive)
|
||||
|
Loading…
Reference in New Issue
Block a user