; core ; ; Routines used by pretty much all parts. You will want to include it first ; in your glue file. ; *** CONSTS *** .equ ASCII_BS 0x08 .equ ASCII_CR 0x0d .equ ASCII_LF 0x0a .equ ASCII_DEL 0x7f ; *** DATA *** ; Useful data to point to, when a pointer is needed. P_NULL: .db 0 ; *** REGISTER FIDDLING *** ; add the value of A into DE addDE: push af add a, e jr nc, .end ; no carry? skip inc inc d .end: ld e, a pop af noop: ; piggy backing on the first "ret" we have ret ; copy (HL) into DE, then exchange the two, utilising the optimised HL instructions. ; ld must be done little endian, so least significant byte first. intoHL: push de ld e, (hl) inc hl ld d, (hl) ex de, hl pop de ret intoDE: ex de, hl call intoHL ex de, hl ; de preserved by intoHL, so no push/pop needed ret intoIX: push ix ex (sp), hl ;swap hl with ix, on the stack call intoHL ex (sp), hl ;restore hl from stack pop ix ret ; add the value of A into HL ; affects carry flag according to the 16-bit addition, Z, S and P untouched. addHL: push de ld d, 0 ld e, a add hl, de pop de ret ; subtract the value of A from HL ; affects flags according to the 16-bit subtraction. subHL: push de ld d, 0 ld e, a or a ;reset carry flag sbc hl, de ;There is no 'sub hl, de', so we must use sbc pop de ret ; Compare HL with DE and sets Z and C in the same way as a regular cp X where ; HL is A and DE is X. cpHLDE: push hl or a ;reset carry flag sbc hl, de ;There is no 'sub hl, de', so we must use sbc pop hl ret ; Write the contents of HL in (DE) ; de and hl are preserved, so no pushing/popping necessary writeHLinDE: ex de, hl ld (hl), e inc hl ld (hl), d dec hl ex de, hl ret ; Call the method (IX) is a pointer to. In other words, call intoIX before ; callIX callIXI: push ix call intoIX call callIX pop ix ret ; jump to the location pointed to by IX. This allows us to call IX instead of ; just jumping it. We use IX because we seldom use this for arguments. callIX: jp (ix) callIY: jp (iy) ; Ensures that Z is unset (more complicated than it sounds...) ; There are often better inline alternatives, either replacing rets with ; appropriate jmps, or if an 8 bit register is known to not be 0, an inc ; then a dec. If a is nonzero, 'or a' is optimal. unsetZ: or a ;if a nonzero, Z reset ret nz cp 1 ;if a is zero, Z reset ret ; *** STRINGS *** ; Fill B bytes at (HL) with A fill: push bc push hl .loop: ld (hl), a inc hl djnz .loop pop hl pop bc ret ; Increase HL until the memory address it points to is equal to A for a maximum ; of 0xff bytes. Returns the new HL value as well as the number of bytes ; iterated in A. ; If a null char is encountered before we find A, processing is stopped in the ; same way as if we found our char (so, we look for A *or* 0) ; Set Z if the character is found. Unsets it if not findchar: push bc ld c, a ; let's use C as our cp target ld a, 0xff ld b, a .loop: ld a, (hl) cp c jr z, .match or a ; cp 0 jr z, .nomatch inc hl djnz .loop .nomatch: call unsetZ jr .end .match: ; We ran 0xff-B loops. That's the result that goes in A. ld a, 0xff sub b cp a ; ensure Z .end: pop bc ret ; Format the lower nibble of A into a hex char and stores the result in A. fmtHex: ; The idea here is that there's 7 characters between '9' and 'A' ; in the ASCII table, and so we add 7 if the digit is >9. ; daa is designed for using Binary Coded Decimal format, where each ; nibble represents a single base 10 digit. If a nibble has a value >9, ; it adds 6 to that nibble, carrying to the next nibble and bringing the ; value back between 0-9. This gives us 6 of that 7 we needed to add, so ; then we just condtionally set the carry and add that carry, along with ; a number that maps 0 to '0'. We also need the upper nibble to be a ; set value, and have the N, C and H flags clear. or 0xf0 daa ; now a =0x50 + the original value + 0x06 if >= 0xfa add a, 0xa0 ; cause a carry for the values that were >=0x0a adc a, 0x40 ret ; Formats value in A into a string hex pair. Stores it in the memory location ; that HL points to. Does *not* add a null char at the end. fmtHexPair: push af ; let's start with the rightmost char inc hl call fmtHex ld (hl), a ; and now with the leftmost dec hl pop af push af rra \ rra \ rra \ rra call fmtHex ld (hl), a pop af ret ; Compares strings pointed to by HL and DE up to A count of characters. If ; equal, Z is set. If not equal, Z is reset. strncmp: push bc push hl push de ld b, a .loop: ld a, (de) cp (hl) jr nz, .end ; not equal? break early. NZ is carried out ; to the called cp 0 ; If our chars are null, stop the cmp jr z, .end ; The positive result will be carried to the ; caller inc hl inc de djnz .loop ; We went through all chars with success, but our current Z flag is ; unset because of the cp 0. Let's do a dummy CP to set the Z flag. cp a .end: pop de pop hl pop bc ; Because we don't call anything else than CP that modify the Z flag, ; our Z value will be that of the last cp (reset if we broke the loop ; early, set otherwise) ret ; Transforms the character in A, if it's in the a-z range, into its upcase ; version. upcase: cp 'a' ret c ; A < 'a'. nothing to do cp 'z'+1 ret nc ; A >= 'z'+1. nothing to do ; 'a' - 'A' == 0x20 sub 0x20 ret