more scanline shader parameters
[zxemut.git] / tapeaccel.d
blob4704540d6171bf6b25ee088fccd26c0dc59b727c
1 /* loader.c: loader detection
2 * Copyright (c) 2006 Philip Kendall
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Author contact information:
20 * E-mail: philip-fuse@shadowmagic.org.uk
22 * Adapted for DZXEmuT by Ketmar // Invisible Vector
24 module tapeaccel;
25 private:
27 import iv.alice;
28 import iv.cmdcon;
30 import emuconfig;
31 import lstape;
34 // ////////////////////////////////////////////////////////////////////////// //
35 private @property uint tsmax () nothrow @trusted @nogc {
36 pragma(inline, true);
37 return Config.machine.timings.tstatesPerFrame;
41 // ////////////////////////////////////////////////////////////////////////// //
42 public bool isTapePlaying () nothrow @trusted @nogc { pragma(inline, true); return tapePlaying; }
44 public __gshared bool optFlashLoad = true;
46 __gshared bool optDebugFlashLoad = false;
47 __gshared bool optTapeFlashLoad = true;
48 __gshared bool optTapeDetector = true;
49 __gshared bool optAutoMaxSpeed = true;
50 __gshared bool optTapeSlowScan = true;
51 __gshared bool optTapeAutoRewind = false;
52 public __gshared bool optTapeShowProgress = true;
54 __gshared bool tapeComplete = false;
55 __gshared bool tapePlaying = false;
56 __gshared bool tapeLastEdge = false;
57 __gshared long tapeNextInFrame = -1;
58 public __gshared bool tapeNeedRewind = false;
59 public __gshared bool tapeAutostarted = false; // true: drop maxspeed on tape stop
60 __gshared int tapStopAfterNextEdge = 0; // <0: stop on 48k; >0: stop anyway
61 //__gshared bool tapNextEdge = false;
62 __gshared int tapeBlockCount = 0;
63 __gshared int tapeBlockLastSeen = 0;
64 // in t-states
65 __gshared int tapeCurrTState = 0;
66 __gshared int tapeTotalTStates = 0;
68 public __gshared bool tapeCurrentIs48K = true; // used for `LIBSPECTRUM_TAPE_FLAGS_STOP48`
71 public struct TapeState {
72 int currBlock, blockCount;
73 // t-states
74 int curr, total;
75 bool playing;
76 bool complete;
80 static if (LibSpectrumHere) {
81 public __gshared libspectrum_tape *curtape = null;
84 public TapeState tapeGetState () nothrow @nogc {
85 TapeState ts;
86 ts.currBlock = tapeBlockLastSeen;
87 ts.blockCount = tapeBlockCount;
88 ts.curr = tapeCurrTState;
89 ts.total = tapeTotalTStates;
90 ts.playing = tapePlaying;
91 ts.complete = tapeComplete;
92 return ts;
96 public void tapeNextFrame () nothrow @nogc {
97 if (tapePlaying) tapeNextInFrame -= tsmax;
98 if (tapDetectLastInTS > -100666) tapDetectLastInTS -= tsmax;
102 void tapeShowProgress () nothrow {
103 if (!curtape || tapeComplete || !libspectrum_tape_present(curtape)) return;
104 int cblk;
105 auto tres = libspectrum_tape_position(&cblk, curtape);
106 if (tres != LIBSPECTRUM_ERROR_NONE && tres != LIBSPECTRUM_ERROR_WARNING) return;
107 if (cblk != tapeBlockLastSeen) {
108 tapeBlockLastSeen = cblk;
109 if (!optTapeAutoRewind && cblk == 0) {
110 // autorewind; stop the tape, set the flags
111 emuStopTape();
112 tapeComplete = true;
113 tapeCurrTState = 0;
114 conwriteln("tape: complete");
115 } else {
116 conwriteln("tape: block ", cblk, " of ", tapeBlockCount);
122 public bool tapeCurEdge () nothrow {
123 if (!tapePlaying || !curtape || !libspectrum_tape_present(curtape)) return false;
124 if (tapeNextInFrame > emuz80.tstates || tapeComplete) return tapeLastEdge;
125 tapeShowProgress();
126 while (tapeNextInFrame <= emuz80.tstates && tapePlaying) {
127 int flags = 0;
128 uint tstates = 0;
129 auto tres = libspectrum_tape_get_next_edge(&tstates, &flags, curtape);
130 if (tres != LIBSPECTRUM_ERROR_NONE && tres != LIBSPECTRUM_ERROR_WARNING) {
131 conwriteln("tape reading error");
132 tapePlaying = false;
133 tapeComplete = true;
134 return tapeLastEdge;
136 tapeCurrTState += tstates;
137 if (flags&LIBSPECTRUM_TAPE_FLAGS_LEVEL_LOW) tapeLastEdge = false;
138 else if (flags&LIBSPECTRUM_TAPE_FLAGS_LEVEL_HIGH) tapeLastEdge = true;
139 else if ((flags&LIBSPECTRUM_TAPE_FLAGS_NO_EDGE) == 0) tapeLastEdge = !tapeLastEdge;
140 if (flags&(LIBSPECTRUM_TAPE_FLAGS_TAPE|LIBSPECTRUM_TAPE_FLAGS_STOP)) { conwriteln("tape: stop00"); tapePlaying = false; }
141 if (flags&LIBSPECTRUM_TAPE_FLAGS_STOP48 && tapeCurrentIs48K) { conwriteln("tape: stop01"); tapePlaying = false; }
142 if (flags&LIBSPECTRUM_TAPE_FLAGS_TAPE) { conwriteln("tape complete"); tapeComplete = true; }
143 tapeNextInFrame += tstates;
144 //conwriteln("tapeNextInFrame=", tapeNextInFrame, "; emuz80.tstates=", emuz80.tstates, "; tapeLastEdge=", tapeLastEdge);
146 return tapeLastEdge;
150 int emuTapeLdrGetNextPulse () nothrow {
151 if (!tapePlaying || !curtape || !libspectrum_tape_present(curtape) || tapeComplete) return -1;
153 int nts = 0;
154 bool curedge = tapeLastEdge;
155 for (;;) {
156 if (!tapePlaying) return -1;
157 ++nts;
158 emuz80.tstates += 1;
159 while (emuz80.tstates >= tsmax) { tapeNextFrame(); emuz80.tstates -= tsmax; }
160 if (curedge != tapeCurEdge()) return nts;
164 if (tapeNextInFrame > 0) tapeNextInFrame = 0; // just in case
165 //emuz80.tstates = 0;
167 bool curedge = tapeLastEdge;
168 int ts = 0;
169 tapeShowProgress();
170 while (tapePlaying) {
171 int flags = 0;
172 uint tstates = 0;
173 auto tres = libspectrum_tape_get_next_edge(&tstates, &flags, curtape);
174 if (tres != LIBSPECTRUM_ERROR_NONE && tres != LIBSPECTRUM_ERROR_WARNING) {
175 conwriteln("tape reading error");
176 tapePlaying = false;
177 break;
179 tapeCurrTState += tstates;
180 if (flags&LIBSPECTRUM_TAPE_FLAGS_LEVEL_LOW) tapeLastEdge = false;
181 else if (flags&LIBSPECTRUM_TAPE_FLAGS_LEVEL_HIGH) tapeLastEdge = true;
182 else if ((flags&LIBSPECTRUM_TAPE_FLAGS_NO_EDGE) == 0) tapeLastEdge = !tapeLastEdge;
183 if (flags&(LIBSPECTRUM_TAPE_FLAGS_TAPE|LIBSPECTRUM_TAPE_FLAGS_STOP)) { conwriteln("tape: stop10"); tapePlaying = false; }
184 if (flags&LIBSPECTRUM_TAPE_FLAGS_STOP48 && tapeCurrentIs48K) { conwriteln("tape: stop11"); tapePlaying = false; }
185 if (flags&LIBSPECTRUM_TAPE_FLAGS_TAPE) { conwriteln("tape complete"); tapeComplete = true; break; }
186 ts += tstates;
187 if (tapeLastEdge != curedge) return ts;
189 return -1; // oops
193 // ////////////////////////////////////////////////////////////////////////// //
194 shared static this () {
195 // tape
196 conRegVar!optDebugFlashLoad("tape_debug_flashload", "show FlashLoader(utm) debug messages?");
198 conRegVar!optTapeFlashLoad("tape_flashload", "is FlashLoader(utm) enabled?");
199 conRegVar!optTapeDetector("tape_detect_loaders", "detect loaders and automatically start/stop tape?");
200 conRegVar!optAutoMaxSpeed("tape_auto_max_speed", "switch emu to max speed when loader detected?");
202 conRegVar!tapePlaying("tape_is_playing", "is tape playing?");
204 conRegVar!optTapeShowProgress("tape_progress", "show tape progress?");
205 conRegVar!optTapeSlowScan("tape_slow_scan", "use slow, but more precise tape length scanner?");
206 conRegVar!optTapeAutoRewind("tape_autorewind", "autorewind and continue playing on tape end?");
208 static if (LibSpectrumHere) {
209 conRegFunc!emuStartTape("tape_play", "play tape");
210 conRegFunc!emuStopTape("tape_stop", "stop tape");
211 conRegFunc!emuRewindTape("tape_rewind", "rewind tape");
216 // ////////////////////////////////////////////////////////////////////////// //
217 public void emuEjectTape () nothrow {
218 emuStopTape();
219 if (curtape !is null) { libspectrum_tape_free(curtape); curtape = null; }
220 tapeBlockCount = tapeBlockLastSeen = 0;
221 tapeCurrTState = tapeTotalTStates = 0;
222 tapeAutostarted = false;
223 tapeComplete = false;
227 void emuStopTape () nothrow {
228 if (tapePlaying) {
229 conwriteln("tape: stopped");
230 emuTapeAcceleratorStop();
231 tapePlaying = false;
232 tapeLastEdge = false;
233 tapeNextInFrame = -1;
234 if (tapeAutostarted && optAutoMaxSpeed) Config.maxSpeed = false;
236 tapeAutostarted = false;
240 void emuStartTape () nothrow @nogc {
241 if (curtape && libspectrum_tape_present(curtape) && !tapePlaying && !tapeComplete) {
242 tapePlaying = true;
243 emuTapeAcceleratorStart();
245 tapeAutostarted = false;
249 void emuStartTapeAuto () nothrow {
250 bool oldpl = false;
251 emuStartTape();
252 if (tapePlaying) {
253 tapeAutostarted = true;
254 if (!oldpl) {
255 conwriteln("tape autostarted");
256 if (optAutoMaxSpeed) Config.maxSpeed = true;
262 public void emuRewindTape () nothrow {
263 tapeAutostarted = false;
264 tapeComplete = false;
265 tapeBlockCount = tapeBlockLastSeen = 0;
266 tapeCurrTState = tapeTotalTStates = 0;
267 if (curtape && libspectrum_tape_present(curtape)) {
268 if (auto last = libspectrum_tape_peek_last_block(curtape)) {
269 libspectrum_tape_nth_block(curtape, 0);
270 if (!optTapeSlowScan) tapeTotalTStates += libspectrum_tape_block_length(libspectrum_tape_current_block(curtape));
271 ++tapeBlockCount;
272 while (libspectrum_tape_current_block(curtape) !is last) {
273 if (libspectrum_tape_select_next_block(curtape) is null) break;
274 if (!optTapeSlowScan) tapeTotalTStates += libspectrum_tape_block_length(libspectrum_tape_current_block(curtape));
275 ++tapeBlockCount;
277 // slow scanner
278 if (optTapeSlowScan) {
279 libspectrum_tape_nth_block(curtape, 0);
280 int lastBlock = 0;
281 tapeTotalTStates = 0;
282 for (;;) {
283 int flags = 0;
284 uint tstates = 0;
285 auto tres = libspectrum_tape_get_next_edge(&tstates, &flags, curtape);
286 if (tres != LIBSPECTRUM_ERROR_NONE && tres != LIBSPECTRUM_ERROR_WARNING) break;
287 int cblk;
288 tres = libspectrum_tape_position(&cblk, curtape);
289 if (tres != LIBSPECTRUM_ERROR_NONE && tres != LIBSPECTRUM_ERROR_WARNING) break;
290 if (cblk != lastBlock) {
291 lastBlock = cblk;
292 if (cblk == 0) break;
294 tapeTotalTStates += tstates;
298 libspectrum_tape_nth_block(curtape, 0);
299 conwriteln("tape rewound (", tapeBlockCount, " blocks; ", tapeTotalTStates, " tstates)");
300 if (tapePlaying) emuTapeAcceleratorStart();
305 // ////////////////////////////////////////////////////////////////////////// //
306 public void emuTapeLoaderDetector () nothrow {
307 int tdelta = emuz80.tstates-tapDetectLastInTS;
308 ubyte bdelta = (cast(int)emuz80.BC.b-tapDetectLastInB)&0xff;
310 tapDetectLastInTS = emuz80.tstates;
311 tapDetectLastInB = emuz80.BC.b;
313 if (optTapeDetector) {
314 // detector is on
315 if (tapePlaying) {
316 // tape is playing
317 if (tdelta > 1000 || (bdelta != 1 && bdelta != 0 && bdelta != 0xff)) {
318 if (++tapDetectInCount >= 2) {
319 // seems that this is not a loader anymore
320 if (tapeAutostarted) {
321 if (tapePlaying) conwriteln("tape autostopped");
322 emuStopTape();
325 } else {
326 tapDetectInCount = 0;
328 } else {
329 // tape is not playing
330 if (tdelta <= 500 && (bdelta == 1 || bdelta == 0xff)) {
331 if (curtape !is null && !tapeNeedRewind) {
332 if (++tapDetectInCount >= 10) {
333 // seems that this is LD-EDGE
334 emuStartTapeAuto();
336 } else {
337 tapDetectInCount = 0;
339 } else {
340 tapDetectInCount = 0;
343 } else {
344 // detector is off
345 tapDetectInCount = 0;
348 if (tapePlaying) emuTapeCheckForAcceleration();
352 // ////////////////////////////////////////////////////////////////////////// //
353 __gshared int lengthKnown1 = 0, lengthKnown2 = 0;
354 __gshared int lengthLong1 = 0, lengthLong2 = 0;
356 __gshared int tapNextEdgeTS; // can exceed tsperframe or be negative
357 __gshared int tapDetectInCount = 0;
358 __gshared int tapDetectLastInTS = -666;
359 __gshared int tapDetectLastInB = 0x00;
362 enum TapeAccelMode {
363 None = 0,
364 Increasing,
365 Decreasing,
369 __gshared TapeAccelMode accelerationMode;
370 __gshared ushort accelerationPC;
373 public void emuTapeAcceleratorStart () nothrow @nogc {
374 tapDetectInCount = 0;
375 accelerationMode = TapeAccelMode.None;
379 public void emuTapeAcceleratorStop () nothrow @nogc {
380 tapDetectInCount = 0;
381 accelerationMode = TapeAccelMode.None;
385 public void emuTapeAcceleratorEdgeAdvance (int flags) nothrow @nogc {
386 if (flags&LIBSPECTRUM_TAPE_FLAGS_LENGTH_SHORT) {
387 lengthKnown2 = 1;
388 lengthLong2 = 0;
389 } else if (flags&LIBSPECTRUM_TAPE_FLAGS_LENGTH_LONG) {
390 lengthKnown2 = 1;
391 lengthLong2 = 1;
392 } else {
393 lengthKnown2 = 0;
398 void emuTapeDoAcceleration () nothrow {
399 if (lengthKnown1) {
400 emuz80.BC.b = ((lengthLong1^(accelerationMode == TapeAccelMode.Decreasing)) ? 0xfe : 0x00);
401 emuz80.AF.f |= 0x01;
402 emuz80.PC = emuz80.pop();
403 //tape_next_edge(z80.tstates, 0, NULL);
404 tapNextEdgeTS = emuz80.tstates;
405 tapDetectInCount = 0;
406 zxBorderColor = 0;
408 lengthKnown1 = lengthKnown2;
409 lengthLong1 = lengthLong2;
413 TapeAccelMode emuTapeAccelerationDetector (ushort pc) nothrow {
414 int state = 0, count = 0;
415 for (;;) {
416 ubyte b = emuz80.memPeekB(pc);
417 pc = (pc+1)&0xffff;
418 ++count;
419 switch (state) {
420 case 0:
421 switch (b) {
422 case 0x04: state = 1; break; // INC B - Many loaders
423 default: state = 13; break; // Possible Digital Integration
425 break;
426 case 1:
427 switch (b) {
428 case 0xc8: state = 2; break; // RET Z
429 default: return TapeAccelMode.None;
431 break;
432 case 2:
433 switch (b) {
434 case 0x3e: state = 3; break; // LD A,nn
435 default: return TapeAccelMode.None;
437 break;
438 case 3:
439 switch (b) {
440 case 0x00: // Search Loader
441 case 0x7f: // ROM loader and variants
442 state = 4; break; // Data byte
443 default: return TapeAccelMode.None;
445 break;
446 case 4:
447 switch (b) {
448 case 0xdb: state = 5; break; // IN A,(nn)
449 default: return TapeAccelMode.None;
451 break;
452 case 5:
453 switch (b) {
454 case 0xfe: state = 6; break; // Data byte
455 default: return TapeAccelMode.None;
457 break;
458 case 6:
459 switch (b) {
460 case 0x1f: state = 7; break; // RRA
461 case 0xa9: state = 24; break; // XOR C - Search Loader
462 default: return TapeAccelMode.None;
464 break;
465 case 7:
466 switch (b) {
467 case 0x00: // NOP - Bleepload
468 case 0xa7: // AND A - Microsphere
469 case 0xc8: // RET Z - Paul Owens
470 case 0xd0: // RET NC - ROM loader
471 state = 8; break;
472 case 0xa9: state = 9; break; // XOR C - Speedlock
473 default: return TapeAccelMode.None;
475 break;
476 case 8:
477 switch (b) {
478 case 0xa9: state = 9; break; // XOR C
479 default: return TapeAccelMode.None;
481 break;
482 case 9:
483 switch (b) {
484 case 0xe6: state = 10; break; // AND nn
485 default: return TapeAccelMode.None;
487 break;
488 case 10:
489 switch (b) {
490 case 0x20: state = 11; break; // Data byte
491 default: return TapeAccelMode.None;
493 break;
494 case 11:
495 switch (b) {
496 case 0x28: state = 12; break; // JR nn
497 default: return TapeAccelMode.None;
499 break;
500 case 12:
501 return (b == 0x100-count ? TapeAccelMode.Increasing : TapeAccelMode.None);
502 /* Digital Integration loader */
503 case 13:
504 state = 14; break; // Possible Digital Integration
505 case 14:
506 switch (b) {
507 case 0x05: state = 15; break; // DEC B - Digital Integration
508 default: return TapeAccelMode.None;
510 break;
511 case 15:
512 switch (b) {
513 case 0xc8: state = 16; break; // RET Z
514 default: return TapeAccelMode.None;
516 break;
517 case 16:
518 switch (b) {
519 case 0xdb: state = 17; break; // IN A,(nn)
520 default: return TapeAccelMode.None;
522 break;
523 case 17:
524 switch (b) {
525 case 0xfe: state = 18; break; // Data byte
526 default: return TapeAccelMode.None;
528 break;
529 case 18:
530 switch (b) {
531 case 0xa9: state = 19; break; // XOR C
532 default: return TapeAccelMode.None;
534 break;
535 case 19:
536 switch (b) {
537 case 0xe6: state = 20; break; // AND nn
538 default: return TapeAccelMode.None;
540 break;
541 case 20:
542 switch (b) {
543 case 0x40: state = 21; break; // Data byte
544 default: return TapeAccelMode.None;
546 break;
547 case 21:
548 switch (b) {
549 case 0xca: state = 22; break; // JP Z,nnnn
550 default: return TapeAccelMode.None;
552 break;
553 case 22: // LSB of jump target
554 if (b == ((cast(int)emuz80.PC-4)&0xff)) {
555 state = 23;
556 } else {
557 return TapeAccelMode.None;
559 break;
560 case 23: // MSB of jump target
561 return (b == (((cast(int)emuz80.PC-4)&0xff00)>>8) ? TapeAccelMode.Decreasing : TapeAccelMode.None);
562 /* Search loader */
563 case 24:
564 switch (b) {
565 case 0xe6: state = 25; break; // AND nn
566 default: return TapeAccelMode.None;
568 break;
569 case 25:
570 switch (b) {
571 case 0x40: state = 26; break; // Data byte
572 default: return TapeAccelMode.None;
574 break;
575 case 26:
576 switch (b) {
577 case 0xd8: state = 27; break; // RET C
578 default: return TapeAccelMode.None;
580 break;
581 case 27:
582 switch (b) {
583 case 0x00: state = 11; break; // NOP
584 default: return TapeAccelMode.None;
586 break;
587 default: assert(0); // can't happen
593 void emuTapeCheckForAcceleration () nothrow {
594 // if the IN occured at a different location to the one we're accelerating, stop acceleration
595 if (accelerationMode && emuz80.PC != accelerationPC) accelerationMode = TapeAccelMode.None;
596 // if we're not accelerating, check if this is a loader
597 if (!accelerationMode) {
598 accelerationMode = emuTapeAccelerationDetector((cast(int)emuz80.PC-6)&0xffff);
599 accelerationPC = emuz80.PC;
601 if (accelerationMode != TapeAccelMode.None) {
602 //fprintf(stderr, "ACCELMODE: %d\n", (int)acceleration_mode);
603 emuTapeDoAcceleration();
608 // ////////////////////////////////////////////////////////////////////////// //
609 // FlashLoader(utm): not taken from FUSE, entirely mine shitcode!
611 // custom flashloaders
612 enum {
613 CFL_NO_TYPE = 0x0001, // no type byte, assumes CFL_FLAG_ANY_TYPE
614 CFL_NO_PARITY = 0x0002, // no parity byte
615 CFL_ANY_TYPE = 0x0004, // don't check block type in A
616 CFL_REVERSE = 0x0008, // loading in reverse direction, first decrement IX
617 CFL_NO_LEN_GLITCH = 0x0010, // no 'loader len' glitch
618 CFL_PARITY_FIRST = 0x0020, // parity byte comes before block
622 struct LoaderTimings {
623 int pilot_minlen; // 807
624 int pilot; // 2168
625 int sync1; // 667
626 int sync2; // 735
627 int bit0; // 855
628 int bit1; // 1710
629 int init_parity; // >0: init parity with this
630 uint flags; // CFL_xxx
634 // ////////////////////////////////////////////////////////////////////////// //
635 bool checkByte (int addr, ubyte v) nothrow @trusted {
636 pragma(inline, true);
637 return (emuz80.memPeekB(addr&0xffff) == v);
641 bool checkWord (int addr, ushort v) nothrow @trusted {
642 pragma(inline, true);
643 return
644 emuz80.memPeekB(addr&0xffff) == (v&0xff) &&
645 emuz80.memPeekB((addr+1)&0xffff) == ((v>>8)&0xff);
649 ushort getWord (int addr) nothrow @trusted {
650 pragma(inline, true);
651 return cast(ushort)(emuz80.memPeekB(addr&0xffff)|(emuz80.memPeekB((addr+1)&0xffff)<<8));
655 ////////////////////////////////////////////////////////////////////////////////
656 /* legend:
657 * <0: new negative address offset
658 * 0: next is 0 too? exit, else new positive address offset
659 * >0xff && <0x8000: check swapped word
660 * -32764 -- positive offset from the current address
661 * -32767 -- NOP
662 * -32666 -- skip this byte
664 int isPatternOk(bool showData=false) (int addr, const(short)[] pattern) nothrow @trusted {
665 usize ppos = 0;
666 int a = addr;
667 while (ppos < pattern.length) {
668 short p = pattern.ptr[ppos++];
669 if (p == -32764) {
670 // positive offset from the current address
671 if (ppos >= pattern.length) return true; // end of data, ok
672 p = pattern.ptr[ppos++];
673 a = (a+p)&0xffff;
674 if (optDebugFlashLoad) conwritefln!" pattern #%04X: #%04X (%d)"(addr, a, p);
675 } else if (p <= 0 && p > -32000) {
676 // new offset or end
677 if (p == 0) {
678 // positive offset or end
679 if (ppos >= pattern.length) return true; // end of data, ok
680 p = pattern.ptr[ppos++];
681 if (p == 0) return true; // end of data, ok
683 a = (addr+p)&0xffff;
684 if (optDebugFlashLoad) conwritefln!" pattern #%04X: #%04X (%d)"(addr, a, p);
685 } else if (p != -32666) {
686 if (p == -32767) p = 0;
687 static if (showData) conwritefln!"p=0x%04x"(p);
688 if (p > 0xff) {
689 if (emuz80.memPeekB(a&0xffff) != ((p>>8)&0xff)) return 0;
690 a = (a+1)&0xffff;
691 if (emuz80.memPeekB(a&0xffff) != (p&0xff)) return 0;
692 } else {
693 if (emuz80.memPeekB(a&0xffff) != p) return 0;
695 a = (a+1)&0xffff;
696 } else {
697 a = (a+1)&0xffff;
700 return true; // end of data, ok
704 ////////////////////////////////////////////////////////////////////////////////
705 struct LoaderInfo {
706 string name;
707 immutable short[] pattern;
708 bool function () nothrow detectFn;
709 bool function () nothrow accelFn;
710 int exitOfs; // 0: detect
714 ////////////////////////////////////////////////////////////////////////////////
715 static immutable LoaderInfo[$] knownLoaders = [
716 LoaderInfo("ROM Loader", null, null, &emuTapeDoROMLoad, 123), // 0x564
717 LoaderInfo("Edge Loader", pattern_edge, null, &do_edge, 105),
718 LoaderInfo("Flash Load", pattern_flashload, null, &doFlashLoad, 114),
719 LoaderInfo("FTL Loader", pattern_ftl, null, &doFTL, 131),
720 LoaderInfo("Hewson Slowload", pattern_hewson, null/*&isHewson*/, &emuTapeDoROMLoad, 128),
721 LoaderInfo("Elite UniLoader", pattern_uniloader, &isUniLoader, &emuTapeDoROMLoad, 116),
722 LoaderInfo("Players2 Loader", pattern_pl2, null, &do_pl2, 121),
723 //LoaderInfo("SetoLOAD Loader", pattern_seto, null, &do_seto, 123),
727 ////////////////////////////////////////////////////////////////////////////////
729 * <0: new negative address offset
730 * 0: next is 0 too? exit, else new positive address offset
731 * >0xff && <0x8000: check swapped word
732 * -32767 -- NOP
733 * -32666 -- skip this byte
735 static immutable short[$] pattern_seto = [
736 -15,
737 0x37, // SCF
738 0x14, // INC D
739 0x08, // EX AF,AF'
740 0x15, // DEC D
741 0xF3, // DI
742 0x3E, -32666, // LD A,xx
743 0xD3, 0xFE, // OUT (#FE),A
744 0x21, -32666, -32666, // LD HL,xxx
745 0xE5, // PUSH HL
746 0xDB, 0xFE, // IN A,(#FE)
747 0x1F, // RRA
748 -32764, 7, 0xCD, // CALL LD-EDGE2
749 -32764, 14, 0xCD, // CALL LD-EDGE2
750 // check constants
751 -32764, 4, 0x069C, // LD B,#9C
752 -32764, 5, 0x3EC6, // LD A,#C6
753 -32764, 6, 0x06C9, // LD B,#C9
754 -32764, 6, 0xFE, 0xD4, // CP #D4
755 -32764, 12, 0x0658, // LD B,#58
756 //k8: enough, i am sooo lazy
757 // end
758 0, 0
762 bool do_seto () nothrow {
763 static immutable LoaderTimings timing = {
764 pilot_minlen: 807, // stdlen: 3223
765 pilot: 2168,
766 sync1: 714,
767 sync2: 714,
768 bit0: 454,
769 bit1: 907,
771 return emuTapeDoFlashLoadEx(timing);
775 ////////////////////////////////////////////////////////////////////////////////
776 static immutable short[$] pattern_edge = [
777 -10,
778 0xC5, // PUSH BC
779 0xDD, 0xE1, // POP IX
780 0xF3, // DI
781 0x3E, -32666, // LD A,xx
782 0xD3, 0xFE, // OUT (#FE),A
783 0, 10, 0xCD, 0xE7, 0x05, // CALL LD-EDGE1
784 0, 22, 0xCD, 0xE3, 0x05, // CALL LD-EDGE2
785 // check constants
786 0, 31, 0x069C, // LD B,#9C
787 0, 38, 0x3EC6, // LD A,#C6
788 0, 46, 0x06C9, // LD B,#C9
789 0, 54, 0xFE, 0xD4, // CP #D4
790 0, 79, 0x06B2, // LD B,#B2
791 0, 88, 0x3EBD, // LD A,#BD
792 0, 115, 0xED, 0x56, // IM 1
793 // exit
794 0, 105,
795 0x7C, // LD A,H
796 0xB7, // OR A
797 0x20, -32666, // JR NZ, ...
798 0xE1, // POP HL
799 0xC1, // POP BC
800 0x05, // DEC B
801 // end
802 0, 0
806 bool do_edge () nothrow {
807 static immutable LoaderTimings timing = {
808 pilot_minlen: 100,
809 pilot: 2168,
810 sync1: 667,
811 sync2: 735,
812 bit0: 550,
813 bit1: 1110,
814 flags: CFL_NO_TYPE
816 return emuTapeDoFlashLoadEx(timing);
820 ////////////////////////////////////////////////////////////////////////////////
821 static immutable short[$] pattern_flashload = [
822 -4, 0xD3, 0xFE, // OUT (#FE),A
823 -14,
824 0x14, // INC D
825 0x08, // EX AF,AF'
826 0x15, // DEC D
827 0xDD, 0x19, // ADD IX,DE
828 0xDD, 0x2B, // DEC IX
829 0xF3, // DI
830 0, 6, 0xCD, // CALL LD-EDGE2
831 0, 21, 0xCD, // CALL LD-EDGE
832 0, 114,
833 0x7C, // LD A,H
834 0xFE, 0x01, // CP #01
835 0xC9, // RET
836 // check constants
837 0, 26, 0x069C, // LD B,#9C
838 0, 33, 0x3EC6, // LD A,#C6
839 0, 41, 0x06C9, // LD B,#C9
840 0, 49, 0xFE, 0xD4, // CP #D4
841 0, 63, 0x06D0, // LD B,#D0
842 0, 89, 0x06D2, // LD B,#D2
843 0, 97, 0x3ED7, // LD A,#D7
844 0, 102, 0x06D0, // LD B,#D0
845 // end
846 0, 0
850 bool doFlashLoad () nothrow {
851 static immutable LoaderTimings timing = {
852 pilot_minlen: 807,
853 pilot: 2168,
854 sync1: 667,
855 sync2: 735,
856 bit0: 543,
857 bit1: 839,
858 flags: CFL_REVERSE,
860 return emuTapeDoFlashLoadEx(timing);
864 ////////////////////////////////////////////////////////////////////////////////
865 static immutable short[$] pattern_ftl = [
866 -4, 0xD3, 0xFE, // OUT (#FE),A
867 -12,
868 0x263D, // LD H,#3D
869 0x01, 0x0140, // LD BC,#4001
870 0xD9, // EXX
871 0, 8, 0xCD, // CALL LD-EDGE2
872 0, 23, 0xCD, // CALL LD-EDGE
873 0, 131,
874 0x7C, // LD A,H
875 0xFE, 0x01, // CP #01
876 0x21, // LD HL,...
877 0, 137,
878 0xE5, // PUSH HL
879 0xC3, // JP ...
880 // check constants
881 0, 28, 0x069C, // LD B,#9C
882 0, 35, 0x3EC6, // LD A,#C6
883 0, 43, 0x06C9, // LD B,#C9
884 0, 51, 0xFE, 0xD4, // CP #D4
885 0, 72, 0x06B0, // LD B,#B0
886 0, 106, 0x06B0, // LD B,#B0
887 0, 114, 0x3ED4, // LD A,#D4
888 0, 119, 0x06B0, // LD B,#B0
889 // end
890 0, 0
894 bool doFTL () nothrow {
895 static immutable LoaderTimings timing = {
896 pilot_minlen: 807,
897 pilot: 2168,
898 sync1: 667,
899 sync2: 735,
900 bit0: 829,
901 bit1: 1658,
903 return emuTapeDoFlashLoadEx(timing);
907 ////////////////////////////////////////////////////////////////////////////////
908 // Hewson Slowload
909 static immutable short[$] pattern_hewson = [
910 -4, 0xD3, 0xFE, // OUT (#FE),A
911 -14,
912 0x14, // INC D
913 0x08, // EX AF,AF'
914 0x15, // DEC D
915 0xF3, // DI
916 0xD9, // EXX
917 0, 8, 0xCD, // CALL LD-EDGE2
918 0, 23, 0xCD, // CALL LD-EDGE
919 0, 128,
920 0x7C, // LD A,H
921 0xFE, 0x01, // CP #01
922 0xC9, // RET
923 // check constants
924 0, 28, 0x069C, // LD B,#9C
925 0, 35, 0x3EC6, // LD A,#C6
926 0, 43, 0x06C9, // LD B,#C9
927 0, 51, 0xFE, 0xD4, // CP #D4
928 0, 70, 0x06B0, // LD B,#B0
929 0, 103, 0x06B2, // LD B,#B2
930 0, 111, 0x3ECB, // LD A,#CB
931 0, 116, 0x06B0, // LD B,#B0
932 // end
933 0, 0
938 static int isHewson (void) {
939 int addr = z80.pc;
940 //if (z80.ix.w <= addr && z80.ix.w+z80.de.w >= addr) return 0; // don't accelerate self-modifying loaders
941 return 1;
946 ////////////////////////////////////////////////////////////////////////////////
947 static immutable short[$] pattern_pl2 = [
948 -15,
949 0xAF, // XOR A
950 0x37, // SCF
951 0x14, // INC D
952 0x08, // EX AF,AF'
953 0x15, // DEC D
954 0x3E, -32666, // LD A,xx
955 0xD3, 0xFE, // OUT (#FE),A
956 0x21, -32666, -32666, // LD HL,xxx
957 0xE5, // PUSH HL
958 0, 6, 0xCD, // CALL LD-EDGE2
959 0, 21, 0xCD, // CALL LD-EDGE
960 // check constants
961 0, 26, 0x069C, // LD B,#9C
962 0, 33, 0x3EC6, // LD A,#C6
963 0, 41, 0x06C9, // LD B,#C9
964 0, 49, 0xFE, 0xD4, // CP #D4
965 0, 63, 0x067B, // LD B,#7B
966 0, 96, 0x067D, // LD B,#7D
967 0, 104, 0x3E8E, // LD A,#8E
968 0, 109, 0x067B, // LD B,#7B
969 // exit
970 0, 121,
971 0x7C, // LD A,H
972 0xFE, 0x01, // CP #01
973 0xC9, // RET
974 // end
975 0, 0
979 bool do_pl2 () nothrow {
980 static immutable LoaderTimings timing = {
981 pilot_minlen: 807,
982 pilot: 2168,
983 sync1: 667,
984 sync2: 735,
985 bit0: 560,
986 bit1: 1120,
988 return emuTapeDoFlashLoadEx(timing);
992 ////////////////////////////////////////////////////////////////////////////////
993 static immutable short[$] pattern_uniloader = [
994 -4, 0xD3, 0xFE, // OUT (#FE),A
995 -10,
996 0x14, // INC D
997 0x08, // EX AF,AF'
998 0x15, // DEC D
999 0xF3, // DI
1000 0, 6, 0xCD, // CALL LD-EDGE2
1001 0, 21, 0xCD, // CALL LD-EDGE
1002 0, 116,
1003 0x7C, // LD A,H
1004 0xFE, 0x01, // CP #01
1005 0xD8, // RET C
1006 // check constants
1007 0, 26, 0x069C, // LD B,#9C
1008 0, 33, 0x3EC6, // LD A,#C6
1009 0, 41, 0x06C9, // LD B,#C9
1010 0, 49, 0xFE, 0xD4, // CP #D4
1011 0, 64, 0x06B0, // LD B,#B0
1012 0, 91, 0x06B2, // LD B,#B2
1013 0, 100, 0x3ECB, // LD A,#CB
1014 0, 105, 0x06B0, // LD B,#B0
1015 // end
1016 0, 0
1020 // Elite UniLoader
1021 bool isUniLoader () nothrow {
1022 ushort lde, lde2;
1023 int addr = emuz80.PC;
1024 //if (z80.ix.w <= addr && z80.ix.w+z80.de.w >= addr) return 0; // don't accelerate self-modifying loaders
1025 // address of LD-EDGE & LD-EDGE2
1026 lde2 = getWord(addr+7); // LD-EDGE2 address
1027 lde = getWord(addr+22); // LD-EDGE address
1028 if (lde2-lde != 4) return false;
1029 // check LD-EDGE
1030 if (!checkByte(lde, 0xCD)) return false; // CALL
1031 if (!checkWord(lde+1, lde2)) return false;
1032 if (!checkWord(lde2, 0x163E)) return false; // LD A,0x16
1033 return true;
1037 ////////////////////////////////////////////////////////////////////////////////
1038 bool executeLoader() (ushort addr, in auto ref LoaderInfo li) nothrow {
1039 static int loadErrorCount = 0;
1040 if (optDebugFlashLoad) conwritefln!"+++ LOADER: %s"(li.name);
1041 conwriteln("detected loader: ", li.name);
1042 if (!tapePlaying) emuStartTapeAuto();
1043 if (li.accelFn()) {
1044 if (emuz80.HL.h) {
1045 // prevent endless loading error loops
1046 conwriteln("*** tape loading error");
1047 if (++loadErrorCount >= 16) {
1048 loadErrorCount = 0;
1049 optTapeFlashLoad = false;
1051 } else {
1052 loadErrorCount = 0;
1054 emuz80.PC = cast(ushort)(addr+li.exitOfs);
1055 emuStopTape();
1056 //if (optDebugFlashLoad) dbgSetActive(1);
1057 return true;
1059 conwriteln("block not found!");
1060 emuStopTape();
1061 //if (optDebugFlashLoad) dbgSetActive(1);
1062 return false;
1066 public bool emuTapeFlashLoad () nothrow {
1067 if (!optTapeFlashLoad) return false;
1068 if (!curtape || !libspectrum_tape_present(curtape) || tapeComplete) return false;
1069 int addr = emuz80.PC;
1070 if (addr == 0x0564) return executeLoader(addr&0xffff, LoaderInfo("ROM Loader", null, null, &emuTapeDoROMLoad, 123));
1071 if (addr < 0x4000) return false; // ROM
1072 //if ((z80.afx.f&ZYM_FLAG_C) == 0) return -1; // don't accelerate VERIFY mode
1073 if (!checkByte(addr, 0x1F)) return false; //RRA?
1074 // check for SetoLOAD
1075 // not yet
1077 if (checkWord(addr-2, 0xFEDB) && // IN (#FE),A?
1078 checkByte(addr-3, 0xE5) && // PUSH HL
1079 checkWord(addr-5, 0x053F) && checkByte(addr-6, 0x21)) // LD HL,#053F
1081 conwriteln("SETOLOAD!");
1082 //return false;
1083 if (isPatternOk(addr, pattern_seto)) {
1084 static immutable LoaderInfo setoli = LoaderInfo("SetoLOAD loader", pattern_seto, null, &do_pl2, 123);
1085 return executeLoader(addr&0xffff, setoli);
1089 if (!checkWord(addr-4, 0xFED3) && // OUT (#FE),A?
1090 !checkByte(addr-3, 0xE5)) return false; // PUSH HL (elite loader)
1091 //if (optDebugFlashLoad) { dbgSetActive(1); return -1; }
1092 //fprintf(stderr, "trying to detect known loader at 0x%04x\n", addr);
1093 foreach (immutable ref li; knownLoaders) {
1094 //const LoaderInfo *li = detectors[f];
1095 //if (optDebugFlashLoad) fprintf(stderr, "*** trying loader #%u: %s (#%04X)\n", f, li.name, addr);
1096 if (li.pattern.length == 0 && li.detectFn is null) continue;
1097 if (li.pattern.length && !isPatternOk(addr, li.pattern)) continue;
1098 //if (optDebugFlashLoad) fprintf(stderr, " pattern ok\n");
1099 if (li.detectFn !is null && !li.detectFn()) continue;
1100 return executeLoader(addr&0xffff, li);
1102 return false;
1106 ////////////////////////////////////////////////////////////////////////////////
1107 enum {
1108 TAPE_ROM_BIT0_TS = 855,
1109 TAPE_ROM_BIT1_TS = 1710,
1110 TAPE_ROM_SYNC1_TS = 667,
1111 TAPE_ROM_SYNC2_TS = 735,
1112 TAPE_ROM_TAIL_TS = 945,
1113 TAPE_ROM_PILOT_TS = 2168
1117 ////////////////////////////////////////////////////////////////////////////////
1118 bool goodTiming (const int ts, const int timing, int tolerance) nothrow @nogc {
1119 import std.math : abs;
1120 pragma(inline, true);
1121 return (abs(ts-timing) <= tolerance);
1125 // -1: tape not running
1126 // -2: tape loading error (tape MAY BE stopped)
1127 int emuTapeLoadByte (int bit0ts, int bit1ts) nothrow {
1128 import std.math : abs;
1129 if (curtape !is null && tapePlaying) {
1131 static int strobe_debug = -1;
1132 if (strobe_debug < 0) {
1133 const char *var = getenv("ZXEMUT_DEBUG_STROBE");
1134 strobe_debug = (var != NULL && var[0]);
1137 int b = 0x01; // 'stop' flag
1138 int tolerance = abs(bit0ts-bit1ts)/4;
1139 if (tolerance < 18) tolerance = 18;
1140 while (b < 0x100) {
1141 int ts = emuTapeLdrGetNextPulse();
1142 //conwriteln("00: loading pulse(", bit0ts, ",", bit1ts, "): ts=", ts);
1143 if (ts < 0) return -2; // epic fail
1144 if (goodTiming(ts, bit0ts, tolerance) || goodTiming(ts, bit1ts, tolerance)) {
1145 // half-bit found, check the second half
1146 int ts1 = emuTapeLdrGetNextPulse();
1147 //conwriteln("01: loading pulse(", bit0ts, ",", bit1ts, "): ts=", ts1);
1148 if (ts1 < 0 || abs(ts1-ts) > tolerance) {
1149 //if (strobe_debug) fprintf(stderr, " bad pulse 1 timing: %d (0:%d, 1:%d)\n", ts, bit0ts, bit1ts);
1150 return -2; // epic fail
1152 //if (strobe_debug) fprintf(stderr, " strobe timing: %d, %d (0:%d, 1:%d) value=%d\n", ts, ts1, bit0ts, bit1ts, (ts >= bit0ts+tolerance ? 1 : 0));
1153 } else {
1154 //if (strobe_debug) fprintf(stderr, " bad pulse 0 timing: %d (0:%d, 1:%d)\n", ts, bit0ts, bit1ts);
1155 return -2; // epic fail
1157 b = (b<<1)|(ts >= bit0ts+tolerance ? 1 : 0);
1159 b &= 0xff;
1160 //if (optDebugFlashLoad) conwritefln!"byte loaded: 0x%02x (%u)"(b, b);
1161 return b;
1163 //if (optDebugFlashLoad) conwriteln("byte loading failed");
1164 return -1;
1168 // -1: tape not running
1169 // -2: tape loading error (tape MAY BE stopped)
1170 int emuTapeROMLoadByte (void) nothrow {
1171 return emuTapeLoadByte(TAPE_ROM_BIT0_TS, TAPE_ROM_BIT1_TS);
1175 // return:
1176 // false: pilot or block id searching fails (so just continue executing)
1177 // true: something was loaded (with or without error); execute loader epilogue
1178 bool emuTapeDoFlashLoadEx() (in auto ref LoaderTimings ti) nothrow {
1179 import std.math : abs;
1180 int bt, ts, parity = (ti.init_parity < 0 ? 0 : ti.init_parity&0xff);
1181 ubyte flag = emuz80.AFx.a;
1182 int len = emuz80.DE.w, addr = emuz80.IX.w;
1183 if (len == 0) len = 65536;
1184 if (optDebugFlashLoad) conwritefln!"flashload: flags=0x%04x; type=#%02X; addr=#%04X, len=%d"(ti.flags, flag, emuz80.IX.w, len);
1185 emuz80.tstates = 0;
1186 tapeNextInFrame = 0;
1187 int badPilotsCount = 0;
1188 skip_block:
1189 if (optDebugFlashLoad) conwriteln("searching for pilot...");
1190 // search for pilot
1191 if (ti.pilot_minlen > 0) {
1192 int pilotPulses = 0;
1193 int ptolerance = ti.pilot/4;
1194 int stolerance = abs(ti.sync1-ti.sync2)/1-8; // IDIOTIC MetalArmy.tzx!
1195 if (ptolerance < 18) ptolerance = 18;
1196 if (stolerance < 18) stolerance = 18;
1197 if (optDebugFlashLoad) conwritefln!"pilot tolerance: ts=%d; tolerance=%d"(ti.pilot, ptolerance);
1198 if (optDebugFlashLoad) conwritefln!"sync tolerance: ts=%d,%d; tolerance=%d"(ti.sync1, ti.sync2, stolerance);
1199 for (;;) {
1200 if ((ts = emuTapeLdrGetNextPulse()) < 0) return false; // end of tape, nothing was found
1201 if (goodTiming(ts, ti.pilot, ptolerance)) { ++pilotPulses; continue; }
1202 //fprintf(stderr, "pilot ts: %d\n", ts);
1203 // was pilot of enough length found?
1204 if (pilotPulses >= ti.pilot_minlen) {
1205 if (optDebugFlashLoad) conwritefln!"pilot; length=%d (%s)"(pilotPulses, (pilotPulses >= ti.pilot_minlen ? "good" : "BAD"));
1206 // sheck sync1
1207 if (goodTiming(ts, ti.sync1, stolerance)) {
1208 // sheck sync2
1209 if (optDebugFlashLoad) conwritefln!"GOOD sync1: %d"(ts);
1210 if ((ts = emuTapeLdrGetNextPulse()) < 0) return false; // end of tape, nothing was found
1211 // checking for SYNC1 too -- idiotic MetalArmy.tzx sucks cocks!
1212 if (goodTiming(ts, ti.sync2, stolerance) || goodTiming(ts, ti.sync1, stolerance)) {
1213 if (optDebugFlashLoad) conwritefln!"GOOD sync2: %d"(ts);
1214 break;
1216 if (optDebugFlashLoad) conwritefln!"bad sync2: %d"(ts);
1217 } else {
1218 if (optDebugFlashLoad) conwritefln!"bad sync1: %d"(ts);
1220 if (optDebugFlashLoad) conwriteln("bad sync");
1222 if (optDebugFlashLoad && pilotPulses > 0) conwritefln!"bad pilot (pilotPulses=%d)"(pilotPulses);
1223 // pilot too short
1224 pilotPulses = 0;
1225 if (++badPilotsCount > 1_000_000) {
1226 // alas, something went wrong
1227 conwriteln("flashload: something is wrong with the tape (loop?)");
1228 return false; // error
1232 if (optDebugFlashLoad) conwriteln("sync ok");
1233 // load first byte (block type)
1234 if ((ti.flags&CFL_NO_TYPE) == 0) {
1235 if ((bt = emuTapeLoadByte(ti.bit0, ti.bit1)) < 0) return false; // end of tape or tape error
1236 parity ^= bt;
1237 if (optDebugFlashLoad) conwritefln!"block type: #%02X"(bt);
1238 // check block type
1239 if ((ti.flags&CFL_ANY_TYPE) == 0 && ((ti.flags&CFL_NO_LEN_GLITCH) != 0 || emuz80.DE.d != 0xff)) {
1240 if (bt != flag) goto skip_block; // bad block type, skip it
1243 // load parity byte
1244 if ((ti.flags&(CFL_NO_PARITY|CFL_PARITY_FIRST)) == CFL_PARITY_FIRST) {
1245 if ((bt = emuTapeLoadByte(ti.bit0, ti.bit1)) < 0) return false; // end of tape or tape error
1246 parity ^= bt;
1248 if (optDebugFlashLoad) conwritefln!"loading %d bytes to #%04X"(len, addr);
1249 // load block bytes
1250 while (len > 0) {
1251 if ((bt = emuTapeLoadByte(ti.bit0, ti.bit1)) < 0) break; // end of tape or tape error
1252 parity ^= bt;
1253 emuz80.memPokeB(addr&0xffff, bt&0xff);
1254 addr += (ti.flags&CFL_REVERSE ? -1 : 1);
1255 addr &= 0xffff;
1256 --len;
1258 if (optDebugFlashLoad) conwritefln!" done bytes, addr=#%04X, len=%d"(addr, len);
1259 emuz80.DE.w = len&0xffff;
1260 emuz80.IX.w = addr&0xffff;
1261 // load parity byte
1262 if ((ti.flags&(CFL_NO_PARITY|CFL_PARITY_FIRST)) == 0) {
1263 if ((bt = emuTapeLoadByte(ti.bit0, ti.bit1)) >= 0) parity ^= bt;
1265 if (optDebugFlashLoad) conwritefln!"done, parity: #%02X"(parity);
1266 emuz80.HL.h = parity&0xff;
1267 return true;
1271 // return:
1272 // -1: pilot or block id searching fails (so just continue executing)
1273 // 0: something was loaded (with or without error); execute loader epilogue
1274 bool emuTapeDoROMLoad () nothrow {
1275 static immutable LoaderTimings timing = {
1276 pilot_minlen: 807,
1277 pilot: TAPE_ROM_PILOT_TS,
1278 sync1: TAPE_ROM_SYNC1_TS,
1279 sync2: TAPE_ROM_SYNC2_TS,
1280 bit0: TAPE_ROM_BIT0_TS,
1281 bit1: TAPE_ROM_BIT1_TS,
1283 return emuTapeDoFlashLoadEx(timing);
1287 // ////////////////////////////////////////////////////////////////////////// //
1288 } else {
1289 // no libspectrum
1290 public void tapeNextFrame () nothrow {}
1291 public bool tapeCurEdge () nothrow { return false; }
1293 public TapeState tapeGetState () nothrow @nogc {
1294 TapeState ts;
1295 ts.currBlock = ts.blockCount = 0;
1296 ts.curr = ts.total = 0;
1297 ts.playing = false;
1298 ts.complete = true;
1299 return ts;