diff --git a/RunCPM/abstraction_posix.h b/RunCPM/abstraction_posix.h index 3410f37..b164693 100644 --- a/RunCPM/abstraction_posix.h +++ b/RunCPM/abstraction_posix.h @@ -1,6 +1,7 @@ #ifndef ABSTRACT_H #define ABSTRACT_H +#include #include #include #include @@ -15,6 +16,10 @@ #include #define millis() clock()/1000 +#ifdef STREAMIO +#include +#endif + // Lua scripting support #ifdef HASLUA #include "lua/lua.h" @@ -476,8 +481,101 @@ uint32 _HardwareIn(const uint32 Port) { /* Host initialization functions */ /*===============================================================================*/ +static void _file_failure_exit(char *argv[], char* fmt, char* filename) +{ + fprintf(stderr, "%s: ", argv[0]); + fprintf(stderr, fmt, filename); + if (errno) { + fprintf(stderr, ": %s", strerror(errno)); + } + fprintf(stderr, "\n"); + exit(EXIT_FAILURE); +} + +#ifdef STREAMIO +static void _usage(char *argv[]) { + fprintf(stderr, + "RunCPM - an emulator to run CP/M programs on modern hosts\n" + "usage: %s [-i input_file] [-o output_file] [-s]\n", argv[0]); + fprintf(stderr, + " -i input_file: console input will be read from the file " + "with the\ngiven name. " + "After input file's EOF, further console input\nwill be read " + "from the keyboard.\n"); + fprintf(stderr, + " -o output_file: console output will be written to the file " + "with the\ngiven name, in addition to the screen.\n"); + fprintf(stderr, + " -s: console input and output is connected directly to " + "stdin and stdout.\nSince on Posix keyboard input is read from " + "stdin, switching from\nstdin to keyboard on stdin EOF is a " + "no-op. Therefore stdin EOF is an\nerror condition on Posix in " + "this case.\n"); +} + +static void _fail_if_stdin_from_tty(char* argv[]) { + struct termios dummyTermios; + if (0 == tcgetattr(0, &dummyTermios) || + errno != ENOTTY) { + _file_failure_exit(argv, + "option -s is illegal when stdin comes from %s", + "tty"); + } +} + +static void _parse_options(int argc, char *argv[]) { + int c; + int errflg = 0; + while ((c = getopt(argc, argv, ":i:o:s")) != -1) { + switch(c) { + case 'i': + streamInputFile = fopen(optarg, "r"); + if (NULL == streamInputFile) { + _file_failure_exit(argv, + "error opening console input file %s", optarg); + } + streamInputActive = TRUE; + break; + case 'o': + streamOutputFile = fopen(optarg, "w"); + if (NULL == streamOutputFile) { + _file_failure_exit(argv, + "error opening console output file %s", optarg); + } + break; + case 's': + _fail_if_stdin_from_tty(argv); + streamInputFile = stdin; + streamOutputFile = stdout; + streamInputActive = TRUE; + consoleOutputActive = FALSE; + break; + case ':': /* -i or -o without operand */ + fprintf(stderr, + "Option -%c requires an operand\n", optopt); + errflg++; + break; + case '?': + fprintf(stderr, + "Unrecognized option: '-%c'\n", optopt); + errflg++; + } + } + if (errflg || optind != argc) { + _usage(argv); + exit(EXIT_FAILURE); + } +} +#endif + void _host_init(int argc, char* argv[]) { - int x = chdir(dirname(argv[0])); +#ifdef STREAMIO + _parse_options(argc, argv); +#endif + if (chdir(dirname(argv[0]))) { + _file_failure_exit(argv, "error performing chdir(%s)", + dirname(argv[0])); + } } /* Console abstraction functions */ @@ -504,6 +602,25 @@ void _console_reset(void) { tcsetattr(0, TCSANOW, &_old_term); } +#ifdef STREAMIO +extern void _streamioReset(void); + +static void _abort_if_kbd_eof() { + // On Posix, if !streamInputActive && streamInputFile == stdin, + // this means EOF on stdin. Assuming that stdin is connected to a + // file or pipe, further reading from stdin won't read from the + // keyboard but just continue to yield EOF. + // On Windows, this problem doesn't exist because of the separete + // conio.h. + if (!streamInputActive && streamInputFile == stdin) { + _puts("\nEOF on console input from stdin\n"); + _console_reset(); + _streamioReset(); + exit(EXIT_FAILURE); + } +} +#endif + int _kbhit(void) { struct pollfd pfds[1]; diff --git a/RunCPM/abstraction_vstudio.h b/RunCPM/abstraction_vstudio.h index cd869d4..fd55ad0 100644 --- a/RunCPM/abstraction_vstudio.h +++ b/RunCPM/abstraction_vstudio.h @@ -486,8 +486,91 @@ uint32 _HardwareIn(const uint32 Port) { /* Host initialization functions */ /*===============================================================================*/ -void _host_init(int argc, char* argv[]) { +#ifdef STREAMIO +static void _abort_if_kbd_eof() { +} + +static void _file_failure_exit(char *argv[], char* fmt, char* filename) +{ + fprintf(stderr, "%s: ", argv[0]); + fprintf(stderr, fmt, filename); + if (errno) { + fprintf(stderr, ": %s", strerror(errno)); + } + fprintf(stderr, "\n"); + exit(EXIT_FAILURE); +} + +static void _usage(char *argv[]) { + fprintf(stderr, + "RunCPM - an emulator to run CP/M programs on modern hosts\n" + "usage: %s [-i input_file] [-o output_file] [-s]\n", argv[0]); + fprintf(stderr, + " -i input_file: console input will be read from the file " + "with the\n given name. " + "After input file's EOF, further console input\n will be read " + "from the keyboard.\n"); + fprintf(stderr, + " -o output_file: console output will be written to the file " + "with the\n given name, in addition to the screen.\n"); + fprintf(stderr, + " -s: console input and output is connected directly to " + "stdin and stdout.\n"); +} + +#define SET_OPTARG ++i; if (i >= argc) {++errflg; break;} optarg = argv[i]; + +static void _parse_options(int argc, char *argv[]) { + int errflg = 0; + char *optarg; + for (int i = 1; i < argc && errflg == 0; ++i) { + if (strcmp("-i", argv[i]) == 0) { + /* ++i; + if (i >= argc) { + ++errflg; + break; + } + optarg = argv[i]; */ + SET_OPTARG + streamInputFile = fopen(optarg, "r"); + if (NULL == streamInputFile) { + _file_failure_exit(argv, + "error opening console input file %s", optarg); + } + streamInputActive = TRUE; + continue; + } + if (strcmp("-o", argv[i]) == 0) { + SET_OPTARG + streamOutputFile = fopen(optarg, "w"); + if (NULL == streamOutputFile) { + _file_failure_exit(argv, + "error opening console output file %s", optarg); + } + continue; + } + if (strcmp("-s", argv[i]) == 0) { + streamInputFile = stdin; + streamOutputFile = stdout; + streamInputActive = TRUE; + consoleOutputActive = FALSE; + continue; + } + fprintf(stderr, + "Unrecognized option: '%s'\n", argv[i]); + errflg++; + } + if (errflg) { + _usage(argv); + exit(EXIT_FAILURE); + } +} +#endif +void _host_init(int argc, char* argv[]) { +#ifdef STREAMIO + _parse_options(argc, argv); +#endif } /* Console abstraction functions */ @@ -511,6 +594,7 @@ void _console_init(void) { void _console_reset(void) { } + /* Implemented by conio.h int _kbhit(void) { diff --git a/RunCPM/console.h b/RunCPM/console.h index db524d5..c205552 100644 --- a/RunCPM/console.h +++ b/RunCPM/console.h @@ -10,19 +10,15 @@ uint8 mask8bit = 0x7f; // TO be used for masking 8 bit characters (XMODEM relat // required for XMODEM to work. // Use the CONSOLE7 and CONSOLE8 programs to change this on the fly. -uint8 _chready(void) // Checks if there's a character ready for input -{ - return(_kbhit() ? 0xff : 0x00); -} - -uint8 _getchNB(void) // Gets a character, non-blocking, no echo -{ - return(_kbhit() ? _getch() : 0x00); -} - void _putcon(uint8 ch) // Puts a character { +#ifdef STREAMIO + if (consoleOutputActive) _putch(ch & mask8bit); + if (streamOutputFile) fputc(ch & mask8bit, streamOutputFile); +#else _putch(ch & mask8bit); +#endif + } void _puts(const char* str) // Puts a \0 terminated string @@ -43,4 +39,82 @@ void _puthex16(uint16 w) // puts a HHHH hex string _puthex8(w & 0x00ff); } -#endif \ No newline at end of file +#ifdef STREAMIO +int _nextStreamInChar; + +void _getNextStreamInChar(void) +{ + _nextStreamInChar = streamInputFile ? fgetc(streamInputFile) : EOF; + if (EOF == _nextStreamInChar) { + streamInputActive = FALSE; + } +} + +uint8 _getStreamInChar(void) +{ + uint8 result = _nextStreamInChar; + _getNextStreamInChar(); + // TODO: delegate to abstrction_posix.h + if (0x0a == result) result = 0x0d; + return result; +} + +uint8 _getStreamInCharEcho() +{ + uint8 result = _getStreamInChar(); + _putcon(result); + return result; +} + +void _streamioInit(void) +{ + _getNextStreamInChar(); +} + +void _streamioReset(void) +{ + if (streamOutputFile) fclose(streamOutputFile); +} +#endif + +uint8 _chready(void) // Checks if there's a character ready for input +{ +#ifdef STREAMIO + if (streamInputActive) return 0xff; + // TODO: Consider adding/keeping _abort_if_kbd_eof() here. + _abort_if_kbd_eof(); +#endif + return(_kbhit() ? 0xff : 0x00); +} + +uint8 _getconNB(void) // Gets a character, non-blocking, no echo +{ +#ifdef STREAMIO + if (streamInputActive) return _getStreamInChar(); + // TODO: Consider adding/keeping _abort_if_kbd_eof() here. + _abort_if_kbd_eof(); +#endif + return(_kbhit() ? _getch() : 0x00); +} + +uint8 _getcon(void) // Gets a character, blocking, no echo +{ +#ifdef STREAMIO + if (streamInputActive) return _getStreamInChar(); + // TODO: Consider adding/keeping _abort_if_kbd_eof() here. + _abort_if_kbd_eof(); +#endif + return _getch(); +} + +uint8 _getconE(void) // Gets a character, blocking, with echo +{ +#ifdef STREAMIO + if (streamInputActive) return _getStreamInCharEcho(); + // TODO: Consider adding/keeping _abort_if_kbd_eof() here. + _abort_if_kbd_eof(); +#endif + return _getche(); +} + +#endif diff --git a/RunCPM/cpm.h b/RunCPM/cpm.h index ab8fb69..e2511fb 100644 --- a/RunCPM/cpm.h +++ b/RunCPM/cpm.h @@ -536,7 +536,7 @@ void _Bios(void) { break; } case B_CONIN: { // 3 - Console input - SET_HIGH_REGISTER(AF, _getch()); + SET_HIGH_REGISTER(AF, _getcon()); #ifdef DEBUG if (HIGH_REGISTER(AF) == 4) Debug = 1; @@ -698,7 +698,7 @@ void _Bdos(void) { Returns: A=Char */ case C_READ: { - HL = _getche(); + HL = _getconE(); #ifdef DEBUG if (HL == 4) Debug = 1; @@ -766,7 +766,7 @@ void _Bdos(void) { */ case C_RAWIO: { if (LOW_REGISTER(DE) == 0xff) { - HL = _getchNB(); + HL = _getconNB(); #ifdef DEBUG if (HL == 4) Debug = 1; @@ -853,7 +853,7 @@ void _Bdos(void) { // pre-backspace, retype & post backspace counts uint8 preBS = 0, reType = 0, postBS = 0; - chr = _getch(); //input a character + chr = _getcon(); //input a character if (chr == 1) { // ^A - Move cursor one character to the left if (curCol > 0) { diff --git a/RunCPM/cpu.h b/RunCPM/cpu.h index 6a94cff..54a6650 100644 --- a/RunCPM/cpu.h +++ b/RunCPM/cpu.h @@ -1343,7 +1343,7 @@ void Z80debug(void) { _puts("\r\n"); _puts("Command|? : "); - ch = _getch(); + ch = _getcon(); if (ch > 21 && ch < 127) _putch(ch); switch (ch) { @@ -2081,7 +2081,7 @@ static inline void Z80run(void) { #endif _puts("\r\n::CPU HALTED::"); // A halt is a good indicator of broken code _puts("Press any key..."); - _getch(); + _getcon(); #endif --PC; goto end_decode; diff --git a/RunCPM/disk.h b/RunCPM/disk.h index 933b9b2..543e4df 100644 --- a/RunCPM/disk.h +++ b/RunCPM/disk.h @@ -35,7 +35,7 @@ void _error(uint8 error) { _puts("\r\nCP/M ERR"); break; } - Status = _getch(); + Status = _getcon(); _puts("\r\n"); cDrive = oDrive = _RamRead(DSKByte) & 0x0f; Status = 2; diff --git a/RunCPM/globals.h b/RunCPM/globals.h index f4bd2d6..90e4dfa 100644 --- a/RunCPM/globals.h +++ b/RunCPM/globals.h @@ -93,6 +93,9 @@ //#define HASLUA // Will enable Lua scripting (BDOS call 254) // Should be passed externally per-platform with -DHASLUA +//#define STREAMIO // Will enable command line flags to read + // console input from file and to log console output to file + // Should be passed externally per-platform with -DSTREAMIO //#define PROFILE // For measuring time taken to run a CP/M command // This should be enabled only for debugging purposes when trying to improve emulation speed @@ -228,6 +231,15 @@ static uint8 firstBoot = TRUE; // True if this is the first boot static uint32 timer; +#ifdef STREAMIO +#include +static FILE *streamInputFile = NULL; +static FILE *streamOutputFile = NULL; +static uint8 streamInputActive = FALSE; +static uint8 consoleOutputActive = TRUE; +#endif + + /* Definition of externs to prevent precedence compilation errors */ #ifdef __cplusplus // If building on Arduino extern "C" diff --git a/RunCPM/main.c b/RunCPM/main.c index 33bbadd..9a33a09 100644 --- a/RunCPM/main.c +++ b/RunCPM/main.c @@ -61,6 +61,9 @@ int main(int argc, char* argv[]) { #endif _host_init(argc, &argv[0]); +#ifdef STREAMIO + _streamioInit(); +#endif _console_init(); _clrscr(); _puts(" CP/M Emulator v" VERSION " by Marcelo Dantas\r\n"); @@ -143,6 +146,9 @@ int main(int argc, char* argv[]) { _puts("\r\n"); _console_reset(); +#ifdef STREAMIO + _streamioReset(); +#endif return(0); }