#ifndef CCP_H #define CCP_H // CP/M BDOS calls #include "cpm.h" #define CmdFCB (BatchFCB + 48) // FCB for use by internal commands #define ParFCB 0x005C // FCB for use by line parameters #define SecFCB 0x006C // Secondary part of FCB for renaming files #define Trampoline (CmdFCB + 36) // Trampoline for running external commands #define inBuf (BDOSjmppage - 256) // Input buffer location #define cmdLen 125 // Maximum size of a command line (sz+rd+cmd+\0) #define defDMA 0x0080 // Default DMA address #define defLoad 0x0100 // Default load address // CCP global variables uint8 pgSize = 22; // for TYPE uint8 curDrive = 0; // 0 -> 15 = A -> P .. Current drive for the CCP (same as RAM[DSKByte]) uint8 parDrive = 0; // 0 -> 15 = A -> P .. Drive for the first file parameter uint8 curUser = 0; // 0 -> 15 .. Current user area to access 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) static const char *Commands[] = { // Standard CP/M commands "DIR", "ERA", "TYPE", "SAVE", "REN", "USER", // Extra CCP commands "CLS", "DEL", "EXIT", "PAGE", "VOL", "?", NULL }; // Used to call BDOS from inside the CCP uint16 _ccp_bdos(uint8 function, uint16 de) { SET_LOW_REGISTER(BC, function); DE = de; _Bdos(); return (HL & 0xffff); } // _ccp_bdos // Compares two strings (Atmel doesn't like strcmp) uint8 _ccp_strcmp(char *stra, char *strb) { while (*stra && *strb && (*stra == *strb)) { ++stra; ++strb; } return (*stra == *strb); } // _ccp_strcmp // Gets the command ID number uint8 _ccp_cnum(void) { uint8 result = 255; uint8 command[9]; uint8 i = 0; if (!_RamRead(CmdFCB)) { // If a drive was set, then the command is external while (i < 8 && _RamRead(CmdFCB + i + 1) != ' ') { command[i] = _RamRead(CmdFCB + i + 1); ++i; } command[i] = 0; i = 0; while (Commands[i]) { if (_ccp_strcmp((char *)command, (char *)Commands[i])) { result = i; perr = defDMA + 2; break; } ++i; } } return (result); } // _ccp_cnum // Returns true if character is a separator uint8 _ccp_delim(uint8 ch) { return (ch == 0 || ch == ' ' || ch == '=' || ch == '.' || ch == ':' || ch == ';' || ch == '<' || ch == '>'); } // Prints the FCB filename void _ccp_printfcb(uint16 fcb, uint8 compact) { uint8 i, ch; ch = _RamRead(fcb); if (ch && compact) { _ccp_bdos( C_WRITE, ch + '@'); _ccp_bdos( C_WRITE, ':'); } for (i = 1; i < 12; ++i) { ch = _RamRead(fcb + i); if ((ch == ' ') && compact) continue; if (i == 9) _ccp_bdos(C_WRITE, compact ? '.' : ' '); _ccp_bdos(C_WRITE, ch); } } // _ccp_printfcb // Initializes the FCB void _ccp_initFCB(uint16 address, uint8 size) { uint8 i; for (i = 0; i < size; ++i) _RamWrite(address + i, 0x00); for (i = 0; i < 11; ++i) _RamWrite(address + 1 + i, 0x20); } // _ccp_initFCB // Name to FCB uint8 _ccp_nameToFCB(uint16 fcb) { uint8 pad, plen, ch, n = 0; // Checks for a drive and places it on the Command FCB if (_RamRead(pbuf + 1) == ':') { ch = toupper(_RamRead(pbuf++)); _RamWrite(fcb, ch - '@'); // Makes the drive 0x1-0xF for A-P ++pbuf; // Points pbuf past the : blen -= 2; } if (blen) { ++fcb; plen = 8; pad = ' '; ch = toupper(_RamRead(pbuf)); while (blen && plen) { if (_ccp_delim(ch)) break; ++pbuf; --blen; if (ch == '*') pad = '?'; if (pad == '?') { ch = pad; n = n | 0x80; // Name is not unique } --plen; ++n; _RamWrite(fcb++, ch); ch = toupper(_RamRead(pbuf)); } while (plen--) _RamWrite(fcb++, pad); plen = 3; pad = ' '; if (ch == '.') { ++pbuf; --blen; } while (blen && plen) { ch = toupper(_RamRead(pbuf)); if (_ccp_delim(ch)) break; ++pbuf; --blen; if (ch == '*') pad = '?'; if (pad == '?') { ch = pad; n = n | 0x80; // Name is not unique } --plen; ++n; _RamWrite(fcb++, ch); } while (plen--) _RamWrite(fcb++, pad); } return (n); } // _ccp_nameToFCB // Converts the ParFCB name to a number uint16 _ccp_fcbtonum() { uint8 ch; uint16 n = 0; uint8 pos = ParFCB + 1; while (TRUE) { ch = _RamRead(pos++); if ((ch < '0') || (ch > '9')) { break; } n = (n * 10) + (ch - '0'); } return (n); } // _ccp_fcbtonum // DIR command void _ccp_dir(void) { uint8 i; uint8 dirHead[6] = "A: "; uint8 dirSep[6] = " | "; uint32 fcount = 0; // Number of files printed uint32 ccount = 0; // Number of columns printed if (_RamRead(ParFCB + 1) == ' ') for (i = 1; i < 12; ++i) _RamWrite(ParFCB + i, '?'); dirHead[0] = _RamRead(ParFCB) ? _RamRead(ParFCB) + '@' : prompt[2]; _puts("\r\n"); if (!_SearchFirst(ParFCB, TRUE)) { _puts((char *)dirHead); _ccp_printfcb(tmpFCB, FALSE); ++fcount; ++ccount; while (!_SearchNext(ParFCB, TRUE)) { if (!ccount) { _puts( "\r\n"); _puts( (char *)dirHead); } else { _puts((char *)dirSep); } _ccp_printfcb(tmpFCB, FALSE); ++fcount; ++ccount; if (ccount > 3) ccount = 0; } } else { _puts("No file"); } } // _ccp_dir // ERA command void _ccp_era(void) { if (_ccp_bdos(F_DELETE, ParFCB)) _puts("\r\nNo file"); } // _ccp_era // TYPE command uint8 _ccp_type(void) { uint8 i, c, l = 0, error = TRUE; uint16 a, p = 0; if (!_ccp_bdos(F_OPEN, ParFCB)) { _puts("\r\n"); while (!_ccp_bdos(F_READ, ParFCB)) { i = 128; a = dmaAddr; while (i) { c = _RamRead(a); if (c == 0x1a) break; _ccp_bdos(C_WRITE, c); if (c == 0x0a) { ++l; if (pgSize && (l == pgSize)) { l = 0; p = _ccp_bdos(C_READ, 0x0000); if (p == 3) break; } } --i; ++a; } if (p == 3) break; } error = FALSE; } return (error); } // _ccp_type // SAVE command uint8 _ccp_save(void) { uint8 error = TRUE; uint16 pages = _ccp_fcbtonum(); uint16 i, dma; if (pages < 256) { error = FALSE; while (_RamRead(pbuf) == ' ' && blen) { // Skips any leading spaces ++pbuf; --blen; } _ccp_nameToFCB(SecFCB); // Loads file name onto the ParFCB if (_ccp_bdos(F_MAKE, SecFCB)) { _puts("Err: create"); } else { if (_ccp_bdos(F_OPEN, SecFCB)) { _puts("Err: open"); } else { pages *= 2; // Calculates the number of CP/M blocks to write dma = defLoad; _puts("\r\n"); for (i = 0; i < pages; i++) { _ccp_bdos( F_DMAOFF, dma); _ccp_bdos( F_WRITE, SecFCB); dma += 128; _ccp_bdos( C_WRITE, '.'); } _ccp_bdos(F_CLOSE, SecFCB); } } } return (error); } // _ccp_save // REN command void _ccp_ren(void) { uint8 ch, i; ++pbuf; _ccp_nameToFCB(SecFCB); for (i = 0; i < 12; ++i) { // Swap the filenames on the fcb ch = _RamRead(ParFCB + i); _RamWrite( ParFCB + i, _RamRead(SecFCB + i)); _RamWrite( SecFCB + i, ch); } if (_ccp_bdos(F_RENAME, ParFCB)) _puts("\r\nNo file"); } // _ccp_ren // USER command uint8 _ccp_user(void) { uint8 error = TRUE; curUser = (uint8)_ccp_fcbtonum(); if (curUser < 16) { _ccp_bdos(F_USERNUM, curUser); error = FALSE; } return (error); } // _ccp_user // PAGE command uint8 _ccp_page(void) { uint8 error = TRUE; uint16 r = _ccp_fcbtonum(); if (r < 256) { pgSize = (uint8)r; error = FALSE; } return (error); } // _ccp_page // VOL command uint8 _ccp_vol(void) { uint8 error = FALSE; uint8 letter = _RamRead(ParFCB) ? '@' + _RamRead(ParFCB) : 'A' + curDrive; uint8 folder[5] = {letter, FOLDERCHAR, '0', FOLDERCHAR, 0}; uint8 filename[13] = {letter, FOLDERCHAR, '0', FOLDERCHAR, 'I', 'N', 'F', 'O', '.', 'T', 'X', 'T', 0}; uint8 bytesread; uint8 i, j; _puts("\r\nVolumes on "); _putcon(folder[0]); _puts(":\r\n"); for (i = 0; i < 16; ++i) { folder[2] = i < 10 ? i + 48 : i + 55; if (_sys_exists(folder)) { _putcon(i < 10 ? ' ' : '1'); _putcon(i < 10 ? folder[2] : 38 + i); _puts(": "); filename[2] = i < 10 ? i + 48 : i + 55; bytesread = (uint8)_sys_readseq(filename, 0); if (!bytesread) { for (j = 0; j < 128; ++j) { if ((_RamRead(dmaAddr + j) < 32) || (_RamRead(dmaAddr + j) > 126)) break; _putcon(_RamRead(dmaAddr + j)); } } _puts("\r\n"); } } return (error); } // _ccp_vol // ?/Help command uint8 _ccp_hlp(void) { _puts("\r\nCCP Commands:\r\n"); _puts("\t? - Shows this list of commands\r\n"); _puts("\tCLS - Clears the screen\r\n"); _puts("\tDEL - Alias to ERA\r\n"); _puts("\tEXIT - Terminates RunCPM\r\n"); _puts("\tPAGE [] - Sets the page size for TYPE\r\n"); _puts("\t or disables paging if no parameter passed\r\n"); _puts("\tVOL [drive] - Shows the volume information\r\n"); _puts("\t which comes from each volume's INFO.TXT"); return(FALSE); } #ifdef HASLUA // External (.LUA) command uint8 _ccp_lua(void) { uint8 error = TRUE; uint8 found, drive, user = 0; uint16 loadAddr = defLoad; _RamWrite( CmdFCB + 9, 'L'); _RamWrite( CmdFCB + 10, 'U'); _RamWrite( CmdFCB + 11, 'A'); drive = _RamRead(CmdFCB); found = !_ccp_bdos(F_OPEN, CmdFCB); // Look for the program on the FCB drive, current or specified if (!found) { // If not found if (!drive) { // and the search was on the default drive _RamWrite(CmdFCB, 0x01); // Then look on drive A: user 0 if (curUser) { user = curUser; // Save the current user _ccp_bdos(F_USERNUM, 0x0000); // then set it to 0 } found = !_ccp_bdos(F_OPEN, CmdFCB); if (!found) { // If still not found then if (curUser) { // If current user not = 0 _RamWrite(CmdFCB, 0x00); // look on current drive user 0 found = !_ccp_bdos(F_OPEN, CmdFCB); // and try again } } } } if (found) { _puts("\r\n"); _ccp_bdos(F_RUNLUA, CmdFCB); if (user) { // If a user was selected _ccp_bdos(F_USERNUM, curUser); // Set it back user = 0; } _RamWrite(CmdFCB, drive); // Set the command FCB drive back to what it was cDrive = oDrive; // And restore cDrive error = FALSE; } if (user) // If a user was selected _ccp_bdos(F_USERNUM, curUser); // Set it back _RamWrite(CmdFCB, drive); // Set the command FCB drive back to what it was return (error); } // _ccp_lua #endif // ifdef HASLUA // External (.COM) command uint8 _ccp_ext(void) { bool error = TRUE, found = FALSE; uint8 drive = 0, user = 0; uint16 loadAddr = defLoad; bool wasBlank = (_RamRead(CmdFCB + 9) == ' '); bool wasSUB = ((_RamRead(CmdFCB + 9) == 'S') && (_RamRead(CmdFCB + 10) == 'U') && (_RamRead(CmdFCB + 11) == 'B')); if (!wasSUB) { if (wasBlank) { _RamWrite(CmdFCB + 9, 'C'); //first look for a .COM file _RamWrite(CmdFCB + 10, 'O'); _RamWrite(CmdFCB + 11, 'M'); } drive = _RamRead(CmdFCB); // Get the drive from the command FCB found = !_ccp_bdos(F_OPEN, CmdFCB); // Look for the program on the FCB drive, current or specified if (!found) { // If not found if (!drive) { // and the search was on the default drive _RamWrite(CmdFCB, 0x01); // Then look on drive A: user 0 if (curUser) { user = curUser; // Save the current user _ccp_bdos(F_USERNUM, 0x0000); // then set it to 0 } found = !_ccp_bdos(F_OPEN, CmdFCB); if (!found) { // If still not found then if (curUser) { // If current user not = 0 _RamWrite(CmdFCB, 0x00); // look on current drive user 0 found = !_ccp_bdos(F_OPEN, CmdFCB); // and try again } } } } if (!found) { _RamWrite(CmdFCB, drive); // restore previous drive _ccp_bdos(F_USERNUM, curUser); // restore to previous user } } //if .COM not found then look for a .SUB file if ((wasBlank || wasSUB) && !found && !sFlag) { //don't auto-submit while executing a submit file _RamWrite(CmdFCB + 9, 'S'); _RamWrite(CmdFCB + 10, 'U'); _RamWrite(CmdFCB + 11, 'B'); drive = _RamRead(CmdFCB); // Get the drive from the command FCB found = !_ccp_bdos(F_OPEN, CmdFCB); // Look for the program on the FCB drive, current or specified if (!found) { // If not found if (!drive) { // and the search was on the default drive _RamWrite(CmdFCB, 0x01); // Then look on drive A: user 0 if (curUser) { user = curUser; // Save the current user _ccp_bdos(F_USERNUM, 0x0000); // then set it to 0 } found = !_ccp_bdos(F_OPEN, CmdFCB); if (!found) { // If still not found then if (curUser) { // If current user not = 0 _RamWrite(CmdFCB, 0x00); // look on current drive user 0 found = !_ccp_bdos(F_OPEN, CmdFCB); // and try again } } } } if (!found) { _RamWrite(CmdFCB, drive); // restore previous drive _ccp_bdos(F_USERNUM, curUser); // restore to previous user } if (found) { //_puts(".SUB file found!\n"); int i; //move FCB's (CmdFCB --> ParFCB --> SecFCB) for (i = 0; i < 16; i++) { //ParFCB to SecFCB _RamWrite(SecFCB + i, _RamRead(ParFCB + i)); //CmdFCB to ParFCB _RamWrite(ParFCB + i, _RamRead(CmdFCB + i)); } // (Re)Initialize the CmdFCB _ccp_initFCB(CmdFCB, 36); //put 'SUBMIT.COM' in CmdFCB const char *str = "SUBMIT COM"; int s = (int)strlen(str); for (i = 0; i < s; i++) _RamWrite(CmdFCB + i + 1, str[i]); //now try to find SUBMIT.COM file found = !_ccp_bdos(F_OPEN, CmdFCB); // Look for the program on the FCB drive, current or specified if (!found) { // If not found if (!drive) { // and the search was on the default drive _RamWrite(CmdFCB, 0x01); // Then look on drive A: user 0 if (curUser) { user = curUser; // Save the current user _ccp_bdos(F_USERNUM, 0x0000); // then set it to 0 } found = !_ccp_bdos(F_OPEN, CmdFCB); if (!found) { // If still not found then if (curUser) { // If current user not = 0 _RamWrite(CmdFCB, 0x00); // look on current drive user 0 found = !_ccp_bdos(F_OPEN, CmdFCB); // and try again } } } } if (found) { //insert "@" into command buffer //note: this is so the rest will be parsed correctly blen = _RamRead(defDMA); if (blen < cmdLen) { blen++; _RamWrite(defDMA, blen); } uint8 lc = '@'; for (i = 0; i < blen; i++) { uint8 nc = _RamRead(defDMA + 1 + i); _RamWrite(defDMA + 1 + i, lc); lc = nc; } } } } if (found) { // Program was found somewhere _puts("\r\n"); _ccp_bdos(F_DMAOFF, loadAddr); // Sets the DMA address for the loading while (!_ccp_bdos(F_READ, CmdFCB)) { // Loads the program into memory loadAddr += 128; if (loadAddr == BDOSjmppage) { // Breaks if it reaches the end of TPA _puts("\r\nNo Memory"); break; } _ccp_bdos(F_DMAOFF, loadAddr); // Points the DMA offset to the next loadAddr } _ccp_bdos(F_DMAOFF, defDMA); // Points the DMA offset back to the default if (user) { // If a user was selected _ccp_bdos(F_USERNUM, curUser); // Set it back user = 0; } _RamWrite(CmdFCB, drive); // Set the command FCB drive back to what it was cDrive = oDrive; // And restore cDrive // Place a trampoline to call the external command // as it may return using RET instead of JP 0000h loadAddr = Trampoline; _RamWrite(loadAddr, CALL); // CALL 0100h _RamWrite16(loadAddr + 1, defLoad); _RamWrite(loadAddr + 3, JP); // JP USERF _RamWrite16(loadAddr + 4, BIOSjmppage + B_USERF); Z80reset(); // Resets the Z80 CPU SET_LOW_REGISTER(BC, _RamRead(DSKByte)); // Sets C to the current drive/user PC = loadAddr; // Sets CP/M application jump point SP = BDOSjmppage; // Sets the stack to the top of the TPA Z80run(); // Starts Z80 simulation error = FALSE; } if (user) // If a user was selected _ccp_bdos(F_USERNUM, curUser); // Set it back _RamWrite(CmdFCB, drive); // Set the command FCB drive back to what it was return(error); } // _ccp_ext // Prints a command error void _ccp_cmdError() { uint8 ch; _puts("\r\n"); while ((ch = _RamRead(perr++))) { if (ch == ' ') break; _ccp_bdos(C_WRITE, toupper(ch)); } _puts("?\r\n"); } // _ccp_cmdError // Reads input, either from the $$$.SUB or console void _ccp_readInput(void) { uint8 i; uint8 chars; if (sFlag) { // Are we running a submit? if (!sRecs) { // Are we already counting? _ccp_bdos(F_OPEN, BatchFCB); // Open the batch file sRecs = _RamRead(BatchFCB + 15); // Gets its record count } --sRecs; // Counts one less _RamWrite(BatchFCB + 32, sRecs); // And sets to be the next read _ccp_bdos( F_DMAOFF, defDMA); // Reset current DMA _ccp_bdos( F_READ, BatchFCB); // And reads the last sector chars = _RamRead(defDMA); // Then moves it to the input buffer for (i = 0; i <= chars; ++i) _RamWrite(inBuf + i + 1, _RamRead(defDMA + i)); _RamWrite(inBuf + i + 1, 0); _puts((char *)_RamSysAddr(inBuf + 2)); if (!sRecs) { _ccp_bdos(F_DELETE, BatchFCB); // Deletes the submit file sFlag = FALSE; // and clears the submit flag } } else { _ccp_bdos(C_READSTR, inBuf); // Reads the command line from console if (Debug) Z80run(); } } // _ccp_readInput // Main CCP code void _ccp(void) { uint8 i; sFlag = (bool)_ccp_bdos(DRV_ALLRESET, 0x0000); _ccp_bdos(DRV_SET, curDrive); for (i = 0; i < 36; ++i) _RamWrite(BatchFCB + i, _RamRead(tmpFCB + i)); 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(); blen = _RamRead(inBuf + 1); // Obtains the number of bytes read _ccp_bdos(F_DMAOFF, defDMA); // Reset current DMA if (blen) { _RamWrite(inBuf + 2 + blen, 0); // "Closes" the read buffer with a \0 pbuf = inBuf + 2; // Points pbuf to the first command character while (_RamRead(pbuf) == ' ' && blen) { // Skips any leading spaces ++pbuf; --blen; } if (!blen) // There were only spaces continue; if (_RamRead(pbuf) == ';') // Found a comment line continue; // parse for DU: command line shortcut bool errorFlag = FALSE, continueFlag = FALSE; uint8 ch, tDrive = 0, tUser = curUser, u = 0; for (i = 0; i < blen; i++) { ch = toupper(_RamRead(pbuf + i)); if ((ch >= 'A') && (ch <= 'P')) { if (tDrive) { // if we've already specified a new drive break; // not a DU: command } else { tDrive = ch - '@'; } } else if ((ch >= '0') && (ch <= '9')) { tUser = u = (u * 10) + (ch - '0'); } else if (ch == ':') { if (i == blen - 1) { // if we at the end of the command line if (tUser >= 16) { // if invalid user errorFlag = TRUE; break; } if (tDrive != 0) { cDrive = oDrive = tDrive - 1; _RamWrite(DSKByte, (_RamRead(DSKByte) & 0xf0) | cDrive); _ccp_bdos(DRV_SET, cDrive); if (Status) curDrive = 0; } if (tUser != curUser) { curUser = tUser; _ccp_bdos(F_USERNUM, curUser); } continueFlag = TRUE; } break; } else { // invalid character break; // don't error; may be valid (non-DU:) command } } if (errorFlag) { _ccp_cmdError(); // print command error continue; } if (continueFlag) { continue; } _ccp_initFCB(CmdFCB, 36); // Initializes the command FCB 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 continue; } _RamWrite(defDMA, blen); // Move the command line at this point to 0x0080 for (i = 0; i < blen; ++i) _RamWrite(defDMA + i + 1, toupper(_RamRead(pbuf + i))); while (i++ < 127) // "Zero" the rest of the DMA buffer _RamWrite(defDMA + i, 0); _ccp_initFCB( ParFCB, 18); // Initializes the parameter FCB _ccp_initFCB( SecFCB, 18); // Initializes the secondary FCB while (_RamRead(pbuf) == ' ' && blen) { // Skips any leading spaces ++pbuf; --blen; } _ccp_nameToFCB(ParFCB); // Loads the next file parameter onto the parameter FCB while (_RamRead(pbuf) == ' ' && blen) { // Skips any leading spaces ++pbuf; --blen; } _ccp_nameToFCB(SecFCB); // Loads the next file parameter onto the secondary FCB i = FALSE; // Checks if the command is valid and executes switch (_ccp_cnum()) { // Standard CP/M commands case 0: { // DIR _ccp_dir(); break; } case 1: { // ERA _ccp_era(); break; } case 2: { // TYPE i = _ccp_type(); break; } case 3: { // SAVE i = _ccp_save(); break; } case 4: { // REN _ccp_ren(); break; } case 5: { // USER i = _ccp_user(); break; } // Extra CCP commands case 6: { // CLS _clrscr(); break; } case 7: { // DEL is an alias to ERA _ccp_era(); break; } case 8: { // EXIT _puts( "Terminating RunCPM.\r\n"); _puts( "CPU Halted.\r\n"); Status = 1; break; } case 9: { // PAGE i = _ccp_page(); break; } case 10: { // VOL i = _ccp_vol(); break; } case 11: { // HELP i = _ccp_hlp(); break; } // External/Lua commands case 255: { // It is an external command i = _ccp_ext(); #ifdef HASLUA if (i) i = _ccp_lua(); #endif // ifdef HASLUA break; } default: { i = TRUE; break; } } // switch cDrive = oDrive = curDrive; // Restore cDrive and oDrive if (i) _ccp_cmdError(); } if ((Status == 1) || (Status == 2)) break; } _puts("\r\n"); } // _ccp #endif // ifndef CCP_H