home *** CD-ROM | disk | FTP | other *** search
/ ProfitPress Mega CDROM2 …eeware (MSDOS)(1992)(Eng) / ProfitPress-MegaCDROM2.B6I / PROG / ASSEMBLY / V20BOOT.ZIP / V20BOOT.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1990-04-30  |  47.3 KB  |  1,586 lines

  1.  
  2.  
  3. (* Copyright 1990 by Clarence Wilkerson. All rights reserved *)
  4.  
  5.  
  6. (* This emulator has reached the endpoint of its evolution, and
  7.    I've decided to make it available to the public in source
  8.    code form. There are other shareware or public domain emulators,
  9.    but none that I know of with source code. One interesting
  10.    feature is that it is almost entirely in Turbo Pascal.
  11.      I have not included because of its length the CCP
  12.    assembly language code. I had at one time plans to emulate
  13.    this in Turbo Pascal. 
  14. *)
  15.  
  16. { Things left to do
  17.     1) IOBYTE                     ok
  18.     2) ENHANCED CCP   by offloading functions to turbo nope
  19.     3) TERMINAL EMULATION         ok  H19, check nansi code
  20.     4) SOFTWARE EMULATION for Z80 nope
  21.        and support for z80 boards nope
  22.     5) Emulate the FCB's better   ok
  23.     6) bootup configuration file  ok
  24.     7) allow cp/m 3.0 emulation   nope
  25. }
  26.  
  27. program v20boot;
  28. uses crt,dos,printer;
  29.   (*
  30.    This is an CP/M 2.2 emulation program using the NEC
  31.     v20 8080 emulation mode.
  32.  
  33.     Turbo Pascal 4.0-5.5 revised edition . The major design aim was to
  34.     do this almost exclusively in Turbo Pascal rather than assembler.
  35.  
  36. Structure of the source:
  37.  
  38.   1) V20BOOT.PAS  TP 4.0-5.5 pascal source with 99.9% of 8088 code
  39.   2) GOTOCPM.ASM  MASM source file for routine to enter V20 8080 emulation
  40.      mode.
  41.   3) V20BDOS.ASM  ASM80 source for stub BDOS and BIOS in 8080 code
  42.   3) Not included because of size  V20CCP.ASM  minor changes to ZCPR v.2
  43.      , stripping user code and submit cpability
  44.  
  45. Binary files included:
  46.   1) V20BOOT.EXE  complete compiled version
  47.   2) MEDLEY.ARC   set of public domain CP/M programs.. LASM, MLOAD
  48.      DISK76, SD useful for rebuilding and testing
  49.   3) V20BDOS.OBJ  linkable form of 8080 code V20bdos
  50.   3) V20CCP.OBJ   " ""                       V20CCP
  51.   4) GOTOCPM.OBJ  "  "  of help routine, gotocpm used as external routine
  52.      in Pascal source.
  53.  
  54. Description:
  55. General Theory:
  56.  
  57.    The V20-30 series contains an 8080 emulation mode. The mode is entered
  58.    by executing a "BRKEM XXX", where XXX is a software interrupt between
  59.    0 and $ff. Many of these interrupts on a pc are reserved for other
  60.    functions. You may have to poke around on your fully configured system
  61.    to find a set of 4 consecutive interrupts to be used by this program.
  62.    I use $C0-> C4 as the default. This is changeable in the INITV20.CPM
  63.    file.
  64.  
  65.       The interrupt points to a segment:offset, and the 8080 code begins
  66.    executing at that point. The registers for the emulation have the
  67.    obvious matches to the 8088 registers, except that 8080 sp = 8086 bp.
  68.    Also, you must set the 8080 data segment in ds before you enter the
  69.    BRKEM.
  70.  
  71.        The 8080 emulation mode has calls to execute 8088 code, via
  72.    interrupt calls, and to return from emulation mode permanently.
  73.    I have implemented most of the BDOS-BIOS emulation in Turbo Pascal, with
  74.    access from the 8080 code via these interrupt calls.
  75.  
  76.  
  77.    In short, this programs allocates a 8080 code segment, sets up bdos and
  78.    bios stubs in it, and then enters emulation mode. The CCP used is a
  79.    slightly modified ZCPR, so the look and feel is aprroximately that
  80.    of a stock CP/M 2.2 system.
  81.  
  82.    The program goes to some trouble to emulate CP/M bdos calls exactly, as
  83.    well as the console - printer oriented bios calls. One can write a
  84.    fairly short emulator if the exactitude of bdos calls is ignored, and 
  85.   it might suffice for some purposes. 
  86.        However, there is a difference in the File
  87.    control blocks for CP/M versus MSDOS, and several CP/M calls have no
  88.    exact MSDOS analogue. For example, few of the first generation emulators
  89.    will run the directory programs SD or DISK76. Often, however, assemblers
  90.    and compilers will run under the direct translation method. On the other
  91.    hand, this program will not run the WordStar Install program correctly,
  92.    since it apparently tries to implement random disk access by manipulating
  93.    reserved fields in the FCB.
  94.  
  95.    All BDOS functions are supported, including getalloc and get DPB,
  96.    ( this was important for using SD and DISK76 ).
  97.    Not many of the bios functions are supported, espcially the disk oriented
  98.    ones. The main thing this  loses for you is the direct ability to use
  99.    CP/M diskettes, and to use DU, the disk zapper. I made up for the loss
  100.    of CP/M disks with my READCPM utility, also written in Turbo Pascal.
  101.    The DU loss is tougher --DU is very handy and semi-programmable.
  102.    I find Norton's Utilities as a poor replacement in some aspects.
  103.  
  104.        It wouldn't take that much effort to merge my READCPM and this code,
  105.    but that's another project.
  106.  
  107.    One limitation is that this  V20 emulation mode does not run z-80 code.
  108.    However, this Turbo program is written  so that only a few changes
  109.    would be required to support a memory mapped Z80 card or software 8080
  110.    or z80 emulation. Joan Riff's Z80MU  exists already, however.
  111.  
  112.    CP/M features missing stem from my hacking which  eliminated from ZCPR
  113.    CCP user numbers and support for SUBMIT.
  114.    To make up for this lack of support for SUBMIT, the standard console
  115.    functions from bdos and bios are left in place so that cp/m utilities
  116.    such as EX can be used.
  117.  
  118.    Furthermore, the IOBYTE feature is implemented, and one can add new
  119.    physical devices to supplement the ones included ( raw io and H19
  120.    emulation ).
  121.  
  122. *)
  123.  
  124. const
  125.   ESC = #27;
  126.   CARRYFLAG = $01;
  127.   WHICHCOMM     : byte = $01;   { USE COMM1 }
  128.   DEFAULTIOBYTE : byte = $81;  { CRTT, LPTT }
  129.   BDOSSIZE :word = $600; {12 cp/m sectors} { size of disk file in cp/m sectors }
  130.   CCPSIZE  :word = $800; {16 cp/m sectors}
  131.   NUMALLOCBYTES  = $100;  {bytes in 8080 bdos for bit map of allocation }
  132.   escinflag: boolean=false;
  133.   escoutflag: boolean=false;
  134.   escstr: string[64] ='';
  135.  
  136.   { 8088 interrupts used }
  137.   { should make this configurable }
  138.  
  139.   baseinterrupt : byte = $c0;
  140.   coldb      = $0;     { plus baseinterrupt starting point }
  141.   ccphandle  = $1;
  142.   bdoshandle = $2;
  143.   bioshandle = $3;   { these handles are indices into table of addresses }
  144.  
  145.  
  146.  { special V-20 instructions }
  147.  
  148.   CALLN =   $eded;  { used from 8080 mode not reversed byte order  }
  149.   RETEM =   $edfd;  { used from 8080 mode stored in mem as ed,fd }
  150.   BKREM =   $0fff;  { used from 8086 mode }
  151.   RETI  =   $CF;    { used from 8086 mode }
  152.  
  153.  
  154. { cpm-80 constants }
  155.  
  156. { i/o byte masks }
  157.    consolemask = $03;
  158.    consoleshift= $0;
  159.    readermask  = $0c;
  160.    readershift = $2;
  161.    punchmask   = $30;
  162.    punchshift  = $4;
  163.    listmask    = $c0;
  164.    listshift   = $6;
  165.  
  166. { possible console values  SHR 0 }
  167.  
  168.    TTY     =  0;
  169.    CRTT    =  1;  (* CRT conflict with DOS unit in TP 4.0 *)
  170.    BAT     =  2;
  171.    UC1     =  3;
  172.  
  173. { possible reader values SHR 2 }
  174. {  TTY     =  0; }
  175.    RDR0    =  1;
  176.    UR1     =  2;
  177.    UR2     =  3;
  178.  
  179. { possible punch values  SHR 4 }
  180. {  TTY     =  0 }
  181.    PUN     =  1;
  182.    UP1     =  2;
  183.    UP2     =  3;
  184.  
  185. { possible list values  SHR 6 }
  186. {  TTY     =  0; }
  187. {  CRT     =  1; }
  188.    LPTT    =  2;
  189.    UL1     =  3;     { output to a file }
  190.  
  191.  
  192. { offsets in CPM-80 code segment }
  193.  
  194.    ccpaddr : word =  $f200;
  195.    bdosaddr :word =  $fa00;
  196.    biosaddr :word =  $ff00;
  197.  
  198.    bdosdpb  =  $9;  {  + bdosaddr }
  199.    bdosalloc=  $19; {  + bdosaddr }
  200.    jmp     =   $c3;
  201.    jmpwarm =   $0;
  202.    jmpbdos =   $5;
  203.    kfcb1   =   $80;
  204.    kfcb2   =   $5c;
  205.    cpmstack   =   $ff80;  (* default value *)
  206.    cpmoutname: string[64] ='V20OUT.LST';   { list output to a file }
  207.    cpm80     : word = 0;
  208.  
  209. type
  210.   segment = array[0..$7ff0] of word;
  211.   filler  = array[0..$1000] of word;
  212.  
  213.   reg8080 = record case integer of
  214.               1:  (a,x,l,h,c,b,e,d  : byte); { 8080 mode }
  215.               2:  (achr,u,lchr,hchr,cchr,bchr,echr,dchr: char);
  216.               3:  (psw,hl,bc,de : word);  { word mode }
  217.             end;
  218.   reg8080ptr = ^reg8080;
  219.   interruptrec  = record
  220.                    loc     : byte;
  221.                    offset  : word;
  222.                    segment : word;
  223.             end;
  224.  
  225.   regpakptr =^registers;
  226. { cp/m and dos types }
  227.     fcbname = array[0..11] of char;
  228.     nstring = string[64];
  229.     anystring = string[255];
  230.  
  231.     dpbblk = record case integer of
  232.         1: (spt : word;
  233.             bsh : byte;
  234.             blm : byte;
  235.             exm : byte;
  236.             dsm : word;
  237.             drm : word;
  238.             al01 : word;
  239.             cks  : word;
  240.             off  : word;
  241.             );
  242.         2: (dpb : array[1..15] of byte);
  243.      end;
  244.  
  245.     allocblk = record case integer of
  246.                  1: ( allshort: array[1..16] of byte);
  247.                  2: ( alllong: array[1..8] of word);
  248.                 end;
  249.  
  250.     cpmfcb = record case integer of
  251.  
  252.         1: (  drive: byte;
  253.               name : array[1..11] of char; { 1..11 }
  254.               curext : byte;  { 12 }
  255.               rs12   : word;     { 13..14 }
  256.               recused : byte;       { 15 }
  257.               ablk : allocblk;      { 15..31 }
  258.               currec : byte;        { 32 }
  259.               randrec: word;     { 33..34 }
  260.               overflow: byte        { 35 }      );
  261.        2:  (fcb : array[0..35] of byte);
  262.       end;
  263.  
  264.      dosfcb =   record case integer of
  265.         1: (  drive: byte; { 0 for default before opening, then A=1,..etc }
  266.               name : array[1..11] of char; { 8 + 3, spaces to fill both pieces}
  267.               curext :  word;
  268.               recsize : word;  { set to 80 hex on opening }
  269.               filesize: array[1..4] of byte;
  270.               date : word;
  271.               time : word;
  272.               reserved : array[24..31] of byte;
  273.               currec : byte;
  274.               randrec: array[1..2] of word);
  275.               { user should set to 0 normally }
  276.         2:  (fcb : array[0..36] of byte);
  277.         3:  ( cpmseqfcb: array[0..32] of byte;
  278.               fillit : array[33..36] of byte) { for sequential records };
  279.         4:  (cpmrandfcb: array[0..35] of byte;
  280.              myextra : byte  { for random access });
  281.       end;
  282.  
  283.   dosdir = record   case integer of
  284.          1: (  dskflg : byte;
  285.                name   : array[1..8] of char;
  286.                ext    : array[1..3] of char;
  287.                attr  :  byte;
  288.                rsvrd :  array[1..10] of byte;
  289.                time  :  word;
  290.                date  :  word;
  291.                strtcluster : word;
  292.                size  :  array[1..4] of byte);
  293.         2:  ( cpmdir :  array[0..31] of byte);
  294.       end;
  295.  
  296.       fname = array[1..11] of char;
  297.       alloc = array[0.. NUMALLOCBYTES] of byte;
  298.  
  299.  
  300.  
  301.     { replace absolute variables in the 8080 address space by pointers }
  302. var    myseg : word;
  303.        mystack : word;
  304.        myinterrupt : word;
  305.        oldinterrupt  : array[0..3] of interruptrec;
  306.        newinterrupt  : array[0..3] of interruptrec;
  307.        cursX, cursY : byte; { h-19 emulation }
  308.        bioschar  : ^char;
  309.        bioswhich,biosbyte : ^byte;
  310.        cpmseg    : ^segment;
  311.        extra     : ^filler;
  312.        biosreg   : reg8080ptr ;
  313.        bdosreg   : reg8080ptr ;
  314.        ccpregs   : reg8080ptr ;
  315.        user      : ^byte ;    { cpm80:4;}
  316.        iobyte    : ^byte ;    { cpm80:3;}
  317.        abortflag : ^byte ;    { cpm80:$ffff;}  { put <> 0 to abort }
  318.        zero      : ^byte ;    { cpm80:0; }
  319.  
  320.        { misc. variables }
  321.  
  322.        lastdma  :  word;
  323.        ccpfile  :  file;
  324.        bdosfile :  file;
  325.        cpmout   :  text; { for output to disk file from list }
  326.        initf    :  text; { set cp/m parameters }
  327.        z        : registers;
  328.        myownfcb : dosfcb;
  329.        listouthandle : word;
  330.  
  331. procedure v20ccp ; external ;
  332. {$L v20ccp.obj}
  333. { 8080 code stored in "v20ccp.obj" }
  334. { contains 8080 code ccp extracted from zcpr 2.0 }
  335. { with user numbers removed, and SUBMIT file removed }
  336. { Fancier versions could pass most functions off to Turbo routines }
  337. { such as assigning  MSDOS paths for user numbers or drives }
  338. { changing directories }
  339.  
  340. procedure v20bdos ; external ;
  341. {$L v20bdos.obj}
  342. { 8080 code stored in "v20bdos.obj" }
  343. { has cp/m 2.2 console handlers, passes rest off to routines here to }
  344. { imitate with msdos functions}
  345. { Bios is only a stub, handles character routines only, others when }
  346. { graceful imitation is possible}
  347. { fancier version could add cp/m disk imitation for a floppy}
  348. { but not necessary given my read cp/m }
  349.  
  350.  
  351.  
  352. function getcurrent : byte;
  353. var u : registers;
  354. begin
  355.    u.ah:=$19;
  356.    msdos(u);
  357.    getcurrent:=u.al and $0f;
  358. end; { getcurrent }
  359.  
  360. procedure resetdisk;
  361. var u : registers;
  362. begin
  363.    u.ah:=$0d;
  364.    msdos(u);
  365.    user^:=2;
  366. end; { resetdisk }
  367.  
  368.  
  369. procedure setjumps;
  370. var u : registers;
  371.     s : ^fname;
  372.     j : integer;
  373.     temp: byte;
  374. begin
  375.    temp:=iobyte^;
  376.    { now intialize lowest page of memory in the cpm80 seg }
  377.    fillchar(zero^,$ff,0);
  378.    iobyte^:=temp;
  379.    abortflag^:=0; { special }
  380.    mem[cpm80:0] :=jmp;
  381.    memw[cpm80:1]:=biosaddr+3;
  382.    mem[cpm80:5] :=jmp;
  383.    memw[cpm80:6]:=bdosaddr + 6; { skip serial number }
  384.    user^:= getcurrent;
  385.    { blank fcb's }
  386.    s:=ptr(cpm80,$5d);
  387.    for j:=1 to 11 do s^[j]:=' ';
  388.    s:=ptr(cpm80,$6d);
  389.    for j:=1 to 11 do s^[j]:=' ';
  390. end; { set jumps }
  391.  
  392.  
  393. procedure putnibble( x : byte);
  394. begin
  395.    x:=x and $0f;
  396.    if x < 10 then write(chr($30+x)) else write(chr(55+x));
  397. end;
  398.  
  399. procedure putbyte( x : byte );
  400. begin
  401.    x:=x and $ff;
  402.    putnibble(x shr 4);
  403.    putnibble(x);
  404. end;
  405.  
  406. procedure putword( x : word);
  407. begin
  408.    putbyte(hi(x));
  409.    putbyte(lo(x));
  410. end;
  411.  
  412. procedure putinteger( x : integer);
  413. begin
  414.    putbyte(hi(x));
  415.    putbyte(lo(x));
  416. end;
  417.  
  418.  procedure setinterrupt ( x : interruptrec);
  419.  { set interrupt vector x to xseg:xofs }
  420.  var   z : registers;
  421.  begin
  422.     z.ds:= x.segment;
  423.     z.dx:= x.offset;
  424.     z.ah:= $25;
  425.     z.al:= x.loc;    { load ah with 25h , al with interrupt number}
  426.     msdos(z);
  427.  end; { setinterrupt }
  428.  
  429.  procedure getinterrupt ( var x : interruptrec);
  430.  { get interrupt vector x into xseg,xofs }
  431.  var   z : registers;
  432.  begin
  433.      z.ah:= $35;
  434.      z.al:= x.loc;
  435.      msdos(z);
  436.      x.segment:=z.es;
  437.      x.offset  :=z.bx;
  438.  end; { getinterrupt }
  439.  
  440. procedure setdefdma;
  441. var z :registers;
  442.   begin
  443.      z.ds:=cpm80;
  444.      z.dx:=$80;
  445.      z.ah:=$1a;    { set default dma while we're at it }
  446.      msdos(z);
  447.   end; { setdefdma }
  448.  
  449. procedure loadbdos;
  450. var u : ^byte;
  451.     bbbb : ^byte;
  452. begin
  453.    u   :=ptr(cpm80,bdosaddr);
  454.    bbbb:=@v20bdos;
  455.    move(bbbb^,u^,BDOSSIZE);
  456. end; { load bdos }
  457.  
  458.  
  459. procedure loadccp;
  460. var  u    : ^byte;
  461.      cccc : ^byte;
  462. begin
  463.    u:= ptr(cpm80,ccpaddr);
  464.    cccc:=@v20ccp;
  465.    move(cccc^,u^,CCPSIZE);
  466. end; { move ccp }
  467.  
  468. procedure warmboot( var x : registers);
  469. var  u : registers;
  470. begin  { reload ccp and reset dma to $80 }
  471.     loadccp;
  472.     resetdisk;
  473.     setdefdma; { stack and DMA }
  474.     lastdma:=$80;
  475.     setjumps;  { clean up 0 to $ff to ffh in  cpm80 seg }
  476.     x.bx:=ccpaddr + 3;
  477. end; { warmboot }
  478.  
  479. procedure notimplemented;
  480. var  ch : char;
  481. begin
  482.    write(#7,#7,'Warning! Bios call ');
  483.    putbyte(biosreg^.a);
  484.    writeln('H not implemented.');
  485.    writeln;
  486.    write('Resume 8080 program or abort? <R, A>  '); ch:=readkey;
  487.    write(ch);
  488.    if not ( upcase(ch) = 'R') then abortflag^ := $ff else writeln;
  489. end; { not implemented }
  490.  
  491. { bios routines next }
  492. { cpm/80 sends parameters in c or bc, gets back in a and hl }
  493.  
  494. { first are physical character output routines }
  495.  
  496. { CRT device }
  497.  
  498. procedure CRTOUTPUT( x : byte);
  499. var u : registers;
  500. begin
  501.   write(chr(x));
  502. end; { crtoutput }
  503.  
  504. procedure CRTINPUT( var x : byte);
  505. var ch: char;
  506. begin
  507.       ch:=readkey;
  508.       x:=ord(ch);
  509. end; { CRTINPUT }
  510.  
  511. procedure CRTSTATUS( var x : byte);
  512. var u : registers;
  513. begin
  514.    if keypressed then x:=$ff else x:=0;
  515. end; { CRTSTATUS }
  516.  
  517. { TTY device }
  518. { use native TURBO routines }
  519. { emulates H-19 terminal codes }
  520.  
  521. var escqueue    : char;
  522.  
  523. procedure ttystatus( var x : byte);
  524. begin
  525.    if escinflag  then x:=$ff  { if have some in queue }
  526.    else
  527.     if keypressed then x:=$ff else x:=$0;
  528. end; { ttystatus }
  529.  
  530.  
  531. procedure longescape;
  532. var x,y : byte;
  533. begin
  534.     { now take a look at longer escape sequences }
  535.     case escstr[2] of
  536.  
  537.       { cursor positioning }
  538.       'Y' : if length(escstr) >=4 then begin
  539.                y:=ord(escstr[3]) - 31;
  540.                x:=ord(escstr[4]) - 31;
  541.                gotoxy(x,y);
  542.                escstr:='';escoutflag:=false;
  543.              end;
  544.  
  545.       { various setup codes not implemented }
  546.        'x','y' :  if length(escstr) >=3 then begin
  547.                       escstr:='';
  548.                       escoutflag:=false;
  549.                   end;
  550.     end; { case }
  551.   end; { long escape }
  552.  
  553. procedure processescape;  { try minor emulation of h-19}
  554. var x,y,i,j : byte;
  555.     notproc : boolean;
  556. begin
  557.    notproc:=false;
  558.    case escstr[2] of
  559.       'E' : clrscr;
  560.       'M' : delLine;
  561.       'L' : InsLine;
  562.       'K' : ClrEol;
  563.       'H' : gotoxy(1,1);
  564.       'J' : { EOS }
  565.            begin
  566.                x:=whereX; y :=whereY; clreol;
  567.                for j:=y +1 to 25 do begin
  568.                   gotoxy(1,j);clreol;
  569.                end;
  570.                gotoxy(x,y);
  571.             end;
  572.       'p' : lowvideo;
  573.       'q' : NormVideo;
  574.       'l' : { erase entire line
  575.               begin
  576.                  delLine; InsLine;
  577.               end;
  578.       'N' : { delete char }  write(' ');
  579.       'A' : { cursor up }
  580.             begin
  581.                x:=whereX; y:=whereY -1;  if y < 1 then x:=1;
  582.                gotoxy(x,y);
  583.              end;
  584.       'B' : { cursor down }
  585.              begin
  586.                x:=whereX; y:=whereY +1;  if y > 25 then y:=25;
  587.                gotoxy(x,y);
  588.              end;
  589.       'C' : { cursor right }
  590.               begin
  591.                 x:=whereX +1; y:=whereY;  if x > 80 then x:=80;
  592.                 gotoxy(x,y);
  593.              end;
  594.       'D' : { cursor left }
  595.              begin
  596.                x:=whereX-1; y:=whereY;  if y < 1 then x:=1;
  597.                gotoxy(x,y);
  598.              end;
  599.       'j' : { push cursor }
  600.             begin
  601.               cursX:=whereX;
  602.               cursY:=whereY;
  603.             end;
  604.       'k' : { pop cursor }  gotoxy(cursX,cursY);
  605.       'n' : begin
  606.             end;
  607.       '@' : begin end; { go to insert mode }
  608.       'b' : begin  { erase beginning of display }
  609.             x:=whereX;y:=whereY;
  610.             for i:=1 to y-1 do begin
  611.                 gotoxy(1,i);
  612.                 clreol;
  613.             end;
  614.             gotoxy(1,y);
  615.             for i:=1 to y do write(' ');
  616.             gotoxy(x,y);
  617.            end;
  618.  
  619.       'I' : { reverse index }
  620.              begin
  621.                x:=whereX; y:=whereY -1; if y < 1 then y:=1;
  622.                gotoxy(x,y);
  623.              end;
  624.       'o' : begin    { erase beginning of line }
  625.               x:=whereX;y:=whereY;
  626.               gotoxy(1,y);
  627.               for i:=1 to x do write(' ');
  628.               gotoxy(x,y);
  629.             end; { erase beginning of  line }
  630.       'F','G','[','=','>','t','u','<','z','Z','#',']','}','{','v','w' : begin end;
  631.       'Y','y','x' : begin notproc:=true; longescape; end;
  632.   end; { case }
  633.     if not notproc then begin
  634.        escoutflag:=false;
  635.        escstr:='';
  636.        exit;
  637.     end;
  638.  
  639. end; { processescape }
  640.  
  641. procedure ttyoutput( x : byte);
  642. var s,y,yy : byte;
  643.      j : integer;
  644. begin
  645.    if x=27 then begin   { start accumulating chars again }
  646.       escoutflag:=true;
  647.       escstr:= ''+ ESC;   { start over }
  648.       exit;
  649.    end;
  650.    if not escoutflag then begin
  651.        if x <> 9 then write(chr(x))  { expand tabs }
  652.        else begin
  653.           s:=WhereY;y:=WhereX; yy:=y;
  654.           y:= 9 + 8*(y div 8);  if y > 80 then y:=80;
  655.           (* do it with blanks *)
  656.           for j:=yy to y do write(' ');
  657.           gotoxy(y,s);
  658.        end;
  659.     end
  660.    else begin
  661.       { check for escape sequence abort }
  662.       if x=30 then begin escoutflag:=false; escstr:='';exit;end;
  663.       escstr:=escstr+chr(x);
  664.       processescape;
  665.    end;
  666. end; { ttyoutput }
  667.  
  668. function translate( ch : char) : char;
  669. { translate turbo scan codes to H-19 codes }
  670. begin
  671.    case ch of
  672.     #75,#115,#178 : translate:='D';
  673.     #77,#116,#180 : translate:='C';
  674.     #72,#160,#175 : translate:='A';
  675.     #80,#164,#183 : translate:='B';
  676.     #71,#174      : translate:='H';
  677.     #59,#84,#94,#104 : translate:='S';   { function keys 1..5 }
  678.     #60,#85,#95,#105 : translate:='T';
  679.     #61,#86,#96,#106 : translate:='U';
  680.     #62,#87,#97,#107 : translate:='V';
  681.     #63,#88,#98,#108 : translate:='W';
  682.     #66,#91,#101,#111: translate:='P';   { blue, grey, red }
  683.     #67,#92,#102,#112: translate:='Q';
  684.     #68,#93,#103,#113: translate:='R';
  685.     else translate:=ch;
  686.   end; { case }
  687. end; { translate }
  688.  
  689.  
  690. procedure ttyinput( var x : byte);
  691. var ch: char;
  692. begin
  693. { see if any in line already }
  694. if escinflag then begin
  695.       x:=ord(escqueue);
  696.       escinflag:=false;
  697.       exit;
  698.   end;
  699.  { else wait for a real character }
  700.   ch:=readkey;
  701.   x:=ord(ch); { return the character }
  702.   if (ch = ESC) and keypressed then begin
  703.      ch:=readkey;
  704.      escinflag:=true;
  705.      escqueue:= translate(ch);  { change on the fly }
  706.   end;
  707. end; { ttyinput }
  708.  
  709.  
  710. { user console 1 routines to allow external CRT on COMM1 }
  711. { use interrupt 14h }
  712.  
  713. procedure uc1status( var x : byte);
  714. var u : registers;
  715. begin
  716.   u.ah:=3;
  717.   u.dx:=WHICHCOMM;
  718.   intr($14,u);
  719.   { status now in u.ah }
  720.   if (u.ah and $1) <> 0 then x:=$ff else x:=$0;
  721. end; { uc1status }
  722.  
  723. procedure uc1input( var x : byte);
  724. var u : registers;
  725. begin
  726.    repeat  uc1status(x) until x = $ff;
  727.    u.ah:=2;
  728.    u.dx:=whichcomm;
  729.    intr($14,u);
  730.    x:=u.al;
  731. end; { uc1input }
  732.  
  733. procedure uc1output( x : byte);
  734. var u : registers;
  735. begin
  736.   repeat
  737.     u.ah:=3;
  738.     u.dx:=whichcomm;
  739.     intr($14,u);
  740.   until u.ah and $40 <> 0;
  741.   u.ah:=1;
  742.   u.al:=x;
  743.   u.dx:=whichcomm;
  744.   intr($14,u);
  745. end; { uc1output }
  746.  
  747.  
  748. var nextlist : byte;
  749. { routed to a file, V20OUT.LST, presently }
  750.  
  751. procedure ul1output( x : byte);
  752. var u : registers;
  753. begin
  754.   u.bx:=listouthandle;
  755.   u.ds:=dseg;
  756.   u.dx:=ofs(nextlist);  { a 1 byte buffer }
  757.   nextlist:=x;
  758.   u.cx:=1;
  759.   u.ah:=$40;
  760.   msdos(u);
  761.   if u.flags and carryflag <> 0 then write(#7);
  762.   if u.ax<> 1 then write('?');
  763. end; { ul1output }
  764.  
  765.  
  766. procedure breader( var x : byte);
  767. var ch : char;
  768.     u : registers;
  769. begin
  770.    case  (iobyte^ and readermask) shr readershift of
  771.       TTY  :  begin ch:=readkey;  x:=ord(ch); end;
  772.       RDR0 ,UR1, UR2:begin
  773.            u.ah:=$03;
  774.            msdos(u);
  775.            x:=u.al;
  776.           end;
  777.   end; { case }
  778. end;   { breader }
  779.  
  780. procedure bpunch( x : byte);
  781. var  u : registers;
  782. begin
  783.   case (iobyte^ and punchmask) shr punchshift of
  784.   TTY,PUN,UP1,UP2: begin
  785.         u.ah:=$04;
  786.         u.dl:=x;
  787.         msdos(u);
  788.       end;
  789.   end;
  790. end; { bpunch }
  791.  
  792. procedure blist( x : byte);
  793. begin
  794.   case (iobyte^ and listmask) shr listshift of
  795.     TTY:  TTYOUTPUT(x);
  796.     CRTT: crtoutput(x);
  797.     LPTT:  write(lst,chr(x));
  798.     UL1:  ul1output(x);
  799.  end; { case }
  800. end; { blist }
  801.  
  802. procedure blistst( var x : byte);
  803. begin
  804.     x:=$0ff;
  805. end; { blistst }
  806.  
  807.  
  808. procedure bconst( var x : byte);
  809. begin
  810.    case (iobyte^ and consolemask)  of
  811.      TTY : TTYSTATUS(x);
  812.      CRTT: CRTSTATUS(x);
  813.      BAT : if keypressed then x:=$ff else x:=0;
  814.      UC1 : uc1status(x);
  815.    end; { case }
  816. end;
  817.  
  818. procedure bconin( var x : byte);
  819. var u : registers;
  820.    ch : char;
  821. begin   { bconin }
  822.   case (iobyte^ and consolemask)  of
  823.     CRTT: crtinput(x);
  824.     TTY : ttyinput(x);
  825.     BAT : breader(x) ;
  826.     UC1 : uc1input(x);
  827.  end; { case }
  828. end; { bconin }
  829.  
  830. procedure bconout( x : byte);
  831. begin
  832.    case (iobyte^ and consolemask) of
  833.      TTY  : ttyoutput(x);
  834.      CRTT : crtoutput(x);
  835.      BAT :  blist(x);
  836.      UC1 :  uc1output(x);
  837.    end;
  838. end; { bconout }
  839.  
  840. procedure bseldsk( var x : registers);
  841. var  z : registers;
  842. begin
  843.    z.ah:=$0e;
  844.    z.dl:=x.cl;   { restored at end }
  845.    msdos(z);
  846.    x.bx:=0;      { no DPH for now }
  847. end; { bseldsk }
  848.  
  849.  
  850. procedure bsetdma(var x : registers);
  851. var z : registers;
  852. begin
  853.    { do msdos STA }
  854.    { DMA address in BC }
  855.    z.ah:=$1a;
  856.    z.dx:=x.cx;  { x.dx is restored at end ; }
  857.    lastdma:=x.cx;
  858.    msdos(z);
  859. end; { bsetdma }
  860.  
  861. function bsectran( x : word) : word;
  862. begin
  863.    bsectran:=x;
  864. end; { sectran }
  865.  
  866. var   x : registers;
  867. var biostmp : byte;
  868.  
  869. { next routine accessed only via CALLN BIOSHANDLE from 8080 mode }
  870.  
  871. procedure biosemul;
  872. interrupt;  (* for turbo 4.0, try built in stuff *)
  873. { communicates via biosreg, a has call, bc data, returns in hl }
  874. var x :registers;
  875. begin
  876.    (* writeln('Got to BIOS. call  ',biosreg^.a);   *)
  877.    x.CX:=biosreg^.bc; { pass data on }
  878.    x.ds:=cpm80;
  879. case bioswhich^ of
  880.     4:   (* bconout(x.cl);*)
  881.             case (iobyte^ and consolemask) of
  882.                CRTT : write(bioschar^);
  883.                TTY : ttyoutput(biosbyte^);
  884.                BAT : blist(biosbyte^);
  885.                UC1 : uc1output(biosbyte^);
  886.              end;
  887.  
  888.     0:   abortflag^:=1;      (* cold boot should abort *)
  889.     1:   begin
  890.             warmboot(x);
  891.             x.bx:=ccpaddr + 3;  { jump to beginning of ccp }
  892.          end;
  893.          { check this for the new bdos !!! }
  894.     2:   bconst(x.al);
  895.     3:   bconin(x.al);
  896.     5:   blist(x.cl);
  897.     6:   bpunch(x.cl);    { punch  }  { do as  COM1 later }
  898.     7:   breader(x.al);   { reader }  { do as  COM1 later }
  899.     8:   begin  end;      { home   }  { fake it }
  900.     9:   bseldsk(x);      { does not return DPE pointer }
  901.     10:  notimplemented ; { settrk }
  902.     11:  notimplemented ; { setsec }
  903.     12:  bsetdma(x);
  904.     13:  notimplemented ; { read   }
  905.     14:  notimplemented ; { write  }
  906.     15:  blistst( x.al);
  907.     16:  biosreg^.hl:=bsectran(x.cx); { probably not used }
  908.   else notimplemented;
  909.  end; { case }
  910.  biosreg^.a:=x.al;
  911.  biosreg^.hl:=x.bx; (* wrong for 16 *)
  912. end; { bios emulation }
  913.  
  914.  
  915. { now bdos routines }
  916. { cpm80 sends parameters in e or de, gets back in a or hl }
  917.  
  918. procedure nobdos( n : byte);
  919. begin
  920.     writeln;write('BDOS call # ');putbyte(n);writeln(' not implemented.');
  921.     writeln;
  922.     abortflag^:=1;
  923. end; { nobdos }
  924.  
  925.  
  926. function getalloc : word;
  927. var    v : ^alloc;
  928.        u : registers;
  929. begin
  930.    v:=ptr(cpm80,bdosaddr+bdosalloc);
  931.    fillchar(v^[0],NUMALLOCBYTES,$ff);    { say that it's used }
  932.    u.dl:=0; { default drive }
  933.    u.ah:=$36;
  934.    msdos(u);
  935.    if u.ax=$ffff then writeln('Invalid CP/M drive number.');
  936.    if u.bx <> 0 then begin
  937.        { compute number of free blocks, and mark them with a zero }
  938.        if u.bx > (NUMALLOCBYTES shl 3)   then u.bx:= NUMALLOCBYTES shl 3 ;
  939.        v^[0]:= (not (u.bx and $0007)) and $ff;     { partial block }
  940.        fillchar(v^[$1], (u.bx shr 3 ),0);          { whole blocks }
  941.    end;
  942.    getalloc:=bdosaddr+bdosalloc;
  943. end; { getalloc }
  944.  
  945. { construct a fake dpb from the DOS data }
  946. { need this for programs like DISK76 and SD to work }
  947.  
  948. function makedpb : word;
  949. var    u : registers;
  950.        fatid : byte;
  951.        numalloc : word; { number of allocation blocks }
  952.        secalloc : byte;    { number of sectors per alloc block }
  953.        physize  : word; { number of bytes in physical sector }
  954.        fake : dpbblk;
  955.        cdpb : ^dpbblk;
  956.        fat  : ^byte;
  957.        cpmphys : byte;
  958.        small   : boolean;
  959.        blksize : word; { cpm alloc block size }
  960. begin
  961.   cdpb:=ptr(cpm80,bdosaddr+bdosdpb);
  962.   fillchar(cdpb^,16,0);
  963.   with fake do begin
  964.       u.ah:=$1b;  { use newer dos call }
  965.       msdos(u);
  966.       fat:=ptr(u.ds,u.bx);
  967.       fatid:=fat^;     { in asm code, would have to protect DS }
  968.  
  969.       u.ah:=$36;u.dl:=0;
  970.       msdos(u);
  971.       numalloc:=u.dx;  { number of allocation units }
  972.       secalloc:=u.al;  { physical sectors per allocation unit }
  973.       physize:=u.cx;   { size of physical sector }
  974.       cpmphys:= physize shr 7; { 128 byte sectors per physical sector }
  975.                                {assume multiple of 128 }
  976.       if fatid <> $f8 then begin
  977.           { check for dos 1.x versus dos 2.x formats }
  978.           if (fatid and 2) <> 0 then spt:= 8*cpmphys else spt:=9*cpmphys;
  979.       end else begin { is harddisk }
  980.           spt:=18*4; { 18 physical 512 byte sectors per track }
  981.       end;
  982.           { compute block size } { minimum of 1k for cpm }
  983.       blksize:= ( secalloc * physize ) shr 10 ; {  in 1024 chunks }
  984.       if blksize = 0 then begin
  985.             numalloc:=numalloc shr 1 ;
  986.             blksize:=1;
  987.       end;   { smallest allowable cpm blocks are 1k, so adjust }
  988.       if numalloc > 255 then small:=true else small:=false;
  989.       { decides byte or word value for alloc blocks in cpm fcb }
  990.       if (numalloc < 0)  or ( numalloc > 8*$d0) then numalloc:=8*$d0;
  991.       { only have $d0 bytes for allocation vector }
  992.  
  993. case blksize of
  994.        1: begin  bsh:=3; blm:=7;   end;
  995.        2: begin  bsh:=4; blm:=15;  end;
  996.        4: begin  bsh:=5; blm:=31;  end;
  997.        8: begin  bsh:=6; blm:=63;  end;
  998.       16: begin  bsh:=7; blm:=127; end;
  999.     else writeln(#$0d,#$0a,#7,'Screwy blocksize.');
  1000.    end; { case }
  1001.    { set extent mask block }
  1002.    { 16k per extent. exm is # of extents per dir entry -1}
  1003.    { there are 16 bytes of data/entry, so if 1 byte pointers, gives}
  1004.    { 16 blocks/entry. If 2 bytes per counter, gives 8 blocks/entry }
  1005.    if small then
  1006.        exm:= blksize -1
  1007.    else exm:= (blksize shr 1) -1;
  1008.    if exm < 0 then begin
  1009.         writeln;
  1010.         writeln('Bad decode of BPB information giving invalid CP/M DPB.');
  1011.         writeln;
  1012.    end;
  1013.    dsm:=numalloc-1;
  1014.    drm:=511;
  1015.    cks:=0;
  1016.    al01:=$ff00;
  1017.    off:=0; { no reserved  tracks }
  1018.  end; { with fake }
  1019.  { pass a allocation vector .. a bit map of usage of disk blocks }
  1020.  cdpb^:=fake;
  1021.  makedpb:=bdosaddr+bdosdpb;
  1022. end; { makedpb }
  1023.  
  1024. var
  1025.      help : dosfcb;
  1026.         w : ^dosfcb;
  1027.  
  1028. procedure setdma; { make  sure the  dma address is set correctly }
  1029. var  t : registers;
  1030. begin
  1031.   t.ah:=$1a;
  1032.   t.ds:=cpm80;
  1033.   t.dx:=lastdma;
  1034.   msdos(t);
  1035. end; { setdma }
  1036.  
  1037.  
  1038. { CP/M programs seemed to destroy parts of the record in fcb needed }
  1039. { by msdos to do findnext. The following kludge is to save the }
  1040. { result of the fcb for each findfile or findnext for use later. }
  1041. { If different fcbs were used, this could be a problem???? }
  1042. { However, the big problem would be a fallacious match and then }
  1043. { a delete, so avoid this by a check on the fcb address }
  1044. { However, this did not work.... }
  1045. { The CCP - EMUL interaction clobbers the DMA address somewhere }
  1046.  
  1047.  
  1048. { kludge.. set the first directory byte = 0 always }
  1049. { because msdos puts it to be disk number a=1,..e=5 }
  1050. { sets returned size in size bytes of fcb since some programs }
  1051. { eg, SD, use this to size the file, instead of the BDOS CALL }
  1052.  
  1053.  
  1054. procedure fakeit;
  1055. var u : ^dosdir;
  1056.    y, x : longint;
  1057.    z,t :  byte;
  1058.    j : word;
  1059. begin
  1060. { adjust directory entry returned for right size and user }
  1061. { MSDOS returns 1st byte to be disk number. CP/M expects user number }
  1062. { Our CP/M emulation has only userr 0, so set this to be zero, and   }
  1063. { make the extent byte and the leftover sectors byte }
  1064.  
  1065.    u:=ptr(cpm80,lastdma);  { look at the directory entry returned }
  1066.  
  1067.    { read the MSDOS file size from this directory entry }
  1068.    x:= longint(u^.size[1]) +  (longint(u^.size[2]) shl 8) +
  1069.        (longint(u^.size[3]) shl 16) + (longint(u^.size[4]) shl 24) ;
  1070.    if  x > $400000 then x:=$400000; { set a cut off size of 4 megs }
  1071.    y:= x shr 14 ; { number of 16k extents }
  1072.    { if y > 256, in trouble, so x < 256*16k = 4 meg }
  1073.    x:= x and $3FFF; { get remainder mod 16384 }
  1074.    x:= x shr 7;   { number of physical sectors }
  1075.    t:= x      ;
  1076.    for j:=12 to 31 do u^.cpmdir[j]:=0; { zero the rest of it }
  1077.    u^.cpmdir[0]:=0;  { set user byte = 0 }
  1078.    u^.cpmdir[$f]:=t; { sectors left over from last extent }
  1079.    u^.cpmdir[$c]:=z; { extent byte  }
  1080. end; { fakeit }
  1081.  
  1082.  
  1083. {  according to cp/m rules, a program can do no disk calls between }
  1084. {  findfirst and findnext, or between sucessive findnext calls }
  1085. {  MSDOS claims you can, but not with same FCB }
  1086.  
  1087.  
  1088. procedure findfile(var z : registers);
  1089. var u : ^dosfcb;
  1090.     p : ^byte;
  1091. begin
  1092.    u:=ptr(z.ds,z.dx);
  1093.    z.ah:=$11;
  1094.    msdos(z);
  1095.    p:=ptr(cpm80,lastdma);
  1096.    if p^<> $e5 then fakeit;
  1097.    myownfcb:=u^;     { save the exact fcb for use for find next }
  1098.    z.bl:=z.al;  { bdos returns a in l }
  1099.    {writeln('findfile  a= ',z.al);  }
  1100. end; { findfile }
  1101.  
  1102. procedure findnext( var z : registers);
  1103. var u : ^dosfcb;
  1104.     p : ^byte;
  1105. begin
  1106.    u:=ptr(z.ds,z.dx);
  1107.    u^:=myownfcb;         { replace it by my copy from last time }
  1108.    z.ah:=$12;
  1109.    msdos(z);
  1110.    myownfcb:=u^;        { hoard it away again }
  1111.    p:=ptr(cpm80,lastdma);  { see what it returned }
  1112.    if p^<> $e5 then fakeit;
  1113.    { writeln('findnext a= ',z.al); }
  1114.    z.bl:=z.al;
  1115. end; { findnext }
  1116.  
  1117.  
  1118. { all disk calls go directly to MSDOS bdos }
  1119. { character calls go through 8080 BIOS and on to 8086 bios emulator }
  1120.  
  1121.  
  1122. { adapt for interrupt servicing }
  1123. var   f : ^dosfcb;
  1124.       mine : dosfcb;
  1125.  
  1126. { NOTES on interfacing Turbo modules as CALLN service routines
  1127.   For a Turbo routine with no parameters or local storage the
  1128.   code produced is
  1129.   push  bp
  1130.   mov   bp,sp
  1131.   push  bp
  1132.   jump  xxxx
  1133. xxxx:   ....... Turbo code
  1134.   jmp   yyyy
  1135. yyyy:   mov   sp,bp
  1136.   pop   bp
  1137.   ret
  1138.  
  1139.  I'm trying the sequence
  1140.   push  ds
  1141.   mov   ds, cs:[oldds]     ; at xxxx
  1142.  
  1143.   pop   ds
  1144.   mov   sp,bp
  1145.   pop   bp                 ; before jump to yyyy
  1146.   iret
  1147. }
  1148.  
  1149.  
  1150. var  spin : word;
  1151.      cpmrecord : word;
  1152.  
  1153. procedure bdosemul;  { communicates with bdosreg pointer }
  1154. { emulate cpm80 bdos using msdos bdos }
  1155. { problem with record lengths.. 36 versus 37 bytes }
  1156. { so doctor the fcbs on read and write calls }
  1157. interrupt; (* for turbo 4.0 *)
  1158. begin
  1159.     x.ah:=bdosreg^.c;  { could do this in 8080 bdos }
  1160.                         { prepare for msdos call }
  1161.     x.dx:=bdosreg^.de;
  1162.     x.ds:=cpm80;
  1163.  
  1164.  case x.ah of
  1165.  { calls 0-c are handled by 8080 bdos stub via bios calls }
  1166.  { this allows for more compatibility with CP/M-80 console handling }
  1167.  
  1168.       $D : msdos(x);
  1169.       $E..$10,$13:  begin
  1170.                   msdos(x);
  1171.                   x.bl:=x.al;
  1172.                 end;
  1173.       $11:  findfile(x);
  1174.       $12:  findnext(x);
  1175.       $14:  begin  { read  seq. }
  1176.                setdma;
  1177.                f:=ptr( cpm80,bdosreg^.de);
  1178.                mine:=f^;  { switch fcb's for disk calls }
  1179.                x.dx:=ofs(mine);
  1180.                x.ds:=seg(mine);
  1181.                mine.recsize:=$80;
  1182.                msdos(x);
  1183.                f^.cpmseqfcb:=mine.cpmseqfcb; { identify these pieces }
  1184.                if x.al=3 then x.al:=0; { read 1 more }
  1185.                x.bl:=x.al;
  1186.                if x.al=2 then writeln(#7,'BDOS read error. DTA too small.');
  1187.                { msdos sends a 3 for partial record read. }
  1188.                { But cpm only knows about 128 byte records, so fake it }
  1189.                { one problem is that some dos programs do not terminate
  1190.                text file with $1a. should probably fake this }
  1191.             end; { read }
  1192.       $15:   begin   { write seq. }
  1193.                setdma;
  1194.                f:=ptr( cpm80,bdosreg^.de);
  1195.                mine:=f^;  { switch fcb's for disk calls }
  1196.                mine.recsize:=$80;
  1197.                x.dx:=ofs(mine);
  1198.                x.ds:=seg(mine);
  1199.                msdos(x); x.bl:=x.al;
  1200.                f^.cpmseqfcb:=mine.cpmseqfcb; { identify these pieces }
  1201.                x.bl:=x.al;
  1202.             end;
  1203.       $16..$17: begin
  1204.                   msdos(x);
  1205.                   if x.al <> 0 then x.al:=$ff;
  1206.                   x.bl:=x.al;
  1207.                 end;
  1208.       $18:   begin
  1209.                x.ah:=$19;msdos(x);
  1210.                x.ah:=$e; msdos(x);
  1211.                { now convert number in x.al to vector in x.bx}
  1212.                spin:=$ffff;
  1213.                spin:=spin shr (16 - x.al);
  1214.                x.bx:=spin;
  1215.              end; { log in vector }
  1216.       $19:   begin msdos(x); x.bl:=x.al; end;
  1217.       $1a:   begin       { set dma address }
  1218.                 lastdma:=x.dx;
  1219.                 setdma;
  1220.              end;
  1221.       $1b: begin x.bx:=getalloc; x.al:=x.bl;end;
  1222.                  { have to calculate .. use $1c dos call }
  1223.       $1c: begin x.al:=0;end;      { set write protect..fake it  }
  1224.       $1d: begin x.bx:=0; x.al:=0; end;      { no write protected disks }
  1225.       $1e: begin x.bl:=0; x.al:=0; end;      { set attributes }
  1226.       $1f: x.bx:=makedpb;  { make it up in 8080 space }
  1227.       $20: begin   { get/set user number }
  1228.               if x.dl=$ff then begin x.al:=0; x.bl:=0; end;
  1229.            end;
  1230.       $21..$22: begin { random read and write }
  1231.                   writeln(#7,'Used Random READ-WRITE calls.');
  1232.                   setdma;
  1233.                   f:=ptr( cpm80,bdosreg^.de);
  1234.                   { get the record number to save }
  1235.                   mine:=f^;  { switch fcb's for disk calls }
  1236.                   cpmrecord:=f^.randrec[1];
  1237.                   mine.randrec[2]:=0;
  1238.                   x.dx:=ofs(mine);
  1239.                   x.ds:=seg(mine);
  1240.                   mine.recsize:=$80;
  1241.                   msdos(x);
  1242.                   f^.cpmrandfcb:=mine.cpmrandfcb; { identify these pieces }
  1243.                   f^.randrec[1]:= cpmrecord; { restore the record number }
  1244.                   x.bl:=x.al;
  1245.                 end;
  1246.       $23 :  begin   { file size }
  1247.                   (* writeln(#7,'Used File Size DOS call.'); *)
  1248.                   f:=ptr( cpm80,bdosreg^.de);
  1249.                   { get the record number to save }
  1250.                   mine:=f^;  { switch fcb's for disk calls }
  1251.                   x.dx:=ofs(mine);
  1252.                   x.ds:=seg(mine);
  1253.                   mine.recsize:=$80;
  1254.                   msdos(x);
  1255.                   f^.cpmrandfcb:=mine.cpmrandfcb; { identify these pieces }
  1256.                   x.bl:=x.al;
  1257.                 end;
  1258.       $24 :  begin   { set random record number}
  1259.                   (* writeln(lst,'Used SET RND REC'); *)
  1260.                   f:=ptr( cpm80,bdosreg^.de);
  1261.                   mine:=f^;  { switch fcb's for disk calls }
  1262.                   x.dx:=ofs(mine);
  1263.                   x.ds:=seg(mine);
  1264.                   mine.recsize:=$80;
  1265.                   msdos(x);
  1266.                   f^.cpmrandfcb:=mine.cpmrandfcb; { identify these pieces }
  1267.                   x.bl:=x.al;
  1268.              end;
  1269.       $25 :  begin    { selective reset disk }
  1270.                x.ah:=$0d;
  1271.                msdos(x);
  1272.                x.al:=0; x.bl:=0;
  1273.              end;
  1274.       $28:  begin    { zero fill random write }
  1275.                   x.ah:=$22; { fake it with usual random write }
  1276.                   setdma;
  1277.                   f:=ptr( cpm80,bdosreg^.de);
  1278.                   { get the record number to save }
  1279.                   mine:=f^;  { switch fcb's for disk calls }
  1280.                   cpmrecord:=f^.randrec[1];
  1281.                   mine.randrec[2]:=0;
  1282.                   x.dx:=ofs(mine);
  1283.                   x.ds:=seg(mine);
  1284.                   mine.recsize:=$80;
  1285.                   msdos(x);
  1286.                   f^.cpmrandfcb:=mine.cpmrandfcb; { identify these pieces }
  1287.                   f^.randrec[1]:= cpmrecord; { restore the record number }
  1288.                   x.bl:=x.al;
  1289.            end;
  1290.     else  nobdos(x.cl);
  1291.   end; { case }
  1292.   bdosreg^.hl:=x.bx;
  1293.   bdosreg^.a:=x.al;
  1294. end; { bdos emulation }
  1295.  
  1296. var   st : ^anystring;
  1297. (* this later piece is not implemented yet
  1298.    the thought was to rewrite the ccp in pascal and add various features
  1299. *)
  1300. procedure ccpemul;
  1301. interrupt;
  1302. begin
  1303.  case ccpregs^.c of
  1304.   0 : begin  { chdir }
  1305.          st:=ptr(cpm80,ccpregs^.de +1);
  1306.          ChDir(st^);
  1307.          user^:=getcurrent;
  1308.       end;
  1309.   1 : begin
  1310.          st:=ptr(cpm80,ccpregs^.de +1);
  1311.          MkDir(st^)
  1312.       end;
  1313.   2 : begin
  1314.          st:=ptr(cpm80,ccpregs^.de +1);
  1315.          RmDir(st^)
  1316.       end;
  1317.   3 : begin
  1318.          st:=ptr(cpm80,ccpregs^.de +1);
  1319.          user^:=getcurrent;
  1320.          GetDir(user^+1,st^)
  1321.       end;
  1322.  else begin end;   { pass them through }
  1323.  end; { case }
  1324. end;     { ccp emul }
  1325.  
  1326.  
  1327. procedure logo;
  1328. var x : word;
  1329. begin
  1330. clrscr;
  1331.   writeln('CP/M-80 Emulator for NEC V-20 and the IBM-PC');
  1332.   writeln('Uses default setup or reads from file ');
  1333.   write('8080 Code Loaded at 0');putword(cpm80);writeln('H Segment');
  1334.   write('Uses interrupts ');putbyte(baseinterrupt);
  1335.   write('..');putbyte(baseinterrupt+3);writeln('H.');
  1336.   writeln;
  1337.   write('CCP loaded at ');putword(ccpaddr);
  1338.   write('H, BDOS at ')   ;putword(bdosaddr);
  1339.   write('H, BIOS at ')   ;putword(biosaddr);writeln('H.');
  1340.   x:=ccpaddr;
  1341.   writeln('TPA of ',x:6,' bytes.');
  1342.   write('Default IObyte is ');putbyte(defaultiobyte);writeln('H.');
  1343.   if(defaultiobyte and 1) = 0 then
  1344.                     writeln('TTY: device emulates H-19 terminal.');
  1345.   writeln('UL1: device is ',cpmoutname,'.');
  1346.   writeln;
  1347.   writeln('Copyright Nov.10, 1985 by Clarence Wilkerson');
  1348.   writeln('All rights reserved.');
  1349.   writeln('Modified for Turbo Pascal 4.0, December 6, 1988');
  1350.   writeln;
  1351.   writeln;
  1352.   writeln('Exit with the BYE command, or by writing 1 in location $ffff.');
  1353.   writeln;writeln;
  1354. end;
  1355.  
  1356.  
  1357. procedure findcpm;
  1358. var uu : ^byte;
  1359. begin     { put some error checking here eventually }
  1360.   new(cpmseg);
  1361.   new(extra); { hope these are together }
  1362.   cpm80:=seg(cpmseg^[0]) + (ofs(cpmseg^[0]) shr 4 ) + 1 ;
  1363.   uu:=ptr(cpm80,0);
  1364.   fillchar( uu^,$ffff,#0);
  1365. end; { findcpm }
  1366.  
  1367. procedure interruptset;
  1368. var  i : integer;
  1369. begin
  1370.   for i:=0 to 3 do begin
  1371.      oldinterrupt[i].loc:=baseinterrupt + i;
  1372.      newinterrupt[i].loc:=baseinterrupt + i;
  1373.      getinterrupt(oldinterrupt[i]);       { store it away for restoration }
  1374.   end;
  1375.   newinterrupt[coldb].segment       :=cpm80;
  1376.   newinterrupt[coldb].offset        :=biosaddr;  { DO 8080 COLD BOOT ROUTINE }
  1377.   newinterrupt[ccphandle ].segment  :=seg(ccpemul);
  1378.   newinterrupt[ccphandle ].offset   :=ofs(ccpemul);
  1379.   newinterrupt[bdoshandle].segment  :=seg(bdosemul);
  1380.   newinterrupt[bdoshandle].offset   :=ofs(bdosemul);
  1381.   newinterrupt[bioshandle].segment  :=seg(biosemul);
  1382.   newinterrupt[bioshandle].offset   :=ofs(biosemul);
  1383.   MEM[CPM80:$FFb0]:=BASEinterrupt+COLDB;
  1384.   MEM[CPM80:$FFb1]:=BASEinterrupt+CCPHANDLE;
  1385.   MEM[CPM80:$FFb2]:=BASEinterrupt+BDOSHANDLE;
  1386.   MEM[CPM80:$FFb3]:=BASEinterrupt+BIOSHANDLE;
  1387.   for i:=0 to 3 do setinterrupt(newinterrupt[i]);
  1388. end; { interruptsetup }
  1389.  
  1390.  
  1391. procedure opencpmoutput;
  1392. var u : registers;
  1393. begin   { use dos 2.0 handle calls }
  1394.   u.ah:=$3c;
  1395.   u.ds:=seg(cpmoutname);
  1396.   u.dx:=ofs(cpmoutname) + 1;
  1397.   cpmoutname:=cpmoutname + #0;
  1398.   u.cx:=0;
  1399.   msdos(u);
  1400.   listouthandle:=u.ax;
  1401.   if u.flags and carryflag <> 0 then begin
  1402.      writeln;writeln('Error on list handle opening.');
  1403.      writeln;
  1404.  end;
  1405. end; { open cpm output }
  1406.  
  1407. procedure closehandle( x : integer);
  1408. var u : registers;
  1409. begin
  1410.   u.ah:=$3e;
  1411.   u.bx:=x;
  1412.   msdos(u);
  1413.   if u.flags and carryflag <> 0 then begin
  1414.      writeln;writeln('Error on closing handle.');
  1415.   end;
  1416. end; { closehandle }
  1417.  
  1418. procedure restoreinterrupts;
  1419. var  i : integer;
  1420. begin
  1421.   for i:=0 to 3 do setinterrupt(oldinterrupt[i]);
  1422. end;  { restore interrupt vectors }
  1423.  
  1424. procedure digest(var s : anystring);
  1425. var i,n,code : integer;
  1426.     t,tt,ttt : anystring;
  1427. begin
  1428.    t:='';
  1429.    for i:=1 to length(s) do begin
  1430.        s[i]:=upcase(s[i]);
  1431.        if s[i] in ['A'..'Z','0'..'9','=','.','-','\',':','$'] then t:=t+s[i];
  1432.        { strip spaces and junk off }
  1433.    end; { for }
  1434.        n:=pos('=',t);
  1435.        if n=0 then exit;
  1436.        tt:=copy(t,1,n-1);
  1437.        ttt:=copy(t,n+1,255);
  1438.        if tt=  'IOBYTE' then begin
  1439.                      val(ttt,n,code);
  1440.                      if (code = 0) and (length(ttt) >0 ) then
  1441.                         defaultiobyte:=n;
  1442.                      end { get iobyte }
  1443.        else if tt=  'LIST'   then begin
  1444.                       if length(ttt) > 0 then cpmoutname:=ttt;
  1445.                     end { get LST output file }
  1446.        else if tt=  'INTER' then begin
  1447.                      val(ttt,n,code);
  1448.                      if (code = 0) and (length(ttt) >0 ) then
  1449.                         baseinterrupt:=n ;
  1450.                     end { get beginning interrupt number }
  1451.        else if tt= 'CCP'  then begin
  1452.                      val(ttt,n,code);
  1453.                      if (code = 0) and (length(ttt) >0 ) then
  1454.                         CCPSIZE:=n * 128;
  1455.                     end { length of ccp in 128 byte chunks }
  1456.       else if tt= 'ADDR'  then begin
  1457.                      val(ttt,n,code);
  1458.                      if (code = 0) and (length(ttt) >0 ) then
  1459.                         CCPADDR:=n;
  1460.                     end { beginning of ccp  }
  1461.  
  1462.       else if tt= 'BDOS' then begin
  1463.                      val(ttt,n,code);
  1464.                      if (code = 0) and (length(ttt) >0 ) then
  1465.                         BDOSSIZE:=n * 128;
  1466.                    end; { length of bdos-bios in 128 byte chunks }
  1467. end; { digest }
  1468.  
  1469. PROCEDURE getinitfile;
  1470. var  n : integer;
  1471.      parmstrng : anystring;
  1472. { allows change of most of the setup parameters }
  1473. begin
  1474.    assign (initf,'INITV20.CPM');
  1475.    {$I-}
  1476.    reset(initf);
  1477.    n:=IOresult;
  1478.    {$I+}
  1479.    if n <> 0 then exit; { no file }
  1480.    while not eof(initf) do begin
  1481.       readln(initf,parmstrng);
  1482.       digest(parmstrng);
  1483.    end; { while not eof }
  1484.    close(initf);
  1485. end; { get initialization file }
  1486.  
  1487. procedure initcpm; { set some cpm addresses }
  1488. begin
  1489.    lastdma  :=$80;
  1490.    bdosreg  :=ptr(cpm80,$ffe0);
  1491.    biosreg  :=ptr(cpm80,$ffd0);
  1492.    bioswhich:=ptr(cpm80,$ffd0);
  1493.    bioschar:=ptr(cpm80,$ffd4);
  1494.    biosbyte:=ptr(cpm80,$ffd4);
  1495.    ccpregs  :=ptr(cpm80,$ffc0);
  1496.    user     :=ptr(cpm80,4); (* user byte *)
  1497.    iobyte   :=ptr(cpm80,3); (* iobyte is implemented *)
  1498.    iobyte^  :=defaultiobyte;
  1499.    abortflag:=ptr(cpm80,$ffff);
  1500.    zero     :=ptr(cpm80,0);
  1501.    bdosaddr :=ccpaddr + CCPSIZE;
  1502. end; { initcpm }
  1503.  
  1504. procedure gotocpm(seg8080,stack8080pointer,brkemint : word); external;
  1505. {$L gotocpm.obj}
  1506. (*   Has to be assembled with MASM or TASM
  1507. ;
  1508. ;
  1509. ; procedure gotocpm(cpmseg,cpmstack,baseint : word);
  1510. ;begin
  1511. ; contains a kludge to get variable interrupt call
  1512. ; or BRKEM call
  1513. ; have to rewrite byte after the opcode
  1514. ; on machines with a cache, could be a problem
  1515.  
  1516. code segment word public
  1517.      assume cs:code
  1518.      public  gotocpm
  1519. gotocpm proc near
  1520.             push bp    ;
  1521.             mov  bp,sp ; standard set up code, no local variables
  1522.             mov  ax,[bp + 4] ; brkem int # patch
  1523.             mov  byte ptr cs:hack,al  ; modify code
  1524.             mov  ax,[bp + 8] ; using near call, so last param at bp + 4
  1525.             ; since we pushed bp to start, plus return address
  1526.             mov  dx,[bp +6]  ; cpmstack to dx
  1527.             push bp       ; used by V20 as stack pointer
  1528.             push ds       ; set equal to CP/M data=code
  1529.             mov  ds,ax    ; [cpm80] set data segment for cpm
  1530.             mov  bp, dx   ; cpmstack to set cp/m stack
  1531.             db   0fh,0ffh ;   BRKEM
  1532. hack:       db   000h;    ;   BRKEM  XX  ; uses XX interrupt
  1533.                           ; overwritten in this version
  1534.             pop ds
  1535.             pop bp
  1536.             mov sp,bp  ; standard exit code
  1537.             pop bp
  1538.             ret 6   ; 3 two byte parameters pushed on stack
  1539. gotocpm     endp
  1540.  
  1541. code ends
  1542. end
  1543.  
  1544. *)
  1545.  
  1546. var ExitSave : pointer;
  1547.  
  1548. procedure CPMExit;
  1549. (* get here by disk error, etc, so cold booting  CP/M would be best
  1550. solution. However, have not done.
  1551. *)
  1552.  
  1553. begin
  1554.   writeln('Error in CP/M handler.');
  1555.   ErrorAddr:=Nil;
  1556.   ExitProc:=Nil;
  1557. end;
  1558.  
  1559. begin { main }
  1560.    GETINITFILE;
  1561.    findcpm; { get the 64k code space for cp/m-80 }
  1562.    logo;
  1563.    initcpm; { set up some cpm variables }
  1564.    setjumps;  { set lower memory }
  1565.    loadccp;
  1566.    loadbdos;
  1567.    opencpmoutput;
  1568.    interruptset; { set up the interrupts }
  1569.    setdefdma;    { set stack and DTA, data segment }
  1570.    iobyte^:=DEFAULTIOBYTE;
  1571.    myseg:=cpm80;
  1572.    mystack:=cpmstack;
  1573.    myinterrupt:=baseinterrupt;
  1574.    Exitsave:=ExitProc;
  1575.    ExitProc:=@CPMexit;
  1576. (* set up segments and use BRKEM to get to emulator *)
  1577.  
  1578.    gotocpm(myseg,mystack,myinterrupt);
  1579.  
  1580.    ExitProc:=ExitSave;
  1581.    restoreinterrupts;
  1582.    closehandle(listouthandle);
  1583. end
  1584. .
  1585. 
  1586.