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