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