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