/*
 * Copyright (c) Des Herriott 1993, 1994
 *
 * 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, and that the name of the copyright holder not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  The copyright holder makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * Author: Des Herriott
 */

/*
 * Source file main.c - contains startup, snapshot, and main loop.
 */

#define Z80_MAIN

#include <sys/types.h>
#include <sys/time.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include "z80.h"
#include "resource.h"
#include "saveload.h"

#include "auxfuncs.c"

#define SNA_SIZE 49179
#define I_DELTA 50
#define I_MIN 2000

static void MainLoop(void);
static void InitGlobals(void);
static void Interrupted(void);
static void Recalibrate(void);

int  ReadSnapshot(char *fname);
int  ReadSNAFormat(char *fname);
int  ReadZ80Format(char *fname);
int  WriteSNAFormat(char *fname);
void Quit(int status);
void DoReset(void);
FILE *open_xzx_file(char *name, char *access);

/* from spectrum.c */
extern int init_spectrum(int *argcp, char **argvp, struct config *cfg_block);
extern void refresh_screen(void);
extern void handle_flashing(void);
extern void check_x_events(void);

/* from if1.c */
#ifdef ZX_IF1
extern void init_if1(struct config *cfg_block);
extern void close_if1(void);
extern void page_in_rom(void);
extern void page_out_rom(void);
#endif

#ifdef LEVEL_LOADER
char last_snapshot_name[256];
#endif

#ifdef SLOWDOWN
static int speed_fact;
#endif

/* list of functions to be invoked when Quit is called */
struct on_quit_rec {
	PFV f;
	struct on_quit_rec *next;
} *on_quit_list = NULL;

static struct config cfg_block;	/* run-time configuration record */
static uns8 *storage;			/* pointer to base of memory and IO ports */
static int ints_in_sec = 0;		/* interrupts over this 1 second time slice */
static int refresh_rate;		/* repaint screen every <refresh_rate> ints. */
static int total_ints = 0;		/* running total of interrupts to date */

int instr_count = 0;			/* for counter-style interrupts */
int flash_freq = 25;			/* flash every <flash_freq> interrupts */

/* Initially, an interrupt every 5833 ops.  This is based on an average
 * execution time of 12 T-states per operation on a 3.5MHz CPU.
 */
static int i_interval = 5833;

/* These are for setitimer-generated interrupts */
static int int_occurred = 0;
static struct itimerval itv;
static struct sigaction sigact;


#ifdef DEBUG
#define DIS_LOG_FILE "dis.log"
int logging = 0;		/* log to DIS_LOG_FILE - toggle by pressing F9 */
static FILE *disfp;
static int start, finish;		/* provide stats about execution speed */
static int total_refreshes = 0; /* running total of refreshes */
#endif


int
main(int argc, char **argv)
{
	int i, d;
	FILE *ifp;
	char *path;

	InitGlobals();

	switch(init_spectrum(&argc, argv, &cfg_block)) {
		case -1:	/* failure */
			fprintf(stderr, "xzx: spectrum initialisation failure!\n");
			exit(1);
		case 0:	/* no MIT-SHM */
			refresh_rate = cfg_block.rr_noshm * SCALING;
			break;
		case 1:	/* we have MIT-SHM */
			refresh_rate = cfg_block.rr_shm * SCALING;
			break;
	}

#ifdef SLOWDOWN
	speed_fact = cfg_block.slowdown;
#endif

	DoReset();		/* we've just powered the "chip" up; do a reset */

	/* It looks a bit more authentic this way :-) */
	srand(getpid());
	for (i = 16384; i < 23296; i++)
		mem_write(i, rand() & 0xff);
	refresh_screen();

	/* Load the ROM image into the bottom 16K of memory */
	if ((ifp = open_xzx_file(cfg_block.rom_name, "r")) == NULL) {
		fprintf(stderr, "xzx: sorry, can't open ROM image <%s>\n",
			cfg_block.rom_name);
		exit(1);
	}
	i = 0;
	while((d = fgetc(ifp)) != EOF)
		theMemory[i++] = d;		/* don't use mem_write here */

#ifdef ZX_IF1
     init_if1(&cfg_block);			/* set up Interface 1 */
#endif

	/* Load a snapshot into the processor and RAM, if specified */
	if (argc == 2) {
		ReadSnapshot(argv[1]);
		refresh_screen();
	}

#ifdef DEBUG
	/* Try to open a file for disassembly logging purposes */
	if ((disfp = fopen(DIS_LOG_FILE, "w")) == (FILE*) 0) {
		fprintf(stderr, "xzx: can't open %s, writing to stdout instead\n",
			DIS_LOG_FILE);
		disfp = stdout;
	}
#endif

	signal(SIGINT,  Quit);
	signal(SIGQUIT, Quit);
	signal(SIGTERM, Quit);

	if (cfg_block.icnt && cfg_block.recalibrate) {
		/* interrupt freq. will be recalibrated once/sec, if desired */
		sigact.sa_handler = Recalibrate;
		sigaction(SIGALRM, &sigact, NULL);
		alarm(1);
	} else if (!cfg_block.icnt) {
		/* use setitimer to generate SIGLARM every 20ms */
		sigact.sa_handler = Interrupted;
		sigaction(SIGALRM, &sigact, NULL);
		itv.it_interval.tv_sec = 0;
		itv.it_interval.tv_usec = 20000;
		itv.it_value.tv_sec = 0;
		itv.it_value.tv_usec = 20000;
		setitimer(ITIMER_REAL, &itv, NULL);
	}

#ifdef DEBUG
	fprintf(stderr, "xzx: Execution begins...\n");
#endif
	MainLoop();

	return 0;
}


/* DoReset() :
 * Clear the program counter, I & R registers, set the interrupt mode to 0,
 * and disable interrupts.
 */
void
DoReset(void)
{
#ifdef DEBUG
	fprintf(stderr, "xzx: Processor reset\n");
#endif
	*pc = 0;
	theProcessor->int_vec = 0;
	theProcessor->refresh = 0;
	theProcessor->im      = 0;
	theProcessor->iff1    = 0;
	theProcessor->iff2    = 0;
#ifdef ZX_IF1
	close_if1();						/* ensure files up to date */
	page_out_rom();						/* switch old rom back in */
#endif
#ifdef LEVEL_LOADER
	*last_snapshot_name = 0;
#endif

}


/* MainLoop() :
 * Do the fetch/execute/increment PC biz over and over.
 */
static void
MainLoop(void)
{
	register uns16 vector;
	register uns8 current_op;
	register int num_interrupts = 0;
	extern PFV z80ops[];
	int i;

#ifdef DEBUG
	char dis[80];
	start = time(NULL);
#endif

	while (1) {
#ifdef SLOWDOWN
		for (i = 0; i < speed_fact; i++) nop();
#endif
#ifdef LOAD_SAVE_1
		if ((*pc == LOAD_ADDR || *pc == SAVE_ADDR) && theMemory[0] == 0xf3)
			load_or_save();
#endif
#ifdef LOAD_SAVE_2
		if (*pc == LOAD_ADDR && theMemory[0] == 0xf3) loader();
		if (*pc == SAVE_ADDR && theMemory[0] == 0xf3) saver();
#endif
		instr_count++;
		if (cfg_block.icnt && (instr_count > i_interval))
			int_occurred = 1;

		theProcessor->refresh++;

#ifdef ZX_IF1
                if (*pc == 0x8 || *pc == 0x1708)
                        page_in_rom();
#endif

                current_op = GetNextOp;

#ifdef ZX_IF1
                if (*pc == 0x701)
                        page_out_rom();
#endif

#ifdef DEBUG
		if (logging) {
			disassemble(dis, *pc - 1);
			fputs(dis, disfp); putc('\n', disfp);
		}
#endif
		z80ops[current_op]();

		if (int_occurred) {
			if ((++total_ints % flash_freq) == 0)
				handle_flashing();
			ints_in_sec++;
			check_x_events();
			if (++num_interrupts == refresh_rate) {
				refresh_screen();
				num_interrupts = 0;
#ifdef DEBUG
				total_refreshes++;
#endif
			}

			instr_count = 0;
			int_occurred = 0;

			/* don't do anything else if interrupts are disabled */
			if (!theProcessor->iff2)
				continue;

			/* KLUDGE: make sure we don't return to a halt instruction */
			if (current_op == 0x76)
				(*pc)++;

			theMemory[--(*sp)] = *pc_high;
			theMemory[--(*sp)] = *pc_low;
			if (theProcessor->im == 2) {
				vector = (theProcessor->int_vec << 8) | 0xff;
				*pc = theMemory[vector] + (theMemory[vector + 1] << 8);
			} else {
				*pc = 0x38;		/* IM0 or IM1 - do a CALL to 0x0038 */
			}
		}
	}
	
#ifdef ZX_IF1
	close_if1();						/* update files */
#endif
	
	/* NOTREACHED */
	exit(0);
}


static void
InitGlobals(void)
{
	int i;

	if ((storage = (uns8 *)
	  malloc(MEM_SIZE + IN_SIZE + OUT_SIZE + sizeof(struct z80))) == 0) {
		perror("malloc");
		exit(1);
	}

	for (i = 0; i < (MEM_SIZE + IN_SIZE + OUT_SIZE + sizeof(struct z80)); ++i)
		storage[i] = 0; /* for memory access checkers, stop unset errs */

	theMemory = storage;
	inPorts   = storage + MEM_SIZE;
	outPorts  = inPorts + IN_SIZE;
	theProcessor = (struct z80 *)(storage + MEM_SIZE + IN_SIZE + OUT_SIZE);

	af = &(theProcessor->af_pair);
	hl = &(theProcessor->hl_pair);
	bc = &(theProcessor->bc_pair);
	de = &(theProcessor->de_pair);
	sp = &(theProcessor->sp_reg);
	pc = &(theProcessor->pc_reg);
	ix = &(theProcessor->ix_reg);
	iy = &(theProcessor->iy_reg);

#ifdef LITTLE_ENDIAN
	a  = (uns8 *)&(theProcessor->af_pair) + 1;
	f  = (uns8 *)&(theProcessor->af_pair);
	h  = (uns8 *)&(theProcessor->hl_pair) + 1;
	l  = (uns8 *)&(theProcessor->hl_pair);
	b  = (uns8 *)&(theProcessor->bc_pair) + 1;
	c  = (uns8 *)&(theProcessor->bc_pair);
	d  = (uns8 *)&(theProcessor->de_pair) + 1;
	e  = (uns8 *)&(theProcessor->de_pair);
	pc_high = (uns8 *)&(theProcessor->pc_reg) + 1;
	pc_low  = (uns8 *)&(theProcessor->pc_reg);
#else
	a  = (uns8 *)&(theProcessor->af_pair);
	f  = (uns8 *)&(theProcessor->af_pair) + 1;
	h  = (uns8 *)&(theProcessor->hl_pair);
	l  = (uns8 *)&(theProcessor->hl_pair) + 1;
	b  = (uns8 *)&(theProcessor->bc_pair);
	c  = (uns8 *)&(theProcessor->bc_pair) + 1;
	d  = (uns8 *)&(theProcessor->de_pair);
	e  = (uns8 *)&(theProcessor->de_pair) + 1;
	pc_high = (uns8 *)&(theProcessor->pc_reg);
	pc_low  = (uns8 *)&(theProcessor->pc_reg) + 1;
#endif

	carryFlag =  addsubFlag = par_overFlag = 0;
	hcarryFlag = zeroFlag =   signFlag = 0;

	/* Initialise all input ports to 0xff */
	for (i = 0; i <= 0xff; i++) 
		inPorts[i] = 0xff;
	inPorts[0x1f] = 0;
}


/* Since the flags are stored in ints instead of in the f register itself,
 * we must store/retrieve them whenever the f register is read/written.
 * Fortunately, this doesn't happen much.
 */
void StoreFlags()
{
	if (carryFlag)    *f |= F_CARRY;    else *f &= ~F_CARRY;
	if (addsubFlag)   *f |= F_NADD;     else *f &= ~F_NADD;
	if (par_overFlag) *f |= F_OVERFLOW; else *f &= ~F_OVERFLOW;
	if (hcarryFlag)   *f |= F_HCARRY;   else *f &= ~F_HCARRY;
	if (zeroFlag)     *f |= F_ZERO;     else *f &= ~F_ZERO;
	if (signFlag)     *f |= F_NEG;      else *f &= ~F_NEG;
}

void RetrieveFlags()
{
	carryFlag    = *f & F_CARRY    ? 1 : 0;
	addsubFlag   = *f & F_NADD     ? 1 : 0;
	par_overFlag = *f & F_OVERFLOW ? 1 : 0;
	hcarryFlag   = *f & F_HCARRY   ? 1 : 0;
	zeroFlag     = *f & F_ZERO     ? 1 : 0;
	signFlag     = *f & F_NEG      ? 1 : 0;
}


/* Very primitive debugging function */
void PrintCpuState()
{
	uns16 c;
	StoreFlags();

	fprintf(stderr, "xzx: PANIC: unimplemented instruction\n\n");
	fprintf(stderr, "af = 0x%04x\taf' = 0x%04x\n", theProcessor->af_pair,
		theProcessor->af_alt); 
	fprintf(stderr, "bc = 0x%04x\tbc' = 0x%04x\n", theProcessor->bc_pair,
		theProcessor->bc_alt);
	fprintf(stderr, "de = 0x%04x\tde' = 0x%04x\n", theProcessor->de_pair,
		theProcessor->de_alt);
	fprintf(stderr, "hl = 0x%04x\thl' = 0x%04x\n", theProcessor->hl_pair,
		theProcessor->hl_alt);
	fprintf(stderr, "PC = 0x%04x\tSP  = 0x%04x\n", theProcessor->pc_reg,
		theProcessor->sp_reg);
	fprintf(stderr, "ix = 0x%04x\tiy  = 0x%04x\n", theProcessor->ix_reg,
		theProcessor->iy_reg);
	fprintf(stderr, "i  = 0x%02x\tr   = 0x%02x\n", theProcessor->int_vec,
		theProcessor->refresh);
	fprintf(stderr, "Interrupt mode %d\n", theProcessor->im);
#define state(flg) flg?"yes":"no"
	fprintf(stderr, "Flags: S=%s\tZ=%s\tH=%s\tP/V=%s\tN=%s\tC=%s\n",
		state(signFlag), state(zeroFlag), state(hcarryFlag),
		state(par_overFlag), state(addsubFlag), state(carryFlag));
#undef state
	fprintf(stderr, "Memory:[0x%x-0x%x]\t", *pc - 2, *pc + 8);
	for (c = *pc - 2; c < (uns16)(*pc + 8); c++)
		fprintf(stderr, "0x%x, ", theMemory[c]);
	putc('\n', stderr);

	alarm(0);
	printf("\nPress RETURN..."); c = getchar();
	Quit(0);
}


/* Shut down the emulator cleanly, calling any function on the quit
 * list.  Currently this is spectrum.c:closedown()
 */
void Quit(int status)
{
	struct on_quit_rec *p = on_quit_list;

#ifdef ZX_IF1
	close_if1();
#endif
	
	/* invoke each registered function on the quit list */
	while(p) {
		p->f(); p = p->next;
	}
	
#ifdef DEBUG
	finish = time(NULL);
	puts("xzx: quitting");
	fprintf(stderr, "xzx ran for %d seconds\n", finish - start);
	fprintf(stderr, "%d interrupts occurred (~%d/sec)\n", total_ints, 
		total_ints / (finish - start));
	fprintf(stderr, "screen was refreshed %d times (~%d fps)\n", total_refreshes, 
		total_refreshes / (finish - start));
	fclose(disfp);
#endif
	exit(status);
}


/* Read a snapshot - decide whether it's .SNA or .Z80 format */
int 
ReadSnapshot(char *fname)
{
	char *ch;
#ifdef LEVEL_LOADER
	/* this bit provides for saving a multi-loading game as something
	* other than, say, 'gauntlet.sna'. If I saved a game as 'wibble.sna',
	* and was in the same directory as the .dat level files, I could
	* reload it by specifying the filename as this:
	*  gauntlet,wibble.sna
	* if the .dat files were in ../speccy/gauntlet, I could do this:
	*  ../speccy/gauntlet/gauntlet,wibble.sna
	*/
	ch = strrchr(fname, ',');
	if (ch != NULL) {
		strncpy(last_snapshot_name,fname,ch-fname);
		last_snapshot_name[ch-fname]=0;
		fname = ch+1;
		strcat(last_snapshot_name,".sna");
	} else {
		strcpy(last_snapshot_name,fname);
	}
#ifdef DEBUG
	fprintf(stderr,"last snap was <%s>\nnow loading <%s>\n",last_snapshot_name,fname);
#endif
#endif

	ch = strrchr(fname, '.');

	if (ch && strcmp(ch, ".z80") == 0)
		return(ReadZ80Format(fname));
	else
		return(ReadSNAFormat(fname));
}


/* Read a file in .SNA format, as used in Arnt Gulbrandsen's JPP
 * and Peter McGavin's Spectrum 1.7.
 */
int 
ReadSNAFormat(char *fname)
{
	FILE *ifp;
	int bytes_read;
	char *path;
	int ch;
	void exx(), exx_alt(), ex_afaf(), retn();

	if ((ifp = open_xzx_file(fname, "r")) == NULL) {
		fprintf(stderr, "xzx: can't find snapshot <%s>\n", fname);
		return(-1);
	}

	for (bytes_read = 0; bytes_read < SNA_SIZE; bytes_read++) {
		if ((ch = fgetc(ifp)) == EOF) {
			fprintf(stderr, "xzx: premature EOF on %s\n", fname);
			return (-1);
		}
		switch(bytes_read) {
			case 0:  theProcessor->int_vec = ch; break;
			case 1:  exx(); *l = ch; break;
			case 2:  *h = ch; break;
			case 3:  *e = ch; break;
			case 4:  *d = ch; break;
			case 5:  *c = ch; break;
			case 6:  *b = ch; break;
			case 7:  ex_afaf(); *f = ch; break;
			case 8:  *a = ch; break;
			case 9:  exx_alt(); *l = ch; break;
			case 10: *h = ch; break;
			case 11: *e = ch; break;
			case 12: *d = ch; break;
			case 13: *c = ch; break;
			case 14: *b = ch; break;
			case 15: *iy = ch; break;
			case 16: *iy |= ch << 8; break;
			case 17: *ix = ch; break;
			case 18: *ix |= ch << 8; break;
			case 19: theProcessor->iff2 = ch ? 1 : 0; break;
			case 20: theProcessor->refresh = ch; break;
			case 21: ex_afaf(); *f = ch; break;
			case 22: *a = ch; break;
			case 23: *sp = ch; break;
			case 24: *sp |= ch << 8; break;
			case 25: theProcessor->im = ch; break;
			case 26: out_byte(0xfe, ch); break;
			default:	/* write the rest into RAM */
				/* bytes_read == 27 -> first byte of RAM */
				mem_write(bytes_read + 16357, ch);
				break;
		}
	}

	retn();

#ifdef DEBUG
	fprintf(stderr, "xzx: snapshot file %s loaded OK\n", fname);
#endif

	fclose(ifp);
	return(0);
}


int
WriteSNAFormat(char *fname)
{
	FILE *ofp;
	void exx(), exx_alt(), ex_afaf(), retn();
	int using_alt_reg = 0, using_alt_af = 0;
	int i;

	extern int border_col;

	if ((ofp = fopen(fname, "w")) == NULL) {
		perror(fname);
		return(-1);
	}

	StoreFlags();
	PushValue(*pc);

	/* swap into alternate reg. set if necessary */
	if (hl == &(theProcessor->hl_alt)) using_alt_reg = 1;
	if (af == &(theProcessor->af_alt)) using_alt_af = 1;

	if (!using_alt_reg) exx();
	if (!using_alt_af) ex_afaf();

	fputc(theProcessor->int_vec, ofp);
	fputc(*l, ofp); fputc(*h, ofp);
	fputc(*e, ofp); fputc(*d, ofp);
	fputc(*c, ofp); fputc(*d, ofp);
	fputc(*f, ofp); fputc(*a, ofp);

	/* back to the main set */
	exx_alt(); ex_afaf();

	fputc(*l, ofp); fputc(*h, ofp);
	fputc(*e, ofp); fputc(*d, ofp);
	fputc(*c, ofp); fputc(*d, ofp);
	fputc(*iy & 0xff, ofp); fputc((*iy & 0xff00) >> 8, ofp);
	fputc(*ix & 0xff, ofp); fputc((*ix & 0xff00) >> 8, ofp);
	fputc(theProcessor->iff2, ofp);
	fputc(theProcessor->refresh, ofp);
	fputc(*f, ofp); fputc(*a, ofp);
	fputc(*sp & 0xff, ofp); fputc((*sp & 0xff00) >> 8, ofp);
	fputc(theProcessor->im, ofp);
	fputc(border_col, ofp);

	/* and back to the alternate set if necessary */
	if (using_alt_reg) exx();
	if (using_alt_af) ex_afaf();

	for (i = 16384; i < 65536; i++) 
		fputc(theMemory[i], ofp);

	*pc = PopValue;

	fclose(ofp);

	fprintf(stderr, "Successfully dumped <%s>\n", fname);
	return(0);
}


int 
ReadZ80Format(char *fname)
{
	FILE *ifp;
	char *path;
	int bytes_read, addr;
	int count;
	int ch;
	int compressed = 0;
	int mode128 = 0;
	int got_one_ed = 0;

	void exx(), exx_alt();

	if ((ifp = open_xzx_file(fname, "r")) == NULL) {
		fprintf(stderr, "xzx: can't find <%s>\n", fname);
		return(-1);
	}

	for (bytes_read = 0; bytes_read < 30; bytes_read++) {
		if ((ch = fgetc(ifp)) == EOF) {
			fprintf(stderr, "xzx: premature EOF on %s\n", fname);
			return(-1);
		}
		switch(bytes_read) {
			case 0: *a = ch; break;
			case 1: *f = ch; RetrieveFlags(); break;
			case 2: *c = ch; break;
			case 3: *b = ch; break;
			case 4: *l = ch; break;
			case 5: *h = ch; break;
			case 6: *pc = ch; break;
			case 7: *pc |= (ch << 8); if (!*pc) mode128 = 1; break;
			case 8: *sp = ch; break;
			case 9: *sp |= (ch << 8); break;
			case 10: theProcessor->int_vec = ch; break;
			case 11: theProcessor->refresh = ch; break;
			case 12:
				if (ch == 255) ch = 1;
				theProcessor->refresh |= (ch & 0x01) << 7;
				out_byte(0xfe, (ch & 0x0e) >> 1);
				compressed = ch & 0x20;
				break;
			case 13: *e = ch; break;
			case 14: *d = ch; exx(); break;
			case 15: *c = ch; break;
			case 16: *b = ch; break;
			case 17: *e = ch; break;
			case 18: *d = ch; break;
			case 19: *l = ch; break;
			case 20: *h = ch; break;
			case 21: *a = ch; break;
			case 22: *f = ch; exx_alt(); break;
			case 23: theProcessor->iy_reg = ch; break;
			case 24: theProcessor->iy_reg |= (ch << 8); break;
			case 25: theProcessor->ix_reg = ch; break;
			case 26: theProcessor->ix_reg |= (ch << 8); break;
			case 27: theProcessor->iff1 = ch ? 1 : 0; break;
			case 28: theProcessor->iff2 = ch ? 1 : 0; break;
			case 29: theProcessor->im = ch & 0x03; break;
			default:
				fprintf(stderr, "shouldn't get here!");
				exit(1);
		}
	}

	if (mode128) {
		fprintf(stderr, "xzx: 128k .z80 snaps are not supported, sorry.\n");
		fclose(ifp);
		return(-1);
	}

	/* now read the rest of the snapshot */
	if (compressed) {
		addr = 16384;
		while ((ch = fgetc(ifp)) != EOF) {
			if (ch != 0xed) {
				mem_write(addr, ch); addr++;
				got_one_ed = 0;
			} else {
				ch = fgetc(ifp);
				if (ch != 0xed) {
					mem_write(addr, 0xed); addr++;
					ungetc(ch, ifp);
				} else {
					count = fgetc(ifp);
					ch = fgetc(ifp); 
					while (count) {
						mem_write(addr, ch); addr++;
						count--;
					}
				}
			}
		}
	} else {
		for (addr = 16384; addr < 65536; addr++) {
			if ((ch = fgetc(ifp)) == EOF) {
				fprintf(stderr, "xzx: premature EOF on %s\n", fname);
				return(-1);
			}
			mem_write(addr, ch);
		}
	}

	fclose(ifp);
	return(0);
}


/*
 * Recalibrate interrupt frequency if interrupts are coming too fast or slow.
 * Called once a second (signal handler function)
 */
static void
Recalibrate()
{
	i_interval -= (I_DELTA * (50 - ints_in_sec));

	/* make sure that the processor gets a look-in */
	if (i_interval < I_MIN) i_interval = I_MIN;

	ints_in_sec = 0;
	alarm(1);
}


/*
 * Register void f(void) as a function to be called when we invoke Quit()
 */
void
OnQuit(PFV f)
{
	struct on_quit_rec *p;

	p = (struct on_quit_rec *)malloc(sizeof (struct on_quit_rec));
	if (p) {
		p->f = f;
		p->next = on_quit_list;
		on_quit_list = p;
	} else {
		perror("xzx");
		exit(1);
	}
}


static void
Interrupted(void)
{
	int_occurred = 1;
}


/* Request a filename from stdin, but make sure that SIGALRM is switched
 * off before-hand and back on afterwards.
 */
char *
RequestFilename(char *prompt)
{
	char *res;
	extern char *getline(FILE *, int);

	if (cfg_block.icnt && cfg_block.recalibrate) {
		alarm(0);
	} else if (!cfg_block.icnt) {
		itv.it_interval.tv_sec = 0;
		itv.it_interval.tv_usec = 0;
		itv.it_value.tv_sec = 0;
		itv.it_value.tv_usec = 0;
		setitimer(ITIMER_REAL, &itv, NULL);
	}

	printf("%s: ", prompt);
	res = getline(stdin, 0);

	if (cfg_block.icnt && cfg_block.recalibrate) {
		alarm(1);
		sigact.sa_handler = Recalibrate;
		sigaction(SIGALRM, &sigact, NULL);
	} else if (!cfg_block.icnt) {
		itv.it_interval.tv_sec = 0;
		itv.it_interval.tv_usec = 20000;
		itv.it_value.tv_sec = 0;
		itv.it_value.tv_usec = 20000;
		setitimer(ITIMER_REAL, &itv, NULL);
	}

	return(res);
}


/* Open a file.  If <name> is an absolute pathname, just try to open that 
 * file, and return the result.  Otherwise, search each element of libDir.
 */
FILE *
open_xzx_file(char *name, char *access)
{
	FILE *fp;
	char *buf;
	char *path, *element;

	if (name[0] == '/')
		return(fopen(name, access));

	path = (char *) malloc(strlen(cfg_block.lib_dir) + 1);
	strcpy(path, cfg_block.lib_dir);

	element = strtok(path, ":");
	while (element) {
		buf = malloc(strlen(name) + strlen(element) + 2);
		if (!buf) return(NULL);
		sprintf(buf, "%s/%s", element, name);
		fp = fopen(buf, access);
		free(buf);
		if (fp)
			break;
		element = strtok(NULL, ":");
	}
	free(path);

	return(fp);
}


#ifdef SLOWDOWN
void
adjust_speed(int inc)
{
	speed_fact += inc;

	if (speed_fact < 0) speed_fact = 0;
}
#endif
