Feature/plugin api experimental (#12)

* added a null check to fix crash, expose internal latency value (its buggy)

* fix latency calculations

* sending packets from c#

* world save event, move shutdown def, move called location of shutdown, expose FourKit.FireEvent

* add docs

---------

Co-authored-by: sylvessa <225480449+sylvessa@users.noreply.github.com>
This commit is contained in:
DrPerkyLegit 2026-04-05 22:21:22 -04:00 committed by GitHub
parent 682989c8f1
commit 21b5accc69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1420 additions and 20 deletions

View file

@ -572,7 +572,7 @@ EXTRACT_PACKAGE = NO
# included in the documentation.
# The default value is: NO.
EXTRACT_STATIC = NO
EXTRACT_STATIC = YES
# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
# locally in source files will be included in the documentation. If set to NO,

View file

@ -350,7 +350,7 @@ void PlayerConnection::handleMovePlayer(shared_ptr<MovePlayerPacket> packet)
// Anti-cheat: reject movement packets that exceed server-authoritative bounds.
double velocitySq = player->xd * player->xd + player->yd * player->yd + player->zd * player->zd;
double maxAllowedSq = kMoveBaseAllowanceSq + (velocitySq * kMoveVelocityAllowanceScale);
if (player->isAllowedToFly() || player->gameMode->isCreative())
if (player->isAllowedToFly() || (player->gameMode != nullptr && player->gameMode->isCreative()))
{
// Creative / flight-allowed players can move farther legitimately per tick.
maxAllowedSq *= 1.5;
@ -1717,8 +1717,13 @@ void PlayerConnection::handleKeepAlive(shared_ptr<KeepAlivePacket> packet)
{
if (packet->id == lastKeepAliveId)
{
int time = static_cast<int>(System::nanoTime() / 1000000 - lastKeepAliveTime);
player->latency = (player->latency * 3 + time) / 4;
int64_t now = (System::nanoTime() / 1000000);
if (lastKeepAliveTime == 0) lastKeepAliveTime = now;
int64_t delta = static_cast<int64_t>(now - lastKeepAliveTime);
player->latency = (player->latency * 3 + delta) / 4;
lastKeepAliveTime = now;
}
}

View file

@ -38,6 +38,7 @@ public class Player : HumanEntity, OfflinePlayer, CommandSender
SetNameInternal(name);
IsOnline = true;
_playerInventory._holder = this;
_connection = new PlayerConnection(this);
}
/// <inheritdoc/>
@ -55,6 +56,13 @@ public class Player : HumanEntity, OfflinePlayer, CommandSender
return true;
}
/// <summary>
/// <b>Experimental.</b> Gets the player's <see cref="PlayerConnection"/>, which can be used
/// to send raw packet data directly to the client.
/// </summary>
/// <returns>The player's connection.</returns>
public PlayerConnection getConnection() => _connection;
/// <inheritdoc/>
public Player? getPlayer() => IsOnline ? this : null;
@ -86,10 +94,32 @@ public class Player : HumanEntity, OfflinePlayer, CommandSender
public new Guid getUniqueId() => _playerUniqueId;
/// <summary>
/// <b>Experimental.</b> Gets the raw online XUID (Xbox User ID) for this player.
/// The online XUID is used for guests.
/// </summary>
/// <returns>The raw online XUID value.</returns>
public ulong getRawOnlineXUID() => _playerRawOnlineXUID;
/// <summary>
/// <b>Experimental.</b> Gets the raw offline XUID (Xbox User ID) for this player.
/// The offline XUID is the main XUID used by the client.
/// </summary>
/// <returns>The raw offline XUID value.</returns>
public ulong getRawOfflineXUID() => _playerRawOfflineXUID;
/// <summary>
/// Gets the player's estimated ping in milliseconds.
/// This value represents a weighted average of the response time to application layer ping packets sent. This value does not represent the network round trip time and as such may have less granularity and be impacted by other sources. For these reasons it should not be used for anti-cheat purposes. Its recommended use is only as a qualitative indicator of connection quality.
/// </summary>
/// <returns>The player's estimated ping in milliseconds.</returns>
public int getPing()
{
if (NativeBridge.GetPlayerLatency == null)
return -1;
return NativeBridge.GetPlayerLatency(getEntityId());
}
/// <summary>
/// Gets the player's current saturation level.

View file

@ -6,7 +6,7 @@ public class WorldEvent : Event
{
internal World? _world;
public WorldEvent(World? world) : base()
internal WorldEvent(World? world) : base()
{
_world = world;
}

View file

@ -0,0 +1,11 @@
namespace Minecraft.Server.FourKit.Event.World;
using Minecraft.Server.FourKit;
public class WorldSaveEvent : Event
{
internal WorldSaveEvent() : base()
{
}
}

View file

@ -1,9 +1,35 @@
using System;
using Minecraft.Server.FourKit.Entity;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace Minecraft.Server.FourKit.Experimental;
public class PlayerConnection
{
private Player _player;
internal PlayerConnection(Player player)
{
this._player = player;
}
/// <summary>
/// Sends raw packet data directly to the client over the player's connection.
/// The byte array must contain the complete packet including the packet ID as the first byte. The server automatically prepends the 4-byte big-endian size header before transmitting.
/// </summary>
/// <param name="data">The raw packet bytes to send, where <c>data[0]</c> is the packet ID.</param>
public void send(byte[] data)
{
var gh = GCHandle.Alloc(data, GCHandleType.Pinned);
try
{
NativeBridge.SendRaw?.Invoke(_player.getEntityId(), gh.AddrOfPinnedObject(), data.Length);
}
finally
{
gh.Free();
}
}
}

View file

@ -6,6 +6,9 @@ using Minecraft.Server.FourKit.Event;
using Minecraft.Server.FourKit.Inventory;
using Minecraft.Server.FourKit.Plugin;
/// <summary>
/// The main entry point for the FourKit plugin API.
/// </summary>
public static class FourKit
{
private static readonly EventDispatcher _dispatcher = new();

View file

@ -32,11 +32,11 @@ public static partial class FourKitHost
}
[UnmanagedCallersOnly]
public static void SetPlayerCallbacks(IntPtr kickPlayer, IntPtr banPlayer, IntPtr banPlayerIp, IntPtr getPlayerAddress)
public static void SetPlayerCallbacks(IntPtr kickPlayer, IntPtr banPlayer, IntPtr banPlayerIp, IntPtr getPlayerAddress, IntPtr getPlayerLatency)
{
try
{
NativeBridge.SetPlayerCallbacks(kickPlayer, banPlayer, banPlayerIp, getPlayerAddress);
NativeBridge.SetPlayerCallbacks(kickPlayer, banPlayer, banPlayerIp, getPlayerAddress, getPlayerLatency);
}
catch (Exception ex)
{
@ -44,6 +44,19 @@ public static partial class FourKitHost
}
}
[UnmanagedCallersOnly]
public static void SetPlayerConnectionCallbacks(IntPtr sendRaw)
{
try
{
NativeBridge.SetPlayerConnectionCallbacks(sendRaw);
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"SetPlayerConnectionCallbacks error: {ex}");
}
}
[UnmanagedCallersOnly]
public static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer, IntPtr getItemMeta, IntPtr setItemMeta, IntPtr setHeldItemSlot)
{

View file

@ -15,6 +15,19 @@ namespace Minecraft.Server.FourKit;
public static partial class FourKitHost
{
[UnmanagedCallersOnly]
public static void FireWorldSave()
{
try
{
FourKit.FireEvent(new WorldSaveEvent());
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireWorldSave error: {ex}");
}
}
[UnmanagedCallersOnly]
public static int FirePlayerPreLogin(IntPtr namePtr, int nameByteLen, IntPtr ipPtr, int ipByteLen, int port)
{
@ -34,7 +47,7 @@ public static partial class FourKitHost
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FirePlayerJoin error: {ex}");
ServerLog.Error("fourkit", $"FirePlayerPreLogin error: {ex}");
return 0;
}
}

View file

@ -87,6 +87,12 @@ internal static class NativeBridge
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetPlayerAddressDelegate(int entityId, IntPtr outIpBuf, int outIpBufSize, IntPtr outPort);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetPlayerLatencyDelegate(int entityId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeSendRawDelegate(int entityId, IntPtr dataBuf, int dataBufSize);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeGetPlayerInventoryDelegate(int entityId, IntPtr outBuffer);
@ -203,6 +209,8 @@ internal static class NativeBridge
internal static NativeBanPlayerDelegate? BanPlayer;
internal static NativeBanPlayerIpDelegate? BanPlayerIp;
internal static NativeGetPlayerAddressDelegate? GetPlayerAddress;
internal static NativeGetPlayerLatencyDelegate? GetPlayerLatency;
internal static NativeSendRawDelegate? SendRaw;
internal static NativeGetPlayerInventoryDelegate? GetPlayerInventory;
internal static NativeSetPlayerInventorySlotDelegate? SetPlayerInventorySlot;
internal static NativeGetContainerContentsDelegate? GetContainerContents;
@ -264,12 +272,18 @@ internal static class NativeBridge
DropItem = Marshal.GetDelegateForFunctionPointer<NativeDropItemDelegate>(dropItem);
}
internal static void SetPlayerCallbacks(IntPtr kickPlayer, IntPtr banPlayer, IntPtr banPlayerIp, IntPtr getPlayerAddress)
internal static void SetPlayerCallbacks(IntPtr kickPlayer, IntPtr banPlayer, IntPtr banPlayerIp, IntPtr getPlayerAddress, IntPtr getPlayerLatency)
{
KickPlayer = Marshal.GetDelegateForFunctionPointer<NativeKickPlayerDelegate>(kickPlayer);
BanPlayer = Marshal.GetDelegateForFunctionPointer<NativeBanPlayerDelegate>(banPlayer);
BanPlayerIp = Marshal.GetDelegateForFunctionPointer<NativeBanPlayerIpDelegate>(banPlayerIp);
GetPlayerAddress = Marshal.GetDelegateForFunctionPointer<NativeGetPlayerAddressDelegate>(getPlayerAddress);
GetPlayerLatency = Marshal.GetDelegateForFunctionPointer<NativeGetPlayerLatencyDelegate>(getPlayerLatency);
}
internal static void SetPlayerConnectionCallbacks(IntPtr sendRaw)
{
SendRaw = Marshal.GetDelegateForFunctionPointer<NativeSendRawDelegate>(sendRaw);
}
internal static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer, IntPtr getItemMeta, IntPtr setItemMeta, IntPtr setHeldItemSlot)

File diff suppressed because it is too large Load diff

View file

@ -17,14 +17,15 @@ namespace FourKitBridge
{
typedef void(__stdcall *fn_initialize)();
typedef int(__stdcall* fn_fire_player_prelogin)(const char* nameUtf8, int nameByteLen, const char* ipUtf8, int ipByteLen, int port);
typedef void(__stdcall *fn_shutdown)();
typedef void(__stdcall *fn_fire_world_save)();
typedef int(__stdcall *fn_fire_player_prelogin)(const char* nameUtf8, int nameByteLen, const char* ipUtf8, int ipByteLen, int port);
typedef int(__stdcall *fn_fire_player_login)(const char* nameUtf8, int nameByteLen, const char* ipUtf8, int ipByteLen, int port, int type, unsigned long long* offlineXUID, unsigned long long* onlineXUID);
typedef void(__stdcall *fn_fire_player_join)(int entityId, const char *nameUtf8, int nameByteLen, const char *uuidUtf8, int uuidByteLen, unsigned long long offlineXUID, unsigned long long onlineXUID);
typedef void(__stdcall *fn_fire_player_quit)(int entityId);
typedef int(__stdcall *fn_fire_player_kick)(int entityId, int disconnectReason,
const char *reasonUtf8, int reasonByteLen,
char *outBuf, int outBufSize, int *outLen);
typedef void(__stdcall *fn_shutdown)();
typedef int(__stdcall *fn_fire_player_move)(int entityId,
double fromX, double fromY, double fromZ,
double toX, double toY, double toZ,
@ -56,7 +57,8 @@ typedef int(__stdcall *fn_fire_player_death)(int entityId,
const char *deathMsgUtf8, int deathMsgByteLen, int exp,
char *outMsgBuf, int outMsgBufSize, int *outMsgLen, int *outKeepInventory,
int *outNewExp, int *outNewLevel, int *outKeepLevel);
typedef void(__stdcall *fn_set_player_callbacks)(void *kickPlayer, void *banPlayer, void *banPlayerIp, void *getPlayerAddress);
typedef void(__stdcall *fn_set_player_callbacks)(void *kickPlayer, void *banPlayer, void *banPlayerIp, void *getPlayerAddress, void *getPlayerLatency);
typedef void(__stdcall *fn_set_player_connection_callbacks)(void *sendRaw);
typedef long long(__stdcall *fn_fire_player_drop_item)(int entityId,
int itemId, int itemCount, int itemAux,
int *outItemId, int *outItemCount, int *outItemAux);
@ -103,13 +105,14 @@ struct OpenContainerInfo
static std::unordered_map<int, OpenContainerInfo> s_openContainerInfo;
static fn_initialize s_managedInit = nullptr;
static fn_shutdown s_managedShutdown = nullptr;
static fn_fire_world_save s_managedFireWorldSave = nullptr;
static fn_fire_player_prelogin s_managedFirePreLogin = nullptr;
static fn_fire_player_login s_managedFireLogin = nullptr;
static fn_fire_player_join s_managedFireJoin = nullptr;
static fn_update_entity_id s_managedUpdateEntityId = nullptr;
static fn_fire_player_quit s_managedFireQuit = nullptr;
static fn_fire_player_kick s_managedFireKick = nullptr;
static fn_shutdown s_managedShutdown = nullptr;
static fn_fire_player_move s_managedFireMove = nullptr;
static fn_set_native_callbacks s_managedSetCallbacks = nullptr;
static fn_set_world_callbacks s_managedSetWorldCallbacks = nullptr;
@ -122,6 +125,7 @@ static fn_fire_sign_change s_managedFireSignChange = nullptr;
static fn_fire_entity_death s_managedFireEntityDeath = nullptr;
static fn_fire_player_death s_managedFirePlayerDeath = nullptr;
static fn_set_player_callbacks s_managedSetPlayerCallbacks = nullptr;
static fn_set_player_connection_callbacks s_managedSetPlayerConnectionCallbacks = nullptr;
static fn_fire_player_drop_item s_managedFirePlayerDropItem = nullptr;
static fn_set_inventory_callbacks s_managedSetInventoryCallbacks = nullptr;
static fn_fire_player_interact s_managedFirePlayerInteract = nullptr;
@ -171,13 +175,14 @@ void Initialize()
struct { const wchar_t *name; void **target; } entries[] = {
{L"Initialize", (void **)&s_managedInit},
{L"FirePlayerPreLogin", (void**)&s_managedFirePreLogin},
{L"Shutdown", (void **)&s_managedShutdown},
{L"FireWorldSave", (void **)&s_managedFireWorldSave},
{L"FirePlayerPreLogin", (void **)&s_managedFirePreLogin},
{L"FirePlayerLogin", (void **)&s_managedFireLogin},
{L"FirePlayerJoin", (void **)&s_managedFireJoin},
{L"FirePlayerQuit", (void **)&s_managedFireQuit},
{L"FirePlayerKick", (void **)&s_managedFireKick},
{L"FirePlayerMove", (void **)&s_managedFireMove},
{L"Shutdown", (void **)&s_managedShutdown},
{L"SetNativeCallbacks", (void **)&s_managedSetCallbacks},
{L"SetWorldCallbacks", (void **)&s_managedSetWorldCallbacks},
{L"UpdatePlayerEntityId", (void **)&s_managedUpdateEntityId},
@ -189,7 +194,8 @@ void Initialize()
{L"FireSignChange", (void **)&s_managedFireSignChange},
{L"FireEntityDeath", (void **)&s_managedFireEntityDeath},
{L"FirePlayerDeath", (void **)&s_managedFirePlayerDeath},
{L"SetPlayerCallbacks", (void **)&s_managedSetPlayerCallbacks},
{L"SetPlayerCallbacks", (void**)&s_managedSetPlayerCallbacks},
{L"SetPlayerConnectionCallbacks", (void **)&s_managedSetPlayerConnectionCallbacks},
{L"FirePlayerDropItem", (void **)&s_managedFirePlayerDropItem},
{L"SetInventoryCallbacks", (void **)&s_managedSetInventoryCallbacks},
{L"FirePlayerInteract", (void **)&s_managedFirePlayerInteract},
@ -257,7 +263,11 @@ void Initialize()
(void *)&NativeKickPlayer,
(void *)&NativeBanPlayer,
(void *)&NativeBanPlayerIp,
(void *)&NativeGetPlayerAddress);
(void *)&NativeGetPlayerAddress,
(void *)&NativeGetPlayerLatency);
s_managedSetPlayerConnectionCallbacks(
(void*)&NativeSendRaw);
s_managedSetInventoryCallbacks(
(void *)&NativeGetPlayerInventory,
@ -314,6 +324,18 @@ void Shutdown()
LogInfo("fourkit", "FourKit shut down.");
}
void FireWorldSave()
{
if (!s_initialized || !s_managedFireWorldSave)
{
return;
}
s_managedFireWorldSave();
LogDebugf("fourkit", "Fired WorldSave");
}
bool FirePlayerPreLogin(const std::wstring& name, const std::string& ip, int port)
{
if (!s_initialized || !s_managedFirePreLogin)

View file

@ -5,6 +5,7 @@ namespace FourKitBridge
{
void Initialize();
void Shutdown();
void FireWorldSave();
bool FirePlayerPreLogin(const std::wstring& name, const std::string& ip, int port);
bool FirePlayerLogin(const std::wstring& name, const std::string& ip, int port, int type, unsigned long long* onlineXUID, unsigned long long* offlineXUID);
void FirePlayerJoin(int entityId, const std::wstring& name, const std::wstring& uuid, unsigned long long onlineXUID, unsigned long long offlineXUID);

View file

@ -612,6 +612,25 @@ int __cdecl NativeGetPlayerAddress(int entityId, char *outIpBuf, int outIpBufSiz
return 1;
}
int __cdecl NativeGetPlayerLatency(int entityId)
{
auto player = FindPlayer(entityId);
if (!player) return -1;
return player->latency;
}
int __cdecl NativeSendRaw(int entityId, unsigned char *bufferData, int bufferSize)
{
auto player = FindPlayer(entityId);
if (!player) return -1;
if (!player->connection || !player->connection->connection)
return -1;
player->connection->connection->send(bufferData, bufferSize);
}
void WriteInventoryItemData(std::shared_ptr<ItemInstance> item, int index, int* outBuffer) {
if (item) {
//ItemFlags Key:

View file

@ -34,7 +34,11 @@ namespace FourKitBridge
void __cdecl NativeKickPlayer(int entityId, int reason);
int __cdecl NativeBanPlayer(int entityId, const char *reasonUtf8, int reasonByteLen);
int __cdecl NativeBanPlayerIp(int entityId, const char *reasonUtf8, int reasonByteLen);
int __cdecl NativeGetPlayerAddress(int entityId, char *outIpBuf, int outIpBufSize, int *outPort);
int __cdecl NativeGetPlayerAddress(int entityId, char* outIpBuf, int outIpBufSize, int* outPort);
int __cdecl NativeGetPlayerLatency(int entityId);
//plr connection
int __cdecl NativeSendRaw(int entityId, unsigned char* dataBuf, int dataBufSize);
// inv
void __cdecl NativeGetPlayerInventory(int entityId, int *outData);

View file

@ -678,6 +678,7 @@ int main(int argc, char **argv)
{
LogWorldIO("requesting autosave");
app.SetXuiServerAction(kServerActionPad, eXuiServerAction_AutoSaveGame);
FourKitBridge::FireWorldSave();
autosaveRequested = true;
}
nextAutosaveTick = now + autosaveIntervalMs;
@ -688,6 +689,8 @@ int main(int argc, char **argv)
serverCli.Stop();
app.m_bShutdown = true;
FourKitBridge::Shutdown(); //close out the translation layer early for plugin shutdown
LogInfof("shutdown", "Dedicated server stopped");
MinecraftServer *server = MinecraftServer::getInstance();
if (server != NULL)
@ -721,7 +724,6 @@ int main(int argc, char **argv)
}
LogInfof("shutdown", "Cleaning up and exiting.");
FourKitBridge::Shutdown();
WinsockNetLayer::Shutdown();
LogDebugf("shutdown", "Network layer shutdown complete.");
g_NetworkManager.Terminate();

View file

@ -1,3 +1,4 @@
#include "Connection.h"
#include "stdafx.h"
#include "InputOutputStream.h"
#include "Socket.h"
@ -32,6 +33,7 @@ void Connection::_init()
disconnectReason = DisconnectPacket::eDisconnect_None;
noInputTicks = 0;
estimatedRemaining = 0;
estimatedRemainingRaw = 0;
fakeLag = 0;
slowWriteDelay = 50;
@ -145,6 +147,23 @@ void Connection::setListener(PacketListener *packetListener)
this->packetListener = packetListener;
}
void Connection::send(unsigned char* buffer, int size)
{
if (quitting) return;
MemSect(15);
// 4J Jev, synchronized (&writeLock)
EnterCriticalSection(&writeLock);
estimatedRemainingRaw += size;
outgoingRaw.push(std::make_pair(buffer, size));
// 4J Jev, end synchronized.
LeaveCriticalSection(&writeLock);
MemSect(0);
}
void Connection::send(shared_ptr<Packet> packet)
{
if (quitting) return;
@ -232,6 +251,32 @@ bool Connection::writeTick()
didSomething = true;
}
if (!outgoingRaw.empty())
{
std::pair<unsigned char*, int> rawPacket;
EnterCriticalSection(&writeLock);
rawPacket = outgoingRaw.front();
outgoingRaw.pop();
estimatedRemainingRaw -= rawPacket.second;
LeaveCriticalSection(&writeLock);
for (int i = 0; i < rawPacket.second; i++) {
byteArrayDos->writeByte(rawPacket.first[i]);
}
// 4J Stu - Changed this so that rather than writing to the network stream through a buffered stream we want to:
// a) Only push whole "game" packets to QNet, rather than amalgamated chunks of data that may include many packets, and partial packets
// b) To be able to change the priority and queue of a packet if required
//sos->writeWithFlags( baos->buf, 0, baos->size(), 0 );
//baos->reset();
int value = rawPacket.first[0];
writeSizes[value] += rawPacket.second;
didSomething = true;
}
if ((slowWriteDelay-- <= 0) && !outgoing_slow.empty() && (fakeLag == 0 || System::currentTimeMillis() - outgoing_slow.front()->createTime >= fakeLag))
{
shared_ptr<Packet> packet;

View file

@ -54,6 +54,7 @@ private:
queue<shared_ptr<Packet> > incoming; // 4J - was using synchronizedList...
CRITICAL_SECTION incoming_cs; // ... now has this critical section
queue<std::pair<unsigned char*, int>> outgoingRaw; // 4J - was using synchronizedList - but don't think it is required as usage is wrapped in writeLock critical section
queue<shared_ptr<Packet> > outgoing; // 4J - was using synchronizedList - but don't think it is required as usage is wrapped in writeLock critical section
queue<shared_ptr<Packet> > outgoing_slow; // 4J - was using synchronizedList - but don't think it is required as usage is wrapped in writeLock critical section
@ -76,6 +77,7 @@ private:
int noInputTicks;
int estimatedRemaining;
int estimatedRemainingRaw;
int tickCount; // 4J Added
@ -99,6 +101,7 @@ public:
void setListener(PacketListener *packetListener);
void send(shared_ptr<Packet> packet);
void send(unsigned char *buffer, int size);
public:
void queueSend(shared_ptr<Packet> packet);