Разработка и ромхакинг > Программирование
[SMD] MegaDriveRecomp - статический рекомпилятор
<< < (2/2)
Беларус:

--- Цитата: Werton от 04 Май 2026, 16:19:02 ---И в чём сложность пройти все ветки кода, когда как раз всё равно придётся "проходить весь код"?
--- Конец цитаты ---
У тебя есть опыт реверса? Как ты автоматически найдёш конец таблицы прыжком switch или вычислиш адрес прыжка, который записываетса в регистр? Или может проходил игру так, штобы эмулятр полностью разметил весь ром (где код, а где данные)? Это же надо применить всё, потыкать в каждый тайник, использовать все приёмы и т.д. И всё равно останетса код, который не доступен - што-то вырезанное, отладочное, всякий мусор...
Если бы можно было пройти код автоматически полностью, то не было бы проблемы в хаках сдвигать код.
Я делаю свой рекомпилятр игр Сеги, и человеку придётса делать в игре всё, штобы покрыть все пути. Програма будет подсказывать типа "холодно-горячо", насколько близок чел к новой ветке кода.

Я бы понял, если бы эти новые рекомпилятры были эмулятрами, которые слиты с игрой, и по мере прохождения код игры заменялся бы на код железа, на котором они запущены. Тогда при первом прохождении много чево тормозило бы, но зато потом уже летало бы.
И всё равно в С++ коде имена магическим образом не появились бы, это смогла бы нейросеть, видя картинку игры.
У меня имена даёт человек.


--- Цитата: Werton от 04 Май 2026, 16:19:02 ---но вряд ли, кто такое юзал в мегадрайве
--- Конец цитаты ---
И такое есть, хоть и редко. Мефисто и worm с таким сталкивались:

--- Цитата ---Sonic the Hedgehog (1, 2, 3 & Knuckles):
Некоторые элементы движка переписывают инструкции отрисовки в RAM «на лету» для ускорения работы, что формально является модификацией кода перед исполнением.
Fantastic Dizzy:
Использовала сложные системы сжатия данных, где распаковщик мог модифицировать собственные инструкции для анализа сжатых спрайтов.
Игры с использованием нестандартных декомпрессоров (MEGAPACK):
Многие поздние игры, использующие систему сжатия MEGAPACK, применяли bit-packing, который требовал динамической генерации кода распаковки в RAM.
--- Конец цитаты ---
Werton:

--- Цитата: Беларус от 04 Май 2026, 20:33:12 ---И всё равно в С++ коде имена магическим образом не появились бы, это смогла бы нейросеть, видя картинку игры.
У меня имена даёт человек.
--- Конец цитаты ---
Этого никто и не обещал, тут понятное дело нужно будет создавать "словарь" по адресам и вручную обзывать  функции.


--- Цитата: Беларус от 04 Май 2026, 20:33:12 ---У тебя есть опыт реверса?
--- Конец цитаты ---
нет :)


--- Цитата: Беларус от 04 Май 2026, 20:33:12 ---Как ты автоматически найдёш конец таблицы прыжком switch или вычислиш адрес прыжка, который записываетса в регистр? Или может проходил игру так, штобы эмулятр полностью разметил весь ром (где код, а где данные)? Это же надо применить всё, потыкать в каждый тайник, использовать все приёмы и т.д. И всё равно останетса код, который не доступен - што-то вырезанное, отладочное, всякий мусор...
Если бы можно было пройти код автоматически полностью, то не было бы проблемы в хаках сдвигать код.
Я делаю свой рекомпилятр игр Сеги, и человеку придётса делать в игре всё, штобы покрыть все пути. Програма будет подсказывать типа "холодно-горячо", насколько близок чел к новой ветке кода.
--- Конец цитаты ---
Как любит отвечать один форумчанин - а ты гадалок ии спрашивал? :lol:
Да, статический анализ никогда не сможет идеально восстановить весь код. Но конечная цель — не «восстановить всё», а создать работоспособную программу, что достигается прагматичными подходами. Главное недоразумение в том, что цель рекомпиляции — не перевести код на 100%, а создать работающую программу, используя прагматичные подходы. Статическая рекомпиляция не пытается быть идеальной, она просто достаточно хороша для практического использования.

1. 🎯 Полный «прогон» игры не нужен (но данные — критичны)
Даже при самом идеальном прохождении, путь игрока в коде — лишь малая толика. Ошибка в том, что он приравнивает код к данным. Например, редко используемые диалоги — это данные, а не код. С ними всё просто: рекомпилятор может передать управление в специальный рантайм, который выполнит нужный кусок кода, если до него всё же дойдёт дело. Остаются только «мёртвые» зоны для разработчиков, которые нигде не вызываются, — их отсутствие в рекомпиляции не страшно.

2. 🧩 Главный трюк: ручная разметка вместо магии
Ни один рекомпилятор не работает на магии. И N64Recomp, и PS2Recomp изначально рассчитаны на помощь человека-исследователя. Вы просто даёте инструменту карту:

Файл символов: Он указывает, где в игре находятся функции и глобальные переменные.

Конфигурация (TOML): Здесь вручную прописываются «заглушки» для сложных систем и подсказывается, как перевести код.

Такой гибрид сберегает нервы разработчика и даёт гарантию точности перевода.

3. 🎮 Игры не такие сложные, как кажется
Сложность статической рекомпиляции часто переоценивают из-за параноидального подхода. На деле современные игры (даже для старых консолей) в основном пишутся на высокоуровневых языках (C/C++), а значит, их код стандартизирован, предсказуем и не пытается скрыть свои границы.

Если же какой-то кусок кода упрятан, инженеры не паникуют, а применяют чёткие стратегии:

Заглушки (Stubs) - Заранее, в конфиге, прописывается заглушка, чтобы компиляция не сломалась при её отсутствии в коде.   Не нашёлся код аудиодрайвера — система сделает заглушку, а игра просто запустится без звука.

Динамический поиск - Алгоритм эвристически ищет код, опираясь на стандартные шаблоны компилятора (например, определяет начало функции по push ebp или аналогам).   Инструмент находит jr $ra и догадывается, что это конец функции.

Гибридный прогон - Игра как бы «запускается» под наблюдением: пишется лог адресов инструкций, которые выполнились, и только они потом переводятся в код. (Вероятно имелся в виду именно такой подход — самый надёжный для очень сложных проектов)

Проблема в том, что в бинарном коде таблица переходов switch и код вокруг неё — это просто поток чисел, и статическому анализатору нужно как-то угадать границы. Но это не значит, что задача невыполнима: рекомпиляторы и дизассемблеры научились распознавать их с помощью следующих методов.

🕵️‍♂️ Метод 1: Поиск по шаблону (Pattern Matching)
Это самый распространённый подход. Анализатор ищет в коде характерную сигнатуру, которую оставляют компиляторы. В 99% случаев switch генерируется по чёткому сценарию.

Типичный паттерн: Сначала выполняется проверка границ значения переменной. Затем значение преобразуется в индекс. После этого код загружает адрес из таблицы переходов (jump table) по этому индексу и выполняет безусловный переход (jr $t0 на MIPS или jmp [rax] на x86).

Чем это полезно: Обнаружив такую последовательность из инструкций «проверка-вычисление-загрузка-переход», анализатор с высокой уверенностью говорит: «Это switch». Затем легко вычислить и саму таблицу.

Ограничение метода: Этот подход ненадёжен для нестандартного или обфусцированного кода. Например, Majin Buu's Revenge использует switch всего с одним case. Такой код часто генерируется условным переходом, похожим на if, а не на полноценную таблицу, из-за чего автоматические инструменты могут его не распознать.

🔬 Метод 2: Анализ потока данных (Data Flow Analysis)
Этот метод применяется, когда простой поиск по шаблону не срабатывает.

Обратный анализ (Backward Slicing): Вместо того чтобы искать готовый шаблон, анализатор идёт от инструкции jr $t0 (прыжок) назад по ходу выполнения программы. Он выясняет, как регистр t0 получил своё значение. Например, он может обнаружить, что t0 = table_base + index * 4. Если ему удаётся отследить, откуда берётся table_base, то он может восстановить расположение таблицы, даже если код вокруг неё выглядит нестандартно.

Результат позволяет точно определить, где в памяти лежат адреса переходов для каждого case, даже если компилятор их немного завуалировал.

🤝 Метод 3: Гибридный подход (Символическое выполнение)
Это уже «тяжёлая артиллерия», которая используется для очень сложных случаев.

Пример JTR: Исследовательский инструмент JTR решает проблему, вычисляя все возможные значения, которые могут оказаться в регистре перед прыжком. Если удаётся определить, что их, например, 10, значит, перед нами switch с таким количеством case.

Пример PS2Recomp: Анализатор ps2xAnalyzer этого рекомпилятора комбинирует методы. Сначала он пытается «угадать» таблицу переходов, а если не получается — оставляет для неё специальную заглушку (stub). При запуске, если игра доходит до этого места, специальная рантайм-система перехватывает управление, определяет адрес перехода и «доучивает» перевод. Этот подход надёжен и не приводит к сбоям.

полностью автоматическое статическое определение во всех возможных случаях — задача нерешаемая. Но на практике инженеры создают инструменты, которые справляются с этим достаточно хорошо — для 90-95% кода, написанного стандартными компиляторами.
Именно поэтому рекомпиляторы прекрасно работают, когда их создатели либо «обучают» их на тысячах примеров, либо предусматривают механизмы для ручного уточнения и динамической доводки.
Но судя из ответа ии, да, ты от части прав, и полная 100% статическая рекомпиляция невозможна, но близкая к этому вполне. Хотя возможно иишка и наврала, они это умеют, а мои полномочия на этом как-бы все :biggrin:
Беларус:

--- Цитата: Werton от 05 Май 2026, 03:23:16 ---Этого никто и не обещал
--- Конец цитаты ---
Да, я перепутал тему с темой тово чела, который хочет получить Си-код от дизасма игры Сеги.


--- Цитата: Werton от 05 Май 2026, 03:23:16 ---нет  :)
--- Конец цитаты ---
А то я прочитал твой вопрос как "Чё там сложново-то? Ерунда".

ИИшка што-то прям всё в кучу наболтала и всё напирает на то, што распознаютса особенности компилятров, которыми собиралась игра. Да, Ида на это опираетса. Но только вот мы разбираем игры, многие из которых писались на асэмблере. А ещё она не поняла, што мы хотим именно полново охвата кода и 100%-ной надёжности распознавания.

Навигация
Главная страница сообщений
Предыдущая страница

Перейти к полной версии