home *** CD-ROM | disk | FTP | other *** search
/ DP Tool Club 21 / CD_ASCQ_21_040595.iso / dos / prg / c / freedos3 / source / mode / mode.asm next >
Encoding:
Assembly Source File  |  1994-12-31  |  70.6 KB  |  1,843 lines

  1. ;-------------------------------------------------------------------------------
  2. ; MODE -- Mode setting utility for Free-DOS
  3. ;
  4. ; Written for the Free-DOS project.
  5. ; (c) Copyright 1994-1995 by K. Heidenstrom.
  6. ;
  7. ; This program is free software.  You may redistribute the source and
  8. ; executable in unmodified form and/or modify it for your own use only.
  9. ; If you make any changes, please include a note to that effect in the
  10. ; usage summary.  Do not distribute modified versions of this program.
  11. ; If you make significant improvements, please consider sending the
  12. ; change details to the author, kheidens@actrix.gen.nz on the Internet
  13. ; or K. Heidenstrom c/- P.O. Box 27-103, Wellington, New Zealand, so
  14. ; that other Free-DOS users may benefit.
  15. ;
  16. ; This program is provided "as-is" without any warranty of any kind,
  17. ; including the implied warranty of merchantability or fitness for a
  18. ; particular purpose.  In no event will the author be liable for any
  19. ; damages of any kind related to the use of this program.
  20. ;
  21. ; This program is based on, but not derived from, the DR-DOS MODE program.
  22. ; In writing this program, I used only the functionality and usage syntax
  23. ; of that program as a guideline.  I have no knowledge of the internal
  24. ; operation of the original MODE program and I did not disassemble it to
  25. ; determine how it works, nor did I use any code from it.  In other words,
  26. ; this program is an entirely original work, based only on the documented
  27. ; functionality and command syntax of the original MODE program.  KH.941230.
  28. ;
  29. ; See the accompanying file MODE.TXT for more information.
  30. ;
  31. ;-------------------------------------------------------------------------------
  32. ;
  33. ; This program must be assembled with Borland's TASM.  The syntax is:
  34. ;
  35. ;    tasm /ml /t /w2 mode;
  36. ;    tlink /t mode, mode, nul
  37. ;
  38. ;-------------------------------------------------------------------------------
  39. ;
  40. ; Modified:
  41. ;
  42. ; KH.941123.001  0.0.0    Started
  43. ; KH.941124.002        Design
  44. ; KH.941212.003        Design
  45. ; KH.941227.004        Parameter handlers working
  46. ; KH.941228.005        Typematic and COM functions working
  47. ; KH.941229.006        PARK functions implemented - still need LPT and video
  48. ; KH.941230.007        LPT infinite retries and redirection implemented
  49. ; KH.941231.008  1.0.0    First version, probably lots of bugs lurking here
  50.  
  51. Ver        EQU    1
  52. SubVer        EQU    0
  53. ModVer        EQU    0
  54. VerDate        EQU    "941231"        ; YYMMDD format
  55.  
  56. ;-------------------------------------------------------------------------------
  57.  
  58.         PAGE    ,132
  59.         IDEAL
  60.         %TITLE    "MODE -- Mode setting utility for Free-DOS"
  61.         NAME    MODE
  62.  
  63. ; Equates
  64.  
  65. AppName        EQU    "MODE"
  66.  
  67. ; Shorthand
  68.  
  69. MACRO    point    Reg,Label
  70.         mov    Reg,OFFSET Label
  71. ENDM
  72.  
  73. MACRO    DBBW    Byte1,Byte2,Word
  74.         DB    Byte1,Byte2
  75.         DW    Word
  76. ENDM
  77.  
  78. MACRO    prexp    Text1,Exp,Text2        ; Invoke in MASM mode only
  79.         %OUT    Text1 &Exp Text2
  80. ENDM
  81.  
  82. NL        EQU    13,10
  83.  
  84. ; Program
  85.  
  86.         SEGMENT    ComFile
  87.         ASSUME    cs:ComFile,ds:ComFile,es:nothing,ss:nothing
  88.  
  89.         ORG    0100h        ; Com-type file
  90. PROC    Main        near
  91.         jmp    Main2
  92. ENDP    Main
  93.  
  94. Signature    DB    "SIG: Free-DOS MODE.COM version ",Ver+"0",".",SubVer+"0",".",ModVer+"0",", ",VerDate,": "
  95. ;---------------
  96. ;!! Change the following line if you create a derivative version of this program
  97.         DB    "Original"
  98. ;---------------
  99.         DB    "  ",26        ; Ctrl-Z
  100. SigLen        =    $ - Main    ; Length of program signature
  101.  
  102. ; Resident data ================================================================
  103.  
  104. Int8Busy    DB    0        ; Flag whether int 8 is parking now
  105. Int13Busy    DB    0        ; Flag whether disk request in progress
  106. Park_Ticks    DW    0        ; Number of ticks or zero for Mode_Park
  107. ParkDrive0    DW    0        ; Control variable for parking drive 0
  108. ParkDrive1    DW    0        ; Control variable for parking drive 1
  109.  
  110. ; Printer persistent behaviour and serial port redirection control bytes:
  111. ;
  112. ; 7 6 5 4 3 2 1 0
  113. ; * . . . . . . .  Persistent mode: 0=Normal, 1=Persistent
  114. ; . * * * * . . .  Not used
  115. ; . . . . . * * *  Redirection: 0=None, 1-4 = COM1-COM4, 7=NUL
  116.  
  117. LPT_Ctrl    DB    0,0,0,0        ; Printer persistent/redirection control
  118.  
  119. ; Resident code ================================================================
  120.  
  121.         ASSUME    ds:nothing
  122.  
  123. ; Int 8 intercepter ------------------------------------------------------------
  124.  
  125. ; The int 8 intercepter hooks into the hardware timer tick interrupt and is
  126. ; used by the timeout park function.  When a hard drive is accessed via int
  127. ; 13h, the ParkDrive0 or ParkDrive1 variable is reset to the timeout period
  128. ; (Park_Ticks).  These two counter variables are handled separately.  Each is
  129. ; decremented (if non-zero) on every tick by the int 8 handler.  When the
  130. ; counter variable decrements to zero, the appropriate drive is parked.  This
  131. ; is done by the int 8 handler.
  132. ; When the int 8 handler decides to park a drive, it chains to the original
  133. ; handler first.  This causes an EOI to be issued, thus lower priority
  134. ; interrupts and other timer tick interrupts are enabled.  During the time
  135. ; that the hard drive is being parked, the Int8Busy flag is set.  If the
  136. ; int 8 intercepter is entered with this flag set, it will just chain to the
  137. ; original handler without performing its normal timeout function.
  138. ; If the ParkDrive0 or ParkDrive1 variables are zero, this indicates that they
  139. ; have timed out and the park has been performed; they will not be decremented.
  140. ; I know that on the tick when drive 0 is parked, the drive 1 tick count will
  141. ; not be decremented - I don't care; the timing error is insignificant.
  142.  
  143.         ASSUME    ds:nothing
  144.  
  145. PROC    New08        far        ; Int 08h (timer tick) intercepter
  146.         pushf            ; Preserve flags
  147.         cmp    [Int8Busy],0    ; Check whether we're already busy
  148.         jnz    GoOld08        ; If so, don't do anything
  149.         cmp    [ParkDrive0],0    ; Drive 0 already timed out?
  150.         jnz    DecrDrive0    ; If not
  151. CheckDrive1:    cmp    [ParkDrive1],0    ; Drive 1 already timed out?
  152.         jnz    DecrDrive1    ; If not
  153.  
  154. ; Idle - just jump to old handler
  155.  
  156. GoOld08:    popf            ; Fix stack
  157.         DB    0EAh        ; JMP xxxx:yyyy
  158. Old08Vec    = THIS    DWORD        ; Vector to original handler
  159. Old08Ofs    DW    0        ; Offset
  160. Old08Seg    DW    0        ; Segment
  161.  
  162. DecrDrive0:    dec    [ParkDrive0]    ; Count down ticks for drive 0 park
  163.         jnz    CheckDrive1    ; If not zero yet
  164.  
  165. ; Time to park drive 0 - make sure no disk operation is in progress first!
  166.  
  167.         cmp    [Int13Busy],0    ; Check for disk operation in progress
  168.         jz    DoPark0        ; If idle
  169.         inc    [ParkDrive0]    ; Try again next tick
  170.         jmp    SHORT CheckDrive1 ; Handle the drive 1 counter and exit
  171.  
  172. ; Safe to park drive 0 - do it
  173.  
  174. DoPark0:    inc    [Int8Busy]    ; Set busy flag
  175.         pushf            ; Simulate stack for an INT
  176.         call    [Old08Vec]    ; Chain to original handler (send EOI)
  177.         sti            ; Enable interrupts
  178.         push    dx        ; Preserve
  179.         push    cx        ; Preserve
  180.         push    ax        ; Preserve
  181.         mov    dx,80h        ; Drive I.D is 80h for first hard disk
  182.         DB    0B9h        ; MOV CX,nnnn
  183. MaxCylinder0    DW    0        ; Operand is modified by install code
  184.         jmp    SHORT DoSeek    ; Go to common do-seek and return stuff
  185.  
  186. ; Count down timer for drive 1
  187.  
  188. DecrDrive1:    dec    [ParkDrive1]    ; Count down ticks for drive 1 park
  189.         jnz    GoOld08        ; If not zero yet
  190.  
  191. ; Time to park drive 1 - make sure no disk operation is in progress first!
  192.  
  193.         cmp    [Int13Busy],0    ; Check for disk operation in progress
  194.         jz    DoPark1        ; If idle
  195.         inc    [ParkDrive1]    ; Try again next tick
  196.         jmp    SHORT GoOld08    ; Nothing more we can do at the moment
  197.  
  198. ; Safe to park drive 1 - do it
  199.  
  200. DoPark1:    inc    [Int8Busy]    ; Set busy flag
  201.         pushf            ; Simulate stack for an INT
  202.         call    [Old08Vec]    ; Chain to original handler (send EOI)
  203.         sti            ; Enable interrupts
  204.         push    dx        ; Preserve
  205.         push    cx        ; Preserve
  206.         push    ax        ; Preserve
  207.         mov    dx,81h        ; Drive I.D is 81h for second hard disk
  208.         DB    0B9h        ; MOV CX,nnnn
  209. MaxCylinder1    DW    0        ; Operand is modified by install code
  210. DoSeek:        jcxz    NoSuchDrive    ; If no such drive
  211.         mov    ah,0Ch        ; Seek function
  212.         pushf            ; Simulate INT
  213.         call    [cs:Old13Vec]    ; Don't issue int 13h - that would reset
  214. NoSuchDrive:    pop    ax        ;   the timeout counter!
  215.         pop    cx        ; Restore
  216.         pop    dx        ; Restore
  217.         mov    [Int8Busy],0    ; No longer busy
  218.         popf            ; Restore flags pushed at start of int 8
  219.         iret            ; Finally, return to application
  220. ENDP    New08
  221.  
  222. ; Int 13h intercepter ----------------------------------------------------------
  223.  
  224. ; The int 13h intercepter hooks into the disk function interrupt and is
  225. ; responsible for resetting the park timeout counters used by the int 8
  226. ; intercepter when the hard drive(s) is/are accessed.  The first two
  227. ; physical hard drives are supported.  It also maintains the Int13Busy
  228. ; flag which is used by the int 8 intercepter to ensure that it does not
  229. ; call int 13h while another floppy or hard disk request is in progress.
  230.  
  231.         ASSUME    ds:nothing
  232.  
  233. PROC    New13        far        ; Int 13h (disk functions) intercepter
  234.         pushf            ; Preserve flags
  235.         mov    [Int13Busy],1    ; Set int 13h busy flag
  236.         cmp    dl,80h        ; Check for hard drive 0
  237.         je    AccessDrive0    ; If so
  238.         cmp    dl,81h        ; Check for hard drive 1
  239.         je    AccessDrive1    ; If so
  240.  
  241. ; Jump to old handler
  242.  
  243. GoOld13:    popf            ; Fix stack
  244.         pushf            ; Simulate interrupt
  245.         DB    9Ah        ; CALL xxxx:yyyy
  246. Old13Vec    = THIS    DWORD        ; Vector to original handler
  247. Old13Ofs    DW    0        ; Offset
  248. Old13Seg    DW    0        ; Segment
  249.         mov    [Int13Busy],0    ; Clear busy flag
  250.         retf    2        ; Return with flags returned by BIOS
  251.  
  252. AccessDrive0:    push    [Park_Ticks]    ; Get timeout count
  253.         pop    [ParkDrive0]    ; Set timeout count for drive 0
  254.         jmp    SHORT GoOld13    ; Chain
  255.  
  256. AccessDrive1:    push    [Park_Ticks]    ; Get timeout count
  257.         pop    [ParkDrive1]    ; Set timeout count for drive 1
  258.         jmp    SHORT GoOld13    ; Chain
  259. ENDP    New13
  260.  
  261. ; Int 16h intercepter ----------------------------------------------------------
  262.  
  263. ; The int 16h intercepter provides the LOCK function on the typematic delay
  264. ; and rate.  Function calls with AH=3 are related to typematic functions.
  265. ; The normal function to set the typematic parameters is AH=3, AL=5 with BL
  266. ; and BH containing the two parameters (delay and rate).
  267. ; According to version 39 of Ralf Brown's Interrupt List, only subfunctions
  268. ; 0-6 (in AL) exist.  Subfunction 6 allows the typematic parameters to be
  269. ; requested on some machines; all other defined subfunctions relate to setting
  270. ; or modifying the typematic parameters.
  271. ; This intercepter intercepts all calls with AH=3 (i.e. all typematic related
  272. ; function calls) except AL=6 (request typematic parameters) and higher (these
  273. ; are currently undefined subfunctions) and replaces the call with AL=5 (set
  274. ; typematic parameters) using the 'locked' typematic parameters in BX.  When
  275. ; it intercepts the function, it preserves the typematic parameters provided
  276. ; in BX and the function and subfunction numbers provided in AX so that they
  277. ; may be returned unchanged to the caller.
  278. ; There may be potential problems with intercepting the typematic functions if
  279. ; functions greater than 6 are added in future.  At the moment, these will be
  280. ; passed through as normal.  If they would result in a change to the typematic
  281. ; settings, then the lock function will not successfully lock the parameters.
  282. ; Alternative approaches would be:
  283. ; 1. Instead of replacing subfunctions 0-5 with subfunction 5, simply ignore
  284. ;    these subfunctions.  This would cause extra difficulty in setting the
  285. ;    locked parameters from a transient copy of MODE.
  286. ; 2. Instead of passing undefined subfunctions (7 and higher) they could be
  287. ;    intercepted in the same way or just ignored.  This would cause trouble
  288. ;    if the future subfunction was supposed to return values from the BIOS,
  289. ;    because the BIOS function would not be called and the values would not
  290. ;    be returned, though the caller is expecting them.
  291.  
  292.         ASSUME    ds:nothing
  293.  
  294. PROC    New16        far        ; Int 16h (keyboard funcs) intercepter
  295.         pushf            ; Preserve caller flags
  296.         cmp    ah,3        ; Check for typematic functions
  297.         jne    NoIntercept16    ; If not
  298.         cmp    al,6        ; Check for subfunctions 0-5
  299.         jb    YesIntercept16    ; If so
  300.  
  301. ; Do not intercept this function call
  302.  
  303. NoIntercept16:    popf            ; Fix stack
  304.         DB    0EAh        ; JMP xxxx:yyyy
  305. Old16Vec    = THIS    DWORD        ; Vector to original handler
  306. Old16Ofs    DW    0        ; Offset
  307. Old16Seg    DW    0        ; Segment
  308.  
  309. ; Intercept this function call
  310.  
  311. YesIntercept16:    popf            ; Fix stack
  312.         push    bx        ; Keep parameters supplied by caller
  313.         push    ax        ; Keep caller-supplied function too
  314.         mov    al,5        ; Use standard set typematic function
  315.         DB    0BBh        ; MOV BX,nnnn
  316. Type_Parm    = THIS    WORD        ; Both typematic parameters
  317. Type_Rate    DB    0FFh        ; Typematic rate 0-31         MUST BE
  318. Type_Delay    DB    0FFh        ; Typematic delay 0-3        ADJACENT
  319.         pushf            ; Simulate an interrupt
  320.         call    [Old16Vec]    ; Call old handler
  321.         pop    ax        ; Restore AX provided by caller
  322.         pop    bx        ; Restore BX provided by caller
  323.         retf    2        ; Return flags returned by BIOS
  324. ENDP    New16
  325.  
  326. ; Int 17h intercepter ----------------------------------------------------------
  327.  
  328. ; The int 17h intercepter provides the persistent (infinite timeout) function
  329. ; and redirection function for printer I/O.  Up to four parallel ports are
  330. ; supported.  Each printer port (LPT1-4) has a control byte, which is allocated
  331. ; as listed in the variable declarations for LPT_Ctrl.    The port may be set
  332. ; for persistent mode, in which case this intercepter will continually retry
  333. ; if a timeout occurs while trying to send a character, and/or may be redirected
  334. ; to a serial port or to NUL, in which case this intercepter translates the
  335. ; specified request and calls the BIOS serial firmware functions (int 14h) or
  336. ; ignores the request.
  337. ; Other programs (e.g. TSRs, BIOS extensions) may hook into int 17h, but
  338. ; calls to these programs will have AH and/or DX out of range.    AH is the
  339. ; function number, which may be 0 (send character), 1 (initialise port), or
  340. ; 2 (get status).  DX is the port number, normally 0-2 for LPT1-3 but MODE
  341. ; will support up to LPT4 although DOS may not provide an LPT4 device.
  342.  
  343.         ASSUME    ds:nothing
  344.  
  345. PROC    New17        far        ; Int 17h (printer funcs) intercepter
  346.         pushf            ; Preserve those flags
  347.         cmp    ah,3        ; Check for invalid function
  348.         jae    NoIntercept17    ; If so, probably a TSR function
  349.         cmp    dx,4        ; Check for invalid port
  350.         jae    NoIntercept17    ; If so, probably a TSR function too
  351.  
  352. ; Get port function control byte for the specified parallel port
  353.  
  354.         push    bx        ; Preserve
  355.         mov    bx,dx        ; Port number
  356.         mov    bl,[LPT_Ctrl+bx] ; Get control byte for this port
  357.  
  358. ; First check for redirection.    If the port is redirected, handle this
  359. ; further on.
  360.  
  361.         test    bl,00000111b    ; Redirected?
  362.         jnz    IsRedirected    ; If so
  363.  
  364. ; Port is not redirected.  If persistent retries are not specified, just chain
  365. ; to the normal int 17h handler.  Also, if it's just a status call or an init
  366. ; printer call, chain to the normal handler too.  This leaves the case where
  367. ; the function is send-character and persistent operation is specified.
  368.  
  369.         test    bl,10000000b    ; Persistent?
  370.         jz    NoInter17BX    ; If not, just go to old int 17h handler
  371.         test    ah,ah        ; Sending a character?
  372.         jnz    NoInter17BX    ; If not, do old thing too
  373.  
  374. ; Persistent operation specified on non-redirected port - call the old int 17h
  375. ; handler repeatedly until it returns successful (no timeout)
  376.  
  377. PersistLoop:    xor    ah,ah        ; Set zero function number (send char)
  378.         pushf            ; Simulate stack for interrupt
  379.         call    [cs:Old17Vec]    ; Call original BIOS handler
  380.         test    ah,00000001b    ; Timeout flag set?
  381.         jnz    PersistLoop    ; If so, try again (and again...)
  382.         jmp    SHORT Return17BX ; Exit
  383.  
  384. ; Port is redirected.  Check for redirected to NUL - if so, just ignore the
  385. ; request and return a faked 'happy' status.  Otherwise, check the function
  386. ; number.  The possible functions are 0 (send character), 1 (initialise
  387. ; printer), and 2 (get status).  Function 1 (init printer) has no meaning
  388. ; when used with a serial port; it just reports the status, so it will be
  389. ; treated the same as function 2.
  390.  
  391. IsRedirected:    cmp    bl,7        ; Redirecting to NUL?
  392.         jne    RedirNotNUL    ; If not
  393.  
  394.         mov    ah,10010000b    ; Happy happy
  395.         jmp    SHORT Return17BX ; Exit
  396.  
  397. RedirNotNUL:    dec    bx        ; Get serial port (0-3) to redirect to
  398.         push    dx        ; Keep original port number
  399.         mov    dl,bl        ; Get serial port number
  400.         and    dl,01111111b    ; Turn off the persistent bit in DL
  401.         test    ah,ah        ; Check function number
  402.         jnz    Redir_GetStat    ; If not zero (get status)
  403.  
  404. ; Now sending a character to a redirected port.  Convert the call first, and
  405. ; after it completes, if a timeout was flagged, check whether the port was set
  406. ; for persistent retries, and retry if so.  During all this stuff, DX contains
  407. ; the redirected serial port number, not the original parallel port number.
  408.  
  409. SerialPersist:    mov    ah,1        ; Function to send character (serial)
  410.         int    14h        ; Send it
  411.         test    ah,10000000b    ; Successful?
  412.         jz    Redir_GetStat    ; If so
  413.         test    bl,10000000b    ; Persistent behaviour?
  414.         jnz    SerialPersist    ; If so, just try again etc etc
  415.         mov    bh,10000000b    ; Flag that we had a timeout
  416.  
  417. ; Had an error sending a character to the redirected port - just return
  418. ; the status byte normally - fall through...
  419.  
  420. ; Request status of redirected port.  The persistence flag is not relevant.
  421. ; The status is returned in AH, in the form:
  422. ;
  423. ;    7 6 5 4 3 2 1 0
  424. ;    * . . . . . . .  Printer ready (1 = ready, 0 = not ready; busy)
  425. ;    . * . . . . . .  Acknowledgement from printer, returned as 0
  426. ;    . . * . . . . .  Out of Paper from printer, returned as 0
  427. ;    . . . * . . . .  Selected signal from printer, returned as 1
  428. ;    . . . . * . . .  I/O error, returned as 0
  429. ;    . . . . . * * .  Not used, returned as 0
  430. ;    . . . . . . . *  Timeout error flag (true if set)
  431.  
  432. Redir_GetStat:    push    ax        ; Keep whatever was in AL
  433.         mov    ah,3
  434.         int    14h        ; Request serial port status
  435.  
  436. ; Serial function 3 gets the line status and modem status registers to AH
  437. ; and AL respectively.    I will use the CTS line, reported in bit 4 of AL,
  438. ; to indicate whether the printer is ready.  Bit 7 of AH reports the serial
  439. ; timeout error.  Now construct the appropriate printer status: r001000t
  440. ; where 'r' is the ready flag and 't' is the timeout error flag.
  441.  
  442.         or    ah,bh        ; OR in timeout from serial function 1
  443.         shl    ah,1        ; Get timeout flag to carry
  444.         mov    ah,00001000b    ; Get initial value shifted right once
  445.         rcl    ah,1        ; Incorporate timeout flag
  446.         test    al,00010000b    ; Is CTS active?
  447.         jz    NoCTS        ; If not
  448.         or    ah,10000000b    ; If so, set the ready flag
  449. NoCTS:        pop    bx        ; Get original AL value to BL
  450.         mov    al,bl        ; Restore AL as provided to call
  451.  
  452. ; Restore registers and return with result code in AH
  453.  
  454. Return17DXBX:    pop    dx        ; Restore DX
  455. Return17BX:    pop    bx        ; Restore BX
  456.         popf            ; Restore flags
  457.         iret            ; Return to caller
  458.  
  459. ; Pass the function call on to the BIOS int 17h handler
  460.  
  461. NoInter17BX:    pop    bx        ; Restore
  462. NoIntercept17:    popf            ; Fix stack
  463.         DB    0EAh        ; JMP xxxx:yyyy
  464. Old17Vec    = THIS    DWORD        ; Vector to original handler
  465. Old17Ofs    DW    0        ; Offset
  466. Old17Seg    DW    0        ; Segment
  467. ENDP    New17
  468.  
  469. ; TSR support ------------------------------------------------------------------
  470.  
  471. ; TSR residency detection is via int 2Fh.  When the transient copy wants to
  472. ; locate a resident copy of itself, if one exists, it issues int 2Fh with
  473. ; registers set as follows: AX = 0F73Fh, BX = 484Bh, ES = segment-paragraph
  474. ; of transient copy.
  475. ; The resident int 2Fh handler compares its own signature to the signature of
  476. ; the transient copy (the segment of the transient copy is provided in the ES
  477. ; register by the caller), and if they do not match, chains to the previous
  478. ; owner of int 2Fh.  If the signatures do match, the handler sets ES to the
  479. ; segment-paragraph of the resident copy, sets BX to 4D4Ah and returns.  The
  480. ; signature is assumed to start at offset 100h into the segment and must at
  481. ; least contain the program name and version number.
  482.  
  483.         ASSUME    ds:nothing
  484.  
  485. PROC    New2F        far
  486.         pushf
  487.         sti            ; Enable hardware interrupts
  488.         cmp    ax,0F73Fh    ; Calling me?
  489.         jne    NotForMe    ; If not
  490.         cmp    bx,484Bh    ; Check BX too
  491.         jne    NotForMe    ; If not
  492.         push    ds        ; Preserve these regs
  493.         push    di
  494.         push    si
  495.         push    cx
  496.         push    cs        ; Set DS to this segment
  497.         pop    ds
  498.         mov    si,OFFSET Main    ; Point to our signature
  499.         mov    di,si        ; Point to possible transient signature
  500.         mov    cx,SigLen    ; Number of chars to compare
  501.         cld            ; Upwards compare direction
  502.         repe    cmpsb        ; Compare signatures
  503.         pop    cx        ; Restore registers
  504.         pop    si
  505.         pop    di
  506.         pop    ds
  507.         jne    NotForMe    ; If signatures did not match
  508.         mov    bx,4D4Ah    ; Flag acknowledgement
  509.         push    cs
  510.         pop    es        ; Set ES to resident segment
  511.         popf            ; Restore flags
  512.         iret            ; Return to caller
  513. NotForMe:    popf            ; Restore flags
  514.         DB    0EAh        ; JMP xxxx:yyyy
  515. Old2FVec    = THIS    DWORD        ; Vector to original handler
  516. Old2FOfs    DW    0        ; Offset
  517. Old2FSeg    DW    0        ; Segment
  518. ENDP    New2F
  519.  
  520. ; End of resident / start of transient =========================================
  521.  
  522.         MASM
  523. Discard        =    $        ; Start of transient portion
  524. TSRParas    =    (OFFSET (Discard-@curseg+15) SHR 4)
  525.         prexp    <Resident size:> %(TSRParas SHL 4) < bytes>
  526.         IDEAL
  527.  
  528. ; Transient messages -----------------------------------------------------------
  529.  
  530. ; Note - I have used American spelling ("color") in the help text but not
  531. ; in the internal comments or labels.
  532.  
  533. DOSVersMsg    DB    AppName,": Requires DOS version 2.0 or later",NL,"$"
  534. MemoryEM    DB    162,AppName,": Insufficient memory",NL,0
  535. UsageEM        DB    255,NL,AppName," -- Free-DOS mode setting and miscellaneous utility  Version ",Ver+"0",".",SubVer+"0",".",ModVer+"0",", ",VerDate
  536.         DB    NL,9,"(c) Copyright 1994-1995 by K. Heidenstrom (kheidens@actrix.gen.nz)"
  537.         DB    NL
  538.         DB    NL,AppName," Videomode[,Lines]  - Select video mode and lines, Videomode may be:"
  539.         DB    NL,9,9,"MONO",9,9,"- 80-column monochrome (MDA and Hercules)"
  540.         DB    NL,9,9,"BW40, BW80",9,"- 40-column and 80-column color-suppressed CGA"
  541.         DB    NL,9,9,"CO40, CO80",9,"- 40-column and 80-column color"
  542.         DB    NL,9,"Lines may be 25, 43, or 50 (43 requires EGA or VGA, 50 requires VGA)"
  543.         DB    NL,AppName," COMn:r,p,d,s  - Set serial port parameters (not permanent):"
  544.         DB    NL,9,"n = port number (1-4)"
  545.         DB    NL,9,"r = baud rate (50, 110, 150, 300, 600, 1200, 2400, 4800, 9600,"
  546.         DB    NL,9,9,"14400, 19200, 28800, 38400, 57600, 115200, abbreviations)"
  547.         DB    NL,9,"p = parity (e = even, n = none, o = odd)"
  548.         DB    NL,9,"d = data bits (5-8)"
  549.         DB    NL,9,"s = stop bits (1-2)"
  550.         DB    NL,AppName," LPTn:P  - Infinite timeout on parallel port (1-4) (",AppName," will go resident)"
  551.         DB    NL,AppName," LPTn:=COMx  - Redirect printer output to COM port (",AppName," will go resident)"
  552.         DB    NL,AppName," LPTn:=NUL:  - Redirect printer output to NUL (",AppName," will go resident)"
  553.         DB    NL,AppName," LPTn:  - Remove redirection and infinite timeout on parallel port"
  554.         DB    NL,AppName," PARK[,minutes[:seconds]]  - Park now or after idle (",AppName," will go resident)"
  555.         DB    NL,AppName," DELAY=d RATE=r [LOCK]  - Set keyboard typematic delay and rate:"
  556.         DB    NL,9,"d = delay (1-4)   r = rate (1-32)   LOCK = permanent (will go resident)"
  557.         DB    NL,0
  558. BadCmdModeEM    DB    246,AppName,": Only one command allowed per invocation",NL,0
  559. OutRangeEM    DB    241,AppName,": Parameter out of range - check the help text",NL,0
  560. LinesVModeEM    DB    242,AppName,": 43-line and 50-line modes are only usable with CO80 mode",NL,0
  561. BadBaudEM    DB    243,AppName,": Unknown baud rate specified",NL,0
  562. TypeParamEM    DB    244,AppName,": Must specify both DELAY and RATE for typematic setting",NL,0
  563. NoSuchSPortEM    DB    115,AppName,": Specified serial port does not exist",NL,0
  564. SerialSetMsg    DB    AppName,": Serial port parameters set (not locked)",NL,"$"
  565. SetNormalMsg    DB    AppName,": LPT"
  566. SetNormalLPT    DB    "1 set to normal mode",NL,"$"
  567. NoHDiskEM    DB    117,AppName,": No hard drive(s) found to park!",NL,0
  568. Parked1Msg    DB    AppName,": Hard drive has been parked, switch off or press Ctrl-C to return to DOS",NL,"$"
  569. Parked2Msg    DB    AppName,": Hard drives have been parked, switch off or press Ctrl-C to return to DOS",NL,"$"
  570. ParkInst1Msg    DB    AppName,": Timed park function installed for one hard drive, ",AppName," is resident",NL,"$"
  571. ParkInst2Msg    DB    AppName,": Timed park function installed for two hard drives, ",AppName," is resident",NL,"$"
  572. ParkResMsg    DB    AppName,": Timed park timeout parameter updated in resident copy",NL,"$"
  573. ParkEnab1Msg    DB    AppName,": Timed park function enabled in resident copy for one hard drive",NL,"$"
  574. ParkEnab2Msg    DB    AppName,": Timed park function enabled in resident copy for two hard drives",NL,"$"
  575. TypeSetMsg    DB    AppName,": Typematic parameters set (not locked)",NL,"$"
  576. LockInstMsg    DB    AppName,": Typematic parameters locked, resident portion of ",AppName," installed",NL,"$"
  577. ResLockedMsg    DB    AppName,": Typematic parameters now locked by resident portion of ",AppName,NL,"$"
  578. TResUpdateMsg    DB    AppName,": Locked typematic parameters updated",NL,"$"
  579. UnsuppVidEM    DB    118,AppName,": Specified video mode is not supported on this machine",NL,0
  580. SetPersistMsg    DB    AppName,": Parallel port set for infinite retry on timeout",NL,"$"
  581. SetRedNULMsg    DB    AppName,": Parallel port redirected to NUL:",NL,"$"
  582. SetRedirMsg    DB    AppName,": Parallel port redirected to COM"
  583. SetRedirCOM    DB    "0",NL,"$"
  584. ResInstallMsg    DB    AppName,": Resident portion of MODE is now installed",NL,"$"
  585.  
  586. ; Transient tables -------------------------------------------------------------
  587.  
  588. ; Command table - all entries are in lower case and will be compared to the
  589. ; characters from the command line ORed with 00100000 binary.
  590.  
  591. CmdTable:    DBBW    "mono",0,Param_MONO
  592.         DBBW    "bw40",0,Param_BW40
  593.         DBBW    "bw80",0,Param_BW80
  594.         DBBW    "co40",0,Param_CO40
  595.         DBBW    "co80",0,Param_CO80
  596.         DBBW    "com",0,Param_Com
  597.         DBBW    "lpt",0,Param_LPT
  598.         DBBW    "park",0,Param_PARK
  599.         DBBW    "delay=",0,Param_DELAY
  600.         DBBW    "rate=",0,Param_RATE
  601.         DBBW    "lock",0,Param_LOCK
  602.         DB    0        ; End of table marker
  603.  
  604. ; Despatch table
  605.  
  606. ModeTbl        DW    BadUsage    ; No parameters
  607.         DW    DoMode_Vid    ; Set video mode (and lines)
  608.         DW    DoMode_Com    ; Set serial port parameters
  609.         DW    DoMode_LPT    ; Redirect parallel port or no timeout
  610.         DW    DoMode_Park    ; Park now or after an idle period
  611.         DW    DoMode_Typematic ; Set typematic parameters
  612.  
  613. ; Baud rate table - the value read from the command line is 16-bit therefore
  614. ; 115200 will appear as 49664 (115200 % 65536).  Therefore if the user enters
  615. ; 49664 bps, this will be interpreted as 115200 bps rather than flagging an
  616. ; error.
  617. ; This table takes care of minimal text abbreviations, e.g. 96 for 9600 bps.
  618. ; The second number in each entry is the raw baud rate divisor value.
  619. ; I initially used DW 115200 / rate but TASM gives incorrect results (but no
  620. ; error message, though!) so I had to divide the 115200 and the baud rates
  621. ; by 10 in each calculation.
  622.  
  623. BaudTbl:    DW    12,    11520/120    ; 1200
  624.         DW    14,    11520/1440    ; 14400
  625.         DW    19,    11520/1920    ; 19200
  626.         DW    24,    11520/240    ; 2400
  627.         DW    28,    11520/2880    ; 28800
  628.         DW    38,    11520/3840    ; 38400
  629.         DW    48,    11520/480    ; 4800
  630.         DW    50,    11520/5        ; 50
  631.         DW    57,    11520/5760    ; 57600
  632.         DW    96,    11520/960    ; 9600
  633.         DW    110,    11520/11    ; 110
  634.         DW    115,    11520/11520    ; 115200
  635.         DW    144,    11520/1440    ; 14400
  636.         DW    150,    11520/15    ; 150
  637.         DW    192,    11520/1920    ; 19200
  638.         DW    288,    11520/2880    ; 28800
  639.         DW    300,    11520/30    ; 300
  640.         DW    384,    11520/3840    ; 38400
  641.         DW    576,    11520/5760    ; 57600
  642.         DW    600,    11520/60    ; 600
  643.         DW    1152,    11520/11520    ; 115200
  644.         DW    1200,    11520/120    ; 1200
  645.         DW    2400,    11520/240    ; 2400
  646.         DW    4800,    11520/480    ; 4800
  647.         DW    9600,    11520/960    ; 9600
  648.         DW    14400,    11520/1440    ; 14400
  649.         DW    19200,    11520/1920    ; 19200
  650.         DW    28800,    11520/2880    ; 28800
  651.         DW    38400,    11520/3840    ; 38400
  652.         DW    49664,    11520/11520    ; 115200 % 65536 = 49664
  653.         DW    57600,    11520/5760    ; 57600
  654.         DW    65535,    11520/960    ; End of table
  655.  
  656. ; LPT subfunction table - used after LPTn has been parsed to determine
  657. ; which of the LPT subfunctions are requested - persistent, back to normal,
  658. ; or redirect.
  659.  
  660. LPTTable:    DBBW    ":=com",0,LPT_Redirect
  661.         DBBW    ":=nul:",0,LPT_RedNUL
  662.         DBBW    ":=nul",0,LPT_RedNUL
  663.         DBBW    ":p",0,LPT_Persist
  664.         DBBW    ":",0,LPT_Normal
  665.         DB    0        ; End of table marker
  666.  
  667. ; Video BIOS function 1A00h returns display combination codes in BL and BH.
  668. ; BL contains the DCC for the active display, BH contains the DCC for the
  669. ; inactive display.  The following table maps DCC values to bitflags for the
  670. ; modes that are supported, which will be ORed into the VidHave variable that
  671. ; indicates what modes are acceptable.    I'm not sure what the story is with
  672. ; the PGA and the MCGA.  They may support 43-line mode.
  673. ;
  674. ; References:
  675. ;
  676. ; Programmer's Guide to PC & PS/2 Video Systems
  677. ; Copyright 1987 by Richard Wilton
  678. ; Published by Microsoft Press
  679. ; ISBN 1-55615-103-9
  680. ;
  681. ; EGA/VGA  A Programmer's Reference Guide
  682. ; Copyright 1988 by Bradley Dyck Kliewer
  683. ; Published by Intertext Publications, McGraw-Hill Book Company, New York, NY
  684. ; ISBN 0-07-035089-2.  Library of Congress Catalog Card number 87-83102.
  685.  
  686. Vid_50        =    8 ; â”Œâ”€â”€â”€â”€â”€ Supports 50 lines in mode 3
  687. Vid_43        =    4 ; â”‚┌──── Supports 43 lines in mode 3
  688. Vid_3        =    2 ; â”‚│┌─── Supports mode 0-3
  689. Vid_7        =    1 ; â”‚││┌── Supports mode 7
  690. ;                â”‚│││
  691. DCCTable    DB    00000000b    ; 0    No display adapter
  692.         DB    00000001b    ; 1    MDA/Hercules
  693.         DB    00000010b    ; 2    CGA
  694.         DB    00000000b    ; 3    Reserved
  695.         DB    00000111b    ; 4    EGA
  696.         DB    00000111b    ; 5    EGA with monochrome monitor
  697.         DB    00000011b    ; 6    PGA (Professional Grph. Adapter)
  698.         DB    00001111b    ; 7    VGA with monochrome monitor
  699.         DB    00001111b    ; 8    VGA with colour display
  700.         DB    00000000b    ; 9    Reserved
  701.         DB    00000011b    ; 10    MCGA with digital colour monitor
  702.         DB    00000011b    ; 11    MCGA with monochrome monitor
  703.         DB    00000011b    ; 12    MCGA with colour monitor
  704.  
  705. ; Transient data ---------------------------------------------------------------
  706.  
  707. Already        DW    0        ; Segment-para of resident copy or zero
  708.  
  709. ; Command line modes:
  710.  
  711. Mode_None    =    0
  712. Mode_Vid    =    2        ; Set video mode (and lines)
  713. Mode_Com    =    4        ; Set serial port parameters
  714. Mode_LPT    =    6        ; Redirect parallel port or no timeout
  715. Mode_Park    =    8        ; Park now or after an idle period
  716. Mode_Typematic    =    10        ; Set typematic parameters
  717.  
  718. ; Note - several of these byte parameters have an extra zero byte; this is
  719. ; because they are sometimes accessed as words.
  720.  
  721. CmdMode        DB    0,0        ; Command line mode - one of above
  722.  
  723. Vid_Mode    DB    0FFh,0        ; Video mode number for Mode_Vid
  724. Vid_Lines    DB    0        ; Number of lines (0=25, 1=43, 2=50)
  725. VidHave        DB    0        ; Video adapter mode flags
  726.  
  727. Com_Port    DB    0FFh,0        ; Port number for Mode_Com
  728. Com_BRD        DW    0        ; Baud rate divisor
  729. Com_LCR        DB    0        ; Line control register value
  730.  
  731. LPT_Port    DB    0FFh,0        ; Port number for Mode_LPT
  732. LPT_Func    DB    0,0        ; 1-4 = redirect, 80h = persist,
  733.                     ; 0 = return to normal operation
  734.  
  735. Type_Lock    DB    0        ; Typematic lock flag (0/1)
  736.  
  737. ; Transient code ===============================================================
  738.  
  739.         ASSUME    ds:ComFile
  740.  
  741. PROC    Main2        near
  742.         mov    ah,30h
  743.         int    21h
  744.         cmp    al,2        ; Expect DOS 2.0 or later
  745.         jae    DOS_Ok
  746.         mov    dx,OFFSET DOSVersMsg
  747.         mov    ah,9
  748.         int    21h
  749.         int    20h
  750.  
  751. BadUsage:    point    bx,UsageEM    ; Incorrect usage
  752.  
  753. ; Errexit code - aborts the program with a specified message and errorlevel.
  754. ; On entry, BX points to a control string within the current code segment,
  755. ; which consists of a one-byte errorlevel followed by a null-terminated error
  756. ; message which may be blank.  This code assumes DOS version 2.0 handle
  757. ; functions are supported.  After writing the error message to StdErr, DOS
  758. ; function 30h (terminate with return code) is used to terminate the program.
  759.  
  760. ErrExit:    push    [WORD cs:bx]    ; Errorlevel onto stack
  761.         inc    bx        ; Point to error message
  762.         call    ErrWriteMsg    ; Display
  763.         pop    ax        ; Errorlevel
  764. ErrExitQ:    mov    ah,4Ch
  765.         int    21h        ; Terminate with errorlevel in AL
  766.  
  767. ErrWriteMsg:    mov    cx,2        ; Handle of Standard Error device
  768. WriteMsg:    push    cs
  769.         pop    ds        ; Make DS valid
  770.         mov    dx,bx        ; Point DX for later
  771. Err_Parse1:    inc    bx        ; Bump pointer
  772.         cmp    [BYTE ds:bx-1],0 ; Hit null terminator yet?
  773.         jnz    Err_Parse1    ; If not, loop
  774.         xchg    cx,bx        ; Get address of null terminator
  775.         sub    cx,dx        ; Subtract offset of start of text
  776.         dec    cx        ; Adjust to correct number of chars
  777.         jz    Err_Parse2    ; If no text present
  778.         mov    ah,40h        ; Function to write to file or device
  779.         int    21h        ; Write error message to StdErr
  780. Err_Parse2:    ret            ; Return to exit code or whoever
  781.  
  782. ; Check amount of available memory ---------------------------------------------
  783.  
  784. DOS_Ok:        mov    ax,cs        ; Get current segment
  785.         DB    5        ; ADD AX,nnnn (avoid TASM warning!)
  786.         DW    MemParas    ; Get paragraph past end of program
  787.         cmp    ax,[WORD ds:2]    ; Check against paragraph of mem top
  788.         mov    bx,OFFSET MemoryEM ; Prepare for error
  789.         jae    ErrExit        ; If not enough memory
  790.         point    sp,WorkStackTop    ; Relocate stack to safe area
  791.  
  792. ; Detect resident copy ---------------------------------------------------------
  793.  
  794. ; This code locates the resident copy of this program, if any.    It modifies
  795. ; the first three bytes of the program image then calls int 2Fh to look for
  796. ; a response from a resident handler for the same program.  If a resident copy
  797. ; was found, ES contains the segment-paragraph of the resident copy.  Note
  798. ; that the signature is completed manually here - this ensures that only
  799. ; copies of this program which have actually _executed_ will have the correct
  800. ; signature, and copies which are in disk buffers will not.  Though this is not
  801. ; really required (due to the int 2Fh residency check method), it is tidy.
  802.  
  803.         mov    [WORD Main],"TS" ; Complete the signature
  804.         mov    [BYTE Main+2],"D"
  805.         mov    ax,0F73Fh
  806.         mov    bx,484Bh
  807.         int    2Fh        ; TSR installation check
  808.         cmp    bx,4D4Ah
  809.         jne    NoResident
  810.         mov    [Already],es    ; Store segment-para of resident copy
  811. NoResident:
  812.  
  813. ; Command tail parsing ---------------------------------------------------------
  814.  
  815. ; Scan the command tail for the start of a text token, then locate the token
  816. ; in the command table CmdTable.  If not found, issue usage error message.
  817. ; If found, call the parameter handler, with SI pointing past the part of the
  818. ; token that matched the command table entry.  The handler function will return
  819. ; if no error was found.  The parameter handler may jump to BadUsage or ErrExit
  820. ; to abort the program with a syntax message or a specific error message.
  821. ; On return from the parameter handler (i.e. if no error was found), SI points
  822. ; past the text processed as part of the parameter, and should point to
  823. ; whitespace or the C/R that terminates the command tail.
  824.  
  825.         push    cs
  826.         pop    es        ; Make sure ES addresses to ComFile
  827.         cld            ; Upwards string direction
  828.         mov    si,81h        ; Prepare for command tail parsing
  829. Parameter:    lodsb            ; Next character
  830.         cmp    al,13        ; Check for end of command tail
  831.         je    DoneParms    ; If so
  832.         cmp    al," "        ; Check for space or other whitespace
  833.         jbe    Parameter    ; Skip it
  834.         dec    si        ; Point to first char of parameter
  835.         point    di,CmdTable    ; Point to start of command table
  836.         call    StringLCComp    ; Find a match in the command table
  837.         call    bx        ; Call appropriate handler or BadUsage
  838.         jmp    SHORT Parameter    ; Next parameter
  839.  
  840. ; Reached end of command line
  841.  
  842. DoneParms:    push    cs
  843.         pop    es        ; Make sure ES points to transient copy
  844.         mov    bx,[WORD CmdMode] ; Get mode format number
  845.         jmp    [ModeTbl+bx]    ; Go to appropriate handler
  846. ENDP    Main2
  847.  
  848. ; Parameter handlers ===========================================================
  849.  
  850. ; These handlers are called with SI pointing just past the end of the command
  851. ; token that was scanned.  If the handler detects an error it may jump to
  852. ; BadUsage (syntax errors) or ErrExit (for specific errors).  If no problems
  853. ; were found, it returns with SI pointing to the next character to be scanned,
  854. ; which may be whitespace or the final C/R at the end of the command tail.
  855. ; The handler is responsible for making sure that it doesn't scan past the
  856. ; terminating C/R unless this will cause an error, in other words if the
  857. ; handler returns, it must not scan past the final C/R - if it finds the end
  858. ; of the command tail during processing without an error, it must return with
  859. ; SI pointing to the terminating C/R.
  860.  
  861. ; Video parameter handlers -----------------------------------------------------
  862.  
  863. ; Video mode setting handlers - these check for more than one mode provided
  864. ; on the command line and parse the optional ,nn number of lines, but do not
  865. ; check that the specified mode is valid for the video adapter(s) present.
  866. ; If the ,nn parameter is present, the mode must be CO80 otherwise an error
  867. ; is reported.
  868.  
  869. PROC    VidParams    near        ; All video mode parameters
  870. Param_BW40:    mov    al,0        ; Colour-suppressed 40-column mode
  871.         DB    3Dh        ; Skip next two-byte instruction
  872. Param_CO40:    mov    al,1        ; Colour 40-column mode
  873.         DB    3Dh        ; Skip next two-byte instruction
  874. Param_BW80:    mov    al,2        ; Colour-suppressed 80-column mode
  875.         DB    3Dh        ; Skip next two-byte instruction
  876. Param_CO80:    mov    al,3        ; Colour 80-column mode (incl 43/50)
  877.         DB    3Dh        ; Skip next two-byte instruction
  878. Param_MONO:    mov    al,7        ; Monochrome video mode
  879.  
  880. ; AL = desired video mode number - check that mode has not already been given
  881.  
  882.         xchg    al,[Vid_Mode]    ; Set video mode number
  883.         inc    al        ; Make sure there wasn't one already
  884.         jnz    BadUsage1    ; If there was
  885.  
  886. ; Check for optional number of lines
  887.  
  888.         cmp    [BYTE si],","    ; Check for optional number of lines
  889.         jne    VidParam_Done    ; If no error
  890.         inc    si        ; Skip comma
  891.         call    ReadDecimal    ; Get the number
  892.         sub    ax,25        ; Check for 25 lines
  893.         jz    LinesOK        ; If so
  894.         sub    ax,43-25-1    ; Convert 43 to 1
  895.         cmp    ax,1        ; Check for it
  896.         je    LinesOK        ; If correct
  897.         sub    ax,50-43-1    ; Convert 50 to 2
  898.         cmp    ax,2        ; Check for it
  899.         je    LinesOK        ; If so
  900.         jmp    OutOfRange    ; Go to out of range error stuff
  901. LinesOK:    mov    [Vid_Lines],al    ; Store (0/1/2)
  902.         test    ax,ax        ; Check for 25-line
  903.         jz    VidParam_Done    ; If so, don't require CO80
  904.         cmp    [Vid_Mode],3    ; Only allow 43/50-line mode with CO80
  905.         point    bx,LinesVModeEM    ; Prepare for error
  906.         jne    GoErrExit    ; If error
  907. VidParam_Done:    mov    al,Mode_Vid    ; Get mode
  908.         jmp    CheckMode    ; Set command mode, check for conflict
  909. BadUsage1:    jmp    BadUsage
  910. ENDP    VidParams
  911.  
  912. ; Serial port parameter handler ------------------------------------------------
  913.  
  914. PROC    Param_Com    near
  915.         mov    cx,4*256+1    ; Adjust by 1, check against 4
  916.         call    GetAdjCheck    ; Get parameter, adjust and check
  917.         xchg    al,[Com_Port]    ; Store, get old value
  918.         inc    al        ; Check for multiple commands
  919.         jz    ComPortOK    ; If alright
  920.         jmp    BadCmdMode    ; If more than one port command
  921. ComPortOK:    lodsb            ; Get colon
  922.         cmp    al,":"        ; Validate
  923.         jne    BadUsage1    ; If wrong
  924.         call    ReadDecimal    ; Parse baud rate
  925.         point    bx,BaudTbl-4    ; Point to before baud rate table
  926. NextBaud:    add    bx,4        ; Bump pointer
  927.         cmp    ax,[bx]        ; Does value match?
  928.         ja    NextBaud    ; If not, keep looking
  929.         je    GotBaud        ; If so
  930. BadBaudRate:    point    bx,BadBaudEM    ; Scanned whole table - bad baud rate
  931. GoErrExit:    jmp    ErrExit        ; Go to error exit
  932.  
  933. GotBaud:    mov    ax,[bx+2]    ; Get baud rate divisor value
  934.         mov    [Com_BRD],ax    ; Store it
  935.         call    SkipComma    ; Check for and skip a comma
  936.         lodsb            ; Get odd/even/none
  937.         or    al,00100000b    ; Convert to lower case
  938.         cmp    al,"n"        ; Check for none
  939.         je    GotParity    ; If so
  940.         cmp    al,"o"        ; Check for odd
  941.         je    SetParity    ; If so
  942.         cmp    al,"e"        ; Check for even
  943.         jne    BadUsage1    ; If bad syntax
  944.         or    [Com_LCR],00010000b ; Set even parity select
  945. SetParity:    or    [Com_LCR],00001000b ; Set parity enable
  946. GotParity:    call    SkipComma    ; Skip next comma
  947.         mov    cx,4*256+5    ; Adjust by 5, check against 4
  948.         call    GetAdjCheck    ; Get parameter, adjust and check it
  949.         or    [Com_LCR],al    ; Combine into LCR value
  950.         call    SkipComma    ; Skip next comma
  951.         mov    cx,2*256+1    ; Adjust by 1, check against 2
  952.         call    GetAdjCheck    ; Get parameter, adjust and check it
  953.         shl    al,1        ; Shift bit
  954.         shl    al,1        ;   to b2
  955.         or    [Com_LCR],al    ; Combine into LCR value
  956.         mov    al,Mode_Com    ; Get mode
  957.         jmp    CheckMode    ; Set command mode, check for conflict
  958. ENDP    Param_Com
  959.  
  960. ; Parallel port parameter handler ----------------------------------------------
  961.  
  962. PROC    Param_LPT    near
  963.         mov    cx,4*256+1    ; Adjust by 1, check against 4
  964.         call    GetAdjCheck    ; Get parameter, adjust and check
  965.         xchg    al,[LPT_Port]    ; Store, get old value
  966.         inc    al        ; Check for multiple commands
  967.         jnz    BadCmdMode    ; If more than one port command
  968.         point    di,LPTTable    ; Point to subfunction table
  969.         call    StringLCComp    ; Scan for subfunction
  970.         jmp    bx        ; Go to appropriate handler or BadUsage
  971.  
  972. LPT_Redirect:    mov    cx,4*256+1    ; Adjust by 1, check against 4
  973.         call    GetAdjCheck    ; Get parameter, adjust and check it
  974.         inc    ax        ; Convert 0-3 to 1-4, port num in b0-2
  975.         DB    3Dh        ; Skip next two-byte instruction
  976. LPT_RedNUL:    mov    al,7        ; Fake redirection to NUL as COM7
  977.         DB    3Dh        ; Skip next two-byte instruction
  978. LPT_Persist:    mov    al,10000000b    ; Persistent
  979.         DB    3Dh        ; Skip next two-byte instruction
  980. LPT_Normal:    mov    al,0        ; 'Back to normal' action code
  981.  
  982. GotLPTFunc:    mov    [LPT_Func],al    ; Store LPT action code
  983.         mov    al,Mode_LPT    ; Get command mode
  984.         jmp    SHORT CheckMode    ; Set command mode, check for conflict
  985. ENDP    Param_LPT
  986.  
  987. ; Hard disk park parameter handler ---------------------------------------------
  988.  
  989. Param_PARK:    cmp    [BYTE si],","    ; Check for optional idle timeout
  990.         jne    NoIdle        ; If not
  991.         inc    si        ; Skip comma
  992.         mov    cx,51*256    ; Limit it to 50 minutes
  993.         call    GetAdjCheck    ; Get and check minutes
  994.         mov    dx,1092        ; Number of ticks in one minute (approx)
  995.         mul    dx        ; Get number of ticks
  996.         mov    [Park_Ticks],ax    ; Store
  997.         cmp    [BYTE si],":"    ; Check for colon and seconds
  998.         jne    NoSeconds    ; If not
  999.         inc    si        ; Skip colon
  1000.         mov    cx,60*256    ; Limit it to 59 seconds
  1001.         call    GetAdjCheck    ; Get and check seconds
  1002.         mov    dx,18        ; Number of ticks in one second (approx)
  1003.         mul    dx        ; Get number of ticks
  1004.         add    [Park_Ticks],ax    ; Store
  1005. NoSeconds:    cmp    [Park_Ticks],0    ; Check we got something
  1006.         jz    OutOfRange    ; If out of range
  1007. NoIdle:        mov    al,Mode_Park    ; Get mode
  1008.         jmp    SHORT CheckMode    ; Set command mode, check for conflict
  1009.  
  1010. BadCmdMode:    point    bx,BadCmdModeEM ; Error - more than one command type
  1011.         jmp    ErrExit        ; Go to error exit stuff
  1012.  
  1013. ; Typematic parameter handlers -------------------------------------------------
  1014.  
  1015. Param_DELAY:    mov    cx,4*256+1    ; Adjust by 1, check against 4
  1016.         call    GetAdjCheck    ; Read, adjust and check parameter
  1017.         xchg    al,[Type_Delay]    ; Set it, get old value
  1018.         inc    al        ; Check for only DELAY= parameter
  1019.         jnz    BadUsage2    ; If not
  1020.         jmp    SHORT Typematic
  1021.  
  1022. Param_RATE:    mov    cx,32*256+1    ; Adjust by 1, check against 32
  1023.         call    GetAdjCheck    ; Read, adjust and check parameter
  1024.         neg    al        ; Convert to negative
  1025.         add    al,31        ; Convert 0-31 to 31-0
  1026.         xchg    al,[Type_Rate]    ; Set it, get old value
  1027.         inc    al        ; Check for only RATE= parameter
  1028.         jnz    BadUsage2    ; If not
  1029.         jmp    SHORT Typematic
  1030.  
  1031. Param_LOCK:    mov    [Type_Lock],1    ; Set lock flag
  1032. Typematic:    mov    al,Mode_Typematic ; Command mode
  1033.  
  1034. ; Check and set command mode ---------------------------------------------------
  1035.  
  1036. CheckMode:    xchg    al,[CmdMode]    ; Set it
  1037.         test    al,al        ; Check that it was previously zero
  1038.         jz    ARet1        ; If so
  1039.         cmp    al,[CmdMode]    ; Command mode same as before?
  1040.         je    ARet1        ; If so
  1041.         jmp    SHORT BadCmdMode ; If not, error!
  1042. ARet1:        ret
  1043.  
  1044. BadUsage2:    jmp    BadUsage    ; Go to bad usage exit point
  1045.  
  1046. ; String matcher ---------------------------------------------------------------
  1047.  
  1048. ; This function accepts a string at SI and a pointer to a table in DI, and
  1049. ; scans the table for a matching string.  Each entry in the table consists of
  1050. ; a lower-case string, with a null-terminator, followed by a two-byte value
  1051. ; that will be returned by this function if a match is found (successful).
  1052. ; The end of the table is marked by a zero-length string.  The strings in the
  1053. ; table are in lower case and the case of the source string will be converted
  1054. ; by ORing each character with 00100000b.  Note that this will produce some
  1055. ; unexpected effects on characters which are not alphabetic - for example, a
  1056. ; space will be translated into a "0" (among others!)
  1057.  
  1058. ; In:    SI -> String to be matched
  1059. ;    DI -> Table of strings to match with, format as described above
  1060. ; Out:    CF = Success/failure: NC = Success, CY = Failure
  1061. ;    BX = Value from table if successful, otherwise points to BadUsage
  1062. ;    SI unchanged if unsuccessful, points past matched string if successful
  1063. ; Note:    ES and DS must be equal
  1064.  
  1065. PROC    StringLCComp    near
  1066. StrLCComp1:    xor    bx,bx        ; Zero index
  1067. StrLCComp2:    mov    al,[si+bx]    ; Char from source string
  1068.         or    al,00100000b    ; Convert to lower case
  1069.         cmp    al,[di+bx]    ; Match?
  1070.         je    StrLCComp4    ; If so
  1071.         xor    al,al        ; Null char
  1072. StrLCComp3:    scasb            ; Check for terminator on table entry
  1073.         jnz    StrLCComp3    ; If not yet
  1074.         inc    di
  1075.         inc    di        ; Skip return parameter
  1076.         cmp    [BYTE di],0    ; Scanned whole table?
  1077.         jnz    StrLCComp1    ; If not, try next entry
  1078.         stc            ; If so, set carry indicating no match
  1079.         point    bx,BadUsage    ; No match - set pointer to BadUsage
  1080.         ret
  1081. StrLCComp4:    inc    bx        ; Next position
  1082.         cmp    [BYTE di+bx],0    ; Has whole parameter matched?
  1083.         jnz    StrLCComp2    ; If not, keep checking
  1084.         add    si,bx        ; Skip matched string, clear carry
  1085.         mov    bx,[di+bx+1]    ; Get parameter from table
  1086.         ret            ; Return carry clear - successful
  1087. ENDP    StringLCComp
  1088.  
  1089. ; Decimal input ----------------------------------------------------------------
  1090.  
  1091. ; This function calls ReadDecimal to parse a decimal formatted ASCII number
  1092. ; at DS:SI, then adjusts the number by subtracting the value provided in CL
  1093. ; and validates the result against the value provided in CH; if the value
  1094. ; is greater than or equal to the value in CH, it aborts the program with the
  1095. ; parameter out of range error message.  It returns the result in AL.
  1096. ; AX, BX, DX and SI are destroyed.  The parameter before and after adjustment
  1097. ; is not allowed to exceed 255.
  1098.  
  1099. PROC    GetAdjCheck    near
  1100.         call    ReadDecimal
  1101.         test    ah,ah        ; Greater than 255?
  1102.         jnz    OutOfRange    ; If so
  1103.         sub    al,cl        ; Adjust
  1104.         cmp    al,ch        ; Check against limit + 1
  1105.         jae    OutOfRange    ; If bad
  1106.         ret            ; If alright
  1107. OutOfRange:    point    bx,OutRangeEM    ; If not, illegal number given
  1108.         jmp    ErrExit        ; Go to error exit stuff
  1109. ENDP    GetAdjCheck
  1110.  
  1111. ; This function scans a number in ASCII format from the command tail and returns
  1112. ; the binary equivalent of the number.    It is limited to numbers in the range 0
  1113. ; to 65535, and does not distinguish 'no number found' from 'zero found' (both
  1114. ; return zero result).    Before calling, SI must point to the first character to
  1115. ; be scanned.  On return, SI will point past the last valid character, i.e. to
  1116. ; the character which terminated the evaluation, and AX will contain the result.
  1117. ; The function destroys AX, BX, DX, and SI, and assumes that DF is clear.
  1118.  
  1119. PROC    ReadDecimal    near
  1120.         xor    bx,bx        ; Clear result
  1121. ReadNumLp:    lodsb            ; Get a character
  1122.         sub    al,"0"        ; Convert "0"-"9" to 0-9
  1123.         cmp    al,10        ; Check for valid char
  1124.         jae    ReadNumFin    ; If not, terminator
  1125.         cbw            ; Zero AH
  1126.         xchg    ax,bx        ; New digit to BL, old total to AX
  1127.         mov    dx,10        ; Ten to unused register
  1128.         mul    dx        ; Multiply old value by ten
  1129.         add    bx,ax        ; Add to new digit
  1130.         jmp    SHORT ReadNumLp    ; Loop for more
  1131. ReadNumFin:    dec    si        ; Point to just past number
  1132.         xchg    ax,bx        ; Return value in AX
  1133.         ret            ; Finished
  1134. ENDP    ReadDecimal
  1135.  
  1136. ; Skip comma -------------------------------------------------------------------
  1137.  
  1138. PROC    SkipComma    near
  1139.         cmp    [BYTE si],","    ; Check for comma
  1140.         jne    BadUsage2    ; If not
  1141.         inc    si        ; Skip it
  1142.         ret
  1143. ENDP    SkipComma
  1144.  
  1145. ; Command mode handlers ========================================================
  1146.  
  1147. ; After the command tail has been processed by the parameter handler functions,
  1148. ; the appropriate command mode handler is called to perform the desired MODE
  1149. ; operation.  At this point, the command type has been determined, the command
  1150. ; tail has been parsed and parameters have been processed and syntax-checked,
  1151. ; and the appropriate variables have been set.
  1152.  
  1153. ; Video command handler --------------------------------------------------------
  1154.  
  1155. ; First, determine the hardware configuration.    The following adapters will be
  1156. ; supported: EGA or VGA, MDA/Hercules, CGA, and a system with both CGA and
  1157. ; MDA/Hercules.
  1158.  
  1159. PROC    DoMode_Vid    near
  1160.         mov    ax,1A00h
  1161.         int    10h        ; Get display combination code (EGA/VGA)
  1162.         cmp    al,1Ah        ; Will be equal if supported
  1163.         jne    NoDCCFunc    ; If unsupported
  1164.         push    bx        ; Keep inactive monitor parms
  1165.         xor    bh,bh        ; Zero hibyte
  1166.         cmp    bl,13        ; Check for valid DCC
  1167.         jae    BadDCC1        ; If bad
  1168.         mov    al,[DCCTable+bx] ; Get flags for this display type
  1169.         or    [VidHave],al    ; Enable those features
  1170. BadDCC1:    pop    bx        ; Restore inactive monitor data in BH
  1171.         mov    bl,bh        ; To BL
  1172.         xor    bh,bh        ; Zero hibyte again
  1173.         cmp    bl,13        ; Check for valid DCC
  1174.         jae    BadDCC2        ; If bad
  1175.         mov    al,[DCCTable+bx] ; Get flags for this display type
  1176.         or    [VidHave],al    ; Enable those features
  1177. BadDCC2:    jmp    SHORT GotDisplay ; We know what displays are present
  1178.  
  1179. ; Function 1A00h (get display combination) did not work - we have a CGA, MDA
  1180. ; or Hercules, or old EGA card.  Now try function 12h, subfunction 10h (in BL).
  1181. ; This returns the amount of video RAM, default mode (colour/monochrome) and
  1182. ; feature and configuration bits, for the EGA and VGA.
  1183.  
  1184. NoDCCFunc:    mov    ax,1200h    ; Function 12h
  1185.         mov    bx,0FF10h    ; Subfunction code in BL
  1186.         int    10h        ; Get some EGA/VGA information
  1187.         inc    bh        ; See whether anything happened
  1188.         jz    NoEGAFunc    ; If nothing happened
  1189.         or    [VidHave],Vid_43+Vid_3+Vid_7 ; 43-line, colour and mono
  1190.         jmp    SHORT GotDisplay ; We know what display is present
  1191.  
  1192. ; Oh dear, this poor user has a MDA/Hercules and/or CGA card!  Look for a CRTC
  1193. ; at the two standard addresses - 3B4h for MDA/Hercules, and 3D4h for CGA.
  1194.  
  1195. NoEGAFunc:    mov    dx,3B4h        ; Try for monochrome first
  1196.         call    TestForCRTC    ; Is there a CRTC there?
  1197.         jne    NoCRTC1        ; If not
  1198.         or    [VidHave],Vid_7    ; If so, we can support monochrome mode
  1199. NoCRTC1:    mov    dx,3D4h        ; Try for colour
  1200.         call    TestForCRTC    ; Is there a CRTC there?
  1201.         jne    NoCRTC2        ; If not
  1202.         or    [VidHave],Vid_3    ; If so, we can support colour modes
  1203. NoCRTC2:
  1204.  
  1205. ; We now know which video modes are allowed - this is determined by VidHave.
  1206. ; Now make sure that the desired mode is allowed.  First, check for 43-line
  1207. ; and 50-line modes specified and reject the request if not appropriate.
  1208.  
  1209. GotDisplay:    cmp    [Vid_Lines],1    ; 43-line or 50-line specified?
  1210.         jb    Not43_50    ; If not
  1211.  
  1212. ; Either 43-line or 50-line mode was specified - therefore it must be
  1213. ; standard colour mode (mode 3, CO80).    Now check that 43-line or 50-line
  1214. ; mode is allowed on this machine.
  1215.  
  1216.         ja    Test_50        ; If 50-line
  1217.         test    [VidHave],Vid_43 ; Is 43-line - is it allowed?
  1218.         jnz    VModeOK        ; If so, continue
  1219.  
  1220. Test_50:    test    [VidHave],Vid_50 ; Is 50-line - is it allowed?
  1221.         jnz    VModeOK        ; If so
  1222.  
  1223. BadVideo:    point    bx,UnsuppVidEM    ; Error - video mode is not supported
  1224.         jmp    ErrExit        ; Go to standard error exit stuff
  1225.  
  1226. Not43_50:    cmp    [Vid_Mode],4    ; Check for colour mode specified
  1227.         jae    NotColour    ; If not
  1228.         test    [VidHave],Vid_3    ; Are colour modes allowed?
  1229.         jz    BadVideo    ; If not
  1230.         jmp    SHORT VModeOK    ; Continue
  1231.  
  1232. NotColour:    test    [VidHave],Vid_7    ; Is monochrome mode allowed?
  1233.         jz    BadVideo    ; If not
  1234.  
  1235. ; If the system does not have EGA/VGA (i.e. the Vid_43 bit in VidHave is clear)
  1236. ; it may be necessary to modify the equipment list byte at 0000:0410 according
  1237. ; to the type of video mode required.
  1238.  
  1239. VModeOK:    test    [VidHave],Vid_43+Vid_50 ; Have EGA or better?
  1240.         jnz    LeaveEquip    ; If so
  1241.  
  1242.         xor    ax,ax
  1243.         mov    ds,ax        ; Address BDA with DS
  1244.         ASSUME    ds:nothing
  1245.         mov    al,[ds:410h]    ; Get equipment list byte
  1246.         and    al,11001111b    ; Mask off adapter type bits
  1247.         or    al,00100000b    ; Set bit for CGA
  1248.         cmp    [Vid_Mode],7    ; Check for mono
  1249.         jne    NoEquipMono    ; If not
  1250.         or    al,00110000b    ; Set bits for MONO
  1251. NoEquipMono:    mov    [ds:410h],al    ; Store it back
  1252.         push    cs
  1253.         pop    ds
  1254.         ASSUME    ds:ComFile
  1255.  
  1256. ; If this is a VGA system, we must select the number of scan lines first,
  1257. ; using video BIOS function 12h (in AH) subfunction 30h (BL).  This accepts
  1258. ; a number in AL which determines the number of scan lines.  This number
  1259. ; will be 1 for 43-line mode, to select 350 scan lines, or 2 for 25-line
  1260. ; and 50-line modes, to select 400 scan lines.
  1261. ; There are three possible values for Vid_Lines.  They map as follows:
  1262. ;
  1263. ;    Vid_Lines    Char lines    Value in AL    Scan lines
  1264. ;    0        25        2        400
  1265. ;    1        43        1        350
  1266. ;    2        50        2        400
  1267.  
  1268. LeaveEquip:    cmp    [Vid_Mode],3    ; Check for CO80 specified
  1269.         jne    NoSetScanlines    ; If not
  1270.         test    [VidHave],Vid_50 ; Scan lines function supported?
  1271.         jz    NoSetScanlines    ; If not
  1272.  
  1273.         mov    ah,12h        ; Function for setting scan lines
  1274.         mov    bl,30h        ; For alphanumeric mode
  1275.         mov    al,[Vid_Lines]    ; 43-line = 1 = 350, 50-line = 2 = 400
  1276.         shr    al,1        ; Set carry if 43-line
  1277.         mov    al,2        ; Prepare for 25-line or 50-line
  1278.         sbb    al,0        ; Decrement if it was 43-line
  1279.         int    10h        ; Set number of scan lines
  1280.  
  1281. ; Finally, select the specified mode.  If 43-line or 50-line mode, load the
  1282. ; 8x8 ROM character set.
  1283.  
  1284. NoSetScanlines:    mov    ax,[WORD Vid_Mode] ; Get the specified mode
  1285.         xor    bx,bx        ; Page zero
  1286.         int    10h        ; Set it
  1287.         cmp    [Vid_Lines],0    ; 43-line or 50-line?
  1288.         jz    NoLoad8x8    ; If not
  1289.         mov    ax,1112h    ; Load 8x8 ROM character set
  1290.         int    10h        ; Do it
  1291.  
  1292. NoLoad8x8:    jmp    Exit0        ; Finished
  1293.  
  1294. ENDP    DoMode_Vid
  1295.  
  1296. ; The following function tries to determine whether a CRTC (CRT controller
  1297. ; chip used in video cards) exists at the I/O base address specified in DX
  1298. ; on entry.  If a CRTC is found, it returns with the zero flag set, otherwise
  1299. ; it returns with the zero flag clear.
  1300.  
  1301. PROC    TestForCRTC    near
  1302.         mov    al,0Fh        ; Use cursor location low byte
  1303.         cli            ; No nasty interrupts please
  1304.         out    dx,al        ; Select the register
  1305.         inc    dx        ; Point to the register
  1306.         in    al,dx        ; Get current value
  1307.         jmp    SHORT $+2    ; Short delay
  1308.         jmp    SHORT $+2    ; And another
  1309.         mov    ah,al        ; Save it
  1310.         not    al        ; Reverse all bits
  1311.         out    dx,al        ; Stick it back
  1312.         jmp    SHORT $+2    ; Short delay
  1313.         jmp    SHORT $+2    ; And another
  1314.         in    al,dx        ; Read the register again
  1315.         not    al        ; Reverse all bits
  1316.         jmp    SHORT $+2    ; Short delay
  1317.         jmp    SHORT $+2    ; And another
  1318.         out    dx,al        ; Put it back
  1319.         sti            ; Interrupts back on
  1320.         cmp    al,ah        ; Same?
  1321.         ret
  1322. ENDP    TestForCRTC
  1323.  
  1324. ; Serial port command handler --------------------------------------------------
  1325.  
  1326. ; The functions here are simple - program the specified serial port with the
  1327. ; specified line communication parameters (baud rate, parity, data bits, and
  1328. ; stop bits).
  1329.  
  1330. PROC    DoMode_Com    near
  1331.         xor    bx,bx
  1332.         mov    es,bx        ; Address BIOS data area
  1333.         mov    bx,[WORD Com_Port] ; Get port number
  1334.         shl    bx,1        ; Double for word sized I/O addresses
  1335.         mov    dx,[es:bx+400h]    ; Get I/O address of specified port
  1336.         test    dx,dx        ; Does the port exist?
  1337.         point    bx,NoSuchSPortEM ; Prepare for no
  1338.         jz    Com_Error    ; If it doesn't exist, abort with error
  1339.         push    dx        ; Keep base pointer
  1340.         add    dx,3        ; Point to LCR
  1341.         mov    al,10000000b    ; Set DLAB (divisor latch access bit)
  1342.         out    dx,al        ; Set the LCR value
  1343.         pop    dx        ; Point back to lobyte of divisor
  1344.         mov    ax,[Com_BRD]    ; Get baud rate divisor
  1345.         out    dx,al        ; Set the lobyte
  1346.         inc    dx        ; Point to hibyte
  1347.         mov    al,ah        ; Get hibyte
  1348.         out    dx,al        ; Set the hibyte
  1349.         inc    dx
  1350.         inc    dx        ; Point to the LCR again
  1351.         mov    al,[Com_LCR]    ; Get specified LCR value
  1352.         out    dx,al        ; Set it
  1353.  
  1354.         point    dx,SerialSetMsg    ; Message
  1355.         jmp    MessageExit0    ; Issue message and terminate
  1356.  
  1357. Com_Error:    jmp    ErrExit        ; Go to error exit handler
  1358. ENDP    DoMode_Com
  1359.  
  1360. ; Parallel port command handler ------------------------------------------------
  1361.  
  1362. ; There are four parallel port commands - the LPTn:P command which specifies
  1363. ; persistent behaviour, i.e. infinite timeout, on the specified parallel port,
  1364. ; the LPTn:=COMx command, which redirects output from the specified parallel
  1365. ; port to the specified serial port, the LPTn:=NUL command, which redirects
  1366. ; output from the specified parallel port to nowhere, and the LPTn: command
  1367. ; which removes the infinite timeout (if active) and the parallel port
  1368. ; redirection (if active) for the specified parallel port.
  1369. ; Note - the specified parallel port may be LPT1 through LPT4, and MODE does
  1370. ; not ensure that the port physically exists.  This allows (for example) LPT2
  1371. ; to be redirected to a serial port when only LPT1 is present, giving an
  1372. ; "extra" addressable port.  However, if a parallel port is redirected to a
  1373. ; serial port, MODE does check that the serial port exists.  Here is the
  1374. ; behaviour table:
  1375. ;
  1376. ; Func    Inst?    Hook?    Action                Message
  1377. ;
  1378. ; Norm    N    N/A    None                Port set to normal
  1379. ; Norm    Y    N    Set param in resident copy    Port set to normal
  1380. ; Norm    Y    Y    Set param in resident copy    Port set to normal
  1381. ; Pers    N    N/A    Hook this copy, go resident    Port state, resident
  1382. ; Pers    Y    N    Hook resident copy, set param    Port state
  1383. ; Pers    Y    Y    Set param in resident copy    Port state
  1384. ; Redr    N    N/A    Hook this copy, go resident    Port state, resident
  1385. ; Redr    Y    N    Hook resident copy, set param    Port state, resident
  1386. ; Redr    Y    Y    Set param in resident copy    Port state, resident
  1387. ;
  1388. ; Notes:
  1389. ;
  1390. ; Some of the messages are a bit misleading; this is due to the great number
  1391. ; of possible combinations of installed/not installed, hooked/not hooked,
  1392. ; existing port mode, new port mode, and command specified.  For example, if
  1393. ; MODE is not installed, or if the installed copy is not hooked into the
  1394. ; printer function interrupt, and the LPTn: command (return to normal) is
  1395. ; issued, MODE says that the port has been set for normal operation, even
  1396. ; though it was never set to anything else.  Also, if the mode is changed
  1397. ; with any of the three command types, MODE does not take notice of the
  1398. ; previous mode.  Thus if you entered MODE LPT1:P twice, it would say the
  1399. ; same thing both times - that the port is now configured for persistent
  1400. ; retries - even though the second time it was already configured for
  1401. ; persistent retries before MODE was invoked.
  1402. ;
  1403. ; Redirection to NUL is faked as redirection to COM7.
  1404.  
  1405. PROC    DoMode_LPT    near
  1406.         mov    bx,[WORD LPT_Port] ; Get port number
  1407.         push    ds
  1408.         pop    es        ; Will address vars with ES
  1409.         cmp    [Already],0    ; Is there an installed copy?
  1410.         jz    NotInst        ; If not
  1411.         mov    es,[Already]    ; Get resident copy
  1412.  
  1413. ; Check for set-normal command
  1414.  
  1415. NotInst:    cmp    [LPT_Func],0    ; Check for set-to-normal command
  1416.         jnz    NotSetNormal    ; If not
  1417.         mov    [es:LPT_Ctrl+bx],0 ; Remove special settings
  1418.         add    [SetNormalLPT],bl ; Set appropriate port number
  1419.         point    dx,SetNormalMsg    ; Message
  1420. GoMsgExit0:    jmp    MessageExit0    ; Exit with message, errorlevel zero
  1421.  
  1422. ; Was not LPTn: command - must be LPTn:P or LPTn:=COMx/NUL.  If it's the
  1423. ; latter, now check that the specified serial port (to be redirected to)
  1424. ; exists.  Treat a port value of 7 specially - it indicates redirection to
  1425. ; NUL.
  1426.  
  1427. NotSetNormal:    js    NoCheckRedir    ; If redirect command
  1428.  
  1429.         mov    bx,[WORD LPT_Func] ; Get serial port to redirect _to_
  1430.         cmp    bl,7        ; Is it COM7?
  1431.         je    NoCheckRedir    ; If so, don't check!
  1432.         shl    bx,1        ; Double for word sized I/O addresses
  1433.         xor    ax,ax
  1434.         mov    ds,ax        ; Address BIOS data area with DS
  1435.         ASSUME    ds:nothing
  1436.         mov    dx,[ds:bx+3FEh]    ; Get I/O address of serial port
  1437.         push    cs
  1438.         pop    ds        ; DS back to normal
  1439.         ASSUME    ds:ComFile
  1440.         test    dx,dx        ; Does the port exist?
  1441.         point    bx,NoSuchSPortEM ; Prepare for no
  1442.         jz    Com_Error    ; If it doesn't exist, abort with error
  1443.  
  1444. ; Was not LPTn: command - must be LPTn:P or LPTn:=COMx/NUL.  For these
  1445. ; commands, MODE must either already _be_ resident, or must _go_ resident.
  1446. ; Check for installed copy and address either this copy, or the resident copy.
  1447. ; Have ES pointing either to local segment, or to installed copy (if there
  1448. ; is an installed copy).
  1449.  
  1450. NoCheckRedir:    mov    ax,[es:Old17Ofs] ; Get offset of chain vector
  1451.         or    ax,[es:Old17Seg] ; Has vector been hooked?
  1452.         jnz    WasHooked    ; If so
  1453.  
  1454. ; Either this copy (which will go resident), or the resident copy, did not have
  1455. ; int 17h hooked - so hook it now.
  1456.  
  1457.         push    es
  1458.         pop    ds
  1459.         ASSUME    ds:nothing    ; DS now addresses whichever copy
  1460.         mov    ax,3517h
  1461.         int    21h        ; Get int 17h vector
  1462.         mov    [ds:Old17Ofs],bx
  1463.         mov    [ds:Old17Seg],es ; Store it
  1464.         point    dx,New17
  1465.         mov    ax,2517h
  1466.         int    21h        ; Set new vector
  1467.  
  1468.         push    ds
  1469.         pop    es        ; ES back to this or resident copy
  1470.         push    cs
  1471.         pop    ds
  1472.         ASSUME    ds:ComFile    ; DS back to normal
  1473.  
  1474. ; Either this copy, or the resident copy, is hooked in to int 17h and needs
  1475. ; to have the appropriate parallel port parameter set.    ES addresses whichever
  1476. ; copy is or will be installed.
  1477.  
  1478. WasHooked:    mov    bx,[WORD LPT_Port] ; Get port number again
  1479.         mov    al,[LPT_Func]    ; Get command again
  1480.         test    al,10000000b    ; Redirect or persistent?
  1481.         js    SetPersist    ; If persistent
  1482.         and    [es:LPT_Ctrl+bx],11111000b ; Mask off old redirection
  1483. SetPersist:    or    [es:LPT_Ctrl+bx],al ; Set new redirection or persistent
  1484.  
  1485. ; Issue the appropriate message - either port set for infinite timeout or
  1486. ; port redirected
  1487.  
  1488.         mov    al,[LPT_Func]    ; Get function
  1489.         test    al,al        ; Which is it?
  1490.         point    dx,SetPersistMsg ; Prepare for set to persistent
  1491.         js    GotPersRedir    ; If so
  1492.         cmp    al,7        ; Check for redirection to NUL
  1493.         point    dx,SetRedNULMsg    ; Prepare for it
  1494.         je    GotPersRedir    ; If it was
  1495.         point    dx,SetRedirMsg    ; Set to redirected
  1496.         add    [SetRedirCOM],al ; Set serial port number in message
  1497.  
  1498. ; Now have message, so do we need to install or not?
  1499.  
  1500. GotPersRedir:    cmp    [Already],0    ; Already installed?
  1501.         jnz    GoMsgExit0    ; If so, just issue the message
  1502.         mov    ah,9
  1503.         int    21h        ; Display message
  1504.         point    dx,ResInstallMsg ; Inform user MODE is now resident
  1505.         jmp    MessageTSR0    ; Issue message and TSR
  1506. ENDP    DoMode_LPT
  1507.  
  1508. ; Park command handler ---------------------------------------------------------
  1509.  
  1510. ; The park command is either immediate (i.e. no minutes:seconds value provided)
  1511. ; or a timed park.  First, check that at least one hard disk is present.  If
  1512. ; the park is immediate, just park the hard drives and issue a message then
  1513. ; wait for a reboot, etc.  In the second case, we either go resident with the
  1514. ; timer and disk function interrupts hooked, or update the timed park parameter
  1515. ; in the resident copy.
  1516.  
  1517. PROC    DoMode_Park    near
  1518.         mov    dl,80h        ; Test first hard disk for Ready
  1519.         mov    ah,10h
  1520.         int    13h
  1521.         point    bx,NoHDiskEM    ; Prepare for error
  1522.         jnc    HaveHDisk    ; If alright
  1523.         jmp    ErrExit        ; If no hard disk present!
  1524.  
  1525. HaveHDisk:    mov    ax,[Park_Ticks]    ; Get number of ticks specified
  1526.         test    ax,ax        ; Any park time specified?
  1527.         jnz    TimedPark    ; If so
  1528.  
  1529. ; Immediate park was requested - ignore any resident copy, if present.    Get
  1530. ; end cylinder for drive 0 and park it, check for existence of a second drive
  1531. ; and if it exists, do the same
  1532.  
  1533.         mov    ah,8        ; Get drive parameters function
  1534.         int    13h        ; Do it
  1535.         mov    dl,80h        ; Reinstate drive number
  1536.         mov    ah,0Ch        ; Seek function
  1537.         int    13h        ; Do it
  1538.         point    si,Parked1Msg    ; Prepare for only one hard disk parked
  1539.  
  1540.         inc    dx        ; Test second hard disk for Ready
  1541.         mov    ah,10h
  1542.         int    13h
  1543.         jc    NoDisk1        ; If no second hard disk
  1544.  
  1545.         mov    ah,8        ; Get drive parameters function
  1546.         int    13h        ; Do it
  1547.         mov    dl,80h        ; Reinstate drive number
  1548.         mov    ah,0Ch        ; Seek function
  1549.         int    13h        ; Do it
  1550.         point    si,Parked2Msg    ; Two hard drives were parked
  1551.  
  1552. ; Display informative message and wait
  1553.  
  1554. NoDisk1:    mov    dx,si        ; Get message
  1555.         mov    ah,9        ; Display message function
  1556.         int    21h        ; Call DOS
  1557.  
  1558. ParkLoop:    mov    ah,8        ; Keyboard input, no echo, break active
  1559.         int    21h        ; Call DOS
  1560.         jmp    SHORT ParkLoop    ; Forever
  1561.  
  1562. ; Timed park function has been requested - check whether a resident copy is
  1563. ; present.  If not, initialise the timeout counters in this copy, store the
  1564. ; drive parameters, and go resident with the appropriate message.  If there
  1565. ; is a resident copy present, update the resident parameters, including
  1566. ; resetting the two actual timeout counters, and check whether the resident
  1567. ; copy has its timed park function active.  This is true if the int 13h chain
  1568. ; vector in the resident copy is nonzero.  If so, exit with the message that
  1569. ; the resident parameters have been modified.  If not, determine the drive
  1570. ; parameters and set them in the resident copy, hook the vectors into the
  1571. ; resident copy and exit with the message that the timed park function has
  1572. ; now been enabled in the resident copy.
  1573.  
  1574. TimedPark:    cmp    [Already],0    ; Already have a resident copy?
  1575.         jnz    WasAlready4    ; If so
  1576.  
  1577. ; Timed park was requested and there is no resident copy - set some parameters
  1578. ; and go resident
  1579.  
  1580.         mov    [ParkDrive0],ax    ; Initialise timeout
  1581.         mov    [ParkDrive1],ax    ;   for both drives
  1582.  
  1583.         mov    ah,8        ; Get drive parameters function
  1584.         int    13h        ; Do it
  1585.         mov    [MaxCylinder0],cx ; Store number of last cylinder
  1586.         point    si,ParkInst1Msg    ; Prepare for only one hard disk found
  1587.  
  1588.         mov    dl,81h        ; Test second hard disk for Ready
  1589.         mov    ah,10h
  1590.         int    13h
  1591.         jc    NoDisk2        ; If no second hard disk
  1592.         mov    ah,8        ; Get drive parameters function
  1593.         int    13h        ; Do it
  1594.         mov    [MaxCylinder1],cx ; Store number of last cylinder
  1595.         point    si,ParkInst2Msg    ; Point to message for two drives
  1596.  
  1597. ; Hook interrupts 8 and 13h
  1598.  
  1599. NoDisk2:    mov    ax,3508h
  1600.         int    21h        ; Get int 8 vector
  1601.         mov    [Old08Ofs],bx
  1602.         mov    [Old08Seg],es    ; Store it
  1603.         point    dx,New08
  1604.         mov    ax,2508h
  1605.         int    21h        ; Set new vector
  1606.  
  1607.         mov    ax,3513h
  1608.         int    21h        ; Get int 13h vector
  1609.         mov    [Old13Ofs],bx
  1610.         mov    [Old13Seg],es    ; Store it
  1611.         point    dx,New13
  1612.         mov    ax,2513h
  1613.         int    21h        ; Set new vector
  1614.  
  1615.         mov    dx,si        ; Appropriate message to DX
  1616.         jmp    MessageTSR0    ; Issue message and go resident
  1617.  
  1618. ; A resident copy exists
  1619.  
  1620. WasAlready4:    mov    es,[Already]    ; Get segment of installed copy
  1621.         mov    [es:Park_Ticks],ax ; Update resident timeout parameter
  1622.         mov    [es:ParkDrive0],ax ; Reset resident timeout counter
  1623.         mov    [es:ParkDrive1],ax ;   for both drives
  1624.  
  1625.         mov    ax,[es:Old13Ofs] ; Get chain vector offset
  1626.         or    ax,[es:Old13Seg] ; Check whether park function active
  1627.  
  1628.         jz    ParkWasNot    ; If not
  1629.  
  1630.         point    dx,ParkResMsg    ; If so, resident parameter modified
  1631.         jmp    MessageExit0    ; Display message and terminate
  1632.  
  1633. ; An installed copy is present but it does not have its park function enabled
  1634.  
  1635. ParkWasNot:    mov    ah,8        ; Get drive parameters function
  1636.         int    13h        ; Do it
  1637.         mov    [es:MaxCylinder0],cx ; Store number of last cylinder
  1638.         point    si,ParkEnab1Msg    ; Prepare for only one hard disk found
  1639.  
  1640.         mov    dl,81h        ; Test second hard disk for Ready
  1641.         mov    ah,10h
  1642.         int    13h
  1643.         jc    NoDisk3        ; If no second hard disk
  1644.         mov    ah,8        ; Get drive parameters function
  1645.         int    13h        ; Do it
  1646.         mov    [MaxCylinder1],cx ; Store number of last cylinder
  1647.         point    si,ParkEnab2Msg    ; Point to message for two drives
  1648.  
  1649. ; Hook interrupts 8 and 13h to resident copy
  1650.  
  1651. NoDisk3:    push    es
  1652.         pop    ds        ; Set DS to resident copy
  1653.  
  1654.         ASSUME    ds:nothing
  1655.  
  1656.         mov    ax,3508h
  1657.         int    21h        ; Get int 8 vector
  1658.         mov    [ds:Old08Ofs],bx
  1659.         mov    [ds:Old08Seg],es ; Store it
  1660.         point    dx,New08
  1661.         mov    ax,2508h
  1662.         int    21h        ; Set new vector
  1663.  
  1664.         mov    ax,3513h
  1665.         int    21h        ; Get int 13h vector
  1666.         mov    [ds:Old13Ofs],bx
  1667.         mov    [ds:Old13Seg],es ; Store it
  1668.         point    dx,New13
  1669.         mov    ax,2513h
  1670.         int    21h        ; Set new vector
  1671.  
  1672.         push    cs
  1673.         pop    ds        ; DS back to local segment
  1674.  
  1675.         ASSUME    ds:ComFile
  1676.  
  1677.         mov    dx,si        ; Appropriate message to DX
  1678.         jmp    MessageExit0    ; Issue message and exit
  1679. ENDP    DoMode_Park
  1680.  
  1681. ; Typematic command handler ----------------------------------------------------
  1682.  
  1683. ; Typematic parameters were specified.    First, check that both delay and rate
  1684. ; were given - both are required.  Then check whether a resident copy exists,
  1685. ; and if so, copy the typematic settings into the resident copy, and check
  1686. ; whether the resident copy has its typematic lock function enabled (this is
  1687. ; true if the chain vector for int 16 in the resident copy is nonzero).  Also
  1688. ; check the lock flag in the transient copy.  Behave according to the following
  1689. ; table:
  1690. ;
  1691. ; Res?    Locked?  LOCK?    Action                Message(s)
  1692. ;
  1693. ; N    N/A     N    Call BIOS            Set
  1694. ; N    N/A     Y    Hook, go resident        Locked, installed
  1695. ; Y    N     N    Update TSR, call BIOS        Set
  1696. ; Y    N     Y    Update TSR, hook to resident    Locked parms set
  1697. ; Y    Y     N/A    Update TSR, call BIOS        Locked parms set
  1698.  
  1699. PROC    DoMode_Typematic near
  1700.         point    bx,TypeParamEM    ; Prepare for parameter error
  1701.         mov    ax,[Type_Parm]    ; Get delay and rate
  1702.         cmp    ah,0FFh        ; Make sure a delay was entered
  1703.         je    Type_Error    ; If not
  1704.         cmp    al,0FFh        ; Make sure a rate was entered
  1705.         jne    TypeParamsOK    ; If alright
  1706.  
  1707. Type_Error:    jmp    ErrExit        ; Go to error exit handler
  1708.  
  1709. TypeParamsOK:    cmp    [Already],0    ; Are we already installed?
  1710.         jnz    WasAlready5    ; If so
  1711.  
  1712. ; Not resident - if no lock flag, just call the BIOS function and exit with
  1713. ; the message 'typematic parameters set'.  If lock flag is set, hook int 16h
  1714. ; into transient copy and go resident, with an appropriate message.
  1715.  
  1716.         cmp    [Type_Lock],0    ; Lock specified?
  1717.         jnz    Type_IsLock    ; If so
  1718.  
  1719. TypeSet:    point    dx,TypeSetMsg    ; Message
  1720.         jmp    SetTypeExit    ; Call BIOS, display message, and exit
  1721.  
  1722. ; Was not resident but lock was specified - hook vector and go resident
  1723.  
  1724. Type_IsLock:    mov    ax,3516h
  1725.         int    21h        ; Get int 16h vector
  1726.         mov    [Old16Ofs],bx
  1727.         mov    [Old16Seg],es    ; Store it
  1728.         point    dx,New16
  1729.         mov    ax,2516h
  1730.         int    21h        ; Set new vector
  1731.  
  1732.         mov    ax,305h        ; Function to set typematic
  1733.         int    16h        ; Set it - no parameter needed
  1734.                     ;   because lock code is resident
  1735.  
  1736.         point    dx,LockInstMsg    ; Message
  1737.  
  1738. ; Entry point for use by other handlers - at this point, DX points to the
  1739. ; message to be issued.  The message will be sent via DOS function 9, and
  1740. ; the program will hook int 2Fh (TSR locate vector), deallocate its copy of
  1741. ; the environment, and terminate and stay resident with errorlevel zero.
  1742.  
  1743. MessageTSR0:    mov    ah,9
  1744.         int    21h        ; Display message
  1745.  
  1746. TSR0:        mov    ax,352Fh
  1747.         int    21h        ; Get int 2Fh vector
  1748.         mov    [Old2FOfs],bx
  1749.         mov    [Old2FSeg],es    ; Store it
  1750.         point    dx,New2F
  1751.         mov    ax,252Fh
  1752.         int    21h        ; Set new vector
  1753.  
  1754.         mov    es,[ComFile:2Ch] ; Get segment of environment block
  1755.         mov    ah,49h        ; Free memory block function
  1756.         int    21h        ; Deallocate our copy of environment
  1757.  
  1758.         mov    dx,TSRParas    ; Number of paragraphs to leave resident
  1759.         mov    ax,3100h
  1760.         int    21h        ; Go resident
  1761.  
  1762. ; Resident copy was found - update resident typematic parameters, see whether
  1763. ; resident copy has typematic lock enabled and whether lock was requested then
  1764. ; behave according to above table.
  1765.  
  1766. WasAlready5:    mov    es,[Already]    ; Get resident segment
  1767.         mov    [WORD es:Type_Parm],ax ; Update resident values
  1768.  
  1769.         mov    ax,[es:Old16Ofs] ; Get old int 16h vector offset
  1770.         or    ax,[es:Old16Seg] ; Is resident copy in locked mode?
  1771.         jnz    Res_Locked    ; If so
  1772.  
  1773. ; Resident copy is not locked - did user specify locking?
  1774.  
  1775.         cmp    [Type_Lock],0    ; Lock requested?
  1776.         jz    TypeSet        ; If not, just set typematic and exit
  1777.  
  1778. ; Resident copy is not locked but user specified locking
  1779.  
  1780.         push    es
  1781.         pop    ds        ; DS to resident copy
  1782.  
  1783.         ASSUME    ds:nothing
  1784.  
  1785.         mov    ax,3516h
  1786.         int    21h        ; Get int 16h vector
  1787.         mov    [ds:Old16Ofs],bx
  1788.         mov    [ds:Old16Seg],es ; Store it in resident copy
  1789.         point    dx,New16
  1790.         mov    ax,2516h
  1791.         int    21h        ; Set new vector
  1792.         push    cs
  1793.         pop    ds        ; Restore to transient copy
  1794.  
  1795.         ASSUME    ds:ComFile
  1796.  
  1797.         point    dx,ResLockedMsg    ; Message
  1798.         jmp    SHORT SetTypeExit
  1799.  
  1800. ; Resident copy was locked
  1801.  
  1802. Res_Locked:    point    dx,TResUpdateMsg ; Resident locked parameters updated
  1803.  
  1804. ; At this point - DX points to the message to be displayed.  Get the typematic
  1805. ; parameters and call the BIOS (this may actually set typematic, or may just
  1806. ; cause the locked parameters to be set, depending on whether there is a
  1807. ; hooked copy of MODE), then display the specified message and terminate
  1808. ; with errorlevel zero.
  1809.  
  1810. SetTypeExit:    mov    bx,[Type_Parm]    ; Parameters to BX
  1811.         mov    ax,305h        ; Function to set typematic
  1812.         int    16h        ; Set it
  1813.  
  1814. ; Entry point for use by other handlers - at this point, DX points to the
  1815. ; message to be issued.  The message will be sent via DOS function 9, and
  1816. ; the program will terminate and return to DOS with errorlevel zero.
  1817.  
  1818. MessageExit0:    mov    ah,9        ; Display message
  1819.         int    21h
  1820. Exit0:        mov    ax,4C00h    ; Terminate with errorlevel zero
  1821.         int    21h
  1822. ENDP    DoMode_Typematic
  1823.  
  1824. ; Working stack area -----------------------------------------------------------
  1825.  
  1826.         ALIGN    2
  1827.  
  1828. WorkStack    DB    512 DUP(?)    ; Working stack
  1829. WorkStackTop    = $
  1830.  
  1831. ; End of transient portion -----------------------------------------------------
  1832.  
  1833.         MASM
  1834. FreeSpace    =    $
  1835. MemParas    =    (OFFSET (FreeSpace-@curseg+15) SHR 4)
  1836.         prexp    <Transient size:> %(MemParas SHL 4) < bytes>
  1837.         IDEAL
  1838.  
  1839.         ENDS    ComFile
  1840.         END    Main
  1841.  
  1842. ;-------------------------------------------------------------------------------
  1843.