From d265f1323d51c29b3c41e434ce9d8877adecc1da Mon Sep 17 00:00:00 2001 From: Wayne Warthen Date: Wed, 13 Oct 2021 17:33:40 -0700 Subject: [PATCH] Add ZMD --- Source/Apps/Build.cmd | 1 + Source/Apps/Clean.cmd | 1 + Source/Apps/Makefile | 2 +- Source/Apps/ZMD/-29sep88 | 0 Source/Apps/ZMD/-notice.doc | 24 + Source/Apps/ZMD/-zmd.for | 10 + Source/Apps/ZMD/Build.cmd | 37 + Source/Apps/ZMD/Clean.cmd | 10 + Source/Apps/ZMD/Makefile | 10 + Source/Apps/ZMD/autoinst.doc | 36 + Source/Apps/ZMD/equates.ins | 88 + Source/Apps/ZMD/mkcoms.sub | 44 + Source/Apps/ZMD/zforp.z80 | 420 +++ Source/Apps/ZMD/zfors.z80 | 233 ++ Source/Apps/ZMD/zmap.z80 | 268 ++ Source/Apps/ZMD/zmd.pdf | Bin 0 -> 83801 bytes Source/Apps/ZMD/zmd.ws | 1144 ++++++++ Source/Apps/ZMD/zmd.z80 | 4858 ++++++++++++++++++++++++++++++++++ Source/Apps/ZMD/zmdel.z80 | 264 ++ Source/Apps/ZMD/zmdhb.z80 | 916 +++++++ Source/Apps/ZMD/zmdhdr.z80 | 637 +++++ Source/Apps/ZMD/znewp.z80 | 517 ++++ Source/Apps/ZMD/znews.z80 | 298 +++ Source/Images/Build.cmd | 5 +- Source/Images/Makefile | 3 +- Source/Images/d_bp.txt | 2 + Source/Images/d_cpm22.txt | 1 + Source/Images/d_cpm3.txt | 1 + Source/Images/d_nzcom.txt | 1 + Source/Images/d_zpm3.txt | 1 + Source/Images/d_zsdos.txt | 1 + Source/ver.inc | 2 +- Source/ver.lib | 2 +- 33 files changed, 9831 insertions(+), 6 deletions(-) create mode 100644 Source/Apps/ZMD/-29sep88 create mode 100644 Source/Apps/ZMD/-notice.doc create mode 100644 Source/Apps/ZMD/-zmd.for create mode 100644 Source/Apps/ZMD/Build.cmd create mode 100644 Source/Apps/ZMD/Clean.cmd create mode 100644 Source/Apps/ZMD/Makefile create mode 100644 Source/Apps/ZMD/autoinst.doc create mode 100644 Source/Apps/ZMD/equates.ins create mode 100644 Source/Apps/ZMD/mkcoms.sub create mode 100644 Source/Apps/ZMD/zforp.z80 create mode 100644 Source/Apps/ZMD/zfors.z80 create mode 100644 Source/Apps/ZMD/zmap.z80 create mode 100644 Source/Apps/ZMD/zmd.pdf create mode 100644 Source/Apps/ZMD/zmd.ws create mode 100644 Source/Apps/ZMD/zmd.z80 create mode 100644 Source/Apps/ZMD/zmdel.z80 create mode 100644 Source/Apps/ZMD/zmdhb.z80 create mode 100644 Source/Apps/ZMD/zmdhdr.z80 create mode 100644 Source/Apps/ZMD/znewp.z80 create mode 100644 Source/Apps/ZMD/znews.z80 diff --git a/Source/Apps/Build.cmd b/Source/Apps/Build.cmd index da042c4c..cd71ed07 100644 --- a/Source/Apps/Build.cmd +++ b/Source/Apps/Build.cmd @@ -32,6 +32,7 @@ pushd Tune && call Build || exit /b & popd pushd FAT && call Build || exit /b & popd pushd Test && call Build || exit /b & popd pushd ZMP && call Build || exit /b & popd +pushd ZMD && call Build || exit /b & popd copy *.com %APPBIN%\ || exit /b diff --git a/Source/Apps/Clean.cmd b/Source/Apps/Clean.cmd index 8e9810e6..22259b3a 100644 --- a/Source/Apps/Clean.cmd +++ b/Source/Apps/Clean.cmd @@ -13,3 +13,4 @@ pushd Tune && call Clean || exit /b 1 & popd pushd FAT && call Clean || exit /b 1 & popd pushd Test && call Clean || exit /b 1 & popd pushd ZMP && call Clean || exit /b 1 & popd +pushd ZMD && call Clean || exit /b 1 & popd diff --git a/Source/Apps/Makefile b/Source/Apps/Makefile index 7ca650ad..ea2decb0 100644 --- a/Source/Apps/Makefile +++ b/Source/Apps/Makefile @@ -1,7 +1,7 @@ OBJECTS = sysgen.com survey.com \ syscopy.com assign.com format.com talk.com mode.com rtc.com \ timer.com rtchb.com -SUBDIRS = XM FDU FAT Tune Test ZMP +SUBDIRS = XM FDU FAT Tune Test ZMP ZMD DEST = ../../Binary/Apps TOOLS =../../Tools diff --git a/Source/Apps/ZMD/-29sep88 b/Source/Apps/ZMD/-29sep88 new file mode 100644 index 00000000..e69de29b diff --git a/Source/Apps/ZMD/-notice.doc b/Source/Apps/ZMD/-notice.doc new file mode 100644 index 00000000..79faa5ad --- /dev/null +++ b/Source/Apps/ZMD/-notice.doc @@ -0,0 +1,24 @@ + + ----------------------------------------------------------------------- + Copyright (c) 1987, 1988 by Robert W. Kramer III, ALL RIGHTS RESERVED + Release 1.50 1569 40th St. + 09/29/88 Rock Island, Il. 61201 + Voice: (309) 786-6711, 24hr + RI RCPM-RDOS (128 Meg) Data: (309) 786-6227, 24hr, 300/1200/2400 + ----------------------------------------------------------------------- + + + USER AGREEMENT: + --------------- + + ZMD is free to use, providing program signon messages (my name, program + name, release date and copyright notices) remain intact as released. + You may modify any .Z80 file as you wish so long as your modified copy + is used for your own personal non-profit use and is NOT passed on to + any other party. + + You are encouraged to distribute ZMD releases to any interested party + providing all files remain in their ORIGINAL UNMODIFIED RELEASE FORM + as distributed from RI RCPM-RDOS. + + \ No newline at end of file diff --git a/Source/Apps/ZMD/-zmd.for b/Source/Apps/ZMD/-zmd.for new file mode 100644 index 00000000..151dceae --- /dev/null +++ b/Source/Apps/ZMD/-zmd.for @@ -0,0 +1,10 @@ +----- +ZMD150.LBR - CP/M Communications + 09/29/88 - This release is super clean, super fast - and smaller. + Some major problems in the way Ymodem Batch protocol was implemented + on CP/M systems has been fixed, virtually eliminating all delays + between files. CPM3 systems can now run all ZMD programs with no + problems. Some other bug fixes, modifications and enhancements. + This is probably the last release of ZMD until ZMD with ZMODEM is + released. Bob Kramer - RI RCPM-RDOS (128 Mb) - (309) 786-6711 + \ No newline at end of file diff --git a/Source/Apps/ZMD/Build.cmd b/Source/Apps/ZMD/Build.cmd new file mode 100644 index 00000000..fdc64135 --- /dev/null +++ b/Source/Apps/ZMD/Build.cmd @@ -0,0 +1,37 @@ +@echo off +setlocal + +set TOOLS=..\..\..\Tools + +set PATH=%TOOLS%\zx;%PATH% + +set ZXBINDIR=%TOOLS%\cpm\bin\ +set ZXLIBDIR=%TOOLS%\cpm\lib\ +set ZXINCDIR=%TOOLS%\cpm\include\ + +zx z80asm -zmd/fm +zx l80 -zmd,zmd/n/e + +zx z80asm -zmap/fm +zx l80 -zmap,zmap/n/e + +zx z80asm -znews/fm +zx l80 -znews,znews/n/e + +zx z80asm -znewp/fm +zx l80 -znewp,znewp/n/e + +zx z80asm -zfors/fm +zx l80 -zfors,zfors/n/e + +zx z80asm -zforp/fm +zx l80 -zforp,zforp/n/e + +zx z80asm -zmdel/fm +zx l80 -zmdel,zmdel/n/e + +zx z80asm -zmdhb/fh +zx mload25 zmd=zmd.com,zmdhb + +copy /Y zmd.com ..\..\..\Binary\Apps\ || exit /b + diff --git a/Source/Apps/ZMD/Clean.cmd b/Source/Apps/ZMD/Clean.cmd new file mode 100644 index 00000000..04e795a6 --- /dev/null +++ b/Source/Apps/ZMD/Clean.cmd @@ -0,0 +1,10 @@ +@echo off +setlocal + +ren zmdsubs.rel zmdsubs.rel.sav +if exist *.rel del *.rel +ren zmdsubs.rel.sav zmdsubs.rel +if exist *.hex del *.hex +if exist *.prn del *.prn +if exist *.lst del *.lst +if exist *.com del *.com diff --git a/Source/Apps/ZMD/Makefile b/Source/Apps/ZMD/Makefile new file mode 100644 index 00000000..af10bc44 --- /dev/null +++ b/Source/Apps/ZMD/Makefile @@ -0,0 +1,10 @@ +OBJECTS = zmd.com +DEST = ../../../Binary/Apps +TOOLS = ../../../Tools +OTHERS = *.hex zmd.rel + +include $(TOOLS)/Makefile.inc + +zmd.com: zmd.rel zmdhb.hex + $(ZXCC) $(CPM)/l80 -zmd,zmd/n/e + $(ZXCC) $(CPM)/MLOAD25 zmd=zmd.com,zmdhb diff --git a/Source/Apps/ZMD/autoinst.doc b/Source/Apps/ZMD/autoinst.doc new file mode 100644 index 00000000..b5209d59 --- /dev/null +++ b/Source/Apps/ZMD/autoinst.doc @@ -0,0 +1,36 @@ + + ----------------------------------------------------------------------- + Copyright (c) 1987, 1988 by Robert W. Kramer III, ALL RIGHTS RESERVED + Release 1.50 1569 40th St. + 09/29/88 Rock Island, Il. 61201 + Voice: (309) 786-6711, 24hr + RI RCPM-RDOS (128 Meg) Data: (309) 786-6227, 24hr, 300/1200/2400 + ----------------------------------------------------------------------- + + + ZMD AUTO-INSTALL FEATURE + ------------------------ + + ZINSTL can automatically install the new release of ZMD (and utilities) + with the use of the '!' option. ZINSTL ! will read all switch/toggle + configurations and port/modem/rtc overlays from your already installed + version of ZMD where you can then use them to install this new release. + + Follow these steps: + + 1) Extract all files from ZMDnnn.LBR + 2) Rename your already configured ZMD.COM to ZMD149.COM + 3) Run ZINSTL (Release 1.50) with the '!' option: ZINSTL ! + 4) Answer any questions ZINSTL may ask + 5) Use the 'J' option to save configurations + 6) Rename ZMD150.COM to ZMD.COM + + + NOTE: This will not work with any other version of ZMD except for 1.49 + and 1.50. Running ZINSTL without the ! option will cause it to run just + like you've all been used to. In the future, if the new release header + file ZMDHDR.Z80 becomes incompatible with that of the previous release + this feature will be disabled. + + + \ No newline at end of file diff --git a/Source/Apps/ZMD/equates.ins b/Source/Apps/ZMD/equates.ins new file mode 100644 index 00000000..f38310c6 --- /dev/null +++ b/Source/Apps/ZMD/equates.ins @@ -0,0 +1,88 @@ +; +; Version identification +; +VERS EQU 1 ; Release +MODLEV EQU 5 ; Major modification level +REV EQU 0 ; Minor revision number + +VMONTH EQU 09 ; Version month +VDAY EQU 29 ; Version day +VYEAR EQU 88 ; Version year +; +;========================================================================== +; +NO EQU 0 +YES EQU 0FFH +; +;-------------------------------------------------------------------------- +; Define ASCII characters used +;-------------------------------------------------------------------------- + +SOH EQU 1 ; ^A - start of header +STX EQU 2 ; ^B - start of 1k header +CTRLC EQU 3 ; ^C - abort/break character +EOT EQU 4 ; ^D - end of transmission +ACK EQU 6 ; ^F - acknowledge +BELL EQU 7 ; ^G - bell +BS EQU 8 ; ^H - backspace +TAB EQU 9 ; ^I - horizontal tab +LF EQU 10 ; ^J - line feed +CTRLK EQU 11 ; ^K - abort character +CR EQU 13 ; ^M - carriage return +PAUSE EQU 19 ; ^S - pause request +NAK EQU 21 ; ^U - negative acknowledge +CANCEL EQU 24 ; ^X - cancel +EOF EQU 26 ; ^Z - end of file +CRC EQU 67 ; C - CRC request character +KSND EQU 75 ; K - 1k block request character +DEL EQU 127 ; Delete/Rubout +; +; +; Define standard CP/M BDOS equates +; +DIRCON EQU 6 ; Direct console output +PRINT EQU 9 ; Print string function +GETVER EQU 12 ; Get CP/M version +SELDSK EQU 14 ; Select drive +OPEN EQU 15 ; Open file +CLOSE EQU 16 ; Close file +SRCHF EQU 17 ; Search for first occurence +SRCHN EQU 18 ; Search for next occurence +DELETE EQU 19 ; Delete file +READ EQU 20 ; Read record +WRITE EQU 21 ; Write record +MAKE EQU 22 ; Make new file +RENAME EQU 23 ; Rename a file +CURDRV EQU 25 ; Get current drive +SETDMA EQU 26 ; Set DMA +SETFILE EQU 30 ; Set file attributes +SETUSR EQU 32 ; Set user area to receive file +RRDM EQU 33 ; Read random +WRDM EQU 34 ; Write random +FILSIZ EQU 35 ; Compute file size +SETRRD EQU 36 ; Set random record + +BDOS EQU 0005H ; Address for BDOS jump vectors +TBUF EQU 0080H ; Default DMA address +FCB EQU 005CH ; System FCB +FCB1 EQU 006CH ; Secondary FCB area +FCBEXT EQU FCB+12 ; File extent +FCBRNO EQU FCB+32 ; Record number +RANDOM EQU FCB+33 ; Random record field +RLEN EQU 128 ; Record length +ITEMSZ EQU 16 ; Number of data bytes in each filename entry +; +; +TAGFIL EQU 7 +DWNTAG EQU 6 +NOSYS EQU 5 +NOLBS EQU 4 +NOCOMS EQU 3 +NOCOMR EQU 2 +RESERV1 EQU 1 +ZCPR EQU 0 +; +; +; +;-------------------------------------------------------------------------- + \ No newline at end of file diff --git a/Source/Apps/ZMD/mkcoms.sub b/Source/Apps/ZMD/mkcoms.sub new file mode 100644 index 00000000..2486776a --- /dev/null +++ b/Source/Apps/ZMD/mkcoms.sub @@ -0,0 +1,44 @@ +; +; +; MKCOMS.SUB - 09/29/88 - Assemble/Link ZMD and utilities +; +; To use this file with M80, you'll have to edit all 'z80asm ' +; to 'm80 ='. Older versions of M80 which truncate labels to 6 +; bytes will not assemble the programs properly. +; +; --> MICROSOFT MACRO-80 v3.43 has been tested and proven to +; assemble them without error. +; +; +z80asm zmd/m +l80 zmd,zmd/n/e +era zmd.rel +; +z80asm zmap/m +l80 zmap,zmap/n/e +era zmap.rel +; +z80asm znews/m +l80 znews,znews/n/e +era znews.rel +; +z80asm znewp/m +l80 znewp,znewp/n/e +era znewp.rel +; +z80asm zfors/m +l80 zfors,zfors/n/e +era zfors.rel +; +z80asm zforp/m +l80 zforp,zforp/n/e +era zforp.rel +; +z80asm zmdel/m +l80 zmdel,zmdel/n/e +era zmdel.rel +; +; +; Done... +; + \ No newline at end of file diff --git a/Source/Apps/ZMD/zforp.z80 b/Source/Apps/ZMD/zforp.z80 new file mode 100644 index 00000000..be6414c0 --- /dev/null +++ b/Source/Apps/ZMD/zforp.z80 @@ -0,0 +1,420 @@ +; + + TITLE ZFORP.Z80 - 09/29/88 - ZMD Public Description Utility +; Copyrighted (c) 1987, 1988 +; Robert W. Kramer III + + PAGE +;- -; +; Update History ; +; ; +; Date Release Comments ; +; -------- ------- ---------------------------------------------- ; +; ; +; 09/29/88 v1.50 - No change(s) made to this file. ; +; 03/18/88 v1.49 - No change(s) made to this file ; +; 03/13/88 v1.48 - Redefined buffer table at end of programs. STACK; +; and filename buffers now EQUated with offsets ; +; from the last switch/toggle in program instead ; +; of with DS directive. ; +; - Some systems which do NOT have an interrupt ; +; driven keyboard may have noticed problems when ; +; an invalid key was entered in the ZNEWP, ZFORP ; +; and ZMDEL programs. In ZNEWP and ZFORP, if a ; +; CR was entered to pause the output, output was ; +; limited to one line at a time per key pressed. ; +; If an invalid key was hit, output would have ; +; remained in a paused state until one of the ; +; abort keys were pressed. This was difficult to ; +; find since my keyboard is interrupt driven and ; +; I could not duplicate the problem on my own ; +; system. ; +; 02/25/88 v1.47 - No change(s) made to this file ; +; 01/27/88 v1.46 - Some changes were made to ZMDSUBS that are not ; +; directly related to this file ; +; - Fixed typo in help guide reflecting '/' as the ; +; flag to force LF in search routine ; +; 01/17/88 v1.45 - First public release ; +; 12/24/87 v1.01 - Some trivial bugs fixed ; +; 11/10/87 v1.00 - Initial version ; +;- -; + +;-------------------------------------------------------------------------; +; EXTERNAL Declarations: | +;-------------------------------------------------------------------------; + + + EXTRN CKABRT,CMDBUF,DBUF,ERXIT,EXIT,ILPRTB,LINCNT,NOFILE + EXTRN NOFOR,OLDDRV,OLDUSR,PRINTV,RECAR1,RECDR1,RENFCB + EXTRN RSDMA,SHONM4,STACK,TYPE,UCASE,NOFOR + +; +;-------------------------------------------------------------------------; +; Program Starts Here | +;-------------------------------------------------------------------------; + + + .Z80 + ASEG + ORG 100H ; Program starts + JP BEGIN ; Jump around configuration table + INCLUDE ZMDHDR.Z80 ; Include the ZMD header overlay + .REQUEST ZMDSUBS ; Include the ZMD subroutines + +; +; +; Save CP/M stack, initialize new one for this program +; +BEGIN: LD (STACK),SP ; Save return address to CCP + LD SP,STACK ; Initialize new one for this program + +; +; Get current drive/user and save for later +; + LD A,255 + CALL RECAR1 + LD (OLDUSR),A ; Save current user area + LD C,CURDRV + CALL BDOS + LD (OLDDRV),A ; Save current drive + +; +; Tell em who we are +; + LD HL,PUBFOR + CALL PRINTV + +; +; See if descriptions enabled +; + LD A,(DESCRIB) + OR A + JP NZ,BEGIN1 + LD A,(MSGDESC) + OR A + JP Z,NOFOR + +BEGIN1: CALL ILPRTB + DB '(S to Pause - C K or X Abort - ? for Help)' + DB CR,LF,LF,0 + LD A,8 + LD (LINCNT),A + +; +; See if user wants file displayed 'nonstop' ($N) +; + LD A,(TBUF) ; Number of bytes in command tail + OR A ; Were there any? + LD (SHOWALL),A ; Tell rest of program + JP Z,OPNFIL ; Just go display the file if not + + LD A,(FCB+1) ; Get first character on command line + CP '$' ; Must specify '$' first for pause disabling + JP NZ,CKHLP ; Nope, continue normal + + LD A,(FCB+2) ; Get second character on command line + CP 'N' ; 'N' for nonstop display? + JP NZ,CKHLP ; No + + XOR A + LD (SHOWALL),A ; Disable string search + LD (PAGLEN),A ; Else disable page pauses + JP OPNFIL + +; +; See if requesting help +; +CKHLP: LD A,(TBUF+2) ; Get character after space + CP '?' ; ?? + JP NZ,SAVTAIL ; No, save command tail for comparison + + LD A,(TBUF+3) ; Any more chracters? + OR A + JP Z,HELP ; No, so must want HELP + +; +; Move command line buffer to internal buffer +; +SAVTAIL:LD HL,TBUF ; Point to command line buffer + LD B,(HL) ; Character count is first byte into 'B' + LD DE,CMDBUF ; Destination is internal buffer + INC HL ; Increment to ' ', next INC gets first chr + LD A,(HL) ; Get this character + CP ' ' ; Is it a space? + JR Z,SVTAIL1 ; Yes, leave things alone + DEC HL ; No, decrement pointer + +SVTAIL1:INC HL ; Increment to next character + LD A,(HL) ; Into A + LD (DE),A ; Store in internal buffer + INC DE ; Increment pointer + DJNZ SVTAIL1 ; One less character on command line + +; +; Open FOR file +; +OPNFIL: LD A,(USER) ; Get user area to find FOR file + CALL RECAR1 ; Log into it + LD A,(DRIVE) ; Get drive to find FOR file + CALL RECDR1 ; Log into it + + LD HL,FILE ; Initialize internal FCB1 + LD DE,FORNAM ; With FOR filename + CALL RENFCB + + LD DE,FILE ; Internal FCB1 contains filename + LD C,OPEN ; Now attempt open + CALL BDOS + INC A ; Open successful? + LD HL,FORNAM ; Point to FOR filename for 'not found' + JP Z,NOFILE ; No, inform user and abort + +; +; Read a 128 byte record into DBUF at end of program +; + XOR A + LD (FILE+12),A ; Start with first extent + LD (FILE+32),A ; And first record + LD DE,DBUF ; Destination buffer + +; +RDRECD: PUSH DE ; Save current data buffer address + CALL RSDMA ; Reset DMA + + LD C,READ ; Read next record + LD DE,FILE ; From FOR file + CALL BDOS + POP DE ; Get current DBUF address back + OR A ; Read successful? + JP NZ,RERROR ; No, go check EOF + LD HL,TBUF ; 128 byte buffer in page 0 + +WRDLP: LD A,(LINEND) ; At end of line? + OR A + JP Z,WDLP1 ; No + + XOR A + LD (LINEND),A ; Else we aren't anymore + + LD A,(HL) ; Get the character + AND 7FH ; Strip parity + CP '-' ; Start of next description? + JP NZ,WDLP1 ; No + + LD A,3 + LD (DE),A ; Stuff a break for beginning of last descrip + JP SEARCH + +WDLP1: LD A,(HL) ; Get character + AND 7FH ; Strip high bit + CP DEL ; Rubout? + JP Z,NEXT ; Yes, ignore and get next character + CP EOF ; EOF - End of file marker? + JP Z,ENDFIL ; Yes, all done + LD B,A ; Save character for now + LD A,(SHOWALL) ; Looking for specified string? + OR A + LD A,B ; Get our character back now + JP NZ,WDLP2 ; Yes, just write to memory + CALL TYPE ; Output to console + EX AF,AF' ; Save flags (NZ=displaying to console) + LD A,0 ; A=0 disables pausing while checking abort + EX AF,AF' ; Save it for now, get character back + JP WDLP3 ; And see if at end of line + +WDLP2: LD (DE),A ; Else writing to memory + INC DE ; Next buffer position + EX AF,AF' ; Save flags (Z=writing to memory) + LD A,1 ; A=1 enables puasing while checking abort + EX AF,AF' ; Save it for now, get character back + +WDLP3: CP LF ; Are we at end of line? + JP NZ,NEXT ; No get next character + + LD A,(SHOWALL) ; Get string search toggle + LD (LINEND),A ; If set, at end of line, and writng to memory + EX AF,AF' ; AF'=1 enable pauses, disable if 0 + CALL CKABRT ; Check for user abort (and pauses if A=1) + +NEXT: INC L ; One more byte + JP Z,RDRECD ; If no more get next record + JP WRDLP ; Else get next character + +; +; Search for a match with search string +; +SEARCH: PUSH HL ; Save HL + LD HL,CMDBUF ; Point to buffer containing command tail + +SEARC1: LD (CMDPTR),HL ; Save command tail buffer pointer + LD HL,DBUF ; Disk buffer with FOR text + +SEARC2: LD DE,(CMDPTR) ; Get command tail buffer pointer again + PUSH HL ; Save it (command tail pointer still in DE) + +SEARC3: LD A,(DE) ; Get a character + CP '\' ; Force LF? + JP NZ,SEARC4 ; No + LD A,LF ; Else LF value in A for comparison + +SEARC4: INC DE ; Increment to next command tail character + OR A ; Anything there? + JP Z,SEARC8 ; No + CP '|' ; String seperator? + JP Z,SEARC8 ; Yes + LD B,A ; Save character for compare + LD A,(HL) ; Get a FOR text character in A + CALL UCASE ; Convert it to uppercase + +SEARC5: LD C,A ; Put FOR text character in C for now + INC HL ; And increment to next one + LD A,B ; Get comparison character back (from CMDBUF) + CP '?' ; Accept any character? + JP Z,SEARC3 ; Yes, call it a match + CP C ; Else are they the same? + JP Z,SEARC3 ; Yes, call it a match + POP HL ; Else get FOR buffer address back + INC HL ; Increment to next character + LD B,0 ; Initialize count to 0 + LD A,(HL) ; Get next character from FOR buffer + CP CTRLC ; Beginning of description entry? + JP Z,SEARC6 ; Yes + CP 4 ; End of file? + JP NZ,SEARC2 ; No + INC B ; Else, show we hit end of buffer + +SEARC6: LD HL,(CMDPTR) ; Get command tail pointer back + +SEARC7: LD A,(HL) ; Get character from saved command tail + INC HL ; Point to next one + CP '|' ; Searching for multiple strings? + JP Z,SEARC1 ; Yes, go search next one + OR A ; Else are we all done? + JP NZ,SEARC7 ; No, keep looking + LD A,B ; Else see if at end of buffer + OR A + JP NZ,ENDFL1 ; Yes + LD DE,DBUF+1 + POP HL ; Get record count back + JP NEXT ; Go get next + +SEARC8: POP HL + LD A,CR + CALL TYPE ; Output CR for end of line + LD HL,DBUF + +SEARC9: LD A,(HL) ; Get character + CP CTRLC ; Start of description? + JP Z,SEARC10 ; Yes, go get next character + CP 4 ; End of file? + JP Z,ENDFL1 ; Yes + CALL TYPE ; Output character to console + CP LF ; At end of line? + LD A,0 ; Disable page pauses + CALL Z,CKABRT ; Check for aborts + INC HL ; Next character + JP SEARC9 ; Loop until a ^C or ^D + +SEARC10:POP HL + LD DE,DBUF+1 + JP NEXT ; Go get next byte + +; +; The following routine displays the help guide to the user +; +HELP: CALL PRTABT + DB CR,LF + DB 'Usage examples:' + DB CR,LF + DB CR,LF,' FOR Show entire file' + DB CR,LF,' FOR $N Show entire file without paging' + DB CR,LF,' FOR ZMD All descriptions containing string ''ZMD''' + DB CR,LF,' FOR ZMD|LU ''|'' seperates multiple strings to search' + DB CR,LF,' FOR \ZMD ''\'' forces line feed and looks at filenames' + DB CR,LF,' FOR ZM? ''?'' matches any character in this position' + DB CR,LF,0 + + LD A,(INCLDU) + OR A + JP Z,HELP1 + CALL PRTABT + DB ' FOR (B0:) Descriptions for files received on B0:',0 + +HELP1: CALL PRTABT + DB CR,LF,0 + LD A,(DSTAMP) + OR A + JP Z,HELP4 + LD A,(EDATE) + OR A + JP Z,HELP2 + CALL PRTABT + DB ' FOR ??/12',0 + JP HELP3 + +HELP2: CALL PRTABT + DB ' FOR 12/??',0 + +HELP3: CALL PRTABT + DB ' Show files received in month of December',0 + +HELP4: CALL ERXIT + DB CR,LF,LF,LF,LF,'$' + +; +; Inline print routine checks for user abort on each line feed +; +PRTABT: POP HL ; Get address following CALL + +AM1: LD A,(HL) ; Character in A + OR A ; Null terminator? + JP Z,AM2 ; Yes, all done + CALL TYPE ; Else output to console + CP LF ; Was it a LF? + LD A,0 ; Enable page pauses + CALL Z,CKABRT ; Check for user abort, or pause request + INC HL ; Point to next character + JP AM1 ; Loop until a null + +AM2: PUSH HL ; Current address in HL=return address + RET + +; +; +RERROR: CP 1 + JP Z,ENDFIL + CALL ERXIT + DB '-- Source file read error$' +; +ENDFIL: LD A,(SHOWALL) + OR A + JP Z,ENDFL1 + LD A,4 + LD (DE),A + JP SEARCH + +ENDFL1: LD C,CLOSE + LD DE,FILE + CALL BDOS + CALL ERXIT + DB CR + DB '-----' + DB CR,LF + DB '-- End of file --$' + + +; +; These next are dummy routines to satisfy external ZMDSUBS requests. +; They do nothing, but leave alone +; +DONE:: JP EXIT +TIME:: RET + + +LINEND: DB 0 +SHOWALL:DB 0 +CMDPTR: DW 0 + + + END + \ No newline at end of file diff --git a/Source/Apps/ZMD/zfors.z80 b/Source/Apps/ZMD/zfors.z80 new file mode 100644 index 00000000..3461842c --- /dev/null +++ b/Source/Apps/ZMD/zfors.z80 @@ -0,0 +1,233 @@ +; + + TITLE ZFORS.Z80 - 09/29/88 - ZMD Sysop Description Utility +; Copyrighted (c) 1987, 1988 +; Robert W. Kramer III + + PAGE +;- -; +; Update History ; +; ; +; Date Release Comments ; +; -------- ------- ---------------------------------------------- ; +; ; +; 09/29/88 v1.50 - Added trivial routines to display the user's ; +; prompt selections to screen. ; +; 03/13/88 v1.49 - No changes made to this file ; +; 03/13/88 v1.48 - Had a small problem with TPA fix which has been ; +; corrected. CHKTPA was calculating the total ; +; number of bytes available for DBUF, but wasn't ; +; clearing register L (forcing an even amount of ; +; sectors before initializing OUTSIZ buffer limit ; +; comparison word). This may have introduced ; +; minimal garbage to your FOR file if your FOR ; +; file is large enough to fill available TPA with ; +; ZMD, ZFORS or to the log file if running ZMDEL. ; +; - Rewrote OUTCHR routine in ZMDSUBS. ; +; - Redefined buffer table at end of programs. STACK; +; and filename buffers now EQUated with offsets ; +; from the last switch/toggle in program instead ; +; of with DS directive. ; +; - Some systems which do NOT have an interrupt ; +; driven keyboard may have noticed problems when ; +; an invalid key was entered in the ZNEWP, ZFORP ; +; and ZMDEL programs. In ZNEWP and ZFORP, if a ; +; CR was entered to pause the output, output was ; +; limited to one line at a time per key pressed. ; +; If an invalid key was hit, output would have ; +; remained in a paused state until one of the ; +; abort keys were pressed. This was difficult to ; +; find since my keyboard is interrupt driven and ; +; I could not duplicate the problem on my own ; +; system. ; +; 02/25/88 v1.47 - No change(s) made to this file +; 01/27/88 v1.46 - Set MODE to 255 so ZMDSUBS knows we might be ; +; running in local mode and not to worry if BYE ; +; is running or not when CHKENV checks to see ; +; which clock features are valid. ; +; 01/17/88 v1.45 - First public release ; +; 12/06/87 v1.02 - Fixed numerous trivial bugs. ; +;- -; + +;-------------------------------------------------------------------------; +; EXTERNAL Declarations: | +;-------------------------------------------------------------------------; + + + EXTRN CLRLIN,DECOUT,DSCFLG,ERXIT,EXIT,FILCNT,GETDAT + EXTRN GETDSC,GETFN,GETKIND,ILPRTB,INPUT,NEWNAM,OLDDRV + EXTRN OLDUSR,OPNFOR,PRINTV,RECAR1,SHOCAT,STACK,TYPE + EXTRN UCASE,NOFOR,MODE + +; +;-------------------------------------------------------------------------; +; Program Starts Here | +;-------------------------------------------------------------------------; + + + .Z80 + ASEG + ORG 100H ; Program starts + JP BEGIN ; Jump around configuration table + INCLUDE ZMDHDR.Z80 ; Include the ZMD header overlay + .REQUEST ZMDSUBS ; Include the ZMD subroutines + +; +; +; Save CP/M stack, initialize new one for this program +; +BEGIN: LD (STACK),SP ; Save return address to CCP + LD SP,STACK ; Initialize new one for this program + +; +; Save current drive/user +; + LD A,255 ; Get current user + CALL RECAR1 + LD (OLDUSR),A ; Store it + LD C,CURDRV ; Get current drive + CALL BDOS + LD (OLDDRV),A ; Store it + +; +; Disable sleepy caller timeout and set description routines for up to +; 50 new entries +; + XOR A + LD (DESWAIT),A ; Disable sleepy caller timer + LD A,50 + LD (FILCNT),A ; Allow up to 50 descriptions + + LD A,255 + LD (MODE),A ; Tell ZMDSUBS we're a sysop utility + +; +; Display program name and version +; +BEGIN1: LD HL,SYSFOR ; Point to name of this program + CALL PRINTV ; Display it + + LD A,(DESCRIB) ; Regular FOR descriptions? + OR A + JP NZ,MAKDESC ; Yes, skip next + + LD A,(MSGDESC) ; BBS message base descriptions? + OR A + JP Z,NOFOR ; No, descriptions disabled + LD A,1 + LD (DSCFLG),A ; Set flag to show message base descriptions + LD A,(PRUSR) ; Get the private user + LD (USER),A ; FOR destination + LD A,(PRDRV) ; Get the private drive + LD (DRIVE),A ; FOR destination + JP MAKFN ; Skip date stuff + +; +; Get the date of the upload if suppose to. +; +MAKDESC:LD A,(DSTAMP) ; Datestamping description header? + OR A + JP Z,MAKFN ; No, go get filename + CALL ILPRTB + DB 'Date of upload: ',0 + CALL GETDAT ; Get the date + +; +; Get the filename (no spaces with '.' filename seperator) +; +MAKFN: CALL ILPRTB + DB CR,LF + DB ' Name of file: ',0 + CALL GETFN ; Get filename + CALL CLRLIN ; Clear current line + LD A,B ; Get filename bytes remaining count in A + CP 11 ; Anything entered? + JP NZ,GOTNM1 ; Yes, skip this + +; +; No filename was entered, check for save or abort, else continue +; + CALL ILPRTB + DB 'Save, Continue, Abort: ',0 + CALL INPUT ; Get character + CALL UCASE ; Convert to uppercase + CP 'S' ; Save changes? + JP Z,OPNFOR ; Yes, write current buffer to FOR file + + CP 'A' ; Abort program? + JP NZ,BEGIN1 ; No, get next character + CALL ILPRTB + DB CR + DB 'Abort with NO changes? ',0 + CALL INPUT + CALL UCASE + CP 'Y' + JP NZ,BEGIN1 + JP EXIT ; Then abort + +; +; Display file descriptor/upload areas and get choice +; +GOTNM1: CALL GETKIND ; Get file category for description header + +; +; Show the drive/user +; + LD A,(MSGDESC) ; Using BBS message base for descriptions? + OR A + JP NZ,SHOWDU ; Yes, show drive/user of uploaded file + LD A,(INCLDU) ; Include du in description header? + OR A + JP Z,SHOWFN ; No, then skip this stuff + +SHOWDU: LD A,(DRV) ; Get upload drive + CALL TYPE ; Output to console + LD A,(USR) ; Get upload user area + LD H,0 + LD L,A ; In A + CALL DECOUT ; Decimal output routine + LD A,':' ; Output colon + CALL TYPE + +; +; Show the filename +; +SHOWFN: LD HL,NEWNAM + +SHONM1: LD A,(HL) + CP LF + JP Z,SHOWCT + CALL TYPE + INC HL + JP SHONM1 + +; +; Show the file descriptor/upload area +; +SHOWCT: LD A,(ASKIND) ; Supposed to be showing file descriptor? + OR A + JP Z,MKENTRY ; No, get the description + CALL ILPRTB + DB ' - ',0 + CALL SHOCAT ; If so, show it + +; +; Get up to 7 lines for description +; +MKENTRY:CALL GETDSC ; Get up to 7 lines for description + JP NZ,BEGIN1 ; If none entered, or incorrect, skip next + + LD IY,FILCNT ; Else point to maximum entries left + DEC (IY) ; One less to go + JP BEGIN1 ; Get next one + +; +; These next are dummy routines to satisfy ZMDSUBS external requests. +; They do nothing, but leave them alone. +; +DONE:: JP EXIT ; Exit routine can take care of us +TIME:: RET + + + END + \ No newline at end of file diff --git a/Source/Apps/ZMD/zmap.z80 b/Source/Apps/ZMD/zmap.z80 new file mode 100644 index 00000000..4af048d8 --- /dev/null +++ b/Source/Apps/ZMD/zmap.z80 @@ -0,0 +1,268 @@ +; + + TITLE ZMAP.Z80 - 09/29/88 - ZMD System MAP Utility +; Copyrighted (c) 1988 +; Robert W. Kramer III + + PAGE +;- -; +; Update History ; +; ; +; Date Release Comments ; +; -------- ------- ---------------------------------------------- ; +; ; +; 09/29/88 v1.50 - Initial release ; +;- -; + +;-------------------------------------------------------------------------; +; EXTERNAL Declarations: | +;-------------------------------------------------------------------------; + + + EXTRN EXIT,ILPRTB,OLDDRV,OLDUSR,PRINTV,RECAR1,STACK,TYPE + EXTRN KIND,CATADR,SHONM4,WHLCHK,DECOUT + +; +;-------------------------------------------------------------------------; +; Program Starts Here | +;-------------------------------------------------------------------------; + + + .Z80 + ASEG + ORG 100H ; Program starts + JP BEGIN ; Jump around configuration table + INCLUDE ZMDHDR.Z80 ; Include the ZMD header overlay + .REQUEST ZMDSUBS ; Include the ZMD subroutines + +; +; +; Save CP/M stack, initialize new one for this program +; +BEGIN: LD (STACK),SP ; Save return address to CCP + LD SP,STACK ; Initialize new one for this program + +; +; Save current drive/user +; + LD A,255 ; Get current user + CALL RECAR1 + LD (OLDUSR),A ; Store it + LD C,CURDRV ; Get current drive + CALL BDOS + LD (OLDDRV),A ; Store it + +; +; +; Display program name and version +; +BEGIN1: LD HL,MAPNAM ; Point to name of this program + CALL PRINTV ; Display it + + LD A,(ASKAREA) + OR A + JP Z,NOTDEF ; Upload areas not enabled, Abort + + CALL WHLCHK + JP Z,BEGIN2 + LD A,1 + LD (SPECIAL),A + +BEGIN2: CALL HBORD + + LD A,'A' + LD (KIND),A + + LD A,(MAXTYP) + RRA + PUSH AF + + LD A,(MAXTYP) + SUB A,'A' + RRA + LD C,A + + POP AF + JP NC,NOTODD + LD A,1 + LD (ISODD),A + +NOTODD: INC C + LD A,'A' + ADD A,C + LD C,A ; C contains letter of 2nd column descriptors + + LD A,(MAXTYP) + SUB A,'@' + LD B,A ; B contains binary number of descriptors + + LD DE,DESTBL ; DE contains address to 26 byte table + LD A,C + EX AF,AF' + LD A,'A' ; Initialize first entry + +SETLP: LD (DE),A + INC DE + INC A + EX AF,AF' + DJNZ SETLP + LD A,0 + LD (DE),A ; Stuff terminator for display routines + +; +; Main program loop +; +LOOP: LD A,(COLM) + OR A + JP NZ,DOCOL2 + + CALL ILPRTB + DB ' | ',0 + JP ATEND + +DOCOL2: CALL ILPRTB + DB ' || ',0 + +ATEND: CALL SHOWFD + CALL SHOWDU + LD A,(COLM) + OR A + JP Z,ATEND1 + CALL ILPRTB + DB ' |' + DB CR,LF,0 + XOR A + JP ATEND2 + +ATEND1: LD A,1 + +ATEND2: LD (COLM),A + + LD HL,(TBLSAV) + INC HL + LD (TBLSAV),HL + LD A,(HL) + LD (KIND),A + OR A + JP NZ,LOOP + + LD A,(ISODD) + OR A + JP Z,ISDN1 + +ISDONE: CALL ILPRTB + DB ' || ',0 + LD A,(SPECIAL) + OR A + JP Z,IS1 + CALL ILPRTB + DB ' ',0 + +IS1: CALL ILPRTB + DB ' |',CR,LF,0 + +ISDN1: CALL HBORD + CALL ILPRTB + DB CR,LF + DB ' Use FOR/NEW to view recent upload description/log listings.' + DB 0 + JP EXIT +; +; +SHOWDU: LD A,(KIND) + SUB 'A' + RLA + RLA + LD D,0 + LD E,A + LD IY,TYPTBL + ADD IY,DE + LD A,(IY) + CALL TYPE + + LD A,(IY+1) + CALL SHODU1 + + CALL ILPRTB + DB ' ',0 + + LD A,(SPECIAL) + OR A + RET Z + + LD A,(IY+2) + CALL TYPE + + LD A,(IY+3) + +SHODU1: CP 10 + PUSH AF + + LD H,0 + LD L,A + CALL DECOUT + CALL ILPRTB + DB ':',0 + + POP AF + RET C + + CALL ILPRTB + DB ' ',0 + RET + +; +; +SHOWFD: CALL CATADR + INC DE + INC DE + LD B,23 + +SHOFD1: LD A,(DE) + CALL TYPE + INC DE + DJNZ SHOFD1 + CALL ILPRTB + DB ' ',0 + RET + +; +; Show horizontal border for top and bottom +; +HBORD: CALL ILPRTB + DB ' ================================================================',0 + LD A,(SPECIAL) + OR A + JP Z,HBORD1 + CALL ILPRTB + DB '==========',0 + +HBORD1: CALL ILPRTB + DB CR,LF,0 + RET + +NOTDEF: CALL ILPRTB + DB CR,LF + DB 'ZMD Upload Routing feature not enabled, ',0 + LD HL,MAPNAM + CALL SHONM4 + CALL ILPRTB + DB 'aborting...',0 + JP EXIT +; +; These next are dummy routines to satisfy ZMDSUBS external requests. +; They do nothing, but leave them alone. +; +DONE:: JP EXIT ; Exit routine can take care of us +TIME:: RET + +TBLSAV: DW DESTBL +DESTBL: DB 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +ISODD: DB 0 +COUNT: DB 0 +COLM: DB 0 +SPECIAL:DB 0 + + + END + \ No newline at end of file diff --git a/Source/Apps/ZMD/zmd.pdf b/Source/Apps/ZMD/zmd.pdf new file mode 100644 index 0000000000000000000000000000000000000000..464b212e1185d137ce51202cbdc88e4be48180b0 GIT binary patch literal 83801 zcma&MQ*@@y7A+jxwrxA<*d03^+jhsc?c|NkH@0ot?AV>;|Mni|od4px*;iFhJvCO1 z8e@%FbIrLZmBb|(S(!NCC`Yg2i{L0JW$fJ@OpIKu9PIf>9F1HpnJrvM7?l`F7)@10 zBqdb-8O0pz{`p~LY-MZZ>LqXHZe}a^PXH1|r~k80K~YIWT~*?rouVRYV$vjxF5(gz zGGY>fj;7`w&Q`8w|E##UnmV|-3jX+Ks_5qG=;kVEWo!1I&;RA5A8?!`EF=!b)^Gv> z%<^XT7Os{gT>q@7F#i+9)y$cMS<=?X)lA&X#KF`|NC?iw)!EF*4$dT19>{g?DkB|KDQ^HY%RV1SI=K7#IXAXj}?zH zkSG!m2naNiI-kA^;i}6Tq{fbC_cBG0=1)lbDkBHw8C^l&({`lGiA!;1)&hP%dOJu6hHc=Gx8Qz8rqN$O)|2XfWD5!?m$%K~2oPehRucDkU`Ay=>cT0A2MehhyT5%L6%s@)f6IFy23~DUV zJu=~uN_J(Io&l2RJM7nhA^xiHEf8nizJKT3OR1d(fCg;2HdJ#${SdvB;x&Xgl1_3VIZes| z<4rxn8Ec@}6mF$#2XQ7H$4aP~LhvQ|$i!U$!uCs+iJ5+A8um;)>ykK7YJ)eXz5Q;N zX*UqTVC)C<6zfrWO#`;pVKQL22Cly(#mueNgaEMD%c*YeKzn-c1bH zUd=2{2V{~if|dqPfno_wBC?Y0!vg0j0fF$o-cA=R=O(A3= zu@IW-^ff2og0ed!=z=r}pP*()jn?}Z{#hY{3TeE1W&m7zZyb{>DAeJpjBM?IY1{ z9(_`4O_XRFArC?{SAe-a0YqvF*hep5t30f>jO58fSAJ#FnB^2)u%pm@{8ffTs?fG= zTCKD$LToaVZ~@6dLVgLof$5#a>*pqW;&=Z|I`@YjU%XL?8|!4*<;Kle{`P8le*WV{ z)Gbcd$g`hh*~nU*w4*QpT|?fL*X$6wTE*DOj6XvW;Sb>|0TPg72fIA9rdcY4NVI+K z5lWq*v$OU7nRa6rwb>$s6tjG>?;-f!TyRTcH+7B=bk4b?+tI1la$AqpI9*V%be>V5co5{rR< zZe$}O929pW?-G*<=0pLN%I^`o?B9G5d|~`^*G|Lb)=+555x?4eBxvGh-HB1S>`68V zf=3iuh}`(+vi5hXdC25p=bTGfd_#GpzG&YcF8?yG!L^H>=u8e2DU(clz1yO_Jod|S z`O=BIcM(Ii?+(x5jL{_&?PyK+mgy;Msrtr(A6f>C(ZfXeQ#EAVsj4g7Cd3W)o9hhK z9#CGUyIOCmnG75Ol!>r@cpAWgb4pO&(0638QO$(v`_x-v!ap-nXd@hK%d^1s= z?-3?{BCT96TuT*-pUxk~H_>&k0@hK>sPU7hz~Z0tK|PJiio8RJj9#D@nn^l7gK)s{ z2+zT>PXgtqsSbK!Wa|YE} ztNjF8#q8cU5KdvAhQ6 zmO;E9?8SxeUwu*3OV;mWe9@*zUU^xE76!oQx9f|g{f-#oUNvRlF-5Pfipf+m=`4sx z3NZkwZT3m&1u+uKt5(huY60Def!k0}s0iyv*9LOeP)B*6dV}g5L0q0H*6ZNb{?XeX z*=O6cHyG^4dee!!bkR6#-LTu(paw3%nIn_qa{R_}CheI#C&FI1Uq4tgoauWVNu|D7 zf_CorcOoAq5D}{!8cva_=)3de*P6L4ANYlA3cB3{$OEv#BQ31^mN2wfOTw*jU3|;$ zSPR1&&5beflIJ9B@qu$5cE0objCt#1dGkt*2e=KnBYO%tjZ|?Y-}U_?BQCqB>`7bU zhhGON<2(jB(@0`DI>pnaD#XvwHU#W;>J@34zuZwawIxN)0`!Lz>T2P9y*DWs=>>&r zkYfBL{r?s~3fUNsnigdasvRWVVk@~>3*JfiU%*bUaVW_`+Zo}8PO{lYKIm0hjPk`L z3aE2$*dk&p+dc1o|4)4?7_ub+OYyS9Z zYXaeE+4G{AW%7xBN{qnCbyYa)J`}*-NY*Tz2G&v`*vVq_@HF!xOODtU%%4ENr;g$! zFz;?_J@gK!Q~*{BP!klflJHDN?OoDejtd{_tW>|k*t7R^OqIemwdmIC;%a?73Z0cWBd z;6QNeRH%|&4o1<_RY5N3bcrRbVTUkspsWReMUBx?LoP9Dg?~>W@LAaqW(snS_PkEo zAJo-Wa*1t;m26R$%{f%m&US~#dR5p$I9)OvEjBPRz&!xYhhXkYq_v`4;(jmx-fd3C zR4wGX8D>&OiJ9&Pq#dr5w(r1GpO>#vrqU?pr2KVh1`ZZm1q>Bg%6tC$Xmy}Ac@v0v z;1t>{CsPRT3Z=r7uyxoJR0bj&xuN7(b$~W$6Qp^xJf!^bJQt+-MT;csMi3E;AV+=n zT6BKj(2xnDu0P~dszo-wp}m03d*7qeELpTllSRY>@6>4?xk7*pjQnN;hYjcltW$Q* zqsi*%{1ev@uEkI%x2dVoZ}|#gU+@Ac;cy-iu|A0KncuQ3bXp#Qg-~g6s+vPbuzIYA zm08?TJT3hI_|vo~ilpbQERa1XUtiGpbxV&97RyE9@RK2@D%G6VZ9`ofum*rv!$d(U zU3R@l_bx8e_0h8$p0H|gZA`XOwguF60lD0zWwo31a}@6(LK{40*EHpJ*abu#Q1Q0@ zWW1~}%9mva{5`<%l={~8*jLyOnqonV%i;h(nA&oO?T-bv zDSB(0aV(T}g(hrgiRs83@VDzw8FJ|B3^({|i1&o=;H5oMMMMu~!=M{XH_oKLeUO=}Gv(|f%4g>|&7XE|Yd zI~iWnr7auGQ$-vN4#NgMCq7k|y15jc^nGTs7k4}@J2uw%UG@y#YP|t|M$zB$>crNz z1>pQ?8Zc4}(CXi4@9B6NmHzvWb%p*lcX7+)d?;AXoqQE~@!OgfbS9@_$Fv#GNAEcN z7{RCzRo!i4i0_w)c=2>;2toJjUeh~2jD-5l4erxmWlETxWU-ULO9An*wVb={4Sm^m z%-XKJZcQFjT(mum5?BD{yPo*K^`xcdB_Rj&pAX~4Rni0-IHcLg>Eeqy(R76`jC=1P z2Z|Z0qE1JQg*V7V9TU<>0gNm$gM*d!Yi6pV(=Rg}4F=7L z6w{-ql1b3O9IQPbh4mHqXCj?O*G0d3B^t#$cUEX)(ohM~G*3acIJQB-;oPj(^fW+r&GoyO>HRSOp>QzB+YHeW-$LMP-N$iYw{x7@;-2vKVOMD)Tp#1^Ur)BG( zPy^lGEiS#Y{5vt@A?PF(4cEzDS|NY(2l4~8X}-%)#L${@Y~(X;oTzSy_b8rkTueTy zhJBpx0JkxJ1q^R7aaJy(^#1&(2V$lMu(5xG-+w^uKj6p8%ER%0!H@0Vf&kn9CJ1P6 zs%)~L_`YbqvYszVCe?XO#d6uTbKp#1yn}$MY)}Y;P2Ik<%;+SFUxm5D0oXIAs-Mpq z(~h3jV_gr~@;l|fqMTXWx!xoBb9ko+`M8m!ighu98#X8Ik*xIhxwV2j*F(j?OXQ2z zrblL!r)zx3jO5bS3WI2TwSu{1gvbL`@Bl3yGtJJlPyLwUh^mDrBe67+?~kYw&4rF4#%gq_3$y#ud; z(IE#E^_boR6u>OS}9iI@@i~S^B}I!E7z;A7u!y=hPIZf6f764 zAQBav+EpjWO!F=O@&`=h9#|FaZrUhXQmSv1ti?Yu2`)!GqJWZ^`huhiT`Bo7<-v>t z;CB%(Sd)o`ut`K=A=;ZzdgkKO5rv*~f_ov?H1pWHYraSBdHZ++LHh#wVEbSL;RE2o zR0FIc19by*!O#O>1Bry|w4mdggJb5|m>S%n93}VQ_M-Z*`ZPrH3QHliz~GYcovOQCOl=-)hIG_ zy99q6cO1I^<_SH%0Q9r1fS%D@q!y6u<%9f?Tfnk0BM(p>XZv}ghIp0=YAT0S&oOHW z+B_vNc%VmQZ8AIB`A;-il%XsS%^VMtawp205yx}S=}t34C>DCsez}0jq+SIqbwtpx zi9Qo1{fShpNbO%%KWn_f(#~xHF3srcGl1Uk81aim&JMK55Lmjp$lDNW44ypmGd(Hc zj0^spL|h$Rd`8t?p2J=|fmv$zj$r8uD*}I`PVJYw+~6E_2IlCReo0d?UH}!Q8PJ+x z-jVidLq@N2miHzu0Ns2Rtp|NVeSV3;(bO*W65m7pq#x6miR0sThmThqJa1Weqjs*S zPkMxd;|~>wSx#2&zqspwcq5#8U~jFJyo^ksA0reB*m1{EQhuznqe6Js)E8~D1ZDAG zCus3Si=&FN7yP0Z&q()zrcmt`?iZDGXCw(Ub40?qNHMjKDjk^*yx*T-Upj3J|3aJp zqR4;IhK1w*$&YORrdn+O6V>Y0)z!rRul(pq{48wV_c(07=>?AFzTOuGs%TD~>C4i} z+TE}n$85A_(95VUqn8ihLgHN?a`0X9>R;O{mRy{rObo8v zh03d1=_-B{ym~F`@AAngPh4qTjsEk~?$SYxF}!ruI3x0?{OMx%)Bmbv5wLDY(9#k6 zWm#vt^gX+yoRxa{A(kW~KdyLeZGP?k7MonI#}p^~OZlK!;rG%OA?0=wzrD&?P>^+v zoAGqZfw{T$v|E3UwMgOzbg^6<72{%beAyURV~jJq3iL@~hra$$g6=?DMzHG>SdMnisO`tpo#6rl8HWsCgX^8g`_T=uuoivSn*03 zg90loBRRT9R25hasm&M{haqXocE4zGy5u6a0gB$>%^22fRBV*aQl?O5^d#UH4cTgf zl4=W-11yS%a3}u7NiX6|h&GMh^i}WKO5-`wNP`Z4FQ!h0ZbzR?PY5PTm!wuIc!p>R zKzq5xXCiV?+V4NkRL6NadbSsqAzhdMq&SN)}rD_YqCBBPiGU(`$ zp__Mk}45khFuTtU+0aAf_Dmt%p=D!CG=Jmt~Ww=hLO4^;^Y>200?i`Hk$w z&=C&%1U(@{3_myaVK69LHL273?TlJ@!yI@cnVN(olgw8^X@`otAU58as_2_cc2d2V zqsrgmse!HPfUFzt9w1}~L9Oqhf zJyfT+G%%#ppwtJ4Skmfvk~XhfEjPmozlQMM<{w8J2Z|Ira9E2+!}Rhsn3Cy3Ft#jHg`d~h=8I>O_&Z>cBp(Z z%nR9eUKHvE)+Ux76xhve#g1ljhe?m?4BJ2vxC7m8JrhP_G88}wXGvYgEW~tZaes|~ zt>I=TM!x8`NR)ZM_^s{&2S~oO{gGun&_Z%6e?v)&S1|Mrs=2Fc4XvNhmocZ_n$f)| zDc&9iTToW={c6AhG6GyIelEDlGYCD# z_hZO)rt0K{su>$$2)b?53S+DsG?W?V6WCB2-e`x~RA-o`5A!1Biios!4#2MU=PU{( zZgcw&_QIve9FNuaV5_IERKuRhMc?_zqEHZ5C*3rAER~67o%zrw+K<+k`wy)wJJPcb z%{i*V4o9J_8dLD+rEPkz%^Ddvv*6(14JJ@F2L8+wVYV1Kzf{}Y3_+C05bP@tbr6Ul z_}F5VU-K4%G-jx(KG*CUp2$tdkiLJYbh8n<$$H^HdqAK$PRyR=-)OmP?hgQ1kMb7YaYNO9;x}i zP>rkmqt=Kemxly`p@q|t6sA3@KvF+@#9nF1L+2hAP%w%v47dw(S8;ryKjJiJ%57;` zVv*=#6~=tu7INGU%9|ui3x_(O?38E8D|wm$SE2UrL1J_#^0@bjkm?2gVNE&?SWz9e zBrdX?4cl9Ykdbr`9RX3##Ac;z3LUNHA{G3)5+xdY#PR*CGo*ZK?W>h~!_l()^)?v> z$KT-0PdfQ`uV)|aIHvfajzgeUQSp+0@Tl02me!*H}k zsDiu@uc6|ErMr-)ajA!}X2agnK&w;5!qiV93Q}oF;mD3mFpZdO!&_K9Pe2b1g~i|V z<4OQgHQyCz$VHPCtZtP$tr`8+PxX&&K%ey$FSC%%YNLey#)V=WK98$Hdo!Td+)Ii_ z@Jbsln+6b4p0AcLH(T%lBfF)hOY0LROpKn}ZQk(|D5aNI%wd5PT)xU(7;J@LA-Cc^ z@(4!&w|O-NSsi@k3%?V4@uMN4l?I+{>_qp6GH1ffNnE-{<+}VMMpv^+s%gP#)hf`m z1*1cM5M#_`BQT?!T~Nik|9&>LR2z6W(ty{H%i*|F^ry#S_Nf1vYM0=%vscD zaS&W2RlY#V{x7a%_8ZbshAh$4n1cAo^N2!w*U18<2?+yP4GvjhP%yLy$geoWDbV&b z1rU!8MN?RTYI#lls>DV9LV@H?azf<_XxeYB@?}xOm|!0_l$4pXWR6>6!7+L8z^TB! z5x25!rIJgskHW<%oI_#+&l*n-iJ6MP_6sryJ4a1-B(0G)e6~C)Rq_Z9d-zSaO-FRf zi{rUs5%Vw-G$UD~)-9!R+C(|BpJX=?IjCO9>O2pKiS%T)EW^sCg1P9vP$PD<{ma#? z?>sP!$&uR6T^S{@Yu*|g=&p|-MuxsWjDfWsU8t)Cma9!AAG?e|ac~x3Fi^!n4_&|I zFxuFibA|?8O3N#~EtPi0j56&|W5Znl%a#>Nl9l~zT3{WetnFlw&c#9&bespm#$+>a zx&jr&rNjpD^}ULTLm&uPWhr^%2*2z)jmM#P^%FHxchaTwGDr&NqIt^;TNU5%a{T&) z=(BXKQ^vo7H4M9FAq-b;+V>`t^DrRm`KHvg&6frl)2fl1lisPcXv#LD`wBItORMPCqlpHp$-E^y z=Aub`F8LvFP#U-RdSv47*~#n%t&Y#v%#x)^L;TWwy6@!)z4*OpsuaAC*&X1?lTAQO zJpA`$#%;t!>G;kvw^xJRA^6xpYvmE-cNJimSg-VSQ%sm1oyoe@?)9 z77ncz1hM!HRNe${PuwU$)|#WddVPxedNDnf+!4iIVawNrOw`BaZd51EF|}JLK3sP` zp8wsHtp|7@On0udwG?mf1AzRP25*%lM>E!jX39TtLXuS`_d1|wH!7MqiLQtW2ewvG z6^@YfSy!?tJ=Jl$B1I0vY6A1xjFH12+L2=>>p-2U|ET+G*Zk`EQp{&)^}}on5R>6k zTqsGri{jrMe`CtO6;<j7H^Z8z(bu7sS%rZsQgfZ5 zDD+%ARIAY4t@paOQF{|7;ek}ZjxW*nGE$xA!s8Je?OngoBNexx8mW4N5T+|Pp2hpr zGH@A@@W!1h#l`fO{4v3C_=jDn)i9!g-z`3>AIDlXEXE-s1$*-cd;CjnYqhEqnnLJW z;rt(Ob2LW<^KWhhO;U<;mhl3WtmdGO_!Dd;Ur*7bZ=7rAaz+vtSh#*)gmXQN!#&8W z2H)Z%+6wO{yx$?gz!i}k=zO)VJH=fK2Xv(dc5vTW-unQVk>37D|d5gI`#is3OWPK7H8WMr#p#)qd}NUDUJVN^3Z))a`@7tgG2Oh$Jo8aYsGAXsS5|A<7WC; z1Q}G7D2E9CA)WOGP5d1ZoWFF(9K*x(95)ubZlmpfk|JnB-K|9Z290#g6DWNbJsyw^ zJhUsg%#dB?c*Nl#HK|c6%8Qw8&oc^c(cP=>fKFvYzUjg%QUxxS!#jiQV1HY_Ny=Fe z@rY?In}NEai0C?3+C1o15rSEa~I#dO3Pw@S@Pd z?`&xZ1}48u$#h}iq{$}QdVecJ^eNXD{(%365rZf&>*08x!cP@@>3v(McPF84u?}oPSr~S* zpdv7d+aAAdF|m*{gSAI*G+$>GvF82Y99x8-h%dW^phoYK^WWV4DEa$A>?ND2`0wiL z|7tz#>|Fn^`eOgL*2DfkX+7OK|F`;jWn_Vt_t>=G{E#GBXW3_ipP6FF^t~l?_#M3& zcIf}3rZTCcCk?@zy=nB+wSpp*Le4F{N3+QKSLBqUp})JM_iA-aQ*b$((5oMcs)>ZK zzlPWDIDhkflr_KgXzI*a0PHkyU79|3y?=FZVqRt;h79~rho0~6`58rt1A8PAx#-5D zV2Q)Qa`V+H3eN>arBpa_h~DB$5dRh`8{kGdRkKo{%wcy0hmEMXDvP_)d?Hr?=kEn# z_PmGaZYbfPYbqd48^a*4HGnt+3Q7yD5rM=aBCMY9%RCsuC{xSHI!K5TQ_WFzvX9cEbN}fiIgI=GbAdo30$E6P+hTF6*tpIT z3QL8B6T$5L%Xo}SZ7bs-DvDn9mx_~B6g(Z1JZrIWDYnU%^MXCjS2X(9BC|ZsBdkBZyimhvFsO9#nO zP@-bUZ@A`P=+3c=w_hc1V;?!S#`QT;2W?KTQ-%YE(*?78iTw#83Uif-aqMe3{Q>m( zILWQF!9OE@ZD4`)h|Ge@H=9?Fu5pd(97mQm!#@FPy?&?Mpj)|Y6jIPC`1LW0K}Eo3 zu&a|ZDcDXk!k;NcWAI;RD8MjJVyM?*58wtf1^;u+DcV%Ieb3>a*jvui!Wm>%!AeYBBR4b{N-@w?4;ag31pr0Kd!`mU;=VXfFHQrZ^bb| z@*7yM*)eR<9*BRQFln)(2+DNNm1C`uNyITM!PRs`WyyVAn~#+W;Vp(5Bz z#;Uk`P=uzT=^%;GR{B8LV28qq3YqB7mCx^HRHt$)^_S+})82Yi z>AtrZvGcoxO4@**(Md~mxp`Sv{jQ~0jSnc z?W~WHg`At`G6}NL8~&?}f)KcSd!5E*%DRJR^Y79T6C&DLt$o&Pb6?LX$kDCaN*{G4 zU|8s0;=nD zpEhDg*C0xN+UCm4gL;qs34yjMFF4TM{_~XZULW8dmSBulANX4_3k&ZfHD!S(Ma4Ue zBFWUBjlcETC;IQ=QfU{UiO4~TYPk0vB3DybRg-lxFKtCSDeb%kTW=P#;ei2hJrmPY zYHumXiiqw!?}|v>nWa{Cfo%mJDd(2|ml}FEUMsip%7IZe8<1PVQP-K-YIf{ zXq2Q?EI~h=Xl}s=kIXZqq+s<9K`t`+=qE2O@RGkMI@1{bj9<(nAQfokso~o{>hl~k zt}}~~(RBk!{dFuq*A`!WbtkhLojg-*XvyjnslSAnhZoUK**aKhC^~*wnSxDqJ6d=H zZJgx=Mr$b_<3REwjvP99`FG_0vzQ7>I)XjU7JI_pcT_*IRJE2*LHY+%hcAv_yy1fRiEdoJ4Dh&xy(eHw<< zw+d2d6{#5utW{vTaXdy6*SDO;4n@#k@@e{)$q7}3Cw>lviI|czpeX9E19HINFEECy z(~9PJ&r9b|m3a6(lo$RC5oaNxKt@+o?t;+B1)%u=*o>Flc?2W`t_(PVtbey6%T@8M zs&1!9sL*$5iq#2V-fE?yr86nZ)%Rna=`0x@Q0yAd4+UY zw8fn6OzB%Km2+^)Lj_Xn2nS*&^hC#c1%+0TBuXEg z7tWfXrC*4~DnDTB1Pv!tLlw)vMiXzsMmrRJ7DA!$n)Z>{76Ipc68?Ji#rJ#LYnwL8 z=zZe3%SItGOJI!g$%gxO(35)@ zvL1@tHC}-sQbFefeHW}jlq`$Y+X)1*URS#wdsyrEjArsSf0UNnan4{J^avB~7E7DO zoRQ;jU#UO9so!wWaJXRBaLnYT@Dyaj6cg9PnDV~Q%aR`s29NV|HAql0q`Ti;2Bh{??$5-p-6w&k4TdKey-apa^;Fl*4o zmlQ4l@#oqTf(cjPwSR{dB7GkyW+zYj?QcA2b(S)+H&91HMIain3cl)#6NN%;2BjpR z;%6>4)qwc+N^hT>EM(zm!)v-~cN{O^jIiZuoDyN}`xaEsIq6Zr%v;NGv+EjpR{I_U zk$Dih_1-Qwz(HpZ`_R})MS)J(Lv1UrA^AzrP$;aYTo1=tyM0#)nxH9ZW}iY2DggxP z8SXa^jZ4r~=FEEZYD^tQEJ{WZaGly{FXKY%L$4nvy1q_-c*%>OL-zW3EwDKMfT`0@ zgWg5?DOcOiWKfo}0HYTV9iv(J{51iLeeWO1t8<&8-vV|o^P;aV{Qed(NJf#1)$aJMAiPvpj1nnI4t2(X`$^8})5CN~3Tk%da5d7t zL~F$?wrC0AL?GPEf7tL<6rRx%yNfdW^S;qSjUQyf;_P9gydgCvDVV$tN{A()3<8J% zxb{d?J)R*-Z^R7LI_y@Y{L_+-vTK6){VWB)+g$1MM4YeSB0jY*Hc&83y=h|W0}vzK z4|Ey%g9GFK%T$V2K51`Q?Q3R@Gg$YZ_d*E6E)2bESm`=$8A5@u2AYTfYWUS}>7*4e zwCo+`bBi&Vg00Jf0Y{=(PzS8fqQYBts%Je}lUutfq2&{5N~ebo9)h>0XT^#scLk%R zTK#onvB#zX!Mv1bg3}J~+Rg?>A7uAyVh@S#2DJ0A-8dC04T%scALs94SWOh%)dwaJ zhKHV7nkn#Wf&2UH4JvqV_N)3p{JZrb3?lp*lzBTfH@fbK9gqlVH;1JE415!{_>^Yl ze$A?>W-j~w#M44-V!Fp}?R&eucoRF_GkCjERLETWw?FjX%Hu!&5IgJt?+>y6+aF^8 zpZuX=d_9-VwmZFEQ5R;5?IAXqNaBm^S@&A2zljqUUQ`OimNIGUN=0O{^X90eQ*5f7iMZN09wFXbk~`?&(|x;{V3$)qde z{`BF$UeEi&Q&(CLNM6r(x%tffA%JQ)r9FE=yV(QI7(vYP+#(4GL){7lSr|T1yjM^y zHG$XN5dMa^0?R9tW`Qt;oAp`IZ+HgFGubXKC5=x(bkI_a6Ay>^YFkmz9C&mU-nTm>hKw z$t0B|swL8LNKxmxSc^Ic^ydoR-k#%O@KlcIrRL~d9cJEpImc4VTqo5I)6JmVx0(`| zA6X6~k0Ccyp}@5pt9>uEg`%AkFPlgFj|{)r@c6<$!XP!*lOmVc0`Yp`gnjG>!CvJ^ zDOMjk^GWq|m=hQ{2ouxHkkll!QBj7U$?=J`+;eJr{ju)dqAcig)sOdmMtg1^v>KGT zMABv9(qPg-V^YFGWDAf0IZ*-|DxHeJ6PyGSaVC)Vgla=~6h}-;e5e^v8`#N=D}jQ( z6&M}S043!n4s6O7~R631Q7h_RP2<|d~IutnSH#X5k6cA4KP@pCTrvwMKgzs1ACd&Xgwj;i<$lsh?c*-=cS; z+QB_UOgt)`Fw0L^3G|--yqjvHWNl%1j&vz_udaP*2J5Y#8oDFDbd}gbEJ>0j^G`O` zoEEv!p_2K=Yozjy5!$o~=U+H{C(0BEi3^{OCzJAFf+b-}jj1a#)ZsK-Tntv%zB{3& zVlIKIz6~TwPVfO2b{|3%C;|2;4CO!(%rR7X)D+HJUkoxI$C!W&3z)R0iRva4daKfk zj31%-=tJGIb|vKBOm&a)`4YrVWct5re-M3(!~YbW&)PVdb;?Vh>r$I$x6G?JLF91I zU-uF^fAefilr=%s*6z*fbIDlu@Kjuz0eUMc0re4NtNXI9#1&_=4Zp!aQJEp7wUfs0 zqr-H~@-OOSTp|j@ZzJ|Lo?(34r5=DmR!jjV9;$cQaElP#Q68P3M^K6E!ZRrC5J&gW zUfjj+80Vt1ye^XdS9iQK0@8>87%ce=;93iJiKckh-0-qQbaQUoD^B`tb;$7mePS{b zsNo7uNc#8jC)x#$iV}1z@=-8%E=h(2Lzd{lXa}02<=RIG89sn9xCa zJ)y+TDlapPPUUK&p7OTowXiM*X5%HdvAoSy!oCe_J~&3z(l*#_cx{5sf{bN7;x&ZY zl`U?3`q7bcYrPwW39Gg(fez9J@j4He&JPpM4EQbMkSblDie={GQ*?JiGjnXMFszex|I3__zfuo)tFf{gPLCnK|#RT0MQulH0W1m_QiCsS-j`mwLP~5C@bi zv{;x-tDRXQ>2C@yaH`QwhkdusBOGJ9wpe8v^YNe8{@rvg%ClYI@E7O2+pL>>nf_Yn z?!!o=n{40)6SBFTv(g~2kc~kt)jHnp=9}P+g6nqV1G@u^ca6R#OhuYU7&@!jQ7N61Jlsi z#}Qsagt!qB1fw(tbrKZ=ru>0Dh#UePf;NaVi0b~1RAS#GbFW=kX@%|ZqeEz`<|gy0U1^-8N;>x+-S5ZA&}E;$$_89 ztH1Ihe z5XJMXBi;MM^X?7gc5fw~cxhz>tK4LuKGmPil z-|P&cT#W))v;4cAL#A1D>;t7aM1SSIz`i86Brx6eMGN?Tzlqy97Rtd)7w&^cqp$Gv z#T#A>6ni~{SxP`yQrJ~q213$!Ya-!hbi|lZ$s5-)(geWkPHzypbvDx5mT+8p`MzuT z(|a9Bm-;XX7sj$=#cJhc50OO0{HAkfL?)C=Ki~Oi$g1{|QYwqM;4d-XZxfQ|LcgW- z!HQw*rDrQd*pOaW_l`r}C?y^3R)(vkX}@Y3vn87uRAJXpOXt55V>%^VdzX$?*web7 zX;ms|YzxVy7(er1C|X*A(Wrq@-O2hX!9t5pW6@(2Hr?N1MeiCS(m9GK$ei&pvJ0LPz=&QZw>Na7th;26mk>~ak_bxf0~ z6WZ#?EQ-$Q7rl|;O6}R*PAh1V!VX~oOK9hb&g6;wP1~p6eMfQqYpoa+4-Fz13KO%J zI}r#rufqKDoW%MM{=Y||e07jqL&pN~20Q_Ys9+toAK{LZVw;SMKlQ-gRXjhZcOu_z zuCfOfu=yP_yMdY|33nq%P&rKcSchQ@S4G;9FnUlhzaK`G_RwxfPMqR;CM1;uTh;%r z)=3!hx(s8)lkF7b2Ykyqb6I~jy?+oRf{2Fv%R~4t^ZswMJP!}&|Lqs$__v3^@jrP8 z*V>!*_}nPIyLD?o=}?Lr2_NO1iw*njr51Wx+5!M_hg>LA9FzOsmu+5*batPHr6plP z5)|RwLBga#(o+UXzt-F^8~#V$-^l4J+Wd`Wx|0oMD;AT(Vjs7hzcx;{he#CY@EFUE zH?|{7@KkUi3%B)ji0R#1{z$}$?bZ_WrxI5_p_m=+w^tCkhbRBzcWI2XEnyl~$G5m!eFkkh_TQBpn#a&D(YdZXOWeAe_!s&#lghE9UQff~jcE>f*qujP* zb0saoKCE*-(#A>aNs+I<_e4;*1v>wDPlWDPiLgsgcq_F-c2TZDUZHhCbWyxw-P*Mf zeIRjbgV(Bm3XTabJAiOQj0TtlAqOC5ZeZ?3?IAg)UosI=NvnnJaeeh;d9i!;Pp1_u=4n32#9Tpt>T6-Ms!LVzFc4W9u(k}J+ zdhCiNOZ=2q$1%-HUr`ZTd4X^&x`+n|WMaT`;9J7+OyfL)en6xL$OSoo?83YiiDbaq z@MzNQdzl+ZFBg@b{}%VuVz!O*>GRE`OP<2E`jEMwl@z$=g7vgO>^sW2Tp(%(>!j0` zEy*NWl_xji`M#KrO$N>+Gq6VHp)@Tz;jLB#1-H#0<;%RHChKZW41%Ks~=TXx)Vqv13cztSa|P`LtfXr$^e{6OExd{jk{ zURfVpwGqS;6(ml{ozjfGX1&eHMxl(L81UC|83}{BXoN;SEwpdDryArcgeR+k0>O#& zwmr6gm+0Aym>SzllBv;894<_$wwUQcIs3BkLb~HJ{A~>gJ|jug77?RoGH1&Gx~><- z?M$s6wKDBj7;rZFF`+#$+@WjYak#2SFSuiwWUC~rUfdG3aNNUH5;1j~Gz}3`3K-Wd zVbPh#Bdg)9U?LXOZm9PIT1Sxy>Ea6ur0|6NrFAu;BFrz7IZ#d6A)WV}DmR?d8s_1| zX_QwPg9}}1dVl)Rvrpc${gfu;RW{GqV{`HY`#q5vv7@L&X4-pm5s*(Nn~56j z&kKL<-_OqGHmcF$Nt@a8oxPf>?;aJ2FZDOn<`8*l_2G0CD6?eXp7`>q`2R6>PEDEw zT9+={wr$(C{g!Rpwr$(Cx@_C->aty3({m=~yE!*sL|)_{$Q`-Xv-a9&m=q0XissMz z5-I+{?0K2HbZ=D_f=$!0KBX0XhFSGwmQU>dJ6IxwC^t!%Qq=2 z$+|Pj@Pa!CqLtg<-mI`UwNjuk9@7`H-0ik{WmZ?y*Wkmg-tNkx---|c2>sh$$T|Vx zWy7_59DdQkP_yRP^m^%kY1UKOB3I_sQE>h9Y}Qk}-#zid3Ig%uBQzkcw8S}39b~6x z+GqQdTWIpZZyc%$Lh;0l{tieQDB5qLR-WxlNcxwHOXenR<9??;Z!g-%{$`LTgkh&F z6dfS{Hn-=c;zu6Voy2}N_-pfK)NqB7=U=mX*%6-Mvn?_FbJfK^a|*Ldy$!|>+T zQ&>&cG2oSyw#`msI5`*_hz)83vIZ9mg~iN7X-EJJ1kYm$Wr-Dt-Aj4Ef33_rFUzXT z`PiT3B~kg0LLe;|70L=}dZ@H-i6`W^8l>~9FKSfMY%6c(HgA*CSKDnZvAre92-1X- zn`O}OmS1U^Nv7ggUF9|dtgEY?f9Qc7)ZeMi6xH3OHjM&}bJ77I%9~N^uk>aPU5D7f z7JG7ZFi*tIoT+`#A4pUN)SqcDwUyNh^R|!SG#pg~MkF9bMYf{fZZ!(02t}t((Ne2V z?JMiE=w+4ZAzOJK!|13AXSp+6Sr6K;)||JH%LyaH8r}B8aW(Z;Jdh{InjS8kX|nZQ z(j<6Jv&@vgiA7wQG0O!vKgH(j9c6isvpsUZjD0%f;C(h2y6gYojM=#^*Q#QNDvt0L zw8yGZ7u5u+LhPa3r3bNs%#m0ULcc}Y?fhwd42HzPg|;=jnULE1mHtf-y0AMDEdk|4 z{dJ9An0QwFj0N8R?Ps_mi#Ig&iA&lI3F^jDpU`&xoLy5mz(Yk;zLVlygR{dW1JqY>*PdO)$96<_Y}u( z9xcpd24S78yec;({}4v$3GO{}%WWOLIVeqTM#ghOH%iPU_ecO~J-(Aabfz6MN=V6B z73EE3VSc2NrnJltgUlB{alfzdSb~P0fU~;zGw`g9!;QZ3Tfx1VBacdmB?}E4SN`eG zFZyR!$d{evq0pO_{*%0TZYD3fS0QE>saD?x?|7NS1@>Sy1)KyGa&|pSh_=#Z17^|Z z++2N-DAcxd9C<&%a1-}fwsKQTur|6@-dUMv}q~FyGRBRt9UZDqr-}G&ZurVYflaf%RE@|6sLtrhQv;~Cp~1{RContl!RRyP zw0JqA)K+R_c36 zUy1`(P*IR@I4t^9URxrz4QZvlnyGr!{l zdn&p)FyZCv7<-k8IuM|^BBj8XXnWe&X2Nzt?ILJTavX51T&}3Zjn;*)cxBmOS!5D-EbJY?SpBPj)_0LP)tlmP8TbUCnmnrV5bhIDIB`N6jJ1(tstv5)U? zMrDJUQkVKSCC=EcsRl~gl!(T=T{F18^54}pd*k{QPH<1qcc2*idFA2o-)@RhuP{~; zn^~OV9;~imjWTuE_Vi6WyqN0tqkMGgay$}p9iBFETBK8wE2-YkuA#$fxe{% z6OwW!-|xS0kn+vW>an@7Hl|@g7zns$B7?AywSE~3vCAbOHa~Lb&`Cq^$X>oP!6Bgn z^|LQ$yi`7~KtZ77TAUK#X5K$^78rP*f(M_IQebVm!iPoB2vQ#CrV*;XXqhEY)f60r z@}VS?QDkk-w4pGO2teh8S`hobbXOrohsp z&M;WL<}Tr4)b1W&yn87QaKO{t_kR@B$!3MaTeUf$B*2~ek5dTG36%qY5SJCt9U8yz zZ&69JMbEXeHD_bkgY zGTRpRjiK}jdriaz5qtk~M=g>LD^boczdC(BaV~w|79D>F5QY^dW7rq0tp#>E) zLsWZVH4_xnR7k{7Mwk&5&YdU|{o@jEqiu(~Cx4Yeobd5`FZA}8 z?Uqt8rzB)d-|-1pGUw#@_k@TODl>=_qYpf<&03Ch#dG?E%fC7!iduRCmM2)sTP`mn zX7s7xB67-%cCUOu-Xp&#WUlYo5WuoR(!hn~_ymH-CKg0?AvXmeg#C+*U?!zscLRrs zv9JZ(P+xStTM->;1=rMETJ>0A{ZW2AmsmLg?z?Ge?a^dJCZX3M(3{h-5(`Ho+JYFr zbnUnl3SN^@w>;4aClVAG00c)a zP4lWp?%B;w%$~lozUcT86-pC9$Vo`QH#O3~vBU`9ux4q|fU=#x_X*@+Q6u^qOS{ky z=nSmK?qpllJqp|`y@}3Ok_PShj$F4GzcSo~xv`Vu#pQaM@0nH4`wsLfp(;trQ@K1Y zPm5d<0?1Kaew_b>h9JQsS>wlfM|PK;{vh8=FE_n-vjpx$Mz?Mw_TK*yns=Rnv*msJ zKEZWnxnlWlM`Yj@l3fmbKN@K%@>bjV#E;NdY5&qNPYA%EnA0`|Z>d1&IRAK{zD|%A zY`Maq%6S2Vj5~}MG9O5KFrCr43ub0FyLmDt@uCsBf{9#JN#SdL<#|F5RC_RS9Axic z8Fw6p9>}xAQJCm93!b?q+7W*kJoO%v*1s>%p4Td(f3FxxelNYWh4}%t;G+D&%4fY) zbhb}C3X2zzUCCu6#<+S2~q1o%dgB*UDUwY_|UeNNX{h8^jOjb0)a& zco1VNG_}z?ep#;%0o*~C$%jcBPVo=?o__P<-44oDyItP^^#ef*CiNoTa~yQBr3)c2 zFdvHT?(*8?_kRR=yr7(|cp5m!NVa}PqJCE_3GWJVqB?RU{G!&29_)9z5?zAL9Hy~# zgN0pf*zuyCx8p3)45NkQXrWo5^5~zW_r})dOtx+WLEH7kdqlSLT~EbA{MJ49=)UJw zz1Ge97*gg1RUlfl?@}~Ovw>WlZ_krSmlj;LCme(Y#}a&t`5^GCC~;No;ziJ7$O!C( zy4~Hqvi^}}<1QG>v6*_c5hRJxr?JvQ3Vw87p<5<=oR0e#gOv0|`4Yo?qNJB@6D*t> z!F;`lSMu_Y_mO{J-}>PO%XhY?BTuYgF1X_Pg-c_q!FCI9Sfd!*K#XOHW?o+U$Q?Ga z7_7&?IZbh~en!0pNyr`QO{nv;h4}&7PiI47BRq=8%x)hDy?TV7=WNc=9Bl31*3|V% zV5WZPe!iK?$83*_r-6DZ=s0>Y$iif(8JG!`aG@{o3fRU>(5G?U{q{lmYeD_9;b(pQO}dxG%u==PzW1jh@+m3*`UFJ9Ba|{eOYX{@-3D`~S+T zYy)V!Y;&RbzcwrYj;f@HsgqJ)=zqskx+lx4Nt5sSC7MdFiJ`FbTrCb56yr(;09pDt zr&dF;0kZ)W--iFSCeLe4t1MuktVyXLoLcksNNzo&49F{8*;pDiUG)~I<$b!u%oApj z0t|0LBdX1_FzO7iPM#jGS@F`Qh^1L|6!87MZq-|8nModp1_t#OD6_!m{gYFHV<>4M z&FJ-&RtW3ee}X^=mAT|XaRE>^Savbqt2Bx=Av}^g++B1Lo}W5U(o+TD zd7Al?)j2PG>cc#%TW{(3F>pvo>BV{Z1i028VDIIrf`tF!v+{`f`24G@gs+-S{(fAu zOrog>QVu3ZIv5@Z5Bg8sNnP5#LOgr3&(X8nc4>+WjSj{K>VXAXQGm&jKmoDWmUrPA z0+1RF4}t?>_k^?IdEU~N^eZf0QkBZLaz>crTf|W0rd}Y%V_Y zE(TP;X;%kWs8>SM$}eEsrd99}gjE^Lbzie-{!3ntqD7Nq@(MKZIgJO5(%Uddd`Nx9pLy&!v*D8o#C_9<)c`bf={h%D1jkFp zvD*1(T2GG#N5NqFwwxCx;l^Ohr9l~GeSU#!g9j>`Lx)@T=Xp_>LUjr&T_*TXFLv-DvOwW6=X zl!$$T{0JCMD73DLqbKP9o=VC~9KL44GZVrc8m7vT zg>FSsqT>t1?7wb@kdSJ-{gxnd=4xvW?y_&Oxc?JB{IcWtOj4Vl4#|^ zy_HCQqJoPK(ug&5<=OUSQkn`;^o`Q(N+pr)HApIe`di?5rTNkWtWf)RO)~a)3d{I2Zhi%E%_p z5UY+rZ&(1TCoNttXSy=L>%P#=ahT{(7TDc*WnkoahD%$CG3M^@QgVyCkV30FIF zc%Ir>v>A@zqP0LD;LE*iH`S()SxYrD=znxafPG&V(cNGze`mO~zZmTa0YM0tm8jh2 zAjVsF;P#XE++@a2MFZ46kSv4gI<@D@aFuq@8P^hTfxeRr{Lm2%!aG-k6QAI21`ZLc zcyh&gDNVj=9JhM7O{nuTi|{w zN}_-vKKJuXmjwxTNnwNX0q4xQS$Ti@TP~tN25=jKt_`sQzsGD>Zsng)@2=vaTPP4M z{jBA)-^dNV(u2uC>tMYIVON~iZx~J|zvu-h4|X&mW}6_OK!ubJS%C{~wP8Zbfj4DA z^aTQ$yjNOYTlk*lU7ZX5j0h^_sS=1*BUbqL-R_3vja!I6}`*1jLVdYSd1N>)_9ap zh*--(+u-TWImq!+?1_eZcBD|FZrY6Q4Smow^~G;a`%ZXw6Vk=%EQ?(*?U|(Wi&GZ6 z&hf{U1Vf;N1ue$9uvPOSs$3gaEZZT`V&P*PRx&Kyn%K|b_Z~7A)9<95{2Y=(AGu65 z3eS`4gJiVe948p&Dop>xDpZ&D_C`&7vv7|c|xvuw`6K^*ajnFxG2I*FMC|9@Svm3o;XZI0~m(d%UMxn2obXaLT zG}LZlsXRZks~d`R(7F^52tXA!n~*tBM|7|;tfk(*i&&Y#dXdX#YqP@HbQdF`;G zZAldckf*CmWtOS~!^va8le-SG+x%5@3mK_oIOds|rTt=hLUiZG)=8cRDs(@b^P*q( zlNu68x}hGElC{j^EcKo4#Tj3mOpI~4?XIZ%4RNVn*?E8dG^j8h;bU4&oAAR9MH~G|bYAuuX**7SbFkN>&qOwSke2g#esp0M+)` z5zpDFNfp(w>Uzp18R67u)p%DGO1t z&ordx1l(ncA8<@S>_<`sWU`~d z%$Kx>BrI9nMYZAr-UMG4#zDsA`09qsc(R<3w+y`Zb@D;G*H8sTysY57`9^R9&x)Xn zZ{zsb%hTC%@|+fZiyR?iRvy3XCTN^(WRgEQ&Z5sVavUR+VI{J;UbDmYZwy-^t}se4 zJ~#HB9~{iq7IJU3s6wKM@hnm_;qbtf0p4oxS75$HziIu^VLrM%S(ED}XMf^tpTR{? z9o7)UlQzzsb0&m#v{v%t^7^En@SAnQtAqK^?Td?3?n^`Z;t&pBA z0P23CtDMy|J0L#COdgo9X`={Y7*5KXK~ssEeu&8nIUxGoUX-nogymDl>#=5BnB5@j zGJ2%+&3R{>M+jCNgJp+V8_B&pGu9xhcA{XT%5N!|x)%M_5&>#si!2EZqJ<<8{9Z_ zB|Wf>l%EYW;1jOXZ05A`hgaFoXc==lkqbLB33vO`I#M*Gg~;5r6uc6amXQb43g_bd z(_;ayp-zgWESOB4lP)gW)Avt5(tYmu)z>?XW;SukP!eZqvdXY$1L}|QFtXSrie(c59UG^tb zU5ji`y9m50iXY8JhrJN;z^&luk_=-qyO3WazXR~3MNJqQgAp!pMCfM(J-YVNxQK=u z#i|Un%7A_}KbWVl;srIJ&rp1ghqidQ>VBw;N<0fX-+&mI_kM_7Gu$BYWxn!a0&sat ztX8-?&X2MbsVtcO(w&%?R#zf^Q{<}SauIRQ8-#)JZ0;>- zc?y>UQ+P~<$UNmutXBY;VbGPPAODWqN?B(N$2TxnW6vWtr4MdRDaPy6HMEm@olpMje)wT*>I z%SEn>Tc{~LiJFY3C5eFUarD)W9L%`C#<$D<$AwvLDPm<-J#sHUZVPnh+q22vp@{^3 z1q+<8&N7ec=(g8%f5<)wp?&1{O(;ASCqnyGf%l<2rT3F2l~*&zh0yk+bGcF3|3OVd zz@mr5e%fJ?l&Xl%)9jy|&Oe1ub(Uu;FI1~1g0BHP)4b9po7A}9_j~omKN)@IJsY$4 zgkq~(rP|52I&{v@sL=YWrtp(;G9Vxsv_mcU|fXA&Lg>xrK|4} zbLhnAq_QIEv2ak>OdvZSp-)}2#m5q$vop|HtSqFaV-qeS`IiD70Iox;ark1{RK5ED zyYVR{A%#J0g}P*ZqwO$Wo8^|dyC}S;oU%Ie$_Uk~bl?I~Kzo-v=mOqPa+d}oh|;NA zV9)8nVn0AP{3_8t?cO|q4&IU5#;grvWY-O|GBMR!<3!$4+Dw1WEA~t?t;@-47K(nH zon*9dnMA$j*C94WQK@}ZDpIduNTkM0uf;kWyHkdZuFP^;dSp5|8Hg(1wdlNJXI2hh zipwm_>jsv+53;o-1OS9EzmBN&K~!T?n(T`G8RL!+pevlFRhVPDomsVZn#{oYb5b`X zU!EH*N9ACO;^sU(sZ^tQwsMPN?gi1p5(2z)S>NiRYOQHcNn@3Li5U6eG%^`8&sAIKp3`MXXI?ugh1ZIZrm;li?L0BH#Z|#+ zYvxzoz^m3wY|T93Fq^_s)~`Q~$zYSYbWKLL*Z z5h;|prMYa}lixhA%ya6M8-Pp!{!CteX*;Vajv)a9fI~@+4QOxs(#)A!jHMA?8-?FC zg=9=cTOFxu;DnGRD_-L+R0-{C49!_r6^!i*?Ba9YTX~*-^LG1gJxdDw-f_MeXLQp} zGeq$Q90kLN;jI@k%5O@`VTLLSX3;X%_1rIWmM8;i_c8#}44~=V2E>lf;9YMK;uFT3 zEzsRvqxp#=pZUobJdLvP{-jK`Bve+VCb8WV6qQ_-rtyet2Kq{VZLbC5%ng7&ccoXm z+{tJgT1yvNDXqK-6~$$mp*wYz-qL^dGQ%`}e;3A%x|b?KvxvX>tC54LK`d;cCWR7% zzt~NQD55$2n>I|N(Z#1O7rA3mrrS z&qww&=Pw%~5TsEHK6ty=ql|GUQNXY(p-^d{XhvK(z+&I9p4L5|Pfs9BFABgg3V(H8 zmhPELb!Fv$`GS3hHx^TP2ddP;Vw}t0l558(P);Pj< zZ9eNuU8XF1;NOOUh5LHLvb=6`wmQEBg`@K^Aq#hFr{bzWQE-Oi!KbK>?wzJ@Z0n+$ z>fH&&?z((HbsG{S)a?X_s{r24QQ0lG%Og8*9?3gJ&I)N`1cx+L9TPn+2q%m#a;M5c z@L-rl5?CFRC@F}f?NmPU!%Phqr6a=10<7?zp-*v6t>mUcKdi-P6=ATY%t7j70yOuF z7R;x&`~kaaj8yD-tr=8d+qq04n&&W|SffPi8*k9SbTnQmk`qy6vw4>jg4eJ$?7S7I zKY_Oa?A@m6xgChWDaDQivtiM9GdCMcuv~1YE31)@e#*YMc2#?@C;<4k5B}@MwY*hw3b@RlR;fuK5=@r~Jdrc8^#X_)bp{AcCg&p( zB7Z}Yrc(9TCzWM9R_~EXaLRc%BfYCIn23b}YBGkx;i9AixK3W53;3mki#p>&YTAQj z(|U0gEaC+?3pMXKt3T*YKo{D0zw!Gu%U55zH(V8)W;?fp`vE5gae!Saa zDu*b}_TQrKuN77>M+ZE04>Krf6@5c>Y)#_f_mqpYu|fqhI)0V?Izx+lX@hlm#0q_O zqEOnt^M<9gF|JL!tkKiyw0kG*53~6qs7l&2qAY9hQp3McZ=+jfe@jl$VI35!*Yboui$78`|k^@BlA|k{)h;N z&eWSVx`tg^5Bo@OI|@gK9JC|Ihrdu{F;Rpg9VQIYICP`!V{E)1r@)8Kxo~~sv=rhY z%ov*eZsC2(0P{+R;gVyi zmEiL*cZi{buFJd{o;WM~rgNU3__@$f*8swpYiM0;67Kbo>y$bDK`Gys7y7CWD}s@< zW<;lCAv=K~+pCbT@(>Cd9C=?H|CH~jT00(j zah&=u(JSO@c^;`MtW8I1FEXC;$qMChH~Gdclttie5ZdXY`?Yj4e6IMqyWsfGt%3TT7LG>N7-W!n{CNO5?_L}ITP8-yfBYg&cP|6Pr`8s0@ zdYZ%$T&MOPpuM#zZ$#1&yJDbgQLx+MdAQ73Yid#;qD-Rc!dMA2kRPa8Fe;2n#KfQ$ za?ohQDWweCkONW4v|Hdya@?={ywZO8R^&;|%!#>S2$qY#*?^!2#ICMl|L=YyTWiM* z5Lo10J&_b|=?Sw^Y$5=vu{~RuN=VJeza9$D%)>&58?Z0<$-lT|5J!TbejuMPz6XM! z7w<8R%}3=rmd)VkZgRKp>WVX^+Gf8cIW->ZKB~V`6V*;J!7v<2=Me*2S%0hh-TeZY zHeR!uhGb;tjo`!{;u-bY&cLkF$=mpsz-xcoiW|!ryWO9sbBRN%#JDqWm0uCXNZaXjoxb2_g0AALl5T0NSRMd#RcVqz6?9B+z7eJGk(9lN2DD? z){3gD$ejFELb^!6D8JP*&k&K194X5?h`uBJ4r2fJyx!=2>%xPVQ)Dx%I`jpp8Wt|h_gJ*|5j)J!_-*0|EDaUl(KR)GSF-XUXc? z$I}u3tJgX!a3Gb#T(Ir_TlHmH93_UdLZxA+vUH>s{%+}cCfp;=#p{7Z#3XKXsB|GmZiaqi?OkV- z#Z`wd1KSQX(xjDZINEKyH7BUsKUFJ7YysnfK=8-j67YnN$K}$=8AfPi@1UU0k`qWt z#Kfv%<)Kf=e|!=FV)p#}CSqgxW!sUh_xP(rM5vN+L*CU1ILCvT=Bvn6sKq{(QKdRD zINmZ#f$=zS;Ef1{1MFf&^qid|Vi1RC#7!DCU zKUH;OZX{M?tHUmGmXu}39id%lQyMpCuNGO;X{eDx9;w4y9n#S%?7z~lsw!roL!L*4 z@tm=Xc@O?eUyiQ4ys!_Dtn>5Nr@A-!CY%DN!)x3uEI^hAVQB3sK~`1G*OoAH zvd~*E>($982^aMq(C#w(xs|1n8i1ag=a|D~Mw7L1mB*vo=v)Xd2uHh)rqT$uWYh1X zHH#{kU#ZtiUCmTxE3ubXH!v4pwZ2I^RQ}GcCRXQUt4Br~xZ1%9TphyaT-fG~E!4=2 zL9JST5i5hDjf8Gt)lp#uY#-)?T9Y8ie4NV zwCH8ei6Xh>Cf2hmzz(sW)ZsyZeztUuXfS;f$2KS*7?-gZoO`!zfh6i00lR`vw^(T( z*ZPy`^h8a&ot>d-2TU2BqQXWr%*Kh4u#$7X@I^~s-7i1Z4wlDCJXu%uJFPz!yrB+Q zi~mjRd_pHvfl%A{OMT3LlJjXRHSJHxO5A`7W`iW@eo?X=3vX*RwqG^eggJ24W^Dmxsp@ zZ~k$i%O&+j@H&+KBmQC7sCKGq-C-!r<^=DPf{kKbzx&>6z5fPx)EKT6G4)JTB_ zeVG52F2m3*_!0AD8R?({5TEGXj8jCcMWSQ$zN90e(3xIS6PzvS281KzFMJ?UFj62~ zC=Q^RGP->GoVj~(fWd;Nani8`m>@uoUBc0p+q4{AVV4F{#nZrP0nnaRdDf^ zMS$qd(8&x@*b#6v!Dkp42mG{-RIA-UCCr3O)JNosi+g}kX~5&qG7QT8R2#$*d{PCq ziEGY0f8m11U10@++!TUM!Ybk&`NSs$iEL&^J}WXuXgp3F*DZFEoTw+}4!tRA>HrWU zN8*+|(XGQn>$VTX2gP9*Ha)t)Zkh%s!3@PCb*?RMeHzNwOj~A$o2AXoBuYm@Ax1%N z4w;DmO|5{;{Y-Qe_!)UM4chJUv^`YPk@@Tuj&H$s0Z-xpHAxOBFr5D56$F()dtKl^ z4?-!Ue2@0z46#|Y>+$=CHlR_SoDkpiN^ynE`<^O|UaLZ>|vBj8D zs-dvQD0J>)vMM3~tBy^Ca=tGOt4#j09%hQ)*C}R7t&U8>8EN?=Sj{>Q!Gp1Fe>I@{ zoqkJ=CX$@hapISLB(EJUKY*;6!JmWi`loou0>fM;3&T4*7D5Teyhw8gb{RLqK

YfF8J4SS}92EE(r(bgaf&E+wo!a96CwO58~psQ0;vLQlA{o zH`C&$rPvL`FI?J#7x4;=#0R19gA5J1GZt_YUn~k2mmw;XVx)j#M}Tfak6dX4e^eBo zHdMBvuKN-}KdRd|zq3-b)kJXhrVk)m7FCUaJXy+#x0qVIYI_*f$T8+uo4 z^5`giT>NZf%oV&Z6Pp9aZ+Ls0%F8&_x)*!}EKA-oRL>!TwxK9~HjIz3QL2CKCFUx8=8hID?{tD$V|6tHfiI{*UA*H^%t)Z`y=iafqj#kad%RgG-f^`HsHrvvaT79L~dOT4o{8gQZ6vLZj zq~#wXc{d9U_@vneX^44UZsbJ#&>5=j6QT5T8TcmLzqtuoUsKaiIqZJ1@=J8n;RPe- z2-A0Hsf=09dld^PO8w#8{SdEQp~<&ZW^JXqX(KBVg|stY;B+15L$T!PJBc zWEIq4##&h<o5up?=ra$Np zKh9IN>t+%Y5Jm7}n~))9CGK9Ah(gENnm$c%Z(zr+hcxXY;|w?7)p3#y0<`tBclZ0rQS)^Z@IdZjOHy$Je=P?hZ3Tj?rB%g}LyNS=Oiy?t_T{C17$EM=`AW);_lqU zEXS!!dAcxD=n2gJVW-o~#TVvhJWIDp@;I2~D$lJl%bg0DssqHSbrXyatz4^e_2_Yv z?5Kv!`q2t`m;1gH92QpWmx~yR!TJ%tccY9gBXQDO@q!ONYb$r9eXvS(ARqbF^<30y z9{GJtzX#G5>@+TkH z3OOE3E3dm*HUGANa#RQcy$F&|3c70%Z~Vgokhn~*UlG^Fx8LMv$M#V#EXR3cgL`S` zRoDwiC;<&fzTgJfLxX5n2udL61nrg|qyZits&I!C-+7$i9VPC&x;!mt_H3EHQs zQZ`+?OK8*52-K3{D}7rV{4_w!jo7)P%H5rAL2;F#AVG}e+elE!S3i*#paF6I8^Cg} z{D;;#pq$Q`%?0l-ZXJx)p4NIN)u>>*WmBLC>U6<$H>=yb>l}g+UfN1G%SXja2$0vM z7~z;R0UVEU{$kSB1^e>G_!nW!LZd-rsTQ<2D&t!r=x!)e^>AnuHqH|*Zg)Zm!d6_0D#MJ`~SsmcYu_eu_RO3X5Qt9 z9q(j{%hINfYj+<4G=RIW^=1`W!}?D@iI57A^l5h$M)DGRcKlrNa2 zC>rG1#)MEc2dPPvFoeV}Yd3&Ucc5$C2U4*B6TXzFRG&9@;9jE9nHs49!}vc5*D;j8 zE^z+5eQZ@@&GpydvJh9@=1Z63F6p~h^X6amQknlj*y;n6dAJ;mT=Wb&lO|L9%@u3L zWZ-0?_P@)v=5R9`wteA8>{C?YLKJtrI@N*EAZ6u)a!JyG(uqcS^g-GrEgk5 zC9~8_6_rAGm50BT_&i)k%O}>HKxRgW(?L^sw;HXQ-TJ`KFwh_S1U{4f8UaiS6TP}2GW?X>+>xFdd+z-=Pz|n$c6tJ@!Y2F!uOO57L$0zW^hIBEOd}HUHO6ql zoC$3w?Wra;dL)O-eUPsID%kzIAyiJKlUz(L4O{DO`=fF#{A+>V0Ljg{_&_Wz212zt zq(f|$oLaFo*ciHp`JEt4G(<%n?hDB3jYL?LQ< zY{Tyv+uEUrQm;Fw(A;QU*LW5hWpg-< zMhrEw^ZTQy-5^1y0kML2Q8@ud?4p3kODMSsV#yXlZvh4X4+^s8lQu-s!?sgkZ0l=72#rR zPCL+~OwjB(&2J-DNQG>OGU`{SS>6d)7Rw|y3NIeGJ}@!-;wDCn$Yw`03P-I4FPvuRZDgI|aOs zuiQ8?{^S8HO8nzS3ob(3vydy>eUY`)m&&rkJT1!3+OC!yg)|ie{2T26;x60>q@;@+ zMW&CxsImRv;3c;rzwn%xS6qo~Me&b4PL9@+GB8@uR%sq9TT}eTnzJk~^_bz>Aw&-PgrO@zaisD4@-#5z@8dm8sQTxmdw_ z1gM1`w~iSvwDTc6B2yb`FqUXL=-H)FGN%du1;AjIk?F>34Lxh zB@<7!`PoZo3t<#rlRAqnwbi0#Sr&Uc0H|VC)mNr~rcIp6iCu6zu`@OXMc{2ic*+jI z+t`J_VNFaJ%CD7ofx4e9#*GSTua#o^?EtrknuE{(#yOT9@CiWq3QWk3X*Nwnv$g`_xqhoW% zQB?--Eo$;2O<;~7tP8mnc~O1yubIA3erJwyHAW_HLpR42P| z6Qi%NrV;wfgr3#(7Dl7eTyu1JjA`=deOJkAl%Z!dPie-fy17w+M_sRTar@~_q|U|^ zNCKOc2e=_-e@KhBCuM)h*7-rDqR01*6bCFxEBs=PD!qu}0&0AVT*9tQ-uc z(fJ+(AtIuZg<^xxERyK=JT&@-b4TG=#5Nn7mV}QqT(0`1(urto7D=#z;26(hCo968 z@cEW1*#B_dWD+lL5uEVD9^!TCwD>y&|2u<>%?eRpi&Z?*lobptD=R^U(du?62D{d4 zNV?M$y!MyQvFroL!>@KVUIdM#%{e`nhToK1e$22@57alyZ9^y7!~hQ= zO z$y0?Yns4CmEGAFr^e{`^5Xu|K-H%JbX+(<#CTq+CNvgj|M92H3+X46N5f@tzHwcAC z2R}wMbhev7EltO@9!mFf8l4bRy@^0(WH?~u|L@IwK`O#jr+Jd?|1fq=-I;Y;yN*$@ z%_p{P+qP|672CFrif!9T#ZJXGcD<{$_3eZAWc`D2Fz0B!UsoUf<_|C=vboPnvo@%G z+$-L@X0tm;%il#Z0mKIB3+|-^Qp^%@vrDn2(*m728_d8VfjFJSPm4Yw{t6rMPFL?g zgkY;Ru+1GF(aIqrmfjRGKo3mCtu5VJtV5 zm|>?=F&L5&=Rgw!I1`3dLfQYgCm$yFcTH~aRqpKrcta_5 zke=aq38>>h4vWXgIsD~K4x+O!;fh?|;Y@_#WLP^gQI-Yi8OGg99mtD3NJG%~=CUsq z+BP!jRrsG1m$98CI@BrB3VGBY5*yG&A0--P3`G>`yRF^< zd*C2heY@5ZILI{ z2A@z<`PDv#%%aK6)oL@L94jzBu0^^ATw~5vXMt732(DDPf1C=T&))>4TfF%>kLR5n zed-vJ@|DEu-+$Ea0&~7qtQ<3Y5AuY>h!Xsd<009Iv^N8*HD38*VICc6z>(r|YO%3;+kn10* z&~p>z5J1Lsa$yo(hNR=P%SjZz+a*0DN$xUat6Hh|zvOf5+J|T}bgE>l9~=m46F5fh z60hwfhQw+~-SW=b+aQ#(=CDEeM3G53dzS~o^^Pgb>1~7=$ZiPBfdg~%(Ie9if;`m= zcQl@b^M9xIa4s~(H??2I(kklsJoaF6&25bMak^*dmV=*M!CKumuPLtnCWy5Xd6bC5 z4u{79X}#(wTXLf5(*{vr;1ISo58@1%B{4m)XRQJOnXVFW4%8-^80$sf-|k( zW`I|N>$Pr7EmWN}vYi`=j)Pnh=n{qXn0>LSxRg&nZaL05UNIWO%W{;2OQjJ?v1lCv ztF)*(@p@Kp4H?oVV^YbTz{1P@{_}JxO47#oVtWn-GTb}GK?4p$HR4OcFrPA%W{LLI zyTKKz%IcUqZ7~v`!^p2{?jD|%8r*`OCScC0M?L_Q zW|hdsV|SCY9E(ssU1UV&(DL70#=Qe}YFc0E$b|exwaRmJaTO=A0&$G}r$pYDoG345 zo4FI3>KOuGi-U|uWPtP>OW~uT8JPrq%c6}{obw#XUH3oy`?AT>vJf8ZbbcmVeV0;W zgpzs&FQ2ILsyS_x;9KvpRA)~=YjnpBD>w|8-3FP<6b_y1zG)cIv>kW)lH-?1sGW5{ znCMq`A>Mzc2~4++W<+bJA1)vl0BA^s9(l%ByF*pJXME#3W*JkAI8+MJQJuJ`x0)XY zJG3--uW@LqzH)U(2tcnco#`v6Ij8G2^#53;vq}p#B=-BI)(Feu`t(jn}qKlTuK2;>H0`t=cT3Mr5lPFK_8rRU+U z2=ufI`2t7kNZ!b1qn{S8r)3A`ofH<15Ap^*FTdHmh0JWGSrm>HSf8_3t;j(vpuq@G z$w)_nqBaYz6d%cGT!^6UhE>(0588k{Wif=|dlQoDrL;!d*LbF+Klj-OS zEJ-wxvF1TFZ>b1JE;CwEmlm|J&ef!{w$njdyi1!?)s^n&?rg)Dt?kpVU2fe!8jC=0 zp)~2)9Vrtj`H|g0#c)#0AQB47zzC$_E4!2^fFuUuN=`)(eio^@QW}*B?jh}}i6sYW zC6`ZbrWuh_LC4IPF|RT<%4G@$0qNir%112sssY%xR2XxQg#o^fOi32NnGR&CR0(T7 z!|K^;v*g=Ffq@%NN??*vqBAFTQXE?shfU_YiaaDo7MH+9x`mFA?<7u&7{Wp}mx9-* zG$rKuZr}av*=?$Q0;b?_!29hC(pz;I_Q*s(MDrDKh7gj;iGFnpnRL=1s_u#7_@Uz( zyerUTtIUeEh`v)}m6-mXB%huo7SWHqu9)E0BqM4Sq*gPP(qF%IL~k>s!6jM&3*(Wi zBwl4P4DeyfX)M(fFl%_`oh<6K(;RCyOw69Y@e!wU3>X4N8kN9Baf}&ieowTX zR^z2|RhXP*$@Tml*wDgxz58Rn_?vd~#U!P18$Fy##2{BJd%S zUHZ_-9IOpWIV{|iukaF9AfPfiwZ2|!t_7*Xw?>uc?2}D#6)YAS7tsR7!BR(#ZCxRK zRJ^+*(kLo9)QjSWQHJ7pJIx3>7&VAAh%pFYUX%b;1f0AQUKa!fgvvl#D52hbaU1EB zVg^1Ws5C{drJ2Sdb+cnIl|j%3iPcsr5l z2{yS#OG7bY!5|1Y$AHtq6;U8x6ucTX{lb%)QB%69Sa4P`Rs)jzh_sYuB1EOv)XUW2 zKBacynw?nBa{RFTaeQZZp$GP)Go}IeG1K}ga~-FVe%$&|?yc06_=6a`qzNYqKL7(2 z!m&aA=(LH|p^KcGdAwhT2k$wt+5=!nJe_M!!^7L2SlRW=A5ISSr$Hv|^>1uVEKDyS z{Ewp$0~2?{GkZdo^P2LePivHV6SrdGK}0O%=_xW5A6rp=&m-zWy5Iy4QCq8-_e(~& ztd8)lb&mG_S~g>R!+JjY#fFszD(m&<*I`{OixIisz9es+@<^q%2t{07$TMAsjy)?v z9BXHLY%S*MUvp0UOyc|>{c*>^@_<`?jmL;kU^FzbYjWzA9^U7gLN8&y( zj0uV7^nAwYY-bxWivkuDJh}`!i6M&@p-95BNW72_VDl(}=&c0Fe8;y8?iw$Gbz}Y8 zw@uW&bQ1Fqv^Y7lTT)FYEF=Txm3!FREp3f%zJdS3227oR*K4-|-GlX$b-xaZ*@{j7m(fhZtcI<26= zDL1!}1LFEx@)93w$mZ>gnOh<92D!3K%so-Z6_G2d4O)AH!5^^ie+XhSO`LT=iAjuDq|+el6_qB1;4 znumMfc1*xvyL6FV)sJ^-WfrY3(>b$-Jl}85D1D%}e^ogX+YsoF8<&Xh>7FypL|0sy zCA6S6*IPE46a|}pLxk~-ch>|%_L)Mm9KF3tZLOG8_>H7n<`>NcqBNS8+XlX2?XuR~ zOal$q8}^(KUW0U|VgbmHP!?e7K`!dCAk-W-niG}bt1|UxcwA{4KInu_b;_I8_^LM8 zUoUmaWc(ehN&(z@?Mp-^Y7?n-{xrgTG!Xe=Rd8?f20Ew)Bi~a z_J2zU_WwybEaPlA{T{o^soULC8MXnZftKw13m+Qf_p33C%YLoj2ejvbh6ga5_v*_& z>vWil1>{_)O{U7N!d_ zHd)MEWXD6{Ks6GF>4?4hAtU9vYp`of5a)j+~{^nXQ-u{`PE0aMMv-TmXDlGwxsvKu!$P6}v zmLF#@7(k-AyBoMC{X3I+PF)wM8p#A|q&m+=TXOpc$Y5N%RpA3D#0}nRlE&ls5>q~3 zzlGP7tqYWB2#{ zm#%99ztu4M@q)b7JgL9Oi;*-3>P>9cNnr2##M*9RFj`^F4{h=(o?x~TZ?Q0{3rrSD z&pOPe6E|9+aNMRxtjo+25`892YS&y;h+~59!&bEZ$oP`6wcPdTiV6)^^j3?% zn3ZZYE=$2TmJ%BA(x4}A%Tp)lsvSyx(nn2BOJEVtn%vbmwTKLI625c|R)S+Nel~qn zx+ym}<{`!;??xWi(`0$zoGpZGNmHwmRct1z77--^+zQ7QtHehKi}J+(L3!*ZHMXD&9Xn06 z@ZUU!^e~ceJ+ekasnEoW{pKEw4P{yY@)LCk%#X^im0=Ii2D?Uub`l_2!$xfxKGrFrlJ?|goSFJ zYAt@s1Tgo9f>4bN|ZxeeKD?M3H1gp(>P+Fg6B$UOCcm28gp!O*E3^ z?D2I>>yQL+M^ed4C!4!I*>szZf zv%iD{BV4O$W3--cq&qMcQjtK=O^lVBuGxOVe8=e#DU3qCG`r+?%PS&-FR4-sY8{;g zLZ(8Jf7)nxlA86Wt04zX#}l^>_*FmcFhg-iTr@2J*F;RP7>^=QfsBn&qS%8T@RJ65 z^eUobx-b{1A#pmfNK|TrKpOA5_42W+2_!4!jYhcice1y9VuAQZWB_<&c`z^EtPB?P zpIE~a^n+7m_VdQOyX@4T8&es%{!Oiv%0g+dIz%tT?LFg#@J;noD@_?)$jq#&YD3>n z&z^BOPYa=ks^`6J20@5JkOBn#fFMo~NhT3O4fz+WYowf433dxGr=e1t3)~HlqogZ3 zP*bLK%>spGWwy(BV7c~tT7}<1efd7Tf!<*EZ^&*wsU`L>%Op0=fwb|$`ZGRv83bGM zy`OPxol%~MiGh$Ikw`;Y&%}pf>mKeaO4xuOBn`$c!MROuB}H{m>Hu$XkZx$LEhThA zTYi`_0h=ThNSpovubJfsiEoJ2&`W>MD3x9Q?J%{TYC^(JSX_O&RwN9;2>N&ondk^nExZQAnZCz7Z!I;3PId-Z75NBYJ9#kNn$XZO`eeGvm-G@AK|phT}-dZ5HYJm47I+$PqMA& z?Do(Ty6Kwq~TBX z#9~rAulntK6Zx_!R}IcU#GzSt_cRc@t+dV_vAmkXUJenwRxG?9WetYP&fF&FKm@1M zRH0Dfl0?p^XyHz)Ujd5)aeB?d)h7Fp zavj`toZTAjInuvGLIH8CqWt30t)OnB2{bYE2Gb)17Mk_lg0$k?fR_so=d~PreP5S#Cde}36uN@2DG385V^o>81PhbO?LDR*MKE; zJh`t3-;RF1IRoj}_B=zEhlWO(n)$^HOp1fdccPJmJc~Y2v>bfK9Y!c?!U+o$F9k7u z@f~3hx+F`gD7{4qLyoWHI9yJXi$ep2MmhXLRc${tLaMZAr_oqhwQAvGZt=C38KoD? zeF<+@lR)No7R3N%7J~TTME{IuAV^-sdJHYf&$;H3)+uBSc%MmVJKmnB?Lkha0me~h zyVTw(*JQ)1<}8iG&B;4Kn)AWpkpnX=@${iPUV&N2Suh!WMeX$aeq!=Af(A*~#LN~r z$qB9RbN?)YgUx0<#p|ftj5Y$ee;Uo{tiLmfDBE}qVtqP|}!;u*fL5)lF zpxToz{-#&2LFIEGV_fy2r*9Y0c3~b;_!<7<7C|x@neG}Xa>WZNMCZEv@+ylU;Y;MzNS_>S;J50 zLqmpcaSlC!)i>r|+&^E|2%Cf{GJR>wTJU6yp9Syumlx^Il zc3R5sE%Sf+DA*!^75}u{q=uCH{sY&Q&vURN%$3Vp7;Aqd2C_r1J=*-)jTw<14iym| z4jU02PKqo|%=VXowh8V-v@+ZXQ_n4uj7dmXMGdu>W2cCjIMavft$H(fK1#@f?Dcuvg_0)x$;z> zDn2dbskeSe)omBjqsO$75yXJNjB_G(#=DMBFGq);gR%LfJ+`5hE-lD!@WFCX&|X4> zojhl9&{4Xp{SYHPPwztmUiK8*)pFm5tUjSH*AEQtOYF<+i|u>nb+!?U4q=L%=t&il zmfB4y?ozZ3lkz7!Yyo4-6n`GC=^n({*NL3&v9wb6ZeO*M?5m|e?oOZ6Fw>B4Lz+|} ze=Lqe8xH%bGM@$`Z`yogF8!F?A$E+uWNq1bZYF^IO2*2;Y_nY3i-fjqr-1$(YT=|Y z{aFIeX0*VprqcpRGGsp!8X?An=r*mX-#=lzn-k)U%SFU*_#2dA=vC^x2W6-G5q}CS z{4^;Bmv>%y!*)Cq0u{QXeQxAvCFsDbnO3@JFS{n4XTHvI2P|Muux;{@sy{gpvZYjF2)m*4RA58ko-IZWLO)FaU8Fgn@KDuM-?U zRJ9*D$GGF(HMh;Jihhv%Eb7Z0#qkfJ-lXz3Usd}>Dkmh6m8+d)A(*`ci-N+-8iWEu zFjRdq?P9fEa2fDs+<+}A3dZ!8R^xzhyH@Esss|*U_v>;?1@|QP>Ez6{@kx@lB1Tby zDG*aP>*L&2adoUR?!s!HwHqPrlR zqg{Pr^kNih)V9;K$XHf$u}_CA&zGa|w-%@w+WW8QvtfkyOlj1SHz6lMc&W=j`=^luZnbOL(CIdJ(mOdeoUA z_prFyTyeO>i&?t{S15odeWc{&v!qk7qiDoE^_dE}Vr&j2Ht`63o;hQ^5)1Q(F7!tJFu)y01Q$C z>P#d7U7o3h9*2Ld7)}4HD4PQ-(6e!dI=tvGxu*mODKO)gu=ugcWF>*DO-i$u~UL|KAStqHCM-afV+yEX9z_Sl1&t*OJ zg^3U*fnQb|xjEtn0X0QGD4-M5_N`|2GQBhQFjD9=D-1?RFFe*(9*R75!a8siXAnXN zxd@KQ`m+UG6#q+b6vrVT4uWAVKtXT9za)d&M8b>qSg>pGU!F~9* zM_S^S_iCxVDSUIq4zs#45A(Me&7ghiDAl-^%_Ephg3o*8187h|i^*=0jl!(+T&dZj zTHyCmB5m`45UQWHVwac5x=4+PWM$=S4rW@@a!&)0q-X=O2HH@~V^d`_IT#(3j`~p> zGwy*jV4=0pY)8?n5&7?XHE_^626u#<2o13!sWOmPj75@g`WGaXpnKomnz=RTgOuj* zNX)%9(skR(Gf;qdBI}RY_ULEaGPnC0*S_;mKjN8?IfvR$_8`heaZlD?r9HyDV(m{O zN=vRBi&wew{PQ_)7GKIv=)~A7<5+aXZ_|OKxXdEvV)T>x_=vt9rgkGMeNO!_BJ@DK z;t$!y^6?yuE{!s=v=stP`GFT{$h1N28KX{SEsc1EomWlCvai9XNpG|VB~6n7JuHpL zHLJ|?PXW71I}IZne%^R9*>`!w3BB==19DN%G|XBO{@Ia}+P!aiQJ(PJWmT0JS4bu# z6IB{}JzZk}N2CqrPBRs$V7@P6#S;xSOhUvH5gkHD!Qh#m%xrlHp9S`$6Vp_O*K24U zC6%#C3b16K^=#qhX|^`?3Ee#x43Uwu8382 z*R%b8Z@@4#mJR%>c3Yr^)-n~HgWpiBd9D}7+grCOZr--J-VnUJp|A@j>Z7m>f&Av+ zk-A-v9{h=zz{VGMi^a`K`xy*h>W;fI__Hot_uxJniYpxU_O)+WnCktZJ;y=(p#7?5S)|BU#9@<64>pz^4^9jtL>4XsMTtlWrAhqjA^s@* z@Sg|ED8yuV#7rcIunEkY@kMUP#a=m}HkgR2p%TZMC#VS(=9UCVuMz-u<9587{1S4h z1iBJAU}9h*XkzFXL{=+t$DEFb8|qjr|KzR(8#dHQSS)gMJu)iE(P&YXYawM|rroj3 z7^Tzkg1{Hgiq~}+p#%6A1>un;-;rY!wqW<>QuXm$#cn)&9V^b@{yZ$l_DnV8%#y*L zq-UE5v7D+!O|svsqvUz(38z&LDVwJfGVV;jpsYZ$ZQ7MOxD*VDHT63L0~ zOi|m=7HSkaJVlIY;0QDfjZUtRZR`j?jD+5QBPrMl>ICs9OaougHn9IA(wF9`MsjUT znue}G!=Rxa>cPA3ctlde*RzF;nW#Df&iD!FOdgRl&`a&Ys6m=!HC)Hu4^Pcx!@MQf zwb9IE$p%&y?ixLXszfz|XbQ^SqJhF^v%6^tCGGs|BR;bXap8&PjV0YrQR`gbhebZa}yCK@W6yMA!qg~@&NQF)}Z-s)o zf=|B+wYx*s%Z7(dkt6I>r%|31fM!p|m)dLa;UOv#l_qCFNU+EDW z855?%YlFd#M0Xl|h7Tw#(n5N6mQ;!cR^L8y?W{^a_cSAYR=Bl+C6 zP+g`9#rI76ypWt$zXB_$V1>-dS`n$!(gq1R!w-rY%I;4}hZ|RBs8K7nl@~-I^i%!6?>) z6u%O|iPz|r5HP~$+Lvwa;~6J)K12>r3bH~@i541T7U0#~!;jGuwB7j(FC!0wbGI5S zzi030YMPAxAhAWmZ+*n4D;Jv&fr!+a#f<$HCfnAsF-Hyc;#d8kHX_CVSo)lnTD!>{ z4jE8qV*ZGd%Q3#y5%K8@3EQVc<<__mxJ8aiBUVs}NU?r+egxJxjTnh-l6$m-7kL&d z%#3XZQAM%c^GIY+od~ar*r)v^SRW!uX?UEi=Xt@j=g9uzY08O?v(?qymmK72WT@#s zCl-#$@AO_<%ojyz(zbOQ<4n-NripBbVf*BuByCR7?xTa+L_S4qV_reT zu^~n$BKIOgRA?%vrcwt(2TK(p-m#ZU`GJE8l;V85V4cT#e9~)CbYQuGe|LUbZg#HD z4k1KH8grSa_PpCSzBKSy9jtWD{a-=jBl`KJddIOD*Z#r^E%af&Lx(7 z8z6{9ljHmtfj2R-4YH7S|I#4lkQr0Jqe%)@eBeBH zTVJBSYk5VDG3(#Au}Q!6Gh-5LL#Q(a$I;=?qm8?Mdf-v(sp6>7;Po>Gi7sk91Mn+l zJS*!XdTY>WGb<_kYFRf0hV@ag#(}4FSswMuk`nrI0N!p-=+hw4e4(ut9L(v z({40@g*ZbUhW#AcT7dV9{41-xS8P;y5-8CbXjb~vLFlA{l8yO9ZF3s#bP~7mjC8*eE5Aoma&w&lgy;S_k zT^4iH!hSec%h_WF(of=4UBSd>lwhjAuvYjAPb$Cr8&~F#e-On@+o3U>QKFZv1Zk+{ z9(9>(WUq5xa|9(#q}}^Li(RS>T3d-vry4hGkl%(*vNa5PF}=~(FkUNW{$6Pr7Q4hq z!3Z**e>!9~p~^Dt+}=~JeGrHhh%gA3Fp5%X?s1D4V+$S|0{BNiP*z8J;XL}2PorosH>;oJzO`Recx;-T>>g~FmsCN!&QHNvf$-k8b)yq zW$Q|@?-quxkf1b{roGLEoMmLNot80F=qzVmft!kGm^l5>tC1-3*v#6BHMIAOA}Bu_ z$@mT(9jR_(p3&!2@~U7>xj=t8$1p{rf`#9J+)J44m96(0fuZiBH5w(>>zrC8 zG6TCux^HJml-Mt4e?6irS_55h*sYI1&NUh0WB!a@{9%XsZB;c%+;sE!F5>&oS-ohw zO|WKg=3V2&1<@V-%8WfhhM$0lI`b4tnOEFeil3VQD=|}t!yP1_H&4`hf+rG<@&eIb zC^d_ZQLzhp_Vd%1?$;M2iP*~3zx#v#gP{zJjQ@q9od1TQoc|jP{X2QG(RQ_^zxPKv zDd+b;&t2bEblkFR7hF)>7iFKPVh^-^&a{0c<-We4fFyxQOu1z{lCY2wB@v5(5rHzU zm-F$}Kwp$y-IX}oWU2;DNz37>cc_Wj)Kr&qR1h{i!_>YCa=|q~oLwy22AwK~b4XW>iG zl8BN#v~VKWBHR;BM$P4thjPtQrtSN@Op5x}q5+|`OTb0J0J2@{QcjW=>bx(Y_JpE3 zW3j?fR7XkBSR~H=yC0JPnsJ&rZ20?H&FbWqI;;SB>S4S>Zl+?$^WyNs<$L5?t<98*~N#?w4UOE|a*T|q%{Go6oWwZgC;d?+1x<=I8 z-TCS?D^R3HgkV3Oj;G`*^*5hFT;=cbdZCm%-9$4%_eg@j1y6PjjMKx zMJY%r5SXD?t9M6ch%laQJTRui#qi5fc`GdTwMPiCzk@ySyNp4jK%r2fKqF8iKoC$6 zKr2YqpdO*SSOH;B_#hq;gf9T-1LSysNNoNhY(G{23K%(ho0tRKE^0qz002xJ$PMWN zWKFOhboWPp%5vKDNH{Ih6e*9?U5C^L2J=SJ2K#A(I%~fVXg@@NJ(3TMfgM%Da}SY( zU&e2^{#)LlXP_s<3uvQyO`Az(FlHbGL>?dpAqJ}JIidPtK@FpHZaGuzAM-R5hrn;+ zkQ4aqXY+JYud49xM8i+c@#y5%5kv-+ZwPcx1RBrxf{l4^?5zE?Lnq$@DzhXI8>>)1 zFMD6eH?QSU$#MWra%0>g+eiyy#j;~)#Ok>L4AX2;uyF@2UF7A2PN8F1MB?GNNOwpH zak($tTaqfgHsKlF4@j>8W012Dr@l%92rD|pEH5Tg!3;k$3DY(7WAIj-fW>bq{dk&X zu$dW+!A4VQO;cT&)9?g>9p`I$(xkabk%}l}FVZboZk2r%4%S7Rj z_^}#iY&Zdh=KTps5}okpRcd?_%3(1SPjB0XS8dokeNS0`%;BRB_HmkNO3o*Z3?|cJ za<3=JGzCA?wPq?h9Lv;`9>BA#8BNtY$7ndrjgzzds7GX6G4?5F)LhARK2xA%@mVlf z3@b)JL{ajVc&eJev%rpvVy9wjs}9u-N$ zmfE$t=JM{x2GzE_nK38eSBwA-n~3`u?5rhknO*hmt? zHPv%L6w=XT6G;6^cWCR>Q zqiMineiR)tN}Y%Avk(-nnpH{v8@a*-|1)z+JVnbe0$JJyzGiA4#kts0CH_p4bAcFK zuarZ~;lxX*>_`S|%N9?AdZPxmS*tq<*0sFi@0zIltO#SRVu<^aYM=LIcWBRNu+Ju@ zM+p;Ri-{raBagnWlQnZP2ja%Dq;!&(xTQYu=vi}d?hX%6Z5q=BDISl~#VCRuhl zL#~MYvajfAJGK$rfW6K0^cg(oxUtW#)Xs`T%!3m8oEO;*M4Zs~tL8idWrlh&lgkWc zo!S(AVly(sv%fX8eXHNiwzMd6`7a=U6{b>Td`=rC-tw)9X>lS*4va^&$rnZCJ?_mP zy@l7#c4CE-YbT>FqG`{K0Ob$UZ}~Bvp_)1jinyo` z)K7crU`bADmfv@13xB3uvWj$zWiPTAUE}>rCl0r9W^d^iwve;spXd*L8O#mH)aVe< zYu)@%W>p6D*vN;aH|%o}w`p-^aHGpeFqOcRGYkyoyZC$0 z;%+q|*`i6^T%&unA%YS*g>SAz7vSZB`jdh^l-RH^J>$l_qmWu)XkG`n9CEe(j0}mU zHycT7T!)xEhgfAo!6pS^y!@=v;g>Xj7c%D_FyY|pOqsf}bD*iMO-@?969IznoW*poYHd5Icj`jDE}Q4}gW(euybus?q^Uz>`ymw_Jvk0DP4 z6)NQ+JLl?JOtT+R6RLyzYC);}e1I9ZZN)wd54CnhXTS8*9QV~LBE%v6I}I#I&*u_w zlMEQ#q`d_kfbi>SVCdj0t7iiFX?eXJAv$)cs0 zRi~jf^oLGkv*vmr@EJYOpr7T$4;jWN8~VErEf*b$?;L%$pAf%zEiCX}2b%vjEgD7b zj(wn;Hm*DQ63-G~Z)2}35A#dsZ4<~POgCO1;L&Zr!`skF_jI@m){ipD&`_t3_yJ;o z)8EojD()^pW|p(cx&OFkr>**O@uuETU6U1=T97%nrj-=(Q5QT~)PdkjP@?aAopjee zcg_(0adrj=5D!wWFwk`tSqTJ$-+un&NqJ3VA~pBU zfyqQDoiihScwX6)ign8JHv$pKa~5O>CB>oN>`P(NHx5Bh729gn{AZasAaWcs*n!*d zQ1?h_itE_RDW!qYclsxjMVo3j542Nz0aN&m*7q95QQA?Y`P;LLQy5eSQ5w~{f(o8sU4o6Y_lt32 zoXk+&YxB&CF@w%^&HL5rEiHz*z+5g87aB}?r4&dL0fb)Mc1kLKU_P-erzjE~x?0;4iM(beTnC{WHW9Jc)UX3X_FeWZzeTtQ>|cgl zB65+@QWAy~OcgX>`0sG|!#jz+;6s2JoeN>kxf^?0Mas-XGWth{kO`_2;F7fhHX=8~ z3#|*e4}>cqhCD=!3;o*hf>&2qq`334Zdw?f%;3n#$?77g zNXcq4g4c1wIRpM6Ee<|x%^i3niRhUSpCy`MnBgaT1Oiz}t;GU74bb&hs^gqM#Was)v;HA5`3 z6YXu&4B405UwaBVtVmZ~8)=uNMaxY722X0#dKjHWd0R*GYxIaIA4$ zHtBAkqT9;ZV_-p$eDq>irS`VRG`4iP?@5NpyJ6q#?-0l;l*8(rCtmU!7m=_)Czs>M zuZ37m2OR0b)z_19P}R!AVDQP-RQBqn3kLMvD(fNKv%uz)UmKNEK9%R|)MI{uIDuIW z-h{?(+I@+ZGwV%@T%Dii>LIo2)}h&+o)BaEFog`McvWJ}I<-`Z@EpKb!|$U#Us#Sb@L5& zNvA5_izrcFZ7qLc_+tN(vAUZ1D@mHRQFlP+N?w56yodF0#8(t!eNbB+&&p#mU~v=e zemGH{%9ict0x2VSNyS`aFSgbfXnT`gyu3)*u^DB`+luL;INQhyw9uawC0_E9uenz` z|8F4J*u#l`-xL2O1)S{vcTF%d{F@XoGW<`d;7EJR8HXL-e^tLAgJ)@GZQ@F*O4qb> z@m74ZtvP2&Z9^u>JH9Egnp-pL48ZmI+zKXfg+Ow&*8Zet$0CeKA`zs62qthgYqk`b zmGEZC;bqa)F`c=n3b2yH@nsgt(GP9e;@~KdN>rw${OR+?2!|s_7hY&F{ zY)l$M9A@;2kH^!?bE||+wlF9>o}i?TO1?XBtGF`2E=MfqY!7j;mYUxpP)B4m89>w* z{1Ld2K&>?09tW)IkZ(F8HU{_Wp;J$Zr>L3Dk7^-39;1Z1M5zZ4Yq7Qxx`7kFbz_7sYWUdVpAnGy_~Fw3g$BM{09wlc<9U<8H4wGx*a_;nGrYcGrF-iji; zv8gF#h3d%EC1%J}j~-75#)~=U$hP&V?gEW_VyxC{WaS%*)54_z`x~R6`u6Y@SIvp6 zVZY&k(CRey6qej;m#I6iWu*^Dh-jR5e-gT&eDkqA$6;Au&_Phkr zPvWKkT=vs|4}T}1KlVfv^BT^o2lsM=23(dmqCa{ddmw&5D8AdtLnvx84Q>>U0#3c~ zTu8BYJJ!|9$~)g$H8TWD1VJ+#dfdq^PP?^H-!C29LXe}$;-7t+W7E+66j;$!t*9Gg zX~YASnY`{#s@iz}9QesQc6$>cYD^M-)#O4tH;gboH$)Df#Kvqs0Nkp1VSSe>8yz4h)ld?!j!Se zi?16#p`iF78e)LJ+ShK-l>jJfF-n~NnA8*ihoO?h(x@Ef`Z+v-d8sM;H&3&U~ z=jWzWi-9+~jU$-(+zn}#oEuxby|mtN)Ak_2*13Cbvfy&>EWTSW#)kRvMSF^pYA#F{ z%NST-8{DtmD&-g2$w6)rzCfK?ui3rm0YZi$Vfi|XhP!p`@N=8%R{OG$CMhTg2?f-( z2v+i#O8UaL0{sosg0_@~okCk#Gu8=nfR)_(gdw04-4-WI9et9*gqUVG{!-rK%nnHH?e;w(H4 zPAZ6UPfAJ|oXxaASzL#iA9lJ9mdyM2YvS*W3kJPPbBHK#O-X>$aMvDw6LWEAy%R0( zAWr7;wQFuDqLPmzKJz@xWp_eL+r6qsyYW#J=S3_nlSDmOk=@iXA9)`;$0q-}2Xy8f zV(e)@OJ3%UFB#hlSMiu+uWL29o|VMH?gqCsgPT{`F%N+BwExLnCB7@e5vJIJ-8GvV zL2>|_AET-eSK+%hq3fVX+TwZOZE%AaXx7uX$G~pPrcFb(_||q_fnc*1{aD>|8h4)p zQ%>3EaqdZii5dFeF)Q;bsRwLv6vz2zZ8u_*z z3`t_i_o)6><<6C##aj$lg-oqj&9B%IPq2 zk`%dCID`S1IuyKtBtVT3vCt;wPyEe7rGuqnn6?@*Tlm5_997V8lp>H95m$4sN0wxY+v!*J1_Rc_~I&0)pMFd!apJ)w75L1N@$di7~pwqR#Rx-d zU1yAI`Q^$$_D{rgQRa7Pufxp)o*-0#HT0!67AR-o%$v3=rgb;nY2jXrrY`kqnUAR4 z!bS=(ffa||s}c^S{d0G|yvVv3gxyTV?G`k?&>Tb{3X-C~t@OIGSzGe9fS@Jl>evm{ zJ79~9*4mJbj|9%7o%BzQt9)B4zMX7(T1?6K8I;|%qN6Ec^rmnB%Fm6!u+NmbMgg5@ zAQNU|pJMhjypTd7RJkk zd>3Bvkp`<+2bzf~O*nuyk8T6E%B3#ab;=pFa~vlM5^B43mAugbV9Pf7=>d7~{dawT zkt;H!!+dX4lsxU6tnzIc#oWjyh zVAtxLKv&k-liOVN@!cGjDqaE_2~}5gCFjD)wgy1ti5Ihu>~6-Qu8f2vXl zLBhSJPxSf+d;|s}J-6EO7@08x!v95?q3A_LtV0HDG)4^+aw^<@jCm>ewo2+JY)f)J znmOR%()#L|*YzbIlXKKCR*EXb!&2n*%MT<*F-<9p`2UW^F@i*=x<(uyTY+8E3=>gv z_?YQ?DEudS=8FK|%Q_)bW84KfYuqheZ(ryZgZb0g3y~pfg#rqo!nWf%Y_i{^MVyMxA&bIT1aRH)fY0s-{eGn2D17& zLt2Vl>`B@pqC_R?tR!n2RRyZhKTn&RrK-kKh!ds0&`U>vJ$%@MWIJ)H2VhwMQp!~{bhADdoU;it^URHx5OM4l^-{EE&;^IoGEEHGqYAWWnBzLGSXZ(m&4 zx2!Iw{I@V{7i~YFbByGp^BfD|T$)i~S}z}==W}#V9c>5t zQhn^ltCA1xx~3o*_oNaoYdf;fhV9+!oTjH?5c!<;mBonNoRGk;_tGR-i6DBh#vz~$ zE6{+S>7q6TLPm3n1MdxI?7{M;&g>J6RdcBjCek@wq?rAwZ5w1Y?zoQa0$JAo9pbg< z@S*(q1sa(g^!4A6#Q#PJ|7CY)`hOX_%uN5Cb6{rrACV5*+Bz*hqwZQV#h zip8X7vJPc8a>t8GaCxX&B! z9kpwglK^NHeD>}|i#OI!93?qqJTc*1MUU_$5gLo^6&Hp+luEQbpCRRO$@(lq^vEVM zl!`kdUgGC-OXc=-MTpaJicN;4JQfCg4cH|vGOH4y#xW6=ikZqd=!?St;L9@(!cnHb zbZ*9EHXr5$%Z0-6RL-w9>rrRT6Shimc?uI7GvjPvK`)f0T;M$S9ZM!Sq=oU_V9b(;b+l zkCPZF)RBzS2Qh*z@173IX``v_(HWOWMloKjk>(8xMa3xR7RU-KcHikR)fKequ2tym zO1!ic6n+;TC{%h%j_|5=mmTz!UvV4{9*?jv(Jt1GD9voBNhqt*6HT|Tud_xH{2S!A z$x!11_p|8;pepT6uC1h~hukY?vELD8{hQ?!L=JP#x|g%S)|#k3ytk#n>4tXgVhVk;)o)%uCCZ=HHs!uBs2 z(_NS6K2`9NUOk1hZ}2rZpM&|)?<0gtt<6{^L5Tun-8TaQ3K1|*Iahg zRC*2PMZK}KY`>#R%rx1bsi#onMhd}~lLV|QH1Yn{(O_ss+fDKD=>hfptRA#DZrI#9 zXg~2iJb`Z1BDcJjC?%iA-ct!t#{V2Dn8f*^l`bW!=nXQ%{Z6*g9P-(}-U8dU6NyMt z^Q2b+-0QpB1rc5(5Q8X0=qy=xQc-bIAv_XF<6gezWUWGTBE@BO>b2}k^!IB_74OIO z5lO3tr{q)|=|VGuA4-j$GHXxUUlMMk<`7nT;Uo5_>X#SkH?M|fgs0v?iYw8We=vBS zu9WbSP1drzms;t7Z$1Yfw5ja$cWS`NO;c4=AGJaK36%!W{*@i>q9#~;Fh@<0Rk)_n&1-5O>$};WEnZct2=i-*TZpP27yxyxShXhHqD_mXJTgAW% zgQQI_w(^oNL)A{wg+1NzsdP*K(4F3#GJf zR|jWQJ>lCvf(HeuDvEm7&jUomqVN%mi)0}ue1n0M9Q11g1C)6xFF6o7mKT{V4B*EJUw@=IQ7%UTemmKe2Gm?v58HD#);orol% zRe(*x{>>JAby)I`_V&1zLHp$$ofDM&cAmg9?i z2A-CTna_-RHocfm9;;vN@{)vU=TLT2!C4!solODN;U#<2%93#N`L)SR>J&GoGKI+F zigT|alFXV|GkvfCcfW_+`Q?lO&wl-ypg2jX0~Y8YI=lP8wM~QTQD;_Gi0Ru$pW|&5 z(ruKZXytwEaB}^vx13PtlPW&>^5~;5V>t%Y*_DPois~ks#1xAZ%^F z%AeS|G=$0ANFPWqPz-Q=ti*vDQzCgMRj~L@Qe(--m7u>I7tXDq5=?JZy?iqE4?g}|N#kSj6W zXIZE_T(H4YjwtR2S0!B|bA$9(pl$4eGv68EvvONVPNeI6dq;sVj3^Hw-oK2PMOp&- zXDtug+ty(@{gJ(HK3UP=RBErmK)mpRcpG*a^-uzAdd&blqTIs@<(+avBSY@4}+fOHWrtlWd&_8iZsL1 za$)UEh`EBO>+{gROT$6)xX!@=`n}MF_6)jAWhESlqlXFq3)Q8ltK}T5m zB|EdYxE@YhD+W)|c2$P-p9mjNo4lY1RSZ#k-E2r`Tg*oWImnum1ed13Pkts57j2Z< zu}|og!1xmzYne5|UEWu+IJj~7aYeD1$0@DTDe4 zs~ax*^p{wf|Nc3QW~>XE+`(tMCKuZ>thq%q+Oa!gIxJ|ZOp3~Q{5p^$T-%7yRGO1* za^UtkvbE%~8K66?#_A8&S?QgQ71yA5r=gKN2{Ft7N%Ql`Evd!!>g76*hDnH%kkWE& zTufyZny%K6&pv>@g}SF2^yHlddbGo3tEqM+9hn580^9#Xyl0+D^ofX;4$Nh&f)^;{ zM0l2wS2-0Wgs3%X&kS}DxK9LXoE^IUf*b}m6y~WT9(q-Zz%qDM`_oh3bl%EuVRDD0 zY%{+e!x`z~$DNM*iX;sI6;>jsv1+V-q-yuU4f7^}MymiQVA{KzWW3e6h>X;zd`*pX zu({WWA%PHs%ia}r6bm_|!;7#`&0dcELJiy#dNur%8j+Ob1|!`&vI|0xVF~vsJV;tp z`lgK9#3GId5g!KXjMVTCAAm=9P`zVC7vxQgxvb-o%vS^(KuG7{r5geHKH1~Hs-O7M zP7R^h1C9J0u7v5gp9;v@%={f8ulw#zb};V-g81)66@Do5H@BQF6)qBU{9yPRl2K~3 zqn?=uPzaNg+SSdL+aW(F2eea+VzgFsc-k+L18OF=#jqR?G~Y>VGM{6Zo zT>H9Gp2kM7p|+*|0*~A(Cffxe%_AiSy@OL}X}k~-VOd=~7r$VGuz<+c3McAt$dT^y zQ05%osY?mN>!~Y#+8GJuw70U&TVIlYDNsv>UdNwLs48XL%4`fP=2*XiRDX=UR%-ln z`1(NnnE5FD5Bur=p}JW(SpKg=n3?%MaNW%RBd&Y-r+gi^1Nmn^-ODOTmYNyBPJB;X zFdMtlLjD>($JcFIywfWgj#V^Vx z2W-o{wrjWR+zU58XKr|z*;ibd)0Yu^#KQne^Jfytr`j& zO2rdgS7KeiH>yq_Z7JvN#iY`5<-IBN<;VDzfp~c$wCAri24~K_rtoW_vF9HU_Q?AY zAMw(0*rTl;3%o$tRjyS}50WI^#b=@UPeVg5a8MWWEhJ{kw-9LR3^RRnksZXkUtV_V zc(rINmvwTq9okjkE|EOqbQ-q!CL=Af#kB4QVZ01YMaF{@z=>fc zB!Z(s#yv!OSxJ6!f-_j1CC0JsWcg!Bn(%shi&bjtMD=+2YK5hO5`Ky_QU6#!6_1D_ zc-}ysRBhh;Vc+fWG8X6*A;@3Fsgg*Xq|2(_i(-A}yRYZXZ$7GICpIx;!vCo8*(bE|XiXrw9pFD^G}TK@=sLU5BZN-eS6mwvLl3w3fhG zInI;hm=?O0IAglmE=CBTP^&Fkgn$mTk-=Avvw! zPNdE?_&hYGcWFv_Hco;L%tK89U}P=Y7$Z^^BNMlAhi0bZI5CQp0Y_KY=`JG*m4Tss zF05i3{S+9ZXa1C!ecE|*)L6WxoUG{mY5JMRl;a?!59=>v#`d`2>PLbIV!B_1NGkXR zG{4~D0|d8YtS>v;EX=1r3?#OvvQZA1ma#4BxeHp|+a>|^SRs+xzmR%H;9_YhRzox> zF;~>kV4+`|$5tLsVdFBj+A5PghNM$a8O)7jrr1nDA>_&8RQLUfmARvIOAW=rYNQZQ zorA@ge^F?6kQ$ASg5yzh8$crGObFa6>JS2?jIjVH!~BdUgt@Dvwot=uNw^d>qL zk@RiwBL}Og>DF0mDZ&bRY#r$lA{qjjgGu)eXSS87(JSK{mYy``gA?hn?-ULx2e%12 zuJdr~aPq=VCyVW@Ny(`JY~pW&B=fL((1(@>T_}vd3)mP(v}Lk0Xb!uD4MhX+L3jZ* zS6N5mS|$b7>$><#;0)+%l=XG}kRXI;?IeyR{k$MIXj~+YWK>k&K)%u30WRP>sGhTW zm+PoW3Q*Zpw~zsX*;big9O$nJ{c$rFas~?^)V3^BAYwbHuR9q&GJ8uIxUHZZRuX$t zIlQxq7fab(h{=}<|1camX;!aqb8O~kz`mtO`7B>f_q^an%6l7noetx7WHcY-=buHV zTG%Y2%zQthI}8BrD3IV+k2Z;aSLu!%pgN3>NQnuty^O$wsIC=)eT~JYNSj}0<1tnS zfr%lbb&;I!loxCZ(dD*j?3(jMvm}oyXdM9j3R<6FdLYZUG-*QcxAeb{BaW1cQ%X?_ zE3^61go88mR4Et7f9Sr-eGDHbDfZD+$yqIJH7;TS!CBu~U(%yn(lxZsA%V6nN2kFN zOwu}jYKPebNvZcONL@Uq{m$cRzDuLVGOE3fgQJ&Ngv6VfUQumqwfbI9QMjc#7Ni3U z;vL4I`W@NTw`QF>%7%+MUxnyzxPWyCVnl7HBMi-Dj;hdtQ^OA+b8By3>ex|IycqbV z;}u>_8=J0UBwQ7>8z`^+0*2fhPt8}$P3n{TqmP8qjm;uUb9OLRuzwCTT*ZbLN2hY7 z7yo{tOus$e!ZA`#DjKua12nx(q!et<1oG zIq$r%yBay>|R6UFWPTPWwh=NzUdzXSYaTLFd-zE1p16Vy z+w^UKQSs5(fiv6w4KLvA!RyETef|@GdxHr{(YR>3bv%O+u^Ew5ARNzcjTeg%pvLt{l>cjs+muJ zH>QgaUG*BR=e+du3Bk148MO1C6W*+^T`_;t4}Tu_W$G>8ujs36}YBz7<;HK zuDcfY0GlzePy~|H*G!^Z_L$}VwkUrAbbB3#XVB9=@32)dUa8O)3m|EV$&M+L$OXuBP3&(H*ULey-@F5jrLB0DI#LHNr6Xw? zV}_v-M#T^{yawXTNL7s(F)0_;emwr8ZrjZIf?oH)01yg~@;(p>o(nQEx{)k$Hd=rL zvd&ReW8|E1n_5DDK8~W5ooTTsJ1>l+nvp(vRjW>gqbg-p3tHgnygZXu$0CNg-$A1` zva|AERrC`rxG7_`Ai|5`?pQh8k!?O=(C=XhoPm%Tl7MZ*fZWp!JgU z_+SW6w=|H<_MLhUn%wiVS0XT%;ii&OH2<(78BL*0Q@gVky#tKOxVC60a!ZdW9;?+c zZ5`8ux~pesV?$+32N1i(*W8S4h)uPxg(b=|&o0e=4XdA=8uzW=@1NzC9uKci;2I#C zpY*kH4OkXq?|ol9?xqbF>*OLNZ0FspvlwaHkV5yk+5GZ)DHC1=Be##wWpCi~_MZPn z0r93uj(;4ahz&a-&+y*7kl2;Nrc1%A_ znJ8XB(fLEZcA`^GSO`(YHhmj@(KkUhYYHN+M_!+-*R$bsf~at^%am-KoLUXpEo*U2Jkuxd{$%@A!1isHqY$DMbY+I9JG8W~ly&oL8gqd`85Q8~c2Qc>|J4#5< z5s3z>hbh}2#E?y>sJG)=s5ELn{oa+lB%okXQ_$*Ejqf^=*Xr3nxdQ#8-4DG?-F2X# zCK-&^f%^=r_M_|HyFs5LFC^^(Y|V%=kzx<8P}}YyC!2g*6l4Nf0>GYe7IhFG4Z~5!pq2r-#zwY6?}Rs{+a7(3y`A z5B(FdgVfhUay!w%6*lRJ3R@a)T!lV0Qnq&60Ojp>aGL;*ANe^3LM|3_*-OOm|GAQMvP%|CQ*jImTQyEiAX4W$o5Bo;1GPeruh2AV?1 z@1E%_j1^!9o*XZZ8*@&BE}5FaJSR0GkX9y`(07C<;|9c_6@*L;aaDP9n+xREOlgs!Z*VR^ru6!e z4uG?h?s&2L{F_Ei7PoNPOy80`bM@kHraUdy+1tSkve<3QgQ0}rBd=Wg5ym-Sy-Cyr z$6~n6cu=+wOAbzQ)iO>Z{u$}=-xEsXi_I|KNz`cmjZ}udLPrWg)eEmhm_uEJc!KrOg>nT-3o>0q4;zaK-S@6MD5z$!Gb~%8@q5V@&ca7N-1jkV< zC~`EUp+>EE=*pxspBZ`@SryA&%Wrq3ao_1b{MY}3qFFgP{*SYandLu#XqNvGh`!R* zQNiU!`E{!CYG=gB#cDFUwo{p1Nfr+(?N7(G4nz5FXlpWi0DA{6^6L}ZO*XTRT%HN~ z&$YUp61%c&O)A?Wr`x1&t@u+rMuni>bD55Uf=dZZ0; z(H>N{1ML^*D9B0++G*?U^A*C)mYnTkbn(h)+$+oXI^Hg9ejVv%eB`-;QlVN@JL@u;HQZ`fp7$_Og4{m9|H{8r{Lb-P!>G3bnVr1bOXv;0dr_pub?pfK+? zOXfN5oqme-P}1z^b_~ipUg{2+F|+L^RFOIi>VvUJiL1`euMZ3_C=GO5~C@2iZ z*Y;vhQ$%ngHt8>Z_=a7{({KtgKN#$!zH`-2s{f*FH2mBO>{_ ztRwEWXMgmtx$QB!HeygEghvc+e{~rxJ<)m+V3`qEfJ3@M;{=@;-1p~NICyU%_h-&M zVA%HOh+CI!h)o+g(F#o$_*tgy045Q@s||BR?VYDew{cb#2F!DUz+=xw*ip%6pGVLM zJLd~Msv=<-N_F9WuG~Z(WDm9I<~)H6$PN_~c}SQ(rdFm$Usuq*}aR#6R|H3x+9 z|F%5i56(0~6n335*kEmVC|nk-Q={imuwr&;MkqNekOp{BW3Y1K8cM{Hd=Rbn`?;K- zYD$_}_0}5?*A5ZFA95b&1~nbnKg(&u=>o0R9?4bu*yAyS6Vav)&H>n0Voo*Wiw&uk zsJSeTD>>58LpGkd|4H(Zo1sg!eDD{aLd3=z>)WMV*bnn>h#kHBHQV+)f0OhcY?zN( zieZS&3G^{mu$vtQ>ria;V^}ff?Q}S|4LI|_A^-H)deCOvESA;mgzURDe;8++&rBEjh)lDOAr{(PLfbQ}EVMh!<>S^52ct`ZrsDT?_oqzKLIzh=(SzKPws@ zNNk1oWpi)Yg^${ma!b|rYa3XPTe5IorQd13VI1Pc7BCeP5*rW|nvg$fxEgEZR!^lv zP|;kOU0`J4nb&}10vO z%?`PxhMTQ!iKr&3?-SukE*zjZPRdr4Qdo>fteS+9X!ov&l%)GU#lJ3q85M*gfO)xC zT)_>E!rt^)c9HiSMMT3G)lshB$se#VV1hYL3PpEo-mW&mfCStD`wj(MMWZ9?%z)IZ z{Pb61AR}JlHX9m!tU3`WaoZgi-7=&bY5fMhPF<4QG>*dHY%B6M&+rd3@FBin`fIem z1uw~~M85;r2Zm;UqQ9B{fOx*6SpT?(qJh#FEqB=|MPE^@N3@$rKQw6RSbEvqb5&8^ za>Ej2ui&W|(Ltd|`%i}%6xX5wBh3(h*^<+QCL|ah(`}-E8r&svJ!ZV0a7TeLA8{9a zD|jrhzoXDHsG6VXGN<-4Fr}%h7&;g$8m4PCcRFi7L@;foz8WDSm&vVTglQm!zbxQ4 zE4>#|_08c`ZX%r1KPS`iu806RLlP5SKJN8+8+cSN?ZZV9&iC`Ecv?S72B8M2r7?_ z`R}8Xd=e#16>ufpf~!5ogIS0yi&1b5Rfuq*1|8zL_YCQKs%JOP8QOY4|VF9ln94bXG_S@ragBlyflCe=Q{0O0?ab`ae~F{;l1}8MTx&_~UUzyGHAp6LxL|r{|AdSC-ZbI( zPx}(&?z#OmJnBJl6lL)j$GcOGkH650GmSvoJ*N8+rH#Lh09M-wV_048QzTjU@cm3MzXs$ zjFe4;od~og_uVM=L#%N&_H87N$y`KJLwM<_x145$s@*MH6=l*twQ*^TY=AC!h`lVA zs_%!cg%WCGc+~J^z0S#!q(7)hjas+OU(_H$4Z59JHRJTMaBe}aGqUF%qIus8St)Lp zP_*&+W^tb*5 z@6SuhaDM)OzR@YgMkX+c?Ce+Yv!>3lAuA8nWjEcuzNnOghIzI8HaQCtlQ#9I~uFHGwahWc(9?4De z^Cw;KA@vx6jcRsR|B=YcQXeMy@{FWMHy@cSrVmwf(UuHD@`eQ@IK$1K#e%9hB{K&{ zoSEQ#msl;qYuKaFnxEh|zmnf=OhEVJ=FyLsqN{F%&fIoZr<6qG&pdYKCml_d%Jipl z6DTqHU@|KrX%iig7T7hDR~EY6$At{Gt1j~%6CoT9gza4WB4c#C3{5g_&sJUcYvh9W z+Ek6d#>orhe?1hsUxsiOQvpQrJkJTXArS0zOI<_oN(hXAhL|lcg1+;2RO(zLJ+i9a z{}k!pHZudP@H62%`z$gRXy(;vQ-tPD!?SW|Dr~WPwm_Q#g;B}ahq;j=G+O>m8}ha` z|49A1=x&LSNGKf$Ty`TmB7YW8oGrJ6iJ>cSHIqYs6R+73z4AtND{g9ADy@c{K;!P* z;1+iwMaMpWQXlUZ+`JNBmYbG7deM2`$EF!p#3)ERK5zB5qi&HjGx_G*iFvJe%G!|STUrswtxM2 z$w;?nkcJPSX`1J=<9rtwb*k%n8t=l!h&38s4mQ%=aFAnx268JMQpe=yBP^OKpKRWRfCHk$>UGIpbQ2hez z(!s!!6}2PLiO$saFq|_40M0jUq2h6$vB#3#!mxJHW0-S@L~16zz1`u*+`gjR z)tb{{JsIyGi`biuB&rX@3J>a3elwRw4j=Yg=J$*Kmr^-y$5WOVh?kejS=wKV^fDSBc$r$`{o(GvovlrEr4<^h*E6F~8Rc>A2s8}^8CYp&3q?c~5IX1R8%2jlp?D zZa#&apWF%XP!K+>U#hU^c0-EO!_2cX)+efLcVCzHp|rh2JCf7ReF9-E_Z|G~qWSS- z@SYXLE@xq6`<9KK;RXv}&U?mu8Av<(=4xc5q1sk&`^K{Rc-A8Cs=d!leldgWi^~z> zZJeswKKqXdPhFGy(tpi(+)g_i<~HoZniV%Q&Jv2}bWZ-R8~COR{$0Gql8t(8ZSc34RIt%S>lY9J4Wz@WT?q%4{)P&6}vB4>?T2^lZ zBMC3@22}17+`PN}CVBTld4Rvk^WTx@e*wz>ViY+3U#JN)%YR@LSpG+hLXD=K%O(fh zSFJ&TLz7Q}NRMKo&+1NzQ>$xJ8vR`-$GN~7z{KZChu+8O?XN6IF_e^h=jN3!q(7LL z$WajHzbMARam+W1SI_+%{nM}F$6&#`UQc8yzoAjs?`a`yG;5N}aKI5F0iUnpqOSRu z;r@2Zhda`#h$R7eh-Hr@B=#I120d^P!kkLJlSyg4s-Pl2ew1pnz0nfnhIo(-mM_Sg z&=Gt>n$VcLgkY!*T!goz`H3YOkeT4BsGI-3`oyrKpjU7?epw{8mG1igRan?cBVb-x)q3!`# zo9N)yFs5ymNSH=O@EqmLybA03v}_8fZ9Ab>9i_douv*;^=EV~mKjqF|Aa0&*;nMl3Dwn8^HT zpyx1zBr#&q{Bh`YqWJkWQ7uXEZlH^?udTqYpy8#+h^ls5v zT>cR^G7JF))+V(V9i_jA3HR%5_+<-U?yp~@CdQ`kb){E9QYs(l%>wrY$xoQqK>)V)BWRRCK^ zgQ`Wb%>Q@5&Msy`tW`hYRVHe;1zs4g#S`JcYZ~@`4LY21Y;`-Ry)FVcJOCrjGf~`r zu_03lvTRax7PtFVqX5ed#)?>n1q#9GNo<2xljSwbI033^r=kAUaH(S`H=>8KHG2KB zBYviM@+C^Nps5jheXvJAr`<+@!x%T@ffknYp*Kl8rWnCi&M+Rx+w?>>37qE|{p51w zFZmd~X`rW}4Pm`OLXR1@jGiZYDz83cBuD@Q9R8lF8rh?vJWd=vHJpm7V8TU1(^E0f z5}2dL6M-dQ#Ybcjn~_9`;7;N%>r>m24)@4Z=UH{DX##u(_565mvOp;tK<|qu+v#gj zF$}wgp5wIFJDX^m0j}G@XV)ym}R$n zTyQOucqM1vrxFgI&xHMG*6QL}VHgh$Z?>3^DP0t^s)ClbDS_KuyX_@{L&TpSvzU5O zhF*^n}2VS}RYG(ya2!Z?MeIC3iyQbTo(Z`H+mj zkN9|dLUhZ=*mU=SN8`QE+F!dsvJTy>|6x}CAC~Tin&bMvG~JK?kcsGjO;A2QMmaNk z3s*}bX4d}^O*f*crE(~N^y{VWR>v?XhDa|onR54Vz51&yxuXSS+pV1XB-=5LAb;r)*O*1sN>6i%1up_EOBT1|j z4Aka|ySK?L<_vC9R2hpzs-YYCL;F^ij#2`|{D>5J8Bv6VvIHRkk-ZH&)kCv38d1>= z*|h<#-gV+!=q=0AoWkd*VLtD!uBP8(17Fa3Y*F0^>wEd)@ofa}TN29V&>NuGyoQ)B z#w5-}UN2Hl;^@Bz%d1_gD1SRcZoDW9H0l* z1tviHDrXpIxWLrR9q2X0H6*;s+QW=qj!qhGfCr?|j_F2vI3zBgHq{B$3HFS9nS{t` z>A{yL_Z;aD1&ThcK&G9+1gR(;TKF*lMybju9-UfXa3|}O9t9TfHI$2@2V7V&*I8$_ z(M<8@&!sR{vX@C#`q|8e*x|3EQHBfQS)(1_S;o@NLvJ>h`s*{`Kat~GP}H$OERjmT zB+1B~Wy$JjT%Jf1hN+6w8R z=u-+-X;qFxtpRJJv^HFojN#d4mma=)Cs}k$rl_GdjaE_7c3&xiCTGs>w#>P!6y^gu!5-TUZwm<0?3^z`X{O$ zRotHJ7G1gdH6L9)x<67Frm?FEumB-(jY%9*f?9d>&8Iwp#; z{wJ~lw9mzPmK)z|>QS4XQuD;SFO%b*)9Y0lwckR|Su0Iy zeTHY>M?n{6#_so-%M#O4${8^0;#TZ73qIDFc^xP_^;JkSVk5e@%ksOV@Y|aRA3(Km zx12!k+D5D6wE^-8@&VKF(`bJYB2nk53l}5bP}a%oSxqhaPsUIBcSw9D0iofciU~wn zd^f|*ASTZuec?!1_aj*EpN4-WGgD^-$-h^w?WSrl--aY(`SyRY*C8=Iv!;i_nlk$H zOFrYRVXb4WnV80Th4y8>Kp=IpDVY3R3hxkgRnR5sDbCe;Lq!sRZUU=I@tg8{PT%#z zgdQRc2b9F`m#o@#-#;TgLb)QjLSeB3yplF9XRiL_&A8No;DX_Tn1CH#5EO1WfiBBj zS(ye;kb?L?J-04r1>FPe4G91`cf4J-q}ff#a=XZllKqjYEzk!~pPagKkY)fgX2@{_ zwD*e<)xW32w>TJ!!h62EBXAQvt9*0$b+lj_U<^pQNI&sJTU&Mw`6NmT@%$BJ7wC-| zDcLPivx}d73@-k*x0PmJj}l#*9y_X5Qjbx-%PQr3!qLM;{(kR)=QV;b<88ay$rytS zN$5`&U}kAjd!(;gy4P_fseZt~O4^t))3BgG-u4I~lFw>*K|u41uCshHwC}y1stt!f zQ{gc^hEfJofwUKO-GH_C`T;*84_&}^auW@f5glhGF0i_vleE|OQ{+fY>(Amc^)l0X zl|35e#Fy9>70!(ytXuP8=gX7&zuwn|Uqp6%>~YHj_EZS&wj*sy6rd#bZcMynhz5$O zsMZMgsD22kMW;LWvXgT7N?uUDXFVFC`7eD%?N2^;pYR|H;qU+bCHt@U>3_Xh%pCte z9g+1vZ2heNqpkl6*UAN_s6l|nh*|qs*5S%-j6??hqX3~@!Ll&c_+8FX2{c84B-$MvYvD)k(ZvQ1> zdf^K#a`u!+gwD3pSHQJGKY-ZIFbIQ)Gafat{ePF$WFNt7(Fs z2v4YMsbzR61Ch8Cs8o(AFOYt`QK+j#J95g$@6A37kljBRAKI%fGwJ9ANoNnwv55}1ih#4!BnkTQT-L`|W_nL>oXq=Ka&;}J+C{qcyE zL5hQ+vSv<%vCp}Q9>&bKL5tH^5VG7r;~{K)25P`YA~$GO=)lP2upjA(91?^S>F;}9 ztVc3Q&O~G}yU>BD@8Jif!PWy$P14+}#ndP{MVKUzWDRO4T0!d(0vQ642H-i)ZRGrc zi&)IuvQNU5A~Yv`z>ur5h*y^B|Mqe##>qQ|_$$;qJfo)Da$9U$*k1XqaqT`F_As>P zCc3s=f_#IelVFiz)$?5HmnWkaVQ317Dk|4d6~rESC_bvz)p=E06LE<;2JV6WG{Qli zgjWnt+LQ2xMQg(jAd`Aeg??v>lBiJ}Nw|c@Gmv49_7%IO!p|_!Pl8_7F$~)!{~vupPRXjk>YN&QT_pTI0>rE6ank5JQSc znRB#S?iz;mvQO)h2)V4MWkGbl3UV4-Ri&3VQOa1cJFlFbbx(cXPVmsiZFqnT*kmMj z85+!=`>0acAsugOvo(HvVt87e|DBSt+;59KR)lWbmy%k?KkI*N)2Yr}Tf(#ky)5TX zm2p;=8B+oqyiU-f_!o>J3WYYo%1>54*!7!6QHEl@tD)OO;NuIht}+DwaDt0o)k zJ-^Z(_ijRiiRj$rb*vZe3ee$Kv%+n?gA^+seto&Nx1jUn^`khy6s3RWed-t!ZyEK^ zTmR}=g^sK{w8{IQCK7ZcvE9KkioQ=XAIdh8z(d+NDT`Q+}+}UaC;CL+iF_i33 zyS?`+_u+`o(fYirEhEw+E9=jC1gg@yt{>qZ*5a@5-)LK&wX6Q0o#SpR&8u{FEYp2= z+&bv7{r%0y<0joY{J}L#o!nvGyzrmJV{IC&Pg;js+gq!vcFqV*_Z}9Mw5E4^+bgT5 zUUnZ6GN*l3qJ=EIHd++4psCR6qBW)Y@xs;Y9n_PqtZEJLWQ~JOdT*Ar>!x#bLQjuY ziK*OrhHrSr##K`_eQrV7^&_STRKj~UjpXWD^mXnp zR8iTV5O!X9vX*f$Nw?1q+GnoLIiOtJX>rW%hV7!1_*UcEtXQ{QvZMB|uL=07L)YOW)n%%~(ZrV3hAsHuijD&u5Fz8ax3($C=F=eteO-B31R8sYOPCtBaB zL;D(!9`f$m8gQF;$XoSM=+X&Y`O1gl!;ko1sGXa>@?prZQB(4Dy_<`?pIrpcebdhJ zPA0fKh&gbLd@v!J>HN0AoBT&h_tvVvY2F>X{q{WWIKDmj{hf`k)hJtPES62*OB<&D zCc^B@$2)7?&n<30dEqL};J4lTAN~G%b~rVONM!#SHe=J~{BaC=D4#($@ICogUP$My z6u!>pnVp78$)t#ahatS!hF=4c8z-)_#G4nqZwW4Zuf+4QT<*xSa;nuetINI+NXb9@ z#YFH`*2R%Tm1>DstD9DDrku*wbjTMhYH>B)_{jTZgGtfTh}=A;|2_RsgRQU6 ztGYez{sXT(J9y65_9oV(gip%N=hJ2U$&>3JZ68)#$$d+!LcwRqG-!{IIw2^O)=NimU2P zK87ZjRH+K_majD0LQj9(2A^`b++KRxvTtHsUT|UcUN@oW>Vv%ph3C@V^}JQnjBFgO zWO@9}>qNsQb0}le=QVLV4`}SW^;^Qd?55n8bD~;mwxqk~8?M`_8UK$jyWqw2_p8b+ z6Wp?E0=r#KL4|pOWS6V)TL?VTYa_MkCr{9I_=yp^-pcAnc623{FFEvet48F=q9vPg z#g;0#&l@&xbxK;ctIR5)C1d_g=J{c!X|~H}oYAyb$$ORX_Rxy^QxkUSS=H8d1iMsU zw(h7NqprJSU3wYSpYpOQn4(xWq1Aozo4{zZQIEdZ+!$H7ty1TW@|g|7Z&iwXpVRZ< z_vyz+sxTWp4q4%CBb0YO)Y)e~b(Kbt_K1nL?_D`9^WHn2@{MHJ-GpuCZY>S6c`qpa zYMcYdUtgwmcyx~1Go#XMo1ozh(RE=HN>?XV;eTOkRELe#<+wLE@U_FetMwffC#2e1 z>&M$AvUeYI(bqn#6U$MGPlJZfkFM}jY&dd`lB2>djoZTy`$i-!R;r2id4NB7<^3FX zMqJL=geNmpeCFgV{B=A<`;pqJ$=h{;ickO2`+8RuQQOlmkuh9pyU9pqp3VgOB3kSX z&m+%_=B?ZrF|y4q%{KI}J;`gDmKV^@ZyZr$!_kW0WV1Tzd{0nRUR4_3F8-rp{Iay- zQ614O;?UvKK zEhQFx8oR>+_X!HitdC?J%srs>pe@)`Q&TU$?QX-Qd$DGGudh45eI1i?+Qmf9rdC{@ zA<l(Lr`I-zdcDYh%?|s&ic)vay^~{l z#rDsp4H;VNx;3L*<3*ET`(IKDJV@B7{g%-Ewe zVQ+NYtfSHQmj*=ZYHbK^&VGLE;)H3;N4q=>+$oPW=sS$_|GHvx9q+FcN%tvk3X1qp zz|AOsrE)Qnt6OcXu2z3z#+5OGA_ z_%lZQi%oYgx%0G+y@2kw(^~7P<%PZT9moe%SlXsLHXqBU{dq5NHLLa5Ukmt2UmG@z z_P&IQIX6}7wHtj+(ZyST#uBLgdl-H;25Ltb;tNlsyxJz9`%9PUSV;%0iftHhf9d~c1 z9u^(-ANLD$#Nh|?&rVV(dsQ-VPQ7Yn(fIo*ic@ZStUc%Ea#TIGcT3InN%u3OBa;ZC z^0(*s+xM?|KeZG$&ZwnWAt80;`kWnWl@Fdz{FwK`bHtsQZ3-O4ggWgMy*XD_&9>{@ zxE2n$O{fg_s?;4>j$an`DD!3+?VW>#;qT!uS>0ue*|%4jEwaxF+QglG5LXk(kUZ7EGoUOB7Ksld-SjtPJedk>{3Q4 z`GRA~E2sREdb;Fy6Rk%kjZfH%S5@8dQO!8OxF{-cT%63zjlmR zM5@6FCp{x1c1-2B-8#_`bNs+#ebbe5mvGic48L>W!ZE?TFnm?k${BV+_&NHix>jE| z6dUai?=5TmM{W0Irs>okhu;*|caK^_R*YJdIFIh_l+`9|dP?!0n=xC~IpcG(*RD^$?3d7>?wnLu?R@SQ z-&cbi_-@`k`#z@4*%NK|ZltIrUt5n?Y1t{78>gB~q{de*njE*v=cp=${#HWhs<-!hCSo>?n_-!ty_16%UPjol;y-! zn$&k-{=JC>*=g-7vMMs2x;FZ39PQ___G)8I<+!Y?Uzj5}>A^R8f-KJY^9q)CrH?lC z?%oZD#ZCJ9wDpsdcCttBm6{zE9pyO=x+V;VcRkmqb3$*J@P8;6V z4P560KjSZI9v(cV*1WyjKl1mufEOo}MlO62)Xe5BwLf!a#iVbgdroK^=WrTZK_}J{AT0ei?!|n!R|e0+qx63u)E`obLknmF-aAqV{cT*I@7g;!FKMa z9b+}@3}a7ZG^JYnLk#ckhL-M6*55NV%X2}Wk&Q>*`@g@c-~3Sd{;k>r@RXf8m1zN= z+s#w$RsXbz{p9Jui)hTe{b!oytw^cUUgV)0zC7LH=}h=c87O;KIX~4UhPiG^+>x zeL4KwunaC=g%n)TwWxvmy!+aA{?lwbN>6oW`NzihWAkp$?CQ&^0zdU(aa7|iQ-j@n zH;G|ny&{6aZr_?bj_6XcreWjs<-4MtzWDD`?n~3^SazDW`Bd{aex=_2=fCw(+k5A3 z$|!v~?Pavafr)t1IkhF4CtEfji#)U756#xy1+|G<>u0*}tls~wPN7yUJ^hrwd9`3; z?CMmzy-8tLU)=pRJ$OZ(pvxrUo`%-G=7%%&Ysk;9oT%44@M4|2USKR?*0)#Ja!ifp zrF&;=`BRj=YV(T3Z+EH{1bc>ueqg_>?R|PZti5D8|3Fno($}67)pM5w)IQ+9d$BCO zY~h{MzzOG!0&|c35qNY&jKv$Xx)dFQ!l`C^rzSTZJ`*tWLqz7~t(Pian=PXehZ(^%l9DHP!BK6tdFJ2T@khO0c zd$Jzwn{;E{WaFjLj-3t{+rL?y_)_$$s;M>T3vqUo+@qv#-lJnpaJ;+L=T015Q4{d1HW7I!$9=A*{IQC z3u!}ap(KPPIS``XXOOgzG{hK^Brzn(!4SPQU&_KE#*ie5Az2QF=p7uA7Ltb;L$V}> zWH}h3=cZB?4l#ygNen4+FhuL&k`_{i7(s%qREYvg+q)XMG`}*91PKgLDE9% z5MxM{#E>cnLn@LWN?AC>7*ZuMq{+dMiY%^@7Se_oLz*OpG&va3WHF=-F@`ir3}HDK z(qu7&hZsXx5<^%HhOjJ#@DO7NOJYcugCQ)7A$^E3q)TE*mxCc)7DM_FV@Q|8kRb;{ z^m)>fbD1&37&0U=WXQn~eWIO|g+q)XLlQ%XC_CKwLyRIsl!Or?%E7V!2md93 zgos0oBt(>gQeL{!|5Kt;RzgFKrF6;y$-@$xv_M0RB_xHV+`I*oxIjalxj<5>3nVXh z>Hl7_l*dU!jiprj0?Er?`fov(vT~?17)UCGf#l^d5GIL%$U}{#R2l=x%VQu+A_EO| zCId;OGLXDn2ErsW5aoZu^52zoq>4GvK>i|C!GSP23sNQOFX4cK5U4TX(IQg-p0Jq5 z3BbGI`qu(9ZZg3wRp@RVo%u{0I<=TMbnJq+ej18C6Ng?N6NhF*;H{sA?(La4bOX)A zp}SP@)=xwCTTC3fsbS*KodbC5r}Zy^Ks~z1fOoMu1NQWXBG5iy5j0>@|9}PU14i|O z&cwOnLCGi(6uC$Yq`)Pv6@5oy00?WAh~*>j5+gEDPjlpoSR6pXLqBrD0SU;`Em;ox zURlEtmO44&Aq$Ivj!3v2=*Wa|=!!342eK;=Q|UOg(kMYi93rM-d65N1OvUmJsuvCW zKjlS(ub7I_iw0vc6{8moiDD{-7Y&hODux#gRAMTYchKua!^cl~(QqQBV)UYuT1>_0 zMJKtKisc;?XLQ*7lo!qE#Z-)5G|3lJF?!K#TTI39qUpDois41`U@;ZTJ1EX*hWb-p zG-VZ2F?!M5QB1|?MUzP}70Wv)&S;wVQ(iRR5>qjH(F9CP#pp#dAu$!hi>5|mDux%$ zQN&a%@1QuN*~3qH`_lX@4E)!9vt(*16cpIL@XSO+TbCq zfA{cHZt+nSIQW^*3g99qM#z#XUk%neW$Q}7vSCK%w+E+TJp16M~UyhtGM@#cXogbBk0GKR==@t#7K zFE>~q^u|LbGy@<~$ni8~f`hhxf#5%LgAF2)pDBS592{&COg0e+y$BG4!5|Px1QN*@ z^e_$x<%?J$#{2*spbs^K=lY$EA7FyoWWpBs5_qlz$b?7`E3yHXBjovscmh5iQOxoX z1d7ZJ0t0y*QyPg3bGU4ZF^xpx7(ZjfgT$e;Jz+XStj^#blwcJImxCc; zv4Zc-7X2YevXcV5a`9T+ECu4o(=8< zK;{S-(#R|^v@u1d7|6|tAiagr*=0Z^8h~zK77p6udma=bd;;b~MV zC`fqX``cg`LQ3Dr3(p^DqZ6sfBS)p$U>XBS2c_FcB&19z)kY*@ zxywVkjRBZt+h7v1AC&F~ld=6sWST55k;uSY2ayaTwKiFKSX*E+EGrKtQITv)iWkU3 zzUU;~MqwcPP^mT;rXY`5l5NBA!Xz3}MwjXb+<|Q2q}#xqDRSgtFaUayXG%-)0!WcO zNUDuUL-!=oZ44@AY``#8j$SGiqk~Muj29wG|YS> z(TQ@#6IuSe1DP-hiLRtsFUe-q-3Sq7nrW3I~rxUS0r%;gmUs?yuz{G3{C`QZ17nm+5 zzJOtmxi$(7DNRc00P>ImrF0vah9t34Z9s>dYa?NOPQi|63YmtKA*JL|XfToqOSOTa zi}4$1ljCy`oMd=`Jfw6lC65Az3kCx^S&q-iSf5kT6BHSFz;QAg@ zvj)I)8fM(XU`oqk3gltiC}5$Hu?5IOZk&*|nJ#Bd0YAVb>vI}~jKPIQrD5kU9Y*T7 zvO18eqI4U8K-OIbF_cKx8!kyCc5eqa_MCt%WTG!U?4+JMWkV+d?5ur||a zvc3d5F!2bvkhMYLbz~Br6w_)rffk`cMZD7lQ-P_U`$c-M-xG;#4 zIE#cVo)1?DZVSc}oOxkfWP^_f_a_U$ZN(tjVB!h(d`|%$po`qiYj1;hGqa&G7+_~< z%OHZ;JRc1E`4pJQu(6pBA~o510mb6~@8OP|A=char ----- + CARCHË Carrier check ----- Z=Carrier +      GETCHÒ  Receive a character ----- Char in A +      RCVRDÙ  Check receive ready ----- Z=char +      SNDRDÙ  Check send ready ----- Z=ready + + + A.1.2 Installing Your Modem I/O Overlays + +     ZMÄ musô havå accesó tï youò modeí foò obviouó reasons®  Iô +     needó tï senä data¬  receivå data¬  perforí erroò  checking¬ +     monitoò  carrieò  anä givå controì bacë tï youò BYÅ  prograí +     wheî  carrieò  ió lost®  Iô haó tï kno÷ wheî thå  modeí  ió +     readù  tï  senä  anotheò  characteò anä wheî  onå  haó  beeî +     received® Iæ youò systeí useó extendeä BDOÓ calló tï accesó +     youò BYÅ programó port/modeí routines¬  yoõ caî instalì  ZMÄ +     anä  utilitieó withouô regarä tï thió  section®  Otherwise¬ +     follo÷ these steps: + +      1©  Finä aî overlaù froí ZMDOVLn.LBÒ thaô besô fitó youò +      modem/porô requirements® Yoõ maù havå tï creatå onå +      foò youò systeí iæ onå doesn'ô exisô already® Some +      standarä  formató arå includeä (seå -OVERLAY.LSÔ  iî +      the ZMDOVLn.LBR). +    2©  Ediô iô witè youò favoritå wordprocessoò aó needed® +      Makå  surå yoõ locaì consolå outpuô  addresó  (BIOS© +      caî bå calculateä usinç standarä CP/Í methodó  (i.e® +      (JР COLDBOOT+9©  Iæ  youò BIOÓ JР TABLÅ  haó  beeî +      altered¬  yoõ  wilì havå tï providå thió addresó foò +      ZMÄ  aô labeì CONOUT®  Iî mosô cases¬  ZMÄ wilì  bå +      ablå tï calculatå thió addresó foò you® Iæ yoõ wilì +      neeä  tï  initializå anythinç aô prograí  starô  up¬ +      includå  youò custoí routinå aô labeì INIÔ anä  youò +      uninitializå  routinå aô UNINIT®  INIÔ ió calleä aô +      prograí startup¬  anä oæ courså UNINIÔ ió calleä aô +      prograí exit. +.pa Š      3© Assemblå witè M80¬ oò SLRMAà oò otheò Z8° compatiblå +      assembler to produce ZMxx-n.HEX +    4© Uså MLOAÄ (included© tï loaä ZMxx-n.HEØ oveò ZMD.COÍ +      likå this: + +      A0>MLOAD ZMD=ZMD.COM,ZMxx-n + + +     ZINSTÌ wilì no÷ recognizå youò modeí overlay®  Yoõ caî alsï +     uså  DDÔ tï patcè youò overlaù in®  Makå surå iô startó  aô +     580H and ends by 5FFH (128 bytes). + +     NOTEº  Iæ ZMÄ attemptó tï seô thå currenô useò areá tï  24± +     anä 7· ió returneä iî registeò A¬  BYE'ó extendeä BDOÓ calló +     will be used for modem I/O. +.pa Š +     B.1 RTC Clock/Date Reader Code + +     Á fe÷ oæ ZMD'ó nicå featureó arå dependenô upoî accesó tï  á +     Reaì Timå Clock®  MBYÅ anä BYEµ useró whï havå theiò  clocë +     anä  datå  readeò codå installeä neeä onlù seô CLOCË tï  YEÓ +     anä  leavå RTà seô NO®  Iæ oî thå otheò hanä youò BYÅ  doeó +     noô  reaä youò systeí clock¬  yoõ wilì havå tï  inserô  youò +     clocë  anä  datå readeò codå aô labeì RTCTIÍ iî  ZMDHDR.Z80® +     Witè  RTà seô  YES¬  alì ZMÄ routineó havå  accesó  tï  thå +     followinç binary valueó (pokeä bù youò clocë insert): + + +      Address Length Range +      +---------+--------+-------+ +      | MONTH | 1 byte | 1-12 | +      | DAY  | 1 byte | 1-31 | +      | YEAR  | 1 byte | 0-99 | +      | HOUR  | 1 byte | 0-23 | +      | MINUTE | 1 byte | 0-59 | +      +---------+----------------+ + + + Youò inserô musô starô aô 4FEÈ anä enä bù 57FÈ (13° bytes). +.pa Š +     C.1 File Descriptors/Categories + +     Thió  tablå  defineó  thå  texô tï  bå  includeä  iî  uploaä +     descriptioî  headeró  (DESCRI  anä ASKIND©  and/oò  defineó +     categorieó  foò uploadinç tï multiplå drive/useò  areaó  (Iæ +     ASKAREA)®  Changå aó desired¬ iæ thió lisô ió noô suitable® +     Dï  NOÔ  removå anù oæ thå texô aô KNDTBL®  Simplù ediô  thå +     categorù  texô  belo÷ uð to/includinç youò  MAXTYР setting® +     MAXTYP‚  belo÷  musô bå seô tï whateveò letteò  youò  maximuí +     choicå wilì be® + + +     MAXTYP: DB 'W' ; Highest category you will support. + +     KNDTBL: + +     DB ' A) - CP/M Utility ',CR,LF + D ' B© - CP/Í Applicatioî ',CR,LF +     D ' C© - CP/Í Gamå ',CR,LF +     D ' D© - Wordprocessinç ',CR,LF + DB ' E) - Text & Information ',CR,LF + DB ' F) - Printer Related ',CR,LF + DB ' G) - Communications - IMP ',CR,LF + DB ' H) - Communications - MEX ',CR,LF + DB ' I) - Communications - Other',CR,LF + DB ' J) - RCP/M Software ',CR,LF + DB ' K) - BBS List ',CR,LF + DB ' L) - ZCPR1/2/3 ',CR,LF +     DB ' M) - Pascal Utility/Source ',CR,LF + DB ' N) - dBase Utility/Source ',CR,LF + DB ' O) - Basic Utility/Source ',CR,LF + DB ' P) - ''Other'' Language ',CR,LF + DB ' Q) - EPSON Specific ',CR,LF + DB ' R) - ZMD Support ',CR,LF + DB ' S) - IBM Utility ',CR,LF + DB ' T) - IBM Application ',CR,LF + DB ' U) - IBM Game ',CR,LF + DB ' V) - IBM Communications ',CR,LF + DB ' W) - Mixed Batch/Misc ',CR,LF + DB ' X) - <<<< Not Defined! >>>>',CR,LF + DB ' Y) - <<<< Not Defined! >>>>',CR,LF + DB ' Z) - <<<< Not Defined! >>>>',CR,LF + DB 0 ; leave the table terminator alone. + + +     ------------------------------------------------------------ +     NOTEº  Makå surå yoõ leavå alì thå categorieó abovå EXACTLÙ +     3±  byteó lonç (2¹ byteó oæ texô pluó thå CR,LÆ equaló  31© +     oò yoõ wilì havå problemó witè thå doublå columî  formattinç +     routines. +     ------------------------------------------------------------ +.pa Š +     D.1 Upload Routing Table + +     Iæ  yoõ decideä tï enablå uploaä routinç tï  multiplå  drivå +     users¬ yoõ wilì havå tï seô thå followinç tablå foò youò owî +     requirements®  Ediô  alì areaó aô TYPTBÌ uð tï anä includinç +     youò  MAXTYР setinç  tï matcè thå messagå  texô  iî  KNDTBÌ +     above® Notå thaô PRIVATÅ uploadó maù bå senô tï á differenô +     drivå  aó  welì aó á differenô useò  area®  Eacè  entrù  ió +     expresseä aó 'drivå letter',useò area® Simplù seô MAXTYÐ tï +     thå  highesô letteò choicå supported®  (Dï NOÔ commenô  ouô +     anù oæ theså storagå bytes). + + _________ + NOTE: / A \ <--- Corresponds to category 'A' + 'A',1,'B',15, + \ / \ / + Normal upload --+ | + Private upload -------+ + + + TYPTBL: + _________ _________ _________ _________ + / A \ / B \ / C \ / D \ + DB 'A',1,'B',15, 'A',2,'B',15, 'B',2,'B',15, 'B',4,'B',15 + _________ _________ + / E \ / Z \ + DB 'B',3,'B',15, ... thru ... 'B',7,'C',12 + + + \ No newline at end of file diff --git a/Source/Apps/ZMD/zmd.z80 b/Source/Apps/ZMD/zmd.z80 new file mode 100644 index 00000000..c47274a7 --- /dev/null +++ b/Source/Apps/ZMD/zmd.z80 @@ -0,0 +1,4858 @@ +; + + TITLE ZMD.Z80 - 09/29/88 - Z80 RCP/M File Transfer Program +; Copyrighted (c) 1987, 1988 +; Robert W. Kramer III + + PAGE +;- -; +; Update History ; +; ; +; Date Release Comments ; +; -------- ------- ---------------------------------------------- ; +; ; +; 09/29/88 v1.50 - If an LBR/ARK/ARC member extraction is being ; +; made, a check at OPNOK5: determines if (FCB+9) ; +; is an 'L' for a .LBR member extraction. If so, ; +; ZMD informs the receiver that the file is ready ; +; for downloading. If (FCB+9) is other than an ; +; 'L' an ARC/ARK member extraction is assumed and ; +; the receiver is told to name the received file ; +; accordingly. Extractions on LBR/ARC/ARK files ; +; with the high bit set in (FCB+9) triggers this ; +; test unreliable. The simple fix has been made, ; +; which strips the parity bit of (FCB+9) before ; +; anylizing it. ; +; - Fixed a problem that caused some systems to ; +; ignore the first character typed in some ZMD ; +; utilities. The modification was made at the ; +; TYPE: routine in ZMDSUBS. Instead of using ; +; BDOS function 2 to display a character, we are ; +; now using BDOS call 6. ; +; - Fixed CONSTAT: routine in ZMDSUBS to properly ; +; react to cancel requests from Sysop. ; +; - Modified all low level I/O routines to preserve ; +; HL, DE, and BC registers. Whether you're using ; +; BYE's extended BDOS calls or have a custom I/O ; +; overlay installed in ZMD, those registers are ; +; will return intact. This allows those writing ; +; overlays to not have to worry about register ; +; integrity. ; +; - Single file Receive now defaults to 1k blocks. ; +; - Library extractions now default to 1k blocks. ; +; - Fixed bugs in the handshaking that have been ; +; around since 1k Batch was implemented on CPM ; +; systems. This required several modifications ; +; to the SNDFIL routines. All of them were ; +; modified specifically for the Batch routines, ; +; however, other Send routines were directly ; +; effected by them. ; +; ; +; 1. ZMD does not use the BDOS call 35 to get ; +; the file size since this information is ; +; supplied in the directory entry for each ; +; extent retreived during filename lookup. ; +; ; +; 2. We no longer open a file to send until ; +; the receiver has ACKnowledged header 0 ; +; *AND* sent his invitation for CRC ('C'). ; +; Previously, opening the file immediately ; +; after the header 0 ACK caused the 'C' to ; +; be missed during the time the sender is ; +; opening the file and filling his transmit ; +; buffer. ; +; ; +; 3. I personally feel there is no need to ; +; purge the line of incoming characters at ; +; anytime during a batch session. All of the; +; occurences of CALL CATCH have been removed; +; from the Send batch routines with the ; +; exception of the GTACK routine, in which ; +; case a call to CATCH is only made if the ; +; current byte received is other than an ; +; ACK, NAK, 7Bh, FBh, or a CANCEL. ; +; ; +; I do not have the resources to change all ; +; the other CP/M file transfer utilities ; +; floating around, so you may still notice ; +; some minor delays between files in BATCH ; +; protocol. Since most of the problem was ; +; in the Send routines, you'll not notice ; +; lengthy delays when using ZMD to send. ; +; Delays when using ZMD to receive will be ; +; dependant upon what software the sender ; +; is using. (IBM comm programs don't seem ; +; to have this problem, however all CP/M ; +; comm programs will have these irritating ; +; delays - except ZMD). ; +; ; +; - Added code to initialize KDRV with current ; +; drive at program startup. CPM3 systems were ; +; experiencing problems with BDOS 46. ; +; - CPM3 BDOS call 46 returns the current disk space; +; free in the first 3 bytes of the currently set ; +; DMA. This was being done during a check for ; +; the batch intention and destroying the current ; +; command line buffer (at 80h where DMA address is; +; usually set). CPM3 systems should now have no ; +; problems with ZMD. ; +; - Fixed bug to allow 255 files to be transferred ; +; when descriptions are disabled (either by the ; +; ZINSTL program or by using the 'RW' option). ; +; - Added CKWILD routine to prevent commands such ; +; as 'ZMD R *.*' enter '*.*' into the directory. ; +; Using any wildcards in the filename or extent ; +; of the receive filename will trigger YMODEM 1k ; +; Batch. ; +; - Log file routines were writing some filename ; +; attributes to the log file. Fixed at PUTLOG: ; +; - Modified ZMDSUBS.REL and send/send Batch code ; +; to take care of incorrect drive display problem ; +; for local terminal. Created new subroutine to ; +; poke the current binary 'BDOS' drive/user area ; +; into (DUU) and (DUD) in both SNDFIL: and SBTCH: ; +; routines. Both DUD and DUU have been moved to ; +; the ZMDSUBS file for global use. ; +; - Fixed EDATE routines for LOG and FOR files. ; +; Previously, the EDATE setting had no effect on ; +; the ZMD.Z80 program. ; +; - Fixed message upload routine at RCVFL to check ; +; for access restrictions. If ACCESS is set to ; +; NO, the message upload will be accepted, else ; +; bit 3 of AFBYTE will be anylized. ; +; ; +; 03/18/88 v1.49 - No change(s) made to this file ; +; 03/13/88 v1.48 - Had a small problem with TPA fix which has been ; +; corrected. CHKTPA was calculating the total ; +; number of bytes available for DBUF, but wasn't ; +; clearing register L (forcing an even amount of ; +; sectors before initializing OUTSIZ buffer limit ; +; comparison word). This may have introduced ; +; minimal garbage to your FOR file if your FOR ; +; file is large enough to fill available TPA with ; +; ZMD, ZFORS or to the log file if running ZMDEL. ; +; - Rewrote OUTCHR routine in ZMDSUBS. ; +; - Redefined buffer table at end of programs. STACK; +; and filename buffers now EQUated with offsets ; +; from the last switch/toggle in program instead ; +; of with DS directive. ; +; - Some systems which do NOT have an interrupt ; +; driven keyboard may have noticed problems when ; +; an invalid key was entered in the ZNEWP, ZFORP ; +; and ZMDEL programs. In ZNEWP and ZFORP, if a ; +; CR was entered to pause the output, output was ; +; limited to one line at a time per key pressed. ; +; If an invalid key was hit, output would have ; +; remained in a paused state until one of the ; +; abort keys were pressed. This was difficult to ; +; find since my keyboard is interrupt driven and ; +; I could not duplicate the problem on my own ; +; system. ; +; - Fixed a problem in the MODE parsing routines ; +; that caused ZMD to default to 128 byte blocks ; +; in single file SEND mode. Now defaults to 1k ; +; YMODEM. ; +; 02/25/88 v1.47 - Fixed a problem that caused only partial display; +; of the help guide if an invalid command such as ; +; ZMD RPC with no filename was entered. ; +; - Repaired routine that loads access flags byte ; +; into AFBYTE. If ACCESS was disabled and BYE ; +; was running, a check for a modem overlay was ; +; being made that caused a system hang up. v1.46 ; +; was the only version with this problem. ; +; - And then there were TPA problems. Past versions ; +; of ZMD required at least 48k TPA to run. If ; +; your system has less, and descriptions enabled, ; +; you most likely had a system hang up during the ; +; FOR file read/write operations. This version ; +; will run on any system with as little as 24k of ; +; available TPA. Maximum number of uploads is ; +; automatically calculated according to your TPA ; +; limitations. If upload descriptions have been ; +; disabled (either during installation or during ; +; program execution with the RW or RP options), ; +; as many as 255 files may be uploaded. ; +; - Fixed time out error in CRC receive routine. ; +; Register B contains the number of seconds to ; +; wait for a character on entry to RECV and some- ; +; how I inadvertanly removed the line that loads ; +; this value on entry. (Label RCVCRC2) ; +; - Fixed description routine so that the caller ; +; will be asked for the category of each uploaded ; +; file if ASKAREA is disabled. Problem caused ZMD ; +; to ask only 1 time for the file descriptor of ; +; uploaded files - no matter how many of them ; +; there were. ; +; 01/27/88 v1.46 - Fixed BYE time routines. Now displays correct ; +; time on system when exiting. ; +; - Fixed SUBS file so that discrepency check will ; +; not turn off CLOCK, and DSTAMP if MODE is set ; +; to 255. (So ZFORS and ZFORP can react to the ; +; clock related features without BYE running). ; +; - Added prompt to tell remote when ZMD is waiting ; +; for him to come back to terminal mode. Gets ; +; redisplayed every 3 seconds for a total of ; +; approximately 30 seconds before continuing on ; +; automatically. ; +; - Added code to SUBS file that determines if we ; +; are in private mode or not. If so, only private ; +; drive/user information is displayed when asking ; +; for file descriptors/categories choice. If in ; +; regular mode or in SPACE routines only the ; +; regular drive/user is shown. Fix is for upload ; +; routing routines. ; +; - Repaired message file exit routine. Command ; +; line wasn't being properly built. ; +; - Fixed so that batch mode is not valid with the ; +; RM option. (Slipped by during a rewrite of the ; +; command tail parsing routines). ; +; 01/17/88 v1.45 - First public release ; +; 01/14/88 v1.43 - Removed MBYE/BYE3 specific switches in ZMDHDR. ; +; Added new switch CLOCK. Removed Extended BDOS ; +; calls from time and date routines. Rewrote for ; +; 100% compatibility with BYE5/MBYE/BYE3 remote ; +; console programs. RTC switch can be set for ; +; user defined time and date routines at RTCTIM ; +; in ZMDHDR.Z80 ; +; - Fixed an error in send routines that allowed ; +; FCB+9-FCB+12 to remain initialized to nulls, if ; +; file requested had less than 3 characters in ; +; the filename extent. ; +; 12/29/87 v1.41 - Removed and rewrote description routines to be ; +; universal with ZMD, ZFORS and ZINSTL programs. ; +; Routine is now requested from ZMDSUBS.REL ; +; 12/23/87 v1.40 - Fixed numerous trivial bugs. ; +; - Rewrote the help and time/date routines. ; +; - Added code to determine if BIOS local console ; +; output address has been included in the modem ; +; overlay and if not calculate it according to ; +; standard CP/M specifications and store it for ; +; program use. ; +; 12/13/87 v1.39 - Rewrote common subroutines for inclusion in ; +; ZMDSUBS.REL subroutines file. ; +; 12/07/87 v1.38 - Modified to support drive/user area requests ; +; in SEND BATCH mode. Drive/user restrictions ; +; remain enforced. ; +; 10/02/87 v1.37 - Wrote code for automatic host disk block size ; +; detection. All file sizes (and total file ; +; sizes) are rounded to reflect this block size. ; +; 08/29/87 V1.36 - Rewrote all time routines. Moved RTC reader ; +; code insert address to ZMDHDR. Moved all date ; +; and time routines together to be updated all ; +; in same pass. Values stored at end of program ; +; to free up registers and retain values for any ; +; subsequent use. ; +; 08/14/87 v1.35 - Removed access restriction routines and rewrote ; +; completely. Access switches now bit mapped. ; +; 07/24/87 v1.34 - Updated to detect and process batch filename ; +; requests automatically. (AUTO-BATCH). ; +; - Removed entire SBTCH routines and threw away. ; +; Rewrote from scratch to enhance speed, ; +; efficiency, readability, and user friendliness. ; +; ZMD is capable of locating multiple filenames ; +; on multiple drive user areas with full drive ; +; and user area, filename, and time restrictions ; +; in force in 5 seconds (all other CP/M file ; +; transfer programs take 7 minutes or more) to ; +; find maximum 255 filenames. ; +; - Complete rewrites of: command line parsing, ; +; exit, abort, credit and descriptions routines. ; +; 06/18/87 v1.30 - Removed conditional assembly and modified to ; +; support .COM file installation/reconfiguration ; +; without reassembling. ; +; 06/17/87 v1.29 - Converted entire program to Microsoft MACRO-80 ; +; language. Programs no longer compatible with ; +; 8080 microcomputers. ; +;- -; + +;-------------------------------------------------------------------------; +; EXTERNAL Declarations: | +;------------------------------------------------------------------------- +; + + EXTRN BCDBIN,BLKSIZ,BUFSTR,BYECHK,CATCH,CHARLN,CKDIR,CLEARIT + EXTRN CMDBUF,CNREC,CONIN,CONONL,CONSTAT,CPDEHL,CPM3,CRCCHK + EXTRN CRCVAL,DATDEC,DATMSG,DBUF,DECOUT,DELAY,DIVREC,DSCFLG + EXTRN DSKSAV,DVHLDE,ERXIT,EXIT,FILCNT,FILTM1,FINCRC,FUNCHK + EXTRN GETDSC,GETKIND,GETOFF,GETSPD,GTCURDU,HEXO,ILPRT,ILPRTB + EXTRN ILPRTL,INCRNO,INITFCB,INITFLG,INITIT,INPUT,KDRV,KIND + EXTRN KSHOW,KTIM,LBRARC,LCASE,LOGBUF,LOW41K,MATCH,MCHFTYP + EXTRN MEMFCB,MODE,MOVFCB,MSGFLG,NAMBUF,NOARK,OLDDRV + EXTRN OLDUSR,PGSIZE,PRINTV,PRIVATE,RDARC,RDCOUNT,RECAR1 + EXTRN RECARE,RECDR1,RECDRX,RECTBL,RECV,RENFCB,RENTYP,RSDMA + EXTRN RSTLCK,SEND,SENDBEL,SETLCK,SHOCAT,SHONM,SHONM3,SHOSPD + EXTRN SNDABT,SPCDRV,STACK,STDMA,STORTM,TYPE,UCASE,USRSAV + EXTRN WAIT1,WHLCHK,XTIM,NEWNAM,SHONM4,NOROOM,BYEBDOS,BATCH + EXTRN FILIMT,PUPFLG,CHKTPA + +; +;-------------------------------------------------------------------------; +; PUBLIC Declarations: | +;-------------------------------------------------------------------------; +; + + PUBLIC KTABLE,XTABLE,KECTBL,DONE,EOTFLG,ABORT,ABORTX,HELP + PUBLIC KFLG,FCBBUF,HDRADR,RCNT,RECDNO,SAVEHL,RCDCNT,TIME + +; +;-------------------------------------------------------------------------; +; Program Starts Here | +;-------------------------------------------------------------------------; + + + .Z80 + ASEG + ORG 100H ; Program starts + JP BEGIN ; Jump around configuration table + INCLUDE ZMDHDR.Z80 ; Include the ZMD header overlay + .REQUEST ZMDSUBS ; Include the ZMD subroutines + +; +; +; Save current CP/M stack address +; +BEGIN: LD (STACK),SP ; Save current CP/M stack address + ;LD SP,STACK ; Initialize new one for ZMD + LD SP,0C000H ; [WBW] For HBIOS Fastpath + +; +; Save current drive and user area for later +; + LD A,255 + CALL RECAR1 ; Get current user + LD (OLDUSR),A ; Save current user + LD C,CURDRV ; Current drive + CALL BDOS + LD (OLDDRV),A ; Save current drive + ADD A,'A' ; Make it ASCII + LD (KDRV),A ; And store as default drive for DPB info + +; +; Display signon message and check environmental discrepencies +; + LD HL,ZMDNAM ; Point to this program's name + CALL PRINTV ; Display it and version number + +; +; If running under CPM3 tell rest of program +; + LD C,GETVER ; Get CPM version + CALL BDOS + CP 48 ; Version 3.0? + JR C,$+5 ; No, it's 2.n so skip next + LD (CPM3),A ; Else set CPM3 switch on + +; +; Locate modem I/O routines +; + CALL BYECHK ; BYE extended BDOS valid? + CP 5 + JR NZ,BEGIN0 ; No, check for I/O overlay + LD A,255 + LD (BYEBDOS),A ; Enable BYE extended BDOS for modem I/O + JR BEGIN1 + +BEGIN0: LD A,(MDINP+2) ; Check for MDINP address + OR A ; Anything there? + JP Z,NOIO ; No overlay either, bitch then exit + LD (INITFLG),A ; Tell exit routine to 'UNINIT' + CALL MINIT ; Initialize system routine (if included) + LD (MHZ),A ; [WBW] Save system speed reported by HBIOS + +; +; Get bit mapped access flags byte +; +BEGIN1: LD A,(ACCESS) ; Using access flags byte? + OR A + JR Z,BEGIN3 ; No + + CALL BYECHK ; Check version of BYE + CP 5 ; BYE5? + JR NZ,BEGIN2 ; No + + LD E,255 + LD C,85 ; Get access flags byte + CALL BDOS + LD (AFBYTE),A ; Store it + JR BEGIN3 + +BEGIN2: LD DE,ACBOFF ; Offset to access flags byte + CALL GETOFF ; Get address + LD A,(HL) ; HL points to access flags byte + LD (AFBYTE),A ; Store it + +; +; Set WRTLOC, display time on system +; +BEGIN3: CALL SETLCK ; Set WRTLOC if needed + CALL CATCH ; Gobble up garbage characters from line + CALL TIME ; Get clock values and display time on + +; +;-------------------------------------------------------------------------; +; P a r s e ' M o d e ' f r o m C o m m a n d T a i l | +;-------------------------------------------------------------------------; +; +; Second character in CP/M FCB contains program mode +; + LD HL,FCB+1 + LD A,(HL) ; Get the main option + LD (MODE),A ; Save it for later use + CP 'F' ; Free space? + JP Z,SPACE ; Yes + CP 'A' ; .ARC/.ARK member extraction? + JP Z,CKSND-3 ; Yes + CP 'L' ; .LBR member extraction? + JP Z,CKSND-3 ; Yes + CP 'S' ; Send a file? + JP Z,CKSND ; Yes + CP 'R' ; Receive a file? + JP NZ,HELP ; No, show help guide + +; +; Check additional 'R'eceive mode options +; + INC HL ; Point to next option + LD A,(HL) ; Put in A + CP 'P' ; Receive Private? + JR Z,CKRCV2-3 ; Yes + CP 'W' ; Receive Privileged? (No descriptions) + JR NZ,CKRCV1 ; No + LD A,(PUPOPT) ; Is >0 if allowing privileged uploads + LD (PUPFLG),A ; Sets our flag this way + OR A ; Allowed? + JP Z,HELP ; No, show help guide + JR CKRCV2 ; Else get next option + +CKRCV1: CP 'M' ; Receive message? + JR NZ,CKRCV3 ; No + LD A,(FCB1+1) ; See if a filename was requested + CP ' ' + JP Z,HELP ; No, batch mode not allowed + LD A,(MSGFIL) ; Is >0 if allowing message uploads + LD (MSGFLG),A ; Sets our flag this way + OR A ; Allowed? + JP Z,HELP ; No, show help guide + LD A,'P' ; Else... + LD (PRIVATE),A ; Set the private flag + +CKRCV2: INC HL ; Point to next option + LD A,(HL) ; Put in A + +CKRCV3: CP ' ' ; Anything there? + JP Z,BCHMSG ; No, see if requesting Batch + CP 'B' ; Batch mode? + JP Z,BCHMSG ; Yes + CP 'C' ; Force Checksum? + JP Z,CHKMSG ; Yes + CP 'X' ; Force 128 byte packets? + JP Z,XMDMSG ; Yes + CP 'K' ; Force 1k packets? + JP Z,YMDMSG ; Yes + JP HELP ; Invalid option, show help guide + +; +; Check additional 'S'end mode options +; + LD (LBRARC),A ; Set .LBR/.ARK/.ARC extraction flag + +CKSND: INC HL ; Next option on command line + LD A,(LBRARC) ; Get the member extraction flag + OR A ; Is is set? + JR Z,CKSND0 ; No, we can check for batch + LD A,(HL) ; Get the character + CP ' ' ; Any more options? + JP Z,YMDMSG ; No, and batch not used with extractions + +CKSND0: LD A,(HL) ; Get the character back + CP ' ' ; Any more options? + JR Z,BCHMSG ; No, check for batch intention + CP 'C' ; Force checksums? + JP Z,CHKMSG ; Show protocol + CP 'X' ; Force XMODEM protocol? + JP Z,XMDMSG ; Show protocol + CP 'K' ; Force 1k protocol? + JP Z,YMDMSG ; Show protocol + LD A,(LBRARC) ; Get LBR/ARC extraction flag + OR A ; Enabled? + JP NZ,NOMSG ; Yes, ignore Batch + LD A,(HL) ; Get option back + CP 'B' ; Forcing batch? + JR NZ,CKSND1 ; No + LD A,(PRIVATE) ; Get special download area flag + OR A ; Enabled? + JP NZ,NOMSG ; Yes, don't allow batch + JP BCMSG2 ; Go set batch flag and display mode + +CKSND1: CP 'P' ; Send private? + JP NZ,HELP ; No + LD (PRIVATE),A ; Enable private download + JR CKSND ; Loop for more options + +; +; Display the currently selected (or default) protocol. +; +BCHMSG: LD A,(MODE) ; Get main option again + CP 'R' ; Receiving? + JR NZ,BCMSG1 ; No + LD A,(FCB1+1) ; Was a file requested? + CP ' ' + JR Z,BCMSG2 ; No, in batch receive + + LD HL,FCB1+1 ; Point to secondary FCB + CALL CKWILD ; Check for wildcards + LD A,(BATCH) ; Batch enabled now? + OR A + JR NZ,BCMSG2 ; Yes, report protocol + JR YMDMSG ; Else, single file receive. Default to 1k + +; +; Scan the command line to see if there was any intention of batch and if +; so, set program environment to Ymodem 1k batch protocol. +; +BCMSG1: XOR A ; Clear accumulator + LD (MODE),A ; Gets us back from the SBTCH routines + CALL SBTCH ; Check for batch intention + LD A,'S' + LD (MODE),A ; Fix the transfer mode flag + LD A,(BATCH) ; Get the batch mode flag + OR A ; Was it enabled? + JR Z,YMDMSG ; No, sending single file. Default to 1k + +BCMSG2: CALL LOW41K ; 1k packets allowed? + JP C,TOOSLOW ; No, can't use batch + LD A,1 + LD (BATCH),A ; Enable batch + CALL ILPRTB + DB '1k Batch',0 + JR MSGEND + +YMDMSG: CALL LOW41K ; 1k packets allowed? + JR C,NOMSG ; No + LD (KFLG),A ; Enable 1k + CALL ILPRTB + DB '1k',0 + JR MSGEND + +XMDMSG: XOR A ; Clear accumulator + LD (KFLG),A ; Disable 1k blocks + CALL ILPRTB + DB '128 byte CRC',0 + JR MSGEND + +CHKMSG: XOR A ; Clear accumulator + LD (CRCFLG),A ; Disable CRC + LD (KFLG),A ; Disable 1k blocks (not allowed in Checksum) + CALL ILPRTB + DB '128 byte Checksum',0 + +MSGEND: CALL ILPRTB + DB ' enabled',0 + +NOMSG: CALL CHKTPA ; Calculate TPA limitations + CALL ILPRTB + DB CR,LF,0 + LD A,(MODE) ; Get transfer mode + CP 'R' ; Receiving? + JP Z,RCVFL ; Yes + LD A,'S' + LD (MODE),A ; Else make mode an 'S' (send mode) + +; +;-------------------------------------------------------------------------; +; ----> SNDFIL - S e n d f i l e ( s ) | +;-------------------------------------------------------------------------; +; +; The file specified in the ZMD command line is transferred over the phone +; to another computer with modem using the "S"end option. The data is sent +; 1 record at a time with headers, checksums, and retransmission on errors. +; +SNDFIL: LD A,(BATCH) ; Batch mode requested? + OR A + JP NZ,SBTCH ; Yes, go handle batch mode + +; +; Take care of single file transfer - not in batch +; + CALL LOGDU ; Log into drive and get DPB info + CALL GTCDUD ; Get current binary drive/user in DUU/DUD + CALL CNREC ; Calculate number of records (unless LBRARC) + CALL CATCH ; Clear the decks + CALL OPNFIL ; Open the file and check restrictions + +; +; Loop back here for the start of each BATCH file sent +; +SNDFL1: LD E,60 ; Number of seconds to wait for initial 'NAK' + +SNDFL2: CALL FUNCHK ; Check for function keys + CALL SNDABT ; Local abort? + LD B,1 + CALL RECV ; Wait 1 second for initial NAK + JR C,SNDFL3 ; No character + CP CRC ; CRC request? + JR Z,SNDFL4 ; Yes + CP KSND ; 1k request? + JR Z,SNDFL7 ; Yes + CP NAK ; NAK for checksum? + JR Z,SNDFL8 ; Yes + CP CANCEL ; Cancel? + JP Z,ABORT ; Yes + +SNDFL3: DEC E ; One less second + JP Z,ABORT ; Abort if 0 + JR SNDFL2 ; Else wait some more + +; +; Got a 'C', now wait up to 1 second for 'K' +; +SNDFL4: LD A,(BATCH) ; In batch mode? + OR A + JR NZ,SNDFL7 ; Yes, don't wait for 'K' + LD B,1 + CALL RECV ; Get character from remote + JR C,SNDFL5 ; No character received, so not using 1k + AND 7FH ; Strip high bit + CP '{' + JR Z,SNDFL4 ; Disregard noisy lines + CP KSND ; Requesting 1k? + JR Z,SNDFL7 ; Exit if yes, otherwise set CRC + +; +; Turn on the flag for CRC +; +SNDFL5: LD A,(KFLG) ; KFLG manually set from 'SK'? + OR A + JR NZ,SNDFL7 ; If yes, keep it set + +SNDFL6: XOR A + LD (KFLG),A ; Defaults to 128 character blocks + INC A + LD (CRCFLG),A ; Insures in CRC mode + CALL ILPRTL + DB CR + DB 'CRC',0 + JP SNDFL10 + +; +; Turn on the flag for 1k blocks and insure in CRC mode +; +SNDFL7: CALL LOW41K ; 1k packets allowed? + JP C,SNDFL6 ; No + LD (KFLG),A ; Set the flag for 1k blocks + LD (CRCFLG),A ; Insures in CRC mode + LD A,(BATCH) ; In Ymodem Batch? + OR A + CALL NZ,OPNFIL ; Yes, then open file/check restrictions + CALL ILPRTL + DB CR + DB 'Ymodem',0 + JR SNDFL10 + +; +; Turn on checksum flag, insure sending 128 character blocks +; +SNDFL8: LD A,(BATCH) ; In batch mode now? + OR A + JR NZ,SNDFL9 ; If yes, exit + XOR A + LD (CRCFLG),A ; Make sure in checksum mode + LD (KFLG),A ; Defaults to 128 character blocks + CALL ILPRTL + DB CR + DB 'Checksum',0 + JR SNDFL10 + +SNDFL9: CALL ILPRTL + DB CR + DB '-- Checksum not used in batch' + DB CR,LF,0 + JP SNDFL2 ; If yes, ignore checksum request + +SNDFL10:CALL ILPRTL + DB ' requested ' + DB CR,LF,0 + CALL RDBLOK ; Put up to 16k from file into buffer + CALL SETFLG ; Disable 1k if less than 8 records left + +; +; Loop back here to send the next 1k/128 byte block after a successful trans- +; mission. If using 1k blocks, check the ACK ratio. Check total error count +; vs. records sent, and switch from 1k to 128 byte transmissions if higher. +; +SNDLP: LD A,(KFLG) ; Using 1k blocks? + OR A + JP Z,RDRECD ; If not, skip checking 1k error ratio + LD A,(ERRCNT) ; See if we got any errors last record + CP 4 ; 4 or more? + JR NC,SNDLP1 ; Yes, switch to 128 size + LD A,(ACCERR) ; See if up to minimum errors yet + CP 3 ; Had as many as three errors yet? + JR C,RDRECD ; If not, don't get excited too quickly + + LD HL,(RECDNO) ; Get current record number increment + LD DE,65528 ; Have not successfully sent this 1k yet + ADD HL,DE ; Subtract the current increment, then + LD DE,(ACCERR) ; Number of non-'ACK' errors in HL + CALL DVHLDE ; Get ratio in BC of records/hit + CALL GETSPD ; Get current speed + CP 5 ; 1200 baud? + LD A,70 ; for 1200 bps + JR Z,$+4 ; If 1200, skip next line + LD A,42 ; for 2400 bps + CP C ; Compare with actual ratio + JR C,RDRECD ; Continue if less hits than allowed + +SNDLP1: XOR A ; Clear A + LD (KFLG),A ; Reset system to 128 byte blocks + CALL ILPRTL ; Inform locally + DB ' - YMODEM 1k blocks disabled' + DB CR,LF,0 + +; +; Read a record, refill buffer if empty, update record read +; +RDRECD: LD A,(RECNBF) ; Any records in the buffer? + OR A + JR Z,RDBLOCK ; No, go get some + LD A,(KFLG) ; Using 1k blocks? + OR A + JR Z,RDREC1 ; No, exit + LD A,(RECNBF) ; See how many records in buffer + CP 8 ; 8 or more records? + JR NC,RDREC2 ; Yes, stay in 1k blocks + XOR A + LD (KFLG),A ; Reset the 1k flag for 128 byte + +RDREC1: LD A,(RECNBF) ; Point to number of records in buffer + DEC A ; Decrement it for 128 character blocks + LD (RECNBF),A ; Store it + JP SNDLP2 ; Send it + +RDREC2: SUB 8 ; Subtract 8 records (1k worth) + LD (RECNBF),A ; Store it + JP SNDLP2 ; Send it + +; +; Buffer is empty - read in another block of 16k +; +RDBLOCK:LD A,(EOFLG) ; Get 'EOF' flag + CP 1 ; Is it set? + SCF ; To show 'EOF' + JP Z,SNDLP2 ; Got 'EOF' + CALL RDBLOK ; Read up to 16k into DBUF + JR RDRECD ; Pass record to caller + +; +; Read up to 16k from the disk file into the buffer, ready to send +; +RDBLOK: LD C,0 ; Set number of records in block to 0 + LD DE,DBUF ; Point to disk buffer as destination + +RDBLOK1:PUSH BC + PUSH DE + LD A,(LBRARC) ; Get ARK/ARC/LBR extraction flag + OR A ; Enabled? + JR Z,RDBLOK2 ; No, skip next + LD A,(FCB+9) ; Get filetype byte 1 + AND 7FH ; Strip high bit + CP 'A' ; Is it an ARK/ARC extraction? + CALL Z,RDARC ; Yes, (flags saved at RDARC) + JR Z,RDBLOK3 ; Same flags + +RDBLOK2:CALL STDMA ; Set DMA address + LD C,READ + LD DE,FCB + CALL BDOS + +RDBLOK3:POP DE + POP BC + OR A ; Read ok? + JR NZ,RDBLOK5 ; If not, error or end of file + LD HL,128 ; Add length of one record + ADD HL,DE ; To next buffer + EX DE,HL ; Buffer to 'DE' + INC C ; More records? + LD A,(BUFSIZ) + ADD A,A + ADD A,A + ADD A,A + CP C + JR NZ,RDBLOK1 ; Read more + +; +; Buffer is full or got EOF +; +RDBLOK4:LD (RECNBF),A ; Store record count + LD HL,DBUF ; Get the beginning buffer address + LD (RECPTR),HL ; Save for next record + JP RSDMA ; Reset DMA address to default + +RDBLOK5:DEC A ; 'EOF'? + JR NZ,READERR ; Got 'EOF' + +RDBLOK6:INC A + LD (EOFLG),A ; Set EOF flag + LD A,C + JR RDBLOK4 + +READERR:CALL ILPRTB + DB CR,LF + DB '-- Read Error: ',0 + CALL SHONM3 + JP EXIT + +; +; Now send the next record +; +SNDLP2: JP C,SNDEOF ; Send 'EOF' if done + CALL INCRNO ; Bump record number if sent ok + XOR A ; Initialize error count to zero + LD (ERRCNT),A + +SNDRPT: CALL CKABORT ; Check for remote abort + CALL SNDABT ; Check for local abort + CALL SNDHDR ; Send a header + CALL SNDREC ; Send data record + CALL SNDCHK ; Send CRC or checksum value + CALL GTACK ; Get the 'ACK' + CP ACK ; ACK? + JR NZ,SNDRPT ; No, repeat transmission + LD DE,128 ; For 128 character blocks + LD A,(KFLG) ; See if last block sent was 1k + OR A + JR Z,$+5 ; No, skip next line + LD DE,1024 ; Else set for 1024 character blocks + LD HL,(RECPTR) ; Get the buffer pointer + ADD HL,DE ; Increment for the record just sent + LD (RECPTR),HL ; New buffer address for next block + LD A,(LBRARC) ; Get LBR/ARC/ARK extraction flag + OR A ; Enabled? + JP Z,SNDLP ; No + LD A,(KFLG) ; 1k enabled? + LD DE,65535 ; 128 byte + OR A + JR Z,$+5 + LD DE,65528 ; 1k + LD HL,(RCNT) ; Alter the records-sent count + ADD HL,DE + LD (RCNT),HL ; One less transmission to go + OR A ; 'K' flag set? + CALL NZ,SETFLG ; Yes, see if enough records for 1k packet + LD HL,(RCNT) ; See if anything was actually sent + LD A,H + OR L ; L and H both zero now? + JP NZ,SNDLP ; No, continue + +; +; End of Transmission (Send mode) +; +SNDEOF: LD A,(LOGLDS) ; Counting transfers? + OR A + JR Z,SNDEOF1 ; No + LD A,(PRIVATE) ; Is this a private transfer? + OR A + JR NZ,SNDEOF1 ; Yes, don't increment download count + LD IY,(DNLDS) ; Get Downloads counter address + INC (IY) ; One more download since log in + +SNDEOF1:CALL LOGCALL ; Log transfer if supposed to + CALL EOFSND + CALL ADDTON ; Update BYE's time on byte if supposed to + CALL ALLDON + JP DONE + +; +; See if enough records left to use 1k protocol +; +SETFLG: LD HL,(RCNT) + LD A,H ; Anything in the 'H' register? + OR A + RET NZ ; Yes, enough records for another 1k packet + LD A,L ; Get number of records in 'L' register + CP 8 ; At least 8 yet? + RET NC ; Yes, keep going + XOR A ; Reset the 'K' flag + LD (KFLG),A + RET + +; +; HL points to filename FCB - now search for it wildcards. If any, enable +; BATCH flag and pad with '?' as needed +; +CKWILD: LD B,8 ; Check first 8 bytes + CALL CKWLD1 + LD B,3 ; And check filetype + +CKWLD1: LD A,(HL) ; Get the character + CP '*' ; '*'? + JR NZ,CKWLD2 ; No, check for little wildcards + LD (BATCH),A ; Enable AUTO-BATCH + LD A,'?' ; Fill rest with '?' character + JP INITIT ; Initialize + +CKWLD2: CP '?' ; '?'? + JR NZ,$+5 ; No, don't enable AUTO-BATCH + LD (BATCH),A ; Enable AUTO-BATCH + INC HL ; Point to next character + DJNZ CKWLD1 ; Loop until B=0 + RET + +; +; Get the current drive/user +; +GTCDUD: LD A,0FFH ; Stuffed into E at RECAR1 + CALL RECAR1 ; Get current user area + LD (DUU),A ; Store it + LD C,CURDRV ; Get current drive + CALL BDOS + LD (DUD),A ; Store it + RET + +; +;-------------------------------------------------------------------------; +; S e n d B a t c h | +;-------------------------------------------------------------------------; +; +; Copy original command line buffer to internal work buffer +; +SBTCH: LD A,(FSTFLG) ; If first time through + OR A + JP NZ,SBTCH1 ; If not first time, exit + + LD HL,TBUF ; Source + LD DE,CMDBUF ; Destination + LD BC,128 ; Count + LDIR ; Move + +; +; Locate end of command line and place a ' ' as a delimiter +; + LD HL,CMDBUF ; Point to number of bytes in line + LD B,0 ; Zero high order + LD C,(HL) ; Number of characters in command line + INC HL ; Point to start of line + ADD HL,BC ; Plus number of characters equals end of line + LD (HL),' ' ; Place the delimiter at end of line + INC BC ; Increment character count for delimiter + +; +; Count ambiguous/unambiguous filenames in command line +; + XOR A ; Clear accumulator + LD (FILCNT),A ; Reset the file count + LD (NAMECT),A ; Reset name count (used in parsing routines) + LD HL,CMDBUF+2 ; Point to command tail option + LD A,' ' ; Looking for space/non-space characters + CPIR ; On command option, look for next space + JP PO,SCANDN ; If at end of line, done + CPI ; Find first character of first name + JP PO,SCANDN ; If at end of line, done + JR Z,$-5 ; Eat extra spaces + DEC HL ; CPI is one ahead of us, so back up + LD (BGNMS),HL ; Store address of beginning name + INC HL ; And it was supposed to be, so restore it + +SCANLP: CPIR ; Move to end of current name (next space) + EX AF,AF' ; Save A (match char) & current flags (result) + LD A,(NAMECT) ; Get current name count + INC A ; Bump it one + LD (NAMECT),A ; Put it back + CP 255 ; 255 names? + JR Z,SCANDN ; Yes, that's all we allow + EX AF,AF' ; Restore A and old flags + JP PO,SCANDN ; If at end of line, done + CPI ; Find next non-space character + JP PO,SCANDN ; If at end of line, done + JR Z,$-5 ; Was a space, keep looking + JR SCANLP ; Found next non-space, find next name + +SCANDN: LD A,(NAMECT) ; Get the ambiguous filename count + OR A ; Were there any? + JP Z,HELP ; No, they must need help + CP 1 ; Just 1 name? + JR Z,$+5 ; If only 1, don't force AUTO-BATCH here + LD (BATCH),A ; Else set batch mode flag (for AUTO-BATCH) + LD HL,NAMBUF ; Get start of batch filename buffer + LD (NBSAVE),HL ; Save as address of the first name + +; +; Place a name in work buffer +; +TNLP: LD B,0 ; Initialize character count + LD HL,(BGNMS) ; Source is address of first name + LD DE,FCBBUF+2 ; Destination + +TNLP1: LD A,(HL) ; Get a byte in A + CP ' ' ; A space? + JR Z,TNLP2 ; Yes, done with name + LD (DE),A ; Move character to FCB buffer + INC HL ; Increment pointers + INC DE + INC B ; Bump count of characters in name + JR TNLP1 ; Loop until space + +TNLP2: INC HL ; Point to next character + LD A,(HL) ; Put it in A + CP ' ' ; Is it a ' '? + JR Z,TNLP2 ; Yes, eat extra spaces + LD (BGNMS),HL ; Store address of next name + LD HL,FCBBUF+1 ; # characters in filename + LD (HL),B ; Before name + +; +; Initialize FCB for search routines +; + LD A,0 ; String of all 0's for intitialization + LD HL,FCB ; Destination + LD B,16 ; 16 bytes + CALL INITIT ; Initialize FCB + + LD HL,FCBBUF+1 ; Point to # of bytes in command line + LD D,0 ; Zero high order + LD E,(HL) ; Load DE pair with # bytes + INC HL ; Increment to start of command line + ADD HL,DE ; Point to byte after last character + LD (HL),CR ; Store CR for delimiter + +; +; Check for valid drive/user combination and move filename to FCB +; + LD HL,FCBBUF+2 ; Start of filename + LD DE,DUSAVE ; Isolate possible 'duu:' + LD BC,4 ; Up to 4 bytes + LDIR ; For logging into specified d/u + + LD HL,FCBBUF+1 ; Point to amount of characters in filename + LD B,(HL) ; In B for d/u parsing routines + INC B ; Increment character count for CR terminator + INC HL ; And point to start filename again + LD (SAVEHL),HL ; Initialize 'current' address pointer + + LD A,(MODE) ; Get transfer mode + PUSH AF ; Save it while checking valid d/u + XOR A ; Zero accumulator for new mode + LD (MODE),A ; Save it (keeps us out of trouble in LGDU1:) + CALL LGDU1 ; Check valid d/u and copy filename to FCB + CALL GTCDUD ; Get current binary drive user in DUU/DUD + POP AF ; Get possible previously determined mode + LD (MODE),A ; Restore + LD HL,FCB+1 ; Filename FCB + CALL CKWILD ; Check it for wildcards, enable Batch if any + + LD A,(MODE) ; Get file transfer mode + OR A ; 0=checking batch intention + RET Z ; All done if so + +; +; Now search directory and store first matching filename +; + CALL RSDMA ; Reset to default memory address + LD A,'?' + LD (FCBEXT),A ; Fetch all extents of matching filenames + XOR A + LD (FCBRNO),A ; Clear FCB record number byte + LD DE,FCB ; Use default FCB for search + LD C,SRCHF ; Search for first occurence + CALL BDOS + CP 0FFH ; Anything found? + JP Z,NEXTNM ; No, go get next ambiguous filename + LD HL,(LIST) ; Initialize list pointer parameters + LD (LISTPOS),HL ; Save current position of list + +; +; Calculate offset to matched directory entry +; +FNDENT: AND 3 ; Zero based, two bit index + ADD A,A ; *2 + ADD A,A ; *4 + ADD A,A ; *8 + ADD A,A ; *16 + ADD A,A ; *32 to make position index + LD C,A ; Put in BC + XOR B ; Clear MSB + LD HL,TBUF ; Address of default command line buffer + ADD HL,BC ; And offset to matched directory entry + LD A,(DUD) ; Get drive number + LD (HL),A ; Put in front of name in name buffer + +; +; Check the match for download restrictions +; + PUSH HL ; Save address of matched entry + PUSH HL ; Save another copy + POP IX ; As address of filename to check + CALL RESTRCT ; Check for download restrictions + POP HL ; Get our matched entry address back + JP NZ,DONEXT ; NZ=entry not allowed + +; +; Trap zero length file before adding to list +; + PUSH HL ; Save matched entry address + POP IY ; Get a copy in IY + LD A,(IY+12) ; Get the extent byte + OR A ; Is this the first extent? (#0) + JR NZ,COPYNM ; No, can't be 0 length (at least 16k already) + LD A,(IY+15) ; Get it + OR A ; Any records? + JP Z,DONEXT ; No, zero length, but in batch so no messages + +; +; Copy the name to list +; +COPYNM: LD A,(FSTFLG) ; Displayed the following message yet? + OR A + JR NZ,NAM2LST ; Yes, they alreay know to wait + PUSH HL ; Save matched entry address + CALL ILPRTB + DB CR,LF + DB 'Locating selection(s)...',0 + LD A,1 + LD (FSTFLG),A ; Set so message don't show again + POP HL ; Restore matched entry address + +NAM2LST:LD DE,(LISTPOS) ; Pointer to current load point in list + LD B,12 ; Move drive number and name to list + +NM2LST1:LD A,(HL) ; HL contains address of entry + AND 7FH ; All done with high bits + LD (DE),A ; Move it to list + INC HL ; Increment pointer + INC DE + DJNZ NM2LST1 ; Loop until B equals 0 + LD A,(HL) ; Get the EX byte + LD (DE),A ; Put it in list + INC HL ; Increment to RC byte + INC HL + INC HL + INC DE + LD A,(HL) ; Get it + LD (DE),A ; Put it in list + INC DE ; Point to start of next name in list + LD A,(DUU) + LD (DE),A + INC DE + INC DE + LD (LISTPOS),DE ; Store address of next load point + +; +; Search for next occurance of specified filename +; +DONEXT: LD C,SRCHN ; Search next function code + LD DE,FCB ; Filename specification field + CALL BDOS + CP 0FFH ; See if all through directory yet + JP NZ,FNDENT ; If not, calculate code offset and add 2 list + +; +; Trap conditions of 0 files found +; + LD HL,(LISTPOS) ; Get the end of list address + LD DE,(LIST) ; Get beginning of list address + CALL CPDEHL ; Are they the same? + JP Z,NEXTNM ; Yes, none of the files found were allowed + +; +; Prepare associated sort parameters +; + LD HL,(LIST) ; Adjust I and J pointers for initial sort + LD (LISTI),HL ; Beginning of list + LD DE,ITEMSZ ; Get offset to next name + ADD HL,DE ; Add the offset + LD (LISTJ),HL ; Into J variable + +; +; Don't need a sort if only 1 file extent found +; + LD HL,(LIST) ; Was there more than one entry found? + LD BC,ITEMSZ + ADD HL,BC + EX DE,HL + LD HL,(LISTPOS) ; Next load name of list is start of buffer + LD (LISTEND),HL ; Set list end marker + CALL CPDEHL ; Compare DE address with HL address + JP Z,MINNN ; If same, no sort needed + +; +; Sort the list by disk, filename, and EX byte. +; +SORT: LD HL,(LISTI) ; Compare entries I and J + LD DE,(LISTJ) + LD B,13 ; Number of bytes to compare + CALL MATCH ; + JR NC,SORT1 ; Swap entries if J is larger than I + LD HL,(LISTI) ; Get our original pointers back + LD DE,(LISTJ) + LD B,ITEMSZ ; Counter for number of bytes to swap + +SWAP: LD C,(HL) ; Get character from string 1 + LD A,(DE) ; And one from other string + LD (HL),A ; Second into first + LD A,C ; First into second + LD (DE),A + INC HL ; Bump swap pointers + INC DE + DJNZ SWAP ; Loop until B=0 + +SORT1: LD HL,(LISTJ) ; Increment J pointer + LD DE,ITEMSZ ; By the amount of items per entry + ADD HL,DE + LD (LISTJ),HL + LD DE,(LISTEND) ; Get the address of the end of list + CALL CPDEHL ; DE and HL the same? + JR NZ,SORT ; No, so more J loop + + LD HL,(LISTI) ; Get the I pointer + LD DE,ITEMSZ ; Get offset to next name + ADD HL,DE ; Add + LD (LISTI),HL + ADD HL,DE ; Add offset to next name + LD (LISTJ),HL ; Start J loop over again + LD DE,(LISTEND) ; Get the address of the end of list + CALL CPDEHL ; DE and HL the same? + JR NZ,SORT ; No, must be more I loop to go + +; +; List minimization loop +; + LD HL,(LIST) ; Point to the beginning of our list + LD (LISTI),HL ; Initialize current name pointer + LD DE,ITEMSZ ; Get offset to next name + ADD HL,DE ; Add it to current name address + LD (LISTJ),HL ; Store as next name + +MINCL: LD DE,(LISTEND) ; End of list address + LD HL,(LISTJ) ; Next name address + CALL CPDEHL ; Are they the same? + JR Z,MINNN ; Yes, go set kbytes on last name (End of list) + + LD DE,(LISTJ) ; Next name address + LD HL,(LISTI) ; Current name address + LD B,12 ; # of bytes to check + CALL MATCH ; Are they the same? + JR NZ,MINNN ; No, go set kbytes on last extent (Next name) + +; +; Increment next name pointer and get parameter bytes +; + LD HL,(LISTJ) ; Fetch EX and RC from next name + LD DE,ITEMSZ ; Offset to next name + ADD HL,DE ; Add it + LD (LISTJ),HL ; Save bumped J value + + DEC HL ; Point to parameter bytes of previous name + DEC HL + DEC HL + LD B,(HL) ; Save the RC byte + DEC HL + LD C,(HL) ; Save the EX number + + LD HL,(LISTI) ; Point at current name + ADD HL,DE ; Point at current name info bytes + DEC HL + DEC HL + DEC HL + LD D,(HL) ; Fetch that RC byte + DEC HL + LD E,(HL) ; Fetch current EX byte + + LD A,E ; Check if new EXtent is bigger than last + CP C + JR NC,MINCL ; Skip using size of a less or equal EX + LD (HL),C ; Put new sizes into the location + INC HL + LD (HL),B ; New RC byte too + JR MINCL ; Continue handling as current file + +; +; File size computation loop +; +MINNN: LD HL,(LISTI) ; Point to name to convert records + LD DE,ITEMSZ-4 ; Index to largest extent number + ADD HL,DE ; Add the offset + LD B,(HL) ; Get the extent number for a loop counter + INC HL ; Bump to the last extent RC byte + PUSH HL ; Save pointer to list parameters + LD HL,0 ; Zero extent total record count + LD DE,128 ; Set size of one extent + +; +; Calculate total number of 128 byte records +; +MINEL: LD A,B ; Get the number of extents left + OR A ; Any more? + JR Z,MINELD ; No, done with 128 multiply + ADD HL,DE ; Else add another 128 to HL + DEC B ; 1 less extent left + JR MINEL ; Loop until no more + +MINELD: EX DE,HL ; Total extent size to DE + POP HL ; Get back RC byte pointer + LD B,0 + LD C,(HL) ; Get final extent size to BC + EX DE,HL ; Add remainder to total records in DE + ADD HL,BC + LD B,H ; Move total record count to BC + LD C,L + LD HL,(TOTREC) ; Get current total records + ADD HL,BC ; Add records of this file + LD (TOTREC),HL ; And save it for later display + EX DE,HL ; Get table entry pointer back in HL + CALL ROUNDK ; Get disk space needed for file DE + LD (HL),D ; Put kilobyte count in table + DEC HL + LD (HL),E + LD A,(FILCNT) ; Bump the file count + CP 255 ; 255 file names yet? + JR Z,MINN0 ; Yes, that's all we allow + INC A ; Else bump it one + LD (FILCNT),A ; Store it + LD HL,(FILEK) ; Get current total file kilobytes + ADD HL,DE ; Add in the current file's kilobyte size + LD (FILEK),HL ; And store it + LD HL,(LISTI) ; Source + LD DE,(NBSAVE) ; Destination + LD BC,16 ; 16 byte count + LDIR ; Move filename to names buffer + LD (NBSAVE),DE ; And store address to put next filename + +MINN0: LD DE,(LISTJ) ; Done with all names + LD HL,(LISTEND) ; Check if at end of list + CALL CPDEHL + JR Z,NEXTNM ; Get next ambiguous filename, if finished + LD HL,(LISTI) ; Point to LISTI value + LD DE,ITEMSZ ; Point to next position + ADD HL,DE + LD (LISTI),HL ; Set new working LISTI + LD DE,(LISTI) + LD HL,(LISTJ) ; Next name position to copy from + CALL CPDEHL ; See if pointers only one apart + JR Z,MINN1 ; If so, don't cover up one name + LD BC,ITEMSZ + LDIR ; Move that name up there + JR MINN2 + +MINN1: LD DE,ITEMSZ ; No open slot, so just move LISTJ up one slot + ADD HL,DE + +MINN2: LD (LISTJ),HL + JP MINCL ; Go to MIN NAME start of loop + +NEXTNM: LD A,(OLDDRV) + CALL RECDRX ; Restore default drive + LD A,(OLDUSR) + CALL RECAR1 ; Restore default user + LD A,(NAMECT) ; Get number of names found + DEC A ; Decrement it + LD (NAMECT),A ; Put it back + JP NZ,TNLP ; Loop until zero + LD (FSTFLG),A ; Done with first time flag, reinitialize it + LD HL,NAMBUF ; Save start of buffer + LD (NBSAVE),HL + + LD A,(FILCNT) ; Get total files + LD (SHOCNT),A + OR A ; Were there any? + JR NZ,NXTNM1 ; Yes + CALL ILPRT + DB CR,LF,0 + JP NOFILE ; No + +NXTNM1: CALL ILPRTB + DB CR + DB 'Number of files found > ',0 + LD A,(SHOCNT) + LD L,A + LD H,0 + CALL DECOUT ; Show number of files found + LD HL,(TOTREC) + LD (RCNT),HL + LD A,1 + LD (SBSHOW),A ; Get's us back early + CALL OPNOK2 ; Go show total file stats + XOR A + LD (SBSHOW),A + +SBTCH1: LD A,(FILCNT) ; Get the count of files to send + OR A ; Is there any? + JP Z,SNDFN ; No + LD A,(FSTFLG) ; Past first batch file yet? + LD (CONONL),A ; Toggle to local display only + OR A + CALL NZ,CLEARIT ; Else show local + CALL ILPRT + DB CR,LF,LF + DB 'Total transfer time > ',0 + CALL GETSPD ; Get speed indicator + CP 1 ; Are we at 300 bps? + LD HL,XTABLE ; This gives us 128-byte transfer time + JR Z,$+5 ; Yes, skip next line, show 128-byte time + LD HL,KTABLE ; This gives us 1k transfer time + LD D,0 + LD E,A ; Set up for table access + ADD HL,DE ; Index to proper factor + ADD HL,DE + LD E,(HL) + INC HL + LD D,(HL) + LD HL,(TOTREC) ; Get number of records + CALL FILTM1 + CALL XFRTIM ; Check for time restrictions + CALL ILPRT + DB CR,LF,0 + LD A,(FSTFLG) + OR A + CALL Z,DLRDY + +; +; Send the batch filename to remote +; +SNDFN: CALL CKABORT ; Check for remote abort + LD HL,FCB + CALL INITFCB ; Initialize FCB + XOR A + LD (ERRCNT),A ; Reset the error count + INC A + LD (CONONL),A ; Set to local display only + LD A,(FILCNT) ; Get file count + OR A + JP Z,CCHECK ; No more files, exit + LD A,1 + LD (CRCFLG),A ; Make sure in CRC mode + + LD A,0FFH + CALL RECAR1 ; Get the current user area + LD B,A ; Save current user area + PUSH BC ; On stack + LD HL,(NBSAVE) ; Get start of filename + LD BC,14 ; Offset to user area + ADD HL,BC ; Point to binary user area + LD A,(HL) ; Get it + POP BC ; Get current user area back + CP B ; Same? + CALL NZ,RECAR1 ; No, but it is now + CALL SPCDRV ; Get disk parameter block info + + LD HL,(NBSAVE) ; Get address of next batch filename + INC (HL) ; Escape default situation in FCB drive byte + LD DE,FCB ; Where to put it + LD BC,12 ; 12 bytes for drive and filename + LDIR ; Move + LD BC,4 ; Next filename is 4 bytes away + ADD HL,BC ; Add offset + LD (NBSAVE),HL ; Store address for next filename + LD HL,(RECPTR) ; Where to load the 0 block + EX DE,HL ; Put into DE + LD HL,FCB+1 ; Get the start of the filename in HL + LD B,8 + +SZMD1: LD A,(HL) + AND 7FH ; Strip any high bit set + OR A + JR Z,SZMD6 ; Null pathname + CP ' ' + JR Z,SZMD3 + +SZMD2: CALL LCASE ; Put file name in lower case for UNIX + LD (DE),A + INC HL + INC DE + DJNZ SZMD1 + JR SZMD4 + +SZMD3: INC HL ; Skip over spaces if short name + DJNZ SZMD3 + +SZMD4: LD A,(HL) + CP ' ' + JR Z,SZMD6 ; Missing file type field + LD A,'.' ; Send name-type seperator + LD (DE),A + INC DE + LD B,3 + +SZMD5: LD A,(HL) + AND 7FH ; Strip any high bit set + CP ' ' + JR Z,SZMD6 + CALL LCASE ; Put in lower case for UNIX + LD (DE),A + INC HL + INC DE + DJNZ SZMD5 + +SZMD6: EX DE,HL ; Get the address back to HL + LD (HL),0 + INC HL + LD (HDRADR),HL + CALL CNREC ; Get number of records in this file + CALL CHARLN ; Include the ASCII character length + +SZMD7: LD (HL),0 ; Fill rest with zeroes + INC L ; Pad to end of block with binary 0 + JR NZ,SZMD7 + LD HL,(RCNT) + LD (BUFSTR),HL ; Store the file length at end of block + XOR A ; Make sure the header starts with Zero + LD (RCDCNT),A + +; +; Wait for 'C' from remote to indicate he is ready +; +CCHECK: LD E,60 ; Wait up to 60 seconds to abort + +CCHECK1:CALL CKABORT ; Manually requesting an abort? + LD B,3 + CALL RECV ; Wait up to 5 seconds for a character + JR C,CCHECK2 ; No character, decrement counter + CP CANCEL ; If they sent a CTL-X, abort now + CALL Z,CKCAN + CP CRC ; If they sent a CRC, go to work + JR Z,SZMD8 + JR CCHECK ; None of these, wait some more + +CCHECK2:DEC E ; One less to go + JR NZ,CCHECK1 + JP ACKMSG ; Abort if timed out and no character + +SZMD8: LD A,(FILCNT) ; Any files to send? + OR A + JR NZ,SZMD9 ; Yes, continue + XOR A ; Reset the pointers + LD (ACKCHK),A ; Reset flag for normal GTACK use + LD (RCDCNT),A ; Reset the record counter + LD (KFLG),A ; Show in 128 size now + LD HL,(RECPTR) + LD (HL),A ; Reset record pointer + LD A,SOH ; Send a start of header + CALL SEND + CALL SNDHNM ; This header is a zero count + CALL SNDREC ; Send an empty record + CALL SNDCRC ; Send the CRC for the empty record + LD A,(GOTONE) ; Did we actually send at least one? + OR A + JP Z,ABORT ; If not, don't act like we did + CALL EOFSND ; No more files so send EOT to finish + CALL XFRDON + JP EXIT + +; +; Now send the 128 byte filename record +; +SZMD9: DEC A ; Decrement file count for this one + LD (FILCNT),A ; Store it + +SZMD10: XOR A + LD (KFLG),A + LD A,SOH ; Send SOH + LD (ACKCHK),A + CALL SEND ; Send SOH character to the modem + CALL SNDHNM ; Send header (record number, inverse) + CALL SNDREC ; Send a 128 byte record + CALL SNDCRC ; Send a two byte CRC + CALL GTACK + CP ACK + JR NZ,SZMD10 ; Not an ACK, send it again + XOR A + LD (ACKCHK),A ; Reset flag for normal GTACK use + CALL LOW41K ; Check speed being used + JR C,$+7 ; Don't allow 1k blocks if less than MINKSPD + LD A,1 + LD (KFLG),A ; Change to 1k for normal file xfer + XOR A ; Clear A + LD (ERRCNT),A ; Start fresh for the main file + JP SNDFL1 + +; +; Send EOT. Get Acknowledgement from remote. Try up to 4 times then abort. +; +EOFSND: LD A,EOT ; Send an 'EOT' + CALL SEND + LD A,(CHKEOT) ; Did not get an ACK, try again + INC A + LD (CHKEOT),A ; Limit number of retries to 4 + CP 4 ; (to prevent possible 'lock-up') + RET NC ; Quit if already sent 4 or more + CALL GTACK ; Get the ACK + CP ACK + JR NZ,EOFSND ; Resend if no ACK + RET + +ALLDON: LD A,(BATCH) ; In batch mode? + OR A + RET NZ ; If yes, ignore message + CALL ILPRT ; (Want to keep this a separate message) + DB CR,LF,0 + +XFRDON: CALL ILPRTL + DB CR,LF + DB '-- Transfer completed' + DB CR,LF,0 + RET + +; +;-------------------------------------------------------------------------; +; ----> RCVFL - R e c e i v e f i l e ( s ) | +;-------------------------------------------------------------------------; +; +; The filename specified in ZMD command line is transferred over the phone +; from the user's computer to the RCPM system via modem using the 'R' +; (receive) option. The data is sent one record at a time, with headers +; and checksums and retransmissions on errors. 'RM' option is disallowed +; at time of command tail parsing at beginning of program (MSGFLG cannot +; be set unless MSGFIL is enabled). +; +RCVFL: LD A,(MSGFLG) ; Get message file upload flag + OR A ; Enabled? + JR Z,RCVF2 ; No, skip the rest + CALL WHLCHK ; Yes, WHEEL byte set? + JR NZ,RCVF1 ; Yes, turn it off and skip access check + + LD A,(ACCESS) ; Checking access restrictions? + OR A + JR Z,RCVF3 ; No + LD A,(AFBYTE) ; Get access flags byte + AND 8 ; Test for write access (bit 3) + JP Z,NOACC ; Not allowed to write messages + +RCVF1: XOR A ; Clear A + LD HL,(WHEEL) ; Point to WHEEL byte + LD (HL),A ; And stuff a 0 to turn WHEEL off + JR RCVF3 ; WHEEL 'was' on, so skip access check + +; +; Check additional receive flags +; +RCVF2: LD A,(ACCESS) ; Checking access restrictions? + OR A + JR Z,RCVF3 ; No + CALL WHLCHK ; SYSOP online? + JR NZ,RCVF3 ; Yep, skip all this checking + LD A,(PUPFLG) ; Privileged transfer option request? + OR A + LD A,(AFBYTE) ; Get access flags byte + JR Z,$+7 ; No + AND 80H ; Test for privileged user access (bit 7) + JP Z,NOACC ; Not allowed to use "RW" option + AND 40H ; Test for upload access (bit 6) + JP Z,NOACC ; Not allowed to upload files + +; +; User has the access he asked for +; +RCVF3: LD A,(BATCH) ; Requesting batch mode? + OR A + JP NZ,RBTCH ; Yes, go do batch stuff first + CALL RCVFL1 ; Find drive/user/filetype permitted + LD IX,FCB + CALL RESTRCT ; Check restrictions on uploads + CALL CONTIN ; Display drive/user area + CALL MAKEFIL ; Open the file, ready to receive + +; +; Receive records until EOT +; +RCVLP: XOR A + LD (ERRCNT),A ; Initialize error count to zero + CALL RCVRECD ; Receive a record + JR NC,RCVLP1 ; If not EOT, store this record and get next + LD HL,(RECDNO) ; Get number of records + LD A,H + OR L ; 0 length file? + JP Z,ABORT ; Yes, abort and erase file + LD A,(EOTFLG) ; This the first EOT character? + OR A + JP NZ,RCVEOT ; No, exit + LD A,NAK + LD (EOTFLG),A ; Set the flag + CALL SEND ; Send a NAK + JR RCVLP ; Go wait another EOT + +; +; Increment record number +; +RCVLP1: CALL INCRNO ; Bump record number, if received ok + LD HL,(RECPTR) ; Get buffer address + LD DE,128 ; 128 chars/record + LD A,(KFLG) ; Using 1k blocks? + OR A + JR Z,$+5 ; If not, skip next line + LD DE,1024 ; 1k/record + ADD HL,DE ; To next buffer + LD (RECPTR),HL ; Save buffer address + LD A,(KFLG) ; Using 1k blocks? + OR A + LD A,(RECNBF) ; Get number of records in buffer + JR Z,$+6 ; If not, skip next 2 lines + ADD A,8 ; Increment it 8 records for 1k + JR $+3 ; Skip next line + INC A ; Else only 1 record + LD (RECNBF),A ; Store new record count + +; +; If 16k in buffer, write to disk +; + LD C,A ; Put the record count in C + LD A,(BUFSIZ) ; Buffer size in A + ADD A,A + ADD A,A + ADD A,A + CP C ; Is the buffer full, yet? + CALL Z,WRBLOCK ; No, return + CALL SNDACK ; Ack the record + JP RCVLP ; Loop until 'EOF' + +; +; End of transmission received +; +RCVEOT: CALL SNDACK ; ACK the record + CALL WRBLOCK + JP RCVEOT0 + +WRBLOCK:LD A,(RECNBF) ; Number of records in the buffer + OR A ; 0 means end of file + RET Z ; None to write + LD C,A ; Save count + LD DE,DBUF ; Point to disk buff + +WRBLOK1:PUSH HL + PUSH DE + PUSH BC + CALL STDMA ; Set DMA + LD DE,FCB ; Then write the block + LD C,WRITE + CALL BDOS + POP BC + POP DE + POP HL + OR A ; Write error? + JR Z,WRBLOK2 ; No + CALL RSDMA ; Reset DMA to normal + LD A,CANCEL ; Cancel + CALL SEND ; Sender + CALL SEND + CALL SEND + CALL CLOSFIL ; Kill received file + CALL ILPRTB ; Exit with msg: + DB CR,LF + DB '-- Write Error: ',0 + CALL SHONM3 + JP EXIT + +WRBLOK2:LD HL,128 ; Length of 1 record + ADD HL,DE ; 'HL'= next buff + EX DE,HL ; To 'DE' for setdma + DEC C ; More records? + JR NZ,WRBLOK1 ; Yes, loop + XOR A ; Get a zero + LD (RECNBF),A ; Reset number of records + LD HL,DBUF ; Reset buffer + LD (RECPTR),HL ; Save buffer address + JP RSDMA + +; +; Write record to log file if LOGCAL is YES +; +RCVEOT0:CALL CLOSFIL ; Close the file + LD HL,(RECDNO) ; Get # of records + LD (RCNT),HL ; Stuff in RCNT + CALL XTIM ; Calculate approximate transfer time + CALL STORTM ; Store time + CALL LOGCALL ; Log transfer if supposed to + +RCVEOT1:LD A,(LOGLDS) ; Counting uploads? + OR A + JR Z,RCVEOT2 ; No + LD A,(PRIVATE) ; Private upload? + OR A + JR NZ,RCVEOT2 ; Yes, no credit for private uploads + LD IY,(UPLDS) ; Get Upload Counter + INC (IY) ; One more upload since log in + +RCVEOT2:CALL ALLDON ; If not in BATCH, print transfer complete + JP CRED ; Credit upload time and ask for descriptions + +; +;-------------------------------------------------------------------------; +; R e c e i v e B a t c h | +;-------------------------------------------------------------------------; +; +RBTCH: XOR A ; Using batch so reset some flags + LD (FRSTIM),A ; Needs to be reset for each new file + LD A,(FSTFLG) ; First batch file? + OR A + JR Z,RBTCH0 ; Yes, give them time to setup + LD A,CRC + CALL SEND ; In case he's quick like us + JP RBTCH1 + +; +; Initial setup only +; +RBTCH0: CALL RCVFL1 ; Find drive/user/filetype permitted + CALL CONTIN ; Display drive/user area + LD HL,NAMBUF + LD (NBSAVE),HL + LD A,1 + LD (FSTFLG),A ; No need to run those routines again + +; +; Get the batch file name and display +; +RBTCH1: LD HL,FCB + CALL INITFCB ; Initialize FCB + XOR A + LD (RCVTRY),A + INC A ; Set to local display only + LD (CONONL),A + +RBTCH2: CALL CKABORT ; Check for user abort + LD B,5 + CALL RECV ; Wait up to 5 seconds for SOH from remote + JR C,RBTCH3 ; No character, decrement counter + CP CANCEL ; Was it a CTL-X for cancel? + CALL Z,CKCAN ; Check for abort + CP SOH + JR Z,RBTCH5 ; Got SOH + JR RBTCH2 ; None of these, wait some more + +RBTCH3: LD A,CRC ; Send a 'C' + CALL SEND + +RBTCH4: LD A,(RCVTRY) + INC A + LD (RCVTRY),A + CP 20 + JR C,RBTCH2 + JP ABORT ; Quit and try to force him to quit also + +RBTCH5: LD B,5 + CALL RECV ; Wait up to 5 seconds for sector number + JP C,TOTERR + LD D,A ; Save sector number in D + OR A ; Must be a 0 if sending batch + JP NZ,WRGHDR + LD B,5 + CALL RECV ; Wait up to 5 seconds for reciprocal + JP C,TOTERR + CPL ; Invert it and compare to sector # + CP D + JP NZ,CRCERR ; Bad match + LD HL,0 + LD (CRCVAL),HL ; Clear CRC counter + LD E,128 ; Expecting a 128 character block + LD HL,(RECPTR) ; Point to the buffer address + +RBTCH6: LD B,5 + CALL RECV ; Up to 5 seconds for 128 byte header block + JP C,TOTERR ; Exit if no character + LD (HL),A ; Store the character + INC HL ; Point to next buffer location + DEC E ; One less to go + JR NZ,RBTCH6 + LD E,2 ; Number of CRC bytes to get + +RBTCH7: LD B,5 + CALL RECV ; Up to 5 seconds for CRC bytes + JP C,TOTERR + DEC E ; Done? + JR NZ,RBTCH7 ; No + CALL CRCCHK ; Compare CRC received against ours + OR A ; Ok? + JP NZ,CRCERR ; No + CALL SNDACK ; Yes, acknowledge to remote + +; +; Decode pathname into CPM format +; + LD DE,FCB+1 ; Where to put it + LD HL,(RECPTR) ; Where to get it + LD B,8 ; Filename length + +RBTCH8: LD A,(HL) ; Get the character from the buffer + OR A ; Was it a zero? + JR Z,RBTCH12 ; If yes, all done + CP '.' ; Was it a delimiter? + JR Z,RBTCH9 + CALL UCASE ; Insure name is in upper case + CP '_' ; Is it an underline? + JR NZ,$+4 ; No + LD A,'-' ; Else make it a dash + LD (DE),A ; Store filename character in FCB + INC DE ; Increment pointers + INC HL + DJNZ RBTCH8 ; If not 8, keep going + LD A,(HL) ; Get the character back + OR A ; We had 8, was there an extent? + JR Z,RBTCH11 ; If zero, was all done + JR RBTCH10 ; Else must be a '.' + +RBTCH9: LD A,' ' ; Spaces to make up 8 spaces for name + LD (DE),A ; Store space character in FCB + INC DE ; Increment pointers + DJNZ RBTCH9 ; Keep going until in extent area + +RBTCH10:INC HL ; Skip the '.' position + LD B,3 ; Extent length + +RBTCH11:LD A,(HL) ; Get the character from the buffer + OR A ; Was it a zero? + JR Z,RBTCH12 ; If yes, all done + CALL UCASE ; Insure extent is in upper case + LD (DE),A ; Store extent character + INC DE ; Increment pointers + INC HL + DJNZ RBTCH11 ; Keep going until finished + +RBTCH12:LD A,(FCB+1) ; See if there was any filename at all + CP ' ' + JP Z,RBCHDON ; No, all done, no more files + CALL CLEARIT ; Clear screen locally if suppose to + +RBTCH13:LD HL,(BUFSTR) ; Get the file length, if provided + LD A,H + OR L + JR NZ,$+7 ; If not both zero, length is provided + CALL SHONM ; Else show the filename + JR RBTCH14 ; And wait to receive + LD (RCNT),HL ; Store the file length + CALL OPNOK1 ; Show filename and file sizes + CALL ILPRTL + DB CR,LF + DB 'Ymodem transfer time > ',0 + CALL GETSPD ; Get speed indicator + CP 5 ; Are we less than 1200 bps? + JR C,$+7 ; Yes, skip 1k time + CALL KTIM ; Get 1k transfer time + JR $+5 ; Skip 128 byte transfer time + CALL XTIM ; Get 128 byte transfer time + CALL XFRTIM ; Display transfer time + +RBTCH14:CALL ILPRTL + DB CR,LF,LF,0 ; Finish the filename line + XOR A ; Reset the carry flag + LD (RCVTRY),A ; Reset the error counter + LD IX,FCB + CALL RESTRCT ; Check restrictions on uploads + CALL CHEKFIL ; Already have a file with that name? + CALL MAKEFIL ; If not, make it + CALL BCHINR + CALL WAITMSG ; Display '[ waiting ]' message locally + LD A,CRC + CALL SEND + JP RCVLP ; Start receiving the file + +RBCHDON:XOR A ; Zero the batch mode flag + LD (BATCH),A + LD A,(GOTONE) ; Were there any files received? + OR A + JP Z,ABORT ; No, abort + CALL XFRDON ; Show transmission is finished + JP CRED ; Ask for descriptions + +CRCERR: CALL ILPRTL + DB '-- CRC error' + DB CR,LF,0 + JP INCERR + +WRGHDR: CALL ILPRTL + DB '-- Wrong header type' + DB CR,LF,0 + JR INCERR + +TOTERR: CALL ILPRTL + DB '-- Timeout receiving filename' + DB CR,LF,0 + +INCERR: CALL WAIT1 ; Make sure sender has stopped + LD A,NAK ; Tell sender it was not successful + CALL SEND + LD A,(RCVTRY) ; Increment the error counter + INC A + LD (RCVTRY),A + CP 33 + JP C,RBTCH4 ; Send a NAK and tell him to try again + JP ABORT ; Else abort + +; +;-------------------------------------------------------------------------; +; C r e d i t R o u t i n e s | +;-------------------------------------------------------------------------; +; +; The following credits the caller for the amount of time spent uploading +; any non-private files with descriptions. +; +CRED: LD A,(BATCH) + OR A + JR NZ,CRED0A + LD E,10 ; Set up for a 30 second wait + +CRED0: LD B,3 ; 3 seconds to receive a character + CALL RECV + JR NC,CRED0A ; Got one, continue + CALL ILPRTB ; Make sure this goes to modem + DB CR + DB '-- Hit a key' + DB CR,0 ; Let them know we're waiting + DEC E ; 2 less seconds + JR NZ,CRED0 ; Wait until 0 + +CRED0A: LD A,(CREDIT) ; Credit caller with upload time? + OR A + JP Z,CRED2 ; No + CALL WHLCHK ; WHEEL byte set? + JP NZ,CRED2 ; Yes, skip credit + LD A,(PUPFLG) ; Privileged transfer request? + OR A + JP NZ,CRED2 ; Yes, skip credit + LD A,(PRIVATE) ; Was this a private file? + OR A + JP NZ,CRED2 ; Yes, skip credit + LD A,(BATCH) ; In batch mode now? + OR A + JP NZ,CRED1 ; If yes, skip following messages + + CALL ILPRTB ; Show to remote also + DB CR,LF + DB 'Thanks for the ',0 + CALL SHOCAT ; Show upload area descriptor, if supposed to + CALL ILPRTB + DB 'upload(s)!',CR,LF,0 + CALL ILPRTB + DB 'Upload time has been credited to time left.',0 + +CRED1: LD A,(MAXTOS) ; Get maximum time allowed + OR A ; Unlimited? + JR Z,CRED2 ; Yes, skip credit + LD HL,(RECDNO) ; Else get the number of records + LD (RCNT),HL + CALL XTIM ; Get transfer time in C + LD A,(MAXTOS) ; Get maximum time allowed back + INC A ; Increment to next full minute + ADD A,C ; Add upload time + LD (MAXTOS),A ; Save for internal use + +; +; If not still in BATCH mode, ask for file description +; +CRED2: LD A,(BATCH) ; Still in batch? + OR A + JP NZ,DONE ; Yes, see if anymore files left + LD A,(HIDEIT) ; Did we make this upload a $SYS file? + OR A + JR Z,CRED3 ; No, skip all this + CALL WHLCHK ; Wheel byte set? + JR NZ,CRED3 ; Yes, file not set to $SYS + LD A,(PRIVATE) ; Was this a private upload? + OR A + JR NZ,CRED3 ; Yes, file not set to $SYS + CALL ILPRTB + DB CR,LF + DB 'Uploads remain hidden until cleared by Sysop.',0 + +CRED3: CALL ILPRTB + DB CR,LF,LF,0 + CALL RSTLCK ; Clear WRTLOC before descriptions + CALL ADDTON ; Update BYE's time on byte if supposed to + +; +;-------------------------------------------------------------------------; +; D e s c r i p t i o n R o u t i n e s | +;-------------------------------------------------------------------------; + +ASK: LD A,(PRIVATE) ; Private upload? + OR A + JP NZ,EXIT ; Yes, no descriptions + LD A,(PUPFLG) ; Privileged transfer request? + OR A + JP NZ,EXIT ; Yes, no descriptions + LD A,(DESCRIB) ; Requiring descriptions? + OR A + JR NZ,ASK1 ; Yes + + LD A,(MSGDESC) ; To BBS message base? + OR A + JP Z,EXIT ; No + LD (DSCFLG),A ; Set flag to show message base descriptions + LD A,(PRUSR) ; Get the private user + LD (USER),A ; FOR destination + LD A,(PRDRV) ; Get the private drive + LD (DRIVE),A ; FOR destination + +ASK1: CALL GETTIME + LD HL,DATMSG+6 + + LD A,(EDATE) ; European date format? + OR A + JR Z,ASK1A ; No + LD A,(DAY) + CALL DATDEC ; Print DD + INC HL + LD A,(MONTH) ; Print MM + JR ASK1B ; And finish with YY + +ASK1A: LD A,(MONTH) + CALL DATDEC ; Print MM + INC HL + LD A,(DAY) + +ASK1B: CALL DATDEC ; Print DD + INC HL + LD A,(YEAR) + CALL DATDEC ; Print YY + + LD A,(FILCNT) ; Any batch filenames? + OR A + JR Z,ASK3 ; No + LD HL,NAMBUF ; Point to name buffer + LD (NBSAVE),HL + +ASK2: LD IY,FILCNT ; One less file to describe + DEC (IY) + LD HL,(NBSAVE) ; Get address of next batch filename + LD DE,FCB ; Where to put it + LD BC,12 + LDIR + LD (NBSAVE),HL ; Store address for next filename + +ASK3: LD A,(DESCRIB) ; FOR file descriptions? + OR A + JR Z,ASK6 ; No + + LD A,(ASKAREA) ; Using upload routing? + OR A + JR Z,ASK4 ; No, KIND contents doesn't matter + + LD A,(KIND) ; Do we have a the upload area yet? + OR A + JR NZ,ASK6 ; Yes, don't ask them twice + + CALL ILPRTB + DB CR,LF,LF + DB 'Upload category: ' + DB CR,LF,0 + JR ASK5 + +ASK4: LD A,(ASKIND) ; Need file descriptors for FOR entries? + OR A + JR Z,ASK6 ; No + + CALL ILPRTB + DB CR,LF,LF,0 + CALL SHONM3 ; Show the file name + + CALL ILPRTB + DB ' - this file is for:' + DB CR,LF,0 + +ASK5: CALL GETKIND ; Get file category for description header + CALL TYPE ; Output to both consoles + +ASK6: CALL ILPRTB + DB CR + DB 'Describe ',0 + CALL SHONM3 ; Show the filename + CALL ILPRTB + DB ' - 7 lines or less - ^W disables WRAP - CR when done',0 + + LD HL,FCB+1 ; FCB contains current filename + LD DE,NEWNAM ; Needed in here for description routines + LD B,8 ; Filename is up to 8 bytes long + CALL ASK7 ; Go store it until a space + + LD A,'.' + LD (DE),A ; Add seperator + + INC DE + LD HL,FCB+9 ; Point to file extent at FCB + LD B,3 ; File extent is up to 3 bytes long + CALL ASK7 ; Go store until space or B=3 + + LD A,LF ; Stuff Terminator + LD (DE),A + + CALL GETDSC ; Show typing guide and get upload description + JP Z,ASK2 ; If we got a description, get next + JP ASK3 ; Else get this one over again + +; +; Small subroutine to store the filename located at FCB+1 into buffer area +; located at DE +; +ASK7: LD A,(HL) ; Get character + AND 7FH ; Done with high bits + CP ' ' ; A space? + RET Z ; Yes, all done + LD (DE),A ; Else store it in destination + INC HL ; Increment source pointer + INC DE ; Increment destination + DJNZ ASK7 ; Keep looping until B=0 or (HL)=' ' + RET + +; +;----------------------- +; Set upload drive/user +; +RCVFL1: CALL LOGDU ; Select drive/user for upload + LD A,(PUPFLG) ; Place "RW" file as needed + OR A ; Can only be set if user is privileged + JR NZ,RCVFL2 ; Privileged, else check if sysop... + CALL WHLCHK ; Let WHEEL user put file wherever he wants + JR Z,RCVFL6 ; If WHEEL byte not set, stay normal + +RCVFL2: LD A,(RCVDRV) + OR A + JR Z,RCVFL3 + SUB 'A' ; Convert ASCII drive to binary + JR RCVFL4 + +RCVFL3: LD A,(OLDDRV) + +RCVFL4: INC A + LD (FCB),A + ADD A,'A'-1 ; Convert binary to ASCII + LD (DRV),A ; Drive + LD A,(RCVDRV) ; See if a drive was requested + OR A + LD A,(OLDUSR) ; Current user + JR Z,RCVFL5 ; If not, use current user + LD A,(RCVUSR) ; Else get requested user + +RCVFL5: LD (USR),A ; User + RET + +RCVFL6: LD A,(SETAREA) + OR A + JR NZ,RCVFL7 + LD A,(ASKAREA) + OR A + JR Z,RCVFL8 + +RCVFL7: LD A,(DRV) + SUB 40H + LD (FCB),A + +RCVFL8: LD A,(PRIVATE) ; Receiving to a private area? + OR A + RET Z ; If not, exit + LD A,(PRDRV) ; Private area takes precedence + SUB 40H ; Convert to binary + LD (FCB),A ; Store drive to be used + RET + +; +; Display where file(s) will go, open file and display name +; +CONTIN: LD A,(ASKAREA) ; Upload routing enabled? + OR A + JR NZ,CONT0 ; No + LD A,(ASKIND) + OR A + JR Z,CONT1 + +CONT0: CALL WHLCHK ; Is WHEEL byte set? + JR NZ,CONT1 ; No, skip this + CALL GETKIND ; Get upload area + LD A,CR ; So the line feed (LF) doesn't get printed + LD (CONT1+4),A + +CONT1: CALL ILPRTB + DB CR,LF + DB 'Receiving on: Drive ',0 + LD A,(PRIVATE) ; Private upload? + OR A + LD A,(PRUSR) ; Get private user area + LD B,A ; Put in B for now + LD A,(PRDRV) ; Get private drive + JR NZ,CONT2+3 ; Yes, priority 1 + LD A,(USR) ; Get the regular user area + LD B,A ; And put it in B + LD A,(PUPFLG) ; Privileged upload? + OR A + JR NZ,CONT2 ; Yes, priority 2 + CALL WHLCHK ; WHEEL set? + JR NZ,CONT2 ; Yes, priority 3 + LD A,(SETAREA) ; Uploading to designated drive/user? + OR A + JR NZ,CONT2 ; Yes, priority 4 + LD A,(ASKAREA) ; Upload routing enabled? + OR A + JR Z,CONT3 ; No + +CONT2: LD A,(DRV) ; Get regular upload drive + PUSH AF ; Save ASCII upload drive + SUB 40H ; Convert drive to binary + LD (FCB),A ; Store it in File Control Block + POP AF ; Get ASCII drive back + JR CONT4 ; All done, now display it + +CONT3: LD A,(OLDUSR) ; Get current user area for default + LD B,A ; Save in B + DB 0,0 ; Contains 'LD B,n' (DUU) from GETDU + LD A,(OLDDRV) ; Get current drive for default + ADD A,'A' ; Convert to ASCII + DB 0,0 ; Contains 'LD A,n' (DUD) from GETDU + +CONT4: LD (KDRV),A ; Save it for KSHOW + CALL TYPE ; Print the drive to store on + CALL ILPRTB + DB ', User ',0 + LD A,B ; B contains the user area + LD (USR),A ; Save for MSGDESC upload info + LD H,0 + LD L,A ; Binary user area in L + CALL DECOUT ; Decimal output + CALL ILPRTB + DB '. (',0 + CALL KSHOW ; Show available space remaining + CALL ILPRTB + DB ')',0 + CALL CHEKFIL ; See if file exists + + LD A,(DESCRIB) ; Descriptions enabled? + OR A + JR NZ,CONT5 ; Yes + LD A,(MSGDESC) ; Message base descriptions? + OR A + JR Z,CONT6 ; No + +CONT5: LD A,(PRIVATE) ; Private upload? + OR A + JR NZ,CONT6 ; Yes, no descriptions + LD A,(PUPFLG) ; Privileged upload? + OR A + JR NZ,CONT6 ; Yes, no descriptions + CALL ILPRTB + DB CR,LF + DB 'Description(s) needed - ',0 + JR CONT7 + +CONT6: CALL ILPRTB + DB CR,LF,0 + +CONT7: CALL ILPRTB + DB 'Abort: ^X pause ^X' + DB CR,LF,LF,0 + CALL WAITMSG + RET + +; +; Increment the file count +; +BCHINR: LD HL,(NBSAVE) ; Where to put the name + LD DE,FCB ; Where to get the name + EX DE,HL + LD BC,12 ; Move current filename to buffer for ASK: + LDIR + EX DE,HL + LD (NBSAVE),HL ; Store address for next filename + LD A,(FILCNT) ; Increment the file count + INC A + LD (FILCNT),A + RET + +; +;-------------------------------------------------------------------------; +; T r a n s f e r c o m p l e t e | +;-------------------------------------------------------------------------; +; +; Done transferring current file. Check to see if in BATCH mode and if so, +; display filename transferred and reset flags for next possible file. +; Otherwise eat garbage from line, reset WRTLOC, do timekeeping and exit +; to CP/M (Forward text file to BBS message base if supposed to). +; +DONE: LD A,(BATCH) ; Still in batch mode? + OR A + JP Z,EXIT ; No. All done + LD A,(OLDDRV) ; Restore the original drive + CALL RECDRX + LD A,(OLDUSR) ; Restore the original number + CALL RECAR1 + CALL RSDMA ; Reset to default DMA address + LD A,1 ; Display filename locally only + LD (GOTONE),A ; Indicates there was a file handled + CALL ILPRTL ; Display the file name + DB CR,LF,0 + CALL SHONM3 ; Show the filename at FCB+1 + CALL ILPRT + DB ' transferred',CR,LF,0 + +; +; Now reset some flags for another possible batch file +; + XOR A + LD (EOFLG),A ; Clear end of file flag + LD (EOTFLG),A ; And end of transmission flag + LD (CHKEOT),A ; Clear the "resend EOT" flag + LD HL,0 + LD (ACCERR),HL ; Reset the accumulate error count + LD (RECNBF),HL ; Zero number of records in the buffer + LD (RECDNO),HL ; Zero the current record number + LD (RCDCNT),HL ; Zero the transmit record counter + LD HL,DBUF ; Reset buffer pointers + LD (RECPTR),HL + LD A,(MODE) ; Get transfer mode + CP 'S' ; Sending files? + JP Z,SNDFIL ; Yes + + LD A,(FILIMT) ; Maximum upload (TPA limitation) + LD B,A ; Into B for comparison + LD A,(FILCNT) ; Get current count received + CP B ; Received BATCH transfer limit yet? + JP C,RCVFL + LD A,CANCEL + CALL SEND + CALL SEND + CALL SEND + CALL WAIT1 + + CALL ILPRTB + DB CR,LF + DB '-- ',0 + LD A,(FILIMT) + LD H,0 + LD L,A + CALL DECOUT + CALL ILPRTB + DB ' file limit in BATCH receive',CR,LF,0 + + XOR A + LD (BATCH),A ; Reset the batch mode flag to zero + JP CRED3 ; Go back and ask for descriptions + +; +;-------------------------------------------------------------------------; +; C o m m o n S u b r o u t i n e s | +;-------------------------------------------------------------------------; +; +; Universal access check routine checks restrictions of current file being +; considered for transfer. +; +; On entry: IX = start address of byte before filename +; On exit: Z = File ok to send/receive +; NZ = Transfer denied +; +; Each bit of this word contains an image of the high bit within the filename +; pointed to by IX+1 on entry. +; +HBITMAP:DW 0000000000000000B + +; +; First, make a bit map containing an image of the high bits in the filename +; pointed to by IX+1 on entry. +; +RESTRCT:LD B,11 ; Number of bytes to map + LD HL,0 ; Initialize destination for bit map + PUSH IX ; Save current filename address + INC IX ; Skip past drive indicator + +ACCMASK:LD A,(IX) ; Get next character of filename + AND 80H ; Isolate attribute bit + RLCA ; Move MS bit into LS bit + OR L ; OR in any previously set bits + LD L,A ; Save result + ADD HL,HL ; Shift HL left one bit for next time + INC IX ; IY+1 equals next character in filename-type + DJNZ ACCMASK ; Loop through all 11 bytes + POP IX ; Get our original filename pointer back + +; +; Most significant bit will already be in bit 11 of HL, so only 4 shifts are +; necessary +; + ADD HL,HL ; 000?????$??????00 + ADD HL,HL ; 00??????$?????000 + ADD HL,HL ; 0???????$????0000 + ADD HL,HL ; ????????$???00000 + LD (HBITMAP),HL ; Store filename high bit image + +; +; See which (if any) restrictions we need to enforce +; + CALL WHLCHK ; WHEEL byte set? + JP NZ,SENDOK ; Yes, transfer is approved + LD A,(ACCMAP) ; Get user defined restriction flags + LD B,A + LD A,(MODE) ; Get the file transfer mode + CP 'S' ; Sending? + JR Z,RSTRCT2 ; Yes, check send restrictions + +; +; Check RECEIVE restrictions +; + LD IX,FCB + BIT NOCOMR,B ; Rename '.COM' uploads to '.OBJ'? + JR Z,RSTRCT1 ; No, check for ZCPR restrictions + LD DE,COMCHG ; Compare to 'COM' + CALL MCHFTYP ; Are they the same? + CALL Z,RENTYP ; Yes, rename it to 'OBJ' + +RSTRCT1:BIT ZCPR,B ; Using with ZCPR? + RET Z ; No, all done + LD DE,SYSCHK ; Compare to 'SYS' + CALL MCHFTYP ; Are they the same? + JR Z,FTYPERR ; Yes, tell them to use a different filetype + LD DE,NDRCHK ; Compare to 'NDR' + CALL MCHFTYP ; ... + JR Z,FTYPERR ; ... + LD DE,RCPCHK ; Compare to 'RCP' + CALL MCHFTYP ; ... + RET NZ ; If no match, filetype is ok to receive + +FTYPERR:CALL ERXIT + DB CR,LF + DB '-- Use a different file extent','$' + +; +; Check SEND restrictions +; +RSTRCT2:LD A,(BATCH) ; In BATCH? + OR A + JR NZ,RSTRT2A ; Yes, require send access for any batch file + BIT DWNTAG,B ; Allow F3 tagged file regardless of access? + JR Z,RSTRT2A ; No, skip this + BIT 5,H ; Byte 3 of filename set? + JP NZ,SENDOK ; Yes, send it immediately + +RSTRT2A:LD A,(ACCESS) + OR A + JR Z,RSTRCT3 + LD A,(AFBYTE) ; Get BYE or BBS bit mapped access flag + AND 20H ; Download access allowed? + JP Z,NOACC ; No, inform user of restricted function + +RSTRCT3:LD A,(LBRARC) ; Get member extraction flag + OR A ; Enabled? + JR NZ,RSTRCT4 ; Yes, skip these restrictions + BIT TAGFIL,B ; Restricting tagged files? + JR Z,RSTRCT4 ; No + BIT 7,H ; First byte of filename set? + JR NZ,NOSEND ; Yes, can't send it + +RSTRCT4:BIT NOSYS,B ; Restricting $SYS files? + JR Z,RSTRCT5 ; No + BIT 6,L ; First byte of filetype set? + JR NZ,NOSEND ; Yes, can't send + +RSTRCT5:LD A,(LBRARC) ; Get member extraction flag + OR A ; Enabled? + RET NZ ; Yes, and file was not tagged (returning NZ) + BIT NOLBS,B ; Restricting files with labels (#)? + JR Z,RSTRCT6 ; No + LD A,(IX+11) ; Get possible label + AND 7FH ; Strip the high bit + CP '#' ; Labeled? + JR Z,NOSEND ; Yes, can't send + +RSTRCT6:BIT NOCOMS,B ; Allow sending 'COM' files? + JP Z,SENDOK ; Yes + LD DE,COMCHG ; Point to string to compare with + CALL MCHFTYP ; Is it a .COM file? + JP NZ,SENDOK ; No + +; +; Common exit point +; +COMTRY: LD A,(BATCH) ; In batch mode? + OR A + JP NZ,NOSND2 ; Yes, just set flag to not include (NZ) + POP HL ; Remove call from OPNOK from stack + CALL ERXIT + DB CR,LF + DB '-- Can''t send .COM files','$' + +NOSEND: LD A,(BATCH) ; Are we in batch mode? + OR A + JP NZ,NOSND2 ; Yes, no error messages, just checking + LD DE,LBRNAM + CALL NOSND0 + JR Z,NOSND1 + LD DE,ARKNAM + CALL NOSND0 + JR Z,NOSND1 + LD DE,ARCNAM + CALL NOSND0 + JR Z,NOSND1 + CALL ERXIT + DB CR,LF + DB '-- File is not for distribution','$' + +NOSND0: LD B,3 + LD HL,FCB+9 + CALL MATCH + RET + +NOSND1: CALL ERXIT + DB CR,LF + DB '-- Individual members only','$' + +NOSND2: LD A,1 ; Return NZ if file not allowed + OR A + RET + +SENDOK: XOR A ; Return Z if file is ok + RET + +; +; See if next character is ' ' or non ' '. File name error if no ASCII +; character. +; +CHKFSP: LD A,(BATCH) ; Requesting batch mode now? + OR A + JR Z,CHKFSP2 ; Exit if not + LD A,(MODE) ; Sending batch? + CP 'S' + JR Z,CHKFSP2 ; If yes, exit + DEC B + JR Z,CHKFSP1 + INC B + JR CHKFSP2 + +CHKFSP1:POP HL ; Do not return to LOGDU + RET ; Return instead to SNDFIL + +CHKFSP2:DEC B + JP Z,NFN1 ; Error if end of chars. + LD A,(HL) + CP ' '+1 + RET NC ; Ok if valid character so return + INC HL + JR CHKFSP + +; +; Check next character to see if a space or non-space, go to menu if a command +; error. +; +CHKSP: LD A,(BATCH) ; Requesting batch mode? + OR A + JR Z,CHKSP2 ; Exit if not + LD A,(MODE) ; Sending in batch mode now? + CP 'S' + JR Z,CHKSP2 ; If yes, exit + DEC B + JR Z,CHKSP1 + INC B + JR CHKSP2 + +CHKSP1: POP HL ; Don't return to LOGDU + RET ; Return to SNDFIL + +CHKSP2: DEC B + JP Z,HELP + INC HL + LD A,(HL) ; Get the character there + CP ' ' ; Space character? + RET ; Z = space, NZ = non-space + +; +; Determine the amount of disk storage needed for the current file. On +; entry: BC = total record count of file +; +ROUNDK: LD DE,(BLKSIZ) ; Fetch block size in kilobytes + PUSH DE ; Save block size + PUSH BC ; Save file record count + LD B,3 ; Make a mask for size limit + +MSKCMP: OR A ; Clear carry + RL E ; Make mask for size limit + RL D ; Shift until + DJNZ MSKCMP ; Shift until DE is A + DEC DE ; Mask of records per block + POP BC ; Get a copy of file record count + PUSH BC + + LD A,C ; Mask file size with block size mask + AND E + LD C,A + LD A,B + AND D + OR C ; Zero result indicates no block + POP BC + PUSH AF ; Remainder in file size + LD A,D ; Compliment mask and zero file size + CPL ; Remainder in BC + AND B + LD B,A + LD A,E + CPL + AND C + LD C,A + LD E,3 ; Shift count to divide masked file + +MINKL: OR A ; Clear carry + RR B ; Rotate high byte through carry + RR C + DEC E ; Decrement shift count + JR NZ,MINKL + + POP AF ; Check if even block size + POP DE ; Get back block size + PUSH HL ; Save kilobyte insert address + LD HL,0 ; Initial zero of remainderI + JR Z,MINKS ; Zero if even + EX DE,HL ; Block size to HL if remainder + +MINKS: ADD HL,BC ; Add in total kilobyte count + EX DE,HL ; Total size to DE + POP HL ; Get back load address + RET + +; +; Log into drive and user +; +; (If specified). If none mentioned, falls through to 'TRAP' routine for +; normal use. +; +LOGDU: LD HL,TBUF ; Point to default buffer command line + LD B,(HL) ; Store number of characters in command + INC B ; Add in current location + CALL CHKSP ; Skip spaces to find 1st command + JR Z,$-3 ; Loop until non-space character + CALL CHKSP ; Skip 1st command (non-spaces) + JR NZ,$-3 ; Loop until a space + INC HL + CALL CHKFSP ; Skip spaces to find 2nd command + LD (SAVEHL),HL ; Save start address of the 2nd command + +; +; Now pointing to the first byte in the argument. (If it was of a format +; similar to: 'B6:HELLO.DOC' then we point at the drive character 'B'. Then +; transfer up to 4 bytes from the command line buffer (pointed at by HL) to +; the drive/user storage buffer pointed at by DE +; +LGDU1: PUSH HL ; Save command line position + PUSH BC ; And character count + LD DE,DUSAVE ; Destination buffer + LD C,4 ; Drive/user is 4 characters maximum 'B15:' + +LGDU2: LD A,(HL) ; Get character + CP ' '+1 ; Space or return? + JP C,TRAP ; Yes, all done + LD (DE),A ; Else store it in DUSAVE + INC HL ; Increment to next argument + INC DE ; Increment DUSAVE + CP ':' ; Was it a colon? + JR Z,LGDU3 ; Yes, was drive/user requested + DEC B ; One less position to check + DEC C ; One less to go + JR NZ,LGDU2 ; Loop until a colon or C=0 + JP TRAP ; Move name to FCB + +; +; Get Disk and User from DUSAVE and log in if valid. +; +LGDU3: EXX ; Save HL (buffer) pointer and BC (char count) + POP BC ; We don't need these back, but fix the stack + POP HL + EXX ; And get HL and BC back to continue + LD A,(BATCH) ; Requesting batch mode? + OR A + JR Z,LGDU4 ; No + LD A,(MODE) ; Get program transfer mode + CP 'R' ; Receiving batch? + JR Z,LGDU5 ; Yes, skip next two lines + +LGDU4: CALL CHKFSP ; See if a file name is included + LD (SAVEHL),HL ; Save location of the filename + +LGDU5: LD A,(PRIVATE) ; Uploading to a private area? + OR A + JP NZ,TRAP2 ; If yes, going to a specified area + + LD A,(OLDDRV) ; Get current drive + LD (DUD),A + ADD A,'A' + LD (RCVDRV),A + + LD HL,DUSAVE ; Point to drive/user + LD A,(HL) ; Get 1st character + CP '0' ; It is a ' ', CR or LF? + JR C,LGDU6 ; Yes, skip next 2 lines + CP '9'+1 ; Is it an ASCII number 0-9? + JR C,LGDU10 + +LGDU6: LD (RCVDRV),A ; Allows SYSOP to upload to any drive + CP 'A'-1 + JR C,LGDU9 ; Satisfied with current drive + SUB 'A' + LD (DUD),A + + LD A,(PUPFLG) ; Privileged user upload request? + OR A + LD A,(DUD) + JR NZ,LGDU8 ; Yes + + CALL WHLCHK + LD A,(DUD) + JR NZ,LGDU8 + + LD A,(USEMAX) ; Using ZCPR low memory bytes? + OR A + JR NZ,LGDU7 ; Yes + LD A,(MAXDRV) + LD C,A + LD A,(DUD) + CP C + JP NC,ILLDU ; Drive selection not available + JR LGDU8 + +LGDU7: LD A,(DUD) ; Get the drive back + LD IY,(DRIVMAX) ; Point to max drive byte + INC (IY) + CP (IY) ; And check it + PUSH AF ; Save flags from the CP + DEC (IY) ; Restore max drive to normal + POP AF ; Restore flags from the CP + JP NC,ILLDU + +LGDU8: INC HL ; Get 2nd character + +LGDU9: LD A,(HL) + CP ':' + JP Z,LGDU17 ; Colon for drive only, no user number + CALL CKNUM ; Check if numeric + +LGDU10: SUB '0' ; Convert ASCII to binary + LD (DUU),A ; Save it + INC HL ; Get 3rd character if any + LD A,(HL) + CP ':' + JR Z,LGDU11 + LD A,(DUU) + CP 1 ; Is first number a '1'? + JP NZ,ILLDU + LD A,(HL) + CALL CKNUM + SUB 38 + LD (DUU),A + INC HL ; Get 4th (and last character) if any + LD A,(HL) + CP ':' + JP NZ,ILLDU + +LGDU11: LD A,(MODE) + CP 'R' ; Receiving a file? + LD A,(DUU) + JR Z,LGDU12 + LD A,(SPLDRV) + SUB 'A' + LD C,A + LD A,(DUD) + CP C + JR NZ,LGDU12 + LD A,(SPLUSR) + LD C,A + LD A,(DUU) + CP C + JR Z,LGDU15 + +LGDU12: CALL WHLCHK ; SYSOP using the system? + JR Z,LGDU13 + LD A,(DUU) ; Restore desired user area + LD (RCVUSR),A ; Allows SYSOP to upload anywhere + JR NZ,LGDU15 ; If yes, let him have all user areas + +LGDU13: LD A,(USEMAX) ; Using ZCPR low memory bytes? + OR A + JR NZ,LGDU14 ; Yes + LD A,(MAXUSR) ; Check for maximum user download area + ADD A,1 + LD C,A + LD A,(DUU) + CP C + JP NC,ILLDU ; Error if more (and not special area) + JR LGDU15 + +LGDU14: LD A,(DUU) + LD IY,(USRMAX) ; Point at maximum user byte + CP (IY) ; And check it + JP NC,ILLDU + +LGDU15: LD E,A + LD A,(SETAREA) ; Using designated drv/usr for reg. uploads? + OR A + JR NZ,LGDU16 ; Yes + LD A,(ASKAREA) ; Using upload routing? + OR A + JR NZ,LGDU16 ; Yes + LD A,E + LD (CONT3+5),A ; Store requested user area + LD A,6 ; 'LD B,n' instruction + LD (CONT3+4),A + +LGDU16: LD C,SETUSR ; Set to requested user area + CALL BDOS + +LGDU17: LD A,(DUD) ; Get drive + LD E,A + LD A,(SETAREA) ; Using designated drv/usr for reg. uploads? + OR A + JR NZ,LGDU18 ; Yes + LD A,(ASKAREA) ; Using upload routing? + OR A + JR NZ,LGDU18 ; Yes + LD A,E + ADD A,'A' + LD (CONT3+12),A ; Store requested drive + LD A,3EH ; 'LD A,n' instruction + LD (CONT3+11),A + +LGDU18: LD C,SELDSK ; Set to requested drive + CALL BDOS + JR TRAP2 ; Now find file selected + +; +; If we get here, no d/u was specified. Restore original command line pointer +; and character count and move name to FCB. +; +TRAP: POP BC ; Get original character count back + POP HL ; And original command line buffer position + +; +; Check for no file name or ambiguous name +; +TRAP1: LD A,(PRIVATE) ; Get the private transfer flag + OR A ; Is it enabled? + JR Z,TRAP2 ; No, current du stays normal + LD A,(SPLUSR) ; Get the special download user area + CALL RECAR1 ; Set user area to special download user + LD A,(SPLDRV) ; Get the special download drive + CALL RECDR1 ; Set drive to special download drive + +TRAP2: CALL SPCDRV ; Keep DPB info straight + LD HL,FCB + CALL INITFCB ; Make sure FCB initialized + CALL MOVFCB ; Move the filename into the file block + LD HL,FCB+1 ; Point to file name + LD A,(HL) ; Get first character + CP ' ' ; Any there? + JR NZ,TRAP3 ; Yes, check wildcards + LD HL,FCB+9 ; Else point to file extent + LD A,(HL) ; Get character + CP ' ' ; Space also? + JP Z,NFN ; Yes, we have no filename, exit with error + LD HL,FCB+1 ; Else point to start again + +TRAP3: LD A,(PRIVATE) ; Get the private transfer flag + OR A ; Is it enabled? + RET Z ; No, then don't trap wildcards + LD B,11 ; Else check all 11 characters of filename + +TRAP4: LD A,(HL) ; Get char from FCB + CP '?' ; Ambiguous? + JR Z,NOWILD ; Yes, exit with error message + CP '*' ; Even more ambiguous? + JR Z,NOWILD ; Yes, exit with error message + INC HL ; Point to next character + DJNZ TRAP4 ; Not done, check some more + RET + +CKNUM: CP '0' + JR C,ILLDU ; Error if less than ascii '0' + CP '9'+1 + RET C ; Error if more than ascii '9' + +ILLDU: CALL ERXIT + DB CR,LF + DB '-- Unauthorized drive/user','$' + +NFN: CALL ILPRT + DB CR,LF,0 + +NFN1: CALL ERXIT ; Print message, exit + DB '-- No filename(s) requested','$' + +NOWILD: CALL ERXIT ; Print message, exit + DB CR,LF + DB '-- Wildcards not valid for PRIVATE downloads','$' + +; +; Previous record repeated, due to the last ACK being garbaged. ACK it so the +; sender will catch up +; +RCVACK: CALL SNDACK ; Send the ACK + XOR A + LD (ERRCNT),A ; Reset the error count + +; +; Receive a record - returns with carry bit set if EOT received +; +RCVRECD:CALL FUNCHK ; Check function keys + CALL SNDABT ; See if wanting to abort + LD A,(FRSTIM) ; Have we started, yet? + OR A + LD B,10 ; Check every ten seconds if already started + JR Z,$+4 ; If yes, skip next line + LD B,5 ; Check every 5 seconds until started + CALL RECV ; Get character + JP C,RCVSTOT ; Timeout error if no character received + CP SOH ; SOH? + JP Z,RCVSOH ; Yes, get record + CP STX ; STX for 1k blocks? + JR NZ,$+11 ; No + LD (KFLG),A ; Set the 1k flag + LD (CRCFLG),A ; Insure in CRC mode for 1k blocks + JP RCVS1 + + CP CANCEL ; Was it a CTL-X to abort? + CALL Z,CKCAN ; If yes, check for aborting + OR A ; Get another character, if a null + JR Z,RCVRECD + CP 7BH ; V.22 synch character, ignore + JR Z,RCVRECD + CP 0FBH ; V.22 synch character with high bit set + JR Z,RCVRECD + CP EOT ; See if end of transmission + SCF ; Set carry + RET Z ; Return with carry set + CP CRC ; Ignore our own character coming back + JR Z,RCVRECD + CP KSND ; Ignore our own character coming back + JR Z,RCVRECD + CP NAK ; Ignore our own character coming back + JR Z,RCVRECD + CALL ILPRTL ; Show locally only + DB CR,'-- ',0 + LD A,B + CALL HEXO + CALL ILPRTL + DB 'H received not SOH',CR,LF,0 + JR RCVSR + +; +; Checksum error +; +CKSMERR:CALL ILPRTL + DB ' - Checksum error',CR,LF,0 + JR RCVSR ; Go check the error limit and send NAK + +; +; Bad record number in header error +; +HDRERR: CALL ILPRTL + DB ' - Error in header',CR,LF,0 + JR RCVSR ; Go check error limit and send NAK + +; +; Timed out on receive error +; +RCVSTOT:LD A,(FRSTIM) ; First time flag set yet? + OR A + JR Z,RCVSR ; If not, don't show an error + CALL TOTMSG + +; +; Didn't get SOH or EOT or did not get valid header so purge the line, then +; send NAK. +; +RCVSR: CALL WAIT1 ; Get anything coming in and discard + CALL SNDABT ; See if wanting to abort + LD A,(FRSTIM) ; Get first time switch + OR A ; Has first 'SOH' been received? + LD A,NAK + JR NZ,RCVSR1 ; Yes, then send 'NAK' + LD A,(CRCFLG) ; Get the 'CRC' flag + OR A ; 'CRC' in effect? + LD A,NAK ; Put 'NAK' in 'A' register + JR Z,RCVSR1 ; No, send the 'NAK' for checksum + LD A,CRC ; Tell sender we have 'CRC' + CALL SEND + LD A,(KFLG) ; Requesting 1k transmissions? + OR A + JR Z,RCVSR1 ; If not, exit + LD A,KSND ; Tell sender we also have 1k capability + +RCVSR1: CALL SEND ; The 'NAK' or 'CRC' request + LD A,(ERRCNT) ; Get the error count + INC A ; Increment error count + LD (ERRCNT),A ; Store new value + LD B,A ; Keep the error count for now + LD A,(FRSTIM) ; Have we gotten under way yet? + OR A + LD A,B ; get the value back + JR Z,RCVSR2 ; If not, exit + CP 10 ; 10 errors the limit, once under way + JP NC,ABORT ; Abort if over the limit + CALL RDCOUNT ; Display record count before repeating + JP RCVRECD ; Less than 10, keep going + +RCVSR2: CP 7 ; 7 times for 1k/CRC yet? (40 seconds) + JP C,RCVRECD ; Keep trying if less + XOR A ; Else flip to checksum mode + LD (CRCFLG),A + LD A,B ; Get the count back + CP 3 ; Another 3 times for checksum? + JP C,RCVRECD ; If less, try again, quit at 60 seconds + JP ABORT + +; +; Aborts with 1 CTL-X if first time flag is not set, two otherwise +; +CKCAN: LD A,(FRSTIM) ; First time flag set yet? + OR A + JR Z,CKCAN1 ; If not, abort + LD B,2 + CALL RECV ; Maximum of 2 seconds for extra ^X + RET C ; No additional character, ignore single ^X + CP CANCEL ; Got a character, is it a ^X? + RET NZ ; No, ignore 1st ^X and return + +CKCAN1: POP HL ; Reset stack for CALL CKCAN + JP ABORT ; Got 2nd ^X, abort and close file + +; +; Got SOH - get block number (complemented) +; +RCVSOH: XOR A + LD (KFLG),A ; If SOH, clear the 1k flag + +RCVS1: LD A,1 ; Get something to store + LD (FRSTIM),A ; Indicate first 'SOH' or 'STX' recvd. + LD B,5 + CALL RECV ; Wait up to 5 seconds for block number + JP C,RCVSTOT ; Got timeout + LD D,A ; Save block number + LD B,5 + CALL RECV ; 5 seconds for complimented record number + JP C,RCVSTOT ; Timeout + CPL ; Get the complement + CP D ; Same as original block number? + JP NZ,HDRERR ; No, go report bad record number in header + LD A,D ; Get record number + LD (RCVCNT),A ; Save it + LD C,0 ; Initialize checksum + LD HL,0 ; Initialize CRC + LD (CRCVAL),HL ; Clear CRC counter + LD DE,128 ; For 128 character blocks + LD A,(KFLG) ; Using 1k blocks? + OR A + JR Z,$+5 ; If not, skip next line + LD DE,1024 ; If using 1k blocks + LD HL,(RECPTR) ; Get buffer address + +RCVCHR: LD B,5 + CALL RECV ; 5 seconds for character + JP C,RCVSTOT ; Timeout + LD (HL),A ; Store the character + INC HL ; Point to next character + DEC DE ; One less to go + LD A,E ; See if 'D' and 'E' are both empty + OR D + JR NZ,RCVCHR ; No, get next character + LD A,(CRCFLG) ; Using 'CRC'? + OR A + JP NZ,RCVCRC ; If yes go get 'CRC' + +; +; Verify checksum +; + LD D,C ; Save checksum + LD B,5 + CALL RECV ; Up to 5 seconds for checksum + JP C,RCVSTOT ; Timeout + CP D ; Checksum ok? + JP NZ,CKSMERR ; No, report error + +; +; Got a record, it's a duplicate if equal to the previous number, it's OK if +; previous + 1 record +; +CHKSNUM:LD A,(RCVCNT) ; Get received record number + LD B,A ; Save it + LD A,(RCDCNT) ; Get previous record number + CP B ; Previous record repeated? + JP Z,RCVACK ; If yes 'ACK' to catch up + INC A ; Increment by 1 for 120 character block + CP B ; Match this one we just got? + JP NZ,ABORT ; No match, stop the sender, exit + RET ; Else return with carry not set, was ok + +; +; 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: LD E,2 ; Number of bytes to receive + +RCVCRC2:LD B,5 + CALL RECV ; Up to 5 seconds for CRC byte + JP C,RCVSTOT ; Timeout + DEC E ; Decrement the number of bytes + JR NZ,RCVCRC2 ; Get both bytes + CALL CRCCHK ; Check received CRC against calc'd CRC + OR A ; Is CRC okay? + JR Z,CHKSNUM ; Yes, go check record numbers + CALL ILPRTL ; Show locally only + DB ' - CRC error',CR,LF,0 + JP RCVSR ; Go check error limit and send NAK + +; +;------------------ +; Send subroutines +;------------------ +; +; Send an ACK for the record +; +SNDACK: LD A,ACK ; Get 'ACK' + JP SEND ; And send it + +; +; Send SOH, block number and complemented block number (3 bytes total) +; +SNDHDR: LD A,(KFLG) ; Sending 1k blocks? + OR A + LD A,STX ; If yes, send a STX rather than SOH + JR NZ,$+4 + LD A,SOH ; Send start of header + CALL SEND + +SNDHNM: LD A,(RCDCNT) ; Send the current record number + CALL SEND + LD A,(RCDCNT) ; Get the record number again + CPL ; Complemented + JP SEND ; From SENDHDR + +; +; Send data record +; +SNDREC: LD C,0 ; Initialize checksum + LD HL,0 ; Initialize CRC + LD (CRCVAL),HL + LD A,(KFLG) ; Sending 1k blocks? + OR A + LD DE,1024 + JR NZ,$+5 ; If yes, skip the next line + LD DE,128 + LD HL,(RECPTR) ; Get buffer address + +SENDC: LD A,(HL) ; Get a character + CALL SEND ; Send it + INC HL ; Point to next character + DEC DE + LD A,E + OR D + JR NZ,SENDC ; If DE not zero, keep going + RET ; From SENDREC + +; +; Send the CRC or checksum value +; +SNDCHK: LD A,(CRCFLG) ; See if sending 'CRC' or 'checksum' + OR A + JR NZ,SNDCRC ; If not zero, send the 'CRC' value + +; +; Send Checksum +; +SNDCKS: LD A,C ; Send the checksum + JP SEND ; From SNDCKS + +; +; Send CRC (2 characters). Call FINCRC to calculate the CRC which will be +; in 'DE' upon return. +; +SNDCRC: CALL FINCRC ; Calculate the 'CRC' for this record + LD A,D ; Put first 'CRC' byte in accumulator + CALL SEND ; Send it + LD A,E ; Put second 'CRC' byte in accumulator + CALL SEND ; Send it + XOR A ; Set zero return code + RET + +; +; Get acknowlegement +; +; After a record is sent, a character is returned telling if it was received +; properly or not. An ACK allows the next record to be sent. A NAK causes +; the current record to be resent. If no character (or any character other +; than ACK or NAK) is received after a short wait (10-12 seconds), a timeout +; error message is shown and the record will be resent. +; +GTACK: LD B,12 + CALL RECV ; Wait up to 12 seconds for ACK or NAK + JR NC,GTACK1 ; Got one + CALL TOTMSG + JP ACKERR ; Set the carry bit and return + +GTACK1: CP ACK ; See if an ACK already + RET Z ; If yes, return + CP NAK ; See if a NAK + JR Z,GTACK2 ; If yes, print error, then resend + CP 07BH ; V.22 synch character? + JR Z,GTACK ; If yes, ignore it + CP 0FBH ; V.22 synch character? + JR Z,GTACK ; If yes, ignore it + CP CANCEL ; CTL-X to cancel attempt? + CALL Z,CKCAN + +GTACK2: LD B,A ; Save the character + LD A,(CHKEOT) ; Sending EOT? + OR A + JP NZ,ACKERR ; If yes, don't show error (for ZMD) + CALL ILPRTL + DB ' - ',0 + LD A,B + CP NAK + JR Z,GTACK3 + CALL HEXO + CALL ILPRTL + DB 'H',0 + JR GTACK4 + +GTACK3: CALL ILPRTL + DB 'NAK',0 + +GTACK4: CALL ILPRTL + DB ' received not ACK',CR,LF,0 + CALL CATCH ; None of them, establish clear line again + +; +; Timeout or error on ACK - bump error count then resend the record if +; error limit is not exceeded +; +ACKERR: LD A,(ACCERR) ; Count accumulated errors on ACK + INC A ; Add in this error + LD (ACCERR),A + LD A,(ERRCNT) ; Get count + INC A ; Bump it + LD (ERRCNT),A ; Save back + CP 10 ; At limit? + JR NC,ACKMSG ; If yes, send error message and abort + LD A,(ACKCHK) ; Checking after a batch header? + OR A + CALL Z,RDCOUNT ; Yes, show the record count for repeat + + LD A,B ; Get character back + CP NAK ; NAK? + JP NZ,GTACK ; No, ignore and wait for ACK or NAK + RET ; And go back + +; +; Reached error limit +; +ACKMSG: CALL WAIT1 ; Wait for any input to stop + LD A,CANCEL ; Tell remote we are quitting + CALL SEND + CALL SEND + CALL SEND + LD B,2 + CALL RECV ; Up to 2 seconds for remote to quit too + LD A,BS + CALL SEND ; Clear any CTL-X from buffer + CALL SEND + CALL SEND + CALL ERXIT + DB CR + DB '-- File transfer aborted','$' + +; +; Routines to trap abort conditions +; +; Check to see if a cancel requested. Fall through to ABORT if so. +; +CKABORT:CALL CONSTAT + OR A + RET Z + CALL CONIN + CP CANCEL + RET NZ + +; +; Aborts send or receive routines and returns to command line +; +ABORT: CALL WAIT1 ; 1- second delay to clear input + CALL CATCH + LD A,(EOTFLG) ; Timed out after only 1 EOT? + OR A + JP NZ,RCVEOT+3 ; Accept as valid EOT then + LD A,CANCEL ; Show you are cancelling + CALL SEND ; They may quit also with enough CTL-X + CALL SEND + CALL SEND + CALL WAIT1 ; 1-second delay to clear input + CALL CATCH + LD A,BS + CALL SEND + CALL SEND + CALL SEND + +ABORTX: CALL CATCH ; Eat garbage characters + CALL ABRTMSG ; Show we have aborted + LD A,(MODE) ; Get file transfer mode + CP 'R' ; Sending a file? + JP NZ,EXIT ; Yes, quit to CP/M + +; +; Take care of received file (if any). +; +CLOSFIL:LD C,CLOSE ; Get function + LD DE,FCB ; Point to file + CALL BDOS ; Close it + INC A ; Close ok? + JR NZ,CLOSFL1 ; Yes + CALL ILPRT ; No, abort + DB CR,LF + DB '-- Received file not closed',0 + JP NTDEL1 + +CLOSFL1:LD A,(EOTFLG) ; Get end of transmission flag + OR A ; Received entire file? + RET NZ ; Yes, return to RCVEOT routines + CALL ILPRTB + DB CR,LF + DB '-- Upload has been cancelled',0 + +; +; Delete the received file +; + LD C,DELETE ; Get function + LD DE,FCB ; Point to file + CALL BDOS ; Delete it + INC A ; Delete ok? + JR Z,NOTDEL ; No + CALL ERXIT ; Print second half of message + DB CR,LF + DB '-- Partial file is deleted','$' + +; +; Unsuccessful delete +; +NOTDEL: CALL ILPRT + DB CR,LF + DB '-- Received file not deleted' + +NTDEL1: CALL ERXIT + DB ' or no file received','$' + +; +; See if a file exists. If it exists, ask for a different name. +; +CHEKFIL:LD A,(SETAREA) ; Uploading to designated drive/user? + OR A + JR NZ,CHEKF1 ; Yes + LD A,(ASKAREA) ; Upload routing enabled? + OR A + JR NZ,CHEKF1 ; Yes + LD A,(PRIVATE) ; Receiving in private area? + OR A + JR Z,$+5 ; No + +CHEKF1: CALL RECARE ; Set the designated area up + LD C,SRCHF ; See if it exists + LD DE,FCB ; Point to control block + CALL BDOS + INC A ; Found? + RET Z ; No, return + LD A,CANCEL ; Tell the remote we are aborting + CALL SEND ; Send several cancel requests + CALL SEND + CALL SEND + +CHEKF2: LD B,1 + CALL RECV ; Up to 1 seconds for character + JR NC,CHEKF2 ; Wait until no more characters + LD A,(BATCH) ; Using batch mode now? + LD (CONONL),A ; If not, send message to modem also + OR A + JR Z,CHEKF3 ; If not, exit + LD A,CANCEL + CALL SEND + CALL SEND + CALL SEND + LD A,BS + CALL SEND + +CHEKF3: CALL ERXIT ; Exit, print error message + DB CR,LF + DB '-- File already exists','$' + +; +; Make the file to be received +; +MAKEFIL:XOR A ; Set extent and record number to 0 + LD (FCBEXT),A + LD (FCBRNO),A + LD A,(HIDEIT) + OR A + JR Z,MAKEF1 ; HIDEIT not enabled, skip all this + CALL WHLCHK + JR NZ,MAKEF1 ; Don't make it $SYS if SYSOP online + LD A,(PRIVATE) + OR A + JR NZ,MAKEF1 ; Don't make it $SYS if private upload + + LD DE,FCB+10 ; Point at second char of file extent + LD A,(DE) + OR 80H ; And turn on the high bit (Make file $SYS) + LD (DE),A ; Put it back + +MAKEF1: LD C,MAKE ; Get BDOS FNC + LD DE,FCB ; Point to FCB + CALL BDOS ; To the make + PUSH AF ; Save MAKE error code + + LD C,SETFILE ; Set up for BDOS FUNCTION 30 + LD DE,FCB + CALL BDOS ; Set file attributes + POP AF ; Error code from BDOS make function + INC A ; 0FFH=bad? + RET NZ ; Open ok + + LD HL,FCB+1 + JP NOROOM ; Tell them directory might be full + +; +; Open file to be sent +; +OPNFIL: XOR A ; Zero accumulator + LD (FCBEXT),A ; Set extent to 0 + LD (FCBRNO),A ; Set record number to 0 + LD DE,FCB ; Point to file + LD C,OPEN ; Open it + CALL BDOS + INC A ; Open ok? + JR NZ,OPNOK ; Yes, check restrictions + + LD A,(LBRARC) ; Get extraction flag + OR A ; Enabled? + JP Z,NOFILE ; No, abort + LD HL,ARCNAM ; Force .ARC filetype + CALL CHNGEXT ; Try to open it + JR NZ,OPNOK ; File found + LD HL,ARKNAM ; Force .ARK filetype + CALL CHNGEXT ; Try to open it + JR NZ,OPNOK ; File found + LD HL,LBRNAM ; Force .LBR filetype + CALL CHNGEXT ; Try to open it + JR NZ,OPNOK ; File found + JP NOARK ; Not found and no more filetypes to try + +CHNGEXT:LD DE,FCB+9 + LD BC,3 + LDIR + LD C,OPEN + LD DE,FCB + CALL BDOS + INC A + RET ; Z flag set=file not found + +; +; Requested file was found, now check some restrictions +; +OPNOK: LD IX,FCB ; Point to filename + CALL RESTRCT ; Check it for restrictions + LD A,(LBRARC) ; Get the member extraction flag + OR A ; Enabled? + JR Z,OPNOK1 ; No, skip this + CALL RSDMA ; Reset to default DMA address + LD C,READ ; Read first file record + LD DE,FCB + CALL BDOS + OR A ; Read ok? + JP NZ,READERR ; If not, error + CALL CKDIR ; Take care of LBR stuff + +OPNOK1: LD HL,(RCNT) ; Get record count + LD A,H + OR L + JP Z,ZEROLN ; Can't send 0-length files + LD A,(BATCH) + OR A + JR Z,OPNOK1A ; Don't clear screen unless in BATCH mode + LD A,(FSTFLG) ; Get first file sent flag + OR A ; Sent it already? + LD A,1 ; Show we have for next time + LD (FSTFLG),A + CALL Z,CLEARIT ; No, need to clear screen here first time + +OPNOK1A:CALL SHONM ; Show the name of this file + CALL LOW41K ; Less than MINKSPD? + JR C,OPNOK3 ; Yes, don't show 1k packets + +OPNOK2: CALL ILPRT + DB CR,LF + DB 'Ymodem packets total > ',0 + LD HL,(RCNT) ; Get record count + CALL DIVREC ; Divide number of records by 8 + CALL DECOUT ; Show # of 1k packets + +OPNOK3: CALL ILPRT + DB CR,LF + DB 'Xmodem packets total > ',0 + LD HL,(RCNT) ; Get original count + CALL DECOUT ; Show # of 128 byte packets + LD A,(MODE) ; Get transfer mode + CP 'R' ; Receiving? + RET Z ; Yes, all done + + CALL ILPRT + DB CR,LF + DB 'Disk space you need > ',0 + + LD A,(SBSHOW) ; Displaying intial BATCH screen to remote? + OR A + PUSH AF ; Save answer + LD HL,(FILEK) ; Get precalculated total 'k' for all files + JR NZ,OPNOK4 ; Go show it + LD BC,(RCNT) ; Else get single file record count back + CALL ROUNDK ; Round disk space needed + EX DE,HL +; +OPNOK4: CALL DECOUT ; Decimal output + CALL ILPRT + DB 'k (',0 + LD HL,(BLKSIZ) ; Get host disk block size + CALL DECOUT ; Decimal output + CALL ILPRT + DB 'k blocks)',0 + POP AF ; Displaying initial BATCH screen to remote? + RET NZ ; Yes, then we're done in here + +; +; Show transfer time, first for 1k blocks, then for 128-byte blocks. If we are +; at 300 bps, report both transfer times the same. (skip the 1k times for +; speeds slower than MINKSPD bps.) +; +KSPD: CALL LOW41K ; Less than MINKSPD? + JR C,XSPD ; Yes, skip 1k display + CALL ILPRT + DB CR,LF + DB 'Ymodem time / 1k > ',0 + CALL GETSPD ; Get current modem speed + CP 1 ; At 300 bps? + JR Z,KSPD1 ; 1k transfer time in BC (minutes) if >300 + CALL KTIM + JR KSPD2 + +KSPD1: LD HL,XECTBL + LD (RECTBL+1),HL + CALL XTIM + +KSPD2: CALL STORTM ; Store it + CALL XFRTIM ; Display it + +XSPD: CALL ILPRT + DB CR,LF + DB 'Xmodem time / 128 byte > ',0 + LD HL,XECTBL ; 128 size values (300 bps) + LD (RECTBL+1),HL + CALL XTIM ; Xmodem transfer time + LD A,(KFLG) ; If 'SK' set, 1k time already stored + OR A + CALL Z,STORTM + CALL XFRTIM + LD HL,KECTBL ; Restore to original 1k values + LD (RECTBL+1),HL + CALL ILPRT + DB CR,LF,0 + + LD A,(BATCH) ; In batch mode? + OR A + JP Z,OPNOK5 ; No, couldn't have been here before + LD A,(FSTFLG) ; Yes, been here before? + OR A + JP Z,OPNOK5 ; No, following gets shown next time + +; +; In batch, show files remaining after this one is sent +; + CALL ILPRTL + DB CR,LF + DB 'Files remaining > ',0 + LD A,(SHOCNT) ; Get cumulative files + DEC A + LD (SHOCNT),A ; Less one + LD L,A + LD H,0 + CALL DECOUT + + CALL ILPRTL + DB CR,LF + DB 'Ymodem packets remaining > ',0 + LD HL,(RCNT) ; Get this file's record count again + EX DE,HL ; Put in DE + LD HL,(TOTREC) ; Total records remaining + LD A,L + SUB E + LD L,A + LD A,H + SBC A,D + LD H,A + JR NC,$+5 + LD HL,0 ; In case of a slightly negative number + PUSH HL ; Save it for Xmodem packets show + CALL DIVREC ; Divide number of records by 8 + CALL DECOUT + + CALL ILPRTL + DB CR,LF + DB 'Xmodem packets remaining > ',0 + POP HL ; Get total records remaining after this file + LD (TOTREC),HL + CALL DECOUT ; Show remote remaining records + CALL ILPRTL + DB CR,LF,LF,0 + CALL WAITMSG ; Display '[ waiting ]' message locally + RET + +; +; If sending an ARC or ARK file, tell user to rename to .ARK or .ARC file type. +; +OPNOK5: LD A,(LBRARC) ; Get extraction flag + OR A ; Enabled? + JP Z,DLRDY ; No, skip this + LD A,(FCB+9) ; Point to member filetype + AND 7FH ; Strip parity + CP 'L' ; LBR member extraction? + JP Z,DLRDY ; Yes, skip this + CALL ILPRTB + DB CR,LF + DB 'You MUST name file > ',0 + LD D,8 ; Filename count - ignore filetype + LD HL,MEMFCB ; Get requested member name + +OPNOK6: LD A,(HL) + CP ' ' ; Short filename? + JR Z,OPNOK7 ; If so, fill in type + CALL TYPE + + DEC D ; One less... + INC HL ; Next character + JR NZ,OPNOK6 ; Loop until done + +OPNOK7: LD A,(FCB+11) ; Get last character of parent filetype + LD ($+9),A ; Stuff it below to display + CALL ILPRTB + DB '.AR?' ; Either a 'C' or a 'K' gets poked at '?' + DB CR,LF,0 + CALL DLRDY ; Tell them their download(s) are ready + RET + +; +; These routines display the transfer time in minutes & seconds and check for +; time restrictions, if a clock is enabled. +; +XFRTIM: PUSH HL ; Save seconds in 'L' + CALL WHLCHK ; Sysop online? + JR NZ,SKPTIM ; Yes, then skip the limit + + LD A,(MAXTOS) + OR A + JR Z,SKPTIM + LD D,C ; Save minutes for now + INC D ; Increment to next full minute + LD A,(TIMEON) ; Using TIMEON? + OR A + LD A,D ; Get length of this program + JR Z,XFRTM1 ; No, don't increment time + LD HL,TON ; Point to time on system + ADD A,(HL) ; Else add time on system to transfer time + +XFRTM1: LD (XFRMIN),A ; Store it + OR A + LD A,B ; Get hours in A + JR NZ,$+3 ; Don't increment if not zero + INC A ; Increment to next full minute + LD (XFRMIN+1),A + +SKPTIM: LD H,B ; Get most significant in H (hours) + LD L,C ; Get least significant byte of minutes in L + CALL DECOUT ; Print decimal number of minutes + CALL ILPRT + DB ':',0 + POP HL ; Get seconds back + LD A,L ; Get the number of seconds + CP 10 ; 10 seconds or more? + JR NC,$+7 ; If yes, disregard next two lines + CALL ILPRT + DB '0',0 + CALL DECOUT ; Print decimal number of seconds + CALL ILPRT + DB ' at ',0 + CALL GETSPD ; Get modem speed value in A + CALL SHOSPD ; Display in BPS + +; +; Determine if the caller has enough time left online to make the +; requested download(s). +; +XFRTM3: LD A,(MODE) ; Get transfer mode + CP 'R' ; Receiving? + RET Z ; Yes, all done + + LD A,(MAXTOS) ; Get maximum time allowed + OR A ; Unlimited? + RET Z ; Yes, skip time restriction + + LD A,(XFRMIN+1) ; Get most significant byte of minutes + OR A ; 0? + JR NZ,OVERTM ; If not, over 255 minutes + LD A,(XFRMIN) ; Get least significant byte of minute count + LD B,A ; Put in B + LD A,(MAXTOS) ; Get maximum time allowed + INC A + SBC A,B + RET NC + +; +; There is not enough time to download the requested file(s). Inform user and +; abort to CP/M. +; +OVERTM: CALL ILPRTB + DB CR,LF,LF,0 + CALL ABRTMSG ; Display both local and remote we aborted + CALL ILPRTB + DB CR,LF,LF + DB 'Required send time exceeds the ',0 + LD A,(TLOS) ; Get time left on system + LD H,0 ; Zero H + LD L,A ; Time left on system in L + CALL DECOUT ; Decimal output routine + CALL ERXIT ; Display following message and abort to CP/M + DB ' minutes allowed','$' + +; +;-------------------------------------------------------------------------; +; L o g F i l e T r a n s f e r | +;-------------------------------------------------------------------------; +; +; Main log file routine, adds record to log file +; +LOGCALL:LD A,(LOGCAL) ; Logging file transfers? + OR A + RET Z ; No + CALL GTCURDU ; Get current drive/user in USRSAV and DSKSAV + + LD HL,FCBCLR ; FCB to initialize + LD DE,LSTCLR ; Filename to insert + CALL RENFCB ; Initialize FCB + + LD A,(LASTDRV) + SUB 'A' + LD (DEFDSK),A + LD A,(LASTUSR) + LD (DEFUSR),A + LD DE,FCBCLR + CALL OPENF ; Open LASTCALR file + JR NZ,LGCAL1 + CALL ILPRT + DB CR,LF + DB '-- File not Found: LASTCALR.???' + DB CR,LF,0 + RET ; Now go send EOT + +LGCAL1: LD C,SETRRD ; Get random record # + LD DE,FCBCLR ; (for first record in file) + CALL BDOS + + LD DE,DBUF ; Set DMA to DBUF + CALL STDMA + + LD C,RRDM ; Read first (and only) record + LD DE,FCBCLR + CALL BDOS + + LD HL,DBUF ; Set pointer to beginning of record + LD A,(CLOCK) ; Is there a clock installed? + OR A + JR Z,LGCAL2 ; No, skip this then + LD DE,0 ; Zero DE + LD A,(LCNAME) ; Offset to start of caller's name + LD E,A ; To E + ADD HL,DE ; HL now points to start of name + +LGCAL2: LD (CLRPTR),HL + LD DE,LOGBUF ; Set DMA address to LOGBUF + CALL STDMA + + LD HL,FCBLOG ; FCB to initialize + LD DE,LOGNAM ; Filename to insert + CALL RENFCB ; Initialize FCB + + LD A,(LOGDRV) + SUB 'A' + LD (DEFDSK),A + LD A,(LOGUSR) + LD (DEFUSR),A + LD DE,FCBLOG + CALL OPENF ; Open log file + JR NZ,LGCAL5 ; If file exists, skip create + LD DE,FCBLOG + LD C,MAKE ; Create a new file if needed + CALL BDOS + INC A + JR NZ,LGCAL3 ; No error, continue + CALL ILPRT ; File create error + DB CR,LF + DB '-- Directory Full: ',0 + LD HL,LOGNAM + CALL SHONM4 + RET ; Go back and send EOT + +LGCAL3: LD DE,LOGBUF ; Set DMA back to LOGBUF + CALL STDMA + + LD C,SETRRD ; Set random record # + LD DE,FCBLOG ; (for first record in file) + CALL BDOS + +LGCAL4: LD A,EOF + LD (LOGBUF),A + JR LGCAL6 + +LGCAL5: LD DE,LOGBUF ; Set DMA to LOGBUF + CALL STDMA + + LD C,FILSIZ ; Get file length + LD DE,FCBLOG + CALL BDOS + LD HL,(FCBLOG+33) ; Back up to last record + LD A,L + OR H + JR Z,LGCAL4 ; Unless zero length file + DEC HL + LD (FCBLOG+33),HL + LD DE,FCBLOG + LD C,RRDM ; And read it + CALL BDOS + +LGCAL6: CALL RSTLP ; Initialize LOGPTR and LOGCNT + +LGCAL7: LD A,(LOGCNT) + INC A + LD (LOGCNT),A + CP 129 + JR NZ,LGCAL8 + LD HL,(FCBLOG+33) + INC HL + LD (FCBLOG+33),HL + LD HL,LOGBUF+1 + LD (LOGPTR),HL + LD A,1 + LD (LOGCNT),A + LD A,EOF + JR LGCAL8A + +LGCAL8: LD HL,(LOGPTR) + LD A,(HL) + INC HL + LD (LOGPTR),HL + +LGCAL8A:CP EOF + JR NZ,LGCAL7 ; Until EOF + LD A,(LOGCNT) ; Then backup one character + DEC A + LD (LOGCNT),A + LD HL,(LOGPTR) + DEC HL + LD (LOGPTR),HL + +; +; Print file transfer mode to LOG file (R, S, P, A, L) +; + LD A,(PUPFLG) + OR A ; Privileged upload option request? + JR Z,LGCAL8B ; No, skip next 2 lines + LD A,'P' ; Else, + JR LGCAL9 ; Show as private upload for log file + +LGCAL8B:LD A,(PRIVATE) + OR A + JR NZ,LGCAL9 + LD A,(MODE) ; Get transfer mode back and put in file + +LGCAL9: CALL PUTLOG + +; +; Print baud rate to LOG file +; + CALL GETSPD ; Get speed factor + ADD A,30H + CALL PUTLOG + CALL PUTSP ; Blank + +; +; Print program size (in minutes and seconds) to LOG file +; + LD A,(PGSIZE) ; Now the program size in minutes.. + CALL PNDEC ; Of transfer time (mins) + LD A,':' + CALL PUTLOG ; ':' + LD A,(PGSIZE+2) + CALL PNDEC ; And seconds + CALL PUTSP ; Blank + +; +; Log the drive and user area as a prompt +; + LD A,(FCB) + OR A + JR NZ,WDRV + LD A,(DSKSAV) + INC A + +WDRV: ADD A,'A'-1 + CALL PUTLOG + LD A,(USRSAV) + CALL PNDEC + LD A,'>' ; Make it look like a prompt + CALL PUTLOG + LD A,(LBRARC) + OR A ; Member extraction? + JR Z,WDRV1 ; No, won't be member name + LD HL,MEMFCB ; Name of file in library + LD B,11 + CALL PUTSTR + CALL PUTSP ; ' ' + +; +; Put filename in LOG file +; +WDRV1: LD HL,FCB+1 ; Now the name of the file + LD B,11 + CALL PUTSTR + LD A,(LBRARC) + OR A ; Member extraction? + JR Z,WDRV2 ; No, won't be member name + LD C,1 + JR SPLOOP + +WDRV2: LD C,13 + +SPLOOP: PUSH BC + CALL PUTSP ; Put ' ' + POP BC + DEC C + JR NZ,SPLOOP + +; +; Print number of 'k' to LOG file +; + LD HL,(RECDNO) ; Get record count + CALL DIVREC ; Divide record count by 8 + +EXKB2: CALL PNDEC3 ; Print to log file (right just xxxk) + LD HL,LOGK ; 'k ' + LD B,2 + CALL PUTSTR + XOR A + LD (COMMA),A ; Reset field counter + +; +; Print date and time of transfer to LOG file +; + LD A,(CLOCK) ; Clock available in BYE? + OR A + JR NZ,EXKB3 ; Yes, continue + LD A,(RTC) ; Else how about an RTC overlay? + OR A + JR Z,CLOOP ; Nope, foget date and time + +EXKB3: CALL GETTIME ; Get CURRENT time for log + + LD A,(EDATE) ; European date format? + OR A + JR Z,EXKB4 ; No + + LD A,(DAY) + CALL PNDEC ; Print DD + LD A,'/' ; '/' + CALL PUTLOG + LD A,(MONTH) + CALL PNDEC ; Print MM + JR EXKB5 + +EXKB4: LD A,(MONTH) + CALL PNDEC ; Print MM + LD A,'/' ; '/' + CALL PUTLOG + LD A,(DAY) + CALL PNDEC ; Print DD + +EXKB5: LD A,'/' ; '/' + CALL PUTLOG + LD A,(YEAR) + CALL PNDEC ; Print YY + CALL PUTSP ; ' ' + LD A,(HOUR) ; Get current hour + CALL PNDEC ; Print hr to file + LD A,':' ; With ':' + CALL PUTLOG ; Between HH:MM + LD A,(MINUTE) ; Get min + CALL PNDEC ; And print min + CALL PUTSP ; Print a space + +; +; Print name of caller to LOG file +; +CLOOP: LD HL,(CLRPTR) + LD A,(HL) + INC HL + LD (CLRPTR),HL + CP EOF ; End of file? + JR Z,QUIT ; Yes + CP CR ; Do not print 2nd line of 'LASTCALR' + JR NZ,CLOP1 + +CEND: CALL PUTLOG + LD A,LF + CALL PUTLOG ; And add a LF + JR QUIT + +CLOP1: CP ' ' ; Space? + JR NZ,CLOP1A ; No, check for comma + LD A,',' ; Convert space to comma for field checking + +CLOP1A: CP ',' ; Comma? + JR NZ,CLOP2 + LD A,(COMMA) + CP 1 ; Is this the second comma or space? + JR NZ,CLOP1B ; No, bump the counter + LD A,CR + JR CEND ; Yes, stop taking data from lastcalr + +CLOP1B: INC A ; Bump it one + LD (COMMA),A + LD A,' ' ; Instead send a ' ' + +CLOP2: CALL PUTLOG + JR CLOOP + +QUIT: LD A,EOF ; Put in EOF + CALL PUTLOG + LD A,(LOGCNT) ; Check count of chars in buffer + CP 1 + JR NZ,QUIT ; Fill last buffer & write it + LD DE,FCBCLR ; Close lastcaller file + LD C,CLOSE + CALL BDOS + INC A + JR Z,QUIT1 + LD HL,(FCBLOG+33) ; Move pointer back to show + DEC HL ; Actual file size + LD (FCBLOG+33),HL + LD DE,FCBLOG ; Close log file + LD C,CLOSE + CALL BDOS + INC A + RET NZ ; If OK, return now... + +QUIT1: CALL ILPRT ; If error, oops + DB CR,LF + DB '-- Close Error: ',0 + LD HL,LOGNAM + CALL SHONM4 + RET ; Go back and send EOT + +; +;------------------------- +; LOGCAL Support Routines +; +; Open file with FCB pointed to by DE (disk/user passed in DEFDSK and DEFUSR) +; +OPENF: PUSH DE ; Save FCB address + LD A,(DEFDSK) ; Get disk for file + CALL RECDRX ; Log into it + LD A,(DEFUSR) ; Get default user + CALL RECAR1 ; Log into it + POP DE ; Get FCB address + LD A,(CPM3) ; Using with CPM3? + OR A + JR Z,OPENF1 ; No + PUSH DE ; Save FCB address + CALL RSDMA ; Set DMA to 80H + POP DE ; Get back pointer to FCB + PUSH DE ; Save FCB pointer again + LD C,SRCHF ; Search for first match + CALL BDOS + INC A ; Did file match? + POP DE + RET Z ; No, return + PUSH DE + DEC A ; A=directory code (0-3) + ADD A,A ; *2 + ADD A,A ; *4 + ADD A,A ; *8 + ADD A,A ; *16 + ADD A,A ; *32 + LD E,A + LD D,0 + LD HL,TBUF ; Add (32*dir code) to default DMA + ADD HL,DE ; to find first match filename + POP DE ; DE=FCB + PUSH DE ; Save DE again + INC HL ; Move HL past user # byte in buffer + INC DE ; Move DE past drive # byte in FCB + LD BC,11 + LDIR ; Move name found to FCB + POP DE ; And continue with open + +OPENF1: LD C,OPEN ; Open file + CALL BDOS + CP 0FFH ; Not present? + RET ; Return to caller + +; +; Write character to log file +; +PUTLOG: LD HL,(LOGPTR) ; Get pointer + AND 7FH ; Strip any attributes + LD (HL),A ; Put data + INC HL ; Increment pointer + LD (LOGPTR),HL ; Update pointer + LD B,A ; Save character in B + LD A,(LOGCNT) ; Get count + INC A ; Increment it + LD (LOGCNT),A ; Update count + CP 129 ; Check it + RET NZ ; If not EOB, return + PUSH BC ; Save character + LD DE,FCBLOG ; Else, write this sector + LD C,WRDM + CALL BDOS + OR A + JR Z,ADVRCP ; If ok, cont. + CALL ILPRT + DB CR,LF + DB '-- Disk Full: ',0 + LD HL,LOGNAM + CALL SHONM4 + RET + +ADVRCP: LD HL,(FCBLOG+33) ; Advance record number + INC HL + LD (FCBLOG+33),HL + CALL RSTLP ; Reset buffer pointers + POP AF ; Get saved character + JP PUTLOG ; Put it in buffer and return + +RSTLP: LD HL,LOGBUF ; Reset pointers + LD (LOGPTR),HL ; And return + LD A,0 + LD (LOGCNT),A + RET + +; +; Print number in decimal format (into log file) IN: HL=binary number +; OUT: nnn=right justified with spaces +; +PNDEC3: LD A,H ; Check high byte + OR A + JR NZ,DECOT ; If on, is at least 3 digits + LD A,L ; Else, check low byte + CP 100 + JR NC,TEN + CALL PUTSP + +TEN: CP 10 + JR NC,DECOT + CALL PUTSP + JR DECOT + +; +; Print number in decimal format (into log file) +; +PNDEC: CP 10 ; Two column decimal format routine + JR C,ONE ; One or two digits to area number? + JR TWO + +ONE: PUSH AF + LD A,'0' + CALL PUTLOG + POP AF + +TWO: LD H,0 + LD L,A + +DECOT: PUSH BC + PUSH DE + PUSH HL + LD BC,-10 + LD DE,-1 + +DECOT2: ADD HL,BC + INC DE + JR C,DECOT2 + LD BC,10 + ADD HL,BC + EX DE,HL + LD A,H + OR L + CALL NZ,DECOT + LD A,E + +DECOT3: ADD A,'0' + CALL PUTLOG + +DECOT4: POP HL + POP DE + POP BC + RET + +; +; Put string to log file +; +PUTSTR: LD A,(HL) + PUSH HL + PUSH BC + CALL PUTLOG + POP BC + POP HL + INC HL + DJNZ PUTSTR + RET + +; +; Puts a single space in log file, saves PSW/HL +; +PUTSP: PUSH AF + PUSH HL + LD A,' ' + CALL PUTLOG + POP HL + POP AF + RET + +; +;-------------------------------------------------------------------------; +; T I M E & D A T E R o u t i n e s | +;-------------------------------------------------------------------------; +; +; Get RTCBUF address if running BYE +; +TIME: LD A,(CLOCK) ; Clock in BYE? + OR A + JR Z,TIME1 ; No + LD DE,25 ; Offset to RTCBUF address + CALL GETOFF ; Point to JP COLDBOOT + offset in DE + LD E,(HL) ; HL points to RTCBUF address + INC HL ; To most significant byte of address + LD D,(HL) + EX DE,HL ; Back to HL + LD (RTCBUF),HL ; Save for later use + CALL GETTIME ; Store RTCBUF contents internally + + LD HL,(RTCBUF) ; Get RTC buffer address + LD DE,7 ; Offset to time on system (TOS) word + ADD HL,DE ; Address in HL + LD A,(HL) ; Get minutes on system + LD (TON),A ; Store time on system for SHOWTOS + +; +; Get MAXTOS if restricting downloads to time left +; + LD A,(TIMEON) ; Policing time on system? + OR A + JP Z,SHOWTOS ; No + LD DE,24 ; Offset to maximum time allowed + CALL GETOFF ; Point to JP COLDBOOT + offset in D + + LD A,(MODE) ; Exiting? (Gets set NZ in exit routine) + OR A + JR Z,TIME0 ; No, skip next + LD A,(MAXTOS) ; Reset maximum time allowed + LD (HL),A + JR TIME0A + +TIME0: LD A,(HL) ; Get maximum time allowed + LD (MAXTOS),A ; Store it + LD (HL),0 ; Disable BYE from checking time for now + +TIME0A: LD A,(TON) + LD B,A ; Save time on system for comparison + LD A,(MAXTOS) ; Get maximum time allowed + SUB B ; Get time left on system + LD (TLOS),A ; Store time left on system + JP SHOWTOS ; Go show TON + +; +; Get TON if RTC +; +TIME1: LD A,(RTC) ; Clock reader code installed in ZMD? + OR A + JP Z,SHOWTOS ; No + CALL GETTIME + + LD HL,(LHOUR) ; Get address to logon hour + LD A,(HOUR) + CP (HL) ; Same as current hour? + INC HL ; Point to logon minute + LD D,(HL) ; Get it in D + JR NZ,TIME2 ; No, not the same + LD A,(MINUTE) ; Else get current minute + SUB D ; Subtract logon minute + LD (TON),A ; Store it as time on system + JR TIME3 ; Get maximum allowed + +TIME2: LD A,60 ; Fake an hour + SUB D ; Subtract logon minute + LD HL,MINUTE ; Point to current minute + ADD A,(HL) ; Add them + LD (TON),A ; Store as current time on system + +; +; Get MAXTOS if TIMEON +; +TIME3: LD A,(TIMEON) ; Restricting downloads to time left? + OR A + JR Z,SHOWTOS ; No + CALL WHLCHK ; WHEEL byte set? + JR NZ,SHOWTOS ; Yes, just display time on system + LD A,(MODE) ; Else been here before? + OR A + JR NZ,TIME4 ; Yes (MODE is 0 first time through) + LD A,(MAXMIN) + LD (MAXTOS),A ; Else set maximum time allowed + LD (TLOS),A ; And current time left on system + +TIME4: LD A,(MAXTOS) ; Get current maximum time allowed + OR A ; Unlimited? + JR Z,SHOWTOS ; Yes, just display time on system + LD A,(MAXMIN) ; Else get original maximum minutes allowed + LD B,A ; Into B + LD A,(TON) ; Get current time on system + SUB B ; Time up? + JR C,SHOWTOS ; No, just display time on system + + CALL ILPRTB + DB CR,LF,LF + DB '-- Your time is up, please share the system with others' + DB CR,LF,0 + POP HL + LD A,0CDH + LD (0),A + JP 0 + +; +; Display the time on system +; +SHOWTOS:LD A,(DSPTOS) ; Display time on system message? + OR A + RET Z ; No, all done + LD A,(MODE) ; Else exiting? + OR A + JR Z,SHOTOS1 ; Yes, no line feed + CALL ILPRTB + DB CR,LF,0 + +SHOTOS1:CALL ILPRTB + DB 'Online ',0 + LD A,(TON) ; Get time on system + LD H,0 ; Zero H + LD L,A ; TON in L + CALL DECOUT ; Decimal output + CALL ILPRTB + DB ' minute',0 + LD A,(TON) ; Get time on system + CP 1 ; 1? + JR Z,SHOTOS2 ; Yes, leave display as 'minute' + CALL ILPRTB + DB 's',0 ; Else make it plural + +SHOTOS2:LD A,(MODE) + OR A + RET NZ + CALL ILPRT + DB CR,LF,0 + RET + +; +; Transfer BYE's RTCBUF contents to internal storage +; +GETTIME:LD A,(RTC) ; User installed clock routines? + OR A + JP NZ,RTCTIM ; Yes, go do it + LD HL,(RTCBUF) + + LD A,(HL) ; 00: + CALL BCDBIN ; Convert to binary + LD (HOUR),A ; Save + + CALL GETTIM3 ; :00 + LD (MINUTE),A ; Save + + INC HL ; Skip seconds + INC HL ; Skip '19'nn + CALL GETTIM3 ; YY + LD (YEAR),A ; Save + + CALL GETTIM3 ; MM + LD (MONTH),A ; Save + + CALL GETTIM3 ; DD + LD (DAY),A ; Save + RET ; And return + +GETTIM3:INC HL ; Increment to next RTC byte value + LD A,(HL) ; Get it + JP BCDBIN ; Return with binary value in A + +; +; Add the time of the last upload/download to BYE's time on system byte +; +ADDTON: LD A,(TIMEON) ; Using TIMEON? + OR A + RET Z + + CALL BYECHK ; If so, see if BYE is running + OR A ; 0 if no clock, or 0 if no BYE. + LD HL,TON ; Prepare for internal RTC + JR Z,ADDTN1 + + LD HL,(RTCBUF) ; Get RTC buffer address + LD DE,7 ; Get offset to TOS word + ADD HL,DE ; Add offset, HL contains TON address + +ADDTN1: PUSH HL ; Save it + LD HL,(RECDNO) + LD (RCNT),HL + CALL XTIM ; Calculate transfer time + POP HL ; Restore TON address + LD A,(HL) ; Get time on in A + LD B,A ; Save it + LD A,(MODE) ; Get current transfer mode + CP 'S' ; Is this a download? + JR Z,ADDTN2 ; Yes, subtract download time + LD A,(CREDIT) ; Else crediting upload time? + OR A + RET Z ; No, skip this + LD A,B ; Else get time on system back + SUB C ; Subtract upload time + LD (HL),A ; Store it + RET + +ADDTN2: LD A,B + INC A ; Bump it one + ADD A,C ; Add transfer time + LD (HL),A ; Put it back for BYE + RET + +; +;-------------------------------------------------------------------------; +; A v a i l a b l e U p l o a d S p a c e | +;-------------------------------------------------------------------------; +; +; This routine is called with the 'F' option from both CP/M (with 'ZMD F') +; or from The HELP Guide routines. First determine where uploads are +; suppose to go. +; +SPACE: CALL RSTLCK ; Go reset WRTLOC if needed + CALL WHLCHK ; WHEEL byte set? + JR NZ,SPACE1 ; Yes, give space for current drive/user + LD A,(ASKAREA) + OR A + JR NZ,SPACE2 + LD A,(SETAREA) + OR A + JR NZ,SPACE2 ; Yes + +SPACE1: LD A,(OLDDRV) ; Get currently logged drive + ADD A,'A' ; Make it ASCII + LD (DRV),A + LD (KDRV),A ; Store it for KSHOW + LD A,(OLDUSR) ; Get currently logged user + LD (USR),A ; Store it for KSHOW + +SPACE2: CALL WHLCHK + CALL Z,GETKIND ; Get upload area if ASKAREA + CALL ILPRTB + DB CR + DB ' Regular ',0 + CALL SPACE8 + CALL ILPRTB + DB CR,LF + DB ' Private ',0 + LD A,1 + LD (PRVSPC),A + CALL SPACE8 + JP EXIT ; Exit to CP/M + +; +; Displays the file descriptor/category when showing available upload space. +; +SPACE8: CALL WHLCHK + CALL Z,SHOCAT ; Show upload area descriptor, if supposed to + CALL ILPRTB + DB 'uploads received on ',0 + LD A,(PRVSPC) ; Want private area space? + OR A + JR NZ,SPACE9 ; Yes, do private stuff + LD A,(DRV) ; Get drive to receive regular upload + LD (KDRV),A ; Store it for KSHOW + CALL TYPE ; Output to modem + LD A,(USR) ; Get user area to receive regular upload + JR SPACE10 ; Go show free space + +SPACE9: LD A,(PRDRV) ; Get drive to receive private upload + LD (KDRV),A ; Store it for KSHOW + CALL TYPE ; Output to modem + LD A,(PRUSR) ; Get user area to receive private upload + +SPACE10:LD H,0 + LD L,A ; User area in L + CALL DECOUT ; Decimal output + CALL ILPRTB + DB ':',0 + LD A,(PRVSPC) ; Getting private info? + OR A + JR Z,SPACE11 ; No + LD A,(DRV) ; Else get regular drive + LD HL,PRDRV ; Point to private drive + CP (HL) ; Private same as regular drive? + RET Z ; Yes, don't report 'k' this time + +SPACE11:CALL ILPRTB + DB ' (',0 + LD A,(KDRV) ; Get upload drive + CALL KSHOW ; Show available space for drive + CALL ILPRTB + DB ')',0 + RET + +; +;-------------------------------------------------------------------------; +; R u n t i m e H e l p G u i d e | +;-------------------------------------------------------------------------; +; +; Either 'ZMD' was entered by itself from CP/M, or an invalid option +; given. +; +HELP: CALL ILPRTB + DB CR,LF,' mode drive/user' + DB CR,LF,' / /' + DB CR,LF,'Usage: ZMD SK {du:} ' + DB CR,LF,' / /' + DB CR,LF,' protocol filename' + DB CR,LF + DB CR,LF,'Mode: Protocol:' + DB CR,LF,' S - Send file from BBS ' + DB 'X - Xmodem 128 byte blocks (CRC)' + DB CR,LF,' SP - Send from private area ' + DB 'C - Xmodem 128 byte blocks (Checksum)' + DB CR,LF,' A - Send ARK/ARC/LBR member ' + DB 'K - Ymodem 1024 byte blocks (CRC only)' + DB CR,LF,' R - Receive file from YOU' + DB CR,LF,' RP - Receive in private area',0 + + + CALL ILPRTB + DB CR,LF,0 + LD A,(MSGFIL) + OR A + JR Z,HELP1 + CALL ILPRTB + DB ' RM - Receive preformatted message base upload',0 + +HELP1: CALL ILPRTB + DB CR,LF,0 + CALL WHLCHK + JR Z,HELP2 + CALL ILPRTB + DB ' RW - Receive without description(s)',0 + +HELP2: CALL ILPRTB + DB CR,LF,' F - Displays available upload space' + DB CR,LF + DB CR,LF + DB CR,LF + DB '--SPACE BAR displays specific examples--',0 + + CALL INPUT + CP ' ' + JP NZ,EXIT + + LD HL,ZMDNAM + CALL PRINTV + CALL ILPRTB + DB 'Usage examples:' + DB CR,LF + DB CR,LF,' ZMD S filename.ext ' + DB 'Send single file (Automatic detect)' + DB CR,LF,' ZMD S B4:filename.ext ' + DB 'Send single file (Automatic detect)' + DB CR,LF,' ZMD SK filename.ext ' + DB 'Send single file (Ymodem 1k)' + DB CR,LF,' ZMD S filename.* ' + DB 'Send from current d/u (Ymodem 1k Batch)' + DB CR,LF,' ZMD S D1:*.* B9:*.doc ' + DB 'Send from multiple d/u (Ymodem 1k Batch)' + DB CR,LF,' ZMD A librnam lbrmber.ext ' + DB 'Send ARK/ARC/LBR member (Automatic detect)' + DB CR,LF,' ZMD AK librnam lbrmber.ext ' + DB 'Send ARK/ARC/LBR member (Ymodem 1k)' + DB CR,LF + DB CR,LF,' ZMD R filename.ext ' + DB 'Receive single file (Automatic detect)' + DB CR,LF,' ZMD R ' + DB 'Receive multiple files (Ymodem 1k Batch)' + DB CR,LF,' ZMD RPC filename.ext ' + DB 'Receive to private area (Checksum)' + DB CR,LF,LF + DB 'Protocol may be omitted for automatic protocol detection.' + DB CR,LF + DB 'Ymodem 1k Batch is enabled upon detection of wildcards or' + DB ' multiple' + DB CR,LF + DB ' filenames on command line (can also be forced with' + DB ' ''SB'' mode).',0 + JP EXIT + + +ABRTMSG:CALL ILPRTB + DB CR + DB '-- ZMD Aborted',0 + RET + +NOACC: CALL SENDBEL ; Send a bell out modem only + CALL ERXIT + DB CR,LF + DB '-- Restricted Function - Access Denied','$' + +ZEROLN: CALL ERXIT + DB CR,LF + DB '-- File empty - ZMD aborted','$' + +NOFILE: CALL ERXIT + DB CR + DB '-- No matching filename(s) found','$' + +NOIO: XOR A + LD (RTC),A + LD (TIMEON),A + LD (DSPTOS),A + LD (CLOCK),A + CALL ERXIT + DB BELL + DB '-- Modem I/O unavailable - Aborting','$' + +TOOSLOW:CALL ERXIT + DB CR,LF,LF + DB '-- YMODEM 1k/BATCH not valid - Modem speed too slow','$' + + +TOTMSG: CALL ILPRTL + DB ' - Timeout, no character received',CR,LF,0 + RET + +DLRDY: CALL ILPRT + DB CR,LF + DB 'Your file(s) now ready to download',0 + CALL CONT6 + RET + +WAITMSG:CALL ILPRTL + DB ' -- Waiting --' + DB CR,0 + RET + +; +;------------------------------- +; File type restriction storage +;------------------------------- +; +; Don't allow ___ (If ZCPR is YES) +; \ +SYSCHK: DB 'SYS' +NDRCHK: DB 'NDR' +RCPCHK: DB 'RCP' + +; +; If receiving __ change it to __ (If NOCOMR is YES) +; \ \ +COMCHG: DB 'COM', 'OBJ' +PRLCHG: DB 'PRL', 'OBP' + +; +; If the library extraction flag (LBRARC) is set and an unsuccessful open with +; the default filetype occurs, the following file types are copied to FCB+9 +; and the open attempt is repeated. +; +ARCNAM: DB 'ARC' ; Copied to FCB+9 +LBRNAM: DB 'LBR' ; Copied to FCB+9 +ARKNAM: DB 'ARK' ; Copied to FCB+9 + +; +;--------------------- +; LOGCALL allocations +;--------------------- +; +DEFDSK: DB 0 ; Disk for open stored here +DEFUSR: DB 0 ; User for open stored here +CLRPTR: DW LOGBUF +LOGPTR: DW DBUF +LOGCNT: DB 0 +LOGK: DB 'k ' +DUSAVE: DB 0,0,0,0 ; Buffer for drive/user + +; +;------------------ +; Time allocations +;------------------ +; +MAXTOS: DB 0 ; Maximum time left on system +RTCBUF: DW 0 ; RTCBUF address +TLOS: DB 0 ; Current time left on system +TON: DB 0 ; Current time on system + +; +XTABLE: DW 5, 13, 19, 25, 30, 48, 85, 141, 210, 280, 0 +KTABLE: DW 5, 14, 21, 27, 32, 53, 101, 190, 330, 525, 0 +XECTBL: DB 192, 74, 51, 38, 32, 20, 11, 8, 5, 3, 0 +KECTBL: DB 192, 69, 46, 36, 30, 18, 10, 5, 3, 2, 0 + +; +;-------------------- +; Batch mode storage +;-------------------- +; +BGNMS: DW 0 ; Start address of filenames in TBUFF +LIST: DW DBUF ; Filename storage in send batch mode +LISTPOS:DW 0 ; Next position to store matching filename +LISTEND:DW 0 ; Address of last matching filename +LISTI: DW 0 ; Pointer 1 for two-dimensional bubble sort +LISTJ: DW 0 ; Pointer 2 for two-dimensional bubble sort +FILEK: DW 0 ; Total kilobytes of files found (send batch) +FCBBUF: DS 21 ; Batch filename from command line +FSTFLG: DB 0 ; Set to 1 when command line scan done +NAMECT: DB 0 ; # of names on command line +NBSAVE: DW 0 ; Start address in NAMBUF for next file +SBSHOW: DB 0 ; Set shows partial stat display in batch +SHOCNT: DB 0 ; Counter to show files left +TOTREC: DW 0 ; Total records to be sent + +; +;------------------------ +; Temporary storage area +;------------------------ +; +ACKCHK: DB 0 ; Lets batch header user GTACK routine +AFBYTE: DB 0 ; Access flags byte storage +CHKEOT: DB 0 ; Prevents locking up after an EOT +COMMA: DB 0 ; Field counter for logcal +CRCFLG: DB 1 ; For sending checksum rather than CRC +EOFLG: DB 0 ; EOF (End of file) flag +EOTFLG: DB 0 ; EOT (End of transmission) status flag +ERRCNT: DB 0 ; Error count +FRSTIM: DB 0 ; Turned on after first 'SOH' received +GOTONE: DB 0 ; Prevents asking for a description +KFLG: DB 1 ; For sending 1k blocks (Defaults to 1k) +PRVSPC: DB 0 ; Shows in private display in SPACE: if set +RCVCNT: DB 0 ; Record number received +RCVTRY: DB 0 ; Keeps track of number of attempts +RCVDRV: DB 0 ; Requested drive number +RCVUSR: DB 0 ; Requested user number + +ACCERR: DW 0 ; No 'ACK' error count for 1k ratio +HDRADR: DW 0 ; Current location in batch header block +RCNT: DW 0 ; Record count +RECDNO: DW 0 ; Current record number +RCDCNT: DW 0 ; Used in sending the record header +RECPTR: DW DBUF +RECNBF: DW 0 ; Number of records in the buffer +SAVEHL: DW 0 ; Saves TBUF command line address +XFRMIN: DW 0 ; Transfer time in mins for TIMEON + +; + END ; 'Almost'... + \ No newline at end of file diff --git a/Source/Apps/ZMD/zmdel.z80 b/Source/Apps/ZMD/zmdel.z80 new file mode 100644 index 00000000..81113f46 --- /dev/null +++ b/Source/Apps/ZMD/zmdel.z80 @@ -0,0 +1,264 @@ +; + + TITLE ZMDEL.Z80 - 09/29/88 - ZMD Sysop Transfer Log Purge Utility +; Copyrighted (c) 1987, 1988 +; Robert W. Kramer III + + PAGE +;- -; +; Update History ; +; ; +; Date Release Comments ; +; -------- ------- ---------------------------------------------- ; +; ; +; 09/29/88 v1.50 - No change(s) made to this file ; +; 03/18/88 v1.49 - No change(s) made to this file ; +; 03/13/88 v1.48 - Had a small problem with TPA fix which has been ; +; corrected. CHKTPA was calculating the total ; +; number of bytes available for DBUF, but wasn't ; +; clearing register L (forcing an even amount of ; +; sectors before initializing OUTSIZ buffer limit ; +; comparison word). This may have introduced ; +; minimal garbage to your FOR file if your FOR ; +; file is large enough to fill available TPA with ; +; ZMD, ZFORS or to the log file if running ZMDEL. ; +; - Rewrote OUTCHR routine in ZMDSUBS. ; +; - Redefined buffer table at end of programs. STACK; +; and filename buffers now EQUated with offsets ; +; from the last switch/toggle in program instead ; +; of with DS directive. ; +; - Some systems which do NOT have an interrupt ; +; driven keyboard may have noticed problems when ; +; an invalid key was entered in the ZNEWP, ZFORP ; +; and ZMDEL programs. In ZNEWP and ZFORP, if a ; +; CR was entered to pause the output, output was ; +; limited to one line at a time per key pressed. ; +; If an invalid key was hit, output would have ; +; remained in a paused state until one of the ; +; abort keys were pressed. This was difficult to ; +; find since my keyboard is interrupt driven and ; +; I could not duplicate the problem on my own ; +; system. ; +; 02/25/88 v1.47 - Fixed line count display routine. Number of ; +; lines read was being improperly decremented ; +; before displaying. Same fix included removing ; +; code that added an extra CR,LF to the beginning ; +; of the file. Count is now right. ; +; 01/27/88 v1.46 - Some changes were made to the ZMDSUBS file that ; +; are not directly related to this file ; +; 01/17/88 v1.45 - First public release ; +; 11/20/87 v1.00 - Initial version ; +;- -; + +;-------------------------------------------------------------------------; +; EXTERNAL Declarations: | +;-------------------------------------------------------------------------; + + + EXTRN CKABRT,CLEARIT,CLRLIN,DBUF,DECOUT,ERXIT,EXIT,ILPRTB + EXTRN INPUT,NOFILE,NOLOG,NOROOM,OLDDRV,OLDUSR,OUTCHR,PRINTV + EXTRN RECAR1,RECDR1,RENFCB,RERROR,RSDMA,SHONM4,STACK,STDMA + EXTRN TDONE,TYPE,UCASE + +; +;-------------------------------------------------------------------------; +; Program Starts Here | +;-------------------------------------------------------------------------; + + + .Z80 + ASEG + ORG 100H ; Program starts + JP BEGIN ; Jump around configuration table + INCLUDE ZMDHDR.Z80 ; Include the ZMD header overlay + .REQUEST ZMDSUBS ; Include the ZMD subroutines + +; +; +; Save CP/M stack, initialize new one for this program +; +BEGIN: LD (STACK),SP ; Save return address to CCP + LD SP,STACK + +; +; Save current drive/user +; + LD A,255 ; Get user function + CALL RECAR1 + LD (OLDUSR),A ; Save user area for later + LD C,CURDRV ; Get current drive function + CALL BDOS + LD (OLDDRV),A ; Save drive for later + +; +; Tell em who we are and ask if they want to continue +; + LD HL,PRGUTL + CALL PRINTV + + LD A,(LOGCAL) ; Log file enabled? + OR A + JP Z,NOLOG ; No, then don't run program + + CALL ILPRTB + DB 'Purge all except "R" entries from log? ',0 + LD A,0 + LD (DESWAIT),A ; Disable sleepy caller timout + CALL INPUT + CALL UCASE + CP 'Y' ; Continue? + JP NZ,EXIT ; No, exit to CP/M + CALL CLRLIN + +; +; Now log into drive/user area of ZMD.LOG +; + LD A,(LOGUSR) ; Get user area to find ZMD.LOG file + CALL RECAR1 + LD A,(LOGDRV) ; Get drive to find ZMD.LOG file + CALL RECDR1 + +; +; Open the 'ZMD .LOG' file +; + LD DE,LOGNAM ; Point to LOG filename + LD HL,FILE ; Destination is internal FCB1 + CALL RENFCB ; Initialize FCB + + LD DE,FILE ; Point to ZMD.LOG FCB + LD C,OPEN ; Open file + CALL BDOS + INC A ; Does file exist? + LD HL,LOGNAM ; Point to log filename + JP Z,NOFILE ; No, inform user and abort to CP/M + +; +; Open the ' .$$$' file +; + LD DE,TEMPFIL ; Point to temporary filename + LD HL,DEST ; Destination is internal FCB2 + CALL RENFCB ; Initialize FCB + + LD HL,FILE ; Point to default FCB + LD DE,DEST ; + LD BC,9 ; Move drive and filename bytes + LDIR + + LD C,DELETE ; Delete possible '$$$' temporary file + LD DE,DEST + CALL BDOS + + LD C,MAKE ; And make new one for this session + LD DE,DEST + CALL BDOS + INC A ; Successful create? + JP Z,NOROOM ; No, inform user and exit to CP/M + + CALL ILPRTB ; Warn them of the wait + DB CR + DB 'Cleaning ',0 + LD HL,LOGNAM + CALL SHONM4 + +RDRECD: CALL RSDMA ; Reset DMA + LD DE,FILE ; Read a record from ZMD.LOG + LD C,READ + CALL BDOS + OR A ; Read ok? + JP NZ,RERROR ; No, inform user and exit + LD HL,TBUF ; Point to first character in read buffer + +GETBYT: LD A,(HL) ; Get a byte from read buffer + AND 7FH ; Strip parity bit + CP 7FH ; Del (Rubout) + JP Z,NEXT ; Yes, ignore it + CP EOF ; End of file marker + JP Z,TDONE ; Transfer done. Close and exit + LD B,A ; Save character + CP LF ; Was it a line feed? + JP Z,NEWLIN ; Yes toggle line counters, start new line + +; +; LOGMODE is set to 0 everytime a line feed is encountered. If LOGMODE is +; is 0 at this point, the current byte is anylized as this log entry's +; mode of transfer. Only entries made in the 'R' receive mode are written +; to the new ZMD.$$$ file. Encountering an 'R' when LOGMODE is 0 causes +; KEEPER to be set non zero +; + LD A,(LOGMODE) ; Start of new line? + OR A + JP NZ,NXTBYT ; No, then we don't care what this byte is + INC A + LD (LOGMODE),A ; Else show we are not on LOGMODE byte anymore + LD A,B ; Get the character back + CP 'R' ; Is this an upload entry? + LD (KEEPER),A ; Indicate saving byte just in case + JP Z,NXTBYT ; Yes, leave KEEPER non-zero so it's written + XOR A + LD (KEEPER),A ; Else 0 keep flag (0=don't keep this entry) + +NXTBYT: LD A,(KEEPER) ; Keeping this log entry? + OR A + JP Z,NEXT ; No, get next byte + +; +; Place current byte in buffer and write to disk if buffer full +; +WRTBYT: LD A,B + CALL OUTCHR + +NEXT: INC L ; Done with sector? + JP Z,RDRECD ; Yes, get another sector + JP GETBYT ; Else get another byte + +NEWLIN: XOR A ; Zero accumulator + LD (LOGMODE),A ; Show current byte is log mode byte + LD IY,(LNSREAD) ; Get lines read counter + INC IY ; Add one more + LD (LNSREAD),IY + LD A,1 ; Disable page pauses + CALL CKABRT ; User abort? + LD A,(KEEPER) ; Are we keeping this entry? + OR A + JP Z,NEXT ; No, read another byte + LD IY,(LNSWROT) ; Get lines written count + INC IY ; Add one more + LD (LNSWROT),IY + JP WRTBYT + +; +DONE:: CALL CLRLIN + CALL ILPRTB + DB CR,'Finished.' + DB CR,LF + DB ' ',0 + + LD HL,(LNSREAD) + CALL DECOUT + CALL ILPRTB + DB TAB + DB 'original lines',CR,LF + DB ' ',0 + + LD HL,(LNSWROT) + CALL DECOUT + CALL ILPRTB + DB TAB + DB 'retained lines ',0 + JP EXIT + +; +; These next are dummy routines to satisfy external ZMDSUBS requests. +; They do nothing, but leave alone. +; +TIME:: RET + + +KEEPER: DB 0 +LOGMODE:DB 0 +LNSREAD:DW 0 +LNSWROT:DW 0 + + + END + \ No newline at end of file diff --git a/Source/Apps/ZMD/zmdhb.z80 b/Source/Apps/ZMD/zmdhb.z80 new file mode 100644 index 00000000..9d4cddd5 --- /dev/null +++ b/Source/Apps/ZMD/zmdhb.z80 @@ -0,0 +1,916 @@ +;======================================================================= +; +; XMHB.Z80 - XMODEM12 PATCH FILE FOR ROMWBW HBIOS +; +; Wayne Warthen - wwarthen@gmail.com +; +; 2020-05-23 WBW Rewrite for HBIOS FastPath(tm) +; +;======================================================================= +; + ASEG +; +BASE EQU 100H ; Start of CP/M normal program area +FCB EQU 005CH ; System FCB +; +BDOS EQU 0005H ; BDOS function dispatch vector +; +;======================================================================= +; +; Jump table: The jump table must be in exactly the same sequence as the +; one in ZMD. +; + ORG 0580H ; ZMD I/O overlay address +; + JP CONOUT ; must be 00000h if not used, see below + JP MINIT ; initialization routine (if needed) + JP UNINIT ; undo whatever 'MINIT' did (or return) +JPTBL: + JP SENDR ; send character (via pop psw) + JP CAROK ; test for carrier + ;JP MDIN ; receive data byte (not present in ZMD) + JP GETCHR ; get character from modem + JP RCVRDY ; check receive ready + JP SNDRDY ; check send ready + JP SPEED ; get speed value for file transfer time + JP EXTRA1 ; extra for custom routine + JP EXTRA2 ; extra for custom routine + JP EXTRA3 ; extra for custom routine +; + ORG 0600H +; +; ZMD expects to find a byte with the baud rate code somewhere. For +; lack of a better location, we put it at 0600H. The highest baud +; rate code understood by ZMD is 9 which means 19200 baud. We just +; use that since we are almost certainly running faster than this. +; +BAUDCD DB 9 ; Baud rate storage +; +;----------------------------------------------------------------------- +; +; Output character to console +; +; This is intended to write file transfer progress to a RCPM +; BBS console. It is stubbed out because it will write to the +; active file transfer port when not using an RCPM. +; +CONOUT: + RET +; +;----------------------------------------------------------------------- +; +; Initialize modem +; +; This procedure has been usurped to dynamically detect the type +; of system we are running on and install the *real* jump table +; entries as appropriate. +; +MINIT: +; +; Scan the command line for a number. If found, this is used +; as the HBIOS unit number. +; + LD HL,FCB+1 ; Start after drive byte + LD B,8 ; Check 8 characters +MINIT1: + LD A,(HL) ; Get character + CP '0' ; Start of numerics + JR C,MINIT2 ; If below '0', continue scan + CP '9' ; Last numeric + JR NC,MINIT2 ; If above '9', continue scan + SUB '0' ; Got it, convert to binary + LD (UNIT),A ; Save it + LD A,' ' ; Space character to accum + LD (HL),A ; Remove numberic from FCB + JR MINIT3 ; Proceed with initialization + +MINIT2: + INC HL + DJNZ MINIT1 + +MINIT3: + ; Announce + LD DE,TAG ; Tagline + LD C,9 ; BDOS string display function + CALL BDOS ; Do it +; + ; Identify BIOS (RomWBW HBIOS or UNA UBIOS) + CALL IDBIO ; 1=HBIOS, 2=UBIOS + LD (BIOID),A ; Save it + DEC A ; Test for HBIOS + JR Z,MINIT_HB ; Do HBIOS setup + DEC A ; Test for UBIOS + JR Z,MINIT_UB ; Do UBIOS setup +; + ; Neither UNA nor RomWBW + LD DE,ERR_BIO ; BIOS error message + JP FAIL ; Print msg and bail out +; +MINIT_HB: + ; Display RomWBW notification string + LD DE,HB_TAG ; BIOS notification string + LD C,9 ; BDOS string display function + CALL BDOS ; Do it +; + ; Get CPU speed from RomWBW HBIOS and save it + LD B,0F8H ; HBIOS SYSGET function 0xF8 + LD C,0F0H ; CPUINFO subfunction 0xF0 + RST 08 ; Do it, L := CPU speed in MHz + LD A,L ; Move it to A + LD (CPUSPD),A ; Save it +; + ; Get HBIOS bank id + LD BC,0F8F2H ; HBIOS SYSGET, Bank Info + RST 08 ; do it + JP NZ,APIERR ; handle API error + LD A,D ; BIOS bank id to A + LD (BIOSBID),A ; save it +; + LD A,(UNIT) ; get current unit specified + CP 0FFH ; check for undefined + JR NZ,MINIT_HB1 ; if not undefined, go ahead +; + ; Lookup current console to use as default for transfer + LD B,0FAH ; HBIOS PEEK + LD A,(BIOSBID) ; get BIOS bank id + LD D,A ; ... and put in D + LD HL,100H + 12H ; HCB console unit address + RST 08 ; E := value + LD A,E ; put in A + LD (UNIT),A ; replace UNIT with console UNIT +; +MINIT_HB1: + ; Get HBIOS device type + LD B,06H ; HBIOS DEVICE function 0x06 + LD A,(UNIT) ; Get xfer unit + LD C,A ; Put in C + RST 08 ; Do it, D=device type + LD A,D ; Put result in A + CP 00H ; UART? + JP Z,MINIT_HB2 ; If so, do UART H/W init + CP 80H ; USB-FIFO? + JP Z,UF_INIT ; If so, do USB-FIFO H/W init + JP HB_INIT ; Otherwise, use BIOS I/O +; +MINIT_HB2: + ; Handle UART driver special. If receive interrupts active, + ; we must use BIOS I/O. Otherwise, direct UART I/O is best + LD B,06H ; Serial device function + LD A,(UNIT) ; Get xfer unit + LD C,A ; Put in C + RST 08 ; Do it, H = UART TYPE + BIT 7,H ; Check for interrupt driven receive + JP Z,UA_INIT ; If not, do direct UART I/O + JP HB_INIT ; else use BIOS I/O +; +MINIT_UB: + ; Display UNA notification string + LD DE,UB_TAG ; BIOS notification string + LD C,9 ; BDOS string display function + CALL BDOS ; Do it +; + ; Get CPU speed from UNA and save it + LD C,0F8H ; UNA BIOS Get PHI function + RST 08 ; Returns speed in Hz in DE:HL + LD B,4 ; Divide MHz in DE:HL by 100000H +MINIT_UB1: + SRL D ; ... to get approx CPU speed in + RR E ; ...MHz. Throw away HL, and + DJNZ MINIT_UB1 ; ...right shift DE by 4. + INC E ; Fix up for value truncation + LD A,E ; Put in A + LD (CPUSPD),A ; Save it +; + JP UB_INIT ; UNA BIOS init +; +MINIT_RET: + PUSH HL ; Save HL (JP table adr) + + ; Display handler label + LD C,9 ; BDOS string display function + CALL BDOS ; Do it +; + ; Display port (unit number) + LD DE,COM_LBL ; Prefix + LD C,9 ; BDOS string display function + CALL BDOS ; Do it + LD A,(UNIT) ; Get unit number + ADD A,'0' ; Make displayable + LD E,A ; Put in E for display + LD C,2 ; BDOS console write char + CALL BDOS ; Do it +; + ; Newline + LD C,9 ; BDOS string display function + LD DE,CRLF ; Newline + CALL BDOS ; Do it +; + ; Copy real vectors into active jump table + POP HL ; Recover HL + LD DE,JPTBL ; Real jump table is destination + LD BC,7 * 3 ; Copy 7 3-byte entries + LDIR ; Do the copy +; + ; Return with CPU speed in A + LD A,(CPUSPD) ; A := CPU speed in MHz + LD HL,(RCVSCL) ; HL := receive scalar + RET ; and return +; +;----------------------------------------------------------------------- +; +; Uninitialize modem +; +UNINIT: + LD A,(BIOID) + CP 1 ; Is HBIOS? + JR Z,HUNINIT ; Handle HBIOS + CP 2 ; Is UBIOS? + JR Z,UUNINIT ; Handle UBIOS + RET ; Just return +; +HUNINIT: + ; HBIOS: Reset character device 0 + LD B,04H ; HBIOS CIOINIT function 0x04 + LD A,(UNIT) ; HBIOS serial unit number + LD C,A ; Put in C for func call + LD DE,-1 ; Reset w/ current settings + RST 08 ; Do it + RET ; not initialized, so no 'UN-INITIALIZE' +; +UUNINIT: + ; UBIOS: Reset character device 0 + LD C,10H ; UNA INIT function 0x10 + LD A,(UNIT) ; UBIOS serial unit number + LD B,A ; Put in B for func call + LD DE,-1 ; Reset w/ current settings + RST 08 ; Do it + RET ; not initialized, so no 'UN-INITIALIZE' +; +; Identify active BIOS. RomWBW HBIOS=1, UNA UBIOS=2, else 0 +; +IDBIO: +; + ; Check for UNA (UBIOS) + LD A,(0FFFDH) ; fixed location of UNA API vector + CP 0C3H ; jp instruction? + JR NZ,IDBIO1 ; if not, not UNA + LD HL,(0FFFEH) ; get jp address + LD A,(HL) ; get byte at target address + CP 0FDH ; first byte of UNA push ix instruction + JR NZ,IDBIO1 ; if not, not UNA + INC HL ; point to next byte + LD A,(HL) ; get next byte + CP 0E5H ; second byte of UNA push ix instruction + JR NZ,IDBIO1 ; if not, not UNA, check others + LD A,2 ; UNA BIOS id = 2 + RET ; and done +; +IDBIO1: + ; Check for RomWBW (HBIOS) + LD HL,(0FFFEH) ; HL := HBIOS ident location + LD A,'W' ; First byte of ident + CP (HL) ; Compare + JR NZ,IDBIO2 ; Not HBIOS + INC HL ; Next byte of ident + LD A,~'W' ; Second byte of ident + CP (HL) ; Compare + JR NZ,IDBIO2 ; Not HBIOS + LD A,1 ; HBIOS BIOS id = 1 + RET ; and done +; +IDBIO2: + ; No idea what this is + XOR A ; Setup return value of 0 + RET ; and done +; +HWERR: + ; Failed to identify target comm hardware + LD DE,ERR_HW ; Hardware error message + JP FAIL ; Print message and bail out +; +APIERR: + ; API returned unepected failure + LD DE,ERR_API ; API error message + JP FAIL ; Pprint message and bail out +; +FAIL: + ; DE has error string address + LD C,9 ; BDOS string display function + CALL BDOS ; Do it + JP 0 ; Bail out! +; +;----------------------------------------------------------------------- +; +; The following are all dummy routines that are unused because MINIT +; dynamically installs the real jump table. +; +SENDR: +CAROK: +MDIN: +GETCHR: +RCVRDY: +SNDRDY: +SPEED: +EXTRA1: +EXTRA2: +EXTRA3: + RET +; +BIOID DB 0 ; BIOS ID, 1=HBIOS, 2=UBIOS +CPUSPD DB 10 ; CPU speed in MHz +RCVSCL DW 6600 ; RECV loop timeout scalar +UNIT DB 0FFH ; BIOS serial device unit number +BIOSBID DB 00H ; BIOS bank id +; +TAG DB "RomWBW, 30-May-2020$" +; +HB_LBL DB ", HBIOS FastPath$" +UB_LBL DB ", UNA UBIOS$" +UA_LBL DB ", UART$" +UF_LBL DB ", USB-FIFO$" +COM_LBL DB " on COM$" +; +UB_TAG DB " [UNA]$" +HB_TAG DB " [WBW]$" +; +CRLF DB 13, 10, "$" +; +ERR_BIO DB 13, 10, 13, 10, "++ Unknown BIOS ++", 13, 10, "$" +ERR_HW DB 13, 10, 13, 10, "++ Unknown Hardware ++", 13, 10, "$" +ERR_API DB 13, 10, 13, 10, "++ BIOS API Error ++", 13, 10, "$" +; +;======================================================================= +;======================================================================= +; +; RomWBW HBIOS Interface +; +;======================================================================= +;======================================================================= +; +; Following jump table is dynamically patched over initial jump +; table at program startup. See MINIT above. Note that only a +; subset of the jump table is overlaid (SENDR to SPEED). +; +HB_JPTBL: + JP HB_SENDR ; send character (via pop psw) + JP HB_CAROK ; test for carrier + ;JP HB_MDIN ; receive data byte (not present in ZMD) + JP HB_GETCHR ; get character from modem + JP HB_RCVRDY ; check receive ready + JP HB_SNDRDY ; check send ready + JP HB_SPEED ; get speed value for file transfer time +; +;----------------------------------------------------------------------- +; +; HBIOS initialization +; +HB_INIT: + LD HL,2150 ; Smaller receive loop timeout scalar + LD (RCVSCL),HL ; ... to compensate for BIOS overhead +; + ; Patch SENDR w/ FastPath addresses + LD BC,0F801H ; Get CIO func/data adr + LD D,01H ; Func=CIO OUT + LD A,(UNIT) ; get desired char unit + LD E,A ; and put in E + RST 08 + JP NZ,APIERR ; handle API error + LD (HB_UDAT),DE ; Plug in data adr + LD (HB_SCFN),HL ; Plug in func adr +; + ; Patch GETCHR/MDIN w/ FastPath addresses + LD BC,0F801H ; Get CIO func/data adr + LD D,00H ; Func=CIO IN + LD A,(UNIT) ; get desired char unit + LD E,A ; and put in E + RST 08 + JP NZ,APIERR ; handle API error + LD (HB_GCFN),HL ; Plug in func adr +; + ; Patch RCVRDY w/ FastPath addresses + LD BC,0F801H ; Get CIO func/data adr + LD D,02H ; Func=CIO IST + LD A,(UNIT) ; get desired char unit + LD E,A ; and put in E + RST 08 + JP NZ,APIERR ; handle API error + LD (HB_RRFN),HL ; Plug in func adr +; + ; Patch SNDRDY w/ FastPath addresses + LD BC,0F801H ; Get CIO func/data adr + LD D,03H ; Func=CIO OST + LD A,(UNIT) ; get desired char unit + LD E,A ; and put in E + RST 08 + JP NZ,APIERR ; handle API error + LD (HB_SRFN),HL ; Plug in func adr +; + ; Claim 1 MHz CPU to offset overhead of HBIOS + LD A,1 + LD (CPUSPD),A ; Save it +; + LD HL,HB_JPTBL + LD DE,HB_LBL + JP MINIT_RET +; +;----------------------------------------------------------------------- +; +; Send character on top of stack +; +HB_SENDR: + POP AF ; get character to send from stack + PUSH BC + PUSH DE + PUSH HL + LD E,A ; character to E + LD IY,(HB_UDAT) + LD A,(BIOSBID) ; call into HBIOS bank + LD IX,0000H +HB_SCFN EQU $-2 + CALL 0FFF9H ; HBIOS bank call + POP HL + POP DE + POP BC + RET +; +;----------------------------------------------------------------------- +; +; Test and report carrier status, Z set if carrier present +; +HB_CAROK: + XOR A ; not used, always indicate present + RET +; +;----------------------------------------------------------------------- +; +; Get a character +; +; GETCHR must not block +; +HB_GETCHR: + CALL HB_RCVRDY + RET NZ + ; Fall thru if char ready +; +; MDIN can assume a character is ready +; +HB_MDIN: + PUSH BC + PUSH DE + PUSH HL + LD IY,(HB_UDAT) + LD A,(BIOSBID) ; call into HBIOS bank + LD IX,0000H +HB_GCFN EQU $-2 + CALL 0FFF9H ; HBIOS bank call + LD A,E ; byte received to A + POP HL + POP DE + POP BC + RET +; +;----------------------------------------------------------------------- +; +; Test for character ready to receive, Z = ready +; Error code returned in A register +; *** Error code does not seem to be used *** +; +HB_RCVRDY: + PUSH BC + PUSH DE + PUSH HL + LD IY,(HB_UDAT) + LD A,(BIOSBID) ; call into HBIOS bank + LD IX,0000H +HB_RRFN EQU $-2 + CALL 0FFF9H ; HBIOS bank call + SUB 1 ; CF set IFF zero + RL A ; CF to bit 0 of A + AND 01H ; set Z flag as needed + LD A,0 ; report no line errors + POP HL + POP DE + POP BC + RET +; +;----------------------------------------------------------------------- +; +; Test for ready to send a character, Z = ready +; +HB_SNDRDY: + PUSH BC + PUSH DE + PUSH HL + LD IY,(HB_UDAT) + LD A,(BIOSBID) ; call into HBIOS bank + LD IX,0000H +HB_SRFN EQU $-2 + CALL 0FFF9H ; HBIOS bank call + SUB 1 ; CF set IFF zero + RL A ; CF to bit 0 of A + AND 01H ; set Z flag as needed + POP HL + POP DE + POP BC + RET +; +;----------------------------------------------------------------------- +; +; Report baud rate (index into SPTBL returned in register A) +; +HB_SPEED: + LD A,8 ; arbitrarily return 9600 baud + RET +; +; +; +HB_UDAT DW 0000H ; Unit data address +; +; +;======================================================================= +;======================================================================= +; +; UNA UBIOS Interface +; +;======================================================================= +;======================================================================= +; +; Following jump table is dynamically patched over initial jump +; table at program startup. See MINIT above. Note that only a +; subset of the jump table is overlaid (SENDR to SPEED). +; +UB_JPTBL: + JP UB_SENDR ; send character (via pop psw) + JP UB_CAROK ; test for carrier + ;JP UB_MDIN ; receive data byte (not present in ZMD) + JP UB_GETCHR ; get character from modem + JP UB_RCVRDY ; check receive ready + JP UB_SNDRDY ; check send ready + JP UB_SPEED ; get speed value for file transfer time +; +;----------------------------------------------------------------------- +; +; UBIOS initialization +; +UB_INIT: +; +; TODO: +; - TEST!!! +; - ADJUST RCVSCL? +; + LD HL,3000 ; Smaller receive loop timeout scalar + LD (RCVSCL),HL ; ... to compensate for BIOS overhead +; + ; Claim 1 MHz CPU to offset overhead of HBIOS + LD A,1 + LD (CPUSPD),A ; Save it +; + LD HL,UB_JPTBL + LD DE,UB_LBL + JP MINIT_RET +; +;----------------------------------------------------------------------- +; +; Send character on top of stack +; +UB_SENDR: + POP AF ; get character to send from stack + PUSH BC + PUSH DE + PUSH HL + LD BC,0012H ; unit 0, func 12h (write char) + LD E,A ; character to E + RST 08 + POP HL + POP DE + POP BC + RET +; +;----------------------------------------------------------------------- +; +; Test and report carrier status, Z set if carrier present +; +UB_CAROK: + XOR A ; not used, always indicate present + RET +; +;----------------------------------------------------------------------- +; +; Get a character +; +; GETCHR must not block +; +UB_GETCHR: + CALL UB_RCVRDY + RET NZ + ; Fall thru if char ready +; +; MDIN can assume a character is ready +; +UB_MDIN: + PUSH BC + PUSH DE + PUSH HL + LD BC,0011H ; unit 0, func 12h (write char) + RST 08 + LD A,E ; byte received to A + POP HL + POP DE + POP BC + RET +; +;----------------------------------------------------------------------- +; +; Test for character ready to receive, Z = ready +; Error code returned in A register +; *** Error code does not seem to be used *** +; +UB_RCVRDY: + PUSH BC + PUSH DE + PUSH HL + LD BC,0013H ; unit 0, func 13h (input stat) + RST 08 + XOR A ; zero accum ; 4 + CP E ; CF means not zero ; 4 + CCF ; CF means zero ; 4 + RLA ; ZF means not zero ; 4 + LD A,0 ; report no line errors + POP HL + POP DE + POP BC + RET +; +;----------------------------------------------------------------------- +; +; Test for ready to send a character, Z = ready +; +UB_SNDRDY: + PUSH BC + PUSH DE + PUSH HL + LD BC,0014H ; unit 0, func 14h (output stat) + RST 08 + XOR A ; zero accum ; 4 + CP E ; CF means not zero ; 4 + CCF ; CF means zero ; 4 + RLA ; ZF means not zero ; 4 + POP HL + POP DE + POP BC + RET +; +;----------------------------------------------------------------------- +; +; Report baud rate (index into SPTBL returned in register A) +; +UB_SPEED: + LD A,8 ; arbitrarily return 9600 baud + RET +; +;======================================================================= +;======================================================================= +; +; 8250-like UART +; +;======================================================================= +;======================================================================= +; +; UART constants +; +UA_SNDB EQU 20H ; bit to test for send ready +UA_SNDR EQU 20H ; value when ready to send +UA_RCVB EQU 01H ; bit to test for receive ready +UA_RCVR EQU 01H ; value when ready to receive +UA_PARE EQU 04H ; bit for parity error +UA_OVRE EQU 02H ; bit for overrun error +UA_FRME EQU 08H ; bit for framing error +UA_ERRS EQU UA_FRME | UA_OVRE | UA_PARE +; +; Following jump table is dynamically patched into real jump +; table at program startup. See MINIT above. Note that only a +; subset of the jump table is overlaid (SENDR to SPEED). +; +UA_JPTBL: + JP UA_SENDR ; send character (via pop psw) + JP UA_CAROK ; test for carrier + ;JP UA_MDIN ; receive data byte (not present in ZMD) + JP UA_GETCHR ; get character from modem + JP UA_RCVRDY ; check receive ready + JP UA_SNDRDY ; check send ready + JP UA_SPEED ; get speed value for file transfer time +; +;----------------------------------------------------------------------- +; +; UART initialization +; +UA_INIT: + LD DE,13000 ; receive loop timeout scalar + LD (RCVSCL),DE ; ... for UART RCVRDY timing +; + LD A,L ; get base I/O port address + LD (UA_SCP),A ; set port value in SENDR + LD (UA_GCP),A ; set port value in GETCHR + ADD A,5 ; UART control port is 5 higher + LD (UA_RRP),A ; set port value in RCVRDY + LD (UA_SRP),A ; set port value in SNDRDY +; + LD HL,UA_JPTBL + LD DE,UA_LBL + JP MINIT_RET +; +;----------------------------------------------------------------------- +; +; Send character on top of stack +; +UA_SENDR: + POP AF ; get character to send from stack + OUT (0FFH),A ; send to port +UA_SCP EQU $-1 ; port value + RET +; +;----------------------------------------------------------------------- +; +; Test and report carrier status, Z set if carrier present +; +UA_CAROK: + XOR A ; not used, always indicate present + RET +; +;----------------------------------------------------------------------- +; +; Get a character +; +; GETCHR must not block +; +UA_GETCHR: + CALL UA_RCVRDY + RET NZ + ; Fall thru if char ready +; +; MDIN can assume a character is ready +; +UA_MDIN: + IN A,(0FFH) ; read character from port +UA_GCP EQU $-1 ; port value + RET +; +;----------------------------------------------------------------------- +; +; Test for character ready to receive, Z = ready +; Error code returned in A register +; *** Error code does not seem to be used *** +; +UA_RCVRDY: + IN A,(0FFH) ; get modem status +UA_RRP EQU $-1 ; port value + AND UA_RCVB ; isolate ready bit + CP UA_RCVR ; test it (set flags) + LD A,0 ; report no line errors +; + RET +; +;----------------------------------------------------------------------- +; +; Test for ready to send a character, Z = ready +; +UA_SNDRDY: + IN A,(0FFH) ; get status +UA_SRP EQU $-1 ; port value + AND UA_SNDB ; isolate transmit ready bit + CP UA_SNDR ; test for ready value + RET +; +;----------------------------------------------------------------------- +; +; Report baud rate (index into SPTBL returned in register A) +; +UA_SPEED: + LD A,8 ; arbitrarily return 9600 baud + RET +; +; +; +UA_BASE DB 00H ; UART base port I/O address +UA_CTLP DB 00H ; UART control port I/O address +; +; +;======================================================================= +;======================================================================= +; +; WILL SOWERBUTTS ECB USB-FIFO +; +;======================================================================= +;======================================================================= +; +UF_BASE EQU 0CH +UF_DATA EQU (UF_BASE+0) +UF_STATUS EQU (UF_BASE+1) +UF_SEND_IMM EQU (UF_BASE+2) +; +; Following jump table is dynamically patched over initial jump +; table at program startup. See MINIT above. Note that only a +; subset of the jump table is overlaid (SENDR to SPEED). +; +UF_JPTBL: + JP UF_SENDR ; send character (via pop psw) + JP UF_CAROK ; test for carrier + ;JP UF_MDIN ; receive data byte (not present in ZMD) + JP UF_GETCHR ; get character from modem + JP UF_RCVRDY ; check receive ready + JP UF_SNDRDY ; check send ready + JP UF_SPEED ; get speed value for file transfer time +; +;----------------------------------------------------------------------- +; +; USB-FIFO initialization +; +UF_INIT: + LD DE,12000 ; receive loop timeout scalar + LD (RCVSCL),DE ; ... for UART RCVRDY timing +; + LD A,L ; get base I/O port address (data port) + LD (UF_SCDP),A ; set data port in SENDR + LD (UF_GCDP),A ; set data port in GETCHR/MDIN + INC A ; bump to status port + LD (UF_RRSP),A ; set status port in RCVRDY + LD (UF_SRSP),A ; set status port in SNDRDY + INC A ; bump to send immediate port + LD (UF_SCIP),A ; set send immed port in SENDR +; + LD HL,UF_JPTBL + LD DE,UF_LBL + JP MINIT_RET +; +;----------------------------------------------------------------------- +; +; Send character on top of stack +; +UF_SENDR: + + POP AF ; get character to send from stack + OUT (0FFH),A ; write to fifo +UF_SCDP EQU $-1 ; data port + OUT (0FFH),A ; send immediate +UF_SCIP EQU $-1 ; send immediate port + RET +; +;----------------------------------------------------------------------- +; +; Test and report carrier status, Z set if carrier present +; +UF_CAROK: + XOR A ; not used, always indicate present + RET +; +;----------------------------------------------------------------------- +; +; Get a character (assume character ready has already been tested) +; +; GETCHR must not block +; +UF_GETCHR: + CALL UF_RCVRDY ; check for char ready + RET NZ ; return if not + ; Fall thru if char ready +; +; MDIN can assume a character is ready +; +UF_MDIN: + IN A,(0FFH) ; get char +UF_GCDP EQU $-1 ; data port + RET +; +;----------------------------------------------------------------------- +; +; Test for character ready to receive, Z = ready +; Error code returned in A register +; *** Error code does not seem to be used *** +; +UF_RCVRDY: + IN A,(0FFH) ; bit 7 = 0 if char avail, = 1 if no char. +UF_RRSP EQU $-1 ; status port + RLCA ; bit 0 = 0 if char avail, = 1 if no char. + AND 00000001B ; A = 0, ZF = 1 if no char, A = 1, ZF = 0 if char avail. + LD A,0 ; no errors + RET +; +;----------------------------------------------------------------------- +; +; Test for ready to send a character, Z = ready +; +UF_SNDRDY: + IN A,(0FFH) ; bit 0 = 0 if space avail, = 1 if full +UF_SRSP EQU $-1 ; status port + AND 00000001B ; A = 0, ZF = 1 if space avail, A = 1, ZF = 0 if full. + RET +; +;----------------------------------------------------------------------- +; +; Report baud rate (index into SPTBL returned in register A) +; +UF_SPEED: + LD A,8 ; arbitrarily return 9600 baud + RET +; + END diff --git a/Source/Apps/ZMD/zmdhdr.z80 b/Source/Apps/ZMD/zmdhdr.z80 new file mode 100644 index 00000000..f5c96da5 --- /dev/null +++ b/Source/Apps/ZMD/zmdhdr.z80 @@ -0,0 +1,637 @@ +; + + TITLE ZMDHDR.Z80 - 09/29/88 - ZMD Configuration Header +; Copyrighted (c) 1987, 1988 +; Robert W. Kramer III + + PAGE +;- -; +; Update History ; +; ; +; Date Release Comments ; +; -------- ------- ---------------------------------------------- ; +; ; +; 09/29/88 v1.50 - Changed file descriptor table. (Sorry, but as ; +; many times as I am required to reassemble the ; +; ZMD system, it saves alot of time for me). ; +; 03/18/88 v1.49 - No change(s) were made to this file ; +; 03/13/88 v1.48 - Added STDBUF switch to enable/disable automatic ; +; calculation of memory available for DBUF. If ; +; enabled, CHKTPA will set OUTSIZ with an even ; +; paged maximum memory comparison word according ; +; to the address contained at location 6 and 7. ; +; The value set as end of program during assembly ; +; is subtracted from it. If BYE is NOT running, ; +; an additional 806H is subtracted. The LSB of ; +; the result is discarded and the new even paged ; +; value is stored for later comparison as maximum ; +; Read/Write memory available for disk buffer ; +; operations. ; +; ; +; * * SPECIAL NOTE * * MOST ALL systems will benefit by enabling this ; +; feature. Some special CP/M systems which place ; +; buffers below location (6 and 7), may need to ; +; disable this feature in which case DBUF size ; +; will be set to 16k. This fix consumed 1 more ; +; byte in the switch/value table below. Previous ; +; configuration headers are incompatible with this; +; release. Additional program code is 9 bytes. ; +; (See explaination at STDBUF:) ; +; ; +; 02/25/88 v1.47 - No change(s) made to configuration file ; +; 01/27/88 v1.46 - No change(s) made to configuration file ; +; 01/17/88 v1.45 - First public release ; +; 11/19/87 v1.00 - Initial version ; +;- -; + +;-------------------------------------------------------------------------; +; Introduction | +;-------------------------------------------------------------------------; +; +; This document is a detailed listing of all the program option toggles +; and their functions within the ZMD environment which when used in +; conjunction with the source listings, install program and reference +; manual, provide all the necessary documentation to support program +; maintenance. + +; +;-------------------------------------------------------------------------; +; EQUates Header | +;-------------------------------------------------------------------------; + + + INCLUDE EQUATES.INS ; Include system constant definitions + +; +;-------------------------------------------------------------------------; +; PUBLIC Declarations: | +;-------------------------------------------------------------------------; + + + PUBLIC ACCMAP,ASKAREA,ASKIND,CLRSCRN,CLRSTR,DESWAIT,DRV + PUBLIC LOCOFF,MHZ,MINKSPD,MSPEED,PAGLEN,PRDRV,PRUSR,USR,WHEEL + PUBLIC WRAP,WRTLOC,CONOUT,MDINST,MDINP,MDOUTST,MDCARCK,MDOUTP + PUBLIC KNDTBL,TYPTBL,MAXTYP,DESCRIB,FORNAM,MSGDESC,DSTAMP + PUBLIC INCLDU,UNINIT,DRIVE,USER,LOGNAM,SETAREA,CLOCK,RTC + PUBLIC TIMEON,CREDIT,EDATE,DSPTOS,STDBUF + +; +;-------------------------------------------------------------------------; +; EXTERNAL Declarations: | +;-------------------------------------------------------------------------; + + + EXTRN MONTH,DAY,YEAR,HOUR,MINUTE,DEST,FILE,ZMDNAM,INSNAM + EXTRN MAPNAM,PRGUTL,PUBFOR,PUBNEW,SYSFOR,PUBFOR,INSNAM + EXTRN SYSNEW,FCBCLR,FCBLOG,TEMPFIL,BCDBIN,TIMBUF,DUD,DUU + +; +;-------------------------------------------------------------------------; +; Program configuration starts here | +;-------------------------------------------------------------------------; +; +; Options that are most often changed are marked with ';*' at the start of +; the comment line for that option. Simple systems not using time clocks, +; user logs, etc. will keep most of those 'NO'. RCPM systems running the +; usual bulletin board systems, etc., will change most of those to 'YES'. +; +MHZ: DB 10 ; Microprocessor speed - use integer 1,4,5, etc. +;MSPEED: DW 003CH ; Location of modem speed indicator byte +MSPEED: DW 0600H ; Location of modem speed indicator byte + ; [WBW] placed in I/O overlay + +WRTLOC: DB NO ; Set/reset WRTLOC so BYE won't hang up. Check +LOCOFF: DB 12 ; your BBS documentation - many modern systems + ; don't need WRTLOC. If unsure, set WRTLOC to NO. + ; Code to set and reset WRTLOC assumes the WRTLOC + ; byte to be located "LOCOFF" bytes from the JP + ; COLDBOOT instruction at the beginning of the BYE + ; BIOS jump table. (YES for MBBS and PBBS). + +STDBUF: DB NO ; If the size of your TPA cannot be calculated by + ; using one of the two following methods, disable + ; this by setting to NO. ZMD will Automatically + ; calculate the maximum disk buffer size allowed. If + ; enabled, CHKTPA will determine whether or not + ; BYE is running, if so uses (0006)-1. Else + ; subtracts 806H from (0006). If disabled, DBUF + ; setting will be used as default (Normally set to + ; 16k). Either way, if descriptions are allowed, + ; the total number of uploads allowed is set based + ; on the value of OUTSIZ divided by maximum bytes + ; per description entry. + +DESWAIT:DB 2 ; This is the number of minutes of inactivity during + ; an upload description or Help Guide prompt before + ; logging aborting the input routine. If the + ; caller was entering a description, the current + ; description buffer is written to disk before + ; resetting your BYE program's disk write lock flag + ; (WRTLOC), if enabled. + +MINKSPD:DB 1 ; Minimum speed acceptable for 1k packet file + ; transfers. If you are on a network such as PC + ; Pursuit, and are able to RECEIVE incoming calls, + ; set this byte to 1. The delays these networks + ; use to send data back and forth make 1k packets + ; advantageous to even 300 bps users. If you are + ; not on a network such as PC Pursuit, it's simply + ; a matter of preference, but why not let the 300 + ; bps callers experience the 1k packet transfers? + ; (1 = 300, 5 = 1200, 6 = 2400, ..., 9 = 19,200) + +BUFSIZ: DB 16 ; Normal disk systems can transfer 16k from + ; computer to disk in 2-3-4 seconds and less. Some + ; very slow 5-1/4" floppy systems (such as North + ; Star) may take up to 20-30 seconds to transfer + ; 16k. This would cause several timeouts at 10 + ; seconds each. If you experience any timeouts, + ; try changing BUFSIZ to something smaller, perhaps + ; 8k or even 4k. + +CLRSCRN:DB NO ; Yes, you want ZMD to clear your screen locally +CLRSTR: DB 1AH,00H ; during display of batch file transfers and all + DB 00H,00H ; the help menus. If you set CLRSCRN to YES, enter + DB 00H,00H ; your clear screen sequence in the 6 bytes aside. + DB '$' ; (If your terminal uses ^Z, leave as is, 1AH = ^Z) + +PAGLEN: DB 24 ; This is the number of lines to display in between + ; [more] pauses. (Set to 0 to disable pauses). + +; +;-------------------------------------------------------------------------; +; Timekeeping Considerations | +;-------------------------------------------------------------------------; +; +; If you have a clock installed (either in your BYE program or internal), +; set the following switches to your liking. If running ZMD without a +; clock, set these all NO. +; +CLOCK: DB NO ; Clock/date reader code installed in BYE program +RTC: DB NO ; Clock/date reader code installed at RTCTIM: + +TIMEON: DB NO ; Restrict downloads to maximum time allowed +MAXMIN: DB 60 ; Total minutes allowed for downloads. This should + ; be set if TIMEON is YES and CLOCK is NO. +DSPTOS: DB NO ; Yes to display 'Online nn minutes' messages +LHOUR: DW 0000H ; Logon hour address (If RTC). LHOUR+2 = logon + ; minute address. + +; +;-------------------------------------------------------------------------; +; Bit Map Layout | +;-------------------------------------------------------------------------; +; +; The following byte contains information corresponding to the filename +; bytes of the file being considered for transfer. Enabling any of the +; options causes ZMD to look at the high bit of the byte position +; indicated below (F1=filename byte 1, T2=file type byte 2, etc). These +; restrictions are always bypassed when using ZCPR and the WHEEL is set. +; +; Bit: 76543210 ; Correspond to definitions below +; |||||||| +ACCMAP: DB 11111101B ; Set any bits of this byte according +; __________\______/ ; to your own preference. +; / +; +; 7 | F1 | File not for distribution. If file is a ARK/ARC/LBR +; | | file, individual members may be downloaded (no Batch) +; 6 | F3 | File can be downloaded regardless of user's access +; 5 | T2 | $SYS files not sent or reported +; 4 | T3 | .??# files with labels not sent +; 3 | T1/2/3 | .COM files not sent or reported +; 2 | T1/2/3 | Rename .COM to .OBJ and .PRL to .OBP on receive +; 1 | -- | RESERVED FOR FUTURE USE +; 0 | T1/2/3 | .SYS, .NDR, .RCP, file extents not accepted. (ZCPR) + +; +;-------------------------------------------------------------------------; +; ZCMD/ZCPR | +;-------------------------------------------------------------------------; +; +; If using ZCPR low memory bytes to keep track of maximum drive and user, +; set USEMAX to yes. ZMD will use the values at locations DRIVMAX and +; USRMAX for maximum drive/user. If USEMAX is NO, hardcode MAXDRV and +; MAXUSR to your own requirements. +; +WHEEL: DW 3EH ; Location of ZCPR wheel byte +USEMAX: DB NO ;*Use values at DRIVMAX and USRMAX for maximum + ; (Else use MAXDRV and MAXUSR values) +DRIVMAX:DW 3DH ; ZCPR maximum drive memory byte +USRMAX: DW 3FH ; ZCPR maximum user memory byte +MAXDRV: DB 16 ; Maximum download drive allowed +MAXUSR: DB 16 ; Maximum download user allowed + +; +;-------------------------------------------------------------------------; +; Access Restrictions | +;-------------------------------------------------------------------------; +; +; If ACCESS is YES, ZMD will inspect AFBYTE (located ACBOFF bytes from JP +; COLDBOOT) for the following flag data: +; +; +; Bit: 7 6 5 4 3 2 1 0 +; | | | | | | | | +; Privileged user ---* | | | | | | | +; Upload -----* | | | | | | +; Download -------* | | | | | * Of these bits, only 3, 5, 6 +; CP/M ---------+ | | | | and 7 are used by ZMD. Bit +; Write -----------* | | | numbers are powers of 2, with +; Read -------------+ | | bit 0 being least significant +; BBS ---------------+ | bit of byte. +; System -----------------+ +; +; +ACCESS: DB NO ;*Yes, your system sets BYE's bit-mapped flag + ; register to restrict user's ability to upload, + ; download, use the 'RM' option to upload message + ; files to your BBS's message base, or to use the + ; 'RW' option for 'privileged user' upload without + ; being required to give upload descriptions. + +ACBOFF: DB 21 ; If you set ACCESS to YES, you 'might' need to + ; set this to reflect the number of bytes from JP + ; COLDBOOT to ACCESS byte. In most cases, leave + ; alone. + +; +;-------------------------------------------------------------------------; +; Upload Configurations | +;-------------------------------------------------------------------------; +; +MSGFIL: DB NO ; Some BBS's allow callers to upload preformatted + ; text files which are appended to the message + ; base. (MBBS and PBBS are examples of this). If + ; you're running MBBS or PBBS and wish to support + ; this, simply set MSGFIL to YES. Message file + ; uploads go to PRDRV and PRUSR. + +HIDEIT: DB NO ; Yes, make all normal uploads $SYS files. This + ; way, new uploads will not appear in a DIRectory + ; listing and cannot be viewed or even downloaded + ; until they are cleared by SYSOP. (New uploads + ; will show up when the WHEEL byte is ON and a $S + ; option is used to show SYSTEM files. Use the $O + ; option to list ONLY $SYS files. Reference will + ; be made to these files in the in the NEW and FOR + ; listings if those features are enabled). Private + ; uploads, and uploads made with the WHEEL byte + ; set are NOT made $SYS. You can use POWER or + ; NSWEEP to set to $DIR. + +; +;-------------------------------------------------------------------------; +; Upload Routing Options | +;-------------------------------------------------------------------------; +; +; The following equates determine what drive/user area uploads will be sent +; to. If you prefer to enable upload routing (ASKAREA set YES), you will +; have to set MAXTYP to the letter of the highest category you wish to +; support, and configure TYPTBL: and KNDTBL: tables below for your own +; system. Do NOT set ASKAREA and SETAREA both to YES. +; +ASKAREA:DB NO ;*Yes, you want upload routing to multiple drive + ; and user areas. The caller will be asked what + ; the file (or files) he is uploading are for and + ; his uploads will then be forwarded to the + ; appropriate area. You will need to set up the + ; categories at KNDTBL: for your own system and + ; set the drive/user area each category belongs on + ; at label TYPTBL:. There can be up to 26 different + ; drive/user and category combinations. This applies + ; for both private and normal uploads. Upload + ; routing is disabled when the WHEEL byte is set, + ; in which case, normal uploads will go to the + ; current drive/user area and private uploads will + ; go to the drive/user equated at PRDRV and PRUSR. + +SETAREA:DB NO ;*Yes, you wish to have all regular uploads sent +DRV: DB 'B' ; to the drive/user equated at DRV and USR to left. +USR: DB 0 ; If WHEEL byte is set, regular uploads will go to + ; the current or specified drive/user. Unless you + ; ASKAREA is YES, all private files uploaded with + ; the 'RP' option will be sent to PRDRV and PRUSR + ; regardless of WHEEL status. + +PRDRV: DB 'H' ;*This is the drive/user area where ALL files sent +PRUSR: DB 15 ;*to the sysop with the 'RP' option will go (unless + ; ASKAREA below is YES). This permits experimental + ; files, replacement and/or proprietary programs to + ; be sent to an area only accessible by the sysop. + ; This is also the drive and user area where message + ; files are uploaded, if MSGFIL is set YES. (If + ; ASKAREA is YES, this is the drive/user where + ; uploads will go when 'RP' is specified, and WHEEL + ; is set). (If MSGDESC is YES, this is the drive + ; and user area the FOR text file will be placed + ; before appending it to the BBS system's message + ; base). + +CREDIT: DB NO ;*Yes, callers are given credit for the amount of + ; time they spend uploading non-private files. A + ; caller who spends 30 minutes sending an upload + ; gets 30 minutes added to his TLOS. (You must + ; set either CLOCK or TIMEON to YES to use this + ; feature). + +; +;-------------------------------------------------------------------------; +; Upload Description Options | +;-------------------------------------------------------------------------; +; +; This section has to do with upload descriptions. If you do not intend +; on implementing upload descriptions, set DESCRIB and MSGDESC to NO. The +; rest of these values are then ignored. If using descriptions, set ONLY +; one of these to YES, not both. +; +MSGDESC:DB NO ; Yes, your BBS system supports message uploads, + ; and you prefer upload descriptions to be placed + ; in your BBS message system (set DESCRIB NO). + ; MBBS users need to install MFMSG.COM with the + ; MBBSINIT program. Then set your BYE program to + ; know about message file uploads by setting the + ; MSGFIL option in BYE/MBYE to YES. If set YES, + ; ZMD will produce a FOR text file when writing + ; upload descriptions. This FOR file will go to + ; the drive and user area equated at PRDRV and + ; PRUSR just before being appended to your BBS + ; system's message base. + +DESCRIB:DB NO ;*Yes, requiring users to provide descriptions for + ; any files they upload. These descriptions will + ; be added to the current FOR file where they can + ; be viewed by callers with the ZFORP utility. + ; Sysop can add new descriptions with the ZFORS + ; utility (See ZMD.WS for more information on ZMD + ; support files). Uploads sent to the SYSOP private + ; upload area will not be require descriptions, nor + ; will files uploaded with the 'RW' option - user + ; must be a privileged user (bit 7 in ACCESS byte + ; set) or have WHEEL access and PUPOPT must be set + ; YES to use the 'RW' option. + +FORNAM: DB 'FOR ' ; Description text file (Must be 11 bytes) +DRIVE: DB 'A' ;*If using with DESCRIB set YES, you must indicate +USER: DB 14 ;*what drive/user you want the 'FOR' file to be + ; placed. + +; +; If DESCRIB above is set to YES, you'll have to tell ZMD what information +; you want included in the first line of each description. Code is included +; in ZMD to place all (any) information in the upload description header in +; in the same column position no matter what the filename or file category +; length is. The following illustrates a full implementation of DESCRIB. +; +; Example upload description header: +; +; ----- +; ZMD150.LBR - Communications (C3:) Rcvd: 09/21/88 +; / / / +; _______/ ______/ _______/ +; ASKIND INCLDU DSTAMP +; +; +ASKIND: DB NO ;*Yes, ask for the category of uploads and write it + ; into the upload description header. If you set + ; this to YES, make sure you set MAXTYP below to + ; the highest letter choice you wish to support and + ; edit the text at KNDTBL: up to and including your + ; MAXTYP setting. (Used only with DESCRIB). + +INCLDU: DB YES ;*Yes, include the drive/user area of the uploaded + ; file into the upload description header. + ; (Used only with DESCRIB). + +DSTAMP: DB YES ;*Yes, include the date the upload was received into + ; the upload description header. (NO if no clock) + ; (Used only with DESCRIB). + +PUPOPT: DB YES ;*Yes, description request of file upload will be + ; skipped when "RW" is used in the ZMD command line + ; (i.e. ZMD RW FILE.EXT). This command may only be + ; used by those considered "priviledged" users on + ; your system or WHEEL users. Uploads of this type + ; will be tagged in the ZMD.LOG file as private, so + ; as not to display with the NEW command. (See + ; ACCESS equate description above for information + ; on 'priviledged' users). + +WRAP: DB 64 ; Column position where word wrap will occur. If + ; you are using MSGDESC and have problems with an + ; 'Invalid format' error from MFMSG.COM, try setting + ; WRAP to something smaller, like 62 or 63. (Word + ; wrap can be disabled by the user with ^W during + ; description entry. Enter 72 here to disable + ; completely). + +; +;-------------------------------------------------------------------------; +; Special Sysop Downloads | +;-------------------------------------------------------------------------; +; +SPLDRV: DB 'H' ;*Drive/user area for downloading private files +SPLUSR: DB 14 ;*from sysop. This permits him to put a special + ; file in this area, then leave a private note + ; to that person mentioning the name of the file + ; and how to download it (ZMD SP filename.ext). + ; Although anybody 'could' download that program, + ; they don't know what (if any) files are there. + ; A high degree of security exists, while the + ; SYSOP still has the ability to make special + ; files available. Thus any person can be a + ; temporary 'privileged user'. + +; +;-------------------------------------------------------------------------; +; File Transfer Log | +;-------------------------------------------------------------------------; +; +LOGCAL: DB NO ;*ZMD.LOG is produced if LOGCAL is set YES. All + ; file transfers are logged. You can then use + ; ZNEWP.COM to show listings of recent uploads. +EDATE: DB NO ; Yes, show date in ZMD.LOG in dd/mm/yy format + ; instead of mm/dd/yy format. + +LOGNAM: DB 'ZMD LOG' ; File transfer log (Must be 11 bytes) +LOGDRV: DB 'A' ;*Drive to find ZMD.LOG on. +LOGUSR: DB 14 ;*User area to find ZMD.LOG on. + +LSTCLR: DB 'LASTCALR???' ; Last caller file (Must be 11 bytes) +LASTDRV:DB 'A' ;*Drive to find LASTCALR??? file on. +LASTUSR:DB 14 ;*User area to find LASTCALR??? file on. +LCNAME: DB 0 ;*Position of last caller's name in the LASTCALR??? + ; file. (MBBS is 11, PBBS is 0). + +LOGLDS: DB NO ;*Count number of up/down loads since login. Your +UPLDS: DW 0054H ; BBS program can check UPLDS and DNLDS when a user +DNLDS: DW 0055H ; logs out and update either the user's file or a + ; file for this purpose. You can either modify + ; your BBS entry program to check the LASTCALR file + ; before updating and then update (risky), or make + ; a separate program that BYE calls when logging off + ; a user (preferred). (YES for PBBS). Clear UPLDS + ; and DNLDS to 0 from your BBS program when somebody + ; logs in. NOTE: Clear ONLY when a user logs in, + ; not when he re-enters the BBS program from CP/M. + +; +;-------------------------------------------------------------------------; +; File Descriptors | +;-------------------------------------------------------------------------; +; +; This table defines the text to be included in upload description headers +; (DESCRIB and ASKIND) and/or defines categories for uploading to multiple +; drive/user areas (If ASKAREA and NOT SETAREA). Change as desired, if +; this list is not suitable. Do NOT remove any of the text at KNDTBL:. +; Simply edit the text below up to/and including your MAXTYP setting. +; MAXTYP below must be set to whatever letter your maximum choice will be. +; Make sure you leave all the following categories EXACTLY 31 bytes long +; (29 bytes of text plus the CR,LF equals 31) or you will be CERTAIN to +; have problems with the double column formatting later on in the program. +; +MAXTYP: DB 'W' ; Letter of highest category you will support. + +KNDTBL: DB ' A) - BBS Lists, PC Pursuit ',CR,LF + DB ' B) - CP/M Modem Program ',CR,LF + DB ' C) - CP/M Utility ',CR,LF + DB ' D) - CP/M Lbr, Ark, Catalog',CR,LF + DB ' E) - CP/M Plus Specific ',CR,LF + DB ' F) - CP/M Games & Humor ',CR,LF + DB ' G) - CP/M Wordprocessing ',CR,LF + DB ' H) - CP/M Printer Utility ',CR,LF + DB ' I) - CP/M OS Enhancement ',CR,LF + DB ' J) - CP/M BBS Software ',CR,LF + DB ' K) - CP/M Assemb/Disassemb ',CR,LF + DB ' L) - CP/M Language (Other) ',CR,LF + DB ' M) - Turbo Pascal ',CR,LF + DB ' N) - dBase & Database ',CR,LF + DB ' O) - "C" ',CR,LF + DB ' P) - Text & Information ',CR,LF + DB ' Q) - DOS Modem Program ',CR,LF + DB ' R) - DOS BBS Software ',CR,LF + DB ' S) - DOS Language ',CR,LF + DB ' T) - DOS Assemb/Disassemb ',CR,LF + DB ' U) - DOS Lbr, Arc, Catalog ',CR,LF + DB ' V) - DOS Wordprocessing ',CR,LF + DB ' W) - DOS Printer Utility ',CR,LF + DB ' X) - DOS Games & Humor ',CR,LF + DB ' Y) - DOS Application ',CR,LF + DB ' Z) - DOS Utility ',CR,LF + DB 0 ; leave the table terminator alone. + +; +;-------------------------------------------------------------------------; +; Upload Routing Table | +;-------------------------------------------------------------------------; +; +; If ASKAREA is set YES, then set these areas up to match the message text +; in KNDTBL: above. Note that PRIVATE uploads may be sent to a different +; drive as well as a different user area. Each entry is expressed as +; 'drive letter',user area. Simply set MAXTYP above to the highest +; letter choice supported. (Do NOT comment out any of the following +; storage bytes). +; +; _________ +; NOTE: / A \ <--- 'A' Corresponds to category 'A' above +; 'A',1,'B',15, +; \ / \ / +; Normal upload --+ | +; Private upload -------+ +; + +TYPTBL: +; _________ _________ _________ _________ +; / A \ / B \ / C \ / D \ + DB 'B',1,'F',15, 'B',0,'F',15, 'B',2,'F',15, 'B',4,'F',15 +; _________ _________ _________ _________ +; / E \ / F \ / G \ / H \ + DB 'B',5,'F',15, 'B',6,'F',15, 'C',0,'F',15, 'C',1,'F',15 +; _________ _________ _________ _________ +; / I \ / J \ / K \ / L \ + DB 'C',2,'F',15, 'C',3,'F',15, 'A',1,'F',15, 'A',5,'F',15 +; _________ _________ _________ _________ +; / M \ / N \ / O \ / P \ + DB 'A',2,'F',15, 'A',3,'F',15, 'A',4,'F',15, 'A',6,'F',15 +; _________ _________ _________ _________ +; / Q \ / R \ / S \ / T \ + DB 'E',0,'F',15, 'E',1,'F',15, 'E',2,'F',15, 'E',3,'F',15 +; _________ _________ _________ _________ +; / U \ / V \ / W \ / X \ + DB 'F',0,'F',15, 'F',1,'F',15, 'F',2,'F',15, 'F',3,'F',15 +; _________ _________ +; / Y \ / Z \ + DB 'G',0,'F',15, 'H',0,'F',15 + +; +;=========================================================================; +;>>>>>>>>>>>>>> DO NOT CHANGE ANYTHING BEYOND THIS POINT <<<<<<<<<<<<<<<| +;=========================================================================; +;-------------------------------------------------------------------------; +; Version Identification | +;-------------------------------------------------------------------------; +; +; If/when INSTALL is first ran, it checks these next 3 bytes to make sure +; it is installing the proper version of ZMD.COM. If these bytes do not +; match, the INSTALL procedure will abort. (Don't change this one) +; +INSVERS:DB '150' ; Don't change this one + +; +;-------------------------------------------------------------------------; +; User Definable Storage Bytes | +;-------------------------------------------------------------------------; +; +; If you make changes to the configuration table, make them here. You +; are allowed 2 bytes for switches, or storage for an address, etc. Any +; changes before this point and ZINSTL will not run. +; +SPARE1: DB 0 ; User definable storage +SPARE2: DB 0 + +; +;-------------------------------------------------------------------------; +; Clock/Date Reader Code | +;-------------------------------------------------------------------------; +; +; Install your clock/date reader code here. RTCTIM gets called to retrieve +; the current date and time. All values must be stored in binary form. +; A 6 byte clock buffer called TIMBUF can be used as a work area. Call +; BCDBIN to convert your BCD (Binary Coded Decimal) value in A to binary +; value in A. Delete all ';<=' lines; after installing your custom +; routine. +; + ORG 4FEH ; Allow 130 bytes for RTC insert +; +RTCTIM::LD A,0 ;<= Delete lines if reader code installed + LD (MONTH),A ;<= Current month (1-12) + LD (DAY),A ;<= Current date (1-31) + LD (YEAR),A ;<= Current year (0-99) + LD (MINUTE),A ;<= Current minute (0-59) + LD (HOUR),A ;<= Current hour (0-23) + RET + +; +;-------------------------------------------------------------------------; +; Input/Output Patch Area | +;-------------------------------------------------------------------------; + + ORG 580H ; Modem routine starts here + +CONOUT: JP 0000H ; BIOS local console output routine address +MINIT: JP 0000H ; Startup initialization routine +UNINIT: JP 0000H ; Exit uninitialize routine +MDOUTP: JP 0000H ; Send character out modem +MDCARCK:JP 0000H ; Test for carrier +MDINP: JP 0000H ; Get character from modem +MDINST: JP 0000H ; Check receive ready +MDOUTST:JP 0000H ; Check send ready + + ;ORG 600H ; Allow 128 bytes for I/O overlay + ORG 1000H ; Allow for large I/O overlay + + +; +; Overlay ends here +; + \ No newline at end of file diff --git a/Source/Apps/ZMD/znewp.z80 b/Source/Apps/ZMD/znewp.z80 new file mode 100644 index 00000000..56f8750f --- /dev/null +++ b/Source/Apps/ZMD/znewp.z80 @@ -0,0 +1,517 @@ +; + + TITLE ZNEWP.Z80 - 09/29/88 - ZMD Public Transfer Log Utility +; Copyrighted (c) 1987, 1988 +; Robert W. Kramer III + +;- -; +; Update History ; +; ; +; Date Release Comments ; +; -------- ------- ---------------------------------------------- ; +; ; +; 09/29/88 v1.50 - No change(s) made to this file ; +; 03/18/88 v1.49 - No change(s) made to this file ; +; 03/13/88 v1.48 - Redefined buffer table at end of programs. STACK; +; and filename buffers now EQUated with offsets ; +; from the last switch/toggle in program instead ; +; of with DS directive. ; +; - Some systems which do NOT have an interrupt ; +; driven keyboard may have noticed problems when ; +; an invalid key was entered in the ZNEWP, ZFORP ; +; and ZMDEL programs. In ZNEWP and ZFORP, if a ; +; CR was entered to pause the output, output was ; +; limited to one line at a time per key pressed. ; +; If an invalid key was hit, output would have ; +; remained in a paused state until one of the ; +; abort keys were pressed. This was difficult to ; +; find since my keyboard is interrupt driven and ; +; I could not duplicate the problem on my own ; +; system. ; +; 02/25/88 v1.47 - Fixed to determine if CLOCK is enabled before ; +; inserting spaces after 8 'date' positions. ; +; (Keeps caller name from being split with an ; +; extra space at column 52). ; +; 01/27/88 v1.46 - Now uses BDOS call 35 to calculate virtual size ; +; of log file (in records) which is returned in ; +; bytes 33 and 34 of log FCB. It then uses BDOS ; +; call 33 to compute the extent of the specified ; +; record number (bytes 33 and 34 of log FCB), ; +; decrements the record number for next random ; +; read and displays current record. This method ; +; of reading the log file allows for files up to ; +; 8,388,480 bytes in size to be displayed at an ; +; instant. ; +; 01/17/88 v1.45 - First public release ; +; 11/01/87 v1.00 - Initial version ; +;- -; + +;-------------------------------------------------------------------------; +; EXTERNAL Declarations: | +;-------------------------------------------------------------------------; + + + EXTRN CKABRT,CLEARIT,DBUF,ERXIT,EXIT,ILPRTB,LINCNT,MODE + EXTRN NOFILE,NOLOG,OLDDRV,OLDUSR,PRINTV,PRIVATE,RECAR1 + EXTRN RECDR1,RENFCB,RSDMA,SHONM4,STACK,TYPE,WHLCHK + +; +;-------------------------------------------------------------------------; +; Program Starts Here | +;-------------------------------------------------------------------------; + + + .Z80 + ASEG + ORG 100H ; Program starts + JP BEGIN ; Jump around configuration table + INCLUDE ZMDHDR.Z80 ; Include the ZMD header overlay + .REQUEST ZMDSUBS ; Include the ZMD subroutines + +; +; +; Save CP/M stack, initialize new one for this program +; +BEGIN: LD (STACK),SP ; Save return address to CCP + LD SP,STACK ; Initialize new one for this program + +; +; Get current drive/user +; + LD A,255 ; Get current user + CALL RECAR1 + LD (OLDUSR),A ; Store it + LD C,CURDRV ; Get current drive + CALL BDOS + LD (OLDDRV),A ; Store it + +; +; Tell em who we are +; + LD A,255 ; Need this so discrepency check will + LD (MODE),A ; Leave clock settings alone in case local + LD HL,PUBNEW ; Point to this filename + CALL PRINTV ; Display it + + LD A,(LOGCAL) ; See if log file enabled + OR A + JP Z,NOLOG ; Don't run program if not + + CALL ILPRTB + DB '(S to Pause - C K or X Abort)' + DB CR,LF,LF + DB 0 + + LD A,9 ; Leave program name on first screen + LD (LINCNT),A + +; +; If WHEEL byte set, check to see if 'A' specified in command tail. If +; it is, set switch to display entire transfer log +; + CALL WHLCHK ; WHEEL byte set? + JP Z,BEGIN2 ; No don't look for options + LD A,(FCB+1) ; Get possible option + CP 'A' ; Want to show all of file? + JP NZ,BEGIN2 ; No + LD A,1 + LD (SHOWALL),A ; Set to show all entries + LD A,8 + LD (LINCNT),A + +; +; See if user wants file displayed 'nonstop' ($N) +; +BEGIN2: LD HL,FCB+1 ; Get first character on command line + LD A,(HL) ; Into A + CP '$' ; Must specify '$' first + JP NZ,OPNLOG ; Nope, continue normal + INC HL ; Point to next byte + LD A,(HL) ; Into A for comparison + CP 'N' ; 'N' for nonstop display? + JP NZ,OPNLOG ; No + LD A,0 + LD (PAGLEN),A ; Else disable page pauses + +; +; Set drive/user to the log file area and open ZMD.LOG +; +OPNLOG: LD A,(LOGUSR) ; Set user area to ZMD.LOG area + CALL RECAR1 + LD A,(LOGDRV) ; Set drive to ZMD.LOG area + CALL RECDR1 + + LD HL,FILE ; Destination is internal FCB + LD DE,LOGNAM ; For log filename + CALL RENFCB ; Initialize and rename + + LD DE,FILE + LD C,OPEN ; Open log file + CALL BDOS + INC A ; Check for no open + LD HL,FILE+1 + JP Z,NOFILE ; No file, exit + +; +; See if special sysop access was allowed/requested. Display enabled +; message if so. +; + LD A,(SHOWALL) ; Showing entire transfer log? + OR A + JP Z,SHOHDR ; No, display header as usual + CALL ILPRTB + DB CR + DB '-- Sysop access enabled --',0 + JP CALCSIZ + +; +; Display all applicable field titles +; +SHOHDR: CALL ILPRTB + DB CR + DB 'D/u Filename Size Speed ',0 + + LD A,(CLOCK) + OR A + JP Z,SHOHDR1 + CALL ILPRTB + DB ' Date Time ',0 + +SHOHDR1:CALL ILPRTB + DB ' Uploaded by' + DB CR,LF,0 + +; +; Get number of records in log file +; +CALCSIZ:LD DE,FILE ; Point to log FCB + LD C,FILSIZ ; Compute file size function + CALL BDOS + LD DE,DBUF+81 ; Point to beggining of write buffer + LD A,CR + LD (DE),A ; Force first CR + DEC E + +; +; Decrement number of records left. If any left, read on into write +; buffer, otherwise close file and exit +; +NXTRCD: PUSH DE ; Save write buffer address + LD HL,FILE+33 ; Get current record counter + LD E,(HL) ; LSB record count + INC HL + LD D,(HL) ; MSB record count + DEC DE ; Decrement it + LD (HL),D ; Put it back + DEC HL + LD (HL),E + LD A,E + CP 0FFH ; Any more records? + JP NZ,RDRCD ; Yes, go read it + LD A,D ; Maybe not, check MSB + CP 0FFH ; Any more? + JP Z,TDONE ; No, all done + +; +; Read a record from source file +; +RDRCD: CALL RSDMA ; Reset DMA to 80H + LD DE,FILE + LD C,RRDM ; Random read + CALL BDOS + POP DE ; Get write buffer address back + OR A ; Read ok? + JP NZ,RDERR ; Yes + LD HL,TBUF+127 ; End address of read buffer + LD B,128 ; Buffer is filled backwards + +; +; Write record from disk to memory (in reverse order) +; +WRTBYT: LD A,(HL) ; Get byte from read buffer + AND 7FH ; Strip parity bit + CP LF ; End of line? + JP Z,DSPLIN ; Yes, show it if supposed to + CP 7FH ; Del (rubout)? + JP Z,NXTBYT ; Yes, ignore it + CP EOF ; End of file marker? + JP Z,NXTBYT ; Yes, ignore it + LD (DE),A + DEC E ; Decrement write buffer pointer + +NXTBYT: DEC L ; Decrement read buffer pointer + DJNZ WRTBYT ; And character count, get next if any left + JP NXTRCD ; Else read another record + +; +; Found end (CR) of current entry. Display it. +; +DSPLIN: DEC B ; Decrement character counter + PUSH AF ; Save flags + DEC L ; Decrement write buffer pointer + PUSH HL ; Save write address + PUSH BC ; Save character count + EX DE,HL ; HL now contains current buffer read address + LD (HL),A ; Store the LF + + LD A,(SHOWALL) ; Show entire file? + OR A + JP NZ,NEXT ; Yes + + INC L ; Else next character is transfer mode + LD A,(HL) ; Get it + DEC L ; Restore read pointer + CP 'R' ; Uploaded entry? + JP NZ,SENDLF1 ; No, ignore it and reset pointers + +; +; Get next character from read buffer. If end of line, go display CR,LF. +; +NEXT: INC L ; Increment next read byte + LD A,(HL) ; Get character + LD B,A ; Into B + CP CR ; End of line? + JP Z,SENDLF ; Yes, send a CR,LF and reset pointers + + LD A,(COLUMN) ; Get column count + CP 79 ; End of line? + JP Z,DSPLP2 ; Yes, start new line + OR A ; At beginning of line? + JP NZ,DSPLP3 ; No, continue + INC A + LD (COLUMN),A ; We aren't anymore + + LD A,B ; Get the character back + LD (STORE),A ; Store it for later comparison + + CP 'P' ; This entry private upload? + JP NZ,DSPLP1 ; No, check for regular upload + LD A,(SHOWALL) ; Yes, were we supposed to show entire file? + OR A + JP Z,DSPLP2 ; No, so ignore it + LD (PRIVATE),A ; Set private flag + JP DSPLP4 ; Go show it + +DSPLP1: CP 'R' ; Regular upload? + JP Z,DSPLP4 ; Yes, keep the flag set + + LD A,(SHOWALL) + OR A + JP NZ,DSPLP4 + + XOR A + LD (STORE),A + LD (COLUMN),A ; Start in column 0 + JP NEXT + +DSPLP2: XOR A + LD (STORE),A ; Otherwise reset flag to zero + +DSPLP3: LD A,(STORE) ; Storing into memory? + OR A + JP Z,NEXT ; If not, exit + +DSPLP4: LD A,(COLUMN) ; Increment the column counter + INC A + LD (COLUMN),A + + CP 3 ; User's modem speed is in column 2 + JP NZ,DSPLP5 ; If not column 2, continue + LD A,B ; Otherwise get the character + LD (STORE),A ; Store it for conversion to baud rate + JP NEXT ; Do not print the "MSPEED" number + +DSPLP5: CP 11 + JP C,NEXT ; Skip everything through column 10 + +; +; Display drive and user of file +; + CP 14 + JP C,SEND ; Print everything through column 13 + JP NZ,DSPFN + +; +; If a private file, make a special note of it +; + LD A,(PRIVATE) ; Private entry? + OR A + JP Z,DSPDU ; No + + XOR A ; Reset flag for next time + LD (PRIVATE),A + LD A,'*' ; Displays entry is private + CALL TYPE + LD B,' ' ; Keep our distance from next field + JP SEND + +DSPDU: LD A,':' ; Stick in a colon after column 12 + CALL TYPE + LD B,' ' ; Send a space + JP SEND + +; +; Display filename and extent +; +DSPFN: CP 22 ; Print through column 20 + JP C,SEND + JP NZ,DSPFT + LD A,B + CALL TYPE ; Send character in colum 21 + LD B,'.' ; Add a period after the file name + JP SEND + +DSPFT: CP 27 + JP C,SEND ; Print file type and some spaces + CALL Z,PRTSPC + CP 39 + JP C,NEXT ; Ignore the "big gap" + CP 43 + JP C,SEND ; Print the file size + JP Z,DSPBD + JP DSPDAT + +; +; Display the baud rate (300-19200 bps) +; +DSPBD: PUSH HL + LD A,(STORE) + CP '1' + JP Z,B300 + CP '5' + JP Z,B1200 + CP '6' + JP Z,B2400 + CP '7' + JP Z,B4800 + CP '8' + JP Z,B9600 + CP '9' + JP Z,B19200 + CALL ILPRTB + DB ' ',0 + POP HL + JP NEXT ; Go get next byte from read buffer + +B300: CALL ILPRTB + DB ' 3',0 + JP BFINISH + +B1200: CALL ILPRTB + DB ' 12',0 + JP BFINISH + +B2400: CALL ILPRTB + DB ' 24',0 + JP BFINISH + +B4800: CALL ILPRTB + DB ' 48',0 + JP BFINISH + +B9600: CALL ILPRTB + DB ' 96',0 + JP BFINISH + +B19200: CALL ILPRTB + DB ' 192',0 + +BFINISH:CALL ILPRTB + DB '00 bps ',0 + POP HL + JP NEXT + +; +; Display time and date +; +DSPDAT: LD A,(CLOCK) ; Clock enabled? + OR A + LD A,(COLUMN) + JP Z,SEND ; No, leave caller's name alone + CP 52 + JP C,SEND ; Print the date + JP NZ,DSPTIM + CALL PRTSPC ; Keep our distance from next field + JP SEND + +DSPTIM: CP 58 + JP C,SEND ; Print the time + JP NZ,SEND + CALL PRTSPC ; Keep our distance from next field + +; +; Display character in B +; +SEND: LD A,B ; Get the character back + CALL TYPE ; Display it + JP NEXT + +; +; Reached end of entry. Send CR,LF and reset pointers +; +SENDLF: LD A,CR ; Output CR + CALL TYPE + LD A,LF ; And LF for next line + CALL TYPE + XOR A ; Enable page pauses for output + JP $+5 + +SENDLF1:LD A,1 + CALL CKABRT ; Check for aborts/pauses + XOR A ; Start new line + LD (COLUMN),A ; And indicate column 0 + LD DE,DBUF+80 ; Beginning of write buffer again + POP BC ; Number of characters left in buffer + POP HL ; Current read buffer address + POP AF ; Were there any characters left? + JP Z,NXTRCD ; No, get next record + JP WRTBYT ; Else get next character + +; +; S u b r o u t i n e s +;----------------------- +; +; Routine to display a space - saves character in A on entry +; +PRTSPC: PUSH AF + LD A,' ' + CALL TYPE + POP AF + RET + +; +; Transfer is done - close destination file +; +TDONE: LD C,CLOSE + LD DE,FILE + CALL BDOS + CALL ERXIT + DB CR,LF + DB '-- End of listing','$' + +; +RDERR: CALL ILPRTB + DB CR,LF + DB '-- Read Error: ',0 + LD HL,FILE+1 + CALL SHONM4 + JP EXIT + +; +; +; These next are just to satisfy ZMDSUBS external requests. Leave alone. +; +DONE:: JP EXIT +TIME:: RET +; +; +; Initialized storage area +;-------------------------- +; +COLUMN: DB 0 ; Column of ZMD.LOG line +STORE: DB 0 +SHOWALL:DB 0 ; 1=all transfers, 0=uploads only + + + END + \ No newline at end of file diff --git a/Source/Apps/ZMD/znews.z80 b/Source/Apps/ZMD/znews.z80 new file mode 100644 index 00000000..12fdb725 --- /dev/null +++ b/Source/Apps/ZMD/znews.z80 @@ -0,0 +1,298 @@ +; + + TITLE ZNEWS.Z80 - 09/29/88 - ZMD Sysop Transfer Log Utility +; Copyrighted (c) 1987, 1988 +; Robert W. Kramer III + + PAGE +;- -; +; Update History ; +; ; +; Date Release Comments ; +; -------- ------- ---------------------------------------------- ; +; ; +; 09/29/88 v1.50 - Fixed problem that caused a 'ZMD .L$$' file ; +; to not be deleted from directory after aborted ; +; sessions. ; +; - Also, was moving 9 filename bytes intead of 8, ; +; this is what caused the mysterious filename of ; +; '.L$$' as mentioned above. The filename left ; +; in the directory should have been '.$$$'. ; +; - Some minor cosmetic changes. ; +; 03/18/88 v1.49 - No change(s) made to this file ; +; 03/13/88 v1.48 - Redefined buffer table at end of programs. STACK; +; and filename buffers now EQUated with offsets ; +; from the last switch/toggle in program instead ; +; of with DS directive. ; +; 02/25/88 v1.47 - No change(s) made to this file +; 01/27/88 v1.46 - Some changes were made to ZMDSUBS file that are ; +; not directly related to this file ; +; 01/17/88 v1.45 - First public release ; +; 11/19/87 v1.00 - Initial version ; +;- -; + +; +;-------------------------------------------------------------------------; +; EXTERNAL Declarations: | +;-------------------------------------------------------------------------; + + + EXTRN CASEFLG,CKABRT,CLEARIT,CMDBUF,DESC,DSTOR,DSTOR1,ERXIT + EXTRN EXIT,ILPRTB,INPUT,LNLNGTH,NOFILE,NOLOG,NOROOM,OLDDRV + EXTRN OLDUSR,OLINE,PRINTV,RECAR1,RECDR1,RENFCB,RERROR,RSDMA + EXTRN SHONM4,STACK,TDONE,UCASE + +; +;-------------------------------------------------------------------------; +; Program Starts Here | +;-------------------------------------------------------------------------; + + + .Z80 + ASEG + ORG 100H ; Program starts + JP BEGIN ; Jump around configuration table + INCLUDE ZMDHDR.Z80 ; Include the ZMD header overlay + .REQUEST ZMDSUBS ; Include the ZMD subroutines + +; +; +; Save CP/M stack, initialize new one for this program +; +BEGIN: LD (STACK),SP ; Save current CCP stack address + LD SP,STACK ; Initialize new one for this program + +; +; Get current drive/user area and store for later +; + LD A,255 ; Get current user area + CALL RECAR1 + LD (OLDUSR),A ; Save it + LD C,CURDRV ; Get current drive + CALL BDOS + LD (OLDDRV),A ; Save it + +; +; Display program name, version, and copyright notice +; +BEGIN1: LD HL,SYSNEW + CALL PRINTV + + LD A,(LOGCAL) ; Log file enabled? + OR A + JP Z,NOLOG ; No, then don't run program + + LD A,(GOTLAST) ; Already located last entry? + OR A + JP NZ,GTNEW ; Yes, then just show it + LD (DESWAIT),A ; Disable sleepy caller time out + + CALL ILPRTB + DB 'Working.',0 + +; +; Log into log file drive/user +; + LD A,(LOGUSR) ; User area to find ZMD.LOG + CALL RECAR1 + LD A,(LOGDRV) ; Drive to find ZMD.LOG + CALL RECDR1 + +; +; Open 'ZMD .LOG' file +; + LD DE,LOGNAM ; Current log filename + LD HL,FILE ; Internal FCB + CALL RENFCB ; Initialize + + LD DE,FILE + LD C,OPEN ; Open log file + CALL BDOS + INC A ; ZMD.LOG file exist? + LD HL,LOGNAM + JP Z,NOFILE ; No, inform user and exit to CP/M + +; +; Open 'ZMD .$$$' file +; + LD DE,TEMPFIL ; Current '$$$' filename + LD HL,DEST ; Internal FCB + CALL RENFCB ; Initialize + + LD HL,FILE+1 ; Point to log filename + LD DE,DEST+1 ; And temporary filename + LD BC,8 ; Set to move filename bytes only + LDIR + + LD C,OPEN ; Open new log file + LD DE,DEST + CALL BDOS + INC A ; Did file already exist? + + LD C,DELETE ; Prepare for delete + LD DE,DEST + CALL NZ,BDOS ; Yes, delete existing file + + LD C,MAKE ; Make new temporary file + LD DE,DEST + CALL BDOS + INC A + LD HL,DEST+1 + JP Z,NOROOM ; Exit if no more disk space + +; +; Read record from ZMD.LOG file +; + LD DE,CMDBUF ; Point to last log entry buffer + +RDRECD: PUSH DE ; Save current buffer position + CALL RSDMA ; Reset DMA + LD DE,FILE + LD C,READ + CALL BDOS + POP DE ; Last entry buffer address + OR A ; Read ok? + JP NZ,RERROR ; No + +; +; Now look for the end of the file overwriting OLINE with each entry found +; (from LF to LF). Upon ^Z (EOF) display last entry and get prompt for new +; one +; + LD HL,TBUF + +WRDLP: LD A,(HL) ; Get a character + AND 7FH + CP 7FH ; Delete character? + JP Z,NEXT ; Yes, don't store this character + + CP EOF ; End of file? + JP Z,GTNEW ; Yes, display last entry and get new one + + LD (DE),A ; Else store character in last entry buffer + INC DE ; Next positition in last entry buffer + CP LF ; Was it a line feed? + JP NZ,NEXT ; No, get next character + +; +; Check periodically (every LF) for user abort +; + LD A,1 ; Disable page pausing + CALL CKABRT ; Check for user requests + LD A,'$' ; Terminator for BDOS print function + LD (DE),A ; At end of last entry string + + PUSH HL + LD HL,CMDBUF + CALL DSTOR1 + POP HL + LD DE,CMDBUF + +NEXT: INC L + JP Z,RDRECD + JP WRDLP + +; +; Get new entry to ZMD.LOG. First display the last entry added to the file +; for use as a typing guide. +; +GTNEW: CALL ILPRTB + DB CR + DB 'Current format of ',0 + + LD HL,LOGNAM ; Point to log filename + CALL SHONM4 + + CALL SHWLAST ; Show last entry in log file + LD A,1 + LD (GOTLAST),A ; Show we've found/displayed last entry + +; +; Get the new entry. Process input - CR terminates entry. +; + LD A,79 + LD (LNLNGTH+1),A ; Set for up to 79 character string + LD (WRAP),A ; Disable word wrap + LD (CASEFLG),A ; Convert all input to uppercase + CALL DESC ; Go get string + LD A,0 + LD (CASEFLG),A ; Disable uppercase lock + JP Z,NOCHANG ; Z=CR entered on blank line + LD (HL),'$' ; Place a '$' for BDOS print function + +; +; Done with entry, ask for verification before writing to disk +; + CALL ILPRTB + DB CR,LF + DB ' Repeating to verify:',0 + + CALL SHWLAST ; Show last entry + LD DE,OLINE ; Point to new entry buffer + LD C,PRINT ; BDOS print function + CALL BDOS + + CALL ILPRTB + DB CR,LF + DB 'Correct? (Y/N): ',0 + +GETOK1: CALL INPUT ; Get repsonse + CALL UCASE ; Convert to uppercase + CP 'N' ; No? + JP NZ,GETOK2 ; Yes, all done + + CALL ILPRTB + DB 'No.' + DB CR,LF,0 + JP BEGIN1 + +GETOK2: CP 'Y' ; Yes? + JP NZ,GETOK1 ; Loop until we get a yes or no + + CALL ILPRTB + DB CR + DB 'Writing log entry to ',0 + LD HL,LOGNAM + CALL SHONM4 + CALL ILPRTB + DB '...',0 + CALL DSTOR ; Go store it in disk buffer + JP TDONE ; Transfer to disk +; +; +;------------------- +; UTILITIES SECTION +;------------------- +; +NOCHANG:CALL ILPRTB ; Else nothing typed, abort + DB CR,LF + DB '-- Log entry aborted... ',0 + + LD C,DELETE + LD DE,DEST + CALL BDOS ; Clean up (Erase 'ZMD .$$$' file) + + JP EXIT + +; +; Show last entry in ZMD.LOG file +; +SHWLAST:CALL ILPRTB + DB CR,LF,LF,0 + LD DE,CMDBUF ; Point to last entry buffer + LD C,PRINT ; BDOS print function + JP BDOS + +; +; These next are just dummy routines to satisfy external ZMDSUBS requests. +; They do nothing but leave alone. +; +DONE:: JP EXIT +TIME:: RET + + +GOTLAST:DB 0 + + + END + \ No newline at end of file diff --git a/Source/Images/Build.cmd b/Source/Images/Build.cmd index 5288225f..f2e81edb 100644 --- a/Source/Images/Build.cmd +++ b/Source/Images/Build.cmd @@ -1,13 +1,16 @@ @echo off setlocal +::call BuildDisk.cmd bp wbw_hd512 || exit /b +::goto :eof + echo. echo Building Floppy Disk Images... echo. call BuildDisk.cmd cpm22 wbw_fd144 ..\cpm22\cpm_wbw.sys || exit /b call BuildDisk.cmd zsdos wbw_fd144 ..\zsdos\zsys_wbw.sys || exit /b ::call BuildDisk.cmd nzcom wbw_fd144 ..\zsdos\zsys_wbw.sys || exit /b -call BuildDisk.cmd cpm3 wbw_fd144 ..\cpm3\cpmldr.sys || exit /b +::call BuildDisk.cmd cpm3 wbw_fd144 ..\cpm3\cpmldr.sys || exit /b ::call BuildDisk.cmd zpm3 wbw_fd144 ..\cpm3\cpmldr.sys || exit /b call BuildDisk.cmd ws4 wbw_fd144 || exit /b diff --git a/Source/Images/Makefile b/Source/Images/Makefile index 0bb2a201..7f5a082e 100644 --- a/Source/Images/Makefile +++ b/Source/Images/Makefile @@ -3,8 +3,7 @@ # SYSTEMS = ../CPM22/cpm_wbw.sys ../ZSDOS/zsys_wbw.sys ../CPM3/cpmldr.sys -FDIMGS = fd144_cpm22.img fd144_zsdos.img \ - fd144_cpm3.img fd144_ws4.img +FDIMGS = fd144_cpm22.img fd144_zsdos.img fd144_ws4.img HD512IMGS = hd512_cpm22.img hd512_zsdos.img hd512_nzcom.img \ hd512_cpm3.img hd512_zpm3.img hd512_ws4.img # HDIMGS += hd512_bp.img diff --git a/Source/Images/d_bp.txt b/Source/Images/d_bp.txt index 82cf1a97..e6ce4a2b 100644 --- a/Source/Images/d_bp.txt +++ b/Source/Images/d_bp.txt @@ -31,6 +31,8 @@ ../../Binary/Apps/zmterm.ovr 15: ../../Binary/Apps/zminit.ovr 15: ../../Binary/Apps/zmconfig.ovr 15: +../../Binary/Apps/zmd.com 15: + # #../../Binary/Apps/i2clcd.com 2: #../../Binary/Apps/i2cscan.com 2: diff --git a/Source/Images/d_cpm22.txt b/Source/Images/d_cpm22.txt index 6c9810d9..8e4245a9 100644 --- a/Source/Images/d_cpm22.txt +++ b/Source/Images/d_cpm22.txt @@ -27,6 +27,7 @@ d_cpm22/ReadMe.txt 0: ../../Binary/Apps/zmterm.ovr 0: ../../Binary/Apps/zminit.ovr 0: ../../Binary/Apps/zmconfig.ovr 0: +../../Binary/Apps/zmd.com 0: # #../../Binary/Apps/i2clcd.com 2: #../../Binary/Apps/i2cscan.com 2: diff --git a/Source/Images/d_cpm3.txt b/Source/Images/d_cpm3.txt index cf1e0335..036adcf8 100644 --- a/Source/Images/d_cpm3.txt +++ b/Source/Images/d_cpm3.txt @@ -43,6 +43,7 @@ ../../Binary/Apps/zmterm.ovr 0: ../../Binary/Apps/zminit.ovr 0: ../../Binary/Apps/zmconfig.ovr 0: +../../Binary/Apps/zmd.com 0: # #../../Binary/Apps/i2clcd.com 2: #../../Binary/Apps/i2cscan.com 2: diff --git a/Source/Images/d_nzcom.txt b/Source/Images/d_nzcom.txt index 3e1a2c3c..e9cc1e3f 100644 --- a/Source/Images/d_nzcom.txt +++ b/Source/Images/d_nzcom.txt @@ -44,6 +44,7 @@ d_zsdos/u0/*.* 0: ../../Binary/Apps/zmterm.ovr 0: ../../Binary/Apps/zminit.ovr 0: ../../Binary/Apps/zmconfig.ovr 0: +../../Binary/Apps/zmd.com 0: # #../../Binary/Apps/i2clcd.com 2: #../../Binary/Apps/i2cscan.com 2: diff --git a/Source/Images/d_zpm3.txt b/Source/Images/d_zpm3.txt index 7ef5d483..8bf0ce43 100644 --- a/Source/Images/d_zpm3.txt +++ b/Source/Images/d_zpm3.txt @@ -42,6 +42,7 @@ ../../Binary/Apps/zmterm.ovr 15: ../../Binary/Apps/zminit.ovr 15: ../../Binary/Apps/zmconfig.ovr 15: +../../Binary/Apps/zmd.com 15: # #../../Binary/Apps/i2clcd.com 2: #../../Binary/Apps/i2cscan.com 2: diff --git a/Source/Images/d_zsdos.txt b/Source/Images/d_zsdos.txt index 247ed7d7..a691c5df 100644 --- a/Source/Images/d_zsdos.txt +++ b/Source/Images/d_zsdos.txt @@ -40,6 +40,7 @@ d_cpm22/u0/XSUB.COM 0: ../../Binary/Apps/zmterm.ovr 0: ../../Binary/Apps/zminit.ovr 0: ../../Binary/Apps/zmconfig.ovr 0: +../../Binary/Apps/zmd.com 0: # #../../Binary/Apps/i2clcd.com 2: #../../Binary/Apps/i2cscan.com 2: diff --git a/Source/ver.inc b/Source/ver.inc index 7c0b44a9..e3ae1f93 100644 --- a/Source/ver.inc +++ b/Source/ver.inc @@ -2,4 +2,4 @@ #DEFINE RMN 1 #DEFINE RUP 1 #DEFINE RTP 0 -#DEFINE BIOSVER "3.1.1-pre.125" +#DEFINE BIOSVER "3.1.1-pre.126" diff --git a/Source/ver.lib b/Source/ver.lib index 2ab0d992..28df2c0e 100644 --- a/Source/ver.lib +++ b/Source/ver.lib @@ -3,5 +3,5 @@ rmn equ 1 rup equ 1 rtp equ 0 biosver macro - db "3.1.1-pre.125" + db "3.1.1-pre.126" endm