/////////////////////////////////////////////////////////////////////////////////
//
//      Persona 2 Eternal Punishment (PSX) JAP / Custom Characters/Data Patch
//      Author: Sergey Shemet 05/11/2025
//
//       v 1.1 - Grouped Chars VRAM Rendering
//

.psx

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

.definelabel MyAddr,                    0x8009

.open "2_EP/BIN/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      ra, 0x48(sp)               // Сохраняем регистры
    sw      s0, 0x28(sp)
    sw      s1, 0x2c(sp)
    sw      s2, 0x30(sp)
    sw      s3, 0x34(sp)
    sw      s4, 0x38(sp)               // Сохраняем регистры
    sw      s5, 0x3c(sp)
    sw      s6, 0x40(sp)
    sw      s7, 0x44(sp)

    sw      s8, 0x24(sp)

    move    s6, a0                     // Адрес чтения текста -> s6 
    move    s1, a1
    move    s2, a2
    

    // Смещаем поинтер чтения
    andi    v0, v0, 0xff               // Берем младший байт
    addiu   s6, s6, 0x2                // Сдвигаем указатель текста на +2 байта
    lui     s3, MyAddr
    sh      v0, -0x10(s3)              // Сохраняем счетчик байтов @ 8008fff0 для общего количества
    sh      v0, -0x0e(s3)              // Сохраняем счетчик байтов @ 8008fff2 для обратного отсчёта

    lui     t0, 0xff
    ori     t0, t0, 0xffff             // t0 = 00FFFFFF

    sw      s0, 0x28(sp)               // Сохраняем s0 в стеке
    andi    s0, a3, 0xff               // s0 - режим текста (цвет и тень)
    move    a0, s0                     // a0 = s0 (обрезанный цвет)
    lui     a3, 0x8008                 // a3 = 80080000
    lui     a2, 0xff00                 // a2 = FF000000

//Начинаем формировать цепочки
    addiu   t1, a3, -0x31a8            // t1 = 8007ce68 - Второй счётчик цепочек (корорый меньше)
    lw      v1, 0x0(t1)            // v1 = загружаем свободную цепочку
    lbu     a1, 0x60(sp)               // Флаг тени из стека -> a1
    lui     v0, 0x8000                 // v0 = 80000000

    and     a2, v1, a2                 // a2 = v1 & ff000000
    andi    a1, a1, 0xff               // Очищаем режим текста
    and     v1, v1, t0                 // v1 & 00ffffff
    or      s4, v1, v0                 // s5 - основной адрес свободной цепочки
    lw      v0, 0x4(t1)                // Загружаем счетчик DMA
    lw      v1, 0x0(s4)                // v1 = следующий свободный адрес в цепочке
    addiu   v0, v0, -0x1               // Уменьшаем счетчик DMA
    and     v1, v1, t0                 // v1 & 00ffffff
    or      a2, a2, v1                 // a2 | v1 = следующая свободная цепочка
    sw      v0, 0x4(t1)                // Сохраняем счетчик DMA
    jal     storeColor                 //Сохраняем цвет
    sw      a2, 0x0(t1)                // Сохраняем следующую свободную цепочку

    move    a0, s4                     // Текущий адрес цепочки как параметр инициализации (a0)
    sll     a1, s1, 0x10               // a1 = X << 16
    sra     a1, a1, 0x10               // a1 = X координата
    sll     a2, s2, 0x10               // a2 = Y << 16
    sra     a2, a2, 0x10               // a2 = Y координата
    jal     initCopyCharChain             // Инициализация цепочки символов (в a0 адрес, a1 - X, a2 - Y)
    move    a3, s0                      //Режим

cputovram_scratchpad_template:
    // Инициализация ScratchPad (CPU to VRAM)
    lui     a3, 0x1f80
    ori     a3, a3, 0x0348              // a3 = CpuToVram cmd template addr

    lui      a1, 0x0F00                   // Количество команд без flushcache
    sw      a1, 0x0(a3)                // Сохраняем количество команд

    lui      a0, 0xa000                   // a0 = a0000000
    sw      a0, 0x4(a3)               

    lui     v0, 0x01f0
    ori     v0, 0x0130
    sw      v0, 0x8(a3)                // сохраняем VU для CPUtoVRAM

    lui     v0, 0xc
    ori     v0, 0x2
    sw      v0, 0xc(a3)                // сохраняем 000c0002 (ширина в 16-битных пикселях)

_1bppTo4bpp_table_template:
    // Копирование таблицы преобразования 1bpp в 4bpp
    lui        t8, 0x1f80
    ori        t8, t8, 0x390
    lui        t7, 0x8001
    ori        t7, t7, 0x5fc
    lw         t5, 0x0(t7)
    lw         t6, 0x4(t7)
    sw         t5, 0x0(t8)
    sw         t6, 0x4(t8)
    lw         t5, 0x8(t7)
    lw         t6, 0xC(t7)
    sw         t5, 0x8(t8)
    sw         t6, 0xC(t8)
    lw         t5, 0x10(t7)
    lw         t6, 0x14(t7)
    sw         t5, 0x10(t8)
    sw         t6, 0x14(t8)
    lw         t5, 0x18(t7)
    lw         t6, 0x1C(t7)
    sw         t5, 0x18(t8)
    sw         t6, 0x1C(t8)

rect_scratchpad_template:
    // Шаблон команды rect в scratchpad
    lui     s3, 0x1f80
    ori     s3, 0x03d0              // s3 = 1f8003d0 - адрес rect в scratchpad
    lui      v0, 0x0400
    sw      v0, 0x0(s3)                // Длина цепочки = 4
    lui     v0, 0x6480
    ori     v0, 0x8080
    sw      v0, 0x4(s3)
    li      v0, 0xf0c0
    sh      v0, 0xc(s3)                // Сохраняем координаты VU (всегда f0c0)
    lhu     v1, 0xa0(gp)               // Загружаем CLUT из GP (всегда 7FD3)
    
    li      v0, 0
    sw      v0, 0x8(s3)                 //Чистим экранные координаты
    sh      s2, 0xa(s3)                 //И сразу устанавливаем Y

    lui     v0, 0xc
    ori     v0, 0x8                    // Ширина спрайта = 8
    sw      v0, 0x10(s3)               // Сохраняем ширину
    sh      v1, 0xe(s3)                // Сохраняем CLUT

make_sprite_size_table:
    lui     s3, 0x1f80
    ori     s3, 0x03f0                  // Будем серить в scratch 
    lui     v0, 0x0403                  //      таблицей рассчитанных команд и ширин 
    ori     v0, 0x0201                  //          необходимых для спрайтов линии (cmdCount << 2)
    sw      v0, 0x0(s3)
    lui     v1, 0x0706
    ori     v1, 0x0504
    sw      v1, 0x4(s3)
    li      v0, 0x0807
    sh      v0, 0x8(s3)

misc_init:
    move    t6, s1                          // Текущий экранный X, который будет смещаться и писаться в команду rect
                                        // Устанавливаем, исходя из прилетевшей координаты X

TextGroupReadLoop:
    // Смотрим количество символов до предела (не более 10) (проверяем MyAddr-0xE)
    // Отнимаем количество символов в MyAddr-0xE. Устанавливаем признак, если ещё не конец
    // Копируем символы во временный буфер MyAddr (-0x0с)

        lui     s3, 0x1f80
        ori     s3, 0x03f0                  // Перезадаём адрес таблицы количества спрайтов для нового цикла
        lui     t1, MyAddr      //Постоянный счётчик

        lh      v1, -0x0e(t1)   //Загружаем количество оставшихся символов
        lui     t2, MyAddr      //Сдвиг для буфера
        sltiu   a0, v1, 0x0B      //Меньше 10 в строке?
        bne     a0, zero, readTextToBuffer
        clear   s2              //Обнуляем счётчик символов
        li      v1, 0xA         //Установка максимального количество символов

    readTextToBuffer:
        lbu     a0, 0x0(s6)                // Основное чтение байта текста
        addiu   s6, s6, 0x1                // Сдвиг адреса чтения на +1 байт
        addiu   v1, v1, -0x1               // Осталось в этом блоке...
        sb      a0, -0x0c(t2)              // Запись символа в буфер
        addiu   s2, s2, 0x1                // Счётчик прочитанного +1
        lh      v0, -0x0e(t1)              // Грузим общий счётчик
        addiu   t2, t2, 0x1                // Сдвиг записи +1
        addiu   v0, v0, -0x01              // Общий счётчик -1
        bne     v1, zero, readTextToBuffer // Проверяем частный счётчик != 0
        sh      v0, -0x0e(t1)              // Сохраняем общий счётчик
        
        li      a0, 0
        sb      a0, -0x0c(t2)               //На всякий случай сохраняем 00-терминатор

        addu    s3, s3, s2                  //  получаем адрес количества спрайтов из таблицы
        addiu   s3, -0x1                    //      index -1
        lbu     a1, 0x0(s3)                 //  Читаем количество в a1
        nop
        move    t5, a1                      // Храним количество блоков для спрайта

        lui     t3, 0x1f80
        ori     t3, t3, 0x1c0              // Начало данных спрайта в scratch

    form_char_data_in_scratch:
        jal make_char_line_in_scratch       //Вызываем процедуру формирования строки
        move a0, s2                           //Передаём в неё длину строки в символах (a0) и в спрайтах (a1)
                                            //  (формируем спрайт размером до 60x12)

    rect_cmd_init:

        lui     s3, 0x1f80
        ori     s3, 0x03d0                 // s3 = 1f8003d0 - адрес rect в scratchpad

        lui     s1, 0x8008                 // Загружаем 80080000

        lui     s0, 0xff
        ori     s0, s0, 0xffff             // s0 = 00ffffff
        lui     s7, 0x8000                 // s7 = 80000000

        lui     t2, 0xff00                 // t2 = ff000000
        move    t4, s4                     // t4 - указатель на print params ОСНОВНОЙ
        move    t1, s4                     // t1 - print params для смещения страниц

    rect_set_X:
        sh      t6, 0x8(s3)                // Сохраняем X координату в текущий rect

        sll     v0, s2, 1
        addu    v0, s2                     // Умножаем количество обработанных символов на 6
        sll     v0, 1                      
        addu    t6, v0                     // Сдвигаем X (прочитаем со следующим блоком символов)
    rect_set_width:
        sll     v0, t5, 3                   //Количество блоков спрайта * 8 = ширина спрайта
        sh      v0, 0x10(s3)               // Сохраняем ширину спрайта

    PageLoop:

                ////// Балансировщик нагрузки выбора цепочек для равномерного размещения команд (2 команды в 7ce58 + 1 команда в 7ce68)
                    //////////////////////////////////////// TODO: CHAIN BALANCER
        addiu   s5, s1, -0x3198            // s5 = 7ce68 (Второй свободный адрес dma)

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

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


    rect_scratch_connect:
        lw      v1, 0x18(t1)               // Загружаем адрес след цепочки из print params (cur page)
        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)               // Загружаем адрес след цепочки из print params (cur page)
        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)               // Сохраняем адрес след актуальной цепочки в print params (cur page)
        
        lhu     v0, 0x4(t4)                // Проверяем счетчик символов в print params
        nop
        bne     v0, zero, cpu2vram_cmd_loop     // Если счетчик символов ≠ 0, переходим (уже инициализировали)
        clear   s8                         // Сбрасываем счётчик команд спрайтов

        sw      a1, 0x28(t1)               // Устанавливаем адрес начала цепочки символов в print_params (для хранения значения "цепочка от адреса...")
        sw      a3, 0xc(t4)                // Сохраняем экранные координаты в print params


    cpu2vram_cmd_loop:
        // Цикл команд cpu_to_vram (формирование спрайтов текста в VRAM)

                    ////// Балансировщик нагрузки выбора цепочек для равномерного размещения команд (2 команды в 7ce58 + 1 команда в 7ce68)
                        //////////////////////////////////////// TODO: CHAIN BALANCER
            addiu   s5, s1, -0x31a8            // s5 = 7ce58 (Первый свободный адрес dma)

            //s5 - содержит указатель на адрес следующей свободной цепочки. В зависимости от балансировщика может быть 7ce58 или 7ce68

            lw      v0, 0x0(s5)            // Следующая свободная цепочка 7ce58 ()
            nop
            and     a0, v0, t2                 // a0 = v0 & FF000000
            and     v0, v0, s0                 // v0 & 00FFFFFF
            or      a3, v0, s7                 // a3 = следующая цепочка & 80...
            move    a2, a3                     // a2 = a3 (следующая цепочка)
            lw      v0, 0x4(s5)                // Счетчик команд
            lw      v1, 0x0(a3)                // Новый адрес свободной цепочки
            addiu   v0, v0, -0x1               // Уменьшаем счетчик
            and     v1, v1, s0                 // v1 & 00FFFFFF
            or      a0, a0, v1                 // a0 & v1
            sw      v0, 0x4(s5)                // Сохраняем счетчик DMA
            sw      a0, 0x0(s5)            // Сохраняем новую свободную цепочку

                //Копируем шаблон cpu2vram

            lui     a1, 0x1f80
            ori     a1, a1, 0x348              // 1f800348 = Scratch команда cpu2vram
            lw      t8, 0x0(a1)
            lw      t7, 0x4(a1)
            sw      t8, 0x0(a2)
            sw      t7, 0x4(a2)
            lw      t8, 0x8(a1)                 //Сохраняем базу команды cpu2vram
            lw      t7, 0xc(a1)
            sw      t8, 0x8(a2)
            sw      t7, 0xc(a2)

            sll     v1, s8, 1                   //Текущий индекс блока * 2
            addiu   v0, v1, 0x130                   //складываем со 0x130 (X начала спрайта в VRAM)
            sh      v0, 0x8(a2)                 //Обновляем VU X прямо в RAM

        copy_char_data_start:
            lui     t3, 0x1f80
            ori     t3, t3, 0x1c0              // Начало данных спрайта в scratch
            
            addiu   a2, 0x10                    //Адрес для записи данных после заголовка команды
            clear   v0                          //Основной счётчик строк (0-11)

            sll     v1, t5, 2                   //v1 = количество байт в одной строке (блоки * 4) (инкремент)
            sll     a1, s8, 2                   //Вычисляем смещение начала чтения
            addu    a1, t3                      //a1 = адрес начала чтения в scratch

        copy_char_data_loop:
            lw      t7, 0x0(a1)                 //Грузим данные
            addu    a1, v1                      //Смещаем адрес чтения
            sw      t7, 0x0(a2)                 //Сохраняем данные
            
            addiu   a2, 0x04                    //Смещаем адрес записи
            addiu   v0, 0x01                    //Инкремент счётчика строк
            bne     v0, 0x0c, copy_char_data_loop
            nop

            bne     s8, zero, cpu2vram_dma_link // Если это первая команда (последняя выполняемая, то добавляем flushcache)
            nop

        set_flush_cache_cmd:
            li     t8,  0x10
            sb      t8, -0x3d(a2)               //Обновляем длину команды до 0x10
            lui     t8, 0x0100
            sw      t8, 0x0(a2)               //Устанавливаем команду flush cache в конце команды

        cpu2vram_dma_link:
            // Связывание предыдущей команды с текущей (формирование ссылки в заголовке команды)
            lw      v1, 0x18(t1)               // v1 <- 18+(t1) - указатель на конец цепочки в текущей странице)
            lw      v0, 0x0(a3)                // v0 <- 0+(a3) - снова читаем первую команду (в данном случае - количество команд)
            lw      v1, 0x0(v1)                // Разыменование указателя на след свободный адрес в цепочке (из print_params)
            and     v0, v0, t2                 // v0 & FF..... Чистим количество команд
            and     v1, v1, s0                 // v1 & 00FFFFFF - чистим след свободный адрес
            or      v0, v0, v1                 // Совмещаем ссылку - Cmd+ADDR
            sw      v0, 0x0(a3)                // Сохраняем ссылку в цепочку

            //Связывание текущей команды со следующей (формирование ссылки в заголовке команды + запись в print params)
            lw      a0, 0x18(t1)               // v1 <- 18+(t1) - указатель на конец цепочки в текущей странице)
            nop
            lw      v0, 0x0(a0)                // Читаем то, на что указывает указатель (след адрес в цепочке из цепочки)
            and     v1, a3, s0                 // v1 = a3 & 00FFFFFF - от следующего адреса свободной команды отрезаем 80
            and     v0, v0, t2                 // v0 & FF000000 (количество команд) от след адреса
            or      v0, v0, v1                 // Совмещаем ссылку - Cmd+ADDR
            sw      v0, 0x0(a0)                // Сохраняем ссылку в цепочку
            sw      a3, 0x18(t1)               // Сохраняем адрес последней команды в print params

            //Конец цикла команд cpu_to_vram
            addiu   s8, 0x1                    //Смешаем индекс читаемого спрайта
            bne     s8, t5, cpu2vram_cmd_loop  //Проверка на достижение количества спрайтов
            nop

        addiu   t1, t1, 0x4                // Сдвигаем print params для следующей страницы
        addiu   v0, t4, 0x8                // v0 = t4 + 8 (t1 не должен быть больше, чем t4 + 4)
        sltu    v0, t1, v0                 // Проверяем прохождение первой страницы
        bne     v0, zero, PageLoop         // Если не прошли, продолжаем цикл страниц
        nop

    chunk_making_end:
        // Увеличиваем счетчик символов + проверка
        lhu     v0, 0x4(s4)                // Текущее количество спрайтов
        lui     t3, MyAddr
        addiu   v0, v0, 0x1                // Увеличиваем на 1
        lhu     v1, -0x0e(t3)              // Количество оставшихся символов
        sh      v0, 0x4(s4)                // Сохраняем счетчик спрайтов в print_params

        bne     v1, zero, TextGroupReadLoop   
                     // Если обработаны все буквы и созданы все спрайты, то выходим
        nop


TextEnd:
    clear   s2
    lui     s3, 0x8008
    addiu   s5, s3, -0x3198            // s5 = 7ce68 (Второй свободный адрес dma)
    lui     s1, 0xff
    ori     s1, s1, 0xffff             // s1 = 00ffffff
    move    s0, s4                     // s0 = Указатель на print params (теперь они и в t1, s4 и s0)
    clear   a1

PageTLoop:
    //Добавляем команду переключения текстурной страницы (SetDrawTPage) в обе страницы
    li      a2, 0x1                     // a2 = 1
    addu    s2, s2, a2                // Увеличиваем счетчик страниц

    lui     v0, 0x8000                  // v0 = 80000000
    lui     a0, 0xff00                  // a0 = ff000000

    lw      v1, 0x0(s5)                // Следующая свободная цепочка
    lw      a3, 0x9c(gp)               // Текстурная страница из параметров
    and     v1, v1, s1                // v1 & 00FFFFFF
    or      v1, v1, v0                // v1 | v0 = 80.....
    sw      v1, 0x30(s0)               // Сохраняем последний адрес в print params

    //Линкуем адреса
    lw      v0, 0x0(s5)                // Следующая свободная цепочка ещё раз!
    lw      v1, 0x0(v1)                // разыменование
    and     v0, v0, a0                // v0 & ff000000
    and     v1, v1, s1                // v1 & 00FFFFFF
    lw      a0, 0x4(s5)                // Счетчик DMA
    or      v0, v0, v1                // v0 | v1
    sw      v0, 0x0(s5)            // Сохраняем свободный адрес
    addiu   a0, -0x1               // Уменьшаем счетчик DMA
    sw      a0, 0x4(s5)                // Сохраняем счетчик DMA
    lw      a0, 0x30(s0)               // Читаем адрес команды в a0 для передачи в функцию установки texPage
    jal     SetDrawTPage                  // Устанавливаем страницу отрисовки
    addiu   s0, s0, 0x4                // Смещаем print params для следующей страницы
    sltiu   v0, s2, 0x2                // Проверяем счетчик < 2
    bne     v0, zero, PageTLoop        // Если да, продолжаем цикл
    clear   a1                          // a1 = 0

    move    v0, s4                     // Возвращаем основные параметры 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)

    lw      s8, 0x24(sp)                //need to be saved!

    jr      ra                          // Возврат
    addiu   sp, sp, 0x50               // Восстанавливаем стек


.include "2_EP/EP_charload_grouped.asm"
.include "2_EP/EP_charCalcs.asm"
.close

.include "2_EP/EP_txtpatches.asm"       // misc text patches




//  compile with                    ./armips -sym 2_EP/BUILD_LOGS/SLPS_028.25.map -temp 2_EP/BUILD_LOGS/SLPS_028.25.txt 2_EP/EP_main_grouped.asm