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
34 // ////////////////////////////////////////////////////////////////////////// //
35 private @property uint tsmax () nothrow @trusted @nogc {
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;
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
;
80 static if (LibSpectrumHere
) {
81 public __gshared libspectrum_tape
*curtape
= null;
84 public TapeState
tapeGetState () nothrow @nogc {
86 ts
.currBlock
= tapeBlockLastSeen
;
87 ts
.blockCount
= tapeBlockCount
;
88 ts
.curr
= tapeCurrTState
;
89 ts
.total
= tapeTotalTStates
;
90 ts
.playing
= tapePlaying
;
91 ts
.complete
= tapeComplete
;
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;
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
114 conwriteln("tape: complete");
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
;
126 while (tapeNextInFrame
<= emuz80
.tstates
&& tapePlaying
) {
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");
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);
150 int emuTapeLdrGetNextPulse () nothrow {
151 if (!tapePlaying ||
!curtape ||
!libspectrum_tape_present(curtape
) || tapeComplete
) return -1;
154 bool curedge = tapeLastEdge;
156 if (!tapePlaying) return -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
;
170 while (tapePlaying
) {
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");
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; }
187 if (tapeLastEdge
!= curedge
) return ts
;
193 // ////////////////////////////////////////////////////////////////////////// //
194 shared static this () {
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 {
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 {
229 conwriteln("tape: stopped");
230 emuTapeAcceleratorStop();
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
) {
243 emuTapeAcceleratorStart();
245 tapeAutostarted
= false;
249 void emuStartTapeAuto () nothrow {
253 tapeAutostarted
= true;
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
));
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
));
278 if (optTapeSlowScan
) {
279 libspectrum_tape_nth_block(curtape
, 0);
281 tapeTotalTStates
= 0;
285 auto tres
= libspectrum_tape_get_next_edge(&tstates
, &flags
, curtape
);
286 if (tres
!= LIBSPECTRUM_ERROR_NONE
&& tres
!= LIBSPECTRUM_ERROR_WARNING
) break;
288 tres
= libspectrum_tape_position(&cblk
, curtape
);
289 if (tres
!= LIBSPECTRUM_ERROR_NONE
&& tres
!= LIBSPECTRUM_ERROR_WARNING
) break;
290 if (cblk
!= lastBlock
) {
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
) {
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");
326 tapDetectInCount
= 0;
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
337 tapDetectInCount
= 0;
340 tapDetectInCount
= 0;
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;
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
) {
389 } else if (flags
&LIBSPECTRUM_TAPE_FLAGS_LENGTH_LONG
) {
398 void emuTapeDoAcceleration () nothrow {
400 emuz80
.BC
.b
= ((lengthLong1^
(accelerationMode
== TapeAccelMode
.Decreasing
)) ?
0xfe : 0x00);
402 emuz80
.PC
= emuz80
.pop();
403 //tape_next_edge(z80.tstates, 0, NULL);
404 tapNextEdgeTS
= emuz80
.tstates
;
405 tapDetectInCount
= 0;
408 lengthKnown1
= lengthKnown2
;
409 lengthLong1
= lengthLong2
;
413 TapeAccelMode
emuTapeAccelerationDetector (ushort pc
) nothrow {
414 int state
= 0, count
= 0;
416 ubyte b
= emuz80
.memPeekB(pc
);
422 case 0x04: state
= 1; break; // INC B - Many loaders
423 default: state
= 13; break; // Possible Digital Integration
428 case 0xc8: state
= 2; break; // RET Z
429 default: return TapeAccelMode
.None
;
434 case 0x3e: state
= 3; break; // LD A,nn
435 default: return TapeAccelMode
.None
;
440 case 0x00: // Search Loader
441 case 0x7f: // ROM loader and variants
442 state
= 4; break; // Data byte
443 default: return TapeAccelMode
.None
;
448 case 0xdb: state
= 5; break; // IN A,(nn)
449 default: return TapeAccelMode
.None
;
454 case 0xfe: state
= 6; break; // Data byte
455 default: return TapeAccelMode
.None
;
460 case 0x1f: state
= 7; break; // RRA
461 case 0xa9: state
= 24; break; // XOR C - Search Loader
462 default: return TapeAccelMode
.None
;
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
472 case 0xa9: state
= 9; break; // XOR C - Speedlock
473 default: return TapeAccelMode
.None
;
478 case 0xa9: state
= 9; break; // XOR C
479 default: return TapeAccelMode
.None
;
484 case 0xe6: state
= 10; break; // AND nn
485 default: return TapeAccelMode
.None
;
490 case 0x20: state
= 11; break; // Data byte
491 default: return TapeAccelMode
.None
;
496 case 0x28: state
= 12; break; // JR nn
497 default: return TapeAccelMode
.None
;
501 return (b
== 0x100-count ? TapeAccelMode
.Increasing
: TapeAccelMode
.None
);
502 /* Digital Integration loader */
504 state
= 14; break; // Possible Digital Integration
507 case 0x05: state
= 15; break; // DEC B - Digital Integration
508 default: return TapeAccelMode
.None
;
513 case 0xc8: state
= 16; break; // RET Z
514 default: return TapeAccelMode
.None
;
519 case 0xdb: state
= 17; break; // IN A,(nn)
520 default: return TapeAccelMode
.None
;
525 case 0xfe: state
= 18; break; // Data byte
526 default: return TapeAccelMode
.None
;
531 case 0xa9: state
= 19; break; // XOR C
532 default: return TapeAccelMode
.None
;
537 case 0xe6: state
= 20; break; // AND nn
538 default: return TapeAccelMode
.None
;
543 case 0x40: state
= 21; break; // Data byte
544 default: return TapeAccelMode
.None
;
549 case 0xca: state
= 22; break; // JP Z,nnnn
550 default: return TapeAccelMode
.None
;
553 case 22: // LSB of jump target
554 if (b
== ((cast(int)emuz80
.PC
-4)&0xff)) {
557 return TapeAccelMode
.None
;
560 case 23: // MSB of jump target
561 return (b
== (((cast(int)emuz80
.PC
-4)&0xff00)>>8) ? TapeAccelMode
.Decreasing
: TapeAccelMode
.None
);
565 case 0xe6: state
= 25; break; // AND nn
566 default: return TapeAccelMode
.None
;
571 case 0x40: state
= 26; break; // Data byte
572 default: return TapeAccelMode
.None
;
577 case 0xd8: state
= 27; break; // RET C
578 default: return TapeAccelMode
.None
;
583 case 0x00: state
= 11; break; // NOP
584 default: return TapeAccelMode
.None
;
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
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
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);
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 ////////////////////////////////////////////////////////////////////////////////
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
662 * -32666 -- skip this byte
664 int isPatternOk(bool showData
=false) (int addr
, const(short)[] pattern
) nothrow @trusted {
667 while (ppos
< pattern
.length
) {
668 short p
= pattern
.ptr
[ppos
++];
670 // positive offset from the current address
671 if (ppos
>= pattern
.length
) return true; // end of data, ok
672 p
= pattern
.ptr
[ppos
++];
674 if (optDebugFlashLoad
) conwritefln
!" pattern #%04X: #%04X (%d)"(addr
, a
, p
);
675 } else if (p
<= 0 && p
> -32000) {
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
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
);
689 if (emuz80
.memPeekB(a
&0xffff) != ((p
>>8)&0xff)) return 0;
691 if (emuz80
.memPeekB(a
&0xffff) != (p
&0xff)) return 0;
693 if (emuz80
.memPeekB(a
&0xffff) != p
) return 0;
700 return true; // end of data, ok
704 ////////////////////////////////////////////////////////////////////////////////
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
733 * -32666 -- skip this byte
735 static immutable short[$] pattern_seto
= [
742 0x3E, -32666, // LD A,xx
743 0xD3, 0xFE, // OUT (#FE),A
744 0x21, -32666, -32666, // LD HL,xxx
746 0xDB, 0xFE, // IN A,(#FE)
748 -32764, 7, 0xCD, // CALL LD-EDGE2
749 -32764, 14, 0xCD, // CALL LD-EDGE2
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
762 bool do_seto () nothrow {
763 static immutable LoaderTimings timing
= {
764 pilot_minlen
: 807, // stdlen: 3223
771 return emuTapeDoFlashLoadEx(timing
);
775 ////////////////////////////////////////////////////////////////////////////////
776 static immutable short[$] pattern_edge
= [
779 0xDD, 0xE1, // POP IX
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
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
797 0x20, -32666, // JR NZ, ...
806 bool do_edge () nothrow {
807 static immutable LoaderTimings timing
= {
816 return emuTapeDoFlashLoadEx(timing
);
820 ////////////////////////////////////////////////////////////////////////////////
821 static immutable short[$] pattern_flashload
= [
822 -4, 0xD3, 0xFE, // OUT (#FE),A
827 0xDD, 0x19, // ADD IX,DE
828 0xDD, 0x2B, // DEC IX
830 0, 6, 0xCD, // CALL LD-EDGE2
831 0, 21, 0xCD, // CALL LD-EDGE
834 0xFE, 0x01, // CP #01
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
850 bool doFlashLoad () nothrow {
851 static immutable LoaderTimings timing
= {
860 return emuTapeDoFlashLoadEx(timing
);
864 ////////////////////////////////////////////////////////////////////////////////
865 static immutable short[$] pattern_ftl
= [
866 -4, 0xD3, 0xFE, // OUT (#FE),A
869 0x01, 0x0140, // LD BC,#4001
871 0, 8, 0xCD, // CALL LD-EDGE2
872 0, 23, 0xCD, // CALL LD-EDGE
875 0xFE, 0x01, // CP #01
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
894 bool doFTL () nothrow {
895 static immutable LoaderTimings timing
= {
903 return emuTapeDoFlashLoadEx(timing
);
907 ////////////////////////////////////////////////////////////////////////////////
909 static immutable short[$] pattern_hewson
= [
910 -4, 0xD3, 0xFE, // OUT (#FE),A
917 0, 8, 0xCD, // CALL LD-EDGE2
918 0, 23, 0xCD, // CALL LD-EDGE
921 0xFE, 0x01, // CP #01
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
938 static int isHewson (void) {
940 //if (z80.ix.w <= addr && z80.ix.w+z80.de.w >= addr) return 0; // don't accelerate self-modifying loaders
946 ////////////////////////////////////////////////////////////////////////////////
947 static immutable short[$] pattern_pl2
= [
954 0x3E, -32666, // LD A,xx
955 0xD3, 0xFE, // OUT (#FE),A
956 0x21, -32666, -32666, // LD HL,xxx
958 0, 6, 0xCD, // CALL LD-EDGE2
959 0, 21, 0xCD, // CALL LD-EDGE
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
972 0xFE, 0x01, // CP #01
979 bool do_pl2 () nothrow {
980 static immutable LoaderTimings timing
= {
988 return emuTapeDoFlashLoadEx(timing
);
992 ////////////////////////////////////////////////////////////////////////////////
993 static immutable short[$] pattern_uniloader
= [
994 -4, 0xD3, 0xFE, // OUT (#FE),A
1000 0, 6, 0xCD, // CALL LD-EDGE2
1001 0, 21, 0xCD, // CALL LD-EDGE
1004 0xFE, 0x01, // CP #01
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
1021 bool isUniLoader () nothrow {
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;
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
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();
1045 // prevent endless loading error loops
1046 conwriteln("*** tape loading error");
1047 if (++loadErrorCount
>= 16) {
1049 optTapeFlashLoad
= false;
1054 emuz80
.PC
= cast(ushort)(addr
+li
.exitOfs
);
1056 //if (optDebugFlashLoad) dbgSetActive(1);
1059 conwriteln("block not found!");
1061 //if (optDebugFlashLoad) dbgSetActive(1);
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
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!");
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
);
1106 ////////////////////////////////////////////////////////////////////////////////
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;
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));
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);
1160 //if (optDebugFlashLoad) conwritefln!"byte loaded: 0x%02x (%u)"(b, b);
1163 //if (optDebugFlashLoad) conwriteln("byte loading failed");
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
);
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
);
1186 tapeNextInFrame
= 0;
1187 int badPilotsCount
= 0;
1189 if (optDebugFlashLoad
) conwriteln("searching 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
);
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"));
1207 if (goodTiming(ts
, ti
.sync1
, stolerance
)) {
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
);
1216 if (optDebugFlashLoad
) conwritefln
!"bad sync2: %d"(ts
);
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
);
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
1237 if (optDebugFlashLoad
) conwritefln
!"block type: #%02X"(bt);
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
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
1248 if (optDebugFlashLoad
) conwritefln
!"loading %d bytes to #%04X"(len
, addr
);
1251 if ((bt = emuTapeLoadByte(ti
.bit0
, ti
.bit1
)) < 0) break; // end of tape or tape error
1253 emuz80
.memPokeB(addr
&0xffff, bt&0xff);
1254 addr
+= (ti
.flags
&CFL_REVERSE ?
-1 : 1);
1258 if (optDebugFlashLoad
) conwritefln
!" done bytes, addr=#%04X, len=%d"(addr
, len
);
1259 emuz80
.DE
.w
= len
&0xffff;
1260 emuz80
.IX
.w
= addr
&0xffff;
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;
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
= {
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 // ////////////////////////////////////////////////////////////////////////// //
1290 public void tapeNextFrame () nothrow {}
1291 public bool tapeCurEdge () nothrow { return false; }
1293 public TapeState
tapeGetState () nothrow @nogc {
1295 ts
.currBlock
= ts
.blockCount
= 0;
1296 ts
.curr
= ts
.total
= 0;