2016-09-26 18:33:05 +10:00

312 lines
10 KiB
C#

// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT
using System;
using ClassicalSharp.Gui.Screens;
using ClassicalSharp.Entities;
using OpenTK;
#if __MonoCS__
using Ionic.Zlib;
#else
using System.IO.Compression;
#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 );
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.ReadCp437String();
net.ServerMotd = reader.ReadCp437String();
game.Chat.SetLogName( net.ServerName );
game.LocalPlayer.Hacks.SetUserType( reader.ReadUInt8() );
game.LocalPlayer.Hacks.ParseHackFlags( net.ServerName, net.ServerMotd );
game.LocalPlayer.CheckHacksConsistency();
game.Events.RaiseHackPermissionsChanged();
}
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.ReadInt16();
gzippedMap.Position = 0;
gzippedMap.Offset = reader.index;
gzippedMap.SetLength( 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.ReadInt16();
int mapHeight = reader.ReadInt16();
int mapLength = reader.ReadInt16();
double loadingMs = (DateTime.UtcNow - mapReceiveStart).TotalMilliseconds;
Utils.LogDebug( "map loading took: " + loadingMs );
game.World.SetNewMap( map, mapWidth, mapHeight, mapLength );
game.WorldEvents.RaiseOnNewMapLoaded();
map = null;
gzipStream.Dispose();
net.wom.CheckSendWomID();
gzipStream = null;
GC.Collect();
}
void HandleSetBlock() {
int x = reader.ReadInt16();
int y = reader.ReadInt16();
int z = reader.ReadInt16();
byte type = 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, type );
}
#else
if( !game.World.IsNotLoaded && game.World.IsValidPos( x, y, z ) ) {
game.UpdateBlock( x, y, z, type );
}
#endif
}
void HandleAddEntity() {
byte id = reader.ReadUInt8();
string name = reader.ReadAsciiString();
string skin = name;
net.CheckName( id, ref name, ref skin );
net.AddEntity( id, name, skin, true );
// Also workaround for LegendCraft as it declares it supports 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 yaw = (float)Utils.PackedToDegrees( reader.ReadUInt8() );
float pitch = (float)Utils.PackedToDegrees( reader.ReadUInt8() );
LocationUpdate update = LocationUpdate.MakePosAndOri( x, y, z, yaw, pitch, 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 yaw = (float)Utils.PackedToDegrees( reader.ReadUInt8() );
float pitch = (float)Utils.PackedToDegrees( reader.ReadUInt8() );
LocationUpdate update = LocationUpdate.MakeOri( yaw, pitch );
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.ReadCp437String();
game.Disconnect( "&eLost connection to the server", reason );
net.Dispose();
}
void HandleSetPermission() {
game.LocalPlayer.Hacks.SetUserType( reader.ReadUInt8() );
}
internal void ReadAbsoluteLocation( byte id, bool interpolate ) {
float x = reader.ReadInt16() / 32f;
float y = ( reader.ReadInt16() - 51 ) / 32f; // We have to do this.
if( id == 255 ) y += 22/32f;
float z = reader.ReadInt16() / 32f;
float yaw = (float)Utils.PackedToDegrees( reader.ReadUInt8() );
float pitch = (float)Utils.PackedToDegrees( reader.ReadUInt8() );
if( id == 0xFF ) {
net.receivedFirstPosition = true;
}
LocationUpdate update = LocationUpdate.MakePosAndOri( x, y, z, yaw, pitch, 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 yaw, float pitch ) {
byte payload = net.cpeData.sendHeldBlock ? game.Inventory.HeldBlock : (byte)0xFF;
writer.WriteUInt8( (byte)Opcode.EntityTeleport );
writer.WriteUInt8( payload ); // held block when using HeldBlock, otherwise just 255
writer.WriteInt16( (short)(pos.X * 32) );
writer.WriteInt16( (short)((int)(pos.Y * 32) + 51) );
writer.WriteInt16( (short)(pos.Z * 32) );
writer.WriteUInt8( (byte)Utils.DegreesToPacked( yaw ) );
writer.WriteUInt8( (byte)Utils.DegreesToPacked( pitch ) );
net.SendPacket();
}
internal void SendSetBlock( int x, int y, int z, bool place, byte 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 );
writer.WriteUInt8( block );
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
}
}