; RUNENT - Written by Bill Sudbrink in June 2006 ; ; USAGE: ; RUNENT [<2ND ENT FILE NAME>] [:] ; ; This program allows one or two ENT files to be loaded and run from CP/M. ; ; An ENT file is file containing the memory image of a Processor Technology ; program that was originally intended to be loaded from tape. The file ; is ASCII text and the main data has the format: ; ; AAAA: BB CC DD... ; ; where "AAAA" is a four digit HEX address and "BB CC DD" are byte ; values (also in HEX) to store in memory starting at that address. ; The ENT files normally have an address for every 16 values with ; the address starting a new line. This program does not require ; embedded addresses, just a starting address but it will honor ; addresses whereever they are found in the file. Also, this program ; ignores whitespace (defined here as any value below 21H with the ; exception of 1AH which is recognized as end of file). ENT files ; are usually prefixed with "EN
" or "ENTER
" ; on the first line. This program ignores the file content up to ; the first valid address. ENT files are normally terminated with a ; slash character "/". This program will continue to process a file ; until: ; 1) The normal ENT file end marker (/) is encountered ; 2) A CP/M file end marker (HEX 1A, control-Z) is encounterd ; 3) An error is encountered. Only a small amount of error checking ; is performed. Possible errors are: ; A) An invalid character is encountered after the first ; address. Valid characters are whitespace, control-z, /, ; 0-9, :, A-F, a-f ; B) No address is found in the file ; C) An odd number of HEX digits are found between two ; colons ; D) Fewer than four HEX digits are found between two ; colons ; E) An odd number of HEX digits are found between the ; last colon in the file and the end of the file ; ; NOTE: THIS IS NOT IMPLEMENTED: ; F) An address is reached that would overwrite this ; program's work code at the top of the TPA ; NOTE: RIGHT NOW, THIS PROGRAM WILL STOMP ON ITSELF IF GIVEN A FILE ; WITH THE RIGHT (WRONG?) ADDRESSES. I've tried to implement ; this several times. Each time it just got too big and ugly. ; Maybe some other smart person will come up with a neat solution. ; ; If an error is encountered, a short message is printed to the console ; and control is returned to CP/M. ; ; The program carries out the following steps: ; ; 1) Process the command line parameters. ; 2) check that the file(s) exist and contain a valid address (XXXX:) specifier ; 3) Relocate the work code to the top of the TPA (discovered by examining ; address 6 and 7). ; 4) Jump to the work code which will then: ; 5) Copy 0000H-00FFH to 0100H-01FFH. This allows CP/M compatable ENT files to ; be run. ; 5) Load the specified file(s). Target addresses below BF00H are offset by ; 0100H to ensure that CP/M runs normally until the file load(s) is(are) ; completed. Above BF00H? You're on your own. This allows the PT 8080 ; development system to be loaded. ; 6) Shift memory down, that is, move the memory from 0100H- ; to 0000H-. ; 7) Set up the stack pointer and the HL register pair to look like Solos/Cutter ; did the work. NOTE: You might need to change this if your Solos/Cutter is ; not at address 0C000H ; 8) Jump to the specified start address or 0000H default. ; CPM EQU 5 FCB1 EQU 5CH FCB2 EQU 6CH PARAMS EQU 81H ORG 100H JMP START RLADR: DW 0 ; relocation address... where the code is going FUINC: DW 0 ; fixup address increment... how much will ; addresses be adjusted FUTP: DW 0 ; fixup table pointer ; fixup table: FUTBL: DW FU000 + 1 ; These first two entries should always be here DW FU001 + 1 DW FU002 + 1 DW FU003 + 1 DW FU004 + 1 DW FU005 + 1 DW FU006 + 1 DW FU007 + 1 DW FU008 + 1 DW FU009 + 1 DW FU00A + 1 DW FU00B + 1 DW FU00C + 1 DW FU00D + 1 DW FU00E + 1 DW FU00F + 1 DW FU010 + 1 DW FU011 + 1 DW FU012 + 1 DW FU013 + 1 DW FU014 + 1 DW FU015 + 1 DW FU016 + 1 DW FU017 + 1 DW FU018 + 1 DW FU050 + 1 DW FU051 + 1 DW FU052 + 1 DW FU053 + 1 DW FU054 + 1 DW FU055 + 1 DW FU056 + 1 DW FU057 + 1 DW FU058 + 1 DW FU059 + 1 DW FU05X + 1 DW FU05Y + 1 DW FU060 + 1 DW FU061 + 1 DW FU062 + 1 DW FU063 + 1 DW FU064 + 1 DW FU065 + 1 DW FU066 + 1 DW FU067 + 1 DW FU068 + 1 DW FU069 + 1 DW FU06A + 1 DW FU06X + 1 DW FU06Y + 1 DW FU06Z + 1 DW FU06P + 1 DW FU06Q + 1 DW FU100 + 1 DW FU101 + 1 DW FU102 + 1 DW FU103 + 1 DW FU104 + 1 DW FU105 + 1 DW FU106 + 1 DW FU107 + 1 DW FU108 + 1 DW FU109 + 1 DW FU10A + 1 DW FU10B + 1 DW FU10C + 1 DW FU200 + 1 DW FU201 + 1 DW FU202 + 1 DW FU203 + 1 DW FU204 + 1 DW FU205 + 1 DW FU206 + 1 DW FU207 + 1 DW FU208 + 1 DW FU209 + 1 DW FU20A + 1 DW FU500 + 1 DW FU501 + 1 DW FU502 + 1 DW FU503 + 1 DW 0H ; leave this zero here, it marks the table end WELCOME:DB 'RUNENT - Written in 2006 by Bill Sudbrink',0DH,0AH,'$' USEMSG: DB 'USAGE:',0DH,0AH,0DH,0AH DB ' RUNENT' DB ' ' DB ' [<2ND ENT FILE NAME>]' DB ' [:]$' FBDMSG: DB ' open failed.',0DH,0AH,'$' FOPMSG: DB ' opened.$' FNOAD: DB ' No address found in file.',0DH,0AH,'$' FADOK: DB ' Address found.',0DH,0AH,'$' BSADMSG:DB 'Bad start address. Please supply four hexadecimal digits.',0DH,0AH,'$' NSADMSG:DB 'No start address specified, assuming 0000H.' NEWLN: DB 0DH,0AH,'$' PTSADR: DW 0 ; pointer to the starting address parameter in the ; command line (if present) DIGCNT: DB 0 WRKFCB: DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DB 0,0,0,0 ; REMOVE AFTER DEBUGGING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ; DB '1000: 0e 02 1e 3a cd 05 00 C3 00 00 /' ; 16 bit subtract DE from HL SUBX: MOV A,L SUB E MOV L,A MOV A,H SBB D MOV H,A RET ; move the block of bytes starting at DE and ending at BC-1 to HL ; we need to have this routine twice, once not to be relocated (MOVBK1) ; and the other to be relocated (MOVBK2) MOVBK1: LDAX D ; get the data INX D ; increment the source pointer MOV M,A ; store the data INX H ; increment the destination pointer MOV A,D ; check the high byte of the source pointer against... CMP B ; the high byte of the source end pointer JNZ MOVBK1 ; no match? keep going MOV A,E ; check the low byte of the source pointer against... CMP C ; the low byte of the source end pointer JNZ MOVBK1 ; no match? keep going RET ; otherwise, done PRTSTR: MVI C,9 ; print string command JMP CPM ; cute trick, let CP/M do the return for us PRTCHR: MVI C,2 ; print character command JMP CPM ; cute trick, let CP/M do the return for us ; print the string pointed to by DE and exit PRTEXIT: CALL PRTSTR JMP 0 ; back to CP/M ; print the file name in the work FCB, including the drive letter if ; not default PRTWKFN: LDA WRKFCB ORA A JZ NODRV ADI 64 MOV E,A CALL PRTCHR MVI E,':' CALL PRTCHR NODRV: MVI A,'$' STA WRKFCB + 12 LXI D,WRKFCB + 1 CALL PRTSTR XRA A STA WRKFCB + 12 RET ; check the file name in the work FCB, if OK, print it and try to open it ; if the open fails, we print an error message and go back to CP/M (don't return) ; if we return with the zero flag set, there was no file name DOWKFCB: LDA WRKFCB + 1 CPI ' ' JNZ FNOK LDA WRKFCB + 9 CPI ' ' RZ FNOK: CALL PRTWKFN LXI D,WRKFCB MVI C,15 ; open CALL CPM INR A JNZ OPENOK LXI D,FBDMSG JMP PRTEXIT OPENOK: LXI D,FOPMSG CALL PRTSTR XRA A INR A RET ; store the work FCB to HL STWKFCB: LXI D,WRKFCB; block move source in DE LXI B,WRKFCB+36; block move source end in BC (get the whole thing to record the opened state) CALL MOVBK1 RET ; check to be sure that the file (whose open FCB is pointed to by CURFCB) ; has an address specification (XXXX:) in it. if we find the address, ; back up the current file offset to the start of it ; print error message and go back to CP/M on failure (don't return) ; print address good message if we find the address CHKADDR: XRA A STA DIGCNT CHKLOOP: CALL RDCHR JZ GOTCHR LXI D,FNOAD; no address in the file JMP PRTEXIT GOTCHR: CALL TSTDIG JC CHKBAD LDA DIGCNT INR A STA DIGCNT JMP CHKLOOP CHKBAD: CPI ':' JNZ CHKADDR LDA DIGCNT CPI 4 JC CHKADDR LHLD CUROFF DCX H DCX H DCX H DCX H DCX H SHLD CUROFF LXI D,FADOK CALL PRTSTR RET ; reset the current file offset and buffer pointer RESET: XRA A STA CUROFF STA CUROFF + 1 DCR A STA BUFPTR ; force a block read (0FFH in BUFPTR) RET START: LXI SP,STACK; set up our stack... ; note that we relocate it later ; print welcome LXI D,WELCOME CALL PRTSTR ; save FCB2 LXI H,SFCB2 ; block move destination in HL LXI D,FCB2 ; block move source in DE LXI B,FCB2+13; block move source end in BC (just get the drive and name) CALL MOVBK1 ; find and (if found) process the starting address parameter DOSADR: LXI H,PARAMS; point LH to the command line SADRLP: MOV A,M INX H ORA A JZ NOSADR ; hit zero? then no address specified CPI ' ' ; see if we're on a space... JNZ SADRLP MOV A,M ; followed by a... CPI ':' ; colon JNZ SADRLP ; nope? loop INX H ; skip the colon SHLD PTSADR ; store the pointer to what should be the four ASCII char HEX start address MVI C,4 ; check the four digits of the specified address CALL TSTDGS JNC GOODSADR LXI D,BSADMSG JMP PRTEXIT NOSADR: LXI D,NSADMSG CALL PRTSTR JMP SADRDONE GOODSADR: LHLD PTSADR CALL GDGQAD ; convert the ASCII hex value into a value in DE XCHG ; move it to HL SHLD JMPSADR + 1; store it in our jump instruction SADRDONE: ; prep to check and print first file name (move FCB1 to the work FCB) LXI H,WRKFCB; block move destination in HL LXI D,FCB1 ; block move source in DE LXI B,FCB1+13; block move source end in BC (just get the drive and name) CALL MOVBK1 ; check for a file name in WRKFCB and print and open it CALL DOWKFCB JNZ F1OK LXI D,USEMSG; no filename, print use message JMP PRTEXIT F1OK: ; move the opened FCB back to FCB1 LXI H,FCB1 CALL STWKFCB CALL CHKADDR ; now see if it has a valid address in it ; store the offset where the first address was found in the first file LHLD CUROFF SHLD STOFF CALL RESET ; REMOVE AFTER DEBUGGING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ; MVI A,0FFH ; second file is optional ; STA F2DONE ; JMP PDONE ; prep to check and print second file name (move SFCB2 to the work FCB) LXI H,WRKFCB; block move destination in HL LXI D,SFCB2 ; block move source in DE LXI B,SFCB2+36; block move source end in BC (get the whole thing to zero out leftovers from first file) CALL MOVBK1 ; check for a file name in WRKFCB and print and open it CALL DOWKFCB JNZ F2OK MVI A,0FFH ; second file is optional STA F2DONE JMP PDONE ; go do the starting offset parameter processing F2OK: ; move the opened FCB back to SFCB2 LXI H,SFCB2 ; block move destination in HL CALL STWKFCB CALL CHKADDR ; store the offset where the first address was found in the second file LHLD CUROFF SHLD STOFF2 CALL RESET ; done with command line parameters PDONE: ; set the current FCB to FCB1 LXI H,FCB1 SHLD CURFCB ; REMOVE AFTER DEBUGGING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ; JMP RLBST ; Code to perform relocation starts here: ; Calc size of the relocate block (RLBND - RLBST) + 3 ; note that the '+ 3' assumes that the smallest possible ; relocatable block must contain one 3 byte instruction LXI H,RLBST ; relocate block start XCHG LXI H,RLBND ; relocate block end CALL SUBX INX H INX H INX H ; HL has size of the relocate block ; Calc new offset of relocate block ( - ) XCHG ; size of the relocate block in DE LHLD CPM + 1 ; top of TPA in HL CALL SUBX SHLD RLADR ; HL has relocation target address ; Calc address increment (RLADR - RLBST) XCHG ; relocation target address in DE LXI H,RLBST ; relocation block start XCHG CALL SUBX SHLD FUINC ; HL has fixup address increment ; Walk through the fixup table, increment each address by FUINC LXI H,FUTBL ; start at top of table FULOOP: MOV E,M ; get fixup pointer from table into DE INX H MOV D,M INX H SHLD FUTP ; store fixup table pointer XCHG ; get fixup pointer into HL MOV A,H ; see if we're zero, end of table ANA A JNZ FUCONT ; not zero, continue MOV A,L ANA A JZ FUDONE ; end of table, exit fixup loop FUCONT: MOV C,M ; get the address from the block into BC INX H MOV B,M DCX H XCHG ; put the fixup pointer back into DE LHLD FUINC ; get the increment value DAD B ; add the code value XCHG ; fixup pointer back in HL, new address in DE MOV M,E INX H MOV M,D LHLD FUTP ; get the fixup table pointer back into HL JMP FULOOP ; process the next table entry ; fixup is done, now move the block FUDONE: LHLD RLADR ; block move destination in HL LXI D,RLBST ; block move source in DE LXI B,RLBND+1; block move source end in BC CALL MOVBK1 ; Now jump to the relocated code. FU000: JMP RLBST ; This jump should have been fixed ; up by the first table entry ; Code to relocate starts here:::::::::::::::::::::::::::::::::::::::::::::::::::::::: RLBST: FU001: LXI SP,STACK; reset the stack... ; fixed up by the second table entry ; copy the "zero page" up so we can copy it back down later if it isn't overwritten LXI D,0 LXI B,0100H LXI H,0100H FU500: CALL MOVBK2 STSEEK: ; seek CURFCB to STOFF FU100: LHLD STOFF MOV A,H ORA A FU101: JNZ NXTCHR MOV A,L ORA A FU102: JZ SEEKOK NXTCHR: DCX H FU103: SHLD STOFF FU104: CALL RDCHR FU105: JMP STSEEK SEEKOK: ; finished the seek, read the first address FU106: LXI H,RDBUF MVI C,5 FU107: CALL MULTIRD MAINLP: FU108: CALL STEP1 ; process address if we're on one FU109: JZ DOFL2 ; zero flag if we hit EOF MOV A,B CPI 1 FU10A: JZ MAINLP ; B is 1 if STEP1 processed an address FU10B: CALL STEP2 ; process a byte FU10C: JMP MAINLP ; switch to the second file FCB if we haven't done it already DOFL2: FU200: LDA F2DONE ; test if we've done file 2 ORA A FU201: JNZ FPDONE FU202: STA CUROFF ; reset current offset FU203: STA CUROFF + 1 DCR A FU204: STA F2DONE ; set the file 2 done flag FU205: STA BUFPTR ; force a block read FU206: LXI H,SFCB2 FU207: SHLD CURFCB FU208: LHLD STOFF2 ; set the starting offset to the second file's value FU209: SHLD STOFF FU20A: JMP STSEEK ; file processing done FPDONE: ; shift the whole TPA down on top of the "zero page". if the ENT file had any ; "zero page" addresses in it, here's where we trash CP/M LXI H,0 LXI D,0100H FU501 LXI B,RLBST FU502: CALL MOVBK2 ; Attempt to set up a couple of registers like Solos/Cutter would: : NOTE: You will need to modify these if you have Solos/Cutter at ; some other address. LXI SP,0CC00H ; Point the stack into Solos/Cutter RAM LXI H,0C000H ; Point HL at Solos/Cutter base address JMPSADR: JMP 0 ; STEP1: look for an address spec, EOF or invalid data ; EOF - zero flag ; not address - B <- 0 ; processed address - B <- 1 ; error - No return, bomb out here STEP1: MVI B,0 FU050: LDA RDBUF CPI 1AH ; EOF? RZ CPI '/' ; EOF? RZ FU051: LDA RDBUF + 4 CPI ':' ; address? RNZ PUSH B MVI C,4 FU052: LXI H,RDBUF FU053: CALL TSTDGS FU054: JC FLERR FU05X: LXI D,CRLF MVI C,9 CALL CPM FU05Y: LXI D,RDBUF MVI C,9 CALL CPM FU055: LXI H,RDBUF FU056: CALL GDGQAD MOV A,D CPI 0BFH FU057: JNC NOTTPA INR D ; keep us in the TPA NOTTPA: PUSH D FU058: LXI H,RDBUF MVI C,5 FU059: CALL MULTIRD POP D POP B INR B RET ; STEP2: process the first 2 characters in RDBUF as a byte value, ; store the value at DE, increment DE, shift RDBUF down 2 bytes ; and read 2 more bytes into the end. File error if the first 2 ; characters in RDBUF are not HEX digits... prints message and ; quits (does not return) STEP2: MVI C,2 FU060: LXI H,RDBUF FU061: CALL TSTDGS FU062: JC FLERR FU063: LXI H,RDBUF FU064: CALL GDGPAR STAX D INX D PUSH D FU06X: LXI D,BLANK MVI C,9 CALL CPM FU06Z: LDA RDBUF+2 MOV B,A PUSH B MVI A,'$' FU06P: STA RDBUF+2 FU06Y: LXI D,RDBUF MVI C,9 CALL CPM POP B MOV A,B FU06Q: STA RDBUF+2 FU065: LXI D,RDBUF + 2 FU066: LXI B,RDBUF + 5 FU067: LXI H,RDBUF FU068: CALL MOVBK2 MVI C,2 FU069: LXI H,RDBUF + 3 FU06A: CALL MULTIRD POP D RET FLERR: FU503: LXI D,BADFL MVI C,9 CALL CPM JMP 0 ; read C bytes into HL. Fill with 1AH if we hit EOF (due to RDCHR) MULTIRD: PUSH H PUSH B FU002: CALL RCHRNW POP B POP H MOV M,A INX H DCR C FU003: JNZ MULTIRD RET ; read a byte into A but skip values lower than 21H (except for 1A) RCHRNW: FU004: CALL RDCHR RNZ CPI 1AH RZ NOT1A: CPI 21H FU005: JC RCHRNW ; less than 21H is white space for us RET RDCHR: FU006: LHLD CUROFF INX H FU007: SHLD CUROFF FU008: LDA BUFPTR INR A FU009: JNZ BUFOK FU00A: CALL RDBLK FU00B: JZ RDBOK MVI A,1AH RET RDBOK: MVI A,80H BUFOK: MOV L,A FU00C: STA BUFPTR XRA A ; zero flag - OK MOV H,A MOV A,M RET RDBLK: FU00D: LHLD CURFCB XCHG MVI C,20 CALL CPM ORA A RET ; zero flag - OK, no zero flag - EOF ; move the block of bytes starting at DE and ending at BC-1 to HL ; we need to have this routine twice, once not to be relocated (MOVBK1) ; and the other to be relocated (MOVBK2) MOVBK2: LDAX D ; get the data INX D ; increment the source pointer MOV M,A ; store the data INX H ; increment the destination pointer MOV A,D ; check the high byte of the source pointer against... CMP B ; the high byte of the source end pointer FU00E: JNZ MOVBK2 ; no match? keep going MOV A,E ; check the low byte of the source pointer against... CMP C ; the low byte of the source end pointer FU00F: JNZ MOVBK2 ; no match? keep going RET ; otherwise, done ; test C characters at HL to see if they are valid HEX digits ; they will be converted to upper case ; no carry - good hex digits, carry - at least one non-hex digit TSTDGS: MOV A,M FU010: CALL TSTDIG RC MOV M,A INX H DCR C FU011: JNZ TSTDGS RET ; test the character in A to see whether it is a valid HEX digit ; convert it to upper case if necessary ; no carry - good hex digit, carry - not a hex digit TSTDIG: CPI '0' RC ; less than zero CPI ':' FU012: JC TSTGD ; it's between zero and nine, return good CPI 60H ; make sure it's upper case FU013: JC UCOK ANI 0DFH UCOK: CPI 'A' ; check A to F RC ; less than A CPI 'G' FU014: JNC TSTBAD TSTGD: CMC RET TSTBAD: STC RET ; NOTE: the routines below assume checked data, converted to upper case! ; translate the four HEX digits at HL into a 16-bit value in DE GDGQAD: FU015: CALL GDGPAR MOV D,A FU016: CALL GDGPAR MOV E,A RET ; translate the pair of HEX digits at HL into a byte value in A GDGPAR: FU017: CALL GETDIG RLC RLC RLC RLC MOV B,A FU018: CALL GETDIG ADD B RET ; translate a single HEX digit at HL into a nibble in A GETDIG: MOV A,M INX H SBI '0' CPI 10 RC SBI 7 RET DS 32 STACK: DB 0 BADFL: DB 'Invalid file data' CRLF: DB 0DH,0AH,'$' BLANK DB ' $' CURFCB: DW WRKFCB BUFPTR: DB 0FFH CUROFF: DW 0 STOFF: DW 0 STOFF2: DW 0 RDBUF: DB 0,0,0,0,0,'$' F2DONE: DB 0 SFCB2: DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DB 0,0,0,0 RLBND: JMP 0 ; end of relocated block END START