I was looking everywhere how to implement proper auto-login but didn't find anything except primitive Auto-IT scripts so I decided the best place to keep this information is here.
On the beginning a bit theory. WoW has special "GlueXML" interface for all out-of-the-game stuff. It can be extracted out of patch.mpq with WinMPQ. All logic for login, character selection and other can be found there. Only 1 interface I don't have idea what it about is "SecurityMatrix".
Now go back to auto-login. You need lua module injected into wow to use GlueXML. All code below in C# but my skills in this language is not quite good so if you have more elegant solution post it here.
First define state machine. The currently known login states are next:
Code:
// Login States
static Array LoginState = Array.CreateInstance( typeof(String), 11);
static Login()
{
LoginState.SetValue("login", 0); // "AccountLogin";
LoginState.SetValue("charselect", 1); // "CharacterSelect";
LoginState.SetValue("realmwizard", 2); // "RealmWizard";
LoginState.SetValue("charcreate", 3); // "CharacterCreate";
LoginState.SetValue("patchdownload", 4); // "PatchDownload";
LoginState.SetValue("trialconvert", 5); // "TrialConvert";
LoginState.SetValue("movie", 6); // "MovieFrame";
LoginState.SetValue("credits", 7); // "CreditsFrame";
LoginState.SetValue("options", 8); // "OptionsFrame";
LoginState.SetValue("tos", 9); // "TOS Dialog";
LoginState.SetValue("eula", 10); // EULA
LoginState.SetValue("disconnect", 11); // EULA
}
other states is
-1 - Failure/Error
100 - Waiting
101 - Retry (Networking problem)
999 - Success
How WoW switches state we don't know so on the beginning first we do is defining current state
Code:
/// <summary>
/// Execute Auto-Login
/// </summary>
/// <param name="realm">Realm Name</param>
/// <param name="account">WoW account</param>
/// <param name="pwd">Account password</param>
/// <param name="name">Character name</param>
/// <param name="retry"># of retries (if networking problems)</param>
/// <returns>TRUE if succeed and FALSE if not</returns>
public static bool AutoLogin(string realm, string account, string pwd, string name, int retry)
{
int RetryCount = 0;
do
{
if (SetGlueState() == -1)
return false;
....
} while (!((State == 999) || (RetryCount > retry)));
function to set&get current state is next:
Code:
/// <summary>
/// Check current Glue window and set login state
/// </summary>
/// <returns>Login State ID or -1 if Glue Window not found or state unknown</returns>
static int SetGlueState()
{
Lua_DoString(@"(function()
local d1, d2, d3
if (GlueDialog:IsShown()) then
d1 = GlueDialog.which
d2 = GlueDialogText:GetText()
if (GlueDialogHTML:IsShown()) then
d3 = 'html'
end
else
if (TOSFrame:IsShown()) then
d1 = string.lower(TOSFrame.noticeType)
local scrollbar = _G[TOSFrame.noticeType .. 'ScrollFrameScrollBar'];
if (scrollbar:IsShown()) then
local min, max = scrollbar:GetMinMaxValues()
scrollbar:SetValue(max)
end
end
end
return CURRENT_GLUE_SCREEN, CURRENT_GLUE_PENDING, d1, d2, d3, IsConnectedToServer()
end)()");
CurrentGlueScreen = Lua_GetLocalizedText(0);
string PendingScreen = Lua_GetLocalizedText(1);
CurrentGlueDialog = Lua_GetLocalizedText(2);
string DialogText = Lua_GetLocalizedText(3);
string d3 = Lua_GetLocalizedText(4);
string Connected = Lua_GetLocalizedText(5);
bool IsDialogText = (!((DialogText == null) || DialogText.Equals("")));
bool IsHtml = ((d3 != null) && !d3.Equals(""));
As you see I'm also analyzing TOS frame but I'll return to EULA\TOS later
I saw some logic in GlueXML to fade screens but never triggered this. But just in case
Code:
if (!((PendingScreen == null) || PendingScreen.Equals("")))
{
Log("'" + PendingScreen + "' coming ...");
return SetState(100);
}
If CurrentGlueScreen not set means we not on login screen
Code:
if (CurrentGlueScreen == null)
{
Log("Not on login page");
// not on login page
return -1;
}
CurrentGlueScreen name must corresponds to our state
Code:
int idx = Array.IndexOf(LoginState, CurrentGlueScreen);
if (idx < 0)
{
Log("Unknown GlueScreen '" + CurrentGlueScreen + "'");
return -1;
}
SetState(idx);
That's not enough. GlueScreen might have some dialogs shown (TOS, EULA, realm selection) so checking for it too
Code:
// Check for dialog
if ((CurrentGlueDialog == null) || CurrentGlueDialog.Equals(""))
{
// we done, no dialog
return State;
}
// Analyze dialogs
// TOS & EULA & realm select shown in dialog
int idy = Array.IndexOf(LoginState, CurrentGlueDialog);
if (idy >= 0)
{
return SetState(idy);
}
Other dialog on GlueScreen can show login progress, connection error, Blizz error (wrong accont/passwor, ban etc) so checking for it
Code:
// Check for progress dialog
if (CurrentGlueDialog.Equals("CANCEL")) {
// Current state in progress
string s = "Current state in progress";
if (IsDialogText)
s += DialogText;
s += " ...";
Log(s);
return SetState(100);
}
// Check for disconnection
if (CurrentGlueDialog.Equals("DISCONNECT"))
return SetState(99);
// Check for Blizz message
if (IsHtml) {
if (CurrentGlueDialog.Equals("CONNECTION_HELP_HTML") && IsHtml)
{
Log("Network problem. Retrying in 10 sec");
// Connection problem
SendKeys(CommandManager.SK_ESC);
return SetState(101);
} else {
Log("Received Blizz. message. Interrupting login");
return -1;
}
}
"CANCEL" dialog handles all action-in progress so we need wait (but not long) until wow make changes on the screen.
I can't find at the moment how to read Blizz html error to better handle this process but except "CONNECTION_HELP_HTML" dialog
the rest means we can't login even if we retry. If you can find how to get that text let me know.
If nothing matches conditions above, but some dialog shown, log it and also wait
Code:
if (IsDialogText)
{
Log("received " + DialogText);
return SetState(100);
}
and at the end if we find that something not implemented than interrupt login
Code:
// If we still here than something wrong
Log(string.Format(@"Unknow state detected for GlueScreen: {0};
GlueDialog: {1}", CurrentGlueScreen, CurrentGlueDialog));
return -1;
That's pretty much all about wow login state machine. Realm selection not implemented yet (it has it's own dialog)
Now define action for each state
"login" - Fill out fields and start login
Code:
case 0: // Login
SendLogin(realm, account, pwd);
break;
............
/// <summary>
/// Sending account/password
/// </summary>
/// <param name="realm">Realm name</param>
/// <param name="user">Account</param>
/// <param name="pwd">Password</param>
static void SendLogin(string realm, string user, string pwd)
{
string realm_cmd = "";
if (!realm.Equals(""))
realm_cmd = string.Format(@"local realm = AccountLoginRealmName:SetText(serverName);
if (realm != {0})
AccountLoginRealmName:SetText({0})", realm);
Lua_DoString(string.Format(@"(function()
{0}
AccountLoginAccountEdit:SetText('{1}')
AccountLoginPasswordEdit:SetText('{2}')
DefaultServerLogin(AccountLoginAccountEdit:GetText(), AccountLoginPasswordEdit:GetText())
end)()", realm_cmd, user, pwd));
// Wait
Thread.Sleep(1000);
}
"character selection". It might not be in list so need anylyze returning result
Code:
case 1: // Character Selection
int idx = SelectCharacter(name);
if (idx > 0)
{
Log("Found " + name + " as id:" + idx);
SendKeys(CommandManager.SK_ENTER);
// We done. World loading
State = 999;
}
else if (idx == -1)
{
Log("Character '" + name +
"' not found in list for realm '" + realm + "'");
return false;
}
else
return false;
break;
/// <summary>
/// Selecting character
/// </summary>
/// <param name="name">Character name</param>
/// <returns>Character index or -1 if character not found on realm</returns>
static int SelectCharacter(string name)
{
Lua_DoString(string.Format(@"(function()
local found = nill
local numChars = GetNumCharacters();
for i=1, numChars, 1 do
local name = GetCharacterInfo(i);
if (name == '{0}') then
found = i
break
end
end
if (found) then
CharacterSelect_SelectCharacter(found)
end
return found
end)()", name));
string idx = Lua_GetLocalizedText(0);
if ((idx == null) || idx.Equals(""))
{
Log("Unable found character name '" + name + "'");
return -1;
} else
return Convert.ToInt32(idx);
}
"movie" - just hit ESC
Code:
case 6: // Movie
// Ohhh, cmon
SendKeys(SK_ESC);
break;
"tos", "eula". Here I want return to SetGlueScreen. TOS & EULA displayed in same TOSFrame but with different title.
When that dialogs detected on the Glue Screen I do scroll down to enable "Accept" buttton
and in next iteration execute button click. Can be done differently but I've chosen human-like approach
Code:
case 9: // tos
case 10: // eula
Lua_DoString(string.Format(
@"(function()
Accept{0}()
TOSFrame:Hide()
TOSNotice:Hide()
AccountLogin_ShowUserAgreements()
end)()", CurrentGlueDialog.ToUpper()));
break;
"disconnect" - hit ESC and try again
Code:
case 11: // disconnect
SendKeys(CommandManager.SK_ESC);
break;
"waiting" - keep checkig that we don't wait too long. If process stack hit ESC and retry
Code:
case 100: // Pending
if (DateTime.Now.Millisecond - StateChangeTime.Millisecond <= MaxScreenWaitTime) {
// Cancel current process and retry
RetryCount++;
SendKeys(CommandManager.SK_ESC);
Thread.Sleep(10000);
StateChangeTime = DateTime.Now;
} else
Thread.Sleep(1000);
break;
"retry" (networking problem) - increase retry count, wait a bit and go again
Code:
case 101: // Retry
RetryCount++;
Thread.Sleep(10000);
StateChangeTime = DateTime.Now;
break;
exit if state unknown
Code:
default:
Log("'" + LoginState.GetValue(State) + "' not implemented yet");
return false;
"realm wizard", "realm selection", patch download (with apply process) not implemented yet.
For non-lua implementetion only SetGlueState and SelectCharacter needs to be re-implemented.Actually, if anyone have solution where to find parameters above in memory post it here. Could be interesting try same but without lua.
To handle movie/tos/eula is simply set parameters below in WTF\config.wtf before start WoW.exe
SET readEULA "1"
SET readTOS "1"
Last used realm/account can be also set in this file so for login just enough send password + ENTER
The full source code can be found in babbot project - https://sourceforge.net/projects/babbot/ and everyone also welcome to join