@fugloy/emu 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +27 -0
- package/README.md +119 -2
- package/package.json +30 -3
- package/src/cpu/z80cpu.js +3343 -0
- package/src/debug/debugger.js +777 -0
- package/src/debug/instr.js +90 -0
- package/src/debug/label-file.js +423 -0
- package/src/debug/state.js +214 -0
- package/src/devices/cassette-tape.js +254 -0
- package/src/devices/cassette.js +259 -0
- package/src/devices/disk-image.js +578 -0
- package/src/devices/fdc-wd1793.js +466 -0
- package/src/devices/key-queue.js +261 -0
- package/src/devices/keyboard.js +176 -0
- package/src/devices/printer.js +44 -0
- package/src/devices/sound.js +53 -0
- package/src/devices/video.js +66 -0
- package/src/index.js +22 -0
- package/src/loaders/bas.js +107 -0
- package/src/loaders/cas-format.js +213 -0
- package/src/loaders/cmd-format.js +88 -0
- package/src/loaders/handoff.js +29 -0
- package/src/loaders/load.js +135 -0
- package/src/machine/io.js +290 -0
- package/src/machine/machine.js +295 -0
- package/src/machine/memory.js +207 -0
- package/src/machine/rom-hashes.js +14 -0
- package/src/machine/sha256.js +74 -0
- package/src/machine/trs80-system.js +232 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WD1793 Floppy Disk Controller (Model III disk subsystem)
|
|
3
|
+
*
|
|
4
|
+
* Ports (handled by IOSystem):
|
|
5
|
+
* 0xF0 status (R) / command (W) 0xF2 sector register
|
|
6
|
+
* 0xF1 track register 0xF3 data register
|
|
7
|
+
* 0xF4 (W) drive select: bits 0-3 drive, bit 4 side, bit 7 MFM
|
|
8
|
+
*
|
|
9
|
+
* Implements what the boot ROM and LDOS/TRSDOS-class DOSes use: Type I
|
|
10
|
+
* RESTORE/SEEK/STEP, Type II READ/WRITE SECTOR with a DRQ byte pump,
|
|
11
|
+
* Type III READ ADDRESS, Type IV FORCE INTERRUPT. Command completion is
|
|
12
|
+
* delayed in emulated T-states via tick(); INTRQ is exposed for the
|
|
13
|
+
* 0xE4 NMI latch. Sector data comes from DiskImage objects attached per
|
|
14
|
+
* drive.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// Status bits (Type I / common)
|
|
18
|
+
const ST_BUSY = 0x01;
|
|
19
|
+
const ST_INDEX = 0x02; // Type I: index pulse
|
|
20
|
+
const ST_DRQ = 0x02; // Type II/III: data request
|
|
21
|
+
const ST_TRACK0 = 0x04;
|
|
22
|
+
const ST_CRC_ERROR = 0x08;
|
|
23
|
+
const ST_RNF = 0x10; // record not found / seek error
|
|
24
|
+
const ST_HEAD_LOADED = 0x20;
|
|
25
|
+
const ST_RECORD_TYPE = 0x20; // Type II read: deleted data mark (0xF8)
|
|
26
|
+
const ST_WRITE_PROTECT = 0x40;
|
|
27
|
+
const ST_NOT_READY = 0x80;
|
|
28
|
+
|
|
29
|
+
// Index hole: 300 rpm = one pulse per 405,504 T-states, ~6k wide
|
|
30
|
+
const INDEX_PERIOD = 405504;
|
|
31
|
+
const INDEX_WIDTH = 8000;
|
|
32
|
+
|
|
33
|
+
const COMPLETION_DELAY = 12000; // T-states for Type I/II command spin-up
|
|
34
|
+
|
|
35
|
+
// WRITE TRACK capture cap: a DD track is ~6250 bytes; an unthrottled
|
|
36
|
+
// DOS can shovel more filler than that in one emulated revolution.
|
|
37
|
+
const TRACK_WRITE_CAP = 20000;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Turn the byte stream a formatting DOS sends during WRITE TRACK into
|
|
41
|
+
* sectors. MFM control bytes: 0xF5 writes an A1 sync byte, 0xFE starts
|
|
42
|
+
* an ID field (track, side, sector, size code), 0xFB/0xF8 start a
|
|
43
|
+
* normal/deleted data field, 0xF7 writes the CRC. FM (single density)
|
|
44
|
+
* uses the bare marks after a run of 0x00 sync bytes. The density latch
|
|
45
|
+
* is only a guess about what the DOS meant, so a parse that finds no
|
|
46
|
+
* sectors retries as the other density.
|
|
47
|
+
*
|
|
48
|
+
* @returns {{track:number, side:number, sector:number, size:number,
|
|
49
|
+
* deleted:boolean, data:Uint8Array, mfm:boolean}[]}
|
|
50
|
+
*/
|
|
51
|
+
export function parseWriteTrackStream(bytes, mfmGuess = true) {
|
|
52
|
+
const first = mfmGuess ? scanMfm(bytes) : scanFm(bytes);
|
|
53
|
+
if (first.length) return first;
|
|
54
|
+
return mfmGuess ? scanFm(bytes) : scanMfm(bytes);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function scanMfm(bytes) {
|
|
58
|
+
const sectors = [];
|
|
59
|
+
let i = 0;
|
|
60
|
+
let id = null;
|
|
61
|
+
while (i < bytes.length) {
|
|
62
|
+
if (bytes[i] !== 0xf5) {
|
|
63
|
+
i++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
let j = i;
|
|
67
|
+
while (j < bytes.length && bytes[j] === 0xf5) j++;
|
|
68
|
+
const mark = bytes[j];
|
|
69
|
+
if (mark === 0xfe && j + 4 < bytes.length) {
|
|
70
|
+
id = {
|
|
71
|
+
track: bytes[j + 1],
|
|
72
|
+
side: bytes[j + 2],
|
|
73
|
+
sector: bytes[j + 3],
|
|
74
|
+
size: 128 << (bytes[j + 4] & 3),
|
|
75
|
+
};
|
|
76
|
+
i = j + 5;
|
|
77
|
+
} else if ((mark === 0xfb || mark === 0xf8) && id) {
|
|
78
|
+
sectors.push({
|
|
79
|
+
...id,
|
|
80
|
+
deleted: mark === 0xf8,
|
|
81
|
+
data: bytes.slice(j + 1, j + 1 + id.size),
|
|
82
|
+
mfm: true,
|
|
83
|
+
});
|
|
84
|
+
i = j + 1 + id.size;
|
|
85
|
+
id = null;
|
|
86
|
+
} else {
|
|
87
|
+
i = j + 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return sectors;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function scanFm(bytes) {
|
|
94
|
+
const sectors = [];
|
|
95
|
+
let i = 0;
|
|
96
|
+
let id = null;
|
|
97
|
+
let zeros = 0;
|
|
98
|
+
while (i < bytes.length) {
|
|
99
|
+
const b = bytes[i];
|
|
100
|
+
if (b === 0x00) {
|
|
101
|
+
zeros++;
|
|
102
|
+
i++;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (zeros >= 2) {
|
|
106
|
+
if (b === 0xfe && i + 4 < bytes.length) {
|
|
107
|
+
id = {
|
|
108
|
+
track: bytes[i + 1],
|
|
109
|
+
side: bytes[i + 2],
|
|
110
|
+
sector: bytes[i + 3],
|
|
111
|
+
size: 128 << (bytes[i + 4] & 3),
|
|
112
|
+
};
|
|
113
|
+
zeros = 0;
|
|
114
|
+
i += 5;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if ((b === 0xfb || b === 0xf8) && id) {
|
|
118
|
+
sectors.push({
|
|
119
|
+
...id,
|
|
120
|
+
deleted: b === 0xf8,
|
|
121
|
+
data: bytes.slice(i + 1, i + 1 + id.size),
|
|
122
|
+
mfm: false,
|
|
123
|
+
});
|
|
124
|
+
zeros = 0;
|
|
125
|
+
i += 1 + id.size;
|
|
126
|
+
id = null;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
zeros = 0;
|
|
131
|
+
i++;
|
|
132
|
+
}
|
|
133
|
+
return sectors;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export class FDC1793 {
|
|
137
|
+
constructor() {
|
|
138
|
+
this.drives = [null, null, null, null]; // DiskImage or null
|
|
139
|
+
this.selected = 0;
|
|
140
|
+
this.side = 0;
|
|
141
|
+
|
|
142
|
+
this.trackReg = 0;
|
|
143
|
+
this.sectorReg = 0;
|
|
144
|
+
this.dataReg = 0;
|
|
145
|
+
this.physicalTrack = [0, 0, 0, 0];
|
|
146
|
+
|
|
147
|
+
this.status = 0;
|
|
148
|
+
this.intrq = false;
|
|
149
|
+
this.commandType = 1;
|
|
150
|
+
|
|
151
|
+
// In-flight operation
|
|
152
|
+
this.pending = null; // { kind, delay }
|
|
153
|
+
this.buffer = null; // Uint8Array being pumped
|
|
154
|
+
this.bufferPos = 0;
|
|
155
|
+
this.writing = false;
|
|
156
|
+
this.writeDeleted = false; // WRITE SECTOR bit a0: write a 0xF8 DAM
|
|
157
|
+
this.trackWrite = null; // WRITE TRACK capture (array of bytes)
|
|
158
|
+
this.mfm = true; // port 0xF4 bit 7 density latch (Model III default DD)
|
|
159
|
+
this._readAddrIndex = 0; // READ ADDRESS round-robins the track's IDs
|
|
160
|
+
|
|
161
|
+
this.indexClock = 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
attachDrive(n, image) {
|
|
165
|
+
this.drives[n & 3] = image;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
ejectDrive(n) {
|
|
169
|
+
this.drives[n & 3] = null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
anyDiskMounted() {
|
|
173
|
+
return this.drives.some((d) => d !== null);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
currentDisk() {
|
|
177
|
+
return this.drives[this.selected];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Port 0xF4 write */
|
|
181
|
+
selectDrive(value) {
|
|
182
|
+
if (value & 0x01) this.selected = 0;
|
|
183
|
+
else if (value & 0x02) this.selected = 1;
|
|
184
|
+
else if (value & 0x04) this.selected = 2;
|
|
185
|
+
else if (value & 0x08) this.selected = 3;
|
|
186
|
+
this.side = value & 0x10 ? 1 : 0;
|
|
187
|
+
this.mfm = (value & 0x80) !== 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Advance emulated time; completes in-flight commands. */
|
|
191
|
+
tick(cycles) {
|
|
192
|
+
this.indexClock = (this.indexClock + cycles) % INDEX_PERIOD;
|
|
193
|
+
if (this.pending) {
|
|
194
|
+
this.pending.delay -= cycles;
|
|
195
|
+
if (this.pending.delay <= 0) {
|
|
196
|
+
const kind = this.pending.kind;
|
|
197
|
+
this.pending = null;
|
|
198
|
+
this._complete(kind);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
_complete(kind) {
|
|
204
|
+
const disk = this.currentDisk();
|
|
205
|
+
switch (kind) {
|
|
206
|
+
case "type1":
|
|
207
|
+
this.status = this._type1Status();
|
|
208
|
+
this._finish();
|
|
209
|
+
break;
|
|
210
|
+
|
|
211
|
+
case "read-sector": {
|
|
212
|
+
const read = disk
|
|
213
|
+
? disk.readSectorEx(this.physicalTrack[this.selected], this.side, this.sectorReg)
|
|
214
|
+
: null;
|
|
215
|
+
if (!read) {
|
|
216
|
+
this.status = ST_RNF;
|
|
217
|
+
this._finish();
|
|
218
|
+
} else {
|
|
219
|
+
this.buffer = read.data;
|
|
220
|
+
this.bufferPos = 0;
|
|
221
|
+
this.writing = false;
|
|
222
|
+
// The 179x knows the DAM before the first data byte, and DOSes
|
|
223
|
+
// check bit 5 after completion — _finish only drops BUSY|DRQ,
|
|
224
|
+
// so the record type survives until the next command.
|
|
225
|
+
this.status = ST_BUSY | ST_DRQ | (read.deleted ? ST_RECORD_TYPE : 0);
|
|
226
|
+
}
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
case "write-sector": {
|
|
231
|
+
if (!disk) {
|
|
232
|
+
this.status = ST_RNF;
|
|
233
|
+
this._finish();
|
|
234
|
+
} else if (disk.writeProtected) {
|
|
235
|
+
this.status = ST_WRITE_PROTECT;
|
|
236
|
+
this._finish();
|
|
237
|
+
} else {
|
|
238
|
+
// Probe the sector exists before accepting data
|
|
239
|
+
const probe = disk.readSector(this.physicalTrack[this.selected], this.side, this.sectorReg);
|
|
240
|
+
if (!probe) {
|
|
241
|
+
this.status = ST_RNF;
|
|
242
|
+
this._finish();
|
|
243
|
+
} else {
|
|
244
|
+
this.buffer = new Uint8Array(probe.length);
|
|
245
|
+
this.bufferPos = 0;
|
|
246
|
+
this.writing = true;
|
|
247
|
+
this.status = ST_BUSY | ST_DRQ;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
case "read-address": {
|
|
254
|
+
// 6 ID bytes: track, side, sector, size code, CRC1, CRC2 — from
|
|
255
|
+
// the real IDs on the track, round-robin like passing IDAMs
|
|
256
|
+
// (FORMAT verify passes read these to check the seek landed).
|
|
257
|
+
const track = this.physicalTrack[this.selected];
|
|
258
|
+
const ids = disk ? disk.sectorsOn(track, this.side) : [];
|
|
259
|
+
if (ids.length === 0) {
|
|
260
|
+
this.status = ST_RNF;
|
|
261
|
+
this._finish();
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
const id = ids[this._readAddrIndex++ % ids.length];
|
|
265
|
+
this.buffer = new Uint8Array([track, this.side, id.sector, id.sizeCode, 0, 0]);
|
|
266
|
+
// The 1793 copies the ID's track address into the sector register
|
|
267
|
+
this.sectorReg = track;
|
|
268
|
+
this.bufferPos = 0;
|
|
269
|
+
this.writing = false;
|
|
270
|
+
this.status = ST_BUSY | ST_DRQ;
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
case "write-track": {
|
|
275
|
+
const stream = new Uint8Array(this.trackWrite || []);
|
|
276
|
+
this.trackWrite = null;
|
|
277
|
+
const sectors = parseWriteTrackStream(stream, this.mfm);
|
|
278
|
+
if (disk && sectors.length) {
|
|
279
|
+
disk.formatTrack(this.physicalTrack[this.selected], this.side, sectors);
|
|
280
|
+
}
|
|
281
|
+
this.status = 0;
|
|
282
|
+
this._finish();
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
default:
|
|
287
|
+
this._finish();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
_finish() {
|
|
292
|
+
// Completion drops BUSY and DRQ together (type I status is
|
|
293
|
+
// recomputed live on the next read, so losing bit 1 there is fine)
|
|
294
|
+
this.status &= ~(ST_BUSY | ST_DRQ);
|
|
295
|
+
this.intrq = true;
|
|
296
|
+
this.buffer = null;
|
|
297
|
+
this.writing = false;
|
|
298
|
+
this.trackWrite = null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
_type1Status() {
|
|
302
|
+
const disk = this.currentDisk();
|
|
303
|
+
let s = ST_HEAD_LOADED;
|
|
304
|
+
if (!disk) s |= ST_NOT_READY;
|
|
305
|
+
if (this.physicalTrack[this.selected] === 0) s |= ST_TRACK0;
|
|
306
|
+
if (disk && disk.writeProtected) s |= ST_WRITE_PROTECT;
|
|
307
|
+
if (this.indexClock < INDEX_WIDTH) s |= ST_INDEX;
|
|
308
|
+
return s;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Port 0xF0 read */
|
|
312
|
+
readStatus() {
|
|
313
|
+
this.intrq = false; // reading status clears INTRQ
|
|
314
|
+
if (this.commandType === 1) {
|
|
315
|
+
// Recompute live bits (index pulse, track 0)
|
|
316
|
+
if (!(this.status & ST_BUSY)) {
|
|
317
|
+
this.status = this._type1Status();
|
|
318
|
+
}
|
|
319
|
+
return this.status;
|
|
320
|
+
}
|
|
321
|
+
return this.status;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Port 0xF0 write */
|
|
325
|
+
writeCommand(value) {
|
|
326
|
+
this.intrq = false;
|
|
327
|
+
const high = value >> 4;
|
|
328
|
+
|
|
329
|
+
// Type IV: force interrupt
|
|
330
|
+
if (high === 0x0d) {
|
|
331
|
+
this.pending = null;
|
|
332
|
+
this.buffer = null;
|
|
333
|
+
this.trackWrite = null;
|
|
334
|
+
this.status &= ~ST_BUSY;
|
|
335
|
+
this.commandType = 1;
|
|
336
|
+
this.status = this._type1Status();
|
|
337
|
+
if (value & 0x08) this.intrq = true;
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (this.status & ST_BUSY && this.pending) {
|
|
342
|
+
return; // busy: ignore new commands (except force interrupt above)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (high <= 0x07) {
|
|
346
|
+
// Type I: restore/seek/step family
|
|
347
|
+
this.commandType = 1;
|
|
348
|
+
const drive = this.selected;
|
|
349
|
+
if (high === 0x00) {
|
|
350
|
+
this.physicalTrack[drive] = 0;
|
|
351
|
+
this.trackReg = 0;
|
|
352
|
+
} else if (high === 0x01) {
|
|
353
|
+
this.physicalTrack[drive] = this.dataReg;
|
|
354
|
+
this.trackReg = this.dataReg;
|
|
355
|
+
} else {
|
|
356
|
+
// STEP/STEP-IN/STEP-OUT with optional track update (bit 4)
|
|
357
|
+
const dir = high >= 0x06 ? -1 : high >= 0x04 ? 1 : this._lastStepDir || 1;
|
|
358
|
+
if (high >= 0x04) this._lastStepDir = dir;
|
|
359
|
+
this.physicalTrack[drive] = Math.max(0, this.physicalTrack[drive] + dir);
|
|
360
|
+
if (value & 0x10) this.trackReg = this.physicalTrack[drive];
|
|
361
|
+
}
|
|
362
|
+
this.status = ST_BUSY;
|
|
363
|
+
this.pending = { kind: "type1", delay: COMPLETION_DELAY };
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (high === 0x08 || high === 0x09) {
|
|
368
|
+
// READ SECTOR (multi-sector treated as single)
|
|
369
|
+
this.commandType = 2;
|
|
370
|
+
this.status = ST_BUSY;
|
|
371
|
+
this.pending = { kind: "read-sector", delay: COMPLETION_DELAY };
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (high === 0x0a || high === 0x0b) {
|
|
375
|
+
// WRITE SECTOR (bit 0 selects the DAM: 1 = deleted/0xF8)
|
|
376
|
+
this.commandType = 2;
|
|
377
|
+
this.writeDeleted = (value & 0x01) !== 0;
|
|
378
|
+
this.status = ST_BUSY;
|
|
379
|
+
this.pending = { kind: "write-sector", delay: COMPLETION_DELAY };
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (high === 0x0c) {
|
|
383
|
+
// READ ADDRESS
|
|
384
|
+
this.commandType = 3;
|
|
385
|
+
this.status = ST_BUSY;
|
|
386
|
+
this.pending = { kind: "read-address", delay: COMPLETION_DELAY };
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (high === 0x0e) {
|
|
390
|
+
// READ TRACK: rarely used; accept and complete with no data
|
|
391
|
+
this.commandType = 3;
|
|
392
|
+
this.status = ST_BUSY;
|
|
393
|
+
this.pending = { kind: "noop", delay: COMPLETION_DELAY };
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// WRITE TRACK (0xF): a formatting DOS streams a whole track image
|
|
398
|
+
// through the data register; the pass ends at the next index pulse
|
|
399
|
+
// (one revolution), when the stream is parsed into sectors and the
|
|
400
|
+
// track is rebuilt on the mounted image.
|
|
401
|
+
this.commandType = 3;
|
|
402
|
+
const disk = this.currentDisk();
|
|
403
|
+
if (!disk) {
|
|
404
|
+
this.status = ST_NOT_READY;
|
|
405
|
+
this.intrq = true;
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (disk.writeProtected) {
|
|
409
|
+
this.status = ST_WRITE_PROTECT;
|
|
410
|
+
this.intrq = true;
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
this.trackWrite = [];
|
|
414
|
+
this.status = ST_BUSY | ST_DRQ;
|
|
415
|
+
this.pending = { kind: "write-track", delay: INDEX_PERIOD };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
readTrackReg() {
|
|
419
|
+
return this.trackReg;
|
|
420
|
+
}
|
|
421
|
+
writeTrackReg(v) {
|
|
422
|
+
this.trackReg = v & 0xff;
|
|
423
|
+
}
|
|
424
|
+
readSectorReg() {
|
|
425
|
+
return this.sectorReg;
|
|
426
|
+
}
|
|
427
|
+
writeSectorReg(v) {
|
|
428
|
+
this.sectorReg = v & 0xff;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** Port 0xF3 read: pump read buffer */
|
|
432
|
+
readData() {
|
|
433
|
+
if (this.buffer && !this.writing) {
|
|
434
|
+
this.dataReg = this.buffer[this.bufferPos++];
|
|
435
|
+
if (this.bufferPos >= this.buffer.length) {
|
|
436
|
+
this._finish();
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return this.dataReg;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** Port 0xF3 write: pump write buffer */
|
|
443
|
+
writeData(v) {
|
|
444
|
+
this.dataReg = v & 0xff;
|
|
445
|
+
if (this.trackWrite) {
|
|
446
|
+
if (this.trackWrite.length < TRACK_WRITE_CAP) {
|
|
447
|
+
this.trackWrite.push(this.dataReg);
|
|
448
|
+
}
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (this.buffer && this.writing) {
|
|
452
|
+
this.buffer[this.bufferPos++] = this.dataReg;
|
|
453
|
+
if (this.bufferPos >= this.buffer.length) {
|
|
454
|
+
const disk = this.currentDisk();
|
|
455
|
+
disk.writeSector(
|
|
456
|
+
this.physicalTrack[this.selected],
|
|
457
|
+
this.side,
|
|
458
|
+
this.sectorReg,
|
|
459
|
+
this.buffer,
|
|
460
|
+
{ deleted: this.writeDeleted }
|
|
461
|
+
);
|
|
462
|
+
this._finish();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|