#pragma once

// -----------------------------------------------------------------------------
// patterns.h
// -----------------------------------------------------------------------------
// The ONLY file that needs to change when a CS2 update breaks an offset:
// every byte pattern used to resolve the client/engine globals lives here.
//
// How a RIP-relative pattern resolves:
//   slot = match + instLength + rel32(match + relOffset)
// The result is the module-relative offset of the referenced global. Some
// globals hold a pointer (dwEntityList, dwLocalPlayerPawn), others are the
// struct itself (dwViewMatrix, dwViewAngles).
//
// "Chained" patterns (a2x cs2-dumper style) need two steps: the first
// pattern resolves a global, the second pattern's disp32 is an offset INTO
// that global (e.g. the local pawn pointer sits at prediction + 0xF8).
//
// Pattern sources (all offline, no network involved):
//   - valthrun (github.com/WolverinDEV/valthrun-cs2, cs2/src/offsets.rs)
//   - a2x cs2-dumper (github.com/a2x/cs2-dumper, src/analysis/offsets.rs)
//
// When a pattern stops matching (log line: "pattern '<name>': no match in
// <module>") check the latest patterns of the two projects above and update
// only the entry here.
// -----------------------------------------------------------------------------

#include <cstdint>

namespace core {

// A simple RIP-relative slot pattern: bytes + mask ('x' = fixed, '?' =
// wildcard); the rel32 sits at relOffset, the instruction is instLength
// bytes long. slot = match + instLength + rel32.
struct PatternDef
{
	const char* module;      // module to scan (e.g. "client.dll")
	const char* pattern;     // byte pattern, wildcards as 0x00
	const char* mask;        // 'x'/'?' per pattern byte
	uint32_t relOffset;      // position of the 4-byte rel32
	uint32_t instLength;     // instruction length (7 for [reg+disp32])
	const char* name;        // log label
};

// Second step of a chained pattern: the disp32 at relOffset is an offset
// into the global resolved by the first step.
struct ChainedStep
{
	const char* pattern;     // byte pattern, wildcards as 0x00
	const char* mask;        // 'x'/'?' per pattern byte
	uint32_t relOffset;      // rel32 position in this pattern
};

// ---------------------------------------------------------------------------
// client.dll globals
// ---------------------------------------------------------------------------

// dwEntityList (pointer slot -> outer list pointer).
// Tried in order; only the first match is used.
//   1. 4C 8B 0D ? ? ? ? 8B 97          (valthrun / classic community)
//   2. 48 89 0D ? ? ? ? E9 ? ? ? ? CC   (a2x current)
inline constexpr PatternDef kEntityListPatterns[] = {
	{ "client.dll", "\x4C\x8B\x0D\x00\x00\x00\x00\x8B\x97", "xxx????xx", 3, 7, "entity-list" },
	{ "client.dll", "\x48\x89\x0D\x00\x00\x00\x00\xE9\x00\x00\x00\x00\xCC", "xxx????x????x", 3, 7, "entity-list-v2" },
};

// dwLocalPlayerPawn (pointer slot -> pawn pointer).
// The pawn pointer global changed its pattern across builds. The preferred
// route is dwLocalPlayerController (pointer slot -> local player CONTROLLER,
// resolved via the stable controller patterns below) + the schema field
// CCSPlayerController::m_hPlayerPawn: the controller is always reachable and
// the pawn follows from the handle.
//   1. a2x current: 48 8B 05 ? ? ? ? 41 89 BE   (controller slot)
//   2. classic:     48 8B 05 ? ? ? ? 48 85 C0 74 4F
//   3. a2x chain:   prediction global + 0xF8   (see kPredictionPattern)
//   4. classic pawn: 48 8B 05 ? ? ? ? 48 63 DB  (stale on recent builds)
inline constexpr PatternDef kLocalControllerPatterns[] = {
	{ "client.dll", "\x48\x8B\x05\x00\x00\x00\x00\x41\x89\xBE", "xxx????xxx", 3, 7, "local-player-controller" },
	{ "client.dll", "\x48\x8B\x05\x00\x00\x00\x00\x48\x85\xC0\x74\x4F", "xxx????xxxxx", 3, 7, "local-player-controller-v2" },
};

// Classic pawn pointer slot (only used as a last-resort fallback).
inline constexpr PatternDef kLocalPawnPattern = {
	"client.dll", "\x48\x8B\x05\x00\x00\x00\x00\x48\x63\xDB", "xxx????xxx", 3, 7, "local-player-pawn",
};

// a2x chain step 1: "return &prediction" stub (lea + ret + padding + next
// function prologue) resolves the prediction global.
inline constexpr PatternDef kPredictionPattern = {
	"client.dll",
	"\x48\x8D\x05\x00\x00\x00\x00\xC3"
	"\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC"
	"\x40\x53\x56\x41\x54",
	"xxx????x" "xxxxxxxxxxxxxxxx" "xxxxx",
	3, 7, "prediction",
};

// a2x chain step 2: cmp [prediction + disp32], r14; jz; ...  -> disp32 is
// the local pawn slot offset inside the prediction struct.
inline constexpr ChainedStep kLocalPawnChain = {
	"\x4C\x39\xB6\x00\x00\x00\x00\x74\x00\x44\x88\xBE", "xxx????x?xxx", 3,
};

// dwViewAngles (QAngle struct, inline inside CSGOInput).
// a2x chain:
//   1. 48 89 05 ? ? ? ? 0F 57 C0 0F 11 05 ? ? ? ?  -> CSGOInput global
//   2. F2 42 0F 10 84 28 ? ? ? ? (movsd xmm0, [rax+disp32]) -> disp32 is the
//      view angles offset inside CSGOInput (e.g. 0x688).
inline constexpr PatternDef kCsgoInputPattern = {
	"client.dll",
	"\x48\x89\x05\x00\x00\x00\x00\x0F\x57\xC0\x0F\x11\x05\x00\x00\x00\x00",
	"xxx????xxxxxx????",
	3, 7, "csgo-input",
};
inline constexpr ChainedStep kViewAnglesChain = {
	"\xF2\x42\x0F\x10\x84\x28\x00\x00\x00\x00", "xxxxxx????", 6,
};

// dwViewMatrix (matrix struct, inline). valthrun: 48 8D 0D ? ? ? ? 48 C1 E0 06.
inline constexpr PatternDef kViewMatrixPattern = {
	"client.dll", "\x48\x8D\x0D\x00\x00\x00\x00\x48\xC1\xE0\x06", "xxx????xxxx", 3, 7, "view-matrix",
};

// dwGlobalVars (pointer slot, client.dll) - the game time source used by
// the web radar for the planted C4 countdown (Globals::time_2 @ 0x30).
// a2x current: 48 89 15 ? ? ? ? 48 89 42.
inline constexpr PatternDef kGlobalVarsPattern = {
	"client.dll", "\x48\x89\x15\x00\x00\x00\x00\x48\x89\x42", "xxx????xxx", 3, 7, "global-vars",
};

// ---------------------------------------------------------------------------
// Other modules
// ---------------------------------------------------------------------------

// Schema system instance (schemasystem.dll). valthrun: 48 8B 0D ? ? ? ? 48 8B 55 A0.
inline constexpr PatternDef kSchemaSystemPattern = {
	"schemasystem.dll", "\x48\x8B\x0D\x00\x00\x00\x00\x48\x8B\x55\xA0", "xxx????xxxx", 3, 7, "schema-system",
};

// Engine build info global (engine2.dll). valthrun: 48 8B 05 ? ? ? ? 48 85 C0 48 0F 45 C8.
inline constexpr PatternDef kBuildInfoPattern = {
	"engine2.dll", "\x48\x8B\x05\x00\x00\x00\x00\x48\x85\xC0\x48\x0F\x45\xC8", "xxx????xxxxxxx", 3, 7, "build-info",
};

// CNetworkGameClient instance pointer slot (engine2.dll). a2x current:
//   48 89 3D ? ? ? ? FF 87   (mov [rip+disp32], rdi; jmp [rdi+disp32])
// The slot holds a POINTER to the CNetworkGameClient instance (the map name
// lives on it, see CNetworkGameClient::m_szMapName). The disp32 sits at
// 0x03 and the mov instruction is 7 bytes (48 89 3D + disp32).
inline constexpr PatternDef kNetworkGameClientPattern = {
	"engine2.dll", "\x48\x89\x3D\x00\x00\x00\x00\xFF\x87", "xxx????xx", 3, 7, "network-game-client",
};

// ---------------------------------------------------------------------------
// Non-schema constants (not resolvable by patterns; changed by Valve only
// when they alter internal structures the schema does not expose).
// ---------------------------------------------------------------------------

// CModelState::m_boneArray: bone array pointer inside the model state.
inline constexpr uintptr_t kBoneArrayOffset = 0x80;

} // namespace core
