-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:
Vas Crabb 2022-06-25 04:57:24 +10:00
parent 2f453da00c
commit 923ef2c25d
14 changed files with 767 additions and 426 deletions

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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 824 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

View File

@ -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;

View File

@ -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)