- 531
- 263
LJEdit – это библиотека для языка программирования Lua, написанная на Lua, которая призвана помочь с чтением и редактированием байт-кода LuaJIT. Она не требует LuaJIT для работы, но его использование рекомендуется, особенно при работе с большими скриптами. Работает с байт-кодом LuaJIT версии 2.0 и 2.1. Работа с модификациями LuaJIT, которые затрагивают байт-код, не гарантируется.
1. Использование:
Чтобы начать её использование, необходимо её загрузить:
Возвращённое значение будет представлять собой таблицу, в которой будут находиться следующие функции:
В объектах, указанных ранее, используются массивы, которые могут начинаться либо с 1 (array1), либо с 0 (array0), содержащие два метода: "insert" и "remove". Для каждого из двух видов массивов используются свои методы, которые учитывают их особенности.
Объект скрипта имеет следующую структуру:
И три метода. Для примера предположим, что переменная "script" содержит объект скрипта, в таком случае:
script:proper() - изменяет структуру объекта таким образом, что она становится сходна со структурой прототипов в памяти LuaJIT, перемещая прототипы из массива прототипов в константы других прототипов, заменяя нули, служащие заглушкой, до тех пор, пока не останется только один – главный прототип. Может потребовать значительное количество времени.
script:literal() - раскладывает прототипы в один линейный массив. Автоматически вызывается компилятором, так как он умеет работать только с линейной структурой, что в разы медленнее, но заметно только при работе с большими скриптами.
script:compile() - компилирует объект скрипта обратно в строку, содержащую байт-код, и возвращает её. Сам объект не должен изменяться.
Предупреждение: объект скрипта может жрать в 17 раз больше оперативной памяти, чем обычная строка, содержащая тот же байт-код, что нехило, однако, и виноваты в этом, без всякого сомнения, Lua-таблицы, потому что они пиздец жирные. Фиг знает, почему так происходит.
Каждый объект прототипа имеет следующую структуру:
Мета-таблицу, содержащую поле "__proto", которая равна "true" и нужна для определения прототипа среди прочих таблиц и объектов; и метод "get_varname(pt, pc, slot)", который получает название локальной переменной прототипа по позиции инструкции, которая работает с ней, и номеру слота.
Каждый объект инструкции на деле представляет собой таблицу, содержащую инструкцию в виде целого числа, чтобы уменьшить потребление памяти, а также мета-таблицу, которая позволяет индексировать её так, как если бы она представляла собой обычную таблицу с полями. Структура объекта инструкции выглядит следующим образом:
Также каждая инструкция имеет метод "info", который представляет собой вышеупомянутую "ljedit.get_instruction_info". Поле li может содержать номер строки в исходном коде скрипта, которая представляется инструкцией, и заполняется автоматически, если скрипт содержит дебаг-информацию. Оно игнорируется, если скрипт не должен содержать дебаг-информации.
Каждый объект верхней переменной содержит два поля: "id" и "name", где второе является необязательным и заполняется автоматически, если скрипт содержит дебаг-информацию. Поле "name" игнорируется, если скрипт не должен содержать дебаг-информацию.
Kgc-объекты бывают разными: чистыми строками, таблицами (они, почти как обычные Lua-таблицы, но имеют мета-таблицу с полем "__tab", чтобы его можно было отличить от других объектов), прототипами или нулями, в зависимости от структуры объекта скрипта, знаковыми 64-битными числами, беззнаковыми 64-битными числами и мнимыми числами. Последние три вида представляют собой таблицы с u32-объектами на 0 и 1 (*[0], *[1]). Два u32 могут быть только у мнимых чисел, а проверить наличие знака у 64-битных чисел можно, проверяя поле "signed".
Числовые константы могут быть либо u32, либо целым числом.
u32-объекты не могут быть созданы напрямую, так как это почти бесполезно, ибо функции, создающие kgc-объекты и числовые константы, уже это делают автоматически. Они имеют следующую структуру:
И метод "double" который преобразовывает их обратно в Lua-числа. Не работает с kgc-объектами.
Чтобы начать её использование, необходимо её загрузить:
Пример:
local ljedit = require('ljedit')
- (ljedit.edit or ljedit)(bcdump [, do_not_convert]) - главная функция библиотеки, которая читает данный ей в виде строки байт-код и возвращает скрипт в виде объекта, структура которого будет описана ниже. Второй необязательный аргумент заставит парсер пропустить автоматическую реструктуризацию объекта.
- ljedit.new.script([version, big_endian, stripped, has_ffi, fr2, chunkname]) - создаёт новый пустой скрипт в виде объекта.
- ljedit.new.proto() - создаёт новый пустой объект прототипа.
- ljedit.new.bcins(op, A, B, C, D) - создаёт новую инструкцию в виде объекта. В зависимости от того, указан операнд "B" или нет, будет создана инструкция вида ABC или AD.
- ljedit.new.kgc(tp [, string | table | lo, hi | lo0, hi0, lo1, hi1]) - создаёт новый объект комплексной константы указанного типа.
- ljedit.new.knum(lo, hi | num | int) - создаёт новую числовую константу. Если даны два целых 32-битных числа, то они будут собраны в u32. Если дано одно Lua-число, которое может быть больше максимального 32-битного значения или быть вещественным, то оно будет автоматически преобразовано в u32. Если же дано одно целое число в диапазоне знакового 32-битного числа, то оно вернётся в неизменном виде. u32 – объект, представляющий собой два целых 32-битных числа, склеенных в одно 64-битное.
- ljedit.new.uv(id, [, name]) - создаёт новый объект верхней переменной с заданным айди и названием (название будет сохранено только в том случае, если скрипт может содержать дебаг-информацию). Задавать название необязательно.
- ljedit.get_instruction_info(bcins | op [, version]) - возвращает таблицу, содержащую информацию про инструкцию по типу названия и типа операндов. Первый аргумент может быть объектом инструкции либо опкодом. Второй операнд является необязательным и задаёт версию байт-кода (по умолчанию используется версия 2, которая соответствует версии 2.1, даже если скрипт версии 1 (2.0)).
В объектах, указанных ранее, используются массивы, которые могут начинаться либо с 1 (array1), либо с 0 (array0), содержащие два метода: "insert" и "remove". Для каждого из двух видов массивов используются свои методы, которые учитывают их особенности.
Объект скрипта имеет следующую структуру:
Код:
{
header = {
version = int (1 or 2, not 2.0 or 2.1),
flags = {
big_endian = boolean,
stripped = boolean,
has_ffi = boolean,
fr2 = boolean
},
chunkname = string
},
protos or main = array1 or proto
}
script:proper() - изменяет структуру объекта таким образом, что она становится сходна со структурой прототипов в памяти LuaJIT, перемещая прототипы из массива прототипов в константы других прототипов, заменяя нули, служащие заглушкой, до тех пор, пока не останется только один – главный прототип. Может потребовать значительное количество времени.
script:literal() - раскладывает прототипы в один линейный массив. Автоматически вызывается компилятором, так как он умеет работать только с линейной структурой, что в разы медленнее, но заметно только при работе с большими скриптами.
script:compile() - компилирует объект скрипта обратно в строку, содержащую байт-код, и возвращает её. Сам объект не должен изменяться.
Предупреждение: объект скрипта может жрать в 17 раз больше оперативной памяти, чем обычная строка, содержащая тот же байт-код, что нехило, однако, и виноваты в этом, без всякого сомнения, Lua-таблицы, потому что они пиздец жирные. Фиг знает, почему так происходит.
Каждый объект прототипа имеет следующую структуру:
Код:
{
flags = {
has_children = boolean,
is_vararg = boolean,
has_ffi = boolean,
no_jit = boolean,
iloop = boolean
},
numparams = int,
framesize = int,
sizeuv = int,
sizekgc = int,
sizekn = int,
sizebc = int,
sizedbg = int,
firstline = nil or int,
numline = nil or int,
bcins = array1,
uv = array1,
kgc = array0,
knum = array0,
lineinfo = nil or string,
uvinfo = nil or string,
varinfo = nil or string
}
Каждый объект инструкции на деле представляет собой таблицу, содержащую инструкцию в виде целого числа, чтобы уменьшить потребление памяти, а также мета-таблицу, которая позволяет индексировать её так, как если бы она представляла собой обычную таблицу с полями. Структура объекта инструкции выглядит следующим образом:
Код:
{
[0] = int,
A = byte,
B = byte or nil,
C = byte or nil,
D = short or nil,
li = int or nil
}
Каждый объект верхней переменной содержит два поля: "id" и "name", где второе является необязательным и заполняется автоматически, если скрипт содержит дебаг-информацию. Поле "name" игнорируется, если скрипт не должен содержать дебаг-информацию.
Kgc-объекты бывают разными: чистыми строками, таблицами (они, почти как обычные Lua-таблицы, но имеют мета-таблицу с полем "__tab", чтобы его можно было отличить от других объектов), прототипами или нулями, в зависимости от структуры объекта скрипта, знаковыми 64-битными числами, беззнаковыми 64-битными числами и мнимыми числами. Последние три вида представляют собой таблицы с u32-объектами на 0 и 1 (*[0], *[1]). Два u32 могут быть только у мнимых чисел, а проверить наличие знака у 64-битных чисел можно, проверяя поле "signed".
Числовые константы могут быть либо u32, либо целым числом.
u32-объекты не могут быть созданы напрямую, так как это почти бесполезно, ибо функции, создающие kgc-объекты и числовые константы, уже это делают автоматически. Они имеют следующую структуру:
Код:
{
u32 = {
lo = lo,
hi = hi
}
}