Retrolang — язык для PlayStation 1
PS1 — наверное, одна из самых культовых консолей всех времен. Количество часов моего детства, проведенных за ней, бессчетно. Я иногда даже вставал пораньше, чтобы часок поиграть перед школой) Классические части Need for Speed и Final Fantasy, Tekken и Soul Blade, Twisted Metal, Driver, TOCA, Crash Bandicoot, Spyro the Dragon, Medievil, Sim Theme Park — список моих любимых игр с первой «соньки» можно продолжать долго. В некоторые кроссплатформенные игры типа GTA, Quake 2, Myst я тоже впервые поиграл именно на PS. 3D-графика в то время сносила крышу и казалась совершеннейшей фантастикой — я ей увлекся как раз благодаря любимой консоли. А с появлением компьютера и Интернета я быстро освоил эмуляцию, стал качать образы игр, которые было не найти в магазинах, и записывать свои диски.
Надо ли говорить, что мне всегда хотелось научиться программировать под PS. Я редко пишу об этом, но этой темой я периодически занимаюсь с конца нулевых, и с тех пор скопил много информации, инструментов и исходников. Началось все с того, что я научился компилировать проги при помощи утекшего в Сеть старого официального SDK PsyQ, который использует C89. Именно на нем было создано большинство хитов. С ним работать довольно удобно, несмотря на то, что это древний софт, написанный еще в эпоху Windows 95. Но мне показалось, что как-то нехорошо зависеть от пиратского ПО, пусть и заброшенного. К тому же, он закрытый, его невозможно модифицировать. Поэтому я взялся писать собственную графическую библиотеку PSXLib. Дело пошло, я довольно глубоко погрузился в детали на уровне ассемблера, но мне ни один существующий ассемблер для консоли не понравился по ряду причин, о которых сейчас излишне рассказывать. Я решил сделать свой собственный, который назвал Retrolang, попутно изучив архитектуру MIPS-I и протокол загрузки PlayStation во всех деталях.
Недавно я взял этот проект и ради прикола скормил его Claude, чтобы он сгенерировал под это дело компилятор языка чуть ниже уровнем, чем C. Получилось лучше, чем я ожидал! Я добавил простейший препроцессор (#define, #include) плюс еще кое-какие расширения, и получилась вполне рабочая штука для низкоуровневых экспериментов. Недавно я допилил все это до состояния альфа-версии и выложил на GitHub:
https://github.com/gecko0307/retrolang
Язык во многом похож на C, но поддерживает не все его фичи. Используется стандартный для 32-битной архитектуры набор целочисленных типов (char/uchar, short/ushort, int/uint). 64-битных чисел нет, как нет и float’ов. Поддерживаются указатели, массивы, строковые литералы, структуры (в том числе вложенные). Вызовы функций соответствуют ABI O32, за исключением аргументов на стеке — они пока не поддерживаются, поэтому функции могут принимать только по 4 аргумента.
В данный момент можно выводить текст в последовательный порт (местный аналог TTY), определять нажатия кнопок геймпада и рисовать простейшую графику, причем все это получается более-менее читаемо. Например, вот как выглядит Hello, World:
void main()
{
bios_a(0x13, "Hello, World!\n");
}
Вызовы BIOS являются интринсиками компилятора — это фича, которой не было ни в PsyQ, ни где-либо еще, такие вызовы раньше всегда делались через ассемблерные вставки.
Есть возможность встраивать файлы в бинарник — очень не хватало такого в C. Я для этого ввел специальный синтаксис атрибутов для глобальных переменных:
char* timFile @("assets/img.tim");
Кодогенератор интерпретирует это как обычный статический массив, данные которого нужно прочитать из внешнего файла. Таким образом можно легко запихнуть в игру ресурсы (если их совсем мало), вместо того, чтобы читать с диска в рантайме.
И, конечно, есть ассемблер, с которого проект и начался. Ассемблерные вставки добавляются в программу при помощи таких же атрибутов:
void asmFunc() @("asmFunc.s");
Код тела такой функции реализуется во внешнем ассемблерном листинге. При этом asmFunc — обычная функция с прологом и эпилогом.
Я работаю над стандартной библиотекой с собственным драйвером GPU, который отчасти основан на коде из PSXLib. Реализован как прямой рендеринг через GP0, так и непрямой через командные буферы и DMA. Есть начальная поддержка 3D-графики — трансформация вершин в экранное пространство и Z-упорядочивание. Правда, 3D там намного сложнее, чем 2D, и требует самых изощренных хаков, с которыми я сталкивался в своей жизни как графический разработчик. Это вам не в Unity кнопочки тыкать. Перед людьми, которые придумали, как рендерить 3D-модели на платформе с 1 мегабайтом видеопамяти и без Z-буфера, я снимаю шляпу (хотя имен их не знаю).

Трудно сказать, для чего все это сегодня нужно — эпоха 32-битных консолей давно в прошлом, и оценить подобные штуки могут лишь единичные любители ретрокомпьютинга и системного программирования. Это, конечно, в основном хобби для души. С другой стороны, MIPS отлично подходит в качестве учебной архитектуры — она довольно простая, можно на ее примере изучить работу процессора и то, как абстрактные логико-математические концепции отображаются на реальное железо. Опыт работы на таком низком уровне незаменим. Так что, надеюсь, мои наработки кому-нибудь пригодятся, хотя бы как референс.
Кстати, я также веду собственную вики по PS, которая находится тут: https://psxdev.pixperfect.online/wiki/