(********************************************************************************************)
(*                                                                                          *)
(* Dieses Programm erzeugt eine 64 K-Byte Datei fuer ein bootfaehiges PROF-180X EPROM       *)
(*                                                                                          *)
(* Es werden mindestens die folgenden drei Eingangsdateien benoetigt:                       *)
(*   PMON.HCX    : PROF-180X Monitor Version 1.3 oder groesser                              *)
(*   CPM3.SYS    : CP/M plus fuer PROF-180X, BIOS Version 1.1 oder groesser                 *)
(*   CCP.COM     : Standard CCP fuer CP/M plus                                              *)
(*                                                                                          *)
(* Letzte Aenderung am: 19.10.1986                                                          *)
(*                                                                                          *)
(*                                                         (c) Joachim Hanst, Conitec 1986  *)
(*                                                                                          *)
(********************************************************************************************)


program genboot;

const

name_monitor_datei  = 'PMON.HXC';                  (* Name der PROF-180X Monitordatei *)
name_cpm3_datei     = 'CPM3.SYS';                  (* Name der CP/M plus Datei        *)
name_ccp_datei      = 'CCP.COM';                   (* Name der CCP Datei              *)
name_ziel_datei     = 'EPMON.HXC';                 (* Hier Name der Zieldatei angeben *)

(* Es folgen Textkonstanten fuer Benutzer-Dialog *)

kopf1              = 'Boot EPROM Generator for PROF-180X Version 1.0';
kopf2              = '                           (c) JH Conitec 1986';
oeffne_fehler      = 'error, one ore more of the folowing files are not found :';
ende_text          = 'end of GENBOOT';
kopie_text         = 'copying : ';
speicherplatz_text = 'remaining space : ';
byte_text          = ' byte';
datei_zugross_text = 'error, file to long';
datei_null_text    = 'error, file is emty';
nicht_gefunden     = 'error, file not found';
datei_doppelt      = 'error, file already copied';
name_eingabe_text  = 'enter file name to copy or RETURN to stop : ';


type

byte128            = array[0..127] of byte;
str11              = string[11];
str14              = string[14];
str15              = string[15];
str80              = string[80];
data_file          = file;

header_rec = record
             res_top_page  : byte;
             res_length    : byte;
             bnk_top_page  : byte;
             bnk_length    : byte;
             bios_begin    : integer;
             res1          : array[1..10] of byte;
             cop_msg       : array[1..36] of byte;
             res2          : byte;
             ser_low       : integer;
             ser_middle    : integer;
             ser_high      : integer;
             res3          : array[1..69] of byte;
             end;

patch_rec  = record
             res1          : array[1..16] of byte;
             bios_start    : integer;
             boot_dir      : integer;
             res2          : array[1..108] of byte;
             end;

fcb_rec    = record
             def_drive     : byte;
             name          : array[0..7] of char;
             extension     : array[0..2] of char;
             dummy         : array[0..20] of byte;
             r0_r1         : integer;
             r2            : byte;
             end;

dir_rec    = record
             name          : array[0..7] of char;
             extension     : array[0..2] of char;
             anfang        : integer;
             laenge        : byte;
             stop          : integer;
             end;

var

dir_zeiger         : integer;
dir_records        : integer;
directory          : array[0..255] of dir_rec;
no_open_err        : boolean;
fcb                : fcb_rec;
buffer             : byte128;           (* Record Buffer fuer Datenuebertragung       *)
header             : header_rec absolute buffer;
patch              : patch_rec absolute buffer;

bios_start         : integer;           (* Hier wird BIOS-Einsprungadresse abgelegt   *)
res_zeiger         : integer;
bnk_zeiger         : integer;
res_laenge         : integer;
bnk_laenge         : integer;
source_zeiger      : integer;
dest_down_pointer  : integer;            (* Zeiger auf ersten freien Record von unten *)
dest_up_pointer    : integer;            (* Zeiger auf ersten freien Record von oben  *)
i                  : integer;
monitor_datei      : file;
cpm3_datei         : file;
daten_datei        : file;
ziel_datei         : file;

function const_str(c:char;n:integer):str80;

var s : string[80];

begin
  if n<0 then n:=0;
  s[0]:=chr(n);
  fillchar(s[1],n,c);
  const_str:=s;
end;

(*
Testet ob im Buffer nur FFh steht, wenn ja liefert die Funktion true
*)
function test_buffer:boolean;

var i    : integer;
    flag : boolean;

begin
  flag:=true;
  i:=0;
  while (i<128) and flag do
  begin
    if buffer[i] <> 255 then flag:=false;
    i:=i+1;
  end;
  test_buffer:=flag;
end;

(*
Compute file size ermittelt die anzahl der Records einer Datei
und erzeugt aus dem in Stringform vorliegenden Dateinamen die Daten fuer den FCB.
Enhaelt die Datei mehr als 32767 Records ist das Ergebniss negativ.
*)

function compute_file_size(file_string:str15):integer;

var pfcb : array[1..2] of integer;

begin
  file_string:=file_string+chr(0);
  pfcb[1]:=addr(file_string)+1;
  pfcb[2]:=addr(fcb);
  bdos(152,addr(pfcb));
  bdos(35,addr(fcb));
  compute_file_size:=fcb.r0_r1;
  if fcb.r2 <> 0 then compute_file_size:=-1;
end;


procedure show_remainig_space;

begin
  write(speicherplatz_text,((dest_down_pointer-dest_up_pointer+1)*128.0):5:0);
  write(byte_text);
end;

procedure beep;

begin
  write(char(7));
end;


(*
Schreibe 128-Byte Variable in Zieldatei)
*)
procedure schreibe_ziel_datei(var block : byte128; var zeiger : integer);

begin
    seek(ziel_datei,zeiger);
    blockwrite(ziel_datei,block,1);
end;


(*
Erzeuge Zieldatei; eine bereits existierende Zieldatei wird geloescht.
*)
procedure erzeuge_ziel_datei;

begin;
  assign(ziel_datei,name_ziel_datei);
  rewrite(ziel_datei);
  fillchar(buffer,128,255);
  for i:=0 to 511 do schreibe_ziel_datei(buffer,i); (* fuelle Zieldatei mit FFh *);
end;

(*
Oeffne Datei; falls die Datei nicht vorhanden ist wird flag auf false gesetzt.
*)
procedure oeffne_datei(var filevar : data_file; filname : str14; var flag : boolean);

begin
  assign(filevar,filname);
  (*$I-*)
  reset(filevar);
  (*$I+*)
  flag:=(ioresult = 0);
end;

(*
Oeffne Default Dateien: Oeffnet Monitor, CPM3.SYS und CCP.COM, falls eine Datei nicht
exisitert wird die Variable no_open_err auf false gesetzt und Fehlermeldung ausgegeben.
*)
procedure oeffne_default_dateien;

begin
  oeffne_datei(monitor_datei,name_monitor_datei,no_open_err);
  if no_open_err then
  begin
    oeffne_datei(cpm3_datei,name_cpm3_datei,no_open_err);
    if no_open_err then oeffne_datei(daten_datei,name_ccp_datei,no_open_err)
  end;
  if not no_open_err then
  begin
    beep;
    writeln;
    writeln(oeffne_fehler);
    writeln;
    writeln('                    ',name_monitor_datei);
    writeln('                    ',name_cpm3_datei);
    writeln('                    ',name_ccp_datei);
  end;
end;

(*
Kopiere die CP/M plus Systemdatei in die Zieldatei
*)
procedure kopiere_system_datei;

begin
  write(kopie_text,name_cpm3_datei,const_str(' ',16-length(name_cpm3_datei)));
  blockread(cpm3_datei,buffer,1);        (* lese Header der Systemdatei und            *)
  res_zeiger:=header.res_top_page*2;     (* uebertrage die wichtigen Daten             *)
  bnk_zeiger:=header.bnk_top_page*2;     (* in globale Variablen                       *)
  res_laenge:=header.res_length*2;
  bnk_laenge:=header.bnk_length*2;
  bios_start:=header.bios_begin;
  source_zeiger:=2;                      (* setze source_zeiger auf ersten Datenrecord *)
  dest_down_pointer:=res_zeiger-1;
  for i:=1 to res_laenge+bnk_laenge do   (* Starte Kopiervorgang                       *)
  begin
    seek(cpm3_datei,source_zeiger);
    blockread(cpm3_datei,buffer,1);
    schreibe_ziel_datei(buffer,dest_down_pointer);
    source_zeiger:=source_zeiger+1;
    if i = res_laenge
    then
      dest_down_pointer:=bnk_zeiger-1
    else
      dest_down_pointer:=dest_down_pointer-1;
  end;
  show_remainig_space;
  writeln;
end;


(*
Kopiere die Monitordatei in die Zieldatei
*)

procedure kopiere_monitor_datei;

var zeiger : integer;
    flag   : boolean;
begin
  write(kopie_text,name_monitor_datei,const_str(' ',16-length(name_monitor_datei)));
  zeiger:=128;                           (* setze Zeiger auf letzten Monitor Record    *)
  flag:=true;
  while flag and (zeiger > 0) do         (* lese bis Record ohne FFh kommt             *)
  begin
    zeiger:=zeiger-1;
    seek(monitor_datei,zeiger);
    blockread(monitor_datei,buffer,1);
    flag:=test_buffer;
  end;
  if flag then zeiger:=zeiger-1;         (* setze dest_up_pointer auf ersten freien   *)
  dest_up_pointer:=zeiger+1;             (* Record im Monitor.                        *)
  while (zeiger >= 0) do                 (* Kopiere alle Records, die Daten           *)
  begin                                  (* enthalten, in die Zieldatei.              *)
    schreibe_ziel_datei(buffer,zeiger);
    zeiger:=zeiger-1;
    if zeiger >= 0 then
    begin
      seek(monitor_datei,zeiger);
      blockread(monitor_datei,buffer,1);
    end;
  end;
  show_remainig_space;
  writeln;
end;


(*
Kopiere Daten Datei
*)

procedure kopiere_daten_datei(var datei_name:str14);

var

flag          : boolean;
records       : integer;
diradr        : integer;
fcbadr        : integer;
k             : integer;

function datei_schon_kopiert:boolean;

var

i,k           : integer;
daddr,faddr   : integer;
flag          : boolean;
ch            :char;

begin
  i:=0;
  flag:=false;
  while (i < dir_zeiger) and (not flag) do
  begin
    daddr:=addr(directory[i].name);
    faddr:=addr(fcb.name);
    k:=0;
    flag:=true;
    while (k < 11) and flag do
    begin
      if mem[daddr+k] <> mem[faddr+k] then flag:= false;
      k:=k+1;
    end;
    i:=i+1;
  end;
  datei_schon_kopiert:=flag;
end;

begin
  write(kopie_text,datei_name,const_str(' ',16-length(datei_name)));
  oeffne_datei(daten_datei,datei_name,flag);
  if flag then
  begin (* datei da *)
    records:=compute_file_size(datei_name);
    if (records > (dest_down_pointer-dest_up_pointer)) or (records < 0) or (records > 255)
    then writeln(^G,datei_zugross_text)
    else if records = 0 then writeln(^G,datei_null_text)
    else if datei_schon_kopiert then writeln(^G,datei_doppelt)
    else
      begin
        diradr:=addr(directory[dir_zeiger].name);
        fcbadr:=addr(fcb.name);
        for k:=0 to 10 do mem[diradr+k]:=mem[fcbadr+k];
        directory[dir_zeiger].laenge:=records;
        while records <> 0 do
        begin
          records:=records-1;
          seek(daten_datei,records);
          blockread(daten_datei,buffer,1);
          schreibe_ziel_datei(buffer,dest_down_pointer);
          dest_down_pointer:=dest_down_pointer-1;
        end;
        directory[dir_zeiger].anfang:=(dest_down_pointer+1)*128;
        if dir_zeiger <> 0 then directory[dir_zeiger-1].stop:=0;
        dir_zeiger:=dir_zeiger+1;
        if dir_zeiger = ((dir_zeiger shr 3)*8) then
                                               begin
                                                 dest_up_pointer:=dest_up_pointer+1;
                                                 dir_records:=dir_records+1;
                                                end;
        dest_up_pointer:=dest_up_pointer+1;
        show_remainig_space;                (* Zeige vergleibenden Speicherplatz *)
        dest_up_pointer:=dest_up_pointer-1;
      end;
  end  (* Datei da *)
  else writeln(^G,nicht_gefunden);
end;

procedure kopiere_alle_daten_dateien;

var

in_str        : str14;
k             : integer;
bufad         : integer;
dirad         : integer;

begin
  dir_records:=1;
  fillchar(directory,4096,255);
  dir_zeiger:=0;
  in_str:=name_ccp_datei;
  while in_str <> '' do
  begin
    kopiere_daten_datei(in_str);
    writeln;
    write(name_eingabe_text);
    read(in_str);
    writeln;
  end;
  if dir_zeiger = ((dir_zeiger shr 3)*8) then dir_records:=dir_records-1;
  dir_zeiger:=dir_zeiger-1;
  dir_zeiger:=(dir_zeiger shr 3)*8;
  for i:=1 to dir_records do
  begin
    bufad:=addr(buffer);
    dirad:=addr(directory[dir_zeiger]);
    for k:=0 to 127 do
    begin
      mem[bufad]:=mem[dirad];
      bufad:=bufad+1;
      dirad:=dirad+1;
    end;
    schreibe_ziel_datei(buffer,dest_down_pointer);
    dir_zeiger:=dir_zeiger-8;
    dest_down_pointer:=dest_down_pointer-1;
  end;
  seek(ziel_datei,1);   (* patche BIOS-Startadresse und DIR-Zeiger in Monitor *)
  blockread(ziel_datei,buffer,1);
  patch.bios_start:=bios_start;
  patch.boot_dir:=(dest_down_pointer+1)*128;
  seek(ziel_datei,1);
  blockwrite(ziel_datei,buffer,1);
end;

(*
Schreibe Startmeldung des Programms auf den Bildschirm
*)
procedure anfangs_meldung;

var i : integer;

begin
  for i:=1 to 30 do writeln;
  writeln(kopf1);
  writeln(kopf2);
  writeln;
end;

begin
  anfangs_meldung;
  oeffne_default_dateien;
  if no_open_err = (not false) then
  begin
    dest_down_pointer:=511;
    dest_up_pointer:=0;
    erzeuge_ziel_datei;
    kopiere_system_datei;
    kopiere_monitor_datei;
    kopiere_alle_daten_dateien;
    close(ziel_datei);
  end;
  writeln;
  writeln(ende_text);
end.
end.
