Title 'Time stamp   vers: 03.11  date: 10/11/83  time: 22:37:17'

;************************
;			*
;	timestamp3	*
;			*
; version, date & time	*
;	stamp		*
;    for cp/m+		*
;			*
;************************

;	by	dick lieber - Bridgeport RCP/M+ (Chicago) 312-326-4392
;	based on a program by Eric Forbes
;
;	version 3.12	fix leap year bug  rcl
;	version 3.11	fix wrong data for July & Nov in months table
;	version 3.1	don't abort if file not found so a new file
;			can be created with editor.
;
;	if assembled as a transient program
;
;	A>ts3 name.typ [o]
;
;	Will increment the minor version number & date/time stamp the file
;	name.typ if the strings:
;
;		"vers:"
;		"time:"
;		"date:"
;
;	are found in the first 128 bytes of the file.  Be sure
;	to allow enough blank space after each string for the 
;	information shown!
;
;	[o] is an option:
;
;		where o=N	to leave old version number
;		      o=M	to increment major version number
;				and zero minor version number
;
;	if assembled as a patch and SIDed onto an editor
;	the same functions will take place automatically
;	before the actual edit:
;
;	A>edit name.typ [o]
;
;	an additional option is added with the patched version:
;
;	[z] will cause the timestamp patch to not modify the
;	file at all
;
;	To attach the patched version of timestamp:
;
;	A>mac ts3		<= assemble "patched" version
;	CP/M MACRO ASSEM 2.0
;	2B2E
;	024H USE FACTOR
;	END OF ASSEMBLY

;	A>sid ws.com		<= load your editor
;	CP/M 3 SID - Version 3.0
;	NEXT MSZE  PC  END
;	2700 2700 0100 CEFF
;	#rts3.hex		<= load ts3 patch
;	NEXT MSZE  PC  END
;	2B2E 2B2E 0100 CEF	<= note the NEXT address
;	#wwsts.com,100,2b2e	<= save patched editpor
;	0055h record(s) written.
;	#^C

;	Use and enjoy you new editor that automatically
;	updates version etc.
;

maclib	z80		;as supplied with cp/m+

no:	equ	0
yes:	equ	not no

patched: equ	yes 	;yes to attach to an editor
			;no to make transient command

opt$del: equ	'['	;option delimiter
recsiz:	equ	128		;record length
cr:	equ	0dh
lf:	equ	0ah
bell:	equ	7


	if 	patched	;into an editor
;
;	these equates define interface of 
;	this program to the editor
;
;	values show are for Wordmaster

endasm:	equ	2700h	;where this program will live

oldjmp:	equ	269h	;used to restore the jump at the
			;start of the assembler

jmpadd:	equ	0100h	;address of the JP (C3h) used to get
			;to this program

assemb:	equ	0100h	;address to return to when finished
			;updating the file
;
;	this will patch the hook from the editor 
;	to this porgram.  it gets restored automatically
;	when when version info has been updated
;
	org	jmpadd + 1
	dw	endasm		;patch hook to this program

	else

endasm	equ	100h	;org if stand alone
fcb	equ	5ch	;work at default fcb cause
			;we don't care if it's altered
	endif

	org	endasm		;end of assembler or tpa

start:	lxi	d,crlf
	mvi	c,prstr		;new line
	call	bdos
;
;	check version
;
	mvi	c,getvers
	call	bdos
	lxi	d,bad$ver$mess
	mov	a,h
	cpi	1		;check if mp/m
	jrz	version$ok
	mov	a,l
	cpi	31h
	jc	exit		;below cp/m+
version$ok:

	if 	patched	;into an editor
;
;	copy name to timestamp's fcb
;
	lxi	h,5ch	;default fcb set up by ccp
	lxi	d,fcb
	lxi	b,15	;length of drive & name
	ldir		;move it
	endif		;patched
;
;	copy password to dma
;
	lda	53h	;get password length
	ora	a	;set flags
	jrz	no$password
	mov	c,a	;get length of password
	mvi	b,0
	lhld	51h	;get password address (source)
	lxi	d,buff	;(dest)
	ldir		;move password to dma
			;password must be in dma
			;on open call if file is
			;read protected
no$password:
	mvi	a,' '	;no option
	sta	option	
	lda	80h	;get command tail length
	mov	c,a
	mvi	b,0
	lxi	h,81h	;start of tail
	dad	b	;point to end of tail
	mvi	a,opt$del
	ccdr		;search for delimeter
	jpo	option$done
	inx h! inx h	;point to option byte
	mov	a,m
	sta	option
	cpi	'N'		;keep old version option
	jrz	option$done
	cpi	'M'		;major version update option
	jrz	option$done

	if	patched
	cpi	'Z'		;no change at all option
	mvi	a,0		;no abort
	lxi	d,as$is$msg
	jz	exit
	endif	;patched

	lxi	d,bad$opt$msg
	mvi	a,0ffh		;abort flag
	jmp	exit
option$done:
;
;	open file
;
	lxi	d,buff
	mvi	c,setdma
	call	bdos
	lxi	d,fcb
	mvi	c,15
	call	bdos
	lxi	d,nofile	;report if can't open and
	inr	a
	if 	patched
	mvi	a,0		;reset abort flag
	else
	mvi	a,0ffh		;set abort flag
	endif
	jz	exit
;
;	zero record number
;
	lxi	h,fcb+33
	mvi	b,2
rn$loop:
	mvi	m,0
	dcr	b
	inx	h
	jrnz	rn$loop

	lxi	d,fcb
	mvi	c,readran	;read record 1st record (0)
	call	bdos

	lxi	d,verstx
	call	find		;update version
	lxi	d,novers
	cnz	print
	cz	versn

	lxi	d,datetx
	call	find		;update date
	lxi	d,nodate
	cnz	print
	cz	date

	lxi	d,timetx
	call	find		;update time
	lxi	d,notime
	cnz	print
	cz	time
;
;	write record back into file and exit to editor or CP/M
;
	mvi	c,writeran	;random write
	lxi	d,fcb
	call	bdos

	mvi	c,closef
	lxi	d,fcb
	call	bdos

	lxi	d,done$msg

exit:	call	print		;print error message and exit.
	ora	a		;a=ff means abort after message
	jnz	0		;abort if error

	if 	patched	;into editor
;
;	Restore the editor to it's original condition and jump to it
;
	lxi	h,oldjmp	;restore value in hl
	shld	jmpadd+1	;restore old jump address
	jmp	assemb		;jump to the assembler

	else

	ret	;to cp/m+

	endif	;patched
;
;	print a string (de) and file name
;
print:	push	psw
	mvi	c,prstr
	call	bdos
	lxi	h,fcb+1
	lxi	d,prtnam
	lxi	b,11
	ldir
	lxi	d,prtfil
	mvi	c,prstr
	call	bdos
	pop	psw
	ret
;
;Increment the version number
;
versn:	inx	h		;hl --> units of major change
	lda	option		;is a major change requested
	cpi	'M'
	cz	twoinc		;inc major change number
	jrz	zeromn		;zero minor change number
	cpi	'N'		;do not change version if
	rz			;'n' option given
	inx	h
	inx	h
	inx	h		;hl --> units of vers no.

twoinc:	push	psw		;increment a 2 digit field
	push	h
	mvi	b,2
	mvi	a,'9'+1		;hl --> units position
two1:	inr	m
	cmp	m
	jrnz	twox		;exit if not > 9 ascii
	mvi	m,'0'		;else zero the units
	dcx	h		;and inc the tens
	djnz	two1
twox:	pop	h
	pop	psw
	ret

zeromn:	inx	h		;zero minor version
	inx	h
	mvi	m,'0'	;used when major changes
	inx	h
	mvi	m,'0'
	ret
;
;	insert new date into buffer at hl
;
date:
	call	get$clock
	push	h
	lhld	datepb		;get days since 1/1/78
	call	cnvday		;convert to month,day, year
	pop	h
	lda	months
	call	putbcd

	mvi	m,'/'
	inx	h

	lda	days
	call	putbcd

	mvi	m,'/'
	inx	h

	lda	years
	call	putbcd
	ret
;
;	convert bcd in a to two ascii charcters at hl
;
putbcd:
	push	psw
	rrc ! rrc ! rrc ! rrc	;get high nibble
	call	putnib
	pop	psw
	call	putnib
	ret
;
;	make ascii & put into buffer
;
putnib:	ani	0fh
	ori	'0'
	mov	m,a
	inx	h		;next buffer location
	ret
;
;	insert time into buffer at hl
;
time:
	call	get$clock
	lxi	d,datepb+2	;point to seconds byte
	sta	datepb+4	;put seconds in date parm block
	mvi	b,2
time1:	ldax	d
	inx	d
	call	putbcd		;insert hh:mm:
	mvi	m,':'
	inx	h
	djnz	time1
	ldax	d
	call	putbcd		;insert seconds
	ret
;
;	get time from cp/m+
;
get$clock:
	push	h
;
;	this bios call is needed because the bdos rdtime
;	function doesn't call bios to update the clock data
;	in the scb
;
	mvi	c,drbios	;direct bios call
	lxi	d,biospb	;read time function
	call	bdos
	mvi	c,rdtime	;get date/time
	lxi	d,datepb	;where to put date
	call	bdos
	pop	h
	ret

biospb:	db	26	;time function
	db	0	;value for A 

;
;	find the first character of string in de (V, D or T)
;
find:	lxi	h,buff		;de = compare string
	lxi	b,recsiz	;limit search to 1 rec
trynxt:	ldax	d		;get 1st char to find
	ccir
	rpo			;ret nz set = not found
;
;	see if the rest of the string compares equal. Retry
;	until we get to the end of the buffer
;
	push	b
	push	d
	mvi	b,5		;compare next 5 chars
find2:	inx	d
	ldax	d
	cmp	m
	jrnz	tryagn		;try for another string
	inx	h
	djnz	find2		;keep comparing til b = 0
	pop	d
	pop	b
	ret			;ret with z set

tryagn:	pop	d		;Found the 1st character, but
	pop	b		;there was a bad compare in
	jr	trynxt		;the next 6 characters.

;************************************************
;						*
;	cnvdate					*
;						*
;	routine to convert cp/m or mp/m date 	*
;	from days since 1/1/78 to month, day &	*
;	year.					*
;						*
;						*
;************************************************
;
;
year	equ	365	;leap year gets adjusted for
;
;	count up until goal days exceeded
;
cnvday:
	shld	goaldays	;save as goal to reach
	mvi	a,1		;initial values
	sta	days
	sta	months		;month counter (jan is one)

	lxi	h,0
	shld	dayscnt		;start at zero
	lxi	d,year		;one year of days
	
	mvi	a,78h		;the first year (bcd)
	sta	years		;save year value
;
;	it's much eaiser to detect leap years in binary
;
	mvi	a,78		;1st year (binary)
	sta	years$bin	;for leap year determination
yearloop:
	lhld	dayscnt
	dad	d		;trial add one year of days
	call	ckleap
	jrnz	no$leap
	inx	h		;was a leap year
no$leap:
	call	ckgoal
	jnc	yeardone	;year over flowed
	lda	years
	cmc			;daa screws up if carry is set
	inr	a		;add one to the year count
	daa			;make bcd
	sta	years		;save years
	jz	done		;exact match
	shld	dayscnt		;year was ok to add
	lxi	h,years$bin
	inr	m
	jr	yearloop
;	
yeardone:
;
;	see if this is a leap year and adjust Feb if required
;
	call	ckleap
	lxi	h,feb		;point to feburary
	mvi	m,28
	jrnz	not$leap	;no, don't adjust feb
	inr	m		;make 29
not$leap:
	lxi	d,montbl	;point to month table
monthloop:
	lhld	dayscnt
	ldax	d		;get month
	mov	c,a
	mvi	b,0
	dad	b
	call	ckgoal
	jnc	monthdone
	push	psw
	cmc
	lda	months
	inr	a		;count to next month
	daa
	sta	months
	pop	psw
	jrz	done		;exact (on the first of the month)
	shld	dayscnt
	inx	d		;point to next month
	jr	monthloop
monthdone:
;
;	continue counting until day of month attained
;
	lhld	dayscnt
daysloop:
	inx	h		;add one day
	call	ckgoal
	jnc	done
	cmc			;zero carry bit so daa is screwed up
	lda	days
	inr	a
	daa
	sta	days
	jr	daysloop
done:	ret			;leave module

	
;
;	compare count (in hl) with goal
;	C if count < goal
;	Z if count = goal
;
ckgoal:	push	d
	lded	goaldays
	mov	a,h
	cmp	d
	jrnz	ckend
	mov	a,l
	cmp	e
ckend:	pop	d
	ret
;
;	check for leap year
;	return z if leap year
;
ckleap:
	lda	years$bin	;get years
	ani	03h
	ret

montbl:
	db	31	;jan
feb:	db	28	;feb
	db	31	;mar
	db	30	;apr
	db	31	;may
	db	30	;jun
	db	31	;jul
	db	31	;aug
	db	30	;sep
	db	31	;oct
	db	30	;nov
	db	31	;dec
;	dseg
;
;	these values are the output of this module
;
days:	  ds	1	;counts days in month (bcd)
months:	  ds	1	;counts months in year (bcd)
years:    ds	1	;counts years (starts at 78) (bcd)
;
;	cnvdays ram area
;
years$bin: ds	1	;years in binary (for leap year calc)

dayscnt:  ds	2	;counts days
goaldays: ds	2	;days since 1/1/78

done$msg:
	db	'Version Update$'

nofile:	db	'File not found$'

novers:	db	'No '
verstx:	db	'vers: '
	db	'in$'

nodate:	db	'No '
datetx:	db	'date: '	
	db	'in$'

notime:	db	'No ' 
timetx:	db	'time: '	
	db	'in$'

crlf:	db	cr,lf,'$'


	if 	patched		;transient version uses def fcb
fcb:	db	0
	rept	35
	db	0
	endm
	endif

option:	ds	1		;saved option byte
buff:	rept	recsiz		;fill with blanks so password
	db	' '		;is set in blanks
	endm

prtfil:	db	' File:- '
prtnam:	db	'           ',cr,lf,lf,'$'
datepb: ds	5		;date parameter block
 
bad$ver$mess:
	db	'Requires CP/M version 3', cr, lf, '$'
bad$opt$msg:
	db	'Bad timestamp option.',cr,lf
	db	'Options are:',cr,lf
	db	9,'[N] for no version update.',cr,lf
	db	9,'[M] to increment major version.',cr,lf
	if	patched
	db	9,'[Z] to not alter version or timestamping',cr,lf
	endif
	db	'$'
	if	patched
as$is$msg:
	db	'Version & time unchanged.',cr,lf,'$'
	endif
;
;	bdos functions  (cp/m 3.0)
;
bdos	equ	5
sysrst	equ	0	;warmstart
rdcon	equ	1	;wait for & read console character
wrcon	equ	2	;write to console
rdaux	equ	3	;wait for & read aux
wraux	equ	4	;write to aux
list	equ	5	;write to list device
drcon	equ	6	;direct console i/o character or
;					    ff=read
;					    fe=status only
;					    fd=wait for input	
auxinst	equ	7	;aux in status
auxotst	equ	8	;aux out status
prstr	equ	9	;de=string
rdbuff	equ	10	;de=buffer   max, count, c1,c2,... 
const	equ	11	;console in status
getvers	equ	12	;return version in HL
dskrst	equ	13	;reset disk system
seldsk	equ	14	;select disk 0=a:
openf	equ	15	;open file de=fcb
closef	equ	16	;close file
searchf	equ	17	;search for first occurance
searchn	equ	18	;find next occurance
delete	equ	19	;delete file
read	equ	20	;read sequential
write	equ	21	;write sequential
make	equ	22	;make file
rename	equ	23	;rename file new name at fcb+16
loginv	equ	24	;return login vector in hl
curdsk	equ	25	;return current disk in a
setdma	equ	26	;set new dma
getalv	equ	27	;get allocation vector address
wrtprt	equ	28	;write protect current disk
rovec	equ	29	;get r/o vector in hl
setflag	equ	30	;set file attributes
getdpb	equ	31	;return dpb address
user	equ	32	;get/set user ff=get
readran	equ	33	;read random record
writeran equ	34	;write random record
flsize	equ	35	;compute file size
setran	equ	36	;set random record from last sequential read
rstdrv	equ	37	;reset drive
accdrv	equ	38	;mpm only - access drive
freedrv equ	39	;mpm only - free drive
wrran0	equ	40	;fill a random record with 0
tstwrt	equ	41	;mpm only - test and write
lock	equ	42	;mpm only - lock record
unlock	equ	43	;mpm only - unlock record	
multi	equ	44	;set multi sector count
errmd	equ	45	;set bdos error mode
freesp	equ	46	;return free space in 1st 3 bytes of dma
chain	equ	47	;chain to pgm name in def buff (80h)
flush	equ	48	;flush buffers
scb	equ	49	;get set scb de=.scb pb   db offset
;						  db ffh set byte
;						     feh set word
;						     00h get
;					  	  dw value
drbios	equ	50	;direct bios call de=.bios pb  db bios func #
;						       db = A
;						       dw =BC
;						       dw =DE
;						       dw =HL
ldovl	equ	59	;load overlay
calrsx	equ	60	;call rsx  de=.rsx pb	db rsx func #
;						db # of word parameters
;						dw p1
;						dw p2
frblks	equ	98	;free temporary blocks
trunf	equ	99	;truncate file
setdirl	equ	100	;set driectory label
dirl	equ	101	;return directory label data e=drive
rdstamp	equ	102	;read file stamps & password mode
wrxfcb	equ	103
setdate	equ	104
rdtime	equ	105
defpwd	equ	106	;set def password
retsn	equ	107	;return serial number address
retcode	equ	108	;get/set return code
conmode	equ	109	;get/set console mode
delim	equ	110	;get/set string output delimiter
prtblk	equ	111	;print block  de = ccb	dw=address
;						dw=length
lstblk	equ	112	;list block
parse	equ	152	;parse file name   de=pfcb   dw=input addr
;						     dw=fcb addr
	end

length
lstblk	equ	112	;list block
parse	equ	152	;parse file name   de=pfcb   dw=input a
