1. Введение
Данное руководство предназначено для разработчиков, создающих клиентские модификации для игры San Andreas Multiplayer (SA:MP) с использованием MoonLoader и sampfuncs. Оно описывает корректные методы работы с объектом BitStream — основным инструментом низкоуровневой сериализации сетевых данных в SA:MP.Все примеры основаны на официальных структурах пакетов и RPC, полученных из реверс-инжиниринга клиента и подтверждённых сообществом (Brunoo16, SA-MP Wiki, disassembly).
Важно: SA:MP использует кастомизированную версию RakNet 3.712. Стандартные идентификаторы RakNet не применяются. Все ID — внутренние и специфичны для SA:MP.
2. Основные понятия
2.1. Что такое BitStream?
BitStream — это буфер памяти фиксированного или динамического размера, предназначенный для последовательной записи и чтения типизированных данных. Каждая операция записи или чтения смещает внутренний указатель позиции.2.2. Типы сетевых сообщений
Тип | Описание | Содержимое BitStream |
|---|---|---|
Пакет (Packet) | Синхронизационное сообщение | [MessageID (1 байт)][Payload] |
RPC (Remote Procedure Call) | Событие или команда | [Payload] (ID передаётся отдельно) |
3. Типы данных и сериализация
Все значения должны быть записаны в том порядке и с тем типом, который ожидает получатель. Lua не имеет нативных целочисленных типов, поэтому вы обязаны вручную контролировать диапазон и знак.| C++ тип | Lua-функция записи | Размер | Диапазон |
|---|---|---|---|
| uint8_t | raknetBitStreamWriteUInt8 | 1 байт | 0–255 |
| int16_t | raknetBitStreamWriteInt16 | 2 байта | –32768–32767 |
| uint16_t | raknetBitStreamWriteUInt16 | 2 байта | 0–65535 |
| int32_t | raknetBitStreamWriteInt32 | 4 байта | –2147483648–2147483647 |
| uint32_t | raknetBitStreamWriteUInt32 | 4 байта | 0–4294967295 |
| float | raknetBitStreamWriteFloat | 4 байта | IEEE 754 single |
| char[] | raknetBitStreamWriteString | переменный | нуль-терминированная строка |
Предупреждение: Передача числа 50000 в raknetBitStreamWriteInt16 приведёт к неопределённому поведению, так как оно выходит за пределы диапазона int16_t.
4. Отправка исходящих RPC
Исходящие RPC отправляются функцией raknetSendRpc(id, bs). В BitStream передаётся только payload.Пример 1: GivePlayerWeapon (ID 22)
Спецификация:
C:
GivePlayerWeapon - ID: 22
Parameters: UINT32 dWeaponID, UINT32 dBullets
Код:
Lua:
local bs = raknetNewBitStream()
raknetBitStreamWriteUInt32(bs, 31) -- M4
raknetBitStreamWriteUInt32(bs, 500) -- боезапас
raknetSendRpc(22, bs)
raknetDeleteBitStream(bs)
Пример 2: SetPlayerHealth (ID 14)
Спецификация:
C:
SetPlayerHealth - ID: 14
Parameters: float health
Код:
Lua:
local bs = raknetNewBitStream()
raknetBitStreamWriteFloat(bs, 100.0) -- полное здоровье
raknetSendRpc(14, bs)
raknetDeleteBitStream(bs)
5. Эмуляция входящих RPC
Используется для симуляции получения сообщения от сервера: raknetEmulRpcReceiveBitStream(id, bs).Пример: Create3DTextLabel (ID 36)
Спецификация:
C:
Create3DTextLabel - ID: 36
Parameters:
UINT16 wLabelID,
UINT32 color,
float x, y, z,
float DrawDistance,
UINT8 testLOS,
UINT16 attachedPlayer,
UINT16 attachedVehicle,
CSTRING text[4096]
Код:
Lua:
local bs = raknetNewBitStream()
raknetBitStreamWriteUInt16(bs, 5001) -- ID метки
raknetBitStreamWriteUInt32(bs, 0xFF0000FF) -- красный цвет
raknetBitStreamWriteFloat(bs, 1500.0) -- X
raknetBitStreamWriteFloat(bs, -1500.0) -- Y
raknetBitStreamWriteFloat(bs, 15.0) -- Z
raknetBitStreamWriteFloat(bs, 20.0) -- дистанция отрисовки
raknetBitStreamWriteUInt8(bs, 1) -- проверка LOS
raknetBitStreamWriteUInt16(bs, 65535) -- не привязан к игроку
raknetBitStreamWriteUInt16(bs, 65535) -- не привязан к ТС
raknetBitStreamEncodeString(bs, "Тестовая метка")
raknetEmulRpcReceiveBitStream(36, bs)
raknetDeleteBitStream(bs)
6. Работа с пакетами
6.1. Структура пакета
Каждый пакет обязательно начинается с 1 байта — MessageID. Примеры корректных ID:| Название | ID |
|---|---|
| ID_ONFOOT_SYNC | 212 |
| ID_VEHICLE_SYNC | 219 |
| ID_PASSENGER_SYNC | 216 |
| ID_AIM_SYNC | 218 |
| ID_BULLET_SYNC | 224 |
6.2. Отправка пакета: ID_BULLET_SYNC (224)
Используется для синхронизации выстрелов.Структура (stBulletSync, 39 байт):
C:
BYTE bHitType; // 0 = игрок, 1 = ТС, 2 = объект...
short iHitID; // ID цели
float fBullFrom[3]; // позиция выстрела
float fBullTo[3]; // направление выстрела
float fBodyOffset[3]; // смещение по телу (если попал в игрока)
Код:
Lua:
local bs = raknetNewBitStream()
raknetBitStreamWriteUInt8(bs, 224) -- ID пакета
raknetBitStreamWriteUInt8(bs, 0) -- попал в игрока
raknetBitStreamWriteInt16(bs, 2) -- ID игрока-цели
-- Позиция выстрела
raknetBitStreamWriteFloat(bs, 1400.0)
raknetBitStreamWriteFloat(bs, -1400.0)
raknetBitStreamWriteFloat(bs, 20.0)
-- Направление (вектор)
raknetBitStreamWriteFloat(bs, 0.0)
raknetBitStreamWriteFloat(bs, 1.0)
raknetBitStreamWriteFloat(bs, 0.0)
-- Смещение по телу (грудь)
raknetBitStreamWriteFloat(bs, 0.0)
raknetBitStreamWriteFloat(bs, 0.3)
raknetBitStreamWriteFloat(bs, 0.0)
raknetSendPacket(bs)
raknetDeleteBitStream(bs)
7. Перехват и анализ пакетов
7.1. Перехват ID_PASSENGER_SYNC (211)
Используется для отслеживания позиций пассажиров.Пример: вывод ID транспорта и позиции
Lua:
function onReceivePacket(id, bs)
if id == 211 then
local vehicleID = raknetBitStreamReadUInt16(bs) -- +0
local seatID = raknetBitStreamReadUInt8(bs) -- +2
raknetBitStreamIgnoreBits(bs, 24) -- пропуск weapon/health/armor
raknetBitStreamIgnoreBits(bs, 48) -- пропуск клавиш
local x = raknetBitStreamReadFloat(bs) -- +12
local y = raknetBitStreamReadFloat(bs)
local z = raknetBitStreamReadFloat(bs)
print(string.format(
"Пассажир в ТС %d, место %d: (%.1f, %.1f, %.1f)",
vehicleID, seatID, x, y, z
))
end
end
8. Безопасность и лучшие практики
- Всегда сверяйтесь со спецификацией.
Источники: Brunoo16/samp-packet-list / https://cleo.fandom.com/ru/wiki/Структуры_SA:MP - Никогда не используйте SetWriteOffset на частично заполненном BitStream.
Это нарушает целостность данных. - Заполняйте все поля пакета.
Даже если вам нужны только координаты — запишите все 68 байт stOnFootData. - Управляйте памятью явно.
Всегда вызывайте raknetDeleteBitStream(bs). - Проверяйте диапазоны значений.
- Не отправляйте пакеты без ID.
Первый байт — всегда MessageID.
9. Справочник идентификаторов
Часто используемые RPC
| Название | ID | Направление |
|---|---|---|
| GivePlayerMoney | 18 | исходящий |
| SetPlayerHealth | 14 | исходящий |
| ShowDialog | 61 | входящий |
| Create3DTextLabel | 36 | входящий |
| SendSpawn | 52 | исходящий |
Часто используемые пакеты
| Название | ID | Структура |
|---|---|---|
| ID_ONFOOT_SYNC | 212 | stOnFootData (68 байт) |
| ID_VEHICLE_SYNC | 219 | stInCarData (63 байта) |
| ID_PASSENGER_SYNC | 211 | stPassengerData (24 байта) |
| ID_AIM_SYNC | 218 | stAimData (31 байт) |
| ID_BULLET_SYNC | 224 | stBulletSync (39 байт) |
Ниже прикрепил пару полезных файлов. А так же файлы которые вам необходимо постоянно смотреть.
Вложения
Последнее редактирование: