
;
; BIOS FOR THE CP/M EMULATOR
;
;
; (C) 1990,1991,1992,1993,1994,1995 by Jrgen Weber
;
; Version: 1.0
;          1.1     Load/Save disparameters with  /F fname
;          1.11    bios read signals Dos File End
;          1.2     conin translates Csr keys to WS
;                  time functon and blink attribute
;          zsim12 uploaded to simtel
;          1.21    Option /Snnn to restrain ramdisc size
;          1.22    look first for `COMSPEC` then \COMMAND.COM
;                  free 128K mem for OS-Shell
;                  some bug fixes
;                  do not save cpm_drv to disc parameter file anymore
;          1.3     can be assembled with option to make an interrupt
;                  request with every timer tick
;          1.4     user bios call for file transfer to/from msdos
;                  menu option reset screen
;          1.5     select drive 0 after warm boot from ctrl-brk-menu
;                  menu option swap drives
;                  no longer swap to root dir at os shell
;          1.6     IOBYTE support:
;                    function 6 and 7 can be set to the serial port
;          1.7     option /D to set physical drive
;                  option /H to get option help
;          2.0     Z80 emulation is now 20% faster
;          zsim20 uploaded to simtel
;          2.1     harddisc file C:
;                  work with only one disk drive no longer supported
;          2.2     option /A to start with A: as ramdisc
;                  option /N to start with autologin disabled
;                  combined functions for low level read and write
;          zsim22 uploaded to simtel
;          2.21    took NOP as default opcode to execute at int mode 0
;                  as zsid enables ints at the go command
;                  stored path of swapfile at os-shell, as swapfile
;                  couldn't be reloaded if user changed directory
;                  several bugs removed in Z80 emulations interrupt system
;          2.3     hard coded other screen control codes
;                  option /Vx to select screen ctrl table x
;                  x = k : Kaypro
;                      o : Osborn 1
;                  default: use EMS as Z80 segment
;                  option /X : don't use EMS as Z80 segment
;                  eliminated nasty bug with too small alv space for
;                  ramdisc
;          zsim23 uploaded to simtel
;          2.4     ZSIM finally supports most double sided disc formats
;		   ZSIM loads support files from startup dir
;		   ALT-A - ALT-Z and F1 - F10 can be defined with macros
;          2.41    fixed bug in proc filepunch. create file was called
;                  with invalid file name
;
	


.286


DEFAULT_IOBYTE EQU   10000001b ; lpt0,,,video

CVERSION equ '2.41' ; to display on screen
CVERSNR  equ 24h   ; to return if requested
CVERSDATE equ '15/01/95'
FLCSTR    equ '1990,1995'

; CHANGE THE FELLOWING TO THE PHYSICAL DISK
; THAT YOU WANT TO USE FOR CPM

IFNDEF PHYS_DRV
  PHYS_DRV EQU 0   ; phys number of pc drive to use as CP/M floppy
ENDIF

IF PHYS_DRV GT 1
   %OUT ERROR: PHYSICAL CP/M DRIVE SET TO ILLEGAL VALUE: VALID IS 0 OR 1
   ERR
ENDIF

HARD_DISC_TOO EQU -1
HARD_DISC_SIZE EQU 8192  ; the maximal amount for CP/M 2.2 is 8 M

OVL_SIZE EQU 2000h ; # of paragraphs to swap out at os shell


; IF TIMER_INTS THEN
;    at every PC Timertick a z80 interrupt request is made
; (if default int mode 0 is set execute DATABUS_BTE,
;  elseif int mode 1 do a RST 38H
;  else (int mode 2) call word ptr [I_Reg shl 8 + (DATABUS_BTE shl 1) )
; (Z80 emulator defaults to interrupt DISabled, IM 0)

TIMER_INTS EQU 0
DATABUS_BTE EQU 0H   ; NOP, as secure default

INCLUDE Z80EMU.INC

EXTRN crtout:near,crtin:near,crtinstat:near,reset_crt:near,sel_scr_page:near
EXTRN cursor_off:near,cursor_on:near,select_scr_codes:near
EXTRN Reset_Screen:near

PUBLIC prg_exit,bios88
PUBLIC open_instrtup_dir,create_instrtup_dir 

PUBLIC breakflag


%nosyms        ; keine symbols im listing
warn           ; alle warnings an

DOSSEG   ; UNBEDINGT notwendig, da fuer Speicherverwaltung des Z80 Segments
         ; und der Ramdisk Stacksegment als letztes kommen muss
         ; auch mussen Stack- und Codesegment Klassen 'STACK' bzw.
         ; 'CODE'bekommen

LOCALS   ; fuer locale Labels in Prozeduren

JUMPS    ; automatische Sprunglaengenanpassung

CALLZ macro label ; Call if zero
local exit
      jnz short exit
      call label
exit:
endm

CALLNZ macro label ; Call if not zero
local exit
      jz   short exit
      call label
exit:
endm

CALLC macro label ; Call if carry
local exit
      jnc  short exit
      call label
exit:
endm

MOVZ macro targ,src ; move if zero
local exit
      jnz short exit
      mov targ,src
exit:
endm

MOVNZ macro targ,src ; move if not zero
local exit
      jz short exit
      mov targ,src
exit:
endm


TSTNCLR MACRO FLAG    ; Z-> war nicht gesetzt
        CMP FLAG,FALSE
        MOV FLAG,FALSE
ENDM


;
; segmente des emulators:
;

; emulator_seg   : emulator und bios code
; emudata_seg    : emulator data und z80 bios code
; stack_seg      : emulator und bios stack
; z80cpu_seg     : Segment des emulierten z80, 64k, dummy at 0 (in z80seg_adr)
; ramdisc_seg    : ramdisc fuer cp/m
;


include dos.inc         ; dos functionen

;
; ******* KONSTANTEN ********
;

DEFAULT_DMA        EQU 80H
RECORD_LEN         EQU 80H



ZBIOSLEN           equ (zbiosend-zbiosbeg)
ZBIOSDISPL         equ (0ffffh-ZBIOSLEN) and 0ff00h
; dies bewirkt Start auf neuer Seite, damit Bios auf XX00 anfaengt
ZBIOS              equ offset zbiosbeg+ZBIOSDISPL

CTRL_BREAK_INT EQU 1BH

ESC_KEY            EQU 27

EMU_SCR_PAGE EQU 0
DOS_SCR_PAGE EQU 1

; folgende Konstanten werden als Flags zur Addressberechnung
; im Sektorbuffer benoetigt

READ_OP            EQU 0
WRITE_OP           EQU 0FFH




BDOSLEN            equ 1600h     ; Laenge ccp+bdos cp/m 2.2
BDOS_CHK_SUM       equ 2d88h     ; Checksumme ueber Bdos Copyright Meldung

SYS_FRST_SEC       EQU 28
         ; erster absoluter Sektor, den Bdos auf SysSpuren belegt

SYS_SECS           EQU BDOSLEN/RECORD_LEN ; Anzahl Sektoren, die System belegt

TRACK_BUF_LEN      EQU 10*512

CPM_EOF            EQU 26


RMD                equ 1   ; phys Number of ramdisk IN CP/M
IF HARD_DISC_TOO
HARD_DISC          equ 2   ; phys Number of harddisk IN CP/M
ENDIF



PHYS_SEKLEN        equ 512
NSECTS             equ    2
IF HARD_DISC_TOO
NDISKS             equ    3  ; last disk # +1
ELSE
NDISKS             equ    2  ; last disk # +1
ENDIF
BPS                equ    2

; -------------------------------------
;
; emulator data segment
;
; --------------------------------------


emudata_seg segment para public 'DATA'

include zbios.inc

dpb_cpc     dpb <SPTC,BSHC,BLMC,EXMC,DSMC,DRMC,AL0C,AL1C,CKSC,OFFC>
dpb_dta     dpb <SPTD,BSHD,BLMD,EXMD,DSMD,DRMD,AL0D,AL1D,CKSD,OFFD>
dpb_eig     dpb <SPTD,BSHD,BLMD,EXMD,DSMD,DRMD,AL0D,AL1D,CKSD,OFFD>
dpb_cpm86SS dpb <SPT0,BSH0,BLM0,EXM0,DSM0,DRM0,AL00,AL10,CKS0,OFF0>
dpb_cpm86DS dpb <SPT1,BSH1,BLM1,EXM1,DSM1,DRM1,AL01,AL11,CKS1,OFF1>

ramdisklen dw (?)
lasttrack_rd db (?) ; 0..127  (512K)

PUNCH_BUF_SIZE  equ 64
READER_BUF_SIZE equ 64

ld_rd_string db 27,'E',27,'H','Initializing ramdisc ...',13,10,10,0
ld_hd_string db 'Initializing CP/M harddisc ...',0
start_string db 27,'E',27,'Y',32+6,32+24,27,'p'
             db 'ZSIM'
             db 27,'Y',32+8,32+18
             db 'THE Z80 / CP/M Emulator',27,'q'
             db 27,'Y',32+10,32+18
             db 'Free for personal use'
             db 27,'Y',32+12,32+18
             db 27,'p','USE AT YOUR OWN RISC !',27,'q'
             db 27,'Y',32+15,32+18
             db '(C) '
             db FLCSTR
             db ' by '
             db 27,'Y',32+17,32+18
             db 'Jrgen G. Weber'
             db 27,'Y',32+18,32+18
             db 'Wiesentalstraáe 1'
             db 27,'Y',32+19,32+18
             db 'D-74523 Schw„bisch Hall'
             db 27,'Y',32+20,32+18
             db 'Federal Republic of Germany'
             db 27,'Y',32+24,32+17,27,'p'
             db 'Insert a CP/M disk into drive '
drv_letter   db 'A'
             db ': then press any key'
             db 27,'q'
             db 0


help_str     db 13,10
             db 'ZSIM version '
             db CVERSION
             db ' - Z80 EMULATOR + CP/M 80 BIOS'
             db 13,10,10
             db '(C) 1990,1992,1993,1994,1995 by Jrgen Weber',13,10,10
             db 'Options:',13,10,10
             db '/Dn   n=0,1    Use PC disc drive n',13,10
             db '/F <parmfile>  Use disc parameter file <parmfile>',13,10
             db '/H or /?       Get this help',13,10
             db '/Snnn          Restrain ramdisc size to nnn K',13,10
             db '/C             Create the harddisc file HARDDISC.CPM',13,10
             db '               WARNING: this erases the old one.',13,10
             db '/X             Do not use EMS memory',13,10
             db '/N             Start with autologin disabled',13,10
             db '/A             Start with ramdisc=A:',13,10
             db '/Vx            Use terminal control sequences x',13,10
             db '               x=k : Kaypro = ADM 3',13,10
             db '               x=o : Osborn I',13,10
             db 0

help_str_end equ $

db 'ZSIM is copyrighted by Jrgen Weber but'
db ' it looks for the fellowing string: '

bdos_str db '        COPYRIGHT (C) 1979, DIGITAL RESEARCH  '
bdos_str_end equ this byte

cpmsys_fileName  db 'CPMSYS.CPM',0
ramdisc_fname    db 'RAMDISC.CPM',0
harddisc_fname   db 'HARDDISC.CPM',0
punch_fileName   db 'PUNCH.CPM',0
reader_fileName  db 'READER.CPM',0

emmstr           db 'EMMXXXX0'
emmstrend equ $

ems_handle dw 0
use_ems_flg  db TRUE
fmt_file_flg db FALSE
new_hdsk_flg db FALSE
fmt_file_name db 'FORMATS\'
fmt_file_bsename  db 14 dup (?)
file_promt_str db 'Enter Disk Parameter filename: ',0
file_promt_str_end equ $


menutab label word

     dw m_esc

     dw m_continue
     dw m_save_rd_quit
     dw m_quit
     dw m_save_rd
     dw m_del_pf
     dw m_init_rdr
     dw m_shell
     dw m_edit_disk_parm
     dw m_save_d_parm
     dw m_load_d_parm
     dw m_login_disc
     dw m_swap_drives
     dw m_reset_scr
     dw m_w_boot
     dw m_info

MESSAGE_COUNT=($-menutab)/2 - 1 ; ESC has no entry string

; remember to set on entry in menutab for every menu_str
menu_strs    db '\Continue','|'
             db 'Save \Ramdisc/Quit','|'
             db '\Quit','|'
             db '\Save Ramdisc','|'
             db '\Delete Punch File','|'
             db '\Init Reader','|'
             db '\OS Shell','|'
             db 'Disk \Parameters','|'
             db 'Sa\ve Parameters','|'
             db '\Load Parameters','|'
             db 'Logi\n Disc','|'
             db 'Swap Driv\es','|'
             db 'Rese\t Screen','|'
             db '\Warm Boot','|'
             db '\About','|'
             db 0
menu_strs_end equ this byte

MESG_ESC equ 0

LONGEST_MESSAGE=20


prg_ext_box_res  dw 0ffffh


disk_err_txt db 'Disk Error '
disk_err_num db 2 dup (?)
             db 'H, Class '
disk_err_cls db 2 dup (?)
             db 'H. Press ESC|',0
disk_err_txt_end equ this byte


f_not_found_txt db 'File not found. Press ESC|',0
f_not_found_txt_end equ this byte

no_mem_txt db 'Not enough memory to shell. Press ESC|',0
no_mem_txt_end equ this byte

on_sign_string equ this byte
                   db 27,'E',27,'H'
                   db 'jgw 64K cp/m 80 bios ver '
                   db CVERSION
                   db ' --  '
                   db CVERSDATE
                   db '  (C) '
                   db FLCSTR
                   db ' by Jrgen G. Weber'
                   db 13,10,10
                   db 'BDOS: ',27,'j',0
bad_format         db 13,10
                   db 'Unknown disc format. Insert new disc into drive '
drv_letter1        db 'A'
                   db ': and press any key'
                   db 13,10,0
no_sys             db 13,10
                   db 'Could not read System sectors. Insert'
                   db ' new disc and press any key'
cr_lf_txt          db 13,10,0
control_c_txt      db '^C...',0

mfulstr db 13,10,'Not enough memory',13,10
mfsend equ this byte

illparm_str db 13,10,'Illegal Command Line Parameter',13,10
illparm_strend equ this byte

illdriv_str db 13,10,'CP/M drive my not be current MS-Dos drive.',13,10
illdriv_strend equ this byte

dma_txt db 13,10
        db 'Fatal: DMA Boundary Crossing'
        db 13,10
dma_txt_end equ this byte

exit_str db 'Type EXIT to return to CP/M Emulator ...',13,10
exit_str_end equ this byte


exec_par_block equ this word
       dw 0
       dw offset exec_cmd_line
       dw seg    exec_cmd_line
       dd 0
       dd 0
exec_cmd_line equ this word
       db ec_str_end-ec_str
ec_str db ''
ec_str_end equ this byte
       db 13

exec_usr_flg db FALSE

exec_usr_lne equ $
db 0
 db '/C '
usr_lne equ $
db 80 dup (?)


no_cc_str  db 13,10,'COMMAND.COM not found. Press ESC.',13,10
no_cc_str_end equ this byte

tempfilename db 'ZSIM.TMP',0	
exec_fname db '\COMMAND.COM',0
comspec db 80 dup (?)

var_comspec db 'COMSPEC='
var_comspec_len equ $-var_comspec

good_dpb_edit db (?)
db 2 dup (?)

breakflag  db FALSE

TRACK_TRANS_MASK EQU 00000111B
COMPLEMENT_MASK  EQU 00001000B
SECTR_DOUBL_MASK EQU 00110000B
SECTR_ID_MD_MASK EQU 11000000B

NO_TRACK_TRANS EQU 0
SIDE_TRANS     EQU 1
CYLINDER_TRANS EQU 2
EAGLE_TRANS    EQU 3


old_int1b  dd (?)
stackpoi   dw (?)

sp_save    dw (?)
ss_save    dw (?)

z80seg_adr dw (?)
rdseg_adr  dw (?)
rdlen      dw (?)
max_rd_size dw 1024   ; default auf maximal
exec_mem_start dw (?)

default_dta dd (?)
psp_adr    dw (?)
prog_len   dw (?)
mem_end    dw (?)
prg_end    dw (?)
ovl_base   dw (?)

hd_drive_flag db (?)
ms_current_drive db (?)

tmp_word        dw (?)

z80_pc             dw ?
ccp_adr            dw ?
bdos_adr           dw ?
bios_adr           dw ?

month_tab db 31,28,31,30,31,30,31,31,30,31,30,31

; flag, dass im Speicher noch ein nicht auf disk geschriebener
; nicht-dir Track ist
write_flag         db FALSE

in_boot_flag       db FALSE

spec_scr_flg       db 0 ; bit 0: kaypro

home_flag          db FALSE

trck_loc struc
   cylndr  db ?
   cyl_sde db ?
trck_loc ends

last_track_read    trck_loc <0ffh,0>  ; default unmoeglich, noch nicht gelesen
last_track_written trck_loc <0,0>
dirtrack           trck_loc <0,0>
cphys_track  	   trck_loc <0,0>;  current physical track
r_track            trck_loc <0,0>
w_track            trck_loc <0,0>

track              db 0,0
sector             db 0,0


ms_phys_sec_len    db (?)
pspt_last          db 9
phys_tracks_last   db 40
frstps_last        db 041h
currnt_first_Psec  db (?)
dirtr_last         trck_loc <2,0>
last_sect_of_side  db 0	
dpb_last           dw offset dpb_cpc

drv_swap_flag      db FALSE

; ACHTUNG: die Reihenfolge der folgenden Vars nicht
;          ver„ndern, da sie so ans Modula UP bergeben werden

cpmdrv_set_flg     db FALSE
cpm_drive          db PHYS_DRV
	
first_phys_sec     db (?)
phys_tracks        db (?)
phys_sec_pt        db (?)
cpm_phys_sec_len   db 2
flag_byte          db 0
autologin_flag     db TRUE

; end Reihenfolge wichtig


retry_count        db 5
; side               db 0

dmaad              dw ?          ;direct memory address
diskno             db 0          ;disk number 0-15


punch_buf_entries  dw (?)
punch_buf_ptr      dw (?)

reader_empty_flag  db TRUE
reader_buf_entries dw (?)
reader_buf_ptr     dw (?)
reader_file_pointer dw 0,0

new_dosfilespecflg db (?)
openin_handle      dw 0
openout_handle     dw (?)
hd_handle          dw 0          ; default: none
dosfilespec        db 80 dup (?)

basenameptr	   dd far ptr 0:0
	
; zeigt auf Bufferadresse des Tracks, der geschrieben werden soll
outbuf_ptr         dw (?)

cpm_bdos_buf db BDOSLEN dup (?)

dirtrbuf db TRACK_BUF_LEN dup (?)
secbuf   db TRACK_BUF_LEN dup (?)
wrtbuf   db TRACK_BUF_LEN dup (?)

startdir db 100 dup (?)
	
punch_buf  db PUNCH_BUF_SIZE dup (?)
reader_buf db READER_BUF_SIZE dup (?)

; dass die Buffer in der EXE-Datei erscheinen, laesst sich
; durch die Verwendung einer Gruppe mit den Buffern als
; Extra Segment umgehen
; jedoch muss dann bei jedem offset der Gruppenname angegeben werden

emudata_seg ends

; ------------------------

DATA segment para public 'DATA'
     ; Dummysegment zur Kombinierung mit Modula 2
DATA ends

STACK_SIZE EQU 1000H

stack_seg  segment para stack 'STACK'
      dw STACK_SIZE dup (?)            ; Stack ist recht gross
                                  ; wegen Modula 2 UPs
stack_seg  ends

z80cpu_seg segment at 0 ; dummy
z80cpu_seg ends

; -------------------------------------
; emulator und bios segment start
; -------------------------------------

invalidate_rdbuf macro
       mov  last_track_read.cylndr,0ffh
endm

emulator_seg segment para public 'CODE'

assume  cs:emulator_seg,ds:emudata_seg,es:z80cpu_seg,ss:stack_seg

m2_ds  dw (?)                          ; fmodula routinen exspect
                                       ; bei cs:0 ihr DS


init proc                              ; emulator start

       mov  ax,emudata_seg

       mov  ds,ax            ; damit Vars angesprochen werden koennen

       mov  stackpoi,sp

       mov bx,ss             ; finde Programmende
       mov ax,sp
       mov cl,4
       shr ax,cl             ; ax:=4
       add bx,ax             ; bx=>programm ende
       inc bx                ; vorsichtshalber

       mov prg_end,bx

       push bx
       mov  ax,es            ; es:0 => PSP
       mov  psp_adr,ax
       mov  bx,ax

       mov si,0
       mov ax,[es:si+2]      ; ax=> mem end
       mov  mem_end,ax
       push ax
       sub  ax,OVL_SIZE
       mov  ovl_base,ax      ; -> dos_shell
       pop  ax
       push ax
       sub  ax,bx            ; mem end - prg start
       mov  prog_len,ax
       pop  ax               ; mem end
       pop  bx               ; prg end

       sub ax,bx             ; ax:=free mem
       mov  cx,bx            ; cx:=bx=start free mem

       pusha
       call parse_cmd_line
       mov dx,offset illparm_str
       mov cx,illparm_strend-illparm_str
       popa
       jc abort              ; Fehler: falscher Parameter

        cmp use_ems_flg,TRUE
        mov  dx,1000h     ; = (10000h / 16) = 64K in Paragraphs
        jnz short @@no_ems

        pusha
        mov ax,4             ; get 4 pages
        call get_emm_seg
        mov z80seg_adr,ax
        cmp ax,0
        popa
        jz short @@no_ems

        mov  cx,z80seg_adr
        mov  dx,0

@@no_ems:

       mov  z80seg_adr,cx
       add  bx,dx
       mov  rdseg_adr,bx

       sub  ax,dx          ; - Z80 seg len
       jc   @@memful

       mov  ramdisklen,ax

       PUSHR <es,bx>
       DOS GET_DTA
       mov word ptr default_dta,bx
       mov bx,es
       mov word ptr default_dta+2,bx
       POPR <bx,es>

       DOS  GET_DISK_DRIVE
       mov  ms_current_drive,al
       call get_cpm_drive
       cmp  ms_current_drive,al
       mov dx,offset illdriv_str
       mov cx,illdriv_strend-illdriv_str
       je short abort              ; error: dosdrive equals cp/m drive


mov ax,440dh
mov bl,2
mov ch,8
mov cl,0
; int 21h ioctrl lock drive does not work ??

       mov  bx,offset ld_rd_string
       call puts

       call init_ramdisc

       mov  bx,offset ld_hd_string
       call puts

       call init_harddisc
       call get_cpm_drive
       add al,'A'
       mov drv_letter,al
       mov drv_letter1,al       ; 2 times needed

       mov  bx,offset start_string
       call puts
       call crtin            ; wait for keypressed

       call patch_int

       mov  ax,z80seg_adr
       call clearz80         ; loesche z80 mem

       call patch_box

; physikalische Sektorlaenge merken

      push es
      mov  ax,0
      mov  es,ax
      les  di,[es:78h]
      mov  al,es:[di+3]
      pop  es
      mov  ms_phys_sec_len,al


;
; offset z80 bios start merken
;
       mov bios_adr,ZBIOSDISPL          ; bios Sprungleiste einrichten

       call z80ini                      ; reset z80 cpu

       mov ax,z80seg_adr
       mov es,ax
       mov ds,ax
;
; weiter mit cp/m cold boot
;
       mov al,0
       jmp bios88


@@memful:
       mov cx,mfsend-mfulstr
       mov dx,offset mfulstr
abort:
       mov bx,STDERR
       DOS WRITE_TO_HANDLE
       call release_emm_pages
       mov al,1
       DOS TERMINATE_EXE
init   endp

get_comspec proc
; on entry: ds->psp
; si=seg data !

        PUSHR <ds,es,si,di>
        mov ax,[ds:2ch]
        mov ds,ax
        mov es,si
        mov bx,0
@@loop:
        mov si,bx		; ds:si
        inc bx
        mov di,offset var_comspec	; es:di
        mov cx,var_comspec_len
        repz cmpsb
        jcxz short @@found
        cmp word ptr [ds:si-1],0
        jnz @@loop
@@exit:
	add si,3
	;;  now we should be at argv[0]
	;;  search for end of string and go back to last backslash
	push si
@@l1:	
	lodsb
	or al,al
	jnz @@l1
	;; now go back to last backslash
	std
@@l2:		
	lodsb
	cmp al,'\'
	
	jnz @@l2
	cld
	inc si
	inc si
	mov byte ptr [ds:si],0	; so strcpy will end
	pop si

		;; 	ds:si = source, es:di = target

	mov ax,es
	seges
	mov word ptr basenameptr+2,ax

	mov di,offset startdir
	call strcpy
	dec di
	seges
	mov word ptr basenameptr,di

        POPR <di,si,es,ds>
       ret
@@found:
        mov di,offset comspec
        call strcpy
	dec si
	mov bx,si
        jmp @@loop
get_comspec endp

get_startup_path proc
get_startup_path endp


parse_cmd_line proc
LOCAL  dsseg:WORD = AUTO_SIZE
       push bp
       mov  bp,sp
       sub  sp,AUTO_SIZE
       PUSHR <ax,bx,cx,di,si,ds,es>


        mov fmt_file_flg,FALSE
        mov ax,ds
        mov dsseg,ds
        mov si,ax
        mov es,ax             ; es:=prog vars
        mov ax,psp_adr
        mov ds,ax

        call get_comspec

        mov di,offset fmt_file_bsename
        mov si,80h            ; si => Kommandozeilenparam
        lodsb                 ; count
        mov bh,0
        mov bl,al
        mov [si+bx],bh        ; 0
        or  al,al
        mov al,FALSE
        jz  @@parm_done
@@findslash:
; don't forget string help_str
        lodsb
        or al,al
        jz  @@parm_done
        cmp al," "
        jz short @@findslash     ; +DEC CX
        cmp al,"/"
        jz  short @@good_sep
        cmp al,"-"
        jnz @@parm_err
@@good_sep:
        lodsb
        cmp al,'?'
        jz helpnabort
        and al,not ('a'-'A')  ; toupper
        cmp al,'H'
        jz helpnabort


        cmp al,'X'
        jnz short @@use_ems
        mov bx,offset use_ems_flg
        mov byte ptr [es:bx],FALSE
        jmp @@findslash

@@use_ems:

        cmp al,'C'
        jnz short @@no_create
        mov bx,offset new_hdsk_flg
        mov byte ptr [es:bx],TRUE
        jmp @@findslash
@@no_create:
        cmp al,'N'
        jnz short @@no_no_autolog
        mov bx,offset autologin_flag
        mov byte ptr [es:bx],FALSE
        jmp @@findslash
@@no_no_autolog:
        cmp al,'V' ; select video param table
        jnz short @@no_sel_vid
        lodsb
        and al,not ('a'-'A')  ; toupper
        cmp al,'K' ; Kaypro
        mov bx,offset spec_scr_flg
        jnz short @@no_kay_par
        or byte ptr [es:bx],1
@@no_kay_par:
        cmp al,'O'
        jnz short @@no_os_par
        or byte ptr [es:bx],2
@@no_os_par:
@@no_sel_vid:
        cmp al,'A'
        jnz short @@no_swap
        mov bx,offset drv_swap_flag
        mov byte ptr [es:bx],TRUE
        jmp @@findslash
@@no_swap:
        cmp al,'F'
        jz  short @@parm_file
        cmp al,'D'
        jz  short @@phys_drive
        cmp al,'S'
        jnz @@parm_err

        call @@parse_num
        cmp ax,640
        ja @@parm_err
        cmp ax,64
        jb short @@parm_err
        mov bx,offset max_rd_size
        mov [es:bx],ax
        jmp @@findslash
@@parm_file:
        mov di,offset fmt_file_bsename
@@SKIP_BLN:
        lodsb
        cmp al," "
        jz @@SKIP_BLN; +DEC CX
        stosb         ; 1. non-blank
@@cp_loop:
        lodsb
        stosb
        or al,al
        jz  short @@name_done
        cmp al,' '
        jz  short @@name_done
        cmp al,13
        jz  short @@name_done
        jmp @@cp_loop
@@name_done:
        dec si
        mov al,0
        stosb         ; Filename Endzeichen
        mov di,offset fmt_file_flg
        mov byte ptr es:[di],TRUE
        jmp @@findslash
@@phys_drive:
        lodsb
        cmp al," "
        jz @@phys_drive ; +DEC CX
        sub al,'0'
        jc short @@parm_err
        cmp al,1
        ja short @@parm_err
        push ds
        mov  ds,dsseg
        call set_cpm_drive
        mov  cpmdrv_set_flg,TRUE
        pop  ds
        jmp @@findslash

@@parse_num:
        mov bx,10
        mov ax,0
        mov dh,0
@@addloop:
        mov dl,[si]
        or dl,dl
        jz  short @@done
        inc si
        sub dl,'0'
        jc short @@done
        cmp dl,9
        ja short @@done
        push dx
        mul bx
        pop dx
        add ax,dx
        jmp short @@addloop
@@done:
        ret

@@parm_done:
       POPR <es,ds,si,di,cx,bx,ax>
       clc
       add  sp,AUTO_SIZE
       pop bp
       ret
@@parm_err:
       POPR <es,ds,si,di,cx,bx,ax>
       stc
       add  sp,AUTO_SIZE
       pop bp
       ret
parse_cmd_line endp

helpnabort proc
       mov  ax,emudata_seg
       mov  ds,ax
       mov cx,help_str_end-help_str
       mov dx,offset help_str
       jmp abort
helpnabort endp

tst_hd_drive proc
       PUSHR <ax,dx>
       mov hd_drive_flag,FALSE
       call get_cpm_drive
       mov dl,al
       mov ah,15h                 ; Laufwerktyp ?
       int 13h
       jc  short @@exit           ; PC,XT
       cmp ah,2                   ; erkennt DiskWechsel
       jnz short @@exit
       mov hd_drive_flag,TRUE
@@exit:
       POPR <dx,ax>
       ret
tst_hd_drive endp

;
; der Interupt 1bH (Control-Break Interupt) muá auf eine Routine
; gelegt werden, die ein Abbruch Flag setzt
; Dieses wird dann bei jedem Bios Aufruf ausgewertet
;
patch_int proc
       PUSHR <ds,es,ax,bx,dx>
       mov al,CTRL_BREAK_INT
       DOS GET_VECTOR
       mov word ptr old_int1b,bx
       mov bx,es
       mov word ptr old_int1b+2,bx
       mov  dx,offset new_int1b
       push cs
       pop  ds
       mov al,CTRL_BREAK_INT
       DOS SET_VECTOR
if TIMER_INTS
       mov al,1cH  ; timer
       DOS GET_VECTOR
       mov word ptr cs:old_int1C,bx
       mov bx,es
       mov word ptr cs:old_int1C+2,bx
       mov  dx,offset new_int1C
       push cs
       pop  ds
       mov al,1cH
       DOS SET_VECTOR
endif
       POPR <dx,bx,ax,es,ds>
       ret
patch_int endp

restore_int proc
       push ds
       lds dx,old_int1b
       mov al,CTRL_BREAK_INT
       DOS SET_VECTOR
if TIMER_INTS
       lds dx,cs:old_int1C
       mov al,1Ch
       DOS SET_VECTOR
endif
       pop ds
       ret
restore_int endp

new_int1b proc
       push ax
       push ds
       mov  ax,emudata_seg
       mov  ds,ax
       cmp byte ptr cs:in_emu_flg,TRUE
       jnz short @@not_in_emu
       call ctrl_break_req
       jmp short @@exit
@@not_in_emu:
       mov  breakflag,TRUE   ; nur wenn gerade bios code abgearbeitet wird
@@exit:
       pop  ds
       pop  ax
       iret
new_int1b endp

if TIMER_INTS

old_int1C  dd (?)

; at every timer tick do an interrupt request

new_int1C proc
       cmp byte ptr cs:in_emu_flg,TRUE
       push ax
       mov al,DATABUS_BTE
       CALLZ interrupt_request    ; only if Z80 is running
       pop ax
       jmp dword ptr cs:[old_int1C]
new_int1C endp

endif ; TIMER_INTS

; wird auch von Z80EMU.OP76 HALT aufgerufen

prg_exit proc FAR
       PUSHR <ds,es,ax>
       PUSHR <bx,cx,dx,si>
       mov  ax,emudata_seg
       mov  ds,ax
       mov  ax,z80seg_adr
       mov  es,ax
       call restore_int
       mov  breakflag,FALSE

       call wrt_out_punch

       mov  cx,menu_strs_end-menu_strs   ; high(messages)
       mov  si,offset menu_strs          ; offs(messages)

       call box_call
       sal ax,1
       mov di,ax
       call menutab[di]

       call patch_int
       POPR <si,dx,cx,bx>
       POPR <ax,es,ds>
       ret
prg_exit endp


; Men durch ESC abgebrochen
m_esc proc
       ret
m_esc endp

; Menpunkt Emulation fortfahren
m_continue proc
      ret
m_continue endp

; Menpunkt Quit
m_quit proc
; etwaige Zeichen im Punch Buffer noch ausschreiben
       call wrt_out_punch

       mov cl,31                  ; GotoXY(1,25)
       call crtout
       mov cl,1
       call crtout
       mov cl,25
       call crtout
       mov  al,0
ABORT_EMU:
       call release_emm_pages
       DOS TERMINATE_EXE
m_quit endp

; Menpunkt Ramdisk speichern
m_save_rd proc
      call save_ramdisc
      ret
m_save_rd endp

; Menpunkt Ramdisk speichern + Quit
m_save_rd_quit proc
       call save_ramdisc
       jmp short m_quit
m_save_rd_quit endp

; Menpunkt Punch buffer file l”schen
m_del_pf proc
     call del_punch_file
     ret
m_del_pf endp
; Menpunkt Reader initialisieren
m_init_rdr proc
      call reset_reader_buf
      ret
m_init_rdr endp

; Menpunkt OS Shell
m_shell proc
       call dos_shell
       ret
m_shell endp

update_disk_pars proc
      PUSHR <AX,BX>
       mov  ah,0
       mov  al,dpb0.off-dpb0.spt  ; off
       call get_dpb_entrie        ; nach bx
       mov  dirtrack.cylndr,bl
       mov  dirtrack.cyl_sde,0    ; dir is always side 0
      POPR <BX,AX>
      ret
update_disk_pars endp

; Menpunkt Diskettenparameter editieren
m_edit_disk_parm proc
       call edit_disk_pars
       cmp  good_dpb_edit,1
       jnz  short @@exit
       call update_disk_pars

;       cmp  in_boot_flag,TRUE
;       jz   short @@exit          ; mit boot weitermachen
       call m_w_boot
@@exit:
       ret
m_edit_disk_parm endp

prepare_fname proc
       mov  di,offset fmt_file_bsename
       push di
       mov  cx,80
       mov  si,offset file_promt_str
       mov  bx,file_promt_str_end-file_promt_str
       call input_string
       pop  bx
       or   byte ptr [bx],0          ; Test ob was eingegeben, nein => Z
       ret
prepare_fname endp

; Menpunkt Diskettenparameter speichern
m_save_d_parm proc
       call prepare_fname
       jz short @@exit
       call save_disk_pars
@@exit:
       ret
m_save_d_parm endp

; Menpunkt Diskettenparameter laden
m_load_d_parm proc
       call prepare_fname
       jz short @@exit
       call load_disk_pars
       jmp  short m_w_boot
@@exit:
       ret
m_load_d_parm endp

; Menpunkt CP/M warm boot durchfhren
m_w_boot proc
       mov sp,stackpoi
       call patch_int
       mov ax,z80seg_adr
       mov es,ax
       mov di,CDISK
       and byte ptr es:[di],11110001b   ; select sensible disk, keep user
       call init_iobyte
       mov al,1
       cmp  in_boot_flag,TRUE
       MOVZ al,0 ;       jz   short @@exit          ; mit boot weitermachen
       jmp bios88
m_w_boot endp

m_login_disc proc
       push word ptr autologin_flag
       mov  autologin_flag,TRUE
       call login_disc
       invalidate_rdbuf
       pop  word ptr autologin_flag
       ret
m_login_disc endp

; Menpunkt About Author
m_info proc
       call about_author
       ret
m_info endp

m_swap_drives proc
       xor drv_swap_flag,TRUE
       jmp  short m_w_boot
m_swap_drives endp

; Menpunkt Reset Screen
m_reset_scr proc
        call Reset_Screen
        ret
m_reset_scr endp


; Fehler durch Ueberschreiten der DMA Segment Grenze
; siehe c't 4/90 S.412

dma_bound proc
       mov bx,STDERR
       mov cx,dma_txt_end-dma_txt
       mov dx,offset dma_txt
       DOS WRITE_TO_HANDLE
       mov al,1
       jmp ABORT_EMU
dma_bound endp

disp_disk_err proc
       pushf                      ; cy aufheben
       PUSHR <si,cx,dx,bx>
        PUSHR <DI,BP,ES,DS>
        DOS GET_EXTENDED_ERRORS
        POPR <DS,ES,BP,DI>
        mov  bx,offset disk_err_num
        call hexbyte
        mov  al,bh
        mov  bx,offset disk_err_cls
        call hexbyte
       mov si,offset disk_err_txt
       mov cx,disk_err_txt_end-disk_err_txt
       call box_call
       POPR <bx,dx,cx,si>
       popf
       ret
disp_disk_err endp

disp_f_not_found proc
       pushf                      ; cy aufheben
       PUSHR <si,cx>
       mov si,offset f_not_found_txt
       mov cx,f_not_found_txt_end-f_not_found_txt
       call box_call
       POPR <cx,si>
       popf
       ret
disp_f_not_found endp

disp_no_mem proc
       PUSHR <si,cx>
       mov si,offset no_mem_txt
       mov cx,no_mem_txt_end-no_mem_txt
       call box_call
       POPR <cx,si>
       ret
disp_no_mem endp


hexbyte    PROC
; in: bx-> store for hexbyte
;     al=hexbyte
          push ax
          shr al,1                   ;extract high nibble
          shr al,1
          shr al,1
          shr al,1
          call nib
          pop ax
          and al,0fh
nib:
          add al,90h                 ;special hex conversion sequence
          daa                        ;using ADDs and DAA's
          adc al,40h
          daa                        ;nibble now converted to ASCII
          mov [bx],al
          inc bx
          ret
hexbyte ENDP


EXTRN EmuMenu_DoMenue:far,EmuMenu_EditDPB:far
EXTRN EmuMenu_InputString:far,EmuMenu_About:far,EmuMenu_init:far

; fmodula2 laedt merkwuerdigerweise am Anfang DS aus CS:0
; also muss man dort ds hinbringen
; Trotzdem sollte vor Aufruf einer Funktion DS richtig
; geladen sein.

patch_box proc
       mov  ax,DATA
       mov  [cs:0],ax
       ret
patch_box endp

box_call proc                     ; um Modula 2 UPs aufzurufen
;
; in:  ds:si => menuetext
;      cx    =  len(menuetext)
; out: ax = result
;
; auf dem STACK muss GENUG FREI sein, um zu erzeugendes WINDOW
; ABZUSPEICHERN
;
COMMENT @

DEFINITION MODULE EmuM2;

FROM SYSTEM IMPORT BYTE,WORD;

TYPE PhysDiskPars = RECORD
           cpm_drive       : BYTE;
           first_phys_sec  : BYTE;
           phys_tracks     : BYTE;
           phys_sec_pt     : BYTE;
           bytes_per_sec   : BYTE;
           flag_byte       : BYTE;
           autologin_flag  : BYTE;
       END;

dpb = RECORD
             spt  : WORD;
             bsh  : BYTE;
             blm  : BYTE;
             exm  : BYTE;
             dsm  : WORD;
             drm  : WORD;
             al0  : BYTE;
             al1  : BYTE;
             cks  : WORD;
             off  : WORD;
           END;
     DPBPtr = POINTER TO dpb;
     PDPPtr = POINTER TO PhysDiskPars;


PROCEDURE DoMenue(x,y:CARDINAL;messages:ARRAY OF CHAR;
                  VAR WinSave:ARRAY OF CHAR;VAR res:CARDINAL);
(* Pop Up Menue, obere linke Ecke des Rahmens bei x,y.
   messages = String mit darzustellenden Menuepunkten; Stringende = 0C.
   Menuepunkte werden durch | getrennt, auch nach letztem Menuepunkt
   MUSS | stehen.
   Jeder Menuepunkt darf durch Druecken eines einzigen Zeichens angewaehlt
   werden, das hell dargestellt wird. Diesem Zeichen muss der Backslash \
   vorangestellt werden.
   Es duerfen maximal 20 Menuepunkte sein.
   WinSave muss genug Platz enthalten, um Hintergrund + dessen Attribute
   abzuspeichern, = (Laengster Menuepunkt+2)*2*(Menuepunkte+2)
*)

PROCEDURE EditDPB(p:DPBPtr;q:PDPPtr;
                  VAR WinSave:ARRAY OF CHAR;VAR OK:BOOLEAN);
(* Len WinSave=20*18*2=720 *)

PROCEDURE InputString(VAR WinSave,s,p:ARRAY OF CHAR);
(* Es wird vorrausgesetzt, daá prompt p < 30 und string s < 30 *)


PROCEDURE About(VAR WinSave:ARRAY OF CHAR);
(* Author ausgeben *)


END EmuM2.


@

HIGH_WinSave=(LONGEST_MESSAGE+2)*2*(MESSAGE_COUNT+2)

IF HIGH_WinSave gt 2*STACK_SIZE/3
   %out WARNING: POPUP WINDOW REQUIRES MUCH OF STACK
ENDIF

       PUSHR <bx,cx,dx,di,si,bp,ds,es>
       sub  sp,HIGH_WinSave
       mov  bx,sp
       mov  ax,20
       push ax                 ; x
       mov  ax,7
       push ax                 ; y
       push cx                 ; high(messages)
       push ds                 ; seg(messages)
       push si                 ; offs(messages)
       mov  cx,HIGH_WinSave
       push cx                 ; high(WinSave)
       push ss                 ; seg(WinSave)
       push bx                 ; offs(WinSave)
       mov  ax,seg prg_ext_box_res
       push ax                 ; seg(prg_ext_box_res)
       mov  ax,offset prg_ext_box_res
       push ax                 ; offs(prg_ext_box_res)
;box_adr equ this byte
       mov  ax,DATA
       mov  ds,ax
       call EmuMenu_DoMenue
       add  sp,HIGH_WinSave
       POPR <es,ds>
       mov  ax,prg_ext_box_res
       POPR <bp,si,di,dx,cx,bx>
       ret
box_call endp


edit_disk_pars proc

HIGH_WinSave=800

IF HIGH_WinSave gt 2*STACK_SIZE/3
   %out WARNING: POPUP WINDOW REQUIRES MUCH OF STACK
ENDIF
       PUSHR <bx,cx,dx,di,si,bp,ds,es>
       sub  sp,HIGH_WinSave
       mov  bx,sp
       mov  ax,z80seg_adr
       push ax                 ; seg(p)  p:DPBPtr
       mov  ax,offset dpb0+ZBIOSDISPL
       push ax                 ; offs(p)
       mov  ax,seg cpm_drive
       push ax                 ; seg (q) q:PDPPtr;
       mov  ax,offset cpm_drive
       push ax                 ; offs (q)
       mov  cx,HIGH_WinSave
       push cx                 ; high(WinSave)
       push ss                 ; seg(WinSave)
       push bx                 ; offs(WinSave)
       mov  ax,seg good_dpb_edit
       push ax                 ; seg(ok)
       mov  ax,offset good_dpb_edit
       push ax                 ; offs(ok)
       mov  ax,DATA
       mov  ds,ax
       call EmuMenu_EditDPB
       add  sp,HIGH_WinSave
       POPR <es,ds,bp,si,di,dx,cx,bx>
       ret
edit_disk_pars endp

;PROCEDURE InputString(VAR WinSave,s,p:ARRAY OF CHAR);

input_string proc   ; ds:si = prompt, bx = len prompt
                    ; ds:di = string  cx = len string
LOCAL  prompt_str,lenpr,in_str,lenstr:WORD = AUTO_SIZE
       push bp
       mov  bp,sp
       sub  sp,AUTO_SIZE
       mov  prompt_str,si
       mov  in_str,di
       mov lenpr,bx
       mov lenstr,cx
HIGH_WinSave=800

IF HIGH_WinSave gt 2*STACK_SIZE/3
   %out WARNING: POPUP WINDOW REQUIRES MUCH OF STACK
ENDIF
       PUSHR <bx,cx,dx,di,si,bp,ds,es>
       sub  sp,HIGH_WinSave
       mov  bx,sp
       mov  ax,z80seg_adr

       mov  cx,HIGH_WinSave
       push cx                 ; high(WinSave)
       push ss                 ; seg(WinSave)
       push bx                 ; offs(WinSave)

       push lenstr             ; high(s)
       push ds                 ; seg(s)
       push in_str             ; offs(s)
       push lenpr              ; high(p)
       push ds                 ; seg(p)
       push prompt_str         ; offs(p)
       mov  ax,DATA
       mov  ds,ax
       call EmuMenu_InputString
       add  sp,HIGH_WinSave
       POPR <es,ds,bp,si,di,dx,cx,bx>
       mov  sp,bp
       pop  bp
       ret
input_string endp

open_instrtup_dir proc
	;; in:	al: access mode
	mov ah,OPEN_FILE
	jmp short strtup_openfile
open_instrtup_dir endp
	
create_instrtup_dir proc
	;; in:	cx: create mode
	mov ah,CREATE_FILE
	jmp short strtup_openfile
create_instrtup_dir endp

delete_instrtup_dir proc	
       mov dx,offset startdir
       DOS  DELETE_FILE
	ret
delete_instrtup_dir endp
	
strtup_openfile	proc
	;; open or create file in startupdir of ZSIM
	;; in:	ds:si -> filename
	;; ah:	open or create code
	;; out:	ax:	handle or cy set if error

	PUSHR <ds,es,di,dx>
	push cx
	push ax
	
	mov ax,seg basenameptr
	mov es,ax
	seges
	les di,basenameptr

	
	call strcpy
	mov ax,seg startdir
	mov ds,ax
	mov dx,offset startdir

	pop ax
        pop cx
        int 21H

	POPR <dx,di,es,ds>
	ret
strtup_openfile	endp
	

load_disk_pars proc
LOCAL  handle:WORD = AUTO_SIZE
       push bp
       mov  bp,sp
       sub  sp,AUTO_SIZE
       call @@fopen
       jnc  short @@ok
       cmp  ax,2
       CALLZ disp_f_not_found
       jc  short @@exit           ; nicht da
@@ok:
       call @@fread
       call @@fclose
       call update_disk_pars
       mov  autologin_flag,FALSE
@@exit:
       mov  sp,bp
       pop  bp
       ret

@@fopen:
       mov si,offset fmt_file_name
       mov al,0
       call open_instrtup_dir
       mov handle,ax
       ret

@@fclose:
       mov bx,handle
       DOS CLOSE_FILE
       CALLC disp_disk_err
       ret

@@fread:
       mov dx,offset tmp_word
       mov bx,handle
       mov cx,2
       DOS READ_FROM_HANDLE
       CALLC disp_disk_err
       mov bx,dx
       mov bx,[bx]
       cmp bx,'WJ'
       jnz  short @@ldexit         ; falscher Kenncode <> 'JW'

       ; cp/m Parameter laden

       mov dx,offset offset dpb0+ZBIOSDISPL
       mov bx,handle
       mov cx,size dpb
       push ds
       mov ax,z80seg_adr
       mov ds,ax
       DOS READ_FROM_HANDLE
       pop ds
       CALLC disp_disk_err

       ; physikalische Parameter laden

       mov dx,offset first_phys_sec
       mov bx,handle
       mov cx,autologin_flag-first_phys_sec
       DOS READ_FROM_HANDLE
       CALLC disp_disk_err

@@ldexit:
     ret
load_disk_pars endp

save_disk_pars proc
LOCAL  handle:WORD = AUTO_SIZE
       push bp
       mov  bp,sp
       sub  sp,AUTO_SIZE
       call @@fopen
            jc short @@exit
       call @@fwrite
       call @@fclose
@@exit:
       mov  sp,bp
       pop  bp
       ret


@@fopen:
       mov cx,0
       mov si,offset fmt_file_name
	call create_instrtup_dir
	CALLC disp_disk_err
       mov handle,ax
       ret

@@fclose:
       mov bx,handle
       DOS CLOSE_FILE
       CALLC disp_disk_err
       ret

@@fwrite:

; erst Kennung schreiben

       mov bx,offset tmp_word
       mov [bx],'WJ'
       mov dx,bx
       mov bx,handle
       mov cx,2
       DOS WRITE_TO_HANDLE
       CALLC disp_disk_err

      ; cp/m Parameter schreiben

       mov dx,offset offset dpb0+ZBIOSDISPL
       mov bx,handle
       mov cx,size dpb
       push ds
       mov ax,z80seg_adr
       mov ds,ax
       DOS WRITE_TO_HANDLE
       pop ds
       CALLC disp_disk_err

       ; physikalische Parameter schreiben

       mov dx,offset first_phys_sec
       mov bx,handle
       mov cx,autologin_flag-first_phys_sec
       DOS WRITE_TO_HANDLE
       CALLC disp_disk_err

     ret
save_disk_pars endp

about_author proc
       PUSHR <bx,cx,dx,di,si,bp,ds,es>
       sub  sp,HIGH_WinSave
       mov  bx,sp
       mov  cx,HIGH_WinSave
       push cx                 ; high(WinSave)
       push ss                 ; seg(WinSave)
       push bx                 ; offs(WinSave)
       mov  ax,DATA
       mov  ds,ax
       call EmuMenu_About
       add  sp,HIGH_WinSave
       POPR <es,ds,bp,si,di,dx,cx,bx>
       ret
about_author endp

clearz80 proc    ; loesche z80mem
;
; input: ax=seg z80 segment
; alle  register ok
;
       push es
       PUSHR <ax,cx,di>
       mov es,ax
       mov cx,8000h                      ; 32k words
       mov di,0
       mov ax,0
       rep stosw                         ; es:di := 0000h
       POPR <di,cx,ax>
       pop es
       ret
clearz80 endp

dos_exec proc
       cmp  exec_usr_flg,TRUE
       jz short @@cont1
       mov   al,DOS_SCR_PAGE
       call  sel_scr_page
       mov bx,STDOUT
       mov cx,exit_str_end-exit_str
       mov dx,offset exit_str
       DOS WRITE_TO_HANDLE
@@cont1:
       PUSHR <bx,cx,dx,di,si,bp,es>
; first try calling `COMSPEC`
       mov   sp_save,sp
       mov   ss_save,ss
       mov   bx,seg    exec_par_block
       mov   es,bx
       mov   bx,offset exec_par_block
       mov   dx,offset comspec
       mov   ax,seg    comspec
       mov   ds,ax
       mov   al,0                 ; exec
       DOS   EXEC                 ; ax:=error code
       jnc short @@done
; if that failed try \COMMAND.COM
       mov   bx,seg    exec_par_block
       mov   es,bx
       mov   bx,offset exec_par_block
       mov   dx,offset exec_fname
       mov   ax,seg    exec_fname
       mov   ds,ax
       mov   al,0                 ; exec
       DOS   EXEC                 ; ax:=error code
@@done:
       mov   bx,emudata_seg
       mov   ds,bx
       cli
       mov   ss,ss_save
       mov   sp,sp_save
       sti
       POPR  <es,bp,si,di,dx,cx,bx>
       cmp   ax,2             ; file not found
       jnz   short @@cc_found
       mov bx,STDERR
       mov cx,no_cc_str_end-no_cc_str
       mov dx,offset no_cc_str
       DOS WRITE_TO_HANDLE
@@press_esc:
       call crtin
       cmp  al,ESC_KEY
       jnz  short @@press_esc
@@cc_found:
       cmp  exec_usr_flg,TRUE
       mov   al,0
       CALLNZ  sel_scr_page
       ret
dos_exec endp


dos_shell proc
LOCAL  handle:WORD,mem_adr:WORD = AUTO_SIZE
       PUSHR <ax,bx,cx,dx,si,di>
       push bp
       mov  bp,sp
       sub  sp,AUTO_SIZE

       mov  ax,ovl_base
       cmp  ax,prg_end
       ja short @@memok
       call disp_no_mem
       jmp  short @@exit
@@memok:
       call @@fcreate
            jc short @@exit
       call @@fwrite
       call @@fclose
       call @@set_free
       call dos_exec
       call @@get_mem
       call @@fopen
       call @@fread
       call @@fclose
       call delete_instrtup_dir
       CALLC disp_disk_err
@@exit:
       mov  sp,bp
       pop  bp
       POPR <di,si,dx,cx,bx,ax>
       ret

@@set_free:
       push es
       mov  es,psp_adr
       mov  bx,prog_len
       sub  bx,OVL_SIZE
       DOS  MODIFY_MEMORY
       mov  bx,mem_end
       sub  bx,OVL_SIZE
       mov  ovl_base,bx
       pop  es
       ret

@@get_mem:
       push es
       mov  es,psp_adr
       mov  bx,prog_len
       DOS  MODIFY_MEMORY
       pop  es
       ret

@@fcreate:
       mov si,offset tempfilename
		
       mov cx,2  		  ; hidden
       call create_instrtup_dir
       CALLC disp_disk_err        ; Abbruch nach Error in toplevel
       mov handle,ax
       ret

@@fopen:
       mov si,offset tempfilename
       mov al,0
       call open_instrtup_dir
       CALLC disp_disk_err
       mov handle,ax
       ret

@@fclose:
       mov bx,handle
       DOS CLOSE_FILE
       CALLC disp_disk_err
       ret

@@fwrite:
       push ds
       mov  si,ovl_base
       mov  cx,OVL_SIZE/800h               ; do 4*32K
@@wlp:
       push cx
       mov  bx,handle
       mov  cx,8000h
       mov  ds,si
       mov  dx,0
       DOS WRITE_TO_HANDLE
       CALLC disp_disk_err
       add  si,800h
       pop  cx
       loop @@wlp
       pop  ds
      ret
; end fwrite
@@fread:
       push ds
       mov  si,ovl_base
       mov  cx,OVL_SIZE/800h               ; do 4*32K
@@rlp:
       push cx
       mov  bx,handle
       mov  cx,8000h
       mov  ds,si
       mov  dx,0
       DOS  READ_FROM_HANDLE
       CALLC disp_disk_err
       add  si,800h
       pop  cx
       loop @@rlp
       pop  ds
      ret
; end fread
dos_shell endp


if HARD_DISC_TOO

create_harddisc proc

       call @@fopen
            jc short @@exit

; we use secbuf as buffer, therefore it's contents must
; be invalidated
       invalidate_rdbuf


; fill a buffer of 1K with empty byte
       push es
       push ds
       pop es
       mov cx,1024/2
       mov ax,0e5e5h
       mov di,offset secbuf
       rep stosw
       pop es

       mov cx,(DRMH+1)*32/1024   ; size of directory in K

@@wrloop:
       push cx
       mov  cx,1024
       mov  dx,offset secbuf
       mov bx,handle
       DOS WRITE_TO_HANDLE
       CALLC disp_disk_err
       pop cx
       loop @@wrloop

       call @@fclose
@@exit:
       ret


@@fopen:
       mov cx,0
       mov si,offset harddisc_fname
       call create_instrtup_dir
       CALLC disp_disk_err
       mov handle,ax
       ret

@@fclose:
       mov bx,handle
       DOS CLOSE_FILE
       CALLC disp_disk_err
       ret
create_harddisc endp
endif


if HARD_DISC_TOO
init_harddisc proc

       PUSHR <ax,bx,cx,dx,si,di>

       cmp new_hdsk_flg,TRUE ; /C
       CALLZ create_harddisc

       mov al,2             ; read/write
       mov  si,offset harddisc_fname
       call open_instrtup_dir
       jc short @@exit       ; not found

       mov hd_handle,ax      ; also flag for hd present

@@exit:
       POPR <di,si,dx,cx,bx,ax>
       ret
init_harddisc endp
endif

init_ramdisc proc
       push es
       PUSHR <ax,cx>
       mov ax,ramdisklen          ; in paragraphen
       shr ax,6                   ; ax:=ramdisk len in KB
       and ax,0fff0h or 1100b     ; um Vielfache von 4K zu bekommen
       cmp ax,max_rd_size         ; -> /Snnn
       jna short @@rd_size_ok
       mov ax,max_rd_size
@@rd_size_ok:
       push ax                    ; size in K
       shr ax,2                   ; ax/=4
       mov lasttrack_rd,al        ; Tracks zu 4 K (-> SPTR)
       pop ax
                                  ; dsm=ramdisklen in K/2 -1 , da bls=2k
       shr ax,1                   ; geht klar, da sowieso vielfaches von 4
       dec ax
       cmp ax,255
       jna short @@nobig
;       mov dpb1.exm,0
       mov ax,255                 ; mehr wie 510k geht im PC sowieso kaum
@@nobig:
       mov dpb1.dsm,ax            ; block number
       inc ax                     ; as KB=(dsm+1)*2
       shl ax,1
       mov ramdisklen,ax          ; KBnumber


                                  ; clear ramdisc
       mov ax,rdseg_adr
       mov es,ax
       mov ax,ramdisklen
@@clrloop:
       cmp ax,64 ; k
       jb  short @@less64
       push ax
       mov cx,8000h               ; do 32k words
       mov di,0
       mov ax,0e5e5h
       rep stosw                  ; es:di := E5E5
       mov ax,es
       add ax,1000h
       mov es,ax
       pop ax
       sub ax,64
       jmp @@clrloop
@@less64:
       LOG2(512)
       shl ax,cl                  ; ax*=1024/2 (word)
       mov cx,ax
       mov di,0
       mov ax,0e5e5h
       rep stosw                  ; es:di := E5E5
       POPR <cx,ax>
       pop es
       call load_ramdisc
      ret
init_ramdisc endp

; speichere Ramdisk auf Platte
;
; der LOCAL Befehl legt lokale Auto-Vars auf dem Stack an mit
; Laenge AUTO_SIZE
;
save_ramdisc proc
LOCAL  handle:WORD = AUTO_SIZE

       push bp
       mov  bp,sp
       sub  sp,AUTO_SIZE
       call @@fopen
            jc short @@exit
       call @@fwrite
       call @@fclose
@@exit:
       mov  sp,bp
       pop  bp
       ret


@@fopen:
       mov cx,0
       mov si,offset ramdisc_fname
	call create_instrtup_dir
       CALLC disp_disk_err
       mov handle,ax
       ret

@@fclose:
       mov bx,handle
       DOS CLOSE_FILE
       CALLC disp_disk_err
       ret

@@fwrite:
       ; erst mal Ramdiskgroesse schreiben, beim Laden muss sie stimmen

       mov bx,offset secbuf
       mov ax,ramdisklen
       mov [bx],ax
       mov dx,bx
       mov bx,handle
       mov cx,2
       DOS WRITE_TO_HANDLE
       CALLC disp_disk_err

       mov bx,rdseg_adr
       mov ax,ramdisklen
@@wrtloop:
       cmp  ax,0                  ; fertig ?
                                  ; 0 oder negativ = Abbruch
       jle short @@wrtexit
       push ax
       cmp ax,32                  ; kilobyte
       mov  cx,8000h ; 32k
       ja  short @@more32
       LOG2(1024)                 ; nach cl
       shl ax,cl                  ; ax*=1024
       mov cx,ax                  ; cx = Laenge in Byte
@@more32:
       push bx
         push ds
         push bx
         pop  ds
         mov  bx,handle           ; klappt, da ueber SS:BP
         mov  dx,0 ; offset
         DOS WRITE_TO_HANDLE
         CALLC disp_disk_err
         pop  ds
       pop bx
       pop ax
       add bx,800h                ; segment um 32k weitersetzen
       sub ax,32                  ; restlaenge -=32
      jmp @@wrtloop
@@wrtexit:
      ret
; end fwrite
save_ramdisc endp


; hole Ramdisk von Platte
;
; der LOCAL Befehl legt lokale Auto-Vars auf dem Stack an mit
; Laenge AUTO_SIZE
;
load_ramdisc proc
LOCAL  handle:WORD = AUTO_SIZE

       push bp
       mov  bp,sp
       sub  sp,AUTO_SIZE
       call @@fopen
       jc   short @@exit           ; nicht da

       call @@fread
       call @@fclose
@@exit:
       mov  sp,bp
       pop  bp
       ret


@@fopen:
        mov si,offset ramdisc_fname
	mov al,0
	call open_instrtup_dir
	mov handle,ax
       ret

@@fclose:
       mov bx,handle
       DOS CLOSE_FILE
       CALLC disp_disk_err
       ret

@@fread:
       mov dx,offset secbuf
       mov bx,handle
       mov cx,2
       DOS READ_FROM_HANDLE
       CALLC disp_disk_err
       mov bx,dx
       mov bx,[bx]
       cmp bx,ramdisklen
       ja  short @@rdexit         ; ueberhaupt zu gross

       mov ax,bx                  ; Laenge Ramdisk auf Platte
       mov bx,rdseg_adr
@@rdloop:
       cmp  ax,0                  ; fertig ?
       jle short @@rdexit
       push ax
       cmp ax,32
       mov  cx,8000h              ; 32k
       ja  short @@more32
       LOG2(1024)                 ; nach cl
       shl ax,cl                  ; ax*=1024
       mov cx,ax                  ; cx = Laenge in Byte
@@more32:
       push bx
         push ds
         push bx
         pop  ds
         mov  bx,handle           ; klappt, da ueber SS:BP
         mov  dx,0                ; offset
         DOS READ_FROM_HANDLE
         CALLC disp_disk_err
         pop  ds
       pop bx
       pop ax
       add bx,800h                ; segment um 32k weitersetzen
       sub ax,32                  ; restlaenge -=32
      jmp short @@rdloop
@@rdexit:
      ret
; end fread
load_ramdisc endp

in_emu_flg db FALSE

bios88 proc
;
; bios88 ist handler fuer alle bios calls
; In:  al=bios nummer (warm boot = 1)
;      cx,bx,dx wie bei cp/m bios calls
; Out: di,ah zerstoert
;         si kann veraendert sein, falls bios change_si_flag
;            hat nach wboot
;
; waehrend aller bios routinen zeigt ds auf emudata_seg
;                                    es     z80cpu_seg

       mov cs:in_emu_flg,FALSE
       mov di,emudata_seg
       mov ds,di

       mov z80_pc,si

       cmp breakflag,TRUE

       CALLZ prg_exit

       PUSHR <bp,dx>
; cx nicht pushen, da cl:=drive # bei wboot

       cmp al,30
       jna short @@goodfn
       mov al,1                   ; illegal function -> warmboot
@@goodfn:
       xor ah,ah                  ; bios # = index
       sal ax,1
       mov di,ax

         call cs:biostab[di]
       mov     si,z80_pc

       POPR  <dx,bp>

       cmp breakflag,TRUE

       CALLZ  prg_exit

       mov di,z80seg_adr
       mov ds,di
       mov es,di
       mov cs:in_emu_flg,TRUE
       jmp bios88ret

bios88 endp


biostab label word
        dw boot                   ; cold start
        dw wboot                  ; warm start
        dw constat                ; console status
        dw conin                  ; console character in
        dw conout                 ; console character out
        dw list                   ; list character out
        dw punch                  ; punch character out
        dw reader                 ; reader character out
        dw home                   ; move head to home position
        dw seldsk                 ; select disk
        dw settrk                 ; set track number
        dw setsec                 ; set sector number
        dw setdma                 ; set dma address
        dw read                   ; read disk
        dw write                  ; write disk
        dw listst                 ; return list status
        dw sectran                ; sector translate
        rept 25-16-1
          dw wboot                ; not used
        endm
        dw cpm3_move              ; move
        dw cpm_get_time           ; time
        rept 30-26-1
          dw wboot                ; not used
        endm
        dw userfn                 ; here: used for data transfer dos/cpm
;
;
;  individuelle UPs um jede Funktion auszufuehren
;
boot proc

;simplest case is to just perform parameter initialization
       mov in_boot_flag,TRUE
       mov bx,offset on_sign_string
       call puts

       mov     di,0
       mov     byte ptr es:[di+0],0c3h
       mov     bx,bios_adr
       mov     es:[di+1],bx    ; for the time of boot there should
                               ; be a jp boot at 0


       call instzbios

       cmp  fmt_file_flg,TRUE
       CALLZ load_disk_pars


       call read_sys  ; lade bdos aus file CPMSYS.CPM
       jnc  short @@goodload ; laden aus File klappte
       call get_sys   ; BDOS mit BIOS fkt von Diskette laden
@@goodload:

; Copyright von dr ausgeben

       mov cl,27
       call crtout
       mov cl,'k'                 ; pop cursor pos
       call crtout
       mov cl,27
       call crtout
       mov cl,'J'                 ; Rest vom Fenster loeschen
       call crtout

       mov bx,offset cpm_bdos_buf+18h
       call puts

       mov bx,offset cr_lf_txt
       call puts
;
; berechne adresse von ccp,bdos und bios
; dabei wird die JMP CCPENTRY Anweisung bei CCP+0 benutzt
;
       mov di,offset cpm_bdos_buf+807h
       mov al,[di]
       cmp al,11h
       jnz short @@no_standard
       mov di,offset cpm_bdos_buf+1
       mov ax,[di]
       sub ax,35ch                ; ax:=start ccp
; CP/M CCP? dann al jetzt 0
       or al,al
       jz short @@is_standard
@@no_standard:
       mov di,offset cpm_bdos_buf+807h ; bdos entry
       mov ax,[di] ; at XX06 should be a JP XXyy
       and ax,0ff00h ; keep XX00
       sub ax,800h   ; CCP is 800h lower
@@is_standard:
       mov ccp_adr,ax
       add  ax,806h
       mov bdos_adr,ax
       add  ax,(BDOSLEN-806h)
       mov bios_adr,ax

       call reset_crt

       call init_iobyte

       mov di,CDISK
       mov byte ptr es:[di],0     ; select disk zero
       call reset_punch_buf
       call reset_reader_buf

; possibly select other screen parameters

       mov al,spec_scr_flg
       or al,al
       CALLNZ select_scr_codes

       mov in_boot_flag,FALSE
       jmp  wboot
boot endp

init_iobyte proc
       mov di,IOBYTE
       mov byte ptr es:[di],DEFAULT_IOBYTE   
     ret
init_iobyte endp
;
; setze Punch buffer auf leer
;

reset_punch_buf proc
       mov punch_buf_entries,0
       mov punch_buf_ptr,offset punch_buf
       ret
reset_punch_buf endp


;
; setze Reader buffer auf nicht belegt
;

reset_reader_buf proc
       mov reader_empty_flag,FALSE     ; noch nicht lesen versucht
       mov reader_buf_entries,0
       mov reader_buf_ptr,offset reader_buf
       mov reader_file_pointer,0
       mov reader_file_pointer+2,0
       ret
reset_reader_buf endp

;
; Stringausgabe
; In: ds:bx zeigt auf String, Stringende = \0
;
puts proc
@@loop:
       mov cl,[bx]
       or  cl,cl
       jz  short @@exit
       inc bx
       push bx
       call crtout
       pop  bx
       jmp short @@loop
@@exit:ret
puts endp

;
; In: ds:si = source, es:di = target
; copy string-zero
;
strcpy proc
        push ax
@@loop:
        lodsb
        stosb
        or al,al
        jnz short @@loop
        pop ax
        ret
strcpy endp

instzbios proc
;
; installiere z80 bios
; und discparameter
;

       PUSHR <si,di,cx>

         mov  si,offset zbiosbeg
         mov  di,ZBIOS
         mov  cx,dpb0-zbiosbeg
         cld
         rep movsb                ; es:di := ds:si
; leave out parameters for physical drive

         add si,size dpb
         add di,size dpb
         mov  cx,zbiosend-dpb1
         rep movsb                ; es:di := ds:si

       POPR <cx,di,si>
       ret
instzbios endp


instjp proc
;
; installiere z80 bios Sprungleiste bei bios_adr
;
; register o.k.
;
; darf nicht aufgerufen werden, bevor bios_adr
; von boot aufgesetzt wurde
;

       PUSHR <si,di,cx>

         mov  si,offset wboote-3
         mov  di,bios_adr
         mov  cx,zvecend-wboote+3
         cld
         rep movsb                ; es:di := ds:si

       POPR <cx,di,si>

       ret
instjp endp


read_sys proc
;
; lade cp/m bdos aus file CPMSYS.CPM in buffer
; um es durch warm boot zu benutzen
;
; das File entspricht dem durch MOVCPM erzeugten Speicherauszug
; dabei beginnt das eingentlich System erst ab Offset 880h

MOVCPM_OFFS = 880H

     PUSHR <ax,bx,cx,dx,si>
       mov al,0
       mov si,offset cpmsys_fileName
       call open_instrtup_dir
       jc  short @@exit
       mov si,ax
       ; setze Filepointer auf CCP Anfang im File
       mov cx,0                   ; cx:dx: offset
       mov dx,MOVCPM_OFFS
       mov al,0                   ; offset from start
       mov bx,si
       DOS MOVE_FILE_POINTER
;
; emudata_seg:buffer := CPMSYS.CPM
;
       mov cx,BDOSLEN             ; Laenge
       mov bx,si                  ; handle
       mov dx,offset cpm_bdos_buf
       DOS READ_FROM_HANDLE
       jc  short @@exit
       mov bx,si
       DOS CLOSE_FILE
       CALLC disp_disk_err
@@exit:
       POPR <si,dx,cx,bx,ax>
       ret
read_sys  endp


; System von SystemSpuren laden

; while sector in systemsectoren do
;    lies sector
;    if an Adresse     String COPYRIGHT 1979 ..
;       sys=naechste SYS_SECS sektoren
;       lies sys
;       exit
;       mache Checksumme ueber sys
;       if falsch
;          "no sys"
;       endif
;    endif
; endwhile
; "no sys"

get_sys proc
LOCAL  sys_recs,last_sys_rec:WORD = AUTO_SIZE

       push bp
       mov  bp,sp
       sub  sp,AUTO_SIZE
@@get_sys_loop:
       call login_disc
       call home                  ; else dir track will not be read
       mov  ah,0                  ; take drive A: = floppy disc
       mov  al,dpb0.off-dpb0.spt  ; off
       call get_dpb_entrie        ; nach bx
       push bx
       mov  ah,0                  ; A:
       mov  al,dpb0.spt-dpb0.spt  ; spt
       call get_dpb_entrie        ; nach bx
       pop  ax
       mul  bx                    ; ax:=system records
       mov  sys_recs,ax
       mov  cx,0
; im Systembereich nach String "COPYRIGHT (C) 1979 " suchen
@@srch_loop:
       cmp  cx,sys_recs
       jnc  short @@read_err      ; schon ausserhalb sys
       push cx  ; sector
       push cx
       mov  cx,DEFAULT_DMA
       call setdma
       pop  cx
       mov  ah,0                  ; A:
       call set_abs_sect
       call read
       pop  cx
       cmp  al,1
       jz   short @@read_err
       call @@compare
       jz   short @@found
       inc  cx
       jnz  @@srch_loop
@@found:
       mov  bx,DEFAULT_DMA
       push cx
       add  cx,SYS_SECS
       mov  last_sys_rec,cx
       pop  cx
@@loop:
       push cx  ; sector
       push bx  ; dma
       push cx
       mov  cx,bx
       call setdma
       pop  cx
       mov  ah,0                  ; take drive A: = floppy disc
       call set_abs_sect
       call read
       pop  bx
       pop  cx
       cmp  al,1
       jz   short @@read_err
       add  bx,RECORD_LEN
       inc  cx
       cmp  cx,last_sys_rec
       jnz  @@loop

       jmp  short @@good_load

@@read_err:
       mov  bx,offset no_sys
       call puts
       call crtin
       jmp  @@get_sys_loop

@@good_load:

; System aus CP/M Segment nach Datensegment uebertragen

       PUSHR <si,di,bx,cx,ax>
       push ds
       push es
         mov di,offset cpm_bdos_buf
         mov si,DEFAULT_DMA
         mov ax,z80seg_adr
         mov ds,ax
         mov ax,emudata_seg
         mov es,ax
         mov cx,BDOSLEN/2
         cld
         rep movsw                ; es:di := ds:si
       pop es
       pop ds
       POPR <ax,cx,bx,di,si>
       mov  sp,bp
       pop  bp
       ret
@@compare:
       PUSHR <si,di,cx,ax>
       mov  si,offset bdos_str
       mov  di,DEFAULT_DMA+10H
       mov  cx,bdos_str_end-bdos_str
       call strncmp
       POPR <ax,cx,di,si>
       ret
get_sys endp
;

; compare *ds:si and *es:di
; max cx chars
; ret: Z == equal

strncmp proc
PUSHR <si,di,cx,ax>
@@cp_loop:
       mov al,[es:di]
       mov ah,[si]
       inc si
       inc di
       cmp al,ah
       jnz  short @@cp_exit
       loop @@cp_loop
@@cp_exit:
       POPR <ax,cx,di,si>
       ret
strncmp endp

set_abs_sect proc    ; absoluten Sektor in cx, drive ah setzen
       push cx
       mov  al,0                  ; spt
       call get_dpb_entrie        ; nach bx
       pop  ax
       mov  dx,0
       div  bx
       push dx
       mov  cx,ax
       call settrk
       pop  cx
       call setsec
       ret
set_abs_sect endp


wboot proc

     push   di
       call   copy_bdos
       call   instjp              ; bios Sprungleiste installieren
;
; 0000h := JP WBOOT
; 0005h := JP BDOS
;
       mov     di,0               ; start cp/m memory
       mov     byte ptr es:[di+0],0c3h
       mov     bx,bios_adr
       add     bx,3               ; bx:=warmboot
       mov     es:[di+1],bx       ; set address field for jmp at 0
;
       mov     byte ptr es:[di+5],0c3h
       mov     bx,bdos_adr        ; bdos entry point
       mov     es:[di+6],bx       ; address field of jump at 5 to bdos 
;
       mov     cx,DEFAULT_DMA     ; default dma address is 80h
       call   setdma

       call login_disc ; physikalisches Login
;
       mov     ax,ccp_adr
       mov     z80_pc,ax          ; jp ccp

       mov     di,CDISK
       mov     cl,es:[di]         ; get current disk number & send to ccp 
     pop di
     ret                          ;go to cp/m for further processing

copy_bdos:
       PUSHR <si,di,cx>

         mov di,ccp_adr
         mov si,offset cpm_bdos_buf
         mov cx,BDOSLEN/2
         rep movsw                ; es:di := ds:si

       POPR <cx,di,si>
       ret

wboot  endp
;

;
;
constat proc
;
; console status, return 0ffh if character ready, 00h,Z_flag if not
;
; Out: al=status
;
        mov bx,IOBYTE
        mov bl,es:[bx]
        and bx,11b
        add bx,bx
        jmp cs:consttab[bx]

consttab label word
       dw offset crtinstat
       dw offset crtinstat
       dw offset readstat
       dw offset crtinstat

constat endp

;
;
conin proc   ; CON:
;
; Out: al=char
;
        mov bx,IOBYTE
        mov bl,es:[bx]
        and bx,11b
        add bx,bx
        jmp cs:conintab[bx]
conintab label word
; -> consttab
       dw offset crtin      ;  TTY:
       dw offset crtin      ;  CRT:
       dw offset reader     ;  BAT:
       dw offset crtin      ;  UC1:
conin endp

conout proc  ; CON:
;
; In: c=char
;
        mov bx,IOBYTE
        mov bl,es:[bx]
        and bx,11b
        add bx,bx
        mov al,1
        jmp cs:conouttab[bx]
conouttab label word
       dw offset crtout    ;  TTY:
       dw offset crtout    ;  CRT:
       dw offset punch     ;  BAT:
       dw offset crtout    ;  UC1:
conout endp


;
; test auf ^C, wenn ja, warm boot
;
tst_ctrl_c proc
       push ax
       call constat
       jz   short @@exit
       call crtin
       cmp  al,3                  ; ^C
       clc                        ; cy = no error
       jnz  short @@exit
       mov  bx,offset control_c_txt
       call puts
       stc
@@exit:
       pop ax
       ret
tst_ctrl_c endp

list proc  ; LST:
    mov bx,IOBYTE
    mov bl,es:[bx]
    and bx,11000000b
    shr bx,6-1
    jmp cs:lsttab[bx]
lsttab label word
       dw offset crtout      ; TTY:
       dw offset crtout      ; CRT:
       dw offset lpt0list    ; LPT:
       dw offset lpt1list    ; UL1:
list endp

lpt0list proc
        xor dx,dx
        jmp short lptlist
lpt0list endp

lpt1list proc
        mov dx,1
        jmp short lptlist
lpt1list endp

;
; list character in register c auf Drucker
;
lptlist proc
       call listst
       jnz  short @@ok
       call tst_ctrl_c
       jnc  short lptlist
       mov  z80_pc,0              ; Z80 macht ab Adresse 0 weiter
       ret
@@ok:
       mov al,cl                  ; char nach register a
       xor ah,ah                  ; list character
       push bp
       int 17h
       pop bp
       cmp ah,10h                 ; ok
       jnz short lptlist
       ret
lptlist endp
;
; return list status (0,Z_flag if not ready, 1 if ready)
;
listst proc
       push dx
       mov ah,2                   ; test printer
       int 17h
       mov al,1
       cmp ah,90h                 ; on line und not busy
       jz short @@lret
       dec al
@@lret:
       or  al,al
       pop dx
       ret
listst endp


com0list proc
        xor dx,dx
        jmp short comlist
com0list endp

com1list proc
        mov dx,1
        jmp short comlist
com1list endp

comlist proc
        mov ah,1
        mov al,cl
        int 14h
        ret
comlist endp

com0read proc
        xor dx,dx
        jmp short comread
com0read endp

com1read proc
        mov dx,1
        jmp short comread
com1read endp

comread proc
        mov ah,2
        mov al,cl
        int 14h
        ret
comread endp

date_dos_2_cpm proc  ; out: AX=tage seit 1.Jan. 1978
    push bx
    push cx
    push dx
    DOS GET_DATE
    push dx
    mov bx,1978
    inc cx      ; ein Jahr zuviel, fr Test ob akt. Schaltjahr
    MOV AX,0
@@add_loop:
    cmp  bx,cx  ; aktuelles Jahr erreicht ?
    jz short @@years_done

    mov  dx,bx
    and  dx,11b ; 0 => durch 4 teilbar
    sub  dx,1   ; 0 => cy
    mov  dx,365
    adc  dx,0   ; wird unten zum abziehen gebraucht
    add  ax,dx  ; 0 => 366

    inc  bx
    jmp  @@add_loop
@@years_done:
    sub ax,dx  ; da ein Jahr oben zuviel, 365 oder 366 wieder weg
    pop dx   ; mon/tag
    mov ch,0
    mov cl,dl
    add ax,cx ; tag
    mov cl,dh ; monat
    dec cl    ; da aktueller Monat schon dran
    jz  short @@is_jan
    mov bx,offset month_tab
@@add_months:
    mov dl,[bx]    ; verschieden lange monate
    inc bx
    mov dh,0
    add ax,dx
    loop @@add_months
@@is_jan:
    pop dx
    pop cx
    pop bx
    ret
date_dos_2_cpm endp

cpm3_move proc   ; move wie bei cpm plus bios
    push ds
    push es
    pop  ds
    mov  si,bx
    mov  di,dx
    add  bx,cx      ; hl=blockende
    cmp  bx,dx      ; gr”áer Ziel ?
    jnb   short @@topdown
    cld
    rep movsb
    jmp short @@exit
@@topdown:
    add si,cx
    add di,cx
    mov bx,si
    mov dx,di
    dec si
    dec di
    std
    rep movsb
    pop ds
    ret
@@exit:
    mov bx,si
    mov dx,di
    pop ds
   ret
cpm3_move endp


cpm_get_time proc   ; dos zeit nach cpm plus zeit
    PUSHR <AX,CX,DX>
    cmp cl,0
    jnz short @@exit ; set time ignorieren
    call date_dos_2_cpm
    push ds
    push es
    pop  ds
    mov @date+ZBIOSDISPL,ax
    DOS GET_TIME
    mov dl,10
    mov al,cl
    mov ah,0         ; sonst divide overflow
    div dl
    mov cl,4
    shl al,cl
    or  al,ah
    mov @min+ZBIOSDISPL,al
    mov al,ch
    mov ah,0
    div dl
    shl al,cl
    or  al,ah
    mov @hour+ZBIOSDISPL,al
    mov al,dh
    mov ah,0
    div dl
    shl al,cl
    or  al,ah
    mov @sec+ZBIOSDISPL,al
    pop ds
    mov bx,(offset @date)+ZBIOSDISPL
@@exit:
    POPR <DX,CX,AX>
    ret
cpm_get_time endp
        
del_punch_file proc
       mov dx,offset punch_fileName
       DOS DELETE_FILE
       ret
del_punch_file endp

; on entry to all userfunctions:
;  c = proc number
; de = entry data
; hl = pointer to variable, where to store return value
userfn proc
        cmp cl,10
        ja wboot             ; illegal function
        mov ch,0
        mov si,cx
        add si,si
        push bx              ; hl
        call cs:userfntab[si]
        pop  di
        xchg ax,bx
        stosw                ; es:di:=ax
        mov  ax,bx
        ret

userfntab label word
        dw versionfn         ; return version NR
        dw openout           ; open out to dos
        dw closeout          ; close out to dos
        dw doswrite          ; write record to dos
        dw dosexec           ; execute dos command
        dw setfname          ; set filename for getnextname ("*.c")
        dw getnextname       ; return next suitable name
        dw openin            ; open in dosfile
        dw dosread           ; read next record from dos
        dw closein           ; abandon dos file
        dw get_phys_drv      ; return physical drive number
; don't forget to adapt test at start of userfn

userfn endp

; return physical drive number
; out: al = physical drive number
get_phys_drv proc
        call get_cpm_drive
        mov ah,0
        ret
get_phys_drv endp

; execute dos command
; in: DE-> zero terminated command line
; out: -
dosexec proc
        mov si,dx
        mov di,offset usr_lne
        mov cx,80            ; in order not to copy too much
@@loop:
        mov al,es:[si]
        cmp al,0
        jz  short @@copied
        mov ds:[di],al
        inc si
        inc di
        loop @@loop
@@copied:
        mov byte ptr ds:[di],13
        mov al,83
        sub al,cl
        mov byte ptr exec_usr_lne,al
        mov exec_usr_flg,TRUE
        mov [exec_par_block+2],offset exec_usr_lne
mov di,offset exec_par_block
        call dos_shell
        mov [exec_par_block+2],offset exec_cmd_line
        mov exec_usr_flg,FALSE
        mov al,0
       ret
dosexec endp

; return version NR
versionfn proc
        mov ax,41751         ; identification of ZSIM
        mov bx,CVERSNR
        ret
versionfn endp

; open out file to dos
; in: DE-> zero terminated filename
; out: if error A=1 else 0
; first try to close an old file
openout proc
           push dx
           call closeout
           pop  dx
           push ds
           mov ax,es
           mov ds,ax         ; ds-> z80cpu seg
           mov cl,0 ; write only
           DOS CREATE_FILE
           pop ds
           CALLC disp_disk_err
           mov openout_handle,ax
           mov al,0
           adc al,0         ; error -> 1 else 0
          ret
openout endp

; close outfile
; in: -
; out: if error A=1 else 0
closeout proc
        mov bx,openout_handle
        mov openout_handle,0
        cmp bx,0
        stc
        jz  short @@exit
        DOS CLOSE_FILE
        CALLC disp_disk_err
@@exit:
        mov al,0
        adc al,0
       ret
closeout endp

; write 128 bytes to dos
; in: DE -> data
; out: if error A=1 else 0
doswrite proc
        push ds
        mov bx,openout_handle
        mov ax,es
        mov ds,ax         ; ds-> z80cpu seg
        mov cx,128
        DOS WRITE_TO_HANDLE
        pop ds
        CALLC disp_disk_err
        mov al,0
        adc al,0
       ret
doswrite endp

; set filename for getnextname ("*.c\0")
; in: DE -> fname
; out: if error A=1 else 0
setfname proc
        mov cx,80
        mov di,offset dosfilespec
        mov si,dx
@@loop:
        mov al,es:[si]
        mov ds:[di],al
        or al,al
        jz short @@done
      
        inc si
        inc di
        loop @@loop
        mov al,1             ; Name is too long
@@done:                      ; A is 0
        mov new_dosfilespecflg,TRUE
        ret
setfname endp

; return next suitable name
; in: DE -> buffer to store filename
; out: if error A=1 else 0
; if found copy filename to buffer
getnextname proc
        push dx

        push ds
        lds dx,default_dta
        DOS SET_DTA
        pop ds

        TSTNCLR new_dosfilespecflg
        mov cx,0
        mov dx,offset dosfilespec
        jz short @@open_next
          DOS FIND_FIRST
          jmp short @@cont
@@open_next:
        DOS FIND_NEXT
@@cont:
        pop di
        mov al,1
        jc short @@exit

        push ds
        lds si,default_dta
        add si,1eh           ; si-> file name in dta returned
@@loop:
        lodsb                ; copy filename
        stosb
        or al,al             ; \0 ?
        jnz @@loop
        pop ds
@@exit:
        ret
getnextname endp

; open in dosfile
; in: DE -> filename\0
; out: if error A=1 else 0
; first try to close an old file
openin proc
        push dx
        call closein
        pop  dx
        push ds
        mov ax,es
        mov ds,ax
        mov al,0                                               
        DOS OPEN_FILE
        pop ds
        mov openin_handle,ax
        mov al,0
        adc al,0
        ret
openin endp

; read next record from dos
; in: DE -> buffer
; out: if error A=1 else 0;HL=length (1..128)
dosread proc
        mov bx,openin_handle
        mov cx,128
        push ds
        mov ax,es
        mov ds,ax
        DOS READ_FROM_HANDLE
        pop ds
        mov bx,ax            ; length actually read
        mov al,0
        adc al,0
       ret
dosread endp

; abandon dos file
; in: -
; out: if error A=1 else 0
; is called before every openin
closein proc
        mov bx,openin_handle
        mov openin_handle,0
        cmp bx,0
        stc
        jz  short @@exit
        DOS CLOSE_FILE
@@exit:
        mov al,0
        adc al,0
       ret
closein endp

;
; punch character in register c
; wird durch Output in File auf MsDos Disk ersetzt
; Buffer wird auf File geschrieben, falls er voll ist
; und  auch am Ende des Emulator-
; laufs
;
punch proc   ; PUN:
    mov bx,IOBYTE
    mov bl,es:[bx]
    and bx,110000b
    shr bx,4-1
    jmp cs:puntab[bx]
puntab label word
       dw offset filepunch   ; TTY:
       dw offset filepunch   ; PTP:
       dw offset com0list    ; UP1:
       dw offset com1list    ; UP2:
punch endp

filepunch proc
;
; fill buffer until full or eof
; open file
; if not exist file
;    create file
; endif
; go to end of file
; append character
; close file
; empty buffer
;
; falls punch von bios88 aufgerufen wurde, ist al<>0
; nach Aufruf von wrt_out_punch aber al=0

     PUSHR <ax,bx,cx,dx,si>
       cmp al,0
       jz  short @@write_out
       mov bx,punch_buf_ptr
       mov [bx],cl                ; buf:=character
       inc bx
       mov punch_buf_ptr,bx
       inc punch_buf_entries
       cmp punch_buf_entries,PUNCH_BUF_SIZE
       jnz short @@exit
@@write_out:
       mov al,1
       mov si,offset punch_fileName
	call open_instrtup_dir
       jnc short @@file_found
           mov cl,0 ; write only
           mov si,offset punch_fileName
	  call create_instrtup_dir
           CALLC disp_disk_err
@@file_found:

       mov bx,ax                  ; handle
       mov cx,0
       mov dx,0
       mov al,2                   ; offset from end
       DOS MOVE_FILE_POINTER

       mov cx,punch_buf_entries
       mov dx,offset punch_buf
       DOS WRITE_TO_HANDLE
       CALLC disp_disk_err

       DOS CLOSE_FILE

       call reset_punch_buf
@@exit:
       POPR <si,dx,cx,bx,ax>
       ret
filepunch endp

wrt_out_punch proc
       cmp punch_buf_entries,0
       jz  short @@exit
       xor  al,al
       call punch
@@exit:
       ret
wrt_out_punch endp
;
; RDR:
reader proc       ;read character into register a from reader device
        mov bx,IOBYTE
        mov bl,es:[bx]
        and bx,1100b
        shr bx,2-1
        jmp cs:rdrtab[bx]
rdrtab label word
       dw offset filereader  ; TTY:
       dw offset filereader  ; PTR:
       dw offset com0read    ; UR1:
       dw offset com1read    ; UR2:
reader endp

filereader proc
; if buffer empty
;    open file
;    if file not present
;       exit
;    else
;       fill buffer
;    endif
; endif
; read char

       cmp reader_empty_flag,TRUE
       jz  short @@empty
       cmp reader_buf_entries,0
       jnz short @@do_read

; Buffer fuellen
         mov al,0
         mov si,offset reader_fileName
	call open_instrtup_dir
	        
         jc  short @@empty
         push ax
         mov bx,ax
         mov dx,reader_file_pointer
         mov cx,reader_file_pointer+2
         PUSHR <dx,cx>
          add dx,READER_BUF_SIZE
          adc cx,0
          mov reader_file_pointer,dx
          mov reader_file_pointer+2,cx
         POPR  <cx,dx>
         mov al,0                   ; offset from start
         DOS MOVE_FILE_POINTER
         pop bx                     ; handle
         mov cx,READER_BUF_SIZE
         mov dx,offset reader_buf
         DOS READ_FROM_HANDLE
         push ax
         DOS CLOSE_FILE
         pop ax
         cmp ax,0
         jz  short @@empty

 ; buffer erfolgreich gefuellt
         mov reader_empty_flag,FALSE
         mov reader_buf_entries,ax
         mov reader_buf_ptr,offset reader_buf

@@do_read:
         mov bx,reader_buf_ptr
         mov al,[bx]
         inc bx
         mov reader_buf_ptr,bx
         dec reader_buf_entries
         jmp short @@exit
@@empty:
       mov reader_empty_flag,TRUE
       mov al,CPM_EOF
       mov bx,0ffffh       ; hl=FFFF  == Dos File Ende
       ret
@@exit:
       mov bx,0            ; a=hl=gelesenes byte
       mov bl,al
       ret
filereader endp

readstat proc
       mov al,0
; the following is commented out because else
; ccp cancels operations if input is read from bat
; and al=ff
;       cmp reader_empty_flag,TRUE
 ;      jz short @@exit
  ;     dec al
@@exit:
       ret
readstat endp

; In:  ah=drive
;      al=offset im dpb
; Out: bx=word dpb[al]

get_dpb_entrie proc
       push si
       mov  bl,ah
       call get_dph_adr           ; bx:=dph
       xor  ah,ah
       mov  si,ax
       mov  bx,[es:bx+10]         ; bx:=dpb
       mov  bx,[es:bx+si]         ; bx:=dpb[ax]
       pop  si
       ret
get_dpb_entrie endp

;
; select disk in register c
;
seldsk proc
       cmp cl,NDISKS-1
       mov bx,0000h               ; 0 = Drive nicht existent
       ja  short @@exit

if HARD_DISC_TOO
       cmp cl,HARD_DISC
       jnz short @@no_hd
       cmp hd_handle,0
       mov bx,0
       jz  short @@exit
       jmp short @@noswp
@@no_hd:
endif

       cmp drv_swap_flag,TRUE
       jnz short @@noswp
       mov bl,cl
       mov cl,1
       sub cl,bl
@@noswp:

       mov diskno,cl
       mov bl,cl
       call get_dph_adr
@@exit:
       ret
seldsk endp

;  In:  bl:=Drive #
;  Out: bx:= disk parameter header address
;
get_dph_adr proc
       mov bh,0                   ; high order zero
       push cx
       LOG2(16)                   ; nach cl
       sal bx,cl                  ; bx*=16 (size of each header)
       pop cx
       add bx,offset dph_base+ZBIOSDISPL
       ret                        ;hl=.dph_base(diskno*16)
get_dph_adr endp

;
;  move to the track 00 position of current drive
;  translate this call into a settrk call with parameter 00
;
home proc
       mov cx,0                   ; select track 0
       call settrk
       mov  home_flag,TRUE        ; zur Auswertung durch read_track
       ret
home endp

;
; set track given by register bc
;
settrk proc
       mov word ptr track,cx
       ret
settrk endp
;
; set sector given by register bc
;
setsec proc
       mov word ptr sector,cx
       ret
setsec endp

;
;translate the sector given by bc using the
;translate table given by de
;
sectran proc
       mov  bx,cx
       or   dx,dx                 ; table ptr == 0 ?
       jz   short @@exit
       add  cx,cx                 ; index *= 2
       mov  bx,dx                 ; hl=trans
       add  bx,cx
       mov  bx,[es:bx]            ;hl=trans[sector]
@@exit:
       ret                        ;with value in hl
sectran endp
;
; set dma address given by registers b und c
;
setdma proc
       mov dmaad,cx
       ret
setdma endp


calcadr proc
;
; ax:bx := record adresse in Ramdisk
; illegal sec: cy
;
; 1. 64k track  0..15
; 2. 64k track 16..31
; ..
       mov al,track
       cmp al,lasttrack_rd
       ja short @@badrec

       mov al,sector
       cmp al,LAST_SEC1
       ja short @@badrec

; phys recordnum = track * spt + sector

       PUSHR <cx,dx>
       mov  ah,RMD
       mov  al,0                  ; spt
       call get_dpb_entrie
       mov  cx,bx                 ; cx:=spt B:
       mov  ax,word ptr track
       mul  cx
       add  ax,word ptr sector    ; ax:=phys record num
       mov  cx,512                ; 64K = 512 records @ 128 Bytes
       mov  dx,0
       div  cx                    ; ax:= # of 64K, dx:=recordnum
       mov  cx,1000h
       push dx
       mul  cx
       pop  dx
       mov  bx,rdseg_adr
       add  bx,ax
       mov  ax,dx
       LOG2(RECORD_LEN)
       shl  ax,cl
       xchg bx,ax
       POPR <dx,cx>
      clc
@@exit:
      ret
@@badrec:
      stc
      jmp short @@exit
calcadr endp

;
read proc
;
; lies einen record von 128 bytes
;
if HARD_DISC_TOO
        cmp  diskno,HARD_DISC
        jnz short @@no_hd
        call read_hdisc
        mov al,0
       ret
@@no_hd:
endif

       cmp  diskno,RMD
       jnz  short @@rfdisc
       call calcadr               ; ax:bx := record addresse
       jc   short @@badsec
       call rdrec
@@exit:xor al,al                  ; o.k.
       ret
@@badsec:
       mov al,1
       ret

@@rfdisc:
       call is_valid_rec          ; ax=rec/sec
       jc   short @@badsec

       call read_track
       jc   short @@badsec
;
; record num ist in ah
; ax:bx := adresse Track Buffer
;
       mov  al,READ_OP
       call get_adr_in_buf        ; nach bx
       mov  ax,ds                 ; ax := seg track buffer
       call rdrec
       jmp  short @@exit


read endp

if HARD_DISC_TOO

read_hdisc proc
     mov ah,READ_FROM_HANDLE
     jmp short rw_hdisc
read_hdisc endp

write_hdisc proc
     push cx
     mov ah,WRITE_TO_HANDLE
     call rw_hdisc
     pop cx
     cmp cl,1
     jnz short @@exit
; with cl=1 bdos has signaled that this is a write to directory (=file
; close), so flush msdos file

       mov bx,hd_handle
       DOS DUPLICATE_HANDLE  ; to flush the file
       CALLC disp_disk_err
       mov bx,ax
       DOS CLOSE_FILE
@@exit:
     ret
write_hdisc endp

rw_hdisc proc
       PUSHA
       call @@rw_sec
       POPA
       ret

@@rw_sec:

; phys recordnum = track * spt + sector
       push ax
       mov  ah,HARD_DISC
       mov  al,0                  ; spt
       call get_dpb_entrie
       mov  cx,bx                 ; cx:=spt B:
       mov  ax,word ptr track
       mul  cx
       add  ax,word ptr sector    ; ax:=phys record num

       mov  cx,0
       mov  dx,ax                 ; cx:dx *= 128
       rept 7  ; 128 = 2^7
        shl dx,1
        rcl cx,1
       endm

       mov al,0              ; seek from start
       mov bx,hd_handle
       DOS MOVE_FILE_POINTER ; cx:dx := file length

       pop ax
       mov dx,dmaad
       mov bx,hd_handle
       push ds
       push es
       pop ds
       mov cx,128            ; read/write one record
       int 21h
       pop ds
@@exit:
       ret
rw_hdisc endp
endif



; berechne Sektoradresse im Buffer
; a=sector*128+buf
; input: al=READ_OP / WRITE_OP

get_adr_in_buf proc
      PUSHR <AX,CX>
       call is_dir_track
       mov  bx,offset dirtrbuf
        jz  short @@calc
       mov  bx,offset secbuf
       cmp  al,READ_OP
        jz  short @@calc
       mov  bx,offset wrtbuf
@@calc:
;; if sectorcount in second page sub sec count per side

       xor  ax,ax
       mov  al,sector
       cmp  al,last_sect_of_side
       jb short @@nosub
       sub  al,last_sect_of_side

@@nosub:
       LOG2(RECORD_LEN)                  ; nach cl
       shl  ax,cl                 ; ax *= 128
       add  bx,ax
      POPR <CX,AX>
      RET
get_adr_in_buf endp


is_dir_track proc
      push ax
        call calc_trck_n_side
	mov ax,cphys_track
	cmp ax,dirtrack
      pop  ax
      ret
is_dir_track endp

;
;
;
is_valid_rec proc
; Out:  cy   sector illegal
;
; test auf gueltigen track/sector auf DISC
;
COMMENT *
      mov al,track
      cmp al,LAST_TRCK0
      stc
      ja  short @@exit
      mov al,sector
      cmp al,LAST_SEC0
      stc
      ja short @@exit
; ok, clear cy
*
      clc
@@exit:
      ret
is_valid_rec endp

get_compl_flg proc
        mov al,flag_byte
        and al,COMPLEMENT_MASK
        ret
get_compl_flg endp

set_cpm_drive proc
        ;; in:   al=drive
        and al,1  ; only 0 or 1
        mov cpm_drive,al
        ret
set_cpm_drive endp

get_cpm_drive proc
        ;; out:   al=drive
        mov al,cpm_drive
        ret
get_cpm_drive endp

    
set_track_tran_mode proc
	;; in:	 al=mode
        and flag_byte,(NOT TRACK_TRANS_MASK)
        and al,TRACK_TRANS_MASK
        or  flag_byte,al
	ret
set_track_tran_mode endp

get_track_tran_mode proc
        ;; out:   al=mode
        mov al,flag_byte
        and al,TRACK_TRANS_MASK
	ret
get_track_tran_mode endp

get_sect_enhance_mode proc
        ;; out:   al=mode
        mov al,flag_byte
        and al,SECTR_DOUBL_MASK
        shr al,4
	ret
get_sect_enhance_mode endp


get_side_id_mode proc
        ;; out:   al=mode
        mov al,flag_byte
        and al,SECTR_ID_MD_MASK
        shr al,6
	ret
get_side_id_mode endp

        
track_transl proc
	;; in:	 	al:	 track
	;; out:		al,ah:	track,side
	mov si,ax
        call get_track_tran_mode
        and al,111b
	mov ah,0
	
	xchg si,ax
        add si,si
        jmp cs:trtran_tab[si]

no_change:			; no translation or order sides
	mov ah,0	
	ret
ord_cylinder:		        ; v0..vn,rn..r0 (as does CP/M 86 DS)
          cmp  al,phys_tracks
	  mov ah,0
          jb  short @@exit
	  mov  ah,al
	  mov  al,phys_tracks
	  add  al,al
	  dec  al               ;  al:=2*physical tracks - 1
	  sub  al,ah
	  mov  ah,1		; side 1
          jmp  short @@exit

ord_eagle:			; v0..vn,r0..rn
	  cmp  al,phys_tracks
	  mov ah,0
          jb  short @@exit
	  sub  al,phys_tracks
          mov  ah,1		; side 1
          jmp  short @@exit	
ord_side:                       ; v0,r0..vn,rn
	  mov  ah,al
	  and  ah,1             ; odd tracks go to side 1
	  shr  al,1
	
@@exit:
	ret

trtran_tab label word
        dw no_change            ; no translation
        dw ord_side             ; v0,r0..vn,rn
	dw ord_cylinder	        ; v0..vn,rn..r0
	dw ord_eagle		; v0..vn,r0..rn
	dw no_change		; no translation
	dw no_change		; no translation
	dw no_change		; no translation
	dw no_change		; no translation
		
	
track_transl endp
	
calc_trck_n_side proc
	mov cphys_track.cyl_sde,0	
	mov al,sector
	cmp al,last_sect_of_side
        jb short @@do_trans
	

        inc cphys_track.cyl_sde ;  go to second side
	mov al,track
	mov cphys_track.cylndr,al	
	jmp short @@exit	;  and assume no track translation

@@do_trans:
	mov al,track
	call track_transl
	mov cphys_track.cylndr,al
	mov cphys_track.cyl_sde,ah
@@exit:		
        mov al,first_phys_sec
        mov currnt_first_Psec,al
        cmp cphys_track.cyl_sde,0
        jz short @@done
        call frst_sec_snd       ;  calculate 1st sect second side
        mov currnt_first_Psec,al
@@done:
;        call add_side_id_mode
	ret
calc_trck_n_side endp

add_side_id_mode proc
; if there is a bios extention it will use bit 1 of
; dh to see if the side number in the ID is the actual disc side
; if bit 1 = 1 it is not equal

        call get_side_id_mode   ; which side number in the sector ID fr

        cmp cphys_track.cyl_sde,0
        jz  short @@side0
        shl al,1                ; use the r bit
        and al,10b
        or cphys_track.cyl_sde,al
        jmp short @@exit
@@side0:
                                ; use the f bit
        and al,10b
        or cphys_track.cyl_sde,al
@@exit:
        ret
add_side_id_mode endp


frst_sec_snd proc      ;  calculate 1st sect second side

        call get_sect_enhance_mode
        mov ah,al
        mov al,first_phys_sec
        or ah,ah
        jz short @@exit  ; same first sector as front side
        add al,phys_sec_pt
        cmp ah,1
        jz short @@exit  ; go on counting on second side
        cmp ah,2
        mov al,20
@@exit:
        ret
frst_sec_snd endp

;
; lies ganzen Track, falls noch nicht im Speicher
;
read_track proc
;
; Error: cy=1
;
      PUSHR <ax,bx,cx,dx>

; der DirTrack wird nur gelesen nach HOME

	call calc_trck_n_side
        mov  ax,cphys_track
	cmp  ax,dirtrack
	
      jnz  short @@nodir
      TSTNCLR home_flag
      mov  bx,offset dirtrbuf
      jnz  short @@read_dir_track
      clc
      jmp short @@exit
@@nodir:
       cmp  ax,last_track_read
       jz   short @@exit

           mov  bx,offset secbuf
           mov  last_track_read,ax
@@read_dir_track:
           mov  r_track,ax

           call read_phys_track
           ; ab jetzt CY nicht mehr veraendern !

@@exit:
      POPR <dx,cx,bx,ax>
      ret
read_track endp

;
; lies ganzen Track, in den geschrieben werden soll, ein,
;  falls noch nicht im Speicher
;
read_w_track proc
;
; Error: cy=1
;
      PUSHR <ax,bx,cx,dx>
      ; der DirTrack ist sowieso im Speicher und braucht
      ; nicht gelesen werden

	call calc_trck_n_side
	mov ax,cphys_track
	cmp ax,dirtrack
      jz  short @@exit

      cmp  ax,last_track_written
      jz   short @@exit

      ; falls noetig alten Track im WriteBuffer erst
      ; ausschreiben
      call write_out_old_track

      mov  ax,cphys_track
      mov  bx,offset wrtbuf
      mov  last_track_written,ax
      mov  r_track,ax
      call read_phys_track

      ; ab jetzt CY nicht mehr veraendern !

@@exit:
      POPR <dx,cx,bx,ax>
      ret
read_w_track endp

; physikalische Sektorlaenge herstellen
dos_phys_len proc
       PUSHR <es,ax,di>
       mov  ax,0
       mov  es,ax
       les  di,[es:78h]
       mov  al,ms_phys_sec_len
       mov  es:[di+3],al
       POPR <di,ax,es>
       ret
dos_phys_len endp


; INT 1E - Disk Initialization Parameter Table Vector
; far pointer to the diskette base table

; unfortunately this points to ROM within a os/2 vdm
; so it cannot be changed
; (maybe it could be copied to ram)

; physikalische Sektorlaenge herstellen
cpm_phys_len proc
       PUSHR <es,ax,di>
       mov  ax,0
       mov  es,ax
       les  di,[es:1Eh*4]
       mov  al,cpm_phys_sec_len
       mov  es:[di+3],al
       POPR <di,ax,es>
       ret
cpm_phys_len endp

do_complement proc
        PUSHR <ax,bx,cx>
	mov cx,(TRACK_BUF_LEN/2); we do words
	mov ax,0ffffh
@@loop:
	xor [bx],ax
	add bx,2
	loop @@loop
	
        POPR <cx,bx,ax>
	ret
do_complement endp

do_nop proc
	ret
do_nop endp

cplmnt_fct  dw offset do_nop

set_complementing proc
	mov cplmnt_fct,offset do_complement 
	ret
set_complementing endp

set_no_complementing proc
	mov cplmnt_fct,offset do_nop 
	ret
set_no_complementing endp

rw_phys_track proc  ; common for read and write
; in: al: fn # for int 13
;     dh: side fn # for int 13
;     dl: track to read/write
;     bx: buf offset

      PUSHR <ax,bx,cx,dx,bp>

      mov bp,ax  ; save code
      mov  cl,retry_count
      xor  ch,ch
@@tryrw:
        call cpm_phys_len
        push es
        mov  ax,emudata_seg
        mov  es,ax              ; by default z80cpu_seg
        push cx

        mov  ch,dl              ; Track
        call get_cpm_drive
        mov  dl,al              ; diskno
        and  dl,1               ; only disc 0 or 1

        mov  cl,currnt_first_Psec; physical sector number
        mov  ax,bp              ; op code to al
        mov  ah,al
        mov  al,phys_sec_pt     ; read/write whole track

        int 13h                 ; es:bx = track buffer

        pop cx
        pop es
        call dos_phys_len
        jnc  short @@exit
        cmp  ah,9
        jz   dma_bound
; error, disk reset and try again
        mov ah,0
        call get_cpm_drive
        mov dl,al
        int 13h                   ; disk reset
      loop  @@tryrw
      stc                         ; Error, cy
@@exit:
      POPR <bp,dx,cx,bx,ax>
      ret
rw_phys_track endp


read_phys_track proc

; der naechste Test sollte eigentlich nicht noetig sein, aber
; mein 386 Ami Bios weigert sich sonst, den gleichen Track nochmal
; zu lesen; jedenfalls kann auch gleich ein Diskwechsel festgestellt
; werden
      call tst_disk_change
      MOVNZ  last_track_read.cylndr,0ffh

      PUSHR <dx>

       mov  dl,r_track.cylndr
       mov  dh,r_track.cyl_sde
       mov  al,2        ; read sector
       call rw_phys_track
       call cs:[cplmnt_fct]
	
      POPR <dx>
      ret
read_phys_track endp


write_phys_track proc
      PUSHR <bx,dx>

       mov  bx,outbuf_ptr
       mov  dl,w_track.cylndr
       mov  dh,w_track.cyl_sde
       mov  al,3        ; write sector
       
       call cs:[cplmnt_fct]
	
       call rw_phys_track

      POPR <dx,bx>
      ret
write_phys_track endp

tst_disk_change proc
          PUSHR <ax,dx>
          cmp hd_drive_flag,FALSE
          jz  short @@exit        ; kein Diskwechsel => Z
          mov ah,16h
          call get_cpm_drive
          mov dl,al
          int 13h  ; Test auf Wechsel
          cmp ah,0                ; kein Diskwechsel => Z
@@exit:
          POPR <dx,ax>
          ret
tst_disk_change endp

; falls noch nicht geschriebener Track im Speicher ist,
; der kein DirTrack ist, ausschreiben

write_out_old_track proc
      cmp  write_flag,TRUE
      jnz short @@exit
      mov  ax,last_track_written
      mov  w_track,ax
      mov  outbuf_ptr,offset wrtbuf
        call write_phys_track
      mov  write_flag,FALSE
@@exit:
      ret
write_out_old_track endp

;
; den Dir Track auf Disk schreiben
;
write_out_dir proc
      call write_out_old_track    ; falls noetig
      mov  ax,dirtrack
      mov  w_track,ax
      mov  outbuf_ptr,offset dirtrbuf
          call write_phys_track
      ret
write_out_dir endp

;
; versuchen, richtiges physikalisches Format zu finden
; und disc parameter block herrichten
;
; falls autologin_flag = FALSE , kann das Zeitaufwendige login
; entfallen

login_disc proc
      push bx
      call tst_hd_drive
      invalidate_rdbuf            ; Track Buffer auf leer setzen
                                  ; damit bei read_track gelesen wird
      mov write_flag,FALSE        ; sonst wird beim naechsten write
                                  ; erst alter Schrott ausgeschrieben
      cmp  autologin_flag,TRUE
      jnz  @@exit
      cmp  cpmdrv_set_flg,TRUE
      jz short @@no_drset
      mov  al,PHYS_DRV
      call set_cpm_drive
@@no_drset:
      mov  al,ms_phys_sec_len
      mov  cpm_phys_sec_len,al


      mov al,NO_TRACK_TRANS
      call set_track_tran_mode

      mov retry_count,2           ; Leseversuche herabsetzen,
                                  ; damit bei falschem
                                  ; Format nicht zuviel Zeit verschwendet wird

@@loop:

       cmp breakflag,TRUE

       CALLZ  prg_exit

; erst mal mit dem zuletzt verwendeten Format versuchen
; aber nicht, wenn letztes CP/M 86 war, da sonst das
; aehnliche Format mit 9 Sektoren nicht erkannt wird

      cmp byte ptr phys_sec_pt,8
      jz  short @@tst_cpm86

      mov al,pspt_last
      mov phys_sec_pt,al
      mov al,phys_tracks_last
      mov phys_tracks,al
      mov al,frstps_last
      mov currnt_first_Psec,al       ; physical sector
      mov ax,dirtr_last
      mov dirtrack,ax
      inc al
      mov r_track.cylndr,al
      mov r_track.cyl_sde,0
	
      mov bx,offset secbuf
      call read_phys_track
      mov  bx,dpb_last
      jnc  @@goodfmt


;
; CP/M 86 format ?
;
; Bedingungen: Seite 0 muss Sektoren 1-8 haben, aber nicht 1-9
;              Seite 1 muss Sektoren 1-8 haben

; setze 9 Phys. Sektoren, bei cp/m 86 mit 8 Sektoren pro Track muss
; es einen Lesefehler geben

@@tst_cpm86:
      mov phys_sec_pt,9
      mov phys_tracks,40
      mov currnt_first_Psec,1h

      mov r_track.cylndr,1+1
      mov r_track.cyl_sde,0
	
      mov dirtrack.cylndr,1
      mov dirtrack.cyl_sde,0
	
      mov bx,offset secbuf
      call read_phys_track
      jnc  short @@nocpm86

      mov phys_sec_pt,8
      mov phys_tracks,40
      mov currnt_first_Psec,1h

      mov r_track.cylndr,1+1      
      mov r_track.cyl_sde,0
      
      mov dirtrack.cylndr,1
      mov dirtrack.cyl_sde,0
	
      mov bx,offset secbuf
      call read_phys_track
      jc  short @@nocpm86


  ; Teste noch Seite 2 mit SektorNummer > 39

      mov phys_sec_pt,8
      mov phys_tracks,40
      mov currnt_first_Psec,1h

      mov dirtrack.cylndr,1
      mov dirtrack.cyl_sde,0

; try to read track 1 side 1
      mov r_track.cylndr,1
      mov r_track.cyl_sde,1

        
      mov bx,offset secbuf
      call read_phys_track

      mov  bx,offset dpb_cpm86SS
      jc  @@goodfmt		; error, but o.k. it was single sided

      mov al,CYLINDER_TRANS
      call set_track_tran_mode
	
      mov  bx,offset dpb_cpm86DS
      jmp  @@goodfmt

@@nocpm86:

;
; cpc system format ?
;
      mov phys_sec_pt,9
      mov phys_tracks,40
      mov currnt_first_Psec,41h

      mov r_track.cylndr,2+1
      mov r_track.cyl_sde,0

      mov dirtrack.cylndr,2
      mov dirtrack.cyl_sde,0
	
      mov bx,offset secbuf
      call read_phys_track
      mov  bx,offset dpb_cpc
      jnc  short @@goodfmt
;
; data format ?
;
      mov phys_sec_pt,9
      mov phys_tracks,40
      mov currnt_first_Psec,0c1h

      mov r_track.cylndr,0+1
      mov r_track.cyl_sde,0
	
      mov dirtrack.cylndr,0
      mov dirtrack.cyl_sde,0	

      mov bx,offset secbuf
      call read_phys_track
      mov  bx,offset dpb_dta
      jnc  short @@goodfmt

@@tsteigfrm:
;
; is it own format
;
      mov phys_sec_pt,9
      mov phys_tracks,40
      mov currnt_first_Psec,1

      mov r_track.cylndr,0+1
      mov r_track.cyl_sde,0
	

      mov dirtrack.cylndr,0
      mov dirtrack.cyl_sde,0	

      mov bx,offset secbuf
      call read_phys_track
      mov  bx,offset dpb_eig
      jnc  short @@goodfmt

@@badfmt:
      mov  bx,offset bad_format
      call puts
      call crtin
      cmp  autologin_flag,TRUE    ; waehrend conin kann Edit DPH gewes. sein
      jnz  short @@exit
      jmp  @@loop

@@goodfmt:

; physikalisches Format merken

       mov al,phys_sec_pt
       mov pspt_last,al
       mov al,currnt_first_Psec
       mov first_phys_sec,al
       mov frstps_last,al
       mov ax,dirtrack
       mov dirtr_last,ax
       mov dpb_last,bx



       mov  si,bx
       mov  di,offset dpb0+ZBIOSDISPL
       mov  cx,size dpb
       cld
       rep movsb                  ; es:di := ds:si

@@exit:

       mov al,1
       mov cl,cpm_phys_sec_len
       shl al,cl                ; so al=sector count per phys. sector

       mov ah,phys_sec_pt
       mul ah                   ; so ax=sectors per track
       mov last_sect_of_side,al ; save it

; update complement function pointer
;       call get_compl_flg
;       or al,al
;       CALLNZ set_complementing
;       CALLZ set_no_complementing

       pop bx
       mov retry_count,5
       ret
login_disc endp


write proc
;
; write a record of 128 bytes
;
if HARD_DISC_TOO
  cmp  diskno,HARD_DISC
  jnz short @@no_hd
  call write_hdisc
  mov al,0
  ret
@@no_hd:
endif
       cmp  diskno,RMD
       jnz  short wtodisc
       call calcadr               ; ax:bx := record adress
       jc   short @@bad
       call wrtrec                ; buffer := record
       jc   short @@bad
       xor  al,al                 ; write ok
       ret
@@bad: mov al,1
       ret
write endp

wtodisc proc
       push cx
       call is_valid_rec          ; ax=rec/sec
       pop  cx
       jc   short @@badsec


; if track noch nicht im Buffer
;    if noch alter Track im Buffer
;       alten Track ausschreiben
;    endif
;    lies neuen Track ein
; endif
; if track ist dirtrack
;    if noch alter Track im Buffer
;       alten Track ausschreiben
;    endif
;    dirtrack ausschreiben
; else
;    Setze Flag, daá Sektor geschreiben wurde
; endif
;

       push cx                    ; bdos info, ob dirsectoren
       call read_w_track
       pop  cx
       jc   short @@badsec
;
; record # ist in ah
;

       push cx
       mov  al,WRITE_OP
       call get_adr_in_buf
       mov  ax,ds                 ; ax:= seg track buffer
       call wrtrec
       pop  cx
       cmp  cl,1
       jz   short @@isdir1
       call is_dir_track
       jz   short @@isdir1

       mov  write_flag,TRUE
       mov  ax,cphys_track
       mov  last_track_written,ax
	
       mov  al,0                  ; o.k.
       jmp  short @@exit

@@isdir1:
       call write_out_dir
       jc short @@badsec
       mov  al,0
       jmp  short @@exit
@@badsec:
      mov al,1
@@exit:
      ret
wtodisc endp

;
; In: ax:bx = source adr
;
rdrec proc
;
       PUSHR <si,di,bx,cx,ax>
       push ds
         mov si,bx
         mov di,dmaad             ; ds MUSS =  emudata_seg sein
         mov ds,ax
         mov cx,RECORD_LEN/2             ; record laenge in words
         cld
         rep movsw                ; es:di := ds:si
       pop ds
       POPR <ax,cx,bx,di,si>
       ret
rdrec endp
;
; In: ax:bx=target adr
;
wrtrec proc
       PUSHR <si,di,cx,ax>
       push es
       push ds

       mov si,dmaad               ; ACHTUNG: der MOV muss
                                  ; vor DS Segmentwechsel sein
       mov di,bx
       mov bx,ax                  ; ramdisc
       mov ax,es                  ; cp/m
       mov es,bx
       mov ds,ax

       mov cx,RECORD_LEN/2               ; record laenge in words
       cld
       rep movsw                  ; es:di := ds:si
       pop ds
       pop es
       POPR <ax,cx,di,si>
       ret
wrtrec endp

; in: ax pages need
; out: ax seq address if enough pages else 0

get_emm_seg proc
LOCAL  needed:WORD = AUTO_SIZE
       push bp
       mov  bp,sp
       sub  sp,AUTO_SIZE
       mov needed,ax
        call tst_for_emm
        jnz short @@no_ems
        mov ah,40h ; test emm status
        int 67h
        cmp ah,0
        jnz short @@no_ems
        mov ah,42h  ; get free page count to bx
        int 67h
        cmp ah,0
        jnz short @@no_ems
        cmp bx,needed
        jna short @@no_ems
        mov ah,43h      ; allocate bx pages
        mov bx,needed
        int 67h
        cmp ah,0
        jnz short @@no_ems
        mov ems_handle,dx

        IRP page,<0,1,2,3>
                mov ah,44h ; map page
                mov al,page   ; phys page number
                mov bx,page   ; log page number
                mov dx,ems_handle
                int 67h
                cmp ah,0
                jnz short @@no_ems
        ENDM

        mov ah,41h  ; get frame address to bx
        int 67h
        cmp ah,0
        mov ax,bx
        jz short @@exit
@@no_ems: mov ax,0
@@exit:
        add  sp,AUTO_SIZE
        pop bp
        ret
get_emm_seg endp

; Z = emm installed
tst_for_emm proc
       PUSHR <es,di,si,cx>
       mov si,offset emmstr
       mov di,0
       mov es,di
       mov di,67h*4
       les di,es:di ; emm int
       mov di,10
       mov cx,emmstrend-emmstr
       call strncmp
       POPR <cx,si,di,es>
      ret
tst_for_emm endp

release_emm_pages proc
        pusha
        cmp ems_handle,0
        jz short @@exit
        mov dx,ems_handle
        mov ah,45h
        int 67h
@@exit:
        popa
        ret
release_emm_pages endp

emulator_seg ends


end init

