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
);