From 7be035defb120072dbdbae4f6c940a57dbe4e6dc Mon Sep 17 00:00:00 2001 From: MockbaTheBorg Date: Tue, 7 May 2024 09:05:20 -0400 Subject: [PATCH 1/8] Implement autoexec functionality as in CP/M 2.2 --- RunCPM/ccp.h | 47 ++++++++++++++++++++++++++++++++++++++--------- RunCPM/globals.h | 7 ++++++- RunCPM/main.c | 27 +++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/RunCPM/ccp.h b/RunCPM/ccp.h index 261d084..635eef6 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,50 @@ 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) { + 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; + } + 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 @@ -876,6 +904,7 @@ void _ccp(void) { if (i) _ccp_cmdError(); } + blen = 0; if ((Status == 1) || (Status == 2)) break; } diff --git a/RunCPM/globals.h b/RunCPM/globals.h index 6f4c19f..ffd729a 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 @@ -221,6 +221,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.BAT" // Name of the autoexec file +#define BOOTONLY TRUE // If TRUE, the autoexec file will only be loaded on the first boot + static uint32 timer; /* Definition of externs to prevent precedence compilation errors */ diff --git a/RunCPM/main.c b/RunCPM/main.c index 46554a1..6cd61aa 100644 --- a/RunCPM/main.c +++ b/RunCPM/main.c @@ -96,6 +96,33 @@ 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) { + 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 From 4d21a8bfa1835f5bf0e154255a1124d56c890613 Mon Sep 17 00:00:00 2001 From: MockbaTheBorg Date: Tue, 7 May 2024 09:14:29 -0400 Subject: [PATCH 2/8] Set default to FALSE to match CP/M 2.2 behavior. --- RunCPM/globals.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RunCPM/globals.h b/RunCPM/globals.h index ffd729a..fa85003 100644 --- a/RunCPM/globals.h +++ b/RunCPM/globals.h @@ -224,7 +224,7 @@ static uint16 physicalExtentBytes;// # bytes described by 1 directory entry /* definition of an autoexec functionality */ static uint8 firstBoot = TRUE; // True if this is the first boot #define AUTOEXEC "AUTOEXEC.BAT" // Name of the autoexec file -#define BOOTONLY TRUE // If TRUE, the autoexec file will only be loaded on the first boot +#define BOOTONLY FALSE // If TRUE, the autoexec file will only be loaded on the first boot static uint32 timer; From ebc4cd1ef7187c80a401bb61aaca444ad08c74a8 Mon Sep 17 00:00:00 2001 From: MockbaTheBorg Date: Tue, 7 May 2024 09:27:46 -0400 Subject: [PATCH 3/8] Added gensub.c to the repository --- tools/gensub.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 tools/gensub.c 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 From 25188f38a6009ad8ebb148f845441bc14b4e63bd Mon Sep 17 00:00:00 2001 From: MockbaTheBorg Date: Tue, 7 May 2024 09:32:53 -0400 Subject: [PATCH 4/8] Renamed default autoexec to avoid confusion with DOS/Windows --- RunCPM/globals.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RunCPM/globals.h b/RunCPM/globals.h index fa85003..f4bd2d6 100644 --- a/RunCPM/globals.h +++ b/RunCPM/globals.h @@ -223,7 +223,7 @@ static uint16 physicalExtentBytes;// # bytes described by 1 directory entry /* definition of an autoexec functionality */ static uint8 firstBoot = TRUE; // True if this is the first boot -#define AUTOEXEC "AUTOEXEC.BAT" // Name of the autoexec file +#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; From 4eefdc81dfc2eddbd89be8684cfb44fdce1c1894 Mon Sep 17 00:00:00 2001 From: MockbaTheBorg Date: Tue, 7 May 2024 09:57:25 -0400 Subject: [PATCH 5/8] Fix for tight loop introduced by autoexec --- RunCPM/ccp.h | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/RunCPM/ccp.h b/RunCPM/ccp.h index 635eef6..b7bf295 100644 --- a/RunCPM/ccp.h +++ b/RunCPM/ccp.h @@ -713,7 +713,11 @@ void _ccp(void) { } 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 @@ -726,7 +730,7 @@ void _ccp(void) { if(!blen){ _puts((char *)prompt); - _RamWrite(inBuf, cmdLen); // Sets the buffer size to read the command line + _RamWrite(inBuf, cmdLen); // Sets the buffer size to read the command line _ccp_readInput(); } blen = _RamRead(inBuf + 1); // Obtains the number of bytes read @@ -742,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; @@ -785,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 @@ -795,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 @@ -904,7 +913,6 @@ void _ccp(void) { if (i) _ccp_cmdError(); } - blen = 0; if ((Status == 1) || (Status == 2)) break; } From 11bacb3b33d322cdcbfc9cf4a547d80578559c08 Mon Sep 17 00:00:00 2001 From: Mockba the Borg Date: Tue, 7 May 2024 10:26:02 -0400 Subject: [PATCH 6/8] Update readme.md with automation info --- readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/readme.md b/readme.md index ba9e295..e4e5cf8 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. From 094b66aa98ca3609339414eb91bd683455b1486b Mon Sep 17 00:00:00 2001 From: Mockba the Borg Date: Tue, 7 May 2024 10:28:45 -0400 Subject: [PATCH 7/8] Update readme.md --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index e4e5cf8..f5a3bbd 100644 --- a/readme.md +++ b/readme.md @@ -305,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 From 3771101d3edbb4791e9f5e53825a2b0d7e39ac84 Mon Sep 17 00:00:00 2001 From: MockbaTheBorg Date: Tue, 7 May 2024 14:30:32 -0400 Subject: [PATCH 8/8] Fix loop when running SUBMIT and autoexec together --- RunCPM/ccp.h | 6 +++--- RunCPM/main.c | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/RunCPM/ccp.h b/RunCPM/ccp.h index b7bf295..88137ca 100644 --- a/RunCPM/ccp.h +++ b/RunCPM/ccp.h @@ -688,10 +688,10 @@ void _ccp(void) { 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) { + if (firstBoot && !sFlag) { uint8 dmabuf[128]; uint16 cmd = inBuf + 1; if (_sys_exists((uint8*)AUTOEXEC)) { @@ -718,7 +718,7 @@ void _ccp(void) { _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 diff --git a/RunCPM/main.c b/RunCPM/main.c index 6cd61aa..33bbadd 100644 --- a/RunCPM/main.c +++ b/RunCPM/main.c @@ -99,7 +99,7 @@ int main(int argc, char* argv[]) { // 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) { + if (firstBoot an not sFlag) { uint8 dmabuf[128]; uint8 bytesread; uint16 cmd = CCPaddr + 7; @@ -123,6 +123,7 @@ int main(int argc, char* argv[]) { 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