Files
tsvm/assets/disk0/tvdos/TVDOS.SYS
2022-09-06 20:30:46 +09:00

968 lines
32 KiB
Plaintext

// 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 <drive-letter> : [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) {
if (driverID === undefined) {
if (!files.reservedNames.includes(path0)) {
throw Error(`${path0} is not a valid device file`)
}
this._driveLetter = undefined
this._path = path0
this._driverID = `DEV${path0}`
this._driver = _TVDOS.DRV.FS[`DEV${path0}`] // can't just put `driverID` here
}
else {
let p = path0.replaceAll("/", "\\")
// oh well...
while (p.endsWith("\\")) {
p = p.substring(0, p.length - 1)
}
this._driveLetter = p[0]
this._path = p.substring(2) // detaches A:
this._driverID = driverID
this._driver = _TVDOS.DRV.FS[driverID]
serial.println(`TVDOSFileDescriptor input path: ${path0}, p = ${p}, driveLetter = ${this._driveLetter}, path = ${this._path}`)
}
}
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() {
return (!this.isDirectory) ? undefined : 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)
}
remove() {
return this.driver.remove(this)
}
get exists() {
return this.driver.exists(this)
}
}
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS.SERIAL = {}
// TODO \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[0], "CLOSE")
}
_TVDOS.DRV.FS.SERIAL.flush = (fd) => {
let portNo = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter)
com.sendMessage(portNo[0], "FLUSH")
}
_TVDOS.DRV.FS.SERIAL.getFileLen = (fd) => {
if (129 == _TVDOS.DRV.FS.SERIAL._openr(fd)) throw Error(`No such file: ${fd.fullPath}`)
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.fullPath}`)
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, string) => {
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)
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)) return false // file not exists
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.fullPath}`)
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} <name> [ \x1E {\x11 | \x12} <name> ] \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.remove = (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
}
_TVDOS.DRV.FS.SERIAL.exists = (fd) => {
return (0 == _TVDOS.DRV.FS.SERIAL._openr(fd))
}
Object.freeze(_TVDOS.DRV.FS.SERIAL)
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS.DEVRND = {}
_TVDOS.DRV.FS.DEVRND._mkrndbytes = (length) => {
let bytes = new Uint8Array(length)
let timeNow = sys.currentTimeInMills()
let state = 12345678910 + timeNow
for (let i = 0; i < length; i++) {
let x = state
x ^= x << 13
x ^= x >> 7
x ^= x << 17
state = x
bytes[i] = state & 255
}
return bytes
}
_TVDOS.DRV.FS.DEVRND.pread = (fd, ptr, count, offset) => {
let bytes = _TVDOS.DRV.FS.DEVRND._mkrndbytes(count + offset)
for (let i = 0; i < count; i++) {
sys.poke(ptr + i, bytes[offset + i])
}
}
_TVDOS.DRV.FS.DEVRND.pwrite = (fd, ptr, count, offset) => {}
_TVDOS.DRV.FS.DEVRND.bwrite = (fd, bytes) => {}
_TVDOS.DRV.FS.DEVRND.swrite = (fd, string) => {}
_TVDOS.DRV.FS.DEVRND.flush = () => {}
_TVDOS.DRV.FS.DEVRND.close = () => {}
_TVDOS.DRV.FS.DEVRND.isDirectory = () => false
_TVDOS.DRV.FS.DEVRND.listFiles = () => undefined
_TVDOS.DRV.FS.DEVRND.touch = () => {}
_TVDOS.DRV.FS.DEVRND.mkDir = () => {}
_TVDOS.DRV.FS.DEVRND.mkFile = () => {}
_TVDOS.DRV.FS.DEVRND.remove = () => {}
_TVDOS.DRV.FS.DEVRND.exists = () => true
Object.freeze(_TVDOS.DRV.FS.DEVRND)
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS.DEVNUL = {}
_TVDOS.DRV.FS.DEVNUL.pread = (fd, ptr, count, offset) => {
for (let i = 0; i < count; i++) {
sys.poke(ptr + i, -1)
}
}
_TVDOS.DRV.FS.DEVNUL.bread = (fd) => { return [] }
_TVDOS.DRV.FS.DEVNUL.sread = (fd) => { return "" }
_TVDOS.DRV.FS.DEVNUL.pwrite = (fd, ptr, count, offset) => {}
_TVDOS.DRV.FS.DEVNUL.bwrite = (fd, bytes) => {}
_TVDOS.DRV.FS.DEVNUL.swrite = (fd, string) => {}
_TVDOS.DRV.FS.DEVNUL.flush = () => {}
_TVDOS.DRV.FS.DEVNUL.close = () => {}
_TVDOS.DRV.FS.DEVNUL.isDirectory = () => false
_TVDOS.DRV.FS.DEVNUL.listFiles = () => undefined
_TVDOS.DRV.FS.DEVNUL.touch = () => {}
_TVDOS.DRV.FS.DEVNUL.mkDir = () => {}
_TVDOS.DRV.FS.DEVNUL.mkFile = () => {}
_TVDOS.DRV.FS.DEVNUL.remove = () => {}
_TVDOS.DRV.FS.DEVNUL.exists = () => true
Object.freeze(_TVDOS.DRV.FS.DEVNUL)
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS.DEVZERO = {}
_TVDOS.DRV.FS.DEVZERO.pread = (fd, ptr, count, offset) => {
for (let i = 0; i < count; i++) {
sys.poke(ptr + i, 0)
}
}
_TVDOS.DRV.FS.DEVZERO.pwrite = (fd, ptr, count, offset) => {}
_TVDOS.DRV.FS.DEVZERO.bwrite = (fd, bytes) => {}
_TVDOS.DRV.FS.DEVZERO.swrite = (fd, string) => {}
_TVDOS.DRV.FS.DEVZERO.flush = () => {}
_TVDOS.DRV.FS.DEVZERO.close = () => {}
_TVDOS.DRV.FS.DEVZERO.isDirectory = () => false
_TVDOS.DRV.FS.DEVZERO.listFiles = () => undefined
_TVDOS.DRV.FS.DEVZERO.touch = () => {}
_TVDOS.DRV.FS.DEVZERO.mkDir = () => {}
_TVDOS.DRV.FS.DEVZERO.mkFile = () => {}
_TVDOS.DRV.FS.DEVZERO.remove = () => {}
_TVDOS.DRV.FS.DEVZERO.exists = () => true
Object.freeze(_TVDOS.DRV.FS.DEVZERO)
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS.DEVCON = {}
_TVDOS.DRV.FS.DEVCON.pread = (fd, ptr, count, offset) => {
let mem = graphics.getGpuMemBase()
let consize = Math.min(count, grapihcs.getTermDimension().sum())
for (let i = 0; i < consize; i++) {
sys.poke(ptr + i, sys.peek(mem - 5122 - i))
}
}
_TVDOS.DRV.FS.DEVCON.bread = (fd) => {
let mem = graphics.getGpuMemBase()
let consize = Math.min(count, grapihcs.getTermDimension().sum())
let r = new Uint8Array(consize)
for (let i = 0; i < consize; i++) {
r[i] = sys.peek(mem - 5122 - i)
}
return r
}
_TVDOS.DRV.FS.DEVCON.sread = (fd) => {
let mem = graphics.getGpuMemBase()
let consize = Math.min(count, grapihcs.getTermDimension().sum())
let r = ''
for (let i = 0; i < consize; i++) {
r += String.fromCharCode(sys.peek(mem - 5122 - i))
}
return r
}
_TVDOS.DRV.FS.DEVCON.pwrite = (fd, ptr, count, offset) => {}
_TVDOS.DRV.FS.DEVCON.bwrite = (fd, bytes) => {
let string = String.fromCharCode.apply(null, bytes) // no spreading: has length limit
_TVDOS.DRV.FS.DEVZERO.swrite(fd, string)
}
_TVDOS.DRV.FS.DEVCON.swrite = (fd, string) => {
print(string)
}
_TVDOS.DRV.FS.DEVCON.flush = () => {}
_TVDOS.DRV.FS.DEVCON.close = () => {}
_TVDOS.DRV.FS.DEVCON.isDirectory = () => false
_TVDOS.DRV.FS.DEVCON.listFiles = () => undefined
_TVDOS.DRV.FS.DEVCON.touch = () => {}
_TVDOS.DRV.FS.DEVCON.mkDir = () => {}
_TVDOS.DRV.FS.DEVCON.mkFile = () => {}
_TVDOS.DRV.FS.DEVCON.remove = () => {}
_TVDOS.DRV.FS.DEVCON.exists = () => true
Object.freeze(_TVDOS.DRV.FS.DEVCON)
///////////////////////////////////////////////////////////////////////////////
_TVDOS.DRV.FS.DEVFBIPF = {}
_TVDOS.DRV.FS.DEVFBIPF.pwrite = (fd, ptr, _1, _2) => {
let decodefun = ([graphics.decodeIpf1, graphics.decodeIpf2])[sys.peek(ptr + 13)]
// offset 24..27: gzipped size
let width = sys.peek(ptr+8) | (sys.peek(ptr+9) << 8)
let height = sys.peek(ptr+10) | (sys.peek(ptr+11) << 8)
let hasAlpha = (sys.peek(12) != 0)
// println("Calling GPU")
decodefun(ptr + 24, -1048577, -1310721, width, height, hasAlpha)
// println("Changing graphics mode")
graphics.setGraphicsMode(4)
// println("cya!")
}
_TVDOS.DRV.FS.DEVFBIPF.bwrite = (fd, bytes) => {
// TODO pread the file
let fp = sys.malloc(bytes.length)
bytes.forEach((it,i) => {
sys.poke(fp + i, it)
})
_TVDOS.DRV.FS.DEVFBIPF.pwrite(fd, fp)
sys.free(fp)
}
// _TVDOS.DRV.FS.DEVFBIPF will be write-only: no way to select IPF1/2 and/or transparency
_TVDOS.DRV.FS.DEVFBIPF.flush = () => {}
_TVDOS.DRV.FS.DEVFBIPF.close = () => {}
_TVDOS.DRV.FS.DEVFBIPF.isDirectory = () => false
_TVDOS.DRV.FS.DEVFBIPF.listFiles = () => undefined
_TVDOS.DRV.FS.DEVFBIPF.touch = () => {}
_TVDOS.DRV.FS.DEVFBIPF.mkDir = () => {}
_TVDOS.DRV.FS.DEVFBIPF.mkFile = () => {}
_TVDOS.DRV.FS.DEVFBIPF.remove = () => {}
_TVDOS.DRV.FS.DEVFBIPF.exists = () => true
Object.freeze(_TVDOS.DRV.FS.DEVFBIPF)
///////////////////////////////////////////////////////////////////////////////
// Legacy Serial filesystem, !!pending for removal!!
/*
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.remove = (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 = {}
files.reservedNames = ["AUX", // unused
"COM1","COM2","COM3","COM4", // serial ports
"CON", // the tty
"FB1","FB2", // raw framebuffer, 256 colours, typ. 560x448
"FBIPF", // framebuffer that accepts IPF-formatted bytes, reduced colours, typ. 560x448
"HFB1","HFB2","HFB3","HFB4", // half-dimension framebuffers, 256 colours, typ. 280x224
"LPT1","LPT2","LPT3","LPT4", // reserved for "parallel ports"; unused
"MEM", // /dev/mem
"NUL", // /dev/null
"PMEM0","PMEM1","PMEM2","PMEM3","PMEM4","PMEM5","PMEM6","PMEM7", // /dev/mem for peripherals
"PRN", // a printer
"RND", // /dev/urandom
"XFB", // raw framebuffer, 4096 colours, typ. 560x448. Memory layout follows the gpu's (0..250779: RG-plane, 250880..262143: gap, 262144..513023: BA-plane)
"ZERO"] // /dev/zero
/** This function only creates a file descriptor; will not actually interact with the drives yet. */
files.open = (fullPath) => {
if (files.reservedNames.includes(fullPath.toUpperCase())) {
return new TVDOSFileDescriptor(fullPath.toUpperCase())
}
if (fullPath[2] != '/' && fullPath[2] != '\\') throw Error("Expected full path with drive letter, got " + fullPath)
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 f = files.open(`A:/tvdos/${name.toLowerCase()}.key`)
let res0 = f.sread().trim()
if (res0.length == 0 && inputwork.keymap.length == 0) throw new Error(`I/O Error on ${f.fullPath}`)
try {
inputwork.keymap = eval(res0)
}
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
* [ <eventname>, args ]
* where:
* "key_down", <key symbol string>, <repeat count>, keycode0, keycode1 .. keycode7
* "key_change", <key symbol string (what went up)>, 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, <button down?>, oldpos-x, oldpos-y
*/
input.withEvent = function(callback) {
// TODO mouse event
function arrayEq(a,b) {
for (let i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}
function arrayDiff(a,b) {
return a.filter(x => !b.includes(x));
}
function keysToStr(keys) {
//let shiftin = keys.includes(59) || keys.includes(60);
let headkey = keys.head();
//return (inputwork.keymap[headkey] == undefined) ? undefined : (shiftin) ? (inputwork.keymap[headkey][1] || inputwork.keymap[headkey][0]) : inputwork.keymap[headkey][0];
return (inputwork.keymap[headkey] == undefined) ? undefined : inputwork.keymap[headkey];
}
sys.poke(-40, 255);
let keys = [sys.peek(-41),sys.peek(-42),sys.peek(-43),sys.peek(-44),sys.peek(-45),sys.peek(-46),sys.peek(-47),sys.peek(-48)];
let mouse = [sys.peek(-33) | (sys.peek(-34) << 8), sys.peek(-35) | (sys.peek(-36) << 8), sys.peek(-37)];
let keyChanged = !arrayEq(keys, inputwork.oldKeys)
let keyDiff = arrayDiff(keys, inputwork.oldKeys)
// FIXME does not work with QMK's "LT(1,KC_ENT)" or something similar
//if (keys.sum() > 0) serial.println(`! captured=${keys.join()}`)
if (inputwork.stroboStatus % 2 == 0 && keys[0] != 0) {
inputwork.stroboStatus += 1;
inputwork.stroboTime = sys.nanoTime();
inputwork.repeatCount += 1;
let shiftin = keys.includes(59) || keys.includes(60);
let keysym0 = keysToStr(keys);
let newKeysym0 = keysToStr(keyDiff);
let keysym = (keysym0 === undefined) ? undefined : (shiftin && keysym0[1]) ? keysym0[1] : keysym0[0];
let newKeysym = (newKeysym0 === undefined) ? undefined : (shiftin && newKeysym0[1]) ? newKeysym0[1] : newKeysym0[0];
//serial.println(`keys=${keys.join()}; oldkeys=${inputwork.oldKeys}; keyDiff=${keyDiff}`)
//serial.println(`keysym=${keysym}; newkeysym=${newKeysym}`)
if (!keyChanged) {
callback(["key_down", keysym, inputwork.repeatCount].concat(keys));
}
else if (newKeysym !== undefined) {
callback(["key_down", newKeysym, inputwork.repeatCount].concat(keys));
}
inputwork.oldKeys = keys; // don't put this outside of if-cascade
}
else if (keyChanged || keys[0] == 0) {
inputwork.stroboStatus = 0;
inputwork.repeatCount = 0;
if (keys[0] == 0)
inputwork.keyChanged = false;
if (keyChanged) {
//callback(["key_change", keysToStr(arrayDiff(keys, inputwork.oldKeys)), inputwork.repeatCount].concat(keys));
//serial.println(`$ oldkeys=${inputwork.oldKeys}; newkeys=${keys}`)
//serial.println(`$ keydiff=${arrayDiff(keys, inputwork.oldKeys)}; newkeysym=${keysToStr(arrayDiff(keys, inputwork.oldKeys))}`)
//inputwork.keyChanged = keysToStr(arrayDiff(keys, inputwork.oldKeys));
}
}
else if (inputwork.stroboStatus % 2 == 1 && sys.nanoTime() - inputwork.stroboTime < inputwork.stroboDelays[inputwork.stroboStatus]) {
sys.spin();
}
else {
inputwork.stroboStatus += 1;
if (inputwork.stroboStatus >= 4) {
inputwork.stroboStatus = 2;
}
}
inputwork.oldMouse = mouse;
};
Object.freeze(input);
///////////////////////////////////////////////////////////////////////////////
const unicode = {};
unicode.utf8toCodepoints = function(utf8text) {
let UTF8_ACCEPT = 0
let UTF8D = [
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
12,36,12,12,12,12,12,12,12,12,12,12,
]
let state = UTF8_ACCEPT
let codep = 0
let codepoints = []
for (let i=0; i < utf8text.length; i++) {
let byte = utf8text.charCodeAt(i)
let type = UTF8D[byte]
codep = (state != UTF8_ACCEPT) ?
(byte & 0x3f) | (codep << 6) : (0xff >> type) & (byte)
state = UTF8D[256 + state + type]
if (state == UTF8_ACCEPT)
codepoints.push(codep)
}
return codepoints
}
// array of array: [predicate_for_coderange: (Codepoint) -> Boolean , printing_function: (Codepoint) -> Unit_that_does_screen_drawing]
// Usage: unicode.uniprint.push[(c) => 0xAC00 <= c && c <= 0xD7A3, printHalfRowHangul]
unicode.uniprint = [];
unicode.uniprint.push([c => (0 <= c && c <= 255), c => { sys.print(String.fromCodePoint(c)) }]);
// @return [predicate, printing function]
unicode.getUniprint = (c) => {
for (let k = 0; k < unicode.uniprint.length; k++) {
if (unicode.uniprint[k][0](c))
return unicode.uniprint[k]
}}
print = function(str) {
if ((typeof str === 'string' || str instanceof String) && str.length > 0) {
let cp = unicode.utf8toCodepoints(str)
cp.forEach(c => {
let q = unicode.getUniprint(c)
if (q == undefined || !q[0](c)) {
con.addch(4)
con.curs_right()
}
else {
q[1](c)
}
})
}
}
Object.freeze(unicode);
///////////////////////////////////////////////////////////////////////////////
// install other stuffs
let glfile = files.open("A:/tvdos/gl.js")
const GL = eval(glfile.sread())
let checkTerm = `if (sys.peek(-49)&1) throw new InterruptedException();`
let injectIntChk = (s, n) => {
// primitive way of injecting a code; will replace a JS string that matches the regex...
let k = s
.replace(/while *\([^\n]+\) *{/, "$& "+n+"();")
.replace(/for *\([^\n]+\) *{/, "$& "+n+"();")
.replace(/do *{/, "$& "+n+"();");
//serial.println(k);
return k;
}
// @param cmdsrc JS source code
// @param args arguments for the program, must be Array, and args[0] is always the name of the program, e.g.
// for command line 'echo foo bar', args[0] must be 'echo'
// @return status returned by the program
var execApp = (cmdsrc, args, appname) => {
appname = (appname) ? `_${appname}` : "_appStub"
var intchkFunName = `tvdosSIGTERM_${generateRandomHashStr(16)}`;
var execAppPrg = eval(
`var ${intchkFunName} = function(){ ${checkTerm} };` +
`var ${appname}=function(exec_args){${injectIntChk(cmdsrc, intchkFunName)}\n};` +
`${appname}`); // making 'exec_args' a app-level global
execAppPrg(args);
}
///////////////////////////////////////////////////////////////////////////////
// Boot script
serial.println("TVDOS.SYS initialised, running boot script...");
var _G = {};
let cmdfile = files.open("A:/tvdos/bin/command.js")
eval(`var _AUTOEXEC=function(exec_args){${cmdfile.sread()}\n};` +
`_AUTOEXEC`)(["", "/c", "\\AUTOEXEC.BAT"])