Add Multiple transports system, begin work on WebSockets

This commit is contained in:
Li 2022-11-16 01:59:32 +13:00
parent e869a23463
commit e74f66a439
22 changed files with 3752 additions and 3301 deletions

View file

@ -48,11 +48,13 @@ namespace HISP.Cli
{
fs.Close();
fs.Dispose();
fs = null;
}
if(sw != null)
{
sw.Close();
sw.Dispose();
sw = null;
}
shutdownHandle.Set();

View file

@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.8.36.0")]
[assembly: AssemblyFileVersion("1.8.36.0")]
[assembly: AssemblyVersion("1.8.38.0")]
[assembly: AssemblyFileVersion("1.8.38.0")]

View file

@ -1,5 +1,5 @@
Package: hisp
Version: 1.8.36
Version: 1.8.38
Depends: coreutils,systemd,mariadb-server,libsqlite3-dev,zlib1g-dev,libicu-dev,libkrb5-dev
Maintainer: Li
Homepage: https://islehorse.com

View file

@ -1,4 +1,6 @@
using HISP.Tests.Properties;
//#define GENERATE
using HISP.Tests.Properties;
using HISP.Game.SwfModules;
using HISP.Game;
using HISP.Server;
@ -11,47 +13,44 @@ using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace HISP.Tests.UnitTests
{
public class PacketTest
{
private const bool GENERATE = false;
private static Dictionary<string, string> knownGoodPackets = new Dictionary<string, string>();
public static bool Test(string name, byte[] packet)
{
#if GENERATE
knownGoodPackets.Add(name, Convert.ToBase64String(packet));
return true;
#else
string goodPacketStr = null;
knownGoodPackets.TryGetValue(name, out goodPacketStr);
byte[] goodPacket = Convert.FromBase64String(goodPacketStr);
if (GENERATE)
if(!goodPacket.SequenceEqual(packet))
{
knownGoodPackets.Add(name, Convert.ToBase64String(packet));
ResultLogger.LogTestResult(false, "PACKET_TEST "+name, BitConverter.ToString(packet).Replace("-", ""), goodPacket.ToString().Replace("-", ""));
return false;
}
else
{
string goodPacketStr = null;
knownGoodPackets.TryGetValue(name, out goodPacketStr);
byte[] goodPacket = Convert.FromBase64String(goodPacketStr);
if(!goodPacket.SequenceEqual(packet))
{
ResultLogger.LogTestResult(false, "PACKET_TEST "+name, BitConverter.ToString(packet).Replace("-", ""), goodPacket.ToString().Replace("-", ""));
}
else
{
ResultLogger.LogTestStatus(true, "PACKET_TEST " + name, "Success.");
}
ResultLogger.LogTestStatus(true, "PACKET_TEST " + name, "Success.");
return true;
}
return true;
#endif
}
public static bool RunPacketTest()
{
if (!GENERATE)
{
JObject jobj = JsonConvert.DeserializeObject(Resources.PacketTestDataSet) as JObject;
knownGoodPackets = jobj.ToObject<Dictionary<string, string>>();
}
#if GENERATE
JObject jobj = JsonConvert.DeserializeObject(Resources.PacketTestDataSet) as JObject;
knownGoodPackets = jobj.ToObject<Dictionary<string, string>>();
#endif
List<bool> results = new List<bool>();
@ -401,11 +400,10 @@ namespace HISP.Tests.UnitTests
results.Add(Test("TimeAndWeatherUpdate", PacketBuilder.CreateTimeAndWeatherUpdate(10, 4, 541, "SUNNY")));
results.Add(Test("WeatherUpdate", PacketBuilder.CreateWeatherUpdate("CLOUD")));
if (GENERATE)
{
string resultsStr = JsonConvert.SerializeObject(knownGoodPackets, Formatting.Indented);
File.WriteAllText("test.json", resultsStr);
}
#if GENERATE
string resultsStr = JsonConvert.SerializeObject(knownGoodPackets, Formatting.Indented);
File.WriteAllText("test.json", resultsStr);
#endif
foreach(bool result in results)
{

View file

@ -30,8 +30,8 @@ using System.Runtime.InteropServices;
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.8.36.0")]
[assembly: AssemblyFileVersion("1.8.36.0")]
[assembly: AssemblyVersion("1.8.38.0")]
[assembly: AssemblyFileVersion("1.8.38.0")]

View file

@ -2,11 +2,15 @@
# Horse Isle Server Configuration
# =======================
#
# HISP was Created and Developed by SilicaAndPina
# HISP was Created and Developed by Li / SilicaAndPina
# However it is NOT COPYRIGHTED! This software is in the Public Domain!
#
# Ip address the server will bind to (default: 0.0.0.0 ALL INTERFACES)
# =======================
# Network
# =======================
# Ip address the server will bind to (default: 0.0.0.0 ALL INTERFACES)
ip=0.0.0.0
# Port the server will bind to defaults: (on beta.horseisle.com: 12321, on pinto.horseisle.com: 443)
@ -14,6 +18,15 @@ ip=0.0.0.0
# running on the same port, so i prefer 12321.
port=12321
# Listen for WebSockets as well as raw Flash XMLSocket Connection
# If this feature is enabled, players can play Horse Isle
# on HTML5 and WebAssembly with a fork of the Ruffle Flash Player Emulator
enable_websocket=true
# =======================
# Database
# =======================
# MariaDB Database Information
# For best performance, the database should be hosted on the SAME MACHINE as the HISP server.
# Or atleast, on a local network.
@ -26,16 +39,6 @@ db_port=3306
# Connect to a sqllite database instead of a sql server.
sql_lite=false
# File that contains the map tile data
# the default was downloaded from the original server
map=HI1.MAP
# This folder contains all definitions in the game
# such as items, horses. and quest data.
# NOTE: This can be a folder or a file.
gamedata=gamedata
# =======================
# Security
# =======================
@ -71,6 +74,15 @@ enable_spam_filter=true
# Misc Settings.
# =======================
# File that contains the map tile data
# the default was downloaded from the original server
map=HI1.MAP
# This folder contains all definitions in the game
# such as items, horses. and quest data.
# NOTE: This can be a folder or a file.
gamedata=gamedata
# Should the server consider all users "Subscribers"
# (warning: makes ranches be in use forever.)
all_users_subscribed=false

View file

@ -70,6 +70,13 @@ namespace HISP.Security
return decrypt.Replace(" ", "");
}
public static byte[] Sha1Digest(byte[] message)
{
using (SHA1 sha1 = SHA1.Create())
return sha1.ComputeHash(message);
}
public static byte[] Sha512Digest(byte[] message)
{
using (SHA512 sha512 = SHA512.Create())

View file

@ -5,7 +5,7 @@ namespace HISP.Security
{
public class CrossDomainPolicy
{
public static byte[] GetPolicy()
public static byte[] GetPolicyFile()
{
if (!File.Exists(ConfigReader.CrossDomainPolicyFile)) {
Logger.InfoPrint("Cross-Domain-Policy file not found, using default");

View file

@ -21,7 +21,6 @@ namespace HISP.Server
public static string GameData = "gamedata.json";
public static string CrossDomainPolicyFile = "CrossDomainPolicy.xml";
public static string ModsFolder = "mods";
public static int LogLevel = 4;
public static bool SqlLite = false;
@ -32,6 +31,8 @@ namespace HISP.Server
public static bool EnableCorrections = true;
public static bool EnableNonViolations = true;
public static bool EnableWebSocket = true;
public static string ConfigurationFileName = "server.properties";
public static void OpenConfig()
{
@ -99,28 +100,28 @@ namespace HISP.Server
CrossDomainPolicyFile = data;
break;
case "all_users_subscribed":
AllUsersSubbed = data == "true";
AllUsersSubbed = (data == "true");
break;
case "enable_corrections":
EnableCorrections = data == "true";
EnableCorrections = (data == "true");
break;
case "sql_lite":
SqlLite = data == "true";
SqlLite = (data == "true");
break;
case "enable_non_violation_check":
EnableNonViolations = data == "true";
EnableNonViolations = (data == "true");
break;
case "enable_spam_filter":
EnableSpamFilter = data == "true";
EnableSpamFilter = (data == "true");
break;
case "enable_websocket":
EnableWebSocket = (data == "true");
break;
case "fix_offical_bugs":
FixOfficalBugs = data == "true";
FixOfficalBugs = (data == "true");
break;
case "enable_word_filter":
EnableSwearFilter = data == "true";
break;
case "mods_folder":
ModsFolder = data;
EnableSwearFilter = (data == "true");
break;
case "intrest_rate":
IntrestRate = int.Parse(data);

View file

@ -39,7 +39,7 @@ namespace HISP.Server
RegisterCrashHandler();
Console.Title = ServerVersion.GetBuildString();
ConfigReader.OpenConfig();
CrossDomainPolicy.GetPolicy();
CrossDomainPolicy.GetPolicyFile();
Database.OpenDatabase();
GameDataJson.ReadGamedata();

View file

@ -8,6 +8,8 @@ using HISP.Game.Horse;
using HISP.Game.Events;
using HISP.Game.Items;
using HISP.Util;
using HISP.Server.Network;
using System.Net;
namespace HISP.Server
{
@ -22,9 +24,17 @@ namespace HISP.Server
}
}
public Socket ClientSocket;
public string RemoteIp;
private Transport networkTransport;
private bool loggedIn = false;
public string RemoteIp
{
get
{
return networkTransport.Ip;
}
}
public bool LoggedIn
{
get
@ -50,7 +60,7 @@ namespace HISP.Server
private Timer kickTimer;
private Timer minuteTimer;
private bool isDisconnecting = false;
private int timeoutInterval = 95 * 1000;
private int totalMinutesElapsed = 0;
@ -58,40 +68,19 @@ namespace HISP.Server
private int warnInterval = GameServer.IdleWarning * 60 * 1000; // Time before showing a idle warning
private int kickInterval = GameServer.IdleTimeout * 60 * 1000; // Time before kicking for inactivity
private List<byte> currentPacket = new List<byte>();
private byte[] workBuffer = new byte[0x8000];
public GameClient(Socket clientSocket)
{
clientSocket.SendTimeout = 10 * 1000; // 10sec
clientSocket.ReceiveTimeout = 10 * 1000; // 10sec
ClientSocket = clientSocket;
if(clientSocket.RemoteEndPoint != null)
{
RemoteIp = clientSocket.RemoteEndPoint.ToString();
if (RemoteIp.Contains(":"))
RemoteIp = RemoteIp.Substring(0, RemoteIp.IndexOf(":"));
Logger.DebugPrint("Client connected @ " + RemoteIp);
}
else
{
Logger.DebugPrint("Client connected @ (IP UNKNOWN) // How is this possible?");
}
kickTimer = new Timer(new TimerCallback(kickTimerTick), null, kickInterval, kickInterval);
warnTimer = new Timer(new TimerCallback(warnTimerTick), null, warnInterval, warnInterval);
minuteTimer = new Timer(new TimerCallback(minuteTimerTick), null, oneMinute, oneMinute);
connectedClients.Add(this);
networkTransport = new Hybrid();
networkTransport.Accept(clientSocket, parsePackets, disconnectHandler);
Logger.DebugPrint(networkTransport.Name + " : Client connected @ " + networkTransport.Ip);
SocketAsyncEventArgs evt = new SocketAsyncEventArgs();
evt.Completed += receivePackets;
evt.SetBuffer(workBuffer, 0, workBuffer.Length);
if (!clientSocket.ReceiveAsync(evt))
receivePackets(null, evt);
}
public static void OnShutdown()
@ -120,26 +109,29 @@ namespace HISP.Server
}
public static void CreateClient(object sender, SocketAsyncEventArgs e)
{
#if !DEBUG
try
{
#endif
do
{
Socket eSocket = e.AcceptSocket;
Socket clientSocket = e.AcceptSocket;
if (GameServer.ServerSocket == null)
return;
if (eSocket == null)
if (clientSocket == null)
continue;
if (eSocket.RemoteEndPoint == null)
if (clientSocket.RemoteEndPoint == null)
continue;
new GameClient(eSocket);
connectedClients.Add(new GameClient(clientSocket));
e.AcceptSocket = null;
if (GameServer.ServerSocket == null)
return;
} while (!GameServer.ServerSocket.AcceptAsync(e));
#if !DEBUG
}
catch (ObjectDisposedException ex) { Logger.ErrorPrint("Server shutdown due to " + ex.Message); } // server shutdown
#endif
}
private void timeoutTimerTick(object state)
{
@ -149,20 +141,8 @@ namespace HISP.Server
}
}
public void Disconnect()
private void disconnectHandler()
{
if (this.isDisconnecting)
return;
this.isDisconnecting = true;
// Close Socket
if (ClientSocket != null)
{
ClientSocket.Disconnect(false);
ClientSocket.Dispose();
ClientSocket = null;
}
// Stop Timers
if (timeoutTimer != null)
@ -191,50 +171,11 @@ namespace HISP.Server
connectedClients.Remove(this);
GameServer.OnDisconnect(this);
LoggedIn = false;
}
private void receivePackets(object sender, SocketAsyncEventArgs e)
public void Disconnect()
{
do
{
// HI1 Packets are terminates by 0x00 so we have to read until we receive that terminator
if (isDisconnecting ||
ClientSocket == null ||
e.BytesTransferred <= 0 ||
!ClientSocket.Connected ||
e.SocketError != SocketError.Success)
{
Disconnect();
return;
}
int availble = e.BytesTransferred;
if (availble >= 1) // More than 1 byte transfered..
{
for (int i = 0; i < availble; i++)
{
currentPacket.Add(e.Buffer[i]);
if (e.Buffer[i] == PacketBuilder.PACKET_TERMINATOR) // Read until \0...
{
parsePackets(currentPacket.ToArray());
currentPacket.Clear();
}
}
}
if (availble == 0)
Disconnect();
if (isDisconnecting || ClientSocket == null)
return;
} while (!ClientSocket.ReceiveAsync(e));
if(!networkTransport.Disconnected)
networkTransport.Disconnect();
}
private void keepAliveTick(object state)
@ -242,8 +183,7 @@ namespace HISP.Server
Logger.DebugPrint("Sending keep-alive packet to " + LoggedinUser.Username);
byte[] updatePacket = PacketBuilder.CreateKeepAlive();
SendPacket(updatePacket);
if(!isDisconnecting && keepAliveTimer != null) // wtf how is this still a problem?
keepAliveTimer.Change(oneMinute, oneMinute);
keepAliveTimer.Change(oneMinute, oneMinute);
}
private void minuteTimerTick(object state)
{
@ -406,9 +346,8 @@ namespace HISP.Server
if (totalMinutesElapsed % 15 == 0)
LoggedinUser.Tiredness--;
}
if (!isDisconnecting)
minuteTimer.Change(oneMinute, oneMinute);
minuteTimer.Change(oneMinute, oneMinute);
}
private void warnTimerTick(object state)
@ -450,13 +389,13 @@ namespace HISP.Server
timeoutTimer = new Timer(new TimerCallback(timeoutTimerTick), null, timeoutInterval, timeoutInterval);
}
private void parsePackets(byte[] Packet)
private void parsePackets(byte[] packet)
{
if (Packet.Length < 1)
if (packet.Length < 1)
{
Logger.ErrorPrint("Received an invalid packet (size: "+Packet.Length+")");
Logger.ErrorPrint("Received an invalid packet (size: "+packet.Length+")");
}
byte identifier = Packet[0];
byte identifier = packet[0];
/*
* Every time ive tried to fix this properly by just checking if its null or something
@ -514,11 +453,8 @@ namespace HISP.Server
{
switch (identifier)
{
case PacketBuilder.PACKET_FLASH_XML_CROSSDOMAIN:
GameServer.OnCrossdomainPolicyRequest(this, Packet);
break;
case PacketBuilder.PACKET_LOGIN:
GameServer.OnLoginRequest(this, Packet);
GameServer.OnUserLogin(this, packet);
break;
}
}
@ -527,76 +463,76 @@ namespace HISP.Server
switch (identifier)
{
case PacketBuilder.PACKET_LOGIN:
GameServer.OnUserInfoRequest(this, Packet);
GameServer.OnUserInfoRequest(this, packet);
break;
case PacketBuilder.PACKET_MOVE:
GameServer.OnMovementPacket(this, Packet);
GameServer.OnMovementPacket(this, packet);
break;
case PacketBuilder.PACKET_PLAYERINFO:
GameServer.OnPlayerInfoPacket(this, Packet);
GameServer.OnPlayerInfoPacket(this, packet);
break;
case PacketBuilder.PACKET_PLAYER:
GameServer.OnProfilePacket(this, Packet);
GameServer.OnProfilePacket(this, packet);
break;
case PacketBuilder.PACKET_CHAT:
GameServer.OnChatPacket(this, Packet);
GameServer.OnChatPacket(this, packet);
break;
case PacketBuilder.PACKET_CLICK:
GameServer.OnClickPacket(this, Packet);
GameServer.OnClickPacket(this, packet);
break;
case PacketBuilder.PACKET_KEEP_ALIVE:
GameServer.OnKeepAlive(this, Packet);
GameServer.OnKeepAlive(this, packet);
break;
case PacketBuilder.PACKET_TRANSPORT:
GameServer.OnTransportUsed(this, Packet);
GameServer.OnTransportUsed(this, packet);
break;
case PacketBuilder.PACKET_INVENTORY:
GameServer.OnInventoryRequested(this, Packet);
GameServer.OnInventoryRequested(this, packet);
break;
case PacketBuilder.PACKET_DYNAMIC_BUTTON:
GameServer.OnDynamicButtonPressed(this, Packet);
GameServer.OnDynamicButtonPressed(this, packet);
break;
case PacketBuilder.PACKET_DYNAMIC_INPUT:
GameServer.OnDynamicInputReceived(this, Packet);
GameServer.OnDynamicInputReceived(this, packet);
break;
case PacketBuilder.PACKET_ITEM_INTERACTION:
GameServer.OnItemInteraction(this, Packet);
GameServer.OnItemInteraction(this, packet);
break;
case PacketBuilder.PACKET_ARENA_SCORE:
GameServer.OnArenaScored(this, Packet);
GameServer.OnArenaScored(this, packet);
break;
case PacketBuilder.PACKET_QUIT:
GameServer.OnQuitPacket(this, Packet);
GameServer.OnQuitPacket(this, packet);
break;
case PacketBuilder.PACKET_NPC:
GameServer.OnNpcInteraction(this, Packet);
GameServer.OnNpcInteraction(this, packet);
break;
case PacketBuilder.PACKET_BIRDMAP:
GameServer.OnBirdMapRequested(this, Packet);
GameServer.OnBirdMapRequested(this, packet);
break;
case PacketBuilder.PACKET_SWFMODULE:
GameServer.OnSwfModuleCommunication(this, Packet);
GameServer.OnSwfModuleCommunication(this, packet);
break;
case PacketBuilder.PACKET_HORSE:
GameServer.OnHorseInteraction(this, Packet);
GameServer.OnHorseInteraction(this, packet);
break;
case PacketBuilder.PACKET_WISH:
GameServer.OnWish(this, Packet);
GameServer.OnWish(this, packet);
break;
case PacketBuilder.PACKET_RANCH:
GameServer.OnRanchPacket(this, Packet);
GameServer.OnRanchPacket(this, packet);
break;
case PacketBuilder.PACKET_AUCTION:
GameServer.OnAuctionPacket(this, Packet);
GameServer.OnAuctionPacket(this, packet);
break;
case PacketBuilder.PACKET_PLAYER_INTERACTION:
GameServer.OnPlayerInteration(this, Packet);
GameServer.OnPlayerInteration(this, packet);
break;
case PacketBuilder.PACKET_SOCIALS:
GameServer.OnSocialPacket(this, Packet);
GameServer.OnSocialPacket(this, packet);
break;
default:
Logger.ErrorPrint("Unimplemented Packet: " + BitConverter.ToString(Packet).Replace('-', ' '));
Logger.ErrorPrint("Unimplemented packet: " + BitConverter.ToString(packet).Replace('-', ' '));
break;
}
}
@ -619,16 +555,9 @@ namespace HISP.Server
Logger.InfoPrint("CLIENT: "+RemoteIp+" KICKED for: "+Reason);
}
public void SendPacket(byte[] PacketData)
public void SendPacket(byte[] packetData)
{
try
{
ClientSocket.Send(PacketData);
}
catch (Exception)
{
Disconnect();
}
networkTransport.Send(packetData);
}
}

View file

@ -255,17 +255,7 @@ namespace HISP.Server
* eg: OnMovementPacket is whenever the server receies a movement request from the client.
*/
public static void OnCrossdomainPolicyRequest(GameClient sender, byte[] packet)
{
if (Encoding.UTF8.GetString(packet).StartsWith("<policy-file-request/>"))
{
Logger.DebugPrint("Cross-Domain-Policy request received from: " + sender.RemoteIp);
byte[] crossDomainPolicyResponse = CrossDomainPolicy.GetPolicy();
sender.SendPacket(crossDomainPolicyResponse);
}
}
// HI1 Protocol
public static void OnPlayerInteration(GameClient sender, byte[] packet)
{
@ -7353,7 +7343,7 @@ namespace HISP.Server
UpdateInventory(sender);
}
public static void OnLoginRequest(GameClient sender, byte[] packet)
public static void OnUserLogin(GameClient sender, byte[] packet)
{
Logger.DebugPrint("Login request received from: " + sender.RemoteIp);

View file

@ -0,0 +1,101 @@
using System;
using System.Net.Sockets;
namespace HISP.Server.Network
{
public class Hybrid : Transport
{
Transport actualTransport = null;
public override string Name
{
get
{
if(actualTransport == null)
return "TransportDeterminer";
else
return actualTransport.Name;
}
}
public override bool Disconnected
{
get
{
if (actualTransport == null)
return base.Disconnected;
else
return actualTransport.Disconnected;
}
}
public override string Ip
{
get
{
if (actualTransport == null)
return base.Ip;
else
return actualTransport.Ip;
}
}
public override void Disconnect()
{
if (actualTransport == null)
{
base.Disconnect();
}
else
{
actualTransport.Disconnect();
}
}
public override void ProcessReceivedPackets(int available, byte[] buffer)
{
for (int i = 0; i < available; i++)
base.currentPacket.Add(buffer[i]);
if (currentPacket.Count >= 3)
{
if (ConfigReader.EnableWebSocket && WebSocket.IsStartOfHandshake(currentPacket.ToArray()))
{
Logger.InfoPrint(this.Ip + " Switching to WebSocket");
actualTransport = new WebSocket();
actualTransport.passObjects(this.socket, this.onReceiveCallback, this.onDisconnectCallback);
actualTransport.ProcessReceivedPackets(available, buffer);
actualTransport.Accept(base.socket, base.onReceiveCallback, base.onDisconnectCallback);
}
else
{
Logger.InfoPrint(this.Ip + " Switching to XmlSocket");
actualTransport = new XmlSocket();
actualTransport.passObjects(this.socket, this.onReceiveCallback, this.onDisconnectCallback);
actualTransport.ProcessReceivedPackets(available, buffer);
actualTransport.Accept(base.socket, base.onReceiveCallback, base.onDisconnectCallback);
}
}
}
internal override void receivePackets(object sender, SocketAsyncEventArgs e)
{
if (!base.checkForError(e))
ProcessReceivedPackets(e.BytesTransferred, e.Buffer);
}
public override void Send(byte[] data)
{
if(actualTransport == null)
base.Send(data);
else
actualTransport.Send(data);
}
}
}

View file

@ -0,0 +1,16 @@
using System;
using System.Net.Sockets;
namespace HISP.Server.Network
{
public interface ITransport
{
public string Name { get; }
public bool Disconnected { get; }
public string Ip { get; }
public void Accept(Socket socket, Action<byte[]> onReceive, Action onDisconnect);
public void Send(byte[] data);
public void Disconnect();
}
}

View file

@ -0,0 +1,128 @@
using HISP.Util;
using System;
using System.Collections.Generic;
using System.Net.Sockets;
namespace HISP.Server.Network
{
public abstract class Transport : ITransport
{
internal Socket socket;
internal string remoteIp;
internal Action<byte[]> onReceiveCallback;
internal Action onDisconnectCallback;
internal List<byte> currentPacket = new List<byte>();
internal byte[] workBuffer = new byte[0xFFFF];
internal bool isDisconnecting = false;
public abstract void ProcessReceivedPackets(int available, byte[] buffer);
public abstract string Name { get; }
internal virtual bool checkForError(SocketAsyncEventArgs e)
{
if (isDisconnecting || socket == null || e.BytesTransferred <= 0 || !socket.Connected || e.SocketError != SocketError.Success)
{
Disconnect();
return true;
}
else
{
return false;
}
}
internal virtual void receivePackets(object sender, SocketAsyncEventArgs e)
{
do
{
if (!checkForError(e))
ProcessReceivedPackets(e.BytesTransferred, e.Buffer);
else
break;
} while (!socket.ReceiveAsync(e));
}
public virtual string Ip
{
get
{
return this.remoteIp;
}
}
public virtual bool Disconnected
{
get
{
return this.isDisconnecting;
}
}
internal virtual void passObjects(Socket socket, Action<byte[]> onReceive, Action onDisconnect)
{
socket.SendTimeout = 10 * 1000; // 10sec
socket.ReceiveTimeout = 10 * 1000; // 10sec
this.socket = socket;
this.onReceiveCallback = onReceive;
this.onDisconnectCallback = onDisconnect;
this.remoteIp = Helper.GetIp(socket.RemoteEndPoint);
}
public virtual void Accept(Socket socket, Action<byte[]> onReceive, Action onDisconnect)
{
passObjects(socket, onReceive, onDisconnect);
SocketAsyncEventArgs e = new SocketAsyncEventArgs();
e.Completed += receivePackets;
e.SetBuffer(workBuffer, 0, workBuffer.Length);
if (!socket.ReceiveAsync(e))
receivePackets(null, e);
}
public virtual void Disconnect()
{
if (this.isDisconnecting)
return;
this.isDisconnecting = true;
// Close Socket
if (socket != null)
{
try
{
socket.Disconnect(false);
socket.Dispose();
socket = null;
}
catch (SocketException e) { }
catch (ObjectDisposedException e) { };
}
onDisconnectCallback();
}
public virtual void Send(byte[] data)
{
if (Disconnected) return;
if (data == null) return;
try
{
socket.Send(data);
}
catch (ObjectDisposedException)
{
if (!Disconnected)
Disconnect();
}
}
}
}

View file

@ -0,0 +1,196 @@
using HISP.Security;
using HISP.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace HISP.Server.Network
{
public class WebSocket : Transport
{
private const string WEBSOCKET_SEED = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private const byte WEBASSEMBLY_CONTINUE = 0x0;
private const byte WEBASSEMBLY_TEXT = 0x1;
private const byte WEBASSEMBLY_LENGTH_INT16 = 0x7E;
private const byte WEBASSEMBLY_LENGTH_INT64 = 0x7F;
private List<byte> currentMessage = new List<byte>();
private string secWebsocketKey = null;
private bool handshakeDone = false;
private Dictionary<string, string> parseHttpHeaders(string httpResponse)
{
Dictionary<string, string> httpHeaders = new Dictionary<string, string>();
string[] parts = httpResponse.Replace("\r", "").Split("\n");
foreach (string part in parts)
{
if (part.StartsWith("GET")) continue;
if (part.Contains(":"))
{
string[] keyValuePairs = part.Split(":");
if (keyValuePairs.Length >= 2)
httpHeaders.Add(keyValuePairs[0].Trim().ToLower(), keyValuePairs[1].Trim());
}
else
{
continue;
}
}
return httpHeaders;
}
private string deriveWebsocketSecKey(string webSocketKey)
{
byte[] derivedKey = Authentication.Sha1Digest(Encoding.UTF8.GetBytes(webSocketKey.Trim() + WEBSOCKET_SEED.Trim()));
return Convert.ToBase64String(derivedKey);
}
private byte[] createHandshakeResponse(string secWebsocketKey)
{
return Encoding.UTF8.GetBytes(String.Join("\r\n", new string[] {
"HTTP/1.1 101 Switching Protocols",
"Connection: Upgrade",
"Upgrade: websocket",
"Sec-WebSocket-Accept: " + secWebsocketKey,
"",
""
}));
}
private byte[] parseHandshake(string handshakeResponse)
{
Dictionary<string, string> headers = parseHttpHeaders(handshakeResponse);
string webSocketKey = null;
headers.TryGetValue("sec-websocket-key", out webSocketKey);
if (webSocketKey != null)
{
string secWebsocketKey = deriveWebsocketSecKey(webSocketKey);
return createHandshakeResponse(secWebsocketKey);
}
return createHandshakeResponse("");
}
public static bool IsStartOfHandshake(byte[] data)
{
return Helper.ByteArrayStartsWith(data, Encoding.UTF8.GetBytes("GET"));
}
public static bool IsEndOfHandshake(byte[] data)
{
return Helper.ByteArrayEndsWith(data, Encoding.UTF8.GetBytes("\r\n\r\n"));
}
public override void ProcessReceivedPackets(int available, byte[] buffer)
{
for (int i = 0; i < available; i++)
currentPacket.Add(buffer[i]);
byte[] webAsmMsg = currentPacket.ToArray();
if (!handshakeDone)
{
if (IsStartOfHandshake(webAsmMsg) && IsEndOfHandshake(webAsmMsg))
{
string httpHandshake = Encoding.UTF8.GetString(webAsmMsg);
byte[] handshakeResponse = parseHandshake(httpHandshake);
base.Send(handshakeResponse);
currentPacket.Clear();
handshakeDone = true;
}
}
if (currentPacket.Count >= 2)
{
bool finished = (currentPacket[0] & 0b10000000) != 0;
int opcode = (currentPacket[0] & 0b00001111);
bool mask = (currentPacket[1] & 0b10000000) != 0;
UInt64 messageLength = Convert.ToUInt64(currentPacket[1] & 0b01111111);
int offset = 2;
if (messageLength == WEBASSEMBLY_LENGTH_INT16)
{
if(currentPacket.Count >= offset + 2)
{
byte[] uint16Bytes = new byte[2];
Array.ConstrainedCopy(webAsmMsg, offset, uint16Bytes, 0, uint16Bytes.Length);
uint16Bytes = uint16Bytes.Reverse().ToArray();
messageLength = BitConverter.ToUInt16(uint16Bytes);
offset += uint16Bytes.Length;
}
}
else if (messageLength == WEBASSEMBLY_LENGTH_INT64)
{
if (currentPacket.Count >= offset + 8)
{
byte[] uint64Bytes = new byte[8];
Array.ConstrainedCopy(webAsmMsg, offset, uint64Bytes, 0, uint64Bytes.Length);
uint64Bytes = uint64Bytes.Reverse().ToArray();
messageLength = BitConverter.ToUInt64(uint64Bytes);
offset += uint64Bytes.Length;
}
}
if (mask)
{
switch (opcode)
{
case WEBASSEMBLY_TEXT:
if (currentPacket.LongCount() >= (offset + 4))
{
byte[] unmaskKey = new byte[4];
Array.ConstrainedCopy(buffer, offset, unmaskKey, 0, unmaskKey.Length);
offset += unmaskKey.Length;
for (int i = 0; i < (currentPacket.Count - offset); i++)
{
currentMessage.Add(Convert.ToByte(currentPacket[offset+ i] ^ unmaskKey[i % unmaskKey.Length]));
}
currentPacket.Clear();
}
break;
}
if (finished)
{
onReceiveCallback(currentMessage.ToArray());
currentMessage.Clear();
currentPacket.Clear();
}
}
}
}
public override string Name
{
get
{
return "WebSocket";
}
}
public override void Send(byte[] data)
{
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,38 @@
using HISP.Security;
using HISP.Util;
using System.Text;
namespace HISP.Server.Network
{
public class XmlSocket : Transport
{
public override void ProcessReceivedPackets(int available, byte[] buffer)
{
// In XmlSocket Packets are terminates by 0x00 so we have to read until we receive that terminator
for (int i = 0; i < available; i++)
{
currentPacket.Add(buffer[i]);
if (buffer[i] == PacketBuilder.PACKET_TERMINATOR) // Read until \0...
{
onReceiveCallback(currentPacket.ToArray());
currentPacket.Clear();
}
}
// Handle XMLSocket Policy File
if (Helper.ByteArrayStartsWith(buffer, Encoding.UTF8.GetBytes("<policy-file-request/>")))
{
this.Send(CrossDomainPolicy.GetPolicyFile());
}
}
public override string Name
{
get
{
return "XmlSocket";
}
}
}
}

View file

@ -1,4 +1,6 @@
using System;
#define WEBSOCKET_ENABLED
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
@ -11,9 +13,9 @@ namespace HISP.Server
{
public class PacketBuilder
{
public const byte PACKET_TERMINATOR = 0x00;
public const byte PACKET_CLIENT_TERMINATOR = 0x0A;
public const byte PACKET_FLASH_XML_CROSSDOMAIN = 0x3C;
public const byte PACKET_LOGIN = 0x7F;
public const byte PACKET_CHAT = 0x14;

View file

@ -2,6 +2,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
namespace HISP.Util
{
@ -52,10 +54,31 @@ namespace HISP.Util
return dtDateTime;
}
public static bool ByteArrayStartsWith(byte[] byteArray, byte[] searchValue)
{
if (byteArray.Length < searchValue.Length) return false;
byte[] buffer = new byte[searchValue.Length];
Array.ConstrainedCopy(byteArray, 0, buffer, 0, searchValue.Length);
return buffer.SequenceEqual(searchValue);
}
public static bool ByteArrayEndsWith(byte[] byteArray, byte[] searchValue)
{
if (searchValue.Length > byteArray.Length) return false;
byte[] buffer = new byte[searchValue.Length];
Array.ConstrainedCopy(byteArray, (byteArray.Length - searchValue.Length), buffer, 0, searchValue.Length);
return buffer.SequenceEqual(searchValue);
}
public static void ByteArrayToByteList(byte[] byteArray, List<byte> byteList)
{
byteList.AddRange(byteArray.ToList());
}
public static string RandomString(string allowedCharacters)
{
int length = GameServer.RandomNumberGenerator.Next(7, 16);
@ -73,5 +96,13 @@ namespace HISP.Util
return newStr;
}
public static string GetIp(EndPoint ep)
{
string endPointIp = ep.ToString();
if (endPointIp.Contains(":"))
endPointIp = endPointIp.Substring(0, endPointIp.IndexOf(":"));
return endPointIp;
}
}
}

View file

@ -30,8 +30,8 @@ using System.Runtime.InteropServices;
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.8.36.0")]
[assembly: AssemblyFileVersion("1.8.36.0")]
[assembly: AssemblyVersion("1.8.38.0")]
[assembly: AssemblyFileVersion("1.8.38.0")]

View file

@ -129,7 +129,7 @@ namespace MPN00BS
Entry.SetShutdownCallback(OnShutdown);
ProgressCallback();
CrossDomainPolicy.GetPolicy();
CrossDomainPolicy.GetPolicyFile();
ProgressCallback();
GameDataJson.ReadGamedata();