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
26 * Implementation notes
28 * The A2 had 3 display modes: text, lores, and hires. Text was 40x24, and it
29 * disabled color in the TV. Lores gave you 40x48 graphics blocks, using the
30 * same memory as the text screen. Each could be one of 16 colors. Hires gave
31 * you 280x192 pixels. Odd pixels were blue or purple, and even pixels were
32 * orange or green depending on the setting of the high bit in each byte.
34 * The graphics modes could also have 4 lines of text at the bottom. This was
35 * fairly unreadable if you had a color monitor.
37 * Each mode had 2 different screens using different memory space. In hires
38 * mode this was sometimes used for double buffering, but more often the lower
39 * screen was full of code/data and the upper screen was used for display, so
40 * you got random garbage on the screen.
42 * The text font is based on X's standard 6x10 font, with a few tweaks like
43 * putting a slash across the zero.
45 * To use this, you'll call apple2(display, window, duration,
46 * controller) where the function controller defines what will happen.
47 * See bsod.c and apple2-main.c for example controllers. The
48 * controller function gets called whenever the machine ready to start
49 * something new. By setting sim->printing or sim->typing, it'll be
50 * busy for some time spitting characters out one at a time. By
51 * setting *next_actiontime+=X.X, it'll pause and just update the screen
52 * for that long before calling the controller function again.
54 * By setting stepno to A2CONTROLLER_DONE, the loop will end. It will also end
55 * after the time specified by the delay parameter. In either case, it calls
56 * the controller with stepno==A2CONTROLLER_FREE to allow it to release any
59 * The void* apple2_sim_t::controller_data is for the use of the controller.
60 * It will be initialize to NULL, and the controller can store its own state
66 a2_scroll(apple2_state_t *st)
69 st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off cursor */
71 for (i=0; i<23; i++) {
72 memcpy(st->textlines[i],st->textlines[i+1],40);
74 memset(st->textlines[23],0xe0,40);
78 a2_printc_1(apple2_state_t *st, char c, int scroll_p)
80 st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */
82 if (c == '\n') /* ^J == NL */
95 else if (c == 014) /* ^L == CLS, Home */
100 else if (c == '\t') /* ^I == tab */
102 a2_goto(st, st->cursy, (st->cursx+8)&~7);
104 else if (c == 010) /* ^H == backspace */
106 st->textlines[st->cursy][st->cursx]=0xe0;
107 a2_goto(st, st->cursy, st->cursx-1);
109 else if (c == '\r') /* ^M == CR */
115 st->textlines[st->cursy][st->cursx]=c ^ 0xc0;
128 st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
132 a2_printc(apple2_state_t *st, char c)
134 a2_printc_1(st, c, 1);
138 a2_printc_noscroll(apple2_state_t *st, char c)
140 a2_printc_1(st, c, 0);
145 a2_prints(apple2_state_t *st, char *s)
147 while (*s) a2_printc(st, *s++);
151 a2_goto(apple2_state_t *st, int r, int c)
155 st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */
158 st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
162 a2_cls(apple2_state_t *st)
165 for (i=0; i<24; i++) {
166 memset(st->textlines[i],0xe0,40);
171 a2_clear_gr(apple2_state_t *st)
174 for (i=0; i<24; i++) {
175 memset(st->textlines[i],0x00,40);
180 a2_clear_hgr(apple2_state_t *st)
183 for (i=0; i<192; i++) {
184 memset(st->hireslines[i],0,40);
189 a2_invalidate(apple2_state_t *st)
194 a2_poke(apple2_state_t *st, int addr, int val)
197 if (addr>=0x400 && addr<0x800) {
199 int row=((addr&0x380)/0x80) + ((addr&0x7f)/0x28)*8;
200 int col=(addr&0x7f)%0x28;
201 if (row<24 && col<40) {
202 st->textlines[row][col]=val;
203 if (!(st->gr_mode&(A2_GR_HIRES)) ||
204 (!(st->gr_mode&(A2_GR_FULL)) && row>=20)) {
208 else if (addr>=0x2000 && addr<0x4000) {
209 int row=(((addr&0x1c00) / 0x400) * 1 +
210 ((addr&0x0380) / 0x80) * 8 +
211 ((addr&0x0078) / 0x28) * 64);
212 int col=((addr&0x07f)%0x28);
213 if (row<192 && col<40) {
214 st->hireslines[row][col]=val;
215 if (st->gr_mode&A2_GR_HIRES) {
222 a2_hplot(apple2_state_t *st, int hcolor, int x, int y)
226 highbit=((hcolor<<5)&0x80) ^ 0x80; /* capture bit 2 into bit 7 */
228 if (y<0 || y>=192 || x<0 || x>=280) return;
230 for (run=0; run<2 && x<280; run++) {
231 unsigned char *vidbyte = &st->hireslines[y][x/7];
232 unsigned char whichbit=1<<(x%7);
235 *vidbyte = (*vidbyte & 0x7f) | highbit;
237 /* use either bit 0 or 1 of hcolor for odd or even pixels */
238 masked_bit = (hcolor>>(1-(x&1)))&1;
240 /* Set whichbit to 1 or 0 depending on color */
241 *vidbyte = (*vidbyte & ~whichbit) | (masked_bit ? whichbit : 0);
248 a2_hline(apple2_state_t *st, int hcolor, int x1, int y1, int x2, int y2)
250 int dx,dy,incx,incy,x,y,balance;
252 /* Bresenham's line drawing algorithm */
276 a2_hplot(st, hcolor, x, y);
284 a2_hplot(st, hcolor, x, y);
290 a2_hplot(st, hcolor, x, y);
298 a2_hplot(st, hcolor, x, y);
303 a2_plot(apple2_state_t *st, int color, int x, int y)
308 if (x<0 || x>=40 || y<0 || y>=48) return;
310 byte=st->textlines[textrow][x];
312 byte = (byte&0xf0) | (color&0x0f);
314 byte = (byte&0x0f) | ((color&0x0f)<<4);
316 st->textlines[textrow][x]=byte;
320 a2_display_image_loading(apple2_state_t *st, unsigned char *image,
324 When loading images,it would normally just load the big binary
325 dump into screen memory while you watched. Because of the way
326 screen memory was laid out, it wouldn't load from the top down,
327 but in a funny interleaved way. You should call this with lineno
328 increasing from 0 thru 191 over a period of a few seconds.
331 int row=(((lineno / 24) % 8) * 1 +
332 ((lineno / 3 ) % 8) * 8 +
333 ((lineno / 1 ) % 3) * 64);
335 memcpy (st->hireslines[row], &image[row * 40], 40);
339 Simulate plausible initial memory contents for running a program.
342 a2_init_memory_active(apple2_sim_t *sim)
346 apple2_state_t *st=sim->st;
348 while (addr<0x4000) {
351 switch (random()%4) {
355 for (i=0; i<n && addr<0x4000; i++) {
356 unsigned char rb=((random()%6==0 ? 0 : random()%16) |
357 ((random()%5==0 ? 0 : random()%16)<<4));
358 a2_poke(st, addr++, rb);
363 /* Simulate shapes stored in memory. We use the font since we have it.
364 Unreadable, since rows of each character are stored in consecutive
365 bytes. It was typical to store each of the 7 possible shifts of
366 bitmaps, for fastest blitting to the screen. */
367 x=random()%(sim->text_im->width);
368 for (i=0; i<100; i++) {
369 for (y=0; y<8; y++) {
371 for (j=0; j<8; j++) {
372 c |= XGetPixel(sim->text_im, (x+j)%sim->text_im->width, y)<<j;
374 a2_poke(st, addr++, c);
376 x=(x+1)%(sim->text_im->width);
383 for (i=0; i<n && addr<0x4000; i++) {
384 a2_poke(st, addr++, 0);
394 #if 1 /* jwz: since MacOS doesn't have "6x10", I dumped this font to an XBM...
397 #include "images/apple2font.xbm"
400 a2_make_font(apple2_sim_t *sim)
402 Pixmap text_pm = XCreatePixmapFromBitmapData (sim->dpy, sim->window,
403 (char *) apple2_font_bits,
407 if (apple2_font_width != 64*7) abort();
408 if (apple2_font_height != 8) abort();
409 sim->text_im = XGetImage(sim->dpy, text_pm, 0, 0,
410 apple2_font_width, apple2_font_height,
412 XFreePixmap(sim->dpy, text_pm);
417 /* This table lists fixes for characters that differ from the standard 6x10
418 font. Each encodes a pixel, as (charindex*7 + x) + (y<<10) + (value<<15)
419 where value is 0 for white and 1 for black. */
420 static const unsigned short a2_fixfont[] = {
421 /* Fix $ */ 0x8421, 0x941d,
422 /* Fix % */ 0x8024, 0x0028, 0x8425, 0x0426, 0x0825, 0x1027, 0x1426, 0x9427,
424 /* Fix * */ 0x8049, 0x8449, 0x8849, 0x0c47, 0x0c48, 0x0c4a, 0x0c4b, 0x9049,
426 /* Fix , */ 0x9057, 0x1458, 0x9856, 0x1857, 0x1c56,
427 /* Fix . */ 0x1465, 0x1864, 0x1866, 0x1c65,
428 /* Fix / */ 0x006e, 0x186a,
429 /* Fix 0 */ 0x8874, 0x8c73, 0x9072,
430 /* Fix 1 */ 0x0878, 0x1878, 0x187c,
431 /* Fix 5 */ 0x8895, 0x0c94, 0x0c95,
432 /* Fix 6 */ 0x809f, 0x8c9c, 0x109c,
433 /* Fix 7 */ 0x8ca4, 0x0ca5, 0x90a3, 0x10a4,
434 /* Fix 9 */ 0x08b3, 0x8cb3, 0x98b0,
435 /* Fix : */ 0x04b9, 0x08b8, 0x08ba, 0x0cb9, 0x90b9, 0x14b9, 0x18b8, 0x18b9,
437 /* Fix ; */ 0x04c0, 0x08bf, 0x08c1, 0x0cc0, 0x90c0, 0x14c1, 0x98bf, 0x18c0,
439 /* Fix < */ 0x80c8, 0x00c9, 0x84c7, 0x04c8, 0x88c6, 0x08c7, 0x8cc5, 0x0cc6,
441 0x94c7, 0x14c8, 0x98c8, 0x18c9,
442 /* Fix > */ 0x80d3, 0x00d4, 0x84d4, 0x04d5, 0x88d5, 0x08d6, 0x8cd6, 0x0cd7,
444 0x94d4, 0x14d5, 0x98d3, 0x18d4,
445 /* Fix @ */ 0x88e3, 0x08e4, 0x8ce4, 0x98e5,
446 /* Fix B */ 0x84ef, 0x04f0, 0x88ef, 0x08f0, 0x8cef, 0x90ef, 0x10f0, 0x94ef,
448 /* Fix D */ 0x84fd, 0x04fe, 0x88fd, 0x08fe, 0x8cfd, 0x0cfe, 0x90fd, 0x10fe,
450 /* Fix G */ 0x8116, 0x0516, 0x9916,
451 /* Fix J */ 0x0129, 0x012a, 0x052a, 0x852b, 0x092a, 0x892b, 0x0d2a, 0x8d2b,
453 0x152a, 0x952b, 0x992a,
454 /* Fix M */ 0x853d, 0x853f, 0x093d, 0x893e, 0x093f,
455 /* Fix Q */ 0x915a, 0x155a, 0x955b, 0x155c, 0x195b, 0x995c, 0x1d5c,
456 /* Fix V */ 0x8d7b, 0x0d7c, 0x0d7e, 0x8d7f, 0x917b, 0x117c, 0x117e, 0x917f,
457 /* Fix [ */ 0x819e, 0x81a2, 0x859e, 0x899e, 0x8d9e, 0x919e, 0x959e, 0x999e,
459 /* Fix \ */ 0x01a5, 0x19a9,
460 /* Fix ] */ 0x81ac, 0x81b0, 0x85b0, 0x89b0, 0x8db0, 0x91b0, 0x95b0, 0x99ac,
462 /* Fix ^ */ 0x01b5, 0x05b4, 0x05b6, 0x09b3, 0x89b5, 0x09b7, 0x8db4, 0x8db6,
464 /* Fix _ */ 0x9db9, 0x9dbf,
469 a2_make_font(apple2_sim_t *sim)
472 Generate the font. It used a 5x7 font which looks a lot like the standard X
473 6x10 font, with a few differences. So we render up all the uppercase
474 letters of 6x10, and make a few tweaks (like putting a slash across the
475 zero) according to fixfont.
479 const char *def_font="6x10";
485 font = XLoadQueryFont (sim->dpy, def_font);
487 fprintf(stderr, "%s: can't load font %s\n", progname, def_font);
491 text_pm=XCreatePixmap(sim->dpy, sim->window, 64*7, 8, 1);
493 memset(&gcv, 0, sizeof(gcv));
497 gc=XCreateGC(sim->dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv);
499 XSetForeground(sim->dpy, gc, 0);
500 XFillRectangle(sim->dpy, text_pm, gc, 0, 0, 64*7, 8);
501 XSetForeground(sim->dpy, gc, 1);
502 for (i=0; i<64; i++) {
508 XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
510 XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
515 for (i=0; a2_fixfont[i]; i++) {
516 XSetForeground (sim->dpy, gc, (a2_fixfont[i]>>15)&1);
517 XDrawPoint(sim->dpy, text_pm, gc, a2_fixfont[i]&0x3ff,
518 (a2_fixfont[i]>>10)&0xf);
520 XWriteBitmapFile(sim->dpy, "/tmp/a2font.xbm", text_pm, 64*7, 8, -1, -1);
523 sim->text_im = XGetImage(sim->dpy, text_pm, 0, 0, 64*7, 8, ~0L, ZPixmap);
524 XFreeGC(sim->dpy, gc);
525 XFreePixmap(sim->dpy, text_pm);
527 for (i=0; a2_fixfont[i]; i++) {
528 XPutPixel(sim->text_im, a2_fixfont[i]&0x3ff,
529 (a2_fixfont[i]>>10)&0xf,
530 (a2_fixfont[i]>>15)&1);
537 apple2_start(Display *dpy, Window window, int delay,
538 void (*controller)(apple2_sim_t *sim,
540 double *next_actiontime))
544 sim=(apple2_sim_t *)calloc(1,sizeof(apple2_sim_t));
546 sim->window = window;
548 sim->controller = controller;
550 sim->st = (apple2_state_t *)calloc(1,sizeof(apple2_state_t));
551 sim->dec = analogtv_allocate(dpy, window);
552 sim->inp = analogtv_input_allocate();
554 sim->reception.input = sim->inp;
555 sim->reception.level = 1.0;
559 if (random()%4==0 && !sim->dec->use_cmap && sim->dec->use_color && sim->dec->visbits>=8) {
560 sim->dec->flutter_tint=1;
562 else if (random()%3==0) {
563 sim->dec->flutter_horiz_desync=1;
565 sim->typing_rate = 1.0;
567 analogtv_set_defaults(sim->dec, "");
568 sim->dec->squish_control=0.05;
569 analogtv_setup_sync(sim->inp, 1, 0);
575 a2_goto(sim->st,23,0);
577 if (random()%2==0) sim->basetime_tv.tv_sec -= 1; /* random blink phase */
578 sim->next_actiontime=0.0;
581 sim->next_actiontime=sim->curtime;
582 sim->controller (sim, &sim->stepno, &sim->next_actiontime);
584 # ifdef GETTIMEOFDAY_TWO_ARGS
585 gettimeofday(&sim->basetime_tv, NULL);
587 gettimeofday(&sim->basetime_tv);
594 apple2_one_frame (apple2_sim_t *sim)
600 if (sim->stepno==A2CONTROLLER_DONE)
601 goto DONE; /* when caller says we're done, be done, dammit! */
604 struct timeval curtime_tv;
605 # ifdef GETTIMEOFDAY_TWO_ARGS
607 gettimeofday(&curtime_tv, &tzp);
609 gettimeofday(&curtime_tv);
611 sim->curtime=(curtime_tv.tv_sec - sim->basetime_tv.tv_sec) +
612 0.000001*(curtime_tv.tv_usec - sim->basetime_tv.tv_usec);
613 if (sim->curtime > sim->dec->powerup)
614 sim->dec->powerup=sim->curtime;
617 blinkphase=sim->curtime/0.8;
619 /* The blinking rate was controlled by 555 timer with a resistor/capacitor
620 time constant. Because the capacitor was electrolytic, the flash rate
621 varied somewhat between machines. I'm guessing 1.6 seconds/cycle was
622 reasonable. (I soldered a resistor in mine to make it blink faster.) */
624 sim->st->blink=((int)blinkphase)&1;
625 if (sim->st->blink!=i && !(sim->st->gr_mode&A2_GR_FULL)) {
627 /* For every row with blinking text, set the changed flag. This basically
628 works great except with random screen garbage in text mode, when we
629 end up redrawing the whole screen every second */
631 for (row=(sim->st->gr_mode ? 20 : 0); row<24; row++) {
632 for (col=0; col<40; col++) {
633 int c=sim->st->textlines[row][col];
634 if ((c & 0xc0) == 0x40) {
645 if (sim->curtime >= sim->delay)
646 sim->stepno = A2CONTROLLER_DONE;
650 while (*sim->printing) {
651 if (*sim->printing=='\001') { /* pause */
655 else if (*sim->printing=='\n') {
656 a2_printc(sim->st,*sim->printing);
662 a2_printc(sim->st,*sim->printing);
666 if (!*sim->printing) sim->printing=NULL;
668 else if (sim->curtime >= sim->next_actiontime) {
672 /* If we're in the midst of typing a string, emit a character with
680 a2_printc(sim->st, c);
681 if (c=='\r' || c=='\n') {
682 sim->next_actiontime = sim->curtime;
685 sim->next_actiontime = sim->curtime + 0.1;
688 sim->next_actiontime = (sim->curtime +
689 (((random()%1000)*0.001 + 0.3) *
694 sim->next_actiontime = sim->curtime;
696 sim->controller (sim, &sim->stepno, &sim->next_actiontime);
698 if (sim->stepno==A2CONTROLLER_DONE) {
701 sim->stepno=A2CONTROLLER_FREE;
702 sim->controller (sim, &sim->stepno, &sim->next_actiontime);
704 XClearWindow(sim->dpy, sim->window);
707 /* This is from a2_make_font */
708 free(sim->text_im->data);
709 sim->text_im->data = 0;
710 XDestroyImage(sim->text_im);
713 analogtv_release(sim->dec);
725 analogtv_setup_sync(sim->inp, sim->st->gr_mode? 1 : 0, 0);
726 analogtv_setup_frame(sim->dec);
728 for (textrow=0; textrow<24; textrow++) {
730 for (row=textrow*8; row<textrow*8+8; row++) {
732 /* First we generate the pattern that the video circuitry shifts out
733 of memory. It has a 14.something MHz dot clock, equal to 4 times
734 the color burst frequency. So each group of 4 bits defines a color.
735 Each character position, or byte in hires, defines 14 dots, so odd
736 and even bytes have different color spaces. So, pattern[0..600]
737 gets the dots for one scan line. */
739 signed char *pp=&sim->inp->signal[row+ANALOGTV_TOP+4][ANALOGTV_PIC_START+100];
741 if ((sim->st->gr_mode&A2_GR_HIRES) &&
742 (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
745 /* Emulate the mysterious pink line, due to a bit getting
746 stuck in a shift register between the end of the last
747 row and the beginning of this one. */
748 if ((sim->st->hireslines[row][0] & 0x80) &&
749 (sim->st->hireslines[row][39]&0x40)) {
750 pp[-1]=ANALOGTV_WHITE_LEVEL;
753 for (col=0; col<40; col++) {
754 unsigned char b=sim->st->hireslines[row][col];
755 int shift=(b&0x80)?0:1;
757 /* Each of the low 7 bits in hires mode corresponded to 2 dot
758 clocks, shifted by one if the high bit was set. */
759 for (i=0; i<7; i++) {
760 pp[shift+1] = pp[shift] = (((b>>i)&1)
761 ?ANALOGTV_WHITE_LEVEL
762 :ANALOGTV_BLACK_LEVEL);
767 else if ((sim->st->gr_mode&A2_GR_LORES) &&
768 (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
770 for (col=0; col<40; col++) {
771 unsigned char nib=((sim->st->textlines[textrow][col] >> (((row/4)&1)*4))
773 /* The low or high nybble was shifted out one bit at a time. */
774 for (i=0; i<14; i++) {
775 *pp = (((nib>>((col*14+i)&3))&1)
776 ?ANALOGTV_WHITE_LEVEL
777 :ANALOGTV_BLACK_LEVEL);
784 for (col=0; col<40; col++) {
786 int c=sim->st->textlines[textrow][col]&0xff;
787 /* hi bits control inverse/blink as follows:
792 rev=!(c&0x80) && (!(c&0x40) || sim->st->blink);
794 for (i=0; i<7; i++) {
795 unsigned long pix=XGetPixel(sim->text_im,
798 pp[1] = pp[2] = ((pix^rev)
799 ?ANALOGTV_WHITE_LEVEL
800 :ANALOGTV_BLACK_LEVEL);
807 analogtv_init_signal(sim->dec, 0.02);
808 analogtv_reception_update(&sim->reception);
809 analogtv_add_signal(sim->dec, &sim->reception);
810 analogtv_draw(sim->dec);
818 a2controller_test(apple2_sim_t *sim, int *stepno, double *next_actiontime)
824 a2_invalidate(sim->st);
826 For testing color rendering. The spec is:
834 6 med blue 20 207 253
835 7 lt blue 208 195 255
840 12 lt green 20 245 60
841 13 yellow 208 221 141
845 sim->st->gr_mode=A2_GR_LORES;
846 for (row=0; row<24; row++) {
847 for (col=0; col<40; col++) {
848 sim->st->textlines[row][col]=(row&15)*17;
851 *next_actiontime+=0.4;
856 if (sim->curtime > 10) *stepno=-1;