1 /* coded by Ketmar // Invisible Vector ( ketmar@ketmar.no-ip.org )
2 * Understanding is not required. Only obedience.
4 * This program is free software. It comes without any warranty, to
5 * the extent permitted by applicable law. You can redistribute it
6 * and/or modify it under the terms of the Do What The Fuck You Want
7 * To Public License, Version 2, as published by Sam Hocevar. See
8 * http://sam.zoy.org/wtfpl/COPYING for more details.
10 //#define DISABLE_Z80EX
11 //#define DISABLE_ZYMOSIS
15 #ifndef _DEFAULT_SOURCE
16 # define _DEFAULT_SOURCE
28 #include <sys/socket.h>
30 #include <sys/types.h>
32 #include <sys/select.h>
36 #include <libspectrum.h>
39 ////////////////////////////////////////////////////////////////////////////////
40 #include "libtinf/tinf.h"
41 #include "../libzymosis/zymosis.h"
42 #include "../libfdc/libfdc.h"
45 ////////////////////////////////////////////////////////////////////////////////
46 #include "emucommon.h"
50 #include "libvideo/video.h"
53 #include "zxscrdraw.h"
58 #include "zxkeyinfo.h"
66 ////////////////////////////////////////////////////////////////////////////////
67 extern const unsigned char keyHelpScr
[];
70 ////////////////////////////////////////////////////////////////////////////////
74 static void initMyDir (void) {
75 if (readlink("/proc/self/exe", binMyDir
, sizeof(binMyDir
)-1) < 0) {
76 strcpy(binMyDir
, ".");
78 char *p
= (char *)strrchr(binMyDir
, '/');
80 if (p
== NULL
) strcpy(binMyDir
, "."); else *p
= '\0';
85 ////////////////////////////////////////////////////////////////////////////////
86 const char *snapshotExtensions
[] = {
113 ////////////////////////////////////////////////////////////////////////////////
114 typedef struct PortHandlers
{
115 int machine
; // ZX_MACHINE_MAX: any
118 int (*portInCB
) (zym_cpu_t
*z80
, uint16_t port
, uint8_t *res
);
119 int (*portOutCB
) (zym_cpu_t
*z80
, uint16_t port
, uint8_t value
);
123 #define MAX_PORTHANDLER (64)
124 static PortHandlers portHandlers
[MAX_PORTHANDLER
];
125 static int phCount
= 0;
128 static void phAdd (const PortHandlers
*ph
) {
130 while (ph
->portInCB
|| ph
->portOutCB
) {
131 if (phCount
>= MAX_PORTHANDLER
) { fprintf(stderr
, "FATAL: too many port handlers!\n"); abort(); }
132 portHandlers
[phCount
++] = *ph
++;
138 ////////////////////////////////////////////////////////////////////////////////
142 ////////////////////////////////////////////////////////////////////////////////
143 static int usock_stdin
= 0;
144 static int usock_srv
= -1;
145 static int usock_client
= -1;
146 static char usock_command
[1024];
147 static size_t usock_command_pos
= 0;
150 static size_t unixsock_create_address (struct sockaddr_un
*addr
, const char *name
) {
151 if (!addr
|| !name
|| !name
[0] || strlen(name
) > 100) return 0;
152 memset((void *)addr
, 0, sizeof(*addr
));
153 addr
->sun_family
= AF_UNIX
;
154 addr
->sun_path
[0] = 0; /* abstract unix socket */
155 strcpy(addr
->sun_path
+1, name
);
156 return strlen(addr
->sun_path
+1)+sizeof(addr
->sun_family
)+1u;
160 static void sock_make_non_blocking (int fd
) {
162 int flags
= fcntl(fd
, F_GETFL
, 0);
164 fcntl(fd
, F_SETFL
, flags
);
169 static void unixsock_drop_client (void) {
170 if (usock_client
>= 0) {
174 usock_command_pos
= 0;
178 static void unixsock_stop_server (void) {
179 unixsock_drop_client();
180 if (usock_srv
>= 0) {
187 static void unixsock_start_server (const char *name
) {
188 unixsock_stop_server();
191 if (name
&& strcmp(name
, "-") == 0) {
193 cprintf("\4ERROR: stdin already closed!\n");
200 sock_make_non_blocking(usock_srv
);
204 usock_srv
= socket(AF_UNIX
, SOCK_STREAM
, 0);
206 cprintf("\4ERROR: cannot create server socket!\n");
210 sock_make_non_blocking(usock_srv
);
212 struct sockaddr_un serv_addr
;
213 size_t servlen
= unixsock_create_address(&serv_addr
, name
);
214 if (bind(usock_srv
, (struct sockaddr
*)&serv_addr
, servlen
) < 0) {
215 cprintf("\4ERROR: cannot bind server socket!\n");
221 if (listen(usock_srv
, 1) < 0) {
222 cprintf("\4ERROR: cannot listen on server socket!\n");
228 cprintf("\1created unix server socket '%s'\n", name
);
232 static void unixsock_handle (void) {
233 if (usock_srv
< 0 && usock_client
< 0) return;
238 /* can accept new client? */
239 if (usock_client
< 0) {
240 /* yeah, check for new client */
242 FD_SET(usock_srv
, &rds
);
243 int res
= select(usock_srv
+1, &rds
, NULL
, NULL
, &tout
);
244 if (res
<= 0) return; /* nothing */
245 /* accept new client */
246 struct sockaddr_un cli_addr
;
247 socklen_t clilen
= (socklen_t
)sizeof(cli_addr
);
248 int newsockfd
= accept(usock_srv
, (struct sockaddr
*)&cli_addr
, &clilen
);
250 cprintf("\4ERROR: error accepting client connection!\n");
253 usock_client
= newsockfd
;
254 cprintf("\1USOCK: accepted new client\n");
256 /* check if we can read from client socket */
257 for (int maxread
= 4096; maxread
> 0; --maxread
) {
260 FD_SET(usock_client
, &rds
);
261 int res
= select(usock_client
+1, &rds
, NULL
, NULL
, &tout
);
262 if (res
<= 0) break; /* nothing */
263 ssize_t rd
= read(usock_client
, &ch
, 1);
265 cprintf("\2USOCK: client closed the connection\n");
266 unixsock_drop_client();
270 cprintf("\4ERROR: error reading from client connection!\n");
271 unixsock_drop_client();
274 if (usock_command_pos
>= sizeof(usock_command
)-2) {
275 cprintf("\4ERROR: too long command from client!\n");
276 unixsock_drop_client();
279 if (ch
== '\n') ch
= 0;
280 usock_command
[usock_command_pos
++] = ch
;
282 /* command complete */
283 usock_command_pos
= 0;
284 char *cmd
= strprintf("::usock_received %s", usock_command
);
285 if (Jim_Eval(jim
, cmd
) == JIM_OK
) {
287 Jim_Obj
*res
= Jim_GetResult(jim
);
288 const char *resstr
= Jim_String(res
);
289 if (resstr
&& strcasecmp(resstr
, "close") == 0) {
290 cprintf("closing client connection (client request).\n");
291 unixsock_drop_client();
294 if (resstr
&& strcasecmp(resstr
, "done") == 0) {
299 Jim_MakeErrorMessage(jim
);
300 cprintf("\4=== JIM ERROR ===\n%s\n=================\n", Jim_GetString(Jim_GetResult(jim
), NULL
));
302 /* close client connection? */
304 if (strcmp(usock_command, "close") == 0) {
305 cprintf("closing client connection (client request).\n");
306 unixsock_drop_client();
310 cprintf("\2USOCK COMMAND:<%s>\n", usock_command
);
311 conExecute(usock_command
, 0);
318 ////////////////////////////////////////////////////////////////////////////////
319 static inline void zxDoKeyUpDown (uint16_t portmask
, int isdown
) {
322 zxKeyboardState
[(portmask
>>8)&0xff] &= ~(portmask
&0xff);
324 zxKeyboardState
[(portmask
>>8)&0xff] |= (portmask
&0xff);
330 ////////////////////////////////////////////////////////////////////////////////
331 static void emuInitMemory (void) {
332 zxMaxMemoryBank
= 64;
333 for (int f
= 0; f
< zxMaxMemoryBank
; ++f
) {
334 if ((zxMemoryBanks
[f
] = calloc(1, 16384)) == NULL
) {
335 fprintf(stderr
, "FATAL: out of memory!\n");
340 zxMaxROMMemoryBank
= 16;
341 for (int f
= 0; f
< zxMaxROMMemoryBank
; ++f
) {
342 if ((zxROMBanks
[f
] = calloc(1, 16384)) == NULL
) {
343 fprintf(stderr
, "FATAL: out of memory!\n");
350 static void emuInit (void) {
353 for (int t
= 0; t
< 3; ++t
) {
354 for (int y
= 0; y
< 8; ++y
) {
355 for (int z
= 0; z
< 8; ++z
) {
356 zxScrLineOfs
[lineNo
++] = (t
<<11)|(z
<<8)|(y
<<5);
361 memset(zxKeyBinds
, 0, sizeof(zxKeyBinds
));
362 memset(zxScreen
, 0, sizeof(zxScreen
));
363 memset(zxUlaSnow
, 0, sizeof(zxUlaSnow
));
373 emuFrameStartTime
= 0;
374 emuLastFPSText
[0] = '\0';
377 emuAddPortHandlers();
381 if ((zxDiskIf
= difCreate(DIF_BDI
)) == NULL
) { fprintf(stderr
, "FATAL: can't create BetaDisk object!\n"); abort(); }
384 //zym_clear_callbacks(&z80);
385 z80
.mem_read
= z80MemRead
;
386 z80
.mem_write
= z80MemWrite
;
387 z80
.mem_contention
= z80Contention
;
388 z80
.port_read
= z80PortIn
;
389 z80
.port_write
= z80PortOut
;
390 z80
.port_contention
= z80PortContention
;
391 z80
.trap_ed
= z80EDTrap
;
392 z80
.evenM1
= machineInfo
.evenM1
;
396 ////////////////////////////////////////////////////////////////////////////////
397 // defattr < 0: use screen$ attr
398 static void paintZXScr (int x0
, int y0
, const void *ptr
, int defattr
) {
399 const uint8_t *buf
= (const uint8_t *)ptr
;
401 for (int y
= 0; y
< 192; ++y
) {
402 int scrA
= zxScrLineOfs
[y
], attrA
= 6144+(y
/8)*32;
404 for (int x
= 0; x
< 32; ++x
) {
405 uint8_t bt
= buf
[scrA
++];
406 uint8_t attr
= (defattr
>= 0 && defattr
<= 255 ? defattr
: buf
[attrA
++]);
407 uint8_t paper
= (attr
>>3)&0x0f, ink
= (attr
&0x07)+(paper
&0x08);
409 if (attr
&0x80 && zxFlashState
) { uint8_t t
= paper
; paper
= ink
; ink
= t
; }
410 for (int f
= 0; f
< 8; ++f
) {
411 putPixel(x0
+x
*8+f
, y0
+y
, (bt
&0x80 ? ink
: paper
));
419 static void paintZXScrZ (int x0
, int y0
) {
420 static uint8_t scr
[6912];
421 unsigned int outlen
= 6912;
422 static int unpacked
= 0;
425 if (tinf_zlib_uncompress(scr
, &outlen
, keyHelpScr
, 2153) == TINF_OK
&& outlen
== 6912) {
433 paintZXScr(x0
, y0
, scr
, -1);
438 static void emuDrawFPS (void) {
439 if (emuFrameStartTime
> 0 && emuFrameCount
) {
440 int64_t tt
= timerGetMS();
441 if (tt
-emuFrameStartTime
>= 1000) {
442 int fps
= emuFrameCount
/((tt
-emuFrameStartTime
)/1000.0)+0.5;
443 if (fps
< 0) fps
= 0;
444 //sprintf(emuLastFPSText, "%.15f %d %3d%%", ((double)emuFrameCount/((double)(tt-emuFrameStartTime)/1000)), fps, fps*100/50);
445 sprintf(emuLastFPSText
, "%d %3d%%", fps
, fps
*100/50);
446 emuFrameStartTime
= tt
;
450 if (emuLastFPSText
[0]) {
451 drawStr6Outline(emuLastFPSText
, frameSfc
->w
-6*strlen(emuLastFPSText
)-2, 2, 12, 0);
456 ////////////////////////////////////////////////////////////////////////////////
457 static void zxPaintOverlaysAndLeds (void) {
459 if (optKeyHelpVisible
) paintZXScrZ(32-zxScreenOfs
, 24);
462 if (optDrawKeyLeds
) keyledBlit(1, frameSfc
->h
-15);
463 if (optTapePlaying
) emuTapeDrawCounter();
464 if (diskLastActivity
> 0) diskBlit(frameSfc
->w
-26, frameSfc
->h
-26);
467 if (optMaxSpeed
) drawStr6Outline("max", frameSfc
->w
-3*6-2, frameSfc
->h
-10, 12, 0);
471 //drawStr8Outline("paused", frameSfc->w-6*8-2, 2, 12, 0);
472 int w
= drawStrZ("paused", 0, 0, 255, 255);
473 drawStrZ("paused", 320-w
-1, 0, 66, 67);
474 } else if (optDrawFPS
) {
478 // "late timings" mark
479 if (zxLateTimings
) drawStr6Outline("LT", 2, 2, 12, 0);
485 if (debuggerActive
) dbgDraw();
488 if (conVisible
) conDraw();
493 static char buf
[1024];
494 Uint8 fg
= ((zxBorder
&0x07) < 6 ? 15 : 0);
495 if (zxRZXFrames
> 0) {
496 int prc
= 100*zxRZXCurFrame
/zxRZXFrames
;
497 int secs
= (int)((zxRZXFrames
-zxRZXCurFrame
)/(50.0*optSpeed
/100.0));
498 if (prc
> 100) prc
= 100;
499 if (secs
< 0) secs
= 0;
500 sprintf(buf
, "RZX:%3d%% (%d:%02d left)", prc
, secs
/60, secs
%60);
504 drawStr6(buf
, 1, 1, fg
, 255);
507 Jim_CollectIfNeeded(jim
);
511 static void zxPaintZXScreen (void) {
512 for (int y
= 0; y
< 240; ++y
) {
513 int lp
= (y
+24)*352+16+zxScreenOfs
;
514 // the real Speccy screen is not 'centered', as aowen says; 10px seems to be the right value
516 Uint8
*d
= (((Uint8
*)frameSfc
->pixels
)+(y
*frameSfc
->pitch
));
517 for (int x
= 0; x
< 320; ++x
, ++lp
, d
+= 4) {
518 //putPixelMix(x, y, zxScreen[zxScreenCurrent][lp], zxScreen[zxScreenCurrent^1][lp]);
519 Uint8 c0
= zxScreen
[zxScreenCurrent
][lp
], c1
= zxScreen
[zxScreenCurrent
^1][lp
];
520 d
[vidRIdx
] = (palRGB
[c0
][0]+palRGB
[c1
][0])/2;
521 d
[vidGIdx
] = (palRGB
[c0
][1]+palRGB
[c1
][1])/2;
522 d
[vidBIdx
] = (palRGB
[c0
][2]+palRGB
[c1
][2])/2;
525 Uint32
*dst
= (Uint32
*)(((Uint8
*)frameSfc
->pixels
)+(y
*frameSfc
->pitch
));
526 if (optSlowShade
&& optSpeed
< 100) {
527 int shadeStart
= zxScreenLineShadeStart(z80
.tstates
);
528 if (lp
>= shadeStart
) {
529 // the whole line is shaded
530 for (int x
= 0; x
< 320; ++x
) {
531 putPixelMix(x
, y
, zxScreen
[zxScreenCurrent
][lp
++], 0);
532 //*dst++ = palette[zxScreen[zxScreenCurrent][lp++]];
534 } else if (lp
< shadeStart
&& lp
+319 >= shadeStart
) {
535 // line part is shaded
536 for (int x
= 0; x
< 320; ++x
) {
537 if (lp
< shadeStart
) {
538 *dst
++ = palette
[zxScreen
[zxScreenCurrent
][lp
++]];
540 putPixelMix(x
, y
, zxScreen
[zxScreenCurrent
][lp
++], 0);
545 for (int x
= 0; x
< 320; ++x
) {
546 //putPixel(x, y, zxScreen[zxScreenCurrent][lp++]);
547 *dst
++ = palette
[zxScreen
[zxScreenCurrent
][lp
++]];
551 for (int x
= 0; x
< 320; ++x
) {
552 //putPixel(x, y, zxScreen[zxScreenCurrent][lp++]);
553 *dst
++ = palette
[zxScreen
[zxScreenCurrent
][lp
++]];
561 static void zxPaintScreen (void) {
563 zxPaintOverlaysAndLeds();
567 ////////////////////////////////////////////////////////////////////////////////
568 static int msCursorDrawn
= 0;
569 static int msRealCursorVisible
= 1;
572 void emuHideRealMouseCursor (void) {
573 if (msRealCursorVisible
) {
575 msRealCursorVisible
= 0;
580 void emuShowRealMouseCursor (void) {
581 if (!msRealCursorVisible
) {
583 msRealCursorVisible
= 1;
588 void emuRealizeRealMouseCursorState (void) {
590 emuHideRealMouseCursor();
591 } else if (SDL_WM_GrabInput(SDL_GRAB_QUERY
) == SDL_GRAB_OFF
) {
593 if (!msCursorDrawn
) emuShowRealMouseCursor(); else emuHideRealMouseCursor();
596 emuHideRealMouseCursor();
601 void emuFullScreenChanged (void) {
602 emuRealizeRealMouseCursorState();
606 static void zxFrameCB (void) {
607 if (!debuggerActive
&& !optPaused
) {
610 const int msscroff
= (optMaxSpeedBadScreen
|| optMaxSpeedBadScreenTape
);
611 if (msscroff
) ++optNoScreenReal
;
612 const int64_t stt
= timerGetMS();
614 while (!debuggerActive
&& !optPaused
&& !z80_was_frame_end
) z80Step();
615 z80_was_frame_end
= 0;
616 } while (!debuggerActive
&& !optPaused
&& timerGetMS()-stt
< 20);
617 // redraw screen; prevent border flicking
621 zxRealiseScreen(machineInfo
.tsperframe
);
623 zxRealiseScreen(z80
.tstates
);
625 } else if (optSpeed
!= 100) {
627 int tickstodo
= machineInfo
.tsperframe
*optSpeed
/100+1;
628 const int64_t stt
= timerGetMS();
629 if (emuPrevFCB
== 0) {
632 if (stt
-emuPrevFCB
< 20) SDL_Delay(20-(stt
-emuPrevFCB
));
633 emuPrevFCB
= timerGetMS();
635 if (optSpeed
< 100) {
636 // slower than the original
637 //fprintf(stderr, "speed=%d; tstates=%d; todo=%d; end=%d; frame=%d\n", optSpeed, z80.tstates, tickstodo, z80.tstates+tickstodo, machineInfo.tsperframe);
638 while (tickstodo
> 0 && !debuggerActive
&& !optPaused
) tickstodo
-= z80Step();
639 z80_was_frame_end
= 0;
641 // faster than the original
643 // do incomplete frame
644 while (!z80_was_frame_end
&& !debuggerActive
&& !optPaused
) tickstodo
-= z80Step();
645 z80_was_frame_end
= 0;
646 // execute full frames, checking for timeout
647 while (tickstodo
>= machineInfo
.tsperframe
&& !debuggerActive
&& !optPaused
) {
649 while (!z80_was_frame_end
&& !debuggerActive
&& !optPaused
) tickstodo
-= z80Step();
650 z80_was_frame_end
= 0;
651 if (timerGetMS()-stt
>= 20) { nomore
= 1; break; }
655 //while (tickstodo > 0 && !debuggerActive && !optPaused && timerGetMS()-stt <= 20) tickstodo -= z80Step();
656 while (tickstodo
> 0 && !debuggerActive
&& !optPaused
) tickstodo
-= z80Step();
659 z80_was_frame_end
= 0;
661 // normal, do one frame till end
662 while (!debuggerActive
&& !optPaused
&& !z80_was_frame_end
) z80Step();
663 if (z80_was_frame_end
) {
664 z80_was_frame_end
= 0;
665 if (!debuggerActive
&& !optPaused
) {
666 // normal execution; draw frame, play sound
675 zxRealiseScreen(z80
.tstates
);
680 ////////////////////////////////////////////////////////////////////////////////
681 static void zxMouseSetButtons (int buttons
) {
682 if (buttons
&MS_BUTTON_WHEELUP
) zxKMouseWheel
= ((int)zxKMouseWheel
-1)&0x0f;
683 if (buttons
&MS_BUTTON_WHEELDOWN
) zxKMouseWheel
= ((int)zxKMouseWheel
+1)&0x0f;
684 zxKMouseButtons
= buttons
&(MS_BUTTON_LEFT
|MS_BUTTON_RIGHT
|MS_BUTTON_MIDDLE
);
688 static void zxMouseCB (int x
, int y
, int xrel
, int yrel
, int buttons
) {
689 if (/*widgetsProcessMouse(x, y, 0, buttons) ||*/ msCursorDrawn
) return;
690 //fprintf(stderr, "mouse: x=%d; y=%d; xrel=%d; yrel=%d; btn=#%02X\n", x, y, xrel, yrel, buttons);
691 if (!msCursorDrawn
&& vidWindowActivated
&& SDL_WM_GrabInput(SDL_GRAB_QUERY
) == SDL_GRAB_ON
) {
693 zxKMouseDXAccum
+= xrel
;
694 zxKMouseDYAccum
+= yrel
;
695 int speed
= zxKMouseSpeed
;
696 if (speed
< 1) speed
= 1; else if (speed
> 256) speed
= 256;
698 while (zxKMouseDXAccum
>= speed
) { xrel
+= 1; zxKMouseDXAccum
-= speed
; }
699 while (zxKMouseDXAccum
<= -speed
) { xrel
-= 1; zxKMouseDXAccum
+= speed
; }
700 while (zxKMouseDYAccum
>= speed
) { yrel
+= 1; zxKMouseDYAccum
-= speed
; }
701 while (zxKMouseDYAccum
<= -speed
) { yrel
-= 1; zxKMouseDYAccum
+= speed
; }
702 zxKMouseDX
= (((int)zxKMouseDX
+xrel
)&0xff);
703 zxKMouseDY
= (((int)zxKMouseDY
-yrel
)&0xff);
705 //int lp = (y+24)*352+16+zxScreenOfs;
707 if (zxMouseWasMoved
) {
715 int speed
= zxKMouseSpeed
;
716 if (speed
< 1) speed
= 1; else if (speed
> 256) speed
= 256;
720 zxKMouseDX
= (((int)zxKMouseDX
+dx
)&0xff);
724 zxKMouseDY
= (((int)zxKMouseDY
-dy
)&0xff);
728 zxMouseSetButtons(buttons
);
735 static void zxMouseButtonCB (int x
, int y
, int btn
, int buttons
) {
736 if (/*widgetsProcessMouse(x, y, btn, buttons) ||*/ msCursorDrawn
) return;
737 if (!msCursorDrawn
&& vidWindowActivated
) {
738 //fprintf(stderr, "buttons=0x%02x\n", buttons);
739 if (optKMouse
&& buttons
== 0 && btn
== (MS_BUTTON_LEFT
|MS_BUTTON_DEPRESSED
) &&
740 SDL_WM_GrabInput(SDL_GRAB_QUERY
) != SDL_GRAB_ON
)
742 SDL_WM_GrabInput(SDL_GRAB_ON
);
743 emuRealizeRealMouseCursorState();
745 return; // ignore this click
747 if (SDL_WM_GrabInput(SDL_GRAB_QUERY
) == SDL_GRAB_ON
) {
748 zxMouseSetButtons(buttons
);
754 ////////////////////////////////////////////////////////////////////////////////
755 static void zxKeyCB (SDL_KeyboardEvent
*key
) {
756 if (conVisible
&& conKeyEvent(key
)) return;
757 //if (msCursorDrawn) return;
758 if (debuggerActive
&& dbgKeyEvent(key
)) return;
759 if (uiovlKey(key
)) return;
760 if (key
->type
== SDL_KEYDOWN
) {
762 if ((bind
= sdlFindKeyBind(sdlJimBindings
, key
->keysym
.sym
, key
->keysym
.mod
)) != NULL
&& bind
->action
!= NULL
) {
763 Jim_Obj
*eres
= NULL
/*, *dupl*/;
764 // duplicate 'action', 'cause Jim can return the same shared object
765 // if there is nothing to 'expand' in it; it's safe to omit
766 // duplication here, but future versions of Jim can check if the
767 // object is 'shared' here, so let's do it right
768 //!dupl = Jim_DuplicateObj(jim, bind->action);
769 //!Jim_IncrRefCount(dupl); // we need to do this after Jim_DuplicateObj()
770 if (Jim_SubstObj(jim
, /*dupl*/bind
->action
, &eres
, 0) == JIM_OK
) {
771 Jim_IncrRefCount(eres
);
772 if (Jim_EvalObjList(jim
, eres
) != JIM_OK
) {
773 Jim_MakeErrorMessage(jim
);
774 cprintf("\4=== JIM ERROR ===\n%s\n=================\n", Jim_GetString(Jim_GetResult(jim
), NULL
));
776 Jim_DecrRefCount(jim
, eres
);
778 //!Jim_DecrRefCount(jim, dupl);
783 if (/*vidWindowActivated &&*/ msCursorDrawn
) return;
785 if (zxKeyBinds
[key
->keysym
.sym
]) {
786 zxDoKeyUpDown(zxKeyBinds
[key
->keysym
.sym
]&0xffff, (key
->type
== SDL_KEYDOWN
));
787 zxDoKeyUpDown((zxKeyBinds
[key
->keysym
.sym
]>>16)&0xffff, (key
->type
== SDL_KEYDOWN
));
793 ////////////////////////////////////////////////////////////////////////////////
794 #define PUSH_BACK(_c) (*ress)[dpos++] = (_c)
795 #define DECODE_TUPLE(tuple,bytes) \
796 for (tmp = bytes; tmp > 0; tmp--, tuple = (tuple & 0x00ffffff)<<8) \
797 PUSH_BACK((char)((tuple >> 24)&0xff))
799 // returns ress length
800 static int ascii85Decode (char **ress
, const char *srcs
/*, int start, int length*/) {
801 static uint32_t pow85
[5] = { 85*85*85*85UL, 85*85*85UL, 85*85UL, 85UL, 1UL };
802 const uint8_t *data
= (const uint8_t *)srcs
;
803 int len
= (int)strlen(srcs
);
805 int count
= 0, c
= 0;
807 int start
= 0, length
= len
;
810 if (start
< 0) start
= 0; else { len
-= start
; data
+= start
; }
811 if (length
< 0 || len
< length
) length
= len
;
814 int xlen = 4*((length+4)/5);
815 kstringReserve(ress, xlen);
819 *ress
= (char *)calloc(1, len
+1);
820 for (int f
= length
; f
> 0; --f
, ++data
) {
822 if (c
<= ' ') continue; // skip blanks
824 case 'z': // zero tuple
826 //fprintf(stderr, "%s: z inside ascii85 5-tuple\n", file);
836 case '~': // '~>': end of sequence
837 if (f
< 1 || data
[1] != '>') { free(*ress
); return -2; } // error
838 if (count
> 0) { f
= -1; break; }
840 if (c
< '!' || c
> 'u') {
841 //fprintf(stderr, "%s: bad character in ascii85 region: %#o\n", file, c);
845 tuple
+= ((uint8_t)(c
-'!'))*pow85
[count
++];
847 DECODE_TUPLE(tuple
, 4);
854 // write last (possibly incomplete) tuple
856 tuple
+= pow85
[count
];
857 DECODE_TUPLE(tuple
, count
);
866 static void decodeBA (char *str
, int len
) {
869 for (int f
= 0; f
< len
; ++f
, ++str
) {
879 static void printEC (const char *txt
) {
883 if ((len
= ascii85Decode(&dest
, txt
)) >= 0) {
885 fprintf(stderr
, "%s\n", dest
);
891 static int isStr85Equ (const char *txt
, const char *str
) {
895 if ((len
= ascii85Decode(&dest
, txt
)) >= 0) {
896 res
= (strcmp(dest
, str
) == 0);
903 static int checkEGG (const char *str
) {
904 if (isStr85Equ("06:]JASq", str
) || isStr85Equ("0/i", str
)) {
906 "H8lZV&6)1>+AZ>m)Cf8;A1/cP+CnS)0OJ`X.QVcHA4^cc5r3=m1c%0D3&c263d?EV6@4&>"
907 "3DYQo;c-FcO+UJ;MOJ$TAYO@/FI]+B?C.L$>%:oPAmh:4Au)>AAU/H;ZakL2I!*!%J;(AK"
908 "NIR#5TXgZ6c'F1%^kml.JW5W8e;ql0V3fQUNfKpng6ppMf&ip-VOX@=jKl;#q\"DJ-_>jG"
909 "8#L;nm]!q;7c+hR6p;tVY#J8P$aTTK%c-OT?)<00,+q*8f&ff9a/+sbU,:`<H*[fk0o]7k"
910 "^l6nRkngc6Tl2Ngs!!P2I%KHG=7n*an'bsgn>!*8s7TLTC+^\\\"W+<=9^%Ol$1A1eR*Be"
911 "gqjEag:M0OnrC4FBY5@QZ&'HYYZ#EHs8t4$5]!22QoJ3`;-&=\\DteO$d6FBqT0E@:iu?N"
912 "a5ePUf^_uEEcjTDKfMpX/9]DFL8N-Ee;*8C5'WgbGortZuh1\\N0;/rJB6'(MSmYiS\"6+"
913 "<NK)KDV3e+Ad[@).W:%.dd'0h=!QUhghQaNNotIZGrpHr-YfEuUpsKW<^@qlZcdTDA!=?W"
914 "Yd+-^`'G8Or)<0-T&CT.i+:mJp(+/M/nLaVb#5$p2jR2<rl7\"XlngcN`mf,[4oK5JLr\\"
915 "m=X'(ue;'*1ik&/@T4*=j5t=<&/e/Q+2=((h`>>uN(#>&#i>2/ajK+=eib1coVe3'D)*75"
916 "m_h;28^M6p6*D854Jj<C^,Q8Wd\"O<)&L/=C$lUAQNN<=eTD:A6kn-=EItXSss.tAS&!;F"
917 "EsgpJTHIYNNnh'`kmX^[`*ELOHGcWbfPOT`J]A8P`=)AS;rYlR$\"-.RG440lK5:Dg?G'2"
918 "['dE=nEm1:k,,Se_=%-6Z*L^J[)EC"
922 if (isStr85Equ("04Jj?B)", str
)) {
924 "IPaSa(`c:T,o9Bq3\\)IY++?+!-S9%P0/OkjE&f$l.OmK'Ai2;ZHn[<,6od7^8;)po:HaP"
925 "m<'+&DRS:/1L7)IA7?WI$8WKTUB2tXg>Zb$.?\"@AIAu;)6B;2_PB5M?oBPDC.F)606Z$V"
926 "=ONd6/5P*LoWKTLQ,d@&;+Ru,\\ESY*rg!l1XrhpJ:\"WKWdOg?l;=RHE:uU9C?aotBqj]"
927 "=k8cZ`rp\"ZO=GjkfD#o]Z\\=6^]+Gf&-UFthT*hN"
931 if (isStr85Equ("04o69A7Tr", str
)) {
933 "Ag7d[&R#Ma9GVV5,S(D;De<T_+W).?,%4n+3cK=%4+0VN@6d\")E].np7l?8gF#cWF7SS_m"
934 "4@V\\nQ;h!WPD2h#@\\RY&G\\LKL=eTP<V-]U)BN^b.DffHkTPnFcCN4B;]8FCqI!p1@H*_"
935 "jHJ<%g']RG*MLqCrbP*XbNL=4D1R[;I(c*<FuesbWmSCF1jTW+rplg;9[S[7eDVl6YsjT"
943 static void addBoots (int simpleAutorun
) {
944 for (int f
= 0; f
< 4; ++f
) {
945 if ((snapWasDisk
&(1<<f
)) && !(snapWasCPCDisk
&(1<<f
))) addAutoBoot(f
, 1, simpleAutorun
);
970 static cli_arun_e cli_autorun
= ARUN_UNDEFINED
;
972 static void show_help (void) {
975 " --48 use 48k model\n"
976 " --128 use 128k model\n"
977 " -A --no-autorun don't autorun file\n"
983 static char *strappend (char *s
, const char *s1
) {
984 if (!s
) s
= strdup("");
985 if (!s1
|| !s1
[0]) return s
;
986 char *res
= strprintf("%s%s", s
, s1
);
1002 static void processOptions (int argc
, char *argv
[], int onlydbg
) {
1003 int nomoreopts
= 0, oldABoot
= optAutoaddBoot
;
1004 int do48
= CLI_MODEL_AUTO
;
1009 for (int f
= 1; f
< argc
; ++f
) {
1010 if (checkEGG(argv
[f
])) exit(1);
1012 if (strcmp(argv
[f
], "--") == 0) { nomoreopts
= 1; continue; }
1013 if (strcmp(argv
[f
], "+") == 0) continue; // console command separator
1014 if (argv
[f
][0] == '+') {
1015 if (onlydbg
) continue;
1016 optAutoaddBoot
= oldABoot
;
1017 if (strchr(argv
[f
], ' ') != NULL
) {
1018 conExecute(argv
[f
]+1, 0);
1020 // collect console command
1021 char *cmd
= strdup(argv
[f
]+1);
1022 for (++f
; f
< argc
; ++f
) {
1023 if (argv
[f
][0] == '+') break;
1024 if (argv
[f
][0] == '-' && argv
[f
][1] == '-') break;
1025 cmd
= strappend(cmd
, " ");
1026 cmd
= strappend(cmd
, argv
[f
]);
1028 --f
; // compensate 'for'
1032 if (oldABoot
!= optAutoaddBoot
) {
1033 if (oldABoot
) addBoots(0);
1034 oldABoot
= optAutoaddBoot
;
1041 if (argv
[f
][0] == '-') {
1042 if (argv
[f
][1] == '-') {
1043 if (strcmp(argv
[f
], "--help") == 0) show_help();
1044 else if (strcmp(argv
[f
], "--48") == 0) do48
= CLI_MODEL_48
;
1045 else if (strcmp(argv
[f
], "--128") == 0) do48
= CLI_MODEL_128
;
1046 else if (strcmp(argv
[f
], "--pent") == 0 || strcmp(argv
[f
], "--pentagon") == 0) do48
= CLI_MODEL_PENT
;
1047 else if (strcmp(argv
[f
], "--plus2a") == 0) do48
= CLI_MODEL_PLUS2A
;
1048 else if (strcmp(argv
[f
], "--plus3") == 0) do48
= CLI_MODEL_PLUS3
;
1049 else if (strcmp(argv
[f
], "--no-autorun") == 0) cli_autorun
= ARUN_NONE
;
1050 else if (strcmp(argv
[f
], "--opense") == 0) {
1052 if (!optOpenSE
) { optOpenSE
= 1; emuSetModel(zxModel
, 1); }
1055 else if (strcmp(argv
[f
], "--usock") == 0 || strcmp(argv
[f
], "--usocket") == 0) {
1057 if (f
>= argc
) { fprintf(stderr
, "option '%s' expects socket name!\n", argv
[f
-1]); exit(1); }
1058 if (!onlydbg
) unixsock_start_server(argv
[f
]);
1060 else if (strcmp(argv
[f
], "--unsafe-tcl") == 0) { if (onlydbg
) Jim_SetAllowUnsafeExtensions(1); }
1061 else if (strcmp(argv
[f
], "--no-unsafe-tcl") == 0) { if (onlydbg
) Jim_SetAllowUnsafeExtensions(0); }
1062 else { fprintf(stderr
, "unknown command line option: '%s'\n", argv
[f
]); exit(1); }
1065 for (const char *a
= argv
[f
]+1; *a
; ++a
) {
1067 case 'h': show_help(); break;
1068 case 'A': cli_autorun
= ARUN_NONE
; break;
1071 fprintf(stderr
, "console dump enabled\n");
1075 case 'q': /* do not dump consote text to stdout */
1076 optConDumpToStdout
= 0;
1080 fprintf(stderr
, "sound debug enabled\n");
1081 optSndSyncDebug
= 1;
1084 default: fprintf(stderr
, "unknown command line option: '%c'\n", *a
); exit(1);
1090 if (onlydbg
) continue;
1092 if (loadSnapshot(argv
[f
], SNAPLOAD_ANY
) == 0) {
1093 cprintf("'%s' loaded\n", argv
[f
]);
1094 if (cli_autorun
!= ARUN_NONE
) {
1095 if (snapWasDisk
) cli_autorun
= (snapWasCPCDisk
? ARUN_P3DOS2A
: ARUN_TRDOS
);
1096 else if (snapWasTape
) cli_autorun
= ARUN_TAP
;
1097 else cli_autorun
= ARUN_UNDEFINED
;
1099 //cprintf("wasdisk=%d; wascpc=%d; ar=%d\n", snapWasDisk, snapWasCPCDisk, cli_autorun);
1101 cprintf("failed to load '%s'\n", argv
[f
]);
1106 optAutoaddBoot
= oldABoot
;
1108 if (oldABoot
&& !snapWasCPCDisk
) addBoots(1);
1109 if (cli_autorun
> ARUN_NONE
) {
1110 if (cli_autorun
!= ARUN_P3DOS2A
&& cli_autorun
!= ARUN_P3DOS3
) {
1112 case CLI_MODEL_AUTO
:
1113 switch (cli_autorun
) {
1114 case ARUN_TAP
: cli_autorun
= ARUN_TAP48
; break;
1115 case ARUN_TRDOS
: cli_autorun
= ARUN_TRDOSPENT
; break;
1120 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOS48
: ARUN_TAP48
);
1123 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOS128
: ARUN_TAP128
);
1125 case CLI_MODEL_PENT
:
1126 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOSPENT
: ARUN_TAPPENT
);
1128 case CLI_MODEL_PLUS2A
:
1129 cli_autorun
= (cli_autorun
== ARUN_P3DOS3
? ARUN_P3DOS2A
: ARUN_TAPP2A
);
1131 case CLI_MODEL_PLUS3
:
1132 cli_autorun
= (cli_autorun
== ARUN_P3DOS3
? ARUN_P3DOS3
: ARUN_TAPP3
);
1135 cprintf("\1UNKNOWN CLI MODEL!\n");
1136 cli_autorun
= (cli_autorun
== ARUN_TRDOS
? ARUN_TRDOS48
: ARUN_TAP48
);
1140 switch (cli_autorun
) {
1141 case ARUN_TRDOS
: conExecute("reset trdos", 0); break;
1142 case ARUN_TRDOS48
: conExecute("reset 48k trdos", 0); break;
1143 case ARUN_TRDOS128
: conExecute("reset 128k trdos", 0); break;
1144 case ARUN_TRDOSPENT
: conExecute("reset pentagon 512 trdos", 0); break;
1145 case ARUN_TAP48
: conExecute("reset 48k", 0); goto do_tape_autoload
;
1146 case ARUN_TAP128
: conExecute("reset 128k", 0); goto do_tape_autoload
;
1147 case ARUN_TAPPENT
: conExecute("reset pentagon 512", 0); goto do_tape_autoload
;
1148 case ARUN_TAPP2A
: conExecute("reset plus2a", 0); goto do_tape_autoload
;
1149 case ARUN_TAPP3
: conExecute("reset plus3", 0); goto do_tape_autoload
;
1150 case ARUN_P3DOS2A
: conExecute("reset plus2a", 0); break;
1151 case ARUN_P3DOS3
: conExecute("reset plus3", 0); break;
1154 conExecute("tape _autoload", 0);
1163 static void xMainLoop (void) {
1164 const int mcsInFrame
= 20*1000;
1165 static int64_t mcsFrameEndWanted
;
1167 mcsFrameEndWanted
= timerGetMicroSeconds()+mcsInFrame
;
1169 int64_t mcsCurFrameEnd
;
1170 eres
= processEvents(0);
1173 mcsCurFrameEnd
= timerGetMicroSeconds();
1174 if (mcsCurFrameEnd
> 0) {
1175 int mcsSleep
= (mcsFrameEndWanted
-mcsCurFrameEnd
);
1176 //fprintf(stderr, "0: wait=%.15g\n", ((double)mcsSleep)/1000.0);
1179 //fprintf(stderr, "SLEEP: %.15g\n", ((double)mcsSleep)/1000.0);
1181 //mcsCurFrameEnd = timerGetMicroSeconds();
1182 //fprintf(stderr, "1:few=%d; cfe=%d; few-cfe=%.15g\n", (int)mcsFrameEndWanted, (int)mcsCurFrameEnd, ((double)(mcsInFrame-(mcsFrameEndWanted-mcsCurFrameEnd)))/1000);
1183 mcsFrameEndWanted
+= mcsInFrame
;
1185 fprintf(stderr
, "DESYNC! (%d)\n", mcsSleep
);
1186 //mcsFrameEndWanted = timerGetMicroSeconds()+mcsInFrame;
1187 mcsFrameEndWanted
= mcsCurFrameEnd
+mcsInFrame
;
1193 mcsFrameEndWanted
= timerGetMicroSeconds()+mcsInFrame
;
1199 ////////////////////////////////////////////////////////////////////////////////
1200 static void cprintLibFDC (int type
, const char *msg
) {
1202 case LIBFDC_MSG_DEBUG
: cprintf("\3LIBFDC[debug]: %s\n", msg
); break;
1203 case LIBFDC_MSG_WARNING
: cprintf("\2LIBFDC[warn]: %s\n", msg
); break;
1204 case LIBFDC_MSG_ERROR
: cprintf("\4LIBFDC[error]: %s\n", msg
); break;
1205 default: cprintf("\3LIBFDC[???]: %s\n", msg
); break; // the thing that should not be
1210 ////////////////////////////////////////////////////////////////////////////////
1211 int main (int argc
, char *argv
[]) {
1212 Jim_SetAllowUnsafeExtensions(1);
1217 libfdcMessageCB
= &cprintLibFDC
;
1219 processOptions(argc
, argv
, 1);
1221 if (libspectrum_init() != LIBSPECTRUM_ERROR_NONE
) {
1222 fprintf(stderr
, "FATAL: can't init libspectrum!\n");
1225 cprintf("===================================\n");
1226 cprintf("using libspectrum v%s\n", libspectrum_version());
1228 switch (timerInit()) {
1229 case TIMER_ERROR
: abort();
1230 case TIMER_HPET
: break;
1233 "\2WARNING: please, set your clock source to HPET!\n"
1234 "you can do this by issuing the following command:\n"
1236 "\1sudo echo 'hpet' > /sys/devices/system/clocksource/clocksource0/current_clocksource\n"
1238 "\2this is not a critical issue, but hpet clock will\n"
1239 "\2give you slightly better emulation.\n"
1244 if (!Jim_GetAllowUnsafeExtensions()) {
1245 cprintf("\2WARNING: Disabled unsafe Tcl extensions.\n");
1247 cprintf("Unsafe Tcl extensions enabled ('--no-unsafe-tcl' to disable).\n");
1254 jimEvalFile("init/init.tcl", 0);
1256 jimEvalFile("init/roms.tcl", 0);
1257 emuSetModel(zxModel
, 1);
1262 jimEvalFile("init/concmd.tcl", 0);
1267 frameCB
= zxFrameCB
;
1269 mouseCB
= zxMouseCB
;
1270 mouseButtonCB
= zxMouseButtonCB
;
1272 //jimEvalFile("init/widgets/init.tcl", 1);
1274 jimEvalFile("autoexec.tcl", 1);
1275 emuSetModel(zxModel
, 1); // in case something vital was changed (like "opense on")
1277 processOptions(argc
, argv
, 0);
1279 jimEvalFile1("./.zxemutrc.tcl");
1280 jimEvalFile1("./.zxemut.tcl");
1281 jimEvalFile1("./zxemutrc.tcl");
1282 jimEvalFile1("./zxemut.tcl");
1285 if (sndSampleRate
< 0 && initSound() != 0) {
1286 fprintf(stderr
, "WARNING: can't initialize sound!\n");
1287 cprintf("\4WARNING: can't initialize sound!\n");
1290 sndAllowUseToggle
= 0;
1291 if (sndSampleRate
<= 0) cprintf("NOSOUND mode");
1293 frameCB
= zxFrameCB
;
1295 mouseCB
= zxMouseCB
;
1296 mouseButtonCB
= zxMouseButtonCB
;
1300 if (sndSampleRate
> 0) {
1301 while (processEvents(0) >= 0) {
1304 // optMaxSpeed implies sndUsing==0
1305 if (!optMaxSpeed
&& optSpeed
== 100 && (debuggerActive
|| optPaused
)) soundWrite();
1311 sndAllowUseToggle
= 0;
1315 unixsock_stop_server();
1318 difDestroy(zxDiskIf
);
1321 if (condumpfl
!= NULL
) fclose(condumpfl
);