; Written by Dan Norstedt, Stockholm, Sweden		Last edit 17-Feb-1990
;
; This program is placed in the public domain.
;
; Use this program to generate MSBPCT.COM for MS-DOS with the commands:
;
;   C>MASM MSBPCG;
;   C>LINK MSBPCG;
;   C>MSBPCG >MSBPCT.COM
;
; The generated MSBPCT.COM file is trick coded, and only contains
; printable 7-bit ASCII characters. Thus, it can be sent as text
; without modifications, just capture it to disk, trim off excess
; text, and execute it. It takes one input argument, the input file.
; If no valid file is given, a Usage: message is given.
;
; The program will decodes about 10Kbyte/sec on my AT hard disk.
;
; The loader and builder portions of this program may be useful for other
; programs, too.

	CODE	SEGMENT PUBLIC

	ASSUME	CS:CODE,DS:CODE

	ORG	100H

START:	JMP	GENCOM

	ORG	100H+72+1		; Address where loader starts loading

CODARE:

;----------------------- beginning of MSBPCT code area -----------------------

INBUF	EQU	((Offset MSBEND-Offset START+1FFH) AND 0FF00H)
INBSIZ	EQU	400H
OUTBUF	EQU	INBUF+INBSIZ

;	CLD			; Already done in the loader
	MOV	DX,Offset MEMTXT
	CMP	SP,0FF00H	; Enough memory?
	JB	OUTMSG
	MOV	SI,80H		; Point at line length byte
	XOR	AH,AH
	LODSB			; Get it
	XCHG	AX,BX
	MOV	Word Ptr [SI+BX],0 ; Clear end of command string
	LEA	DX,[SI+1]
	MOV	AX,3D00H	; Try to open the file
	INT	21H
	MOV	DX,Offset USETXT ; Tell usage if we can't open input file
	JNC	OPENOK
OUTMSG:	MOV	AX,901H		; Output error text
EXIT:	PUSH	AX
	INT	21H
	POP	AX		; Set up exit code
	MOV	AH,4CH
	INT	21H
OPENOK:	XCHG	BX,AX		; Save input handle
	MOV	DX,Offset FILTXT ; Output header
	MOV	AH,9
	MOV	SI,OUTBUF	; Temorary buffer for output filename
RDMORE:	INT	21H
	MOV	AH,3FH		; Read a character
	MOV	CX,1
	MOV	DX,SI
	INT	21H
ERROR:	MOV	DX,Offset ERRTXT
	JC	OUTMSG		; Error if failure here
	DEC	AX
	JNZ	OUTMSG
	LODSB
	XCHG	AX,DX
	MOV	AH,2		; Prepare for output
	CMP	DL,20H
	JA	RDMORE		; Printable character -> save and output
	MOV	AH,3CH		; Prepare for file create
	XOR	CX,CX
	MOV	[SI-1],CL	; Make input filename buffer NUL terminated
	MOV	DX,OUTBUF	; Pointer to file name
	MOV	DI,DX
	INT	21H		; Create output file
	JC	ERROR
	XCHG	AX,BX		; Save file output file handle in BX
	XCHG	AX,BP		; Input file handle to BP
READBU:	PUSH	DX		; Save shift count (initial value in DL = 0)
	MOV	DX,OUTBUF
	MOV	CX,DI
	SUB	CX,DX		; Compute size of output buffer
	JZ	NOWRIT		; Zero, skip write
	MOV	AH,40H		; Write it out
	INT	21H
	JC	ERROR
	SUB	AX,CX		; Write failed?
	JC	ERROR
NOWRIT:	MOV	DI,DX		; No, reset output buffer pointer
	MOV	DX,INBUF	; Load input buffer pointer
	MOV	SI,DX		; Set up for later use
	MOV	CX,INBSIZ	; Read one buffer of appropriate size
	MOV	AH,3FH
	XCHG	BX,BP		; Swap input and output file handles
	INT	21H
	XCHG	BX,BP
	JC	ERROR
	XCHG	AX,CX
	MOV	AX,3E00H	; Load good close function code, just in case
	JCXZ	EXIT		; End of input file, do the good exit
	POP	DX		; Regain shift count (in DL)
INLOOP:	LODSB			; Get a character
	SUB	AL,30H
	JL	CHRDON		; Junk, throw away
	MOV	AH,DH		; Good one, get old saved char to AH
	MOV	DH,AL		; Save current instead
	CMP	AH,4EH		; Old character flags expansion of NULs?
	JZ	NULLS
	SHL	AL,1		; No merge low bits of old char with new char
	SHL	AL,1
	XCHG	CX,DX		; Get current shift amount (0, 2, 4 or 6)
	SHR	AX,CL		; Do shift
	XCHG	CX,DX
	SUB	DL,2		; Adjust for new shift factor,old = 0 -> skip?
	JS	INLOOR		; Yes, skip
	STOSB			; No, save result of shift operation
INLOOR:	AND	DL,6		; Adjust shift count if DL was = 0
	JMP	Short CHRDON	; End loop
NULLS:	CBW			; Expand NUL count
	CWD			; Reset shift count in DL and NULL flag in DH
	PUSH	CX		; Save input character left counter 
	XCHG	AX,CX
	MOV	AL,0
	REP	STOSB		; Store selected amount of NULs
	POP	CX		; Restore counter
CHRDON:	LOOP	INLOOP		; Loop for rest of input block
	JMP	Short READBU	; Try to read another buffer

USETXT	DB	'Usage: MSBPCT file.BOO$'

FILTXT	DB	'Unpacking to file: $'

ERRTXT	DB	'Error during file I/O$'

MEMTXT	DB	'Not enough memory$'

MSBEND:

;-------------------------- end of MSBPCT code area --------------------------

CODEND:

;-- Loader starts here; don't touch unless you REALLY know what's going on --

FIXUP	MACRO	LABL,OFFS,DAT,DA2,DAS
	ORG	$+(OFFS)
LABL	Label	Byte
	IFNB	<DAS>
	 IFNB	<DA2>
	  DB	 (((DAS) OR 65H)-((DAS) AND 65H)) AND 0FFH,0FFH-(DA2)
	  ORG	 $-1
	 ELSE
	  DB	 ((DAS)+65H) AND 0FFH
	 ENDIF
	ELSE
	 DB	0FFH-((DAT) AND 0FFH)
	 IFNB	<DA2>
	  DB	 0FFH-(DA2)
	  ORG	 $-1
	 ENDIF
	ENDIF
	ORG	$-(OFFS)-1
	ENDM

FIXBYT	MACRO	ADDR	 ; Generate "XOR [BX+ADDR-0FFH],AL" with 8-bit offset
	DB	30H,47H,(Offset ADDR-Offset LOADER+100H)-0FFH
	ENDM

FIXBYH	MACRO	ADDR	 ; Generate "XOR [BX+ADDR-0FFH],AH" with 8-bit offset
	DB	30H,67H,(Offset ADDR-Offset LOADER+100H)-0FFH
	ENDM

FIXWRD	MACRO	ADDR	 ; Generate "XOR [BX+ADDR-0FFH],AX" with 8-bit offset
	DB	31H,47H,(Offset ADDR-Offset LOADER+100H)-0FFH
	ENDM

FIXSUB	MACRO	ADDR	 ; Generate "SUB [BX+ADDR-0FFH],AL" with 8-bit offset
	DB	28H,47H,(Offset ADDR-Offset LOADER+100H)-0FFH
	ENDM

LOADER:	POP	AX
	PUSH	AX
	DEC	AX		; Load AX with 0FFFFH
	PUSH	AX		; Now FF FF 00 00 on stack
	INC	SP
	POP	BX		; Load BX with 00FF
	FIXBYT	XORBXX		; Fix up end of loader code
	FIXBYT	XORB1
	FIXBYT	XORB2
	FIXWRD	XORW1
	FIXWRD	XORW2
	DAA			; Load AL with 65H
	FIXSUB	SUBB1
	FIXSUB	SUBB2
	JNZ	J0JMP		; Break pipeline
J0DST:	FIXBYH	XORB3
	FIXSUB	SUBB3
	FIXSUB	SUBB4
	FIXWRD	XORX1

; FC
	CLD			; Set LODSB direction
	FIXUP	SUBB1,-1,,,0FCH
; 8D7749  LEA	SI,[BX+DATA-0FFH] ; Point at 100H+72
	DB	8DH,77H,(Offset DATA-Offset LOADER+100H)-0FFH
	FIXUP	XORB1,-3,8DH
; 56
	PUSH	SI		; Copy SI -> DI
; 5F
	POP	DI
; 46
	INC	SI
J2DST:
; 2AC2
	SUB	AL,DL		; Compute real code/data byte
; AA
	STOSB			; Save it
	FIXUP	XORW1,-2,0C2H,0AAH
J1DST:
J3DST:
; AC
	LODSB			; Get an encoded data byte
	FIXUP	XORB2,-1,0ACH
; 40
	INC	AX
; 3C31
	CMP	AL,31H		; Printable and >= '0'?
; 7CFA
	JL	J1DST
J1SRC:	FIXUP	SUBB2,-1,,,J1DST-J1SRC
; 2C35
	SUB	AL,'4'+1	; Yes, '0'-'3' (or '4' = exit code) ?
; 77F3
	JA	J2DST		; No, store with current prefix code
J2SRC:	FIXUP	SUBB3,-1,,,J2DST-J2SRC
; B102
	MOV	CL,2
	FIXUP	XORB3,-2,0B1H
; D2C8
	ROR	AL,CL
	FIXUP	XORX1,-3,,0D2H,2
; 92
	XCHG	DX,AX		; Yes, just save shifted value
	FIXUP	XORW2,-2,0C8H,92H
; 75EF
	JNZ	J3DST		; No, contine loop
J3SRC:	FIXUP	SUBB4,-1,,,J3DST-J3SRC
; 75D7
J0JMP:	JNZ	J0DST		; (Dummy branch used to clear prefetch queue)
J0SRC:	FIXUP	XORBXX,-1,J0DST-J0SRC
	DB	34H		; Skip over next byte (34H = XOR AL,nn opcode)

DATA:	DB	"$"	    ; CRLF data to make sure JL J1DST taken first time

GENCOM:	PUSH	CS			; Allow use without EXE2BIN
	POP	DS
	MOV	DX,Offset LOADER	; Output LOADER code (!)
	MOV	AH,9
	INT	21H
	CLD
	MOV	SI,Offset CODARE	; Pointer to real MSBPCT code
	XOR	BP,BP			; Reset columns left counter
	MOV	BH,17			; Assure that BH not in range '0'-'3'
BYTLOP:	MOV	AX,0C01H
	SUB	AL,[SI]			; Convert MSBPCT code to loader format
	INC	SI
	MOV	CL,2
	SHL	AX,CL			; First byte is top 2 bits of byte+43H
	NOT	AL
	SHR	AL,CL			; Second byte is low 6 bits of -byte-3
	ADD	AL,35H			; Based value is '5' for second byte
	CMP	AH,BH			; Same prefix as previous byte?
	MOV	BH,AH
	XCHG	DX,AX
OUTBYT:	XCHG	DH,DL			; Swap output order
	JZ	OUTNHI			; Skip unnecessary prefix byte
	DEC	BP
	JG	OUTNCR			; Not 72 chars on the line yet
	PUSH	DX
	MOV	DX,Offset CRLF		; 72 chars on line, add CR LF
	MOV	AH,9
	INT	21H
	MOV	BP,72			; Restart line pointer
	POP	DX
OUTNCR:	MOV	AH,2			; Output a byte
	INT	21H
OUTNHI:	XOR	DL,DL			; Clear out used code byte
	AND	DH,DH			; Anything more to print?
	JNZ	OUTBYT
	CMP	SI,Offset CODEND	; End of area?
	JNZ	BYTLOP
	MOV	DX,Offset ENDTXT	; Yes, add trailer: 34H and CR LF
	MOV	AH,9
	INT	21H
	MOV	AH,4CH			; End GENCOM program section
	INT	21H

ENDTXT	DB	34H			; End of file marker for loader
CRLF	DB	13,10,"$"

CODE ENDS
	END	START
