Skip to content

Commit

Permalink
Implement KC COM to TAP converter
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanschramm committed Dec 17, 2023
1 parent 32ca262 commit 3b2f12b
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 0 deletions.
4 changes: 4 additions & 0 deletions retroload-lib/src/Examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,10 @@ export function getLocalPath(example: ExampleDefinition): string {
return `${__dirname}/../examples/formats/${example.dir}/${example.file}`;
}

export function getLocalPathByDirAndFile(dir: string, file: string): string {
return `${__dirname}/../examples/formats/${dir}/${file}`;
}

export function getUrl(example: ExampleDefinition): string {
return `https://github.com/stefanschramm/retroload/tree/main/retroload-lib/examples/formats/${example.dir}/${example.file}`;
}
13 changes: 13 additions & 0 deletions retroload-lib/src/conversion/ConverterManager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {getLocalPathByDirAndFile} from '../Examples.js';
import {BufferAccess} from '../common/BufferAccess.js';
import {convert} from './ConverterManager.js';
import * as fs from 'fs';

test('ConverterManager calls convert function of ConverterDefinition', () => {
const data = BufferAccess.createFromNodeBuffer(fs.readFileSync(getLocalPathByDirAndFile('kc851_tap', 'rl.com')));

const result = convert(data, 'kctap', {});

const expectedData = BufferAccess.createFromNodeBuffer(fs.readFileSync(getLocalPathByDirAndFile('kc851_tap', 'rl.tap')));
expect(result.asHexDump()).toBe(expectedData.asHexDump());
});
17 changes: 17 additions & 0 deletions retroload-lib/src/conversion/ConverterManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {type BufferAccess} from '../common/BufferAccess.js';
import {FormatNotFoundError} from '../common/Exceptions.js';
import {OptionContainer, type OptionValues} from '../encoding/Options.js';
import {converters} from './ConverterProvider.js';
import {type ConverterDefinition} from './converter/ConverterDefinition.js';

export function convert(data: BufferAccess, identifier: string, options: OptionValues): BufferAccess {
const chosenConverters = converters.filter((c: ConverterDefinition) => c.identifier === identifier);

if (chosenConverters.length === 0) {
throw new FormatNotFoundError(identifier);
}

const converter = chosenConverters[0];

return converter.convert(data, new OptionContainer(options));
}
6 changes: 6 additions & 0 deletions retroload-lib/src/conversion/ConverterProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {type ConverterDefinition} from './converter/ConverterDefinition.js';
import KcTapConverter from './converter/KcTapConverter.js';

export const converters: ConverterDefinition[] = [
KcTapConverter,
];
9 changes: 9 additions & 0 deletions retroload-lib/src/conversion/converter/ConverterDefinition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {type BufferAccess} from '../../common/BufferAccess.js';
import {type OptionContainer, type PublicOptionDefinition} from '../../encoding/Options.js';

export type ConverterDefinition = {
readonly name: string;
readonly identifier: string;
readonly options: PublicOptionDefinition[];
readonly convert: (ba: BufferAccess, options: OptionContainer) => BufferAccess;
};
59 changes: 59 additions & 0 deletions retroload-lib/src/conversion/converter/KcTapConverter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {BufferAccess} from '../../common/BufferAccess.js';
import {type OptionContainer} from '../../encoding/Options.js';
import {type ConverterDefinition} from './ConverterDefinition.js';

const definition: ConverterDefinition = {
name: 'KC .TAP-File',
identifier: 'kctap',
options: [],
convert,
};
export default definition;

function convert(data: BufferAccess, _options: OptionContainer): BufferAccess {
const blocks = data.chunksPadded(128, 0x00);
const outBa = BufferAccess.create(16 + (1 + blocks.length) * 129);

// TODO: use options
const header = createHeader(
'RL',
'COM',
0x0300,
0x0300 + data.length() - 1,
0x0300,
);

// TAP header
outBa.writeUint8(0xc3);
outBa.writeAsciiString('KC-TAPE by AF. ');

// header block (FCB)
outBa.writeUint8(0);
outBa.writeBa(header);

// data blocks
for (let i = 0; i < blocks.length; i++) {
outBa.writeUint8(i === blocks.length - 1 ? 0xff : i + 1);
outBa.writeBa(blocks[i]);
}

return outBa;
}

function createHeader(name: string, fileType: string, loadAddress: number, endAddress: number, startAddress: number): BufferAccess {
const header = BufferAccess.create(128);

header.writeAsciiString(name, 8, 0x00);
header.writeAsciiString(fileType);
header.writeUint8(0x00); // reserved
header.writeUint8(0x00); // reserved
header.writeUint16Le(0x0000); // PSUM (block checksum(?))
header.writeUint8(0x00); // ARB (internal working cell(?))
header.writeUint8(0x03); // BLNR (block number(?)) (count?)
header.writeUint16Le(loadAddress); // AADR
header.writeUint16Le(endAddress); // EADR
header.writeUint16Le(startAddress); // SADR
header.writeUint8(0x00); // SBY (protection byte)

return header;
}

0 comments on commit 3b2f12b

Please sign in to comment.