;
;       Persona 2 Eternal Punishment (PSX) JAP / Custom Characters/Data Patch
;       Author: Sergey Shemet 06/10/2022

.psx

// Определения функций
.definelabel SetDrawTPage,              0x800578fc
.definelabel storeColor,                0x8001c0b4
.definelabel initCopyCharChain,         0x8001b110
.definelabel MakeShadowSmallChar,       0x8001b2a8
.definelabel PrintBigDMAText,           0x8001a3a8

.definelabel MyAddr,                    0x8009

.open "SLPS_028.25", 0x8000F800

.org 0x80090000

ExternalPrint:
    // Текстовая тестовая процедура
    lhu     v0, 0x0(a0)                // Читаем 2 байта текста
    nop
    srl     v1, v0, 0xD                // Проверяем 13-й бит (система 1 байт)
    bne     v1, zero, MyPrintLineRoutine // Если установлен, переходим к нашей процедуре
    clear   v1
    j       PrintBigDMAText              // Иначе используем стандартную процедуру
    nop

MyPrintLineRoutine:
    addiu   sp, sp, -0x50              // Выделяем место в стеке
    sw      s4, 0x38(sp)               // Сохраняем регистры
    move    s4, a0                     // Адрес чтения текста -> s4
    sw      s0, 0x28(sp)
    move    s0, a1
    sw      s1, 0x2c(sp)
    move    s1, a2
    sw      s3, 0x34(sp)

    // Обработка новой команды
    andi    v0, v0, 0xff               // Берем младший байт
    addiu   s4, s4, 0x2                // Сдвигаем указатель текста на +2 байта

    lui     s3, MyAddr
    sw      v0, -0x10(s3)              // Сохраняем счетчик байтов @ 8008fff0

    // Инициализация scratchpad
    lui     s3, 0x1f80
    ori     s3, s3, 0x3d0              // s3 = 1f8003d0 - команда копирования спрайта
    lui     t0, 0xff
    ori     t0, t0, 0xffff             // t0 = 00FFFFFF

    andi    a0, a3, 0xff               // Режим текста (цвет и тень)
    lui     a3, 0x8008                  // a3 = 80080000
    lui     a2, 0xff00                  // a2 = FF000000

    addiu   t1, a3, -0x31a8            // t1 = 8007b6d0 - новая свободная цепочка
    lw      v1, -0x31a8(a3)            // v1 = загружаем свободную цепочку
    lbu     a1, 0x60(sp)               // Флаг тени из стека -> a1
    lui     v0, 0x8000                  // v0 = 80000000
    sw      ra, 0x48(sp)               // Сохраняем регистры
    sw      s7, 0x44(sp)
    sw      s6, 0x40(sp)
    sw      s5, 0x3c(sp)
    sw      s2, 0x30(sp)

    // Инициализация цепочек
    and     a2, v1, a2                // a2 = v1 & ff000000
    andi    a1, a1, 0xff               // Очищаем режим текста
    and     v1, v1, t0                // v1 & 00ffffff
    or      s5, v1, v0                // s5 - основной адрес свободной цепочки
    lw      v0, 0x4(t1)                // Загружаем счетчик DMA
    lw      v1, 0x0(s5)                // v1 = следующая свободная цепочка
    addiu   v0, v0, -0x1               // Уменьшаем счетчик DMA
    and     v1, v1, t0                // v1 & 00ffffff
    or      a2, a2, v1                // a2 | v1 = следующая свободная цепочка
    sw      v0, 0x4(t1)                // Сохраняем счетчик DMA
    sw      a2, -0x31a8(a3)            // Сохраняем следующую свободную цепочку
    jal     storeColor
    move    a0, s5                     // Текущий адрес цепочки
    sll     a1, s0, 0x10               // a1 = X << 16
    sra     a1, a1, 0x10               // a1 = X координата
    sll     a2, s1, 0x10               // a2 = Y << 16
    jal     initCopyCharChain             // Инициализация цепочек символов
    sra     a2, a2, 0x10               // a2 = Y координата

    // Инициализация ScratchPad (CPU to VRAM)
    lui     a2, 0x1f80
    ori     a2, a2, 0x348              // a2 = первый символ начала

    // 4-битная таблица паттернов
    .include "EP_4bitPattern.asm"

    li      v0, 0x2
    sh      v0, 0x1c(sp)
    li      v0, 0xc
    li      a0, 0x10                    // Команды в цепочке формирования символа?
    lui     v1, 0xa000                  // a0 = a0000000
    sh      v0, 0x1e(sp)               // Сохраняем 000C0002 в стек+1c

    // Инициализация символа
    lui     v0, 0x100                   // Сброс кэша 01000000
    sb      a0, 0x3(a2)                // Сохраняем количество команд
    sw      v1, 0x4(a2)                // Сохраняем a0000000

    lui     at, 0x1f80                  // Начало scratchpad
    sw      v0, 0x388(at)              // Сохраняем в конец первой цепочки символов

    // Формирование команды DMA копирования спрайта
    li      v0, 0x4
    sb      v0, 0x3(s3)                // Длина цепочки = 4
    li      v0, 0x64
    sb      v0, 0x7(s3)                // Команда копирования спрайта

    lhu     a0, 0x98(gp)               // Загружаем X координату
    lhu     v1, 0x9a(gp)               // Загружаем Y координату

    li      v0, 0x80
    sb      v0, 0x4(s3)
    sb      v0, 0x5(s3)
    sb      v0, 0x6(s3)                // Формируем 80808064

setY1:
    lw      v0, 0x1c(sp)               // v0 = 000c0002 из стека
    lh      a3, 0x98(gp)               // Для дальнейших расчетов X спрайта
    sh      a0, 0x18(sp)
    sh      v1, 0x1a(sp)               // Сохраняем координаты в стек
    lw      a0, 0x18(sp)               // Загружаем координаты из стека
    move    t0, a3
    sw      a0, 0x8(a2)                // Загружаем координаты в DMA
    sw      v0, 0xc(a2)                // Сохраняем в первый символ

    bgez    a3, LAB_800194fc

SetX1:
    sh      s1, 0x20(sp)               // Сохраняем X половину в sp+20
    addiu   t0, a3, 0x3f

LAB_800194fc:
    // Декодирование координат для исходного спрайта
    sra     v0, t0, 0x6                // v0 = t0 >> 6
    sll     v0, v0, 0x6                // v0 << 6
    subu    v0, a3, v0                // v0 = a3 - v0
    lh      v1, 0x9a(gp)               // Загружаем Y половину из памяти
    sll     v0, v0, 0x2                // v0 << 2
    sb      v0, 0xc(s3)                // Сохраняем X байт спрайта символа
    bgez    v1, LAB_80019520            // Если Y >= 0 - ветвление
    move    v0, v1
    addiu   v0, v1, 0xff               // Иначе v0 = ff - v1

LAB_80019520:
    sra     v0, v0, 0x8
    sll     v0, v0, 0x8
    subu    v0, v1, v0
    sb      v0, 0xd(s3)                // Сохраняем Y байт спрайта символа
    lhu     v1, 0xa0(gp)

    li      v0, 0x8                     // Ширина спрайта = 8
    sh      v0, 0x10(s3)               // Сохраняем ширину
    sh      v1, 0xe(s3)                // Сохраняем значение

    sh      s0, 0x8(s3)                // Сохраняем X
    sh      s1, 0xa(s3)                // Сохраняем Y

    li      v0, 0xc                     // Высота спрайта = 12
    sh      v0, 0x12(s3)               // Сохраняем высоту

    // Начало чтения текста
    lbu     a0, 0x0(s4)                // Основное чтение байта команды
    nop
    addiu   s4, s4, 0x1                // Сдвиг адреса чтения на +1 байт

SpaceCheckBranch:
    jal     makeCharPixelsCustom          // Создание маленького символа в scratch
    clear   s2

    lui     t2, 0xff00                  // t2 = ff000000
    move    t6, s5                     // Параметры
    addiu   t5, sp, 0x10               // t5 = sp + 10
    lui     t3, 0x8008
    addiu   t4, t3, -0x31a8
    move    t1, s5                     // Параметры снова

SetY:
SetX:
PageLoop:
    // Создание цепочки копирования спрайта
    lw      v0, -0x3198(s6)            // v0 = *адрес свободной цепочки
    nop
    and     a0, v0, t2                // a0 = v0 & ff00..
    and     v0, v0, s0                // v0 & 00FFFFFF
    or      a1, v0, s1                // a1 = v0 | 80000000
    lw      v0, 0x4(s7)                // Счетчик команд
    lw      v1, 0x0(a1)                // Новый адрес свободной цепочки
    addiu   v0, v0, -0x1               // Уменьшаем счетчик
    and     v1, v1, s0                // v1 & 00FFFFFF
    or      a0, a0, v1                // a0 & v1
    sw      v0, 0x4(s7)                // Сохраняем счетчик DMA
    sw      a0, -0x3198(s6)            // Сохраняем новую свободную цепочку

CopyCmdFromScratch:
    // Копирование команды из scratch
    lw      t9, 0x0(s3)                // Количество команд DMA
    lw      t7, 0x4(s3)                // Команда копирования спрайта
    lw      t8, 0x8(s3)                // Координаты экрана XY
    lw      t5, 0xc(s3)                // CLUT и координаты текстуры
    sw      t9, 0x0(a1)
    sw      t7, 0x4(a1)
    sw      t8, 0x8(a1)
    sw      t5, 0xc(a1)
    lw      t9, 0x10(s3)               // Размер спрайта после копирования
    nop
    sw      t9, 0x10(a1)               // Сохраняем последнюю команду

    lw      v1, 0x18(t1)               // Следующий параметр
    lw      v0, 0x0(a1)                // Текущий новый адрес цепочки
    lw      v1, 0x0(v1)                // Загружаем значение
    and     v0, v0, t2                // v0 & ff000000
    and     v1, v1, s0                // v1 & 00FFFFFF
    or      v0, v0, v1                // Команды с FFFFFF
    sw      v0, 0x0(a1)                // Обновляем текущий адрес цепочки
    lw      a0, 0x18(t1)               // Первый адрес цепочки
    nop
    lw      v0, 0x0(a0)                // Первая ссылка цепочки на следующую
    and     v1, a1, s0                // v1 = a1 & 00FFFFFF
    and     v0, v0, t2                // v0 & FF000000
    or      v0, v0, v1                // Последний адрес цепочки
    sw      v0, 0x0(a0)                // Первая цепочка следующего адреса
    sw      a1, 0x18(t1)               // Текущий адрес цепочки
    lhu     v0, 0x4(t6)                // Счетчик символов
    nop
    bne     v0, zero, NotFirstChar     // Если счетчик символов ≠ 0, переходим
    nop

    sw      a1, 0x28(t1)               // Устанавливаем первую цепочку символов
    sw      t8, 0xc(t6)                // Сохраняем координаты в параметры
    // sh    s2, 0xe(t4)               // Сохраняем половину Y

NotFirstChar:
    lui     a1, 0x1f80                  // Адрес символа здесь
    lw      v0, -0x31a8(t3)            // Следующая свободная цепочка
    ori     a1, a1, 0x348              // a1 | 1f800348 = Scratch данные символа
    and     a0, v0, t2                // a0 = v0 & FF000000
    and     v0, v0, s0                // v0 & 00FFFFFF
    or      a3, v0, s1                // a3 = следующая цепочка & 80...
    move    a2, a3                     // a2 = a3 (следующая цепочка)
    lw      v0, 0x4(t4)                // Счетчик команд
    lw      v1, 0x0(a3)                // Новый адрес свободной цепочки
    addiu   v0, v0, -0x1               // Уменьшаем счетчик
    and     v1, v1, s0                // v1 & 80... количество команд
    or      a0, a0, v1                // a0 & v1
    sw      v0, 0x4(t4)                // Сохраняем счетчик DMA
    andi    v0, a3, 0x3                // Проверяем адрес назначения MOD 4
    sw      a0, -0x31a8(t3)            // Сохраняем новую свободную цепочку

    beq     v0, zero, Copy16Bytes      // Если MOD 4 = 0, ветвление
    addiu   v0, a1, 0x40

CopyBy2Bytes:
    // Копируем по 2 байта если MOD 4 ≠ 0
    lwl     t9, 0x3(a1)
    lwr     t9, 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     t9, 0x3(a2)
    swr     t9, 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, v0, CopyBy2Bytes
    addiu   a2, a2, 0x10
    j       last4BytesCopy
    nop

Copy16Bytes:
    // Копируем по 16 байт
    lw      t9, 0x0(a1)
    lw      t7, 0x4(a1)
    lw      t8, 0x8(a1)
    lw      t5, 0xc(a1)
    sw      t9, 0x0(a2)
    sw      t7, 0x4(a2)
    sw      t8, 0x8(a2)
    sw      t5, 0xc(a2)
    addiu   a1, a1, 0x10
    bne     a1, v0, Copy16Bytes
    addiu   a2, a2, 0x10

last4BytesCopy:
    // Копируем последние 4 байта
    lwl     t9, 0x3(a1)
    lwr     t9, 0x0(a1)
    nop
    swl     t9, 0x3(a2)
    swr     t9, 0x0(a2)

    // Связывание DMA символа + копирование спрайта
    lw      v1, 0x18(t1)               // Текущая цепочка DMA копирования спрайта
    lw      v0, 0x0(a3)                // Текущие команды цепочки символов
    lw      v1, 0x0(v1)                // Количество команд + следующая ссылка
    and     v0, v0, t2                // v0 & FF.....
    and     v1, v1, s0                // v1 & 00FFFFFF
    or      v0, v0, v1                // Количество команд & FF..
    sw      v0, 0x0(a3)                // Сохраняем текущую команду цепочки
    lw      a0, 0x18(t1)               // Адрес заголовка текущей команды копирования спрайта
    nop
    lw      v0, 0x0(a0)                // 04FFFFFF
    and     v1, a3, s0                // v1 = копирование текущей цепочки символов
    and     v0, v0, t2                // v0 & FF000000 (количество команд)
    or      v0, v0, v1                // Полная команда со ссылкой
    sw      v0, 0x0(a0)                // Сохраняем с ссылкой на цепочку символов
    sw      a3, 0x18(t1)               // Последняя цепочка символов

    addiu   t1, t1, 0x4                // Сдвигаем параметры для следующей страницы
    addiu   v0, t6, 0x8                // Сдвигаем первый параметр цепочки для страницы
    sltu    v0, t1, v0                // Проверяем прохождение первой страницы
    bne     v0, zero, PageLoop         // Если не прошли, продолжаем цикл страниц
    nop

    // Сдвиг X координаты (+6)
    lhu     v0, 0xa8(gp)               // Загружаем начальный X
    lhu     v1, 0x8(s3)                // Загружаем последний X в scratch
    addiu   v0, v0, 0x6                // v0 = начальный X + 6
    addu    v1, v1, v0                // Последний X = последний X + (начальный X + 6)
    sh      v1, 0x8(s3)                // Обновляем последний X в DMA scratch

SpaceJump:
    // Увеличиваем счетчик символов + проверка
    lhu     v0, 0x4(s5)                // Текущее количество символов DMA
    lui     t9, MyAddr
    addiu   v0, v0, 0x1                // Увеличиваем на 1
    sh      v0, 0x4(s5)                // Сохраняем счетчик в цепочку

    // Проверка переполнения DMA
    lw      v0, 0x4(s7)                // Счетчик команд DMA
    move    v1, 5                       // Минимальный счетчик команд
    sltu    v0, v0, v1                // v0 < v1?
    bne     v0, zero, TextEnd          // Если переполнение DMA, завершаем текст
    nop

    lui     t3, MyAddr
    lhu     v0, -0xE(t3)               // Количество символов в строке
    lhu     v1, -0x10(t3)              // Общее количество символов в строке
    addiu   v0, v0, 0x1                // Увеличиваем на 1
    lbu     a0, 0x0(s4)                // Загружаем следующий символ
    sh      v0, -0xE(t3)               // Сохраняем счетчик символов
    addiu   s4, s4, 0x1                // Сдвигаем адрес символа

    bne     v0, v1, NextChar           // Если текущий символ ≠ общему количеству, продолжаем
    nop

TextEnd:
    clear   s2
    lui     s3, 0x8008
    addiu   s4, s3, -0x3198            // s4 = 7b6d0
    lui     s1, 0xff
    ori     s1, s1, 0xffff             // s1 = 00ffffff
    move    s0, s5                     // s0 = текущие параметры строки
    clear   a1

PageTLoop:
    // Завершающая обработка страниц
    li      a2, 0x1                     // a2 = 1
    addu    s2, s2, a2                // Увеличиваем счетчик
    lui     v0, 0x8000                  // v0 = 80000000
    lui     a0, 0xff00                  // a0 = ff000000
    lw      v1, -0x3198(s3)            // Следующая свободная цепочка
    lw      a3, 0x9c(gp)               // Некоторые параметры (почти DMA)
    and     v1, v1, s1                // v1 & 00FFFFFF
    or      v1, v1, v0                // v1 | v0 = 80.....
    sw      v1, 0x30(s0)               // Сохраняем последний свободный адрес в параметрах DMA
    lw      v0, -0x3198(s3)            // Следующая свободная цепочка
    lw      v1, 0x0(v1)                // Создаем еще одну цепочку
    and     v0, v0, a0                // v0 & ff000000
    and     v1, v1, s1                // v1 & 00FFFFFF
    lw      a0, 0x4(s4)                // Счетчик DMA
    or      v0, v0, v1                // v0 | v1
    sw      v0, -0x3198(s3)            // Новый адрес свободной цепочки
    addiu   a0, a0, -0x1               // Уменьшаем счетчик DMA
    sw      a0, 0x4(s4)                // Сохраняем счетчик DMA
    lw      a0, 0x30(s0)               // Начало цепочки DMA
    jal     SetDrawTPage                  // Устанавливаем страницу отрисовки
    addiu   s0, s0, 0x4                // Сдвигаем параметры DMA для следующей страницы
    sltiu   v0, s2, 0x2                // Проверяем счетчик < 2
    bne     v0, zero, PageTLoop        // Если да, продолжаем цикл
    clear   a1                          // a1 = 0

    move    v0, s5                     // Возвращаем основные параметры DMA

    // Восстанавливаем регистры из стека
    lw      ra, 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               // Восстанавливаем стек