1 /* xscreensaver, Copyright (c) 1998-2010 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
11 * Apple ][ CRT simulator, by Trevor Blackwell <tlb@tlb.org>
12 * with additional work by Jamie Zawinski <jwz@jwz.org>
16 #include "screenhackI.h"
19 #ifdef HAVE_XSHM_EXTENSION
24 * Implementation notes
26 * The A2 had 3 display modes: text, lores, and hires. Text was 40x24, and it
27 * disabled color in the TV. Lores gave you 40x48 graphics blocks, using the
28 * same memory as the text screen. Each could be one of 16 colors. Hires gave
29 * you 280x192 pixels. Odd pixels were blue or purple, and even pixels were
30 * orange or green depending on the setting of the high bit in each byte.
32 * The graphics modes could also have 4 lines of text at the bottom. This was
33 * fairly unreadable if you had a color monitor.
35 * Each mode had 2 different screens using different memory space. In hires
36 * mode this was sometimes used for double buffering, but more often the lower
37 * screen was full of code/data and the upper screen was used for display, so
38 * you got random garbage on the screen.
40 * The text font is based on X's standard 6x10 font, with a few tweaks like
41 * putting a slash across the zero.
43 * To use this, you'll call apple2(display, window, duration,
44 * controller) where the function controller defines what will happen.
45 * See bsod.c and apple2-main.c for example controllers. The
46 * controller function gets called whenever the machine ready to start
47 * something new. By setting sim->printing or sim->typing, it'll be
48 * busy for some time spitting characters out one at a time. By
49 * setting *next_actiontime+=X.X, it'll pause and just update the screen
50 * for that long before calling the controller function again.
52 * By setting stepno to A2CONTROLLER_DONE, the loop will end. It will also end
53 * after the time specified by the delay parameter. In either case, it calls
54 * the controller with stepno==A2CONTROLLER_FREE to allow it to release any
57 * The void* apple2_sim_t::controller_data is for the use of the controller.
58 * It will be initialize to NULL, and the controller can store its own state
64 a2_scroll(apple2_state_t *st)
67 st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off cursor */
69 for (i=0; i<23; i++) {
70 memcpy(st->textlines[i],st->textlines[i+1],40);
72 memset(st->textlines[23],0xe0,40);
76 a2_printc_1(apple2_state_t *st, char c, int scroll_p)
78 st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */
80 if (c == '\n') /* ^J == NL */
93 else if (c == 014) /* ^L == CLS, Home */
98 else if (c == '\t') /* ^I == tab */
100 a2_goto(st, st->cursy, (st->cursx+8)&~7);
102 else if (c == 010) /* ^H == backspace */
104 st->textlines[st->cursy][st->cursx]=0xe0;
105 a2_goto(st, st->cursy, st->cursx-1);
107 else if (c == '\r') /* ^M == CR */
113 st->textlines[st->cursy][st->cursx]=c ^ 0xc0;
126 st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
130 a2_printc(apple2_state_t *st, char c)
132 a2_printc_1(st, c, 1);
136 a2_printc_noscroll(apple2_state_t *st, char c)
138 a2_printc_1(st, c, 0);
143 a2_prints(apple2_state_t *st, char *s)
145 while (*s) a2_printc(st, *s++);
149 a2_goto(apple2_state_t *st, int r, int c)
153 st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */
156 st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
160 a2_cls(apple2_state_t *st)
163 for (i=0; i<24; i++) {
164 memset(st->textlines[i],0xe0,40);
169 a2_clear_gr(apple2_state_t *st)
172 for (i=0; i<24; i++) {
173 memset(st->textlines[i],0x00,40);
178 a2_clear_hgr(apple2_state_t *st)
181 for (i=0; i<192; i++) {
182 memset(st->hireslines[i],0,40);
187 a2_invalidate(apple2_state_t *st)
192 a2_poke(apple2_state_t *st, int addr, int val)
195 if (addr>=0x400 && addr<0x800) {
197 int row=((addr&0x380)/0x80) + ((addr&0x7f)/0x28)*8;
198 int col=(addr&0x7f)%0x28;
199 if (row<24 && col<40) {
200 st->textlines[row][col]=val;
201 if (!(st->gr_mode&(A2_GR_HIRES)) ||
202 (!(st->gr_mode&(A2_GR_FULL)) && row>=20)) {
206 else if (addr>=0x2000 && addr<0x4000) {
207 int row=(((addr&0x1c00) / 0x400) * 1 +
208 ((addr&0x0380) / 0x80) * 8 +
209 ((addr&0x0078) / 0x28) * 64);
210 int col=((addr&0x07f)%0x28);
211 if (row<192 && col<40) {
212 st->hireslines[row][col]=val;
213 if (st->gr_mode&A2_GR_HIRES) {
220 a2_hplot(apple2_state_t *st, int hcolor, int x, int y)
224 highbit=((hcolor<<5)&0x80) ^ 0x80; /* capture bit 2 into bit 7 */
226 if (y<0 || y>=192 || x<0 || x>=280) return;
228 for (run=0; run<2 && x<280; run++) {
229 unsigned char *vidbyte = &st->hireslines[y][x/7];
230 unsigned char whichbit=1<<(x%7);
233 *vidbyte = (*vidbyte & 0x7f) | highbit;
235 /* use either bit 0 or 1 of hcolor for odd or even pixels */
236 masked_bit = (hcolor>>(1-(x&1)))&1;
238 /* Set whichbit to 1 or 0 depending on color */
239 *vidbyte = (*vidbyte & ~whichbit) | (masked_bit ? whichbit : 0);
246 a2_hline(apple2_state_t *st, int hcolor, int x1, int y1, int x2, int y2)
248 int dx,dy,incx,incy,x,y,balance;
250 /* Bresenham's line drawing algorithm */
274 a2_hplot(st, hcolor, x, y);
282 a2_hplot(st, hcolor, x, y);
288 a2_hplot(st, hcolor, x, y);
296 a2_hplot(st, hcolor, x, y);
301 a2_plot(apple2_state_t *st, int color, int x, int y)
306 if (x<0 || x>=40 || y<0 || y>=48) return;
308 byte=st->textlines[textrow][x];
310 byte = (byte&0xf0) | (color&0x0f);
312 byte = (byte&0x0f) | ((color&0x0f)<<4);
314 st->textlines[textrow][x]=byte;
318 a2_display_image_loading(apple2_state_t *st, unsigned char *image,
322 When loading images,it would normally just load the big binary
323 dump into screen memory while you watched. Because of the way
324 screen memory was laid out, it wouldn't load from the top down,
325 but in a funny interleaved way. You should call this with lineno
326 increasing from 0 thru 191 over a period of a few seconds.
329 int row=(((lineno / 24) % 8) * 1 +
330 ((lineno / 3 ) % 8) * 8 +
331 ((lineno / 1 ) % 3) * 64);
333 memcpy (st->hireslines[row], &image[row * 40], 40);
337 Simulate plausible initial memory contents for running a program.
340 a2_init_memory_active(apple2_sim_t *sim)
344 apple2_state_t *st=sim->st;
346 while (addr<0x4000) {
349 switch (random()%4) {
353 for (i=0; i<n && addr<0x4000; i++) {
354 unsigned char rb=((random()%6==0 ? 0 : random()%16) |
355 ((random()%5==0 ? 0 : random()%16)<<4));
356 a2_poke(st, addr++, rb);
361 /* Simulate shapes stored in memory. We use the font since we have it.
362 Unreadable, since rows of each character are stored in consecutive
363 bytes. It was typical to store each of the 7 possible shifts of
364 bitmaps, for fastest blitting to the screen. */
365 x=random()%(sim->text_im->width);
366 for (i=0; i<100; i++) {
367 for (y=0; y<8; y++) {
369 for (j=0; j<8; j++) {
370 c |= XGetPixel(sim->text_im, (x+j)%sim->text_im->width, y)<<j;
372 a2_poke(st, addr++, c);
374 x=(x+1)%(sim->text_im->width);
381 for (i=0; i<n && addr<0x4000; i++) {
382 a2_poke(st, addr++, 0);
392 #if 1 /* jwz: since MacOS doesn't have "6x10", I dumped this font to an XBM...
395 #include "images/apple2font.xbm"
398 a2_make_font(apple2_sim_t *sim)
400 Pixmap text_pm = XCreatePixmapFromBitmapData (sim->dpy, sim->window,
401 (char *) apple2_font_bits,
405 if (apple2_font_width != 64*7) abort();
406 if (apple2_font_height != 8) abort();
407 sim->text_im = XGetImage(sim->dpy, text_pm, 0, 0,
408 apple2_font_width, apple2_font_height,
410 XFreePixmap(sim->dpy, text_pm);
415 /* This table lists fixes for characters that differ from the standard 6x10
416 font. Each encodes a pixel, as (charindex*7 + x) + (y<<10) + (value<<15)
417 where value is 0 for white and 1 for black. */
418 static const unsigned short a2_fixfont[] = {
419 /* Fix $ */ 0x8421, 0x941d,
420 /* Fix % */ 0x8024, 0x0028, 0x8425, 0x0426, 0x0825, 0x1027, 0x1426, 0x9427,
422 /* Fix * */ 0x8049, 0x8449, 0x8849, 0x0c47, 0x0c48, 0x0c4a, 0x0c4b, 0x9049,
424 /* Fix , */ 0x9057, 0x1458, 0x9856, 0x1857, 0x1c56,
425 /* Fix . */ 0x1465, 0x1864, 0x1866, 0x1c65,
426 /* Fix / */ 0x006e, 0x186a,
427 /* Fix 0 */ 0x8874, 0x8c73, 0x9072,
428 /* Fix 1 */ 0x0878, 0x1878, 0x187c,
429 /* Fix 5 */ 0x8895, 0x0c94, 0x0c95,
430 /* Fix 6 */ 0x809f, 0x8c9c, 0x109c,
431 /* Fix 7 */ 0x8ca4, 0x0ca5, 0x90a3, 0x10a4,
432 /* Fix 9 */ 0x08b3, 0x8cb3, 0x98b0,
433 /* Fix : */ 0x04b9, 0x08b8, 0x08ba, 0x0cb9, 0x90b9, 0x14b9, 0x18b8, 0x18b9,
435 /* Fix ; */ 0x04c0, 0x08bf, 0x08c1, 0x0cc0, 0x90c0, 0x14c1, 0x98bf, 0x18c0,
437 /* Fix < */ 0x80c8, 0x00c9, 0x84c7, 0x04c8, 0x88c6, 0x08c7, 0x8cc5, 0x0cc6,
439 0x94c7, 0x14c8, 0x98c8, 0x18c9,
440 /* Fix > */ 0x80d3, 0x00d4, 0x84d4, 0x04d5, 0x88d5, 0x08d6, 0x8cd6, 0x0cd7,
442 0x94d4, 0x14d5, 0x98d3, 0x18d4,
443 /* Fix @ */ 0x88e3, 0x08e4, 0x8ce4, 0x98e5,
444 /* Fix B */ 0x84ef, 0x04f0, 0x88ef, 0x08f0, 0x8cef, 0x90ef, 0x10f0, 0x94ef,
446 /* Fix D */ 0x84fd, 0x04fe, 0x88fd, 0x08fe, 0x8cfd, 0x0cfe, 0x90fd, 0x10fe,
448 /* Fix G */ 0x8116, 0x0516, 0x9916,
449 /* Fix J */ 0x0129, 0x012a, 0x052a, 0x852b, 0x092a, 0x892b, 0x0d2a, 0x8d2b,
451 0x152a, 0x952b, 0x992a,
452 /* Fix M */ 0x853d, 0x853f, 0x093d, 0x893e, 0x093f,
453 /* Fix Q */ 0x915a, 0x155a, 0x955b, 0x155c, 0x195b, 0x995c, 0x1d5c,
454 /* Fix V */ 0x8d7b, 0x0d7c, 0x0d7e, 0x8d7f, 0x917b, 0x117c, 0x117e, 0x917f,
455 /* Fix [ */ 0x819e, 0x81a2, 0x859e, 0x899e, 0x8d9e, 0x919e, 0x959e, 0x999e,
457 /* Fix \ */ 0x01a5, 0x19a9,
458 /* Fix ] */ 0x81ac, 0x81b0, 0x85b0, 0x89b0, 0x8db0, 0x91b0, 0x95b0, 0x99ac,
460 /* Fix ^ */ 0x01b5, 0x05b4, 0x05b6, 0x09b3, 0x89b5, 0x09b7, 0x8db4, 0x8db6,
462 /* Fix _ */ 0x9db9, 0x9dbf,
467 a2_make_font(apple2_sim_t *sim)
470 Generate the font. It used a 5x7 font which looks a lot like the standard X
471 6x10 font, with a few differences. So we render up all the uppercase
472 letters of 6x10, and make a few tweaks (like putting a slash across the
473 zero) according to fixfont.
477 const char *def_font="6x10";
483 font = XLoadQueryFont (sim->dpy, def_font);
485 fprintf(stderr, "%s: can't load font %s\n", progname, def_font);
489 text_pm=XCreatePixmap(sim->dpy, sim->window, 64*7, 8, 1);
491 memset(&gcv, 0, sizeof(gcv));
495 gc=XCreateGC(sim->dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv);
497 XSetForeground(sim->dpy, gc, 0);
498 XFillRectangle(sim->dpy, text_pm, gc, 0, 0, 64*7, 8);
499 XSetForeground(sim->dpy, gc, 1);
500 for (i=0; i<64; i++) {
506 XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
508 XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
513 for (i=0; a2_fixfont[i]; i++) {
514 XSetForeground (sim->dpy, gc, (a2_fixfont[i]>>15)&1);
515 XDrawPoint(sim->dpy, text_pm, gc, a2_fixfont[i]&0x3ff,
516 (a2_fixfont[i]>>10)&0xf);
518 XWriteBitmapFile(sim->dpy, "/tmp/a2font.xbm", text_pm, 64*7, 8, -1, -1);
521 sim->text_im = XGetImage(sim->dpy, text_pm, 0, 0, 64*7, 8, ~0L, ZPixmap);
522 XFreeGC(sim->dpy, gc);
523 XFreePixmap(sim->dpy, text_pm);
525 for (i=0; a2_fixfont[i]; i++) {
526 XPutPixel(sim->text_im, a2_fixfont[i]&0x3ff,
527 (a2_fixfont[i]>>10)&0xf,
528 (a2_fixfont[i]>>15)&1);
535 apple2_start(Display *dpy, Window window, int delay,
536 void (*controller)(apple2_sim_t *sim,
538 double *next_actiontime))
542 sim=(apple2_sim_t *)calloc(1,sizeof(apple2_sim_t));
544 sim->window = window;
546 sim->controller = controller;
548 sim->st = (apple2_state_t *)calloc(1,sizeof(apple2_state_t));
549 sim->dec = analogtv_allocate(dpy, window);
550 sim->inp = analogtv_input_allocate();
552 sim->reception.input = sim->inp;
553 sim->reception.level = 1.0;
557 if (random()%4==0 && !sim->dec->use_cmap && sim->dec->use_color && sim->dec->visbits>=8) {
558 sim->dec->flutter_tint=1;
560 else if (random()%3==0) {
561 sim->dec->flutter_horiz_desync=1;
563 sim->typing_rate = 1.0;
565 analogtv_set_defaults(sim->dec, "");
566 sim->dec->squish_control=0.05;
567 analogtv_setup_sync(sim->inp, 1, 0);
573 a2_goto(sim->st,23,0);
575 if (random()%2==0) sim->basetime_tv.tv_sec -= 1; /* random blink phase */
576 sim->next_actiontime=0.0;
579 sim->next_actiontime=sim->curtime;
580 sim->controller (sim, &sim->stepno, &sim->next_actiontime);
582 # ifdef GETTIMEOFDAY_TWO_ARGS
583 gettimeofday(&sim->basetime_tv, NULL);
585 gettimeofday(&sim->basetime_tv);
592 apple2_one_frame (apple2_sim_t *sim)
598 if (sim->stepno==A2CONTROLLER_DONE)
599 goto DONE; /* when caller says we're done, be done, dammit! */
602 struct timeval curtime_tv;
603 # ifdef GETTIMEOFDAY_TWO_ARGS
605 gettimeofday(&curtime_tv, &tzp);
607 gettimeofday(&curtime_tv);
609 sim->curtime=(curtime_tv.tv_sec - sim->basetime_tv.tv_sec) +
610 0.000001*(curtime_tv.tv_usec - sim->basetime_tv.tv_usec);
611 if (sim->curtime > sim->dec->powerup)
612 sim->dec->powerup=sim->curtime;
615 blinkphase=sim->curtime/0.8;
617 /* The blinking rate was controlled by 555 timer with a resistor/capacitor
618 time constant. Because the capacitor was electrolytic, the flash rate
619 varied somewhat between machines. I'm guessing 1.6 seconds/cycle was
620 reasonable. (I soldered a resistor in mine to make it blink faster.) */
622 sim->st->blink=((int)blinkphase)&1;
623 if (sim->st->blink!=i && !(sim->st->gr_mode&A2_GR_FULL)) {
625 /* For every row with blinking text, set the changed flag. This basically
626 works great except with random screen garbage in text mode, when we
627 end up redrawing the whole screen every second */
629 for (row=(sim->st->gr_mode ? 20 : 0); row<24; row++) {
630 for (col=0; col<40; col++) {
631 int c=sim->st->textlines[row][col];
632 if ((c & 0xc0) == 0x40) {
643 if (sim->curtime >= sim->delay)
644 sim->stepno = A2CONTROLLER_DONE;
648 while (*sim->printing) {
649 if (*sim->printing=='\001') { /* pause */
653 else if (*sim->printing=='\n') {
654 a2_printc(sim->st,*sim->printing);
660 a2_printc(sim->st,*sim->printing);
664 if (!*sim->printing) sim->printing=NULL;
666 else if (sim->curtime >= sim->next_actiontime) {
670 /* If we're in the midst of typing a string, emit a character with
678 a2_printc(sim->st, c);
679 if (c=='\r' || c=='\n') {
680 sim->next_actiontime = sim->curtime;
683 sim->next_actiontime = sim->curtime + 0.1;
686 sim->next_actiontime = (sim->curtime +
687 (((random()%1000)*0.001 + 0.3) *
692 sim->next_actiontime = sim->curtime;
694 sim->controller (sim, &sim->stepno, &sim->next_actiontime);
696 if (sim->stepno==A2CONTROLLER_DONE) {
699 sim->stepno=A2CONTROLLER_FREE;
700 sim->controller (sim, &sim->stepno, &sim->next_actiontime);
701 /* if stepno is changed, return 1 */
702 if (sim->stepno != A2CONTROLLER_FREE)
705 XClearWindow(sim->dpy, sim->window);
708 /* This is from a2_make_font */
709 free(sim->text_im->data);
710 sim->text_im->data = 0;
711 XDestroyImage(sim->text_im);
714 analogtv_release(sim->dec);
726 analogtv_setup_sync(sim->inp, sim->st->gr_mode? 1 : 0, 0);
727 analogtv_setup_frame(sim->dec);
729 for (textrow=0; textrow<24; textrow++) {
731 for (row=textrow*8; row<textrow*8+8; row++) {
733 /* First we generate the pattern that the video circuitry shifts out
734 of memory. It has a 14.something MHz dot clock, equal to 4 times
735 the color burst frequency. So each group of 4 bits defines a color.
736 Each character position, or byte in hires, defines 14 dots, so odd
737 and even bytes have different color spaces. So, pattern[0..600]
738 gets the dots for one scan line. */
740 signed char *pp=&sim->inp->signal[row+ANALOGTV_TOP+4][ANALOGTV_PIC_START+100];
742 if ((sim->st->gr_mode&A2_GR_HIRES) &&
743 (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
746 /* Emulate the mysterious pink line, due to a bit getting
747 stuck in a shift register between the end of the last
748 row and the beginning of this one. */
749 if ((sim->st->hireslines[row][0] & 0x80) &&
750 (sim->st->hireslines[row][39]&0x40)) {
751 pp[-1]=ANALOGTV_WHITE_LEVEL;
754 for (col=0; col<40; col++) {
755 unsigned char b=sim->st->hireslines[row][col];
756 int shift=(b&0x80)?0:1;
758 /* Each of the low 7 bits in hires mode corresponded to 2 dot
759 clocks, shifted by one if the high bit was set. */
760 for (i=0; i<7; i++) {
761 pp[shift+1] = pp[shift] = (((b>>i)&1)
762 ?ANALOGTV_WHITE_LEVEL
763 :ANALOGTV_BLACK_LEVEL);
768 else if ((sim->st->gr_mode&A2_GR_LORES) &&
769 (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
771 for (col=0; col<40; col++) {
772 unsigned char nib=((sim->st->textlines[textrow][col] >> (((row/4)&1)*4))
774 /* The low or high nybble was shifted out one bit at a time. */
775 for (i=0; i<14; i++) {
776 *pp = (((nib>>((col*14+i)&3))&1)
777 ?ANALOGTV_WHITE_LEVEL
778 :ANALOGTV_BLACK_LEVEL);
785 for (col=0; col<40; col++) {
787 int c=sim->st->textlines[textrow][col]&0xff;
788 /* hi bits control inverse/blink as follows:
793 rev=!(c&0x80) && (!(c&0x40) || sim->st->blink);
795 for (i=0; i<7; i++) {
796 unsigned long pix=XGetPixel(sim->text_im,
799 pp[1] = pp[2] = ((pix^rev)
800 ?ANALOGTV_WHITE_LEVEL
801 :ANALOGTV_BLACK_LEVEL);
808 analogtv_reception_update(&sim->reception);
810 const analogtv_reception *rec = &sim->reception;
811 analogtv_draw(sim->dec, 0.02, &rec, 1);
820 a2controller_test(apple2_sim_t *sim, int *stepno, double *next_actiontime)
826 a2_invalidate(sim->st);
828 For testing color rendering. The spec is:
836 6 med blue 20 207 253
837 7 lt blue 208 195 255
842 12 lt green 20 245 60
843 13 yellow 208 221 141
847 sim->st->gr_mode=A2_GR_LORES;
848 for (row=0; row<24; row++) {
849 for (col=0; col<40; col++) {
850 sim->st->textlines[row][col]=(row&15)*17;
853 *next_actiontime+=0.4;
858 if (sim->curtime > 10) *stepno=-1;