mirror of https://github.com/wwarthen/RomWBW.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
550 lines
12 KiB
550 lines
12 KiB
#include "zxcc.h"
|
|
|
|
/* Global variables */
|
|
|
|
char* progname;
|
|
char** argv;
|
|
int argc;
|
|
|
|
byte cpm_drive;
|
|
byte cpm_user;
|
|
extern byte cpm_error;
|
|
|
|
char bindir80[CPM_MAXPATH] = "";
|
|
char libdir80[CPM_MAXPATH] = "";
|
|
char incdir80[CPM_MAXPATH] = "";
|
|
|
|
#ifndef _WIN32
|
|
struct termios tc_orig;
|
|
#endif
|
|
|
|
byte RAM[65536]; /* The Z80's address space */
|
|
|
|
void load_comfile(void); /* Forward declaration */
|
|
|
|
static int deinit_term, deinit_gsx;
|
|
static void mkpath(char* fullpath, char* path, char* subdir);
|
|
|
|
void dump_regs(FILE* fp, byte a, byte b, byte c, byte d, byte e, byte f,
|
|
byte h, byte l, word pc, word ix, word iy)
|
|
{
|
|
fprintf(fp, "\tAF=%02x%02x BC=%02x%02x DE=%02x%02x HL=%02x%02x\n"
|
|
"\tIX=%04x IY=%04x PC=%04x\n",
|
|
a, f, b, c, d, e, h, l, pc, ix, iy);
|
|
}
|
|
|
|
char* parse_to_fcb(char* s, int afcb)
|
|
{
|
|
byte* fcb = &RAM[afcb + 1];
|
|
|
|
RAM[afcb] = 0;
|
|
memset(fcb, ' ', 11);
|
|
|
|
while (s[0] == ' ') /* skip leading spaces */
|
|
{
|
|
s++;
|
|
}
|
|
while (1)
|
|
{
|
|
if (s[0] == 0) break;
|
|
if (s[0] == ' ') break;
|
|
if (s[1] == ':')
|
|
{
|
|
RAM[afcb] = s[0] - '@';
|
|
if (RAM[afcb] > 16) RAM[afcb] -= 0x20;
|
|
s += 2;
|
|
continue;
|
|
}
|
|
if (s[0] == '.')
|
|
{
|
|
++s;
|
|
fcb = &RAM[afcb + 9];
|
|
continue;
|
|
}
|
|
*fcb = *s; if (islower(*fcb)) *fcb = toupper(*fcb);
|
|
++s;
|
|
++fcb;
|
|
if (fcb >= &RAM[afcb + 12]) break;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
void ed_fe(byte* a, byte* b, byte* c, byte* d, byte* e, byte* f,
|
|
byte* h, byte* l, word* pc, word* ix, word* iy)
|
|
{
|
|
switch (*a)
|
|
{
|
|
case 0xC0:
|
|
cpmbdos(a, b, c, d, e, f, h, l, pc, ix, iy);
|
|
break;
|
|
|
|
case 0xC1:
|
|
load_comfile();
|
|
break;
|
|
|
|
case 0xC2:
|
|
fprintf(stderr, "%s: Incompatible BIOS.BIN\n", progname);
|
|
zxcc_term();
|
|
zxcc_exit(1);
|
|
|
|
case 0xC3:
|
|
cpmbios(a, b, c, d, e, f, h, l, pc, ix, iy);
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "%s: Z80 encountered invalid trap\n", progname);
|
|
dump_regs(stderr, *a, *b, *c, *d, *e, *f, *h, *l, *pc, *ix, *iy);
|
|
zxcc_term();
|
|
zxcc_exit(1);
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
* load_bios() loads the minimal CP/M BIOS and BDOS.
|
|
*
|
|
*/
|
|
|
|
void load_bios(void)
|
|
{
|
|
char dir[CPM_MAXPATH + 1], fname[CPM_MAXPATH + 1];
|
|
char* q;
|
|
size_t bios_len;
|
|
|
|
FILE* fp = fopen("bios.bin", "rb");
|
|
if (!fp)
|
|
{
|
|
strcpy(fname, bindir80);
|
|
strcat(fname, "bios.bin");
|
|
fp = fopen(fname, "rb");
|
|
}
|
|
if (!fp)
|
|
{
|
|
#ifdef _WIN32
|
|
dir[0] = 0; /* use strncat in case the path is very long */
|
|
strncat(dir, _pgmptr, CPM_MAXPATH - 8); /* copy the executable path */
|
|
#elif defined(__APPLE__)
|
|
uint32_t size = CPM_MAXPATH - 8;
|
|
_NSGetExecutablePath(dir, &size);
|
|
#else
|
|
readlink("/proc/self/exe", dir, CPM_MAXPATH - 8); /* allow room for bios.bin */
|
|
#endif
|
|
q = strrchr(dir, DIRSEPCH);
|
|
*++q = 0;
|
|
strcpy(fname, dir);
|
|
strcat(fname, "bios.bin");
|
|
fp = fopen(fname, "rb");
|
|
}
|
|
if (!fp)
|
|
{
|
|
fprintf(stderr, "%s: Cannot locate bios.bin\n", progname);
|
|
zxcc_term();
|
|
zxcc_exit(1);
|
|
}
|
|
bios_len = fread(RAM + 0xFE00, 1, 512, fp);
|
|
if (bios_len < 1 || ferror(fp))
|
|
{
|
|
fclose(fp);
|
|
fprintf(stderr, "%s: Cannot load bios.bin\n", progname);
|
|
zxcc_term();
|
|
zxcc_exit(1);
|
|
}
|
|
fclose(fp);
|
|
|
|
DBGMSGV("Loaded %d bytes of BIOS\n", bios_len);
|
|
}
|
|
/*
|
|
* try_com() attempts to open file, file.com, file.COM, file.cpm and file.CPM
|
|
*
|
|
*/
|
|
|
|
FILE* try_com(char* s)
|
|
{
|
|
char fname[CPM_MAXPATH + 1];
|
|
FILE* fp;
|
|
|
|
strcpy(fname, s);
|
|
fp = fopen(s, "rb"); if (fp) return fp;
|
|
sprintf(s, "%s.com", fname); fp = fopen(s, "rb"); if (fp) return fp;
|
|
sprintf(s, "%s.COM", fname); fp = fopen(s, "rb"); if (fp) return fp;
|
|
sprintf(s, "%s.cpm", fname); fp = fopen(s, "rb"); if (fp) return fp;
|
|
sprintf(s, "%s.CPM", fname); fp = fopen(s, "rb"); if (fp) return fp;
|
|
|
|
strcpy(s, fname);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* load_comfile() loads the COM file whose name was passed as a parameter.
|
|
*
|
|
*/
|
|
|
|
|
|
void load_comfile(void)
|
|
{
|
|
size_t com_len;
|
|
char fname[CPM_MAXPATH + 1];
|
|
FILE* fp;
|
|
|
|
/* Look in current directory first */
|
|
strcpy(fname, argv[1]);
|
|
fp = try_com(fname);
|
|
if (!fp)
|
|
{
|
|
strcpy(fname, bindir80);
|
|
strcat(fname, argv[1]);
|
|
fp = try_com(fname);
|
|
}
|
|
if (!fp)
|
|
{
|
|
fprintf(stderr, "%s: Cannot locate %s, %s.com, %s.COM, %s.cpm _or_ %s.CPM\n",
|
|
progname, argv[1], argv[1], argv[1], argv[1], argv[1]);
|
|
zxcc_term();
|
|
zxcc_exit(1);
|
|
}
|
|
com_len = fread(RAM + 0x0100, 1, 0xFD00, fp);
|
|
if (com_len < 1 || ferror(fp))
|
|
{
|
|
fclose(fp);
|
|
fprintf(stderr, "%s: Cannot load %s\n", progname, fname);
|
|
zxcc_term();
|
|
zxcc_exit(1);
|
|
}
|
|
fclose(fp);
|
|
|
|
/* read() can corrupt buffer area following data read if length
|
|
* of data read is less than buffer. Clean it up. */
|
|
memset(RAM + 0x0100 + com_len, 0, 0xFD00 - com_len);
|
|
|
|
DBGMSGV("Loaded %d bytes from %s\n", com_len, fname);
|
|
}
|
|
|
|
unsigned int in() { return 0; }
|
|
unsigned int out() { return 0; }
|
|
|
|
/*
|
|
* xltname: Convert a unix filepath into a CP/M compatible drive:name form.
|
|
* The unix filename must be 8.3 or the CP/M code will reject it.
|
|
*
|
|
* This uses the library xlt_name to do the work, and then just strcat()s
|
|
* the result to the command line.
|
|
*/
|
|
|
|
void zxcc_xltname(char* name, char* pcmd)
|
|
{
|
|
char nbuf[CPM_MAXPATH + 1];
|
|
|
|
xlt_name(pcmd, nbuf);
|
|
|
|
strcat(name, nbuf);
|
|
}
|
|
|
|
/* main() parses the arguments to CP/M form. argv[1] is the name of the CP/M
|
|
program to load; the remaining arguments are arguments for the CP/M program.
|
|
|
|
main() also loads the vestigial CP/M BIOS and does some sanity checks
|
|
on the endianness of the host CPU and the sizes of data types.
|
|
*/
|
|
|
|
int main(int ac, char** av)
|
|
{
|
|
int n;
|
|
char* pCmd, * str;
|
|
char* tmpenv;
|
|
|
|
argc = ac;
|
|
argv = av;
|
|
#ifdef __PACIFIC__ /* Pacific C doesn't support argv[0] */
|
|
progname = "ZXCC";
|
|
#endif
|
|
progname = argv[0];
|
|
|
|
/* DJGPP includes the whole path in the program name, which looks
|
|
* untidy...
|
|
*/
|
|
while ((str = strpbrk(progname, DIRSEP)))
|
|
progname = str + 1;
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "\n\n");
|
|
DBGMSG("Start of execution: ");
|
|
for (n = 0; n < argc; n++)
|
|
fprintf(stderr, " %s", argv[n]);
|
|
fprintf(stderr, "\n");
|
|
#endif
|
|
|
|
term_init();
|
|
|
|
if (sizeof(int) > 8 || sizeof(byte) != 1 || sizeof(word) != 2)
|
|
{
|
|
fprintf(stderr, "%s: type lengths incorrect; edit typedefs "
|
|
"and recompile.\n", progname);
|
|
zxcc_exit(1);
|
|
}
|
|
|
|
if (argc < 2)
|
|
{
|
|
fprintf(stderr, "%s: No CP/M program name provided.\n", progname);
|
|
zxcc_exit(1);
|
|
}
|
|
|
|
/* Parse arguments. An argument can be either:
|
|
|
|
* preceded by a '-', in which case it is copied in as-is, less the
|
|
dash;
|
|
* preceded by a '+', in which case it is parsed as a filename and
|
|
then concatenated to the previous argument;
|
|
* preceded by a '+-', in which case it is concatenated without
|
|
parsing;
|
|
* not preceded by either, in which case it is parsed as a filename.
|
|
|
|
So, the argument string "--a -b c +-=q --x +/dev/null" would be rendered
|
|
into CP/M form as "-a b p:c=q -xd:null" */
|
|
|
|
if (!fcb_init())
|
|
{
|
|
fprintf(stderr, "Could not initialise CPMREDIR library\n");
|
|
zxcc_exit(1);
|
|
}
|
|
|
|
#ifdef FILETRACKER
|
|
DBGMSG("File tracking is ENABLED\n");
|
|
#else
|
|
DBGMSG("File tracking is DISABLED\n");
|
|
#endif
|
|
|
|
/* allow environment variables to override default locations */
|
|
/* two options are supported, explicit overrides for each directory
|
|
* (BINDIR80, LIBDIR80, INCDIR80)
|
|
* or a common directory prefix override (CPMDIR80)
|
|
* the explict override takes precedence
|
|
*/
|
|
if ((tmpenv = getenv("CPMDIR80"))) {
|
|
mkpath(bindir80, tmpenv, BIN80); /* use CPMDIR80 & std subdirs */
|
|
mkpath(libdir80, tmpenv, LIB80);
|
|
mkpath(incdir80, tmpenv, INC80);
|
|
}
|
|
if ((tmpenv = getenv("BINDIR80")))
|
|
mkpath(bindir80, tmpenv, "");
|
|
|
|
if ((tmpenv = getenv("LIBDIR80")))
|
|
mkpath(libdir80, tmpenv, "");
|
|
|
|
if ((tmpenv = getenv("INCDIR80")))
|
|
mkpath(incdir80, tmpenv, "");
|
|
|
|
DBGMSGV("BINDIR80=\"%s\"\n", bindir80);
|
|
DBGMSGV("LIBDIR80=\"%s\"\n", libdir80);
|
|
DBGMSGV("INCDIR80=\"%s\"\n", incdir80);
|
|
|
|
xlt_map(0, bindir80); /* Establish the 3 fixed mappings */
|
|
xlt_map(1, libdir80);
|
|
xlt_map(2, incdir80);
|
|
|
|
pCmd = (char*)RAM + 0x81;
|
|
|
|
for (n = 2; n < argc; n++)
|
|
{
|
|
if (argv[n][0] == '+' && argv[n][1] == '-')
|
|
{
|
|
/* Append, no parsing */
|
|
strcat(pCmd, argv[n] + 2);
|
|
}
|
|
else if (!argv[n][0] || argv[n][0] == '-')
|
|
{
|
|
/* Append with space; no parsing. */
|
|
strcat(pCmd, " ");
|
|
strcat(pCmd, argv[n] + 1);
|
|
}
|
|
else if (argv[n][0] == '+')
|
|
{
|
|
zxcc_xltname(pCmd, argv[n] + 1);
|
|
}
|
|
else /* Translate a filename */
|
|
{
|
|
strcat(pCmd, " ");
|
|
zxcc_xltname(pCmd, argv[n]);
|
|
}
|
|
|
|
}
|
|
pCmd[0x7F] = 0; /* Truncate to fit the buffer */
|
|
RAM[0x80] = (byte)strlen(pCmd);
|
|
|
|
str = parse_to_fcb(pCmd, 0x5C);
|
|
parse_to_fcb(str, 0x6C);
|
|
|
|
// This statement is very useful when creating a client like zxc or zxas
|
|
DBGMSGV("Command tail is \"%s\"\n", pCmd);
|
|
|
|
load_bios();
|
|
|
|
memset(RAM + 0xFE9C, 0, 0x64); /* Zap the SCB */
|
|
RAM[0xFE98] = 0x06;
|
|
RAM[0xFE99] = 0xFE; /* FE06, BDOS entry */
|
|
RAM[0xFEA1] = 0x31; /* BDOS 3.1 */
|
|
RAM[0xFEA8] = 0x01; /* UK date format */
|
|
RAM[0xFEAF] = 0x0F; /* CCP drive */
|
|
|
|
#ifdef USE_CPMIO
|
|
RAM[0xFEB6] = cpm_term_direct(CPM_TERM_WIDTH, -1);
|
|
RAM[0xFEB8] = cpm_term_direct(CPM_TERM_HEIGHT, -1);
|
|
#else
|
|
RAM[0xFEB6] = 79;
|
|
RAM[0xFEB8] = 23;
|
|
#endif
|
|
RAM[0xFED1] = 0x80; /* Buffer area */
|
|
RAM[0xFED2] = 0xFF;
|
|
RAM[0xFED3] = '$';
|
|
RAM[0xFED6] = 0x9C;
|
|
RAM[0xFED7] = 0xFE; /* SCB address */
|
|
RAM[0xFED8] = 0x80; /* DMA address */
|
|
RAM[0xFED9] = 0x00;
|
|
RAM[0xFEDA] = 0x0F; /* P: */
|
|
RAM[0xFEE6] = 0x01; /* Multi sector count */
|
|
RAM[0xFEFE] = 0x06;
|
|
RAM[0xFEFF] = 0xFE; /* BDOS */
|
|
|
|
cpm_drive = 0x0F; /* Start logged into P: */
|
|
cpm_user = 0; /* and user 0 */
|
|
|
|
#ifdef USE_CPMIO
|
|
cpm_scr_init(); deinit_term = 1;
|
|
#endif
|
|
#ifdef USE_CPMGSX
|
|
gsx_init(); deinit_gsx = 1;
|
|
#endif
|
|
|
|
/* Start the Z80 at 0xFF00, with stack at 0xFE00 */
|
|
mainloop(0xFF00, 0xFE00);
|
|
|
|
return zxcc_term();
|
|
}
|
|
|
|
void zxcc_exit(int code)
|
|
{
|
|
#ifdef USE_CPMIO
|
|
if (deinit_term) cpm_scr_unit();
|
|
#endif
|
|
#ifdef USE_CPMGSX
|
|
if (deinit_gsx) gsx_deinit();
|
|
#endif
|
|
exit(code);
|
|
}
|
|
|
|
int zxcc_term(void)
|
|
{
|
|
word n;
|
|
|
|
//n = RAM[0x81]; /* Get the return code. This is Hi-Tech C */
|
|
//n = (n << 8) | RAM[0x80]; /* specific and fails with other COM files */
|
|
n = 0;
|
|
|
|
putchar('\n');
|
|
|
|
if (cpm_error != 0) /* The CP/M "set return code" call was used */
|
|
{ /* (my modified Hi-Tech C library uses this */
|
|
n = cpm_error; /* call) */
|
|
}
|
|
|
|
if (n < 256 || n == 0xFFFF)
|
|
DBGMSGV("Return code %d\n", n);
|
|
else
|
|
n = 0;
|
|
|
|
term_reset();
|
|
|
|
return n;
|
|
}
|
|
|
|
/* helper function to build full path */
|
|
/* make sure that a / or \ is present at the end of path
|
|
* before appending the subdir
|
|
*/
|
|
static void mkpath(char* fullpath, char* path, char* subdir) {
|
|
char* s;
|
|
strcpy(fullpath, path);
|
|
s = strchr(fullpath, '\0');
|
|
if (*fullpath && !ISDIRSEP(s[-1])) /* make sure we have dir sep */
|
|
*s++ = '/';
|
|
strcpy(s, subdir);
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
|
|
void raw_init(void)
|
|
{
|
|
struct termios tc_raw;
|
|
|
|
DBGMSG("Enabling RAW Terminal IO\n");
|
|
|
|
if (tcgetattr(STDIN_FILENO, &tc_orig) == -1)
|
|
{
|
|
DBGMSG("Failed to enable RAW Terminal IO - tcgetattr() failed\n");
|
|
zxcc_exit(1);;
|
|
}
|
|
|
|
//tc_raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
|
//tc_raw.c_oflag &= ~(OPOST);
|
|
//tc_raw.c_cflag |= (CS8);
|
|
//tc_raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
|
//tc_raw.c_cc[VMIN] = 1;
|
|
//tc_raw.c_cc[VTIME] = 0;
|
|
|
|
tc_raw.c_iflag &= ~(ICRNL | INPCK | ISTRIP | IXON);
|
|
tc_raw.c_oflag &= ~(OPOST);
|
|
tc_raw.c_cflag |= (CS8);
|
|
tc_raw.c_lflag &= ~(ECHO | ICANON | IEXTEN);
|
|
tc_raw.c_cc[VMIN] = 1;
|
|
tc_raw.c_cc[VTIME] = 0;
|
|
|
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tc_raw) == -1)
|
|
{
|
|
DBGMSG("Failed to enable RAW Terminal IO - tcsetattr() failed\n");
|
|
zxcc_exit(1);
|
|
}
|
|
|
|
DBGMSG("Enabled RAW Terminal IO\n");
|
|
|
|
return;
|
|
}
|
|
|
|
void deinit_raw(void)
|
|
{
|
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tc_orig) == -1)
|
|
{
|
|
DBGMSG("Failed to disable RAW Terminal IO - tcsetattr() failed\n");
|
|
return;
|
|
}
|
|
|
|
DBGMSG("Disabled RAW Terminal IO\n");
|
|
|
|
return;
|
|
}
|
|
|
|
#endif
|
|
|
|
void term_init(void)
|
|
{
|
|
#ifdef _WIN32
|
|
setmode(STDIN_FILENO, O_BINARY);
|
|
setmode(STDOUT_FILENO, O_BINARY);
|
|
#else
|
|
if (_isatty(STDIN_FILENO))
|
|
raw_init();
|
|
#endif
|
|
|
|
if (_isatty(STDIN_FILENO))
|
|
DBGMSG("Using interactive console mode\n");
|
|
else
|
|
DBGMSG("Using standard input/output mode\n");
|
|
|
|
return;
|
|
}
|
|
|
|
void term_reset(void)
|
|
{
|
|
#ifndef _WIN32
|
|
if (_isatty(STDIN_FILENO))
|
|
deinit_raw();
|
|
#endif
|
|
}
|
|
|