mirror of
https://github.com/KarolS/millfork.git
synced 2025-01-16 16:31:04 +00:00
Really early and very incomplete ZX Spectrum support
This commit is contained in:
parent
00be0b552e
commit
24ae52e3cc
@ -56,6 +56,8 @@ Read [the Apple 2 programming guide](./apple2-programming-guide.md) for more inf
|
|||||||
|
|
||||||
* `pc88` – NEC PC-88 (very incomplete and not usable for anything yet)
|
* `pc88` – NEC PC-88 (very incomplete and not usable for anything yet)
|
||||||
|
|
||||||
|
* `zxspectrum` – Sinclair ZX Spectrum 48k (very incomplete and not usable for anything yet)
|
||||||
|
|
||||||
The primary and most tested platform is Commodore 64.
|
The primary and most tested platform is Commodore 64.
|
||||||
|
|
||||||
Currently, targets that assume that the program will be loaded from disk or tape are better tested.
|
Currently, targets that assume that the program will be loaded from disk or tape are better tested.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
;DON'T USE THIS
|
||||||
;a single-load PC-88 program
|
;a single-load PC-88 program
|
||||||
[compilation]
|
[compilation]
|
||||||
arch=z80
|
arch=z80
|
||||||
|
20
include/zxspectrum.ini
Normal file
20
include/zxspectrum.ini
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
;DON'T USE THIS
|
||||||
|
;a single-load ZX Spectrum 48k program
|
||||||
|
[compilation]
|
||||||
|
arch=z80
|
||||||
|
modules=default_panic
|
||||||
|
|
||||||
|
[allocation]
|
||||||
|
segments=default,slowram
|
||||||
|
segment_default_start=$8000
|
||||||
|
segment_default_datastart=after_code
|
||||||
|
segment_default_end=$ffff
|
||||||
|
segment_slowram_start=$5ccb
|
||||||
|
segment_slowram_end=$7fff
|
||||||
|
|
||||||
|
[output]
|
||||||
|
style=single
|
||||||
|
format=tap
|
||||||
|
extension=tap
|
||||||
|
|
||||||
|
|
@ -158,6 +158,7 @@ object Platform {
|
|||||||
case "pagecount" => PageCountOutput
|
case "pagecount" => PageCountOutput
|
||||||
case "allocated" => AllocatedDataOutput
|
case "allocated" => AllocatedDataOutput
|
||||||
case "d88" => D88Output
|
case "d88" => D88Output
|
||||||
|
case "tap" => TapOutput
|
||||||
case n => n.split(":").filter(_.nonEmpty) match {
|
case n => n.split(":").filter(_.nonEmpty) match {
|
||||||
case Array(b, s, e) => BankFragmentOutput(b, parseNumber(s), parseNumber(e))
|
case Array(b, s, e) => BankFragmentOutput(b, parseNumber(s), parseNumber(e))
|
||||||
case Array(s, e) => CurrentBankFragmentOutput(parseNumber(s), parseNumber(e))
|
case Array(s, e) => CurrentBankFragmentOutput(parseNumber(s), parseNumber(e))
|
||||||
|
91
src/main/scala/millfork/output/TapOutput.scala
Normal file
91
src/main/scala/millfork/output/TapOutput.scala
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package millfork.output
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object TapOutput extends OutputPackager {
|
||||||
|
|
||||||
|
def isAlphanum(c: Char): Boolean = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')
|
||||||
|
|
||||||
|
override def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
|
||||||
|
val filteredName: String = mem.programName.filter(isAlphanum)
|
||||||
|
val b = mem.banks(bank)
|
||||||
|
val code = b.output.slice(b.start, b.end + 1)
|
||||||
|
val codeDataBlock = new DataBlock(code)
|
||||||
|
val codeHeaderBlock = new HeaderBlock(3, "CODE", code.length, b.start, 32768)
|
||||||
|
val loaderDataBlock = new DataBlock(ZxSpectrumBasic.loader("CODE", filteredName, b.start))
|
||||||
|
val loaderHeaderBlock = new HeaderBlock(0, "LOADER", loaderDataBlock.inputData.length, 10, loaderDataBlock.inputData.length)
|
||||||
|
val result = Array(loaderHeaderBlock, loaderDataBlock, codeHeaderBlock, codeDataBlock).map(_.toArray)
|
||||||
|
result.flatten
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class TapBlock {
|
||||||
|
def rawData: Array[Byte]
|
||||||
|
|
||||||
|
def checksum: Byte = rawData.foldLeft(0)(_ ^ _).toByte
|
||||||
|
|
||||||
|
def toArray: Array[Byte] = Array[Byte](
|
||||||
|
rawData.length.+(1).toByte,
|
||||||
|
rawData.length.+(1).>>(8).toByte
|
||||||
|
) ++ rawData :+ checksum
|
||||||
|
}
|
||||||
|
|
||||||
|
class HeaderBlock(typ: Int, name: String, lengthOfData: Int, param1: Int, param2: Int) extends TapBlock {
|
||||||
|
val rawData: Array[Byte] = Array[Byte](0, typ.toByte) ++ name.take(10).padTo(10, ' ').getBytes(StandardCharsets.US_ASCII) ++ Array[Byte](
|
||||||
|
lengthOfData.toByte,
|
||||||
|
lengthOfData.>>(8).toByte,
|
||||||
|
param1.toByte,
|
||||||
|
param1.>>(8).toByte,
|
||||||
|
param2.toByte,
|
||||||
|
param2.>>(8).toByte,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class DataBlock(val inputData: Array[Byte]) extends TapBlock {
|
||||||
|
val rawData: Array[Byte] = 0xff.toByte +: inputData
|
||||||
|
}
|
||||||
|
|
||||||
|
object ZxSpectrumBasic {
|
||||||
|
|
||||||
|
class Snippet(val array: Array[Byte]) extends AnyVal
|
||||||
|
|
||||||
|
implicit def _implicit_String_to_Snippet(s: String): Snippet = new Snippet(s.getBytes(StandardCharsets.US_ASCII))
|
||||||
|
|
||||||
|
private def token(i: Int) = new Snippet(Array(i.toByte))
|
||||||
|
|
||||||
|
val SCREEN$: Snippet = token(170)
|
||||||
|
val CODE: Snippet = token(175)
|
||||||
|
val VAL: Snippet = token(176)
|
||||||
|
val USR: Snippet = token(192)
|
||||||
|
val INK: Snippet = token(217)
|
||||||
|
val PAPER: Snippet = token(218)
|
||||||
|
val BORDER: Snippet = token(231)
|
||||||
|
val REM: Snippet = token(234)
|
||||||
|
val LOAD: Snippet = token(239)
|
||||||
|
val POKE: Snippet = token(244)
|
||||||
|
val PRINT: Snippet = token(245)
|
||||||
|
val RUN: Snippet = token(247)
|
||||||
|
val RANDOMIZE: Snippet = token(249)
|
||||||
|
val CLS: Snippet = token(251)
|
||||||
|
val CLEAR: Snippet = token(253)
|
||||||
|
|
||||||
|
def line(number: Int, tokens: Snippet*): Array[Byte] = {
|
||||||
|
val content = tokens.flatMap(_.array).toArray
|
||||||
|
Array[Byte](number.>>(8).toByte, number.toByte, (content.length + 1).toByte, (content.length + 1).>>(8).toByte) ++ content :+ 13.toByte
|
||||||
|
}
|
||||||
|
|
||||||
|
private def quoted(a: Any): Snippet = "\"" + a + "\""
|
||||||
|
|
||||||
|
def loader(filename: String, rem: String, loadAddress: Int): Array[Byte] = {
|
||||||
|
Array(
|
||||||
|
line(10, REM, rem),
|
||||||
|
line(20, BORDER, VAL, "\"0\":", INK, VAL, "\"0\":", PAPER, VAL, "\"7\":", CLS),
|
||||||
|
line(30, CLEAR, VAL, quoted(loadAddress - 1)),
|
||||||
|
line(40, LOAD, quoted(filename), CODE),
|
||||||
|
line(50, RANDOMIZE, USR, VAL, quoted(loadAddress))
|
||||||
|
).flatten
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user