//////////////////////////////////////////////////////////////////////////////////////////////////////// // // Persona 2 Eternal Punishment CD Extra (PSX) JAP / Custom Characters/Data Patch // // Author: Sergey Shemet 17/09/2025 // .psx .definelabel SetDrawTPage, 0x800522F0 .definelabel storeColor, 0x8001ADC8 .definelabel initCopyCharChain, 0x80019D70 .definelabel MakeShadowSmallChar, 0x80019F0C // 0x800196BC -big char shadow??? .definelabel OriginalKanjiPrint, 0x80019000 .definelabel MyAddr, 0x8009 .open "3_CD_EXTRA/BIN/SLPS_028.26", 0x8000F800 .org 0x80090000 ExternalPrint: // Text routing test routine move r3,s3 move s3,r4 lhu v0,0x0(s3) // Read half 2 bytes of text move s3,r3 srl v1,v0,0xD // Check 13th bit (Russian text format 20XX cmd) bne v1,zero,MyPrintLineRoutine clear v1 j OriginalKanjiPrint // Если не включен 13 бит, то это обычный японский текст. Вызов обычной подпрограммы вывода. nop MyPrintLineRoutine: addiu sp, sp, -0x50 // Выделение места в стеке (80 bytes) sw s6, 0x40(sp) // Сохранение s6 move s6, a0 // s6 = param_1 (указатель на текст) sw s1, 0x2C(sp) // Сохранение s1 move s1, a1 // s1 = param_2 (X координата) sw s2, 0x30(sp) // Сохранение s2 move s2, a2 // s2 = param_3 (Y координата) sw s3, 0x34(sp) // Сохранение s3 //v1/r3 - MyChars charcount andi v0, 0xff addiu s6, 0x2 //Moving text read pointer +2bytes lui s3, MyAddr sh v0, -0x10(s3) //store half bytecounter @ 8008fff0 sh v0, -0x0E(s3) //store half bytecounter @ 8008fff2 ////////////////////////////////////// // Инициализация адресов scratch и масок lui s3, 0x1F80 // Базовый адрес GPU ori s3, s3, 0x03D0 // Адрес для команд отрисовки lui t0, 0xFF // Маска для DMA операций ori t0, t0, 0xFFFF sw s0, 0x28(sp) // Сохранение s0 andi s0, a3, 0xFF // s0 = param_4 (цвет/атрибуты) move a0, s0 // a0 = цвет текста // Блок инициализации DMA InitDMA: update_free_dma: lui a3, 0x8008 // Базовый адрес для текстового DMA lui a2, 0xFF00 // Маска для адресной части addiu t1, a3, -0x6C20 // TextDMANexChainAddr lw v1, -0x6C20(a3) // Загрузка текущего DMA адреса lbu a1, 0x60(sp) // Загрузка цвета из стека lui v0, 0x8000 // Флаг для адресации // Сохранение всех регистров sw ra, 0x4C(sp) // Сохранение return address sw s8, 0x48(sp) // Сохранение s8 sw s7, 0x44(sp) // Сохранение s7 sw s5, 0x3C(sp) // Сохранение s5 sw s4, 0x38(sp) // Сохранение s4 // Подготовка DMA цепочки PrepareDMAChain: and a2, v1, a2 // Извлечение первого байта and v1, v1, t0 // Отсекаем первый байт у адреса or s4, v1, v0 // Совмещаем 80...... и 00ADDR = новый DMA адрес lw v0, 0x4(t1) // TextDMACounter lw v1, 0x0(s4) // Разыменовывание указателя на текущий адрес = след адрес addiu v0, v0, -1 // Декремент счетчика and v1, v1, t0 // Очистка адресной части or a2, a2, v1 // Объединение исходного первого байта (FF) с новым адресом sw v0, 0x4(t1) // Сохранение счетчика // Подготовка атрибутов строки PrepareAttrs: andi a1, a1, 0xFF // Маскирование цвета jal storeColor // Установка цвета и тени sw a2, -0x6C20(a3) // Сохранение нового свободного DMA адреса move a0, s4 // a0 = DMA адрес // Подготовка координат sll a1, s1, 0x10 // Подготовка X координаты sra a1, a1, 0x10 sll a2, s2, 0x10 // Подготовка Y координаты sra a2, a2, 0x10 jal initCopyCharChain // Инициализация строки move a3, s0 // a3 = а-три-буты init_cputovram_vars_int_scratch: lui a3, 0x1F80 ori a3, a3, 0x0348 // Адрес паттернов в scratchpad //lui a2, 0x1F80 //ori a2, a2, 0x038C // Disable second kanji half command li v0, 0x02 // Ширина в px в 16битном режиме (Original 3px (12 in 4bit), my - 2px) sh v0, 0x1C(sp) li v0, 0x0C // Высота полукандзи. (Original 6px, my - 12px) li a1, 0x0D // 0D - оригинальная длина команды CPUtoVRAM (Scratch) в words lui a0, 0xA000 // A0000000 - CPUtoVRAM command sh v0, 0x1E(sp) // sp+1c = 000C0002 li a1, 0x10 //INSERT // Переписываем длину команды (т.к. формируем символ в пределах одной команды) sb a1, 0x3(a3) // Установка длины 0x10 элемента цепочки CPUtoVRAM (Scratch) sw a0, 0x4(a3) // Установка команды A0000000 CPUtoVRAM (Scratch) lhu t0, 0x9C(gp) // Загрузка vram X из глобальных настроек lhu v0, 0x9E(gp) // Загрузка vram Y из глобальных настроек lui v1, 0x0100 // GPU flushcache command (0100 0000) lui at, 0x1F80 sw v1, 0x0388(at) // Save flushcache (конец первой команды)(not 37c!) // lower half params set // sb a1, 0x3(a2) // Отключаем данные нижней половины // sw a0, 0x4(a2) // // Настройка координат для каждой половины символа. coords_size_cputovram_scr: lh a1, 0x9C(gp) // Загрузка X move s8, s2 // Сохранение Y позиции // lui at, 0x1F80 // sw v1, 0x03C0(at) // Save flushcache (second command (Disabled)) sh t0, 0x18(sp) // Сохранение XY в стек как полуслова (CONCAT) sh v0, 0x1A(sp) lw a0, 0x18(sp) //Загружаем XY-word из стека для CPUtoVRAM //Абсолютно неоптимальная хрень - можно было сразу вытащить word из 0x9C(gp), // либо просто задать a0 = 3001F001, // так как адрес всегда один и тот же. Скорее всего, он менялся при разработке и дебаге игры // sh v0, 0x1A(sp) //Отключаем вычисление и сохранение VRAM-Y нижнего полусимвола. lw v1, 0x18(sp) lw v0, 0x1C(sp) //Грузим размер символа (16-бит) из стека move t0, a1 // t0 будет X - пригодится ниже, в расчётах sw a0, 0x8(a3) //сохраняем 3001F001 to 00350 (uv-коодинаты в команду cputovram - 130x1f0) // sw v0, 0xc(a2) //disable lower sw v0, 0xC(a3) // set 02000c00 to 00354 (sprite pixel size) // sw v1, 0x8(a2) lui v0,0x8001 //load 80010000 to r2 //4bit pattern in scratch lui t8, 0x1f80 ori t8, t8, 0x2e0 addiu t7, v0, 0x3e8 lwl t5, 0x3(t7) lwr t5, 0x0(t7) lwl t6, 0x7(t7) lwr t6, 0x4(t7) swl t5, 0x3(t8) swr t5, 0x0(t8) swl t6, 0x7(t8) swr t6, 0x4(t8) lwl t5, 0xb(t7) lwr t5, 0x8(t7) lwl t6, 0xf(t7) lwr t6, 0xc(t7) swl t5, 0xb(t8) swr t5, 0x8(t8) swl t6, 0xf(t8) swr t6, 0xc(t8) lwl t5, 0x13(t7) lwr t5, 0x10(t7) lwl t6, 0x17(t7) lwr t6, 0x14(t7) swl t5, 0x13(t8) swr t5, 0x10(t8) swl t6, 0x17(t8) swr t6, 0x14(t8) lwl t5, 0x1b(t7) lwr t5, 0x18(t7) lwl t6, 0x1f(t7) lwr t6, 0x1c(t7) swl t5, 0x1b(t8) swr t5, 0x18(t8) swl t6, 0x1f(t8) swr t6, 0x1c(t8) // Copy Sprite DMA Command Forming init_rect_cmd_in_scr: li v0,0x4 // Длина команды RectTexBlend (04000000) sb v0,0x3(s3) // сохраняем в scratch li v0,0x64 // Команда RectTexBlend (64) sb v0,0x7(s3) // Сохранение команды RectTexBlend (64) li v0,0x80 // 80 - стандартная яркость спрайта sb v0,0x4(s3) // R sb v0,0x5(s3) // G sb v0,0x6(s3) // B = Сохранение 80 80 80 + 64 (3d4,5,6,7) // // Здесь запаковка ИСХОДНЫХ координат спрайта в VRAM. //Но они никогда не меняются. Наверное, можно оптимизировать и записывать f0c0 PositionProcessing: bgez a1, CalculateXOffset // Проверка знака X координаты sh s1, 0x20(sp) // Сохранение X позиции AddOffsetX: addiu t0, a1, 0x3F // Смещение, если меньше ноля CalculateXOffset: sra v0, t0, 0x6 sll v0, v0, 0x6 // округляем X subu v0, a1, v0 lh v1, 0x9E(gp) // Загрузка Y параметра sll v0, v0, 0x2 // Умножение X на 4 sb v0, 0xC(s3) // Сохранение X в команду Reсt // Обработка Y координаты YCoordinateProcessing: bgez v1, CalculateYOffset // Проверка знака Y координаты move v0, v1 AddOffsetY: addiu v0, v1, 0xFF // Добавление смещения если отрицательно CalculateYOffset: sra v0, v0, 0x8 // Округление координаты Y sll v0, v0, 0x8 subu v0, v1, v0 sb v0, 0xD(s3) // Сохранение Y текстуры в команду Reсt // всю эту часть кода можно смело заменять записью 7DF3F0C0 в адрес 0xC(s3) // она рассчитана на динамический расчёт координат UV и CLUT, но они у нас статические lhu v1, 0xA4(gp) // Загружаем из глобальных настроек координаты CLUT текста в VRAM (ВСЕГДА 7FD3 - 304 x 511) li v0, 0x8 sh v0, 0x10(s3) // Сохраняем ширину спрайта в пикселях li v0, 0xc sh v0, 0x12(s3) // Сохраняем высоту спрайта в пикселях (0xC = 12) sh v1, 0xe(s3) // Сохраняем clut в команду rect // Основной цикл чтения символов из памяти LoadCharacter: lbu a0, 0x0(s6) // Загрузка символа из текста (Читаем byte вместо half) - текст хранится побайтово nop //Неактуальный код проверки терминатора иероглифов //sltiu v0, a0, 0x1000 // Проверка на конец текста //beq v0, zero, FinishDrawing // Если символ > 0x1000 (команда) - завершаем addiu s6, s6, 0x1 // Переход к следующему символу (в оригинале было смещение на 2 байта) // Подготовка к отрисовке символа lui s1, 0x8008 addiu s5, s1, -0x6C20 // TextDMANexChainAddr lui s0, 0xFF ori s0, s0, 0xFFFF lui s7, 0x8000 // Цикл отрисовки символа NextChar: addiu r4, r4, -0x20 // Смещаем код символа для правильного рассчёта в шрифте //Тут нужна проверка на пробел isFirstChar: jal makeCharPixelsCustom // Создание символа в scratchpad кастомной процедурой clear s2 // Сброс счетчика // Настройка X для символа Setup_X_coord_in_rect_scratch: lw v0, 0xAC(gp) // Первая координата текста lhu v1, 0x4(s4) // Счётчик отрисованных символов в строке (print_params+4) addiu v0, v0, 0x6 // Добавление смещения к первой координате mult v1, v0 // Умножаем... some_printparams_save: lui t2, 0xFF00 // Маска для адреса move t4, s4 // Копирование адреса print_params addiu t3, sp, 0x10 // Сохраняем адрес стек+0x10 move t1, s4 // t1 - основной регистр print_params, который смещается для разных страниц end_x_coord_rect: sh s8, 0xA(s3) // Сохранение Y позиции в команду Rect (3dc) lhu t5, 0x20(sp) // Загрузка X позиции из стека mflo t6 // Результат умножения в t6 addu v0, t5, t6 // Вычисление финальной позиции (X + смещение) sh v0, 0x8(s3) // Сохранение X позиции в команду Rect (3d8) PageLoop: update_free_dma2: // получаем свободный адрес для цепочки в текущей странице lw v0, -0x6C20(s1) // Текущий свободный DMA адрес nop and a0, v0, t2 // В a0 оставляем первый байт с помощью маски & FF000000 (должно получиться 80000000) and v0, v0, s0 // В v0 срезаем первый байт с помощью маски & 00FFFFFF or a1, v0, s7 // в a1 cовмещаем адрес, приводим к виду (80XXXXXX) - получаем конечный адрес для записи команды rect lw v0, 0x4(s5) // DMA счетчик оставшихся свободных цепочек lw v1, 0x0(a1) // Разыменовываем адрес следующей свободной цепочки (v1 = *a1) addiu v0, v0, -1 // Декремент счетчика and v1, v1, s0 // Очистка адресной части следующей свободной цепочки or a0, a0, v1 // Совмещаем адрес с FF...... sw v0, 0x4(s5) // Сохранение счетчика sw a0, -0x6C20(s1) // Сохранение нового адреса // Копирование данных команды rect в RAM из scratch CopyRectCmd: lw t7, 0x0(s3) // Загрузка данных команды rect из scratch lw t8, 0x4(s3) lw t5, 0x8(s3) lw t6, 0xC(s3) sw t7, 0x0(a1) // Сохранение данных команды rect в RAM sw t8, 0x4(a1) sw t5, 0x8(a1) sw t6, 0xC(a1) lw t7, 0x10(s3) nop // ждём команду, чтобы значение загрузилось в регистр (особенность MIPS) sw t7, 0x10(a1) // докидываем данные // Линкуем предыдущую команду страницы и текущую () previous_last_cmd_link_with_current: lw v1, 0x18(t1) // Загрузка указателя хранения последней команды в странице print_params+0x18 lw v0, 0x0(a1) // Загружаем текущую длину (0004), которая перезаписала адрес в цепочке DMA, когда копировали из sсratch lw v1, 0x0(v1) // Разыменование указателя (v1 = *v1), читаем количество команд последней команды (обычно XXFFFFFF) and v0, v0, t2 // Чистим количество команд (& ff) and v1, v1, s0 // Очистка адресной части из параметров строки print_params or v0, v0, v1 // Объединение (делаем cmdCount + ffffff from next_cmd) - очистка/экранирование? sw v0, 0x0(a1) // Сохранение адреса следующей команды в команде rect (04ffffff) lw a0, 0x18(t1) // Снова грузим указатель на маску print_params+0x18 (00fffff) nop // ПРОПУСК! lw v0, 0x0(a0) // Разыменование указателя (v0 = *a0) and v1, a1, s0 // Отрезаем первый байт у адреса цепочки команды rect (сначала это маска 00ФФФФФФ) and v0, v0, t2 // &ff000000 (c маской) or v0, v0, v1 // Объединение (берём у маски кол-во команд, и след адрес) sw v0, 0x0(a0) // Сохраняем след адрес вместо маски (00freeChain) sw a1, 0x18(t1) // Сохраняем адрес след свободной цепочки в print_params (t1+18) // То, что было маской - стало ссылкой на след команду цепочки в этой странице // Проверка на первый символ (нужна ли запись первой записи в print_params+0x28?) lhu v0, 0x4(t4) // Проверка - есть ли хоть один сформированный символ nop bne v0, zero, NotFirstChar // Если не ноль, пропускаем clear t0 // Очистка счетчика sw a1, 0x28(t1) // Установка первичного адреса в print_params (+16 от ссылки на бывшую маску) //ИТОГО -- PrintParams + 0x18 - указатель на последнюю команду на странице // PrintParams + 0x28 - указатель на первую команду на странице // Для второй страницы - смещаемся +0x04 NotFirstChar: move a3, t0 // Обнуляем a3 - это смещение для копирования данных в полусимволе move a2, t3 // загрузка адреса sp+10. В *a2 будет храниться сформированный адрес cputovram в ram // Здесь был цикл на 2 полусимвола. // Выдаются свободные адреса цепочек и формируется cputoVram1 и 2 (нам нужна одна команда вместо двух) cputovram_cmd_from_scratch_copy: update_free_dma3: halfkanji_loop: lui a1, 0x1F80 lw v0, -0x6C20(s1) // Берём адрес след свободной цепочки ori a1, a1, 0x0348 // Берём адрес в scrathPad символа CPUtoVRAM and v0, v0, s0 // отрезаем первый байт у свободной цепочки &00FFFFFF or v0, v0, s7 // Прикручиваем адрес 80....... || nextChain sw v0, 0x0(a2) // Сохранение след полного адреса в sp+0x10 lw v1, -0x6C20(s1) // Берём ЕЩЁ РАЗ адрес свободной цепочки, но уже в v1 lw v0, 0x0(v0) // Разыменование - v0 = *v0 lw a0, 0x4(s5) // Грузим счётчик and v1, t2 // Отрезаем префикс у адреса свободной цепочки в v1 (0xFF - Мы грузили адрес свободной цепочки, чтобы взять префикс FF???) and v0, s0 // чистим адрес от префикса (который и так 00, но неизвестно, что было в данных)(0x00FFFFFF) or v1, v0 // в v1 - адрес NextFreeDMA cо старым префиксом addiu a0, -0x01 // Декремент счётчика sw v1, -0x6C20(s1) // Сохраняем NextFreeDMA sw a0, 0x4(s5) // save counter lw a0, 0x0(a2) // Грузим след свободный адрес из *sp+10 addu v1, a3, a1 // v1 = смещение полусимвола + начало первого символа в scratch (указание на начало данных полусимвола) or v0, v1, a0 // v0 = Проверка на выравнивание адресов чтения и записи andi v0, v0, 0x0003 // путём схлопывания их адресов через or и проверки первых 2 битов (то есть числа 3). beqz v0, Copy16Bytes // Если биты пусты, значит оба адреса выровнены по 4 байта, можно копировать word целиком addiu v0, v1, 0x40 // v0 = максимальный адрес копирования исходных данных символа в Scratch // Копирование данных команды CPUtoVRAM символа из scratchpad в RAM // Невыровненное копирование по 2 байта CopyBy2Bytes: lwl t7, 0x3(v1) // Загрузка полусловами по 2 байта lwr t7, 0x0(v1) lwl t8, 0x7(v1) lwr t8, 0x4(v1) lwl t5, 0xB(v1) lwr t5, 0x8(v1) lwl t6, 0xF(v1) lwr t6, 0xC(v1) swl t7, 0x3(a0) // Сохранение с выравниванием swr t7, 0x0(a0) swl t8, 0x7(a0) swr t8, 0x4(a0) swl t5, 0xB(a0) swr t5, 0x8(a0) swl t6, 0xF(a0) swr t6, 0xC(a0) addiu v1, 0x10 // Смещение адреса в scratch bne v1, v0, CopyBy2Bytes // Проверяем на достижение целевого адреса addiu a0, a0, 0x10 // Следующий целевой адрес j last4BytesCopy nop // Быстрое выровненное копирование по 4 байта Copy16Bytes: lw t7, 0x0(v1) // Загрузка 32-битных слов lw t8, 0x4(v1) lw t5, 0x8(v1) lw t6, 0xC(v1) sw t7, 0x0(a0) // Сохранение sw t8, 0x4(a0) sw t5, 0x8(a0) sw t6, 0xC(a0) addiu v1, 0x10 // Смещение адреса в scratch bne v1, v0, Copy16Bytes // Проверяем на достижение целевого адреса addiu a0, a0, 0x10 // Следующий целевой адрес // Завершение копирования last4BytesCopy: lwl t7, 0x3(v1) // Копирование остатка lwr t7, 0x0(v1) // lwl t8, 0x7(v1) // lwr t8, 0x4(v1) nop swl t7, 0x3(a0) swr t7, 0x0(a0) // swl t8, 0x7(a0) // swr t8, 0x4(a0) // Здесь старый код цикла копирования второго полукандзи // addiu a3, a3, 0x44 // Увеличение смещения // addiu t0, t0, 0x1 // Инкремент счетчика полусимволов // sltiu v0, t0, 0x2 // Если счётчик <2, // bne v0, zero, halfkanji_loop// то считываем ещё раз, // addiu a2, a2, 0x4 // увеличивая адрес хранения команды полусимвола (a2 = sp+14) // Линковка DMA-команд link_char_dma_cmds: lw a0, 0x10(sp) // в a0 грузим адрес первого полусимвола (который хранился в *sp+10) lw v1, 0x18(t1) // Получили адрес rect из print_params (вернее, видимо, адрес начала цепочки) lw v0, 0x0(a0) // v0 = *a0 - длина команды cpoutovram (10/0d 000000) без следующего адреса lw v1, 0x0(v1) // v1 = *v1 - разыменовали и получили длину команды rect (04ffffff) без следующего адреса and v0, t2 // v0 & FF000000 - чистим длину команды and v1, s0 // v1 & 00FFFFFF - отрезали маску у длины команды rect or v0, v1 // || Совмещаем число количества команд cpoutovram и маску rect (0dffffff) sw v0, 0x0(a0) // сохраняем длину команды cpoutovram с маской ffffff в цепочку lw a0, 0x18(t1) // Опять грузим адрес rect из print_params lw v1, 0x10(sp) // ОпятЬ! грузим адрес первого полусимвола lw v0, 0x0(a0) // v0 = *a0, то есть грузим длину команды с маской (10ffffff) вместо адреса and v1, s0 // &00ffffff у адреса первого полусимвола (режем префикс), оставляя адрес and v0, t2 // в v0 - длина команды rect. Чистим (&ff000000) or v0, v1 // СОЗДАЁТСЯ ЛИНК команды rect и след. адреса (04+nextCommandAddr) sw v0, 0x0(a0) // сохраняем линк в цепочке (команда rect указывает на следующую команду) // односимвольная система j disabled_lower_kanji // перепрыгиваем линковку нижнего полусимвола lw v0, 0x10(sp) // сразу грузим адрес цепочки cpoutovram1 для сохранения в print_params enabled_lower_kanji: // Второй полусимвол - код оставлен, чтобы понимать принцип линковки // зачем-то сначала адрес следующей команды превращается в xxFFFFFF, // скорее всего это маркер конца цепочки текста lw a0, 0x14(sp) // в a0 грузим адрес второго полусимвола (который хранился в *sp+14) lw v1, 0x10(sp) // грузим адрес первого полусимвола lw v0, 0x0(a0) // v0 = *a0, грузим команду с маской (0d000000) вместо адреса lw v1, 0x0(v1) // v1 = *v1, Разыменовываем адрес первого полусимвола (0dffffff) and v0, t2 // в v0 - длина команды cpoutovram2 (&ff000000) and v1, s0 // &00ffffff у адреса первого полусимвола (режем префикс) or v0, v1 // получаем длину команды cpoutovram2 с &00ffffff (0dffffff) sw v0, 0x0(a0) // сохраняем длину команды cpoutovram2 в цепочку lw a0, 0x10(sp) // грузим адрес первого полусимвола lw v1, 0x14(sp) // v1 - адрес второго полусимвола lw v0, 0x0(a0) // v0 = *a0 Разыменовываем адрес первого полусимвола and v1, s0 // &00ffffff у адреса второго полусимвола (режем префикс) and v0, t2 // в v0 - длина команды cpoutovram1. чистим (&ff000000) or v0, v1 // СОЗДАЁТСЯ ЛИНК команды cpoutovram1 и cpoutovram2 (0d+nextCommandAddr) sw v0, 0x0(a0) // сохраняем линк в цепочке (команда cpoutovram1 указывает на следующую команду) lw v0, 0x14(sp) // v0 - адрес второго полусимвола disabled_lower_kanji: addiu s2, 0x1 // Увеличение счетчика страниц sw v0, 0x18(t1) // Обновление связи в print_params (она указывает на адрес последней команды символа в этой странице) sltiu v0, s2, 0x2 // Проверка счётчика текущей страницы bnez v0, PageLoop // Если < 2, то формируем символ ещё раз в новую страницу. addiu t1, t1,0x4 // смещаем базовый адрес text_params для следующей страницы // Обновление индекса и проверка конца чтения SpaceCharJumpHere: lhu v0, 0x4(s4) // Грузим счётчик символов в print_params nop addiu v0, v0, 0x1 // Инкремент счётчика //Моя проверка переменных lui t3, MyAddr lhu v1, -0x10(t3) // грузим количество символов lbu r4, 0x0(s6) // Читаем следующую букву addiu s6, s6, 0x1 // Смещаем позицию чтения sh v0, 0x4(s4) // Сохраняем счётчик сформированных символов в строке в print_params bne v0, v1, NextChar // Сравниваем переменные - моё общее кол-во символов и счётчик символов из print_params. Завершаемся, если количество сошлось. nop //Старый код с иероглифами // lhu a0, 0x0(s6) // Загрузка следующего символа // nop // sltiu v0, a0, 0x1000 // Проверка на конец текста //addiu s6, s6, 0x2 // Старый код - следующий символ // Закрытие отрисовки текста TextEnd: clear s2 // Очистка счётчика текстурных страниц s2 lui s3, 0x8008 // s3 = 80080000 addiu s5, s3, -0x6C20 // s5 = 800793e0 - nextFreeChain lui s1, 0xFF ori s1, s1, 0xFFFF // s1 = 00ffffff move s0, s4 // s0 = print_params clear a1 // Очистка a1 // Цикл установки текстурных страниц PageTLoop: li a2, 0x1 // Длина команды текстурных страниц. Читается в SetDrawTPage addu s2, a2 // Обновление счетчика s += a2 lui v0, 0x8000 // Префикс адресации v0 = 80000000 lui a0, 0xFF00 // Маска адреса a0 = ff000000 //Расчищаем команду под установку текстурной страницы lw v1, -0x6C20(s3) // v1 = nextFreeDma lw a3, 0xA0(gp) // Загрузка a3 = 34 (текстурная страница). Читается в SetDrawTPage and v1, s1 // Очистка префикса nextFreeDma or v1, v0 // превращаем nextFreeDma в адрес (80000000 || nextFreeDma) sw v1, 0x30(s0) // Сохранение адреса пустой команды для drawtpage в print_params lw v0, -0x6C20(s3) // Ещё раз v0 = nextFreeDma lw v1, 0x0(v1) // Разыменовывание v1 = *v1. Получаем содержимое адреса след. свободной цепочки. and v0, a0 // оставляем префикс от nextFreeDma (99,999% это будет ff) and v1, s1 // Чиcтка адреса в цепочке от префикса lw a0, 0x4(s5) // Грузим DMA-счетчик or v0, v0, v1 // Объединение префикса со след пустым адресом sw v0, -0x6C20(s3) // Сохранение nextFreeDma addiu a0, a0, -1 // Декремент счетчика sw a0, 0x4(s5) // Сохранение счетчика свободных команд lw a0, 0x30(s0) // Загрузка адреса пустой команды для drawtpage из print_params jal SetDrawTPage // Установка DrawTPage (текстурной страницы) addiu s0, s0, 0x4 // Смещение в print_params для следующей страницы sltiu v0, s2, 0x2 // Проверка на завершение 2 страниц bne v0, zero, PageTLoop // Если не закончили, продолжаем clear a1 // Очистка a1 // Восстановление регистров и возврат ReturnFromFunction: move v0, s4 // Возвращаем DMA адрес lw ra, 0x4C(sp) // Восстановление return address lw s8, 0x48(sp) // Восстановление s8 lw s7, 0x44(sp) // Восстановление s7 lw s6, 0x40(sp) // Восстановление s6 lw s5, 0x3C(sp) // Восстановление s5 lw s4, 0x38(sp) // Восстановление s4 lw s3, 0x34(sp) // Восстановление s3 lw s2, 0x30(sp) // Восстановление s2 lw s1, 0x2C(sp) // Восстановление s1 lw s0, 0x28(sp) // Восстановление s0 jr ra // Возврат из функции addiu sp, sp, 0x50 // Восстановление стека .include "3_CD_EXTRA/cd_extra_charload.asm" .include "1_IS/charCalcs.asm" .close .include "3_CD_EXTRA/cd_extra_txtpatches.asm" //misc text patches // COMPILE COMMAND: ./armips -sym 3_CD_EXTRA/BUILD_LOGS/SLPS_028.26.map -temp 3_CD_EXTRA/BUILD_LOGS/SLPS_028.26.map 3_CD_EXTRA/cd_extra_main.asm