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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Core/LoginWindowState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ enum LoginWindowState
Selection,
Login,
Code,
MobileConfirmation,
Loading,
Success
}
Expand Down
49 changes: 49 additions & 0 deletions Core/WindowUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,10 @@ public static LoginWindowState GetLoginWindowState(WindowHandle loginWindow)
{
return LoginWindowState.Code;
}
else if (inputs.Count == 0 && buttons.Count == 0 && groups.Count == 0 && images.Count == 3 && texts.Count == 7)
{
return LoginWindowState.MobileConfirmation;
}
else if (inputs.Count == 2 && buttons.Count == 1)
{
return LoginWindowState.Login;
Expand Down Expand Up @@ -334,6 +338,51 @@ public static LoginWindowState HandleAccountSelection(WindowHandle loginWindow)
return LoginWindowState.Invalid;
}

public static LoginWindowState TryMobileToCodeSwitch(WindowHandle loginWindow)
{
using (var automation = new UIA3Automation())
{
try
{
AutomationElement window = automation.FromHandle(loginWindow.RawPtr);

window.Focus();

AutomationElement document = window.FindFirstDescendant(e => e.ByControlType(ControlType.Document));
AutomationElement[] children = document.FindAllChildren();

var texts = new List<AutomationElement>();

foreach (AutomationElement element in children)
{
switch (element.ControlType)
{
case ControlType.Text:
texts.Add(element);
break;
}
}

// Look for "Enter a code instead" text to click
foreach (var text in texts)
{
if (text.Name.ToLower().Contains("enter a code instead"))
{
text.AsButton().Invoke();
return LoginWindowState.Code;
}
}

}
catch (Exception e)
{
Console.WriteLine("Error switching from mobile confirmation to code entry: " + e.Message);
}
}

return LoginWindowState.Invalid;
}

public static LoginWindowState TryCredentialsEntry(WindowHandle loginWindow, string username, string password, bool remember)
{
using (var automation = new UIA3Automation())
Expand Down
128 changes: 123 additions & 5 deletions Views/AccountsWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1405,7 +1405,7 @@ private void EnterCredentials(Process steamProcess, Account account, int tryCoun
SetWindowTitle("Working");
LoginWindowState state = LoginWindowState.None;

while (state != LoginWindowState.Success && state != LoginWindowState.Code)
while (state != LoginWindowState.Success && state != LoginWindowState.Code && state != LoginWindowState.MobileConfirmation)
{
if (steamProcess.HasExited || state == LoginWindowState.Error)
{
Expand All @@ -1415,7 +1415,6 @@ private void EnterCredentials(Process steamProcess, Account account, int tryCoun
Thread.Sleep(100);

state = WindowUtils.GetLoginWindowState(steamLoginWindow);

if (state == LoginWindowState.Selection)
{
WindowUtils.HandleAccountSelection(steamLoginWindow);
Expand All @@ -1428,13 +1427,132 @@ private void EnterCredentials(Process steamProcess, Account account, int tryCoun
state = WindowUtils.TryCredentialsEntry(steamLoginWindow, account.Name, password, settings.User.RememberPassword);
}
}


// Check what type of authentication is required after credential entry
Thread.Sleep(3000); // Small delay to let the UI update
state = WindowUtils.GetLoginWindowState(steamLoginWindow);
string secret = StringCipher.Decrypt(account.SharedSecret, eKey);

if (secret != null && secret.Length > 0)
// Handle both Code and MobileConfirmation states
if ((state == LoginWindowState.Code || state == LoginWindowState.MobileConfirmation) && secret != null && secret.Length > 0)
{
// If it's mobile confirmation, try to switch to code entry first
if (state == LoginWindowState.MobileConfirmation)
{
Console.WriteLine("Mobile confirmation detected and shared secret available. Switching to 2FA code entry...");

// Try to switch from mobile confirmation to code entry
LoginWindowState switchResult = WindowUtils.TryMobileToCodeSwitch(steamLoginWindow);
Thread.Sleep(500);
if (switchResult == LoginWindowState.Code)
{
Console.WriteLine("Successfully switched to 2FA code entry mode");
state = LoginWindowState.Code;
}
else
{
Console.WriteLine("Failed to switch to code entry, falling back to manual mobile confirmation");
MessageBox.Show("Could not automatically switch to 2FA code entry. Please manually select 'Use code instead' or approve on your mobile device.",
"Info", MessageBoxButton.OK, MessageBoxImage.Information);

// Wait for mobile confirmation or timeout
int waitCounter = 0;
int maxWaitTime = 120000; // 2 minutes in milliseconds
int statusUpdateInterval = 15000; // Update status every 15 seconds
int lastStatusUpdate = 0;

while (state == LoginWindowState.MobileConfirmation && waitCounter < maxWaitTime)
{
if (steamProcess.HasExited)
{
return;
}

Thread.Sleep(1000);
waitCounter += 1000;

// Update status periodically
if (waitCounter - lastStatusUpdate >= statusUpdateInterval)
{
int remainingSeconds = (maxWaitTime - waitCounter) / 1000;
lastStatusUpdate = waitCounter;
}

state = WindowUtils.GetLoginWindowState(steamLoginWindow);

// Check if Steam client window is now available (login successful)
if (WindowUtils.GetMainSteamClientWindow(steamProcess).IsValid)
{
PostLogin();
return;
}
}

if (waitCounter >= maxWaitTime)
{
MessageBox.Show("Mobile confirmation timed out. Please try again.", "Timeout", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}

PostLogin();
return;
}
}

// Now handle 2FA code entry (either original Code state or switched from Mobile)
if (state == LoginWindowState.Code)
{
Console.WriteLine("Entering 2FA code...");
EnterReact2FA(steamProcess, account, tryCount);
}
}
// Handle mobile confirmation when no shared secret is available
else if (state == LoginWindowState.MobileConfirmation)
{
EnterReact2FA(steamProcess, account, tryCount);
Console.WriteLine("Mobile confirmation detected, but no shared secret configured. Please approve on mobile device.");

// Wait for mobile confirmation or timeout
int waitCounter = 0;
int maxWaitTime = 120000; // 2 minutes in milliseconds
int statusUpdateInterval = 15000; // Update status every 15 seconds
int lastStatusUpdate = 0;

while (state == LoginWindowState.MobileConfirmation && waitCounter < maxWaitTime)
{
if (steamProcess.HasExited)
{
return;
}

Thread.Sleep(1000);
waitCounter += 1000;

// Update status periodically
if (waitCounter - lastStatusUpdate >= statusUpdateInterval)
{
int remainingSeconds = (maxWaitTime - waitCounter) / 1000;
lastStatusUpdate = waitCounter;
}

state = WindowUtils.GetLoginWindowState(steamLoginWindow);

// Check if Steam client window is now available (login successful)
if (WindowUtils.GetMainSteamClientWindow(steamProcess).IsValid)
{
PostLogin();
return;
}
}

if (waitCounter >= maxWaitTime)
{
MessageBox.Show("Mobile confirmation timed out. Please try again.", "Timeout", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}

PostLogin();
}
// Handle case where no 2FA is configured or login completed
else
{
Thread.Sleep(settings.User.SleepTime);
Expand Down