$title('CP/M 3 System Generation')
gencpm:
do;

/*
  Copyright (C) 1982
  Digital Research
  P.O. Box 579
  Pacific Grove, CA 93950
*/

/*
    Revised:
      02 Dec 82  by Bruce Skidmore
*/

  declare true literally '0FFH';
  declare false literally '0';
  declare forever literally 'while true';
  declare boolean literally 'byte';
  declare cr literally '0dh';
  declare lf literally '0ah';

  mon1:
    procedure (func,info) external;
      declare func byte;
      declare info address;
    end mon1;

  mon2:
    procedure (func,info) byte external;
      declare func byte;
      declare info address;
    end mon2;

  relfix:
    procedure byte external;
    end relfix;

  setbuf:
    procedure external;
    end setbuf;

  getdef:
    procedure external;
    end getdef;

  crtdef:
   procedure external;
   end crtdef;

  declare reset label external;

  declare fcb (1) byte external;
  declare fcb16 (1) byte external;
  declare tbuff (1) byte external;
  declare maxb address external;
  declare bitmap (128) byte external;

  declare FCBin address public;

  declare bios$fcb (36) byte initial (
    0,'BNKBIOS3','SPR',0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);

  declare res$fcb (36) byte initial (
    0,'RESBDOS3','SPR',0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);

  declare bnk$fcb (36) byte initial (
    0,'BNKBDOS3','SPR',0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);

  declare FCBout (36) byte initial (
    0,'CPM3    ','SYS',0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);

  declare data$fcb (36) byte public initial (
    0,'GENCPM  ','DAT',0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);

  declare offset byte public;
  declare prgsiz address public;
  declare bufsiz address public;
  declare codsiz address public;
  declare bios$pg byte public;
  declare scb$pg byte public;
  declare res$pg byte public;
  declare bnk$pg byte public;
  declare bnk$off byte public;
  declare res$len byte public;
  declare non$bnk byte public;
  declare dma address public;

  declare hexASCII (16) byte public data (
    '0123456789ABCDEF');

  declare lnbfr (14) byte public initial (12);

  declare sctbfr (1) structure (
    record (128) byte) public at (.memory);

  declare fcb$msg (13) byte initial ('        .   $');

  declare query boolean public;

/*
     B D O S    P r o c e d u r e   &   F u n c t i o n    C a l l s
*/

  system$reset:
    procedure public;
      call mon1 (0,0);
    end system$reset;

  write$console:
    procedure (char) public;
      declare char byte;
      if display then
        call mon1 (2,char);
    end write$console;

  print$console$buffer:
    procedure (buffer$address) public;
      declare buffer$address address;
      if display then
        call mon1 (9,buffer$address);
    end print$console$buffer;

  read$console$buffer:
    procedure (buffer$address) public;
      declare buffer$address address;
      declare buf based buffer$address (1) byte;
      buf(1) = 0;
      if automatic then 
        do;
          if not query then
            return;
        end;
      call mon1 (10,buffer$address);
      buf(buf(1)+2) = 0;
    end read$console$buffer;

  crlf:
    procedure public;
      call write$console (cr);
      call write$console (lf);
    end crlf;

  error:
    procedure(term$code,err$type,err$msg$adr) public;
      declare (term$code,err$type) byte;
      declare err$msg$adr address;
      display = true;
      call print$console$buffer (.(cr,lf,
          'ERROR:  $'));
      call print$console$buffer (err$msg$adr);
      if err$type = 1 then
        call print$console$buffer(.fcb$msg);
      call crlf;
      if term$code then
        call system$reset;
      if automatic and not query then
        do;
          fcb(1),
          fcb16(1) = ' ';
          goto reset;
        end;
   end error;

  open$file:
    procedure (fcb$address) byte public;
      declare fcb$address address;
      declare fcb based fcb$address (1) byte;
      fcb(12),      /* ex = 0 */
      fcb(32) = 0;  /* cr = 0 */
      return mon2 (15,fcb$address);
    end open$file;

  close$file:
    procedure (fcb$address) public;
      declare fcb$address address;
      call mon1 (16,fcb$address);
    end close$file;

  delete$file:
    procedure (fcb$address) public;
      declare fcb$address address;
      call mon1 (19,fcb$address);
    end delete$file;

  read$record:
    procedure (fcb$address) public;
      declare fcb$address address;
      if mon2 (20,fcb$address) <> 0 then
      do;
        call error(true,1,.(
          'Reading file:  $'));
      end;
    end read$record;

  write$record:
    procedure (fcb$address) public;
      declare fcb$address address;
      if mon2 (21,fcb$address) <> 0 then
      do;
        call error(true,1,.(
          'Writing file:  ','$'));
      end;
    end write$record;

  create$file:
    procedure (fcb$address) public;
      declare fcb$address address;
      declare fcb based fcb$address (1) byte;
      if mon2 (22,fcb$address) = 255 then
      do;
        call error(true,0,.(
          'Directory full','$'));
      end;
      fcb(32) = 0;  /* set cr = 0 */
    end create$file;

  set$DMA$address:
    procedure (DMA$address) public;
      declare DMA$address address;
      call mon1 (26,DMA$address);
    end set$DMA$address;

  read$random$record:
    procedure (fcb$address) public;
      declare fcb$address address;
      if mon2 (33,fcb$address) <> 0 then
      do;
        call error(true,1,.(
          'Reading file: ','$'));
      end;
    end read$random$record;

  write$random$record:
    procedure (fcb$address) public;
      declare fcb$address address;
      if mon2 (34,fcb$address) <> 0 then
      do;
        call error(true,1,.(
          'Writing file:  ','$'));
      end;
    end write$random$record;

  set$random$record:
    procedure (fcb$address) public;
      declare fcb$address address;
      call mon1 (36,fcb$address);
    end set$random$record;


/*
    D a t a    S t r u c t u r e s
*/
  

  declare automatic boolean;
  declare display boolean public;

  declare nmb$sect address;

  declare link address at (.memory);

  declare bios$atts(3) address public;
  declare res$atts(3) address public;
  declare bnk$atts(3) address public;

  declare res$bios$len byte public;
  declare res$base byte public;
  declare pg$dif byte public;
  declare xmove$implemented boolean public;
  declare os$top address;

  declare system$data (256) byte;

  declare common$len byte public at (.system$data(1));
  declare banked$len byte public at (.system$data(3));
  declare sys$entry address public at (.system$data(4));

  declare prt$msg$ptr byte;

  declare dont$hash boolean;

  declare wordadr address;
  declare word based wordadr address;

  declare len byte;
  declare off address;
  declare res$flg byte;
  declare save$mem$top byte;

  declare drvtbl$adr address public;
  declare drvtbl based drvtbl$adr (16) address;

  declare dph$adr address public;
  declare dph based dph$adr structure (
    xlt         address,
    scratch1(4) address,
    scratch2    byte,
    mf          byte,
    dpb         address,
    csv         address,
    alv         address,
    dirbcb      address,
    dtabcb      address,
    hash        address,
    hbank       byte);

  declare dpb$adr address public;
  declare dpb based dpb$adr structure (
    spt         address,
    bsh         byte,
    blm         byte,
    exm         byte,
    dsm         address,
    drm         address,
    al0         byte,
    al1         byte,
    cks         address,
    off         address,
    psh         byte,
    phm         byte);

  declare bnk$swt boolean external;
  declare dbl$alv boolean external;
  declare mem$top byte external;
  declare bnk$top byte external;
  declare lerror boolean external;
  declare bdrive byte external;
  declare con$wid byte external;
  declare con$pag byte external;
  declare bck$spc boolean external;
  declare rubout boolean external;
  declare prt$msg boolean external;
  declare hash(16) boolean external;
  declare num$seg byte external;
  declare crdatf boolean external;

  declare mem$tbl (17) structure(
    base        byte,
    len         byte,
    bank        byte,
    attr        address) external;
 
  declare record(16) structure(
    size        address,
    attr        byte,
    altbnks     byte,
    no$dirrecs  byte,
    no$dtarecs  byte,
    ovlydir$dr  byte,
    ovlydta$dr  byte,
    dir$resp    byte,
    dta$resp    byte) external;

  declare quest(157) boolean external;

  declare hash$data(16) address public;
  declare hash$space address public;
  declare alloc(16) address public;
  declare alloc$space address public;
  declare chk(16) address public;
  declare chk$space address public;

/*
      L o c a l    P r o c e d u r e s
*/

  movef:
    procedure (count,source$adr,dest$adr) public;
      declare count byte;
      declare (source$adr,dest$adr) address;

      if count = 0
      then return;
      else call move (count,source$adr,dest$adr);

    end movef;

  shift$left:
    procedure (pattern, count) address public;
      declare count byte;
      declare pattern address;

      if count = 0
      then return pattern;
      else return shl(pattern,count);

   end shift$left;

  upper:
    procedure(b) byte public;
      declare b byte;
  
      if b < ' ' then return cr; /* all non-graphics */
      /* translate alpha to upper case */
      if b >= 'a' and b <= 'z' then
        b = b and 101$1111b; /* upper case */
      return b;
    end upper;

  valid$drive:
    procedure(drv) boolean public;
      declare drv byte;
      if (drv >= 0) and (drv <= 15) then
        return true;
      call error(false,0,.('Invalid drive.$'));
      return false;
    end valid$drive;

  get$response:
    procedure (val$adr) public;
      declare val$adr address;
      declare val based val$adr byte;
      call write$console ('(');
      if val = 0ffh
        then call write$console ('Y');
        else call write$console ('N');
      call print$console$buffer (.(') ? ','$'));
      call read$console$buffer (.lnbfr);
      if lnbfr(1) = 0 
        then return;  /* accept default */
      val = (upper(lnbfr(2)) = 'Y');
    end get$response;

  dsply$hex:
    procedure (val) public;
      declare val byte;
      call write$console (hexASCII(shr (val,4)));
      call write$console (hexASCII(val and 0fh));
    end dsply$hex;

  dsply$hex$adr:
    procedure (val) public;
      declare val address;
      call write$console (' ');
      call dsply$hex (high (val));
      call dsply$hex (low (val));
      call write$console ('H');
    end dsply$hex$adr;

  dsply$hex$high$adr:
    procedure (val) public;
      declare val byte;
      call dsply$hex$adr (double (val)*256);
    end dsply$hex$high$adr;

  dsply$dec$adr:
    procedure (val) public;
      declare val address;
      declare big address;
      declare (digit,i) byte;
      declare pdigit boolean;

      pdigit = false;
      digit = '0';
      big = 10000;
      if val = 0 then
        call write$console(digit);
      else
        do;
          do i = 0 to 4;
            do while val >= big;
              pdigit = true;
              digit = digit + 1;
              val = val - big;
            end;
            if pdigit then
              do;
                call write$console(digit);
                digit = '0';
              end;
            big = big / 10;
          end;
        end;
    end dsply$dec$adr;
    
  dsply$param:
    procedure (val,base) public;
      declare (val,base) byte;
      call write$console ('(');
      if base = 10 then
      do;
        call write$console ('#');
        call dsply$dec$adr(double(val));
      end;
      else
      do;
        call dsply$hex (val);
      end;
      call print$console$buffer (.(') ? ','$'));
    end dsply$param;

  get$param:
    procedure (string$adr,val$adr,pbase) public;
      declare (string$adr,val$adr) address;
      declare pbase byte;
      declare base byte;
      declare val based val$adr byte;
      declare string based string$adr (1) byte;
      declare char byte;
      declare lbindx byte;

      prompt$read:
        procedure;
          call print$console$buffer (string$adr);
          if string(0) = ' ' then
          do;
            call write$console ('(');
            call dsply$hex (val);
            do lbindx = 1 to 2;
              val$adr = val$adr + 1;
              if (lbindx=2) and (not bnk$swt) then
              do;
                val = 0;
              end;
              else
              do;
                call write$console (',');
                call dsply$hex (val);
              end;
            end;
            val$adr = val$adr - 2;
            call print$console$buffer (.(') ? ','$'));
          end;
          else
          do;
            call dsply$param (val,pbase);
          end;
          base = 16;
          lbindx = 1;
          call read$console$buffer (.lnbfr);
        end prompt$read;

      call prompt$read;
      if lnbfr(1) = 0 then
      do;
        /* accept default value */
        call crlf;
        return;
      end;
      val = 0;
      do while (char := upper(lnbfr(lbindx:=lbindx+1))) <> cr;
        if char = ',' then
        do;
          val$adr = val$adr + 1;
          val = 0;
          base = 16;
        end;
        else
        do;
          if char = '#' then
          do;
            base = 10;
          end;
          else
          do;
            char = char - '0';
            if (base = 16) and (char > 9) then
            do;
              if char > 16
                then char = char - 7;
                else char = 255;
            end;
            if char < base then
            do;
              val = val*base + char;
            end;
            else
            do;
              char,
              val = 0;
              call error (false,0,.(
                'Bad character, re-enter $'));
              call prompt$read;
              val = 0;
            end;
          end;
        end;
      end;
      call crlf;
    end get$param;

  get$seg:
    procedure(type,record$size) byte public;

      declare (type,k,seg$no) byte;
      declare (record$size,max$attr) address;
  
      if not bnk$swt then
        return 0;

      seg$no = 0ffh;
      max$attr = 0ffffh;
      do k = 1 to num$seg;
        if mem$tbl(k).attr >= record$size then
          if type = 1 then
            do;
              if (mem$tbl(k).bank = 0) and
                   (mem$tbl(k).attr < max$attr) then
                do;
                  seg$no = k;
                  max$attr = mem$tbl(k).attr;
                end;
            end;
          else
            do;
              if (mem$tbl(k).bank <> 0) and
                   (mem$tbl(k).attr < max$attr) then
                do;
                  seg$no = k;
                  max$attr = mem$tbl(k).attr;
                end;
            end;
      end;
      if (seg$no = 0ffh) and (type = 2) then
        do k = 1 to num$seg;
          if (mem$tbl(k).attr >= record$size) and
             (mem$tbl(k).bank = 0)            and
             (mem$tbl(k).attr < max$attr)     then
            do;
               seg$no = k;
               max$attr = mem$tbl(k).attr;
             end;
        end;
      return seg$no;

    end get$seg;
     
plm: 
  procedure public;

  st$ascii$hex:
    procedure(string$adr,val);
     declare string$adr address;
     declare string based string$adr (6) byte;
     declare val address;
     declare i byte;
     string(0) = ' ';
     string(1) = ' ';
     string(2) = hexASCII(shr(high(val),4));
     string(3) = hexASCII(high(val) and 0fh);
     string(4) = hexASCII(shr(low(val),4));
     string(5) = hexASCII(low(val) and 0fh);
   end st$ascii$hex;

  setup$scb:
    procedure;
      declare scb$adr address;
      declare scb$dat based scb$adr (100) byte;

      scb$adr = .memory + shl(double(scb$pg-res$pg),8) + 09ch;

      scb$dat(13h) = bdrive;
      scb$dat(1ah) = con$wid;
      scb$dat(1ch) = con$pag;
      scb$dat(2eh) = bck$spc;
      scb$dat(2fh) = rubout;
      call movef(5,.(012h,07h,0,0,0),.scb$dat(58h)); /* December 15, 1982 */
      if not lerror then
        scb$dat(57h) = scb$dat(57h) and 7fh;
      if not dbl$alv and not bnk$swt then
        scb$dat(57h) = scb$dat(57h) or 0100$0000B;
      else
        scb$dat(57h) = scb$dat(57h) and 1011$1111B;
      scb$dat(5eh) = bnk$top;
      
    end setup$scb;

  get$drvtbl$adr:
    procedure address;
      declare temp$adr address;
      declare temp2 based temp$adr address;
      declare temp3 address;

      temp$adr = .memory(43h);
      temp3 = temp2 + 1 + .memory;
      temp$adr = temp3;
      if temp2 = 0fffeh
      then res$flg = 2;
      else res$flg = 0;
      if temp2 < 0fffeh
      then return temp2 + .memory;
      else return 0ffffh;
    end get$drvtbl$adr;

  page$chop:
    procedure;
      declare i byte;

      drvtbl$adr = get$drvtbl$adr;

      dont$hash = true;
      if (drvtbl$adr <> 0ffffh) then
        do;
          do i = 0 to 15;
            if drvtbl(i) <> 0 then
              do;
                dph$adr = drvtbl(i) + .memory;
                if dph.hash <> 0ffffh then
                  dont$hash = false;
              end;
          end;
          if dont$hash and not bnk$swt then
            res$flg = 2;
          else
            res$flg = 0;
        end;

    end page$chop;

  get$xmove:
    procedure boolean;
      declare xmove$adr address;
      declare xmove$val based xmove$adr byte;

      call movef(2,.memory(58h),.xmove$adr);
      xmove$adr = xmove$adr + .memory;
      if xmove$val = 0c9h /* ret instr. */ then
        return false;
      else
        return true;
    end get$xmove;

  display$layout:
    procedure(string$adr,base,length);
      declare string$adr address;
      declare base address;
      declare length byte;

      call print$console$buffer (.(cr,lf,' ','$'));
      call print$console$buffer (string$adr);
      call write$console(' ');
      call dsply$hex$adr (base);
      call write$console(' ');
      call dsply$hex$high$adr (length);
      if prt$msg then
       do;
         call movef(12,string$adr,.system$data(prt$msg$ptr));
         prt$msg$ptr = prt$msg$ptr + 12;
         call st$ascii$hex(.system$data(prt$msg$ptr),base);
         prt$msg$ptr = prt$msg$ptr + 6;
         call st$ascii$hex(.system$data(prt$msg$ptr),
                            double(length)*256);
         prt$msg$ptr = prt$msg$ptr + 6;
         call movef(3,.(cr,lf,' '),.system$data(prt$msg$ptr));
         prt$msg$ptr = prt$msg$ptr + 3;
       end;
   end display$layout;

  reloc$module:
    procedure (fcb$adr);
      declare fcb$adr address;
      FCBin = fcb$adr;
      if relfix <> 0 then
      do;
        call error(true,1,.('Disk read error:  $'));
      end;
      call close$file(fcb$adr);
    end reloc$module;

  load:
    procedure (fcb$adr,atts$adr);
      declare fcb$adr address;
      declare atts$adr address;
      declare atts based atts$adr (3) address;
      declare (i,rdcnt) byte;

      prgsiz = atts(0);
      bufsiz = atts(1);
      codsiz = atts(2);
      call movef(8,fcb$adr+1,.fcb$msg);
      call movef(3,fcb$adr+9,.fcb$msg+9);
      if shr(prgsiz+255,7) > nmb$sect then
      do;
        call error(true,1,.('File cannot fit into GENCPM buffer:  ','$'));
      end;
      rdcnt = low(shr(prgsiz-1,7)) + 1;
      i = 0;
      do while (i < rdcnt);
        call set$dma$address(dma:=.sctbfr(i));
        call read$record(fcb$adr);
        i = i + 1;
      end;
      call movef(128,dma,.bitmap);  /* copy the last sector read, into */
                                    /* the bitmap buffer, relocation   */
                                    /* info might be that last sector  */
      dma = prgsiz + .memory;
  end load;

  wrtbuf:
    procedure (wrtlen,wrtoff$adr);
      declare (i,wrtlen,wrtcnt) byte;
      declare wrtoff$adr address;
      declare wrtoff based wrtoff$adr address;
      if wrtlen <> 0 then
        do;
           call movef(8,.FCBout+1,.fcb$msg);
           call movef(3,.FCBout+9,.fcb$msg+9);
           FCBout(33) = low(wrtoff);
           FCBout(34) = high(wrtoff);
           call write$random$record(.FCBout);
           dma = dma + low(256 - low(dma - .memory));
           wrtcnt = wrtlen * 2 - 1;
           do i = 0 to wrtcnt;
             call set$dma$address(dma:=dma-80h);
             call write$record(.FCBout);
           end;
           call set$random$record(.FCBout);
           call movef(2,.FCBout(33),wrtoff$adr);
        end;
    end wrtbuf;

  get$file$info:
    procedure;
      declare fcb$adr address;
      declare atts$adr address; 
      declare file$atts based atts$adr(3) address;
      declare header$record structure (
        fill1 byte,  
        psize address,
        fill2 byte,
        dsize address,
        fill3 (4) byte,
        csize address,
        fill4 (116) byte) at (.memory);


    get$atts:
      procedure;
        call movef(8,fcb$adr+1,.fcb$msg);
        call movef(3,fcb$adr+9,.fcb$msg+9);
        if open$file(fcb$adr) = 0ffh
        then call error(true,1,.('Unable to open:  $'));
        call set$dma$address(.header$record);
        call read$record(fcb$adr);
        file$atts(0) = header$record.psize;
        file$atts(1) = header$record.dsize;
        file$atts(2) = header$record.csize;
        call read$record(fcb$adr);
      end get$atts;

    if not bnk$swt then
      do;
        call movef(8,.('BDOS3   '),.res$fcb+1);
        call movef(8,.('BIOS3   '),.bios$fcb+1);
      end;
    else 
      do;
         fcb$adr = .bnk$fcb;
         atts$adr = .bnk$atts;
         call get$atts;
      end;
    fcb$adr = .bios$fcb;
    atts$adr = .bios$atts;
    call get$atts;
    fcb$adr = .res$fcb;
    atts$adr = .res$atts;
    call get$atts;
  end get$file$info;

  need$tbl:
    procedure byte;
     declare (all$some,i) byte;

     all$some = false;

     if drvtbl$adr = 0ffffh
     then return false;
     else 
       do i = 0 to 15;
         if drvtbl(i) <> 0 then
           do;
              dph$adr = drvtbl(i) + .memory;
              /* zero the reserved bytes in the DPH */
              call movef(9,.(0,0,0,0,0,0,0,0,0),dph$adr+2);
              if (dph.dirbcb = 0fffeh) or (dph.dtabcb = 0fffeh) or
                 (dph.hash = 0fffeh) or (dph.alv = 0fffeh) or
                 (dph.csv = 0fffeh)
              then all$some = true;
           end;
       end;
     return all$some;

  end need$tbl;

  setup$hash:
    procedure;
      declare (i,j,printed,seg$no,seg0$no,h$bank,hohash) byte;
      declare (size,h$attr,max$attr,max0$attr) address;
      declare nohash boolean;

      printed = false;
      nohash = true;

      do i = 0 to 15;
        dph$adr = drvtbl(i) + .memory;
        if drvtbl(i) <> 0 then
          do;
            if dph.hash < 0fffeh then
              nohash = false;
            if dph.hash = 0fffeh then
              do;
                if not printed then
                  do;
                    printed = true;
                    call print$console$buffer(.
                       (lf,cr,'Setting up directory hash tables:',
                        lf,cr,'$'));
                  end;
                query = quest(27 + i);
                dpb$adr = dph.dpb + .memory;
                size = shl(dpb.drm+1,2);
                call print$console$buffer(.
                     (' Enable hashing for drive $'));
                call write$console('A'+i);
                call print$console$buffer(.(': $'));
                call get$response(.hash(i));
                call crlf;
                if not hash(i) then
                  do;
                    dph.hash = 0ffffh;
                  end;
                else
                  if not bnk$swt then
                    do;
                      nohash = false;
                      hash$data(i) = size;
                      hash$space = hash$space + size;
                    end;
                  else
                    do;
                      if (seg$no := get$seg(2,size)) = 0ffh then
                        call error(false,0,.(
                              'Unable to allocate space for hash table.$'));
                      else
                        do;
                          dph.hbank = mem$tbl(seg$no).bank;
                          dph.hash = shl(double(mem$tbl(seg$no).base),8) +
                                     (shl(double(mem$tbl(seg$no).len),8) -
                                      mem$tbl(seg$no).attr);
                          mem$tbl(seg$no).attr = mem$tbl(seg$no).attr - size;
                        end;
                    end; 
              end;
          end;
      end;
      if (not bnk$swt) and (nohash) then
       do;
         res$flg = 2;
         scb$pg = scb$pg + 2;
         res$pg = res$pg + 2;
       end;
    end setup$hash;

  get$alloc$chk:
    procedure;
      declare (i,dbl$alloc) byte;
      declare printed boolean;

      do i = 0 to 15;
        alloc(i) = 0;
        chk(i) = 0;
      end;

      if not dbl$alv and not bnk$swt then
        dbl$alloc = 1;
      else
        dbl$alloc = 2;

      alloc$space = 0;
      chk$space = 0;
      printed = false;
      do i = 0 to 15;
        if drvtbl(i) <> 0 then
          do;
            dph$adr = drvtbl(i) + .memory;
            dpb$adr = dph.dpb + .memory;
            if dph.alv = 0fffeh then
              do;
                call print$console$buffer(.(cr,lf,
                     'Setting up Allocation vector for drive $'));
                call write$console('A'+i);
                call write$console(':');
                printed = true;
                alloc(i) = (dpb.dsm/8 + 1) * dbl$alloc;
                alloc$space = alloc$space + alloc(i);
              end;
            if dph.csv = 0fffeh then
              do;
                call print$console$buffer(.(cr,lf,
                     'Setting up Checksum vector for drive $'));
                call write$console('A'+i);
                call write$console(':');
                printed = true;
                chk(i) = (dpb.drm + 4)/4;
                chk$space = chk$space + chk(i);
                dpb.cks = (dpb.cks and 8000h) or chk(i);
              end;
          end;
      end;
      if printed then
        call crlf;
    end get$alloc$chk;

  setup$mem$seg$tbl:
    procedure;
      declare (i,j,ok,accept,mlow,mhigh,tlow,thigh) byte;
      declare mem$temp address;

      /* Create first memory segment table entry */
      mem$tbl(0).base = bnk$pg;
      mem$tbl(0).len = bnk$top - bnk$pg;
      mem$tbl(0).attr = 0;
      mem$tbl(0).bank = 0;

      accept = false;

      call print$console$buffer(
                 .(lf,cr,
                  '*** Bank 1 and Common are not included ***',
                   lf,cr,
                  '*** in the memory segment table.       ***',
                   lf,cr,lf,cr,'$'));
      query = quest(10);
      call get$param (.('Number of memory segments $'),
                      .num$seg,10);
      call print$console$buffer(.(cr,lf,
              'CP/M 3 Base,size,bank ($'));
      call dsply$hex(mem$tbl(0).base);
      call write$console(',');
      call dsply$hex(mem$tbl(0).len);
      call write$console(',');
      call dsply$hex(mem$tbl(0).bank);
      call print$console$buffer(.(')',lf,cr,'$'));

      do while not accept;  
        /* Bank switched memory segment table input */
        call print$console$buffer (.(cr,lf,
              'Enter memory segment table:',lf,cr,'$'));
        do j = 1 to num$seg;
          ok = false;
          do while not ok;
            query = quest(11 + j - 1);
            call get$param (.(' Base,size,bank ','$'),
                            .mem$tbl(j),16);
            mem$tbl(j).attr = shl(double(mem$tbl(j).len),8);
            if mem$tbl(j).len = 0 then
              do;
                 call error(false,0,.(
                      'Zero length segment not allowed.$'));
              end;
            else
              if mem$tbl(j).bank = 1 then
                do;
                  call error(false,0,.(
                       'Bank one not allowed.$'));
                end;
              else
                do;
                  tlow = mem$tbl(j).base;
                  mem$temp = double(tlow) + double(mem$tbl(j).len);
                  if (high(mem$temp) <> 0) or (low(mem$temp) > bnk$top) then
                    do;
                       call print$console$buffer(.(cr,lf,'ERROR:  ',
                               'Memory conflict - segment trimmed.',
                                cr,lf,'$'));
                       mem$tbl(j).len = bnk$top - tlow;
                       mem$tbl(j).attr = shl(double(bnk$top - tlow),8);
                    end;
                  else
                    do;
                      thigh = low(mem$temp);
                      i = 0;
                      ok = true;
                      do while ((i < j) and ok);
                        mlow = mem$tbl(i).base;
                        mhigh = mlow + mem$tbl(i).len;
                        if mem$tbl(i).bank = mem$tbl(j).bank then
                          do;
                            if (mhigh >= thigh) and (tlow >= mlow) then
                              do;
                                call error(false,0,.(
                                     'Memory conflict - cannot trim segment.$'));
                                ok = false;
                              end;
                            else
                              if ((thigh > mhigh) and (mhigh > tlow)) then
                                do;
                                  call print$console$buffer(.(cr,lf,'ERROR:  ',
                                       'Memory conflict - segment trimmed.',
                                        cr,lf,'$'));
                                  mem$tbl(j).base = mhigh;
                                  ok = false;
                                end;
                              else
                                if ((thigh > mlow) and (mlow > tlow)) then
                                  do;
                                     call print$console$buffer(.(cr,lf,'ERROR:  ',
                                         'Memory conflict - segment trimmed.',
                                          cr,lf,'$'));
                                     mem$tbl(j).len = mlow - tlow;
                                     mem$tbl(j).attr = shl(double(mlow-tlow),8);
                                     ok = false;
                                  end;
                          end;
                          i = i + 1;
                      end;
                    end;
                end;
          end;
        end;
        call crlf;
        do j = 0 to num$seg;
          if j = 0 then
            call print$console$buffer   (.(' CP/M 3 Sys   ','$'));
          else
            do;
              call print$console$buffer (.(' Memseg No. ','$'));
              call dsply$hex(j-1);
            end;
          call dsply$hex$high$adr (mem$tbl(j).base);
          call dsply$hex$high$adr (mem$tbl(j).len);
          if bnk$swt then
            do;
              call print$console$buffer (.('  Bank ','$'));
              call dsply$hex (mem$tbl(j).bank);
            end;
          call crlf;
        end;
        query = false;
        accept = true;
        call print$console$buffer (.(cr,lf,
          'Accept new memory segment table entries ','$'));
        call get$response (.accept);
      end; /* do while not accept */
      call crlf;
    end setup$mem$seg$tbl;

  get$default$file:
    procedure;
      declare ret byte;

      call print$console$buffer(.(
        'Default entries are shown in (parens).',cr,lf,
        'Default base is Hex, precede entry with # for decimal',
        cr,lf,'$'));
      if (ret:=open$file(.data$fcb)) <> 255 then
        do;
          call movef(8,.data$fcb+1,.fcb$msg);
          call movef(3,.data$fcb+9,.fcb$msg+9);
          call print$console$buffer(.(
            cr,lf,'Use GENCPM.DAT for defaults $'));
          ret = 0ffh;
          call get$response(.ret);
          call crlf;
          if ret then
            call getdef;
          call close$file(.data$fcb);
        end;
      else
        do;
          display = true;
          automatic = false;
        end;

    end get$default$file;

  setup$system$dat:
    procedure;
      declare (i,j,ok,temp) byte;
      ok = false;
      call get$default$file;
      do while not ok;
        query = quest(155);
        call crlf;
        call print$console$buffer(.('Create a new GENCPM.DAT file $'));
        call get$response(.crdatf);
        query = quest(0);
        call crlf;
        call crlf;
        call print$console$buffer(.('Display Load Map at Cold Boot $'));
        call get$response(.prt$msg);
        call crlf;
        call crlf;
        query = quest(1);
        con$wid = con$wid + 1;
        call get$param (.('Number of console columns $'),
                        .con$wid,10);
        con$wid = con$wid - 1;
        query = quest(2);
        con$pag = con$pag + 1;
        call get$param (.('Number of lines in console page $'),
                        .con$pag,10);
        con$pag = con$pag - 1;
        query = quest(3);
        call print$console$buffer(.
                   ('Backspace echoes erased character $'));
        call get$response (.bck$spc);
        call crlf;
        query = quest(4);
        call print$console$buffer(.
                   ('Rubout echoes erased character $'));
        call get$response (.rubout);
        call crlf;
        call crlf;
        query = quest(5);
   err1:
        call print$console$buffer(.('Initial default drive ($'));
        call write$console('A'+bdrive);
        call print$console$buffer(.(':) ? $'));
        call read$console$buffer(.lnbfr);
        if lnbfr(1) <> 0 then
          do;
            temp = upper(lnbfr(2))-'A';
            if not valid$drive(temp) then
              goto err1;
            bdrive = temp;
          end;
        call crlf;
        call crlf;
        query = quest(6);
        call get$param (.('Top page of memory $'),
                        .mem$top,16);
        os$top = shl(double(mem$top),8) + 100h;
        query = quest(7);
        call print$console$buffer(.('Bank switched memory $'));
        call get$response (.bnk$swt);
        call crlf;
        non$bnk = not bnk$swt;
        if bnk$swt then
          do;
            query = quest(8);
            call get$param (.('Common memory base page $'),
                            .bnk$top,16);
            call crlf;
            query = quest(9);
            call print$console$buffer(.('Long error messages $'));
            call get$response(.lerror);
            call crlf;
          end;            
        else
          do;
            query = quest(156);
            call crlf;
            call print$console$buffer(.('Double allocation vectors $'));
            call get$response(.dbl$alv);
            call crlf;
            bnk$top = 0;
          end;
        query = false;
        ok = true;
        call crlf;
        call print$console$buffer(.('Accept new system definition $'));
        call get$response(.ok);
        call crlf;
      end;
      save$mem$top = mem$top;
      mem$top = mem$top + 1;
      rubout = not rubout;
    end setup$system$dat;

  setup$CPM80$sys:
    procedure;
    declare i byte;
      call print$console$buffer (.(  cr,lf,lf,
        'CP/M 3.0 System Generation',cr,lf,
        'Copyright (C) 1982, Digital Research',
        cr,lf,cr,lf,'$'));
      call delete$file (.fcbout);
      call create$file (.fcbout);
      FCBout(32) = 0;
      do i = 0 to 127;
        system$data(i) = 0;
      end;
      do i = 128 to 255;
        system$data(i) = '$';
      end;
      prt$msg$ptr = 128;
      call movef(3,.(cr,lf,' '),.system$data(prt$msg$ptr));
      prt$msg$ptr = 131;
      call movef(8,.FCBout+1,.fcb$msg);
      call movef(3,.FCBout+9,.fcb$msg+9);
      call set$DMA$address (.sctbfr);
      call write$record (.FCBout);
      call write$record (.FCBout);
    end setup$CPM80$sys;

  initialization:
    procedure;
      declare i byte;

      nmb$sect = shr ((maxb-.sctbfr+1),7);

      do i = 0 to 15;
        hash$data(i) = 0;
      end;
      hash$space = 0;

      if fcb(1) = 'A' then
        do;
          automatic = true;
          display = false;
          do i = 0 to 154;
            quest(i) = false;
          end;
        end;
      else
        do;
         automatic = false;
         display = true;
        end;
      if fcb16(1) = 'D' then
        do;
          display = true;
        end;
      query = false;

    end initialization;


/*
     G E N C P M    M a i n    P r o g r a m
*/

  res$flg = 0;
  display = true;
  call setup$CPM80$sys;
  call initialization;
  call setup$system$dat;

  call get$file$info;

  if bios$atts(2) <> 0
  then res$bios$len = high(bios$atts(2) + 255);
  else res$bios$len = high(bios$atts(0) + 255);
  bios$pg = mem$top - res$bios$len;
  bnk$off = bnk$top - (high(bios$atts(0) + 255) - res$bios$len);
  bnk$pg = bnk$off - high(bnk$atts(0) + 255);

  call load(.bios$fcb,.bios$atts);

  call page$chop;


  if not bnk$swt then
    do;
      scb$pg = bios$pg - (3 - res$flg);
      res$pg = bios$pg - high(res$atts(0) + 255) + res$flg;
    end;
  else
    do;
      scb$pg = bios$pg - 1;
      res$pg = bios$pg - high(res$atts(0) + 255);
    end;

  if need$tbl then
    do;
      call get$alloc$chk;
      if bnk$swt then
        do;
          bnk$off = bnk$top - (high(bios$atts(0) + alloc$space +
                               chk$space + 255) - res$bios$len);
          bnk$pg = bnk$off - high(bnk$atts(0) + 255);
          xmove$implemented = get$xmove;
          call setup$mem$seg$tbl;
          if (not xmove$implemented) then
            do len = 0 to 15;
              record(len).altbnks = false;
            end;
        end;
      else
         xmove$implemented = false;
      if not dont$hash then
        call setup$hash;
      call setbuf;
    end;

  res$len = res$bios$len;

  offset = bios$pg;
  call reloc$module(.bios$fcb);

  if bnk$swt
  then call display$layout(.('BNKBIOS3 SPR$'),
                           double(bios$pg)*256,res$bios$len);
  else call display$layout(.('BIOS3    SPR$'),
                           double(bios$pg)*256,res$bios$len);
  if not bnk$swt then
    do;
       len = res$bios$len;
       off = 2;
       call wrtbuf(len,.off);
       common$len = len;
       banked$len = 0;
    end;
  else
    do;
       len = high(bios$atts(0) + 255) - res$bios$len;
       off = (high(res$atts(0) + 255) + res$bios$len) * 2 + 2;
       call display$layout(.('BNKBIOS3 SPR$'),double(bnk$off)*256,len);
       call wrtbuf(len,.off);
       banked$len = len;
       len = res$bios$len;
       off = 2;
       dma = dma - 80h;
       call wrtbuf(len,.off);
       common$len = len;
    end;

  res$len = high(res$atts(0) + 255) - res$flg;
  offset = res$pg;
  call load(.res$fcb,.res$atts);
  call reloc$module(.res$fcb);
  call setup$scb;
  dma = dma - (res$flg * 256);
  len = high(res$atts(0) + 255) - res$flg;
  if not bnk$swt
  then call display$layout(.('BDOS3    SPR$'),double(res$pg)*256,len);
  else call display$layout(.('RESBDOS3 SPR$'),double(res$pg)*256,len);
  call wrtbuf(len,.off);
  common$len = common$len + len;

  if bnk$swt then
  do;
     res$len = 0ffh;
     offset = bnk$pg;
     call load(.bnk$fcb,.bnk$atts);
     call reloc$module(.bnk$fcb);
     len = high(bnk$atts(0) + 255);
     off = off + (high(bios$atts(0) + 255) - res$bios$len) * 2;
     call display$layout(.('BNKBDOS3 SPR$'),double(bnk$pg)*256,len);
     call wrtbuf(len,.off);
     banked$len = banked$len + len;
  end;
  if not prt$msg then prt$msg$ptr = prt$msg$ptr - 3;
  call movef(12,.(lf,cr,' 64K TPA',lf,cr),.system$data(prt$msg$ptr));
  res$pg = shr(res$pg,2);
  system$data(prt$msg$ptr+3) = res$pg/10 + '0';
  system$data(prt$msg$ptr+4) = res$pg mod 10 + '0';
  prt$msg$ptr = prt$msg$ptr + 12;
  sys$entry = bios$pg * 256;
  call movef(36,.('Copyright (C) 1982, Digital Research'),.system$data(10h));
  call movef(6,.memory,.system$data(35h));  /* Copy Serial No. into header */
  FCBout(33) = 0; FCBout(34) = 0; FCBout(35) = 0;
  system$data(0) = mem$top;
  system$data(2) = bnk$top;
  call movef(8,.FCBout+1,.fcb$msg);
  call movef(3,.FCBout+9,.fcb$msg+9);
  call set$DMA$address(.system$data);
  call write$random$record(.FCBout);
  FCBout(33) = 1;
  call set$DMA$address(.system$data(128));
  call write$random$record(.FCBout);
  call close$file(.fcbout);

  if crdatf then
    do;                        /* create a new data file for GENCPM */
      crdatf = false;
      mem$top = save$mem$top;
      rubout = not rubout;
      call movef(8,.data$fcb+1,.fcb$msg);
      call movef(3,.data$fcb+9,.fcb$msg+9);
      call crtdef;
    end;

  display = true;
  call print$console$buffer (.(cr,lf,lf,
    '*** CP/M 3.0 SYSTEM GENERATION DONE ***','$'));
  return;
  
 end plm;
end gencpm;
