// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT using System; using System.Drawing; using System.IO; using System.Net; using System.Net.Sockets; using ClassicalSharp.Entities; using ClassicalSharp.Events; using ClassicalSharp.Gui; using ClassicalSharp.Network; using ClassicalSharp.TexturePack; using ClassicalSharp.Network.Protocols; namespace ClassicalSharp.Network { public partial class NetworkProcessor : IServerConnection { public NetworkProcessor( Game window ) { game = window; cpeData = game.AddComponent( new CPESupport() ); } public override bool IsSinglePlayer { get { return false; } } Socket socket; DateTime lastPacket; Opcode lastOpcode; internal NetReader reader; internal NetWriter writer; internal ClassicProtocol classic; internal CPEProtocol cpe; internal CPEProtocolBlockDefs cpeBlockDefs; internal WoMProtocol wom; internal CPESupport cpeData; internal ScheduledTask task; internal bool receivedFirstPosition; internal byte[] needRemoveNames = new byte[256 >> 3]; public override void Connect( IPAddress address, int port ) { socket = new Socket( address.AddressFamily, SocketType.Stream, ProtocolType.Tcp ); try { socket.Connect( address, port ); } catch( SocketException ex ) { ErrorHandler.LogError( "connecting to server", ex ); game.Disconnect( "&eUnable to reach " + address + ":" + port, "Unable to establish an underlying connection" ); Dispose(); return; } reader = new NetReader( socket ); writer = new NetWriter( socket ); classic = new ClassicProtocol( game ); classic.Init(); cpe = new CPEProtocol( game ); cpe.Init(); cpeBlockDefs = new CPEProtocolBlockDefs( game ); cpeBlockDefs.Init(); wom = new WoMProtocol( game ); wom.Init(); Disconnected = false; receivedFirstPosition = false; lastPacket = DateTime.UtcNow; game.WorldEvents.OnNewMap += OnNewMap; game.UserEvents.BlockChanged += BlockChanged; classic.SendLogin( game.Username, game.Mppass ); lastPacket = DateTime.UtcNow; } public override void Dispose() { game.WorldEvents.OnNewMap -= OnNewMap; game.UserEvents.BlockChanged -= BlockChanged; socket.Close(); Disconnected = true; } public override void Tick( ScheduledTask task ) { if( Disconnected ) return; if( (DateTime.UtcNow - lastPacket).TotalSeconds >= 30 ) { CheckDisconnection( task.Interval ); } if( Disconnected ) return; LocalPlayer player = game.LocalPlayer; this.task = task; try { reader.ReadPendingData(); } catch( SocketException ex ) { ErrorHandler.LogError( "reading packets", ex ); game.Disconnect( "&eLost connection to the server", "I/O error when reading packets" ); Dispose(); return; } while( (reader.size - reader.index) > 0 ) { byte opcode = reader.buffer[reader.index]; // Workaround for older D3 servers which wrote one byte too many for HackControl packets. if( cpeData.needD3Fix && lastOpcode == Opcode.CpeHackControl && (opcode == 0x00 || opcode == 0xFF) ) { Utils.LogDebug( "Skipping invalid HackControl byte from D3 server." ); reader.Skip( 1 ); player.physics.jumpVel = 0.42f; // assume default jump height player.physics.serverJumpVel = player.physics.jumpVel; continue; } if( opcode > maxHandledPacket ) { ErrorHandler.LogError( "NetworkProcessor.Tick", "received an invalid opcode of " + opcode ); reader.Skip( 1 ); continue; } if( (reader.size - reader.index) < packetSizes[opcode] ) break; ReadPacket( opcode ); } reader.RemoveProcessed(); if( receivedFirstPosition ) { SendPosition( player.Position, player.HeadYawDegrees, player.PitchDegrees ); } CheckAsyncResources(); wom.Tick(); } /// Sets the incoming packet handler for the given packet id. public void Set( Opcode opcode, Action handler, int packetSize ) { handlers[(byte)opcode] = handler; packetSizes[(byte)opcode] = (ushort)packetSize; maxHandledPacket = Math.Max( (byte)opcode, maxHandledPacket ); } internal void SendPacket() { if( Disconnected ) { writer.index = 0; return; } try { writer.Send(); } catch( SocketException ) { // NOTE: Not immediately disconnecting, as otherwise we sometimes miss out on kick messages writer.index = 0; } } void ReadPacket( byte opcode ) { reader.Skip( 1 ); // remove opcode lastOpcode = (Opcode)opcode; Action handler = handlers[opcode]; lastPacket = DateTime.UtcNow; if( handler == null ) throw new NotImplementedException( "Unsupported packet:" + (Opcode)opcode ); handler(); } internal void SkipPacketData( Opcode opcode ) { reader.Skip( packetSizes[(byte)opcode] - 1 ); } internal void ResetProtocols() { UsingExtPlayerList = false; UsingPlayerClick = false; SupportsPartialMessages = false; SupportsFullCP437 = false; for( int i = 0; i < handlers.Length; i++ ) handlers[i] = null; packetSizes[(byte)Opcode.CpeEnvSetMapApperance] = 69; packetSizes[(byte)Opcode.CpeDefineBlockExt] = 85; } internal Action[] handlers = new Action[256]; internal ushort[] packetSizes = new ushort[256]; int maxHandledPacket = 0; void BlockChanged( object sender, BlockChangedEventArgs e ) { Vector3I p = e.Coords; byte block = game.Inventory.HeldBlock; if( e.Block == 0 ) { classic.SendSetBlock( p.X, p.Y, p.Z, false, block ); } else { classic.SendSetBlock( p.X, p.Y, p.Z, true, e.Block ); } } void OnNewMap( object sender, EventArgs e ) { // wipe all existing entity states for( int i = 0; i < 256; i++ ) RemoveEntity( (byte)i ); } double testAcc = 0; void CheckDisconnection( double delta ) { testAcc += delta; if( testAcc < 1 ) return; testAcc = 0; if( !socket.Connected || ( socket.Poll( 1000, SelectMode.SelectRead ) && socket.Available == 0 ) ) { game.Disconnect( "&eDisconnected from the server", "Connection timed out" ); Dispose(); } } } }