2021-08-30 02:14:06 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
// Hex editor style memory viewer
|
|
|
|
globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|
|
|
|
|
|
|
// Object constructor
|
|
|
|
constructor(debug, options) {
|
|
|
|
super(debug.gui, options);
|
|
|
|
|
|
|
|
// Configure instance fields
|
2021-09-02 00:16:22 +00:00
|
|
|
this.address = 0x05000000;
|
2021-08-30 02:14:06 +00:00
|
|
|
this.debug = debug;
|
2021-09-02 00:16:22 +00:00
|
|
|
this.rows = [];
|
2021-08-30 02:14:06 +00:00
|
|
|
|
|
|
|
// Configure element
|
|
|
|
this.element.setAttribute("window", "memory");
|
2021-09-02 00:16:22 +00:00
|
|
|
|
|
|
|
// Configure body
|
|
|
|
this.body.element.setAttribute("filter", "");
|
2021-08-30 02:14:06 +00:00
|
|
|
|
|
|
|
// Configure client
|
2021-09-02 00:16:22 +00:00
|
|
|
this.client.setLayout("grid");
|
2021-08-30 02:14:06 +00:00
|
|
|
this.client.addResizeListener(b=>this.refresh(b.height));
|
2021-09-02 00:16:22 +00:00
|
|
|
|
|
|
|
// Wrapping element to hide overflowing scrollbar
|
|
|
|
this.hexWrap = this.client.add(this.newPanel({
|
|
|
|
layout : "grid",
|
|
|
|
columns: "auto"
|
|
|
|
}));
|
|
|
|
this.hexWrap.element.setAttribute("name", "wrap-hex");
|
|
|
|
|
|
|
|
// Configure hex viewer
|
|
|
|
this.hex = this.hexWrap.add(this.client.newPanel({
|
|
|
|
focusable: true,
|
|
|
|
layout : "block",
|
|
|
|
hollow : false,
|
|
|
|
overflowX: "auto",
|
|
|
|
overflowY: "hidden"
|
|
|
|
}));
|
|
|
|
this.hex.element.setAttribute("role", "grid");
|
|
|
|
this.hex.element.setAttribute("name", "hex");
|
|
|
|
this.hex.element.addEventListener("wheel", e=>this.onwheel(e));
|
2021-08-30 02:14:06 +00:00
|
|
|
|
|
|
|
// Configure properties
|
|
|
|
this.setProperty("sim", "");
|
2021-09-02 00:16:22 +00:00
|
|
|
this.rows.push(this.hex.add(new MemoryWindow.Row(this.hex)));
|
|
|
|
this.application.addComponent(this);
|
2021-08-30 02:14:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-09-02 00:16:22 +00:00
|
|
|
///////////////////////////// Package Methods /////////////////////////////
|
2021-08-30 02:14:06 +00:00
|
|
|
|
2021-09-02 00:16:22 +00:00
|
|
|
// Update display text with localized strings
|
|
|
|
localize() {
|
|
|
|
let hex = "";
|
|
|
|
if (this.application)
|
|
|
|
hex = this.application.translate("{memory.hexEditor}");
|
|
|
|
this.hex.element.setAttribute("aria-label", hex);
|
2021-08-30 02:14:06 +00:00
|
|
|
}
|
|
|
|
|
2021-09-02 00:16:22 +00:00
|
|
|
// Update the display with current emulation data
|
|
|
|
refresh(gridHeight, lineHeight) {
|
2021-08-30 02:14:06 +00:00
|
|
|
|
2021-09-02 00:16:22 +00:00
|
|
|
// Do nothing while closed
|
|
|
|
if (!this.isVisible())
|
|
|
|
return;
|
2021-08-30 02:14:06 +00:00
|
|
|
|
2021-09-02 00:16:22 +00:00
|
|
|
// Working variables
|
|
|
|
gridHeight = gridHeight || this.hex.getBounds().height;
|
|
|
|
lineHeight = lineHeight || this.lineHeight();
|
|
|
|
let rowCount = this.lines(false, gridHeight, lineHeight);
|
2021-08-30 02:14:06 +00:00
|
|
|
|
|
|
|
// Request bus data from the WebAssembly core
|
|
|
|
this.debug.core.postMessage({
|
|
|
|
command: "ReadBuffer",
|
|
|
|
debug : "Memory",
|
|
|
|
address: this.address,
|
|
|
|
sim : this.debug.sim,
|
|
|
|
size : rowCount * 16
|
|
|
|
});
|
|
|
|
|
|
|
|
// Configure elements
|
|
|
|
for (let y = this.rows.length; y < rowCount; y++)
|
2021-09-02 00:16:22 +00:00
|
|
|
this.rows[y] = this.hex.add(new MemoryWindow.Row(this.hex));
|
2021-08-30 02:14:06 +00:00
|
|
|
for (let y = rowCount; y < this.rows.length; y++)
|
2021-09-02 00:16:22 +00:00
|
|
|
this.hex.remove(this.rows[y]);
|
2021-08-30 02:14:06 +00:00
|
|
|
if (this.rows.length > rowCount)
|
|
|
|
this.rows.splice(rowCount, this.rows.length - rowCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Message Methods /////////////////////////////
|
|
|
|
|
|
|
|
// Message received
|
|
|
|
message(msg) {
|
|
|
|
switch (msg.command) {
|
|
|
|
case "ReadBuffer": this.readBuffer(msg); break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Received bytes from the bus
|
|
|
|
readBuffer(msg) {
|
|
|
|
let bytes = new Uint8Array(msg.buffer);
|
|
|
|
for (
|
|
|
|
let x = 0, address = this.address, offset = 0;
|
|
|
|
x < this.rows.length && offset < bytes.length;
|
2021-09-03 17:38:48 +00:00
|
|
|
x++, address = (address + 16 & 0xFFFFFFF0) >>> 0, offset += 16
|
2021-08-30 02:14:06 +00:00
|
|
|
) this.rows[x].update(address, bytes, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Private Methods /////////////////////////////
|
|
|
|
|
2021-09-02 00:16:22 +00:00
|
|
|
// The window is being displayed for the first time
|
|
|
|
firstShow() {
|
|
|
|
super.firstShow();
|
|
|
|
this.center();
|
|
|
|
}
|
|
|
|
|
2021-08-30 02:14:06 +00:00
|
|
|
// Determine the height in pixels of one row of output
|
|
|
|
lineHeight() {
|
2021-09-02 00:16:22 +00:00
|
|
|
return Math.max(10, this.rows[0].address.getBounds().height);
|
2021-08-30 02:14:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the number of rows of output
|
2021-09-02 00:16:22 +00:00
|
|
|
lines(fullyVisible, gridHeight, lineHeight) {
|
|
|
|
gridHeight = gridHeight || this.client.getBounds().height;
|
|
|
|
lineHeight = lineHeight || this.lineHeight();
|
|
|
|
let ret = gridHeight / lineHeight;
|
|
|
|
ret = fullyVisible ? Math.floor(ret) : Math.ceil(ret);
|
2021-08-30 13:58:40 +00:00
|
|
|
return Math.max(1, ret);
|
2021-08-30 02:14:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Key down event handler
|
|
|
|
onkeydown(e) {
|
|
|
|
|
|
|
|
// Control is pressed
|
|
|
|
if (e.ctrlKey) switch (e.key) {
|
|
|
|
case "g": case "G":
|
2021-08-30 13:58:40 +00:00
|
|
|
let addr = prompt(this.application.translate("{app.goto_}"));
|
2021-08-30 02:14:06 +00:00
|
|
|
if (addr === null)
|
|
|
|
break;
|
|
|
|
this.seek(
|
|
|
|
(parseInt(addr, 16) & 0xFFFFFFF0) >>> 0,
|
|
|
|
Math.floor(this.lines(true) / 3)
|
|
|
|
);
|
|
|
|
this.refresh();
|
|
|
|
break;
|
|
|
|
default: return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Processing by key
|
|
|
|
else switch (e.key) {
|
|
|
|
case "ArrowDown":
|
2021-09-03 17:38:48 +00:00
|
|
|
this.address = (this.address + 16 & 0xFFFFFFF0) >>> 0;
|
2021-08-30 02:14:06 +00:00
|
|
|
this.refresh();
|
|
|
|
break;
|
|
|
|
case "ArrowUp":
|
2021-09-03 17:38:48 +00:00
|
|
|
this.address = (this.address - 16 & 0xFFFFFFF0) >>> 0;
|
2021-08-30 02:14:06 +00:00
|
|
|
this.refresh();
|
|
|
|
break;
|
|
|
|
case "PageUp":
|
|
|
|
this.address = (this.address - 16 * this.lines(true) &
|
2021-09-03 17:38:48 +00:00
|
|
|
0xFFFFFFF0) >>> 0;
|
2021-08-30 02:14:06 +00:00
|
|
|
this.refresh();
|
|
|
|
break;
|
|
|
|
case "PageDown":
|
|
|
|
this.address = (this.address + 16 * this.lines(true) &
|
2021-09-03 17:38:48 +00:00
|
|
|
0xFFFFFFF0) >>> 0;
|
2021-08-30 02:14:06 +00:00
|
|
|
this.refresh();
|
|
|
|
break;
|
2021-08-30 14:53:31 +00:00
|
|
|
default: return super.onkeydown(e);
|
2021-08-30 02:14:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Configure event
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mouse wheel event handler
|
|
|
|
onwheel(e) {
|
|
|
|
let lineHeight = this.lineHeight();
|
|
|
|
let sign = Math.sign(e.deltaY);
|
|
|
|
let mag = Math.abs (e.deltaY);
|
|
|
|
if (e.deltaMode == WheelEvent.DOM_DELTA_PIXEL)
|
|
|
|
mag = Math.max(1, Math.floor(mag / lineHeight));
|
2021-09-02 00:16:22 +00:00
|
|
|
|
|
|
|
// Configure element
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
|
|
// Specify the new address
|
2021-09-03 17:38:48 +00:00
|
|
|
this.address = (this.address + sign * mag * 16 & 0xFFFFFFF0) >>> 0;
|
2021-08-30 02:14:06 +00:00
|
|
|
this.refresh(null, lineHeight);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Move to a new address positioned at a particular row of output
|
|
|
|
seek(address, index) {
|
2021-09-03 17:38:48 +00:00
|
|
|
this.address = (address - (index || 0) * 16 & 0xFFFFFFF0) >>> 0;
|
2021-08-30 02:14:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
// One row of output
|
2021-09-02 00:16:22 +00:00
|
|
|
MemoryWindow.Row = class Row extends Toolkit.Panel {
|
2021-08-30 02:14:06 +00:00
|
|
|
|
|
|
|
// Object constructor
|
2021-09-02 00:16:22 +00:00
|
|
|
constructor(parent) {
|
|
|
|
super(parent.application, {
|
|
|
|
layout : "grid",
|
|
|
|
columns : "repeat(17, max-content)",
|
|
|
|
hollow : false,
|
|
|
|
overflowX: "visible",
|
|
|
|
overflowY: "visible"
|
|
|
|
});
|
2021-08-30 02:14:06 +00:00
|
|
|
|
|
|
|
// Configure instance fields
|
2021-09-02 00:16:22 +00:00
|
|
|
this.bytes = new Array(16);
|
|
|
|
|
|
|
|
// Configure element
|
|
|
|
this.element.setAttribute("role", "row");
|
2021-08-30 02:14:06 +00:00
|
|
|
|
|
|
|
// Address label
|
2021-09-02 00:16:22 +00:00
|
|
|
this.address = this.add(parent.newLabel({ text: "\u00a0" }));
|
|
|
|
this.address.element.setAttribute("role", "gridcell");
|
2021-08-30 02:14:06 +00:00
|
|
|
this.address.element.setAttribute("name", "address");
|
|
|
|
|
|
|
|
// Byte labels
|
|
|
|
for (let x = 0; x < 16; x++) {
|
|
|
|
let lbl = this.bytes[x] =
|
2021-09-02 00:16:22 +00:00
|
|
|
this.add(parent.newLabel({ text: "\u00a0" }));
|
|
|
|
lbl.element.setAttribute("role", "gridcell");
|
2021-08-30 02:14:06 +00:00
|
|
|
lbl.element.setAttribute("name", "byte");
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////// Package Methods /////////////////////////////
|
|
|
|
|
|
|
|
// Update the output labels with emulation state content
|
|
|
|
update(address, bytes, offset) {
|
|
|
|
this.address.setText(
|
|
|
|
("0000000" + address.toString(16).toUpperCase()).slice(-8));
|
|
|
|
for (let x = 0; x < 16; x++, offset++)
|
|
|
|
this.bytes[x].setText(
|
|
|
|
("0" + bytes[offset].toString(16).toUpperCase()).slice(-2));
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|