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