- PVSM.RU - https://www.pvsm.ru -
Каждый, кто хоть раз заглядывал на Hacker News или r/ItRunsDoom, знает традицию: DOOM должен работать на всём. PDF‑файлы, SQL‑запросы, кишечные бактерии, тест на беременность — список бесконечен, и каждый новый порт абсурднее и интереснее предыдущего
Но я задался вопросом: а можно ли запустить DOOM внутри шрифта?
На вопрос «зачем?» ответить я могу тоже самое, что и авторы остальных портов: «Руководствуясь академическим интересом», «Для исследования границ, казалось бы привычных систем», ну и «Просто по приколу». Суть была в том, чтобы запустить не «рядом» со шрифтом, не «в браузере, который рендерит шрифт». А именно внутри — чтобы сам шрифтовой движок вычислял 3D‑геометрию стен
Оказалось, что можно. И вот как:
Когда Apple проектировала формат TrueType в конце 1980-х, перед инженерами стояла конкретная задача: шрифты должны выглядеть идеально на любом экране, при любом размере. Для этого в формат встроили «hinting» - набор инструкций, которые двигают контрольные точки глифов, подгоняя их под пиксельную сетку.
Набор инструкций оказался... щедрым. Вот что есть в TrueType hinting VM:
FDEF/ENDF — определение функций
CALL — вызов функций
RS/WS — чтение/запись в storage (26+ регистров)
IF/ELSE/EIF — условия
JMPR — безусловный переход (можно сделать while)
MUL/DIV/ADD/SUB — арифметика (F26Dot6 fixed‑point)
SCFS/GC — манипуляция координатами контрольных точек
Функции, циклы, условия, регистры, арифметика. Это стек‑машина, и она Тьюринг‑полна (с оговоркой на конечную память). Кто-то должен был это доказать чем-то поинтереснее счётчика :-)
Идея простая, реализация - нет.
JavaScript играет роль CPU: обрабатывает ввод с клавиатуры, хранит состояние игры (позиция игрока, угол поворота), управляет тайминагми.
Шрифт играет роль GPU: получает позицию и угол через font-variation-settings оси, запускает рейкастинг внутри hinting VM, вычисляет высоту стен для каждого столбца экрана, и возвращает результат через координаты контрольных точек глифа, которые JS читает обратно.
Коммуникация:
JS -> Шрифт: записать позицию/угол в variation axes
Шрифт: запустить hinting-программу, вычислить 3D
Шрифт -> JS: прочитать координаты глифа (это вычисленная высота стен)
Если нажать Tab в демке, откроется debug mode - там видно, как font-variation-settings оси меняются в реальном времени при движении игрока
Первые эксперименты я делал вручную, записывая TT-инструкции по одной. К десятой функции стало ясно, что нужен компилятор.
Собственно, получился мини-компилятор из трёх частей:
Лексер - разбивает DSL-код на токены
Парсер - строит AST
Кодогенератор - транслирует AST в последовательность TT-инструкций
DSL выглядит примерно так: вы пишете функции с арифметикой и условиями, а компилятор превращает это в FDEF/ENDF блоки с правильной стековой адресацией. 451 тест помогает не сойти с ума при рефакторинге (однако их написание - нет).
За время разработки я нашёл несколько багов (или «особенностей»), которые стоят отдельного упоминания.
MUL truncation: 1 × 4 = 0. TrueType использует F26Dot6 fixed-point формат: 26 бит на целую часть, 6 бит на дробную. Когда вы умножаете два числа, каждое из которых меньше 1.0 в этом формате, результат округляется до нуля. Это не баг - это особенность. Но когда рейкастер вдруг перестаёт рисовать стены, а вы два дня ищете ошибку в логике... Короче, неприятно.
SVTCA[0] = Y, а не X. Инструкция SVTCA задаёт вектор проекции. Я предполагал, что [0] - это X-ось (как в любом здравом API). Нет. Спецификация индексирует с Y=0, X=1. Два часа жизни.
«return» не возвращает из while. В TT bytecode нет настоящего return. Точнее, есть, но он выходит из FDEF, а не из «цикла», который на самом деле реализован через JMPR. Пришлось изобретать флаговые переменные для имитации break/return.
llama.ttf (июнь 2024) - LLM внутри шрифта через HarfBuzz WebAssembly shaper. Wasm - это по определению универсальный рантайм, так что «шрифт» тут скорее контейнер. Увидев этот проект, стало интересно разобраться в том, как вообще блин можно запустить модель в шрифте, но, как оказалось, всё куда интереснее, так что спасибо авторам за их проект, он действительно очень необычный и интересный.
Это не просто забавный факт. Каждый браузер, который рендерит TrueType-шрифты с включённым хинтингом, выполняет произвольный код внутри шрифтовой VM. В 2025 году были опубликованы исследования [1] по side-channel атакам через hinting (микроархитектурные тайминги), но вычислительная мощность самой VM пока мало изучена.
Теоретически, вредоносный шрифт мог бы использовать hinting‑программу для чего угодно — от fingerprinting до утечки данных через тайминги рендеринга. TTF‑DOOM — это proof‑of‑concept того, насколько мощен этот «безобидный» механизм
Демо [2](Chrome/Edge, нужна поддержка variable fonts)
Исходники [3]
Нажмите Tab для debug mode - увидите, как font-variation-settings оси обновляются в реальном времени
Если у вас есть идеи, что ещё можно запустить внутри TrueType hinting VM или хотите обсудить угрозы для безопасности - вперёд! Я весь внимание :-)
Автор: Priiiiiinter
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/449141
Ссылки в тексте:
[1] исследования: https://www.researchgate.net/publication/389662886_Hidden_in_Plain_Sight_Scriptless_Microarchitectural_Attacks_via_TrueType_Font_Hinting
[2] Демо : https://4rh1t3ct0r7.github.io/ttf-doom/
[3] Исходники: https://github.com/4RH1T3CT0R7/ttf-doom
[4] Источник: https://habr.com/ru/articles/1020668/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1020668
Нажмите здесь для печати.