/// \file ares_recon.cpp — Công cụ recon bộ nhớ cho Phase 1 (docs/08-reversing-toolchain.md).
/// Thay thế workflow Cheat Engine: valuescan → valuefilter → watch → aob scan.
/// Chỉ ĐỌC bộ nhớ (PROCESS_VM_READ) — không ghi, không inject.

#define NOMINMAX
#include <windows.h>
#include <tlhelp32.h>
#include <psapi.h>

#include <algorithm>
#include <charconv>
#include <cstdint>
#include <cstring>
#include <format>
#include <fstream>
#include <optional>
#include <print>
#include <string>
#include <string_view>
#include <vector>

namespace {

constexpr std::string_view kTag = "recon";

// ---------------------------------------------------------------------------
// Process helpers
// ---------------------------------------------------------------------------

std::optional<DWORD> find_pid(std::wstring_view name_substr) {
    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snap == INVALID_HANDLE_VALUE) return std::nullopt;
    PROCESSENTRY32W pe{};
    pe.dwSize = sizeof(pe);
    std::optional<DWORD> found;
    if (Process32FirstW(snap, &pe)) {
        do {
            if (std::wstring_view(pe.szExeFile).find(name_substr) != std::wstring_view::npos) {
                found = pe.th32ProcessID;
                break;
            }
        } while (Process32NextW(snap, &pe));
    }
    CloseHandle(snap);
    return found;
}

struct ProcessInfo {
    HANDLE handle{};
    DWORD  pid{};
    std::wstring path;
    bool wow64{false};
};

std::optional<ProcessInfo> open_target(std::wstring_view name_substr, DWORD pid_override) {
    DWORD pid = pid_override;
    if (pid == 0) {
        auto p = find_pid(name_substr);
        if (!p) {
            std::println(stderr, "[{}] không tìm thấy process chứa '{}'", kTag,
                         std::string(name_substr.begin(), name_substr.end()));
            return std::nullopt;
        }
        pid = *p;
    }
    HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
    if (!h) {
        std::println(stderr, "[{}] OpenProcess(pid={}) THẤT BẠI — GetLastError={} "
                             "(5=Access Denied → thử chạy với quyền Admin)",
                     kTag, pid, GetLastError());
        return std::nullopt;
    }
    ProcessInfo info;
    info.handle = h;
    info.pid    = pid;

    WCHAR buf[MAX_PATH]{};
    DWORD len = MAX_PATH;
    if (QueryFullProcessImageNameW(h, 0, buf, &len)) info.path = buf;

    USHORT process_machine = 0, native = 0;
    if (IsWow64Process2(h, &process_machine, &native)) {
        info.wow64 = process_machine != IMAGE_FILE_MACHINE_UNKNOWN &&
                     process_machine != native;
    }
    return info;
}

// ---------------------------------------------------------------------------
// Memory regions & read
// ---------------------------------------------------------------------------

bool readable_protect(DWORD p) {
    return p == PAGE_READONLY || p == PAGE_READWRITE || p == PAGE_WRITECOPY ||
           p == PAGE_EXECUTE_READ || p == PAGE_EXECUTE_READWRITE ||
           p == PAGE_EXECUTE_WRITECOPY;
}

struct Region {
    std::uintptr_t base{};
    std::size_t    size{};
    DWORD          protect{};
    DWORD          type{};
};

std::vector<Region> enum_regions(HANDLE h) {
    std::vector<Region> out;
    MEMORY_BASIC_INFORMATION mbi{};
    std::uintptr_t addr = 0;
    while (VirtualQueryEx(h, reinterpret_cast<LPCVOID>(addr), &mbi, sizeof(mbi))) {
        if (mbi.State == MEM_COMMIT && readable_protect(mbi.Protect) && mbi.RegionSize > 0) {
            out.push_back({reinterpret_cast<std::uintptr_t>(mbi.BaseAddress),
                           mbi.RegionSize, mbi.Protect, mbi.Type});
        }
        const std::uintptr_t next =
            reinterpret_cast<std::uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;
        if (next <= addr) break;
        addr = next;
    }
    return out;
}

bool read_mem(HANDLE h, std::uintptr_t addr, void* dst, std::size_t size) {
    SIZE_T got = 0;
    return ReadProcessMemory(h, reinterpret_cast<LPCVOID>(addr), dst, size, &got) &&
           got == size;
}

template <typename T>
std::optional<T> parse_num(std::string_view s, int base = 10) {
    T v{};
    const auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), v, base);
    if (ec != std::errc{} || ptr != s.data() + s.size()) return std::nullopt;
    return v;
}

void hexdump(HANDLE h, std::uintptr_t addr, std::size_t len) {
    len = std::min<std::size_t>(len, 4096);
    std::vector<std::uint8_t> buf(len);
    if (!read_mem(h, addr, buf.data(), len)) {
        std::println(stderr, "[{}] đọc 0x{:X} thất bại (GetLastError={})", kTag, addr,
                     GetLastError());
        return;
    }
    for (std::size_t off = 0; off < len; off += 16) {
        std::string hex, ascii;
        for (std::size_t i = 0; i < 16 && off + i < len; ++i) {
            const std::uint8_t b = buf[off + i];
            hex += std::format("{:02X} ", b);
            ascii += (b >= 0x20 && b < 0x7F) ? static_cast<char>(b) : '.';
        }
        std::println("{:012X}  {:<48} |{}|", addr + off, hex, ascii);
    }
}

// ---------------------------------------------------------------------------
// Modules
// ---------------------------------------------------------------------------

struct ModuleInfoEx {
    std::wstring   name;
    std::uintptr_t base{};
    std::size_t    size{};
};

std::vector<ModuleInfoEx> enum_modules(HANDLE h) {
    std::vector<ModuleInfoEx> out;
    std::vector<HMODULE> mods(1024);
    DWORD needed = 0;
    if (!EnumProcessModulesEx(h, mods.data(), static_cast<DWORD>(mods.size() * sizeof(HMODULE)),
                              &needed, LIST_MODULES_ALL)) {
        mods.resize(needed / sizeof(HMODULE));
        if (needed == 0 ||
            !EnumProcessModulesEx(h, mods.data(), needed, &needed, LIST_MODULES_ALL)) {
            std::println(stderr, "[{}] EnumProcessModulesEx thất bại (GetLastError={})", kTag,
                         GetLastError());
            return out;
        }
    }
    const std::size_t count = std::min<std::size_t>(needed / sizeof(HMODULE), mods.size());
    for (std::size_t i = 0; i < count; ++i) {
        MODULEINFO mi{};
        if (!GetModuleInformation(h, mods[i], &mi, sizeof(mi))) continue;
        WCHAR path[MAX_PATH]{};
        GetModuleFileNameExW(h, mods[i], path, MAX_PATH);
        out.push_back({path, reinterpret_cast<std::uintptr_t>(mi.lpBaseOfDll), mi.SizeOfImage});
    }
    return out;
}

// ---------------------------------------------------------------------------
// AOB pattern scan (trong 1 module)
// ---------------------------------------------------------------------------

struct Pattern {
    std::vector<std::uint8_t> bytes;
    std::vector<bool>         mask; // true = byte phải khớp, false = wildcard
};

std::optional<Pattern> compile_pattern(std::string_view text) {
    Pattern p;
    auto hex2 = [](char c) -> int {
        if (c >= '0' && c <= '9') return c - '0';
        if (c >= 'a' && c <= 'f') return c - 'a' + 10;
        if (c >= 'A' && c <= 'F') return c - 'A' + 10;
        return -1;
    };
    std::size_t i = 0;
    while (i < text.size()) {
        if (text[i] == ' ') { ++i; continue; }
        if (text[i] == '?') {
            p.bytes.push_back(0);
            p.mask.push_back(false);
            i += (i + 1 < text.size() && text[i + 1] == '?') ? 2 : 1;
            continue;
        }
        if (i + 1 >= text.size()) return std::nullopt;
        const int hi = hex2(text[i]), lo = hex2(text[i + 1]);
        if (hi < 0 || lo < 0) return std::nullopt;
        p.bytes.push_back(static_cast<std::uint8_t>(hi * 16 + lo));
        p.mask.push_back(true);
        i += 2;
    }
    if (p.bytes.empty()) return std::nullopt;
    return p;
}

std::size_t aob_scan(HANDLE h, const ModuleInfoEx& mod, const Pattern& pat,
                     std::size_t max_hits) {
    constexpr std::size_t kChunk = 1u << 20; // 1 MB
    std::vector<std::uint8_t> buf;
    std::size_t hits = 0;
    const std::size_t plen = pat.bytes.size();
    for (std::size_t off = 0; off + plen <= mod.size; off += kChunk - plen + 1) {
        const std::size_t take = std::min(kChunk, mod.size - off);
        if (take < plen) break;
        buf.resize(take);
        if (!read_mem(h, mod.base + off, buf.data(), take)) continue;
        for (std::size_t i = 0; i + plen <= take; ++i) {
            bool ok = true;
            for (std::size_t j = 0; j < plen; ++j) {
                if (pat.mask[j] && buf[i + j] != pat.bytes[j]) { ok = false; break; }
            }
            if (ok) {
                std::println("  hit @ 0x{:X} ({}+0x{:X})", mod.base + off + i,
                             std::string(mod.name.begin(), mod.name.end()), off + i);
                if (++hits >= max_hits) return hits;
            }
        }
    }
    return hits;
}

// ---------------------------------------------------------------------------
// Value scan / filter (workflow Cheat Engine: first scan → next scan)
// ---------------------------------------------------------------------------

constexpr const char* kStateFile = "scan_state.txt";

template <typename T>
bool match_value(const std::uint8_t* p, double target, std::string_view op) {
    T v{};
    std::memcpy(&v, p, sizeof(T));
    if constexpr (std::same_as<T, float>) {
        if (v != v) return false; // bỏ NaN
        return op == "eq" ? v == static_cast<float>(target)
             : op == "lt" ? v <  static_cast<float>(target)
             : op == "gt" ? v >  static_cast<float>(target)
                          : false;
    } else {
        return op == "eq" ? v == static_cast<T>(target)
             : op == "neq" ? v != static_cast<T>(target)
             : op == "lt"  ? v <  static_cast<T>(target)
             : op == "gt"  ? v >  static_cast<T>(target)
                           : false;
    }
}

std::size_t value_scan_impl(HANDLE h, std::string_view type, double target,
                            std::string_view op, bool use_saved_addresses) {
    const bool is_float = type == "f32";
    std::vector<std::uintptr_t> hits;
    hits.reserve(1 << 16);

    if (use_saved_addresses) {
        std::ifstream in(kStateFile);
        if (!in) {
            std::println(stderr, "[{}] không có file state '{}' — hãy chạy valuescan trước",
                         kTag, kStateFile);
            return 0;
        }
        std::string line;
        while (std::getline(in, line)) {
            auto addr = parse_num<std::uintptr_t>(line, 16);
            if (!addr) continue;
            std::uint8_t buf[4]{};
            if (!read_mem(h, *addr, buf, 4)) continue;
            const bool m = is_float ? match_value<float>(buf, target, op)
                                    : match_value<std::uint32_t>(buf, target, op);
            if (m) hits.push_back(*addr);
        }
    } else {
        constexpr std::size_t kChunk = 1u << 20;
        std::vector<std::uint8_t> buf(kChunk);
        for (const Region& r : enum_regions(h)) {
            if (r.type != MEM_PRIVATE) continue;
            for (std::uintptr_t off = 0; off + 4 <= r.size; off += kChunk) {
                const std::size_t take = std::min(kChunk, r.size - off);
                if (!read_mem(h, r.base + off, buf.data(), take)) break;
                for (std::size_t i = 0; i + 4 <= take; i += 4) {
                    const bool m = is_float
                        ? match_value<float>(buf.data() + i, target, op)
                        : match_value<std::uint32_t>(buf.data() + i, target, op);
                    if (m) hits.push_back(r.base + off + i);
                }
            }
            if (hits.size() > 10'000'000) {
                std::println(stderr, "[{}] quá 10 triệu kết quả — dừng (giá trị quá phổ biến, "
                                     "chờ giá trị trong game đổi rồi chạy valuefilter)",
                             kTag);
                break;
            }
        }
    }

    {
        std::ofstream out(kStateFile);
        for (auto a : hits) std::format_to(std::ostreambuf_iterator<char>(out), "{:X}\n", a);
    }
    std::println("[{}] lưu {} địa chỉ vào '{}'. 30 địa chỉ đầu:", kTag, hits.size(), kStateFile);
    for (std::size_t i = 0; i < hits.size() && i < 30; ++i) {
        std::uint8_t buf[4]{};
        read_mem(h, hits[i], buf, 4);
        if (is_float) {
            float f = 0; std::memcpy(&f, buf, 4);
            std::println("  0x{:X} = {}", hits[i], f);
        } else {
            std::uint32_t u = 0; std::memcpy(&u, buf, 4);
            std::println("  0x{:X} = {}", hits[i], u);
        }
    }
    return hits.size();
}

void cmd_watch(HANDLE h, std::string_view type, std::uintptr_t addr, int times, int interval_ms) {
    for (int i = 0; i < times; ++i) {
        if (type == "f32") {
            float f = 0;
            if (read_mem(h, addr, &f, 4)) std::println("[{}] f32 @0x{:X} = {}", kTag, addr, f);
            else std::println("[{}] đọc thất bại", kTag);
        } else {
            std::uint32_t u = 0;
            if (read_mem(h, addr, &u, 4)) std::println("[{}] u32 @0x{:X} = {}", kTag, addr, u);
            else std::println("[{}] đọc thất bại", kTag);
        }
        Sleep(static_cast<DWORD>(interval_ms));
    }
}

void print_usage() {
    std::println(R"(ares_recon — Phase 1 memory recon tool
Cách dùng:
  ares_recon info                                — thông tin process (pid, path, bitness)
  ares_recon modules                             — liệt kê module + base + size
  ares_recon regions                             — thống kê vùng nhớ committed
  ares_recon read <hexaddr> <len>                — hexdump bộ nhớ
  ares_recon valuescan <u32|f32> <value>         — first scan (lưu scan_state.txt)
  ares_recon valuefilter <u32|f32> <eq|neq|lt|gt> <value> — next scan trên kết quả cũ
  ares_recon watch <u32|f32> <hexaddr> [times] [ms] — theo dõi địa chỉ
  ares_recon aob <module> "<AA BB ?? CC>"        — signature scan trong module
  Tùy chọn chung: --pid <dec> (mặc định tự tìm process 'Ares'))");
}

} // namespace

int main(int argc, char** argv) {
#ifdef _WIN32
    SetConsoleOutputCP(CP_UTF8);
#endif
    if (argc < 2) { print_usage(); return 1; }

    DWORD pid_override = 0;
    std::vector<std::string_view> args;
    for (int i = 1; i < argc; ++i) args.push_back(argv[i]);
    for (std::size_t i = 0; i + 1 < args.size(); ++i) {
        if (args[i] == "--pid") {
            if (auto p = parse_num<DWORD>(args[i + 1])) pid_override = *p;
        }
    }

    const std::string_view cmd = args[0];
    if (cmd == "help" || cmd == "--help") { print_usage(); return 0; }

    auto info = open_target(L"Ares", pid_override);
    if (!info) return 2;

    if (cmd == "info") {
        std::println("[{}] PID={} bitness={} path={}", kTag, info->pid,
                     info->wow64 ? "32-bit (WOW64)" : "64-bit",
                     std::string(info->path.begin(), info->path.end()));
        return 0;
    }
    if (cmd == "modules") {
        for (const auto& m : enum_modules(info->handle)) {
            std::println("{:016X}  {:>10}  {}", m.base, m.size,
                         std::string(m.name.begin(), m.name.end()));
        }
        return 0;
    }
    if (cmd == "regions") {
        std::size_t total = 0, private_b = 0, image_b = 0, mapped_b = 0, count = 0;
        for (const Region& r : enum_regions(info->handle)) {
            total += r.size; ++count;
            if (r.type == MEM_PRIVATE) private_b += r.size;
            else if (r.type == MEM_IMAGE) image_b += r.size;
            else mapped_b += r.size;
        }
        std::println("[{}] {} vùng committed: total={:.1f} MB private={:.1f} MB image={:.1f} MB mapped={:.1f} MB",
                     kTag, count, total / 1048576.0, private_b / 1048576.0,
                     image_b / 1048576.0, mapped_b / 1048576.0);
        return 0;
    }
    if (cmd == "read") {
        if (args.size() < 3) { print_usage(); return 1; }
        auto addr = parse_num<std::uintptr_t>(args[1], 16);
        auto len  = parse_num<std::size_t>(args[2]);
        if (!addr || !len) { std::println(stderr, "tham số sai"); return 1; }
        hexdump(info->handle, *addr, *len);
        return 0;
    }
    if (cmd == "valuescan") {
        if (args.size() < 3) { print_usage(); return 1; }
        const std::size_t n = value_scan_impl(info->handle, args[1],
                                              std::stod(std::string(args[2])), "eq", false);
        return n == 0 ? 3 : 0;
    }
    if (cmd == "valuefilter") {
        if (args.size() < 4) { print_usage(); return 1; }
        const std::size_t n = value_scan_impl(info->handle, args[1],
                                              std::stod(std::string(args[3])), args[2], true);
        return n == 0 ? 3 : 0;
    }
    if (cmd == "watch") {
        if (args.size() < 3) { print_usage(); return 1; }
        auto addr = parse_num<std::uintptr_t>(args[2], 16);
        if (!addr) { std::println(stderr, "địa chỉ sai"); return 1; }
        cmd_watch(info->handle, args[1], *addr,
                  args.size() > 3 ? std::stoi(std::string(args[3])) : 20,
                  args.size() > 4 ? std::stoi(std::string(args[4])) : 200);
        return 0;
    }
    if (cmd == "aob") {
        if (args.size() < 3) { print_usage(); return 1; }
        for (const auto& m : enum_modules(info->handle)) {
            std::string narrow(m.name.begin(), m.name.end());
            if (narrow.find(args[1]) == std::string::npos) continue;
            auto pat = compile_pattern(args[2]);
            if (!pat) { std::println(stderr, "pattern sai"); return 1; }
            const std::size_t hits = aob_scan(info->handle, m, *pat, 20);
            std::println("[{}] aob '{}': {} hit(s) trong {}", kTag, args[2], hits,
                         std::string(m.name.begin(), m.name.end()));
            return 0;
        }
        std::println(stderr, "[{}] không thấy module '{}'", kTag, args[1]);
        return 4;
    }

    print_usage();
    return 1;
}





