Информация Гайд используем LUA (MoonLoader) в C++

g305noobo

Известный
Автор темы
214
191
Всем привет! Не трудно догадаться, что в этой теме речь будет идти о использовании 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.

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;
}
g_lua это мой мини класс враппер для всего этого
В этом коде мы просто загружаем код из переменной 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);
}

Итог
Получаем такую картину, в которой каждый скрипт вызвал нужный нам код

1714218515908.png

1714218581639.jpeg
 

xSkateboard

Активный
39
126
В WoW так делают lua unlocker'ы protected функций от близзов или на базе этого пишут своих ботов или читы. Интересная тема
 
  • Вау
Реакции: g305noobo

why ega

РП игрок
Модератор
2,556
2,248
Всем привет! Не трудно догадаться, что в этой теме речь будет идти о использовании 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.

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;
}
g_lua это мой мини класс враппер для всего этого
В этом коде мы просто загружаем код из переменной 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);
}

Итог
Получаем такую картину, в которой каждый скрипт вызвал нужный нам код

Посмотреть вложение 238511
Посмотреть вложение 238512
Мне кажется, хукать getfield не лучшая идея, поскольку не все скрипты имеют функцию main. На больную голову решил прохукать lua_newstate, но чет получил таз крашей, поэтому, возможно, кто-то доделает этот шедеврометод
C++:
LSHook::LSHook()
{
    auto luaLibAddress = GetModuleHandleA("lua51.dll");
    if (!luaLibAddress) return;


    luaL_loadstring = reinterpret_cast<luaL_loadstring_t>(GetProcAddress(luaLibAddress, "luaL_loadstring"));
    lua_pcall = reinterpret_cast<lua_pcall_t>(GetProcAddress(luaLibAddress, "lua_pcall"));


    auto luaL_newstateAddress = GetProcAddress(luaLibAddress, "luaL_newstate");
    luaL_newstateHook.set_dest(luaL_newstateAddress);

    luaL_newstateHook.set_cb([this](auto&&... args)
    {
        return luaL_newstateHooked(args...);
    });

    luaL_newstateHook.install();
}



LSHook::~LSHook()
{
    luaL_newstateHook.remove();
}



std::uintptr_t LSHook::luaL_newstateHooked(const decltype(luaL_newstateHook)& hook)
{
    auto L = hook.get_trampoline()();

    auto result = luaL_loadstring(L, "print(\"HUY\")");
    if (result == 0)
        lua_pcall(L, 0, 0, 0);

    return L;
}
header:
#include <Windows.h>


#include "kthook/kthook.hpp"



class LSHook
{
private:
    using luaL_newstate_t = std::uintptr_t(*)();
    kthook::kthook_simple<luaL_newstate_t> luaL_newstateHook;
    std::uintptr_t luaL_newstateHooked(const decltype(luaL_newstateHook)& hook);

    using luaL_loadstring_t = int(*)(std::uintptr_t, const char*);
    using lua_pcall_t = int(*)(std::uintptr_t L, int nargs, int nresults, int errfunc);;

    luaL_loadstring_t luaL_loadstring = nullptr;
    lua_pcall_t lua_pcall = nullptr;

public:
    LSHook(/* args */);
    ~LSHook();
} lshook;
 

g305noobo

Известный
Автор темы
214
191
Мне кажется, хукать getfield не лучшая идея, поскольку не все скрипты имеют функцию main. На больную голову решил прохукать lua_newstate, но чет получил таз крашей, поэтому, возможно, кто-то доделает этот шедеврометод
C++:
LSHook::LSHook()
{
    auto luaLibAddress = GetModuleHandleA("lua51.dll");
    if (!luaLibAddress) return;


    luaL_loadstring = reinterpret_cast<luaL_loadstring_t>(GetProcAddress(luaLibAddress, "luaL_loadstring"));
    lua_pcall = reinterpret_cast<lua_pcall_t>(GetProcAddress(luaLibAddress, "lua_pcall"));


    auto luaL_newstateAddress = GetProcAddress(luaLibAddress, "luaL_newstate");
    luaL_newstateHook.set_dest(luaL_newstateAddress);

    luaL_newstateHook.set_cb([this](auto&&... args)
    {
        return luaL_newstateHooked(args...);
    });

    luaL_newstateHook.install();
}



LSHook::~LSHook()
{
    luaL_newstateHook.remove();
}



std::uintptr_t LSHook::luaL_newstateHooked(const decltype(luaL_newstateHook)& hook)
{
    auto L = hook.get_trampoline()();

    auto result = luaL_loadstring(L, "print(\"HUY\")");
    if (result == 0)
        lua_pcall(L, 0, 0, 0);

    return L;
}
header:
#include <Windows.h>


#include "kthook/kthook.hpp"



class LSHook
{
private:
    using luaL_newstate_t = std::uintptr_t(*)();
    kthook::kthook_simple<luaL_newstate_t> luaL_newstateHook;
    std::uintptr_t luaL_newstateHooked(const decltype(luaL_newstateHook)& hook);

    using luaL_loadstring_t = int(*)(std::uintptr_t, const char*);
    using lua_pcall_t = int(*)(std::uintptr_t L, int nargs, int nresults, int errfunc);;

    luaL_loadstring_t luaL_loadstring = nullptr;
    lua_pcall_t lua_pcall = nullptr;

public:
    LSHook(/* args */);
    ~LSHook();
} lshook;
это хорошая идея, но из-за того что мы хукаем самую начальную точку, moonloader не успевает сделать luaL_openlibs, соотвественно в хуке даже print не работает в luaL_loadstring, не говоря уже о функциях муна.

но если просто сохранить эти указатели и использовать попозже, все будет ок