;*** KEYBD.ASM	- Osborne Executive Keyboard Utility
;*** Version 2.0a - E.Meyer 6/86
;*** This code is for nonprofit use only.
;
;Should assemble with most Z80 assemblers (I use SLR Z80ASM)
;
;>>> If you create and circulate a modified version of this
;>>> program, give credit where it's due, but DO NOT call it
;>>> "KEYBD 2.1" etc -- create your own name.
;
; Syntax:  A>KEYBD source [dest]      (use * for RAM)
; See KEYBD.DOC for further details
;
; NOTE: The tricky thing in this program is dealing with Bank 8
; of RAM, where the keyboards are stored in the ROM's RAM.
; Bank 8 has to be switched in; and it will overshadow the lower
; 16k of the TPA.  Thus the code that does the bank switching,
; and the buffers that hold the data for the swap, have to be
; above address 4000H.
;	The file format (.KBD) in which data is stored by KEYBD is
; exactly identical to the format in RAM.  Only the keyboard
; bytes are significant, however; all other bytes (which have other
; functions in RAM) are ignored.
;
;
KBRAM	EQU	2015H	;address of keyboards in bank 8 RAM
KBLEN	EQU	64	;length of a keyboard
KBRSIZ	EQU	5*KBLEN	;total size of keyboad area (there are 5)
;
KBUF	EQU	4000H	;safe buffer to store data
KBOFF	EQU	13	;offset of keyboard in KBUF
KBUF2	EQU	4400H	;second buffer for copying
HISTD	EQU	4800H	;storage for KEYBD's keyboard
ORIGKB	EQU	4880H	;storage for original keyboard
HILDIR	EQU	4900H	;location for bankswitch routine
;
CTLCR	EQU	209AH	;addr of ctl-RET in bank 8
CTLCOD	EQU	8EH	;use this for ctl-RET
;
;
	ORG	0100H
;
	LD	SP,STACK
	CALL	MSG
	DB	'KEYBD 2.0a (06/86) - Osborne Executive',CR,LF,0
	LD	HL,B0LDIR
	LD	DE,HILDIR
	LD	BC,B0BYTS
	LDIR			;move B0LDIR sub up to HILDIR
	LD	E,0FFH
	LD	C,ERRM		;set error mode to Return
	CALL	BDOS
	LD	C,GDRV
	CALL	BDOS
	INC	A
	LD	(CURDSK),A	;save current disk
	LD	A,(FCB2)
	OR	A
	JR	NZ,DODEF	;if no dest disk,
	LD	HL,FCB2+1	;turn blank dest into *
	LD	A,(HL)
	CP	' '
	JR	NZ,DODEF
	LD	B,8
LP0:	LD	(HL),'?'
	INC	HL
	DJNZ	LP0
DODEF:	LD	HL,FCB+9
	LD	A,(HL)		;default filetypes = KBD
	CP	' '
	CALL	Z,DEFTYP
	LD	HL,FCB2+9
	LD	A,(HL)
	CP	' '
	CALL	Z,DEFTYP
	LD	HL,FCB2
	LD	DE,ARG2
	LD	BC,12		;copy "dest" filename....
	LDIR
	XOR	A		;... into a blank fcb
	LD	B,24
LP1:	LD	(DE),A
	INC	DE
	DJNZ	LP1
;
;--- read in the desired keyboard
;
	CALL	MSG
	DB	'Reading...',0
	LD	A,(FCB+1)
	CP	'?'
	JP	Z,RRAM		;"*" is RAM source
	CP	' '
	JP	Z,RDISK		;no file? read sys trks.
;
RFILE:	LD	DE,FCB		;read from disk file
	LD	C,FOPN
	CALL	BDOS
	OR	A
	JP	NZ,FBAD
	LD	DE,KBUF
	CALL	RFSBR		;read 3 sectors
	LD	DE,KBUF+128
	CALL	RFSBR
	LD	DE,KBUF+256
	CALL	RFSBR
	JP	GOTIT
RFSBR:	LD	C,SDMA		;subroutine to read a sector
	CALL	BDOS
	LD	DE,FCB
	LD	C,RSEQ
	CALL	BDOS
	OR	A
	JP	NZ,RBAD
	RET
;
RDISK:	LD	A,(FCB)		;read from sys trks
	OR	A
	JP	Z,OBAD
	CP	3
	JP	NC,OBAD
	LD	HL,KBUF
	CALL	DOTRK2		;read 3 sectors into KBUF
	JP	GOTIT
;
RRAM:	LD	HL,KBRAM	;read in from RAM
	LD	DE,KBUF+KBOFF
	LD	BC,KBRSIZ
	CALL	HILDIR
;
;--- we now have the keyboards in the buffer
;
GOTIT:	LD	HL,FCB		;decide whether to edit?
	LD	DE,ARG2
	LD	B,12
GOTLP:	LD	A,(DE)		;just EDIT if source = dest
	INC	DE
	CP	(HL)
	INC	HL
	JR	NZ,GOTLPF
	DJNZ	GOTLP
	JR	EDIT
GOTLPF:	LD	A,(FCB+1)	;just LOAD if source=disk, dest=*
	CP	'?'
	JR	Z,EDITQ		;(else ASK if want to Edit)
	LD	A,(ARG2+1)
	CP	'?'
	JP	Z,SAVE
EDITQ:	CALL	MSG
	DB	' Edit keyboard? (Y/N): ',0
	CALL	INP
	CALL	UCASE
	CALL	ECHO
	CP	'N'
	JP	Z,SAVE
;
EDIT:	LD	HL,KBRAM
	LD	DE,ORIGKB
	LD	BC,KBLEN
	CALL	HILDIR		;save original keyboard
	LD	HL,STDKB
	LD	DE,HISTD
	LD	BC,KBLEN
	LDIR			;move STDKB up to HISTD
	LD	HL,HISTD
	LD	DE,KBRAM
	LD	BC,KBLEN
	CALL	HILDIR		;and install STDKB for edit.
	LD	HL,KBUF
	LD	(HL),CTLCOD
	LD	DE,CTLCR	;and ^RET code
	LD	BC,1
	CALL	HILDIR
	CALL	MSG
	DB	CLS,ESC,'f'	;kill function keys
	DB	ESC,'=',32,54,'KEYBD Keyboard Definition Utility'
	DB	ESC,'=',33,54+5,   'v2.0a (c)1986  E.Meyer'
	DB	ESC,'=',34,54,'---------------------------------',0
;
CHOOKB:	CALL	MSG		;which kb to edit?
	DB	ESC,'=',35,32,ESC,'Y'
	DB	ESC,'=',38,44
	DB	'Keyboard to edit:  (1) Normal  (2) Shift  (3) Control'
	DB	ESC,'=',39,44+19,'(4) Caps-lock    (5) Control-shift'
	DB	ESC,'=',42,52,'Select (1-5), or RET to save to ',0
	LD	A,(ARG2+1)
	CP	'?'		;say what dest is
	JR	NZ,CHKB1
	CALL	MSG
	DB	'RAM',0
	JR	CHKB2
CHKB1:	LD	A,(ARG2)
	OR	A
	JR	NZ,CHKB1A
	LD	A,(CURDSK)
CHKB1A:	ADD	'A'-1
	CALL	TYP
	LD	A,':'
	CALL	TYP
	LD	HL,ARG2+1
	LD	A,(HL)
	CP	' '
	JR	Z,CHKB2
	LD	B,8
	CALL	BMSG
	PUSH	HL
	LD	A,'.'
	CALL	TYP
	POP	HL
	LD	B,3
	CALL	BMSG
CHKB2:	CALL	MSG
	DB	': ',0
IN1:	CALL	INP		;get choice
	CP	BRK
	JP	Z,ABORT
	CP	CR
	JP	Z,EDITED
	SUB	31H
	JR	C,IN1B
	CP	5
	JR	C,IN1F
IN1B:	LD	A,BEL		;oops
	CALL	TYP
	JR	IN1
IN1F:	LD	HL,KBUF+KBOFF
	LD	BC,KNAMES
	LD	DE,KBLEN	;compute addr of chosen kb
	OR	A
	JR	Z,SHOKB		;and its title
ADLP:	PUSH	HL
	LD	HL,9
	ADD	HL,BC
	LD	B,H
	LD	C,L
	POP	HL
	ADD	HL,DE
	DEC	A
	JR	NZ,ADLP
SHOKB:	LD	(THISKB),HL	;HL=addr, BC=titleaddr
	LD	H,B
	LD	L,C
	LD	DE,KBNAME
	LD	BC,9
	LDIR			;compose title
;
ELOOP:	CALL	MSG
	DB	ESC,'=',35,32,ESC,'Y'
	DB	ESC,'=',37,61
KBNAME:	DB	'????????? Keyboard:',CR,LF,LF,0	;<---- name here
	LD	B,26
	LD	HL,KORDER
	CALL	NAMES		;show 1st key row (a-z)
	CALL	MSG
	DB	CR,LF,0
	LD	B,26
	LD	DE,KORDER
	CALL	VALUES		;... and their hex values
	CALL	MSG
	DB	CR,LF,LF,0
	LD	B,27
	LD	HL,KORDER+26
	CALL	NAMES		;show 2nd key row (misc)
	LD	A,CR
	CALL	TYP
	LD	B,27
	LD	DE,KORDER+26
	CALL	VALUES		;... and their hex values
IN2:	CALL	MSG
	DB	ESC,'=',46,54,'Key: '
	DB	'      ',ESC,')','(^RET exits)',ESC,'('
	DB	ESC,'=',46,54+5,0
	CALL	INP		;Get key choice
	CP	CTLCOD
	JP	Z,CHOOKB
	CALL	LCASE
	PUSH	AF
	CALL	CNTRL		;echo key NAME
	POP	AF
	LD	HL,STDKB
	LD	BC,KBLEN
	CPIR			;FIND key in STDKB
	JR	Z,IN2F
IN2B:	LD	A,BEL		;oops
	CALL	TYP
	JR	IN2
IN2F:	LD	DE,STDKB
	SCF
	SBC	HL,DE		;compute offset
	LD	DE,(THISKB)
	ADD	HL,DE		;add to get char posn to change
	LD	(THISCH),HL
IN3:	CALL	MSG
	DB	ESC,'=',46,64,'Value: '
	DB	'        ',ESC,')','(chr,RET or H,H)',ESC,'('
	DB	ESC,'=',46,64+7,0
	CALL	INP
	LD	(TEMP),A
	CP	80H
	JR	NC,IN3
	CALL	ECHO
	CALL	INP
	LD	(TEMP+1),A
	CP	CR
	JR	Z,CHRINP
	CALL	ECHO		;HEX input
	LD	HL,TEMP
	CALL	HRBYTE		;translate digits into # in A
NEWC:	LD	HL,(THISCH)
	LD	(HL),A		;insert NEW value in keybd.
	JP	ELOOP
CHRINP:	LD	A,(TEMP)
	JR	NEWC		;character input
;
EDITED:	CALL	RESTOR
	LD	A,CLS
	CALL	TYP
	JP	SAVE
;
RESTOR:	LD	HL,ORIGKB	;restore original keyboard
	LD	DE,KBRAM
	LD	BC,KBLEN
	CALL	HILDIR
	LD	(HL),CR		;and ^RET
	LD	DE,CTLCR
	LD	BC,1
	CALL	HILDIR
	CALL	MSG
	DB	ESC,'e',0	;and function keys
	RET
;
;--- editing finished, now write results
;
SAVE:	CALL	MSG
	DB	'   Saving...',0
	LD	A,(ARG2+1)
	CP	'?'
	JP	Z,WRAM
	CP	' '
	JP	Z,WDISK
;
WFILE:	LD	DE,ARG2		;write to disk file
	LD	C,FDEL
	CALL	BDOS
	LD	DE,ARG2
	LD	C,FMAK
	CALL	BDOS
	OR	A
	JP	NZ,WBAD
	LD	DE,KBUF
	CALL	WFSBR		;write 3 records
	LD	DE,KBUF+128
	CALL	WFSBR
	LD	DE,KBUF+256
	CALL	WFSBR
	LD	DE,ARG2
	LD	C,FCLO
	CALL	BDOS
	JP	DONE
WFSBR:	LD	C,SDMA		;subroutine to write a record
	CALL	BDOS
	LD	DE,ARG2
	LD	C,WSEQ
	CALL	BDOS
	OR	A
	JP	NZ,WBAD
	RET
;
WDISK:	LD	A,(ARG2)	;to write to systrks,
	OR	A
	JP	Z,OBAD
	CP	3
	JP	NC,OBAD		;(A or B only)
	LD	HL,KBUF2
	CALL	DOTRK2		;  first READ existing ones.
	LD	HL,KBUF+KBOFF
	LD	DE,KBUF2+KBOFF
	LD	BC,KBRSIZ
	LDIR			;  move new tables into them,
	LD	HL,WRITE
	LD	(T2OP+1),HL	;  and write them back.
	LD	A,(ARG2)
	LD	HL,KBUF2
	CALL	DOTRK2
	JP	DONE
;
WRAM:	LD	HL,KBUF+KBOFF	;write to RAM
	LD	DE,KBRAM
	LD	BC,KBRSIZ
	CALL	HILDIR
;
;--- all finished now
;
DONE:
EXIT:	CALL	MSG
	DB	CR,LF,0
	LD	C,0
	JP	BDOS
;
;
; *** SUBROUTINES follow
;
;BIOS functions used:
;
SELDSK:	LD	(CREG),A	;C = disk #
	XOR	A
	LD	(EREG),A	;E = 00
	LD	A,9
	JR	BIOS
MULTIO:	LD	A,1
	LD	(CREG),A	;C = 1 block to read
	LD	A,23
	JR	BIOS
SETTRK:	LD	HL,2
	LD	(BCREG),HL	;BC = track 2
	LD	A,10
	JR	BIOS
SETSEC:	LD	HL,4
	LD	(BCREG),HL	;BC = block 4 for sector 33-35
	LD	A,11
	JR	BIOS
SETDMA:	LD	(BCREG),HL	;BC = DMA addr from HL
	LD	A,12
	JR	BIOS
SETBNK:	LD	A,1
	LD	(AREG),A	;A = bank 1
	LD	A,28
	JR	BIOS
READ:	LD	A,13
	JR	BIOS
WRITE:	LD	A,1
	LD	(CREG),A	;C = 1 (nondeferred write)
	LD	A,14
;
BIOS:	LD	(BIOSFN),A	;call BIOS fn in A
	LD	DE,BIOSPB
	LD	C,50
	JP	BDOS
BIOSER:	CALL	MSG
	DB	'<BIOS ERR>',0
	JP	DONE
;
BIOSPB:				; *** BIOS Parameter block
BIOSFN:	DB	0
AREG:	DB	0
BCREG:	DW	0
DEREG:	DW	0
HLREG:	DW	0
CREG	EQU	BCREG
EREG	EQU	DEREG
;
;
DOTRK2:	DEC	A		;takes drive in A, buff addr in HL
	PUSH	HL
	CALL	SELDSK
	CALL	MULTIO
	CALL	SETTRK
	CALL	SETSEC
	POP	HL
	CALL	SETDMA
	CALL	SETBNK
T2OP:	CALL	READ		; *** changes to WRITE if needed
	PUSH	AF
	LD	A,(CURDSK)
	DEC	A
	CALL	SELDSK	;restore original disk
	POP	AF
	OR	A
	JP	NZ,BIOSER
	RET
;
;
DEFTYP:	LD	(HL),'K'	;make default type ".KBD"
	INC	HL
	LD	(HL),'B'
	INC	HL
	LD	(HL),'D'
	RET
;
NAMES:	PUSH	BC		;display B names starting at HL
	PUSH	HL
	CALL	MSG
	DB	ESC,')',0		;(in dim video)
	POP	HL
	POP	BC
NEXTN:	PUSH	BC
	PUSH	HL
	LD	A,(HL)
	CALL	CNTRL
	POP	HL
	POP	BC
	INC	HL
	DJNZ	NEXTN
	CALL	MSG
	DB	ESC,'(',0
	RET
CNTRL:	CP	21H		;SHOW A KEY NAME
	JR	C,NM0
	CP	7FH
	JR	C,NM1
NM0:	CP	8AH
	JR	Z,UP
	CP	8BH
	JR	Z,RGT
	CP	8CH
	JR	Z,DWN
	CP	8DH
	JR	Z,LFT
	CP	' '
	JR	Z,SPC
	CP	CR
	JR	Z,NTR
	CP	TAB
	JR	Z,HTB
	CP	ESC
	JR	Z,SCP
	LD	A,'?'
	JR	NM1
DWN:	LD	A,'v'
	JR	NM1
UP:	LD	A,'^'
	JR	NM1
LFT:	LD	A,'<'
	JR	NM1
RGT:	LD	A,'>'
	JR	NM1
NM1:	CALL	TYP
	CALL	MSG
	DB	'  ',0
	RET
SCP:	CALL	MSG
	DB	'ES ',0
	RET
SPC:	CALL	MSG
	DB	'SP ',0
	RET
HTB:	CALL	MSG
	DB	'TB ',0
	RET
NTR:	CALL	MSG
	DB	'CR ',0
	RET
;
VALUES:	PUSH	BC		;display B values starting at DE
	PUSH	DE
	LD	A,(DE)
	LD	HL,STDKB
	LD	BC,KBLEN
	CPIR			;find key posn
	LD	DE,STDKB
	SCF
	SBC	HL,DE		;compute offset
	LD	DE,(THISKB)
	ADD	HL,DE		;compute value posn
	LD	A,(HL)
	CP	7FH		;is it printable?
	JR	NC,VL1
	CALL	ECHO		;yes, show it
	JR	VL2
VL1:	CALL	HBYTE		;no, show HEX value
VL2:	LD	A,' '
	CALL	TYP
	POP	DE
	INC	DE
	POP	BC
	DJNZ	VALUES
	RET
;
;
HRDIG:	LD	A,(HL)	;converts hex digit at address HL
	AND	7FH	;  to # 0-15 in A and E.
	JR	Z,ERR
	CP	'A'
	JR	C,HRD1
	AND	5FH	;(letter A-F)
	SUB	7
HRD1:	SUB	30H
	JP	M,ERR
	CP	10H
	JR	NC,ERR
	LD	E,A
	RET
ERR:	POP	HL	;kill pending return
	LD	A,BEL
	CALL	TYP
	JP	IN3
HRBYTE:	CALL	HRDIG	;convertes two hex digits at address HL
	RLCA		;  to # 0-255 in A and E.
	RLCA
	RLCA
	RLCA
	INC	HL
	PUSH	AF
	CALL	HRDIG
	POP	AF
	ADD	E
	LD	E,A
	RET
;
HBYTE:	PUSH	AF	;put hex# 0-255 in A to screen
	AND	0F0H
	RRA
	RRA
	RRA
	RRA
	CALL	HDIGIT
	POP	AF
	AND	0FH
HDIGIT:	ADD	30H	;put hex# 0-F in A to screen
	CP	3AH
	JP	C,TYP
	ADD	7
	JP	TYP
;
INP:	LD	A,0FDH	;Plain BDOS console i/o
TYP:	LD	E,A
	LD	C,UCON
	JP	BDOS
MSG:	EX	(SP),HL	;put DB string ending in 0 to screen
	XOR	A
	ADD	(HL)
	INC	HL
	EX	(SP),HL
	RET	Z
	CALL	TYP
	JP	MSG
;
UCASE:	CP	'a'	;uppercase A
	RET	C
	CP	'z'+1
	RET	NC
	AND	5FH
	RET
LCASE:	CP	'A'	;lowercase A
	RET	C
	CP	'Z'+1
	RET	NC
	OR	20H
	RET
;
ECHO:	CP	7FH	;echo input if nice
	RET	NC
	PUSH	AF
	CP	20H
	JR	NC,ECHO1
	LD	A,'^'
	CALL	TYP
	POP	AF
	PUSH	AF
	ADD	40H
	JR	ECHO2
ECHO1:	CALL	TYP
	LD	A,' '
ECHO2:	CALL	TYP
	POP	AF
	RET
;
BMSG:	XOR	A	;show B bytes at (HL)
	ADD	B
	RET	Z
	LD	A,(HL)
	INC	HL
	PUSH	BC
	PUSH	HL
	AND	7FH
	CALL	TYP
	POP	HL
	POP	BC
	DJNZ	BMSG
	RET
;
;
OBAD:	CALL	MSG
	DB	'<BAD ARGT>',0
	JP	DONE
FBAD:	CALL	MSG
	DB	'<NO FILE>',0
	JP	DONE
WBAD:
RBAD:	CALL	MSG
	DB	'<BDOS ERR>',0
	JP	DONE
ABORT:	CALL	RESTOR
	CALL	MSG
	DB	'<ABORTED>',0
	JP	DONE
;
;
B0LDIR:	LD	A,10000001B	;bank 8 LDIR routine
	DI			;(must be above 4000H to work)
	OUT	0,A
	LDIR
	LD	A,00000001B
	OUT	0,A
	EI
	RET
B0BYTS	EQU	$-B0LDIR
;
;data
;
STDKB:	DB	ESC,TAB,0FFH,0FFH,0FFH,CR,27H,'['	;normal KBD
	DB	'12345678'
	DB	'qwertyui'
	DB	'asdfghjk'
	DB	'zxcvbnm,'
	DB	8AH,8DH,'0 .po9'
	DB	8BH,8CH,'-/;\l='
	DB	0FFH,0FFH,0FFH,0FFH,0FFH,0FFH,0FFH,0FFH
;
KORDER:	DB	'abcdefghijklmnopqrstuvwxyz'
	DB	'0123456789[/\-='';,. ',ESC,TAB,CR,8AH,8BH,8CH,8DH
;
KNAMES:	DB	'   Normal','    Shift','  Control'
	DB	'Caps-lock','Ctl-Shift'
;
CURDSK:	DS	1
THISKB:	DS	2
THISCH:	DS	2
TEMP:	DS	2
ARG2:	DS	36
;
	DS	64
STACK	EQU	$
;
;
CR	EQU	0DH
LF	EQU	0AH
BS	EQU	08H
TAB	EQU	09H
ESC	EQU	1BH
CLS	EQU	1AH
BEL	EQU	07H
BRK	EQU	03H
;
BDOS	EQU	0005H
FCB	EQU	005CH
FCB2	EQU	006CH
DMA	EQU	0080H
;
UCON	EQU	6
FOPN	EQU	15
FCLO	EQU	16
FDEL	EQU	19
RSEQ	EQU	20
WSEQ	EQU	21
FMAK	EQU	22
GDRV	EQU	25
SDMA	EQU	26
ERRM	EQU	45
;
	END
