``` c #include #include #include /* Структуры данных */ typedef struct { uint* next_block; // Следующий блок DMA (младшие 24 бита) uint control; // Контрольные биты DMA uint data_addr; // Адрес данных uint tpage_params; // Параметры текстуры (TPage) } DMAControlBlock; typedef struct { short x, y; // Позиция символа short char_width; // Ширина символа ushort flags; // Флаги рендеринга uint* dma_head; // Голова DMA-цепочки uint* dma_tail; // Хвост DMA-цепочки } TextRenderState; /* Константы */ #define SCRAM_BASE 0x1F800000 // Быстрая память PS1 #define DMA_MAGIC 0x80000000 // Флаг DMA-блока #define CHAR_OFFSET 0x20 // Смещение ASCII-кодов /* Глобальные переменные (аналоги оригинальных) */ static uint* gDmaNextFree = (uint*)0x8007CE68; static uint* gDmaCounter = (uint*)0x8007CE6C; static uint gActiveTpage = 0x8007CA0C; /* Основная функция рендеринга текста */ DMAControlBlock* RenderTextSystem( const char* text, short base_x, short base_y, uint color_flags, uint tpage_config ) { /* Инициализация состояния */ TextRenderState state; state.x = base_x; state.y = base_y; state.flags = color_flags; /* Получаем начальный DMA-блок из SCRAM */ DMAControlBlock* dma_blocks = (DMAControlBlock*)(*gDmaNextFree | DMA_MAGIC); state.dma_head = state.dma_tail = (uint*)dma_blocks; /* Настройка параметров рендеринга */ StoreColorToAddr(color_flags, tpage_config); InitDMAtextChain(dma_blocks, base_x, base_y); /* Основной цикл обработки текста */ while (*text) { char current_char = *text++; ushort char_code = current_char - CHAR_OFFSET; /* Особые обработки для специфичных символов */ switch (current_char) { case 'g': case 'j': case 'p': case 'q': case 'y': base_y += 2; // Смещение для символов с хвостами break; } /* Позиционирование символа */ short render_x, render_y; if (state.flags & 0x10) { /* Альтернативный режим позиционирования */ render_x = base_x - GetCharLeftOffset(char_code); render_y = base_y + GetCharBaseline(char_code); } else { /* Стандартный режим */ render_x = base_x + dma_blocks->char_width * 8; render_y = base_y; } /* Генерация DMA-команд для символа */ DMAControlBlock* new_block = AllocDMABlock(); SetupCharRender(new_block, char_code, render_x, render_y); /* Добавление в цепочку */ state.dma_tail->next_block = (uint)new_block & 0xFFFFFF; state.dma_tail = new_block; /* Обновление позиции */ if (dma_blocks->char_width == 0) { state.dma_head = new_block; dma_blocks->char_width = 1; } } /* Финализация текстурных страниц */ for (int i = 0; i < 2; i++) { DMAControlBlock* tpage_block = AllocDMABlock(); SetDrawTPage(tpage_block, 0, 1, gActiveTpage); state.dma_tail->next_block = (uint)tpage_block & 0xFFFFFF; state.dma_tail = tpage_block; } return dma_blocks; } /* Вспомогательные функции */ static DMAControlBlock* AllocDMABlock() { DMAControlBlock* block = (DMAControlBlock*)(*gDmaNextFree | DMA_MAGIC); (*gDmaCounter)--; *gDmaNextFree = (*gDmaNextFree & 0xFF000000) | (*block & 0xFFFFFF); return block; } static void SetupCharRender(DMAControlBlock* block, ushort char_code, short x, short y) { /* Настройка параметров символа */ block->control = 0x1000000; // Базовые флаги DMA block->data_addr = GetCharDataAddr(char_code); block->tpage_params = (y << 16) | x; // Позиция /* Дополнительные настройки из оригинального кода */ block->control |= 0x64808080; // Специфичные флаги PS1 } ```