//#define DEBUG
using System;
using System.Net.Sockets;
using System.Reflection;
using Woodpecker.Core;
using Woodpecker.Storage;
using Woodpecker.Game;
using Woodpecker.Game.Users;
using Woodpecker.Sessions;
using Woodpecker.Net.Game.Messages;
using Woodpecker.Security.Ciphering;
using Woodpecker.Specialized.Encoding;
namespace Woodpecker.Net.Game
{
///
/// Represents a game connection of a Habbo client to the server.
///
public class gameConnection : Reactor
{
#region Fields
///
/// The System.Net.Sockets.Socket object of this connection.
///
private Socket Socket;
///
/// The IP address of this connection.
///
public string ipAddress
{
get { return this.Socket.RemoteEndPoint.ToString().Split(':')[0]; }
}
///
/// A byte array which contains the data of the incoming message.
///
private byte[] dataBuffer;
private int receivedBytesCounter;
///
/// The total amount of bytes that the server has received from this game connection since the last counter reset.
///
public int receivedBytes
{
get { return this.receivedBytesCounter; }
}
///
/// The Woodpecker.Game.reactorHandler object, which keeps and manages all inheritances of the Reactor class.
///
public reactorHandler reactorHandler;
///
/// The unique instance of the encryption client for this game connection.
///
private rc4Provider encryptionClient;
#endregion
#region Constructors
///
/// Initializes a new game connection instance.
///
/// The Woodpecker.Sessions.Session object for this connection.
/// The System.Net.Sockets.Socket object of the game client that has been accepted by the game connection listener.
public gameConnection(Session Session, Socket gameClient)
{
this.Session = Session;
this.Socket = gameClient;
}
#endregion
#region Methods
///
/// Checks if this IP address is banned, if so, the ban is processed, otherwise, the default reactors will be registered and handshake with client is performed..
///
public void Initialize()
{
this.dataBuffer = new byte[1024]; // Max 1024 characters per packet
this.reactorHandler = new reactorHandler(this.Session);
this.reactorHandler.Register(new securityReactor());
this.reactorHandler.Register(new globalReactor());
this.Socket.BeginReceive(this.dataBuffer, 0, this.dataBuffer.Length, SocketFlags.None, new AsyncCallback(this.dataArrival), null);
this.Response.Initialize(0); // "@@" (handshake)
this.sendMessage(Response);
// Quick ping
this.Response.Initialize(50); // "@r"
this.sendMessage(Response);
}
///
/// Immediately aborts the connection and releases all resources.
///
public void Abort()
{
try { this.Socket.Close(); }
catch { }
this.Session.isValid = false;
this.Socket = null;
this.Session = null;
this.dataBuffer = null;
this.Request = null;
this.Response = null;
this.reactorHandler = null;
}
///
/// Resets the received bytes counter to zero.
///
public void resetReceivedBytesCounter()
{
this.receivedBytesCounter = 0;
}
#region Normal message sending
///
/// Sends a message in string format to the client.
///
/// The string representing the message. Use char1 to break messages.
public void sendMessage(string Message)
{
try
{
byte[] Data = Configuration.charTable.GetBytes(Message);
this.Socket.Send(Data);
}
catch { }
}
///
/// Sends a single message in a serverMessage object to the client.
///
/// The serverMessage object representing the message.
public void sendMessage(serverMessage Message)
{
Logging.Log("Session " + this.Session.ID + ">> " + Message, Logging.logType.serverMessageEvent);
this.sendMessage(Message.ToString());
}
#endregion
#region Async message sending
///
/// Sends a message in string format to the client via an asynchronous System.Net.Sockets.Socket.BeginSend action.
///
/// The string object of the message. Use char1 to break messages.
public void sendAsyncMessage(string Message)
{
if (this.Socket == null)
return;
byte[] Data = Configuration.charTable.GetBytes(Message);
this.Socket.BeginSend(Data, 0, Data.Length, SocketFlags.None, new AsyncCallback(sentAsyncMessage), null);
}
///
/// Sends a single message in a serverMessage object to the client via an asynchronous System.Net.Sockets.Socket.BeginSend action.
///
/// The serverMessage object of the message.
public void sendAsyncMessage(serverMessage Message)
{
Logging.Log("Session " + this.Session.ID + ">> " + Message, Logging.logType.serverMessageEvent);
this.sendMessage(Message.ToString());
}
///
/// Ends an asynchronous BeginSend operation.
///
/// The IAsyncResult object which contains the data of the asynchronous operation.
private void sentAsyncMessage(IAsyncResult iAr)
{
if(this.Socket != null)
this.Socket.EndSend(iAr);
}
#endregion
private void dataArrival(IAsyncResult iAr)
{
if (Session == null || !Session.isValid) // Safety above all
return;
string Data = "";
try
{
int bytesReceived = this.Socket.EndReceive(iAr);
this.receivedBytesCounter += bytesReceived;
Data = Configuration.charTable.GetString(this.dataBuffer, 0, bytesReceived);
Logging.Log("Session " + this.Session.ID + "<< " + Data, Logging.logType.debugEvent);
if (this.encryptionClient != null)
Data = this.encryptionClient.Decipher(Data);
while (Data.Length > 0)
{
int v = base64Encoding.Decode(Data.Substring(1, 2)); // Decode length of this message
this.Request = new clientMessage(Data.Substring(3, v)); // Get message content and create clientMessage object
Logging.Log("Session " + this.Session.ID + "<< [" + this.Request.ID + ": " + this.Request.methodName + "] \"" + this.Request.encodedID + "\", \"" + this.Request.Content + "\"", Logging.logType.clientMessageEvent);
this.processMessage();
Data = Data.Substring(v + 3);
}
if (Session != null && Session.isValid) // Session is still allowed to process messages
this.Socket.BeginReceive(this.dataBuffer, 0, this.dataBuffer.Length, SocketFlags.None, new AsyncCallback(this.dataArrival), null);
}
catch (ObjectDisposedException) { }
catch (SocketException)
{
if (this.Session != null)
Engine.Sessions.destroySession(this.Session.ID);
return;
}
catch (ArgumentOutOfRangeException) // Wrong structured packet
{
// Saddam Hussein solution
//Logging.Log("Session " + this.Session.ID + " sent wrong structured packet '" + Data + "'. Session destroyed and attempt to blacklist IP address sent.", Logging.logType.haxEvent);
//Engine.Net.Game.blackListIpAddress(this.Session.ipAddress);
// Nah!
Engine.Sessions.destroySession(this.Session.ID); // Something is wrong with client, weird data?
return;
}
catch (Exception ex)
{
Logging.Log("Unhandled exception occurred in dataArrival method of session, stack trace: \r\n" + ex.StackTrace, Logging.logType.commonError);
}
}
private void processMessage()
{
if (this.Request.Content.Contains("\x01"))
{
Session.isValid = false;
Engine.Sessions.destroySession(Session.ID);
Engine.Net.Game.blackListIpAddress(this.ipAddress);
Logging.Log("Session " + Session.ID + " (ip: " + this.ipAddress + ") sent message with char1 (message breaker), hax! IP address blacklisted.");
return;
}
string requiredMethod = this.Request.methodName;
foreach (Reactor lReactor in this.reactorHandler.Reactors)
{
if (lReactor != null)
{
try
{
Type t = lReactor.GetType();
MethodInfo pMethod = t.GetMethod(requiredMethod);
if (pMethod != null) // Target method is inside this reactor
{
lReactor.setRequest(Request);
pMethod.Invoke(lReactor, null);
lReactor.unsetRequest();
return;
}
}
catch (Exception e)
{
Logging.Log("Unhandled error occurred in " + requiredMethod + " in " + lReactor.GetType().ToString() + ". Error: " + e.InnerException.Message);
// Transmit client error report
Response.Initialize(299); // "Dk"
Response.appendWired(1337); // Server ID
Response.appendWired(Request.ID); // Last message ID
Response.appendClosedValue(DateTime.Now.ToString());
sendResponse();
return;
}
}
}
// Target method could not be reflected
if (Request.methodName == "UNKNOWN") // Non-existing packet
{
/*
Logging.Log("Session " + this.Session.ID + " sent message with ID " + Request.ID + ", this message is not present in the packet protocol, hax!", Logging.logType.haxEvent);
ObjectTree.Net.Game.blackListIpAddress(this.Session.ipAddress);
ObjectTree.Sessions.destroySession(this.Session.ID); */
}
else
Logging.Log("No target method for client message " + this.Request.ID + " [\"" + Request.encodedID + "\": " + Request.methodName + "] found in any of the registered reactors.", Logging.logType.targetMethodNotFoundEvent);
}
///
/// Sends the key of an error, whose description value is inside the external_texts of the client.
///
/// The external_texts key of the error description.
public void sendLocalizedError(string localizedKey)
{
serverMessage Message = new serverMessage(33); // "@a"
Message.Append(localizedKey);
this.sendMessage(Message);
}
///
/// Initializes the RC4 encryption instance of this connection with a given public key.
///
/// The public key to set.
public void initializeEncryption(string Key)
{
this.encryptionClient = new rc4Provider(Key);
}
#endregion
}
}