Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,4 @@
# systemd cannot be trusted with CRLF unit files; keep LF on every platform.
###############################################################################
etc/atlasd.service text eol=lf
src/make-matchmaking.sh text eol=lf
8 changes: 8 additions & 0 deletions etc/atlasd.sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@
"mask_admins_in_broadcasts": false,
"mask_admins_in_ban_message": true,
"mask_admins_in_kick_message": true,
"name_only_logon": {
"W3DM": true
},
"required_game_key_count": {
"D2DV": 1,
"D2XP": 2,
Expand All @@ -118,6 +121,11 @@
"W3XP": 2,
"WAR3": 1
},
"server_signature": {
"modulus": "",
"private_exponent": "",
"address": ""
},
"statstring_updates": {
"D2DV": true,
"D2XP": true,
Expand Down
2 changes: 2 additions & 0 deletions src/Atlasd/Battlenet/Account.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Account
public const string IPAddressKey = "System\\IP";
public const string LastLogoffKey = "System\\Last Logoff";
public const string LastLogonKey = "System\\Last Logon";
public const string NameOnlyKey = "System\\Name Only";
public const string PasswordKey = "System\\Password Digest";
public const string PortKey = "System\\Port";
public const string ProfileAgeKey = "profile\\age";
Expand Down Expand Up @@ -73,6 +74,7 @@ private Account()
{ new AccountKeyValue(IPAddressKey, IPAddress.Any, AccountKeyValue.ReadLevel.Internal, AccountKeyValue.WriteLevel.Internal) },
{ new AccountKeyValue(LastLogoffKey, DateTime.Now, AccountKeyValue.ReadLevel.Owner, AccountKeyValue.WriteLevel.Internal) },
{ new AccountKeyValue(LastLogonKey, DateTime.Now, AccountKeyValue.ReadLevel.Owner, AccountKeyValue.WriteLevel.Internal) },
{ new AccountKeyValue(NameOnlyKey, 0, AccountKeyValue.ReadLevel.Internal, AccountKeyValue.WriteLevel.Internal) },
{ new AccountKeyValue(PortKey, 0, AccountKeyValue.ReadLevel.Internal, AccountKeyValue.WriteLevel.Internal) },
{ new AccountKeyValue(PasswordKey, new byte[0], AccountKeyValue.ReadLevel.Internal, AccountKeyValue.WriteLevel.Internal) },
{ new AccountKeyValue(ProfileAgeKey, "", AccountKeyValue.ReadLevel.Any, AccountKeyValue.WriteLevel.Owner) },
Expand Down
3 changes: 2 additions & 1 deletion src/Atlasd/Battlenet/ClientState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,8 @@ protected void ReceiveProtocolBNFTP(SocketAsyncEventArgs e)
lock (_receiveLock)
{
if (ReceiveBuffer.Length == 0) return;
BNFTPState.Receive(ReceiveBuffer);
var consumed = BNFTPState.Receive(ReceiveBuffer);
ReceiveBuffer = ReceiveBuffer[Math.Min(consumed, ReceiveBuffer.Length)..];
}
}

Expand Down
231 changes: 116 additions & 115 deletions src/Atlasd/Battlenet/Protocols/BNFTP/BNFTPState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,48 @@ class BNFTPState
public UInt32 ClientToken = 0;
public GameKey GameKey = null;

private bool AwaitingVersion2Request = false;

public BNFTPState(ClientState client)
{
Client = client;
ServerToken = (uint)new Random().Next(0, 0x7FFFFFFF);
}

public void Receive(byte[] buffer)
public int Receive(byte[] buffer)
{
using var m = new MemoryStream(buffer);
using var r = new BinaryReader(m);

if (AwaitingVersion2Request)
{
/**
* ## VERSION 2 <-- ##
* ## Client -> Server ##
*
* (TYPE) (FIELD) (DESCRIPTION)
* UINT32 Starting position Facilitates resuming
* FILETIME Local filetime
* UINT32 Client Token
* UINT32 Key Length
* UINT32 Key's product value
* UINT32 Key's public value
* UINT32 Unknown (Always 0)
* UINT32 [5] CD key hash
* STRING Filename
*/

FileStartPosition = r.ReadUInt32();
FileTime = r.ReadUInt64();
ClientToken = r.ReadUInt32();
r.ReadBytes(16);
r.ReadBytes(20);
FileName = Encoding.UTF8.GetString(r.ReadByteString());

ServeFile();
return (int)m.Position;
}

/**
* ## VERSION 1 <-- ##
* ## Client -> Server ##
Expand Down Expand Up @@ -76,82 +107,8 @@ public void Receive(byte[] buffer)
FileTime = r.ReadUInt64();
FileName = Encoding.UTF8.GetString(r.ReadByteString());

/**
* ## VERSION 1 --> ##
* ## Server -> Client ##
*
* (TYPE) (FIELD) (DESCRIPTION)
* UINT16 Header Length Does not include the file length
* UINT16 Type
* UINT32 File size
* UINT32 Ad Banner ID 0 unless downloading an ad banner
* UINT32 Ad Banner File Extension 0 unless downloading an ad banner
* FILETIME Filetime
* STRING Filename
* VOID File data
*/

var file = new BNFTP.File(FileName);
if (file == null)
{
Client.Disconnect();
}

BinaryReader stream = null;

bool uploaded = false;
try
{
if (!file.OpenStream())
{
Client.Disconnect();
break;
}

stream = new BinaryReader(file.StreamReader.BaseStream);
stream.BaseStream.Position = Math.Min(stream.BaseStream.Length, FileStartPosition);

var fileLength = (int)(stream.BaseStream.Length - stream.BaseStream.Position);
HeaderLength = (UInt16)(25 + Encoding.UTF8.GetByteCount(FileName));
var outBuf = new byte[HeaderLength];
using var wm = new MemoryStream(outBuf);
using var w = new BinaryWriter(wm);

w.Write((UInt16)HeaderLength);
w.Write((UInt16)0); // "Type" ???
w.Write((UInt32)fileLength);
w.Write((UInt32)AdId);
w.Write((UInt32)AdFileExtension);
w.Write((UInt64)new FileInfo(file.Path).LastWriteTimeUtc.ToFileTimeUtc());
w.Write((string)FileName);

Write(outBuf);
Write(stream.ReadBytes(fileLength));

uploaded = true;
}
catch (Exception ex)
{
if (!(ex is IOException || ex is FileNotFoundException || ex is UnauthorizedAccessException || ex is PathTooLongException)) throw;

Logging.WriteLine(Logging.LogLevel.Warning, Logging.LogType.Client_BNFTP, Client.RemoteEndPoint, $"{ex.GetType().Name} error encountered for requested file [{FileName}]" + (string.IsNullOrEmpty(ex.Message) ? "" : $"; message: {ex.Message}"));
}
finally
{
if (uploaded)
{
Logging.WriteLine(Logging.LogLevel.Info, Logging.LogType.Client_BNFTP, Client.RemoteEndPoint, $"Uploaded file [{FileName}] ({file.Length} bytes)");
}

if (stream != null)
{
stream.Close();
}

Client.Disconnect();
}

break;
ServeFile();
return (int)m.Position;
}
case 0x0200:
{
Expand All @@ -164,18 +121,12 @@ public void Receive(byte[] buffer)
* UINT32 Product ID See Product Identification
* UINT32 Ad Banner ID 0 unless downloading an ad banner
* UINT32 Ad Banner File Extension 0 unless downloading an ad banner
* UINT32 File start position For resuming an incomplete download
* FILETIME Filetime
* STRING Filename
*/

PlatformId = (Platform.PlatformCode)r.ReadUInt32();
ProductId = (Product.ProductCode)r.ReadUInt32();
AdId = r.ReadUInt32();
AdFileExtension = r.ReadUInt32();
FileStartPosition = r.ReadUInt32();
FileTime = r.ReadUInt64();
FileName = Encoding.UTF8.GetString(r.ReadByteString());

/**
* ## VERSION 2 --> ##
Expand All @@ -185,47 +136,97 @@ public void Receive(byte[] buffer)
* UINT32 Server Token
*/

/**
* ## VERSION 2 <-- ##
* ## Client -> Server ##
*
* (TYPE) (FIELD) (DESCRIPTION)
* UINT32 Starting position Facilitates resuming
* FILETIME Local filetime
* UINT32 Client Token
* UINT32 Key Length
* UINT32 Key's product value
* UINT32 Key's public value
* UINT32 Unknown (Always 0)
* UINT32 [5] CD key hash
* STRING Filename
*/

/**
* ## VERSION 2 --> ##
* ## Server -> Client ##
*
* (TYPE) (FIELD) (DESCRIPTION)
* UINT16 Header Length Does not include the file length
* UINT32 File size
* UINT32 Ad Banner ID 0 unless downloading an ad banner
* UINT32 Ad Banner File Extension 0 unless downloading an ad banner
* FILETIME Filetime
* STRING Filename
* VOID File data
*/

break;
AwaitingVersion2Request = true;
Write(BitConverter.GetBytes(ServerToken));
return (int)m.Position;
}
default:
{
Logging.WriteLine(Logging.LogLevel.Warning, Logging.LogType.Client_BNFTP, $"Received unknown BNFTP protocol version [0x{ProtocolVersion:X4}]");
Client.Disconnect("Unknown BNFTP protocol version");
break;
return buffer.Length;
}
}
}

private void ServeFile()
{
/**
* ## VERSION 1 and 2 --> ##
* ## Server -> Client ##
*
* (TYPE) (FIELD) (DESCRIPTION)
* UINT16 Header Length Does not include the file length
* UINT16 Type
* UINT32 File size
* UINT32 Ad Banner ID 0 unless downloading an ad banner
* UINT32 Ad Banner File Extension 0 unless downloading an ad banner
* FILETIME Filetime
* STRING Filename
* VOID File data
*/

var file = new BNFTP.File(FileName);
if (file == null)
{
Client.Disconnect();
}

BinaryReader stream = null;

bool uploaded = false;
try
{
if (!file.OpenStream())
{
Client.Disconnect();
return;
}

stream = new BinaryReader(file.StreamReader.BaseStream);
stream.BaseStream.Position = Math.Min(stream.BaseStream.Length, FileStartPosition);

var fileLength = (int)(stream.BaseStream.Length - stream.BaseStream.Position);
HeaderLength = (UInt16)(25 + Encoding.UTF8.GetByteCount(FileName));
var outBuf = new byte[HeaderLength];
using var wm = new MemoryStream(outBuf);
using var w = new BinaryWriter(wm);

w.Write((UInt16)HeaderLength);
w.Write((UInt16)0); // "Type" ???
w.Write((UInt32)fileLength);
w.Write((UInt32)AdId);
w.Write((UInt32)AdFileExtension);
w.Write((UInt64)new FileInfo(file.Path).LastWriteTimeUtc.ToFileTimeUtc());
w.Write((string)FileName);

Write(outBuf);
Write(stream.ReadBytes(fileLength));

uploaded = true;
}
catch (Exception ex)
{
if (!(ex is IOException || ex is FileNotFoundException || ex is UnauthorizedAccessException || ex is PathTooLongException)) throw;

Logging.WriteLine(Logging.LogLevel.Warning, Logging.LogType.Client_BNFTP, Client.RemoteEndPoint, $"{ex.GetType().Name} error encountered for requested file [{FileName}]" + (string.IsNullOrEmpty(ex.Message) ? "" : $"; message: {ex.Message}"));
}
finally
{
if (uploaded)
{
Logging.WriteLine(Logging.LogLevel.Info, Logging.LogType.Client_BNFTP, Client.RemoteEndPoint, $"Uploaded file [{FileName}] ({file.Length} bytes)");
}

if (stream != null)
{
stream.Close();
}

Client.Disconnect();
}
}

public void Write(byte[] buffer)
{
Client.Send(buffer);
Expand Down
1 change: 1 addition & 0 deletions src/Atlasd/Battlenet/Protocols/Game/GameState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public enum LogonTypes : UInt32
public UInt32 PingToken;
public UInt32 ProtocolId;
public UInt32 ServerToken;
public UInt32 GameSearchCookie;
public bool SpawnKey;
public byte[] Statstring;
public Int32 TimezoneBias;
Expand Down
3 changes: 3 additions & 0 deletions src/Atlasd/Battlenet/Protocols/Game/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,12 @@ public static Message FromByteArray(byte id, byte[] buffer)
MessageIds.SID_QUERYREALMS2 => new SID_QUERYREALMS2(buffer),
MessageIds.SID_QUERYADURL => new SID_QUERYADURL(buffer),
MessageIds.SID_WARCRAFTGENERAL => new SID_WARCRAFTGENERAL(buffer),
MessageIds.SID_NETGAMEPORT => new SID_NETGAMEPORT(buffer),
MessageIds.SID_NEWS_INFO => new SID_NEWS_INFO(buffer),
MessageIds.SID_AUTH_INFO => new SID_AUTH_INFO(buffer),
MessageIds.SID_AUTH_CHECK => new SID_AUTH_CHECK(buffer),
MessageIds.SID_AUTH_ACCOUNTLOGON => new SID_AUTH_ACCOUNTLOGON(buffer),
MessageIds.SID_AUTH_ACCOUNTLOGONPROOF => new SID_AUTH_ACCOUNTLOGONPROOF(buffer),
MessageIds.SID_SETEMAIL => new SID_SETEMAIL(buffer),
MessageIds.SID_FRIENDSLIST => new SID_FRIENDSLIST(buffer),
MessageIds.SID_FRIENDSUPDATE => new SID_FRIENDSUPDATE(buffer),
Expand Down
Loading
Loading