Я в последнее время экспериментировал с one-shot-декомпиляцией, используя режим Claude без пользовательского интерфейса в непрерывном цикле. Меня на это вдохновила статья о запуске Claude Code в цикле. Эксперимент оказался на удивление продуктивным.

Я занимаюсь декомпиляцией кода игры Snowboard Kids 2. За три недели, прошедших с того момента, как я начал применять описываемый здесь подход, я сделал больше, чем за предыдущие три месяца. Сейчас за ходом работ можно следить на decomp.dev, а до этого сведения о том, что сделано, я вносил прямо в README.md.

В применении к моему проекту смысл понятия «one-shot-декомпиляция» заключается в том, что Claude передаётся промпт, а он, без итеративного уточнения запроса, делает своё дело и завершает работу. А именно, ему передают функцию; он пытается найти её реализацию, после чего этот процесс продолжается. То, что ИИ-агент работает самостоятельно, без обратной связи, которую даёт ему пользователь, позволяет очень сильно повысить пропускную способность системы. Например, я оставил Claude без присмотра более чем на 8 часов, а он всё это время спокойно обрабатывал функции, пытаясь выйти на их исходный код. Правда, такой подход сопряжён с определённым риском. Все видели, как LLM ни с того ни с сего съезжают с катушек. Когда ИИ работает сам, когда человек не может, в случае чего, его поправить, можно, дав ему задание и через несколько часов проверив результаты, выяснить, что квота Claude исчерпана, а результатов — кот наплакал. Но, если правильно настроить вспомогательные механизмы, можно взять этот риск под контроль.
Цель этого материала в том, чтобы задокументировать особенности сложившегося у меня подхода к ИИ-декомпиляции кода. Здесь же я хочу поделиться некоторыми находками, которые, возможно, пригодятся тем, кто занимается похожими проектами.
Организация декомпиляции кода с использованием ИИ-инструментов
Пользователь системы просто запускает такую команду:
. ./tools/vacuum.shПосле этого всем остальным занимается этот скрипт (он изначально получил имя «vacuum», так как предполагалось, что его назначение заключается в том, чтобы он, как пылесос, вытягивал бы из того, что ему передают, простые функции). Он перебирает функции, которые, как ожидается, можно декомпилировать, работая до тех пор, пока ему нечего будет обрабатывать. Он либо находит реализацию функции, декомпилируя её, либо помечает функцию как слишком сложную.

Система состоит из четырёх основных компонентов:
Score оценивает возможность декомпиляции функции. Он выбирает следующую функцию для обработки, отдавая приоритет тем, которые, скорее всего, удастся декомпилировать.
Claude выполняет декомпиляцию, используя предоставленные ему инструменты.
Decompilation Tools — это те инструменты, которые дают Claude то, что ему нужно для декомпиляции функций.
Всем этим управляет компонент Driver, объединяющий то, что показано на схеме. Он вызывает Claude, обрабатывает ошибки, логирует сведения о ходе выполнения задач.
В следующих разделах мы подробно рассмотрим каждый из этих компонентов.
Компонент Score
Цель компонента Score заключается в том, чтобы подбирать очередную функцию, самую простую из имеющихся, и передавать её на декомпиляцию Claude. В этом деле Claude уступает человеку, поэтому эффективнее будет тратить время и энергию на те задачи, в решении которых, скорее всего, удастся достичь существенного прогресса. Такой подход, кроме того, помогает заложить основу для декомпиляции более сложных функций, которые обычно обращаются к тем функциям, которые попроще, вызывая их. Понимание подобных зависимостей упрощает работу с более крупными фрагментами программ. Эту идею подкрепляют реальные доказательства, хотя всё устроено не так однозначно, как я изначально предполагал. А именно, 62,7% вызовов идут от более сложных функций к функциям менее сложным.
Поначалу я, назначая функция�� оценки, использовал следующую формулу, которую я вывел, основываясь лишь на собственном понимании происходящего:
score = instruction_count + 3 * branch_count + 2 * jump_count + 2 * label_count + stack_sizeИдея, лежащая в основе этой формулы заключается в том, что сложность функции, а значит и трудоёмкость её декомпиляции во многом определяются количеством инструкций (instruction_count в формуле). Конструкции управления потоком выполнения кода (количество ветвлений — branch_count и метки — label_count), вероятно, повысят сложность функции. То же самое можно сказать и о вызовах других функций (jump_count). Управление большим стеком — это тоже задача, которая может быть достаточно сложной, поэтому в формуле, для полноты картины, используется и размер стека (stack_size).
Такой подход к оцениванию функций поначалу показывал себя достаточно хорошо. А после того, как я залогировал сведения о нескольких сотнях функций, которые удалось и не удалось декомпилировать, я переключился на модель логистической регрессии, с помощью которой настраивал исходные веса системы, используемой для оценки функций.

В результате модель указала на то, что размер стека почти не обладал предсказательной ценностью, что этот показатель, похоже, способствовал переобучению модели. Я его, в итоге, убрал. Оставшиеся признаки оказались более практичными, но мои исходные предположения по весам были совершенно неверными.
Я периодически, по мере того, как в моём распоряжении оказывается больше данных, переобучаю модель. Это ведёт к небольшим улучшениям в её точности.
Компонент Claude
Claude — это «мозг» всей системы, выполняющий декомпиляцию функций. Большинство успешных попыток декомпиляции сделано с использованием модели Opus 4.5. У меня недостаточно данных для сравнения моделей Opus и Sonnet. Правда, я провёл небольшой эксперимент. В нём модель Opus смогла декомпилировать пять из семи функций, которые модель Sonnet 4.5 сочла слишком сложными.
Полный текст используемого мной промпта размещён в репозитории, но основные инструкции, входящие в его состав, выглядят достаточно просто и понятно:
Создать окружение для декомпиляции функции X.
Следовать инструкциям в этом окружении и использовать предоставленные инструменты для декомпиляции функции.
Прекратить работу, если функция слишком сложна. Если после более чем десяти попыток нет никакого продвижения в решении задачи — агенту нужно переходить к следующей задаче.
Если функцию удалось декомпилировать — её нужно интегрировать в проект, проверить сборку и закоммитить код. Фиксация изменения в системе контроля версий — это очень важный шаг. Он позволяет сохранить наработанные результаты даже в том случае, если Claude «поломает» локальное окружение.
Если декомпилировать функцию не удалось — сделать запись в журнал difficult_functions и завершить работу.
Порог прекращения работы после десяти неудачных попыток нужен для того, чтобы не дать Claude впустую тратить токены в ситуациях, когда дальнейший прогресс маловероятен. Правда, это указание оказалось не слишком действенным, так как Claude, всё равно, иногда делает дюжины попыток декомпиляции сложных функций.
Компонент Decompilation Tools
Мой подход к подбору инструментов, в целом, не поменялся со времён публикации предыдущего материала: применять простые Unix-подобные программы, которые Claude может комбинировать для решения задач. Я не добавляю в список инструментов каких-либо MCP-серверов (Model Context Protocol, протокол контекста модели). Учитывая сказанное, надо отметить, что расширение автономности Claude выявило необходимость применения защитного программирования. Это — чёткие сообщения об ошибках, корректная обработка сбоев, защита от неправильного использования инструментов. Неоднозначное сообщение об ошибке, полученное от инструмента, может загнать Claude в «кроличью нору», впустую растрачивая время и токены.
Например, Claude предписано использование единого скрипта для сборки и проверки проекта: build-and-verify.sh. Для уменьшения склонности Claude к неправильной интерпретации результатов, скрипт даёт точные инструкции относительно обработки успешных и неудачных результатов:
BUILD HAS FAILED. Claude, you should treat this as a build failure. Adding new warnings or accepting a non-matching checksum count as failures.Аналогично — иногда Claude теряется, переходя между окружением, где выполняется декомпиляция, и главным проектом. Бороться с этим можно с помощью универсального (%: ) правила сборки в проблемных директориях, в котором просто говорится следующее:
You are in a matching environment for a specific function. Only use the tools explicitly listed in this directory’s CLAUDE.md. If you’re ready to build against the main project, you need to jump back two directory levels (cd ../..)Такая вот защитная стратегия оказалась эффективнее промпт-инжиниринга при борьбе с типичными проблемами Claude.
Ещё одна важная характеристика системы — эффективность использования токенов. Она стала ещё важнее после того, как Claude начал подолгу работать самостоятельно. Именно это стало причиной того, что vacuum.sh сначала вызывает компонент Scorer, а уже потом передаёт Claude самую «дешёвую» функцию. Я не позволяю Claude выбирать функции самостоятельно. А почему я не отредактировал, с учётом этого, промпт? Из-за того, что это запутывает Claude и он перестаёт коммитить изменения. Большие языковые модели — странные создания. Тут требуется больше экспериментов.
В деле экономии токенов может помочь и соответствующая подстройка инструментов. Например, в попытке снизить потребление токенов, скрипт build-and-verify.sh значительно ограничивает объём выходных данных сборки.
Компонент Driver
Внешний цикл — это просто bash-скрипт, который многократно вызывает Claude. Ему, если нужно, можно передать максимальное количество итераций. Вот как он работает:
Вызывает Claude, передавая ему следующую функцию для декомпиляции.
Если Claude вернул ненулевое значение, повторяет попытки с пятиминутным интервалом — на тот случай, если был достигнут лимит использования службы.
Перехватывает комбинацию клавиш
Ctrl-C. Это позволяет пользователю сообщить скрипту о том, что нужно завершить работу, но при этом не прерывать сеанс работы Claude и не терять результаты текущей попытки декомпиляции функции.Выводит имя функции и сведения о времени в
stdout, что позволяет пользователю быть в курсе того, чем занят скрипт.Помещает все выходные данные Claude в файл. Это — бесценный источник информации для разбора неудачных попыток декомпиляции. Анализируя этот файл, можно разобраться с тем, что именно вызвало сложности у Claude.
Сравнение Claude с другими ИИ-агентами
Я немного поэкспериментировал с агентом Codex, который, когда я только начал этим всем заниматься, привлёк к себе много внимания. Результаты меня разочаровали. У Codex (включая модель 5.1-codex-max) наблюдались трудности и с эффективной декомпиляцией, и, в целом, с выполнением инструкций.
Больше всего неприятностей при испытании Codex вызвали неувязки, связанные Git. Это, видимо, широко распространённая проблема, хотя обновление в моём случае её не решило. Плохая стратегия декомпиляции кода в сочетании с ненадёжными механизмами использования Git — это п��екрасный способ испортить себе жизнь.
Я пока не тестировал Gemini или другие подобные системы.
Итоги
Сложилось так, что проекты, направленные на декомпиляцию неких систем, часто собирали вокруг себя целые команды энтузиастов и длились многие годы. Скорость реализации этих проектов зависела от экспертов — малочисленной группы очень занятых людей. ИИ-агенты, которые умеют программировать, ослабляют это ограничение. Текущие данные по Snowboard Kids 2 указывают на то, что подавляющее большинство функций вполне доступно для декомпиляции с помощью Claude Opus 4.5. Если наблюдаемая тенденция сохранится — декомпилировать, вероятно, удастся примерно 79% функций. А в будущем факторами, ограничивающими проекты по декомпиляции кода, вероятно, станут вычислительная мощность и доступ к передовым моделям, а не внимание людей.

При этом, если говорить о моём проекте, я не могу не отметить неоценимые заслуги тех людей, которые формируют сообщество любителей декомпиляции кода. Без их поддержки в Discord, без их терпения, моего проекта просто бы не существовало. Не было бы его и без таких инструментов, как Splat, m2c, decomp-permuter, asm-differ и многих других. Мы, как говорится, стоим на плечах гигантов. Правда, хотя роли могут измениться, я не думаю, что люди-эксперты в обозримом будущем станут никому не нужными.
Оставшиеся функции Snowboard Kids 2 — это почти наверняка те, которые будет сложнее всего декомпилировать (если не обращать внимания на некоторые странности Claude). Даже когда LLM удаётся декомпилировать код, результат часто выглядит довольно-таки неряшливым: адресная арифметика с указателями вместо операций по доступу к элементам массивов, управление потоком выполнения программы, основанное на командах goto, несуразные временные переменные и другие заморочки, ухудшающие понятность кода. Если цель исследователя заключается в том, чтобы понять то, как работают игры (или в том, чтобы их модифицировать) — точные, но неказистые реализации программ, найденные искусственным интеллектом, дадут ему не так уж и много в сравнении с исходным ассемблерным кодом. Возникает такое ощущение, что в дальнейшем те, кто занимаются декомпиляцией, будут больше ориентироваться на очистку и документирование того, что выдают LLM, а не на написание кода с нуля. Реализации функций, найденные с помощью ИИ, будут использоваться в качестве базы для дальнейшей работы, что очень похоже на то, как были устроены ранние проекты, основанные на том, что выдаёт m2c.
Если вы дочитали до этого места — вы, вероятно, испытываете интерес к декомпиляции кода и к игре Snowboard Kids. Попытайте счастья. Взгляните на список сложных функций в репозитории snowboardkids2-decomp и проверьте — сможете ли вы превзойти LLM в деле декомпиляции этих функций!
О, а приходите к нам работать? 🤗 💰
Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.
Мы предлагаем интересные и сложные задачи по анализу данных и low latency разработке для увлеченных исследователей и программистов. Гибкий график и никакой бюрократии, решения быстро принимаются и воплощаются в жизнь.
Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.
