1 /***************************************************************************
3 * ZXEmuT -- ZX Spectrum Emulator with Tcl scripting
5 * Copyright (C) 2012-2020 Ketmar Dark <ketmar@ketmar.no-ip.org>
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, version 3 of the License ONLY.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **************************************************************************/
20 // directly included from "main.c"
22 #define FNAME_PREFIX "_zxemut_."
23 #define FNAME_ZX_MAX_LEN (15)
26 typedef struct FileDirItem_s
{
27 char zxname
[16]; // sanitized
29 //uint8_t flags; // currently, only bit 0 is defined, it means "read-only"
32 static FileDirItem
*fdirItems
= NULL
;
33 static uint32_t fdirItemsAlloted
= 0;
34 static uint32_t fdirItemsCount
= 0;
37 //==========================================================================
41 //==========================================================================
42 static int fopIsValidZXChar (uint8_t byte
) {
43 if (!byte
|| byte
>= 127) return 0;
44 if (byte
>= 'a' && byte
<= 'z') return 1;
45 if (byte
>= 'A' && byte
<= 'Z') return 1;
46 if (byte
>= '0' && byte
<= '9') return 1;
47 return !!strchr(" !@#$%.+-_", (char)byte
);
51 //==========================================================================
55 //==========================================================================
56 static int fopIsValidZXName (const char *s
) {
57 if (!s
|| !s
[0]) return 0;
62 if (++len
> FNAME_ZX_MAX_LEN
) return 0;
63 if (ch
>= 'a' && ch
<= 'z') continue;
64 if (ch
>= 'A' && ch
<= 'Z') continue;
65 if (ch
>= '0' && ch
<= '9') continue;
66 if (!strchr(" !@#$%.+-_", ch
)) return 0;
72 //==========================================================================
76 //==========================================================================
77 static void fopRescanDir (void) {
80 DIR *dir
= opendir(".");
81 if (!dir
) return; // i don't care about errors for now
83 const size_t pfxlen
= strlen(FNAME_PREFIX
);
86 while ((de
= readdir(dir
)) != NULL
) {
87 size_t fnlen
= strlen(de
->d_name
);
88 if (fnlen
< pfxlen
|| fnlen
> pfxlen
+FNAME_ZX_MAX_LEN
) continue;
89 if (!fopIsValidZXName(de
->d_name
+pfxlen
)) continue;
91 // looks like a valid file name
92 if (fdirItemsCount
== 0xffffU
) break; // too many files
95 if (stat(de
->d_name
, &st
) != 0) continue; // oops
96 if (st
.st_size
> 0x40000000U
) continue; // file too big
97 if (!S_ISREG(st
.st_mode
)) continue; // alas
100 if (fdirItemsCount
== fdirItemsAlloted
) {
101 uint32_t nsz
= fdirItemsAlloted
+4096;
102 FileDirItem
*ndi
= realloc(fdirItems
, sizeof(FileDirItem
)*nsz
);
103 if (!ndi
) break; // out of memory
104 fdirItemsAlloted
= nsz
;
109 strcpy(fdirItems
[fdirItemsCount
].zxname
, de
->d_name
+pfxlen
);
110 fdirItems
[fdirItemsCount
].fsize
= (uint32_t)st
.st_size
; // guaranteed to fit
118 //==========================================================================
122 //==========================================================================
123 static void trapFileOps (zym_cpu_t
*z80
, uint8_t tpc
, uint16_t addr
, uint8_t opcsize
) {
124 if (optFileTrapsMode
== ZOPT_FTP_NONE
) goto general_error
;
126 char diskname
[128]; //FNAME_PREFIX+FNAME_ZX_MAX_LEN+4];
129 // returns non-zero on success
130 // builds disk file name into `diskname`
131 int getFileName (uint16_t addr
) {
132 uint8_t ch
= z80
->mem_read(z80
, addr
, ZYM_MEMIO_OTHER
);
133 if (!fopIsValidZXChar(ch
)) return 0;
134 strcpy(diskname
, FNAME_PREFIX
);
135 char *dest
= diskname
+strlen(FNAME_PREFIX
);
138 if (ofs
> FNAME_ZX_MAX_LEN
) return 0;
139 if ((uint32_t)addr
+ofs
> 0xffff) return 0;
140 uint8_t ch
= z80
->mem_read(z80
, (addr
+ofs
)&0xffff, ZYM_MEMIO_OTHER
);
142 if (!fopIsValidZXChar(ch
)) return 0;
143 dest
[ofs
++] = (char)ch
;
153 z80
->hl
.w
= fdirItemsCount
&0xffffU
;
154 z80
->af
.f
&= ~ZYM_FLAG_C
;
160 const uint32_t fidx
= z80
->hl
.w
;
161 if (fidx
>= fdirItemsCount
) goto general_error
;
162 if (z80
->de
.w
< 0x4000) goto general_error
; //FIXME: +3 can have RAM there!
163 z80
->af
.f
&= ~ZYM_FLAG_C
;
166 if (z80
->af
.a
== 0) {
167 const char *zxname
= fdirItems
[fidx
].zxname
;
170 z80
->mem_write(z80
, (z80
->de
.w
+f
)&0xffff, (uint8_t)zxname
[f
], ZYM_MEMIO_OTHER
);
171 } while (zxname
[f
++] != 0);
175 // 1: get file name w/o extension
176 if (z80
->af
.a
== 1) {
177 const char *zxname
= fdirItems
[fidx
].zxname
;
178 const char *ee
= strrchr(zxname
, '.');
179 if (!ee
) ee
= zxname
+strlen(zxname
);
181 while (zxname
!= ee
) {
182 z80
->mem_write(z80
, (z80
->de
.w
+f
)&0xffff, (uint8_t)zxname
[f
], ZYM_MEMIO_OTHER
);
185 z80
->mem_write(z80
, (z80
->de
.w
+f
)&0xffff, 0, ZYM_MEMIO_OTHER
);
189 // 2: get file extension
190 if (z80
->af
.a
== 2) {
191 const char *zxname
= fdirItems
[fidx
].zxname
;
192 const char *ee
= strrchr(zxname
, '.');
194 z80
->mem_write(z80
, z80
->de
.w
, 0, ZYM_MEMIO_OTHER
);
199 z80
->mem_write(z80
, (z80
->de
.w
+f
)&0xffff, (uint8_t)ee
[f
], ZYM_MEMIO_OTHER
);
200 } while (ee
[f
++] != 0);
205 if (z80
->af
.a
== 3) {
206 uint32_t sz
= fdirItems
[fidx
].fsize
;
207 if (sz
<= 0xffff) z80
->af
.f
|= ZYM_FLAG_Z
; else z80
->af
.f
&= ~ZYM_FLAG_Z
;
208 for (unsigned f
= 0; f
< 4; ++f
) {
209 z80
->mem_write(z80
, (z80
->de
.w
+f
)&0xffff, sz
&0xff, ZYM_MEMIO_OTHER
);
215 // 4: get file attributes
216 if (z80
->af
.a
== 4) {
217 snprintf(diskname
, sizeof(diskname
), "%s%s", FNAME_PREFIX
, fdirItems
[fidx
].zxname
);
218 if (access(diskname
, R_OK
) != 0) goto general_error
;
219 if (optFileTrapsMode
== ZOPT_FTP_RW
&& access(diskname
, W_OK
) == 0) {
220 z80
->mem_write(z80
, z80
->de
.w
, 1, ZYM_MEMIO_OTHER
);
222 z80
->mem_write(z80
, z80
->de
.w
, 0, ZYM_MEMIO_OTHER
);
231 // read file, extended read file
232 if (tpc
== 0x12 || tpc
== 0x13) {
233 if (!getFileName(z80
->hl
.w
)) goto general_error
;
234 if (z80
->de
.w
< 0x4000) goto general_error
; //FIXME: +3 can have RAM there!
235 z80
->af
.f
&= ~ZYM_FLAG_C
;
239 z80
->mem_read(z80
, z80
->de
.w
, ZYM_MEMIO_OTHER
)|
240 ((uint32_t)z80
->mem_read(z80
, (z80
->de
.w
+1)&0xffff, ZYM_MEMIO_OTHER
)<<8)|
241 ((uint32_t)z80
->mem_read(z80
, (z80
->de
.w
+2)&0xffff, ZYM_MEMIO_OTHER
)<<16)|
242 ((uint32_t)z80
->mem_read(z80
, (z80
->de
.w
+3)&0xffff, ZYM_MEMIO_OTHER
)<<24)
245 FILE *fl
= fopen(diskname
, "r");
246 if (!fl
) goto general_error
;
247 if (z80
->bc
.w
== 0) { fclose(fl
); return; }
249 if (rpos
&& fseek(fl
, (long)rpos
, SEEK_SET
) != 0) {
255 uint8_t *buf
= malloc(z80
->bc
.w
);
256 if (!buf
) { fclose(fl
); goto general_error
; } // out of memory
258 ssize_t rd
= fread(buf
, 1, z80
->bc
.w
, fl
);
262 for (unsigned f
= 0; f
< (unsigned)rd
; ++f
) {
263 z80
->mem_write(z80
, (z80
->de
.w
+f
)&0xffff, buf
[f
], ZYM_MEMIO_OTHER
);
265 z80
->bc
.w
= (uint16_t)rd
;
274 // create and write file
276 fdirItemsCount
= 0; // reset directory scan results
277 if (optFileTrapsMode
!= ZOPT_FTP_RW
) goto general_error
;
279 if (!getFileName(z80
->hl
.w
)) goto general_error
;
280 //if (z80->de.w < 0x4000) goto general_error; //FIXME: +3 can have RAM there!
281 z80
->af
.f
&= ~ZYM_FLAG_C
;
283 FILE *fl
= fopen(diskname
, "w");
284 if (!fl
) goto general_error
;
285 if (z80
->bc
.w
== 0) { fclose(fl
); return; }
287 for (unsigned f
= 0; f
< z80
->bc
.w
; ++f
) {
288 uint8_t b
= z80
->mem_read(z80
, (addr
+f
)&0xffff, ZYM_MEMIO_OTHER
);
289 if (fwrite(&b
, 1, 1, fl
) != 1) {
299 // extended write file
301 fdirItemsCount
= 0; // reset directory scan results
302 if (optFileTrapsMode
!= ZOPT_FTP_RW
) goto general_error
;
304 if (!getFileName(z80
->hl
.w
)) goto general_error
;
305 //if (z80->de.w < 0x4000) goto general_error; //FIXME: +3 can have RAM there!
306 z80
->af
.f
&= ~ZYM_FLAG_C
;
310 z80
->mem_read(z80
, z80
->de
.w
, ZYM_MEMIO_OTHER
)|
311 ((uint32_t)z80
->mem_read(z80
, (z80
->de
.w
+1)&0xffff, ZYM_MEMIO_OTHER
)<<8)|
312 ((uint32_t)z80
->mem_read(z80
, (z80
->de
.w
+2)&0xffff, ZYM_MEMIO_OTHER
)<<16)|
313 ((uint32_t)z80
->mem_read(z80
, (z80
->de
.w
+3)&0xffff, ZYM_MEMIO_OTHER
)<<24)
316 FILE *fl
= fopen(diskname
, "r+");
317 if (!fl
) goto general_error
;
318 if (z80
->bc
.w
== 0) { fclose(fl
); return; }
320 if (rpos
&& fseek(fl
, (long)rpos
, SEEK_SET
) != 0) {
325 for (unsigned f
= 0; f
< z80
->bc
.w
; ++f
) {
326 uint8_t b
= z80
->mem_read(z80
, (addr
+f
)&0xffff, ZYM_MEMIO_OTHER
);
327 if (fwrite(&b
, 1, 1, fl
) != 1) {
339 fdirItemsCount
= 0; // reset directory scan results
340 if (optFileTrapsMode
!= ZOPT_FTP_RW
) goto general_error
;
342 if (!getFileName(z80
->hl
.w
)) goto general_error
;
343 z80
->af
.f
&= ~ZYM_FLAG_C
;
349 // check if file exists and readable/writeable
351 fdirItemsCount
= 0; // reset directory scan results
353 if (!getFileName(z80
->hl
.w
)) goto general_error
;
354 z80
->af
.f
&= ~ZYM_FLAG_C
;
356 if (access(diskname
, R_OK
) != 0) goto general_error
;
357 if (optFileTrapsMode
== ZOPT_FTP_RW
&& access(diskname
, W_OK
) == 0) {
368 z80
->af
.f
|= ZYM_FLAG_C
;
372 // ////////////////////////////////////////////////////////////////////////// //
373 static void trdosTraps (void) {
378 // ////////////////////////////////////////////////////////////////////////// //
379 static inline int64_t z80GetULACycle (int tstates
) {
380 if (tstates
>= machineInfo
.topleftpixts
+zxLateTimings
) {
381 const int ts
= tstates
-(machineInfo
.topleftpixts
+zxLateTimings
), line
= ts
/machineInfo
.tsperline
;
382 if (line
< machineInfo
.vscreen
) {
383 const int ltp
= ts
%machineInfo
.tsperline
;
384 if (ltp
< machineInfo
.hscreen
) {
386 return ((int64_t)line
<<16)|((pos
<<8)|(ltp
&0x07));
394 static inline int z80IsContendedAddr (uint16_t addr
) {
397 (!machineInfo
.usePlus3Contention
?
398 (zxMemoryBankNum
[addr
>>14]&0x01) != 0 : // normal
399 (zxMemoryBankNum
[addr
>>14]&0x07) >= 4) : // +2A/+3
400 (!machineInfo
.usePlus3Contention
? 0 : zxLastPagedRAM0
>= 4));
401 // +2A/+3: RAM pages 4, 5, 6 and 7 contended
405 // no contention on borders
406 static void z80Contention (zym_cpu_t
*z80
, uint16_t addr
, int tstates
, zym_memio_t mio
, zym_memreq_t mreq
) {
407 if (machineInfo
.contention
&& z80
->tstates
< machineInfo
.tsperframe
) {
409 if (optEmulateSnow
&& machineInfo
.haveSnow
&& tstates
== 4 &&
410 mreq
== ZYM_MREQ_READ
&& mio
<= ZYM_MEMIO_OPCEXT
&&
411 (z80
->regI
>>6) == 1) {
412 int ots
= z80
->tstates
+1;
413 if (zxContentionTable
[0][ots
] == 4 || zxContentionTable
[0][ots
] == 5) {
414 int64_t lp
= z80GetULACycle(ots
);
416 int pos
= (lp
>>8)&0xff/*, ltp = lp&0x0f*/, line
= (lp
>>16)&0xff;
417 zxUlaSnow
[line
][pos
] = (uint16_t)(zxScreenBank
[((zxScrLineOfs
[line
]+pos
+1)&0xff00)|z80
->regR
])+1;
422 if (z80IsContendedAddr(addr
)) z80
->tstates
+= zxContentionTable
[mreq
== ZYM_MREQ_NONE
][z80
->tstates
];
424 z80
->tstates
+= tstates
;
428 static void z80PortContention (zym_cpu_t
*z80
, uint16_t port
, int tstates
, zym_portio_flags_t pflags
) {
429 if (machineInfo
.iocontention
&& z80
->tstates
< machineInfo
.tsperframe
) {
430 // don't care if this is input our output; check for it with (pflags&ZYM_PORTIO_FLAG_IN) if necessary
431 if (pflags
&ZYM_PORTIO_FLAG_EARLY
) {
432 // early, tstates is ALWAYS 1
433 if (z80IsContendedAddr(port
)) z80
->tstates
+= zxContentionTable
[1][z80
->tstates
]; // ULA bug
436 // later, tstates is ALWAYS 2
437 if ((port
&0x01) == 0) {
439 z80
->tstates
+= zxContentionTable
[1][z80
->tstates
];
443 if (z80IsContendedAddr(port
)) {
444 z80
->tstates
+= zxContentionTable
[1][z80
->tstates
]; ++z80
->tstates
;
445 z80
->tstates
+= zxContentionTable
[1][z80
->tstates
]; ++z80
->tstates
;
446 z80
->tstates
+= zxContentionTable
[1][z80
->tstates
];
453 z80
->tstates
+= tstates
;
458 static uint8_t z80MemRead (zym_cpu_t
*z80
, uint16_t addr
, zym_memio_t mio
) {
460 if (mio != ZYM_MEMIO_OTHER) {
461 //fprintf(stderr, "M%c: %5d; addr=0x%04x; v=0x%02x\n", (mio == ZYM_MEMIO_OPCODE ? (z80->evenM1 ? 'c' : 'C') : 'R'), z80->tstates, addr, zxMemory[addr>>14][addr&0x3fff]);
462 if (optEmulateSnow && machineInfo.haveSnow && (mio == ZYM_MEMIO_OPCODE || mio == ZYM_MEMIO_OPCEXT) && z80->regI >= 0x40 && z80->regI <= 0x7f) {
464 int64_t lp = z80GetULACycle(z80->tstates);
467 int pos = (lp>>8)&0xff, ltp = lp&0x0f, line = (lp>>16)&0xff;
470 case 1: case 2: case 4: case 5:
472 zxUlaSnow[line][pos] = (uint16_t)(zxScreenBank[((zxScrLineOfs[line]+pos)&0xff00)|z80->regR])+1;
474 //zxRealiseScreen(z80->tstates);
482 if (mio
== ZYM_MEMIO_OPCODE
) {
484 if (optTRDOSenabled
> 0 && optTRDOSROMIndex
>= 0) {
485 if (zxTRDOSPagedIn
) {
486 if (addr
>= 0x4000) zxTRDOSpageout();
488 if (zxModel
!= ZX_MACHINE_48K
) {
490 if ((addr
&0xff00) == 0x3d00 && zxLastPagedRAM0
< 0 && zxLastPagedROM
== 1) zxTRDOSpagein();
493 if ((addr
&0xfe00) == 0x3c00) zxTRDOSpagein();
496 if (addr
< 0x4000 && optTRDOSTraps
&& zxTRDOSPagedIn
) {
501 if (z80CheckBP(addr
, DBG_BP_EXEC
|DBG_BP_EXECONE
)) z80
->bp_hit
= 1;
502 } else if (mio
== ZYM_MEMIO_DATA
) {
503 if (z80CheckBP(addr
, DBG_BP_READ
)) z80
->bp_hit
= 1;
506 // for +2A/+3 floating bus
507 if (mio
!= ZYM_MEMIO_OTHER
&& !z80
->bp_hit
&&
508 machineInfo
.floatingBus
> 1 && machineInfo
.contention
&& z80
->tstates
< machineInfo
.tsperframe
)
510 //if (addr < 0x8000) fprintf(stderr, "RD: #%04X (%d); pg=%d; addr=#%04X\n", addr, z80IsContendedAddr(addr), zxMemoryBankNum[addr>>14], addr&0x3fff);
511 if (z80IsContendedAddr(addr
)) {
512 zxLastContendedMemByte
= zxMemory
[addr
>>14][addr
&0x3fff];
513 //fprintf(stderr, "+3CRD: #%04X; ts=%5d; lb=#%02X\n", addr, z80->tstates, zxLastContendedMemByte);
517 return zxMemory
[addr
>>14][addr
&0x3fff];
521 static void z80MemWrite (zym_cpu_t
*z80
, uint16_t addr
, uint8_t value
, zym_memio_t mio
) {
522 //if (mio != ZYM_MEMIO_OTHER) fprintf(stderr, "MW: %5d; addr=0x%04x; v=0x%02x\n", z80->tstates, addr, value);
524 if (addr
>= 0x4000) {
525 if ((addr
&0x3fff) < 6912) {
526 switch (zxMemoryBankNum
[addr
>>14]) {
528 zxRealiseScreen(z80
->tstates
);
532 zxMemory
[addr
>>14][addr
&0x3fff] = value
;
534 if (!zxTRDOSPagedIn
&& zxLastPagedRAM0
>= 0) zxMemory
[0][addr
] = value
;
536 if (mio
== ZYM_MEMIO_DATA
) {
537 if (z80CheckBP(addr
, DBG_BP_WRITE
)) z80
->bp_hit
= 1;
540 // for +2A/+3 floating bus
541 if (mio
!= ZYM_MEMIO_OTHER
&& !z80
->bp_hit
&&
542 machineInfo
.floatingBus
> 1 && machineInfo
.contention
&& z80
->tstates
< machineInfo
.tsperframe
)
544 if (z80IsContendedAddr(addr
)) {
545 zxLastContendedMemByte
= value
;
546 //fprintf(stderr, "+3CRW: #%04X; ts=%5d; lb=#%02X\n", addr, z80->tstates, zxLastContendedMemByte);
552 static uint8_t z80PortInInexistant (zym_cpu_t
*z80
, uint16_t port
) {
553 // floating bus emulation
554 if (machineInfo
.floatingBus
) {
555 if (machineInfo
.floatingBus
== 1) {
556 // "normal" floating bus
557 int64_t lp
= z80GetULACycle(z80
->tstates
);
559 int pos
= (lp
>>8)&0xff, ltp
= lp
&0x0f, line
= (lp
>>16)&0xff;
561 case 2: case 4: // bitmap
562 return zxScreenBank
[zxScrLineOfs
[line
]+pos
];
563 case 3: case 5: // attrs
564 return zxScreenBank
[6144+line
/8*32+pos
];
568 // +2A/+3 floating bus
569 // it doesn't work in 48K mode, and for ports with the given pattern:
570 // 00xx xxxx xxxx xx01
571 if ((port
&0xC003) == 1 && !zx7ffdLocked
) {
572 int64_t lp
= z80GetULACycle(z80
->tstates
);
573 //fprintf(stderr, "+3FB: #%04X; ts=%5d; lb=#%02X lp=%lld\n", port, z80->tstates, zxLastContendedMemByte, lp);
575 // bit 0 is always set
576 int pos
= (lp
>>8)&0xff, ltp
= lp
&0x0f, line
= (lp
>>16)&0xff;
577 //fprintf(stderr, "+3FB: #%04X; ltp=%d; line=%d; pos=%d\n", port, ltp, pos, line);
579 case 2: case 4: // bitmap
580 return zxScreenBank
[zxScrLineOfs
[line
]+pos
]|0x01u
;
581 case 3: case 1: // attrs
582 return zxScreenBank
[6144+line
/8*32+pos
]|0x01u
;
585 return zxLastContendedMemByte
;
594 static uint8_t z80PortIn (zym_cpu_t
*z80
, uint16_t port
, zym_portio_t pio
) {
595 //fprintf(stderr, "PI: %5d; port=#%04x\n", z80->tstates, port);
596 if (pio
== ZYM_PORTIO_NORMAL
) {
597 if (z80CheckBP(port
, DBG_BP_PORTIN
)) z80
->bp_hit
= 1;
600 libspectrum_byte value
;
602 fprintf(stderr, " rzx trying to in: port=0x%04x\n", port);
603 fprintf(stderr, " rts=%u; ", (unsigned)libspectrum_rzx_tstates(zxRZX));
604 fprintf(stderr, " zts=%u; ", (unsigned)z80->tstates);
605 fprintf(stderr, " ic=%u\n", (unsigned)libspectrum_rzx_instructions(zxRZX));
607 if (libspectrum_rzx_playback(zxRZX
, &value
) != LIBSPECTRUM_ERROR_NONE
) {
608 cprintf("RZX playback failed!\n");
609 conMessage("RZX playback failed!");
613 //fprintf(stderr, " rzx in: port=0x%04x, value=0x%02x\n", port, value);
617 for (int f
= phCount
-1; f
>= 0; --f
) {
618 if ((portHandlers
[f
].machine
== ZX_MACHINE_MAX
|| portHandlers
[f
].machine
== zxModel
) &&
619 (port
&portHandlers
[f
].mask
) == portHandlers
[f
].value
&& portHandlers
[f
].portInCB
!= NULL
)
621 //fprintf(stderr, "PC=#%04X port=#%04X\n", z80->pc, port);
623 if (portHandlers
[f
].portInCB(z80
, port
, &res
)) return res
;
626 return z80PortInInexistant(z80
, port
);
630 static int phULAout (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
);
632 static void z80PortOut (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
, zym_portio_t pio
) {
633 //fprintf(stderr, "PW: %5d; port=0x%04x; v=0x%02x\n", z80->tstates, port, value);
634 if (pio
== ZYM_PORTIO_NORMAL
) {
635 if (z80CheckBP(port
, DBG_BP_PORTOUT
)) z80
->bp_hit
= 1;
638 for (int f
= phCount
-1; f
>= 0; --f
) {
639 if ((portHandlers
[f
].machine
== ZX_MACHINE_MAX
|| portHandlers
[f
].machine
== zxModel
) &&
640 (port
&portHandlers
[f
].mask
) == portHandlers
[f
].value
&& portHandlers
[f
].portOutCB
!= NULL
) {
641 if (portHandlers
[f
].portOutCB
== phULAout
) wasULA
= 1;
642 int res
= (portHandlers
[f
].portOutCB(z80
, port
, value
));
643 // vanilla leaks to border select here, WFT?!
647 if (!wasULA
&& !zxTRDOSPagedIn
/*&& (zxModel == ZX_MACHINE_128K || zxModel == ZX_MACHINE_PLUS2 || zxModel == ZX_MACHINE_PENTAGON)*/) {
648 if ((port
&0x01) == 0) phULAout(z80
, port
, value
);
653 // return !0 to exit immediately
654 // called when invalid ED command found
655 // PC points to the next instruction
658 // HL: address to load;
659 // A: A --> level number
660 // return: CARRY complemented --> error
661 static int z80EDTrap (zym_cpu_t
*z80
, uint8_t trapCode
) {
663 case 0xfb: // SLT trap
665 libspectrum_byte
*slt
= libspectrum_snap_slt(zxSLT
, z80
->af
.a
);
668 size_t len
= libspectrum_snap_slt_length(zxSLT
, z80
->af
.a
);
669 uint16_t addr
= z80
->hl
.w
;
672 z80
->mem_write(z80
, addr
, *slt
++, ZYM_MEMIO_OTHER
);
673 addr
= (addr
+1)&0xffff;
678 z80
->af
.f
^= ZYM_FLAG_C
; // complement carry to indicate that there was an error
680 case 0xfe: // ZXEmuT trap, must be followed by JR (and JP?)
681 if (optAllowZXEmuTraps
&& z80
->mem_read(z80
, (z80
->pc
)&0xffff, ZYM_MEMIO_OTHER
) == 0x18) {
682 // 0xed, 0xfe, 0x18 (JR), and then # of data bytes (hehe, JR will jump over 'em)
683 uint8_t opcsize
= z80
->mem_read(z80
, (z80
->pc
+1)&0xffff, ZYM_MEMIO_OTHER
);
684 uint16_t addr
= (z80
->pc
+2+opcsize
)&0xffff;
687 const uint8_t tpc
= z80
->mem_read(z80
, addr
-1, ZYM_MEMIO_OTHER
);
689 case 0: // reset tick counter
691 dbgTickCounter
-= 8; // trap timing (two opcodes)
693 case 1: // show tick counter
694 cprintf("ticks: %llu\n", dbgTickCounter
);
696 case 2: // activate debugger
698 return 1; // exit NOW
699 case 3: // pause emulator
702 case 0xfe: // maxspeed on
705 case 0xff: // maxspeed off
709 if (tpc
>= 0x10 && tpc
<= 0x17) {
710 trapFileOps(z80
, tpc
, addr
, opcsize
);
722 ////////////////////////////////////////////////////////////////////////////////
723 // kempstons (joystick & mouse)
724 static int phKempstonIn (zym_cpu_t
*z80
, uint16_t port
, uint8_t *res
) {
725 //fprintf(stderr, "KEPMSTON: PC=#%04X port=#%04X\n", z80->pc, port);
727 // #00DF check for some Spanish games
728 // ((port&0xffu) == 0xdfu && (port&0xff00u) != 0)
730 // 0xfadf: buttons (hiword bits 0,2 are zero)
731 // 0xfbdf: buttons (hiword bit 2 is zero)
732 // 0xffdf: buttons (not hiword bits are zero)
733 if (optKMouse
&& (port
&0xffu
) == 0xdfu
&&
734 (optKMouseStrict
? (port
&0xfa00u
) == 0xfa00u
: (port
&0xf000u
) == 0xf000u
))
736 //fprintf(stderr, " kmouse!\n");
738 if ((port
|0xfa00) == 0xfadf) {
739 // bit 0: left; bit 1: right; bit 2: middle; pressed if bit reset
741 (zxKMouseButtons
&MS_BUTTON_LEFT
? (optKMouseSwapButtons
? 0x02 : 0x01) : 0)|
742 (zxKMouseButtons
&MS_BUTTON_RIGHT
? (optKMouseSwapButtons
? 0x01 : 0x02) : 0)|
743 (zxKMouseButtons
&MS_BUTTON_MIDDLE
? 0x04 : 0);
744 *res
= (mbt
^0x0f)|((zxKMouseWheel
<<4)&0xf0);
748 if ((port
|0xfa00u
) == 0xfbdfu
) {
753 if ((port
|0xfa00u
) == 0xffdfu
) {
761 int af
= isAutofireKempston();
762 *res
= ((~zxKeyboardState
[8])&0x1f)|af
;
770 static const PortHandlers phKempston
[] = {
772 { ZX_MACHINE_MAX
, 0x0020, 0x0000, phKempstonIn
, NULL
},
775 { ZX_MACHINE_PENTAGON, 0x0521, 0x0501, phKempstonIn, NULL },
776 { ZX_MACHINE_PENTAGON, 0x0521, 0x0101, phKempstonIn, NULL },
777 { ZX_MACHINE_PENTAGON, 0x0121, 0x0001, phKempstonIn, NULL },
779 { 0, 0, 0, NULL
, NULL
}};
782 ////////////////////////////////////////////////////////////////////////////////
783 #define FUSE_RD(name_) \
784 static int emu_##name_ (zym_cpu_t *z80, uint16_t port, uint8_t *res) { \
785 if (zxTRDOSPagedIn /*&& zxDiskIf && difGetHW(zxDiskIf) == DIF_BDI*/) { \
786 *res = name_(port); \
787 diskLastActivity = timerGetMS(); \
794 #define FUSE_WR(name_) \
795 static int emu_##name_ (zym_cpu_t *z80, uint16_t port, uint8_t value) { \
796 if (zxTRDOSPagedIn /*&& zxDiskIf && difGetHW(zxDiskIf) == DIF_BDI*/) { \
797 name_(port, value); \
798 diskLastActivity = timerGetMS(); \
804 FUSE_RD(beta_sr_read
)
805 FUSE_RD(beta_tr_read
)
806 FUSE_RD(beta_sec_read
)
807 FUSE_RD(beta_dr_read
)
808 FUSE_RD(beta_sp_read
)
810 FUSE_WR(beta_cr_write
)
811 FUSE_WR(beta_tr_write
)
812 FUSE_WR(beta_sec_write
)
813 FUSE_WR(beta_dr_write
)
814 FUSE_WR(beta_sp_write
)
817 static const PortHandlers phTRDOS
[] = {
818 { ZX_MACHINE_MAX
, 0x00ff, 0x001f, emu_beta_sr_read
, emu_beta_cr_write
},
819 { ZX_MACHINE_MAX
, 0x00ff, 0x003f, emu_beta_tr_read
, emu_beta_tr_write
},
820 { ZX_MACHINE_MAX
, 0x00ff, 0x005f, emu_beta_sec_read
, emu_beta_sec_write
},
821 { ZX_MACHINE_MAX
, 0x00ff, 0x007f, emu_beta_dr_read
, emu_beta_dr_write
},
822 { ZX_MACHINE_MAX
, 0x00ff, 0x00ff, emu_beta_sp_read
, emu_beta_sp_write
},
823 { 0, 0, 0, NULL
, NULL
}
827 ////////////////////////////////////////////////////////////////////////////////
829 static int phUPD765_status (zym_cpu_t
*z80
, uint16_t port
, uint8_t *res
) {
830 if (!upd765_fdc
) return 0;
831 diskLastActivity
= timerGetMS();
832 *res
= upd_fdc_read_status(upd765_fdc
);
837 static int phUPD765_read (zym_cpu_t
*z80
, uint16_t port
, uint8_t *res
) {
838 if (!upd765_fdc
) return 0;
839 diskLastActivity
= timerGetMS();
840 *res
= upd_fdc_read_data(upd765_fdc
);
845 static int phUPD765_write (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
846 if (!upd765_fdc
) return 0;
847 diskLastActivity
= timerGetMS();
848 upd_fdc_write_data(upd765_fdc
, value
);
853 static int phUPD765_dummy (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
858 // allow uPD765 for any machine
859 static const PortHandlers phUPD765
[] = {
860 { ZX_MACHINE_MAX
, 0xf002, 0x3000, phUPD765_read
, phUPD765_write
}, // fdc r/w
861 { ZX_MACHINE_MAX
, 0xf002, 0x2000, phUPD765_status
, phUPD765_dummy
}, // status
862 { 0, 0, 0, NULL
, NULL
}
866 ////////////////////////////////////////////////////////////////////////////////
867 // 128k memory mode port
868 static int ph7FFDout (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
870 //fprintf(stderr, "O: 7FFD; port=#%04X; value=#%02X\n", port, value);
871 zxLastOut7ffd
= value
;
872 emuRealizeMemoryPaging();
880 static int ph7FFDoutP1 (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
881 if (zxPentagonMemory
> 512) return 0;
882 return ph7FFDout(z80
, port
, value
);
886 static int ph7FFDoutP2 (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
887 if (zxPentagonMemory
< 1024) return 0;
888 return ph7FFDout(z80
, port
, value
);
892 static int ph7FFDin (zym_cpu_t
*z80
, uint16_t port
, uint8_t *res
) {
894 //128k bug with 0x7ffd
895 //fprintf(stderr, "I: 7FFD; port=#%04X\n", port);
896 *res
= z80PortInInexistant(z80
, port
);
898 if (machineInfo
.port7ffDbug
) ph7FFDout(z80
, port
, *res
);
905 // +3/Scorpion/Pentagon512/Pentagon1024 memory mode port
906 static int ph1FFDout (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
907 if (!zx7ffdLocked
&& machineInfo
.port1ffd
>= 0) {
908 //fprintf(stderr, "O: 1FFD; port=#%04X; value=#%02X\n", port, value);
909 zxLastOut1ffd
= value
;
910 emuRealizeMemoryPaging();
913 fdd_motoron(upd765_fdc
->drive
[0], (value
&0x08));
914 fdd_motoron(upd765_fdc
->drive
[1], (value
&0x08));
922 static int ph7FFDoutBad (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
923 if (optFDas7FFD
&& !zx7ffdLocked
) {
924 //fprintf(stderr, "O: 7FFD; port=#%04X; value=#%02X\n", port, value);
925 zxLastOut7ffd
= value
;
926 emuRealizeMemoryPaging();
933 static const PortHandlers phMemory
[] = {
935 { ZX_MACHINE_128K
, 0x8002, 0x0000, ph7FFDin
, ph7FFDout
},
936 { ZX_MACHINE_PLUS2
, 0x8002, 0x0000, ph7FFDin
, ph7FFDout
},
938 { ZX_MACHINE_PLUS2A
, 0x8002, 0x0000, ph7FFDin
, ph7FFDout
},
939 { ZX_MACHINE_PLUS2A
, 0xf002, 0x1000, NULL
, ph1FFDout
},
941 { ZX_MACHINE_PLUS3
, 0x8002, 0x0000, ph7FFDin
, ph7FFDout
},
942 { ZX_MACHINE_PLUS3
, 0xf002, 0x1000, NULL
, ph1FFDout
},
944 { ZX_MACHINE_SCORPION
, 0xc002, 0x4000, NULL
, ph7FFDout
},
945 { ZX_MACHINE_SCORPION
, 0xf002, 0x1000, NULL
, ph1FFDout
},
947 { ZX_MACHINE_PENTAGON
, 0x8002, 0x0000, NULL
, ph7FFDoutP1
},
949 { ZX_MACHINE_PENTAGON
, 0xc002, 0x4000, NULL
, ph7FFDoutP2
},
950 { ZX_MACHINE_PENTAGON
, 0xf008, 0xe000, NULL
, ph1FFDout
},
952 { ZX_MACHINE_MAX
, 0x8002, 0x0000, NULL
, ph7FFDoutBad
},
953 { 0, 0, 0, NULL
, NULL
}};
956 ////////////////////////////////////////////////////////////////////////////////
958 static int phAYinR (zym_cpu_t
*z80
, uint16_t port
, uint8_t *res
) {
961 uint8_t val
= zxAYRegs
[zxLastOutFFFD
&0x0f], val7
= zxAYRegs
[7];
962 //fprintf(stderr, "I: AYR; port=#%04X\n", port);
963 if (zxLastOutFFFD
== 14) *res
= (val7
&0x40 ? 0xbf&val
: 0xbf);
964 else if (zxLastOutFFFD
== 15 && !(val7
&0x80)) *res
= 0xff;
972 static int phAYoutR (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
975 //fprintf(stderr, "O: AYR; port=#%04X\n", port);
976 zxLastOutFFFD
= (value
&0x0f);
983 static int phAYoutD (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
985 //fprintf(stderr, "O: AYD; port=#%04X\n", port);
986 switch (zxLastOutFFFD
) {
987 case 1: case 3: case 5: case 13: value
&= 0x0f; break;
988 case 6: case 8: case 9: case 10: value
&= 0x1f; break;
990 zxAYRegs
[zxLastOutFFFD
] = value
;
991 if (optMaxSpeed
|| optSpeed
!= 100 || !soundIsWorking() || !optSoundAY
) {
992 soundAYResetRegisters();
994 soundAYWrite(zxLastOutFFFD
, value
, z80
->tstates
);
997 soundAYResetRegisters();
1003 static const PortHandlers phAY
[] = {
1004 { ZX_MACHINE_MAX
, 0xc002, 0xc000, phAYinR
, phAYoutR
},
1005 { ZX_MACHINE_MAX
, 0xc002, 0x8000, NULL
, phAYoutD
},
1006 // full: {0xffff, 0xfffd}, {0xffff, 0xbffd}
1007 { 0, 0, 0, NULL
, NULL
}};
1010 ////////////////////////////////////////////////////////////////////////////////
1012 static int phULAin (zym_cpu_t
*z80
, uint16_t port
, uint8_t *res
) {
1013 uint8_t r
= zxUlaOut
, p1
= (port
>>8);
1017 int af
= isAutofireKeyboard();
1019 for (int f
= 0; f
< 8; ++f
) ks
[f
] = zxKeyboardState
[f
];
1020 if (af
) ks
[af
>>8] &= ~(af
&0xff);
1021 // emulate keyboard matrix effect
1022 if (optEmulateMatrix
) {
1025 for (int k
= 0; k
< 7; ++k
) {
1026 for (int j
= k
+1; j
< 8; ++j
) {
1027 if ((ks
[k
]|ks
[j
]) != 0xff && ks
[k
] != ks
[j
]) {
1028 ks
[k
] = ks
[j
] = (ks
[k
]&ks
[j
]);
1036 for (int f
= 0; f
< 8; ++f
) if ((p1
&(1<<f
)) == 0) r
&= ks
[f
];
1037 // check for known tape loaders
1040 zxCurTape
!= NULL
&& !tapNeedRewind
&&
1042 (zxModel
== ZX_MACHINE_PLUS2A
|| zxModel
== ZX_MACHINE_PLUS3
? zxLastPagedROM
== 3 : (zxModel
== ZX_MACHINE_48K
|| zxLastPagedROM
== 1)))
1044 if (emuTapeFlashLoad() == 0) {
1050 if (optTapeDetector
&& detectlde
) {
1051 emuTapeLoaderDetector();
1053 switch (emuTapeGetCurEdge()) {
1054 case 0: r
&= ~0x40; break;
1055 case 1: r
|= 0x40; break;
1058 /*if (optTapePlaying && optTapeSound) soundBeeper((r>>2)&0x10, z80->tstates);*/
1065 static int phULAout (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
1066 uint8_t bc
= (value
&0x07);
1068 if (optBrightBorder
> 0) bc
|= ((value
>>optBrightBorder
)&0x01)<<3;
1069 if (zxBorder
!= bc
) {
1070 zxRealiseScreen(z80
->tstates
);
1073 /*if (!optTapePlaying || !optTapeSound)*/ soundBeeper(/*value&0x10*/((value
&0x10)>>3), z80
->tstates
);
1075 if (zxModel
== ZX_MACHINE_48K
) {
1078 (value
&0x18 ? 0xff : 0xbf) : //issue2
1079 (value
&0x10 ? 0xff : 0xbf)); //issue3, 128k
1082 zxUlaOut
= (value
&0x10 ? 0xff : 0xbf);
1083 //zxUlaOut = 0xbf; //+3
1090 static const PortHandlers phULA
[] = {
1091 { ZX_MACHINE_MAX
, 0x0001, 0x0000, phULAin
, phULAout
},
1092 { 0, 0, 0, NULL
, NULL
}};
1095 ////////////////////////////////////////////////////////////////////////////////
1096 // REMEMBER, THAT HIGHER PRIORITY DEVICES SHOULD BE ADDED LAST!
1097 // THIS IS SO FOR PORT MASK/VALUES TOO!
1098 static void emuAddPortHandlers (void) {