; LOCATE.ASM - Relocator Program by George D. Massar - Version 1.0
;
; To generate a relocatable program to be located at the offset below
; the base address of the CP/M BDOS.
;
; NOTE:  The program is in the public domain, but not to be
;	 used for commercial benefit.  DONOT remove the
;	 author's name and his credit.
;
; Command synthax:
;
;	LOCATE <prog-file> [(-/+)<offset>]
;
;	where <offset> may be optionally chosen below or above
;	the base of BDOS expressing respectively in negative
;	or positive hex-value with its sign (required).  The
;	value must be on the page boundary.
;
; Input file(s) expected:    <prog-file>.COZ  (ORG 000H)
;			     <prog-file>.COM  (ORG 100H)
;
; Output file(s) generated:  <prog-file>.COM  (relocatable)
;
; Please see LOCATE.DOC for complete description.
;
;
;  1 Nov 81 - First implementation
;  7 Nov 81 - Page offset inquiry added (GDM)
;  5 Dec 81 - Program ID added (GDM)
; 12 Dec 81 - First release, Vers. 1.0


TPA	EQU	0100H

BDOS	EQU	5
WRCON	EQU	2		; write console function
PSTRF	EQU	9		; print string function
OPENF	EQU	15		; open file function
MAKEF	EQU	22		; make file funciton
DELETF	EQU	19		; delete file function
CLOSF	EQU	16		; close file function
READF	EQU	20		; disk read function
WRITF	EQU	21		; write file function
SETDMA	EQU	26		; set DMA address

FCB	EQU	05CH		; file control block
FTYP	EQU	FCB+9		; file type
FCBCR	EQU	FCB+32		; current record #
FCB2	EQU	FCB+16		; second file field
DBUF	EQU	80H		; default buffer

CTL	EQU	1FH
CR	EQU	0DH
LF	EQU	0AH
EOT	EQU	CTL AND 'Z'	; end of ASCII text


	ORG	TPA

GENERATOR:
	JMP	START
	DB	CR,LF,'Relocator - Vers 1.0'
	DB	CR,LF,'George D. Massar'
	DB	CR,LF,'Woodland Hills, CA'
	DB	EOT

START:
	LXI	D,FCB2+1	; get page offset value from
				;   the command line
	LDAX	D		; look at its sign
	CPI	' '		; zero offset if not given
	JZ	ZEROFF
	CPI	'-'		; offset below the BDOS base
	JZ	NEGOFF
	CPI	'+'		; offset above the BDOS base
	JZ	POSOFF
GETERR:
	LXI	D,SYNERR	; synthax error
	JMP	SENDMSG

ZEROFF:
	XRA	A		; zero offset
	JMP	STOFF

NEGOFF:
	CALL	CONVERT 	; to binary value in HL
	MOV	A,L
	ORA	A		; at page boundary?
	JNZ	GETERR		; no
	MOV	A,H
	CMA
	INR	A		; negate page offset
	JMP	STOFF

POSOFF:
	CALL	CONVERT 	; to binary value in HL
	MOV	A,L
	ORA	A		; at page boundary?
	JNZ	GETERR		; no
	MOV	A,H

STOFF:
	STA	PGOFF		; store page offset value

; Open program file with org 0000, <prog-file>.COZ

OPENZ:
	LXI	H,0
	SHLD	CSIZE		; reset counter
	LXI	H,BUFFER
	SHLD	BUFPTR		; at the beg. of the buffer
	LXI	H,FTYP
	MVI	M,'C'
	INX	H
	MVI	M,'O'
	INX	H
	MVI	M,'Z'		; FTYP = 'COZ'
	XRA	A
	STA	FCBCR		; clear current record #
	LXI	D,FCB
	MVI	C,OPENF
	CALL	BDOS
	CPI	255		; file present?
	JNZ	LOADZ		; yes - go on
	LXI	D,OPNERR
	JMP	SENDMSG

LOADZ:
	LXI	D,FCB
	MVI	C,READF
	CALL	BDOS		; read next record in DBUF
	ORA	A		; end of file?
	JNZ	OPENX		; yes - open the next file
	LHLD	CSIZE
	LXI	D,128
	DAD	D
	SHLD	CSIZE		; CSIZE = CSIZE + 128
	LXI	D,DBUF
	LHLD	BUFPTR
	MVI	C,128
LOADZ1:
	LDAX	D		; get next byte from DBUF
	INX	D
	MOV	M,A		; and put it in the buffer
	INX	H
	DCR	C		; end of record?
	JZ	LOADZ2		; yes
	MOV	A,C
	ANI	07H		; mod 8 = 0?
	JNZ	LOADZ1		; no - not yet
	INX	H		; leave a room for bit pattern
	JMP	LOADZ1
LOADZ2:
	INX	H		; last room for bit pattern
	SHLD	BUFPTR
	CALL	CKBPTR		; check memory space
	JMP	LOADZ		; ok - go on

; Open program file with org 0100H, <prog-file>.COM

OPENX:
	LXI	H,BUFFER
	SHLD	BUFPTR
	MVI	A,'M'
	STA	FTYP+2		; FTYP = 'COM'
	XRA	A
	STA	FCBCR		; clear current record #
	MVI	C,OPENF
	LXI	D,FCB
	CALL	BDOS
	CPI	255		; file present?
	JNZ	LOADX		; yes
	LXI	D,OPNERR
	JMP	SENDMSG

LOADX:
	LXI	D,FCB
	MVI	C,READF
	CALL	BDOS		; read next record
	ORA	A		; end of file?
	JNZ	END$LOAD	; yes
	LXI	D,DBUF
	LHLD	BUFPTR
	MVI	C,128
LOADX0:
	XRA	A
	STA	BITPAT		; clear bit pattern
	MVI	B,10000000B
LOADX1:
	LDAX	D
	INX	D
	CMP	M		; compare contents bet. files
	INX	H
	JZ	LOADX2		; jump if both are the same
	LDA	BITPAT
	ORA	B		; set bit in the pattern
	STA	BITPAT
LOADX2:
	MOV	A,B
	RAR			; advance bit position
	MOV	B,A
	DCR	C		; end of record?
	JZ	LOADX3		; yes
	MOV	A,C
	ANI	07H		; mod 8 = 0?
	JNZ	LOADX1		; no - not yet
	LDA	BITPAT
	MOV	M,A		; store the pattern in buffer
	INX	H
	JMP	LOADX0
LOADX3:
	LDA	BITPAT
	MOV	M,A		; store the last pattern before
	INX	H		;   reading the next record
	SHLD	BUFPTR
	CALL	CKBPTR		; check memory space
	JMP	LOADX		; ok - go on
END$LOAD:

; Print statistic results and also store them in program ID

	CALL	PRINTA
	DB	CR,LF,'CODE size:   $'
PRINTA:
	POP	D
	MVI	C,PSTRF
	CALL	BDOS
	LXI	H,CODSIZ
	SHLD	ID$PTR		; ptr to code size in the ID
	LHLD	CSIZE
	CALL	PVALUE		; print CSIZE value

	CALL	PRINTB
	DB	CR,LF,'BDOS offset: $'
PRINTB:
	POP	D
	MVI	C,PSTRF
	CALL	BDOS
	LXI	H,BDOFF
	SHLD	ID$PTR		; ptr to BDOS offset in the ID
	LDA	PGOFF
	MOV	H,A
	MVI	L,0
	CALL	PVALUE		; print BDOFF value

; Write the buffer beginning at the MOVER location

MAKEY:
	LHLD	BUFPTR
	DCX	H
	SHLD	LBYTE		; loc. of the last byte in buf
	LXI	D,-DELTA	; DELTA is the size of Generator
	DAD	D
	SHLD	BUFPTR		; ptr to last byte to be moved
	MVI	A,'C'
	STA	FTYP
	MVI	A,'O'
	STA	FTYP+1
	MVI	A,'M'
	STA	FTYP+2		; FTYP = 'COM'
	LXI	D,FCB
	MVI	C,DELETF
	CALL	BDOS		; delete input file .COM
	XRA	A
	STA	FCBCR		; clear current rec #
	LXI	D,FCB
	MVI	C,MAKEF
	CALL	BDOS		; re-create the file .COM
	CPI	255		; disk full? (do we need this?)
	JNZ	WRITY		; no
	LXI	D,DSKERR
	JMP	SENDMSG
WRITY:
	LXI	H,MOVER 	; set DMA at the beginning of
	SHLD	DMA		;   the buffer
	XCHG
WRITY1:
	MVI	C,SETDMA
	CALL	BDOS
	LXI	D,FCB
	MVI	C,WRITF
	CALL	BDOS		; write the next record
	CPI	255		; write error?
	JNZ	WRITY2		; no - go on
	LXI	D,DSKERR
	JMP	SENDMSG
WRITY2:
	LHLD	DMA
	LXI	D,128
	DAD	D		; next DMA
	SHLD	DMA
	XCHG
	LHLD	LBYTE
	CALL	DSUB		; HL = LBYT-DMA
	JNC	WRITY1		; jump if need to write more
CLOSY:
	LXI	D,FCB
	MVI	C,CLOSF
	CALL	BDOS		; close the output file

	JMP	0		; warmboot


; SUPPORT SUBROUTINES

; Convert a value in ASCII pointed-to by DE to binary val. in HL

CONVERT:
	LXI	H,0
	MOV	B,H
CONV1:
	INX	D
	LDAX	D
	CPI	' '		; end of ASCII string?
	RZ			; yes
	SUI	'0'		; A < '0'?
	JNC	CONV2		; no
	LXI	D,SYNERR	; synthax error
	JMP	SENDMSG
CONV2:
	CPI	10
	JM	CONV3
	SUI	7		; A = hex digit
CONV3:
	CPI	17		; A > 16?
	JC	CONV4		; no
	LXI	D,SYNERR	; synthax error
	JMP	SENDMSG
CONV4:
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	MOV	C,A
	DAD	B		; HL = 16*HL + digit
	JMP	CONV1		; continue for next digit

; Print address value in HL

PVALUE:
	MOV	A,H
	PUSH	H
	CALL	PHEX		; print MSB of addr
	POP	H
	MOV	A,L
	CALL	PHEX		; print LSB of addr
	MVI	A,'H'
	CALL	PCHAR
	RET

; print hex char in reg. A

PHEX:
	PUSH	PSW
	RRC
	RRC
	RRC
	RRC			; shift high 4 bits to right
	CALL	PNIB
	POP	PSW
PNIB:
	ANI	0FH
	CPI	10		; A < 10 ?
	JNC	PNIB1		; no
	ADI	'0'
	JMP	PCHAR
PNIB1:
	ADI	'A'-10

; print a char in reg. A

PCHAR:
	LHLD	ID$PTR
	MOV	M,A		; put char in program ID
	INX	H
	SHLD	ID$PTR
	MOV	E,A
	MVI	C,WRCON
	CALL	BDOS		; and print it on console, too
	RET

; Check memory space left

CKBPTR:
	PUSH	D
	LXI	D,10
	DAD	D		; HL = BUFPTR+10
	XCHG
	LHLD	6		; HL = TOPMEM+1
	CALL	DSUB		; HL = TOPMEM-(BUFPTR+9)
	LXI	D,MEMERR	; memory full?
	JC	SENDMSG 	; yes
	POP	D
	RET

; Send error message and go home

SENDMSG:
	MVI	C,PSTRF
	CALL	BDOS
	JMP	0		; warmboot


; DATA SPACE FOR GENERATOR ROUTINE
;
ID$PTR: DS	2
BITPAT: DS	1		; bit pattern
LBYTE:	DS	2		; loc of the last byte
DMA:	DS	2		; addr of DMA
	DS	32*2
STACK:

OPNERR:
	DB	CR,LF,'>> File not present <<$'
DSKERR:
	DB	CR,LF,'>> Disk full <<$'
SYNERR:
	DB	CR,LF,'>> Synthax error <<$'
MEMERR:
	DB	CR,LF,'>> Memory overflow <<$'

DELTA	EQU	$-GENERATOR	; size of generator program


; <<<<<<<<< THE FOLLOWING WILL BE WRITTEN INTO DISK >>>>>>>>>>

; The program ID below is to indicate the name of program,
; its size & location*, and the author of the program when-
; ever a user wishes to see by TYPE-ing <prog-file>.COM.
; The dashed line may be substituted with a program name and
; its version.	Your name as an author of your own program
; may replace the existing name of the LOCATE creator.
; NOTE: this is the only place you can remove the orginal
; author's name!

; The PASS$ID vector should be at the page boundary or some
; other location where the equivalent ASCII characters of
; the vector would not jeopardize while TYPE-ing the program
; ID heading.

; * The location of the relocatable program can be computed
;   as follows, for example:
;
;	Say BDOS vector is E406H (located at 0006H),
;	the offset is F800 (-800H), and the program
;	size is 180H.
;
;	So, the "ending" of the program is E400+F800-1 = DBFFH
;	and the starting is DBFF+1-200 = DA00H.
;
;   Note: the value of 200H is the next page greater than 180H.
;   The actual ending of the program may be far below than
;   DBFFH but no more than a page.


MOVER:
	JMP	PASS$ID
	DB	CR,LF,'Relocatable Program'
	DB	CR,LF,'----------------------'
	DB	CR,LF,'CODE size:   '
CODSIZ: DS	5
	DB	CR,LF,'BDOS offset: '
BDOFF:	DS	5
	DB	CR,LF,'George D. Massar'
	DB	CR,LF,'Woodland Hills, CA'
	DB	EOT		; DONOT remove EOT here
	DS	128-(($-MOVER) MOD 128)
PASS$ID EQU	$-DELTA

; The DELTA offset as indicated here are the special feature
; to locate the MOVER program with the relocatable program
; at 0100H when loading the program under the CCP command.

	LHLD	CSIZE-DELTA	; program size
	XCHG
	LHLD	6		; HL = base of BDOS+6
	LDA	PGOFF-DELTA
	ADD	H		; add page offset
	MOV	H,A
	CALL	DSUB-DELTA
	MOV	B,H		; B = displacement
	MVI	L,0		; HL = new program base
	DAD	D		; HL = dest. of program code
	XCHG
	LHLD	BUFPTR-DELTA
	XCHG			; DE = CODE ptr in buffer

MOVEUP	EQU	$-DELTA
	DCX	H
	LDAX	D		; get next pattern byte
	STA	PATTERN
	DCX	D
	MVI	C,8		; count of 8 bytes to move

MOVE	EQU	$-DELTA
	LDA	PATTERN
	RAR			; put the corresp. bit in CY
	STA	PATTERN
	LDAX	D		; get next byte in the buffer
	DCX	D
	JNC	SKIP		; jump if bit is not set
	ADD	B		; displacement
SKIP	EQU	$-DELTA
	MOV	M,A		; put CODE byte in memory above
	DCX	H
	DCR	C		; 8th byte?
	JNZ	MOVE		; no - go on

	INX	H
	MOV	A,L
	ORA	A		; at page boundary?
	JNZ	MOVEUP		; no - continue to move
	MOV	A,H
	CMP	B		; HL = ptr to the first byte?
	JNZ	MOVEUP		; no - not yet

	PCHL			; jump to the relocated program

; SUPPORT SUBROUTINE(S) common to both routines

DSUB:
	MOV	A,L
	SUB	E
	MOV	L,A
	MOV	A,H
	SBB	D
	MOV	H,A		; HL = HL - DE
	RET
										
; DATA SPACE for MOVER routine

PATTERN EQU	$-DELTA
	DS	1		; bit pattern hold

; DATA ALLOCATION common to both routines

CSIZE:	DS	2		; size of movable program code
PGOFF:	DS	1		; page offset 
BUFPTR: DS	2		; buffer pointer hold
BUFFER: 			; start of "big" buffer

	END
