// offsets, constants and other shit recovered from dll
#ifndef EXPRESSO_H
#define EXPRESSO_H

#include <cstdint>
#include <cstddef>

namespace expresso {

// Engine globals
namespace game_rva {
constexpr uint64_t kGWorld = 0x2DC7380; // RVA 0x15AE96, 0x15A592
constexpr uint64_t kGNames = 0x314A7D0; // RVA 0x15AB18
constexpr uint64_t kProcessEvent = 0x7A8010; // RVA 0x165EBF, 0x160BBF
} // namespace game_rva

// UObject model offsets
namespace uobject {
constexpr uint32_t kClassPrivate = 0x10; // RVA 0x15C265
constexpr uint32_t kNameIndex = 0x18; // RVA 0x15C2D6  FName::ComparisonIndex
constexpr uint32_t kFieldNext = 0x28; // RVA 0x15C351  UField::Next
constexpr uint32_t kSuperStruct = 0x30; // RVA 0x15C36C  UStruct::SuperStruct
constexpr uint32_t kChildren = 0x38; // RVA 0x15C297  UStruct::Children
constexpr uint32_t kFunctionFlags = 0x88; // RVA 0x15C19E  UFunction::FunctionFlags
} // namespace uobject

// World related stuff
namespace world {
constexpr uint32_t kOwningGameInstance = 0x140; // RVA 0x15AF35  UWorld
constexpr uint32_t kLocalPlayers = 0x38; // RVA 0x15AF5A  UGameInstance
constexpr uint32_t kPlayerController = 0x30; // RVA 0x15AF98  ULocalPlayer
constexpr uint32_t kPlayerState = 0x388; // RVA 0x166036  APlayerController
constexpr uint32_t kPlayerNameFString = 0x370; // RVA 0x166065  APlayerState
} // namespace world

// the three pc slots the dll tries in order
namespace pc_slot {
constexpr uint32_t kA = 0x3E0; // RVA 0x15B785
constexpr uint32_t kB = 0x6A0; // RVA 0x15B7E7
constexpr uint32_t kC = 0x3D0; // RVA 0x15B835
constexpr uint32_t kCInner = 0x20; // RVA 0x15B85A, dereferenced from kC
} // namespace pc_slot

// pawn field stuff, symbols i could not name are named after the offset
namespace pawn {
// "Mask HP: %.2f / %.2f" (.rdata 0x59A3B0) at the call site that consumes both reads
constexpr uint32_t kMaskHpCurrent = 0xE58; // RVA 0x160E0A
constexpr uint32_t kMaskHpMax = 0xE64; // RVA 0x160E38
// "Stamina: %.0f / %.0f" (.rdata 0x59A5D8) the same way
constexpr uint32_t kStaminaCurrent = 0x1300; // RVA 0x1637D7
constexpr uint32_t kStaminaMax = 0x130C; // RVA 0x1637E3

// Auto-block preconditions, three bytes that must all read false
constexpr uint32_t kGateE54 = 0xE54; // RVA 0x16015E
constexpr uint32_t kGate1060 = 0x1060; // RVA 0x160186
constexpr uint32_t kGate1604 = 0x1604; // RVA 0x160172
// Set to 1 immediately before the SERVER_OnBlock RPC
constexpr uint32_t kFlag1010 = 0x1010; // RVA 0x160206

// bool gate and fload thats being forced to 1, no clue what it is
// not the stamina, thats defined above
constexpr uint32_t kGateFA0 = 0xFA0; // RVA 0x1600EC
constexpr uint32_t kFloatF24 = 0xF24; // RVA 0x160104
} // namespace pawn

// FName pool RVA 0x15AB63/0x15AB66
namespace fname {
constexpr uint32_t kChunkShift = 14;
constexpr uint32_t kChunkMask = 0x3FFF;
} // namespace fname

// EFunctionFlags values expresso ORs into UFunction::FunctionFlags before
// calling ProcessEvent, then restores. RVA 0x165BCF and 0x160BF7
namespace func_flags {
constexpr uint32_t kNative = 0x00000400;
constexpr uint32_t kPrivate = 0x00040000;
} // namespace func_flags

// corrupt pointer cannot hang the render thread (RVA 0x15C28D, 0x15C2C9, 0x15C2E2).
// bounds are:
namespace limits {
constexpr uint32_t kMaxClassDepth = 0x40;
constexpr uint32_t kMaxFieldsPerCls = 0x7D0;
constexpr uint32_t kMaxNameIndex = 0x1FFFFE;
constexpr uint32_t kMaxLocalPlayers = 7; // RVA 0x15B042: (count-1) <= 6
constexpr uint32_t kPcCacheMs = 2000; // RVA 0x15AF10: cmp rax, 0x7D0
constexpr uint32_t kRenameCooldown = 3000; // RVA 0x165E5A: cmp rcx, 0xBB8
constexpr uint32_t kMaxNameChars = 0x78; // RVA 0x165E2A
constexpr uint32_t kMinClassNameIdx = 0x10000; // RVA 0x15B7DC / 0x15B82A
constexpr uint32_t kMaxClassNameIdx = 0x200000; // RVA 0x15B8B2 / 0x15BFFF
constexpr int32_t kAutoBlockCooldownFrames = 30; // RVA 0x160280: 0x1E
} // namespace limits

class IMemory {
 public:
    virtual ~IMemory() = default;
    virtual bool Read(uint64_t addr, void* out, size_t size) const = 0;
    virtual bool Write(uint64_t addr, const void* src, size_t size) = 0;
};

// Guarded accessors, self explainatory
bool IsCanonicalUserPointer(uint64_t addr); // RVA 0x15A6A0 prologue

uint64_t SafeReadPtr(const IMemory& mem, uint64_t addr); // 0x15A6A0
bool SafeReadBool(const IMemory& mem, uint64_t addr); // 0x16B030
int32_t SafeReadInt32(const IMemory& mem, uint64_t addr); // 0x16B0F0
float SafeReadFloat(const IMemory& mem, uint64_t addr); // 0x16B120
bool SafeWriteInt32(IMemory& mem, uint64_t addr, int32_t v); // 0x16B160
bool SafeWriteFloat(IMemory& mem, uint64_t addr, float v); // 0x16B560
bool SafeSetTrue(IMemory& mem, uint64_t addr); // 0x16B5A0
bool SafeWritePtr(IMemory& mem, uint64_t addr, uint64_t v); // 0x16B5E0

// resolve fname
bool GetNameString(const IMemory& mem, uint64_t game_base,
                   int32_t name_index, char* out, size_t out_size);

// ClassPrivate -> Children -> Next -> SuperStruct comparing FNames
uint64_t FindFunctionByName(const IMemory& mem, uint64_t game_base,
                            uint64_t object, const char* name);

using ProcessEventFn = void (*)(uint64_t object, uint64_t function,
                                void* params, void* user);
bool CallUFunction(IMemory& mem, uint64_t object, uint64_t function,
                   void* params, uint32_t or_flags,
                   ProcessEventFn invoke, void* user);

// Cached local-player resolution, RVA 0x15AE60
struct LocalPlayerCache {
    uint64_t controller = 0; // .data 0x5DE790
    uint64_t world = 0; // .data 0x5DE530
    uint64_t timestamp = 0; // .data 0x5DE788
};
uint64_t GetLocalPlayerController(const IMemory& mem, uint64_t game_base,
                                  LocalPlayerCache& cache, uint64_t now_ms);

// Resolves an object's ClassPrivate->NamePrivate
bool GetObjectClassName(const IMemory& mem, uint64_t game_base, uint64_t object,
                        char* out, size_t out_size);

extern const char* const kPawnRoleNames[5];
constexpr size_t kPawnRoleCount = 5;

extern const char* const kKillerRoleNames[5];
constexpr size_t kKillerRoleCount = 5;

bool NameContains(const char* haystack, const char* needle);

uint64_t GetLocalPawn(const IMemory& mem, uint64_t game_base,
                      LocalPlayerCache& cache, uint64_t now_ms);

// True when the local pawn's class name contains one of kKillerRoleNames.
bool IsLocalPawnKiller(const IMemory& mem, uint64_t game_base,
                       LocalPlayerCache& cache, uint64_t now_ms);

/// One entry of the recovered SERVER_* table 
struct ServerRpcCall {
    const char* function_name;
    uint32_t    or_flags; // 0x400 or 0x40400
    const void* params;
    size_t      params_size;
};

bool InvokeServerRpc(IMemory& mem, uint64_t game_base, uint64_t target_object,
                     const ServerRpcCall& call,
                     ProcessEventFn invoke, void* user);

/// The `[DW:*]` direct-write name spoof, RVA 0x16606C..0x16609D. Overwrites the
/// FString at PlayerState+0x370 in place, bypassing every RPC
bool DirectWritePlayerName(IMemory& mem, uint64_t player_state,
                           uint64_t wide_buffer, int32_t length);

/// The full three-fallback rename, RVA 0x165D90
enum class RenameResult { kOk, kEmpty, kTooLong, kCooldown, kNoProcessEvent,
                          kNoPlayerController, kFailed };
RenameResult SpoofPlayerName(IMemory& mem, uint64_t game_base,
                             const char* utf8_name, uint64_t now_ms,
                             uint64_t& last_rename_ms,
                             uint64_t wide_buffer, int32_t& wide_length,
                             ProcessEventFn invoke, void* user);

// some input stuff E F
struct AutoBlockInput {
    bool interact_down = false;
    bool attack_down   = false;
};

// Frame counter at .data 0x5DE6D4, decremented once per tick and reloaded
struct AutoBlockState {
    int32_t cooldown_frames = 0;
};

enum class AutoBlockResult {
    kDisabled, // feature toggle off (0x160122)
    kNoPawn, // pawn did not validate (0x160135)
    kWrongRole, // local pawn is the antagonist (0x160151)
    kPawnBusy, // one of the three gate bytes set (0x16015E/172/186)
    kMenuOpen, // g_menuOpenMirror set (0x16019A)
    kInputHeld, // interact or attack held (0x1601F5)
    kCoolingDown, // cooldown_frames > 0 (0x160218)
    kNoFunction, // SERVER_OnBlock not found (0x160260)
    kFired, // RPC dispatched (0x16027B)
};

AutoBlockResult AutoBlockTick(IMemory& mem, uint64_t game_base,
                              LocalPlayerCache& cache, uint64_t now_ms,
                              bool feature_enabled, bool menu_open_mirror,
                              const AutoBlockInput& input,
                              AutoBlockState& state,
                              ProcessEventFn invoke, void* user);

// Reconstruction of RVA 0x1600CF..0x160110: pins the float at
// pawn::kFloatF24 to 1.0f whenever the byte at pawn::kGateFA0 is set
bool ForcePawnFloatField(IMemory& mem, uint64_t pawn);

} // namespace expresso

#endif // EXPRESSO_H