OXYGENHUB
◆ main openmsx / src / fdc / NowindHost.cc
19.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "NowindHost.hh"
2
3
#include "DiskContainer.hh"
4
#include "FileOperations.hh"
5
#include "MSXException.hh"
6
#include "SectorAccessibleDisk.hh"
7
8
#include "enumerate.hh"
9
#include "narrow.hh"
10
#include "one_of.hh"
11
#include "serialize.hh"
12
#include "serialize_stl.hh"
13
#include "unreachable.hh"
14
#include "xrange.hh"
15
16
#include <algorithm>
17
#include <array>
18
#include <bit>
19
#include <cassert>
20
#include <cctype>
21
#include <cstdarg>
22
#include <cstdio>
23
#include <ctime>
24
#include <fstream>
25
#include <memory>
26
#include <ranges>
27
28
namespace openmsx {
29
30
static constexpr unsigned SECTOR_SIZE = sizeof(SectorBuffer);
31
32
static void DBERR(const char* message, ...)
33
{
34
	(void)message;
35
#if 0
36
	va_list args;
37
	va_start(args, message);
38
	printf("nowind: ");
39
	vprintf(message, args);
40
	va_end(args);
41
#endif
42
}
43
44
45
NowindHost::NowindHost(const Drives& drives_)
46
	: drives(drives_)
47
{
48
}
49
50
NowindHost::~NowindHost() = default;
51
52
uint8_t NowindHost::peek() const
53
{
54
	return isDataAvailable() ? hostToMsxFifo.front() : 0xFF;
55
}
56
57
// receive:  msx <- pc
58
uint8_t NowindHost::read()
59
{
60
	return isDataAvailable() ? hostToMsxFifo.pop_front() : 0xFF;
61
}
62
63
bool NowindHost::isDataAvailable() const
64
{
65
	return !hostToMsxFifo.empty();
66
}
67
68
69
// send:  msx -> pc
70
void NowindHost::write(uint8_t data, unsigned time)
71
{
72
	using enum State;
73
	unsigned duration = time - lastTime;
74
	lastTime = time;
75
	if (duration >= 500) {
76
		// timeout (500ms), start looking for AF05
77
		purge();
78
		state = SYNC1;
79
	}
80
81
	switch (state) {
82
	case SYNC1:
83
		if (data == 0xAF) state = SYNC2;
84
		break;
85
	case SYNC2:
86
		switch (data) {
87
		case 0x05: state = COMMAND; recvCount = 0; break;
88
		case 0xAF: state = SYNC2; break;
89
		case 0xFF: state = SYNC1; msxReset(); break;
90
		default:   state = SYNC1; break;
91
		}
92
		break;
93
	case COMMAND:
94
		assert(recvCount < 9);
95
		cmdData[recvCount] = data;
96
		if (++recvCount == 9) {
97
			executeCommand();
98
		}
99
		break;
100
	case DISKREAD:
101
		assert(recvCount < 2);
102
		extraData[recvCount] = data;
103
		if (++recvCount == 2) {
104
			doDiskRead2();
105
		}
106
		break;
107
	case DISKWRITE:
108
		assert(recvCount < (transferSize + 2));
109
		extraData[recvCount] = data;
110
		if (++recvCount == (transferSize + 2)) {
111
			doDiskWrite2();
112
		}
113
		break;
114
	case DEVOPEN:
115
		assert(recvCount < 11);
116
		extraData[recvCount] = data;
117
		if (++recvCount == 11) {
118
			deviceOpen();
119
		}
120
		break;
121
	case IMAGE:
122
		assert(recvCount < 40);
123
		extraData[recvCount] = data;
124
		if (data == one_of(uint8_t(0), uint8_t(':')) ||
125
		    (++recvCount == 40)) {
126
			const auto* eData = std::bit_cast<const char*>(extraData.data());
127
			callImage(std::string(eData, recvCount));
128
			state = SYNC1;
129
		}
130
		break;
131
	case MESSAGE:
132
		assert(recvCount < (240 - 1));
133
		extraData[recvCount] = data;
134
		if ((data == 0) || (++recvCount == (240 - 1))) {
135
			extraData[recvCount] = 0;
136
			DBERR("%s\n", std::bit_cast<char*>(extraData.data()));
137
			state = SYNC1;
138
		}
139
		break;
140
	default:
141
		UNREACHABLE;
142
	}
143
}
144
145
void NowindHost::msxReset()
146
{
147
	for (auto& dev : devices) {
148
		dev.fs.reset();
149
	}
150
	DBERR("MSX reset\n");
151
}
152
153
SectorAccessibleDisk* NowindHost::getDisk() const
154
{
155
	uint8_t num = cmdData[7]; // reg_a
156
	if (num >= drives.size()) {
157
		return nullptr;
158
	}
159
	return drives[num]->getSectorAccessibleDisk();
160
}
161
162
void NowindHost::executeCommand()
163
{
164
	using enum State;
165
	assert(recvCount == 9);
166
	uint8_t cmd = cmdData[8];
167
	switch (cmd) {
168
	//case 0x0D: BDOS_0DH_DiskReset();
169
	//case 0x0F: BDOS_0FH_OpenFile();
170
	//case 0x10: BDOS_10H_CloseFile();
171
	//case 0x11: BDOS_11H_FindFirst();
172
	//case 0x12: BDOS_12H_FindNext();
173
	//case 0x13: BDOS_13H_DeleteFile();
174
	//case 0x14: BDOS_14H_ReadSeq();
175
	//case 0x15: BDOS_15H_WriteSeq();
176
	//case 0x16: BDOS_16H_CreateFile();
177
	//case 0x17: BDOS_17H_RenameFile();
178
	//case 0x21: BDOS_21H_ReadRandomFile();
179
	//case 0x22: BDOS_22H_WriteRandomFile();
180
	//case 0x23: BDOS_23H_GetFileSize();
181
	//case 0x24: BDOS_24H_SetRandomRecordField();
182
	//case 0x26: BDOS_26H_WriteRandomBlock();
183
	//case 0x27: BDOS_27H_ReadRandomBlock();
184
	//case 0x28: BDOS_28H_WriteRandomFileWithZeros();
185
	//case 0x2A: BDOS_2AH_GetDate();
186
	//case 0x2B: BDOS_2BH_SetDate();
187
	//case 0x2C: BDOS_2CH_GetTime();
188
	//case 0x2D: BDOS_2DH_SetTime();
189
	//case 0x2E: BDOS_2EH_Verify();
190
	//case 0x2F: BDOS_2FH_ReadLogicalSector();
191
	//case 0x30: BDOS_30H_WriteLogicalSector();
192
193
	case 0x80: { // DSKIO
194
		auto* disk = getDisk();
195
		if (!disk) {
196
			// no such drive or no disk inserted
197
			// (causes a timeout on the MSX side)
198
			state = SYNC1;
199
			return;
200
		}
201
		if (uint8_t reg_f = cmdData[6]; reg_f & 1) { // carry flag
202
			diskWriteInit(*disk);
203
		} else {
204
			diskReadInit(*disk);
205
		}
206
		break;
207
	}
208
209
	case 0x81: DSKCHG();      state = SYNC1; break;
210
	//case 0x82: GETDPB();
211
	//case 0x83: CHOICE();
212
	//case 0x84: DSKFMT();
213
	case 0x85: DRIVES();      state = SYNC1; break;
214
	case 0x86: INIENV();      state = SYNC1; break;
215
	case 0x87: setDateMSX();  state = SYNC1; break;
216
	case 0x88: state = DEVOPEN; recvCount = 0; break;
217
	case 0x89: deviceClose(); state = SYNC1; break;
218
	//case 0x8A: deviceRandomIO(fcb);
219
	case 0x8B: deviceWrite(); state = SYNC1; break;
220
	case 0x8C: deviceRead();  state = SYNC1; break;
221
	//case 0x8D: deviceEof(fcb);
222
	//case 0x8E: auxIn();
223
	//case 0x8F: auxOut();
224
	case 0x90: state = MESSAGE; recvCount = 0; break;
225
	case 0xA0: state = IMAGE;   recvCount = 0; break;
226
	//case 0xFF: vramDump();
227
	default:
228
		// Unknown USB command!
229
		state = SYNC1;
230
		break;
231
	}
232
}
233
234
// send:  pc -> msx
235
void NowindHost::send(uint8_t value)
236
{
237
	hostToMsxFifo.push_back(value);
238
}
239
void NowindHost::send16(uint16_t value)
240
{
241
	hostToMsxFifo.push_back(narrow_cast<uint8_t>(value & 255));
242
	hostToMsxFifo.push_back(narrow_cast<uint8_t>(value >> 8));
243
}
244
245
void NowindHost::purge()
246
{
247
	hostToMsxFifo.clear();
248
}
249
250
void NowindHost::sendHeader()
251
{
252
	send(0xFF); // needed because first read might fail!
253
	send(0xAF);
254
	send(0x05);
255
}
256
257
void NowindHost::DSKCHG()
258
{
259
	auto* disk = getDisk();
260
	if (!disk) {
261
		// no such drive or no disk inserted
262
		return;
263
	}
264
265
	sendHeader();
266
	uint8_t num = cmdData[7]; // reg_a
267
	assert(num < drives.size());
268
	if (drives[num]->diskChanged()) {
269
		send(255); // changed
270
		// read first FAT sector (contains media descriptor)
271
		SectorBuffer sectorBuffer;
272
		try {
273
			disk->readSectors(std::span{&sectorBuffer, 1}, 1);
274
		} catch (MSXException&) {
275
			// TODO read error
276
			sectorBuffer.raw[0] = 0;
277
		}
278
		send(sectorBuffer.raw[0]); // new media descriptor
279
	} else {
280
		send(0); // not changed
281
		// TODO shouldn't we send some (dummy) byte here?
282
		//      nowind-disk-rom seems to read it (but doesn't use it)
283
	}
284
}
285
286
void NowindHost::DRIVES()
287
{
288
	// at least one drive (MSX-DOS1 cannot handle 0 drives)
289
	uint8_t numberOfDrives = std::max<uint8_t>(1, uint8_t(drives.size()));
290
291
	uint8_t reg_a = cmdData[7];
292
	sendHeader();
293
	send(getEnablePhantomDrives() ? 0x02 : 0);
294
	send(reg_a | (getAllowOtherDiskRoms() ? 0 : 0x80));
295
	send(numberOfDrives);
296
297
	romDisk = 255; // no rom disk
298
	for (auto [i, drv] : enumerate(drives)) {
299
		if (drv->isRomDisk()) {
300
			romDisk = uint8_t(i);
301
			break;
302
		}
303
	}
304
}
305
306
void NowindHost::INIENV()
307
{
308
	sendHeader();
309
	send(romDisk); // calculated in DRIVES()
310
}
311
312
void NowindHost::setDateMSX()
313
{
314
	auto td = time(nullptr);
315
	auto* tm = localtime(&td);
316
317
	sendHeader();
318
	send(narrow_cast<uint8_t>(tm->tm_mday));          // day
319
	send(narrow_cast<uint8_t>(tm->tm_mon + 1));       // month
320
	send16(narrow_cast<uint16_t>(tm->tm_year + 1900)); // year
321
}
322
323
324
unsigned NowindHost::getSectorAmount() const
325
{
326
	uint8_t reg_b = cmdData[1];
327
	return reg_b;
328
}
329
unsigned NowindHost::getStartSector() const
330
{
331
	uint8_t reg_c = cmdData[0];
332
	uint8_t reg_e = cmdData[2];
333
	uint8_t reg_d = cmdData[3];
334
	unsigned startSector = reg_e + (reg_d * 256);
335
	if (reg_c < 0x80) {
336
		// FAT16 read/write sector
337
		startSector += reg_c << 16;
338
	}
339
	return startSector;
340
}
341
unsigned NowindHost::getStartAddress() const
342
{
343
	uint8_t reg_l = cmdData[4];
344
	uint8_t reg_h = cmdData[5];
345
	return reg_h * 256 + reg_l;
346
}
347
unsigned NowindHost::getCurrentAddress() const
348
{
349
	unsigned startAddress = getStartAddress();
350
	return startAddress + transferred;
351
}
352
353
354
void NowindHost::diskReadInit(SectorAccessibleDisk& disk)
355
{
356
	unsigned sectorAmount = getSectorAmount();
357
	buffer.resize(sectorAmount);
358
	unsigned startSector = getStartSector();
359
	try {
360
		disk.readSectors(std::span{buffer.data(), sectorAmount}, startSector);
361
	} catch (MSXException&) {
362
		// read error
363
		state = State::SYNC1;
364
		return;
365
	}
366
367
	transferred = 0;
368
	retryCount = 0;
369
	doDiskRead1();
370
}
371
372
void NowindHost::doDiskRead1()
373
{
374
	unsigned bytesLeft = unsigned(buffer.size() * SECTOR_SIZE) - transferred;
375
	if (bytesLeft == 0) {
376
		sendHeader();
377
		send(0x01); // end of receive-loop
378
		send(0x00); // no more data
379
		state = State::SYNC1;
380
		return;
381
	}
382
383
	constexpr unsigned NUMBER_OF_BLOCKS = 32; // 32 * 64 bytes = 2048 bytes
384
	transferSize = std::min(bytesLeft, NUMBER_OF_BLOCKS * 64); // hardcoded in firmware
385
386
	unsigned address = getCurrentAddress();
387
	if (address >= 0x8000) {
388
		if (transferSize & 0x003F) {
389
			transferSectors(address, transferSize);
390
		} else {
391
			transferSectorsBackwards(address, transferSize);
392
		}
393
	} else {
394
		// transfer below 0x8000
395
		// TODO shouldn't we also test for (transferSize & 0x3F)?
396
		unsigned endAddress = address + transferSize;
397
		if (endAddress <= 0x8000) {
398
			transferSectorsBackwards(address, transferSize);
399
		} else {
400
			transferSize = 0x8000 - address;
401
			transferSectors(address, transferSize);
402
		}
403
	}
404
405
	// wait for 2 bytes
406
	state = State::DISKREAD;
407
	recvCount = 0;
408
}
409
410
void NowindHost::doDiskRead2()
411
{
412
	// disk rom sends back the last two bytes read
413
	assert(recvCount == 2);
414
	uint8_t tail1 = extraData[0];
415
	uint8_t tail2 = extraData[1];
416
	if ((tail1 == 0xAF) && (tail2 == 0x07)) {
417
		transferred += transferSize;
418
		retryCount = 0;
419
420
		unsigned address = getCurrentAddress();
421
		size_t bytesLeft = (buffer.size() * SECTOR_SIZE) - transferred;
422
		if ((address == 0x8000) && (bytesLeft > 0)) {
423
			sendHeader();
424
			send(0x01); // end of receive-loop
425
			send(0xff); // more data for page 2/3
426
		}
427
428
		// continue the rest of the disk read
429
		doDiskRead1();
430
	} else {
431
		purge();
432
		if (++retryCount == 10) {
433
			// do nothing, timeout on MSX
434
			// too many retries, aborting readDisk()
435
			state = State::SYNC1;
436
			return;
437
		}
438
439
		// try again, wait for two bytes
440
		state = State::DISKREAD;
441
		recvCount = 0;
442
	}
443
}
444
445
// sends "02" + "transfer_addr" + "amount" + "data" + "0F 07"
446
void NowindHost::transferSectors(unsigned transferAddress, unsigned amount)
447
{
448
	sendHeader();
449
	send(0x00); // don't exit command, (more) data is coming
450
	send16(narrow_cast<uint16_t>(transferAddress));
451
	send16(narrow_cast<uint16_t>(amount));
452
453
	std::span fullBuf{buffer[0].raw.data(), buffer.size() * SECTOR_SIZE};
454
	for (auto buf = fullBuf.subspan(transferred, amount);
455
	     auto b : buf) {
456
		send(b);
457
	}
458
	send(0xAF);
459
	send(0x07); // used for validation
460
}
461
462
// sends "02" + "transfer_addr" + "amount" + "data" + "0F 07"
463
void NowindHost::transferSectorsBackwards(unsigned transferAddress, unsigned amount)
464
{
465
	sendHeader();
466
	send(0x02); // don't exit command, (more) data is coming
467
	send16(narrow_cast<uint16_t>(transferAddress + amount));
468
	send(narrow_cast<uint8_t>(amount / 64));
469
470
	std::span fullBuf{buffer[0].raw.data(), buffer.size() * SECTOR_SIZE};
471
	for (auto buf = fullBuf.subspan(transferred, amount);
472
	     auto b : std::views::reverse(buf)) {
473
		send(b);
474
	}
475
	send(0xAF);
476
	send(0x07); // used for validation
477
}
478
479
480
void NowindHost::diskWriteInit(const SectorAccessibleDisk& disk)
481
{
482
	if (disk.isWriteProtected()) {
483
		sendHeader();
484
		send(1);
485
		send(0); // WRITE PROTECTED
486
		state = State::SYNC1;
487
		return;
488
	}
489
490
	unsigned sectorAmount = std::min(128u, getSectorAmount());
491
	buffer.resize(sectorAmount);
492
	transferred = 0;
493
	doDiskWrite1();
494
}
495
496
void NowindHost::doDiskWrite1()
497
{
498
	unsigned bytesLeft = unsigned(buffer.size() * SECTOR_SIZE) - transferred;
499
	if (bytesLeft == 0) {
500
		// All data transferred!
501
		auto sectorAmount = unsigned(buffer.size());
502
		unsigned startSector = getStartSector();
503
		if (auto* disk = getDisk()) {
504
			try {
505
				disk->writeSectors(std::span{buffer.data(), sectorAmount}, startSector);
506
			} catch (MSXException&) {
507
				// TODO write error
508
			}
509
		}
510
		sendHeader();
511
		send(255);
512
		state = State::SYNC1;
513
		return;
514
	}
515
516
	constexpr unsigned BLOCKSIZE = 240;
517
	transferSize = std::min(bytesLeft, BLOCKSIZE);
518
519
	unsigned address = getCurrentAddress();
520
	unsigned endAddress = address + transferSize;
521
	if ((address ^ endAddress) & 0x8000) {
522
		// would cross page 1-2 boundary -> limit to page 1
523
		transferSize = 0x8000 - address;
524
	}
525
526
	sendHeader();
527
	send(0);          // data ahead!
528
	send16(narrow_cast<uint16_t>(address));
529
	send16(narrow_cast<uint16_t>(transferSize));
530
	send(0xaa);
531
532
	// wait for data
533
	state = State::DISKWRITE;
534
	recvCount = 0;
535
}
536
537
void NowindHost::doDiskWrite2()
538
{
539
	assert(recvCount == (transferSize + 2));
540
	std::span fullBuf{buffer[0].raw.data(), buffer.size() * SECTOR_SIZE};
541
	auto dst = fullBuf.subspan(transferred, transferSize);
542
	auto src = subspan(extraData, 1, transferSize);
543
	copy_to_range(src, dst);
544
545
	uint8_t seq1 = extraData[0];
546
	uint8_t seq2 = extraData[transferSize + 1];
547
	if ((seq1 == 0xaa) && (seq2 == 0xaa)) {
548
		// good block received
549
		transferred += transferSize;
550
551
		unsigned address = getCurrentAddress();
552
		size_t bytesLeft = (buffer.size() * SECTOR_SIZE) - transferred;
553
		if ((address == 0x8000) && (bytesLeft > 0)) {
554
			sendHeader();
555
			send(254); // more data for page 2/3
556
		}
557
	} else {
558
		// ERROR!!!
559
		// This situation is still not handled correctly!
560
		purge();
561
	}
562
563
	// continue the rest of the disk write
564
	doDiskWrite1();
565
}
566
567
568
uint16_t NowindHost::getFCB() const
569
{
570
	// note: same code as getStartAddress(), merge???
571
	uint8_t reg_l = cmdData[4];
572
	uint8_t reg_h = cmdData[5];
573
	return uint16_t(reg_h * 256 + reg_l);
574
}
575
576
std::string NowindHost::extractName(int begin, int end) const
577
{
578
	std::string result;
579
	for (auto i : xrange(begin, end)) {
580
		auto c = narrow_cast<char>(extraData[i]);
581
		if (c == ' ') break;
582
		result += char(toupper(c));
583
	}
584
	return result;
585
}
586
587
int NowindHost::getDeviceNum() const
588
{
589
	auto fcb = getFCB();
590
	for (auto [i, dev] : enumerate(devices)) {
591
		if (dev.fs && dev.fcb == fcb) {
592
			return int(i);
593
		}
594
	}
595
	return -1;
596
}
597
598
int NowindHost::getFreeDeviceNum()
599
{
600
	if (int dev = getDeviceNum(); dev != -1) {
601
		// There already was a device open with this fcb address,
602
		// reuse that device.
603
		return dev;
604
	}
605
	// Search for free device.
606
	for (auto [i, dev] : enumerate(devices)) {
607
		if (!dev.fs) return int(i);
608
	}
609
	// All devices are in use. This can't happen when the MSX software
610
	// functions correctly. We'll simply reuse the first device. It would
611
	// be nicer if we reuse the oldest device, but that's harder to
612
	// implement, and actually it doesn't really matter.
613
	return 0;
614
}
615
616
void NowindHost::deviceOpen()
617
{
618
	state = State::SYNC1;
619
620
	assert(recvCount == 11);
621
	std::string filename = extractName(0, 8);
622
	std::string ext      = extractName(8, 11);
623
	if (!ext.empty()) {
624
		strAppend(filename, '.', ext);
625
	}
626
627
	auto fcb = getFCB();
628
	unsigned dev = getFreeDeviceNum();
629
	devices[dev].fs.emplace(); // takes care of deleting old fs
630
	devices[dev].fcb = fcb;
631
632
	sendHeader();
633
	uint8_t errorCode = 0;
634
	uint8_t openMode = cmdData[2]; // reg_e
635
	switch (openMode) {
636
	case 1: // read-only mode
637
		devices[dev].fs->open(filename.c_str(), std::ios::in  | std::ios::binary);
638
		errorCode = 53; // file not found
639
		break;
640
	case 2: // create new file, write-only
641
		devices[dev].fs->open(filename.c_str(), std::ios::out | std::ios::binary);
642
		errorCode = 56; // bad file name
643
		break;
644
	case 8: // append to existing file, write-only
645
		devices[dev].fs->open(filename.c_str(), std::ios::out | std::ios::binary | std::ios::app);
646
		errorCode = 53; // file not found
647
		break;
648
	case 4:
649
		send(58); // sequential I/O only
650
		return;
651
	default:
652
		send(0xFF); // TODO figure out a good error number
653
		return;
654
	}
655
	assert(errorCode != 0);
656
	if (devices[dev].fs->fail()) {
657
		devices[dev].fs.reset();
658
		send(errorCode);
659
		return;
660
	}
661
662
	unsigned readLen = 0;
663
	bool eof = false;
664
	std::array<char, 256> buf;
665
	if (openMode == 1) {
666
		// read-only mode, already buffer first 256 bytes
667
		readLen = readHelper1(dev, buf);
668
		assert(readLen <= 256);
669
		eof = readLen < 256;
670
	}
671
672
	send(0x00); // no error
673
	send16(fcb);
674
	send16(narrow_cast<uint16_t>(9 + readLen + (eof ? 1 : 0))); // number of bytes to transfer
675
676
	send(openMode);
677
	send(0);
678
	send(0);
679
	send(0);
680
	send(cmdData[3]); // reg_d
681
	send(0);
682
	send(0);
683
	send(0);
684
	send(0);
685
686
	if (openMode == 1) {
687
		readHelper2(subspan(buf, 0, readLen));
688
	}
689
}
690
691
void NowindHost::deviceClose()
692
{
693
	int dev = getDeviceNum();
694
	if (dev == -1) return;
695
	devices[dev].fs.reset();
696
}
697
698
void NowindHost::deviceWrite()
699
{
700
	int dev = getDeviceNum();
701
	if (dev == -1) return;
702
	auto data = narrow_cast<char>(cmdData[0]); // reg_c
703
	devices[dev].fs->write(&data, 1);
704
}
705
706
void NowindHost::deviceRead()
707
{
708
	int dev = getDeviceNum();
709
	if (dev == -1) return;
710
711
	std::array<char, 256> buf;
712
	unsigned readLen = readHelper1(dev, buf);
713
	bool eof = readLen < 256;
714
	send(0xAF);
715
	send(0x05);
716
	send(0x00); // dummy
717
	send16(narrow_cast<uint16_t>(getFCB() + 9));
718
	send16(narrow_cast<uint16_t>(readLen + (eof ? 1 : 0)));
719
	readHelper2(subspan(buf, 0, readLen));
720
}
721
722
unsigned NowindHost::readHelper1(unsigned dev, std::span<char, 256> buf)
723
{
724
	assert(dev < MAX_DEVICES);
725
	unsigned len = 0;
726
	for (/**/; len < 256; ++len) {
727
		devices[dev].fs->read(&buf[len], 1);
728
		if (devices[dev].fs->eof()) break;
729
	}
730
	return len;
731
}
732
733
void NowindHost::readHelper2(std::span<const char> buf)
734
{
735
	for (auto c : buf) {
736
		send(c);
737
	}
738
	if (buf.size() < 256) {
739
		send(0x1A); // end-of-file
740
	}
741
}
742
743
744
// strips a string from outer double-quotes and anything outside them
745
// ie: 'pre("foo")bar' will result in 'foo'
746
static constexpr std::string_view stripQuotes(std::string_view str)
747
{
748
	auto first = str.find_first_of('\"');
749
	if (first == std::string::npos) {
750
		// There are no quotes, return the whole string.
751
		return str;
752
	}
753
	auto last = str.find_last_of ('\"');
754
	if (first == last) {
755
		// Error, there's only a single double-quote char.
756
		return {};
757
	}
758
	// Return the part between the quotes.
759
	return str.substr(first + 1, last - first - 1);
760
}
761
762
void NowindHost::callImage(const std::string& filename)
763
{
764
	uint8_t num = cmdData[7]; // reg_a
765
	if (num >= drives.size()) {
766
		// invalid drive number
767
		return;
768
	}
769
	if (drives[num]->insertDisk(FileOperations::expandTilde(std::string(stripQuotes(filename))))) {
770
		// TODO error handling
771
	}
772
}
773
774
775
static constexpr auto stateInfo = std::to_array<enum_string<NowindHost::State>>({
776
	{ "SYNC1",     NowindHost::State::SYNC1     },
777
	{ "SYNC2",     NowindHost::State::SYNC2     },
778
	{ "COMMAND",   NowindHost::State::COMMAND   },
779
	{ "DISKREAD",  NowindHost::State::DISKREAD  },
780
	{ "DISKWRITE", NowindHost::State::DISKWRITE },
781
	{ "DEVOPEN",   NowindHost::State::DEVOPEN   },
782
	{ "IMAGE",     NowindHost::State::IMAGE     },
783
});
784
SERIALIZE_ENUM(NowindHost::State, stateInfo);
785
786
template<typename Archive>
787
void NowindHost::serialize(Archive& ar, unsigned /*version*/)
788
{
789
	// drives is serialized elsewhere
790
791
	ar.serialize("hostToMsxFifo", hostToMsxFifo,
792
	             "lastTime",      lastTime,
793
	             "state",         state,
794
	             "recvCount",     recvCount,
795
	             "cmdData",       cmdData,
796
	             "extraData",     extraData);
797
798
	// for backwards compatibility, serialize buffer as a vector<uint8_t>
799
	size_t bufSize = buffer.size() * sizeof(SectorBuffer);
800
	std::span<uint8_t> buf{buffer.data()->raw.data(), bufSize};
801
	auto tmp = to_vector(buf);
802
	ar.serialize("buffer", tmp);
803
	copy_to_range(tmp, buf);
804
805
	ar.serialize("transfered",          transferred, // for bw compat, keep typo in serialize name
806
	             "retryCount",          retryCount,
807
	             "transferSize",        transferSize,
808
	             "romdisk",             romDisk,
809
	             "allowOtherDiskroms",  allowOtherDiskRoms,
810
	             "enablePhantomDrives", enablePhantomDrives);
811
812
	// Note: We don't serialize 'devices'. So after a loadstate it will be
813
	// as-if the devices are closed again. The reason for not serializing
814
	// this is that it's very hard to serialize a fstream (and we anyway
815
	// can't restore the state of the host filesystem).
816
}
817
INSTANTIATE_SERIALIZE_METHODS(NowindHost);
818
819
} // namespace openmsx