[Урок] Трансляция конструкций SourcePawn в байт-код

Kailo

Участник
Сообщения
168
Реакции
755
в процессе написания

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

Функция
Начнём с основы, во что же преобразуется обычная пустая функция
PHP:
void MyFunc()
{
}
Как я уже упоминал, у любой функции всегда будет вызываться return, даже если его нет или void.
PHP:
proc
break
zero.pri
retn
break появляется после proc т.к. идет блок кода (тело функции)
Когда-то ранее можно было делать функции без указания блока
PHP:
myFunc(); // старый синтаксис, и такой код не являлся ошибкой, просто пустая функция
У неё break будет отствутсовать
PHP:
proc
zero.pri
retn
Возврат значения (return)
Т.к. инструкция производит возврат значения из основного регистра, то сначала происходит получение значения и собственно сам возврат. При этом замечу, что при автоматическом добавлении инструкции с нулём, break не добавляется, т.к. он служит указателем новой строки для отладчика: нет строки - нет break. Но это помогает определить был ли return в оригинальной функции или нет.
PHP:
void MyFunc()
{
    return;
}
PHP:
proc
break
break // появилась эта инструкция
zero.pri
retn
В общем случае
PHP:
// [предыдущая инструкция]
break
// [установка значения для возврата]
retn
Ну и еще пример для закрепления пункта про функцию и возврат
PHP:
MyFunc() return 7; // старый синтаксис, функция без тела
PHP:
proc
break // он здесь как часть "return 7;", а не функции
const.pri 7
retn
и в тоже время
PHP:
int MyFunc()
{
    return 10;
}
PHP:
proc
break
break
const.pri 10
retn
Объявление переменной
Этот вопрос уже затрагивался в предыдущем уроке, поэтому только кратко вспомним.
Если у нас переменная - то образование переменной будет осуществятся инструкций push.c N. Где N значение присвоенное при объявлении или 0, если значения нет (т.к. по умолчанию переменным присваивается 0)
PHP:
int myvar;
PHP:
break
push.c 0
аналогичной байт-код даст и старый синтаксис с переменной любого типа
PHP:
new myvar;
// или
new bool:myvar;
// или
new Handle:myvar;
// и т.д.
Теперь о break и последовательном объявлении:
PHP:
int myvar1, myvar2;
PHP:
break
push.c 0 // myvar1
push.c 0 // myvar2
А если мы разделим строки, то появится break
PHP:
break
push.c 0
break
push.c 0
Думаю, к этому уроку, тех, кому это не интересно, тут не осталось, поэтому уверен, что вы будете изучать байт-код плагинов, включая плагины написанные на старом синтаксисе.
Напоминаю что "decl" приводит к игнорированию правила зануления, таким образом объявление переменной будет выглядеть как
PHP:
decl myvar1, myvar2;
decl myvar3;
PHP:
break
stack -4
stack -4
break
stack -4
Если в отрицательном stack сразу распознается переменная, то с push немного сложнее, т.к. это может быть и передача параметров. Но легко отличить тем что будет break до и после и не будет никаких вызовов.

Объявление массивов
Увидеть объявление массива довольно просто (кроме одного исключения, о чём позже)
Производится инструкцией stack и отрицательное число равное размеру массива в байтах.
PHP:
int array[4];
char str[30];
PHP:
break
stack -16
zero.pri
addr.alt -16
fill 16
break
stack -32
zero.pri
addr.alt -48
fill 32
Как видите, размеры строковых массивов выравниваются до кратного 4-ем.
(Связано с тем что процессоры заточены по работу с адресами памяти кратными 4-ем, иначе ему для получение значения придется совершать не одну операцию чтения/записи, а две и потом еще проводя бинарные преобразования, поэтому для скорости все выравнено по 4 байтной "сетки").
Ну и после создания, массивы так-же зануляются, используя fill.
decl с массивами убирает зануление.
PHP:
decl String:str1[10], String:str2[10];
PHP:
break
stack -12
stack -12
Теперь тех, кто читал прошу вспомнить, а тех кто не читал, прочитать: [Мини-урок] Структура массивов SourcePawn
Соответственно, переходя к многоразмерным массивам, не забываем о том что еще были добавлены адресные ячейки, т.е. размер следующего массива не будет равен 24.
PHP:
char strings[3][8];
PHP:
break
stack -36
zero.pri
addr.alt -36
fill 36
const.pri 2220
addr.alt -36
movs 12
При чтении кода в data было сформированы значения для адресных ячеек массива, как видим по адресу 2220, если туда посмотреть и прочитать 12 байт (как числа по 4 байта), то увидим: 0x0c 0x10 0x14 в десятичной 12 16 20 (если не понятно, почему именно эти значения, то еще раз прочитайте о структуре массивов).
Соответственно 24 байта занимают данные и еще 3 адреса по 4 байта: 24 + 3 * 4 = 36, вот и получаем наш размер в (stack -36).

Инициализация массивов
Аналогична установке вспомогательных адресов в многомерном массиве (для удобства совмещаются).
PHP:
char str[3][8] = {"hello", "fucking", "world"};
char str2[12] = "some random text";
float vec[3] = {255.0, -1.0, 345.0};
PHP:
break
stack -36
const.pri 2220
addr.alt -36
movs 36 // здесь и установка адресов и инициализация
break
stack -20
zero.pri
addr.alt -56
fill 20
const.pri 2256
addr.alt -56
movs 16
break
stack -12
const.pri 2272
addr.alt -68
movs 12
Теперь о исключении, что я ранее упоминал. Это глупая (т.к. не имеет толку) конструкция старого синтаксиса:
PHP:
decl array[1];
Да-да, не обнуленный и не инициализированный одноразмерный массив размером 1.
PHP:
break
stack -4
В байт-коде его объявление не отличимо от не обнуленной переменной.
Но об этом можно догадаться далее, т.к. инструкции обращающиеся к нему (массиву) будет отличатся от инструкций для переменных (и там будет одно исключение, но никогда в реальности не встречаемое).

Вызов нативных функций
Вызов таких функций довольно прост в байт-коде
PHP:
// [передача N аргументов]
sysreq.n Idx N
// [действия с возвратом или следующая инструкция]
В общем виде все легко, по индексу Idx в .native секции плагина будет найден адрес имени нативной функции, потом получено из раздела .strings и произойдет обращение к системе управления нативными функциями по вызову соответствующей функции.

Вызов функций
С обычными функциями тоже все довольно просто, разница лишь в том что кол-во аргументов передано подобно аргументу в стек.
PHP:
// [передача N аргументов]
push.c N
call ADR
// [действия с возвратом или следующая инструкция]
ADR - число, адрес начала функции в байт-коде.

Передача аргументов, переменные
Самая простая передача аргумента, когда наша функция принимает значение переменной.
Следующий текст, когда я его написал, показался мне слишком заумным и лишним: (Это значит, что должна быть создана переменная, которой есть доступ внутри вызываемой функции и содержать значение, которое мы хотим передать).
Функция, которую мы вызываем выглядит так:
PHP:
void MySuperFunc(int client, int value);
Мы должны передать два числа, надо рассмотреть общий принцип передачи и частные случаи.
В общем, мы должны "запихнуть" нужную нам информацию в стек в обратном порядке, т.е. сначала value, потом client.
Если то будут константы, то мы используем push для констант
PHP:
MySuperFunc(6, 100); // так выглядит вызов
PHP:
push.c 100
push.c 6
push.c 2
call MySuperFunc // тут конечно, вместо MySuperFunc её адрес
Но помним, что у нас есть, в целях оптимизации, инструкции на несколько аргументов сразу
PHP:
push3.c 100 6 2
call MySuperFunc
С константами разобрались, теперь локальные пременные и аргументы:
PHP:
public void MyPublic(int client)
{
    int newvalue = GetRandomInt(50, 100);
    MySuperFunc(client, newvalue);
}
PHP:
push2.s -4 12
push.c 2
call MySuperFunc
Если не понимаете или не помните почему -4 и 12, перечитайте предыдущей урок: [Урок] Виртуальная машина SourcePawn
Ситуация с комбинацией констант и локальных переменных (аргументов) думаю и так понятна.

Если же мы должны передать значение глобальной переменной, все так же просто, но используется push.
PHP:
MySuperFunc(MaxClients, 333);
PHP:
push.c 333
push MaxClients // MaxClients это число - адрес глобальной переменной в памяти плагина (а так же в секции data в частности).
push.c 2
call MySuperFunc
// к примеру это может выглядеть так
push.c 333
push 2216
push.c 2
call 3148
Если же, мы должны передать, как аргумент, результат каких либо действий: суммы, возврат другого вызова и т.д., то мы производим передачу в стек значения из соответствующего регистра. Пример:
PHP:
void MyFunc()
{
    int v = GetRandomInt(0, 10);
    MySuperFunc(MaxClients + v, GetRandomInt(50, 100));
}
PHP:
proc
break
break
stack -4
push2.c 10 0
sysreq.n 2 2
stor.s.pri -4
break
push2.c 100 50
sysreq.n 2 2
push.pri
load.s.pri -4
load.alt 2216
add
push.pri
push.c 2
call 3060
stack 4
zero.pri
retn
Заключение - в аргументе содержится число, если передается переменная, то копия её значения. Для передачи, значение передается в стек.

Передача аргументов, ссылки
Теперь разговор пойдет о функции вида:
PHP:
void AnotherMyFunc(int &result)
{
    result = GetRandomInt(0, 1);
}
А вызов этой функции будет
PHP:
int r = 3;
AnotherMyFunc(r);
PrintToServer("%i", r);
Как надеюсь все понимают, будет выведено 0 или 1, но ни как уж не 3, так как была передана ссылка на переменную r, и когда функция AnotherMyFunc работала, они изменила значение переменной r, а не аргумента. Надеюсь последние мои слова вас не запутали.
Теперь посмотрим на байт-код вызова:
PHP:
push.adr -4
push.c 1
call 3016
Как мы видим, новое значение (аргумента) было передано в стек, но это значение является адресом в памяти плагина, где расположена наша переменная.
Инструкции для работы с аргументами переданными по значению, и аргументами переданными по ссылки отличаются, об этом будет ниже.

SP позволяет так же передать константу в виде аргумента который передает ссылку в исключительных случаях.
Т.е. к примеру с выше упомянутой функцией AnotherMyFunc, так сделать нельзя:
PHP:
AnotherMyFunc(5);
Но в SM есть много функций с "опциональной ссылкой", т.е. аргумент, который является ссылкой является опциональным и имеет значение по умолчанию:
PHP:
int MyFuncToo(int &result = 0)
{
    return result = GetRandomInt(0, 1);
}
Для этого, до вызова функции будет создана временная переменная, heap, которая уже упоминались в прошлом уроке, значение константы присвоено переменной и передан её адрес в качестве значения аргумента. Если мы сделаем вызов вида:
PHP:
MyFuncToo();
То байт-код вызова будет выглядеть как
PHP:
zero.pri // значение которые мы должны присвоить переменной после создания
heap 4 // создаем переменную (4 байта), её адрес помещается в alt регистр
stor.i // присваиваем значение из pri регистр переменной чей адрес указан в alt
push.alt // передаем адрес переменной как аргумент вызова
push.c 1 // кол-во аргументов в вызове
call 2984 // вызов функции
heap -4 // дабы не допускать утечку памяти, уничтожаем временную переменную
Передача аргументов, массивы
Массивы. Передаются только по адресу, и никогда по значению.
Сначала важное уточнение по синтаксису. Вы могли видеть функции следующего вида, принимающие массивы:
PHP:
void Func(int[] a);
void Func(float a[3]);
void Func(const float a[3]);
void Func(const char[] a);
С точки зрения байт-кода, между ними НЕТ разницы!
const и [3] производят влияние лишь на этапе компиляции, в виде проверок, байт-код же это все опускает и видит функции как:
PHP:
void Func(int[] a);
void Func(float[] a);
void Func(float[] a);
void Func(char[] a);
Следующий абзац, "лирическое отступление", можете его пропустить.
Для чего же нужны const и [3] и как они работают:
const, как вы знаете, означает константу, т.е. неизменяемое. Следовательно, на этапе компиляции он начнет проверять все действия с переменной a, и если эти действия могут изменить значение переменной, то компилятор выдаст ошибку. Так же мы не сможем передать эту переменную как аргумент, если у аргумента вызываемой функции так же не будет присутствовать const, а значит уверенность в неизменности.
Теперь о [3] и подобном - это указания компилятору проверить, что переменная переданная в виде указного аргумента является массивом размером [3], и выдать ошибку если это не так.

Теперь рассмотрим передачу адресов массивов на примерах:
PHP:
int g_a[] = {5, 7, 3, 2, 9, 1};
public void OnPluginStart()
{
    static int s[] = {10, 20};
    int a[] = {6, 9, 4};
    int b = MyFuncSum(a, sizeof(a));
    int c = MyFuncSum(g_a, sizeof(g_a));
    int d = MyFuncSum(s, sizeof(s));
    PrintToServer("%i %i %i", b, c, d);
}
int MyFuncSum(int[] arraytosum, int size)
{
    ...
}
Байт-код OnPluginStart (число в начале строки - адрес инструкции):
PHP:
2920: proc
2924: break
2928: break // этот break тут из-за объявления статической переменной
2932: break
2936: stack -12 // объявление массива a
2944: const.pri 2252 // адрес в data, где лежат значения для инициализации {6, 9, 4}
2952: addr.alt -12
2960: movs 12 // инициализация массива a
2968: break
2972: stack -4 // объявление переменной b
2980: push.c 3 // передача константы 3 как 2го аргумента size функции MyFuncSum
2988: push.adr -12 // передача массива a как 1го аргумента size функции MyFuncSum
2996: push.c 2 // передача кол-ва аргументов
3004: call 3164
3012: stor.s.pri -16 // сохранение результата вызова в переменной b
3020: break
3024: stack -4 // объявление переменной c
3032: push3.c 6 2220 2 // 6 - как size, 2220 - адрес g_a как arraytosum, 2 - вол-во аргументов
3048: call 3164
3056: stor.s.pri -20 // сохранение результата вызова в переменной c
3064: break
3068: stack -4 // объявление переменной d
3076: push3.c 2 2244 2 // 2 - как size, 2244 - адрес s как arraytosum, 2 - вол-во аргументов
3092: call 3164
3100: stor.s.pri -24 // сохранение результата вызова в переменной d
3108: break
3112: push3.adr -24 -20 -16 // передача адресов переменных d, c, b соответственно
3128: push.c 2264 // передача адреса, где лежит строка (эквивалентно, если бы "%i %i %i" был бы глобальной переменной)
3136: sysreq.n 2 4 // Вызов PrintToServer с 4я аргументами
3148: stack 24 // освобождение памяти локальных переменных
3156: zero.pri
3160: retn
Для передачи массива, мы должны передать его адрес - для переменных с глобальной областью жизни - адрес уже известен и передается просто как константа, для локальных переменных используется инструкция push.adr, которая сначала получит из адреса, относительного к фрейму, адрес относительный к виртуальной памяти плагина и передаст его на вершину стека (как аргумент).
Если же мы и так знаем, что значение переменной является адресом другой переменной, мы передадим её значение, к примеру:
PHP:
public void OnPluginStart()
{
    int a[] = {6, 9, 4};
    int b = MyFunc(a, sizeof(a));
    PrintToServer("%i", b);
}

int MyFunc(int[] arraytosum, int size)
{
    return MyFuncSum(arraytosum, size);
}

int MyFuncSum(int[] arraytosum, int size)
{
    ...
}
Байт-код функции MyFunc
PHP:
3064: proc
3068: break
3072: break
3076: push2.s 16 12 // сначала передается значение size, затем значение arraytosum
3088: push.c 2
3096: call 3108
3104: retn
Возврат массива функцией
Редкий случай, мало кто так делает, но все же встретить возможно.
Речь идет о функциях вида:
PHP:
public void OnPluginStart()
{
    int sum = MyFuncSum(MyFunc(), 2);
    PrintToServer("%i", sum);
}
int[] MyFunc()
{
    int a[2] = {6, 3};
    return a;
}
int MyFuncSum(int[] arraytosum, int size)
{
    ...
}
Надеюсь все знали что так можно...
Байт код функций OnPluginStart и MyFunc:
PHP:
2920: proc // Начало OnPluginStart
2924: break
2928: break
2932: stack -4 // объявление переменной sum
2940: push.c 2 // начало вызова функции MyFuncSum, передача 2го аргумента (size)
// Теперь что бы получить значение 1го аргумента для вызова MyFuncSum надо вызвать MyFunc
2948: heap 8 // создается временная переменная размером эквивалентном массиву, что должен возвращаться функцией MyFunc
2956: push.alt // Передаем в стек как последний аргумент MyFunc. Т.к. у MyFunc 0 аргументов, то он единственный аргумент, первый и последний одновременно
2960: push.c 0 // кол-во аргументов в вызове, и да, оно равно оригинальному кол-ву аргументов в функции. Т.к. после конца вызова из стека аргументы удаляются,
// а нам надо что бы адрес временной переменной там остался, мы извлечем его сами позже
2968: call 3064 // вызов MyFunc
2976: pop.pri // извлекаем из стека адрес временной переменной в pri регистр
2980: push.pri // передаем его как 1-ый аргумент (arraytosum) вызова MuFuncSum
// (Выглядит с одной стороны глупо, сначала извлечь из стека, а потом туда же запихнуть, но этому есть объяснение: есть шаблон (блок байт-кодов) по вызову и получению массива,
// как возвращаемого значения, в результате которого вы гарантировано получим адрес переменной в pri регистре, и дальше может быть много разных действий, может мы захотим присвоить его
// другому массиву, передать как аргумент (как в примере) и т.д.)
2984: push.c 2 // кол-во аргументов в вызове MyFuncSum
2992: call 3148 // вызов MyFuncSum
3000: heap -8 // Как временная переменная нам более не нужна, уничтожаем её
3008: stor.s.pri -4 // сохраняем результат в переменную sum
3016: break
3020: push.adr -4
3028: push.c 2220
3036: sysreq.n 2 2
3048: stack 4
3056: zero.pri
3060: retn
3064: proc // Начало MuFunc
3068: break
3072: break
3076: stack -8 // объявление переменной a
3084: const.pri 2224
3092: addr.alt -8
3100: movs 8 // инициализация переменной a
3108: break
// далее происходит возврат значения
3112: addr.pri -8  // в pri регистр помещаем адрес массива a
3120: load.s.alt 12 // получаем значение 1го аргумента, т.к. мы знаем, что у функции 0 собственных аргументов
3128: movs 8 // производим копирование содержимого с адреса pri в адрес alt размером 8 байт
3136: stack 8 // перед концом функции уничтожаем локальные переменные
3144: retn
Т.е. схема в общем виде следующая:
Создается временный массив, адрес которого передается в функцию как последний аргумент, в которой присваивают возвращаемое значение.

Рабочая схема, но в ней есть исключение - переменной кол-во аргументов функции. Если раньше мы передавали адрес временного массива как последний аргумент, и мы знали конкретно каким он будет по счету, то теперь мы не можем этого знать. Как быть?
PHP:
public void OnPluginStart()
{
    PrintToServer("%s", MyFunc("%s %s %s?", "WTF", "I'm", "Doing"));
}

char[] MyFunc(const char[] format, any ...)
{
    char buffer[128];
    VFormat(buffer, sizeof(buffer), format, 2);
    return buffer;
}
(Мда, такой код только в страшном сне присниться), но его байт-код:
PHP:
2920: proc // Начало OnPluginStart
2924: break
2928: break
2932: heap 128 // временный массив
2940: push.alt // сначала передается адрес временной переменной как последний аргумент
2944: push5.c 2244 2240 2236 2224 4 // адреса массивов со строками и кол-во аргументов - 4
2968: call 3020 // вызов MyFunc
2976: pop.pri // получение адреса временной перменной
2980: push.pri // передача его как 2го аргумента в PrintToServer
2984: push.c 2220 // Передача строки в PrintToServer
2992: sysreq.n 2 2 // вызов PrintToServer с 2я аргументами
3004: heap -128
3012: zero.pri
3016: retn
3020: proc // Начало MyFunc
3024: break
3028: break
3032: stack -128 // объявление buffer
3040: zero.pri
3044: addr.alt -128
3052: fill 128 // заполнение buffer нулями
3060: break
// начинается вызов VFormat
3064: push.c 2
3072: push.s 12
3080: push.c 128
3088: push.adr -128
3096: sysreq.n 3 4 // вызов VFormat
3108: break
// начинается return
3112: addr.pri -128 // в pri заносим адрес buffer
3120: push.pri // передаем адрес buffer в стек для временного сохранения (подробнее в разделе "Временное сохранение в стек")
3124: addr.alt 12 // получаем адрес 1-го аргумента функции
3132: load.s.pri 8 // получаем число аргументов в вызове
3140: idxaddr // прибавляем к адресу переменной суммарный размер аргументов в байтах и получим адрес, указывающий на область, после последнего аргумента,
// там как раз и есть наш переданный адрес временного массива
3144: load.i // получаем адрес временного массива
3148: move.alt // перемещаем его в alt
3152: pop.pri // возвращаем из стека адрес buffer в pri
3156: movs 128 переносим значение buffer во временный массив
3164: stack 128
3172: retn
Надеюсь комментариев в байт-коде достаточно для понимания. Так же еще раз напомню: смещение 1-го аргумента функции относительно её фрейма - 12 байт (3 переменных: 0, адрес фрейма вызвавшей функции, кол-во аргументов в вызове, об этом говорилось весь прошлый урок). Так что load.s.pri 8 получает число идущие перед 1-ым аргументом - кол-во аргументов.

Присвоение локальных переменных
Присвоение значение локальной или глобальной переменной довольно простое.
Сначала поговорим о локальных.
Если мы хотим присвоить переменной константное значение
PHP:
int a = 5;
a = 3;
PHP:
break
push.c 5
break
const.s -4 3
Если это результат какого либо мат. алгоритма или вызова другой функции:
PHP:
    int a = 5;
    a = a * 2;
    a = GetRandomInt(0, 10);
PHP:
break
push.c 5
break
load.s.pri -4
smul.c 2
stor.s.pri -4
break
push2.c 10 0
sysreq.n 2 2
stor.s.pri -4
Если эта переменная аргумент, все будет аналогично, только адрес смещения от фрейма будет не отрицательным, вида -4, а 12, 16 и тому подобное.
Аналогично будет, если результат действия окажется в alt регистре, будет использовано stor.s.alt.
Иногда можно будет встретить инструкции sref.s.pri и sref.s.alt, это так же присвоение, аргументу ссылке:
PHP:
void MyFunc(int &a, Handle &b)
{
    a = GetRandomInt(5, 6);
    b = null;
}
PHP:
proc
break
break
push2.c 6 5
sysreq.n 3 2
sref.s.pri 12
break
zero.pri
sref.s.pri 16
zero.pri
retn
Присвоение глобальных переменных
С глобальными переменными аналогично, есть const, есть stor, в зависимости от того константа ли это или значение а регистре.
PHP:
int g_a;
Handle g_h;
public void OnPluginStart()
{
    g_a = 66;
    g_h = CreateArray(g_a);
}
PHP:
proc
break
break
const 2220 66
break
push.c 0
push 2220
sysreq.n 2 2
stor.pri 2224
zero.pri
retn
Получение низшей размерности массивов
TODO

Присвоение массивов
TODO

Динамические массивы
TODO

Математические/логические операции
TODO

Инкремент/декремент
TODO

Временное сохранение в стек
В результате многих операций может потребоваться временно сохранить значение. Для его сохранения используется стек.
Подобное уже было в возврате массива функцией, с переменным кол-вом аргументов.
Еще к примеру, классическое деление с вырождением в числителе и знаменателе:
PHP:
int c = (a + b) / (a - b);
С чего бы мы не начинали вычисление (числитель или знаменатель), все равно придется на время сохранить значение с помощью push, а позднее извлечь с помощью pop.
PHP:
// a и b были объявлены ранее и имеют адреса -4 и -8 соответственно
stack -4 // объявляем c
load.s.both -8 -4 // получаем значения a и b (b в pri, a в alt)
add // суммируем alt и pri, результат суммы тепереь в pri
push.pri // сохраняем временно значение из pri в стек
load.s.both -8 -4 // еще раз получаем значения a и b (b в pri, a в alt)
sub.alt // вычитаем (pri = alt - pri)
pop.alt // возвращаем сохраненное значение числителя из стека в alt
sdiv.alt // делим (pri = alt / pri)
stor.s.pri -12 // сохраняем полученный результат в переменную c
Логическое условие (if)
Классический if преобразуется в условный переход совершаемый инструкцией jzer (если pri равен 0, то перейти по указанному адресу)
PHP:
public void OnPlayerKill(int attacker, int victim)
{
    if (IsPlayerAlive(attacker))
    {
        GiveReward(attacker); // GiveReward просто какая-то функция, которая вознаграждает игрок за убийство
    }
}
PHP:
3192: proc
3196: break
3200: break
3204: push.s 12
3212: sysreq.n 6 1 // вызов IsPlayerAlive, результат вызова в pri
3224: jzer 3260 // если pri == 0, то перейти по адресу 3260
3232: break
3236: push.s 12
3244: push.c 1
3252: call 3268
3260: zero.pri // сюда будет выполнен условный переход
3264: retn
Так же есть оптимизации распространенных случаев:
PHP:
public void OnPlayerKill(int attacker, int victim)
{
    if (attacker != 0)
        GiveReward(attacker);
    if (attacker == 0)
        GiveReward(attacker);
    if (!attacker)
        GiveReward(attacker);
    if (attacker > 0)
        GiveReward(attacker);
    if (attacker >= 0)
        GiveReward(attacker);
    if (attacker < 0)
        GiveReward(attacker);
    if (attacker <= 0)
        GiveReward(attacker);
    if (attacker == victim)
        GiveReward(attacker);
    if (attacker != victim)
        GiveReward(attacker);
    if (attacker > victim)
        GiveReward(attacker);
    if (attacker >= victim)
        GiveReward(attacker);
    if (attacker < victim)
        GiveReward(attacker);
    if (attacker <= victim)
        GiveReward(attacker);
}
PHP:
3192: proc
3196: break
3200: break
// if (attacker != 0)
3204: load.s.pri 12
3212: jzer 3248
3220: break
3224: push.s 12
3232: push.c 1
3240: call 3892
3248: break // 3212
// if (attacker == 0)
3252: load.s.pri 12
3260: jnz 3296
3268: break
3272: push.s 12
3280: push.c 1
3288: call 3892
3296: break // 3260
// if (!attacker)
3300: load.s.pri 12
3308: not
3312: jzer 3348
3320: break
3324: push.s 12
3332: push.c 1
3340: call 3892
3348: break // 3312
// if (attacker > 0)
3352: load.s.pri 12
3360: move.alt
3364: zero.pri
3368: jsgeq 3404
3376: break
3380: push.s 12
3388: push.c 1
3396: call 3892
3404: break // 3368
// if (attacker >= 0)
3408: load.s.pri 12
3416: move.alt
3420: zero.pri
3424: jsgrtr 3460
3432: break
3436: push.s 12
3444: push.c 1
3452: call 3892
3460: break // 3424
// if (attacker < 0)
3464: load.s.pri 12
3472: move.alt
3476: zero.pri
3480: jsleq 3516
3488: break
3492: push.s 12
3500: push.c 1
3508: call 3892
3516: break // 3480
// if (attacker <= 0)
3520: load.s.pri 12
3528: move.alt
3532: zero.pri
3536: jsless 3572
3544: break
3548: push.s 12
3556: push.c 1
3564: call 3892
3572: break // 3536
// if (attacker == victim)
3576: load.s.both 16 12
3588: jneq 3624
3596: break
3600: push.s 12
3608: push.c 1
3616: call 3892
3624: break // 3588
// if (attacker != victim)
3628: load.s.both 16 12
3640: jeq 3676
3648: break
3652: push.s 12
3660: push.c 1
3668: call 3892
3676: break // 3640
// if (attacker > victim)
3680: load.s.both 12 16
3692: jsleq 3728
3700: break
3704: push.s 12
3712: push.c 1
3720: call 3892
3728: break // 3692
// if (attacker >= victim)
3732: load.s.both 12 16
3744: jsless 3780
3752: break
3756: push.s 12
3764: push.c 1
3772: call 3892
3780: break // 3744
// if (attacker < victim)
3784: load.s.both 12 16
3796: jsgeq 3832
3804: break
3808: push.s 12
3816: push.c 1
3824: call 3892
3832: break // 3796
// if (attacker <= victim)
3836: load.s.both 12 16
3848: jsgrtr 3884
3856: break
3860: push.s 12
3868: push.c 1
3876: call 3892
3884: zero.pri // 3848
Цикл while
TODO
(continue/break)

Цикл for
TODO
(continue/break)

Конструкция switch
TODO

Конструкция delete
Эта конструкция была добавлена вместе с "methodmap" конструкциями. И должна была обозначать удаление объекта как в более развитых языках как C++. Т.е. вызывать деструктор объекта. В настоящее время создание пользовательских дестукторов запрещено и существует только единственный деструктор для Handle и его производных типов.
т.е. Теоретически, должно быть
PHP:
delete hndl;
// эквивалентно
hndl.~Handle(), hndl = null;
Но вот метод ~Handle не может быть вызван из-за того, что парсер не может распознать имя метода начинающегося с тильды (~). Так что мы используем эквивалентный ~Handle - Close. Важно понимать, что методы ~Handle и Close преобразуются автоматически в вызов CloseHandle.
PHP:
public void OnPluginStart()
{
    DataPack hndl;
    delete hndl;
    hndl.Close(), hndl = null;
}
PHP:
2920: proc
2924: break
2928: break
2932: push.c 0
2940: break
2944: load.s.pri -4
2952: push.pri
2956: sysreq.n 2 1
2968: zero.s -4
2976: break
2980: push.s -4
2988: sysreq.n 2 1
3000: zero.s -4
3008: stack 4
3016: zero.pri
3020: retn
Статические переменные
Объявление статической переменной оставляет в байт-коде след в виде break инструкции.
Сама переменная вдет себя полностью подобно глобальной переменной.
Т.е. при декомпиляции отличить от глобальной и распознать статическую переменную можно только записью в debug секции о её статическом виде. Если же debug отсутствует, переменная неотличима от глобальной. Единственный способ понять, что переменная была ранее статической, её применение только внутри одной функции, адрес переменной отделенный от основной части глобальных переменных и наличие аномального break на месте её объявления.
PHP:
char g_BUG[] = "BUG!";

public bool Anything(const char[] str)
{
    bool r = false;
    static char s_bug[] = "Some bug here!";

    if (StrEqual(s_bug, str, true))
        r = true;
    if (StrEqual(g_BUG, str, true))
        r = true;

    return r;
}
PHP:
2972: proc
2976: break
2980: break
2984: push.c 0
2992: break // Этот break, след от объявления статической перменной
2996: break
3000: push.c 1
3008: push.s 12
3016: push2.c 2228 3 // 2228 это адрес s_bug
3028: call 2920
3036: jzer 3060
3044: break
3048: const.s -4 1
3060: break // 3036
3064: push.c 1
3072: push.s 12
3080: push2.c 2220 3 // 2220 это адрес g_BUG
3092: call 2920
3100: jzer 3124
3108: break
3112: const.s -4 1
3124: break // 3100
3128: load.s.pri -4
3136: stack 4
3144: retn
Methodmaps
TODO
(property set)
 
Последнее редактирование:
Сверху