Информация Гайд Работа с BitStream в LUA.

Доброго времени вечера.
В этом гайде я расскажу что такое BitStream и как с ним правильно работать.​



SAMP для обменна данными между сервером и игроком использует сетевой движок RakNet.
Передаваемые данные храняться в битовом потоке BitStream.
BitStream - штука (структура), которая хранит в себе даннные, в виде битов.
SAMP может отправлять на сервер пакеты и RPC. Пакеты отличаются от рпц тем, что сначала идет ID пакета, а потом уже его данные (если они есть), а у RPC сразу идут данные (опять же, если есть).

Мы можем перехватывать/отправлять/эмулировать как пакеты, так и рпц, этим мы сейчас и займемся.
Если вы что-то писали на языках по типу C++, то вы знаете, что в таких языках существуют типы данных, вот и при записи и чтении данных с битстрима, нам необходимо указывать тип, которые мы будем читать/вписывать.
Lua:
raknetBitStreamWriteBool(bs, value) -- вписывает true/false значение в битстрим
raknetBitStreamWriteInt8(bs, value) -- вписывает числовое значение, весом до 8 бит
raknetBitStreamWriteInt16(bs, value) -- вписываетчисловое значение, весом до 16 бит
raknetBitStreamWriteInt32(bs, value) -- вписывает числовое значение, весом до 32 бит
raknetBitStreamWriteFloat(bs,value) -- вписывает значение с плавающей точкой (дроби)
raknetBitStreamWriteString(bs, value) -- вписывает строку
raknetBitStreamEncodeString(bs, value) -- вписывает декодированную строку (используется допустим в dialog'ах)
raknetBitStreamWriteBuffer(bs, dest, size) -- вписывает buffer значение

Вы заметили, что у некоторых функций есть приставки 8, 16, 32 - это количество битов, которые нам надо записать или прочитать. Float всегда равен 32 битам (4 байта, в одном байте 8 битов, если вы не ходили на информатику). Также существуют другие типы, допустим UInt, который может записать уже число не от –2147483648 до 2147483647 как в int32, а от 0 до 4294967295. Другие функции мунлоадера, для взаимодействия с битстримом вы сможете посмотреть тут или тут.

Допустим нам надо отправить серверу информацию о том, что мы заспавнились, для этого нам нужен 52 RPC, смотрим его структуру и видим, что никаких данных она не содержит.
Lua:
{52, {}, description = "Отправляется при появлении.", name = "SPAWN"}
Чтобы отправить его нам понадобиться просто объявить объекты битстрима и отправить его.
Lua:
local bs = raknetNewBitStream() -- объявляем битстрим класс
raknetSendRpc(52, bs) -- 52 - id RPC, bs - наш битстрим, который мы объявили выше
raknetDeleteBitStream(bs) -- удаляем наш объект (в RakLua можно не удалять, т.к. в нем сборщик мусора делает это за вас, но в данном случае мы используем функции sampfuncs'а, а не RakLua

И так, отправить RPC мы смогли, теперь попробуем эмулировать рпц о изменение позиции нашего ТС. Смотрим его структуру.
Lua:
{159, {"vehicleId", "int16"}, {"positionX", "float"}, {"positionY", "float"}, {"positionZ", "float"}, name = "SETVEHICLEPOS"}
Мы видим, что в ней находяться значения int16 - айди машины и 3 float, обозначающие координаты x,y,z, объявляем битстрим и заполняем структуру.
Lua:
local bs = raknetNewBitStream()
raknetBitStreamWriteInt16(bs, 1023) -- изменяем позицию у машины с ID 1023
raknetBitStreamWriteFloat(bs, 0) -- posX
raknetBitStreamWriteFloat(bs, 0) -- posY
raknetBitStreamWriteFloat(bs, 0) -- posZ
raknetEmulRpcReceiveBitStream(159, bs) -- id RPC - 159
raknetDeleteBitStream(bs)

С отправкой и эмуляцией RPC мы разобрались, перейдем к пакетам.

Допустим, мы хотим отправить серверу фейковую позицию, для этого нам нужен PACKET_PLAYER_SYNC, вот его структура:
Lua:
{207, {"leftRightKeys", "int16"}, {"upDownKeys", "int16"}, {"keysData", "int16"}, {"positionX", "float"}, {"positionY", "float"}, {"positionZ", "float"}, {"quaternionW", "float"}, {"quaternionX", "float"}, {"quaternionY", "float"}, {"quaternionZ", "float"}, {"health", "int8"}, {"armor", "int8"}, {"weapon", "int8"}, {"specialAction", "int8"}, {"moveSpeedX", "float"}, {"moveSpeedY", "float"}, {"moveSpeedZ", "float"}, {"surfingOffsetsX", "float"}, {"surfingOffsetsY", "float"}, {"surfingOffsetsZ", "float"}, {"surfingVehicleId", "int16"}, {"animationId", "int16"}, {"animationFlags", "int16"}, name = "PACKET_PLAYER_SYNC"}
Видем в ней много данных, отвечающих за позицию, скорость, анимацию и так далее, но нам надо изменить только позицию, для этого мы будем устанавливать offset (сдвиг) записи, ведь как мы помним, в битстриме все данные храняться по порядку, т.е. нам нужно передвинуться на нужное количество битов, чтобы достать позицию. Оффсеты можно получить тут (не пинайте, не реклама)
Lua:
local bs = raknetNewBitStream()
raknetBitStreamWriteInt8(bs, 207) -- первым делом, мы вписываем ID пакета, который нам надо отправить
raknetBitStreamSetWriteOffset(bs, 6) -- вписываем полученный сдвиг (6 байтов)
-- указываем X, Y, Z координаты (0, 0, 0)
raknetBitStreamWriteFloat(bs, 0)
raknetBitStreamWriteFloat(bs, 0)
raknetBitStreamWriteFloat(bs, 0)
raknetSendBitStream((207, bs) -- как мы помним, PACKET_PLAYER_SYNC имеет 207 айдишник
raknetDeleteBitStream(bs)
Пакет отправили, теперь давайте эмулируем позицию машины, т.е. PACKET_VEHICLE_SYNC
Lua:
{200, {"playerId", "int16"}, {"vehicleId", "int16"}, {"leftRightKeys", "int16"}, {"upDownKeys", "int16"}, {"keysData", "int16"}, {"quaternionW", "float"}, {"quaternionX", "float"}, {"quaternionY", "float"}, {"quaternionZ", "float"}, {"positionX", "float"}, {"positionY", "float"}, {"positionZ", "float"}, {"moveSpeedX", "float"}, {"moveSpeedY", "float"}, {"moveSpeedZ", "float"}, {"vehicleHealth", "float"}, {"playerHealth", "int8"}, {"armor", "int8"}, {"weapon", "int8"}, {"siren", "int8"}, {"landingGearState", "int8"}, {"trailerId", "int16"}, {"trainSpeed", "float"}, name = "PACKET_VEHICLE_SYNC"}
Lua:
local bs = raknetNewBitStream()
raknetBitStreamSetWriteOffset(bs, 56) -- запись позиции начинается с 56 байта (не 48, т.к. во входящем пакете вех синхры после ид пакета идет ид игрока
raknetBitStreamWriteFloat(bs, 0)
raknetBitStreamWriteFloat(bs, 0)
raknetBitStreamWriteFloat(bs, 0)
raknetEmulPacketReceiveBitStream(200, bs) -- у пакета вех синхры 200 айди
raknetDeleteBitStream(bs)

С отправкой и эмуляциец закончили, теперь перейдем к перехвату ака хуку данных.
О том, как отлавливать RPC, вы можете почитать в этом гайде, я же расскажу про перехват пакетов.
Представим, что мы хотим узнать скокость всех каров в зоне стрима, для этого нам необходимо добавить событие, которое будет отлавливать входящий пакет PACKET_VEHICLE_SYNC, делается это так:
Lua:
function onReceivePacket(id, bs)
    if id == 200 then -- если id пакета = 200, то будем его насиловать
        -- eblya
    end
end
Хук есть, теперь нам нужно прочитать данные из пакета, чтобы после допустим вывести их на экран, для этого используем функции чтения данных из битстрима.
Lua:
function onReceivePacket(id, bs)  -- нам приходит id пакета и его битстрим
    if id == 200 then
        raknetBitStreamIgnoreBits(bs, 416) -- игнорируем 416 битов где 64 бита - это ID пакета и еще 64 бита - айди игрока, а дальше уже остальные данные, т.к. сампфункс не позволяет игнорить байты :(
        -- moveSpeed по X, Y, Z
        local xMov = raknetBitStreamReadFloat(bs)
        local yMov = raknetBitStreamReadFloat(bs)
        local zMov = raknetBitStreamReadFloat(bs)
        printStringNow("Speed: X " .. xMov .. " Y " .. yMov .. " Z " .. zMov, 1000)
    end
end
Теперь давайте по аналогии перехватим исходящий пакет PACKET_PLAYER_SYNC и изменим в нем данные.
Lua:
function onSendPacket(id, bs, priority, reliability, orderingChannel)
    if id == 207 then
        raknetBitStreamResetWritePointer(bs) -- т.к. пакеты к нам приходят уже заполненные, там необходимо обнулить указатель записи
        raknetBitStreamSetWriteOffset(bs, 6) -- переходим к записи позиции, то есть смещаемся на 6 байтов
        raknetBitStreamWriteFloat(bs, 0) -- X
        raknetBitStreamWriteFloat(bs, 0) -- Y
        raknetBitStreamWriteFloat(bs, 0) -- Z
        return {id, bs, priority, reliability, orderingChannel} -- возвращаем перезаписанные значения
    end
end
структуры всех пакетов можно узнать в moonloader/samp/synchronization.lua или в этой теме.

ну вот вроде и все, дописывайте, чем можно дополнить статью.


 
Последнее редактирование:

chapo

🫡 В армии с 17.10.2023. В ЛС НЕ ОТВЕЧАЮ
Друг
8,765
11,209
Почему в качестве примера структуры пакета ты показываешь свой массив (который есть только у тебя), а не например данные из samp.lua?
 

why ega

РП игрок
Автор темы
Модератор
2,539
2,224
Почему в качестве примера структуры пакета ты показываешь свой массив (который есть только у тебя), а не например данные из samp.lua?
потому-что он взят из оттуда, да, в нем нет проверки на сюрф, анимации и кейдата, но в данном случае оно и не нужно, а кому нужна будет структура пакета, то думаю смогут найти либо в самп луа, либо на гите, либо тут
 

chapo

🫡 В армии с 17.10.2023. В ЛС НЕ ОТВЕЧАЮ
Друг
8,765
11,209
потому-что он взят из оттуда, да, в нем нет проверки на сюрф, анимации и кейдата, но в данном случае оно и не нужно, а кому нужна будет структура пакета, то думаю смогут найти либо в самп луа, либо на гите, либо тут
человек откроет список ивентов самп луа и не поймет как читать например "vector3d" или "string8".
1668533824968.png
1668533843801.png
 
  • Нравится
  • Вау
Реакции: kru_tin и why ega

lorgon

Известный
657
266
Lua:
local xMov = raknetBitStreamReadFloat(bs)
local xMov = raknetBitStreamReadFloat(bs)
local xMov = raknetBitStreamReadFloat(bs)
У тебя везде xMove
 
  • Нравится
Реакции: why ega

why ega

РП игрок
Автор темы
Модератор
2,539
2,224

chapo

🫡 В армии с 17.10.2023. В ЛС НЕ ОТВЕЧАЮ
Друг
8,765
11,209
Но с другой стороны, в моем массиве расписан вектор
Так я про то что ты показал как брать битстрим на примере твоего массива, однако сам массив со всеми структурами ты не выложил
 
  • Нравится
Реакции: why ega

E E E E E E E

Участник
115
15
Доброго времени вечера.
В этом гайде я расскажу что такое BitStream и как с ним правильно работать.



SAMP для обменна данными между сервером и игроком использует сетевой движок RakNet.
Передаваемые данные храняться в битовом потоке BitStream.
BitStream - штука (структура), которая хранит в себе даннные, в виде битов.
SAMP может отправлять на сервер пакеты и RPC. Пакеты отличаются от рпц тем, что сначала идет ID пакета, а потом уже его данные (если они есть), а у RPC сразу идут данные (опять же, если есть).

Мы можем перехватывать/отправлять/эмулировать как пакеты, так и рпц, этим мы сейчас и займемся.
Если вы что-то писали на языках по типу C++, то вы знаете, что в таких языках существуют типы данных, вот и при записи и чтении данных с битстрима, нам необходимо указывать тип, которые мы будем читать/вписывать.
Lua:
raknetBitStreamWriteBool(bs, value) -- вписывает true/false значение в битстрим
raknetBitStreamWriteInt8(bs, value) -- вписывает числовое значение, весом до 8 бит
raknetBitStreamWriteInt16(bs, value) -- вписываетчисловое значение, весом до 16 бит
raknetBitStreamWriteInt32(bs, value) -- вписывает числовое значение, весом до 32 бит
raknetBitStreamWriteFloat(bs,value) -- вписывает значение с плавающей точкой (дроби)
raknetBitStreamWriteString(bs, value) -- вписывает строку
raknetBitStreamEncodeString(bs, value) -- вписывает декодированную строку (используется допустим в dialog'ах)
raknetBitStreamWriteBuffer(bs, dest, size) -- вписывает buffer значение

Вы заметили, что у некоторых функций есть приставки 8, 16, 32 - это количество битов, которые нам надо записать или прочитать. Float всегда равен 32 битам (4 байта, в одном байте 8 битов, если вы не ходили на информатику). Также существуют другие типы, допустим UInt, который может записать уже число не от –2147483648 до 2147483647 как в int32, а от 0 до 4294967295. Другие функции мунлоадера, для взаимодействия с битстримом вы сможете посмотреть тут или тут.

Допустим нам надо отправить серверу информацию о том, что мы заспавнились, для этого нам нужен 52 RPC, смотрим его структуру и видим, что никаких данных она не содержит.
Lua:
{52, {}, description = "Отправляется при появлении.", name = "SPAWN"}
Чтобы отправить его нам понадобиться просто объявить объекты битстрима и отправить его.
Lua:
local bs = raknetNewBitStream() -- объявляем битстрим класс
raknetSendRpc(52, bs) -- 52 - id RPC, bs - наш битстрим, который мы объявили выше
raknetDeleteBitStream(bs) -- удаляем наш объект (в RakLua можно не удалять, т.к. в нем сборщик мусора делает это за вас, но в данном случае мы используем функции sampfuncs'а, а не RakLua

И так, отправить RPC мы смогли, теперь попробуем эмулировать рпц о изменение позиции нашего ТС. Смотрим его структуру.
Lua:
{159, {"vehicleId", "int16"}, {"positionX", "float"}, {"positionY", "float"}, {"positionZ", "float"}, name = "SETVEHICLEPOS"}
Мы видим, что в ней находяться значения int16 - айди машины и 3 float, обозначающие координаты x,y,z, объявляем битстрим и заполняем структуру.
Lua:
local bs = raknetNewBitStream()
raknetBitStreamWriteInt16(bs, 1023) -- изменяем позицию у машины с ID 1023
raknetBitStreamWriteFloat(bs, 0) -- posX
raknetBitStreamWriteFloat(bs, 0) -- posY
raknetBitStreamWriteFloat(bs, 0) -- posZ
raknetEmulRpcReceiveBitStream(159, bs) -- id RPC - 159
raknetDeleteBitStream(bs)

С отправкой и эмуляцией RPC мы разобрались, перейдем к пакетам.

Допустим, мы хотим отправить серверу фейковую позицию, для этого нам нужен PACKET_PLAYER_SYNC, вот его структура:
Lua:
{207, {"leftRightKeys", "int16"}, {"upDownKeys", "int16"}, {"keysData", "int16"}, {"positionX", "float"}, {"positionY", "float"}, {"positionZ", "float"}, {"quaternionW", "float"}, {"quaternionX", "float"}, {"quaternionY", "float"}, {"quaternionZ", "float"}, {"health", "int8"}, {"armor", "int8"}, {"weapon", "int8"}, {"specialAction", "int8"}, {"moveSpeedX", "float"}, {"moveSpeedY", "float"}, {"moveSpeedZ", "float"}, {"surfingOffsetsX", "float"}, {"surfingOffsetsY", "float"}, {"surfingOffsetsZ", "float"}, {"surfingVehicleId", "int16"}, {"animationId", "int16"}, {"animationFlags", "int16"}, name = "PACKET_PLAYER_SYNC"}
Видем в ней много данных, отвечающих за позицию, скорость, анимацию и так далее, но нам надо изменить только позицию, для этого мы будем устанавливать offset (сдвиг) записи, ведь как мы помним, в битстриме все данные храняться по порядку, т.е. нам нужно передвинуться на нужное количество битов, чтобы достать позицию. Оффсеты можно получить тут (не пинайте, не реклама)
Lua:
local bs = raknetNewBitStream()
raknetBitStreamWriteInt8(bs, 207) -- первым делом, мы вписываем ID пакета, который нам надо отправить
raknetBitStreamSetWriteOffset(bs, 6) -- вписываем полученный сдвиг (6 байтов)
-- указываем X, Y, Z координаты (0, 0, 0)
raknetBitStreamWriteFloat(bs, 0)
raknetBitStreamWriteFloat(bs, 0)
raknetBitStreamWriteFloat(bs, 0)
raknetEmulPacketReceiveBitStream(207, bs) -- как мы помним, PACKET_PLAYER_SYNC имеет 207 айдишник
raknetDeleteBitStream(bs)
Пакет отправили, теперь давайте эмулируем позицию машины, т.е. PACKET_VEHICLE_SYNC
Lua:
{200, {"playerId", "int16"}, {"vehicleId", "int16"}, {"leftRightKeys", "int16"}, {"upDownKeys", "int16"}, {"keysData", "int16"}, {"quaternionW", "float"}, {"quaternionX", "float"}, {"quaternionY", "float"}, {"quaternionZ", "float"}, {"positionX", "float"}, {"positionY", "float"}, {"positionZ", "float"}, {"moveSpeedX", "float"}, {"moveSpeedY", "float"}, {"moveSpeedZ", "float"}, {"vehicleHealth", "float"}, {"playerHealth", "int8"}, {"armor", "int8"}, {"weapon", "int8"}, {"siren", "int8"}, {"landingGearState", "int8"}, {"trailerId", "int16"}, {"trainSpeed", "float"}, name = "PACKET_VEHICLE_SYNC"}
Lua:
local bs = raknetNewBitStream()
raknetBitStreamSetWriteOffset(bs, 56) -- запись позиции начинается с 56 байта (не 48, т.к. во входящем пакете ве синхры после ид пакета идет ид игрока
raknetBitStreamWriteFloat(bs, 0)
raknetBitStreamWriteFloat(bs, 0)
raknetBitStreamWriteFloat(bs, 0)
raknetEmulPacketReceiveBitStream(200, bs) -- у пакета вех синхры 200 айди
raknetDeleteBitStream(bs)

С отправкой и эмуляциец закончили, теперь перейдем к перехвату ака хуку данных.
О том, как отлавливать RPC, вы можете почитать в этом гайде, я же расскажу про перехват пакетов.
Представим, что мы хотим узнать скокость всех каров в зоне стрима, для этого нам необходимо добавить событие, которое будет отлавливать входящий пакет PACKET_VEHICLE_SYNC, делается это так:
Lua:
function onReceivePacket(id, bs)
    if id == 200 then -- если id пакета = 200, то будем его насиловать
        -- eblya
    end
end
Хук есть, теперь нам нужно прочитать данные из пакета, чтобы после допустим вывести их на экран, для этого используем функции чтения данных из битстрима.
Lua:
function onReceivePacket(id, bs)  -- нам приходит id пакета и его битстрим
    if id == 200 then
        raknetBitStreamIgnoreBits(bs, 416) -- игнорируем 416 битов где 64 бита - это ID пакета и еще 64 бита - айди игрока, а дальше уже остальные данные, т.к. сампфункс не позволяет игнорить байты :(
        -- moveSpeed по X, Y, Z
        local xMov = raknetBitStreamReadFloat(bs)
        local yMov = raknetBitStreamReadFloat(bs)
        local zMov = raknetBitStreamReadFloat(bs)
        printStringNow("Speed: X " .. xMov .. " Y " .. yMov .. " Z " .. zMov, 1000)
    end
end
Теперь давайте по аналогии перехватим исходящий пакет PACKET_PLAYER_SYNC и изменим в нем данные.
Lua:
function onSendPacket(id, bs, priority, reliability, orderingChannel)
    if id == 207 then
        raknetBitStreamResetWritePointer(bs) -- т.к. пакеты к нам приходят уже заполненные, там необходимо обнулить указатель записи
        raknetBitStreamSetWriteOffset(bs, 6) -- переходим к записи позиции, то есть смещаемся на 6 байтов
        raknetBitStreamWriteFloat(bs, 0) -- X
        raknetBitStreamWriteFloat(bs, 0) -- Y
        raknetBitStreamWriteFloat(bs, 0) -- Z
        return {id, bs, priority, reliability, orderingChannel} -- возвращаем перезаписанные значения
    end
end
структуры всех пакетов можно узнать в moonloader/samp/synchronization.lua или в этой теме.

ну вот вроде и все, дописывайте, чем можно дополнить статью.
а как изменить скорость или ещё чёто изменить в PACKET_PLAYER_SYNC?
 

why ega

РП игрок
Автор темы
Модератор
2,539
2,224
а как изменить скорость или ещё чёто изменить в PACKET_PLAYER_SYNC?
берем структуру этого пакета:
1669978836711.png

первое значение - тип данных, второй - название, 3 - оффсет.
если ты хочешь отправить серверу скорость, то тебе нужен moveSpeed, оффсет которого 38.
Lua:
local bs = raknetNewBitStream() -- объявляем битстрим как в гайде
raknetBitStreamWriteInt8(bs, 207) -- записываем ид пакета опять же как в гайде
raknetBitStreamSetWriteOffset(bs, 38) -- вписываем оффсет в виде байтов КАК В ГАЙДЕ 0_0
-- указываем скорость по X, Y, Z  (1, 0, -1)
raknetBitStreamWriteFloat(bs, 1) -- заполняем скорость по X
raknetBitStreamWriteFloat(bs, 0) -- заполняем скорость по Y
raknetBitStreamWriteFloat(bs, -1) -- заполняем скорость по Z
raknetSendBitStream(bs) -- отправляем пакет
raknetDeleteBitStream(bs) -- удаляем созданный битстрим
 
  • Нравится
Реакции: kru_tin

E E E E E E E

Участник
115
15
берем структуру этого пакета:
Посмотреть вложение 180076
первое значение - тип данных, второй - название, 3 - оффсет.
если ты хочешь отправить серверу скорость, то тебе нужен moveSpeed, оффсет которого 38.
Lua:
local bs = raknetNewBitStream() -- объявляем битстрим как в гайде
raknetBitStreamWriteInt8(bs, 207) -- записываем ид пакета опять же как в гайде
raknetBitStreamSetWriteOffset(bs, 38) -- вписываем оффсет в виде байтов КАК В ГАЙДЕ 0_0
-- указываем скорость по X, Y, Z  (1, 0, -1)
raknetBitStreamWriteFloat(bs, 1) -- заполняем скорость по X
raknetBitStreamWriteFloat(bs, 0) -- заполняем скорость по Y
raknetBitStreamWriteFloat(bs, -1) -- заполняем скорость по Z
raknetSendBitStream(bs) -- отправляем пакет
raknetDeleteBitStream(bs) -- удаляем созданный битстрим
а где брать структуры? в каком они файле?
 

why ega

РП игрок
Автор темы
Модератор
2,539
2,224
структуры всех пакетов можно узнать в moonloader/samp/synchronization.lua или в этой теме.
а где брать структуры? в каком они файле?
 

pugna

Участник
112
7
Привет, мне сказали что это можно решить с помощью rpc:
Есть 3д тексты, но их можно убрать в игре. Через 30 секунд они появляются(В ИГРЕ). Но когда пишу !labels, новые 3д тексты не появляются.
Как это решить?
 
  • Вау
Реакции: why ega