Skip to content

Commit

Permalink
re-add files
Browse files Browse the repository at this point in the history
  • Loading branch information
radj307 committed Dec 1, 2021
1 parent c54edcc commit 210eac5
Show file tree
Hide file tree
Showing 12 changed files with 1,061 additions and 0 deletions.
11 changes: 11 additions & 0 deletions ARRCON/CMakeLists.txt
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.
58 changes: 58 additions & 0 deletions ARRCON/Help.hpp
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();
}
};
72 changes: 72 additions & 0 deletions ARRCON/arg-handlers.hpp
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);
}
72 changes: 72 additions & 0 deletions ARRCON/config.hpp
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);
}
}
79 changes: 79 additions & 0 deletions ARRCON/globals.h
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()) };
}

Loading

0 comments on commit 210eac5

Please sign in to comment.