Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/platforms/tec1g/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,21 @@
}
}
const sdSpi = sdEnabled
? new SdSpi({ highCapacity: sdHighCapacity, ...(sdImage ? { image: sdImage } : {}) })
? new SdSpi({
highCapacity: sdHighCapacity,
...(sdImage ? { image: sdImage } : {}),
...(sdImagePath && sdImage

Check failure on line 371 in src/platforms/tec1g/runtime.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest)

Unexpected nullable string value in conditional. Please handle the nullish/empty cases explicitly

Check failure on line 371 in src/platforms/tec1g/runtime.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

Unexpected nullable string value in conditional. Please handle the nullish/empty cases explicitly
? {
onWrite: (image) => {

Check failure on line 373 in src/platforms/tec1g/runtime.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest)

Missing return type on function

Check failure on line 373 in src/platforms/tec1g/runtime.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

Missing return type on function
try {
fs.writeFileSync(sdImagePath, image);
} catch {
// Ignore persistence failures; runtime continues with in-memory image.
}
},
}
: {}),
})
: null;
let cartridgePresentDefault = config.cartridgeHex !== undefined;
const state: Tec1gState = {
Expand Down
8 changes: 8 additions & 0 deletions src/platforms/tec1g/sd-spi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type SdSpiOptions = {
csActiveLow?: boolean;
highCapacity?: boolean;
image?: Uint8Array;
onWrite?: (image: Uint8Array) => void;
};

const MOSI_BIT = 0x01;
Expand All @@ -37,6 +38,7 @@ export class SdSpi {
private lastCommand: SdCommand | undefined;
private outputQueue: number[] = [];
private image: Uint8Array | null = null;
private onWrite?: (image: Uint8Array) => void;
private pendingResponse: number[] | null = null;
private delayBytes = 0;
private appCommand = false;
Expand All @@ -60,6 +62,9 @@ export class SdSpi {
this.csActiveLow = options.csActiveLow !== false;
this.highCapacity = options.highCapacity !== false;
this.image = options.image ?? null;
if (options.onWrite !== undefined) {
this.onWrite = options.onWrite;
}
}

/**
Expand Down Expand Up @@ -355,6 +360,9 @@ export class SdSpi {
this.image[idx] = this.writeState.buffer[i] ?? 0x00;
}
}
if (this.onWrite) {
this.onWrite(this.image);
}
}
// Data response token: 0bxxx00101 = 0x05 (accepted).
this.pendingResponse = [0x05, 0xff];
Expand Down
3 changes: 3 additions & 0 deletions tests/platforms/tec1g/sd-spi-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
rtcEnabled: false,
sdEnabled: true,
sdHighCapacity: true,
...(sdImagePath ? { sdImagePath } : {}),

Check failure on line 40 in tests/platforms/tec1g/sd-spi-runtime.test.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest)

Unexpected nullable string value in conditional. Please handle the nullish/empty cases explicitly

Check failure on line 40 in tests/platforms/tec1g/sd-spi-runtime.test.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

Unexpected nullable string value in conditional. Please handle the nullish/empty cases explicitly
};
return { rt: createTec1gRuntime(config, () => {}), sdImagePath };
}
Expand Down Expand Up @@ -155,5 +155,8 @@
expect(readByte(rt)).toBe(0xde);
expect(readByte(rt)).toBe(0xad);
expect(readByte(rt)).toBe(0xbe);
sendCommand(rt, [0x4d, 0x00, 0x00, 0x00, 0x00, 0xff]);
expect(readResponse(rt)).toBe(0x00);
expect(readByte(rt)).toBe(0x00);
});
});
30 changes: 29 additions & 1 deletion tests/platforms/tec1g/sd-spi.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import { SdSpi } from '../../../src/platforms/tec1g/sd-spi';

const MOSI_BIT = 0x01;
Expand Down Expand Up @@ -179,6 +179,34 @@ describe('SdSpi', () => {
expect(image[2]).toBe(0x56);
});

it('invokes onWrite callback after CMD24 completes', () => {
const image = new Uint8Array(1024);
const payload = new Uint8Array(512);
payload[0] = 0xde;
payload[1] = 0xad;
payload[2] = 0xbe;
const onWrite = vi.fn();
const spi = new SdSpi({ csMask: CS_BIT, image, onWrite });
writeSpi(spi, 0x00);
sendCommand(spi, [0x77, 0x00, 0x00, 0x00, 0x00, 0x65]);
readResponseByte(spi);
sendCommand(spi, [0x69, 0x40, 0x00, 0x00, 0x00, 0x77]);
readResponseByte(spi);
sendCommand(spi, [0x77, 0x00, 0x00, 0x00, 0x00, 0x65]);
readResponseByte(spi);
sendCommand(spi, [0x69, 0x40, 0x00, 0x00, 0x00, 0x77]);
readResponseByte(spi);
sendCommand(spi, [0x58, 0x00, 0x00, 0x00, 0x00, 0xff]);
expect(readResponseByte(spi)).toBe(0x00);
writeDataBlock(spi, payload);
expect(readResponseByte(spi)).toBe(0x05);
expect(image[0]).toBe(0xde);
expect(image[1]).toBe(0xad);
expect(image[2]).toBe(0xbe);
expect(onWrite).toHaveBeenCalledTimes(1);
expect(onWrite).toHaveBeenCalledWith(image);
});

it('responds to CMD13 with status', () => {
const spi = new SdSpi({ csMask: CS_BIT });
writeSpi(spi, 0x00);
Expand Down
Loading