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();
659 //fprintf(stderr, "!!!\n");
662 } while (!debuggerActive
&& !optPaused
&& timerGetMS()-stt
< 20);
663 //fprintf(stderr, "tst=%d; xtime=%u\n", tst, (uint32_t)(timerGetMS()-stt));
664 z80_was_frame_end
= 0;
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);
688 // faster than the original
690 // do incomplete frame
691 tickstodo
-= z80Step(tickstodo
);
692 z80_was_frame_end
= 0;
693 // execute full frames, checking for timeout
694 while (tickstodo
>= machineInfo
.tsperframe
&& !debuggerActive
&& !optPaused
) {
696 tickstodo
-= z80Step(tickstodo
);
697 z80_was_frame_end
= 0;
698 if (timerGetMS()-stt
>= 20) { nomore
= 1; break; }
702 while (tickstodo
> 0 && !debuggerActive
&& !optPaused
) {
703 tickstodo
-= z80Step(tickstodo
);
707 z80_was_frame_end
= 0;
709 // normal, do one frame
711 if (z80_was_frame_end
) {
712 z80_was_frame_end
= 0;
713 if (!debuggerActive
&& !optPaused
) {
714 // normal execution; draw frame, play sound
723 zxRealiseScreen(z80
.tstates
);
728 ////////////////////////////////////////////////////////////////////////////////
729 static void zxMouseSetButtons (int buttons
) {
730 if (buttons
&MS_BUTTON_WHEELUP
) zxKMouseWheel
= ((int)zxKMouseWheel
-1)&0x0f;
731 if (buttons
&MS_BUTTON_WHEELDOWN
) zxKMouseWheel
= ((int)zxKMouseWheel
+1)&0x0f;
732 zxKMouseButtons
= buttons
&(MS_BUTTON_LEFT
|MS_BUTTON_RIGHT
|MS_BUTTON_MIDDLE
);
736 void emuSetKMouseAbsCoords (void) {
737 int x
= msRealX
, y
= msRealY
;
738 //fprintf(stderr, "MS: scale=%d, x=%d; y=%d\n", vidScale, x / vidScale, y / vidScale);
741 x
-= 32 + zxScreenOfs
;
743 //fprintf(stderr, "MS: x=%d; y=%d\n", x, y);
744 kmouseAbsX
= x
; kmouseAbsY
= y
;
748 static void zxMouseCB (int x
, int y
, int xrel
, int yrel
, int buttons
) {
749 if (/*widgetsProcessMouse(x, y, 0, buttons) ||*/ msCursorDrawn
) return;
750 //fprintf(stderr, "mouse: x=%d; y=%d; xrel=%d; yrel=%d; btn=#%02X\n", x, y, xrel, yrel, buttons);
752 if (optKMouse
&& optKMouseAbsolute
) {
753 emuSetKMouseAbsCoords();
757 if (!msCursorDrawn
&& vidWindowActivated
&& SDL_WM_GrabInput(SDL_GRAB_QUERY
) == SDL_GRAB_ON
) {
759 zxKMouseDXAccum
+= xrel
;
760 zxKMouseDYAccum
+= yrel
;
761 int speed
= zxKMouseSpeed
;
762 if (speed
< 1) speed
= 1; else if (speed
> 256) speed
= 256;
764 while (zxKMouseDXAccum
>= speed
) { xrel
+= 1; zxKMouseDXAccum
-= speed
; }
765 while (zxKMouseDXAccum
<= -speed
) { xrel
-= 1; zxKMouseDXAccum
+= speed
; }
766 while (zxKMouseDYAccum
>= speed
) { yrel
+= 1; zxKMouseDYAccum
-= speed
; }
767 while (zxKMouseDYAccum
<= -speed
) { yrel
-= 1; zxKMouseDYAccum
+= speed
; }
768 zxKMouseDX
= (((int)zxKMouseDX
+xrel
)&0xff);
769 zxKMouseDY
= (((int)zxKMouseDY
-yrel
)&0xff);
771 //int lp = (y+24)*352+16+zxScreenOfs;
773 if (zxMouseWasMoved
) {
781 int speed
= zxKMouseSpeed
;
782 if (speed
< 1) speed
= 1; else if (speed
> 256) speed
= 256;
786 zxKMouseDX
= (((int)zxKMouseDX
+dx
)&0xff);
790 zxKMouseDY
= (((int)zxKMouseDY
-dy
)&0xff);
794 if (!zxMouseWasMoved
) {
795 zxMouseLastX
= zxMouseLastY
= 0;
796 zxMouseFracX
= zxMouseFracY
= 0;
799 int speed
= zxKMouseSpeed
;
800 if (speed
< 1) speed
= 1; else if (speed
> 256) speed
= 256;
801 zxMouseFracX
+= xrel
;
802 zxMouseFracY
-= yrel
;
805 while (zxMouseFracX
<= -speed
) { --dx
; zxMouseFracX
+= speed
; }
806 while (zxMouseFracX
>= speed
) { ++dx
; zxMouseFracX
-= speed
; }
807 while (zxMouseFracY
<= -speed
) { --dy
; zxMouseFracY
+= speed
; }
808 while (zxMouseFracY
>= speed
) { ++dy
; zxMouseFracY
-= speed
; }
809 //if (abs(dx) >= 6) { if (dx < 0) dx -= (-dx)/2; else dx += dx/2; }
810 //if (abs(dy) >= 6) { if (dy < 0) dy -= (-dy)/2; else dy += dy/2; }
811 if (dx
&& zxMouseFracX
<= 2) zxMouseFracX
= 0;
812 if (dy
&& zxMouseFracY
<= 2) zxMouseFracY
= 0;
813 if (abs(dx
) >= 6) dx
*= 2;
814 if (abs(dy
) >= 6) dy
*= 2;
817 zxKMouseDX
= (((int)zxKMouseDX
+dx
)&0xff);
818 zxKMouseDY
= (((int)zxKMouseDY
+dy
)&0xff);
821 zxMouseSetButtons(buttons
);
828 static void zxMouseButtonCB (int x
, int y
, int btn
, int buttons
) {
829 if (/*widgetsProcessMouse(x, y, btn, buttons) ||*/ msCursorDrawn
) return;
830 if (!msCursorDrawn
&& vidWindowActivated
) {
831 //fprintf(stderr, "buttons=0x%02x\n", buttons);
832 if (optKMouse
&& optKMouseAbsolute
) {
833 zxMouseSetButtons(buttons
);
836 if (optKMouse
&& buttons
== 0 && btn
== (MS_BUTTON_LEFT
|MS_BUTTON_DEPRESSED
) &&
837 SDL_WM_GrabInput(SDL_GRAB_QUERY
) != SDL_GRAB_ON
)
839 SDL_WM_GrabInput(SDL_GRAB_ON
);
840 emuRealizeRealMouseCursorState();
842 return; // ignore this click
844 if (SDL_WM_GrabInput(SDL_GRAB_QUERY
) == SDL_GRAB_ON
) {
845 zxMouseSetButtons(buttons
);
851 ////////////////////////////////////////////////////////////////////////////////
852 static void zxKeyCB (SDL_KeyboardEvent
*key
) {
854 if (conKeyEvent(key
)) return;
855 // skip debugger check here, i want it to be transparent
856 if (conVisible
|| memviewActive
|| sprviewActive
) return;
858 //if (msCursorDrawn) return;
859 if (debuggerActive
&& dbgKeyEvent(key
)) return;
860 if (memviewActive
&& memviewKeyEvent(key
)) return;
861 if (sprviewActive
&& sprviewKeyEvent(key
)) return;
862 if (uiovlKey(key
)) return;
864 // skip debugger check here, i want it to be transparent
865 if (conVisible
|| memviewActive
|| sprviewActive
) return;
867 if (key
->type
== SDL_KEYDOWN
) {
869 if ((bind
= sdlFindKeyBind(sdlJimBindings
, key
->keysym
.sym
, key
->keysym
.mod
)) != NULL
&& bind
->action
!= NULL
) {
870 Jim_Obj
*eres
= NULL
/*, *dupl*/;
871 // duplicate 'action', 'cause Jim can return the same shared object
872 // if there is nothing to 'expand' in it; it's safe to omit
873 // duplication here, but future versions of Jim can check if the
874 // object is 'shared' here, so let's do it right
875 //!dupl = Jim_DuplicateObj(jim, bind->action);
876 //!Jim_IncrRefCount(dupl); // we need to do this after Jim_DuplicateObj()
877 if (Jim_SubstObj(jim
, /*dupl*/bind
->action
, &eres
, 0) == JIM_OK
) {
878 Jim_IncrRefCount(eres
);
879 if (Jim_EvalObjList(jim
, eres
) != JIM_OK
) {
880 Jim_MakeErrorMessage(jim
);
881 cprintf("\4=== JIM ERROR ===\n%s\n=================\n", Jim_GetString(Jim_GetResult(jim
), NULL
));
883 Jim_DecrRefCount(jim
, eres
);
885 //!Jim_DecrRefCount(jim, dupl);
890 if (/*vidWindowActivated &&*/ msCursorDrawn
) return;
892 if (zxKeyBinds
[key
->keysym
.sym
]) {
893 zxDoKeyUpDown(zxKeyBinds
[key
->keysym
.sym
]&0xffff, (key
->type
== SDL_KEYDOWN
));
894 zxDoKeyUpDown((zxKeyBinds
[key
->keysym
.sym
]>>16)&0xffff, (key
->type
== SDL_KEYDOWN
));
900 ////////////////////////////////////////////////////////////////////////////////
901 #define PUSH_BACK(_c) (*ress)[dpos++] = (_c)
902 #define DECODE_TUPLE(tuple,bytes) \
903 for (tmp = bytes; tmp > 0; tmp--, tuple = (tuple & 0x00ffffff)<<8) \
904 PUSH_BACK((char)((tuple >> 24)&0xff))
906 // returns ress length
907 static int ascii85Decode (char **ress
, const char *srcs
/*, int start, int length*/) {
908 static uint32_t pow85
[5] = { 85*85*85*85UL, 85*85*85UL, 85*85UL, 85UL, 1UL };
909 const uint8_t *data
= (const uint8_t *)srcs
;
910 int len
= (int)strlen(srcs
);
912 int count
= 0, c
= 0;
914 int start
= 0, length
= len
;
917 if (start
< 0) start
= 0; else { len
-= start
; data
+= start
; }
918 if (length
< 0 || len
< length
) length
= len
;
921 int xlen = 4*((length+4)/5);
922 kstringReserve(ress, xlen);
926 *ress
= (char *)calloc(1, len
+1);
927 for (int f
= length
; f
> 0; --f
, ++data
) {
929 if (c
<= ' ') continue; // skip blanks
931 case 'z': // zero tuple
933 //fprintf(stderr, "%s: z inside ascii85 5-tuple\n", file);
943 case '~': // '~>': end of sequence
944 if (f
< 1 || data
[1] != '>') { free(*ress
); return -2; } // error
945 if (count
> 0) { f
= -1; break; }
947 if (c
< '!' || c
> 'u') {
948 //fprintf(stderr, "%s: bad character in ascii85 region: %#o\n", file, c);
952 tuple
+= ((uint8_t)(c
-'!'))*pow85
[count
++];
954 DECODE_TUPLE(tuple
, 4);
961 // write last (possibly incomplete) tuple
963 tuple
+= pow85
[count
];
964 DECODE_TUPLE(tuple
, count
);
973 static void decodeBA (char *str
, int len
) {
976 for (int f
= 0; f
< len
; ++f
, ++str
) {
986 static void printEC (const char *txt
) {
990 if ((len
= ascii85Decode(&dest
, txt
)) >= 0) {
992 fprintf(stderr
, "%s\n", dest
);
998 static int isStr85Equ (const char *txt
, const char *str
) {
1002 if ((len
= ascii85Decode(&dest
, txt
)) >= 0) {
1003 res
= (strcmp(dest
, str
) == 0);
1010 static int checkEGG (const char *str
) {
1011 if (isStr85Equ("06:]JASq", str
) || isStr85Equ("0/i", str
)) {
1013 "H8lZV&6)1>+AZ>m)Cf8;A1/cP+CnS)0OJ`X.QVcHA4^cc5r3=m1c%0D3&c263d?EV6@4&>"
1014 "3DYQo;c-FcO+UJ;MOJ$TAYO@/FI]+B?C.L$>%:oPAmh:4Au)>AAU/H;ZakL2I!*!%J;(AK"
1015 "NIR#5TXgZ6c'F1%^kml.JW5W8e;ql0V3fQUNfKpng6ppMf&ip-VOX@=jKl;#q\"DJ-_>jG"
1016 "8#L;nm]!q;7c+hR6p;tVY#J8P$aTTK%c-OT?)<00,+q*8f&ff9a/+sbU,:`<H*[fk0o]7k"
1017 "^l6nRkngc6Tl2Ngs!!P2I%KHG=7n*an'bsgn>!*8s7TLTC+^\\\"W+<=9^%Ol$1A1eR*Be"
1018 "gqjEag:M0OnrC4FBY5@QZ&'HYYZ#EHs8t4$5]!22QoJ3`;-&=\\DteO$d6FBqT0E@:iu?N"
1019 "a5ePUf^_uEEcjTDKfMpX/9]DFL8N-Ee;*8C5'WgbGortZuh1\\N0;/rJB6'(MSmYiS\"6+"
1020 "<NK)KDV3e+Ad[@).W:%.dd'0h=!QUhghQaNNotIZGrpHr-YfEuUpsKW<^@qlZcdTDA!=?W"
1021 "Yd+-^`'G8Or)<0-T&CT.i+:mJp(+/M/nLaVb#5$p2jR2<rl7\"XlngcN`mf,[4oK5JLr\\"
1022 "m=X'(ue;'*1ik&/@T4*=j5t=<&/e/Q+2=((h`>>uN(#>&#i>2/ajK+=eib1coVe3'D)*75"
1023 "m_h;28^M6p6*D854Jj<C^,Q8Wd\"O<)&L/=C$lUAQNN<=eTD:A6kn-=EItXSss.tAS&!;F"
1024 "EsgpJTHIYNNnh'`kmX^[`*ELOHGcWbfPOT`J]A8P`=)AS;rYlR$\"-.RG440lK5:Dg?G'2"
1025 "['dE=nEm1:k,,Se_=%-6Z*L^J[)EC"
1029 if (isStr85Equ("04Jj?B)", str
)) {
1031 "IPaSa(`c:T,o9Bq3\\)IY++?+!-S9%P0/OkjE&f$l.OmK'Ai2;ZHn[<,6od7^8;)po:HaP"
1032 "m<'+&DRS:/1L7)IA7?WI$8WKTUB2tXg>Zb$.?\"@AIAu;)6B;2_PB5M?oBPDC.F)606Z$V"
1033 "=ONd6/5P*LoWKTLQ,d@&;+Ru,\\ESY*rg!l1XrhpJ:\"WKWdOg?l;=RHE:uU9C?aotBqj]"
1034 "=k8cZ`rp\"ZO=GjkfD#o]Z\\=6^]+Gf&-UFthT*hN"
1038 if (isStr85Equ("04o69A7Tr", str
)) {
1040 "Ag7d[&R#Ma9GVV5,S(D;De<T_+W).?,%4n+3cK=%4+0VN@6d\")E].np7l?8gF#cWF7SS_m"
1041 "4@V\\nQ;h!WPD2h#@\\RY&G\\LKL=eTP<V-]U)BN^b.DffHkTPnFcCN4B;]8FCqI!p1@H*_"
1042 "jHJ<%g']RG*MLqCrbP*XbNL=4D1R[;I(c*<FuesbWmSCF1jTW+rplg;9[S[7eDVl6YsjT"
1050 static void addBoots (int simpleAutorun
) {
1051 for (int f
= 0; f
< 4; ++f
) {
1052 if ((snapWasDisk
&(1<<f
)) && !(snapWasCPCDisk
&(1<<f
))) addAutoBoot(f
, 1, simpleAutorun
);
1077 static cli_arun_e cli_autorun
= ARUN_UNDEFINED
;
1079 static void show_help (void) {
1082 " --48 use 48k model\n"
1083 " --128 use 128k model\n"
1084 " -A --no-autorun don't autorun file\n"
1090 static char *strappend (char *s
, const char *s1
) {
1091 if (!s
) s
= strdup("");
1092 if (!s1
|| !s1
[0]) return s
;
1093 char *res
= strprintf("%s%s", s
, s1
);
1109 static void processOptions (int argc
, char *argv
[], int onlydbg
) {
1110 int nomoreopts
= 0, oldABoot
= optAutoaddBoot
;
1111 int do48
= CLI_MODEL_AUTO
;
1116 for (int f
= 1; f
< argc
; ++f
) {
1117 if (checkEGG(argv
[f
])) exit(1);
1119 if (strcmp(argv
[f
], "--") == 0) { nomoreopts
= 1; continue; }
1120 if (strcmp(argv
[f
], "+") == 0) continue; // console command separator
1121 if (argv
[f
][0] == '+') {
1122 if (onlydbg
) continue;
1123 optAutoaddBoot
= oldABoot
;
1124 if (strchr(argv
[f
], ' ') != NULL
) {
1125 conExecute(argv
[f
]+1, 0);
1127 // collect console command
1128 char *cmd
= strdup(argv
[f
]+1);
1129 for (++f
; f
< argc
; ++f
) {
1130 if (argv
[f
][0] == '+') break;
1131 if (argv
[f
][0] == '-' && argv
[f
][1] == '-') break;
1132 cmd
= strappend(cmd
, " ");
1133 cmd
= strappend(cmd
, argv
[f
]);
1135 --f
; // compensate 'for'
1139 if (oldABoot
!= optAutoaddBoot
) {
1140 if (oldABoot
) addBoots(0);
1141 oldABoot
= optAutoaddBoot
;
1148 if (argv
[f
][0] == '-') {
1149 if (argv
[f
][1] == '-') {
1150 if (strcmp(argv
[f
], "--help") == 0) show_help();
1151 else if (strcmp(argv
[f
], "--48") == 0) do48
= CLI_MODEL_48
;
1152 else if (strcmp(argv
[f
], "--128") == 0) do48
= CLI_MODEL_128
;
1153 else if (strcmp(argv
[f
], "--pent") == 0 || strcmp(argv
[f
], "--pentagon") == 0) do48
= CLI_MODEL_PENT
;
1154 else if (strcmp(argv
[f
], "--plus2a") == 0) do48
= CLI_MODEL_PLUS2A
;
1155 else if (strcmp(argv
[f
], "--plus3") == 0) do48
= CLI_MODEL_PLUS3
;
1156 else if (strcmp(argv
[f
], "--no-autorun") == 0) cli_autorun
= ARUN_NONE
;
1157 else if (strcmp(argv
[f
], "--opense") == 0) {
1159 if (!optOpenSE
) { optOpenSE
= 1; emuSetModel(zxModel
, 1); }
1162 else if (strcmp(argv
[f
], "--usock") == 0 || strcmp(argv
[f
], "--usocket") == 0) {
1164 if (f
>= argc
) { fprintf(stderr
, "option '%s' expects socket name!\n", argv
[f
-1]); exit(1); }
1165 if (!onlydbg
) unixsock_start_server(argv
[f
]);
1167 else if (strcmp(argv
[f
], "--unsafe-tcl") == 0) { if (onlydbg
) Jim_SetAllowUnsafeExtensions(1); }
1168 else if (strcmp(argv
[f
], "--no-unsafe-tcl") == 0) { if (onlydbg
) Jim_SetAllowUnsafeExtensions(0); }
1169 else if (strcmp(argv
[f
], "--tcl-verbose-loading") == 0) { jim_verbose_loading
= 1; }
1170 else if (strcmp(argv
[f
], "--tcl-silent-loading") == 0) { jim_verbose_loading
= 0; }
1171 else { fprintf(stderr
, "unknown command line option: '%s'\n", argv
[f
]); exit(1); }
1174 for (const char *a
= argv
[f
]+1; *a
; ++a
) {
1176 case 'h': show_help(); break;
1177 case 'A': cli_autorun
= ARUN_NONE
; break;
1180 fprintf(stderr
, "console dump enabled\n");
1184 case 'q': /* do not dump consote text to stdout */
1185 optConDumpToStdout
= 0;
1189 fprintf(stderr
, "sound debug enabled\n");
1190 optSndSyncDebug
= 1;
1193 default: fprintf(stderr
, "unknown command line option: '%c'\n", *a
); exit(1);
1199 if (onlydbg
) continue;
1201 if (loadSnapshot(argv
[f
], SNAPLOAD_ANY
) == 0) {
1202 cprintf("'%s' loaded\n", argv
[f
]);
1203 if (cli_autorun
!= ARUN_NONE
) {
1205 cli_autorun
= (snapWasCPCDisk
? ARUN_P3DOS2A
: ARUN_TRDOS
);
1206 if (do48
== CLI_MODEL_AUTO
|| do48
== CLI_MODEL_128
) do48
= CLI_MODEL_PENT
;
1207 } else if (snapWasTape
) {
1208 cli_autorun
= ARUN_TAP
;
1210 cli_autorun
= ARUN_UNDEFINED
;
1213 //cprintf("wasdisk=%d; wascpc=%d; ar=%d\n", snapWasDisk, snapWasCPCDisk, cli_autorun);
1215 cprintf("failed to load '%s'\n", argv
[f
]);
1220 optAutoaddBoot
= oldABoot
;
1222 if (oldABoot
&& !snapWasCPCDisk
) addBoots(1);
1223 if (cli_autorun
> ARUN_NONE
) {
1224 if (cli_autorun
!= ARUN_P3DOS2A
&& cli_autorun
!= ARUN_P3DOS3
) {
1226 case CLI_MODEL_AUTO
:
1227 switch (cli_autorun
) {
1228 case ARUN_TAP
: cli_autorun
= ARUN_TAP48
; break;
1229 case ARUN_TRDOS
: cli_autorun
= ARUN_TRDOSPENT
; break;
1234 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOS48
: ARUN_TAP48
);
1237 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOS128
: ARUN_TAP128
);
1239 case CLI_MODEL_PENT
:
1240 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOSPENT
: ARUN_TAPPENT
);
1242 case CLI_MODEL_PLUS2A
:
1243 cli_autorun
= (cli_autorun
== ARUN_P3DOS3
? ARUN_P3DOS2A
: ARUN_TAPP2A
);
1245 case CLI_MODEL_PLUS3
:
1246 cli_autorun
= (cli_autorun
== ARUN_P3DOS3
? ARUN_P3DOS3
: ARUN_TAPP3
);
1249 cprintf("\1UNKNOWN CLI MODEL!\n");
1250 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOS48
: ARUN_TAP48
);
1254 switch (cli_autorun
) {
1255 case ARUN_TRDOS
: conExecute("reset trdos", 0); break;
1256 case ARUN_TRDOS48
: conExecute("reset 48k trdos", 0); break;
1257 case ARUN_TRDOS128
: conExecute("reset 128k trdos", 0); break;
1258 case ARUN_TRDOSPENT
: conExecute("reset pentagon 512 trdos", 0); break;
1259 case ARUN_TAP48
: conExecute("reset 48k", 0); goto do_tape_autoload
;
1260 case ARUN_TAP128
: conExecute("reset 128k", 0); goto do_tape_autoload
;
1261 case ARUN_TAPPENT
: conExecute("reset pentagon 512", 0); goto do_tape_autoload
;
1262 case ARUN_TAPP2A
: conExecute("reset plus2a", 0); goto do_tape_autoload
;
1263 case ARUN_TAPP3
: conExecute("reset plus3", 0); goto do_tape_autoload
;
1264 case ARUN_P3DOS2A
: conExecute("reset plus2a", 0); break;
1265 case ARUN_P3DOS3
: conExecute("reset plus3", 0); break;
1268 conExecute("tape _autoload", 0);
1277 static void xMainLoop (void) {
1278 const int mcsInFrame
= 20*1000;
1279 static int64_t mcsFrameEndWanted
;
1281 mcsFrameEndWanted
= timerGetMicroSeconds()+mcsInFrame
;
1283 int64_t mcsCurFrameEnd
;
1284 eres
= processEvents(0);
1287 mcsCurFrameEnd
= timerGetMicroSeconds();
1288 if (optMaxSpeed
&& !debuggerActive
&& !optPaused
) {
1289 mcsFrameEndWanted
= mcsCurFrameEnd
+mcsInFrame
;
1292 if (mcsCurFrameEnd
> 0) {
1293 int mcsSleep
= (mcsFrameEndWanted
-mcsCurFrameEnd
);
1294 //fprintf(stderr, "0: wait=%.15g\n", ((double)mcsSleep)/1000.0);
1297 //fprintf(stderr, "SLEEP: %.15g\n", ((double)mcsSleep)/1000.0);
1299 //mcsCurFrameEnd = timerGetMicroSeconds();
1300 //fprintf(stderr, "1:few=%d; cfe=%d; few-cfe=%.15g\n", (int)mcsFrameEndWanted, (int)mcsCurFrameEnd, ((double)(mcsInFrame-(mcsFrameEndWanted-mcsCurFrameEnd)))/1000);
1301 mcsFrameEndWanted
+= mcsInFrame
;
1303 fprintf(stderr
, "DESYNC! (%d)\n", mcsSleep
);
1304 //mcsFrameEndWanted = timerGetMicroSeconds()+mcsInFrame;
1305 mcsFrameEndWanted
= mcsCurFrameEnd
+mcsInFrame
;
1311 mcsFrameEndWanted
= timerGetMicroSeconds()+mcsInFrame
;
1317 ////////////////////////////////////////////////////////////////////////////////
1318 static void cprintLibFDC (int type
, const char *msg
) {
1320 case LIBFDC_MSG_DEBUG
: cprintf("\3LIBFDC[debug]: %s\n", msg
); break;
1321 case LIBFDC_MSG_WARNING
: cprintf("\2LIBFDC[warn]: %s\n", msg
); break;
1322 case LIBFDC_MSG_ERROR
: cprintf("\4LIBFDC[error]: %s\n", msg
); break;
1323 default: cprintf("\3LIBFDC[???]: %s\n", msg
); break; // the thing that should not be
1328 ////////////////////////////////////////////////////////////////////////////////
1329 int main (int argc
, char *argv
[]) {
1330 Jim_SetAllowUnsafeExtensions(1);
1335 libfdcMessageCB
= &cprintLibFDC
;
1337 processOptions(argc
, argv
, 1);
1339 if (libspectrum_init() != LIBSPECTRUM_ERROR_NONE
) {
1340 fprintf(stderr
, "FATAL: can't init libspectrum!\n");
1343 cprintf("===================================\n");
1344 cprintf("using libspectrum v%s\n", libspectrum_version());
1346 switch (timerInit()) {
1347 case TIMER_ERROR
: abort();
1348 case TIMER_HPET
: break;
1351 "\2WARNING: please, set your clock source to HPET!\n"
1352 "you can do this by issuing the following command:\n"
1354 "\1sudo echo 'hpet' > /sys/devices/system/clocksource/clocksource0/current_clocksource\n"
1356 "\2this is not a critical issue, but hpet clock will\n"
1357 "\2give you slightly better emulation.\n"
1362 if (!Jim_GetAllowUnsafeExtensions()) {
1363 cprintf("\2WARNING: Disabled unsafe Tcl extensions.\n");
1365 cprintf("Unsafe Tcl extensions enabled ('--no-unsafe-tcl' to disable).\n");
1372 jimEvalFile("init/init.tcl", 0);
1374 jimEvalFile("init/roms.tcl", 0);
1375 emuSetModel(zxModel
, 1);
1382 jimEvalFile("init/concmd.tcl", 0);
1387 frameCB
= zxFrameCB
;
1389 mouseCB
= zxMouseCB
;
1390 mouseButtonCB
= zxMouseButtonCB
;
1392 //jimEvalFile("init/widgets/init.tcl", 1);
1394 jimEvalFile("autoexec.tcl", 1);
1395 emuSetModel(zxModel
, 1); // in case something vital was changed (like "opense on")
1397 processOptions(argc
, argv
, 0);
1399 jimEvalFile1("./.zxemutrc.tcl");
1400 jimEvalFile1("./.zxemut.tcl");
1401 jimEvalFile1("./zxemutrc.tcl");
1402 jimEvalFile1("./zxemut.tcl");
1405 if (sndSampleRate
< 0 && initSound() != 0) {
1406 fprintf(stderr
, "WARNING: can't initialize sound!\n");
1407 cprintf("\4WARNING: can't initialize sound!\n");
1410 sndAllowUseToggle
= 0;
1411 if (sndSampleRate
<= 0) cprintf("NOSOUND mode");
1413 frameCB
= zxFrameCB
;
1415 mouseCB
= zxMouseCB
;
1416 mouseButtonCB
= zxMouseButtonCB
;
1420 if (sndSampleRate
> 0) {
1421 while (processEvents(0) >= 0) {
1424 // optMaxSpeed implies sndUsing==0
1425 if (!optMaxSpeed
&& optSpeed
== 100 && (debuggerActive
|| optPaused
)) soundWrite();
1431 sndAllowUseToggle
= 0;
1435 unixsock_stop_server();
1441 if (condumpfl
!= NULL
) fclose(condumpfl
);