                FFSKIP Usage:

Disables output to the printer via INT 17h until a specified number of
form-feed-delimited pages have been sent to the printer.  The two primary
uses of FFSKIP:
    1. Disable the printer.  A count of 65535 effectively does this if
       all you print is text.
    2. Skip the first n pages of a file sent to the printer *if the file
       uses form-feeds to separate pages*.

The command:
    FFSKIP [count] [comment text]
       (count can be 0 to 65535, actual result will be unpredictable
         if a higher number is specified.  If omitted, it defaults
         to 0 (FFSKIP disabled).)  The first non-digit, non-space character
         ends count evaluation and the rest of the command-line is ignored.
Examples:
a.  Load FFSKIP in de-activated state if not already loaded.
    Deactivate resident copy if already loaded.
        FFSKIP
b.  The same as above.
        FFSKIP wears army boots.
c.  Load FFSKIP if not already resident.  Count set to 65535 so
    printer output will be discarded until 65535 form-feeds are
    sent to the printer or FFSKIP is reset by another command or
    a printer reset function call.  Effectively, turn off the printer.
        FFSKIP 65535
d.  Your printer ribbon broke whle printing page 84 of a 123-page report with
        TYPE report.txt >LPT1
    To continue later where you left off and skip the first 83 pages after
    you get a new printer ribbon:
        FFSKIP 83
        TYPE report.txt >LPT1
    (This situation is what prompted me to write FFSKIP in the first place.)

Result:
a.  If this is the first time FFSKIP is run, it will stay resident
    with the count saved in a flag word.  If this is not the first
    time, the current count will be passed to the earlier copy and
    FFSKIP will terminate normally.

b.  When FFSKIP is resident and the count is 0, it is effectively
    disabled and all printer BIOS calls are passed to the original
    printer interrupt routine.

c.  When FFSKIP is resident and the count is not 0, BIOS calls to
    the printer interrupt vector INT 17h are intercepted and the
    following results are generated:
    Function 00 (Write Character):
       -- If the character is a form-feed (new page) then the count is
          decremented.
       -- The character is discarded without being sent to the printer.
       -- FFSKIP returns a status of bit 7 (not busy) and bit 4 (selected)
          set (status 90h, printer OK)
    Function 01 (Initialize printer port):
       -- FFSKIP resets its counter to 0 (turns itself off) and jumps to
          the original printer interrupt vector to invoke any other
          initialization code.
    Function 02 (Get printer status)
       -- FFSKIP returns a status of bit 7 (not busy) and bit 4 (selected)
          set (90h, printer OK)
    Functions 03 and higher (Undefined at time of writing FFSKIP).
       -- FFSKIP returns a status of bit 4 (I/O error).
 

                Commented disassembly of FFSKIP
                ===============================

The original source file for FFSKIP is on one of my old, currently
inaccessible 5 1/4 inch floppies (if the disk is even readable anymore).
That's how old FFSKIP is.  The following was produced by editing a DEBUG
disassembly of FFSKIP.

NOTE:  All numbers in instructions are in DEBUG hexadecimal format except where
       noted and this disassembly is for information only.
****** This file is not usable with an assembler without editing. ******

A complete hex-dump of FFSKIP:
------------------------------

178D:0100  EB 65 90 20 46 46 53 4B-49 50 2E 43 4F 4D 20 2D   .e. FFSKIP.COM -
178D:0110  2D 2D 20 62 79 20 4E 2E-20 4C 2E 20 44 65 46 6F   -- by N. L. DeFo
178D:0120  72 65 73 74 20 20 2D 2D-2D 2D 20 00 00 00 00 00   rest  ---- .....
178D:0130  00 9C 2E F7 06 2F 01 FF-FF 75 06 9D 2E FF 2E 2B   ...../...u.....+
178D:0140  01 80 FC 02 77 14 74 0E-80 FC 01 74 11 3C 0C 75   ....w.t....t.<.u
178D:0150  05 2E FF 0E 2F 01 B4 90-9D CF B4 08 9D CF 2E C7   ..../...........
178D:0160  06 2F 01 00 00 EB D4 FC-8A 0E 80 00 B5 00 E3 10   ./..............
178D:0170  BF 81 00 B0 20 0E 07 F3-AE E8 70 00 89 16 2F 01   .... .....p.../.
178D:0180  BE 03 01 8B FE 0E 1F 0E-07 B9 28 00 FC AC 0C 20   ..........(.... 
178D:0190  AA E2 FA B8 70 00 8C CB-8B 2E 03 01 BF 03 01 8E   ....p...........
178D:01A0  D8 3B 2D 74 08 40 3B C3-72 F2 EB 18 90 8B F7 8E   .;-t.@;.r.......
178D:01B0  C3 B9 28 00 F3 A6 75 ED-8E C0 8E DB BE 2F 01 8B   ..(...u....../..
178D:01C0  FE A5 CD 20 33 C0 8E D8-8B 1E 5C 00 2E 89 1E 2B   ... 3.....\....+
178D:01D0  01 8B 1E 5E 00 2E 89 1E-2D 01 0E 1F BA 31 01 B8   ...^....-....1..
178D:01E0  17 25 CD 21 BA 67 01 83-C2 10 CD 27 4F 4F 33 D2   .%.!.g.....'OO3.
178D:01F0  47 8A 05 2C 30 72 17 3C-09 77 13 50 8B C2 33 D2   G..,0r.<.w.P..3.
178D:0200  BB 0A 00 F7 E3 8B D0 58-B4 00 03 D0 EB E2 C3      .......X.......


Commented disassembly:
----------------------

ENTRY:
0100 EB65          JMP	0167  ; go to START of transient code.
0102 90            NOP

SIGNATURE:
0103  204646534B49502E434F4D20   DB    " FFSKIP.COM "
010F  2D2D2D206279204E2E204C2E   DB    "--- by N. L."
011B  204465466F726573742020     DB    " DeForest  "
0126  2D2D2D2D20                 DB    "---- "

OLD_INT_17H:                            ; filled in by GO-TSR routine
012B  0000           DW        0000     ; segment
012D  0000           DW        0000     ; offset

COUNTER:
012F  0000           DW        0000


; On entry: AH = 00 = Write Character
;           AH = 01 = Initialize printer port
;           AH = 02 = Get printer status
;           AH > 02 = Undefined at time of writing FFSKIP

NEW_INT_17_ROUTINE:
0131 9C              PUSHF	                         ; SAVE FLAGS
0132 2EF7062F01FFFF  TEST	WORD PTR CS:[012F],FFFF  ; COUNTER = 0?
0139 7506            JNZ	0141                     ; No? IGNORE.
CHAIN_TO_OLD:
013B 9D              POPF                                ; RESTORE FLAGS
013C 2EFF2E2B01      JMP	FAR CS:[012B]            ; GO TO ORIGINAL
                                                         ; PRINTER INTERRUPT
                                                         ; ROUTINE.
IGNORE:
0141 80FC02        CMP	AH,02               ; Check function, status request?
0144 7714          JA	015A                ; If higher, FUNCTION_3_OR_HIGHER
0146 740E          JZ	0156                ; If status request, jump.
0148 80FC01        CMP	AH,01               ; Initialize printer?
014B 7411          JZ	015E                ; Jump if yes.
PRINT_BYTE:
014D 3C0C          CMP	AL,0C               ; Form-feed?
014F 7505          JNZ	0156                ; No, just ignore.
0151 2EFF0E2F01    DEC	WORD PTR CS:[012F]  ; Decrement skip count.
STATUS:
0156 B490          MOV	AH,90     ; Return bit 7 (not busy) and bit 4 (selected)
0158 9D            POPF           ; restore flags
0159 CF            IRET           ; return to caller.

FUNCTION_3_OR_HIGHER:
015A B408          MOV	AH,08     ; I/O error status code
015C 9D            POPF           ; restore flags
015D CF            IRET           ; return to caller.

FUNCTION_1:  ; Initialize printer
015E 2EC7062F010000   MOV	WORD PTR CS:[012F],0000  ; clear the count
0165 EBD4             JMP	013B    ; and then go to the original interrupt
                                        ; vector.

; FYI -- quoting from Ralf Brown's Interrupt List:

; Bitfields for printer status:
; Bit(s) Description	(Table 00631)
;  7     not busy
;  6     acknowledge
;  5     out of paper
;  4     selected
;  3     I/O error
;  2-1   unused
;  0     timeout
; Notes:  If both, bit 5 "out of paper" and 4 "selected" are set, the
;         MS-DOS/ PC DOS kernel assumes that no printer is attached.
;         for Tandy 2000, bit 7 indicates printer-busy when set rather than clear


; --------- Transient code run when run as a program -------------
START:
0167 FC            CLD	                                   
0168 8A0E8000      MOV	CL,[0080] ; How many characters in command-line?
016C B500          MOV	CH,00     ; convert from byte count to word count
016E E310          JCXZ	0180      ; skip if nothing to process, go to SEARCH
0170 BF8100        MOV	DI,0081   ; point to start of command-line
0173 B020          MOV	AL,20     ; skip spaces
0175 0E            PUSH	CS        ; ES:DI --> command-line            
0176 07            POP	ES
0177 F3            REPZ	          ; scan line until end or non-space found
0178 AE            SCASB	  
0179 E87000        CALL	01EC      ; EVALUATE from there                         
017C 89162F01      MOV	[012F],DX ; set COUNTER to evaluated value

SEARCH:
0180 BE0301        MOV	SI,0103   ; Point DS:SI and ES:DI at SIGNATURE
0183 8BFE          MOV	DI,SI
0185 0E            PUSH	CS
0186 1F            POP	DS
0187 0E            PUSH	CS
0188 07            POP	ES
0189 B92800        MOV	CX,0028   ; 40 bytes (28h) to check
018C FC            CLD            ; Scan forward
CASECHANGE:
018D AC            LODSB          ; Get a byte
018E 0C20          OR	AL,20     ; Force lower-case if upper
0190 AA            STOSB	  ; Replace with new value
0191 E2FA          LOOP	018D      ; Repeat for entire signature
                                  ; "Why do you do this?" wou ask.
                                  ; I want to search for earlier copies of
                                  ; FFSKIP in memory in case it's already
                                  ; resident.  However, I don't want to falsely
                                  ; detect a copy of *this* invocation of FFSKIP
                                  ; that happens to be sitting in the system's
                                  ; disk buffers.  Altering the SIGNATURE prevents
                                  ; such false recognition.

0193 B87000        MOV	AX,0070   ; Start search at 0070:0103
0196 8CCB          MOV	BX,CS     ; for comparison below
0198 8B2E0301      MOV	BP,[0103] ; BP <-- 1st word of signature     
SEARCH2:
019C BF0301        MOV	DI,0103   ; Point to possible start of signature
019F 8ED8          MOV	DS,AX     ;   at segment to check
01A1 3B2D          CMP	BP,[DI]   ; 1st 2 bytes match?      
01A3 7408          JZ	01AD      ; Yes, scan entire signature area.
NOTFOUNDYET:
01A5 40            INC	AX        ; Increment segment to search.
01A6 3BC3          CMP	AX,BX     ; Has search reached *this* segment?
01A8 72F2          JB	019C      ; No, loop back to check next segment.
01AA EB18          JMP	01C4      ; No earlier copy found.  Go TSR.
01AC 90            NOP	                                   

SCAN4SIG:
01AD 8BF7          MOV	SI,DI     ; DS:SI --> earlier segment possible signature
01AF 8EC3          MOV	ES,BX     ; and ES:DI points to this segment signature
01B1 B92800        MOV	CX,0028   ; 40 bytes to check
01B4 F3            REPZ	          ; Compare the two
01B5 A6            CMPSB	  ; areas.                
01B6 75ED          JNZ	01A5      ; If no match, go back to NOTFOUNDYET to check
                                  ; next segment.
01B8 8EC0          MOV	ES,AX     ; Point ES:DI to counter in other segment
01BA 8EDB          MOV	DS,BX     ; and DS:SI to counter in this segment.
01BC BE2F01        MOV	SI,012F
01BF 8BFE          MOV	DI,SI
01C1 A5            MOVSW          ; Copy counter from this FFSKIP to earlier one.
01C2 CD20          INT	20        ; Old TERMINATE command.

GO_TSR:
01C4 33C0          XOR	AX,AX         ; Point DS to Interrupt Vector area.
01C6 8ED8          MOV	DS,AX         
01C8 8B1E5C00      MOV	BX,[005C]     ; Get old INT 17h segment.
01CC 2E891E2B01    MOV	CS:[012B],BX  ; Save in OLD_INT_17H segment word.
01D1 8B1E5E00      MOV	BX,[005E]     ; Get old INT 17h offset.
01D5 2E891E2D01    MOV	CS:[012D],BX  ; Save in OLD_INT_17H offset word.

01DA 0E            PUSH	CS            ; Point DS:DX to NEW_INT_17_ROUTINE
01DB 1F            POP	DS                                 
01DC BA3101        MOV	DX,0131                            
01DF B81725        MOV	AX,2517       ; Function 25 (set Int vector), Int 17
01E2 CD21          INT	21            ; Hook INT 17

01E4 BA6701        MOV	DX,0167       ; Offset past end of NEW_INT_17_ROUTINE
01E7 83C210        ADD	DX,+10        ; add 16 bytes to ensure no truncation error
01EA CD27          INT	27            ; Terminate and Stay Resident

EVALUATE:
01EC 4F            DEC	DI            ; Back up DI to compensate for INC below
01ED 4F            DEC	DI
01EE 33D2          XOR	DX,DX         ; Count initially 0
EVAL_LOOP:
01F0 47            INC	DI            ; Point to first/next character to examine
01F1 8A05          MOV	AL,[DI]       ; Get the character
01F3 2C30          SUB	AL,30         ; Subtract ASCII "0"                  
01F5 7217          JB	020E          ; If not a digit, we are done.          
01F7 3C09          CMP	AL,09         ; AL now 0 to 9?
01F9 7713          JA	020E          ; If above that, again not a digit.
01FB 50            PUSH	AX            ; Save number
01FC 8BC2          MOV	AX,DX         ;  Word in DX --> DWord in DX:AX
01FE 33D2          XOR	DX,DX
0200 BB0A00        MOV	BX,000A       ; Decimal ten
0203 F7E3          MUL	BX            ; Multiply current count by ten        
0205 8BD0          MOV	DX,AX         ; Count back to DX
0207 58            POP	AX            ; Latest digit back to AL   
0208 B400          MOV	AH,00         ; Zero-extend AL to AX
020A 03D0          ADD	DX,AX         ; Add new digit to count
020C EBE2          JMP	01F0          ; Loop back to check for another digit.

020E C3            RET	              ; Evaluation done, count in DX.

