diff --git a/retroload-lib/examples/formats/atari_cas/Makefile b/retroload-lib/examples/formats/atari_bin/Makefile similarity index 75% rename from retroload-lib/examples/formats/atari_cas/Makefile rename to retroload-lib/examples/formats/atari_bin/Makefile index 38a2b1c..81350bd 100644 --- a/retroload-lib/examples/formats/atari_cas/Makefile +++ b/retroload-lib/examples/formats/atari_bin/Makefile @@ -1,6 +1,6 @@ include ../Makefile.inc -all: rl.cas +all: rl.bin rl.cas # https://atari800.github.io/ run: rl.cas @@ -10,11 +10,11 @@ run: rl.cas retroload $< -o $@ # https://a8cas.sourceforge.net/ -%.cas: %.img +%.cas: %.bin a8cas-convert -f c -r $< $@ -%.img: %.asm +%.bin: %.asm $(65XX_ASM) $< -o $@ clean: - rm -f *.wav *.cas *.img + rm -f *.wav *.cas *.bin diff --git a/retroload-lib/examples/formats/atari_cas/rl.asm b/retroload-lib/examples/formats/atari_bin/rl.asm similarity index 100% rename from retroload-lib/examples/formats/atari_cas/rl.asm rename to retroload-lib/examples/formats/atari_bin/rl.asm diff --git a/retroload-lib/examples/formats/atari_bin/rl.bin b/retroload-lib/examples/formats/atari_bin/rl.bin new file mode 100644 index 0000000..e6bbda0 Binary files /dev/null and b/retroload-lib/examples/formats/atari_bin/rl.bin differ diff --git a/retroload-lib/examples/formats/atari_cas/rl.cas b/retroload-lib/examples/formats/atari_bin/rl.cas similarity index 100% rename from retroload-lib/examples/formats/atari_cas/rl.cas rename to retroload-lib/examples/formats/atari_bin/rl.cas diff --git a/retroload-lib/src/Examples.ts b/retroload-lib/src/Examples.ts index b2ee920..f11bf51 100644 --- a/retroload-lib/src/Examples.ts +++ b/retroload-lib/src/Examples.ts @@ -46,7 +46,7 @@ const examples: ExampleDefinition[] = [ }, // Atari 800 XL { - dir: 'atari_cas', + dir: 'atari_bin', file: 'rl.cas', options: {}, hash: '8d36a2a696c7e27807c4d1f058fdec34', diff --git a/retroload-lib/src/common/Utils.ts b/retroload-lib/src/common/Utils.ts index f030534..1a7a16f 100644 --- a/retroload-lib/src/common/Utils.ts +++ b/retroload-lib/src/common/Utils.ts @@ -27,6 +27,19 @@ export function calculateChecksum8Xor(ba: BufferAccess, initial = 0x00) { return sum; } +export function calculateChecksum8WithCarry(ba: BufferAccess) { + // 8 bit checksum with carry being added + let sum = 0; + for (let i = 0; i < ba.length(); i++) { + sum += ba.getUint8(i); + if (sum > 255) { + sum = (sum & 0xff) + 1; + } + } + + return sum; +} + /** * https://gist.github.com/chitchcock/5112270?permalink_comment_id=3834064#gistcomment-3834064 * diff --git a/retroload-lib/src/conversion/ConverterManager.test.ts b/retroload-lib/src/conversion/ConverterManager.test.ts index 292ef10..e5bd7a3 100644 --- a/retroload-lib/src/conversion/ConverterManager.test.ts +++ b/retroload-lib/src/conversion/ConverterManager.test.ts @@ -3,7 +3,7 @@ import {BufferAccess} from '../common/BufferAccess.js'; import {convert} from './ConverterManager.js'; import * as fs from 'fs'; -test('ConverterManager calls convert function of ConverterDefinition', () => { +test('Format kctap is converted correctly', () => { const data = BufferAccess.createFromNodeBuffer(fs.readFileSync(getLocalPathByDirAndFile('kc851_tap', 'rl.com'))); const result = convert(data, 'kctap', {}); @@ -11,3 +11,12 @@ test('ConverterManager calls convert function of ConverterDefinition', () => { const expectedData = BufferAccess.createFromNodeBuffer(fs.readFileSync(getLocalPathByDirAndFile('kc851_tap', 'rl.tap'))); expect(result.asHexDump()).toBe(expectedData.asHexDump()); }); + +test('Format ataricas is converted correctly', () => { + const data = BufferAccess.createFromNodeBuffer(fs.readFileSync(getLocalPathByDirAndFile('atari_bin', 'rl.bin'))); + + const result = convert(data, 'ataricas', {}); + + const expectedData = BufferAccess.createFromNodeBuffer(fs.readFileSync(getLocalPathByDirAndFile('atari_bin', 'rl.cas'))); + expect(result.asHexDump()).toBe(expectedData.asHexDump()); +}); diff --git a/retroload-lib/src/conversion/ConverterProvider.ts b/retroload-lib/src/conversion/ConverterProvider.ts index 829f566..9aeb97c 100644 --- a/retroload-lib/src/conversion/ConverterProvider.ts +++ b/retroload-lib/src/conversion/ConverterProvider.ts @@ -1,6 +1,8 @@ import {type ConverterDefinition} from './converter/ConverterDefinition.js'; +import AtariCasConverter from './converter/AtariCasConverter.js'; import KcTapConverter from './converter/KcTapConverter.js'; export const converters: ConverterDefinition[] = [ + AtariCasConverter, KcTapConverter, ]; diff --git a/retroload-lib/src/conversion/converter/AtariCasConverter.ts b/retroload-lib/src/conversion/converter/AtariCasConverter.ts new file mode 100644 index 0000000..9a851d2 --- /dev/null +++ b/retroload-lib/src/conversion/converter/AtariCasConverter.ts @@ -0,0 +1,71 @@ +import {BufferAccess} from '../../common/BufferAccess.js'; +import {calculateChecksum8WithCarry} from '../../common/Utils.js'; +import {type OptionContainer} from '../../encoding/Options.js'; +import {type ConverterDefinition} from './ConverterDefinition.js'; + +const definition: ConverterDefinition = { + name: 'Atari .CAS-File', + identifier: 'ataricas', + options: [], + convert, +}; +export default definition; + +const markerByte = 0x55; +const blockTypeFull = 0xfc; +const blockTypePartial = 0xfa; +const blockTypeEndOfFile = 0xfe; +const dataBytesPerBlock = 128; + +const pilotIrgLength = 20000; +const defaultIrgLength = 250; // TODO: longer for 'ENTER'-loading + +function convert(data: BufferAccess, _options: OptionContainer): BufferAccess { + const chunks = data.chunks(dataBytesPerBlock); + + // FUJI-Header, baud-block, data blocks, end of file block + const outBa = BufferAccess.create(8 + 8 + (chunks.length + 1) * (8 + 132)); + + outBa.writeAsciiString('FUJI'); + outBa.writeUint16Le(0x0000); + outBa.writeUint16Le(0x0000); + + outBa.writeAsciiString('baud'); + outBa.writeUint16Le(0x0000); + outBa.writeUint16Le(600); // default baud rate + + for (let i = 0; i < chunks.length; i++) { + const chunk = chunks[i]; + + outBa.writeAsciiString('data'); + outBa.writeUint16Le(132); + outBa.writeUint16Le(i === 0 ? pilotIrgLength : defaultIrgLength); + + const blockBa = BufferAccess.create(132); + blockBa.writeUint8(markerByte); + blockBa.writeUint8(markerByte); + const partialBlock = chunk.length() !== dataBytesPerBlock; + const blockType = partialBlock ? blockTypePartial : blockTypeFull; + blockBa.writeUint8(blockType); + blockBa.writeBa(partialBlock ? chunk.chunksPadded(dataBytesPerBlock, 0x00)[0] : chunk); + if (partialBlock) { + blockBa.setUint8(130, chunk.length()); + } + blockBa.setUint8(131, calculateChecksum8WithCarry(blockBa)); + + outBa.writeBa(blockBa); + } + + // end of file block + outBa.writeAsciiString('data'); + outBa.writeUint16Le(132); + outBa.writeUint16Le(defaultIrgLength); + const endBlock = BufferAccess.create(132); + endBlock.writeUint8(markerByte); + endBlock.writeUint8(markerByte); + endBlock.writeUint8(blockTypeEndOfFile); + endBlock.setUint8(131, calculateChecksum8WithCarry(endBlock)); + outBa.writeBa(endBlock); + + return outBa; +} diff --git a/retroload-lib/src/encoding/adapter/atari/AtariGenericAdapter.ts b/retroload-lib/src/encoding/adapter/atari/AtariGenericAdapter.ts index 6646861..e0f3e55 100644 --- a/retroload-lib/src/encoding/adapter/atari/AtariGenericAdapter.ts +++ b/retroload-lib/src/encoding/adapter/atari/AtariGenericAdapter.ts @@ -4,6 +4,7 @@ import {type OptionContainer} from '../../Options.js'; import {type RecorderInterface} from '../../recorder/RecorderInterface.js'; import {unidentifiable, type FormatIdentification} from '../AdapterDefinition.js'; import {type AdapterDefinition} from '../AdapterDefinition.js'; +import {calculateChecksum8WithCarry} from '../../../common/Utils.js'; const definition: AdapterDefinition = { name: 'Atari (Generic data)', @@ -45,7 +46,7 @@ function encode(recorder: RecorderInterface, ba: BufferAccess, _options: OptionC blockBa.setUint8(130, chunkBa.length()); } - blockBa.setUint8(131, calculateChecksum(blockBa)); + blockBa.setUint8(131, calculateChecksum8WithCarry(blockBa)); e.recordIrg((blockId === 0) ? pilotIrgLength : defaultIrgLength); // TODO: create option (longer values are required for "ENTER-loading") e.recordBytes(blockBa); } @@ -55,20 +56,8 @@ function encode(recorder: RecorderInterface, ba: BufferAccess, _options: OptionC eofBlockBa.writeUint8(markerByte); eofBlockBa.writeUint8(markerByte); eofBlockBa.writeUint8(blockTypeEndOfFile); - eofBlockBa.setUint8(131, calculateChecksum(eofBlockBa)); + eofBlockBa.setUint8(131, calculateChecksum8WithCarry(eofBlockBa)); e.recordIrg(defaultIrgLength); // TODO: create option (longer values are required for "ENTER-loading") e.recordBytes(eofBlockBa); } -function calculateChecksum(ba: BufferAccess) { - // 8 bit checksum with carry being added - let sum = 0; - for (let i = 0; i < ba.length(); i++) { - sum += ba.getUint8(i); - if (sum > 255) { - sum = (sum & 0xff) + 1; - } - } - - return sum; -}