mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-10 23:04:04 +09:00
codes split into modules: tsvm_core, tsvm_executable, TerranBASICexecutable
This commit is contained in:
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory
|
||||
21
tsvm_core/src/net/torvald/tsvm/Base64Delegate.kt
Normal file
21
tsvm_core/src/net/torvald/tsvm/Base64Delegate.kt
Normal file
@@ -0,0 +1,21 @@
|
||||
package net.torvald.tsvm
|
||||
|
||||
import com.badlogic.gdx.utils.Base64Coder
|
||||
|
||||
object Base64Delegate {
|
||||
|
||||
fun atob(inputstr: String): ByteArray {
|
||||
return Base64Coder.decode(inputstr)
|
||||
}
|
||||
|
||||
fun atostr(inputstr: String): String {
|
||||
return Base64Coder.decode(inputstr).toString(VM.CHARSET)
|
||||
}
|
||||
|
||||
fun btoa(inputbytes: ByteArray): String {
|
||||
val sb = StringBuilder()
|
||||
sb.append(Base64Coder.encode(inputbytes))
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
}
|
||||
193
tsvm_core/src/net/torvald/tsvm/CircularArray.kt
Normal file
193
tsvm_core/src/net/torvald/tsvm/CircularArray.kt
Normal file
@@ -0,0 +1,193 @@
|
||||
package net.torvald.tsvm
|
||||
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* buffer[head] contains the most recent item, whereas buffer[tail] contains the oldest one.
|
||||
*
|
||||
* Notes for particle storage:
|
||||
* Particles does not need to be removed, just let it overwrite as their operation is rather
|
||||
* lightweight. So, just flagDespawn = true if it need to be "deleted" so that it won't update
|
||||
* anymore.
|
||||
*
|
||||
* Created by minjaesong on 2017-01-22.
|
||||
*/
|
||||
internal class CircularArray<T>(val size: Int, val overwriteOnOverflow: Boolean): Iterable<T> {
|
||||
|
||||
/**
|
||||
* What to do RIGHT BEFORE old element is being overridden by the new element (only makes sense when ```overwriteOnOverflow = true```)
|
||||
*
|
||||
* This function will not be called when ```removeHead()``` or ```removeTail()``` is called.
|
||||
*/
|
||||
var overwritingPolicy: (T) -> Unit = {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
val buffer: Array<T> = arrayOfNulls<Any>(size) as Array<T>
|
||||
|
||||
/** Tail stands for the oldest element. The tail index points AT the tail element */
|
||||
var tail: Int = 0; private set
|
||||
/** Head stands for the youngest element. The head index points AFTER the head element */
|
||||
var head: Int = 0; private set
|
||||
|
||||
private var overflow = false
|
||||
|
||||
val lastIndex = size - 1
|
||||
|
||||
/**
|
||||
* Number of elements that forEach() or fold() would iterate.
|
||||
*/
|
||||
val elemCount: Int
|
||||
get() = if (overflow) size else head - tail
|
||||
val isEmpty: Boolean
|
||||
get() = !overflow && head == tail
|
||||
|
||||
private inline fun incHead() { head = (head + 1).wrap() }
|
||||
private inline fun decHead() { head = (head - 1).wrap() }
|
||||
private inline fun incTail() { tail = (tail + 1).wrap() }
|
||||
private inline fun decTail() { tail = (tail - 1).wrap() }
|
||||
|
||||
fun clear() {
|
||||
tail = 0
|
||||
head = 0
|
||||
overflow = false
|
||||
}
|
||||
|
||||
/**
|
||||
* When the overflowing is enabled, tail element (ultimate element) will be changed into the penultimate element.
|
||||
*/
|
||||
fun appendHead(item: T) {
|
||||
if (overflow && !overwriteOnOverflow) {
|
||||
throw StackOverflowError()
|
||||
}
|
||||
else {
|
||||
if (overflow) {
|
||||
overwritingPolicy.invoke(buffer[head])
|
||||
}
|
||||
|
||||
buffer[head] = item
|
||||
incHead()
|
||||
}
|
||||
|
||||
if (overflow) {
|
||||
incTail()
|
||||
}
|
||||
|
||||
// must be checked AFTER the actual head increment; otherwise this condition doesn't make sense
|
||||
if (tail == head) {
|
||||
overflow = true
|
||||
}
|
||||
}
|
||||
|
||||
fun appendTail(item: T) {
|
||||
// even if overflowing is enabled, appending at tail causes head element to be altered, therefore such action
|
||||
// must be blocked by throwing overflow error
|
||||
|
||||
// if you think this behaviour is wrong, you're confusing appendHead() with appendTail(). Use appendHead() and removeTail()
|
||||
if (overflow) {
|
||||
throw StackOverflowError()
|
||||
}
|
||||
else {
|
||||
decTail()
|
||||
buffer[tail] = item
|
||||
}
|
||||
|
||||
// must be checked AFTER the actual head increment; otherwise this condition doesn't make sense
|
||||
if (tail == head) {
|
||||
overflow = true
|
||||
}
|
||||
}
|
||||
|
||||
fun removeHead(): T? {
|
||||
if (isEmpty) return null
|
||||
|
||||
decHead()
|
||||
overflow = false
|
||||
|
||||
return buffer[head]
|
||||
}
|
||||
|
||||
fun removeTail(): T? {
|
||||
if (isEmpty) return null
|
||||
|
||||
val ret = buffer[tail]
|
||||
incTail()
|
||||
overflow = false
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
/** Returns the youngest (last of the array) element */
|
||||
fun getHeadElem(): T? = if (isEmpty) null else buffer[(head - 1).wrap()]
|
||||
/** Returns the oldest (first of the array) element */
|
||||
fun getTailElem(): T? = if (isEmpty) null else buffer[tail]
|
||||
|
||||
/**
|
||||
* Relative-indexed get. Index of zero will return the head element.
|
||||
*/
|
||||
operator fun get(index: Int): T? = buffer[(head - 1 - index).wrap()]
|
||||
|
||||
private fun getAbsoluteRange() = 0 until when {
|
||||
head == tail -> buffer.size
|
||||
tail > head -> buffer.size - (((head - 1).wrap()) - tail)
|
||||
else -> head - tail
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<T> {
|
||||
if (isEmpty) {
|
||||
return object : Iterator<T> {
|
||||
override fun next(): T = throw EmptyStackException()
|
||||
override fun hasNext() = false
|
||||
}
|
||||
}
|
||||
|
||||
val rangeMax = getAbsoluteRange().last
|
||||
var counter = 0
|
||||
return object : Iterator<T> {
|
||||
override fun next(): T {
|
||||
val ret = buffer[(counter + tail).wrap()]
|
||||
counter += 1
|
||||
return ret
|
||||
}
|
||||
|
||||
override fun hasNext() = (counter <= rangeMax)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates the array with oldest element (tail) first.
|
||||
*/
|
||||
fun forEach(action: (T) -> Unit) {
|
||||
// for (element in buffer) action(element)
|
||||
// return nothing
|
||||
|
||||
iterator().forEach(action)
|
||||
}
|
||||
|
||||
fun <R> fold(initial: R, operation: (R, T) -> R): R {
|
||||
// accumulator = initial
|
||||
// for (element in buffer) accumulator = operation(accumulator, element)
|
||||
// return accumulator
|
||||
|
||||
var accumulator = initial
|
||||
|
||||
if (isEmpty)
|
||||
return initial
|
||||
else {
|
||||
iterator().forEach {
|
||||
accumulator = operation(accumulator, it)
|
||||
}
|
||||
}
|
||||
|
||||
return accumulator
|
||||
}
|
||||
|
||||
private inline fun Int.wrap() = this fmod size
|
||||
|
||||
override fun toString(): String {
|
||||
return "CircularArray(size=${buffer.size}, head=$head, tail=$tail, overflow=$overflow)"
|
||||
}
|
||||
|
||||
private inline infix fun Int.fmod(other: Int) = Math.floorMod(this, other)
|
||||
}
|
||||
42
tsvm_core/src/net/torvald/tsvm/CompressorDelegate.kt
Normal file
42
tsvm_core/src/net/torvald/tsvm/CompressorDelegate.kt
Normal file
@@ -0,0 +1,42 @@
|
||||
package net.torvald.tsvm
|
||||
|
||||
import com.badlogic.gdx.utils.compression.Lzma
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.zip.GZIPInputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
|
||||
object CompressorDelegate {
|
||||
|
||||
/*fun comp(ba: ByteArray): ByteArray {
|
||||
val bin = ByteArrayInputStream(ba)
|
||||
val bout = ByteArrayOutputStream(256)
|
||||
Lzma.compress(bin, bout)
|
||||
return bout.toByteArray()
|
||||
}
|
||||
|
||||
fun decomp(ba: ByteArray): ByteArray {
|
||||
val bin = ByteArrayInputStream(ba)
|
||||
val bout = ByteArrayOutputStream(256)
|
||||
Lzma.decompress(bin, bout)
|
||||
return bout.toByteArray()
|
||||
}*/
|
||||
|
||||
fun comp(ba: ByteArray): ByteArray {
|
||||
val baos = ByteArrayOutputStream()
|
||||
val gz = GZIPOutputStream(baos)
|
||||
gz.write(ba); gz.flush(); gz.finish()
|
||||
baos.flush(); baos.close()
|
||||
return baos.toByteArray()
|
||||
}
|
||||
|
||||
fun decomp(ba: ByteArray): ByteArray {
|
||||
val bais = ByteArrayInputStream(ba)
|
||||
val gz = GZIPInputStream(bais)
|
||||
val ret = gz.readBytes()
|
||||
gz.close(); bais.close()
|
||||
return ret
|
||||
}
|
||||
|
||||
val GZIP_HEADER = byteArrayOf(31,-117,8) // .gz in DEFLATE
|
||||
}
|
||||
79
tsvm_core/src/net/torvald/tsvm/DMADelegate.kt
Normal file
79
tsvm_core/src/net/torvald/tsvm/DMADelegate.kt
Normal file
@@ -0,0 +1,79 @@
|
||||
package net.torvald.tsvm
|
||||
|
||||
import net.torvald.UnsafeHelper
|
||||
import net.torvald.tsvm.peripheral.GraphicsAdapter
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2021-10-15.
|
||||
*/
|
||||
class DMADelegate(val vm: VM) {
|
||||
|
||||
private val READ = "READ".toByteArray(VM.CHARSET)
|
||||
private val FLUSH = "FLUSH".toByteArray(VM.CHARSET)
|
||||
private val CLOSE = "CLOSE".toByteArray(VM.CHARSET)
|
||||
|
||||
private fun WRITE(n: Int) = "WRITE$n".toByteArray(VM.CHARSET)
|
||||
|
||||
fun ramToFrame(from: Int, devnum: Int, offset: Int, length: Int) {
|
||||
(vm.peripheralTable[devnum].peripheral as? GraphicsAdapter)?.let {
|
||||
val data = ByteArray(length)
|
||||
UnsafeHelper.memcpyRaw(null, vm.usermem.ptr + from, data, UnsafeHelper.getArrayOffset(data), length.toLong())
|
||||
it.framebuffer.pixels.position(offset)
|
||||
it.framebuffer.pixels.put(data)
|
||||
it.framebuffer.pixels.position(0) // rewinding to avoid graphical glitch
|
||||
}
|
||||
}
|
||||
|
||||
fun ramToFrame(from: Int, to: Int, length: Int) {
|
||||
for (i in 0..7) {
|
||||
if (vm.peripheralTable[i].type == VM.PERITYPE_GPU_AND_TERM) {
|
||||
ramToFrame(from, i, to, length)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun frameToRam(from: Int, to: Int, devnum: Int, length: Int) {
|
||||
(vm.peripheralTable[devnum].peripheral as? GraphicsAdapter)?.let {
|
||||
val data = ByteArray(length)
|
||||
it.framebuffer.pixels.position(from)
|
||||
it.framebuffer.pixels.get(data)
|
||||
it.framebuffer.pixels.position(0) // rewinding to avoid graphical glitch
|
||||
UnsafeHelper.memcpyRaw(data, UnsafeHelper.getArrayOffset(data), null, vm.usermem.ptr + to, length.toLong())
|
||||
}
|
||||
}
|
||||
|
||||
fun frameToRam(from: Int, to: Int, length: Int) {
|
||||
for (i in 0..7) {
|
||||
if (vm.peripheralTable[i].type == VM.PERITYPE_GPU_AND_TERM) {
|
||||
frameToRam(from, to, i, length)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun comToRam(portNo: Int, srcOff: Int, destOff: Int, length: Int) {
|
||||
SerialHelper.sendMessage(vm, portNo, READ)
|
||||
val response = SerialHelper.getStatusCode(vm, portNo)
|
||||
if (response == 0) {
|
||||
val file = SerialHelper.pullMessage(vm, portNo)
|
||||
UnsafeHelper.memcpyRaw(file, UnsafeHelper.getArrayOffset(file) + srcOff.toLong(), null, vm.usermem.ptr + destOff, length.toLong())
|
||||
}
|
||||
}
|
||||
|
||||
fun ramToCom(srcOff: Int, portNo: Int, length: Int) {
|
||||
SerialHelper.sendMessage(vm, portNo, WRITE(length))
|
||||
val response = SerialHelper.getStatusCode(vm, portNo)
|
||||
if (response == 0) {
|
||||
val msg = ByteArray(length)
|
||||
UnsafeHelper.memcpyRaw(null, vm.usermem.ptr + srcOff, msg, UnsafeHelper.getArrayOffset(msg), length.toLong())
|
||||
SerialHelper.sendMessage(vm, portNo, msg)
|
||||
SerialHelper.sendMessage(vm, portNo, FLUSH)
|
||||
SerialHelper.sendMessage(vm, portNo, CLOSE)
|
||||
}
|
||||
}
|
||||
|
||||
fun ramToRam(from: Int, to: Int, length: Int) {
|
||||
UnsafeHelper.memcpy(vm.usermem.ptr + from, vm.usermem.ptr + to, length.toLong())
|
||||
}
|
||||
}
|
||||
66
tsvm_core/src/net/torvald/tsvm/FilesystemDelegate.kt
Normal file
66
tsvm_core/src/net/torvald/tsvm/FilesystemDelegate.kt
Normal file
@@ -0,0 +1,66 @@
|
||||
package net.torvald.tsvm
|
||||
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint
|
||||
import java.io.InputStream
|
||||
|
||||
class VmFilesystemDelegate(val vm: VM, val portNo: Int) {
|
||||
|
||||
fun getFileInputStream(path: String) = DiskDriveFileInputStream(vm, portNo, path)
|
||||
|
||||
}
|
||||
|
||||
class DiskDriveFileInputStream(val vm: VM, val portNo: Int, val path: String) : InputStream() {
|
||||
|
||||
private val contents: ByteArray
|
||||
private var readCursor = 0
|
||||
|
||||
init {
|
||||
SerialHelper.sendMessage(vm, portNo, "OPENR\"$path\"".toByteArray(VM.CHARSET))
|
||||
SerialHelper.waitUntilReady(vm, portNo)
|
||||
contents = SerialHelper.sendMessageGetBytes(vm, portNo, "READ".toByteArray(VM.CHARSET))
|
||||
}
|
||||
|
||||
override fun markSupported() = true
|
||||
|
||||
override fun read(): Int {
|
||||
if (readCursor >= contents.size) return -1
|
||||
val ret = contents[readCursor].toUint()
|
||||
readCursor += 1
|
||||
return ret
|
||||
}
|
||||
|
||||
override fun skip(n: Long): Long {
|
||||
val newReadCursor = minOf(contents.size.toLong(), readCursor + n)
|
||||
val diff = newReadCursor - readCursor
|
||||
readCursor = newReadCursor.toInt()
|
||||
return diff
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
readCursor = 0
|
||||
}
|
||||
|
||||
override fun mark(i: Int) {
|
||||
TODO()
|
||||
}
|
||||
|
||||
override fun read(p0: ByteArray): Int {
|
||||
var readBytes = 0
|
||||
for (k in p0.indices) {
|
||||
val r = read()
|
||||
p0[k] = r.toByte()
|
||||
if (r >= 0) readBytes += 1
|
||||
}
|
||||
|
||||
return readBytes
|
||||
}
|
||||
|
||||
override fun read(p0: ByteArray, p1: Int, p2: Int): Int {
|
||||
TODO()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
SerialHelper.sendMessage(vm, portNo, "CLOSE".toByteArray(VM.CHARSET))
|
||||
}
|
||||
}
|
||||
|
||||
173
tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt
Normal file
173
tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt
Normal file
@@ -0,0 +1,173 @@
|
||||
package net.torvald.tsvm
|
||||
|
||||
import net.torvald.UnsafeHelper
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint
|
||||
import net.torvald.tsvm.peripheral.GraphicsAdapter
|
||||
import net.torvald.tsvm.peripheral.fmod
|
||||
|
||||
class GraphicsJSR223Delegate(val vm: VM) {
|
||||
|
||||
private fun getFirstGPU(): GraphicsAdapter? {
|
||||
return vm.findPeribyType(VM.PERITYPE_GPU_AND_TERM)?.peripheral as? GraphicsAdapter
|
||||
}
|
||||
|
||||
fun resetPalette() {
|
||||
getFirstGPU()?.poke(250883L, 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index which palette number to modify, 0-255
|
||||
* @param r g - b - a - RGBA value, 0-15
|
||||
*/
|
||||
fun setPalette(index: Int, r: Int, g: Int, b: Int, a: Int = 16) {
|
||||
getFirstGPU()?.let {
|
||||
it.paletteOfFloats[index * 4] = (r and 15) / 15f
|
||||
it.paletteOfFloats[index * 4 + 1] = (g and 15) / 15f
|
||||
it.paletteOfFloats[index * 4 + 2] = (b and 15) / 15f
|
||||
it.paletteOfFloats[index * 4 + 3] = (a and 15) / 15f
|
||||
}
|
||||
}
|
||||
|
||||
/*fun loadBulk(fromAddr: Int, toAddr: Int, length: Int) {
|
||||
getFirstGPU()?._loadbulk(fromAddr, toAddr, length)
|
||||
}
|
||||
|
||||
fun storeBulk(fromAddr: Int, toAddr: Int, length: Int) {
|
||||
getFirstGPU()?._storebulk(fromAddr, toAddr, length)
|
||||
}*/
|
||||
|
||||
fun plotPixel(x: Int, y: Int, color: Int) {
|
||||
getFirstGPU()?.let {
|
||||
if (x in 0 until it.config.width && y in 0 until it.config.height) {
|
||||
it.poke(y.toLong() * it.config.width + x, color.toByte())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets absolute position of scrolling
|
||||
*/
|
||||
fun setFramebufferScroll(x: Int, y: Int) {
|
||||
getFirstGPU()?.let {
|
||||
it.framebufferScrollX = x
|
||||
it.framebufferScrollY = y
|
||||
}
|
||||
}
|
||||
|
||||
fun scrollFrame(xdelta: Int, ydelta: Int) {
|
||||
getFirstGPU()?.let {
|
||||
it.framebufferScrollX = (it.framebufferScrollX + xdelta) fmod it.framebuffer.width
|
||||
it.framebufferScrollY = (it.framebufferScrollY + ydelta) fmod it.framebuffer.height
|
||||
}
|
||||
}
|
||||
|
||||
fun setLineOffset(line: Int, offset: Int) {
|
||||
getFirstGPU()?.let {
|
||||
it.poke(250900L + 2*line, offset.shr(8).toByte()) // absolutely not USHR
|
||||
it.poke(250901L + 2*line, offset.toByte())
|
||||
}
|
||||
}
|
||||
|
||||
fun getLineOffset(line: Int): Int {
|
||||
getFirstGPU()?.let {
|
||||
var xoff = it.peek(250900L + 2*line)!!.toUint().shl(8) or it.peek(250901L + 2*line)!!.toUint()
|
||||
if (xoff.and(0x8000) != 0) xoff = xoff or 0xFFFF0000.toInt()
|
||||
return xoff
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
fun getPixelDimension(): IntArray {
|
||||
getFirstGPU()?.let { return intArrayOf(it.framebuffer.width, it.framebuffer.height) }
|
||||
return intArrayOf(-1, -1)
|
||||
}
|
||||
|
||||
fun getTermDimension(): IntArray {
|
||||
getFirstGPU()?.let { return intArrayOf(it.TEXT_ROWS, it.TEXT_COLS) }
|
||||
return intArrayOf(-1, -1)
|
||||
}
|
||||
|
||||
fun getCursorYX(): IntArray {
|
||||
getFirstGPU()?.let {
|
||||
val (cx, cy) = it.getCursorPos()
|
||||
return intArrayOf(cy + 1, cx + 1)
|
||||
}
|
||||
return intArrayOf(-1, -1)
|
||||
}
|
||||
|
||||
fun setCursorYX(cy: Int, cx: Int) {
|
||||
getFirstGPU()?.let {
|
||||
it.setCursorPos(cy - 1, cx - 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun setBackground(r: Int, g: Int, b: Int) {
|
||||
getFirstGPU()?.let {
|
||||
it.poke(250880, r.toByte())
|
||||
it.poke(250881, g.toByte())
|
||||
it.poke(250882, b.toByte())
|
||||
}
|
||||
}
|
||||
|
||||
fun clearText() {
|
||||
getFirstGPU()?.eraseInDisp(2)
|
||||
}
|
||||
|
||||
fun clearPixels(col: Int) {
|
||||
getFirstGPU()?.poke(250884L, col.toByte())
|
||||
getFirstGPU()?.poke(250883L, 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* prints a char as-is; won't interpret them as an escape sequence
|
||||
*/
|
||||
fun putSymbol(c: Int) {
|
||||
getFirstGPU()?.let {
|
||||
val (cx, cy) = it.getCursorPos()
|
||||
|
||||
|
||||
it.putChar(cx, cy, c.toByte())
|
||||
}
|
||||
}
|
||||
|
||||
fun putSymbolAt(cy: Int, cx: Int, c: Int) {
|
||||
getFirstGPU()?.let {
|
||||
it.putChar(cx - 1, cy - 1, c.toByte())
|
||||
}
|
||||
}
|
||||
|
||||
/*private fun GraphicsAdapter._loadbulk(fromAddr: Int, toAddr: Int, length: Int) {
|
||||
UnsafeHelper.memcpy(
|
||||
vm.usermem.ptr + fromAddr,
|
||||
(this.framebuffer.pixels as DirectBuffer).address() + toAddr,
|
||||
length.toLong()
|
||||
)
|
||||
}
|
||||
|
||||
private fun GraphicsAdapter._storebulk(fromAddr: Int, toAddr: Int, length: Int) {
|
||||
UnsafeHelper.memcpy(
|
||||
(this.framebuffer.pixels as DirectBuffer).address() + fromAddr,
|
||||
vm.usermem.ptr + toAddr,
|
||||
length.toLong()
|
||||
)
|
||||
}*/
|
||||
|
||||
private fun GraphicsAdapter._loadSprite(spriteNum: Int, ptr: Int) {
|
||||
UnsafeHelper.memcpy(
|
||||
vm.usermem.ptr + ptr,
|
||||
(this.textArea).ptr + (260 * spriteNum) + 4,
|
||||
256
|
||||
)
|
||||
}
|
||||
|
||||
private fun GraphicsAdapter._storeSprite(spriteNum: Int, ptr: Int) {
|
||||
UnsafeHelper.memcpy(
|
||||
(this.textArea).ptr + (260 * spriteNum) + 4,
|
||||
vm.usermem.ptr + ptr,
|
||||
256
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
18
tsvm_core/src/net/torvald/tsvm/LoadShader.kt
Normal file
18
tsvm_core/src/net/torvald/tsvm/LoadShader.kt
Normal file
@@ -0,0 +1,18 @@
|
||||
package net.torvald.tsvm
|
||||
|
||||
import com.badlogic.gdx.graphics.glutils.ShaderProgram
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2021-12-03.
|
||||
*/
|
||||
internal object LoadShader {
|
||||
operator fun invoke(vert: String, frag: String): ShaderProgram {
|
||||
val s = ShaderProgram(vert, frag)
|
||||
|
||||
if (s.log.toLowerCase().contains("error")) {
|
||||
throw Error(String.format("Shader program loaded with %s, %s failed:\n%s", vert, frag, s.log))
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
}
|
||||
150
tsvm_core/src/net/torvald/tsvm/SerialHelper.kt
Normal file
150
tsvm_core/src/net/torvald/tsvm/SerialHelper.kt
Normal file
@@ -0,0 +1,150 @@
|
||||
package net.torvald.tsvm
|
||||
|
||||
import net.torvald.UnsafeHelper
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint
|
||||
import net.torvald.tsvm.peripheral.BlockTransferInterface.Companion.BLOCK_SIZE
|
||||
import net.torvald.tsvm.peripheral.BlockTransferInterface.Companion.END_OF_SEND_BLOCK
|
||||
import net.torvald.tsvm.peripheral.trimNull
|
||||
import java.io.ByteArrayOutputStream
|
||||
import kotlin.experimental.and
|
||||
import kotlin.experimental.or
|
||||
import kotlin.math.ceil
|
||||
|
||||
object SerialHelper {
|
||||
|
||||
private const val SLEEP_TIME = 4L
|
||||
|
||||
fun sendMessageGetBytes(vm: VM, portNo: Int, message: ByteArray): ByteArray {
|
||||
sendMessage(vm, portNo, message)
|
||||
waitUntilReady(vm, portNo)
|
||||
return fetchResponse(vm, portNo)
|
||||
}
|
||||
|
||||
fun sendMessage(vm: VM, portNo: Int, message: ByteArray) {
|
||||
if (!checkIfDeviceIsThere(vm, portNo)) throw IllegalStateException("Device not connected")
|
||||
|
||||
/*UnsafeHelper.memcpyRaw(
|
||||
message, UnsafeHelper.getArrayOffset(message),
|
||||
null, vm.getIO().blockTransferTx[portNo].ptr,
|
||||
minOf(BLOCK_SIZE, message.size).toLong()
|
||||
)*/
|
||||
|
||||
for (blockCount in 0 until ceil(message.size.toFloat() / BLOCK_SIZE).toInt()) {
|
||||
for (k in 0 until BLOCK_SIZE) {
|
||||
val index = BLOCK_SIZE * blockCount + k
|
||||
vm.getIO().blockTransferTx[portNo][k.toLong()] = if (index >= message.size) 0 else message[index]
|
||||
}
|
||||
initiateWriting(vm, portNo)
|
||||
waitUntilReady(vm, portNo)
|
||||
}
|
||||
|
||||
|
||||
// TODO assuming the write operation is finished... (wait for something?)
|
||||
getReady(vm, portNo)
|
||||
}
|
||||
|
||||
// Returns what's on the RX buffer after sendMessage()
|
||||
// i.e won't (actually shouldn't) clobber the device's message compose buffer (see TestDiskDrive)
|
||||
fun fetchResponse(vm: VM, portNo: Int): ByteArray {
|
||||
val incomingMsg = ByteArray(BLOCK_SIZE)
|
||||
|
||||
// incoming message is always 4K long and unused bytes are zero-filled. THIS IS INTENTIONAL
|
||||
UnsafeHelper.memcpyRaw(
|
||||
null, vm.getIO().blockTransferRx[portNo].ptr,
|
||||
incomingMsg, UnsafeHelper.getArrayOffset(incomingMsg),
|
||||
BLOCK_SIZE.toLong()
|
||||
)
|
||||
|
||||
return incomingMsg
|
||||
}
|
||||
|
||||
// Initiates startSend() function from the connected device
|
||||
fun pullMessage(vm: VM, portNo: Int): ByteArray {
|
||||
val msgBuffer = ByteArrayOutputStream(BLOCK_SIZE)
|
||||
|
||||
// pull all the blocks of messages
|
||||
do {
|
||||
initiateReading(vm, portNo)
|
||||
waitUntilReady(vm, portNo)
|
||||
|
||||
val transStat = getBlockTransferStatus(vm, portNo)
|
||||
val receivedLen = transStat.first//vm.getIO().blockTransferPorts[portNo].yourBlockSize()
|
||||
//println("[SerialHelper.pullMessage()] received length: $receivedLen")
|
||||
|
||||
for (k in 0 until minOf(BLOCK_SIZE, receivedLen)) {
|
||||
msgBuffer.write(vm.getIO().blockTransferRx[portNo][k.toLong()].toInt())
|
||||
}
|
||||
|
||||
} while (transStat.second)
|
||||
|
||||
getReady(vm, portNo)
|
||||
|
||||
return msgBuffer.toByteArray()
|
||||
}
|
||||
|
||||
fun getDeviceStatus(vm: VM, portNo: Int): DeviceStatus {
|
||||
val msgStr = sendMessageGetBytes(vm, portNo, "DEVSTU$END_OF_SEND_BLOCK".toByteArray(VM.CHARSET))
|
||||
return DeviceStatus(
|
||||
msgStr[1].toUint(),
|
||||
msgStr.sliceArray(3 until msgStr.size - 1).toString(VM.CHARSET)
|
||||
)
|
||||
}
|
||||
|
||||
fun waitUntilReady(vm: VM, portNo: Int) {
|
||||
while (!checkIfDeviceIsReady(vm, portNo)) { Thread.sleep(SLEEP_TIME) }
|
||||
}
|
||||
|
||||
fun getStatusCode(vm: VM, portNo: Int) = vm.getIO().mmio_read(4080L + portNo)!!.toInt().and(255)
|
||||
|
||||
fun checkIfDeviceIsThere(vm: VM, portNo: Int) =
|
||||
(vm.getIO().mmio_read(4092L + portNo)!! and 1.toByte()) == 1.toByte()
|
||||
|
||||
fun checkIfDeviceIsReady(vm: VM, portNo: Int) =
|
||||
(vm.getIO().mmio_read(4092L + portNo)!! and 0b111.toByte()) == 0b011.toByte()
|
||||
|
||||
private fun initiateWriting(vm: VM, portNo: Int) {
|
||||
vm.getIO().mmio_write(4092L + portNo, 0b1110)
|
||||
}
|
||||
|
||||
private fun initiateReading(vm: VM, portNo: Int) {
|
||||
vm.getIO().mmio_write(4092L + portNo, 0b0110)
|
||||
}
|
||||
|
||||
private fun getReady(vm: VM, portNo: Int) {
|
||||
val flags = vm.getIO().mmio_read(4092L + portNo)!!
|
||||
val newFlags = flags.and(0b1111_1001.toByte()).or(0b0000_0010)
|
||||
vm.getIO().mmio_write(4092L + portNo, newFlags)
|
||||
}
|
||||
|
||||
private fun setBlockTransferStatus(vm: VM, portNo: Int, blockSize: Int, moreToSend: Boolean = false) {
|
||||
vm.getIO().mmio_write(4084L + (portNo * 2), (blockSize and 255).toByte())
|
||||
vm.getIO().mmio_write(4085L + (portNo * 2),
|
||||
((blockSize ushr 8).and(15) or (moreToSend.toInt() shl 7)).toByte()
|
||||
)
|
||||
}
|
||||
|
||||
private fun getBlockTransferStatus(vm: VM, portNo: Int): Pair<Int, Boolean> {
|
||||
val bits = vm.getIO().mmio_read(4084L + (portNo * 2))!!.toUint() or
|
||||
(vm.getIO().mmio_read(4085L + (portNo * 2))!!.toUint() shl 8)
|
||||
val rawcnt = bits.and(4095)
|
||||
val gotMore = bits.and(0x8000) != 0
|
||||
return (if (rawcnt == 0) BLOCK_SIZE else rawcnt) to gotMore
|
||||
}
|
||||
|
||||
|
||||
private fun Boolean.toInt() = if (this) 1 else 0
|
||||
|
||||
data class DeviceStatus(val code: Int, val message: String)
|
||||
}
|
||||
|
||||
class SerialHelperDelegate(val vm: VM) {
|
||||
fun sendMessage(portNo: Int, message: String) = SerialHelper.sendMessage(vm, portNo, message.toByteArray(VM.CHARSET))
|
||||
fun pullMessage(portNo: Int) = SerialHelper.pullMessage(vm, portNo).toString(VM.CHARSET)
|
||||
fun sendMessageGetBytes(portNo: Int, message: String) = SerialHelper.sendMessageGetBytes(vm, portNo, message.toByteArray(VM.CHARSET)).toString(VM.CHARSET)
|
||||
fun fetchResponse(portNo: Int) = SerialHelper.fetchResponse(vm, portNo).toString(VM.CHARSET)
|
||||
fun waitUntilReady(portNo: Int) = SerialHelper.waitUntilReady(vm, portNo)
|
||||
fun getStatusCode(portNo: Int) = SerialHelper.getStatusCode(vm, portNo)
|
||||
/** @return Object where { code: <Int>, message: <String> } */
|
||||
fun getDeviceStatus(portNo: Int) = SerialHelper.getDeviceStatus(vm, portNo)
|
||||
fun areYouThere(portNo: Int) = SerialHelper.checkIfDeviceIsThere(vm, portNo)
|
||||
}
|
||||
100
tsvm_core/src/net/torvald/tsvm/TextureRegionPack.kt
Executable file
100
tsvm_core/src/net/torvald/tsvm/TextureRegionPack.kt
Executable file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Terrarum Sans Bitmap
|
||||
*
|
||||
* Copyright (c) 2017 Minjae Song (Torvald)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.torvald.tsvm
|
||||
|
||||
import com.badlogic.gdx.files.FileHandle
|
||||
import com.badlogic.gdx.graphics.Texture
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion
|
||||
import com.badlogic.gdx.utils.Disposable
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2017-06-15.
|
||||
*/
|
||||
class TextureRegionPack(
|
||||
val texture: Texture,
|
||||
val tileW: Int,
|
||||
val tileH: Int,
|
||||
val hGap: Int = 0,
|
||||
val vGap: Int = 0,
|
||||
val hFrame: Int = 0,
|
||||
val vFrame: Int = 0,
|
||||
val xySwapped: Boolean = false // because Unicode chart does, duh
|
||||
): Disposable {
|
||||
|
||||
constructor(ref: String, tileW: Int, tileH: Int, hGap: Int = 0, vGap: Int = 0, hFrame: Int = 0, vFrame: Int = 0, xySwapped: Boolean = false) :
|
||||
this(Texture(ref), tileW, tileH, hGap, vGap, hFrame, vFrame, xySwapped)
|
||||
constructor(fileHandle: FileHandle, tileW: Int, tileH: Int, hGap: Int = 0, vGap: Int = 0, hFrame: Int = 0, vFrame: Int = 0, xySwapped: Boolean = false) :
|
||||
this(Texture(fileHandle), tileW, tileH, hGap, vGap, hFrame, vFrame, xySwapped)
|
||||
|
||||
companion object {
|
||||
/** Intented for Y-down coord system, typically fon Non-GDX codebase */
|
||||
var globalFlipY = false
|
||||
}
|
||||
|
||||
val regions: Array<TextureRegion>
|
||||
|
||||
val horizontalCount = (texture.width - 2 * hFrame + hGap) / (tileW + hGap)
|
||||
val verticalCount = (texture.height - 2 * vFrame + vGap) / (tileH + vGap)
|
||||
|
||||
init {
|
||||
//println("texture: $texture, dim: ${texture.width} x ${texture.height}, grid: $horizontalCount x $verticalCount, cellDim: $tileW x $tileH")
|
||||
|
||||
if (!xySwapped) {
|
||||
regions = Array<TextureRegion>(horizontalCount * verticalCount) {
|
||||
val region = TextureRegion()
|
||||
val rx = (it % horizontalCount * (tileW + hGap)) + hFrame
|
||||
val ry = (it / horizontalCount * (tileH + vGap)) + vFrame
|
||||
|
||||
region.setRegion(texture)
|
||||
region.setRegion(rx, ry, tileW, tileH)
|
||||
|
||||
region.flip(false, globalFlipY)
|
||||
|
||||
/*return*/region
|
||||
}
|
||||
}
|
||||
else {
|
||||
regions = Array<TextureRegion>(horizontalCount * verticalCount) {
|
||||
val region = TextureRegion()
|
||||
val rx = (it / verticalCount * (tileW + hGap)) + hFrame
|
||||
val ry = (it % verticalCount * (tileH + vGap)) + vFrame
|
||||
|
||||
region.setRegion(texture)
|
||||
region.setRegion(rx, ry, tileW, tileH)
|
||||
|
||||
region.flip(false, globalFlipY)
|
||||
|
||||
/*return*/region
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun get(x: Int, y: Int) = regions[y * horizontalCount + x]
|
||||
|
||||
override fun dispose() {
|
||||
texture.dispose()
|
||||
}
|
||||
|
||||
}
|
||||
150
tsvm_core/src/net/torvald/tsvm/UnsafePtr.kt
Normal file
150
tsvm_core/src/net/torvald/tsvm/UnsafePtr.kt
Normal file
@@ -0,0 +1,150 @@
|
||||
package net.torvald
|
||||
|
||||
import sun.misc.Unsafe
|
||||
import java.io.PrintStream
|
||||
|
||||
/**
|
||||
* Further read:
|
||||
* - http://www.docjar.com/docs/api/sun/misc/Unsafe.html
|
||||
*
|
||||
* Created by minjaesong on 2019-06-21.
|
||||
*/
|
||||
|
||||
internal object UnsafeHelper {
|
||||
val unsafe: Unsafe
|
||||
|
||||
init {
|
||||
val unsafeConstructor = Unsafe::class.java.getDeclaredConstructor()
|
||||
unsafeConstructor.isAccessible = true
|
||||
unsafe = unsafeConstructor.newInstance()
|
||||
}
|
||||
|
||||
/**
|
||||
* A factory method to allocate a memory of given size and return its starting address as a pointer.
|
||||
*/
|
||||
fun allocate(size: Long): UnsafePtr {
|
||||
val ptr = unsafe.allocateMemory(size)
|
||||
return UnsafePtr(ptr, size)
|
||||
}
|
||||
|
||||
fun memcpy(src: UnsafePtr, fromIndex: Long, dest: UnsafePtr, toIndex: Long, copyLength: Long) =
|
||||
unsafe.copyMemory(src.ptr + fromIndex, dest.ptr + toIndex, copyLength)
|
||||
fun memcpy(srcAddress: Long, destAddress: Long, copyLength: Long) =
|
||||
unsafe.copyMemory(srcAddress, destAddress, copyLength)
|
||||
fun memcpyRaw(srcObj: Any?, srcPos: Long, destObj: Any?, destPos: Long, len: Long) =
|
||||
unsafe.copyMemory(srcObj, srcPos, destObj, destPos, len)
|
||||
|
||||
/**
|
||||
* The array object in JVM is stored in this memory map:
|
||||
*
|
||||
* 0 w 2w *
|
||||
* | Some identifier | Other identifier | the actual data ... |
|
||||
*
|
||||
* (where w = 4 for 32-bit JVM and 8 for 64-bit JVM. If Compressed-OOP is involved, things may get complicated)
|
||||
*
|
||||
* @return offset from the array's base memory address (aka pointer) that the actual data begins.
|
||||
*/
|
||||
fun getArrayOffset(obj: Any) = unsafe.arrayBaseOffset(obj.javaClass).toLong()
|
||||
}
|
||||
|
||||
/**
|
||||
* To allocate a memory, use UnsafeHelper.allocate(long)
|
||||
*
|
||||
* All the getFloat/Int/whatever methods will follow the endianness of your system,
|
||||
* e.g. it'll be Little Endian on x86, Big Endian on PPC, User-defined on ARM; therefore these functions should not be
|
||||
* used when the portability matters (e.g. Savefile). In such situations, do byte-wise operations will be needed.
|
||||
*
|
||||
* Use of hashCode() is forbidden, use the pointer instead.
|
||||
*/
|
||||
internal class UnsafePtr(pointer: Long, allocSize: Long) {
|
||||
var destroyed = false
|
||||
private set
|
||||
|
||||
var ptr: Long = pointer
|
||||
private set
|
||||
|
||||
var size: Long = allocSize
|
||||
private set
|
||||
|
||||
fun realloc(newSize: Long) {
|
||||
ptr = UnsafeHelper.unsafe.reallocateMemory(ptr, newSize)
|
||||
size = newSize
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
if (!destroyed) {
|
||||
println("[UnsafePtr] Destroying pointer $this; called from:")
|
||||
printStackTrace(this)
|
||||
|
||||
UnsafeHelper.unsafe.freeMemory(ptr)
|
||||
|
||||
destroyed = true
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun checkNullPtr(index: Long) { // ignore what IDEA says and do inline this
|
||||
//// commenting out because of the suspected (or minor?) performance impact.
|
||||
//// You may break the glass and use this tool when some fucking incomprehensible bugs ("vittujen vitun bugit")
|
||||
//// appear (e.g. getting garbage values when it fucking shouldn't)
|
||||
|
||||
//assert(!destroyed) { throw NullPointerException("The pointer is already destroyed ($this)") }
|
||||
//if (index !in 0 until size) throw IndexOutOfBoundsException("Index: $index; alloc size: $size")
|
||||
}
|
||||
|
||||
operator fun get(index: Long): Byte {
|
||||
checkNullPtr(index)
|
||||
return UnsafeHelper.unsafe.getByte(ptr + index)
|
||||
}
|
||||
|
||||
operator fun set(index: Long, value: Byte) {
|
||||
checkNullPtr(index)
|
||||
UnsafeHelper.unsafe.putByte(ptr + index, value)
|
||||
}
|
||||
|
||||
// NOTE: get/set multibyte values are NOT BYTE-ALIGNED!
|
||||
|
||||
fun getFloat(index: Long): Float {
|
||||
checkNullPtr(index)
|
||||
return UnsafeHelper.unsafe.getFloat(ptr + index)
|
||||
}
|
||||
|
||||
fun getInt(index: Long): Int {
|
||||
checkNullPtr(index)
|
||||
return UnsafeHelper.unsafe.getInt(ptr + index)
|
||||
}
|
||||
|
||||
fun getShort(index: Long): Short {
|
||||
checkNullPtr(index)
|
||||
return UnsafeHelper.unsafe.getShort(ptr + index)
|
||||
}
|
||||
|
||||
fun setFloat(index: Long, value: Float) {
|
||||
checkNullPtr(index)
|
||||
UnsafeHelper.unsafe.putFloat(ptr + index, value)
|
||||
}
|
||||
|
||||
fun setInt(index: Long, value: Int) {
|
||||
checkNullPtr(index)
|
||||
UnsafeHelper.unsafe.putInt(ptr + index, value)
|
||||
}
|
||||
|
||||
fun setShort(index: Long, value: Short) {
|
||||
checkNullPtr(index)
|
||||
UnsafeHelper.unsafe.putShort(ptr + index, value)
|
||||
}
|
||||
|
||||
fun fillWith(byte: Byte) {
|
||||
UnsafeHelper.unsafe.setMemory(ptr, size, byte)
|
||||
}
|
||||
|
||||
override fun toString() = "0x${ptr.toString(16)} with size $size"
|
||||
override fun equals(other: Any?) = this.ptr == (other as UnsafePtr).ptr && this.size == other.size
|
||||
|
||||
inline fun printStackTrace(obj: Any) = printStackTrace(obj, System.out) // because of Java
|
||||
|
||||
fun printStackTrace(obj: Any, out: PrintStream = System.out) {
|
||||
Thread.currentThread().stackTrace.forEach {
|
||||
out.println("[${obj.javaClass.simpleName}] ... $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
215
tsvm_core/src/net/torvald/tsvm/VM.kt
Normal file
215
tsvm_core/src/net/torvald/tsvm/VM.kt
Normal file
@@ -0,0 +1,215 @@
|
||||
package net.torvald.tsvm
|
||||
|
||||
import net.torvald.UnsafeHelper
|
||||
import net.torvald.UnsafePtr
|
||||
import net.torvald.tsvm.peripheral.IOSpace
|
||||
import net.torvald.tsvm.peripheral.PeriBase
|
||||
import net.torvald.tsvm.peripheral.VMProgramRom
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.*
|
||||
import kotlin.math.ceil
|
||||
|
||||
|
||||
class ErrorIllegalAccess(vm: VM, addr: Long) : RuntimeException("Segmentation fault at 0x${addr.toString(16).padStart(8, '0')} on VM id ${vm.id}")
|
||||
|
||||
|
||||
/**
|
||||
* A class representing an instance of a Virtual Machine
|
||||
*/
|
||||
|
||||
class VM(
|
||||
_memsize: Long,
|
||||
val worldInterface: WorldInterface,
|
||||
val roms: Array<VMProgramRom?> // first ROM must contain the BIOS
|
||||
) {
|
||||
|
||||
val id = java.util.Random().nextInt()
|
||||
|
||||
val memsize = minOf(USER_SPACE_SIZE, _memsize.toLong())
|
||||
private val MALLOC_UNIT = 64
|
||||
private val mallocBlockSize = (memsize / MALLOC_UNIT).toInt()
|
||||
|
||||
internal val usermem = UnsafeHelper.allocate(memsize)
|
||||
|
||||
val peripheralTable = Array(8) { PeripheralEntry() }
|
||||
|
||||
fun getIO(): IOSpace = peripheralTable[0].peripheral as IOSpace
|
||||
|
||||
//lateinit var printStream: OutputStream
|
||||
//lateinit var errorStream: OutputStream
|
||||
//lateinit var inputStream: InputStream // InputStream should not be a singleton, as it HAS TO open and close the stream.
|
||||
// Printstreams don't need that so they're singleton.
|
||||
|
||||
var getPrintStream: () -> OutputStream = { TODO() }
|
||||
var getErrorStream: () -> OutputStream = { TODO() }
|
||||
var getInputStream: () -> InputStream = { TODO() }
|
||||
|
||||
var startTime: Long = -1
|
||||
|
||||
var resetDown = false
|
||||
var stopDown = false
|
||||
|
||||
var romMapping = 255
|
||||
internal set
|
||||
|
||||
init {
|
||||
init()
|
||||
}
|
||||
|
||||
fun init() {
|
||||
peripheralTable[0] = PeripheralEntry(
|
||||
IOSpace(this),
|
||||
HW_RESERVE_SIZE,
|
||||
MMIO_SIZE.toInt() - 256,
|
||||
64
|
||||
)
|
||||
|
||||
println("[VM] Creating new VM with ID of $id, memsize $memsize")
|
||||
|
||||
startTime = System.nanoTime()
|
||||
}
|
||||
|
||||
|
||||
fun findPeribyType(searchTerm: String): PeripheralEntry? {
|
||||
for (i in 0..7) {
|
||||
if (peripheralTable[i].type == searchTerm) return peripheralTable[i]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun update(delta: Float) {
|
||||
getIO().update(delta)
|
||||
}
|
||||
|
||||
fun dispose() {
|
||||
usermem.destroy()
|
||||
peripheralTable.forEach { it.peripheral?.dispose() }
|
||||
}
|
||||
|
||||
open fun getUptime() = System.nanoTime() - startTime
|
||||
|
||||
/*
|
||||
NOTE: re-fill peripheralTable whenever the VM cold-boots!
|
||||
you are absolutely not supposed to hot-swap peripheral cards when the computer is on
|
||||
*/
|
||||
|
||||
|
||||
companion object {
|
||||
val CHARSET = Charsets.ISO_8859_1
|
||||
|
||||
val MMIO_SIZE = 128.kB()
|
||||
val HW_RESERVE_SIZE = 1024.kB()
|
||||
val USER_SPACE_SIZE = 8192.kB()
|
||||
|
||||
const val PERITYPE_GPU_AND_TERM = "gpu"
|
||||
}
|
||||
|
||||
internal fun translateAddr(addr: Long): Pair<Any?, Long> {
|
||||
return when (addr) {
|
||||
// DO note that numbers in Lua are double precision floats (ignore Lua 5.3 for now)
|
||||
in 0..8192.kB() - 1 -> usermem to addr
|
||||
in -1024.kB()..-1 -> peripheralTable[0].peripheral to (-addr - 1)
|
||||
in -2048.kB()..-1024.kB() - 1 -> peripheralTable[1].peripheral to (-addr - 1 - 1024.kB())
|
||||
in -3072.kB()..-2048.kB() - 1 -> peripheralTable[2].peripheral to (-addr - 1 - 2048.kB())
|
||||
in -4096.kB()..-3072.kB() - 1 -> peripheralTable[3].peripheral to (-addr - 1 - 3072.kB())
|
||||
in -5120.kB()..-4096.kB() - 1 -> peripheralTable[4].peripheral to (-addr - 1 - 4096.kB())
|
||||
in -6144.kB()..-5120.kB() - 1 -> peripheralTable[5].peripheral to (-addr - 1 - 5120.kB())
|
||||
in -7168.kB()..-6144.kB() - 1 -> peripheralTable[6].peripheral to (-addr - 1 - 6144.kB())
|
||||
in -8192.kB()..-7168.kB() - 1 -> peripheralTable[7].peripheral to (-addr - 1 - 7168.kB())
|
||||
else -> null to addr
|
||||
}
|
||||
}
|
||||
|
||||
fun poke(addr: Long, value: Byte) {
|
||||
val (memspace, offset) = translateAddr(addr)
|
||||
if (memspace == null)
|
||||
throw ErrorIllegalAccess(this, addr)
|
||||
else if (memspace is UnsafePtr) {
|
||||
if (addr >= memspace.size)
|
||||
throw ErrorIllegalAccess(this, addr)
|
||||
else
|
||||
memspace.set(offset, value)
|
||||
}
|
||||
else
|
||||
(memspace as PeriBase).poke(offset, value)
|
||||
}
|
||||
|
||||
fun peek(addr:Long): Byte? {
|
||||
val (memspace, offset) = translateAddr(addr)
|
||||
return if (memspace == null)
|
||||
null
|
||||
else if (memspace is UnsafePtr) {
|
||||
if (addr >= memspace.size)
|
||||
throw ErrorIllegalAccess(this, addr)
|
||||
else
|
||||
memspace.get(offset)
|
||||
}
|
||||
else
|
||||
(memspace as PeriBase).peek(offset)
|
||||
}
|
||||
|
||||
private val mallocMap = BitSet(mallocBlockSize)
|
||||
private val mallocSizes = HashMap<Int, Int>() // HashMap<Block Index, Block Count>
|
||||
|
||||
private fun findEmptySpace(blockSize: Int): Int? {
|
||||
var cursorHead = 0
|
||||
var cursorTail: Int
|
||||
val cursorHeadMaxInclusive = mallocBlockSize - blockSize
|
||||
while (cursorHead <= cursorHeadMaxInclusive) {
|
||||
cursorHead = mallocMap.nextClearBit(cursorHead)
|
||||
cursorTail = cursorHead + blockSize - 1
|
||||
if (cursorTail > mallocBlockSize) return null
|
||||
if (mallocMap.get(cursorTail) == false) {
|
||||
var isNotEmpty = false
|
||||
for (k in cursorHead..cursorTail) {
|
||||
isNotEmpty = isNotEmpty or mallocMap[k]
|
||||
}
|
||||
|
||||
if (!isNotEmpty) {
|
||||
mallocMap.set(cursorHead, cursorTail + 1)
|
||||
return cursorHead
|
||||
}
|
||||
}
|
||||
cursorHead = cursorTail + 1
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
internal fun malloc(size: Int): Int {
|
||||
val allocBlocks = ceil(size.toDouble() / MALLOC_UNIT).toInt()
|
||||
val blockStart = findEmptySpace(allocBlocks) ?: throw OutOfMemoryError()
|
||||
|
||||
mallocSizes[blockStart] = allocBlocks
|
||||
return blockStart * MALLOC_UNIT
|
||||
}
|
||||
|
||||
internal fun free(ptr: Int) {
|
||||
val index = ptr / MALLOC_UNIT
|
||||
val count = mallocSizes[index] ?: throw OutOfMemoryError()
|
||||
|
||||
mallocMap.set(index, index + count, false)
|
||||
mallocSizes.remove(index)
|
||||
}
|
||||
|
||||
internal data class VMNativePtr(val address: Int, val size: Int)
|
||||
}
|
||||
|
||||
class PeripheralEntry(
|
||||
val peripheral: PeriBase? = null,
|
||||
val memsize: Long = 0,
|
||||
val mmioSize: Int = 0,
|
||||
val interruptCount: Int = 0, // max: 4
|
||||
) {
|
||||
val type = peripheral?.typestring
|
||||
}
|
||||
|
||||
class PeripheralEntry2(
|
||||
val memsize: Long = 0,
|
||||
val mmioSize: Int = 0,
|
||||
val interruptCount: Int = 0, // max: 4
|
||||
val peripheralClassname: String,
|
||||
vararg val args: Any
|
||||
)
|
||||
|
||||
fun Int.kB() = this * 1024L
|
||||
145
tsvm_core/src/net/torvald/tsvm/VMJSR223Delegate.kt
Normal file
145
tsvm_core/src/net/torvald/tsvm/VMJSR223Delegate.kt
Normal file
@@ -0,0 +1,145 @@
|
||||
package net.torvald.tsvm
|
||||
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUlong
|
||||
import java.nio.charset.Charset
|
||||
|
||||
/**
|
||||
* Pass the instance of the class to the ScriptEngine's binding, preferably under the namespace of "vm"
|
||||
*/
|
||||
class VMJSR223Delegate(val vm: VM) {
|
||||
|
||||
fun poke(addr: Int, value: Int) = vm.poke(addr.toLong(), value.toByte())
|
||||
fun peek(addr: Int) = vm.peek(addr.toLong())!!.toInt().and(255)
|
||||
fun nanoTime() = System.nanoTime()
|
||||
fun malloc(size: Int) = vm.malloc(size)
|
||||
fun free(ptr: Int) = vm.free(ptr)
|
||||
fun mapRom(slot: Int) {
|
||||
vm.romMapping = slot.and(255)
|
||||
}
|
||||
fun romReadAll(): String {
|
||||
if (vm.romMapping == 255 || vm.romMapping !in vm.roms.indices || vm.roms[vm.romMapping] == null) return ""
|
||||
return vm.roms[vm.romMapping]!!.readAll()
|
||||
}
|
||||
|
||||
fun uptime(): Long {
|
||||
vm.poke(-69, -1)
|
||||
var r = 0L
|
||||
for (i in 0L..7L) {
|
||||
r = r or vm.peek(-73 - i)!!.toUlong().shl(8 * i.toInt())
|
||||
}
|
||||
return r
|
||||
}
|
||||
fun currentTimeInMills(): Long {
|
||||
vm.poke(-69, -1)
|
||||
var r = 0L
|
||||
for (i in 0L..7L) {
|
||||
r = r or vm.peek(-81 - i)!!.toUlong().shl(8 * i.toInt())
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
fun print(s: Any) {
|
||||
//System.out.print("[Nashorn] $s")
|
||||
//System.out.print(s)
|
||||
vm.getPrintStream().write("$s".toByteArray(VM.CHARSET))
|
||||
}
|
||||
fun println(s: Any = "") {
|
||||
System.out.println("[Graal] $s")
|
||||
//System.out.println(s)
|
||||
vm.getPrintStream().write(("$s\n").toByteArray(VM.CHARSET))
|
||||
}
|
||||
|
||||
/**
|
||||
* @return key being hit, of which:
|
||||
* a-zA-Z1-9: corresponding ASCII code
|
||||
*
|
||||
* Up: 200
|
||||
* Left: 203
|
||||
* Down: 208
|
||||
* Right: 205
|
||||
*
|
||||
* PgUp: 201
|
||||
* PgDn: 209
|
||||
* Home: 199
|
||||
* End: 207
|
||||
* Ins: 201
|
||||
* Del: 211
|
||||
*
|
||||
* Return: 13 (^M)
|
||||
* Bksp: 8 (^H)
|
||||
*
|
||||
* ^A-^Z: 1 through 26
|
||||
*/
|
||||
fun readKey(): Int {
|
||||
val inputStream = vm.getInputStream()
|
||||
var key: Int = inputStream.read()
|
||||
inputStream.close()
|
||||
return key
|
||||
}
|
||||
|
||||
/**
|
||||
* Read series of key inputs until Enter/Return key is pressed. Backspace will work but any other non-printable
|
||||
* characters (e.g. arrow keys) won't work.
|
||||
*/
|
||||
fun read(): String {
|
||||
val inputStream = vm.getInputStream()
|
||||
val sb = StringBuilder()
|
||||
var key: Int
|
||||
do {
|
||||
key = inputStream.read()
|
||||
|
||||
if ((key == 8 && sb.isNotEmpty()) || key in 0x20..0x7E) {
|
||||
this.print("${key.toChar()}")
|
||||
}
|
||||
|
||||
when (key) {
|
||||
8 -> if (sb.isNotEmpty()) sb.deleteCharAt(sb.lastIndex)
|
||||
in 0x20..0x7E -> sb.append(key.toChar())
|
||||
}
|
||||
} while (key != 13 && key != 10)
|
||||
this.print("\n") // printout \n
|
||||
|
||||
inputStream.close()
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Read series of key inputs until Enter/Return key is pressed. Backspace will work but any other non-printable
|
||||
* characters (e.g. arrow keys) won't work.
|
||||
*/
|
||||
fun readNoEcho(): String {
|
||||
val inputStream = vm.getInputStream()
|
||||
val sb = StringBuilder()
|
||||
var key: Int
|
||||
do {
|
||||
key = inputStream.read()
|
||||
|
||||
when (key) {
|
||||
8 -> if (sb.isNotEmpty()) sb.deleteCharAt(sb.lastIndex)
|
||||
in 0x20..0x7E -> sb.append(key.toChar())
|
||||
}
|
||||
} while (key != 13 && key != 10)
|
||||
this.println() // printout \n
|
||||
|
||||
inputStream.close()
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
fun spin() {
|
||||
Thread.sleep(4L);
|
||||
}
|
||||
|
||||
fun waitForMemChg(addr: Int, andMask: Int, xorMask: Int) {
|
||||
while ((peek(addr) xor xorMask) and andMask == 0) {
|
||||
spin();
|
||||
}
|
||||
}
|
||||
fun waitForMemChg(addr: Int, andMask: Int) = waitForMemChg(addr, andMask, 0)
|
||||
|
||||
}
|
||||
|
||||
class VMSerialDebugger(val vm: VM) {
|
||||
fun print(s: Any?) = System.out.print("$s")
|
||||
fun println(s: Any?) = System.out.println("$s")
|
||||
fun printerr(s: Any?) = System.err.println("$s")
|
||||
}
|
||||
103
tsvm_core/src/net/torvald/tsvm/VMRunnerFactory.kt
Normal file
103
tsvm_core/src/net/torvald/tsvm/VMRunnerFactory.kt
Normal file
@@ -0,0 +1,103 @@
|
||||
package net.torvald.tsvm
|
||||
|
||||
import org.graalvm.polyglot.Context
|
||||
import org.graalvm.polyglot.HostAccess
|
||||
import java.io.FileReader
|
||||
import javax.script.ScriptEngineManager
|
||||
|
||||
abstract class VMRunner(val extension: String) {
|
||||
abstract suspend fun executeCommand(command: String)
|
||||
abstract suspend fun evalGlobal(command: String)
|
||||
abstract fun close()
|
||||
}
|
||||
|
||||
object VMRunnerFactory {
|
||||
|
||||
private var firstTime = true
|
||||
|
||||
operator fun invoke(vm: VM, extension: String): VMRunner {
|
||||
|
||||
if (firstTime) {
|
||||
firstTime = false
|
||||
ScriptEngineManager().engineFactories.forEach {
|
||||
println("[VMRunnerFactory] ext: ${it.extensions}, name: ${it.engineName}")
|
||||
}
|
||||
}
|
||||
|
||||
return when (extension) {
|
||||
/*"vt2" -> {
|
||||
object : VMRunner(extension) {
|
||||
|
||||
val engine =
|
||||
Videotron2K(vm.findPeribyType(VM.PERITYPE_GPU_AND_TERM)!!.peripheral!! as GraphicsAdapter)
|
||||
|
||||
override suspend fun executeCommand(command: String) {
|
||||
engine.eval(command)
|
||||
}
|
||||
|
||||
override suspend fun evalGlobal(command: String) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
}*/
|
||||
"js" -> {
|
||||
object : VMRunner(extension) {
|
||||
private val context = Context.newBuilder("js")
|
||||
.allowHostAccess(HostAccess.ALL)
|
||||
.allowHostClassLookup { false }
|
||||
.allowIO(false)
|
||||
.build()
|
||||
private val bind = context.getBindings("js")
|
||||
|
||||
init {
|
||||
// see https://github.com/graalvm/graaljs/blob/master/docs/user/ScriptEngine.md
|
||||
bind.putMember("polyglot.js.allowHostAccess", true)
|
||||
bind.putMember("js.console", false)
|
||||
|
||||
bind.putMember("sys", VMJSR223Delegate(vm)) // TODO use delegator class to access peripheral (do not expose VM itself)
|
||||
bind.putMember("graphics", GraphicsJSR223Delegate(vm))
|
||||
bind.putMember("serial", VMSerialDebugger(vm))
|
||||
bind.putMember("gzip", CompressorDelegate)
|
||||
bind.putMember("base64", Base64Delegate)
|
||||
bind.putMember("com", SerialHelperDelegate(vm))
|
||||
bind.putMember("dma", DMADelegate(vm))
|
||||
|
||||
val fr = FileReader("./assets/JS_INIT.js")
|
||||
val prg = fr.readText()
|
||||
fr.close()
|
||||
context.eval("js", sanitiseJS(prg))
|
||||
}
|
||||
|
||||
override suspend fun executeCommand(command: String) {
|
||||
try {
|
||||
context.eval("js", encapsulateJS(sanitiseJS(command)))
|
||||
}
|
||||
catch (e: javax.script.ScriptException) {
|
||||
System.err.println("ScriptException from the script:")
|
||||
System.err.println(command.substring(0, minOf(1024, command.length)))
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun evalGlobal(command: String) {
|
||||
context.eval("js", "\"use strict\";" + sanitiseJS(command))
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
context.close(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw UnsupportedOperationException("Unsupported script extension: $extension")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun sanitiseJS(code: String) = code//.replace("\\", "\\\\")
|
||||
private fun encapsulateJS(code: String) = "\"use strict\";(function(){$code})()"
|
||||
|
||||
}
|
||||
12
tsvm_core/src/net/torvald/tsvm/WorldInterface.kt
Normal file
12
tsvm_core/src/net/torvald/tsvm/WorldInterface.kt
Normal file
@@ -0,0 +1,12 @@
|
||||
package net.torvald.tsvm
|
||||
|
||||
interface WorldInterface {
|
||||
fun currentTimeInMills(): Long
|
||||
}
|
||||
|
||||
/**
|
||||
* Real world interface for non-ingame testing. For the Ingame, implement your own.
|
||||
*/
|
||||
class TheRealWorld : WorldInterface {
|
||||
override fun currentTimeInMills(): Long = System.currentTimeMillis()
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package net.torvald.tsvm.peripheral
|
||||
|
||||
abstract class BlockTransferInterface(val isMaster: Boolean, val isSlave: Boolean) {
|
||||
|
||||
protected var recipient: BlockTransferInterface? = null
|
||||
|
||||
@Volatile var ready = true
|
||||
@Volatile var busy = false
|
||||
|
||||
@Volatile var statusCode = 0
|
||||
|
||||
protected var sendmode = false; private set
|
||||
@Volatile var blockSize = 0
|
||||
|
||||
open fun attachDevice(device: BlockTransferInterface?) {
|
||||
recipient = device
|
||||
device?.recipient = this
|
||||
}
|
||||
|
||||
open fun areYouReady(): Boolean = recipient?.ready ?: false
|
||||
open fun areYouBusy(): Boolean = recipient?.busy ?: false
|
||||
|
||||
/** Writes a thing to the recipient.
|
||||
* A method exposed to outside of the box
|
||||
* @return number of bytes actually sent over*/
|
||||
abstract fun startSendImpl(recipient: BlockTransferInterface): Int
|
||||
/** The actual implementation */
|
||||
fun startSend() {
|
||||
//if (areYouReady()) {
|
||||
busy = true
|
||||
ready = false
|
||||
|
||||
recipient?.let {
|
||||
this.blockSize = startSendImpl(it)
|
||||
//println("[BlockTransferInterface.startSend()] recipients blocksize = ${this.blockSize}")
|
||||
}
|
||||
|
||||
busy = false
|
||||
ready = true
|
||||
//}
|
||||
//else {
|
||||
// throw IOException("${this.javaClass.canonicalName}: Device '${recipient?.javaClass?.canonicalName}' is not ready to receive")
|
||||
//}
|
||||
}
|
||||
|
||||
/** Ask the recipient to start send its thing to me so that I can 'read'
|
||||
*/
|
||||
open fun startRead() {
|
||||
recipient?.startSend()
|
||||
}
|
||||
|
||||
/** A method called by the sender so it can ACTUALLY write its thing onto me.
|
||||
*
|
||||
* @param inputData received message, usually 4096 bytes long and null-padded */
|
||||
abstract fun writeoutImpl(inputData: ByteArray)
|
||||
/** The actual implementation; must be called by a sender class */
|
||||
fun writeout(inputData: ByteArray) {
|
||||
busy = true
|
||||
ready = false
|
||||
blockSize = minOf(inputData.size, BLOCK_SIZE)
|
||||
writeoutImpl(inputData)
|
||||
busy = false
|
||||
ready = true
|
||||
}
|
||||
abstract fun hasNext(): Boolean
|
||||
open fun doYouHaveNext(): Boolean = recipient?.hasNext() ?: false
|
||||
open fun yourBlockSize(): Int = recipient?.blockSize ?: 0
|
||||
|
||||
fun getYourStatusCode() = recipient?.statusCode ?: 0
|
||||
|
||||
/** @param sendmode TRUE for send, FALSE for receive */
|
||||
open fun setMode(sendmode: Boolean) {
|
||||
this.sendmode = sendmode
|
||||
}
|
||||
/** @return TRUE for send, FALSE for receive */
|
||||
open fun getMode(): Boolean = sendmode
|
||||
|
||||
open fun cableConnected(): Boolean = recipient?.recipient == this
|
||||
|
||||
companion object {
|
||||
const val BLOCK_SIZE = 4096
|
||||
|
||||
// these consts are UNUSABLE on writeoutImpl because wtf
|
||||
// still possible to use on stringbuilder tho
|
||||
const val GOOD_NEWS = 0x06.toByte()
|
||||
const val BAD_NEWS = 0x15.toByte()
|
||||
const val UNIT_SEP = 0x1F.toByte()
|
||||
const val END_OF_SEND_BLOCK = 0x17.toByte()
|
||||
}
|
||||
}
|
||||
|
||||
fun ByteArray.trimNull(): ByteArray {
|
||||
var cnt = BlockTransferInterface.BLOCK_SIZE - 1
|
||||
while (cnt >= 0) {
|
||||
if (this[cnt] != 0.toByte()) break
|
||||
cnt -= 1
|
||||
}
|
||||
return this.sliceArray(0..cnt)
|
||||
}
|
||||
|
||||
fun ByteArray.startsWith(other: ByteArray) = this.sliceArray(other.indices).contentEquals(other)
|
||||
@@ -0,0 +1,34 @@
|
||||
package net.torvald.tsvm.peripheral
|
||||
|
||||
import net.torvald.UnsafeHelper
|
||||
import net.torvald.tsvm.VM
|
||||
|
||||
/**
|
||||
* Implementation of single COM port
|
||||
*/
|
||||
class BlockTransferPort(val vm: VM, val portno: Int) : BlockTransferInterface(true, false) {
|
||||
|
||||
internal var hasNext = false
|
||||
|
||||
override fun startSendImpl(recipient: BlockTransferInterface): Int {
|
||||
recipient.writeout(ByteArray(BLOCK_SIZE) { vm.getIO().blockTransferTx[portno][it.toLong()] })
|
||||
return blockSize // use MMIO to modify this variable
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean = hasNext
|
||||
|
||||
override fun writeoutImpl(inputData: ByteArray) {
|
||||
//val copySize = minOf(BLOCK_SIZE, inputData.size).toLong()
|
||||
//val arrayOffset = UnsafeHelper.getArrayOffset(inputData)
|
||||
//UnsafeHelper.memcpyRaw(inputData, arrayOffset, null, vm.getIO().blockTransferRx[portno].ptr, copySize)
|
||||
|
||||
// not exposing raw memory to block probable security hole
|
||||
//println("[BlockTranferPort] writeout size: ${inputData.size}")
|
||||
for (k in 0 until BLOCK_SIZE) {
|
||||
vm.getIO().blockTransferRx[portno][k.toLong()] = if (k >= inputData.size) 0 else inputData[k]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package net.torvald.tsvm.peripheral
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.graphics.Texture
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUlong
|
||||
import net.torvald.tsvm.TextureRegionPack
|
||||
import net.torvald.tsvm.VM
|
||||
|
||||
class CharacterLCDdisplay(vm: VM) : GraphicsAdapter(vm, AdapterConfig(
|
||||
"pmlcd_inverted", 240, 64, 40, 8, 253, 255, 262144L, "lcd2.png", 0.7f, TEXT_TILING_SHADER_LCD, DRAW_SHADER_FRAG_LCD, 2f
|
||||
)
|
||||
) {
|
||||
|
||||
private val machine = Texture("./assets/4008_portable_full.png")
|
||||
private val lcdFont = TextureRegionPack(Texture("./assets/lcd.png"), 12, 16)
|
||||
|
||||
/*override fun peek(addr: Long): Byte? {
|
||||
return when (addr) {
|
||||
in 0 until 250880 -> (-1).toByte()
|
||||
else -> super.peek(addr)
|
||||
}
|
||||
}
|
||||
|
||||
override fun poke(addr: Long, byte: Byte) {
|
||||
when (addr) {
|
||||
in 0 until 250880 -> { /*do nothing*/ }
|
||||
else -> super.poke(addr, byte)
|
||||
}
|
||||
}*/
|
||||
|
||||
override fun render(delta: Float, batch: SpriteBatch, xoff: Float, yoff: Float) {
|
||||
batch.shader = null
|
||||
batch.inUse {
|
||||
batch.color = Color.WHITE
|
||||
batch.draw(machine, xoff, yoff)
|
||||
}
|
||||
super.render(delta, batch, xoff+74, yoff+102)
|
||||
|
||||
// draw BMS and RTC
|
||||
val batPerc = "89"
|
||||
val batVolt = "5.1"
|
||||
val batText = " $batPerc% ${batVolt}V"
|
||||
val msg = (1024L until 1048L).map { vm.getIO().mmio_read(it)!!.toInt().and(255) }
|
||||
vm.poke(-69,2)
|
||||
val time_t = currentTimeInMills()
|
||||
val min = (time_t / 60000) % 60
|
||||
val hour = (time_t / 3600000) % 24
|
||||
val clock = "${"$hour".padStart(2,'0')}:${"$min".padStart(2,'0')} "
|
||||
|
||||
batch.shader = null
|
||||
batch.inUse {
|
||||
batch.color = Color.WHITE
|
||||
val y = yoff + 102 + config.height * config.drawScale
|
||||
for (x in 0 until config.textCols) {
|
||||
batch.draw(lcdFont.get(0,0), xoff+74 + x * lcdFont.tileW, y)
|
||||
}
|
||||
for (x in clock.indices) {
|
||||
val ccode = clock[x].toInt()
|
||||
batch.draw(lcdFont.get(ccode % 16, ccode / 16), xoff+74 + x * lcdFont.tileW, y)
|
||||
}
|
||||
for (x in msg.indices) {
|
||||
val ccode = msg[x]
|
||||
batch.draw(lcdFont.get(ccode % 16, ccode / 16), xoff+74 + (x + 6) * lcdFont.tileW, y)
|
||||
}
|
||||
for (x in batText.indices) {
|
||||
val ccode = batText[x].toInt()
|
||||
batch.draw(lcdFont.get(ccode % 16, ccode / 16), xoff+74 + (config.textCols - batText.length + x) * lcdFont.tileW, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun currentTimeInMills(): Long {
|
||||
vm.poke(-69, -1)
|
||||
var r = 0L
|
||||
for (i in 0L..7L) {
|
||||
r = r or vm.peek(-81 - i)!!.toUlong().shl(8 * i.toInt())
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
override fun dispose() {
|
||||
machine.dispose()
|
||||
lcdFont.dispose()
|
||||
super.dispose()
|
||||
}
|
||||
}
|
||||
155
tsvm_core/src/net/torvald/tsvm/peripheral/ExtDisp.kt
Normal file
155
tsvm_core/src/net/torvald/tsvm/peripheral/ExtDisp.kt
Normal file
@@ -0,0 +1,155 @@
|
||||
package net.torvald.tsvm.peripheral
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.graphics.Pixmap
|
||||
import com.badlogic.gdx.graphics.Texture
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import com.badlogic.gdx.math.Matrix4
|
||||
import net.torvald.tsvm.LoadShader
|
||||
import net.torvald.tsvm.VM
|
||||
|
||||
/**
|
||||
* External Display that is always visible through its own UI ingame.
|
||||
*
|
||||
* Created by minjaesong on 2021-12-01.
|
||||
*/
|
||||
class ExtDisp(val vm: VM, val width: Int, val height: Int) : PeriBase {
|
||||
|
||||
constructor(vm: VM, w: java.lang.Integer, h: java.lang.Integer) : this(
|
||||
vm, w.toInt(), h.toInt()
|
||||
)
|
||||
|
||||
override val typestring = "oled"
|
||||
|
||||
override fun getVM(): VM {
|
||||
return vm
|
||||
}
|
||||
|
||||
internal val framebuffer = Pixmap(width, height, Pixmap.Format.Alpha)
|
||||
private val outFBObatch = SpriteBatch()
|
||||
|
||||
protected val drawShader = LoadShader(GraphicsAdapter.DRAW_SHADER_VERT, OLED_PAL_SHADER)
|
||||
|
||||
init {
|
||||
// no orthographic camera, must be "raw" Matrix4
|
||||
val m = Matrix4()
|
||||
m.setToOrtho2D(0f, 0f, width.toFloat(), height.toFloat())
|
||||
outFBObatch.projectionMatrix = m
|
||||
|
||||
framebuffer.blending = Pixmap.Blending.None
|
||||
framebuffer.setColor(0)
|
||||
framebuffer.fill()
|
||||
}
|
||||
|
||||
private lateinit var tex: Texture
|
||||
|
||||
open fun render(uiBatch: SpriteBatch, xoff: Float, yoff: Float) {
|
||||
framebuffer.pixels.position(0)
|
||||
|
||||
tex = Texture(framebuffer)
|
||||
|
||||
uiBatch.inUse {
|
||||
uiBatch.color = Color.WHITE
|
||||
uiBatch.shader = drawShader
|
||||
uiBatch.draw(tex, xoff, yoff)
|
||||
}
|
||||
|
||||
tex.dispose()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next power of two of the given number.
|
||||
*
|
||||
* E.g. for an input 100, this returns 128.
|
||||
* Returns 1 for all numbers <= 1.
|
||||
*
|
||||
* @param number The number to obtain the POT for.
|
||||
* @return The next power of two.
|
||||
*/
|
||||
private fun nextPowerOfTwo(number: Int): Int {
|
||||
var number = number
|
||||
number--
|
||||
number = number or (number shr 1)
|
||||
number = number or (number shr 2)
|
||||
number = number or (number shr 4)
|
||||
number = number or (number shr 8)
|
||||
number = number or (number shr 16)
|
||||
number++
|
||||
number += if (number == 0) 1 else 0
|
||||
return number
|
||||
}
|
||||
|
||||
override fun peek(addr: Long): Byte? {
|
||||
val adi = addr.toInt()
|
||||
return when (addr) {
|
||||
in 0 until width * height -> {
|
||||
framebuffer.pixels.get(adi)
|
||||
}
|
||||
in 0 until nextPowerOfTwo(width * height) -> { null }
|
||||
else -> peek(addr % nextPowerOfTwo(width * height))
|
||||
}
|
||||
}
|
||||
|
||||
override fun poke(addr: Long, byte: Byte) {
|
||||
val adi = addr.toInt()
|
||||
val bi = byte.toInt().and(255)
|
||||
when (addr) {
|
||||
in 0 until width * height -> {
|
||||
framebuffer.pixels.put(adi, byte)
|
||||
}
|
||||
(width * height).toLong() -> {
|
||||
framebuffer.setColor(bi.shl(24) or bi.shr(16) or bi.shl(8) or bi)
|
||||
framebuffer.fill()
|
||||
}
|
||||
in 0 until nextPowerOfTwo(width * height) -> { /* do nothing */ }
|
||||
else -> poke(addr % nextPowerOfTwo(width * height), byte)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mmio_read(addr: Long): Byte? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun mmio_write(addr: Long, byte: Byte) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
try { framebuffer.dispose() } catch (e: Throwable) {}
|
||||
try { tex.dispose() } catch (e: Throwable) {}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
val OLED_PAL_SHADER = """
|
||||
#version 130
|
||||
|
||||
varying vec4 v_color;
|
||||
varying vec2 v_texCoords;
|
||||
uniform sampler2D u_texture;
|
||||
vec4 pal[16] = vec4[](
|
||||
vec4(0.0,0.0,0.0,1.0),
|
||||
vec4(0.0,0.1765,0.6667,1.0),
|
||||
vec4(0.0,0.6667,0.0,1.0),
|
||||
vec4(0.0,0.7255,0.6667,1.0),
|
||||
vec4(0.6667,0.0,0.0,1.0),
|
||||
vec4(0.6667,0.1765,0.6667,1.0),
|
||||
vec4(0.6667,0.6667,0.0,1.0),
|
||||
vec4(0.6667,0.6667,0.6667,1.0),
|
||||
|
||||
vec4(0.0,0.0,0.0,1.0),
|
||||
vec4(0.0,0.2667,1.0,1.0),
|
||||
vec4(0.0,1.0,0.0,1.0),
|
||||
vec4(0.0,1.0,1.0,1.0),
|
||||
vec4(1.0,0.0,0.0,1.0),
|
||||
vec4(1.0,0.2667,1.0,1.0),
|
||||
vec4(1.0,1.0,0.0,1.0),
|
||||
vec4(1.0,1.0,1.0,1.0)
|
||||
);
|
||||
|
||||
void main(void) {
|
||||
gl_FragColor = pal[int(texture2D(u_texture, v_texCoords).a * 255.0) % 16];
|
||||
}
|
||||
"""
|
||||
}
|
||||
}
|
||||
357
tsvm_core/src/net/torvald/tsvm/peripheral/GlassTty.kt
Normal file
357
tsvm_core/src/net/torvald/tsvm/peripheral/GlassTty.kt
Normal file
@@ -0,0 +1,357 @@
|
||||
package net.torvald.tsvm.peripheral
|
||||
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Implements standard TTY that can interpret some of the ANSI escape sequences
|
||||
*
|
||||
* A paper tty must be able to implemented by extending this class (and butchering some of the features), of which it
|
||||
* sets limits on some of the functions (notably 'setCursorPos')
|
||||
*/
|
||||
abstract class GlassTty(val TEXT_ROWS: Int, val TEXT_COLS: Int) {
|
||||
|
||||
/**
|
||||
* (x, y)
|
||||
*/
|
||||
abstract fun getCursorPos(): Pair<Int, Int>
|
||||
|
||||
/**
|
||||
* Think of it as a real paper tty;
|
||||
* setCursorPos must "wrap" the cursor properly when x-value goes out of screen bound.
|
||||
* For y-value, only when y < 0, set y to zero and don't care about the y-value goes out of bound.
|
||||
*/
|
||||
abstract fun setCursorPos(x: Int, y: Int)
|
||||
|
||||
abstract var rawCursorPos: Int
|
||||
abstract var blinkCursor: Boolean
|
||||
|
||||
abstract var ttyFore: Int
|
||||
abstract var ttyBack: Int
|
||||
abstract var ttyRawMode: Boolean
|
||||
|
||||
abstract fun putChar(x: Int, y: Int, text: Byte, foreColour: Byte = ttyFore.toByte(), backColour: Byte = ttyBack.toByte())
|
||||
|
||||
fun writeOut(char: Byte) {
|
||||
val (cx, cy) = getCursorPos()
|
||||
|
||||
val printable = acceptChar(char) // this function processes the escape codes and CRLFs
|
||||
|
||||
if (printable) {
|
||||
putChar(cx, cy, char)
|
||||
setCursorPos(cx + 1, cy) // should automatically wrap and advance a line for out-of-bound x-value
|
||||
}
|
||||
}
|
||||
|
||||
private var ttyEscState = TTY_ESC_STATE.INITIAL
|
||||
private val ttyEscArguments = Stack<Int>()
|
||||
/**
|
||||
* ONLY accepts a character to either process the escape sequence, or say the input character is allowed to print.
|
||||
* This function will alter the internal state of the TTY intepreter (aka this very class)
|
||||
*
|
||||
* Any unrecognisable escape sequence will result the internal state to be reset but the character WILL NOT be marked
|
||||
* as printable.
|
||||
*
|
||||
* @return true if character should be printed as-is
|
||||
*/
|
||||
private fun acceptChar(char: Byte): Boolean {
|
||||
fun reject(): Boolean {
|
||||
ttyEscState = TTY_ESC_STATE.INITIAL
|
||||
ttyEscArguments.clear()
|
||||
return true
|
||||
}
|
||||
fun accept(execute: () -> Unit): Boolean {
|
||||
ttyEscState = TTY_ESC_STATE.INITIAL
|
||||
execute.invoke()
|
||||
ttyEscArguments.clear()
|
||||
return false
|
||||
}
|
||||
fun registerNewNumberArg(newnum: Byte, newState: TTY_ESC_STATE) {
|
||||
ttyEscArguments.push(char.toInt() - 0x30)
|
||||
ttyEscState = newState
|
||||
}
|
||||
fun appendToExistingNumber(newnum: Byte) {
|
||||
ttyEscArguments.push(ttyEscArguments.pop() * 10 + (newnum.toInt() - 0x30))
|
||||
}
|
||||
|
||||
//println("[tty] accepting char $char, state: $ttyEscState")
|
||||
|
||||
when (ttyEscState) {
|
||||
TTY_ESC_STATE.INITIAL -> {
|
||||
when (char) {
|
||||
ESC -> ttyEscState = TTY_ESC_STATE.ESC
|
||||
LF -> crlf()
|
||||
BS -> backspace()
|
||||
TAB -> insertTab()
|
||||
BEL -> ringBell()
|
||||
in 0x00.toByte()..0x1F.toByte() -> return false
|
||||
else -> return true
|
||||
}
|
||||
}
|
||||
TTY_ESC_STATE.ESC -> {
|
||||
when (char.toChar()) {
|
||||
'c' -> return accept { resetTtyStatus() }
|
||||
'[' -> ttyEscState = TTY_ESC_STATE.CSI
|
||||
else -> return reject()
|
||||
}
|
||||
}
|
||||
TTY_ESC_STATE.CSI -> {
|
||||
when (char.toChar()) {
|
||||
'A' -> return accept { cursorUp() }
|
||||
'B' -> return accept { cursorDown() }
|
||||
'C' -> return accept { cursorFwd() }
|
||||
'D' -> return accept { cursorBack() }
|
||||
'E' -> return accept { cursorNextLine() }
|
||||
'F' -> return accept { cursorPrevLine() }
|
||||
'G' -> return accept { cursorX() }
|
||||
'J' -> return accept { eraseInDisp() }
|
||||
'K' -> return accept { eraseInLine() }
|
||||
'S' -> return accept { scrollUp() }
|
||||
'T' -> return accept { scrollDown() }
|
||||
'm' -> return accept { sgrOneArg() }
|
||||
'?' -> ttyEscState = TTY_ESC_STATE.PRIVATESEQ
|
||||
';' -> {
|
||||
ttyEscArguments.push(0)
|
||||
ttyEscState = TTY_ESC_STATE.SEP1
|
||||
}
|
||||
in '0'..'9' -> registerNewNumberArg(char, TTY_ESC_STATE.NUM1)
|
||||
else -> return reject()
|
||||
}
|
||||
}
|
||||
TTY_ESC_STATE.PRIVATESEQ -> {
|
||||
when (char.toChar()) {
|
||||
in '0'..'9' -> registerNewNumberArg(char, TTY_ESC_STATE.PRIVATENUM)
|
||||
else -> return reject()
|
||||
}
|
||||
}
|
||||
TTY_ESC_STATE.PRIVATENUM -> {
|
||||
when (char.toChar()) {
|
||||
'h' -> return accept { privateSeqH(ttyEscArguments.pop()) }
|
||||
'l' -> return accept { privateSeqL(ttyEscArguments.pop()) }
|
||||
in '0'..'9' -> appendToExistingNumber(char)
|
||||
else -> return reject()
|
||||
}
|
||||
}
|
||||
TTY_ESC_STATE.NUM1 -> {
|
||||
when (char.toChar()) {
|
||||
'A' -> return accept { cursorUp(ttyEscArguments.pop()) }
|
||||
'B' -> return accept { cursorDown(ttyEscArguments.pop()) }
|
||||
'C' -> return accept { cursorFwd(ttyEscArguments.pop()) }
|
||||
'D' -> return accept { cursorBack(ttyEscArguments.pop()) }
|
||||
'E' -> return accept { cursorNextLine(ttyEscArguments.pop()) }
|
||||
'F' -> return accept { cursorPrevLine(ttyEscArguments.pop()) }
|
||||
'G' -> return accept { cursorX(ttyEscArguments.pop()) }
|
||||
'J' -> return accept { eraseInDisp(ttyEscArguments.pop()) }
|
||||
'K' -> return accept { eraseInLine(ttyEscArguments.pop()) }
|
||||
'S' -> return accept { scrollUp(ttyEscArguments.pop()) }
|
||||
'T' -> return accept { scrollDown(ttyEscArguments.pop()) }
|
||||
'm' -> return accept { sgrOneArg(ttyEscArguments.pop()) }
|
||||
';' -> ttyEscState = TTY_ESC_STATE.SEP1
|
||||
in '0'..'9' -> appendToExistingNumber(char)
|
||||
else -> return reject()
|
||||
}
|
||||
}
|
||||
TTY_ESC_STATE.NUM2 -> {
|
||||
when (char.toChar()) {
|
||||
in '0'..'9' -> appendToExistingNumber(char)
|
||||
'H' -> return accept {
|
||||
val arg2 = ttyEscArguments.pop()
|
||||
val arg1 = ttyEscArguments.pop()
|
||||
cursorXY(arg1, arg2)
|
||||
}
|
||||
'm' -> return accept {
|
||||
val arg2 = ttyEscArguments.pop()
|
||||
val arg1 = ttyEscArguments.pop()
|
||||
sgrTwoArg(arg1, arg2)
|
||||
}
|
||||
';' -> ttyEscState = TTY_ESC_STATE.SEP2
|
||||
else -> return reject()
|
||||
}
|
||||
}
|
||||
TTY_ESC_STATE.NUM3 -> {
|
||||
when (char.toChar()) {
|
||||
in '0'..'9' -> appendToExistingNumber(char)
|
||||
'm' -> return accept {
|
||||
val arg3 = ttyEscArguments.pop()
|
||||
val arg2 = ttyEscArguments.pop()
|
||||
val arg1 = ttyEscArguments.pop()
|
||||
sgrThreeArg(arg1, arg2, arg3)
|
||||
}
|
||||
else -> return reject()
|
||||
}
|
||||
}
|
||||
TTY_ESC_STATE.SEP1 -> {
|
||||
when (char.toChar()) {
|
||||
in '0'..'9' -> registerNewNumberArg(char, TTY_ESC_STATE.NUM2)
|
||||
'H' -> return accept {
|
||||
val arg1 = ttyEscArguments.pop()
|
||||
cursorXY(arg1, 0)
|
||||
}
|
||||
'm' -> return accept {
|
||||
val arg1 = ttyEscArguments.pop()
|
||||
sgrTwoArg(arg1, 0)
|
||||
}
|
||||
';' -> {
|
||||
ttyEscArguments.push(0)
|
||||
ttyEscState = TTY_ESC_STATE.SEP2
|
||||
}
|
||||
else -> return reject()
|
||||
}
|
||||
}
|
||||
TTY_ESC_STATE.SEP2 -> {
|
||||
when (char.toChar()) {
|
||||
'm' -> return accept {
|
||||
val arg2 = ttyEscArguments.pop()
|
||||
val arg1 = ttyEscArguments.pop()
|
||||
sgrThreeArg(arg1, arg2, 0)
|
||||
}
|
||||
in '0'..'9' -> registerNewNumberArg(char, TTY_ESC_STATE.NUM3)
|
||||
else -> return reject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
abstract fun resetTtyStatus()
|
||||
abstract fun cursorUp(arg: Int = 1)
|
||||
abstract fun cursorDown(arg: Int = 1)
|
||||
abstract fun cursorFwd(arg: Int = 1)
|
||||
abstract fun cursorBack(arg: Int = 1)
|
||||
abstract fun cursorNextLine(arg: Int = 1)
|
||||
abstract fun cursorPrevLine(arg: Int = 1)
|
||||
abstract fun cursorX(arg: Int = 1) // aka Cursor Horizintal Absolute
|
||||
abstract fun eraseInDisp(arg: Int = 0)
|
||||
abstract fun eraseInLine(arg: Int = 0)
|
||||
/** New lines are added at the bottom */
|
||||
abstract fun scrollUp(arg: Int = 1)
|
||||
/** New lines are added at the top */
|
||||
abstract fun scrollDown(arg: Int = 1)
|
||||
abstract fun sgrOneArg(arg: Int = 0)
|
||||
abstract fun sgrTwoArg(arg1: Int, arg2: Int)
|
||||
abstract fun sgrThreeArg(arg1: Int, arg2: Int, arg3: Int)
|
||||
/** The values are one-based
|
||||
* @param arg1 y-position (row)
|
||||
* @param arg2 x-position (column) */
|
||||
abstract fun cursorXY(arg1: Int, arg2: Int)
|
||||
abstract fun ringBell()
|
||||
abstract fun insertTab()
|
||||
abstract fun crlf()
|
||||
abstract fun backspace()
|
||||
abstract fun privateSeqH(arg: Int)
|
||||
abstract fun privateSeqL(arg: Int)
|
||||
|
||||
abstract fun getPrintStream(): OutputStream
|
||||
abstract fun getErrorStream(): OutputStream
|
||||
abstract fun getInputStream(): InputStream
|
||||
|
||||
private val CR = 0x0D.toByte()
|
||||
private val LF = 0x0A.toByte()
|
||||
private val TAB = 0x09.toByte()
|
||||
private val BS = 0x08.toByte()
|
||||
private val BEL = 0x07.toByte()
|
||||
private val ESC = 0x1B.toByte()
|
||||
|
||||
private enum class TTY_ESC_STATE {
|
||||
INITIAL, ESC, CSI, NUM1, SEP1, NUM2, SEP2, NUM3, PRIVATESEQ, PRIVATENUM
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Puts a key into a keyboard buffer
|
||||
*/
|
||||
abstract fun putKey(key: Int)
|
||||
|
||||
/**
|
||||
* Takes a key from a keyboard buffer
|
||||
*/
|
||||
abstract fun takeKey(): Int
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Note 1. State machine for Escape sequence
|
||||
|
||||
digraph G {
|
||||
|
||||
ESC -> Reset [label="c"]
|
||||
ESC -> CSI [label="["]
|
||||
|
||||
CSI -> numeral [label="0..9"]
|
||||
CSI -> CursorUp [label="A"]
|
||||
CSI -> CursorDown [label="B"]
|
||||
CSI -> CursorFwd [label="C"]
|
||||
CSI -> CursorBack [label="D"]
|
||||
CSI -> CursorNextLine [label="E"]
|
||||
CSI -> CursorPrevLine [label="F"]
|
||||
CSI -> CursorX [label="G"]
|
||||
CSI -> EraseInDisp [label="J"]
|
||||
CSI -> EraseInLine [label="K"]
|
||||
CSI -> ScrollUp [label="S"]
|
||||
CSI -> ScrollDown [label="T"]
|
||||
CSI -> SGR [label="m"]
|
||||
CSI -> separator1 [label="; (zero)"]
|
||||
|
||||
CSI -> privateseq [label="?"]
|
||||
|
||||
privateseq -> privatenum [label="0..9"]
|
||||
|
||||
privatenum -> privateSeqH [label=h]
|
||||
privatenum -> privateSeqL [label=l]
|
||||
|
||||
numeral -> numeral [label="0..9"]
|
||||
numeral -> CursorUp [label="A"]
|
||||
numeral -> CursorDown [label="B"]
|
||||
numeral -> CursorFwd [label="C"]
|
||||
numeral -> CursorBack [label="D"]
|
||||
numeral -> CursorNextLine [label="E"]
|
||||
numeral -> CursorPrevLine [label="F"]
|
||||
numeral -> CursorX [label="G"]
|
||||
numeral -> EraseInDisp [label="J"]
|
||||
numeral -> EraseInLine [label="K"]
|
||||
numeral -> ScrollUp [label="S"]
|
||||
numeral -> ScrollDown [label="T"]
|
||||
|
||||
numeral -> SGR [label="m"]
|
||||
|
||||
numeral -> separator1 [label=";"]
|
||||
|
||||
separator1 -> numeral2 [label="0..9"]
|
||||
separator1 -> separator2 [label="; (zero)"]
|
||||
separator1 -> CursorPos [label="H (zero)"]
|
||||
separator1 -> SGR2 [label="m (zero)"]
|
||||
|
||||
numeral2 -> numeral2 [label="0..9"]
|
||||
numeral2 -> CursorPos [label="H"]
|
||||
numeral2 -> SGR2 [label="m"]
|
||||
numeral2 -> separator2 [label="; (zero)"]
|
||||
|
||||
separator2 -> numeral3 [label="0..9"]
|
||||
numeral3 -> numeral3 [label="0..9"]
|
||||
|
||||
separator2 -> SGR3 [label="m (zero)"]
|
||||
numeral3 -> SGR3 [label="m"]
|
||||
|
||||
ESC [shape=Mdiamond]
|
||||
Reset -> end
|
||||
CursorUp -> end
|
||||
CursorDown -> end
|
||||
CursorFwd -> end
|
||||
CursorBack -> end
|
||||
CursorNextLine -> end
|
||||
CursorPrevLine -> end
|
||||
CursorX -> end
|
||||
EraseInDisp -> end
|
||||
EraseInLine -> end
|
||||
ScrollUp -> end
|
||||
ScrollDown -> end
|
||||
CursorPos -> end
|
||||
SGR -> end
|
||||
SGR2 -> end
|
||||
SGR3 -> end
|
||||
end [shape=Msquare]
|
||||
}
|
||||
|
||||
*/
|
||||
1762
tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt
Normal file
1762
tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt
Normal file
File diff suppressed because it is too large
Load Diff
344
tsvm_core/src/net/torvald/tsvm/peripheral/IOSpace.kt
Normal file
344
tsvm_core/src/net/torvald/tsvm/peripheral/IOSpace.kt
Normal file
@@ -0,0 +1,344 @@
|
||||
package net.torvald.tsvm.peripheral
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.Input
|
||||
import com.badlogic.gdx.InputProcessor
|
||||
import net.torvald.UnsafeHelper
|
||||
import net.torvald.tsvm.CircularArray
|
||||
import net.torvald.tsvm.VM
|
||||
import kotlin.experimental.and
|
||||
|
||||
class IOSpace(val vm: VM) : PeriBase, InputProcessor {
|
||||
|
||||
override val typestring = "io"
|
||||
|
||||
override fun getVM(): VM {
|
||||
return vm
|
||||
}
|
||||
|
||||
/** Absolute x-position of the computer GUI */
|
||||
var guiPosX = 0
|
||||
/** Absolute y-position of the computer GUI */
|
||||
var guiPosY = 0
|
||||
|
||||
/** Accepts a keycode */
|
||||
private val keyboardBuffer = CircularArray<Byte>(32, true)
|
||||
|
||||
internal val blockTransferRx = arrayOf(
|
||||
UnsafeHelper.allocate(4096),
|
||||
UnsafeHelper.allocate(4096),
|
||||
UnsafeHelper.allocate(4096),
|
||||
UnsafeHelper.allocate(4096)
|
||||
)
|
||||
internal val blockTransferTx = arrayOf(
|
||||
UnsafeHelper.allocate(4096),
|
||||
UnsafeHelper.allocate(4096),
|
||||
UnsafeHelper.allocate(4096),
|
||||
UnsafeHelper.allocate(4096)
|
||||
)
|
||||
/*private*/ val blockTransferPorts = Array(4) { BlockTransferPort(vm, it) }
|
||||
|
||||
private val peripheralFast = UnsafeHelper.allocate(1024)
|
||||
|
||||
private val keyEventBuffers = ByteArray(8)
|
||||
|
||||
init {
|
||||
//blockTransferPorts[1].attachDevice(TestFunctionGenerator())
|
||||
//blockTransferPorts[0].attachDevice(TestDiskDrive(vm, 0, File("assets")))
|
||||
//blockTransferPorts[0].attachDevice(TestDiskDrive(vm, 0, File("assets/disk0")))
|
||||
|
||||
// for testers: use EmulInstance
|
||||
|
||||
peripheralFast.fillWith(0)
|
||||
}
|
||||
|
||||
private fun composeBlockTransferStatus(portno: Int): Int {
|
||||
return blockTransferPorts[portno].isMaster.toInt().shl(5) or
|
||||
blockTransferPorts[portno].isSlave.toInt().shl(4) or
|
||||
blockTransferPorts[portno].getMode().toInt().shl(3) or
|
||||
blockTransferPorts[portno].busy.toInt().shl(2) or
|
||||
blockTransferPorts[portno].areYouReady().toInt().shl(1) or
|
||||
blockTransferPorts[portno].cableConnected().toInt()
|
||||
}
|
||||
|
||||
private fun setBlockTransferPortStatus(portno: Int, bits: Byte) {
|
||||
blockTransferPorts[portno].setMode(bits.and(0b0000_1000) != 0.toByte())
|
||||
blockTransferPorts[portno].ready = bits.and(0b0000_0010) != 0.toByte()
|
||||
if (bits.and(0b0000_0100) != 0.toByte()) {
|
||||
if (blockTransferPorts[portno].getMode()) {
|
||||
//println("[IOSpace] startSend()")
|
||||
blockTransferPorts[portno].startSend()
|
||||
}
|
||||
else {
|
||||
//println("[IOSpace] startRead()")
|
||||
blockTransferPorts[portno].startRead()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun peek(addr: Long): Byte? {
|
||||
return mmio_read(addr)
|
||||
}
|
||||
|
||||
override fun poke(addr: Long, byte: Byte) {
|
||||
mmio_write(addr, byte)
|
||||
}
|
||||
|
||||
override fun mmio_read(addr: Long): Byte? {
|
||||
val adi = addr.toInt()
|
||||
return when (addr) {
|
||||
in 0..31 -> keyboardBuffer[(addr.toInt())] ?: -1
|
||||
in 32..33 -> (mouseX.toInt() shr (adi - 32).times(8)).toByte()
|
||||
in 34..35 -> (mouseY.toInt() shr (adi - 34).times(8)).toByte()
|
||||
36L -> mouseDown.toInt().toByte()
|
||||
37L -> keyboardBuffer.removeTail() ?: -1
|
||||
38L -> keyboardInputRequested.toInt().toByte()
|
||||
39L -> rawInputFunctionLatched.toInt().toByte()
|
||||
in 40..47 -> keyEventBuffers[adi - 40]
|
||||
48L -> ((vm.resetDown.toInt() shl 7) or (vm.stopDown.toInt())).toByte()
|
||||
|
||||
in 64..67 -> vm.memsize.shr((adi - 64) * 8).toByte()
|
||||
68L -> (uptimeCounterLatched.toInt() or RTClatched.toInt().shl(1)).toByte()
|
||||
|
||||
in 72..79 -> systemUptime.ushr((adi - 72) * 8).and(255).toByte()
|
||||
in 80..87 -> rtc.ushr((adi - 80) * 8).and(255).toByte()
|
||||
|
||||
88L -> vm.romMapping.toByte()
|
||||
|
||||
in 1024..2047 -> peripheralFast[addr - 1024]
|
||||
|
||||
4076L -> blockTransferPorts[0].statusCode.toByte()
|
||||
4077L -> blockTransferPorts[1].statusCode.toByte()
|
||||
4078L -> blockTransferPorts[2].statusCode.toByte()
|
||||
4079L -> blockTransferPorts[3].statusCode.toByte()
|
||||
|
||||
4080L -> blockTransferPorts[0].getYourStatusCode().toByte()
|
||||
4081L -> blockTransferPorts[1].getYourStatusCode().toByte()
|
||||
4082L -> blockTransferPorts[2].getYourStatusCode().toByte()
|
||||
4083L -> blockTransferPorts[3].getYourStatusCode().toByte()
|
||||
|
||||
4084L -> (blockTransferPorts[0].yourBlockSize().toByte())
|
||||
4085L -> (blockTransferPorts[0].doYouHaveNext().toInt().shl(7) or blockTransferPorts[0].yourBlockSize().ushr(8).and(15)).toByte()
|
||||
4086L -> (blockTransferPorts[1].yourBlockSize().toByte())
|
||||
4087L -> (blockTransferPorts[1].doYouHaveNext().toInt().shl(7) or blockTransferPorts[1].yourBlockSize().ushr(8).and(15)).toByte()
|
||||
4088L -> (blockTransferPorts[2].yourBlockSize().toByte())
|
||||
4089L -> (blockTransferPorts[2].doYouHaveNext().toInt().shl(7) or blockTransferPorts[2].yourBlockSize().ushr(8).and(15)).toByte()
|
||||
4090L -> (blockTransferPorts[3].yourBlockSize().toByte())
|
||||
4091L -> (blockTransferPorts[3].doYouHaveNext().toInt().shl(7) or blockTransferPorts[3].yourBlockSize().ushr(8).and(15)).toByte()
|
||||
|
||||
in 4092..4095 -> composeBlockTransferStatus(adi - 4092).toByte()
|
||||
|
||||
in 4096..8191 -> blockTransferRx[0][addr - 4096]
|
||||
in 8192..12287 -> blockTransferRx[1][addr - 8192]
|
||||
in 12288..16383 -> blockTransferRx[2][addr - 12288]
|
||||
in 16384..20479 -> blockTransferRx[3][addr - 16384]
|
||||
|
||||
in 65536..131071 -> if (vm.romMapping == -1) 255.toByte() else vm.roms[vm.romMapping]?.get(adi - 65536)
|
||||
|
||||
in 131072..262143 -> vm.peripheralTable[1].peripheral?.mmio_read(addr - 131072)
|
||||
in 262144..393215 -> vm.peripheralTable[2].peripheral?.mmio_read(addr - 262144)
|
||||
in 393216..524287 -> vm.peripheralTable[3].peripheral?.mmio_read(addr - 393216)
|
||||
in 524288..655359 -> vm.peripheralTable[4].peripheral?.mmio_read(addr - 524288)
|
||||
in 655360..786431 -> vm.peripheralTable[5].peripheral?.mmio_read(addr - 655360)
|
||||
in 786432..917503 -> vm.peripheralTable[6].peripheral?.mmio_read(addr - 786432)
|
||||
in 917504..1048575 -> vm.peripheralTable[7].peripheral?.mmio_read(addr - 917504)
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun mmio_write(addr: Long, byte: Byte) {
|
||||
val adi = addr.toInt()
|
||||
val bi = byte.toInt().and(255)
|
||||
when (addr) {
|
||||
37L -> keyboardBuffer.appendHead(byte)
|
||||
38L -> {
|
||||
keyboardInputRequested = (byte.isNonZero())
|
||||
if (keyboardInputRequested) keyboardBuffer.clear()
|
||||
}
|
||||
39L -> rawInputFunctionLatched = (byte.isNonZero())
|
||||
in 40..47 -> keyEventBuffers[adi - 40] = byte
|
||||
68L -> {
|
||||
uptimeCounterLatched = byte.and(0b01).isNonZero()
|
||||
RTClatched = byte.and(0b10).isNonZero()
|
||||
}
|
||||
|
||||
88L -> vm.romMapping = bi
|
||||
|
||||
in 1024..2047 -> peripheralFast[addr - 1024] = byte
|
||||
|
||||
4076L -> blockTransferPorts[0].statusCode = bi
|
||||
4077L -> blockTransferPorts[1].statusCode = bi
|
||||
4078L -> blockTransferPorts[2].statusCode = bi
|
||||
4079L -> blockTransferPorts[3].statusCode = bi
|
||||
|
||||
4084L -> blockTransferPorts[0].blockSize = blockTransferPorts[0].blockSize.and(0xFF00) or byte.toInt().and(255)
|
||||
4085L -> {
|
||||
blockTransferPorts[0].hasNext = (byte < 0)
|
||||
blockTransferPorts[0].blockSize = blockTransferPorts[0].blockSize.and(0x00FF) or byte.toInt().and(15)
|
||||
}
|
||||
4086L -> blockTransferPorts[1].blockSize = blockTransferPorts[1].blockSize.and(0xFF00) or byte.toInt().and(255)
|
||||
4087L -> {
|
||||
blockTransferPorts[1].hasNext = (byte < 0)
|
||||
blockTransferPorts[1].blockSize = blockTransferPorts[1].blockSize.and(0x00FF) or byte.toInt().and(15)
|
||||
}
|
||||
4088L -> blockTransferPorts[2].blockSize = blockTransferPorts[2].blockSize.and(0xFF00) or byte.toInt().and(255)
|
||||
4089L -> {
|
||||
blockTransferPorts[2].hasNext = (byte < 0)
|
||||
blockTransferPorts[2].blockSize = blockTransferPorts[2].blockSize.and(0x00FF) or byte.toInt().and(15)
|
||||
}
|
||||
4090L -> blockTransferPorts[3].blockSize = blockTransferPorts[3].blockSize.and(0xFF00) or byte.toInt().and(255)
|
||||
4091L -> {
|
||||
blockTransferPorts[3].hasNext = (byte < 0)
|
||||
blockTransferPorts[3].blockSize = blockTransferPorts[3].blockSize.and(0x00FF) or byte.toInt().and(15)
|
||||
}
|
||||
|
||||
in 4092..4095 -> setBlockTransferPortStatus(adi - 4092, byte)
|
||||
|
||||
in 4096..8191 -> blockTransferTx[0][addr - 4096] = byte
|
||||
in 8192..12287 -> blockTransferTx[1][addr - 8192] = byte
|
||||
in 12288..16383 -> blockTransferTx[2][addr - 12288] = byte
|
||||
in 16384..20479 -> blockTransferTx[3][addr - 16384] = byte
|
||||
|
||||
in 131072..262143 -> vm.peripheralTable[1].peripheral?.mmio_write(addr - 131072, byte)
|
||||
in 262144..393215 -> vm.peripheralTable[2].peripheral?.mmio_write(addr - 262144, byte)
|
||||
in 393216..524287 -> vm.peripheralTable[3].peripheral?.mmio_write(addr - 393216, byte)
|
||||
in 524288..655359 -> vm.peripheralTable[4].peripheral?.mmio_write(addr - 524288, byte)
|
||||
in 655360..786431 -> vm.peripheralTable[5].peripheral?.mmio_write(addr - 655360, byte)
|
||||
in 786432..917503 -> vm.peripheralTable[6].peripheral?.mmio_write(addr - 786432, byte)
|
||||
in 917504..1048575 -> vm.peripheralTable[7].peripheral?.mmio_write(addr - 917504, byte)
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
blockTransferRx.forEach { it.destroy() }
|
||||
blockTransferTx.forEach { it.destroy() }
|
||||
peripheralFast.destroy()
|
||||
}
|
||||
|
||||
private var mouseX: Short = 0
|
||||
private var mouseY: Short = 0
|
||||
private var mouseDown = false
|
||||
private var systemUptime = 0L
|
||||
private var rtc = 0L
|
||||
|
||||
fun update(delta: Float) {
|
||||
if (rawInputFunctionLatched) {
|
||||
rawInputFunctionLatched = false
|
||||
|
||||
// store mouse info
|
||||
mouseX = (Gdx.input.x + guiPosX).toShort()
|
||||
mouseY = (Gdx.input.y + guiPosY).toShort()
|
||||
mouseDown = Gdx.input.isTouched
|
||||
|
||||
// strobe keys to fill the key read buffer
|
||||
var keysPushed = 0
|
||||
keyEventBuffers.fill(0)
|
||||
for (k in 1..254) {
|
||||
if (Gdx.input.isKeyPressed(k)) {
|
||||
keyEventBuffers[keysPushed] = k.toByte()
|
||||
keysPushed += 1
|
||||
}
|
||||
|
||||
if (keysPushed >= 8) break
|
||||
}
|
||||
}
|
||||
|
||||
if (uptimeCounterLatched) {
|
||||
uptimeCounterLatched = false
|
||||
systemUptime = vm.getUptime()
|
||||
}
|
||||
|
||||
if (RTClatched) {
|
||||
RTClatched = false
|
||||
rtc = vm.worldInterface.currentTimeInMills()
|
||||
}
|
||||
|
||||
// SIGTERM key combination: Ctrl+Shift+T+R
|
||||
vm.stopDown = Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) &&
|
||||
Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) &&
|
||||
Gdx.input.isKeyPressed(Input.Keys.T) &&
|
||||
Gdx.input.isKeyPressed(Input.Keys.R)
|
||||
if (vm.stopDown) println("[VM-${vm.id}] SIGTERM requested")
|
||||
|
||||
// RESET key combination: Ctrl+Shift+R+S
|
||||
vm.resetDown = Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) &&
|
||||
Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) &&
|
||||
Gdx.input.isKeyPressed(Input.Keys.R) &&
|
||||
Gdx.input.isKeyPressed(Input.Keys.S)
|
||||
if (vm.resetDown) println("[VM-${vm.id}] RESET requested")
|
||||
}
|
||||
|
||||
override fun touchUp(p0: Int, p1: Int, p2: Int, p3: Int): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun mouseMoved(p0: Int, p1: Int): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun keyTyped(p0: Char): Boolean {
|
||||
if (keyboardInputRequested && p0.toInt() > 0) {
|
||||
//println("[IO] key typed = ${p0.toInt()}")
|
||||
keyboardBuffer.appendHead(p0.toByte())
|
||||
return true
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun scrolled(p0: Float, p1: Float): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun keyUp(p0: Int): Boolean {
|
||||
//ttySpecialKeyLatched = false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun touchDragged(p0: Int, p1: Int, p2: Int): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
private var keyboardInputRequested = false
|
||||
private var uptimeCounterLatched = false
|
||||
private var RTClatched = false
|
||||
private var rawInputFunctionLatched = false
|
||||
private var specialKeys = hashMapOf(
|
||||
Input.Keys.HOME to 199.toByte(),
|
||||
Input.Keys.UP to 200.toByte(),
|
||||
Input.Keys.PAGE_UP to 201.toByte(),
|
||||
Input.Keys.LEFT to 203.toByte(),
|
||||
Input.Keys.RIGHT to 205.toByte(),
|
||||
Input.Keys.END to 207.toByte(),
|
||||
Input.Keys.DOWN to 208.toByte(),
|
||||
Input.Keys.PAGE_DOWN to 209.toByte(),
|
||||
Input.Keys.INSERT to 210.toByte(),
|
||||
Input.Keys.FORWARD_DEL to 211.toByte()
|
||||
)
|
||||
override fun keyDown(p0: Int): Boolean {
|
||||
if (keyboardInputRequested) {
|
||||
if (p0 in Input.Keys.A..Input.Keys.Z && (Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) || Gdx.input.isKeyPressed(Input.Keys.CONTROL_RIGHT))) {
|
||||
keyboardBuffer.appendHead((p0 - 28).toByte())
|
||||
}
|
||||
else {
|
||||
specialKeys[p0]?.let {
|
||||
//println("[IO] key special = ${it.toUInt()}")
|
||||
keyboardBuffer.appendHead(it)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun touchDown(p0: Int, p1: Int, p2: Int, p3: Int): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun Boolean.toInt() = if (this) 1 else 0
|
||||
private fun Byte.isNonZero() = this != 0.toByte()
|
||||
}
|
||||
25
tsvm_core/src/net/torvald/tsvm/peripheral/PeriBase.kt
Normal file
25
tsvm_core/src/net/torvald/tsvm/peripheral/PeriBase.kt
Normal file
@@ -0,0 +1,25 @@
|
||||
package net.torvald.tsvm.peripheral
|
||||
|
||||
import net.torvald.tsvm.VM
|
||||
|
||||
interface PeriBase {
|
||||
|
||||
/**
|
||||
* Addr is not an offset; they can be "wired" into any other "chip" in the card other than its RAM
|
||||
*/
|
||||
fun peek(addr: Long): Byte?
|
||||
|
||||
/**
|
||||
* Addr is not an offset; they can be "wired" into any other "chip" in the card other than its RAM
|
||||
*/
|
||||
fun poke(addr: Long, byte: Byte)
|
||||
|
||||
fun mmio_read(addr: Long): Byte?
|
||||
fun mmio_write(addr: Long, byte: Byte)
|
||||
|
||||
fun dispose()
|
||||
|
||||
fun getVM(): VM
|
||||
|
||||
val typestring: String
|
||||
}
|
||||
247
tsvm_core/src/net/torvald/tsvm/peripheral/TTY.kt
Normal file
247
tsvm_core/src/net/torvald/tsvm/peripheral/TTY.kt
Normal file
@@ -0,0 +1,247 @@
|
||||
package net.torvald.tsvm.peripheral
|
||||
|
||||
import com.badlogic.gdx.graphics.Texture
|
||||
import net.torvald.UnsafeHelper
|
||||
import net.torvald.tsvm.VM
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
class TTY(val vm: VM) : GlassTty(TEXT_ROWS, TEXT_COLS), PeriBase {
|
||||
|
||||
override val typestring = VM.PERITYPE_GPU_AND_TERM
|
||||
|
||||
companion object {
|
||||
const val TEXT_ROWS = 25
|
||||
const val TEXT_COLS = 80
|
||||
}
|
||||
|
||||
private val chrrom = Texture("./assets/tty.png")
|
||||
private val textBuffer = UnsafeHelper.allocate(TEXT_ROWS * TEXT_COLS * 2L)
|
||||
override var rawCursorPos = 0
|
||||
|
||||
private val TEXT_AREA_SIZE = TEXT_COLS * TEXT_ROWS
|
||||
private val memTextOffset = 0L
|
||||
private val memTextAttrOffset = TEXT_AREA_SIZE.toLong()
|
||||
|
||||
override var ttyFore = 0 // 0: normal, 1: intense, 2: dim
|
||||
override var ttyBack: Int
|
||||
get() = 0
|
||||
set(value) {}
|
||||
var ttyInv = false
|
||||
override var blinkCursor = true
|
||||
override var ttyRawMode = false
|
||||
|
||||
override fun getCursorPos() = rawCursorPos % TEXT_COLS to rawCursorPos / TEXT_COLS
|
||||
/**
|
||||
* Think of it as a real paper tty;
|
||||
* setCursorPos must "wrap" the cursor properly when x-value goes out of screen bound.
|
||||
* For y-value, only when y < 0, set y to zero and don't care about the y-value goes out of bound.
|
||||
*/
|
||||
override fun setCursorPos(x: Int, y: Int) {
|
||||
var newx = x
|
||||
var newy = y
|
||||
|
||||
if (newx >= TEXT_COLS) {
|
||||
newx = 0
|
||||
newy += 1
|
||||
}
|
||||
else if (newx < 0) {
|
||||
newx = 0
|
||||
}
|
||||
|
||||
if (newy < 0) {
|
||||
newy = 0 // DON'T SCROLL when cursor goes ABOVE the screen
|
||||
}
|
||||
|
||||
rawCursorPos = toTtyTextOffset(newx, newy)
|
||||
}
|
||||
private fun toTtyTextOffset(x: Int, y: Int) = y * TEXT_COLS + x
|
||||
|
||||
|
||||
override fun peek(addr: Long): Byte? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun poke(addr: Long, byte: Byte) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun mmio_read(addr: Long): Byte? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun mmio_write(addr: Long, byte: Byte) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getVM() = vm
|
||||
|
||||
override fun putChar(x: Int, y: Int, text: Byte, foreColour: Byte, backColour: Byte) {
|
||||
val textOff = toTtyTextOffset(x, y)
|
||||
textBuffer[memTextAttrOffset + textOff] = 0
|
||||
textBuffer[memTextOffset + textOff] = text
|
||||
}
|
||||
|
||||
override fun resetTtyStatus() {
|
||||
ttyFore = 0
|
||||
ttyInv = false
|
||||
}
|
||||
|
||||
override fun cursorUp(arg: Int) {
|
||||
val (x, y) = getCursorPos()
|
||||
setCursorPos(x, y - arg)
|
||||
}
|
||||
|
||||
override fun cursorDown(arg: Int) {
|
||||
val (x, y) = getCursorPos()
|
||||
val newy = y + arg
|
||||
setCursorPos(x, if (newy >= TEXT_ROWS) TEXT_ROWS - 1 else newy)
|
||||
}
|
||||
|
||||
override fun cursorFwd(arg: Int) {
|
||||
val (x, y) = getCursorPos()
|
||||
setCursorPos(x + arg, y)
|
||||
}
|
||||
|
||||
override fun cursorBack(arg: Int) {
|
||||
val (x, y) = getCursorPos()
|
||||
setCursorPos(x - arg, y)
|
||||
}
|
||||
|
||||
override fun cursorNextLine(arg: Int) {
|
||||
val (_, y) = getCursorPos()
|
||||
val newy = y + arg
|
||||
setCursorPos(0, if (newy >= TEXT_ROWS) TEXT_ROWS - 1 else newy)
|
||||
if (newy >= TEXT_ROWS) {
|
||||
scrollUp(newy - TEXT_ROWS + 1)
|
||||
}
|
||||
}
|
||||
|
||||
override fun cursorPrevLine(arg: Int) {
|
||||
val (_, y) = getCursorPos()
|
||||
setCursorPos(0, y - arg)
|
||||
}
|
||||
|
||||
override fun cursorX(arg: Int) {
|
||||
val (_, y) = getCursorPos()
|
||||
setCursorPos(arg, y)
|
||||
}
|
||||
|
||||
override fun eraseInDisp(arg: Int) {
|
||||
when (arg) {
|
||||
else -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
override fun eraseInLine(arg: Int) {
|
||||
when (arg) {
|
||||
else -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
override fun sgrOneArg(arg: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun sgrTwoArg(arg1: Int, arg2: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun sgrThreeArg(arg1: Int, arg2: Int, arg3: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun cursorXY(arg1: Int, arg2: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun ringBell() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun insertTab() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun crlf() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun backspace() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun privateSeqH(arg: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun privateSeqL(arg: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getPrintStream(): OutputStream {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getErrorStream(): OutputStream {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getInputStream(): InputStream {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun putKey(key: Int) {
|
||||
vm.poke(-39, key.toByte())
|
||||
}
|
||||
|
||||
/**
|
||||
* @return key code in 0..255 (TODO: JInput Keycode or ASCII-Code?)
|
||||
*/
|
||||
override fun takeKey(): Int {
|
||||
return vm.peek(-38)!!.toInt().and(255)
|
||||
}
|
||||
|
||||
/** New lines are added at the bottom */
|
||||
override fun scrollUp(arg: Int) {
|
||||
val displacement = arg.toLong() * TEXT_COLS
|
||||
UnsafeHelper.memcpy(
|
||||
textBuffer.ptr + memTextOffset + displacement,
|
||||
textBuffer.ptr + memTextOffset,
|
||||
TEXT_AREA_SIZE - displacement
|
||||
)
|
||||
UnsafeHelper.memcpy(
|
||||
textBuffer.ptr + memTextAttrOffset + displacement,
|
||||
textBuffer.ptr + memTextAttrOffset,
|
||||
TEXT_AREA_SIZE - displacement
|
||||
)
|
||||
for (i in 0 until displacement) {
|
||||
textBuffer[memTextOffset + TEXT_AREA_SIZE - displacement + i] = 0
|
||||
textBuffer[memTextAttrOffset + TEXT_AREA_SIZE - displacement + i] = ttyFore.toByte()
|
||||
}
|
||||
}
|
||||
|
||||
/** New lines are added at the top */
|
||||
override fun scrollDown(arg: Int) {
|
||||
val displacement = arg.toLong() * TEXT_COLS
|
||||
UnsafeHelper.memcpy(
|
||||
textBuffer.ptr + memTextOffset,
|
||||
textBuffer.ptr + memTextOffset + displacement,
|
||||
TEXT_AREA_SIZE - displacement
|
||||
)
|
||||
UnsafeHelper.memcpy(
|
||||
textBuffer.ptr + memTextAttrOffset,
|
||||
textBuffer.ptr + memTextAttrOffset + displacement,
|
||||
TEXT_AREA_SIZE - displacement
|
||||
)
|
||||
for (i in 0 until displacement) {
|
||||
textBuffer[memTextOffset + TEXT_AREA_SIZE + i] = 0
|
||||
textBuffer[memTextAttrOffset + TEXT_AREA_SIZE + i] = ttyFore.toByte()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun dispose() {
|
||||
chrrom.dispose()
|
||||
}
|
||||
}
|
||||
7
tsvm_core/src/net/torvald/tsvm/peripheral/TermSim.kt
Normal file
7
tsvm_core/src/net/torvald/tsvm/peripheral/TermSim.kt
Normal file
@@ -0,0 +1,7 @@
|
||||
package net.torvald.tsvm.peripheral
|
||||
|
||||
internal class TermSim {
|
||||
|
||||
|
||||
|
||||
}
|
||||
456
tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt
Normal file
456
tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt
Normal file
@@ -0,0 +1,456 @@
|
||||
package net.torvald.tsvm.peripheral
|
||||
|
||||
import net.torvald.tsvm.VM
|
||||
import net.torvald.tsvm.VMJSR223Delegate
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath: File? = null) : BlockTransferInterface(false, true) {
|
||||
|
||||
companion object {
|
||||
const val STATE_CODE_STANDBY = 0
|
||||
const val STATE_CODE_OPERATION_FAILED = 1
|
||||
|
||||
const val STATE_CODE_ILLEGAL_COMMAND = 128
|
||||
const val STATE_CODE_FILE_NOT_FOUND = 129
|
||||
const val STATE_CODE_FILE_ALREADY_OPENED = 130
|
||||
const val STATE_CODE_OPERATION_NOT_PERMITTED = 131
|
||||
const val STATE_CODE_READ_ONLY = 132
|
||||
const val STATE_CODE_NOT_A_FILE = 133
|
||||
const val STATE_CODE_NOT_A_DIRECTORY = 134
|
||||
const val STATE_CODE_NO_FILE_OPENED = 135
|
||||
const val STATE_CODE_SYSTEM_IO_ERROR = 192
|
||||
const val STATE_CODE_SYSTEM_SECURITY_ERROR = 193
|
||||
|
||||
|
||||
val errorMsgs = Array(256) { "" }
|
||||
|
||||
init {
|
||||
errorMsgs[STATE_CODE_STANDBY] = "READY"
|
||||
errorMsgs[STATE_CODE_OPERATION_FAILED] = "OPERATION FAILED"
|
||||
|
||||
errorMsgs[STATE_CODE_ILLEGAL_COMMAND] = "SYNTAX ERROR"
|
||||
errorMsgs[STATE_CODE_FILE_NOT_FOUND] = "FILE NOT FOUND"
|
||||
errorMsgs[STATE_CODE_FILE_ALREADY_OPENED] = "FILE ALREADY OPENED"
|
||||
errorMsgs[STATE_CODE_SYSTEM_IO_ERROR] = "IO ERROR ON SIMULATED DRIVE"
|
||||
errorMsgs[STATE_CODE_SYSTEM_SECURITY_ERROR] = "SECURITY ERROR ON SIMULATED DRIVE"
|
||||
errorMsgs[STATE_CODE_OPERATION_NOT_PERMITTED] = "OPERATION NOT PERMITTED"
|
||||
errorMsgs[STATE_CODE_NOT_A_FILE] = "NOT A FILE"
|
||||
errorMsgs[STATE_CODE_NOT_A_DIRECTORY] = "NOT A DIRECTORY"
|
||||
errorMsgs[STATE_CODE_NO_FILE_OPENED] = "NO FILE OPENED"
|
||||
}
|
||||
}
|
||||
|
||||
private val DBGPRN = true
|
||||
|
||||
private fun printdbg(msg: Any) {
|
||||
if (DBGPRN) println("[TestDiskDrive] $msg")
|
||||
}
|
||||
|
||||
fun composePositiveAns(vararg msg: String): ByteArray {
|
||||
val sb = ArrayList<Byte>()
|
||||
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
|
||||
private var fileOpenMode = -1 // 1: 'W", 2: 'A'
|
||||
private var file = File(rootPath.toURI())
|
||||
//private var readModeLength = -1 // always 4096
|
||||
private var writeMode = false
|
||||
private var writeModeLength = -1
|
||||
|
||||
private val messageComposeBuffer = ByteArrayOutputStream(BLOCK_SIZE) // always use this and don't alter blockSendBuffer please
|
||||
private var blockSendBuffer = ByteArray(1)
|
||||
private var blockSendCount = 0
|
||||
|
||||
|
||||
init {
|
||||
statusCode = STATE_CODE_STANDBY
|
||||
|
||||
if (!rootPath.exists()) {
|
||||
rootPath.mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun resetBuf() {
|
||||
blockSendCount = 0
|
||||
messageComposeBuffer.reset()
|
||||
}
|
||||
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
|
||||
|
||||
return (blockSendCount * BLOCK_SIZE < blockSendBuffer.size)
|
||||
}
|
||||
|
||||
/** Computer's attempt to startRead() will result in calling this very function.
|
||||
*
|
||||
* Disk drive must send prepared message (or file transfer packet) to the computer.
|
||||
*/
|
||||
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 lateinit var writeBuffer: ByteArray
|
||||
private var writeBufferUsage = 0
|
||||
|
||||
/** Computer's attempt to startSend() will result in calling this very function.
|
||||
* In such cases, `inputData` will be the message the computer sends.
|
||||
*
|
||||
* Disk drive must create desired side effects in accordance with the input message.
|
||||
*/
|
||||
override fun writeoutImpl(inputData: ByteArray) {
|
||||
if (writeMode) {
|
||||
//println("[DiskDrive] writeout with inputdata length of ${inputData.size}")
|
||||
//println("[DiskDriveMsg] ${inputData.toString(Charsets.UTF_8)}")
|
||||
|
||||
if (!fileOpen) throw InternalError("File is not open but the drive is in write mode")
|
||||
|
||||
System.arraycopy(inputData, 0, writeBuffer, writeBufferUsage, minOf(writeModeLength - writeBufferUsage, inputData.size, BLOCK_SIZE))
|
||||
writeBufferUsage += inputData.size
|
||||
|
||||
if (writeBufferUsage >= writeModeLength) {
|
||||
writeMode = false
|
||||
// commit to the disk
|
||||
file.writeBytes(writeBuffer)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val inputString = inputData.trimNull().toString(VM.CHARSET)
|
||||
|
||||
if (inputString.startsWith("DEVRST\u0017")) {
|
||||
printdbg("Device Reset")
|
||||
//readModeLength = -1
|
||||
fileOpen = false
|
||||
fileOpenMode = -1
|
||||
file = File(rootPath.toURI())
|
||||
blockSendCount = 0
|
||||
statusCode = STATE_CODE_STANDBY
|
||||
writeMode = false
|
||||
writeModeLength = -1
|
||||
}
|
||||
else if (inputString.startsWith("DEVSTU\u0017"))
|
||||
recipient?.writeout(composePositiveAns("${statusCode.toChar()}", errorMsgs[statusCode]))
|
||||
else if (inputString.startsWith("DEVTYP\u0017"))
|
||||
recipient?.writeout(composePositiveAns("STOR"))
|
||||
else if (inputString.startsWith("DEVNAM\u0017"))
|
||||
recipient?.writeout(composePositiveAns("Testtec Virtual Disk Drive"))
|
||||
else if (inputString.startsWith("OPENR\"") || inputString.startsWith("OPENW\"") || inputString.startsWith("OPENA\"")) {
|
||||
if (fileOpen) {
|
||||
|
||||
statusCode = STATE_CODE_FILE_ALREADY_OPENED
|
||||
return
|
||||
}
|
||||
|
||||
printdbg("msg: $inputString, lastIndex: ${inputString.lastIndex}")
|
||||
|
||||
val openMode = inputString[4]
|
||||
printdbg("open mode: $openMode")
|
||||
// split inputstring into path and optional drive-number
|
||||
|
||||
// get position of latest delimeter (comma)
|
||||
var commaIndex = inputString.lastIndex
|
||||
while (commaIndex > 6) {
|
||||
if (inputString[commaIndex] == ',') break; commaIndex -= 1
|
||||
}
|
||||
// sanity check if path is actually enclosed with double-quote
|
||||
if (commaIndex != 6 && inputString[commaIndex - 1] != '"') {
|
||||
statusCode = STATE_CODE_ILLEGAL_COMMAND
|
||||
return
|
||||
}
|
||||
val pathStr = inputString.substring(6, if (commaIndex == 6) inputString.lastIndex else commaIndex - 1)
|
||||
val driveNum =
|
||||
if (commaIndex == 6) null else inputString.substring(commaIndex + 1, inputString.length).toInt()
|
||||
val filePath = filterSuperRoot(sanitisePath(pathStr))
|
||||
|
||||
// TODO driveNum is for disk drives that may have two or more slots built; for testing purposes we'll ignore it
|
||||
|
||||
file = File(rootPath, filePath)
|
||||
printdbg("file path: ${file.canonicalPath}, drive num: $driveNum")
|
||||
|
||||
if (openMode == 'R' && !file.exists()) {
|
||||
printdbg("! file not found")
|
||||
statusCode = STATE_CODE_FILE_NOT_FOUND
|
||||
return
|
||||
}
|
||||
|
||||
statusCode = STATE_CODE_STANDBY
|
||||
fileOpen = true
|
||||
fileOpenMode = when (openMode) {
|
||||
'W' -> 1
|
||||
'A' -> 2
|
||||
else -> -1
|
||||
}
|
||||
blockSendCount = 0
|
||||
}
|
||||
else if (inputString.startsWith("LISTFILES")) {
|
||||
// TODO temporary behaviour to ignore any arguments
|
||||
resetBuf()
|
||||
if (!fileOpen) {
|
||||
statusCode = STATE_CODE_NO_FILE_OPENED
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (file.isDirectory) {
|
||||
file.listFiles()!!.forEachIndexed { index, lsfile ->
|
||||
if (index != 0) messageComposeBuffer.write(0x1E)
|
||||
messageComposeBuffer.write(if (lsfile.isDirectory) 0x11 else 0x12)
|
||||
messageComposeBuffer.write(lsfile.name.toByteArray(VM.CHARSET))
|
||||
}
|
||||
|
||||
statusCode = STATE_CODE_STANDBY
|
||||
}
|
||||
else {
|
||||
statusCode = STATE_CODE_NOT_A_DIRECTORY
|
||||
return
|
||||
}
|
||||
}
|
||||
catch (e: SecurityException) {
|
||||
statusCode = STATE_CODE_SYSTEM_SECURITY_ERROR
|
||||
return
|
||||
}
|
||||
catch (e1: IOException) {
|
||||
statusCode = STATE_CODE_SYSTEM_IO_ERROR
|
||||
return
|
||||
}
|
||||
}
|
||||
else if (inputString.startsWith("LIST")) {
|
||||
// TODO temporary behaviour to ignore any arguments
|
||||
resetBuf()
|
||||
if (!fileOpen) {
|
||||
statusCode = STATE_CODE_NO_FILE_OPENED
|
||||
return
|
||||
}
|
||||
|
||||
messageComposeBuffer.write(getReadableLs().toByteArray(VM.CHARSET))
|
||||
statusCode = STATE_CODE_STANDBY
|
||||
}
|
||||
else if (inputString.startsWith("CLOSE")) {
|
||||
fileOpen = false
|
||||
fileOpenMode = -1
|
||||
statusCode = STATE_CODE_STANDBY
|
||||
}
|
||||
else if (inputString.startsWith("READ")) {
|
||||
//readModeLength = inputString.substring(4 until inputString.length).toInt()
|
||||
|
||||
resetBuf()
|
||||
if (file.isFile) {
|
||||
try {
|
||||
messageComposeBuffer.write(file.readBytes())
|
||||
statusCode = STATE_CODE_STANDBY
|
||||
}
|
||||
catch (e: IOException) {
|
||||
statusCode = STATE_CODE_SYSTEM_IO_ERROR
|
||||
}
|
||||
}
|
||||
else {
|
||||
statusCode = STATE_CODE_OPERATION_NOT_PERMITTED
|
||||
return
|
||||
}
|
||||
}
|
||||
else if (inputString.startsWith("LOADBOOT")) {
|
||||
var commaIndex = 0
|
||||
while (commaIndex < inputString.length) {
|
||||
if (inputString[commaIndex] == ',') break
|
||||
commaIndex += 1
|
||||
}
|
||||
val driveNum = if (commaIndex >= inputString.length) null else commaIndex
|
||||
|
||||
// TODO driveNum is for disk drives that may have two or more slots built; for testing purposes we'll ignore it
|
||||
|
||||
val bootFile = File(rootPath, "!BOOTSEC")
|
||||
|
||||
if (!bootFile.exists()) {
|
||||
statusCode = STATE_CODE_FILE_NOT_FOUND
|
||||
return
|
||||
}
|
||||
val fis = FileInputStream(bootFile)
|
||||
try {
|
||||
val retMsg = ByteArray(BLOCK_SIZE)
|
||||
fis.read(retMsg)
|
||||
recipient?.writeout(retMsg)
|
||||
statusCode = STATE_CODE_STANDBY
|
||||
}
|
||||
catch (e: IOException) {
|
||||
statusCode = STATE_CODE_SYSTEM_IO_ERROR
|
||||
return
|
||||
}
|
||||
finally {
|
||||
fis.close()
|
||||
}
|
||||
}
|
||||
else if (inputString.startsWith("MKDIR")) {
|
||||
if (!fileOpen) {
|
||||
statusCode = STATE_CODE_NO_FILE_OPENED
|
||||
return
|
||||
}
|
||||
if (fileOpenMode < 1) {
|
||||
statusCode = STATE_CODE_OPERATION_NOT_PERMITTED
|
||||
return
|
||||
}
|
||||
try {
|
||||
val status = file.mkdir()
|
||||
statusCode = if (status) 0 else 1
|
||||
}
|
||||
catch (e: SecurityException) {
|
||||
statusCode = STATE_CODE_SYSTEM_SECURITY_ERROR
|
||||
}
|
||||
}
|
||||
else if (inputString.startsWith("MKFILE")) {
|
||||
if (!fileOpen) {
|
||||
statusCode = STATE_CODE_NO_FILE_OPENED
|
||||
return
|
||||
}
|
||||
if (fileOpenMode < 1) {
|
||||
statusCode = STATE_CODE_OPERATION_NOT_PERMITTED
|
||||
return
|
||||
}
|
||||
try {
|
||||
val f1 = file.createNewFile()
|
||||
statusCode = if (f1) STATE_CODE_STANDBY else STATE_CODE_OPERATION_FAILED
|
||||
return
|
||||
}
|
||||
catch (e: IOException) {
|
||||
statusCode = STATE_CODE_SYSTEM_IO_ERROR
|
||||
}
|
||||
catch (e1: SecurityException) {
|
||||
statusCode = STATE_CODE_SYSTEM_SECURITY_ERROR
|
||||
}
|
||||
}
|
||||
else if (inputString.startsWith("TOUCH")) {
|
||||
if (!fileOpen) {
|
||||
statusCode = STATE_CODE_NO_FILE_OPENED
|
||||
return
|
||||
}
|
||||
if (fileOpenMode < 1) {
|
||||
statusCode = STATE_CODE_OPERATION_NOT_PERMITTED
|
||||
return
|
||||
}
|
||||
try {
|
||||
val f1 = file.setLastModified(vm.worldInterface.currentTimeInMills())
|
||||
statusCode = if (f1) STATE_CODE_STANDBY else STATE_CODE_OPERATION_FAILED
|
||||
return
|
||||
}
|
||||
catch (e: IOException) {
|
||||
statusCode = STATE_CODE_SYSTEM_IO_ERROR
|
||||
}
|
||||
catch (e1: SecurityException) {
|
||||
statusCode = STATE_CODE_SYSTEM_SECURITY_ERROR
|
||||
}
|
||||
}
|
||||
else if (inputString.startsWith("WRITE")) {
|
||||
if (!fileOpen) {
|
||||
statusCode = STATE_CODE_NO_FILE_OPENED
|
||||
return
|
||||
}
|
||||
if (fileOpenMode < 0) {
|
||||
statusCode = STATE_CODE_OPERATION_NOT_PERMITTED
|
||||
return
|
||||
}
|
||||
writeMode = true
|
||||
writeModeLength = inputString.substring(5, inputString.length).toInt()
|
||||
writeBuffer = ByteArray(writeModeLength)
|
||||
writeBufferUsage = 0
|
||||
statusCode = STATE_CODE_STANDBY
|
||||
}
|
||||
else
|
||||
statusCode = STATE_CODE_ILLEGAL_COMMAND
|
||||
}
|
||||
}
|
||||
|
||||
val diskID: UUID = UUID(0, 0)
|
||||
|
||||
private fun getReadableLs(): String {
|
||||
val sb = StringBuilder()
|
||||
val isRoot = (file.absolutePath == rootPath.absolutePath)
|
||||
|
||||
if (file.isFile) sb.append(file.name)
|
||||
else {
|
||||
sb.append("Current directory: ")
|
||||
sb.append(if (isRoot) "(root)" else file.path)
|
||||
sb.append('\n')
|
||||
|
||||
sb.append(".\n")
|
||||
if (isRoot) sb.append("..\n")
|
||||
// actual entries
|
||||
file.listFiles()!!.forEach {
|
||||
var filenameLen = it.name.length
|
||||
|
||||
sb.append(it.name)
|
||||
|
||||
if (it.isDirectory) {
|
||||
sb.append("/")
|
||||
filenameLen += 1
|
||||
}
|
||||
|
||||
sb.append(" ".repeat(40 - filenameLen))
|
||||
|
||||
if (it.isFile) {
|
||||
sb.append("${it.length()} B")
|
||||
}
|
||||
|
||||
sb.append('\n')
|
||||
}
|
||||
}
|
||||
|
||||
return if (sb.last() == '\n') sb.substring(0, sb.lastIndex) else sb.toString()
|
||||
}
|
||||
|
||||
private fun sanitisePath(s: String) = s.replace('\\','/').replace(Regex("""\?<>:\*\|"""),"-")
|
||||
|
||||
// applies a "cap" if the path attemps to access parent directory of the root
|
||||
private fun filterSuperRoot(path: String): String {
|
||||
if (path.isEmpty()) return path
|
||||
|
||||
var parentCount = 0
|
||||
val paths = path.split('/')
|
||||
val newPaths = ArrayList<String>()
|
||||
paths.forEach {
|
||||
if (it.isBlank() || it.isEmpty()) {
|
||||
/*do nothing*/
|
||||
}
|
||||
else if (it == "..") {
|
||||
parentCount -= -1
|
||||
}
|
||||
else if (it != ".") {
|
||||
parentCount += 1
|
||||
}
|
||||
|
||||
if (parentCount < -1) parentCount = -1
|
||||
|
||||
if (parentCount >= 0) {
|
||||
newPaths.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
return newPaths.joinToString("/")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package net.torvald.tsvm.peripheral
|
||||
|
||||
import net.torvald.tsvm.VM
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.ArrayList
|
||||
|
||||
class TestFunctionGenerator : BlockTransferInterface(true, false) {
|
||||
|
||||
val filecontent_lorem = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi ipsum magna, ultrices eu leo eu, consequat eleifend arcu. Nam tempor nunc aliquam mi cursus mollis. Aenean dictum iaculis dolor eget porttitor. Fusce vulputate dui id mauris ultricies, non aliquet nulla pulvinar. Integer consectetur nulla at cursus cursus. Nullam enim nisl, elementum a fermentum sed, suscipit id sapien. Duis eget enim lacinia, aliquam sapien ac, commodo risus. Morbi at enim sem. Aenean sollicitudin purus et sem porttitor, convallis ultricies nulla posuere. Suspendisse euismod sagittis vestibulum. Mauris lorem nisl, placerat et finibus non, cursus non ex. Interdum et malesuada fames ac ante ipsum primis in faucibus. Suspendisse finibus non dui vel tempor. Nam rhoncus ligula et massa sagittis fringilla. Cras convallis pellentesque nulla in rutrum.
|
||||
|
||||
Quisque ac orci sodales, semper neque eu, consequat lacus. Nulla suscipit orci felis, id tempor quam ultrices quis. Integer eu vulputate risus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Maecenas posuere sem sed erat tristique laoreet. Sed in ante est. Fusce nec est ut nunc aliquam condimentum viverra non ex. Pellentesque nisi ante, efficitur id neque sit amet, convallis tincidunt sapien. Nunc condimentum rutrum nisi, eu lobortis libero tempor non. Morbi euismod venenatis tincidunt. Nulla facilisi. Ut interdum nec nisi pharetra pretium.
|
||||
|
||||
Sed condimentum semper erat convallis vulputate. Donec rhoncus sodales faucibus. Morbi pulvinar elit quis lectus accumsan, a sagittis turpis scelerisque. Praesent at interdum quam. In hac habitasse platea dictumst. Vestibulum vulputate sem id massa maximus, quis malesuada nisl vestibulum. Nam fermentum feugiat tortor non imperdiet. Cras elementum ipsum at magna consectetur pellentesque. Etiam gravida mi sed magna venenatis pulvinar. Aenean scelerisque justo eu volutpat mattis.
|
||||
|
||||
Pellentesque faucibus tempus nibh, nec ultricies tortor aliquam at. Vestibulum nec imperdiet nulla. Nulla imperdiet neque vel ultrices molestie. Sed elementum sed quam id hendrerit. Ut sit amet scelerisque purus, eu porttitor enim. Curabitur luctus a lectus vitae commodo. Aenean sollicitudin metus non consequat molestie. Nulla ut venenatis lacus. Phasellus cursus erat et lorem sagittis elementum.
|
||||
|
||||
Nam in aliquet velit, vitae aliquam sapien. Phasellus imperdiet nulla augue, fermentum malesuada dolor hendrerit nec. Phasellus finibus dictum risus, tincidunt tincidunt turpis egestas ut. Pellentesque dapibus ipsum orci, vel volutpat justo sollicitudin a. Curabitur ut rhoncus ex. Maecenas ac dui vitae mauris iaculis sollicitudin. Etiam ac augue vitae elit consectetur condimentum et non turpis. Phasellus nunc leo, ultricies eu massa ut, elementum auctor odio. Duis interdum in est in suscipit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Suspendisse suscipit vel tortor id lacinia. Nunc maximus turpis maximus arcu viverra, eu facilisis felis tincidunt. Curabitur id lectus libero.
|
||||
|
||||
Nunc mollis nibh vitae sapien consequat, ut vestibulum sem pharetra. Aliquam iaculis, felis ut auctor porta, ipsum diam laoreet ex, sed egestas lacus est at neque. Aenean venenatis blandit arcu at porta. Nunc sed est magna. Duis pulvinar, nulla eu tristique mattis, dui diam malesuada sem, ac condimentum turpis nunc iaculis urna. Nam et ligula aliquet, fermentum lectus nec, consectetur ipsum. Proin convallis, mi id consectetur lobortis, urna nulla pellentesque odio, a finibus tortor nisl nec tortor. Suspendisse blandit nisl in magna hendrerit tristique. Cras sit amet metus et lacus rutrum tempus. In sapien elit, facilisis quis tristique a, vestibulum a massa. Donec ligula diam, posuere ac velit eget, lobortis tincidunt ante. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam lectus massa, egestas eu urna id, tempor pulvinar odio. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam in suscipit mauris, quis faucibus dui. Cras tincidunt turpe es.""".toByteArray(Charsets.US_ASCII)
|
||||
|
||||
val fileContent_multiblocks = """Zeroth Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
256th Byte 00000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
512nd Byte 00000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
768th Byte 00000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
1024th byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
1280th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
1536th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
1792nd Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
2048th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
2304th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
2560th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
2816th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
3072nd Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
3328th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
3584th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
3840th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
4096th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
4352nd Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
4608th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
4864th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
5120th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
5376th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
5632nd Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
5880th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
6144th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
6400th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
6656th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
6912nd Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
7168th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
7424th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
7680th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
7936th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
8192nd Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
8448th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
8704th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
8960th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
9216th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
9472nd Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
9728th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
9984th Byte 0000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
10240th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
10496th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
10752nd Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
11008th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
11264th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
11520th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
11776th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
12032nd Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
12288th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
12544th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
12800th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
13056th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
13312nd Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
13568th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
13824th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
14080th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
14336th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
14592nd Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
14848th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
15104th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
15360th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
15616th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
15872nd Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
16128th Byte 000111111111111111122222222222222223333333333333333444444444444444455555555555555556666666666666666777777777777777788888888888888889999999999999999AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFF
|
||||
16384th Byte and some trailing bytes too!""".toByteArray(Charsets.US_ASCII)
|
||||
|
||||
private var fileOpen = false
|
||||
|
||||
private var blockSendBuffer = ByteArray(1)
|
||||
private var blockSendCount = 0
|
||||
private var writeMode = false
|
||||
private var writeModeLength = -1
|
||||
|
||||
private val writeBuffer = ByteArrayOutputStream()
|
||||
|
||||
fun composeSerialAns(vararg msg: String): ByteArray {
|
||||
val sb = ArrayList<Byte>()
|
||||
sb.addAll(msg[0].toByteArray().toTypedArray())
|
||||
for (k in 1 until msg.lastIndex) {
|
||||
sb.add(0x1F)
|
||||
sb.addAll(msg[k].toByteArray().toTypedArray())
|
||||
}
|
||||
sb.add(0x17)
|
||||
return sb.toByteArray()
|
||||
}
|
||||
|
||||
override fun startSendImpl(recipient: BlockTransferInterface): Int {
|
||||
if (blockSendCount == 0) {
|
||||
//blockSendBuffer = messageComposeBuffer.toByteArray()
|
||||
blockSendBuffer = fileContent_multiblocks
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
|
||||
|
||||
return (blockSendCount * BLOCK_SIZE < blockSendBuffer.size)
|
||||
}
|
||||
override fun writeoutImpl(inputData: ByteArray) {
|
||||
if (writeMode) {
|
||||
inputData.forEach {
|
||||
if (writeModeLength > 0) {
|
||||
writeBuffer.write(it.toInt())
|
||||
writeModeLength -= 1
|
||||
}
|
||||
}
|
||||
|
||||
if (writeModeLength <= 0) {
|
||||
writeMode = false
|
||||
|
||||
// test printout
|
||||
val bufout = writeBuffer.toByteArray()
|
||||
println("[TestFunctionGenerator] written bytes: ${bufout.size}")
|
||||
println("[TestFunctionGenerator] written data: ${bufout.toString(VM.CHARSET)}")
|
||||
|
||||
writeBuffer.reset()
|
||||
}
|
||||
}
|
||||
else {
|
||||
val inputString = inputData.trimNull().toString(VM.CHARSET)
|
||||
|
||||
if (inputString.startsWith("DEVRST\u0017")) {
|
||||
fileOpen = false
|
||||
blockSendCount = 0
|
||||
} else if (inputString.startsWith("DEVTYP\u0017"))
|
||||
recipient?.writeout(composeSerialAns("STOR"))
|
||||
else if (inputString.startsWith("DEVNAM\u0017"))
|
||||
recipient?.writeout(composeSerialAns("Testtec Signal Generator"))
|
||||
else if (inputString.startsWith("OPENR\""))
|
||||
fileOpen = true
|
||||
else if (inputString.startsWith("CLOSE"))
|
||||
fileOpen = false
|
||||
//else if (inputString.startsWith("READ"))
|
||||
// readModeLength = inputString.substring(4 until inputString.length).toInt()
|
||||
else if (inputString.startsWith("LIST"))
|
||||
recipient?.writeout("\"LOREM.TXT\" TXT\nTotal 1 files on the disk".toByteArray())
|
||||
else if (inputString.startsWith("WRITE")) {
|
||||
writeMode = true
|
||||
writeModeLength = inputString.substring(5, inputString.length).toInt()
|
||||
statusCode = 0
|
||||
}
|
||||
else
|
||||
statusCode = 128
|
||||
|
||||
blockSendCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun setMode(sendmode: Boolean) {
|
||||
}
|
||||
|
||||
override fun getMode(): Boolean = true
|
||||
}
|
||||
93
tsvm_core/src/net/torvald/tsvm/peripheral/TexticsAdapter.kt
Normal file
93
tsvm_core/src/net/torvald/tsvm/peripheral/TexticsAdapter.kt
Normal file
@@ -0,0 +1,93 @@
|
||||
package net.torvald.tsvm.peripheral
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.graphics.GL20
|
||||
import com.badlogic.gdx.graphics.Texture
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import net.torvald.tsvm.VM
|
||||
import net.torvald.tsvm.kB
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
open class TexticsAdapterBase(vm: VM, config: AdapterConfig) : GraphicsAdapter(vm, config) {
|
||||
|
||||
private val crtGradTex = Texture("./assets/crt_grad.png")
|
||||
|
||||
companion object {
|
||||
val crtColor = hashMapOf(
|
||||
"white" to Color(0xe4eaffff.toInt()),
|
||||
"amber" to Color(0xffd600ff.toInt()),
|
||||
"green" to Color(0x4aff00ff)
|
||||
)
|
||||
}
|
||||
|
||||
override fun peek(addr: Long): Byte? {
|
||||
return when (addr) {
|
||||
in 0 until 250972 -> (-1).toByte()
|
||||
else -> super.peek(addr)
|
||||
}
|
||||
}
|
||||
|
||||
override fun poke(addr: Long, byte: Byte) {
|
||||
when (addr) {
|
||||
in 0 until 250972 -> { /*do nothing*/ }
|
||||
else -> super.poke(addr, byte)
|
||||
}
|
||||
}
|
||||
|
||||
private val TEX_HEIGHT = WIDTH * Math.sqrt(HEIGHT.toDouble() / WIDTH).toFloat()
|
||||
private val ALIGN = (HEIGHT - TEX_HEIGHT).absoluteValue / 2f
|
||||
private val phosphorCol = crtColor[theme.substring(4)] ?: crtColor["white"]
|
||||
|
||||
override fun render(delta: Float, batch: SpriteBatch, xoff: Float, yoff: Float) {
|
||||
|
||||
super.render(delta, batch, xoff, yoff)
|
||||
|
||||
batch.inUse {
|
||||
batch.enableBlending()
|
||||
|
||||
// phosphor
|
||||
batch.setBlendFunction(GL20.GL_DST_COLOR, GL20.GL_ONE_MINUS_SRC_ALPHA)
|
||||
batch.color = phosphorCol
|
||||
batch.draw(faketex, xoff, HEIGHT + yoff, WIDTH.toFloat(), -HEIGHT.toFloat())
|
||||
|
||||
// CRT glass
|
||||
batch.setBlendFunction(GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_COLOR)
|
||||
batch.color = Color.WHITE
|
||||
batch.draw(crtGradTex, xoff, HEIGHT + ALIGN + yoff, WIDTH.toFloat(), -TEX_HEIGHT)
|
||||
//batch.draw(crtGradTex, xoff, HEIGHT + yoff, WIDTH.toFloat(), -HEIGHT.toFloat())
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
crtGradTex.dispose()
|
||||
super.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
class Term(vm: VM) : TexticsAdapterBase(vm, AdapterConfig(
|
||||
"crt_white",
|
||||
720,
|
||||
480,
|
||||
80,
|
||||
32,
|
||||
254,
|
||||
0,
|
||||
256.kB(),
|
||||
"./hp2640.png",
|
||||
0.32f,
|
||||
GraphicsAdapter.TEXT_TILING_SHADER_MONOCHROME
|
||||
))
|
||||
|
||||
class WpTerm(vm: VM) : TexticsAdapterBase(vm, AdapterConfig(
|
||||
"crt_amber",
|
||||
810,
|
||||
360,
|
||||
90,
|
||||
20,
|
||||
254,
|
||||
0,
|
||||
256.kB(),
|
||||
"./wpfont.png",
|
||||
0.32f,
|
||||
GraphicsAdapter.TEXT_TILING_SHADER_MONOCHROME
|
||||
))
|
||||
38
tsvm_core/src/net/torvald/tsvm/peripheral/VMProgramRom.kt
Normal file
38
tsvm_core/src/net/torvald/tsvm/peripheral/VMProgramRom.kt
Normal file
@@ -0,0 +1,38 @@
|
||||
package net.torvald.tsvm.peripheral
|
||||
|
||||
import net.torvald.tsvm.CompressorDelegate
|
||||
import net.torvald.tsvm.CompressorDelegate.GZIP_HEADER
|
||||
import net.torvald.tsvm.VM
|
||||
import java.io.File
|
||||
|
||||
open class VMProgramRom(path: String) {
|
||||
private val contents: ByteArray
|
||||
|
||||
init {
|
||||
val bytes = File(path).readBytes()
|
||||
contents = bytes.sliceArray(0 until minOf(65536, bytes.size))
|
||||
}
|
||||
|
||||
fun readAll(): String {
|
||||
// check if bios is compressed in gzip
|
||||
return if (contents.startsWith(GZIP_HEADER))
|
||||
CompressorDelegate.decomp(contents).toString(VM.CHARSET)
|
||||
else
|
||||
contents.toString(VM.CHARSET)
|
||||
}
|
||||
|
||||
fun get(addr: Int): Byte = contents[addr]
|
||||
}
|
||||
|
||||
object GenericBios : VMProgramRom("./assets/bios/bios1.bin")
|
||||
object OEMBios : VMProgramRom("./assets/bios/TBMBIOS.js")
|
||||
object QuickBios : VMProgramRom("./assets/bios/quick.js")
|
||||
object BasicBios : VMProgramRom("./assets/bios/basicbios.js")
|
||||
object TandemBios : VMProgramRom("./assets/bios/tandemport.js")
|
||||
object TsvmBios : VMProgramRom("./assets/bios/tsvmbios.js")
|
||||
object BasicRom : VMProgramRom("./assets/bios/basic.bin")
|
||||
object TBASRelBios : VMProgramRom("./assets/bios/tbasdist.js")
|
||||
object WPBios : VMProgramRom("./assets/bios/wp.js")
|
||||
|
||||
object PipBios : VMProgramRom("./assets/bios/pipboot.rom")
|
||||
object PipROM : VMProgramRom("./assets/bios/pipcode.bas")
|
||||
10
tsvm_core/src/net/torvald/tsvm/vdc/V2kParserTest.kt
Normal file
10
tsvm_core/src/net/torvald/tsvm/vdc/V2kParserTest.kt
Normal file
@@ -0,0 +1,10 @@
|
||||
package net.torvald.tsvm.vdc
|
||||
|
||||
import net.torvald.tsvm.VM
|
||||
import net.torvald.tsvm.peripheral.GraphicsAdapter
|
||||
|
||||
fun main() {
|
||||
val vdc = Videotron2K(null)
|
||||
|
||||
vdc.eval(Videotron2K.screenfiller)
|
||||
}
|
||||
114
tsvm_core/src/net/torvald/tsvm/vdc/V2kRunTest.kt
Normal file
114
tsvm_core/src/net/torvald/tsvm/vdc/V2kRunTest.kt
Normal file
@@ -0,0 +1,114 @@
|
||||
package net.torvald.tsvm.vdc
|
||||
|
||||
import com.badlogic.gdx.ApplicationAdapter
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application
|
||||
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration
|
||||
import com.badlogic.gdx.graphics.OrthographicCamera
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||
import com.badlogic.gdx.graphics.glutils.ShaderProgram
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import net.torvald.tsvm.*
|
||||
import net.torvald.tsvm.peripheral.GraphicsAdapter
|
||||
|
||||
class V2kRunTest : ApplicationAdapter() {
|
||||
|
||||
val vm = VM(64.kB(), TheRealWorld(), arrayOf())
|
||||
lateinit var gpu: GraphicsAdapter
|
||||
|
||||
lateinit var batch: SpriteBatch
|
||||
lateinit var camera: OrthographicCamera
|
||||
|
||||
lateinit var vmRunner: VMRunner
|
||||
|
||||
lateinit var coroutineJob: Job
|
||||
lateinit var vdc: Videotron2K
|
||||
|
||||
override fun create() {
|
||||
super.create()
|
||||
|
||||
gpu = GraphicsAdapter(vm, GraphicsAdapter.DEFAULT_CONFIG_COLOR_CRT)
|
||||
|
||||
vm.peripheralTable[1] = PeripheralEntry(
|
||||
gpu,
|
||||
GraphicsAdapter.VRAM_SIZE,
|
||||
16,
|
||||
0
|
||||
)
|
||||
|
||||
batch = SpriteBatch()
|
||||
camera = OrthographicCamera(net.torvald.tsvm.AppLoader.WIDTH.toFloat(), net.torvald.tsvm.AppLoader.WIDTH.toFloat())
|
||||
camera.setToOrtho(false)
|
||||
camera.update()
|
||||
batch.projectionMatrix = camera.combined
|
||||
Gdx.gl20.glViewport(0, 0, net.torvald.tsvm.AppLoader.WIDTH, net.torvald.tsvm.AppLoader.HEIGHT)
|
||||
|
||||
vm.getPrintStream = { gpu.getPrintStream() }
|
||||
vm.getErrorStream = { gpu.getErrorStream() }
|
||||
vm.getInputStream = { gpu.getInputStream() }
|
||||
|
||||
vdc = Videotron2K(gpu)
|
||||
|
||||
vmRunner = VMRunnerFactory(vm, "js")
|
||||
coroutineJob = GlobalScope.launch {
|
||||
vdc.eval(Videotron2K.screenfiller)
|
||||
}
|
||||
|
||||
|
||||
Gdx.input.inputProcessor = vm.getIO()
|
||||
}
|
||||
|
||||
private var updateAkku = 0.0
|
||||
private var updateRate = 1f / 60f
|
||||
|
||||
override fun render() {
|
||||
Gdx.graphics.setTitle("${net.torvald.tsvm.AppLoader.appTitle} $EMDASH F: ${Gdx.graphics.framesPerSecond} $EMDASH VF: ${(1.0 / vdc.statsFrameTime).toInt()}")
|
||||
|
||||
super.render()
|
||||
|
||||
val dt = Gdx.graphics.rawDeltaTime
|
||||
updateAkku += dt
|
||||
|
||||
var i = 0L
|
||||
while (updateAkku >= updateRate) {
|
||||
updateGame(updateRate)
|
||||
updateAkku -= updateRate
|
||||
i += 1
|
||||
}
|
||||
|
||||
renderGame(dt)
|
||||
}
|
||||
|
||||
private var latch = true
|
||||
|
||||
private fun updateGame(delta: Float) {
|
||||
vm.update(delta)
|
||||
}
|
||||
|
||||
private fun renderGame(delta: Float) {
|
||||
gpu.render(delta, batch, 0f, 0f)
|
||||
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
super.dispose()
|
||||
batch.dispose()
|
||||
coroutineJob.cancel()
|
||||
vm.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
ShaderProgram.pedantic = false
|
||||
|
||||
val appConfig = Lwjgl3ApplicationConfiguration()
|
||||
appConfig.setIdleFPS(60)
|
||||
appConfig.setForegroundFPS(60)
|
||||
appConfig.useVsync(false)
|
||||
appConfig.setResizable(false)
|
||||
appConfig.setTitle("Videotron2K Test")
|
||||
appConfig.setWindowedMode(560, 448)
|
||||
Lwjgl3Application(V2kRunTest(), appConfig)
|
||||
}
|
||||
633
tsvm_core/src/net/torvald/tsvm/vdc/Videotron2K.kt
Normal file
633
tsvm_core/src/net/torvald/tsvm/vdc/Videotron2K.kt
Normal file
@@ -0,0 +1,633 @@
|
||||
package net.torvald.tsvm.vdc
|
||||
|
||||
import net.torvald.UnsafeHelper
|
||||
import torvald.random.HQRNG
|
||||
import net.torvald.tsvm.peripheral.GraphicsAdapter
|
||||
import java.lang.NumberFormatException
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.collections.HashSet
|
||||
|
||||
/**
|
||||
* See ./Videotron2K.md for documentation
|
||||
*
|
||||
* ## Variable Namespace
|
||||
* - Scenes and Variables use same namespace
|
||||
*
|
||||
*/
|
||||
class Videotron2K(var gpu: GraphicsAdapter?) {
|
||||
|
||||
companion object {
|
||||
const val REGISTER_PREFIX = 0x7FFFFFFF_00000000L
|
||||
const val VARIABLE_PREFIX = 0x3FFFFFFF_00000000L
|
||||
const val PREFIX_MASK = (0xFFFFFFFFL).shl(32)
|
||||
|
||||
const val REG_PX = REGISTER_PREFIX or 12
|
||||
const val REG_PY = REGISTER_PREFIX or 13
|
||||
const val REG_FRM = REGISTER_PREFIX or 14
|
||||
const val REG_TMR = REGISTER_PREFIX or 15
|
||||
|
||||
const val REG_R1 = REGISTER_PREFIX
|
||||
const val REG_C1 = REGISTER_PREFIX + 6
|
||||
|
||||
const val INDEXED_SCENE_MAX = 1048575
|
||||
|
||||
const val VARIABLE_RATET = VARIABLE_PREFIX or 0
|
||||
const val VARIABLE_RATEF = VARIABLE_PREFIX or 1
|
||||
const val VARIABLE_WIDTH = VARIABLE_PREFIX or 2
|
||||
const val VARIABLE_HEIGHT = VARIABLE_PREFIX or 3
|
||||
|
||||
private val specialRegs = hashMapOf(
|
||||
"px" to REG_PX,
|
||||
"py" to REG_PY,
|
||||
"frm" to REG_FRM,
|
||||
"tmr" to REG_TMR
|
||||
)
|
||||
|
||||
private val reverseSpecialRegs = HashMap(specialRegs.entries.associate { (k, v) -> v to k })
|
||||
|
||||
/*
|
||||
Registers internal variable ID:
|
||||
|
||||
r1 = REGISTER_PREFIX + 0
|
||||
r2 = REGISTER_PREFIX + 1
|
||||
r3 = REGISTER_PREFIX + 2
|
||||
r4 = REGISTER_PREFIX + 3
|
||||
r5 = REGISTER_PREFIX + 4
|
||||
r6 = REGISTER_PREFIX + 5
|
||||
|
||||
c1 = REGISTER_PREFIX + 6
|
||||
c2 = REGISTER_PREFIX + 7
|
||||
c3 = REGISTER_PREFIX + 8
|
||||
c4 = REGISTER_PREFIX + 9
|
||||
c5 = REGISTER_PREFIX + 10
|
||||
c6 = REGISTER_PREFIX + 11
|
||||
|
||||
px = REGISTER_PREFIX + 12
|
||||
py = REGISTER_PREFIX + 13
|
||||
|
||||
frm = REGISTER_PREFIX + 14
|
||||
tmr = REGISTER_PREFIX + 15
|
||||
*/
|
||||
|
||||
val screenfiller = """
|
||||
DEFINE RATEF 60
|
||||
DEFINE height 448
|
||||
DEFINE width 560
|
||||
|
||||
mov r6 12345
|
||||
|
||||
SCENE rng ; r6 is RNG value
|
||||
mul r6 r6 48271
|
||||
mod r6 r6 2147483647
|
||||
exit
|
||||
END SCENE
|
||||
|
||||
SCENE fill_line
|
||||
@ mov px 0
|
||||
perform rng
|
||||
plot r6
|
||||
; plot c1 ; will auto-increment px by one
|
||||
; inc c1
|
||||
; cmp r1 c1 251
|
||||
; movzr r1 c1 0 ; mov (-zr r1) c1 0 -- first, the comparison is made with r1 then runs 'mov c1 0' if r1 == 0
|
||||
exitzr px
|
||||
END SCENE
|
||||
|
||||
SCENE loop_frame
|
||||
@ mov py 0
|
||||
perform fill_line
|
||||
inc py
|
||||
cmp r1 py 448
|
||||
movzr r1 py 0
|
||||
next
|
||||
; exeunt
|
||||
; there's no EXIT command so this scene will make the program to go loop indefinitely
|
||||
END SCENE
|
||||
|
||||
perform loop_frame
|
||||
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
internal var regs = UnsafeHelper.allocate(16 * 4)
|
||||
internal var internalMem = UnsafeHelper.allocate(16384)
|
||||
internal var callStack = Stack<Pair<Long, Int>>() // Pair of Scene-ID (has SCENE_PREFIX; 0 with no prefix for root scene) and ProgramCounter
|
||||
|
||||
/* Compile-time variables */
|
||||
private var scenes = HashMap<Long, Array<VT2Statement>>() // Long can have either SCENE_PREFIX- or INDEXED_SCENE_PREFIX-prefixed value
|
||||
private var varIdTable = HashMap<String, Long>() // String is always uppercase, Long always has VARIABLE_PREFIX added
|
||||
//private var sceneIdTable = HashMap<String, Long>() // String is always uppercase, Long always has SCENE_PREFIX added
|
||||
internal var currentScene = 0L // VARIABLE_PREFIX is normally added but ROOT scene is just zero. Used by both parser and executor
|
||||
internal var currentLineIndex = 0
|
||||
|
||||
internal var pcLoopedBack = false
|
||||
internal var exeunt = false
|
||||
|
||||
/* Run-time variables and status */
|
||||
internal var variableMap = HashMap<Long, Int>() // VarId with VARIABLE_PREFIX, Integer-value
|
||||
internal var sleepLatch = false
|
||||
|
||||
// statistics stuffs
|
||||
internal var performanceCounterTmr = 0L
|
||||
var statsFrameTime = 0.0 // in seconds
|
||||
internal set
|
||||
|
||||
fun resetVarIdTable() {
|
||||
varIdTable.clear()
|
||||
varIdTable["RATET"] = VARIABLE_RATET
|
||||
varIdTable["RATEF"] = VARIABLE_RATEF
|
||||
varIdTable["WIDTH"] = VARIABLE_WIDTH
|
||||
varIdTable["HEIGHT"] = VARIABLE_HEIGHT
|
||||
}
|
||||
|
||||
private val reComment = Regex(""";[^\n]*""")
|
||||
private val reTokenizer = Regex(""" +""")
|
||||
private val reGeneralReg = Regex("""[rR][0-9]""")
|
||||
private val reCountReg = Regex("""[cC][0-9]""")
|
||||
|
||||
private val infoPrint = true
|
||||
private val debugPrint = false
|
||||
private val rng = torvald.random.HQRNG()
|
||||
|
||||
fun eval(command: String) {
|
||||
val rootStatements = parseCommands(command)
|
||||
|
||||
|
||||
if (infoPrint) {
|
||||
scenes.forEach { id, statements ->
|
||||
println("SCENE #${id and 0xFFFFFFFFL}")
|
||||
statements.forEachIndexed { i, it -> println("I ${i.toString().padEnd(5, ' ')}$it") }
|
||||
println("END SCENE\n")
|
||||
}
|
||||
|
||||
rootStatements.forEachIndexed { i, it -> println("I ${i.toString().padEnd(5, ' ')}$it") }
|
||||
}
|
||||
|
||||
execute(rootStatements)
|
||||
}
|
||||
|
||||
private fun execute(rootStatements: Array<VT2Statement>) {
|
||||
variableMap.clear()
|
||||
currentScene = 0
|
||||
regs.fillWith(0)
|
||||
|
||||
while (!exeunt) {
|
||||
val scene = if (currentScene == 0L) rootStatements else scenes[currentScene]!!
|
||||
val it = scene[currentLineIndex]
|
||||
val oldSceneNo = currentScene
|
||||
|
||||
if (it.prefix != StatementPrefix.INIT || it.prefix == StatementPrefix.INIT && !pcLoopedBack) {
|
||||
if (debugPrint) println("Run-Scene: ${currentScene and 0xFFFFFFFFL}, Lindex: $currentLineIndex, Inst: $it")
|
||||
Command.checkConditionAndRun(it.command, this, it.args)
|
||||
if (debugPrint) println("Reg-r1: ${regs.getInt((REG_R1 and 0xF) * 4)}, c1: ${regs.getInt((REG_C1 and 0xF) * 4)}, px: ${regs.getInt((REG_PX and 0xF) * 4)}")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// increment PC
|
||||
currentLineIndex += 1
|
||||
//// check if PC should loop back into the beginning of the scene
|
||||
if (currentScene == oldSceneNo && currentLineIndex == scene.size) {
|
||||
currentLineIndex = 0
|
||||
pcLoopedBack = true
|
||||
}
|
||||
if (currentScene != oldSceneNo) {
|
||||
pcLoopedBack = false
|
||||
}
|
||||
}
|
||||
exeunt = false
|
||||
|
||||
|
||||
if (infoPrint) println("Ende")
|
||||
}
|
||||
|
||||
/**
|
||||
* Clobbers scenes, varIdTable, sceneIdTable and temporary variable sceneIdTable
|
||||
*
|
||||
* @return root statements; scene statements are stored in 'scenes'
|
||||
*/
|
||||
private fun parseCommands(command: String): Array<VT2Statement> {
|
||||
scenes.clear()
|
||||
resetVarIdTable()
|
||||
//sceneIdTable.clear()
|
||||
val rootStatements = ArrayList<VT2Statement>()
|
||||
val sceneStatements = ArrayList<VT2Statement>()
|
||||
|
||||
command.replace(reComment, "").split('\n')
|
||||
.mapIndexed { index, s -> index to s }.filter { it.second.isNotBlank() }
|
||||
.forEach { (lnum, stmt) ->
|
||||
val stmt = stmt.trim()
|
||||
val stmtUpper = stmt.toUpperCase()
|
||||
val wordsUpper = stmtUpper.split(reTokenizer)
|
||||
|
||||
if (stmtUpper.startsWith("SCENE_")) { // indexed scene
|
||||
if (currentScene != 0L) throw IllegalStateException("Line $lnum: Scenes cannot be nested")
|
||||
|
||||
val scenenumStr = stmt.substring(6)
|
||||
try {
|
||||
val scenenum = scenenumStr.toLong()
|
||||
|
||||
currentScene = VARIABLE_PREFIX or scenenum
|
||||
}
|
||||
catch (e: NumberFormatException) {
|
||||
throw IllegalArgumentException("Line $lnum: Illegal scene numeral on $scenenumStr")
|
||||
}
|
||||
}
|
||||
else if (stmtUpper.startsWith("SCENE ")) { // named scene
|
||||
if (currentScene != 0L) throw IllegalStateException("Line $lnum: Scenes cannot be nested")
|
||||
|
||||
val sceneName = wordsUpper[1]
|
||||
if (sceneName.isNullOrBlank()) {
|
||||
throw IllegalArgumentException("Line $lnum: Illegal scene name on $stmt")
|
||||
}
|
||||
else if (hasVar(sceneName)) {
|
||||
throw IllegalArgumentException("Line $lnum: Scene name or variable '$sceneName' already exists")
|
||||
}
|
||||
|
||||
currentScene = registerNewVariable(sceneName) // scenes use same addr space as vars, to make things easier on the backend
|
||||
}
|
||||
else if (wordsUpper[0] == "END" && wordsUpper[1] == "SCENE") { // END SCENE
|
||||
if (currentScene == 0L) throw IllegalArgumentException("Line $lnum: END SCENE is called without matching SCENE definition")
|
||||
|
||||
scenes[currentScene] = sceneStatements.toTypedArray()
|
||||
|
||||
sceneStatements.clear()
|
||||
currentScene = 0L
|
||||
}
|
||||
else {
|
||||
val cmdBuffer = if (currentScene != 0L) sceneStatements else rootStatements
|
||||
|
||||
cmdBuffer.add(translateLine(lnum + 1, stmt))
|
||||
}
|
||||
}
|
||||
|
||||
return rootStatements.toTypedArray()
|
||||
}
|
||||
|
||||
private fun translateLine(lnum: Int, line: String): VT2Statement {
|
||||
val tokens = line.split(reTokenizer)
|
||||
if (tokens.isEmpty()) throw InternalError("Line $lnum: empty line not filtered!")
|
||||
|
||||
//println(tokens)
|
||||
|
||||
val isInit = tokens[0] == "@"
|
||||
val cmdstr = tokens[isInit.toInt()].toUpperCase()
|
||||
|
||||
val cmd: Int = Command.dict[cmdstr] ?: throw RuntimeException("Undefined instruction on line $lnum: $cmdstr ($line)") // conditional code is pre-added on dict
|
||||
val args = tokens.subList(1 + isInit.toInt(), tokens.size).map { parseArgString(cmdstr, lnum, it) }
|
||||
|
||||
return VT2Statement(
|
||||
lnum,
|
||||
if (isInit) StatementPrefix.INIT else StatementPrefix.NONE,
|
||||
cmd,
|
||||
args.toLongArray()
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseArgString(parentCmd: String, lnum: Int, token: String): Long {
|
||||
if (token.toIntOrNull() != null) // number literal
|
||||
return token.toLong().and(0xFFFFFFFF)
|
||||
else if (token.endsWith('h') && token.substring(0, token.lastIndex).toIntOrNull() != null) // hex literal
|
||||
return token.substring(0, token.lastIndex).toInt(16).toLong().and(0xFFFFFFFF)
|
||||
else if (specialRegs.contains(token.toLowerCase())) // special registers
|
||||
return specialRegs[token.toLowerCase()]!!
|
||||
else if (token.matches(reGeneralReg)) // r-registers
|
||||
return REGISTER_PREFIX or token.substring(1, token.length).toLong().minus(1).and(0xFFFFFFFF)
|
||||
else if (token.matches(reCountReg)) // c-registers
|
||||
return REGISTER_PREFIX or token.substring(1, token.length).toLong().plus(5).and(0xFFFFFFFF)
|
||||
else {
|
||||
val varId = varIdTable[token.toUpperCase()] ?: (
|
||||
if (parentCmd in Command.varDefiningCommands) registerNewVariable(token)
|
||||
else throw NullPointerException("Undefined variable '$token' in line $lnum")
|
||||
)
|
||||
|
||||
return varId
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerNewVariable(varName: String): Long {
|
||||
var id: Long
|
||||
do {
|
||||
id = VARIABLE_PREFIX or rng.nextLong().and(0xFFFFFFFFL)
|
||||
} while (varIdTable.containsValue(id) || (id and 0xFFFFFFFFL) <= INDEXED_SCENE_MAX)
|
||||
|
||||
varIdTable[varName.toUpperCase()] = id
|
||||
return id
|
||||
}
|
||||
|
||||
private fun hasVar(name: String) = (varIdTable.containsKey(name.toUpperCase()))
|
||||
|
||||
|
||||
private class VT2Statement(val lnum: Int, val prefix: Int = StatementPrefix.NONE, val command: Int, val args: LongArray) {
|
||||
override fun toString(): String {
|
||||
return "L ${lnum.toString().padEnd(5, ' ')}" + StatementPrefix.toString(prefix) + " " + Command.reverseDict[command] + " " + (args.map { argsToString(it) })
|
||||
}
|
||||
|
||||
private fun argsToString(i: Long): String {
|
||||
if (reverseSpecialRegs.contains(i))
|
||||
return reverseSpecialRegs[i]!!
|
||||
else if (i and REGISTER_PREFIX == REGISTER_PREFIX) {
|
||||
val regnum = i and 0xFFFFFFFFL
|
||||
return if (regnum < 6) "r${regnum + 1}" else "c${regnum - 5}"
|
||||
}
|
||||
else if (i and VARIABLE_PREFIX == VARIABLE_PREFIX) {
|
||||
return "var:${i and 0xFFFFFFFF}"
|
||||
}
|
||||
else return i.toInt().toString()
|
||||
}
|
||||
}
|
||||
|
||||
fun dispose() {
|
||||
regs.destroy()
|
||||
}
|
||||
|
||||
private fun Boolean.toInt() = if (this) 1 else 0
|
||||
|
||||
private fun <T> Array<T>.linearSearch(selector: (T) -> Boolean): Int? {
|
||||
this.forEachIndexed { index, it ->
|
||||
if (selector.invoke(it)) return index
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
object StatementPrefix {
|
||||
const val NONE = 0
|
||||
const val INIT = 1
|
||||
|
||||
fun toString(key: Int) = when(key) {
|
||||
INIT -> "@"
|
||||
else -> " "
|
||||
}
|
||||
}
|
||||
|
||||
object Command {
|
||||
val conditional = arrayOf("ZR", "NZ", "GT", "LS", "GE", "LE")
|
||||
|
||||
const val NOP = 0
|
||||
const val ADD = 0x8
|
||||
const val SUB = 0x10
|
||||
const val MUL = 0x18
|
||||
const val DIV = 0x20
|
||||
const val AND = 0x28
|
||||
const val OR = 0x30
|
||||
const val XOR = 0x38
|
||||
const val SHL = 0x40
|
||||
const val SHR = 0x48
|
||||
const val USHR = 0x50
|
||||
const val INC = 0x58
|
||||
const val DEC = 0x60
|
||||
const val NOT = 0x68
|
||||
const val NEG = 0x70
|
||||
const val MOD = 0x78
|
||||
|
||||
const val CMP = 0x80
|
||||
const val MOV = 0x88
|
||||
const val DATA = 0x90
|
||||
const val MCP = 0x98
|
||||
|
||||
const val PERFORM = 0x100
|
||||
const val NEXT = 0x108
|
||||
const val EXIT = 0x110
|
||||
const val EXEUNT = 0x118
|
||||
|
||||
const val FILLIN = 0x200
|
||||
const val PLOT = 0x208
|
||||
const val FILLSCR = 0x210
|
||||
const val GOTO = 0x218
|
||||
const val BORDER = 0x220
|
||||
const val PLOTP = 0x228
|
||||
|
||||
const val WAIT = 0x700
|
||||
const val DEFINE = 0x708
|
||||
|
||||
const val INST_ID_MAX = 0x800 // 256 if divided by 8
|
||||
|
||||
val dict = HashMap<String, Int>()
|
||||
|
||||
val varDefiningCommands = HashSet<String>()
|
||||
val transferInst = HashSet<Int>()
|
||||
|
||||
|
||||
// fill in conditionals to dict
|
||||
init {
|
||||
/* dict = */hashMapOf(
|
||||
"NOP" to NOP,
|
||||
"ADD" to ADD,
|
||||
"SUB" to SUB,
|
||||
"MUL" to MUL,
|
||||
"DIV" to DIV,
|
||||
"AND" to AND,
|
||||
"OR" to OR,
|
||||
"XOR" to XOR,
|
||||
"SHL" to SHL,
|
||||
"SHR" to SHR,
|
||||
"USHR" to USHR,
|
||||
"INC" to INC,
|
||||
"DEC" to DEC,
|
||||
"NOT" to NOT,
|
||||
"NEG" to NEG,
|
||||
"MOD" to MOD,
|
||||
|
||||
"CMP" to CMP,
|
||||
|
||||
"MOV" to MOV,
|
||||
"DATA" to DATA,
|
||||
"MCP" to MCP,
|
||||
|
||||
"PERFORM" to PERFORM,
|
||||
"NEXT" to NEXT,
|
||||
"EXIT" to EXIT,
|
||||
"EXEUNT" to EXEUNT,
|
||||
|
||||
"FILLIN" to FILLIN,
|
||||
"PLOT" to PLOT,
|
||||
"PLOTP" to PLOTP,
|
||||
"FILLSCR" to FILLSCR,
|
||||
"GOTO" to GOTO,
|
||||
"BORDER" to BORDER,
|
||||
|
||||
"WAIT" to WAIT,
|
||||
|
||||
"DEFINE" to DEFINE
|
||||
).entries.forEach { (command, opcode) ->
|
||||
dict[command] = opcode
|
||||
|
||||
conditional.forEachIndexed { i, cond ->
|
||||
dict[command + cond] = opcode + i + 1
|
||||
}
|
||||
}
|
||||
|
||||
/* varDefiningCommands = */hashSetOf(
|
||||
"DEFINE"
|
||||
).forEach { command ->
|
||||
varDefiningCommands.add(command)
|
||||
|
||||
conditional.forEach { cond ->
|
||||
varDefiningCommands.add(command + cond)
|
||||
}
|
||||
}
|
||||
|
||||
/* transferInst = */hashSetOf(
|
||||
PERFORM, EXIT, EXEUNT
|
||||
).forEach { command ->
|
||||
transferInst.add(command)
|
||||
|
||||
conditional.forEachIndexed { cond, _ ->
|
||||
transferInst.add(command + cond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val reverseDict = HashMap<Int, String>(dict.entries.associate { (k,v)-> v to k })
|
||||
|
||||
/**
|
||||
* function (instance: Videotron2K, args: LongArray)
|
||||
*/
|
||||
val instSet = Array<(Videotron2K, LongArray) -> Unit>(256) { { _, _ -> TODO("Instruction ID $it (${reverseDict[it * 8]})") } }
|
||||
|
||||
init {
|
||||
instSet[NOP] = { _, _ -> }
|
||||
instSet[DEFINE shr 3] = { instance, args -> // DEFINE variable value
|
||||
if (args.size != 2) throw ArgsCountMismatch(2, args)
|
||||
|
||||
instance.variableMap[args[0]] = if (args[1] and Videotron2K.VARIABLE_PREFIX == Videotron2K.VARIABLE_PREFIX) {
|
||||
instance.variableMap[args[1]] ?: throw NullVar()
|
||||
}
|
||||
else if (args[1] and Videotron2K.PREFIX_MASK == 0L) {
|
||||
args[1].toInt()
|
||||
}
|
||||
else {
|
||||
throw UnknownVariableType(args[1])
|
||||
}
|
||||
}
|
||||
instSet[MOV shr 3] = { instance, args -> // MOV register value
|
||||
if (args.size != 2) throw ArgsCountMismatch(2, args)
|
||||
checkRegisterLH(args[0])
|
||||
instance.regs.setInt((args[0] and 0xF) * 4, resolveVar(instance, args[1]))
|
||||
}
|
||||
instSet[MUL shr 3] = { instance, args -> // MUL ACC LH RH
|
||||
twoArgArithmetic(instance, args) { a,b -> a*b }
|
||||
}
|
||||
instSet[ADD shr 3] = { instance, args -> // ADD ACC LH RH
|
||||
twoArgArithmetic(instance, args) { a,b -> a+b }
|
||||
}
|
||||
instSet[MOD shr 3] = { instance, args -> // MOD ACC LH RH
|
||||
twoArgArithmetic(instance, args) { a,b -> a fmod b }
|
||||
}
|
||||
instSet[INC shr 3] = { instance, args -> // INC register
|
||||
if (args.size != 1) throw ArgsCountMismatch(1, args)
|
||||
checkRegisterLH(args[0])
|
||||
instance.regs.setInt((args[0] and 0xF) * 4, 1 + instance.regs.getInt((args[0] and 0xF) * 4))
|
||||
}
|
||||
instSet[DEC shr 3] = { instance, args -> // DEC register
|
||||
if (args.size != 1) throw ArgsCountMismatch(1, args)
|
||||
checkRegisterLH(args[0])
|
||||
instance.regs.setInt((args[0] and 0xF) * 4, 1 - instance.regs.getInt((args[0] and 0xF) * 4))
|
||||
}
|
||||
instSet[NEXT shr 3] = { instance, _ ->
|
||||
instance.regs.setInt((Videotron2K.REG_FRM and 0xF) * 4, 1 + instance.regs.getInt((Videotron2K.REG_FRM and 0xF) * 4))
|
||||
instance.sleepLatch = true
|
||||
|
||||
val timeTook = (System.nanoTime() - instance.performanceCounterTmr).toDouble()
|
||||
instance.statsFrameTime = timeTook * 0.000001
|
||||
instance.performanceCounterTmr = System.nanoTime()
|
||||
}
|
||||
instSet[CMP shr 3] = { instance, args -> // CMP rA rB rC
|
||||
twoArgArithmetic(instance, args) { a,b -> if (a>b) 1 else if (a<b) -1 else 0 }
|
||||
}
|
||||
instSet[PLOT shr 3] = { instance, args -> // PLOT vararg-bytes
|
||||
if (args.isNotEmpty()) {
|
||||
val px = instance.regs.getInt((Videotron2K.REG_PX and 0xF) * 4)
|
||||
val py = instance.regs.getInt((Videotron2K.REG_PY and 0xF) * 4)
|
||||
val width = instance.variableMap[Videotron2K.VARIABLE_WIDTH]!!
|
||||
val memAddr = py * width + px
|
||||
|
||||
args.forEachIndexed { index, variable ->
|
||||
val value = resolveVar(instance, variable).toByte()
|
||||
instance.gpu?.poke(memAddr.toLong() + index, value)
|
||||
}
|
||||
|
||||
// write back auto-incremented value
|
||||
instance.regs.setInt((Videotron2K.REG_PX and 0xF) * 4, (px + args.size) fmod width)
|
||||
}
|
||||
}
|
||||
instSet[PERFORM shr 3] = { instance, args -> // PERFORM scene
|
||||
instance.callStack.push(instance.currentScene to instance.currentLineIndex)
|
||||
instance.currentScene = args[0]
|
||||
instance.currentLineIndex = -1
|
||||
}
|
||||
instSet[EXIT shr 3] = { instance, _ ->
|
||||
val (scene, line) = instance.callStack.pop()
|
||||
instance.currentScene = scene
|
||||
instance.currentLineIndex = line
|
||||
//println("EXIT!")
|
||||
//Thread.sleep(1000L)
|
||||
}
|
||||
instSet[EXEUNT shr 3] = { instance, _ ->
|
||||
instance.exeunt = true
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun twoArgArithmetic(instance: Videotron2K, args: LongArray, operation: (Int, Int) -> Int) {
|
||||
if (args.size != 3) throw ArgsCountMismatch(3, args)
|
||||
checkRegisterLH(args[0])
|
||||
val lh = resolveVar(instance, args[1])
|
||||
val rh = resolveVar(instance, args[2])
|
||||
instance.regs.setInt((args[0] and 0xF) * 4, operation(lh, rh))
|
||||
}
|
||||
|
||||
fun checkConditionAndRun(inst: Int, instance: Videotron2K, args: LongArray) {
|
||||
val opcode = inst shr 3
|
||||
val condCode = inst and 7
|
||||
|
||||
if (condCode == 0) {
|
||||
//if (inst !in transferInst) instance.currentLineIndex += 1
|
||||
instSet[opcode].invoke(instance, args)
|
||||
return
|
||||
}
|
||||
|
||||
val condition = when (condCode) {
|
||||
1 -> resolveVar(instance, args[0]) == 0
|
||||
2 -> resolveVar(instance, args[0]) != 0
|
||||
3 -> resolveVar(instance, args[0]) > 0
|
||||
4 -> resolveVar(instance, args[0]) < 0
|
||||
5 -> resolveVar(instance, args[0]) >= 0
|
||||
6 -> resolveVar(instance, args[0]) <= 0
|
||||
else -> throw InternalError()
|
||||
}
|
||||
|
||||
if (condition) {
|
||||
//if (inst !in transferInst) instance.currentLineIndex += 1
|
||||
instSet[opcode].invoke(instance, args.sliceArray(1 until args.size))
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveVar(instance: Videotron2K, arg: Long): Int {
|
||||
return if (arg and Videotron2K.REGISTER_PREFIX == Videotron2K.REGISTER_PREFIX) {
|
||||
instance.regs.getInt((arg and 0xF) * 4)
|
||||
}
|
||||
else if (arg and Videotron2K.VARIABLE_PREFIX == Videotron2K.VARIABLE_PREFIX) {
|
||||
instance.variableMap[arg] ?: throw NullVar()
|
||||
}
|
||||
else arg.toInt()
|
||||
}
|
||||
|
||||
private fun checkRegisterLH(arg: Long) {
|
||||
if (arg and Videotron2K.REGISTER_PREFIX != Videotron2K.REGISTER_PREFIX) {
|
||||
throw BadLeftHand("register")
|
||||
}
|
||||
}
|
||||
|
||||
private infix fun Int.fmod(other: Int) = Math.floorMod(this, other)
|
||||
}
|
||||
|
||||
internal class ArgsCountMismatch(expected: Int, args: LongArray) : RuntimeException("Argument count mismatch: expected $expected, got ${args.size}")
|
||||
internal class UnknownVariableType(arg: Long) : RuntimeException("Unknown variable type: ${arg.ushr(32).toString(16)}")
|
||||
internal class NullVar : RuntimeException("Variable is undeclared")
|
||||
internal class BadLeftHand(type: String) : RuntimeException("Left-hand argument is not $type")
|
||||
internal class BadRightHand(type: String) : RuntimeException("Right-hand argument is not $type")
|
||||
Reference in New Issue
Block a user