-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Realm #18
base: develop
Are you sure you want to change the base?
Realm #18
Changes from all commits
f56e2bd
9b66228
066acfd
aae0c86
36a9b57
6bfdd2c
e69b7b1
dd1a49c
69d4575
20d8181
0c66edc
fde20f7
37577db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
using Atlasd.Battlenet.Protocols.Game; | ||
using Atlasd.Battlenet.Protocols.Game.Messages; | ||
using Atlasd.Battlenet.Protocols.MCP.Models; | ||
using Atlasd.Battlenet.Protocols.Udp; | ||
using Atlasd.Daemon; | ||
using Atlasd.Localization; | ||
|
@@ -42,13 +43,17 @@ public ShutdownEvent(string adminMessage, bool cancelled, DateTime eventDate, Ti | |
public static ConcurrentDictionary<string, Channel> ActiveChannels; | ||
public static ConcurrentDictionary<byte[], Clan> ActiveClans; | ||
public static ConcurrentDictionary<Socket, ClientState> ActiveClientStates; | ||
public static ConcurrentDictionary<UInt32, ClientState> RealmClientStates; | ||
public static List<GameAd> ActiveGameAds; | ||
public static ConcurrentDictionary<string, GameState> ActiveGameStates; | ||
public static Realm Realm; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alphabetize this by property name |
||
public static IPAddress DefaultAddress { get; private set; } | ||
public static int DefaultPort { get; private set; } | ||
public static ServerSocket Listener { get; private set; } | ||
public static RealmSocket RealmListener { get; private set; } | ||
public static IPAddress ListenerAddress { get; private set; } | ||
public static IPEndPoint ListenerEndPoint { get; private set; } | ||
public static IPEndPoint RealmListenerEndPoint { get; private set; } | ||
public static int ListenerPort { get; private set; } | ||
public static Timer NullTimer { get; private set; } | ||
public static Timer PingTimer { get; private set; } | ||
|
@@ -102,14 +107,17 @@ public static void Initialize() | |
ActiveChannels = new ConcurrentDictionary<string, Channel>(StringComparer.OrdinalIgnoreCase); | ||
ActiveClans = new ConcurrentDictionary<byte[], Clan>(); | ||
ActiveClientStates = new ConcurrentDictionary<Socket, ClientState>(); | ||
RealmClientStates = new ConcurrentDictionary<UInt32, ClientState>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alphabetize this |
||
ActiveGameAds = new List<GameAd>(); | ||
ActiveGameStates = new ConcurrentDictionary<string, GameState>(StringComparer.OrdinalIgnoreCase); | ||
Realm = new Realm(); | ||
|
||
InitializeAds(); | ||
|
||
DefaultAddress = IPAddress.Any; | ||
DefaultPort = 6112; | ||
InitializeListener(); | ||
InitializeRealmListener(); | ||
|
||
NullTimer = new Timer(ProcessNullTimer, ActiveGameStates, 100, 100); | ||
PingTimer = new Timer(ProcessPingTimer, ActiveGameStates, 100, 100); | ||
|
@@ -201,7 +209,35 @@ private static void InitializeListener() | |
UdpListener = new UdpListener(ListenerEndPoint); | ||
Listener = new ServerSocket(ListenerEndPoint); | ||
} | ||
|
||
|
||
private static void InitializeRealmListener() | ||
{ | ||
Settings.State.RootElement.TryGetProperty("battlenet", out var battlenetJson); | ||
battlenetJson.TryGetProperty("realm_listener", out var listenerJson); | ||
listenerJson.TryGetProperty("interface", out var interfaceJson); | ||
listenerJson.TryGetProperty("port", out var portJson); | ||
|
||
var listenerAddressStr = interfaceJson.GetString(); | ||
Comment on lines
+215
to
+220
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be refactored using the settings getter.
|
||
if (!IPAddress.TryParse(listenerAddressStr, out IPAddress listenerAddress)) | ||
{ | ||
Logging.WriteLine(Logging.LogLevel.Error, Logging.LogType.Server, $"Unable to parse IP address from [battlenet.realm_listener.interface] with value [{listenerAddressStr}]; using any"); | ||
listenerAddress = DefaultAddress; | ||
} | ||
ListenerAddress = listenerAddress; | ||
|
||
portJson.TryGetInt32(out var port); | ||
ListenerPort = port; | ||
|
||
if (!IPEndPoint.TryParse($"{ListenerAddress}:{ListenerPort}", out IPEndPoint listenerEndPoint)) | ||
{ | ||
Logging.WriteLine(Logging.LogLevel.Error, Logging.LogType.Server, $"Unable to parse endpoint with value [{ListenerAddress}:{ListenerPort}]"); | ||
return; | ||
} | ||
ListenerEndPoint = listenerEndPoint; | ||
|
||
RealmListener = new RealmSocket(ListenerEndPoint); | ||
} | ||
|
||
public static uint GetActiveClientCountByProduct(Product.ProductCode productCode) | ||
{ | ||
var count = (uint)0; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
using System; | ||
|
||
namespace Atlasd.Battlenet.Exceptions | ||
{ | ||
class RealmProtocolException : ProtocolException | ||
{ | ||
public RealmProtocolException(ClientState client) : base(Battlenet.ProtocolType.Types.Game, client) { } | ||
public RealmProtocolException(ClientState client, string message) : base(Battlenet.ProtocolType.Types.Game, client, message) { } | ||
public RealmProtocolException(ClientState client, string message, Exception innerException) : base(Battlenet.ProtocolType.Types.Game, client, message, innerException) { } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
using System.IO; | ||
using System.Linq; | ||
using System.Text; | ||
using Atlasd.Helpers; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alphabetize the using list |
||
|
||
namespace Atlasd.Battlenet.Protocols.Game.Messages | ||
{ | ||
|
@@ -26,6 +27,7 @@ public SID_ENTERCHAT(byte[] buffer) | |
public override bool Invoke(MessageContext context) | ||
{ | ||
if (context == null || context.Client == null || !context.Client.Connected) return false; | ||
var realmState = context.Client.RealmState; | ||
var gameState = context.Client.GameState; | ||
|
||
switch (context.Direction) | ||
|
@@ -68,7 +70,24 @@ public override bool Invoke(MessageContext context) | |
|
||
if (Product.IsDiabloII(gameState.Product)) | ||
{ | ||
statstring = Product.ToByteArray(gameState.Product); | ||
// for some reason, the client sends SID_ENTERCHAT with no MCP_CHARLOGON, right after MCP_CHARCREATE | ||
// i'm not certain this is the correct design, but it works for now | ||
var providedStatstring = statstring.AsString(); | ||
var tokens = providedStatstring.Split(","); | ||
var realm = tokens[0]; | ||
var name = tokens[1]; | ||
|
||
if (realm.Length > 0 && realmState != null) | ||
{ | ||
var character = Battlenet.Common.Realm.GetCharacter(accountName, name); | ||
if (character != null) | ||
{ | ||
realmState.ActiveCharacter = character; | ||
gameState.CharacterName = character.Name.ToBytes(); | ||
} | ||
} | ||
|
||
statstring = context.Client.GenerateDiabloIIStatstring(); | ||
} | ||
|
||
// Do not use client-provided statstring if config.battlenet.emulation.statstring_updates is not enabled for this product. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,10 @@ | |
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Net; | ||
using System.Text; | ||
using Atlasd.Helpers; | ||
using Atlasd.Utilities; | ||
|
||
namespace Atlasd.Battlenet.Protocols.Game.Messages | ||
{ | ||
|
@@ -62,8 +66,6 @@ public override bool Invoke(MessageContext context) | |
} | ||
case MessageDirection.ServerToClient: | ||
{ | ||
var clientToken = (UInt32)context.Arguments["clientToken"]; | ||
|
||
/** | ||
* [Note this format is slightly different from BNETDocs reference as of 2023-02-18] | ||
* (UINT32) MCP Cookie (Client Token) | ||
|
@@ -75,15 +77,84 @@ public override bool Invoke(MessageContext context) | |
* (STRING) Battle.net unique name (* as of D2 1.14d, this is empty) | ||
*/ | ||
|
||
Buffer = new byte[8]; // MCP Cookie + MCP Status only; Atlas does not have realm/MCP servers implemented yet. | ||
if (((byte[])context.Arguments["realmTitle"]).AsString() == "Olympus") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make it configurable |
||
{ | ||
Buffer = new byte[18 * 4 + 1]; | ||
|
||
using var m = new MemoryStream(Buffer); | ||
using var w = new BinaryWriter(m); | ||
using var m = new MemoryStream(Buffer); | ||
using var w = new BinaryWriter(m); | ||
|
||
var cookie = (UInt32)(new Random().Next(int.MinValue, int.MaxValue)); | ||
|
||
w.Write((UInt32)cookie); | ||
w.Write((UInt32)0x00000000); // status | ||
|
||
var chunk1 = new UInt32[] | ||
{ | ||
0x33316163, | ||
0x65303830 | ||
}.GetBytes(); | ||
|
||
w.Write(chunk1); | ||
|
||
#if DEBUG | ||
string ipString = "127.0.0.1"; | ||
#else | ||
string ipString = NetworkUtilities.GetPublicAddress(); | ||
#endif | ||
|
||
IPAddress ipAddress = IPAddress.Parse(ipString); | ||
|
||
w.Write(ipAddress.GetBytes()); | ||
|
||
w.Write((UInt32)clientToken); | ||
w.Write((UInt32)Statuses.RealmUnavailable); // Atlas does not have realm/MCP servers implemented yet. | ||
Settings.State.RootElement.TryGetProperty("battlenet", out var battlenetJson); | ||
battlenetJson.TryGetProperty("realm_listener", out var listenerJson); | ||
listenerJson.TryGetProperty("interface", out var interfaceJson); | ||
listenerJson.TryGetProperty("port", out var portJson); | ||
|
||
portJson.TryGetUInt16(out var port); | ||
Comment on lines
+110
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be refactored using the settings getter.
|
||
|
||
// strange protocol design | ||
ushort hostOrderPort = port; | ||
UInt32 networkOrderPort = (UInt32)IPAddress.HostToNetworkOrder((short)hostOrderPort); | ||
|
||
w.Write(networkOrderPort); | ||
|
||
// this could use some love | ||
var chunk2 = new UInt32[] | ||
{ | ||
0x66663162, | ||
0x34613566, | ||
0x64326639, | ||
0x63336330, | ||
0x38326135, | ||
0x39663937, | ||
0x62653134, | ||
0x36313861, | ||
0x36353032, | ||
0x31353066, | ||
0x00000000, | ||
0x00000000 | ||
}.GetBytes(); | ||
|
||
w.Write(chunk2); | ||
w.WriteByteString(Encoding.UTF8.GetBytes("")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
// to allow associating game and realm connections after MCP_STARTUP | ||
Battlenet.Common.RealmClientStates.TryAdd(cookie, context.Client); | ||
} | ||
else | ||
{ | ||
Buffer = new byte[4]; | ||
|
||
using var m = new MemoryStream(Buffer); | ||
using var w = new BinaryWriter(m); | ||
|
||
w.Write((UInt32)Statuses.RealmUnavailable); | ||
} | ||
|
||
Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); | ||
|
||
context.Client.Send(ToByteArray(context.Client.ProtocolType)); | ||
return true; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,12 +42,13 @@ public override bool Invoke(MessageContext context) | |
} | ||
case MessageDirection.ServerToClient: | ||
{ | ||
Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); | ||
|
||
Dictionary<byte[], byte[]> realms = | ||
context.Arguments == null || !context.Arguments.ContainsKey("realms") ? | ||
new Dictionary<byte[], byte[]>() : | ||
(Dictionary<byte[], byte[]>)context.Arguments["realms"]; | ||
Dictionary<byte[], byte[]> realms = new Dictionary<byte[], byte[]> | ||
{ | ||
{ | ||
Encoding.UTF8.GetBytes("Olympus"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make this configurable from the server config json |
||
Encoding.UTF8.GetBytes("Diablo II Realm Server") | ||
} | ||
}; | ||
|
||
/** | ||
* (UINT32) Unknown (0) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
using System; | ||
using System.Collections.Concurrent; | ||
|
||
namespace Atlasd.Battlenet.Protocols.MCP | ||
{ | ||
class Frame | ||
{ | ||
public ConcurrentQueue<Message> Messages { get; protected set; } | ||
|
||
public Frame() | ||
{ | ||
Messages = new ConcurrentQueue<Message>(); | ||
} | ||
|
||
public Frame(ConcurrentQueue<Message> messages) | ||
{ | ||
Messages = messages; | ||
} | ||
|
||
public byte[] ToByteArray(ProtocolType protocolType) | ||
{ | ||
var framebuf = new byte[0]; | ||
var msgs = new ConcurrentQueue<Message>(Messages); // Clone Messages into local variable | ||
|
||
while (msgs.Count > 0) | ||
{ | ||
if (!msgs.TryDequeue(out var msg)) break; | ||
|
||
var messagebuf = msg.ToByteArray(protocolType); | ||
var buf = new byte[framebuf.Length + messagebuf.Length]; | ||
|
||
Buffer.BlockCopy(framebuf, 0, buf, 0, framebuf.Length); | ||
Buffer.BlockCopy(messagebuf, 0, buf, framebuf.Length, messagebuf.Length); | ||
|
||
framebuf = buf; | ||
} | ||
|
||
return framebuf; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be made into a configurable. Perhaps you want to use this?
var realmName = Settings.GetString(new string[] { "realm", "name" });