ClassiCube/ClassicalSharp/Network/NetworkProcessor.cs
2016-09-15 12:02:19 +10:00

212 lines
6.1 KiB
C#

// 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();
}
/// <summary> Sets the incoming packet handler for the given packet id. </summary>
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();
}
}
}
}