mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
Merge pull request #2 from salexandru1/master
ImGui Companion (Server Browser + Server hosting utils)
This commit is contained in:
commit
ab17fb44b8
1
.gitignore
vendored
1
.gitignore
vendored
@ -348,3 +348,4 @@ MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
.vscode/settings.json
|
||||
|
BIN
external/detours/lib.X64/detours.lib
vendored
Normal file
BIN
external/detours/lib.X64/detours.lib
vendored
Normal file
Binary file not shown.
BIN
external/detours/lib.X64/detours_trace.lib
vendored
Normal file
BIN
external/detours/lib.X64/detours_trace.lib
vendored
Normal file
Binary file not shown.
BIN
external/detours/lib.X64/syelog.lib
vendored
Normal file
BIN
external/detours/lib.X64/syelog.lib
vendored
Normal file
Binary file not shown.
7677
r5dev/include/httplib.h
Normal file
7677
r5dev/include/httplib.h
Normal file
File diff suppressed because it is too large
Load Diff
26154
r5dev/include/json.hpp
Normal file
26154
r5dev/include/json.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
#include "imgui.h"
|
||||
|
||||
#include "serverlisting.h"
|
||||
#include "json.hpp"
|
||||
#include <vector>
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Initialization
|
||||
void PrintDXAddress();
|
||||
@ -19,4 +21,68 @@ void Strtrim(char* s);
|
||||
// Globals
|
||||
inline ImVector<char*> Items;
|
||||
|
||||
inline std::string OriginUID = "1010417302770";
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using json = nlohmann::json;
|
||||
void RunConsoleCommand(std::string command);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// ServerBrowser
|
||||
|
||||
class CCompanion
|
||||
{
|
||||
public:
|
||||
enum class ESection {
|
||||
ServerBrowser,
|
||||
HostServer,
|
||||
Settings
|
||||
} CurrentSection;
|
||||
|
||||
enum class EHostStatus {
|
||||
NotHosting,
|
||||
WaitingForStateChange,
|
||||
Hosting,
|
||||
ConnectedToSomeoneElse
|
||||
};
|
||||
|
||||
CCompanion();
|
||||
////////////////////
|
||||
// Server Browser //
|
||||
////////////////////
|
||||
ImVector<ServerListing*> ServerList;
|
||||
ServerListing* SelectedServer;
|
||||
|
||||
ImGuiTextFilter ServerBrowserFilter;
|
||||
|
||||
char ServerConnStringBuffer[256] = { 0 };
|
||||
|
||||
////////////////////
|
||||
// Settings //
|
||||
////////////////////
|
||||
char MatchmakingServerStringBuffer[256] = { 0 };
|
||||
|
||||
|
||||
////////////////////
|
||||
// Host Server //
|
||||
////////////////////
|
||||
std::vector<std::string> MapsList;
|
||||
std::string* SelectedMap = nullptr;
|
||||
char ServerNameBuffer[64] = { 0 };
|
||||
bool StartAsDedi;
|
||||
EHostStatus HostingStatus = EHostStatus::NotHosting;
|
||||
|
||||
void RefreshServerList();
|
||||
|
||||
void SendHostingPostRequest();
|
||||
void SetSection(ESection section);
|
||||
void CompMenu();
|
||||
void ServerBrowserSection();
|
||||
void SettingsSection();
|
||||
void HostServerSection();
|
||||
void Draw(const char* title, bool* p_open);
|
||||
void UpdateHostingStatus();
|
||||
|
||||
std::string GetGameStateLastMap();
|
||||
};
|
19
r5dev/include/serverlisting.h
Normal file
19
r5dev/include/serverlisting.h
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
class ServerListing
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
std::string name;
|
||||
std::string map;
|
||||
std::string ip;
|
||||
std::string version;
|
||||
int expiry;
|
||||
|
||||
ServerListing(std::string name, std::string map, std::string ip, std::string version, int expiry);
|
||||
bool Select();
|
||||
};
|
||||
|
@ -291,12 +291,15 @@
|
||||
<ClInclude Include="include\enums.h" />
|
||||
<ClInclude Include="include\gameclasses.h" />
|
||||
<ClInclude Include="include\hooks.h" />
|
||||
<ClInclude Include="include\httplib.h" />
|
||||
<ClInclude Include="include\id3dx.h" />
|
||||
<ClInclude Include="include\input.h" />
|
||||
<ClInclude Include="include\json.hpp" />
|
||||
<ClInclude Include="include\opcptc.h" />
|
||||
<ClInclude Include="include\overlay.h" />
|
||||
<ClInclude Include="include\patterns.h" />
|
||||
<ClInclude Include="include\r5dev.h" />
|
||||
<ClInclude Include="include\serverlisting.h" />
|
||||
<ClInclude Include="include\structs.h" />
|
||||
<ClInclude Include="include\utility.h" />
|
||||
</ItemGroup>
|
||||
@ -315,6 +318,7 @@
|
||||
<ClCompile Include="src\input.cpp" />
|
||||
<ClCompile Include="src\opcptc.cpp" />
|
||||
<ClCompile Include="src\overlay.cpp" />
|
||||
<ClCompile Include="src\serverlisting.cpp" />
|
||||
<ClCompile Include="src\utility.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -99,6 +99,9 @@
|
||||
<ClCompile Include="src\input.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\serverlisting.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="include\console.h">
|
||||
@ -452,6 +455,15 @@
|
||||
<ClInclude Include="include\gameclasses.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\httplib.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\serverlisting.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\json.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="r5dev.def">
|
||||
|
@ -1,19 +1,26 @@
|
||||
|
||||
#include "overlay.h"
|
||||
#include <thread>
|
||||
#include <fstream>
|
||||
|
||||
#include <stdio.h>
|
||||
#include "httplib.h"
|
||||
#include <windows.h>
|
||||
#include <detours.h>
|
||||
|
||||
#include "hooks.h"
|
||||
#include "id3dx.h"
|
||||
#include "overlay.h"
|
||||
#include "console.h"
|
||||
#include "patterns.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_dx11.h"
|
||||
#include "imgui_impl_win32.h"
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
#include <sstream>
|
||||
|
||||
#include <shlobj.h>
|
||||
#include <objbase.h>
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* _overlay.cpp
|
||||
@ -49,6 +56,7 @@ public:
|
||||
Commands.push_back("CLEAR");
|
||||
Commands.push_back("CLASSIFY");
|
||||
|
||||
|
||||
AddLog("[DEBUG] THREAD ID: %ld\n", g_dThreadId);
|
||||
}
|
||||
~CGameConsole()
|
||||
@ -334,9 +342,13 @@ public:
|
||||
{// Auto focus previous widget
|
||||
ImGui::SetKeyboardFocusHere(-1);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Exec
|
||||
void ProcessCommand(const char* command_line)
|
||||
@ -442,6 +454,372 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
CCompanion::CCompanion()
|
||||
{
|
||||
memset(MatchmakingServerStringBuffer, 0, sizeof(MatchmakingServerStringBuffer));
|
||||
memset(ServerNameBuffer, 0, sizeof(ServerNameBuffer));
|
||||
memset(ServerConnStringBuffer, 0, sizeof(ServerConnStringBuffer));
|
||||
|
||||
|
||||
strcpy_s(MatchmakingServerStringBuffer, "r5a-comp-sv.herokuapp.com");
|
||||
|
||||
|
||||
std::string path = "stbsp";
|
||||
for (const auto& entry : std::filesystem::directory_iterator(path))
|
||||
{
|
||||
std::string filename = entry.path().string();
|
||||
int slashPos = filename.rfind("\\", std::string::npos);
|
||||
filename = filename.substr((INT8)slashPos + 1, std::string::npos);
|
||||
filename = filename.substr(0, filename.size() - 6);
|
||||
MapsList.push_back(filename);
|
||||
}
|
||||
|
||||
SelectedMap = &MapsList[0];
|
||||
|
||||
//RefreshServerList();
|
||||
|
||||
static std::thread HostingServerRequestThread([this]()
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
UpdateHostingStatus();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
|
||||
}
|
||||
});
|
||||
HostingServerRequestThread.detach();
|
||||
|
||||
}
|
||||
|
||||
void CCompanion::UpdateHostingStatus()
|
||||
{
|
||||
std::string lastMap = GetGameStateLastMap();
|
||||
|
||||
switch(HostingStatus)
|
||||
{
|
||||
case EHostStatus::NotHosting:
|
||||
{
|
||||
if(lastMap != "no_map" && lastMap != "")
|
||||
HostingStatus = EHostStatus::ConnectedToSomeoneElse;
|
||||
break;
|
||||
}
|
||||
case EHostStatus::WaitingForStateChange:
|
||||
{
|
||||
if(lastMap != "no_map" && lastMap != "")
|
||||
HostingStatus = EHostStatus::Hosting;
|
||||
break;
|
||||
}
|
||||
case EHostStatus::Hosting:
|
||||
{
|
||||
if (lastMap == "no_map" || lastMap == "")
|
||||
HostingStatus = EHostStatus::NotHosting;
|
||||
else SendHostingPostRequest();
|
||||
break;
|
||||
}
|
||||
case EHostStatus::ConnectedToSomeoneElse:
|
||||
{
|
||||
if(lastMap == "no_map" || lastMap == "")
|
||||
{
|
||||
HostingStatus = EHostStatus::NotHosting;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string CCompanion::GetGameStateLastMap()
|
||||
{
|
||||
|
||||
// I know you're gonna comment abt this function; but I dont think it's necessary to include a fully-fledged VDF parser for now
|
||||
|
||||
wchar_t* savedGamesPath = new wchar_t[256];
|
||||
|
||||
SHGetKnownFolderPath(FOLDERID_SavedGames, KF_FLAG_CREATE, nullptr, &savedGamesPath);
|
||||
|
||||
if (savedGamesPath == nullptr) return "";
|
||||
|
||||
std::wstringstream filePath;
|
||||
|
||||
filePath << savedGamesPath;
|
||||
filePath << "\\Respawn\\Apex\\local\\previousgamestate.txt";
|
||||
|
||||
CoTaskMemFree(static_cast<void*>(savedGamesPath));
|
||||
|
||||
std::ifstream f(filePath.str());
|
||||
std::string line;
|
||||
|
||||
|
||||
for(int i = 0; i < 7; i++)
|
||||
{
|
||||
std::getline(f, line);
|
||||
}
|
||||
|
||||
// returns the substring of the line that contains only the map name
|
||||
return line.substr(35, line.size() - 35 - 1);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void CCompanion::RefreshServerList()
|
||||
{
|
||||
ServerList.clear();
|
||||
|
||||
static bool bThreadLocked = false;
|
||||
|
||||
if (!bThreadLocked) {
|
||||
std::thread t([this]() {
|
||||
std::cout << "Refreshing server list with string" << MatchmakingServerStringBuffer << std::endl;
|
||||
bThreadLocked = true;
|
||||
httplib::Client client(MatchmakingServerStringBuffer);
|
||||
client.set_connection_timeout(10);
|
||||
auto res = client.Get("/servers");
|
||||
if (res)
|
||||
{
|
||||
json root = json::parse(res->body);
|
||||
for (auto obj : root["servers"])
|
||||
{
|
||||
ServerList.push_back(
|
||||
new ServerListing(obj["name"], obj["map"], obj["ip"], obj["version"], obj["expire"])
|
||||
);
|
||||
}
|
||||
}
|
||||
bThreadLocked = false;
|
||||
});
|
||||
|
||||
t.detach();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
void CCompanion::SendHostingPostRequest()
|
||||
{
|
||||
httplib::Client client(MatchmakingServerStringBuffer);
|
||||
client.set_connection_timeout(10);
|
||||
// send a post request to "/servers/add" with a json body
|
||||
json body = json::object();
|
||||
body["name"] = ServerNameBuffer;
|
||||
body["map"] = *SelectedMap;
|
||||
body["version"] = "1.0";
|
||||
|
||||
std::string body_str = body.dump();
|
||||
|
||||
std::cout << body_str << "\n";
|
||||
|
||||
auto res = client.Post("/servers/add", body_str.c_str(), body_str.length(), "application/json");
|
||||
if (res)
|
||||
{
|
||||
std::cout << "Hosting Request Result: " << res->body << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CCompanion::SetSection(ESection section)
|
||||
{
|
||||
CurrentSection = section;
|
||||
}
|
||||
|
||||
|
||||
void CCompanion::CompMenu()
|
||||
{
|
||||
ImGui::BeginTabBar("CompMenu");
|
||||
if (ImGui::TabItemButton("Server Browser"))
|
||||
{
|
||||
SetSection(ESection::ServerBrowser);
|
||||
}
|
||||
if (ImGui::TabItemButton("Host Server"))
|
||||
{
|
||||
SetSection(ESection::HostServer);
|
||||
}
|
||||
if (ImGui::TabItemButton("Settings"))
|
||||
{
|
||||
SetSection(ESection::Settings);
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
void CCompanion::ServerBrowserSection()
|
||||
{
|
||||
|
||||
ImGui::BeginGroup();
|
||||
ServerBrowserFilter.Draw();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Refresh List"))
|
||||
{
|
||||
RefreshServerList();
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::BeginChild("ServerListChild", { 0, 780 }, false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
|
||||
ImGui::BeginTable("bumbumceau", 6);
|
||||
|
||||
ImGui::TableSetupColumn("Name", 0, 35);
|
||||
ImGui::TableSetupColumn("IP Address", 0, 20);
|
||||
ImGui::TableSetupColumn("Map", 0, 25);
|
||||
ImGui::TableSetupColumn("Version", 0, 10);
|
||||
ImGui::TableSetupColumn("Expiry", 0, 10);
|
||||
ImGui::TableSetupColumn("", 0, 8);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
for (ServerListing* server : ServerList)
|
||||
{
|
||||
const char* name = server->name.c_str();
|
||||
const char* ip = server->ip.c_str();
|
||||
const char* map = server->map.c_str();
|
||||
const char* version = server->version.c_str();
|
||||
int expiry = server->expiry;
|
||||
|
||||
if (ServerBrowserFilter.PassFilter(name)
|
||||
|| ServerBrowserFilter.PassFilter(ip)
|
||||
|| ServerBrowserFilter.PassFilter(map)
|
||||
|| ServerBrowserFilter.PassFilter(version))
|
||||
{
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(name);
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
ImGui::Text(ip);
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
ImGui::Text(map);
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
ImGui::Text(version);
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
std::stringstream expirySS;
|
||||
expirySS << expiry << " seconds left";
|
||||
ImGui::Text(expirySS.str().c_str());
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
std::string selectButtonText = "Connect##";
|
||||
selectButtonText += (server->name + server->ip + server->map);
|
||||
|
||||
if (ImGui::Button(selectButtonText.c_str()))
|
||||
{
|
||||
SelectedServer = server;
|
||||
server->Select();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::InputTextWithHint("##ServerBrowser_ServerConnString", "Enter an ip address or \"localhost\"", ServerConnStringBuffer, IM_ARRAYSIZE(ServerConnStringBuffer));
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Connect to the server", ImVec2(ImGui::GetWindowSize().x * (1.f / 3.f), 19)))
|
||||
{
|
||||
std::stringstream cmd;
|
||||
cmd << "connect " << ServerConnStringBuffer;
|
||||
RunConsoleCommand(cmd.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void CCompanion::HostServerSection()
|
||||
{
|
||||
ImGui::InputTextWithHint("Server Name##ServerHost_ServerName", "Required Field", ServerNameBuffer, IM_ARRAYSIZE(ServerNameBuffer));
|
||||
ImGui::Spacing();
|
||||
if (ImGui::BeginCombo("Map##ServerHost_MapListBox", SelectedMap->c_str()))
|
||||
{
|
||||
for (auto& item : MapsList)
|
||||
{
|
||||
if (ImGui::Selectable(item.c_str(), &item == SelectedMap))
|
||||
{
|
||||
SelectedMap = &item;
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Checkbox("Start as dedicated server (HACK)##ServerHost_DediCheckbox", &StartAsDedi);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::Button("Start The Server##ServerHost_StartServerButton", ImVec2(ImGui::GetWindowSize().x, 32)))
|
||||
{
|
||||
|
||||
if (strcmp(ServerNameBuffer, "") != 0)
|
||||
{
|
||||
UpdateHostingStatus();
|
||||
if(HostingStatus == EHostStatus::NotHosting)
|
||||
HostingStatus = EHostStatus::WaitingForStateChange;
|
||||
UpdateHostingStatus();
|
||||
|
||||
std::stringstream cmd;
|
||||
cmd << "map " << SelectedMap->c_str();
|
||||
RunConsoleCommand(cmd.str());
|
||||
if (StartAsDedi)
|
||||
{
|
||||
ToggleDevCommands();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (strcmp(ServerNameBuffer, "") == 0) ImGui::TextColored(ImVec4(1.00f, 0.00f, 0.00f, 1.00f), "ERROR: Please specify a name for the server!");
|
||||
if (StartAsDedi)
|
||||
{
|
||||
if (ImGui::Button("Reload Scripts##ServerHost_ReloadServerButton", ImVec2(ImGui::GetWindowSize().x, 32)))
|
||||
{
|
||||
RunConsoleCommand("reparse_weapons");
|
||||
RunConsoleCommand("reload");
|
||||
}
|
||||
if (ImGui::Button("Stop The Server##ServerHost_StopServerButton", ImVec2(ImGui::GetWindowSize().x, 32)))
|
||||
{
|
||||
RunConsoleCommand("disconnect");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void CCompanion::SettingsSection()
|
||||
{
|
||||
ImGui::InputText("Matchmaking Server String", MatchmakingServerStringBuffer, IM_ARRAYSIZE(MatchmakingServerStringBuffer), 0);
|
||||
}
|
||||
|
||||
|
||||
void CCompanion::Draw(const char* title, bool* p_open)
|
||||
{
|
||||
ImGui::SetNextWindowSize(ImVec2(800, 890), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetWindowPos(ImVec2(-500, 50), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (!ImGui::Begin(title, NULL))
|
||||
{
|
||||
ImGui::End(); return;
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
CompMenu();
|
||||
|
||||
switch (CurrentSection)
|
||||
{
|
||||
case ESection::ServerBrowser:
|
||||
ServerBrowserSection();
|
||||
break;
|
||||
|
||||
case ESection::HostServer:
|
||||
HostServerSection();
|
||||
break;
|
||||
|
||||
case ESection::Settings:
|
||||
SettingsSection();
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
//#############################################################################
|
||||
// INTERNALS
|
||||
//#############################################################################
|
||||
@ -476,6 +854,7 @@ void Strtrim(char* s)
|
||||
char* str_end = s + strlen(s); while (str_end > s && str_end[-1] == ' ') str_end--; *str_end = 0;
|
||||
}
|
||||
|
||||
|
||||
//#############################################################################
|
||||
// ENTRYPOINT
|
||||
//#############################################################################
|
||||
@ -483,5 +862,14 @@ void Strtrim(char* s)
|
||||
void ShowGameConsole(bool* p_open)
|
||||
{
|
||||
static CGameConsole console;
|
||||
static CCompanion browser;
|
||||
console.Draw("Console", p_open);
|
||||
browser.Draw("Companion", p_open);
|
||||
}
|
||||
|
||||
|
||||
void RunConsoleCommand(std::string command)
|
||||
{
|
||||
static CGameConsole console;
|
||||
console.ProcessCommand(command.c_str());
|
||||
}
|
20
r5dev/src/serverlisting.cpp
Normal file
20
r5dev/src/serverlisting.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
#include "serverlisting.h"
|
||||
#include "overlay.h"
|
||||
#include "httplib.h"
|
||||
|
||||
ServerListing::ServerListing(std::string name, std::string map, std::string ip, std::string version, int expiry)
|
||||
{
|
||||
this->name = name;
|
||||
this->map = map;
|
||||
this->ip = ip;
|
||||
this->version = version;
|
||||
this->expiry = expiry;
|
||||
}
|
||||
|
||||
bool ServerListing::Select()
|
||||
{
|
||||
std::stringstream cmd;
|
||||
cmd << "connect " << this->ip;
|
||||
RunConsoleCommand(cmd.str().c_str());
|
||||
return true;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user