forked from radj307/ARRCON
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
1,061 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# CMakeList.txt : CMake project for ARRCON-CMake, include source and define | ||
# project specific logic here. | ||
# | ||
cmake_minimum_required (VERSION 3.13) | ||
|
||
# Add source to this project's executable. | ||
add_executable (ARRCON "main.cpp") | ||
#set_target_properties(ARRCON PROPERTIES LINKER_LANGUAGE CXX) | ||
target_include_directories(ARRCON INTERFACE .) | ||
|
||
# TODO: Add tests and install targets if needed. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/** | ||
* @file Help.hpp | ||
* @author radj307 | ||
* @brief Contains the Help object, which contains the help display menu & hard-coded options. | ||
*/ | ||
#pragma once | ||
#include "globals.h" | ||
|
||
/** | ||
* @struct Help | ||
* @brief Functor that prints out the help display with auto-formatting. | ||
*/ | ||
struct Help { | ||
private: | ||
const std::string _program_name; | ||
const std::vector<std::pair<std::string, std::string>> _options{ | ||
{ "-H <Host> --host <Host>"s, "RCON Server Address. (Default: \""s + Global.DEFAULT_HOST + "\")"s }, | ||
{ "-P <Port> --port <Port>"s, "RCON Server Port. (Default: \""s + Global.DEFAULT_PORT + "\")"s }, | ||
{ "-p <Password> --password <Pass>"s, "RCON Server Password." }, | ||
{ "-f <file> --file <file>"s, "Load the specified file and run each line as a command."s }, | ||
{ "-h --help"s, "Show the help display."s }, | ||
{ "-v --version"s, "Print the current version number."s }, | ||
{ "-i --interactive"s, "Start interactive mode after sending all commands specified on the commandline."s}, | ||
{ "-q --quiet"s, "Don't print server response packets."s }, | ||
{ "-d <ms> --delay <ms>"s, "Time in milliseconds to wait between each command in commandline mode."s }, | ||
{ "-n --no-color"s, "Disable colorized console output."s }, | ||
{ "--no-prompt"s,"Hides the prompt in interactive mode."s}, | ||
{ "--write-ini"s, "(Over)write the configuration file with the default values & exit."s}, | ||
}; | ||
const size_t _longest_optname, _max_line_length; | ||
|
||
public: | ||
Help(const std::string& program_name, const size_t& max_line_sz = 120ull) : _program_name{ program_name }, _longest_optname{ [this]() { size_t longest{0ull}; for (auto& [optname, desc] : _options) if (const auto sz{optname.size()}; sz > longest) longest = sz; return longest; }() }, _max_line_length{ max_line_sz } {} | ||
friend std::ostream& operator<<(std::ostream& os, const Help& help) | ||
{ | ||
const auto tabSize{ help._longest_optname + 2ull }; | ||
os << help._program_name << ((help._program_name != DEFAULT_PROGRAM_NAME) ? "("s + DEFAULT_PROGRAM_NAME + ") "s : " ") << "v" << VERSION << '\n' | ||
<< "CLI Application that allows communicating with servers using the Source RCON Protocol.\n" | ||
<< '\n' | ||
<< "USAGE:\n" | ||
<< " " << help._program_name << " [OPTIONS] [COMMANDS]\n" | ||
<< '\n' | ||
<< "OPTIONS:\n"; | ||
for (auto& [optname, desc] : help._options) | ||
os << " " << optname << str::VIndent(tabSize + 2ull, optname.size()) << desc << '\n'; | ||
os << '\n' | ||
<< "MODES:\n" | ||
<< " [1]\tInteractive\tInteractive shell prompt. This is the default mode.\n" | ||
<< " [2]\tCommandline\tExecutes commands passed on the commandline.\n" | ||
<< "\t\t\tThis mode is automatically used when commandline input is detected (Excluding OPTIONS).\n" | ||
<< "\t\t\tYou can also specify files using \"-f <file>\" or \"--file <file>\".\n" | ||
<< "\t\t\tEach line will be executed as a command in commandline mode.\n" | ||
<< "\t\t\tScript commands are executed in - order, after any commands passed as arguments.\n" | ||
<< "\t\t\tYou can specify multiple files per command.\n" | ||
; | ||
return os.flush(); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
#pragma once | ||
#include "globals.h" | ||
#include "Help.hpp" | ||
#include <ParamsAPI2.hpp> | ||
#include <env.hpp> | ||
|
||
/** | ||
* @brief Retrieve the user's specified connection target. | ||
* @param args Arguments from main(). | ||
* @returns std::tuple<std::string, std::string, std::string> | ||
*\n 0 RCON Hostname | ||
*\n 1 RCON Port | ||
*\n 2 RCON Password | ||
*/ | ||
inline std::tuple<std::string, std::string, std::string> get_server_info(const opt::ParamsAPI2& args) | ||
{ | ||
return{ | ||
args.typegetv<opt::Flag>('H').value_or(Global.DEFAULT_HOST), // hostname | ||
args.typegetv<opt::Flag>('P').value_or(Global.DEFAULT_PORT), // port | ||
args.typegetv<opt::Flag>('p').value_or(Global.DEFAULT_PASS) // password | ||
}; | ||
} | ||
|
||
/** | ||
* @brief Handle commandline arguments. | ||
* @param args Arguments from main() | ||
*/ | ||
inline void handle_args(const opt::ParamsAPI2& args, const std::string& program_name) | ||
{ | ||
// help: | ||
if (args.check_any<opt::Flag, opt::Option>('h', "help")) { | ||
std::cout << Help(program_name); | ||
std::exit(EXIT_SUCCESS); | ||
} | ||
// version: | ||
else if (args.check_any<opt::Flag, opt::Option>('v', "version")) { | ||
std::cout << DEFAULT_PROGRAM_NAME << " v" << VERSION << std::endl; | ||
std::exit(EXIT_SUCCESS); | ||
} | ||
// write-ini: | ||
if (args.check_any<opt::Option>("write-ini")) { | ||
if (!Global.ini_path.empty() && config::write_default_config(Global.ini_path)) { | ||
std::cout << "Successfully wrote to config: \"" << Global.ini_path << '\"' << std::endl; | ||
std::exit(EXIT_SUCCESS); | ||
} | ||
else throw std::exception(("I/O operation failed: \""s + Global.ini_path + "\" couldn't be written to."s).c_str()); | ||
} | ||
// quiet: | ||
if (args.check_any<opt::Option, opt::Flag>('q', "quiet")) | ||
Global.quiet = true; | ||
// no-prompt | ||
if (args.check_any<opt::Option>("no-prompt")) | ||
Global.no_prompt = true; | ||
// force interactive: | ||
if (args.check_any<opt::Option, opt::Flag>('i', "interactive")) | ||
Global.force_interactive = true; | ||
// command delay: | ||
if (const auto arg{ args.typegetv_any<opt::Flag, opt::Option>('d', "delay") }; arg.has_value()) { | ||
if (std::all_of(arg.value().begin(), arg.value().end(), isdigit)) { | ||
if (const auto t{ std::chrono::milliseconds(std::abs(str::stoll(arg.value()))) }; t <= MAX_DELAY) | ||
Global.command_delay = t; | ||
else throw std::exception(("Cannot set a delay value longer than "s + std::to_string(MAX_DELAY.count()) + " hours!"s).c_str()); | ||
} | ||
else throw std::exception(("Invalid delay value given: \""s + arg.value() + "\", expected an integer."s).c_str()); | ||
} | ||
// disable colors: | ||
if (const auto arg{ args.typegetv_any<opt::Option, opt::Flag>('n', "no-color") }; arg.has_value()) | ||
Global.palette.setActive(false); | ||
// scriptfiles: | ||
for (auto& scriptfile : args.typegetv_all<opt::Option, opt::Flag>('f', "file")) | ||
Global.scriptfiles.emplace_back(scriptfile); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/** | ||
* @file config.hpp | ||
* @author radj307 | ||
* @brief Contains the config namespace, and the configuration object that reads the INI config file. | ||
*/ | ||
#pragma once | ||
#include "globals.h" | ||
|
||
#define USE_DEPRECATED_INI | ||
#include <INI.hpp> | ||
|
||
namespace config { | ||
inline file::ini::INI read_config(const std::string& filename) noexcept(false) | ||
{ | ||
return (file::exists(filename) ? file::ini::INI(filename) : file::ini::INI{}); | ||
} | ||
|
||
inline constexpr const auto HEADER_APPEARANCE{ "appearance" }, HEADER_TIMING{ "timing" }, HEADER_TARGET{ "target" }; | ||
|
||
inline auto apply_config(const std::string& filename) | ||
{ | ||
using namespace std::chrono_literals; | ||
if (const auto ini{ read_config(filename) }; !ini.empty()) { | ||
// Appearance: | ||
Global.no_prompt = str::string_to_bool(ini.getv("appearance", "bDisablePrompt").value_or("")).value_or(false); | ||
if (ini.checkv("appearance", "bDisableColors", "true", false)) | ||
Global.palette.setActive(false); | ||
Global.custom_prompt = ini.getv("appearance", "sCustomPrompt").value_or(""); | ||
// Timing: | ||
const auto to_ms{ [](const std::optional<std::string>& str, const std::chrono::milliseconds& def) -> std::chrono::milliseconds { return ((str.has_value() && std::all_of(str.value().begin(), str.value().end(), isdigit)) ? std::chrono::milliseconds(str::stoi(str.value())) : def); } }; | ||
Global.command_delay = to_ms(ini.getv("timing", "iCommandDelay"), 0ms); | ||
Global.receive_delay = to_ms(ini.getv("timing", "iReceiveDelay"), 10ms); | ||
Global.select_timeout = to_ms(ini.getv("timing", "iSelectTimeout"), 500ms); | ||
// Target: | ||
Global.DEFAULT_HOST = ini.getv("target", "sHost").value_or(Global.DEFAULT_HOST); | ||
Global.DEFAULT_PORT = ini.getv("target", "sPort").value_or(Global.DEFAULT_PORT); | ||
Global.DEFAULT_PASS = ini.getv("target", "sPass").value_or(Global.DEFAULT_PASS); | ||
} | ||
} | ||
|
||
namespace _internal { | ||
struct MakeHeader { | ||
const std::string _str; | ||
constexpr MakeHeader(const std::string& str) : _str{ str } {} | ||
friend std::ostream& operator<<(std::ostream& os, const MakeHeader& h) { return os << '[' << h._str << ']' << '\n'; } | ||
}; | ||
} | ||
|
||
inline auto write_default_config(const std::string& filename, const bool& append = false) | ||
{ | ||
using namespace _internal; | ||
std::stringstream ss; | ||
ss | ||
<< MakeHeader(HEADER_TARGET) | ||
<< "sHost = 127.0.0.1\n" | ||
<< "sPort = 27015\n" | ||
<< "sPass = \n" | ||
<< '\n' | ||
<< MakeHeader(HEADER_APPEARANCE) | ||
<< "bDisablePrompt = false\n" | ||
<< "bDisableColors = false\n" | ||
<< "sCustomPrompt =\n" | ||
<< '\n' | ||
<< MakeHeader(HEADER_TIMING) | ||
<< "iCommandDelay = 0\n" | ||
<< "iReceiveDelay = 10\n" | ||
<< "iSelectTimeout = 500\n" | ||
<< '\n'; | ||
|
||
return file::write(filename, ss, append); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/** | ||
* @file globals.h | ||
* @author radj307 | ||
* @brief Contains global variables used in other files. | ||
*/ | ||
#pragma once | ||
#include <ColorPalette.hpp> | ||
|
||
#include <../libunistd/unistd/sys/socket.h> | ||
#include <chrono> | ||
|
||
inline constexpr const auto | ||
VERSION{ "1.2.0" }, | ||
DEFAULT_PROGRAM_NAME{ "ARRCON.exe" }; | ||
|
||
/** | ||
* @enum UIElem | ||
* @brief Defines various UI elements, used by the color palette to select appropriate colors. | ||
*/ | ||
enum class UIElem : unsigned char { | ||
TERM_PROMPT_NAME, // interactive mode prompt name | ||
TERM_PROMPT_ARROW, // interactive mode prompt arrow '>' | ||
PACKET, // interactive mode response | ||
COMMAND_ECHO, // commandline mode command echo | ||
}; | ||
|
||
inline constexpr const auto MAX_DELAY{ std::chrono::hours(24) }; | ||
|
||
static struct { | ||
/// @brief Color palette | ||
color::ColorPalette<UIElem> palette{ | ||
std::make_pair(UIElem::TERM_PROMPT_NAME, color::setcolor{ color::green, color::FormatFlag::BOLD }), | ||
std::make_pair(UIElem::TERM_PROMPT_ARROW, color::green), | ||
std::make_pair(UIElem::PACKET, color::white), | ||
std::make_pair(UIElem::COMMAND_ECHO, color::green), | ||
}; | ||
std::string | ||
DEFAULT_HOST{ "localhost" }, | ||
DEFAULT_PORT{ "27015" }, | ||
DEFAULT_PASS{ "" }; | ||
|
||
|
||
/// @brief When true, response packets are not printed to the terminal | ||
bool quiet{ false }; | ||
|
||
/// @brief When true, hides the prompt in interactive mode. | ||
bool no_prompt{ false }; | ||
|
||
std::string custom_prompt{}; | ||
|
||
/// @brief When true, the RCON socket is currently connected. | ||
bool connected{ false }; | ||
|
||
/// @brief Delay between sending each command when using commandline mode. | ||
std::chrono::milliseconds command_delay{ 0ll }; | ||
|
||
/// @brief Delay between receive calls. Changing this may break or fix multi-packet response handling. (Default is 10) | ||
std::chrono::milliseconds receive_delay{ 10ll }; | ||
|
||
/// @brief amount of time before the select() function times out. | ||
std::chrono::milliseconds select_timeout{ 500ll }; | ||
|
||
std::string ini_path; | ||
|
||
/// @brief Global socket connected to the RCON server. | ||
SOCKET socket{ static_cast<SOCKET>(SOCKET_ERROR) }; | ||
|
||
/// @brief When true, interactive mode is started after running any commands specified on the commandline. | ||
bool force_interactive{ false }; | ||
|
||
/// @brief When entries are present, the user specified at least one [-f|--file] option. | ||
std::vector<std::string> scriptfiles{}; | ||
} Global; | ||
|
||
inline timeval duration_to_timeval(const std::chrono::milliseconds& ms) | ||
{ | ||
return{ static_cast<long>(std::trunc(Global.select_timeout.count() / 1000ll)), static_cast<long>(Global.select_timeout.count()) }; | ||
} | ||
|
Oops, something went wrong.