sz81 Code
Brought to you by:
thunor
--- a +++ b/sz81/common.c @@ -0,0 +1,1429 @@ +/* z81/xz81, Linux console and X ZX81/ZX80 emulators. + * Copyright (C) 1994 Ian Collier. z81 changes (C) 1995-2004 Russell Marks. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + * common.c - various routines/vars common to z81/xz81. + */ + +#define Z81_VER "2.1" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <signal.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <dirent.h> +#include "common.h" +#include "sound.h" +#include "z80.h" +#include "allmain.h" + +unsigned char mem[65536],*helpscrn; +unsigned char keyports[9]={0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff}; + +/* this two work on a per-k basis, so we can support 1k etc. properly */ +unsigned char *memptr[64]; +int memattr[64]; + +int help=0; +int sound=0; +int sound_vsync=0,sound_ay=0,sound_ay_type=AY_TYPE_NONE; +int load_hook=1,save_hook=1; +int vsync_visuals=0; +int invert_screen=0; + +volatile int signal_int_flag=0; +volatile int exit_program_flag=0; +int interrupted=0; +int nmigen=0,hsyncgen=0,vsync=0; +int scrn_freq=2; +int unexpanded=0; +int taguladisp=0; +int fakedispx=0,fakedispy=0; /* set by main.c/xmain.c */ + +/* for the printer */ +static int zxpframes,zxpcycles,zxpspeed,zxpnewspeed; +static int zxpheight,zxppixel,zxpstylus; +static FILE *zxpfile=NULL; +static char *zxpfilename=NULL; +static unsigned char zxpline[256]; + + + +int refresh_screen=1; + +/* =1 if emulating ZX80 rather than ZX81 */ +int zx80=0; + +int ignore_esc=0; + +int autolist=0; +int autoload=0; +char autoload_filename[1024]; + + +/* not too many prototypes needed... :-) */ +char *load_selector(void); + + + +void sighandler(int a) +{ +signal_int_flag=1; +} + + +char *libdir(char *file) +{ +static char buf[1024]; + +if(strlen(LIBDIR)+strlen(file)+2>sizeof(buf)) + strcpy(buf,file); /* we know file is a short constant */ +else + sprintf(buf,"%s/%s",LIBDIR,file); +return(buf); +} + + +void autoload_setup(char *filename) +{ +if(zx80 || (filename && strlen(filename)+1>sizeof(autoload_filename))) + return; +autoload=1; +if(filename) + strcpy(autoload_filename,filename); +else + *autoload_filename=0; +} + + +static void exit_program_flag_set(int foo) +{ +exit_program_flag=1; +} + + +void startsigsandtimer() +{ +struct sigaction sa; +struct itimerval itv; +int f,tmp=1000/50; /* 50 ints/sec */ +int badsig[]={SIGHUP,SIGINT,SIGQUIT,SIGILL,SIGSEGV,SIGTERM,SIGBUS}; +int numsig=sizeof(badsig)/sizeof(badsig[0]); + +sigemptyset(&sa.sa_mask); +sa.sa_handler=exit_program_flag_set; +/* it's a bit odd using SA_RESTART for one-off exit code, + * but Solaris's SA_RESETHAND behaviour *as documented* is bogus + * (even though it seems ok in practice), so we play it safe. + */ +sa.sa_flags=SA_RESTART; + +/* XXX this extra-careful masking stuff may be superfluous + * now it just sets an `exit soon' flag... + * + * handle all signals listed in badsig[], making sure that all + * signals in the list are blocked whenever handling one of them. + * There are two loops as sa_mask must be fully set before calling + * sigaction(). + */ +for(f=0;f<numsig;f++) + sigaddset(&sa.sa_mask,badsig[f]); + +for(f=0;f<numsig;f++) + sigaction(badsig[f],&sa,NULL); + +#ifdef OSS_SOUND_SUPPORT +/* no timer signals when sound is enabled */ +if(sound_enabled) + return; +#endif + +sigemptyset(&sa.sa_mask); /* SIGALRM implicit */ +sa.sa_handler=sighandler; +sa.sa_flags=SA_RESTART; + +sigaction(SIGALRM,&sa,NULL); + +itv.it_interval.tv_sec= tmp/1000; +itv.it_interval.tv_usec=(tmp%1000)*1000; +itv.it_value.tv_sec=itv.it_interval.tv_sec; +itv.it_value.tv_usec=itv.it_interval.tv_usec; +setitimer(ITIMER_REAL,&itv,NULL); +} + + +void loadrom(void) +{ +FILE *in; +char *fname=libdir(zx80?"zx80.rom":"zx81.rom"); + +if((in=fopen(fname,"rb"))!=NULL) + { + int siz=(zx80?4096:8192); + + fread(mem,1,siz,in); + + /* fill first 16k with extra copies of it + * (not really needed given since improved memptr[] resolution, + * but what the heck :-)) + */ + memcpy(mem+siz,mem,siz); + if(zx80) + memcpy(mem+siz*2,mem,siz*2); + + fclose(in); + } +else + { + fprintf(stderr, + "z81: couldn't load ROM file %s.\n" + " You *must* install this for z81 to work.\n", + fname); + exit(1); + } +} + + +void zx81hacks() +{ +/* patch save routine */ +if(save_hook) + { + mem[0x2fc]=0xed; mem[0x2fd]=0xfd; + mem[0x2fe]=0xc3; mem[0x2ff]=0x07; mem[0x300]=0x02; + } + +/* patch load routine */ +if(load_hook) + { + mem[0x347]=0xeb; + mem[0x348]=0xed; mem[0x349]=0xfc; + mem[0x34a]=0xc3; mem[0x34b]=0x07; mem[0x34c]=0x02; + } +} + + +void zx80hacks() +{ +/* patch save routine */ +if(save_hook) + { + mem[0x1b6]=0xed; mem[0x1b7]=0xfd; + mem[0x1b8]=0xc3; mem[0x1b9]=0x83; mem[0x1ba]=0x02; + } + +/* patch load routine */ +if(load_hook) + { + mem[0x206]=0xed; mem[0x207]=0xfc; + mem[0x208]=0xc3; mem[0x209]=0x83; mem[0x20a]=0x02; + } +} + + +void initmem() +{ +int f; +int ramsize; +int count; + +loadrom(); +memset(mem+0x4000,0,0xc000); + +/* ROM setup */ +count=0; +for(f=0;f<16;f++) + { + memattr[f]=memattr[32+f]=0; + memptr[f]=memptr[32+f]=mem+1024*count; + count++; + if(count>=(zx80?4:8)) count=0; + } + +/* RAM setup */ +ramsize=16; +if(unexpanded) + ramsize=1; +count=0; +for(f=16;f<32;f++) + { + memattr[f]=memattr[32+f]=1; + memptr[f]=memptr[32+f]=mem+1024*(16+count); + count++; + if(count>=ramsize) count=0; + } + +if(zx80) + zx80hacks(); +else + zx81hacks(); +} + + +void loadhelp(void) +{ +FILE *in; +char buf[128]; + +/* but first, set up location of help/selector */ +fakedispx=(ZX_VID_HMARGIN-FUDGE_FACTOR)/8; +fakedispy=ZX_VID_MARGIN; + +if((in=fopen(libdir(zx80?"zx80kybd.pbm":"zx81kybd.pbm"),"rb"))!=NULL) + { + /* ditch header lines */ + fgets(buf,sizeof(buf),in); + fgets(buf,sizeof(buf),in); + if((helpscrn=calloc(256*192/8,1))==NULL) + { + fprintf(stderr,"z81: couldn't allocate memory for help screen.\n"); + exit(1); + } + fread(helpscrn,1,256*192/8,in); + fclose(in); + } +else + { + fprintf(stderr,"z81: couldn't load help screen.\n"); + exit(1); + } +} + + + +void zxpopen(void) +{ +zxpstylus=zxpspeed=zxpheight=zxpnewspeed=0; +zxppixel=-1; + +if(!zxpfilename) + return; + +if((zxpfile=fopen(zxpfilename,"wb"))==NULL) + { + fprintf(stderr,"z81: couldn't open printer file, printing disabled\n"); + return; + } + +/* we reserve 10 chars for height */ +fprintf(zxpfile,"P4\n256 %10d\n",0); +} + + +void zxpupdateheader(void) +{ +long pos; + +if(!zxpfile || !zxpheight) return; + +pos=ftell(zxpfile); + +/* seek back to write the image height */ +if(fseek(zxpfile,strlen("P4\n256 "),SEEK_SET)!=0) + fprintf(stderr,"z81: warning: couldn't seek to write image height\n"); +else + { + /* I originally had spaces after the image height, but that actually + * breaks the format as defined in pbm(5) (not to mention breaking + * when read by zgv :-)). So they're now before the height. + */ + fprintf(zxpfile,"%10d",zxpheight); + } + +if(fseek(zxpfile,pos,SEEK_SET)!=0) + { + fprintf(stderr,"z81: error: printer file re-seek failed, printout disabled!\n"); + fclose(zxpfile); + zxpfile=NULL; + } +} + + +void zxpclose(void) +{ +unsigned long tmp; +int f; + +/* stop the printer */ +tmp=tstates; +tstates=tsmax; +out(0,0xfb,4); +tstates=tmp; + +if(!zxpfile) + return; + +/* a blank line if we haven't actually printed anything :-) */ +if(!zxpheight) + { + zxpheight++; + for(f=0;f<32;f++) + fputc(0,zxpfile); + } + +/* write header */ +zxpupdateheader(); + +fclose(zxpfile); +zxpfile=NULL; +} + + +void zxpout(void) +{ +int i,j,d; + +if(!zxpfile) return; + +zxpheight++; +for(i=0;i<32;i++) + { + for(d=j=0;j<8;j++) + d=(d<<1)+(zxpline[i*8+j]?1:0); + fputc(d,zxpfile); + } +} + + + +/* ZX Printer support, transliterated from IMC's xz80 by a bear of + * very little brain. :-) Or at least, I don't grok it that well. + * It works wonderfully though. + */ +int printer_inout(int is_out,int val) +{ +/* Note that we go through the motions even if printer support isn't + * enabled, as the alternative would seem to be to crash... :-) + */ +if(!is_out) + { + /* input */ + + if(!zxpspeed) + return 0x3e; + else + { + int frame=frames-zxpframes; + int cycles=tstates-zxpcycles; + int pix=zxppixel; + int sp=zxpnewspeed; + int x,ans; + int cpp=440/zxpspeed; + + if(frame>400) + frame=400; + cycles+=frame*tsmax; + x=cycles/cpp-64; /* x-coordinate reached */ + + while(x>320) + { /* if we are on another line, */ + pix=-1; /* find out where we are */ + x-=384; + if(sp) + { + x=(x+64)*cpp; + cpp=440/sp; + x=x/cpp-64; + sp=0; + } + } + if((x>-10 && x<0) | zxpstylus) + ans=0xbe; + else + ans=0x3e; + if(x>pix) + ans|=1; + return ans; + } + } + + +/* output */ + +if(!zxpspeed) + { + if(!(val&4)) + { + zxpspeed=(val&2)?1:2; + zxpframes=frames; + zxpcycles=tstates; + zxpstylus=val&128; + zxppixel=-1; + } + } +else + { + int frame=frames-zxpframes; + int cycles=tstates-zxpcycles; + int i,x; + int cpp=440/zxpspeed; + + if(frame>400) + frame=400; /* limit height of blank paper */ + cycles+=frame*tsmax; + x=cycles/cpp-64; /* x-coordinate reached */ + for(i=zxppixel;i<x && i<256;i++) + if(i>=0) /* should be, but just in case */ + zxpline[i]=zxpstylus; + if(x>=256 && zxppixel<256) + zxpout(); + + while(x>=320) + { /* move to next line */ + zxpcycles+=cpp*384; + if(zxpcycles>=tsmax) + zxpcycles-=tsmax,zxpframes++; + x-=384; + if(zxpnewspeed) + { + zxpspeed=zxpnewspeed; + zxpnewspeed=0; + x=(x+64)*cpp; + cpp=440/zxpspeed; + x=x/cpp-64; + } + for(i=0;i<x && i<256;i++) + zxpline[i]=zxpstylus; + if(x>=256) + zxpout(); + } + if(x<0) + x=-1; + if(val&4) + { + if(x>=0 && x<256) + { + for(i=x;i<256;i++) + zxpline[i]=zxpstylus; + zxpout(); + } + zxpspeed=zxpstylus=0; + + /* this is pretty frequent (on a per-char-line basis!), + * but it's the only time we can really do it automagically. + */ + zxpupdateheader(); + } + else + { + zxppixel=x; + zxpstylus=val&128; + if(x<0) + zxpspeed=(val&2)?1:2; + else + { + zxpnewspeed=(val&2)?1:2; + if(zxpnewspeed==zxpspeed) + zxpnewspeed=0; + } + } + } + +return(0); +} + + +unsigned int in(int h,int l) +{ +int ts=0; /* additional cycles*256 */ +int tapezeromask=0x80; /* = 0x80 if no tape noise (?) */ + +if(!(l&4)) l=0xfb; +if(!(l&1)) l=0xfe; + +switch(l) + { + case 0xfb: + return(printer_inout(0,0)); + + case 0xfe: + /* also disables hsync/vsync if nmi off + * (yes, vsync requires nmi off too, Flight Simulation confirms this) + */ + if(!nmigen) + { + hsyncgen=0; + + /* if vsync was on before, record position */ + if(!vsync) + vsync_raise(); + vsync=1; +#ifdef OSS_SOUND_SUPPORT + sound_beeper(vsync); +#endif + } + + switch(h) + { + case 0xfe: return(ts|(keyports[0]^tapezeromask)); + case 0xfd: return(ts|(keyports[1]^tapezeromask)); + case 0xfb: return(ts|(keyports[2]^tapezeromask)); + case 0xf7: return(ts|(keyports[3]^tapezeromask)); + case 0xef: return(ts|(keyports[4]^tapezeromask)); + case 0xdf: return(ts|(keyports[5]^tapezeromask)); + case 0xbf: return(ts|(keyports[6]^tapezeromask)); + case 0x7f: return(ts|(keyports[7]^tapezeromask)); + default: + { + int i,mask,retval=0xff; + + /* some games (e.g. ZX Galaxians) do smart-arse things + * like zero more than one bit. What we have to do to + * support this is AND together any for which the corresponding + * bit is zero. + */ + for(i=0,mask=1;i<8;i++,mask<<=1) + if(!(h&mask)) + retval&=keyports[i]; + return(ts|(retval^tapezeromask)); + } + } + break; + } + +return(ts|255); +} + + +unsigned int out(int h,int l,int a) +{ +/* either in from fe or out to ff takes one extra cycle; + * experimentation strongly suggests not only that out to + * ff takes one extra, but that *all* outs do. + */ +int ts=1; /* additional cycles */ + +if(sound_ay && sound_ay_type==AY_TYPE_ZONX) + { + /* the examples in the manual (using DF/0F) and the + * documented ports (CF/0F) don't match, so decode is + * important for that. + */ + if(!(l&0xf0)) /* not sure how many needed, so assume all 4 */ + l=0x0f; + else + if(!(l&0x20)) /* bit 5 low is common to DF and CF */ + l=0xdf; + } + +if(!(l&4)) l=0xfb; +if(!(l&2)) l=0xfd; +if(!(l&1)) l=0xfe; + +/*printf("out %2X\n",l);*/ + +switch(l) + { + case 0x0f: /* Zon X data */ + if(sound_ay && sound_ay_type==AY_TYPE_ZONX) + sound_ay_write(ay_reg,a); + break; + case 0xdf: /* Zon X reg. select */ + if(sound_ay && sound_ay_type==AY_TYPE_ZONX) + ay_reg=(a&15); + break; + + case 0xfb: + return(ts|printer_inout(1,a)); + case 0xfd: + nmigen=0; + break; + case 0xfe: + if(!zx80) + { + nmigen=1; + break; + } + /* falls through, if zx80 */ + case 0xff: /* XXX should *any* out turn off vsync? */ + /* fill screen gap since last raising of vsync */ + if(vsync) + vsync_lower(); + vsync=0; + hsyncgen=1; +#ifdef OSS_SOUND_SUPPORT + sound_beeper(vsync); +#endif + break; + } + +return(ts); +} + + +/* the ZX81 char is used to index into this, to give the ascii. + * awkward chars are mapped to '_' (underscore), and the ZX81's + * all-caps is converted to lowercase. + * The mapping is also valid for the ZX80 for alphanumerics. + * WARNING: this only covers 0<=char<=63! + */ +static char zx2ascii[64]={ +/* 0- 9 */ ' ', '_', '_', '_', '_', '_', '_', '_', '_', '_', +/* 10-19 */ '_', '\'','#', '$', ':', '?', '(', ')', '>', '<', +/* 20-29 */ '=', '+', '-', '*', '/', ';', ',', '.', '0', '1', +/* 30-39 */ '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', +/* 40-49 */ 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', +/* 50-59 */ 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', +/* 60-63 */ 'w', 'x', 'y', 'z' +}; + + +void save_p(int a) +{ +static unsigned char fname[256]; +unsigned char *ptr=mem+a,*dptr=fname; +FILE *out; + +if(zx80) + strcpy(fname,"zx80prog.p"); +else + { + /* so the filename is at ptr, in the ZX81 char set, with the last char + * having bit 7 set. First, get that name in ASCII. + */ + + memset(fname,0,sizeof(fname)); + do + *dptr++=zx2ascii[(*ptr)&63]; + while((*ptr++)<128 && dptr<fname+sizeof(fname)-3); + + /* add '.p' */ + strcat(fname,".p"); + } + +/* now open the file */ +if((out=fopen(fname,"wb"))==NULL) + return; + +/* work out how much to write, and write it. + * we need to write from 0x4009 (0x4000 on ZX80) to E_LINE inclusive. + */ +if(zx80) + fwrite(mem+0x4000,1,fetch2(16394)-0x4000+1,out); +else + fwrite(mem+0x4009,1,fetch2(16404)-0x4009+1,out); + +fclose(out); +} + + +void load_p(int a) +{ +static unsigned char fname[sizeof(autoload_filename)]; +unsigned char *ptr=mem+(a&32767),*dptr=fname; +FILE *in; +int got_ascii_already=0; + +if(zx80) + strcpy(fname,"zx80prog.p"); +else + { + if(a>=32768) /* they did LOAD "" */ + { + got_ascii_already=1; + + if(autoload && !autolist) /* autoload stuff did it */ + { + refresh_screen=1; /* make sure we redraw screen soon */ + strcpy(fname,autoload_filename); /* guaranteed to be ok */ + + /* add .p if needed */ + if(strlen(fname)<sizeof(fname)-3 && + (strlen(fname)<=2 || strcasecmp(fname+strlen(fname)-2,".p")!=0)) + strcat(fname,".p"); + } + else /* user did it (or `-l') */ + { + char *ret=load_selector(); + + if(ret==NULL || strlen(ret)+1>=sizeof(fname)) + { + /* if autolist is aborted or goes wrong, we exit completely */ + if(autolist) + exit_program(); + return; + } + + autoload=autolist=0; /* in case it was `-l' */ + strcpy(fname,ret); + } + } + + /* so the filename is at ptr, in the ZX81 char set, with the last char + * having bit 7 set. First, get that name in ASCII. + */ + if(!got_ascii_already) + { + /* test for Xtender-style LOAD " STOP " to quit */ + if(*ptr==227) exit_program(); + + memset(fname,0,sizeof(fname)); + do + *dptr++=zx2ascii[(*ptr)&63]; + while((*ptr++)<128 && dptr<fname+sizeof(fname)-3); + + /* add '.p' */ + strcat(fname,".p"); + } + } + +/* now open the file */ +if((in=fopen(fname,"rb"))==NULL) + { + /* the partial snap will crash without a file, so reset */ + if(autoload) + reset81(),autoload=0; + return; + } + +autoload=0; + +/* just read it all */ +fread(mem+(zx80?0x4000:0x4009),1,16384,in); + +fclose(in); + +/* XXX is this still valid given proper video? */ +/* don't ask me why, but the ZX80 ROM load routine does this if it + * works... + */ +if(zx80) + store(0x400b,fetch(0x400b)+1); +} + + +void overlay_help(void) +{ +unsigned char *ptr,*optr; +int y; + +ptr=helpscrn; +optr=scrnbmp+fakedispy*ZX_VID_FULLWIDTH/8+fakedispx; + +for(y=0;y<192;y++) + { + memcpy(optr,ptr,32); + ptr+=32; + optr+=ZX_VID_FULLWIDTH/8; + } +} + + +void do_interrupt() +{ +static int count=0; + +if(exit_program_flag) + exit_program(); + +/* being careful here not to screw up any pending reset... */ +if(interrupted==1) + interrupted=0; + +/* only do screen update every 1/Nth */ +count++; +if(count>=scrn_freq) + { + count=0; + if(help) overlay_help(); + update_scrn(); + } +check_events(); /* on X checks events, on VGA scans kybd */ +} + + +/* despite the name, this also works for the ZX80 :-) */ +void reset81() +{ +interrupted=2; /* will cause a reset */ +memset(mem+16384,0,49152); +refresh_screen=1; +if(sound_ay) + sound_ay_reset(); +} + + +void usage_help(char *cmd) +{ +printf("z81 " Z81_VER + " - copyright (C) 1994-2004 Ian Collier and Russell Marks.\n\n"); +printf("usage: %s [-hilLosSTuV] [-a sound_addon_type] [-p printout.pbm]\n" + "\t\t[-r refresh_rate] [filename.p]\n", + cmd); +puts("\n" +" -a emulate AY-chip-based sound addon chip, with\n" +" `sound_addon_type' specifying the addon from the types\n" +" below:\n" +"\n" +" q Quicksilva sound board\n" +" z Bi-Pak Zon X-81\n" +"\n" +" You can also enable ACB stereo, by adding `s' (e.g. `-a qs').\n" +" -h this usage help.\n" +" -i invert screen, giving a white-on-black display.\n" +" -l boot directly to the `LOAD \"\"' file selector. This only\n" +" works when starting with `filename.p' would (see note below).\n" +" -L disable LOAD hook. (Not terribly useful.)\n" +" -o emulate `old ROM' machine, i.e. emulate a ZX80.\n" +" -p emulate ZX Printer, with PBM output in specified file.\n" +" (Most picture viewers/converters can handle PBM files.)\n" +" -r set how often the screen is redrawn in 1/50ths of a second.\n" +" -s emulate VSYNC-based sound support (the usual kind).\n" +" -S disable SAVE hook. Useful if you want to hear what SAVE\n" +" does when sound is enabled. :-) Use `-V' to see it too.\n" +" -T toggle top bit of ULA-displayed bytes (mainly for debugging,\n" +" but also useful for checking the display's `shape').\n" +" -u emulate unexpanded machine, e.g. a 1k ZX81.\n" +" -V emulate VSYNC visuals, e.g. the patterns you get when you\n" +" LOAD or SAVE on the real machine, and the little flicker you\n" +" get when typing in a program line. (This tends to be a bit\n" +" annoying when typing in a program with the default frame-skip,\n" +" so it's disabled by default.)\n" +"\n" +" filename.p\n" +" load the specified program instantly on startup;\n" +" this doesn't work when emulating a ZX80, or 1k ZX81."); +} + + +void parseoptions(int argc,char *argv[]) +{ +int done=0; + +opterr=0; + +do + switch(getopt(argc,argv,"a:hilLop:r:sSTuV")) + { + case 'a': + sound=1; + sound_ay=1; + switch(*optarg) + { + case 'q': + sound_ay_type=AY_TYPE_QUICKSILVA; + break; + case 'z': + sound_ay_type=AY_TYPE_ZONX; + break; + default: + fprintf(stderr,"z81: unrecognised sound addon type `%s'.\n",optarg); + exit(1); + } + if(*optarg && optarg[1]=='s') + sound_stereo=1,sound_stereo_acb=1; + break; + case 'h': + usage_help(argv[0]); + exit(1); + case 'i': /* invert display */ + invert_screen=1; + break; + case 'l': /* autoload, but to file selector */ + autolist=1; + break; + case 'L': /* no LOAD hook */ + load_hook=0; + break; + case 'o': /* old rom machine, i.e. ZX80 */ + zx80=1; + break; + case 'p': /* printer support (and output filename) */ + zxpfilename=optarg; + break; + case 'r': /* refresh rate */ + scrn_freq=atoi(optarg); + if(scrn_freq<1) scrn_freq=1; + if(scrn_freq>50) scrn_freq=50; + break; + case 's': /* sound (!) - specifically, VSYNC-based sound */ + sound=1; + sound_vsync=1; + break; + case 'S': /* no SAVE hook */ + save_hook=0; + break; + case 'T': /* tag ULA bytes when displayed */ + taguladisp=1; + break; + case 'u': /* unexpanded (no RAM pack) */ + unexpanded=1; + break; + case 'V': /* emulate VSYNC visuals */ + vsync_visuals=1; + break; + case '?': + switch(optopt) + { + case 'a': + fprintf(stderr,"z81: " + "the -a option needs a sound addon type as argument " + "(see man page).\n"); + break; + case 'p': + fprintf(stderr,"z81: " + "the -p option needs a PBM output filename as argument.\n"); + break; + case 'r': + fprintf(stderr,"z81: " + "the -r option needs a refresh rate as argument.\n"); + break; + default: + fprintf(stderr,"z81: option `%c' not recognised.\n",optopt); + } + exit(1); + case -1: + done=1; + } +while(!done); + +if(optind==argc-1 || autolist) /* if filename or `-l' given... */ + { + if(zx80 || unexpanded) + { + autolist=0; + fprintf(stderr,"z81: auto-loading not supported for ZX80 or 1k ZX81.\n"); + } + else + { + if(!load_hook) + { + load_hook=1; + fprintf(stderr,"z81: warning: auto-load is re-enabling LOAD hook.\n"); + } + autoload_setup(autolist?NULL:argv[optind]); + } + } +} + + +void frame_pause(void) +{ +static int first=1; +static sigset_t mask,oldmask; + +#ifdef OSS_SOUND_SUPPORT +if(sound_enabled) + { + /* we block on the sound instead. It's a bit unpleasant, + * but it's the best way. + */ + sound_frame(); + + if(interrupted<2) + interrupted=1; + return; + } +#endif + +/* we leave it blocked most of the time, only unblocking + * temporarily with sigsuspend(). + */ +if(first) + { + first=0; + sigemptyset(&mask); + sigaddset(&mask,SIGALRM); + sigprocmask(SIG_BLOCK,&mask,&oldmask); + } + +/* the procmask stuff is to avoid a race condition */ +while(!signal_int_flag) + sigsuspend(&oldmask); + +signal_int_flag=0; + +if(interrupted<2) + interrupted=1; +} + + + +static char ascii2zx[96]= + { + 0, 0,11,12,13, 0, 0,11,16,17,23,21,26,22,27,24, + 28,29,30,31,32,33,34,35,36,37,14,25,19,20,18,15, + 23,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52, + 53,54,55,56,57,58,59,60,61,62,63,16,24,17,11,22, + 11,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52, + 53,54,55,56,57,58,59,60,61,62,63,16,24,17,11, 0 + }; + + +void draw_files(unsigned char *scrn,int top,int cursel,int numfiles, + char *filearr,int elmtsiz) +{ +int x,y,n,xor,c,len; +unsigned char *sptr; + +/* since this is quick and easy (and incrementally updated for us :-)), + * we redraw each time no matter what. + */ + +/* clear filenames area */ +for(y=3;y<21;y++) + memset(scrn+1+y*33+1,0,30); + +n=top; +for(y=3;y<21;y++,n++) + { + if(n>=numfiles) break; + xor=128*(n==cursel); + sptr=scrn+1+y*33+1; + len=strlen(filearr+elmtsiz*n); + if(len>30) len=30; + for(x=0;x<len;x++) + { + c=(filearr+elmtsiz*n)[x]-32; + if(c<0 || c>95) c=0; + c=ascii2zx[c]; + *sptr++=(c^xor); + } + } +} + + +void draw_load_frame(unsigned char *scrn) +{ +int x,y; +unsigned char *sptr=scrn+1; +/* these must be exactly 32 chars long */ +/* 01234567890123456789012345678901 */ +char *text1=" choose a program to load "; +char *text2=" q=up a=dn enter=load spc=abort "; + +memset(sptr,131,32); +sptr[33*2]=7; +memset(sptr+33*2+1,3,30); +sptr[33*2+31]=132; +for(y=3;y<21;y++) + sptr[33*y]=5,sptr[33*y+31]=133; +sptr[33*21]=130; +memset(sptr+33*21+1,131,30); +sptr[33*21+31]=129; +for(x=0;x<32;x++) + { + sptr[33 +x]=128+ascii2zx[text1[x]-32]; + sptr[33*22+x]=128+ascii2zx[text2[x]-32]; + } +memset(sptr+33*23,3,32); +} + + +/* convert our text display to a bitmap in scrnbmp */ +void selscrn_to_scrnbmp(unsigned char *scrn) +{ +unsigned char *cptr=mem+0x1e00; +unsigned char *ptr,*optr,*optrsav; +int x,y,b,c,d,inv; + +memset(scrnbmp,0,ZX_VID_FULLHEIGHT*ZX_VID_FULLWIDTH/8); + +ptr=scrn+1; +optr=scrnbmp+fakedispy*ZX_VID_FULLWIDTH/8+fakedispx; + +for(y=0;y<24;y++,ptr++) + { + optrsav=optr; + for(x=0;x<32;x++,ptr++,optr++) + { + c=*ptr; + inv=(c&128); c&=63; + + for(b=0;b<8;b++,optr+=ZX_VID_FULLWIDTH/8) + { + d=cptr[c*8+b]; + if(inv) d^=255; + *optr=d; + } + optr-=ZX_VID_FULLWIDTH; + } + + optr=optrsav+ZX_VID_FULLWIDTH; + } +} + + +/* simulate lastk generation from keyports[] */ +int make_lastk(void) +{ +int y,b; +int lastk0=0,lastk1=0; + +for(y=0;y<8;y++) /* 8 half-rows */ + { + b=(keyports[y]|0xe0); + + /* contribute to it if key was pressed */ + if((b&31)!=31) + { + /* set y bit in lastk0 if not shift */ + if(!(y==0 && ((b&31)|1)==31)) lastk0|=(1<<y); + + /* now set pos-in-half-row bit(s) in lastk1 */ + b=(((~b)&31)<<1); + /* move bit 1 of b back down to bit 0 if it's shift bit */ + if(y==0) b=((b&0xfc)|((b&2)>>1)); + + lastk1|=b; + } + } + +return(0xffff^(lastk0|(lastk1<<8))); +} + + +/* wait for them to let go of all keys */ +void sel_waitnokeys(void) +{ +while(make_lastk()!=0xffff) + { + frame_pause(); + do_interrupt(); + } +} + + +char *load_selector() +{ +static char returned_filename[256]; +static unsigned char selscrn[33*24+1]; /* pseudo-DFILE */ +int f,height=18; +char *files=NULL; +int files_size,files_incr=0x2000; +DIR *dirfile; +struct dirent *entry; +struct stat sbuf; +int files_ofs,numfiles,len,maxlen; +int top,cursel; +int quit,got_one,isdir; +char *filearr; +char *ptr; +int krwait=25,krwrep=3; /* wait before key rpt and wait before next rpt */ +int krheld=0,krsubh=0; +int oldkey,key,virtkey; + +/* should never get here if emulating ZX80, but FWIW... */ +if(zx80) return(NULL); + +/* usually won't need this on a 16k, but a 1k responds to LOAD "" + * much more quickly... :-) + */ +sel_waitnokeys(); + +ignore_esc=1; /* avoid possibility of esc screwing things up */ + +oldkey=-1; + +do + { + if((dirfile=opendir("."))==NULL) + { + fprintf(stderr,"couldn't read current directory!\n"); + ignore_esc=0; + return(NULL); + } + + files_size=0x4000; + files_ofs=0; + if((files=malloc(files_size))==NULL) + { + fprintf(stderr,"not enough memory for file selector!\n"); + ignore_esc=0; + return(NULL); + } + + memset(selscrn,0,sizeof(selscrn)); /* blank our screen */ + draw_load_frame(selscrn); /* draw everything apart from files list */ + /* force an update */ + selscrn_to_scrnbmp(selscrn); + refresh_screen=1; + update_scrn(); + + numfiles=0; + maxlen=-1; + top=cursel=0; + quit=got_one=0; + + /* read list of .p files */ + while((entry=readdir(dirfile))!=NULL) + { + isdir=0; + + /* must be non-hidden dir (and not `.') or .p file */ + len=strlen(entry->d_name); + if((stat(entry->d_name,&sbuf)!=-1 && (isdir=S_ISDIR(sbuf.st_mode)) && + (strcmp(entry->d_name,"..")==0 || entry->d_name[0]!='.')) || + (len>2 && (strcasecmp(entry->d_name+len-2,".p")==0))) + { + /* make array of filenames bigger if needed */ + if(files_ofs+len+(isdir?3:1)>=files_size) + { + files_size+=files_incr; + if((files=realloc(files,files_size))==NULL) + { + fprintf(stderr,"not enough memory for file selector!\n"); + ignore_esc=0; + closedir(dirfile); + return(NULL); + } + } + + /* copy filename */ + if(isdir) + { + files[files_ofs]='('; + strcpy(files+files_ofs+1,entry->d_name); + strcat(files+files_ofs+1,")"); + len+=2; + } + else + strcpy(files+files_ofs,entry->d_name); + + files_ofs+=len+1; + if(len+1>maxlen) maxlen=len+1; + numfiles++; + } + } + + closedir(dirfile); + + /* have to put them into something more like a normal array to + * use qsort... + * (I later realised I could just have sorted an array of pointers, + * but never mind, I've got the code to do it this way now. :-/) + */ + if(numfiles==0 || (filearr=malloc(maxlen*numfiles))==NULL) + { + free(files); + if(numfiles!=0) + fprintf(stderr,"not enough memory for file selector!\n"); + ignore_esc=0; + return(NULL); + } + + /* copy filenames */ + ptr=files; + for(f=0;f<numfiles;f++) + { + strcpy(filearr+maxlen*f,ptr); + ptr+=strlen(ptr)+1; + } + + free(files); + + /* and sort */ + qsort(filearr,numfiles,maxlen,(int (*)(const void *,const void *))strcmp); + + + /* now do the file selector stuff */ + + while(!quit && !got_one) + { + /* draw file list */ + draw_files(selscrn,top,cursel,numfiles,filearr,maxlen); + /* convert to graphics, in scrnbmp */ + selscrn_to_scrnbmp(selscrn); + + /* hang around, update real display, read keys */ + frame_pause(); + do_interrupt(); + + key=make_lastk(); + + /* auto-repeat stuff */ + virtkey=key; + if(key!=oldkey) + krheld=0,krsubh=0; + else + { + krheld++; + if(krheld<krwait) + virtkey=-1; + else + if(krheld>krwait) + { + krsubh++; + if(krsubh<krwrep) + virtkey=-1; + else + krsubh=0; + } + } + + switch(virtkey) + { + case 0xfdfb: /* q */ + cursel--; break; + case 0xfdfd: /* a */ + cursel++; break; + case 0xfcfb: /* shift(alt)-q */ + cursel-=17; break; + case 0xfcfd: /* shift(alt)-a */ + cursel+=17; break; + case 0xfdbf: /* enter */ + got_one=1; break; + case 0xfd7f: /* space */ + quit=1; break; + } + if(cursel<0) cursel=0; + if(cursel>=numfiles) cursel=numfiles-1; + if(cursel<top) top--; + if(cursel>=top+height) top++; + + oldkey=key; + } + + isdir=0; + if(got_one) + { + strcpy(returned_filename,ptr=filearr+cursel*maxlen); + if(*ptr=='(') + { + ptr[strlen(ptr)-1]=0; + if(stat(ptr+1,&sbuf)!=-1 && S_ISDIR(sbuf.st_mode)) + { + isdir=1; + /* copy dir name instead */ + strcpy(returned_filename,ptr+1); + } + } + } + free(filearr); + + sel_waitnokeys(); + + if(quit) + { + ignore_esc=0; + return(NULL); + } + } +while(got_one && isdir && chdir(returned_filename)==0); + +/* if not a dir, return file to load */ +ignore_esc=0; + +/* or error, if the chdir() failed */ +if(got_one && isdir) return(NULL); + +return(returned_filename); +}