diff options
Diffstat (limited to 'scripts/minimaws/lib/assets')
-rw-r--r-- | scripts/minimaws/lib/assets/common.js | 144 | ||||
-rw-r--r-- | scripts/minimaws/lib/assets/digest.js | 235 | ||||
-rw-r--r-- | scripts/minimaws/lib/assets/disclosedown.svg | 4 | ||||
-rw-r--r-- | scripts/minimaws/lib/assets/discloseup.svg | 4 | ||||
-rw-r--r-- | scripts/minimaws/lib/assets/machine.js | 112 | ||||
-rw-r--r-- | scripts/minimaws/lib/assets/romident.js | 642 | ||||
-rw-r--r-- | scripts/minimaws/lib/assets/sortasc.png | bin | 1157 -> 0 bytes | |||
-rw-r--r-- | scripts/minimaws/lib/assets/sortasc.svg | 4 | ||||
-rw-r--r-- | scripts/minimaws/lib/assets/sortdesc.png | bin | 1195 -> 0 bytes | |||
-rw-r--r-- | scripts/minimaws/lib/assets/sortdesc.svg | 4 | ||||
-rw-r--r-- | scripts/minimaws/lib/assets/sortind.png | bin | 1247 -> 0 bytes | |||
-rw-r--r-- | scripts/minimaws/lib/assets/sortind.svg | 5 | ||||
-rw-r--r-- | scripts/minimaws/lib/assets/style.css | 7 |
13 files changed, 1121 insertions, 40 deletions
diff --git a/scripts/minimaws/lib/assets/common.js b/scripts/minimaws/lib/assets/common.js index 79200217ddb..3b10b785fdd 100644 --- a/scripts/minimaws/lib/assets/common.js +++ b/scripts/minimaws/lib/assets/common.js @@ -1,55 +1,127 @@ // license:BSD-3-Clause // copyright-holders:Vas Crabb +'use strict'; -function sort_table(tbl, col, dir, numeric) +function make_collapsible(heading, section) { - var tbody = tbl.tBodies[0]; - var trows = Array.prototype.slice.call(tbody.rows, 0).sort( - function (x, y) + var hidden = false; + var display = section.style.display; + var icon = heading.insertBefore(document.createElement('img'), heading.firstChild); + icon.setAttribute('src', assetsurl + '/disclosedown.svg'); + icon.setAttribute('class', 'disclosure'); + icon.addEventListener( + 'click', + function (event) { - if (numeric) - return dir * (parseInt(x.cells[col].textContent) - parseInt(y.cells[col].textContent)); + hidden = !hidden; + if (hidden) + { + icon.setAttribute('src', assetsurl + '/discloseup.svg'); + section.style.display = 'none'; + } else - return dir * x.cells[col].textContent.localeCompare(y.cells[col].textContent); - }) - trows.forEach(function (row) { tbody.appendChild(row); }); + { + icon.setAttribute('src', assetsurl + '/disclosedown.svg'); + section.style.display = display; + var headingtop = 0; + for (var element = heading; element !== null; element = element.offsetParent) + headingtop += element.offsetTop; + var sectionbot = section.offsetHeight; + for (var element = section; element !== null; element = element.offsetParent) + sectionbot += element.offsetTop; + if ((window.pageYOffset + window.innerHeight) < sectionbot) + { + if ((sectionbot - headingtop) > window.innerHeight) + heading.scrollIntoView(true); + else + window.scroll(window.pageXOffset, sectionbot - window.innerHeight); + } + } + }); + + return function () + { + hidden = true; + icon.setAttribute('src', assetsurl + '/discloseup.svg'); + section.style.display = 'none'; + }; } function make_table_sortable(tbl) { - var headers = tbl.tHead.rows[0].cells; - for (var i = 0; i < headers.length; i++) + var sorticons = new Array(tbl.tHead.rows[0].cells.length); + + function TableSortHelper(i) { - (function (col) - { - var dir = 1; - var sorticon = document.createElement('img'); - sorticon.setAttribute('src', assetsurl + '/sortind.png'); - sorticon.style.cssFloat = 'right'; - sorticon.style.marginLeft = '0.5em'; - headers[col].appendChild(sorticon); - headers[col].addEventListener( - 'click', - function (event) + this.column = i; + this.header = tbl.tHead.rows[0].cells[i]; + this.sorted = false; + this.direction = 1; + + var container = this.header.appendChild(document.createElement('div')); + container.setAttribute('class', 'sorticon'); + + this.icon = container.appendChild(document.createElement('img')); + this.icon.setAttribute('src', assetsurl + '/sortind.svg'); + this.icon.addEventListener('click', this.icon_click.bind(this)); + } + + TableSortHelper.prototype = (function () + { + function set_unsorted(obj) + { + obj.sorted = false; + obj.icon.setAttribute('src', assetsurl + '/sortind.svg'); + } + + function sort_table(obj) + { + var c = obj.header.cellIndex; + var numeric = obj.header.getAttribute('class') == 'numeric'; + var tbody = tbl.tBodies[0]; + var trows; + if (numeric) + { + trows = Array.prototype.slice.call(tbody.rows, 0).sort( + function (x, y) + { + return obj.direction * (parseFloat(x.cells[c].textContent) - parseFloat(y.cells[c].textContent)); + }); + } + else { - var imgsrc = sorticon.getAttribute('src'); - imgsrc = imgsrc.substr(imgsrc.lastIndexOf('/') + 1); - if (imgsrc != 'sortind.png') - dir = -dir; - if (dir < 0) - sorticon.setAttribute('src', assetsurl + '/sortdesc.png'); + trows = Array.prototype.slice.call(tbody.rows, 0).sort( + function (x, y) + { + return obj.direction * x.cells[c].textContent.localeCompare(y.cells[c].textContent); + }); + } + trows.forEach(function (row) { tbody.appendChild(row); }); + } + + return { + icon_click : function (event) + { + if (this.sorted) + this.direction = -this.direction; + if (this.direction < 0) + this.icon.setAttribute('src', assetsurl + '/sortdesc.svg'); else - sorticon.setAttribute('src', assetsurl + '/sortasc.png'); - for (var i = 0; i < headers.length; i++) + this.icon.setAttribute('src', assetsurl + '/sortasc.svg'); + this.sorted = true; + for (var i = 0; i < sorticons.length; i++) { - if (i != col) - headers[i].lastChild.setAttribute('src', assetsurl + '/sortind.png'); + if (i != this.column) + set_unsorted(sorticons[i]); } - sort_table(tbl, col, dir, headers[col].getAttribute('class') == 'numeric'); - }); - }(i)); - } + sort_table(this); + } + }; + })(); + + for (var i = 0; i < sorticons.length; i++) + sorticons[i] = new TableSortHelper(i); } diff --git a/scripts/minimaws/lib/assets/digest.js b/scripts/minimaws/lib/assets/digest.js new file mode 100644 index 00000000000..91285ff529f --- /dev/null +++ b/scripts/minimaws/lib/assets/digest.js @@ -0,0 +1,235 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +'use strict'; + +function Crc32Digester() +{ + this.crc = 0xffffffff; +} + +Crc32Digester.prototype = (function () + { + var table = new Uint32Array(256); + (function () + { + for (var i = 0; i < 256; i++) + { + var crc = i; + for (var b = 0; b < 8; b++) + crc = (crc >>> 1) ^ ((crc & 1) ? 0xedb88320 : 0x00000000); + table[i] = crc; + } + })(); + + return { + digest: function (data, view) + { + for (var i = 0; i < view.length; i++) + this.crc = (this.crc >>> 8) ^ table[(this.crc & 0x000000ff) ^ view[i]]; + }, + + finalise: function () + { + return this.crc ^ 0xffffffff; + } + }; + })(); + + +function Sha1Digester() +{ + this.st = new Uint32Array(5); + this.st[0] = 0xc3d2e1f0; + this.st[1] = 0x10325476; + this.st[2] = 0x98badcfe; + this.st[3] = 0xefcdab89; + this.st[4] = 0x67452301; + + this.cnt = new Uint32Array(2); + this.cnt[0] = 0; + this.cnt[1] = 0; + + this.buf = new DataView(new ArrayBuffer(64)); +} + +Sha1Digester.prototype = (function () + { + function rol(x, n) + { + return ((x << n) | (x >>> (32 - n))) & 0xffffffff; + } + + function b(data, 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(data, d, 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(data, d, 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(data, i) + 0x5a827999 + rol(d[(i + 4) % 5], 5)); + d[(i + 3) % 5] = rol(d[(i + 3) % 5], 30); + } + + function r2(data, d, i) + { + d[i % 5] = 0xffffffff & (d[i % 5] + (d[(i + 3) % 5] ^ d[(i + 2) % 5] ^ d[(i + 1) % 5]) + b(data, i) + 0x6ed9eba1 + rol(d[(i + 4) % 5], 5)); + d[(i + 3) % 5] = rol(d[(i + 3) % 5], 30); + } + + function r3(data, d, 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(data, i) + 0x8f1bbcdc + rol(d[(i + 4) % 5], 5)); + d[(i + 3) % 5] = rol(d[(i + 3) % 5], 30); + } + + function r4(data, d, i) + { + d[i % 5] = 0xffffffff & (d[i % 5] + (d[(i + 3) % 5] ^ d[(i + 2) % 5] ^ d[(i + 1) % 5]) + b(data, i) + 0xca62c1d6 + rol(d[(i + 4) % 5], 5)); + d[(i + 3) % 5] = rol(d[(i + 3) % 5], 30); + } + + function process(st, data) + { + var d = new Uint32Array(st); + var i = 0; + while (i < 16) + r0(data, d, i++); + while (i < 20) + r1(data, d, i++); + while (i < 40) + r2(data, d, i++); + while (i < 60) + r3(data, d, i++); + while (i < 80) + r4(data, d, i++); + for (i = 0; i < st.length; i++) + st[i] = (st[i] + d[i]) & 0xffffffff; + } + + return { + digest: function (data, view) + { + var residual = this.cnt[0]; + this.cnt[0] = (this.cnt[0] + (view.length << 3)) & 0xffffffff; + if (residual > this.cnt[0]) + this.cnt[1]++; + this.cnt[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++) + this.buf.setUint8(offset + residual, view[offset]); + process(this.st, this.buf); + residual = 0; + } + for ( ; (view.length - offset) >= 64; offset += 64) + process(this.st, new DataView(data, offset, 64)); + } + for ( ; offset < view.length; residual++, offset++) + this.buf.setUint8(residual, view[offset]); + }, + + finalise : function () + { + var lenbuf = new ArrayBuffer(8); + var lenview = new DataView(lenbuf); + lenview.setUint32(0, this.cnt[1], false); + lenview.setUint32(4, this.cnt[0], false); + var padbuf = new ArrayBuffer(64 - (63 & ((this.cnt[0] >>> 3) + 8))); + var padview = new Uint8Array(padbuf); + padview[0] = 0x80; + for (var i = 1; i < padview.length; i++) + padview[i] = 0x00; + this.digest(padbuf, padview); + this.digest(lenbuf, new Uint8Array(lenbuf)); + var result = new Array(20); + for (var i = 0; i < 20; i++) + result[i] = (this.st[4 - (i >>> 2)] >> ((3 - (i & 3)) << 3)) & 0x000000ff; + return result.map(x => x.toString(16).padStart(2, '0')).join(''); + } + }; + })(); + + +function StuckBitsDigester(max = 4) +{ + this.offset = 0; + this.state = [ ]; + for (var i = 0; i <= max; i++) + { + var bytes = 1 << i; + this.state.push({ filled: 0, bits: new Uint8Array(bytes), mask: new Uint8Array(bytes) }); + } +} + +StuckBitsDigester.prototype = (function () + { + return { + digest: function (data, view) + { + for (var i = 0; i < view.length; i++) + { + var data = view[i]; + for (var j = 0; j < this.state.length; j++) + { + var detail = this.state[j]; + var bytes = 1 << j; + var o = this.offset % bytes; + var f = o == (bytes - 1); + if (!detail.filled) + { + detail.bits[o] = data; + detail.mask[o] = 0xff; + if (f) + detail.filled = 1; + } + else + { + detail.mask[o] &= ~(data ^ detail.bits[o]); + detail.bits[o] &= detail.mask[o]; + } + if (o == (bytes - 1)) + detail.filled += 1; + } + this.offset = (this.offset + 1) % (1 << (this.state.length - 1)); + } + }, + + finalise: function () + { + var result = [ ]; + var lastresult = null; + for (var i = 0; (i < this.state.length) && (this.state[i].filled >= 4); i++) + { + var detail = this.state[i]; + var bytes = 1 << i; + for (var j = 0; j < bytes; j++) + { + var mask = detail.mask[j]; + if (mask && ((lastresult === null) || (mask != lastresult.mask[j % (1 << (lastresult.mask.length - 1))]))) + { + lastresult = { bits: detail.bits, mask: detail.mask }; + result.push(lastresult); + break; + } + } + } + return result; + } + }; + })(); diff --git a/scripts/minimaws/lib/assets/disclosedown.svg b/scripts/minimaws/lib/assets/disclosedown.svg new file mode 100644 index 00000000000..bb66c7f5de9 --- /dev/null +++ b/scripts/minimaws/lib/assets/disclosedown.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="16" width="16"> + <path d="m 0,5 h 16 l -8,10 z" /> +</svg> diff --git a/scripts/minimaws/lib/assets/discloseup.svg b/scripts/minimaws/lib/assets/discloseup.svg new file mode 100644 index 00000000000..010065a446b --- /dev/null +++ b/scripts/minimaws/lib/assets/discloseup.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="16" width="16"> + <path d="m 5,0 v 16 l 10,-8 z" /> +</svg> diff --git a/scripts/minimaws/lib/assets/machine.js b/scripts/minimaws/lib/assets/machine.js index d0774d02c0a..b488ae9e9a4 100644 --- a/scripts/minimaws/lib/assets/machine.js +++ b/scripts/minimaws/lib/assets/machine.js @@ -1,9 +1,11 @@ // license:BSD-3-Clause // copyright-holders:Vas Crabb +'use strict'; var slot_info = Object.create(null); var bios_sets = Object.create(null); var machine_flags = Object.create(null); +var softwarelist_info = Object.create(null); function make_slot_popup_id(name) { return ('select-slot-choice-' + name).replace(/:/g, '-'); } @@ -136,7 +138,7 @@ var fetch_bios_sets = (function () { pending[device] = true; var req = new XMLHttpRequest(); - req.open('GET', appurl + 'rpc/bios/' + device, true); + req.open('GET', appurl + 'rpc/bios/' + encodeURIComponent(device), true); req.responseType = 'json'; req.onload = function () @@ -171,7 +173,7 @@ var fetch_machine_flags = (function () { pending[device] = true; var req = new XMLHttpRequest(); - req.open('GET', appurl + 'rpc/flags/' + device, true); + req.open('GET', appurl + 'rpc/flags/' + encodeURIComponent(device), true); req.responseType = 'json'; req.onload = function () @@ -197,6 +199,47 @@ var fetch_machine_flags = (function () })(); +var fetch_softwarelist_info = (function () + { + var pending = Object.create(null); + return function (device) + { + if (!Object.prototype.hasOwnProperty.call(softwarelist_info, device) && !Object.prototype.hasOwnProperty.call(pending, device)) + { + pending[device] = true; + var req = new XMLHttpRequest(); + req.open('GET', appurl + 'rpc/softwarelists/' + encodeURIComponent(device), true); + req.responseType = 'json'; + req.onload = + function () + { + delete pending[device]; + if (req.status == 200) + { + softwarelist_info[device] = req.response; + var slotslist = document.getElementById('list-slot-options'); + if (slotslist) + { + for (var item = slotslist.firstChild; item; item = item.nextSibling) + { + if ((item.nodeName == 'DT') && (item.getAttribute('data-slotcard') == device)) + { + var slotname = item.getAttribute('data-slotname'); + add_softwarelist_rows( + device, + slotname, + document.getElementById('select-slot-choice-' + slotname.replace(/:/g, '-')).value); + } + } + } + } + }; + req.send(); + } + }; + })(); + + function add_flag_rows(table, device) { var sorted_features = Object.keys(machine_flags[device].features).sort(); @@ -228,6 +271,51 @@ function add_flag_rows(table, device) } +function add_softwarelist_rows(device, slot, card) +{ + var sorted_softwarelists = Object.keys(softwarelist_info[device]).sort(); + var cardtag = slot + ':' + card; + if (sorted_softwarelists.length) + { + var table = document.getElementById('tbl-softwarelists').tBodies[0]; + sorted_softwarelists.forEach( + function (tag) + { + var cell, link; + var info = softwarelist_info[device][tag]; + var href = appurl + 'softwarelist/' + encodeURIComponent(info.shortname); + var row = table.appendChild(document.createElement('tr')); + row.setAttribute('id', 'row-softwarelists-' + (cardtag + tag).replace(/:/g, '-')) + row.appendChild(document.createElement('td')).textContent = cardtag; + link = row.appendChild(document.createElement('td')).appendChild(document.createElement('a')); + link.setAttribute('href', href); + link.textContent = info.shortname; + link = row.appendChild(document.createElement('td')).appendChild(document.createElement('a')); + link.setAttribute('href', href); + link.textContent = info.description; + row.appendChild(document.createElement('td')).textContent = info.status; + total = info.total; + cell = row.appendChild(document.createElement('td')); + cell.textContent = total.toString(10); + cell.style.textAlign = 'right'; + if (!total) + total = 1; + cell = row.appendChild(document.createElement('td')); + cell.textContent = (info.supported * 100.0 / total).toFixed(1) + '%'; + cell.style.textAlign = 'right'; + cell = row.appendChild(document.createElement('td')); + cell.textContent = (info.partiallysupported * 100.0 / total).toFixed(1) + '%'; + cell.style.textAlign = 'right'; + cell = row.appendChild(document.createElement('td')); + cell.textContent = (info.unsupported * 100.0 / total).toFixed(1) + '%'; + cell.style.textAlign = 'right'; + }); + document.getElementById('heading-softwarelists').style.removeProperty('display'); + document.getElementById('tbl-softwarelists').style.removeProperty('display'); + } +} + + function add_bios_row(slot, table, device) { var sorted_sets = Object.keys(bios_sets[device]).sort(); @@ -394,6 +482,16 @@ function make_slot_change_handler(name, slot, defaults, dfltbtn) slotslist.removeChild(next); } + // clear out any software lists from previous selection + var softwarelist_rowid_prefix = 'row-softwarelists-' + prefix.replace(/:/g, '-'); + for (var candidate = document.getElementById('tbl-softwarelists').tBodies[0].rows[0]; candidate; ) + { + var next = candidate.nextSibling; + if ((candidate.nodeName == 'TR') && candidate.getAttribute('id').startsWith(softwarelist_rowid_prefix)) + candidate.parentNode.removeChild(candidate); + candidate = next; + } + if (selection === null) { // no selection, remove the slot card details table @@ -413,7 +511,7 @@ function make_slot_change_handler(name, slot, defaults, dfltbtn) row.appendChild(document.createElement('th')).textContent = 'Short name:'; var link = row.appendChild(document.createElement('td')).appendChild(document.createElement('a')); link.textContent = selection.device; - link.setAttribute('href', appurl + 'machine/' + selection.device); + link.setAttribute('href', appurl + 'machine/' + encodeURIComponent(selection.device)); // if we have emulation flags, populate now, otherwise fetch asynchronously if (!Object.prototype.hasOwnProperty.call(machine_flags, selection.device)) @@ -427,6 +525,12 @@ function make_slot_change_handler(name, slot, defaults, dfltbtn) else add_bios_row(slotname, tbl, selection.device); + // if we have software list info, populate now, otherweise fetch asynchronously + if (!Object.prototype.hasOwnProperty.call(softwarelist_info, selection.device)) + fetch_softwarelist_info(selection.device); + else + add_softwarelist_rows(selection.device, slotname, choice); + // drop the details table into the list if (def.firstChild) def.replaceChild(tbl, def.firstChild); @@ -487,7 +591,7 @@ function fetch_slots(machine) function make_request(device) { var req = new XMLHttpRequest(); - req.open('GET', appurl + 'rpc/slots/' + device, true); + req.open('GET', appurl + 'rpc/slots/' + encodeURIComponent(device), true); req.responseType = 'json'; req.onload = function () diff --git a/scripts/minimaws/lib/assets/romident.js b/scripts/minimaws/lib/assets/romident.js new file mode 100644 index 00000000000..892894ced56 --- /dev/null +++ b/scripts/minimaws/lib/assets/romident.js @@ -0,0 +1,642 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +'use strict'; + +var matched_names = new Array(); +var unmatched_names = new Array(); +var dump_info = Object.create(null); +var machine_info = Object.create(null); +var softwarelist_info = Object.create(null); +var software_info = Object.create(null); + + +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_software_matches(table, names, part, 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 = part; + 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-unmatched'); + var heading = div.appendChild(document.createElement('h2')); + heading.textContent = 'Unmatched'; + table = div.appendChild(document.createElement('table')); + table.setAttribute('id', 'table-unmatched'); + make_collapsible(heading, table); + } + 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++) + { + var row = table.appendChild(document.createElement('tr')); + row.appendChild(document.createElement('th')).textContent = names[i]; + row.appendChild(document.createElement('td')).textContent = content; + unmatched_names.push(names[i]); + } +} + + +function add_issues(name) +{ + var div = document.getElementById('div-issues'); + var list; + if (!div.hasChildNodes()) + { + var heading = div.appendChild(document.createElement('h2')); + heading.textContent = 'Potential Issues'; + list = div.appendChild(document.createElement('dl')); + make_collapsible(heading, list); + } + else + { + list = div.lastChild; + } + list.appendChild(document.createElement('dt')).textContent = name; + var table = list.appendChild(document.createElement('dd')).appendChild(document.createElement('table')); + table.setAttribute('class', 'sysinfo'); + return table; +} + + +function add_stuck_bits(table, stuck) +{ + function format_stuck_bits(bits, mask) + { + var result = ''; + for (var i = 0; i < bits.length; i++) + { + if (i > 0) + result += ' '; + for (var j = 0; j < 8; j++) + { + if (!((mask[i] >> (7 - j)) & 0x01)) + result += '-'; + else if ((bits[i] >> (7 - j)) & 0x01) + result += '1'; + else + result += '0'; + } + } + var cell = document.createElement('td'); + cell.appendChild(document.createElement('tt')).textContent = result; + return cell; + } + + var row = table.appendChild(document.createElement('tr')); + var header = row.appendChild(document.createElement('th')); + header.textContent = 'Fixed data bits:'; + header.setAttribute('rowspan', stuck.length); + row.appendChild(format_stuck_bits(stuck[0].bits, stuck[0].mask)); + for (var i = 1; i < stuck.length; i++) + { + row = table.appendChild(document.createElement('tr')); + row.appendChild(format_stuck_bits(stuck[i].bits, stuck[i].mask)); + } +} + + +function get_machine_table(shortname, description) +{ + if (Object.hasOwnProperty.call(machine_info, shortname)) + { + return machine_info[shortname]; + } + else + { + var div = document.getElementById('div-machines'); + if (!div.hasChildNodes()) + div.appendChild(document.createElement('h2')).textContent = 'Machines'; + var heading = div.appendChild(document.createElement('h3')); + var link = heading.appendChild(document.createElement('a')); + link.textContent = description; + link.setAttribute('href', appurl + 'machine/' + encodeURIComponent(shortname)); + var table = div.appendChild(document.createElement('table')); + machine_info[shortname] = table; + add_matches(table, matched_names, null); + make_collapsible(heading, table); + return table; + } +} + + +function get_softwarelist_div(shortname, description) +{ + if (Object.hasOwnProperty.call(softwarelist_info, shortname)) + { + return softwarelist_info[shortname]; + } + else + { + software_info[shortname] = Object.create(null) + var div = document.getElementById('div-software'); + if (!div.hasChildNodes()) + div.appendChild(document.createElement('h2')).textContent = 'Software'; + div = div.appendChild(document.createElement('div')); + var heading = div.appendChild(document.createElement('h3')); + var link = heading.appendChild(document.createElement('a')); + link.textContent = description; + link.setAttribute('href', appurl + 'softwarelist/' + encodeURIComponent(shortname)); + softwarelist_info[shortname] = div; + return div; + } +} + + +function get_software_table(softwarelist, shortname, description) +{ + if (Object.hasOwnProperty.call(software_info, softwarelist) && Object.hasOwnProperty.call(software_info[softwarelist], shortname)) + { + return software_info[softwarelist][shortname]; + } + else + { + var div = softwarelist_info[softwarelist]; + var heading = div.appendChild(document.createElement('h4')); + var link = heading.appendChild(document.createElement('a')); + link.textContent = description; + link.setAttribute('href', appurl + 'softwarelist/' + encodeURIComponent(softwarelist) + '/' + encodeURIComponent(shortname)); + var table = div.appendChild(document.createElement('table')); + software_info[softwarelist][shortname] = table; + add_software_matches(table, matched_names, null, 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); + var software = Object.create(null); + var matched = Object.keys(req.response.machines); + var softwarelists = Object.keys(req.response.software); + + if (matched.length > 0) + { + Object.keys(machine_info).forEach( + function (shortname) + { + var machine_table = machine_info[shortname]; + if (Object.hasOwnProperty.call(req.response.machines, shortname)) + { + machines[shortname] = req.response.machines[shortname].matches; + add_matches(machine_table, group.names, req.response.machines[shortname].matches); + } + else + { + add_matches(machine_table, group.names, null); + } + }); + if (softwarelists.length <= 0) + { + Object.keys(software_info).forEach( + function (listname) + { + Object.keys(software_info[listname]).forEach( + function (softwarename) + { + var software_table = software_info[listname][softwarename]; + add_software_matches(software_table, group.names, null, null); + }); + }); + } + matched.forEach( + function (shortname) + { + if (!Object.hasOwnProperty.call(machine_info, shortname)) + { + var machine_details = req.response.machines[shortname]; + var machine_table = get_machine_table(shortname, machine_details.description); + machines[shortname] = req.response.machines[shortname].matches; + add_matches(machine_table, group.names, machine_details.matches); + } + }); + } + + if (softwarelists.length > 0) + { + if (matched.length <= 0) + { + Object.keys(machine_info).forEach( + function (shortname) + { + var machine_table = machine_info[shortname]; + add_matches(machine_table, group.names, null); + }); + } + Object.keys(software_info).forEach( + function (listname) + { + var haslist = Object.hasOwnProperty.call(req.response.software, listname); + Object.keys(software_info[listname]).forEach( + function (softwarename) + { + var software_table = software_info[listname][softwarename]; + if (haslist && Object.hasOwnProperty.call(req.response.software[listname].software, softwarename)) + { + if (!Object.hasOwnProperty.call(software, listname)) + software[listname] = Object.create(null); + if (!Object.hasOwnProperty.call(software[listname], softwarename)) + software[listname][softwarename] = Object.create(null); + var software_details = req.response.software[listname].software[softwarename]; + Object.keys(software_details.parts).forEach( + function (partname) + { + var matches = software_details.parts[partname].matches; + software[listname][softwarename][partname] = matches; + add_software_matches(software_table, group.names, partname, matches); + }); + } + else + { + add_software_matches(software_table, group.names, null, null); + } + }); + }); + softwarelists.forEach( + function (listname) + { + var haslist = Object.hasOwnProperty.call(software_info, listname); + var softwarelist_details = req.response.software[listname]; + var softwarelist_div = get_softwarelist_div(listname, softwarelist_details.description); + Object.keys(softwarelist_details.software).forEach( + function (softwarename) + { + if (!haslist || !Object.hasOwnProperty.call(software_info[listname], softwarename)) + { + if (!Object.hasOwnProperty.call(software, listname)) + software[listname] = Object.create(null); + if (!Object.hasOwnProperty.call(software[listname], softwarename)) + software[listname][softwarename] = Object.create(null); + var software_details = softwarelist_details.software[softwarename]; + var software_table = get_software_table(listname, softwarename, software_details.description); + Object.keys(software_details.parts).forEach( + function (partname) + { + var matches = software_details.parts[partname].matches; + software[listname][softwarename][partname] = matches; + add_software_matches(software_table, group.names, partname, matches); + }); + } + }); + }); + } + + if ((matched.length > 0) | (softwarelists.length > 0)) + { + 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; + group.software = software; + 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('issues')) + { + var issues = group.issues; + if (issues !== null) + issues.parentNode.parentNode.insertBefore(document.createElement('dt'), issues.parentNode).textContent = name; + } + + if (group.hasOwnProperty('machines')) + { + var machines = group.machines; + var software = group.software; + var names = [ name ]; + if ((Object.keys(machines).length > 0) || (Object.keys(software).length > 0)) + { + Object.keys(machine_info).forEach( + function (shortname) + { + var machine_table = machine_info[shortname]; + if (Object.hasOwnProperty.call(machines, shortname)) + add_matches(machine_table, names, machines[shortname]); + else + add_matches(machine_table, names, null); + }); + + Object.keys(software_info).forEach( + function (listname) + { + var haslist = Object.hasOwnProperty.call(software, listname); + Object.keys(software_info[listname]).forEach( + function (softwarename) + { + var software_table = software_info[listname][softwarename]; + if (haslist && Object.hasOwnProperty.call(software[listname], softwarename)) + { + Object.keys(software[listname][softwarename]).forEach( + function (partname) + { + add_software_matches(software_table, names, partname, software[listname][softwarename][partname]); + }); + } + else + { + add_software_matches(software_table, names, null, null); + } + }); + }); + + matched_names.push(name); + } + else + { + add_unmatched(names, crc, sha1); + } + } + } +} + + +function identify_file(file, trychd, progress) +{ + var digested = 0; + var crcproc = new Crc32Digester(); + var sha1proc = new Sha1Digester(); + var stuckbitsproc = new StuckBitsDigester(); + + 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); + crcproc.digest(data, view); + sha1proc.digest(data, view); + stuckbitsproc.digest(data, view); + digested += data.byteLength; + if (digested < file.size) + { + read_block(); + } + else + { + var crc = crcproc.finalise(); + var sha1 = sha1proc.finalise(); + var stuckbits = stuckbitsproc.finalise(); + + var sha1grp = get_sha1_group(sha1); + var crcgrp; + if (!Object.hasOwnProperty.call(sha1grp.crc, crc)) + { + crcgrp = new Object(); + crcgrp.names = [ file.name ]; + sha1grp.crc[crc] = crcgrp; + if (stuckbits.length) + { + crcgrp.issues = add_issues(file.name); + add_stuck_bits(crcgrp.issues, stuckbits); + } + else + { + crcgrp.issues = null; + } + 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 + { + crcgrp = sha1grp.crc[crc]; + add_name(crcgrp, 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); + }; + + 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/sortasc.png b/scripts/minimaws/lib/assets/sortasc.png Binary files differdeleted file mode 100644 index 29b893cdd02..00000000000 --- a/scripts/minimaws/lib/assets/sortasc.png +++ /dev/null diff --git a/scripts/minimaws/lib/assets/sortasc.svg b/scripts/minimaws/lib/assets/sortasc.svg new file mode 100644 index 00000000000..c2ff41afccb --- /dev/null +++ b/scripts/minimaws/lib/assets/sortasc.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="16" width="30"> + <path d="m 14,16 h 16 l -8,-16 z" /> +</svg> diff --git a/scripts/minimaws/lib/assets/sortdesc.png b/scripts/minimaws/lib/assets/sortdesc.png Binary files differdeleted file mode 100644 index 63f48fc7213..00000000000 --- a/scripts/minimaws/lib/assets/sortdesc.png +++ /dev/null diff --git a/scripts/minimaws/lib/assets/sortdesc.svg b/scripts/minimaws/lib/assets/sortdesc.svg new file mode 100644 index 00000000000..4a10b3f6e47 --- /dev/null +++ b/scripts/minimaws/lib/assets/sortdesc.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="16" width="30"> + <path d="m 14,0 h 16 l -8,16 z" /> +</svg> diff --git a/scripts/minimaws/lib/assets/sortind.png b/scripts/minimaws/lib/assets/sortind.png Binary files differdeleted file mode 100644 index 773dd6aec02..00000000000 --- a/scripts/minimaws/lib/assets/sortind.png +++ /dev/null diff --git a/scripts/minimaws/lib/assets/sortind.svg b/scripts/minimaws/lib/assets/sortind.svg new file mode 100644 index 00000000000..6e351b6a6fb --- /dev/null +++ b/scripts/minimaws/lib/assets/sortind.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="16" width="30"> + <path d="m 0,16 h 16 l -8,-16 z" /> + <path d="m 14,0 h 16 l -8,16 z" /> +</svg> diff --git a/scripts/minimaws/lib/assets/style.css b/scripts/minimaws/lib/assets/style.css index 75ea0fa93de..9ba6631a783 100644 --- a/scripts/minimaws/lib/assets/style.css +++ b/scripts/minimaws/lib/assets/style.css @@ -5,7 +5,14 @@ th { text-align: left; background-color: #ddd; padding: 0.25em } td { padding-left: 0.25em; padding-right: 0.25em } +img[class=disclosure] { height: 0.7em; margin-right: 0.25em; vertical-align: baseline } + +div[class=sorticon] { float: right; height: 100%; margin-left: 0.5em } +div[class=sorticon] img { height: 0.7em; vertical-align: baseline } + 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 } |