mirror of
https://github.com/XLabsProject/s1x-client.git
synced 2023-08-02 15:02:12 +02:00
Add console, dedicated, serverlist and more
This commit is contained in:
parent
0614305235
commit
a66c2efe99
204
src/client/component/console.cpp
Normal file
204
src/client/component/console.cpp
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include "console.hpp"
|
||||||
|
#include "loader/component_loader.hpp"
|
||||||
|
#include "game/game.hpp"
|
||||||
|
#include "scheduler.hpp"
|
||||||
|
#include "game_console.hpp"
|
||||||
|
|
||||||
|
#include <utils/thread.hpp>
|
||||||
|
#include <utils/flags.hpp>
|
||||||
|
|
||||||
|
namespace console
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void hide_console()
|
||||||
|
{
|
||||||
|
auto* const con_window = GetConsoleWindow();
|
||||||
|
|
||||||
|
DWORD process;
|
||||||
|
GetWindowThreadProcessId(con_window, &process);
|
||||||
|
|
||||||
|
if(process == GetCurrentProcessId() || IsDebuggerPresent())
|
||||||
|
{
|
||||||
|
ShowWindow(con_window, SW_HIDE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class component final : public component_interface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
component()
|
||||||
|
{
|
||||||
|
hide_console();
|
||||||
|
|
||||||
|
_pipe(this->handles_, 1024, _O_TEXT);
|
||||||
|
_dup2(this->handles_[1], 1);
|
||||||
|
_dup2(this->handles_[1], 2);
|
||||||
|
|
||||||
|
//setvbuf(stdout, nullptr, _IONBF, 0);
|
||||||
|
//setvbuf(stderr, nullptr, _IONBF, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void post_start() override
|
||||||
|
{
|
||||||
|
scheduler::loop([this]()
|
||||||
|
{
|
||||||
|
this->log_messages();
|
||||||
|
}, scheduler::pipeline::main);
|
||||||
|
|
||||||
|
this->console_runner_ = utils::thread::create_named_thread("Console IO", [this]
|
||||||
|
{
|
||||||
|
this->runner();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void pre_destroy() override
|
||||||
|
{
|
||||||
|
printf("\r\n");
|
||||||
|
_flushall();
|
||||||
|
|
||||||
|
this->terminate_runner_ = true;
|
||||||
|
|
||||||
|
if (this->console_runner_.joinable())
|
||||||
|
{
|
||||||
|
this->console_runner_.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
_close(this->handles_[0]);
|
||||||
|
_close(this->handles_[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void post_unpack() override
|
||||||
|
{
|
||||||
|
this->initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
volatile bool console_initialized_ = false;
|
||||||
|
volatile bool terminate_runner_ = false;
|
||||||
|
|
||||||
|
std::mutex mutex_;
|
||||||
|
std::thread console_runner_;
|
||||||
|
std::queue<std::string> message_queue_;
|
||||||
|
|
||||||
|
int handles_[2]{};
|
||||||
|
|
||||||
|
void initialize()
|
||||||
|
{
|
||||||
|
utils::thread::create_named_thread("Console", [this]()
|
||||||
|
{
|
||||||
|
if (game::environment::is_dedi())
|
||||||
|
{
|
||||||
|
game::Sys_ShowConsole();
|
||||||
|
}
|
||||||
|
else if(!utils::flags::has_flag("noconsole"))
|
||||||
|
{
|
||||||
|
game::Sys_ShowConsole();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!game::environment::is_dedi())
|
||||||
|
{
|
||||||
|
// Hide that shit
|
||||||
|
ShowWindow(console::get_window(), SW_MINIMIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> _(this->mutex_);
|
||||||
|
this->console_initialized_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MSG msg;
|
||||||
|
while (!this->terminate_runner_)
|
||||||
|
{
|
||||||
|
if (PeekMessageA(&msg, nullptr, NULL, NULL, PM_REMOVE))
|
||||||
|
{
|
||||||
|
if (msg.message == WM_QUIT) break;
|
||||||
|
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessage(&msg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(1ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit(0);
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
void log_messages()
|
||||||
|
{
|
||||||
|
while (this->console_initialized_ && !this->message_queue_.empty())
|
||||||
|
{
|
||||||
|
std::queue<std::string> message_queue_copy;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> _(this->mutex_);
|
||||||
|
message_queue_copy = std::move(this->message_queue_);
|
||||||
|
this->message_queue_ = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!message_queue_copy.empty())
|
||||||
|
{
|
||||||
|
log_message(message_queue_copy.front());
|
||||||
|
message_queue_copy.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fflush(stdout);
|
||||||
|
fflush(stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_message(const std::string& message)
|
||||||
|
{
|
||||||
|
OutputDebugStringA(message.data());
|
||||||
|
game::Conbuf_AppendText(message.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
void runner()
|
||||||
|
{
|
||||||
|
char buffer[1024];
|
||||||
|
|
||||||
|
while (!this->terminate_runner_ && this->handles_[0])
|
||||||
|
{
|
||||||
|
const auto len = _read(this->handles_[0], buffer, sizeof(buffer));
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> _(this->mutex_);
|
||||||
|
this->message_queue_.push(std::string(buffer, len));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(10ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
HWND get_window()
|
||||||
|
{
|
||||||
|
return *reinterpret_cast<HWND*>((SELECT_VALUE(0x14A9F6070, 0x14B5B94C0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_title(std::string title)
|
||||||
|
{
|
||||||
|
SetWindowText(get_window(), title.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_size(const int width, const int height)
|
||||||
|
{
|
||||||
|
RECT rect;
|
||||||
|
GetWindowRect(get_window(), &rect);
|
||||||
|
|
||||||
|
SetWindowPos(get_window(), nullptr, rect.left, rect.top, width, height, 0);
|
||||||
|
|
||||||
|
auto logoWindow = *reinterpret_cast<HWND*>(SELECT_VALUE(0x14A9F6080, 0x14B5B94D0));
|
||||||
|
SetWindowPos(logoWindow, 0, 5, 5, width - 25, 60, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_COMPONENT(console::component)
|
8
src/client/component/console.hpp
Normal file
8
src/client/component/console.hpp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace console
|
||||||
|
{
|
||||||
|
HWND get_window();
|
||||||
|
void set_title(std::string title);
|
||||||
|
void set_size(int width, int height);
|
||||||
|
}
|
265
src/client/component/dedicated.cpp
Normal file
265
src/client/component/dedicated.cpp
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include "loader/component_loader.hpp"
|
||||||
|
#include "scheduler.hpp"
|
||||||
|
#include "server_list.hpp"
|
||||||
|
#include "network.hpp"
|
||||||
|
#include "command.hpp"
|
||||||
|
#include "game/game.hpp"
|
||||||
|
#include "fastfiles.hpp"
|
||||||
|
#include "dvars.hpp"
|
||||||
|
|
||||||
|
#include <utils/hook.hpp>
|
||||||
|
#include <utils/string.hpp>
|
||||||
|
|
||||||
|
namespace dedicated
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
utils::hook::detour dvar_get_string_hook;
|
||||||
|
|
||||||
|
void init_dedicated_server()
|
||||||
|
{
|
||||||
|
static bool initialized = false;
|
||||||
|
if (initialized) return;
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
// R_LoadGraphicsAssets
|
||||||
|
reinterpret_cast<void(*)()>(0x1405A54F0)();
|
||||||
|
}
|
||||||
|
|
||||||
|
void send_heartbeat()
|
||||||
|
{
|
||||||
|
game::netadr_s target{};
|
||||||
|
if (server_list::get_master_server(target))
|
||||||
|
{
|
||||||
|
network::send(target, "heartbeat", "S1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string>& get_startup_command_queue()
|
||||||
|
{
|
||||||
|
static std::vector<std::string> startup_command_queue;
|
||||||
|
return startup_command_queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute_startup_command(int client, int controllerIndex, const char* command)
|
||||||
|
{
|
||||||
|
if (game::Live_SyncOnlineDataFlags(0) == 0)
|
||||||
|
{
|
||||||
|
game::Cbuf_ExecuteBufferInternal(0, 0, command, game::Cmd_ExecuteSingleCommand);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
get_startup_command_queue().emplace_back(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute_startup_command_queue()
|
||||||
|
{
|
||||||
|
const auto queue = get_startup_command_queue();
|
||||||
|
get_startup_command_queue().clear();
|
||||||
|
|
||||||
|
for (const auto& command : queue)
|
||||||
|
{
|
||||||
|
game::Cbuf_ExecuteBufferInternal(0, 0, command.data(), game::Cmd_ExecuteSingleCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string>& get_console_command_queue()
|
||||||
|
{
|
||||||
|
static std::vector<std::string> console_command_queue;
|
||||||
|
return console_command_queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute_console_command(const int client, const char* command)
|
||||||
|
{
|
||||||
|
if (game::Live_SyncOnlineDataFlags(0) == 0)
|
||||||
|
{
|
||||||
|
game::Cbuf_AddText(client, command);
|
||||||
|
game::Cbuf_AddText(client, "\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
get_console_command_queue().emplace_back(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute_console_command_queue()
|
||||||
|
{
|
||||||
|
const auto queue = get_console_command_queue();
|
||||||
|
get_console_command_queue().clear();
|
||||||
|
|
||||||
|
for (const auto& command : queue)
|
||||||
|
{
|
||||||
|
game::Cbuf_AddText(0, command.data());
|
||||||
|
game::Cbuf_AddText(0, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sync_gpu_stub()
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(1ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
utils::hook::detour com_quit_f_hook;
|
||||||
|
void kill_server()
|
||||||
|
{
|
||||||
|
for (auto i = 0; i < *game::mp::svs_numclients; ++i)
|
||||||
|
{
|
||||||
|
if (game::mp::svs_clients[i].header.state >= 3)
|
||||||
|
{
|
||||||
|
game::SV_GameSendServerCommand(i, game::SV_CMD_CAN_IGNORE, utils::string::va("r \"%s\"", "EXE_ENDOFGAME"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
com_quit_f_hook.invoke<void>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void initialize()
|
||||||
|
{
|
||||||
|
command::execute("onlinegame 1", true);
|
||||||
|
command::execute("xblive_privatematch 1", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
class component final : public component_interface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void* load_import(const std::string& library, const std::string& function) override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void post_unpack() override
|
||||||
|
{
|
||||||
|
if (!game::environment::is_dedi())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't allow sv_hostname to be changed by the game
|
||||||
|
dvars::disable::Dvar_SetString("sv_hostname");
|
||||||
|
|
||||||
|
// Hook R_SyncGpu
|
||||||
|
utils::hook::jump(0x1405A7630, sync_gpu_stub);
|
||||||
|
|
||||||
|
utils::hook::jump(0x14020C6B0, init_dedicated_server);
|
||||||
|
|
||||||
|
// delay startup commands until the initialization is done
|
||||||
|
//utils::hook::call(0x1403CDF63, execute_startup_command);
|
||||||
|
|
||||||
|
// delay console commands until the initialization is done
|
||||||
|
utils::hook::call(0x1403CEC35, execute_console_command);
|
||||||
|
utils::hook::nop(0x1403CEC4B, 5);
|
||||||
|
|
||||||
|
utils::hook::nop(0x1404AE6AE, 5); // don't load config file
|
||||||
|
utils::hook::nop(0x1403AF719, 5); // ^
|
||||||
|
utils::hook::set<uint8_t>(0x1403D2490, 0xC3); // don't save config file
|
||||||
|
utils::hook::set<uint8_t>(0x14022AFC0, 0xC3); // disable self-registration
|
||||||
|
utils::hook::set<uint8_t>(0x1404DA780, 0xC3); // init sound system (1)
|
||||||
|
utils::hook::set<uint8_t>(0x14062BC10, 0xC3); // init sound system (2)
|
||||||
|
utils::hook::set<uint8_t>(0x1405F31A0, 0xC3); // render thread
|
||||||
|
utils::hook::set<uint8_t>(0x140213C20, 0xC3); // called from Com_Frame, seems to do renderer stuff
|
||||||
|
utils::hook::set<uint8_t>(0x1402085C0, 0xC3);
|
||||||
|
// CL_CheckForResend, which tries to connect to the local server constantly
|
||||||
|
utils::hook::set<uint8_t>(0x14059B854, 0); // r_loadForRenderer default to 0
|
||||||
|
utils::hook::set<uint8_t>(0x1404D6952, 0xC3); // recommended settings check - TODO: Check hook
|
||||||
|
utils::hook::set<uint8_t>(0x1404D9BA0, 0xC3); // some mixer-related function called on shutdown
|
||||||
|
utils::hook::set<uint8_t>(0x1403B2860, 0xC3); // dont load ui gametype stuff
|
||||||
|
utils::hook::nop(0x14043ABB8, 6); // unknown check in SV_ExecuteClientMessage
|
||||||
|
utils::hook::nop(0x140439F15, 4); // allow first slot to be occupied
|
||||||
|
utils::hook::nop(0x14020E01C, 2); // properly shut down dedicated servers
|
||||||
|
utils::hook::nop(0x14020DFE9, 2); // ^
|
||||||
|
utils::hook::nop(0x14020E047, 5); // don't shutdown renderer
|
||||||
|
|
||||||
|
utils::hook::set<uint8_t>(0x140057D40, 0xC3); // something to do with blendShapeVertsView
|
||||||
|
utils::hook::nop(0x14062EA17, 8); // sound thing
|
||||||
|
|
||||||
|
utils::hook::set<uint8_t>(0x1404D6960, 0xC3); // cpu detection stuff?
|
||||||
|
utils::hook::set<uint8_t>(0x1405AEC00, 0xC3); // gfx stuff during fastfile loading
|
||||||
|
utils::hook::set<uint8_t>(0x1405AEB10, 0xC3); // ^
|
||||||
|
utils::hook::set<uint8_t>(0x1405AEBA0, 0xC3); // ^
|
||||||
|
utils::hook::set<uint8_t>(0x140275640, 0xC3); // ^
|
||||||
|
utils::hook::set<uint8_t>(0x1405AEB60, 0xC3); // ^
|
||||||
|
utils::hook::set<uint8_t>(0x140572640, 0xC3); // directx stuff
|
||||||
|
utils::hook::set<uint8_t>(0x1405A1340, 0xC3); // ^
|
||||||
|
utils::hook::set<uint8_t>(0x140021D60, 0xC3); // ^ - mutex
|
||||||
|
utils::hook::set<uint8_t>(0x1405A17E0, 0xC3); // ^
|
||||||
|
|
||||||
|
utils::hook::set<uint8_t>(0x1400534F0, 0xC3); // rendering stuff
|
||||||
|
utils::hook::set<uint8_t>(0x1405A1AB0, 0xC3); // ^
|
||||||
|
utils::hook::set<uint8_t>(0x1405A1BB0, 0xC3); // ^
|
||||||
|
utils::hook::set<uint8_t>(0x1405A21F0, 0xC3); // ^
|
||||||
|
utils::hook::set<uint8_t>(0x1405A2D60, 0xC3); // ^
|
||||||
|
utils::hook::set<uint8_t>(0x1405A3400, 0xC3); // ^
|
||||||
|
|
||||||
|
// shaders
|
||||||
|
utils::hook::set<uint8_t>(0x140057BC0, 0xC3); // ^
|
||||||
|
utils::hook::set<uint8_t>(0x140057B40, 0xC3); // ^
|
||||||
|
|
||||||
|
utils::hook::set<uint8_t>(0x1405EE040, 0xC3); // ^ - mutex
|
||||||
|
|
||||||
|
utils::hook::set<uint8_t>(0x1404DAF30, 0xC3); // idk
|
||||||
|
utils::hook::set<uint8_t>(0x1405736B0, 0xC3); // ^
|
||||||
|
|
||||||
|
utils::hook::set<uint8_t>(0x1405A6E70, 0xC3); // R_Shutdown
|
||||||
|
utils::hook::set<uint8_t>(0x1405732D0, 0xC3); // shutdown stuff
|
||||||
|
utils::hook::set<uint8_t>(0x1405A6F40, 0xC3); // ^
|
||||||
|
utils::hook::set<uint8_t>(0x1405A61A0, 0xC3); // ^
|
||||||
|
|
||||||
|
utils::hook::set<uint8_t>(0x14062C550, 0xC3); // sound crashes
|
||||||
|
|
||||||
|
utils::hook::set<uint8_t>(0x140445070, 0xC3); // disable host migration
|
||||||
|
|
||||||
|
utils::hook::set<uint8_t>(0x1403E1A50, 0xC3); // render synchronization lock
|
||||||
|
utils::hook::set<uint8_t>(0x1403E1990, 0xC3); // render synchronization unlock
|
||||||
|
|
||||||
|
utils::hook::nop(0x1404CC482, 5); // Disable sound pak file loading
|
||||||
|
utils::hook::nop(0x1404CC471, 2); // ^
|
||||||
|
utils::hook::set<uint8_t>(0x140279B80, 0xC3); // Disable image pak file loading
|
||||||
|
|
||||||
|
// initialize the game after onlinedataflags is 32 (workaround)
|
||||||
|
scheduler::schedule([=]()
|
||||||
|
{
|
||||||
|
if (game::Live_SyncOnlineDataFlags(0) == 32 && game::Sys_IsDatabaseReady2())
|
||||||
|
{
|
||||||
|
scheduler::once([]()
|
||||||
|
{
|
||||||
|
command::execute("xstartprivateparty", true);
|
||||||
|
command::execute("disconnect", true); // 32 -> 0
|
||||||
|
}, scheduler::pipeline::main, 1s);
|
||||||
|
return scheduler::cond_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
return scheduler::cond_continue;
|
||||||
|
}, scheduler::pipeline::main, 1s);
|
||||||
|
|
||||||
|
scheduler::on_game_initialized([]()
|
||||||
|
{
|
||||||
|
initialize();
|
||||||
|
|
||||||
|
printf("==================================\n");
|
||||||
|
printf("Server started!\n");
|
||||||
|
printf("==================================\n");
|
||||||
|
|
||||||
|
MessageBoxA(nullptr, "server started!", "", 0);
|
||||||
|
|
||||||
|
// remove disconnect command
|
||||||
|
game::Cmd_RemoveCommand((const char*)751);
|
||||||
|
|
||||||
|
execute_startup_command_queue();
|
||||||
|
execute_console_command_queue();
|
||||||
|
|
||||||
|
// Send heartbeat to dpmaster
|
||||||
|
scheduler::once(send_heartbeat, scheduler::pipeline::server);
|
||||||
|
scheduler::loop(send_heartbeat, scheduler::pipeline::server, 2min);
|
||||||
|
command::add("heartbeat", send_heartbeat);
|
||||||
|
}, scheduler::pipeline::main, 1s);
|
||||||
|
|
||||||
|
command::add("killserver", kill_server);
|
||||||
|
com_quit_f_hook.create(0x1403D08C0, &kill_server);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_COMPONENT(dedicated::component)
|
6
src/client/component/dedicated.hpp
Normal file
6
src/client/component/dedicated.hpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace dedicated
|
||||||
|
{
|
||||||
|
void initialize();
|
||||||
|
}
|
60
src/client/component/dedicated_info.cpp
Normal file
60
src/client/component/dedicated_info.cpp
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include "console.hpp"
|
||||||
|
#include "loader/component_loader.hpp"
|
||||||
|
#include "game/game.hpp"
|
||||||
|
#include "scheduler.hpp"
|
||||||
|
#include <utils\string.hpp>
|
||||||
|
|
||||||
|
namespace dedicated_info
|
||||||
|
{
|
||||||
|
class component final : public component_interface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void post_unpack() override
|
||||||
|
{
|
||||||
|
if (!game::environment::is_dedi())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler::loop([]()
|
||||||
|
{
|
||||||
|
auto* sv_running = game::Dvar_FindVar("sv_running");
|
||||||
|
if (!sv_running || !sv_running->current.enabled)
|
||||||
|
{
|
||||||
|
console::set_title("S1-Mod Dedicated Server");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* const sv_hostname = game::Dvar_FindVar("sv_hostname");
|
||||||
|
auto* const sv_maxclients = game::Dvar_FindVar("sv_maxclients");
|
||||||
|
auto* const mapname = game::Dvar_FindVar("mapname");
|
||||||
|
|
||||||
|
auto clientCount = 0;
|
||||||
|
|
||||||
|
for (auto i = 0; i < sv_maxclients->current.integer; i++)
|
||||||
|
{
|
||||||
|
auto* client = &game::mp::svs_clients[i];
|
||||||
|
auto* self = &game::mp::g_entities[i];
|
||||||
|
|
||||||
|
if (client->header.state >= 1 && self && self->client)
|
||||||
|
{
|
||||||
|
clientCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cleaned_hostname;
|
||||||
|
cleaned_hostname.resize(static_cast<int>(strlen(sv_hostname->current.string) + 1));
|
||||||
|
|
||||||
|
utils::string::strip(sv_hostname->current.string, cleaned_hostname.data(),
|
||||||
|
static_cast<int>(strlen(sv_hostname->current.string)) + 1);
|
||||||
|
|
||||||
|
console::set_title(utils::string::va("%s on %s [%d/%d]", cleaned_hostname.data(),
|
||||||
|
mapname->current.string, clientCount,
|
||||||
|
sv_maxclients->current.integer));
|
||||||
|
}, scheduler::pipeline::main, 1s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_COMPONENT(dedicated_info::component)
|
@ -4,6 +4,7 @@
|
|||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
|
|
||||||
#include "game_console.hpp"
|
#include "game_console.hpp"
|
||||||
|
#include "server_list.hpp"
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
#include <utils/hook.hpp>
|
||||||
|
|
||||||
@ -31,6 +32,11 @@ namespace input
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (game::environment::is_mp() && !server_list::sl_key_event(key, down))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
cl_key_event_hook.invoke<void>(local_client_num, key, down, arg4);
|
cl_key_event_hook.invoke<void>(local_client_num, key, down, arg4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
156
src/client/component/map_rotation.cpp
Normal file
156
src/client/component/map_rotation.cpp
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include "loader/component_loader.hpp"
|
||||||
|
#include "command.hpp"
|
||||||
|
#include "scheduler.hpp"
|
||||||
|
#include <utils/hook.hpp>
|
||||||
|
#include <utils/string.hpp>
|
||||||
|
#include "game/game.hpp"
|
||||||
|
|
||||||
|
namespace map_rotation
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void set_dvar(const std::string& dvar, const std::string& value)
|
||||||
|
{
|
||||||
|
command::execute(utils::string::va("%s \"%s\"", dvar.data(), value.data()), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_gametype(const std::string& gametype)
|
||||||
|
{
|
||||||
|
set_dvar("g_gametype", gametype);
|
||||||
|
}
|
||||||
|
|
||||||
|
void launch_map(const std::string& mapname)
|
||||||
|
{
|
||||||
|
command::execute(utils::string::va("map %s", mapname.data()), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void launch_default_map()
|
||||||
|
{
|
||||||
|
auto* mapname = game::Dvar_FindVar("mapname");
|
||||||
|
if (mapname && mapname->current.string && strlen(mapname->current.string) && mapname->current.string != "mp_vlobby_room"s)
|
||||||
|
{
|
||||||
|
launch_map(mapname->current.string);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (game::Com_GetCurrentCoDPlayMode() == game::CODPLAYMODE_ZOMBIES)
|
||||||
|
{
|
||||||
|
launch_map("mp_zombie_lab");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
launch_map("mp_venus");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string load_current_map_rotation()
|
||||||
|
{
|
||||||
|
auto* rotation = game::Dvar_FindVar("sv_mapRotationCurrent");
|
||||||
|
if (!strlen(rotation->current.string))
|
||||||
|
{
|
||||||
|
rotation = game::Dvar_FindVar("sv_mapRotation");
|
||||||
|
set_dvar("sv_mapRotationCurrent", rotation->current.string);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rotation->current.string;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> parse_current_map_rotation()
|
||||||
|
{
|
||||||
|
const auto rotation = load_current_map_rotation();
|
||||||
|
return utils::string::split(rotation, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
void store_new_rotation(const std::vector<std::string>& elements, const size_t index)
|
||||||
|
{
|
||||||
|
std::string value{};
|
||||||
|
|
||||||
|
for (auto i = index; i < elements.size(); ++i)
|
||||||
|
{
|
||||||
|
if (i != index)
|
||||||
|
{
|
||||||
|
value.push_back(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
value.append(elements[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_dvar("sv_mapRotationCurrent", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void perform_map_rotation()
|
||||||
|
{
|
||||||
|
if (game::Live_SyncOnlineDataFlags(0) != 0)
|
||||||
|
{
|
||||||
|
scheduler::on_game_initialized(perform_map_rotation, scheduler::pipeline::main, 1s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto rotation = parse_current_map_rotation();
|
||||||
|
|
||||||
|
for (size_t i = 0; !rotation.empty() && i < (rotation.size() - 1); i += 2)
|
||||||
|
{
|
||||||
|
const auto& key = rotation[i];
|
||||||
|
const auto& value = rotation[i + 1];
|
||||||
|
|
||||||
|
if (key == "gametype")
|
||||||
|
{
|
||||||
|
set_gametype(value);
|
||||||
|
}
|
||||||
|
else if (key == "map")
|
||||||
|
{
|
||||||
|
store_new_rotation(rotation, i + 2);
|
||||||
|
launch_map(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("Invalid map rotation key: %s\n", key.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch_default_map();
|
||||||
|
}
|
||||||
|
|
||||||
|
void trigger_map_rotation()
|
||||||
|
{
|
||||||
|
scheduler::schedule([]()
|
||||||
|
{
|
||||||
|
if (game::CL_IsCgameInitialized())
|
||||||
|
{
|
||||||
|
return scheduler::cond_continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
command::execute("map_rotate", false);
|
||||||
|
return scheduler::cond_end;
|
||||||
|
}, scheduler::pipeline::main, 1s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class component final : public component_interface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void post_unpack() override
|
||||||
|
{
|
||||||
|
if (!game::environment::is_dedi())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler::once([]()
|
||||||
|
{
|
||||||
|
game::Dvar_RegisterString("sv_mapRotation", "", 0, "");
|
||||||
|
game::Dvar_RegisterString("sv_mapRotationCurrent", "", 0, "");
|
||||||
|
}, scheduler::pipeline::main);
|
||||||
|
|
||||||
|
command::add("map_rotate", &perform_map_rotation);
|
||||||
|
|
||||||
|
// Hook GScr_ExitLevel
|
||||||
|
utils::hook::jump(0x14032E490, &trigger_map_rotation);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_COMPONENT(map_rotation::component)
|
@ -6,7 +6,7 @@
|
|||||||
#include "command.hpp"
|
#include "command.hpp"
|
||||||
#include "network.hpp"
|
#include "network.hpp"
|
||||||
#include "scheduler.hpp"
|
#include "scheduler.hpp"
|
||||||
//#include "server_list.hpp"
|
#include "server_list.hpp"
|
||||||
|
|
||||||
#include "steam/steam.hpp"
|
#include "steam/steam.hpp"
|
||||||
|
|
||||||
@ -107,6 +107,30 @@ namespace party
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get_client_num_by_name(const std::string& name)
|
||||||
|
{
|
||||||
|
for (auto i = 0; !name.empty() && i < *game::mp::svs_numclients; ++i)
|
||||||
|
{
|
||||||
|
if (game::mp::g_entities[i].client)
|
||||||
|
{
|
||||||
|
char client_name[16] = { 0 };
|
||||||
|
strncpy_s(client_name, game::mp::g_entities[i].client->name, 16);
|
||||||
|
game::I_CleanStr(client_name);
|
||||||
|
|
||||||
|
if (client_name == name)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset_connect_state()
|
||||||
|
{
|
||||||
|
connect_state = {};
|
||||||
|
}
|
||||||
|
|
||||||
int get_client_count()
|
int get_client_count()
|
||||||
{
|
{
|
||||||
auto count = 0;
|
auto count = 0;
|
||||||
@ -143,7 +167,7 @@ namespace party
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
command::execute("lui_open popup_acceptinginvite", false);
|
command::execute("lui_open_popup popup_acceptinginvite", false);
|
||||||
|
|
||||||
connect_state.host = target;
|
connect_state.host = target;
|
||||||
connect_state.challenge = utils::cryptography::random::get_challenge();
|
connect_state.challenge = utils::cryptography::random::get_challenge();
|
||||||
@ -181,9 +205,20 @@ namespace party
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// starting map like this crashes the game.
|
|
||||||
printf("Starting map: %s\n", mapname.data());
|
printf("Starting map: %s\n", mapname.data());
|
||||||
game::SV_StartMapForParty(0, mapname.data(), true);
|
|
||||||
|
auto* gametype = game::Dvar_FindVar("g_gametype");
|
||||||
|
if (gametype && gametype->current.string)
|
||||||
|
{
|
||||||
|
command::execute(utils::string::va("ui_gametype %s", gametype->current.string), true);
|
||||||
|
}
|
||||||
|
command::execute(utils::string::va("ui_mapname %s", mapname.data()), true);
|
||||||
|
|
||||||
|
// StartServer
|
||||||
|
reinterpret_cast<void(*)(unsigned int)>(0x140492260)(0);
|
||||||
|
|
||||||
|
// this dun work.
|
||||||
|
//game::SV_StartMapForParty(0, mapname.data(), false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,34 +276,11 @@ namespace party
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
command::add("reconnect", [](const command::params& params)
|
command::add("kickClient", [](const command::params& params)
|
||||||
{
|
|
||||||
if (!game::CL_IsCgameInitialized())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto host = connect_state.host;
|
|
||||||
if (host.type == game::NA_LOOPBACK)
|
|
||||||
{
|
|
||||||
command::execute("map_restart", true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (host.type != game::NA_IP)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
command::execute("disconnect", true);
|
|
||||||
scheduler::once([host]()
|
|
||||||
{
|
|
||||||
connect(host);
|
|
||||||
}, scheduler::pipeline::async, 2s);
|
|
||||||
});
|
|
||||||
|
|
||||||
command::add("clientkick", [](const command::params& params)
|
|
||||||
{
|
{
|
||||||
if (params.size() < 2)
|
if (params.size() < 2)
|
||||||
{
|
{
|
||||||
printf("usage: clientkick <num>\n");
|
printf("usage: kickClient <num>\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto client_num = atoi(params.get(1));
|
const auto client_num = atoi(params.get(1));
|
||||||
@ -280,6 +292,33 @@ namespace party
|
|||||||
game::SV_KickClientNum(client_num, "EXE_PLAYERKICKED");
|
game::SV_KickClientNum(client_num, "EXE_PLAYERKICKED");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
command::add("kick", [](const command::params& params)
|
||||||
|
{
|
||||||
|
if (params.size() < 2)
|
||||||
|
{
|
||||||
|
printf("usage: kick <name>\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string name = params.get(1);
|
||||||
|
if (name == "all"s)
|
||||||
|
{
|
||||||
|
for (auto i = 0; i < *game::mp::svs_numclients; ++i)
|
||||||
|
{
|
||||||
|
game::SV_KickClientNum(i, "EXE_PLAYERKICKED");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto client_num = get_client_num_by_name(name);
|
||||||
|
if (client_num < 0 || client_num >= *game::mp::svs_numclients)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
game::SV_KickClientNum(client_num, "EXE_PLAYERKICKED");
|
||||||
|
});
|
||||||
|
|
||||||
scheduler::once([]()
|
scheduler::once([]()
|
||||||
{
|
{
|
||||||
game::Dvar_RegisterString("sv_sayName", "console", game::DvarFlags::DVAR_FLAG_NONE,
|
game::Dvar_RegisterString("sv_sayName", "console", game::DvarFlags::DVAR_FLAG_NONE,
|
||||||
@ -297,7 +336,7 @@ namespace party
|
|||||||
const auto message = params.join(2);
|
const auto message = params.join(2);
|
||||||
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
|
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
|
||||||
|
|
||||||
game::SV_GameSendServerCommand(client_num, 0,
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE,
|
||||||
utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
|
utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
|
||||||
printf("%s -> %i: %s\n", name, client_num, message.data());
|
printf("%s -> %i: %s\n", name, client_num, message.data());
|
||||||
});
|
});
|
||||||
@ -312,7 +351,7 @@ namespace party
|
|||||||
const auto client_num = atoi(params.get(1));
|
const auto client_num = atoi(params.get(1));
|
||||||
const auto message = params.join(2);
|
const auto message = params.join(2);
|
||||||
|
|
||||||
game::SV_GameSendServerCommand(client_num, 0, utils::string::va("%c \"%s\"", 84, message.data()));
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data()));
|
||||||
printf("%i: %s\n", client_num, message.data());
|
printf("%i: %s\n", client_num, message.data());
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -327,7 +366,7 @@ namespace party
|
|||||||
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
|
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
|
||||||
|
|
||||||
game::SV_GameSendServerCommand(
|
game::SV_GameSendServerCommand(
|
||||||
-1, 0, utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
|
-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
|
||||||
printf("%s: %s\n", name, message.data());
|
printf("%s: %s\n", name, message.data());
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -340,7 +379,7 @@ namespace party
|
|||||||
|
|
||||||
const auto message = params.join(1);
|
const auto message = params.join(1);
|
||||||
|
|
||||||
game::SV_GameSendServerCommand(-1, 0, utils::string::va("%c \"%s\"", 84, message.data()));
|
game::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data()));
|
||||||
printf("%s\n", message.data());
|
printf("%s\n", message.data());
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -362,6 +401,7 @@ namespace party
|
|||||||
info.set("sv_maxclients", utils::string::va("%i", *game::mp::svs_numclients));
|
info.set("sv_maxclients", utils::string::va("%i", *game::mp::svs_numclients));
|
||||||
info.set("protocol", utils::string::va("%i", PROTOCOL));
|
info.set("protocol", utils::string::va("%i", PROTOCOL));
|
||||||
info.set("playmode", utils::string::va("%i", game::Com_GetCurrentCoDPlayMode()));
|
info.set("playmode", utils::string::va("%i", game::Com_GetCurrentCoDPlayMode()));
|
||||||
|
info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running")));
|
||||||
|
|
||||||
network::send(target, "infoResponse", info.build(), '\n');
|
network::send(target, "infoResponse", info.build(), '\n');
|
||||||
});
|
});
|
||||||
@ -369,7 +409,7 @@ namespace party
|
|||||||
network::on("infoResponse", [](const game::netadr_s& target, const std::string_view& data)
|
network::on("infoResponse", [](const game::netadr_s& target, const std::string_view& data)
|
||||||
{
|
{
|
||||||
const utils::info_string info{data};
|
const utils::info_string info{data};
|
||||||
//server_list::handle_info_response(target, info);
|
server_list::handle_info_response(target, info);
|
||||||
|
|
||||||
if (connect_state.host != target)
|
if (connect_state.host != target)
|
||||||
{
|
{
|
||||||
@ -402,6 +442,15 @@ namespace party
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto sv_running = info.get("sv_running");
|
||||||
|
if (!std::atoi(sv_running.data()))
|
||||||
|
{
|
||||||
|
const auto str = "Server not running.";
|
||||||
|
printf("%s\n", str);
|
||||||
|
game::Com_Error(game::ERR_DROP, str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const auto mapname = info.get("mapname");
|
const auto mapname = info.get("mapname");
|
||||||
if (mapname.empty())
|
if (mapname.empty())
|
||||||
{
|
{
|
||||||
|
@ -3,9 +3,13 @@
|
|||||||
|
|
||||||
namespace party
|
namespace party
|
||||||
{
|
{
|
||||||
|
void reset_connect_state();
|
||||||
|
|
||||||
void connect(const game::netadr_s& target);
|
void connect(const game::netadr_s& target);
|
||||||
void start_map(const std::string& mapname);
|
void start_map(const std::string& mapname);
|
||||||
|
|
||||||
|
int get_client_num_by_name(const std::string& name);
|
||||||
|
|
||||||
int get_client_count();
|
int get_client_count();
|
||||||
int get_bot_count();
|
int get_bot_count();
|
||||||
}
|
}
|
||||||
|
413
src/client/component/server_list.cpp
Normal file
413
src/client/component/server_list.cpp
Normal file
@ -0,0 +1,413 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include "loader/component_loader.hpp"
|
||||||
|
#include "server_list.hpp"
|
||||||
|
#include "game_console.hpp"
|
||||||
|
#include "command.hpp"
|
||||||
|
#include "localized_strings.hpp"
|
||||||
|
#include "network.hpp"
|
||||||
|
#include "scheduler.hpp"
|
||||||
|
#include "party.hpp"
|
||||||
|
#include "game/game.hpp"
|
||||||
|
|
||||||
|
#include <utils/cryptography.hpp>
|
||||||
|
#include <utils/string.hpp>
|
||||||
|
#include <utils/hook.hpp>
|
||||||
|
|
||||||
|
namespace server_list
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
struct server_info
|
||||||
|
{
|
||||||
|
// gotta add more to this
|
||||||
|
int clients;
|
||||||
|
int max_clients;
|
||||||
|
int bots;
|
||||||
|
int ping;
|
||||||
|
std::string host_name;
|
||||||
|
std::string map_name;
|
||||||
|
std::string game_type;
|
||||||
|
game::CodPlayMode play_mode;
|
||||||
|
char in_game;
|
||||||
|
game::netadr_s address;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
game::netadr_s address{};
|
||||||
|
volatile bool requesting = false;
|
||||||
|
std::unordered_map<game::netadr_s, int> queued_servers{};
|
||||||
|
} master_state;
|
||||||
|
|
||||||
|
std::mutex mutex;
|
||||||
|
std::vector<server_info> servers;
|
||||||
|
|
||||||
|
size_t server_list_index = 0;
|
||||||
|
int server_list_page = 0;
|
||||||
|
|
||||||
|
volatile bool update_server_list = false;
|
||||||
|
|
||||||
|
void refresh_server_list()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> _(mutex);
|
||||||
|
servers.clear();
|
||||||
|
master_state.queued_servers.clear();
|
||||||
|
server_list_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
party::reset_connect_state();
|
||||||
|
|
||||||
|
if (get_master_server(master_state.address))
|
||||||
|
{
|
||||||
|
master_state.requesting = true;
|
||||||
|
network::send(master_state.address, "getservers", utils::string::va("S1 %i full empty", PROTOCOL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void join_server(int, int, const int index)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> _(mutex);
|
||||||
|
|
||||||
|
const auto i = static_cast<size_t>(index) + server_list_index;
|
||||||
|
if (i < servers.size())
|
||||||
|
{
|
||||||
|
// double click disabled for now
|
||||||
|
/*static size_t last_index = 0xFFFFFFFF;
|
||||||
|
if (last_index != i)
|
||||||
|
{
|
||||||
|
last_index = i;
|
||||||
|
}
|
||||||
|
else*/
|
||||||
|
{
|
||||||
|
party::connect(servers[i].address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void trigger_refresh()
|
||||||
|
{
|
||||||
|
update_server_list = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ui_feeder_count()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> _(mutex);
|
||||||
|
if (update_server_list)
|
||||||
|
{
|
||||||
|
update_server_list = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
auto count = static_cast<int>(servers.size());
|
||||||
|
return count > 15 ? 15 : count;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* ui_feeder_item_text(int /*localClientNum*/, void* /*a2*/, void* /*a3*/, const size_t index,
|
||||||
|
const size_t column)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> _(mutex);
|
||||||
|
|
||||||
|
const auto i = server_list_index + index;
|
||||||
|
|
||||||
|
if (i >= servers.size())
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column == 0)
|
||||||
|
{
|
||||||
|
return servers[i].host_name.empty() ? "" : utils::string::va("%s", servers[i].host_name.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column == 1)
|
||||||
|
{
|
||||||
|
return servers[i].map_name.empty() ? "" : utils::string::va("%s", servers[i].map_name.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column == 2)
|
||||||
|
{
|
||||||
|
return utils::string::va("%d/%d [%d]", servers[i].clients, servers[index].max_clients, servers[i].bots);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column == 3)
|
||||||
|
{
|
||||||
|
return servers[i].game_type.empty() ? "" : utils::string::va("%s", servers[i].game_type.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void sort_serverlist()
|
||||||
|
{
|
||||||
|
std::stable_sort(servers.begin(), servers.end(), [](const server_info& a, const server_info& b)
|
||||||
|
{
|
||||||
|
if(a.clients == b.clients)
|
||||||
|
{
|
||||||
|
return a.ping < b.ping;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.clients > b.clients;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert_server(server_info&& server)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> _(mutex);
|
||||||
|
servers.emplace_back(std::move(server));
|
||||||
|
sort_serverlist();
|
||||||
|
}
|
||||||
|
|
||||||
|
void do_frame_work()
|
||||||
|
{
|
||||||
|
auto& queue = master_state.queued_servers;
|
||||||
|
if (queue.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> _(mutex);
|
||||||
|
|
||||||
|
size_t queried_servers = 0;
|
||||||
|
const size_t query_limit = 3;
|
||||||
|
|
||||||
|
for (auto i = queue.begin(); i != queue.end();)
|
||||||
|
{
|
||||||
|
if (i->second)
|
||||||
|
{
|
||||||
|
const auto now = game::Sys_Milliseconds();
|
||||||
|
if (now - i->second > 10'000)
|
||||||
|
{
|
||||||
|
i = queue.erase(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (queried_servers++ < query_limit)
|
||||||
|
{
|
||||||
|
i->second = game::Sys_Milliseconds();
|
||||||
|
network::send(i->first, "getInfo", utils::cryptography::random::get_challenge());
|
||||||
|
}
|
||||||
|
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_server_list_open()
|
||||||
|
{
|
||||||
|
return game::Menu_IsMenuOpenAndVisible(0, "menu_systemlink_join");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool scroll_down()
|
||||||
|
{
|
||||||
|
if (!is_server_list_open())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server_list_index + 15 < servers.size())
|
||||||
|
{
|
||||||
|
server_list_index += 15; // next page
|
||||||
|
server_list_page += 1;
|
||||||
|
trigger_refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool scroll_up()
|
||||||
|
{
|
||||||
|
if (!is_server_list_open())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server_list_index > 0)
|
||||||
|
{
|
||||||
|
server_list_index -= 15; // prev page
|
||||||
|
server_list_page -= 1;
|
||||||
|
trigger_refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resize_host_name(std::string& name)
|
||||||
|
{
|
||||||
|
size_t resize_size = 32;
|
||||||
|
name.resize(resize_size);
|
||||||
|
|
||||||
|
game::Font_s* font;
|
||||||
|
if (game::Com_GetCurrentCoDPlayMode() == game::CODPLAYMODE_ZOMBIES)
|
||||||
|
{
|
||||||
|
font = game::R_RegisterFont("fonts/zmBodyFont");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
font = game::R_RegisterFont("fonts/bodyFont");
|
||||||
|
}
|
||||||
|
auto text_size = game::UI_TextWidth(name.data(), 32, font, 1.0f);
|
||||||
|
|
||||||
|
while (text_size > 450)
|
||||||
|
{
|
||||||
|
text_size = game::UI_TextWidth(name.data(), 32, font, 1.0f);
|
||||||
|
name.resize(resize_size);
|
||||||
|
resize_size--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void lui_open_menu_stub(int /*controllerIndex*/, const char* /*menu*/, int /*a3*/, int /*a4*/,
|
||||||
|
unsigned int /*a5*/)
|
||||||
|
{
|
||||||
|
refresh_server_list();
|
||||||
|
game::Cmd_ExecuteSingleCommand(0, 0, "lui_open menu_systemlink_join\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sl_key_event(const int key, const int down)
|
||||||
|
{
|
||||||
|
if (down)
|
||||||
|
{
|
||||||
|
if (key == game::keyNum_t::K_MWHEELUP)
|
||||||
|
{
|
||||||
|
return !scroll_up();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key == game::keyNum_t::K_MWHEELDOWN)
|
||||||
|
{
|
||||||
|
return !scroll_down();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_master_server(game::netadr_s& address)
|
||||||
|
{
|
||||||
|
//return false;
|
||||||
|
return game::NET_StringToAdr("91.159.5.176:20810", &address);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_info_response(const game::netadr_s& address, const utils::info_string& info)
|
||||||
|
{
|
||||||
|
// Don't show servers that aren't running!
|
||||||
|
auto sv_running = std::atoi(info.get("sv_running").data());
|
||||||
|
if (!sv_running)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only handle servers of the same playmode!
|
||||||
|
auto playmode = game::CodPlayMode(std::atoi(info.get("playmode").data()));
|
||||||
|
if (game::Com_GetCurrentCoDPlayMode() != playmode)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int start_time{};
|
||||||
|
const auto now = game::Sys_Milliseconds();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> _(mutex);
|
||||||
|
const auto entry = master_state.queued_servers.find(address);
|
||||||
|
|
||||||
|
if (entry == master_state.queued_servers.end() || !entry->second)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
start_time = entry->second;
|
||||||
|
master_state.queued_servers.erase(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
server_info server{};
|
||||||
|
server.address = address;
|
||||||
|
server.host_name = info.get("hostname");
|
||||||
|
server.map_name = game::UI_GetMapDisplayName(info.get("mapname").data());
|
||||||
|
server.game_type = game::UI_GetGameTypeDisplayName(info.get("gametype").data());
|
||||||
|
server.play_mode = playmode;
|
||||||
|
server.clients = atoi(info.get("clients").data());
|
||||||
|
server.max_clients = atoi(info.get("sv_maxclients").data());
|
||||||
|
server.bots = atoi(info.get("bots").data());
|
||||||
|
server.ping = (now - start_time) > 999 ? 999 : (now - start_time);
|
||||||
|
|
||||||
|
server.in_game = 1;
|
||||||
|
|
||||||
|
resize_host_name(server.host_name);
|
||||||
|
|
||||||
|
insert_server(std::move(server));
|
||||||
|
}
|
||||||
|
|
||||||
|
class component final : public component_interface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void post_unpack() override
|
||||||
|
{
|
||||||
|
if (!game::environment::is_mp()) return;
|
||||||
|
|
||||||
|
localized_strings::override("PLATFORM_SYSTEM_LINK_TITLE", "SERVER LIST");
|
||||||
|
localized_strings::override("LUA_MENU_STORE", "Server List");
|
||||||
|
localized_strings::override("LUA_MENU_STORE_DESC", "Browse available servers.");
|
||||||
|
|
||||||
|
localized_strings::override("MENU_NUMPLAYERS", "Players");
|
||||||
|
|
||||||
|
// hook LUI_OpenMenu to show server list instead of store popup
|
||||||
|
utils::hook::call(0x1404D5550, &lui_open_menu_stub);
|
||||||
|
|
||||||
|
// replace UI_RunMenuScript call in LUI_CoD_LuaCall_RefreshServerList to our refresh_servers
|
||||||
|
utils::hook::call(0x1400F5AA1, &refresh_server_list);
|
||||||
|
utils::hook::call(0x1400F5F26, &join_server);
|
||||||
|
utils::hook::nop(0x1400F5F45, 5);
|
||||||
|
|
||||||
|
// do feeder stuff
|
||||||
|
utils::hook::call(0x1400F5B55, &ui_feeder_count);
|
||||||
|
utils::hook::call(0x1400F5D35, &ui_feeder_item_text);
|
||||||
|
|
||||||
|
scheduler::loop(do_frame_work, scheduler::pipeline::main);
|
||||||
|
|
||||||
|
network::on("getServersResponse", [](const game::netadr_s& target, const std::string_view& data)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> _(mutex);
|
||||||
|
if (!master_state.requesting || master_state.address != target)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
master_state.requesting = false;
|
||||||
|
|
||||||
|
std::optional<size_t> start{};
|
||||||
|
for (size_t i = 0; i + 6 < data.size(); ++i)
|
||||||
|
{
|
||||||
|
if (data[i + 6] == '\\')
|
||||||
|
{
|
||||||
|
start.emplace(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!start.has_value())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i = start.value(); i + 6 < data.size(); i += 7)
|
||||||
|
{
|
||||||
|
if (data[i + 6] != '\\')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
game::netadr_s address{};
|
||||||
|
address.type = game::NA_IP;
|
||||||
|
address.localNetID = game::NS_CLIENT1;
|
||||||
|
memcpy(&address.ip[0], data.data() + i + 0, 4);
|
||||||
|
memcpy(&address.port, data.data() + i + 4, 2);
|
||||||
|
|
||||||
|
master_state.queued_servers[address] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_COMPONENT(server_list::component)
|
12
src/client/component/server_list.hpp
Normal file
12
src/client/component/server_list.hpp
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "game/game.hpp"
|
||||||
|
#include <utils/info_string.hpp>
|
||||||
|
|
||||||
|
namespace server_list
|
||||||
|
{
|
||||||
|
bool get_master_server(game::netadr_s& address);
|
||||||
|
void handle_info_response(const game::netadr_s& address, const utils::info_string& info);
|
||||||
|
|
||||||
|
bool sl_key_event(int key, int down);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user