Skip to content

Commit 3b2f12b

Browse files
committed
Implement KC COM to TAP converter
1 parent 32ca262 commit 3b2f12b

File tree

6 files changed

+108
-0
lines changed

6 files changed

+108
-0
lines changed

retroload-lib/src/Examples.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,10 @@ export function getLocalPath(example: ExampleDefinition): string {
457457
return `${__dirname}/../examples/formats/${example.dir}/${example.file}`;
458458
}
459459

460+
export function getLocalPathByDirAndFile(dir: string, file: string): string {
461+
return `${__dirname}/../examples/formats/${dir}/${file}`;
462+
}
463+
460464
export function getUrl(example: ExampleDefinition): string {
461465
return `https://github.com/stefanschramm/retroload/tree/main/retroload-lib/examples/formats/${example.dir}/${example.file}`;
462466
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {getLocalPathByDirAndFile} from '../Examples.js';
2+
import {BufferAccess} from '../common/BufferAccess.js';
3+
import {convert} from './ConverterManager.js';
4+
import * as fs from 'fs';
5+
6+
test('ConverterManager calls convert function of ConverterDefinition', () => {
7+
const data = BufferAccess.createFromNodeBuffer(fs.readFileSync(getLocalPathByDirAndFile('kc851_tap', 'rl.com')));
8+
9+
const result = convert(data, 'kctap', {});
10+
11+
const expectedData = BufferAccess.createFromNodeBuffer(fs.readFileSync(getLocalPathByDirAndFile('kc851_tap', 'rl.tap')));
12+
expect(result.asHexDump()).toBe(expectedData.asHexDump());
13+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {type BufferAccess} from '../common/BufferAccess.js';
2+
import {FormatNotFoundError} from '../common/Exceptions.js';
3+
import {OptionContainer, type OptionValues} from '../encoding/Options.js';
4+
import {converters} from './ConverterProvider.js';
5+
import {type ConverterDefinition} from './converter/ConverterDefinition.js';
6+
7+
export function convert(data: BufferAccess, identifier: string, options: OptionValues): BufferAccess {
8+
const chosenConverters = converters.filter((c: ConverterDefinition) => c.identifier === identifier);
9+
10+
if (chosenConverters.length === 0) {
11+
throw new FormatNotFoundError(identifier);
12+
}
13+
14+
const converter = chosenConverters[0];
15+
16+
return converter.convert(data, new OptionContainer(options));
17+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import {type ConverterDefinition} from './converter/ConverterDefinition.js';
2+
import KcTapConverter from './converter/KcTapConverter.js';
3+
4+
export const converters: ConverterDefinition[] = [
5+
KcTapConverter,
6+
];
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import {type BufferAccess} from '../../common/BufferAccess.js';
2+
import {type OptionContainer, type PublicOptionDefinition} from '../../encoding/Options.js';
3+
4+
export type ConverterDefinition = {
5+
readonly name: string;
6+
readonly identifier: string;
7+
readonly options: PublicOptionDefinition[];
8+
readonly convert: (ba: BufferAccess, options: OptionContainer) => BufferAccess;
9+
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {BufferAccess} from '../../common/BufferAccess.js';
2+
import {type OptionContainer} from '../../encoding/Options.js';
3+
import {type ConverterDefinition} from './ConverterDefinition.js';
4+
5+
const definition: ConverterDefinition = {
6+
name: 'KC .TAP-File',
7+
identifier: 'kctap',
8+
options: [],
9+
convert,
10+
};
11+
export default definition;
12+
13+
function convert(data: BufferAccess, _options: OptionContainer): BufferAccess {
14+
const blocks = data.chunksPadded(128, 0x00);
15+
const outBa = BufferAccess.create(16 + (1 + blocks.length) * 129);
16+
17+
// TODO: use options
18+
const header = createHeader(
19+
'RL',
20+
'COM',
21+
0x0300,
22+
0x0300 + data.length() - 1,
23+
0x0300,
24+
);
25+
26+
// TAP header
27+
outBa.writeUint8(0xc3);
28+
outBa.writeAsciiString('KC-TAPE by AF. ');
29+
30+
// header block (FCB)
31+
outBa.writeUint8(0);
32+
outBa.writeBa(header);
33+
34+
// data blocks
35+
for (let i = 0; i < blocks.length; i++) {
36+
outBa.writeUint8(i === blocks.length - 1 ? 0xff : i + 1);
37+
outBa.writeBa(blocks[i]);
38+
}
39+
40+
return outBa;
41+
}
42+
43+
function createHeader(name: string, fileType: string, loadAddress: number, endAddress: number, startAddress: number): BufferAccess {
44+
const header = BufferAccess.create(128);
45+
46+
header.writeAsciiString(name, 8, 0x00);
47+
header.writeAsciiString(fileType);
48+
header.writeUint8(0x00); // reserved
49+
header.writeUint8(0x00); // reserved
50+
header.writeUint16Le(0x0000); // PSUM (block checksum(?))
51+
header.writeUint8(0x00); // ARB (internal working cell(?))
52+
header.writeUint8(0x03); // BLNR (block number(?)) (count?)
53+
header.writeUint16Le(loadAddress); // AADR
54+
header.writeUint16Le(endAddress); // EADR
55+
header.writeUint16Le(startAddress); // SADR
56+
header.writeUint8(0x00); // SBY (protection byte)
57+
58+
return header;
59+
}

0 commit comments

Comments
 (0)