smallCharLine:
    addiu   sp, sp, -0x50
    sw      s7, 0x44(sp)
    move    s7, a0                  ; s7 = text_ptr
    sw      s0, 0x28(sp)
    move    s0, a1                  ; s0 = X
    sw      s2, 0x30(sp)
    move    s2, a2                  ; s2 = Y
    sw      s8, 0x48(sp)
    move    s8, s0                  ; s8 = X (copy)
    lui     t0, 0xFF
    ori     t0, t0, 0xFFFF
    sw      s1, 0x2C(sp)
    andi    s1, a3, 0xFF            ; s1 = color
    move    a0, s1
    sw      s5, 0x3C(sp)
    lui     s5, 0x8008
    lui     a2, 0xFF00
    lui     v0, 0x8000
    move    t5, s5
    lw      v1, -0x6C20(s5)         ; TextDMANexChainAddr
    lw      a3, 0x60(sp)
    addiu   t5, t5, -0x6C20
    sw      ra, 0x4C(sp)
    sw      s6, 0x40(sp)
    sw      s4, 0x38(sp)
    sw      s3, 0x34(sp)

; === DMA INITIALIZATION ===
    and     a2, v1, a2
    andi    a1, a3, 0xFF
    and     v1, v1, t0
    or      s4, v1, v0              ; s4 = DMA base
    andi    a3, a3, 0x10
    sltu    a3, zero, a3
    lw      v0, 0x4(t5)             ; TextDMACounter
    lw      v1, 0x0(s4)
    addiu   v0, v0, -1
    and     v1, v1, t0
    or      a2, a2, v1
    sw      v0, 0x4(t5)
    sw      a2, -0x6C20(s5)
    jal     FUN_8001adc8
    sw      a3, 0x18(sp)

; === CHARACTER SETUP ===
    move    a0, s4
    sll     s0, s0, 0x10
    sra     a1, s0, 0x10
    sll     s2, s2, 0x10
    sra     s0, s2, 0x10
    move    a2, s0
    jal     FUN_80019d70
    move    a3, s1

; === GPU REGISTER SETUP ===
    lui     a1, 0x1F80
    ori     a1, a1, 0x0348
    li      v0, 0x2
    li      a3, 0xC
    sh      v0, 0x14(sp)
    li      v0, 0x10
    sh      a3, 0x16(sp)
    sb      v0, 0x3(a1)
    lui     v0, 0xA000
    sw      v0, 0x4(a1)
    lhu     v1, 0x9C(gp)            ; DAT_80078f40
    lhu     a0, 0x9E(gp)            ; DAT_80078f42
    lui     v0, 0x100
    lui     at, 0x1F80
    sw      v0, 0x388(at)
    lw      v0, 0x14(sp)
    lui     s3, 0x1F80
    sh      v1, 0x10(sp)
    sh     a0, 0x12(sp)
    lw      v1, 0x10(sp)
    lh      a0, 0x9C(gp)
    ori     s3, s3, 0x3D0
    sw      v0, 0xC(a1)
    lui     v0, 0x8001
    move    a2, a0
    sw      v1, 0x8(a1)

; === PATTERN SETUP ===
    lui     t5, 0x1F80
    ori     t5, t5, 0x2E0
    addiu   t8, v0, 0x3E8
    lwl     t6, 0x3(t8)
    lwr     t6, 0x0(t8)
    lwl     t7, 0x7(t8)
    lwr     t7, 0x4(t8)
    swl     t6, 0x3(t5)
    swr     t6, 0x0(t5)
    swl     t7, 0x7(t5)
    swr     t7, 0x4(t5)
    lwl     t6, 0xB(t8)
    lwr     t6, 0x8(t8)
    lwl     t7, 0xF(t8)
    lwr     t7, 0xC(t8)
    swl     t6, 0xB(t5)
    swr     t6, 0x8(t5)
    swl     t7, 0xF(t5)
    swr     t7, 0xC(t5)
    lwl     t6, 0x13(t8)
    lwr     t6, 0x10(t8)
    lwl     t7, 0x17(t8)
    lwr     t7, 0x14(t8)
    swl     t6, 0x13(t5)
    swr     t6, 0x10(t5)
    swl     t7, 0x17(t5)
    swr     t7, 0x14(t5)
    lwl     t6, 0x1B(t8)
    lwr     t6, 0x18(t8)
    lwl     t7, 0x1F(t8)
    lwr     t7, 0x1C(t8)
    swl     t6, 0x1B(t5)
    swr     t6, 0x18(t5)
    swl     t7, 0x1F(t5)
    swr     t7, 0x1C(t5)

; === SPRITE COMMAND SETUP ===
    li      v0, 0x4
    sb      v0, 0x3(s3)
    li      v0, 0x64
    sb      v0, 0x7(s3)
    li      v0, 0x80
    sb      v0, 0x4(s3)
    sb      v0, 0x5(s3)
    bgez    a0, calc_x_offset
    sb      v0, 0x6(s3)
    addiu   a2, a0, 0x3F

calc_x_offset:
    sra     v0, a2, 0x6
    sll     v0, v0, 0x6
    subu    v0, a0, v0
    lh      v1, 0x9E(gp)
    sll     v0, v0, 0x2
    sb      v0, 0xC(s3)
    bgez    v1, calc_y_offset
    move    v0, v1
    addiu   v0, v1, 0xFF

calc_y_offset:
    sra     v0, v0, 0x8
    sll     v0, v0, 0x8
    subu    v0, v1, v0
    sb      v0, 0xD(s3)
    lhu     v1, 0xA4(gp)            ; DAT_80078f48
    li      v0, 0x8
    sh      v0, 0x10(s3)
    sh      a3, 0x12(s3)
    sh      v1, 0xE(s3)

; === MAIN TEXT LOOP ===
    lbu     a1, 0x0(s7)
    beq     a1, zero, finish
    addiu   s7, s7, 0x1
    move    s6, s5
    lui     s5, 0x8008
    addiu   s5, s5, -0x6C20
    sw      s0, 0x1C(sp)
    lui     s0, 0xFF
    ori     s0, s0, 0xFFFF
    sw      s2, 0x20(sp)

text_loop:
    addiu   v1, a1, -0x67
    sltiu   v0, v1, 0x13
    beq     v0, zero, switch_default
    sll     v0, v1, 0x2
    lui     t6, 0x8001
    addiu   t6, t6, 0x4A0
    addu    v0, v0, t6
    lw      v0, 0x0(v0)
    jr      v0

switch_cases:
    lw      t7, 0x1C(sp)
    j       after_switch
    addiu   s2, t7, 0x2

switch_default:
    lw      t8, 0x20(sp)
    sra     s2, t8, 0x10

after_switch:
    lw      t5, 0x18(sp)
    beq     t5, zero, normal_pos
    addiu   a1, a1, -0x20
    lui     v0, 0x8001
    addiu   v0, v0, 0x43C
    addu    v0, a1, v0
    lui     v1, 0x8001
    lbu     v0, 0x0(v0)
    addiu   v1, v1, 0x428
    sll     v0, v0, 0x1
    addu    v0, v0, v1
    lbu     v1, 0x0(v0)
    lbu     v0, 0x1(v0)
    subu    v1, s8, v1
    sll     a0, v1, 0x10
    sra     s1, a0, 0x10
    j       pos_calculated
    addu    s8, v0, v1

normal_pos:
    sll     v1, s8, 0x10
    lhu     v0, 0x4(s4)
    sra     v1, v1, 0x10
    sll     v0, v0, 0x3
    addu    s1, v1, v0

pos_calculated:
    jal     FUN_80019f84
    move    a0, a1
    lui     t4, 0x8000
    lui     t2, 0xFF00
    move    t1, s4
    lui     t3, 0x1F80
    ori     t3, t3, 0x388
    move    t0, s4
    sh      s1, 0x8(s3)
    sh      s2, 0xA(s3)

; === DMA CHAIN PROCESSING ===
dma_loop:
    lw      v0, -0x6C20(s6)
    and     a0, v0, t2
    and     v0, v0, s0
    or      a1, v0, t4
    lw      v0, 0x4(s5)
    lw      v1, 0x0(a1)
    addiu   v0, v0, -1
    and     v1, v1, s0
    or      a0, a0, v1
    sw      v0, 0x4(s5)
    sw      a0, -0x6C20(s6)
    lw      t6, 0x0(s3)
    lw      t7, 0x4(s3)
    lw      t8, 0x8(s3)
    lw      t5, 0xC(s3)
    sw      t6, 0x0(a1)
    sw      t7, 0x4(a1)
    sw      t8, 0x8(a1)
    sw      t5, 0xC(a1)
    lw      t6, 0x10(s3)
    sw      t6, 0x10(a1)
    lw      v1, 0x18(t0)
    lw      v0, 0x0(a1)
    lw      v1, 0x0(v1)
    and     v0, v0, t2
    and     v1, v1, s0
    or      v0, v0, v1
    sw      v0, 0x0(a1)
    lw      a0, 0x18(t0)
    lw      v0, 0x0(a0)
    and     v1, a1, s0
    and     v0, v0, t2
    or      v0, v0, v1
    sw      v0, 0x0(a0)
    sw      a1, 0x18(t0)
    lhu     v0, 0x4(t1)
    beq     v0, zero, is_first
    nop
    j       not_first
    nop

is_first:
    sw      a1, 0x28(t0)
    sh      s1, 0xC(t1)
    sh      s2, 0xE(t1)

not_first:
    lui     a1, 0x1F80
    ori     a1, a1, 0x348
    lw      v0, -0x6C20(s6)
    and     a0, v0, t2
    and     v0, v0, s0
    or      a3, v0, t4
    move    a2, a3
    lw      v0, 0x4(s5)
    lw      v1, 0x0(a3)
    addiu   v0, v0, -1
    and     v1, v1, s0
    or      a0, a0, v1
    sw      v0, 0x4(s5)
    andi    v0, a3, 0x3
    beq     v0, zero, aligned_copy
    sw      a0, -0x6C20(s6)

unaligned_copy:
    lwl     t6, 0x3(a1)
    lwr     t6, 0x0(a1)
    lwl     t7, 0x7(a1)
    lwr     t7, 0x4(a1)
    lwl     t8, 0xB(a1)
    lwr     t8, 0x8(a1)
    lwl     t5, 0xF(a1)
    lwr     t5, 0xC(a1)
    swl     t6, 0x3(a2)
    swr     t6, 0x0(a2)
    swl     t7, 0x7(a2)
    swr     t7, 0x4(a2)
    swl     t8, 0xB(a2)
    swr     t8, 0x8(a2)
    swl     t5, 0xF(a2)
    swr     t5, 0xC(a2)
    addiu   a1, a1, 0x10
    bne     a1, t3, unaligned_copy
    addiu   a2, a2, 0x10
    j       copy_done
    nop

aligned_copy:
    lw      t6, 0x0(a1)
    lw      t7, 0x4(a1)
    lw      t8, 0x8(a1)
    lw      t5, 0xC(a1)
    sw      t6, 0x0(a2)
    sw      t7, 0x4(a2)
    sw      t8, 0x8(a2)
    sw      t5, 0xC(a2)
    addiu   a1, a1, 0x10
    bne     a1, t3, aligned_copy
    addiu   a2, a2, 0x10

copy_done:
    lwl     t6, 0x3(a1)
    lwr     t6, 0x0(a1)
    swl     t6, 0x3(a2)
    swr     t6, 0x0(a2)
    lw      v1, 0x18(t0)
    lw      v0, 0x0(a3)
    lw      v1, 0x0(v1)
    and     v0, v0, t2
    and     v1, v1, s0
    or      v0, v0, v1
    sw      v0, 0x0(a3)
    lw      a0, 0x18(t0)
    lw      v0, 0x0(a0)
    and     v1, a3, s0
    and     v0, v0, t2
    or      v0, v0, v1
    sw      v0, 0x0(a0)
    sw      a3, 0x18(t0)
    addiu   t0, t0, 0x4
    addiu   v0, t1, 0x8
    sltu    v0, t0, v0
    bne     v0, zero, dma_loop
    nop

    lhu     v0, 0x4(s4)
    addiu   v0, v0, 0x1
    sh      v0, 0x4(s4)
    lbu     a1, 0x0(s7)
    bne     a1, zero, text_loop
    addiu   s7, s7, 0x1

; === CLEANUP ===
finish:
    clear   s2
    lui     s3, 0x8008
    addiu   s5, s3, -0x6C20
    lui     s1, 0xFF
    ori     s1, s1, 0xFFFF
    move    s0, s4
    clear   a1

cleanup_loop:
    li      a2, 0x1
    addu    s2, s2, a2
    lui     v0, 0x8000
    lui     a0, 0xFF00
    lw      v1, -0x6C20(s3)
    lw      a3, 0xA0(gp)
    and     v1, v1, s1
    or      v1, v1, v0
    sw      v1, 0x30(s0)
    lw      v0, -0x6C20(s3)
    lw      v1, 0x0(v1)
    and     v0, v0, a0
    and     v1, v1, s1
    lw      a0, 0x4(s5)
    or      v0, v0, v1
    sw      v0, -0x6C20(s3)
    addiu   a0, a0, -1
    sw      a0, 0x4(s5)
    lw      a0, 0x30(s0)
    jal     SetDrawTPage
    addiu   s0, s0, 0x4
    sltiu   v0, s2, 0x2
    bne     v0, zero, cleanup_loop
    clear   a1

    move    v0, s4
    lw      ra, 0x4C(sp)
    lw      s8, 0x48(sp)
    lw      s7, 0x44(sp)
    lw      s6, 0x40(sp)
    lw      s5, 0x3C(sp)
    lw      s4, 0x38(sp)
    lw      s3, 0x34(sp)
    lw      s2, 0x30(sp)
    lw      s1, 0x2C(sp)
    lw      s0, 0x28(sp)
    jr      ra
    addiu   sp, sp, 0x50