Всем привет! Не трудно догадаться, что в этой теме речь будет идти о использовании LUA скриптов в связке с MoonLoader API из под C++.
Вообще это довольно интересная тема, потому что в основном в сампе все ограничиваются использованием одного ЯП (языка программирования), а ведь можно использовать одновременно почти все фичи, благо которых в нашем арсенале очень и очень много.
Например можно реализовать меню на LUA, а функционал на C++: https://www.blast.hk/threads/205273/
Помимо использования подобных фишек как реализация меню и функционала на разных ЯП мы можем даже сделать такую конструкцию:
- C++ Основа [плагин]
- C++ Структура проекта / реализация функционала
- LUA Готовые функции, работающие с GTA / SAMP для использовния в реализации функционала в пункте выше
Единственные минусы, которые по-моему мнению есть в подобных решениях это:
- другая изначальная задумка: никто ведь не плагировал использовать функции для скриптов в плагинах, в связи с этим возможны какие-то неожиданные проблемы.
- излишние зависимости: у этого пункта есть как минусы, так и плюсы. Например, если мы создаем плагин, в котором будем использовать MoonLoader API, то очевидно, что мы зависим от MoonLoader. Однако, как плюс, весь функционал зачастую дублируется; например, функция для регистрации команды может повторяться во многих ASI плагинах. К этому мы добавляем SampFuncs, который экспортирует эту функцию.
Хотелось бы реализовать что-то вроде либы для приятного использования этого всего, если кто-то захочет попробовать, могу отправить часть моей "попытки"
Реализация
Создать плагин, который будет использовать MoonLoader API, довольно просто, и вариантов самой реализации много. Я же буду использовать экспортируемые из lua51.dll, а также хук функции "lua_getfield" из того lua51.dll, эта функция используется в MoonLoader и нужна для того чтобы получить значение по имени ключа из таблицы LUA.
В этой функции мы перехватываем следующее:
- указатель на структуру lua_State(благодаря ей мы будем взаимодействовать)
- индекс на структуру стека (где находится LUA таблица, из которой нужно получить значение)
- название ключа (из которого нужно получить значение)
Сам хук выглядит так:
Поскольку функция "lua_getfield" вызывается почти постоянно в различных местах, нам необходимо фильтровать эти вызовы и брать указатель на структуру lua_State только из скриптов. Сделать это можно легко, так как при инициализации скрипта MoonLoader обращается к ключу "main", на это мы и будем ориентироваться.
Всё почти готово! Теперь давайте определим парочку функций из lua51.dll, которые понадобятся чуть дальше..
Получаем эти функции
Пример использования
Все готово для использования, теперь давайте воспользуемся ранее полученными функциями и попробуем воспроизвести какой-нибудь код на LUA.
Для этого создадим мини-функцию-обёртку
g_lua это мой мини класс враппер для всего этого
В этом коде мы просто загружаем код из переменной code и в случае ошибки выводим ее, как раз для этого нам нужна была функция tolstring, которая преобразовывает луашный текст.
Теперь осталось просто вызвать эту функцию в хуке с любым кодом, который вы бы хотели воспроизвести внутри MoonLoader
Итог
Получаем такую картину, в которой каждый скрипт вызвал нужный нам код
Вообще это довольно интересная тема, потому что в основном в сампе все ограничиваются использованием одного ЯП (языка программирования), а ведь можно использовать одновременно почти все фичи, благо которых в нашем арсенале очень и очень много.
Например можно реализовать меню на LUA, а функционал на C++: https://www.blast.hk/threads/205273/
Помимо использования подобных фишек как реализация меню и функционала на разных ЯП мы можем даже сделать такую конструкцию:
- C++ Основа [плагин]
- C++ Структура проекта / реализация функционала
- LUA Готовые функции, работающие с GTA / SAMP для использовния в реализации функционала в пункте выше
Единственные минусы, которые по-моему мнению есть в подобных решениях это:
- другая изначальная задумка: никто ведь не плагировал использовать функции для скриптов в плагинах, в связи с этим возможны какие-то неожиданные проблемы.
- излишние зависимости: у этого пункта есть как минусы, так и плюсы. Например, если мы создаем плагин, в котором будем использовать MoonLoader API, то очевидно, что мы зависим от MoonLoader. Однако, как плюс, весь функционал зачастую дублируется; например, функция для регистрации команды может повторяться во многих ASI плагинах. К этому мы добавляем SampFuncs, который экспортирует эту функцию.
Хотелось бы реализовать что-то вроде либы для приятного использования этого всего, если кто-то захочет попробовать, могу отправить часть моей "попытки"
Реализация
Создать плагин, который будет использовать MoonLoader API, довольно просто, и вариантов самой реализации много. Я же буду использовать экспортируемые из lua51.dll, а также хук функции "lua_getfield" из того lua51.dll, эта функция используется в MoonLoader и нужна для того чтобы получить значение по имени ключа из таблицы LUA.
C++:
using lua_getfield_t = void(__cdecl*)(lua_State* L, int index, const char* k);
В этой функции мы перехватываем следующее:
- указатель на структуру lua_State(благодаря ей мы будем взаимодействовать)
- индекс на структуру стека (где находится LUA таблица, из которой нужно получить значение)
- название ключа (из которого нужно получить значение)
Сам хук выглядит так:
C++:
class c_hooks {
public:
typedef struct lua_State;
using lua_getfield_t = void(__cdecl*)(lua_State* L, int index, const char* k);
static lua_getfield_t lua_getfield_p;
static void lua_getfield(lua_State* L, int index, const char* k) {
return lua_getfield_p(L, index, k);
}
};
c_hooks::lua_getfield_t c_hooks::lua_getfield_p = nullptr;
void install_hook() {
MH_Initialize();
auto lua_getfield_adr = GetProcAddress(GetModuleHandleA("lua51.dll"), "lua_getfield");
MH_CreateHook(reinterpret_cast<void*>(lua_getfield_adr), &c_hooks::lua_getfield, reinterpret_cast<void**>(&c_hooks::lua_getfield_p));
MH_EnableHook(reinterpret_cast<void*>(lua_getfield_adr));
}
Поскольку функция "lua_getfield" вызывается почти постоянно в различных местах, нам необходимо фильтровать эти вызовы и брать указатель на структуру lua_State только из скриптов. Сделать это можно легко, так как при инициализации скрипта MoonLoader обращается к ключу "main", на это мы и будем ориентироваться.
C++:
static void lua_getfield(lua_State* L, int index, const char* k) {
if (strstr(k, "main")) {
// что тут???
}
return lua_getfield_p(L, index, k);
}
Всё почти готово! Теперь давайте определим парочку функций из lua51.dll, которые понадобятся чуть дальше..
C++:
typedef int (*L_loadstring_t)(lua_State*, const char*);
typedef const char* (*tolstring_t)(lua_State*, int, size_t*);
L_loadstring_t L_loadstring = nullptr;
tolstring_t tolstring = nullptr;
Получаем эти функции
C++:
auto hLua = GetModuleHandleA("lua51.dll");
L_loadstring = (L_loadstring_t)GetProcAddress(hLua, "luaL_loadstring");
tolstring = (tolstring_t)GetProcAddress(hLua, "lua_tolstring");
Пример использования
Все готово для использования, теперь давайте воспользуемся ранее полученными функциями и попробуем воспроизвести какой-нибудь код на LUA.
Для этого создадим мини-функцию-обёртку
C++:
void load_string(lua_State* L, const std::string& code) {
if (L == nullptr) {
std::cerr << "Error lua_State == nullptr" << std::endl;
return;
}
auto loadstring_result = g_lua.L_loadstring(L, code.c_str());
if (loadstring_result == LUA_OK) {
if (g_lua.pcall(L, 0, 0, 0) != LUA_OK) std::cerr << "Error executing Lua code: " << g_lua.tolstring(L, -1, 0) << std::endl;
}
else std::cerr << "Error loading Lua string: " << g_lua.tolstring(L, -1, 0) << std::endl;
}
В этом коде мы просто загружаем код из переменной code и в случае ошибки выводим ее, как раз для этого нам нужна была функция tolstring, которая преобразовывает луашный текст.
Теперь осталось просто вызвать эту функцию в хуке с любым кодом, который вы бы хотели воспроизвести внутри MoonLoader
C++:
static void lua_getfield(lua_State* L, int index, const char* k) {
if (strstr(k, "main")) {
g_lua.load_string(L, "print('Hello, from cpp!')");
}
return lua_getfield_p(L, index, k);
}
Итог
Получаем такую картину, в которой каждый скрипт вызвал нужный нам код