using System; using OpenTK; #if __MonoCS__ using Ionic.Zlib; #else using System.IO.Compression; #endif namespace ClassicalSharp { public partial class NetworkProcessor : INetworkProcessor { #region Writing public override void SendChat( string text, bool partial ) { if( String.IsNullOrEmpty( text ) ) return; byte payload = !ServerSupportsPatialMessages ? (byte)0xFF: partial ? (byte)1 : (byte)0; writer.WriteUInt8( (byte)PacketId.Message ); writer.WriteUInt8( payload ); writer.WriteString( text ); SendPacket(); } public override void SendPosition( Vector3 pos, float yaw, float pitch ) { byte payload = sendHeldBlock ? (byte)game.Inventory.HeldBlock : (byte)0xFF; MakePositionPacket( pos, yaw, pitch, payload ); SendPacket(); } public override void SendSetBlock( int x, int y, int z, bool place, byte block ) { writer.WriteUInt8( (byte)PacketId.SetBlockClient ); writer.WriteInt16( (short)x ); writer.WriteInt16( (short)y ); writer.WriteInt16( (short)z ); writer.WriteUInt8( place ? (byte)1 : (byte)0 ); writer.WriteUInt8( block ); SendPacket(); } void MakeLoginPacket( string username, string verKey ) { writer.WriteUInt8( (byte)PacketId.Handshake ); writer.WriteUInt8( 7 ); // protocol version writer.WriteString( username ); writer.WriteString( verKey ); writer.WriteUInt8( 0x42 ); } void MakePositionPacket( Vector3 pos, float yaw, float pitch, byte payload ) { writer.WriteUInt8( (byte)PacketId.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, 256 ) ); writer.WriteUInt8( (byte)Utils.DegreesToPacked( pitch, 256 ) ); } #endregion #region Reading DateTime receiveStart; DeflateStream gzipStream; GZipHeaderReader gzipHeader; int mapSizeIndex, mapIndex; byte[] mapSize = new byte[4], map; FixedBufferStream gzippedMap; void HandleHandshake() { byte protocolVer = reader.ReadUInt8(); ServerName = reader.ReadCp437String(); ServerMotd = reader.ReadCp437String(); game.LocalPlayer.SetUserType( reader.ReadUInt8() ); receivedFirstPosition = false; game.LocalPlayer.ParseHackFlags( ServerName, ServerMotd ); } void HandlePing() { } void HandleLevelInit() { game.Map.Reset(); game.SetNewScreen( new LoadingMapScreen( game, ServerName, ServerMotd ) ); if( ServerMotd.Contains( "cfg=" ) ) { ReadWomConfigurationAsync(); } 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; receiveStart = DateTime.UtcNow; } void HandleLevelDataChunk() { int usedLength = reader.ReadInt16(); gzippedMap.Position = 0; 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.Remove( 1024 ); byte progress = reader.ReadUInt8(); game.Events.RaiseMapLoading( progress ); } void HandleLevelFinalise() { game.SetNewScreen( null ); int mapWidth = reader.ReadInt16(); int mapHeight = reader.ReadInt16(); int mapLength = reader.ReadInt16(); double loadingMs = ( DateTime.UtcNow - receiveStart ).TotalMilliseconds; Utils.LogDebug( "map loading took:" + loadingMs ); game.Map.SetData( map, mapWidth, mapHeight, mapLength ); game.Events.RaiseOnNewMapLoaded(); map = null; gzipStream.Dispose(); if( sendWomId && !sentWomId ) { SendChat( "/womid WoMClient-2.0.7", false ); sentWomId = true; } gzipStream = null; GC.Collect(); } void HandleSetBlock() { int x = reader.ReadInt16(); int y = reader.ReadInt16(); int z = reader.ReadInt16(); byte type = reader.ReadUInt8(); if( game.Map.IsNotLoaded ) Utils.LogDebug( "Server tried to update a block while still sending us the map!" ); else if( !game.Map.IsValidPos( x, y, z ) ) Utils.LogDebug( "Server tried to update a block at an invalid position!" ); else game.UpdateBlock( x, y, z, type ); } bool[] needRemoveNames; void HandleAddEntity() { byte entityId = reader.ReadUInt8(); string name = reader.ReadAsciiString(); name = Utils.RemoveEndPlus( name ); AddEntity( entityId, name, name, true ); // Workaround for LegendCraft as it declares it supports ExtPlayerList but // doesn't send ExtAddPlayerName packets. So we add a special case here, even // though it is technically against the specification. if( UsingExtPlayerList ) { AddCpeInfo( entityId, name, name, "Players", 0 ); if( needRemoveNames == null ) needRemoveNames = new bool[EntityList.MaxCount]; needRemoveNames[entityId] = true; } } void HandleEntityTeleport() { byte entityId = reader.ReadUInt8(); ReadAbsoluteLocation( entityId, true ); } void HandleRelPosAndOrientationUpdate() { byte playerId = 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 ); UpdateLocation( playerId, update, true ); } void HandleRelPositionUpdate() { byte playerId = 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 ); UpdateLocation( playerId, update, true ); } void HandleOrientationUpdate() { byte playerId = reader.ReadUInt8(); float yaw = (float)Utils.PackedToDegrees( reader.ReadUInt8() ); float pitch = (float)Utils.PackedToDegrees( reader.ReadUInt8() ); LocationUpdate update = LocationUpdate.MakeOri( yaw, pitch ); UpdateLocation( playerId, update, true ); } void HandleRemoveEntity() { byte entityId = reader.ReadUInt8(); Player player = game.Players[entityId]; if( entityId != 0xFF && player != null ) { game.Events.RaiseEntityRemoved( entityId ); player.Despawn(); game.Players[entityId] = null; } // See comment about LegendCraft in AddEntity if( needRemoveNames != null && needRemoveNames[entityId] && player != null ) { game.Events.RaiseCpeListInfoRemoved( entityId ); game.CpePlayersList[entityId] = null; needRemoveNames[entityId] = false; } } void HandleMessage() { byte messageType = reader.ReadUInt8(); string text = reader.ReadChatString( ref messageType, useMessageTypes ); game.Chat.Add( text, (CpeMessage)messageType ); } void HandleKick() { string reason = reader.ReadAsciiString(); game.Disconnect( "&eLost connection to the server", reason ); Dispose(); } void HandleSetPermission() { game.LocalPlayer.SetUserType( reader.ReadUInt8() ); } void AddEntity( byte entityId, string displayName, string skinName, bool readPosition ) { skinName = Utils.StripColours( skinName ); if( entityId != 0xFF ) { Player oldPlayer = game.Players[entityId]; if( oldPlayer != null ) { game.Events.RaiseEntityRemoved( entityId ); oldPlayer.Despawn(); } game.Players[entityId] = new NetPlayer( displayName, skinName, game ); game.Events.RaiseEntityAdded( entityId ); game.AsyncDownloader.DownloadSkin( skinName ); } else { game.LocalPlayer.SkinName = skinName; game.LocalPlayer.SkinIdentifier = "skin_" + skinName; game.AsyncDownloader.DownloadSkin( skinName ); } if( readPosition ) { ReadAbsoluteLocation( entityId, false ); if( entityId == 0xFF ) { game.LocalPlayer.SpawnPoint = game.LocalPlayer.Position; } } } void ReadAbsoluteLocation( byte playerId, bool interpolate ) { float x = reader.ReadInt16() / 32f; float y = ( reader.ReadInt16() - 51 ) / 32f; // We have to do this. if( playerId == 255 ) y += 22/32f; float z = reader.ReadInt16() / 32f; float yaw = (float)Utils.PackedToDegrees( reader.ReadUInt8() ); float pitch = (float)Utils.PackedToDegrees( reader.ReadUInt8() ); if( playerId == 0xFF ) { receivedFirstPosition = true; } LocationUpdate update = LocationUpdate.MakePosAndOri( x, y, z, yaw, pitch, false ); UpdateLocation( playerId, update, interpolate ); } void UpdateLocation( byte playerId, LocationUpdate update, bool interpolate ) { Player player = game.Players[playerId]; if( player != null ) { player.SetLocation( update, interpolate ); } } #endregion } }