/* xscreensaver, Copyright (c) 1998-2010 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. * * Apple ][ CRT simulator, by Trevor Blackwell * with additional work by Jamie Zawinski */ #include #include "screenhackI.h" #include "apple2.h" #include "ximage-loader.h" #ifdef HAVE_XSHM_EXTENSION #include "xshm.h" #endif /* * Implementation notes * * The A2 had 3 display modes: text, lores, and hires. Text was 40x24, and it * disabled color in the TV. Lores gave you 40x48 graphics blocks, using the * same memory as the text screen. Each could be one of 16 colors. Hires gave * you 280x192 pixels. Odd pixels were blue or purple, and even pixels were * orange or green depending on the setting of the high bit in each byte. * * The graphics modes could also have 4 lines of text at the bottom. This was * fairly unreadable if you had a color monitor. * * Each mode had 2 different screens using different memory space. In hires * mode this was sometimes used for double buffering, but more often the lower * screen was full of code/data and the upper screen was used for display, so * you got random garbage on the screen. * * The text font is based on X's standard 6x10 font, with a few tweaks like * putting a slash across the zero. * * To use this, you'll call apple2(display, window, duration, * controller) where the function controller defines what will happen. * See bsod.c and apple2-main.c for example controllers. The * controller function gets called whenever the machine ready to start * something new. By setting sim->printing or sim->typing, it'll be * busy for some time spitting characters out one at a time. By * setting *next_actiontime+=X.X, it'll pause and just update the screen * for that long before calling the controller function again. * * By setting stepno to A2CONTROLLER_DONE, the loop will end. It will also end * after the time specified by the delay parameter. In either case, it calls * the controller with stepno==A2CONTROLLER_FREE to allow it to release any * memory. * * The void* apple2_sim_t::controller_data is for the use of the controller. * It will be initialize to NULL, and the controller can store its own state * there. * */ void a2_scroll(apple2_state_t *st) { int i; st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off cursor */ for (i=0; i<23; i++) { memcpy(st->textlines[i],st->textlines[i+1],40); } memset(st->textlines[23],0xe0,40); } static void a2_printc_1(apple2_state_t *st, char c, int scroll_p) { st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */ if (c == '\n') /* ^J == NL */ { if (st->cursy==23) { if (scroll_p) a2_scroll(st); } else { st->cursy++; } st->cursx=0; } else if (c == 014) /* ^L == CLS, Home */ { a2_cls(st); a2_goto(st,0,0); } else if (c == '\t') /* ^I == tab */ { a2_goto(st, st->cursy, (st->cursx+8)&~7); } else if (c == 010) /* ^H == backspace */ { st->textlines[st->cursy][st->cursx]=0xe0; a2_goto(st, st->cursy, st->cursx-1); } else if (c == '\r') /* ^M == CR */ { st->cursx=0; } else { st->textlines[st->cursy][st->cursx]=c ^ 0xc0; st->cursx++; if (st->cursx==40) { if (st->cursy==23) { if (scroll_p) a2_scroll(st); } else { st->cursy++; } st->cursx=0; } } st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */ } void a2_printc(apple2_state_t *st, char c) { a2_printc_1(st, c, 1); } void a2_printc_noscroll(apple2_state_t *st, char c) { a2_printc_1(st, c, 0); } void a2_prints(apple2_state_t *st, char *s) { while (*s) a2_printc(st, *s++); } void a2_goto(apple2_state_t *st, int r, int c) { if (r > 23) r = 23; if (c > 39) c = 39; st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */ st->cursy=r; st->cursx=c; st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */ } void a2_cls(apple2_state_t *st) { int i; for (i=0; i<24; i++) { memset(st->textlines[i],0xe0,40); } } void a2_clear_gr(apple2_state_t *st) { int i; for (i=0; i<24; i++) { memset(st->textlines[i],0x00,40); } } void a2_clear_hgr(apple2_state_t *st) { int i; for (i=0; i<192; i++) { memset(st->hireslines[i],0,40); } } void a2_invalidate(apple2_state_t *st) { } void a2_poke(apple2_state_t *st, int addr, int val) { if (addr>=0x400 && addr<0x800) { /* text memory */ int row=((addr&0x380)/0x80) + ((addr&0x7f)/0x28)*8; int col=(addr&0x7f)%0x28; if (row<24 && col<40) { st->textlines[row][col]=val; if (!(st->gr_mode&(A2_GR_HIRES)) || (!(st->gr_mode&(A2_GR_FULL)) && row>=20)) { } } } else if (addr>=0x2000 && addr<0x4000) { int row=(((addr&0x1c00) / 0x400) * 1 + ((addr&0x0380) / 0x80) * 8 + ((addr&0x0078) / 0x28) * 64); int col=((addr&0x07f)%0x28); if (row<192 && col<40) { st->hireslines[row][col]=val; if (st->gr_mode&A2_GR_HIRES) { } } } } void a2_hplot(apple2_state_t *st, int hcolor, int x, int y) { int highbit,run; highbit=((hcolor<<5)&0x80) ^ 0x80; /* capture bit 2 into bit 7 */ if (y<0 || y>=192 || x<0 || x>=280) return; for (run=0; run<2 && x<280; run++) { unsigned char *vidbyte = &st->hireslines[y][x/7]; unsigned char whichbit=1<<(x%7); int masked_bit; *vidbyte = (*vidbyte & 0x7f) | highbit; /* use either bit 0 or 1 of hcolor for odd or even pixels */ masked_bit = (hcolor>>(1-(x&1)))&1; /* Set whichbit to 1 or 0 depending on color */ *vidbyte = (*vidbyte & ~whichbit) | (masked_bit ? whichbit : 0); x++; } } void a2_hline(apple2_state_t *st, int hcolor, int x1, int y1, int x2, int y2) { int dx,dy,incx,incy,x,y,balance; /* Bresenham's line drawing algorithm */ if (x2>=x1) { dx=x2-x1; incx=1; } else { dx=x1-x2; incx=-1; } if (y2>=y1) { dy=y2-y1; incy=1; } else { dy=y1-y2; incy=-1; } x=x1; y=y1; if (dx>=dy) { dy*=2; balance=dy-dx; dx*=2; while (x!=x2) { a2_hplot(st, hcolor, x, y); if (balance>=0) { y+=incy; balance-=dx; } balance+=dy; x+=incx; } a2_hplot(st, hcolor, x, y); } else { dx*=2; balance=dx-dy; dy*=2; while (y!=y2) { a2_hplot(st, hcolor, x, y); if (balance>=0) { x+=incx; balance-=dy; } balance+=dx; y+=incy; } a2_hplot(st, hcolor, x, y); } } void a2_plot(apple2_state_t *st, int color, int x, int y) { int textrow=y/2; unsigned char byte; if (x<0 || x>=40 || y<0 || y>=48) return; byte=st->textlines[textrow][x]; if (y&1) { byte = (byte&0xf0) | (color&0x0f); } else { byte = (byte&0x0f) | ((color&0x0f)<<4); } st->textlines[textrow][x]=byte; } void a2_display_image_loading(apple2_state_t *st, unsigned char *image, int lineno) { /* When loading images,it would normally just load the big binary dump into screen memory while you watched. Because of the way screen memory was laid out, it wouldn't load from the top down, but in a funny interleaved way. You should call this with lineno increasing from 0 thru 191 over a period of a few seconds. */ int row=(((lineno / 24) % 8) * 1 + ((lineno / 3 ) % 8) * 8 + ((lineno / 1 ) % 3) * 64); memcpy (st->hireslines[row], &image[row * 40], 40); } /* Simulate plausible initial memory contents for running a program. */ void a2_init_memory_active(apple2_sim_t *sim) { int i,j,x,y,c; int addr=0; apple2_state_t *st=sim->st; while (addr<0x4000) { int n; switch (random()%4) { case 0: case 1: n=random()%500; for (i=0; itext_im->width); for (i=0; i<100; i++) { for (y=0; y<8; y++) { c=0; for (j=0; j<8; j++) { c |= XGetPixel(sim->text_im, (x+j)%sim->text_im->width, y)<text_im->width); } break; case 3: if (addr>0x2000) { n=random()%200; for (i=0; idpy, sim->window, apple2font_png, sizeof(apple2font_png), &pix_w, &pix_h, &m); XImage *im = XGetImage (sim->dpy, p, 0, 0, pix_w, pix_h, ~0L, ZPixmap); XImage *mm = XGetImage (sim->dpy, m, 0, 0, pix_w, pix_h, 1, XYPixmap); unsigned long black = BlackPixelOfScreen (DefaultScreenOfDisplay (sim->dpy)); int x, y; XFreePixmap (sim->dpy, p); XFreePixmap (sim->dpy, m); if (pix_w != 64*7) abort(); if (pix_h != 8) abort(); XGetWindowAttributes (sim->dpy, sim->window, &xgwa); sim->text_im = XCreateImage (sim->dpy, xgwa.visual, 1, XYBitmap, 0, 0, pix_w, pix_h, 8, 0); sim->text_im->data = malloc (sim->text_im->bytes_per_line * sim->text_im->height); /* Convert deep image to 1 bit */ for (y = 0; y < pix_h; y++) for (x = 0; x < pix_w; x++) XPutPixel (sim->text_im, x, y, (XGetPixel (mm, x, y) ? XGetPixel (im, x, y) == black : 0)); XDestroyImage (im); XDestroyImage (mm); } #else /* 0 */ /* This table lists fixes for characters that differ from the standard 6x10 font. Each encodes a pixel, as (charindex*7 + x) + (y<<10) + (value<<15) where value is 0 for white and 1 for black. */ static const unsigned short a2_fixfont[] = { /* Fix $ */ 0x8421, 0x941d, /* Fix % */ 0x8024, 0x0028, 0x8425, 0x0426, 0x0825, 0x1027, 0x1426, 0x9427, 0x1824, 0x9828, /* Fix * */ 0x8049, 0x8449, 0x8849, 0x0c47, 0x0c48, 0x0c4a, 0x0c4b, 0x9049, 0x9449, 0x9849, /* Fix , */ 0x9057, 0x1458, 0x9856, 0x1857, 0x1c56, /* Fix . */ 0x1465, 0x1864, 0x1866, 0x1c65, /* Fix / */ 0x006e, 0x186a, /* Fix 0 */ 0x8874, 0x8c73, 0x9072, /* Fix 1 */ 0x0878, 0x1878, 0x187c, /* Fix 5 */ 0x8895, 0x0c94, 0x0c95, /* Fix 6 */ 0x809f, 0x8c9c, 0x109c, /* Fix 7 */ 0x8ca4, 0x0ca5, 0x90a3, 0x10a4, /* Fix 9 */ 0x08b3, 0x8cb3, 0x98b0, /* Fix : */ 0x04b9, 0x08b8, 0x08ba, 0x0cb9, 0x90b9, 0x14b9, 0x18b8, 0x18b9, 0x18ba, 0x1cb9, /* Fix ; */ 0x04c0, 0x08bf, 0x08c1, 0x0cc0, 0x90c0, 0x14c1, 0x98bf, 0x18c0, 0x1cbf, /* Fix < */ 0x80c8, 0x00c9, 0x84c7, 0x04c8, 0x88c6, 0x08c7, 0x8cc5, 0x0cc6, 0x90c6, 0x10c7, 0x94c7, 0x14c8, 0x98c8, 0x18c9, /* Fix > */ 0x80d3, 0x00d4, 0x84d4, 0x04d5, 0x88d5, 0x08d6, 0x8cd6, 0x0cd7, 0x90d5, 0x10d6, 0x94d4, 0x14d5, 0x98d3, 0x18d4, /* Fix @ */ 0x88e3, 0x08e4, 0x8ce4, 0x98e5, /* Fix B */ 0x84ef, 0x04f0, 0x88ef, 0x08f0, 0x8cef, 0x90ef, 0x10f0, 0x94ef, 0x14f0, /* Fix D */ 0x84fd, 0x04fe, 0x88fd, 0x08fe, 0x8cfd, 0x0cfe, 0x90fd, 0x10fe, 0x94fd, 0x14fe, /* Fix G */ 0x8116, 0x0516, 0x9916, /* Fix J */ 0x0129, 0x012a, 0x052a, 0x852b, 0x092a, 0x892b, 0x0d2a, 0x8d2b, 0x112a, 0x912b, 0x152a, 0x952b, 0x992a, /* Fix M */ 0x853d, 0x853f, 0x093d, 0x893e, 0x093f, /* Fix Q */ 0x915a, 0x155a, 0x955b, 0x155c, 0x195b, 0x995c, 0x1d5c, /* Fix V */ 0x8d7b, 0x0d7c, 0x0d7e, 0x8d7f, 0x917b, 0x117c, 0x117e, 0x917f, /* Fix [ */ 0x819e, 0x81a2, 0x859e, 0x899e, 0x8d9e, 0x919e, 0x959e, 0x999e, 0x99a2, /* Fix \ */ 0x01a5, 0x19a9, /* Fix ] */ 0x81ac, 0x81b0, 0x85b0, 0x89b0, 0x8db0, 0x91b0, 0x95b0, 0x99ac, 0x99b0, /* Fix ^ */ 0x01b5, 0x05b4, 0x05b6, 0x09b3, 0x89b5, 0x09b7, 0x8db4, 0x8db6, 0x91b3, 0x91b7, /* Fix _ */ 0x9db9, 0x9dbf, 0, }; static void a2_make_font(apple2_sim_t *sim) { /* Generate the font. It used a 5x7 font which looks a lot like the standard X 6x10 font, with a few differences. So we render up all the uppercase letters of 6x10, and make a few tweaks (like putting a slash across the zero) according to fixfont. */ int i; const char *def_font="6x10"; XFontStruct *font; Pixmap text_pm; GC gc; XGCValues gcv; font = load_font_retry (sim->dpy, def_font); if (!font) { fprintf(stderr, "%s: can't load font %s\n", progname, def_font); abort(); } text_pm=XCreatePixmap(sim->dpy, sim->window, 64*7, 8, 1); memset(&gcv, 0, sizeof(gcv)); gcv.foreground=1; gcv.background=0; gcv.font=font->fid; gc=XCreateGC(sim->dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv); XSetForeground(sim->dpy, gc, 0); XFillRectangle(sim->dpy, text_pm, gc, 0, 0, 64*7, 8); XSetForeground(sim->dpy, gc, 1); for (i=0; i<64; i++) { char c=32+i; int x=7*i+1; int y=7; if (c=='0') { c='O'; XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1); } else { XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1); } } # if 0 for (i=0; a2_fixfont[i]; i++) { XSetForeground (sim->dpy, gc, (a2_fixfont[i]>>15)&1); XDrawPoint(sim->dpy, text_pm, gc, a2_fixfont[i]&0x3ff, (a2_fixfont[i]>>10)&0xf); } XWriteBitmapFile(sim->dpy, "/tmp/a2font.xbm", text_pm, 64*7, 8, -1, -1); # endif sim->text_im = XGetImage(sim->dpy, text_pm, 0, 0, 64*7, 8, ~0L, ZPixmap); XFreeGC(sim->dpy, gc); XFreePixmap(sim->dpy, text_pm); for (i=0; a2_fixfont[i]; i++) { XPutPixel(sim->text_im, a2_fixfont[i]&0x3ff, (a2_fixfont[i]>>10)&0xf, (a2_fixfont[i]>>15)&1); } } #endif /* 0 */ apple2_sim_t * apple2_start(Display *dpy, Window window, int delay, void (*controller)(apple2_sim_t *sim, int *stepno, double *next_actiontime)) { apple2_sim_t *sim; sim=(apple2_sim_t *)calloc(1,sizeof(apple2_sim_t)); sim->dpy = dpy; sim->window = window; sim->delay = delay; sim->controller = controller; sim->st = (apple2_state_t *)calloc(1,sizeof(apple2_state_t)); sim->dec = analogtv_allocate(dpy, window); sim->inp = analogtv_input_allocate(); sim->reception.input = sim->inp; sim->reception.level = 1.0; sim->prompt=']'; if (random()%4==0 && !sim->dec->use_cmap && sim->dec->use_color && sim->dec->visbits>=8) { sim->dec->flutter_tint=1; } else if (random()%3==0) { sim->dec->flutter_horiz_desync=1; } sim->typing_rate = 1.0; analogtv_set_defaults(sim->dec, ""); sim->dec->squish_control=0.05; analogtv_setup_sync(sim->inp, 1, 0); a2_make_font(sim); sim->stepno=0; a2_goto(sim->st,23,0); if (random()%2==0) sim->basetime_tv.tv_sec -= 1; /* random blink phase */ sim->next_actiontime=0.0; sim->curtime=0.0; sim->next_actiontime=sim->curtime; sim->controller (sim, &sim->stepno, &sim->next_actiontime); # ifdef GETTIMEOFDAY_TWO_ARGS gettimeofday(&sim->basetime_tv, NULL); # else gettimeofday(&sim->basetime_tv); # endif return sim; } int apple2_one_frame (apple2_sim_t *sim) { double blinkphase; int i; int textrow; if (sim->stepno==A2CONTROLLER_DONE) goto DONE; /* when caller says we're done, be done, dammit! */ { struct timeval curtime_tv; # ifdef GETTIMEOFDAY_TWO_ARGS struct timezone tzp; gettimeofday(&curtime_tv, &tzp); # else gettimeofday(&curtime_tv); # endif sim->curtime=(curtime_tv.tv_sec - sim->basetime_tv.tv_sec) + 0.000001*(curtime_tv.tv_usec - sim->basetime_tv.tv_usec); if (sim->curtime > sim->dec->powerup) sim->dec->powerup=sim->curtime; } blinkphase=sim->curtime/0.8; /* The blinking rate was controlled by 555 timer with a resistor/capacitor time constant. Because the capacitor was electrolytic, the flash rate varied somewhat between machines. I'm guessing 1.6 seconds/cycle was reasonable. (I soldered a resistor in mine to make it blink faster.) */ i=sim->st->blink; sim->st->blink=((int)blinkphase)&1; if (sim->st->blink!=i && !(sim->st->gr_mode&A2_GR_FULL)) { int downcounter=0; /* For every row with blinking text, set the changed flag. This basically works great except with random screen garbage in text mode, when we end up redrawing the whole screen every second */ int row, col; for (row=(sim->st->gr_mode ? 20 : 0); row<24; row++) { for (col=0; col<40; col++) { int c=sim->st->textlines[row][col]; if ((c & 0xc0) == 0x40) { downcounter=4; break; } } if (downcounter>0) { downcounter--; } } } if (sim->curtime >= sim->delay) sim->stepno = A2CONTROLLER_DONE; if (sim->printing) { int nlcnt=0; while (*sim->printing) { if (*sim->printing=='\001') { /* pause */ sim->printing++; break; } else if (*sim->printing=='\n') { a2_printc(sim->st,*sim->printing); sim->printing++; nlcnt++; if (nlcnt>=2) break; } else { a2_printc(sim->st,*sim->printing); sim->printing++; } } if (!*sim->printing) sim->printing=NULL; } else if (sim->curtime >= sim->next_actiontime) { if (sim->typing) { int c; /* If we're in the midst of typing a string, emit a character with random timing. */ c =*sim->typing; if (c==0) { sim->typing=NULL; } else { sim->typing++; a2_printc(sim->st, c); if (c=='\r' || c=='\n') { sim->next_actiontime = sim->curtime; } else if (c==010) { sim->next_actiontime = sim->curtime + 0.1; } else { sim->next_actiontime = (sim->curtime + (((random()%1000)*0.001 + 0.3) * sim->typing_rate)); } } } else { sim->next_actiontime = sim->curtime; sim->controller (sim, &sim->stepno, &sim->next_actiontime); if (sim->stepno==A2CONTROLLER_DONE) { DONE: sim->stepno=A2CONTROLLER_FREE; sim->controller (sim, &sim->stepno, &sim->next_actiontime); /* if stepno is changed, return 1 */ if (sim->stepno != A2CONTROLLER_FREE) return 1; XClearWindow(sim->dpy, sim->window); /* free sim */ /* This is from a2_make_font */ free(sim->text_im->data); sim->text_im->data = 0; XDestroyImage(sim->text_im); /* And free else */ analogtv_release(sim->dec); free(sim->st); free(sim->inp); free(sim); return 0; } } } analogtv_setup_sync(sim->inp, sim->st->gr_mode? 1 : 0, 0); analogtv_setup_frame(sim->dec); for (textrow=0; textrow<24; textrow++) { int row; for (row=textrow*8; rowinp->signal[row+ANALOGTV_TOP+4][ANALOGTV_PIC_START+100]; if ((sim->st->gr_mode&A2_GR_HIRES) && (row<160 || (sim->st->gr_mode&A2_GR_FULL))) { int col; /* Emulate the mysterious pink line, due to a bit getting stuck in a shift register between the end of the last row and the beginning of this one. */ if ((sim->st->hireslines[row][0] & 0x80) && (sim->st->hireslines[row][39]&0x40)) { pp[-1]=ANALOGTV_WHITE_LEVEL; } for (col=0; col<40; col++) { unsigned char b=sim->st->hireslines[row][col]; int shift=(b&0x80)?0:1; /* Each of the low 7 bits in hires mode corresponded to 2 dot clocks, shifted by one if the high bit was set. */ for (i=0; i<7; i++) { pp[shift+1] = pp[shift] = (((b>>i)&1) ?ANALOGTV_WHITE_LEVEL :ANALOGTV_BLACK_LEVEL); pp+=2; } } } else if ((sim->st->gr_mode&A2_GR_LORES) && (row<160 || (sim->st->gr_mode&A2_GR_FULL))) { int col; for (col=0; col<40; col++) { unsigned char nib=((sim->st->textlines[textrow][col] >> (((row/4)&1)*4)) & 0xf); /* The low or high nybble was shifted out one bit at a time. */ for (i=0; i<14; i++) { *pp = (((nib>>((col*14+i)&3))&1) ?ANALOGTV_WHITE_LEVEL :ANALOGTV_BLACK_LEVEL); pp++; } } } else { int col; for (col=0; col<40; col++) { int rev; int c=sim->st->textlines[textrow][col]&0xff; /* hi bits control inverse/blink as follows: 0x00: inverse 0x40: blink 0x80: normal 0xc0: normal */ rev=!(c&0x80) && (!(c&0x40) || sim->st->blink); for (i=0; i<7; i++) { unsigned long pix=XGetPixel(sim->text_im, ((c&0x3f)^0x20)*7+i, row%8); pp[1] = pp[2] = ((pix^rev) ?ANALOGTV_WHITE_LEVEL :ANALOGTV_BLACK_LEVEL); pp+=2; } } } } } analogtv_reception_update(&sim->reception); { const analogtv_reception *rec = &sim->reception; analogtv_draw(sim->dec, 0.02, &rec, 1); } return 1; } #if 0 void a2controller_test(apple2_sim_t *sim, int *stepno, double *next_actiontime) { int row,col; switch(*stepno) { case 0: a2_invalidate(sim->st); /* For testing color rendering. The spec is: red grn blu 0 black 0 0 0 1 red 227 30 96 2 dk blue 96 78 189 3 purple 255 68 253 4 dk green 0 163 96 5 gray 156 156 156 6 med blue 20 207 253 7 lt blue 208 195 255 8 brown 96 114 3 9 orange 255 106 60 10 grey 156 156 156 11 pink 255 160 208 12 lt green 20 245 60 13 yellow 208 221 141 14 aqua 114 255 208 15 white 255 255 255 */ sim->st->gr_mode=A2_GR_LORES; for (row=0; row<24; row++) { for (col=0; col<40; col++) { sim->st->textlines[row][col]=(row&15)*17; } } *next_actiontime+=0.4; *stepno=99; break; case 99: if (sim->curtime > 10) *stepno=-1; break; } } #endif