;       NO.ASM -- Hides specified files from command that follows
;       ======

CSEG            Segment
                Assume  CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG
                Org     002Ch
Environment     Label   Word            ; Segment of Environment is here
                Org     0080h
Parameter       Label   Byte            ; Parameter is here
                Org     0100h
Entry:          Jmp     Begin           ; Entry Point

;       Most Data (some more at end of program)
;       ---------------------------------------

                db      "Copyright 1986 Ziff-Davis Publishing Co.",1Ah
                db      " Programmed by Charles Petzold ",1Ah
SyntaxMsg       db      "Syntax: NO filespec command [parameters]$"
DosVersMsg      db      "NO: Needs DOS 2.0 +$"
FileSpecMsg     db      "NO: Incorrect File Spec$"
TooManyMsg      db      "NO: Too many files to hide$"
MemAllocMsg     db      "NO: Allocation Problem$"
CommandMsg      db      "NO: COMMAND Problem$"
Delimiters      db      9,' ,;='
FileList        dw      ?               ; Storage of found files
FileCount       dw      0               ; Count of found files
FileListEnd     dw      ?               ; End of storage of found files
BreakState      db      ?               ; Store original break state here
Comspec         db      'COMSPEC='      ; String for Environment search
ParamBlock      dw      ?               ; Parameter block for EXEC call
                dw      ?, ?
                dw      5Ch, ?
                dw      6Ch, ?
StackPointer    dw      ?               ; Save SP during EXEC call

;       Check DOS Version
;       -----------------

Begin:          Mov     AH, 30h                 ; Check for DOS Version
                Int     21h                     ;   through DOS call
                Cmp     AL, 2                   ; See if it's 2.0 or above
                Jae     DosVersOK               ; If so, continue

                Mov     DX, Offset DosVersMsg   ; Error message
ErrorExit:      Mov     AH, 9                   ; Print String function call
                Int     21h                     ; Do it
                Int     20h                     ; And exit prematurely

;       Parse Command Line to get NO File specification
;       -----------------------------------------------

ScanParam:      Lodsb                           ; SUBROUTINE: Get byte
                Cmp     AL, 13                  ; See if end of parameter
                Je      ErrorExit               ; If so, exit
                Mov     DI, Offset Delimiters   ; Check if delimiter
                Mov     CX, 5                   ; There are 5 of them
                Repne   Scasb                   ; Scan the string
                Ret                             ; And return

DosVersOK:      Mov     DX, Offset SyntaxMsg    ; Possible error msg
                Mov     SI, 1+Offset Parameter  ; NO Parameter string
                Cld                             ; Directions forward

BegSearch:      Call    ScanParam               ; Check byte in subroutine
                Je      BegSearch               ; If delimiter, keep searching
                Mov     BX, SI                  ; Save pointer in BX
                Dec     BX                      ; BX points to NO file spec

EndSearch:      Call    ScanParam               ; Check byte in subroutine
                Jne     EndSearch               ; If not delimiter, keep going

;       Construct full FilePath and save down at end of program
;       -------------------------------------------------------

                Dec     SI                      ; Points after NO file spec
                Xchg    SI, BX                  ; SI points to beg, BX to end
                Mov     DI, Offset FullPath     ; Points to destination
                Cmp     Byte Ptr [SI + 1], ':'  ; See if drive spec included
                Jnz     GetDrive                ; If not, must get the drive
                Lodsw                           ; Otherwise, grab drive spec
                And     AL, 0DFh                ; Capitalize drive letter
                Jmp     Short SaveDrive         ; And skip next section

GetDrive:       Mov     AH, 19h                 ; Get current drive
                Int     21h                     ;   through DOS
                Add     AL, 'A'                 ; Convert to letter
                Mov     AH, ':'                 ; Colon after drive letter

SaveDrive:      Stosw                           ; Save drive spec and colon
                Mov     AL, '\'                 ; Directory divider byte
                Cmp     [SI], AL                ; See if spec starts at root
                Jz      HaveFullPath            ; If so, no need to get path
                Stosb                           ; Store that character
                Push    SI                      ; Save pointer to parameter
                Mov     SI, DI                  ; Destination of current path
                Mov     DL, [SI - 3]            ; Drive letter specification
                Sub     DL, '@'                 ; Convert to number
                Mov     AH, 47h                 ; Get current directory
                Int     21h                     ;   through DOS
                Mov     DX, Offset FileSpecMsg  ; Possible error message
                Jc      ErrorExit               ; Exit if error
                Sub     AL, AL                  ; Search for terminating zero
                Cmp     [SI], AL                ; Check if Root Directory
                Jz      RootDir                 ; If so, don't use it
                Mov     CX, 64                  ; Number of bytes to search
                Repnz   Scasb                   ; Do the search
                Dec     DI                      ; DI points to last zero
                Mov     AL, '\'                 ; Put a backslash in there
                Stosb                           ; So filespec can follow
RootDir:        Pop     SI                      ; Get back SI

HaveFullPath:   Mov     CX, BX                  ; End of NO file spec
                Sub     CX, SI                  ; Number of bytes to transfer
                Rep     Movsb                   ; Transfer them
                Sub     AL, AL                  ; Terminating zero
                Stosb                           ; Save it
                Mov     [FileList], DI          ; Repository for found files

;       Fix up parameter and ParamBlock for eventual COMMAND load
;       ---------------------------------------------------------

                Sub     BX, 4                   ; Points to new param begin
                Mov     AL, [Parameter]         ; Old byte count of parameter
                Add     AL, 80h                 ; Add beginning of old param
                Sub     AL, BL                  ; Subtract beginning of new
                Mov     AH, ' '                 ; Space separator
                Mov     Word Ptr [BX], AX       ; Store it
                Mov     Word Ptr [BX + 2], 'C/' ; Add /C to beginning of rest
                Mov     AX, [Environment]       ; Get environment segment
                Mov     [ParamBlock], AX        ; Save it
                Mov     [ParamBlock + 2], BX    ; Save parameter pointer
                Mov     [ParamBlock + 4], CS    ; Save segment of ParamBlock
                Mov     [ParamBlock + 8], CS
                Mov     [ParamBlock + 10], CS

;       Find Files from NO File Specification
;       -------------------------------------

                Mov     DX, Offset DTABuffer    ; Set File Find buffer
                Mov     AH, 1Ah                 ;   by calling DOS
                Int     21h

                Mov     DI, [FileList]          ; Address of destination
                Mov     DX, Offset FullPath     ; Search string
                Sub     CX, CX                  ; Search Normal files only
                Mov     AH, 4Eh                 ; Find first file

FindFile:       Int     21h                     ; Call DOS to find file
                Jnc     Continue                ; If no error continue
                Cmp     AX, 18                  ; If no more files
                Jz      NoMoreFiles             ;   get out of the loop
                Mov     DX, Offset FileSpecMsg  ; Error message otherwise
                Jmp     ErrorExit               ; Exit and print message

Continue:       Mov     AX, DI                  ; Address of destination
                Add     AX, 512                 ; See if near top of segment
                Jc      TooManyFiles            ; If so, too many files
                Cmp     AX, SP                  ; See if getting too many
                Jb      StillOK                 ; If not, continue

TooManyFiles:   Mov     DX, Offset TooManyMsg   ; Otherwise error message
                Jmp     ErrorExit               ; And terminate

StillOK:        Mov     SI, 30+Offset DTABuffer ; Points to filename
                Call    AsciizTransfer          ; Transfer it to list
                Inc     [FileCount]             ; Kick up counter
                Mov     AH, 4Fh                 ; Find next file
                Jmp     FindFile                ; By looping around

NoMoreFiles:    Mov     [FileListEnd], DI       ; Points after last file
                Mov     DI, [FileList]          ; Points to end of find string
                Mov     CX, 64                  ; Search up to 64 bytes
                Mov     AL, '\'                 ; For the backslash
                Std                             ; Search backwards
                Repnz   Scasb                   ; Do the search
                Mov     Byte Ptr [DI + 2], 0    ; Stick zero in there
                Cld                             ; Fix up direction flag

;       Stop Ctrl-Break Exits and Hide the files
;       ----------------------------------------

                Mov     AX,3300h                ; Get Break State
                Int     21h                     ; By calling DOS
                Mov     [BreakState],DL         ; Save it
                Sub     DL,DL                   ; Set it to OFF
                Mov     AX,3301h                ; Set Break State
                Int     21h                     ; By calling DOS
                Mov     BL, 0FFh                ; Value to AND attribute
                Mov     BH, 02h                 ; Value to OR attribute
                Call    ChangeFileMode          ; Hide all the files

;       Un-allocate rest of memory
;       --------------------------

                Mov     BX, [FileListEnd]       ; Beyond this we don't need
                Add     BX, 512                 ; Allow 512 bytes for stack
                Mov     SP, BX                  ; Set new stack pointer
                Add     BX, 15                  ; Prepare for truncation
                Mov     CL,4                    ; Prepare for shift
                Shr     BX,CL                   ; Convert to segment form
                Mov     AH,4Ah                  ; Shrink allocated memory
                Int     21h                     ; By calling DOS
                Mov     DX,Offset MemAllocMsg   ; Possible Error Message
                Jc      ErrorExit2              ; Print it and terminate

;       Search for Comspec in Environment
;       ---------------------------------

                Push    ES                      ; We'll be changing this
                Mov     ES, [Environment]       ; Set ES to Environment
                Sub     DI, DI                  ; Start at the beginning
                Mov     SI, Offset ComSpec      ; String to search for
                Mov     DX, Offset CommandMsg   ; Possible error message

TryThis:        Cmp     Byte Ptr ES:[DI], 0     ; See if points to zero
                Jz      ErrorExit2              ; If so, we can't go on
                Push    SI                      ; Temporarily save these
                Push    DI
                Mov     CX, 8                   ; Search string has 8 chars
                Repz    Cmpsb                   ; Do the string compare
                Pop     DI                      ; Get back the registers
                Pop     SI
                Jz      LoadCommand             ; If equals, we've found it
                Sub     AL, AL                  ; Otherwise search for zero
                Mov     CX, -1                  ; For 'infinite' bytes
                Repnz   Scasb                   ; Do the search
                Jmp     TryThis                 ; And try the next string

;       Load COMMAND.COM
;       -----------------

LoadCommand:    Add     DI, 8                   ; so points after 'COMSPEC='
                Push    DS                      ; Switch DS and ES registers
                Push    ES
                Pop     DS
                Pop     ES
                Mov     [StackPointer],SP       ; Save Stack Pointer
                Mov     DX, DI                  ; DS:DX = Asciiz of COMMAND
                Mov     BX, Offset ParamBlock   ; ES:BX = parameter block
                Mov     AX, 4B00h               ; EXEC function call
                Int     21h                     ; Load command processor

;       Return from COMMAND.COM
;       -----------------------

                Mov     AX, CS                  ; Current code segment
                Mov     DS, AX                  ; Reset DS to this segment
                Mov     ES, AX                  ; Reset ES to this segment
                Mov     SS, AX                  ; Reset stack segment to it
                Mov     SP, [StackPointer]      ; Reset SP
                Pushf                           ; Save error flag
                Sub     DL,DL                   ; Set Ctrl Break to OFF
                Mov     AX,3301h
                Int     21h                     ; By calling DOS
                Popf                            ; Get back error flag
                Mov     DX,Offset CommandMsg    ; Set up possible error msg
                Jnc     Terminate               ; And print if EXEC error

;       Unhide the Files, restore Ctrl-Break state, and exit
;       ----------------------------------------------------

ErrorExit2:     Mov     AH,9                    ; Will print the string
                Int     21h                     ; Print it
Terminate:      Mov     BL, 0FDh                ; AND value for change
                Mov     BH, 00h                 ; OR value for change
                Call    ChangeFileMode          ; Change file attributes
                Mov     DL,[BreakState]         ; Original break-state
                Mov     AX,3301h                ; Change the break-state
                Int     21h                     ;   by calling DOS
                Int     20h                     ; Terminate

;       SUBROUTINE: Change File Mode (All files, BL = AND, BH = OR)
;       -----------------------------------------------------------

ChangeFileMode: Mov     CX, [FileCount]         ; Number of files
                Jcxz    EndOfChange             ; If no files, do nothing
                Mov     SI, [FileList]          ; Beginning of list
                Mov     DX, [FileListEnd]       ; End of List
ChangeLoop:     Push    SI                      ; Save pointer
                Mov     SI, Offset FullPath     ; Preceeding path string
                Mov     DI, DX                  ; Destination of full name
                Call    AsciizTransfer          ; Transfer it
                Dec     DI                      ; Back up to end zero
                Pop     SI                      ; Get back pointer to filename
                Call    AsciizTransfer          ; Transfer it
                Push    CX                      ; Save the counter
                Mov     AX, 4300h               ; Get attribute
                Int     21h                     ;   by calling DOS
                And     CL, BL                  ; AND with BL
                Or      CL, BH                  ; OR with BH
                Mov     AX, 4301h               ; Now set attribute
                Int     21h                     ;   by calling DOS
                Pop     CX                      ; Get back counter
                Loop    ChangeLoop              ; And do it again if necessary
EndOfChange:    Ret                             ; End of subroutine

;       SUBROUTINE: Asciiz String Transfer (SI, DI in, returned incremented)
;       --------------------------------------------------------------------

AsciizTransfer: Movsb                           ; Transfer Byte
                Cmp     Byte Ptr [DI - 1], 0    ; See if it was end
                Jnz     AsciizTransfer          ; If not, loop
                Ret                             ; Or leave subroutine

;       Variable length data stored at end
;       ----------------------------------

DTABuffer       Label   Byte                    ; For file find calls
FullPath        equ     DTABuffer + 43          ; For file path and names
CSEG            EndS                            ; End of the segment
                End     Entry                   ; Denotes entry point
