using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using JXHelper.Common; using JXHelper.Config; using JXHelper.Platform; namespace JXHelper.Core; /// /// Động cơ tự động đăng nhập từ A-Z cho Võ Lâm Truyền Kỳ 1 (SSoT AutoLoginEngine). /// Xử lý tuần tự: Màn hình Chào (Bắt đầu trò chơi) -> Hộp thoại Đăng nhập (Username/Password) /// -> Xác nhận Đăng nhập -> Chọn Nhân Vật (Slot 1, 2, 3) -> Vào trò chơi. /// Tuân thủ nghiêm ngặt bất biến: INV-BG-INPUT-NO-HIJACK (PostMessage ngầm 100%). /// public static class AutoLoginEngine { public static event Action? OnLoginLog; // Tọa độ chuẩn trên Canvas 800x600 của Client Võ Lâm 1 // Màn hình Chào (Vo Lam Truyen Ky) public const int TitleStartGameX = 400; public const int TitleStartGameY = 385; // Hộp thoại Nhắc nhở public const int NoticeConfirmButtonX = 400; public const int NoticeConfirmButtonY = 440; // Hộp thoại Lựa chọn máy chủ public const int ServerVctkLeftX = 208; public const int ServerVctkLeftY = 195; public const int ServerTronVenRightX = 610; public const int ServerTronVenRightY = 209; public const int ServerConfirmButtonX = 260; public const int ServerConfirmButtonY = 503; // Hộp thoại Đăng nhập (Xác nhận tên tài khoản) public const int LoginUsernameInputX = 400; public const int LoginUsernameInputY = 302; public const int LoginPasswordInputX = 400; public const int LoginPasswordInputY = 340; public const int LoginSubmitButtonX = 320; public const int LoginSubmitButtonY = 418; // Màn hình Chọn nhân vật public const int CharSlot1X = 250; // Trái public const int CharSlot2X = 400; // Giữa public const int CharSlot3X = 550; // Phải public const int CharSlotY = 350; // Nút "Xác định" vào game tại màn hình Chọn nhân vật public const int EnterGameButtonX = 92; public const int EnterGameButtonY = 561; /// /// Thực thi chuỗi tự động đăng nhập ngầm vào cửa sổ game chỉ định. /// Tuân thủ quy trình chuẩn: Chào -> Nhắc nhở -> Chọn Máy Chủ -> Đăng Nhập -> Chọn Nhân Vật -> Vào Game. /// public static async Task ExecuteAutoLoginAsync(IntPtr hWnd, AccountEntry account, Action? logCallback = null) { void Log(string msg) { TelemetryLogger.LogAction($"[AUTOLOGIN] {msg}"); logCallback?.Invoke(msg); OnLoginLog?.Invoke(msg); } if (hWnd == IntPtr.Zero || !NativeMethods.IsWindow(hWnd)) { Log("Lỗi: Handle cửa sổ game không hợp lệ."); return false; } Log($"Bắt đầu chu trình tự động đăng nhập cho '{account.Username}' (Nhân vật: {account.CharacterName}, Slot: {account.CharacterSlot})..."); // Khôi phục cửa sổ nếu đang bị thu nhỏ và kích hoạt lên foreground WindowManager.RestoreIfMinimized(hWnd); NativeMethods.SetForegroundWindow(hWnd); NativeMethods.PostMessage(hWnd, 0x0006 /* WM_ACTIVATE */, (IntPtr)1, IntPtr.Zero); NativeMethods.PostMessage(hWnd, 0x0007 /* WM_SETFOCUS */, IntPtr.Zero, IntPtr.Zero); await Task.Delay(300); // 1. Kích hoạt 'Bắt đầu trò chơi' tại màn hình Chào Log("Bước 1/5: Click 'Bắt đầu trò chơi'..."); await BackgroundInput.SendLeftClickAsync(hWnd, TitleStartGameX, TitleStartGameY, 60); await Task.Delay(100); await BackgroundInput.SendKeyAsync(hWnd, Constants.VK_RETURN, 60); await Task.Delay(1500); // Lượt kích hoạt dự phòng để đảm bảo 100% vượt qua màn hình Chào await BackgroundInput.SendLeftClickAsync(hWnd, TitleStartGameX, TitleStartGameY, 60); await Task.Delay(100); await BackgroundInput.SendKeyAsync(hWnd, Constants.VK_RETURN, 60); await Task.Delay(1500); // 2. Xác nhận popup Nhắc nhở (nếu có) Log("Bước 2/5: Xác nhận popup Nhắc nhở (nếu có)..."); await BackgroundInput.SendLeftClickAsync(hWnd, NoticeConfirmButtonX, NoticeConfirmButtonY, 60); await Task.Delay(100); await BackgroundInput.SendKeyAsync(hWnd, Constants.VK_RETURN, 50); await Task.Delay(1500); // 3. Lựa chọn máy chủ (Võ Công Truyền Kỳ -> Trọn Vẹn Chữ Tình) Log("Bước 3/5: Chọn cụm 'Võ Công Truyền Kỳ' và máy chủ 'Trọn Vẹn Chữ Tình'..."); // Di chuyển xuống 'Võ Công Truyền Kỳ' bằng VK_DOWN await BackgroundInput.SendKeyAsync(hWnd, 0x28 /* VK_DOWN */, 50); await Task.Delay(300); await BackgroundInput.SendLeftClickAsync(hWnd, ServerVctkLeftX, ServerVctkLeftY, 60); await Task.Delay(200); // Chọn server bên phải await BackgroundInput.SendLeftClickAsync(hWnd, ServerTronVenRightX, ServerTronVenRightY, 60); await Task.Delay(200); await BackgroundInput.SendLeftDoubleClickAsync(hWnd, ServerTronVenRightX, ServerTronVenRightY); await Task.Delay(300); // Nhấn Xác định máy chủ (click và Enter để mở hộp thoại đăng nhập) await BackgroundInput.SendLeftClickAsync(hWnd, ServerConfirmButtonX, ServerConfirmButtonY, 80); await Task.Delay(100); await BackgroundInput.SendKeyAsync(hWnd, Constants.VK_RETURN, 50); await Task.Delay(2500); // 4. Nhập thông tin tài khoản & mật khẩu Log($"Bước 4/5: Nhập tài khoản '{account.Username}' và mật khẩu..."); await BackgroundInput.SendKeyAsync(hWnd, 0x09 /* VK_TAB */, 30); await Task.Delay(100); await BackgroundInput.SendLeftClickAsync(hWnd, LoginUsernameInputX, LoginUsernameInputY, 60); await Task.Delay(100); // Xóa sạch text cũ await BackgroundInput.SendKeyAsync(hWnd, 0x23 /* VK_END */, 20); for (int i = 0; i < 25; i++) { await BackgroundInput.SendKeyAsync(hWnd, Constants.VK_BACK, 10); } await BackgroundInput.SendKeyAsync(hWnd, 0x24 /* VK_HOME */, 20); for (int i = 0; i < 25; i++) { await BackgroundInput.SendKeyAsync(hWnd, 0x2E /* VK_DELETE */, 10); } await Task.Delay(100); await BackgroundInput.SendTextAsync(hWnd, account.Username, charDelayMs: 25); await Task.Delay(150); // Chuyển sang ô mật khẩu bằng TAB và click await BackgroundInput.SendKeyAsync(hWnd, 0x09 /* VK_TAB */, 30); await Task.Delay(100); await BackgroundInput.SendLeftClickAsync(hWnd, LoginPasswordInputX, LoginPasswordInputY, 60); await Task.Delay(100); await BackgroundInput.SendKeyAsync(hWnd, 0x23 /* VK_END */, 20); for (int i = 0; i < 25; i++) { await BackgroundInput.SendKeyAsync(hWnd, Constants.VK_BACK, 10); } await BackgroundInput.SendKeyAsync(hWnd, 0x24 /* VK_HOME */, 20); for (int i = 0; i < 25; i++) { await BackgroundInput.SendKeyAsync(hWnd, 0x2E /* VK_DELETE */, 10); } await Task.Delay(100); if (!string.IsNullOrEmpty(account.Password)) { await BackgroundInput.SendTextAsync(hWnd, account.Password, charDelayMs: 25); await Task.Delay(150); } // Gửi lệnh đăng nhập tại nút 'Xác định' Log("Gửi lệnh xác nhận đăng nhập..."); await BackgroundInput.SendLeftClickAsync(hWnd, LoginSubmitButtonX, LoginSubmitButtonY, 60); await Task.Delay(100); await BackgroundInput.SendKeyAsync(hWnd, Constants.VK_RETURN, 60); await Task.Delay(4000); // 5. Chọn nhân vật và vào game int slotX = account.CharacterSlot switch { 2 => CharSlot2X, // Slot 2: Giữa 3 => CharSlot3X, // Slot 3: Phải _ => CharSlot1X // Slot 1: Trái (mặc định) }; Log($"Bước 5/5: Chọn nhân vật Slot {account.CharacterSlot} tại ({slotX}, {CharSlotY}) và vào game..."); await BackgroundInput.SendLeftClickAsync(hWnd, slotX, CharSlotY, 60); await Task.Delay(200); await BackgroundInput.SendLeftDoubleClickAsync(hWnd, slotX, CharSlotY); await Task.Delay(300); await BackgroundInput.SendLeftClickAsync(hWnd, EnterGameButtonX, EnterGameButtonY, 60); await Task.Delay(100); await BackgroundInput.SendKeyAsync(hWnd, Constants.VK_RETURN, 50); await Task.Delay(4000); // Đóng popup Hoạt động hàng ngày nếu có await BackgroundInput.SendLeftClickAsync(hWnd, 300, 570, 50); await Task.Delay(200); await BackgroundInput.SendKeyAsync(hWnd, Constants.VK_ESCAPE, 40); Log($"[THÀNH CÔNG] Hoàn tất chu trình đăng nhập '{account.Username}' ({account.CharacterName})!"); return true; } /// /// Khởi chạy Client mới và tự động chạy chu trình đăng nhập từ A-Z. /// Tự động phát hiện đúng Handle của cửa sổ game mới được tạo ra. /// public static async Task LaunchAndAutoLoginAsync(AccountEntry account, Action? logCallback = null) { void Log(string msg) { TelemetryLogger.LogAction($"[AUTOLAUNCH] {msg}"); logCallback?.Invoke(msg); OnLoginLog?.Invoke(msg); } // 1. Ghi nhận danh sách HWND game hiện hữu trước khi khởi chạy var beforeHwnds = WindowManager.FindGameWindows().Select(w => w.Handle).ToHashSet(); Log($"Đang khởi chạy tiến trình Game client cho '{account.Username}'..."); bool launched = GameLauncher.LaunchGameClient(account); if (!launched) { Log("Lỗi: Không thể khởi chạy Game.exe!"); return false; } // 2. Chờ phát hiện cửa sổ game mới xuất hiện (quét nhanh 200ms, timeout 30s) Log("Đang quét tìm cửa sổ Game mới..."); IntPtr targetHwnd = IntPtr.Zero; for (int i = 0; i < 175; i++) { await Task.Delay(200); // Tự động kiểm tra và đóng popup "Mở Auto" / click "Bắt đầu" nếu có cửa sổ Launcher DismissLauncherModalsAndClickStart(); var currentWins = WindowManager.FindGameWindows(); var newWin = currentWins.FirstOrDefault(w => !beforeHwnds.Contains(w.Handle)); if (newWin != null && newWin.Handle != IntPtr.Zero) { targetHwnd = newWin.Handle; Log($"Đã phát hiện cửa sổ Game mới: HWND 0x{targetHwnd.ToInt64():X8} (PID: {newWin.ProcessId})"); break; } } if (targetHwnd == IntPtr.Zero) { // Chỉ fallback vào cửa sổ đang rỗi (chưa vào game), TUYỆT ĐỐI CẤM gửi lệnh login vào cửa sổ đang chơi (INV-DUAL-FACTOR-INGAME) var idleWin = WindowManager.FindGameWindows().FirstOrDefault(w => w.IsIdle); if (idleWin != null && idleWin.Handle != IntPtr.Zero) { targetHwnd = idleWin.Handle; Log($"[FALLBACK] Sử dụng cửa sổ rỗi: HWND 0x{targetHwnd.ToInt64():X8} (PID: {idleWin.ProcessId})"); } else { Log("Lỗi: Không tìm thấy cửa sổ game mới hoặc cửa sổ rỗi nào sau khi khởi chạy!"); return false; } } // 3. Chờ 3500ms để màn hình Chào tải xong đồ họa DirectX và sẵn sàng nhận lệnh Log("Chờ màn hình DirectX nạp tài nguyên đồ họa (3.5s)..."); await Task.Delay(3500); // 4. Tiến hành chu trình tự động đăng nhập siêu tốc return await ExecuteAutoLoginAsync(targetHwnd, account, logCallback); } /// /// Tự động đóng modal "Mở Auto" và gửi lệnh click "Bắt đầu" (Button 1404) nếu cửa sổ Launcher AutoUpdateVCTK đang hiển thị. /// public static void DismissLauncherModalsAndClickStart() { NativeMethods.EnumWindows((hWnd, lParam) => { if (!NativeMethods.IsWindow(hWnd) || !NativeMethods.IsWindowVisible(hWnd)) return true; NativeMethods.GetWindowThreadProcessId(hWnd, out uint pid); string procName = ""; try { using var proc = System.Diagnostics.Process.GetProcessById((int)pid); procName = proc.ProcessName; } catch {} var sbClass = new System.Text.StringBuilder(256); NativeMethods.GetClassName(hWnd, sbClass, sbClass.Capacity); string cls = sbClass.ToString(); int len = NativeMethods.GetWindowTextLength(hWnd); var sbTitle = new System.Text.StringBuilder(len + 1); NativeMethods.GetWindowText(hWnd, sbTitle, sbTitle.Capacity); string title = sbTitle.ToString(); bool isLauncher = procName.Contains("AutoUpdate", StringComparison.OrdinalIgnoreCase) || title.Contains("AutoUpdate", StringComparison.OrdinalIgnoreCase) || title.Contains("Võ Công Truyền Kỳ", StringComparison.OrdinalIgnoreCase); if (isLauncher) { // Nếu là popup modal "Mở Auto" của launcher if (title.Contains("Mở Auto", StringComparison.OrdinalIgnoreCase) || cls == "#32770" || title.Contains("Auto", StringComparison.OrdinalIgnoreCase)) { NativeMethods.PostMessage(hWnd, 0x0010 /* WM_CLOSE */, IntPtr.Zero, IntPtr.Zero); } else { // Là cửa sổ chính của AutoUpdateVCTK -> Kích hoạt nút Bắt đầu (Control ID 1404) NativeMethods.EnumChildWindows(hWnd, (ch, _) => { int cid = NativeMethods.GetDlgCtrlID(ch); if (cid == 1404) { NativeMethods.PostMessage(ch, 0x00F5 /* BM_CLICK */, IntPtr.Zero, IntPtr.Zero); NativeMethods.PostMessage(hWnd, 0x0111 /* WM_COMMAND */, (IntPtr)1404, ch); return false; } return true; }, IntPtr.Zero); // Fallback click tọa độ "Bắt đầu" (815, 550) và (767, 554) IntPtr lp = NativeMethods.MakeLParam(815, 550); NativeMethods.PostMessage(hWnd, Constants.WM_LBUTTONDOWN, Constants.MK_LBUTTON, lp); NativeMethods.PostMessage(hWnd, Constants.WM_LBUTTONUP, IntPtr.Zero, lp); IntPtr lp2 = NativeMethods.MakeLParam(767, 554); NativeMethods.PostMessage(hWnd, Constants.WM_LBUTTONDOWN, Constants.MK_LBUTTON, lp2); NativeMethods.PostMessage(hWnd, Constants.WM_LBUTTONUP, IntPtr.Zero, lp2); } } return true; }, IntPtr.Zero); } /// /// Đối soát trạng thái giữa danh sách tài khoản cấu hình và các cửa sổ game thực tế đang chạy. /// public static SmartReconcileReport ReconcileAccounts(List? accounts = null) { accounts ??= AccountStorage.LoadAccounts(); var windows = WindowManager.FindGameWindows(); var assignedHandles = new HashSet(); var statuses = new List(); foreach (var acc in accounts) { var matchWin = windows.FirstOrDefault(w => w.IsInGame && !assignedHandles.Contains(w.Handle) && string.Equals(w.CharacterName, acc.CharacterName, StringComparison.OrdinalIgnoreCase)); if (matchWin != null) { assignedHandles.Add(matchWin.Handle); statuses.Add(new AccountReconcileStatus(acc, true, matchWin)); } else { statuses.Add(new AccountReconcileStatus(acc, false, null)); } } // Cửa sổ rỗi: Không có nhân vật online nào hoặc chưa được gán var idleWindows = windows.Where(w => w.IsIdle && !assignedHandles.Contains(w.Handle)).ToList(); int totalOnline = statuses.Count(s => s.IsOnline); int totalPending = statuses.Count(s => !s.IsOnline && s.Account.IsEnabled); return new SmartReconcileReport(statuses, idleWindows, totalOnline, totalPending); } /// /// Đăng nhập thông minh 1-Click Zero-Friction: /// - Tự nhận diện tài khoản đã lưu. /// - Quét xem có PID nào đang chạy mà không log nhân vật -> Tự động log bù ngay lập tức (KHÔNG mở thêm cửa sổ!). /// - Nếu không còn cửa sổ rỗi nào -> Tự động khởi chạy client mới và đăng nhập. /// public static async Task SmartLoginAccountAsync(AccountEntry account, Action? logCallback = null) { void Log(string msg) { TelemetryLogger.LogAction($"[SMART LOGIN] {msg}"); logCallback?.Invoke(msg); OnLoginLog?.Invoke(msg); } var report = ReconcileAccounts(); var currentStatus = report.AccountStatuses.FirstOrDefault(s => s.Account.Id == account.Id || string.Equals(s.Account.CharacterName, account.CharacterName, StringComparison.OrdinalIgnoreCase)); if (currentStatus != null && currentStatus.IsOnline && currentStatus.WindowInfo != null) { Log($"Nhân vật '{account.CharacterName}' đã online tại PID {currentStatus.WindowInfo.ProcessId}! Bỏ qua đăng nhập."); return true; } // 1. Kiểm tra xem có cửa sổ rỗi đang chạy không -> Log bù ngay lập tức! if (report.IdleWindows.Count > 0) { var idleWin = report.IdleWindows[0]; Log($"Phát hiện cửa sổ rỗi PID {idleWin.ProcessId} (HWND 0x{idleWin.Handle.ToInt64():X8}) -> Đang log bù '{account.CharacterName}' ({account.Username})..."); WindowManager.RestoreIfMinimized(idleWin.Handle); return await ExecuteAutoLoginAsync(idleWin.Handle, account, logCallback); } // 2. Không còn cửa sổ rỗi -> Tự động mở client mới và đăng nhập Log($"Không có cửa sổ trống -> Đang khởi chạy client mới cho '{account.CharacterName}' ({account.Username})..."); return await LaunchAndAutoLoginAsync(account, logCallback); } /// /// Đối soát và đăng nhập thông minh tự động cho toàn bộ tài khoản chưa online (Smart Batch Login): /// Ưu tiên log bù vào toàn bộ cửa sổ rỗi hiện có trước, sau đó mới mở thêm client cho số còn lại. /// public static async Task SmartReconcileAllAccountsAsync(Action? logCallback = null) { void Log(string msg) { TelemetryLogger.LogAction($"[SMART BATCH] {msg}"); logCallback?.Invoke(msg); OnLoginLog?.Invoke(msg); } var accounts = AccountStorage.LoadAccounts().Where(a => a.IsEnabled).ToList(); if (accounts.Count == 0) { Log("Không tìm thấy tài khoản nào được kích hoạt trong hệ thống!"); return 0; } var report = ReconcileAccounts(accounts); Log($"Bắt đầu đối soát tự động: {report.TotalOnline}/{accounts.Count} tài khoản đã online, {report.IdleWindows.Count} cửa sổ rỗi, {report.TotalPending} tài khoản chờ log."); if (report.TotalPending == 0) { Log("Tất cả tài khoản đã online đầy đủ! Không cần đăng nhập thêm."); return 0; } int successCount = 0; var pendingAccounts = report.AccountStatuses .Where(s => !s.IsOnline && s.Account.IsEnabled) .Select(s => s.Account) .ToList(); int idleIndex = 0; foreach (var acc in pendingAccounts) { Log($"Tiến hành tự động đăng nhập cho '{acc.CharacterName}' ({acc.Username})..."); bool ok = false; if (idleIndex < report.IdleWindows.Count) { var idleWin = report.IdleWindows[idleIndex++]; Log($"Phát hiện cửa sổ rỗi PID {idleWin.ProcessId} (HWND 0x{idleWin.Handle.ToInt64():X8}) -> Đang log bù '{acc.CharacterName}' ({acc.Username})..."); WindowManager.RestoreIfMinimized(idleWin.Handle); ok = await ExecuteAutoLoginAsync(idleWin.Handle, acc, logCallback); } else { Log($"Không còn cửa sổ rỗi -> Khởi chạy client mới cho '{acc.CharacterName}' ({acc.Username})..."); ok = await LaunchAndAutoLoginAsync(acc, logCallback); } if (ok) { successCount++; Log($"[THÀNH CÔNG] Đã hoàn tất đăng nhập cho '{acc.CharacterName}'!"); } else { Log($"[THẤT BẠI] Không thể đăng nhập cho '{acc.CharacterName}'. Tiếp tục tài khoản kế tiếp..."); } // Chờ 800ms để chuyển sang tài khoản tiếp theo await Task.Delay(800); } Log($"Hoàn tất chu trình đối soát thông minh: {successCount}/{pendingAccounts.Count} tài khoản đã được đăng nhập thành công."); return successCount; } } /// /// Trạng thái đối soát của 1 tài khoản đối với tiến trình cửa sổ game thực tế. /// public record AccountReconcileStatus( AccountEntry Account, bool IsOnline, GameWindowInfo? WindowInfo ); /// /// Báo cáo tổng hợp đối soát toàn bộ tài khoản và cửa sổ game (SSoT Reconciliation Report). /// public record SmartReconcileReport( List AccountStatuses, List IdleWindows, int TotalOnline, int TotalPending );