RS Game Maker Community

Портал => Песочница (статьи) => Тема начата: input.txt от 18 Июня 2017 в 15:10



Название: Декомпилятор для GameMaker:Studio
Отправлено: input.txt от 18 Июня 2017 в 15:10
В старые времена GML был интерпретируемым языком, и код хранился в исполняемом файле чуть ли не с комментариями, так что "декомпилятор" было написать крайне легко. С выходом GM:S хорошие времена для мамкиных хакеров закончились – теперь YoYoGames встроили в исполняемый файл виртуальную машину собственного изобретения и GML компилируется в байт-код для неё. При этом формат байт-кода и файла периодически заботливо меняют.
Насколько мне известно, на данный момент для GM:S не существует инструментов для декомпиляции. Есть конечно распаковщики ресурсов (типа Altar.NET (https://gitlab.com/PoroCYon/Altar.NET)), но они не производят никакого анализа и уж точно не генерируют код на GML. Таким образом, мой проект предназначен для того, чтобы закрыть эту нишу.

Итак, я сотворил прототип декомпилятора на С++. Все желающие могут скачать его с репозитория (https://bitbucket.org/klm-1/gms_dc) и оценить, как он не работает. Для удобства я также подготовил архив с тестовым проектом (https://bitbucket.org/klm-1/gms_dc/downloads/Build.zip), на котором декомпилятор точно не падает.

Принцип работы
В основе лежит алгоритм структурного анализа. Программа разбивается на базовые блоки – участки кода, которые не содержат инструкций перехода. Из них потом строится граф потока управления (flowgraph), где узлы – это собственно базовые блоки (ББ), а дуги – переходы между ними.

[attachment=1]
Рис. 1. Граф потока управления.

Далее алгоритм просматривает поочередно все узлы, и если данный узел является точкой входа в один из шаблонов, то весь этот участок заменяется на абстрактный узел. Процесс заканчивается, когда на очередном проходе не удалось найти ни одного совпадения.
(При этом, если граф сводимый, то в нем останется только одна вершина).

[attachment=2]
Рис. 2. Шаблоны конструкций.

[attachment=3]
Рис. 3. Пример свертки.

Последний оставшийся узел содержит в себе дерево управления, оно же (в данной реализации) синтаксическое дерево. И на основе его уже генерируется код на GML.

[attachment=4]
Рис. 4. Дерево управления.

Основные проблемы
  • Невозможно создать шаблон для циклов, содержащих break и continue. Так что для их распознавания нужен другой метод.
  • Отсутствие какого-либо анализа потока данных не позволяет нормально обрабатывать инструкцию dup, так как в этом случае на стеке оказывается две версии одной переменной, и все ломается.

Текущие возможности
Сейчас декомпилятор может восстанавливать следующие конструкции:
  • if, if-else
  • while, for, repeat-until do-until (for превращается в эквивалентный while)
  • Арифметические выражения любой сложности, с операторами разного приоритета и вложенными скобками. Иногда код после декомпиляции выглядит лучше исходного
  • Аналогично, логические выражения (отдельный пункт, т.к. они представлены совершенно другим образом)
  • Вызовы функций (в смысле скриптов) с любым количеством аргументов.
  • +=, -=, *=, /=, …
  • Массивы, в том числе двумерные.
  • Обращение к полям объектов.
Все перечисленное может иметь любую сложность и глубину вложенности:
a.b.c[d.e || true, f.g.h && s[t, u && v]] = s || x && --lol;

Работа над проектом будет продолжаться, пока GMS2 не выйдет из беты. Так что даже есть шансы, что он будет закончен.



Название: Re: Декомпилятор для GameMaker:Studio
Отправлено: tolich от 19 Июня 2017 в 10:05
Что за repeat-until? Испокон веку было do-until.


Название: Re: Декомпилятор для GameMaker:Studio
Отправлено: input.txt от 19 Июня 2017 в 12:51
Do, конечно. Это я, видимо, с Lua его перепутал.
В любом случае, сейчас перепроверил - он тоже нормально восстанавливается.


Название: Re: Декомпилятор для GameMaker:Studio
Отправлено: Огион от 20 Июня 2017 в 12:33
Круто.
Есть какая-нибудь интересная инфа о внутренней работе GM, которая может быть полезна простому разработчику?


Название: Re: Декомпилятор для GameMaker:Studio
Отправлено: input.txt от 20 Июня 2017 в 23:04
Подборка интересных фактов:
  • Аксессоры – это на самом деле функции, и их можно даже вызвать напрямую.
    a = list[| i ];<=>   ds_list_find_value( list, i );
    list[| i ] = a;<=>   ds_list_set( list, i, a );
    a = map[? key ];<=>   ds_map_find_value( map, key );
    map[? key ] = a;<=>   ds_map_set( map, key, a );
    a = arr[@ i ];<=>   array_get( arr, i );
    arr[@ i ] = a;<=>   array_set( arr, i, a );
    a = grid[# i, j ];<=>   ds_grid_get( grid, i, j );
    grid[# i, j ] = a;<=>   ds_grid_set( grid, i, j, a );

    Это может упростить работу со вложенными массивами
    inner[5] = "Hello";
    arr[1] = inner;

    // show_message( arr[1][5] );   // ERROR
    show_message( array_get( arr[1], 5 ) );   // "Hello"

    Так же, как и функции, не проверяют тип аргумента. Хотя его вообще нельзя проверить, потому что id структуры это просто число, так еще и для каждого типа отдельная нумерация.
    var map = ds_map_create();
    map[? 13 ] = "map";

    var list = ds_list_create();
    list[| 13 ] = "list";

    show_message( map[? 13 ] );  // "map"
    show_message( list[| 13 ] ); // "list"
    show_message( map[| 13 ] );  // "list" !!!
    show_message( list[? 13 ] ); // "map"  !!!
  • Двумерные массивы можно использовать как одномерные с размером до 32000*32000 = 1024000000.
    i = 5;
    j = 2;
    a[i, j] = 42;
    // a[i, j] == a[i*32000 + j] == a[5*32000 + 2] == a[160002] == 42;
    for( var i = 0; i < 1000000; ++i ) {
        a[i] = i + 1;
    }
    show_message( a[65535] ); // 65536