diff --git a/RunCPM/ccp.h b/RunCPM/ccp.h index 261d084..88137ca 100644 --- a/RunCPM/ccp.h +++ b/RunCPM/ccp.h @@ -24,7 +24,7 @@ bool sFlag = FALSE; // Submit Flag uint8 sRecs = 0; // Number of records on the Submit file uint8 prompt[8] = "\r\n >"; uint16 pbuf, perr; -uint8 blen; // Actual size of the typed command line (size of the buffer) +uint8 blen = 0; // Actual size of the typed command line (size of the buffer) static const char *Commands[] = { @@ -685,22 +685,54 @@ void _ccp(void) { sFlag = (bool)_ccp_bdos(DRV_ALLRESET, 0x0000); _ccp_bdos(DRV_SET, curDrive); - for (i = 0; i < 36; ++i) + for (i = 0; i < 36; ++i) { _RamWrite(BatchFCB + i, _RamRead(tmpFCB + i)); - + } + + // Loads an autoexec file if it exists and this is the first boot + // The file contents are loaded at ccpAddr+8 up to 126 bytes then the size loaded is stored at ccpAddr+7 + if (firstBoot && !sFlag) { + uint8 dmabuf[128]; + uint16 cmd = inBuf + 1; + if (_sys_exists((uint8*)AUTOEXEC)) { + FILE* file = _sys_fopen_r((uint8*)AUTOEXEC); + blen = (uint8)_sys_fread(&dmabuf[0], 1, 128, file); + int count = 0; + if (blen) { + for (int i = 0; i < 126; ++i) { + _RamWrite(cmd + 1 + i, 0x00); + if (dmabuf[i] == 0x0D || dmabuf[i] == 0x0A || dmabuf[i] == 0x1A || dmabuf[i] == 0x00) { + break; + } + _RamWrite(cmd + 1 + i, dmabuf[i]); + count++; + } + } + _RamWrite(cmd, count); + _sys_fclose(file); + } + if (BOOTONLY) + firstBoot = FALSE; + } else { + _RamWrite(inBuf, 0); // Clears the buffer + _RamWrite(inBuf + 1, 0); // Clears the buffer + blen = 0; + } + while (TRUE) { curDrive = (uint8)_ccp_bdos(DRV_GET, 0x0000); // Get current drive curUser = (uint8)_ccp_bdos(F_USERNUM, 0x00FF); // Get current user _RamWrite(DSKByte, (curUser << 4) + curDrive); // Set user/drive on addr DSKByte parDrive = curDrive; // Initially the parameter drive is the same as the current drive - + sprintf((char *) prompt, "\r\n%c%u%c", 'A' + curDrive, curUser, sFlag ? '$' : '>'); - _puts((char *)prompt); - - _RamWrite(inBuf, cmdLen); // Sets the buffer size to read the command line - _ccp_readInput(); - + if(!blen){ + _puts((char *)prompt); + + _RamWrite(inBuf, cmdLen); // Sets the buffer size to read the command line + _ccp_readInput(); + } blen = _RamRead(inBuf + 1); // Obtains the number of bytes read _ccp_bdos(F_DMAOFF, defDMA); // Reset current DMA @@ -714,8 +746,10 @@ void _ccp(void) { } if (!blen) // There were only spaces continue; - if (_RamRead(pbuf) == ';') // Found a comment line + if (_RamRead(pbuf) == ';') { // Found a comment line + blen = 0; // Ignore the rest of the line continue; + } // parse for DU: command line shortcut bool errorFlag = FALSE, continueFlag = FALSE; @@ -757,9 +791,11 @@ void _ccp(void) { } if (errorFlag) { _ccp_cmdError(); // print command error + blen = 0; // ignore the rest of the line continue; } if (continueFlag) { + blen = 0; // ignore the rest of the line continue; } _ccp_initFCB(CmdFCB, 36); // Initializes the command FCB @@ -767,6 +803,7 @@ void _ccp(void) { perr = pbuf; // Saves the pointer in case there's an error if (_ccp_nameToFCB(CmdFCB) > 8) { // Extracts the command from the buffer _ccp_cmdError(); // Command name cannot be non-unique or have an extension + blen = 0; // ignore the rest of the line continue; } _RamWrite(defDMA, blen); // Move the command line at this point to 0x0080 diff --git a/RunCPM/globals.h b/RunCPM/globals.h index bb1f09f..90e4dfa 100644 --- a/RunCPM/globals.h +++ b/RunCPM/globals.h @@ -14,7 +14,7 @@ #define USE_LST /* Definitions for file/console based debugging */ -//#define DEBUG // Enables the internal debugger (enabled by default on vstudio debug builds) +#define DEBUG // Enables the internal debugger (enabled by default on vstudio debug builds) //#define DEBUGONHALT // Enables the internal debugger when the CPU halts //#define iDEBUG // Enables instruction logging onto iDebug.log (for development debug only) //#define DEBUGLOG // Writes extensive call trace information to RunCPM.log @@ -224,6 +224,11 @@ static uint16 physicalExtentBytes;// # bytes described by 1 directory entry #define tohex(x) ((x) < 10 ? (x) + 48 : (x) + 87) +/* definition of an autoexec functionality */ +static uint8 firstBoot = TRUE; // True if this is the first boot +#define AUTOEXEC "AUTOEXEC.TXT" // Name of the autoexec file +#define BOOTONLY FALSE // If TRUE, the autoexec file will only be loaded on the first boot + static uint32 timer; #ifdef STREAMIO diff --git a/RunCPM/main.c b/RunCPM/main.c index 9d462bd..9a33a09 100644 --- a/RunCPM/main.c +++ b/RunCPM/main.c @@ -99,6 +99,34 @@ int main(int argc, char* argv[]) { break; } _RamLoad((uint8*)CCPname, CCPaddr); // Loads the CCP binary file into memory + + // Loads an autoexec file if it exists and this is the first boot + // The file contents are loaded at ccpAddr+8 up to 126 bytes then the size loaded is stored at ccpAddr+7 + if (firstBoot an not sFlag) { + uint8 dmabuf[128]; + uint8 bytesread; + uint16 cmd = CCPaddr + 7; + if (_sys_exists((uint8*)AUTOEXEC)) { + FILE* file = _sys_fopen_r((uint8*)AUTOEXEC); + bytesread = (uint8)_sys_fread(&dmabuf[0], 1, 128, file); + int count = 0; + if (bytesread) { + for (int i = 0; i < 126; ++i) { + _RamWrite(cmd + 1 + i, 0x00); + if (dmabuf[i] == 0x0D || dmabuf[i] == 0x0A || dmabuf[i] == 0x1A || dmabuf[i] == 0x00) { + break; + } + _RamWrite(cmd + 1 + i, dmabuf[i]); + count++; + } + } + _RamWrite(cmd, count); + _sys_fclose(file); + } + if (BOOTONLY) + firstBoot = FALSE; + } + Z80reset(); // Resets the Z80 CPU SET_LOW_REGISTER(BC, _RamRead(DSKByte)); // Sets C to the current drive/user PC = CCPaddr; // Sets CP/M application jump point diff --git a/readme.md b/readme.md index ba9e295..f5a3bbd 100644 --- a/readme.md +++ b/readme.md @@ -129,6 +129,11 @@ Disks created by **FORMAT** cannot be removed from inside RunCPM itself, if need The master disk also contains **Z80ASM**, which is a very powerful Z80 assembly that generates .COM files directly.
Other CP/M applications which were not part of the official DRI's distribution are also provided to improve the RunCPM experience. These applications are listed on the 1STREAD.ME file. +## Automation + +If a single line text file named AUTOEXEC.TXT containing a CP/M command up to 125 characters long is placed onto the same folder as the RunCPM executable, the command on this file will be loaded onto the CCP buffer, emulating the patch of the CCP sector on a real CP/M disk.
+This command will then be executed every time the CCP is restarted or once when RunCPM loads, which is configurable in the globals.h header file. + ## Printing Printing to the PUN: and LST: devices is allowed and will generate files called "PUN.TXT" and "LST.TXT" under user area 0 of disk A:. These files can then be tranferred over to a host computer via XMODEM for real physical printing. @@ -300,6 +305,7 @@ I dedicate it also to the memory of some awesome people who unfortunately are no * *Mr. Jon Saxton* - For finding the very first RunCPM bug back in 2014.
* *Dr. Richard Walters* - For writing the Z80 version of mumps, one of the most useful and fun languages I had the joy to use.
* *Mr. Tom L. Burnett* - For helping me with testing/debugging many different CP/M 2.2 applications on RunCPM.
+ May the computers in heaven be all 8-bit.
## Donations diff --git a/tools/gensub.c b/tools/gensub.c new file mode 100644 index 0000000..d14f57e --- /dev/null +++ b/tools/gensub.c @@ -0,0 +1,92 @@ +/* + gensub - Generates a $$$.SUB file from a text file passed as argument and saves it to a destination also passed as argument. + Usage: gensub +*/ +#include +#include +#include + +#define BUFSIZE 128 + +int main(int argc, char* argv[]) { + // Check if the user passed a file as argument + if (argc < 2) { + printf("Usage: %s \n", argv[0]); + return 1; + } + + // Check if the user passed a destination as argument + if (argc < 3) { + printf("Usage: %s \n", argv[0]); + return 1; + } + + // Open the input file + FILE* file = fopen(argv[1], "r"); + if (file == NULL) { + printf("Error: Could not open input file %s\n", argv[1]); + return 1; + } + // Count the number of lines in the file + int lines = 0; + char buffer[BUFSIZE]; + while (fgets(buffer, sizeof(buffer), file) != NULL) { + lines++; + } + // Reset the file pointer + fseek(file, 0, SEEK_SET); + + // Check if the file is empty + if (lines == 0) { + printf("Error: File is empty\n"); + fclose(file); + return 1; + } + + // Allocate 128 bytes for each axisting line + char* subfile = (char*)malloc(lines * 128); + if (subfile == NULL) { + printf("Error: Could not allocate memory\n"); + fclose(file); + return 1; + } + + // Read the file line by line and copy it to the subfile buffer in reverse order of 128 byte blocks + int offset = (lines - 1) * 128; + while (fgets(buffer, sizeof(buffer), file) != NULL) { + int len = strlen(buffer); + if (len > 0) { + if (buffer[len - 1] == '\n') { + buffer[len - 1] = '\0'; + len--; + } + } + if (len > 0) { + if (buffer[len - 1] == '\r') { + buffer[len - 1] = '\0'; + len--; + } + } + subfile[offset] = len; + memcpy(subfile + offset + 1, buffer, len); + offset -= 128; + } + + // Close the input file + fclose(file); + + // Write the subfile buffer to the destination file + FILE* dest = fopen(argv[2], "w"); + if (dest == NULL) { + printf("Error: Could not open output file %s\n", argv[2]); + free(subfile); + return 1; + } + + // Write the subfile buffer to the destination file + fwrite(subfile, 1, lines * 128, dest); + + // Close the destination file + fclose(dest); + +} \ No newline at end of file