mirror of
https://github.com/pkimpel/retro-b5500.git
synced 2026-01-12 00:42:59 +00:00
2172 lines
90 KiB
HTML
2172 lines
90 KiB
HTML
<!DOCTYPE html>
|
|
<html manifest="B5500Manifest.appcache">
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
<title>B5500 Disk Subsystem Coldstart Loader</title>
|
|
<!--
|
|
/***********************************************************************
|
|
* retro-b5500/webUI B5500ColdLoader.html
|
|
************************************************************************
|
|
* Copyright (c) 2012, Paul Kimpel.
|
|
* Licensed under the MIT License,
|
|
* see http://www.opensource.org/licenses/mit-license.php
|
|
************************************************************************
|
|
* B5500 Disk Subsystem Coldstart Loader.
|
|
*
|
|
* This script opens an IndexedDB database in the browser (creating it first, if
|
|
* necessary) and initializes it as a B5500 Head-per-Track disk file subsystem.
|
|
* It creates, as necessary, a separate IDB object store for for the number of
|
|
* Electronics Units specified by the "euSet" constant, each consisting of the number
|
|
* of 30-word segments (sectors) specified by the "EUn" properties of "euSet".
|
|
*
|
|
* If the ColdStart button is clicked on the page, the disk directory structure on
|
|
* EU0 is overwritten with a new, empty directory structure, and a default set of
|
|
* the system parameters is created.
|
|
*
|
|
* Once the disk subsystem has been coldstarted, you can load files from a local
|
|
* file on your workstation. You can run the loader to load files without cold-
|
|
* starting first, but the disk subsystem must have been previously coldstarted
|
|
* in order to load any files.
|
|
*
|
|
* To load files, use the file picker on the upper-right of the page (usually
|
|
* identified by a "Browse" or "Choose File" button) and select a blob file that
|
|
* represents a Burroughs B5500 Library/Maintenance tape.
|
|
*
|
|
* The blob is assumed to be in the so-called ".bcd" format. Each 7-bit frame
|
|
* from the tape is represented as one 8-bit unsigned byte. The low-order six
|
|
* bits (mask 0x3F) contain the character value. The next bit (mask 0x40) is
|
|
* the odd parity bit, and the high-order bit (mask 0x80) indicates the byte is
|
|
* at the start of a physical tape block. Tape marks (EOF) are indicated by a
|
|
* block containing a single 0x8F byte.
|
|
*
|
|
* After selecting the file, the page will display the file directory on the tape.
|
|
* Each file name has a checkbox to its left that will select that file as one to
|
|
* be loaded. Radio buttons on the right may be used optionally to designate one
|
|
* of the selected files as the MCP and another as the System Intrinsics (INT).
|
|
* Marking a file as the MCP or Intrinsics causes the bootstrap parameters in
|
|
* sector 0 of DKA EU0 to be updated accordingly.
|
|
*
|
|
* Once you have selected the files to load, click the "Load" button at the bottom
|
|
* of the page. The page will output a log of the load activity at the end of the
|
|
* page.
|
|
*
|
|
* The files to be loaded must be on one contiguous tape image, contained in one
|
|
* blob file. Continuation "reels" are not currently supported.
|
|
************************************************************************
|
|
* 2012-12-29 P.Kimpel
|
|
* Original version, from B5500LibMaintExtract.html.
|
|
***********************************************************************/
|
|
-->
|
|
<meta name="Author" content="Paul Kimpel">
|
|
<meta http-equiv="Content-Script-Type" content="text/javascript">
|
|
<meta http-equiv="Content-Style-Type" content="text/css">
|
|
<link id=defaultStyleSheet rel=stylesheet type="text/css" href="B5500Common.css">
|
|
|
|
<script>
|
|
"use strict";
|
|
|
|
if (!window.indexedDB) { // for Safari, mostly
|
|
window.indexedDB = window.webkitIndexedDB || window.mozIndexedDB;
|
|
}
|
|
|
|
window.addEventListener("load", function() {
|
|
var configName = "CONFIG"; // database configuration store name
|
|
var dbName = "B5500DiskUnit"; // IDB database name
|
|
var directoryTop = 2000; // start of directory area
|
|
var directoryEnd = 3008; // end of directory area
|
|
var euSize = 200000; // model I size (5 Storage Units: 6MW or 48MC)
|
|
var euPrefix = "EU"; // prefix for EU object store names
|
|
var tapeMark = 0x8F; // .bcd file tapemark (EOF) octet code
|
|
|
|
var availDisk = null; // available disk space map
|
|
var config = null; // copy of CONFIG store contents
|
|
var db = null; // the IDB database object
|
|
var panel = document.getElementById("TextPanel");
|
|
var tapeBlob = null; // blob read from .bcd file
|
|
var tapeData = null; // tape blob as a DataView
|
|
var tapeDir = []; // contents of tape directory from .bcd blob
|
|
|
|
var euSet = {EU0: euSize, EU1: euSize};
|
|
|
|
var tapeCtl = {
|
|
data: null,
|
|
offset: 0,
|
|
dataLength: -1,
|
|
eof: false,
|
|
eot: false,
|
|
blockCount: 0,
|
|
blockLength: 0};
|
|
|
|
var BICtoANSIChar = [ // Index by 6-bit BIC to get ANSI character
|
|
"0", "1", "2", "3", "4", "5", "6", "7",
|
|
"8", "9", "#", "@", "?", ":", ">", "}",
|
|
"+", "A", "B", "C", "D", "E", "F", "G",
|
|
"H", "I", ".", "[", "&", "(", "<", "~",
|
|
"|", "J", "K", "L", "M", "N", "O", "P",
|
|
"Q", "R", "$", "*", "-", ")", ";", "{",
|
|
" ", "/", "S", "T", "U", "V", "W", "X",
|
|
"Y", "Z", ",", "%", "!", "=", "]", "\""];
|
|
|
|
var BICtoANSI = [ // Index by 6-bit BIC to get 8-bit ANSI code
|
|
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37, // 00-07, @00-07
|
|
0x38,0x39,0x23,0x40,0x3F,0x3A,0x3E,0x7D, // 08-1F, @10-17
|
|
0x2B,0x41,0x42,0x43,0x44,0x45,0x46,0x47, // 10-17, @20-27
|
|
0x48,0x49,0x2E,0x5B,0x26,0x28,0x3C,0x7E, // 18-1F, @30-37
|
|
0x7C,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50, // 20-27, @40-47
|
|
0x51,0x52,0x24,0x2A,0x2D,0x29,0x3B,0x7B, // 28-2F, @50-57
|
|
0x20,0x2F,0x53,0x54,0x55,0x56,0x57,0x58, // 30-37, @60-67
|
|
0x59,0x5A,0x2C,0x25,0x21,0x3D,0x5D,0x22]; // 38-3F, @70-77
|
|
|
|
var BICtoBCLANSI = [ // Index by 6-bit BIC to get 8-bit BCL-as-ANSI code
|
|
0x23,0x31,0x32,0x33,0x34,0x35,0x36,0x37, // 00-07, @00-07
|
|
0x38,0x39,0x40,0x3F,0x30,0x3A,0x3E,0x7D, // 08-1F, @10-17
|
|
0x2C,0x2F,0x53,0x54,0x55,0x56,0x57,0x58, // 10-17, @20-27
|
|
0x59,0x5A,0x25,0x21,0x20,0x3D,0x5D,0x22, // 18-1F, @30-37
|
|
0x24,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50, // 20-27, @40-47
|
|
0x51,0x52,0x2A,0x2D,0x7C,0x29,0x3B,0x7B, // 28-2F, @50-57
|
|
0x2B,0x41,0x42,0x43,0x44,0x45,0x46,0x47, // 30-37, @60-67
|
|
0x48,0x49,0x5B,0x26,0x2E,0x28,0x3C,0x7E]; // 38-3F, @70-77
|
|
|
|
var ANSItoBIC = [ // Index by 8-bit ANSI to get 6-bit BIC (upcased, invalid=>"?")
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 00-0F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 10-1F
|
|
0x30,0x3C,0x3F,0x0A,0x2A,0x3B,0x1C,0x0C,0x1D,0x2D,0x2B,0x10,0x3A,0x2C,0x1A,0x31, // 20-2F
|
|
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0D,0x2E,0x1E,0x3D,0x0E,0x0C, // 30-3F
|
|
0x0B,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x21,0x22,0x23,0x24,0x25,0x26, // 40-4F
|
|
0x27,0x28,0x29,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x1B,0x0C,0x3E,0x0C,0x0C, // 50-5F
|
|
0x0C,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x21,0x22,0x23,0x24,0x25,0x26, // 60-6F
|
|
0x27,0x28,0x29,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x2F,0x20,0x0F,0x1F,0x0C, // 70-7F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 80-8F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 90-9F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // A0-AF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // B0-BF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // C0-CF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // D0-DF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // E0-EF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C]; // F0-FF
|
|
|
|
var BCLANSItoBIC = [ // Index by 8-bit BCL-as-ANSI to get 6-bit BIC (upcased, invalid=>"?")
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 00-0F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 10-1F
|
|
0x1C,0x1B,0x1F,0x00,0x20,0x1A,0x3B,0x0C,0x3D,0x2D,0x2A,0x30,0x10,0x2B,0x3C,0x11, // 20-2F
|
|
0x0C,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0D,0x2E,0x3E,0x1D,0x0E,0x0B, // 30-3F
|
|
0x0A,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x21,0x22,0x23,0x24,0x25,0x26, // 40-4F
|
|
0x27,0x28,0x29,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x3A,0x0C,0x1E,0x0C,0x0C, // 50-5F
|
|
0x0C,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x21,0x22,0x23,0x24,0x25,0x26, // 60-6F
|
|
0x27,0x28,0x29,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x2F,0x2C,0x0F,0x3F,0x0C, // 70-7F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 80-8F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 90-9F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // A0-AF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // B0-BF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // C0-CF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // D0-DF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // E0-EF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C]; // F0-FF
|
|
|
|
var pow2 = [ // powers of 2 from 0 to 52
|
|
0x1, 0x2, 0x4, 0x8,
|
|
0x10, 0x20, 0x40, 0x80,
|
|
0x100, 0x200, 0x400, 0x800,
|
|
0x1000, 0x2000, 0x4000, 0x8000,
|
|
0x10000, 0x20000, 0x40000, 0x80000,
|
|
0x100000, 0x200000, 0x400000, 0x800000,
|
|
0x1000000, 0x2000000, 0x4000000, 0x8000000,
|
|
0x10000000, 0x20000000, 0x40000000, 0x80000000,
|
|
0x100000000, 0x200000000, 0x400000000, 0x800000000,
|
|
0x1000000000, 0x2000000000, 0x4000000000, 0x8000000000,
|
|
0x10000000000, 0x20000000000, 0x40000000000, 0x80000000000,
|
|
0x100000000000, 0x200000000000, 0x400000000000, 0x800000000000,
|
|
0x1000000000000, 0x2000000000000, 0x4000000000000, 0x8000000000000,
|
|
0x10000000000000];
|
|
|
|
/**************************************/
|
|
function $$(id) {
|
|
return document.getElementById(id);
|
|
}
|
|
|
|
/**************************************/
|
|
function bit(word, bit) {
|
|
/* Extracts and returns the specified bit from the word */
|
|
var e = 47-bit; // word lower power exponent
|
|
var p; // bottom portion of word power of 2
|
|
|
|
if (e > 0) {
|
|
return ((word - word % (p = pow2[e]))/p) % 2;
|
|
} else {
|
|
return word % 2;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
function fieldIsolate(word, start, width) {
|
|
/* Extracts a bit field [start:width] from word and returns the field */
|
|
var le = 48-start-width; // lower power exponent
|
|
var p; // bottom portion of word power of 2
|
|
|
|
return (le == 0 ? word : (word - word % (p = pow2[le]))/p) % pow2[width];
|
|
};
|
|
|
|
/**************************************/
|
|
function spout(text) {
|
|
/* Appends "text"+NL as a new text node to the panel DOM element */
|
|
var e = document.createTextNode(text + "\n");
|
|
|
|
panel.appendChild(e);
|
|
$$("PageBottom").scrollIntoView();
|
|
}
|
|
|
|
/**************************************/
|
|
function clearPanel() {
|
|
/* Clears the text panel */
|
|
var kid;
|
|
|
|
while (kid = panel.firstChild) {
|
|
panel.removeChild(kid);
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function parseNumber(s) {
|
|
/* Parses the string "s" as a base-10 number. Returns 0 if it is not a number */
|
|
var n = parseInt(s, 10);
|
|
|
|
return (isNaN(n) ? 0 : n);
|
|
}
|
|
|
|
/**************************************/
|
|
function rtrim(s) {
|
|
/* Trims trailing spaces from "s" and returns the resulting string */
|
|
var m = s.match(/^(.*?) *$/);
|
|
|
|
return m[1];
|
|
}
|
|
|
|
/**************************************/
|
|
function padToLength(text, len) {
|
|
/* Converts the input string "text" to exactly "len" characters,
|
|
truncating or padding on the right with spaces as necessary */
|
|
var x = text.length;
|
|
|
|
if (x > len) {
|
|
return text.substring(0, len);
|
|
} else {
|
|
x = len-x;
|
|
while (x-- > 0) {
|
|
text += " ";
|
|
}
|
|
return text;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function stringToANSI(text, bytes, bx, asBinary) {
|
|
/* Translates the characters in a string to upper case, and then to ANSI
|
|
byte-array format. "text" is the input string, "bytes" is the Uint8Array
|
|
output buffer, and "bx" is the offset into that output buffer. If "asBinary" is
|
|
truthy, the translation is binary, otherwise it is done as BCLANSI */
|
|
var len = text.length;
|
|
var table1 = (asBinary ? BICtoANSI : BICtoBCLANSI);
|
|
var utxt = text.toUpperCase();
|
|
var x;
|
|
|
|
bx = bx || 0;
|
|
for (x=0; x<len; x++) {
|
|
bytes[bx++] = table1[ANSItoBIC[utxt.charCodeAt(x) & 0xFF]];
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function ANSItoString(bytes, bx, bLength, asBinary) {
|
|
/* Translates a portion of an ANSI byte array to a string and returns it.
|
|
"bytes" = the Uint8Array byte array
|
|
"bx" = 0-relative offset into "bytes"
|
|
"bLength" = number of bytes to translate
|
|
"asBinary" = if truthy, then binary translation is done; otherwise
|
|
B5500 BCLANSI translation is done */
|
|
var table = (asBinary ? ANSItoBIC : BCLANSItoBIC);
|
|
var text = "";
|
|
var x;
|
|
|
|
if (bLength < 0) {
|
|
bLength = -bLength;
|
|
}
|
|
for (x=0; x<bLength; x++) {
|
|
text += BICtoANSIChar[table[bytes[bx+x]]];
|
|
}
|
|
return text;
|
|
}
|
|
|
|
/**************************************/
|
|
function wordsToANSI(words, wx, wLength, bytes, bx, asBinary) {
|
|
/* Translates an array of B5500 words to ANSI byte-array format.
|
|
"words" = the array of words
|
|
"wx" = the starting index in "words"
|
|
"wLength" = the number of words to translate
|
|
"bytes" = a Uint8Array array
|
|
"bx" = the starting index in "bytes" to store the translated data
|
|
"asBinary" = if truthy, then binary translation is done; otherwise
|
|
B5500 BCLANSI translation is done */
|
|
var c;
|
|
var table = (asBinary ? BICtoANSI : BICtoBCLANSI);
|
|
var w;
|
|
var x;
|
|
var y;
|
|
var z;
|
|
|
|
bx = bx || 0;
|
|
if (wLength < 0) {
|
|
wLength = -wLength;
|
|
}
|
|
for (x=0; x<wLength; x++) {
|
|
w = words[wx+x] || 0;
|
|
for (y=0; y<8; y++) {
|
|
z = w % 0x40000000000;
|
|
c = (w-z)/0x40000000000;
|
|
bytes[bx++] = table[c];
|
|
w = z*64;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function ANSItoWords(bytes, bx, bLength, words, wx, asBinary) {
|
|
/* Translates a portion of an ANSI byte array to a sequence of B5500 words.
|
|
"bytes" = the Uint8Array byte array
|
|
"bx" = 0-relative offset into "bytes"
|
|
"bLength" = number of bytes to translate
|
|
"words" = the word array
|
|
"wx" = 0-relative offset into "words" to store the translated data
|
|
"asBinary" = if truthy, then binary translation is done; otherwise
|
|
B5500 BCLANSI translation is done */
|
|
var cx = 0;
|
|
var w = 0;
|
|
var table = (asBinary ? ANSItoBIC : BCLANSItoBIC);
|
|
var x;
|
|
|
|
wx = wx || 0;
|
|
if (bLength < 0) {
|
|
bLength = -bLength;
|
|
}
|
|
for (x=0; x<bLength; x++) {
|
|
if (cx >= 8) {
|
|
words[wx++] = w;
|
|
w = cx = 0;
|
|
}
|
|
w = w*64 + table[bytes[bx+x]];
|
|
cx++;
|
|
}
|
|
while (cx++ < 8) {
|
|
w *= 64;
|
|
}
|
|
words[wx++] = w;
|
|
}
|
|
|
|
/**************************************/
|
|
function makeAvailable(addr, size) {
|
|
/* Returns "size" segments starting at "addr" to the "availDisk" map.
|
|
No attempt at space consolidate is made, presently, since it is likely
|
|
we are replacing the same file with the same size rows */
|
|
|
|
availDisk.push({addr: addr, size: size});
|
|
}
|
|
|
|
/**************************************/
|
|
function removeAvailable(addr, size) {
|
|
/* Removes "size" segments starting at "addr" from the "availDisk" map */
|
|
var avAddr;
|
|
var avEnd;
|
|
var endAddr = addr+size-1;
|
|
var ex;
|
|
var found = false;
|
|
var mx;
|
|
|
|
if (endAddr >= directoryEnd+4) {
|
|
if (addr < directoryEnd+4) {
|
|
addr = directoryEnd+4;
|
|
size = endAddr - addr + 1;
|
|
}
|
|
|
|
for (mx=availDisk.length-1; mx>=0; mx--) {
|
|
avAddr = availDisk[mx].addr;
|
|
avEnd = availDisk[mx].size + avAddr - 1;
|
|
if (avAddr <= addr && avEnd >= endAddr) {
|
|
found = true;
|
|
if (avAddr == addr) { // remove space from the front of this entry
|
|
availDisk[mx].addr += size;
|
|
availDisk[mx].size -= size;
|
|
} else if (avEnd == endAddr) { // remove space from the end
|
|
availDisk[mx].size -= size;
|
|
} else { // remove space from the middle
|
|
// See if we can find a zero-length entry to reuse for the second area
|
|
for (ex=availDisk.length-1; ex>=0; ex--) {
|
|
if (availDisk[ex].size == 0) {
|
|
break;
|
|
}
|
|
}
|
|
if (ex <= 0) {
|
|
ex = availDisk.length;
|
|
availDisk.push({});
|
|
}
|
|
availDisk[mx].size = addr-avAddr;
|
|
availDisk[ex].addr = endAddr+1;
|
|
availDisk[ex].size = avEnd-endAddr;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
alert("removeAvailable: No map entry covers address " + addr + " for " + size + " segments");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function findDiskSpace(size) {
|
|
/* Searches the "availDisk" map for an available area of sufficient size,
|
|
removes it from the map, and returns the starting address. Returns -1 if no
|
|
suitable area can be found */
|
|
var addr;
|
|
var avSize;
|
|
var mx;
|
|
var bestx = -1;
|
|
var bestSize = 0;
|
|
|
|
for (mx=availDisk.length-1; mx>=0; mx--) {
|
|
avSize = availDisk[mx].size;
|
|
if (avSize >= size) {
|
|
if (avSize == size) { // we will take this one
|
|
bestx = mx;
|
|
break;
|
|
} else if (bestx < 0 || bestSize > avSize) {
|
|
bestx = mx;
|
|
bestSize = avSize;
|
|
}
|
|
}
|
|
}
|
|
if (bestx < 0) {
|
|
return -1; // no area found
|
|
} else {
|
|
addr = availDisk[bestx].addr;
|
|
availDisk[bestx].addr += size;
|
|
availDisk[bestx].size -= size;
|
|
return addr;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function readDiskBlock(eu, addr, segs, block, callback) {
|
|
/* Reads a block from the disk "eu" at "addr" for "segs" segments, translates
|
|
it to words in the "block" array, then calls "callback" passing the address
|
|
and block */
|
|
var bx = 0;
|
|
var nextAddr = addr;
|
|
var range = IDBKeyRange.bound(addr, addr+segs-1);
|
|
var req = eu.openCursor(range);
|
|
var x;
|
|
|
|
req.onsuccess = function(ev) {
|
|
var cursor = ev.target.result;
|
|
|
|
if (cursor) {
|
|
while (cursor.key > nextAddr) {
|
|
for (x=0; x<30; x++) {
|
|
block[bx++] = 0;
|
|
}
|
|
nextAddr++;
|
|
}
|
|
ANSItoWords(cursor.value, 0, 240, block, bx);
|
|
bx += 30;
|
|
nextAddr++;
|
|
cursor.continue();
|
|
} else {
|
|
while (nextAddr < addr+segs) {
|
|
for (x=0; x<30; x++) {
|
|
block[bx++] = 0;
|
|
}
|
|
nextAddr++;
|
|
}
|
|
callback(addr, block);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**************************************/
|
|
function writeDiskWords(eu, addr, words, wx, wLength) {
|
|
/* Translates the B5500 words in "words" starting at "wx" for "wLength" words
|
|
to BCLANSI and writes the block one segment at a time to the "eu", starting at
|
|
segment "addr" */
|
|
var segment = new Uint8Array(240);
|
|
|
|
while (wLength > 30) {
|
|
wordsToANSI(words, wx, 30, segment, 0);
|
|
eu.put(segment, addr);
|
|
addr++;
|
|
wLength -= 30;
|
|
wx += 30;
|
|
}
|
|
wordsToANSI(words, wx, wLength, segment, 0);
|
|
for (wx=wLength*8; wx<240; wx++) {
|
|
segment[wx] = 0x26; // fill partial seg with ASCII "&" => BCL " " => BIC @60
|
|
}
|
|
eu.put(segment, addr);
|
|
}
|
|
|
|
/**************************************/
|
|
function readTextBlock(ctl) {
|
|
/* Reads the next block from the tape, translating the character frames to ANSI
|
|
character codes and returning the data as a string. A block is terminated when
|
|
the next frame has its high-order bit set, or the end of the data is reached.
|
|
The string returned is always at least one character in length, unless the block
|
|
is a tapeMark (in which case the "eof" property is set) or the end of the data
|
|
has been reached (in which case the "eof" and "eot" properties are set) */
|
|
var c;
|
|
var data = ctl.data;
|
|
var limit = ctl.dataLength;
|
|
var text = "";
|
|
var x = ctl.offset;
|
|
|
|
if (x >= limit) {
|
|
ctl.eof = true;
|
|
ctl.eot = true;
|
|
ctl.blockLength = 0;
|
|
} else {
|
|
c = data.getUint8(x);
|
|
if (c == tapeMark) {
|
|
ctl.eof = true;
|
|
ctl.offset = x+1;
|
|
ctl.blockLength = 0;
|
|
} else {
|
|
do {
|
|
text += BICtoANSIChar[c & 0x3F];
|
|
if (++x < limit) {
|
|
c = data.getUint8(x);
|
|
} else {
|
|
c = tapeMark; // to kill the loop
|
|
}
|
|
} while (c < 0x80);
|
|
ctl.eof = false;
|
|
ctl.blockLength = x - ctl.offset;
|
|
ctl.offset = x;
|
|
ctl.blockCount++;
|
|
}
|
|
}
|
|
return text;
|
|
}
|
|
|
|
/**************************************/
|
|
function readWordBlock(ctl) {
|
|
/* Reads the next block from the tape, translating the character frames to an array
|
|
of B5500 binary words and returning the array. A block is terminated when
|
|
the next frame has its high-order bit set, or the end of the data is reached.
|
|
The array returned is always at least one element in length, unless the block
|
|
is a tapeMark (in which case the "eof" property is set) or the end of the data
|
|
has been reached (in which case the "eof" and "eot" properties are set) */
|
|
var c;
|
|
var data = ctl.data;
|
|
var limit = ctl.dataLength;
|
|
var w = 0;
|
|
var words = [];
|
|
var wx = 0;
|
|
var x = ctl.offset;
|
|
|
|
if (x >= limit) {
|
|
ctl.eof = true;
|
|
ctl.eot = true;
|
|
ctl.blockLength = 0;
|
|
} else {
|
|
c = data.getUint8(x);
|
|
if (c == tapeMark) {
|
|
ctl.eof = true;
|
|
ctl.offset = x+1;
|
|
ctl.blockLength = 0;
|
|
} else {
|
|
do {
|
|
if (wx < 8) {
|
|
w = w*64 + (c & 0x3F);
|
|
wx++;
|
|
} else {
|
|
words.push(w);
|
|
w = c & 0x3F;
|
|
wx = 1;
|
|
}
|
|
if (++x < limit) {
|
|
c = data.getUint8(x);
|
|
} else {
|
|
c = tapeMark; // to kill the loop
|
|
}
|
|
} while (c < 0x80);
|
|
|
|
// Right-justify the last word as necessary
|
|
while (wx++ < 8) {
|
|
w *= 64;
|
|
}
|
|
words.push(w);
|
|
ctl.eof = false;
|
|
ctl.blockLength = x - ctl.offset;
|
|
ctl.offset = x;
|
|
ctl.blockCount++;
|
|
}
|
|
}
|
|
return words;
|
|
}
|
|
|
|
/**************************************/
|
|
function readTapeLabel(ctl) {
|
|
/* Reads the next block from the tape and determines if it is a B5500 tape label.
|
|
If so, decodes the label into a label object and returns the object */
|
|
var rec;
|
|
var s;
|
|
|
|
var lab = {
|
|
isLabel: false,
|
|
text: "",
|
|
heading: "",
|
|
mfid: "",
|
|
fid: "",
|
|
reel: 0,
|
|
dateWritten:0,
|
|
cycle: 0,
|
|
datePurge: 0,
|
|
sentinel: 0,
|
|
blockCount: 0,
|
|
recordCount:0,
|
|
memdumpKey: 0,
|
|
tapeNumber: ""};
|
|
|
|
rec = readTextBlock(ctl);
|
|
if (!ctl.eof) {
|
|
lab.text = rec;
|
|
if (ctl.blockLength == 80 && (s = rec.substring(0, 8)) == " LABEL ") {
|
|
lab.isLabel = true;
|
|
lab.heading = s;
|
|
lab.mfid = rec.substring(9, 16);
|
|
lab.fid = rec.substring(17, 24);
|
|
lab.reel = parseNumber(rec.substring(24, 27));
|
|
lab.dateWritten = parseNumber(rec.substring(27, 32));
|
|
lab.cycle = parseNumber(rec.substring(32, 34));
|
|
lab.datePurge = parseNumber(rec.substring(34, 39));
|
|
lab.sentinel = parseNumber(rec.substring(39, 40));
|
|
lab.blockCount = parseNumber(rec.substring(40, 45));
|
|
lab.recordCount = parseNumber(rec.substring(45, 52));
|
|
lab.memdumpKey = parseNumber(rec.substring(52, 53));
|
|
lab.tapeNumber = rec.substring(53, 58);
|
|
}
|
|
}
|
|
return lab;
|
|
}
|
|
|
|
/**************************************/
|
|
function readTapeDirectory(ctl) {
|
|
/* Reads the Lib/Maint tape directory and returns and array of file names, indexed
|
|
starting at 1. If the directory is invalid, returns an empty array */
|
|
var dir = [];
|
|
var done;
|
|
var fid;
|
|
var lab;
|
|
var lab2;
|
|
var mfid;
|
|
var rec;
|
|
var w;
|
|
var x;
|
|
|
|
lab = readTapeLabel(ctl);
|
|
if (ctl.eof) {
|
|
spout("TapeDir: EOF encountered when tape label expected, block=" + ctl.blockCount);
|
|
} else if (!lab.isLabel) {
|
|
spout(lab.text);
|
|
spout("TapeDir: Above block encountered when a tape label was expected, block=" + ctl.blockCount);
|
|
} else {
|
|
dir.push(rtrim(lab.mfid) + "/" + rtrim(lab.fid)); // store the tape name in dir[0]
|
|
rec = readTextBlock(ctl);
|
|
if (!ctl.eof) {
|
|
spout("TapeDir: EOF expected after starting label, block=" + ctl.blockCount);
|
|
}
|
|
|
|
do {
|
|
rec = readTextBlock(ctl);
|
|
if (!ctl.eof) {
|
|
x = 0;
|
|
done = false;
|
|
do {
|
|
if (x+8 > rec.length) {
|
|
spout("TapeDir: No terminating entry, block=" + ctl.blockCount + ", x=" + x);
|
|
done = true;
|
|
} else if (rec.substring(x, x+8) == "0000000?") {
|
|
done = true;
|
|
} else if (x+16 > rec.length) {
|
|
spout("TapeDir: Truncated directory entry, block=" + ctl.blockCount + ", x=" + x);
|
|
done = true;
|
|
} else {
|
|
mfid = rec.substring(x+1, x+8);
|
|
fid = rec.substring(x+9, x+16);
|
|
dir.push(rtrim(mfid) + "/" + rtrim(fid));
|
|
x += 16;
|
|
}
|
|
} while (!done);
|
|
}
|
|
} while (!ctl.eof);
|
|
|
|
lab2 = readTapeLabel(ctl);
|
|
if (!lab2.isLabel) {
|
|
spout("TapeDir: Tape label expected after directory, block=" + ctl.blockCount);
|
|
} else if (lab2.mfid != lab.mfid || lab2.fid != lab.fid) {
|
|
spout("TapeDir: Directory ending label mismatch, block=" + ctl.blockCount);
|
|
}
|
|
}
|
|
return dir;
|
|
}
|
|
|
|
/**************************************/
|
|
function readDiskHeader(ctl) {
|
|
/* Reads the next block from the tape blob and (partially) decodes it as a B5500
|
|
disk header, returning the header object */
|
|
var block;
|
|
|
|
var header = {
|
|
recordLength: 0,
|
|
blockLength: 0,
|
|
recordsPerBlock: 0,
|
|
segmentsPerBlock: 0,
|
|
logCreationDate: 0,
|
|
logCreationTime: 0,
|
|
lastAccessDate: 0,
|
|
creationDate: 0,
|
|
fileClass: 0,
|
|
fileType: 0,
|
|
recordCount: 0,
|
|
segmentsPerRow: 0,
|
|
maxRows: 0,
|
|
rowAddress: [],
|
|
words: []};
|
|
|
|
block = readWordBlock(ctl);
|
|
if (ctl.eof) {
|
|
spout("DiskHeader: EOF encountered reading header, block=" + ctl.blockCount);
|
|
} else if (block.length < 11) {
|
|
spout("DiskHeader: header too short, got " + block.length + ", block=" + ctl.blockCount);
|
|
} else {
|
|
header.recordLength = fieldIsolate(block[0], 0, 15);
|
|
header.blockLength = fieldIsolate(block[0], 15, 15);
|
|
header.recordsPerBlock = fieldIsolate(block[0], 30, 12);
|
|
header.segmentsPerBlock = fieldIsolate(block[0], 42, 6);
|
|
header.logCreationDate = fieldIsolate(block[1], 6, 18);
|
|
header.logCreationTime = fieldIsolate(block[1], 25, 23);
|
|
header.lastAccessDate = fieldIsolate(block[3], 12, 18);
|
|
header.creationDate = fieldIsolate(block[3], 30, 18);
|
|
header.fileClass = fieldIsolate(block[4], 9, 2);
|
|
header.fileType = fieldIsolate(block[4], 36, 6);
|
|
header.recordCount = block[7];
|
|
header.segmentsPerRow = block[8];
|
|
header.maxRows = fieldIsolate(block[9], 43, 5);
|
|
header.rowAddress = block.slice(10);
|
|
header.words = block; // save the raw header words
|
|
}
|
|
return header;
|
|
}
|
|
|
|
/**************************************/
|
|
function loadFileRow(ctl, eu, addr, segsPerRow) {
|
|
/* Extracts the next row from the tape blob and writes it to the "eu" disk unit */
|
|
var block;
|
|
var blockSegs;
|
|
var done = false;
|
|
var segs = 0;
|
|
var wordCount;
|
|
|
|
do {
|
|
block = readWordBlock(ctl);
|
|
if (ctl.eof) {
|
|
done = true;
|
|
} else {
|
|
wordCount = block.length;
|
|
blockSegs = Math.floor((wordCount+29)/30);
|
|
if (segs+blockSegs > segsPerRow) {
|
|
done = true;
|
|
alert("loadFileRow: row too long: " + segs);
|
|
blockSegs = segsPerRow - segs;
|
|
wordCount = blockSegs*30;
|
|
}
|
|
if (wordCount > 0) {
|
|
writeDiskWords(eu, addr, block, 0, wordCount);
|
|
addr += blockSegs;
|
|
segs += blockSegs;
|
|
}
|
|
if (segs >= segsPerRow) {
|
|
done = true;
|
|
}
|
|
}
|
|
} while (!done);
|
|
}
|
|
|
|
/**************************************/
|
|
function loadFile(ctl, fileNr, eu, diskHeader) {
|
|
/* Extracts the next file in sequence from the tape blob, converts the data
|
|
from BIC to ANSI, and writes it a row at a time to the disk "eu".
|
|
Establishes the "diskHeader" disk file header from the tape and updates the
|
|
disk header address row words as the rows are allocated and written to the disk.
|
|
Returns the disk address of the first row loaded */
|
|
var addr;
|
|
var block;
|
|
var firstRowAddr = -1;
|
|
var lab;
|
|
var lab2;
|
|
var rowCount = 0;
|
|
var tapeHeader;
|
|
var text;
|
|
var x;
|
|
|
|
lab = readTapeLabel(ctl);
|
|
if (ctl.eof) {
|
|
spout("loadFile: EOF encountered when tape label expected, block=" + ctl.blockCount);
|
|
} else if (!lab.isLabel) {
|
|
spout(lab.text);
|
|
spout("loadFile: Above block encountered when a tape label was expected, block=" + ctl.blockCount);
|
|
} else {
|
|
block = readWordBlock(ctl);
|
|
if (!ctl.eof) {
|
|
spout("loadFile: EOF expected after starting label, block=" + ctl.blockCount);
|
|
}
|
|
tapeHeader = readDiskHeader(ctl);
|
|
|
|
// Display a bunch of header detail values
|
|
spout(" " + lab.mfid + "/" + lab.fid +
|
|
": REC=" + tapeHeader.recordLength +
|
|
", BLK=" + tapeHeader.blockLength +
|
|
", RPB=" + tapeHeader.recordsPerBlock +
|
|
", SPB=" + tapeHeader.segmentsPerBlock +
|
|
", LCD=" + tapeHeader.logCreationDate +
|
|
", LCT=" + tapeHeader.logCreationTime +
|
|
", LAD=" + tapeHeader.lastAccessDate +
|
|
", CRD=" + tapeHeader.creationDate +
|
|
", FCL=" + tapeHeader.fileClass +
|
|
", FTY=" + tapeHeader.fileType +
|
|
", CNT=" + tapeHeader.recordCount +
|
|
", SPR=" + tapeHeader.segmentsPerRow +
|
|
", MXR=" + tapeHeader.maxRows);
|
|
|
|
text = " Rows @ [";
|
|
for (x=0; x<tapeHeader.rowAddress.length; x++) {
|
|
if (x>0) {
|
|
text += ", ";
|
|
}
|
|
text += tapeHeader.rowAddress[x].toString(10);
|
|
if (tapeHeader.rowAddress[x] != 0) {
|
|
rowCount++;
|
|
}
|
|
}
|
|
spout(text + "], allocated=" + rowCount);
|
|
|
|
// Copy the header words from the tape to the disk header
|
|
for (x=0; x<30; x++) {
|
|
diskHeader[x] = tapeHeader.words[x] || 0;
|
|
}
|
|
|
|
// Load the rows and update the disk header address words
|
|
for (x=0; x<tapeHeader.maxRows; x++) {
|
|
if (diskHeader[10+x] > 0) {
|
|
addr = findDiskSpace(tapeHeader.segmentsPerRow);
|
|
if (addr < 0) {
|
|
alert("Cannot get space for row #" + x);
|
|
diskHeader[10+x] = 0;
|
|
} else {
|
|
diskHeader[10+x] = addr;
|
|
loadFileRow(ctl, eu, addr, tapeHeader.segmentsPerRow);
|
|
if (firstRowAddr < 0) {
|
|
firstRowAddr = addr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
block = readWordBlock(ctl);
|
|
if (!ctl.eof) {
|
|
spout("loadFile: EOF expected after last data row, block=" + ctl.blockCount);
|
|
}
|
|
|
|
lab2 = readTapeLabel(ctl);
|
|
if (!lab2.isLabel) {
|
|
spout("loadFile: Tape label expected after file data, block=" + ctl.blockCount);
|
|
} else if (lab2.mfid != lab.mfid || lab2.fid != lab.fid) {
|
|
spout("loadFile: File ending label mismatch, block=" + ctl.blockCount);
|
|
}
|
|
}
|
|
return firstRowAddr;
|
|
}
|
|
|
|
/**************************************/
|
|
function skipTapeFile(ctl, fileNr) {
|
|
/* Spaces over the next file in the tape blob */
|
|
var block;
|
|
var lab;
|
|
var lab2;
|
|
|
|
lab = readTapeLabel(ctl);
|
|
if (ctl.eof) {
|
|
spout("skipTapeFile: EOF encountered when tape label expected, block=" + ctl.blockCount);
|
|
} else if (!lab.isLabel) {
|
|
spout(lab.text);
|
|
spout("skipTapeFile: Above block encountered when a tape label was expected, block=" + ctl.blockCount);
|
|
} else {
|
|
block = readWordBlock(ctl);
|
|
if (!ctl.eof) {
|
|
spout("skipTapeFile: EOF expected after starting label, block=" + ctl.blockCount);
|
|
}
|
|
|
|
do {
|
|
block = readWordBlock(ctl);
|
|
} while (!ctl.eof);
|
|
|
|
lab2 = readTapeLabel(ctl);
|
|
if (!lab2.isLabel) {
|
|
spout("skipTapeFile: Tape label expected after file data, block=" + ctl.blockCount);
|
|
} else if (lab2.mfid != lab.mfid || lab2.fid != lab.fid) {
|
|
spout("skipTapeFile: File ending label mismatch, block=" + ctl.blockCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function findDiskFile(mfid, fid, eu, successor) {
|
|
/* Searches for an existing disk file named "mfid"/"fid". If found, calls the
|
|
"successor" function, passing its directory block, address, and slot number.
|
|
If not found, calls the successor function with a block containing an available
|
|
slot, its address, and slot number. The block address will be negative if the
|
|
name is not found */
|
|
var availAddr = -1;
|
|
var availBlock = null;
|
|
var availSlot = -1;
|
|
var block = new Array(480);
|
|
|
|
function searchDirBlock(addr, block) {
|
|
/* Searches the directory block "block" for a file named "mfid" and "fid".
|
|
If found or at end, calls the successor function, otherwiseadvances to
|
|
the next block */
|
|
var atEnd = false;
|
|
var bx;
|
|
var found = false;
|
|
var namex;
|
|
var rowMax;
|
|
var rowSize;
|
|
var rx;
|
|
|
|
// Step through the file name entries backwards
|
|
for (namex=14; namex>=0; namex--) {
|
|
bx = namex*2 + 450;
|
|
if (block[bx] == 0x4C) { // 0x4C=@114, end-of-directory marker
|
|
atEnd = true;
|
|
if (availAddr < 0) {
|
|
availAddr = addr;
|
|
availSlot = namex;
|
|
availBlock = block;
|
|
}
|
|
break;
|
|
} else if (block[bx] == 0x0C) { // 0x0C=@14, available directory slot
|
|
if (availAddr < 0) {
|
|
availAddr = addr;
|
|
availSlot = namex;
|
|
availBlock = block;
|
|
}
|
|
} else { // check for a name match
|
|
if (block[bx] == mfid && block[bx+1] == fid) {
|
|
found = true;
|
|
availAddr = addr;
|
|
availSlot = namex;
|
|
availBlock = block;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (found) {
|
|
successor(availAddr, availBlock, availSlot);
|
|
} else if (atEnd) {
|
|
successor(-availAddr, availBlock, availSlot);
|
|
} else {
|
|
readDiskBlock(eu, addr+16, 16, block, searchDirBlock);
|
|
}
|
|
|
|
}
|
|
|
|
/***** outer block of findDiskFile *****/
|
|
|
|
readDiskBlock(eu, directoryTop+4, 16, block, searchDirBlock);
|
|
}
|
|
|
|
/**************************************/
|
|
function loadTapeFile(tapeCtl, topFileNr, fileNr) {
|
|
/* Locates and loads the next selected file from the tape blob. On
|
|
completion, if there are more file to load, chains itself for the next one */
|
|
var names = [0, 0];
|
|
var buf = new Uint8Array(16);
|
|
var euName = euPrefix + "0";
|
|
var txn = db.transaction(euName, "readwrite");
|
|
var eu = txn.objectStore(euName);
|
|
var fid;
|
|
var mfid;
|
|
var loadAsInt;
|
|
var loadAsMCP;
|
|
var firstRowAddr = -1;
|
|
var nameParts;
|
|
|
|
function loadFileHost(addr, block, slot) {
|
|
/* Controls the loading of the current file from tape and updates
|
|
the disk directory with the new file information */
|
|
var extendDirectory = false;
|
|
var diskHeader;
|
|
var hx = slot*30;
|
|
var nx = slot*2 + 450;
|
|
var rowMax;
|
|
var rowSize;
|
|
var x;
|
|
|
|
if (addr < 0) {
|
|
addr = -addr;
|
|
diskHeader = new Array(30);
|
|
} else {
|
|
diskHeader = block.slice(hx, hx+30);
|
|
// Deallocate the existing rows
|
|
rowSize = diskHeader[8];
|
|
rowMax = fieldIsolate(diskHeader[9], 43, 5);
|
|
for (x=0; x<rowMax; x++) {
|
|
if (diskHeader[10+x] > 0) {
|
|
makeAvailable(diskHeader[10+x], rowSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
firstRowAddr = loadFile(tapeCtl, fileNr, eu, diskHeader);
|
|
|
|
// Move the new header into the directory block
|
|
for (x=0; x<30; x++) {
|
|
block[hx+x] = diskHeader[x] || 0;
|
|
}
|
|
|
|
// Check if using the last slot in the directory
|
|
if (block[nx] == 0x4C) {
|
|
if (slot > 0) {
|
|
block[nx-2] = 0x4C;
|
|
block[nx-1] = 0;
|
|
} else {
|
|
extendDirectory = true;
|
|
}
|
|
}
|
|
|
|
// Update the file name label words and write the block
|
|
block[nx] = mfid;
|
|
block[nx+1] = fid;
|
|
writeDiskWords(eu, addr, block, 0, 480);
|
|
|
|
if (extendDirectory) {
|
|
block = new Array(30);
|
|
for (x=0; x<30; x+=2) {
|
|
block[x] = 0x4C; // = @114, end of directory marker
|
|
block[x+1] = 0;
|
|
}
|
|
writeDiskWords(eu, addr+31, block, 0, 30);
|
|
}
|
|
|
|
// Check to see if seg.0 must be updated for MCP or Intrinsics
|
|
if (loadAsMCP || loadAsInt) {
|
|
readDiskBlock(eu, 0, 1, block, function(addr, block) {
|
|
if (loadAsMCP) {
|
|
block[10] = mfid;
|
|
block[11] = fid;
|
|
block[12] = firstRowAddr;
|
|
}
|
|
if (loadAsInt) {
|
|
block[13] = mfid;
|
|
block[14] = fid;
|
|
}
|
|
writeDiskWords(eu, 0, block, 0, 30);
|
|
});
|
|
}
|
|
}
|
|
|
|
/***** loadTapeFile outer block *****/
|
|
|
|
while (!$$("File_" + fileNr).checked) {
|
|
spout("Skipping #" + fileNr + ": " + tapeDir[fileNr]);
|
|
skipTapeFile(tapeCtl, fileNr);
|
|
fileNr++;
|
|
}
|
|
txn.oncomplete = function(ev) {
|
|
if (fileNr < topFileNr) {
|
|
loadTapeFile(tapeCtl, topFileNr, fileNr+1);
|
|
} else {
|
|
alert("Tape load completed");
|
|
}
|
|
};
|
|
|
|
nameParts = tapeDir[fileNr].split("/");
|
|
mfid = "0" + padToLength(nameParts[0] || " ", 7);
|
|
fid = "0" + padToLength(nameParts[1] || " ", 7);
|
|
stringToANSI(mfid, buf, 0, true);
|
|
stringToANSI(fid, buf, 8, true);
|
|
ANSItoWords(buf, 0, 16, names, 0, true);
|
|
mfid = names[0];
|
|
fid = names[1];
|
|
loadAsMCP = $$("AsMCP_" + fileNr).checked;
|
|
loadAsInt = $$("AsINT_" + fileNr).checked;
|
|
|
|
spout(" ");
|
|
spout("Loading #" + fileNr + ": " + tapeDir[fileNr]);
|
|
findDiskFile(mfid, fid, eu, loadFileHost);
|
|
}
|
|
|
|
/**************************************/
|
|
function loadFromTape(ev) {
|
|
/* Event handler that responds to the "Load" button and initiates the load of
|
|
the files selected on the UI page */
|
|
var cb;
|
|
var fileNr;
|
|
var topFileNr = 0;
|
|
var x;
|
|
|
|
for (x=1; x<tapeDir.length; x++) {
|
|
cb = $$("File_" + x);
|
|
if (cb && cb.checked) {
|
|
topFileNr = x;
|
|
}
|
|
}
|
|
if (topFileNr <= 0) {
|
|
alert("loadFromTape: no files selected");
|
|
} else {
|
|
loadTapeFile(tapeCtl, topFileNr, 1);
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function directoryComplement(successor) {
|
|
/* Reads the existing directory structure to develop the global "availDisk" map
|
|
map of available areas on EU0. When finished, calls the "successor" function */
|
|
var block = new Array(480);
|
|
var eu;
|
|
var euName = euPrefix + "0";
|
|
var txn;
|
|
|
|
function complementDirBlock(addr, block) {
|
|
/* Complements "availDisk" from the entries in the resulting block; if
|
|
not the last block, advances to the next block */
|
|
var atEnd = false;
|
|
var bx;
|
|
var namex;
|
|
var rowAddr;
|
|
var rowMax;
|
|
var rowSize;
|
|
var rx;
|
|
|
|
// Step through the file name entries backwards
|
|
for (namex=14; namex>=0; namex--) {
|
|
bx = namex*2 + 450;
|
|
if (block[bx] == 0x4C) { // 0x4C=@114, end-of-directory marker
|
|
atEnd = true;
|
|
break;
|
|
} else if (block[bx] != 0x0C) { // 0x0C=@14, available directory slot
|
|
// Got a live one -- complement its in-use space
|
|
bx = namex*30;
|
|
rowSize = block[bx+8];
|
|
rowMax = fieldIsolate(block[bx+9], 43, 5);
|
|
for (rx=0; rx<rowMax; rx++) {
|
|
rowAddr = block[bx+10+rx];
|
|
if (rowAddr > 0 && rowAddr < 1000000) { // ignore rows not on EU0
|
|
removeAvailable(rowAddr, rowSize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (atEnd) {
|
|
successor();
|
|
} else {
|
|
readDiskBlock(eu, addr+16, 16, block, complementDirBlock);
|
|
}
|
|
|
|
}
|
|
|
|
/***** outer block of directoryComplement *****/
|
|
|
|
if (!config.EU0) {
|
|
alert("No EU0 in disk configuration -- cannot load");
|
|
} else {
|
|
availDisk = [{addr: directoryEnd+4, size: config.EU0-directoryEnd-4}];
|
|
txn = db.transaction(euName);
|
|
eu = txn.objectStore(euName);
|
|
readDiskBlock(eu, directoryTop+4, 16, block, complementDirBlock);
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function fileLoader_onLoad(ev) {
|
|
/* Handle the onload event for an ArrayBuffer FileReader */
|
|
var body = $$("TapeDirBody");
|
|
var cell;
|
|
var e;
|
|
var row;
|
|
var text = "";
|
|
var x = 0;
|
|
|
|
function resetSelAll(ev) {
|
|
$$("SelAll").checked = false;
|
|
}
|
|
|
|
function selectAll(ev) {
|
|
var checkboxes = body.getElementsByTagName("input");
|
|
var e;
|
|
var x;
|
|
|
|
for (x=0; x<checkboxes.length; x++) {
|
|
e = checkboxes[x];
|
|
if (e.type == "checkbox" && e.id != "SelAll") {
|
|
e.checked = ev.target.checked;
|
|
}
|
|
}
|
|
}
|
|
|
|
clearPanel();
|
|
while (body.firstChild) {
|
|
body.removeChild(body.firstChild);
|
|
}
|
|
|
|
tapeBlob = ev.target.result;
|
|
tapeData = new DataView(tapeBlob); // use DataView() to avoid problems with little-endians.
|
|
|
|
tapeCtl.data = tapeData;
|
|
tapeCtl.offset = 0;
|
|
tapeCtl.dataLength = tapeBlob.byteLength;
|
|
tapeCtl.eof = false;
|
|
tapeCtl.eot = false;
|
|
tapeCtl.blockCount = 0;
|
|
|
|
tapeDir = readTapeDirectory(tapeCtl);
|
|
|
|
row = document.createElement("tr");
|
|
// Checkbox, File number, and File name
|
|
cell = document.createElement("td");
|
|
cell.colSpan=3;
|
|
e = document.createElement("input");
|
|
e.type = "checkbox";
|
|
e.id = "SelAll";
|
|
e.value = "SelAll";
|
|
e.addEventListener("click", selectAll);
|
|
cell.appendChild(e);
|
|
e = document.createElement("label");
|
|
e.appendChild(document.createTextNode("Select All"));
|
|
e.htmlFor = "SelAll";
|
|
cell.appendChild(e);
|
|
row.appendChild(cell);
|
|
// Load as MCP for no selection
|
|
cell = document.createElement("td");
|
|
cell.className = "center";
|
|
e = document.createElement("input");
|
|
e.type = "radio";
|
|
e.id = "AsMCP_0";
|
|
e.value = "0";
|
|
e.name = "AsMCP";
|
|
e.checked = true;
|
|
cell.appendChild(e);
|
|
row.appendChild(cell);
|
|
// Load as Intrinsics for no selection
|
|
cell = document.createElement("td");
|
|
cell.className = "center";
|
|
e = document.createElement("input");
|
|
e.type = "radio";
|
|
e.id = "AsINT_0";
|
|
e.value = "0";
|
|
e.name = "AsINT";
|
|
e.checked = true;
|
|
cell.appendChild(e);
|
|
row.appendChild(cell);
|
|
body.appendChild(row);
|
|
|
|
for (x=1; x<tapeDir.length; x++) {
|
|
row = document.createElement("tr");
|
|
// Load selection checkbox
|
|
cell = document.createElement("td");
|
|
e = document.createElement("input");
|
|
e.type = "checkbox";
|
|
e.id = "File_" + x;
|
|
e.value = "File_" + x;
|
|
e.addEventListener("click", resetSelAll);
|
|
cell.appendChild(e);
|
|
row.appendChild(cell);
|
|
// File number
|
|
cell = document.createElement("td");
|
|
cell.className = "rj";
|
|
cell.appendChild(document.createTextNode(x.toFixed(0)));
|
|
row.appendChild(cell);
|
|
// File ID
|
|
cell = document.createElement("td");
|
|
e = document.createElement("label");
|
|
e.appendChild(document.createTextNode(tapeDir[x]));
|
|
e.htmlFor = "File_" + x;
|
|
cell.appendChild(e);
|
|
row.appendChild(cell);
|
|
// Load as MCP selection radio button
|
|
cell = document.createElement("td");
|
|
cell.className = "center";
|
|
e = document.createElement("input");
|
|
e.type = "radio";
|
|
e.id = "AsMCP_" + x;
|
|
e.value = x;
|
|
e.name = "AsMCP";
|
|
cell.appendChild(e);
|
|
row.appendChild(cell);
|
|
// Load as Intrinsics selection radio button
|
|
cell = document.createElement("td");
|
|
cell.className = "center";
|
|
e = document.createElement("input");
|
|
e.type = "radio";
|
|
e.id = "AsINT_" + x;
|
|
e.value = x;
|
|
e.name = "AsINT";
|
|
cell.appendChild(e);
|
|
row.appendChild(cell);
|
|
body.appendChild(row);
|
|
}
|
|
|
|
directoryComplement(function() {
|
|
var x;
|
|
|
|
$$("TapeDirDiv").style.display = "block";
|
|
$$("LoadBtn").disabled = false;
|
|
|
|
// Debug dump of avail table
|
|
spout("Disk Available-Space Table:");
|
|
for (x=0; x<availDisk.length; x++) {
|
|
spout(x.toFixed(0) + " = " + availDisk[x].addr + ":" + availDisk[x].size);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**************************************/
|
|
function fileSelector_onChange(ev) {
|
|
/* Handle the <input type=file> onchange event when a file is selected */
|
|
var f = ev.target.files[0];
|
|
var reader = new FileReader();
|
|
|
|
//alert("File selected: " + f.name +
|
|
// "\nModified " + f.lastModifiedDate +
|
|
// "\nType=" + f.type + ", Size=" + f.size + " octets");
|
|
|
|
reader.onload = fileLoader_onLoad;
|
|
reader.readAsArrayBuffer(f);
|
|
}
|
|
|
|
/**************************************/
|
|
function initializeDisk() {
|
|
/* Performs a B5500 Cold Start by initializing the directory structure on
|
|
the disk, building segment 0, and loading the bootstrap, overwriting (and
|
|
destroying) whatever else was there before */
|
|
var buffer = new Uint8Array(240); // one-segment buffer area
|
|
var eu; // IDB object store for EU0
|
|
var euName = euPrefix + "0";
|
|
var fileLabels = new Uint8Array(240); // one-segment buffer for file names
|
|
var fileNr = 0;
|
|
var info = []; // configuration options segment
|
|
var segNr = null; // next address to allocate (set by initializeDirectory)
|
|
var shar = []; // segment 0 buffer
|
|
var txn; // IDB transaction object
|
|
var zeroes = new Uint8Array(240); // buffer of binary zeroes
|
|
var x;
|
|
|
|
function loadBootstrap() {
|
|
/* Creates the Halt/Load Button Card image and stores it in segment 1 */
|
|
/* Note: this has been replaced by loadKernel -- not used */
|
|
var w = [];
|
|
|
|
w[ 0] = parseInt("0441341003604231", 8);
|
|
w[ 1] = parseInt("7500000000000023", 8);
|
|
w[ 2] = parseInt("0211001441310435", 8);
|
|
w[ 3] = parseInt("7012700704210014", 8);
|
|
w[ 4] = parseInt("4411005441314155", 8);
|
|
w[ 5] = parseInt("6461106500000425", 8);
|
|
w[ 6] = parseInt("0074013100644131", 8);
|
|
w[ 7] = parseInt("0000006200644131", 8);
|
|
w[ 8] = parseInt("0000006601044131", 8);
|
|
w[ 9] = parseInt("0000007201244131", 8);
|
|
w[10] = parseInt("0000007601444131", 8);
|
|
w[11] = parseInt("5140000040700137", 8);
|
|
w[12] = parseInt("5140000047700461", 8);
|
|
w[13] = parseInt("5140000047704223", 8);
|
|
w[14] = parseInt("7700000000000037", 8);
|
|
w[15] = parseInt("0153020404050000", 8);
|
|
w[16] = parseInt("0167010604410440", 8);
|
|
w[17] = parseInt("0163010604410010", 8);
|
|
w[18] = parseInt("0157010604410660", 8);
|
|
w[19] = parseInt("0600017205204131", 8);
|
|
wordsToANSI(w, 0, 30, buffer, 0);
|
|
eu.put(buffer, 1);
|
|
}
|
|
|
|
function loadKernel() {
|
|
/* Creates the Kernel bootstrap loaderand stores it starting in
|
|
segment 1. This code was generated using the Mark XVI KERNEL source
|
|
and XVI ESPOLXEM compiler */
|
|
var addr = 1; // Bootstrap starts at segment 1
|
|
var w = [];
|
|
|
|
w[ 0] = parseInt("0740623100000000", 8);
|
|
w[ 1] = parseInt("0000000000000000", 8);
|
|
w[ 2] = parseInt("0211101607302231", 8);
|
|
w[ 3] = parseInt("0004613100000000", 8);
|
|
w[ 4] = parseInt("0000000000000000", 8);
|
|
w[ 5] = parseInt("0014613100000000", 8);
|
|
w[ 6] = parseInt("0020613100000000", 8);
|
|
w[ 7] = parseInt("0060202102350000", 8);
|
|
w[ 8] = parseInt("0064202102350000", 8);
|
|
w[ 9] = parseInt("0070202102350000", 8);
|
|
w[10] = parseInt("0074202102350000", 8);
|
|
w[11] = parseInt("0000000000000000", 8);
|
|
w[12] = parseInt("0050613100000000", 8);
|
|
w[13] = parseInt("0000000000000000", 8);
|
|
w[14] = parseInt("0060613100000000", 8);
|
|
w[15] = parseInt("0064613100000000", 8);
|
|
w[16] = parseInt("0000000000000000", 8);
|
|
w[17] = parseInt("0000000000000000", 8);
|
|
w[18] = parseInt("0000000000000000", 8);
|
|
w[19] = parseInt("0000000000000000", 8);
|
|
w[20] = parseInt("0000000000000000", 8);
|
|
w[21] = parseInt("0000000000000000", 8);
|
|
w[22] = parseInt("0000000000000000", 8);
|
|
w[23] = parseInt("0000000000000000", 8);
|
|
w[24] = parseInt("0000000000000000", 8);
|
|
w[25] = parseInt("0000000000000000", 8);
|
|
w[26] = parseInt("0000000000000000", 8);
|
|
w[27] = parseInt("0000000000000000", 8);
|
|
w[28] = parseInt("0000000000000000", 8);
|
|
w[29] = parseInt("0000000000000000", 8);
|
|
wordsToANSI(w, 0, 30, buffer, 0);
|
|
eu.put(buffer, addr++);
|
|
|
|
w[ 0] = parseInt("0000000000000000", 8);
|
|
w[ 1] = parseInt("0000000000000000", 8);
|
|
w[ 2] = parseInt("0000000000000000", 8);
|
|
w[ 3] = parseInt("0534623100000000", 8);
|
|
w[ 4] = parseInt("0000000000000000", 8);
|
|
w[ 5] = parseInt("0000000000000000", 8);
|
|
w[ 6] = parseInt("0000000000000000", 8);
|
|
w[ 7] = parseInt("0000000000000000", 8);
|
|
w[ 8] = parseInt("0000000000000000", 8);
|
|
w[ 9] = parseInt("0000000000000000", 8);
|
|
w[10] = parseInt("0000000000000000", 8);
|
|
w[11] = parseInt("0000000000000000", 8);
|
|
w[12] = parseInt("0000000000000000", 8);
|
|
w[13] = parseInt("0000000000000000", 8);
|
|
w[14] = parseInt("0000000000000000", 8);
|
|
w[15] = parseInt("0000000000000000", 8);
|
|
w[16] = parseInt("0000000000000000", 8);
|
|
w[17] = parseInt("0000000000000000", 8);
|
|
w[18] = parseInt("0000000000000000", 8);
|
|
w[19] = parseInt("0000000000000000", 8);
|
|
w[20] = parseInt("0000000000000000", 8);
|
|
w[21] = parseInt("0000000000000000", 8);
|
|
w[22] = parseInt("0000000000000000", 8);
|
|
w[23] = parseInt("0000000000000000", 8);
|
|
w[24] = parseInt("0000000000000000", 8);
|
|
w[25] = parseInt("0000000000000000", 8);
|
|
w[26] = parseInt("0000000000000000", 8);
|
|
w[27] = parseInt("0000000000000000", 8);
|
|
w[28] = parseInt("0000000000000000", 8);
|
|
w[29] = parseInt("0000000000000000", 8);
|
|
wordsToANSI(w, 0, 30, buffer, 0);
|
|
eu.put(buffer, addr++);
|
|
|
|
w[ 0] = parseInt("0000000000000000", 8);
|
|
w[ 1] = parseInt("0000000000000000", 8);
|
|
w[ 2] = parseInt("0000000000000000", 8);
|
|
w[ 3] = parseInt("0000000000000000", 8);
|
|
w[ 4] = parseInt("0000000000000000", 8);
|
|
w[ 5] = parseInt("0000000000000000", 8);
|
|
w[ 6] = parseInt("0000000000000000", 8);
|
|
w[ 7] = parseInt("0000000000000000", 8);
|
|
w[ 8] = parseInt("0000000000000000", 8);
|
|
w[ 9] = parseInt("0000000000000000", 8);
|
|
w[10] = parseInt("0000000000000000", 8);
|
|
w[11] = parseInt("0000000000000000", 8);
|
|
w[12] = parseInt("0000000000000000", 8);
|
|
w[13] = parseInt("0000000000000000", 8);
|
|
w[14] = parseInt("0000000000000000", 8);
|
|
w[15] = parseInt("0000000000000000", 8);
|
|
w[16] = parseInt("0000000000000000", 8);
|
|
w[17] = parseInt("0000000000000000", 8);
|
|
w[18] = parseInt("0000000000000000", 8);
|
|
w[19] = parseInt("0000000000000000", 8);
|
|
w[20] = parseInt("0000000000000000", 8);
|
|
w[21] = parseInt("0000000000000000", 8);
|
|
w[22] = parseInt("0000000000000000", 8);
|
|
w[23] = parseInt("0000000000000000", 8);
|
|
w[24] = parseInt("0000000000000000", 8);
|
|
w[25] = parseInt("0000000000000000", 8);
|
|
w[26] = parseInt("0000000000000000", 8);
|
|
w[27] = parseInt("0000000000000000", 8);
|
|
w[28] = parseInt("0000000000000000", 8);
|
|
w[29] = parseInt("0000000000000000", 8);
|
|
wordsToANSI(w, 0, 30, buffer, 0);
|
|
eu.put(buffer, addr++);
|
|
|
|
w[ 0] = parseInt("0000000000000000", 8);
|
|
w[ 1] = parseInt("0000000000000000", 8);
|
|
w[ 2] = parseInt("0000000000000000", 8);
|
|
w[ 3] = parseInt("0000000000000000", 8);
|
|
w[ 4] = parseInt("0000000000000000", 8);
|
|
w[ 5] = parseInt("0000000000000000", 8);
|
|
w[ 6] = parseInt("0000000000000000", 8);
|
|
w[ 7] = parseInt("0000000000000000", 8);
|
|
w[ 8] = parseInt("0000000000000000", 8);
|
|
w[ 9] = parseInt("0000000000000000", 8);
|
|
w[10] = parseInt("0000000000000000", 8);
|
|
w[11] = parseInt("0000000000000000", 8);
|
|
w[12] = parseInt("0000000000000000", 8);
|
|
w[13] = parseInt("0000000000000000", 8);
|
|
w[14] = parseInt("0000000000000000", 8);
|
|
w[15] = parseInt("0000000000000000", 8);
|
|
w[16] = parseInt("0000000000000000", 8);
|
|
w[17] = parseInt("0000000000000000", 8);
|
|
w[18] = parseInt("0000000000000000", 8);
|
|
w[19] = parseInt("0000000000000000", 8);
|
|
w[20] = parseInt("0000000000000000", 8);
|
|
w[21] = parseInt("0000000000000000", 8);
|
|
w[22] = parseInt("5000000000000000", 8);
|
|
w[23] = parseInt("0000000000000000", 8);
|
|
w[24] = parseInt("0000000000000000", 8);
|
|
w[25] = parseInt("0000000000000000", 8);
|
|
w[26] = parseInt("7560020000000367", 8);
|
|
w[27] = parseInt("7660220000000022", 8);
|
|
w[28] = parseInt("7560070000000371", 8);
|
|
w[29] = parseInt("7560140000000400", 8);
|
|
wordsToANSI(w, 0, 30, buffer, 0);
|
|
eu.put(buffer, addr++);
|
|
|
|
w[ 0] = parseInt("0400001421410230", 8);
|
|
w[ 1] = parseInt("1003014102301003", 8);
|
|
w[ 2] = parseInt("0141202111120055", 8);
|
|
w[ 3] = parseInt("0101102510211003", 8);
|
|
w[ 4] = parseInt("0141000010250421", 8);
|
|
w[ 5] = parseInt("0211023010030141", 8);
|
|
w[ 6] = parseInt("2021111600550225", 8);
|
|
w[ 7] = parseInt("0024223102301003", 8);
|
|
w[ 8] = parseInt("0141000010250421", 8);
|
|
w[ 9] = parseInt("0044613100550055", 8);
|
|
w[10] = parseInt("0000000000010000", 8);
|
|
w[11] = parseInt("0000000000070000", 8);
|
|
w[12] = parseInt("0004101441211020", 8);
|
|
w[13] = parseInt("2021062001411003", 8);
|
|
w[14] = parseInt("0141777462551261", 8);
|
|
w[15] = parseInt("1265101004210204", 8);
|
|
w[16] = parseInt("1003014110102021", 8);
|
|
w[17] = parseInt("5355304510250421", 8);
|
|
w[18] = parseInt("0204100301412021", 8);
|
|
w[19] = parseInt("1003014102001025", 8);
|
|
w[20] = parseInt("0421021010030141", 8);
|
|
w[21] = parseInt("0064102504210441", 8);
|
|
w[22] = parseInt("1642005502041003", 8);
|
|
w[23] = parseInt("0141202154251032", 8);
|
|
w[24] = parseInt("0000442504740231", 8);
|
|
w[25] = parseInt("0224100301410004", 8);
|
|
w[26] = parseInt("1012045510451025", 8);
|
|
w[27] = parseInt("0421020410030141", 8);
|
|
w[28] = parseInt("2021100301411646", 8);
|
|
w[29] = parseInt("0055022410030141", 8);
|
|
wordsToANSI(w, 0, 30, buffer, 0);
|
|
eu.put(buffer, addr++);
|
|
|
|
w[ 0] = parseInt("2021745550610265", 8);
|
|
w[ 1] = parseInt("1025042104411652", 8);
|
|
w[ 2] = parseInt("0055020410030141", 8);
|
|
w[ 3] = parseInt("2021542510320051", 8);
|
|
w[ 4] = parseInt("0204100301412021", 8);
|
|
w[ 5] = parseInt("1003014116560055", 8);
|
|
w[ 6] = parseInt("0224100301412021", 8);
|
|
w[ 7] = parseInt("7455506102651025", 8);
|
|
w[ 8] = parseInt("0421044116520055", 8);
|
|
w[ 9] = parseInt("0204100301412021", 8);
|
|
w[10] = parseInt("5425103200510210", 8);
|
|
w[11] = parseInt("1003014102101003", 8);
|
|
w[12] = parseInt("0141202102241003", 8);
|
|
w[13] = parseInt("0141202100240401", 8);
|
|
w[14] = parseInt("0101102504210204", 8);
|
|
w[15] = parseInt("1003014120211003", 8);
|
|
w[16] = parseInt("0141000010250421", 8);
|
|
w[17] = parseInt("0220100301410441", 8);
|
|
w[18] = parseInt("0204100301412021", 8);
|
|
w[19] = parseInt("1662005501411032", 8);
|
|
w[20] = parseInt("1025042102201003", 8);
|
|
w[21] = parseInt("0141202100004425", 8);
|
|
w[22] = parseInt("0024213100041003", 8);
|
|
w[23] = parseInt("0141001010121025", 8);
|
|
w[24] = parseInt("0421021410030141", 8);
|
|
w[25] = parseInt("0004101210250421", 8);
|
|
w[26] = parseInt("0210100301412021", 8);
|
|
w[27] = parseInt("1012100304211002", 8);
|
|
w[28] = parseInt("0004100301412021", 8);
|
|
w[29] = parseInt("4125004022310230", 8);
|
|
wordsToANSI(w, 0, 30, buffer, 0);
|
|
eu.put(buffer, addr++);
|
|
|
|
w[ 0] = parseInt("1003014116660055", 8);
|
|
w[ 1] = parseInt("1025042102341003", 8);
|
|
w[ 2] = parseInt("0141167200551025", 8);
|
|
w[ 3] = parseInt("0421024010030141", 8);
|
|
w[ 4] = parseInt("1676005510250421", 8);
|
|
w[ 5] = parseInt("0441170200551032", 8);
|
|
w[ 6] = parseInt("0051000000140131", 8);
|
|
w[ 7] = parseInt("0200100301410444", 8);
|
|
w[ 8] = parseInt("1025042104411706", 8);
|
|
w[ 9] = parseInt("0055023010030141", 8);
|
|
w[10] = parseInt("2021021510360200", 8);
|
|
w[11] = parseInt("1003014100501025", 8);
|
|
w[12] = parseInt("0421044117120055", 8);
|
|
w[13] = parseInt("0230100301412021", 8);
|
|
w[14] = parseInt("0215103602001003", 8);
|
|
w[15] = parseInt("0141000010250421", 8);
|
|
w[16] = parseInt("0441171600550230", 8);
|
|
w[17] = parseInt("1003014120210215", 8);
|
|
w[18] = parseInt("1036023010030141", 8);
|
|
w[19] = parseInt("0230100301412021", 8);
|
|
w[20] = parseInt("0200010110250421", 8);
|
|
w[21] = parseInt("0064100301411722", 8);
|
|
w[22] = parseInt("0055102504210070", 8);
|
|
w[23] = parseInt("1003014117260055", 8);
|
|
w[24] = parseInt("1025042100741003", 8);
|
|
w[25] = parseInt("0141173200551025", 8);
|
|
w[26] = parseInt("0421100200100301", 8);
|
|
w[27] = parseInt("1003042110020224", 8);
|
|
w[28] = parseInt("1003014120217455", 8);
|
|
w[29] = parseInt("2461026502141003", 8);
|
|
wordsToANSI(w, 0, 30, buffer, 0);
|
|
eu.put(buffer, addr++);
|
|
|
|
w[ 0] = parseInt("0141202174552261", 8);
|
|
w[ 1] = parseInt("0265100304210004", 8);
|
|
w[ 2] = parseInt("0014214100000010", 8);
|
|
w[ 3] = parseInt("2141006410030141", 8);
|
|
w[ 4] = parseInt("4231000401042231", 8);
|
|
w[ 5] = parseInt("0024413100550055", 8);
|
|
w[ 6] = parseInt("0140004000000000", 8);
|
|
w[ 7] = parseInt("0000000000004060", 8);
|
|
w[ 8] = parseInt("0140000100000000", 8);
|
|
w[ 9] = parseInt("0000000000006060", 8);
|
|
w[10] = parseInt("0140000040100000", 8);
|
|
w[11] = parseInt("3145652143312460", 8);
|
|
w[12] = parseInt("2124245125626260", 8);
|
|
w[13] = parseInt("2646516044234737", 8);
|
|
w[14] = parseInt("0740000000000046", 8);
|
|
w[15] = parseInt("0140000047704235", 8);
|
|
w[16] = parseInt("0140000047700473", 8);
|
|
w[17] = parseInt("0140000041200017", 8);
|
|
w[18] = parseInt("0441023201004441", 8);
|
|
w[19] = parseInt("0253010453527705", 8);
|
|
w[20] = parseInt("3705005101002411", 8);
|
|
w[21] = parseInt("0000102642314006", 8);
|
|
w[22] = parseInt("0235000000000000", 8);
|
|
w[23] = parseInt("0000700744110220", 8);
|
|
w[24] = parseInt("1003014104411022", 8);
|
|
w[25] = parseInt("1025042102201003", 8);
|
|
w[26] = parseInt("0141202141552345", 8);
|
|
w[27] = parseInt("4004042140060024", 8);
|
|
w[28] = parseInt("0415000044250140", 8);
|
|
w[29] = parseInt("0131400602350000", 8);
|
|
wordsToANSI(w, 0, 30, buffer, 0);
|
|
eu.put(buffer, addr++);
|
|
|
|
w[ 0] = parseInt("0441100202001003", 8);
|
|
w[ 1] = parseInt("0141202101017006", 8);
|
|
w[ 2] = parseInt("5355304544410222", 8);
|
|
w[ 3] = parseInt("0104106601007006", 8);
|
|
w[ 4] = parseInt("1003014120210555", 8);
|
|
w[ 5] = parseInt("1045003402317006", 8);
|
|
w[ 6] = parseInt("0060715503610565", 8);
|
|
w[ 7] = parseInt("7004042102201003", 8);
|
|
w[ 8] = parseInt("0141044170061032", 8);
|
|
w[ 9] = parseInt("1025042102201003", 8);
|
|
w[10] = parseInt("0141202100004425", 8);
|
|
w[11] = parseInt("0100013104350000", 8);
|
|
w[12] = parseInt("0000000000000000", 8);
|
|
w[13] = parseInt("0000000000000000", 8);
|
|
w[14] = parseInt("0000000000000000", 8);
|
|
w[15] = parseInt("0000000000000000", 8);
|
|
wordsToANSI(w, 0, 30, buffer, 0);
|
|
eu.put(buffer, addr++);
|
|
}
|
|
|
|
function enterFile(mfid, fid, areas, areasize) {
|
|
/* Enters a file into the disk directory. This routine will only create
|
|
one directory block, so do not call this routine more than 15 times.
|
|
Only the first row is allocated. Returns the next available segment address */
|
|
var header = [];
|
|
var labelx = 240-(fileNr+1)*16;
|
|
|
|
stringToANSI(padToLength(mfid, 7), fileLabels, labelx+1);
|
|
stringToANSI(padToLength(fid, 7), fileLabels, labelx+9);
|
|
if (labelx > 15) {
|
|
stringToANSI("0000001?", fileLabels, labelx-16); // @114, last-entry marker
|
|
stringToANSI("00000000", fileLabels, labelx-8);
|
|
}
|
|
header[0] = ((30*0x8000 + 30)*0x1000 + 1)*0x40 + 1;
|
|
// 30 words/rec, 30 words/block, 1 rec/block, 1 seg/block
|
|
header[3] = (999*0x40000 + 80001) * 0x40000 + 80001; // save=999, create+update=1980-01-01
|
|
header[7] = areas*areasize-1; // record count
|
|
header[8] = areasize; // row size
|
|
header[9] = areas; // number of areas
|
|
header[10] = segNr; // row[0] address
|
|
wordsToANSI(header, 0, 30, buffer, 0);
|
|
eu.put(buffer, directoryTop + 18 - fileNr);
|
|
|
|
segNr += areasize;
|
|
fileNr++;
|
|
}
|
|
|
|
function createSystemLog(areasize) {
|
|
/* Creates an empty SYSTEM/LOG file. This uses the same mechanism as enterFile(),
|
|
so do not call this plus enterFile() more than 15 times */
|
|
var header = [];
|
|
var labelx = 240-(fileNr+1)*16;
|
|
|
|
stringToANSI(padToLength("SYSTEM", 7), fileLabels, labelx+1);
|
|
stringToANSI(padToLength("LOG", 7), fileLabels, labelx+9);
|
|
if (labelx > 15) {
|
|
stringToANSI("0000001?", fileLabels, labelx-16); // @114, last-entry marker
|
|
stringToANSI("00000000", fileLabels, labelx-8);
|
|
}
|
|
header[0] = ((5*0x8000 + 30)*0x1000 + 6)*0x40 + 1;
|
|
// 5 words/rec, 30 words/block, 6 rec/block, 1 seg/block
|
|
header[3] = (999*0x40000 + 80001) * 0x40000 + 80001; // save=999, create+update=1980-01-01
|
|
header[7] = areasize*6-1; // record count
|
|
header[8] = areasize; // row size
|
|
header[9] = 1; // number of areas
|
|
header[10] = segNr; // row[0] address
|
|
wordsToANSI(header, 0, 30, buffer, 0);
|
|
eu.put(buffer, directoryTop + 18 - fileNr);
|
|
|
|
segNr += areasize;
|
|
fileNr++;
|
|
}
|
|
|
|
function initializeDirectory(config) {
|
|
/* Initializes the directory structure on EU0. "config" is the
|
|
database CONFIG structure */
|
|
var b55Date;
|
|
var b55Time;
|
|
var stamp = new Date();
|
|
var x;
|
|
|
|
// Compute a 1980s date based on today's date, formatted as BIC
|
|
x = stamp.getTime() - new Date(stamp.getFullYear(), 0, 1).getTime();
|
|
x = Math.floor(x/86400000) + 1; // day-of-year number
|
|
b55Date = (((8*64 + stamp.getYear()%10)*64 +
|
|
Math.floor(x/100))*64 + Math.floor(x/10)%10)*64 + x%10;
|
|
b55Time = Math.floor(((stamp.getHours()*60 + stamp.getMinutes())*60 +
|
|
stamp.getSeconds())*60 + stamp.getMilliseconds()*60/1000);
|
|
|
|
// Initialize the directory labels segment
|
|
stringToANSI("0000001?", fileLabels, 14*16); // @114, last-entry marker
|
|
stringToANSI("00000000", fileLabels, 14*16+8);
|
|
|
|
// Initialize segment 0
|
|
shar[ 0] = 1; // number of shared-disk systems
|
|
shar[ 1] = directoryTop;
|
|
shar[ 2] = 0;
|
|
shar[ 3] = 0;
|
|
shar[ 4] = directoryEnd; // DIRECT deck option
|
|
wordsToANSI(shar, 0, 30, buffer, 0);
|
|
eu.put(buffer, 0);
|
|
|
|
// Load the Halt Load Button Card image
|
|
loadKernel(); // load the Kernel bootstrap program
|
|
|
|
// Note: it is not necessary the clear out the ESPDISK area, since on a
|
|
// cold start the entire EU object store is cleared.
|
|
|
|
// Initialize the DIRECTORYTOP segment
|
|
info[ 0] = // option word
|
|
// 47: use DRA
|
|
// 46: use DRB
|
|
pow2[47-45] + // 45: print BOJ
|
|
pow2[47-44] + // 44: print EOJ
|
|
pow2[47-43] + // 43: type file open
|
|
pow2[47-42] + // 42: call TERMINATE procedure
|
|
// 41: initialize date @ H/L
|
|
pow2[47-40] + // 40: initialize time @ H/L
|
|
// 39: use only one breakout tape
|
|
pow2[47-38] + // 38: automatically print pbt
|
|
pow2[47-37] + // 37: clear write ready status @ terminal
|
|
pow2[47-36] + // 36: write disc. code on terminal
|
|
pow2[47-35] + // 35: type when compiler files open & close
|
|
pow2[47-34] + // 34: type file close
|
|
pow2[47-33] + // 33: error msgs when progr recovery used
|
|
pow2[47-32] + // 32: type MT retention messages
|
|
pow2[47-31] + // 31: type library messages
|
|
pow2[47-30] + // 30: type schedule messages
|
|
pow2[47-29] + // 29: type file security messages
|
|
pow2[47-28] + // 28: prevent I/O below user disk area
|
|
pow2[47-27] + // 27: prevent disk RELEASE statement
|
|
pow2[47-26] + // 26: printer backup disk release
|
|
// 25: check memory links
|
|
pow2[47-24] + // 24: type disk error messages
|
|
pow2[47-23] + // 23: disk logging
|
|
pow2[47-22] + // 22: suppress library error messages
|
|
pow2[47-21] + // 21: go to printer back-up only
|
|
pow2[47-20] + // 20: dont stack files on PB tapes
|
|
pow2[47-19] + // 19: print set or reset messages
|
|
// 18: no user disk will unload expired
|
|
pow2[47-17] + // 17: run all decks(SHAREDISK)
|
|
// 16: olay core to ECM(AUXMEM)
|
|
pow2[47-15] + // 15: job core estimates(STATISTICS)
|
|
// 14: olay data to ECM(AUXMEM)
|
|
pow2[47-13] + // 13: makes system hang on should-HL msg
|
|
// 12: enables datacom(TSS, if not DCP)
|
|
pow2[47-11] + // 11: library messages for CANDE
|
|
pow2[47-10] + // 10: ZIP decks to run on batch(SHAREDISK)
|
|
// 9: controls running of batch jobs on TSS
|
|
// 8: UNUSED
|
|
// 7: UNUSED
|
|
// 6: UNUSED
|
|
// 5: UNUSED
|
|
// 4: UNUSED
|
|
// 3: UNUSED
|
|
pow2[47- 2] + // 2: Model III I/O channels
|
|
// 1: UNUSED
|
|
0; // 0: (flag bit)
|
|
|
|
info[ 1] = b55Date; // YYDDD date as BIC
|
|
info[ 2] = config.eus; // number of EUs
|
|
info[ 3] = 0; // not used
|
|
info[ 4] = directoryEnd; // DIRECT deck option
|
|
info[ 5] = 0; // last number used for control deck
|
|
info[ 6] = 0; // first control deck queued
|
|
info[ 7] = 0; // last control deck queued
|
|
info[ 8] = 0; // next number available for printer backup disk
|
|
info[ 9] = 100*0x40000000; // multiprocessing core factor * 100
|
|
info[10] = 0; // SPO stations (through info[15])
|
|
info[11] = 0;
|
|
info[12] = 0;
|
|
info[13] = 0;
|
|
info[14] = 0;
|
|
info[15] = 0;
|
|
info[16] = 15; // Q value for datacom input
|
|
info[17] = 0; // remote SPO mask
|
|
info[18] = b55Time; // time of day (XCLOCK)
|
|
info[19] = 0; // FENCE address (TSSMCP only)
|
|
info[20] = 0; // log pointer
|
|
info[21] = 0; // status of schedule lines (TSSMCP only)
|
|
info[22] = 0; // log serial number fields
|
|
info[23] = 0; // not used
|
|
info[24] = 0; // DKA SU configuration
|
|
info[25] = 0; // DKA, continued
|
|
info[26] = 0; // DKB SU configuration (if no DFX)
|
|
info[27] = 0; // DKB, continued
|
|
info[28] = directoryTop; // disk address of DIRECTORYTOP
|
|
wordsToANSI(info, 0, 30, buffer, 0);
|
|
eu.put(buffer, directoryTop);
|
|
|
|
// Create a pseudo file for the disk directory itself
|
|
segNr = directoryTop + 4; // address of the directory
|
|
enterFile("DIRCTRY", "DISK", 1, directoryEnd-directoryTop-4);
|
|
segNr = directoryEnd + 4; // next address to allocate
|
|
|
|
// Create a file entry for the system log
|
|
createSystemLog(10000);
|
|
|
|
// Store the directory labels segment
|
|
eu.put(fileLabels, directoryTop + 19); // write the directory block file labels
|
|
}
|
|
|
|
/***** Start of initializeDisk *****/
|
|
|
|
wordsToANSI(shar, 0, 30, zeroes, 0); // create a segment buffer of zeroes
|
|
wordsToANSI(shar, 0, 30, fileLabels, 0); // initialize the file name label block
|
|
|
|
// Start a transaction
|
|
txn = db.transaction([euName, configName], "readwrite");
|
|
txn.oncomplete = function(ev) {
|
|
alert("Cold Start completed successfully");
|
|
};
|
|
|
|
eu = txn.objectStore(euName);
|
|
txn.objectStore(configName).get(0).onsuccess = function(ev) {
|
|
config = ev.target.result; // global assignment
|
|
if (!config) {
|
|
alert("No CONFIG object in Disk Subsystem database");
|
|
txn.abort();
|
|
} else {
|
|
// If we're going to cold start, may as well start with an empty EU store
|
|
eu.clear().onsuccess = function(ev) {
|
|
initializeDirectory(config);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/**************************************/
|
|
function configureDatabase(ev, req) {
|
|
/* Handles the onupgradeneeded event for the database. "ev" is the
|
|
event, "req" is the DB open request object */
|
|
var configStore = null;
|
|
var db = ev.target.result;
|
|
var stores = db.objectStoreNames;
|
|
var txn = req.transaction;
|
|
|
|
function configureEUs(configStore, config) {
|
|
var euName;
|
|
|
|
// Note: for now we will not worry about shrinking or deleting EUs that
|
|
// are in conflict with the contents of euSet.
|
|
|
|
for (euName in euSet) {
|
|
if (euName.indexOf(euPrefix) == 0) {
|
|
if (!config[euName]) {
|
|
config[euName] = euSet[euName];
|
|
}
|
|
if (stores.contains(euName)) {
|
|
if (euSet[euName] > config[euName]) {
|
|
config[euName] = euSet[euName];
|
|
}
|
|
} else {
|
|
config[euName] = euSet[euName];
|
|
config.eus++;
|
|
db.createObjectStore(euName);
|
|
}
|
|
}
|
|
}
|
|
configStore.put(config, 0);
|
|
}
|
|
|
|
switch (true) {
|
|
case ev.oldVersion < 1:
|
|
if (!confirm("retro-B5500 Disk Subsystem does not exist.\nDo you want to create it?")) {
|
|
txn.abort();
|
|
db.close();
|
|
db = null;
|
|
alert("No Disk Subsystem created -- please close this page");
|
|
} else {
|
|
config = {eus:0};
|
|
configStore = db.createObjectStore(configName);
|
|
configureEUs(configStore, config);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
alert("Disk Subsystem is higher version than Loader -- aborting");
|
|
} // switch
|
|
}
|
|
|
|
/**************************************/
|
|
function genericDBError(ev) {
|
|
/* Formats a generic alert when otherwise-unhandled database errors occur */
|
|
var db = ev.currentTarget.result;
|
|
|
|
alert("Disk Subsystem \"" + db.name + "\" error: " + ev.target.result.error);
|
|
}
|
|
|
|
/**************************************/
|
|
function dumpDisk() {
|
|
/* Dumps the initial and directory portions of the disk */
|
|
var txn = db.transaction("EU0");
|
|
var endKey = directoryTop+100;
|
|
var eu = txn.objectStore("EU0");
|
|
var range = IDBKeyRange.upperBound(endKey);
|
|
var req = eu.openCursor(range);
|
|
var lastKey = -1;
|
|
|
|
spout("===== START OF DISK DUMP =====");
|
|
req.onsuccess = function(ev) {
|
|
var cursor = ev.target.result;
|
|
|
|
if (cursor) {
|
|
if (cursor.key-lastKey > 1) {
|
|
spout("----- " + (cursor.key-lastKey-1) + " unallocated segments -----");
|
|
}
|
|
spout(cursor.key + ": " + ANSItoString(cursor.value, 0, cursor.value.length));
|
|
lastKey = cursor.key;
|
|
cursor.continue();
|
|
} else {
|
|
if (endKey > lastKey) {
|
|
spout("----- " + (endKey-lastKey) + " unallocated segments thru " + endKey + " -----");
|
|
}
|
|
spout("===== END OF DISK DUMP =====");
|
|
}
|
|
};
|
|
}
|
|
|
|
/**************************************/
|
|
function openDatabase(name) {
|
|
/* Attempts to open the disk subsystem database for the specified "name".
|
|
Stores the IDB database object in "db" if successful, or stores null
|
|
if unsuccessful */
|
|
var req;
|
|
|
|
req = window.indexedDB.open(name);
|
|
|
|
req.onerror = function(ev) {
|
|
alert("Cannot open retro-B5500 Disk Subsystem database:\n" + ev.target.error);
|
|
};
|
|
|
|
req.onblocked = function(ev) {
|
|
alert("Disk Subsystem open is blocked -- cannot continue");
|
|
};
|
|
|
|
req.onupgradeneeded = function(ev) {
|
|
configureDatabase(ev, req);
|
|
};
|
|
|
|
req.onsuccess = function(ev) {
|
|
var txn;
|
|
|
|
db = ev.target.result; // save the object reference globally for later use
|
|
db.onerror = genericDBError;
|
|
alert("Disk Subsystem opened: " + name + " #" + db.version);
|
|
$$("DeleteDBBtn").disabled = false;
|
|
|
|
// dumpDisk(); // <<<<<<<<<<<<<< DEBUG <<<<<<<<<<<<<<<<<
|
|
|
|
if (!db.objectStoreNames.contains(configName)) {
|
|
config = null;
|
|
alert("No CONFIG structure exists -- must delete & recreate DB");
|
|
} else {
|
|
txn = db.transaction(configName);
|
|
txn.objectStore(configName).get(0).onsuccess = function(ev) {
|
|
config = ev.target.result;
|
|
$$("ColdstartBtn").disabled = false;
|
|
$$("FileSelector").disabled = false;
|
|
};
|
|
}
|
|
};
|
|
}
|
|
|
|
/**************************************/
|
|
function deleteDiskDatabase(name) {
|
|
/* Attempts to permanently delete the disk subsystem database for the
|
|
specified "name". Clears the browser window if successful */
|
|
var req;
|
|
|
|
if (confirm("This will PERMANENTLY DELETE the retro-B5500 emulator's\nentire Disk Subsystem." +
|
|
"\n\nAre you sure you want to do this?")) {
|
|
if (confirm("Deletion of the Disk Subsystem CANNOT BE UNDONE.\n\nAre you really sure?")) {
|
|
if (db) {
|
|
db.close();
|
|
db = null; // invalidate the IDB handle
|
|
}
|
|
req = window.indexedDB.deleteDatabase(name);
|
|
|
|
req.onerror = function(ev) {
|
|
alert("Cannot delete the Disk Subsystem database:\n" + ev.target.error);
|
|
};
|
|
|
|
req.onblocked = function(ev) {
|
|
alert("Disk Subsystem delete is blocked -- cannot continue");
|
|
};
|
|
|
|
req.onsuccess = function(ev) {
|
|
document.open();
|
|
document.write("B5500ColdLoader terminated.");
|
|
document.close();
|
|
alert("retro-B5500 Disk Subsystem database deleted successfully." +
|
|
"\nPlease close this page.");
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function checkBrowser() {
|
|
/* Checks whether this browser can support the necessary stuff */
|
|
var missing = "";
|
|
|
|
if (!window.File) {missing += ", File"}
|
|
if (!window.FileReader) {missing += ", FileReader"}
|
|
if (!window.FileList) {missing += ", FileList"}
|
|
if (!window.Blob) {missing += ", Blob"}
|
|
if (!window.ArrayBuffer) {missing += ", ArrayBuffer"}
|
|
if (!window.DataView) {missing += ", DataView"}
|
|
if (!window.indexedDB) {missing += ", IndexedDB"}
|
|
|
|
if (missing.length == 0) {
|
|
return false;
|
|
} else {
|
|
alert("No can do... your browser does not support the following features:\n" +
|
|
missing.substring(2));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/********** Start of window.onload() **********/
|
|
|
|
if (!checkBrowser()) {
|
|
$$("FileSelector").addEventListener("change", fileSelector_onChange, false);
|
|
$$("ColdstartBtn").addEventListener("click", function(ev) {
|
|
if (confirm("Are you sure you want to do a COLD START?\n" +
|
|
"This will PERMANENTLY DELETE all MCP files in the B5500 Disk Subsystem.")) {
|
|
initializeDisk();
|
|
}
|
|
}, false);
|
|
$$("DeleteDBBtn").addEventListener("click", function(ev) {
|
|
deleteDiskDatabase(dbName);
|
|
});
|
|
$$("LoadBtn").addEventListener("click", loadFromTape);
|
|
openDatabase(dbName);
|
|
}
|
|
}, false);
|
|
</script>
|
|
|
|
<style>
|
|
BODY {
|
|
font-family: DejaVuSansWeb, sans-serif;
|
|
font-size: small}
|
|
TABLE {
|
|
border-collapse: collapse}
|
|
TH {
|
|
vertical-align: bottom}
|
|
#TapeDirDiv {
|
|
display: none}
|
|
#TapeDirBody {
|
|
font-family: DejaVuSansMonoBookWeb, monospace}
|
|
BUTTON {
|
|
height: 22px;
|
|
width: auto;
|
|
border: 2px solid black;}
|
|
.center {
|
|
text-align: center}
|
|
.rj {
|
|
text-align: right}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div style="position:relative; width:100%; height:4em">
|
|
<div style="position:absolute; left:0; top:0; width:auto">
|
|
<img src="./resources/retro-B5500-Logo.png" alt="retro-B5500 Logo" style="float:left">
|
|
Disk SubSystem Coldstart Loader
|
|
</div>
|
|
<div style="position:absolute; left:30em; top:0; width:auto">
|
|
<button id=ColdstartBtn type=button DISABLED>Cold Start</button>
|
|
|
|
<button id=DeleteDBBtn type=button DISABLED>Delete Disk Subsystem</button>
|
|
</div>
|
|
<div style="position:absolute; top:2em; left:30em; width:auto">
|
|
<input id=FileSelector type=file size=60 DISABLED>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div id=TapeDirDiv>
|
|
<table id=TapeDirTable border=1 cellspacing=0 cellpadding=1>
|
|
<thead>
|
|
<tr>
|
|
<th>Load
|
|
<th>Nr
|
|
<th>File ID
|
|
<th>as MCP
|
|
<th>as INT
|
|
<tbody id=TapeDirBody>
|
|
</table>
|
|
<br><input id=LoadBtn type=button value="Load" accessKey=L DISABLED>
|
|
</div>
|
|
|
|
<pre id=TextPanel>
|
|
</pre>
|
|
|
|
<div id=PageBottom>
|
|
</div>
|
|
|
|
</body>
|
|
</html> |