HSDPA supporting file larger than 2GB

This commit is contained in:
minjaesong
2025-10-07 22:41:34 +09:00
parent 00e390d879
commit 769b6481da
5 changed files with 106 additions and 66 deletions

View File

@@ -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 // 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 // So we return a very large number to indicate it's available
// Using Number.MAX_SAFE_INTEGER to support files >2GB
driver.getFileLen = (fd) => { 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 // Sequential read from tape

View File

@@ -548,11 +548,20 @@ function tryReadNextTAVHeader() {
offsetBytes.push(seqread.readOneByte()) offsetBytes.push(seqread.readOneByte())
} }
element.offset = 0 // Split into low 32 bits and high 16 bits
for (let j = 0; j < 6; j++) { let low32 = 0
element.offset |= (offsetBytes[j] << (j * 8)) 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)`) serial.println(`Element ${i + 1}: ${element.name} -> offset ${element.offset} (internal)`)
} else { } else {
serial.println(`Error: Unknown addressing mode: ${element.addressingMode}`) serial.println(`Error: Unknown addressing mode: ${element.addressingMode}`)
@@ -585,6 +594,7 @@ function tryReadNextTAVHeader() {
} }
let lastKey = 0 let lastKey = 0
let skipped = false
// Playback loop - properly adapted from TEV with multi-file support // Playback loop - properly adapted from TEV with multi-file support
try { try {
@@ -614,6 +624,7 @@ try {
akku = FRAME_TIME akku = FRAME_TIME
akku2 = 0.0 akku2 = 0.0
audio.purgeQueue(0) audio.purgeQueue(0)
skipped = true
} }
} }
else if (keyCode == 20 && cueElements.length > 0) { // Down arrow - next cue else if (keyCode == 20 && cueElements.length > 0) { // Down arrow - next cue
@@ -627,6 +638,7 @@ try {
akku = FRAME_TIME akku = FRAME_TIME
akku2 = 0.0 akku2 = 0.0
audio.purgeQueue(0) audio.purgeQueue(0)
skipped = true
} }
} }
} }
@@ -652,6 +664,11 @@ try {
FRAME_TIME = 1.0 / header.fps FRAME_TIME = 1.0 / header.fps
audio.purgeQueue(0) audio.purgeQueue(0)
currentFileIndex++ currentFileIndex++
if (skipped) {
skipped = false
} else {
currentCueIndex++
}
totalFilesProcessed++ totalFilesProcessed++
console.log(`\nStarting file ${currentFileIndex}:`) console.log(`\nStarting file ${currentFileIndex}:`)
@@ -834,7 +851,6 @@ try {
notifHidden = true notifHidden = true
} }
con.color_pair(253, 0) con.color_pair(253, 0)
let guiStatus = { let guiStatus = {
fps: header.fps, fps: header.fps,
@@ -845,8 +861,8 @@ try {
qCo: decoderDbgInfo.qCo, qCo: decoderDbgInfo.qCo,
qCg: decoderDbgInfo.qCg, qCg: decoderDbgInfo.qCg,
akku: akku2, akku: akku2,
fileName: fullFilePathStr, fileName: (cueElements.length > 0) ? `${cueElements[currentCueIndex].name}` : fullFilePathStr,
fileOrd: currentFileIndex, fileOrd: (cueElements.length > 0) ? currentCueIndex+1 : currentFileIndex,
resolution: `${header.width}x${header.height}${(isInterlaced) ? 'i' : ''}`, resolution: `${header.width}x${header.height}${(isInterlaced) ? 'i' : ''}`,
colourSpace: header.version % 2 == 0 ? "ICtCp" : "YCoCg", colourSpace: header.version % 2 == 0 ? "ICtCp" : "YCoCg",
currentStatus: 1 currentStatus: 1

View File

@@ -203,7 +203,7 @@ function skip(n0) {
let n = n0 let n = n0
while (n > 0) { while (n > 0) {
let skiplen = Math.min(n, 16777215) let skiplen = Math.min(n, 16777215)
serial.println(`skip ${skiplen}; remaining: ${n}`) // serial.println(`skip ${skiplen}; remaining: ${n}`)
hsdpaSkip(skiplen) hsdpaSkip(skiplen)
n -= skiplen n -= skiplen
} }
@@ -237,14 +237,23 @@ function isReady() {
} }
function seek(position) { function seek(position) {
if (position < 0) {
throw Error("seek: position must be non-negative")
}
let relPos = position - readCount let relPos = position - readCount
if (position == 0) {
return if (relPos == 0) {
} else if (position > 0) { return // Already at target position
skip(relPos) } else if (relPos < 0) {
// Seeking backward - must rewind and skip forward
hsdpaRewind() // This resets readCount to 0
if (position > 0) {
skip(position)
}
} else { } else {
hsdpaRewind() // Seeking forward - skip the difference
skip(position) skip(relPos)
} }
} }

View File

@@ -78,8 +78,8 @@ open class HSDPA(val vm: VM, val baudRate: Long = 133_333_333L): PeriBase("hsdpa
} }
private var opcodeBuf = 0 private var opcodeBuf = 0
private var arg1 = 0 private var arg1 = 0L // Long to support >2GB sequential I/O position accumulation
private var arg2 = 0 private var arg2 = 0L // Long to support >2GB memory addressing
/** /**
* Reads a value from the MMIO register * 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) val byteOffset = (address - REG_SEQ_IO_ARG1)
if (byteOffset == 0) { if (byteOffset == 0) {
// Reset arg1 when writing to LSB // Reset arg1 when writing to LSB
arg1 = value.toUint() arg1 = value.toUint().toLong()
} else { } 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 -> { in REG_SEQ_IO_ARG2..REG_SEQ_IO_ARG2+2 -> {
val byteOffset = (address - REG_SEQ_IO_ARG2) val byteOffset = (address - REG_SEQ_IO_ARG2)
if (byteOffset == 0) { if (byteOffset == 0) {
// Reset arg2 when writing to LSB // Reset arg2 when writing to LSB
arg2 = value.toUint() arg2 = value.toUint().toLong()
} else { } else {
arg2 = arg2 or (value.toUint() shl (byteOffset * 8)) arg2 = arg2 or ((value.toUint().toLong()) shl (byteOffset * 8))
} }
} }
else -> null 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 * Skip bytes in sequential I/O mode
*/ */
protected open fun sequentialIOSkip(bytes: Int) { protected open fun sequentialIOSkip(bytes: Long) {
sequentialIOPosition += bytes sequentialIOPosition += bytes
} }
/** /**
* Read bytes from disk to VM memory in sequential I/O mode * 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 // Default implementation - subclasses should override
// For now, just advance the position // For now, just advance the position
sequentialIOPosition += bytes sequentialIOPosition += bytes
} }
/** /**
* Write bytes from VM memory to disk in sequential I/O mode * 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 // Default implementation - subclasses should override
// For now, just advance the position // For now, just advance the position
sequentialIOPosition += bytes sequentialIOPosition += bytes

View File

@@ -2,7 +2,9 @@ package net.torvald.tsvm.peripheral
import net.torvald.tsvm.VM import net.torvald.tsvm.VM
import java.io.File 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) * Host File High Speed Disk Peripheral Adapter (HostFileHSDPA)
@@ -25,7 +27,8 @@ class HostFileHSDPA : HSDPA {
} }
// Host files for each disk slot // Host files for each disk slot
private val hostFiles = Array<RandomAccessFile?>(MAX_DISKS) { null } private val hostFileStreams = Array<FileInputStream?>(MAX_DISKS) { null }
private val hostFileChannels = Array<FileChannel?>(MAX_DISKS) { null }
private val hostFilePaths = Array<String?>(MAX_DISKS) { null } private val hostFilePaths = Array<String?>(MAX_DISKS) { null }
private fun initializeHostFiles(hostFilePathsList: List<String>) { private fun initializeHostFiles(hostFilePathsList: List<String>) {
@@ -33,7 +36,9 @@ class HostFileHSDPA : HSDPA {
for (i in 0 until minOf(hostFilePathsList.size, MAX_DISKS)) { for (i in 0 until minOf(hostFilePathsList.size, MAX_DISKS)) {
val file = File(hostFilePathsList[i]) val file = File(hostFilePathsList[i])
if (file.exists() && file.isFile) { 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] this.hostFilePaths[i] = hostFilePathsList[i]
println("HostFileHSDPA: Attached file '${hostFilePathsList[i]}' to disk $i") println("HostFileHSDPA: Attached file '${hostFilePathsList[i]}' to disk $i")
} else { } else {
@@ -50,15 +55,18 @@ class HostFileHSDPA : HSDPA {
*/ */
fun attachHostFile(diskIndex: Int, filePath: String) { fun attachHostFile(diskIndex: Int, filePath: String) {
if (diskIndex < 0 || diskIndex >= MAX_DISKS) return if (diskIndex < 0 || diskIndex >= MAX_DISKS) return
try { try {
// Close existing file if any // Close existing file if any
hostFiles[diskIndex]?.close() hostFileChannels[diskIndex]?.close()
hostFileStreams[diskIndex]?.close()
// Open new file // Open new file
val file = File(filePath) val file = File(filePath)
if (file.exists() && file.isFile) { if (file.exists() && file.isFile) {
hostFiles[diskIndex] = RandomAccessFile(file, "r") val stream = FileInputStream(file)
hostFileStreams[diskIndex] = stream
hostFileChannels[diskIndex] = stream.channel
hostFilePaths[diskIndex] = filePath hostFilePaths[diskIndex] = filePath
println("HSDPA: Attached file '$filePath' to disk $diskIndex") println("HSDPA: Attached file '$filePath' to disk $diskIndex")
} else { } else {
@@ -75,10 +83,12 @@ class HostFileHSDPA : HSDPA {
*/ */
fun detachHostFile(diskIndex: Int) { fun detachHostFile(diskIndex: Int) {
if (diskIndex < 0 || diskIndex >= MAX_DISKS) return if (diskIndex < 0 || diskIndex >= MAX_DISKS) return
try { try {
hostFiles[diskIndex]?.close() hostFileChannels[diskIndex]?.close()
hostFiles[diskIndex] = null hostFileStreams[diskIndex]?.close()
hostFileChannels[diskIndex] = null
hostFileStreams[diskIndex] = null
hostFilePaths[diskIndex] = null hostFilePaths[diskIndex] = null
println("HSDPA: Detached file from disk $diskIndex") println("HSDPA: Detached file from disk $diskIndex")
} catch (e: Exception) { } catch (e: Exception) {
@@ -93,15 +103,15 @@ class HostFileHSDPA : HSDPA {
*/ */
fun getAttachedFileSize(diskIndex: Int): Long { fun getAttachedFileSize(diskIndex: Int): Long {
if (diskIndex < 0 || diskIndex >= MAX_DISKS) return 0L if (diskIndex < 0 || diskIndex >= MAX_DISKS) return 0L
return try { return try {
hostFiles[diskIndex]?.length() ?: 0L hostFileChannels[diskIndex]?.size() ?: 0L
} catch (e: Exception) { } catch (e: Exception) {
0L 0L
} }
} }
override fun sequentialIOSkip(bytes: Int) { override fun sequentialIOSkip(bytes: Long) {
sequentialIOPosition += bytes sequentialIOPosition += bytes
// Clamp position to file bounds if needed // Clamp position to file bounds if needed
val activeDiskIndex = getActiveDiskIndex() 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() val activeDiskIndex = getActiveDiskIndex()
if (activeDiskIndex < 0 || hostFiles[activeDiskIndex] == null) { if (activeDiskIndex < 0 || hostFileChannels[activeDiskIndex] == null) {
// No file attached, just advance position // No file attached, just advance position
sequentialIOPosition += bytes sequentialIOPosition += bytes
return return
} }
// convert Uint24 to Int32 // convert Int24 memory pointer to Int32
val vmMemoryPointer = if (vmMemoryPointer0 and 0x800000 != 0) val vmMemoryPointer = if (vmMemoryPointer0 and 0x800000 != 0L)
(0xFF000000.toInt() or vmMemoryPointer0) (0xFF000000.toInt().toLong() or vmMemoryPointer0)
else else
vmMemoryPointer0 vmMemoryPointer0
try { try {
val file = hostFiles[activeDiskIndex]!! val channel = hostFileChannels[activeDiskIndex]!!
val readPosition = sequentialIOPosition
file.seek(sequentialIOPosition) // Read data using positional read (supports >2GB positions)
val buffer = ByteBuffer.allocate(bytes.toInt())
// Read data into a temporary buffer val bytesRead = channel.read(buffer, sequentialIOPosition)
val readBuffer = ByteArray(bytes)
val bytesRead = file.read(readBuffer)
if (bytesRead > 0) { if (bytesRead > 0) {
buffer.flip()
val readBuffer = ByteArray(bytesRead)
buffer.get(readBuffer)
// Copy data to VM memory // Copy data to VM memory
// Handle negative addresses (backwards addressing) vs positive addresses // Handle negative addresses (backwards addressing) vs positive addresses
if (vmMemoryPointer < 0) { if (vmMemoryPointer < 0) {
// Negative addresses use backwards addressing // Negative addresses use backwards addressing
for (i in 0 until bytesRead) { for (i in 0 until bytesRead) {
vm.poke(vmMemoryPointer - i.toLong(), readBuffer[i]) vm.poke(vmMemoryPointer - i.toLong(), readBuffer[i])
} }
@@ -151,42 +163,43 @@ class HostFileHSDPA : HSDPA {
} }
} }
sequentialIOPosition += bytesRead sequentialIOPosition += bytesRead
} }
// Fill remaining bytes with zeros if we read less than requested // 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) { if (vmMemoryPointer < 0) {
// Negative addresses use backwards addressing // Negative addresses use backwards addressing
for (i in bytesRead until bytes) { for (i in actualBytesRead until bytes.toInt()) {
vm.poke(vmMemoryPointer - i.toLong(), 0) vm.poke(vmMemoryPointer - i.toLong(), 0)
} }
} else { } else {
// Positive addresses use forward addressing // Positive addresses use forward addressing
for (i in bytesRead until bytes) { for (i in actualBytesRead until bytes.toInt()) {
vm.poke(vmMemoryPointer + i.toLong(), 0) vm.poke(vmMemoryPointer + i.toLong(), 0)
} }
} }
sequentialIOPosition += (bytes - bytesRead) sequentialIOPosition += (bytes - actualBytesRead)
} }
} catch (e: Exception) { } catch (e: Exception) {
// Just advance position on error // Just advance position on error
sequentialIOPosition += bytes sequentialIOPosition += bytes
} }
} }
override fun sequentialIOWrite(bytes: Int, vmMemoryPointer0: Int) { override fun sequentialIOWrite(bytes: Long, vmMemoryPointer0: Long) {
val activeDiskIndex = getActiveDiskIndex() val activeDiskIndex = getActiveDiskIndex()
if (activeDiskIndex < 0 || hostFiles[activeDiskIndex] == null) { if (activeDiskIndex < 0 || hostFileChannels[activeDiskIndex] == null) {
// No file attached, just advance position // No file attached, just advance position
sequentialIOPosition += bytes sequentialIOPosition += bytes
return return
} }
// convert Uint24 to Int32 // convert Int24 memory pointer to Int32
val vmMemoryPointer = if (vmMemoryPointer0 and 0x800000 != 0) val vmMemoryPointer = if (vmMemoryPointer0 and 0x800000 != 0L)
(0xFF000000.toInt() or vmMemoryPointer0) (0xFF000000.toInt().toLong() or vmMemoryPointer0)
else else
vmMemoryPointer0 vmMemoryPointer0
@@ -207,11 +220,12 @@ class HostFileHSDPA : HSDPA {
override fun dispose() { override fun dispose() {
super.dispose() super.dispose()
// Close all open files // Close all open files
for (i in 0 until MAX_DISKS) { for (i in 0 until MAX_DISKS) {
try { try {
hostFiles[i]?.close() hostFileChannels[i]?.close()
hostFileStreams[i]?.close()
} catch (e: Exception) { } catch (e: Exception) {
// Ignore errors during cleanup // Ignore errors during cleanup
} }