;USERPORT.ASM -- customizable userport interface for MS-DOS C64 emulators
;link with the /t or /tiny switch to create a .COM program

CODE segment 'CODE'
assume CS:CODE,DS:CODE
org 100h
EntryPoint:
  jmp Main

;------------------------------------------------------------------------------
;Add your own variables here.

acEmulator db "PC64.EXE",0              ;path to C64 emulator
LPTNum equ 1                            ;the LPT used for output, from 1 to 4
wLPTPort dw ?                           ;the LPT's port address

;------------------------------------------------------------------------------
;The Init function is called once at startup when the emulator isn't running
;yet. If your special hardware can't be found, you should print out an error
;message and return to DOS.

Init proc near
  xor AX,AX                             ;get LPT port address
  mov ES,AX
  mov AX,ES:[0408h+(LPTNum-1)*2]
  cmp AX,0100h                          ;check for existence and network
  jb NoPort                             ; tricks
  test AL,00000011b
  jne NoPort
  mov wLPTPort,AX                       ;store the LPT port
  call Print                            ;Print() will continue program
  db "C64 Userport output is "          ; execution after the concluding 0
  db "now redirected to LPT",LPTNum+'0',13,10,0
  ret                                   ;return and run the emulator
NoPort:                                 ;the LPT port was not present or is
  call Print                            ; in use by a network
  db "Userport redirection error: LPT",LPTNum+'0'," not found!",13,10,0
  mov AX,4C01h                          ;back to DOS, don't run the emulator
  int 21h
Init endp

;------------------------------------------------------------------------------
;If you've used resources like interrupt vectors in the Init() function, you
;must release them here. Exit() is also the right place to reset your
;individual hardware.

Exit proc near
  ret                                   ;No resources used, simply return
Exit endp

;------------------------------------------------------------------------------
;The following functions are called by the emulator when the C64 program reads
;and writes the Userport or the IO area. Note that DS belongs to the emulator,
;so you cannot access variables via the data segment register. You must also
;preserve all registers except AX.

assume DS:nothing                       ;tell the assembler not to use DS

;------------------------------------------------------------------------------
;CIA 2 Port A:
;  Bit 2 = RS232 TXD out
;  Bit 3 = IEC ATN out
;  Bit 4 = IEC CLOCK out
;  Bit 5 = IEC DATA out
;  Bit 6 = IEC CLOCK in
;  Bit 7 = IEC DATA in
;The bits 0 and 1 are for the 16K VIC bank, you cannot change them here. Some
;emulators may also mask out the IEC bits 3 to 7, leaving only bit 2 for own
;purposes.

ReadDD00 proc far                       ;AL=PEEK(56576)
  mov AL,0                              ;return a dummy value
  ret
ReadDD00 endp

WriteDD00 proc far                      ;POKE 56576,AL
  ret
WriteDD00 endp

WriteDD02 proc far                      ;POKE 56578,AL
  ret
WriteDD02 endp

;------------------------------------------------------------------------------
;CIA 2 Port B:
;  Bit 0 = RS232 RXD in
;  Bit 1 = RS232 RTS out
;  Bit 2 = RS232 DTR out
;  Bit 3 = RS232 RI in
;  Bit 4 = RS232 DCD in
;  Bit 5 = free in
;  Bit 6 = RS232 CTS in
;  Bit 7 = RS232 DSR in
;This is the standard Userport register for 8 bit I/O. Remember to preserve
;everything except AX!

ReadDD01 proc far                       ;AL=PEEK(56577)
  mov AL,0                              ;sorry, no input in this example
  ret
ReadDD01 endp

WriteDD01 proc far                      ;POKE 56577,AL
  push DX
  mov DX,wLPTPort                       ;the assembler will use CS: here
  out DX,AL                             ;write the data byte to LPTx
  pop DX
  ret
WriteDD01 endp

WriteDD03 proc far                      ;POKE 56579,AL
  ret                                   ;direction register not used here
WriteDD03 endp

;------------------------------------------------------------------------------
;The following functions are called if a program accesses the IO range from
;$DE00 to $DF9F. BX contains the address. $DFA0 to $DFFF is reserved for
;recognizing C64 emulators. Don't forget to preserve everything except AX!

ReadIO proc far                         ;AL=PEEK(BX)
  mov AL,0
  ret
ReadIO endp

WriteIO proc far                        ;POKE BX,AL
  ret
WriteIO endp

;------------------------------------------------------------------------------
;This is private stuff which shouldn't be modified.

acIdentification db "C64 Emulator Userport Interface",0
apFunctions dw acIdentification
  dw ReadDD00,WriteDD00,WriteDD02
  dw ReadDD01,WriteDD01,WriteDD03
  dw ReadIO,WriteIO
;Emulator writers can add additional functions here, e.g. the Serial Data
;Registers $DC0C and $DD0C or user customizable keyboard and joystick mapping
;in $DC00 and $DC01. A individual identification string should precede those
;function blocks to avoid conflicts with other extensions. The emulator will
;stop searching for extensions when it reaches the NULL pointer.
  dw 0

NewInt2F proc
  cmp AX,0C640h
  jne NextHandler
  cmp DX,'UI'
  jne NextHandler
  mov AX,offset apFunctions             ;pass function pointers to emulator
  mov DX,CS                             ;same segment for all functions
  iret
NextHandler:
  db 0EAh                               ;jmp far ptr xxxx:xxxx
OldInt2F dw ?,?
NewInt2F endp

PrintSI proc near                       ;prints text in CS:SI until 0
  mov DL,CS:[SI]
  inc SI
  and DL,DL
  je Return
  mov AH,02h
  int 21h
  jmp PrintSI
Return:
  ret
PrintSI endp

Print proc near
  pop SI                                ;return address = start of string
  call PrintSI
  jmp SI                                ;jump behind the terminating 0
Print endp

wEnvSeg dw ?                            ;Parameter Block for function 4B00h
wCmdOfs dw 128
wCmdSeg dw ?
wFCB1Ofs dw 5Ch
wFCB1Seg dw ?
wFCB2Ofs dw 6Ch
wFCB2Seg dw ?

Main proc near
assume DS:code
ifdef DEBUG
  mov AH,0Dh                            ;prevent loss of source code
  int 21h
endif
  mov SP,ProgramSizeInParas*16+512      ;leave all memory for the emulator
  mov BX,ProgramSizeInParas+512/16
  mov AH,4Ah
  int 21h                               ;ES must be set to CS here
  mov AX,352Fh                          ;enable detection interrupt
  int 21h
  mov OldInt2F[0],BX
  mov OldInt2F[2],ES
  mov DX,offset NewInt2F
  mov AX,252Fh
  int 21h
  call Init
  mov AX,DS:[002Ch]                    ;execute emulator as child process
  mov wEnvSeg,AX
  mov wCmdSeg,CS
  mov wFCB1Seg,CS
  mov wFCB2Seg,CS
  mov BX,offset wEnvSeg
  push CS
  pop ES
  mov DX,offset acEmulator
  mov AX,4B00h
  int 21h
  jnc NoError
  mov SI,offset acEmulator
  call PrintSI
  call Print
  db ": error executing child process",13,10,0
NoError:
  call Exit
  mov DX,OldInt2F[0]                    ;restore detection interrupt
  mov DS,OldInt2F[2]
  mov AX,252Fh
  int 21h
  mov AH,4Dh                            ;pass emulator exit code
  int 21h
  mov AH,4Ch
  int 21h
Main endp

ProgramSizeInParas equ ($-EntryPoint+100h+15)/16
CODE ends
end EntryPoint
