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 **************************************************************************/
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"
54 ////////////////////////////////////////////////////////////////////////////////
55 #include "emucommon.h"
56 #include "../libfusefdc/libfusefdc.h"
60 #include "libvideo/video.h"
65 #include "zxscrdraw.h"
70 #include "zxkeyinfo.h"
78 ////////////////////////////////////////////////////////////////////////////////
79 extern const unsigned char keyHelpScr
[];
82 ////////////////////////////////////////////////////////////////////////////////
86 static void initMyDir (void) {
87 if (readlink("/proc/self/exe", binMyDir
, sizeof(binMyDir
)-1) < 0) {
88 strcpy(binMyDir
, ".");
90 char *p
= (char *)strrchr(binMyDir
, '/');
92 if (p
== NULL
) strcpy(binMyDir
, "."); else *p
= '\0';
97 ////////////////////////////////////////////////////////////////////////////////
98 const char *snapshotExtensions
[] = {
125 ////////////////////////////////////////////////////////////////////////////////
126 typedef struct PortHandlers
{
127 int machine
; // ZX_MACHINE_MAX: any
130 int (*portInCB
) (zym_cpu_t
*z80
, uint16_t port
, uint8_t *res
);
131 int (*portOutCB
) (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
);
135 #define MAX_PORTHANDLER (64)
136 static PortHandlers portHandlers
[MAX_PORTHANDLER
];
137 static int phCount
= 0;
140 static void phAdd (const PortHandlers
*ph
) {
142 while (ph
->portInCB
|| ph
->portOutCB
) {
143 if (phCount
>= MAX_PORTHANDLER
) { fprintf(stderr
, "FATAL: too many port handlers!\n"); abort(); }
144 portHandlers
[phCount
++] = *ph
++;
150 ////////////////////////////////////////////////////////////////////////////////
154 ////////////////////////////////////////////////////////////////////////////////
155 static int usock_stdin
= 0;
156 static int usock_srv
= -1;
157 static int usock_client
= -1;
158 static char usock_command
[1024];
159 static size_t usock_command_pos
= 0;
162 static size_t unixsock_create_address (struct sockaddr_un
*addr
, const char *name
) {
163 if (!addr
|| !name
|| !name
[0] || strlen(name
) > 100) return 0;
164 memset((void *)addr
, 0, sizeof(*addr
));
165 addr
->sun_family
= AF_UNIX
;
166 addr
->sun_path
[0] = 0; /* abstract unix socket */
167 strcpy(addr
->sun_path
+1, name
);
168 return strlen(addr
->sun_path
+1)+sizeof(addr
->sun_family
)+1u;
172 static void sock_make_non_blocking (int fd
) {
174 int flags
= fcntl(fd
, F_GETFL
, 0);
176 fcntl(fd
, F_SETFL
, flags
);
181 static void unixsock_drop_client (void) {
182 if (usock_client
>= 0) {
186 usock_command_pos
= 0;
190 static void unixsock_stop_server (void) {
191 unixsock_drop_client();
192 if (usock_srv
>= 0) {
199 static void unixsock_start_server (const char *name
) {
200 unixsock_stop_server();
203 if (name
&& strcmp(name
, "-") == 0) {
205 cprintf("\4ERROR: stdin already closed!\n");
212 sock_make_non_blocking(usock_srv
);
216 usock_srv
= socket(AF_UNIX
, SOCK_STREAM
, 0);
218 cprintf("\4ERROR: cannot create server socket!\n");
222 sock_make_non_blocking(usock_srv
);
224 struct sockaddr_un serv_addr
;
225 size_t servlen
= unixsock_create_address(&serv_addr
, name
);
226 if (bind(usock_srv
, (struct sockaddr
*)&serv_addr
, servlen
) < 0) {
227 cprintf("\4ERROR: cannot bind server socket!\n");
233 if (listen(usock_srv
, 1) < 0) {
234 cprintf("\4ERROR: cannot listen on server socket!\n");
240 cprintf("\1created unix server socket '%s'\n", name
);
244 static void unixsock_handle (void) {
245 if (usock_srv
< 0 && usock_client
< 0) return;
250 /* can accept new client? */
251 if (usock_client
< 0) {
252 /* yeah, check for new client */
254 FD_SET(usock_srv
, &rds
);
255 int res
= select(usock_srv
+1, &rds
, NULL
, NULL
, &tout
);
256 if (res
<= 0) return; /* nothing */
257 /* accept new client */
258 struct sockaddr_un cli_addr
;
259 socklen_t clilen
= (socklen_t
)sizeof(cli_addr
);
260 int newsockfd
= accept(usock_srv
, (struct sockaddr
*)&cli_addr
, &clilen
);
262 cprintf("\4ERROR: error accepting client connection!\n");
265 usock_client
= newsockfd
;
266 cprintf("\1USOCK: accepted new client\n");
268 /* check if we can read from client socket */
269 for (int maxread
= 4096; maxread
> 0; --maxread
) {
272 FD_SET(usock_client
, &rds
);
273 int res
= select(usock_client
+1, &rds
, NULL
, NULL
, &tout
);
274 if (res
<= 0) break; /* nothing */
275 ssize_t rd
= read(usock_client
, &ch
, 1);
277 cprintf("\2USOCK: client closed the connection\n");
278 unixsock_drop_client();
282 cprintf("\4ERROR: error reading from client connection!\n");
283 unixsock_drop_client();
286 if (usock_command_pos
>= sizeof(usock_command
)-2) {
287 cprintf("\4ERROR: too long command from client!\n");
288 unixsock_drop_client();
291 if (ch
== '\n') ch
= 0;
292 usock_command
[usock_command_pos
++] = ch
;
294 /* command complete */
295 usock_command_pos
= 0;
296 char *cmd
= strprintf("::usock_received %s", usock_command
);
297 if (Jim_Eval(jim
, cmd
) == JIM_OK
) {
299 Jim_Obj
*res
= Jim_GetResult(jim
);
300 const char *resstr
= Jim_String(res
);
301 if (resstr
&& strcasecmp(resstr
, "close") == 0) {
302 cprintf("closing client connection (client request).\n");
303 unixsock_drop_client();
306 if (resstr
&& strcasecmp(resstr
, "done") == 0) {
311 Jim_MakeErrorMessage(jim
);
312 cprintf("\4=== JIM ERROR ===\n%s\n=================\n", Jim_GetString(Jim_GetResult(jim
), NULL
));
314 /* close client connection? */
316 if (strcmp(usock_command, "close") == 0) {
317 cprintf("closing client connection (client request).\n");
318 unixsock_drop_client();
322 cprintf("\2USOCK COMMAND:<%s>\n", usock_command
);
323 conExecute(usock_command
, 0);
330 ////////////////////////////////////////////////////////////////////////////////
331 static inline void zxDoKeyUpDown (uint16_t portmask
, int isdown
) {
334 zxKeyboardState
[(portmask
>>8)&0xff] &= ~(portmask
&0xff);
336 zxKeyboardState
[(portmask
>>8)&0xff] |= (portmask
&0xff);
342 ////////////////////////////////////////////////////////////////////////////////
343 static void emuInitMemory (void) {
344 zxMaxMemoryBank
= 64;
345 for (int f
= 0; f
< zxMaxMemoryBank
; ++f
) {
346 if ((zxMemoryBanks
[f
] = calloc(1, 16384)) == NULL
) {
347 fprintf(stderr
, "FATAL: out of memory!\n");
352 zxMaxROMMemoryBank
= 16;
353 for (int f
= 0; f
< zxMaxROMMemoryBank
; ++f
) {
354 if ((zxROMBanks
[f
] = calloc(1, 16384)) == NULL
) {
355 fprintf(stderr
, "FATAL: out of memory!\n");
362 static void emuInit (void) {
365 for (int t
= 0; t
< 3; ++t
) {
366 for (int y
= 0; y
< 8; ++y
) {
367 for (int z
= 0; z
< 8; ++z
) {
368 zxScrLineOfs
[lineNo
++] = (t
<<11)|(z
<<8)|(y
<<5);
373 memset(zxKeyBinds
, 0, sizeof(zxKeyBinds
));
374 memset(zxScreen
, 0, sizeof(zxScreen
));
375 memset(zxUlaSnow
, 0, sizeof(zxUlaSnow
));
385 emuFrameStartTime
= 0;
386 emuLastFPSText
[0] = '\0';
389 emuAddPortHandlers();
396 //zym_clear_callbacks(&z80);
397 z80
.mem_read
= z80MemRead
;
398 z80
.mem_write
= z80MemWrite
;
399 z80
.mem_contention
= z80Contention
;
400 z80
.port_read
= z80PortIn
;
401 z80
.port_write
= z80PortOut
;
402 z80
.port_contention
= z80PortContention
;
403 z80
.trap_ed
= z80EDTrap
;
404 z80
.evenM1
= machineInfo
.evenM1
;
408 ////////////////////////////////////////////////////////////////////////////////
409 // defattr < 0: use screen$ attr
410 static void paintZXScr (int x0
, int y0
, const void *ptr
, int defattr
) {
411 const uint8_t *buf
= (const uint8_t *)ptr
;
413 for (int y
= 0; y
< 192; ++y
) {
414 int scrA
= zxScrLineOfs
[y
], attrA
= 6144+(y
/8)*32;
416 for (int x
= 0; x
< 32; ++x
) {
417 uint8_t bt
= buf
[scrA
++];
418 uint8_t attr
= (defattr
>= 0 && defattr
<= 255 ? defattr
: buf
[attrA
++]);
419 uint8_t paper
= (attr
>>3)&0x0f, ink
= (attr
&0x07)+(paper
&0x08);
421 if (attr
&0x80 && zxFlashState
) { uint8_t t
= paper
; paper
= ink
; ink
= t
; }
422 for (int f
= 0; f
< 8; ++f
) {
423 putPixel(x0
+x
*8+f
, y0
+y
, (bt
&0x80 ? ink
: paper
));
431 static void paintZXScrZ (int x0
, int y0
) {
432 static uint8_t scr
[6912];
433 unsigned int outlen
= 6912;
434 static int unpacked
= 0;
437 if (tinf_zlib_uncompress(scr
, &outlen
, keyHelpScr
, 2153) == TINF_OK
&& outlen
== 6912) {
445 paintZXScr(x0
, y0
, scr
, -1);
450 static void emuDrawFPS (void) {
451 if (emuFrameStartTime
> 0 && emuFrameCount
) {
452 int64_t tt
= timerGetMS();
453 if (tt
-emuFrameStartTime
>= 1000) {
454 int fps
= emuFrameCount
/((tt
-emuFrameStartTime
)/1000.0)+0.5;
455 if (fps
< 0) fps
= 0;
456 //sprintf(emuLastFPSText, "%.15f %d %3d%%", ((double)emuFrameCount/((double)(tt-emuFrameStartTime)/1000)), fps, fps*100/50);
457 sprintf(emuLastFPSText
, "%d %3d%%", fps
, fps
*100/50);
458 emuFrameStartTime
= tt
;
462 if (emuLastFPSText
[0]) {
463 drawStr6Outline(emuLastFPSText
, frameSfc
->w
-6*strlen(emuLastFPSText
)-2, 2, 12, 0);
468 ////////////////////////////////////////////////////////////////////////////////
469 static void zxPaintOverlaysAndLeds (void) {
471 if (optKeyHelpVisible
) paintZXScrZ(32-zxScreenOfs
, 24);
474 if (optDrawKeyLeds
) keyledBlit(1, frameSfc
->h
-15);
475 if (optTapePlaying
) emuTapeDrawCounter();
476 diskBlit(frameSfc
->w
-26, frameSfc
->h
-26);
479 if (optMaxSpeed
) drawStr6Outline("max", frameSfc
->w
-3*6-2, frameSfc
->h
-10, 12, 0);
483 //drawStr8Outline("paused", frameSfc->w-6*8-2, 2, 12, 0);
484 int w
= drawStrZ("paused", 0, 0, 255, 255);
485 drawStrZ("paused", 320-w
-1, 0, 66, 67);
486 } else if (optDrawFPS
) {
490 // "late timings" mark
491 if (zxLateTimings
) drawStr6Outline("LT", 2, 2, 12, 0);
497 if (debuggerActive
) dbgDraw();
499 // memory view window
500 if (memviewActive
) memviewDraw();
502 // sprite view window
503 if (sprviewActive
) sprviewDraw();
505 const int otherTextActive
= (debuggerActive
|| memviewActive
|| sprviewActive
);
509 if (!otherTextActive
) {
510 vid_textsrc_height
= CON_HEIGHT
;
515 vid_textsrc_height
= 0;
517 if (debuggerActive
&& !dbgIsHidden()) vid_textsrc_height
= 0;
519 vid_textscr_active
= ((debuggerActive
&& !dbgIsHidden()) || conVisible
|| memviewActive
|| sprviewActive
);
525 static char buf
[1024];
526 Uint8 fg
= ((zxBorder
&0x07) < 6 ? 15 : 0);
527 if (zxRZXFrames
> 0) {
528 int prc
= 100*zxRZXCurFrame
/zxRZXFrames
;
529 int secs
= (int)((zxRZXFrames
-zxRZXCurFrame
)/(50.0*optSpeed
/100.0));
530 if (prc
> 100) prc
= 100;
531 if (secs
< 0) secs
= 0;
532 sprintf(buf
, "RZX:%3d%% (%d:%02d left)", prc
, secs
/60, secs
%60);
536 drawStr6(buf
, 1, 1, fg
, 255);
539 Jim_CollectIfNeeded(jim
);
543 static void zxPaintZXScreen (void) {
544 for (int y
= 0; y
< 240; ++y
) {
545 int lp
= (y
+24)*352+16+zxScreenOfs
;
546 // the real Speccy screen is not 'centered', as aowen says; 10px seems to be the right value
547 if (optNoFlic
> 0 || optNoFlicDetected
) {
548 Uint8
*d
= (((Uint8
*)frameSfc
->pixels
)+(y
*frameSfc
->pitch
));
549 for (int x
= 0; x
< 320; ++x
, ++lp
, d
+= 4) {
550 //putPixelMix(x, y, zxScreen[zxScreenCurrent][lp], zxScreen[zxScreenCurrent^1][lp]);
551 Uint8 c0
= zxScreen
[zxScreenCurrent
][lp
], c1
= zxScreen
[zxScreenCurrent
^1][lp
];
552 d
[vidRIdx
] = (palRGB
[c0
][0]+palRGB
[c1
][0])/2;
553 d
[vidGIdx
] = (palRGB
[c0
][1]+palRGB
[c1
][1])/2;
554 d
[vidBIdx
] = (palRGB
[c0
][2]+palRGB
[c1
][2])/2;
557 Uint32
*dst
= (Uint32
*)(((Uint8
*)frameSfc
->pixels
)+(y
*frameSfc
->pitch
));
558 if (optSlowShade
&& optSpeed
< 100) {
559 int shadeStart
= zxScreenLineShadeStart(z80
.tstates
);
560 if (lp
>= shadeStart
) {
561 // the whole line is shaded
562 for (int x
= 0; x
< 320; ++x
) {
563 putPixelMix(x
, y
, zxScreen
[zxScreenCurrent
][lp
++], 0);
564 //*dst++ = palette[zxScreen[zxScreenCurrent][lp++]];
566 } else if (lp
< shadeStart
&& lp
+319 >= shadeStart
) {
567 // line part is shaded
568 for (int x
= 0; x
< 320; ++x
) {
569 if (lp
< shadeStart
) {
570 *dst
++ = palette
[zxScreen
[zxScreenCurrent
][lp
++]];
572 putPixelMix(x
, y
, zxScreen
[zxScreenCurrent
][lp
++], 0);
577 for (int x
= 0; x
< 320; ++x
) {
578 //putPixel(x, y, zxScreen[zxScreenCurrent][lp++]);
579 *dst
++ = palette
[zxScreen
[zxScreenCurrent
][lp
++]];
583 for (int x
= 0; x
< 320; ++x
) {
584 //putPixel(x, y, zxScreen[zxScreenCurrent][lp++]);
585 *dst
++ = palette
[zxScreen
[zxScreenCurrent
][lp
++]];
593 static void zxPaintScreen (void) {
595 zxPaintOverlaysAndLeds();
599 ////////////////////////////////////////////////////////////////////////////////
600 static int msCursorDrawn
= 0;
601 static int msRealCursorVisible
= 1;
602 static int msLastGrabState
= 0;
605 void emuHideRealMouseCursor (void) {
606 if (msRealCursorVisible
) {
608 msRealCursorVisible
= 0;
613 void emuShowRealMouseCursor (void) {
614 if (!msRealCursorVisible
) {
616 msRealCursorVisible
= 1;
621 void emuRealizeRealMouseCursorState (void) {
623 emuHideRealMouseCursor();
624 if (!msLastGrabState
) {
627 zxMouseLastX
= zxMouseLastY
= 0;
628 zxMouseFracX
= zxMouseFracY
= 0;
630 } else if (SDL_WM_GrabInput(SDL_GRAB_QUERY
) == SDL_GRAB_OFF
) {
632 if (!msCursorDrawn
) emuShowRealMouseCursor(); else emuHideRealMouseCursor();
636 emuHideRealMouseCursor();
637 if (!msLastGrabState
) {
640 zxMouseLastX
= zxMouseLastY
= 0;
641 zxMouseFracX
= zxMouseFracY
= 0;
647 void emuFullScreenChanged (void) {
648 emuRealizeRealMouseCursorState();
652 static void zxFrameCB (void) {
653 if (!debuggerActive
&& !optPaused
) {
656 const int msscroff
= (optMaxSpeedBadScreen
|| optMaxSpeedBadScreenTape
);
657 if (msscroff
) ++optNoScreenReal
;
658 const int64_t stt
= timerGetMS();
660 while (!debuggerActive
&& !optPaused
&& !z80_was_frame_end
) {
663 z80_was_frame_end
= 0;
664 } while (!debuggerActive
&& !optPaused
&& timerGetMS()-stt
< 20);
665 // redraw screen; prevent border flicking
669 zxRealiseScreen(machineInfo
.tsperframe
);
671 zxRealiseScreen(z80
.tstates
);
673 } else if (optSpeed
!= 100) {
675 int tickstodo
= machineInfo
.tsperframe
*optSpeed
/100+1;
676 const int64_t stt
= timerGetMS();
677 if (emuPrevFCB
== 0) {
680 if (stt
-emuPrevFCB
< 20) SDL_Delay(20-(stt
-emuPrevFCB
));
681 emuPrevFCB
= timerGetMS();
683 if (optSpeed
< 100) {
684 // slower than the original
685 //fprintf(stderr, "speed=%d; tstates=%d; todo=%d; end=%d; frame=%d\n", optSpeed, z80.tstates, tickstodo, z80.tstates+tickstodo, machineInfo.tsperframe);
686 while (tickstodo
> 0 && !debuggerActive
&& !optPaused
) {
687 tickstodo
-= z80Step();
689 z80_was_frame_end
= 0;
691 // faster than the original
693 // do incomplete frame
694 while (!z80_was_frame_end
&& !debuggerActive
&& !optPaused
) {
695 tickstodo
-= z80Step();
697 z80_was_frame_end
= 0;
698 // execute full frames, checking for timeout
699 while (tickstodo
>= machineInfo
.tsperframe
&& !debuggerActive
&& !optPaused
) {
701 while (!z80_was_frame_end
&& !debuggerActive
&& !optPaused
) {
702 tickstodo
-= z80Step();
704 z80_was_frame_end
= 0;
705 if (timerGetMS()-stt
>= 20) { nomore
= 1; break; }
709 //while (tickstodo > 0 && !debuggerActive && !optPaused && timerGetMS()-stt <= 20) tickstodo -= z80Step();
710 while (tickstodo
> 0 && !debuggerActive
&& !optPaused
) {
711 tickstodo
-= z80Step();
715 z80_was_frame_end
= 0;
717 // normal, do one frame till end
718 while (!debuggerActive
&& !optPaused
&& !z80_was_frame_end
) {
721 if (z80_was_frame_end
) {
722 z80_was_frame_end
= 0;
723 if (!debuggerActive
&& !optPaused
) {
724 // normal execution; draw frame, play sound
733 zxRealiseScreen(z80
.tstates
);
738 ////////////////////////////////////////////////////////////////////////////////
739 static void zxMouseSetButtons (int buttons
) {
740 if (buttons
&MS_BUTTON_WHEELUP
) zxKMouseWheel
= ((int)zxKMouseWheel
-1)&0x0f;
741 if (buttons
&MS_BUTTON_WHEELDOWN
) zxKMouseWheel
= ((int)zxKMouseWheel
+1)&0x0f;
742 zxKMouseButtons
= buttons
&(MS_BUTTON_LEFT
|MS_BUTTON_RIGHT
|MS_BUTTON_MIDDLE
);
746 static void zxMouseCB (int x
, int y
, int xrel
, int yrel
, int buttons
) {
747 if (/*widgetsProcessMouse(x, y, 0, buttons) ||*/ msCursorDrawn
) return;
748 //fprintf(stderr, "mouse: x=%d; y=%d; xrel=%d; yrel=%d; btn=#%02X\n", x, y, xrel, yrel, buttons);
749 if (!msCursorDrawn
&& vidWindowActivated
&& SDL_WM_GrabInput(SDL_GRAB_QUERY
) == SDL_GRAB_ON
) {
751 zxKMouseDXAccum
+= xrel
;
752 zxKMouseDYAccum
+= yrel
;
753 int speed
= zxKMouseSpeed
;
754 if (speed
< 1) speed
= 1; else if (speed
> 256) speed
= 256;
756 while (zxKMouseDXAccum
>= speed
) { xrel
+= 1; zxKMouseDXAccum
-= speed
; }
757 while (zxKMouseDXAccum
<= -speed
) { xrel
-= 1; zxKMouseDXAccum
+= speed
; }
758 while (zxKMouseDYAccum
>= speed
) { yrel
+= 1; zxKMouseDYAccum
-= speed
; }
759 while (zxKMouseDYAccum
<= -speed
) { yrel
-= 1; zxKMouseDYAccum
+= speed
; }
760 zxKMouseDX
= (((int)zxKMouseDX
+xrel
)&0xff);
761 zxKMouseDY
= (((int)zxKMouseDY
-yrel
)&0xff);
763 //int lp = (y+24)*352+16+zxScreenOfs;
765 if (zxMouseWasMoved
) {
773 int speed
= zxKMouseSpeed
;
774 if (speed
< 1) speed
= 1; else if (speed
> 256) speed
= 256;
778 zxKMouseDX
= (((int)zxKMouseDX
+dx
)&0xff);
782 zxKMouseDY
= (((int)zxKMouseDY
-dy
)&0xff);
786 if (!zxMouseWasMoved
) {
787 zxMouseLastX
= zxMouseLastY
= 0;
788 zxMouseFracX
= zxMouseFracY
= 0;
791 int speed
= zxKMouseSpeed
;
792 if (speed
< 1) speed
= 1; else if (speed
> 256) speed
= 256;
793 zxMouseFracX
+= xrel
;
794 zxMouseFracY
-= yrel
;
797 while (zxMouseFracX
<= -speed
) { --dx
; zxMouseFracX
+= speed
; }
798 while (zxMouseFracX
>= speed
) { ++dx
; zxMouseFracX
-= speed
; }
799 while (zxMouseFracY
<= -speed
) { --dy
; zxMouseFracY
+= speed
; }
800 while (zxMouseFracY
>= speed
) { ++dy
; zxMouseFracY
-= speed
; }
801 //if (abs(dx) >= 6) { if (dx < 0) dx -= (-dx)/2; else dx += dx/2; }
802 //if (abs(dy) >= 6) { if (dy < 0) dy -= (-dy)/2; else dy += dy/2; }
803 if (dx
&& zxMouseFracX
<= 2) zxMouseFracX
= 0;
804 if (dy
&& zxMouseFracY
<= 2) zxMouseFracY
= 0;
805 if (abs(dx
) >= 6) dx
*= 2;
806 if (abs(dy
) >= 6) dy
*= 2;
809 zxKMouseDX
= (((int)zxKMouseDX
+dx
)&0xff);
810 zxKMouseDY
= (((int)zxKMouseDY
+dy
)&0xff);
813 zxMouseSetButtons(buttons
);
820 static void zxMouseButtonCB (int x
, int y
, int btn
, int buttons
) {
821 if (/*widgetsProcessMouse(x, y, btn, buttons) ||*/ msCursorDrawn
) return;
822 if (!msCursorDrawn
&& vidWindowActivated
) {
823 //fprintf(stderr, "buttons=0x%02x\n", buttons);
824 if (optKMouse
&& buttons
== 0 && btn
== (MS_BUTTON_LEFT
|MS_BUTTON_DEPRESSED
) &&
825 SDL_WM_GrabInput(SDL_GRAB_QUERY
) != SDL_GRAB_ON
)
827 SDL_WM_GrabInput(SDL_GRAB_ON
);
828 emuRealizeRealMouseCursorState();
830 return; // ignore this click
832 if (SDL_WM_GrabInput(SDL_GRAB_QUERY
) == SDL_GRAB_ON
) {
833 zxMouseSetButtons(buttons
);
839 ////////////////////////////////////////////////////////////////////////////////
840 static void zxKeyCB (SDL_KeyboardEvent
*key
) {
842 if (conKeyEvent(key
)) return;
843 // skip debugger check here, i want it to be transparent
844 if (conVisible
|| memviewActive
|| sprviewActive
) return;
846 //if (msCursorDrawn) return;
847 if (debuggerActive
&& dbgKeyEvent(key
)) return;
848 if (memviewActive
&& memviewKeyEvent(key
)) return;
849 if (sprviewActive
&& sprviewKeyEvent(key
)) return;
850 if (uiovlKey(key
)) return;
852 // skip debugger check here, i want it to be transparent
853 if (conVisible
|| memviewActive
|| sprviewActive
) return;
855 if (key
->type
== SDL_KEYDOWN
) {
857 if ((bind
= sdlFindKeyBind(sdlJimBindings
, key
->keysym
.sym
, key
->keysym
.mod
)) != NULL
&& bind
->action
!= NULL
) {
858 Jim_Obj
*eres
= NULL
/*, *dupl*/;
859 // duplicate 'action', 'cause Jim can return the same shared object
860 // if there is nothing to 'expand' in it; it's safe to omit
861 // duplication here, but future versions of Jim can check if the
862 // object is 'shared' here, so let's do it right
863 //!dupl = Jim_DuplicateObj(jim, bind->action);
864 //!Jim_IncrRefCount(dupl); // we need to do this after Jim_DuplicateObj()
865 if (Jim_SubstObj(jim
, /*dupl*/bind
->action
, &eres
, 0) == JIM_OK
) {
866 Jim_IncrRefCount(eres
);
867 if (Jim_EvalObjList(jim
, eres
) != JIM_OK
) {
868 Jim_MakeErrorMessage(jim
);
869 cprintf("\4=== JIM ERROR ===\n%s\n=================\n", Jim_GetString(Jim_GetResult(jim
), NULL
));
871 Jim_DecrRefCount(jim
, eres
);
873 //!Jim_DecrRefCount(jim, dupl);
878 if (/*vidWindowActivated &&*/ msCursorDrawn
) return;
880 if (zxKeyBinds
[key
->keysym
.sym
]) {
881 zxDoKeyUpDown(zxKeyBinds
[key
->keysym
.sym
]&0xffff, (key
->type
== SDL_KEYDOWN
));
882 zxDoKeyUpDown((zxKeyBinds
[key
->keysym
.sym
]>>16)&0xffff, (key
->type
== SDL_KEYDOWN
));
888 ////////////////////////////////////////////////////////////////////////////////
889 #define PUSH_BACK(_c) (*ress)[dpos++] = (_c)
890 #define DECODE_TUPLE(tuple,bytes) \
891 for (tmp = bytes; tmp > 0; tmp--, tuple = (tuple & 0x00ffffff)<<8) \
892 PUSH_BACK((char)((tuple >> 24)&0xff))
894 // returns ress length
895 static int ascii85Decode (char **ress
, const char *srcs
/*, int start, int length*/) {
896 static uint32_t pow85
[5] = { 85*85*85*85UL, 85*85*85UL, 85*85UL, 85UL, 1UL };
897 const uint8_t *data
= (const uint8_t *)srcs
;
898 int len
= (int)strlen(srcs
);
900 int count
= 0, c
= 0;
902 int start
= 0, length
= len
;
905 if (start
< 0) start
= 0; else { len
-= start
; data
+= start
; }
906 if (length
< 0 || len
< length
) length
= len
;
909 int xlen = 4*((length+4)/5);
910 kstringReserve(ress, xlen);
914 *ress
= (char *)calloc(1, len
+1);
915 for (int f
= length
; f
> 0; --f
, ++data
) {
917 if (c
<= ' ') continue; // skip blanks
919 case 'z': // zero tuple
921 //fprintf(stderr, "%s: z inside ascii85 5-tuple\n", file);
931 case '~': // '~>': end of sequence
932 if (f
< 1 || data
[1] != '>') { free(*ress
); return -2; } // error
933 if (count
> 0) { f
= -1; break; }
935 if (c
< '!' || c
> 'u') {
936 //fprintf(stderr, "%s: bad character in ascii85 region: %#o\n", file, c);
940 tuple
+= ((uint8_t)(c
-'!'))*pow85
[count
++];
942 DECODE_TUPLE(tuple
, 4);
949 // write last (possibly incomplete) tuple
951 tuple
+= pow85
[count
];
952 DECODE_TUPLE(tuple
, count
);
961 static void decodeBA (char *str
, int len
) {
964 for (int f
= 0; f
< len
; ++f
, ++str
) {
974 static void printEC (const char *txt
) {
978 if ((len
= ascii85Decode(&dest
, txt
)) >= 0) {
980 fprintf(stderr
, "%s\n", dest
);
986 static int isStr85Equ (const char *txt
, const char *str
) {
990 if ((len
= ascii85Decode(&dest
, txt
)) >= 0) {
991 res
= (strcmp(dest
, str
) == 0);
998 static int checkEGG (const char *str
) {
999 if (isStr85Equ("06:]JASq", str
) || isStr85Equ("0/i", str
)) {
1001 "H8lZV&6)1>+AZ>m)Cf8;A1/cP+CnS)0OJ`X.QVcHA4^cc5r3=m1c%0D3&c263d?EV6@4&>"
1002 "3DYQo;c-FcO+UJ;MOJ$TAYO@/FI]+B?C.L$>%:oPAmh:4Au)>AAU/H;ZakL2I!*!%J;(AK"
1003 "NIR#5TXgZ6c'F1%^kml.JW5W8e;ql0V3fQUNfKpng6ppMf&ip-VOX@=jKl;#q\"DJ-_>jG"
1004 "8#L;nm]!q;7c+hR6p;tVY#J8P$aTTK%c-OT?)<00,+q*8f&ff9a/+sbU,:`<H*[fk0o]7k"
1005 "^l6nRkngc6Tl2Ngs!!P2I%KHG=7n*an'bsgn>!*8s7TLTC+^\\\"W+<=9^%Ol$1A1eR*Be"
1006 "gqjEag:M0OnrC4FBY5@QZ&'HYYZ#EHs8t4$5]!22QoJ3`;-&=\\DteO$d6FBqT0E@:iu?N"
1007 "a5ePUf^_uEEcjTDKfMpX/9]DFL8N-Ee;*8C5'WgbGortZuh1\\N0;/rJB6'(MSmYiS\"6+"
1008 "<NK)KDV3e+Ad[@).W:%.dd'0h=!QUhghQaNNotIZGrpHr-YfEuUpsKW<^@qlZcdTDA!=?W"
1009 "Yd+-^`'G8Or)<0-T&CT.i+:mJp(+/M/nLaVb#5$p2jR2<rl7\"XlngcN`mf,[4oK5JLr\\"
1010 "m=X'(ue;'*1ik&/@T4*=j5t=<&/e/Q+2=((h`>>uN(#>&#i>2/ajK+=eib1coVe3'D)*75"
1011 "m_h;28^M6p6*D854Jj<C^,Q8Wd\"O<)&L/=C$lUAQNN<=eTD:A6kn-=EItXSss.tAS&!;F"
1012 "EsgpJTHIYNNnh'`kmX^[`*ELOHGcWbfPOT`J]A8P`=)AS;rYlR$\"-.RG440lK5:Dg?G'2"
1013 "['dE=nEm1:k,,Se_=%-6Z*L^J[)EC"
1017 if (isStr85Equ("04Jj?B)", str
)) {
1019 "IPaSa(`c:T,o9Bq3\\)IY++?+!-S9%P0/OkjE&f$l.OmK'Ai2;ZHn[<,6od7^8;)po:HaP"
1020 "m<'+&DRS:/1L7)IA7?WI$8WKTUB2tXg>Zb$.?\"@AIAu;)6B;2_PB5M?oBPDC.F)606Z$V"
1021 "=ONd6/5P*LoWKTLQ,d@&;+Ru,\\ESY*rg!l1XrhpJ:\"WKWdOg?l;=RHE:uU9C?aotBqj]"
1022 "=k8cZ`rp\"ZO=GjkfD#o]Z\\=6^]+Gf&-UFthT*hN"
1026 if (isStr85Equ("04o69A7Tr", str
)) {
1028 "Ag7d[&R#Ma9GVV5,S(D;De<T_+W).?,%4n+3cK=%4+0VN@6d\")E].np7l?8gF#cWF7SS_m"
1029 "4@V\\nQ;h!WPD2h#@\\RY&G\\LKL=eTP<V-]U)BN^b.DffHkTPnFcCN4B;]8FCqI!p1@H*_"
1030 "jHJ<%g']RG*MLqCrbP*XbNL=4D1R[;I(c*<FuesbWmSCF1jTW+rplg;9[S[7eDVl6YsjT"
1038 static void addBoots (int simpleAutorun
) {
1039 for (int f
= 0; f
< 4; ++f
) {
1040 if ((snapWasDisk
&(1<<f
)) && !(snapWasCPCDisk
&(1<<f
))) addAutoBoot(f
, 1, simpleAutorun
);
1065 static cli_arun_e cli_autorun
= ARUN_UNDEFINED
;
1067 static void show_help (void) {
1070 " --48 use 48k model\n"
1071 " --128 use 128k model\n"
1072 " -A --no-autorun don't autorun file\n"
1078 static char *strappend (char *s
, const char *s1
) {
1079 if (!s
) s
= strdup("");
1080 if (!s1
|| !s1
[0]) return s
;
1081 char *res
= strprintf("%s%s", s
, s1
);
1097 static void processOptions (int argc
, char *argv
[], int onlydbg
) {
1098 int nomoreopts
= 0, oldABoot
= optAutoaddBoot
;
1099 int do48
= CLI_MODEL_AUTO
;
1104 for (int f
= 1; f
< argc
; ++f
) {
1105 if (checkEGG(argv
[f
])) exit(1);
1107 if (strcmp(argv
[f
], "--") == 0) { nomoreopts
= 1; continue; }
1108 if (strcmp(argv
[f
], "+") == 0) continue; // console command separator
1109 if (argv
[f
][0] == '+') {
1110 if (onlydbg
) continue;
1111 optAutoaddBoot
= oldABoot
;
1112 if (strchr(argv
[f
], ' ') != NULL
) {
1113 conExecute(argv
[f
]+1, 0);
1115 // collect console command
1116 char *cmd
= strdup(argv
[f
]+1);
1117 for (++f
; f
< argc
; ++f
) {
1118 if (argv
[f
][0] == '+') break;
1119 if (argv
[f
][0] == '-' && argv
[f
][1] == '-') break;
1120 cmd
= strappend(cmd
, " ");
1121 cmd
= strappend(cmd
, argv
[f
]);
1123 --f
; // compensate 'for'
1127 if (oldABoot
!= optAutoaddBoot
) {
1128 if (oldABoot
) addBoots(0);
1129 oldABoot
= optAutoaddBoot
;
1136 if (argv
[f
][0] == '-') {
1137 if (argv
[f
][1] == '-') {
1138 if (strcmp(argv
[f
], "--help") == 0) show_help();
1139 else if (strcmp(argv
[f
], "--48") == 0) do48
= CLI_MODEL_48
;
1140 else if (strcmp(argv
[f
], "--128") == 0) do48
= CLI_MODEL_128
;
1141 else if (strcmp(argv
[f
], "--pent") == 0 || strcmp(argv
[f
], "--pentagon") == 0) do48
= CLI_MODEL_PENT
;
1142 else if (strcmp(argv
[f
], "--plus2a") == 0) do48
= CLI_MODEL_PLUS2A
;
1143 else if (strcmp(argv
[f
], "--plus3") == 0) do48
= CLI_MODEL_PLUS3
;
1144 else if (strcmp(argv
[f
], "--no-autorun") == 0) cli_autorun
= ARUN_NONE
;
1145 else if (strcmp(argv
[f
], "--opense") == 0) {
1147 if (!optOpenSE
) { optOpenSE
= 1; emuSetModel(zxModel
, 1); }
1150 else if (strcmp(argv
[f
], "--usock") == 0 || strcmp(argv
[f
], "--usocket") == 0) {
1152 if (f
>= argc
) { fprintf(stderr
, "option '%s' expects socket name!\n", argv
[f
-1]); exit(1); }
1153 if (!onlydbg
) unixsock_start_server(argv
[f
]);
1155 else if (strcmp(argv
[f
], "--unsafe-tcl") == 0) { if (onlydbg
) Jim_SetAllowUnsafeExtensions(1); }
1156 else if (strcmp(argv
[f
], "--no-unsafe-tcl") == 0) { if (onlydbg
) Jim_SetAllowUnsafeExtensions(0); }
1157 else { fprintf(stderr
, "unknown command line option: '%s'\n", argv
[f
]); exit(1); }
1160 for (const char *a
= argv
[f
]+1; *a
; ++a
) {
1162 case 'h': show_help(); break;
1163 case 'A': cli_autorun
= ARUN_NONE
; break;
1166 fprintf(stderr
, "console dump enabled\n");
1170 case 'q': /* do not dump consote text to stdout */
1171 optConDumpToStdout
= 0;
1175 fprintf(stderr
, "sound debug enabled\n");
1176 optSndSyncDebug
= 1;
1179 default: fprintf(stderr
, "unknown command line option: '%c'\n", *a
); exit(1);
1185 if (onlydbg
) continue;
1187 if (loadSnapshot(argv
[f
], SNAPLOAD_ANY
) == 0) {
1188 cprintf("'%s' loaded\n", argv
[f
]);
1189 if (cli_autorun
!= ARUN_NONE
) {
1191 cli_autorun
= (snapWasCPCDisk
? ARUN_P3DOS2A
: ARUN_TRDOS
);
1192 if (do48
== CLI_MODEL_AUTO
|| do48
== CLI_MODEL_128
) do48
= CLI_MODEL_PENT
;
1193 } else if (snapWasTape
) {
1194 cli_autorun
= ARUN_TAP
;
1196 cli_autorun
= ARUN_UNDEFINED
;
1199 //cprintf("wasdisk=%d; wascpc=%d; ar=%d\n", snapWasDisk, snapWasCPCDisk, cli_autorun);
1201 cprintf("failed to load '%s'\n", argv
[f
]);
1206 optAutoaddBoot
= oldABoot
;
1208 if (oldABoot
&& !snapWasCPCDisk
) addBoots(1);
1209 if (cli_autorun
> ARUN_NONE
) {
1210 if (cli_autorun
!= ARUN_P3DOS2A
&& cli_autorun
!= ARUN_P3DOS3
) {
1212 case CLI_MODEL_AUTO
:
1213 switch (cli_autorun
) {
1214 case ARUN_TAP
: cli_autorun
= ARUN_TAP48
; break;
1215 case ARUN_TRDOS
: cli_autorun
= ARUN_TRDOSPENT
; break;
1220 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOS48
: ARUN_TAP48
);
1223 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOS128
: ARUN_TAP128
);
1225 case CLI_MODEL_PENT
:
1226 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOSPENT
: ARUN_TAPPENT
);
1228 case CLI_MODEL_PLUS2A
:
1229 cli_autorun
= (cli_autorun
== ARUN_P3DOS3
? ARUN_P3DOS2A
: ARUN_TAPP2A
);
1231 case CLI_MODEL_PLUS3
:
1232 cli_autorun
= (cli_autorun
== ARUN_P3DOS3
? ARUN_P3DOS3
: ARUN_TAPP3
);
1235 cprintf("\1UNKNOWN CLI MODEL!\n");
1236 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOS48
: ARUN_TAP48
);
1240 switch (cli_autorun
) {
1241 case ARUN_TRDOS
: conExecute("reset trdos", 0); break;
1242 case ARUN_TRDOS48
: conExecute("reset 48k trdos", 0); break;
1243 case ARUN_TRDOS128
: conExecute("reset 128k trdos", 0); break;
1244 case ARUN_TRDOSPENT
: conExecute("reset pentagon 512 trdos", 0); break;
1245 case ARUN_TAP48
: conExecute("reset 48k", 0); goto do_tape_autoload
;
1246 case ARUN_TAP128
: conExecute("reset 128k", 0); goto do_tape_autoload
;
1247 case ARUN_TAPPENT
: conExecute("reset pentagon 512", 0); goto do_tape_autoload
;
1248 case ARUN_TAPP2A
: conExecute("reset plus2a", 0); goto do_tape_autoload
;
1249 case ARUN_TAPP3
: conExecute("reset plus3", 0); goto do_tape_autoload
;
1250 case ARUN_P3DOS2A
: conExecute("reset plus2a", 0); break;
1251 case ARUN_P3DOS3
: conExecute("reset plus3", 0); break;
1254 conExecute("tape _autoload", 0);
1263 static void xMainLoop (void) {
1264 const int mcsInFrame
= 20*1000;
1265 static int64_t mcsFrameEndWanted
;
1267 mcsFrameEndWanted
= timerGetMicroSeconds()+mcsInFrame
;
1269 int64_t mcsCurFrameEnd
;
1270 eres
= processEvents(0);
1273 mcsCurFrameEnd
= timerGetMicroSeconds();
1274 if (mcsCurFrameEnd
> 0) {
1275 int mcsSleep
= (mcsFrameEndWanted
-mcsCurFrameEnd
);
1276 //fprintf(stderr, "0: wait=%.15g\n", ((double)mcsSleep)/1000.0);
1279 //fprintf(stderr, "SLEEP: %.15g\n", ((double)mcsSleep)/1000.0);
1281 //mcsCurFrameEnd = timerGetMicroSeconds();
1282 //fprintf(stderr, "1:few=%d; cfe=%d; few-cfe=%.15g\n", (int)mcsFrameEndWanted, (int)mcsCurFrameEnd, ((double)(mcsInFrame-(mcsFrameEndWanted-mcsCurFrameEnd)))/1000);
1283 mcsFrameEndWanted
+= mcsInFrame
;
1285 fprintf(stderr
, "DESYNC! (%d)\n", mcsSleep
);
1286 //mcsFrameEndWanted = timerGetMicroSeconds()+mcsInFrame;
1287 mcsFrameEndWanted
= mcsCurFrameEnd
+mcsInFrame
;
1293 mcsFrameEndWanted
= timerGetMicroSeconds()+mcsInFrame
;
1299 ////////////////////////////////////////////////////////////////////////////////
1300 static void cprintLibFDC (int type
, const char *msg
) {
1302 case LIBFDC_MSG_DEBUG
: cprintf("\3LIBFDC[debug]: %s\n", msg
); break;
1303 case LIBFDC_MSG_WARNING
: cprintf("\2LIBFDC[warn]: %s\n", msg
); break;
1304 case LIBFDC_MSG_ERROR
: cprintf("\4LIBFDC[error]: %s\n", msg
); break;
1305 default: cprintf("\3LIBFDC[???]: %s\n", msg
); break; // the thing that should not be
1310 ////////////////////////////////////////////////////////////////////////////////
1311 int main (int argc
, char *argv
[]) {
1312 Jim_SetAllowUnsafeExtensions(1);
1317 libfdcMessageCB
= &cprintLibFDC
;
1319 processOptions(argc
, argv
, 1);
1321 if (libspectrum_init() != LIBSPECTRUM_ERROR_NONE
) {
1322 fprintf(stderr
, "FATAL: can't init libspectrum!\n");
1325 cprintf("===================================\n");
1326 cprintf("using libspectrum v%s\n", libspectrum_version());
1328 switch (timerInit()) {
1329 case TIMER_ERROR
: abort();
1330 case TIMER_HPET
: break;
1333 "\2WARNING: please, set your clock source to HPET!\n"
1334 "you can do this by issuing the following command:\n"
1336 "\1sudo echo 'hpet' > /sys/devices/system/clocksource/clocksource0/current_clocksource\n"
1338 "\2this is not a critical issue, but hpet clock will\n"
1339 "\2give you slightly better emulation.\n"
1344 if (!Jim_GetAllowUnsafeExtensions()) {
1345 cprintf("\2WARNING: Disabled unsafe Tcl extensions.\n");
1347 cprintf("Unsafe Tcl extensions enabled ('--no-unsafe-tcl' to disable).\n");
1354 jimEvalFile("init/init.tcl", 0);
1356 jimEvalFile("init/roms.tcl", 0);
1357 emuSetModel(zxModel
, 1);
1364 jimEvalFile("init/concmd.tcl", 0);
1369 frameCB
= zxFrameCB
;
1371 mouseCB
= zxMouseCB
;
1372 mouseButtonCB
= zxMouseButtonCB
;
1374 //jimEvalFile("init/widgets/init.tcl", 1);
1376 jimEvalFile("autoexec.tcl", 1);
1377 emuSetModel(zxModel
, 1); // in case something vital was changed (like "opense on")
1379 processOptions(argc
, argv
, 0);
1381 jimEvalFile1("./.zxemutrc.tcl");
1382 jimEvalFile1("./.zxemut.tcl");
1383 jimEvalFile1("./zxemutrc.tcl");
1384 jimEvalFile1("./zxemut.tcl");
1387 if (sndSampleRate
< 0 && initSound() != 0) {
1388 fprintf(stderr
, "WARNING: can't initialize sound!\n");
1389 cprintf("\4WARNING: can't initialize sound!\n");
1392 sndAllowUseToggle
= 0;
1393 if (sndSampleRate
<= 0) cprintf("NOSOUND mode");
1395 frameCB
= zxFrameCB
;
1397 mouseCB
= zxMouseCB
;
1398 mouseButtonCB
= zxMouseButtonCB
;
1402 if (sndSampleRate
> 0) {
1403 while (processEvents(0) >= 0) {
1406 // optMaxSpeed implies sndUsing==0
1407 if (!optMaxSpeed
&& optSpeed
== 100 && (debuggerActive
|| optPaused
)) soundWrite();
1413 sndAllowUseToggle
= 0;
1417 unixsock_stop_server();
1423 if (condumpfl
!= NULL
) fclose(condumpfl
);