Groundwork for loading local disk images in PCjs

This commit is contained in:
Jeff Parsons 2014-11-09 18:57:13 -08:00 committed by jeffpar
commit 43860a588c
20 changed files with 1355 additions and 1157 deletions

View file

@ -869,10 +869,10 @@ if (DEBUGGER) {
];
Debugger.aaOp0FDescs = {
0x00: [Debugger.INS.GRP6, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH],
0x01: [Debugger.INS.GRP7, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH],
0x02: [Debugger.INS.LAR, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_286, Debugger.TYPE_MEM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
0x03: [Debugger.INS.LSL, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_286, Debugger.TYPE_MEM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
0x00: [Debugger.INS.GRP6, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH],
0x01: [Debugger.INS.GRP7, Debugger.TYPE_MODRM | Debugger.TYPE_WORD | Debugger.TYPE_BOTH],
0x02: [Debugger.INS.LAR, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_286, Debugger.TYPE_MEM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
0x03: [Debugger.INS.LSL, Debugger.TYPE_REG | Debugger.TYPE_WORD | Debugger.TYPE_IN | Debugger.TYPE_286, Debugger.TYPE_MEM | Debugger.TYPE_WORD | Debugger.TYPE_IN],
0x05: [Debugger.INS.LOADALL,Debugger.TYPE_286]
};
@ -2747,6 +2747,43 @@ if (DEBUGGER) {
/**
* parseInstruction(sOp, sOperand, addr)
*
* This generally requires an exact match of both the operation code (sOp) and mode operand
* (sOperand) against the aOps[] and aOpMods[] arrays, respectively; however, the regular
* expression built from aOpMods and stored in regexOpModes does relax the matching criteria
* slightly; ie, a 4-digit hex value ("nnnn") will be satisfied with either 3 or 4 digits, and
* similarly, a 2-digit hex address (nn) will be satisfied with either 1 or 2 digits.
*
* Note that this function does not actually store the instruction into memory, even though it requires
* a target address (addr); that parameter is currently needed ONLY for "branch" instructions, because in
* order to calculate the branch displacement, it needs to know where the instruction will ultimately be
* stored, relative to its target address.
*
* Another handy feature of this function is its ability to display all available modes for a particular
* operation. For example, while in "assemble mode", if one types:
*
* ldy?
*
* the Debugger will display:
*
* supported opcodes:
* A0: LDY nn
* A4: LDY [nn]
* AC: LDY [nnnn]
* B4: LDY [nn+X]
* BC: LDY [nnnn+X]
*
* Use of a trailing "?" on any opcode will display all variations of that opcode; no instruction will be
* assembled, and the operand parameter, if any, will be ignored.
*
* Although this function is capable of reporting numerous errors, roughly half of them indicate internal
* consistency errors, not user errors; the former should really be asserts, but I'm not comfortable bombing
* out because of my error as opposed to their error. The only errors a user should expect to see:
*
* "unknown operation": sOp is not a valid operation (per aOps)
* "unknown operand": sOperand is not a valid operand (per aOpMods)
* "unknown instruction": the combination of sOp + sOperand does not exist (per aaOpDescs)
* "branch out of range": the branch address, relative to addr, is too far away
*
* @this {Debugger}
* @param {string} sOp
* @param {string|undefined} sOperand

View file

@ -35,14 +35,13 @@
*
* 1) creating an empty disk: create()
* 2) loading a disk image: load()
* 3) mounting a disk image: mount()
* 4) getting disk information: info()
* 5) dumping disk contents: dump()
* 6) seeking a disk sector: seek()
* 7) reading data from a sector: read()
* 8) writing data to a sector: write()
* 9) save disk deltas: save()
* 10) restore disk deltas: restore()
* 3) getting disk information: info()
* 4) dumping disk contents: dump()
* 5) seeking a disk sector: seek()
* 6) reading data from a sector: read()
* 7) writing data to a sector: write()
* 8) save disk deltas: save()
* 9) restore disk deltas: restore()
*
* More functionality may be factored out of the FDC and HDC components later and moved here, to
* further reduce some of the duplication between them, but the above functionality is a good start.
@ -349,7 +348,7 @@ Disk.prototype.powerUp = function(data, fRepower) {
if (!fRepower) {
if (this.fOnDemand && !this.fRemote) {
this.setReady(false);
this.load(this.sDiskName, this.sDiskPath, this.donePowerUp, this);
this.load(this.sDiskName, this.sDiskPath, null, this.donePowerUp, this);
}
}
return true;
@ -467,12 +466,16 @@ Disk.prototype.create = function()
};
/**
* load(sDiskName, sDiskPath, fnNotify, controller)
* load(sDiskName, sDiskPath, file, fnNotify)
*
* TODO: Figure out how we can strongly type fnNotify, because the Closure Compiler has issues with:
*
* param {function(Component,Object,Disk,string,string)} fnNotify
*
* for:
*
* this.fnNotify.call(this.controller, this.drive, disk, this.sDiskName, this.sDiskPath);
*
* Also, while we're at it, learn if there are ways to:
*
* 1) declare a function taking NO parameters (ie, generate a warning if any parameters are specified)
@ -481,10 +484,11 @@ Disk.prototype.create = function()
* @this {Disk}
* @param {string} sDiskName
* @param {string} sDiskPath
* @param {File} [file] is set if there's an associated File object
* @param {function(...)} [fnNotify]
* @param {Component} [controller]
*/
Disk.prototype.load = function(sDiskName, sDiskPath, fnNotify, controller)
Disk.prototype.load = function(sDiskName, sDiskPath, file, fnNotify, controller)
{
var sDiskURL = sDiskPath;
@ -497,8 +501,6 @@ Disk.prototype.load = function(sDiskName, sDiskPath, fnNotify, controller)
this.messageDebugger(sMessage);
}
Component.assert(!this.fnNotify);
if (this.fnNotify) {
if (DEBUG) this.controller.log('too many load requests for "' + sDiskName + '" (' + sDiskPath + ')');
return;
@ -509,6 +511,16 @@ Disk.prototype.load = function(sDiskName, sDiskPath, fnNotify, controller)
this.fnNotify = fnNotify;
this.controllerNotify = controller || this.controller;
if (file) {
var disk = this;
var reader = new FileReader();
reader.onload = function() {
disk.build(reader.result, true);
};
reader.readAsArrayBuffer(file);
return;
}
/*
* If there's an occurrence of API_ENDPOINT anywhere in the path, we assume we can use it as-is;
* ie, that the user has already formed a URL of the type we use ourselves for unconverted disk images.
@ -563,6 +575,60 @@ Disk.prototype.load = function(sDiskName, sDiskPath, fnNotify, controller)
web.loadResource(sDiskURL, true, null, this, this.doneLoad, sDiskPath);
};
/**
*
* build(buffer, fDirty)
*
* Builds a disk image from an ArrayBuffer (eg, from a FileReader object), rather than from JSON-encoded data.
*
* @this {Disk}
* @param {?} buffer (we KNOW this is an ArrayBuffer, but we can't seem to convince the Closure Compiler)
* @param {boolean} [fDirty] is true if we should mark the entire disk dirty (to ensure that we save/restore it)
*/
Disk.prototype.build = function(buffer, fDirty)
{
var disk;
var cbDiskData = buffer? buffer.byteLength : 0;
var disketteFormat = DiskAPI.DISKETTE_FORMATS[cbDiskData];
if (disketteFormat) {
var ib = 0;
var dwChecksum = 0;
var cbSector = 512, dwPattern = 0;
var dv = new DataView(buffer, 0, cbDiskData);
this.aDiskData = new Array(disketteFormat[0]);
for (var iCylinder = 0; iCylinder < this.aDiskData.length; iCylinder++) {
var cylinder = this.aDiskData[iCylinder] = new Array(disketteFormat[1]);
for (var iHead = 0; iHead < cylinder.length; iHead++) {
var head = cylinder[iHead] = new Array(disketteFormat[2]);
for (var iSector = 0; iSector < head.length; iSector++) {
var sector = this.initSector(null, iCylinder, iHead, iSector + 1, cbSector, dwPattern);
var cdw = cbSector >> 2;
var adw = sector['data'];
for (var idw = 0; idw < cdw; idw++, ib += 4) {
var dw = adw[idw] = dv.getInt32(ib, true);
dwChecksum = (dwChecksum + dw) & 0xffffffff;
}
if (fDirty) {
sector.cModify = cdw;
sector.fDirty = true;
}
head[iSector] = sector;
}
}
}
this.dwChecksum = dwChecksum;
disk = this;
} else {
this.notice("Unrecognized diskette format (" + cbDiskData + " bytes)");
}
if (this.fnNotify) {
this.fnNotify.call(this.controller, this.drive, disk, this.sDiskName, this.sDiskPath);
this.fnNotify = null;
}
};
/**
* doneLoad(sDiskFile, sDiskData, nErrorCode, sDiskPath)
*
@ -749,27 +815,12 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath)
}
delete sector['bytes'];
}
/*
* The current sector should now have ALL the properties of a proper Sector object; ie:
*
* 'sector': sector number
* 'length': size of the sector, in bytes
* 'data': array of dwords
* 'pattern': dword pattern to use for empty or partial sectors
*
* In addition, we will maintain the following information on a per-sector basis,
* as sectors are modified:
*
* iModify: index of first modified dword in sector
* cModify: number of modified dwords in sector
* fDirty: true if sector is dirty, false if clean (or cleaning in progress)
*
* And for the disk as a whole, we maintain a checksum of the original unmodified data:
*
* dwChecksum: summation of all dwords in all non-empty sectors
*/
this.initSector(sector, iCylinder, iHead);
/*
* For the disk as a whole, we maintain a checksum of the original unmodified data:
*
* dwChecksum: summation of all dwords in all non-empty sectors
*
* Pattern-filling of sectors is deferred until absolutely necessary (eg, when a sector is
* being written). So all we need to do at this point is checksum all the initial sector data.
*/
@ -797,6 +848,20 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath)
/**
* initSector(sector, iCylinder, iHead, iSector, cbSector, dwPattern)
*
* Ensures every sector has ALL the properties of a proper Sector object; ie:
*
* 'sector': sector number
* 'length': size of the sector, in bytes
* 'data': array of dwords
* 'pattern': dword pattern to use for empty or partial sectors (null for unread remote sectors)
*
* In addition, we will maintain the following information on a per-sector basis,
* as sectors are modified:
*
* iModify: index of first modified dword in sector
* cModify: number of modified dwords in sector
* fDirty: true if sector is dirty, false if clean (or cleaning in progress)
*
* @param {Object} sector
* @param {number} iCylinder
* @param {number} iHead

View file

@ -143,19 +143,19 @@ function FDC(parmsFDC) {
this['dmaWrite'] = this.dmaWrite;
this['dmaFormat'] = this.dmaFormat;
this.pAutoMount = null;
this.configAutoMount = null;
if (parmsFDC['autoMount']) {
this.pAutoMount = parmsFDC['autoMount'];
if (typeof this.pAutoMount == "string") {
this.configAutoMount = parmsFDC['autoMount'];
if (typeof this.configAutoMount == "string") {
try {
/*
* The most likely source of any exception will be right here, where we're parsing
* the JSON-encoded diskette data.
*/
this.pAutoMount = eval("(" + parmsFDC['autoMount'] + ")");
this.configAutoMount = eval("(" + parmsFDC['autoMount'] + ")");
} catch (e) {
Component.error("FDC auto-mount error: " + e.message + " (" + parmsFDC['autoMount'] + ")");
this.pAutoMount = null;
this.configAutoMount = null;
}
}
}
@ -412,117 +412,109 @@ FDC.aCmdInfo = {
*/
FDC.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
{
var fdc = this;
switch (sBinding) {
case "listDisks":
this.bindings[sBinding] = control;
/*
* Add the special path of "?" to the list, which will prompt the user for a URL.
*/
var controlOption = window.document.createElement("option");
controlOption['value'] = "?";
controlOption.innerHTML = "User-defined URL...";
case "listDisks":
this.bindings[sBinding] = control;
var addControlOption = function(sValue, sDisplay) {
var controlOption;
controlOption = window.document.createElement("option");
controlOption['value'] = sValue;
controlOption.innerHTML = sDisplay;
control.appendChild(controlOption);
/*
* Now add an 'onchange' handler.
*/
control.onchange = function(fdc, controlDisks) {
return function onChangeListDisks() {
var controlDesc = fdc.bindings["descDisk"];
if (controlDesc) {
var controlOption = controlDisks.options[controlDisks.selectedIndex];
if (controlOption) {
var dataValue = {};
var sValue = controlOption.getAttribute("data-value");
if (sValue) {
try {
dataValue = eval("({" + sValue + "})");
} catch (e) {
Component.error("FDC option error: " + (e.message || e));
}
}
var sDesc = dataValue['desc'];
if (sDesc === undefined) sDesc = "";
var sHRef = dataValue['href'];
if (sHRef !== undefined) sDesc = "<a href=\"" + sHRef + "\" target=\"_blank\">" + sDesc + "</a>";
controlDesc.innerHTML = sDesc;
}
};
addControlOption("?", "Remote Disk");
control.onchange = function onChangeListDisks(event) {
var controlDesc = fdc.bindings["descDisk"];
var controlOption = control.options[control.selectedIndex];
if (controlDesc && controlOption) {
var dataValue = {};
var sValue = controlOption.getAttribute("data-value");
if (sValue) {
try {
dataValue = eval("({" + sValue + "})");
} catch (e) {
Component.error("FDC option error: " + e.message);
}
};
}(this, control);
return true;
}
var sDesc = dataValue['desc'];
if (sDesc === undefined) sDesc = "";
var sHRef = dataValue['href'];
if (sHRef !== undefined) sDesc = "<a href=\"" + sHRef + "\" target=\"_blank\">" + sDesc + "</a>";
controlDesc.innerHTML = sDesc;
}
};
return true;
case "descDisk":
case "listDrives":
case "descDisk":
case "listDrives":
this.bindings[sBinding] = control;
/*
* I tried going with onclick instead of onchange, so that if you wanted to confirm what's
* loaded in a particular drive, you could click the drive control without having to change it.
* However, that doesn't seem to work for all browsers, so I've reverted to onchange.
*/
control.onchange = function onChangeListDrives(event) {
var iDrive = str.parseInt(control.value, 10);
if (iDrive != null) fdc.displayDiskette(iDrive);
};
return true;
case "loadDrive":
this.bindings[sBinding] = control;
control.onclick = function onClickLoadDrive(event) {
var controlDisks = fdc.bindings["listDisks"];
if (controlDisks) {
var sDisketteName = controlDisks.options[controlDisks.selectedIndex].text;
var sDiskettePath = controlDisks.value;
fdc.loadSelectedDrive(sDisketteName, sDiskettePath);
}
};
return true;
case "loadLocal":
/*
* Check for availability of FileReader
*/
if (window.FileReader && window.File && window.FileList && window.Blob ) {
this.bindings[sBinding] = control;
/*
* I tried going with onclick instead of onchange, so that if you wanted to confirm what's
* loaded in a particular drive, you could click the drive control without having to change it.
* However, that doesn't seem to work for all browsers, so I've reverted to onchange.
* Enable "Load Local File" button only if a file is actually selected
*/
control.onchange = function(fdc, controlDrives) {
return function onChangeListDrives() {
var iDrive = parseInt(controlDrives.value, 10);
if (!isNaN(iDrive)) fdc.displayDiskette(iDrive);
};
}(this, control);
return true;
control.addEventListener('change', function() {
var fieldset = control.children[0];
var files = fieldset.children[0].files;
var submit = fieldset.children[1];
submit.disabled = (files.length == 0);
});
case "loadDrive":
this.bindings[sBinding] = control;
control.onclick = function(fdc) {
return function onClickLoadDrive() {
var iDrive;
var controlDisks = fdc.bindings["listDisks"];
var controlDrives = fdc.bindings["listDrives"];
if (controlDisks && controlDrives && !isNaN(iDrive = parseInt(controlDrives.value, 10)) && iDrive >= 0 && iDrive < fdc.aDrives.length) {
var sDiskettePath = controlDisks.value;
if (!sDiskettePath) {
fdc.unloadDrive(iDrive);
return;
}
var sDisketteName = controlDisks.options[controlDisks.selectedIndex].text;
control.onsubmit = function(event) {
var file = event.currentTarget[1].files[0];
if (file) {
var sDiskettePath = file.name;
var sDisketteName = str.getBaseName(sDiskettePath, true);
fdc.loadSelectedDrive(sDisketteName, sDiskettePath, file);
}
/*
* Prevent reloading of web page after form submission
*/
return false;
};
}
else {
this.println("FileReader support not available, disabling local file load");
control.parentNode.removeChild(control);
}
return true;
/*
* If the special path of "?" is selected, then we want to prompt the user for a URL. Oh, and
* make sure we pass an empty string as the 2nd parameter to prompt(), so that IE won't display
* "undefined" -- because after all, undefined and "undefined" are EXACTLY the same thing, right?
*
* TODO: This is literally all I've done to support external disk images. There's probably more
* I should do, like dynamically updating "listDisks" to include new entries, and adding new entries
* to the save/restore data.
*/
if (sDiskettePath == "?") {
sDiskettePath = window.prompt("Enter the URL of a disk image to load.", "");
if (!sDiskettePath)
return;
sDisketteName = str.getBaseName(sDiskettePath);
fdc.println("Attempting to load " + sDiskettePath + " as \"" + sDisketteName + "\"");
}
while (fdc.loadDiskette(iDrive, sDisketteName, sDiskettePath, false)) {
if (!window.confirm("Click OK to reload the original disk.\n(WARNING: All disk changes will be discarded)")) {
return;
}
/*
* So here's the story: loadDiskette() returned true, which it does ONLY if the specified disk is already
* mounted, AND the user clicked OK to reload the original disk image. So we must toss any history we have
* for the disk, unload it, and then loop back around to loadDiskette().
*
* loadDiskette() should NEVER return true the second time, since no disk is loaded. In other words, this
* isn't really a loop so much as a one-time retry operation.
*/
fdc.removeDiskHistory(sDisketteName, sDiskettePath);
fdc.unloadDrive(iDrive, false, true);
}
return;
}
fdc.notice("Nothing to load");
};
}(this);
return true;
default:
break;
default:
break;
}
return false;
};
@ -1143,17 +1135,17 @@ FDC.prototype.seekDrive = function(drive, iSector, nSectors)
FDC.prototype.autoMount = function(fRemount)
{
if (!fRemount) this.cAutoMount = 0;
if (this.pAutoMount) {
for (var sDrive in this.pAutoMount) {
var pDriveConfig = this.pAutoMount[sDrive];
if (pDriveConfig['name'] && pDriveConfig['path']) {
if (this.configAutoMount) {
for (var sDrive in this.configAutoMount) {
var configDrive = this.configAutoMount[sDrive];
if (configDrive['name'] && configDrive['path']) {
/*
* WARNING: This conversion of drive letter to drive number, starting with A:, is very simplistic
* and is not guaranteed to match the drive mapping that DOS ultimately uses.
*/
var iDrive = sDrive.charCodeAt(0) - 0x41;
if (iDrive >= 0 && iDrive < this.aDrives.length) {
if (!this.loadDiskette(iDrive, pDriveConfig['name'], pDriveConfig['path'], true) && fRemount)
if (!this.loadDiskette(iDrive, configDrive['name'], configDrive['path'], true) && fRemount)
this.setReady(false);
continue;
}
@ -1165,18 +1157,75 @@ FDC.prototype.autoMount = function(fRemount)
};
/**
* loadDiskette(iDrive, sDisketteName, sDiskettePath, fAutoMount)
* loadSelectedDrive(sDisketteName, sDiskettePath, file)
*
* @this {FDC}
* @param {string} sDisketteName
* @param {string} sDiskettePath
* @param {File} [file] is set if there's an associated File object
*/
FDC.prototype.loadSelectedDrive = function(sDisketteName, sDiskettePath, file)
{
var iDrive;
var controlDrives = this.bindings["listDrives"];
if (controlDrives && !isNaN(iDrive = parseInt(controlDrives.value, 10)) && iDrive >= 0 && iDrive < this.aDrives.length) {
if (!sDiskettePath) {
this.unloadDrive(iDrive);
return;
}
/*
* If the special path of "?" is selected, then we want to prompt the user for a URL. Oh, and
* make sure we pass an empty string as the 2nd parameter to prompt(), so that IE won't display
* "undefined" -- because after all, undefined and "undefined" are EXACTLY the same thing, right?
*
* TODO: This is literally all I've done to support remote disk images. There's probably more
* I should do, like dynamically updating "listDisks" to include new entries, and adding new entries
* to the save/restore data.
*/
if (sDiskettePath == "?") {
sDiskettePath = window.prompt("Enter the URL of a remote disk image.", "") || "";
if (!sDiskettePath) return;
sDisketteName = str.getBaseName(sDiskettePath);
this.println("Attempting to load " + sDiskettePath + " as \"" + sDisketteName + "\"");
}
this.println("loading disk " + sDiskettePath + "...");
while (this.loadDiskette(iDrive, sDisketteName, sDiskettePath, false, file)) {
if (!window.confirm("Click OK to reload the original disk.\n(WARNING: All disk changes will be discarded)")) {
return;
}
/*
* So here's the story: loadDiskette() returned true, which it does ONLY if the specified disk is already
* mounted, AND the user clicked OK to reload the original disk image. So we must toss any history we have
* for the disk, unload it, and then loop back around to loadDiskette().
*
* loadDiskette() should NEVER return true the second time, since no disk is loaded. In other words,
* this isn't really a loop so much as a one-time retry operation.
*/
this.removeDiskHistory(sDisketteName, sDiskettePath);
this.unloadDrive(iDrive, false, true);
}
return;
}
this.notice("Nothing to load");
};
/**
* loadDiskette(iDrive, sDisketteName, sDiskettePath, fAutoMount, file)
*
* NOTE: If sDiskettePath is already loaded in the drive, nothing needs to be done.
*
* @this {FDC}
* @param {number} iDrive (pre-validated)
* @param {number} iDrive
* @param {string} sDisketteName
* @param {string|null} sDiskettePath
* @param {boolean} fAutoMount
* @param {string} sDiskettePath
* @param {boolean} [fAutoMount]
* @param {File} [file] is set if there's an associated File object
* @return {boolean} true if diskette (already) loaded, false if queued up (or busy)
*/
FDC.prototype.loadDiskette = function(iDrive, sDisketteName, sDiskettePath, fAutoMount)
FDC.prototype.loadDiskette = function(iDrive, sDisketteName, sDiskettePath, fAutoMount, file)
{
var drive = this.aDrives[iDrive];
if (sDiskettePath && drive.sDiskettePath != sDiskettePath) {
@ -1192,7 +1241,7 @@ FDC.prototype.loadDiskette = function(iDrive, sDisketteName, sDiskettePath, fAut
this.messageDebugger("loading diskette '" + sDisketteName + "'");
}
var disk = new Disk(this, drive, DiskAPI.MODE.PRELOAD);
disk.load(sDisketteName, sDiskettePath, this.doneLoadDiskette);
disk.load(sDisketteName, sDiskettePath, file, this.doneLoadDiskette);
return false;
}
return true;

View file

@ -1151,7 +1151,7 @@ HDC.prototype.loadDisk = function(iDrive, sDiskName, sDiskPath, fAutoMount)
this.messageDebugger("loading " + sDiskName);
}
var disk = drive.disk || new Disk(this, drive, drive.mode);
disk.load(sDiskName, sDiskPath, this.doneLoadDisk);
disk.load(sDiskName, sDiskPath, null, this.doneLoadDisk);
return false;
};

View file

@ -135,9 +135,6 @@ function Memory(addr, size, fReadOnly, controller) {
*/
if (TYPEDARRAYS) {
this.buffer = new ArrayBuffer(size);
/**
* @type {DataView}
*/
this.dv = new DataView(this.buffer, 0, size);
/*
* We could also use dv.getUint8() and dv.setUint8(), but using ab[] to get/set bytes

View file

@ -356,6 +356,14 @@
<xsl:when test="@type = 'heading'">
<div><xsl:value-of select="."/></div>
</xsl:when>
<xsl:when test="@type = 'file'">
<form class="{$APPCLASS}-{@class}" data-value="{$type},{$binding}">
<fieldset>
<input type="file"/>
<input type="submit" value="Mount" disabled="true"/>
</fieldset>
</form>
</xsl:when>
<xsl:when test="@type = 'separator'">
<hr/>
</xsl:when>