mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-10-05 12:07:25 -04:00
326 lines
10 KiB
C#
326 lines
10 KiB
C#
// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
|
|
using System;
|
|
using ClassicalSharp.Gui.Screens;
|
|
using ClassicalSharp.Entities;
|
|
using OpenTK;
|
|
#if __MonoCS__
|
|
using Ionic.Zlib;
|
|
#else
|
|
using System.IO.Compression;
|
|
#endif
|
|
|
|
#if USE16_BIT
|
|
using BlockID = System.UInt16;
|
|
#else
|
|
using BlockID = System.Byte;
|
|
#endif
|
|
|
|
namespace ClassicalSharp.Network.Protocols {
|
|
|
|
/// <summary> Implements the packets for the original classic. </summary>
|
|
public sealed class ClassicProtocol : IProtocol {
|
|
|
|
public ClassicProtocol(Game game) : base(game) { }
|
|
|
|
public override void Init() {
|
|
gzippedMap = new FixedBufferStream(net.reader.buffer);
|
|
Reset();
|
|
}
|
|
|
|
public override void Reset() {
|
|
net.Set(Opcode.Handshake, HandleHandshake, 131);
|
|
net.Set(Opcode.Ping, HandlePing, 1);
|
|
net.Set(Opcode.LevelInit, HandleLevelInit, 1);
|
|
net.Set(Opcode.LevelDataChunk, HandleLevelDataChunk, 1028);
|
|
net.Set(Opcode.LevelFinalise, HandleLevelFinalise, 7);
|
|
net.Set(Opcode.SetBlock, HandleSetBlock, 8);
|
|
|
|
net.Set(Opcode.AddEntity, HandleAddEntity, 74);
|
|
net.Set(Opcode.EntityTeleport, HandleEntityTeleport, 10);
|
|
net.Set(Opcode.RelPosAndOrientationUpdate, HandleRelPosAndOrientationUpdate, 7);
|
|
net.Set(Opcode.RelPosUpdate, HandleRelPositionUpdate, 5);
|
|
net.Set(Opcode.OrientationUpdate, HandleOrientationUpdate, 4);
|
|
net.Set(Opcode.RemoveEntity, HandleRemoveEntity, 2);
|
|
|
|
net.Set(Opcode.Message, HandleMessage, 66);
|
|
net.Set(Opcode.Kick, HandleKick, 65);
|
|
net.Set(Opcode.SetPermission, HandleSetPermission, 2);
|
|
}
|
|
|
|
DateTime mapReceiveStart;
|
|
DeflateStream gzipStream;
|
|
GZipHeaderReader gzipHeader;
|
|
int mapSizeIndex, mapIndex;
|
|
byte[] mapSize = new byte[4], map;
|
|
FixedBufferStream gzippedMap;
|
|
Screen prevScreen;
|
|
bool prevCursorVisible;
|
|
|
|
#region Read
|
|
|
|
void HandleHandshake() {
|
|
byte protocolVer = reader.ReadUInt8();
|
|
net.ServerName = reader.ReadString();
|
|
net.ServerMotd = reader.ReadString();
|
|
game.Chat.SetLogName(net.ServerName);
|
|
|
|
game.LocalPlayer.Hacks.SetUserType(reader.ReadUInt8());
|
|
game.LocalPlayer.Hacks.HacksFlags = net.ServerName + net.ServerMotd;
|
|
game.LocalPlayer.Hacks.UpdateHacksState();
|
|
}
|
|
|
|
void HandlePing() { }
|
|
|
|
void HandleLevelInit() {
|
|
if (gzipStream != null) return;
|
|
game.World.Reset();
|
|
prevScreen = game.Gui.activeScreen;
|
|
if (prevScreen is LoadingMapScreen)
|
|
prevScreen = null;
|
|
prevCursorVisible = game.CursorVisible;
|
|
|
|
game.Gui.SetNewScreen(new LoadingMapScreen(game, net.ServerName, net.ServerMotd), false);
|
|
net.wom.CheckMotd();
|
|
net.receivedFirstPosition = false;
|
|
gzipHeader = new GZipHeaderReader();
|
|
|
|
// Workaround because built in mono stream assumes that the end of stream
|
|
// has been reached the first time a read call returns 0. (MS.NET doesn't)
|
|
#if __MonoCS__
|
|
gzipStream = new DeflateStream(gzippedMap, true);
|
|
#else
|
|
gzipStream = new DeflateStream(gzippedMap, CompressionMode.Decompress);
|
|
if (OpenTK.Configuration.RunningOnMono) {
|
|
throw new InvalidOperationException("You must compile ClassicalSharp with __MonoCS__ defined " +
|
|
"to run on Mono, due to a limitation in Mono.");
|
|
}
|
|
#endif
|
|
|
|
mapSizeIndex = 0;
|
|
mapIndex = 0;
|
|
mapReceiveStart = DateTime.UtcNow;
|
|
net.task.Interval = 1.0 / 60;
|
|
}
|
|
|
|
void HandleLevelDataChunk() {
|
|
// Workaround for some servers that send LevelDataChunk before LevelInit
|
|
// due to their async packet sending behaviour.
|
|
if (gzipStream == null) HandleLevelInit();
|
|
int usedLength = reader.ReadUInt16();
|
|
gzippedMap.pos = 0;
|
|
gzippedMap.bufferPos = reader.index;
|
|
gzippedMap.len = usedLength;
|
|
|
|
if (gzipHeader.done || gzipHeader.ReadHeader(gzippedMap)) {
|
|
if (mapSizeIndex < 4) {
|
|
mapSizeIndex += gzipStream.Read(mapSize, mapSizeIndex, 4 - mapSizeIndex);
|
|
}
|
|
|
|
if (mapSizeIndex == 4) {
|
|
if (map == null) {
|
|
int size = mapSize[0] << 24 | mapSize[1] << 16 | mapSize[2] << 8 | mapSize[3];
|
|
map = new byte[size];
|
|
}
|
|
mapIndex += gzipStream.Read(map, mapIndex, map.Length - mapIndex);
|
|
}
|
|
}
|
|
|
|
reader.Skip(1025); // also skip progress since we calculate client side
|
|
float progress = map == null ? 0 : (float)mapIndex / map.Length;
|
|
game.WorldEvents.RaiseMapLoading(progress);
|
|
}
|
|
|
|
void HandleLevelFinalise() {
|
|
net.task.Interval = 1.0 / 20;
|
|
game.Gui.SetNewScreen(null);
|
|
game.Gui.activeScreen = prevScreen;
|
|
if (prevScreen != null && prevCursorVisible != game.CursorVisible) {
|
|
game.CursorVisible = prevCursorVisible;
|
|
}
|
|
prevScreen = null;
|
|
|
|
int mapWidth = reader.ReadUInt16();
|
|
int mapHeight = reader.ReadUInt16();
|
|
int mapLength = reader.ReadUInt16();
|
|
|
|
double loadingMs = (DateTime.UtcNow - mapReceiveStart).TotalMilliseconds;
|
|
Utils.LogDebug("map loading took: " + loadingMs);
|
|
|
|
#if USE16_BIT
|
|
game.World.SetNewMap(Utils.UInt8sToUInt16s(map), mapWidth, mapHeight, mapLength);
|
|
#else
|
|
game.World.SetNewMap(map, mapWidth, mapHeight, mapLength);
|
|
#endif
|
|
game.WorldEvents.RaiseOnNewMapLoaded();
|
|
|
|
map = null;
|
|
gzipStream.Dispose();
|
|
net.wom.CheckSendWomID();
|
|
gzipStream = null;
|
|
GC.Collect();
|
|
}
|
|
|
|
void HandleSetBlock() {
|
|
int x = reader.ReadUInt16();
|
|
int y = reader.ReadUInt16();
|
|
int z = reader.ReadUInt16();
|
|
byte block = reader.ReadUInt8();
|
|
|
|
#if DEBUG_BLOCKS
|
|
if (game.World.IsNotLoaded) {
|
|
Utils.LogDebug("Server tried to update a block while still sending us the map!");
|
|
} else if (!game.World.IsValidPos(x, y, z)) {
|
|
Utils.LogDebug("Server tried to update a block at an invalid position!");
|
|
} else {
|
|
game.UpdateBlock(x, y, z, block);
|
|
}
|
|
#else
|
|
if (!game.World.IsNotLoaded && game.World.IsValidPos(x, y, z)) {
|
|
game.UpdateBlock(x, y, z, block);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void HandleAddEntity() {
|
|
byte id = reader.ReadUInt8();
|
|
string name = reader.ReadString();
|
|
string skin = name;
|
|
net.CheckName(id, ref name, ref skin);
|
|
net.AddEntity(id, name, skin, true);
|
|
|
|
if (!net.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));
|
|
}
|
|
|
|
void HandleEntityTeleport() {
|
|
byte id = reader.ReadUInt8();
|
|
ReadAbsoluteLocation(id, true);
|
|
}
|
|
|
|
void HandleRelPosAndOrientationUpdate() {
|
|
byte id = reader.ReadUInt8();
|
|
float x = reader.ReadInt8() / 32f;
|
|
float y = reader.ReadInt8() / 32f;
|
|
float z = reader.ReadInt8() / 32f;
|
|
|
|
float rotY = (float)Utils.PackedToDegrees(reader.ReadUInt8());
|
|
float headX = (float)Utils.PackedToDegrees(reader.ReadUInt8());
|
|
LocationUpdate update = LocationUpdate.MakePosAndOri(x, y, z, rotY, headX, true);
|
|
net.UpdateLocation(id, update, true);
|
|
}
|
|
|
|
void HandleRelPositionUpdate() {
|
|
byte id = reader.ReadUInt8();
|
|
float x = reader.ReadInt8() / 32f;
|
|
float y = reader.ReadInt8() / 32f;
|
|
float z = reader.ReadInt8() / 32f;
|
|
|
|
LocationUpdate update = LocationUpdate.MakePos(x, y, z, true);
|
|
net.UpdateLocation(id, update, true);
|
|
}
|
|
|
|
void HandleOrientationUpdate() {
|
|
byte id = reader.ReadUInt8();
|
|
float rotY = (float)Utils.PackedToDegrees(reader.ReadUInt8());
|
|
float headX = (float)Utils.PackedToDegrees(reader.ReadUInt8());
|
|
|
|
LocationUpdate update = LocationUpdate.MakeOri(rotY, headX);
|
|
net.UpdateLocation(id, update, true);
|
|
}
|
|
|
|
void HandleRemoveEntity() {
|
|
byte id = reader.ReadUInt8();
|
|
net.RemoveEntity(id);
|
|
}
|
|
|
|
void HandleMessage() {
|
|
byte type = reader.ReadUInt8();
|
|
// Original vanilla server uses player ids in message types, 255 for server messages.
|
|
bool prepend = !net.cpeData.useMessageTypes && type == 0xFF;
|
|
|
|
if (!net.cpeData.useMessageTypes) type = (byte)MessageType.Normal;
|
|
string text = reader.ReadChatString(ref type);
|
|
if (prepend) text = "&e" + text;
|
|
|
|
if (!text.StartsWith("^detail.user", StringComparison.OrdinalIgnoreCase))
|
|
game.Chat.Add(text, (MessageType)type);
|
|
}
|
|
|
|
void HandleKick() {
|
|
string reason = reader.ReadString();
|
|
game.Disconnect("&eLost connection to the server", reason);
|
|
net.Dispose();
|
|
}
|
|
|
|
void HandleSetPermission() {
|
|
game.LocalPlayer.Hacks.SetUserType(reader.ReadUInt8());
|
|
game.LocalPlayer.Hacks.UpdateHacksState();
|
|
}
|
|
|
|
internal void ReadAbsoluteLocation(byte id, bool interpolate) {
|
|
Vector3 P = reader.ReadPosition(id);
|
|
float rotY = (float)Utils.PackedToDegrees(reader.ReadUInt8());
|
|
float headX = (float)Utils.PackedToDegrees(reader.ReadUInt8());
|
|
|
|
if (id == EntityList.SelfID) net.receivedFirstPosition = true;
|
|
LocationUpdate update = LocationUpdate.MakePosAndOri(P, rotY, headX, false);
|
|
net.UpdateLocation(id, update, interpolate);
|
|
}
|
|
#endregion
|
|
|
|
#region Write
|
|
|
|
internal void SendChat(string text, bool partial) {
|
|
int payload = !net.SupportsPartialMessages ? 0xFF: (partial ? 1 : 0);
|
|
writer.WriteUInt8((byte)Opcode.Message);
|
|
|
|
writer.WriteUInt8((byte)payload);
|
|
writer.WriteString(text);
|
|
net.SendPacket();
|
|
}
|
|
|
|
internal void SendPosition(Vector3 pos, float rotY, float headX) {
|
|
int payload = net.cpeData.sendHeldBlock ? game.Inventory.Selected : 0xFF;
|
|
writer.WriteUInt8((byte)Opcode.EntityTeleport);
|
|
|
|
writer.WriteUInt8((byte)payload); // held block when using HeldBlock, otherwise just 255
|
|
writer.WritePosition(pos);
|
|
writer.WriteUInt8((byte)Utils.DegreesToPacked(rotY));
|
|
writer.WriteUInt8((byte)Utils.DegreesToPacked(headX));
|
|
net.SendPacket();
|
|
}
|
|
|
|
internal void SendSetBlock(int x, int y, int z, bool place, BlockID block) {
|
|
writer.WriteUInt8((byte)Opcode.SetBlockClient);
|
|
|
|
writer.WriteInt16((short)x);
|
|
writer.WriteInt16((short)y);
|
|
writer.WriteInt16((short)z);
|
|
writer.WriteUInt8(place ? (byte)1 : (byte)0);
|
|
|
|
#if USE16_BIT
|
|
writer.WriteUInt8((byte)block);
|
|
#else
|
|
writer.WriteUInt8(block);
|
|
#endif
|
|
net.SendPacket();
|
|
}
|
|
|
|
internal void SendLogin(string username, string verKey) {
|
|
byte payload = game.UseCPE ? (byte)0x42 : (byte)0x00;
|
|
writer.WriteUInt8((byte)Opcode.Handshake);
|
|
|
|
writer.WriteUInt8(7); // protocol version
|
|
writer.WriteString(username);
|
|
writer.WriteString(verKey);
|
|
writer.WriteUInt8(payload);
|
|
net.SendPacket();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|