Program AUTOBOOT;

{  By Waid Reynolds   June '85

   Ever want to set up a boot disk so that it would run a
   program automatically when your Kaypro boots up?  This
   can be done when you format a disk using the COPY program,
   but, you may want to later change the boot instructions
   without having to reformat the disk.

   Here's a simple Turbo Pascal program which will allow
   you to specify a command line of up to 16 characters
   for the autoboot sequence.

   To run this program after it is compiled, just type
   AUTOBOOT followed by the <RETURN> key.  The program
   will prompt you for the disk drive and the desired
   autoboot command line.  Once entered, the program
   will write your command line to the appropriate disk,
   then ask if you want to do it again.

   The command line is written to track 0, sector 2
   beginning at byte 8.  After booting up, CP/M looks
   at this location to determine if an autoboot is
   desired.  A valid command line will then
   be executed, if found.
}

Type
  str17 = string[17];

Const
  { BIOS entry points relative to Warm Boot }
  seldsk  = 8;  { select disk }
  settrk  = 9;  { set track }
  setsec  = 10; { set sector }
  setdma  = 11; { set dma address }
  rd      = 12; { read selected sector }
  wr      = 13; { write it}
  getdisk = 25; { BDOS get disk function }
  track = 0;    { Location of auto boot }
  sec   = 1;    { command line }
  quit: boolean = false;

Var
  cmd_line: str17; { The new command line string. Includes one
                     extra byte, so we can tell if user entered too
                     long a string. }
  cmd_line_len: byte;
  bios_address: integer absolute 1; { Address of BIOS jump vector }
  default_drive_num, drive_num: byte;
  drive_letter: char; { Drive A - P }
  sector: array [0..127] of char; { Buffer for the disk sector we are
                                    going to modify }
  dma_address: integer;

Procedure Init;
  (* Set the dma address in memory for sector t0,s1 to be read
     into memory so that it will overlay our array called
     "sector[]".  Also find out what the currently logged
     disk drive is.
  *)
begin
  dma_address := addr(sector); { The addr of the 1st byte of "sector[]" array }
  default_drive_num := Bdos(getdisk); { 0 = A:, 1 = B:, etc. }
End; {init}

Procedure Intro;
  (* Introduce the program *)
begin
  clrscr;
  writeln('AUTOBOOT ver 1.01');
  writeln('June 1985 by Waid Reynolds');
  writeln('[Modified 8/14/85 by George B. Cabellon]');
  writeln;
  writeln('Public domain software for setting the autoboot command line of Kaypro');
  writeln('floppy or hard disks.  This program works with all 8 bit Kaypros, including');
  writeln('the Kaypro 10.  On the 10, only hard drive A: and floppy C: are boot drives.');
  writeln('DO NOT put an autoboot command on hard drive B:.  It won''t work.');
  writeln;
End; {Intro}

Procedure Get_drive;
  (* Get the user to enter the letter of the desired disk drive.
  *)
begin
  repeat
    writeln;
    write('Enter the letter of the desired drive (A - P) or Q to quit: ');
    read(kbd, drive_letter);
    drive_letter := upcase(drive_letter);

    { "upcase()" is a built-in Turbo
      function which converts one
      character to upper case if it
      is lower case. }

  until drive_letter in ['A'..'Q'];

  quit := (drive_letter = 'Q');

  if not quit then (* Convert to number *)
  begin
    drive_num := ord(drive_letter) - 65;

    { The above line converts the ASCII value of drive_letter to a number
      in the range of 0 - 15.  The ASCII values of letters A to P are 65 to
      80.  The "ord" built-in function tells Turbo to return the value of
      the character, 65 - 80 in this case.   By subtracting 65 we then
      arrive at a number in the range of 0 - 15, which CP/M can
      understand. }

    Bios(seldsk, drive_num); { Select the drive }
  end;
End; {get_drive}

Procedure Pad(var s: str17);
  (* Fill out the command string to full length of 16 chars
     using spaces.  We don't want anything other than
     spaces following our command line.  By using
     "var" ahead of "s" in the first line of this
     procedure, we tell Turbo to pass the actual
     location of the string to be operated on.  If
     "var" was not present, we would be operating on
     a temporary string, "s" created in this proc.
     Thus adding spaces to the end of "s" would not
     effect the real string.
  *)
  Var pos: integer;
begin
  while length(s) < 16 do
    s := s + ' ';
End; {pad}

Procedure Upcase_string(var s: str17);
  (* Convert the command line string to upper case so CP/M won't choke on
     it.  Note the use of "var" again, so that we will be operating
     on the real string, not a temporary clone of it.
  *)
  Var pos: integer;
begin
  for pos := 1 to length(s) do
  begin
    if s[pos] in ['a'..'z'] then
       s[pos] := upcase(s[pos]);
  end;
End; {upcase_string}

Procedure Display_old_cmd_line;
  (* Display the command line, if any, currently located
     at track 0, sector 1 on the designated disk drive.
  *)
  Var pos: integer;
begin
  Bios(settrk, track);       { Set the track }
  Bios(setsec, sec);         { Set the sector }
  Bios(setdma, dma_address); { Set dma address }
  Bios(rd); { Read the disk sector into memory starting
              at the beginning of our array called "sector[]".
              It is critical that this be done so that the array
              will contain the proper data when it is rewritten
              to disk later on. }

  { Now, display the current command line, if any. }
  writeln;
  write('Current autoboot command line ---------------> "');
  for pos := 8 to 23 do
    write(sector[pos]);
  writeln('"');
  writeln;
End; {display_old_cmd_line}

Procedure Get_command_line;
  Var pos: integer;
begin
  writeln;
  Display_old_cmd_line;
  repeat
    writeln('Press <RETURN> to quit or');
    writeln('Enter desired command line');
    writeln(' of up to 16 characters:                       [................]');
    write(' [Upper or lower case may be used] -----------> ');
    readln(cmd_line);
    cmd_line_len := length(cmd_line);
    if cmd_line_len > 16 then
    begin
      writeln;
      writeln(#7,' ++ Sorry, must be less than 16 characters long ++');
      { "#7" makes the Kaypro beep. }
      writeln;
    end; {if}
  until cmd_line_len <= 16;

  quit := (cmd_line_len = 0);

  if not quit then
  begin
    Pad(cmd_line);           { Fill out w/spaces to length of 16 }
    Upcase_string(cmd_line); { Convert to uppercase }

    { Assign new cmd line to sector[] character by character }
    for pos := 8 to 23 do
      sector[pos] := cmd_line[pos-7];

    { And set the length byte so CP/M will know how long the new
      command line is }
    sector[7] := chr(cmd_line_len);
  end; {if}
End; {get_command_line}

Procedure Write_command_line;
  (* Write the "sector[]" array to disk at sector t0, s1.
  *)
  Var secno: integer;
begin
  writeln;
  write('Writing new autoboot command line ------------> ');
  write(cmd_line,'  to disk...');

  Bios(settrk, track); { Set the track }
  Bios(setsec, sec);   { Set the sector }
  Bios(setdma, dma_address); { Set dma address }
  Bios(wr); { Write the sector to disk }

  { Force BIOS to actually write our modified sector to disk.
    Kaypro CP/M buffers 4 128 byte sectors in memory.
    So, a write to disk must be forced by reading enough
    sectors to cause CP/M to flush the current buffer to disk. }
  for secno := sec+1 to sec + 5 do
  begin
    Bios(setsec, secno);
    Bios(rd);
  end;
  writeln;
  writeln;
  writeln(#7,' ++ Done ++');
End; {write_command_line}


BEGIN {============ MAIN =============}
  Init;
  Intro;
  repeat
    Get_drive;
    if not quit then
    begin
      Get_command_line;
      if not quit then
        Write_command_line;
    end; {if}

    { Set default drive back to original }
    Bios(seldsk, default_drive_num);
  until quit;
END. of Autoboot