mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 19:51:51 +09:00
968 lines
32 KiB
Plaintext
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"])
|