;
;Scramble.asm - program to scramble CP/M files
;using an 8 byte password
;
month   equ     3       ;last..
day     equ     14      ;..modification..
year    equ     79      ;..date
;
;Scrambling is done in place, i.e. the file is
;modified on top of itself.  The same password
;used to scramble the file is used to unscramble
;it, using the exact same command.  This is because
;the scrambling code is exclusive-or'ed with the
;data file, and two same exclusive ors result
;in the original value being returned.
;
;Command format:
;
;       scramble filename.type password
;
;where password is any 8 character string which
;is allowable as a file name (i.e. no '.', etc).
;
MF      SET     0       ;SHOW MOVE NOT REQUESTED
CF      SET     0       ;SHOW COMP NOT REQUESTED
;
;(FROM EQU8.LIB...)
;DEFINE SOME MACROS TO MAKE THINGS EASIER
;
;DEFINE DATA MOVE MACRO: MOVE from,to,length
;       from may be addr, or quoted string
;
MOVE    MACRO   ?F,?T,?L
       IF      NOT NUL ?F
       IRPC    ?C,?F
?Q      SET     '&?C&?C' ;;TEST FOR QUOTE
       EXITM
       ENDM
       IF      ?Q EQ ''''
       LOCAL   ?B,?Z
       CALL    ?Z
?B      DB      ?F
?Z      POP     H       ;GET FROM
       LXI     B,?Z-?B ;GET LEN
       ELSE
       LXI     H,?F
       ENDIF
       ENDIF
       IF      NOT NUL ?T
       LXI     D,?T
       ENDIF
       IF      NOT NUL ?L
       LXI     B,?L
       ENDIF
       CALL    MOVER
MF      SET     -1      ;;SHOW EXPANSION
       ENDM
;
;DEFINE CP/M MACRO - CPM FNC,PARM
;
CPM     MACRO   ?F,?P
       PUSH    B
       PUSH    D
       PUSH    H
       IF      NOT NUL ?F
       MVI     C,?F
       ENDIF
       IF      NOT NUL ?P
       LXI     D,?P
       ENDIF
       CALL    BDOS
       POP     H
       POP     D
       POP     B
       ENDM
;
       ORG     100H
       CALL    START
       DB      'SCRAMBLE.COM AS OF '
       DB      '0'+MONTH/10
       DB      '0'+MONTH MOD 10,'/'
       DB      '0'+DAY/10
       DB      '0'+DAY MOD 10,'/'
       DB      '0'+YEAR/10
       DB      '0'+YEAR MOD 10
       DB      0DH,0AH,'$'
START   POP     D       ;GET ID
       MVI     C,PRINT
       CALL    BDOS    ;PRINT ID
;
;INIT LOCAL STACK
;
       LXI     H,0
       DAD     SP
       SHLD    STACK
       LXI     SP,STACK
;
;START OF PROGRAM EXECUTION
;
;SCRAMBLE A WHILE TO MIX UP THE SEED
;
       MVI     H,0     ;GET 256 #'S
MIXUP   CALL    PSEURAN ;GET A #
       DCR     H       ;MORE?
       JNZ     MIXUP   ;LOOP IF SO
;
;SEE THAT THE PASSWORD IS 8 CHARACTERS
;
       LDA     FCB2+8
       CPI     ' '
       JNZ     PWIS8
       CALL    ERXIT
       DB      '++ PASSWORD NOT 8 BYTES ++$'
;
;SAVE THE PASSWORD
;
PWIS8   MOVE    FCB2+1,PASSWD,8
;
;PASSWORD IS 8 BYTES, NOW MAKE SURE NO CHARACTER
;IS REPEATED MORE THAN 2 TIMES
;
       LXI     H,PASSWD
       MVI     B,8     ;8 CHARS TO TEST
DUPTEST CALL    CKDUP   ;ABORTS IF 3 = CHARS
       INX     H       ;TO NEXT CHAR
       DCR     B
       JNZ     DUPTEST
;
;SEE THAT THE INPUT FILE EXISTS
;
       CPM     OPEN,FCB
       INR     A       ;OK?
       JNZ     SCRAMLP ;YES, SCRAMBLE IT
       CALL    ERXIT
       DB      '++NO SUCH FILE++$'
;
;READ THE FILE, SCRAMBLE A SECTOR, RE-WRITE IT.
;
SCRAMLP CALL    RDSECT  ;READ A SECTOR
       JC      FINISH  ;EXIT LOOP IF EOF
       CALL    SCRAMBL ;SCRAMBLE IT
       CALL    BACKUP  ;RE-POSITION FOR WRITE
       CALL    WRSECT  ;RE-WRITE THE SECTOR
       JMP     SCRAMLP ;LOOP UNTIL EOF
;
;ALL DONE - ON A "NORMAL" CP/M SYSTEM, WE WOULDN'T
;HAVE TO DO ANYTHING, BECAUSE WE RE-WROTE IN PLACE.
;
;HOWEVER, FOR SUCH SYSTEMS AS THE NORTHSTAR CP/M,
;WE MUST EXPLICITLY CLOSE THE FILE, BECAUSE THE WRITE
;TO THE DIRECTORY WILL CAUSE THE CLEVER LIFEBOAT-
;DESIGNED BIOS TO FLUSH IT'S MEMORY-RESIDENT DISK
;BUFFERS
;
FINISH  CPM     CLOSE,FCB
       INR     A       ;THIS BETTER WORK..
       JNZ     EXIT
       DB      '++ CLOSE ERROR - FILE LEFT IN '
       DB      'UNKNOWN CONDITION ++$'
;
;SECTOR READ ROUTINE
;
RDSECT  CPM     READ,FCB
       ORA     A
       RZ              ;ALL OK
;
;READ ERROR OR EOF
;
       CPI     1       ;EOF?
       STC             ;CARRY SHOWS EOF
       RZ              ;RET, CARRY SET
       CALL    ERXIT
       DB      '++ READ ERROR - FILE MAY BE '
       DB      'DESTROYED ++$'
;
;SCRAMBLE THE SECTOR
;
SCRAMBL LXI     H,80H   ;POINT TO SECTOR
SCRLP   CALL    PSEURAN ;GET PSEUDO RANDOM #
       XRA     M       ;SCRAMBLE
       MOV     M,A
       INR     L       ;MORE IN SECTOR?
       JNZ     SCRLP
       RET
;
;BACKUP THE FILE POINTER FOR THE RE-WRITE
;
BACKUP  LDA     FCBRNO  ;GET SECTOR #
       DCR     A       ;BACK UP
       STA     FCBRNO
       RP              ;RETURN IF OK
;
;WE BACKED UP INTO PREVIOUS EXTENT, WILL HAVE
;TO RE-OPEN IT
;
       LDA     FCBEXT  ;GET EXTENT
       DCR     A       ;BACK UP 1
       STA     FCBEXT
       CPM     OPEN,FCB ;RE-OPEN
       INR     A
       JNZ     OPEN2OK
       CALL    ERXIT
       DB      '++ RE-OPENING EXTENT FAILED',0DH,0AH
       DB      '++ FILE IS CLOBBERED $'
OPEN2OK MVI     A,7FH   ;GET HI SECTOR
       STA     FCBRNO
       RET
;
;WRITE BACK THE SECTOR
;
WRSECT  CPM     WRITE,FCB
       ORA     A
       RZ
       CALL    ERXIT
       DB      '++ WRITE ERROR - FILE CLOBBERED ++$'
;
;GET A PSEUDO-RANDOM 8 BIT NUMBER USING THE PASSWORD
;AS A SEED
;
;       FOR SPEED, THIS ROUTINE DOES NO REGISTER
;       PUSHES AND POPS, HOWEVER HL AREN'T USED.
;
PSEURAN MVI     C,4     ;GRAB EVERY 4TH PSEU. #
PSEULP0 MVI     B,8     ;SHIFT THRU 8 BYTES
       LXI     D,PASSWD
       ORA     A       ;CLEAR INITIAL CARRY
PSEULP1 LDAX    D       ;GET A CHAR
       RAR             ;SHIFT
       STAX    D
       INX     D
       DCR     B
       JNZ     PSEULP1
;EXCLUSIVE-OR THE LAST FEW BITS INTO THE FIRST ONE
       DCX     D       ;BACK UP TO LAST
       RAR
       RAR             ;SHIFT A FEW MORE
       XCHG
       XRA     M
       RRC             ;SHIFT LO BIT INTO HI
       ANI     80H     ;ISOLATE SINGLE BIT
       LXI     H,PASSWD ;GET FIRST BYTE
       ORA     M       ;'OR' IN THE BIT
       MOV     M,A     ;MOVE IT BACK
       XCHG            ;RESTORE HL
       DCR     C
       JNZ     PSEULP0 ;LOOP IF MORE PASSES
       RET
;
;ROUTINE TO CHECK FOR DUPLICATE CHARS IN PASSWORD
;
CKDUP   MVI     C,3     ;DUP CHAR COUNTER
       LXI     D,PASSWD
       MVI     A,8     ;CHAR COUNT
CKDLP   PUSH    PSW     ;SAVE COUNT
       LDAX    D       ;GET CHAR
       CMP     M       ;DUP?
       JNZ     CKNDUP
       DCR     C       ;COUNT DUPS
       JNZ     CKNDUP
       STA     DUPCHAR ;SAVE FOR PRINT
       CALL    ERXIT
       DB      '++ NO CHARACTER MAY APPEAR MORE '
       DB      'THAN TWICE IN THE PASSWORD.  ',0DH,0AH
       DB      ''''
DUPCHAR DB      $-$,''' DOES IN YOURS ++$'
CKNDUP  INX     D
       POP     PSW     ;GET COUNT
       DCR     A
       JNZ     CKDLP
       RET             ;OK, NOT 3 DUP
;
;FOLLOWING FROM 'EQU7.LIB'---->
;
;MOVE SUBROUTINES
;
       IF      MF      ;MACRO EXPANSION FLAG SET?
MOVER   MOV     A,M
       STAX    D
       INX     H
       INX     D
       DCX     B
       MOV     A,B
       ORA     C
       JNZ     MOVER
       RET
       ENDIF
;
;EXIT WITH ERROR MESSAGE
MSGEXIT EQU     $       ;EXIT W/"INFORMATIONAL" MSG
ERXIT   POP     D       ;GET MSG
       MVI     C,PRINT
       CALL    BDOS
;EXIT, RESTORING STACK AND RETURN
EXIT    LHLD    STACK
       SPHL
       RET             ;TO CCP
PASSWD  DS      8       ;PASSWORD KEPT HERE
       DS      40H     ;STACK AREA
STACK   DS      2
;BDOS/CBIOS EQUATES (VERSION 7)
RDCON   EQU     1
WRCON   EQU     2
PRINT   EQU     9
CONST   EQU     11
OPEN    EQU     15
CLOSE   EQU     16
SRCHF   EQU     17
SRCHN   EQU     18
ERASE   EQU     19
READ    EQU     20
WRITE   EQU     21
MAKE    EQU     22
REN     EQU     23
STDMA   EQU     26
BDOS    EQU     5
FCB     EQU     5CH
FCB2    EQU     6CH
FCBEXT  EQU     FCB+12
FCBRNO  EQU     FCB+32