/* 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);
}