home *** CD-ROM | disk | FTP | other *** search
/ ftp.update.uu.se / ftp.update.uu.se.2014.03.zip / ftp.update.uu.se / pub / rainbow / msdos / decus / RB139 / wut312sc.lzh / WUSUBL.PAS < prev    next >
Pascal/Delphi Source File  |  1988-12-16  |  40KB  |  1,037 lines

  1. {** file wusubl.pas ⌐ Copyright 1986 Anthony G. Camas, all rights reserved **}
  2. { **************************************************************************
  3.               UTILITY PROCEDURES AND FUNCTIONS (TERMINAL OUTPUT)
  4.   ************************************************************************** }
  5.  
  6. { PrintChar - Return printable character version of passed byte.
  7.   A byte value is passed.  This function returns the CHAR equivalent of that
  8.   value if it is printable.  If it is not printable, an "equivalent" printable
  9.   character is returned.  This is sometimes the character code for a "special"
  10.   character printable only by the direct screen I/O routines; thus this
  11.   function should only be called when in Fast Video mode (see code appearing
  12.   later in this module). }
  13. function PrintChar (Number: Byte) :Char;
  14. var
  15.   Result :Char;
  16. begin
  17.   Number := (Number AND $7F);
  18.   if (Number < Ord (' ')) or (Number = $7F) then
  19.   begin
  20.     if Number = 0 then Number := HollowBoxChar  { Box outline for null }
  21.     else if Number = 8 then Number := BSChar    { "½" for backspace }
  22.     else if Number = 9 then Number := HTChar    { Displayable "HT" }
  23.     else if Number = 10 then Number := LFChar   { Displayable "LF" }
  24.     else if Number = 12 then Number := FFChar   { Displayable "FF" }
  25.     else if Number = 13 then Number := CRChar   { Displayable "CR" }
  26.     else Number := InvertedQuesChar;            { "┐" for all others }
  27.   end;
  28.   PrintChar := Chr(Number);
  29. end {PrintChar};
  30.  
  31. { HexDigit - Return hexadecimal character corresponding to input value (0-15) }
  32. function HexDigit (Number: Byte) :Char;
  33. const
  34.   CharZero = $30;
  35.   CharA = $41;
  36. begin
  37.   if (Number > 9) then HexDigit := Chr(CharA-10+Number)
  38.                   else HexDigit := Chr(CharZero+Number);
  39. end {HexDigit};
  40.  
  41. { Hex2Digit - Return two hex characters corresponding to an 8-bit value }
  42. function Hex2Digit (Number:Byte) :Str2;
  43. begin
  44.   Number := (Number AND $FF);
  45.   Hex2Digit := Concat (HexDigit (Number DIV $10),
  46.                        HexDigit (Number MOD $10));
  47. end {Hex2Digit};
  48.  
  49. { Hex4Digit - Return four hex characters corresponding to a 16-bit value }
  50. function Hex4Digit (Number:Integer) :Str4;
  51. begin
  52.   Hex4Digit := Concat (Hex2Digit(Hi(Number)), Hex2Digit(Lo(Number)));
  53. end {Hex4Digit};
  54.  
  55. { WriteHex - Display an 8-bit value as two hex digits }
  56. procedure WriteHex (Number:Byte);
  57. begin
  58.   Number := (Number AND $FF);
  59.   Write (HexDigit(Number DIV $10),
  60.          HexDigit(Number MOD $10));
  61. end {WriteHex};
  62.  
  63. { WriteHex4 - Display a 16-bit value as four hex digits }
  64. procedure WriteHex4 (Number:Integer);
  65. begin
  66.   WriteHex (Hi(Number));  WriteHex (Lo(Number));
  67. end {WriteHex4};
  68.  
  69. { WriteTSL - Display track, sector, and length information in standard format }
  70. procedure WriteTSL (Var TSL:DiskAddrLength);
  71. begin
  72.   Write ('Track ');
  73.   WriteHex4 (TSL.Track);
  74.   Write ('H Sector ');
  75.   WriteHex (TSL.Sector);
  76.   Write ('H (');
  77.   WriteHex4 (TSL.Length);
  78.   Write ('H sectors)');
  79. end {WriteTSL};
  80.  
  81. { SetPrintFile - Set print screen output file.
  82.   This routine is passed the name of a file to which future printer output
  83.   should be sent.  It attempts to open that file as PrintFile.  If the open
  84.   succeeds, this function returns TRUE and puts the name of the file
  85.   opened in the global variable PrintFileName.  If it fails, the function
  86.   returns FALSE and leaves PrintFileName as is. }
  87. function SetPrintFile (Name :Str60) :Boolean;
  88. begin
  89. {$I-}
  90.   { Close old print file, if there was one }
  91.   If Length(PrintFileName) > 0 then Close (PrintFile);
  92.   Assign (PrintFile, Name);
  93.   Rewrite (PrintFile);
  94.   If IOResult = 0 then
  95.   begin
  96.     If Name = FileSpecForPrinter then PrintFileName := 'Printer'
  97.                                  else PrintFileName := Name;
  98.     SetPrintFile := True;
  99.   end
  100.   else SetPrintFile := False;
  101. {$I+}
  102. end {SetPrintFile};
  103.  
  104. { PrintTheScreen - Prints contents of screen to specified device/file. }
  105. procedure PrintTheScreen;
  106. const
  107.   ScreenMemSeg = $EE00;
  108.   EightBitsValue = 4;
  109. var
  110.   CCB :Record
  111.     Func, ReturnCode, Char, CharStat,
  112.     DeviceNumber, ModemControl, StopBits, DataBits, Parity,
  113.     RcvBaud, XmtBaud, XONChar, XOFFChar, RcvXONXOFF, XmtXONXOFF,
  114.     X0B, X0C, X0D, X0E, X0F, X10 :Byte;
  115.   End;
  116.   I :Integer;
  117.   Ch :Byte;
  118.   Count :Integer;
  119.   DECPrinter :Boolean;
  120.   EmptyLine :Boolean;
  121.  
  122. begin
  123.   { We will now try to determine if we are dealing with a DEC printer or
  124.     some other printer.  We'll do this by looking at the programming of the
  125.     printer device.  If it is set to an "eight bit" mode, we'll assume it's
  126.     a DEC printer and that it can receive special characters.  If it's not,
  127.     we'll send it only "regular" characters.  Note, by the way, that we
  128.     won't bother with this check unless printer output is going to the printer.
  129.     If it's going to a file, we'll always assume we're not a dec printer. }
  130.   If PrintFileName <> FileSpecForPrinter then DECPrinter := False
  131.   else
  132.   begin
  133.     Registers.AX := $4402; { IOCTL function to read printer configuration }
  134.     Registers.BX := $04;   { Printer port }
  135.     Registers.DS := Seg (CCB);
  136.     Registers.DX := Ofs (CCB);
  137.     CCB.Func := 3;
  138.     CCB.DeviceNumber := 2;
  139.     MsDos (Registers);
  140.     DECPrinter := (CCB.DataBits = EightBitsValue);
  141.   end;
  142.  
  143.   { We have to read what's in screen memory for a total of 26/29 lines,
  144.     depending on the Rainbow's 50/60Hz setting.  On 50Hz the first five lines
  145.     and on 60Hz the first two lines will always be blank.  They will cause a
  146.     jump around the global data used by the video controller.  We have to stop
  147.     after the 24 lines are printed, because otherwise we'll end up printing 
  148.     endless stuff. }
  149.   I := 0;
  150.   Count := 0;
  151.   EmptyLine := True;
  152.  
  153.   { Now we skip over the blank lines.  In a blank line the first byte is the
  154.     terminator $FF.  So, all we do is skip over all lines that have the ter-
  155.     minator in the first byte. }
  156.   Repeat {Until EmptyLine = False}
  157.     begin
  158.       Ch := Mem[ScreenMemSeg:I];
  159.       If Ch <> $FF then EmptyLine := False
  160.       else
  161.         begin
  162.           I := I + 1;
  163.           I := MemW[ScreenMemSeg:I];
  164.         end;
  165.     end;
  166.   Until EmptyLine = False;
  167.  
  168.   Repeat {Until Count = 24}
  169.     Ch := Mem[ScreenMemSeg:I];
  170.     I := I + 1;
  171.     If Ch = $FF then
  172.     begin
  173.       I := MemW[ScreenMemSeg:I];
  174.       Count := Count + 1;
  175.       WriteLn (PrintFile);
  176.     end
  177.     else
  178.     begin
  179.       { Printers don't have the same character set as we do for the screen.
  180.         We will make some translations, therefore, to make sure this character
  181.         can be printed. }
  182.       If (Ch = ULCornerChar) or (Ch = URCornerChar)
  183.       or (Ch = LLCornerChar) or (Ch = LRCornerChar) then Ch := Ord ('+')
  184.       else if Ch = VLineChar then Ch := Ord ('|')
  185.       else if Ch = HLineChar then Ch := Ord ('-')
  186.       else if (DECPrinter)
  187.           and (   (Ch < $20)
  188.                or ((Ch > $7E) and (Ch < $A1))) then Ch := $B7 { Centered dot }
  189.       else if (NOT DECPrinter)
  190.           and (   (Ch < $20)
  191.                or (Ch > $7E)) then Ch := Ord ('.');
  192.       Write (PrintFile, Char(Ch));
  193.     end;
  194.   Until Count = 24;
  195.   if FFAfterPrint then Write (PrintFile, ^L);
  196.   WriteLn (PrintFile);
  197.   Flush (PrintFile);
  198. end {PrintTheScreen};
  199.  
  200. { CursorOff and CursorOn - Disable/enable display of cursor }
  201. procedure CursorOff;
  202. begin
  203.   Registers.DI := $08;
  204.   Intr ($18, Registers);
  205. end {CursorOff};
  206.  
  207. procedure CursorOn;
  208. begin
  209.   Registers.DI := $0A;
  210.   Intr ($18, Registers);
  211. end {CursorOn};
  212.  
  213. { FastVideoOut - Writes one character to "current" position using fast video.
  214.   This procedure becomes the TURBO "ConOut" procedure when StartFastVideo
  215.   is called until EndFastVideo is called subsequently.  It is passed a
  216.   character to write, and it writes it at the current position using firmware
  217.   fast video routines.  The attribute value in FVAttribute is used for the
  218.   attribute, unless it is $FF, in which case the "current" attribute it used.
  219.   Carriage return, line feed, and backspace are handled properly by adjusting
  220.   the row and column values; all other characters are taken literally and
  221.   printed. }
  222. procedure FastVideoOut (Ch: Char);
  223. begin
  224.   IF Ch = #13 THEN         { Return }
  225.     FVColumn := FVLMargin
  226.   ELSE IF Ch = #10 THEN    { Line Feed }
  227.   begin
  228.     FVRow := FVRow + 1;
  229.     IF FVRow > FVBmargin THEN FVRow := FVTMargin; { Wrap -- too hard to scroll }
  230.   end
  231.   ELSE IF Ch = #8 THEN     { Backspace }
  232.   begin
  233.     IF FVColumn > FVLMargin THEN FVColumn := FVColumn - 1;
  234.   end
  235.   ELSE IF Ch = #7 THEN     { Bell }
  236.   begin
  237.     Registers.DI := $1E;
  238.     Intr ($18, Registers);
  239.   end
  240.   ELSE                     { All others (printable) }
  241.   begin
  242.     IF FVColumn > FVRmargin THEN
  243.     begin
  244.       FVColumn := FVLMargin;
  245.       FVRow := FVRow + 1;
  246.       IF FVRow > FVBMargin THEN FVRow := FVTMargin;
  247.     end;
  248.     FVChar := Ch;
  249.     WITH Registers DO
  250.     begin
  251.       DI := $14; { Send data to screen function }
  252.       IF FVAttribute = AtDefault THEN
  253.         AX := 2  { Just Characters }
  254.       ELSE
  255.         AX := 0; { Characters AND Attributes }
  256.       BL := Byte(FVRow); BH := Byte(FVColumn);
  257.       CX := 1;
  258.       DX := Ofs (FVAttribute);
  259.       SI := Ofs (FVChar);
  260.       BP := DSeg;
  261.     end;
  262.     Intr ($18, Registers);
  263.     FVColumn := FVColumn + 1;
  264.   end;
  265. end {FastVideoOut};
  266.  
  267. { WriteFast - Writes a string to the current position (very) fast.
  268.   This procedure writes the contents of a string to the screen at the current
  269.   fast video position "very" fast.  If you are writing just strings (with no
  270.   data which needs to be formatted), you should use this instead of Write,
  271.   because this routine will write the whole string at once, whereas Write
  272.   writes the line one character at a time through FastVideoOut.  After the
  273.   string is written, the current position is incremented to point to the
  274.   posotion following the string just written (as if Write had been used).
  275.   THIS ROUTINE DOES NOT CHECK FOR MARGIN WRAPPING.  ANYTHING YOU WRITE WITH
  276.   IT MUST FIT ON THE SCREEN OR YOU MAY CRASH YOUR MACHINE. }
  277. procedure WriteFast (Text :Str80);
  278. var
  279.   AttribBuff :Array [1..80] of Byte;
  280.   I :Integer;
  281. begin
  282.   If FVAttribute <> AtDefault then FillChar (AttribBuff[1], Length(Text),
  283.                                              FVAttribute);
  284.   WITH Registers DO
  285.   begin
  286.     DI := $14; { Send data to screen function }
  287.     IF FVAttribute = AtDefault THEN
  288.       AX := 2  { Just Characters }
  289.     ELSE
  290.       AX := 0; { Characters AND Attributes }
  291.     BL := Byte(FVRow); BH := Byte(FVColumn);
  292.     CX := Length(Text);
  293.     DX := Ofs (AttribBuff);
  294.     SI := Ofs (Text[1]);
  295.     BP := SSeg;
  296.   end;
  297.   Intr ($18, Registers);
  298.   FVColumn := FVColumn + Length(Text);
  299.  
  300. end {WriteFast};
  301.  
  302. { WriteFastLn - Writes a string to the current position (very) fast + New Line.
  303.   This procedure writes the contents of a string to the screen at the current
  304.   fast video position "very" fast and then advances the current position to
  305.   the left margin of the next line (as if WriteLn) had been used.  SEE COMMENTS
  306.   FOR WriteFast ROUTINE FOR IMPORTANT ADDITIONAL INFORMATION. }
  307. procedure WriteFastLn (Text :Str80);
  308. begin
  309.   WriteFast (Text);
  310.   FVColumn := FVLMargin;
  311.   FVRow := FVRow + 1;
  312.   if FVRow > FVBmargin then FVRow := FVTmargin;
  313. end {WriteFastLn};
  314.  
  315.  
  316. { StartFastVideo - Sets up to do fast video output starting at given position.
  317.   An initial row, column, and attribute value are passed.  Then this routine
  318.   adjusts so that Turbo Pascal sends all output through the FastVideoOut
  319.   routing.  Also, right and bottom margin values are passed, which are used
  320.   in wrapping the display.  The left and top margin values are set to the
  321.   initial column and row values (these are assumed to be the upper left corner
  322.   of the area being used for text).  The cursor is also hidden while fast
  323.   video mode is turned on.
  324.   If this routine is called when fast video is already enabled, everything
  325.   functions as described above, except that the cursor is not turned off
  326.   "again". }
  327. procedure StartFastVideo (InitialColumn, InitialRow, InitialAttribute,
  328.                           RightMargin, BottomMargin :Byte);
  329. begin
  330.   FVRow := InitialRow;
  331.   FVColumn := InitialColumn;
  332.   FVAttribute := InitialAttribute;
  333.   FVLMargin := InitialColumn;
  334.   FVTMargin := InitialRow;
  335.   FVRMargin := RightMargin;
  336.   FVBMargin := BottomMargin;
  337.   IF ConOutPtr <> Ofs(FastVideoOut) THEN
  338.   begin
  339.     SaveConOutPtr := ConOutPtr;
  340.     ConOutPtr := Ofs(FastVideoOut);
  341.     CursorOff;
  342.   end;
  343. end {StartFastVideo};
  344.  
  345. { EndFastVideo - Turns off fast video mode so normal output resumes.
  346.   Disables fast video mode and restores turbo output to its normal mode.
  347.   If fast video mode is already off, nothing happens. }
  348. procedure EndFastVideo;
  349. begin
  350.   IF ConOutPtr = Ofs(FastVideoOut) THEN
  351.   begin
  352.     ConOutPtr := SaveConOutPtr;
  353.     CursorOn;
  354.   end;
  355. end {EndFastVideo};
  356.  
  357. { NormalExit - Reset and exit back to MS-DOS. }
  358. procedure NormalExit;
  359. const
  360.   BIOSSeg = $40;
  361.  
  362. begin
  363.   EndFastVideo;
  364.   ClrScr;
  365.   LowVideo;
  366.   If Length (PrintFileName) > 0 then Close (PrintFile);
  367.   If LongChain = TRUE then MemW[BIOSSeg:PatchLocation] := $478A;
  368.   Halt (0);
  369. end {NormalExit};
  370.  
  371. { RawChar - Returns 16-bit value of character in "Raw Key buffer".
  372.   This procedure waits until a character has been typed on the keyboard
  373.   and returns that character's 16-bit "raw key" code.
  374.   It also processes the EXIT and PRINT SCREEN keys.  }
  375. function RawChar :Integer;
  376. begin
  377.   repeat
  378.     Registers.DI := $06;
  379.     Intr ($18, Registers);
  380.     IF Registers.CL = $01 THEN
  381.     repeat
  382.       Registers.DI := $02;
  383.       Intr ($18, Registers);
  384.     until Registers.CL = $00;
  385.     { Intercept PrintScreen key and do a print screen; then pretend nothing
  386.       was typed so loop will continue. }
  387.     if  (Registers.CL = $FF)
  388.     and ((Registers.AX and CapsLockMask) = PrintScreenKeyCode) then
  389.     begin
  390.       PrintTheScreen;
  391.       Registers.CL := $00;
  392.     end;
  393.   until Registers.CL = $FF;
  394.   if (EXITAllowed) and ((Registers.AX and CapsLockMask) = ExitKeyCode) then
  395.     NormalExit;
  396.   RawChar := Registers.AX;
  397. end {RawChar};
  398.  
  399. { WaitForResume - Waits until the RESUME key is pressed. }
  400. procedure WaitForResume;
  401. var
  402.   I :Integer;
  403. begin
  404.   repeat
  405.     I := (RawChar AND CapsLockMask);
  406.     IF I <> ResumeKeyCode THEN Write (^G);
  407.   until I = ResumeKeyCode;
  408. end {WaitForResume};
  409.  
  410. { DrawBox - Display a box on the screen using direct screen I/O.
  411.   This procedure draws a box on the screen when given the upper left and
  412.   lower right coordinates and the "attribute" value to use for the characters
  413.   displayed.  The interior of the box contains spaces, but with the same
  414.   attribute as the outside of the box.  Thus, fast video output to the
  415.   interior of the box will have the same attributes as the box edges if
  416.   not changed when written.
  417.   This routine does not check that the values given are in range.  If they
  418.   are not, results are unpredictable. }
  419. procedure DrawBox (ULColumn, ULRow, LRColumn, LRRow :Integer;
  420.                    Attribute :Byte);
  421. const
  422.   Blank = $20;
  423. var
  424.   I, Height, Width :Integer;
  425.   CharBuff, AttribBuff :Array [1..80] OF Byte;
  426. begin
  427.   { Compute height and width of box.  If either value is less than or equal
  428.     to one, something is wrong...get out and don't draw anything }
  429.   Width := (LRColumn - ULColumn) + 1;
  430.   Height := (LRRow - ULRow) + 1;
  431.   IF (Width < 2) OR (Height < 2) THEN Exit;
  432.   { Turn off cursor while we do this }
  433.   CursorOff;
  434.   { Fill Character Buffer with horizontal line chars and attribute buffer
  435.     with specified value }
  436.   FillChar (CharBuff[ULColumn], Width, HLineChar);
  437.   FillChar (AttribBuff[ULColumn], Width, Attribute);
  438.   { Form top line by changing first and last characters in buffer to upper
  439.     corners }
  440.   CharBuff[ULColumn] := ULCornerChar;
  441.   CharBuff[LRColumn] := URCornerChar;
  442.   { Write top line }
  443.   WITH Registers DO
  444.   begin
  445.     DI := $14; { Send data to screen function }
  446.     AX := 0;   { Characters AND Attributes }
  447.     BL := Byte(ULRow); BH := Byte(ULColumn);
  448.     CX := Width;
  449.     DX := Ofs (AttribBuff[ULColumn]);
  450.     SI := Ofs (CharBuff[ULColumn]);
  451.     BP := Seg (CharBuff[ULColumn]);
  452.   end;
  453.   Intr ($18, Registers);
  454.  
  455.   { Form bottom line by changing first and last characters in buffer to lower
  456.     corners }
  457.   CharBuff[ULColumn] := LLCornerChar;
  458.   CharBuff[LRColumn] := LRCornerChar;
  459.   { Write bottom line }
  460.   WITH Registers DO
  461.   begin
  462.     DI := $14; { Send data to screen function }
  463.     AX := 0;   { Characters AND Attributes }
  464.     BL := Byte(LRRow); BH := Byte(ULColumn);
  465.     CX := Width;
  466.     DX := Ofs (AttribBuff[ULColumn]);
  467.     SI := Ofs (CharBuff[ULColumn]);
  468.     BP := Seg (CharBuff[ULColumn]);
  469.   end;
  470.   Intr ($18, Registers);
  471.  
  472.   { If the height of the box is 2, we're done.  Else, set up intermediate
  473.     lines containing vertical lines on each side and spaces in between }
  474.   IF Height > 2 THEN
  475.   begin
  476.     FillChar (CharBuff[ULColumn], Width, Blank);
  477.     CharBuff[ULColumn] := VLineChar;
  478.     CharBuff[LRColumn] := VLineChar;
  479.  
  480.     { Now display each line containing box sides }
  481.     FOR I := (ULRow + 1) TO (LRRow - 1) DO
  482.     begin
  483.       WITH Registers DO
  484.       begin
  485.         DI := $14; { Send data to screen function }
  486.         AX := 0;   { Characters AND Attributes }
  487.         BL := Byte(I); BH := Byte(ULColumn);
  488.         CX := Width;
  489.         DX := Ofs (AttribBuff[ULColumn]);
  490.         SI := Ofs (CharBuff[ULColumn]);
  491.         BP := Seg (CharBuff[ULColumn]);
  492.       end;
  493.       Intr ($18, Registers);
  494.     end;
  495.   end;
  496.  
  497.   { Re-enable cursor }
  498.   CursorOn;
  499.  
  500.   { All done.  Return now. }
  501. end {DrawBox};
  502.  
  503. { Center - Display in center of current line using fast video stuff. }
  504. procedure Center (Msg :Str80);
  505. begin
  506.   FVColumn := ((80 - Length(Msg)) div 2) + 1;
  507.   WriteFastLn (Msg);
  508. end {Center};
  509.  
  510. { DrawOutline - Display a box around the screen borders with program name. 
  511.   This procedure displays a box around the perimeter of the screen and
  512.   centers the program name in the top line of the box.  This procedure is
  513.   used to "clear" most screens before they are displayed.  Fast video I/O
  514.   is also set up to start at row 3, column 3, with bottom and top margins
  515.   at 23 and 79, respectively.  These can be changed after the procedure is
  516.   called if so desired.  Note that if the outer box has already been drawn,
  517.   it is not drawn again. }
  518. procedure DrawOutline;
  519. var
  520.   I :Integer;
  521. begin
  522.   If not OutlineDrawn then
  523.   begin
  524.     DrawBox (1, 1, 80, 24, AtNormal);
  525.     FVRow := 1;
  526.     Center (Concat (' ', HeaderString, ' '));
  527.     OutlineDrawn := True;
  528.   end
  529.   else
  530.   begin
  531.     StartFastVideo (2, 2, AtNormal, 79, 23);
  532.     For I := 1 to 22 do WriteFastLn
  533. ('                                                                              ');
  534.   end;
  535.   if TwoDrives then
  536.   begin
  537.     FVRow := 24; FVAttribute := AtBold;
  538.     Center (Concat(' Physical Drive ',Chr($30+CurrentDrive),' selected '));
  539.     if SetUpExit = True then
  540.     begin
  541.       FVRow := 1; FVAttribute := ATBlinkBold;
  542.       Center (' Select main menu item 9 BEFORE pressing SETUP/CTRL-SETUP !!! ');
  543.     end;
  544.   end;
  545.   StartFastVideo (3, 3, AtNormal, 79, 23);
  546. end {DrawOutline};
  547.  
  548. { ReadScreen - Use fast video and raw character calls to read a string.
  549.   This procedure is passed a row and column, a maximum input length, a list
  550.   of allowed (printable) characters in the input, and a list of allowed
  551.   function keys.  It then reads data from the screen, handling the delete key
  552.   appropriately.  The result is returned in the string area passed, and the
  553.   "terminator" key value is also returned.  The terminator key is 0 for
  554.   Return, Keypad Enter, or DO, or the actual raw key value for allowed
  555.   function keys. }
  556. procedure ReadScreen (Column, Row :Byte; MaxLength :Integer;
  557.                       LegalChars :CharsAllowed;
  558.                       Functions :KeysAllowed;
  559.                       Var Result :Str80;
  560.                       Var Terminator :Integer);
  561. var
  562.   Attributes :Array [0..79] of Byte;
  563.   Text :Array [0..79] of Byte;
  564.   Position :Integer;
  565.   I, J :Integer;
  566.   UpshiftMode :Boolean;
  567.  
  568.   {sub}procedure PrintIt;
  569.   begin
  570.     WITH Registers DO
  571.     begin
  572.       DI := $14; { Send data to screen function }
  573.       AX := 0;   { Characters and Attributes }
  574.       BL := Byte(Row); BH := Byte(Column);
  575.       CX := MaxLength+1;
  576.       DX := Ofs (Attributes);
  577.       SI := Ofs (Text);
  578.       BP := SSeg;
  579.     end;
  580.     Intr ($18, Registers);
  581.   end {PrintIt};
  582.  
  583. begin
  584.   { Some special magic:  If the allowed characters shows that alphabetic
  585.     upper case characters (or "A", anyway) are allowed but that lower case
  586.     are not, we will upshift any lower case things to upper case.  Determine
  587.     if this will be the case, and flag it if so. }
  588.   UpshiftMode := (('A' in LegalChars) and (Not ('a' in LegalChars)));
  589.   { Start with spaces everywhere }
  590.   FillChar (Text, MaxLength+1, Ord (' '));
  591.   FillChar (Attributes, MaxLength+1, AtNormal);
  592.   { Start with no characters }
  593.   Position := 0;
  594.   While True do { do forever until we exit }
  595.   begin
  596.     { Put cursor character at current cursor position and make it blink }
  597.     Text[Position] := HollowBoxChar;
  598.     Attributes[Position] := AtBlink;
  599.     { Display current string at given position }
  600.     PrintIt;
  601.     Text[Position] := Ord (' ');
  602.     Attributes[Position] := AtNormal;
  603.     { Get character and remove caps lock indicator.  Then remove shift and ctrl
  604.       bits unless key is a function key.  And upshift lower case alphabetics
  605.       if we're dealing with one and that mode is on.  }
  606.     I := (RawChar and CapsLockMask);
  607.     If ((I and $100) = 0) then
  608.     begin
  609.       I := Lo (I);
  610.       If (UpshiftMode) then if ((I >= Ord ('a')) and (I <= Ord ('z'))) then
  611.         I := (I and not $20);
  612.     end;
  613.     { DO, ENTER, and RETURN are all changed to value zero }
  614.     IF (I = ReturnKeyCode) or (I = DoKeyCode) or (I = KeypadEnterKeyCode) then
  615.       I := 0;
  616.     { Now process characters received.  If printable character, add it to
  617.       what we have unless we've filled our alloted space.  If delete key,
  618.       back up one position unless we're already at the beginning.  If anything
  619.       else, see if it's a legitimate terminator and finish up if it is.
  620.       Otherwise, beep and ignore the character. }
  621.     If ((I = $7F) or (I = $08)) and (Position > 0) then
  622.     begin
  623.       Text[Position] := Ord(' ');
  624.       Position := Position - 1;
  625.     end
  626.     else If ((I >= $20) and (I < $7F)) and (Char(I) in LegalChars)
  627.         and (Position < MaxLength) then
  628.     begin
  629.       Text[Position] := I;
  630.       Position := Position + 1;
  631.     end
  632.     else If (I = 0) or ((I >= $100) and ((I-$100) in Functions)) then
  633.     begin
  634.       PrintIt; { Redisplay without hollow box "cursor" }
  635.       For J := 0 To Position-1 do Result[J+1] := Char(Text[J]);
  636.       Result[0] := Char(Position);
  637.       Terminator := I;
  638.       Exit;
  639.     end
  640.     else Write (^G);
  641.   end;
  642. end {ReadScreen};
  643.  
  644. { **************************************************************************
  645.                 OTHER LOW-LEVEL UTILITY FUNCTIONS & PROCEDURES
  646.   ************************************************************************** }
  647.  
  648. { Max - Returns maximum of two integers
  649.   Min - Returns minimum of two integers }
  650. function Min (a, b:Integer) :integer;
  651. begin
  652.   if a < b then Min := a else Min := b;
  653. end {Min};
  654. function Max (a, b:Integer) :integer;
  655. begin
  656.   if a < b then Max := b else Max := a;
  657. end {Max};
  658.  
  659. { Checksum - Return checksum of a sector block.
  660.   This function computes the checksum of a block by adding all 256 words
  661.   contained in the block (including the checksum word).  To check that the
  662.   checksum of a block just read is correct, see that this routine returns
  663.   zero for the block.  To compute the checksum for a block to be written,
  664.   set the checksum word to zero, call this function, and then set the
  665.   checksum word to the two's complement (negative) of the value returned.
  666.   For example, when you set the checksum to zero and call this routine it
  667.   returns 183.  You should replace the checksum word with -183; this will
  668.   yield a (proper) checksum of zero for the block being written. }
  669. function Checksum (Var block :SectorBlock): Integer;
  670. var
  671.   I,
  672.   Sum :Integer; {Running checksum total}
  673. begin
  674.   Sum := 0;
  675.   WITH block DO For I := 0 TO 255 DO Sum := Sum + SectorArray[I];
  676.   Checksum := Sum;
  677. end {Checksum};
  678.  
  679. { ComputeBATBlocks - Compute number of BAT sectors needed for disk capacity.
  680.   This function is passed the number of tracks on a disk.  It returns the
  681.   number of sectors required for the BAT area (250 tracks fit in one BAT
  682.   block). }
  683. function ComputeBATBlocks (Capacity :Integer) :Integer;
  684. var
  685.   I, J :Integer;
  686. begin
  687.   I := Capacity div 250;
  688.   J := Capacity mod 250;
  689.   If J > 0 then I := I + 1;
  690.   ComputeBATBlocks := I;
  691. end {ComputeBATBlocks};
  692.  
  693. { ComputeASTBlocks - Compute # of AST sectors needed to map alternate sectors.
  694.   This function is passed the number of sectors allocated to the alternate
  695.   sector area.  It returns the number of sectors required for the AST area
  696.   (100 alternate sectors fit in one AST block). }
  697. function ComputeASTBlocks (AltSectors :Integer) :Integer;
  698. var
  699.   I, J :Integer;
  700. begin
  701.   I := AltSectors div 100;
  702.   J := AltSectors mod 100;
  703.   If J > 0 then I := I + 1;
  704.   ComputeASTBlocks := I;
  705. end {ComputeASTBlocks};
  706.  
  707. { Xlate - Translate O/S logical sector number to physical sector number.
  708.   Under operating systems (CP/M and MS-DOS) disk sectors are "skewed" in
  709.   order to improve access time.  When this procedure is called with a
  710.   sector number (1-16), it takes that sector number as a sector number
  711.   used by the operating system and translates it to the correct physical
  712.   sector.  In practice, these sector translations occur for all blocks
  713.   except "boot" blocks and blocks on the disk which are not operating
  714.   system related (the HOM block, DPD block, etc.) }
  715. function Xlate (InSector :Byte) :Byte;
  716. const
  717.   XlateTable :Array [1..16] of Byte = ( 1,  8, 15,  6, 13,  4, 11,  2,
  718.                                         9, 16,  7, 14,  5, 12,  3, 10);
  719. begin
  720.   Xlate := XlateTable[InSector];
  721. end {Xlate};
  722.  
  723. { NextSector - Increments passed track & sector to point to next sector }
  724. procedure NextSector (Var Track :Integer; Var Sector :Byte);
  725. begin
  726.   Sector := Sector + 1;
  727.   If Sector > 16 then
  728.   begin
  729.     Track := Track + 1;
  730.     Sector := 1;
  731.   end;
  732. end {NextSector};
  733.  
  734. { PrevSector - Decrements passed track & sector to point to previous sector }
  735. procedure PrevSector (Var Track :Integer; Var Sector :Byte);
  736. begin
  737.   Sector := Sector - 1;
  738.   If Sector < 1 then
  739.   begin
  740.     Track := Track - 1;
  741.     Sector := 16;
  742.   end;
  743. end {PrevSector};
  744.  
  745. { **************************************************************************
  746.                     WINCHESTER I/O FUNCTIONS & PROCEDURES
  747.   ************************************************************************** }
  748.  
  749. { DoHDFunction - Perform the specified winchester control function.
  750.   This procedure performs the specified function (read, write, write/verify,
  751.   format, etc.) on the specified track and sector of the winchester disk.
  752.   The buffer is either the place the data comes from or goes to, depending
  753.   on the function; its segment and offset are passed separately.  The routine
  754.   returns a status value (nonzero if error) and an error type (providing
  755.   additional error information if status <> 0). }
  756. procedure DoHDFunction (FuncNum :Integer; RTrack :Integer; RSector :Byte;
  757.                         BSeg: Integer; BOffset :Integer;
  758.                         VAR RStatus :Byte; VAR RErrorType :Byte);
  759. begin
  760.   { Initialize block for to DOS }
  761.   WITH WinchesterBlock DO
  762.   begin
  763.     Func := FuncNum;         { Requested function }
  764.     DriveCode := $FF;        { Physical unit, not logical drive }
  765.     Count := 1;              { Number of sectors to read = 1 }
  766.     BuffOfs := BOffset;      { Pointer to buffer... }
  767.     BuffSeg := BSeg;
  768.     Sector := RSector;
  769.     Surface := (RTrack MOD HOMBlock.HOM.Surfaces) OR $40;
  770.     Track := (RTrack DIV HOMBlock.HOM.Surfaces);
  771.     Status := $FF;  ErrorType := $FF;
  772.   end;
  773.   { Now set up request block that will be sent to BIOS }
  774.   With DriverRequest do
  775.   begin
  776.     HeaderLength := 13;
  777.     UnitCode     := 5;
  778.     CommandCode  := 3; { IOCTL function }
  779.     Status       := 0;
  780.     MediaDescrip := $FF;
  781.     TransferAddr := Ptr (Seg (WinchesterBlock), Ofs (WinchesterBlock));
  782.     Count        := 0;   { Not used for IOCTL }
  783.     Start        := 0;   { Not used for IOCTL }
  784.   end;
  785.   { Now use assembly code to call BIOS.  First the stragety entry point, then
  786.     the interrupt entry point }
  787.   inline (
  788.         $8C/$DB/              { MOV BX,DS }
  789.         $8E/$C3/              { MOV ES,BX }
  790.         $BB/DriverRequest/    { MOV BX,OFFSET DriverRequest }
  791.         $FF/$1E/HDStrat/      { CALL DWORD PTR HDStrategy }
  792.         $FF/$1E/HDInter       { CALL DWORD PTR HDInterrupt }
  793.   );
  794.   { Return resulting status to caller }
  795.   With WinchesterBlock DO
  796.   begin
  797.     RStatus := Status;
  798.     RErrorType := ErrorType;
  799.   end;
  800. end {DoHDFunction};
  801.  
  802. { ReadSector - Read the specified track/sector into the specified buffer.
  803.   This function returns TRUE if the read succeeds, FALSE if it fails }
  804. function ReadSector (RTrack :Integer; RSector :Byte; Var Buffer :SectorBlock)
  805.                     :Boolean;
  806. Var
  807.   Status, ErrorType :Byte;
  808. begin
  809.   { Perform read of specified track and sector }
  810.   DoHDFunction (READFunc,
  811.                 RTrack, RSector,
  812.                 Seg(Buffer), Ofs(Buffer),
  813.                 Status, ErrorType);
  814.  
  815.   ReadSector := (Status = 0); { Return TRUE if resulting status is zero }
  816. end {ReadSector};
  817.  
  818. { WriteSector - Write the specified track/sector from the specified buffer.
  819.   This function returns TRUE if the write succeeds, FALSE if it fails.  A
  820.   Write with Verify function is used to assure that the data was written
  821.   correctly. }
  822. function WriteSector (WTrack :Integer; WSector :Byte; Var Buffer :SectorBlock)
  823.                      :Boolean;
  824. Var
  825.   Status, ErrorType :Byte;
  826. begin
  827. { WriteLn (Lst, 'Write track ', WTrack, ' sector ', WSector);
  828.   WriteSector := True; Exit; }
  829.   { Perform Write of specified track and sector }
  830.   DoHDFunction (WRITEVERIFYFunc,
  831.                 WTrack, WSector,
  832.                 Seg(Buffer), Ofs(Buffer),
  833.                 Status, ErrorType);
  834.  
  835.   WriteSector := (Status = 0); { Return TRUE if resulting status is zero }
  836. end {WriteSector};
  837.  
  838. { FormatTrack - Format the specified track.
  839.   This function returns TRUE if the format succeeds, FALSE if it fails. }
  840. function FormatTrack (WTrack :Integer) :Boolean;
  841. Var
  842.   Status, ErrorType :Byte;
  843. begin
  844.   { Perform Write of specified track and sector }
  845.   DoHDFunction (FORMATFunc,
  846.                 WTrack, 1,
  847.                 Seg(FormatData), Ofs(FormatData),
  848.                 Status, ErrorType);
  849.  
  850.   FormatTrack := (Status = 0); { Return TRUE if resulting status is zero }
  851. end {FormatTrack};
  852.  
  853. { WriteNoError - Write the specified track/sector, abort if error.
  854.   This procedure works just like function WriteSector, above, but it prints
  855.   a warning message if the write fails.  It does not return a status value. }
  856. procedure WriteNoError (WTrack :Integer; WSector :Byte;
  857.                         Var Buffer :SectorBlock);
  858. var
  859.   I :Integer;
  860. begin
  861.   If Not WriteSector (WTrack, WSector, Buffer) then With Buffer do
  862.   begin
  863.     DrawOutline;
  864.     StartFastVideo (3, 5, AtBlinkBold, 78, 23);
  865.     Center ('ERROR WRITING DATA TO HARD DISK!');
  866.     Write (^G^G^G);
  867.     FVAttribute := AtBold;
  868.     Center (Concat ('Track: ', Hex4Digit (WTrack), 'H  Sector: ',
  869.                     Hex2Digit (WSector), 'H  Block type: ',
  870.                     PrintChar (SectorBytes[0]), PrintChar (SectorBytes[1]),
  871.                     PrintChar (SectorBytes[2])));
  872.     If WTrack in [0..1] then
  873.       Center (Concat ('(Duplicate data in track ', Chr(Ord('0')+WTrack+2),
  874.                       ' may still permit use of the disk)'))
  875.     else if WTrack in [2..3] then
  876.       Center (Concat ('(Duplicate data in track ', Chr(Ord('0')+Wtrack-2),
  877.                       ' may still permit use of the disk)'))
  878.     else
  879.       WriteLn;
  880.     WriteLn;
  881.     FVAttribute := AtNormal;
  882.     Center ('WUTIL was unable to write the specified track and sector to your');
  883.     Center ('hard disk.  Generally, this indicates a serious error and your');
  884.     Center ('disk will not be usable.  In some cases, the disk may still be');
  885.     Center ('usable because primary data stored in tracks 0 and 1 is');
  886.     Center ('duplicated in tracks 2 and 3.  A message to that effect is');
  887.     Center ('printed above if your disk is still salvageable.');
  888.     WriteLn;
  889.     Center ('Whether this disk appears usable or not after you reboot, you');
  890.     Center ('you should reformat and initialize the disk at your earliest');
  891.     Center ('convenience so that the disk may continue to be used without');
  892.     Center ('difficulty.');
  893.     FVRow := 22;  FVAttribute := AtBold;
  894.     Center ('Press any key to continue');  
  895.     I := RawChar;
  896.   end;
  897. end {WriteNoError};
  898.  
  899. { WriteMajorBlock - Write the specified "major block" from the specified buffer.
  900.   This procedure performs several functions.  First, it computes a proper
  901.   checksum for the block contained in "Buffer", which is assumed to be one
  902.   of the major blocks (HOM, OSN, DPD, BAT, AST) contained in the first tracks
  903.   on the disk.  Then it writes the block, first to the specified "WTrack" and
  904.   "WSector", and then to the same sector in track "WTrack+2", which is the
  905.   location of the backup copy of the sector on the disk.  Writes are performed
  906.   using procedure WriteNoError, which will print a warning message if there
  907.   is a problem with the write.  Note that the writing of the additional "backup"
  908.   copy only occurs if the requested track number is 0 or 1.  Otherwise, the
  909.   block is assumed to reside in an unusual location and is only written at
  910.   its primary location. }
  911. procedure WriteMajorBlock (WTrack :Integer; WSector :Byte;
  912.                            Var Buffer :SectorBlock);
  913. begin
  914.   { The checksum is always in the same position.  We'll use the definition
  915.     for the HOM block, but it doesn't really matter which one we use. }
  916.   Buffer.HOM.Checksum := 0;
  917.   Buffer.HOM.Checksum := -(Checksum(Buffer));
  918.   { The result of the operation above is that the sum of the integer data in
  919.     the whole block, including the Checksum word, will be zero. }
  920.   WriteNoError (WTrack, WSector, Buffer);
  921.   If WTrack < 2 then WriteNoError (WTrack+2, WSector, Buffer);
  922. end {WriteMajorBlock};
  923.  
  924. { ReadHOMBlock - Read the specified track/sector, verify as valid HOM block.
  925.   If block cannot be read or does not appear to be a HOM block, returns
  926.   value FALSE; returns TRUE if all looks OK }
  927. function ReadHOMBlock (Track :Integer; Sector :Byte; Var Buffer :SectorBlock)
  928.                       :Boolean;
  929. begin
  930.   if not ReadSector (Track, Sector, Buffer) then
  931.   begin
  932.     ReadHOMBlock := FALSE;
  933.     Exit;
  934.   end;
  935.   { Set return value to TRUE if checksum is OK and block ID says it's a HOM
  936.     block.  FALSE otherwise. }
  937.   ReadHOMBlock := ((Checksum(Buffer) = 0) AND (Buffer.HOM.ID = 'HOM'));
  938. end {ReadHOMBlock};
  939.  
  940. { ReadDPDBlock - Read the specified track/sector, verify as valid DPD block.
  941.   If block cannot be read or does not appear to be a DPD block, returns
  942.   value FALSE; returns TRUE if all looks OK }
  943. function ReadDPDBlock (Track :Integer; Sector :Byte; Var Buffer :SectorBlock)
  944.                       :Boolean;
  945. begin
  946.   if not ReadSector (Track, Sector, Buffer) then
  947.   begin
  948.     ReadDPDBlock := FALSE;
  949.     Exit;
  950.   end;
  951.   { Set return value to TRUE if checksum is OK and block ID says it's a DPD
  952.     block.  FALSE otherwise. }
  953.   ReadDPDBlock := ((Checksum(Buffer) = 0) AND (Buffer.DPD.ID = 'DPD'));
  954. end {ReadDPDBlock};
  955.  
  956. { ReadOSNBlock - Read the specified track/sector, verify as valid OSN block.
  957.   If block cannot be read or does not appear to be a OSN block, returns
  958.   value FALSE; returns TRUE if all looks OK }
  959. function ReadOSNBlock (Track :Integer; Sector :Byte; Var Buffer :SectorBlock)
  960.                       :Boolean;
  961. begin
  962.   if not ReadSector (Track, Sector, Buffer) then
  963.   begin
  964.     ReadOSNBlock := FALSE;
  965.     Exit;
  966.   end;
  967.   { Set return value to TRUE if checksum is OK and block ID says it's a OSN
  968.     block.  FALSE otherwise. }
  969.   ReadOSNBlock := ((Checksum(Buffer) = 0) AND (Buffer.OSN.ID = 'OSN'));
  970. end {ReadOSNBlock};
  971.  
  972. { ReadBATBlock - Read the specified track/sector, verify as valid BAT block.
  973.   If block cannot be read or does not appear to be a BAT block, returns
  974.   value FALSE; returns TRUE if all looks OK.  Unlike the other block-reading
  975.   routines here, this routine is also passed a logical block number which is
  976.   expected.  If the block which is read is not the logical BAT block we
  977.   expected to see, we'll also return an error.  }
  978. function ReadBATBlock (Track :Integer; Sector :Byte; LogicalBlock :Byte;
  979.                        Var Buffer :SectorBlock) :Boolean;
  980. begin
  981.   if not ReadSector (Track, Sector, Buffer) then
  982.   begin
  983.     ReadBATBlock := FALSE;
  984.     Exit;
  985.   end;
  986.   { Set return value to TRUE if checksum is OK and block ID says it's a BAT
  987.     block and it's the expected logical block.  FALSE otherwise. }
  988.   ReadBATBlock := ((Checksum(Buffer) = 0) AND (Buffer.BAT.ID = 'BAT')
  989.                    AND (Buffer.BAT.LBN = LogicalBlock));
  990. end {ReadBATBlock};
  991.  
  992. { ReadASTBlock - Read the specified track/sector, verify as valid AST block.
  993.   If block cannot be read or does not appear to be a AST block, returns
  994.   value FALSE; returns TRUE if all looks OK.  Unlike the other block-reading
  995.   routines here, this routine is also passed a logical block number which is
  996.   expected.  If the block which is read is not the logical AST block we
  997.   expected to see, we'll also return an error.  }
  998. function ReadASTBlock (Track :Integer; Sector :Byte; LogicalBlock :Byte;
  999.                        Var Buffer :SectorBlock) :Boolean;
  1000. begin
  1001.   if not ReadSector (Track, Sector, Buffer) then
  1002.   begin
  1003.     ReadASTBlock := FALSE;
  1004.     Exit;
  1005.   end;
  1006.   { Set return value to TRUE if checksum is OK and block ID says it's a AST
  1007.     block and it's the expected logical block.  FALSE otherwise. }
  1008.   ReadASTBlock := ((Checksum(Buffer) = 0) AND (Buffer.AST.ID = 'AST')
  1009.                    AND (Buffer.AST.LBN = LogicalBlock));
  1010. end {ReadASTBlock};
  1011.  
  1012. { SectorIsBad - Returns TRUE if specified track/sector is marked as bad }
  1013. Function SectorIsBad (Track :Integer; Sector :Byte) :Boolean;
  1014. begin
  1015.   { Note: The first check we do (num <> 0) would seem to be superfluous, but
  1016.     it allows us to skip the (very slow) "IN" test if none of the sectors
  1017.     in the given track are bad, which will be the case most of the time. }
  1018.   If SectorTable[Track].Num = 0 then
  1019.   begin
  1020.     SectorIsBad := False;
  1021.     Exit;
  1022.   end;
  1023.   SectorIsBad := (Pred(Sector) IN SectorTable[Track].Bits);
  1024. end;
  1025.  
  1026. { MarkSectorGood - Marks the given sector as good in the sector table }
  1027. Procedure MarkSectorGood (Track :Integer; Sector :Byte);
  1028. begin
  1029.   With SectorTable[Track] do Bits := Bits - [Pred(Sector)];
  1030. end;
  1031.  
  1032. { MarkSectorBad - Marks the given sector as bad in the sector table }
  1033. Procedure MarkSectorBad (Track :Integer; Sector :Byte);
  1034. begin
  1035.   With SectorTable[Track] do Bits := Bits + [Pred(Sector)];
  1036. end;
  1037.