├── etc └── .gitignore ├── var └── bnftp │ ├── www │ ├── robots.txt │ └── index.shtml │ ├── icons.bni │ ├── newbie.save │ ├── IX86ver1.mpq │ ├── PMACver1.mpq │ ├── XMACver1.mpq │ ├── icons-WAR3.bni │ ├── icons_STAR.bni │ ├── icons_clan.bni │ ├── icons_lag.bni │ ├── ver-IX86-0.mpq │ ├── ver-IX86-1.mpq │ ├── ver-IX86-2.mpq │ ├── ver-IX86-3.mpq │ ├── ver-IX86-4.mpq │ ├── ver-IX86-5.mpq │ ├── ver-IX86-6.mpq │ ├── ver-IX86-7.mpq │ ├── w3xp_icons.bni │ ├── war3_icons.bni │ ├── ad template.pdn │ ├── legacy_icons.bni │ ├── IX86ExtraWork.mpq │ ├── ad template bg.png │ ├── ad-20201031-vl.jpg │ ├── ad-20201031-vl.smk │ ├── ad-20201030-3days.png │ ├── ad-20201030-3days.smk │ ├── ad-20201030-ghacv.png │ ├── ad-20201030-ghacv.smk │ ├── ad-20201030-ktbpa.png │ ├── ad-20201030-ktbpa.smk │ ├── bnetdocs-gateways.reg │ ├── lockdown-IX86-00.mpq │ ├── lockdown-IX86-01.mpq │ ├── lockdown-IX86-02.mpq │ ├── lockdown-IX86-03.mpq │ ├── lockdown-IX86-04.mpq │ ├── lockdown-IX86-05.mpq │ ├── lockdown-IX86-06.mpq │ ├── lockdown-IX86-07.mpq │ ├── lockdown-IX86-08.mpq │ ├── lockdown-IX86-09.mpq │ ├── lockdown-IX86-10.mpq │ ├── lockdown-IX86-11.mpq │ ├── lockdown-IX86-12.mpq │ ├── lockdown-IX86-13.mpq │ ├── lockdown-IX86-14.mpq │ ├── lockdown-IX86-15.mpq │ ├── lockdown-IX86-16.mpq │ ├── lockdown-IX86-17.mpq │ ├── lockdown-IX86-18.mpq │ ├── lockdown-IX86-19.mpq │ ├── ad-20201030-discord.png │ ├── ad-20201030-discord.smk │ ├── ad-20201030-nobtry.png │ ├── ad-20201030-nobtry.smk │ ├── ad-20230225-pvpgn-1.png │ ├── ad-20230225-pvpgn-1.smk │ ├── ad-20230225-pvpgn-2.mng │ ├── ad-20230225-pvpgn-2.smk │ ├── .gitattributes │ ├── bnserver.ini │ ├── bnserver-D2DV.ini │ ├── bnserver-D2XP.ini │ ├── bnserver-WAR3.ini │ ├── tos_USA.txt │ └── tos-unicode_USA.txt ├── docs ├── Channels.ods ├── Starcraft Game Advertisement (Advex) Matchmaking Model.png └── Starcraft Game Advertisement (Advex) Matchmaking Model.drawio ├── .gitmodules ├── src ├── Atlasd │ ├── Battlenet │ │ ├── Protocols │ │ │ ├── MessageDirection.cs │ │ │ ├── Game │ │ │ │ ├── VersionInfo.cs │ │ │ │ ├── LocaleInfo.cs │ │ │ │ ├── OldAuth.cs │ │ │ │ ├── MessageContext.cs │ │ │ │ ├── ChatCommands │ │ │ │ │ ├── ChatCommandContext.cs │ │ │ │ │ ├── InvalidCommand.cs │ │ │ │ │ ├── UsersCommand.cs │ │ │ │ │ ├── ReJoinCommand.cs │ │ │ │ │ ├── TimeCommand.cs │ │ │ │ │ ├── EmoteCommand.cs │ │ │ │ │ ├── AdminBroadcastCommand.cs │ │ │ │ │ ├── JoinCommand.cs │ │ │ │ │ ├── AwayCommand.cs │ │ │ │ │ ├── UnBanCommand.cs │ │ │ │ │ ├── AdminReloadCommand.cs │ │ │ │ │ ├── AdminClanCommand.cs │ │ │ │ │ ├── AdminShutdownCommand.cs │ │ │ │ │ ├── SquelchCommand.cs │ │ │ │ │ ├── UnsquelchCommand.cs │ │ │ │ │ ├── KickCommand.cs │ │ │ │ │ ├── AdminClanListCommand.cs │ │ │ │ │ ├── BanCommand.cs │ │ │ │ │ ├── WhoCommand.cs │ │ │ │ │ ├── VersionCommand.cs │ │ │ │ │ ├── HelpCommand.cs │ │ │ │ │ ├── FriendMessageCommand.cs │ │ │ │ │ ├── AdminHelpCommand.cs │ │ │ │ │ ├── ClanCommand.cs │ │ │ │ │ ├── FriendRemoveCommand.cs │ │ │ │ │ ├── FriendListCommand.cs │ │ │ │ │ ├── WhoAmICommand.cs │ │ │ │ │ ├── FriendAddCommand.cs │ │ │ │ │ ├── DesignateCommand.cs │ │ │ │ │ └── FriendCommand.cs │ │ │ │ ├── Messages │ │ │ │ │ ├── SID_NULL.cs │ │ │ │ │ ├── SID_FLOODDETECTED.cs │ │ │ │ │ ├── SID_FRIENDSREMOVE.cs │ │ │ │ │ ├── SID_LEAVECHAT.cs │ │ │ │ │ ├── SID_FRIENDSPOSITION.cs │ │ │ │ │ ├── SID_CLICKAD.cs │ │ │ │ │ ├── SID_CHATEVENT.cs │ │ │ │ │ ├── SID_LEAVEGAME.cs │ │ │ │ │ ├── SID_LOGONCHALLENGE.cs │ │ │ │ │ ├── SID_LOGONCHALLENGEEX.cs │ │ │ │ │ ├── SID_GAMEDATAADDRESS.cs │ │ │ │ │ ├── SID_STOPADV.cs │ │ │ │ │ ├── SID_SYSTEMINFO.cs │ │ │ │ │ ├── SID_CLANINFO.cs │ │ │ │ │ ├── SID_FRIENDSADD.cs │ │ │ │ │ ├── SID_DISPLAYAD.cs │ │ │ │ │ ├── SID_WARCRAFTGENERAL.cs │ │ │ │ │ ├── SID_CLANMEMBERSTATUSCHANGE.cs │ │ │ │ │ ├── SID_GAMERESULT.cs │ │ │ │ │ ├── SID_UDPPINGRESPONSE.cs │ │ │ │ │ ├── SID_GETICONDATA.cs │ │ │ │ │ ├── SID_SETEMAIL.cs │ │ │ │ │ ├── SID_LOCALEINFO.cs │ │ │ │ │ ├── SID_MESSAGEBOX.cs │ │ │ │ │ ├── SID_QUERYREALMS.cs │ │ │ │ │ ├── SID_READMEMORY.cs │ │ │ │ │ ├── SID_CHECKDATAFILE2.cs │ │ │ │ │ ├── SID_CREATEACCOUNT.cs │ │ │ │ │ ├── SID_CREATEACCOUNT2.cs │ │ │ │ │ ├── SID_PING.cs │ │ │ │ │ └── SID_CDKEY.cs │ │ │ │ ├── Frame.cs │ │ │ │ └── Advertisement.cs │ │ │ ├── UDP │ │ │ │ └── Messages.cs │ │ │ ├── IListener.cs │ │ │ ├── Common.cs │ │ │ ├── MCP │ │ │ │ └── Messages.cs │ │ │ ├── HTTP │ │ │ │ └── HttpHeader.cs │ │ │ └── D2GS │ │ │ │ └── Messages.cs │ │ ├── Exceptions │ │ │ ├── GameProtocolViolationException.cs │ │ │ ├── ChatProtocolException.cs │ │ │ ├── GameProtocolException.cs │ │ │ ├── BNFTPProtocolException.cs │ │ │ ├── ClientException.cs │ │ │ ├── ProtocolNotSupportedException.cs │ │ │ └── ProtocolException.cs │ │ ├── AccountKeyValue.cs │ │ ├── Platform.cs │ │ ├── BinaryWriter.cs │ │ ├── BinaryReader.cs │ │ └── ProtocolType.cs │ ├── Atlasd.csproj │ ├── Daemon │ │ └── Logging.cs │ └── app.manifest ├── udptest.py └── Atlas.sln ├── COPYRIGHT.txt ├── .github └── workflows │ ├── dotnet-core.yml │ └── codeql-analysis.yml ├── LICENSE.txt ├── .vscode ├── launch.json └── tasks.json └── .gitattributes /etc/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /var/bnftp/www/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /docs/Channels.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/docs/Channels.ods -------------------------------------------------------------------------------- /var/bnftp/icons.bni: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/icons.bni -------------------------------------------------------------------------------- /var/bnftp/newbie.save: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/newbie.save -------------------------------------------------------------------------------- /var/bnftp/IX86ver1.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/IX86ver1.mpq -------------------------------------------------------------------------------- /var/bnftp/PMACver1.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/PMACver1.mpq -------------------------------------------------------------------------------- /var/bnftp/XMACver1.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/XMACver1.mpq -------------------------------------------------------------------------------- /var/bnftp/icons-WAR3.bni: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/icons-WAR3.bni -------------------------------------------------------------------------------- /var/bnftp/icons_STAR.bni: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/icons_STAR.bni -------------------------------------------------------------------------------- /var/bnftp/icons_clan.bni: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/icons_clan.bni -------------------------------------------------------------------------------- /var/bnftp/icons_lag.bni: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/icons_lag.bni -------------------------------------------------------------------------------- /var/bnftp/ver-IX86-0.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ver-IX86-0.mpq -------------------------------------------------------------------------------- /var/bnftp/ver-IX86-1.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ver-IX86-1.mpq -------------------------------------------------------------------------------- /var/bnftp/ver-IX86-2.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ver-IX86-2.mpq -------------------------------------------------------------------------------- /var/bnftp/ver-IX86-3.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ver-IX86-3.mpq -------------------------------------------------------------------------------- /var/bnftp/ver-IX86-4.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ver-IX86-4.mpq -------------------------------------------------------------------------------- /var/bnftp/ver-IX86-5.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ver-IX86-5.mpq -------------------------------------------------------------------------------- /var/bnftp/ver-IX86-6.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ver-IX86-6.mpq -------------------------------------------------------------------------------- /var/bnftp/ver-IX86-7.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ver-IX86-7.mpq -------------------------------------------------------------------------------- /var/bnftp/w3xp_icons.bni: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/w3xp_icons.bni -------------------------------------------------------------------------------- /var/bnftp/war3_icons.bni: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/war3_icons.bni -------------------------------------------------------------------------------- /var/bnftp/ad template.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad template.pdn -------------------------------------------------------------------------------- /var/bnftp/legacy_icons.bni: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/legacy_icons.bni -------------------------------------------------------------------------------- /var/bnftp/IX86ExtraWork.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/IX86ExtraWork.mpq -------------------------------------------------------------------------------- /var/bnftp/ad template bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad template bg.png -------------------------------------------------------------------------------- /var/bnftp/ad-20201031-vl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20201031-vl.jpg -------------------------------------------------------------------------------- /var/bnftp/ad-20201031-vl.smk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20201031-vl.smk -------------------------------------------------------------------------------- /var/bnftp/ad-20201030-3days.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20201030-3days.png -------------------------------------------------------------------------------- /var/bnftp/ad-20201030-3days.smk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20201030-3days.smk -------------------------------------------------------------------------------- /var/bnftp/ad-20201030-ghacv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20201030-ghacv.png -------------------------------------------------------------------------------- /var/bnftp/ad-20201030-ghacv.smk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20201030-ghacv.smk -------------------------------------------------------------------------------- /var/bnftp/ad-20201030-ktbpa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20201030-ktbpa.png -------------------------------------------------------------------------------- /var/bnftp/ad-20201030-ktbpa.smk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20201030-ktbpa.smk -------------------------------------------------------------------------------- /var/bnftp/bnetdocs-gateways.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/bnetdocs-gateways.reg -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-00.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-00.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-01.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-01.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-02.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-02.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-03.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-03.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-04.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-04.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-05.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-05.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-06.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-06.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-07.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-07.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-08.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-08.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-09.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-09.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-10.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-10.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-11.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-11.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-12.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-12.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-13.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-13.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-14.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-14.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-15.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-15.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-16.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-16.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-17.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-17.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-18.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-18.mpq -------------------------------------------------------------------------------- /var/bnftp/lockdown-IX86-19.mpq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/lockdown-IX86-19.mpq -------------------------------------------------------------------------------- /var/bnftp/ad-20201030-discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20201030-discord.png -------------------------------------------------------------------------------- /var/bnftp/ad-20201030-discord.smk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20201030-discord.smk -------------------------------------------------------------------------------- /var/bnftp/ad-20201030-nobtry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20201030-nobtry.png -------------------------------------------------------------------------------- /var/bnftp/ad-20201030-nobtry.smk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20201030-nobtry.smk -------------------------------------------------------------------------------- /var/bnftp/ad-20230225-pvpgn-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20230225-pvpgn-1.png -------------------------------------------------------------------------------- /var/bnftp/ad-20230225-pvpgn-1.smk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20230225-pvpgn-1.smk -------------------------------------------------------------------------------- /var/bnftp/ad-20230225-pvpgn-2.mng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20230225-pvpgn-2.mng -------------------------------------------------------------------------------- /var/bnftp/ad-20230225-pvpgn-2.smk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/var/bnftp/ad-20230225-pvpgn-2.smk -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/MBNCSUtil"] 2 | path = vendor/MBNCSUtil 3 | url = https://github.com/BNETDocs/MBNCSUtil.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /docs/Starcraft Game Advertisement (Advex) Matchmaking Model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BNETDocs/Atlas/HEAD/docs/Starcraft Game Advertisement (Advex) Matchmaking Model.png -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/MessageDirection.cs: -------------------------------------------------------------------------------- 1 | namespace Atlasd.Battlenet.Protocols 2 | { 3 | enum MessageDirection 4 | { 5 | ClientToServer, 6 | ServerToClient, 7 | PeerToPeer, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/VersionInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Atlasd.Battlenet.Protocols.Game 4 | { 5 | class VersionInfo 6 | { 7 | public UInt32 EXEChecksum; 8 | public byte[] EXEInformation; 9 | public UInt32 EXERevision; 10 | public UInt32 VersionByte; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/UDP/Messages.cs: -------------------------------------------------------------------------------- 1 | namespace Atlasd.Battlenet.Protocols.Udp 2 | { 3 | enum Messages : byte 4 | { 5 | PKT_STORM = 0x00, 6 | PKT_CLIENTREQ = 0x03, 7 | PKT_SERVERPING = 0x05, 8 | PKT_KEEPALIVE = 0x07, 9 | PKT_CONNTEST = 0x08, 10 | PKT_CONNTEST2 = 0x09, 11 | } 12 | } -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/IListener.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.Sockets; 3 | 4 | namespace Atlasd.Battlenet.Protocols 5 | { 6 | interface IListener 7 | { 8 | public IPEndPoint LocalEndPoint { get; } 9 | public bool IsListening { get; } 10 | public Socket Socket { get; } 11 | 12 | public void Close(); 13 | public void SetLocalEndPoint(IPEndPoint endp); 14 | public void Start(); 15 | public void Stop(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/LocaleInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Atlasd.Battlenet.Protocols.Game 4 | { 5 | struct LocaleInfo 6 | { 7 | public UInt32 SystemLocaleId; 8 | public UInt32 UserLocaleId; 9 | public UInt32 UserLanguageId; 10 | public UInt32 LanguageCode; 11 | public string LanguageNameAbbreviated; 12 | public string CountryCode; 13 | public string CountryNameAbbreviated; 14 | public string CountryName; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/udptest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import socket 3 | from struct import * 4 | 5 | PKT_SERVERPING = 0x05 6 | PKT_CONNTEST2 = 0x09 7 | 8 | addr = '10.0.1.11' 9 | port = 6112 10 | buf = pack('iii', PKT_CONNTEST2, 0, 0) 11 | 12 | endp = (addr, port) 13 | sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) 14 | 15 | # sending twice mimics official client behavior, but 16 | # it's also a good thing to do for udp datagrams. 17 | 18 | sock.sendto(buf, endp) 19 | sock.sendto(buf, endp) 20 | 21 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Exceptions/GameProtocolViolationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Atlasd.Battlenet.Exceptions 4 | { 5 | class GameProtocolViolationException : GameProtocolException 6 | { 7 | public GameProtocolViolationException(ClientState client) : base(client) { } 8 | public GameProtocolViolationException(ClientState client, string message) : base(client, message) { } 9 | public GameProtocolViolationException(ClientState client, string message, Exception innerException) : base(client, message, innerException) { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /var/bnftp/.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto eol=crlf 5 | 6 | ############################################################################### 7 | # Behavior related to Battle.net BNFTP files. 8 | ############################################################################### 9 | *.bni binary 10 | *.ini text eol=crlf 11 | *.mng binary 12 | *.mpq binary 13 | *.smk binary 14 | *.txt text eol=crlf 15 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Exceptions/ChatProtocolException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Atlasd.Battlenet.Exceptions 4 | { 5 | class ChatProtocolException : ProtocolException 6 | { 7 | public ChatProtocolException(ClientState client) : base(Battlenet.ProtocolType.Types.Chat, client) { } 8 | public ChatProtocolException(ClientState client, string message) : base(Battlenet.ProtocolType.Types.Chat, client, message) { } 9 | public ChatProtocolException(ClientState client, string message, Exception innerException) : base(Battlenet.ProtocolType.Types.Chat, client, message, innerException) { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Exceptions/GameProtocolException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Atlasd.Battlenet.Exceptions 4 | { 5 | class GameProtocolException : ProtocolException 6 | { 7 | public GameProtocolException(ClientState client) : base(Battlenet.ProtocolType.Types.Game, client) { } 8 | public GameProtocolException(ClientState client, string message) : base(Battlenet.ProtocolType.Types.Game, client, message) { } 9 | public GameProtocolException(ClientState client, string message, Exception innerException) : base(Battlenet.ProtocolType.Types.Game, client, message, innerException) { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Exceptions/BNFTPProtocolException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Atlasd.Battlenet.Exceptions 4 | { 5 | class BNFTPProtocolException : ProtocolException 6 | { 7 | public BNFTPProtocolException(ClientState client) : base(Battlenet.ProtocolType.Types.BNFTP, client) { } 8 | public BNFTPProtocolException(ClientState client, string message) : base(Battlenet.ProtocolType.Types.BNFTP, client, message) { } 9 | public BNFTPProtocolException(ClientState client, string message, Exception innerException) : base(Battlenet.ProtocolType.Types.BNFTP, client, message, innerException) { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/OldAuth.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Atlasd.Battlenet.Protocols.Game 5 | { 6 | class OldAuth 7 | { 8 | public static byte[] CheckDoubleHashData(byte[] data, uint clientToken, uint serverToken) 9 | { 10 | var buf = new byte[28]; 11 | using var m = new MemoryStream(buf); 12 | using var w = new BinaryWriter(m); 13 | 14 | w.Write(clientToken); 15 | w.Write(serverToken); 16 | w.Write(data); 17 | 18 | return MBNCSUtil.XSha1.CalculateHash(buf); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Common.cs: -------------------------------------------------------------------------------- 1 | namespace Atlasd.Battlenet.Protocols 2 | { 3 | class Common 4 | { 5 | public const string HumanDateTimeFormat = "ddd MMM dd hh:mm tt"; // example: "Sat Oct 17 6:11 AM" 6 | 7 | public static string DirectionToString(MessageDirection direction) 8 | { 9 | return (direction) switch 10 | { 11 | MessageDirection.ClientToServer => "C>S", 12 | MessageDirection.ServerToClient => "S>C", 13 | MessageDirection.PeerToPeer => "P2P", 14 | _ => "???", 15 | }; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/MessageContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Atlasd.Battlenet.Protocols.Game 4 | { 5 | class MessageContext 6 | { 7 | public Dictionary Arguments { get; protected set; } 8 | public ClientState Client { get; protected set; } 9 | public MessageDirection Direction { get; protected set; } 10 | 11 | public MessageContext(ClientState client, MessageDirection direction, Dictionary arguments = null) 12 | { 13 | Arguments = arguments; 14 | Client = client; 15 | Direction = direction; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /COPYRIGHT.txt: -------------------------------------------------------------------------------- 1 | Atlas 2 | 3 | Copyright (c) 2020 Carl Bennett 4 | Copyright (c) 2020 William LaFrance 5 | 6 | Special thanks to: BNETDocs 7 | Honorable mention to: Valhalla Legends (a.k.a. vL) 8 | 9 | Atlas is free software distributed under the MIT License. It is not officially 10 | affiliated with or endorsed by Blizzard Entertainment, its subsidiaries, or 11 | business partners. Battle.net, Diablo, StarCraft, and WarCraft are registered 12 | trademarks of Blizzard Entertainment in the United States. This software is 13 | provided as-is in the hopes that it is useful without warranty of any kind. 14 | 15 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/ChatCommandContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Atlasd.Battlenet.Protocols.Game 4 | { 5 | class ChatCommandContext 6 | { 7 | public ChatCommand Command { get; protected set; } 8 | public Dictionary Environment { get; protected set; } 9 | public GameState GameState { get; protected set; } 10 | 11 | public ChatCommandContext(ChatCommand command, Dictionary environment, GameState gameState) 12 | { 13 | Command = command; 14 | Environment = environment; 15 | GameState = gameState; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/MCP/Messages.cs: -------------------------------------------------------------------------------- 1 | namespace Atlasd.Battlenet.Protocols.MCP 2 | { 3 | enum Messages : byte 4 | { 5 | MCP_STARTUP = 0x01, 6 | MCP_CHARCREATE = 0x02, 7 | MCP_CREATEGAME = 0x03, 8 | MCP_JOINGAME = 0x04, 9 | MCP_GAMELIST = 0x05, 10 | MCP_GAMEINFO = 0x06, 11 | MCP_CHARLOGON = 0x07, 12 | MCP_CHARDELETE = 0x0A, 13 | MCP_REQUESTLADDERDATA = 0x11, 14 | MCP_MOTD = 0x12, 15 | MCP_CANCELGAMECREATE = 0x13, 16 | MCP_CREATEQUEUE = 0x14, 17 | MCP_CHARRANK = 0x16, 18 | MCP_CHARLIST = 0x17, 19 | MCP_CHARUPGRADE = 0x18, 20 | MCP_CHARLIST2 = 0x19, 21 | } 22 | } -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Exceptions/ClientException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace Atlasd.Battlenet.Exceptions 5 | { 6 | class ClientException : Exception 7 | { 8 | public ClientState Client { get; private set; } 9 | 10 | public ClientException(ClientState client) : base() 11 | { 12 | Client = client; 13 | } 14 | 15 | public ClientException(ClientState client, string message) : base(message) 16 | { 17 | Client = client; 18 | } 19 | 20 | public ClientException(ClientState client, string message, Exception innerException) : base(message, innerException) 21 | { 22 | Client = client; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Exceptions/ProtocolNotSupportedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Atlasd.Battlenet.Exceptions 4 | { 5 | class ProtocolNotSupportedException : ClientException 6 | { 7 | public ProtocolType.Types ProtocolType { get; private set; } 8 | 9 | public ProtocolNotSupportedException(ProtocolType.Types protocolType, ClientState client, string message = "Unsupported protocol") : base(client, message) 10 | { 11 | ProtocolType = protocolType; 12 | } 13 | 14 | public ProtocolNotSupportedException(ProtocolType.Types protocolType, ClientState client, string message, Exception innerException) : base(client, message, innerException) 15 | { 16 | ProtocolType = protocolType; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /var/bnftp/bnserver.ini: -------------------------------------------------------------------------------- 1 | [Server List Version] 2 | VER=1001 3 | 4 | [Server Gateways] 5 | 1=uswest.battle.net 6 | 2=useast.battle.net 7 | 3=asia.battle.net 8 | 4=europe.battle.net 9 | 5=atlas.bnetdocs.org 10 | 6=10.0.1.4 11 | 7=pvpgn.bnetdocs.org 12 | 13 | [uswest.battle.net] 14 | ZONE=8 15 | ENU=U.S. West 16 | FRA=U.S. Ouest 17 | 18 | [useast.battle.net] 19 | ZONE=6 20 | ENU=U.S. East 21 | FRA=U.S. Est 22 | 23 | [asia.battle.net] 24 | ZONE=-9 25 | ENU=Asia 26 | FRA=Asie 27 | 28 | [europe.battle.net] 29 | ZONE=-1 30 | ENU=Europe 31 | FRA=Europe 32 | 33 | [atlas.bnetdocs.org] 34 | ZONE=6 35 | ENU=Atlas@BNETDocs 36 | FRA=Atlas@BNETDocs 37 | 38 | [10.0.1.4] 39 | ZONE=6 40 | ENU=AtlasDebug@BNETDocs 41 | FRA=AtlasDebug@BNETDocs 42 | 43 | [pvpgn.bnetdocs.org] 44 | ZONE=6 45 | ENU=PvPGN@BNETDocs 46 | FRA=PvPGN@BNETDocs 47 | -------------------------------------------------------------------------------- /var/bnftp/bnserver-D2DV.ini: -------------------------------------------------------------------------------- 1 | [Server List Version] 2 | VER=1001 3 | 4 | [Server Gateways] 5 | 1=uswest.battle.net 6 | 2=useast.battle.net 7 | 3=asia.battle.net 8 | 4=europe.battle.net 9 | 5=atlas.bnetdocs.org 10 | 6=10.0.1.4 11 | 7=pvpgn.bnetdocs.org 12 | 13 | [uswest.battle.net] 14 | ZONE=8 15 | ENU=U.S. West 16 | FRA=U.S. Ouest 17 | 18 | [useast.battle.net] 19 | ZONE=6 20 | ENU=U.S. East 21 | FRA=U.S. Est 22 | 23 | [asia.battle.net] 24 | ZONE=-9 25 | ENU=Asia 26 | FRA=Asie 27 | 28 | [europe.battle.net] 29 | ZONE=-1 30 | ENU=Europe 31 | FRA=Europe 32 | 33 | [atlas.bnetdocs.org] 34 | ZONE=6 35 | ENU=Atlas@BNETDocs 36 | FRA=Atlas@BNETDocs 37 | 38 | [10.0.1.4] 39 | ZONE=6 40 | ENU=AtlasDebug@BNETDocs 41 | FRA=AtlasDebug@BNETDocs 42 | 43 | [pvpgn.bnetdocs.org] 44 | ZONE=6 45 | ENU=PvPGN@BNETDocs 46 | FRA=PvPGN@BNETDocs 47 | -------------------------------------------------------------------------------- /var/bnftp/bnserver-D2XP.ini: -------------------------------------------------------------------------------- 1 | [Server List Version] 2 | VER=1001 3 | 4 | [Server Gateways] 5 | 1=uswest.battle.net 6 | 2=useast.battle.net 7 | 3=asia.battle.net 8 | 4=europe.battle.net 9 | 5=atlas.bnetdocs.org 10 | 6=10.0.1.4 11 | 7=pvpgn.bnetdocs.org 12 | 13 | [uswest.battle.net] 14 | ZONE=8 15 | ENU=U.S. West 16 | FRA=U.S. Ouest 17 | 18 | [useast.battle.net] 19 | ZONE=6 20 | ENU=U.S. East 21 | FRA=U.S. Est 22 | 23 | [asia.battle.net] 24 | ZONE=-9 25 | ENU=Asia 26 | FRA=Asie 27 | 28 | [europe.battle.net] 29 | ZONE=-1 30 | ENU=Europe 31 | FRA=Europe 32 | 33 | [atlas.bnetdocs.org] 34 | ZONE=6 35 | ENU=Atlas@BNETDocs 36 | FRA=Atlas@BNETDocs 37 | 38 | [10.0.1.4] 39 | ZONE=6 40 | ENU=AtlasDebug@BNETDocs 41 | FRA=AtlasDebug@BNETDocs 42 | 43 | [pvpgn.bnetdocs.org] 44 | ZONE=6 45 | ENU=PvPGN@BNETDocs 46 | FRA=PvPGN@BNETDocs 47 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Exceptions/ProtocolException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Atlasd.Battlenet.Exceptions 4 | { 5 | class ProtocolException : ClientException 6 | { 7 | public ProtocolType.Types ProtocolType { get; protected set; } 8 | 9 | public ProtocolException(ProtocolType.Types protocolType, ClientState client) : base(client) 10 | { 11 | ProtocolType = protocolType; 12 | } 13 | 14 | public ProtocolException(ProtocolType.Types protocolType, ClientState client, string message) : base(client, message) 15 | { 16 | ProtocolType = protocolType; 17 | } 18 | 19 | public ProtocolException(ProtocolType.Types protocolType, ClientState client, string message, Exception innerException) : base(client, message, innerException) 20 | { 21 | ProtocolType = protocolType; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/AccountKeyValue.cs: -------------------------------------------------------------------------------- 1 | namespace Atlasd.Battlenet 2 | { 3 | class AccountKeyValue 4 | { 5 | public enum ReadLevel 6 | { 7 | Any, 8 | Owner, 9 | Internal, 10 | }; 11 | public enum WriteLevel 12 | { 13 | Any, 14 | Owner, 15 | Internal, 16 | ReadOnly, 17 | }; 18 | 19 | public string Key { get; private set; } 20 | public ReadLevel Readable { get; private set; } 21 | public dynamic Value; 22 | public WriteLevel Writable { get; private set; } 23 | 24 | public AccountKeyValue(string key, dynamic value, ReadLevel readable, WriteLevel writable) 25 | { 26 | Key = key; 27 | Readable = readable; 28 | Value = value; 29 | Writable = writable; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /var/bnftp/bnserver-WAR3.ini: -------------------------------------------------------------------------------- 1 | [Server List Version] 2 | VER=1008 3 | 4 | [Server Gateways] 5 | 1=uswest.battle.net 6 | 2=useast.battle.net 7 | 3=asia.battle.net 8 | 4=europe.battle.net 9 | 5=classicbeta.battle.net 10 | 6=atlas.bnetdocs.org 11 | 7=10.0.1.4 12 | 8=pvpgn.bnetdocs.org 13 | 14 | [uswest.battle.net] 15 | ZONE=8 16 | ENU=Lordaeron (U.S. West) 17 | 18 | [useast.battle.net] 19 | ZONE=6 20 | ENU=Azeroth (U.S. East) 21 | 22 | [asia.battle.net] 23 | ZONE=-9 24 | ENU=Kalimdor (Asia) 25 | 26 | [europe.battle.net] 27 | ZONE=-1 28 | ENU=Northrend (Europe) 29 | 30 | [classicbeta.battle.net] 31 | ZONE=8 32 | ENU=Westfall (ClassicBeta) 33 | 34 | [atlas.bnetdocs.org] 35 | ZONE=6 36 | ENU=Atlas@BNETDocs 37 | FRA=Atlas@BNETDocs 38 | 39 | [10.0.1.4] 40 | ZONE=6 41 | ENU=AtlasDebug@BNETDocs 42 | FRA=AtlasDebug@BNETDocs 43 | 44 | [pvpgn.bnetdocs.org] 45 | ZONE=6 46 | ENU=PvPGN@BNETDocs 47 | FRA=PvPGN@BNETDocs 48 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/InvalidCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System.Collections.Generic; 3 | 4 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 5 | { 6 | class InvalidCommand : ChatCommand 7 | { 8 | public InvalidCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 9 | 10 | public override bool CanInvoke(ChatCommandContext context) 11 | { 12 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 13 | } 14 | 15 | public override void Invoke(ChatCommandContext context) 16 | { 17 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, Resources.InvalidChatCommand).WriteTo(context.GameState.Client); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | pull_request: 7 | branches: [ develop ] 8 | 9 | jobs: 10 | build-atlasd: 11 | name: Build Atlasd 12 | 13 | runs-on: ubuntu-latest 14 | env: 15 | working-directory: ./src/Atlasd 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Checkout submodules 20 | run: git submodule update --init --recursive 21 | - name: Setup .NET Core 22 | uses: actions/setup-dotnet@v1 23 | with: 24 | dotnet-version: 3.1.301 25 | - name: Install dependencies 26 | run: dotnet restore 27 | working-directory: ${{env.working-directory}} 28 | - name: Build 29 | run: dotnet build --configuration Release --no-restore 30 | working-directory: ${{env.working-directory}} 31 | - name: Test 32 | run: dotnet test --no-restore --verbosity normal 33 | working-directory: ${{env.working-directory}} 34 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Platform.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Atlasd.Battlenet 4 | { 5 | 6 | public class Platform 7 | { 8 | public enum PlatformCode : UInt32 9 | { 10 | None = 0, // None/Zero/Null 11 | MacOSPPC = 0x504D4143, // PMAC 12 | MacOSX86 = 0x584D4143, // XMAC 13 | Windows = 0x49583836, // IX86 14 | } 15 | 16 | public static string PlatformName(PlatformCode code, bool extended = true) 17 | { 18 | return code switch 19 | { 20 | PlatformCode.None => "None", 21 | PlatformCode.MacOSPPC => $"macOS Classic{(extended ? " (PowerPC)" : "")}", 22 | PlatformCode.MacOSX86 => $"macOS{(extended ? " (x86)" : "")}", 23 | PlatformCode.Windows => $"Windows{(extended ? " (x86)" : "")}", 24 | _ => $"Unknown{(extended ? $" (0x{(UInt32)code:X8})" : "")}", 25 | }; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/BinaryWriter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace Atlasd.Battlenet 5 | { 6 | class BinaryWriter : System.IO.BinaryWriter 7 | { 8 | private readonly object _lock = new object(); 9 | 10 | public BinaryWriter(Stream output) : base(output, Encoding.UTF8) { } 11 | public BinaryWriter(Stream output, Encoding encoding) : base(output, encoding) { } 12 | public BinaryWriter(Stream output, Encoding encoding, bool leaveOpen) : base(output, encoding, leaveOpen) { } 13 | 14 | public override void Write(string value) 15 | { 16 | lock (_lock) 17 | { 18 | if (value != null) Write(Encoding.UTF8.GetBytes(value)); 19 | Write((byte)0); // null-terminator 20 | } 21 | } 22 | 23 | public void WriteByteString(byte[] value) 24 | { 25 | lock (_lock) 26 | { 27 | if (value != null) Write(value); 28 | Write((byte)0); // null-terminator 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Carl Bennett, William LaFrance, see COPYRIGHT.txt document. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_NULL.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | 4 | namespace Atlasd.Battlenet.Protocols.Game.Messages 5 | { 6 | class SID_NULL : Message 7 | { 8 | public SID_NULL() 9 | { 10 | Id = (byte)MessageIds.SID_NULL; 11 | Buffer = new byte[0]; 12 | } 13 | 14 | public SID_NULL(byte[] buffer) 15 | { 16 | Id = (byte)MessageIds.SID_NULL; 17 | Buffer = buffer; 18 | } 19 | 20 | public override bool Invoke(MessageContext context) 21 | { 22 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 23 | 24 | if (Buffer.Length != 0) 25 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be 0 bytes"); 26 | 27 | if (context.Direction == MessageDirection.ServerToClient) 28 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 29 | 30 | return true; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | // Use IntelliSense to find out which attributes exist for C# debugging 6 | // Use hover for the description of the existing attributes 7 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/src/Atlasd/bin/Debug/netcoreapp3.1/atlasd.dll", 14 | "args": ["--config=${workspaceFolder}/etc/atlasd.json"], 15 | "cwd": "${workspaceFolder}/src/Atlasd", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/UsersCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 6 | { 7 | class UsersCommand : ChatCommand 8 | { 9 | public UsersCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 10 | 11 | public override bool CanInvoke(ChatCommandContext context) 12 | { 13 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 14 | } 15 | 16 | public override void Invoke(ChatCommandContext context) 17 | { 18 | var r = Battlenet.Common.GetServerStats(context.GameState.Client); 19 | 20 | foreach (var kv in context.Environment) 21 | { 22 | r = r.Replace("{" + kv.Key + "}", kv.Value); 23 | } 24 | 25 | foreach (var line in r.Split(Battlenet.Common.NewLine)) 26 | new ChatEvent(ChatEvent.EventIds.EID_INFO, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/ReJoinCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 4 | { 5 | class ReJoinCommand : ChatCommand 6 | { 7 | public ReJoinCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 8 | 9 | public override bool CanInvoke(ChatCommandContext context) 10 | { 11 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 12 | } 13 | 14 | public override void Invoke(ChatCommandContext context) 15 | { 16 | if (context.GameState.ActiveChannel == null) 17 | { 18 | new InvalidCommand(RawBuffer, Arguments).Invoke(context); 19 | return; 20 | } 21 | 22 | if (!(context.GameState.ChannelFlags.HasFlag(Account.Flags.Employee) 23 | || context.GameState.ChannelFlags.HasFlag(Account.Flags.ChannelOp) 24 | || context.GameState.ChannelFlags.HasFlag(Account.Flags.Admin))) 25 | { 26 | return; 27 | } 28 | 29 | Channel.MoveUser(context.GameState, context.GameState.ActiveChannel, true); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/TimeCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 6 | { 7 | class TimeCommand : ChatCommand 8 | { 9 | public TimeCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 10 | 11 | public override bool CanInvoke(ChatCommandContext context) 12 | { 13 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 14 | } 15 | 16 | public override void Invoke(ChatCommandContext context) 17 | { 18 | var r = Resources.TimeCommand; 19 | 20 | foreach (var kv in context.Environment) 21 | { 22 | r = r.Replace("{" + kv.Key + "}", kv.Value); 23 | } 24 | 25 | r = r.Replace(" 0", " "); 26 | 27 | foreach (var line in r.Split(Battlenet.Common.NewLine)) 28 | { 29 | new ChatEvent(ChatEvent.EventIds.EID_INFO, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Frame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | 4 | namespace Atlasd.Battlenet.Protocols.Game 5 | { 6 | class Frame 7 | { 8 | public ConcurrentQueue Messages { get; protected set; } 9 | 10 | public Frame() 11 | { 12 | Messages = new ConcurrentQueue(); 13 | } 14 | 15 | public Frame(ConcurrentQueue messages) 16 | { 17 | Messages = messages; 18 | } 19 | 20 | public byte[] ToByteArray(ProtocolType protocolType) 21 | { 22 | var framebuf = new byte[0]; 23 | var msgs = new ConcurrentQueue(Messages); // Clone Messages into local variable 24 | 25 | while (msgs.Count > 0) 26 | { 27 | if (!msgs.TryDequeue(out var msg)) break; 28 | 29 | var messagebuf = msg.ToByteArray(protocolType); 30 | var buf = new byte[framebuf.Length + messagebuf.Length]; 31 | 32 | Buffer.BlockCopy(framebuf, 0, buf, 0, framebuf.Length); 33 | Buffer.BlockCopy(messagebuf, 0, buf, framebuf.Length, messagebuf.Length); 34 | 35 | framebuf = buf; 36 | } 37 | 38 | return framebuf; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/src/Atlasd/Atlasd.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/src/Atlasd/Atlasd.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "--project", 36 | "${workspaceFolder}/src/Atlasd/Atlasd.csproj" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Advertisement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Threading; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game 7 | { 8 | class Advertisement 9 | { 10 | public long DisplayCount; 11 | public string Filename { get; private set; } 12 | public DateTime Filetime { get; private set; } 13 | public string Url { get; private set; } 14 | public List Products { get; private set; } 15 | public List Locales { get; private set; } 16 | 17 | public Advertisement(string filename, string url, List products = null, List locales = null) 18 | { 19 | var file = new BNFTP.File(filename); 20 | if (file != null) 21 | { 22 | Filename = file.Name; 23 | Filetime = file.LastAccessTimeUtc; 24 | } 25 | else 26 | { 27 | Filename = filename; 28 | Filetime = DateTime.MinValue; 29 | } 30 | 31 | Url = url; 32 | Products = products; 33 | Locales = locales; 34 | 35 | DisplayCount = 0; // Each matching SID_DISPLAYAD increments this by one. 36 | } 37 | 38 | public void IncrementDisplayCount() 39 | { 40 | Interlocked.Increment(ref DisplayCount); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/EmoteCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System.Collections.Generic; 3 | 4 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 5 | { 6 | class EmoteCommand : ChatCommand 7 | { 8 | public EmoteCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 9 | 10 | public override bool CanInvoke(ChatCommandContext context) 11 | { 12 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 13 | } 14 | 15 | public override void Invoke(ChatCommandContext context) 16 | { 17 | if (context.GameState.ActiveChannel == null) 18 | { 19 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, Resources.InvalidChatCommand).WriteTo(context.GameState.Client); 20 | return; 21 | } 22 | 23 | context.GameState.ActiveChannel.WriteChatMessage(context.GameState, RawBuffer, true); 24 | 25 | if (context.GameState.ActiveChannel.Count <= 1 || context.GameState.ActiveChannel.ActiveFlags.HasFlag(Channel.Flags.Silent)) 26 | { 27 | new ChatEvent(ChatEvent.EventIds.EID_INFO, context.GameState.ActiveChannel.ActiveFlags, 0, context.GameState.ActiveChannel.Name, Resources.NoOneHearsYou).WriteTo(context.GameState.Client); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_FLOODDETECTED.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | 4 | namespace Atlasd.Battlenet.Protocols.Game.Messages 5 | { 6 | class SID_FLOODDETECTED : Message 7 | { 8 | 9 | public SID_FLOODDETECTED() 10 | { 11 | Id = (byte)MessageIds.SID_FLOODDETECTED; 12 | Buffer = new byte[0]; 13 | } 14 | 15 | public SID_FLOODDETECTED(byte[] buffer) 16 | { 17 | Id = (byte)MessageIds.SID_FLOODDETECTED; 18 | Buffer = buffer; 19 | } 20 | 21 | public override bool Invoke(MessageContext context) 22 | { 23 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 24 | 25 | if (context.Direction != MessageDirection.ServerToClient) 26 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from server to client"); 27 | 28 | if (Buffer.Length != 0) 29 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be 0 bytes"); 30 | 31 | Logging.WriteLine(Logging.LogLevel.Info, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"Sent flood detected to client"); 32 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 33 | return true; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_FRIENDSREMOVE.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System.IO; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Game.Messages 6 | { 7 | class SID_FRIENDSREMOVE : Message 8 | { 9 | 10 | public SID_FRIENDSREMOVE() 11 | { 12 | Id = (byte)MessageIds.SID_FRIENDSREMOVE; 13 | Buffer = new byte[0]; 14 | } 15 | 16 | public SID_FRIENDSREMOVE(byte[] buffer) 17 | { 18 | Id = (byte)MessageIds.SID_FRIENDSREMOVE; 19 | Buffer = buffer; 20 | } 21 | 22 | public override bool Invoke(MessageContext context) 23 | { 24 | if (context.Direction != MessageDirection.ServerToClient) 25 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from server to client"); 26 | 27 | /** 28 | * (UINT8) Entry number 29 | */ 30 | 31 | Buffer = new byte[1]; 32 | 33 | using var m = new MemoryStream(Buffer); 34 | using var w = new BinaryWriter(m); 35 | 36 | w.Write((byte)context.Arguments["friend"]); 37 | 38 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 39 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 40 | return true; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/HTTP/HttpHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Http 6 | { 7 | class HttpHeader 8 | { 9 | public const int MaxKeyLength = 4096; 10 | public const int MaxValueLength = 4096; 11 | 12 | protected string Key; 13 | protected string Value; 14 | 15 | public HttpHeader(string key, string value) 16 | { 17 | SetKey(key); 18 | SetValue(value); 19 | } 20 | 21 | public string GetKey() 22 | { 23 | return Key; 24 | } 25 | 26 | public string GetValue() 27 | { 28 | return Value; 29 | } 30 | 31 | public void SetKey(string value) 32 | { 33 | if (string.IsNullOrEmpty(value) || value.Length > MaxKeyLength) 34 | { 35 | throw new ArgumentOutOfRangeException($"value length must be between 1-{MaxKeyLength}"); 36 | } 37 | 38 | Key = value; 39 | } 40 | 41 | public void SetValue(string value) 42 | { 43 | if (string.IsNullOrEmpty(value) || value.Length > MaxValueLength) 44 | { 45 | throw new ArgumentOutOfRangeException($"value length must be between 1-{MaxValueLength}"); 46 | } 47 | 48 | Value = value; 49 | } 50 | 51 | public override string ToString() 52 | { 53 | return $"{Key}: {Value}\r\n"; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/AdminBroadcastCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Daemon; 2 | using Atlasd.Localization; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 7 | { 8 | class AdminBroadcastCommand : ChatCommand 9 | { 10 | public AdminBroadcastCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 11 | 12 | public override bool CanInvoke(ChatCommandContext context) 13 | { 14 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 15 | } 16 | 17 | public override void Invoke(ChatCommandContext context) 18 | { 19 | Task.Run(() => 20 | { 21 | var maskBroadcaster = Settings.GetBoolean(new string[] { "battlenet", "emulation", "mask_admins_in_broadcasts" }, false); 22 | 23 | var broadcasterFlags = maskBroadcaster ? Account.Flags.Admin : context.GameState.ChannelFlags; 24 | var broadcasterPing = maskBroadcaster ? -1 : context.GameState.Ping; 25 | var broadcasterName = maskBroadcaster ? Settings.GetString(new string[] { "battlenet", "realm", "name" }, Resources.Battlenet) : context.GameState.OnlineName; 26 | 27 | var chatEvent = new ChatEvent(ChatEvent.EventIds.EID_BROADCAST, broadcasterFlags, broadcasterPing, broadcasterName, RawBuffer); 28 | foreach (var gameState in Battlenet.Common.ActiveGameStates.Values) chatEvent.WriteTo(gameState.Client); 29 | }); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_LEAVECHAT.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Game.Messages 6 | { 7 | class SID_LEAVECHAT : Message 8 | { 9 | 10 | public SID_LEAVECHAT() 11 | { 12 | Id = (byte)MessageIds.SID_LEAVECHAT; 13 | Buffer = new byte[0]; 14 | } 15 | 16 | public SID_LEAVECHAT(byte[] buffer) 17 | { 18 | Id = (byte)MessageIds.SID_LEAVECHAT; 19 | Buffer = buffer; 20 | } 21 | 22 | public override bool Invoke(MessageContext context) 23 | { 24 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 25 | 26 | if (context.Direction != MessageDirection.ClientToServer) 27 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from client to server"); 28 | 29 | if (Buffer.Length != 0) 30 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be 0 bytes, got {Buffer.Length}"); 31 | 32 | try 33 | { 34 | lock (context.Client.GameState.ActiveChannel) 35 | context.Client.GameState.ActiveChannel.RemoveUser(context.Client.GameState); 36 | } 37 | catch (ArgumentNullException) { } 38 | catch (NullReferenceException) { } 39 | 40 | return true; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_FRIENDSPOSITION.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System.IO; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Game.Messages 6 | { 7 | class SID_FRIENDSPOSITION : Message 8 | { 9 | 10 | public SID_FRIENDSPOSITION() 11 | { 12 | Id = (byte)MessageIds.SID_FRIENDSPOSITION; 13 | Buffer = new byte[0]; 14 | } 15 | 16 | public SID_FRIENDSPOSITION(byte[] buffer) 17 | { 18 | Id = (byte)MessageIds.SID_FRIENDSPOSITION; 19 | Buffer = buffer; 20 | } 21 | 22 | public override bool Invoke(MessageContext context) 23 | { 24 | if (context.Direction != MessageDirection.ServerToClient) 25 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from server to client"); 26 | 27 | /** 28 | * (UINT8) Old entry number 29 | * (UINT8) New entry number 30 | */ 31 | 32 | Buffer = new byte[2]; 33 | 34 | using var m = new MemoryStream(Buffer); 35 | using var w = new BinaryWriter(m); 36 | 37 | w.Write((byte)context.Arguments["old"]); 38 | w.Write((byte)context.Arguments["new"]); 39 | 40 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 41 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 42 | return true; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/JoinCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 6 | { 7 | class JoinCommand : ChatCommand 8 | { 9 | public JoinCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 10 | 11 | public override bool CanInvoke(ChatCommandContext context) 12 | { 13 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 14 | } 15 | 16 | public override void Invoke(ChatCommandContext context) 17 | { 18 | var gs = context.GameState; 19 | if (gs.ActiveChannel == null) 20 | { 21 | new InvalidCommand(RawBuffer, Arguments).Invoke(context); 22 | return; 23 | } 24 | 25 | if (Arguments.Count < 1) 26 | { 27 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, gs.ChannelFlags, gs.Client.RemoteIPAddress, gs.Ping, gs.OnlineName, Resources.InvalidChannelName).WriteTo(gs.Client); 28 | return; 29 | } 30 | 31 | var channelName = string.Join(" ", Arguments); 32 | 33 | gs.ActiveAccount.Get(Account.FlagsKey, out var userFlags); 34 | var ignoreLimits = ((Account.Flags)((AccountKeyValue)userFlags).Value).HasFlag(Account.Flags.Employee); 35 | 36 | if (StringComparer.InvariantCultureIgnoreCase.Equals(channelName, gs.ActiveChannel.Name)) return; 37 | Channel.MoveUser(gs, channelName, true, ignoreLimits, false); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Atlas.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29519.87 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Atlasd", "Atlasd\Atlasd.csproj", "{E6E0DA5F-CDF0-41F6-A3B3-66EAEF70B9EC}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBNCSUtil", "..\vendor\MBNCSUtil\src\MBNCSUtil.csproj", "{C1DC2666-B308-4B01-A037-7C9F7ECFBD1E}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {E6E0DA5F-CDF0-41F6-A3B3-66EAEF70B9EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {E6E0DA5F-CDF0-41F6-A3B3-66EAEF70B9EC}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {E6E0DA5F-CDF0-41F6-A3B3-66EAEF70B9EC}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {E6E0DA5F-CDF0-41F6-A3B3-66EAEF70B9EC}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {C1DC2666-B308-4B01-A037-7C9F7ECFBD1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {C1DC2666-B308-4B01-A037-7C9F7ECFBD1E}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {C1DC2666-B308-4B01-A037-7C9F7ECFBD1E}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {C1DC2666-B308-4B01-A037-7C9F7ECFBD1E}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {1A27F8FF-3253-43A6-8397-E83D85FD2034} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_CLICKAD.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.Messages 7 | { 8 | class SID_CLICKAD : Message 9 | { 10 | public SID_CLICKAD() 11 | { 12 | Id = (byte)MessageIds.SID_CLICKAD; 13 | Buffer = new byte[0]; 14 | } 15 | 16 | public SID_CLICKAD(byte[] buffer) 17 | { 18 | Id = (byte)MessageIds.SID_CLICKAD; 19 | Buffer = buffer; 20 | } 21 | 22 | public override bool Invoke(MessageContext context) 23 | { 24 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 25 | 26 | if (context.Direction != MessageDirection.ClientToServer) 27 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from client to server"); 28 | 29 | if (Buffer.Length != 8) 30 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be 8 bytes"); 31 | 32 | using var m = new MemoryStream(Buffer); 33 | using var r = new BinaryReader(m); 34 | 35 | var adId = r.ReadUInt32(); 36 | var requestType = r.ReadUInt32(); 37 | 38 | Logging.WriteLine(Logging.LogLevel.Info, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"Ad [Id: 0x{adId:X8}] was clicked!"); 39 | 40 | return true; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_CHATEVENT.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.Text; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.Messages 7 | { 8 | class SID_CHATEVENT : Message 9 | { 10 | public SID_CHATEVENT() 11 | { 12 | Id = (byte)MessageIds.SID_CHATEVENT; 13 | Buffer = new byte[26]; 14 | } 15 | 16 | public SID_CHATEVENT(byte[] buffer) 17 | { 18 | Id = (byte)MessageIds.SID_CHATEVENT; 19 | Buffer = buffer; 20 | } 21 | 22 | public override bool Invoke(MessageContext context) 23 | { 24 | if (context.Direction == MessageDirection.ClientToServer) 25 | throw new GameProtocolViolationException(context.Client, $"Client is not allowed to send {MessageName(Id)}"); 26 | 27 | var chatEvent = (ChatEvent)context.Arguments["chatEvent"]; 28 | Buffer = chatEvent.ToByteArray(context.Client.ProtocolType.Type); 29 | 30 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)}: {ChatEvent.EventIdToString(chatEvent.EventId)} ({4 + Buffer.Length:D} bytes)"); 31 | 32 | return true; 33 | } 34 | 35 | public new byte[] ToByteArray(ProtocolType protocolType) 36 | { 37 | if (protocolType.IsChat()) 38 | { 39 | return Buffer; 40 | } 41 | else 42 | { 43 | return base.ToByteArray(protocolType); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/BinaryReader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace Atlasd.Battlenet 5 | { 6 | class BinaryReader : System.IO.BinaryReader 7 | { 8 | private readonly object _lock = new object(); 9 | 10 | public BinaryReader(Stream input) : base(input, Encoding.UTF8) { } 11 | public BinaryReader(Stream input, Encoding encoding) : base(input, encoding) { } 12 | public BinaryReader(Stream input, Encoding encoding, bool leaveOpen) : base(input, encoding, leaveOpen) { } 13 | 14 | public long GetNextNull() 15 | { 16 | lock (_lock) 17 | { 18 | long lastPosition = BaseStream.Position; 19 | 20 | while (BaseStream.CanRead) 21 | { 22 | if (ReadByte() == 0) 23 | { 24 | long r = BaseStream.Position; 25 | BaseStream.Position = lastPosition; 26 | return r; 27 | } 28 | } 29 | 30 | return -1; 31 | } 32 | } 33 | 34 | public byte[] ReadByteString() 35 | { 36 | lock (_lock) 37 | { 38 | var size = GetNextNull() - BaseStream.Position; 39 | return ReadBytes((int)size)[..^1]; 40 | } 41 | } 42 | 43 | public override string ReadString() 44 | { 45 | lock (_lock) 46 | { 47 | string str = ""; 48 | char chr; 49 | while ((int)(chr = ReadChar()) != 0) 50 | str += chr; 51 | return str; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /var/bnftp/tos_USA.txt: -------------------------------------------------------------------------------- 1 | Atlas 2 | 3 | Copyright (c) 2020 Carl Bennett 4 | Copyright (c) 2020 William LaFrance 5 | 6 | Special thanks to: BNETDocs 7 | Honorable mention to: Valhalla Legends (a.k.a. vL) 8 | 9 | Atlas is free software distributed under the MIT License. It is not officially 10 | affiliated with or endorsed by Blizzard Entertainment, its subsidiaries, or 11 | business partners. Battle.net, Diablo, StarCraft, and WarCraft are registered 12 | trademarks of Blizzard Entertainment in the United States. This software is 13 | provided as-is in the hopes that it is useful without warranty of any kind. 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | -------------------------------------------------------------------------------- /var/bnftp/tos-unicode_USA.txt: -------------------------------------------------------------------------------- 1 | Atlas 2 | 3 | Copyright (c) 2020 Carl Bennett 4 | Copyright (c) 2020 William LaFrance 5 | 6 | Special thanks to: BNETDocs 7 | Honorable mention to: Valhalla Legends (a.k.a. vL) 8 | 9 | Atlas is free software distributed under the MIT License. It is not officially 10 | affiliated with or endorsed by Blizzard Entertainment, its subsidiaries, or 11 | business partners. Battle.net, Diablo, StarCraft, and WarCraft are registered 12 | trademarks of Blizzard Entertainment in the United States. This software is 13 | provided as-is in the hopes that it is useful without warranty of any kind. 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/AwayCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 7 | { 8 | class AwayCommand : ChatCommand 9 | { 10 | public AwayCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 11 | 12 | public override bool CanInvoke(ChatCommandContext context) 13 | { 14 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 15 | } 16 | 17 | public override void Invoke(ChatCommandContext context) 18 | { 19 | string message = Arguments.Count == 0 ? null : string.Join(" ", Arguments); 20 | string r; // reply 21 | 22 | if (context.GameState.Away == null || !string.IsNullOrEmpty(message)) 23 | { 24 | context.GameState.Away = string.IsNullOrEmpty(message) ? "Not available" : message; 25 | r = Resources.AwayCommandOn; 26 | } else 27 | { 28 | context.GameState.Away = null; 29 | r = Resources.AwayCommandOff; 30 | } 31 | 32 | foreach (var kv in context.Environment) 33 | { 34 | r = r.Replace("{" + kv.Key + "}", kv.Value); 35 | } 36 | 37 | foreach (var line in r.Split(Battlenet.Common.NewLine)) 38 | { 39 | new ChatEvent(ChatEvent.EventIds.EID_INFO, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_LEAVEGAME.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.Messages 7 | { 8 | class SID_LEAVEGAME : Message 9 | { 10 | public SID_LEAVEGAME() 11 | { 12 | Id = (byte)MessageIds.SID_LEAVEGAME; 13 | Buffer = new byte[0]; 14 | } 15 | 16 | public SID_LEAVEGAME(byte[] buffer) 17 | { 18 | Id = (byte)MessageIds.SID_LEAVEGAME; 19 | Buffer = buffer; 20 | } 21 | 22 | public override bool Invoke(MessageContext context) 23 | { 24 | if (context.Direction != MessageDirection.ClientToServer) 25 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from client to server"); 26 | 27 | if (context.Client == null || !context.Client.Connected || context.Client.GameState == null) return false; 28 | 29 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 30 | 31 | if (Buffer.Length != 0) 32 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be 0 bytes, got {Buffer.Length}"); 33 | 34 | if (!Product.IsDiabloII(context.Client.GameState.Product)) 35 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} received but client not identified as Diablo II"); 36 | 37 | // TODO: Implement action to take from receiving SID_LEAVEGAME. 38 | return true; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_LOGONCHALLENGE.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.Messages 7 | { 8 | class SID_LOGONCHALLENGE : Message 9 | { 10 | 11 | public SID_LOGONCHALLENGE() 12 | { 13 | Id = (byte)MessageIds.SID_LOGONCHALLENGE; 14 | Buffer = new byte[4]; 15 | } 16 | 17 | public SID_LOGONCHALLENGE(byte[] buffer) 18 | { 19 | Id = (byte)MessageIds.SID_LOGONCHALLENGE; 20 | Buffer = buffer; 21 | } 22 | 23 | public override bool Invoke(MessageContext context) 24 | { 25 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 26 | 27 | if (context.Direction != MessageDirection.ServerToClient) 28 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from server to client"); 29 | 30 | if (Buffer.Length != 4) 31 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be 4 bytes"); 32 | 33 | using var m = new MemoryStream(Buffer); 34 | using var w = new BinaryWriter(m); 35 | 36 | w.Write((UInt32)context.Client.GameState.ServerToken); 37 | 38 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 39 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 40 | return true; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/UnBanCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 6 | { 7 | class UnBanCommand : ChatCommand 8 | { 9 | public UnBanCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 10 | 11 | public override bool CanInvoke(ChatCommandContext context) 12 | { 13 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 14 | } 15 | 16 | public override void Invoke(ChatCommandContext context) 17 | { 18 | if (context.GameState.ActiveChannel == null) 19 | { 20 | new InvalidCommand(RawBuffer, Arguments).Invoke(context); 21 | return; 22 | } 23 | 24 | if (!(context.GameState.ChannelFlags.HasFlag(Account.Flags.Employee) 25 | || context.GameState.ChannelFlags.HasFlag(Account.Flags.ChannelOp) 26 | || context.GameState.ChannelFlags.HasFlag(Account.Flags.Admin))) 27 | { 28 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, Resources.YouAreNotAChannelOperator).WriteTo(context.GameState.Client); 29 | return; 30 | } 31 | 32 | var target = ""; 33 | if (Arguments.Count > 0) 34 | { 35 | target = Arguments[0]; 36 | Arguments.RemoveAt(0); 37 | RawBuffer = RawBuffer[(Encoding.UTF8.GetByteCount(target) + (Arguments.Count > 0 ? 1 : 0))..]; 38 | } 39 | 40 | context.GameState.ActiveChannel.UnBanUser(context.GameState, target); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_LOGONCHALLENGEEX.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.Messages 7 | { 8 | class SID_LOGONCHALLENGEEX : Message 9 | { 10 | 11 | public SID_LOGONCHALLENGEEX() 12 | { 13 | Id = (byte)MessageIds.SID_LOGONCHALLENGEEX; 14 | Buffer = new byte[8]; 15 | } 16 | 17 | public SID_LOGONCHALLENGEEX(byte[] buffer) 18 | { 19 | Id = (byte)MessageIds.SID_LOGONCHALLENGEEX; 20 | Buffer = buffer; 21 | } 22 | 23 | public override bool Invoke(MessageContext context) 24 | { 25 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 26 | 27 | if (context.Direction != MessageDirection.ServerToClient) 28 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from server to client"); 29 | 30 | if (Buffer.Length != 8) 31 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be 8 bytes"); 32 | 33 | using var m = new MemoryStream(Buffer); 34 | using var w = new BinaryWriter(m); 35 | 36 | w.Write((UInt32)context.Client.GameState.UDPToken); 37 | w.Write((UInt32)context.Client.GameState.ServerToken); 38 | 39 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 40 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 41 | return true; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/AdminReloadCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Daemon; 2 | using Atlasd.Localization; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 8 | { 9 | class AdminReloadCommand : ChatCommand 10 | { 11 | public AdminReloadCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 12 | 13 | public override bool CanInvoke(ChatCommandContext context) 14 | { 15 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 16 | } 17 | 18 | public override void Invoke(ChatCommandContext context) 19 | { 20 | Exception e; 21 | string r; 22 | ChatEvent.EventIds eid; 23 | 24 | try 25 | { 26 | Settings.Load(); 27 | e = null; 28 | r = Resources.AdminReloadCommandSuccess; 29 | eid = ChatEvent.EventIds.EID_INFO; 30 | } 31 | catch (Exception ex) 32 | { 33 | e = ex; 34 | r = Resources.AdminReloadCommandFailure.Replace("{exception}", e.GetType().Name); 35 | eid = ChatEvent.EventIds.EID_ERROR; 36 | } 37 | 38 | foreach (var kv in context.Environment) 39 | { 40 | r = r.Replace("{" + kv.Key + "}", kv.Value); 41 | } 42 | 43 | foreach (var line in r.Split(Battlenet.Common.NewLine)) 44 | { 45 | new ChatEvent(eid, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 46 | } 47 | 48 | if (e != null) 49 | { 50 | throw e; 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_GAMEDATAADDRESS.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | using System.Net; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.Messages 8 | { 9 | class SID_GAMEDATAADDRESS : Message 10 | { 11 | 12 | public SID_GAMEDATAADDRESS() 13 | { 14 | Id = (byte)MessageIds.SID_GAMEDATAADDRESS; 15 | Buffer = new byte[0]; 16 | } 17 | 18 | public SID_GAMEDATAADDRESS(byte[] buffer) 19 | { 20 | Id = (byte)MessageIds.SID_GAMEDATAADDRESS; 21 | Buffer = buffer; 22 | } 23 | 24 | public override bool Invoke(MessageContext context) 25 | { 26 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 27 | 28 | if (context.Direction != MessageDirection.ClientToServer) 29 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from client to server"); 30 | 31 | if (Buffer.Length != 16) 32 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be 16 bytes"); 33 | 34 | using var m = new MemoryStream(Buffer); 35 | using var r = new BinaryReader(m); 36 | 37 | var unknown0 = r.ReadUInt16(); 38 | var port = r.ReadUInt16(); 39 | var address = r.ReadBytes(4); 40 | var unknown1 = r.ReadUInt32(); 41 | var unknown2 = r.ReadUInt32(); 42 | 43 | context.Client.GameState.GameDataAddress = new IPAddress(address); 44 | context.Client.GameState.GameDataPort = port; 45 | 46 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 47 | return true; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/AdminClanCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Daemon; 2 | using Atlasd.Localization; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 8 | { 9 | class AdminClanCommand : ChatCommand 10 | { 11 | public AdminClanCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 12 | 13 | public override bool CanInvoke(ChatCommandContext context) 14 | { 15 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 16 | } 17 | 18 | public override void Invoke(ChatCommandContext context) 19 | { 20 | var cmd = string.Empty; 21 | if (Arguments.Count > 0) 22 | { 23 | cmd = Arguments[0]; 24 | Arguments.RemoveAt(0); 25 | } 26 | 27 | // Calculates and removes (cmd+' ') from (raw) which prints into (newRaw): 28 | RawBuffer = RawBuffer[(Encoding.UTF8.GetByteCount(cmd) + (Arguments.Count > 0 ? 1 : 0))..]; 29 | 30 | switch (cmd.ToLowerInvariant()) 31 | { 32 | case "list": 33 | new AdminClanListCommand(RawBuffer, Arguments).Invoke(context); return; 34 | default: 35 | { 36 | var g = context.GameState; 37 | var r = Localization.Resources.InvalidAdminCommand; 38 | foreach (var kv in context.Environment) r = r.Replace("{" + kv.Key + "}", kv.Value); 39 | foreach (var line in r.Split(Battlenet.Common.NewLine)) 40 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, g.ChannelFlags, g.Client.RemoteIPAddress, g.Ping, g.OnlineName, line).WriteTo(g.Client); 41 | break; 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/AdminShutdownCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 6 | { 7 | class AdminShutdownCommand : ChatCommand 8 | { 9 | public AdminShutdownCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 10 | 11 | public override bool CanInvoke(ChatCommandContext context) 12 | { 13 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 14 | } 15 | 16 | public override void Invoke(ChatCommandContext context) 17 | { 18 | var periodStr = Arguments.Count == 0 ? "" : Arguments[0]; 19 | if (Arguments.Count > 0) Arguments.RemoveAt(0); 20 | var message = string.Join(' ', Arguments); 21 | if (message.Length == 0) message = null; 22 | 23 | if (periodStr.Length == 0) 24 | { 25 | periodStr = "30"; // default 30 seconds delay if empty periodStr 26 | } 27 | 28 | if (StringComparer.OrdinalIgnoreCase.Equals(periodStr, "cancel") || StringComparer.OrdinalIgnoreCase.Equals(periodStr, "abort")) 29 | { 30 | Battlenet.Common.ScheduleShutdownCancelled(message, context); 31 | } 32 | else 33 | { 34 | if (!double.TryParse(periodStr, out var periodDbl)) 35 | { 36 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, Resources.AdminShutdownCommandParseError).WriteTo(context.GameState.Client); 37 | return; 38 | } 39 | 40 | Battlenet.Common.ScheduleShutdown(TimeSpan.FromSeconds(periodDbl), message, context); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/SquelchCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 7 | { 8 | class SquelchCommand : ChatCommand 9 | { 10 | public SquelchCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 11 | 12 | public override bool CanInvoke(ChatCommandContext context) 13 | { 14 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 15 | } 16 | 17 | public override void Invoke(ChatCommandContext context) 18 | { 19 | string r; // reply 20 | string t; // target 21 | 22 | t = Arguments.Count == 0 ? "" : Arguments[0]; 23 | 24 | if (!Battlenet.Common.GetClientByOnlineName(t, out var target) || target == null) 25 | { 26 | r = Resources.UserNotLoggedOn; 27 | foreach (var line in r.Split(Environment.NewLine)) 28 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 29 | return; 30 | } 31 | 32 | var ipAddress = IPAddress.Parse(target.Client.RemoteEndPoint.ToString().Split(':')[0]); 33 | 34 | lock (context.GameState.SquelchedIPs) 35 | { 36 | if (!context.GameState.SquelchedIPs.Contains(ipAddress)) 37 | { 38 | context.GameState.SquelchedIPs.Add(ipAddress); 39 | } 40 | } 41 | 42 | lock (context.GameState.ActiveChannel) 43 | { 44 | if (context.GameState.ActiveChannel != null) context.GameState.ActiveChannel.SquelchUpdate(context.GameState); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/UnsquelchCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 7 | { 8 | class UnsquelchCommand : ChatCommand 9 | { 10 | public UnsquelchCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 11 | 12 | public override bool CanInvoke(ChatCommandContext context) 13 | { 14 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 15 | } 16 | 17 | public override void Invoke(ChatCommandContext context) 18 | { 19 | string r; // reply 20 | string t; // target 21 | 22 | t = Arguments.Count == 0 ? "" : Arguments[0]; 23 | 24 | if (!Battlenet.Common.GetClientByOnlineName(t, out var target) || target == null) 25 | { 26 | r = Resources.UserNotLoggedOn; 27 | foreach (var line in r.Split(Battlenet.Common.NewLine)) 28 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 29 | return; 30 | } 31 | 32 | var ipAddress = IPAddress.Parse(target.Client.RemoteEndPoint.ToString().Split(":")[0]); 33 | 34 | lock (context.GameState.SquelchedIPs) 35 | { 36 | if (context.GameState.SquelchedIPs.Contains(ipAddress)) 37 | { 38 | context.GameState.SquelchedIPs.Remove(ipAddress); 39 | } 40 | } 41 | 42 | lock (context.GameState.ActiveChannel) 43 | { 44 | if (context.GameState.ActiveChannel != null) context.GameState.ActiveChannel.SquelchUpdate(context.GameState); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_STOPADV.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.Messages 8 | { 9 | class SID_STOPADV : Message 10 | { 11 | public SID_STOPADV() 12 | { 13 | Id = (byte)MessageIds.SID_STOPADV; 14 | Buffer = new byte[0]; 15 | } 16 | 17 | public SID_STOPADV(byte[] buffer) 18 | { 19 | Id = (byte)MessageIds.SID_STOPADV; 20 | Buffer = buffer; 21 | } 22 | 23 | public override bool Invoke(MessageContext context) 24 | { 25 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 26 | 27 | if (context.Direction != MessageDirection.ClientToServer) 28 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from client to server"); 29 | 30 | if (Buffer.Length != 0) 31 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be 0 bytes"); 32 | 33 | GameState gs = context.Client.GameState; 34 | 35 | if (gs == null) 36 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} was received without an active GameState"); 37 | 38 | if (gs.GameAd == null) 39 | return true; // No game advertisement to stop. No action to do. 40 | 41 | bool gameAdOwner = gs.GameAd != null && gs.GameAd.Owner == gs; 42 | if (!gameAdOwner) 43 | Logging.WriteLine(Logging.LogLevel.Info, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"{MessageName(Id)} was received but they are not the owner of the game advertisement"); 44 | else 45 | Battlenet.Common.ActiveGameAds.Remove(gs.GameAd); 46 | 47 | return true; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_SYSTEMINFO.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.Messages 7 | { 8 | class SID_SYSTEMINFO : Message 9 | { 10 | public SID_SYSTEMINFO() 11 | { 12 | Id = (byte)MessageIds.SID_SYSTEMINFO; 13 | Buffer = new byte[16]; 14 | } 15 | 16 | public SID_SYSTEMINFO(byte[] buffer) 17 | { 18 | Id = (byte)MessageIds.SID_SYSTEMINFO; 19 | Buffer = buffer; 20 | } 21 | 22 | public override bool Invoke(MessageContext context) 23 | { 24 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 25 | 26 | if (context.Direction != MessageDirection.ClientToServer) 27 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from client to server"); 28 | 29 | if (Buffer.Length != 28) 30 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be 28 bytes"); 31 | 32 | /** 33 | * (UINT32) Number of processors 34 | * (UINT32) Processor architecture 35 | * (UINT32) Processor level 36 | * (UINT32) Processor timing 37 | * (UINT32) Total physical memory 38 | * (UINT32) Total page file 39 | * (UINT32) Free disk space 40 | */ 41 | 42 | using var m = new MemoryStream(Buffer); 43 | using var r = new BinaryReader(m); 44 | 45 | var cpuCount = r.ReadUInt32(); 46 | var cpuArch = r.ReadUInt32(); 47 | var cpuLevel = r.ReadUInt32(); 48 | var cpuTiming = r.ReadUInt32(); 49 | var totalRAM = r.ReadUInt32(); 50 | var totalSwap = r.ReadUInt32(); 51 | var freeDiskSpace = r.ReadUInt32(); 52 | 53 | return true; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/KickCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System.Collections.Generic; 3 | 4 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 5 | { 6 | class KickCommand : ChatCommand 7 | { 8 | public KickCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 9 | 10 | public override bool CanInvoke(ChatCommandContext context) 11 | { 12 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 13 | } 14 | 15 | public override void Invoke(ChatCommandContext context) 16 | { 17 | if (context.GameState.ActiveChannel == null) 18 | { 19 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, Resources.InvalidChatCommand).WriteTo(context.GameState.Client); 20 | return; 21 | } 22 | 23 | if (!(context.GameState.ChannelFlags.HasFlag(Account.Flags.Admin) 24 | || context.GameState.ChannelFlags.HasFlag(Account.Flags.ChannelOp) 25 | || context.GameState.ChannelFlags.HasFlag(Account.Flags.Employee))) 26 | { 27 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, context.GameState.ChannelFlags, context.GameState.Ping, context.GameState.OnlineName, Resources.YouAreNotAChannelOperator).WriteTo(context.GameState.Client); 28 | return; 29 | } 30 | 31 | if (Arguments.Count < 1) 32 | { 33 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, Resources.UserNotLoggedOn).WriteTo(context.GameState.Client); 34 | return; 35 | } 36 | 37 | var target = Arguments[0]; 38 | Arguments.RemoveAt(0); 39 | 40 | var reason = string.Join(" ", Arguments); 41 | 42 | context.GameState.ActiveChannel.KickUser(context.GameState, target, reason); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_CLANINFO.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.Messages 8 | { 9 | class SID_CLANINFO : Message 10 | { 11 | public SID_CLANINFO() 12 | { 13 | Id = (byte)MessageIds.SID_CLANINFO; 14 | Buffer = new byte[6]; 15 | } 16 | 17 | public SID_CLANINFO(byte[] buffer) 18 | { 19 | Id = (byte)MessageIds.SID_CLANINFO; 20 | Buffer = buffer; 21 | } 22 | 23 | public override bool Invoke(MessageContext context) 24 | { 25 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 26 | 27 | if (context.Direction != MessageDirection.ServerToClient) 28 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from server to client"); 29 | 30 | /** 31 | * (UINT8) Unknown (0) 32 | * (UINT32) Clan tag 33 | * (UINT8) Rank 34 | */ 35 | 36 | byte unknown = 0; 37 | byte[] tag = (byte[])context.Arguments["tag"]; 38 | byte rank = (byte)context.Arguments["rank"]; 39 | 40 | if (tag.Length != 4) 41 | throw new GameProtocolViolationException(context.Client, $"Clan tag must be exactly 4 bytes"); 42 | 43 | Buffer = new byte[2 + tag.Length]; 44 | 45 | using var m = new MemoryStream(Buffer); 46 | using var w = new BinaryWriter(m); 47 | 48 | w.Write(unknown); 49 | w.Write(tag); 50 | w.Write(rank); 51 | 52 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 53 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 54 | return true; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /var/bnftp/www/index.shtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Atlas 7 | 8 | 9 |
10 |
11 |

Danger: This endpoint is fragile, do be gentle.

12 |
13 | 14 |

Atlas

15 |

This server is an emulation for Classic Battle.net™ service.

16 | 17 |

Status

18 |

19 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | 29 | 30 | 31 | 33 | 34 | 35 | 36 |

Process Uptime:

24 |

The duration that the service has been running.

Node Uptime:

28 |

The duration that the host machine node has been running.

Users Online:

32 |

The number of users that are currently logged on to the service.

37 |
38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_FRIENDSADD.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.Messages 7 | { 8 | class SID_FRIENDSADD : Message 9 | { 10 | 11 | public SID_FRIENDSADD() 12 | { 13 | Id = (byte)MessageIds.SID_FRIENDSADD; 14 | Buffer = new byte[0]; 15 | } 16 | 17 | public SID_FRIENDSADD(byte[] buffer) 18 | { 19 | Id = (byte)MessageIds.SID_FRIENDSADD; 20 | Buffer = buffer; 21 | } 22 | 23 | public override bool Invoke(MessageContext context) 24 | { 25 | if (context.Direction != MessageDirection.ServerToClient) 26 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from server to client"); 27 | 28 | /** 29 | * (STRING) Account 30 | * (UINT8) Status 31 | * (UINT8) Location id 32 | * (UINT32) Product id 33 | * (STRING) Location name 34 | */ 35 | 36 | var friend = (Friend)context.Arguments["friend"]; 37 | var account = (byte[])friend.Username; 38 | var status = (byte)friend.StatusId; 39 | var location = (byte)friend.LocationId; 40 | var product = (UInt32)friend.ProductCode; 41 | var locationStr = (byte[])friend.LocationString; 42 | 43 | var bufferSize = (uint)(8 + account.Length + locationStr.Length); 44 | 45 | Buffer = new byte[bufferSize]; 46 | 47 | using var m = new MemoryStream(Buffer); 48 | using var w = new BinaryWriter(m); 49 | 50 | w.WriteByteString(account); 51 | w.Write(status); 52 | w.Write(location); 53 | w.Write(product); 54 | w.WriteByteString(locationStr); 55 | 56 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 57 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 58 | return true; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_DISPLAYAD.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System.IO; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Game.Messages 6 | { 7 | class SID_DISPLAYAD : Message 8 | { 9 | public SID_DISPLAYAD() 10 | { 11 | Id = (byte)MessageIds.SID_DISPLAYAD; 12 | Buffer = new byte[0]; 13 | } 14 | 15 | public SID_DISPLAYAD(byte[] buffer) 16 | { 17 | Id = (byte)MessageIds.SID_DISPLAYAD; 18 | Buffer = buffer; 19 | } 20 | 21 | public override bool Invoke(MessageContext context) 22 | { 23 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 24 | 25 | if (context.Direction != MessageDirection.ClientToServer) 26 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from client to server"); 27 | 28 | if (Buffer.Length < 14) 29 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be at least 14 bytes"); 30 | 31 | using var m = new MemoryStream(Buffer); 32 | using var r = new BinaryReader(m); 33 | 34 | var platformId = r.ReadUInt32(); 35 | var productId = r.ReadUInt32(); 36 | var adId = r.ReadUInt32(); 37 | var adFilename = r.ReadByteString(); 38 | var adUrl = r.ReadByteString(); 39 | 40 | if (Battlenet.Common.ActiveAds.TryGetValue(adId, out var ad) && ad != null) 41 | { 42 | ad.IncrementDisplayCount(); 43 | 44 | Logging.WriteLine(Logging.LogLevel.Info, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"Client displayed advertisement (id: {adId}) (filename: {ad.Filename})"); 45 | } 46 | else 47 | { 48 | Logging.WriteLine(Logging.LogLevel.Warning, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"Client displayed advertisement (id: 0x{adId:X8}) but id cannot be found"); 49 | } 50 | 51 | return true; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/AdminClanListCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Daemon; 2 | using Atlasd.Localization; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 8 | { 9 | class AdminClanListCommand : ChatCommand 10 | { 11 | public AdminClanListCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 12 | 13 | public override bool CanInvoke(ChatCommandContext context) 14 | { 15 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 16 | } 17 | 18 | public override void Invoke(ChatCommandContext context) 19 | { 20 | var clanList = Battlenet.Common.ActiveClans.ToArray(); 21 | Array.Sort(clanList, (x, y) => string.Compare(Encoding.UTF8.GetString(x.Key), Encoding.UTF8.GetString(y.Key), StringComparison.OrdinalIgnoreCase)); 22 | 23 | ChatEvent.EventIds replyEventId; 24 | string reply; 25 | 26 | if (clanList.Length == 0) 27 | { 28 | replyEventId = ChatEvent.EventIds.EID_ERROR; 29 | reply = Resources.AdminClanListCommandEmpty; 30 | } 31 | else 32 | { 33 | replyEventId = ChatEvent.EventIds.EID_INFO; 34 | reply = Resources.AdminClanListCommand; 35 | foreach (var pair in clanList) 36 | { 37 | var clan = pair.Value; 38 | var clanName = Encoding.UTF8.GetString(clan.Name).Replace((char)0x00, (char)0x20).Trim(); 39 | var clanTag = Encoding.UTF8.GetString(clan.Tag).Replace((char)0x00, (char)0x20).Trim(); 40 | 41 | reply += $"{Battlenet.Common.NewLine}[{clanTag}] {clanName}, {clan.Count} members"; 42 | } 43 | } 44 | 45 | var gameState = context.GameState; 46 | foreach (var kv in context.Environment) reply = reply.Replace("{" + kv.Key + "}", kv.Value); 47 | foreach (var line in reply.Split(Battlenet.Common.NewLine)) 48 | new ChatEvent(replyEventId, gameState.ChannelFlags, gameState.Client.RemoteIPAddress, gameState.Ping, gameState.OnlineName, line).WriteTo(gameState.Client); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_WARCRAFTGENERAL.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | using System.Collections.Generic; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.Messages 8 | { 9 | class SID_WARCRAFTGENERAL : Message 10 | { 11 | public enum SubCommands : byte 12 | { 13 | WID_GAMESEARCH = 0x00, 14 | WID_MAPLIST = 0x02, 15 | WID_CANCELSEARCH = 0x03, 16 | WID_USERRECORD = 0x04, 17 | WID_TOURNAMENT = 0x07, 18 | WID_CLANRECORD = 0x08, 19 | WID_ICONLIST = 0x09, 20 | WID_SETICON = 0x0A, 21 | }; 22 | 23 | public SID_WARCRAFTGENERAL() 24 | { 25 | Id = (byte)MessageIds.SID_WARCRAFTGENERAL; 26 | Buffer = new byte[0]; 27 | } 28 | 29 | public SID_WARCRAFTGENERAL(byte[] buffer) 30 | { 31 | Id = (byte)MessageIds.SID_WARCRAFTGENERAL; 32 | Buffer = buffer; 33 | } 34 | 35 | public override bool Invoke(MessageContext context) 36 | { 37 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 38 | 39 | if (context.Client.GameState == null || !Product.IsWarcraftIII(context.Client.GameState.Product)) 40 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} is Warcraft III game client exclusive"); 41 | 42 | if (Buffer.Length < 1) 43 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be at least 1 byte"); 44 | 45 | byte subcommand; 46 | using (var m = new MemoryStream(Buffer)) 47 | using (var r = new BinaryReader(m)) 48 | subcommand = r.ReadByte(); 49 | 50 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} received subcommand {subcommand:X2}"); 51 | 52 | // TODO: Compare subcommand variable with SubCommands enum and do procedures, for now just ignore 53 | 54 | return true; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_CLANMEMBERSTATUSCHANGE.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.Messages 8 | { 9 | class SID_CLANMEMBERSTATUSCHANGE : Message 10 | { 11 | public SID_CLANMEMBERSTATUSCHANGE() 12 | { 13 | Id = (byte)MessageIds.SID_CLANMEMBERSTATUSCHANGE; 14 | Buffer = new byte[6]; 15 | } 16 | 17 | public SID_CLANMEMBERSTATUSCHANGE(byte[] buffer) 18 | { 19 | Id = (byte)MessageIds.SID_CLANMEMBERSTATUSCHANGE; 20 | Buffer = buffer; 21 | } 22 | 23 | public override bool Invoke(MessageContext context) 24 | { 25 | if (context.Direction != MessageDirection.ServerToClient) 26 | { 27 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 28 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from server to client"); 29 | } 30 | 31 | /** 32 | * (STRING) Username 33 | * (UINT8) Rank 34 | * (UINT8) Online status 35 | * (STRING) Location 36 | */ 37 | 38 | byte[] username = (byte[])context.Arguments["username"]; 39 | byte rank = (byte)context.Arguments["rank"]; 40 | byte status = (byte)context.Arguments["status"]; 41 | byte[] location = (byte[])context.Arguments["location"]; 42 | 43 | Buffer = new byte[4 + username.Length + location.Length]; 44 | 45 | using var m = new MemoryStream(Buffer); 46 | using var w = new BinaryWriter(m); 47 | 48 | w.WriteByteString(username); 49 | w.Write(rank); 50 | w.Write(status); 51 | w.WriteByteString(location); 52 | 53 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 54 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 55 | return true; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/BanCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 6 | { 7 | class BanCommand : ChatCommand 8 | { 9 | public BanCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 10 | 11 | public override bool CanInvoke(ChatCommandContext context) 12 | { 13 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 14 | } 15 | 16 | public override void Invoke(ChatCommandContext context) 17 | { 18 | if (context.GameState.ActiveChannel == null) 19 | { 20 | new InvalidCommand(RawBuffer, Arguments).Invoke(context); 21 | return; 22 | } 23 | 24 | if (!(context.GameState.ChannelFlags.HasFlag(Account.Flags.Employee) 25 | || context.GameState.ChannelFlags.HasFlag(Account.Flags.ChannelOp) 26 | || context.GameState.ChannelFlags.HasFlag(Account.Flags.Admin))) 27 | { 28 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, Resources.YouAreNotAChannelOperator).WriteTo(context.GameState.Client); 29 | return; 30 | } 31 | 32 | var target = ""; 33 | if (Arguments.Count > 0) 34 | { 35 | target = Arguments[0]; 36 | Arguments.RemoveAt(0); 37 | RawBuffer = RawBuffer[(Encoding.UTF8.GetByteCount(target) + (Arguments.Count > 0 ? 1 : 0))..]; 38 | } 39 | 40 | if (string.IsNullOrEmpty(target) 41 | || !Battlenet.Common.ActiveGameStates.TryGetValue(target, out var targetState) 42 | || targetState == null) 43 | { 44 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, Resources.UserNotLoggedOn).WriteTo(context.GameState.Client); 45 | return; 46 | } 47 | 48 | context.GameState.ActiveChannel.BanUser(context.GameState, targetState, string.Join(" ", Arguments)); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_GAMERESULT.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.Messages 8 | { 9 | class SID_GAMERESULT : Message 10 | { 11 | public SID_GAMERESULT() 12 | { 13 | Id = (byte)MessageIds.SID_GAMERESULT; 14 | Buffer = new byte[0]; 15 | } 16 | 17 | public SID_GAMERESULT(byte[] buffer) 18 | { 19 | Id = (byte)MessageIds.SID_GAMERESULT; 20 | Buffer = buffer; 21 | } 22 | 23 | public override bool Invoke(MessageContext context) 24 | { 25 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 26 | 27 | if (context.Direction != MessageDirection.ClientToServer) 28 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from client to server"); 29 | 30 | /** 31 | * (UINT32) Game type 32 | * (UINT32) Number of results - always 8 33 | * (UINT32) [8] Results 34 | * (STRING) [8] Game players - always 8 35 | * (STRING) Map name 36 | * (STRING) Player score 37 | */ 38 | 39 | if (Buffer.Length < 10) 40 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be at least 10 bytes"); 41 | 42 | using var m = new MemoryStream(Buffer); 43 | using var r = new BinaryReader(m); 44 | 45 | var gameType = r.ReadUInt32(); 46 | var resultCount = r.ReadUInt32(); 47 | var results = new List(); 48 | var players = new List(); 49 | 50 | for (var i = 0; i < resultCount; i++) 51 | { 52 | results.Add(r.ReadUInt32()); 53 | } 54 | 55 | for (var i = 0; i < resultCount; i++) 56 | { 57 | players.Add(r.ReadByteString()); 58 | } 59 | 60 | var mapName = r.ReadByteString(); 61 | var playerScore = r.ReadByteString(); 62 | 63 | return true; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/WhoCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 6 | { 7 | class WhoCommand : ChatCommand 8 | { 9 | public WhoCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 10 | 11 | public override bool CanInvoke(ChatCommandContext context) 12 | { 13 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 14 | } 15 | 16 | public override void Invoke(ChatCommandContext context) 17 | { 18 | var channelName = string.Join(" ", Arguments); 19 | var ch = channelName.Length > 0 ? Channel.GetChannelByName(channelName, false) : context.GameState.ActiveChannel; 20 | string r; // reply 21 | 22 | if (ch == null) 23 | { 24 | r = Resources.ChannelNotFound; 25 | foreach (var line in r.Split(Battlenet.Common.NewLine)) 26 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 27 | return; 28 | } 29 | 30 | if (ch.ActiveFlags.HasFlag(Channel.Flags.Restricted)) 31 | { 32 | r = Resources.ChannelIsRestricted; 33 | foreach (var line in r.Split(Battlenet.Common.NewLine)) 34 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, ch.ActiveFlags, 0, ch.Name, line).WriteTo(context.GameState.Client); 35 | return; 36 | } 37 | 38 | r = Resources.WhoCommand; 39 | 40 | r = r.Replace("{channel}", ch == null ? "(null)" : ch.Name); 41 | r = r.Replace("{users}", ch.GetUsersAsString(context.GameState)); 42 | 43 | foreach (var kv in context.Environment) 44 | { 45 | r = r.Replace("{" + kv.Key + "}", kv.Value); 46 | } 47 | 48 | foreach (var line in r.Split(Battlenet.Common.NewLine)) 49 | new ChatEvent(ChatEvent.EventIds.EID_INFO, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/VersionCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System.Collections.Generic; 3 | using System; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 6 | { 7 | class VersionCommand : ChatCommand 8 | { 9 | public VersionCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 10 | 11 | public override bool CanInvoke(ChatCommandContext context) 12 | { 13 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 14 | } 15 | 16 | public override void Invoke(ChatCommandContext context) 17 | { 18 | var assembly = typeof(Program).Assembly; 19 | var server = $"{assembly.GetName().Name}/{assembly.GetName().Version} ({Program.DistributionMode})"; 20 | 21 | var systemUptime = TimeSpan.FromMilliseconds(Environment.TickCount64); 22 | var systemUptimeStr = $"{Math.Floor(systemUptime.TotalDays)} day{(Math.Floor(systemUptime.TotalDays) == 1 ? "" : "s")} {(systemUptime.Hours < 10 ? "0" : "")}{systemUptime.Hours}:{(systemUptime.Minutes < 10 ? "0" : "")}{systemUptime.Minutes}:{(systemUptime.Seconds < 10 ? "0" : "")}{systemUptime.Seconds}"; 23 | 24 | var processUptime = TimeSpan.FromMilliseconds(Environment.TickCount64 - Program.TickCountAtInit); 25 | var processUptimeStr = $"{Math.Floor(processUptime.TotalDays)} day{(Math.Floor(processUptime.TotalDays) == 1 ? "" : "s")} {(processUptime.Hours < 10 ? "0" : "")}{processUptime.Hours}:{(processUptime.Minutes < 10 ? "0" : "")}{processUptime.Minutes}:{(processUptime.Seconds < 10 ? "0" : "")}{processUptime.Seconds}"; 26 | 27 | var hasAdmin = context.GameState.HasAdmin(); 28 | string r = hasAdmin ? Resources.VersionCommandWithAdmin : Resources.VersionCommand; 29 | 30 | context.Environment["version"] = server; 31 | context.Environment["systemUptime"] = systemUptimeStr; 32 | context.Environment["processUptime"] = processUptimeStr; 33 | 34 | foreach (var kv in context.Environment) 35 | { 36 | r = r.Replace("{" + kv.Key + "}", kv.Value); 37 | } 38 | 39 | foreach (var line in r.Split(Battlenet.Common.NewLine)) 40 | new ChatEvent(ChatEvent.EventIds.EID_INFO, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Atlasd/Atlasd.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | Atlas is cross-platform software that emulates Classic Battle.net in a compatible model for Diablo, StarCraft, and WarCraft. 7 | © 2020-2021 Carl Bennett <carl@carlbennett.me> 8 | BNETDocs 9 | Atlas 10 | Caaaaarrrrlll <carl@bnetdocs.org> 11 | LICENSE.txt 12 | https://github.com/BNETDocs/Atlas 13 | https://github.com/BNETDocs/Atlas 14 | GitHub 15 | Atlas, Battle.net, Blizzard, Classic, Diablo, Emulation, Linux, StarCraft, WarCraft, Windows 16 | atlasd 17 | app.manifest 18 | 0.1.0.0 19 | 0.1.0.0 20 | 0.1.0.0 21 | true 22 | 23 | 24 | 25 | false 26 | 4 27 | 28 | 29 | 30 | 31 | True 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | PublicResXFileCodeGenerator 42 | Resources.Designer.cs 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | True 61 | True 62 | Resources.resx 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/HelpCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Daemon; 2 | using Atlasd.Localization; 3 | using System.Collections.Generic; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 6 | { 7 | class HelpCommand : ChatCommand 8 | { 9 | public HelpCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 10 | 11 | public override bool CanInvoke(ChatCommandContext context) 12 | { 13 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 14 | } 15 | 16 | public override void Invoke(ChatCommandContext context) 17 | { 18 | var hasAdmin = context.GameState.HasAdmin(); 19 | var topic = Arguments.Count > 0 ? Arguments[0] : string.Empty; 20 | if (!string.IsNullOrEmpty(topic)) Arguments.RemoveAt(0); 21 | var remarks = hasAdmin ? Resources.HelpCommandRemarksWithAdmin : Resources.HelpCommandRemarks; 22 | 23 | switch (topic.ToLower()) 24 | { 25 | case "admin": 26 | { 27 | if (hasAdmin) 28 | { 29 | new AdminHelpCommand(RawBuffer, Arguments).Invoke(context); return; 30 | } 31 | break; 32 | } 33 | case "advanced": 34 | remarks = Resources.HelpCommandAdvancedRemarks; break; 35 | case "aliases": 36 | remarks = Resources.HelpCommandAliasesRemarks; break; 37 | case "ban": 38 | remarks = Resources.HelpCommandBanRemarks; break; 39 | case "channel": 40 | case "join": 41 | case "j": 42 | remarks = Resources.HelpCommandJoinRemarks; break; 43 | case "commands": 44 | remarks = Resources.HelpCommandCommandsRemarks; break; 45 | case "time": 46 | remarks = Resources.HelpCommandTimeRemarks; break; 47 | } 48 | 49 | foreach (var kv in context.Environment) 50 | { 51 | remarks = remarks.Replace("{" + kv.Key + "}", kv.Value); 52 | } 53 | 54 | foreach (var line in remarks.Split(Battlenet.Common.NewLine)) 55 | new ChatEvent(ChatEvent.EventIds.EID_INFO, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/FriendMessageCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Protocols.Game.Messages; 2 | using Atlasd.Localization; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 8 | { 9 | class FriendMessageCommand : ChatCommand 10 | { 11 | public FriendMessageCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 12 | 13 | public override bool CanInvoke(ChatCommandContext context) 14 | { 15 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 16 | } 17 | 18 | public override void Invoke(ChatCommandContext context) 19 | { 20 | var replyEventId = ChatEvent.EventIds.EID_ERROR; 21 | var reply = string.Empty; 22 | var friends = (List)context.GameState.ActiveAccount.Get(Account.FriendsKey, new List()); 23 | 24 | var messageString = string.Join(" ", Arguments); 25 | if (string.IsNullOrEmpty(messageString)) 26 | { 27 | reply = Resources.WhisperCommandEmptyMessage; 28 | } 29 | else 30 | { 31 | new ChatEvent(ChatEvent.EventIds.EID_WHISPERTO, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, Resources.WhisperFromYourFriends, messageString).WriteTo(context.GameState.Client); 32 | 33 | foreach (var friend in friends) 34 | { 35 | var friendString = Encoding.UTF8.GetString(friend); 36 | if (!Battlenet.Common.GetClientByOnlineName(friendString, out var friendGameState) || friendGameState == null) continue; 37 | if (!string.IsNullOrEmpty(friendGameState.DoNotDisturb)) continue; 38 | new ChatEvent(ChatEvent.EventIds.EID_WHISPERFROM, context.GameState.ChannelFlags, context.GameState.Ping, Channel.RenderOnlineName(friendGameState, context.GameState), messageString).WriteTo(friendGameState.Client); 39 | } 40 | } 41 | 42 | if (string.IsNullOrEmpty(reply)) return; 43 | foreach (var kv in context.Environment) reply = reply.Replace("{" + kv.Key + "}", kv.Value); 44 | foreach (var line in reply.Split(Battlenet.Common.NewLine)) 45 | new ChatEvent(replyEventId, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/D2GS/Messages.cs: -------------------------------------------------------------------------------- 1 | namespace Atlasd.Battlenet.Protocols.D2GS 2 | { 3 | enum Messages : byte 4 | { 5 | D2GS_WALKTOLOCATION = 0x01, 6 | D2GS_WALKTOENTITY = 0x02, 7 | D2GS_RUNTOLOCATION = 0x03, 8 | D2GS_RUNTOENTITY = 0x04, 9 | D2GS_LEFTSKILLONLOCATION = 0x05, 10 | D2GS_LEFTSKILLONENTITY = 0x06, 11 | D2GS_LEFTSKILLONENTITYEX = 0x07, 12 | D2GS_LEFTSKILLONLOCATIONEX = 0x08, 13 | D2GS_LEFTSKILLONENTITYEX2 = 0x09, 14 | D2GS_LEFTSKILLONENTITYEX3 = 0x0A, 15 | D2GS_RIGHTSKILLONLOCATION = 0x0C, 16 | D2GS_RIGHTSKILLONENTITY = 0x0D, 17 | D2GS_RIGHTSKILLONENTITYEX = 0x0E, 18 | D2GS_RIGHTSKILLONLOCATIONEX = 0x0F, 19 | D2GS_RIGHTSKILLONENTITYEX2 = 0x10, 20 | D2GS_CHARTOOBJ = 0x10, 21 | D2GS_RIGHTSKILLONENTITYEX3 = 0x11, 22 | D2GS_INTERACTWITHENTITY = 0x13, 23 | D2GS_OVERHEADMESSAGE = 0x14, 24 | D2GS_PICKUPITEM = 0x16, 25 | D2GS_DROPITEM = 0x17, 26 | D2GS_ITEMTOBUFFER = 0x18, 27 | D2GS_PICKUPBUFFERITEM = 0x19, 28 | D2GS_SMALLGOLDPICKUP = 0x19, 29 | D2GS_ITEMTOBODY = 0x1A, 30 | D2GS_SWAP2HANDEDITEM = 0x1B, 31 | D2GS_PICKUPBODYITEM = 0x1C, 32 | D2GS_SWITCHBODYITEM = 0x1D, 33 | D2GS_SETBYTEATTR = 0x1D, 34 | D2GS_SETWORDATTR = 0x1E, 35 | D2GS_SWITCHINVENTORYITEM = 0x1F, 36 | D2GS_SETDWORDATTR = 0x1F, 37 | D2GS_USEITEM = 0x20, 38 | D2GS_STACKITEM = 0x21, 39 | D2GS_REMOVESTACKITEM = 0x22, 40 | D2GS_ITEMTOBELT = 0x23, 41 | D2GS_REMOVEBELTITEM = 0x24, 42 | D2GS_SWITCHBELTITEM = 0x25, 43 | D2GS_USEBELTITEM = 0x26, 44 | D2GS_INSERTSOCKETITEM = 0x28, 45 | D2GS_SCROLLTOTOME = 0x29, 46 | D2GS_ITEMTOCUBE = 0x2A, 47 | D2GS_TRADERESULT = 0x2A, 48 | D2GS_UNSELECTOBJ = 0x2D, 49 | D2GS_NPCINIT = 0x2F, 50 | D2GS_NPCCANCEL = 0x30, 51 | D2GS_NPCBUY = 0x32, 52 | D2GS_NPCSELL = 0x33, 53 | D2GS_NPCTRADE = 0x38, 54 | D2GS_CHARACTERPHRASE = 0x3F, 55 | D2GS_WAYPOINT = 0x49, 56 | D2GS_TRADE = 0x4F, 57 | D2GS_DROPGOLD = 0x50, 58 | D2GS_WORLDOBJECT = 0x51, 59 | D2GS_COMPSTARTGAME = 0x5C, 60 | D2GS_PARTY = 0x5E, 61 | D2GS_POTIONTOMERCENARY = 0x61, 62 | D2GS_GAMELOGON = 0x68, 63 | D2GS_ENTERGAMEENVIRONMENT = 0x6A, 64 | D2GS_PING = 0x6D, 65 | D2GS_TRADEACTION = 0x77, 66 | D2GS_LOGONRESPONSE = 0x7A, 67 | D2GS_UNIQUEEVENTS = 0x89, 68 | D2GS_NEGOTIATECOMPRESSION = 0xAF, 69 | } 70 | } -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/AdminHelpCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 6 | { 7 | class AdminHelpCommand : ChatCommand 8 | { 9 | public AdminHelpCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 10 | 11 | public override bool CanInvoke(ChatCommandContext context) 12 | { 13 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 14 | } 15 | 16 | public override void Invoke(ChatCommandContext context) 17 | { 18 | var r = string.Join(Battlenet.Common.NewLine, new List() { 19 | { Resources.AdminHelpCommand }, 20 | { "/admin ? (alias: /admin help)" }, 21 | { "/admin announce (alias: /admin broadcast)" }, 22 | { "/admin broadcast " }, 23 | { "/admin channel disband [destination]" }, 24 | { "/admin channel flags " }, 25 | { "/admin channel maxusers " }, 26 | { "/admin channel rename " }, 27 | { "/admin channel resync" }, 28 | { "/admin channel topic " }, 29 | { "/admin clan list" }, 30 | { "/admin dc (alias: /admin disconnect)" }, 31 | { "/admin disconnect [reason]" }, 32 | { "/admin disconnectforflooding " }, 33 | { "/admin help (this text)" }, 34 | { "/admin msgbox " }, 35 | { "/admin move (alias: /admin moveuser)" }, 36 | { "/admin moveuser " }, 37 | { "/admin shutdown [(cancel [message])|(delay-seconds|30 [message])]" }, 38 | { "/admin spoofuserflag (alias: /admin spoofuserflags)" }, 39 | { "/admin spoofuserflags " }, 40 | { "/admin spoofusergame " }, 41 | { "/admin spoofusername " }, 42 | { "/admin spoofuserping " }, 43 | { "" }, 44 | }); 45 | 46 | foreach (var kv in context.Environment) r = r.Replace("{" + kv.Key + "}", kv.Value); 47 | foreach (var line in r.Split(Battlenet.Common.NewLine)) 48 | new ChatEvent(ChatEvent.EventIds.EID_INFO, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_UDPPINGRESPONSE.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.Messages 7 | { 8 | class SID_UDPPINGRESPONSE : Message 9 | { 10 | public SID_UDPPINGRESPONSE() 11 | { 12 | Id = (byte)MessageIds.SID_UDPPINGRESPONSE; 13 | Buffer = new byte[0]; 14 | } 15 | 16 | public SID_UDPPINGRESPONSE(byte[] buffer) 17 | { 18 | Id = (byte)MessageIds.SID_UDPPINGRESPONSE; 19 | Buffer = buffer; 20 | } 21 | 22 | public override bool Invoke(MessageContext context) 23 | { 24 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 25 | 26 | if (context.Direction != MessageDirection.ClientToServer) 27 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from client to server"); 28 | 29 | if (Buffer.Length != 4) 30 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be 4 bytes"); 31 | 32 | var gameState = context.Client.GameState; 33 | 34 | // UDP reply must be given between S>C SID_AUTH_INFO and C>S SID_ENTERCHAT, not 35 | // before or after. Technically, we could allow this while in chat and proceed then 36 | // by sending a chat event for the update, but the server by convention is not 37 | // supposed to allow it and should instead disconnect the client. 38 | if (!(gameState.Statstring == null || gameState.Statstring.Length == 0)) 39 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} cannot be sent while in chat"); 40 | 41 | UInt32 udpToken; 42 | using (var m = new MemoryStream(Buffer)) 43 | using (var r = new BinaryReader(m)) 44 | udpToken = r.ReadUInt32(); 45 | 46 | gameState.UDPSupported = udpToken == 0x626E6574; // "bnet" 47 | 48 | if (gameState.UDPSupported && gameState.ChannelFlags.HasFlag(Account.Flags.NoUDP)) 49 | { 50 | if (gameState.ActiveChannel == null) 51 | gameState.ChannelFlags = gameState.ChannelFlags & ~Account.Flags.NoUDP; 52 | else 53 | gameState.ActiveChannel.UpdateUser(gameState, gameState.ChannelFlags & ~Account.Flags.NoUDP); 54 | } 55 | 56 | return true; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ develop ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ develop ] 20 | #schedule: 21 | # - cron: '0 0 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'csharp' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | - name: Checkout submodules 41 | run: git submodule update --init --recursive 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto eol=crlf 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_GETICONDATA.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.Messages 8 | { 9 | class SID_GETICONDATA : Message 10 | { 11 | public SID_GETICONDATA() 12 | { 13 | Id = (byte)MessageIds.SID_GETICONDATA; 14 | Buffer = new byte[16]; 15 | } 16 | 17 | public SID_GETICONDATA(byte[] buffer) 18 | { 19 | Id = (byte)MessageIds.SID_GETICONDATA; 20 | Buffer = buffer; 21 | } 22 | 23 | public override bool Invoke(MessageContext context) 24 | { 25 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 26 | 27 | switch (context.Direction) 28 | { 29 | case MessageDirection.ClientToServer: 30 | { 31 | if (Buffer.Length != 0) 32 | { 33 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be 0 bytes"); 34 | } 35 | 36 | return new SID_GETICONDATA().Invoke(new MessageContext(context.Client, MessageDirection.ServerToClient)); 37 | } 38 | case MessageDirection.ServerToClient: 39 | { 40 | /** 41 | * (FILETIME) Filetime 42 | * (STRING) Filename 43 | */ 44 | 45 | var fileInfo = new FileInfo("icons.bni"); 46 | 47 | var fileTime = (UInt64)fileInfo.LastWriteTimeUtc.ToFileTimeUtc(); 48 | var fileName = fileInfo.Name; 49 | 50 | Buffer = new byte[9 + Encoding.UTF8.GetByteCount(fileName)]; 51 | 52 | using var m = new MemoryStream(Buffer); 53 | using var w = new BinaryWriter(m); 54 | 55 | w.Write((UInt64)fileTime); 56 | w.Write(fileName); 57 | 58 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 59 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 60 | return true; 61 | } 62 | } 63 | 64 | return false; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_SETEMAIL.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.Messages 7 | { 8 | class SID_SETEMAIL : Message 9 | { 10 | public SID_SETEMAIL() 11 | { 12 | Id = (byte)MessageIds.SID_SETEMAIL; 13 | Buffer = new byte[0]; 14 | } 15 | 16 | public SID_SETEMAIL(byte[] buffer) 17 | { 18 | Id = (byte)MessageIds.SID_SETEMAIL; 19 | Buffer = buffer; 20 | } 21 | 22 | public override bool Invoke(MessageContext context) 23 | { 24 | if (context == null || context.Client == null || !context.Client.Connected || context.Client.GameState == null) return false; 25 | 26 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 27 | 28 | switch (context.Direction) 29 | { 30 | case MessageDirection.ClientToServer: 31 | { 32 | if (Buffer.Length < 1) 33 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be at least 1 bytes"); 34 | 35 | using var m = new MemoryStream(Buffer); 36 | using var r = new BinaryReader(m); 37 | 38 | var emailAddress = r.ReadByteString(); 39 | if (emailAddress.Length < 0 || emailAddress.Length > 255) 40 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} email address must be within 0-255 bytes, got {emailAddress.Length}"); 41 | 42 | var gameState = context.Client.GameState; 43 | if (gameState.ActiveAccount == null) 44 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} cannot be sent before logging into an account"); 45 | 46 | gameState.ActiveAccount.Set(Account.EmailKey, emailAddress); 47 | Logging.WriteLine(Logging.LogLevel.Info, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"Client set email address for account [{gameState.ActiveAccount.Get(Account.UsernameKey, gameState.Username)}]"); 48 | return true; 49 | } 50 | case MessageDirection.ServerToClient: 51 | { 52 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 53 | return true; 54 | } 55 | default: return false; 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_LOCALEINFO.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | using System.Threading; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.Messages 8 | { 9 | class SID_LOCALEINFO : Message 10 | { 11 | public SID_LOCALEINFO() 12 | { 13 | Id = (byte)MessageIds.SID_LOCALEINFO; 14 | Buffer = new byte[16]; 15 | } 16 | 17 | public SID_LOCALEINFO(byte[] buffer) 18 | { 19 | Id = (byte)MessageIds.SID_LOCALEINFO; 20 | Buffer = buffer; 21 | } 22 | 23 | public override bool Invoke(MessageContext context) 24 | { 25 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 26 | 27 | if (context.Direction != MessageDirection.ClientToServer) 28 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent client to server"); 29 | 30 | /** 31 | * (FILETIME) System time 32 | * (FILETIME) Local time 33 | * (UINT32) Timezone bias 34 | * (UINT32) System LCID 35 | * (UINT32) User LCID 36 | * (UINT32) User language ID 37 | * (STRING) Abbreviated language name 38 | * (STRING) Country code 39 | * (STRING) Abbreviated country name 40 | * (STRING) Country name 41 | */ 42 | 43 | if (Buffer.Length < 36) 44 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be at least 36 bytes"); 45 | 46 | using var m = new MemoryStream(Buffer); 47 | using var r = new BinaryReader(m); 48 | 49 | var systemTime = r.ReadUInt64(); 50 | var localTime = r.ReadUInt64(); 51 | context.Client.GameState.TimezoneBias = r.ReadInt32(); 52 | context.Client.GameState.Locale.SystemLocaleId = r.ReadUInt32(); 53 | context.Client.GameState.Locale.UserLocaleId = r.ReadUInt32(); 54 | context.Client.GameState.Locale.UserLanguageId = r.ReadUInt32(); 55 | context.Client.GameState.Locale.LanguageNameAbbreviated = r.ReadString(); 56 | context.Client.GameState.Locale.CountryCode = r.ReadString(); 57 | context.Client.GameState.Locale.CountryNameAbbreviated = r.ReadString(); 58 | context.Client.GameState.Locale.CountryName = r.ReadString(); 59 | 60 | context.Client.GameState.SetLocale(); 61 | 62 | return true; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/ClanCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Daemon; 2 | using Atlasd.Localization; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 7 | { 8 | class ClanCommand : ChatCommand 9 | { 10 | public ClanCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 11 | 12 | public override bool CanInvoke(ChatCommandContext context) 13 | { 14 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 15 | } 16 | 17 | public override void Invoke(ChatCommandContext context) 18 | { 19 | var hasAdmin = context.GameState.HasAdmin(true); // includeChannelOp=true 20 | var replyEventId = ChatEvent.EventIds.EID_ERROR; 21 | var reply = string.Empty; 22 | 23 | if (!hasAdmin || context.GameState.ActiveChannel == null) 24 | { 25 | reply = Resources.YouAreNotAChannelOperator; 26 | } 27 | else 28 | { 29 | var subcommand = Arguments.Count > 0 ? Arguments[0] : string.Empty; 30 | if (!string.IsNullOrEmpty(subcommand)) Arguments.RemoveAt(0); 31 | 32 | switch (subcommand.ToLowerInvariant()) 33 | { 34 | case "motd": 35 | { 36 | context.GameState.ActiveChannel.SetTopic(string.Join(" ", Arguments)); 37 | break; 38 | } 39 | case "public": 40 | case "pub": 41 | { 42 | context.GameState.ActiveChannel.SetAllowNewUsers(true); 43 | break; 44 | } 45 | case "private": 46 | case "priv": 47 | { 48 | context.GameState.ActiveChannel.SetAllowNewUsers(false); 49 | break; 50 | } 51 | default: 52 | { 53 | reply = Resources.InvalidChatCommand; 54 | break; 55 | } 56 | } 57 | } 58 | 59 | if (string.IsNullOrEmpty(reply)) return; 60 | 61 | foreach (var kv in context.Environment) 62 | { 63 | reply = reply.Replace("{" + kv.Key + "}", kv.Value); 64 | } 65 | 66 | foreach (var line in reply.Split(Battlenet.Common.NewLine)) 67 | { 68 | new ChatEvent(replyEventId, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/ProtocolType.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Daemon; 2 | 3 | namespace Atlasd.Battlenet 4 | { 5 | public class ProtocolType 6 | { 7 | public enum Types : byte 8 | { 9 | // Descriptive Protocols: 10 | Game = 0x01, // Diablo, StarCraft, WarCraft 11 | BNFTP = 0x02, // FTP server for Game protocol 12 | Chat = 0x03, // Human text-based Chat gateway 13 | Chat_Alt1 = 0x43, 14 | Chat_Alt2 = 0x63, 15 | IPC = 0x80, // Inter-Process Communication 16 | 17 | // Letterized Protocols: 18 | Letter_C = 0x43, 19 | Letter_G = 0x47, 20 | Letter_H = 0x48, 21 | Letter_O = 0x4F, 22 | Letter_P = 0x50, 23 | }; 24 | 25 | public Types Type { get; private set; } 26 | 27 | public ProtocolType(Types type) 28 | { 29 | Type = type; 30 | } 31 | 32 | public bool IsBNFTP() 33 | { 34 | return Type == Types.BNFTP; 35 | } 36 | 37 | public bool IsChat() 38 | { 39 | return 40 | Type == Types.Chat || 41 | Type == Types.Chat_Alt1 || 42 | Type == Types.Chat_Alt2 43 | ; 44 | } 45 | 46 | public bool IsGame() 47 | { 48 | return Type == Types.Game; 49 | } 50 | 51 | public bool IsInterProcessCommunication() 52 | { 53 | return Type == Types.IPC; 54 | } 55 | 56 | public bool IsIPC() 57 | { 58 | return IsInterProcessCommunication(); 59 | } 60 | 61 | public static string ProtocolTypeName(Types type) 62 | { 63 | return type switch 64 | { 65 | Types.Game => "Game", 66 | Types.BNFTP => "BNFTP", 67 | Types.Chat => "Chat", 68 | Types.Chat_Alt1 => "Chat_Alt1", 69 | Types.Chat_Alt2 => "Chat_Alt2", 70 | Types.IPC => "IPC", 71 | _ => $"Unknown (0x{(byte)type:X2})", 72 | }; 73 | } 74 | 75 | public static uint ProtocolTypeToLogType(Types type) 76 | { 77 | return (uint)(type switch 78 | { 79 | Types.Game => Logging.LogType.Client_Game, 80 | Types.BNFTP => Logging.LogType.Client_BNFTP, 81 | Types.Chat => Logging.LogType.Client_Chat, 82 | Types.Chat_Alt1 => Logging.LogType.Client_Chat, 83 | Types.Chat_Alt2 => Logging.LogType.Client_Chat, 84 | Types.IPC => Logging.LogType.Client_IPC, 85 | _ => Logging.LogType.Client, 86 | }); 87 | } 88 | 89 | public override string ToString() 90 | { 91 | return ProtocolTypeName(Type); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/FriendRemoveCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Protocols.Game.Messages; 2 | using Atlasd.Localization; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 8 | { 9 | class FriendRemoveCommand : ChatCommand 10 | { 11 | public FriendRemoveCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 12 | 13 | public override bool CanInvoke(ChatCommandContext context) 14 | { 15 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 16 | } 17 | 18 | public override void Invoke(ChatCommandContext context) 19 | { 20 | var replyEventId = ChatEvent.EventIds.EID_ERROR; 21 | var reply = string.Empty; 22 | var friends = (List)context.GameState.ActiveAccount.Get(Account.FriendsKey, new List()); 23 | 24 | var targetString = Arguments.Count > 0 ? Arguments[0] : string.Empty; 25 | if (string.IsNullOrEmpty(targetString)) 26 | { 27 | reply = Resources.RemoveFriendEmptyTarget; 28 | } 29 | else 30 | { 31 | byte[] exists = null; 32 | byte counter = 0; 33 | foreach (var friendByteString in friends) 34 | { 35 | string friendString = Encoding.UTF8.GetString(friendByteString); 36 | if (string.Equals(targetString, friendString, StringComparison.CurrentCultureIgnoreCase)) 37 | { 38 | exists = friendByteString; 39 | break; 40 | } 41 | counter++; 42 | } 43 | if (exists == null || exists.Length == 0) 44 | { 45 | reply = Resources.AlreadyRemovedFriend.Replace("{friend}", targetString); 46 | } 47 | else 48 | { 49 | replyEventId = ChatEvent.EventIds.EID_INFO; 50 | reply = Resources.RemovedFriend.Replace("{friend}", targetString); 51 | 52 | friends.Remove(exists); 53 | 54 | new SID_FRIENDSREMOVE().Invoke(new MessageContext(context.GameState.Client, MessageDirection.ServerToClient, new Dictionary() {{ "friend", counter }})); 55 | } 56 | } 57 | 58 | if (string.IsNullOrEmpty(reply)) return; 59 | foreach (var kv in context.Environment) reply = reply.Replace("{" + kv.Key + "}", kv.Value); 60 | foreach (var line in reply.Split(Battlenet.Common.NewLine)) 61 | new ChatEvent(replyEventId, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/FriendListCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Protocols.Game.Messages; 2 | using Atlasd.Localization; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 8 | { 9 | class FriendListCommand : ChatCommand 10 | { 11 | public FriendListCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 12 | 13 | public override bool CanInvoke(ChatCommandContext context) 14 | { 15 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 16 | } 17 | 18 | public override void Invoke(ChatCommandContext context) 19 | { 20 | var replyEventId = ChatEvent.EventIds.EID_INFO; 21 | var reply = Resources.YourFriendsList; 22 | var friends = (List)context.GameState.ActiveAccount.Get(Account.FriendsKey, new List()); 23 | 24 | var friendCount = 0; 25 | foreach (var friend in friends) 26 | { 27 | if (friendCount++ == 0) reply += Battlenet.Common.NewLine; 28 | var friendString = Encoding.UTF8.GetString(friend); 29 | 30 | var detailString = "offline"; 31 | if (Battlenet.Common.GetClientByOnlineName(friendString, out var friendGameState) && friendGameState != null) 32 | { 33 | detailString = $"using {Battlenet.Product.ProductName(friendGameState.Product, true)}"; 34 | 35 | if (friendGameState.ActiveChannel == null) 36 | detailString += " in Battle.net"; // emulation note: Blizzard servers use a server-specific realm name here. 37 | 38 | if (friendGameState.ActiveChannel != null && friendGameState.ActiveChannel.IsPublic()) 39 | detailString += $" in the channel {friendGameState.ActiveChannel.Name}."; 40 | 41 | if (friendGameState.ActiveChannel != null && !friendGameState.ActiveChannel.IsPublic()) 42 | detailString += $" in a private channel."; 43 | } 44 | if (!string.IsNullOrEmpty(detailString)) detailString = $", {detailString}"; 45 | 46 | reply += $"{friendCount}: {friendString}{detailString}{Battlenet.Common.NewLine}"; 47 | } 48 | if (friendCount > 0) reply = reply[0..(reply.Length - Battlenet.Common.NewLine.Length)]; // strip last newline 49 | 50 | if (string.IsNullOrEmpty(reply)) return; 51 | foreach (var kv in context.Environment) reply = reply.Replace("{" + kv.Key + "}", kv.Value); 52 | foreach (var line in reply.Split(Battlenet.Common.NewLine)) 53 | new ChatEvent(replyEventId, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /docs/Starcraft Game Advertisement (Advex) Matchmaking Model.drawio: -------------------------------------------------------------------------------- 1 | 7V1bc6M2FP41nmkfdgeBufjRTpxsOtkks85sk750FFBsuhilIOfSX18JI4MRsRXH+GAneYl1ECD0nbtuHeto+nya4IfJdxqQqGMawXPHOu6YJnKRx/8JyktOMSRlnIRBTisIo/A/Iivm1FkYkHSpIqM0YuHDMtGncUx8tkTDSUKflqvd02j5rQ94TBTCyMeRSv0zDNhkTvVso6B/I+F4It+MjPzKFMvKOSGd4IA+lUjWsGMdJZSy+a/p8xGJRO/Jfpnfd/LK1UXDEhIznRvO+yf93uno4eqi+/OGTv66vP339kvPyhvHXuQXk4B3QF6kCZvQMY1xNCyog4TO4oCIxxq8VNQ5p/SBExEn/kMYe8nRxDNGOWnCplF+NcDpJLtfFO7DKDqiEU2y11v3nk98n9NTltBfpHTlzrO7trG4IuHgHTmYf4Ro+audk5NSOkt8sqJH7JzJcDImbEU911xgyLmf0ClhyQu/MSERZuHjckNwzoXjRb0CKP4jx+oNuOWtfMTRLH8TR4UGf+Lk776CKOe8B/FzNo36PhPdOXgkCQs5l5/jOxJd0TRkIY15lTvKGJ2WKvSjcCwuMAFtGUM6Y1EYc3yk3Alk7mnMJO+ISpF4/AD7v8YZz5Rhzv42gl+8pEQfSHreJbzh5Hk1G6io5TdYUnqlupLC+1TIfjcnTUpi7xkN4dxz2iafASbefS1Aju+Ru/um5dPVlE/ThpRP93X5HOy1fGrAX5FP58gbDk62I59u2+QTmQrQI86afoLvGSef4inh//pBBldKpuI7TeM3QXj+nf/6jpk/meJfYTwWpbn/VGEP3l1sGdvlvo9pTCpA5SScM4fPX0uSGq6ZhkGQqYynScjI6AFnovfEfTlFjZRZx5bluQIxu9tBt7sMblfF1nVqwO02Bq5Ed7falzyH7Kb0+1Y86qudl46f8ydnhRdZiPkH35QLpbtEsbgtK8n72qzpka4rhowepK5HqAvJJsYmbII+IpsgC5RNVJ/9DxrGnDIiETfRvH/nluK5YzqR0Ph3Cf81Zll3zSnpA46XGM35dybix0wbf3nKlSKPAIyYJlMcFRXkk367IiT5wugX8Z/XO4u5a4GFd/G7fAn/uvl75LsrvF1wLnrFcpS4tOpxvJ15tmBY7IplsWvcBrRb0+KAmpZCT9yWrqwzLYWauF3SEm/XGdBBvsR/vc6wQcN82c6Szjgn+JEsXMvmFEWuGAQuolo5vbB7NaHBL9v3PxfBBpyW8EDSc23REtCehWlpagkH1rHwYP3PTy7R5BLQjJRsZotMyeDjeJwtsCUwQz2fWqKcjtbyOEG1hBqkLjSDFM9TwopI1TgPU1aS47uq+igUyujs+O/T4XX/+Of52eh6ePNOMYcQa6eaogQPJHs2iFRvI92oylsFU4ICm7h1mPYc18LOdiXUMjUlFHZkCXk9UD1e0uL6Semv5sGkDrTZxDEg2UQ2s+zuZZp6BynGEUkes+TiiGFGQPKK2qrjfQmD1pkDhFxI7fDRh6xs7bwiciC1g63mFRU374Ky8F40SYxSqIMT6329i8vrs5PbPy7PLvbQ06uKtgMv2ibIXKC2DDNC231HV7SdVxhrR+6hCxMQLMJ8c4M4/yOyiQnqHiIX1E8wvrqbpIMOx1HQZxPQoQVH9RPqvYHGpyoccBRRHXeEdzUctN495A/GKY/vXkvor3EOj771r48uv3/vXxxv1TsEGSluAWImhDpf1rDFhFBUk8wD17jaw3SgkwkddZhOEb2yvBk/yBSHcaoneeVZHMY5EXOU9z4ycz1o4UOyBXs0tHZAE0Ad/aG1Lqhoa4ytLQnoZqI9aEK0d2NWq8NrLRBtBzacBk66gIt2T1u0QQflZDtLov2DpKSyBKjhaCkJHzET7+qYQgd8w+lVhF9Ikh5w7FR1BjwHXGMYSOnXD76k2jU1pbgHu47H+FwMvylyyDAhoZPtXOVaiXWZyvj3+kTF6Lr/Q8xZGt6o3PEmffnKWlu5enN19Lwb/6ua1zDB/S9XXUitACunsR5N8Btmop0P+z+HIgv14VC1wG2kp5FfHIkV7RtI6+UVF9aDx7R1kC72vfk0nsvejIbx9EBHjHtq7HKCw2iWEFWKJnR6N0tbmj3wKsarRia8GpFobm8ICyQS2CQt2GYxyl1LDTmyQH1QZAHPvPjYEy/02WTuU8JFmWqsMpr5PknTrenbwCZe0K3DwjPvLGdbuZfWKVzjUEyZay53rV2zfGy3XduDHeLqbJAHl5OMDmH5WL53SeuXHaDe3vg8jaxO2R8+sWH5BKnzHPbUClb3lgFX1chRuvaAcmYwCz3hZ3ihmjUb82F+mSN7DyStGMyrZrW4qwze694e2rPtBXUauDe8t5tuUAc8t2fR0ENMae9oKL91I/n2h94xA172u9qyD+vLyoaukv1htgPvWx2u4cX18AewwwVj+3s1u//u2vR/Tv0D3dZVf1ovbN7f1pjXu9eTT3YTcrXPATCNupBrTOJgBRjGxmAs7bpficfqt/tfHCmDGnHBUE0qY9EnZQRQcxCguqkiVa0cB31x1A8v+RFO09DvvD5JYO2uQ9oZ/vVqTu3oUk/aNR0padqaK3/DFQ2zgwVk9qKCYxWeufLNbyoQUp6DzDUPmutm5UEZ1Iuvfg/6dVFVo+hvcc+ptqBfzTRujH71QY2jX7cWrlH0tS3d3qC/OH7svegrD2ocfTWqWnHGVTsOSVkyyBo+Vznj2typVsiqCLKl2vVeDUdWEd+iVVdd5myvME6iIkrm+A7U5SmHhrGwKP0txc8Kxi44xuow1IozsA4N222eelXFdnE45vax5cXiRMq5Ki8O9rSG/wM= -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/WhoAmICommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Daemon; 2 | using Atlasd.Localization; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 9 | { 10 | class WhoAmICommand : ChatCommand 11 | { 12 | public WhoAmICommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 13 | 14 | public override bool CanInvoke(ChatCommandContext context) 15 | { 16 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 17 | } 18 | 19 | public override void Invoke(ChatCommandContext context) 20 | { 21 | var gameState = context.GameState; 22 | var ch = gameState.ActiveChannel; 23 | var g = gameState.GameAd; 24 | string r; 25 | 26 | if (g != null) // TODO: Consider private games and friendship. 27 | r = Resources.YouAreUsingGameInGame; 28 | else if (ch != null) 29 | r = Resources.YouAreUsingGameInTheChannel; 30 | else 31 | r = Resources.YouAreUsingGameInRealm; 32 | 33 | if (gameState.Away != null) 34 | r += Battlenet.Common.NewLine + Resources.AwayCommandStatusSelf.Replace("{awayMessage}", gameState.Away); 35 | 36 | var env = new Dictionary() 37 | { 38 | { "accountName", gameState.Username }, 39 | { "channel", ch == null ? "(null)" : ch.Name }, 40 | { "game", Product.ProductName(gameState.Product, true) }, 41 | { "gameAd", g == null ? "(null)" : Encoding.UTF8.GetString(g.Name) }, 42 | { "host", Settings.GetString(new string[] { "battlenet", "realm", "host" }, "(null)") }, 43 | { "localTime", gameState.LocalTime.ToString(Common.HumanDateTimeFormat).Replace(" 0", " ") }, 44 | { "name", Channel.RenderOnlineName(gameState, gameState) }, 45 | { "onlineName", Channel.RenderOnlineName(gameState, gameState) }, 46 | { "realm", Settings.GetString(new string[] { "battlenet", "realm", "name" }, Resources.Battlenet) }, 47 | { "realmTime", DateTime.Now.ToString(Common.HumanDateTimeFormat).Replace(" 0", " ") }, 48 | { "realmTimezone", $"UTC{DateTime.Now:zzz}" }, 49 | { "user", Channel.RenderOnlineName(gameState, gameState) }, 50 | { "username", Channel.RenderOnlineName(gameState, gameState) }, 51 | { "userName", Channel.RenderOnlineName(gameState, gameState) }, 52 | }; 53 | var en2v = env.Concat(context.Environment); 54 | 55 | foreach (var kv in env) r = r.Replace("{" + kv.Key + "}", kv.Value); 56 | new ChatEvent(ChatEvent.EventIds.EID_INFO, gameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, gameState.Ping, gameState.OnlineName, r).WriteTo(gameState.Client); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/FriendAddCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Protocols.Game.Messages; 2 | using Atlasd.Localization; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 8 | { 9 | class FriendAddCommand : ChatCommand 10 | { 11 | public FriendAddCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 12 | 13 | public override bool CanInvoke(ChatCommandContext context) 14 | { 15 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 16 | } 17 | 18 | public override void Invoke(ChatCommandContext context) 19 | { 20 | var replyEventId = ChatEvent.EventIds.EID_ERROR; 21 | var reply = string.Empty; 22 | var friends = (List)context.GameState.ActiveAccount.Get(Account.FriendsKey, new List()); 23 | 24 | var targetString = Arguments.Count > 0 ? Arguments[0] : string.Empty; 25 | if (string.IsNullOrEmpty(targetString)) 26 | { 27 | reply = Resources.AddFriendEmptyTarget; 28 | } 29 | else 30 | { 31 | var exists = false; 32 | foreach (var friendByteString in friends) 33 | { 34 | string friendString = Encoding.UTF8.GetString(friendByteString); 35 | if (string.Equals(targetString, friendString, StringComparison.CurrentCultureIgnoreCase)) 36 | { 37 | exists = true; 38 | break; 39 | } 40 | } 41 | if (exists) 42 | { 43 | reply = Resources.AlreadyAddedFriend.Replace("{friend}", targetString); 44 | } 45 | else 46 | { 47 | var friendByteString = Encoding.UTF8.GetBytes(targetString); 48 | var friend = new Friend(context.GameState, friendByteString); 49 | friends.Add(friend.Username); 50 | 51 | replyEventId = ChatEvent.EventIds.EID_INFO; 52 | reply = Resources.AddedFriend.Replace("{friend}", Encoding.UTF8.GetString(friend.Username)); 53 | 54 | new SID_FRIENDSADD().Invoke(new MessageContext(context.GameState.Client, MessageDirection.ServerToClient, new Dictionary() {{ "friend", friend }})); 55 | } 56 | } 57 | 58 | if (string.IsNullOrEmpty(reply)) return; 59 | foreach (var kv in context.Environment) reply = reply.Replace("{" + kv.Key + "}", kv.Value); 60 | foreach (var line in reply.Split(Battlenet.Common.NewLine)) 61 | new ChatEvent(replyEventId, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/DesignateCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Localization; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 6 | { 7 | class DesignateCommand : ChatCommand 8 | { 9 | public DesignateCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 10 | 11 | public override bool CanInvoke(ChatCommandContext context) 12 | { 13 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 14 | } 15 | 16 | public override void Invoke(ChatCommandContext context) 17 | { 18 | if (context.GameState.ActiveChannel == null) 19 | { 20 | new InvalidCommand(RawBuffer, Arguments).Invoke(context); 21 | return; 22 | } 23 | 24 | if (!(context.GameState.ChannelFlags.HasFlag(Account.Flags.Employee) 25 | || context.GameState.ChannelFlags.HasFlag(Account.Flags.ChannelOp) 26 | || context.GameState.ChannelFlags.HasFlag(Account.Flags.Admin))) 27 | { 28 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, Resources.YouAreNotAChannelOperator).WriteTo(context.GameState.Client); 29 | return; 30 | } 31 | 32 | var target = ""; 33 | if (Arguments.Count > 0) 34 | { 35 | target = Arguments[0]; 36 | Arguments.RemoveAt(0); 37 | RawBuffer = RawBuffer[(Encoding.UTF8.GetByteCount(target) + (Arguments.Count > 0 ? 1 : 0))..]; 38 | } 39 | 40 | if (string.IsNullOrEmpty(target) 41 | || !Battlenet.Common.ActiveGameStates.TryGetValue(target, out var targetState) 42 | || targetState == null) 43 | { 44 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, Resources.UserNotLoggedOn).WriteTo(context.GameState.Client); 45 | return; 46 | } 47 | 48 | if (targetState.ActiveChannel != context.GameState.ActiveChannel) 49 | { 50 | new ChatEvent(ChatEvent.EventIds.EID_ERROR, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, Resources.InvalidUser).WriteTo(context.GameState.Client); 51 | return; 52 | } 53 | 54 | context.GameState.ActiveChannel.Designate(context.GameState, targetState); 55 | new ChatEvent(ChatEvent.EventIds.EID_INFO, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, Resources.DesignateCommand.Replace("{user}", targetState.OnlineName)).WriteTo(context.GameState.Client); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_MESSAGEBOX.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.Messages 8 | { 9 | class SID_MESSAGEBOX : Message 10 | { 11 | public SID_MESSAGEBOX() 12 | { 13 | Id = (byte)MessageIds.SID_MESSAGEBOX; 14 | Buffer = new byte[6]; 15 | } 16 | 17 | public SID_MESSAGEBOX(byte[] buffer) 18 | { 19 | Id = (byte)MessageIds.SID_MESSAGEBOX; 20 | Buffer = buffer; 21 | } 22 | 23 | public override bool Invoke(MessageContext context) 24 | { 25 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 26 | 27 | if (context.Direction != MessageDirection.ServerToClient) 28 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from server to client"); 29 | 30 | if (Buffer.Length < 6) 31 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be at least 6 bytes"); 32 | 33 | var style = (UInt32)context.Arguments["style"]; 34 | var text = (string)context.Arguments["text"]; 35 | var caption = (string)context.Arguments["caption"]; 36 | 37 | Buffer = new byte[6 + Encoding.UTF8.GetByteCount(text) + Encoding.UTF8.GetByteCount(caption)]; 38 | 39 | using var m = new MemoryStream(Buffer); 40 | using var w = new BinaryWriter(m); 41 | 42 | w.Write((UInt32)style); 43 | w.Write((string)text); 44 | w.Write((string)caption); 45 | 46 | Logging.WriteLine(Logging.LogLevel.Info, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"Sent MessageBox parameters to client"); 47 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 48 | return true; 49 | } 50 | 51 | public new byte[] ToByteArray(ProtocolType protocolType) 52 | { 53 | if (protocolType.IsChat()) 54 | { 55 | using var _m = new MemoryStream(Buffer); 56 | using var r = new BinaryReader(_m); 57 | var style = r.ReadUInt32(); 58 | var text = r.ReadByteString(); 59 | var caption = r.ReadByteString(); 60 | 61 | using var m = new MemoryStream(); 62 | using var w = new System.IO.BinaryWriter(m); 63 | w.Write(Encoding.UTF8.GetBytes($"{2000 + Id} MESSAGEBOX \"")); 64 | w.Write(text); 65 | w.Write((byte)'"'); 66 | w.Write(Encoding.UTF8.GetBytes(Battlenet.Common.NewLine)); 67 | return m.GetBuffer()[0..(int)w.BaseStream.Length]; 68 | } 69 | else 70 | { 71 | return base.ToByteArray(protocolType); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_QUERYREALMS.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | 8 | namespace Atlasd.Battlenet.Protocols.Game.Messages 9 | { 10 | class SID_QUERYREALMS : Message 11 | { 12 | public SID_QUERYREALMS() 13 | { 14 | Id = (byte)MessageIds.SID_QUERYREALMS; 15 | Buffer = new byte[0]; 16 | } 17 | 18 | public SID_QUERYREALMS(byte[] buffer) 19 | { 20 | Id = (byte)MessageIds.SID_QUERYREALMS; 21 | Buffer = buffer; 22 | } 23 | 24 | public override bool Invoke(MessageContext context) 25 | { 26 | var gameState = context.Client.GameState; 27 | 28 | switch (context.Direction) 29 | { 30 | case MessageDirection.ClientToServer: 31 | { 32 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 33 | 34 | if (!Product.IsDiabloII(gameState.Product)) 35 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be sent from D2DV or D2XP"); 36 | 37 | if (Buffer.Length < 9) 38 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be at least 9 bytes, got {Buffer.Length}"); 39 | 40 | /** 41 | * (UINT32) Unused (0) 42 | * (UINT32) Unused (0) 43 | * (STRING) Unknown (empty) 44 | */ 45 | 46 | return new SID_QUERYREALMS().Invoke(new MessageContext(context.Client, MessageDirection.ServerToClient)); 47 | } 48 | case MessageDirection.ServerToClient: 49 | { 50 | /** 51 | * (UINT32) Unknown 52 | * (UINT32) Count 53 | * For Each Realm: 54 | * (UINT32) Unknown 55 | * (STRING) Realm title 56 | * (STRING) Realm description 57 | */ 58 | 59 | Buffer = new byte[8]; 60 | 61 | using var m = new MemoryStream(Buffer); 62 | using var w = new BinaryWriter(m); 63 | 64 | w.Write((UInt32)0); 65 | w.Write((UInt32)0); 66 | 67 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 68 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 69 | return true; 70 | } 71 | } 72 | 73 | return false; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/ChatCommands/FriendCommand.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Protocols.Game.Messages; 2 | using Atlasd.Localization; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.ChatCommands 8 | { 9 | class FriendCommand : ChatCommand 10 | { 11 | public FriendCommand(byte[] rawBuffer, List arguments) : base(rawBuffer, arguments) { } 12 | 13 | public override bool CanInvoke(ChatCommandContext context) 14 | { 15 | return context != null && context.GameState != null && context.GameState.ActiveAccount != null; 16 | } 17 | 18 | public override void Invoke(ChatCommandContext context) 19 | { 20 | var replyEventId = ChatEvent.EventIds.EID_ERROR; 21 | var reply = string.Empty; 22 | 23 | var subcommand = Arguments.Count > 0 ? Arguments[0] : string.Empty; 24 | if (!string.IsNullOrEmpty(subcommand)) 25 | { 26 | Arguments.RemoveAt(0); 27 | // Calculates and removes (subcmd+' ') from (RawBuffer) which prints into (RawBuffer): 28 | var stripSize = subcommand.Length + (RawBuffer.Length - subcommand.Length > 0 ? 1 : 0); 29 | RawBuffer = RawBuffer[stripSize..]; 30 | } 31 | 32 | var friends = (List)context.GameState.ActiveAccount.Get(Account.FriendsKey, new List()); 33 | 34 | switch (subcommand.ToLowerInvariant()) 35 | { 36 | case "add": 37 | case "a": 38 | new FriendAddCommand(RawBuffer, Arguments).Invoke(context); break; 39 | case "demote": 40 | case "d": 41 | new FriendDemoteCommand(RawBuffer, Arguments).Invoke(context); break; 42 | case "list": 43 | case "l": 44 | new FriendListCommand(RawBuffer, Arguments).Invoke(context); break; 45 | case "message": 46 | case "msg": 47 | case "m": 48 | new FriendMessageCommand(RawBuffer, Arguments).Invoke(context); break; 49 | case "promote": 50 | case "p": 51 | new FriendPromoteCommand(RawBuffer, Arguments).Invoke(context); break; 52 | case "remove": 53 | case "rem": 54 | case "r": 55 | new FriendRemoveCommand(RawBuffer, Arguments).Invoke(context); break; 56 | default: 57 | { 58 | reply = Resources.InvalidChatCommand; 59 | break; 60 | } 61 | } 62 | 63 | if (string.IsNullOrEmpty(reply)) return; 64 | foreach (var kv in context.Environment) reply = reply.Replace("{" + kv.Key + "}", kv.Value); 65 | foreach (var line in reply.Split(Battlenet.Common.NewLine)) 66 | new ChatEvent(replyEventId, context.GameState.ChannelFlags, context.GameState.Client.RemoteIPAddress, context.GameState.Ping, context.GameState.OnlineName, line).WriteTo(context.GameState.Client); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_READMEMORY.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.Messages 7 | { 8 | class SID_READMEMORY : Message 9 | { 10 | public SID_READMEMORY() 11 | { 12 | Id = (byte)MessageIds.SID_READMEMORY; 13 | Buffer = new byte[0]; 14 | } 15 | 16 | public SID_READMEMORY(byte[] buffer) 17 | { 18 | Id = (byte)MessageIds.SID_READMEMORY; 19 | Buffer = buffer; 20 | } 21 | 22 | public override bool Invoke(MessageContext context) 23 | { 24 | switch (context.Direction) 25 | { 26 | case MessageDirection.ClientToServer: 27 | { 28 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 29 | 30 | /** 31 | * (UINT32) Request ID 32 | * (VOID) Memory 33 | */ 34 | 35 | if (Buffer.Length < 4) 36 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be at least 4 bytes"); 37 | 38 | using var m = new MemoryStream(Buffer); 39 | using var r = new BinaryReader(m); 40 | 41 | var requestId = r.ReadUInt32(); 42 | var data = r.ReadBytes((int)(r.BaseStream.Length - r.BaseStream.Position)); 43 | 44 | // We don't use this. Why did the client send us this ?? 45 | 46 | break; 47 | } 48 | case MessageDirection.ServerToClient: 49 | { 50 | var requestId = (UInt32)context.Arguments["requestId"]; 51 | var address = (UInt32)context.Arguments["address"]; 52 | var length = (UInt32)context.Arguments["length"]; 53 | 54 | /** 55 | * (UINT32) Request ID 56 | * (UINT32) Address 57 | * (UINT32) Length 58 | */ 59 | 60 | Buffer = new byte[12]; 61 | 62 | using var m = new MemoryStream(Buffer); 63 | using var w = new BinaryWriter(m); 64 | 65 | w.Write((UInt32)requestId); 66 | w.Write((UInt32)address); 67 | w.Write((UInt32)length); 68 | 69 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 70 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 71 | return true; 72 | } 73 | } 74 | 75 | return false; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Atlasd/Daemon/Logging.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace Atlasd.Daemon 5 | { 6 | class Logging 7 | { 8 | public static LogLevel CurrentLogLevel = LogLevel.Debug; 9 | 10 | public enum LogLevel : uint 11 | { 12 | Error, 13 | Warning, 14 | Info, 15 | Debug, 16 | }; 17 | 18 | public enum LogType : uint 19 | { 20 | Account, 21 | BNFTP, 22 | Channel, 23 | Clan, 24 | Config, 25 | Client, 26 | Client_BNFTP, 27 | Client_Chat, 28 | Client_Game, 29 | Client_IPC, 30 | Client_MCP, 31 | Client_UDP, 32 | GameAd, 33 | Http, 34 | Server, 35 | }; 36 | 37 | public static string LogLevelToString(LogLevel level) 38 | { 39 | return level switch 40 | { 41 | LogLevel.Error => "Error", 42 | LogLevel.Warning => "Warning", 43 | LogLevel.Info => "Info", 44 | LogLevel.Debug => "Debug", 45 | _ => throw new IndexOutOfRangeException("Unknown log level"), 46 | }; 47 | } 48 | 49 | public static string LogTypeToString(LogType type) 50 | { 51 | return type switch 52 | { 53 | LogType.Account => "Account", 54 | LogType.BNFTP => "BNFTP", 55 | LogType.Channel => "Channel", 56 | LogType.Config => "Config", 57 | LogType.Client => "Client", 58 | LogType.Client_BNFTP => "Client_BNFTP", 59 | LogType.Client_Chat => "Client_Chat", 60 | LogType.Client_Game => "Client_Game", 61 | LogType.Client_IPC => "Client_IPC", 62 | LogType.Client_MCP => "Client_MCP", 63 | LogType.Client_UDP => "Client_UDP", 64 | LogType.GameAd => "GameAd", 65 | LogType.Http => "Http", 66 | LogType.Server => "Server", 67 | _ => throw new IndexOutOfRangeException("Unknown log type"), 68 | }; 69 | } 70 | 71 | public static LogLevel StringToLogLevel(string value) 72 | { 73 | return value.ToLower() switch 74 | { 75 | "error" => LogLevel.Error, 76 | "warning" => LogLevel.Warning, 77 | "info" => LogLevel.Info, 78 | "debug" => LogLevel.Debug, 79 | _ => throw new IndexOutOfRangeException("Unknown value"), 80 | }; 81 | } 82 | 83 | public static void WriteLine(LogLevel level, LogType type, string buffer) 84 | { 85 | if (level > CurrentLogLevel) return; 86 | 87 | Console.Out.WriteLine($"[{DateTime.Now}] [{LogLevelToString(level)}] [{LogTypeToString(type).Replace("_", "] [")}] {buffer}"); 88 | } 89 | 90 | public static void WriteLine(LogLevel level, LogType type, EndPoint endp, string buffer) 91 | { 92 | WriteLine(level, type, $"[{endp}] {buffer}"); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_CHECKDATAFILE2.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | 8 | namespace Atlasd.Battlenet.Protocols.Game.Messages 9 | { 10 | class SID_CHECKDATAFILE2 : Message 11 | { 12 | public enum Statuses : UInt32 13 | { 14 | Unapproved = 0, 15 | Approved = 1, 16 | LadderApproved = 2, 17 | }; 18 | 19 | public SID_CHECKDATAFILE2() 20 | { 21 | Id = (byte)MessageIds.SID_CHECKDATAFILE2; 22 | Buffer = new byte[0]; 23 | } 24 | 25 | public SID_CHECKDATAFILE2(byte[] buffer) 26 | { 27 | Id = (byte)MessageIds.SID_CHECKDATAFILE2; 28 | Buffer = buffer; 29 | } 30 | 31 | public override bool Invoke(MessageContext context) 32 | { 33 | switch (context.Direction) 34 | { 35 | case MessageDirection.ClientToServer: 36 | { 37 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 38 | 39 | /** 40 | * (UINT32) File size 41 | * (UINT8) [20] File checksum (standard SHA-1) 42 | * (STRING) File name 43 | */ 44 | 45 | if (Buffer.Length < 25) 46 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be at least 25 bytes"); 47 | 48 | using var m = new MemoryStream(Buffer); 49 | using var r = new BinaryReader(m); 50 | 51 | var fileSize = r.ReadUInt32(); 52 | var fileChecksum = r.ReadBytes(20); 53 | var fileName = Encoding.UTF8.GetString(r.ReadByteString()); 54 | 55 | var status = Statuses.Unapproved; 56 | 57 | return new SID_CHECKDATAFILE2().Invoke(new MessageContext(context.Client, MessageDirection.ServerToClient, new Dictionary(){{ "status", status }})); 58 | } 59 | case MessageDirection.ServerToClient: 60 | { 61 | /** 62 | * (UINT32) Status 63 | */ 64 | 65 | Buffer = new byte[4]; 66 | 67 | using var m = new MemoryStream(Buffer); 68 | using var w = new BinaryWriter(m); 69 | 70 | w.Write((UInt32)context.Arguments["status"]); 71 | 72 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 73 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 74 | return true; 75 | } 76 | } 77 | 78 | return false; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Atlasd/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 59 | 60 | 61 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_CREATEACCOUNT.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.Messages 8 | { 9 | class SID_CREATEACCOUNT : Message 10 | { 11 | protected enum Statuses : UInt32 12 | { 13 | Failure = 0, 14 | Success = 1, 15 | }; 16 | 17 | public SID_CREATEACCOUNT() 18 | { 19 | Id = (byte)MessageIds.SID_CREATEACCOUNT; 20 | Buffer = new byte[0]; 21 | } 22 | 23 | public SID_CREATEACCOUNT(byte[] buffer) 24 | { 25 | Id = (byte)MessageIds.SID_CREATEACCOUNT; 26 | Buffer = buffer; 27 | } 28 | 29 | public override bool Invoke(MessageContext context) 30 | { 31 | switch (context.Direction) 32 | { 33 | case MessageDirection.ClientToServer: 34 | { 35 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 36 | 37 | if (Buffer.Length < 21) 38 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be at least 21 bytes"); 39 | 40 | /** 41 | * (UINT32) [5] Password hash 42 | * (STRING) Username 43 | */ 44 | 45 | using var m = new MemoryStream(Buffer); 46 | using var r = new BinaryReader(m); 47 | 48 | var passwordHash = r.ReadBytes(20); 49 | var username = r.ReadString(); 50 | 51 | Account.CreateStatus _status = Account.TryCreate(username, passwordHash, out var _); 52 | var status = _status == Account.CreateStatus.Success ? Statuses.Success : Statuses.Failure; 53 | 54 | return new SID_CREATEACCOUNT().Invoke(new MessageContext(context.Client, MessageDirection.ServerToClient, new Dictionary { 55 | { "status", status } 56 | })); 57 | } 58 | case MessageDirection.ServerToClient: 59 | { 60 | /** 61 | * (UINT32) Status 62 | */ 63 | 64 | Buffer = new byte[4]; 65 | 66 | using var m = new MemoryStream(Buffer); 67 | using var w = new BinaryWriter(m); 68 | 69 | w.Write((UInt32)(Statuses)context.Arguments["status"]); 70 | 71 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 72 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 73 | return true; 74 | } 75 | } 76 | 77 | return false; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_CREATEACCOUNT2.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | 8 | namespace Atlasd.Battlenet.Protocols.Game.Messages 9 | { 10 | class SID_CREATEACCOUNT2 : Message 11 | { 12 | public SID_CREATEACCOUNT2() 13 | { 14 | Id = (byte)MessageIds.SID_CREATEACCOUNT2; 15 | Buffer = new byte[0]; 16 | } 17 | 18 | public SID_CREATEACCOUNT2(byte[] buffer) 19 | { 20 | Id = (byte)MessageIds.SID_CREATEACCOUNT2; 21 | Buffer = buffer; 22 | } 23 | 24 | public override bool Invoke(MessageContext context) 25 | { 26 | switch (context.Direction) 27 | { 28 | case MessageDirection.ClientToServer: 29 | { 30 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 31 | 32 | if (Buffer.Length < 21) 33 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be at least 21 bytes"); 34 | 35 | /** 36 | * (UINT32) [5] Password hash 37 | * (STRING) Username 38 | */ 39 | 40 | using var m = new MemoryStream(Buffer); 41 | using var r = new BinaryReader(m); 42 | 43 | var passwordHash = r.ReadBytes(20); 44 | var username = r.ReadString(); 45 | 46 | Account.CreateStatus status = Account.TryCreate(username, passwordHash, out var _); 47 | 48 | return new SID_CREATEACCOUNT2().Invoke(new MessageContext(context.Client, MessageDirection.ServerToClient, new Dictionary { 49 | { "status", status } // info key optional 50 | })); 51 | } 52 | case MessageDirection.ServerToClient: 53 | { 54 | /** 55 | * (UINT32) Status 56 | * (STRING) Account name suggestion 57 | */ 58 | 59 | string info = context.Arguments.ContainsKey("info") ? (string)context.Arguments["info"] : ""; 60 | 61 | Buffer = new byte[5 + Encoding.UTF8.GetByteCount(info)]; 62 | 63 | using var m = new MemoryStream(Buffer); 64 | using var w = new BinaryWriter(m); 65 | 66 | w.Write((UInt32)(Account.CreateStatus)context.Arguments["status"]); 67 | w.Write(info); 68 | 69 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 70 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 71 | return true; 72 | } 73 | } 74 | 75 | return false; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_PING.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | 6 | namespace Atlasd.Battlenet.Protocols.Game.Messages 7 | { 8 | class SID_PING : Message 9 | { 10 | public SID_PING() 11 | { 12 | Id = (byte)MessageIds.SID_PING; 13 | Buffer = new byte[0]; 14 | } 15 | 16 | public SID_PING(byte[] buffer) 17 | { 18 | Id = (byte)MessageIds.SID_PING; 19 | Buffer = buffer; 20 | } 21 | 22 | public override bool Invoke(MessageContext context) 23 | { 24 | if (context.Client == null || !context.Client.Connected || context.Client.GameState == null) return false; 25 | var gameState = context.Client.GameState; 26 | 27 | switch (context.Direction) 28 | { 29 | case MessageDirection.ClientToServer: 30 | { 31 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 32 | 33 | if (Buffer.Length != 4) 34 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} buffer must be 4 bytes"); 35 | 36 | using var m = new MemoryStream(Buffer); 37 | using var r = new BinaryReader(m); 38 | var token = r.ReadUInt32(); 39 | 40 | gameState.LastPong = DateTime.Now; 41 | var delta = gameState.LastPong - gameState.LastPing; 42 | 43 | var autoRefreshPings = Settings.GetBoolean(new string[] { "battlenet", "emulation", "auto_refresh_pings" }, false); 44 | if (gameState.Ping != -1 && !autoRefreshPings) return true; 45 | 46 | if (gameState.ActiveChannel == null) 47 | gameState.Ping = (int)Math.Round(delta.TotalMilliseconds); 48 | else 49 | gameState.ActiveChannel.UpdateUser(gameState, gameState.Ping); 50 | 51 | Logging.WriteLine(Logging.LogLevel.Info, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"Ping: {gameState.Ping}ms"); 52 | return true; 53 | } 54 | case MessageDirection.ServerToClient: 55 | { 56 | var token = (UInt32)(context.Arguments.ContainsKey("token") ? context.Arguments["token"] : (new Random()).Next()); 57 | 58 | Buffer = new byte[4]; 59 | using var m = new MemoryStream(Buffer); 60 | using var w = new BinaryWriter(m); 61 | w.Write((UInt32)token); 62 | 63 | gameState.LastPing = DateTime.Now; 64 | gameState.PingToken = token; 65 | 66 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 67 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 68 | return true; 69 | } 70 | default: return false; 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Atlasd/Battlenet/Protocols/Game/Messages/SID_CDKEY.cs: -------------------------------------------------------------------------------- 1 | using Atlasd.Battlenet.Exceptions; 2 | using Atlasd.Daemon; 3 | using System; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | namespace Atlasd.Battlenet.Protocols.Game.Messages 8 | { 9 | class SID_CDKEY : Message 10 | { 11 | public const string KEYOWNER_TOOMANYSPAWNS = "TOO MANY SPAWNS"; 12 | public const string KEYOWNER_NOSPAWNING = "NO SPAWNING"; 13 | 14 | public enum Statuses : UInt32 15 | { 16 | Success = 1, 17 | InvalidKey = 2, 18 | BadProduct = 3, 19 | Banned = 4, 20 | InUse = 5, 21 | } 22 | 23 | public SID_CDKEY() 24 | { 25 | Id = (byte)MessageIds.SID_CDKEY; 26 | Buffer = new byte[0]; 27 | } 28 | 29 | public SID_CDKEY(byte[] buffer) 30 | { 31 | Id = (byte)MessageIds.SID_CDKEY; 32 | Buffer = buffer; 33 | } 34 | 35 | public override bool Invoke(MessageContext context) 36 | { 37 | switch (context.Direction) 38 | { 39 | case MessageDirection.ClientToServer: 40 | { 41 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 42 | 43 | if (Buffer.Length < 6) 44 | throw new GameProtocolViolationException(context.Client, $"{MessageName(Id)} must be at least 6 bytes"); 45 | /** 46 | * (UINT32) Spawn Key (1 is TRUE, 0 is FALSE) 47 | * (STRING) Game Key 48 | * (STRING) Key owner name * 49 | */ 50 | 51 | using var m = new MemoryStream(Buffer); 52 | using var r = new BinaryReader(m); 53 | 54 | context.Client.GameState.SpawnKey = r.ReadUInt32() == 1; 55 | context.Client.GameState.GameKeys.Append(new GameKey(r.ReadString())); 56 | context.Client.GameState.KeyOwner = r.ReadByteString(); 57 | 58 | return new SID_CDKEY().Invoke(new MessageContext(context.Client, MessageDirection.ServerToClient)); 59 | } 60 | case MessageDirection.ServerToClient: 61 | { 62 | /** 63 | * (UINT32) Result 64 | * (STRING) Key Owner 65 | */ 66 | 67 | Buffer = new byte[5]; 68 | 69 | using var m = new MemoryStream(Buffer); 70 | using var w = new BinaryWriter(m); 71 | 72 | w.Write((UInt32)Statuses.Success); 73 | w.Write((byte)0); 74 | 75 | Logging.WriteLine(Logging.LogLevel.Debug, Logging.LogType.Client_Game, context.Client.RemoteEndPoint, $"[{Common.DirectionToString(context.Direction)}] {MessageName(Id)} ({4 + Buffer.Length} bytes)"); 76 | context.Client.Send(ToByteArray(context.Client.ProtocolType)); 77 | return true; 78 | } 79 | } 80 | 81 | return false; 82 | } 83 | } 84 | } 85 | --------------------------------------------------------------------------------