using System;
using System.Collections.Generic;
using System.Text;
using D2Sniffer;
using System.Diagnostics;
using System.Net.Sockets;
namespace data
{
public class Bot
{
//fields
BotConfig myBotConfig; //configuration for current bot instance
static PacketSniffer myPacketSniffer; //d2Smell sniffer instance
TcpClient myBnetServerClient, myRealmServerClient,myGameServerClient;//connections to battle.net servers
static System.Net.IPAddress RealmServerIp; //Used as storage to connect to realmserver after the response from BNLS is received
ushort CurrentGameNum = 0; //used in CreateGame packet, increment by 0x02 each game
uint ClientToken, ServerToken; //Tokens used to Hash data at run-time
//TODO change this when implementing the cdkey files and store the damn index cuz appsettings are so retarded...
int KeyListIndex = 0; //keep track of the current used cdkey
string RealmServerString, BNCSUniqueName, CurrentGameName, CurrentGamePassword = "sadsapog"; //game related + internal stuff
bool LoginDone = false, InGame = false, FailedToJoin = false; //used in the Creat/Join game proc
byte[] RSLogon; //a packet that is built in a function but sent in another one.
int RunCreationWaitDelay = 5000; //the delay between each game creation attemps
public int TotalRuns = 0, TotalCompletedRuns = 0; //runs state: TotalCompletedRuns/TotalRuns
public RealmServer.JoinGameResult LastJoinResult = RealmServer.JoinGameResult.Success;
public bool NeedIPReset = false; //this is not yet set
private bool FatalDebugError = false; //replace this eventually; (or I might keep it)
///
/// Ctor
///
/// a BotConfig object containing all revelant information to run the bot
public Bot(BotConfig botConfig)
{
this.myBotConfig = botConfig; //keep this
}
int TotalTimeOutGame = 0;
///
/// Will perform a hard-coded number of runs. Then return to change cdkey
/// Caution: Some runs use 2 games but are considered being only 1run (when the bot use ComeBack if 2000 received packets is reached)
/// So be carefull since if you set it to swap cdkey every 20runs it can create up to 40 games
///
public void doRuns()
{
this.NeedIPReset = false;
//myPacketSniffer.OnConnectionChange += new ConnectionChangeHandler(myPacketSniffer_OnConnectionChange);
CurrentGameNum = 0; //increment by 2 at each game creation
Game.PacketDispatcher myPacketDispatcher = new data.Game.PacketDispatcher();
myPacketSniffer.AddListener(D2Packets.PacketOrigin.GameServer, new PacketEventHandler(myPacketDispatcher.onPacketArrival));
int InitialTicks = 0;
Test myTest = new Test(myBotConfig, false);
//for (int i = 0; i < 10; i++)
int i = 0;
while (true)
{
if (i > 0)
{
//Disconnect_BnetServer();
//Disconnect_RealmServer();
onRunDone();
}
i++;
//if (this.NeedIPReset)
// return;
Game.GameInfo.RST = false;
CurrentGameNum += 2;
this.FailedToJoin = false;
this.InGame = false;
Random rnd = new Random();
Sleep(this.RunCreationWaitDelay);
Game.Belt.Init();
CurrentGameName = rnd.Next(1000000, 99999999).ToString();
CreateGame(CurrentGameNum, CurrentGameName, CurrentGamePassword, DifficultyType.Nightmare, "");
//JoinGame(1, "Dkk3", "Q");
int Timeout2 = 0;
//while (!this.FatalDebugError && !this.InGame)
while (!this.InGame)
{
System.Threading.Thread.Sleep(1);
Timeout2++;
if (Timeout2 > 5000)
{
if (TotalTimeOutGame > 20)
return;
Sleep(5000);
TotalTimeOutGame++;
Logger.Log("Bot(): Create game timed out");
break; ;
}
if (this.FailedToJoin)
{
//Sleep(30000);
Sleep(5000);
this.FailedToJoin = false;
break;
}
}
if (!this.InGame)
{
//onRunDone();
continue;
}
TotalTimeOutGame = 0;
InitialTicks = Environment.TickCount;
//Sleep(100);
//if (Game.GameInfo.RST)
//{
// Logger.Log("RST, create new game");
// continue;
//}
bool RunSuccess = false;
try
{
RunSuccess = myTest.DoRun(ref myGameServerClient);
}
catch
{
this.TotalRuns += 1;
myTest.onRunDone();
continue;
}
if (RunSuccess)
this.TotalCompletedRuns += 1;
this.TotalRuns += 1;
myTest.onRunDone();
//go back to the same game in case we did not finish the run under 2000 rcv packets
if (!RunSuccess && Game.GameInfo.Catacomb2Path.Count > 0 && Game.GameInfo.Catacomb3Path.Count == 0)
{
onRunDone();
Game.Belt.Init();
Game.GameInfo.RST = false;
CurrentGameNum += 2;
this.FailedToJoin = false;
this.InGame = false;
LastJoinResult = RealmServer.JoinGameResult.Success;
Sleep(2000);
for (int ia = 0; ia < 3; ia++)
{
if (ia > 0)
Sleep(this.RunCreationWaitDelay);
JoinGame(this.CurrentGameNum, this.CurrentGameName, this.CurrentGamePassword);
for (int tt = 0; tt < 1000; tt++)
{
Sleep(1);
if (this.InGame)
break;
if (this.FailedToJoin)
continue;
}
if (this.InGame)
break;
if (LastJoinResult == RealmServer.JoinGameResult.GameDoesNotExist)
break;
}
//Sleep(1000);
if (!this.InGame)
continue;
try
{
if (myTest.ComeBack(ref myGameServerClient))
this.TotalCompletedRuns += 1;
}
catch
{
}
}
//go back to the same game in case we did not finish the run under 2000 rcv packets
if (!RunSuccess && Game.GameInfo.Catacomb2Path.Count > 0 && Game.GameInfo.Catacomb3Path.Count > 0)
{
onRunDone();
Game.Belt.Init();
Game.GameInfo.RST = false;
CurrentGameNum += 2;
this.FailedToJoin = false;
this.InGame = false;
LastJoinResult = RealmServer.JoinGameResult.Success;
Sleep(2000);
for (int ia = 0; ia < 3; ia++)
{
if (ia > 0)
Sleep(this.RunCreationWaitDelay);
JoinGame(this.CurrentGameNum, this.CurrentGameName, this.CurrentGamePassword);
for (int tt = 0; tt < 1000; tt++)
{
Sleep(1);
if (this.InGame)
break ;
//break;
if (this.FailedToJoin)
continue;
}
if (this.InGame)
break;
if (LastJoinResult == RealmServer.JoinGameResult.GameDoesNotExist)
break;
}
//Sleep(1000);
if (!this.InGame)
continue;
try
{
if (myTest.ComeBack(ref myGameServerClient))
this.TotalCompletedRuns += 1;
}
catch
{
}
}
Logger.Log("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
Logger.Log("Total completed runs: " + this.TotalCompletedRuns + " on " + this.TotalRuns);
Logger.Log("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
if (FatalDebugError)
{
Logger.Log("Fatal error, trying to reconnect to bnet");
onRunDone();
//continue;
}
}
}
public void CheckCdKeys()
{
if (!System.IO.File.Exists(Environment.CurrentDirectory + "\\system\\State\\" + this.myBotConfig._CharacterName))
System.IO.File.Create(Environment.CurrentDirectory + "\\system\\State\\" + this.myBotConfig._CharacterName).Close();
System.IO.StreamReader sr = new System.IO.StreamReader(Environment.CurrentDirectory + "\\system\\State\\" + this.myBotConfig._CharacterName);
string line = sr.ReadLine();
Logger.Log("CHECK CD KEYS: LINE = " + line);
if (line == null || line == "")
{
this.KeyListIndex = 0;
}
else
{
this.KeyListIndex = Convert.ToInt32(line) + 1;
}
sr.Close();
sr.Dispose();
if (this.KeyListIndex > this.myBotConfig._ClassicKeyList.Length - 1)
this.KeyListIndex = 0;
System.IO.StreamWriter sw = new System.IO.StreamWriter(Environment.CurrentDirectory + "\\system\\State\\" + this.myBotConfig._CharacterName, false);
sw.Write(this.KeyListIndex);
sw.Close();
sw.Dispose();
}
///
/// will simply try to disconnect from the gameserver
///
public void onRunDone()
{
Sleep(100);
byte[] GCExitGame = GameClient.ExitGame.Build();
try
{
myGameServerClient.GetStream().Write(GCExitGame, 0, GCExitGame.Length);
}
catch {}
Sleep(100);
try
{
myGameServerClient.Client.Shutdown(SocketShutdown.Both);
}
catch { }
Sleep(100);
try
{
myGameServerClient.Client.Close();
}
catch { }
Sleep(100);
myGameServerClient = null;
Sleep(100);
}
///
/// Used to connect to battle.net
///
/// Somewhat redundant I can probably remove this (?)
/// success or not
public bool ConnectToBattleNet(BotConfig botConfig)
{
CheckCdKeys();
Logger.Log("Connecting to battle.net...");
this.FatalDebugError = false;
this.LoginDone = false;
this.myBotConfig = botConfig;
myBnetServerClient = new TcpClient(this.GetServerString(this.myBotConfig._Server), 6112);
myBnetServerClient.LingerState = new LingerOption(true, 0);
if (myPacketSniffer != null)
{
myPacketSniffer.Dispose();
myPacketSniffer = null;
}
myPacketSniffer = new PacketSniffer();
myPacketSniffer.AddListener(D2Packets.PacketOrigin.BattleNetServer, new PacketEventHandler(this.onBnetPacketReceived));
myPacketSniffer.AddListener(D2Packets.PacketOrigin.RealmServer, new PacketEventHandler(this.onRealmPacketReceived));
myPacketSniffer.StartSniffing(Process.GetCurrentProcess().Id, myBotConfig._PcapName);
try
{
myBnetServerClient.GetStream().WriteByte(0x01);
myBnetServerClient.GetStream().Write(new BC.ConnectionRequest().Build(), 0, new BC.ConnectionRequest().Build().Length);
}
catch
{
this.NeedIPReset = true;
return false;
}
int Timeout = 0;
while (!this.FatalDebugError && !this.LoginDone)
{
System.Threading.Thread.Sleep(1);
if (Timeout > 5000)
{
Logger.Log("Failed to connect to battle.net: Timed out");
this.NeedIPReset = true;
return false;
}
Timeout++;
}
if (this.FatalDebugError)
{
Logger.Log("Failed to connect to battle.net");
return false;
}
this.NeedIPReset = false;
Logger.Log("We are now logged onto battle.net");
return true;
}
///
/// Disconnect from battle.net
/// this function should maby check gameserver (?)
///
public void Disconnect()
{
Logger.Log("Disconnecting from battle.net...");
this.onRunDone();
if (myRealmServerClient != null)
{
try
{
myRealmServerClient.Client.Shutdown(SocketShutdown.Both);
}
catch { }
Sleep(100);
try
{
myRealmServerClient.Client.Close();
}
catch { }
Sleep(100);
}
if (myBnetServerClient != null)
{
try
{
myBnetServerClient.Client.Shutdown(SocketShutdown.Both);
}
catch { }
Sleep(100);
try
{
myBnetServerClient.Client.Close();
}
catch { }
Sleep(100);
}
myRealmServerClient = null;
myBnetServerClient = null;
myGameServerClient = null;
}
void Disconnect_BnetServer()
{
if (myBnetServerClient != null)
{
try
{
myBnetServerClient.Client.Shutdown(SocketShutdown.Both);
}
catch { }
Sleep(100);
try
{
myBnetServerClient.Client.Close();
}
catch { }
Sleep(100);
}
}
void Disconnect_RealmServer()
{
this.onRunDone();
if (myRealmServerClient != null)
{
try
{
myRealmServerClient.Client.Shutdown(SocketShutdown.Both);
}
catch { }
Sleep(100);
try
{
myRealmServerClient.Client.Close();
}
catch { }
Sleep(100);
}
}
///
/// Join a game. I need to change the return value to bool, it would be alot easier to use.
///
///
///
///
void JoinGame(ushort GameNumber, string GameName, string GamePassword)
{
Logger.Log("Joining game: " + GameName);
byte[] myJoinGame = new data.RC.JoinGame(GameNumber, GameName, GamePassword).GetData();
Sleep(100);
myRealmServerClient.GetStream().Write(myJoinGame, 0, myJoinGame.Length);
}
///
/// Create a game. I need to change the return value to bool, it would be alot easier to use.
///
///
///
///
///
///
void CreateGame(ushort GameNumber, string GameName, string GamePassword,DifficultyType type,string GameDescription)
{
Logger.Log("Creating game: " + GameName);
byte[] myCreateGame = new data.RC.CreateGame(GameNumber, type, GameName, GamePassword, GameDescription).GetData();
Sleep(100);
myRealmServerClient.GetStream().Write(myCreateGame, 0, myCreateGame.Length);
}
///
/// BNCS recv packets handler (event)
///
///
///
void onBnetPacketReceived(object sender, PacketEventArgs args)
{
switch (args.Packet.Data[0])
{
case (int) D2Packets.BnetServerPacket.BnetPing:
BnetServer.BnetPing ping = args.Packet as BnetServer.BnetPing;
//Logger.Log("Bnet: Ping? " + ping.Timestamp);
uint Timestamp = ping.Timestamp;
BC.BasePacket pong = new data.BC.BasePacket(0x25);
pong.InsertUInt32(Timestamp);
byte[] raw = pong.GetData();
try
{
myBnetServerClient.GetStream().Write(raw, 0, raw.Length);
}
catch { }
//Logger.Log("Bnet: Pong!");
break;
case (int)D2Packets.BnetServerPacket.BnetConnectionResponse:
//0x50
//this.KeyListIndex += 1;
if (this.KeyListIndex >= myBotConfig._ClassicKeyList.Length)
this.KeyListIndex = 0;
Logger.Log("BNetConnectionResponse received");
BnetServer.BnetConnectionResponse packet = args.Packet as BnetServer.BnetConnectionResponse;
this.ClientToken = (uint)Environment.TickCount;
MBNCSUtil.DataReader myReader = new MBNCSUtil.DataReader(packet.Data);
myReader.Seek(7);
this.ServerToken = myReader.ReadUInt32();
//send 0x51
BC.AuthorizationCheck myAUTH_CHECK = new data.BC.AuthorizationCheck(ClientToken, packet.VersionFileName
, packet.VersionFormulae, myBotConfig._GameFiles, myBotConfig._ClassicKeyList[KeyListIndex]
, myBotConfig._LodKeyList[KeyListIndex], ServerToken, myBotConfig._OwnerName);
Logger.Log("Sending AuthorizationCheck for cdkey #" + KeyListIndex);
Sleep(50);
myBnetServerClient.GetStream().Write(myAUTH_CHECK.Build(), 0, myAUTH_CHECK.Build().Length);
break;
case (int)D2Packets.BnetServerPacket.BnetAuthResponse:
//0x51
BnetServer.BnetAuthResponse packet2 = args.Packet as BnetServer.BnetAuthResponse;
if (packet2.Result == BnetServer.BnetAuthResult.Success)
{
Logger.Log("Battle.net authorization success");
Logger.Log("Logging onto account: " + myBotConfig._AccountName);
Sleep(10);
byte[] accinfo = new BC.AccountInfo(myBotConfig._AccountName,myBotConfig._AccountPassword,(int)this.ClientToken,(int)this.ServerToken).Build();
myBnetServerClient.GetStream().Write(accinfo, 0, accinfo.Length);
}
else
{
Logger.Log("Could not pass battle.net authorization check. Result: " + packet2.Result.ToString());
if (packet2.Result != BnetServer.BnetAuthResult.CDKeyInUse)
{
this.NeedIPReset = true;
Logger.Log("BAD CDKEY DETECTED AT INDEX " + this.KeyListIndex);
};
this.FatalDebugError = true;
}
break;
case (int) D2Packets.BnetServerPacket.BnetLogonResponse:
//0x3a
BnetServer.BnetLogonResponse packet3 = args.Packet as BnetServer.BnetLogonResponse;
if (packet3.Result == BnetServer.BnetLogonResult.Success)
{
Logger.Log("Account logon success");
//query a list of realms
MBNCSUtil.BncsPacket myQueryPacket = new MBNCSUtil.BncsPacket(0x40);
myBnetServerClient.GetStream().Write(myQueryPacket.GetData(), 0, myQueryPacket.GetData().Length);
}
else
{
Logger.Log("Could not log onto account. Result: " + packet3.Result);
this.FatalDebugError=true;
}
break;
case (int) D2Packets.BnetServerPacket.QueryRealmsResponse:
//0x40
BnetServer.QueryRealmsResponse packet4 = args.Packet as BnetServer.QueryRealmsResponse;
this.RealmServerString = packet4.Realms[0].Name;
//send a request for realm server logon
byte[] myRealmLogon = new data.BC.RealmLogon(this.RealmServerString,(int) this.ClientToken,(int) this.ServerToken).Build();
Sleep(50);
myBnetServerClient.GetStream().Write(myRealmLogon, 0, myRealmLogon.Length);
break;
case (int) D2Packets.BnetServerPacket.RealmLogonResponse :
//0x3e
BnetServer.RealmLogonResponse packet5 = args.Packet as BnetServer.RealmLogonResponse;
if (packet5.Result == BnetServer.RealmLogonResult.Success)
{
Logger.Log("Realm logon data received successfully");
RealmServerIp = packet5.RealmServerIP;
this.BNCSUniqueName = packet5.Username;
myRealmServerClient = new TcpClient();
myRealmServerClient.LingerState = new LingerOption(true, 0);
myRealmServerClient.Connect(RealmServerIp, 6112);
System.Threading.Thread.Sleep(10);
myRealmServerClient.GetStream().WriteByte(0x01);
System.Threading.Thread.Sleep(10);
byte[] tmpdata = new byte[packet5.Data.Length+1];
tmpdata[0] = 0x01;
for (int i = 0; i < packet5.Data.Length; i++)
tmpdata[i + 1] = packet5.Data[i];
RS.Logon alogon = new data.RS.Logon(tmpdata);
RSLogon = new RC.Logon(alogon).Build();
Sleep(50);
myRealmServerClient.GetStream().Write(RSLogon, 0, RSLogon.Length);
}
else
{
Logger.Log("Failed to log onto realm server");
this.FatalDebugError = true;
}
break;
default:
break;
}
}
///
/// Realmserver recv packets handler (event)
///
///
///
void onRealmPacketReceived(object sender, PacketEventArgs args)
{
switch (args.Packet.Data[0])
{
case (int) D2Packets.RealmServerPacket.RealmStartupResponse:
//0x01
RealmServer.RealmStartupResponse rsesponce = args.Packet as RealmServer.RealmStartupResponse;
if (rsesponce.Result == RealmServer.RealmStartupResult.Success)
{
Logger.Log("Realm logon success");
byte[] CharLogonPacket = new RC.CharacterLogon(myBotConfig._CharacterName).GetData();
System.Threading.Thread.Sleep(100);
myRealmServerClient.GetStream().Write(CharLogonPacket, 0, CharLogonPacket.Length);
this.LoginDone = true;
}
else
{
Logger.Log("Failed to log onto realm server. result: " + rsesponce.Result.ToString());
if (rsesponce.Result == RealmServer.RealmStartupResult.TemporaryIPBan)
this.NeedIPReset = true;
this.FatalDebugError = true;
}
break;
case (int)D2Packets.RealmServerPacket.JoinGameResponse:
//0x04
RealmServer.JoinGameResponse JoinResponse = args.Packet as RealmServer.JoinGameResponse;
Logger.Log("JoinGameResponse received. result: " + JoinResponse.Result.ToString());
if (JoinResponse == null)
{
Logger.Log("Failed to join game, Packet is null (WTF?)");
this.FailedToJoin = true;
return;
}
LastJoinResult = JoinResponse.Result;
if (JoinResponse.Result != RealmServer.JoinGameResult.Success)
{
Logger.Log("Failed to join game. Reason: " + JoinResponse.Result);
this.FailedToJoin = true;
if (JoinResponse.Result == RealmServer.JoinGameResult.GameDoesNotExist)
this.NeedIPReset = true;
return;
}
Logger.Log("Connecting to GameServer...");
myGameServerClient = new TcpClient();
myGameServerClient.LingerState = new LingerOption(true, 0);
if (myGameServerClient.Connected)
{
return;
}
myGameServerClient.Connect(JoinResponse.GameServerIP, 4000);
for (int c = 0; c < 1000 && !myGameServerClient.Connected; c++)
System.Threading.Thread.Sleep(1);
if (!myGameServerClient.Connected)
{
this.FailedToJoin = true;
Logger.Log("Failed to connect to gameserver");
return;
}
BC.NotifyJoin myNotifyJoin = new data.BC.NotifyJoin(CurrentGameName,CurrentGamePassword);
byte[] BSNotifyJoin = myNotifyJoin.GetData();//bug here retrieve like packet x10
try
{
myBnetServerClient.GetStream().Write(BSNotifyJoin, 0, BSNotifyJoin.Length);
}
catch
{
this.FailedToJoin = true;
return;
}
Sleep(50);
byte[] GCLogon = new GCp.GameLogon(JoinResponse.GameHash, JoinResponse.GameToken
, myBotConfig._CharacterClass, myBotConfig._CharacterName).GetData();
try
{
myGameServerClient.GetStream().Write(GCLogon, 0, GCLogon.Length);
}
catch
{
this.FailedToJoin = true;
return;
}
Sleep(50);
try
{
myGameServerClient.GetStream().WriteByte(0x6b);
}
catch
{
this.FailedToJoin = true;
return;
}
Sleep(50);
try
{
byte[] TestConnectionPacket = GameClient.SelectSkill.Build(D2Data.SkillType.Telekinesis,D2Data.SkillHand.Right);
myGameServerClient.GetStream().Write(TestConnectionPacket,0,TestConnectionPacket.Length);
}
catch
{
this.FailedToJoin = true;
return;
}
this.InGame = true;
break;
case (int)D2Packets.RealmServerPacket.CreateGameResponse:
//0x03
RealmServer.CreateGameResponse CreateResponse = args.Packet as RealmServer.CreateGameResponse;
if (CreateResponse.Result == RealmServer.CreateGameResult.Success)
{
JoinGame(CurrentGameNum, CurrentGameName, CurrentGamePassword);
}
else
{
Logger.Log("Failed to create game: " + CreateResponse.Result);
this.FatalDebugError = true;
}
break;
default:
break;
}
}
///
/// Convert a Server enum used in botconfig to a server string used in TcpClient.Connect()
///
///
///
string GetServerString(Server server)
{
switch (server)
{
case Server.Asia:
return "asia.battle.net";
case Server.Europe:
return "europe.battle.net";
case Server.UsEast:
return "useast.battle.net";
case Server.UsWest:
return "uswest.battle.net";
default:
throw new ApplicationException("GetServerString() unknown server passed to function");
}
}
///
/// Non-Blocking sleep method
///
///
void Sleep(int delay)
{
int StartTime = Environment.TickCount;
while (true)
{
System.Threading.Thread.Sleep(1);
if (Environment.TickCount - StartTime >= delay)
return;
}
}
///
/// This is more for debugging purpose. What is state IdentifyConnection?
///
///
///
void myPacketSniffer_OnConnectionChange(object sender, D2Packets.ConnectionChangeEventArgs args)
{
Logger.Log("----------- CONNECTION CHANGED -----------");
Logger.Log("CONNECTION STATE: " + args.Type);
Game.GameInfo.RST = true;
}
}
}