[alt.sources] Yet another CP/M emulator part 1 of 3

darcy@druid.uucp (D'Arcy J.M. Cain) (11/16/90)

As promised in comp.os.cpm here is my CP/M emulator for UNIX systems.  It
isn't quite complete but since many people seem to be trying to write this
sort of program lately I am leaving this to expand/enhance/fix/rip off.

#!/bin/sh
# This is a shell archive (produced by shar 3.49)
# To extract the files from this archive, save it to a file, remove
# everything above the "!/bin/sh" line above, and type "sh file_name".
#
# made 11/15/1990 20:30 UTC by darcy@druid
# Source directory /usr/darcy/work/Cpm
#
# existing files WILL be overwritten
#
# This is part 1 of a multipart archive                                    
# do not concatenate these parts, unpack them in order with /bin/sh        
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#   2497 -rw------- readme
#   1032 -rw-r----- Makefile
#   3852 -rw-r----- cpm-i386.h
#  26758 -rw-r----- cpm.c
#   7099 -rw-r----- dasm.c
#  32025 -rw-r----- decode.c
#    381 -rw-r----- mk_tests.c
#    127 -rw-r----- mkbin.c
#
if test -r _shar_seq_.tmp; then
	echo 'Must unpack archives in sequence!'
	echo Please unpack part `cat _shar_seq_.tmp` next
	exit 1
fi
# ============= readme ==============
sed 's/^X//' << 'SHAR_EOF' > 'readme' &&
This is a quick overview of my CP/M emulator for Unix.  I apologize if
it isn't completely up to date as the program is still evolving.
X
The system decodes most of the Z80 instruction set.  mostly the I/O is
not implemented.  Whenever the PC is >= 0xfec0 it executes a return no
matter what is in RAM at that location.  Before calling the routine for
each instruction I look at the PC and end the program if it is zero or
perform a BDOS function if it is 0xfec0 or BIOS if it is >= 0xff00.  Since
the emulator ignores the contents of memory as far as instruction decoding
is concerned, The system has the interesting property of being able to use
all of memory except the first 0x100 bytes for user programs by putting the
stack at 0xfffe.  In fact the default stack when a program is run is set
to 0xfff0.
X
I use Unix commands to simulate some CP/M commands such as DIR, REN, TYPE
and handle things such as SAVE internally.  I use the Unix file system for
drives.  The user defines Unix directories to CP/M drive mappings.  This
allows CP/M to use the same file system as the Unix but does force CP/M
conventions on the file names such as upper case, must have 1 and only 1
period, etc.  It is easy to handle though since one can always link files
as necessary, even from CP/M since I also include a '!' command to do Unix
commands from within CP/M.
X
Programs that use IN and OUT opcodes will fail.  All I/O must go through
the BDOS or the BIOS.  The user can set up the following devices:  Screen
(CON out), keyboard (CON in), RDR, PUN and LST.  The default for screen
and keyboard is stdout and stdin.  The user can set up Unix files for any
of these devices.  The file of course can be an actual file, a device or even
a pipe to a command, even another CP/M command that has the keyboard reading
from the same pipe.
X
Mostly I tried for speed except where speedup would depend on the program
running on a specific processor.  The main part of the decoding module is
a 256 entry switch table with sub-switches for the Z80 extensions.
X
It's not quite finished yet but I am running simple programs with it now.
What would be nice would be some sort of test suite.  Anyone know of any?
X
As for copyright, if you use it say where you got it and send me any fixes
or enhancements if you feel like it.  It's free and you get what you pay
for.  Don't blame me if it breaks anything or doesn't do what you expect.
(In case it isn't obvious I am not a lia^H^Hawyer.)
X
D'Arcy J.M. Cain
UUCP: darcy@druid
X
SHAR_EOF
chmod 0600 readme ||
echo 'restore of readme failed'
Wc_c="`wc -c < 'readme'`"
test 2497 -eq "$Wc_c" ||
	echo 'readme: original size 2497, current size' "$Wc_c"
# ============= Makefile ==============
sed 's/^X//' << 'SHAR_EOF' > 'Makefile' &&
X
# Makefile for cpm
# Written by D'Arcy J.M. Cain
#
X
DIST =		readme Makefile cpm-i386.h cpm.c dasm.c decode.c mk_tests.c mkbin.c
NAME =		cpm
CFLAGS=		-O -Wall
BINARIES =	cpm dcpm tcpm mk_tests 
BIN =		/usr/lbin
LBINS =		$(foreach i, $(BINARIES), $(BIN)/$i)
X
all:	$(BINARIES)
X
clean:
X	rm  -f *.o *.obj core
X
clobber:	clean
X	rm -f $(BINARIES) $(NAME).0?
X
install:	all
X	rm -f $(LBINS1)
X	chmod 711 $(BINARIES)
X	ln $(BINARIES) $(BIN)
X
$(NAME).01:		$(DIST)
X	shar -L30 -vxf -o $(NAME) $^
X
gen:	$(NAME).gen
X
$(NAME).gen:	$(DIST)
X	mkscript $^ > $(NAME).gen
X
shar:	$(NAME).01
X
strip:	$(BINARIES)
X	strip $(BINARIES)
X
mcs:	$(BINARIES)
X	mcs -d $(BINARIES)
X
cpm:	cpm.o decode.o dasm.o
X	$(CC) $(CFLAGS) cpm.o decode.o dasm.o -o cpm
X
tcpm:	tcpm.o decode.o dasm.o
X	$(CC) $(CFLAGS) tcpm.o decode.o dasm.o -o tcpm
X
tcpm.o:	cpm.c
X	$(CC) $(CFLAGS) -DCOMPILE_TEST -c cpm.c -o tcpm.o
X
dcpm:	dcpm.o decode.o dasm.o
X	$(CC) $(CFLAGS) dcpm.o decode.o dasm.o -o dcpm
X
dcpm.o:	cpm.c
X	$(CC) $(CFLAGS) -DCPM_DEBUG -c cpm.c -o dcpm.o
X
cpm.o decode.o dasm.o:	cpm.h
SHAR_EOF
chmod 0640 Makefile ||
echo 'restore of Makefile failed'
Wc_c="`wc -c < 'Makefile'`"
test 1032 -eq "$Wc_c" ||
	echo 'Makefile: original size 1032, current size' "$Wc_c"
# ============= cpm-i386.h ==============
sed 's/^X//' << 'SHAR_EOF' > 'cpm-i386.h' &&
/*
cpm-i386
X
Written by D'Arcy J.M. Cain
darcy@druid
X
This file is the header used by the CP/M emulator when running under
the following machine(s):
X
X	System V Rel 3.2 on Intel 80386 processor
X
Link this file to cpm.h on the above system.  Other entries will be added
as they are tested.
X
To use this program on systems which differ from the above in significant
ways, a header must be created such that the Z80 registers can be accessed
using the following register names:
X	A, B, C, D, E, H, L, BC, DE, HL, IX, IY, SP, PC, AF and FLAGS
In addition the following flags sould be available:
X	CARRY, BCD, PARITY, HALF_CARRY, ZERO and SIGN
Also the HL, SP, IX and IY registers should have a H and L version for
accessing half-words and TEMP should be a scratch variable accessed the
same way.  See below for examples.
X
There should also be a variable IFF and an array of 0x10000 (65,536) bytes
called ram.
X
The two variables psw_bank and gr_bank should allow register context
switching for the AF register and the general registers
X
*/
X
typedef	unsigned char	byte;
typedef unsigned short	word;
/* We use unsigned short to guarantee two byte words and roll-over */
X
#define	lonyb(v)	(v & 0xf)
#define	hinyb(v)	((v >> 4) & 0xf)
#define	when		break; case
X
typedef union {
X	word	af;
X	struct {
X		byte	flags, a;
X	} b;
X	struct {
X		unsigned int	carry:1;
X		unsigned int	bcd:1;
X		unsigned int	parity:1;
X		unsigned int	x1:1;
X		unsigned int	half:1;
X		unsigned int	x2:1;
X		unsigned int	zero:1;
X		unsigned int	sign:1;
X	} bits;
} ACC;
X
typedef union {
X	struct {
X		word	bc;
X		word	de;
X		word	hl;
X	} w;
X	struct {
X		byte	c, b;
X		byte	e, d;
X		byte	l, h;
X	} b;
} GR;
X
typedef union {
X	unsigned char	half[2];
X	unsigned short	whole;
} REG;
X
#define	TEMPH	(reg.half[1])
#define	TEMPL	(reg.half[0])
#define	TEMP	(reg.whole)
#define	SPH		(sp.half[1])
#define	SPL		(sp.half[0])
#define	SP		(sp.whole)
#define IXH		(ix.half[1])
#define IXL		(ix.half[0])
#define IX		(ix.whole)
#define IYH		(iy.half[1])
#define IYL		(iy.half[0])
#define IY		(iy.whole)
X
#ifdef	CPM_DATA
ACC		acc[2];
GR		gr[2];
word	PC;
byte	R, IV;
int		gr_bank = 0, acc_bank = 0, IFF = 1;
REG		reg, sp, ix, iy;
X
#ifdef	COMPILE_TEST
byte	ram[0x10000] = {
X	0x00,				/* 0000: nop */
X	0x21, 0x00, 0x10,	/* 0001: ld hl, 1000h */
X	0xe5,				/* 0004: push hl */
X	0x3e, 0xbb,			/* 0005: ld a, 0bbh */
X	0x87,				/* 0007: add a, a */
X	0x8f,				/* 0008: adc a, a */
X	0x3d,				/* 0009: dec a */
X	0x2f,				/* 000a: cpl */
X	0xb8,				/* 000b: cp b */
X	0xcc, 0x14, 0x00,	/* 000c: call z, 0014h */
X	0xbf,				/* 000f: cp a */
X	0xcc, 0x14, 0x00,	/* 0010: call z, 0014h */
X	0x00,				/* 0013: nop */
X	0xc9,				/* 0014: ret */
X	0x00 };				/* 0015: nop */
#define		TEST_SIZE	24
#else
byte	ram[0x10000];
#endif
X
byte	page_zero[] = {
X	0xc3, 0x03, 0xff,	/* JP BIOS+3 */
X	0x00, 0x00,			/* reserved */
X	0xc3, 0xc0, 0xfe,	/* JP BDOS */
};
X
#else
extern ACC	acc[];
extern GR	gr[];
extern word	PC;
extern byte	R, IV, ram[];
extern int	gr_bank, acc_bank, IFF;
extern REG	reg, sp, ix, iy;
#endif
X
#define		AF			(acc[acc_bank].af)
#define		A			(acc[acc_bank].b.a)
#define		FLAGS		(acc[acc_bank].b.flags)
#define		CARRY		(acc[acc_bank].bits.carry)
#define		BCD			(acc[acc_bank].bits.bcd)
#define		PARITY		(acc[acc_bank].bits.parity)
#define		OVERFLOW	(acc[acc_bank].bits.parity)
#define		HALF_CARRY	(acc[acc_bank].bits.half)
#define		ZERO		(acc[acc_bank].bits.zero)
#define		SIGN		(acc[acc_bank].bits.sign)
X
#define		B			(gr[gr_bank].b.b)
#define		C			(gr[gr_bank].b.c)
#define		D			(gr[gr_bank].b.d)
#define		E			(gr[gr_bank].b.e)
#define		H			(gr[gr_bank].b.h)
#define		L			(gr[gr_bank].b.l)
#define		BC			(gr[gr_bank].w.bc)
#define		DE			(gr[gr_bank].w.de)
#define		HL			(gr[gr_bank].w.hl)
X
#define		BDOS		0xfec0
#define		BIOS		0xff00
#define		bios(x)		(BIOS + (x * 3))
X
#define		MAX_DRIVES	16
X
int			decode(void);
const char	*dasm(const byte *buf);
SHAR_EOF
chmod 0640 cpm-i386.h ||
echo 'restore of cpm-i386.h failed'
Wc_c="`wc -c < 'cpm-i386.h'`"
test 3852 -eq "$Wc_c" ||
	echo 'cpm-i386.h: original size 3852, current size' "$Wc_c"
# ============= cpm.c ==============
sed 's/^X//' << 'SHAR_EOF' > 'cpm.c' &&
/*
cpm
X
CP/M emulator.
Written by D'Arcy J.M. Cain
darcy@druid
X
*/
X
#define		CPM_DATA
X
#include	<stdio.h>
#include	<string.h>
#include	<unistd.h>
#include	<getopt.h>
#include	<ctype.h>
#include	<termio.h>
#include	<io.h>
#include	<signal.h>
#include	<time.h>
#include	<sys/stat.h>
#include	<sys/fcntl.h>
#include	"cpm.h"
X
#define		FILLER_SIZE	(16 - sizeof(FILE *))
X
#ifdef		DEBUG
#define		debug(x)	fprintf(stderr, "%s: %4d  %s\n", __FILE__, __LINE__, x)
#else
#define		debug(x)
#endif
X
typedef struct {
X	byte	dr;			/* drive: 0 = default, 1 = A, 2 = B, etc. */
X	char	name[8];	/* file name up to 8 characters */
X	char	typ[3];		/* file type up to 3 characters */
X	byte	ex, s1, s2, rc;
X	FILE	*fp;		/* Unix file pointer */
X	byte	filler[FILLER_SIZE];
X	byte	cr, r0, r1, r2;
} FCB;
X
#ifdef		CPM_DEBUG
#define		dump_registers(x) \
X	fprintf(x, reg_dump, A, BC, DE, HL, SP, PC,\
X					SIGN ? 'S' : '-',\
X					ZERO ? 'Z' : '-',\
X					HALF_CARRY ? 'H' : '-',\
X					PARITY ? 'P' : '-',\
X					BCD ? 'N' : '-',\
X					CARRY ? 'C' : '-',\
X					ram[PC], dasm(ram + PC))
X
static int		dasm_flag = 0;
static char	*reg_dump =
X	"A=%02.2x BC=%4.4x DE=%04.4x HL=%04.4x SP=%04.4x PC=%04.4x %c%c%c%c%c%c %02.2x %s\r\n";
#else
#define		dump_registers(x)
#endif
X
X
struct termio	old_term, termp;
static int		console, user_break;
X
#ifndef		COMPILE_TEST
static byte		*dma;
static char		*tail;
static int		out_delim = '$', def_drive = 1;
static FILE		*reader = NULL, *punch = NULL, *list = NULL;
#endif
X
/* clean up routine */
void	cleanup(int sig)
{
X	if (sig == SIGINT)
X	{
X		user_break = 1;
X		signal(SIGINT, cleanup);
X		return;
X	}
X
X	ioctl(console, TCSETA, &old_term);
X	printf("\nWe now return you to your regularly scheduled OS\n");
X	exit(0);
}
X
/* How CP/M drives map to Unix: */
/* below is an array of 17 strings.  This corresponds to the allowable */
/* drive names in CP/M (A to P) plus the default drive which actually */
/* is a duplicate of one of the next 16.  At startup, The current Unix */
/* directory is copied into cpm_drive[1] and becomes drive A.  This may */
/* be modified by the startup sequence.  As well, the other drives may */
/* be set up to other directories.  At the end of the startup sequence */
/* the "strcpy(cpm_drive[0], cpm_drive[1]) causes drive A to be the CP/M */
/* default drive.  From that point on, a switch to a new drive involves */
/* simply copying that drive's directory into cpm_drive[0].  I did this */
/* in this way since I expect changing drives to occur less frequently */
/* than accessing files. */
X
#ifndef		COMPILE_TEST
static char		cpm_drive[17][128];
X
/* Convert string to upper case */
static void		strtoup(char *s)
{
X	while (*s)
X	{
X		*s = toupper(*s);
X		s++;
X	}
}
X
/* take a string, terminate it at the first white space and return the
X   string that follows.  I.E: "DIR *.COM" puts a 0 in the first space
X   and returns a pointer to "*.COM".  Note that "DIR" returns a pointer
X   to a NULL string - NOT a NULL pointer. */
static char	*chop_cmd(char *buf)
{
X	char	*ptr = buf;
X
X	/* discard leading space */
X	while (isspace(*ptr))
X		ptr++;
X
X	/* quad left the string */
X	strcpy(buf, ptr);
X
X	/* terminate first word */
X	ptr = buf;
X	while (!isspace(*ptr) && *ptr)
X		ptr++;
X
X	/* is there more? */
X	if (*ptr)
X	{
X		/* terminate first word */
X		*ptr++ = 0;
X
X		/* skip any leading space */
X		while (isspace(*ptr))
X			ptr++;
X
X	}
X
X	return(ptr);
}
X
/* given a drive unit (0 - 16) and a file name, returns Unix file name */
static char *mk_name(int dr, char *fname)
{
X	static char	full_name[148];
X
X	sprintf(full_name, "%s/%s", cpm_drive[dr], fname);
X
X	if (strchr(fname, '.') == NULL)
X		strcat(full_name, ".");
X
X	return(full_name);
}
X
/* given a file spec in standard CP/M format returns Unix file name */
static char	*real_name(char *fname)
{
X	/* does it include a drive letter? */
X	if (fname[1] == ':')
X		return(mk_name(*fname - '@', fname + 2));
X
X	/* else use default drive */
X	return(mk_name(0, fname));
}
X
X
/* given a pointer to an FCB, returns real file name */
char	*fcb2real(FCB *buf)
{
X	char	temp[16], *p = temp;
X	int		k = 0;
X
X	for (k = 0; k < 8; k++)
X		if (!isspace(buf->name[k]))
X			*p++ = buf->name[k];
X
X	*p++ = '.';
X
X	for (k = 0; k < 3; k++)
X		if (!isspace(buf->typ[k]))
X			*p++ = buf->typ[k];
X
X	*p = 0;
X	return(mk_name(buf->dr, temp));
}
X
/* calls system command with CP/M file name converted to Unix */
static void		fsystem(const char *s, char *file)
{
X	char	command[256];
X
X	sprintf(command, s, real_name(file));
X	ioctl(console, TCSETA, &old_term);
X	system(command);
X	ioctl(console, TCSETA, &termp);
}
X
/* formats a CP/M file name into an FCB */
static void	mk_fcb(FCB *buf, char *fname)
{
X	char	*p = fname;
X	int		k, l;
X
X	/* clear FCB to start with */
X	memset(buf, 0, 16);
X
X	/* check for drive name */
X	if (p[1] == ':')
X	{
X		debug("");
X		buf->dr = *p - '@';
X		p += 2;
X	}
X
X	k = l = 0;
X
X	/* format primary name */
X	for (k = 0; k < 8; k++)
X	{
X		debug("");
X
X		if ((p[l] == '.') || (p[l] == 0))
X		{
X			debug("");
X
X			while (k < 8)
X			{
X				debug("");
X				buf->name[k++] = ' ';
X			}
X		}
X		else if (p[l] == '*')
X		{
X			debug("");
X
X			while (k < 8)
X				buf->name[k++] = '?';
X
X			debug("");
X			while (p[l] && (p[l] != '.'))
X				l++;
X
X			debug("");
X		}
X		else
X			buf->name[k] = p[l];
X
X		debug("");
X		l++;
X	}
X
X	debug("");
X
X	/* format file type */
X	for (k = 0; k < 3; k++)
X	{
X		debug("");
X		if ((p[l] == '.') || (p[l] == 0))
X			while (k < 3)
X				buf->typ[k++] = ' ';
X		else if (p[l] == '*')
X			while (k < 3)
X				buf->typ[k++] = '?';
X		else
X			buf->typ[k] = p[l];
X
X		debug("");
X		l++;
X	}
X
X	debug("");
X	return;
}
X
/* add extension to file name.  replace current one if necessary */
static void	addext(char *s1, char *s2)
{
X	char	*p;
X
X	if ((p = strchr(s1, '.')) == NULL)
X		strcat(s1, ".");
X
X	strcat(s1, s2);
}
#endif
X
/* get a character from the terminal */
byte	getch(void)
{
X	byte	c = 0;
X
X	while (read(console, &c, 1) != 1)
X		;
X
X	return(c);
}
X
/* see if character waiting */
#define		kbhit()		ioctl(console, FIORDCHK, NULL)
X
/* get a string */
int		get_str(char *buffer, int maxlen)
{
X	int		k = 0, c;
X
X	while ((c = getch()) != '\r' && c != '\n')
X	{
X		if (k == maxlen)
X			c = '\a';
X		else if (c == '\b')
X		{
X			if (k)
X			{
X				fprintf(stderr, "\b \b");
X				k--;
X			}
X		}
X		else
X		{
X			fputc(c, stdout);
X			buffer[k++] = c;
X		}
X
X	}
X
X	fprintf(stderr, "\r\n");
X	return(k);
}
X
#ifdef	CPM_DEBUG
#define		is_breakpoint(x)	breakpoint(0, (x))
#define		list_breakpoints()	breakpoint(0, 0)
#define		add_breakpoint(x)	breakpoint(1, (x))
#define		del_breakpoint(x)	breakpoint(2, (x))
X
int		breakpoint(int cmd, int bpoint)
{
X	static int	bp[64];
X	int			k;
X
X	switch(cmd)
X	{
X		case 0:
X			for (k = 0; k < 64; k++)
X			{
X				if (bp[k])
X				{
X					if (!bpoint)
X						fprintf(stderr, "Breakpoint %2d: 0x%04.4x\r\n");
X					else if (bp[k] == bpoint)
X						return(1);
X				}
X			}
X
X			return(0);
X			break;
X
X		case 1:
X			for (k = 0; k < 64; k++)
X				if (bp[k] == bpoint)
X					return(k);
X
X			for (k = 0; k < 64; k++)
X			{
X				if (!bp[k])
X				{
X					bp[k] = bpoint;
X					return(k);
X				}
X			}
X
X			fprintf(stderr, "Too many breakpoints\r\n");
X			return(-1);
X			break;
X
X		case 2:
X			for (k = 0; k < 64; k++)
X				if (bp[k] == bpoint)
X					bp[k] = 0;
X
X			return(0);
X			break;
X	}
X
X	return(-1);
}
X
X				
int		debugger()
{
X	char	entry[128], *ptr;
X
X	user_break = 0;
X
X	for (;;)
X	{
X		fprintf(stderr, "\r\nDEBUG> ");
X		ptr = entry;
X
X		while ((*ptr = getch()) != '\n')
X		{
X			if (*ptr == '\b')
X			{
X				if (ptr > entry)
X				{
X					fprintf(stderr, "\b \b");
X					ptr--;
X				}
X			}
X			else
X				fputc(*ptr++, stdout);
X		}
X
X		*ptr = 0;
X		strtoup(entry);
X		fprintf(stderr, "\r\n");
X
X		if (!*entry)
X			;
X		else if (*entry == 'G')
X			return(0);
X		else if (*entry == 'Q')
X			return(1);
X		else if (*entry == 'R')
X			dump_registers(stdout);
X		else if (*entry == '+')
X			add_breakpoint(atoi(entry + 1));
X		else if (*entry == '-')
X			del_breakpoint(atoi(entry + 1));
X		else if (*entry == 'L')
X			list_breakpoints();
#ifdef	CPM_DEBUG
X		else if (isdigit(*entry))
X			dasm_flag = *entry - '0';
#endif
X
#if 0
X		else if (*entry == '')
X		else if (*entry == '')
X		else if (*entry == '')
X		else if (*entry == '')
#endif
X		else
X			fprintf(stderr, "\aUnknown command: %c\n", *entry);
X	}
}
#endif
X
#ifndef	COMPILE_TEST
/* run a program */
static int	run(char *program)
{
X	byte	*mem_ptr = ram + 0x100;
X	char	*fn, fn2[128];
X	int		c, k, pc;
X	FILE	*fp;
X	FCB		*fcb = NULL;
X	long	f_pos;
X	struct stat	s;
X
X	debug("Start run function");
X
X	/* find the program name */
X	strcpy((char *)(mem_ptr), program);
X	addext((char *)(mem_ptr), "COM");
X
X	/* open the command file - return error if not found */
X	if ((fp = fopen((char *)(mem_ptr), "rb")) == NULL)
X		return(-1);
X
X	debug("");
X
X	/* load command into memory */
X	while (fread(mem_ptr, 1, 0x100, fp))
X	{
X		if (mem_ptr > (ram + 0xf000))
X		{
X			fprintf(stderr, "\aCommand file too big\r\n");
X			return(-2);
X		}
X
X		mem_ptr += 0x100;
X	}
X
X	fclose(fp);
X	debug("");
X
X	/* set up registers and page zero */
X	PC = 0x100;
X	SP = 0xfff0;
X
X	/* following for test purposes */
X	A = 1;
X	BC = 0x2345;
X	DE = 0x6789;
X	HL = 0x0abc;
X
X	debug("");
X	strcpy((char *)(ram + 0x80), tail);
X	debug("");
X	mem_ptr = (byte *)(chop_cmd(tail));
X	debug("");
X	mk_fcb((FCB *)(ram + 0x5c), tail);
X	debug("");
X	mk_fcb((FCB *)(ram + 0x6c), (char *)(mem_ptr));
X	debug("");
X	memcpy(ram, page_zero, sizeof(page_zero));
X	debug("");
X	dma = ram + 0x80;
X	debug("");
X
X	debug("");
X
X	/* BDOS, BIOS and default stack */
X	for (k = 0xfc00; k < 0x10000; k++)
X		ram[k] = 0;
X
X	debug("");
X
X	/* run program.  loop stops if PC = 0 - "JP 0" e.g. */
X	while (PC)
X	{
X
#ifdef	CPM_DEBUG
X		if (dasm_flag > 1)
X			dump_registers(stderr);
X
X		if ((user_break && debugger()) || is_breakpoint(PC))
#else
X		if (user_break)
#endif
X		{
X			fprintf(stderr, "\r\n\n\a* Program Interrupted by user *\r\n", ram[PC]);
X			dump_registers(stderr);
X			return(-5);
X		}
X
X		debug("");
X		pc = PC;
X
X		/* check if PC = BDOS entry point */
X		if (PC == BDOS)
X		{
X			/* do CP/M service if so */
X			switch (C)
X			{
X				case 0:						/* system reset */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: System reset\r\n");
#endif
X					return(0);
X
X				case 1:						/* conin */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Console in\r\n");
#endif
X
X					fputc((A = getch()), stdout);
X					break;
X
X				case 2:						/* conout */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Console out (%c)\r\n", E >= ' ' ? E : '.');
#endif
X
X					fputc(E, stdout);
X					break;
X
X				case 3:						/* RDR */ 
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Reader in\r\n");
#endif
X
X					if (reader != NULL)
X						A = fgetc(reader);
X					break;
X
X				case 4:						/* PUN */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Punch out (%c)\r\n", E >= ' ' ? E : '.');
#endif
X
X					if (punch != NULL)
X						fputc(E, punch);
X					break;
X
X				case 5:						/* LST */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: List out (%c)\r\n", E >= ' ' ? E : '.');
#endif
X
X					if (list != NULL)
X						fputc(E, list);
X					break;
X
X				case 6:						/* CONIO */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X					{
X						fprintf(stderr, "BDOS: Conio ");
X						if (E == 0xff)
X							fprintf(stderr, "in\r\n");
X						else
X							fprintf(stderr, "out (%c)\r\n", E >= ' ' ? E : '.');
X					}
#endif
X
X					if (E == 0xff)
X						A = getch();
X					else
X						fputc(E, stdout);
X
X					break;
X
X				case 7:						/* get IOBYTE */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Get IOBYTE\r\n");
#endif
X
X					A = 0x95;
X					break;
X
X				case 8:						/* set IOBYTE */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Set IOBYTE\r\n");
#endif
X
X					break;
X
X				case 28:					/* write protect disk */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Write protect disk\r\n");
#endif
X
X					break;
X
X				case 9:						/* prstr */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Print string\r\n");
#endif
X
X					mem_ptr = ram + DE;
X					while (*mem_ptr != out_delim)
X						fputc(*mem_ptr++, stdout);
X					break;
X
X				case 10:					/* rdstr */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Read console buffer\r\n");
#endif
X
X					ram[DE + 1] = get_str((char *)(ram) + DE + 2, ram[DE]);
X					break;
X
X				case 11:				/* CONSTAT */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Get console status\r\n");
#endif
X
X					A = kbhit() ? 0xff : 0;
X					break;
X
X				case 12:				/* VERSION */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Return version number\r\n");
#endif
X
X					HL = 0x0022;
X					break;
X
X				case 13:				/* RSTDSK */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Reset disk system\r\n");
#endif
X
X					break;
X
X				case 14:				/* SELDSK */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Select disk %c:\r\n", E + 'A');
#endif
X
X					k = E + 1;
X					A = 0xff;
X
X					if ((k < 1) || (k > 16))
X						H = 4;
X					else if (*cpm_drive[k] == 0)
X						H = 1;
X					else
X					{
X						def_drive = k;
X						strcpy(cpm_drive[0], cpm_drive[k]);
X						A = 0;
X					}
X					break;
X
X				case 15:				/* OPENF */
X					fcb = (FCB *)(ram + DE);
X					fn = fcb2real(fcb);
X					memset(&fcb->fp, 0, 24);
X
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Open file %s\r\n", fn);
#endif
X
X					A = 0xff;
X
X					if (strchr(fn, '?') != NULL)
X						HL = 9;
X					else if ((fcb->dr < 0) || (fcb->dr > 16))
X						HL = 4;
X					else if (*cpm_drive[fcb->dr] == 0)
X						HL = 1;
X					else if ((fcb->fp = fopen(fn, "r+")) == NULL)
X						HL = 0;
X					else
X						A = HL = 0;
X
X					break;
X
X				case 16:
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Close file\r\n");
#endif
X
X					fcb = (FCB *)(ram + DE);
X
X					if (fcb->fp != NULL)
X						fclose(fcb->fp);
X
X					fcb->fp = NULL;
X					break;
X
X				case 19:
X					fcb = (FCB *)(ram + DE);
X
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Delete file\r\n", fcb2real(fcb));
#endif
X
X					unlink(fcb2real(fcb));
X					fcb->fp = NULL;
X					break;
X
X				case 20:					/* READ */
X				case 33:					/* READ RANDOM */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X					{
X						fprintf(stderr, "BDOS: Read ");
X						if (C == 20)
X							fprintf(stderr, "sequential");
X						else
X							fprintf(stderr, "random");
X					}
#endif
X
X					if ((fcb = (FCB *)(ram + DE)) == NULL)
X					{
X						A = 9;
X						break;
X					}
X
X					memset(dma, 0x1a, 0x80);
X
X					if (C == 33)
X					{
X						f_pos = (fcb->r2 << 16) + (fcb->r1 << 8) + fcb->r0;
X						fseek(fcb->fp, f_pos * 0x80, SEEK_SET);
X					}
X
X					if (fread(dma, 1, 0x80, fcb->fp) == 0)
X						A = 1;
X					else
X						A = 0;
X
X					break;
X
X				case 21:					/* WRITE */
X				case 34:					/* WRITE RANDOM */
X				case 40:					/* Write Random Zero Fill */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X					{
X						fprintf(stderr, "BDOS: Write ");
X						if (C == 21)
X							fprintf(stderr, "sequential\r\n");
X						else if (C == 34)
X							fprintf(stderr, "random\r\n");
X						else
X							fprintf(stderr, "random with zero fill\r\n");
X					}
#endif
X
X					if ((fcb = (FCB *)(ram + DE)) == NULL)
X					{
X						A = 9;
X						break;
X					}
X
X					if (C == 34)
X					{
X						f_pos = (fcb->r2 << 16) + (fcb->r1 << 8) + fcb->r0;
X						fseek(fcb->fp, f_pos * 0x80, SEEK_SET);
X					}
X
X					if (fwrite(dma, 1, 0x80, fcb->fp) == 0)
X						A = 1;
X					else
X						A = 0;
X
X					break;
X
X				case 22:					/* MAKEF */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Make file\r\n");
#endif
X
X					fcb = (FCB *)(ram + DE);
X					fn = fcb2real(fcb);
X
X					if ((fcb->fp = fopen(fn, "r")) != NULL)
X					{
X						fclose(fcb->fp);
X						A = 0xff;
X						break;
X					}
X
X					memset(&fcb->fp, 0, 24);
X					A = 0xff;
X
X					if (strchr(fn, '?') != NULL)
X						HL = 9;
X					else if ((fcb->dr < 0) || (fcb->dr > 16))
X						HL = 4;
X					else if (*cpm_drive[fcb->dr] == 0)
X						HL = 1;
X					else if ((fcb->fp = fopen(fn, "w")) == NULL)
X						HL = 0;
X					else
X						A = HL = 0;
X
X					break;
X	
X				case 23:					/* RENAME */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Rename file\r\n");
#endif
X
X					fcb = (FCB *)(ram + DE);
X					strcpy(fn2, fcb2real(fcb));
X					fn = fcb2real(fcb + 16);
X
X					if (link(fn2, fn) == -1)
X						A = 0xff;
X					else
X					{
X						unlink(fn2);
X						A = 0;
X					}
X					break;
X
X				case 24:					/* get log in vector */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Get login vector\r\n");
#endif
X
X					c = 1;
X					HL = 0;
X
X					for (k = 1; k <= 16; k++)
X					{
X						if (*cpm_drive[k])
X							HL |= c;
X
X						c <<= 1;
X					}
X
X					A = L;
X					break;
X
X				case 25:
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Return current disk\r\n");
#endif
X
X					A = def_drive - 1;
X					break;
X
X				case 26:
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Set DMA address\r\n");
#endif
X
X					dma = ram + DE;
X					break;
X
X				case 29:					/*  get R/O vector */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Get read only vector\r\n");
#endif
X
X					HL = 0;
X					break;
X
X				case 35:					/* get file size */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Compute file size\r\n");
#endif
X					fcb = (FCB *)(ram + DE);
X					if (stat(fcb2real(fcb), &s) == -1)
X					{
X						A = 0xff;
X						break;
X					}
X
X					A = 0;
X					/* fall through */
X
X				case 36:					/* set random record */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Set random record\r\n");
#endif
X
X					if (C == 36)
X					{
X						if ((fcb = (FCB *)(ram + DE)) == NULL)
X							break;
X
X						s.st_size = ftell(fcb->fp);
X					}
X
X					s.st_size >>= 7;
X					fcb->r0 = s.st_size & 0xff;
X					s.st_size >>= 8;
X					fcb->r1 = s.st_size & 0xff;
X					s.st_size >>= 8;
X					fcb->r2 = s.st_size & 0xff;
X
X					break;
X
X				case 37:					/* reset drive */
#ifdef	CPM_DEBUG
X					if (dasm_flag)
X						fprintf(stderr, "BDOS: Reset drive\r\n");
#endif
X
X					A = 0;
X					break;
X
X				default:
X					fprintf(stderr, "\a\r\nInvalid BDOS call %d\r\n", C);
X					return(-3);
X			}
X		}
X		else if (PC >= BIOS)
X		{
X			if (PC % 3)
X			{
X				fprintf(stderr, "\a\r\nInvalid BIOS jump 0%04.4x\r\n", pc);
X				PC = pc;
X				dump_registers(stderr);
X				return(-5);
X			}
X
#ifdef	CPM_DEBUG
X			if (dasm_flag)
X				fprintf(stderr, "BIOS: Function %d\r\n", (PC - BIOS)/3);
#endif
X
X			switch (PC)
X			{
X				case bios(0):
X					return(0);
X
X				default:
X					PC = pc;
X					fprintf(stderr, "Unimplemented BIOS jump 0%04.4xH\r\n", PC);
X					dump_registers(stderr);
X					return(-6);
X			}
X		}
X
X		if (decode())
X		{
X			PC = pc;
X			fprintf(stderr, "\a\r\nInvalid processor instruction 0x%02.2x\r\n", ram[PC]);
X			dump_registers(stderr);
X			return(-4);
X		}
X
#ifdef	CPM_DEBUG
X		if (dasm_flag > 1 && pc >= BDOS)
X			getch();
#endif
X	}
X
X	return(0);
}
#endif
X
FILE	*open_device(char *dev, char *typ)
{
X	FILE	*fp;
X
X	if (*dev == '!')
X		fp = popen(dev + 1, typ);
X	else
X		fp = fopen(dev, typ);
X
X	if (fp != NULL)
X		return(fp);
X
X	fprintf(stderr, "Error on %s\r\n", dev);
X	perror("Can't open virtual device");
X	exit(1);
X	return(NULL);
}
X
#ifndef	COMPILE_TEST
static int	do_command(char *cmd_str)
{
X	char	entry[256];
X	FILE	*fp;
X
X	if ((*cmd_str == ';') || (*cmd_str == '#'))
X		return(0);
X
X	strcpy(entry, cmd_str);
X
X	if (*entry == '!')
X	{
X		int		r;
X
X		ioctl(console, TCSETA, &old_term);
X		r = system(entry + 1);
X		ioctl(console, TCSETA, &termp);
X		return(r);
X	}
X
X	strtoup(entry);
X	tail = chop_cmd(entry);
X	user_break = 0;
X
X	if ((isspace(entry[2]) || (entry[2] == 0)) && (entry[1] == ':'))
X	{
X		*entry -= '@';
X
X		if ((*entry < 1) || (*entry > MAX_DRIVES) || (*cpm_drive[*entry] == 0))
X		{
X			fprintf(stderr, "\a\r\nInvalid drive specification\r\n");
SHAR_EOF
true || echo 'restore of cpm.c failed'
echo 'End of part 1, continue with part 2'
echo 2 > _shar_seq_.tmp
exit 0

-- 
D'Arcy J.M. Cain (darcy@druid)     |
D'Arcy Cain Consulting             |   I support gun control.
West Hill, Ontario, Canada         |   Let's start with the government!
+ 416 281 6094                     |