From 1eca5206f215f302834d1eb07dbcbcc3d43dc164 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Thu, 26 Apr 2018 22:22:51 +1000 Subject: [PATCH] Mostly port NetworkProcessor to C --- ClassicalSharp/ClassicalSharp.csproj | 1 - .../Network/NetworkProcessor.Helpers.cs | 135 ---------- ClassicalSharp/Network/NetworkProcessor.cs | 33 ++- ClassicalSharp/Network/Protocols/CPE.cs | 24 +- ClassicalSharp/Network/Protocols/Classic.cs | 20 +- ClassicalSharp/Network/Protocols/IProtocol.cs | 102 ++++++++ src/Client/Client.vcxproj | 1 + src/Client/Client.vcxproj.filters | 3 + src/Client/PacketHandlers.c | 19 +- src/Client/PacketHandlers.h | 21 ++ src/Client/Platform.h | 7 + src/Client/ServerConnection.c | 238 +++++++++++++++++- src/Client/ServerConnection.h | 94 +++---- src/Client/Stream.c | 27 +- src/Client/Stream.h | 2 + src/Client/WinPlatform.c | 57 +++++ 16 files changed, 554 insertions(+), 230 deletions(-) delete mode 100644 ClassicalSharp/Network/NetworkProcessor.Helpers.cs create mode 100644 src/Client/PacketHandlers.h diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj index 50421ed3c..6cb10e834 100644 --- a/ClassicalSharp/ClassicalSharp.csproj +++ b/ClassicalSharp/ClassicalSharp.csproj @@ -243,7 +243,6 @@ - diff --git a/ClassicalSharp/Network/NetworkProcessor.Helpers.cs b/ClassicalSharp/Network/NetworkProcessor.Helpers.cs deleted file mode 100644 index c72bd58ca..000000000 --- a/ClassicalSharp/Network/NetworkProcessor.Helpers.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 -using System; -using ClassicalSharp.Entities; -using OpenTK; -using OpenTK.Input; - -namespace ClassicalSharp.Network { - - public partial class NetworkProcessor : IServerConnection { - - internal bool addEntityHack = true; - internal byte[] needRemoveNames = new byte[256 >> 3]; - - public override void SendChat(string text) { - if (String.IsNullOrEmpty(text)) return; - - while (text.Length > Utils.StringLength) { - classic.WriteChat(text.Substring(0, Utils.StringLength), true); - SendPacket(); - text = text.Substring(Utils.StringLength); - } - classic.WriteChat(text, false); - SendPacket(); - } - - public override void SendPosition(Vector3 pos, float rotY, float headX) { - classic.WritePosition(pos, rotY, headX); - SendPacket(); - } - - public override void SendPlayerClick(MouseButton button, bool buttonDown, byte targetId, PickedPos pos) { - cpe.WritePlayerClick(button, buttonDown, targetId, pos); - SendPacket(); - } - - - internal void CheckName(byte id, ref string displayName, ref string skinName) { - displayName = Utils.RemoveEndPlus(displayName); - skinName = Utils.RemoveEndPlus(skinName); - skinName = Utils.StripColours(skinName); - - // Server is only allowed to change our own name colours. - if (id != EntityList.SelfID) return; - if (Utils.StripColours(displayName) != game.Username) - displayName = game.Username; - if (skinName == "") - skinName = game.Username; - } - - internal void AddEntity(byte id, string displayName, string skinName, bool readPosition) { - if (id != EntityList.SelfID) { - Entity oldEntity = game.Entities.List[id]; - if (oldEntity != null) game.Entities.RemoveEntity(id); - - game.Entities.List[id] = new NetPlayer(displayName, skinName, game); - game.EntityEvents.RaiseAdded(id); - } else { - game.LocalPlayer.Despawn(); - // Always reset the texture here, in case other network players are using the same skin as us. - // In that case, we don't want the fetching of new skin for us to delete the texture used by them. - game.LocalPlayer.ResetSkin(); - game.LocalPlayer.fetchedSkin = false; - - game.LocalPlayer.DisplayName = displayName; - game.LocalPlayer.SkinName = skinName; - game.LocalPlayer.UpdateName(); - } - - if (!readPosition) return; - classic.ReadAbsoluteLocation(id, false); - if (id != EntityList.SelfID) return; - - LocalPlayer p = game.LocalPlayer; - p.Spawn = p.Position; - p.SpawnRotY = p.HeadY; - p.SpawnHeadX = p.HeadX; - } - - internal void RemoveEntity(byte id) { - Entity entity = game.Entities.List[id]; - if (entity == null) return; - if (id != EntityList.SelfID) game.Entities.RemoveEntity(id); - - // See comment about some servers in HandleAddEntity - int mask = id >> 3, bit = 1 << (id & 0x7); - if (!addEntityHack || (needRemoveNames[mask] & bit) == 0) return; - - RemoveTablistEntry(id); - needRemoveNames[mask] &= (byte)~bit; - } - - internal void UpdateLocation(byte playerId, LocationUpdate update, bool interpolate) { - Entity entity = game.Entities.List[playerId]; - if (entity != null) { - entity.SetLocation(update, interpolate); - } - } - - - internal void AddTablistEntry(byte id, string playerName, string listName, - string groupName, byte groupRank) { - TabListEntry oldInfo = TabList.Entries[id]; - TabListEntry info = new TabListEntry(playerName, listName, groupName, groupRank); - TabList.Entries[id] = info; - - if (oldInfo != null) { - // Only redraw the tab list if something changed. - if (info.PlayerName != oldInfo.PlayerName || info.ListName != oldInfo.ListName || - info.Group != oldInfo.Group || info.GroupRank != oldInfo.GroupRank) { - game.EntityEvents.RaiseTabListEntryChanged(id); - } - } else { - game.EntityEvents.RaiseTabEntryAdded(id); - } - } - - internal void RemoveTablistEntry(byte id) { - game.EntityEvents.RaiseTabEntryRemoved(id); - TabList.Entries[id] = null; - } - - internal void DisableAddEntityHack() { - if (!addEntityHack) return; - addEntityHack = false; - - for (int id = 0; id < EntityList.MaxCount; id++) { - int mask = id >> 3, bit = 1 << (id & 0x7); - if ((needRemoveNames[mask] & bit) == 0) continue; - - RemoveTablistEntry((byte)id); - needRemoveNames[mask] &= (byte)~bit; - } - } - } -} \ No newline at end of file diff --git a/ClassicalSharp/Network/NetworkProcessor.cs b/ClassicalSharp/Network/NetworkProcessor.cs index 327555eb2..4d1fc8d6a 100644 --- a/ClassicalSharp/Network/NetworkProcessor.cs +++ b/ClassicalSharp/Network/NetworkProcessor.cs @@ -11,10 +11,12 @@ using ClassicalSharp.Network; using ClassicalSharp.Textures; using ClassicalSharp.Network.Protocols; using BlockID = System.UInt16; +using OpenTK; +using OpenTK.Input; namespace ClassicalSharp.Network { - public partial class NetworkProcessor : IServerConnection { + public sealed class NetworkProcessor : IServerConnection { public NetworkProcessor(Game window) { game = window; @@ -64,6 +66,28 @@ namespace ClassicalSharp.Network { lastPacket = DateTime.UtcNow; } + public override void SendChat(string text) { + if (String.IsNullOrEmpty(text)) return; + + while (text.Length > Utils.StringLength) { + classic.WriteChat(text.Substring(0, Utils.StringLength), true); + SendPacket(); + text = text.Substring(Utils.StringLength); + } + classic.WriteChat(text, false); + SendPacket(); + } + + public override void SendPosition(Vector3 pos, float rotY, float headX) { + classic.WritePosition(pos, rotY, headX); + SendPacket(); + } + + public override void SendPlayerClick(MouseButton button, bool buttonDown, byte targetId, PickedPos pos) { + cpe.WritePlayerClick(button, buttonDown, targetId, pos); + SendPacket(); + } + public override void Dispose() { if (Disconnected) return; game.UserEvents.BlockChanged -= BlockChanged; @@ -159,7 +183,7 @@ namespace ClassicalSharp.Network { UsingPlayerClick = false; SupportsPartialMessages = false; SupportsFullCP437 = false; - addEntityHack = true; + IProtocol.addEntityHack = true; for (int i = 0; i < handlers.Length; i++) { handlers[i] = null; @@ -201,8 +225,9 @@ namespace ClassicalSharp.Network { public override void OnNewMap(Game game) { // wipe all existing entity states - for (int i = 0; i < EntityList.MaxCount; i++) - RemoveEntity((byte)i); + for (int i = 0; i < EntityList.MaxCount; i++) { + classic.RemoveEntity((byte)i); + } } double testAcc = 0; diff --git a/ClassicalSharp/Network/Protocols/CPE.cs b/ClassicalSharp/Network/Protocols/CPE.cs index 2fd281f15..9afa5c96f 100644 --- a/ClassicalSharp/Network/Protocols/CPE.cs +++ b/ClassicalSharp/Network/Protocols/CPE.cs @@ -135,21 +135,21 @@ namespace ClassicalSharp.Network.Protocols { // Some server software will declare they support ExtPlayerList, but send AddEntity then AddPlayerName // we need to workaround this case by removing all the tab names we added for the AddEntity packets - net.DisableAddEntityHack(); - net.AddTablistEntry((byte)id, playerName, listName, groupName, groupRank); + DisableAddEntityHack(); + AddTablistEntry((byte)id, playerName, listName, groupName, groupRank); } void HandleExtAddEntity() { byte id = reader.ReadUInt8(); string displayName = reader.ReadString(); string skinName = reader.ReadString(); - net.CheckName(id, ref displayName, ref skinName); - net.AddEntity(id, displayName, skinName, false); + CheckName(id, ref displayName, ref skinName); + AddEntity(id, displayName, skinName, false); } void HandleExtRemovePlayerName() { int id = reader.ReadInt16() & 0xFF; - net.RemoveTablistEntry((byte)id); + RemoveTablistEntry((byte)id); } void HandleMakeSelection() { @@ -257,8 +257,8 @@ namespace ClassicalSharp.Network.Protocols { byte id = reader.ReadUInt8(); string displayName = reader.ReadString(); string skinName = reader.ReadString(); - net.CheckName(id, ref displayName, ref skinName); - net.AddEntity(id, displayName, skinName, true); + CheckName(id, ref displayName, ref skinName); + AddEntity(id, displayName, skinName, true); } const int bulkCount = 256; @@ -428,7 +428,7 @@ namespace ClassicalSharp.Network.Protocols { #region Write - internal void WritePlayerClick(MouseButton button, bool buttonDown, + internal void WritePlayerClick(MouseButton button, bool buttonDown, byte targetId, PickedPos pos) { Player p = game.LocalPlayer; writer.WriteUInt8((byte)Opcode.CpePlayerClick); @@ -444,24 +444,24 @@ namespace ClassicalSharp.Network.Protocols { writer.WriteUInt8((byte)pos.Face); } - internal void WriteExtInfo(string appName, int extensionsCount) { + void WriteExtInfo(string appName, int extensionsCount) { writer.WriteUInt8((byte)Opcode.CpeExtInfo); writer.WriteString(appName); writer.WriteInt16((short)extensionsCount); } - internal void WriteExtEntry(string extensionName, int extensionVersion) { + void WriteExtEntry(string extensionName, int extensionVersion) { writer.WriteUInt8((byte)Opcode.CpeExtEntry); writer.WriteString(extensionName); writer.WriteInt32(extensionVersion); } - internal void WriteCustomBlockSupportLevel(byte version) { + void WriteCustomBlockSupportLevel(byte version) { writer.WriteUInt8((byte)Opcode.CpeCustomBlockSupportLevel); writer.WriteUInt8(version); } - internal void WriteTwoWayPing(bool serverToClient, ushort data) { + void WriteTwoWayPing(bool serverToClient, ushort data) { writer.WriteUInt8((byte)Opcode.CpeTwoWayPing); writer.WriteUInt8((byte)(serverToClient ? 1 : 0)); writer.WriteInt16((short)data); diff --git a/ClassicalSharp/Network/Protocols/Classic.cs b/ClassicalSharp/Network/Protocols/Classic.cs index 8252bdafd..5c429e400 100644 --- a/ClassicalSharp/Network/Protocols/Classic.cs +++ b/ClassicalSharp/Network/Protocols/Classic.cs @@ -225,14 +225,14 @@ namespace ClassicalSharp.Network.Protocols { byte id = reader.ReadUInt8(); string name = reader.ReadString(); string skin = name; - net.CheckName(id, ref name, ref skin); - net.AddEntity(id, name, skin, true); + CheckName(id, ref name, ref skin); + AddEntity(id, name, skin, true); - if (!net.addEntityHack) return; + if (!addEntityHack) return; // Workaround for some servers that declare they support ExtPlayerList, // but doesn't send ExtAddPlayerName packets. - net.AddTablistEntry(id, name, name, "Players", 0); - net.needRemoveNames[id >> 3] |= (byte)(1 << (id & 0x7)); + AddTablistEntry(id, name, name, "Players", 0); + needRemoveNames[id >> 3] |= (byte)(1 << (id & 0x7)); } void HandleEntityTeleport() { @@ -250,7 +250,7 @@ namespace ClassicalSharp.Network.Protocols { float rotY = (float)Utils.PackedToDegrees(reader.ReadUInt8()); float headX = (float)Utils.PackedToDegrees(reader.ReadUInt8()); LocationUpdate update = LocationUpdate.MakePosAndOri(v, rotY, headX, true); - net.UpdateLocation(id, update, true); + UpdateLocation(id, update, true); } void HandleRelPositionUpdate() { @@ -261,7 +261,7 @@ namespace ClassicalSharp.Network.Protocols { v.Z = reader.ReadInt8() / 32f; LocationUpdate update = LocationUpdate.MakePos(v, true); - net.UpdateLocation(id, update, true); + UpdateLocation(id, update, true); } void HandleOrientationUpdate() { @@ -270,12 +270,12 @@ namespace ClassicalSharp.Network.Protocols { float headX = (float)Utils.PackedToDegrees(reader.ReadUInt8()); LocationUpdate update = LocationUpdate.MakeOri(rotY, headX); - net.UpdateLocation(id, update, true); + UpdateLocation(id, update, true); } void HandleRemoveEntity() { byte id = reader.ReadUInt8(); - net.RemoveEntity(id); + RemoveEntity(id); } void HandleMessage() { @@ -309,7 +309,7 @@ namespace ClassicalSharp.Network.Protocols { if (id == EntityList.SelfID) receivedFirstPosition = true; LocationUpdate update = LocationUpdate.MakePosAndOri(P, rotY, headX, false); - net.UpdateLocation(id, update, interpolate); + UpdateLocation(id, update, interpolate); } #endregion diff --git a/ClassicalSharp/Network/Protocols/IProtocol.cs b/ClassicalSharp/Network/Protocols/IProtocol.cs index 3e954251b..5df525cbf 100644 --- a/ClassicalSharp/Network/Protocols/IProtocol.cs +++ b/ClassicalSharp/Network/Protocols/IProtocol.cs @@ -1,5 +1,6 @@ // Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 using System; +using ClassicalSharp.Entities; namespace ClassicalSharp.Network.Protocols { @@ -18,5 +19,106 @@ namespace ClassicalSharp.Network.Protocols { public abstract void Reset(); public abstract void Tick(); + + protected internal static bool addEntityHack = true; + protected static byte[] needRemoveNames = new byte[256 >> 3]; + + protected void CheckName(byte id, ref string displayName, ref string skinName) { + displayName = Utils.RemoveEndPlus(displayName); + skinName = Utils.RemoveEndPlus(skinName); + skinName = Utils.StripColours(skinName); + + // Server is only allowed to change our own name colours. + if (id != EntityList.SelfID) return; + if (Utils.StripColours(displayName) != game.Username) + displayName = game.Username; + if (skinName == "") + skinName = game.Username; + } + + protected void AddEntity(byte id, string displayName, string skinName, bool readPosition) { + if (id != EntityList.SelfID) { + Entity oldEntity = game.Entities.List[id]; + if (oldEntity != null) game.Entities.RemoveEntity(id); + + game.Entities.List[id] = new NetPlayer(displayName, skinName, game); + game.EntityEvents.RaiseAdded(id); + } else { + game.LocalPlayer.Despawn(); + // Always reset the texture here, in case other network players are using the same skin as us. + // In that case, we don't want the fetching of new skin for us to delete the texture used by them. + game.LocalPlayer.ResetSkin(); + game.LocalPlayer.fetchedSkin = false; + + game.LocalPlayer.DisplayName = displayName; + game.LocalPlayer.SkinName = skinName; + game.LocalPlayer.UpdateName(); + } + + if (!readPosition) return; + net.classic.ReadAbsoluteLocation(id, false); + if (id != EntityList.SelfID) return; + + LocalPlayer p = game.LocalPlayer; + p.Spawn = p.Position; + p.SpawnRotY = p.HeadY; + p.SpawnHeadX = p.HeadX; + } + + internal void RemoveEntity(byte id) { + Entity entity = game.Entities.List[id]; + if (entity == null) return; + if (id != EntityList.SelfID) game.Entities.RemoveEntity(id); + + // See comment about some servers in HandleAddEntity + int mask = id >> 3, bit = 1 << (id & 0x7); + if (!addEntityHack || (needRemoveNames[mask] & bit) == 0) return; + + RemoveTablistEntry(id); + needRemoveNames[mask] &= (byte)~bit; + } + + protected void UpdateLocation(byte playerId, LocationUpdate update, bool interpolate) { + Entity entity = game.Entities.List[playerId]; + if (entity != null) { + entity.SetLocation(update, interpolate); + } + } + + + protected void AddTablistEntry(byte id, string playerName, string listName, + string groupName, byte groupRank) { + TabListEntry oldInfo = TabList.Entries[id]; + TabListEntry info = new TabListEntry(playerName, listName, groupName, groupRank); + TabList.Entries[id] = info; + + if (oldInfo != null) { + // Only redraw the tab list if something changed. + if (info.PlayerName != oldInfo.PlayerName || info.ListName != oldInfo.ListName || + info.Group != oldInfo.Group || info.GroupRank != oldInfo.GroupRank) { + game.EntityEvents.RaiseTabListEntryChanged(id); + } + } else { + game.EntityEvents.RaiseTabEntryAdded(id); + } + } + + protected void RemoveTablistEntry(byte id) { + game.EntityEvents.RaiseTabEntryRemoved(id); + TabList.Entries[id] = null; + } + + protected void DisableAddEntityHack() { + if (!addEntityHack) return; + addEntityHack = false; + + for (int id = 0; id < EntityList.MaxCount; id++) { + int mask = id >> 3, bit = 1 << (id & 0x7); + if ((needRemoveNames[mask] & bit) == 0) continue; + + RemoveTablistEntry((byte)id); + needRemoveNames[mask] &= (byte)~bit; + } + } } } diff --git a/src/Client/Client.vcxproj b/src/Client/Client.vcxproj index df2dddaf8..813a0886c 100644 --- a/src/Client/Client.vcxproj +++ b/src/Client/Client.vcxproj @@ -209,6 +209,7 @@ + diff --git a/src/Client/Client.vcxproj.filters b/src/Client/Client.vcxproj.filters index f7d310fc5..c741df0ac 100644 --- a/src/Client/Client.vcxproj.filters +++ b/src/Client/Client.vcxproj.filters @@ -366,6 +366,9 @@ Header Files\Game + + Header Files\Network + diff --git a/src/Client/PacketHandlers.c b/src/Client/PacketHandlers.c index 28ee7d9a3..f8569099e 100644 --- a/src/Client/PacketHandlers.c +++ b/src/Client/PacketHandlers.c @@ -1,3 +1,4 @@ +#include "PacketHandlers.h" #include "Deflate.h" #include "Utils.h" #include "ServerConnection.h" @@ -617,7 +618,7 @@ void CPE_CustomBlockLevel(Stream* stream) { UInt8 supportLevel = Stream_ReadU8(stream); stream = ServerConnection_WriteStream(); CPE_WriteCustomBlockLevel(stream, 1); - ServerConnection_SendPacket(); + Net_SendPacket(); Game_UseCPEBlocks = true; Event_RaiseVoid(&BlockEvents_PermissionsChanged); } @@ -951,7 +952,7 @@ void CPE_TwoWayPing(Stream* stream) { stream = ServerConnection_WriteStream(); CPE_WriteTwoWayPing(stream, true, data); /* server to client reply */ - ServerConnection_SendPacket(); + Net_SendPacket(); } void CPE_SetInventoryOrder(Stream* stream) { @@ -965,7 +966,7 @@ void CPE_SetInventoryOrder(Stream* stream) { } #define Ext_Deg2Packed(x) ((Int16)((x) * 65536.0f / 360.0f)) -void CPE_WritePlayerClick(Stream* stream, MouseButton button, bool buttonDown, UInt8 targetId, PickedPos pos) { +void CPE_WritePlayerClick(Stream* stream, MouseButton button, bool buttonDown, UInt8 targetId, PickedPos* pos) { Entity* p = &LocalPlayer_Instance.Base; Stream_WriteU8(stream, OPCODE_CPE_PLAYER_CLICK); Stream_WriteU8(stream, button); @@ -974,13 +975,13 @@ void CPE_WritePlayerClick(Stream* stream, MouseButton button, bool buttonDown, U Stream_WriteI16_BE(stream, Ext_Deg2Packed(p->HeadX)); Stream_WriteU8(stream, targetId); - Stream_WriteI16_BE(stream, pos.BlockPos.X); - Stream_WriteI16_BE(stream, pos.BlockPos.Y); - Stream_WriteI16_BE(stream, pos.BlockPos.Z); + Stream_WriteI16_BE(stream, pos->BlockPos.X); + Stream_WriteI16_BE(stream, pos->BlockPos.Y); + Stream_WriteI16_BE(stream, pos->BlockPos.Z); UInt8 face = 255; /* Our own face values differ from CPE block face */ - switch (pos.ClosestFace) { + switch (pos->ClosestFace) { case FACE_XMAX: face = 0; break; case FACE_XMIN: face = 1; break; case FACE_YMAX: face = 2; break; @@ -1021,7 +1022,7 @@ void CPE_SendCpeExtInfoReply(void) { Stream* stream = ServerConnection_WriteStream(); CPE_WriteExtInfo(stream, &ServerConnection_AppName, count); - ServerConnection_SendPacket(); + Net_SendPacket(); Int32 i, ver; for (i = 0; i < Array_Elems(cpe_clientExtensions); i++) { @@ -1037,7 +1038,7 @@ void CPE_SendCpeExtInfoReply(void) { } CPE_WriteExtEntry(stream, &name, ver); - ServerConnection_SendPacket(); + Net_SendPacket(); } } diff --git a/src/Client/PacketHandlers.h b/src/Client/PacketHandlers.h new file mode 100644 index 000000000..360a9faf1 --- /dev/null +++ b/src/Client/PacketHandlers.h @@ -0,0 +1,21 @@ +#ifndef CC_PACKETHANDLERS_H +#define CC_PACKETHANDLERS_H +#include "Input.h" +#include "String.h" +#include "Vectors.h" +/* Implements network protocol handlers for original classic, CPE, and WoM textures. + Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 +*/ + +typedef struct PickedPos_ PickedPos; +typedef struct Stream_ Stream; +void Handlers_Reset(void); +void Handlers_Tick(void); + +void Handlers_RemoveEntity(EntityID id); +void Classic_WriteChat(Stream* stream, STRING_PURE String* text, bool partial); +void Classic_WritePosition(Stream* stream, Vector3 pos, Real32 rotY, Real32 headX); +void Classic_WriteSetBlock(Stream* stream, Int32 x, Int32 y, Int32 z, bool place, BlockID block); +void Classic_WriteLogin(Stream* stream, STRING_PURE String* username, STRING_PURE String* verKey); +void CPE_WritePlayerClick(Stream* stream, MouseButton button, bool buttonDown, UInt8 targetId, PickedPos* pos); +#endif \ No newline at end of file diff --git a/src/Client/Platform.h b/src/Client/Platform.h index 217aa15f7..87211804b 100644 --- a/src/Client/Platform.h +++ b/src/Client/Platform.h @@ -65,4 +65,11 @@ void Platform_SetBitmap(Bitmap* bmp); void Platform_ReleaseBitmap(void); Size2D Platform_MeasureText(DrawTextArgs* args); void Platform_DrawText(DrawTextArgs* args, Int32 x, Int32 y); + +void Platform_SocketCreate(void** socket); +ReturnCode Platform_SocketConnect(void* socket, STRING_PURE String* ip, Int32 port); +ReturnCode Platform_SocketRead(void* socket, UInt8* buffer, UInt32 count, UInt32* modified); +ReturnCode Platform_SocketWrite(void* socket, UInt8* buffer, UInt32 count, UInt32* modified); +ReturnCode Platform_SocketClose(void* socket); +ReturnCode Platform_SocketAvailable(void* socket, UInt32* available); #endif \ No newline at end of file diff --git a/src/Client/ServerConnection.c b/src/Client/ServerConnection.c index 125e665f3..9edc087df 100644 --- a/src/Client/ServerConnection.c +++ b/src/Client/ServerConnection.c @@ -16,7 +16,13 @@ #include "Camera.h" #include "TexturePack.h" #include "Menus.h" +#include "ErrorHandler.h" +#include "PacketHandlers.h" +#include "Inventory.h" +/*########################################################################################################################* +*-----------------------------------------------------Common handlers-----------------------------------------------------* +*#########################################################################################################################*/ UInt8 ServerConnection_ServerNameBuffer[String_BufferSize(STRING_SIZE)]; String ServerConnection_ServerName = String_FromEmptyArray(ServerConnection_ServerNameBuffer); UInt8 ServerConnection_ServerMOTDBuffer[String_BufferSize(STRING_SIZE)]; @@ -119,6 +125,9 @@ void ServerConnection_EndGeneration(void) { } +/*########################################################################################################################* +*--------------------------------------------------------PingList---------------------------------------------------------* +*#########################################################################################################################*/ typedef struct PingEntry_ { Int64 TimeSent, TimeReceived; UInt16 Data; @@ -176,6 +185,9 @@ Int32 PingList_AveragePingMs(void) { } +/*########################################################################################################################* +*-------------------------------------------------Singleplayer connection-------------------------------------------------* +*#########################################################################################################################*/ void SPConnection_Connect(STRING_PURE String* ip, Int32 port) { String logName = String_FromConst("Singleplayer"); Chat_SetLogName(&logName); @@ -257,27 +269,237 @@ void ServerConnection_InitSingleplayer(void) { ServerConnection_ReadStream = NULL; ServerConnection_WriteStream = NULL; - ServerConnection_SendPacket = NULL; } -void Net_Set(UInt8 opcode, Net_Handler handler, UInt16 size) { - Net_Handlers[opcode] = handler; - Net_PacketSizes[opcode] = size; - maxHandledPacket = Math.Max(opcode, maxHandledPacket); +/*########################################################################################################################* +*--------------------------------------------------Multiplayer connection-------------------------------------------------* +*#########################################################################################################################*/ +void* net_socket; +DateTime net_lastPacket; +UInt8 net_lastOpcode; +Stream net_readStream; +Stream net_writeStream; +UInt8 net_readBuffer[4096 * 5]; +UInt8 net_writeBuffer[131]; +Int32 net_maxHandledPacket; +bool net_writeFailed; +Int32 net_ticks; + +void MPConnection_Connect(STRING_PURE String* ip, Int32 port) { + Platform_SocketCreate(&net_socket); + Event_RegisterBlock(&UserEvents_BlockChanged, NULL, MPConnection_BlockChanged); + ServerConnection_Disconnected = false; + + ReturnCode result = Platform_SocketConnect(net_socket, &Game_IPAddress, Game_Port); + if (result != 0) { + ErrorHandler.LogError("connecting to server", ex); + game.Disconnect("Failed to connect to " + address + ":" + port, + "You failed to connect to the server. It's probably down!"); + ServerConnection_Free(); + return; + } + + String streamName = String_FromConst("network socket"); + Stream_ReadonlyMemory(&net_readStream, net_readBuffer, sizeof(net_readBuffer), &streamName); + Stream_WriteonlyMemory(&net_writeStream, net_writeBuffer, sizeof(net_writeBuffer), &streamName); + + Handlers_Reset(); + Classic_WriteLogin(&net_writeStream, &Game_Username, &Game_Mppass); + Net_SendPacket(); + Platform_CurrentUTCTime(&net_lastPacket); } +void MPConnection_SendChat(STRING_PURE String* text) { + if (text->length == 0) return; + String remaining = *text; + + while (remaining.length > STRING_SIZE) { + String portion = String_UNSAFE_Substring(&remaining, 0, STRING_SIZE); + Classic_WriteChat(&net_writeStream, &portion, true); + Net_SendPacket(); + remaining = String_UNSAFE_SubstringAt(&remaining, STRING_SIZE); + } + + Classic_WriteChat(&net_writeStream, &remaining, false); + Net_SendPacket(); +} + +void MPConnection_SendPosition(Vector3 pos, Real32 rotY, Real32 headX) { + Classic_WritePosition(&net_writeStream, pos, rotY, headX); + Net_SendPacket(); +} + +void MPConnection_SendPlayerClick(MouseButton button, bool buttonDown, EntityID targetId, PickedPos* pos) { + CPE_WritePlayerClick(&net_writeStream, button, buttonDown, targetId, pos); + Net_SendPacket(); +} + +double testAcc = 0; +void CheckDisconnection(double delta) { + testAcc += delta; + if (testAcc < 1) return; + testAcc = 0; + + if (net_writeFailed || (socket.Poll(1000, SelectMode.SelectRead) && socket.Available == 0)) { + String title = String_FromConst("Disconnected!"); + String reason = String_FromConst("You've lost connection to the server"); + Game_Disconnect(&title, &reason); + } +} + +void MPConnection_Tick(ScheduledTask* task) { + if (ServerConnection_Disconnected) return; + DateTime now; Platform_CurrentUTCTime(&now); + + if (DateTime_MsBetween(&net_lastPacket, &now) >= 30 * 1000) { + CheckDisconnection(task.Interval); + } + if (ServerConnection_Disconnected) return; + + UInt32 modified = 0; + ReturnCode recvResult = Platform_SocketAvailable(net_socket, &modified); + if (recvResult == 0 && modified > 0) { + /* NOTE: Always using a read call that is a multiple of 4096 (appears to?) improve read performance */ + UInt8* src = net_readBuffer + net_readStream.Meta_Mem_Count; + recvResult = Platform_SocketRead(net_socket, src, 4096 * 4, &modified); + net_readStream.Meta_Mem_Count += modified; + } + if (recvResult != 0) { + ErrorHandler.LogError("reading packets", ex); + game.Disconnect("&eLost connection to the server", "I/O error when reading packets"); + return; + } + + while (net_readStream.Meta_Mem_Count > 0) { + UInt8 opcode = net_readStream.Meta_Mem_Buffer[0]; + /* Workaround for older D3 servers which wrote one byte too many for HackControl packets */ + if (cpeData.needD3Fix && net_lastOpcode == OPCODE_CPE_HACK_CONTROL && (opcode == 0x00 || opcode == 0xFF)) { + Platform_LogConst("Skipping invalid HackControl byte from D3 server"); + Stream_Skip(&net_readStream, 1); + + LocalPlayer* p = &LocalPlayer_Instance; + p->Physics.JumpVel = 0.42f; /* assume default jump height */ + p->Physics.ServerJumpVel = p->Physics.JumpVel; + continue; + } + + if (opcode > net_maxHandledPacket) { + throw new InvalidOperationException("Invalid opcode: " + opcode); + } + if (net_readStream.Meta_Mem_Count < Net_PacketSizes[opcode]) break; + + Stream_Skip(&net_readStream, 1); /* remove opcode */ + net_lastOpcode = opcode; + Net_Handler handler = Net_Handlers[opcode]; + Platform_CurrentUTCTime(&net_lastPacket); + + if (handler == NULL) { + throw new InvalidOperationException("Unsupported opcode: " + opcode); + } + handler(&net_readStream); + } + + reader.RemoveProcessed(); + + /* Network is ticked 60 times a second. We only send position updates 20 times a second */ + if ((net_ticks % 3) == 0) { + ServerConnection_CheckAsyncResources(); + Handlers_Tick(); + /* Have any packets been written? */ + if (net_writeStream.Meta_Mem_Buffer != net_writeBuffer) Net_SendPacket(); + } + net_ticks++; +} + +void Net_Set(UInt8 opcode, Net_Handler handler, UInt16 packetSize) { + Net_Handlers[opcode] = handler; + Net_PacketSizes[opcode] = packetSize; + net_maxHandledPacket = max(opcode, net_maxHandledPacket); +} + +void Net_SendPacket(void) { + if (!ServerConnection_Disconnected) { + /* NOTE: Not immediately disconnecting here, as otherwise we sometimes miss out on kick messages */ + UInt32 count = (UInt32)(net_writeStream.Meta_Mem_Buffer - net_writeBuffer), modified = 0; + + while (count > 0) { + ReturnCode result = Platform_SocketWrite(net_socket, net_writeBuffer, count, &modified); + if (result != 0 || modified == 0) { net_writeFailed = true; break; } + count -= modified; + } + } + + net_writeStream.Meta_Mem_Buffer = net_writeBuffer; + net_writeStream.Meta_Mem_Count = sizeof(net_writeBuffer); +} + +void MPConnection_BlockChanged(void* obj, Vector3I coords, BlockID oldBlock, BlockID block) { + Vector3I p = coords; + if (block == BLOCK_AIR) { + block = Inventory_SelectedBlock; + Classic_WriteSetBlock(&net_writeStream, p.X, p.Y, p.Z, false, block); + } else { + Classic_WriteSetBlock(&net_writeStream, p.X, p.Y, p.Z, true, block); + } + Net_SendPacket(); +} + +Stream* MPConnection_ReadStream(void) { return &net_readStream; } +Stream* MPConnection_WriteStream(void) { return &net_writeStream; } +void ServerConnection_InitMultiplayer(void) { + ServerConnection_ResetState(); + ServerConnection_IsSinglePlayer = false; + + ServerConnection_Connect = MPConnection_Connect; + ServerConnection_SendChat = MPConnection_SendChat; + ServerConnection_SendPosition = MPConnection_SendPosition; + ServerConnection_SendPlayerClick = MPConnection_SendPlayerClick; + ServerConnection_Tick = MPConnection_Tick; + + ServerConnection_ReadStream = MPConnection_ReadStream; + ServerConnection_WriteStream = MPConnection_WriteStream; +} + + +void MPConnection_OnNewMap(void) { + if (!ServerConnection_IsSinglePlayer) return; + /* wipe all existing entity states */ + Int32 i; + for (i = 0; i < ENTITIES_MAX_COUNT; i++) { + classic.RemoveEntity((byte)i); + } +} + +void MPConnection_Reset(void) { + if (!ServerConnection_IsSinglePlayer) return; + Int32 i; + for (i = 0; i < OPCODE_COUNT; i++) { + Net_Handlers[i] = NULL; + Net_PacketSizes[i] = 0; + } + + net_writeFailed = false; + net_maxHandledPacket = 0; + Handlers_Reset(); + ServerConnection_Free(); +} void ServerConnection_Free(void) { if (ServerConnection_IsSinglePlayer) { Physics_Free(); } else { - + if (ServerConnection_Disconnected) return; + Event_UnregisterBlock(&UserEvents_BlockChanged, NULL, MPConnection_BlockChanged); + Platform_SocketClose(net_socket); + ServerConnection_Disconnected = true; } } IGameComponent ServerConnection_MakeComponent(void) { IGameComponent comp = IGameComponent_MakeEmpty(); - comp.Free = ServerConnection_Free; + comp.OnNewMap = MPConnection_OnNewMap; + comp.Reset = MPConnection_Reset; + comp.Free = ServerConnection_Free; return comp; -} \ No newline at end of file +} diff --git a/src/Client/ServerConnection.h b/src/Client/ServerConnection.h index 8ad181c2f..b4a39a921 100644 --- a/src/Client/ServerConnection.h +++ b/src/Client/ServerConnection.h @@ -7,52 +7,54 @@ Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 */ -#define OPCODE_HANDSHAKE 0 -#define OPCODE_PING 1 -#define OPCODE_LEVEL_INIT 2 -#define OPCODE_LEVEL_DATA_CHUNK 3 -#define OPCODE_LEVEL_FINALISE 4 -#define OPCODE_SET_BLOCK_CLIENT 5 -#define OPCODE_SET_BLOCK 6 -#define OPCODE_ADD_ENTITY 7 -#define OPCODE_ENTITY_TELEPORT 8 -#define OPCODE_RELPOS_AND_ORIENTATION_UPDATE 9 -#define OPCODE_RELPOS_UPDATE 10 -#define OPCODE_ORIENTATION_UPDATE 11 -#define OPCODE_REMOVE_ENTITY 12 -#define OPCODE_MESSAGE 13 -#define OPCODE_KICK 14 -#define OPCODE_SET_PERMISSION 15 +enum OPCODE_ { + OPCODE_HANDSHAKE, + OPCODE_PING, + OPCODE_LEVEL_INIT, + OPCODE_LEVEL_DATA_CHUNK, + OPCODE_LEVEL_FINALISE, + OPCODE_SET_BLOCK_CLIENT, + OPCODE_SET_BLOCK, + OPCODE_ADD_ENTITY, + OPCODE_ENTITY_TELEPORT, + OPCODE_RELPOS_AND_ORIENTATION_UPDATE, + OPCODE_RELPOS_UPDATE, + OPCODE_ORIENTATION_UPDATE, + OPCODE_REMOVE_ENTITY, + OPCODE_MESSAGE , + OPCODE_KICK, + OPCODE_SET_PERMISSION, -#define OPCODE_CPE_EXT_INFO 16 -#define OPCODE_CPE_EXT_ENTRY 17 -#define OPCODE_CPE_SET_CLICK_DISTANCE 18 -#define OPCODE_CPE_CUSTOM_BLOCK_LEVEL 19 -#define OPCODE_CPE_HOLD_THIS 20 -#define OPCODE_CPE_SET_TEXT_HOTKEY 21 -#define OPCODE_CPE_EXT_ADD_PLAYER_NAME 22 -#define OPCODE_CPE_EXT_ADD_ENTITY 23 -#define OPCODE_CPE_EXT_REMOVE_PLAYER_NAME 24 -#define OPCODE_CPE_ENV_SET_COLOR 25 -#define OPCODE_CPE_MAKE_SELECTION 26 -#define OPCODE_CPE_REMOVE_SELECTION 27 -#define OPCODE_CPE_SET_BLOCK_PERMISSION 28 -#define OPCODE_CPE_SET_MODEL 29 -#define OPCODE_CPE_ENV_SET_MAP_APPEARANCE 30 -#define OPCODE_CPE_ENV_SET_WEATHER 31 -#define OPCODE_CPE_HACK_CONTROL 32 -#define OPCODE_CPE_EXT_ADD_ENTITY2 33 -#define OPCODE_CPE_PLAYER_CLICK 34 -#define OPCODE_CPE_DEFINE_BLOCK 35 -#define OPCODE_CPE_UNDEFINE_BLOCK 36 -#define OPCODE_CPE_DEFINE_BLOCK_EXT 37 -#define OPCODE_CPE_BULK_BLOCK_UPDATE 38 -#define OPCODE_CPE_SET_TEXT_COLOR 39 -#define OPCODE_CPE_ENV_SET_MAP_URL 40 -#define OPCODE_CPE_ENV_SET_MAP_PROPERTY 41 -#define OPCODE_CPE_SET_ENTITY_PROPERTY 42 -#define OPCODE_CPE_TWO_WAY_PING 43 -#define OPCODE_CPE_SET_INVENTORY_ORDER 44 + OPCODE_CPE_EXT_INFO, + OPCODE_CPE_EXT_ENTRY, + OPCODE_CPE_SET_CLICK_DISTANCE, + OPCODE_CPE_CUSTOM_BLOCK_LEVEL, + OPCODE_CPE_HOLD_THIS, + OPCODE_CPE_SET_TEXT_HOTKEY, + OPCODE_CPE_EXT_ADD_PLAYER_NAME, + OPCODE_CPE_EXT_ADD_ENTITY, + OPCODE_CPE_EXT_REMOVE_PLAYER_NAME, + OPCODE_CPE_ENV_SET_COLOR, + OPCODE_CPE_MAKE_SELECTION, + OPCODE_CPE_REMOVE_SELECTION, + OPCODE_CPE_SET_BLOCK_PERMISSION, + OPCODE_CPE_SET_MODEL, + OPCODE_CPE_ENV_SET_MAP_APPEARANCE, + OPCODE_CPE_ENV_SET_WEATHER, + OPCODE_CPE_HACK_CONTROL, + OPCODE_CPE_EXT_ADD_ENTITY2, + OPCODE_CPE_PLAYER_CLICK, + OPCODE_CPE_DEFINE_BLOCK, + OPCODE_CPE_UNDEFINE_BLOCK, + OPCODE_CPE_DEFINE_BLOCK_EXT, + OPCODE_CPE_BULK_BLOCK_UPDATE, + OPCODE_CPE_SET_TEXT_COLOR, + OPCODE_CPE_ENV_SET_MAP_URL, + OPCODE_CPE_ENV_SET_MAP_PROPERTY, + OPCODE_CPE_SET_ENTITY_PROPERTY, + OPCODE_CPE_TWO_WAY_PING, + OPCODE_CPE_SET_INVENTORY_ORDER, +}; typedef struct PickedPos_ PickedPos; typedef struct Stream_ Stream; @@ -73,7 +75,6 @@ void (*ServerConnection_SendPlayerClick)(MouseButton button, bool isDown, Entity void (*ServerConnection_Tick)(ScheduledTask* task); Stream* (*ServerConnection_ReadStream)(void); Stream* (*ServerConnection_WriteStream)(void); -void (*ServerConnection_SendPacket)(void); /* Whether the network processor is currently disconnected from the server. */ bool ServerConnection_Disconnected; @@ -99,4 +100,5 @@ typedef void (*Net_Handler)(Stream* stream); UInt16 Net_PacketSizes[OPCODE_COUNT]; Net_Handler Net_Handlers[OPCODE_COUNT]; void Net_Set(UInt8 opcode, Net_Handler handler, UInt16 size); +void Net_SendPacket(void); #endif \ No newline at end of file diff --git a/src/Client/Stream.c b/src/Client/Stream.c index 08cfc660e..ecd009fb2 100644 --- a/src/Client/Stream.c +++ b/src/Client/Stream.c @@ -72,6 +72,10 @@ ReturnCode Stream_Skip(Stream* stream, UInt32 count) { return count > 0; } +ReturnCode Stream_UnsupportedIO(Stream* stream, UInt8* data, UInt32 count, UInt32* modified) { + *modified = 0; return 1; +} + /*########################################################################################################################* *-------------------------------------------------------FileStream--------------------------------------------------------* @@ -112,9 +116,6 @@ ReturnCode Stream_PortionRead(Stream* stream, UInt8* data, UInt32 count, UInt32* stream->Meta_Portion_Count -= *modified; return code; } -ReturnCode Stream_PortionWrite(Stream* stream, UInt8* data, UInt32 count, UInt32* modified) { - *modified = 0; return 1; -} ReturnCode Stream_PortionClose(Stream* stream) { return 0; } ReturnCode Stream_PortionSeek(Stream* stream, Int32 offset, Int32 seekType) { return 1; } @@ -124,7 +125,7 @@ void Stream_ReadonlyPortion(Stream* stream, Stream* underlying, UInt32 len) { stream->Meta_Portion_Count = len; stream->Read = Stream_PortionRead; - stream->Write = Stream_PortionWrite; + stream->Write = Stream_UnsupportedIO; stream->Close = Stream_PortionClose; stream->Seek = Stream_PortionSeek; } @@ -143,17 +144,33 @@ ReturnCode Stream_MemoryRead(Stream* stream, UInt8* data, UInt32 count, UInt32* return 0; } +ReturnCode Stream_MemoryWrite(Stream* stream, UInt8* data, UInt32 count, UInt32* modified) { + count = min(count, stream->Meta_Mem_Count); + if (count > 0) { Platform_MemCpy(stream->Meta_Mem_Buffer, data, count); } + + stream->Meta_Mem_Buffer += count; + stream->Meta_Mem_Count -= count; + *modified = count; + return 0; +} + void Stream_ReadonlyMemory(Stream* stream, void* data, UInt32 len, STRING_PURE String* name) { Stream_SetName(stream, name); stream->Meta_Mem_Buffer = data; stream->Meta_Mem_Count = len; stream->Read = Stream_MemoryRead; - stream->Write = Stream_PortionWrite; + stream->Write = Stream_UnsupportedIO; stream->Close = Stream_PortionClose; stream->Seek = Stream_PortionSeek; } +void Stream_WriteonlyMemory(Stream* stream, void* data, UInt32 len, STRING_PURE String* name) { + Stream_ReadonlyMemory(stream, data, len, name); + stream->Read = Stream_UnsupportedIO; + stream->Write = Stream_MemoryWrite; +} + /*########################################################################################################################* *-------------------------------------------------Read/Write primitives---------------------------------------------------* diff --git a/src/Client/Stream.h b/src/Client/Stream.h index aa4cffee6..a0c25d505 100644 --- a/src/Client/Stream.h +++ b/src/Client/Stream.h @@ -39,6 +39,8 @@ void Stream_FromFile(Stream* stream, void* file, STRING_PURE String* name); /* Readonly Stream wrapping another Stream, only allows reading up to 'len' bytes from the wrapped stream. */ void Stream_ReadonlyPortion(Stream* stream, Stream* underlying, UInt32 len); void Stream_ReadonlyMemory(Stream* stream, void* data, UInt32 len, STRING_PURE String* name); +void Stream_WriteonlyMemory(Stream* stream, void* data, UInt32 len, STRING_PURE String* name); + UInt8 Stream_ReadU8(Stream* stream); #define Stream_ReadI8(stream) ((Int8)Stream_ReadU8(stream)) diff --git a/src/Client/WinPlatform.c b/src/Client/WinPlatform.c index 772c5ab6f..b0a56997f 100644 --- a/src/Client/WinPlatform.c +++ b/src/Client/WinPlatform.c @@ -10,6 +10,8 @@ #define NOIME #include #include +#include +#include HDC hdc; HANDLE heap; @@ -28,6 +30,10 @@ void Platform_Init(void) { stopwatch_highResolution = QueryPerformanceFrequency(&stopwatch_freq); + WSADATA wsaData; + ReturnCode wsaResult = WSAStartup(MAKEWORD(2, 2), &wsaData); + ErrorHandler_CheckOrFail(wsaResult, "WSAStartup failed"); + UInt32 deviceNum = 0; /* Get available video adapters and enumerate all monitors */ DISPLAY_DEVICEA device = { 0 }; @@ -61,6 +67,7 @@ void Platform_Init(void) { void Platform_Free(void) { HeapDestroy(heap); DeleteDC(hdc); + WSACleanup(); } void Platform_Exit(ReturnCode code) { @@ -384,3 +391,53 @@ void Platform_DrawText(DrawTextArgs* args, Int32 x, Int32 y) { &r, DT_NOPREFIX | DT_SINGLELINE | DT_NOCLIP); SelectObject(hDC, oldFont); } + + +void Platform_SocketCreate(void** socketResult) { + *socketResult = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (*socketResult == INVALID_SOCKET) { + ErrorHandler_FailWithCode(WSAGetLastError(), "Failed to create socket"); + } +} + +ReturnCode Platform_SocketConnect(void* socket, STRING_PURE String* ip, Int32 port) { + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(ip->buffer); + addr.sin_port = htons((UInt16)port); + + ReturnCode result = connect(socket, (SOCKADDR*)(&addr), sizeof(addr)); + return result == SOCKET_ERROR ? WSAGetLastError() : 0; +} + +ReturnCode Platform_SocketRead(void* socket, UInt8* buffer, UInt32 count, UInt32* modified) { + Int32 recvCount = recv(socket, buffer, count, 0); + if (recvCount == SOCKET_ERROR) { + *modified = 0; return WSAGetLastError(); + } else { + *modified = recvCount; return 0; + } +} + +ReturnCode Platform_SocketWrite(void* socket, UInt8* buffer, UInt32 count, UInt32* modified) { + Int32 sentCount = send(socket, buffer, count, 0); + if (sentCount == SOCKET_ERROR) { + *modified = 0; return WSAGetLastError(); + } else { + *modified = sentCount; return 0; + } +} + +ReturnCode Platform_SocketClose(void* socket) { + ReturnCode result = 0; + ReturnCode result1 = shutdown(socket, SD_BOTH); + if (result1 == SOCKET_ERROR) result = WSAGetLastError(); + + ReturnCode result2 = closesocket(socket); + if (result2 == SOCKET_ERROR) result = WSAGetLastError(); + return result; +} + +ReturnCode Platform_SocketAvailable(void* socket, UInt32* available) { + return ioctlsocket(socket, FIONBIO, available); +} \ No newline at end of file