1 /***************************************************************************
3 * ZXEmuT -- ZX Spectrum Emulator with Tcl scripting
5 * Copyright (C) 2012-2022 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 dataaddr
, uint8_t datasize
) {
124 if (optFileTrapsMode
== ZOPT_FTP_NONE
) goto general_error
;
126 char diskname
[128]; //FNAME_PREFIX+FNAME_ZX_MAX_LEN+4];
128 // returns non-zero on success
129 // builds disk file name into `diskname`
130 int getFileName (uint16_t addr
) {
131 uint8_t ch
= z80
->mem_read(z80
, addr
, ZYM_MEMIO_OTHER
);
132 if (!fopIsValidZXChar(ch
)) return 0;
133 strcpy(diskname
, FNAME_PREFIX
);
134 char *dest
= diskname
+strlen(FNAME_PREFIX
);
137 if (ofs
> FNAME_ZX_MAX_LEN
) return 0;
138 if ((uint32_t)addr
+ofs
> 0xffff) return 0;
139 uint8_t ch
= z80
->mem_read(z80
, (addr
+ofs
)&0xffff, ZYM_MEMIO_OTHER
);
141 if (!fopIsValidZXChar(ch
)) return 0;
142 dest
[ofs
++] = (char)ch
;
152 z80
->hl
.w
= fdirItemsCount
&0xffffU
;
153 z80
->af
.f
&= ~ZYM_FLAG_C
;
159 const uint32_t fidx
= z80
->hl
.w
;
160 if (fidx
>= fdirItemsCount
) goto general_error
;
161 if (z80
->de
.w
< 0x4000) goto general_error
; //FIXME: +3 can have RAM there!
162 z80
->af
.f
&= ~ZYM_FLAG_C
;
165 if (z80
->af
.a
== 0) {
166 const char *zxname
= fdirItems
[fidx
].zxname
;
169 z80
->mem_write(z80
, (z80
->de
.w
+f
)&0xffff, (uint8_t)zxname
[f
], ZYM_MEMIO_OTHER
);
170 } while (zxname
[f
++] != 0);
174 // 1: get file name w/o extension
175 if (z80
->af
.a
== 1) {
176 const char *zxname
= fdirItems
[fidx
].zxname
;
177 const char *ee
= strrchr(zxname
, '.');
178 if (!ee
) ee
= zxname
+strlen(zxname
);
180 while (zxname
!= ee
) {
181 z80
->mem_write(z80
, (z80
->de
.w
+f
)&0xffff, (uint8_t)zxname
[f
], ZYM_MEMIO_OTHER
);
184 z80
->mem_write(z80
, (z80
->de
.w
+f
)&0xffff, 0, ZYM_MEMIO_OTHER
);
188 // 2: get file extension
189 if (z80
->af
.a
== 2) {
190 const char *zxname
= fdirItems
[fidx
].zxname
;
191 const char *ee
= strrchr(zxname
, '.');
193 z80
->mem_write(z80
, z80
->de
.w
, 0, ZYM_MEMIO_OTHER
);
198 z80
->mem_write(z80
, (z80
->de
.w
+f
)&0xffff, (uint8_t)ee
[f
], ZYM_MEMIO_OTHER
);
199 } while (ee
[f
++] != 0);
204 if (z80
->af
.a
== 3) {
205 uint32_t sz
= fdirItems
[fidx
].fsize
;
206 if (sz
<= 0xffff) z80
->af
.f
|= ZYM_FLAG_Z
; else z80
->af
.f
&= ~ZYM_FLAG_Z
;
207 for (unsigned f
= 0; f
< 4; ++f
) {
208 z80
->mem_write(z80
, (z80
->de
.w
+f
)&0xffff, sz
&0xff, ZYM_MEMIO_OTHER
);
214 // 4: get file attributes
215 if (z80
->af
.a
== 4) {
216 snprintf(diskname
, sizeof(diskname
), "%s%s", FNAME_PREFIX
, fdirItems
[fidx
].zxname
);
217 if (access(diskname
, R_OK
) != 0) goto general_error
;
218 if (optFileTrapsMode
== ZOPT_FTP_RW
&& access(diskname
, W_OK
) == 0) {
219 z80
->mem_write(z80
, z80
->de
.w
, 1, ZYM_MEMIO_OTHER
);
221 z80
->mem_write(z80
, z80
->de
.w
, 0, ZYM_MEMIO_OTHER
);
230 // read file, extended read file
231 if (tpc
== 0x12 || tpc
== 0x13) {
232 if (!getFileName(z80
->hl
.w
)) goto general_error
;
233 if (z80
->de
.w
< 0x4000) goto general_error
; //FIXME: +3 can have RAM there!
234 z80
->af
.f
&= ~ZYM_FLAG_C
;
238 z80
->mem_read(z80
, z80
->de
.w
, ZYM_MEMIO_OTHER
)|
239 ((uint32_t)z80
->mem_read(z80
, (z80
->de
.w
+1)&0xffff, ZYM_MEMIO_OTHER
)<<8)|
240 ((uint32_t)z80
->mem_read(z80
, (z80
->de
.w
+2)&0xffff, ZYM_MEMIO_OTHER
)<<16)|
241 ((uint32_t)z80
->mem_read(z80
, (z80
->de
.w
+3)&0xffff, ZYM_MEMIO_OTHER
)<<24)
244 FILE *fl
= fopen(diskname
, "r");
245 if (!fl
) goto general_error
;
246 if (z80
->bc
.w
== 0) { fclose(fl
); return; }
248 if (rpos
&& fseek(fl
, (long)rpos
, SEEK_SET
) != 0) {
254 uint8_t *buf
= malloc(z80
->bc
.w
);
255 if (!buf
) { fclose(fl
); goto general_error
; } // out of memory
257 ssize_t rd
= fread(buf
, 1, z80
->bc
.w
, fl
);
261 for (unsigned f
= 0; f
< (unsigned)rd
; ++f
) {
262 z80
->mem_write(z80
, (z80
->de
.w
+f
)&0xffff, buf
[f
], ZYM_MEMIO_OTHER
);
264 z80
->bc
.w
= (uint16_t)rd
;
273 // create and write file
275 fdirItemsCount
= 0; // reset directory scan results
276 if (optFileTrapsMode
!= ZOPT_FTP_RW
) goto general_error
;
278 if (!getFileName(z80
->hl
.w
)) goto general_error
;
279 //if (z80->de.w < 0x4000) goto general_error; //FIXME: +3 can have RAM there!
280 z80
->af
.f
&= ~ZYM_FLAG_C
;
282 FILE *fl
= fopen(diskname
, "w");
283 if (!fl
) goto general_error
;
284 if (z80
->bc
.w
== 0) { fclose(fl
); return; }
286 uint16_t addr
= z80
->de
.w
;
287 for (unsigned f
= 0; f
< z80
->bc
.w
; ++f
, ++addr
) {
288 uint8_t b
= z80
->mem_read(z80
, addr
, 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 uint16_t addr
= z80
->de
.w
+4;
326 for (unsigned f
= 0; f
< z80
->bc
.w
; ++f
, ++addr
) {
327 uint8_t b
= z80
->mem_read(z80
, addr
, ZYM_MEMIO_OTHER
);
328 if (fwrite(&b
, 1, 1, fl
) != 1) {
340 fdirItemsCount
= 0; // reset directory scan results
341 if (optFileTrapsMode
!= ZOPT_FTP_RW
) goto general_error
;
343 if (!getFileName(z80
->hl
.w
)) goto general_error
;
344 z80
->af
.f
&= ~ZYM_FLAG_C
;
350 // check if file exists and readable/writeable
352 fdirItemsCount
= 0; // reset directory scan results
354 if (!getFileName(z80
->hl
.w
)) goto general_error
;
355 z80
->af
.f
&= ~ZYM_FLAG_C
;
357 if (access(diskname
, R_OK
) != 0) goto general_error
;
358 if (optFileTrapsMode
== ZOPT_FTP_RW
&& access(diskname
, W_OK
) == 0) {
366 // extended read/write file
368 if (z80
->af
.a
> 2) goto general_error
; // invalid operation
370 fdirItemsCount
= 0; // reset directory scan results
371 if (z80
->af
.a
!= 0 && optFileTrapsMode
!= ZOPT_FTP_RW
) goto general_error
;
373 if (!getFileName(z80
->hl
.w
)) goto general_error
;
374 //if (z80->de.w < 0x4000) goto general_error; //FIXME: +3 can have RAM there!
375 z80
->af
.f
&= ~ZYM_FLAG_C
;
377 uint32_t rpos
= (((uint32_t)z80
->dex
.w
)<<16)|z80
->hlx
.w
;
381 case 0: fl
= fopen(diskname
, "r"); break; // read
382 case 1: fl
= fopen(diskname
, "r+"); break; // write to existing
383 case 2: // write, create if necessary
384 fl
= fopen(diskname
, "r+");
385 if (fl
== NULL
) fl
= fopen(diskname
, "w");
387 default: goto general_error
;
389 if (fl
== NULL
) goto general_error
;
390 if (z80
->bc
.w
== 0) { fclose(fl
); return; }
392 if (rpos
&& fseek(fl
, (long)rpos
, SEEK_SET
) != 0) {
397 uint16_t addr
= z80
->de
.w
;
399 uint8_t *buf
= malloc(z80
->bc
.w
);
400 if (!buf
) { fclose(fl
); goto general_error
; } // out of memory
402 if (z80
->af
.a
== 0) {
404 ssize_t rd
= fread(buf
, 1, z80
->bc
.w
, fl
);
405 if (rd
!= z80
->bc
.w
&& ferror(fl
)) {
412 for (unsigned f
= 0; f
< (unsigned)rd
; ++f
) {
413 z80
->mem_write(z80
, (addr
+f
)&0xffff, buf
[f
], ZYM_MEMIO_OTHER
);
416 z80
->bc
.w
= (uint16_t)rd
;
418 for (unsigned f
= 0; f
< (unsigned)z80
->bc
.w
; ++f
) {
419 buf
[f
] = z80
->mem_read(z80
, (addr
+f
)&0xffff, ZYM_MEMIO_OTHER
);
422 ssize_t wr
= fwrite(buf
, 1, z80
->bc
.w
, fl
);
423 if (wr
!= z80
->bc
.w
&& ferror(fl
)) {
430 z80
->bc
.w
= (uint16_t)wr
;
440 z80
->af
.f
|= ZYM_FLAG_C
;
444 // ////////////////////////////////////////////////////////////////////////// //
445 static inline int64_t z80GetULACycle (int tstates
) {
446 if (tstates
>= machineInfo
.topleftpixts
+zxLateTimings
) {
447 const int ts
= tstates
-(machineInfo
.topleftpixts
+zxLateTimings
), line
= ts
/machineInfo
.tsperline
;
448 if (line
< machineInfo
.vscreen
) {
449 const int ltp
= ts
%machineInfo
.tsperline
;
450 if (ltp
< machineInfo
.hscreen
) {
452 return ((int64_t)line
<<16)|((pos
<<8)|(ltp
&0x07));
460 static inline int z80IsContendedAddr (uint16_t addr
) {
463 (!machineInfo
.usePlus3Contention
?
464 (zxMemoryBankNum
[addr
>>14]&0x01) != 0 : // normal
465 (zxMemoryBankNum
[addr
>>14]&0x07) >= 4) : // +2A/+3
466 (!machineInfo
.usePlus3Contention
? 0 : zxLastPagedRAM0
>= 4));
467 // +2A/+3: RAM pages 4, 5, 6 and 7 contended
471 // no contention on borders
472 static void z80Contention (zym_cpu_t
*z80
, uint16_t addr
, int tstates
, zym_memio_t mio
, zym_memreq_t mreq
) {
473 if (machineInfo
.contention
&& z80
->tstates
< machineInfo
.tsperframe
) {
475 if (optEmulateSnow
&& machineInfo
.haveSnow
&& tstates
== 4 &&
476 mreq
== ZYM_MREQ_READ
&& mio
<= ZYM_MEMIO_OPCEXT
&&
477 (z80
->regI
>>6) == 1) {
478 int ots
= z80
->tstates
+1;
479 if (zxContentionTable
[0][ots
] == 4 || zxContentionTable
[0][ots
] == 5) {
480 int64_t lp
= z80GetULACycle(ots
);
482 int pos
= (lp
>>8)&0xff/*, ltp = lp&0x0f*/, line
= (lp
>>16)&0xff;
483 zxUlaSnow
[line
][pos
] = (uint16_t)(zxScreenBank
[((zxScrLineOfs
[line
]+pos
+1)&0xff00)|z80
->regR
])+1;
488 if (z80IsContendedAddr(addr
)) z80
->tstates
+= zxContentionTable
[mreq
== ZYM_MREQ_NONE
][z80
->tstates
];
490 z80
->tstates
+= tstates
;
494 static void z80PortContention (zym_cpu_t
*z80
, uint16_t port
, int tstates
, zym_portio_flags_t pflags
) {
495 if (machineInfo
.iocontention
&& z80
->tstates
< machineInfo
.tsperframe
) {
496 // don't care if this is input our output; check for it with (pflags&ZYM_PORTIO_FLAG_IN) if necessary
497 if (pflags
&ZYM_PORTIO_FLAG_EARLY
) {
498 // early, tstates is ALWAYS 1
499 if (z80IsContendedAddr(port
)) z80
->tstates
+= zxContentionTable
[1][z80
->tstates
]; // ULA bug
502 // later, tstates is ALWAYS 2
503 if ((port
&0x01) == 0) {
505 z80
->tstates
+= zxContentionTable
[1][z80
->tstates
];
509 if (z80IsContendedAddr(port
)) {
510 z80
->tstates
+= zxContentionTable
[1][z80
->tstates
]; ++z80
->tstates
;
511 z80
->tstates
+= zxContentionTable
[1][z80
->tstates
]; ++z80
->tstates
;
512 z80
->tstates
+= zxContentionTable
[1][z80
->tstates
];
519 z80
->tstates
+= tstates
;
524 static uint8_t z80MemRead (zym_cpu_t
*z80
, uint16_t addr
, zym_memio_t mio
) {
526 if (mio != ZYM_MEMIO_OTHER) {
527 //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]);
528 if (optEmulateSnow && machineInfo.haveSnow && (mio == ZYM_MEMIO_OPCODE || mio == ZYM_MEMIO_OPCEXT) && z80->regI >= 0x40 && z80->regI <= 0x7f) {
530 int64_t lp = z80GetULACycle(z80->tstates);
533 int pos = (lp>>8)&0xff, ltp = lp&0x0f, line = (lp>>16)&0xff;
536 case 1: case 2: case 4: case 5:
538 zxUlaSnow[line][pos] = (uint16_t)(zxScreenBank[((zxScrLineOfs[line]+pos)&0xff00)|z80->regR])+1;
540 //zxRealiseScreen(z80->tstates);
548 if (mio
== ZYM_MEMIO_OPCODE
) {
549 if (addr
>= 0x4000) zxWasRAMExec
= 1; // for Mr.Gluck Reset Service
551 if (optTRDOSenabled
> 0 && optTRDOSROMIndex
>= 0) {
552 if (zxTRDOSPagedIn
) {
553 if (addr
>= 0x4000) zxTRDOSpageout();
555 if (zxModel
!= ZX_MACHINE_48K
) {
557 if ((addr
&0xff00) == 0x3d00 && zxLastPagedRAM0
< 0 && zxLastPagedROM
== 1) zxTRDOSpagein();
560 if ((addr
&0xfe00) == 0x3c00) zxTRDOSpagein();
564 if (optTRDOSTraps
&& zxTRDOSPagedIn
) {
566 // TR-DOS short track seek trap
567 if (addr
== 0x3dfd) {
569 if (memcmp(&zxMemory
[0][addr
], "\x3e\x50\x0e\xff\x0d\x20\xfd\x3d\x20\xf8\xc9", 11) == 0) {
570 if (optTRDOSTrapDebug
) cprintf("DBG: TR-DOS short track seek trap\n");
573 z80
->af
.f
|= ZYM_FLAG_Z
; // just in case
574 z80
->pc
= 0x3e07; // "ret" address
575 return 0xc9; // "ret"
578 // TR-DOS long track seek trap
579 if (addr
== 0x3ea0) {
581 if (memcmp(&zxMemory
[0][addr
], "\x06\x03\x3e\xff\xcd\xff\x3d\x10\xf9\xc9", 10) == 0) {
582 if (optTRDOSTrapDebug
) cprintf("DBG: TR-DOS long track seek trap\n");
585 z80
->af
.f
|= ZYM_FLAG_Z
; // just in case
586 z80
->pc
= 0x3ea9; // "ret" address
587 return 0xc9; // "ret"
591 if (addr
== 0x3dff) {
593 if (memcmp(&zxMemory
[0][addr
], "\x0e\xff\x0d\x20\xfd\x3d\x20\xf8\xc9", 9) == 0) {
594 if (optTRDOSTrapDebug
) cprintf("DBG: TR-DOS delay trap\n");
597 z80
->af
.f
|= ZYM_FLAG_Z
; // just in case
598 z80
->pc
= 0x3e07; // "ret" address
599 return 0xc9; // "ret"
604 if (addr
== 0x3e01) {
606 if (memcmp(&zxMemory
[0][addr
], "\x0d\x20\xfd\x3d\x20\xf8\xc9", 7) == 0) {
607 if (optTRDOSTrapDebug
) cprintf("DBG: TR-DOS delay trap\n");
611 } else if (addr
== 0x3e04) {
613 if (memcmp(&zxMemory
[0][0x3e01], "\x0d\x20\xfd\x3d\x20\xf8\xc9", 7) == 0) {
614 if (optTRDOSTrapDebug
) cprintf("DBG: TR-DOS short delay trap\n");
622 if (z80CheckBP(addr
, DBG_BP_EXEC
|DBG_BP_EXECONE
)) z80
->bp_hit
= 1;
623 } else if (mio
== ZYM_MEMIO_DATA
) {
624 if (z80CheckBP(addr
, DBG_BP_READ
)) z80
->bp_hit
= 1;
627 // for +2A/+3 floating bus
628 if (mio
!= ZYM_MEMIO_OTHER
&& !z80
->bp_hit
&&
629 machineInfo
.floatingBus
> 1 && machineInfo
.contention
&& z80
->tstates
< machineInfo
.tsperframe
)
631 //if (addr < 0x8000) fprintf(stderr, "RD: #%04X (%d); pg=%d; addr=#%04X\n", addr, z80IsContendedAddr(addr), zxMemoryBankNum[addr>>14], addr&0x3fff);
632 if (z80IsContendedAddr(addr
)) {
633 zxLastContendedMemByte
= zxMemory
[addr
>>14][addr
&0x3fff];
634 //fprintf(stderr, "+3CRD: #%04X; ts=%5d; lb=#%02X\n", addr, z80->tstates, zxLastContendedMemByte);
638 return zxMemory
[addr
>>14][addr
&0x3fff];
642 static void z80MemWrite (zym_cpu_t
*z80
, uint16_t addr
, uint8_t value
, zym_memio_t mio
) {
643 //if (mio != ZYM_MEMIO_OTHER) fprintf(stderr, "MW: %5d; addr=0x%04x; v=0x%02x\n", z80->tstates, addr, value);
645 if (addr
>= 0x4000) {
646 if ((addr
&0x3fff) < 6912) {
647 switch (zxMemoryBankNum
[addr
>>14]) {
649 zxRealiseScreen(z80
->tstates
);
653 zxMemory
[addr
>>14][addr
&0x3fff] = value
;
654 if (mio
!= ZYM_MEMIO_OTHER
) zxMemBanksDirty
[zxMemoryBankNum
[addr
>>14]] = 1;
656 if (!zxTRDOSPagedIn
&& zxLastPagedRAM0
>= 0) {
657 zxMemory
[0][addr
] = value
;
658 if (mio
!= ZYM_MEMIO_OTHER
) zxMemBanksDirty
[zxMemoryBankNum
[0]] = 1;
661 if (mio
== ZYM_MEMIO_DATA
) {
662 if (z80CheckBP(addr
, DBG_BP_WRITE
)) z80
->bp_hit
= 1;
665 // for +2A/+3 floating bus
666 if (mio
!= ZYM_MEMIO_OTHER
&& !z80
->bp_hit
&&
667 machineInfo
.floatingBus
> 1 && machineInfo
.contention
&& z80
->tstates
< machineInfo
.tsperframe
)
669 if (z80IsContendedAddr(addr
)) {
670 zxLastContendedMemByte
= value
;
671 //fprintf(stderr, "+3CRW: #%04X; ts=%5d; lb=#%02X\n", addr, z80->tstates, zxLastContendedMemByte);
677 static uint8_t z80PortInInexistant (zym_cpu_t
*z80
, uint16_t port
) {
678 // floating bus emulation
679 if (machineInfo
.floatingBus
) {
680 if (machineInfo
.floatingBus
== 1) {
681 // "normal" floating bus
682 int64_t lp
= z80GetULACycle(z80
->tstates
);
684 int pos
= (lp
>>8)&0xff, ltp
= lp
&0x0f, line
= (lp
>>16)&0xff;
686 case 2: case 4: // bitmap
687 return zxScreenBank
[zxScrLineOfs
[line
]+pos
];
688 case 3: case 5: // attrs
689 return zxScreenBank
[6144+line
/8*32+pos
];
693 // +2A/+3 floating bus
694 // it doesn't work in 48K mode, and for ports with the given pattern:
695 // 00xx xxxx xxxx xx01
696 if ((port
&0xC003) == 1 && !zx7ffdLocked
) {
697 int64_t lp
= z80GetULACycle(z80
->tstates
);
698 //fprintf(stderr, "+3FB: #%04X; ts=%5d; lb=#%02X lp=%lld\n", port, z80->tstates, zxLastContendedMemByte, lp);
700 // bit 0 is always set
701 int pos
= (lp
>>8)&0xff, ltp
= lp
&0x0f, line
= (lp
>>16)&0xff;
702 //fprintf(stderr, "+3FB: #%04X; ltp=%d; line=%d; pos=%d\n", port, ltp, pos, line);
704 case 2: case 4: // bitmap
705 return zxScreenBank
[zxScrLineOfs
[line
]+pos
]|0x01u
;
706 case 3: case 1: // attrs
707 return zxScreenBank
[6144+line
/8*32+pos
]|0x01u
;
710 return zxLastContendedMemByte
;
719 static uint8_t z80PortIn (zym_cpu_t
*z80
, uint16_t port
, zym_portio_t pio
) {
720 //fprintf(stderr, "PI: %5d; port=#%04x\n", z80->tstates, port);
721 if (pio
== ZYM_PORTIO_NORMAL
) {
722 if (z80CheckBP(port
, DBG_BP_PORTIN
)) z80
->bp_hit
= 1;
725 libspectrum_byte value
;
727 fprintf(stderr, " rzx trying to in: port=0x%04x\n", port);
728 fprintf(stderr, " rts=%u; ", (unsigned)libspectrum_rzx_tstates(zxRZX));
729 fprintf(stderr, " zts=%u; ", (unsigned)z80->tstates);
730 fprintf(stderr, " ic=%u\n", (unsigned)libspectrum_rzx_instructions(zxRZX));
732 if (libspectrum_rzx_playback(zxRZX
, &value
) != LIBSPECTRUM_ERROR_NONE
) {
733 cprintf("RZX playback failed!\n");
734 conMessage("RZX playback failed!");
738 //fprintf(stderr, " rzx in: port=0x%04x, value=0x%02x\n", port, value);
742 for (int f
= phCount
-1; f
>= 0; --f
) {
743 if ((portHandlers
[f
].machine
== ZX_MACHINE_MAX
|| portHandlers
[f
].machine
== zxModel
) &&
744 (port
&portHandlers
[f
].mask
) == portHandlers
[f
].value
&& portHandlers
[f
].portInCB
!= NULL
)
746 //fprintf(stderr, "PC=#%04X port=#%04X\n", z80->pc, port);
748 if (portHandlers
[f
].portInCB(z80
, port
, &res
)) return res
;
751 return z80PortInInexistant(z80
, port
);
755 static int phULAout (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
);
757 static void z80PortOut (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
, zym_portio_t pio
) {
758 //fprintf(stderr, "PW: %5d; port=0x%04x; v=0x%02x\n", z80->tstates, port, value);
759 if (pio
== ZYM_PORTIO_NORMAL
) {
760 if (z80CheckBP(port
, DBG_BP_PORTOUT
)) z80
->bp_hit
= 1;
763 for (int f
= phCount
-1; f
>= 0; --f
) {
764 if ((portHandlers
[f
].machine
== ZX_MACHINE_MAX
|| portHandlers
[f
].machine
== zxModel
) &&
765 (port
&portHandlers
[f
].mask
) == portHandlers
[f
].value
&& portHandlers
[f
].portOutCB
!= NULL
) {
766 if (portHandlers
[f
].portOutCB
== phULAout
) wasULA
= 1;
767 int res
= (portHandlers
[f
].portOutCB(z80
, port
, value
));
768 // vanilla leaks to border select here, WFT?!
772 if (!wasULA
&& !zxTRDOSPagedIn
/*&& (zxModel == ZX_MACHINE_128K || zxModel == ZX_MACHINE_PLUS2 || zxModel == ZX_MACHINE_PENTAGON)*/) {
773 if ((port
&0x01) == 0) phULAout(z80
, port
, value
);
778 //==========================================================================
780 // zxemutTrapPrintChar
782 //==========================================================================
783 static void zxemutTrapPrintChar (uint8_t ch
) {
784 if (ch
== 13 || ch
== 10) { cprintf("\n"); return; }
785 if (ch
< 32 || ch
== 127) ch
= '.';
789 cprintf("%s", (const char *)s
);
793 //==========================================================================
797 //==========================================================================
798 static int zxemutTraps (zym_cpu_t
*z80
, uint8_t code
, uint16_t dataaddr
, uint8_t datasize
) {
800 case 0: // reset tick counter
801 // check for subcommands
803 //cprintf("PC=$%04X: EBAL: %llu (p=%d)\n", z80->pc, dbgTickCounter, dbtTickCounterPaused);
804 const uint8_t sbc
= z80
->mem_read(z80
, dataaddr
, ZYM_MEMIO_OTHER
);
805 // data byte should be subtracted too
806 const uint8_t subts
= (datasize
> 1 ? z80
->mem_read(z80
, dataaddr
+1, ZYM_MEMIO_OTHER
) : 0);
807 // -8 to compensate trap cost
809 case 1: dbtTickCounterPaused
= 1; dbgTickCounter
-= subts
; break; // pause
810 case 2: dbtTickCounterPaused
= 0; dbgTickCounter
-= subts
; break; // resume
813 // reset tick counter
815 //cprintf("PC=$%02X; HUI: %llu (p=%d)\n", z80->pc, dbgTickCounter, dbtTickCounterPaused);
818 case 1: // show tick counter
819 cprintf("PC=$%04X: ticks: %llu\n", /*dataaddr-5*/z80
->pc
, dbgTickCounter
+ z80
->trap_ts
);
821 case 2: // activate debugger
823 return 1; // exit NOW
824 case 3: // console printing
826 switch (z80
->mem_read(z80
, dataaddr
, ZYM_MEMIO_OTHER
)) {
827 case 0: // print char in A
828 zxemutTrapPrintChar(z80
->af
.a
);
830 case 1: // print string in HL, BC bytes
831 for (unsigned f
= 0; f
< z80
->bc
.w
; ++f
) {
832 zxemutTrapPrintChar(z80
->mem_read(z80
, z80
->hl
.w
+f
, ZYM_MEMIO_OTHER
));
835 case 2: // print asciiz string in HL
836 for (unsigned f
= 0; f
< 65536; ++f
) {
837 uint8_t b
= z80
->mem_read(z80
, z80
->hl
.w
+f
, ZYM_MEMIO_OTHER
);
839 zxemutTrapPrintChar(b
);
842 case 3: // print trap data
843 for (unsigned f
= 1; f
< datasize
; ++f
) {
844 uint8_t b
= z80
->mem_read(z80
, dataaddr
+f
, ZYM_MEMIO_OTHER
);
846 zxemutTrapPrintChar(b
);
849 case 4: // print hex byte from A
850 cprintf("%02X", z80
->af
.a
);
852 case 5: // print hex word from HL
853 cprintf("%02X", z80
->hl
.w
);
855 case 6: // print dec byte from A
856 cprintf("%03u", z80
->af
.a
);
858 case 7: // print dec word from HL
859 cprintf("%05u", z80
->hl
.w
);
864 case 0x10: // emulator control functions
866 switch (z80
->mem_read(z80
, dataaddr
, ZYM_MEMIO_OTHER
)) {
867 case 0x00: // pause the emulation
870 case 0x01: // maxspeed on
873 case 0x02: // maxspeed off
876 case 0x60: // kmouse on, absolute
878 optKMouseAbsolute
= 1;
879 emuHideRealMouseCursor();
880 emuSetKMouseAbsCoords();
883 case 0x61: // kmouse normal
884 optKMouseAbsolute
= 0;
886 case 0x62: // hide system mouse cursor
887 emuHideRealMouseCursor();
889 case 0x63: // show system mouse cursor
890 emuShowRealMouseCursor();
895 case 0x13: // file API
897 trapFileOps(z80
, z80
->mem_read(z80
, dataaddr
, ZYM_MEMIO_OTHER
),
898 dataaddr
+1, datasize
-1);
902 z80
->af
.f
|= ZYM_FLAG_C
;
905 case 0xff: // check version
906 z80
->af
.f
&= ~ZYM_FLAG_Z
;
908 switch (optFileTrapsMode
) {
909 case ZOPT_FTP_RO
: z80
->af
.a
|= 0x02; break;
910 case ZOPT_FTP_RW
: z80
->af
.a
|= 0x02|0x04; break;
924 // return !0 to exit immediately
925 // called when invalid ED command found
926 // PC points to the next instruction
929 // HL: address to load;
930 // A: A --> level number
931 // return: CARRY complemented --> error
932 static zym_bool
z80EDTrap (zym_cpu_t
*z80
, uint8_t trapCode
) {
934 case 0xfb: // SLT trap
936 libspectrum_byte
*slt
= libspectrum_snap_slt(zxSLT
, z80
->af
.a
);
938 size_t len
= libspectrum_snap_slt_length(zxSLT
, z80
->af
.a
);
939 uint16_t addr
= z80
->hl
.w
;
941 z80
->mem_write(z80
, addr
, *slt
++, ZYM_MEMIO_OTHER
);
942 addr
= (addr
+1)&0xffff;
947 z80
->af
.f
^= ZYM_FLAG_C
; // complement carry to indicate that there was an error
949 case 0xfe: // ZXEmuT trap, must be followed by JR (and JP?)
950 if (optAllowZXEmuTraps
&& z80
->mem_read(z80
, (z80
->pc
)&0xffff, ZYM_MEMIO_OTHER
) == 0x18) {
951 uint8_t opcsize
= z80
->mem_read(z80
, (z80
->pc
+1)&0xffff, ZYM_MEMIO_OTHER
);
952 if (opcsize
> 0 && opcsize
<= 0x7f) {
953 // 0xed, 0xfe, 0x18 (JR), and then # of data bytes (hehe, JR will jump over 'em)
954 const uint16_t addr
= z80
->pc
+3; // data address
955 z80
->pc
+= opcsize
+2;
956 if (zxemutTraps(z80
, z80
->mem_read(z80
, addr
-1, ZYM_MEMIO_OTHER
), addr
, opcsize
-1)) {
957 return 1; // exit NOW (used to activate the debugger)
967 ////////////////////////////////////////////////////////////////////////////////
968 // kempstons (joystick & mouse)
969 static int iclamp (int val
, int vmin
, int vmax
) {
970 return (val
< vmin
? vmin
: val
> vmax
? vmax
: val
);
973 static int phKempstonIn (zym_cpu_t
*z80
, uint16_t port
, uint8_t *res
) {
974 //fprintf(stderr, "KEPMSTON: PC=#%04X port=#%04X\n", z80->pc, port);
976 // #00DF check for some Spanish games
977 // ((port&0xffu) == 0xdfu && (port&0xff00u) != 0)
979 // 0xfadf: buttons (hiword bits 0,2 are zero)
980 // 0xfbdf: buttons (hiword bit 2 is zero)
981 // 0xffdf: buttons (not hiword bits are zero)
982 if (optKMouse
&& (port
&0xffu
) == 0xdfu
&&
983 (optKMouseStrict
? (port
&0xfa00u
) == 0xfa00u
: (port
&0xf000u
) == 0xf000u
))
985 //fprintf(stderr, " kmouse!\n");
987 if ((port
|0xfa00) == 0xfadf) {
988 // bit 0: left; bit 1: right; bit 2: middle; pressed if bit reset
990 (zxKMouseButtons
&MS_BUTTON_LEFT
? (optKMouseSwapButtons
? 0x02 : 0x01) : 0)|
991 (zxKMouseButtons
&MS_BUTTON_RIGHT
? (optKMouseSwapButtons
? 0x01 : 0x02) : 0)|
992 (zxKMouseButtons
&MS_BUTTON_MIDDLE
? 0x04 : 0);
993 *res
= (mbt
^0x0f)|((zxKMouseWheel
<<4)&0xf0);
997 if ((port
|0xfa00u
) == 0xfbdfu
) {
998 *res
= (optKMouseAbsolute
? iclamp(kmouseAbsX
, 0, 255) : zxKMouseDX
);
1002 if ((port
|0xfa00u
) == 0xffdfu
) {
1003 *res
= (optKMouseAbsolute
? iclamp(kmouseAbsY
, 0, 191) : zxKMouseDY
);
1009 // kempston joystick
1010 int af
= isAutofireKempston();
1011 *res
= ((~zxKeyboardState
[8])&0x1f)|af
;
1019 static const PortHandlers phKempston
[] = {
1021 { ZX_MACHINE_MAX
, 0x0020, 0x0000, phKempstonIn
, NULL
},
1024 { ZX_MACHINE_PENTAGON, 0x0521, 0x0501, phKempstonIn, NULL },
1025 { ZX_MACHINE_PENTAGON, 0x0521, 0x0101, phKempstonIn, NULL },
1026 { ZX_MACHINE_PENTAGON, 0x0121, 0x0001, phKempstonIn, NULL },
1028 { 0, 0, 0, NULL
, NULL
}};
1031 ////////////////////////////////////////////////////////////////////////////////
1032 #define FUSE_RD(name_) \
1033 static int emu_##name_ (zym_cpu_t *z80, uint16_t port, uint8_t *res) { \
1034 if (zxTRDOSPagedIn /*&& zxDiskIf && difGetHW(zxDiskIf) == DIF_BDI*/) { \
1035 *res = name_(port); \
1036 diskLastActivity = timerGetMS(); \
1043 #define FUSE_WR(name_) \
1044 static int emu_##name_ (zym_cpu_t *z80, uint16_t port, uint8_t value) { \
1045 if (zxTRDOSPagedIn /*&& zxDiskIf && difGetHW(zxDiskIf) == DIF_BDI*/) { \
1046 name_(port, value); \
1047 diskLastActivity = timerGetMS(); \
1053 FUSE_RD(beta_sr_read
)
1054 FUSE_RD(beta_tr_read
)
1055 FUSE_RD(beta_sec_read
)
1056 FUSE_RD(beta_dr_read
)
1057 FUSE_RD(beta_sp_read
)
1059 FUSE_WR(beta_cr_write
)
1060 FUSE_WR(beta_tr_write
)
1061 FUSE_WR(beta_sec_write
)
1062 FUSE_WR(beta_dr_write
)
1063 FUSE_WR(beta_sp_write
)
1066 static const PortHandlers phTRDOS
[] = {
1067 { ZX_MACHINE_MAX
, 0x00ff, 0x001f, emu_beta_sr_read
, emu_beta_cr_write
},
1068 { ZX_MACHINE_MAX
, 0x00ff, 0x003f, emu_beta_tr_read
, emu_beta_tr_write
},
1069 { ZX_MACHINE_MAX
, 0x00ff, 0x005f, emu_beta_sec_read
, emu_beta_sec_write
},
1070 { ZX_MACHINE_MAX
, 0x00ff, 0x007f, emu_beta_dr_read
, emu_beta_dr_write
},
1071 { ZX_MACHINE_MAX
, 0x00ff, 0x00ff, emu_beta_sp_read
, emu_beta_sp_write
},
1072 { 0, 0, 0, NULL
, NULL
}
1076 ////////////////////////////////////////////////////////////////////////////////
1078 static int phUPD765_status (zym_cpu_t
*z80
, uint16_t port
, uint8_t *res
) {
1079 if (!upd765_fdc
) return 0;
1080 diskLastActivity
= timerGetMS();
1081 *res
= upd_fdc_read_status(upd765_fdc
);
1086 static int phUPD765_read (zym_cpu_t
*z80
, uint16_t port
, uint8_t *res
) {
1087 if (!upd765_fdc
) return 0;
1088 diskLastActivity
= timerGetMS();
1089 *res
= upd_fdc_read_data(upd765_fdc
);
1094 static int phUPD765_write (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
1095 if (!upd765_fdc
) return 0;
1096 diskLastActivity
= timerGetMS();
1097 upd_fdc_write_data(upd765_fdc
, value
);
1102 static int phUPD765_dummy (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
1107 // allow uPD765 for any machine
1108 static const PortHandlers phUPD765
[] = {
1109 { ZX_MACHINE_MAX
, 0xf002, 0x3000, phUPD765_read
, phUPD765_write
}, // fdc r/w
1110 { ZX_MACHINE_MAX
, 0xf002, 0x2000, phUPD765_status
, phUPD765_dummy
}, // status
1111 { 0, 0, 0, NULL
, NULL
}
1115 ////////////////////////////////////////////////////////////////////////////////
1116 // 128k memory mode port
1117 static int ph7FFDout (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
1118 if (!zx7ffdLocked
) {
1119 //fprintf(stderr, "O: 7FFD; port=#%04X; value=#%02X\n", port, value);
1120 zxLastOut7ffd
= value
;
1121 emuRealizeMemoryPaging();
1129 static int ph7FFDoutP1 (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
1130 if (zxPentagonMemory
> 512) return 0;
1131 return ph7FFDout(z80
, port
, value
);
1135 static int ph7FFDoutP2 (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
1136 if (zxPentagonMemory
< 1024) return 0;
1137 return ph7FFDout(z80
, port
, value
);
1141 static int ph7FFDin (zym_cpu_t
*z80
, uint16_t port
, uint8_t *res
) {
1142 if (!zx7ffdLocked
) {
1143 //128k bug with 0x7ffd
1144 //fprintf(stderr, "I: 7FFD; port=#%04X\n", port);
1145 *res
= z80PortInInexistant(z80
, port
);
1147 if (machineInfo
.port7ffDbug
) ph7FFDout(z80
, port
, *res
);
1154 // +3/Scorpion/Pentagon512/Pentagon1024 memory mode port
1155 // for Pentagon1024, this is actually #EFFD port (A3, A12)
1156 static int ph1FFDout (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
1157 if (!zx7ffdLocked
&& machineInfo
.port1ffd
>= 0) {
1158 //fprintf(stderr, "O: 1FFD; port=#%04X; value=#%02X\n", port, value);
1159 zxLastOut1ffd
= value
;
1160 emuRealizeMemoryPaging();
1163 fdd_motoron(upd765_fdc
->drive
[0], (value
&0x08));
1164 fdd_motoron(upd765_fdc
->drive
[1], (value
&0x08));
1172 static int ph7FFDoutBad (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
1173 if (optFDas7FFD
&& !zx7ffdLocked
) {
1174 //fprintf(stderr, "O: 7FFD; port=#%04X; value=#%02X\n", port, value);
1175 zxLastOut7ffd
= value
;
1176 emuRealizeMemoryPaging();
1183 static const PortHandlers phMemory
[] = {
1185 { ZX_MACHINE_128K
, 0x8002, 0x0000, ph7FFDin
, ph7FFDout
},
1186 { ZX_MACHINE_PLUS2
, 0x8002, 0x0000, ph7FFDin
, ph7FFDout
},
1188 { ZX_MACHINE_PLUS2A
, 0x8002, 0x0000, ph7FFDin
, ph7FFDout
},
1189 { ZX_MACHINE_PLUS2A
, 0xf002, 0x1000, NULL
, ph1FFDout
},
1191 { ZX_MACHINE_PLUS3
, 0x8002, 0x0000, ph7FFDin
, ph7FFDout
},
1192 { ZX_MACHINE_PLUS3
, 0xf002, 0x1000, NULL
, ph1FFDout
},
1194 { ZX_MACHINE_SCORPION
, 0xc002, 0x4000, NULL
, ph7FFDout
},
1195 { ZX_MACHINE_SCORPION
, 0xf002, 0x1000, NULL
, ph1FFDout
},
1197 { ZX_MACHINE_PENTAGON
, 0x8002, 0x0000, NULL
, ph7FFDoutP1
},
1199 { ZX_MACHINE_PENTAGON
, 0xc002, 0x4000, NULL
, ph7FFDoutP2
},
1200 { ZX_MACHINE_PENTAGON
, 0xf008, 0xe000, NULL
, ph1FFDout
},
1202 { ZX_MACHINE_MAX
, 0x8002, 0x0000, NULL
, ph7FFDoutBad
},
1203 { 0, 0, 0, NULL
, NULL
}};
1206 ////////////////////////////////////////////////////////////////////////////////
1208 static int phAYinR (zym_cpu_t
*z80
, uint16_t port
, uint8_t *res
) {
1211 uint8_t val
= zxAYRegs
[zxLastOutFFFD
&0x0f], val7
= zxAYRegs
[7];
1212 //fprintf(stderr, "I: AYR; port=#%04X\n", port);
1213 if (zxLastOutFFFD
== 14) *res
= (val7
&0x40 ? 0xbf&val
: 0xbf);
1214 else if (zxLastOutFFFD
== 15 && !(val7
&0x80)) *res
= 0xff;
1222 static int phAYoutR (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
1225 //fprintf(stderr, "O: AYR; port=#%04X\n", port);
1226 zxLastOutFFFD
= (value
&0x0f);
1233 static int phAYoutD (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
1235 //fprintf(stderr, "O: AYD; port=#%04X\n", port);
1236 switch (zxLastOutFFFD
) {
1237 case 1: case 3: case 5: case 13: value
&= 0x0f; break;
1238 case 6: case 8: case 9: case 10: value
&= 0x1f; break;
1240 zxAYRegs
[zxLastOutFFFD
] = value
;
1241 soundAYWrite(zxLastOutFFFD
, value
, z80
->tstates
);
1247 static const PortHandlers phAY
[] = {
1248 { ZX_MACHINE_MAX
, 0xc002, 0xc000, phAYinR
, phAYoutR
},
1249 { ZX_MACHINE_MAX
, 0xc002, 0x8000, NULL
, phAYoutD
},
1250 // full: {0xffff, 0xfffd}, {0xffff, 0xbffd}
1251 { 0, 0, 0, NULL
, NULL
}};
1254 ////////////////////////////////////////////////////////////////////////////////
1256 static int phULAin (zym_cpu_t
*z80
, uint16_t port
, uint8_t *res
) {
1257 uint8_t r
= zxUlaOut
, p1
= (port
>>8);
1261 int af
= isAutofireKeyboard();
1263 for (int f
= 0; f
< 8; ++f
) ks
[f
] = zxKeyboardState
[f
];
1264 if (af
) ks
[af
>>8] &= ~(af
&0xff);
1265 // emulate keyboard matrix effect
1266 if (optEmulateMatrix
) {
1269 for (int k
= 0; k
< 7; ++k
) {
1270 for (int j
= k
+1; j
< 8; ++j
) {
1271 if ((ks
[k
]|ks
[j
]) != 0xff && ks
[k
] != ks
[j
]) {
1272 ks
[k
] = ks
[j
] = (ks
[k
]&ks
[j
]);
1280 for (int f
= 0; f
< 8; ++f
) if ((p1
&(1<<f
)) == 0) r
&= ks
[f
];
1281 // check for known tape loaders
1284 zxCurTape
!= NULL
&& !tapNeedRewind
&&
1286 (zxModel
== ZX_MACHINE_PLUS2A
|| zxModel
== ZX_MACHINE_PLUS3
? zxLastPagedROM
== 3 : (zxModel
== ZX_MACHINE_48K
|| zxLastPagedROM
== 1)))
1288 if (emuTapeFlashLoad() == 0) {
1294 if (optTapeDetector
&& detectlde
) {
1295 emuTapeLoaderDetector();
1297 switch (emuTapeGetCurEdge()) {
1298 case 0: r
&= ~0x40; break;
1299 case 1: r
|= 0x40; break;
1302 /*if (optTapePlaying && optTapeSound) soundBeeper((r>>2)&0x10, z80->tstates);*/
1309 static int phULAout (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
) {
1310 uint8_t bc
= (value
&0x07);
1312 if (optBrightBorder
> 0) bc
|= ((value
>>optBrightBorder
)&0x01)<<3;
1313 if (zxBorder
!= bc
) {
1314 zxRealiseScreen(z80
->tstates
);
1317 /*if (!optTapePlaying || !optTapeSound)*/ soundBeeper(/*value&0x10*/((value
&0x10)>>3), z80
->tstates
);
1319 if (zxModel
== ZX_MACHINE_48K
) {
1322 (value
&0x18 ? 0xff : 0xbf) : //issue2
1323 (value
&0x10 ? 0xff : 0xbf)); //issue3, 128k
1326 zxUlaOut
= (value
&0x10 ? 0xff : 0xbf);
1327 //zxUlaOut = 0xbf; //+3
1334 static const PortHandlers phULA
[] = {
1335 { ZX_MACHINE_MAX
, 0x0001, 0x0000, phULAin
, phULAout
},
1336 { 0, 0, 0, NULL
, NULL
}};
1339 ////////////////////////////////////////////////////////////////////////////////
1340 // REMEMBER, THAT HIGHER PRIORITY DEVICES SHOULD BE ADDED LAST!
1341 // THIS IS SO FOR PORT MASK/VALUES TOO!
1342 static void emuAddPortHandlers (void) {