///////////////////////////////////////////////////////////////////////////////// // // Persona 2 Innocent Sin (PSX) JAP / Custom Characters/Data Patch // Author: Sergey Shemet 12/10/2025 // // v 1.4 - Grouped Chars VRAM Rendering // .psx // Определения функций .definelabel SetDrawTPage, 0x800542cc .definelabel storeColor, 0x8001b0c8 .definelabel initCopyCharChain, 0x8001a070 .definelabel makeCharPixels, 0x8001a08f // need custom routine with custom font .definelabel GenerateSmallChar, 0x8001a284 // need custom routine .definelabel MakeShadowSmallChar, 0x8001a20c .definelabel PrintBigDMAText, 0x80019300 .definelabel MyAddr, 0x8009 .open "1_IS/BIN/SLPS_021.00", 0x8000F800 .include "1_IS/complex_strings_copy.asm" .org 0x80090000 ExternalPrint: move v1, s3 // Сохраняем s3 move s3, a0 // Устанавливаем s3 как адрес текста lhu v0, 0x0(s3) // Читаем 2 байта текста move s3, v1 // Восстанавливаем s3 srl v1, v0, 0xD // Проверяем 13-й бит (система 1 байт) bne v1, zero, MyPrintLineRoutine // Если установлен, переходим к нашей процедуре clear v1 j PrintBigDMAText // Иначе используем стандартную процедуру nop MyPrintLineRoutine: addiu sp, sp, -0x50 // Выделяем место в стеке sw ra, 0x4c(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 s8, 0x48(sp) sw s7, 0x44(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, -0x4930 // t1 = 8007b6d0 - новая свободная цепочка lw v1, -0x4930(a3) // v1 = загружаем свободную цепочку lbu a1, 0x60(sp) // Флаг тени из стека -> a1 lui v0, 0x8000 // v0 = 80000000 // Инициализация цвета и textParams and a2, v1, a2 // a2 = v1 & ff000000 andi a1, a1, 0xff // Очищаем режим текста and v1, v1, t0 // v1 & 00ffffff or s4, v1, v0 // s4 - основной адрес свободной цепочки 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, -0x4930(a3) // Сохраняем следующую свободную цепочку move a0, s4 // Текущий адрес цепочки 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 // Инициализация цепочек символов move a3, s0 // Сохраняем режим текста // Шаблон cpuToVram cputovram_scratchpad_template: lui a3, 0x1f80 ori a3, a3, 0x0348 // a3 = первый символ начала 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 _1bppTo4bpp_table_template: // Копирование таблицы преобразования 1bpp в 4bpp lui t8, 0x1f80 ori t8, t8, 0x390 lui t7, 0x8001 ori t7, t7, 0x3e8 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, 0xa8(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 addiu s5, s1, -0x4930 // s5 = 7b6d0 (временный DMA) lui s0, 0xff ori s0, s0, 0xffff // s0 = 00ffffff lui s7, 0x8000 // s7 = 80000000 //Старый неактуальный код из далёкого атлусовского прошлого // Инициализация координат XY //lw v0, 0xb0(gp) // Какой-то множитель ширины символа. В нашем случае не нужен! //lhu v1, 0x4(s4) // Старый счётчик символов сейчас считает спрайты // addiu v0, v0, 0x6 // 0x06 * 6px между буквами! // mult v1, v0 // v1 * v0 = смещение X * номер символа // addiu t3, sp, 0x10 // t3 = sp + 10 // sh s8, 0xa(s3) // экранный Y установлен на этапе формироания rect lui t2, 0xff00 // t2 = ff000000 move t4, s4 // t4 - указатель на print params ОСНОВНОЙ move t1, s4 // t1 - print params для смещения страниц rect_set_X: // lhu t5, 0x20(sp) // Загружаем X из стека // mflo t6 // Результат умножения // addu v0, t5, t6 // v0 = 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: // Создание цепочки копирования спрайта lw v0, -0x4930(s1) // 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, -0x4930(s1) // Сохраняем новую свободную цепочку 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) // Следующий параметр 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(t4) // Счетчик символов 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 lw v0, -0x4930(s1) // Следующая свободная цепочка 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, -0x4930(s1) // Сохраняем новую свободную цепочку //Тут "оптимизировали" копирование в short, и сделали своё копирование со смещением спрайтов 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 (начало блока спрайта в 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 = начинаем с него 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: // Связывание 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) // Последняя цепочка символов //Конец цикла команд cpu_to_vram addiu s8, 0x1 bne s8, t5, cpu2vram_cmd_loop nop addiu t1, t1, 0x4 // Сдвигаем параметры для следующей страницы addiu v0, t4, 0x8 // Сдвигаем первый параметр цепочки для страницы 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, -0x4930 // s5 = 7b6d0 lui s1, 0xff ori s1, s1, 0xffff // s1 = 00ffffff move s0, 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, -0x4930(s3) // Следующая свободная цепочка lw a3, 0xa4(gp) // Текстурная страница and v1, v1, s1 // v1 & 00FFFFFF or v1, v1, v0 // v1 | v0 = 80..... sw v1, 0x30(s0) // Сохраняем последний свободный адрес в параметрах DMA lw v0, -0x4930(s3) // Следующая свободная цепочка 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, -0x4930(s3) // Новый адрес свободной цепочки addiu a0, a0, -0x1 // Уменьшаем счетчик DMA sw a0, 0x4(s5) // Сохраняем счетчик 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, s4 // Возвращаем основные параметры DMA // Восстанавливаем регистры из стека 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 // Восстанавливаем стек .ascii "This game was officially cracked by SERGEY SHEMET 2021-2025. WHAT ARE YOU DOING HERE, BOY?" .byte 0x00 .byte 0x00 // Включаем дополнительные файлы .include "1_IS/IS_charload_grouped.asm" .include "1_IS/IS_battle_GG_name_render.asm" .include "1_IS/charCalcs.asm" .close .include "1_IS/txtpatches.asm" // misc text patches // compile with ./armips -sym 1_IS/BUILD_LOGS/SLPS_021.00.map -temp 1_IS/BUILD_LOGS/SLPS_021.00.txt 1_IS/is_main_grouped.asm