using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Drawing; using JXHelper.Platform; namespace JXHelper.Core; /// /// Bộ định danh tên nhân vật thông minh (Character Name Resolver). /// Hỗ trợ quét bộ nhớ tiến trình (Process Memory Scan) và ánh xạ từ UserData của AutoVLBS. /// public static class CharacterNameResolver { private static readonly string[] PossibleVlbsPaths = new[] { @"C:\JXVCTK\JxVoCongTruyenKy\_AutoVLBS\VLBS19\UserData", @"C:\JXVCTK\JxVoCongTruyenKy\_AutoVLBS\VLBS\UserData", @"C:\AutoVLBS\UserData", }; private static bool _privilegeEnabled = false; /// /// Kích hoạt SeDebugPrivilege để cho phép đọc bộ nhớ tiến trình game khi chạy quyền Administrator. /// public static void EnableDebugPrivilege() { if (_privilegeEnabled) return; try { if (NativeMethods.OpenProcessToken(Process.GetCurrentProcess().Handle, NativeMethods.TOKEN_ADJUST_PRIVILEGES | NativeMethods.TOKEN_QUERY, out var hToken)) { if (NativeMethods.LookupPrivilegeValue(null, "SeDebugPrivilege", out var luid)) { var tp = new NativeMethods.TOKEN_PRIVILEGES { PrivilegeCount = 1, Luid = luid, Attributes = NativeMethods.SE_PRIVILEGE_ENABLED }; NativeMethods.AdjustTokenPrivileges(hToken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); } NativeMethods.CloseHandle(hToken); } _privilegeEnabled = true; } catch { // Bỏ qua nếu môi trường không cho phép } } /// /// Lấy danh sách tất cả tên nhân vật đã biết từ SSoT accounts.json và UserData của AutoVLBS. /// public static List GetKnownCharacterNames() { var names = new List(); // 1. SSoT: Đọc trực tiếp từ config/accounts.json try { var accounts = Config.AccountStorage.LoadAccounts(); foreach (var a in accounts) { if (!string.IsNullOrWhiteSpace(a.CharacterName) && !names.Contains(a.CharacterName.Trim(), StringComparer.OrdinalIgnoreCase)) { names.Add(a.CharacterName.Trim()); } } } catch {} // 2. Bổ sung từ UserData của AutoVLBS nếu có foreach (var path in PossibleVlbsPaths) { if (Directory.Exists(path)) { foreach (var file in Directory.GetFiles(path, "*.cfg")) { var name = Path.GetFileNameWithoutExtension(file).Trim(); if (!string.IsNullOrEmpty(name) && !names.Contains(name, StringComparer.OrdinalIgnoreCase)) { names.Add(name); } } } } return names; } /// /// Địa chỉ tĩnh cố định lưu tên và cấp độ của nhân vật người chơi cục bộ (Local Player) trong vggame.exe. /// INV-STATIC-RAM-LOCAL-PLAYER: Đọc siêu tốc 0.001ms, không cần quét 2GB RAM. /// public const long StaticPlayerNameAddress = 0x006DEC44; public const long StaticPlayerLevelAddress = 0x006DEC66; public const long StaticPlayerMapIdAddress = 0x006966CC; public const long StaticPlayerSecondaryAddress = 0x006DF3B0; /// /// Quét bộ nhớ tiến trình game vggame.exe để dò tìm chính xác tên nhân vật đang điều khiển. /// public static string? ScanProcessForKnownName(int pid, List candidates) { if (candidates.Count == 0) return null; EnableDebugPrivilege(); IntPtr hProcess = NativeMethods.OpenProcess(NativeMethods.PROCESS_QUERY_INFORMATION | NativeMethods.PROCESS_VM_READ, false, pid); if (hProcess == IntPtr.Zero) return null; try { // 1. FAST PATH (INV-STATIC-RAM-LOCAL-PLAYER): Đọc trực tiếp địa chỉ tĩnh 0x006DEC44 của Local Player byte[] nameBuf = new byte[32]; if (NativeMethods.ReadProcessMemory(hProcess, (IntPtr)StaticPlayerNameAddress, nameBuf, (IntPtr)nameBuf.Length, out var readBytes) && readBytes.ToInt64() > 0) { string rawName = Encoding.ASCII.GetString(nameBuf).TrimEnd('\0').Trim(); if (!string.IsNullOrEmpty(rawName)) { var match = candidates.FirstOrDefault(c => string.Equals(c, rawName, StringComparison.OrdinalIgnoreCase)); if (match != null) return match; if (rawName.Length >= 3 && rawName.All(c => char.IsLetterOrDigit(c) || c == '_')) { return rawName; } } } // 2. SECONDARY FAST PATH: Đọc địa chỉ tĩnh thứ hai 0x006DF3B0 if (NativeMethods.ReadProcessMemory(hProcess, (IntPtr)StaticPlayerSecondaryAddress, nameBuf, (IntPtr)nameBuf.Length, out readBytes) && readBytes.ToInt64() > 0) { string rawName = Encoding.ASCII.GetString(nameBuf).TrimEnd('\0').Trim(); if (!string.IsNullOrEmpty(rawName)) { var match = candidates.FirstOrDefault(c => string.Equals(c, rawName, StringComparison.OrdinalIgnoreCase)); if (match != null) return match; } } } catch { // Bỏ qua lỗi scan } finally { NativeMethods.CloseHandle(hProcess); } return null; } /// /// Định danh trạng thái nhân vật và cửa sổ game: /// - Quét bộ nhớ thực tế (Process Memory Scan) qua địa chỉ tĩnh 0x006DEC44. /// - Nếu tìm thấy tên nhân vật trong RAM -> Đã đăng nhập vào game (IsInGame = true). /// - Nếu đọc RAM bị chặn (do chưa Admin) -> Kiểm tra Pixel HUD góc máu/mana để xác nhận trạng thái in-game. /// - Nếu không tìm thấy tên và không có HUD -> Cửa sổ rỗi / chưa đăng nhập (IsInGame = false, CharacterName = ""). /// public static (string CharacterName, bool IsInGame) ResolveCharacterState( int pid, int windowIndex, List knownNames, HashSet assignedNames, IntPtr hWnd = default) { var scannedName = ScanProcessForKnownName(pid, knownNames); if (!string.IsNullOrEmpty(scannedName) && !assignedNames.Contains(scannedName)) { assignedNames.Add(scannedName); return (scannedName, true); } // INV-DUAL-FACTOR-INGAME: Fallback kiểm tra Pixel HUD nếu không đọc được RAM if (hWnd != IntPtr.Zero && CheckWindowIsInGameHud(hWnd)) { // Cửa sổ đang hiển thị thế giới game thật, gán tên từ danh sách tài khoản đã biết string candidate = windowIndex < knownNames.Count ? knownNames[windowIndex] : $"Char_{pid}"; if (!assignedNames.Contains(candidate)) { assignedNames.Add(candidate); return (candidate, true); } return (candidate, true); } // Không tìm thấy tên nhân vật trong RAM và không có HUD in-game -> Cửa sổ rỗi (Chờ log / Màn hình ngoài) return (string.Empty, false); } /// /// Nhận diện xem cửa sổ game đã vào thế giới chưa bằng Pixel HUD (Không cần quyền Admin). /// public static bool CheckWindowIsInGameHud(IntPtr hWnd) { if (hWnd == IntPtr.Zero || !NativeMethods.IsWindow(hWnd)) return false; if (!NativeMethods.GetClientRect(hWnd, out var rect)) return false; int w = rect.Width; int h = rect.Height; if (w < 640 || h < 480) return false; try { // Chụp 200x100 góc trên bên trái (nơi chứa bình máu / mana của Võ Lâm 1) using var bmp = new Bitmap(200, 100); using (var g = Graphics.FromImage(bmp)) { IntPtr hdc = g.GetHdc(); try { if (NativeMethods.PrintWindow(hWnd, hdc, 0)) { int redCount = 0; int blueCount = 0; for (int x = 30; x < 150; x += 10) { for (int y = 20; y < 60; y += 10) { var color = bmp.GetPixel(x, y); if (color.R > 120 && color.R > color.G * 1.5 && color.R > color.B * 1.5) redCount++; if (color.B > 120 && color.B > color.R * 1.3 && color.B > color.G * 1.3) blueCount++; } } if (redCount >= 1 || blueCount >= 1) return true; } } finally { g.ReleaseHdc(hdc); } } } catch { // Bỏ qua lỗi GDI } return false; } /// /// Đọc cấp độ nhân vật từ địa chỉ RAM tĩnh 0x006DEC66. /// public static int ReadCharacterLevel(int pid) { EnableDebugPrivilege(); IntPtr hProcess = NativeMethods.OpenProcess(NativeMethods.PROCESS_QUERY_INFORMATION | NativeMethods.PROCESS_VM_READ, false, pid); if (hProcess == IntPtr.Zero) return 0; try { byte[] buf = new byte[2]; if (NativeMethods.ReadProcessMemory(hProcess, (IntPtr)StaticPlayerLevelAddress, buf, (IntPtr)2, out var read) && read.ToInt64() >= 2) { return BitConverter.ToInt16(buf, 0); } } finally { NativeMethods.CloseHandle(hProcess); } return 0; } /// /// Định danh tên nhân vật từ bộ nhớ RAM thực tế. /// public static string ResolveCharacterName(int pid, int windowIndex, List knownNames, HashSet assignedNames, IntPtr hWnd = default) { var (charName, _) = ResolveCharacterState(pid, windowIndex, knownNames, assignedNames, hWnd); return charName; } }