home *** CD-ROM | disk | FTP | other *** search
- ; I/O-CAP.ASM Version 1.0 as of September 13, 1981
- ;
- ; By: Kelly Smith, CP/M-Net
- ;
- ; Version 1.0: Initial release by Kelly Smith
- ;
- ; Note: Please append any changes to I/O-CAP with a
- ; 'Version Log' and change comments (your name would be nice
- ; too) top-down from the initial Version 1.0 release.
- ;
- ; Running I/O-CAP (the first time) will relocate the
- ; CONIN, and CONOUT jump vectors to high memory and then ALL
- ; subsequent input or output (depending on conditional
- ; assembly switches) will be buffered (16 sectors) for
- ; eventual output to disk with a filename and type called
- ; USER.LOG. This file will then be updated as long as I/O-CAP
- ; is active in the system. Note: I/O-CAP may be made
- ; inactive by just type I/O-CAP again, to toggle it off (or
- ; on).
- ;
- ; I/O-CAP requires an unusually LARGE amount of
- ; relocation memory, because it must buffer the entire CP/M
- ; system image to high memory to save and restore all CCP and
- ; BDOS pointers as it captures each 16 sector block of console
- ; input or output...crude, but the ONLY other wait to do this
- ; would be to 'hot-patch' the CCP and BDOS 'on-the-fly' and
- ; that get's just a bit gruesome from a UNIVERSAL
- ; applications standpoint...It's much easier to just SYSGEN a
- ; smaller CP/M system than your actual maximum available
- ; memory, and then have I/O-CAP relocate itself above your
- ; BIOS. If anyone has a better way to do this WITHOUT this
- ; relocation crap, I would be eager to see it...I just got
- ; frustrated with trying to figure out what CP/M was doing
- ; internally to pursue it further...local stacks, pointers,
- ; etc.,...ARGH...so I took the easy way out.
- ;
- ; Thanks to Mike Karas for discovering the BDOS CALL
- ; contention problems when the BDOS stack was not saved and
- ; restored when saving the console buffer to disk.
- ;
- ; - Applications -
- ;
- ; (1) Don't have a printer, and want 'Hard Copy' of a user
- ; dialog with the system? Use I/O-CAP to creat it as a disk
- ; file...for instance, when making patches into 'uncertain'
- ; areas of the system with DDT (or SID, or whatever) you can
- ; keep a running history on disk of the patches AND their
- ; effects.
- ;
- ; (2) Need to show example of console dialog for an article
- ; or book and you don't want to 'hand type' it in? Use I/O-CAP
- ; to creat the examples for you, and edit the dialog to suit
- ; your needs.
- ;
- ; (3) Need to know all the events in 'history form' leading
- ; to some bizarre system blow-up? Use I/O-CAP to record that
- ; history for you (but only if the blow-up is recoverable by
- ; NOT COLD BOOTING the system).
- ;
- ; (4) Want to (secretly) monitor the activities of other
- ; users of the system? Use I/O-CAP to record user console
- ; input, and check in from time-to-time to look at the system
- ; activity. Used in conjunction with BYE on a remote CP/M
- ; system, you can finally figure-out how that 'twit' clobbers
- ; your system from 3000 miles away...and NOT fill a room full
- ; of paper by logging all input to your printer. Note: Run
- ; I/O-CAP first, then BYE.COM to 'grab' the vectors set-up by
- ; the I/O-CAP program.The capture of incoming data will appear
- ; to be transparent to the user, with a slight pause when it
- ; updates the USER.LOG file...but this only happens every 2048
- ; character entrys, so it should generally go un-noticed.
- ;
- ; - Using I/O-CAP -
- ;
- ; Examine the various conditional assembly switches and
- ; set TRUE or FALSE depending on your requirements with your
- ; editor (ED.COM). Then assemble with ASM.COM (or MAC.COM),
- ; load it to creat a .COM file and then run. The conditional
- ; assembly switches allow the following options:
- ;
- ; (1) DEBUG - I/O-CAP runs at 8000 Hex...about right for
- ; most small applications programs that use memory from 100 to
- ; 7FFF Hex in a 56K CP/M system. If FALSE, I/O-CAP runs above
- ; a 48K CP/M system (C800 Hex), with no restrictions on
- ; applications programs.
- ;
- ; (2) QUIET - If FALSE, rings the console bell just before it
- ; writes 2048 bytes of captured console INPUT or OUTPUT.
- ;
- ; (3) ERRDISP - If TRUE, I/O-CAP will display an 'OOPS...'
- ; message on the console if the disk or directory is full.
- ;
- ; (4) INPUT - If TRUE, only console keyboard INPUT is
- ; captured. Note: OUTPUT must be FALSE if INPUT is TRUE.
- ;
- ; (5) OUTPUT - If TRUE, both console keyboard INPUT and
- ; OUTPUT will be captured...uses 'gobs' of disk storage if you
- ; let I/O-CAP run for any length of time. Note: INPUT must be
- ; FALSE if OUTPUT is TRUE.
- ;
- ; (6) QUIT - If TRUE, when a Control-Z and Carriage Return are
- ; entered at the console keyboard, I/O-CAP will append the
- ; USER.LOG file with a physical end-of-file (i.e., no further
- ; data will be displayed in USER.LOG although it may be
- ; physically appended to it)...Note: You must type I/O-CAP<cr>
- ; to CLOSE the current USER.LOG, and reset the disk to normal
- ; R/W status. Failure to do so will result in a R/O BDOS
- ; Error on any subsequent attempt to write to the disk by
- ; means other than I/O-CAP.
- ;
- ; (7) SYSLOG - If TRUE, creates USER.LOG as a $SYS (invisible
- ; to directory) file, so that 'secrecy' is maintained when
- ; capturing user input...be sure and rename USER.LOG to your
- ; 'private' name, or replace the TYPE command with MLIST.COM.
- ;
- ; Please send any changes, 'bug' reports, suggestions,
- ; comments, gripes or bitches to the CP/M-Net system, (805)
- ; 527-9321...have fun with this program. It's in the public
- ; domain, but NOT TO BE USED for COMMERCIAL BENEFIT.
- ;
- ; Best regards,
- ;
- ; Kelly Smith, CP/M-Net
- ;
- ;
- ;
- ; define TRUE/FALSE assembly parameters
- ;
- true equ -1 ; define TRUE
- false equ not true; define FALSE
- debug equ true ; define DEBUG
- quiet equ false ; define QUIET (ring BELL, if not true)
- errdisp equ true ; define ERRDISP (display errors, if true)
- quit equ true ; define QUIT (EOF, if Control-Z found)
- syslog equ false ; define SYSLOG (make USER.LOG a $SYS file)
- ;
- ; >>> Note: only one of the following two assembly switches may be true <<<
- ;
- input equ false ; define INPUT (I/O-CAP console input)
- output equ true ; define OUTPUT (I/O-CAP console output)
- ;
- if DEBUG
- dest equ 08000h ; running location of code
- endif ; DEBUG
-
- if not DEBUG
- dest equ 0c800h ; running location of code
- endif ; DEBUG
- ;
- ; BDOS entry point and function codes
- ;
- base equ 0 ; <<-- set to offset of CP/M for your
- ; system, standard systems are 0, some
- ; 'alternate' systems are 4200H
- ;
- bdos equ base+5
- resdsk equ 13 ; reset disk system
- offc equ 15 ; open file
- cffc equ 16 ; close file
- dffc equ 19 ; delete file
- rrfc equ 20 ; read record
- wrfc equ 21 ; write record
- mffc equ 22 ; make file
- sdma equ 26 ; set dma address
- ;
- ; secondary FCB field definitions
- ;
- fn equ 1 ; file name field (rel)
- ft equ 9 ; file type field (rel)
- ex equ 12 ; file extent field (rel)
- frc equ 15 ; file record count (rel)
- nr equ 32 ; next record field (rel)
- ;
- ; ASCII control characters
- ;
- cr equ 0dh ; carriage return
- lf equ 0ah ; line feed
- bel equ 07h ; bell signal
- ;
- ; This program runs up in high ram. It gets there, by being
- ; moved there when 'I/O-CAP' is typed. Change the following
- ; equate to an area in your high memory where this program may
- ; patch itself in. Approximate memory requirements: 2k bytes
- ; or more, depending upon the options selected. a marker has
- ; been placed at the end to deliberately print an error
- ; message during assembly in order to determine the actual
- ; ending address of the program. The error message will not
- ; affect the assembly. make sure you have memory available up
- ; to the address shown.
- ;
- org base+100h
- ;
- ; Move 'I/O-CAP' program up to high ram and jump to it
- ;
- lxi h,0 ; save old stack pointer
- dad sp
- shld oldstk
- lxi sp,stack; make a new stack pointer
- lxi h,tbuf ; set pointer to tbuf
- shld ptr
- lxi h,0 ; set size = 0
- shld size
- lhld base+1 ; get BIOS pointer
- lxi d,5 ; add bias to console status address
- dad d
- mov d,m ; save in [d]
- lhld newjtbl+1 ; see if vector addresses active
- mov a,h ; been patched by previous execution?
- cmp d
- jz unpatch ; un-patch, if so
- lxi b,pend-start+1 ; number of bytes to move
- lxi h,dest+pend-start+1 ; end of moved code
- lxi d,source+pend-start ; end of source code
- ;
- mvlp ldax d ; get byte
- dcx h ; bump pointers
- mov m,a ; new home
- dcx d
- dcx b ; bump byte count
- mov a,b ; check if zero
- ora c
- jnz mvlp ; if not, do some more
- pchl ; do it, to it...
- ;
- source equ $ ; boundary memory marker
- ;
- offset equ dest-source ; relocation amount
- ;
- ; The following code gets moved to high ram located at "dest",
- ; where it is executed. C A U T I O N : if modifying anything
- ; in this program from here on: ALL labels must be of the form:
- ;
- ; label equ $+offset
- ;
- ; ...in order that the relocation to high ram work
- ; successfully. Forgetting to specify '$+offset' will cause
- ; the program to jmp into whatever is currently in low memory,
- ; with unpredictable results. Be careful....
- ;
- start equ $+offset
- ;
- ; patch in the new jump table (saving the old)
- ;
- patch equ $+offset
- call tbladdr ; calc [hl] = CP/M jmp table
- lxi d,vcstat ; point to save location
- call move ; move it
- ;
- ; now move new jump table to CP/M
- ;
- call tbladdr ; calc [hl] = CP/M's jmp table
- xchg ; move to de
- lxi h,newjtbl ; point to new
- call move ; move it
- lxi h,active$message
- call msgout
- lhld oldstk ; get old CP/M stack pointer
- sphl
- ret
- ;
- unpatch equ $+offset
- call reset ; reset disk in case it's R/O
- lxi h,inactive$message
- call msgout
- call tbladdr ; [hl] = CP/M's jmp table
- xchg ; move to de
- lxi h,vcstat ; get saved table
- call move ; move orig back
- lhld oldstk ; get old CP/M stack pointer
- sphl
- ret
- ;
- ; calculate [hl] = CP/M's jump table, [b] = length
- ;
- tbladdr equ $+offset
- lhld base+1 ; get BIOS pointer
- inx h ; ..skip
- inx h ; ..to
- inx h ; ..console status
- mvi b,9 ; move console jump vectors
- ret
- ;
- ; move [hl] to [de], length in [b]
- ;
- move equ $+offset
- mov a,m ; get a byte
- stax d ; put at new home
- inx d ; bump pointers
- inx h
- dcr b ; decrement byte count
- jnz move ; if more, do it
- ret ; if not, return
- ;
- ; move [hl] to [de], length in [bc]
- ;
- movecpm equ $+offset
- mov a,m ; get a byte
- stax d ; put at new home
- inx d ; bump pointers
- inx h
- dcx b ; decrement byte count
- mov a,b
- ora c
- jnz movecpm ; if more, do it
- ret ; if not, return
- ;
- msgout equ $+offset
- mov a,m ; get character from message string
- ora a ; all of string displayed?
- rz ; return, if so
- inx h ; no, bump pointer for next character
- mov c,a ; pass character to 'old' BIOS vector
- call conout
- jmp msgout ; display next character in message string
- ;
- ; This area is used for vectoring calls to the user's CBIOS,
- ; but saving the registers first in case they are destroyed.
- ;
- constat equ $+offset
- push b
- push d
- push h
- call vcstat
- pop h
- pop d
- pop b
- ret
- ;
- conin equ $+offset
- push b
- push d
- push h
- call vcin
- pop h
- pop d
- pop b
- ret
- ;
- conout equ $+offset
- push b
- push d
- push h
- call vcout
- pop h
- pop d
- pop b
- ret
- ;
- ; This is the jump table which is copied on top of the one
- ; pointed to by location 1 in CP/M
- ;
- newjtbl equ $+offset
- jmp constat ; console status test
-
- if INPUT
- jmp capture ; console input I/O-CAP routine
- endif ; INPUT
-
- if OUTPUT
- jmp conin ; console input routine
- endif ; OUTPUT
-
- if INPUT
- jmp conout ; console output routine
- endif ; INPUT
-
- if OUTPUT
- jmp capture ; console I/O-CAP output routine
- endif ; OUTPUT
-
- ;
- capture equ $+offset
- push h
- push d
- push b
- lxi h,0 ; save old stack pointer
- dad sp
- shld oldstk
- lxi sp,stack; make a new stack pointer
-
- if INPUT
- call vcin ; get console input
- mov c,a ; save in [c] for 'save'
- push psw ; and save on the stack
- endif ; INPUT
-
- call save ; save characters in buffer
-
- if INPUT
- pop psw ; get console input of the stack
- endif ; INPUT
-
- lhld oldstk ; get old CP/M stack pointer
- sphl
- pop b
- pop d
- pop h
-
- if OUTPUT
- jmp vcout
- endif ; OUTPUT
-
- if INPUT
- ret
- endif ; INPUT
-
- ;
- save: equ $+offset
- lhld size ; size = size + 1
- inx h
- shld size
- lhld ptr
- mov m,c
- inx h
-
- if INPUT
- mov a,c
- cpi cr ; carriage return?
- jnz notcr
- mvi m,lf ; yes, so add line feed because CP/M does not
- inx h ; bump pointer, for next time thru
- endif ; INPUT
-
- notcr equ $+offset; make label for next instruction...
-
- if QUIT
- mov a,c
- cpi 'Z'-40h ; control-Z?
- jz wtb ; write buffer, if so
- endif ; QUIT
-
- shld ptr
- lxi d,endmark ; get 'endmark' for buffer top address
- mov a,d
- cmp h ; getting near the end of buffer yet?
- rnz ; if not, just return
- mov a,e ; very near the top now, final address loaded?
- cmp l
- rnz ; if not, just return
-
- if not QUIET
- mvi c,bel ; warn user that we need to write to disk
- call conout
- endif ; QUIET
-
- ;
- ; wtb - write text buffer to disk
- ;
- wtb: equ $+offset
- lhld base+6 ; get warm boot address for next bias to CCP
- lxi d,0fffah; make bias to CCP
- dad d ; add bias to [hl]
- lxi b,1600h ; make quantity to move
- lxi d,cpmbuf; buffer all of CP/M system
- call movecpm ; move it...
- call reset ; reset disk in case it's R/O
- call open ; attempt to open USER.LOG
- inr a ; check CP/M return code
- jnz makeok ; USER.LOG already exist?
- ;
- nolog: equ $+offset
- ;
- call make ; make new file
- inr a ; check CP/M return code
- jnz makeok
-
- if ERRDISP
- lxi h,dirful; oops...can't make file, return to CP/M
- call msgout
- endif ; ERRDISP
-
- jmp base
- ;
- ; USER.LOG exists, so set the FCB entry for next append to file
- ;
- makeok: equ $+offset
- ;
- lda fcb+frc ; get record count
- sta fcb+nr ; make next record
- lhld size ; [de] = tbuf size
- xchg
- lxi h,dbuf ; top of stack points to dbuf
- push h
- lxi h,tbuf ; [hl] points to tbuf
- ;
- wtb1: equ $+offset
- mvi c,128 ; disk buffer size
- ;
- wtb2: equ $+offset
- mov a,m ; fetch next byte of tbuf
- inx h
- xthl
- mov m,a ; store in dbuf
- inx h
- xthl
- dcx d ; size = size - 1
- mov a,d ; exit loop if size = 0
- ora e
- jz wtb3
- dcr c ; loop until dbuf full
- jnz wtb2
- call setdma ; set dma to dbuf
- call write ; write full dbuf to disk
- push psw ; save possible error code
- lda fcb+frc ; get record count
- sta fcb+nr ; make next record
- pop psw ; get possible error code
- ora a ; check CP/M return code
- jz nextbuf
-
- if ERRDISP
- lxi h,dskful; oops...disk is full
- call msgout
- endif ; ERRDISP
-
- jmp base
- ;
- nextbuf equ $+offset
- ;
- xthl ; top of stack points to dbuf
- lxi h,dbuf
- xthl
- jmp wtb1 ; loop until end of tbuf
- ;
- wtb3: equ $+offset
- pop h ; [hl] points to current place in dbuf
- ;
- wtb4: equ $+offset
- mvi m,'Z'-40h ; store eof code
- inx h
- dcr c ; loop thru rest if dbuf
- jnz wtb4
- call setdma ; set dma to dbuf
- call write ; write last sector to disk
- push psw ; save possible error code
- lda fcb+frc ; get record count
- sta fcb+nr ; make next record
- pop psw ; get possible error code
- ora a ; check CP/M return code
- jz closeup
-
- if ERRDISP
- lxi h,dskful; oops...disk is full
- call msgout
- endif ; ERRDISP
-
- jmp base
- ;
- closeup equ $+offset
- ;
- call close ; clean up act and go home
- lxi h,tbuf ; clear text buffer
- shld ptr
- lxi h,0
- shld size
- wtb5: equ $+offset
- lhld base+6 ; get warm boot address for next bias to CCP
- lxi d,0fffah; make bias to CCP
- dad d ; add bias to [hl]
- lxi b,1600h ; make quantity to move
- lxi d,cpmbuf; buffer all of CP/M system
- xchg ; swap
- call movecpm ; move it...
- ret
- ;
- ; reset - reset disk
- ;
- reset: equ $+offset
- push h
- push d
- push b
- mvi c,resdsk
- call bdos
- pop b
- pop d
- pop h
- ret
- ;
- ; open - open disk file
- ;
- open: equ $+offset
- push h
- push d
- push b
- lxi d,fcb
- mvi c,offc
- call bdos
- pop b
- pop d
- pop h
- ret
- ;
- ; read - read record from disk file
- ;
- read: equ $+offset
- push h
- push d
- push b
- lxi d,fcb
- mvi c,rrfc
- call bdos
- pop b
- pop d
- pop h
- ret
- ;
- ; close - close disk file
- ;
- close: equ $+offset
- push h
- push d
- push b
- lxi d,fcb
- mvi c,cffc
- call bdos
- pop b
- pop d
- pop h
- ret
- ;
- ; delt - delete disk file
- ;
- delt: equ $+offset
- push h
- push d
- push b
- lxi d,fcb
- mvi c,dffc
- call bdos
- pop b
- pop d
- pop h
- ret
- ;
- ; write - write record to disk
- ;
- write: equ $+offset
- push h
- push d
- push b
- lxi d,fcb
- mvi c,wrfc
- call bdos
- pop b
- pop d
- pop h
- ret
- ;
- ; make - make new disk file
- ;
- make: equ $+offset
- push h
- push d
- push b
- lxi d,fcb
- mvi c,mffc
- call bdos
- pop b
- pop d
- pop h
- ret
- ;
- ; setdma - set dma address for disk file
- ;
- setdma: equ $+offset
- push h
- push d
- push b
- lxi d,dbuf
- mvi c,sdma
- call bdos
- pop b
- pop d
- pop h
- ret
- ;
-
- if ERRDISP
- dskful: equ $+offset
- db cr,lf,bel,'OOPS...disk is full!',0
- ;
- dirful: equ $+offset
- db cr,lf,bel,'OOPS...directory is full!',0
- endif ; ERRDISP
-
- ;
- active$message equ $+offset
- db ' (Active)',0
- ;
- inactive$message equ $+offset
- db ' (Inactive)',0
- ;
- fcb equ $+offset
- db 0 ; default drive specifier
-
- if SYSLOG
- db 'USER L','O'+80h,'G'
- endif ; SYSLOG
-
- if not SYSLOG
- db 'USER LOG'
- endif ; SYSLOG
-
- db 0,0,0,0,0,0,0,0,0,0
- ;
- pend equ $+offset; end of relocated code
- ;
- ; data area
- ;
- ds 128 ; 64 level stack
- stack equ $+offset;local stack
- ;
- ptr: equ $+offset
- ds 2 ; text buffer pointer
- ;
- size: equ $+offset
- ds 2 ; text buffer size
- ;
- ; Save the CP/M jump table here
- ;
- vcstat equ $+offset
- ds 3
- ;
- vcin equ $+offset
- ds 3
- ;
- vcout equ $+offset
- ds 3
- ;
- oldstk equ $+offset
- ds 2 ; storage for old CP/M stack pointer
- ;
- cpmbuf equ $+offset
- ds 1600h ; storage CP/M system image
- ;
- dbuf equ $+offset
- ds 128 ; secondary disk buffer address
- ;
- tbuf: equ $+offset
- ds 16*128 ; I/O-CAP storage for 16 sectors (2048 bytes)
- ;
- endmark equ $+offset;! ignore error - this marks end of program
- ;
- end
-