Информация Гайд Работа с 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 или в этой теме.

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


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

KarimCobain

Известный
52
30
Хотел бы спросить, есть где достойный материал изучения на английском?
 

why ega

РП игрок
Автор темы
Модератор
2,522
2,184
Очень информативная тема, пару нюансов. Некоторые проекты создают свои битстримы. Например Arizona RP - 220 битстрим. Дополни пожалуйста тему что проекты могут сделать свои битстримы.
Это выражение не совсем корректно (возможно я сам ввел кого-то в заблуждение своей статьей). Если коротко, BitStream это лишь абстракция для удобной работы с данными. Говоря о том, что кто-то создает "свой битстрим", ты скорее всего имеешь ввиду кастомные ракнет пакеты, которые не предусмотрены сампом. Вскоре, возможно, сделаю небольшой гайд на тему ракнета
 

CoderKolaNedo

Участник
27
8
Это выражение не совсем корректно (возможно я сам ввел кого-то в заблуждение своей статьей). Если коротко, BitStream это лишь абстракция для удобной работы с данными. Говоря о том, что кто-то создает "свой битстрим", ты скорее всего имеешь ввиду кастомные ракнет пакеты, которые не предусмотрены сампом. Вскоре, возможно, сделаю небольшой гайд на тему ракнета
Я даже сам не понял про 220 битстрим. Мб и кино попытаюсь починить.
 

четыреста четыре

Известный
120
19
Это выражение не совсем корректно (возможно я сам ввел кого-то в заблуждение своей статьей). Если коротко, BitStream это лишь абстракция для удобной работы с данными. Говоря о том, что кто-то создает "свой битстрим", ты скорее всего имеешь ввиду кастомные ракнет пакеты, которые не предусмотрены сампом. Вскоре, возможно, сделаю небольшой гайд на тему ракнета
зачем возвращать переписанные значения? это же не самп евентс
 

why ega

РП игрок
Автор темы
Модератор
2,522
2,184
зачем возвращать переписанные значения?
верно подмечено, т.к. эта функция должна возвращать либо любое значение (пакет отправится), либо false (пакет не отправится). но в любом случае, возвращать ничего не нужно