MayoMayn
BestDev
- Oct 18, 2016
- 1,423
- 683
Windows is pretty useless when it comes to having a webserver on it.Windows is far from useless, anyway Mono Support will be implemented upon release.
Sent from my SM-G928F using Tapatalk
Windows is pretty useless when it comes to having a webserver on it.Windows is far from useless, anyway Mono Support will be implemented upon release.
I agree.Windows is pretty useless when it comes to having a webserver on it.
Sent from my SM-G928F using Tapatalk
public bool WorkingConnection()
{
try
{
using (var databaseConnection = GetConnection())
{
databaseConnection.OpenConnection();
databaseConnection.CloseConnection();
}
}
catch (MySqlException)
{
return false;
}
return true;
}
internal sealed class ServerProcessWorker : IDisposable
{
private Timer _serverTimer;
private readonly int _timerInterval;
private DateTime _lastUpdate;
private bool _disposing;
public ServerProcessWorker()
{
_timerInterval = 10000;
}
public void Start()
{
_serverTimer = new System.Threading.Timer(e => CheckStatus(), null, TimeSpan.Zero, TimeSpan.FromMilliseconds(_timerInterval));
}
private void CheckStatus()
{
if ((DateTime.Now - _lastUpdate).TotalMilliseconds <= _timerInterval )
{
return;
}
var serverUptime = DateTime.Now - Sahara.GetServer().GetServerInformation().ServerStarted;
var days = serverUptime.Days + " day" + (serverUptime.Days != 1 ? "s" : "") + ", ";
var hours = serverUptime.Hours + " hour" + (serverUptime.Hours != 1 ? "s" : "") + ", and ";
var minutes = serverUptime.Minutes + " minute" + (serverUptime.Minutes != 1 ? "s" : "");
var uptimeString = days + hours + minutes;
Console.Title = Sahara.GetServer().GetServerInformation().ServerName + " - Uptime: " + uptimeString;
_lastUpdate = DateTime.Now;
}
public void Dispose()
{
if (_disposing)
{
return;
}
_disposing = true;
_serverTimer.Dispose();
_serverTimer = null;
}
}
internal sealed class LogManager : IDisposable, ILog
{
private readonly List<string> _logsToSave;
private readonly ConcurrentDictionary<LogType, ConsoleColor> _logColors;
private readonly string _errorLogsFile;
private bool _isDisposing;
public LogManager()
{
_logsToSave = new List<string>();
_logColors = new ConcurrentDictionary<LogType, ConsoleColor>();
_logColors.TryAdd(LogType.Information, ConsoleColor.White);
_logColors.TryAdd(LogType.Debug, ConsoleColor.Cyan);
_logColors.TryAdd(LogType.Error, ConsoleColor.Red);
_logColors.TryAdd(LogType.Warning, ConsoleColor.Yellow);
_errorLogsFile = "/logs/error_logs.txt";
}
public void Information(string log)
{
Log(LogType.Information, log);
}
public void Debug(string log)
{
Log(LogType.Debug, log);
}
public void Error(string log, Exception exception = null)
{
if (exception != null)
{
HandleException(exception);
}
Log(LogType.Error, log);
}
public void Warn(string log)
{
Log(LogType.Warning, log);
}
private void Log(LogType logType, string log)
{
var oldConsoleColor = Console.ForegroundColor;
SetColor(logType);
Console.WriteLine(" " + DateTime.Now.ToString("HH:mm:ss") + " [" + logType.ToString() + "] " + log);
Console.ForegroundColor = oldConsoleColor;
}
private void HandleException(Exception exception)
{
_logsToSave.Add("Error occurred at " + DateTime.Now.ToString("HH:mm:ss"));
_logsToSave.Add(exception.Message);
_logsToSave.Add(exception.StackTrace);
_logsToSave.Add(string.Empty);
}
private void SetColor(LogType logType)
{
ConsoleColor colorToSet;
if (_logColors.TryGetValue(logType, out colorToSet))
{
Console.ForegroundColor = colorToSet;
return;
}
Console.ForegroundColor = ConsoleColor.White;
}
private void SaveLogs()
{
if (Utilities.IsFileLocked(new FileInfo(_errorLogsFile)))
{
return;
}
var stringBuilder = new StringBuilder();
foreach (var errorLog in _logsToSave)
{
stringBuilder.AppendLine(errorLog);
}
File.WriteAllText(_errorLogsFile, stringBuilder.ToString());
}
public void Dispose()
{
if (_isDisposing)
{
return;
}
_isDisposing = true;
SaveLogs();
}
}
Jw why give every room it's own cycle instead of just using 1 timer then cycling through each room instance on a calculated interval?I agree.
Updates:
- Finished AI manager
- Finished GameMapping
- Finished PathFinding
- Finished RoomManager and basic methods in RoomClient
- Finished a few more packets (even unhandled ones)
- Utilities separated to different types
- Recoded the logging (didn't like it)
- Sockets are now Asynchronous
Fixed up the database library a little, no longer runs a simple query to check for working connection.
Code:public bool WorkingConnection() { try { using (var databaseConnection = GetConnection()) { databaseConnection.OpenConnection(); databaseConnection.CloseConnection(); } } catch (MySqlException) { return false; } return true; }
Recoded the ServerProcessWorker (updating title)
Code:internal sealed class ServerProcessWorker : IDisposable { private Timer _serverTimer; private readonly int _timerInterval; private DateTime _lastUpdate; private bool _disposing; public ServerProcessWorker() { _timerInterval = 10000; } public void Start() { _serverTimer = new System.Threading.Timer(e => CheckStatus(), null, TimeSpan.Zero, TimeSpan.FromMilliseconds(_timerInterval)); } private void CheckStatus() { if ((DateTime.Now - _lastUpdate).TotalMilliseconds <= _timerInterval ) { return; } var serverUptime = DateTime.Now - Sahara.GetServer().GetServerInformation().ServerStarted; var days = serverUptime.Days + " day" + (serverUptime.Days != 1 ? "s" : "") + ", "; var hours = serverUptime.Hours + " hour" + (serverUptime.Hours != 1 ? "s" : "") + ", and "; var minutes = serverUptime.Minutes + " minute" + (serverUptime.Minutes != 1 ? "s" : ""); var uptimeString = days + hours + minutes; Console.Title = Sahara.GetServer().GetServerInformation().ServerName + " - Uptime: " + uptimeString; _lastUpdate = DateTime.Now; } public void Dispose() { if (_disposing) { return; } _disposing = true; _serverTimer.Dispose(); _serverTimer = null; } }
Logging
Code:internal sealed class LogManager : IDisposable, ILog { private readonly List<string> _logsToSave; private readonly ConcurrentDictionary<LogType, ConsoleColor> _logColors; private readonly string _errorLogsFile; private bool _isDisposing; public LogManager() { _logsToSave = new List<string>(); _logColors = new ConcurrentDictionary<LogType, ConsoleColor>(); _logColors.TryAdd(LogType.Information, ConsoleColor.White); _logColors.TryAdd(LogType.Debug, ConsoleColor.Cyan); _logColors.TryAdd(LogType.Error, ConsoleColor.Red); _logColors.TryAdd(LogType.Warning, ConsoleColor.Yellow); _errorLogsFile = "/logs/error_logs.txt"; } public void Information(string log) { Log(LogType.Information, log); } public void Debug(string log) { Log(LogType.Debug, log); } public void Error(string log, Exception exception = null) { if (exception != null) { HandleException(exception); } Log(LogType.Error, log); } public void Warn(string log) { Log(LogType.Warning, log); } private void Log(LogType logType, string log) { var oldConsoleColor = Console.ForegroundColor; SetColor(logType); Console.WriteLine(" " + DateTime.Now.ToString("HH:mm:ss") + " [" + logType.ToString() + "] " + log); Console.ForegroundColor = oldConsoleColor; } private void HandleException(Exception exception) { _logsToSave.Add("Error occurred at " + DateTime.Now.ToString("HH:mm:ss")); _logsToSave.Add(exception.Message); _logsToSave.Add(exception.StackTrace); _logsToSave.Add(string.Empty); } private void SetColor(LogType logType) { ConsoleColor colorToSet; if (_logColors.TryGetValue(logType, out colorToSet)) { Console.ForegroundColor = colorToSet; return; } Console.ForegroundColor = ConsoleColor.White; } private void SaveLogs() { if (Utilities.IsFileLocked(new FileInfo(_errorLogsFile))) { return; } var stringBuilder = new StringBuilder(); foreach (var errorLog in _logsToSave) { stringBuilder.AppendLine(errorLog); } File.WriteAllText(_errorLogsFile, stringBuilder.ToString()); } public void Dispose() { if (_isDisposing) { return; } _isDisposing = true; SaveLogs(); } }
UPDATES
GameClycing completed
Each Room has its own Cycle class (RoomCycle) that will cycle through things every 500ms.
Coded a caching system to rely less on MySQL
Instead of making calls to MySQL for various functions (Plus Emulator did this a lot) I have chosen to code a caching system that will cache pretty much anything. This will hopefully improve speed, performance and rely less on calling MySQL queries everywhere on Utility functions that rely on MySQL giving us the information.
Enhanced MySQL pooling
MySQL pooling has been implemented, this means that the connections will now be pooled instead of having to re-establish a new connection every time. I guess you could look at it as caching in a way.
Live Staff Updates
Now this is a feature that was given by me from a member of the community. Say you have a staff member online and you want to derank them but don't want to cause too much fuss in doing so, once their rank is reset to 1 in the database it will automatically update in-game without having to reload.
Please remember that all these ideas are ideas given by people who wanted them and all these features are easily enabled or disabled, you don't have to use them. Saharas main purpose was to listen to the users input unlike other emulator creators did, hopefully we can accomplish that goal.
I'm using a concurrent dictionary as the dictionary will be accessed from multiple threads. As for the cycle idea I guess you got me there, I'll change my code up tonight. As for the LogManager I was going to use log4net but I was in the mood to code a LogManager of my own.Jw why give every room it's own cycle instead of just using 1 timer then cycling through each room instance on a calculated interval?
Also, why are you using a Concurrent Dictionary for storing readonly values that are constant + small?
I don't really understand your reasons for the LogManager either.
Even still, accessing the data from a regular dictionary will do no different because the data in the dictionary will never be modified.I'm using a concurrent dictionary as the dictionary will be accessed from multiple threads. As for the cycle idea I guess you got me there, I'll change my code up tonight. As for the LogManager I was going to use log4net but I was in the mood to code a LogManager of my own.
Thank you, I wasn't aware it only applied if the data was modified.Even still, accessing the data from a regular dictionary will do no different because the data in the dictionary will never be modified.
if (Player.GetPlayerData().PlayerFigure.GetBody() == "idk-the-code-for-here")
{
// The user has a six pack...
}
Foreach loops are faster than LINQ in many cases (keep that in mind)Thank you, I wasn't aware it only applied if the data was modified.
Finished:
I have also coded a PlayerFigure.cs file which will come in handy when RP functionality is introduced and you want to do some advanced things with the figure, current methods:
- Quests
- Permissions
- Subscriptions
- Support
- Talents
- Landing View
- Games
GetHair()
GetFace()
GetBody()
GetClothes()
GetShoes()
IsTopless()
IsTrouserless()
WearingHat()
in the future you could possible do something such as (I don't really know the exact figure code for checking but you guys can do that.
PHP:if (Player.GetPlayerData().PlayerFigure.GetBody() == "idk-the-code-for-here") { // The user has a six pack... }
I'm really not sure how Habbo figuring works but I am sure someone will be able to shine some light on that when the time comes. This class can pretty much be extended to a lot more if you're smart about it.
Small updates include converting many for each loops into LINQ and removing redundant class qualifiers.
Thank you, I'll keep that in mind.Foreach loops are faster than LINQ in many cases (keep that in mind)
If your variable is dynamic then use auto properties, otherwise it seems cleaner to use an expression body when the value is predefined.Thank you, I'll keep that in mind.
I haven't really been doing much work recently, I have just been improving the code quality with small improvements and a lot of converting to string interpolation because I was too dumb to do that from the off.
Damn, I wish there was a VS extension that converted all my strings to string interpolation for me I've been using ReSharper recently but even the brain of that can't help me, code rush is actually the only extension that picks up string interpolation possibility (defaultly, not sure if ReSharper can be configured to do it?), I would use string.format but I prefer the look of string interpolation, and it gets converted to string.format on compilation anyway.
I really need to start using public fields instead of a private field with a public expression body, but I really like the look of a private field + public expression body, not sure which is better for performance, @Jaden ? obviously less code is better but is there any significant different.
using System.Collections.Concurrent;
using System.Data;
using System.Diagnostics;
namespace Sahara.Base.Game.Habbo.Other
{
public sealed class GameMessages
{
private readonly ConcurrentDictionary<string, string> _languageLocales;
internal GameMessages()
{
var stopwatch = Stopwatch.StartNew();
_languageLocales = new ConcurrentDictionary<string,string>();
InitializeLocales();
stopwatch.Stop();
Sahara.GetServer().GetLogManager().Information("Loaded Locales [" + stopwatch.ElapsedMilliseconds + "ms]");
}
private void InitializeLocales()
{
using (var mysqlConnection = Sahara.GetServer().GetDatabaseManager().GetConnection())
{
mysqlConnection.SetQuery("SELECT * FROM `server_locale`");
var localesTable = mysqlConnection.GetTable();
if (localesTable == null)
{
return;
}
foreach (DataRow localesRow in localesTable.Rows)
{
_languageLocales.TryAdd(localesRow["key"].ToString(), localesRow["value"].ToString());
}
}
}
internal bool TryGetLocale(string key, out string value)
{
return _languageLocales.TryGetValue(key, out value);
}
}
}
string notificationLocale;
if (Sirius.GetServer().GetGameManager().GetLanguageLocale().TryGetLocale("user_not_found", out notificationLocale))
{
player.SendGameNotification(notificationLocale);
}
string notificationLocale = "Locale wasn't found, you could enter a custom message here.";
Sirius.GetServer().GetGameManager().GetLanguageLocale().TryGetLocale("user_not_found", out notificationLocale);
player.SendGameNotification(notificationLocale);
# Mus Configuration
mus.max_connections=10
mus.allowed_hosts=127.0.0.1,192.168.0.1
private static void Main(string[] args)
{
Console.Title = "Sirius Mus Test";
try
{
_s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_s.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2345));
Console.WriteLine("Type something to mus...");
var sendToMus = Console.ReadLine();
var bytesToSend = Encoding.Default.GetBytes(sendToMus);
_s.BeginSend(bytesToSend, 0, bytesToSend.Length, 0, OnSend, null);
Console.WriteLine("sent to MUS at " + DateTime.Now.ToLongTimeString());
while (true)
{
Console.ReadKey();
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
}
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Sirius.Core.Logging;
namespace Sirius.Core.Mus
{
internal sealed class MusManager : IDisposable
{
private readonly Socket _musSocket;
private readonly LogManager _log;
private readonly ConcurrentDictionary<string, MusConnection> _musConnections;
private readonly List<string> _allowedIps;
private readonly Timer _timer;
private bool _disposing;
internal MusManager()
{
var stopwatch = Stopwatch.StartNew();
_musSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_log = Sirius.GetServer().GetLogManager();
_musConnections = new ConcurrentDictionary<string, MusConnection>();
_allowedIps = new List<string>();
_timer = new Timer(e => CheckConnections(), null, TimeSpan.Zero, TimeSpan.FromMilliseconds(10000));
var allowedIpsString = Sirius.GetServer().GetConfigManager().GetConfigElement("mus.allowed_hosts");
if (allowedIpsString.Contains(","))
{
foreach (var ipSplit in allowedIpsString.Split(','))
{
_allowedIps.Add(ipSplit);
}
}
else
{
_allowedIps.Add(allowedIpsString);
}
Listen();
stopwatch.Stop();
_log.Information("Loaded Mus Socket [" + stopwatch.ElapsedMilliseconds + "ms]");
}
private void CheckConnections()
{
var disconnectedCount = 0;
foreach (var musConnection in _musConnections.Values.Where(musConnection => !musConnection.StillConnected()))
{
musConnection.Dispose();
disconnectedCount++;
}
if (disconnectedCount > 0)
{
Sirius.GetServer().GetLogManager().Information("Disconnected " + disconnectedCount + " inactive mus connection" + (disconnectedCount != 0 ? "s" : "") + ".");
}
}
private void Listen()
{
try
{
_musSocket.Bind(new IPEndPoint(IPAddress.Any, 2345));
_musSocket.Listen(50);
_musSocket.BeginAccept(OnNewMusConnection, _musSocket);
}
catch (SocketException socketException)
{
_log.Error("Failed to start sockets: " + socketException.Message, socketException);
}
}
private void OnNewMusConnection(IAsyncResult asyncResult)
{
try
{
var server = (Socket)asyncResult.AsyncState;
var client = server.EndAccept(asyncResult);
var clientIp = client.RemoteEndPoint.ToString().Split(':')[0];
if (_musConnections.Count >= Convert.ToInt32(Sirius.GetServer().GetConfigManager().GetConfigElement("mus.max_connections")) || _musConnections.ContainsKey(clientIp))
{
_log.Information("Connection from mus rejected: too many connections.");
return;
}
_log.Debug("Connection from mus received. [" + clientIp + "]");
if (!_musConnections.TryAdd(clientIp, new MusConnection(client, clientIp)))
{
_log.Error("Failed to create new mus connection.");
}
}
catch (SocketException socketException)
{
_log.Error("Failed to accept mus connection: " + socketException.Message, socketException);
}
finally
{
_musSocket?.BeginAccept(OnNewMusConnection, _musSocket);
}
}
public void TryRemoveConnection(string ipAddress)
{
MusConnection connectionTemp;
if (_musConnections.TryRemove(ipAddress, out connectionTemp))
{
Sirius.GetServer().GetLogManager().Information("Mus connection has left. [" + ipAddress + "]");
}
}
public void Dispose()
{
if (_musSocket == null || _disposing)
{
return;
}
_disposing = true;
_musSocket.Shutdown(SocketShutdown.Both);
_musSocket.Close();
_musSocket.Dispose();
_musConnections.Clear();
foreach (var musConnection in _musConnections.Values)
{
musConnection.Dispose();
}
}
}
}
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Sirius.Core.Utility;
namespace Sirius.Core.Mus
{
internal sealed class MusConnection : IDisposable
{
private readonly Socket _socket;
private readonly string _ipAddress;
private readonly byte[] _buffer;
private bool _disposing;
internal MusConnection(Socket socket, string ipAddress)
{
_socket = socket;
_ipAddress = ipAddress;
_buffer = new byte[1024];
try
{
_socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, OnReceiveData, _socket);
}
catch
{
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
_socket.Dispose();
}
}
private void OnReceiveData(IAsyncResult asyncResult)
{
try
{
var receivedBytes = 0;
try
{
receivedBytes = _socket.EndReceive(asyncResult);
}
catch
{
Dispose();
return;
}
var data = Encoding.Default.GetString(_buffer, 0, receivedBytes);
if (data.Length < 1)
{
return;
}
Sirius.GetServer().GetLogManager().Information("Mus Connection: " + data);
}
catch (Exception exception)
{
Sirius.GetServer().GetLogManager().Error("Error receiving mus data: " + exception.Message, exception);
}
finally
{
_socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, OnReceiveData, _socket);
}
}
public bool StillConnected()
{
return Utilities.SocketConnected(_socket);
}
public void Dispose()
{
if (_disposing)
{
return;
}
_disposing = true;
Sirius.GetServer().GetMusManager().TryRemoveConnection(_ipAddress);
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
_socket.Dispose();
}
}
}
public static bool SocketConnected(Socket socket)
{
return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
}
internal static class InteractionTypes
{
internal static InteractionType GetTypeFromString(string pType)
{
var enumNames = Enum.GetNames(typeof(InteractionType));
var enumValueName = enumNames.FirstOrDefault(x => pType.IndexOf(x, StringComparison.OrdinalIgnoreCase) >= 0);
if (enumValueName != null)
{
return (InteractionType) Enum.Parse(typeof(InteractionType), enumValueName);
}
return InteractionType.None;
}
}
Looks good man! Keep it up. I will definitely make this emulator compatible with my CMS once it's released.Not really done much on this recently, but I have 2 weeks off college the coming week so hopefully I could get some development in during that time off.
Coded Language Locales
Code:using System.Collections.Concurrent; using System.Data; using System.Diagnostics; namespace Sahara.Base.Game.Habbo.Other { public sealed class GameMessages { private readonly ConcurrentDictionary<string, string> _languageLocales; internal GameMessages() { var stopwatch = Stopwatch.StartNew(); _languageLocales = new ConcurrentDictionary<string,string>(); InitializeLocales(); stopwatch.Stop(); Sahara.GetServer().GetLogManager().Information("Loaded Locales [" + stopwatch.ElapsedMilliseconds + "ms]"); } private void InitializeLocales() { using (var mysqlConnection = Sahara.GetServer().GetDatabaseManager().GetConnection()) { mysqlConnection.SetQuery("SELECT * FROM `server_locale`"); var localesTable = mysqlConnection.GetTable(); if (localesTable == null) { return; } foreach (DataRow localesRow in localesTable.Rows) { _languageLocales.TryAdd(localesRow["key"].ToString(), localesRow["value"].ToString()); } } } internal bool TryGetLocale(string key, out string value) { return _languageLocales.TryGetValue(key, out value); } } }
As I said before, I'll keep the same kind-of code structure as Plus as well as the same database structure so it'll be easier for new users to use. Language Locales aren't sent as a notification if they weren't sent (in noob language.. if you deleted a language locale in your database, it wont even try and send the notification), example below
Code:string notificationLocale; if (Sirius.GetServer().GetGameManager().GetLanguageLocale().TryGetLocale("user_not_found", out notificationLocale)) { player.SendGameNotification(notificationLocale); }
You could always do this, if you really wanted too...
Code:string notificationLocale = "Locale wasn't found, you could enter a custom message here."; Sirius.GetServer().GetGameManager().GetLanguageLocale().TryGetLocale("user_not_found", out notificationLocale); player.SendGameNotification(notificationLocale);
I'm back with some real updates. I have recently been working on a small thing called Sirius Mus Manager, and before you guys see the word Sirius and wonder 'what the hell', I have renamed Sahara to Sirius, as I preferred the name.
I finally implemented mus
Disconnection notifications:
Sending data:
As you can see, I show examples of connecting, disconnect, and maximum connections above in screenshots.
How does maximum connections work?
There is also a limit on 1 connection per IP, I haven't added a configuration element for that as I do't think it needs one, why would you need multiple connections from the same IP? unless you're doing something like public access to your mus etc.Code:# Mus Configuration mus.max_connections=10 mus.allowed_hosts=127.0.0.1,192.168.0.1
I rushed the Mus Manager thing, it is literally the fastest program I have ever made, the code is below if you want to look at it..
Code:private static void Main(string[] args) { Console.Title = "Sirius Mus Test"; try { _s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _s.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2345)); Console.WriteLine("Type something to mus..."); var sendToMus = Console.ReadLine(); var bytesToSend = Encoding.Default.GetBytes(sendToMus); _s.BeginSend(bytesToSend, 0, bytesToSend.Length, 0, OnSend, null); Console.WriteLine("sent to MUS at " + DateTime.Now.ToLongTimeString()); while (true) { Console.ReadKey(); } } catch (Exception e) { Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); } }
Code Snippets?
Code:using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading; using Sirius.Core.Logging; namespace Sirius.Core.Mus { internal sealed class MusManager : IDisposable { private readonly Socket _musSocket; private readonly LogManager _log; private readonly ConcurrentDictionary<string, MusConnection> _musConnections; private readonly List<string> _allowedIps; private readonly Timer _timer; private bool _disposing; internal MusManager() { var stopwatch = Stopwatch.StartNew(); _musSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _log = Sirius.GetServer().GetLogManager(); _musConnections = new ConcurrentDictionary<string, MusConnection>(); _allowedIps = new List<string>(); _timer = new Timer(e => CheckConnections(), null, TimeSpan.Zero, TimeSpan.FromMilliseconds(10000)); var allowedIpsString = Sirius.GetServer().GetConfigManager().GetConfigElement("mus.allowed_hosts"); if (allowedIpsString.Contains(",")) { foreach (var ipSplit in allowedIpsString.Split(',')) { _allowedIps.Add(ipSplit); } } else { _allowedIps.Add(allowedIpsString); } Listen(); stopwatch.Stop(); _log.Information("Loaded Mus Socket [" + stopwatch.ElapsedMilliseconds + "ms]"); } private void CheckConnections() { var disconnectedCount = 0; foreach (var musConnection in _musConnections.Values.Where(musConnection => !musConnection.StillConnected())) { musConnection.Dispose(); disconnectedCount++; } if (disconnectedCount > 0) { Sirius.GetServer().GetLogManager().Information("Disconnected " + disconnectedCount + " inactive mus connection" + (disconnectedCount != 0 ? "s" : "") + "."); } } private void Listen() { try { _musSocket.Bind(new IPEndPoint(IPAddress.Any, 2345)); _musSocket.Listen(50); _musSocket.BeginAccept(OnNewMusConnection, _musSocket); } catch (SocketException socketException) { _log.Error("Failed to start sockets: " + socketException.Message, socketException); } } private void OnNewMusConnection(IAsyncResult asyncResult) { try { var server = (Socket)asyncResult.AsyncState; var client = server.EndAccept(asyncResult); var clientIp = client.RemoteEndPoint.ToString().Split(':')[0]; if (_musConnections.Count >= Convert.ToInt32(Sirius.GetServer().GetConfigManager().GetConfigElement("mus.max_connections")) || _musConnections.ContainsKey(clientIp)) { _log.Information("Connection from mus rejected: too many connections."); return; } _log.Debug("Connection from mus received. [" + clientIp + "]"); if (!_musConnections.TryAdd(clientIp, new MusConnection(client, clientIp))) { _log.Error("Failed to create new mus connection."); } } catch (SocketException socketException) { _log.Error("Failed to accept mus connection: " + socketException.Message, socketException); } finally { _musSocket?.BeginAccept(OnNewMusConnection, _musSocket); } } public void TryRemoveConnection(string ipAddress) { MusConnection connectionTemp; if (_musConnections.TryRemove(ipAddress, out connectionTemp)) { Sirius.GetServer().GetLogManager().Information("Mus connection has left. [" + ipAddress + "]"); } } public void Dispose() { if (_musSocket == null || _disposing) { return; } _disposing = true; _musSocket.Shutdown(SocketShutdown.Both); _musSocket.Close(); _musSocket.Dispose(); _musConnections.Clear(); foreach (var musConnection in _musConnections.Values) { musConnection.Dispose(); } } } }
Code:using System; using System.Collections.Generic; using System.Net.Sockets; using System.Text; using System.Threading; using Sirius.Core.Utility; namespace Sirius.Core.Mus { internal sealed class MusConnection : IDisposable { private readonly Socket _socket; private readonly string _ipAddress; private readonly byte[] _buffer; private bool _disposing; internal MusConnection(Socket socket, string ipAddress) { _socket = socket; _ipAddress = ipAddress; _buffer = new byte[1024]; try { _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, OnReceiveData, _socket); } catch { _socket.Shutdown(SocketShutdown.Both); _socket.Close(); _socket.Dispose(); } } private void OnReceiveData(IAsyncResult asyncResult) { try { var receivedBytes = 0; try { receivedBytes = _socket.EndReceive(asyncResult); } catch { Dispose(); return; } var data = Encoding.Default.GetString(_buffer, 0, receivedBytes); if (data.Length < 1) { return; } Sirius.GetServer().GetLogManager().Information("Mus Connection: " + data); } catch (Exception exception) { Sirius.GetServer().GetLogManager().Error("Error receiving mus data: " + exception.Message, exception); } finally { _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, OnReceiveData, _socket); } } public bool StillConnected() { return Utilities.SocketConnected(_socket); } public void Dispose() { if (_disposing) { return; } _disposing = true; Sirius.GetServer().GetMusManager().TryRemoveConnection(_ipAddress); _socket.Shutdown(SocketShutdown.Both); _socket.Close(); _socket.Dispose(); } } }
I had a crash earlier so this code isn't 100% and it isn't finished, it's just the example I used and it'll be edited a fair bit before production.
If you're also wondering about how I check if a socket is still connected, here is that method.
Code:public static bool SocketConnected(Socket socket) { return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0); }
Hello. I haven't done anything much since my last post except clean up some of the code. I have switched out long switch statemenets (PlusEMU has many, go look) that basically return a string from an enum value and replaced it with this simple method that will do most of the work in 10% of the lines.
Example:
Code:internal static class InteractionTypes { internal static InteractionType GetTypeFromString(string pType) { var enumNames = Enum.GetNames(typeof(InteractionType)); var enumValueName = enumNames.FirstOrDefault(x => pType.IndexOf(x, StringComparison.OrdinalIgnoreCase) >= 0); if (enumValueName != null) { return (InteractionType) Enum.Parse(typeof(InteractionType), enumValueName); } return InteractionType.None; } }
address
Thanks bro, would love to see a decent quality CMS besides rev for thisLooks good man! Keep it up. I will definitely make this emulator compatible with my CMS once it's released.
using System;
using System.Text;
namespace Sirius.Base.Game.Habbo.Commands.Commands.Habbo
{
using Other.Communication.Packets.Outgoing.Notifications;
internal class InfoCommand : ICommand
{
internal void ProcessCommand(Player player)
{
var serverName = Sirius.GetServer().GetServerInformation().ServerName;
var serverVersion = Sirius.GetServer().GetServerInformation().ServerVersion;
var serverCreator = Sirius.GetServer().GetServerInformation().ServerCreator;
var swfVersion = Sirius.GetServer().GetServerInformation().HabboSwfRevision;
var notificationStringBuilder = new StringBuilder();
var serverUptime = DateTime.Now - Sirius.GetServer().GetServerInformation().ServerStarted;
var days = serverUptime.Days + " day" + (serverUptime.Days != 1 ? "s" : "") + ", ";
var hours = serverUptime.Hours + " hour" + (serverUptime.Hours != 1 ? "s" : "") + ", and ";
var minutes = serverUptime.Minutes + " minute" + (serverUptime.Minutes != 1 ? "s" : "");
notificationStringBuilder.Append(serverName + " v" + serverVersion + " [Running on " + swfVersion + "]");
notificationStringBuilder.Append(string.Empty);
notificationStringBuilder.Append("Thanks/Congrats:");
notificationStringBuilder.Append(string.Empty);
notificationStringBuilder.Append("- " + serverCreator + " [Creator]");
notificationStringBuilder.Append(string.Empty);
notificationStringBuilder.Append("Uptime:");
notificationStringBuilder.Append(string.Empty);
notificationStringBuilder.Append("- " + days + hours + minutes);
player.SendMessage(new MotdNotificationComposer(notificationStringBuilder.ToString()));
}
}
}
namespace Sirius.Base.Game.Habbo.Commands.Commands
{
using Other.Players;
internal interface ICommand
{
void ProcessCommand(Player player);
string PermissionRequired { get; }
int NumberOfParameters { get; }
string CommandParameters { get; }
string CommandDescription { get; }
bool CommandCooldown { get; }
int CommandCooldownMs { get; }
}
}
_commands.Add("info|about|serverinfo", new InfoCommand());
public string PermissionRequired => "";
public int NumberOfParameters => 0;
public string CommandParameters => "";
public string CommandDescription => "Lets you view information about the server.";
player.SendWhisper("Invalid command syntax for command " + commandName + " -- " + CommandParameters + "");
public bool CommandHasCooldown => true;
public int CommandCooldownMs => 5000;
@mycommand <username>
>>somecommand 10
_commands.Add(":mycommand", new MyCommand());
_commands.Add("@home", new AtHomeCommand());
_commands.Add(".ilovefish", new ILoveFishCommand());
Thank you, it will be compatible with RevCMS with a few small changes but feel free to try and create a CMS for it.Looking good buddy. I might also code a cms from new framework for this
Thank you, and yes sir.Good luck, hopefully you can finish and release it.
public bool CommandHasCooldown => true;
public int CommandCooldownMs => 5000;
if (command.PermissionRequired.Contains(" "))
{
if (command.PermissionRequired.Split(' ').Any(permission => !player.GetPlayerData().GetPermissions().HasCommand(command.PermissionRequired) &&
!player.GetPlayerData().GetPermissions().HasRight(command.PermissionRequired)))
{
return false;
}
}
Thankyou.Awesome work man! Keep up the great work. Can't wait to see until it releases.
public static NavigatorSearchAllowance GetSearchAllowanceByString(string categoryType)
{
return categoryType == "SHOW_MORE" ? NavigatorSearchAllowance.ShowMore : categoryType == "GO_BACK" ? NavigatorSearchAllowance.GoBack : NavigatorSearchAllowance.Nothing;
}
public static int GetIntegerValue(NavigatorSearchAllowance searchAllowance)
{
return searchAllowance == NavigatorSearchAllowance.ShowMore ? 1 : searchAllowance == NavigatorSearchAllowance.GoBack ? 2 : 0;
}
public static NavigatorViewMode GetViewModeByString(string viewMode)
{
return viewMode.ToUpper() == "THUMBNAIL" ? NavigatorViewMode.Thumbnail : NavigatorViewMode.Regular;
}
public static bool SocketConnected(Socket socket)
{
return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
}
public static string GetMergedString(IEnumerable<string> parts, int start)
{
return string.Join(" ", parts.Skip(start));
}
public static bool IsInteger(string inputString)
{
return int.TryParse("123", out int tempInt);
}
private readonly int _requiredBusinessId => 4;
private readonly int _requiredBusinessRank => 6;
private readonly GovernmentType _govRequired => GovernmentType.None;
private readonly BusinessType _requiredBusinessType => businessType.Retail;
private readonly bool _forceWorking => true;
namespace Sirius.Base.Game.Roleplay.Players
{
using System;
using System.Collections.Generic;
using System.Data;
using Other.GameClients;
public class RoleplayData : IDisposable
{
private readonly Player _player;
private readonly Dictionary<string, string> _cachedPlayerData;
private bool _disposing;
public void Dispose()
{
try
{
if (_disposing)
{
return;
}
_disposing = true;
using (var mysqlConnection = Sirius.GetServer().GetDatabaseManager().GetConnection())
{
var saveQuery = "UPDATE `users` SET ";
var foreachIndex = 0;
foreach (var dataPair in _cachedPlayerData)
{
foreachIndex++;
saveQuery += "`" + dataPair.Key + "` = @" + dataPair.Key + (foreachIndex < _cachedPlayerData.Count ? ", " : " LIMIT 1");
mysqlConnection.AddParameter(dataPair.Key, dataPair.Value);
}
if (foreachIndex == 0)
{
return;
}
mysqlConnection.SetQuery(saveQuery, false);
mysqlConnection.RunQuery();
}
}
catch (Exception exception)
{
Sirius.GetServer().GetLogManager().Error("Error saving roleplay data for " + _player.GetPlayerData().Username + ": " + exception.Message);
}
}
public RoleplayData(Player player)
{
_cachedPlayerData = new Dictionary<string, string>();
_player = player;
LoadPlayerData(player.GetPlayerData().Id);
}
private void LoadPlayerData(int userId)
{
try
{
using (var mysqlConnection = Sirius.GetServer().GetDatabaseManager().GetConnection())
{
mysqlConnection.SetQuery("SELECT * FROM srp_user_statistics` WHERE `user_id` = @userId");
mysqlConnection.AddParameter("userId", userId);
var playerTable = mysqlConnection.GetTable();
if (playerTable == null)
{
return;
}
foreach (DataRow roleplayRow in playerTable.Rows)
{
foreach (DataColumn column in playerTable.Columns)
{
_cachedPlayerData.Add(column.ColumnName, Convert.ToString(roleplayRow[column]));
}
}
}
}
catch (Exception exception)
{
Sirius.GetServer().GetLogManager().Error("Error during login for user " + userId + ": " + exception.Message);
}
}
private string SelectColumn(string columnName, bool fromDatabase)
{
if (_cachedPlayerData.ContainsKey(columnName) && !fromDatabase)
{
return _cachedPlayerData[columnName];
}
using (var mysqlConnection = Sirius.GetServer().GetDatabaseManager().GetConnection())
{
mysqlConnection.SetQuery("SELECT `" + columnName + "` FROM `users` WHERE `id` = @userId");
mysqlConnection.AddParameter("userId", _player.GetPlayerData().Id);
var columnValue = mysqlConnection.GetString();
_cachedPlayerData.Add(columnName, columnValue);
return columnValue;
}
}
public int SelectColumnAsInt(string columnName, bool fromDatabase)
{
return Convert.ToInt32(SelectColumn(columnName, fromDatabase));
}
public void UpdateColumn(string columnName, string newValue, bool inDatabase)
{
if (_cachedPlayerData.ContainsKey(columnName))
{
_cachedPlayerData[columnName] = newValue;
}
if (!inDatabase)
{
return;
}
UpdateColumnWithNewValue(columnName, newValue);
}
private void UpdateColumnWithNewValue(string columnName, string newValue)
{
using (var mysqlConnection = Sirius.GetServer().GetDatabaseManager().GetConnection())
{
mysqlConnection.SetQuery("UPDATE `users` SET `" + columnName + "` = @newValue WHERE `id` = @userId");
mysqlConnection.AddParameter("newValue", newValue);
mysqlConnection.AddParameter("userId", _player.GetPlayerData().Id);
mysqlConnection.RunQuery();
}
}
}
}
public void Dispose()
{
try
{
if (_disposing)
{
return;
}
_disposing = true;
using (var mysqlConnection = Sirius.GetServer().GetDatabaseManager().GetConnection())
{
var saveQuery = "UPDATE `users` SET ";
var foreachIndex = 0;
foreach (var dataPair in _cachedPlayerData)
{
foreachIndex++;
saveQuery += "`" + dataPair.Key + "` = @" + dataPair.Key + (foreachIndex < _cachedPlayerData.Count ? ", " : " LIMIT 1");
mysqlConnection.AddParameter(dataPair.Key, dataPair.Value);
}
if (foreachIndex == 0)
{
return;
}
mysqlConnection.SetQuery(saveQuery, false);
mysqlConnection.RunQuery();
}
}
catch (Exception exception)
{
Sirius.GetServer().GetLogManager().Error("Error saving roleplay data for " + _player.GetPlayerData().Username + ": " + exception.Message);
}
}
window.ws = new wsImpl('ws://localhost:8181/');
var hasConnected = false;
function startWebSockets() {
ws.onmessage = function (messageEvent) {
onReceiveMessage(messageEvent.Data);
};
ws.onopen = function () {
onConnectionOpened();
};
ws.onclose = function () {
onConnectionClosed();
}
}
function onReceiveMessage(messageData) {
alert('You received a message from the WebSocket server: ' + messageData);
}
function onConnectionOpened() {
console.log('Connected to the WebSocket server.');
hasConnected = true;
}
function onConnectionClosed() {
if (!hasConnected) {
console.log('Failed to connect to the WebSocket server.');
}
else {
console.log('Your connection to the WebSocket server was unexpectedly closed.');
}
}
startWebSockets();