diff --git a/assets/JS_INIT.js b/assets/JS_INIT.js index 766a2c7..d9081fe 100644 --- a/assets/JS_INIT.js +++ b/assets/JS_INIT.js @@ -334,6 +334,18 @@ Array.prototype.tail = function() { Array.prototype.init = function() { return this.slice(0, this.length - 1) } +String.prototype.head = function() { + return this[0] +} +String.prototype.last = function() { + return this[this.length - 1] +} +String.prototype.tail = function() { + return this.substring(1) +} +String.prototype.init = function() { + return this.substring(0, this.length - 1) +} Array.prototype.shuffle = function() { let counter = this.length; diff --git a/assets/bios/tandemport.js b/assets/bios/tandemport.js index 3784a48..179ccac 100644 --- a/assets/bios/tandemport.js +++ b/assets/bios/tandemport.js @@ -8,19 +8,18 @@ for (let y = 0; y < 40; y++) { for (let x = 0; x < 30; x++) { let octet = imageBits[y * 30 + x] for (let i = 0; i < 8; i++) { - graphics.plotPixel(8*x + i, y+8, ((octet >>> (7 - i)) & 1 != 0) ? 255 : 239) + graphics.plotPixel(120 + 8*x + i, 36 + y, ((octet >>> (7 - i)) & 1 != 0) ? 255 : 239) } } } -con.move(8,1+(40-t.length>>1)) +con.move(13,1+(80-t.length>>1)) print(t) // wait arbitrary time -for (let b=0;b{ + println(`${it.path}\t${it.name}\t${it.size}`) +}) + +println(`Size again: ${f.size}`) \ No newline at end of file diff --git a/assets/disk0/memtest.js b/assets/disk0/memtest.js new file mode 100644 index 0000000..90dbeb2 --- /dev/null +++ b/assets/disk0/memtest.js @@ -0,0 +1,9 @@ +let ptr1 = sys.malloc(1024) +let ptr2 = sys.malloc(4321) +println(`ptr: ${ptr1} ${ptr2}; used mem: ${sys.getUsedMem()}`) +println(`freeing ptr1`) +sys.free(ptr1) +println(`used mem now: ${sys.getUsedMem()}`) +println(`freeing ptr2`) +sys.free(ptr2) +println(`used mem now: ${sys.getUsedMem()}`) \ No newline at end of file diff --git a/assets/disk0/nettest.js b/assets/disk0/nettest.js new file mode 100644 index 0000000..0195f77 --- /dev/null +++ b/assets/disk0/nettest.js @@ -0,0 +1,11 @@ +let url="http://localhost/testnet/test.txt" + +let file = files.open("B:\\"+url) + +if (!file.exists) { + printerrln("No such URL: "+url) + return 1 +} + +let text = file.sread() +println(text) diff --git a/assets/disk0/tvdos/TVDOS.SYS b/assets/disk0/tvdos/TVDOS.SYS index 046f461..41edd39 100644 --- a/assets/disk0/tvdos/TVDOS.SYS +++ b/assets/disk0/tvdos/TVDOS.SYS @@ -36,11 +36,38 @@ 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 +_TVDOS.DRIVEINFO = {}; // 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.DRIVEINFO["A"] = { + name: com.sendMessageGetBytes(_BIOS.FIRST_BOOTABLE_PORT[0], "DEVNAM\x17").init(), + type: com.sendMessageGetBytes(_BIOS.FIRST_BOOTABLE_PORT[0], "DEVTYP\x17").substring(0,4) +} + +// probe filesystem devices +let devnameToDriver = {"PRNT":"LP","STOR":"SERIAL","COMM":"COM","HTTP":"NET"} +let currentDriveLetter = ["A","B","C","D"] +for (let portNo = 1; portNo < 4; portNo++) { + if (com.areYouThere(portNo)) { + com.sendMessage(portNo, "DEVRST\x17") + sys.spin() + let devname = com.sendMessageGetBytes(portNo, "DEVTYP\x17").substring(0,4) + let driver = devnameToDriver[devname] + serial.println(`port: ${portNo}, devname: ${devname} ${com.sendMessageGetBytes(portNo, "DEVNAM\x17")}`) + if (driver) { + let dlet = currentDriveLetter[portNo] + _TVDOS.DRIVEFS[dlet] = driver + _TVDOS.DRIVES[dlet] = [portNo, 1] + _TVDOS.DRIVEINFO[dlet] = { + name: com.sendMessageGetBytes(portNo, "DEVNAM\x17").init(), + type: devname + } + } + } +} + _TVDOS.DRV = {} @@ -590,6 +617,40 @@ Object.freeze(_TVDOS.DRV.FS.DEVFBIPF) /////////////////////////////////////////////////////////////////////////////// +_TVDOS.DRV.FS.NET = {} + + +_TVDOS.DRV.FS.NET.sread = (fd) => { + let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) + let url = fd.path.substring(fd.path.indexOf("\\")+1).replaceAll("\\","/") + + serial.println("NETFILE GET " + url) + com.sendMessage(port[0], "GET " + url) + com.waitUntilReady(port[0]) + + return com.pullMessage(port[0]) +} + +_TVDOS.DRV.FS.NET.flush = () => {} +_TVDOS.DRV.FS.NET.close = () => {} +_TVDOS.DRV.FS.NET.isDirectory = () => false +_TVDOS.DRV.FS.NET.listFiles = () => undefined +_TVDOS.DRV.FS.NET.touch = () => {} +_TVDOS.DRV.FS.NET.mkDir = () => {} +_TVDOS.DRV.FS.NET.mkFile = () => {} +_TVDOS.DRV.FS.NET.remove = () => {} +_TVDOS.DRV.FS.NET.exists = (fd) => { + let port = _TVDOS.DRV.FS.SERIAL._toPorts(fd.driveLetter) + let url = fd.path.substring(fd.path.indexOf("\\")+1).replaceAll("\\","/") + + com.sendMessage(port[0], "GET " + url) + com.waitUntilReady(port[0]) + + return (0 == com.getStatusCode(port[0])) +} + +/////////////////////////////////////////////////////////////////////////////// + // Legacy Serial filesystem, !!pending for removal!! diff --git a/assets/disk0/tvdos/bin/drives.js b/assets/disk0/tvdos/bin/drives.js new file mode 100644 index 0000000..3c1ad67 --- /dev/null +++ b/assets/disk0/tvdos/bin/drives.js @@ -0,0 +1,5 @@ +Object.entries(_TVDOS.DRIVES).forEach(it=>{ + let [letter, [port, drivenum]] = it + let dinfo = _TVDOS.DRIVEINFO[letter] + println(`${letter}: COM${port+1},${drivenum} (${dinfo.name}-${dinfo.type})`) +}) diff --git a/doc/implementation.tex b/doc/implementation.tex index 0d7f16e..28072b9 100644 --- a/doc/implementation.tex +++ b/doc/implementation.tex @@ -25,6 +25,10 @@ Your Javascript program is stored into the Program Memory, and since its capacit \1\inlinesynopsis[Array]{init}{}{returns the subarray that omits the \emph{last} element.} \1\inlinesynopsis[Array]{sum}{selector}{returns the sum of the elements of the array. Selector is optionally defined to indicate how the value must be transformed to obtain the sum.} \1\inlinesynopsis[Array]{max}{selector}{returns the maximum among the elements of the array. Selector is optionally defined to indicate how the value must be transformed to obtain the max.} +\1\inlinesynopsis[String]{head}{}{returns the first character of the string.} +\1\inlinesynopsis[String]{last}{}{returns the last character of the string.} +\1\inlinesynopsis[String]{tail}{}{returns the substring that omits the \emph{head} character.} +\1\inlinesynopsis[String]{init}{}{returns the substring that omits the \emph{last} character.} \1\inlinesynopsis[String]{trimNull}{}{trims null characters at the \emph{end} of the string.} \end{outline} diff --git a/serialdev.txt b/serialdev.txt index 1748195..440bdb4 100644 --- a/serialdev.txt +++ b/serialdev.txt @@ -36,6 +36,7 @@ Returns: type of the device, of which but not exhaustive: - STOR: Storage device (floppy drive, etc.) - COMM: Modem (slave-mode device) - COMP: Modem (master-mode device, typically an other computer connected though a null-modem) +- HTTP: Internet Modem DEVNAM diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/HttpModem.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/HttpModem.kt new file mode 100644 index 0000000..7afc396 --- /dev/null +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/HttpModem.kt @@ -0,0 +1,177 @@ +package net.torvald.tsvm.peripheral + +import net.torvald.tsvm.VM +import net.torvald.tsvm.peripheral.TestDiskDrive.Companion.STATE_CODE_NO_SUCH_FILE_EXISTS +import net.torvald.tsvm.peripheral.TestDiskDrive.Companion.STATE_CODE_OPERATION_FAILED +import net.torvald.tsvm.peripheral.TestDiskDrive.Companion.STATE_CODE_SYSTEM_IO_ERROR +import net.torvald.tsvm.peripheral.TestDiskDrive.Companion.composePositiveAns +import java.io.* +import java.net.MalformedURLException +import java.net.URL + + +/** + * Created by minjaesong on 2022-09-22. + */ +class HttpModem(private val vm: VM) : BlockTransferInterface(false, true) { + + private val DBGPRN = true + + private fun printdbg(msg: Any) { + if (DBGPRN) println("[WgetModem] $msg") + } + + private var cnxOpen = false + private var cnxUrl: String? = null + + private val messageComposeBuffer = ByteArrayOutputStream(BLOCK_SIZE) // always use this and don't alter blockSendBuffer please + private var blockSendBuffer = ByteArray(1) + private var blockSendCount = 0 + + private var writeMode = false + private var writeModeLength = -1 + + + init { + statusCode = TestDiskDrive.STATE_CODE_STANDBY + } + + override fun hasNext(): Boolean { + return (blockSendCount * BLOCK_SIZE < blockSendBuffer.size) + } + + override fun startSendImpl(recipient: BlockTransferInterface): Int { + if (blockSendCount == 0) { + blockSendBuffer = messageComposeBuffer.toByteArray() + } + + val sendSize = if (blockSendBuffer.size - (blockSendCount * BLOCK_SIZE) < BLOCK_SIZE) + blockSendBuffer.size % BLOCK_SIZE + else BLOCK_SIZE + + recipient.writeout(ByteArray(sendSize) { + blockSendBuffer[blockSendCount * BLOCK_SIZE + it] + }) + + blockSendCount += 1 + + return sendSize + } + + private fun resetBuf() { + blockSendCount = 0 + messageComposeBuffer.reset() + } + + private fun selfReset() { + //readModeLength = -1 + cnxOpen = false + cnxUrl = null + blockSendCount = 0 + writeMode = false + writeModeLength = -1 + } + + private lateinit var writeBuffer: ByteArray + private var writeBufferUsage = 0 + + override fun writeoutImpl(inputData: ByteArray) { + if (writeMode) { + //println("[DiskDrive] writeout with inputdata length of ${inputData.size}") + //println("[DiskDriveMsg] ${inputData.toString(Charsets.UTF_8)}") + + if (!cnxOpen) throw InternalError("Connection is not established but the modem is in write mode") + + System.arraycopy(inputData, 0, writeBuffer, writeBufferUsage, minOf(writeModeLength - writeBufferUsage, inputData.size, BLOCK_SIZE)) + writeBufferUsage += inputData.size + + if (writeBufferUsage >= writeModeLength) { + // commit to the disk + TODO("do something with writeBuffer") + + writeMode = false + } + } + else { + val inputString = inputData.trimNull().toString(VM.CHARSET) + + if (inputString.startsWith("DEVRST\u0017")) { + printdbg("Device Reset") + selfReset() + statusCode = TestDiskDrive.STATE_CODE_STANDBY + } + else if (inputString.startsWith("DEVSTU\u0017")) + recipient?.writeout(composePositiveAns("${statusCode.toChar()}", TestDiskDrive.errorMsgs[statusCode])) + else if (inputString.startsWith("DEVTYP\u0017")) + recipient?.writeout(composePositiveAns("HTTP")) + else if (inputString.startsWith("DEVNAM\u0017")) + recipient?.writeout(composePositiveAns("Wget Company HTTP Modem")) + else if (inputString.startsWith("GET ")) { + if (cnxUrl != null) { + statusCode = TestDiskDrive.STATE_CODE_FILE_ALREADY_OPENED + return + } + + printdbg("msg: $inputString, lastIndex: ${inputString.lastIndex}") + + cnxUrl = inputString.substring(4).filter { it in '!'..'~' } + + printdbg("URL: $cnxUrl") + + this.ready = false + this.busy = true + + var httpIn: InputStream? = null + var bufferedOut: OutputStream? = null + resetBuf() + try { + // check the http connection before we do anything to the fs + httpIn = BufferedInputStream(URL(cnxUrl).openStream()) + messageComposeBuffer.reset() + bufferedOut = BufferedOutputStream(messageComposeBuffer, 1024) + val data = ByteArray(1024) + var fileComplete = false + var count = 0 + while (!fileComplete) { + count = httpIn.read(data, 0, 1024) + if (count <= 0) { + fileComplete = true + } else { + bufferedOut.write(data, 0, count) + } + } + statusCode = TestDiskDrive.STATE_CODE_STANDBY + } + catch (e: MalformedURLException) { + statusCode = STATE_CODE_NO_SUCH_FILE_EXISTS // MalformedUrl + printdbg("Malformed URL: $cnxUrl") + } + catch (e: IOException) { + statusCode = STATE_CODE_SYSTEM_IO_ERROR // IoException + printdbg("IOException: $cnxUrl") + } + finally { + try { + bufferedOut?.close() + messageComposeBuffer.close() + httpIn?.close() + } + catch (e: IOException) { + statusCode = STATE_CODE_OPERATION_FAILED // UnableToCloseOutputStream + printdbg("Unable to close: $cnxUrl") + } + finally { + printdbg("Data in the URL: ${messageComposeBuffer.toString(VM.CHARSET)}") + selfReset() + } + } + } + else + statusCode = TestDiskDrive.STATE_CODE_ILLEGAL_COMMAND + + } + + } + + +} \ No newline at end of file diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt index 6262ad7..50184b8 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt @@ -7,6 +7,9 @@ import java.io.FileInputStream import java.io.IOException import java.util.* +/** + * @param driveNum 0 for COM drive number 1, but the file path will still be zero-based + */ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath: File? = null) : BlockTransferInterface(false, true) { companion object { @@ -41,6 +44,17 @@ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath: errorMsgs[STATE_CODE_NOT_A_DIRECTORY] = "NOT A DIRECTORY" errorMsgs[STATE_CODE_NO_FILE_OPENED] = "NO FILE OPENED" } + + fun composePositiveAns(vararg msg: String): ByteArray { + val sb = ArrayList() + sb.addAll(msg[0].toByteArray().toTypedArray()) + for (k in 1 until msg.size) { + sb.add(UNIT_SEP) + sb.addAll(msg[k].toByteArray().toTypedArray()) + } + sb.add(END_OF_SEND_BLOCK) + return sb.toByteArray() + } } private val DBGPRN = true @@ -49,17 +63,6 @@ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath: if (DBGPRN) println("[TestDiskDrive] $msg") } - fun composePositiveAns(vararg msg: String): ByteArray { - val sb = ArrayList() - sb.addAll(msg[0].toByteArray().toTypedArray()) - for (k in 1 until msg.size) { - sb.add(UNIT_SEP) - sb.addAll(msg[k].toByteArray().toTypedArray()) - } - sb.add(END_OF_SEND_BLOCK) - return sb.toByteArray() - } - private val rootPath = theRootPath ?: File("test_assets/test_drive_$driveNum") private var fileOpen = false diff --git a/tsvm_executable/src/net/torvald/tsvm/AppLoader.java b/tsvm_executable/src/net/torvald/tsvm/AppLoader.java index 78d403c..2001c00 100644 --- a/tsvm_executable/src/net/torvald/tsvm/AppLoader.java +++ b/tsvm_executable/src/net/torvald/tsvm/AppLoader.java @@ -7,6 +7,8 @@ import kotlin.Pair; import kotlin.collections.CollectionsKt; import net.torvald.tsvm.peripheral.*; +import java.io.File; + public class AppLoader { public static String appTitle = "tsvm"; @@ -28,28 +30,37 @@ public class AppLoader { appConfig.setWindowedMode(WIDTH, HEIGHT); + + String diskPath = "assets/disk0"; + + // VM vm = new VM(64 << 10, new TheRealWorld(), new VMProgramRom[]{BasicBios.INSTANCE, BasicRom.INSTANCE}); // VM vm = new VM(64 << 10, new TheRealWorld(), new VMProgramRom[]{OEMBios.INSTANCE, BasicRom.INSTANCE}); VM vm = new VM("./assets", 64 << 10, new TheRealWorld(), new VMProgramRom[]{TandemBios.INSTANCE, BasicRom.INSTANCE}, 2); // VM vm = new VM(128 << 10, new TheRealWorld(), new VMProgramRom[]{BasicBios.INSTANCE, WPBios.INSTANCE}); // VM vm = new VM("./assets", 8192 << 10, new TheRealWorld(), new VMProgramRom[]{TsvmBios.INSTANCE}); // VM vm = new VM("./assets", 8192 << 10, new TheRealWorld(), new VMProgramRom[]{OpenBios.INSTANCE}, 8); - VM pipvm = new VM("./assets", 4096, new TheRealWorld(), new VMProgramRom[]{PipBios.INSTANCE, PipROM.INSTANCE}, 8); +// VM pipvm = new VM("./assets", 4096, new TheRealWorld(), new VMProgramRom[]{PipBios.INSTANCE, PipROM.INSTANCE}, 8); + + vm.getIO().getBlockTransferPorts()[0].attachDevice(new TestDiskDrive(vm, 0, new File(diskPath))); + vm.getIO().getBlockTransferPorts()[1].attachDevice(new HttpModem(vm)); + - String diskPath = "assets/disk0"; EmulInstance reference = new EmulInstance(vm, "net.torvald.tsvm.peripheral.ReferenceGraphicsAdapter2", diskPath, 560, 448); EmulInstance reference2 = new EmulInstance(vm, "net.torvald.tsvm.peripheral.ReferenceLikeLCD", diskPath, 560, 448); EmulInstance term = new EmulInstance(vm, "net.torvald.tsvm.peripheral.Term", diskPath, 720, 480); EmulInstance portable = new EmulInstance(vm, "net.torvald.tsvm.peripheral.CLCDDisplay", diskPath, 1080, 436); EmulInstance wp = new EmulInstance(vm, "net.torvald.tsvm.peripheral.WpTerm", "assets/wpdisk", 810, 360); - EmulInstance pip = new EmulInstance(pipvm, null, diskPath, 640, 480, CollectionsKt.listOf(new Pair(1, new PeripheralEntry2( + + + /*EmulInstance pip = new EmulInstance(pipvm, null, diskPath, 640, 480, CollectionsKt.listOf(new Pair(1, new PeripheralEntry2( 32768L, 1, 0, "net.torvald.tsvm.peripheral.ExtDisp", pipvm, 160, 140 - )))); + ))));*/ new Lwjgl3Application(new VMGUI(portable, WIDTH, HEIGHT), appConfig); } diff --git a/tsvm_executable/src/net/torvald/tsvm/VMGUI.kt b/tsvm_executable/src/net/torvald/tsvm/VMGUI.kt index a1d1399..01d68bc 100644 --- a/tsvm_executable/src/net/torvald/tsvm/VMGUI.kt +++ b/tsvm_executable/src/net/torvald/tsvm/VMGUI.kt @@ -76,8 +76,6 @@ class VMGUI(val loaderInfo: EmulInstance, val viewportWidth: Int, val viewportHe val loadedClassInstance = loadedClassConstructor.newInstance("./assets", vm, ) gpu = (loadedClassInstance as GraphicsAdapter) - vm.getIO().blockTransferPorts[0].attachDevice(TestDiskDrive(vm, 0, File(loaderInfo.diskPath))) - vm.peripheralTable[1] = PeripheralEntry( gpu, GraphicsAdapter.VRAM_SIZE, @@ -95,14 +93,6 @@ class VMGUI(val loaderInfo: EmulInstance, val viewportWidth: Int, val viewportHe vm.getInputStream = { System.`in` } } - vm.getIO().blockTransferPorts[1].attachDevice( - WorldRadar( - Gdx.files.internal( - "test_assets/test_terrain.png" - ) - ) - ) - loaderInfo.extraPeripherals.forEach { (port, peri) -> val typeargs = peri.args.map { it.javaClass }.toTypedArray()