diff --git a/Core/LoginWindowState.cs b/Core/LoginWindowState.cs index c5e96c3..f486be5 100644 --- a/Core/LoginWindowState.cs +++ b/Core/LoginWindowState.cs @@ -8,6 +8,7 @@ enum LoginWindowState Selection, Login, Code, + MobileConfirmation, Loading, Success } diff --git a/Core/WindowUtils.cs b/Core/WindowUtils.cs index c5fcc1f..8fb8404 100644 --- a/Core/WindowUtils.cs +++ b/Core/WindowUtils.cs @@ -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; @@ -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(); + + 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()) diff --git a/Views/AccountsWindow.xaml.cs b/Views/AccountsWindow.xaml.cs index f971711..bc041b7 100644 --- a/Views/AccountsWindow.xaml.cs +++ b/Views/AccountsWindow.xaml.cs @@ -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) { @@ -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); @@ -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);