;--- Sample RAM implementation of a MSX-UNAPI specification ; By Konami Man, 7-2007 ; ; This code implements a sample mathematical specification, "SIMPLE_MATH", ; which has just two functions: ; Function 1: Returns HL = L + E ; Function 2: Returns HL = L * E ; The code installs on a mapped RAM segment. The RAM helper is installed ; if necessary. ; ; To create your own implementation: ; 1) In the "Constants" section, modify the API version and implementation version ; constants, as well as MAX_FN and MAX_IMPFN. ; 2) In the "Functions code" section, leave FN_INFO unmodified, ; and add your own functions. ; 3) Add an entry for each function in the "Functions addresses table" section. ; 4) In the "Data" section, modify the specification identifier ; and the implementation identifier. ; ; Optional improvements: ; - Add support for DOS 1 (DOS 2 is currently needed for the mapper support routines). ; - Add code for uninstallation. ;******************* ;*** CONSTANTS *** ;******************* ;--- System variables and routines _TERM0: equ #00 _STROUT: equ #09 ENDTPA: equ #0006 ENASLT: equ #0024 HIMSAV: equ #F349 EXPTBL: equ #FCC1 EXTBIO: equ #FFCA SLTWRK: equ #FD09 ARG: equ #F847 ;--- API version and implementation version API_V_P: equ 1 API_V_S: equ 0 ROM_V_P: equ 1 ROM_V_S: equ 0 ;--- Maximum number of available standard and implementation-specific function numbers ;Must be 0 to 127 MAX_FN: equ 2 ;Must be either zero (if no implementation-specific functions available), or 128 to 254 MAX_IMPFN: equ 0 ;*************************** ;*** INSTALLATION CODE *** ;*************************** org #100 ;--- Shows welcome message ld de,WELCOME_S ld c,_STROUT call 5 ;--- Check if it is already installed. ; Do this by searching all the SIMPLE_MATH ; implementations installed, and comparing ; the implementation name of each one with ; our implementation name. ;* Search the RAM helper, ; we will use it for RAM implementations. ; If not installed, the address will be 0, ; but we will not call it anyway since ; there will be no RAM implementations. ld de,#2222 ld hl,0 ld a,#FF call EXTBIO ld (HELPER_ADD),hl ;* Copy the implementation identifier to ARG ld hl,UNAPI_ID-SEG_CODE_START+SEG_CODE ld de,ARG ld bc,UNAPI_ID_END-UNAPI_ID ldir ;* Obtain the number of installed implementations ld de,#2222 xor a ld b,0 call EXTBIO ld a,b or a jr z,NOT_INST ;>>> The loop for each installed implementations ; starts here, with A=implementation index IMPL_LOOP: push af ;* Obtain the slot, segment and entry point ; for the implementation ld de,#2222 call EXTBIO ld (ALLOC_SLOT),a ld a,b ld (ALLOC_SEG),a ld (IMPLEM_ENTRY),hl ;* If the implementation is in page 3 ; or in ROM, skip it ld a,h and %10000000 jr nz,NEXT_IMP ld a,b cp #FF jr z,NEXT_IMP ;* Call the routine for obtaining ; the implementation information ld a,(ALLOC_SLOT) ld iyh,a ld a,(ALLOC_SEG) ld iyl,a ld ix,(IMPLEM_ENTRY) ld hl,(HELPER_ADD) xor a call CALL_HL ;Returns HL=name address ;* Compare the name of the implementation ; against our own name ld a,(ALLOC_SEG) ld b,a ld de,APIINFO-SEG_CODE_START+SEG_CODE ld ix,(HELPER_ADD) inc ix inc ix inc ix ;Now IX=helper routine to read from segment NAME_LOOP: ld a,(ALLOC_SLOT) push bc push de push hl push ix call CALL_IX pop ix push hl push de push bc ld c,a ld a,(de) cp c jr nz,NEXT_IMP or a inc hl inc de jr nz,NAME_LOOP ;* The names match: already installed ld de,ALINST_S ld c,_STROUT call 5 ld c,_TERM0 jp 5 ;* Names don't match: go to the next implementation NEXT_IMP: pop af dec a jr nz,IMPL_LOOP ;* No more implementations: ; continue installation process NOT_INST: ;--- Obtain the mapper support routines table xor a ld de,#0402 call EXTBIO or a jr nz,OK_MAPPER ld de,NOMAPPER_S ;Terminate if no mapper support routines available ld c,_STROUT call 5 ld c,_TERM0 jp 5 OK_MAPPER: ld a,b ld (PRIM_SLOT),a ld de,ALL_SEG ld bc,15*3 ldir ;--- Try to allocate a segment ld a,(PRIM_SLOT) or %00100000 ;Try primary mapper, then try others ld b,a ld a,1 ;System segment call ALL_SEG jr nc,ALLOC_OK ld de,NOFREE_S ;Terminate if no free segments available ld c,_STROUT call 5 ld c,_TERM0 jp 5 ALLOC_OK: ld (ALLOC_SEG),a ld a,b ld (ALLOC_SLOT),a ;--- Install the RAM helper if not installed ld hl,(HELPER_ADD) ld a,h or l call z,INST_HELPER ;--- Switch segment, copy code, and setup data call GET_P1 ;Backup current segment ld (P1_SEG),a ld a,(ALLOC_SLOT) ;Switch slot and segment ld h,#40 call ENASLT ld a,(ALLOC_SEG) call PUT_P1 ld hl,#4000 ;Clear the segment first ld de,#4001 ld bc,#4000-1 ld (hl),0 ldir ld hl,SEG_CODE ;Copy the code to the segment ld de,#4000 ld bc,SEG_CODE_END-SEG_CODE_START ldir ld hl,(ALLOC_SLOT) ;Setup slot and segment information ld (MY_SLOT),hl ;* Now backup and patch the EXTBIO hook ; so that it calls address #4010 of the allocated segment ld hl,EXTBIO ld de,OLD_EXTBIO ld bc,5 ldir di ld a,#CD ;Code for "CALL" ld (EXTBIO),a ld hl,(HELPER_ADD) ld bc,C40100001-P3_HSTART0001 add hl,bc ;Now HL points to the routine to call address #4010 ld (EXTBIO+1),hl ld hl,(ALLOC_SLOT) ld (EXTBIO+3),hl ei ;--- Restore slot and segment, and terminate ld a,(PRIM_SLOT) ld h,#40 call ENASLT ld a,(P1_SEG) call PUT_P1 ld de,OK_S ld c,_STROUT call 5 ld a,(HELPER_INST) or a ld c,_TERM0 jp z,5 ;--- If RAM helper has been installed, we need to jump to BASIC and do _SYSTEM ld hl,SystemProg ld de,08000h ld bc,0200h ldir jp 08000h SystemProg: ld a,(0FCC1h) push af ld h,0 call 024h pop af ld h,040h call 024h xor a ld hl,0F41Fh ld (0F860h),hl ld hl,0F423h ld (0F41Fh),hl ld (hl),a ld hl,0F52Ch ld (0F421h),hl ld (hl),a ld hl,0F42Ch ld (0F862h),hl ld hl,#8030 jp 04601h ;The following is copied to address #8030 db 03Ah,0CAh ;Tokens for ":CALL" db "SYSTEM" db 0 ;>>> Routine for installing the RAM helper. INST_HELPER: ;--- Check that TPA end address is at least #C1000 ld a,(ENDTPA+1) cp #C1 jr nc,OK_TPA ld de,NOTPA_S ld c,_STROUT call 5 ld c,_TERM0 jp 5 OK_TPA: ;--- Allocate space on system work area on page 3 ld hl,(HIMSAV) ld bc,P3_HEND0001-P3_HSTART0001 or a sbc hl,bc ld (HIMSAV),hl ;--- Prepare the two copies of the page 3 code ld hl,EXTBIO ld de,HOLDEXT0001 ld bc,5 ldir ld hl,EXTBIO ld de,HOLDEXT0002 ld bc,5 ldir ld hl,PUT_P1 ld de,PUT_P10001 ld bc,6 ldir ld hl,PUT_P1 ld de,PUT_P10002 ld bc,6 ldir ;--- Copy the code to page 3, patching relative addresses ld hl,P3_HCODE1 ld de,P3_HCODE2 ld ix,(HIMSAV) ld iy,(HIMSAV) ld bc,P3_HEND0001-P3_HSTART0001 call REALLOC ;--- Patch the EXTBIO hook di ld a,#C3 ;Code for "JP" ld (EXTBIO),a ld hl,(HIMSAV) ld bc,NEWEXT0001-P3_HSTART0001 add hl,bc ld (EXTBIO+1),hl ld a,#C9 ;Code for "RET" ld (EXTBIO+3),a ld (EXTBIO+4),a ei ;--- All done, set the helper address and return ld a,1 ld (HELPER_INST),a ld hl,(HIMSAV) ld (HELPER_ADD),hl ret ;>>> This routine reallocates a piece of code ; based on two different copies assembled in different locations. ; Input: HL = Address of first copy ; DE = Address of second copy ; IX = Target reallocation address ; IY = Address where the patched code will be placed ; BC = Length of code REALLOC: push iy push bc push de push hl ;First copy code "as is" push iy ;(HL to IY, length BC) pop de ldir pop hl pop de push de pop iy ;IY = Second copy ld b,h ld c,l push ix pop hl or a sbc hl,bc ld b,h ld c,l ;BC = Distance to sum (IX - HL) exx pop bc exx pop ix ;Originally IY ;At this point: IX = Destination ; IY = Second copy ; BC = Distance to sum (new dir - 1st copy) ; BC'= Length REALOOP: ld a,(ix) cp (iy) jr z,NEXT ;If no differences, go to next byte ld l,a ld h,(ix+1) ;HL = Data to be changed add hl,bc ;HL = Data changed ld (ix),l ;IX = Address of the data to be changed ld (ix+1),h call CHKCOMP jr z,ENDREALL inc ix inc iy NEXT: inc ix ;Next byte to compare inc iy ;(if we have done substitution, we need to increase twice) call CHKCOMP jr nz,REALOOP ENDREALL: ret CHKCOMP: exx dec bc ;Decrease counter, if it reaches 0, ld a,b ;return with Z=1 or c exx ret ;>>> Other auxiliary code CALL_IX: jp (ix) CALL_HL: jp (hl) ;>>> Data for the installation code PRIM_SLOT: db 0 ;Primary mapper slot number P1_SEG: db 0 ;Segment number for TPA on page 1 ALLOC_SLOT: db 0 ;Slot for the allocated segment ALLOC_SEG: db 0 ;Allocated segment HELPER_INST: db 0 ;Not zero if we have installed the RAM helper HELPER_ADD: dw 0 ;Address of the RAM helper jump table IMPLEM_ENTRY: dw 0 ;Entry point for implementations ;DOS 2 mapper support routines ALL_SEG: ds 3 FRE_SEG: ds 3 RD_SEG: ds 3 WR_SEG: ds 3 CAL_SEG: ds 3 CALLS: ds 3 PUT_PH: ds 3 GET_PH: ds 3 PUT_P0: ds 3 GET_P0: ds 3 PUT_P1: ds 3 GET_P1: ds 3 PUT_P2: ds 3 GET_P2: ds 3 PUT_P3: ds 3 ;>>> Strings for the installation code WELCOME_S: db "UNAPI Sample RAM implementation 1.0 (SIMPLE_MATH)",13,10 db "(c) 2007 by Konamiman",13,10 db 13,10 db "$" NOMAPPER_S: db "*** ERROR: No mapper support routines found (running DOS 1?)",13,10,"$" NOFREE_S: db "*** ERROR: Could not allocate any RAM segment",13,10,"$" NOTPA_S: db "*** ERROR: Not enough TPA space",13,10,"$" OK_S: db "Installed. Have fun!",13,10,"$" ALINST_S: db "*** Already installed.",13,10,"$" ;************************************** ;*** RAM HELPER CODE (for page 3) *** ;************************************** ;This code is defined as a macro so that it is easy ;to assemble it twice in order to reallocate it. P3_HCODE: macro P3_HSTART@sym: ;--- Hook and jump table area HOLDEXT@sym: ds 5 ;Old EXTBIO hook PUT_P1@sym: ds 3 ;To be filled with routine PUT_P1 GET_P1@sym: ds 3 ;To be filled with routine GET_P1 CALLRAM@sym: jp _CALLRAM@sym READRAM@sym: jp _READRAM@sym C4010@sym: jp _C4010@sym ;--- New destination of the EXTBIO hook NEWEXT@sym: push af inc a jr nz,IGNORE@sym ld a,d cp #22 jr nz,IGNORE@sym ld a,e cp #22 jr nz,IGNORE@sym ld hl,CALLRAM@sym ;Address of the jump table pop af ret IGNORE@sym: pop af jr HOLDEXT@sym ;--- Routine to call code in a RAM segment ; Input: ; IYh = Slot number ; IYl = Segment number ; IX = Target routine address (must be a page 1 address) ; AF, BC, DE, HL = Parameters for the target routine ;Output: ; AF, BC, DE, HL, IX, IY = Parameters returned from the target ; routine _CALLRAM@sym: call SETSLOT@sym call CALLIX@sym jp RESTSLOT@sym ;--- Routine to read a byte from a RAM segment ;Input: ; A = Slot number ; B = Segment number ; HL = Address to be read from ; (higher two bits will be ignored) ;Output: ; A = Data readed from the specified address ; BC, DE preserved _READRAM@sym: ld iyh,a ld a,b ld iyl,a push hl pop ix call SETSLOT@sym ld a,(ix) jp RESTSLOT@sym ;--- Routine to call address #4010 on a RAM segment ; Input: ; AF, BC, DE, HL = Parameters for the target routine ;Output: ; AF, BC, DE, HL, IX, IY = Parameters returned from the target ; routine ;Call as: ; CALL C4010 ; ;C4010: ; CALL ; DB ; DB _C4010@sym: push hl pop ix ex (sp),iy ld h,(iy) inc iy ld l,(iy) inc iy ex (sp),iy push hl pop iy push ix pop hl ld ix,#4010 inc sp inc sp jr _CALLRAM@sym ;--- Routine to save current slot & segment, and switch ; slot IYh, segment IYl SETSLOT@sym: push af push bc push de push hl push ix push iy call GETSLOT1@sym ld (SAVESLOT@sym),a call GET_P1@sym ld (SAVESEG@sym),a ld a,iyh ld h,#40 call ENASLT pop iy ld a,iyl call PUT_P1@sym pop ix pop hl pop de pop bc pop af ret ;--- Routine to restore slot & segment RESTSLOT@sym: push af push bc push de push hl push ix push iy ld a,(SAVESLOT@sym) ld h,#40 call ENASLT ld a,(SAVESEG@sym) call PUT_P1@sym pop iy pop ix pop hl pop de pop bc pop af ret ;--- Routine to obtain the slot connected on page 1 GETSLOT1@sym: di exx in a,(#A8) ld e,a and %00001100 sra a sra a ld c,a ;C = Slot ld b,0 ld hl,EXPTBL add hl,bc bit 7,(hl) jr z,NOEXP1@sym EXP1@sym: inc hl inc hl inc hl inc hl ld a,(hl) and %00001100 or c or #80 ld c,a NOEXP1@sym: ld a,c exx ei ret ;--- Temporary data CALLIX@sym: jp (ix) SAVESLOT@sym: db 0 SAVESEG@sym: db 0 P3_HEND@sym: endm ;These are the two copies of the code above P3_HCODE1: P3_HCODE P3_HCODE2: P3_HCODE ;********************************************* ;*** DATA TO BE INSTALLED ON RAM SEGMENT *** ;********************************************* SEG_CODE: org #4000 SEG_CODE_START: ds 16 ;=============================== ;=== EXTBIO hook execution === ;=============================== ;>>> Note that this code starts exactly at address #4010 DO_EXTBIO: push hl push bc push af ld a,d cp #22 jr nz,JUMP_OLD cp e jr nz,JUMP_OLD ;Check API ID ld hl,UNAPI_ID ld de,ARG LOOP: ld a,(de) call TOUPPER cp (hl) jr nz,JUMP_OLD2 inc hl inc de or a jr nz,LOOP ;A=255: Jump to old hook pop af push af inc a jr z,JUMP_OLD2 ;A=0: B=B+1 and jump to old hook pop af pop bc or a jr nz,DO_EXTBIO2 inc b pop hl ld de,#2222 jp OLD_EXTBIO DO_EXTBIO2: ;A=1: Return A=Slot, B=Segment, HL=UNAPI entry address dec a jr nz,DO_EXTBIO3 pop hl ld a,(MY_SEG) ld b,a ld a,(MY_SLOT) ld hl,UNAPI_ENTRY ld de,#2222 ret ;A>1: A=A-1, and jump to old hook DO_EXTBIO3: ;A=A-1 already done pop hl ld de,#2222 jp OLD_EXTBIO ;--- Jump here to execute old EXTBIO code JUMP_OLD2: ld de,#2222 JUMP_OLD: ;Assumes "push hl,bc,af" done pop af pop bc pop hl ;Old EXTBIO hook contents is here ;(it is setup at installation time) OLD_EXTBIO: ds 5 ;==================================== ;=== Functions entry point code === ;==================================== UNAPI_ENTRY: push hl push af ld hl,FN_TABLE bit 7,a if MAX_IMPFN >= 128 jr z,IS_STANDARD ld hl,IMPFN_TABLE and %01111111 cp MAX_IMPFN-128 jr z,OK_FNUM jr nc,UNDEFINED IS_STANDARD: else jr nz,UNDEFINED endif cp MAX_FN jr z,OK_FNUM jr nc,UNDEFINED OK_FNUM: add a,a push de ld e,a ld d,0 add hl,de pop de ld a,(hl) inc hl ld h,(hl) ld l,a pop af ex (sp),hl ret ;--- Undefined function: return with registers unmodified UNDEFINED: pop af pop hl ret ;=================================== ;=== Functions addresses table === ;=================================== ;--- Standard routines addresses table FN_TABLE: FN_0: dw FN_INFO FN_1: dw FN_ADD FN_2: dw FN_MULT ;--- Implementation-specific routines addresses table if MAX_IMPFN >= 128 IMPFN_TABLE: FN_128: dw FN_DUMMY endif ;======================== ;=== Functions code === ;======================== ;--- Mandatory routine 0: return API information ; Input: A = 0 ; Output: HL = Descriptive string for this implementation, on this slot, zero terminated ; DE = API version supported, D.E ; BC = This implementation version, B.C. ; A = 0 and Cy = 0 FN_INFO: ld bc,256*ROM_V_P+ROM_V_S ld de,256*API_V_P+API_V_S ld hl,APIINFO xor a ret ;--- Sample routine 1: adds two 8-bit numbers ; Input: E, L = Numbers to add ; Output: HL = Result FN_ADD: ld h,0 ld d,0 add hl,de ret ;--- Sample routine 2: multiplies two 8-bit numbers ; Input: E, L = Numbers to multiply ; Output: HL = Result FN_MULT: ld b,e ld e,l ld d,0 ld hl,0 MULT_LOOP: add hl,de djnz MULT_LOOP ret ;============================ ;=== Auxiliary routines === ;============================ ;--- Convert a character to upper-case if it is a lower-case letter TOUPPER: cp "a" ret c cp "z"+1 ret nc and #DF ret ;============================ ;=== UNAPI related data === ;============================ ;This data is setup at installation time MY_SLOT: db 0 MY_SEG: db 0 ;--- Specification identifier (up to 15 chars) UNAPI_ID: db "SIMPLE_MATH",0 UNAPI_ID_END: ;--- Implementation name (up to 63 chars and zero terminated) APIINFO: db "Konamiman's RAM implementation of SIMPLE_MATH UNAPI",0 SEG_CODE_END: