init
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
|
||||
namespace simple_rcon
|
||||
{
|
||||
class CommandQueue
|
||||
{
|
||||
public:
|
||||
using Executor = std::function<std::string(const std::string&)>;
|
||||
|
||||
explicit CommandQueue(Executor executor);
|
||||
|
||||
std::string enqueue_and_wait(const std::string& command, std::chrono::milliseconds timeout);
|
||||
int drain(int max_items = 32);
|
||||
void shutdown();
|
||||
|
||||
private:
|
||||
struct Task
|
||||
{
|
||||
std::string command;
|
||||
std::promise<std::string> result;
|
||||
};
|
||||
|
||||
Executor m_executor;
|
||||
std::mutex m_mutex;
|
||||
std::queue<std::shared_ptr<Task>> m_tasks;
|
||||
bool m_stopping{false};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace simple_rcon
|
||||
{
|
||||
struct Config
|
||||
{
|
||||
std::string bind_address{"127.0.0.1"};
|
||||
uint16_t port{27015};
|
||||
std::string password{"CHANGE_ME_BEFORE_USE"};
|
||||
bool auth_log{true};
|
||||
int max_connections{4};
|
||||
int packet_body_bytes{4000};
|
||||
std::chrono::milliseconds command_timeout{std::chrono::milliseconds{10000}};
|
||||
std::string dispatch_context{"auto"};
|
||||
bool offline_admin_dispatch{true};
|
||||
bool native_admin_executor{true};
|
||||
bool allow_empty_password{false};
|
||||
|
||||
// Read from SCUM/Saved/Config/WindowsServer/AdminUsers.ini by default.
|
||||
// This prevents silently treating an arbitrary online player as an admin.
|
||||
std::string admin_users_file{"auto"};
|
||||
// Optional selector only; it is accepted only when also present in
|
||||
// admin_users_file and is never a replacement authorization source.
|
||||
std::string preferred_admin_steam_id{};
|
||||
bool require_configured_admin{true};
|
||||
};
|
||||
|
||||
Config load_config(const std::filesystem::path& path);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace simple_rcon
|
||||
{
|
||||
/**
|
||||
* SCUM's normal admin commands are not Unreal console commands. They are
|
||||
* instances of AdminCommand executed by a native server function. This
|
||||
* class creates a server-only ConZPlayerController/ConZCharacter pair and
|
||||
* calls that function, so an actual player account does not need to be
|
||||
* connected.
|
||||
*
|
||||
* It is deliberately game-build guarded: if the known executor signature
|
||||
* does not resolve to exactly one location, no native call is made.
|
||||
*/
|
||||
class OfflineAdminExecutor
|
||||
{
|
||||
public:
|
||||
// Kept public solely so the .cpp's game-thread helper functions can
|
||||
// operate on it without exposing any Unreal types in this header.
|
||||
struct RuntimeState;
|
||||
|
||||
OfflineAdminExecutor();
|
||||
~OfflineAdminExecutor();
|
||||
OfflineAdminExecutor(const OfflineAdminExecutor&) = delete;
|
||||
OfflineAdminExecutor& operator=(const OfflineAdminExecutor&) = delete;
|
||||
|
||||
struct Result
|
||||
{
|
||||
bool attempted{false};
|
||||
bool success{false};
|
||||
std::string message;
|
||||
};
|
||||
|
||||
void configure(bool enabled, bool native_executor_enabled);
|
||||
void on_unreal_init();
|
||||
// Runs the non-mutating portion of the offline path on the game
|
||||
// thread: discover command classes, create/reuse the synthetic caller,
|
||||
// and resolve the guarded native executor address.
|
||||
Result probe(std::string_view configured_admin_steam_id);
|
||||
Result execute(std::string_view command, std::string_view configured_admin_steam_id);
|
||||
std::string status() const;
|
||||
|
||||
private:
|
||||
bool m_enabled{true};
|
||||
bool m_native_executor_enabled{true};
|
||||
bool m_unreal_ready{false};
|
||||
RuntimeState* m_state{nullptr};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <winsock2.h>
|
||||
|
||||
#include "Config.hpp"
|
||||
|
||||
namespace simple_rcon
|
||||
{
|
||||
class RconServer
|
||||
{
|
||||
public:
|
||||
using CommandHandler = std::function<std::string(const std::string&)>;
|
||||
using Logger = std::function<void(const std::string&)>;
|
||||
|
||||
RconServer(Config config, CommandHandler handler, Logger logger);
|
||||
~RconServer();
|
||||
|
||||
bool start();
|
||||
void stop();
|
||||
bool running() const;
|
||||
|
||||
private:
|
||||
struct Packet
|
||||
{
|
||||
int32_t id{};
|
||||
int32_t type{};
|
||||
std::string body;
|
||||
};
|
||||
|
||||
void accept_loop();
|
||||
void client_loop(SOCKET client, std::string remote);
|
||||
bool read_packet(SOCKET socket, Packet& out);
|
||||
bool send_packet(SOCKET socket, int32_t id, int32_t type, const std::string& body);
|
||||
bool send_response_chunks(SOCKET socket, int32_t id, const std::string& body);
|
||||
void close_client_socket(SOCKET socket);
|
||||
void log(const std::string& line);
|
||||
|
||||
Config m_config;
|
||||
CommandHandler m_handler;
|
||||
Logger m_logger;
|
||||
std::atomic<bool> m_running{false};
|
||||
std::atomic<int> m_active_clients{0};
|
||||
std::thread m_accept_thread;
|
||||
SOCKET m_listen_socket{INVALID_SOCKET};
|
||||
std::mutex m_clients_mutex;
|
||||
std::unordered_set<SOCKET> m_clients;
|
||||
std::vector<std::thread> m_client_threads;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace simple_rcon
|
||||
{
|
||||
/**
|
||||
* Handles ordinary SCUM chat independently of AdminCommand dispatch.
|
||||
*
|
||||
* It never creates or picks an arbitrary PlayerRpcChannel. A targeted
|
||||
* chat message is sent only after a configured SteamID is matched against
|
||||
* a real, currently network-backed ConZPlayerController whose Player is a
|
||||
* live UNetConnection. Broadcast chat is the same operation performed
|
||||
* once for each such controller.
|
||||
*/
|
||||
class RealChatDispatcher
|
||||
{
|
||||
public:
|
||||
struct Result
|
||||
{
|
||||
bool handled{false};
|
||||
bool success{false};
|
||||
std::string message;
|
||||
};
|
||||
|
||||
RealChatDispatcher();
|
||||
~RealChatDispatcher();
|
||||
RealChatDispatcher(const RealChatDispatcher&) = delete;
|
||||
RealChatDispatcher& operator=(const RealChatDispatcher&) = delete;
|
||||
|
||||
// Recognizes: SendChat <type 0-7> "message" [target SteamID64]
|
||||
// This function must be called on the game thread.
|
||||
Result dispatch_if_chat(std::string_view command);
|
||||
// Non-mutating game-thread diagnostic for the real-player chat path.
|
||||
Result probe();
|
||||
std::string status() const;
|
||||
|
||||
// Public only so the game-thread helper functions in the .cpp can
|
||||
// keep all Unreal implementation details out of this header.
|
||||
struct RuntimeState;
|
||||
|
||||
private:
|
||||
RuntimeState* m_state{nullptr};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Config.hpp"
|
||||
#include "OfflineAdminExecutor.hpp"
|
||||
#include "RealChatDispatcher.hpp"
|
||||
|
||||
namespace RC::Unreal
|
||||
{
|
||||
class UObject;
|
||||
}
|
||||
|
||||
namespace simple_rcon
|
||||
{
|
||||
class ScumBridge
|
||||
{
|
||||
public:
|
||||
void configure(const Config& config, const std::filesystem::path& mod_directory);
|
||||
void on_unreal_init();
|
||||
std::string execute(const std::string& command);
|
||||
std::string admin_status();
|
||||
std::string dispatch_status();
|
||||
|
||||
private:
|
||||
RC::Unreal::UObject* resolve_server_context(std::string& detail);
|
||||
bool process_server_console(RC::Unreal::UObject* context, const std::wstring& command, std::string& detail);
|
||||
void refresh_admin_ids_locked();
|
||||
std::string selected_admin_id_locked() const;
|
||||
static std::filesystem::path resolve_admin_users_file(const Config& config, const std::filesystem::path& mod_directory);
|
||||
static std::wstring widen_utf8(const std::string& s);
|
||||
static std::string trim_command(std::string s);
|
||||
|
||||
std::mutex m_mutex;
|
||||
bool m_unreal_ready{false};
|
||||
bool m_require_configured_admin{true};
|
||||
std::string m_preferred_admin_steam_id;
|
||||
std::filesystem::path m_admin_users_file;
|
||||
std::filesystem::file_time_type m_admin_users_last_write{};
|
||||
bool m_admin_file_seen{false};
|
||||
std::unordered_set<std::string> m_admin_ids;
|
||||
OfflineAdminExecutor m_offline_executor;
|
||||
RealChatDispatcher m_real_chat;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <Mod/CppUserModBase.hpp>
|
||||
|
||||
#include "CommandQueue.hpp"
|
||||
#include "Config.hpp"
|
||||
#include "RconServer.hpp"
|
||||
#include "ScumBridge.hpp"
|
||||
|
||||
namespace simple_rcon
|
||||
{
|
||||
class SimpleRconMod final : public RC::CppUserModBase
|
||||
{
|
||||
public:
|
||||
SimpleRconMod();
|
||||
~SimpleRconMod() override;
|
||||
|
||||
auto on_unreal_init() -> void override;
|
||||
auto on_update() -> void override;
|
||||
|
||||
private:
|
||||
void log(const std::string& line);
|
||||
std::string handle_rcon_command(const std::string& command);
|
||||
void install_game_thread_drain();
|
||||
void drain_game_thread();
|
||||
|
||||
Config m_config;
|
||||
ScumBridge m_bridge;
|
||||
CommandQueue m_queue;
|
||||
std::unique_ptr<RconServer> m_server;
|
||||
bool m_hook_installed{false};
|
||||
uint64_t m_tick_callback_id{0};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user