// define exceptions function InterruptedException(m) { this.message = m; this.stack = (new Error()).stack; }; InterruptedException.prototype = Object.create(Error.prototype); InterruptedException.prototype.name = 'InterruptedException'; InterruptedException.prototype.constructor = InterruptedException; function IllegalAccessException(m) { this.message = m; this.stack = (new Error()).stack; }; IllegalAccessException.prototype = Object.create(Error.prototype); IllegalAccessException.prototype.name = 'IllegalAccessException'; IllegalAccessException.prototype.constructor = IllegalAccessException; class SIG { constructor(name, number) { this.name = "SIG" + name; this.number = number|0; } } const SIGTERM = new SIG("TERM",15); const SIGSEGV = new SIG("SEGV",11); function generateRandomHashStr(len) { let cs = 'qwfpgarstdzxcvbjluyhneiokmQWFPGARSTDZXCVBJLUYHNEIOKM'; let s = ''; for (let i = 0; i < len; i++) { s += cs[(Math.random()*cs.length)|0]; } return s; } // define TVDOS const _TVDOS = {}; _TVDOS.VERSION = "1.0"; _TVDOS.DRIVES = {}; // Object where key-value pair is : [serial-port, drive-number] _TVDOS.DRIVEFS = {}; // filesystem driver for the drive letter // actually figure out the drive letter association // Drive A is always the device we're currently on _TVDOS.DRIVES["A"] = _BIOS.FIRST_BOOTABLE_PORT _TVDOS.DRIVEFS["A"] = "SERIAL" //TODO _TVDOS.DRV = {} _TVDOS.getPath = function() { return [''].concat(_TVDOS.variables.PATH.split(';')); }; // initial values _TVDOS.variables = { DOSDIR: "\\tvdos", LANG: "EN", KEYBOARD: "us_qwerty", PATH: "\\tvdos\\bin;\\tbas;\\home", PATHEXT: ".com;.bat;.js", HELPPATH: "\\tvdos\\help", OS_NAME: "Terrarum Virtual DOS", OS_VERSION: _TVDOS.VERSION }; Object.freeze(_TVDOS); /////////////////////////////////////////////////////////////////////////////// _TVDOS.DRV.FS = {} class TVDOSFileDescriptor { constructor(path0, driverID) { let p = path0.replaceAll("/", "\\") // oh well... while (p.endsWith("\\")) { p = p.substring(0, p.length - 1) } serial.println(`TVDOSFileDescriptor input path: ${path0}, p = ${p}`) this._driveLetter = p[0] this._path = p.substring(2) // detaches A: this._driverID = driverID this._driver = _TVDOS.DRV.FS[driverID] } get size() { return this.driver.getFileLen(this) } get path() { return this._path } get driverID() { return this._driverID } get driver() { return this._driver } get driveLetter() { return this._driveLetter } get fullPath() { return `${this._driveLetter}:${this._path}` } /** reads the file bytewise and puts it to the specified memory address * @param count optional -- how many bytes to read * @param offset optional -- how many bytes to skip initially */ pread(ptr, count, offset) { this.driver.pread(this, ptr, count, offset) } /** @return bytewise contents of the file in JS array */ bread() { return this.driver.bread(this) } /** @return textwise contents of the file in JS string */ sread() { return this.driver.sread(this) } /** writes the bytes stored in the memory[ptr .. ptr+count-1] to file[offset .. offset+count-1] * - @param offset is optional */ pwrite(ptr, count, offset) { this.driver.pwrite(this, ptr, count, offset) } /** @param bytes bytewise contents to write, in JS array */ bwrite(bytes) { this.driver.bwrite(this, bytes) } /** @param string stringwise contents to write, in JS array */ swrite(string) { this.driver.swrite(this, string) } flush() { this.driver.flush(this) } close() { this.driver.close(this) } get isDirectory() { return this.driver.isDirectory(this) } get name() { return this.path.split("\\").last() } get parentPath() { // return this.path.split("\\").init().join("\\") let li = this.path.lastIndexOf("\\") return this.path.substring(0, li) } list() { if (!this.isDirectory) throw Error(`File is not a directory: ${this.path}`) return this.driver.listFiles(this) } /** When the file does not exist, mkfile() will be called; if you want to make a directory, use mkdir() */ touch() { return this.driver.touch(this) } /** Creates the directory named by this abstract pathname, including any necessary but nonexistent parent directories */ mkDir() { return this.driver.mkDir(this) } mkFile() { return this.driver.mkFile(this) } delete() { return this.driver.delete(this) } } _TVDOS.DRV.FS.SERIAL = {} _TVDOS.DRV.FS.ROMFS = {} _TVDOS.DRV.FS.DEVFS = {} // \dev\null zero full random lp fb0..fb3 mem pmem0..pmem7 com1..com4 _TVDOS.DRV.FS.SERIAL._toPorts = (driveLetter) => { if (driveLetter.toUpperCase === undefined) { throw Error("'"+driveLetter+"' (type: "+typeof driveLetter+") is not a valid drive letter") } var port = _TVDOS.DRIVES[driveLetter.toUpperCase()] if (port === undefined) { throw Error("Drive letter '" + driveLetter.toUpperCase() + "' does not exist") } return port } _TVDOS.DRV.FS.SERIAL._openr = (fd) => { let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) _TVDOS.DRV.FS.SERIAL._flush(port[0]);_TVDOS.DRV.FS.SERIAL._close(port[0]) com.sendMessage(port[0], "OPENR"+'"'+fd.path+'",'+port[1]) return com.getStatusCode(port[0]) } _TVDOS.DRV.FS.SERIAL._openw = (fd) => { let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) _TVDOS.DRV.FS.SERIAL._flush(port[0]);_TVDOS.DRV.FS.SERIAL._close(port[0]) com.sendMessage(port[0], "OPENW"+'"'+fd.path+'",'+port[1]) return com.getStatusCode(port[0]) } _TVDOS.DRV.FS.SERIAL._opena = (fd) => { let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) _TVDOS.DRV.FS.SERIAL._flush(port[0]);_TVDOS.DRV.FS.SERIAL._close(port[0]) com.sendMessage(port[0], "OPENA"+'"'+fd.path+'",'+port[1]) return com.getStatusCode(port[0]) } _TVDOS.DRV.FS.SERIAL._close = (portNo) => { com.sendMessage(portNo, "CLOSE") } _TVDOS.DRV.FS.SERIAL._flush = (portNo) => { com.sendMessage(portNo, "FLUSH") } _TVDOS.DRV.FS.SERIAL.close = (fd) => { let portNo = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) com.sendMessage(portNo, "CLOSE") } _TVDOS.DRV.FS.SERIAL.flush = (fd) => { let portNo = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) com.sendMessage(portNo, "FLUSH") } _TVDOS.DRV.FS.SERIAL.getFileLen = (fd) => { if (129 == _TVDOS.DRV.FS.SERIAL._openr(fd)) throw Error(`No such file: ${fd.path}`) let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) com.sendMessage(port[0], "GETLEN") var response = com.getStatusCode(port[0]) if (135 == response) { throw Error("File not opened") } if (response < 0 || response >= 128) { throw Error("Reading a file failed with "+response) } return Number(com.pullMessage(port[0])) } // TODO pread replaces DMA.comToRam // TODO pwrite replaces DMA.ramToCom _TVDOS.DRV.FS.SERIAL.sread = (fd) => { if (129 == _TVDOS.DRV.FS.SERIAL._openr(fd)) throw Error(`No such file: ${fd.path}`) let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) com.sendMessage(port[0], "READ") let response = com.getStatusCode(port[0]) if (135 == response) { throw Error("File not opened") } if (response < 0 || response >= 128) { throw Error("Reading a file failed with "+response) } return com.pullMessage(port[0]) } _TVDOS.DRV.FS.SERIAL.swrite = (fd, str) => { let rrrr = _TVDOS.DRV.FS.SERIAL._openw(fd); if (rrrr != 0) throw Error("Writing a file failed with "+rrrr) let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) com.sendMessage(port[0], "WRITE"+string.length) let response = com.getStatusCode(port[0]) if (135 == response) { throw Error("File not opened") } if (response < 0 || response >= 128) { throw Error("Writing a file failed with "+response) } com.sendMessage(port[0], string) _TVDOS.DRV.FS.SERIAL._flush(port[0]);_TVDOS.DRV.FS.SERIAL._close(port[0]) } _TVDOS.DRV.FS.SERIAL.bread = (fd) => { let str = _TVDOS.DRV.FS.SERIAL.sread(fd.driveLetter) let bytes = new Uint8Array(str.length) for (let i = 0; i < str.length; i++) { bytes[i] = str.charCodeAt(i) } return bytes } _TVDOS.DRV.FS.SERIAL.bwrite = (fd, bytes) => { // pwrite replaces DMA.ramToCom let string = String.fromCharCode.apply(null, bytes) // no spreading: has length limit _TVDOS.DRV.FS.SERIAL.swrite(fd, string) } _TVDOS.DRV.FS.SERIAL.isDirectory = (fd) => { if (129 == _TVDOS.DRV.FS.SERIAL._openr(fd)) throw Error(`No such file: ${fd.path}`) let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) com.sendMessage(port[0], "LISTFILES") let response = com.getStatusCode(port[0]) return (response === 0) } _TVDOS.DRV.FS.SERIAL.listFiles = (fd) => { if (129 == _TVDOS.DRV.FS.SERIAL._openr(fd)) throw Error(`No such file: ${fd.path}`) let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) com.sendMessage(port[0], "LISTFILES") let response = com.getStatusCode(port[0]) if (response !== 0) return undefined let rawStr = com.pullMessage(port[0]) // {\x11 | \x12} [ \x1E {\x11 | \x12} ] \x17 let fdsInDir = [] let parentPath = fd.fullPath rawStr.substring(0, rawStr.length).split('\x1E').forEach(it => { let filename = it.substring(1) let newfd = new TVDOSFileDescriptor(`${parentPath}/${filename}`, "SERIAL") fdsInDir.push(newfd) }) return fdsInDir } _TVDOS.DRV.FS.SERIAL.touch = (fd) => { let rrrr = _TVDOS.DRV.FS.SERIAL._openw(fd); if (rrrr != 0) throw Error("Touching a file failed with "+rrrr) let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) com.sendMessage(port[0], "TOUCH") let response = com.getStatusCode(port[0]) return (response === 0) } _TVDOS.DRV.FS.SERIAL.mkDir = (fd) => { let rrrr = _TVDOS.DRV.FS.SERIAL._openw(fd); if (rrrr != 0) throw Error("Mkdir a file failed with "+rrrr) let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) com.sendMessage(port[0], "MKDIR") let response = com.getStatusCode(port[0]) if (response < 0 || response >= 128) { var status = com.getDeviceStatus(port[0]) throw Error(`Creating a directory failed with (${response}): ${status.message}\n`) } return (response === 0) // possible status codes: 0 (success), 1 (fail) } _TVDOS.DRV.FS.SERIAL.mkFile = (fd) => { let rrrr = _TVDOS.DRV.FS.SERIAL._openw(fd); if (rrrr != 0) throw Error("Mkfile failed with "+rrrr) let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) com.sendMessage(port[0], "MKFILE") let response = com.getStatusCode(port[0]) return (response === 0) } _TVDOS.DRV.FS.SERIAL.delete = (fd) => { let rrrr = _TVDOS.DRV.FS.SERIAL._openw(fd); if (rrrr != 0) throw Error("Writing a file failed with "+rrrr) let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) com.sendMessage(port[0], "DELETE") let response = com.getStatusCode(port[0]) return (response === 0) } Object.freeze(_TVDOS.DRV.FS.SERIAL) Object.freeze(_TVDOS.DRV.FS.ROMFS) Object.freeze(_TVDOS.DRV.FS.DEVFS) /////////////////////////////////////////////////////////////////////////////// const filesystem = {}; filesystem._toPorts = (driveLetter) => { if (driveLetter.toUpperCase === undefined) { throw Error("'"+driveLetter+"' (type: "+typeof driveLetter+") is not a valid drive letter"); } var port = _TVDOS.DRIVES[driveLetter.toUpperCase()]; if (port === undefined) { throw Error("Drive letter '" + driveLetter.toUpperCase() + "' does not exist"); } return port }; filesystem._close = (portNo) => { com.sendMessage(portNo, "CLOSE") } filesystem._flush = (portNo) => { com.sendMessage(portNo, "FLUSH") } // @return disk status code (0 for successful operation) // throws if: // - java.lang.NullPointerException if path is null // - Error if operation mode is not "R", "W" nor "A" filesystem.open = (driveLetter, path, operationMode) => { var port = filesystem._toPorts(driveLetter); filesystem._flush(port[0]); filesystem._close(port[0]); var mode = operationMode.toUpperCase(); if (mode != "R" && mode != "W" && mode != "A") { throw Error("Unknown file opening mode: " + mode); } com.sendMessage(port[0], "OPEN"+mode+'"'+path+'",'+port[1]); return com.getStatusCode(port[0]); }; filesystem.getFileLen = (driveLetter) => { var port = filesystem._toPorts(driveLetter); com.sendMessage(port[0], "GETLEN"); var response = com.getStatusCode(port[0]); if (135 == response) { throw Error("File not opened"); } if (response < 0 || response >= 128) { throw Error("Reading a file failed with "+response); } return Number(com.pullMessage(port[0])); }; // @return the entire contents of the file in String filesystem.readAll = (driveLetter) => { var port = filesystem._toPorts(driveLetter); com.sendMessage(port[0], "READ"); var response = com.getStatusCode(port[0]); if (135 == response) { throw Error("File not opened"); } if (response < 0 || response >= 128) { throw Error("Reading a file failed with "+response); } return com.pullMessage(port[0]); }; filesystem.readAllBytes = (driveLetter) => { var str = filesystem.readAll(driveLetter); var bytes = new Uint8Array(str.length); for (let i = 0; i < str.length; i++) { bytes[i] = str.charCodeAt(i); } return bytes; }; filesystem.write = (driveLetter, string) => { var port = filesystem._toPorts(driveLetter); com.sendMessage(port[0], "WRITE"+string.length); var response = com.getStatusCode(port[0]); if (135 == response) { throw Error("File not opened"); } if (response < 0 || response >= 128) { throw Error("Writing a file failed with "+response); } com.sendMessage(port[0], string); filesystem._flush(port[0]); filesystem._close(port[0]); }; filesystem.writeBytes = (driveLetter, bytes) => { var string = String.fromCharCode.apply(null, bytes); // no spreading: has length limit filesystem.write(driveLetter, string); }; filesystem.isDirectory = (driveLetter) => { var port = filesystem._toPorts(driveLetter); com.sendMessage(port[0], "LISTFILES"); var response = com.getStatusCode(port[0]); return (response === 0); }; filesystem.mkDir = (driveLetter) => { var port = filesystem._toPorts(driveLetter); com.sendMessage(port[0], "MKDIR"); var response = com.getStatusCode(port[0]); if (response < 0 || response >= 128) { var status = com.getDeviceStatus(port[0]); throw Error("Creating a directory failed with ("+response+"): "+status.message+"\n"); } return (response === 0); // possible status codes: 0 (success), 1 (fail) }; filesystem.touch = (driveLetter) => { var port = filesystem._toPorts(driveLetter); com.sendMessage(port[0], "TOUCH"); var response = com.getStatusCode(port[0]); return (response === 0); }; filesystem.mkFile = (driveLetter) => { var port = filesystem._toPorts(driveLetter); com.sendMessage(port[0], "MKFILE"); var response = com.getStatusCode(port[0]); return (response === 0); }; filesystem.delete = (driveLetter) => { var port = filesystem._toPorts(driveLetter); com.sendMessage(port[0], "DELETE"); var response = com.getStatusCode(port[0]); return (response === 0); }; Object.freeze(filesystem); /////////////////////////////////////////////////////////////////////////////// const files = {} /** This function only creates a file descriptor; will not actually interact with the drives yet. */ files.open = (fullpath) => { if (fullpath[2] != '/' && fullpath[2] != '\\') throw Error("Expected full path with drive letter") let driveLetter = fullpath[0].toUpperCase() let driver = _TVDOS.DRIVEFS[driveLetter] return new TVDOSFileDescriptor(fullpath, driver) } Object.freeze(files) /////////////////////////////////////////////////////////////////////////////// const input = {}; const inputwork = {}; inputwork.keymap = []; input.changeKeyLayout = function(name) { let res0 = filesystem.open("A",`tvdos/${name.toLowerCase()}.key`,"R"); if (res0 != 0 && inputwork.keymap.length == 0) throw new Error(`I/O Error ${res0} - A:\\tvdos\\${name.toLowerCase()}.key`); try { inputwork.keymap = eval(filesystem.readAll("A")); } catch (e) { printerrln(e); return -1; } } // load initial key layout input.changeKeyLayout(_TVDOS.variables.KEYBOARD || "us_qwerty"); // states to run the keyboard input inputwork.stroboTime = 4294967296*0; inputwork.stroboDelays = [0,250000000,0,25000000,0]; // 250ms, 25ms inputwork.stroboStatus = 0; // 0: first key, 1: waiting for initial delay, 2: repeating key, 3: waiting for repeat delay inputwork.oldKeys = []; inputwork.oldMouse = []; inputwork.repeatCount = 0; //inputwork.keyChanged = false; /** * callback: takes one argument of object which * [ , args ] * where: * "key_down", , , keycode0, keycode1 .. keycode7 * "key_change", , 0, keycode0, keycode1 .. keycode7 (remaining keys that are held down) * "mouse_down", pos-x, pos-y, 1 // yes there's only one mouse button :p * "mouse_up", pos-x, pos-y, 0 * "mouse_move", pos-x, pos-y,