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