Example program for the Cidco MailStation Z80 computer
at main 815 lines 16 kB view raw
1; vim:syntax=z8a:ts=8 2; 3; putchar for 5x8 font 4; Copyright (c) 2019-2021 joshua stein <jcs@jcs.org> 5; 6; Permission to use, copy, modify, and distribute this software for any 7; purpose with or without fee is hereby granted, provided that the above 8; copyright notice and this permission notice appear in all copies. 9; 10; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11; WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12; MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13; ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14; WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15; ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16; OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17; 18 19 .module putchar 20 21 .include "mailstation.inc" 22 23 ; screen contents (characters) array in upper memory 24 .equ _screenbuf, #0xc000 25 .equ _screenbufend, #0xc3ff 26 27 ; per-character attributes array in upper memory 28 .equ _screenattrs, #0xc400 29 .equ _screenattrsend, #0xc7ff 30 31 .area _DATA 32 33font_data:: 34 .include "font/spleen-5x8.inc" 35 36 ; lookup table for putchar 37 ; left-most 5 bits are col group for lcd_cas 38 ; last 3 bits are offset into col group 39cursorx_lookup_data:: 40 .include "cursorx_lookup.inc" 41 42_cursorx:: ; cursor x position, 0-indexed 43 .db #0 44_cursory:: ; cursor y position, 0-indexed 45 .db #0 46_saved_cursorx:: ; cursor x position, 0-indexed 47 .db #0 48_saved_cursory:: ; cursor y position, 0-indexed 49 .db #0 50_putchar_sgr:: ; current SGR for putchar() 51 .db #0 52 53 54 .area _CODE 55 56; void screen_init(void) 57; quick initialization of the screen 58_screen_init:: 59 push hl 60 call _clear_screen_bufs 61 call _clear_screen 62 call _recursor 63 ld hl, #0 64 push hl 65 push hl 66 call _stamp_char 67 pop hl 68 pop hl 69 pop hl 70 ret 71 72 73; void lcd_cas(unsigned char col) 74; enable CAS, address the LCD column col (in h), and disable CAS 75_lcd_cas:: 76 push ix 77 ld ix, #0 78 add ix, sp 79 push de 80 push hl 81 ld hl, (p2shadow) 82 ld a, (hl) 83 and #0b11110111 ; CAS(0) - turn port2 bit 3 off 84 ld (hl), a 85 out (#0x02), a ; write p2shadow to port2 86 ld de, #LCD_START 87 ld a, 4(ix) 88 ld (de), a ; write col argument 89 ld a, (hl) 90 or #0b00001000 ; CAS(1) - turn port2 bit 3 on 91 ld (hl), a 92 out (#0x02), a 93 pop hl 94 pop de 95 ld sp, ix 96 pop ix 97 ret 98 99 100; void clear_screen(void) 101_clear_screen:: 102 di 103 push hl 104 in a, (#SLOT_DEVICE) 105 ld h, a 106 in a, (#SLOT_PAGE) 107 ld l, a 108 push hl 109 ld a, #DEVICE_LCD_RIGHT 110 out (#SLOT_DEVICE), a 111 call _clear_lcd_half 112 ld a, #DEVICE_LCD_LEFT 113 out (#SLOT_DEVICE), a 114 call _clear_lcd_half 115 pop hl 116 ld a, h 117 out (#SLOT_DEVICE), a 118 ld a, l 119 out (#SLOT_PAGE), a 120 pop hl 121 ei 122 ret 123 124_clear_screen_bufs:: 125 di 126 push bc 127 push de 128 push hl 129 xor a 130 ld (_cursorx), a 131 ld (_cursory), a 132 ld (_saved_cursorx), a 133 ld (_saved_cursory), a 134 ld (_putchar_sgr), a 135zero_screenbuf: 136 ld hl, #_screenbuf 137 ld de, #_screenbuf + 1 138 ld bc, #_screenbufend - _screenbuf 139 ld (hl), #' ' 140 ldir 141zero_screenattrs: 142 ld hl, #_screenattrs 143 ld de, #_screenattrs + 1 144 ld bc, #_screenattrsend - _screenattrs 145 ld (hl), #0 146 ldir 147clear_screen_out: 148 pop hl 149 pop de 150 pop bc 151 ei 152 ret 153 154 155; void clear_lcd_half(void) 156; zero out the current LCD module (must already be in SLOT_DEVICE) 157; from v2.54 firmware at 0x2490 158_clear_lcd_half:: 159 push bc 160 push de 161 ld b, #20 ; do 20 columns total 162clear_lcd_column: 163 ld h, #0 164 ld a, b 165 dec a ; columns are 0-based 166 ld l, a 167 push hl 168 call _lcd_cas 169 pop hl 170 push bc ; preserve our column counter 171 ld hl, #LCD_START 172 ld (hl), #0 ; zero out hl, then copy it to de 173 ld de, #LCD_START + 1 ; de will always be the next line 174 ld bc, #128 - 1 ; iterate (LCD_HEIGHT - 1) times 175 ldir ; ld (de), (hl), bc-- until 0 176 pop bc ; restore column counter 177 djnz clear_lcd_column ; column--, if not zero keep going 178clear_done: 179 pop de 180 pop bc 181 ret 182 183 184; void redraw_screen(void) 185_redraw_screen:: 186 push bc 187 push de 188 push hl 189 ld b, #0 190redraw_rows: 191 ld d, b ; store rows in d 192 ld b, #0 193redraw_cols: 194 push bc ; XXX figure out what is corrupting 195 push de ; bc and de in stamp_char, these shouldn't be needed 196 push hl 197 ld h, #0 ; cols 198 ld l, b 199 push hl 200 ld h, #0 ; rows 201 ld l, d 202 push hl 203 call _stamp_char 204 pop hl 205 pop hl 206 pop hl 207 pop de 208 pop bc 209redraw_cols_next: 210 inc hl 211 inc b 212 ld a, b 213 cp #LCD_COLS 214 jr nz, redraw_cols 215 ld b, d 216 inc b 217 ld a, b 218 cp #LCD_ROWS 219 jr nz, redraw_rows 220redraw_screen_out: 221 pop hl 222 pop de 223 pop bc 224 ret 225 226 227; void scroll_lcd(void) 228; scroll entire screen up by FONT_HEIGHT rows, minus statusbar 229_scroll_lcd:: 230 di 231 push bc 232 push de 233 push hl 234 in a, (#SLOT_DEVICE) 235 ld h, a 236 in a, (#SLOT_PAGE) 237 ld l, a 238 push hl 239 ld a, #DEVICE_LCD_LEFT 240 out (#SLOT_DEVICE), a 241 call _scroll_lcd_half 242 ld a, #DEVICE_LCD_RIGHT 243 out (#SLOT_DEVICE), a 244 call _scroll_lcd_half 245 pop hl 246 ld a, h 247 out (#SLOT_DEVICE), a 248 ld a, l 249 out (#SLOT_PAGE), a 250shift_bufs: 251 ld b, #0 252screenbuf_shift_loop: 253 ld h, b 254 ld l, #0 255 call screenbuf_offset 256 ld de, #_screenbuf 257 add hl, de ; hl = screenbuf[b * LCD_COLS] 258 push hl 259 ld de, #LCD_COLS 260 add hl, de ; hl += LCD_COLS 261 pop de ; de = screenbuf[b * LCD_COLS] 262 push bc 263 ld bc, #LCD_COLS 264 ldir ; ld (de), (hl), de++, hl++, bc-- 265 pop bc 266 inc b 267 ld a, b 268 cp #TEXT_ROWS - 1 269 jr nz, screenbuf_shift_loop 270screenattrs_shift: 271 ld b, #0 272screenattrs_shift_loop: 273 ld h, b 274 ld l, #0 275 call screenbuf_offset 276 ld de, #_screenattrs 277 add hl, de ; hl = screenattrs[b * LCD_COLS] 278 push hl 279 ld de, #LCD_COLS 280 add hl, de 281 pop de 282 push bc 283 ld bc, #LCD_COLS 284 ldir 285 pop bc 286 inc b 287 ld a, b 288 cp #TEXT_ROWS - 1 289 jr nz, screenattrs_shift_loop 290last_row_zero: 291 ld a, #TEXT_ROWS - 1 292 ld h, a 293 ld l, #0 294 call screenbuf_offset 295 ld de, #_screenbuf 296 add hl, de 297 ld d, #0 298 ld e, #LCD_COLS - 1 299 add hl, de 300 ld b, #LCD_COLS 301 ld a, (_putchar_sgr) 302last_row_zero_loop: 303 ld (hl), #' ' 304 dec hl 305 djnz last_row_zero_loop 306scroll_lcd_out: 307 pop hl 308 pop de 309 pop bc 310 ei 311 ret 312 313 314; void scroll_lcd_half(void) 315; scroll current LCD module up by FONT_HEIGHT rows, minus statusbar and 316; zero out the last line of text (only to the LCD) 317_scroll_lcd_half:: 318 push ix 319 ld ix, #0 320 add ix, sp 321 push bc 322 push de 323 push hl 324 ; alloc 2 bytes on the stack for local storage 325 push hl 326 ld a, #LCD_HEIGHT - (FONT_HEIGHT * 2) ; iterations of pixel row moves 327scroll_init: 328 ld -1(ix), a ; store iterations 329 ld b, #20 ; do 20 columns total 330scroll_lcd_column: 331 ld -2(ix), b ; store new column counter 332 ld a, b 333 sub #1 ; columns are 0-based 334 ld h, #0 335 ld l, a 336 push hl 337 call _lcd_cas 338 pop hl 339scroll_rows: 340 ld b, #0 341 ld c, -1(ix) ; bc = row counter 342 ld hl, #LCD_START + 8 ; start of next line 343 ld de, #LCD_START 344 ldir ; ld (de), (hl), bc-- until 0 345scroll_zerolast: 346 ld hl, #LCD_START 347 ld d, #0 348 ld e, -1(ix) 349 add hl, de 350 ld b, #FONT_HEIGHT 351scroll_zerolastloop: ; 8 times: zero hl, hl++ 352 ld (hl), #0 353 inc hl 354 djnz scroll_zerolastloop 355 ld b, -2(ix) 356 djnz scroll_lcd_column ; column--, if not zero keep going 357 pop hl 358 pop de 359 pop bc 360 ld sp, ix 361 pop ix 362 ret 363 364 365; address of screenbuf or screenattrs offset for a row/col in hl, returns in hl 366screenbuf_offset: 367 push bc 368 push de 369 ; uses hl 370 ex de, hl 371 ld hl, #0 372 ld a, d ; row 373 cp #0 374 jr z, multiply_srow_out ; only add rows if > 0 375 ld bc, #LCD_COLS 376multiply_srow: 377 add hl, bc 378 dec a 379 cp #0 380 jr nz, multiply_srow 381multiply_srow_out: 382 ld d, #0 ; col in e 383 add hl, de ; hl = (row * LCD_COLS) + col 384 pop de 385 pop bc 386 ret ; hl 387 388 389; void stamp_char(unsigned int row, unsigned int col) 390; row at 4(ix), col at 6(ix) 391_stamp_char:: 392 push ix 393 ld ix, #0 394 add ix, sp 395 push bc 396 push de 397 push hl 398 ld hl, #-15 ; stack bytes for local storage 399 add hl, sp 400 ld sp, hl 401 in a, (#SLOT_DEVICE) 402 ld -3(ix), a ; stack[-3] = old slot device 403 in a, (#SLOT_PAGE) 404 ld -4(ix), a ; stack[-4] = old slot page 405find_char: 406 ld h, 4(ix) 407 ld l, 6(ix) 408 call screenbuf_offset 409 push hl 410 ld de, #_screenbuf 411 add hl, de ; hl = screenbuf[(row * LCD_COLS) + col] 412 ld a, (hl) 413 ld -5(ix), a ; stack[-5] = character to stamp 414 pop hl 415 ld de, #_screenattrs 416 add hl, de ; hl = screenattrs[(row * LCD_COLS) + col] 417 ld a, (hl) 418 ld -6(ix), a ; stack[-6] = character attrs 419calc_font_data_base: 420 ld h, #0 421 ld l, -5(ix) ; char 422 add hl, hl ; hl = char * FONT_HEIGHT (8) 423 add hl, hl 424 add hl, hl 425 ld de, #font_data 426 add hl, de 427 ld -7(ix), l 428 ld -8(ix), h ; stack[-8,-7] = char font data base addr 429calc_char_cell_base: 430 ld h, #0 431 ld l, 4(ix) ; row 432 add hl, hl 433 add hl, hl 434 add hl, hl ; hl = row * FONT_HEIGHT (8) 435 ld de, #LCD_START 436 add hl, de ; hl = 4038 + (row * FONT_HEIGHT) 437 ld -9(ix), l 438 ld -10(ix), h ; stack[-10,-9] = lcd char cell base 439fetch_from_table: 440 ld a, 6(ix) ; col 441 ld hl, #cursorx_lookup_data 442 ld b, #0 443 ld c, a 444 add hl, bc 445 ld b, (hl) 446 ld a, b 447pluck_col_group: 448 and #0b11111000 ; upper 5 bits are col group 449 srl a 450 srl a 451 srl a 452 ld -11(ix), a ; stack[-11] = col group 453pluck_offset: 454 ld a, b 455 and #0b00000111 ; lower 3 bits are offset 456 ld -12(ix), a ; stack[-12] = offset 457 ld -15(ix), #0 ; stack[-15] = previous lcd col 458 ld d, #FONT_HEIGHT ; for (row = FONT_HEIGHT; row >= 0; row--) 459next_char_row: 460 ld a, d 461 dec a 462 ld h, -8(ix) ; char font data base 463 ld l, -7(ix) 464 ld b, #0 465 ld c, a 466 add hl, bc 467 ld a, (hl) ; font_addr + (char * FONT_HEIGHT) + row 468 ld b, -6(ix) 469 bit #ATTR_BIT_REVERSE, b 470 jr nz, reverse 471 bit #ATTR_BIT_CURSOR, b 472 jr nz, reverse 473 jr not_reverse 474reverse: 475 cpl ; flip em 476 and #0b00011111 ; mask off bits not within FONT_WIDTH 477not_reverse: 478 ld -13(ix), a ; stack[-13] = working font data 479 ld a, -6(ix) 480 bit #ATTR_BIT_UNDERLINE, a 481 jr z, not_underline 482 ld a, d 483 cp #FONT_HEIGHT 484 jr nz, not_underline 485underline: 486 ld -13(ix), #0xff 487not_underline: 488 ld a, 6(ix) ; col 489 cp #LCD_COLS / 2 ; assume a char never spans both LCD sides 490 jr nc, rightside 491leftside: 492 ld a, #DEVICE_LCD_LEFT 493 jr swap_lcd 494rightside: 495 ld a, #DEVICE_LCD_RIGHT 496swap_lcd: 497 out (#SLOT_DEVICE), a 498 ld e, #FONT_WIDTH ; for (col = FONT_WIDTH; col > 0; col--) 499next_char_col: ; inner loop, each col of each row 500 ld -14(ix), #0b00011111 ; font data mask that will get shifted 501determine_cas: 502 ld c, #0 503 ld b, -11(ix) ; col group 504 ld a, -12(ix) ; bit offset 505 add #FONT_WIDTH 506 sub e ; if offset+(5-col) is >= 8, advance col 507 cp #LCD_COL_GROUP_WIDTH 508 jr c, skip_advance ; if a >= 8, advance (dec b) 509 dec b 510 ld c, -12(ix) ; bit offset 511 ld a, #LCD_COL_GROUP_WIDTH 512 sub c 513 ld c, a ; c = number of right shifts 514skip_advance: 515do_lcd_cas: 516 ld a, -15(ix) ; previous lcd cas 517 cp b 518 jr z, prep_right_shift 519 ld h, #0 520 ld l, b 521 push hl 522 call _lcd_cas 523 pop hl 524 ld -15(ix), b ; store lcd col for next round 525 ; if this character doesn't fit entirely in one lcd column, we need to 526 ; span two of them and on the left one, shift font data and masks right 527 ; to remove right-most bits that will be on the next column 528prep_right_shift: 529 ld a, c 530 cp #0 531 jr z, prep_left_shift 532 ld b, c 533 ld c, -14(ix) ; matching mask 00011111 534 ld a, -13(ix) ; load font data like 00010101 535right_shift: 536 srl a ; shift font data right #b times 537 srl c ; and mask to match 538 djnz right_shift ; -> 10101000 539 ld -14(ix), c 540 jr done_left_shift 541prep_left_shift: 542 ld c, -14(ix) ; mask 543 ld a, -12(ix) ; (bit offset) times, shift font data 544 cp #0 545 ld b, a 546 ld a, -13(ix) ; read new font data 547 jr z, done_left_shift 548left_shift: 549 sla a 550 sla c 551 djnz left_shift 552done_left_shift: 553 ld b, a 554 ld a, c 555 cpl 556 ld -14(ix), a ; store inverted mask 557 ld a, b 558read_lcd_data: 559 ld h, -10(ix) 560 ld l, -9(ix) 561 ld b, a 562 ld a, d 563 dec a 564 ld c, a 565 ld a, b 566 ld b, #0 567 add hl, bc ; hl = 4038 + (row * FONT_HEIGHT) + row - 1 568 ld b, a ; store new font data 569 ld a, (hl) ; read existing cell data 570 and -14(ix) ; mask off new char cell 571 or b ; combine data into cell 572 ld (hl), a 573 dec e 574 jp nz, next_char_col 575 dec d 576 jp nz, next_char_row 577stamp_char_out: 578 ld a, -3(ix) ; restore old slot device 579 out (#SLOT_DEVICE), a 580 ld a, -4(ix) ; restore old slot page 581 out (#SLOT_PAGE), a 582 ld hl, #15 ; remove stack bytes 583 add hl, sp 584 ld sp, hl 585 pop hl 586 pop de 587 pop bc 588 ld sp, ix 589 pop ix 590 ret 591 592 593; void uncursor(void) 594; remove cursor attribute from old cursor position 595_uncursor:: 596 push de 597 push hl 598 ld a, (_cursory) 599 ld h, a 600 ld a, (_cursorx) 601 ld l, a 602 call screenbuf_offset 603 ld de, #_screenattrs 604 add hl, de ; screenattrs[(cursory * TEXT_COLS) + cursorx] 605 ld a, (hl) 606 res #ATTR_BIT_CURSOR, a ; &= ~(ATTR_CURSOR) 607 ld (hl), a 608 ld a, (_cursorx) 609 ld l, a 610 push hl 611 ld a, (_cursory) 612 ld l, a 613 push hl 614 call _stamp_char 615 pop hl 616 pop hl 617 pop hl 618 pop de 619 ret 620 621; void recursor(void) 622; force-set cursor attribute 623_recursor:: 624 push de 625 push hl 626 ld a, (_cursory) 627 ld h, a 628 ld a, (_cursorx) 629 ld l, a 630 call screenbuf_offset 631 ld de, #_screenattrs 632 add hl, de ; screenattrs[(cursory * TEXT_COLS) + cursorx] 633 ld a, (hl) 634 set #ATTR_BIT_CURSOR, a 635 ld (hl), a 636 pop hl 637 pop de 638 ret 639 640 641; int putchar(int c) 642_putchar:: 643 push ix 644 ld ix, #0 645 add ix, sp ; char to print is at 4(ix) 646 push de 647 push hl 648 call _uncursor 649 ld a, 4(ix) 650 cp #'\b' ; backspace 651 jr nz, not_backspace 652backspace: 653 ld a, (_cursorx) 654 cp #0 655 jr nz, cursorx_not_zero 656 ld a, (_cursory) 657 cp #0 658 jp z, putchar_fastout ; cursorx/y at 0,0, nothing to do 659 dec a 660 ld (_cursory), a ; cursory-- 661 ld a, #LCD_COLS - 2 662 ld (_cursorx), a 663 jp putchar_draw_cursor 664cursorx_not_zero: 665 dec a 666 ld (_cursorx), a ; cursorx--; 667 jp putchar_draw_cursor 668not_backspace: 669 cp #'\r' 670 jr nz, not_cr 671 xor a 672 ld (_cursorx), a ; cursorx = 0 673 jr not_crlf 674not_cr: 675 cp #'\n' 676 jr nz, not_crlf 677 xor a 678 ld (_cursorx), a ; cursorx = 0 679 ld a, (_cursory) 680 inc a 681 ld (_cursory), a ; cursory++ 682not_crlf: 683 ld a, (_cursorx) 684 cp #LCD_COLS 685 jr c, not_longer_text_cols ; cursorx < TEXT_COLS 686 xor a 687 ld (_cursorx), a ; cursorx = 0 688 ld a, (_cursory) 689 inc a 690 ld (_cursory), a 691not_longer_text_cols: 692 ld a, (_cursory) 693 cp #TEXT_ROWS 694 jr c, scroll_out 695scroll_up_screen: 696 call _scroll_lcd 697 xor a 698 ld (_cursorx), a 699 ld a, #TEXT_ROWS - 1 700 ld (_cursory), a ; cursory = TEXT_ROWS - 1 701scroll_out: 702 ld a, 4(ix) 703 cp a, #'\r' 704 jr z, cr_or_lf 705 cp a, #'\n' 706 jr z, cr_or_lf 707 jr store_char_in_buf 708cr_or_lf: 709 jp putchar_draw_cursor 710store_char_in_buf: 711 ld a, (_cursory) 712 ld h, a 713 ld a, (_cursorx) 714 ld l, a 715 call screenbuf_offset 716 push hl 717 ld de, #_screenbuf 718 add hl, de ; hl = screenbuf[(cursory * LCD_COLS) + cursorx] 719 ld a, 4(ix) 720 ld (hl), a ; store character 721 pop hl 722 ld de, #_screenattrs 723 add hl, de ; hl = screenattrs[(cursory * LCD_COLS) + cursorx] 724 ld a, (_putchar_sgr) 725 ld (hl), a ; = putchar_sgr 726 ld a, (_cursorx) 727 ld l, a 728 push hl 729 ld a, (_cursory) 730 ld l, a 731 push hl 732 call _stamp_char 733 pop hl 734 pop hl 735advance_cursorx: 736 ld a, (_cursorx) 737 inc a 738 ld (_cursorx), a 739 cp #LCD_COLS ; if (cursorx >= LCD_COLS) 740 jr c, putchar_draw_cursor 741 xor a 742 ld (_cursorx), a 743 ld a, (_cursory) 744 inc a 745 ld (_cursory), a 746check_cursory: 747 cp #TEXT_ROWS ; and if (cursory >= TEXT_ROWS) 748 jr c, putchar_draw_cursor 749 call _scroll_lcd 750 ld a, #TEXT_ROWS - 1 751 ld (_cursory), a ; cursory = TEXT_ROWS - 1 752putchar_draw_cursor: 753 ld a, (_cursory) 754 ld h, a 755 ld a, (_cursorx) 756 ld l, a 757 call screenbuf_offset 758 ld de, #_screenattrs 759 add hl, de ; hl = screenattrs[(cursory * LCD_COLS) + cursorx] 760 ld a, (hl) ; read existing attrs 761 set #ATTR_BIT_CURSOR, a 762 ld (hl), a ; = putchar_sgr | ATTR_CURSOR 763 ld a, (_cursorx) 764 ld l, a 765 push hl 766 ld a, (_cursory) 767 ld l, a 768 push hl 769 call _stamp_char 770 pop hl 771 pop hl 772putchar_fastout: 773 pop hl 774 pop de 775 ld sp, ix 776 pop ix 777 ret 778 779 780; void putchar_attr(unsigned char row, unsigned char col, char c, char attr) 781; directly manipulates screenbuf/attrs without scrolling or length checks 782; row at 4(ix), col at 5(ix), c at 6(ix), attr at 7(ix) 783_putchar_attr:: 784 push ix 785 ld ix, #0 786 add ix, sp 787 push de 788 push hl 789store_char: 790 ld h, 4(ix) 791 ld l, 5(ix) 792 call screenbuf_offset 793 push hl 794 ld de, #_screenbuf 795 add hl, de ; screenbuf[(row * TEXT_COLS) + col] 796 ld a, 6(ix) 797 ld (hl), a 798store_attrs: 799 pop hl 800 ld de, #_screenattrs 801 add hl, de ; screenattrs[(row * TEXT_COLS) + col] 802 ld a, 7(ix) 803 ld (hl), a 804 ld l, 5(ix) 805 push hl 806 ld l, 4(ix) 807 push hl 808 call _stamp_char 809 pop hl 810 pop hl 811 pop hl 812 pop de 813 ld sp, ix 814 pop ix 815 ret