zxemut: init absolute kmouse coords when "absolute" mode is turned on
[zymosis.git] / src / ZXEmuT / zymcb.c
blobc011a9e20b441e3b7604552d504884ae78067be0
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
28 uint32_t fsize;
29 //uint8_t flags; // currently, only bit 0 is defined, it means "read-only"
30 } FileDirItem;
32 static FileDirItem *fdirItems = NULL;
33 static uint32_t fdirItemsAlloted = 0;
34 static uint32_t fdirItemsCount = 0;
37 //==========================================================================
39 // fopIsValidZXChar
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 //==========================================================================
53 // fopIsValidZXName
55 //==========================================================================
56 static int fopIsValidZXName (const char *s) {
57 if (!s || !s[0]) return 0;
58 int len = 0;
59 for (;;) {
60 char ch = *s++;
61 if (!ch) break;
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;
68 return 1;
72 //==========================================================================
74 // fopRescanDir
76 //==========================================================================
77 static void fopRescanDir (void) {
78 fdirItemsCount = 0;
80 DIR *dir = opendir(".");
81 if (!dir) return; // i don't care about errors for now
83 const size_t pfxlen = strlen(FNAME_PREFIX);
85 struct dirent *de;
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
94 struct stat st;
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
99 // record new file
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;
105 fdirItems = ndi;
108 // guaranteed to fit
109 strcpy(fdirItems[fdirItemsCount].zxname, de->d_name+pfxlen);
110 fdirItems[fdirItemsCount].fsize = (uint32_t)st.st_size; // guaranteed to fit
111 ++fdirItemsCount;
114 closedir(dir);
118 //==========================================================================
120 // trapFileOps
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);
135 unsigned ofs = 0;
136 for (;;) {
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);
140 if (!ch) break;
141 if (!fopIsValidZXChar(ch)) return 0;
142 dest[ofs++] = (char)ch;
144 dest[ofs] = 0;
145 return 1;
149 // scan dir
150 if (tpc == 0x10) {
151 fopRescanDir();
152 z80->hl.w = fdirItemsCount&0xffffU;
153 z80->af.f &= ~ZYM_FLAG_C;
154 return;
157 // get info
158 if (tpc == 0x11) {
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;
164 // 0: get file name
165 if (z80->af.a == 0) {
166 const char *zxname = fdirItems[fidx].zxname;
167 unsigned f = 0;
168 do {
169 z80->mem_write(z80, (z80->de.w+f)&0xffff, (uint8_t)zxname[f], ZYM_MEMIO_OTHER);
170 } while (zxname[f++] != 0);
171 return;
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);
179 unsigned f = 0;
180 while (zxname != ee) {
181 z80->mem_write(z80, (z80->de.w+f)&0xffff, (uint8_t)zxname[f], ZYM_MEMIO_OTHER);
182 ++f;
184 z80->mem_write(z80, (z80->de.w+f)&0xffff, 0, ZYM_MEMIO_OTHER);
185 return;
188 // 2: get file extension
189 if (z80->af.a == 2) {
190 const char *zxname = fdirItems[fidx].zxname;
191 const char *ee = strrchr(zxname, '.');
192 if (!ee) {
193 z80->mem_write(z80, z80->de.w, 0, ZYM_MEMIO_OTHER);
194 return;
196 unsigned f = 0;
197 do {
198 z80->mem_write(z80, (z80->de.w+f)&0xffff, (uint8_t)ee[f], ZYM_MEMIO_OTHER);
199 } while (ee[f++] != 0);
200 return;
203 // 3: get file size
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);
209 sz >>= 4;
211 return;
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);
220 } else {
221 z80->mem_write(z80, z80->de.w, 0, ZYM_MEMIO_OTHER);
223 return;
226 // unknown query
227 goto general_error;
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;
236 uint32_t rpos =
237 (tpc == 0x12 ? 0 :
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) {
249 fclose(fl);
250 z80->bc.w = 0;
251 return;
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);
258 fclose(fl);
260 if (rd > 0) {
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;
265 } else {
266 z80->bc.w = 0;
269 free(buf);
270 return;
273 // create and write file
274 if (tpc == 0x14) {
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) {
290 fclose(fl);
291 goto general_error;
295 fclose(fl);
296 return;
299 // extended write file
300 if (tpc == 0x15) {
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;
308 uint32_t rpos =
309 (tpc == 0x12 ? 0 :
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) {
321 fclose(fl);
322 goto general_error;
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) {
329 fclose(fl);
330 goto general_error;
334 fclose(fl);
335 return;
338 // delete file
339 if (tpc == 0x16) {
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;
346 unlink(diskname);
347 return;
350 // check if file exists and readable/writeable
351 if (tpc == 0x17) {
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) {
359 z80->af.f = 1;
360 } else {
361 z80->af.f = 0;
363 return;
366 // extended read/write file
367 if (tpc == 0x20) {
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;
379 FILE *fl;
380 switch (z80->af.a) {
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");
386 break;
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) {
393 fclose(fl);
394 goto general_error;
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) {
403 // read
404 ssize_t rd = fread(buf, 1, z80->bc.w, fl);
405 if (rd != z80->bc.w && ferror(fl)) {
406 free(buf);
407 fclose(fl);
408 goto general_error;
410 fclose(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;
417 } else {
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)) {
424 free(buf);
425 fclose(fl);
426 goto general_error;
428 fclose(fl);
430 z80->bc.w = (uint16_t)wr;
433 free(buf);
434 return;
437 general_error:
438 // general error
439 z80->af.a = 1;
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) {
451 int pos = ltp/4;
452 return ((int64_t)line<<16)|((pos<<8)|(ltp&0x07));
456 return -1;
460 static inline int z80IsContendedAddr (uint16_t addr) {
461 return
462 (addr >= 0x4000 ?
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) {
474 // ULA snow effect
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);
481 if (lp != -1) {
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;
484 zxWasUlaSnow = 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
500 ++z80->tstates;
501 } else {
502 // later, tstates is ALWAYS 2
503 if ((port&0x01) == 0) {
504 // ULA port
505 z80->tstates += zxContentionTable[1][z80->tstates];
506 z80->tstates += 2;
507 } else {
508 // non-ULA port
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];
513 } else {
514 z80->tstates += 2;
518 } else {
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) {
529 // 'ULA snow effect'
530 int64_t lp = z80GetULACycle(z80->tstates);
532 if (lp != -1) {
533 int pos = (lp>>8)&0xff, ltp = lp&0x0f, line = (lp>>16)&0xff;
535 switch (ltp) {
536 case 1: case 2: case 4: case 5:
537 //default:
538 zxUlaSnow[line][pos] = (uint16_t)(zxScreenBank[((zxScrLineOfs[line]+pos)&0xff00)|z80->regR])+1;
539 zxWasUlaSnow = 1;
540 //zxRealiseScreen(z80->tstates);
541 break;
548 if (mio == ZYM_MEMIO_OPCODE) {
549 if (addr >= 0x4000) zxWasRAMExec = 1; // for Mr.Gluck Reset Service
550 // PAGING START
551 if (optTRDOSenabled > 0 && optTRDOSROMIndex >= 0) {
552 if (zxTRDOSPagedIn) {
553 if (addr >= 0x4000) zxTRDOSpageout();
554 } else {
555 if (zxModel != ZX_MACHINE_48K) {
556 // !48k
557 if ((addr&0xff00) == 0x3d00 && zxLastPagedRAM0 < 0 && zxLastPagedROM == 1) zxTRDOSpagein();
558 } else {
559 // 48k
560 if ((addr&0xfe00) == 0x3c00) zxTRDOSpagein();
563 // TR-DOS traps
564 if (optTRDOSTraps && zxTRDOSPagedIn) {
565 #if 0
566 // TR-DOS short track seek trap
567 if (addr == 0x3dfd) {
568 // check bytes
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");
571 z80->bc.c = 0;
572 z80->af.a = 0;
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) {
580 // check bytes
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");
583 z80->bc.w = 0;
584 z80->af.a = 0;
585 z80->af.f |= ZYM_FLAG_Z; // just in case
586 z80->pc = 0x3ea9; // "ret" address
587 return 0xc9; // "ret"
590 // TR-DOS delay trap
591 if (addr == 0x3dff) {
592 // check bytes
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");
595 z80->bc.c = 1;
596 z80->af.a = 1;
597 z80->af.f |= ZYM_FLAG_Z; // just in case
598 z80->pc = 0x3e07; // "ret" address
599 return 0xc9; // "ret"
602 #else
603 // TR-DOS delay trap
604 if (addr == 0x3e01) {
605 // check bytes
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");
608 z80->bc.c = 1;
609 z80->af.a = 1;
611 } else if (addr == 0x3e04) {
612 // check bytes
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");
615 z80->af.a = 1;
618 #endif
621 // PAGING END
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]) {
648 case 5: case 7:
649 zxRealiseScreen(z80->tstates);
650 break;
653 zxMemory[addr>>14][addr&0x3fff] = value;
654 if (mio != ZYM_MEMIO_OTHER) zxMemBanksDirty[zxMemoryBankNum[addr>>14]] = 1;
655 } else {
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);
683 if (lp != -1) {
684 int pos = (lp>>8)&0xff, ltp = lp&0x0f, line = (lp>>16)&0xff;
685 switch (ltp) {
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];
692 } else {
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);
699 if (lp != -1) {
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);
703 switch (ltp) {
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;
715 return 0xff;
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;
724 if (zxRZX != NULL) {
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!");
735 lssnap_rzx_stop();
736 lssnap_slt_clear();
737 } else {
738 //fprintf(stderr, " rzx in: port=0x%04x, value=0x%02x\n", port, value);
739 return 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);
747 uint8_t res;
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;
762 int wasULA = 0;
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?!
769 if (res) break;
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 = '.';
786 uint8_t s[2];
787 s[0] = ch;
788 s[1] = 0;
789 cprintf("%s", (const char *)s);
793 //==========================================================================
795 // zxemutTraps
797 //==========================================================================
798 static int zxemutTraps (zym_cpu_t *z80, uint8_t code, uint16_t dataaddr, uint8_t datasize) {
799 switch (code) {
800 case 0: // reset tick counter
801 // check for subcommands
802 if (datasize > 0) {
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
808 switch (sbc) {
809 case 1: dbtTickCounterPaused = 1; dbgTickCounter -= subts; break; // pause
810 case 2: dbtTickCounterPaused = 0; dbgTickCounter -= subts; break; // resume
812 } else {
813 // reset tick counter
814 dbgTickCounter = 0;
815 //cprintf("PC=$%02X; HUI: %llu (p=%d)\n", z80->pc, dbgTickCounter, dbtTickCounterPaused);
817 break;
818 case 1: // show tick counter
819 cprintf("PC=$%04X: ticks: %llu\n", /*dataaddr-5*/z80->pc, dbgTickCounter + z80->trap_ts);
820 break;
821 case 2: // activate debugger
822 dbgSetActive(1);
823 return 1; // exit NOW
824 case 3: // console printing
825 if (datasize > 0) {
826 switch (z80->mem_read(z80, dataaddr, ZYM_MEMIO_OTHER)) {
827 case 0: // print char in A
828 zxemutTrapPrintChar(z80->af.a);
829 break;
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));
834 break;
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);
838 if (!b) break;
839 zxemutTrapPrintChar(b);
841 break;
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);
845 if (!b) break;
846 zxemutTrapPrintChar(b);
848 break;
849 case 4: // print hex byte from A
850 cprintf("%02X", z80->af.a);
851 break;
852 case 5: // print hex word from HL
853 cprintf("%02X", z80->hl.w);
854 break;
855 case 6: // print dec byte from A
856 cprintf("%03u", z80->af.a);
857 break;
858 case 7: // print dec word from HL
859 cprintf("%05u", z80->hl.w);
860 break;
863 break;
864 case 0x10: // emulator control functions
865 if (datasize > 0) {
866 switch (z80->mem_read(z80, dataaddr, ZYM_MEMIO_OTHER)) {
867 case 0x00: // pause the emulation
868 emuSetPaused(1);
869 break;
870 case 0x01: // maxspeed on
871 emuSetMaxSpeed(1);
872 break;
873 case 0x02: // maxspeed off
874 emuSetMaxSpeed(0);
875 break;
876 case 0x60: // kmouse on, absolute
877 optKMouse = 1;
878 optKMouseAbsolute = 1;
879 emuHideRealMouseCursor();
880 emuSetKMouseAbsCoords();
881 z80->af.a = 1;
882 break;
883 case 0x61: // kmouse normal
884 optKMouseAbsolute = 0;
885 break;
886 case 0x62: // hide system mouse cursor
887 emuHideRealMouseCursor();
888 break;
889 case 0x63: // show system mouse cursor
890 emuShowRealMouseCursor();
891 break;
894 break;
895 case 0x13: // file API
896 if (datasize > 0) {
897 trapFileOps(z80, z80->mem_read(z80, dataaddr, ZYM_MEMIO_OTHER),
898 dataaddr+1, datasize-1);
899 } else {
900 // general error
901 z80->af.a = 1;
902 z80->af.f |= ZYM_FLAG_C;
904 break;
905 case 0xff: // check version
906 z80->af.f &= ~ZYM_FLAG_Z;
907 z80->af.a = 0x01;
908 switch (optFileTrapsMode) {
909 case ZOPT_FTP_RO: z80->af.a |= 0x02; break;
910 case ZOPT_FTP_RW: z80->af.a |= 0x02|0x04; break;
912 // emulator ID
913 z80->bc.w = 0x585a;
914 z80->de.w = 0x6d45;
915 z80->hl.w = 0x5475;
916 // emulator version
917 z80->afx.a = 0;
918 break;
920 return 0;
924 // return !0 to exit immediately
925 // called when invalid ED command found
926 // PC points to the next instruction
927 // trapCode=0xFB:
928 // .SLT trap
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) {
933 switch (trapCode) {
934 case 0xfb: // SLT trap
935 if (zxSLT != NULL) {
936 libspectrum_byte *slt = libspectrum_snap_slt(zxSLT, z80->af.a);
937 if (slt != NULL) {
938 size_t len = libspectrum_snap_slt_length(zxSLT, z80->af.a);
939 uint16_t addr = z80->hl.w;
940 while (len-- > 0) {
941 z80->mem_write(z80, addr, *slt++, ZYM_MEMIO_OTHER);
942 addr = (addr+1)&0xffff;
944 return 0;
947 z80->af.f ^= ZYM_FLAG_C; // complement carry to indicate that there was an error
948 break;
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)
961 break;
963 return 0;
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);
975 // kempston mouse
976 // #00DF check for some Spanish games
977 // ((port&0xffu) == 0xdfu && (port&0xff00u) != 0)
978 // kmouse:
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");
986 // 0xfadf, buttons
987 if ((port|0xfa00) == 0xfadf) {
988 // bit 0: left; bit 1: right; bit 2: middle; pressed if bit reset
989 const uint8_t mbt =
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);
994 return 1;
996 // 0xfbdf, x
997 if ((port|0xfa00u) == 0xfbdfu) {
998 *res = (optKMouseAbsolute ? iclamp(kmouseAbsX, 0, 255) : zxKMouseDX);
999 return 1;
1001 // 0xffdf, y
1002 if ((port|0xfa00u) == 0xffdfu) {
1003 *res = (optKMouseAbsolute ? iclamp(kmouseAbsY, 0, 191) : zxKMouseDY);
1004 return 1;
1008 if (optKJoystick) {
1009 // kempston joystick
1010 int af = isAutofireKempston();
1011 *res = ((~zxKeyboardState[8])&0x1f)|af;
1012 return 1;
1015 return 0;
1019 static const PortHandlers phKempston[] = {
1020 // joystick
1021 { ZX_MACHINE_MAX, 0x0020, 0x0000, phKempstonIn, NULL },
1022 // mouse
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(); \
1037 return 1; \
1039 return 0; \
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(); \
1048 return 1; \
1050 return 0; \
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 ////////////////////////////////////////////////////////////////////////////////
1077 // uPD765 ports
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);
1082 return 1;
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);
1090 return 1;
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);
1098 return 1;
1102 static int phUPD765_dummy (zym_cpu_t *z80, uint16_t port, uint8_t value) {
1103 return 0;
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();
1122 return 1;
1125 return 0;
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);
1146 // ignore +2A/+3
1147 if (machineInfo.port7ffDbug) ph7FFDout(z80, port, *res);
1148 return 1;
1150 return 0;
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();
1161 // uPD motors
1162 if (upd765_fdc) {
1163 fdd_motoron(upd765_fdc->drive[0], (value&0x08));
1164 fdd_motoron(upd765_fdc->drive[1], (value&0x08));
1166 return 1;
1168 return 0;
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();
1177 return 1;
1179 return 0;
1183 static const PortHandlers phMemory[] = {
1184 // 128
1185 { ZX_MACHINE_128K, 0x8002, 0x0000, ph7FFDin, ph7FFDout },
1186 { ZX_MACHINE_PLUS2, 0x8002, 0x0000, ph7FFDin, ph7FFDout },
1187 // +2A
1188 { ZX_MACHINE_PLUS2A, 0x8002, 0x0000, ph7FFDin, ph7FFDout },
1189 { ZX_MACHINE_PLUS2A, 0xf002, 0x1000, NULL, ph1FFDout },
1190 // +3
1191 { ZX_MACHINE_PLUS3, 0x8002, 0x0000, ph7FFDin, ph7FFDout },
1192 { ZX_MACHINE_PLUS3, 0xf002, 0x1000, NULL, ph1FFDout },
1193 // scorpion
1194 { ZX_MACHINE_SCORPION, 0xc002, 0x4000, NULL, ph7FFDout },
1195 { ZX_MACHINE_SCORPION, 0xf002, 0x1000, NULL, ph1FFDout },
1196 // pentagon
1197 { ZX_MACHINE_PENTAGON, 0x8002, 0x0000, NULL, ph7FFDoutP1 },
1198 // pentagon1024
1199 { ZX_MACHINE_PENTAGON, 0xc002, 0x4000, NULL, ph7FFDoutP2 },
1200 { ZX_MACHINE_PENTAGON, 0xf008, 0xe000, NULL, ph1FFDout },
1201 // for optFDas7FFD
1202 { ZX_MACHINE_MAX, 0x8002, 0x0000, NULL, ph7FFDoutBad },
1203 { 0, 0, 0, NULL, NULL }};
1206 ////////////////////////////////////////////////////////////////////////////////
1207 // AY ports
1208 static int phAYinR (zym_cpu_t *z80, uint16_t port, uint8_t *res) {
1209 if (optAYEnabled) {
1210 // register port
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;
1215 else *res = val;
1216 return 1;
1218 return 0;
1222 static int phAYoutR (zym_cpu_t *z80, uint16_t port, uint8_t value) {
1223 if (optAYEnabled) {
1224 // register port
1225 //fprintf(stderr, "O: AYR; port=#%04X\n", port);
1226 zxLastOutFFFD = (value&0x0f);
1227 return 1;
1229 return 0;
1233 static int phAYoutD (zym_cpu_t *z80, uint16_t port, uint8_t value) {
1234 if (optAYEnabled) {
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);
1243 return 0;
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 ////////////////////////////////////////////////////////////////////////////////
1255 // ULA ports
1256 static int phULAin (zym_cpu_t *z80, uint16_t port, uint8_t *res) {
1257 uint8_t r = zxUlaOut, p1 = (port>>8);
1258 uint8_t ks[8];
1259 int done;
1260 int detectlde = 1;
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) {
1267 do {
1268 done = 1;
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]);
1273 done = 0;
1277 } while (!done);
1280 for (int f = 0; f < 8; ++f) if ((p1&(1<<f)) == 0) r &= ks[f];
1281 // check for known tape loaders
1282 if (optFlashLoad &&
1283 !zxTRDOSPagedIn &&
1284 zxCurTape != NULL && !tapNeedRewind &&
1285 // oooh...
1286 (zxModel == ZX_MACHINE_PLUS2A || zxModel == ZX_MACHINE_PLUS3 ? zxLastPagedROM == 3 : (zxModel == ZX_MACHINE_48K || zxLastPagedROM == 1)))
1288 if (emuTapeFlashLoad() == 0) {
1289 detectlde = 0;
1290 return r;
1294 if (optTapeDetector && detectlde) {
1295 emuTapeLoaderDetector();
1297 switch (emuTapeGetCurEdge()) {
1298 case 0: r &= ~0x40; break;
1299 case 1: r |= 0x40; break;
1301 //FIXME
1302 /*if (optTapePlaying && optTapeSound) soundBeeper((r>>2)&0x10, z80->tstates);*/
1304 *res = r;
1305 return 1;
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);
1315 zxBorder = bc;
1317 /*if (!optTapePlaying || !optTapeSound)*/ soundBeeper(/*value&0x10*/((value&0x10)>>3), z80->tstates);
1319 if (zxModel == ZX_MACHINE_48K) {
1320 zxUlaOut =
1321 (optZXIssue == 0 ?
1322 (value&0x18 ? 0xff : 0xbf) : //issue2
1323 (value&0x10 ? 0xff : 0xbf)); //issue3, 128k
1324 } else {
1325 //128k
1326 zxUlaOut = (value&0x10 ? 0xff : 0xbf);
1327 //zxUlaOut = 0xbf; //+3
1330 return 1;
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) {
1343 phAdd(phULA);
1344 phAdd(phMemory);
1345 phAdd(phKempston);
1346 phAdd(phAY);
1347 phAdd(phTRDOS);
1348 phAdd(phUPD765);