REM file: Mkserial.bas - Public Domain DOS Utility
REM Version 1.0a created 02/29/1996
REM Version 1.1a created 04/15/2001

' default integer variables
DEFINT A-Z
REM $DYNAMIC

' define boolean values
CONST True = -1
CONST False = NOT True
CONST TrueD = -1#
CONST FalseD = NOT TrueD
CONST NUL = ""

' define color values
CONST Black = 0
CONST Cyan = 11
CONST Green = 10
CONST Plain = 7
CONST Red = 12
CONST White = 15
CONST Yellow = 14

' get include files
REM $INCLUDE: 'qbx.bi'
REM $INCLUDE: 'dta.bi'
REM $INCLUDE: 'bpb.bi'

' declare functions
DECLARE FUNCTION ParseLine (S$)
DECLARE FUNCTION BreakIS()
DECLARE FUNCTION ClearBreak()
DECLARE FUNCTION KeyIS()

' initialize filename buffer
DIM ASCIIZ AS STRING * 260
COMMON SHARED BPBfile AS BPBtype
COMMON SHARED Drive.Search AS STRING * 1

' declare common work variables
COMMON SHARED Continuous.Display AS INTEGER, Display.Volume AS INTEGER
COMMON SHARED Display.Errors AS INTEGER, Windows.Detected AS INTEGER

' declare program dta
DIM BASIC.DTA.SEG AS INTEGER, BASIC.DTA.OFF AS INTEGER

' declare registers
COMMON SHARED InregsX AS RegTypeX, OutregsX AS RegTypeX

' declare command line work variables
COMMON SHARED Command.Line AS STRING, Command.Line.Redirect AS STRING
COMMON SHARED Command.Work AS STRING, Control.Break AS INTEGER

' declare external procedures
DECLARE SUB SetInt
DECLARE SUB RestInt

' backwards compatible for bc 7.1
REM $INCLUDE: 'bc7.inc'

' increase stack size
STACK STACK

' install new interrupt service routine
CALL SetInt

' declare standard error trap
ON ERROR GOTO Error.Routine

' command line parser
FUNCTION ParseLine (X$)
 Imbedded = INSTR(Command.Line, X$)
 IF Imbedded THEN
    Command.Line = LEFT$(Command.Line, Imbedded - 1) + MID$(Command.Line, Imbedded + LEN(X$))
    ParseLine = True
 ELSE
    ParseLine = False
 END IF
END FUNCTION 

' store basic dta
InregsX.AX = &H2F00
CALL InterruptX(&H21, InregsX, OutregsX)
BASIC.DTA.SEG = OutregsX.ES
BASIC.DTA.OFF = OutregsX.BX

' get current drive
InregsX.AX = &H1900
CALL InterruptX(&H21, InregsX, OutregsX)
Current.Drive$ = CHR$((OutregsX.AX AND &HFF) + 65)

' check windows dos
InregsX.AX = &H160A
CALL InterruptX(&H2F, InregsX, OutregsX)
IF OutregsX.AX = False THEN
   Temp = (OutregsX.BX And &HFF00) / 256
   IF Temp >= 4 THEN
      Windows.Detected = True
   END IF
Endif

' check command line
SELECT CASE COMMAND$
CASE "/?"
   GOTO Boot.Usage
END SELECT

' store and parse command line
Command.Line = COMMAND$
IF Command.Line = NUL THEN
   Command.Line = UCASE$(ENVIRON$("MKSERIAL"))
END IF

' get switches from command line
Continuous.Display = ParseLine ("/C")
Display.Volume = ParseLine ("/V")
Display.Errors = ParseLine ("/Z")
Control.Break = ParseLine ("/~")

Imbedded = INSTR(Command.Line, "/S")
IF Imbedded THEN
   Serial$ = MID$(Command.Line, Imbedded + 2, 8)
   Command.Line = LEFT$(Command.Line, Imbedded - 1) + MID$(Command.Line, Imbedded + 10)
   IF LEN(Serial$) <> 8 THEN
      GOTO Boot.Error
   END IF
END IF

' recheck command line
IF INSTR(Command.Line, "/") THEN
   GOTO Boot.Usage
END IF

' remove blanks from command line
Command.Line = RTRIM$(Command.Line)
Command.Line = LTRIM$(Command.Line)
Command.Line.Redirect = Command.Line

' check break flag override
IF Control.Break THEN
   Var = ClearBreak
END IF

' search through all input filenames
Redirected.Input = False
DO
   ' check control break
   IF BreakIS THEN
      EXIT DO
   END IF

   ' get standard input
   Standard.Input$ = NUL
   InregsX.AX = &HB00
   CALL InterruptX(&H21, InregsX, OutregsX)
   DO WHILE (OutregsX.AX AND &HFF) = &HFF
      Redirected.Input = True
      InregsX.AX = &H800
      CALL InterruptX(&H21, InregsX, OutregsX)
      Char$ = CHR$(OutregsX.AX AND &HFF)
      SELECT CASE ASC(Char$)
      CASE 10, 26
      CASE 13
	 EXIT DO
      CASE ELSE
	 Standard.Input$ = Standard.Input$ + Char$
      END SELECT
      InregsX.AX = &HB00
      CALL InterruptX(&H21, InregsX, OutregsX)
   LOOP

   ' clear break flag
   IF Redirected.Input = False THEN
      IF Cleared = False THEN
         Cleared = True
         Var = ClearBreak
      END IF
   END IF

   ' check control break
   IF BreakIS THEN
      EXIT DO
   END IF

   ' check nul filename input
   IF Redirected.Input = False THEN
      IF Standard.Input$ = NUL THEN
         CALL RestInt ' restore Control-Break
         X$ = Inkey$ ' quits here
         CALL SetInt ' reset Control-Break
         IF X$ = CHR$(0) + CHR$(0) THEN
            EXIT DO
         END IF
      END IF
   END IF

   ' check standard input
   IF Redirected.Input THEN
      IF Standard.Input$ = NUL THEN
	 EXIT DO
      END IF
   END IF

   ' display header
   GOSUB Header

   ' store entire command
   Command.Work = Command.Line.Redirect

   DO
      ' store entire command
      Imbedded = INSTR(Command.Line, " ")
      IF Imbedded THEN
         Command.Work = Standard.Input$ + LEFT$(Command.Line, Imbedded - 1)
         Command.Line = MID$(Command.Line, Imbedded + 1)
      ELSE
         Command.Work = Standard.Input$ + Command.Line
         Command.Line = NUL
      END IF

      ' store current drive
      IF MID$(Command.Work, 2, 1) = ":" THEN
         Drive.Search = LEFT$(Command.Work, 1)
         Command.Work = MID$(Command.Work, 3)
      ELSE
	 Drive.Search = Current.Drive$
      END IF

      ' check windows dos
      IF Windows.Detected THEN
         ' get current directory
         InregsX.AX = &H7147
         InregsX.DX = ASC(Drive.Search) - 64
         InregsX.DS = VARSEG(ASCIIZ)
         InregsX.SI = VARPTR(ASCIIZ)
         CALL InterruptX(&H21, InregsX, OutregsX)
      ELSE
         ' get current directory
         InregsX.AX = &H4700
         InregsX.DX = ASC(Drive.Search) - 64
         InregsX.DS = VARSEG(ASCIIZ)
         InregsX.SI = VARPTR(ASCIIZ)
         CALL InterruptX(&H21, InregsX, OutregsX)
      END IF

      ' display any errors
      CALL DisplayError ("Error changing to drive.")

      ' store current directory
      Directory.Search$ = "\" + LEFT$(ASCIIZ, INSTR(ASCIIZ, CHR$(0)) - 1)
      Imbedded1 = INSTR(Command.Work, "\")
      Imbedded2 = Imbedded1
      WHILE Imbedded1
         Imbedded2 = Imbedded1
         Imbedded1 = INSTR(Imbedded1 + 1, Command.Work, "\")
      WEND
      IF Imbedded2 THEN
         Directory.Search$ = LEFT$(Command.Work, Imbedded2)
         Command.Work = MID$(Command.Work, Imbedded2 + 1)
      END IF
      IF RIGHT$(Directory.Search$, 1) <> "\" THEN
	 Directory.Search$ = Directory.Search$ + "\"
      END IF
   
      ' get filename spec
      Filename.Search$ = Command.Work
      IF Filename.Search$ = NUL THEN
	 Filename.Search$ = "*.*"
      END IF
      Command.Work = NUL
   
      ' change to drive
      InregsX.AX = &HE00
      InregsX.DX = ASC(Drive.Search) - 65
      CALL InterruptX(&H21, InregsX, OutregsX)
   
      ' get volume info
      InregsX.AX = &H6900
      InregsX.BX = ASC(Drive.Search) - 64
      InregsX.DS = VARSEG(BPBfile)
      InregsX.DX = VARPTR(BPBfile)
      CALL InterruptX(&H21, InregsX, OutregsX)

      ' display any errors
      CALL DisplayError ("Error reading volume serial number.")

      ' store serial number
      Old.Serial$ = NUL
      FOR Serial.Digit = 4 TO 1 STEP -1
         IF Serial.Digit = 2 THEN
            Old.Serial$ = Old.Serial$ + "-"
         END IF
         Digit.Value = ASC(MID$(BPBfile.Serial, Serial.Digit, 1))
         Old.Serial$ = Old.Serial$ + RIGHT$(HEX$(Digit.Value + &H100), 2)
      NEXT

      ' calculate volume serial number
      New.Serial$ = NUL
      FOR Serial.Digit = 8 TO 1 STEP -2
         Digit1$ = MID$(Serial$, Serial.Digit, 1)
         Digit2$ = MID$(Serial$, Serial.Digit - 1, 1)
         Hex.Digit$ = "&H" + Digit2$ + Digit1$
         New.Serial$ = New.Serial$ + CHR$(VAL(Hex.Digit$))
      NEXT

      ' store new serial number
      BPBfile.Serial = New.Serial$

      ' reset volume info
      InregsX.AX = &H6901
      InregsX.BX = ASC(Drive.Search) - 64
      InregsX.DS = VARSEG(BPBfile)
      InregsX.DX = VARPTR(BPBfile)
      CALL InterruptX(&H21, InregsX, OutregsX)

      ' display any errors
      CALL DisplayError ("Error reseting volume serial number.")

      ' check flag error
      IF (OutregsX.Flags AND &H1) = &H0 THEN

         ' check display flag
         IF Display.Volume = False THEN

            ' display volume serial number
            COLOR Yellow, Black
            IF Continuous.Display = False THEN
               PRINT "Changing: "; Old.Serial$; " to ";
            END IF
            PRINT UCASE$(LEFT$(Serial$, 4)); "-"; UCASE$(RIGHT$(Serial$, 4))
	 END IF
      END IF

      ' check search filename
      IF Command.Line = NUL THEN
	 EXIT DO
      END IF
   LOOP

   ' check search filename
   IF Standard.Input$ = NUL THEN
      EXIT DO
   END IF
LOOP

End.Mkserial:

' restore basic dta
InregsX.AX = &H1A00
InregsX.DS = BASIC.DTA.SEG
InregsX.DX = BASIC.DTA.OFF
CALL InterruptX(&H21, InregsX, OutregsX)

' restore current drive
InregsX.AX = &HE00
InregsX.DX = ASC(Current.Drive$) - 65
CALL InterruptX(&H21, InregsX, OutregsX)

' display end program
IF Continuous.Display = False THEN
   Prompt$ = "Press <enter> to exit to DOS:"
   CALL MorePrompt(Prompt$, CHR$(13), Outpt$)
END IF

' restore key trapping
CALL RestInt

COLOR Plain, Black
END

' display program usage
Boot.Usage:
 ' restore key trapping
 CALL RestInt
 Var$=Inkey$
 ' make header
 COLOR White, Black
 PRINT "Mkserial v1.1a: Volume serial change utility; "
 COLOR Yellow, Black
 PRINT "Usage:"
 PRINT "   Mkserial [d:][/csvz]"
 PRINT "Where:"
 PRINT "   /c  continuous display"
 PRINT "   /s  is new serial number in hexidecimal form nnnnnnnn"
 PRINT "   /v  don't display volume serial number"
 PRINT "   /z  suppress error messages"
 COLOR Plain, Black
 END

Boot.Error:
 CALL RestInt
 Var$=Inkey$
 COLOR White, Black
 PRINT "Command line error. Type Mkserial /? for help."
 COLOR Plain, Black
 END

' make header
Header:
 IF Header.Flag THEN
    RETURN
 END IF
 Header.Flag = True
 IF Continuous.Display = False THEN
    COLOR White, Black
    PRINT "Mkserial v1.1a: Volume serial change utility; "
 END IF
 RETURN
   
' critical error trap
Error.Routine:
 Data.Error = ERR
 IF Display.Errors THEN
    Error.Level = True
    OutregsX.Flags = &H1
    RESUME NEXT
 END IF
 SELECT CASE Data.Error
 CASE 53
    Temp.Outpt$ = "File not found."
 CASE 61
    Temp.Outpt$ = "Disk full."
 CASE 70
    Temp.Outpt$ = "Permission denied."
 CASE 71
    Temp.Outpt$ = "Disk not ready."
 CASE ELSE
    Temp.Outpt$ = "Untrapped error" + STR$(Data.Error) + "."
 END SELECT
 COLOR White, Black
 PRINT Temp.Outpt$
 Prompt$ = "Press R to retry, Q to quit, C to continue:"
 CALL MorePrompt(Prompt$, "rqc", Outpt$)
 IF BreakIS THEN
    Outpt$ = "q"
 END IF
 SELECT CASE Outpt$
 CASE "r"
    RESUME
 CASE "q"
    Error.Level = True
    RESUME End.Mkserial
 CASE "c"
    OutregsX.Flags = &H1
    RESUME NEXT
 END SELECT
 COLOR Plain, Black
 ' restore key trapping
 CALL RestInt
 END 0

SUB MorePrompt (Input.String$, Input.Mask$, Output.String$)
 COLOR White, Black
 PRINT Input.String$ + " ";
 Input.Char$ = NUL
 DO
    LOCATE , , 1
    DO
       IF BreakIS THEN
          EXIT DO
       END IF
       IF KeyIS THEN
          IF OutregsX.AX <> 0 THEN
             InregsX.AX = &H0000
             CALL InterruptX(&H16, InregsX, OutregsX)
             Input.Char$=CHR$(OutregsX.AX AND &HFF)
             EXIT DO
          END IF
       END IF
    LOOP
    IF BreakIS THEN
       EXIT DO
    END IF
    IF LEN(Input.Char$) THEN
       Input.Char$ = LCASE$(Input.Char$)
       IF INSTR(Input.Mask$, Input.Char$) THEN
	  PRINT Input.Char$
	  Output.String$ = Input.Char$
	  EXIT DO
       END IF
    END IF
 LOOP
END SUB

' clears Control-Break flag
FUNCTION ClearBreak
 DEF SEG = &H40
 POKE &H71, &H0
 DEF SEG
 ClearBreak = True
END FUNCTION

' checks Control-Break flag
FUNCTION BreakIS
 STATIC Var AS INTEGER
 IF KeyIS THEN
    IF OutregsX.AX = 0 THEN
       Var = True
    END IF
 END IF
 IF Var THEN
    Continuous.Display = True
 END IF
 BreakIS = Var
END FUNCTION

' checks keyboard buffer
FUNCTION KeyIS
 InregsX.AX = &H0100
 CALL InterruptX(&H16, InregsX, OutregsX)
 IF (OutregsX.Flags AND &H40) = &H40 THEN
    KeyIS = False
 ELSE
    KeyIS = True
 END IF
END FUNCTION

' displays carry flag error
SUB DisplayError (Temp$)
 ' check carry flag error
 IF (OutregsX.Flags AND &H1) = &H1 THEN
    ' check display errors flag
    IF Display.Errors = False THEN
       ' display error
       COLOR Red, Black
       PRINT Temp$
    END IF
 END IF
END SUB
