Исходник Гайд Использование неблокирующих WebSocket'ов в LUA

artemizgame

Известный
Автор темы
23
40

В этом гайде я покажу как создать соединение по вебсокету на базе SignalR с клиента LUA.​


update 15.12.23. Теперь доступна библиотека для ванильных вебсокетов (любой сервер). Перейти

P.S В этом гайде будет использовано два языка программирования, поэтому некоторые моменты могут быть не понятны для тех кто кодит исключительно на LUA, но я постараюсь объяснить максимально понятно - как, что и почему. Читать далее или нет решать вам.
P.S.S Гайд будет на примере RakSamp


Что нужно для проекта:
1) Сервер вебсокет, в моем случае я буду использовать фреймворк SignalR от майкрософт.
2) Библиотека С++ для работы с соединением и получением информации.
3) LUA клиент.

Часть 1: Создание сервера​

Я буду использовать C# ASP.NET, потому-что SignalR сервер может работать исключительно на .NET (хотя подсоединиться можно почти через что угодно). Плюс это удобно, в одном сервисе крутится вебсокет и обычный WEB-API (post, get ...).
1)Открываем Visual Studio Installer
2) Находим Visual Studio 2022, жмем изменить
3)
Screenshot_38.png
Открываем Visual Studio, нажимаем создание проекта, выбираем Веб-API ASP.NET CORE,
Screenshot_39.png


Жмем далее, настраиваем как на скрине

Screenshot_40.png


Создаем

Если проект создан по спецификации выше, попадаем на класс Program. Это точка входа в наш веб-сокет сервер. Давайте удалим лишнее, вот такое должно получится:
Отредактированный текст:
var builder = WebApplication.CreateBuilder(args); //Создаем приложение

builder.Services.AddEndpointsApiExplorer(); //Говорим ему, чтобы слушал эндпоинты. Эндпоинт это конечный адрес, допустим есть адрес http://localhost:7229/Weather, эндпоинтом будет в нем Weather

var app = builder.Build(); //Применяем настройки выше

app.Run(); //Запускаем приложение

Изначально ASP.NET не подключает SignalR к проекту, поэтому это сделаем мы:
Подключение SIGNALR к проекту:
using Microsoft.AspNetCore.SignalR; //Добавляем пространство имен SignalR к проекту, после этого
//можем получить классы для работы с ним

var builder = WebApplication.CreateBuilder(args);

//Вообще, приложение ASP.NET состоит из сервисов, каждый отвечает за свою часть
builder.Services.AddEndpointsApiExplorer(); //Уже рассказывал
builder.Services.AddSignalR(); //Добавляем сервис SignalR

var app = builder.Build();

app.Run();

Теперь создадим класс для работы с сообщениями, в SignalR это называется Hub. Hub - принимает и отправляет сообщения, хранит данные о подключениях и т.д. Хабов в приложение может быть сколько угодно, они разделяют логику работы. Допустим в приложении есть чат, это будет ChatHub, и есть хаб уведомлений NotifyHub. У нас же будет
DataRecieverHub. Добавляем этот код в самый конец Program.cs.
DataRecieverHub:
internal class DataRecieverHub : Hub
{
//Тут будут обработчики
}

Хаб создан, хоть он и без каких-либо обработчков, формально он работает.
P.S Вообще на данном этапе я бы порекомендовал новичку изучить основы ООП (или глянуть ролик какой на ютубе). Потому что наш DataRecieverHub наследует (т.е перенимает некоторые свойства (переменные) и методы (функции)) от класса Hub. И уже содержит некоторые обработчики, просто они внутри Hub.

Чтобы приложение начало принимать данные в DataRecieverHub, нужно указать это в конфигурации, добавим эту строчку перед app.Run();
C#:
app.MapHub<DataRecieverHub>("data-reciever-hub");

Этой строкой мы говорим: если какой либо из клиентов пытается соединиться по ссылке http://localhost:7229/data-reciever-hub, значит отправляем его соединение в класс DataRecieverHub.

Добавим методы в DataRecieverHub. Обработчик подключения и отключения клиента:
Новые методы:
internal class DataRecieverHub : Hub
{
    //Подключение, этот метод будет вызываться при подключении любого клиента
    public override async Task<Task> OnConnectedAsync()
    {
        //Вызововем клиентский метод PendingMessage, на всех подключенных клиентах (включая того кто только подключился)
        await Clients.All.SendAsync("PendingMessage", $"{this.Context.ConnectionId} is connected!");
        //Базовый метод для подключения из Hub, вызываем его, если интересно можно почитать про полиморфизм в ООП
        return base.OnConnectedAsync();
    }
    //Аналогично, только отключение
    public override async Task OnDisconnectedAsync(Exception exception)
    {
        await base.OnDisconnectedAsync(exception);
    }
}

Часть 2: Подключаемся к серверу​

Как указано выше, я буду подключаться к серверу через раксамп, однако весь этот гайд так же подойдет для любых клиентов если там используется LUA5.1.

Скачиваем библиотеку-прокладку из приложенного архива.
P.S
Для нормальной работы чистого луа не хватит, вообще основная причина написания этого гайда, то что готовыми библиотеками для вебсокетов (например lua websocket) обойтись в случае сампа скорее всего не получится. Мне пришлось создать библиотеку на С++ для луа, которая служит прокладкой между LUA и официальной библиотекой SignalR от майкрософт, она работает асинхронно, поэтому ничего блокировано не будет, о библиотеке-прокладке я расскажу в 3 части. Пока условимся что моя библиотека уже скачана и находится в папке /scripts/libs. Открытый код я так-же приложу далее.


Напишем простейший скрипт на LUA для подключения к нашему вебсокету:


Код подключения:
--Подключение библиотеки прокладки
require("WebS")
require("addon")

local encoding = require "encoding"

--Вызывается после успешной загрузки этого скрипта раксампом
function onLoad()
    --Подключение к нашему эндпоинту
    WebS.Connect("http://localhost:5056/data-reciever-hub");
 
    --Бексконечный цикл получения сообщений с вебсокета.
    newTask(function()
        while true do
            --Получаем последнее сообщение из очереди.
            local output = WebS.GetMessage()
            --Сообщение не пустое?
            if output ~= '' then
                print(output)
            end
            wait(100)
        end
    end)
end

function onDisconnect()
    WebS.Disconnect()
end
Это всё. Запустим проект ASP.NET в Visual Studio. Далее запустим ракбота, увидим следующий текст:
Screenshot_41.png


Значит соединение прошло успешно. Боту присвоен уникальный идентификатор.
Теперь сделаем отправку сообщений на сервер из клиента LUA:
Добавим обработчик события onConnect в скрипт:
Отправка никнейма на сервер после подключения к серверу самп:
function onConnect()
    local table = { getBotNick() }
    WebS.SendMessage("BotConnected", table) -- BotConnect это имя метода (функции) из класса хаба ASP.NET, у нас его нет, поэтому добавим его
end

Добавим обработчик сообщения BotConnected для ASP.NET
Новые метод BotConnect:
internal class DataRecieverHub : Hub
{
    public override async Task<Task> OnConnectedAsync()
    {
        await Clients.All.SendAsync("PendingMessage", $"{this.Context.ConnectionId} is connected!");
        return base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception exception)
    {
        await base.OnDisconnectedAsync(exception);
    }
 
    //Добавленный метод
    public async Task BotConnected(string botNick)
    {
        await Clients.All.SendAsync("PendingMessage", $"{botNick} success connected to server");
    }
}

Хотелось бы уточнить как именно передаются данные из LUA на сервер, как видно метод WebS.SendMessage() принимает первым аргументом название метода из хаба который будет вызван, а вторым аргументом таблицу содержащую коллекцию строк (именно строк, не чисел и т.д)
Если бы мы хотели передавать помимо никнейма еще и пинг при подключении, надо сделать так:
ASP.NET:
C#:
public async Task BotConnected(string botNick, string ping)
{
  await Clients.All.SendAsync("PendingMessage", $"{botNick} success connected to server with ping {ping}");
}
LUA:
Lua:
function onConnect()
    local table = { getBotNick(), tostring(getBotPing())}
    WebS.SendMessage("BotConnected", table)
end

Часть 3: Библиотека-прокладка​

Про библиотеку писать особо не вижу смысла, просто приложу исходник

Конец​

Полный проект (РакСамп (https://www.blast.hk/threads/108052/) + ASP.NET) из гайда прикладываю в виде архива: Скачать

P.S Иногда библиотеки .dll надо положить в КОРЕНЬ раксампа (или гта са), т.е рядом с .exe.
P.S.S если ошибка осталась, и у тебя обычная гта с мунлоадером, положи WebS.dll в папку moonloader/lib, а содержимое папки sampwebsocket из архива, положи в корневую папки ГТА
 

Вложения

  • sampsocket.rar
    2.2 MB · Просмотры: 28
Последнее редактирование:

artemizgame

Известный
Автор темы
23
40
Поступило пара просьб о создании библиотеки для подключения к ванильным вебсокетам, поэтому решил сделать еще одну библу-прокладку.
За основу взята либа IXWebSocket.

Установка для раксамп:
1) Содержимое архива в /scripts/libs
Установка для moonloader:
1) Содержимое архива в /moonloader/lib

Методы:
Код:
ConnectWS("URL") - подключение к вебсокету
SendMessageWS("TEXT") - отправить сообщение на сервер
GetMessageWS() - получить сообщение
DisconnectWS() - отключиться от вебсокета
GetConnectionStatus() - получить статус подключения (строка) -
{
    CONNECTING,
    OPEN,
    CLOSING,
    CLOSED
}


Использование похожее:
Мунлоадер:

Пример для мунлоадера:
local ws = require("websocketsamp")

function main()
    if not isSampLoaded() or not isSampfuncsLoaded() then return end
    while not isSampAvailable() do wait(100) end

    sampRegisterChatCommand("send", sendMsg)

    ws.Connect("wss://socketsbay.com/wss/v2/1/demo/");

    lua_thread.create(function()
        while true do
            local output = ws.GetMessage()
            if output ~= '' then
                sampAddChatMessage(output, 0x00DD00)
            end
            wait(100)
        end
        return true
    end)

end

function sendMsg(arg)
    ws.SendMessage(arg)
end


function onQuitGame()
    ws.Disconnect()
end

Раксамп:
Пример раксамп:
local ws = require("websocketsamp")
require("addon")

function onLoad()
    ws.Connect("wss://socketsbay.com/wss/v2/1/demo/");

    newTask(function()
        while true do
            if ws.GetConnectionStatus() == "OPEN" then
                local output = ws.GetMessage()
                if output ~= '' then
                    print(output)
                end
            end
            wait(100)
        end
    end)
end

function onRunCommand(cmd)
    if cmd:find("^!sendWs %d+$") then
        ws.SendMessage(cmd:match("%d+"))
        return false
    end
end

function onDisconnect()
    WebS.Disconnect()
end


P.S Иногда библу .dll надо положить в КОРЕНЬ раксампа (или гта са), т.е рядом с .exe.
 

Вложения

  • websocketsamp.rar
    270.8 KB · Просмотры: 24
Последнее редактирование:

tripple sixx

Участник
60
31
Автору этой темы огромное спасибо!
Кому интерестно, можете почитать что я придумал:
значит заебали меня эти ваши помойные подключения тг ботов к раксампу, и я сделал ахуенный коннект между aiogram (python) и websocket (lua).

raksamp lua:
local encoding = require("encoding")
local u8 = encoding.UTF8
encoding.default = "CP1251"

local ws = require("websocketsamp")
require("addon")

last_message = ""

function onLoad()
    newTask(function()
        while true do
            if ws.GetConnectionStatus() == "OPEN" then
                local output = ws.GetMessage()
                if output ~= '' and output ~= last_message then
                    print(u8:decode(output))
                end
            else
                ws.Connect("ws://localhost:8765");
            end
            wait(100)
        end
    end)
end

function onRunCommand(cmd)
    if cmd:find("!send (.+)") then
        last_message = cmd:match("!send (.+)")
        ws.SendMessage(u8:encode(last_message))
        return false
    end
end

function onDisconnect()
    WebS.Disconnect()
end


питон:
from aiogram import executor, Dispatcher, Bot, types
import websockets
import asyncio

bot = Bot('токен', parse_mode='HTML')
dp = Dispatcher(bot)

connected_clients = set()
id = ваш тг айди

async def handle_client(websocket, path):
    connected_clients.add(websocket)
    try:
        async for message in websocket:
            await bot.send_message(id, f'<b>📫 Новое сообщение:</b>\n<pre>{message}</pre>')
            await asyncio.gather(*[client.send(message) for client in connected_clients])
    except websockets.exceptions.ConnectionClosedError:
        pass
    finally:
        connected_clients.remove(websocket)

async def send_websocket_message(message):
    if connected_clients:
        await asyncio.gather(*[client.send(message) for client in connected_clients])
    else:
        print("Нет подключенных клиентов.")

@dp.message_handler(commands=['send'])
async def cmd_send(message: types.Message):
    text = message.get_args()
    await message.answer(f'<b>🛫 Отправляю сообщение:</b>\n<pre>{text}</pre>')
    await send_websocket_message(text)

async def main():
    try:
        start_server = websockets.serve(handle_client, "localhost", 8765)
        await asyncio.gather(start_server)
    except Exception as e:
        print(f"Возникла ошибка при запуске: {e}")

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.create_task(main())
    loop.run_until_complete(executor.start_polling(dp, loop=loop))
ps: сделано под эту библиотеку https://www.blast.hk/threads/197405/post-1424989
 
Последнее редактирование:

Rezbirp

Известный
72
69

В этом гайде я покажу как создать соединение по вебсокету на базе SignalR с клиента LUA.​


update 15.12.23. Теперь доступна библиотека для ванильных вебсокетов (любой сервер). Перейти

P.S В этом гайде будет использовано два языка программирования, поэтому некоторые моменты могут быть не понятны для тех кто кодит исключительно на LUA, но я постараюсь объяснить максимально понятно - как, что и почему. Читать далее или нет решать вам.
P.S.S Гайд будет на примере RakSamp

Что нужно для проекта:
1) Сервер вебсокет, в моем случае я буду использовать фреймворк SignalR от майкрософт.
2) Библиотека С++ для работы с соединением и получением информации.
3) LUA клиент.

Часть 1: Создание сервера​

Я буду использовать C# ASP.NET, потому-что SignalR сервер может работать исключительно на .NET (хотя подсоединиться можно почти через что угодно). Плюс это удобно, в одном сервисе крутится вебсокет и обычный WEB-API (post, get ...).
1)Открываем Visual Studio Installer
2) Находим Visual Studio 2022, жмем изменить
3) Посмотреть вложение 224352
Открываем Visual Studio, нажимаем создание проекта, выбираем Веб-API ASP.NET CORE,
Посмотреть вложение 224353

Жмем далее, настраиваем как на скрине

Посмотреть вложение 224354

Создаем

Если проект создан по спецификации выше, попадаем на класс Program. Это точка входа в наш веб-сокет сервер. Давайте удалим лишнее, вот такое должно получится:
Отредактированный текст:
var builder = WebApplication.CreateBuilder(args); //Создаем приложение

builder.Services.AddEndpointsApiExplorer(); //Говорим ему, чтобы слушал эндпоинты. Эндпоинт это конечный адрес, допустим есть адрес http://localhost:7229/Weather, эндпоинтом будет в нем Weather

var app = builder.Build(); //Применяем настройки выше

app.Run(); //Запускаем приложение

Изначально ASP.NET не подключает SignalR к проекту, поэтому это сделаем мы:
Подключение SIGNALR к проекту:
using Microsoft.AspNetCore.SignalR; //Добавляем пространство имен SignalR к проекту, после этого
//можем получить классы для работы с ним

var builder = WebApplication.CreateBuilder(args);

//Вообще, приложение ASP.NET состоит из сервисов, каждый отвечает за свою часть
builder.Services.AddEndpointsApiExplorer(); //Уже рассказывал
builder.Services.AddSignalR(); //Добавляем сервис SignalR

var app = builder.Build();

app.Run();

Теперь создадим класс для работы с сообщениями, в SignalR это называется Hub. Hub - принимает и отправляет сообщения, хранит данные о подключениях и т.д. Хабов в приложение может быть сколько угодно, они разделяют логику работы. Допустим в приложении есть чат, это будет ChatHub, и есть хаб уведомлений NotifyHub. У нас же будет
DataRecieverHub. Добавляем этот код в самый конец Program.cs.
DataRecieverHub:
internal class DataRecieverHub : Hub
{
//Тут будут обработчики
}

Хаб создан, хоть он и без каких-либо обработчков, формально он работает.
P.S Вообще на данном этапе я бы порекомендовал новичку изучить основы ООП (или глянуть ролик какой на ютубе). Потому что наш DataRecieverHub наследует (т.е перенимает некоторые свойства (переменные) и методы (функции)) от класса Hub. И уже содержит некоторые обработчики, просто они внутри Hub.

Чтобы приложение начало принимать данные в DataRecieverHub, нужно указать это в конфигурации, добавим эту строчку перед app.Run();
C#:
app.MapHub<DataRecieverHub>("data-reciever-hub");

Этой строкой мы говорим: если какой либо из клиентов пытается соединиться по ссылке http://localhost:7229/data-reciever-hub, значит отправляем его соединение в класс DataRecieverHub.

Добавим методы в DataRecieverHub. Обработчик подключения и отключения клиента:
Новые методы:
internal class DataRecieverHub : Hub
{
    //Подключение, этот метод будет вызываться при подключении любого клиента
    public override async Task<Task> OnConnectedAsync()
    {
        //Вызововем клиентский метод PendingMessage, на всех подключенных клиентах (включая того кто только подключился)
        await Clients.All.SendAsync("PendingMessage", $"{this.Context.ConnectionId} is connected!");
        //Базовый метод для подключения из Hub, вызываем его, если интересно можно почитать про полиморфизм в ООП
        return base.OnConnectedAsync();
    }
    //Аналогично, только отключение
    public override async Task OnDisconnectedAsync(Exception exception)
    {
        await base.OnDisconnectedAsync(exception);
    }
}

Часть 2: Подключаемся к серверу​

Как указано выше, я буду подключаться к серверу через раксамп, однако весь этот гайд так же подойдет для любых клиентов если там используется LUA5.1.

Скачиваем библиотеку-прокладку из приложенного архива.
P.S
Для нормальной работы чистого луа не хватит, вообще основная причина написания этого гайда, то что готовыми библиотеками для вебсокетов (например lua websocket) обойтись в случае сампа скорее всего не получится. Мне пришлось создать библиотеку на С++ для луа, которая служит прокладкой между LUA и официальной библиотекой SignalR от майкрософт, она работает асинхронно, поэтому ничего блокировано не будет, о библиотеке-прокладке я расскажу в 3 части. Пока условимся что моя библиотека уже скачана и находится в папке /scripts/libs. Открытый код я так-же приложу далее.


Напишем простейший скрипт на LUA для подключения к нашему вебсокету:


Код подключения:
--Подключение библиотеки прокладки
require("WebS")
require("addon")

local encoding = require "encoding"

--Вызывается после успешной загрузки этого скрипта раксампом
function onLoad()
    --Подключение к нашему эндпоинту
    WebS.Connect("http://localhost:5056/data-reciever-hub");
 
    --Бексконечный цикл получения сообщений с вебсокета.
    newTask(function()
        while true do
            --Получаем последнее сообщение из очереди.
            local output = WebS.GetMessage()
            --Сообщение не пустое?
            if output ~= '' then
                print(output)
            end
            wait(100)
        end
    end)
end

function onDisconnect()
    WebS.Disconnect()
end
Это всё. Запустим проект ASP.NET в Visual Studio. Далее запустим ракбота, увидим следующий текст:
Посмотреть вложение 224357

Значит соединение прошло успешно. Боту присвоен уникальный идентификатор.
Теперь сделаем отправку сообщений на сервер из клиента LUA:
Добавим обработчик события onConnect в скрипт:
Отправка никнейма на сервер после подключения к серверу самп:
function onConnect()
    local table = { getBotNick() }
    WebS.SendMessage("BotConnected", table) -- BotConnect это имя метода (функции) из класса хаба ASP.NET, у нас его нет, поэтому добавим его
end

Добавим обработчик сообщения BotConnected для ASP.NET
Новые метод BotConnect:
internal class DataRecieverHub : Hub
{
    public override async Task<Task> OnConnectedAsync()
    {
        await Clients.All.SendAsync("PendingMessage", $"{this.Context.ConnectionId} is connected!");
        return base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception exception)
    {
        await base.OnDisconnectedAsync(exception);
    }
 
    //Добавленный метод
    public async Task BotConnected(string botNick)
    {
        await Clients.All.SendAsync("PendingMessage", $"{botNick} success connected to server");
    }
}

Хотелось бы уточнить как именно передаются данные из LUA на сервер, как видно метод WebS.SendMessage() принимает первым аргументом название метода из хаба который будет вызван, а вторым аргументом таблицу содержащую коллекцию строк (именно строк, не чисел и т.д)
Если бы мы хотели передавать помимо никнейма еще и пинг при подключении, надо сделать так:
ASP.NET:
C#:
public async Task BotConnected(string botNick, string ping)
{
  await Clients.All.SendAsync("PendingMessage", $"{botNick} success connected to server with ping {ping}");
}
LUA:
Lua:
function onConnect()
    local table = { getBotNick(), tostring(getBotPing())}
    WebS.SendMessage("BotConnected", table)
end

Часть 3: Библиотека-прокладка​

Про библиотеку писать особо не вижу смысла, просто приложу исходник

Конец​

Полный проект (РакСамп (https://www.blast.hk/threads/108052/) + ASP.NET) из гайда прикладываю в виде архива: Скачать

P.S Иногда библиотеки .dll надо положить в КОРЕНЬ раксампа (или гта са), т.е рядом с .exe.
Приветствую, нужно ли, что-то ещё помимо библиотеки? Так-как столкнулся с ошибкою:

"[ML] (error) client.lua: error loading module 'WebS' from file 'C:\Program Files (x86)\GTA SA\moonloader\lib\WebS.dll':
The specified module could not be found.

stack traceback:
[C]: in ?
[C]: in function 'require'
C:\Program Files (x86)\GTA SA\moonloader\client.lua:1: in main chunk
[ML] (error) client.lua: Script died due to an error. (01BBF504)"

Такую ошибку получил и на RakSamp Lite:

"[15:01:14] [LUA] error loading module 'WebS' from file 'D:\Project\RakBotGuide\scripts\libs\WebS.dll':
The specified module could not be found.


stack traceback:
[C]: in ?
[C]: in function 'require'
D:\Project\RakBotGuide\scripts\guide.lua:1: in main chunk"
 

artemizgame

Известный
Автор темы
23
40
Приветствую, нужно ли, что-то ещё помимо библиотеки? Так-как столкнулся с ошибкою:

"[ML] (error) client.lua: error loading module 'WebS' from file 'C:\Program Files (x86)\GTA SA\moonloader\lib\WebS.dll':
The specified module could not be found.

stack traceback:
[C]: in ?
[C]: in function 'require'
C:\Program Files (x86)\GTA SA\moonloader\client.lua:1: in main chunk
[ML] (error) client.lua: Script died due to an error. (01BBF504)"

Такую ошибку получил и на RakSamp Lite:

"[15:01:14] [LUA] error loading module 'WebS' from file 'D:\Project\RakBotGuide\scripts\libs\WebS.dll':
The specified module could not be found.


stack traceback:
[C]: in ?
[C]: in function 'require'
D:\Project\RakBotGuide\scripts\guide.lua:1: in main chunk"


Просто закинь все DLL в корневую папку, рядом с .exe, а не в папку библиотек.


Я уверен что есть способ указать путь до длл, чтобы избежать засорения корневой папки, но пробовал по разному и у меня не получилось (на дедике столкнулся с этой проблемой когда впервые загрузил туда ракбота). Хотя у меня на компе все работает в папке с либами.

Может кто из опытных форумчан шарит почему не подключаются dll из папки либ?
 

Rezbirp

Известный
72
69
Просто закинь все DLL в корневую папку, рядом с .exe, а не в папку библиотек.


Я уверен что есть способ указать путь до длл, чтобы избежать засорения корневой папки, но пробовал по разному и у меня не получилось (на дедике столкнулся с этой проблемой когда впервые загрузил туда ракбота). Хотя у меня на компе все работает в папке с либами.

Может кто из опытных форумчан шарит почему не подключаются dll из папки либ?

.dll видит, если б не нашло, была б другая ошибка:​


"[ML] (error) client.lua: C:\Program Files (x86)\GTA SA\moonloader\client.lua:1: module 'WebS' not found:
no field package.preload['WebS']
no file 'C:\Program Files (x86)\GTA SA\moonloader\lib\WebS.lua'
no file 'C:\Program Files (x86)\GTA SA\moonloader\lib\WebS\init.lua'
no file 'C:\Program Files (x86)\GTA SA\moonloader\WebS.lua'
no file 'C:\Program Files (x86)\GTA SA\moonloader\WebS\init.lua'
no file '.\WebS.lua'
no file 'C:\Program Files (x86)\GTA SA\moonloader\lib\WebS.luac'
no file 'C:\Program Files (x86)\GTA SA\moonloader\lib\WebS\init.luac'
no file 'C:\Program Files (x86)\GTA SA\moonloader\WebS.luac'
no file 'C:\Program Files (x86)\GTA SA\moonloader\WebS\init.luac'
no file '.\WebS.luac'
no file 'C:\Program Files (x86)\GTA SA\moonloader\lib\WebS.dll'
stack traceback:
[C]: in function 'require'
C:\Program Files (x86)\GTA SA\moonloader\client.lua:1: in main chunk
[ML] (error) client.lua: Script died due to an error. (0BB15EB4)"

В этом случаи .dll в корневой папке GTA SA (возле gta_sa.exe)​