libfusefdc: better TR-DOS boot setting
[zymosis.git] / src / ZXEmuT / zymcb.c
blobf249b3ab4964b8c760287a79b3e3c4f3563548b6
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
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 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);
136 unsigned ofs = 0;
137 for (;;) {
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);
141 if (!ch) break;
142 if (!fopIsValidZXChar(ch)) return 0;
143 dest[ofs++] = (char)ch;
145 dest[ofs] = 0;
146 return 1;
150 // scan dir
151 if (tpc == 0x10) {
152 fopRescanDir();
153 z80->hl.w = fdirItemsCount&0xffffU;
154 z80->af.f &= ~ZYM_FLAG_C;
155 return;
158 // get info
159 if (tpc == 0x11) {
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;
165 // 0: get file name
166 if (z80->af.a == 0) {
167 const char *zxname = fdirItems[fidx].zxname;
168 unsigned f = 0;
169 do {
170 z80->mem_write(z80, (z80->de.w+f)&0xffff, (uint8_t)zxname[f], ZYM_MEMIO_OTHER);
171 } while (zxname[f++] != 0);
172 return;
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);
180 unsigned f = 0;
181 while (zxname != ee) {
182 z80->mem_write(z80, (z80->de.w+f)&0xffff, (uint8_t)zxname[f], ZYM_MEMIO_OTHER);
183 ++f;
185 z80->mem_write(z80, (z80->de.w+f)&0xffff, 0, ZYM_MEMIO_OTHER);
186 return;
189 // 2: get file extension
190 if (z80->af.a == 2) {
191 const char *zxname = fdirItems[fidx].zxname;
192 const char *ee = strrchr(zxname, '.');
193 if (!ee) {
194 z80->mem_write(z80, z80->de.w, 0, ZYM_MEMIO_OTHER);
195 return;
197 unsigned f = 0;
198 do {
199 z80->mem_write(z80, (z80->de.w+f)&0xffff, (uint8_t)ee[f], ZYM_MEMIO_OTHER);
200 } while (ee[f++] != 0);
201 return;
204 // 3: get file size
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);
210 sz >>= 4;
212 return;
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);
221 } else {
222 z80->mem_write(z80, z80->de.w, 0, ZYM_MEMIO_OTHER);
224 return;
227 // unknown query
228 goto general_error;
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;
237 uint32_t rpos =
238 (tpc == 0x12 ? 0 :
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) {
250 fclose(fl);
251 z80->bc.w = 0;
252 return;
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);
259 fclose(fl);
261 if (rd > 0) {
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;
266 } else {
267 z80->bc.w = 0;
270 free(buf);
271 return;
274 // create and write file
275 if (tpc == 0x14) {
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) {
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 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) {
328 fclose(fl);
329 goto general_error;
333 fclose(fl);
334 return;
337 // delete file
338 if (tpc == 0x16) {
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;
345 unlink(diskname);
346 return;
349 // check if file exists and readable/writeable
350 if (tpc == 0x17) {
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) {
358 z80->af.f = 1;
359 } else {
360 z80->af.f = 0;
362 return;
365 general_error:
366 // general error
367 z80->af.a = 1;
368 z80->af.f |= ZYM_FLAG_C;
372 // ////////////////////////////////////////////////////////////////////////// //
373 static void trdosTraps (void) {
374 // nothing here yet
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) {
385 int pos = ltp/4;
386 return ((int64_t)line<<16)|((pos<<8)|(ltp&0x07));
390 return -1;
394 static inline int z80IsContendedAddr (uint16_t addr) {
395 return
396 (addr >= 0x4000 ?
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) {
408 // ULA snow effect
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);
415 if (lp != -1) {
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;
418 zxWasUlaSnow = 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
434 ++z80->tstates;
435 } else {
436 // later, tstates is ALWAYS 2
437 if ((port&0x01) == 0) {
438 // ULA port
439 z80->tstates += zxContentionTable[1][z80->tstates];
440 z80->tstates += 2;
441 } else {
442 // non-ULA port
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];
447 } else {
448 z80->tstates += 2;
452 } else {
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) {
463 // 'ULA snow effect'
464 int64_t lp = z80GetULACycle(z80->tstates);
466 if (lp != -1) {
467 int pos = (lp>>8)&0xff, ltp = lp&0x0f, line = (lp>>16)&0xff;
469 switch (ltp) {
470 case 1: case 2: case 4: case 5:
471 //default:
472 zxUlaSnow[line][pos] = (uint16_t)(zxScreenBank[((zxScrLineOfs[line]+pos)&0xff00)|z80->regR])+1;
473 zxWasUlaSnow = 1;
474 //zxRealiseScreen(z80->tstates);
475 break;
482 if (mio == ZYM_MEMIO_OPCODE) {
483 // PAGING START
484 if (optTRDOSenabled > 0 && optTRDOSROMIndex >= 0) {
485 if (zxTRDOSPagedIn) {
486 if (addr >= 0x4000) zxTRDOSpageout();
487 } else {
488 if (zxModel != ZX_MACHINE_48K) {
489 // !48k
490 if ((addr&0xff00) == 0x3d00 && zxLastPagedRAM0 < 0 && zxLastPagedROM == 1) zxTRDOSpagein();
491 } else {
492 // 48k
493 if ((addr&0xfe00) == 0x3c00) zxTRDOSpagein();
496 if (addr < 0x4000 && optTRDOSTraps && zxTRDOSPagedIn) {
497 trdosTraps();
500 // PAGING END
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]) {
527 case 5: case 7:
528 zxRealiseScreen(z80->tstates);
529 break;
532 zxMemory[addr>>14][addr&0x3fff] = value;
533 } else {
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);
558 if (lp != -1) {
559 int pos = (lp>>8)&0xff, ltp = lp&0x0f, line = (lp>>16)&0xff;
560 switch (ltp) {
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];
567 } else {
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);
574 if (lp != -1) {
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);
578 switch (ltp) {
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;
590 return 0xff;
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;
599 if (zxRZX != NULL) {
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!");
610 lssnap_rzx_stop();
611 lssnap_slt_clear();
612 } else {
613 //fprintf(stderr, " rzx in: port=0x%04x, value=0x%02x\n", port, value);
614 return 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);
622 uint8_t res;
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;
637 int wasULA = 0;
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?!
644 if (res) break;
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
656 // trapCode=0xFB:
657 // .SLT trap
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) {
662 switch (trapCode) {
663 case 0xfb: // SLT trap
664 if (zxSLT != NULL) {
665 libspectrum_byte *slt = libspectrum_snap_slt(zxSLT, z80->af.a);
667 if (slt != NULL) {
668 size_t len = libspectrum_snap_slt_length(zxSLT, z80->af.a);
669 uint16_t addr = z80->hl.w;
671 while (len-- > 0) {
672 z80->mem_write(z80, addr, *slt++, ZYM_MEMIO_OTHER);
673 addr = (addr+1)&0xffff;
675 return 0;
678 z80->af.f ^= ZYM_FLAG_C; // complement carry to indicate that there was an error
679 break;
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;
685 z80->pc = addr;
686 if (opcsize == 1) {
687 const uint8_t tpc = z80->mem_read(z80, addr-1, ZYM_MEMIO_OTHER);
688 switch (tpc) {
689 case 0: // reset tick counter
690 dbgTickCounter = 0;
691 dbgTickCounter -= 8; // trap timing (two opcodes)
692 break;
693 case 1: // show tick counter
694 cprintf("ticks: %llu\n", dbgTickCounter);
695 break;
696 case 2: // activate debugger
697 dbgSetActive(1);
698 return 1; // exit NOW
699 case 3: // pause emulator
700 emuSetPaused(1);
701 break;
702 case 0xfe: // maxspeed on
703 emuSetMaxSpeed(1);
704 break;
705 case 0xff: // maxspeed off
706 emuSetMaxSpeed(0);
707 break;
708 default:
709 if (tpc >= 0x10 && tpc <= 0x17) {
710 trapFileOps(z80, tpc, addr, opcsize);
712 break;
716 break;
718 return 0;
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);
726 // kempston mouse
727 // #00DF check for some Spanish games
728 // ((port&0xffu) == 0xdfu && (port&0xff00u) != 0)
729 // kmouse:
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");
737 // 0xfadf, buttons
738 if ((port|0xfa00) == 0xfadf) {
739 // bit 0: left; bit 1: right; bit 2: middle; pressed if bit reset
740 const uint8_t mbt =
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);
745 return 1;
747 // 0xfbdf, x
748 if ((port|0xfa00u) == 0xfbdfu) {
749 *res = zxKMouseDX;
750 return 1;
752 // 0xffdf, y
753 if ((port|0xfa00u) == 0xffdfu) {
754 *res = zxKMouseDY;
755 return 1;
759 if (optKJoystick) {
760 // kempston joystick
761 int af = isAutofireKempston();
762 *res = ((~zxKeyboardState[8])&0x1f)|af;
763 return 1;
766 return 0;
770 static const PortHandlers phKempston[] = {
771 // joystick
772 { ZX_MACHINE_MAX, 0x0020, 0x0000, phKempstonIn, NULL },
773 // mouse
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(); \
788 return 1; \
790 return 0; \
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(); \
799 return 1; \
801 return 0; \
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 ////////////////////////////////////////////////////////////////////////////////
828 // uPD765 ports
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);
833 return 1;
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);
841 return 1;
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);
849 return 1;
853 static int phUPD765_dummy (zym_cpu_t *z80, uint16_t port, uint8_t value) {
854 return 0;
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) {
869 if (!zx7ffdLocked) {
870 //fprintf(stderr, "O: 7FFD; port=#%04X; value=#%02X\n", port, value);
871 zxLastOut7ffd = value;
872 emuRealizeMemoryPaging();
873 return 1;
876 return 0;
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) {
893 if (!zx7ffdLocked) {
894 //128k bug with 0x7ffd
895 //fprintf(stderr, "I: 7FFD; port=#%04X\n", port);
896 *res = z80PortInInexistant(z80, port);
897 // ignore +2A/+3
898 if (machineInfo.port7ffDbug) ph7FFDout(z80, port, *res);
899 return 1;
901 return 0;
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();
911 // uPD motors
912 if (upd765_fdc) {
913 fdd_motoron(upd765_fdc->drive[0], (value&0x08));
914 fdd_motoron(upd765_fdc->drive[1], (value&0x08));
916 return 1;
918 return 0;
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();
927 return 1;
929 return 0;
933 static const PortHandlers phMemory[] = {
934 // 128
935 { ZX_MACHINE_128K, 0x8002, 0x0000, ph7FFDin, ph7FFDout },
936 { ZX_MACHINE_PLUS2, 0x8002, 0x0000, ph7FFDin, ph7FFDout },
937 // +2A
938 { ZX_MACHINE_PLUS2A, 0x8002, 0x0000, ph7FFDin, ph7FFDout },
939 { ZX_MACHINE_PLUS2A, 0xf002, 0x1000, NULL, ph1FFDout },
940 // +3
941 { ZX_MACHINE_PLUS3, 0x8002, 0x0000, ph7FFDin, ph7FFDout },
942 { ZX_MACHINE_PLUS3, 0xf002, 0x1000, NULL, ph1FFDout },
943 // scorpion
944 { ZX_MACHINE_SCORPION, 0xc002, 0x4000, NULL, ph7FFDout },
945 { ZX_MACHINE_SCORPION, 0xf002, 0x1000, NULL, ph1FFDout },
946 // pentagon
947 { ZX_MACHINE_PENTAGON, 0x8002, 0x0000, NULL, ph7FFDoutP1 },
948 // pentagon1024
949 { ZX_MACHINE_PENTAGON, 0xc002, 0x4000, NULL, ph7FFDoutP2 },
950 { ZX_MACHINE_PENTAGON, 0xf008, 0xe000, NULL, ph1FFDout },
951 // for optFDas7FFD
952 { ZX_MACHINE_MAX, 0x8002, 0x0000, NULL, ph7FFDoutBad },
953 { 0, 0, 0, NULL, NULL }};
956 ////////////////////////////////////////////////////////////////////////////////
957 // AY ports
958 static int phAYinR (zym_cpu_t *z80, uint16_t port, uint8_t *res) {
959 if (optAYEnabled) {
960 // register port
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;
965 else *res = val;
966 return 1;
968 return 0;
972 static int phAYoutR (zym_cpu_t *z80, uint16_t port, uint8_t value) {
973 if (optAYEnabled) {
974 // register port
975 //fprintf(stderr, "O: AYR; port=#%04X\n", port);
976 zxLastOutFFFD = (value&0x0f);
977 return 1;
979 return 0;
983 static int phAYoutD (zym_cpu_t *z80, uint16_t port, uint8_t value) {
984 if (optAYEnabled) {
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();
993 } else {
994 soundAYWrite(zxLastOutFFFD, value, z80->tstates);
996 } else {
997 soundAYResetRegisters();
999 return 0;
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 ////////////////////////////////////////////////////////////////////////////////
1011 // ULA ports
1012 static int phULAin (zym_cpu_t *z80, uint16_t port, uint8_t *res) {
1013 uint8_t r = zxUlaOut, p1 = (port>>8);
1014 uint8_t ks[8];
1015 int done;
1016 int detectlde = 1;
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) {
1023 do {
1024 done = 1;
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]);
1029 done = 0;
1033 } while (!done);
1036 for (int f = 0; f < 8; ++f) if ((p1&(1<<f)) == 0) r &= ks[f];
1037 // check for known tape loaders
1038 if (optFlashLoad &&
1039 !zxTRDOSPagedIn &&
1040 zxCurTape != NULL && !tapNeedRewind &&
1041 // oooh...
1042 (zxModel == ZX_MACHINE_PLUS2A || zxModel == ZX_MACHINE_PLUS3 ? zxLastPagedROM == 3 : (zxModel == ZX_MACHINE_48K || zxLastPagedROM == 1)))
1044 if (emuTapeFlashLoad() == 0) {
1045 detectlde = 0;
1046 return r;
1050 if (optTapeDetector && detectlde) {
1051 emuTapeLoaderDetector();
1053 switch (emuTapeGetCurEdge()) {
1054 case 0: r &= ~0x40; break;
1055 case 1: r |= 0x40; break;
1057 //FIXME
1058 /*if (optTapePlaying && optTapeSound) soundBeeper((r>>2)&0x10, z80->tstates);*/
1060 *res = r;
1061 return 1;
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);
1071 zxBorder = bc;
1073 /*if (!optTapePlaying || !optTapeSound)*/ soundBeeper(/*value&0x10*/((value&0x10)>>3), z80->tstates);
1075 if (zxModel == ZX_MACHINE_48K) {
1076 zxUlaOut =
1077 (optZXIssue == 0 ?
1078 (value&0x18 ? 0xff : 0xbf) : //issue2
1079 (value&0x10 ? 0xff : 0xbf)); //issue3, 128k
1080 } else {
1081 //128k
1082 zxUlaOut = (value&0x10 ? 0xff : 0xbf);
1083 //zxUlaOut = 0xbf; //+3
1086 return 1;
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) {
1099 phAdd(phULA);
1100 phAdd(phMemory);
1101 phAdd(phKempston);
1102 phAdd(phAY);
1103 phAdd(phTRDOS);
1104 phAdd(phUPD765);