// i'm so fucking tired dude
#include "expresso_recovered.h"

#include <cstring>

namespace expresso {

// SERVER_ funcs used by this POS
const char* const kRecoveredServerRpcs[] = {
    "SERVER_OnBlock", // 0x59A3D8
    "SERVER_Attack", // 0x59A3E8
    "SERVER_Dodge", // 0x59A3F8
    "SERVER_SetCombatStance", // 0x59A408
    "SERVER_FillAbilities", // 0x59A420
    "SERVER_ActivateRage", // 0x59A440
    "SERVER_SetShift", // 0x59A458
    "SERVER_SetCharacterDisabled", // 0x59A480
    "SERVER_LockFearTo", // 0x59A4A0
    "SERVER_SetSprintingWithButtonState", // 0x59A4B8
    "SERVER_DeadFriendlySpotted", // 0x59A4E0
    "SERVER_UseCurrentSmallItem", // 0x59A568
    "SERVER_UseCurrentLargeItem", // 0x59A588
    "SERVER_DestroyDoor", // 0x59A660
    "SERVER_DeactivateLights", // 0x59A6A8
    "SERVER_NotifyMinigameFail", // 0x59A748
    "SERVER_NotifyKillerOfMinigameFail", // 0x59A768
    "SERVER_OpenDoor", // 0x59A798
    "SERVER_ForceStopInteractAnim", // 0x59A7A8
    "SERVER_FinishInteractAnim", // 0x59A7C8
    "SERVER_CancelInteractAnim", // 0x59A7E8
    "SERVER_OnMorph", // 0x59B3C0
};
const size_t kRecoveredServerRpcCount =
    sizeof(kRecoveredServerRpcs) / sizeof(kRecoveredServerRpcs[0]);

// RVA 0x160B80 shape every SERVER_* call site shares
//
//   0x160BB5  cmp  qword [g_ProcessEvent], 0 ; resolve once
//   0x160BBF  lea  rax, [rbx + 0x7A8010] ; gameBase + kProcessEvent
//   0x160BD4  lea  r8,  "SERVER_SetShift"
//   0x160BE1  call FindFunctionByName
//   0x160BE6  <canonical check on the result>
//   0x160BF7  mov  r9d, 0x40400 ; FUNC_Native | FUNC_Private
//   0x160BFD  mov  byte [rsp+0x20], 1 ; params.bEnable = true
//   0x160C0D  call CallUFunction
bool InvokeServerRpc(IMemory& mem, uint64_t game_base, uint64_t target_object,
                     const ServerRpcCall& call,
                     ProcessEventFn invoke, void* user) {
    if (call.function_name == nullptr) return false;
    if (!IsCanonicalUserPointer(target_object)) return false;

    const uint64_t fn =
        FindFunctionByName(mem, game_base, target_object, call.function_name);
    if (!IsCanonicalUserPointer(fn)) return false; // 0x160BF0 / 0x160BF5

    unsigned char frame[256] = {0};
    const size_t n = (call.params_size < sizeof(frame)) ? call.params_size
                                                        : sizeof(frame);
    if (call.params != nullptr && n != 0) std::memcpy(frame, call.params, n);

    return CallUFunction(mem, target_object, fn, frame, call.or_flags,
                         invoke, user);
}

// RVA 0x16606C .. 0x16609D -- the "[DW:*]" direct write
//
//   0x166065  lea  r14, [rax + 0x370] ; &PlayerState->PlayerName (FString)
//   0x16606C  lea  rdx, [g_wideNameBuf]
//   0x166076  call SafeWritePtr ; FString.Data   = buffer
//   0x16607E  lea  rcx, [r14 + 8]
//   0x166088  call SafeWriteInt32 ; FString.ArrayNum = len
//   0x166093  lea  rcx, [r14 + 0xC]
//   0x16609D  call SafeWriteInt32 ; FString.ArrayMax = len
//
// No RPC, no engine call, three stores into a replicated property. Any
// server-side name policy that reads the replicated value is defeated by this;
// the name must be owned by the server (ROSE JOHN: please fix this or else)
bool DirectWritePlayerName(IMemory& mem, uint64_t player_state,
                           uint64_t wide_buffer, int32_t length) {
    if (!IsCanonicalUserPointer(player_state)) return false;

    const uint64_t fstring = player_state + world::kPlayerNameFString;

    const bool ok_data = SafeWritePtr  (mem, fstring + 0x0, wide_buffer);
    const bool ok_num  = SafeWriteInt32(mem, fstring + 0x8, length);
    const bool ok_max  = SafeWriteInt32(mem, fstring + 0xC, length);

    return ok_data && ok_num && ok_max; // 0x1660A5 AND chain
}

// RVA 0x165D90, the rename driver, in the order the recovered code tries
//
// Status strings, all in restored .rdata:
//   "[SCN:noFn]" 0x59A858   "[SCN:OK]" 0x59A868   "[SCN:EX]" 0x59A878
//   "[BP:noLib]" 0x59A888   "[BP:noFn]" 0x59A8A8  "[BP:OK]"  0x59A8B8
//   "[DW:noPS]"  0x59A8C8   "[DW:OK]"  0x59A8D8   "[DW:FAIL]" 0x59A8E0
//   "ERR: empty" 0x59A8F8   "ERR: too long" 0x59A908
//   "COOLDOWN %llums" 0x59A918   "ERR: PE null" 0x59A928
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) {
    if (utf8_name == nullptr || utf8_name[0] == '\0') // 0x165E1E
        return RenameResult::kEmpty;

    const size_t len = std::strlen(utf8_name);
    if (len > limits::kMaxNameChars) // 0x165E2A cmp rax,0x78
        return RenameResult::kTooLong;

    // 0x165E5A: 3000 ms client-side cooldown, reported as "COOLDOWN %llums"
    if (last_rename_ms != 0 &&
        now_ms - last_rename_ms < limits::kRenameCooldown) {
        return RenameResult::kCooldown;
    }
    last_rename_ms = now_ms; // 0x165EA5 xchg

    // 0x165EBF: ProcessEvent is resolved lazily, from a fixed RVA
    const uint64_t process_event = game_base + game_rva::kProcessEvent;
    if (!IsCanonicalUserPointer(process_event)) // 0x165ED9
        return RenameResult::kNoProcessEvent;

    LocalPlayerCache cache;
    const uint64_t pc =
        GetLocalPlayerController(mem, game_base, cache, now_ms); // 0x165EEE
    if (!IsCanonicalUserPointer(pc)) // 0x165F0A
        return RenameResult::kNoPlayerController;

    wide_length = static_cast<int32_t>(len);

    // ServerChangeName RPC (RVA 0x165B60, flags 0x400)
    // CFG does not even have this function, is this guy (or his AI Agent) retarded?
    {
        struct FStringParam { // 0x165BB5..0x165BCB
            uint64_t data;
            int32_t  array_num;
            int32_t  array_max;
        } params{wide_buffer, wide_length, wide_length};

        const ServerRpcCall call{"ServerChangeName", func_flags::kNative,
                                 &params, sizeof(params)};
        if (InvokeServerRpc(mem, game_base, pc, call, invoke, user))
            return RenameResult::kOk; // "[SCN:OK]"
    }

    // AdvancedSessionsLibrary CDO SetPlayerName (0x165C50)
    // Resolved as FindFunctionByName(CDO("Default__AdvancedSessionsLibrary"),
    // "SetPlayerName").
    {
        struct SetPlayerNameParams {
            uint64_t player_controller;
            uint64_t name_data;
            int32_t  name_num;
            int32_t  name_max;
        } params{pc, wide_buffer, wide_length, wide_length};

        const ServerRpcCall call{"SetPlayerName", func_flags::kNative,
                                 &params, sizeof(params)};
        if (InvokeServerRpc(mem, game_base, pc, call, invoke, user))
            return RenameResult::kOk; // "[BP:OK]"
    }

    // direct FString write (0x166036)
    // ich ficke dieses networking frfr
    const uint64_t player_state = SafeReadPtr(mem, pc + world::kPlayerState);
    if (!IsCanonicalUserPointer(player_state))
        return RenameResult::kFailed; // "[DW:noPS]"

    return DirectWritePlayerName(mem, player_state, wide_buffer, wide_length)
               ? RenameResult::kOk // "[DW:OK]"
               : RenameResult::kFailed; // "[DW:FAIL]"
}

} // namespace expresso
