From 769b6481da5d8e2dee0e7b0044d0608c002b25cd Mon Sep 17 00:00:00 2001 From: minjaesong Date: Tue, 7 Oct 2025 22:41:34 +0900 Subject: [PATCH] HSDPA supporting file larger than 2GB --- assets/disk0/tvdos/HSDPADRV.SYS | 3 +- assets/disk0/tvdos/bin/playtav.js | 28 ++++-- assets/disk0/tvdos/include/seqreadtape.mjs | 23 +++-- .../src/net/torvald/tsvm/peripheral/HSDPA.kt | 22 ++--- .../torvald/tsvm/peripheral/HostFileHSDPA.kt | 96 +++++++++++-------- 5 files changed, 106 insertions(+), 66 deletions(-) diff --git a/assets/disk0/tvdos/HSDPADRV.SYS b/assets/disk0/tvdos/HSDPADRV.SYS index 61878e1..f4416c2 100644 --- a/assets/disk0/tvdos/HSDPADRV.SYS +++ b/assets/disk0/tvdos/HSDPADRV.SYS @@ -117,8 +117,9 @@ for (let tapeIndex = 0; tapeIndex < 4; tapeIndex++) { // Get file size - for HSDPA tapes, we don't know the size ahead of time // So we return a very large number to indicate it's available + // Using Number.MAX_SAFE_INTEGER to support files >2GB driver.getFileLen = (fd) => { - return 0x7FFFFFFF // Return max positive 32-bit integer + return Number.MAX_SAFE_INTEGER // 2^53 - 1 (9007199254740991) - safe for JS arithmetic } // Sequential read from tape diff --git a/assets/disk0/tvdos/bin/playtav.js b/assets/disk0/tvdos/bin/playtav.js index 707f397..71d223b 100644 --- a/assets/disk0/tvdos/bin/playtav.js +++ b/assets/disk0/tvdos/bin/playtav.js @@ -548,11 +548,20 @@ function tryReadNextTAVHeader() { offsetBytes.push(seqread.readOneByte()) } - element.offset = 0 - for (let j = 0; j < 6; j++) { - element.offset |= (offsetBytes[j] << (j * 8)) + // Split into low 32 bits and high 16 bits + let low32 = 0 + for (let j = 0; j < 4; j++) { + low32 |= (offsetBytes[j] << (j * 8)) } + let high16 = 0 + for (let j = 4; j < 6; j++) { + high16 |= (offsetBytes[j] << ((j - 4) * 8)) + } + + // Combine using multiplication (avoids bitwise 32-bit limit) + element.offset = (high16 * 0x100000000) + (low32 >>> 0) + serial.println(`Element ${i + 1}: ${element.name} -> offset ${element.offset} (internal)`) } else { serial.println(`Error: Unknown addressing mode: ${element.addressingMode}`) @@ -585,6 +594,7 @@ function tryReadNextTAVHeader() { } let lastKey = 0 +let skipped = false // Playback loop - properly adapted from TEV with multi-file support try { @@ -614,6 +624,7 @@ try { akku = FRAME_TIME akku2 = 0.0 audio.purgeQueue(0) + skipped = true } } else if (keyCode == 20 && cueElements.length > 0) { // Down arrow - next cue @@ -627,6 +638,7 @@ try { akku = FRAME_TIME akku2 = 0.0 audio.purgeQueue(0) + skipped = true } } } @@ -652,6 +664,11 @@ try { FRAME_TIME = 1.0 / header.fps audio.purgeQueue(0) currentFileIndex++ + if (skipped) { + skipped = false + } else { + currentCueIndex++ + } totalFilesProcessed++ console.log(`\nStarting file ${currentFileIndex}:`) @@ -834,7 +851,6 @@ try { notifHidden = true } - con.color_pair(253, 0) let guiStatus = { fps: header.fps, @@ -845,8 +861,8 @@ try { qCo: decoderDbgInfo.qCo, qCg: decoderDbgInfo.qCg, akku: akku2, - fileName: fullFilePathStr, - fileOrd: currentFileIndex, + fileName: (cueElements.length > 0) ? `${cueElements[currentCueIndex].name}` : fullFilePathStr, + fileOrd: (cueElements.length > 0) ? currentCueIndex+1 : currentFileIndex, resolution: `${header.width}x${header.height}${(isInterlaced) ? 'i' : ''}`, colourSpace: header.version % 2 == 0 ? "ICtCp" : "YCoCg", currentStatus: 1 diff --git a/assets/disk0/tvdos/include/seqreadtape.mjs b/assets/disk0/tvdos/include/seqreadtape.mjs index daffd51..54b51e7 100644 --- a/assets/disk0/tvdos/include/seqreadtape.mjs +++ b/assets/disk0/tvdos/include/seqreadtape.mjs @@ -203,7 +203,7 @@ function skip(n0) { let n = n0 while (n > 0) { let skiplen = Math.min(n, 16777215) - serial.println(`skip ${skiplen}; remaining: ${n}`) +// serial.println(`skip ${skiplen}; remaining: ${n}`) hsdpaSkip(skiplen) n -= skiplen } @@ -237,14 +237,23 @@ function isReady() { } function seek(position) { + if (position < 0) { + throw Error("seek: position must be non-negative") + } + let relPos = position - readCount - if (position == 0) { - return - } else if (position > 0) { - skip(relPos) + + if (relPos == 0) { + return // Already at target position + } else if (relPos < 0) { + // Seeking backward - must rewind and skip forward + hsdpaRewind() // This resets readCount to 0 + if (position > 0) { + skip(position) + } } else { - hsdpaRewind() - skip(position) + // Seeking forward - skip the difference + skip(relPos) } } diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/HSDPA.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/HSDPA.kt index 3a2fd00..c3f005a 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/HSDPA.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/HSDPA.kt @@ -78,8 +78,8 @@ open class HSDPA(val vm: VM, val baudRate: Long = 133_333_333L): PeriBase("hsdpa } private var opcodeBuf = 0 - private var arg1 = 0 - private var arg2 = 0 + private var arg1 = 0L // Long to support >2GB sequential I/O position accumulation + private var arg2 = 0L // Long to support >2GB memory addressing /** * Reads a value from the MMIO register @@ -167,18 +167,18 @@ open class HSDPA(val vm: VM, val baudRate: Long = 133_333_333L): PeriBase("hsdpa val byteOffset = (address - REG_SEQ_IO_ARG1) if (byteOffset == 0) { // Reset arg1 when writing to LSB - arg1 = value.toUint() + arg1 = value.toUint().toLong() } else { - arg1 = arg1 or (value.toUint() shl (byteOffset * 8)) + arg1 = arg1 or ((value.toUint().toLong()) shl (byteOffset * 8)) } } in REG_SEQ_IO_ARG2..REG_SEQ_IO_ARG2+2 -> { val byteOffset = (address - REG_SEQ_IO_ARG2) if (byteOffset == 0) { // Reset arg2 when writing to LSB - arg2 = value.toUint() + arg2 = value.toUint().toLong() } else { - arg2 = arg2 or (value.toUint() shl (byteOffset * 8)) + arg2 = arg2 or ((value.toUint().toLong()) shl (byteOffset * 8)) } } else -> null @@ -390,23 +390,23 @@ open class HSDPA(val vm: VM, val baudRate: Long = 133_333_333L): PeriBase("hsdpa /** * Skip bytes in sequential I/O mode */ - protected open fun sequentialIOSkip(bytes: Int) { + protected open fun sequentialIOSkip(bytes: Long) { sequentialIOPosition += bytes } - + /** * Read bytes from disk to VM memory in sequential I/O mode */ - protected open fun sequentialIORead(bytes: Int, vmMemoryPointer: Int) { + protected open fun sequentialIORead(bytes: Long, vmMemoryPointer: Long) { // Default implementation - subclasses should override // For now, just advance the position sequentialIOPosition += bytes } - + /** * Write bytes from VM memory to disk in sequential I/O mode */ - protected open fun sequentialIOWrite(bytes: Int, vmMemoryPointer: Int) { + protected open fun sequentialIOWrite(bytes: Long, vmMemoryPointer: Long) { // Default implementation - subclasses should override // For now, just advance the position sequentialIOPosition += bytes diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/HostFileHSDPA.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/HostFileHSDPA.kt index 9245e2e..4aaa852 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/HostFileHSDPA.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/HostFileHSDPA.kt @@ -2,7 +2,9 @@ package net.torvald.tsvm.peripheral import net.torvald.tsvm.VM import java.io.File -import java.io.RandomAccessFile +import java.io.FileInputStream +import java.nio.ByteBuffer +import java.nio.channels.FileChannel /** * Host File High Speed Disk Peripheral Adapter (HostFileHSDPA) @@ -25,7 +27,8 @@ class HostFileHSDPA : HSDPA { } // Host files for each disk slot - private val hostFiles = Array(MAX_DISKS) { null } + private val hostFileStreams = Array(MAX_DISKS) { null } + private val hostFileChannels = Array(MAX_DISKS) { null } private val hostFilePaths = Array(MAX_DISKS) { null } private fun initializeHostFiles(hostFilePathsList: List) { @@ -33,7 +36,9 @@ class HostFileHSDPA : HSDPA { for (i in 0 until minOf(hostFilePathsList.size, MAX_DISKS)) { val file = File(hostFilePathsList[i]) if (file.exists() && file.isFile) { - this.hostFiles[i] = RandomAccessFile(file, "r") + val stream = FileInputStream(file) + this.hostFileStreams[i] = stream + this.hostFileChannels[i] = stream.channel this.hostFilePaths[i] = hostFilePathsList[i] println("HostFileHSDPA: Attached file '${hostFilePathsList[i]}' to disk $i") } else { @@ -50,15 +55,18 @@ class HostFileHSDPA : HSDPA { */ fun attachHostFile(diskIndex: Int, filePath: String) { if (diskIndex < 0 || diskIndex >= MAX_DISKS) return - + try { // Close existing file if any - hostFiles[diskIndex]?.close() - + hostFileChannels[diskIndex]?.close() + hostFileStreams[diskIndex]?.close() + // Open new file val file = File(filePath) if (file.exists() && file.isFile) { - hostFiles[diskIndex] = RandomAccessFile(file, "r") + val stream = FileInputStream(file) + hostFileStreams[diskIndex] = stream + hostFileChannels[diskIndex] = stream.channel hostFilePaths[diskIndex] = filePath println("HSDPA: Attached file '$filePath' to disk $diskIndex") } else { @@ -75,10 +83,12 @@ class HostFileHSDPA : HSDPA { */ fun detachHostFile(diskIndex: Int) { if (diskIndex < 0 || diskIndex >= MAX_DISKS) return - + try { - hostFiles[diskIndex]?.close() - hostFiles[diskIndex] = null + hostFileChannels[diskIndex]?.close() + hostFileStreams[diskIndex]?.close() + hostFileChannels[diskIndex] = null + hostFileStreams[diskIndex] = null hostFilePaths[diskIndex] = null println("HSDPA: Detached file from disk $diskIndex") } catch (e: Exception) { @@ -93,15 +103,15 @@ class HostFileHSDPA : HSDPA { */ fun getAttachedFileSize(diskIndex: Int): Long { if (diskIndex < 0 || diskIndex >= MAX_DISKS) return 0L - + return try { - hostFiles[diskIndex]?.length() ?: 0L + hostFileChannels[diskIndex]?.size() ?: 0L } catch (e: Exception) { 0L } } - override fun sequentialIOSkip(bytes: Int) { + override fun sequentialIOSkip(bytes: Long) { sequentialIOPosition += bytes // Clamp position to file bounds if needed val activeDiskIndex = getActiveDiskIndex() @@ -113,34 +123,36 @@ class HostFileHSDPA : HSDPA { } } - override fun sequentialIORead(bytes: Int, vmMemoryPointer0: Int) { + override fun sequentialIORead(bytes: Long, vmMemoryPointer0: Long) { val activeDiskIndex = getActiveDiskIndex() - if (activeDiskIndex < 0 || hostFiles[activeDiskIndex] == null) { + if (activeDiskIndex < 0 || hostFileChannels[activeDiskIndex] == null) { // No file attached, just advance position sequentialIOPosition += bytes return } - // convert Uint24 to Int32 - val vmMemoryPointer = if (vmMemoryPointer0 and 0x800000 != 0) - (0xFF000000.toInt() or vmMemoryPointer0) + // convert Int24 memory pointer to Int32 + val vmMemoryPointer = if (vmMemoryPointer0 and 0x800000 != 0L) + (0xFF000000.toInt().toLong() or vmMemoryPointer0) else vmMemoryPointer0 try { - val file = hostFiles[activeDiskIndex]!! - val readPosition = sequentialIOPosition - file.seek(sequentialIOPosition) - - // Read data into a temporary buffer - val readBuffer = ByteArray(bytes) - val bytesRead = file.read(readBuffer) - + val channel = hostFileChannels[activeDiskIndex]!! + + // Read data using positional read (supports >2GB positions) + val buffer = ByteBuffer.allocate(bytes.toInt()) + val bytesRead = channel.read(buffer, sequentialIOPosition) + if (bytesRead > 0) { + buffer.flip() + val readBuffer = ByteArray(bytesRead) + buffer.get(readBuffer) + // Copy data to VM memory // Handle negative addresses (backwards addressing) vs positive addresses if (vmMemoryPointer < 0) { - // Negative addresses use backwards addressing + // Negative addresses use backwards addressing for (i in 0 until bytesRead) { vm.poke(vmMemoryPointer - i.toLong(), readBuffer[i]) } @@ -151,42 +163,43 @@ class HostFileHSDPA : HSDPA { } } sequentialIOPosition += bytesRead - + } - + // Fill remaining bytes with zeros if we read less than requested - if (bytesRead < bytes) { + val actualBytesRead = if (bytesRead > 0) bytesRead else 0 + if (actualBytesRead < bytes) { if (vmMemoryPointer < 0) { // Negative addresses use backwards addressing - for (i in bytesRead until bytes) { + for (i in actualBytesRead until bytes.toInt()) { vm.poke(vmMemoryPointer - i.toLong(), 0) } } else { // Positive addresses use forward addressing - for (i in bytesRead until bytes) { + for (i in actualBytesRead until bytes.toInt()) { vm.poke(vmMemoryPointer + i.toLong(), 0) } } - sequentialIOPosition += (bytes - bytesRead) + sequentialIOPosition += (bytes - actualBytesRead) } - + } catch (e: Exception) { // Just advance position on error sequentialIOPosition += bytes } } - override fun sequentialIOWrite(bytes: Int, vmMemoryPointer0: Int) { + override fun sequentialIOWrite(bytes: Long, vmMemoryPointer0: Long) { val activeDiskIndex = getActiveDiskIndex() - if (activeDiskIndex < 0 || hostFiles[activeDiskIndex] == null) { + if (activeDiskIndex < 0 || hostFileChannels[activeDiskIndex] == null) { // No file attached, just advance position sequentialIOPosition += bytes return } - // convert Uint24 to Int32 - val vmMemoryPointer = if (vmMemoryPointer0 and 0x800000 != 0) - (0xFF000000.toInt() or vmMemoryPointer0) + // convert Int24 memory pointer to Int32 + val vmMemoryPointer = if (vmMemoryPointer0 and 0x800000 != 0L) + (0xFF000000.toInt().toLong() or vmMemoryPointer0) else vmMemoryPointer0 @@ -207,11 +220,12 @@ class HostFileHSDPA : HSDPA { override fun dispose() { super.dispose() - + // Close all open files for (i in 0 until MAX_DISKS) { try { - hostFiles[i]?.close() + hostFileChannels[i]?.close() + hostFileStreams[i]?.close() } catch (e: Exception) { // Ignore errors during cleanup }