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 **************************************************************************/
23 #ifndef _DEFAULT_SOURCE
24 # define _DEFAULT_SOURCE
37 #include <sys/socket.h>
40 #include <sys/types.h>
42 #include <sys/select.h>
46 #include <libspectrum.h>
49 ////////////////////////////////////////////////////////////////////////////////
50 #include "libtinf/tinf.h"
51 #include "../libzymosis/zymosis.h"
52 #include "../libfdc/libfdc.h"
55 ////////////////////////////////////////////////////////////////////////////////
56 #include "emucommon.h"
60 #include "libvideo/video.h"
63 #include "zxscrdraw.h"
68 #include "zxkeyinfo.h"
76 ////////////////////////////////////////////////////////////////////////////////
77 extern const unsigned char keyHelpScr
[];
80 ////////////////////////////////////////////////////////////////////////////////
84 static void initMyDir (void) {
85 if (readlink("/proc/self/exe", binMyDir
, sizeof(binMyDir
)-1) < 0) {
86 strcpy(binMyDir
, ".");
88 char *p
= (char *)strrchr(binMyDir
, '/');
90 if (p
== NULL
) strcpy(binMyDir
, "."); else *p
= '\0';
95 ////////////////////////////////////////////////////////////////////////////////
96 const char *snapshotExtensions
[] = {
123 ////////////////////////////////////////////////////////////////////////////////
124 typedef struct PortHandlers
{
125 int machine
; // ZX_MACHINE_MAX: any
128 int (*portInCB
) (zym_cpu_t
*z80
, uint16_t port
, uint8_t *res
);
129 int (*portOutCB
) (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
);
133 #define MAX_PORTHANDLER (64)
134 static PortHandlers portHandlers
[MAX_PORTHANDLER
];
135 static int phCount
= 0;
138 static void phAdd (const PortHandlers
*ph
) {
140 while (ph
->portInCB
|| ph
->portOutCB
) {
141 if (phCount
>= MAX_PORTHANDLER
) { fprintf(stderr
, "FATAL: too many port handlers!\n"); abort(); }
142 portHandlers
[phCount
++] = *ph
++;
148 ////////////////////////////////////////////////////////////////////////////////
152 ////////////////////////////////////////////////////////////////////////////////
153 static int usock_stdin
= 0;
154 static int usock_srv
= -1;
155 static int usock_client
= -1;
156 static char usock_command
[1024];
157 static size_t usock_command_pos
= 0;
160 static size_t unixsock_create_address (struct sockaddr_un
*addr
, const char *name
) {
161 if (!addr
|| !name
|| !name
[0] || strlen(name
) > 100) return 0;
162 memset((void *)addr
, 0, sizeof(*addr
));
163 addr
->sun_family
= AF_UNIX
;
164 addr
->sun_path
[0] = 0; /* abstract unix socket */
165 strcpy(addr
->sun_path
+1, name
);
166 return strlen(addr
->sun_path
+1)+sizeof(addr
->sun_family
)+1u;
170 static void sock_make_non_blocking (int fd
) {
172 int flags
= fcntl(fd
, F_GETFL
, 0);
174 fcntl(fd
, F_SETFL
, flags
);
179 static void unixsock_drop_client (void) {
180 if (usock_client
>= 0) {
184 usock_command_pos
= 0;
188 static void unixsock_stop_server (void) {
189 unixsock_drop_client();
190 if (usock_srv
>= 0) {
197 static void unixsock_start_server (const char *name
) {
198 unixsock_stop_server();
201 if (name
&& strcmp(name
, "-") == 0) {
203 cprintf("\4ERROR: stdin already closed!\n");
210 sock_make_non_blocking(usock_srv
);
214 usock_srv
= socket(AF_UNIX
, SOCK_STREAM
, 0);
216 cprintf("\4ERROR: cannot create server socket!\n");
220 sock_make_non_blocking(usock_srv
);
222 struct sockaddr_un serv_addr
;
223 size_t servlen
= unixsock_create_address(&serv_addr
, name
);
224 if (bind(usock_srv
, (struct sockaddr
*)&serv_addr
, servlen
) < 0) {
225 cprintf("\4ERROR: cannot bind server socket!\n");
231 if (listen(usock_srv
, 1) < 0) {
232 cprintf("\4ERROR: cannot listen on server socket!\n");
238 cprintf("\1created unix server socket '%s'\n", name
);
242 static void unixsock_handle (void) {
243 if (usock_srv
< 0 && usock_client
< 0) return;
248 /* can accept new client? */
249 if (usock_client
< 0) {
250 /* yeah, check for new client */
252 FD_SET(usock_srv
, &rds
);
253 int res
= select(usock_srv
+1, &rds
, NULL
, NULL
, &tout
);
254 if (res
<= 0) return; /* nothing */
255 /* accept new client */
256 struct sockaddr_un cli_addr
;
257 socklen_t clilen
= (socklen_t
)sizeof(cli_addr
);
258 int newsockfd
= accept(usock_srv
, (struct sockaddr
*)&cli_addr
, &clilen
);
260 cprintf("\4ERROR: error accepting client connection!\n");
263 usock_client
= newsockfd
;
264 cprintf("\1USOCK: accepted new client\n");
266 /* check if we can read from client socket */
267 for (int maxread
= 4096; maxread
> 0; --maxread
) {
270 FD_SET(usock_client
, &rds
);
271 int res
= select(usock_client
+1, &rds
, NULL
, NULL
, &tout
);
272 if (res
<= 0) break; /* nothing */
273 ssize_t rd
= read(usock_client
, &ch
, 1);
275 cprintf("\2USOCK: client closed the connection\n");
276 unixsock_drop_client();
280 cprintf("\4ERROR: error reading from client connection!\n");
281 unixsock_drop_client();
284 if (usock_command_pos
>= sizeof(usock_command
)-2) {
285 cprintf("\4ERROR: too long command from client!\n");
286 unixsock_drop_client();
289 if (ch
== '\n') ch
= 0;
290 usock_command
[usock_command_pos
++] = ch
;
292 /* command complete */
293 usock_command_pos
= 0;
294 char *cmd
= strprintf("::usock_received %s", usock_command
);
295 if (Jim_Eval(jim
, cmd
) == JIM_OK
) {
297 Jim_Obj
*res
= Jim_GetResult(jim
);
298 const char *resstr
= Jim_String(res
);
299 if (resstr
&& strcasecmp(resstr
, "close") == 0) {
300 cprintf("closing client connection (client request).\n");
301 unixsock_drop_client();
304 if (resstr
&& strcasecmp(resstr
, "done") == 0) {
309 Jim_MakeErrorMessage(jim
);
310 cprintf("\4=== JIM ERROR ===\n%s\n=================\n", Jim_GetString(Jim_GetResult(jim
), NULL
));
312 /* close client connection? */
314 if (strcmp(usock_command, "close") == 0) {
315 cprintf("closing client connection (client request).\n");
316 unixsock_drop_client();
320 cprintf("\2USOCK COMMAND:<%s>\n", usock_command
);
321 conExecute(usock_command
, 0);
328 ////////////////////////////////////////////////////////////////////////////////
329 static inline void zxDoKeyUpDown (uint16_t portmask
, int isdown
) {
332 zxKeyboardState
[(portmask
>>8)&0xff] &= ~(portmask
&0xff);
334 zxKeyboardState
[(portmask
>>8)&0xff] |= (portmask
&0xff);
340 ////////////////////////////////////////////////////////////////////////////////
341 static void emuInitMemory (void) {
342 zxMaxMemoryBank
= 64;
343 for (int f
= 0; f
< zxMaxMemoryBank
; ++f
) {
344 if ((zxMemoryBanks
[f
] = calloc(1, 16384)) == NULL
) {
345 fprintf(stderr
, "FATAL: out of memory!\n");
350 zxMaxROMMemoryBank
= 16;
351 for (int f
= 0; f
< zxMaxROMMemoryBank
; ++f
) {
352 if ((zxROMBanks
[f
] = calloc(1, 16384)) == NULL
) {
353 fprintf(stderr
, "FATAL: out of memory!\n");
360 static void emuInit (void) {
363 for (int t
= 0; t
< 3; ++t
) {
364 for (int y
= 0; y
< 8; ++y
) {
365 for (int z
= 0; z
< 8; ++z
) {
366 zxScrLineOfs
[lineNo
++] = (t
<<11)|(z
<<8)|(y
<<5);
371 memset(zxKeyBinds
, 0, sizeof(zxKeyBinds
));
372 memset(zxScreen
, 0, sizeof(zxScreen
));
373 memset(zxUlaSnow
, 0, sizeof(zxUlaSnow
));
383 emuFrameStartTime
= 0;
384 emuLastFPSText
[0] = '\0';
387 emuAddPortHandlers();
391 if ((zxDiskIf
= difCreate(DIF_BDI
)) == NULL
) { fprintf(stderr
, "FATAL: can't create BetaDisk object!\n"); abort(); }
394 //zym_clear_callbacks(&z80);
395 z80
.mem_read
= z80MemRead
;
396 z80
.mem_write
= z80MemWrite
;
397 z80
.mem_contention
= z80Contention
;
398 z80
.port_read
= z80PortIn
;
399 z80
.port_write
= z80PortOut
;
400 z80
.port_contention
= z80PortContention
;
401 z80
.trap_ed
= z80EDTrap
;
402 z80
.evenM1
= machineInfo
.evenM1
;
406 ////////////////////////////////////////////////////////////////////////////////
407 // defattr < 0: use screen$ attr
408 static void paintZXScr (int x0
, int y0
, const void *ptr
, int defattr
) {
409 const uint8_t *buf
= (const uint8_t *)ptr
;
411 for (int y
= 0; y
< 192; ++y
) {
412 int scrA
= zxScrLineOfs
[y
], attrA
= 6144+(y
/8)*32;
414 for (int x
= 0; x
< 32; ++x
) {
415 uint8_t bt
= buf
[scrA
++];
416 uint8_t attr
= (defattr
>= 0 && defattr
<= 255 ? defattr
: buf
[attrA
++]);
417 uint8_t paper
= (attr
>>3)&0x0f, ink
= (attr
&0x07)+(paper
&0x08);
419 if (attr
&0x80 && zxFlashState
) { uint8_t t
= paper
; paper
= ink
; ink
= t
; }
420 for (int f
= 0; f
< 8; ++f
) {
421 putPixel(x0
+x
*8+f
, y0
+y
, (bt
&0x80 ? ink
: paper
));
429 static void paintZXScrZ (int x0
, int y0
) {
430 static uint8_t scr
[6912];
431 unsigned int outlen
= 6912;
432 static int unpacked
= 0;
435 if (tinf_zlib_uncompress(scr
, &outlen
, keyHelpScr
, 2153) == TINF_OK
&& outlen
== 6912) {
443 paintZXScr(x0
, y0
, scr
, -1);
448 static void emuDrawFPS (void) {
449 if (emuFrameStartTime
> 0 && emuFrameCount
) {
450 int64_t tt
= timerGetMS();
451 if (tt
-emuFrameStartTime
>= 1000) {
452 int fps
= emuFrameCount
/((tt
-emuFrameStartTime
)/1000.0)+0.5;
453 if (fps
< 0) fps
= 0;
454 //sprintf(emuLastFPSText, "%.15f %d %3d%%", ((double)emuFrameCount/((double)(tt-emuFrameStartTime)/1000)), fps, fps*100/50);
455 sprintf(emuLastFPSText
, "%d %3d%%", fps
, fps
*100/50);
456 emuFrameStartTime
= tt
;
460 if (emuLastFPSText
[0]) {
461 drawStr6Outline(emuLastFPSText
, frameSfc
->w
-6*strlen(emuLastFPSText
)-2, 2, 12, 0);
466 ////////////////////////////////////////////////////////////////////////////////
467 static void zxPaintOverlaysAndLeds (void) {
469 if (optKeyHelpVisible
) paintZXScrZ(32-zxScreenOfs
, 24);
472 if (optDrawKeyLeds
) keyledBlit(1, frameSfc
->h
-15);
473 if (optTapePlaying
) emuTapeDrawCounter();
474 if (diskLastActivity
> 0) diskBlit(frameSfc
->w
-26, frameSfc
->h
-26);
477 if (optMaxSpeed
) drawStr6Outline("max", frameSfc
->w
-3*6-2, frameSfc
->h
-10, 12, 0);
481 //drawStr8Outline("paused", frameSfc->w-6*8-2, 2, 12, 0);
482 int w
= drawStrZ("paused", 0, 0, 255, 255);
483 drawStrZ("paused", 320-w
-1, 0, 66, 67);
484 } else if (optDrawFPS
) {
488 // "late timings" mark
489 if (zxLateTimings
) drawStr6Outline("LT", 2, 2, 12, 0);
495 if (debuggerActive
) dbgDraw();
499 vid_textsrc_height
= CON_HEIGHT
;
502 vid_textsrc_height
= 0;
504 if (debuggerActive
&& !dbgIsHidden()) vid_textsrc_height
= 0;
506 vid_textscr_active
= ((debuggerActive
&& !dbgIsHidden()) || conVisible
);
512 static char buf
[1024];
513 Uint8 fg
= ((zxBorder
&0x07) < 6 ? 15 : 0);
514 if (zxRZXFrames
> 0) {
515 int prc
= 100*zxRZXCurFrame
/zxRZXFrames
;
516 int secs
= (int)((zxRZXFrames
-zxRZXCurFrame
)/(50.0*optSpeed
/100.0));
517 if (prc
> 100) prc
= 100;
518 if (secs
< 0) secs
= 0;
519 sprintf(buf
, "RZX:%3d%% (%d:%02d left)", prc
, secs
/60, secs
%60);
523 drawStr6(buf
, 1, 1, fg
, 255);
526 Jim_CollectIfNeeded(jim
);
530 static void zxPaintZXScreen (void) {
531 for (int y
= 0; y
< 240; ++y
) {
532 int lp
= (y
+24)*352+16+zxScreenOfs
;
533 // the real Speccy screen is not 'centered', as aowen says; 10px seems to be the right value
534 if (optNoFlic
> 0 || optNoFlicDetected
) {
535 Uint8
*d
= (((Uint8
*)frameSfc
->pixels
)+(y
*frameSfc
->pitch
));
536 for (int x
= 0; x
< 320; ++x
, ++lp
, d
+= 4) {
537 //putPixelMix(x, y, zxScreen[zxScreenCurrent][lp], zxScreen[zxScreenCurrent^1][lp]);
538 Uint8 c0
= zxScreen
[zxScreenCurrent
][lp
], c1
= zxScreen
[zxScreenCurrent
^1][lp
];
539 d
[vidRIdx
] = (palRGB
[c0
][0]+palRGB
[c1
][0])/2;
540 d
[vidGIdx
] = (palRGB
[c0
][1]+palRGB
[c1
][1])/2;
541 d
[vidBIdx
] = (palRGB
[c0
][2]+palRGB
[c1
][2])/2;
544 Uint32
*dst
= (Uint32
*)(((Uint8
*)frameSfc
->pixels
)+(y
*frameSfc
->pitch
));
545 if (optSlowShade
&& optSpeed
< 100) {
546 int shadeStart
= zxScreenLineShadeStart(z80
.tstates
);
547 if (lp
>= shadeStart
) {
548 // the whole line is shaded
549 for (int x
= 0; x
< 320; ++x
) {
550 putPixelMix(x
, y
, zxScreen
[zxScreenCurrent
][lp
++], 0);
551 //*dst++ = palette[zxScreen[zxScreenCurrent][lp++]];
553 } else if (lp
< shadeStart
&& lp
+319 >= shadeStart
) {
554 // line part is shaded
555 for (int x
= 0; x
< 320; ++x
) {
556 if (lp
< shadeStart
) {
557 *dst
++ = palette
[zxScreen
[zxScreenCurrent
][lp
++]];
559 putPixelMix(x
, y
, zxScreen
[zxScreenCurrent
][lp
++], 0);
564 for (int x
= 0; x
< 320; ++x
) {
565 //putPixel(x, y, zxScreen[zxScreenCurrent][lp++]);
566 *dst
++ = palette
[zxScreen
[zxScreenCurrent
][lp
++]];
570 for (int x
= 0; x
< 320; ++x
) {
571 //putPixel(x, y, zxScreen[zxScreenCurrent][lp++]);
572 *dst
++ = palette
[zxScreen
[zxScreenCurrent
][lp
++]];
580 static void zxPaintScreen (void) {
582 zxPaintOverlaysAndLeds();
586 ////////////////////////////////////////////////////////////////////////////////
587 static int msCursorDrawn
= 0;
588 static int msRealCursorVisible
= 1;
589 static int msLastGrabState
= 0;
592 void emuHideRealMouseCursor (void) {
593 if (msRealCursorVisible
) {
595 msRealCursorVisible
= 0;
600 void emuShowRealMouseCursor (void) {
601 if (!msRealCursorVisible
) {
603 msRealCursorVisible
= 1;
608 void emuRealizeRealMouseCursorState (void) {
610 emuHideRealMouseCursor();
611 if (!msLastGrabState
) {
614 zxMouseLastX
= zxMouseLastY
= 0;
615 zxMouseFracX
= zxMouseFracY
= 0;
617 } else if (SDL_WM_GrabInput(SDL_GRAB_QUERY
) == SDL_GRAB_OFF
) {
619 if (!msCursorDrawn
) emuShowRealMouseCursor(); else emuHideRealMouseCursor();
623 emuHideRealMouseCursor();
624 if (!msLastGrabState
) {
627 zxMouseLastX
= zxMouseLastY
= 0;
628 zxMouseFracX
= zxMouseFracY
= 0;
634 void emuFullScreenChanged (void) {
635 emuRealizeRealMouseCursorState();
639 static void zxFrameCB (void) {
640 if (!debuggerActive
&& !optPaused
) {
643 const int msscroff
= (optMaxSpeedBadScreen
|| optMaxSpeedBadScreenTape
);
644 if (msscroff
) ++optNoScreenReal
;
645 const int64_t stt
= timerGetMS();
647 while (!debuggerActive
&& !optPaused
&& !z80_was_frame_end
) {
650 z80_was_frame_end
= 0;
651 } while (!debuggerActive
&& !optPaused
&& timerGetMS()-stt
< 20);
652 // redraw screen; prevent border flicking
656 zxRealiseScreen(machineInfo
.tsperframe
);
658 zxRealiseScreen(z80
.tstates
);
660 } else if (optSpeed
!= 100) {
662 int tickstodo
= machineInfo
.tsperframe
*optSpeed
/100+1;
663 const int64_t stt
= timerGetMS();
664 if (emuPrevFCB
== 0) {
667 if (stt
-emuPrevFCB
< 20) SDL_Delay(20-(stt
-emuPrevFCB
));
668 emuPrevFCB
= timerGetMS();
670 if (optSpeed
< 100) {
671 // slower than the original
672 //fprintf(stderr, "speed=%d; tstates=%d; todo=%d; end=%d; frame=%d\n", optSpeed, z80.tstates, tickstodo, z80.tstates+tickstodo, machineInfo.tsperframe);
673 while (tickstodo
> 0 && !debuggerActive
&& !optPaused
) {
674 tickstodo
-= z80Step();
676 z80_was_frame_end
= 0;
678 // faster than the original
680 // do incomplete frame
681 while (!z80_was_frame_end
&& !debuggerActive
&& !optPaused
) {
682 tickstodo
-= z80Step();
684 z80_was_frame_end
= 0;
685 // execute full frames, checking for timeout
686 while (tickstodo
>= machineInfo
.tsperframe
&& !debuggerActive
&& !optPaused
) {
688 while (!z80_was_frame_end
&& !debuggerActive
&& !optPaused
) {
689 tickstodo
-= z80Step();
691 z80_was_frame_end
= 0;
692 if (timerGetMS()-stt
>= 20) { nomore
= 1; break; }
696 //while (tickstodo > 0 && !debuggerActive && !optPaused && timerGetMS()-stt <= 20) tickstodo -= z80Step();
697 while (tickstodo
> 0 && !debuggerActive
&& !optPaused
) {
698 tickstodo
-= z80Step();
702 z80_was_frame_end
= 0;
704 // normal, do one frame till end
705 while (!debuggerActive
&& !optPaused
&& !z80_was_frame_end
) {
708 if (z80_was_frame_end
) {
709 z80_was_frame_end
= 0;
710 if (!debuggerActive
&& !optPaused
) {
711 // normal execution; draw frame, play sound
720 zxRealiseScreen(z80
.tstates
);
725 ////////////////////////////////////////////////////////////////////////////////
726 static void zxMouseSetButtons (int buttons
) {
727 if (buttons
&MS_BUTTON_WHEELUP
) zxKMouseWheel
= ((int)zxKMouseWheel
-1)&0x0f;
728 if (buttons
&MS_BUTTON_WHEELDOWN
) zxKMouseWheel
= ((int)zxKMouseWheel
+1)&0x0f;
729 zxKMouseButtons
= buttons
&(MS_BUTTON_LEFT
|MS_BUTTON_RIGHT
|MS_BUTTON_MIDDLE
);
733 static void zxMouseCB (int x
, int y
, int xrel
, int yrel
, int buttons
) {
734 if (/*widgetsProcessMouse(x, y, 0, buttons) ||*/ msCursorDrawn
) return;
735 //fprintf(stderr, "mouse: x=%d; y=%d; xrel=%d; yrel=%d; btn=#%02X\n", x, y, xrel, yrel, buttons);
736 if (!msCursorDrawn
&& vidWindowActivated
&& SDL_WM_GrabInput(SDL_GRAB_QUERY
) == SDL_GRAB_ON
) {
738 zxKMouseDXAccum
+= xrel
;
739 zxKMouseDYAccum
+= yrel
;
740 int speed
= zxKMouseSpeed
;
741 if (speed
< 1) speed
= 1; else if (speed
> 256) speed
= 256;
743 while (zxKMouseDXAccum
>= speed
) { xrel
+= 1; zxKMouseDXAccum
-= speed
; }
744 while (zxKMouseDXAccum
<= -speed
) { xrel
-= 1; zxKMouseDXAccum
+= speed
; }
745 while (zxKMouseDYAccum
>= speed
) { yrel
+= 1; zxKMouseDYAccum
-= speed
; }
746 while (zxKMouseDYAccum
<= -speed
) { yrel
-= 1; zxKMouseDYAccum
+= speed
; }
747 zxKMouseDX
= (((int)zxKMouseDX
+xrel
)&0xff);
748 zxKMouseDY
= (((int)zxKMouseDY
-yrel
)&0xff);
750 //int lp = (y+24)*352+16+zxScreenOfs;
752 if (zxMouseWasMoved
) {
760 int speed
= zxKMouseSpeed
;
761 if (speed
< 1) speed
= 1; else if (speed
> 256) speed
= 256;
765 zxKMouseDX
= (((int)zxKMouseDX
+dx
)&0xff);
769 zxKMouseDY
= (((int)zxKMouseDY
-dy
)&0xff);
773 if (!zxMouseWasMoved
) {
774 zxMouseLastX
= zxMouseLastY
= 0;
775 zxMouseFracX
= zxMouseFracY
= 0;
778 int speed
= zxKMouseSpeed
;
779 if (speed
< 1) speed
= 1; else if (speed
> 256) speed
= 256;
780 zxMouseFracX
+= xrel
;
781 zxMouseFracY
-= yrel
;
784 while (zxMouseFracX
<= -speed
) { --dx
; zxMouseFracX
+= speed
; }
785 while (zxMouseFracX
>= speed
) { ++dx
; zxMouseFracX
-= speed
; }
786 while (zxMouseFracY
<= -speed
) { --dy
; zxMouseFracY
+= speed
; }
787 while (zxMouseFracY
>= speed
) { ++dy
; zxMouseFracY
-= speed
; }
788 //if (abs(dx) >= 6) { if (dx < 0) dx -= (-dx)/2; else dx += dx/2; }
789 //if (abs(dy) >= 6) { if (dy < 0) dy -= (-dy)/2; else dy += dy/2; }
790 if (dx
&& zxMouseFracX
<= 2) zxMouseFracX
= 0;
791 if (dy
&& zxMouseFracY
<= 2) zxMouseFracY
= 0;
792 if (abs(dx
) >= 6) dx
*= 2;
793 if (abs(dy
) >= 6) dy
*= 2;
796 zxKMouseDX
= (((int)zxKMouseDX
+dx
)&0xff);
797 zxKMouseDY
= (((int)zxKMouseDY
+dy
)&0xff);
800 zxMouseSetButtons(buttons
);
807 static void zxMouseButtonCB (int x
, int y
, int btn
, int buttons
) {
808 if (/*widgetsProcessMouse(x, y, btn, buttons) ||*/ msCursorDrawn
) return;
809 if (!msCursorDrawn
&& vidWindowActivated
) {
810 //fprintf(stderr, "buttons=0x%02x\n", buttons);
811 if (optKMouse
&& buttons
== 0 && btn
== (MS_BUTTON_LEFT
|MS_BUTTON_DEPRESSED
) &&
812 SDL_WM_GrabInput(SDL_GRAB_QUERY
) != SDL_GRAB_ON
)
814 SDL_WM_GrabInput(SDL_GRAB_ON
);
815 emuRealizeRealMouseCursorState();
817 return; // ignore this click
819 if (SDL_WM_GrabInput(SDL_GRAB_QUERY
) == SDL_GRAB_ON
) {
820 zxMouseSetButtons(buttons
);
826 ////////////////////////////////////////////////////////////////////////////////
827 static void zxKeyCB (SDL_KeyboardEvent
*key
) {
828 if (conVisible
&& conKeyEvent(key
)) return;
829 //if (msCursorDrawn) return;
830 if (debuggerActive
&& dbgKeyEvent(key
)) return;
831 if (uiovlKey(key
)) return;
832 if (key
->type
== SDL_KEYDOWN
) {
834 if ((bind
= sdlFindKeyBind(sdlJimBindings
, key
->keysym
.sym
, key
->keysym
.mod
)) != NULL
&& bind
->action
!= NULL
) {
835 Jim_Obj
*eres
= NULL
/*, *dupl*/;
836 // duplicate 'action', 'cause Jim can return the same shared object
837 // if there is nothing to 'expand' in it; it's safe to omit
838 // duplication here, but future versions of Jim can check if the
839 // object is 'shared' here, so let's do it right
840 //!dupl = Jim_DuplicateObj(jim, bind->action);
841 //!Jim_IncrRefCount(dupl); // we need to do this after Jim_DuplicateObj()
842 if (Jim_SubstObj(jim
, /*dupl*/bind
->action
, &eres
, 0) == JIM_OK
) {
843 Jim_IncrRefCount(eres
);
844 if (Jim_EvalObjList(jim
, eres
) != JIM_OK
) {
845 Jim_MakeErrorMessage(jim
);
846 cprintf("\4=== JIM ERROR ===\n%s\n=================\n", Jim_GetString(Jim_GetResult(jim
), NULL
));
848 Jim_DecrRefCount(jim
, eres
);
850 //!Jim_DecrRefCount(jim, dupl);
855 if (/*vidWindowActivated &&*/ msCursorDrawn
) return;
857 if (zxKeyBinds
[key
->keysym
.sym
]) {
858 zxDoKeyUpDown(zxKeyBinds
[key
->keysym
.sym
]&0xffff, (key
->type
== SDL_KEYDOWN
));
859 zxDoKeyUpDown((zxKeyBinds
[key
->keysym
.sym
]>>16)&0xffff, (key
->type
== SDL_KEYDOWN
));
865 ////////////////////////////////////////////////////////////////////////////////
866 #define PUSH_BACK(_c) (*ress)[dpos++] = (_c)
867 #define DECODE_TUPLE(tuple,bytes) \
868 for (tmp = bytes; tmp > 0; tmp--, tuple = (tuple & 0x00ffffff)<<8) \
869 PUSH_BACK((char)((tuple >> 24)&0xff))
871 // returns ress length
872 static int ascii85Decode (char **ress
, const char *srcs
/*, int start, int length*/) {
873 static uint32_t pow85
[5] = { 85*85*85*85UL, 85*85*85UL, 85*85UL, 85UL, 1UL };
874 const uint8_t *data
= (const uint8_t *)srcs
;
875 int len
= (int)strlen(srcs
);
877 int count
= 0, c
= 0;
879 int start
= 0, length
= len
;
882 if (start
< 0) start
= 0; else { len
-= start
; data
+= start
; }
883 if (length
< 0 || len
< length
) length
= len
;
886 int xlen = 4*((length+4)/5);
887 kstringReserve(ress, xlen);
891 *ress
= (char *)calloc(1, len
+1);
892 for (int f
= length
; f
> 0; --f
, ++data
) {
894 if (c
<= ' ') continue; // skip blanks
896 case 'z': // zero tuple
898 //fprintf(stderr, "%s: z inside ascii85 5-tuple\n", file);
908 case '~': // '~>': end of sequence
909 if (f
< 1 || data
[1] != '>') { free(*ress
); return -2; } // error
910 if (count
> 0) { f
= -1; break; }
912 if (c
< '!' || c
> 'u') {
913 //fprintf(stderr, "%s: bad character in ascii85 region: %#o\n", file, c);
917 tuple
+= ((uint8_t)(c
-'!'))*pow85
[count
++];
919 DECODE_TUPLE(tuple
, 4);
926 // write last (possibly incomplete) tuple
928 tuple
+= pow85
[count
];
929 DECODE_TUPLE(tuple
, count
);
938 static void decodeBA (char *str
, int len
) {
941 for (int f
= 0; f
< len
; ++f
, ++str
) {
951 static void printEC (const char *txt
) {
955 if ((len
= ascii85Decode(&dest
, txt
)) >= 0) {
957 fprintf(stderr
, "%s\n", dest
);
963 static int isStr85Equ (const char *txt
, const char *str
) {
967 if ((len
= ascii85Decode(&dest
, txt
)) >= 0) {
968 res
= (strcmp(dest
, str
) == 0);
975 static int checkEGG (const char *str
) {
976 if (isStr85Equ("06:]JASq", str
) || isStr85Equ("0/i", str
)) {
978 "H8lZV&6)1>+AZ>m)Cf8;A1/cP+CnS)0OJ`X.QVcHA4^cc5r3=m1c%0D3&c263d?EV6@4&>"
979 "3DYQo;c-FcO+UJ;MOJ$TAYO@/FI]+B?C.L$>%:oPAmh:4Au)>AAU/H;ZakL2I!*!%J;(AK"
980 "NIR#5TXgZ6c'F1%^kml.JW5W8e;ql0V3fQUNfKpng6ppMf&ip-VOX@=jKl;#q\"DJ-_>jG"
981 "8#L;nm]!q;7c+hR6p;tVY#J8P$aTTK%c-OT?)<00,+q*8f&ff9a/+sbU,:`<H*[fk0o]7k"
982 "^l6nRkngc6Tl2Ngs!!P2I%KHG=7n*an'bsgn>!*8s7TLTC+^\\\"W+<=9^%Ol$1A1eR*Be"
983 "gqjEag:M0OnrC4FBY5@QZ&'HYYZ#EHs8t4$5]!22QoJ3`;-&=\\DteO$d6FBqT0E@:iu?N"
984 "a5ePUf^_uEEcjTDKfMpX/9]DFL8N-Ee;*8C5'WgbGortZuh1\\N0;/rJB6'(MSmYiS\"6+"
985 "<NK)KDV3e+Ad[@).W:%.dd'0h=!QUhghQaNNotIZGrpHr-YfEuUpsKW<^@qlZcdTDA!=?W"
986 "Yd+-^`'G8Or)<0-T&CT.i+:mJp(+/M/nLaVb#5$p2jR2<rl7\"XlngcN`mf,[4oK5JLr\\"
987 "m=X'(ue;'*1ik&/@T4*=j5t=<&/e/Q+2=((h`>>uN(#>&#i>2/ajK+=eib1coVe3'D)*75"
988 "m_h;28^M6p6*D854Jj<C^,Q8Wd\"O<)&L/=C$lUAQNN<=eTD:A6kn-=EItXSss.tAS&!;F"
989 "EsgpJTHIYNNnh'`kmX^[`*ELOHGcWbfPOT`J]A8P`=)AS;rYlR$\"-.RG440lK5:Dg?G'2"
990 "['dE=nEm1:k,,Se_=%-6Z*L^J[)EC"
994 if (isStr85Equ("04Jj?B)", str
)) {
996 "IPaSa(`c:T,o9Bq3\\)IY++?+!-S9%P0/OkjE&f$l.OmK'Ai2;ZHn[<,6od7^8;)po:HaP"
997 "m<'+&DRS:/1L7)IA7?WI$8WKTUB2tXg>Zb$.?\"@AIAu;)6B;2_PB5M?oBPDC.F)606Z$V"
998 "=ONd6/5P*LoWKTLQ,d@&;+Ru,\\ESY*rg!l1XrhpJ:\"WKWdOg?l;=RHE:uU9C?aotBqj]"
999 "=k8cZ`rp\"ZO=GjkfD#o]Z\\=6^]+Gf&-UFthT*hN"
1003 if (isStr85Equ("04o69A7Tr", str
)) {
1005 "Ag7d[&R#Ma9GVV5,S(D;De<T_+W).?,%4n+3cK=%4+0VN@6d\")E].np7l?8gF#cWF7SS_m"
1006 "4@V\\nQ;h!WPD2h#@\\RY&G\\LKL=eTP<V-]U)BN^b.DffHkTPnFcCN4B;]8FCqI!p1@H*_"
1007 "jHJ<%g']RG*MLqCrbP*XbNL=4D1R[;I(c*<FuesbWmSCF1jTW+rplg;9[S[7eDVl6YsjT"
1015 static void addBoots (int simpleAutorun
) {
1016 for (int f
= 0; f
< 4; ++f
) {
1017 if ((snapWasDisk
&(1<<f
)) && !(snapWasCPCDisk
&(1<<f
))) addAutoBoot(f
, 1, simpleAutorun
);
1042 static cli_arun_e cli_autorun
= ARUN_UNDEFINED
;
1044 static void show_help (void) {
1047 " --48 use 48k model\n"
1048 " --128 use 128k model\n"
1049 " -A --no-autorun don't autorun file\n"
1055 static char *strappend (char *s
, const char *s1
) {
1056 if (!s
) s
= strdup("");
1057 if (!s1
|| !s1
[0]) return s
;
1058 char *res
= strprintf("%s%s", s
, s1
);
1074 static void processOptions (int argc
, char *argv
[], int onlydbg
) {
1075 int nomoreopts
= 0, oldABoot
= optAutoaddBoot
;
1076 int do48
= CLI_MODEL_AUTO
;
1081 for (int f
= 1; f
< argc
; ++f
) {
1082 if (checkEGG(argv
[f
])) exit(1);
1084 if (strcmp(argv
[f
], "--") == 0) { nomoreopts
= 1; continue; }
1085 if (strcmp(argv
[f
], "+") == 0) continue; // console command separator
1086 if (argv
[f
][0] == '+') {
1087 if (onlydbg
) continue;
1088 optAutoaddBoot
= oldABoot
;
1089 if (strchr(argv
[f
], ' ') != NULL
) {
1090 conExecute(argv
[f
]+1, 0);
1092 // collect console command
1093 char *cmd
= strdup(argv
[f
]+1);
1094 for (++f
; f
< argc
; ++f
) {
1095 if (argv
[f
][0] == '+') break;
1096 if (argv
[f
][0] == '-' && argv
[f
][1] == '-') break;
1097 cmd
= strappend(cmd
, " ");
1098 cmd
= strappend(cmd
, argv
[f
]);
1100 --f
; // compensate 'for'
1104 if (oldABoot
!= optAutoaddBoot
) {
1105 if (oldABoot
) addBoots(0);
1106 oldABoot
= optAutoaddBoot
;
1113 if (argv
[f
][0] == '-') {
1114 if (argv
[f
][1] == '-') {
1115 if (strcmp(argv
[f
], "--help") == 0) show_help();
1116 else if (strcmp(argv
[f
], "--48") == 0) do48
= CLI_MODEL_48
;
1117 else if (strcmp(argv
[f
], "--128") == 0) do48
= CLI_MODEL_128
;
1118 else if (strcmp(argv
[f
], "--pent") == 0 || strcmp(argv
[f
], "--pentagon") == 0) do48
= CLI_MODEL_PENT
;
1119 else if (strcmp(argv
[f
], "--plus2a") == 0) do48
= CLI_MODEL_PLUS2A
;
1120 else if (strcmp(argv
[f
], "--plus3") == 0) do48
= CLI_MODEL_PLUS3
;
1121 else if (strcmp(argv
[f
], "--no-autorun") == 0) cli_autorun
= ARUN_NONE
;
1122 else if (strcmp(argv
[f
], "--opense") == 0) {
1124 if (!optOpenSE
) { optOpenSE
= 1; emuSetModel(zxModel
, 1); }
1127 else if (strcmp(argv
[f
], "--usock") == 0 || strcmp(argv
[f
], "--usocket") == 0) {
1129 if (f
>= argc
) { fprintf(stderr
, "option '%s' expects socket name!\n", argv
[f
-1]); exit(1); }
1130 if (!onlydbg
) unixsock_start_server(argv
[f
]);
1132 else if (strcmp(argv
[f
], "--unsafe-tcl") == 0) { if (onlydbg
) Jim_SetAllowUnsafeExtensions(1); }
1133 else if (strcmp(argv
[f
], "--no-unsafe-tcl") == 0) { if (onlydbg
) Jim_SetAllowUnsafeExtensions(0); }
1134 else { fprintf(stderr
, "unknown command line option: '%s'\n", argv
[f
]); exit(1); }
1137 for (const char *a
= argv
[f
]+1; *a
; ++a
) {
1139 case 'h': show_help(); break;
1140 case 'A': cli_autorun
= ARUN_NONE
; break;
1143 fprintf(stderr
, "console dump enabled\n");
1147 case 'q': /* do not dump consote text to stdout */
1148 optConDumpToStdout
= 0;
1152 fprintf(stderr
, "sound debug enabled\n");
1153 optSndSyncDebug
= 1;
1156 default: fprintf(stderr
, "unknown command line option: '%c'\n", *a
); exit(1);
1162 if (onlydbg
) continue;
1164 if (loadSnapshot(argv
[f
], SNAPLOAD_ANY
) == 0) {
1165 cprintf("'%s' loaded\n", argv
[f
]);
1166 if (cli_autorun
!= ARUN_NONE
) {
1167 if (snapWasDisk
) cli_autorun
= (snapWasCPCDisk
? ARUN_P3DOS2A
: ARUN_TRDOS
);
1168 else if (snapWasTape
) cli_autorun
= ARUN_TAP
;
1169 else cli_autorun
= ARUN_UNDEFINED
;
1171 //cprintf("wasdisk=%d; wascpc=%d; ar=%d\n", snapWasDisk, snapWasCPCDisk, cli_autorun);
1173 cprintf("failed to load '%s'\n", argv
[f
]);
1178 optAutoaddBoot
= oldABoot
;
1180 if (oldABoot
&& !snapWasCPCDisk
) addBoots(1);
1181 if (cli_autorun
> ARUN_NONE
) {
1182 if (cli_autorun
!= ARUN_P3DOS2A
&& cli_autorun
!= ARUN_P3DOS3
) {
1184 case CLI_MODEL_AUTO
:
1185 switch (cli_autorun
) {
1186 case ARUN_TAP
: cli_autorun
= ARUN_TAP48
; break;
1187 case ARUN_TRDOS
: cli_autorun
= ARUN_TRDOSPENT
; break;
1192 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOS48
: ARUN_TAP48
);
1195 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOS128
: ARUN_TAP128
);
1197 case CLI_MODEL_PENT
:
1198 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOSPENT
: ARUN_TAPPENT
);
1200 case CLI_MODEL_PLUS2A
:
1201 cli_autorun
= (cli_autorun
== ARUN_P3DOS3
? ARUN_P3DOS2A
: ARUN_TAPP2A
);
1203 case CLI_MODEL_PLUS3
:
1204 cli_autorun
= (cli_autorun
== ARUN_P3DOS3
? ARUN_P3DOS3
: ARUN_TAPP3
);
1207 cprintf("\1UNKNOWN CLI MODEL!\n");
1208 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOS48
: ARUN_TAP48
);
1212 switch (cli_autorun
) {
1213 case ARUN_TRDOS
: conExecute("reset trdos", 0); break;
1214 case ARUN_TRDOS48
: conExecute("reset 48k trdos", 0); break;
1215 case ARUN_TRDOS128
: conExecute("reset 128k trdos", 0); break;
1216 case ARUN_TRDOSPENT
: conExecute("reset pentagon 512 trdos", 0); break;
1217 case ARUN_TAP48
: conExecute("reset 48k", 0); goto do_tape_autoload
;
1218 case ARUN_TAP128
: conExecute("reset 128k", 0); goto do_tape_autoload
;
1219 case ARUN_TAPPENT
: conExecute("reset pentagon 512", 0); goto do_tape_autoload
;
1220 case ARUN_TAPP2A
: conExecute("reset plus2a", 0); goto do_tape_autoload
;
1221 case ARUN_TAPP3
: conExecute("reset plus3", 0); goto do_tape_autoload
;
1222 case ARUN_P3DOS2A
: conExecute("reset plus2a", 0); break;
1223 case ARUN_P3DOS3
: conExecute("reset plus3", 0); break;
1226 conExecute("tape _autoload", 0);
1235 static void xMainLoop (void) {
1236 const int mcsInFrame
= 20*1000;
1237 static int64_t mcsFrameEndWanted
;
1239 mcsFrameEndWanted
= timerGetMicroSeconds()+mcsInFrame
;
1241 int64_t mcsCurFrameEnd
;
1242 eres
= processEvents(0);
1245 mcsCurFrameEnd
= timerGetMicroSeconds();
1246 if (mcsCurFrameEnd
> 0) {
1247 int mcsSleep
= (mcsFrameEndWanted
-mcsCurFrameEnd
);
1248 //fprintf(stderr, "0: wait=%.15g\n", ((double)mcsSleep)/1000.0);
1251 //fprintf(stderr, "SLEEP: %.15g\n", ((double)mcsSleep)/1000.0);
1253 //mcsCurFrameEnd = timerGetMicroSeconds();
1254 //fprintf(stderr, "1:few=%d; cfe=%d; few-cfe=%.15g\n", (int)mcsFrameEndWanted, (int)mcsCurFrameEnd, ((double)(mcsInFrame-(mcsFrameEndWanted-mcsCurFrameEnd)))/1000);
1255 mcsFrameEndWanted
+= mcsInFrame
;
1257 fprintf(stderr
, "DESYNC! (%d)\n", mcsSleep
);
1258 //mcsFrameEndWanted = timerGetMicroSeconds()+mcsInFrame;
1259 mcsFrameEndWanted
= mcsCurFrameEnd
+mcsInFrame
;
1265 mcsFrameEndWanted
= timerGetMicroSeconds()+mcsInFrame
;
1271 ////////////////////////////////////////////////////////////////////////////////
1272 static void cprintLibFDC (int type
, const char *msg
) {
1274 case LIBFDC_MSG_DEBUG
: cprintf("\3LIBFDC[debug]: %s\n", msg
); break;
1275 case LIBFDC_MSG_WARNING
: cprintf("\2LIBFDC[warn]: %s\n", msg
); break;
1276 case LIBFDC_MSG_ERROR
: cprintf("\4LIBFDC[error]: %s\n", msg
); break;
1277 default: cprintf("\3LIBFDC[???]: %s\n", msg
); break; // the thing that should not be
1282 ////////////////////////////////////////////////////////////////////////////////
1283 int main (int argc
, char *argv
[]) {
1284 Jim_SetAllowUnsafeExtensions(1);
1289 libfdcMessageCB
= &cprintLibFDC
;
1291 processOptions(argc
, argv
, 1);
1293 if (libspectrum_init() != LIBSPECTRUM_ERROR_NONE
) {
1294 fprintf(stderr
, "FATAL: can't init libspectrum!\n");
1297 cprintf("===================================\n");
1298 cprintf("using libspectrum v%s\n", libspectrum_version());
1300 switch (timerInit()) {
1301 case TIMER_ERROR
: abort();
1302 case TIMER_HPET
: break;
1305 "\2WARNING: please, set your clock source to HPET!\n"
1306 "you can do this by issuing the following command:\n"
1308 "\1sudo echo 'hpet' > /sys/devices/system/clocksource/clocksource0/current_clocksource\n"
1310 "\2this is not a critical issue, but hpet clock will\n"
1311 "\2give you slightly better emulation.\n"
1316 if (!Jim_GetAllowUnsafeExtensions()) {
1317 cprintf("\2WARNING: Disabled unsafe Tcl extensions.\n");
1319 cprintf("Unsafe Tcl extensions enabled ('--no-unsafe-tcl' to disable).\n");
1326 jimEvalFile("init/init.tcl", 0);
1328 jimEvalFile("init/roms.tcl", 0);
1329 emuSetModel(zxModel
, 1);
1334 jimEvalFile("init/concmd.tcl", 0);
1339 frameCB
= zxFrameCB
;
1341 mouseCB
= zxMouseCB
;
1342 mouseButtonCB
= zxMouseButtonCB
;
1344 //jimEvalFile("init/widgets/init.tcl", 1);
1346 jimEvalFile("autoexec.tcl", 1);
1347 emuSetModel(zxModel
, 1); // in case something vital was changed (like "opense on")
1349 processOptions(argc
, argv
, 0);
1351 jimEvalFile1("./.zxemutrc.tcl");
1352 jimEvalFile1("./.zxemut.tcl");
1353 jimEvalFile1("./zxemutrc.tcl");
1354 jimEvalFile1("./zxemut.tcl");
1357 if (sndSampleRate
< 0 && initSound() != 0) {
1358 fprintf(stderr
, "WARNING: can't initialize sound!\n");
1359 cprintf("\4WARNING: can't initialize sound!\n");
1362 sndAllowUseToggle
= 0;
1363 if (sndSampleRate
<= 0) cprintf("NOSOUND mode");
1365 frameCB
= zxFrameCB
;
1367 mouseCB
= zxMouseCB
;
1368 mouseButtonCB
= zxMouseButtonCB
;
1372 if (sndSampleRate
> 0) {
1373 while (processEvents(0) >= 0) {
1376 // optMaxSpeed implies sndUsing==0
1377 if (!optMaxSpeed
&& optSpeed
== 100 && (debuggerActive
|| optPaused
)) soundWrite();
1383 sndAllowUseToggle
= 0;
1387 unixsock_stop_server();
1390 difDestroy(zxDiskIf
);
1393 if (condumpfl
!= NULL
) fclose(condumpfl
);