Mostly port NetworkProcessor to C

This commit is contained in:
UnknownShadow200 2018-04-26 22:22:51 +10:00
parent b5f2c1f9f3
commit 1eca5206f2
16 changed files with 554 additions and 230 deletions

View File

@ -243,7 +243,6 @@
<Compile Include="Network\Enums.cs" />
<Compile Include="Network\IServerConnection.cs" />
<Compile Include="Network\NetworkProcessor.cs" />
<Compile Include="Network\NetworkProcessor.Helpers.cs" />
<Compile Include="Network\Utils\AsyncDownloader.cs" />
<Compile Include="Network\Utils\NetReader.cs" />
<Compile Include="Network\Utils\FixedBufferStream.cs" />

View File

@ -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;
}
}
}
}

View File

@ -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;

View File

@ -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;
@ -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);

View File

@ -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

View File

@ -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;
}
}
}
}

View File

@ -209,6 +209,7 @@
<ClInclude Include="Input.h" />
<ClInclude Include="InputHandler.h" />
<ClInclude Include="Menus.h" />
<ClInclude Include="PacketHandlers.h" />
<ClInclude Include="Physics.h" />
<ClInclude Include="Inventory.h" />
<ClInclude Include="IsometricDrawer.h" />

View File

@ -366,6 +366,9 @@
<ClInclude Include="Audio.h">
<Filter>Header Files\Game</Filter>
</ClInclude>
<ClInclude Include="PacketHandlers.h">
<Filter>Header Files\Network</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Noise.c">

View File

@ -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();
}
}

View File

@ -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

View File

@ -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

View File

@ -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) {
/*########################################################################################################################*
*--------------------------------------------------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] = size;
maxHandledPacket = Math.Max(opcode, maxHandledPacket);
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.OnNewMap = MPConnection_OnNewMap;
comp.Reset = MPConnection_Reset;
comp.Free = ServerConnection_Free;
return comp;
}

View File

@ -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

View File

@ -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---------------------------------------------------*

View File

@ -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))

View File

@ -10,6 +10,8 @@
#define NOIME
#include <Windows.h>
#include <stdlib.h>
#include <winsock2.h>
#include <ws2tcpip.h>
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);
}