; ; ; ==>> THIS CODE WILL ONLY RUN ON A Z80 CPU ; ==>> IT MUST BE COMPILED WITH M80 AND LINKED WITH L80 ; ; PROTECT.MAC by Simon Ewins, Sysop EMX RCPM, Toronto, Ontario ; ; Based on: PASSWORD.ASM v3.0 by Bo McCormick 08/06/81 ; ;----------------------------------------------------------------------- ; ; - The command line allows an ASCII value (^A through the upper ; case letter Z) to be passed as the level of protection for the ; .COM file. If a code or level that is less than an ASCII ; 'space' is desired, you may use a caret (^) before the ASCII ; character that is desired to turn it into a 'control' character. ; One character value is reserved as an 'unprotect' switch. In ; the example below, ^Z is such a value. The caret '^' may be ; used as in ^^ but a space or @ may NOT. If you enter a single ; ^ as an argument an error will occur. ; ; - syntax is: ; ; PROTECT FILENAME[.COM] ^Z <-- to 'unprotect' the file ; PROTECT FILENAME[.COM] 5 <-- set level to (ASCII) ; 5 (35 hex) ; PROTECT FILENAME[.COM] Q <-- set level to (ascii) ; Q (51 hex) ; PROTECT FILENAME[.COM] ^I <-- set to (ASCII) CRL-I ; (9 hex) ; ; - The .COM filetype is optional as .COM will be assumed and any ; other filetype will generate an error exit ; ; - This file, when run, relocates itself under the CCP and then ; loads the .COM file requested. The first record is moved to the ; end of the file and replaced with a jump at 100h to the next ; record at the end of the file. There a little routine reads the ; byte in memory that is your 'control' byte and tries to match ; the protection level of the file. If there is no match, a warm ; boot is done. If there is a match, or the level of protection ; is less than the level of the file, then the last record is ; moved back to 100h and the file is executed. ; ; - Alternately you can set EXACT to YES and then the file will run ; only if the levels match (no 'less than' stuff). ; ; - If you update this file, first, be careful since the stack is ; used to pass arguments to the program once it has relocated to ; high memory. If you do update please send a copy to: ; ; EMX RCPM, Toronto, Ontario ... 416 484-9663 ; ;----------------------------------------------------------------------- ; ; Equates ; NO EQU 0 YES EQU NOT NOT ; RDCHAR EQU 1 MSGOUT EQU 9 ; BDOS functions INCON EQU 10 OPEN EQU 15 CLOSE EQU 16 DELETE EQU 19 READ EQU 20 BWRITE EQU 21 SETDMA EQU 26 ; CR EQU 0DH ; ASCII values LF EQU 0AH EOS EQU '$' ; BOOT EQU 0 ; 0 for standard CP/M BDOS EQU 5 FCB EQU 5CH DEFBUF EQU 80H TPA EQU 100H ; ;----------------------------------------------------------------------- ; ; This is the address of the start of your CCP. It can be determined by ; either using CPMLOOK, SHOW or TELL or with: ; ; ; Under DDT or ZSID examine location 0001h take the value ; at loation 0002h and subtract 16h from it. Take the re- ; sult and assume it to be the msb of a two byte value the ; LBS of which is 0... This is the value for the next ; equate. Example: ; 00Hh = 0C3H ; 01H = 03H ; Warm boot is 3 above BIOS ; 02H = 0CEH ; Page address is BIOS start ; ; subtract 16H from 0CEH to get 0B8H ; therefore CCP = 0B800H ; CCP EQU 0A700H ; Address of CCP ; ;----------------------------------------------------------------------- ; ; This is the address of the byte in memory to check against the one ; that the file is locked with.. ; PROBYT EQU 0003FH ; Access level byte location ; ;----------------------------------------------------------------------- ; ; Id EXACT is YES, then the byte in memory and the level of the file ; must match exactly... If it is NO, then the value in memory must be ; equal to or greater than the level of the file ; EXACT EQU YES ; True if bytes must match exactly ; ;----------------------------------------------------------------------- ; ; This is the character that you wish to use as the 'unprotect' key. It ; cannot be used as a protection level as well. if you select a control ; character, remember that you must use '^' in the command line followed ; by the ASCII character. ; UKEY EQU 'Z'-40H ; Use CTL-z as unprotect key ; ; (use ^Z on command line) ; ;----------------------------------------------------------------------- ; MOVLEN EQU (CODEND-CODBEG)+1 ; Length of relocated code RELLOC EQU CCP-MOVLEN-1 ; Start address of relocated code OFF EQU RELLOC-CODBEG ; Dif between reloc code and assem code ; ; .Z80 ASEG ORG 100H ; JP START ; KEYCOD::DB UKEY ; Store unprotect key as a byte at 103h so ; That it may be easily patched ; ; Start of program ; START:: LD A,(FCB+1) ; See if help needed CP '?' JP Z,HELP ; Yes CP '/' JP Z,HELP ; Yes CP ' ' JP Z,HELP ; For sure! LD A,(FCB+9) ; Get first char of extension CP ' ' ; If ' ' then change to .COM JP Z,NOTYPE CP 'C' ; If there is an extension, JP NZ,HELP ; Make sure it's .COM LD A,(FCB+10) ; Check second letter CP 'O' JP NZ,HELP LD A,(FCB+11) CP 'M' ; Last letter JP Z,ISCOM0 ; If it is a .COM, then continue JP HELP ; Wrong type ; NOTYPE::LD A,'C' ; Force filetype to .COM LD (FCB+9),A LD A,'O' LD (FCB+10),A LD A,'M' LD (FCB+11),A ; ISCOM0::LD A,(FCB+17) ; Get argument, if any CP ' ' ; None? JP Z,HELP ; Explain this program CP '^' ; Is it a control character? JP NZ,NOTCTL ; No so no conversion needed LD A,(FCB+18) ; Get next character CP ' ' ; If space then explain program JP Z,HELP CP 'A' ; Less than 'A' not allowed JP C,HELP SUB 40H ; Convert to control character ; NOTCTL::LD HL,KEYCOD ; Point to UKEY CP (HL) ; Is it 'unprotect' code? JR Z,UNPSET ; Yes, go set flag LD (PBYTE),A ; No, so assume we want to protect file JR REL ; UNPSET::LD (UBYTE),A ; Store unprotect flag with value ; ; Now relocate the needed code up high and then jump to it... ; REL:: LD DE,VERSIO LD C,MSGOUT CALL BDOS LD BC,MOVLEN ; Number of bytes to move LD DE,RELLOC ; Where to and ... LD HL,CODBEG ; Where from LDIR ; Move code into place JP RELLOC ; And go to it ; VERSIO::DB CR,LF,'PROTECT v1.0 -- (c)1984 Simon Ewins',CR,LF,EOS ; HELP:: LD DE,HLPMSG LD C,MSGOUT CALL BDOS JP 0 ; HLPMSG::DB CR,LF,LF DB 'PROTECT v1.0 -- (c)1984 Simon Ewins' DB CR,LF,LF DB '- the command line allows an ASCII value ' DB '(^A through the upper case' DB CR,LF DB ' letter Z) to be passed as the level ' DB 'of protection for the .COM file.' DB CR,LF DB ' If a code or level that is less than ' DB 'an ASCII ''space'' is desired you' DB CR,LF DB ' may use a caret (^) before the ASCII ' DB 'character that is desired to' DB CR,LF DB ' turn it into a ''control'' character. ' DB 'One character value is reserved' DB CR,LF DB ' as an ''unprotect'' switch. in the ' DB 'example below ^Z is such a value.' DB CR,LF DB ' the character ''^'' may be used as ' DB 'in ^^ but a space or ''@'' may NOT.' DB CR,LF DB ' id est: if you enter a single ^ as ' DB 'an argument an error will occur.' DB CR,LF,LF DB '- syntax is:' DB CR,LF DB ' PROTECT FILENAME[.COM] ^Z ' DB '<-- to ''unprotect'' the file' DB CR,LF DB ' PROTECT FILENAME[.COM] 5 ' DB '<-- set level to (ASCII) 5 (35 hex)' DB CR,LF DB ' PROTECT FILENAME[.COM] Q ' DB '<-- set level to (ASCII) Q (51 hex)' DB CR,LF DB ' protect filename[.com] ^I ' DB '<-- set to (ASCII) CRT-I (9 hex)' DB CR,LF,LF DB '- the .COM filetype is optional as ' DB '.COM will be assumed and any' DB CR,LF DB ' other filetype will generate an error exit' DB CR,LF,EOS ; ; This code gets relocated to high memory where it loads, modifies and ; then saves the .COM file that is being protected... ; ; NOTE: All jumps from here to the end of the program should be rela- ; tive. If the JR label is out of range, use 'stepping-stones' as ; intermediate jump locations. reference to labels for loading ; registers etc., should be of the form: LABEL+off ; CODBEG EQU $ ; Mark start of code to relocate ; LD DE,OFF+RDMSG LD C,MSGOUT CALL BDOS LD DE,TPA ; Point to where program goes LD C,SETDMA ; Set DMA command PUSH DE ; Save it CALL BDOS ; And tell CP/M LD A,0 ; Zero record count LD (FCB+32),A LD (FCB+17),A ; And any arguments LD (FCB+18),A LD C,OPEN ; Open file command LD DE,FCB ; Load address of FCB in DE CALL BDOS ; Open file INC A ; Successful? JR NZ,OPENOK ; If so, then continue LD DE,OFF+ERR0 LD C,MSGOUT CALL BDOS JP 0 ; Error ; OPENOK::POP DE ; Get starting DMA back ; RLOOP:: LD C,SETDMA ; And set it in loop PUSH DE ; Save it CALL BDOS LD DE,FCB ; Point to FCB LD C,READ ; Read sector command CALL BDOS ; Do it POP DE ; Get DMA address back AND A ; Eof? JR NZ,RDDONE ; If so, then go to work PUSH DE ; Save dma LD HL,RELLOC ; Check for overload of tpa DEC H ; Page for safety INC D ; Rest of page AND A ; Clear carry SBC HL,DE ; Top of tpa - current dma JR NC,DMAOK ; Hl still greater POP DE ; Unjunk stack LD DE,OFF+TPAMSG LD C,MSGOUT CALL BDOS ; JP 0000H ; Reboot ; TPAMSG::DB CR,LF,'++ tpa too small ++',CR,LF,EOS DMAOK: POP DE ; Get dma back LD HL,80H ; Length of sector ADD HL,DE ; Bump dma EX DE,HL ; Put new address in de JR RLOOP ; And read some more RDDONE: LD (OFF+ENDADD),DE ; Save end of file LD A,(OFF+UBYTE) ; Was code valid for unprotecting? OR A JR NZ,UNPRO ; Knew the code so go do it ; ; Else falls through to protect routine... Since if UBYTE is 0 then ; PBYTE must have a value and vice versa. Further, the only way that ; UBYTE can have a by the time we reach here is if it was a valid match ; to the unprotect code. ; ; Protect the file ; PRO:: CALL OFF+PROCHK ; Check if file is protected now OR A JR Z,PROT ; 1=yes, 0=no LD DE,OFF+ERR1 LD C,MSGOUT CALL BDOS JP 0 ; PROT:: EX DE,HL ; Got to make sure this file has not been LD DE,128 ; Protected before, else the file will grow AND A ; By one sector each time it is protected! SBC HL,DE ; Hl->start of last sector LD DE,TPA ; Now we compare it to the first sector CALL OFF+MATCH CP 0 ; If 0 then sectors are the same JR Z,NOMOVE ; So don't adjust pointers LD DE,(OFF+ENDADD) ; Get back end of file LD (OFF+OLDEND),DE ; Set pointer for runtime load of sector 1 LD A,(OFF+PBYTE) ; Get protection level requested LD (OFF+LEVEL),A ; Store protection level for this program LD HL,TPA ; And save original data at end of file LD BC,128 ; De already has address of end of program LDIR ; Move first sector to end of program LD HL,OFF+PRODAT ; Get protect routine LD DE,TPA ; And move it to start of file LD BC,128 LDIR ; Move it LD HL,(OFF+ENDADD) ; Need to save one more sector than original LD DE,128 ; So bump marker ADD HL,DE LD (OFF+ENDADD),HL ; And store it in write routine JR WRITE ; NOMOVE::LD HL,(OFF+ENDADD) ; Set runtime sector load address LD DE,128 ; Since it was here already we must point AND A ; To the second last sector SBC HL,DE LD (OFF+OLDEND),HL ; Store adjusted pointer LD A,(OFF+PBYTE) ; Not moving sector but must set new level LD (OFF+LEVEL),A ; Set level LD HL,OFF+PRODAT ; Move protect routine to tpa LD DE,TPA LD BC,128 LDIR JR WRITE ; No need to adjust end address ; ; Unprotect the file ; UNPRO:: CALL OFF+PROCHK ; See if already protected OR A JR NZ,UNPROT ; 1=yes, 0=no LD DE,OFF+ERR2 LD C,MSGOUT CALL BDOS ; JP 0000H ; Reboot ; ERR2:: DB CR,LF,'++ file not protected ++',CR,LF,EOS ; UNPROT::EX DE,HL ; End of program in hl now LD DE,128 ; Less the sector for protecting AND A ; Clear carry SBC HL,DE ; HL->old last sector of file LD DE,TPA ; Start of file LD BC,128 ; Move one sector LDIR ; Back in place ; ; This writes either the full file, if we just protected it, plus one ; record or the full file less the last record, if we unprotected it... ; WRITE:: LD DE,OFF+WRMSG LD C,MSGOUT CALL BDOS XOR A ; Zero A LD (FCB+12),A ; Zero bytes in FCB LD (FCB+14),A LD (FCB+32),A LD C,OPEN ; Open file command LD DE,FCB ; Point to FCB CALL BDOS ; Open the file LD DE,TPA ; Point to program start PUSH DE ; WLOOP1:;POP DE ; Get DMA PUSH DE ; Put it back on stack LD C,SETDMA ; Set DMA command CALL BDOS ; Tell CP/M LD DE,FCB ; Point to FCB LD C,BWRITE ; Write sector command CALL BDOS ; Do it POP HL ; Get dma address from stack LD DE,80H ; Length of sector ADD HL,DE ; HL has new DMA PUSH HL ; Put it on stack ; ; .8080 ; ; DB (LXI D) ; ; .Z80 ; ; ENDADD::DW 0000H ; Get ending address AND A ; Clear carry SBC HL,DE ; See if current dma is less JR C,WLOOP1 ; Still more to write ; LD C,CLOSE ; That's it. Close the file LD DE,FCB ; Point to FCB CALL BDOS ; Do it LD DE,OFF+DNMSG ; Say we done LD C,MSGOUT CALL BDOS JP 0000H ; Reboot ; ; This data gets moved down to the start of the TPA where it is executed ; each time the protected file is run. it is the code that checks the ; values that determine if the file may be run or not... if it can be ; run, then this code replaces itself with the code that was in the ; first reecord of the file originally.... control is then passed back ; to the start of the TPA... ; TPAOFF EQU $ ; PRODAT::JR PROT0 ; Jump around ff marker stuff DW 0FFFFH DW 0FFFFH DW 0FFFFH DW 0FFFFH ; To mark protected file ; PROEXT::LD DE,TPA+PROMSG-TPAOFF LD C,MSGOUT CALL BDOS JP 00000H ; PROMSG::DB CR,LF,'Restricted access...',EOS ; ; .8080 ; ; PROT0:: DB (MVI B) ; ; .Z80 ; ; LEVEL:: DB 0 ; (0 is set to code earlier) LD HL,PROBYT ; Get current user's level LD A,(HL) CP B ; Match to file's level ; IF EXACT JR NZ,PROEXT ; No match so drop out ENDIF ; End EXACT test ; IF NOT EXACT JR C,PROEXT ; Current level less than protect level ENDIF ; End not exact test ; ; .8080 ; ; DB (LXI H) ; ; .Z80 ; ; OLDEND::DW 0000H ; Filled with data at protect time PUSH HL ; Save for after move LD DE,129 ; Point to end of current program + 1 ADD HL,DE PUSH HL ; Hl=address to move to EX DE,HL ; De now=where we are going LD HL,TPA+START1-TPAOFF ; Where we move from LD BC,END1-START1 ; Move this much LDIR POP HL ; Relocated code JP (HL) ; Go finish running file ; START1::POP HL ; Get back address of real program start LD DE,TPA ; Move to start of tpa LD BC,128 LDIR JP TPA ; Go run file ; END1:: DB 0 ; ; End of the control code section ; ; This routine tests to see if the file loaded is protected or not ; PROCHK::LD HL,TPA+2 ; Point to start of file + 2 for jump relative LD C,8 ; Testing next 8 bytes in a row for 0ffh ; TESTIT::LD A,(HL) CP 0FFH JR NZ,NPRO ; Not protected INC HL DEC C JR NZ,TESTIT ; Check all 8 bytes ; YPRO:: LD A,1 ; Indicate protected file RET ; NPRO:: LD A,0 ; Indicate unprotected file RET ;..... ; ; Compare 2 records, return with A=0 if equal ; MATCH:: LD B,128 ; Number of bytes to check ; MLOP:: LD A,(DE) SUB (HL) RET NZ INC DE INC HL DJNZ MLOP ; Do all 128 bytes RET ; A=0 ;..... ; ERR0:: DB CR,LF,'++ Can''t open file ++',CR,LF,EOS ERR1:: DB CR,LF,'++ already protected ++',CR,LF,EOS RDMSG:: DB CR,LF,'Reading....',EOS WRMSG:: DB CR,LF,'Writing....',EOS DNMSG:: DB CR,LF,'Done.......',CR,LF,EOS ; PBYTE:: DB 0 ; Flags protect wanted UBYTE:: DB 0 ; Flags unprotect wanted ; CODEND EQU $ ; END