Skip to content

Commit

Permalink
Restructure tests and add options
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanschramm committed Dec 19, 2023
1 parent 3df2eb4 commit 0b382bf
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 44 deletions.
50 changes: 37 additions & 13 deletions retroload-lib/src/conversion/ConverterManager.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,46 @@
import {getLocalPathByDirAndFile} from '../Examples.js';
import {BufferAccess} from '../common/BufferAccess.js';
import {type OptionValues} from '../encoding/Options.js';
import {convert} from './ConverterManager.js';
import * as fs from 'fs';

test('Format kctap is converted correctly', () => {
const data = BufferAccess.createFromNodeBuffer(fs.readFileSync(getLocalPathByDirAndFile('kc851_tap', 'rl.com')));
type TestDefinition = {
name: string;
dir: string;
input: string;
expected: string;
options: OptionValues;
};

const result = convert(data, 'kctap', {});
const formatTests: TestDefinition[] = [
{
name: 'ataricas',
dir: 'atari_bin',
input: 'rl.bin',
expected: 'rl.cas',
options: {},
},
{
name: 'kctap',
dir: 'kc851_tap',
input: 'rl.com',
expected: 'rl.tap',
options: {load: '0300', entry: '0300', name: 'RL', kctype: 'COM'},
},
];

const expectedData = BufferAccess.createFromNodeBuffer(fs.readFileSync(getLocalPathByDirAndFile('kc851_tap', 'rl.tap')));
expect(result.asHexDump()).toBe(expectedData.asHexDump());
describe('Formats are converted correctly', () => {
it.each(formatTests.map((t: TestDefinition) => ({label: getTestLabel(t), definition: t})))(
'$label',
(test) => {
const data = BufferAccess.createFromNodeBuffer(fs.readFileSync(getLocalPathByDirAndFile(test.definition.dir, test.definition.input)));
const result = convert(data, test.definition.name, test.definition.options);
const expectedData = BufferAccess.createFromNodeBuffer(fs.readFileSync(getLocalPathByDirAndFile(test.definition.dir, test.definition.expected)));
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());
});
function getTestLabel(test: TestDefinition): string {
return `${test.name}: ${test.dir}/${test.input} --> ${test.dir}/${test.expected}, options: ${JSON.stringify(test.options)}`;
}
72 changes: 51 additions & 21 deletions retroload-lib/src/conversion/converter/AtariCasConverter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
import {BufferAccess} from '../../common/BufferAccess.js';
import {InvalidArgumentError} from '../../common/Exceptions.js';
import {calculateChecksum8WithCarry} from '../../common/Utils.js';
import {type OptionContainer} from '../../encoding/Options.js';
import {type OptionContainer, type ArgumentOptionDefinition} from '../../encoding/Options.js';
import {type ConverterDefinition} from './ConverterDefinition.js';

const irgLengthOption: ArgumentOptionDefinition<number | undefined> = {
name: 'irglength',
label: 'Intra record gap length',
description: 'Gap between blocks in ms (default: 250)',
common: false,
required: false,
type: 'text',
parse(value: string) {
if (value === '') {
return undefined;
}
const casted = Number(value);
if (isNaN(casted)) {
throw new InvalidArgumentError(this.name, `Option ${this.name} is expected to be a number in decimal or hexadecimal notation.`);
}

return casted;
},
};

const definition: ConverterDefinition = {
name: 'Atari .CAS-File',
identifier: 'ataricas',
Expand All @@ -20,7 +41,9 @@ const dataBytesPerBlock = 128;
const pilotIrgLength = 20000;
const defaultIrgLength = 250; // TODO: longer for 'ENTER'-loading

function convert(data: BufferAccess, _options: OptionContainer): BufferAccess {
function convert(data: BufferAccess, options: OptionContainer): BufferAccess {
const irgLength = options.getArgument(irgLengthOption) ?? defaultIrgLength;

const chunks = data.chunks(dataBytesPerBlock);

// FUJI-Header, baud-block, data blocks, end of file block
Expand All @@ -36,36 +59,43 @@ function convert(data: BufferAccess, _options: OptionContainer): BufferAccess {

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);
outBa.writeUint16Le(i === 0 ? pilotIrgLength : irgLength);
outBa.writeBa(createDataBlock(chunk));
}

// end of file block
outBa.writeAsciiString('data');
outBa.writeUint16Le(132);
outBa.writeUint16Le(defaultIrgLength);
outBa.writeUint16Le(irgLength);
outBa.writeBa(createEndBlock());

return outBa;
}

function createDataBlock(data: BufferAccess): BufferAccess {
const dataBlock = BufferAccess.create(132);
dataBlock.writeUint8(markerByte);
dataBlock.writeUint8(markerByte);
const partialBlock = data.length() !== dataBytesPerBlock;
const blockType = partialBlock ? blockTypePartial : blockTypeFull;
dataBlock.writeUint8(blockType);
dataBlock.writeBa(partialBlock ? data.chunksPadded(dataBytesPerBlock, 0x00)[0] : data);
if (partialBlock) {
dataBlock.setUint8(130, data.length());
}
dataBlock.setUint8(131, calculateChecksum8WithCarry(dataBlock));

return dataBlock;
}

function createEndBlock(): BufferAccess {
const endBlock = BufferAccess.create(132);
endBlock.writeUint8(markerByte);
endBlock.writeUint8(markerByte);
endBlock.writeUint8(blockTypeEndOfFile);
endBlock.setUint8(131, calculateChecksum8WithCarry(endBlock));
outBa.writeBa(endBlock);

return outBa;
return endBlock;
}
53 changes: 43 additions & 10 deletions retroload-lib/src/conversion/converter/KcTapConverter.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,59 @@
import {BufferAccess} from '../../common/BufferAccess.js';
import {type OptionContainer} from '../../encoding/Options.js';
import {InvalidArgumentError} from '../../common/Exceptions.js';
import {type ArgumentOptionDefinition, entryOption, loadOption, nameOption, type OptionContainer} from '../../encoding/Options.js';
import {type ConverterDefinition} from './ConverterDefinition.js';

const kctypeOption: ArgumentOptionDefinition<string | undefined> = {
name: 'kctype',
label: 'File type',
description: 'File type, 3 characters (default: COM)',
common: false,
required: false,
type: 'text',
parse(value: string) {
if (value === '') {
return undefined;
}
if (value.length !== 3) {
throw new InvalidArgumentError(this.name, `Option ${this.name} is expected have exactly 3 characters (example: COM).`);
}

return value;
},
};

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

function convert(data: BufferAccess, _options: OptionContainer): BufferAccess {
const maxFileNameLength = 8;

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 loadAddress = options.getArgument(loadOption) ?? 0x0300;
const entryAddress = options.getArgument(entryOption) ?? 0xffff; // default: no auto start
const filename = options.getArgument(nameOption) ?? '';
const filetype = options.getArgument(kctypeOption) ?? 'COM';
if (filename.length > maxFileNameLength) {
throw new InvalidArgumentError('name', `Maximum length of filename (${maxFileNameLength}) exceeded.`);
}

const header = createHeader(
'RL',
'COM',
0x0300,
0x0300 + data.length() - 1,
0x0300,
filename,
filetype,
loadAddress,
loadAddress + data.length() - 1,
entryAddress,
);

// TAP header
Expand All @@ -43,7 +76,7 @@ function convert(data: BufferAccess, _options: OptionContainer): BufferAccess {
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(name, maxFileNameLength, 0x00);
header.writeAsciiString(fileType);
header.writeUint8(0x00); // reserved
header.writeUint8(0x00); // reserved
Expand Down

0 comments on commit 0b382bf

Please sign in to comment.