| Разработка и ромхакинг > Программирование |
| [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%-ной надёжности распознавания. |
| Навигация |
| Главная страница сообщений |
| Предыдущая страница |