Pawn.RakNet

repository·omp·Indexed 41 days ago

https://github.com/katursis/pawn.raknet

A plugin for SA:MP 0.3.7 servers that enables deep inspection and manipulation of RakNet network traffic. It allows developers to intercept, modify, and filter incoming and outgoing packets and Remote Procedure Calls (RPCs), as well as inject custom packets and emulate network behavior.

Tokens
6K
Snippets
18
Records
26
Agent score
52%

What's inside Pawn.RakNet

  1. Overview of Pawn.RakNet

    omp

    Pawn.RakNet is a plugin for SA:MP 0.3.7 servers designed to capture and analyze RakNet traffic. It provides developers with the ability to intercept and manipulate network communication between the server and players.

    Key Capabilities:

    • Intercepting Traffic: Capture, modify, and filter incoming and outgoing packets and Remote Procedure Calls (RPCs).
    • Packet Injection: Send custom packets and RPCs directly to a player.
    • Emulation: Emulate incoming packets and RPCs from a player to simulate network behavior.
  2. Initialize NetworkBitStream

    omp

    The NetworkBitStream class is used to read and write native types as a string of bits for network transmission. You can initialize it in several ways:

    1. Default Constructor: Creates an empty bitstream.
    2. Pre-allocation: Specify an initial number of bytes to allocate to avoid multiple reallocations if the size is known.
    3. Wrapping existing data: Use this to cast an existing byte array (like a packet's data) into a bitstream for reading. Crucial: Set _copyData to false when reading from an existing buffer to avoid unnecessary copying. Do not use Write functions if _copyData is false, as it will write to unallocated memory.

    To wrap a packet for reading:

    RakNet::NetworkBitStream bs(packet->data, packet->length, false);
  3. Capture and analyze packets using IPacket

    omp

    You can capture specific packets by defining an IPacket function. This function is triggered when a packet with a specific ID is received. Inside the function, you can use BitStream functions to read the packet data into structured arrays or variables.

    In the example below, a PLAYER_SYNC packet (ID 207) is captured, and its data is read into an onFootData array of type PR_OnFootSync using BS_ReadOnFootSync.

    const PLAYER_SYNC = 207;
    
    IPacket:PLAYER_SYNC(playerid, BitStream:bs)
    {
      new onFootData[PR_OnFootSync];
    
      BS_IgnoreBits(bs, 8); // ignore packet id (uint8)
      BS_ReadOnFootSync(bs, onFootData);
    
      printf(
        "PLAYER_SYNC[%d]:\nlrKey %d \nudKey %d \nkeys %d \nposition %.2f %.2f %.2f \nquaternion %.2f %.2f %.2f %.2f \nhealth %d \narmour %d \nadditionalKey %d \nweaponId %d \nspecialAction %d \nvelocity %.2f %.2f %.2f \nsurfingOffsets %.2f %.2f %.2f \nsurfingVehicleId %d \nanimationId %d \nanimationFlags %d",
        playerid,
        onFootData[PR_lrKey],
        onFootData[PR_udKey],
        onFootData[PR_keys],
        onFootData[PR_position][0],
        onFootData[PR_position][1],
        onFootData[PR_position][2],
        onFootData[PR_quaternion][0],
        onFootData[PR_quaternion][1],
        onFootData[PR_quaternion][2],
        onFootData[PR_quaternion][3],
        onFootData[PR_health],
        onFootData[PR_armour],
        onFootData[PR_additionalKey],
        onFootData[PR_weaponId],
        onFootData[PR_specialAction],
        onFootData[PR_velocity][0],
        onFootData[PR_velocity][1],
        onFootData[PR_velocity][2],
        onFootData[PR_surfingOffsets][0],
        onFootData[PR_surfingOffsets][1],
        onFootData[PR_surfingOffsets][2],
        onFootData[PR_surfingVehicleId],
        onFootData[PR_animationId],
        onFootData[PR_animationFlags]
      );
    
      return 1;
    }
  4. SerializeOrthMatrix for orthogonal rotation matrices

    omp

    Use SerializeOrthMatrix to transmit a 3x3 orthogonal rotation matrix.

    To save bandwidth, this method converts the matrix into a quaternion and then uses SerializeNormQuat to transmit the rotation. On the receiving end, the quaternion is converted back into an orthogonal matrix.

    Requires 9 templateType parameters representing the matrix elements m00 through m22.

    // Writing an orthogonal matrix
    stream.SerializeOrthMatrix(true, m00, m01, m02, m10, m11, m12, m20, m21, m22);
    
    // Reading an orthogonal matrix
    stream.SerializeOrthMatrix(false, m00, m01, m02, m10, m11, m12, m20, m21, m22);
  5. Send packets and RPCs

    omp

    Pawn.RakNet provides methods to send data to players via both raw packets and Remote Procedure Calls (RPCs).

    Send a Packet

    Use PR_SendPacket() to send a BitStream to a specific player. You can define the priority, reliability, and ordering channel.

    Send an RPC

    Use PR_SendRPC() to send an RPC to a specific player. This requires an RPCIndex and allows setting priority, reliability, and ordering channel.

    Emulate Incoming Data

    For testing or simulation, you can use PR_EmulateIncomingPacket() or PR_EmulateIncomingRPC() to trigger incoming data events manually.

    cell PR_SendPacket(BitStream *bs, int player_id, PR_PacketPriority priority, PR_PacketReliability reliability, unsigned char ordering_channel);
    cell PR_SendRPC(BitStream *bs, int player_id, RPCIndex rpc_id, PR_PacketPriority priority, PR_PacketReliability reliability, unsigned char ordering_channel);
    cell PR_EmulateIncomingPacket(BitStream *bs, int player_id);
    cell PR_EmulateIncomingRPC(BitStream *bs, int player_id, RPCIndex rpc_id);
  6. Use Serialize methods for unified read/write operations

    omp

    The NetworkBitStream class provides Serialize methods that act as a unified interface for both reading and writing data. By passing a bool writeToBitstream flag, you can use the same code path for both serialization and deserialization, which helps ensure that the read and write logic remains perfectly synchronized.

    Supported serialization types include:

    • Delta: SerializeDelta (requires a lastValue to compare against).
    • Compressed: SerializeCompressed (lossy compression for floats/doubles).
    • Compressed Delta: SerializeCompressedDelta (combines compression and delta encoding).
    • Vectors: SerializeVector and SerializeNormVector.
    • Quaternions: SerializeNormQuat.
    • Matrices: SerializeOrthMatrix (orthogonal matrices).
    • Raw Data: Serialize (for byte arrays) and SerializeBits (for bit-level data).
    // Example of unified serialization logic
    void SyncData(NetworkBitStream& stream, bool isWriting, MyData& data, MyData& lastData) {
        // This single call handles both writing and reading based on 'isWriting'
        stream.SerializeDelta(isWriting, data, lastData);
    }
  7. Manage the Pawn.RakNet plugin lifecycle

    omp

    The Plugin class provides the primary interface for managing the lifecycle of the Pawn.RakNet plugin. Developers interacting with the plugin's core can use these methods to handle loading, unloading, and periodic processing.

    • OnLoad(): Called when the plugin is being loaded. Returns true if loading was successful.
    • OnUnload(): Called when the plugin is being unloaded.
    • OnProcessTick(): Called during every server tick for periodic processing.
    • Get(): Returns the singleton instance of the plugin.
    // Access the plugin instance
    Plugin &plugin = Plugin::Get();
    
    // Lifecycle methods are typically managed by the host application,
    // but are part of the public interface.
    plugin.OnLoad();
    plugin.OnProcessTick();
    plugin.OnUnload();
  8. Write primitive types to NetworkBitStream

    omp

    Use the following methods to write various data types into the bitstream. These methods handle the bit-level serialization:

    • Bits: writeBIT(bool data), Write0(), Write1()
    • Unsigned Integers: writeUINT8(T data), writeUINT16(T data), writeUINT32(T data), writeUINT64(T data)
    • Signed Integers: writeINT8(T data), writeINT16(T data), writeINT32(T data), writeINT64(T data)
    • Floating Point: writeFLOAT(float data), writeDOUBLE(double data)
    • Vectors: writeVEC2(Vector2 data), writeVEC3(Vector3 data), writeVEC4(Vector4 data)
    • Strings: writeDynStr8(StringView data), writeDynStr16(StringView data), writeDynStr32(StringView data) (prefixed with length)
    • Arrays: writeArray(Span<T> data) or writeArray(StaticArray<T, S> data)
  9. Read and Write values to BitStreams

    omp

    Use BS_WriteValue and BS_ReadValue to handle various data types within a BitStream. These functions typically take a parameter list containing the type and the value to be processed.

    cell BS_WriteValue(cell *params);
    cell BS_ReadValue(cell *params);
  10. SerializeCompressed for lossy floating-point compression

    omp

    Use SerializeCompressed to reduce the size of floating-point numbers. This is a lossy operation.

    • Float: Compressed into a 2-byte unsigned short. The value must be in the range [-1.0, 1.0]. Values outside this range are clamped.
    • Double: Compressed into a 4-byte unsigned long. The value must be in the range [-1.0, 1.0]. Values outside this range are clamped.
    • Bool: Treated as a standard bit write.

    For non-floating point types, this is lossless but only provides benefits if the type uses less than half its range.

    // Compress a float between -1 and 1 into 2 bytes
    float myValue = 0.5f;
    stream.SerializeCompressed(true, myValue);
    
    // Decompress
    stream.SerializeCompressed(false, myValue);
  11. Configure and inspect Custom RPCs

    omp

    Pawn.RakNet allows for the definition and checking of custom Remote Procedure Calls (RPCs). Use these methods to manage which RPC IDs are treated as custom.

    • SetCustomRPC(RPCIndex rpc_id): Marks a specific RPC ID as a custom RPC.
    • IsCustomRPC(RPCIndex rpc_id): Checks if a given RPC ID is registered as a custom RPC.
    // Example of setting and checking a custom RPC
    Plugin::Get().SetCustomRPC(MY_CUSTOM_RPC_ID);
    
    if (Plugin::Get().IsCustomRPC(some_id)) {
        // Handle custom RPC logic
    }
  12. Use compressed serialization for vectors and quaternions

    omp

    For bandwidth efficiency, NetworkBitStream provides specialized compressed methods for common geometric types:

    Compressed Vectors

    • writeCompressedVEC3(Vector3 data) / readCompressedVEC3(Vector3& data): Specialized compression for 3D vectors.
    • writeNormVector(x, y, z) / readNormVector(x, y, z): Writes a normalized 3D vector using at most 4 bytes + 3 bits. Accurate to 1/32767.5.
    • writeVector(x, y, z) / readVector(x, y, z): Writes a vector using 10 bytes instead of 12. Loses ~3/10ths accuracy.

    Compressed Quaternions and Matrices

    • writeGTAQuat(GTAQuat data) / readGTAQuat(GTAQuat& data): Uses GTAQuat format.
    • writeNormQuat(w, x, y, z) / readNormQuat(w, x, y, z): Writes a normalized quaternion in 6 bytes + 4 bits. Slightly lossy.
    • writeOrthMatrix(...) / readOrthMatrix(...): Serializes an orthogonal matrix by writing 3 components of a quaternion in 2 bytes each (6 bytes total instead of 36). Lossy but renormalized.