;	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