using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Text; using JXHelper.Core; namespace JXHelper.Platform; public record GameWindowInfo( IntPtr Handle, int ProcessId, string ProcessName, string Title, bool IsMinimized = false, string CharacterName = "", bool IsInGame = false) { public bool IsIdle => !IsInGame || string.IsNullOrEmpty(CharacterName); public string DisplayName => IsInGame && !string.IsNullOrEmpty(CharacterName) ? CharacterName + (IsMinimized ? " [Thu nhỏ]" : "") : $"[Chờ log - PID: {ProcessId}] {(!string.IsNullOrEmpty(Title) ? Title : ProcessName)}" + (IsMinimized ? " [Thu nhỏ]" : ""); public override string ToString() => DisplayName; } /// /// Quản lý tìm kiếm, lọc và xác thực cửa sổ game JX. /// Tuân thủ bất biến: INV-TWO-FACTOR-WINDOW. /// public static class WindowManager { private static readonly HashSet KnownProcessNames = new(StringComparer.OrdinalIgnoreCase) { "vggame", "game64", "game", "game_y", "game_r", "game_film", "so2game", "jxonline", "jx", "volam", "swordman" }; private static readonly HashSet BlacklistedProcessNames = new(StringComparer.OrdinalIgnoreCase) { "antigravity", "devenv", "code", "chrome", "msedge", "firefox", "opera", "brave", "jxhelper", "jxhelper_runtime", "taskmgr", "explorer", "ultraviewer", "ultraviewer_desktop" }; public static List FindGameWindows() { List result = new(); var t = new System.Threading.Thread(() => { try { IntPtr hDesk = NativeMethods.OpenInputDesktop(0, false, 0x01FF); if (hDesk != IntPtr.Zero) { NativeMethods.SetThreadDesktop(hDesk); } } catch { } result = InternalFindGameWindows(); }); t.SetApartmentState(System.Threading.ApartmentState.STA); t.Start(); t.Join(); return result; } private static List InternalFindGameWindows() { var result = new List(); NativeMethods.EnumWindows((hWnd, lParam) => { if (!NativeMethods.IsWindow(hWnd)) return true; NativeMethods.GetWindowThreadProcessId(hWnd, out uint pid); if (pid == 0) return true; string procName = "Unknown"; try { using var proc = Process.GetProcessById((int)pid); procName = proc.ProcessName; } catch { return true; } // Tuyệt đối không chọn nhầm các phần mềm lập trình, trình duyệt hay chính JXHelper if (BlacklistedProcessNames.Contains(procName)) return true; var sbClass = new StringBuilder(256); NativeMethods.GetClassName(hWnd, sbClass, sbClass.Capacity); string className = sbClass.ToString(); // Loại trừ splash screen, tooltip, helper IME if (className.Contains("SplashScreen", StringComparison.OrdinalIgnoreCase) || className.Contains("TPUtilWindow", StringComparison.OrdinalIgnoreCase) || className.Contains("GDI+", StringComparison.OrdinalIgnoreCase) || className.Contains("IME", StringComparison.OrdinalIgnoreCase)) { return true; } bool isSwordClass = className.Equals("Sword3 Class", StringComparison.OrdinalIgnoreCase) || className.StartsWith("Sword", StringComparison.OrdinalIgnoreCase) || className.Contains("KG3D", StringComparison.OrdinalIgnoreCase); bool isKnownProc = KnownProcessNames.Contains(procName) || procName.StartsWith("KY_", StringComparison.OrdinalIgnoreCase); if (!isSwordClass && !isKnownProc) return true; bool isVisible = NativeMethods.IsWindowVisible(hWnd); bool isIconic = NativeMethods.IsIconic(hWnd); // Bỏ qua các cửa sổ rác hoàn toàn không hiển thị và không phải Sword3 Class if (!isVisible && !isIconic && !isSwordClass) return true; int length = NativeMethods.GetWindowTextLength(hWnd); var sbTitle = new StringBuilder(Math.Max(length + 1, 256)); NativeMethods.GetWindowText(hWnd, sbTitle, sbTitle.Capacity); string title = sbTitle.ToString().Trim(); string lowerTitle = title.ToLowerInvariant(); bool isTitleMatch = lowerTitle.Contains("vo lam") || lowerTitle.Contains("võ lâm") || lowerTitle.Contains("vo cong") || lowerTitle.Contains("võ công") || lowerTitle.Contains("kiem the") || lowerTitle.Contains("kiếm thế") || lowerTitle.Contains("kiem hiep") || lowerTitle.Contains("kiếm hiệp") || lowerTitle.Contains("swordman") || lowerTitle.Contains("jx"); if (isSwordClass || (isKnownProc && isTitleMatch)) { if (string.IsNullOrEmpty(title)) { title = isSwordClass ? "Võ Lâm Truyền Kỳ" : procName; } result.Add(new GameWindowInfo(hWnd, (int)pid, procName, title, isIconic)); } return true; }, IntPtr.Zero); // Tự động định danh tên nhân vật từ bộ nhớ game thực tế (Process Memory Scan) var knownNames = CharacterNameResolver.GetKnownCharacterNames(); var assignedNames = new HashSet(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < result.Count; i++) { var win = result[i]; var (charName, isInGame) = CharacterNameResolver.ResolveCharacterState(win.ProcessId, i, knownNames, assignedNames, win.Handle); result[i] = win with { CharacterName = charName, IsInGame = isInGame }; } // Ưu tiên cửa sổ đã có nhân vật online lên trước, cửa sổ rỗi (chờ log) xếp sau result.Sort((a, b) => { int gameA = a.IsInGame ? 1 : 0; int gameB = b.IsInGame ? 1 : 0; if (gameA != gameB) return gameB.CompareTo(gameA); int scoreA = (a.ProcessName.Equals("vggame", StringComparison.OrdinalIgnoreCase) || a.ProcessName.Equals("game", StringComparison.OrdinalIgnoreCase)) ? 2 : 1; int scoreB = (b.ProcessName.Equals("vggame", StringComparison.OrdinalIgnoreCase) || b.ProcessName.Equals("game", StringComparison.OrdinalIgnoreCase)) ? 2 : 1; if (scoreA != scoreB) return scoreB.CompareTo(scoreA); // Cửa sổ không bị thu nhỏ ưu tiên xếp trước int iconicA = a.IsMinimized ? 0 : 1; int iconicB = b.IsMinimized ? 0 : 1; return iconicB.CompareTo(iconicA); }); return result; } /// /// Khôi phục cửa sổ nếu đang bị thu nhỏ (Iconic) để các lệnh PostMessage chuột/phím của DirectX hoạt động ổn định. /// public static void RestoreIfMinimized(IntPtr hWnd) { if (hWnd != IntPtr.Zero && NativeMethods.IsWindow(hWnd) && NativeMethods.IsIconic(hWnd)) { NativeMethods.ShowWindow(hWnd, NativeMethods.SW_RESTORE); } } /// /// Chuyển đổi vị trí con trỏ chuột vật lý hiện tại thành tọa độ tương đối (Client X, Y) của cửa sổ game. /// Dùng cho tính năng "Lấy tọa độ chuột một chạm" khi người dùng trỏ vào ô Búa sửa đồ. /// public static Point? GetRelativeCursorPoint(IntPtr hWnd) { if (hWnd == IntPtr.Zero || !NativeMethods.IsWindow(hWnd)) return null; if (NativeMethods.GetCursorPos(out var ptScreen)) { NativeMethods.ScreenToClient(hWnd, ref ptScreen); return ptScreen.ToPoint(); } return null; } }