diff options
author | 2019-09-30 14:38:26 +1000 | |
---|---|---|
committer | 2019-09-30 14:38:26 +1000 | |
commit | 24544c9dfdf496cbfc36056cd95970e0f16a7542 (patch) | |
tree | 2baa36f1d0b1564e6dbaf8901f36bf42a2f2416b /scripts/minimaws | |
parent | 2f99560b087a7200761ffc344fb5134efff4f5fd (diff) |
minimaws: add web interface for identifying ROM dumps
Diffstat (limited to 'scripts/minimaws')
-rw-r--r-- | scripts/minimaws/lib/assets/romident.js | 485 | ||||
-rw-r--r-- | scripts/minimaws/lib/assets/style.css | 2 | ||||
-rw-r--r-- | scripts/minimaws/lib/htmltmpl.py | 29 | ||||
-rw-r--r-- | scripts/minimaws/lib/wsgiserve.py | 101 | ||||
-rwxr-xr-x | scripts/minimaws/minimaws.py | 5 |
5 files changed, 617 insertions, 5 deletions
diff --git a/scripts/minimaws/lib/assets/romident.js b/scripts/minimaws/lib/assets/romident.js new file mode 100644 index 00000000000..0e77d62ca25 --- /dev/null +++ b/scripts/minimaws/lib/assets/romident.js @@ -0,0 +1,485 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb + +var matched_names = new Array(); +var unmatched_names = new Array(); +var dump_info = Object.create(null); +var machine_info = Object.create(null); +var crc_table = new Uint32Array(256); + +for (var i = 0; i < 256; i++) +{ + var crc = i; + for (var b = 0; b < 8; b++) + crc = (crc >>> 1) ^ ((crc & 1) ? 0xedb88320 : 0x00000000); + crc_table[i] = crc; +} + + +function get_chd_sha1(header) +{ + if (header.byteLength < 16) + return null; + if (String.fromCharCode.apply(null, new Uint8Array(header, 0, 8)) != 'MComprHD') + return null; + var view = new DataView(header); + var headerlen = view.getUint32(8, false); + var version = view.getUint32(12, false); + var sha1offs; + if (version == 3) + { + if (headerlen != 120) + return null; + sha1offs = 80; + } + else if (version == 4) + { + if (headerlen != 108) + return null; + sha1offs = 48; + } + else if (version == 5) + { + if (headerlen != 124) + return null; + sha1offs = 84; + } + else + { + return null; + } + if (header.byteLength < (sha1offs + 20)) + return null; + return Array.from(new Uint8Array(header, sha1offs, 20)).map(x => x.toString(16).padStart(2, '0')).join(''); +} + + +function get_sha1_group(sha1) +{ + if (Object.hasOwnProperty.call(dump_info, sha1)) + { + return dump_info[sha1]; + } + else + { + var result = new Object(); + result.crc = Object.create(null); + dump_info[sha1] = result; + return result; + } +} + + +function add_matches(table, names, matches) +{ + for (var i = 0; i < names.length; i++) + { + var row = table.appendChild(document.createElement('tr')); + var name = row.appendChild(document.createElement('th')); + name.textContent = names[i]; + if (matches !== null) + { + name.setAttribute('rowspan', matches.length); + row.appendChild(document.createElement('td')).textContent = matches[0].name; + var bad = row.appendChild(document.createElement('td')); + if (matches[0].bad) + bad.textContent = 'BAD'; + for (var j = 1; j < matches.length; j++) + { + row = table.appendChild(document.createElement('tr')); + row.appendChild(document.createElement('td')).textContent = matches[j].name; + bad = row.appendChild(document.createElement('td')); + if (matches[j].bad) + bad.textContent = 'BAD'; + } + } + } +} + + +function add_unmatched(names, crc, sha1) +{ + var table; + if (unmatched_names.length > 0) + { + table = document.getElementById('table-unmatched'); + } + else + { + var div = document.getElementById('div-machines'); + var heading = document.body.insertBefore(document.createElement('h2'), div); + heading.textContent = 'Unmatched'; + table = document.body.insertBefore(document.createElement('table'), div); + table.setAttribute('id', 'table-unmatched'); + } + var row = table.appendChild(document.createElement('tr')); + var content; + if (crc === null) + { + content = 'SHA1(' + sha1 + ')'; + } + else + { + if (crc < 0) + crc = 0xffffffff + crc + 1; + content = 'CRC(' + crc.toString(16).padStart(8, '0') + ') SHA1(' + sha1 + ')'; + } + for (var i = 0; i < names.length; i++) + { + row.appendChild(document.createElement('th')).textContent = names[i]; + row.appendChild(document.createElement('td')).textContent = content; + unmatched_names.push(names[i]); + } +} + + +function get_machine_table(shortname, description) +{ + if (Object.hasOwnProperty.call(machine_info, shortname)) + { + return machine_info[shortname]; + } + else + { + var div = document.getElementById('div-machines'); + var heading = div.appendChild(document.createElement('h2')); + var link = heading.appendChild(document.createElement('a')); + link.textContent = description; + link.setAttribute('href', appurl + 'machine/' + shortname); + var table = div.appendChild(document.createElement('table')); + machine_info[shortname] = table; + add_matches(table, matched_names, null); + return table; + } +} + + +function request_dumps(name, group, crc, sha1, url, progress) +{ + var req = new XMLHttpRequest(); + req.responseType = 'json'; + req.onload = + function () + { + if (req.status == 200) + { + var machines = Object.create(null); + if (Object.keys(req.response).length > 0) + { + Object.keys(machine_info).forEach( + function (shortname) + { + var table = machine_info[shortname]; + if (Object.hasOwnProperty.call(req.response, shortname)) + { + machines[shortname] = req.response[shortname].matches; + add_matches(table, group.names, req.response[shortname].matches); + delete req.response[shortname]; + } + else + { + add_matches(table, group.names, null); + } + }); + Object.keys(req.response).forEach( + function (shortname) + { + var info = req.response[shortname]; + var table = get_machine_table(shortname, info.description); + machines[shortname] = req.response[shortname].matches; + add_matches(table, group.names, info.matches); + }); + for (var i = 0; i < group.names.length; i++) + matched_names.push(group.names[i]); + } + else + { + add_unmatched(group.names, crc, sha1); + } + group.machines = machines; + progress.parentNode.removeChild(progress); + } + else + { + progress.textContent = 'Error identifying \u201C' + name + '\u201D: HTTP status code ' + req.status.toString(10); + if (crc !== null) + delete dump_info[sha1].crc[crc]; + else + delete dump_info[sha1].disk; + } + }; + req.onerror = + function () + { + progress.textContent = 'Error identifying \u201C' + name + '\u201D'; + if (crc !== null) + delete dump_info[sha1].crc[crc]; + else + delete dump_info[sha1].disk; + }; + req.onabort = + function () + { + progress.textContent = 'Error identifying \u201C' + name + '\u201D: server request aborted'; + if (crc !== null) + delete dump_info[sha1].crc[crc]; + else + delete dump_info[sha1].disk; + }; + req.open('GET', url); + req.send(); +} + + +function add_name(group, name, crc, sha1) +{ + if (group.names.indexOf(name) < 0) + { + group.names.push(name); + if (group.hasOwnProperty('machines')) + { + var machines = group.machines; + var names = [ name ]; + if (Object.keys(machines).length > 0) + { + Object.keys(machine_info).forEach( + function (shortname) + { + var table = machine_info[shortname]; + if (Object.hasOwnProperty.call(machines, shortname)) + add_matches(table, names, machines[shortname]); + else + add_matches(table, names, null); + }); + matched_names.push(name); + } + else + { + add_unmatched(names, crc, sha1); + } + } + } +} + + +function identify_file(file, trychd, progress) +{ + var digested = 0; + var crc = 0xffffffff; + var sha1st = new Uint32Array(5); + var sha1cnt = new Uint32Array(2); + var sha1buf = new DataView(new ArrayBuffer(64)); + + function rol(x, n) + { + return ((x << n) | (x >>> (32 - n))) & 0xffffffff; + } + + function sha1_process(data) + { + var d = new Uint32Array(sha1st); + + function b(i) + { + var r = data.getUint32(((i + 13) & 15) << 2, false); + r ^= data.getUint32(((i + 8) & 15) << 2, false); + r ^= data.getUint32(((i + 2) & 15) << 2, false); + r ^= data.getUint32((i & 15) << 2, false); + r = rol(r, 1); + data.setUint32((i & 15) << 2, r, false); + return r; + } + function r0(i) + { + d[i % 5] = 0xffffffff & (d[i % 5] + ((d[(i + 3) % 5] & (d[(i + 2) % 5] ^ d[(i + 1) % 5])) ^ d[(i + 1) % 5]) + data.getUint32(i << 2, false) + 0x5a827999 + rol(d[(i + 4) % 5], 5)); + d[(i + 3) % 5] = rol(d[(i + 3) % 5], 30); + } + function r1(i) + { + d[i % 5] = 0xffffffff & (d[i % 5] + ((d[(i + 3) % 5] & (d[(i + 2) % 5] ^ d[(i + 1) % 5])) ^ d[(i + 1) % 5])+ b(i) + 0x5a827999 + rol(d[(i + 4) % 5], 5)); + d[(i + 3) % 5] = rol(d[(i + 3) % 5], 30); + } + function r2(i) + { + d[i % 5] = 0xffffffff & (d[i % 5] + (d[(i + 3) % 5] ^ d[(i + 2) % 5] ^ d[(i + 1) % 5]) + b(i) + 0x6ed9eba1 + rol(d[(i + 4) % 5], 5)); + d[(i + 3) % 5] = rol(d[(i + 3) % 5], 30); + } + function r3(i) + { + d[i % 5] = 0xffffffff & (d[i % 5] + (((d[(i + 3) % 5] | d[(i + 2) % 5]) & d[(i + 1) % 5]) | (d[(i + 3) % 5] & d[(i + 2) % 5])) + b(i) + 0x8f1bbcdc + rol(d[(i + 4) % 5], 5)); + d[(i + 3) % 5] = rol(d[(i + 3) % 5], 30); + } + function r4(i) + { + d[i % 5] = 0xffffffff & (d[i % 5] + (d[(i + 3) % 5] ^ d[(i + 2) % 5] ^ d[(i + 1) % 5]) + b(i) + 0xca62c1d6 + rol(d[(i + 4) % 5], 5)); + d[(i + 3) % 5] = rol(d[(i + 3) % 5], 30); + } + + var i = 0; + while (i < 16) + r0(i++); + while (i < 20) + r1(i++); + while (i < 40) + r2(i++); + while (i < 60) + r3(i++); + while (i < 80) + r4(i++); + for (i = 0; i < sha1st.length; i++) + sha1st[i] = (sha1st[i] + d[i]) & 0xffffffff; + } + + function sha1_digest(data, view) + { + var residual = sha1cnt[0]; + sha1cnt[0] = (sha1cnt[0] + (view.length << 3)) & 0xffffffff; + if (residual > sha1cnt[0]) + sha1cnt[1]++; + sha1cnt[1] += (view.length >>> 29); + residual = (residual >>> 3) & 63; + var offset = 0; + if ((residual + view.length) >= 64) + { + if (residual > 0) + { + for (offset = 0; (offset + residual) < 64; offset++) + sha1buf.setUint8(offset + residual, view[offset]); + sha1_process(sha1buf); + residual = 0; + } + for ( ; (view.length - offset) >= 64; offset += 64) + sha1_process(new DataView(data, offset, 64)); + } + for ( ; offset < view.length; residual++, offset++) + sha1buf.setUint8(residual, view[offset]); + } + + function sha1_finalise() + { + var lenbuf = new ArrayBuffer(8); + var lenview = new DataView(lenbuf); + lenview.setUint32(0, sha1cnt[1], false); + lenview.setUint32(4, sha1cnt[0], false); + var padbuf = new ArrayBuffer(64 - (63 & ((sha1cnt[0] >>> 3) + 8))); + var padview = new Uint8Array(padbuf); + padview[0] = 0x80; + for (var i = 1; i < padview.length; i++) + padview[i] = 0x00; + sha1_digest(padbuf, padview); + sha1_digest(lenbuf, new Uint8Array(lenbuf)); + var result = new Array(20); + for (var i = 0; i < 20; i++) + result[i] = (sha1st[4 - (i >>> 2)] >> ((3 - (i & 3)) << 3)) & 0x000000ff; + return result.map(x => x.toString(16).padStart(2, '0')).join(''); + } + + function process_chunk(e) + { + if (e.target.error === null) + { + var data = e.target.result; + if ((digested == 0) && trychd) + { + var chdsha1 = get_chd_sha1(data); + if (chdsha1 !== null) + { + var sha1grp = get_sha1_group(chdsha1); + if (!sha1grp.hasOwnProperty('disk')) + { + var diskgrp = new Object(); + diskgrp.names = [ file.name ]; + sha1grp.disk = diskgrp; + request_dumps(file.name, diskgrp, null, chdsha1, appurl + 'rpc/diskdumps?sha1=' + chdsha1, progress); + } + else + { + add_name(sha1grp.disk, file.name, crc, sha1); + progress.parentNode.removeChild(progress); + } + return; + } + } + var view = new Uint8Array(data); + for (var i = 0; i < view.length; i++) + crc = (crc >>> 8) ^ crc_table[(crc & 0x000000ff) ^ view[i]]; + sha1_digest(data, view); + digested += data.byteLength; + if (digested < file.size) + { + read_block(); + } + else + { + crc ^= 0xffffffff; + var sha1 = sha1_finalise(); + var sha1grp = get_sha1_group(sha1); + if (!Object.hasOwnProperty.call(sha1grp.crc, crc)) + { + var crcgrp = new Object(); + crcgrp.names = [ file.name ]; + sha1grp.crc[crc] = crcgrp; + var crcstr = ((crc < 0) ? (0xffffffff + crc + 1) : crc).toString(16).padStart(8, '0'); + request_dumps(file.name, crcgrp, crc, sha1, appurl + 'rpc/romdumps?crc=' + crcstr + '&sha1=' + sha1, progress); + } + else + { + add_name(sha1grp.crc[crc], file.name, crc, sha1); + progress.parentNode.removeChild(progress); + } + } + } + else + { + progress.textContent = 'Error identifying \u201C' + file.name + '\u201D: ' + e.target.error; + } + } + + function read_block() + { + var remaining = file.size - digested; + var chunk = file.slice(digested, digested + Math.min(remaining, 65536)); + var reader = new FileReader(); + reader.onload = process_chunk; + reader.readAsArrayBuffer(chunk); + }; + + sha1st[0] = 0xc3d2e1f0; + sha1st[1] = 0x10325476; + sha1st[2] = 0x98badcfe; + sha1st[3] = 0xefcdab89; + sha1st[4] = 0x67452301; + sha1cnt[0] = 0; + sha1cnt[1] = 0; + progress.textContent = 'Identifying \u201C' + file.name + '\u201D\u2026'; + read_block(); +} + + +function add_dump_files(files) +{ + var prog = document.getElementById('div-progress'); + for (var i = 0; i < files.length; i++) + { + var name = files[i].name; + var dot = name.lastIndexOf('.'); + var trychd = (dot > 0) && (name.substring(dot + 1).toLowerCase() == 'chd'); + identify_file(files[i], trychd, prog.appendChild(document.createElement('p'))); + } +} + + +function div_dropzone_dragover(e) +{ + e.stopPropagation(); + e.preventDefault(); + e.dataTransfer.dropEffect = 'link'; +} + + +function div_dropzone_drop(e) +{ + e.stopPropagation(); + e.preventDefault(); + add_dump_files(e.dataTransfer.files); +} diff --git a/scripts/minimaws/lib/assets/style.css b/scripts/minimaws/lib/assets/style.css index 75ea0fa93de..1e6ebd57ec0 100644 --- a/scripts/minimaws/lib/assets/style.css +++ b/scripts/minimaws/lib/assets/style.css @@ -7,5 +7,7 @@ td { padding-left: 0.25em; padding-right: 0.25em } table[class=sysinfo] th { text-align: right } +div[class=dropzone] { padding: 0 1em; border-width: 2px; border-radius: 1em; border-style: dashed } + dl[id=list-slot-options] dt { font-weight: bold; margin-top: 1em } dl[id=list-slot-options] dd table { margin-top: 0.5em; margin-bottom: 1em } diff --git a/scripts/minimaws/lib/htmltmpl.py b/scripts/minimaws/lib/htmltmpl.py index 1a030d04e93..55034b0d079 100644 --- a/scripts/minimaws/lib/htmltmpl.py +++ b/scripts/minimaws/lib/htmltmpl.py @@ -163,3 +163,32 @@ SOURCEFILE_LIST_ROW = string.Template( ' <td>${sourcefile}</td>\n' \ ' <td style="text-align: right">${machines}</td>\n' \ ' </tr>\n') + +ROMIDENT_PAGE = string.Template( + '<!DOCTYPE html>\n' \ + '<html>\n' \ + '<head>\n' \ + ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n' \ + ' <meta http-equiv="Content-Style-Type" content="text/css">\n' \ + ' <meta http-equiv="Content-Script-Type" content="text/javascript">\n' \ + ' <link rel="stylesheet" type="text/css" href="${assets}/style.css">\n' \ + ' <script type="text/javascript">\n' \ + ' var appurl="${app}"\n' \ + ' var assetsurl="${assets}"\n' \ + ' </script>\n' \ + ' <script type="text/javascript" src="${assets}/common.js"></script>\n' \ + ' <script type="text/javascript" src="${assets}/romident.js"></script>\n' \ + ' <title>Identify ROM/Disk Dumps</title>\n' \ + '</head>\n' \ + '<body>\n' \ + '<h1>Identify ROM/Disk Dumps</h1>\n' \ + '<p>No files are uploaded. Files are examined locally and checksums/digests are sent to the server. File checksums and digests may be logged on the server.</p>\n' \ + '<div id="div-dropzone" class="dropzone" ondragover="div_dropzone_dragover(event)" ondrop="div_dropzone_drop(event)">\n' \ + '<p><button type="button" onclick="document.getElementById("input-dumps").click()">Select ROM/disk dumps</button></p>\n' \ + '<p>Drag and drop ROM/disk dump files here to identify them.</p>\n' \ + '</div>\n' \ + '<input id="input-dumps" type="file" multiple onchange="add_dump_files(this.files)" style="display: none">\n' \ + '<div id="div-progress"></div>\n' \ + '<div id="div-machines"></div>\n' \ + '</body>\n' \ + '</html>\n') diff --git a/scripts/minimaws/lib/wsgiserve.py b/scripts/minimaws/lib/wsgiserve.py index b30828e01e0..f05ba6b7e76 100644 --- a/scripts/minimaws/lib/wsgiserve.py +++ b/scripts/minimaws/lib/wsgiserve.py @@ -424,6 +424,25 @@ class SourceFileHandler(QueryPageHandler): parent=cgi.escape(machine_info['cloneof'] or '')).encode('utf-8') +class RomIdentHandler(QueryPageHandler): + def __init__(self, app, application_uri, environ, start_response, **kwargs): + super(QueryPageHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) + self.dbcurs = app.dbconn.cursor() + + def __iter__(self): + if self.environ['PATH_INFO']: + self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(404) + elif self.environ['REQUEST_METHOD'] != 'GET': + self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(405) + else: + self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + yield htmltmpl.ROMIDENT_PAGE.substitute( + app=self.js_escape(cgi.escape(self.application_uri, True)), + assets=self.js_escape(cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True))).encode('utf-8') + + class BiosRpcHandler(MachineRpcHandlerBase): def data_page(self, machine): result = { } @@ -477,12 +496,86 @@ class SlotsRpcHandler(MachineRpcHandlerBase): yield json.dumps(result).encode('utf-8') +class RomDumpsRpcHandler(QueryPageHandler): + def __init__(self, app, application_uri, environ, start_response, **kwargs): + super(RomDumpsRpcHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) + + def __iter__(self): + if self.environ['PATH_INFO']: + self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(404) + elif self.environ['REQUEST_METHOD'] != 'GET': + self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(405) + else: + try: + args = urlparse.parse_qs(self.environ['QUERY_STRING'], keep_blank_values=True, strict_parsing=True) + crc = args.get('crc') + sha1 = args.get('sha1') + if (len(args) == 2) and (crc is not None) and (len(crc) == 1) and (sha1 is not None) and (len(sha1) == 1): + crc = int(crc[0], 16) + sha1 = sha1[0] + self.start_response('200 OK', [('Content-type', 'application/json; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.data_page(crc, sha1) + except BaseException as e: + pass + self.start_response('500 %s' % (self.STATUS_MESSAGE[500], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(500) + + def data_page(self, crc, sha1): + result = { } + for shortname, description, label, bad in self.dbcurs.get_rom_dumps(crc, sha1): + machine = result.get(shortname) + if machine is None: + machine = { 'description': description, 'matches': [ ] } + result[shortname] = machine + machine['matches'].append({ 'name': label, 'bad': bool(bad) }) + yield json.dumps(result).encode('utf-8') + + +class DiskDumpsRpcHandler(QueryPageHandler): + def __init__(self, app, application_uri, environ, start_response, **kwargs): + super(DiskDumpsRpcHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs) + + def __iter__(self): + if self.environ['PATH_INFO']: + self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(404) + elif self.environ['REQUEST_METHOD'] != 'GET': + self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(405) + else: + try: + args = urlparse.parse_qs(self.environ['QUERY_STRING'], keep_blank_values=True, strict_parsing=True) + sha1 = args.get('sha1') + if (len(args) == 1) and (sha1 is not None) and (len(sha1) == 1): + sha1 = sha1[0] + self.start_response('200 OK', [('Content-type', 'application/json; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')]) + return self.data_page(sha1) + except BaseException as e: + pass + self.start_response('500 %s' % (self.STATUS_MESSAGE[500], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')]) + return self.error_page(500) + + def data_page(self, sha1): + result = { } + for shortname, description, label, bad in self.dbcurs.get_disk_dumps(sha1): + machine = result.get(shortname) + if machine is None: + machine = { 'description': description, 'matches': [ ] } + result[shortname] = machine + machine['matches'].append({ 'name': label, 'bad': bool(bad) }) + yield json.dumps(result).encode('utf-8') + + class MiniMawsApp(object): JS_ESCAPE = re.compile('([\"\'\\\\])') RPC_SERVICES = { - 'bios': BiosRpcHandler, - 'flags': FlagsRpcHandler, - 'slots': SlotsRpcHandler } + 'bios': BiosRpcHandler, + 'flags': FlagsRpcHandler, + 'slots': SlotsRpcHandler, + 'romdumps': RomDumpsRpcHandler, + 'diskdumps': DiskDumpsRpcHandler } def __init__(self, dbfile, **kwargs): super(MiniMawsApp, self).__init__(**kwargs) @@ -498,6 +591,8 @@ class MiniMawsApp(object): return MachineHandler(self, application_uri, environ, start_response) elif module == 'sourcefile': return SourceFileHandler(self, application_uri, environ, start_response) + elif module == 'romident': + return RomIdentHandler(self, application_uri, environ, start_response) elif module == 'static': return AssetHandler(self.assetsdir, self, application_uri, environ, start_response) elif module == 'rpc': diff --git a/scripts/minimaws/minimaws.py b/scripts/minimaws/minimaws.py index 399efa1784a..bf28d6d649d 100755 --- a/scripts/minimaws/minimaws.py +++ b/scripts/minimaws/minimaws.py @@ -26,8 +26,9 @@ ## $ python minimaws.py listclones "unkch*" ## $ python minimaws.py listbrothers superx ## -## The romident command does not support archives, but it's far faster -## than using MAME as it has optimised indexes: +## The romident command does not support archives or software lists, but +## it's far faster than using MAME as it has optimised indexes, and +## results are grouped by machine rather than by file: ## ## $ python minimaws.py romident 27c64.bin dump-dir ## |