Skip to content

Commit

Permalink
Develop (#108)
Browse files Browse the repository at this point in the history
* #106 Support for CTM 9 format
* #106 Prepare for 1.7.6 release
  • Loading branch information
maciejmalecki authored Oct 6, 2023
1 parent 46c81f2 commit 2d0af98
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGES.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
= Change log

1.7.6::
* `Charpad`: Support for CTM 9 file format.

1.7.5::
* Possibility to set BIN or PRG as output format from KickAssembler.

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ plugins {
allprojects {

group = "com.github.c64lib"
version = "1.7.5"
version = "1.7.6"

if (project.hasProperty(tagPropertyName)) {
version = project.property(tagPropertyName) ?: version
Expand Down
4 changes: 2 additions & 2 deletions doc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
:docinfo:
:actualKickAssVersion: 5.25
:actualDockerVersion: 0.1.7
:actualPluginVersion: 1.6.0
:actualPluginVersion: 1.7.6

Retro Build Tool brings automation to the C64 software building process.
It is implemented as a Gradle plugin.
Expand Down Expand Up @@ -422,7 +422,7 @@ Retro assembler build tool supports Charpad CTM file format and provides export
* maps,
* CTM file meta data.

Currently supported versions of CTM file are 5, 6, 7 and 8.
Currently supported versions of CTM file are 5, 6, 7, 8 and 9.

Charpad processor is a part of preprocessor and is executed before assembling process is started.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import com.github.c64lib.rbt.processors.charpad.domain.TileTagsProducer
import com.github.c64lib.rbt.processors.charpad.usecase.post6.CTM6Processor
import com.github.c64lib.rbt.processors.charpad.usecase.post6.CTM7Processor
import com.github.c64lib.rbt.processors.charpad.usecase.post6.CTM8Processor
import com.github.c64lib.rbt.processors.charpad.usecase.post6.CTM9Processor
import com.github.c64lib.rbt.processors.charpad.usecase.pre6.CTM5Processor
import com.github.c64lib.rbt.shared.processor.InputByteStream
import com.github.c64lib.rbt.shared.processor.OutputProducer
Expand Down Expand Up @@ -117,6 +118,7 @@ class ProcessCharpadUseCase(
7 -> CTM7Processor(this@ProcessCharpadUseCase)
8,
82 -> CTM8Processor(this@ProcessCharpadUseCase, version, ctm8PrototypeCompatibility)
9 -> CTM9Processor(this@ProcessCharpadUseCase)
else -> throw InvalidCTMFormatException("Unsupported version: $version")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
/*
MIT License
Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library
Copyright (c) 2018-2023 Maciej Małecki
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 com.github.c64lib.rbt.processors.charpad.usecase.post6

import com.github.c64lib.rbt.processors.charpad.domain.CTMHeader
import com.github.c64lib.rbt.processors.charpad.domain.ColouringMethod
import com.github.c64lib.rbt.processors.charpad.domain.Dimensions
import com.github.c64lib.rbt.processors.charpad.domain.InvalidCTMFormatException
import com.github.c64lib.rbt.processors.charpad.domain.ScreenMode
import com.github.c64lib.rbt.processors.charpad.domain.colouringMethodFrom
import com.github.c64lib.rbt.processors.charpad.domain.screenModeFrom
import com.github.c64lib.rbt.processors.charpad.usecase.ProcessCharpadUseCase
import com.github.c64lib.rbt.shared.binutils.combineNybbles
import com.github.c64lib.rbt.shared.binutils.convertToHiNybbles
import com.github.c64lib.rbt.shared.binutils.isolateEachNth
import com.github.c64lib.rbt.shared.binutils.toUnsignedByte
import com.github.c64lib.rbt.shared.processor.InputByteStream
import kotlin.experimental.and

internal class CTM9Processor(charpadProcessor: ProcessCharpadUseCase) :
BlockBasedCTMProcessor(charpadProcessor) {

override fun process(inputByteStream: InputByteStream) {

blockCounter = 0

val header = readHeader(inputByteStream)
val colouringMethod = colouringMethodFrom(header.colouringMethod)
val screenMode = screenModeFrom(header.displayMode)
val (lo, hi) = screenMode.getPaletteIndexes()
val primaryColorIndex = screenMode.getPrimaryColorIndex()

val coloursSize =
when (screenMode) {
ScreenMode.BitmapHires -> 2
ScreenMode.BitmapMulticolor -> 3
else -> 1
}

// block 0 charset
val numChars = processCharsetBlock(inputByteStream)

// block 1 char materials
val charMaterialData = processCharsetMaterialsBlock(numChars, inputByteStream)

if (colouringMethod == ColouringMethod.PerChar) {
// block 2 char colours
readBlockMarker(inputByteStream)
if (numChars > 0) {
val charColoursData = inputByteStream.read(numChars * coloursSize)
processCharpadUseCase.processCharColours {
it.write(isolateEachNth(charColoursData, coloursSize, primaryColorIndex))
}
processCharpadUseCase.processCharScreenColours {
it.write(
combineNybbles(
isolateEachNth(charColoursData, coloursSize, lo),
isolateEachNth(charColoursData, coloursSize, hi),
),
)
}
if (charMaterialData != null) {
processCharpadUseCase.processCharAttributes {
it.write(
combineNybbles(
isolateEachNth(charColoursData, coloursSize, primaryColorIndex),
charMaterialData,
),
)
}
}
}
} else if (charMaterialData != null) {
processCharpadUseCase.processCharAttributes { it.write(convertToHiNybbles(charMaterialData)) }
}

var tileWidth: Byte? = null
var tileHeight: Byte? = null

if (header.flags and CTM8Flags.TileSys.bit != 0.toByte()) {
// block n tiles
val (numTiles, width, height) = processTilesBlock(inputByteStream)
tileWidth = width
tileHeight = height
ensureTileSizeLimits(tileWidth, tileHeight)

if (colouringMethodFrom(header.colouringMethod) == ColouringMethod.PerTile) {
// block n tile colours
readBlockMarker(inputByteStream)
val tileColoursData = inputByteStream.read(numTiles * coloursSize)
processCharpadUseCase.processTileColours {
it.write(isolateEachNth(tileColoursData, coloursSize, primaryColorIndex))
}
processCharpadUseCase.processTileScreenColours {
it.write(
combineNybbles(
isolateEachNth(tileColoursData, coloursSize, lo),
isolateEachNth(tileColoursData, coloursSize, hi),
),
)
}
}

// block n tile tags
processTilesTagsBlock(numTiles, inputByteStream)
// block n tile names
processTilesNamesBlock(numTiles, inputByteStream)
}

// block n map
val (mapWidth, mapHeight) = processMapBlock(inputByteStream)

// all data here, process header
processCharpadUseCase.processHeader {
it.write(
header.toHeader(tileWidth, tileHeight, mapWidth, mapHeight),
)
}
}

private fun ensureTileSizeLimits(tileWidth: Byte, tileHeight: Byte) {
val limit = 10
if (tileWidth > limit) {
throw InvalidCTMFormatException("Tile width too big: $tileWidth.")
}
if (tileHeight > limit) {
throw InvalidCTMFormatException("Tile height too big: $tileHeight.")
}
}

private fun readHeader(inputByteStream: InputByteStream): CTM9Header {
val displayMode = inputByteStream.readByte()
val colouringMethod = inputByteStream.readByte()
val flags = inputByteStream.readByte()

val flexigridWidth = inputByteStream.readByte() + 256 * inputByteStream.readByte()
val flexigridHeight = inputByteStream.readByte() + 256 * inputByteStream.readByte()
inputByteStream.readByte() // ignore

val screenColor = inputByteStream.readByte()
val multicolor1 = inputByteStream.readByte()
val multicolor2 = inputByteStream.readByte()
val backgroundColor4 = inputByteStream.readByte()
val colorBase0 = inputByteStream.readByte()
val colorBase1 = inputByteStream.readByte()
val colorBase2 = inputByteStream.readByte()
val colorBase3: Byte = 0

return CTM9Header(
displayMode = displayMode,
screenColour = screenColor,
multicolour1 = multicolor1,
multicolour2 = multicolor2,
backgroundColour4 = backgroundColor4,
charColour0 = colorBase0,
charColour1 = colorBase1,
charColour2 = colorBase2,
charColour3 = colorBase3,
colouringMethod = colouringMethod,
flags = flags,
flexigridWidth = flexigridWidth,
flexigridHeight = flexigridHeight,
)
}

private fun ScreenMode.getPaletteIndexes(): ScreenMemoryPalette =
when (this) {
ScreenMode.TextHires,
ScreenMode.TextMulticolor,
ScreenMode.TextExtendedBackground -> ScreenMemoryPalette(0, 0)
ScreenMode.BitmapHires -> ScreenMemoryPalette(0, 1)
ScreenMode.BitmapMulticolor -> ScreenMemoryPalette(1, 2)
}

private fun ScreenMode.getPrimaryColorIndex(): Int = 0
}

internal enum class CTM9Flags(val bit: Byte) {
TileSys(0x01)
}

internal data class CTM9Header(
val displayMode: Byte,
val screenColour: Byte,
val multicolour1: Byte,
val multicolour2: Byte,
val backgroundColour4: Byte,
val charColour0: Byte,
val charColour1: Byte,
val charColour2: Byte,
val charColour3: Byte,
val colouringMethod: Byte,
val flags: Byte,
val flexigridWidth: Int,
val flexigridHeight: Int,
) {

fun toHeader(tileWidth: Byte?, tileHeight: Byte?, mapWidth: Int, mapHeight: Int): CTMHeader =
CTMHeader(
version = 9,
backgroundColour0 = screenColour,
backgroundColour1 = multicolour1,
backgroundColour2 = multicolour2,
backgroundColour3 = backgroundColour4,
charColour = charColour3,
colouringMethod = colouringMethodFrom(colouringMethod),
screenMode = screenModeFrom(displayMode),
tileDimensions =
if (flags and CTM7Flags.TileSys.bit != 0.toUnsignedByte()) {
Dimensions(tileWidth!!, tileHeight!!)
} else {
null
},
mapDimensions = Dimensions(mapWidth, mapHeight),
)
}

0 comments on commit 2d0af98

Please sign in to comment.