Digital Research
This commit is contained in:
2020-11-06 18:50:37 +01:00
parent 621ed8ccaf
commit 31738079c4
8481 changed files with 1888323 additions and 0 deletions

View File

@@ -0,0 +1,786 @@
title 'console command processor (CCP), ver 2.0'
; assembly language version of the CP/M console command processor
;
; version 1.4
;
; Copyright (c) 1976, 1977, 1978
; Digital Research
; Box 579, Pacific Grove,
; California, 93950
;
false equ 0000h
true equ not false
testing equ false ;true if debugging
;
;
if testing
org 3400h
bdosl equ $+800h ;bdos location
else
org 000h
bdosl equ $+800h ;bdos location
endif
tran equ 100h
tranm equ $
ccploc equ $
;
; ********************************************************
; * Base of CCP contains the following code/data *
; * ccp: jmp ccpstart (start with command) *
; * jmp ccpclear (start, clear command) *
; * ccp+6 127 (max command length) *
; * ccp+7 comlen (command length = 00) *
; * ccp+8 ' ... ' (16 blanks) *
; ********************************************************
; * Normal entry is at ccp, where the command line given *
; * at ccp+8 is executed automatically (normally a null *
; * command with comlen = 00). An initializing program *
; * can be automatically loaded by storing the command *
; * at ccp+8, with the command length at ccp+7. In this *
; * case, the ccp executes the command before prompting *
; * the console for input. Note that the command is exe-*
; * cuted on both warm and cold starts. When the command*
; * line is initialized, a jump to "jmp ccpclear" dis- *
; * ables the automatic command execution. *
; ********************************************************
;
jmp ccpstart ;start ccp with possible initial command
jmp ccpclear ;clear the command buffer
maxlen: db 127 ;max buffer length
comlen: db 0 ;command length (filled in by dos)
; (command executed initially if comlen non zero)
combuf:
db ' ' ;8 character fill
db ' ' ;8 character fill
db 'COPYRIGHT (C) 1978, DIGITAL RESEARCH '; 38
ds 128-($-combuf)
; total buffer length is 128 characters
comaddr:dw combuf ;address of next to char to scan
staddr: ds 2 ;starting address of current fillfcb request
;
diska equ 0004h ;disk address for current disk
bdos equ 0005h ;primary bdos entry point
buff equ 0080h ;default buffer
fcb equ 005ch ;default file control block
;
rcharf equ 1 ;read character function
pcharf equ 2 ;print character function
pbuff equ 9 ;print buffer function
rbuff equ 10 ;read buffer function
breakf equ 11 ;break key function
liftf equ 12 ;lift head function (no operation)
initf equ 13 ;initialize bdos function
self equ 14 ;select disk function
openf equ 15 ;open file function
closef equ 16 ;close file function
searf equ 17 ;search for file function
searnf equ 18 ;search for next file function
delf equ 19 ;delete file function
dreadf equ 20 ;disk read function
dwritf equ 21 ;disk write function
makef equ 22 ;file make function
renf equ 23 ;rename file function
logf equ 24 ;return login vector
cself equ 25 ;return currently selected drive number
dmaf equ 26 ;set dma address
;
; special fcb flags
rofile equ 9 ;read only file
sysfile equ 10 ;system file flag
;
; special characters
cr equ 13 ;carriage return
lf equ 10 ;line feed
la equ 5fh ;left arrow
eofile equ 1ah ;end of file
;
; utility procedures
printchar:
mov e,a! mvi c,pcharf! jmp bdos
;
printbc:
;print character, but save b,c registers
push b! call printchar! pop b! ret
;
crlf:
mvi a,cr! call printchar
mvi a,lf! jmp printchar
;
;
print: ;print string starting at b,c until next 00 entry
push b! call crlf! pop h ;now print the string
prin0: mov a,m! ora a! rz ;stop on 00
inx h! push h ;ready for next
call printchar! pop h ;character printed
jmp prin0 ;for another character
;
initialize:
mvi c,initf! jmp bdos
;
select:
mov e,a! mvi c,self! jmp bdos
;
;
open: ;open the file given by d,e
mvi c, openf! call bdos! sta dcnt! inr a! ret
;
openc: ;open comfcb
xra a! sta comrec ;clear next record to read
lxi d,comfcb! jmp open
;
close: ;close the file given by d,e
mvi c,closef! call bdos! sta dcnt! inr a! ret
;
search: ;search for the file given by d,e
mvi c,searf! call bdos! sta dcnt! inr a! ret
;
searchn:
;search for the next occurrence of the file given by d,e
mvi c,searnf! call bdos! sta dcnt! inr a! ret
;
searchcom:
;search for comfcb file
lxi d,comfcb! jmp search
;
delete: ;delete the file given by d,e
mvi c,delf! jmp bdos
;
;
diskread:
;read the next record from the file given by d,e
mvi c,dreadf! call bdos! ora a! ret
;
diskreadc:
;read the comfcb file
lxi d,comfcb! jmp diskread
;
diskwrite:
;write the next record to the file given by d,e
mvi c,dwritf! call bdos! ora a! ret
;
make: ;create the file given by d,e
mvi c,makef! call bdos! sta dcnt! ret
;
renam: ;rename the file given by d,e
mvi c,renf! jmp bdos
;
;
translate:
;translate character in register A to upper case
cpi 61h! rc ;return if below lower case a
cpi 7bh! rnc ;return if above lower case z
ani 5fh! ret ;translated to upper case
;
readcom:
;read the next command into the command buffer
;check for submit file
lda submit! ora a! jz nosub
;scanning a submit file
;change drives to open and read the file
lda cdisk! ora a! mvi a,0! cnz select
;have to open again in case xsub present
lda subrc! dcr a ;read last record(s) first
sta subcr ;current record to read
lxi d,subfcb! call diskread ;end of file if last record
jnz nosub
;disk read is ok, transfer to combuf
lxi d,comlen! lxi h,buff! mvi b,128! call move0
;line is transferred, close the file with a
;deleted record
lxi h,subrc ; mvi m,0 ;clear fwflag
dcr m ;one less record
lxi d,subfcb! call close! jz nosub
;close went ok, return to original drive
lda cdisk! ora a! cnz select
;print to the 00
lxi h,combuf! call prin0
call break$key! jz noread
call del$sub! jmp ccp ;break key depressed
;
nosub: ;no submit file! call del$sub
;translate to upper case, store zero at end
mvi c,rbuff! lxi d,maxlen! call bdos
noread: ;enter here from submit file
;set the last character to zero for later scans
lxi h,comlen! mov b,m ;length is in b
readcom0: inx h! mov a,b! ora a ;end of scan?
jz readcom1! mov a,m ;get character and translate
call translate! mov m,a! dcr b! jmp readcom0
;
readcom1: ;end of scan, h,l address end of command
mov m,a ;store a zero
lxi h,combuf! shld comaddr ;ready to scan to zero
ret
;
break$key:
;check for a character ready at the console
mvi c,breakf! call bdos
ora a! rz
mvi c,rcharf! call bdos ;character cleared
ora a! ret
;
clifter:
mvi c,liftf! jmp bdos
;
cselect:
;get the currently selected drive number to reg-A
mvi c,cself! jmp bdos
;
cpyblock:
ldax d! mov m,a! inx d! inx h
dcr c! jnz cpyblock! ret
;
setdma:
;set dma address to d,e
mvi c,dmaf! jmp bdos
;
del$sub:
;delete the submit file, and set submit flag to false
lxi h,submit! mov a,m! ora a! rz ;return if no sub file
mvi m,0 ;submit flag is set to false
xra a! call select ;on drive a to erase file
lxi d,subfcb! call delete
lda cdisk! jmp select ;back to original drive
;
serialize:
;check serialization
lxi d,serial! lxi h,bdosl! mvi b,6 ;check six bytes
ser0: ldax d! cmp m! jnz badserial
inx d! inx h! dcr b! jnz ser0
ret ;serial number is ok
;
comerr:
;error in command string starting at position
;'staddr' and ending with first delimiter
call crlf ;space to next line
lhld staddr ;h,l address first to print
comerr0: ;print characters until blank or zero
mov a,m! cpi ' '! jz comerr1; not blank
ora a! jz comerr1; not zero, so print it
push h! call printchar! pop h! inx h
jmp comerr0; for another character
comerr1: ;print question mark,and delete sub file
mvi a,'?'! call printchar
call crlf! call del$sub
jmp ccp ;restart with next command
;
; fcb scan and fill subroutine (entry is at fillfcb below)
;fill the comfcb, indexed by A (0 or 16)
;subroutines
delim: ;look for a delimiter
ldax d! ora a! rz ;not the last element
cpi ' '! jc comerr ;non graphic
rz ;treat blank as delimiter
cpi '='! rz
cpi la! rz ;left arrow
cpi '.'! rz
cpi ':'! rz
cpi ';'! rz
cpi '<'! rz
cpi '>'! rz
ret ;delimiter not found
;
deblank: ;deblank the input line
ldax d! ora a! rz ;treat end of line as blank
cpi ' '! rnz! inx d! jmp deblank
;
addh: ;add a to h,l
add l! mov l,a! rnc
inr h! ret
;
fillfcb0:
;equivalent to fillfcb(0)
mvi a,0
;
fillfcb:
lxi h,comfcb! call addh! push h! push h ;fcb rescanned at end
xra a! sta sdisk ;clear selected disk (in case A:...)
lhld comaddr! xchg ;command address in d,e
call deblank ;to first non-blank character
xchg! shld staddr ;in case of errors
xchg! pop h ;d,e has command, h,l has fcb address
;look for preceding file name A: B: ...
ldax d! ora a! jz setcur0 ;use current disk if empty command
sbi 'A'-1! mov b,a ;disk name held in b if : follows
inx d! ldax d! cpi ':'! jz setdsk ;set disk name if :
;
setcur: ;set current disk
dcx d ;back to first character of command
setcur0:
lda cdisk! mov m,a! jmp setname
;
setdsk: ;set disk to name in register b
mov a,b! sta sdisk ;mark as disk selected
mov m,b! inx d ;past the :
;
setname: ;set the file name field
mvi b,8 ;file name length (max)
setnam0: call delim! jz padname ;not a delimiter
inx h! cpi '*'! jnz setnam1 ;must be ?'s
mvi m,'?'! jmp setnam2 ;to dec count
;
setnam1: mov m,a ;store character to fcb! inx d
setnam2: dcr b ;count down length! jnz setnam0
;
;end of name, truncate remainder
trname: call delim! jz setty ;set type field if delimiter
inx d! jmp trname
;
padname: inx h! mvi m,' '! dcr b! jnz padname
;
setty: ;set the type field
mvi b,3! cpi '.'! jnz padty ;skip the type field if no .
inx d ;past the ., to the file type field
setty0: ;set the field from the command buffer
call delim! jz padty! inx h! cpi '*'! jnz setty1
mvi m,'?' ;since * specified! jmp setty2
;
setty1: ;not a *, so copy to type field
mov m,a! inx d
setty2: ;decrement count and go again
dcr b! jnz setty0
;
;end of type field, truncate
trtyp: ;truncate type field
call delim! jz efill! inx d! jmp trtyp
;
padty: ;pad the type field with blanks
inx h! mvi m,' '! dcr b! jnz padty
;
efill: ;end of the filename/filetype fill, save command address
;fill the remaining fields for the fcb
mvi b,3
efill0: inx h! mvi m,0! dcr b! jnz efill0
xchg! shld comaddr ;set new starting point
;
;recover the start address of the fcb and count ?'s
pop h! lxi b,11 ;b=0, c=8+3
scnq: inx h! mov a,m! cpi '?'! jnz scnq0
;? found, count it in b! inr b
scnq0: dcr c! jnz scnq
;
;number of ?'s in c, move to a and return with flags set
mov a,b! ora a! ret
;
intvec:
;intrinsic function names (all are four characters)
db 'DIR '
db 'ERA '
db 'TYPE'
db 'SAVE'
db 'REN '
intlen equ ($-intvec)/4 ;intrinsic function length
serial: ;db 0,0,0,0,0,0
db 14h,0eh,0,0,7h,06dh
;
;
intrinsic:
;look for intrinsic functions (comfcb has been filled)
lxi h,intvec! mvi c,0 ;c counts intrinsics as scanned
intrin0: mov a,c! cpi intlen ;done with scan?! rnc
;no, more to scan
lxi d,comfcb+1 ;beginning of name
mvi b,4 ;length of match is in b
intrin1: ldax d! cmp m ;match?
jnz intrin2 ;skip if no match
inx d! inx h! dcr b
jnz intrin1 ;loop while matching
;
;complete match on name, check for blank in fcb
ldax d! cpi ' '! jnz intrin3 ;otherwise matched
mov a,c! ret ;with intrinsic number in a
;
intrin2: ;mismatch, move to end of intrinsic
inx h! dcr b! jnz intrin2
;
intrin3: ;try next intrinsic
inr c ;to next intrinsic number
jmp intrin0 ;for another round
intrin4:
ora a! jnz intrin5! lxi d,subfcb! call open
jz intrin5
mvi a, 0ffh! jmp intrin6
intrin5:
xra a
intrin6:
sta submit
ret
;
ccpclear:
;clear the command buffer
xra a
sta comlen
;drop through to start ccp
ccpstart:
;enter here from boot loader
lxi sp,stack! push b ;save initial disk number
;(high order 4bits=user code, low 4bits=disk#)
push b! call initialize! pop b
push psw! mov a,c! call select
pop psw! pop b
inr a! ora c
call intrin4 ;proper disk is selected, now check sub files
;check for initial command
lda comlen! ora a! jnz ccp0 ;assume typed already
;
ccp:
;enter here on each command or error condition
lxi sp,stack
call crlf ;print d> prompt, where d is disk name
call cselect ;get current disk number
adi 'A'! call printchar
mvi a,'>'! call printchar
call readcom ;command buffer filled
ccp0: ;(enter here from initialization with command full)
lxi d,buff! call setdma ;default dma address at buff
call cselect! sta cdisk ;current disk number saved
call fillfcb0 ;command fcb filled
cnz comerr ;the name cannot be an ambiguous reference
lda sdisk! ora a! jnz userfunc
;check for an intrinsic function
call intrinsic
lxi h,jmptab ;index is in the accumulator
mov e,a! mvi d,0! dad d! dad d ;index in d,e
mov a,m! inx h! mov h,m! mov l,a! pchl
;pc changes to the proper intrinsic or user function
jmptab:
dw direct ;directory search
dw erase ;file erase
dw type ;type file
dw save ;save memory image
dw rename ;file rename
dw userfunc;user-defined function
badserial:
lxi h,di or (hlt shl 8)
shld ccploc! lxi h,ccploc! pchl
;
;
;utility subroutines for intrinsic handlers
readerr:
;print the read error message
lxi b,rdmsg! jmp print
rdmsg: db 'READ ERROR',0
;
nofile:
;print no file message
lxi b,nofmsg! jmp print
nofmsg: db 'NOT FOUND',0
;
getnumber: ;read a number from the command line
;convert the byte value in comfcb to binary
lxi h,comfcb+1! lxi b,11 ;(b=0, c=11)
;value accumulated in b, c counts name length to zero
conv0: mov a,m! cpi ' '! jz conv1
;more to scan, convert char to binary and add
inx h! sui '0'! cpi 10! jnc comerr ;valid?
mov d,a ;save value! mov a,b ;mult by 10
ani 1110$0000b! jnz comerr
mov a,b ;recover value
rlc! rlc! rlc ;*8
add b! jc comerr
add b! jc comerr ;*8+*2 = *10
add d! jc comerr ;+digit
mov b,a! dcr c! jnz conv0 ;for another digit
ret
conv1: ;end of digits, check for all blanks
mov a,m! cpi ' '! jnz comerr ;blanks?
inx h! dcr c! jnz conv1
mov a,b ;recover value! ret
;
movename:
;move 3 characters from h,l to d,e addresses
mvi b,3
move0: mov a,m! stax d! inx h! inx d
dcr b! jnz move0
ret
;
addhcf: ;buff + a + c to h,l followed by fetch
lxi h,buff! add c! call addh! mov a,m! ret
;
setdisk:
;change disks for this command, if requested
xra a! sta comfcb ;clear disk name from fcb
setdisk1:
lda sdisk! ora a! rz ;no action if not specified
dcr a! lxi h,cdisk! cmp m! rz ;already selected
jmp select
;
resetdisk:
;return to original disk after command
lda sdisk! ora a! rz ;no action if not selected
dcr a! lxi h,cdisk! cmp m! rz ;same disk
lda cdisk! jmp select
;
;individual intrinsics follow
direct:
;directory search
call fillfcb0 ;comfcb gets file name
call setdisk ;change disk drives if requested
lxi h,comfcb+1! mov a,m ;may be empty request
cpi ' '! jnz dir1 ;skip fill of ??? if not blank
;set comfcb to all ??? for current disk
mvi b,11 ;length of fill ????????.???
dir0: mvi m,'?'! inx h! dcr b! jnz dir0
;not a blank request, must be in comfcb
dir1:
call searchcom ;first one has been found
cz nofile ;not found message
dir2: jz endir
;found, but may be system file
call crlf! call cselect
;current disk in A
adi 'A'! call printchar
mvi a,':'! call printchar
mvi a,' '! call printchar
dirhdr1:
lda dcnt! rrc! rrc! rrc! ani 60h! mov c,a
;compute position of name in buffer
mvi b,1 ;start with first character of name
dir3: mov a,b! call addhcf ;buff+a+c fetched
cpi ' '! jnz dir4 ;check for blank type
mvi a,9! call addhcf ;first char of type
mvi a,' '! cmp m! jz dir5
dir4:
call printbc ;char printed
inr b! mov a,b! cpi 12! jnc dir5
;check for break between names
cpi 9! jnz dir3 ;for another char
;print a blank between names
mvi a,' '! call printbc! jmp dir3
;
dir5: ;end of current entry
dir6: call break$key ;check for interrupt at keyboard
jnz endir ;abort directory search
call searchn! jmp dir2 ;for another entry
endir: ;end of directory scan
jmp retcom
;
;
erase:
call fillfcb0 ;cannot be all ???'s
cpi 11
jnz getout
;erasing all of the disk
lxi b,ermsg! call print!
call readcom
lxi h,comlen! dcr m! jnz ccp ;bad input
inx h! mov a,m! cpi 'Y'! jnz ccp
;ok, erase the entire diskette
inx h! shld comaddr ;otherwise error at retcom
erasefile:
lxi h, comfcb! mvi m,'?'
call setdisk1
lxi d,comfcb! call delete
call initialize! jmp retcom
getout:
call setdisk
lxi d, comfcb! call delete
jmp retcom
;
ermsg: db 'ALL FILES (Y/N)?',0
;
type:
call fillfcb0! jnz comerr ;don't allow ?'s in file name
call setdisk! call openc ;open the file
jz typerr ;zero flag indicates not found
;file opened, read 'til eof
call crlf! lxi h,bptr! mvi m,255 ;read first buffer
type0: ;loop on bptr
lxi h,bptr! mov a,m! cpi 128 ;end buffer
jc type1! push h ;carry if 0,1,...,127
;read another buffer full
call diskreadc! pop h ;recover address of bptr
jnz typeof ;hard end of file
xra a! mov m,a ;bptr = 0
type1: ;read character at bptr and print
inr m ;bptr = bptr + 1
lxi h,buff! call addh ;h,l addresses char
mov a,m! cpi eofile! jz retcom
call printchar
call break$key! jnz retcom ;abort if break
jmp type0 ;for another character
;
typeof: ;end of file, check for errors
dcr a! jz retcom
call readerr
typerr: call resetdisk! jmp comerr
;
save:
call fillfcb0! lda sdisk! ora a! jnz comerr
call getnumber; value to register a
push psw ;save it for later
;
;should be followed by a file to save the memory image
call fillfcb0
jnz comerr ;cannot be ambiguous
call setdisk ;may be a disk change
lxi d,comfcb! push d! call delete ;existing file removed
pop d! call make ;create a new file on disk
call openc! jz save1
pop psw ;#pages to write is in a, change to #sectors
mov l,a! mvi h,0! dad h!
lxi d,tran ;h,l is sector count, d,e is load address
save0: ;check for sector count zero
mov a,h! ora l! jz save2 ;may be completed
dcx h ;sector count = sector count - 1
push h ;save it for next time around
lxi h,128! dad d! push h ;next dma address saved
call setdma ;current dma address set
lxi d,comfcb! call diskwrite
pop d! pop h ;dma address, sector count
jnz save1 ;may be disk full case
jmp save0 ;for another sector
;
save1: ;end of dump, close the file
lxi b, fullmsg! call print
save2:
lxi d,comfcb! call close
jnz retsave ;for another command
saverr: ;must be full or read only disk
lxi b,closemsg! call print
retsave:
jmp retcom
fullmsg: db 'NO SPACE',0
;
closemsg: db 'CANNOT CLOSE',0
;
rename:
;rename a file on a specific disk
call fillfcb0! jnz comerr ;must be unambiguous
lda sdisk! push psw ;save for later compare
call setdisk ;disk selected
call searchcom ;is new name already there?
jnz renerr3
;file doesn't exist, move to second half of fcb
lxi h,comfcb! lxi d,comfcb+16! mvi b,16! call move0
;check for = or left arrow
lhld comaddr! xchg! call deblank
cpi '='! jz ren1 ;ok if =
cpi la! jnz renerr2
ren1: xchg! inx h! shld comaddr ;past delimiter
;proper delimiter found
call fillfcb0! jnz renerr2
;check for drive conflict
pop psw! mov b,a ;previous drive number
lxi h,sdisk! mov a,m! ora a! jz ren2
;drive name was specified. same one?
cmp b! mov m,b! jnz renerr2
ren2: mov m,b ;store the name in case drives switched
xra a! sta comfcb! call searchcom ;is old file there?
jz renerr1
;
;everything is ok, rename the file
lxi d,comfcb! call renam
jmp retcom
;
renerr1:; no file on disk
call nofile! jmp retcom
renerr2:; ambigous reference/name conflict
call resetdisk! jmp comerr
renerr3:; file already exists
lxi b,renmsg! call print! jmp retcom
renmsg: db 'FILE EXISTS',0
;
;
userfunc:
call serialize ;check serialization
;load user function and set up for execution
lda comfcb+1! cpi ' '! jnz user0
;no file name, but may be disk switch
lda sdisk! ora a! jz endcom ;no disk name if 0
dcr a! sta cdisk! ;call setdiska ;set user/disk
sta diska
call select! jmp endcom
user0: ;file name is present
lxi d,comfcb+9! ldax d! cpi ' '! jnz comerr ;type ' '
push d! call setdisk! pop d! lxi h,comtype ;.com
call movename ;file type is set to .com
call openc! jz userer
;file opened properly, read it into memory
lxi h,tran ;transient program base
load0: push h ;save dma address
xchg! call setdma
lxi d,comfcb! call diskread! jnz load1
;sector loaded, set new dma address and compare
pop h! lxi d,128! dad d
lxi d,tranm ;has the load overflowed?
mov a,l! sub e! mov a,h! sbb d! jnc loaderr
jmp load0 ;for another sector
;
load1: pop h! dcr a! jnz loaderr ;end file is 1
call resetdisk ;back to original disk
call fillfcb0! lxi h,sdisk! push h
mov a,m! sta comfcb ;drive number set
mvi a,16! call fillfcb ;move entire fcb to memory
pop h! mov a,m! sta comfcb+16
xra a! sta comrec ;record number set to zero
lxi d,fcb! lxi h,comfcb! mvi b,33! call move0
;move command line to buff
lxi h,combuf
bmove0: mov a,m! ora a! jz bmove1! cpi ' '! jz bmove1
inx h! jmp bmove0 ;for another scan
;first blank position found
bmove1: mvi b,0! lxi d,buff+1! ;ready for the move
bmove2: mov a,m! stax d! ora a! jz bmove3
;more to move
inr b! inx h! inx d! jmp bmove2
bmove3: ;b has character count
mov a,b! sta buff
call crlf
;now go to the loaded program
lda cdisk! sta diska! call clifter! lxi d, buff
call setdma ;user code saved
;low memory diska contains user code
call tran ;gone to the loaded program
lxi sp,stack ;may come back here
lda cdisk! call select
jmp ccp
;
userer: ;arrive here on command error
call resetdisk! jmp comerr
;
loaderr:;cannot load the program
lxi b,loadmsg! call print
jmp retcom
loadmsg: db 'LOAD ERROR',0
comtype: db 'COM' ;for com files
;
;
retcom: ;reset disk before end of command check
call resetdisk
;
endcom: ;end of intrinsic command
call fillfcb0 ;to check for garbage at end of line
lda comfcb+1! sui ' '! lxi h,sdisk! ora m
;0 in accumulator if no disk selected, and blank fcb
jnz comerr
jmp ccp
;
;
;
; data areas
ds 16 ;8 level stack
stack:
;
; 'submit' file control block
submit: db 0 ;00 if no submit file, ff if submitting
subfcb: db 0,'$$$ ' ;file name is $$$
db 'SUB',0,0 ;file type is sub
submod: db 0 ;module number
subrc: ds 1 ;record count filed
ds 16 ;disk map
subcr: ds 1 ;current record to read
;
; command file control block
comfcb: ds 32 ;fields filled in later
comrec: ds 1 ;current record to read/write
dcnt: ds 1 ;disk directory count (used for error codes)
cdisk: ds 1 ;current disk
sdisk: ds 1 ;selected disk for current operation
;none=0, a=1, b=2 ...
bptr: ds 1 ;buffer pointer
ds 0eh
ds 38
end ccploc

Binary file not shown.

View File

@@ -0,0 +1 @@
Make CPM 1.4 v 0.1: by Ernie Price. Make CPM 1.4 is a howto on reconstucting the CPM Version 1.4 CCP and BDOS from recovered and regenerated sources.

View File

@@ -0,0 +1,5 @@
rcx
800
nccp.bin
wa00
q

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,253 @@
/* #include <conio.h> */
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct fcb // also directory entry
{
unsigned char user; // not used in CP/M 1.4
unsigned char name[8]; // file name
unsigned char type[3]; // file extension
unsigned char ext; // extent number
unsigned char notused[2];
unsigned char rc; // record count in this extent
unsigned char dm[16]; // single byte extent numbers
};
#define DIR_ENT_SZ 32 // size of one directory entry
#define SECT_SZ 128 // disk sector size
#define SPB 8 // sectors per block
#define DIR_BLOCKS 2 // 2 blocks reserved for directory
#define SPT 26 // sectors per track
#define RESERVED_TRACKS 2 // for cold boot, ccp and cpm
#define DIR_SECTORS (SPB * DIR_BLOCKS) // sectors for directory
#define DIR_LOCN (SPT * RESERVED_TRACKS) // offset of directory
#define DIR_ENT_PER_SEC (SECT_SZ / DIR_ENT_SZ) // directory entries per sector
unsigned char buff1[SPT * SECT_SZ]; // for de-interleaving
unsigned char buff2[SPT * SECT_SZ]; // "
unsigned char fsec [SECT_SZ];
char fname [20]; // file name for current extent
char fname2[20]; // currently open output file name, if any
FILE *in, // raw image file
*out; // extracted CP/M file
unsigned char exm[1024]; // map of processed blocks
// sector interleave table
unsigned char sectran[] = {
"\x01\x07\x0d\x13\x19\x05\x0b\x11"
"\x17\x03\x09\x0f\x15\x02\x08\x0e"
"\x14\x1a\x06\x0c\x12\x18\x04\x0a"
"\x10\x16\x00\x00\x00\x00\x00\x00"};
char temp[] = "junkimg.jnk"; // name of de-interleaved raw image file
/**************************************************
Read the input image file in gulps of 26 sectors, reorder using the
interleace table, and save the to the de-interleaved file
*/
void RemoveInterleave
(
char *fn1, // ram image file name
char * fn2, // reordered image file name
int flag // not used
)
{
int x;
in = fopen(fn1, "rb");
out = fopen(fn2, "wb");
while (fread(buff1, 1, sizeof(buff1), in)) // read a 'track'
{
for (x = 0; x < SPT; x++) // shuffle
{
memcpy(&buff2[x * SECT_SZ], &buff1[(sectran[x] - 1) * SECT_SZ], SECT_SZ);
}
fwrite(buff2, 1, sizeof(buff2), out);
}
fcloseall();
}
/**************************************************
Copy a file name or extension until a space character is found
or limit is reached.
*/
void copyuntil
(
char *dst, // destination for copy
char *src, // source for copy
int count // max copy length
)
{
while (*src != ' ' && count--)
{
*dst++ = *src++;
}
*dst = 0;
}
/**************************************************
Convert an 8,3 filename into name.ext format
*/
void MakeFilename
(
char *fname, // destination for file name
struct fcb *tfcb // pointer to fcb (directory entry)
)
{
copyuntil(fname, tfcb->name, 8); // copy name part
if (tfcb->type[0] != ' ') // if extension is non-blank
{
strcat(fname, "."); // add a dot
copyuntil(fname + strlen(fname), tfcb->type, 3);
}
}
/**************************************************
Read a logical CP/M sector from the image file. Sector is a logical
number based at the directory root. The reserved sectors(tracks)
are added.
*/
void ReadSector
(
unsigned char *where, // destination for read
int sectorno // logical sector number
)
{
fseek(in, (long)(DIR_LOCN + sectorno) * (long)SECT_SZ, SEEK_SET);
fread(where, 1, SECT_SZ, in);
}
/**************************************************
Construct the file name and copy all records in the specified extent into the
output file. If the file name differs from the previously processed file name
close the previous file and open a new one with the current name.
Since extents are psorecces in sorted order, including the extent number, this
results in recovered files.
*/
void CopyExtentToFile
(
struct fcb *tfcb, // pointer to fcb (directory entry)
int deleted // flag indicating a deleted extent
)
{
int x, // general counter
thisSect, // computed logical sector number
said; // flag for printing
static int extent; // logical extent number
MakeFilename(fname, tfcb); // extract filename
if (strcmp(fname2, fname)) // is it different from before?
{
if (out != NULL)
{
fclose(out);
}
out = fopen(fname, "wb"); // create new output file
strcpy(fname2, fname);
printf("Extracting %sfile %s\n", deleted ? "deleted " : "", fname);
extent = -1;
}
extent++;
//printf("%15s", fname);
for (x = 0; x < tfcb->rc; x++) // fetch record count sectors and save
{
if (!(x % SPB))
{
//printf(" %02x", tfcb->dm[x / SPB]);
}
if ((exm[tfcb->dm[x / SPB]] != 0) && !said)
{
said = 1;
printf(" extent %d has blocks used elsewhere\n");
}
exm[tfcb->dm[x / SPB]] = 1; // mark in block map
// convert logical sector to physical
thisSect = (tfcb->dm[x / SPB] * SPB) + (x % SPB);
ReadSector(fsec, thisSect); // load the file sector
fwrite(fsec, 1, SECT_SZ, out); // store in new file
}
//printf("\n");
}
/**************************************************
Compare routine for qsort of directory
*/
int compare
(
void *a, // pointer to key field 'a'
void *b // pointer to key field 'b'
)
{
return memcmp(a, b, 16); // compare keys
}
/**************************************************
*/
main
(
int argc, // count of command line arguments
char **argv // pointers to arguments
)
{
int x;
struct fcb *tfcb = (struct fcb *)buff1;
//printf("Dir locn %x\n", DIR_LOCN * SECT_SZ);
if (argc < 2)
{
printf("File name required for compare\n\n");
printf("Correct usage is:\n");
printf(" extract <filename>\n\n");
printf(" where <filename> is a diskette image file\n");
exit(1);
}
RemoveInterleave(argv[1], temp, 0);
in = fopen(temp, "rb");
// load the directory into memory
for (x = 0; x < DIR_SECTORS; x++)
{
ReadSector(buff1 + (x * SECT_SZ), x);
}
// sort the directory in [user], name and extent order
qsort(buff1, DIR_SECTORS, DIR_ENT_SZ, compare);
// process the sorted list
for (x = 0; x < (DIR_SECTORS * DIR_ENT_PER_SEC); x++)
{
if ((tfcb->name[0] != 0xe5) && (tfcb->name != 0))
{
CopyExtentToFile(tfcb, tfcb->user == 0xe5);
}
tfcb++;
}
fcloseall();
remove(temp);
}

View File

@@ -0,0 +1,3 @@
@echo off
rem extract the BDOS image from cpm.com
call ..\tools\getcpm ..\binaries\cpm.com bdos.bin 1200 d00

View File

@@ -0,0 +1,4 @@
@echo off
rem extract the CCP image from cpm.com
call ..\tools\getcpm ..\binaries\cpm.com ccp.bin a00 800

View File

@@ -0,0 +1,10 @@
@echo off
rem #1 = inputfn, #2 outputfn #3 = addr, #4 = lth
rem build a script for debug
echo rcx>dbg.bat
echo %4>>dbg.bat
echo n%2>>dbg.bat
echo w%3>>dbg.bat
echo q>>dbg.bat
call debug %1 <dbg.bat

View File

@@ -0,0 +1,176 @@
/*
Intel hex to binary converter Converts the specified file to binary
*/
#include <stdio.h>
#include <conio.h>
#include <assert.h>
typedef struct image_t
{
unsigned lowaddress, highaddress;
} image_t;
//void LoadHexFile(char *fn, unsigned char *where, image_t *im);
#define LINE_SIZE 500 // max size of input hex line
#define MAX_IMG_SZ 0x4000
// Intel hex-file record types
#define RT_DATA 0
#define RT_EOF 1
static int hexsum;
static unsigned char *lyne;
/******************************************************************************
Get a hexadecimal nybble from the input stream. Increment the pointer.
*/
static int getnib(char **bt)
{
int x = **bt - '0';
if (x > 9)
{
x -= 7;
}
(*bt)++;
return x;
}
/******************************************************************************
Get a single unsigned byte (2 nibbles) from the input stream. Increment the
pointer and add the fetched byte to the line checksum
*/
static unsigned long gethex(char **bt)
{
int x;
x = getnib(bt) << 4;
x |= getnib(bt);
hexsum += x;
return x;
}
/******************************************************************************
Get an unsigned short from the input stream, The pointer and the line checksum
are maintained.
*/
static unsigned long getshort(char **bt)
{
unsigned short ret;
ret = gethex(bt) << 8;
ret |= gethex(bt);
return ret;
}
/******************************************************************************
Load the specified Intel format hex file into memory as a binary
image. Returns the file loaded in 'prog' with the base and limit filled in
as im->lowaddress and im->highaddress respectively.
*/
void LoadHexFile(char *fn, unsigned char *prog, image_t *im, unsigned limit)
{
char *p1; // pointer to hex input text
FILE *ff = fopen(fn, "rb");
int lth; // line length byte
unsigned address, // current line address
offset; // 0 offset of first byte
int rt; // record type
unsigned char kh; // fetched byte from hex file
printf("Converting %s\n", fn);
lyne = (unsigned char*)malloc(LINE_SIZE);
im->lowaddress = 0xffff;
im->highaddress = 0;
while (fgets(p1 = lyne, LINE_SIZE, ff))
{
if (*p1++ != ':') // all good Intel hex file have this
{
break;
}
hexsum = 0;
lth = gethex(&p1); // fetch length of line
address = getshort(&p1); // fetch target address
if ((rt = gethex(&p1)) == RT_EOF) // fetch record type
{
break; // end of file
}
switch(rt)
{
default:
printf("Invalid record type %02x\n", rt);
getch();
break;
case RT_DATA: // data bytes
if (address < im->lowaddress)
{
assert(im->highaddress == 0);
im->lowaddress = address;
offset = address;
}
while (lth--)
{
kh = (unsigned char)gethex(&p1);
if ((address - offset) > limit)
{
printf("Hex file too large\n");
getch();
exit(0);
}
*(prog + address++ - offset) = kh;
}
if (address > im->highaddress)
{
im->highaddress = address;
}
break;
}
kh = (unsigned char)gethex(&p1);
if (hexsum & 0xff)
{
printf("Hex conversion error\n");
getch();
exit(0);
}
}
free(lyne);
fclose(ff);
}
/******************************************************************************
Convert the specified Intel format hex file <argv[1]> into memory as a binary
image. Saves the binary image as <argv[2]>.
*/
void main(int argc, char **argv)
{
image_t img;
FILE *out;
char *where = (char*)malloc(MAX_IMG_SZ);
memset(where, 0, MAX_IMG_SZ);
LoadHexFile(argv[1], where, &img, MAX_IMG_SZ);
out = fopen(argv[2], "wb");
fwrite(where, 1, img.highaddress - img.lowaddress, out);
fclose(out);
}

View File

@@ -0,0 +1,17 @@
set :F0:=..\isis\plm80
set :F1:=..\isis\utils
set :F2:=.
set :F3:=..\isis\asm80
..\isis\utils\isis :F0:plm80 :F2:bdos.plm
..\isis\utils\isis :F3:asm80 :F2:bdosi.src
..\isis\utils\isis :F1:link :F2:bdosi.obj,:F2:bdos.obj,:F1:plm80.lib to :F2:bdos.mod
del bdosi.obj
del bdos.obj
..\isis\utils\isis :F1:locate :F2:bdos.mod code(0800h) stacksize(0)
del bdos.mod
..\isis\utils\isis :F1:objhex :F2:bdos to :F2:bdos.hex
del bdos
..\tools\hexcon bdos.hex bdos.abs
del bdos.hex

View File

@@ -0,0 +1,7 @@
@echo off
set savepath=%path%
set path=..\tools2;%path%
rmac os2ccp
link os2ccp[l0]
set path=%savepath%
set savepath=