; ; TITLE 'XMODEM ver. 12.5 - 07/13/86' ; ; XMDM125.ASM - REMOTE CP/M FILE TRANSFER PROGRAM ; ; Originally adapted from Ward Christensen's MODEM2 ; by Keith Petersen, W8SDZ ; ; ASEG ;Needed by M80 assemblers, comment out if using MAC ; ; This program allows a remote user to transfer files (to or from) RCPM ; systems running under BYE (remote console program). It can be assem- ; bled with ASM, LASM, MAC, M80, SRLMAC and other 8080 assemblers. ; ; All comments and past revisions have been removed from this file and ; put into the XMODEM.UPD file. Place only the current revision at the ; beginning of this file and move the one that was here to XMODEM.UPD. ; ;======================================================================= ; ; v12.5 Fixed conditional assembly bug which caused date to ; 07/13/86 appear in log twice when MBBS and BYEBDOS were both set ; to YES. ; Fixed conditional assembly bug which did not allow MBFMSG ; to be set to YES while MBDESC was set to NO. ; Removed patch to log download before sending EOF because ; EOF would not be sent, leaving caller's program in file ; transfer mode, if LOGCALL routine exited with an error. ; This problem was noticed by Keith Petersen. ; Modified to abort any download which would result in a ; user exceeding his time limit when BYEBDOS is YES. ; Fixed bug which would cause caller to be logged off ; without updating log file if transmission errors caused ; his download to put him over time limit when BYEBDOS was ; YES and CLOCK and TIMEON in BYE were YES (call to TCHECK ; in BYE's extended BDOS call would hang up on caller). ; Revised comments for some equates to make them easier to ; understand. ; - Murray Simsolo ; ;======================================================================== ; VERSION EQU 1 INTERM EQU 2 MODLEV EQU 5 VMONTH EQU 07 VDAY EQU 13 VYEAR EQU 86 ; NO EQU 0 YES EQU NOT NO ; ; Define ASCII characters used ; BS EQU 08H ; Backspace character ACK EQU 06H ; Acknowledge CAN EQU 18H ; CTL-X for cancel CR EQU 0DH ; Carriage return CRC EQU 'C' ; CRC request character EOF EQU 1AH ; End of file - ^Z EOT EQU 04H ; End of transmission LF EQU 0AH ; Line feed NAK EQU 15H ; Negative acknowledge RLEN EQU 128 ; Record length TAB EQU 09H ; Horizontal tab SOH EQU 01H ; Start of header for 128-byte blocks STX EQU 02H ; 'Start of header' for 1024 byte blocks ; ;======================================================================= ; ; Conditional equates - change to suit your system, then assemble ; MHZ EQU 4 ; Clock speed, use integer (2,4,5,8, etc.) CPM3 EQU NO ; Yes, if operating in CP/M v3.0 environment STOPBIT EQU NO ; No, if using 1 stop bit, yes if using 2 BYEBDOS EQU NO ; Yes, if using BYE338-up, BYE501-up, or NUBYE ; with its I/O (CLOCK in BYE must be YES) ; No if using your own hardware overlay LUXMOD EQU NO ; Set to YES if LUXMODEM version desired rather ; than standard XMODEM with upload options. ; ;======================================================================= ; ; If OK2400 is YES, then it overrides the TAGLBR and MAXMIN restrictions ; if the current caller is operating at 2400 baud (or higher). ; OK2400 EQU NO ; Yes, no restrictions for 2400 bps callers ; MSPEED EQU 3CH ; Location of speed byte set by BYE prgm, must ; be set for OK2400 or BYEBDOS to work ; DSPFNAM EQU YES ; Set to YES if you wish XMODEM to display the ; file name being up or downloaded for user to ; see and verify system received name correctly. ; ; If ZCPR3 is YES, then NO filetypes of .NDR or .RCP will be received. ; This is for security if you need LDR.COM on A0: for cold starts or if ; LDR is in the defined path. (If you don't have LDR on-line or ; accessible, then this equate isn't necessary for ZCPR3 systems.) ; ZCPR3 EQU NO ; Yes, NO filetypes .NDR or .RCP received ; ;======================================================================= ; ; If ZCPR2 = yes, then the following will all be NO if wheel is set ; in local non-zero (0FFH) mode. SYSOP rules... ; ZCPR2 EQU NO ; Yes, if using ZCPR* with WHEEL byte ; WHEEL EQU 3EH ; Location of wheel byte (normally 3EH) NOCOMR EQU YES ; Yes, change .COM to .OBJ on receive NOCOMS EQU YES ; Yes, .COM files not sent NOLBS EQU NO ; Yes, .??# files not sent NOSYS EQU YES ; Yes, no $SYS files sent or reported ; ;======================================================================= ; ; The following are only used by NZCPR or ZCMD systems ; USEMAX EQU NO ; Yes, using NZCPR for maximum du: values ; No, use MAXDRV and MAXUSR specified next DRIVMAX EQU 03DH ; Location of MAXDRIV byte USRMAX EQU 03FH ; Location of MAXUSER byte ; ;======================================================================= ; ; Hard-coded system maximums allowed if USEMAX above is NO ; MAXDRV EQU 4 ; Number of disk drives used (1=A, 2=B, etc) MAXUSR EQU 5 ; Maximum 'SEND' user allowed ; ;======================================================================= ; ; File transfer buffer size - 16k is the same buffer length used in IMP, ; MDM7 and MEX so all those modem programs as well as XMODEM would be ; transferring from the buffer simultaneously, minimizing any delays. ; Slower floppy disk drives may require the use of a smaller buffer, try ; 8k, 4k, or 2k and use largest that does not result in a time-out at ; the sending end. Please note the requirement for the protocol to ac- ; cept any mixture of 1K and small blocks may result in effective buffer ; usage extending an additional 896 bytes (7*128) beyond the 'end' of ; the buffer defined here. (Actually, due to handshaking, the buffers ; are NOT loaded simultaneously, so the above statement is misleading, ; too large a buffer will slow things down if you have a slow disk ; drive.. Too small a buffer will really slow you down though, so ; stick with 16k...) ; BUFSIZ EQU 16 ; File transfer buffer size in Kbytes (16k) ; ;======================================================================= ; ; DESCRIB is used to ask the uploader to give a description of the file ; he just uploaded. If YES and ZCPR2 is YES and wheel is set, it does ; NOT ask for a description unless ASKSYS is set to YES. ; (If using on an MBBS v4.1 and up system, use MBDESC instead of ; this option.) (NDESC can be used with either DESCRIB or MBDESC.) ; DESCRIB EQU NO ; Yes asks for a description of uploaded file DRIVE EQU 'A' ; Drive area for description of upload USER EQU 14 ; User area for description of upload BSIZE EQU 32*1024 ; Set for 16k, 24k or 32k as desired for DESCRIB ; NDESC EQU NO ; If YES, user can add a "N" to option to skip ; description for pre-arranged uploads or ; for the sysop.. ASKSYS EQU NO ; If YES, and ZCPR2=YES, the system will ask ; the sysop for a description of the uploaded ; file ASKIND EQU NO ; IF YES, user is asked for the category of ; the uploaded file. This category is auto- ; matically added to the file description. ; ;======================================================================= ; ; XMODEM transfer log options ; LOGCAL EQU YES ; Yes, logs XMODEM transfers LOGDRV EQU 'A' ; Drive to place 'XMODEM.LOG' file LOGUSR EQU 14 ; User area to put 'XMODEM.LOG' file ; ; OxGate BBS puts the date after the caller's name. If you are using ; either BYEBDOS or B3RTC or RTC, and have an OxGate, then set this ; equate to YES, so the date doesn't appear twice. ; OXGATE EQU NO ; If yes, and B3RTC or RTC is yes, does not read ; date in OxGate's LASTCALR file. ; KNET EQU NO ; If yes, the log file is called XMODEM.TX# with ; $SYS attr set (for K-NET 84(tm) RCP/M Systems) ; LASTDRV EQU 'A' ; Drive to read 'LASTCALR' file from LASTUSR EQU 14 ; User area of 'LASTCALR' file, if 'LOGCAL' yes ; ;======================================================================= ; ; The receiving station sends an 'ACK' for each valid sector received. ; It sends a 'NAK' for each sector incorrectly received. In poor con- ; ditions either may be garbled. Waiting for a valid 'NAK' can slow ; things down somewhat, giving more time for the interference to quit. ; RETRY EQU NO ; Yes requires a valid NAK to resend a record ; No resends a record after any non-ACK ; ; Note that some modem programs will send a "C" instead of a NAK when ; operating in CRC mode. Therefore, RETRY EQU NO will allow XMODEM to ; work correctly with more programs. ; ;======================================================================= ; ; When sending in 1K block mode, XMODEM will downshift to 128 byte ; blocks when the ratio of successfully transmitted blocks to total ; errors falls below the ratio defined here. ; DWNSHFT EQU 5 ; must have at least this many good blocks for ; every error, or will downshift to size 128 ; MINKSP EQU 5 ; set this equate to the minimum MSPEED value ; allowed to use the 1k block protocol.. ; ; MSPEED values: 1=300, 5=1200, 6=2400 ; ;======================================================================= ; ; Allows uploading to be done on a specified driver and user area so all ; can readily find the latest entries. ; SETAREA EQU YES ; Yes, using designated du: to receive files ; No, upload to currently logged du: SPCDU EQU YES ; Yes, upload to designated du: if wheel set ; DRV EQU 'B' ; Drive to receive file on USR EQU 0 ; User area to receive file in ; ASKAREA EQU NO ; If YES, ask user what type of upload and ; set area accordingly. For Multiple ; Operating system support. ; SYSNEW EQU NO ; If YES, then new uploads are made $SYS ; to "hide" them from users until cleared... ; ;======================================================================= ; ; Selects the DU: for uploading private files with XMODEM RP option. ; PRDRV EQU 'B' ; Private drive for SYSOP to receive file PRUSR EQU 14 ; Private user area for SYSOP to receive file ; ;======================================================================= ; ; Selects the DU: for private download files. This permits Sysop ; to put file(s) in this area, then leave a private note to that ; person mentioning the name(s) of the file and its location. ; SPLDRV EQU 'B' ; Special drive area for downloading SYSOP files SPLUSR EQU 14 ; Special user area for downloading SYSOP files ; ;======================================================================= ; ; Selects the DU: used for message files uploaded with the "RM" option. ; (Used only if MBFMSG option enabled) ; MSGDRV EQU 'A' ; Drive used to receive message files MSGUSR EQU 15 ; User used to receive message files ; ;======================================================================= ; ; SYSOP may use NSWP or TAG and set the high bit of F1 to disallow the ; sending of large .LBR files. If TAGLBR is YES, only LUX or the option ; XMODEM L will allow transfer of individual member files from tagged ; .LBR files. The entire .LBR file can NOT be sent using XMODEM S NAME. ; TAGLBR EQU NO ; Yes tagged .LBR files not sent ; ; Note: The OK2400 equate if YES will bypass this restriction if the ; caller is operating at 2400 baud (or faster). ; ;======================================================================= ; ; Some modems will either go onhook immediately after carrier loss or ; can be set to lower values. A good value with the Smartmodem is five ; seconds, since it catches all "call forwarding" breaks. Not all is ; lost after timeout in XMODEM; BYE will still wait some more, but the ; chance of someone slipping in is less now. ; TIMOUT EQU 2 ; Seconds to abort after carrier loss ; ;======================================================================= ; ; Count the number of up/down loads since login. Your BBS program can ; check UPLDS and NDLDS when user logs out and update either the users ; file or another file for this purpose. ; LOGLDS EQU NO ; Count number of up/down loads since login. ; IF LOGLDS UPLDS EQU 054H ; Clear these values to Zero from your BBS pro- DNLDS EQU 055H ; gram when somebody logs in. NOTE: Clear ; ONLY when a user logs in. Not when he re- ; enters the BBS program for CP/M. ENDIF ; ;====================================================================== ; ; Maximum file transfer time allowed. ; ; NOTE: If ZCPR2 = YES and WHEEL byte is set, send time is unlimited. ; ; TIME 300 BPS 1200 BPS ; ------ ------- -------- ; 30 min 48.7k 180k ; 45 min 73.1k 270k ; 60 min 97.5k 360k ; MAXTIM EQU YES ; Yes if limiting transmission time ; MAXMIN EQU 60 ; Minutes for maximum file transfer time. ; this should be set to 60 if TIMEON is YES ; (99 minutes maximum.) (This is ignored if ; BYEBDOS is set.) ; ; Note: The OK2400 equate if YES will bypass MAXMIN limits. ; ;====================================================================== ; ; The following equates need to be set ONLY if you are NOT using the ; BYE-BDOS calls supported in BYE338 and newer. ; ; Length of external patch program. If over 128 bytes, get/set size ; LARGEIO EQU NO ; Yes, if modem patch area over 128 bytes LARSIZE EQU 0 ; If 'LARGEIO' set patch area size (bytes) here ; ;======================================================================= ; ; USECON allows XMODEM to display the record count on the local CRT ; during transfers. All new remote console programs support this ; feature. BYE3* and MBYE3* will tell XMODEM where to find the local ; console's output vector. ; USECON EQU YES ; Yes to get CONOUT address from BYE ; NO, get CONOUT address from the XMODEM overlay ; CONOFF EQU 15 ; Offset to COVECT where original console output ; routine address is stored in BYE3/MBYE ; versions immediately followed by BYE as a ; check to insure BYE is running. ; ;======================================================================= ; start of TIMEON area ; RTC EQU NO ; If YES, add clock and date reader code at ; start of GETTIME: and GETDATE: below ; ; The TIMEON and RTC equates should be NO if B3RTC is YES ; TIMEON EQU NO ; If YES and BYEBDOS is NO, add your clock reader ; code at the start of label GETTIME: and return ; time in registers A & B. Also set to YES if ; BYEBDOS is YES and you want XMODEM to check ; time on system (not necessary if TIMEON in BYE ; is YES - saves unnecessary code). TOSEXIT EQU NO ; If YES, time on system displayed on exit if ; B3RTC or TIMEON or BYEBDOS set to YES ; IF TIMEON AND NOT CPM3 LHOUR EQU 050H ; Set by BBS (or BYE) in binary when user logs LMIN EQU 051H ; on and his status STATUS EQU 053H ENDIF ; IF TIMEON AND CPM3 LHOUR EQU 022H ; Set by BBS (or BYE) in binary when user logs LMIN EQU 023H ; on and his status STATUS EQU 024H ENDIF ; ; end of TIMEON area ;======================================================================== ; Miscellaneous Support Bytes ;======================================================================== ; Set this equate to enable access byte support. ACBOFF specifies ; the offset from the JMP COLDBOOT instruction as above with WRTLOC. ; MBBS and some newer BBS's support this byte, therefore, it is no ; longer specific to MBBS. You must determine if your system uses this. ; ACCESS EQU NO ; Yes, check flags for upload/dwnld restrictions ACBOFF EQU 21 ; # of bytes from JMP COLDBOOT to ACCESS byte. ACWRIT EQU 8 ; Bit to test for BBS msg write OK (1=OK,0=NOT OK) ACDNLD EQU 32 ; Bit to test for downloads OK (1=OK,0=NOT OK) ACUPLD EQU 64 ; Bit to test for uploads OK (1=OK,0=NOT OK) DWNTAG EQU NO ; If YES, files with F3 attribute bit can be ; downloaded regardless of access byte restrictions ; ; Access byte flag bit assignments ; ; Bit ; Used for ; 0 ; System access (no admittance if off) ; 1 ; BBS access (if off, dumped to CP/M) ; 2 ; Read access (if off, no "R" command allowed) ; 3 ; Write access (if off, no "E" command allowed) ; 4 ; CP/M access (if off, no admittance to CP/M) ; 5 ; Download access (if off, no downloads permitted) ; 6 ; Upload access (if off, no uploads permitted) ; 7 ; Privileged user (if on, user is "privileged") ; ; Of these bits, only 5 and 6 are used by XMODEM. Bit numbers are ; powers of 2, bit 0 being least significant bit of byte. ;------------------------------------------------------------------------- ; The CONFUN and WRTLOC are supported by BYE339 and many BBS's require ; the WRTLOC for propoer operation. These functions are not specific to ; MBBS and therefore have been made independant of the MBBS equate. ; ; (Set CONFUN/WRTLOC YES if using with MBBS) ; CONFUN EQU YES ; Yes, check local console for function keys SYSABT EQU YES ; If yes, sysop can abort up/downloads with ^X ; (CONFUN must be enabled to use this option) ; ; If you set CONFUN true, a call to the console status check routine in ; the BIOS will be done during waiting periods and when sector counts ; are displayed on the local console in order to allow MBYE and BYE339 ; function keys to work. This is for MBYE. Other versions of BYE3 ; may or may not check for console function keys during the console ; status check "MSTAT" routine. ; WRTLOC EQU YES ; Yes, set/reset WRTLOC so BYE won't hang up LOCOFF EQU 12 ; # of bytes from JMP COLDBOOT to WRTLOC byte ; ; NOTE: Code to set/reset WRTLOC assumes WRTLOC byte to be ; located "LOCOFF" bytes from the JMP COLDBOOT instruction at ; the beginning of the BYE3 BIOS jump table. On BYE3 versions ; and MBYE versions, this offset is usually 12. Note: ; TIMEON and RTC should be set to no if B3RTC is on. ; (If BYEBDOS is enabled, the appropriate extended BDOS ; calls are used to set and reset the WRTLOC if this ; equate is set and LOCOFF is ignored in these cases.) ; ; End of Miscellaneous Support Bytes ;======================================================================= ; start of MBBS/MBYE specific information ; B3RTC EQU NO ; If YES, your clock is setup in BYE3 (or MBYE) ; set to NO if using BYEBDOS B3COFF EQU 25 ; OFFSET from COLDBOOT: to RTCBUF address B3CMOS EQU 7 ; OFFSET from RTCBUF: to mins on system ; MBMXT EQU NO ; If YES, running MBYE with max. time on system MBMXO EQU 24 ; OFFSET from COLDBOOT: to MXML address ; ; If B3RTC is YES and LOGCAL is YES, the log file will show ; the date and time of all up/downloads. Note: Set RTC, TIMEON, ; and BYEBDOS to NO if using B3RTC or MBMXT. ; ; Note: Some of these equates may not be valid if you are using MBYE* ; with another BBS program - check them carefully. ; MBBS EQU NO ; Yes if running MBBS v2.9 up LOGSYS EQU NO ; Set YES if running MBBS v3.1 or earlier MBDESC EQU NO ; Yes if running MBBS v4.0 up for upload desc. NEWPRV EQU NO ; Yes: all new uploads are private initially MBFMSG EQU NO ; Yes if running MBYE v4.1 up with MFMSG ; ; ;---------------------------------------------------------------------- ; ; If B3RTC is YES download time may be limited using the following ; equates instead of using MAXMIN. MAXMIN will be the default value ; if BYE is not running. ; B3TOS EQU NO ; Yes if using BYE3/MBYE and want to show time on sys ; MTOS EQU NO ; Yes if using maximum time on system instead ; of MAXMIN to limit transmission time ; IF MTOS AND MBMXT ; both must be YES MXTOS EQU YES ; (leave YES) ENDIF ; IF NOT (MTOS AND MBMXT) ; (if either is NO) MXTOS EQU NO ; (leave NO) ENDIF ; MXTL EQU NO ; Yes if limiting transmission time to time ; left plus MAXMIN. MXTOS must be yes. ; IF MXTL AND MXTOS ; both must be YES MTL EQU YES ; (leave YES) ENDIF ; IF NOT (MXTL AND MXTOS); (if either are NO) MTL EQU NO ; (leave NO) ENDIF ; ; end of MBBS/MBYE specific information ;======================================================================= ; ORG 100H JMP BEGIN ; ;----------------------------------------------------------------------- ; ; This is the I/O patch area. Assemble the appropriate I/O patch file ; for your modem, then integrate it into this program via DDT (or SID). ; Initially, all jumps are to zero, which will cause an unpatched XMODEM ; to simply execute a warm boot. All routines must end with RET. ; IF NOT BYEBDOS ; Universal I/O CONOUT: JMP 0 ; See 'CONOUT' discussion above MINIT: JMP 0 ; Initialization routine (if needed) UNINIT: JMP 0 ; Undo whatever MINIT did (or return) SENDR: JMP 0 ; Send character (via POP PSW) CAROK: JMP 0 ; Test for carrier MDIN: JMP 0 ; Receive data byte GETCHR: JMP 0 ; Get character from modem RCVRDY: JMP 0 ; Check receive ready (A - ERRCDE) SNDRDY: JMP 0 ; Check send ready SPEED: JMP 0 ; Get speed value for transfer time EXTRA1: JMP 0 ; Extra for custom routine EXTRA2: JMP 0 ; Extra for custom routine EXTRA3: JMP 0 ; Extra for custom routine ENDIF ; ;----------------------------------------------------------------------- ; IF NOT (LARGEIO OR BYEBDOS) ORG 100H+80H ; Origin plus 128 bytes for patches ENDIF ; IF LARGEIO AND NOT BYEBDOS ORG 100H+LARSIZE ; I/O patch area size if over 128 bytes ENDIF ; ; PRIVATE/SETAREA UPLOAD DISK/USER AREAS: ; ; (Here at start (usually 180H unless LARGEIO) so can be easily patched ; in .COM file using DDT without needing to reassemble. All references ; are made to these locations in memory and not to DRV/PRDRV/USR/PRUSR ; equates directly.) ; XPRDRV: DB PRDRV ; Private uploads go to this disk/user XPRUSR: DB PRUSR ; XDRV: DB DRV ; Forced uploads (if SETAREA EQU YES) XUSR: DB USR ; Go to this disk/user ; IF MBFMSG XMDRV: DB MSGDRV ; Message uploads go to this disk/user XMUSR: DB MSGUSR ; (if MBFMSG option enabled) ENDIF ; ;----------------------------------------------------------------------- ; ; File descriptors, change as desired if this list is not suitable. ; Move the line with the terminating '$' up, if fewer descriptors are ; desired. ; IF ASKIND AND DESCRIB ; KIND0: DB ' 0) - CP/M',CR,LF KIND1: DB ' 1) - ZCPR',CR,LF KIND2: DB ' 2) - MS-DOS/PC-DOS',CR,LF KIND3: DB ' 3) - dBASE',CR,LF KIND4: DB ' 4) - Basic',CR,LF KIND5: DB ' 5) - General',CR,LF KIND6: DB ' 6) - Modems',CR,LF KIND7: DB ' 7) - Games',CR,LF KIND8: DB ' 8) - Xerox/KPro',CR,LF KIND9: DB ' 9) - RCP/M',CR,LF DB '$' ENDIF ;..... ; ;---------------------------------------------------------------------- ; ; If ASKAREA and SETAREA are set, then set these areas up and modify ; the message text in the FILTYP: function below if you desire a ; different choice. (As released in XMDM121, 1 = CP/M, 2 = MS/PC-DOS ; and 3 = General Interest.) ; IF ASKAREA AND SETAREA ; MAXTYP EQU '3' ; Set maximum type choice # here ; TYPTBL: DB 'B',0 ; CHOICE 1 (CP/M NORMAL) DB 'B',9 ; CHOICE 1 (CP/M PRIVATE) DB 'B',3 ; CHOICE 2 (MS/PC-DOS NORMAL) DB 'B',9 ; CHOICE 2 (MS/PC-DOS PRIVATE) DB 'B',0 ; CHOICE 3 (General interest NORMAL) DB 'B',9 ; CHOICE 3 (General interest PRIVATE) ; ENDIF ; ;======================================================================= ; ; PROGRAM STARTS HERE ; ;======================================================================= ; ; Save CP/M stack, initialize new one for this program ; BEGIN: LXI H,0 DAD SP SHLD STACK LXI SP,STACK ; Initialize new stack ; IF BYEBDOS CALL BYECHK JZ BYEOK CALL ILPRT DB 'You need to be running BYEBDOS',CR,LF,0 JMP EXIT2 ; Get stack pointer back and return ; BYEOK: MVI C,BDSTOS ; Get current maximum time on system MVI E,255 CALL BDOS STA MAXTOS ENDIF ; IF B3RTC AND MXTOS AND (NOT BYEBDOS) CALL BYECHK ; If BYE not active MVI A,MAXMIN ; (we'll use MAXMIN as default) JNZ EXTMXT ; Skip MXML update LHLD 0001H ; Get JMP COLDBOOT DCX H MOV D,M DCX H MOV E,M LXI H,MBMXO ; + MBMXO offset to MXML DAD D MOV A,M ; = max time allowed on system ; EXTMXT: STA MAXTOS ; Store max download time ENDIF ; ; Get address of RTCBUF in BYE3 or MBYE ; IF B3RTC AND (NOT BYEBDOS) CALL BYECHK ; See if BYE3/MBYE is running JNZ NOBYE0 ; If not, skip this junk LHLD 0001H ; Get COLDBOOT addr DCX H ; (just before JMP WBOOT) MOV D,M ; And stuff in DE DCX H MOV E,M LXI H,B3COFF ; Add offset to RTCBUF address DAD D ; (in HL) MOV E,M ; Get RTCBUF address INX H ; And MOV D,M ; Stuff in DE XCHG ; Swap into HL SHLD RTCBUF ; Save for use later ENDIF ; NOBYE0: IF CONFUN ; Console status checks to be done? LHLD 0001H ; If so get addr of warmboot (jmp table) INX H INX H INX H ; + 3 = address of console status check SHLD CONCHK+1 ; Stuff after call for FUNCHK ENDIF ; IF WRTLOC ; Set WRITE LOCK? CALL SETLCK ENDIF ; ; Save the current drive and user area ; NOBYE1: MVI E,0FFH ; Get the current user area MVI C,SETUSR CALL BDOS STA OLDUSR ; Save user number here MVI C,CURDRV ; Get the current drive CALL BDOS STA OLDDRV ; Save drive here ; IF B3TOS OR TIMEON CALL TIME ; Get user's time status ENDIF ; IF BYEBDOS AND (NOT TIMEON) MVI C,BDPTOS ; Display time on system and CALL BDOS ; log off if over time limit ENDIF ; CALL ILPRT DB CR,LF ; IF LUXMOD DB 'LUX-' ENDIF ; DB 'XMODEM v' DB VERSION+'0',INTERM+'0','.',MODLEV+'0',' - ' DB VMONTH/10+'0',VMONTH MOD 10+'0','/' DB VDAY/10+'0',VDAY MOD 10+'0','/' DB VYEAR/10+'0',VYEAR MOD 10+'0',CR,LF,0 ; ; Stuff address of BIOS CONOUT vector in our routine as default. ; IF USECON AND NOT BYEBDOS LHLD 0001H ; Point to warm boot for normal BIOS LXI D,9 DAD D ; Calc addr of normal BIOS conout vector SHLD CONOUT+1 ; Save in case no BYE program is active CALL BYECHK JNZ NOBYE XCHG ; Point to the console output routine SHLD CONOUT+1 ; Save vector address supplied by BYE ENDIF ; ; Get option ; NOBYE: LXI H,FCB+1 ; Get primary option MOV A,M STA OPTSAV ; Save option CPI 'R' ; Receive file? JZ RECVOPT ; ; Send option processor ; Single option: "K" - force 1k mode ; INX H ; Look for a 'K' MOV A,M CPI ' ' ; Is it a space? JZ ALLSET ; Then we're ready to send... CPI 'K' JNZ OPTERR ; "K" is the only setable 2nd option LDA MSPEED CPI MINKSP ; If less than MINKSP bps, ignore 1k JC ALLSET ; Request MVI A,'K' ; Set 1k mode STA KFLAG ; First, force us to 1K mode CALL ILPRT DB '(1k protocol selected)',CR,LF,0 JMP ALLSET ; That's it for send... ; ; Receive option processor ; 3 or 4 options: "X" - disable auto-protocol select ; "P" - receive file in private area ; "C" - force checksum protocol ; "M" - message file upload (if MBFMSG) ; RECVOPT:MVI A,'K' ; First off, default to 1K mode STA KFLAG MVI A,0 ; And default to CRC mode STA CRCFLG ; CALL RCVOPC ; Check 1st option CALL RCVOPC ; Check 2nd option CALL RCVOPC ; Check 3rd option ; IF MBFMSG CALL RCVOPC ; Check 4th option ENDIF ; IF NDESC CALL RCVOPC ; Check 4th (or 5th) option ENDIF ; JMP OPTERR ; If 5th or 6th option, whoops! ; RCVOPC: INX H ; Increment pointer to next character MOV A,M ; Get option character HL points to CPI ' ' ; Space? JNZ CHK1ST ; No, we have an option POP PSW ; Else, we are done (restore stack) JMP ALLSET ; Exit routine now ; CHK1ST: CPI 'P' ; Got a "P" option? JNZ CHK2ND ; Nope STA PRVTFL ; Yep, set private upload flag RET ; Check next option ; CHK2ND: CPI 'C' ; Got a "C" option? JNZ CHK3RD ; Nope STA CRCFLG ; Set checksum flag (crc flag="C") CALL ILPRT DB '(Checksum protocol selected)',CR,LF,0 RET ; CHK3RD: CPI 'X' ; Got an "X" for first option? JNZ CHK4TH MVI A,0 STA KFLAG ; Disable "1K" flag CALL ILPRT DB '(128 byte protocol only)',CR,LF,0 RET ; CHK4TH: IF MBFMSG ; Allowing "RM" for message uploads? CPI 'M' ; Got an "M" for message upload? JNZ CHK5TH ; If not, bad option STA MSGFLG ; If "M", set MSGFLG MVI A,'P' ; Also, set PRVTFL STA PRVTFL LDA XMDRV ; And copy XMDRV STA XPRDRV LDA XMUSR ; And XMUSR to XPRDRV / XPRUSR STA XPRUSR RET ENDIF ; CHK5TH: IF NDESC ; Allowing "RN" to skip upload descript? CPI 'N' ; Got an 'N'? JNZ BADROP ; If nope, is NG.. STA NDSCFL ; else set flag to skip descript phase RET ENDIF ; BADROP: POP PSW ; Restore stack JMP OPTERR ; is bad option ; ; All options have been set, gobble up garbage characters from the line ; prior to receive or send and initialize whatever has to be initialized ; ALLSET: CALL GETCHR CALL GETCHR CALL MINIT ; ; Jump to appropriate function ; LDA OPTSAV ; Get primary option again ; IF LOGCAL STA LOGOPT ; But save it ENDIF ; CPI 'L' ; To send a file from a library? JZ SENDFIL CPI 'R' ; To receive a file? JZ RCVFIL CPI 'S' JZ SENDFIL ; Otherwise go send a file ; ; Invalid option ; OPTERR: ; IF ASKAREA AND SETAREA LDA OPTSAV ; Check 'option' CPI 'A' ; If 'A' (avail upload space option) CZ FILTYP ; ask type of upload... ENDIF ; IF NOT (SETAREA OR LUXMOD) CALL ILPRT DB CR,LF,'Uploads files to specified or ' DB 'current disk/user',0 ENDIF ; IF SETAREA AND NOT LUXMOD CALL ILPRT DB CR,LF,'Uploads files to ',0 LDA XDRV CALL CTYPE LDA XUSR MVI H,0 MOV L,A CALL DECOUT MVI A,':' CALL CTYPE CALL ILPRT DB ' (',0 LDA XDRV STA KDRV CALL KSHOW MVI A,')' CALL CTYPE ENDIF ; IF NOT LUXMOD CALL ILPRT DB CR,LF,'Private files to ',0 LDA XPRDRV CALL CTYPE LDA XPRUSR MVI H,0 MOV L,A CALL DECOUT MVI A,':' CALL CTYPE LDA XPRDRV ; If private drive is MOV B,A LDA XDRV ; The same as forced upload drive SUB B JZ SKSK2 ; Skip showing space available 2nd time CALL ILPRT DB ' (',0 LDA XPRDRV ; Else show it.. STA KDRV CALL KSHOW MVI A,')' CALL CTYPE ; SKSK2: CALL ILPRT DB CR,LF,0 ENDIF ; LDA OPTSAV ; Check 'option' CPI 'A' ; If 'A' (avail upload space option) JZ EXIT ; Skip error message ; IF WRTLOC AND NOT BYEBDOS CALL RSTLCK ENDIF ; CALL ERXIT ; Exit with error DB '++ Examples of valid options: ++ ' DB '(use Ctrl-C or Ctrl-K to abort)',CR,LF,LF ; IF NOT LUXMOD DB 'XMODEM S HELLO.DOC send a file to you',CR,LF DB 'XMODEM S B1:HELLO.DOC send from a named ' DB 'drive/area',CR,LF DB 'XMODEM SK HELLO.DOC send in 1k blocks',CR,LF DB 'XMODEM L CAT.LBR CAT.COM send a file from a library' DB CR,LF DB 'XMODEM LK CAT.LBR CAT.COM send in 1k blocks',CR,LF DB ' The ".LBR" file extension may be omitted',CR,LF,LF DB 'XMODEM R HELLO.DOC receive a file from you' DB CR,LF DB 'XMODEM RP HELLO.DOC receive in a private area' DB CR,LF ENDIF ; IF (MBDESC OR DESCRIB) AND NDESC DB 'XMODEM RN FILE.EXT receive without description' DB CR,LF ENDIF ; IF (NOT LUXMOD) AND MBFMSG DB 'XMODEM RM MESSAGE.FIL receive message for MBBS' DB CR,LF ENDIF ; IF NOT LUXMOD DB ' Add "C" for forced checksum ("RC" "RPC")',CR,LF DB ' Add "X" for forced 128 byte protocol ("RX" "RPX")' DB CR,LF DB ' "R" switches from CRC to checksum after 5 retries' DB CR,LF,LF DB 'XMODEM A shows areas/space for ' DB 'uploads$' ENDIF ; IF LUXMOD DB 'SEND MEMBERNAME.TYP sends member with CRC' DB CR,LF DB 'SENDK MEMBERNAME.TYP sends using 1k packets' DB CR,LF,LF DB 'XMODEM S MEMBERNAME.TYP same as SEND command' DB CR,LF DB 'XMODEM SK MEMBERNAME.TYP same as SENDK',CR,LF,LF DB '(XMODEM can NOT receive while in LUX.)$' ENDIF ; ; ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; ; ---> SENDFIL sends a CP/M file ; ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; ; The CP/M file specified in the XMODEM command is transferred over the ; phone to another computer running modem with the "R" (receive) option. ; The data is sent one record at a time with headers and checksums, and ; retransmission on errors. ; SENDFIL:CALL LOGDU ; Check file name or drive/user option LDA OPTSAV CPI 'L' ; If library option skip 'CNREC' CNZ CNREC ; Ignore if in library mode CALL OPENFIL ; Open the file MVI E,100 ; Wait 100 sec for initial 'NAK' CALL WAITNAK LHLD RCNT ; XMDM116.FIX CALL CKKSIZ ; XMDM116.FIX -- Murray Simsolo ; SENDLP: CALL CHKERR ; Check ratio of blocks to errors CALL RDRECD ; Read a record JC SENDEOF ; Send 'EOF' if done CALL INCRRNO ; Bump record number XRA A ; Initialize error count to zero STA ERRCT ; SENDRPT:CALL SENDHDR ; Send a header CALL SENDREC ; Send data record LDA CRCFLG ; Get 'CRC' flag ORA A ; 'CRC' in effect? CZ SENDCRC ; Yes, send 'CRC' CNZ SENDCKS ; No, send checksum CALL GETACK ; Get the 'ACK' JC SENDRPT ; Repeat if no 'ACK' CALL UPDPTR ; Update buffer pointers and counters LDA OPTSAV ; Get the command option again CPI 'L' JNZ SENDLP ; If not library option, go ahead ; ; ; Check to see if done sending LBR member yet, downshift to small blocks ; if less that 8 remaining ; LHLD RCNT MOV A,H ORA L ; See if L and H both zero now JZ SENDEOF ; If finished, exit LDA KFLAG ; Was last record a 1024 byte one? ORA A JZ SNRPT0 ; Just handled an normal 128 byte record DCX H ; Otherwise, must have be a BIG one, so DCX H ; Seven ... DCX H DCX H DCX H DCX H DCX H ; Plus ; SNRPT0: DCX H ; One, is either 1 or 8 SHLD RCNT ; One (or eight) less to go CALL CKKSIZ ; Check to see if at least 8 left JMP SENDLP ; Loop until EOF ; ; File sent, send EOT's ; SENDEOF: IF LOGLDS LDA DNLDS ; Get Down loads Counter INR A ; One more download since log in STA DNLDS ; And update counter ENDIF ; SNDEOFL:LDA EOFCTR ; Get EOF counter CPI 5 ; Tried five times ? JZ EXITLG ; Yes, quit trying MVI A,EOT ; Send an 'EOT' CALL SEND LDA EOFCTR ; Get EOF counter INR A ; Add one STA EOFCTR ; Save new count CALL GETACK ; Get the ACK JC SNDEOFL ; Loop if no ACK JMP EXITLG ; All done ;..... ; ; ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; ; ---> RCVFIL Receive a CP/M file ; ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; ; Receives a file in block format as sent by another person doing ; "XMODEM S FILENAME.TYP". Can be invoked by "XMODEM R FILENAME.TYPE" ; or by "XMODEM RC FILENAME.TYP" if checksum is to be used. ; RCVFIL: IF ACCESS CALL BYECHK JNZ RCVFL1 LHLD 0001H ; Get JMP COLDBOOT DCX H MOV D,M DCX H MOV E,M LXI H,ACBOFF ; + ACBOFF DAD D MOV A,M ; = ACCESS byte address ANI ACUPLD ; Test upload access bit JNZ RCVFL0 ; If bit on, uploads OK CALL ERXIT DB 'Sorry, but you are not allowed to upload files ' DB 'at this time...$' ENDIF ; RCVFL0: IF ACCESS AND MBFMSG LDA MSGFLG ORA A ; Is this "RM" upload? JZ RCVFL1 ; If not, skip ACWRIT check MOV A,M ANI ACWRIT ; If "RM", check if WRITE access JNZ RCVFL1 ; If so, ok CALL ERXIT DB 'Sorry, but you are not allowed to enter messages ' DB 'at this time...$' ENDIF ; RCVFL1: CALL LOGDU ; Check file name or drive/user option ; IF ZCPR2 LDA WHEEL ; Let SYSOP put file wherever he wants ORA A JZ RCVFL5 ; If WHEEL byte not set, stay normal LDA RCVDRV ORA A ENDIF ; ; IF ZCPR2 AND NOT SPCDU JZ RCVFL2 ENDIF ; IF ZCPR2 AND SPCDU JZ RCVFL2 ENDIF ; IF ZCPR2 SUI 'A' ; Convert ASCII drive to binary JMP RCVFL3 ; RCVFL2: LDA OLDDRV ; RCVFL3: INR A STA FCB ADI 'A'-1 ; Convert binary to ASCII STA XDRV ; Drive LDA RCVDRV ; See if a drive was requested ORA A LDA OLDUSR ; Current user JZ RCVFL4 ; If not, use current user LDA RCVUSR ; Else get requested user ; RCVFL4: STA XUSR ; User JMP CONTIN ENDIF ; ZCPR2 ; RCVFL5: IF SETAREA LDA XDRV SUI 40H STA FCB ENDIF ; LDA PRVTFL ; Receiving to a private area? ORA A JZ RCVFL6 ; If not, exit LDA XPRDRV ; Private area takes precedence SUI 40H STA FCB ; Store drive to be used ; RCVFL6: IF NOCOMR LXI H,FCB+9 ; Point to filetype MVI A,'C' ; 1st letter CMP M ; Is it C ? JNZ RCVFL7 ; If not, continue normally INX H ; Get 2nd letter MVI A,'O' ; 2nd letter CMP M ; Is it O ? JNZ RCVFL7 ; If not, continue normally INX H ; Get 3rd letter MVI A,'M' ; 3rd letter CMP M ; Is it M ? JNZ RCVFL7 ; If not, continue normally CALL ILPRT ; Print renaming message DB 'Auto-renaming file to ".OBJ"',CR,LF,0 LXI H,FCB+9 MVI M,'O' INX H MVI M,'B' INX H MVI M,'J' JMP CONTIN ENDIF ; NOCOMR ; RCVFL7: IF NOCOMR AND CPM3 LXI H,FCB+9 ; Point to filetype MVI A,'P' ; 1st letter CMP M ; Is it P ? JNZ RCVFL8 ; If not, continue normally INX H ; Get 2nd letter MVI A,'R' ; 2nd letter CMP M ; Is it R ? JNZ RCVFL8 ; If not, continue normally INX H ; Get 3rd letter MVI A,'L' ; 3rd letter CMP M ; Is it L ? JNZ RCVFL8 ; If not, continue normally CALL ILPRT ; Print renaming message DB 'Auto-renaming file to ".OBP"',CR,LF,0 LXI H,FCB+9 MVI M,'O' INX H MVI M,'B' INX H MVI M,'P' JMP CONTIN ENDIF ; NOCOMR AND CPM3 ; ; Check to see if filetype is .NDR, if so do NOT allow upload ; RCVFL8: IF ZCPR3 LXI H,FCB+9 ; Point to filetype MVI A,'N' ; 1st letter CMP M ; Is it N ? JNZ RCVFL9 ; If not, continue normally INX H ; Get 2nd letter MVI A,'D' ; 2nd letter CMP M ; Is it D ? JNZ RCVFL9 ; If not, continue normally INX H ; Get 3rd letter MVI A,'R' ; 3rd letter CMP M ; Is it R ? JNZ RCVFL9 ; If not, continue normally CALL ERXIT ; Print renaming message DB 'Cannot receive filetype ".NDR"',CR,LF,'$' ; ; Check to see if filetype is .RCP, if so do NOT allow upload ; RCVFL9: LXI H,FCB+9 ; Point to filetype MVI A,'R' ; 1st letter CMP M ; Is it R ? JNZ CONTIN ; If not, continue normally INX H ; Get 2nd letter MVI A,'C' ; 2nd letter CMP M ; Is it C ? JNZ CONTIN ; If not, continue normally INX H ; Get 3rd letter MVI A,'P' ; 3rd letter CMP M ; Is it P ? JNZ CONTIN ; If not, continue normally CALL ERXIT ; Abort with error msg DB 'Cannot receive filetype ".RCP"',CR,LF,'$' ENDIF ; ZCPR3 ; CONTIN: IF MBFMSG LDA MSGFLG ORA A ; Is this "RM" upload? JNZ DONT ; If yes, skip asking what kind of upload ENDIF ; IF ASKAREA AND SETAREA AND (NOT ZCPR2) CALL FILTYP ; Ask caller what kinda beast it is ENDIF ; IF ASKAREA AND SETAREA AND ZCPR2 LDA WHEEL ; Don't ask the SYSOP ORA A JNZ DONT ; If WHEEL byte set, skip asking CALL FILTYP ; Ask caller what kinda beast it is ENDIF ; DONT: CALL ILPRT ; Print the message ; IF NOT DSPFNAM DB CR,LF,'File will be received on ',0 ENDIF ; IF DSPFNAM DB CR,LF,'Receiving: ',0 ENDIF ; LDA PRVTFL ; Going to store in the private area? ORA A JZ CONT1 ; If not, exit ; LDA XPRDRV ; Get private drive JMP CONT2 ; If yes, it takes priority ; CONT1: IF SETAREA LDA XDRV ; Setarea uses a specified drive ENDIF ; IF NOT SETAREA LDA OLDDRV ; Otherwise get current drive ADI 'A' ; Convert to ASCII ; NOTDRV: DB 0,0 ; Filled in by 'GETDU' if requested ENDIF ; CONT2: STA KDRV ; Save drive for KSHOW SUI 40H ; Convert ASCII to binary STA FCB ; Stuff in FCB LDA KDRV ; Get ASCII version back again CALL CTYPE ; Print the drive to store on LDA PRVTFL ; Going to store in the private area? ORA A JZ NOPRVL ; If nope, skip ahead ; IF LOGCAL MVI A,'P' ; If private upload STA LOGOPT ; Show "P" as option ENDIF ; LDA XPRUSR ; Get private user area JMP CONT3 ; It takes priority ; NOPRVL: IF SETAREA LDA XUSR ; Setarea takes next precedence ENDIF ; IF NOT SETAREA LDA OLDUSR ; Get current drive for default ; NOTUSR: DB 0,0 ; Filled in by 'GETDU' if requested ENDIF ; CONT3: MVI H,0 MOV L,A CALL DECOUT ; Print the user area ; IF NOT DSPFNAM CALL ILPRT DB ':',CR,LF,0 ENDIF ; IF DSPFNAM MVI A,':' CALL CTYPE ; We showed disk/user: LXI H,FCB+1 ; Now display filename CALL DSPFN CALL ILPRT DB CR,LF,0 ENDIF ; CALL KSHOW ; Show available space remaining CALL ILPRT DB CR,LF,0 CALL CHEKFIL ; See if file exists CALL MAKEFIL ; If not, start a new file CALL ILPRT DB 'File open - ready to receive',CR,LF DB 'To cancel: Ctrl-X, pause, Ctrl-X',CR,LF,0 ; IF B3RTC AND (NOT MBMXT OR BYEBDOS) CALL GETTOS ; Get time on system SHLD TOSSAV ; Save it for exit ENDIF ; RCVLP: CALL RCVRECD ; Get a record JC RCVEOT ; Got 'EOT' CALL WRRECD ; Write the record CALL INCRRNO ; Bump record number CALL SENDACK ; Ack the record JMP RCVLP ; Loop until 'EOF' ; ; ; Got EOT on record so flush buffers then done ; RCVEOT: LHLD RECDNO ; Check for zero length file MOV A,H ; If no records, no file ORA L JNZ EOT1 ; If not zero, continue, else abort CALL RCVSABT ; Abort and erase the zero length file JMP EXIT ; And exit ; EOT1: CALL WRBLOCK ; Write the last block CALL SENDACK ; Ack the record CALL CLOSFIL ; Close the file XRA A ; Clear CTYPE's console STA CONONL ; Output only flag ; IF LOGLDS LDA UPLDS ; Get Upload Counter INR A ; One more upload since log in STA UPLDS ; Update Counter ENDIF ; ; Logging upload or crediting time on? ; IF LOGCAL LHLD VRECNO ; If yes, get virtual # of recs SHLD RCNT ; And stuff in RCNT CALL FILTIM ; Calculate appox. xfer time ENDIF ; IF B3RTC AND MBMXT AND (NOT BYEBDOS) CALL BYECHK ; If BYE not active JNZ EXITLG ; Skip MXML update LHLD 0001H ; Get JMP COLDBOOT DCX H MOV D,M DCX H MOV E,M LXI H,MBMXO ; + MBMXO offset to MXML DAD D MOV A,M ; = max time allowed on system ORA A ; Check it (zero?) JZ EXITLG ; If unlimited time, skip update INR A ; Else, increment it (for secs) ADD C ; Add mins LSB (can't be >255) JC MAK255 ; If overflow, make it max (255) JZ MAK255 ; (if zero, make 255) MOV M,A ; Update it (credit them for upload) JMP EXITLM ; MAK255: MVI A,255 ; If up to max, make sure they don't MOV M,A ; Get LESS than what they had.. ENDIF ; IF B3RTC AND NOT (BYEBDOS OR MBMXT) CALL BYECHK JNZ EXITLG ; SKIP this if BYE not running LHLD RTCBUF ; Get address of RTCBUF in HL LXI D,B3CMOS ; Add offset to mins on system DAD D ; (addr in HL) LDA TOSSAV ;Get saved time on system MOV M,A ; And restore it INX H ; (don't count upload time LDA TOSSAV+1 ; Against them) MOV M,A ENDIF ; IF BYEBDOS AND (NOT B3RTC) LDA MAXTOS ; Get maximum time allowed ORA A JZ EXITLG ; If zero, he's a super-guy anyway INR A ADD C ; Add in upload time JC MAK254 ; Make it 254 minutes if overflow JZ MAK254 ; (or zero) CPI 255 ; (or 255) JNZ MAXSTR ; MAK254: MVI A,254 ; (254 is max allowed) ; MAXSTR: STA MAXTOS ; Save for internal use MOV E,A MVI C,BDSTOS ; Set maximum time on system CALL BDOS ENDIF ; EXITLM: IF BYEBDOS OR (B3RTC AND MBMXT) CALL ILPRT DB CR,LF,'Upload time credited towards maximum timeon.' DB CR,LF,0 ENDIF ; JMP EXITLG ; ;----------------------------------------------------------------------- ; ; SUBROUTINES ; ;----------------------------------------------------------------------- ; ; FILTYP: Ask file type for uploads ; IF ASKAREA AND SETAREA ; ; Routine to get file type for uploads (modified from XMDM10XX.ASM ; by Russ Pencin (Dallas Connection)). (Modify MAXTYP and TYPTBL ; near the top of the program.) ; FILTYR: CALL ILPRT DB CR,LF,0 ; FILTYP: CALL ILPRT ; Modify message as needed DB CR,LF,'Is file for:',CR,LF,CR,LF DB ' (1) CP/M',CR,LF DB ' (2) MS/PC-DOS',CR,LF DB 'or (3) General interest?',CR,LF,CR,LF DB 'Enter choice (1, 2 or 3): ',0 ENDIF ;ASKAREA AND SETAREA ; IF ASKAREA AND SETAREA AND WRTLOC CALL RSTLCK ;Turn off WRTLOC so RDCON will work ENDIF ; IF ASKAREA AND SETAREA MVI C,RDCON CALL BDOS CPI '1' ;is it a cpm file JC FILTYR ;nope, ask again use default upload area(s) CPI MAXTYP+1 JNC FILTYR SUI '1' ;GET OFFSET FOR TYPTBL RAL RAL MVI D,0 MOV E,A LXI H,TYPTBL DAD D MOV A,M STA XDRV ;set drive INX H MOV A,M ;user STA XUSR INX H MOV A,M ;private drive STA XPRDRV INX H MOV A,M ;and private user values STA XPRUSR CALL ILPRT DB CR,LF,0 ENDIF ;ASKAREA AND SETAREA ; IF ASKAREA AND SETAREA AND WRTLOC CALL SETLCK ;Turn WRTLOC back on ENDIF ; IF ASKAREA AND SETAREA RET ENDIF ; ;--------------------------------------------------------------------- ; WRTLOC ROUTINES (SETLCK AND RSTLCK) ; IF WRTLOC AND NOT BYEBDOS SETLCK: CALL BYECHK ; Is BYE running RNZ ; If not, skip this LHLD 0001H ; Get JMP COLDBOOT DCX H MOV D,M DCX H MOV E,M LXI H,LOCOFF ; + LOCOFF DAD D ORI 0FFH ; = WRTLOC address MOV M,A ; Turn the lock on RET ; RSTLCK: CALL BYECHK ; Is BYE running RNZ ; Nope, don't touch a thing LHLD 0001H ; If so, time to reset it DCX H ; Get JMP COLDBOOT addr. MOV D,M DCX H MOV E,M LXI H,LOCOFF ; + LOCOFF bytes DAD D ; = WRTLOC address XRA A ; Clear it MOV M,A ; (so ctrl-C/ctrl-K work) RET ENDIF ;WRTLOC AND NOT BYEBDOS ; IF WRTLOC AND BYEBDOS SETLCK: MVI C,BDWRTL ; Set/Get writeloc function MVI E,1 ; Turn on WRTLOC flag CALL BDOS RET ; RSTLCK: MVI C,BDWRTL ; Set/Get writeloc function MVI E,0 ; Turn off WRTLOC flag CALL BDOS RET ENDIF ; ;--------------------------------------------------------------------- ; ; Display file name function ; IF DSPFNAM ; HL=FCB address DSPFN: MVI B,8 ; PRNAM: MOV A,M ANI 7FH ; Strip any attribute bits CPI ' ' ; Don't print blanks CNZ CTYPE ; Print filename INX H DCR B JNZ PRNAM ; PRDOT: MVI A,'.' ; After first part, print dot CALL CTYPE MVI B,3 ; PRTYP: MOV A,M ANI 7FH ; Strip any attribute bits CPI ' ' ; Don't print blanks CNZ CTYPE ; Print filetype INX H DCR B JNZ PRTYP RET ENDIF ; DSPFNAM ; ; Check to see if BYE is running before getting CONOUT, checking MBBS ; ACCESS byte or setting/resetting WRTLOC. This routine also returns ; the address of the original cold boot routine in DE. ; ; Go through a big search to see if BYE is active. ; IF BYEBDOS BYECHK: MVI C,32 ; This bizarre combination determines MVI E,241 ; If BYE is not there. CALL BDOS CPI 77 ; Is it there? RET ENDIF ; IF (NOT BYEBDOS) AND (USECON OR ACCESS OR WRTLOC) BYECHK: LHLD 0001H ; Point to warm boot again DCX H ; If BYE active, MOV D,M ; Pick up pointer to BYE variables DCX H ; (COVECT) followed by 'BYE' MOV E,M LXI H,CONOFF ; Calculate address of BYE variable DAD D ; Where ptr to orig BIOS vector stored MOV E,M ; Load that address into DE, if BIOS INX H ; Is active, DE now points to original MOV D,M ; BIOS console output vector INX H ; Point to BYE signon message ; ; Note that if more BYE variables are added after the cold boot pointer, ; extra INX may be needed. Fix to match your BYE. ; MOV A,M ; Get letter ANI 05FH ; Convert to upper case if needed CPI 'B' ; Try to match 'BYE' RNZ ; Out if BYE not active INX H MOV A,M ANI 05FH ; Convert to upper case if needed CPI 'Y' RNZ INX H MOV A,M ANI 05FH ; Convert to upper case if needed CPI 'E' RET ENDIF ; ; Check next character to see if a space or non-space, file name error ; if no ASCII character. ; CHKFSP: DCR B JZ NFN ; Error if end of chars. MOV A,M CPI ' '+1 RNC ; Ok if valid character so return INX H JMP CHKFSP ; Look at next character ; ; Check next character to see if a space or non-space, go to menu if a ; command error. ; CHKSP: DCR B JZ OPTERR INX H MOV A,M ; Get the char. there CPI ' ' ; Space character? RET ; JZ = space, JNZ = non-space ; ; Exit, but first write record to log file and ask for description ; EXITLG: ; IF LOGCAL OR MBDESC OR MBFMSG CALL LOGCALL ENDIF ; ; Ask sysop for a description of the file if ASKSYS is yes ; IF DESCRIB AND ZCPR2 AND (NOT ASKSYS) LDA WHEEL ; If its the Sysop, don't ask ORA A ; For a description because he JNZ EXIT ; Might want to batch recv files ENDIF ; IF DESCRIB AND NDESC LDA NDSCFL ; If user picked "N" option ORA A ; allow them to skip upload JNZ EXIT ; descript ENDIF ; IF DESCRIB AND WRTLOC CALL RSTLCK ; Clear WRTLOC before DESCRIB ENDIF ; IF DESCRIB CALL ASK ; If yes, ask for description of file ENDIF ; ; Finished, clean up and return to CP/M, send thank-you and timeon ; messages if enabled. ; EXIT: XRA A STA CONONL ; Reset 'console only' flag for timeon ; IF WRTLOC CALL RSTLCK ; Clear WRTLOC ENDIF ; NOBYE2: CALL UNINIT ; Reset vectors (if needed) LDA OLDDRV ; Restore the original drive CALL RECDRX LDA OLDUSR ; Restore the original number CALL RECARE LXI D,TBUF ; Reset to default DMA address MVI C,SETDMA CALL BDOS LDA OPTSAV ; If so check option flag CPI 'R' ; Was it 'R' for receive JNZ EXIT1 ; If not, then skip this, CALL ILPRT ; And print DB CR,LF,'Thanks for the upload',CR,LF,0 ; IF SYSNEW CALL ILPRT DB CR,LF,'(Upload set as SYS file and cannot be examined' DB CR,LF,'or downloaded until released by the SYSOP....)' DB CR,LF,0 ENDIF ; IF B3RTC AND NOT (MBMXT OR BYEBDOS) CALL ILPRT ; And print DB CR,LF,'Time online is not increased during uploads' DB CR,LF,0 ENDIF ; IF MBFMSG LDA MSGFLG ; Was this a "XMODEM RM" upload? ORA A JZ NOTMSG CALL BYECHK JNZ EXIT1 CALL ILPRT DB CR,LF DB 'Loading MFMSG for message input, please stand by...' DB CR,LF,LF,0 LXI D,81H ; Our buffer starts at 81H MVI C,0 ; C=# of characters (stuff at 80H) CALL MBDFIL STA 80H ; Save # of chars in 80H MVI A,0C2H ; Stuff C2H (JNZ instruction) STA 0000H ORA A ; Make sure NZ flag set so JNZ will jump JMP 0000H ; NOTMSG: ENDIF ; MBFMSG ; IF MBFMSG AND NOT MBDESC JMP EXIT1 ; If not message upload, exit ENDIF ; ;----------------------------------------------------------------------- ; IF MBDESC AND ZCPR2 AND (NOT ASKSYS) LDA WHEEL ; If its the Sysop, don't ask ORA A ; For a description because he JNZ EXIT1 ; Might want to batch recv files ENDIF ; IF MBDESC AND NDESC LDA NDSCFL ; If user picked "N" option ORA A ; allow them to skip upload JNZ EXIT1 ; descript ENDIF ; IF MBDESC CALL BYECHK JNZ EXIT1 CALL ILPRT DB CR,LF DB 'Loading MBBS for upload description, ' DB 'please stand by...',CR,LF,LF,0 ENDIF ; IF MBDESC AND NEWPRV MVI A,'P' ; ALL "NEW UPLOADS:" private to start ENDIF ; IF MBDESC AND NOT NEWPRV LDA PRVTFL ; 80H=0 if public, "P" if private ENDIF ; IF MBDESC STA 80H ; Stuff "private" flag in page zero LXI D,82H ; Our buffer starts at 82H MVI C,0 ; C=# of characters (stuff at 81H) LXI H,MBDSH ; Heading ("NEW UPLOAD: ") ; MBDSHP: MOV A,M CPI 0 JZ MBDFS CALL MBDPUT INX H JMP MBDSHP ; MBDFS: CALL MBDFIL STA 81H ; Save # of chars in 81H MVI A,0CAH ; Stuff CAH (JZ instruction) STA 0000H XRA A ; Make sure Z flag set so JZ will jump JMP 0000H ; MBDSH: DB 'NEW UPLOAD: ',0 ; Heading stuffed ahead of filename ENDIF ; MBDESC ; IF MBDESC OR MBFMSG MBDFIL: LDA FCB ; Get drive code ORA A ; Check it JNZ MWDRV ; If auto login, use it LDA DSKSAV ; Else, get current disk INR A ; MWDRV: ADI 'A'-1 CALL MBDPUT ; Stuff in command line buffer LDA USRSAV ; Get user # CPI 10 ; Are we 0-9 or above? JC US0 ; Must be 0-9 ORA A ; Clear flags DAA ; Decimal adjust RAR ; Shift down tens digit RAR RAR RAR ANI 0FH ; Mask out tens digit ADI '0' ; Make it ASCII CALL MBDPUT LDA USRSAV ORA A ; Clear flags DAA ; Decimal adjust ANI 0FH ; Mask out singles digit ; US0: ADI '0' ; Make it ASCII CALL MBDPUT MVI A,':' ; Put in a colon CALL MBDPUT LXI H,FCB+1 ; Stuff in filename without spaces MVI B,8 ; DESNM: MOV A,M CPI ' ' CNZ MBDPUT INX H DCR B JNZ DESNM MVI A,'.' CALL MBDPUT MVI B,3 ; DESNM3: MOV A,M CPI ' ' JZ DESGO CPI 0 JZ DESGO CALL MBDPUT INX H DCR B JNZ DESNM3 ; DESGO: MOV A,C RET ; MBDPUT: ANI 7FH ; Strip off any high bits STAX D ; Short routine to stuff A in (DE) and INX D ; Increment pointer and character count INR C RET ENDIF ; MBDESC OR MBFMSG ; ;----------------------------------------------------------------------- ; EXIT1: IF (TIMEON OR B3TOS) AND (NOT LUXMOD) AND TOSEXIT CALL TIME ; Tell user how long he's been on ENDIF ; IF (BYEBDOS AND (NOT TIMEON)) AND TOSEXIT AND (NOT LUXMOD) MVI C,BDPTOS ; Print time on system CALL BDOS ENDIF ; EXIT2: XRA A LHLD STACK SPHL RET ; ; Check local console status in order to let BYE function keys work in ; MBYE and possibly other BYE versions also. (Your BYE must check for ; console function keys in MSTAT.) ; IF CONFUN FUNCHK: PUSH B ; Save everything PUSH D ; (to be safe) PUSH H PUSH PSW ; CONCHK: CALL 0000H ; Address patched in by START ; ENDIF ; IF CONFUN AND SYSABT ORA A ; If SYSABT set, check for JZ CONDNE ; CANCEL (^X) typed by sysop MVI C,RDCON CALL BDOS CPI CAN JNZ CONDNE STA SYSABF ENDIF ; CONDNE: IF CONFUN POP PSW ; For BIOS JMP CONSTAT POP H POP D POP B ; Restore everything RET ; And return ENDIF ; ; Get Disk and User from DUSAVE and log in if valid. ; GETDU: CALL CHKFSP ; See if a file name is included SHLD SAVEHL ; Save location of the filename LDA PRVTFL ; Uploading to a private area? ORA A JNZ TRAP ; If yes, going to a specified area LXI H,DUSAVE ; Point to drive/user LDA OLDDRV ; Get current drive STA DUD ADI 'A' STA RCVDRV MOV A,M ; Get 1st character CPI '0' JC GETDU1 CPI '9'+1 JC NUMER1 ; GETDU1: STA RCVDRV ; Allows SYSOP to upload to any drive CPI 'A'-1 JC NUMER ; Satisfied with current drive SUI 'A' STA DUD ; IF ZCPR2 LDA WHEEL ; SYSOP using the system? ORA A LDA DUD ; Get the value back (flags stay) JNZ GETDU2 ; If sysop, all things are possible ENDIF ; IF NOT USEMAX CPI MAXDRV JNC ILLDU ; Drive selection not available ENDIF ; IF USEMAX PUSH H LXI H,DRIVMAX ; Point to max drive byte INR M CMP M ; And check it PUSH PSW ; Save flags from the CMP DCR M ; Restore max drive to normal POP PSW ; Restore flags from the CPM JNC ILLDU POP H ENDIF ; GETDU2: INX H ; Get 2nd character ; NUMER: MOV A,M CPI ':' JZ OK4 ; Colon for drive only, no user number CALL CKNUM ; Check if numeric ; NUMER1: SUI '0' ; Convert ASCII to binary STA DUU ; Save it INX H ; Get 3rd character if any MOV A,M CPI ':' JZ OK1 LDA DUU CPI 1 ; Is first number a '1'? JNZ ILLDU MOV A,M CALL CKNUM SUI '0'-10 STA DUU INX H ; Get 4th (and last character) if any MOV A,M CPI ':' JNZ ILLDU ; OK1: LDA OPTSAV ; Get the option back CPI 'R' ; Receiving a file? LDA DUU ; Get desired user area JZ OK2 ; Yes, can not use special download area LDA DUD ; Get desired drive CPI SPLDRV-'A' ; Special download drive requested? LDA DUU ; Get user area requested JNZ OK2 ; If none, exit CPI SPLUSR ; Special download area requested? JZ OK3 ; If yes, process request ; OK2: IF ZCPR2 LDA WHEEL ; SYSOP using the system? ORA A LDA DUU ; Restore desired user area STA RCVUSR ; Allows SYSOP to upload anywhere JNZ OK3 ; If yes, let him have all user areas ENDIF ; IF NOT USEMAX CPI MAXUSR+1 ; Check for maximum user download area JNC ILLDU ; Error if more (and not special area) ENDIF ; IF USEMAX PUSH H LXI H,USRMAX ; Point at maximum user byte CMP M ; And check it JNC ILLDU POP H ENDIF ; OK3: MOV E,A ; IF NOT SETAREA STA NOTUSR+1 ; Store requested user area MVI A,3EH ; 'MVI A,--' instruction STA NOTUSR ENDIF ; MVI C,SETUSR CALL BDOS ; Set to requested user area ; OK4: LDA DUD ; Get drive MOV E,A ; IF NOT SETAREA ADI 'A' STA NOTDRV+1 ; Store requested drive MVI A,3EH ; 'MVI A,--' instruction STA NOTDRV ENDIF ; MVI C,SELDSK CALL BDOS ; Set to requested drive ; XIT: JMP TRAP ; Now find file selected ; ; Shows available space on upload disk/area. Uses KDRV data area which ; must be loaded before calling this routine. (So KSHOW will work with ; user specified disk if SETAREA equate is not set YES.) ; ; Print the free space remaining for the received file ; CPMVER EQU 0CH CURDPB EQU 1FH GALLOC EQU 1BH SELDSK EQU 0EH GETFRE EQU 46 ; KDRV: DB 0 ; Drive stored here before calling KSHOW ; KSHOW: LDA KDRV ; Get drive ('A','B','C',etc.) SUI 41H ; Convert to numeric (0,1,2,etc.) MOV E,A ; Stuff in E for BDOS call MVI C,SELDSK ; Select the directory drive to retrieve CALL BDOS ; The proper allocation vector MVI C,CURDPB ; It's 2.X or MP/M...request DPB CALL BDOS INX H INX H MOV A,M ; Get block shift STA BLKSHF INX H ; Bump to block mask MOV A,M INX H INX H MOV E,M ; Get max block # INX H MOV D,M XCHG SHLD BLKMAX ; Save it XCHG INX H MOV E,M ; Get directory size INX H MOV D,M XCHG ; ; Calculate # of K free on selected drive ; MVI C,CPMVER ; Get CP/M version number CALL BDOS MOV A,L ; Get returned version number CPI 30H ; 3.0? JC FREE20 ; Use old method if not LDA KDRV ; Get drive # SBI 'A' ; Change from ASCII to binary MOV E,A ; Use new Compute Free Space BDOS call MVI C,GETFRE CALL BDOS MVI C,3 ; Answer is a 24-bit integer ; FRE3L1: LXI H,80H+2 ; Answer is in 1st 3 bytes of DMA adr MVI B,3 ; Convert it from sectors to K ORA A ; By dividing by 8 ; FRE3L2: MOV A,M RAR MOV M,A DCX H DCR B JNZ FRE3L2 ; Loop for 3 bytes DCR C JNZ FRE3L1 ; Shift 3 times LHLD 80H ; Now get result in K JMP SAVFRE ; Go store it ; FREE20: MVI C,GALLOC ; Get address of allocation vector CALL BDOS XCHG LHLD BLKMAX ; Get its length INX H LXI B,0 ; Init block count to 0 ; GSPBYT: PUSH D ; Save alloc address LDAX D MVI E,8 ; Set to process 8 blocks ; GSPLUP: RAL ; Test bit JC NOTFRE INX B ; NOTFRE: MOV D,A ; Save bits DCX H ; Count down blocks MOV A,L ORA H JZ ENDALC ; Quit if out of blocks MOV A,D ; Restore bits DCR E ; Count down 8 bits JNZ GSPLUP ; Do another bit POP D ; Bump to next byte.. INX D ; Of alloc. vector JMP GSPBYT ; Process it ; ENDALC: POP D ; Clear stack of allocation vector ptr. MOV L,C ; Copy block to HL MOV H,B LDA BLKSHF ; Get block shift factor SUI 3 ; Convert from sectors to K JZ SAVFRE ; Skip shifts if 1K blocks... ; ; Return free in HL FREKLP: DAD H ; Multiply blocks by K/BLK DCR A JNZ FREKLP ; ; Print the amount of free space remaining on the selected drive ; SAVFRE: CALL DECOUT CALL ILPRT DB 'k available for uploads',0 RET ; ; Log into drive and user (if specified). If none mentioned, it falls ; through to 'TRAP' routine for normal use. ; LOGDU: LXI H,TBUF ; Point to default buffer command line MOV B,M ; Store number of characters in command INR B ; Add in current location ; LOG1: CALL CHKSP ; Skip spaces to find 1st command JZ LOG1 ; LOG2: CALL CHKSP ; Skip 1st command (non-spaces) JNZ LOG2 INX H CALL CHKFSP ; Skip spaces to find 2nd command SHLD SAVEHL ; Save start address of the 2nd command ; ; Now point to the first byte in the argument, i.e., if it was of format ; similar to: B6:HELLO.DOC then we point at the drive character 'B'. ; LXI D,DUSAVE MVI C,4 ; Drive/user is 4 characters maximum ; CPLP: MOV A,M CPI ' '+1 ; Space or return, finished JC TRAP STAX D INX H INX D CPI ':' JZ GETDU ; If colon, get drive/user and log in DCR B ; One less position to check DCR C ; One less to go JNZ CPLP ; ; Check for no file name or ambiguous name ; TRAP: CALL MOVEFCB ; Move the filename into the file block LXI H,FCB+1 ; Point to file name MOV A,M ; Get first character of file name CPI ' ' ; Any there? JNZ ATRAP ; Yes, check for ambigous file name ; NFN: CALL ERXIT ; Print message, exit DB '++ No file name requested ++$' ; ATRAP: MVI B,11 ; 11 characters to check ; TRLOOP: MOV A,M ; Get char from FCB CPI '?' ; Ambiguous? JZ TRERR ; Yes, exit with error message CPI '*' ; Even more ambiguous? JZ TRERR ; Yes, exit with error message INX H ; Point to next character DCR B ; One less to go JNZ TRLOOP ; Not done, check some more RET ; TRERR: CALL ERXIT ; Print message, exit DB '++ Wild-card options are not valid ++$' ; CKNUM: CPI '0' JC ILLDU ; Error if less than ascii '0' CPI '9'+1 RC ; Error if more than ascii '9' ; ILLDU: CALL ERXIT DB '++ Improper drive/user combination ++$' ; ; Receive a record - returns with carry bit set if EOT received ; RCVRECD:XRA A ; Initialize error count to zero STA ERRCT ; RCVRPT: IF CONFUN ; Check for function key? CALL FUNCHK ; Yeah, why not? ENDIF ; IF CONFUN AND SYSABT LDA SYSABF ; If SYSABT option, check ORA A ; to see if Abort JNZ RCVSABT ; If so, bail out now... ENDIF ; MVI B,10-1 ; 10-second timeout CALL RECV ; Get any character received JC RCVSTOT ; Timeout ; RCVRPTB:CPI SOH ; 'SOH' for a 128-byte block? JZ RCVSOH ; Yes CPI STX ; A 1024-byte block? JZ RCVSTX ; ORA A ; JZ RCVRPT ; Ignore nulls CPI CRC ; Ignore our own 'CRC' if needed JZ RCVRPT CPI NAK ; Ignore our own 'NAK' if needed JZ RCVRPT CPI CAN ; CANcel? JZ CANRCV ; (look for CAN CAN) CPI EOT ; End of transfer? STC ; Return with carry set if 'EOT' RZ ; ; Didn't get SOH or EOT - or - didn't get valid header - purge the line, ; then send nak ; RCVSERR:MVI B,1 ; Wait for 1 second CALL RECV ; After last char. received JNC RCVSERR ; Loop until sender done LDA FRSTIM ; Is it the first time? ORA A MVI A,NAK JNZ RCVSER2 ; If not first time, send NAK ; ; First time through...do crc/1k/checksum select ; LDA CRCFLG ; Get 'CRC' flag ORA A ; 'CRC' in effect? MVI A,NAK ; Put 'NAK' in accum JNZ RCVSER2 ; And go send it MVI A,CRC ; Tell sender 'CRC' is in effect CALL SEND LDA KFLAG ; Did we want 1k protocol? ORA A JZ RCVSERX ; No, just send the "C" MVI A,'K' ; Else send a C and a K ; RCVSER2:CALL SEND ; The 'NAK' or 'CRC' request ; RCVSERX:LDA ERRCT ; Abort if INR A ; We have reached STA ERRCT ; The error CPI 10 ; Limit? JZ RCVSABT ; Yes, abort CPI 5 ; Have we tried 5 times already? JNZ RCVRPT ; No, try again with same mode MVI A,'C' ; Else flip to checksum mode if CRC STA CRCFLG JMP RCVRPT ; And try again ; ; Error limit exceeded, so abort ; CANRCV: CALL DELAY ; Wait 100ms CALL RCVRDY ; Character waiting? JZ RCVRPT ; If so, no pause, skip CANcel MVI B,4 CALL RECV ; Else wait for 2nd character JC RCVSERR ; If no second character received, error CPI CAN JNZ RCVRPTB ; If second character not CAN, check it ; RCVSABT:CALL CLOSFIL ; Close file CALL ILPRT DB CR,LF,CR,LF,'++ Receive cancelled ++',0 CALL DELFILE ; Delete received file CALL ERXIT ; Print second half of message DB '++ Partial file deleted ++$' ; ; Deletes the received file (used if receive aborts) ; DELFILE:LXI D,FCB ; Point to file MVI C,DELET ; Get function CALL BDOS ; Delete it INR A ; Delete ok? RNZ ; Yes, return CALL ERXIT ; No, abort DB '++ Can''t delete received file ++$' ; ; Timed out on receive ; RCVSTOT:JMP RCVSERR ; Bump error count, etc. ; ; Got SOH or STX - get block number, block number complemented ; RCVSOH: LXI H,128 ; 128 bytes in this block XRA A ; Zero-out KFLAG JMP RCVHDR ; ; RCVSTX: MVI A,0FFH ; Set KFLAG true LXI H,1024 ; 1024 bytes in block ; RCVHDR: SHLD BLKSIZ ; Store block size for later STA KFLAG ; Set KFLAG as appropriate MVI B,1 ; Timeout = 1 sec MVI A,1 ; Need something to store at FRSTIM STA FRSTIM ; Indicate first 'SOH' received CALL RECV ; Get record JC RCVSTOT ; Got timeout MOV D,A ; D=block number MVI B,1 ; Timeout = 1 sec CALL RECV ; Get complimented record number JC RCVSTOT ; Timeout CMA ; Calculate the complement CMP D ; Good record number? JZ RCVDATA ; Yes, get data ; ; Got bad record number ; JMP RCVSERR ; Bump error count ; RCVDATA:MOV A,D ; Get record number STA RCVRNO ; Save it MVI C,0 ; Initialize checksum CALL CLRCRC ; Clear CRC counter LHLD BLKSIZ ; Get block size, XCHG ; And put in DE pair to initialize count LHLD RECPTR ; Get buffer address ; RCVCHR: MVI B,1 ; 1 sec timeout CALL RECV ; Get the character JC RCVSTOT ; Timeout MOV M,A ; Store the character INX H ; Point to next character DCX D ; Done? MOV A,D ORA E JNZ RCVCHR ; No, loop if <= BLKSIZ LDA CRCFLG ; Get 'CRC' flag ORA A ; 'CRC' in effect? JZ RCVCRC ; Yes, to receive 'CRC' ; ; Verify checksum ; MOV D,C ; Save checksum MVI B,1 ; Timeout length CALL RECV ; Get checksum JC RCVSTOT ; Timeout CMP D ; Checksum ok? JNZ RCVSERR ; No, error ; ; Got a record, it's a duplicate if = previous, or OK if = 1 + previous ; record. ; CHKSNUM:LDA RCVRNO ; Get received MOV B,A ; Save it LDA RECDNO ; Get previous CMP B ; Prev repeated? JZ RECVACK ; 'ACK' to catch up INR A ; Calculate next record number CMP B ; Match? JNZ ABORT ; No match - stop sender, exit RET ; Carry off - no errors ; ; Receive the Cyclic Redundancy Check characters (2 bytes) and see if ; the CRC received matches the one calculated. If they match, get next ; record, else send a NAK requesting the record be sent again. ; RCVCRC: MVI E,2 ; Number of bytes to receive ; RCVCRC2:MVI B,1 ; 1 sececond timeout CALL RECV ; Get crc byte JC RCVSTOT ; Timeout DCR E ; Decrement the number of bytes JNZ RCVCRC2 ; Get both bytes CALL CHKCRC ; Check received CRC against calc'd CRC ORA A ; Is CRC okay? JZ CHKSNUM ; Yes, go check record numbers JMP RCVSERR ; Go check error limit and send NAK ; ; Previous record repeated, due to the last ACK being garbaged. ACK it ; so sender will catch up ; RECVACK:CALL SENDACK ; Send the ACK JMP RCVRECD ; Get next block ; ; Send an ACK for the record ; SENDACK:MVI A,ACK ; Get 'ACK' CALL SEND ; And send it RET ; ; Send the record header ; ; Send [(SOH) or (STX)] (block number) (complemented block number) ; SENDHDR:LDA KFLAG ; 1k blocks enabled? ORA A JNZ SENDBIG ; Yes MVI A,SOH ; 128 blocks, use SOH JMP MORHDR ; Send it ; SENDBIG:MVI A,STX ; 1024 byte block - Start of Header ; MORHDR: CALL SEND ; One Start of Header or another LDA RECDNO ; Then send record number CALL SEND LDA RECDNO ; Then record number CMA ; Complemented CALL SEND ; Record number RET ; From SENDHDR ; ; Send the data record ; SENDREC:MVI C,0 ; Initialize checksum CALL CLRCRC ; Clear the 'CRC' counter LDA KFLAG ; Are we using 1K blocks? ORA A JNZ SEND1 ; Yes, 1k size LXI D,128 ; Initialize small count JMP SEND2 ; SEND1: LXI D,1024 ; Initialize big count ; SEND2: LHLD RECPTR ; Get buffer address ; SENDC: MOV A,M ; Get a character CALL SEND ; Send it INX H ; Point to next character DCX D ; Done? MOV A,D ORA E JNZ SENDC ; Loop if <=Blocksize RET ; From SENDREC ; ; Send the checksum ; SENDCKS:MOV A,C ; Send the CALL SEND ; Checksum RET ; From 'SENDCKS' ; ; Send the two Cyclic Redundancy Check characters. Call FINCRC to cal- ; culate the CRC which will be in 'DE' upon return. ; SENDCRC:CALL FINCRC ; Calculate the 'CRC' for this record MOV A,D ; Put first 'CRC' byte in accumulator CALL SEND ; Send it MOV A,E ; Put second 'CRC' byte in accumulator CALL SEND ; Send it XRA A ; Set zero return code RET ; ; Returns with carry clear if ACK received. If an ACK is not received, ; the error count is incremented, and if less than 10, carry is set and ; the record is resent. if the error count is 10, the program aborts. ; waits 12 seconds to avoid any collision with the receiving station. ; GETACK: MVI B,10 ; Wait 10 seconds max CALL RECVDG ; Receive with garbage collect JC ACKERR ; Timed out CPI ACK ; Was it an 'ACK' character? RZ ; Yes, return ; IF RETRY CPI NAK ; Was it an authentic 'NAK'? JNZ GETACK ; Ignore if neither 'ACK' nor 'NAK' ENDIF ; ; Timeout or error on ACK - bump error counters then resend the record ; if error limit is not exceeded. ; ACKERR: LDA ERRCT ; Get count INR A ; Bump it STA ERRCT ; Save back LHLD TOTERR ; Total errors this run INX H SHLD TOTERR ; Update and put back CPI 10 ; At limit? RC ; If not, go resend the record ; ; Reached error limit ; CALL ERXIT DB '++ Send file cancelled ++$' ; CHKERR: LDA KFLAG ORA A ; Check to see if in 1024 mode RZ ; No, so don't bother with rest LHLD TOTERR ; Check on errors to date... MOV A,L ; Skip if less than DWNSHFT error so far CPI DWNSHFT RC ; Not enough errors to bother with yet XCHG ; Total errors to DE LHLD RECDNO ; Get records sent so far CALL DVHLDE ; Divide by errors so far MOV A,C ; Take low order byte of quotient... CPI DWNSHFT ; Compare to specified ratio... RNC ; Better ratio than needed, so return XRA A ; Noisy line, let's try STA KFLAG ; 128 byte blocks RET ; ABORT: LXI SP,STACK ; ABORTL: MVI B,1 ; One second without characters CALL RECV JNC ABORTL ; Loop until sender done MVI A,CAN ; CTL-X CALL SEND ; Stop sending end ; ABORTW: MVI B,1 ; One second without chracters CALL RECV JNC ABORTW ; Loop until sender done MVI A,CR ; Get a space... CALL SEND ; To clear out CTL-X CALL ERXIT ; Exit with abort message DB '++ XMODEM aborted ++$' ; ; Increment record number ; INCRRNO:PUSH H LHLD RECDNO ; Increment record number INX H SHLD RECDNO LHLD VRECNO ; Update Virtual Record Number LDA KFLAG ; Was last record a 1024 byte one? ORA A ; JZ INCRR1 ; Just handled an normal 128 byte record INX H ; Otherwise, must have be a BIG one, so INX H ; Seven ... INX H INX H INX H INX H INX H ; Plus ; INCRR1: INX H ; One SHLD VRECNO ; Equals the new virtual record number ; IF NOT (USECON OR BYEBDOS) LHLD CONOUT+1 ; Check to see if showing count on crt MOV A,H ; If both zero, user did not fill out ORA L ; 'CONOUT: jmp 0000H' in patch area JZ INCRN5 ; With his own console output address ENDIF ; ; Display the record count on the local CRT if "CONOUT" was filled in by ; the implementor ; MVI A,1 STA CONONL ; Set local only LDA OPTSAV ; See if receive or send mode CPI 'R' JZ RMSG CALL ILPRT DB CR,'Sending # ',0 JMP REST ; RMSG: CALL ILPRT DB CR,'Received # ',0 ; REST: LDA KFLAG ORA A JZ REST1 LHLD VRECNO DCX H ; Stupid but simple way to subtract 7 DCX H ; Without dying on high-byte DCX H DCX H DCX H DCX H DCX H CALL DECOUT MVI A,'-' CALL CTYPE ; REST1: LHLD VRECNO ; Virtual record number to minimize CALL DECOUT ; Confusion between 1K and normal CALL ILPRT ; 'record' sizes (always in terms of DB ' ',18H,0 ; 128-byte records) ; IF CONFUN ; Check for sysop console function CALL FUNCHK ; Keys if CONFUN EQU YES ENDIF ; INCRN5: POP H ; Here from above if no CONOUT RET ; ; See if file exists - if it exists, ask for a different name. ; CHEKFIL: IF NOT SETAREA LDA PRVTFL ; Receiving in private area? ORA A CNZ RECAREA ; If yes, set drive and user area ENDIF ; IF SETAREA CALL RECAREA ; Set the designated area up ENDIF ; LXI D,FCB ; Point to control block MVI C,SRCHF ; See if it CALL BDOS ; Exists INR A ; Found? RZ ; No, return CALL ERXIT ; Exit, print error message DB '++ File exists, use a different name ++$' ; ; Makes the file to be received ; MAKEFIL:XRA A ; Set extent and record number to 0 STA FCBEXT STA FCBRNO LXI D,FCB ; Point to FCB MVI C,MAKE ; Get BDOS FNC CALL BDOS ; To the make INR A ; 0FFH=bad? RNZ ; Open ok ; ; Directory full - can't make file ; CALL ERXIT DB '++ Error: can''t make file -' DB ' directory may be full? ++$' ; ; Computes record count, and saves it until a successful file-open. ; CNREC: MVI C,CFSIZE ; Computes file size LXI D,FCB CALL BDOS ; Read first LHLD RANDOM ; Get the file size SHLD RCNT ; Save total record count MOV A,H ORA L RNZ ; Return if not zero length ; NONAME: CALL ERXIT DB '++ File not found, check DIR ++','$' ; ; Opens the file to be sent ; OPENFIL:XRA A ; Set extent and rec number to 0 STA FCBEXT ; For proper open STA FCBRNO LXI D,FCB ; Point to file MVI C,OPEN ; Get function CALL BDOS ; Open it INR A ; Open ok? JNZ OPENOK ; If yes, exit LDA OPTSAV ; Get command line option CPI 'L' ; Want to send a library file? JNZ NONAME ; Exit, if not CALL ILPRT DB CR,LF,'++ Member not found, check DIR ++',CR,LF,0 JMP OPTERR ; ; Check to see if the SYSOP has tagged a .LBR file for NO SEND - if so, ; only allow XMODEM L NAME to transfer individual files. If requested ; file is a $SYS file or has any high bits set, disallow unless WHEEL. ; OPENOK: IF ZCPR2 LDA WHEEL ; Check wheel status if ZCPR2 ORA A ; Is it zero JNZ OPENOK1 ; If non-zero skip all restrictions ENDIF ; IF DWNTAG LDA FCB+3 ; Regardless of access byte? ANI 80H ; If so, JNZ OPENOK1 ; Allow it if F3 set regardless ENDIF ; IF ACCESS CALL BYECHK JNZ SNDFOK LHLD 0001H ; Get JMP COLDBOOT DCX H MOV D,M DCX H MOV E,M LXI H,ACBOFF ; + ACBOFF DAD D MOV A,M ; = ACCESS byte address ANI ACDNLD ; Test download access bit JNZ SNDFOK ; If bit on, downloads OK CALL ERXIT DB 'Sorry, but you are not allowed to download files ' DB 'at this time...','$' ENDIF ; SNDFOK: IF NOSYS AND NOT LUXMOD LDA FCB+10 ANI 80H JNZ NONAME ; If $SYS then fake a "file not found" ENDIF ; IF OK2400 AND TAGLBR AND NOT LUXMOD LDA MSPEED ; Check baudrate byte set by BYE CPI 6 ; Is caller >=2400 baud? JNC OPENOK1 ; If so - let em send the file (PAT2) ENDIF ; IF TAGLBR AND NOT LUXMOD LDA OPTSAV ; Has SYSOP tagged a large .LBR file? CPI 'L' ; Using XMODEM L? JZ OPENOK1 ; Yes, skip tag test LDA FCB+1 ; First char of file name ANI 80H ; Check bit 7 for tag JZ OPENOK1 ; If on, file cannot be sent ENDIF ; IF TAGLBR AND NOT LUXMOD OPENOT: CALL ERXIT ; Exit with message DB '++ File is not for distribution, sorry. ++',CR,LF,CR,LF DB 'For large LBR files please use XMODEM L or LUX',CR,LF DB 'to transfer individual member files','$' ENDIF ; OPENOK1:LDA OPTSAV CPI 'L' JNZ OPN2 LXI D,TBUF MVI C,SETDMA CALL BDOS MVI C,READ LXI D,FCB CALL BDOS ORA A ; Read ok? JNZ LERROR LHLD TBUF+14 ; Value in buffer where DIRSIZE is SHLD DIRSZ LXI H,TBUF MOV A,M ORA A JZ CKDIR ; Check directory present? ; NOTLBR: CALL ERXIT DB '++ Bad .LBR directory, notify Sysop ++','$' ; ; Check to see if there is a .LBR file directory with that name and ; complain if not. ; CKDIR: MVI B,11 ; Maximum length of file name MVI A,' ' ; First entry must be all blanks INX H ; CKDLP: CMP M JNZ NOTLBR DCR B INX H JNZ CKDLP ; ; The first entry in the .LBR directory is indeed blank. Now see if the ; directory size is more than 0. ; MOV D,M ; Get directory starting location INX H ; Which must be 0000H... MOV A,M ORA D JNZ NOTLBR ; Directory does not start in record 0 INX H MOV A,M ; Get size of directory INX H ORA M JZ NOTLBR ; Directory must be >0 records! LXI H,TBUF ; Point to directory ; ; The next routine checks the .LBR directory for the specified member. ; Name one sector at a time. ; CMLP: MOV A,M ; Get member active flag ORA A ; 00=active, anything else can be... MVI B,11 ; Regarded as invalid (erased or blank) INX H ; Point to member name JNZ NOMTCH ; No match if inactive entry ; CKLP: LDAX D ; Now compare the file name specified... CMP M ; Against the member file name JNZ NOMTCH ; Exit loop if no match found INX H INX D DCR B JNZ CKLP ; Check all 11 characters MOV E,M ; Got the file - get file address INX H MOV D,M XCHG SHLD INDEX ; Save file address in .LBR XCHG INX H MOV E,M ; Get the file size INX H MOV D,M XCHG DCX H SHLD RCNT ; Save size a # of records LHLD INDEX ; Get file address SHLD RANDOM ; Place it into random field XRA A STA RANDOM+2 ; Must zero the 3rd byte STA FCBRNO ; Also zero FCB record # LXI D,FCB ; Point to FCB of .LBR file MVI C,RRDM ; Read random CALL BDOS JMP OPENOK3 ; No need to error check ; ; Come here if no file name match and another sector is needed ; NOMTCH: INX H ; Skip past the end of the file entry DCR B JNZ NOMTCH LXI B,20 ; Point to next file entry DAD B LXI D,MEMFCB ; Point to member name again MOV A,H ; See if we checked all 4 entries ORA A JZ CMLP ; No, check next LHLD DIRSZ ; Get directory size MOV A,H ORA L JNZ INLBR ; Continue if still more to check CALL ERXIT DB '++ Member not found, check DIR ++$' ; INLBR: DCX H ; Decrement dirctory size SHLD DIRSZ MVI C,READ ; Read next sector of directory LXI D,FCB CALL BDOS ORA A ; Read ok? JNZ LERROR LXI H,TBUF ; Set our pointers for compare LXI D,MEMFCB JMP CMLP ; Check next sector ; OPN2: IF ZCPR2 LDA WHEEL ; Check status of wheel if zcpr2 ORA A ; Is it zero JNZ OPENOK3 ; If not then skip the # and .com check ENDIF ; IF NOLBS OR NOCOMS ; Check for send restrictions LXI H,FCB+11 MOV A,M ; Check for protect attr ANI 7FH ; Remove CP/M 2.x attrs ENDIF ; IF NOLBS ; Do not allow '#' to be sent CPI '#' ; Chk for '#' as last first JNZ OPELOK ; If '#', can not send, show why CALL ERXIT DB '++ File not for distribution ++$' ; OPELOK: ENDIF ; IF NOCOMS ; Do not allow '.COM' to be sent CPI 'M' ; If not, check for '.COM' JNZ OPENOK3 ; If not, ok to send DCX H MOV A,M ; Check next character ANI 7FH ; Strip attributes CPI 'O' ; 'O'? JNZ OPENOK3 ; If not, ok to send DCX H MOV A,M ; Now check 1st character ANI 7FH ; Strip attributes CPI 'C' ; 'C' as in '.COM'? JNZ OPENOK3 ; If not, continue CALL ERXIT ; Exit with message DB '++ Sending .COM files not allowed ++$' ENDIF ; NOCOMS ; OPENOK3: IF NOT DSPFNAM CALL ILPRT ; Print the message DB 'File open: ',0 ENDIF ; IF DSPFNAM CALL ILPRT DB 'Sending: ',0 LDA OPTSAV CPI 'L' JNZ SFNNL ; If not L opt, just show name LXI H,MEMFCB CALL DSPFN CALL ILPRT DB ' from ',0 ; SFNNL: LXI H,FCB+1 CALL DSPFN CALL ILPRT DB CR,LF,'File size: ',0 ENDIF ; LHLD RCNT ; Get record count LDA OPTSAV CPI 'L' JNZ OPENOK4 ; If send from library add 1 to INX H ; Show correct record count ; OPENOK4:CALL CKKSIZ ; Check to see if it is at least 1K... CALL DECOUT ; Print decimal number of records PUSH H CALL ILPRT DB ' records (',0 POP H ; Get # of 128 byte records LXI D,8 ; Divide by 8 CALL DVHLDE ; To get # of 1024 byte blocks MOV A,H ORA L ; Check if remainder MOV H,B ; Get quotient MOV L,C JZ EXKB ; If 0 remainder, exact kilobytes INX H ; Else, increment to next k ; EXKB: CALL DECOUT ; Show # of kilobytes CALL ILPRT DB 'k)',CR,LF,0 CALL ILPRT DB 'Send time: ',0 CALL FILTIM ; Get file xfer time in mins in BC PUSH H ; Save seconds in HL ; IF ZCPR2 AND MAXTIM LDA WHEEL ; Check wheel status if zcpr2 ORA A ; Is it zero JNZ SKIPTIM ; If its not then skip the limit ENDIF ; IF OK2400 ; No restrictions for 2400 bps callers? LDA MSPEED ; Check baudrate byte set by BYE CPI 6 ; Is >=2400? JNC SKIPTIM ; If so, skip time check ENDIF ; IF MAXTIM MOV A,C ; If limiting get length of this program INR A ; Increment to next full minute ENDIF ; IF MAXTIM AND TIMEON LXI H,TON ADD M ; Add time on to xfer time, TON will ENDIF ; IF MAXTIM STA MINUTE ; Store value for later comparison MOV A,B ; Get high byte of minute if >255 JNZ MXTMC2 ; If no carry from increment/add INR A ; MXTMC2: STA MINUTE+1 ENDIF ; SKIPTIM:MOV L,C MOV H,B CALL DECOUT ; Print decimal number of minutes MVI A,':' CALL CTYPE ; Output colon POP H ; Get seconds MOV A,L CPI 10 MVI A,'0' ; Needs a leading zero CC CTYPE CALL DECOUT ; Print the seconds portion CALL ILPRT DB ' at ',0 LXI H,SPTBL ; Start of baud rate speeds MVI D,0 ; Zero the 'D' register CALL SPEED ; Get speed indicator ADD A ; Index into the baud rate table ADD A MOV E,A ; Now have the index factor in 'DE' DAD D ; Add to 'HL' XCHG ; Put address in 'DE' regs. MVI C,PRINT ; Show the baud CALL BDOS CALL SPEED CPI 5 MVI A,'0' ; Adds a zero for 1200, 2400, 4800 and CNC CTYPE ; 9600 bps ; OPENOK5:CALL ILPRT DB ' baud',CR,LF,0 ; IF ZCPR2 AND MAXTIM LDA WHEEL ; Check wheel status if zcpr2 ORA A ; Is it zero JNZ SKIPEM ; If not then no time limits ENDIF ; IF MAXTIM AND (BYEBDOS OR MXTOS) LDA MAXTOS ; Get maximum time on system ORA A ; If zero, this guy is a winner JZ SKIPEM ; (skip restrictions) LDA MINUTE+1 ; Is it over 255 minutes? ORA A JNZ OVERTM ENDIF ; IF MTL CALL GETTOS ; Get time on system in HL ENDIF ; IF MAXTIM AND BYEBDOS AND (NOT TIMEON) MVI C,BDGRTC ; Get time on system in A CALL BDOS MOV B,A ; Put in B ENDIF ; IF MAXTIM AND (BYEBDOS OR MXTOS) LDA MAXTOS INR A ENDIF ; IF MAXTIM AND BYEBDOS AND (NOT TIMEON) SUB B ENDIF ; IF MTL SUB L ; Get how much time is left ADI MAXMIN ; Give them MAXMIN extra ENDIF ; IF MAXTIM AND (BYEBDOS OR MXTOS) MOV B,A ; Put max time on sys in B LDA MINUTE ; Are we > max time on sys? CMP B JNC OVERTM ENDIF ; IF MAXTIM AND NOT (BYEBDOS OR MXTOS) LDA MINUTE+1 ; Get minute count high byte ORA A ; Check if zero JNZ OVERTM ; If not, is over 255 minutes! LDA MINUTE ; Get minute count CPI MAXMIN+1 ; Compare to MAXTIM value JNC OVERTM ; If greater than MAXTIM ENDIF ; SKIPEM: CALL ILPRT DB 'To cancel: Ctrl-X, pause, Ctrl-X',CR,LF,0 RET ; IF MAXTIM OVERTM: CALL ILPRT DB CR,LF,'++ XMODEM ABORTED - send time exceeds the ',0 ENDIF ; IF MAXTIM AND NOT (BYEBDOS OR MXTOS) LXI H,MAXMIN ENDIF ; IF MAXTIM AND BYEBDOS MVI C,BDGRTC CALL BDOS MOV B,A ENDIF ; IF MTL CALL GETTOS ; Get TOS back into HL ENDIF ; IF MAXTIM AND (BYEBDOS OR MXTOS) LDA MAXTOS ENDIF ; IF MAXTIM AND BYEBDOS SUB B ENDIF ; IF MTL SUB L ; Get time left ADI MAXMIN ; Add MAXMIN ENDIF ; IF MAXTIM AND (BYEBDOS OR MXTOS) MVI H,0 MOV L,A ENDIF ; IF MAXTIM CALL DECOUT CALL ERXIT1 DB ' minutes allowed ++$' ENDIF ; BTABLE: IF NOT STOPBIT ; One stop bit DW 5,13,19,25,30,48,85,141,210,280,0 ENDIF ; IF STOPBIT ; Two stop bits DW 5,12,18,23,27,44,78,128,191,255,0 ENDIF ; KTABLE: IF NOT STOPBIT ; One stop bit DW 5,14,21,27,32,53,101,190,330,525,0 ENDIF ; IF STOPBIT ; Two stop bits DW 5,13,19,25,29,48,92,173,300,477,0 ENDIF ; RECTBL: IF NOT STOPBIT ; One stop bit DB 192,74,51,38,32,20,11,8,5,3,0 ENDIF ; IF STOPBIT ; Two stop bits DB 192,80,53,42,36,22,12,7,5,4,0 ENDIF ; KECTBL: IF NOT STOPBIT ; One stop bit DB 192,69,46,36,30,18,10,5,3,2,0 ENDIF ; IF STOPBIT ; Two stop bits DB 192,74,51,38,33,20,10,6,3,2,0 ENDIF ; SPTBL: DB '110$','300$','450$','600$','710$','120$','240$' DB '480$','960$','1920$' ; ; Pass record count in RCNT: returns file's approximate download/upload ; time in minutes in BC, seconds in HL, also stuffs the # of mins/secs ; values in PGSIZE if LOGCAL is YES. ; FILTIM: CALL SPEED ; Get speed indicator MVI D,0 MOV E,A ; Set up for table access LXI H,BTABLE ; Point to baud factor table LDA KFLAG CPI 'K' JNZ FILTI1 LXI H,KTABLE ; The guy is using 1k file xfers ; FILTI1: DAD D ; Index to proper factor DAD D MOV E,M INX H MOV D,M LHLD RCNT ; Get number of records LDA OPTSAV CPI 'L' ; If not L download JNZ SKINCR ; Skip increment of record count INX H ; Increment record count ; SKINCR: CALL DVHLDE ; Divide HL by value in DE (records/min) PUSH H ; Save remainder LXI H,RECTBL ; Point to divisors for seconds calc. LDA KFLAG CPI 'K' JNZ FILTI2 LXI H,KECTBL ; The guy is using 1k file transfers ; FILTI2: MVI D,0 CALL SPEED ; Get speed indicator MOV E,A DAD D ; Index into table MOV A,M ; Get multiplier POP H ; Get remainder CALL MULHLA ; Multiply 'H' by 'A' CALL SHFTHL CALL SHFTHL CALL SHFTHL CALL SHFTHL MVI H,0 ; HL now = seconds (L=secs,H=0) ; IF LOGCAL MOV A,C ; Add minutes of length (to 0 or 1) STA PGSIZE ; Save as LSB of minutes MOV A,B ; Get MSB of minutes STA PGSIZE+1 ; Save as MSB of minutes (>255?) MOV A,L ; Get LSB of seconds (can't be >59) STA PGSIZE+2 ; Save for LOGCALL ENDIF ; RET ; End of FILTIM routine ; ; Divides 'HL' by value in 'DE' - upon exit: BC=quotient, HL=remainder ; DVHLDE: PUSH D ; Save divisor MOV A,E CMA ; Negate divisor MOV E,A MOV A,D CMA MOV D,A INX D ; 'DE' is now two's complemented LXI B,0 ; Init quotient ; DIVL1: DAD D ; Subtract divisor from divident INX B ; Bump quotient JC DIVL1 ; Loop until sign changes DCX B ; Adjust quotient POP D ; Retrieve divisor DAD D ; Readjust remainder RET ; ; Multiply the value in 'HL' by the value in 'A', return with answer in ; 'HL'. ; MULHLA: XCHG ; Multiplicand to 'DE' LXI H,0 ; Init product INR A ; MULLP: DCR A RZ DAD D JMP MULLP ; ; Shift the 'HL' register pair one bit to the right ; SHFTHL: MOV A,L RAR MOV L,A ORA A ; Clear the carry bit MOV A,H RAR MOV H,A RNC MVI A,128 ORA L MOV L,A RET ; ; Closes the received file ; CLOSFIL:LXI D,FCB ; Point to file MVI C,CLOSE ; Get function CALL BDOS ; Close it INR A ; Close ok? JNZ CLSEXIT ; Yes, continue CALL ERXIT ; No, abort DB '++ Can''t close file ++$' ; CLSEXIT: IF SYSNEW LDA FCB+10 ; Set $SYS attribute ORI 80H STA FCB+10 LXI D,FCB ; Point to file MVI C,SETATT ; Set attribute function CALL BDOS ENDIF ; RET ; ; Decimal output routine - call with decimal value in 'HL' ; DECOUT: PUSH B PUSH D PUSH H LXI B,-10 LXI D,-1 ; DECOU2: DAD B INX D JC DECOU2 LXI B,10 DAD B XCHG MOV A,H ORA L CNZ DECOUT MOV A,E ADI '0' CALL CTYPE POP H POP D POP B RET ; ; Makes sure there are enough records to send. For speed, this routine ; buffers up 16 records at a time. ; RDRECD: LDA KFLAG ; Check for 1024 byte records ORA A JNZ RDRECDK ; Using 1K blocks ; NOTKAY: LDA RECNBF ; Get number of records in buffer DCR A ; Decrement it JM RDBLOCK ; Exhausted? need more ORA A ; Otherwise, clear carry and... RET ; From 'RDRECD' ; RDRECDK:LDA RECNBF ; Get number of records in buffer ORA A ; Any records in buffer? JZ RDBLOCK ; Nope, get more SUI 8 ; Decrement count of records RNC ; 8 or more left XRA A ; Less than 8 left STA KFLAG ; Revert to 128 blocks JMP NOTKAY ; Continue with short blocks ; ; Update buffer pointers and counters AFTER sending a good block. ; UPDPTR: LDA KFLAG ORA A JNZ BIG LXI D,128 ; Small pointer increment MVI B,1 ; Small sector number JMP UPDPTR1 ; BIG: LXI D,1024 ; Big pointer increment MVI B,8 ; Number of sectors in big block ; UPDPTR1:LDA RECNBF ; Update buffer sector count SUB B STA RECNBF LHLD RECPTR ; Get buffer address DAD D ; To next buffer SHLD RECPTR ; Save buffer address RET ; ; Buffer is empty - read in another block of 16 ; RDBLOCK:LDA EOFLG ; Get 'EOF' flag CPI 1 ; Is it set? STC ; To show 'EOF' RZ ; Got 'EOF' MVI C,0 ; Records in block LXI D,DBUF ; To disk buffer ; RDRECLP:PUSH B PUSH D MVI C,SETDMA ; Set DMA address CALL BDOS LXI D,FCB MVI C,READ CALL BDOS POP D POP B ORA A ; Read ok? JZ RDRECOK ; Yes DCR A ; 'EOF'? JZ REOF ; Got 'EOF' ; ; Read error ; LERROR: CALL ERXIT DB '++ File read error ++$' ; RDRECOK:LXI H,128 ; Add length of one record DAD D ; To next buffer XCHG ; Buffer to 'DE' INR C ; More records? MOV A,C ; Get count CPI BUFSIZ*8 ; Done? JZ RDBFULL ; Yes, buffer is full JMP RDRECLP ; Read more ; REOF: MVI A,1 STA EOFLG ; Set EOF flag MOV A,C ; ; Buffer is full, or got EOF ; RDBFULL:STA RECNBF ; Store record count LXI H,DBUF ; Init buffer pointear SHLD RECPTR ; Save buffer address LXI D,TBUF ; Reset DMA address MVI C,SETDMA CALL BDOS JMP RDRECD ; Pass record to caller ; ; Writes the record into a buffer. When 16 have been written, writes ; the block to disk. ; ; Entry point "WRBLOCK" flushes the buffer at EOF ; WRRECD: LHLD BLKSIZ ; Get length of last record XCHG ; Get ready for add LHLD RECPTR ; Get buffer address DAD D ; To next buffer SHLD RECPTR ; Save buffer address XCHG ; Move BLKSIZ to HL CALL SHFTHL ; Divide by 128 to get recors CALL SHFTHL CALL SHFTHL CALL SHFTHL CALL SHFTHL CALL SHFTHL CALL SHFTHL LDA RECNBF ; Bump the records number in the buffer ADD L STA RECNBF CPI BUFSIZ*8 ; Equal to, or past 'end' of buffer? RC ; No, return ; ; Writes a block to disk ; WRBLOCK:LDA RECNBF ; Number of records in the buffer ORA A ; 0 means end of file RZ ; None to write MOV C,A ; Save count LXI D,DBUF ; Point to disk buff ; DKWRLP: PUSH H PUSH D PUSH B MVI C,SETDMA ; Set DMA CALL BDOS ; To buffer LXI D,FCB ; Then write the block MVI C,WRITE CALL BDOS POP B POP D POP H ORA A JNZ WRERR ; Oops, error LXI H,128 ; Length of 1 record DAD D ; 'HL'= next buff XCHG ; To 'DE' for setdma DCR C ; More records? JNZ DKWRLP ; Yes, loop XRA A ; Get a zero STA RECNBF ; Reset number of records LXI H,DBUF ; Reset buffer buffer SHLD RECPTR ; Save buffer address ; RSDMA: LXI D,TBUF ; Reset DMA address MVI C,SETDMA CALL BDOS RET ; WRERR: CALL RSDMA ; Reset DMA to normal MVI C,CAN ; Cancel CALL SEND ; Sender CALL RCVSABT ; Kill receive file CALL ERXIT ; Exit with msg: DB '++ Error writing file ++$' ; ; Receive a character - timeout time is in 'B' in seconds. Entry via ; 'RECVDG' deletes garbage characters on the line. For example, having ; just sent a record calling 'RECVDG' will delete any line-noise-induced ; characters "long" before the ACK/NAK would be received. ; RECVDG: CALL GETCHR CALL GETCHR ; RECV: PUSH D ; Save 'DE' regs. MVI E,MHZ ; Get the clock speed XRA A ; Clear the 'A' reg. ; MSLOOP: ADD B ; Number of seconds DCR E ; One less mhz. to go JNZ MSLOOP ; If not zero, continue MOV B,A ; Put total value back into 'B' ; MSEC: IF NOT BYEBDOS LXI D,6600 ; 1 second DCR count ENDIF ; IF BYEBDOS LXI D,2800 ; (includes BYEBDOS overhead) ENDIF ; MWTI: CALL RCVRDY ; Input from modem ready JZ MCHAR ; Got the character DCR E ; Count down for timeout JNZ MWTI DCR D JNZ MWTI DCR B ; More seconds? JNZ MSEC ; Yes, wait ; ; Test for the presence of carrier - if none, go to 'CARCK' and continue ; testing for specified time. If carrier returns, continue. If it does ; not return, exit. ; CALL CAROK ; Is carrier still on? CNZ CARCK ; If not, test for 15 seconds ; ; Modem timed out receiving - but carrier is still on. ; POP D ; Restore 'DE' STC ; Carry shows timeout RET ; ; Get character from modem. ; MCHAR: CALL MDIN ; Get data byte from modem POP D ; Restore 'DE' ; ; Calculate checksum and CRC ; PUSH PSW ; Save the character CALL UPDCRC ; Calculate CRC ADD C ; Add to checksum MOV C,A ; Save checksum POP PSW ; Restore the character ORA A ; Carry off: no error RET ; From 'RECV' ; ; Common carrier test for receive and send. If carrier returns within ; TIMOUT seconds, normal program execution continues. Else, it will ; abort to CP/M via EXIT. ; CARCK: MVI E,TIMOUT*10 ; Value for 15 second delay ; CARCK1: CALL DELAY ; Kill .1 seconds CALL CAROK ; Is carrier still on? RZ ; Return if carrier on DCR E ; Has 15 seconds expired? JNZ CARCK1 ; If not, continue testing ; ; See if got a local console, and report if so. ; IF NOT (USECON OR BYEBDOS) LHLD CONOUT+1 ; Get conout address MOV A,H ; Zero if no local console ORA L JZ CARCK2 ENDIF ; MVI A,1 ; Print local only STA CONONL CALL ILPRT ; Report loss of carrier DB CR,LF,'++ Carrier lost in XMODEM ++',CR,LF,0 ; CARCK2: LDA OPTSAV ; Get option CPI 'R' ; If not receive JNZ EXIT ; Then abort now, else CALL DELFILE ; Get rid of the junk first JMP EXIT ; Else, abort to CP/M ; ; Delay - 100 millisecond delay. ; DELAY: PUSH B ; Save 'BC' LXI B,MHZ*4167 ; Value for 100 ms. delay ; DELAY2: DCX B ; Update count MOV A,B ; Get MSP byte ORA C ; Count = zero? JNZ DELAY2 ; If not, continue POP B ; Restore 'BC' RET ; Return to CARCK1 ; ;----------------------------------------------------------------------- ; ; Tells user to add description of an uploaded file ; IF DESCRIB ASK: LDA OPTSAV ; Get the option CPI 'R' RNZ ; If not receiving a file, exit LDA PRVTFL ; Sending to "private area"? ORA A RNZ ; If yes, do not ask for description ENDIF ; IF DESCRIB AND ZCPR2 AND (NOT ASKSYS) LDA WHEEL ORA A RNZ ENDIF ; IF DESCRIB MVI B,2 ; Short delay to wait for an input char. CALL RECV ENDIF ; IF DESCRIB AND ASKIND ASK1: CALL DELAY CALL SHONM ; Show the file name CALL DILPRT DB ' - this file is for:',CR,LF,CR,LF,0 MVI C,PRINT ; Display the file descriptors LXI D,KIND0 CALL BDOS CALL DILPRT DB CR,LF,'Select one: ',0 CALL INPUT ; Get a character CALL TYPE CPI '0' JC ASK1 CPI '9'+1 JNC ASK1 STA KIND ENDIF ; IF DESCRIB AND (NOT ASKIND) ASK1: CALL DELAY CALL SHONM ENDIF ; IF DESCRIB ASK2: LXI H,0 SHLD OUTPTR ; Initialize the output pointers CALL DILPRT DB CR,LF,CR,LF DB 'Please describe this file (7 lines or less). Tell ' DB 'what equipment can use',CR,LF,'it and what the ' DB 'program does. Extra RET to quit.',CR,LF,CR,LF,0 CALL SENBEL ; ; Get the file name from FCB, skip any blanks ; LXI H,HLINE CALL DSTOR1 MVI B,8 ; Get FILENAME LXI D,FCB+1 LXI H,OLINE CALL LOPFCB MVI M,'.' MOV A,M ; Separate FILENAME and EXTENT CALL TYPE INX H MVI B,3 ; Get EXTENT name CALL LOPFCB ENDIF ; IF DESCRIB AND ASKIND AFIND1: LDA KIND CPI '0' ; File category 0 LXI D,KIND0+4 CZ DKIND ; File category 1 CPI '1' LXI D,KIND1+4 CZ DKIND ; File category 1 CPI '2' LXI D,KIND2+4 CZ DKIND ; File category 2 CPI '3' LXI D,KIND3+4 CZ DKIND ; File category 3 CPI '4' LXI D,KIND4+4 CZ DKIND ; File category 4 CPI '5' LXI D,KIND5+4 CZ DKIND ; File category 5 CPI '6' LXI D,KIND6+4 CZ DKIND ; File category 6 CPI '7' LXI D,KIND7+4 CZ DKIND ; File category 7 CPI '8' LXI D,KIND8+4 CZ DKIND ; File category 8 CPI '9' LXI D,KIND9+4 CZ DKIND ; File category 9 ENDIF ; DESCRIB AND ASKIND ; IF DESCRIB AND (NOT ASKIND) MVI M,CR INX H MVI M,LF ENDIF ; IF DESCRIB CALL DSTOR ; Put FILENAME line into memory CALL DILPRT DB CR,LF,CR,LF,'0: ---------1---------2---------3' DB '---------4---------5---------6---------',CR,LF,0 XRA A STA ANYET ; Reset the flag for no information yet MVI C,'0' ; EXPLN: INR C MOV A,C CPI '7'+1 JNC EXPL1 CALL TYPE MVI A,' ' CALL OUTCHR CALL OUTCHR CALL OUTCHR CALL DILPRT DB ': ',0 CALL DESC ; Get a line of information CALL DSTOR JMP EXPLN ; EXPL1: MVI A,CR ; All finished, put in an extra CR-LF CALL OUTCHR MVI A,LF CALL OUTCHR MVI A,'$' CALL OUTCHR CALL DILPRT DB ' Repeating to verify:',CR,LF,CR,LF,0 LHLD OUTADR XCHG MVI C,PRINT CALL BDOS LHLD OUTPTR DCX H SHLD OUTPTR ; EXPL2: CALL DILPRT DB CR,LF,'Is this ok (Y/N)? ',0 CALL INPUT CALL TYPE ; Display answer ANI 5FH ; Change to upper case CPI 'N' JZ ASK1 ; If not, do it over CPI 'Y' JNZ EXPL2 ; If yes, finish up, else ask again ; ; Now open the file and put this at the beginning ; EXPL3: LDA 0004H ; Get current drive/user STA DRUSER ; Store ; ; Set drive/user to the area listed above ; MVI E,USER ; Set user to WHATSFOR.TXT area MVI C,SETUSR CALL BDOS MVI A,DRIVE ; Set drive to WHATSFOR.TXT area SUI 41H MOV E,A MVI C,SELDSK CALL BDOS ; ; Open source file ; CALL DILPRT DB CR,LF,0 LXI D,FILE ; Open WHATSFOR.TXT file MVI C,OPEN CALL BDOS INR A ; Check for no open JNZ OFILE ; File exists, exit MVI C,MAKE ; None exists, make a new file LXI D,FILE CALL BDOS INR A JZ NOROOM ; Exit if cannot open new file ; OFILE: LXI H,FILE ; Otherwise use same filename LXI D,DEST ; With .$$$ extent for now MVI B,9 CALL MOVE ; ; Open the destination file ; XRA A STA DEST+12 STA DEST+32 LXI H,BSIZE ; Get Buffer allocated size SHLD OUTSIZ ; Set for comparison MVI C,DELET ; Delete any existing file that name LXI D,DEST CALL BDOS MVI C,MAKE ; Now make a new file that name LXI D,DEST CALL BDOS INR A JZ NOROOM ; Cannot open file, no directory room CALL DILPRT DB CR,LF,'Wait a moment...',0 ; ; Read sector from source file ; READLP: LXI D,TBUF MVI C,SETDMA CALL BDOS LXI D,FILE ; Read from WHATSFOR.TXT MVI C,READ CALL BDOS ORA A ; Read ok? JNZ RERROR LXI H,TBUF ; Read buffer address ; ; Write sector to output file (with buffering) ; WRDLOP: MOV A,M ; Get byte from read buffer ANI 7FH ; Strip parity bit CPI 7FH ; Del (rubout)? JZ NEXT ; Yes, ignore it CPI EOF ; End of file marker? JZ TDONE ; Transfer done, close, exit CALL OUTCHR ; NEXT: INR L ; Done with sector? JZ READLP ; If yes get another sector JMP WRDLOP ; No, get another byte ; ; Handle a backspace character while entering a character string ; BCKSP: CALL TYPE MOV A,B ; Get position on line ORA A JNZ BCKSP1 ; Exit if at initial column CALL SENBEL ; Send a bell to the modem MVI A,' ' ; Delete the character JMP BCKSP3 ; BCKSP1: DCR B ; Show one less column used DCX H ; Decrease buffer location MVI A,' ' MOV M,A ; Clear memory at this point CALL TYPE ; Backspace the "CRT" ; BCKSP2: MVI A,BS ; Reset the "CRT" again ; BCKSP3: CALL TYPE ; Write to the "CRT" RET ; ; Asks for line of information ; DESC: MVI B,0 LXI H,OLINE ; DESC1: CALL INPUT ; Get keyboard character CPI CR JZ DESC4 CPI TAB JZ DESC6 CPI BS JNZ DESC2 CALL BCKSP JMP DESC1 ; Get the next character ; DESC2: CPI ' ' JC DESC1 ; If non-printing character, ignore JZ DESC3 ; If a space, continue STA ANYET ; Show a character has been sent now ; DESC3: MOV M,A CALL TYPE ; Display the character INX H INR B MOV A,B CPI 70 ; Do not exceed line length JC DESC1 CALL SENBEL ; Send a bell to the modem CALL BCKSP2 CALL BCKSP1 ; Do not allow a too-long line JMP DESC1 ; DESC4: LDA ANYET ; Any text typed on first line yet? ORA A JNZ DESC5 ; If yes, exit POP H JMP ASK1 ; Ask again for a description ; DESC5: MVI M,CR MOV A,M CALL TYPE INX H ; Ready for next character MVI M,LF MOV A,M CALL TYPE ; Display the line feed INX H MOV A,B ; See if at first of line ORA A RNZ ; If not, ask for next line POP H ; Clear "CALL" from stack JMP EXPL1 ; DESC6: MOV A,B ; At end of line now? CPI 68 JNC DESC1 ; If yes, disregard MVI M,' ' MOV A,M CALL TYPE INX H INR B MOV A,B ANI 7 JNZ DESC6 JMP DESC1 ; Ask for next character ; DSTOR: LXI H,OLINE ; DSTOR1: MOV A,M CALL OUTCHR CPI LF RZ INX H JMP DSTOR1 ; ; Print message then exit to CP/M ; DEXIT: POP D ; Get message address MVI C,PRINT ; Print message CALL BDOS CALL RESET ; Reset the drive/user JMP EXIT ; all done ; ; Inline print routine - prints string pointed to by stack until a zero ; is found. Returns to caller at the next address after the zero ter- ; minator. ; DILPRT: XTHL ; Save hl, get message address ; DILPLP: MOV A,M ; Get char CALL TYPE ; Output it INX H ; Point to next MOV A,M ; Test ORA A ; For end JNZ DILPLP XTHL ; Restore hl, ret address RET ; Return past the end of the message ; ; ; Disk is full, save original file, erase others. ; FULL: MVI C,DELET LXI D,DEST CALL BDOS CALL DEXIT DB CR,LF,'++ DISK FULL, ABORTING, SAVING ORIGINAL FILE','$' ; ; Get a character, if none ready wait up to 3 minutes, then abort pgm ; INPUT: PUSH H ; Save current values PUSH D PUSH B ; INPUT1: LXI D,1200 ; Outer loop count (about 2 minutes) ; INPUT2: LXI B,MHZ*100 ; Roughly 100 ms. ; INPUT3: PUSH D ; Save the outer delay count PUSH B ; Save the inner delay count MVI E,0FFH MVI C,DIRCON ; Get console status CALL BDOS ANI 7FH POP B ; Restore the inner delay count POP D ; Restore the outer delay count ORA A ; Have a character yet? JNZ INPUT4 ; If yes, exit and get it DCX B MOV A,C ; See if inner loop is finished ORA B JNZ INPUT3 ; If not loop again DCX D MOV A,E ORA D JNZ INPUT2 ; If not reset inner loop and go again MVI A,CR CALL OUTCHR MVI A,LF CALL OUTCHR LXI SP,STACK ; Restore the stack CALL EXPL3 ; Finish appending previous information JMP EXIT ; Finished ; INPUT4: POP B POP D POP H RET ; ; Stores the Filename/extent in the buffer temporarily ; LOPFCB: LDAX D ; Get FCB FILENAME/EXT character CPI ' '+1 JC LOPF1 MOV M,A ; Store in OLINE area CALL TYPE ; Display on CRT INX H ; Next OLINE position ; LOPF1: INX D ; Next FCB position DCR B ; One less to go JNZ LOPFCB ; If not done, get next one RET ; ; No room to open a new file ; NOROOM: CALL DEXIT DB CR,LF,'++ No DIR space: output ++$' ; ; Output error - cannot close destination file ; OERROR: CALL DEXIT DB CR,LF,'++ Cannot close output ++$' ; ; Output a character to the new file buffer - first, see if there is ; room in the buffer for this character. ; OUTCHR: PUSH H PUSH PSW ; Store the character for now LHLD OUTSIZ ; Get buffer size XCHG ; Put in 'DE' LHLD OUTPTR ; Now get the buffer pointers MOV A,L ; Check to see if room in buffer SUB E MOV A,H SBB D JC OUT3 ; If room, go store the character LXI H,0 ; Otherwise reset the pointers SHLD OUTPTR ; Store the new pointer address ; OUT1: XCHG ; Put pointer address into 'DE' LHLD OUTSIZ ; Get the buffer size into 'HL' MOV A,E ; See if buffer is max. length yet SUB L ; By subtracting 'HL' from 'DE' MOV A,D SBB H JNC OUT2 ; If less, exit and keep going ; ; No more room in buffer, stop and transfer to destination file ; LHLD OUTADR ; Get the buffer address DAD D ; Add pointer value XCHG ; Put into 'DE' MVI C,SETDMA CALL BDOS LXI D,DEST MVI C,WRITE CALL BDOS ORA A JNZ FULL ; Exit with error, if disk is full now LXI D,RLEN LHLD OUTPTR DAD D SHLD OUTPTR JMP OUT1 ; OUT2: LXI D,TBUF MVI C,SETDMA CALL BDOS LXI H,0 SHLD OUTPTR ; OUT3: XCHG LHLD OUTADR DAD D XCHG POP PSW ; Get the character back STAX D ; Store the character LHLD OUTPTR ; Get the buffer pointer INX H ; Increment them SHLD OUTPTR ; Store the new pointer address POP H RET ; RERROR: CPI 1 ; File finished? JZ TDONE ; Exit, then MVI C,DELET ; Erase destination file, keep original LXI D,DEST CALL BDOS CALL DEXIT DB '++ Source file read error ++$' ; ; Reset the Drive/User to original, then back to original caller ; RESET: LDA DRUSER ; Get original drive/user area back RAR RAR RAR RAR ANI 0FH ; Just look at the user area MOV E,A MVI C,SETUSR ; Restore original user area CALL BDOS LDA DRUSER ; Get the original drive/user back ANI 0FH ; Just look at the drive for now MOV E,A MVI C,SELDSK ; Restore original drive CALL BDOS CALL DILPRT ; Print CRLF before quitting DB CR,LF,0 RET ; Return to caller (Not JMP EXIT1) ; ; Send a bell just to the modem ; SENBEL: CALL SNDRDY ; Is modem ready for another character? JNZ SENBEL ; If not, wait MVI A,7 PUSH PSW ; Overlay has the "POP PSW" JMP SENDR ; Send to the modem only ; ;..... ; ; ; Shows the Filename/extent ; SHONM: CALL DILPRT DB CR,LF,CR,LF,0 LXI H,FCB+1 MVI B,8 ; Maximum size of file name CALL SHONM1 MOV A,M ; Get the next character CPI ' ' ; Any file extent? RZ ; If not, finished MVI A,'.' CALL TYPE MVI B,3 ; Maximum size of file extent ; SHONM1: MOV A,M ; Get FCB FILENAME/EXT character CPI ' '+1 ; Skip any blanks JC $+6 CALL TYPE ; Display on CRT INX H ; Next FCB position DCR B ; One less to go JNZ SHONM1 ; If not done, get next one RET ;..... ; ; Transfer is done - close destination file ; TDONE: LHLD OUTPTR MOV A,L ANI RLEN-1 JNZ TDONE1 SHLD OUTSIZ ; TDONE1: MVI A,EOF ; Fill remainder of record with ^Z's PUSH PSW CALL OUTCHR POP PSW JNZ TDONE MVI C,CLOSE ; Close WHATSFOR.TXT file LXI D,FILE CALL BDOS MVI C,CLOSE ; Close WHATSFOR.$$$ file LXI D,DEST CALL BDOS INR A JZ OERROR ; ; Rename both files as no destination file name was specified ; LXI H,FILE+1 ; Prepare to rename old file to new LXI D,DEST+17 MVI B,16 CALL MOVE MVI C,DELET ; Delete original WHATSFOR.TXT file LXI D,FILE CALL BDOS LXI D,DEST ; Rename WHATSFOR.$$$ to WHATSFOR.TXT MVI C,RENAME CALL BDOS JMP RESET ; Reset the drive/user, back to caller ; TYPE: PUSH B PUSH D PUSH H PUSH PSW MOV E,A ; Character to 'E' for CP/M MVI C,WRCON ; Write to console CALL BDOS POP PSW POP H POP D POP B RET ENDIF ; DESCRIB ; IF DESCRIB AND ASKIND DKIND: LDAX D ; Get the character from the string CALL TYPE ; Otherwise display the character MOV M,A ; Put in the buffer CPI LF ; Done yet? JZ DKIND1 ; Exit if a LF, done INX D ; Next position in the string INX H ; Next postion in the buffer JMP DKIND ; Keep going until a LF ; DKIND1: LDA KIND ; Get the kind of file back RET ; Finished ENDIF ;..... ; ;----------------------------------------------------------------------- ; ; Send a character to the modem ; SEND: PUSH PSW ; Save the character CALL UPDCRC ; Calculate CRC ADD C ; Calcculate checksum MOV C,A ; Save cksum ; SENDW: CALL SNDRDY ; Is transmit ready JZ SENDR ; Yes, go send ; ; Xmit status not ready, so test for carrier before looping - if lost, ; go to CARCK and give it up to 15 seconds to return. If it doesn't, ; return abort via EXIT. ; PUSH D ; Save 'DE' CALL CAROK ; Is carrier still on? CNZ CARCK ; If not, continue testing it POP D ; Restore 'DE' JMP SENDW ; Else, wait for xmit ready ; ; Waits for initial NAK - to ensure no data is sent until the receiving ; program is ready, this routine waits for the first timeout-nak or the ; letter 'C' for CRC from the receiver. If CRC is in effect then Cyclic ; Redundancy Checks are used instead of checksums. 'E' contains the ; number of seconds to wait. If the first character received is a CAN ; (CTL-X) then the send will be aborted as though it had timed out. ; Since 1K extensions require CRC, KFLAG is set to NULL if the receiver ; requests checksum ; WAITNAK: IF CONFUN ; Check for Sysop function key? CALL FUNCHK ; Yeah, go ahead.. Twit? ENDIF ; IF CONFUN AND SYSABT LDA SYSABF ; If SYSABT option, check ORA A ; to see if Abort JNZ ABORT ; If so, bail out now... ENDIF ; MVI B,1 ; Timeout delay CALL RECV ; Did we get CPI 'K' ; Did he send a "K" first? JZ SET1KX CPI CRC ; 'CRC' indicated? JZ SET1K ; Yes, send block CPI NAK ; A 'NAK' indicating checksum? JZ SETNAK ; Yes go put checksum in effect CPI CAN ; Was it a cancel (CTL-X)? JZ ABORT ; Yes, abort DCR E ; Finished yet? JZ ABORT ; Yes, abort JMP WAITNAK ; No, loop ; ; Turn on checksum flag ; SETNAK: XRA A STA KFLAG ; Make sure transfer uses small blocks MVI A,'C' ; Change to checksum STA CRCFLG RET ; ; Turn on 1k flag ; SET1K: MVI B,1 ; Wait up to 1 second to get "K" CALL RECV CPI 'K' ; Did we get a "K" or something else RNZ ; (or nothing) ; SET1KX: LDA MSPEED CPI 5 RC MVI A,'K' STA KFLAG ; Set 1k flag RET ; ; This routine moves the filename from the default command line buffer ; to the file control block (FCB). ; MOVEFCB:LHLD SAVEHL ; Get position on command line CALL GETB ; Get numeric position LXI D,FCB+1 CALL MOVENAM ; Move name to FCB XRA A STA FCBRNO ; Zero record number STA FCBEXT ; Zero extent LDA OPTSAV ; This going to be a library file? CPI 'L' RNZ ; If not, finished ; ; Handles library entries, first checks for proper .LBR extent. If no ; extent was included, it adds one itself. ; SHLD SAVEHL LXI H,FCB+9 ; 1st extent character MOV A,M CPI ' ' JZ NOEXT ; No extent, make one CPI 'L' ; Check 1st character in extent JNZ LBRERR INX H MOV A,M CPI 'B' ; Check 2nd character in extent JNZ LBRERR INX H MOV A,M CPI 'R' ; Check 3rd character in extent JNZ LBRERR ; ; Get the name of the desired file in the library ; MOVEF1: LHLD SAVEHL ; Get current position on command line CALL CHKMSP ; See if valid library member file name INR B ; Increment for move name LXI D,MEMFCB ; Store member name in special buffer JMP MOVENAM ; Move from command line to buffer, done ; ; Check for any spaces prior to library member file name, if none (or ; only spaces remaining), no name. ; CHKMSP: DCR B JZ MEMERR MOV A,M CPI ' '+1 RNC INX H JMP CHKMSP ; ; Gets the count of characters remaining on the command line ; GETB: MOV A,L SUI TBUF+2 ; Start location of 1st command MOV B,A ; Store for now LDA TBUF ; Find length of command line SUB B ; Subtract those already used MOV B,A ; Now have number of bytes remaining RET ; LBRERR: CALL ERXIT DB '++ Invalid library name ++$' ; MEMERR: CALL ILPRT DB CR,LF,'++ No library member file requested ++',CR,LF,0 JMP OPTERR ; ; Add .LBR extent to the library file name ; NOEXT: LXI H,FCB+9 ; Location of extent MVI M,'L' INX H MVI M,'B' INX H MVI M,'R' JMP MOVEF1 ; Now get the library member name ; ; Move a file name from the 'TBUF' command line buffer into FCB ; MOVENAM:MVI C,1 ; MOVEN1: MOV A,M CPI ' '+1 ; Name ends with space or return JC FILLSP ; Fill with spaces if needed CPI '.' JZ CHKFIL ; File name might be less than 8 chars. STAX D ; Store INX D ; Next position to store the character INR C ; One less to go MOV A,C CPI 12+1 JNC NONAME ; 11 chars. maximum filename plus extent ; MOVEN2: INX H ; Next char. in file name DCR B JZ OPTERR ; End of name, see if done yet JMP MOVEN1 ; ; See if any spaces needed between file name and .ext ; CHKFIL: CALL FILLSP ; Fill with spaces JMP MOVEN2 ; FILLSP: MOV A,C CPI 9 RNC ; Up to 1st character in .ext now MVI A,' ' ; Be sure there is a blank there now STAX D INR C INX D JMP FILLSP ; Go do another ; CTYPE: PUSH B ; Save all registers PUSH D PUSH H MOV E,A ; Character to 'E' in case BDOS (normal) LDA CONONL ; Want to bypass 'BYE' output to modem? ORA A JNZ CTYPEL ; Yes, go directly to CRT, then MVI C,WRCON ; BDOS console output, to CRT and modem CALL BDOS ; Since 'BYE' intercepts the char. POP H ; Restore all registers POP D POP B RET ; CTYPEL: MOV C,E ; BIOS needs it in 'C' CALL CONOUT ; BIOS console output routine, not BDOS POP H ; Restore all registers saved by 'CTYPE' POP D POP B RET ; HEXO: PUSH PSW ; Save for right digit RAR ; Right justify the left digit RAR RAR RAR CALL NIBBL ; Print left digit POP PSW ; Restore right ; NIBBL: ANI 0FH ; Isolate digit ADI 90H DAA ACI 40H DAA JMP CTYPE ; Type it ; ; Inline print of message, terminates with a 0 ; ILPRT: XTHL ; Save HL, get HL=message ; ILPLP: MOV A,M ; Get the character INX H ; To next character ORA A ; End of message? JZ ILPRET ; Yes, return CALL CTYPE ; Type the message JMP ILPLP ; Loop ; ILPRET: XTHL ; Restore HL RET ; Past message ; ; Exit printing message following call ; ERXIT: CALL ILPRT DB CR,LF,0 XRA A STA OPTSAV ; Reset option to zero for TELL ; ERXIT1: MVI C,DIRCON ; Use BDOS Direct MVI E,0FFH ; Console input function CALL BDOS ; To check for abort CPI 'C'-40H ; CTL-C JZ ERXITX ; Abort msg CPI 'K'-40H ; CTL-K JZ ERXITX ; Abort msg POP H ; Get address of next char MOV A,M ; Get char INX H ; Increment to next char PUSH H ; Save address CPI '$' ; End of message? JZ EXITXL ; If '$' is end of message CALL CTYPE ; Else print char on console JMP ERXIT1 ; And repeat until abort/end ; EXITXL: CALL ILPRT DB CR,LF,0 ; ERXITX: POP H ; Restore stack JMP EXIT ; Get out of here ; ; Restore the old user area and drive from a received file ; RECAREA:CALL RECDRV ; Ok set the drive to its place LDA PRVTFL ; Private area wanted? ORA A LDA XPRUSR ; Yes, set to private area JNZ RECARE LDA XUSR ; Ok now set the user area ; RECARE: MOV E,A ; Stuff it in E MVI C,SETUSR ; Tell BDOS what we want to do CALL BDOS ; Now do it RET ; RECDRV: LDA PRVTFL ORA A LDA XPRDRV ; Get private upload drive JNZ RECDR1 LDA XDRV ; Or forced upload drive ; RECDR1: SUI 'A' ; Adjust it ; RECDRX: MOV E,A ; Stuff it in E MVI C,SELDSK ; Tell BDOS CALL BDOS RET ; MOVE: MOV A,M ; Get a character STAX D ; Store it INX H ; To next 'from' INX D ; To next 'to' DCR B ; More? JNZ MOVE ; Yes, loop RET ; ;----------------------------------------------------------------------- ; ; CRC SUBROUTINES ; ;----------------------------------------------------------------------- ; CHKCRC: PUSH H ; Check 'CRC' bytes of received message LHLD CRCVAL MOV A,H ORA L POP H RZ MVI A,0FFH RET ; CLRCRC: PUSH H ; Reset 'CRC' store for a new message LXI H,0 SHLD CRCVAL POP H RET ; FINCRC: PUSH PSW ; Finish 'CRC' calculation XRA A CALL UPDCRC CALL UPDCRC PUSH H LHLD CRCVAL MOV D,H MOV E,L POP H POP PSW RET ; UPDCRC: PUSH PSW ; Update 'CRC' store with byte in 'A' PUSH B PUSH H MVI B,8 MOV C,A LHLD CRCVAL ; UPDLOOP:MOV A,C RLC MOV C,A MOV A,L RAL MOV L,A MOV A,H RAL MOV H,A JNC SKIPIT MOV A,H ; The generator is x^16 + x^12 + x^5 + 1 XRI 10H MOV H,A MOV A,L XRI 21H MOV L,A ; SKIPIT: DCR B JNZ UPDLOOP SHLD CRCVAL POP H POP B POP PSW RET ; ; end of CRC routines ;----------------------------------------------------------------------- ; start of LOGCAL routines ; ; The following allocations are used by the LOGCALL routines ; IF LOGCAL PGSIZE: DB 0,0,0 ; Program length in minutes and seconds LOGOPT: DB '?' ; Primary option stored here DEFAULT$DISK: DB 0 ; Disk for open stored here DEFAULT$USER: DB 0 ; User for open stored here FCBCALLER: DB 0,'LASTCALR???' ; Last caller file FCB DB 0,0,0,0,0,0,0,0,0,0,0,0 DB 0,0,0,0,0,0,0,0,0,0,0 CALLERPTR: DW LOGBUF FCBLOG: DB 0 ; Log file FCB ENDIF ; IF LOGCAL AND NOT (LOGSYS OR KNET) DB 'XMODEM ' DB 'L','O'+80H,'G' ; (the +80H makes this a $SYS file) ENDIF ; IF LOGCAL AND LOGSYS AND NOT KNET DB 'LOG ' DB 'S','Y'+80H,'S' ENDIF ; IF LOGCAL AND KNET AND NOT LOGSYS DB 'XMODEM ' DB 'T','X'+80H,'#' ENDIF ; IF LOGCAL DB 0,0,0,0,0,0,0,0,0,0,0,0 DB 0,0,0,0,0,0,0,0,0,0,0,0 LOGPTR: DW DBUF LOGCNT: DB 0 LOGK: DB 'k ' ENDIF ; IF LOGCAL OR MBFMSG OR MBDESC DSKSAV: DB 0 ; Up/download disk saved here USRSAV: DB 0 ; Up/download user saved here ENDIF ; IF LOGCAL AND (RTC OR B3RTC OR BYEBDOS) YYSAV: DB 0 MMSAV: DB 0 DDSAV: DB 0 MNSAV: DB 0 ENDIF ; ; Main log file routine, adds record to log file ; IF LOGCAL OR MBDESC OR MBFMSG LOGCALL: MVI C,CURDRV ; Get current disk CALL BDOS ; (where down/upload occurred) STA DSKSAV ; And save it... MVI C,SETUSR ; Get current user area MVI E,0FFH ; (where down/upload occurred) CALL BDOS STA USRSAV ; And save it... ENDIF ; IF (MBDESC OR MBFMSG) AND (NOT LOGCAL) RET ; Skip logging if no log ENDIF ; IF LOGCAL XRA A STA FCBCALLER+12 STA FCBCALLER+32 MVI A,LASTDRV-'A' STA DEFAULT$DISK MVI A,LASTUSR STA DEFAULT$USER LXI D,FCBCALLER CALL OPENF ; Open LASTCALR file JNZ LOGC1 CALL ERXIT DB '++ No last caller file found +++$' ; LOGC1: MVI C,SETRRD ; Get random record # LXI D,FCBCALLER ; (for first record in file) CALL BDOS LXI D,DBUF ; Set DMA to DBUF MVI C,SETDMA CALL BDOS LXI D,FCBCALLER ; Read first (& only) record MVI C,RRDM CALL BDOS ENDIF ;LOGCAL ; IF LOGCAL AND NOT (MBBS AND (RTC OR B3RTC OR BYEBDOS)) LXI H,DBUF ; Set pointer to beginning of record ENDIF ; IF LOGCAL AND (MBBS AND (RTC OR B3RTC OR BYEBDOS)) LXI H,DBUF+11 ; Set pointer to skip log on date ENDIF ; IF LOGCAL SHLD CALLERPTR LXI D,LOGBUF ; Set DMA address to LOGBUF MVI C,SETDMA CALL BDOS XRA A STA FCBLOG+12 STA FCBLOG+32 MVI A,LOGDRV-'A' STA DEFAULT$DISK MVI A,LOGUSR STA DEFAULT$USER LXI D,FCBLOG CALL OPENF ; Open log file JNZ LOGC4 ; If file exists, skip create LXI D,FCBLOG MVI C,MAKE ; Create a new file if needed CALL BDOS INR A JNZ LOGC2 ; No error, cont. CALL ERXIT ; File create error DB '++ No dir space: log ++$' ; LOGC2: MVI C,SETRRD ; Set random record # LXI D,FCBLOG ; (for first record in file) CALL BDOS ; LOGC3: MVI A,EOF STA LOGBUF JMP LOGC4B ; LOGC4: MVI C,CFSIZE ; Get file length LXI D,FCBLOG CALL BDOS ; (end+1) LHLD FCBLOG+33 ; Back up to last record MOV A,L ORA H JZ LOGC3 ; Unless zero length file DCX H SHLD FCBLOG+33 LXI D,FCBLOG MVI C,RRDM ; And read it CALL BDOS ; LOGC4B: CALL RSTLP ; Initialize LOGPTR and LOGCNT ; LOGC6: CALL GETLOG ; Get characters out of last record CPI EOF JNZ LOGC6 ; Until EOF LDA LOGCNT ; Then backup one character DCR A STA LOGCNT LHLD LOGPTR DCX H SHLD LOGPTR LDA LOGOPT ; Get option back and put in file CALL PUTLOG CALL SPEED ; Get speed factor ADI 30H CALL PUTLOG CALL PUTSP ; Blank LDA PGSIZE ; Now the program size in minutes.. CALL PNDEC ; Of transfer time (mins) MVI A,':' CALL PUTLOG ; ':' LDA PGSIZE+2 CALL PNDEC ; And secs.. CALL PUTSP ; Blank ; ; Log the drive and user area as a prompt ; LDA FCB ORA A JNZ WDRV LDA DSKSAV INR A ; WDRV: ADI 'A'-1 CALL PUTLOG LDA USRSAV CALL PNDEC MVI A,'>' ; Make it look like a prompt CALL PUTLOG LDA OPTSAV CPI 'L' JNZ WDRV1 LXI H,MEMFCB ; Name of file in library MVI B,11 CALL PUTSTR CALL PUTSP ; ' ' ; WDRV1: LXI H,FCB+1 ; Now the name of the file MVI B,11 CALL PUTSTR LDA OPTSAV CPI 'L' JNZ WDRV2 MVI C,1 JMP SPLOOP ; WDRV2: MVI C,13 ; SPLOOP: PUSH B CALL PUTSP ; Put ' ' POP B DCR C JNZ SPLOOP LHLD VRECNO ; Get VIRTUAL record count LXI D,8 ; Divide record count by 8 CALL DVHLDE ; To get # of 1024 byte blocks MOV A,H ORA L ; Check if remainder MOV H,B ; Get quotient MOV L,C JZ EXKB2 ; If 0 remainder, exact kb INX H ; Else increment to next kb ; EXKB2: CALL PNDEC3 ; Print to log file (right just xxxk) LXI H,LOGK ; 'k ' MVI B,2 CALL PUTSTR ENDIF ; IF LOGCAL AND BYEBDOS MVI C,BDSTOS ; Set max time to 0 so BYE won't MVI E,0 ; hang up when doing BYEBDOS calls CALL BDOS ; when getting time/date ENDIF ; IF LOGCAL AND (B3RTC OR RTC OR BYEBDOS) CALL GETDATE ; IF RTC, get current date PUSH B ; (save DD/YY) CALL PNDEC ; Print MM MVI A,'/' ; '/' CALL PUTLOG POP PSW ; Get DD/YY PUSH PSW ; Save YY CALL PNDEC ; Print DD MVI A,'/' ; '/' CALL PUTLOG POP B ; Get YY MOV A,C CALL PNDEC ; Print YY CALL PUTSP ; ' ' CALL GETTIME ; IF RTC, get current time STA MNSAV ; Save min MOV A,B ; Get current hour CALL PNDEC ; Print hr to file MVI A,':' ; With ':' CALL PUTLOG ; Between HH:MM LDA MNSAV ; Get min CALL PNDEC ; And print min CALL PUTSP ; Print a space ENDIF ; IF LOGCAL AND BYEBDOS LDA MAXTOS ; Reset time on system MOV E,A ; So BYE will hang up MVI C,BDSTOS ; If caller is over time limit CALL BDOS ENDIF ; IF LOGCAL AND OXGATE AND (B3RTC OR RTC OR BYEBDOS) XRA A STA CMMACNT ; Clear comma count ENDIF ; IF LOGCAL CLOOP: CALL GETCALLER ; And the caller CPI EOF JZ QUIT CPI CR ; Do not print 2nd line of 'LASTCALR' JNZ CLOP1 CALL PUTLOG MVI A,LF CALL PUTLOG ; And add a LF JMP QUIT ; CLOP1: CPI ',' ; Do not print the ',' between names JNZ CLOP2 ENDIF ; LOGCAL ; IF LOGCAL AND OXGATE AND (B3RTC OR RTC OR BYEBDOS) LDA CMMACNT ; Get comma count INR A STA CMMACNT CPI 2 ; If reached second comma, do CRLF exit JZ CLOPX ENDIF ; IF LOGCAL MVI A,' ' ; Instead send a ' ' CLOP2: CALL PUTLOG JMP CLOOP ENDIF ; IF LOGCAL AND OXGATE AND (B3RTC OR RTC OR BYEBDOS) CLOPX: MVI A,CR ; Cloop exit... do a CRLF and finish up. CALL PUTLOG MVI A,LF CALL PUTLOG ENDIF ; IF LOGCAL QUIT: MVI A,EOF ; Put in EOF CALL PUTLOG LDA LOGCNT ; Check count of chars in buffer CPI 1 JNZ QUIT ; Fill last buffer & write it LXI D,FCBCALLER ; Close lastcaller file MVI C,CLOSE CALL BDOS INR A JZ QUIT1 LHLD FCBLOG+33 ; Move pointer back to show DCX H ; Actual file size SHLD FCBLOG+33 LXI D,FCBLOG ; Close log file MVI C,CLOSE CALL BDOS INR A RNZ ; If OK, return now... ; QUIT1: CALL ERXIT ; If error, oops DB '++ Cannot close log ++$' ENDIF ; LOGCAL ; ;----------------------------------------------------------------------- ; ; Support routines for LOGCAL ; ; Gets a single byte from DBUF ; IF LOGCAL GETCALLER: LHLD CALLERPTR MOV A,M INX H SHLD CALLERPTR RET ; ; Gets a single byte from log file ; GETLOG: LDA LOGCNT INR A STA LOGCNT CPI 129 JZ EOLF LHLD LOGPTR MOV A,M INX H SHLD LOGPTR RET ; EOLF: LHLD FCBLOG+33 INX H SHLD FCBLOG+33 LXI H,LOGBUF+1 SHLD LOGPTR MVI A,1 STA LOGCNT MVI A,EOF RET ; ; Open file with FCB pointed to by DE (disk/user passed in DEFAULT$DISK ; and DEFAULT$USER) ; OPENF: PUSH D ; Save FCB address LDA DEFAULT$DISK ; Get disk for file CALL RECDRX ; Log into it LDA DEFAULT$USER ; Get default user CALL RECARE ; Log into it POP D ; Get FCB address MVI C,OPEN ; Open file CALL BDOS CPI 255 ; Not present? RET ; Return to caller ; ; Write character to log file ; PUTLOG: LHLD LOGPTR ; Get pointer ANI 7FH ; Mask off any high bits MOV M,A ; Put data INX H ; Increment pointer SHLD LOGPTR ; Update pointer MOV B,A ; Save character in B LDA LOGCNT ; Get count INR A ; Increment it STA LOGCNT ; Update count CPI 129 ; Check it RNZ ; If not EOB, return PUSH B ; Save character LXI D,FCBLOG ; Else, write this sector MVI C,WRDM CALL BDOS ORA A JZ ADVRCP ; If ok, cont. CALL ERXIT DB '++ Disk full - cannot add to log ++$' ; ADVRCP: LHLD FCBLOG+33 ; Advance record number INX H SHLD FCBLOG+33 CALL RSTLP ; Reset buffer pointers POP PSW ; Get saved character JMP PUTLOG ; Put it in buffer and return ; RSTLP: LXI H,LOGBUF ; Reset pointers SHLD LOGPTR ; And return MVI A,0 STA LOGCNT RET ; ; Print number in decimal format (into log file) ; IN: HL=binary number ; OUT: nnn=right justified with spaces ; PNDEC3: MOV A,H ; Check high byte ORA A JNZ DECOT ; If on, is at least 3 digits MOV A,L ; Else, check low byte CPI 100 JNC TEN CALL PUTSP ; TEN: CPI 10 JNC DECOT CALL PUTSP JMP DECOT ; ; Puts a single space in log file, saves PSW/HL ; PUTSP: PUSH PSW PUSH H MVI A,' ' CALL PUTLOG POP H POP PSW RET ; ; Print number in decimal format (into log file) ; PNDEC: CPI 10 ; Two column decimal format routine JC ONE ; One or two digits to area number? JMP TWO ; ONE: PUSH PSW MVI A,'0' CALL PUTLOG POP PSW ; TWO: MVI H,0 MOV L,A ; DECOT: PUSH B PUSH D PUSH H LXI B,-10 LXI D,-1 ; DECOT2: DAD B INX D JC DECOT2 LXI B,10 DAD B XCHG MOV A,H ORA L CNZ DECOT MOV A,E ADI '0' CALL PUTLOG POP H POP D POP B RET ; ; Put string to log file ; PUTSTR: MOV A,M PUSH H PUSH B CALL PUTLOG POP B POP H INX H DCR B JNZ PUTSTR RET ENDIF ; LOGCAL ; ; end of LOGCAL routine ;----------------------------------------------------------------------- ; start of TIMEON routine ; ; Calculate time on system and inform user. Log him off if =>MAXMIN ; unless STATUS is non-zero. ; IF TIMEON TIME: PUSH B ; Save BC pair CALL GETTIME ; Get time from system's RTC STA CMTEMP ; Save in current-hour-temp MOV A,B ; Get current hour POP B ; Restore BC ENDIF ; IF TIMEON AND BYEBDOS PUSH PSW ; save the current hour <== BUG FIX PUSH B ; Lhour was safely moved to highmem PUSH D ; in newer versions of BYE MVI C,BDGRTC CALL BDOS LXI D,11 ; Get address of LHOUR DAD D POP D POP B POP PSW ; Restore current hour...BDOS killed it ENDIF ; IF TIMEON AND NOT BYEBDOS LXI H,LHOUR ; Point to log-on hour (in low memory) ENDIF ; IF TIMEON CMP M ; Equal? INX H ; Point to logon minutes JNZ TIME1 ; No MOV D,M LDA CMTEMP ; Current minutes SUB D STA TON ; Store total time on JMP TIME2 ; TIME1: MOV D,M ; Get logon minutes MVI A,03CH ; 60 min into A SUB D LXI H,CMTEMP ; Point at current min ADD M ; Add current minutes STA TON ENDIF ; TIME2: IF ZCPR2 AND TIMEON LDA WHEEL ; Check wheel status if ZCPR ORA A ; Is it zero JNZ TIME3 ; If not then this is a special user ENDIF ; IF TIMEON LDA MAXTOS ORA A ; If maxtos is zero, guy is superuser JZ TIME3 ENDIF ; IF TIMEON AND NOT BYEBDOS ; BYEBDOS doesn't use status byte ORA A ; Special user? JNZ TIME3 ; Yes, skip log off check LDA TON SUI MAXMIN ; Subtract max time allowed ENDIF ; IF TIMEON AND BYEBDOS LDA MAXTOS MOV B,A LDA TON SUB B ENDIF ; IF TIMEON JC TIME3 ; Still time left CALL TIMEUP ; Time is up, inform user MVI A,0CDH ; Alter jump vector STA 0 ; At zero JMP 0000H ; And log him off ; TIME3: LXI H,MSG1+015H ; Point at message insert bytes LDA TON ; Convert to ASCII MVI B,0FFH ; TIME4: INR B SUI 0AH ; Subtract 10 JNC TIME4 ; Until done ADI 0AH ORI '0' ; Make ASCII MOV M,A DCX H MVI A,'0' ADD B MOV M,A CALL ILPRT ; MSG1: DB CR,LF,'Time on system is 00 minutes',CR,LF,0 ENDIF ; IF TIMEON AND NOT BYEBDOS LDA STATUS ; Check user status ORA A ; Special user? JNZ TIME5 ; Yes, reset TON ENDIF ; IF TIMEON RET ENDIF ; IF TIMEON AND NOT BYEBDOS TIME5: MVI A,0 ; Reset timeout for good guys STA TON RET ENDIF ; IF TIMEON TIMEUP: CALL ILPRT DB CR,LF,CR,LF DB 'Your time is up - wait 24 hours to call back',CR,LF,0 RET ; TON: DB 0 ; Storage for time on system CMTEMP: DB 0 ; Storage for current minute value ENDIF ; ; Get caller's time on system from BYE3 or MBYE and display on console. ; IF B3RTC AND B3TOS TIME: CALL ILPRT DB CR,LF,'Time on system is ',0 CALL GETTOS ; Get Time On System from MBYE's RTC CALL DECOUT ; Print it on the screen CALL ILPRT DB ' minutes',CR,LF,0 RET ENDIF ; ; Get caller's time on system (returned in HL). ; IF B3RTC AND (NOT BYEBDOS) GETTOS: LHLD RTCBUF ; Get RTCBUF addr MOV A,H ORA L RZ ; If 0000H, BYE not running so TOS=0 MOV A,M ; If hours = 99 CPI 099H LXI H,0 RZ ; Return with TOS=0 LHLD RTCBUF LXI D,B3CMOS ; Get offset to TOS word DAD D ; (addr in HL) MOV E,M ; Get minutes on system INX H MOV D,M ; Stuff into DE XCHG ; Swap into HL RET ENDIF ; IF BYEBDOS OR MXTOS MAXTOS: DB 0 ; Maximum time on system ENDIF ; ; end of TIMEON routine ;----------------------------------------------------------------------- ; GETDATE: IF (RTC AND LOGCAL) AND NOT (CPM3 OR BYEBDOS) LDA 45H ; Get the binary day number MOV B,A ; Set to return binary day # B reg. LDA 46H ; Get the binary year number MOV C,A ; Set to return binary year # in C reg. LDA 44H ; Get the binary month number RET ENDIF ; ;----------------------------------------------------------------------- ; start of CPM+ date routine IF RTC AND LOGCAL AND CPM3 MVI C,GETTIM ; BDOS function to get date and time LXI D,TIMEPB ; Get address of 4-byte data structure CALL BDOS ; Transfer the current date/time LHLD TIMEPB MVI B,78 ; Set years counter ; LOOP: CALL CKLEAP LXI D,-365 ; Set up for subtract JNZ NOLPY ; Skip if no leap year DCX D ; Set for leap year ; NOLPY: DAD D ; Subtract JNC YDONE ; Continue if years done MOV A,H ORA L JZ YDONE SHLD TIMEPB ; Else save days count INR B ; Increment years count JMP LOOP ; And do again ; ; The years are now finished, the years count is in 'B' and TIMEPB holds ; the days (HL is invalid) ; YDONE: MOV A,B STA YEAR CALL CKLEAP ; Check if leap year MVI A,-28 JNZ FEBNO ; February not 29 days MVI A,-29 ; Leap year ; FEBNO: STA FEB ; Set february LHLD TIMEPB ; Get days count LXI D,MTABLE ; Point to months table MVI B,0FFH ; Set up 'B' for subtract MVI A,0 ; Set a for # of months ; MLOOP: PUSH PSW LDAX D ; Get month MOV C,A ; Put in 'C' for subtract POP PSW SHLD TIMEPB ; Save days count DAD B ; Subtract INX D ; Increment months counter INR A JC MLOOP ; Loop for next month ; ; The months are finished, days count is on stack. First, calculate ; the month. ; MDONE: MOV B,A ; Save months LHLD TIMEPB MOV A,H ORA L JNZ NZD DCX D DCX D LDAX D CMA INR A MOV L,A DCR B ; NZD: MOV A,B STA MONTH MOV A,L STA DAY LDA YEAR MOV C,A LDA DAY MOV B,A LDA MONTH RET ; ; This routine checks for leap years. ; CKLEAP: MOV A,B ANI 0FCH CMP B RET ; ; This is the month's table ; MTABLE: DB -31 ; January FEB: DB -28 ; February DB -31,-30,-31,-30 ; Mar-Jun DB -31,-31,-30 ; Jul-Sep DB -31,-30,-31 ; Oct-Dec ; YEAR: DB 0 MONTH: DB 0 DAY: DB 0 ENDIF ; RTC AND LOGCAL AND CPM3 ; ; end of CPM+ date routine ;----------------------------------------------------------------------- ; IF LOGCAL AND B3RTC AND NOT BYEBDOS CALL BYECHK ; See if BYE is running JZ GETBDAT ; If so, get date from buffer & convert MVI A,0 ; Else, return 00/00/00 MOV B,A MOV C,A RET ENDIF ; IF LOGCAL AND B3RTC AND (NOT BYEBDOS) GETBDAT:LHLD RTCBUF ; Get RTC buffer in HL ENDIF ; IF LOGCAL AND BYEBDOS AND (NOT B3RTC) MVI C,BDGRTC ; Get RTC buffer in HL CALL BDOS ENDIF ; IF LOGCAL AND (BYEBDOS OR B3RTC) LXI D,4 ; Offset to YY DAD D ; HL=YY Address MOV A,M ; Get YY CALL BCDBIN ; Make it binary STA YYSAV ; Save YY INX H ; Point to MM MOV A,M ; Get MM CALL BCDBIN ; Convert BCD to binary STA MMSAV ; Save it INX H ; Point to DD MOV A,M ; Get DAY CALL BCDBIN ; Convert it to binary MOV B,A ; Stuff DD in B LDA YYSAV ; Get YY MOV C,A ; Put YY in C LDA MMSAV ; Get MM in A RET ; And return ENDIF ; ; ; The routine here should read your real-time clock and return with the ; following information: ; ; register: A - current minute (0-59) ; B - current hour (0-23) ; GETTIME: IF (TIMEON OR RTC) AND NOT (B3RTC OR CPM3 OR BYEBDOS) ; ;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;; (this example is for the Serria SBC-100) ;; ;;SBCHR EQU 040H ; Low memory area where stored ;;SBCMN EQU 041H ;; ;; LDA SBCHR ; Get hour from BIOS memory-clock ;; MOV B,A ;; LDA SBCMN ; Get minute from BIOS memory-clock ;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ; ;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;; (this example is for Don Brown's computer) ;; ;; LDA 43h ; Get the current binary hour number ;; MOV B,A ; Set to return binary hour number in Reg. B ;; LDA 42h ; Get the current binary minute number ;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ; RET ENDIF ; ; The following code is for CP/M Plus ; IF (TIMEON OR RTC) AND CPM3 MVI C,GETTIM ; BDOS function to get date and time LXI D,TIMEPB ; Get address of 4-byte data structure CALL BDOS ; Transfer the current date/time LDA TIMEPB+2 ; Get current hour CALL BCDBIN ; Convert BCD hour to binary MOV B,A ; Position hour for return PUSH B ; Save the binary hour LDA TIMEPB+3 ; Get current minute CALL BCDBIN ; Convert BCD minute to binary POP B ; Restore the binary hour RET ENDIF ; IF LOGCAL AND B3RTC AND (NOT BYEBDOS) CALL BYECHK ; See if BYE is running JZ GETBTIM ; If so, get time from buffer & convert MVI A,0 ; Else, return 00:00 MOV B,A RET ; GETBTIM:LHLD RTCBUF ; Get RTC buffer address ENDIF ; IF LOGCAL AND BYEBDOS AND (NOT B3RTC) MVI C,BDGRTC ; Get RTC buffer address CALL BDOS ENDIF ; IF LOGCAL AND (B3RTC OR BYEBDOS) MOV A,M ; Get hours on system CALL BCDBIN ; Convert BCD value to binary PUSH PSW ; Save hr on stack INX H ; Point to minute MOV A,M ; Get min CALL BCDBIN ; Convert BCD to binary POP B ; Get hr in B (min in A) RET ; And return ENDIF ; ; Convert BCD value in A to binary in A ; IF LOGCAL AND (B3RTC OR CPM3 OR BYEBDOS) BCDBIN: PUSH PSW ; Save A ANI 0F0H ; Mask high nibble RRC ; Move to low nibble RRC RRC RRC MOV C,A ; And stuff in C (C=A) MVI B,9 ; X10 (*9) ; BCDBL: ADD C ; Add orig value to A DCR B ; Decrement B JNZ BCDBL ; Loop nine times (A+(C*9)=A*10) MOV B,A ; Save result in B POP PSW ; Get original value ANI 0FH ; Mask low nibble ADD B ; +B gives binary value of BCD digit A RET ; Return ENDIF ; ; Check to see that HL register is at least 8 records. If it not, make ; sure 1K blocks are turned off ; CKKSIZ: MOV A,H ; Get high order byte ORA A ; Something there? RNZ ; Yes, certainly more than 8 MOV A,L ; Get low order byte CPI 8 ; Looking for at least this many records RNC ; Not Carry means 8 or more records XRA A ; Get nothing STA KFLAG ; Turn off 1K blocks RET ; ;----------------------------------------------------------------------- ; ; BYEBDOS access routines ; ;----------------------------------------------------------------------- ; IF BYEBDOS CONOUT: MOV E,C ; Get character into E MVI C,BDCONO ; Console output (local only) JMP BDOS ; Go to it... ; MINIT: UNINIT: RET ; Modem's already initialized ; SENDR: POP PSW ; Needed by specifications PUSH B PUSH D PUSH H MOV E,A ; Put character in E MVI C,BDMOUT CALL BDOS POP H POP D POP B RET ; GETCHR: MDIN: PUSH B PUSH D PUSH H MVI C,BDMINP CALL BDOS POP H POP D POP B RET ; ; The following 3 routines operate in differently than BYE does, so we ; must make things "backwards" ; CAROK: PUSH B PUSH D PUSH H MVI C,BDCSTA CALL BDOS JMP BKWDS ; RCVRDY: PUSH B PUSH D PUSH H MVI C,BDMIST CALL BDOS JMP BKWDS ; SNDRDY: PUSH B PUSH D PUSH H MVI C,BDMOST CALL BDOS ; ; Flip around bytes, if A>0 then make A zero & set flags ; if A=0 then make A =255 & set flags BKWDS: ORA A MVI A,255 JZ NOSIG XRA A ; NOSIG: ORA A POP H POP D POP B RET ; SPEED: LDA MSPEED RET ENDIF ; ;----------------------------------------------------------------------- ; ; Temporary storage area ; ;----------------------------------------------------------------------- ; IF DESCRIB FILE: DB 0,'WHATSFORTXT',0,0,0,0,0,0,0 DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0 DEST: DB 0,' $$$',0,0,0,0,0,0,0 DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0 ENDIF ; ; Put this ram stuff in the RAM section at the end ; LZFLG: DB 0 ; For the free space printer BLKSHF: DB 0 BLKMAX: DB 0,0 ; IF B3RTC AND NOT BYEBDOS ; If BYE3/MBYE real-time clock RTCBUF: DW 0 ; Address of RTCBUF saved here ENDIF ; IF B3RTC AND NOT (MBMXT OR BYEBDOS) TOSSAV: DW 0 ENDIF ; IF LOGCAL AND OXGATE AND (B3RTC OR RTC OR BYEBDOS) CMMACNT:DB 0 ; Comma counter ENDIF ; IF TIMEON AND CPM3 TIMEPB: DS 4 ; Storage for the system date/time ENDIF ; MINUTE: DW 0 ; Transfer time in mins for MAXTIM MEMFCB: DB ' ' ; Library name (16 bytes required) ANYET: DB 0 ; Any description typed yet? BLKSIZ: DW 0 ; Number of bytes, 128 or 1024 CONONL: DB 0 ; CTYPE console-only flag CRCFLG: DB 0 ; Sets to 'C' if checksum requested CRCVAL: DW 0 ; Current CRC value DIRSZ: DW 0 ; Directory size DRUSER: DB 0 ; Original drive/user, for return DUD: DB 0 ; Specified disk DUSAVE: DB 0,0,0,0 ; Buffer for drive/user DUU: DB 0 ; Specified user ERRCT: DB 0 ; Error count FRSTIM: DB 0 ; Turned on after first 'SOH' received INDEX: DW 0 ; Index into directory KFLAG: DB 0 ; Non-zero if sending 1K blocks OUTPTR: DW 0 RCNT: DW 0 ; Record count RCVDRV: DB 0 ; Requested drive number RCVRNO: DB 0 ; Record number received RCVUSR: DB 0 ; Requested user number RECDNO: DW 0 ; Current record number KIND: DB 0 ; Asks what kind of file this is OLDDRV: DB 0 ; Save the original drive number OLDUSR: DB 0 ; Save the original user number OPTSAV: DB 0 ; Save option here for carrier loss PRVTFL: DB 0 ; Private user area option flag MSGFLG: DB 0 ; Message upload flag SAVEHL: DW 0 ; Saves TBUF command line address TOTERR: DW 0 ; Total errors for transmission attempt VRECNO: DW 0 ; Virtual record # in 128 byte records ; EOFLG: DB 0 ; 'EOF' flag (1=yes) EOFCTR: DB 0 ; EOF send counter OUTADR: DW LOGBUF OUTSIZ: DW BSIZE RECPTR: DW DBUF RECNBF: DW 0 ; Number of records in the buffer ; IF CONFUN AND SYSABT SYSABF: DB 0 ; set if sysop uses ^X to abort ENDIF ; IF (DESCRIB OR MBDESC) AND NDESC NDSCFL: DB 0 ; Used to store "RN" option ENDIF ; to bypass upload descriptions ; IF DESCRIB HLINE: DB '-------------------',CR,LF OLINE: DS 80 ; Temporary buffer to store line ENDIF ; DS 80 ; Minimum stack area ; ; Disk buffer ; ORG ($+127)/128*128 ; DBUF EQU $ ; 16-record disk buffer STACK EQU DBUF-2 ; Save original stack address LOGBUF EQU DBUF+128 ; For use with LOGCAL ; ;----------------------------------------------------------------------- ; ; BDOS equates ; ;----------------------------------------------------------------------- ; RDCON EQU 1 ; Get character from console WRCON EQU 2 ; Output to console DIRCON EQU 6 ; Direct console output PRINT EQU 9 ; Print string function VERNO EQU 12 ; Get CP/M version number SELDSK EQU 14 ; Select drive OPEN EQU 15 ; 0FFH = not found CLOSE EQU 16 ; " " SRCHF EQU 17 ; " " SRCHN EQU 18 ; " " DELET EQU 19 ; Delete file READ EQU 20 ; 0=OK, 1=EOF WRITE EQU 21 ; 0=OK, 1=ERR, 2=?, 0FFH=no dir. space MAKE EQU 22 ; 0FFH=bad RENAME EQU 23 ; Rename a file CURDRV EQU 25 ; Get current drive SETDMA EQU 26 ; Set DMA SETATT EQU 30 ; Set file attributes SETUSR EQU 32 ; Set user area to receive file RRDM EQU 33 ; Read random WRDM EQU 34 ; Write random CFSIZE EQU 35 ; Compute file size SETRRD EQU 36 ; Set random record GETTIM EQU 105 ; CP/M Plus get date/time BDOS EQU 0005H TBUF EQU 0080H ; Default DMA address FCB EQU 005CH ; System FCB FCBEXT EQU FCB+12 ; File extent FCBRNO EQU FCB+32 ; Record number RANDOM EQU FCB+33 ; Random record field ; ; Extended BYEBDOS equates ; IF BYEBDOS BDMIST EQU 61 ; Modem raw input status BDMOST EQU 62 ; Modem raw output status BDMOUT EQU 63 ; Modem output 8 bit char BDMINP EQU 64 ; Modem input 8 bit char BDCSTA EQU 65 ; Modem carrier status BDCONS EQU 66 ; Local console input status BDCONI EQU 67 ; Local console input char BDCONO EQU 68 ; Local console output char BDMXDR EQU 69 ; Set/get maximum drive BDMXUS EQU 70 ; Set/get maximum user area BDNULL EQU 72 ; Set/get nulls BDTOUT EQU 71 ; Set/get idle timeout BDULCS EQU 73 ; Set/get upperlowercase switch BDLFMS EQU 74 ; Set/get line-feed mask BDHRDL EQU 76 ; Set/get hardlog BDWRTL EQU 75 ; Set/get writeloc BDMDMO EQU 77 ; Set/get mdmoff flag BDBELL EQU 78 ; Set/get bell mask flag BDGRTC EQU 79 ; Get address of rtc buffer BDGLCB EQU 80 ; Get address of lc buffer BDSTOS EQU 81 ; Maximum time on system BDSLGT EQU 82 ; Set login time BDPTOS EQU 83 ; Print Time on System ENDIF ; BYEBDOS ; END