mirror of
https://github.com/alterware/iw4-validator.git
synced 2025-06-27 06:41:59 +00:00
init
This commit is contained in:
248
src/component/console.cpp
Normal file
248
src/component/console.cpp
Normal file
@ -0,0 +1,248 @@
|
||||
#include "std_include.hpp"
|
||||
#include "console.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define COLOR_LOG_INFO 11
|
||||
#define COLOR_LOG_WARN 14
|
||||
#define COLOR_LOG_ERROR 12
|
||||
#define COLOR_LOG_DEBUG 15
|
||||
#else
|
||||
#define COLOR_LOG_INFO "\033[0;36m"
|
||||
#define COLOR_LOG_WARN "\033[0;33m"
|
||||
#define COLOR_LOG_ERROR "\033[0;31m"
|
||||
#define COLOR_LOG_DEBUG "\033[0m"
|
||||
#endif
|
||||
|
||||
namespace console
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::mutex signal_mutex;
|
||||
std::function<void()> signal_callback;
|
||||
|
||||
#ifdef _WIN32
|
||||
#define COLOR(win, posix) win
|
||||
using color_type = WORD;
|
||||
#else
|
||||
#define COLOR(win, posix) posix
|
||||
using color_type = const char*;
|
||||
#endif
|
||||
|
||||
const color_type color_array[] =
|
||||
{
|
||||
COLOR(0x8, "\033[0;90m"), // 0 - black
|
||||
COLOR(0xC, "\033[0;91m"), // 1 - red
|
||||
COLOR(0xA, "\033[0;92m"), // 2 - green
|
||||
COLOR(0xE, "\033[0;93m"), // 3 - yellow
|
||||
COLOR(0x9, "\033[0;94m"), // 4 - blue
|
||||
COLOR(0xB, "\033[0;96m"), // 5 - cyan
|
||||
COLOR(0xD, "\033[0;95m"), // 6 - pink
|
||||
COLOR(0xF, "\033[0;97m"), // 7 - white
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
BOOL WINAPI handler(const DWORD signal)
|
||||
{
|
||||
if (signal == CTRL_C_EVENT && signal_callback)
|
||||
{
|
||||
signal_callback();
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#else
|
||||
void handler(int signal)
|
||||
{
|
||||
if (signal == SIGINT && signal_callback)
|
||||
{
|
||||
signal_callback();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string format(va_list* ap, const char* message)
|
||||
{
|
||||
static thread_local char buffer[0x1000];
|
||||
|
||||
#ifdef _WIN32
|
||||
const int count = vsnprintf_s(buffer, _TRUNCATE, message, *ap);
|
||||
#else
|
||||
const int count = vsnprintf(buffer, sizeof(buffer), message, *ap);
|
||||
#endif
|
||||
|
||||
if (count < 0) return {};
|
||||
return {buffer, static_cast<size_t>(count)};
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE get_console_handle()
|
||||
{
|
||||
return GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
}
|
||||
#endif
|
||||
|
||||
void set_color(const color_type color)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
SetConsoleTextAttribute(get_console_handle(), color);
|
||||
#else
|
||||
printf("%s", color);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool apply_color(const std::string& data, const size_t index, const color_type base_color)
|
||||
{
|
||||
if (data[index] != '^' || (index + 1) >= data.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto code = data[index + 1] - '0';
|
||||
if (code < 0 || code > 11)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
code = std::min(code, 7); // Everything above white is white
|
||||
if (code == 7)
|
||||
{
|
||||
set_color(base_color);
|
||||
}
|
||||
else
|
||||
{
|
||||
set_color(color_array[code]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void print_colored(const std::string& line, const color_type base_color)
|
||||
{
|
||||
lock _{};
|
||||
set_color(base_color);
|
||||
|
||||
for (std::size_t i = 0; i < line.size(); ++i)
|
||||
{
|
||||
if (apply_color(line, i, base_color))
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::putchar(line[i]);
|
||||
}
|
||||
|
||||
reset_color();
|
||||
}
|
||||
}
|
||||
|
||||
lock::lock()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
_lock_file(stdout);
|
||||
#else
|
||||
flockfile(stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
lock::~lock()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
_unlock_file(stdout);
|
||||
#else
|
||||
funlockfile(stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
void reset_color()
|
||||
{
|
||||
lock _{};
|
||||
#ifdef _WIN32
|
||||
SetConsoleTextAttribute(get_console_handle(), 7);
|
||||
#else
|
||||
printf("\033[0m");
|
||||
#endif
|
||||
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void info(const char* message, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, message);
|
||||
|
||||
const auto data = format(&ap, message);
|
||||
print_colored("[+] " + data + "\n", COLOR_LOG_INFO);
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void warn(const char* message, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, message);
|
||||
|
||||
const auto data = format(&ap, message);
|
||||
print_colored("[!] " + data + "\n", COLOR_LOG_WARN);
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void error(const char* message, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, message);
|
||||
|
||||
const auto data = format(&ap, message);
|
||||
print_colored("[-] " + data + "\n", COLOR_LOG_ERROR);
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void log(const char* message, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, message);
|
||||
|
||||
const auto data = format(&ap, message);
|
||||
print_colored("[*] " + data + "\n", COLOR_LOG_DEBUG);
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void set_title(const std::string& title)
|
||||
{
|
||||
lock _{};
|
||||
|
||||
#ifdef _WIN32
|
||||
SetConsoleTitleA(title.data());
|
||||
#else
|
||||
printf("\033]0;%s\007", title.data());
|
||||
fflush(stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
signal_handler::signal_handler(std::function<void()> callback)
|
||||
: std::lock_guard<std::mutex>(signal_mutex)
|
||||
{
|
||||
signal_callback = std::move(callback);
|
||||
|
||||
#ifdef _WIN32
|
||||
SetConsoleCtrlHandler(handler, TRUE);
|
||||
#else
|
||||
signal(SIGINT, handler);
|
||||
#endif
|
||||
}
|
||||
|
||||
signal_handler::~signal_handler()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
SetConsoleCtrlHandler(handler, FALSE);
|
||||
#else
|
||||
signal(SIGINT, SIG_DFL);
|
||||
#endif
|
||||
|
||||
signal_callback = {};
|
||||
}
|
||||
}
|
32
src/component/console.hpp
Normal file
32
src/component/console.hpp
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
namespace console
|
||||
{
|
||||
class lock
|
||||
{
|
||||
public:
|
||||
lock();
|
||||
~lock();
|
||||
|
||||
lock(lock&&) = delete;
|
||||
lock(const lock&) = delete;
|
||||
lock& operator=(lock&&) = delete;
|
||||
lock& operator=(const lock&) = delete;
|
||||
};
|
||||
|
||||
void reset_color();
|
||||
|
||||
void info(const char* message, ...);
|
||||
void warn(const char* message, ...);
|
||||
void error(const char* message, ...);
|
||||
void log(const char* message, ...);
|
||||
|
||||
void set_title(const std::string& title);
|
||||
|
||||
class signal_handler : std::lock_guard<std::mutex>
|
||||
{
|
||||
public:
|
||||
signal_handler(std::function<void()> callback);
|
||||
~signal_handler();
|
||||
};
|
||||
}
|
72
src/component/map_rotation.cpp
Normal file
72
src/component/map_rotation.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
#include <std_include.hpp>
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
#include "map_rotation.hpp"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace map_rotation
|
||||
{
|
||||
rotation_data::rotation_data()
|
||||
: index_(0)
|
||||
{
|
||||
}
|
||||
|
||||
void rotation_data::randomize()
|
||||
{
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
|
||||
std::ranges::shuffle(this->rotation_entries_, gen);
|
||||
}
|
||||
|
||||
void rotation_data::add_entry(const std::string& key, const std::string& value)
|
||||
{
|
||||
this->rotation_entries_.emplace_back(std::make_pair(key, value));
|
||||
}
|
||||
|
||||
bool rotation_data::contains(const std::string& key, const std::string& value) const
|
||||
{
|
||||
return std::ranges::any_of(this->rotation_entries_, [&](const auto& entry)
|
||||
{
|
||||
return entry.first == key && entry.second == value;
|
||||
});
|
||||
}
|
||||
|
||||
bool rotation_data::empty() const noexcept
|
||||
{
|
||||
return this->rotation_entries_.empty();
|
||||
}
|
||||
|
||||
std::size_t rotation_data::get_entries_size() const noexcept
|
||||
{
|
||||
return this->rotation_entries_.size();
|
||||
}
|
||||
|
||||
rotation_data::rotation_entry& rotation_data::get_next_entry()
|
||||
{
|
||||
const auto index = this->index_;
|
||||
++this->index_ %= this->rotation_entries_.size();
|
||||
return this->rotation_entries_.at(index);
|
||||
}
|
||||
|
||||
void rotation_data::parse(const std::string& data)
|
||||
{
|
||||
const auto tokens = utils::string::split(data, ' ');
|
||||
for (std::size_t i = 0; !tokens.empty() && i < (tokens.size() - 1); i += 2)
|
||||
{
|
||||
const auto& key = tokens[i];
|
||||
const auto& value = tokens[i + 1];
|
||||
|
||||
if (key == "map"s || key == "gametype"s)
|
||||
{
|
||||
this->add_entry(key, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw map_rotation_parse_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
src/component/map_rotation.hpp
Normal file
32
src/component/map_rotation.hpp
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
namespace map_rotation
|
||||
{
|
||||
struct map_rotation_parse_error : public std::exception
|
||||
{
|
||||
[[nodiscard]] const char* what() const noexcept override { return "Map Rotation Parse Error"; }
|
||||
};
|
||||
|
||||
class rotation_data
|
||||
{
|
||||
public:
|
||||
using rotation_entry = std::pair<std::string, std::string>;
|
||||
|
||||
rotation_data();
|
||||
|
||||
void randomize();
|
||||
|
||||
void add_entry(const std::string& key, const std::string& value);
|
||||
|
||||
[[nodiscard]] bool contains(const std::string& key, const std::string& value) const;
|
||||
[[nodiscard]] bool empty() const noexcept;
|
||||
[[nodiscard]] std::size_t get_entries_size() const noexcept;
|
||||
[[nodiscard]] rotation_entry& get_next_entry();
|
||||
|
||||
void parse(const std::string& data);
|
||||
|
||||
private:
|
||||
std::vector<rotation_entry> rotation_entries_;
|
||||
std::size_t index_;
|
||||
};
|
||||
}
|
339
src/game/cg_client_side_effects_mp.cpp
Normal file
339
src/game/cg_client_side_effects_mp.cpp
Normal file
@ -0,0 +1,339 @@
|
||||
#include <std_include.hpp>
|
||||
|
||||
#include "component/console.hpp"
|
||||
|
||||
#include "cg_client_side_effects_mp.hpp"
|
||||
#include "q_shared.hpp"
|
||||
|
||||
#define MAX_CLIENT_ENT_SOUNDS 1024
|
||||
|
||||
#define SKIP_LINE(buffer, text) \
|
||||
if (match_line_starting_with((buffer), (text))) \
|
||||
{ \
|
||||
(buffer) = skip_line_starting_with((buffer), (text)); \
|
||||
}
|
||||
|
||||
#define SKIP_TEXT(buffer, text) \
|
||||
(buffer) = skip_text((buffer), (text)); \
|
||||
if (!(buffer)) \
|
||||
{ \
|
||||
return false; \
|
||||
}
|
||||
|
||||
#define SKIP_TEXT_PTR(buffer, text) \
|
||||
(buffer) = skip_text((buffer), (text)); \
|
||||
if (!(buffer)) \
|
||||
{ \
|
||||
return nullptr; \
|
||||
}
|
||||
|
||||
#define SKIP_WHITE_SPACE(buffer) \
|
||||
(buffer) = skip_white_space((buffer)); \
|
||||
if (!(buffer)) \
|
||||
{ \
|
||||
return false; \
|
||||
}
|
||||
|
||||
#define SKIP_WHITE_SPACE_PTR(buffer) \
|
||||
(buffer) = skip_white_space((buffer)); \
|
||||
if (!(buffer)) \
|
||||
{ \
|
||||
return nullptr; \
|
||||
}
|
||||
|
||||
#define PARSE_STRING_PTR(buffer, out) \
|
||||
(buffer) = parse_string_finish((buffer), (out), sizeof(out)); \
|
||||
if (!(buffer)) \
|
||||
{ \
|
||||
return nullptr; \
|
||||
}
|
||||
|
||||
#define PARSE_VEC3_PTR(buffer, out) \
|
||||
(buffer) = parse_vec3_finish((buffer), (out)); \
|
||||
if (!(buffer)) \
|
||||
{ \
|
||||
return nullptr; \
|
||||
}
|
||||
|
||||
namespace game
|
||||
{
|
||||
auto client_ent_sound_count = 0;
|
||||
|
||||
// Just report back to the user if there are too many
|
||||
void add_client_ent_sound(const float* origin, const char* sound_alias)
|
||||
{
|
||||
if (client_ent_sound_count == MAX_CLIENT_ENT_SOUNDS)
|
||||
{
|
||||
console::error("Unable to add %s at [%.2f, %.2f, %.2f]-> Too many client ent sounds. Reduce sounds or increase MAX_CLIENT_ENT_SOUNDS (%d).\n",
|
||||
sound_alias, origin[0], origin[1], origin[2], MAX_CLIENT_ENT_SOUNDS);
|
||||
return;
|
||||
}
|
||||
|
||||
++client_ent_sound_count;
|
||||
}
|
||||
|
||||
const char* skip_text(const char* line, const char* skip_text)
|
||||
{
|
||||
char error_text[128]{};
|
||||
|
||||
if (std::strncmp(skip_text, line, std::strlen(skip_text)) != 0)
|
||||
{
|
||||
I_strncpyz(error_text, line, sizeof(error_text));
|
||||
console::error("Unexpected text '%s' when trying to find '%s' in map's effect file\n", error_text, skip_text);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &line[std::strlen(skip_text)];
|
||||
}
|
||||
|
||||
const char* skip_white_space(const char* line)
|
||||
{
|
||||
while (std::isspace(static_cast<unsigned char>(*line)))
|
||||
{
|
||||
++line;
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
const char* skip_line_starting_with(const char* line, const char* skip_line)
|
||||
{
|
||||
const auto* result = skip_text(line, skip_line);
|
||||
if (!result)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (auto i = *result; i != '\n'; i = *++result)
|
||||
{
|
||||
if (i == '\r' || i == '\0')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return skip_white_space(result);
|
||||
}
|
||||
|
||||
bool match_line_starting_with(const char* line, const char* start_line)
|
||||
{
|
||||
return std::strncmp(start_line, line, std::strlen(start_line)) == 0;
|
||||
}
|
||||
|
||||
const char* parse_string(const char* line, char* text, unsigned int buffer_size)
|
||||
{
|
||||
char error_text[128]{};
|
||||
|
||||
if (*line != '"')
|
||||
{
|
||||
I_strncpyz(error_text, line, sizeof(error_text));
|
||||
console::error("Expected a quoted string instead of '%s'\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto c = line[1];
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; ((c != '\"' && (c != '\0')) && (i < buffer_size)); ++i)
|
||||
{
|
||||
text[i] = c;
|
||||
c = line[i + 2];
|
||||
}
|
||||
|
||||
if (i == buffer_size)
|
||||
{
|
||||
I_strncpyz(error_text, line, sizeof(error_text));
|
||||
console::error("String was longer than expected '%s'\n", error_text);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
text[i] = '\0';
|
||||
return &line[i + 2];
|
||||
}
|
||||
|
||||
const char* parse_string_finish(const char* line, char* text, unsigned int buffer_size)
|
||||
{
|
||||
line = parse_string(line, text, buffer_size);
|
||||
if (!line)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (auto i = *line; i != '\n'; i = *++line)
|
||||
{
|
||||
if (i == '\r' || i == '\0')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return skip_white_space(line);
|
||||
}
|
||||
|
||||
const char* parse_float_finish(const char* line, float* value)
|
||||
{
|
||||
char error_text[128]{};
|
||||
|
||||
if (std::sscanf(line, "%f", value) != 1)
|
||||
{
|
||||
I_strncpyz(error_text, line, sizeof(error_text));
|
||||
console::error("Expected a float instead of '%s'\n", error_text);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (auto i = *line; i != '\n'; i = *++line)
|
||||
{
|
||||
if (i == '\r' || i == '\0')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return skip_white_space(line);
|
||||
}
|
||||
|
||||
const char* parse_vec3_finish(const char* line, float* origin)
|
||||
{
|
||||
char error_text[128]{};
|
||||
|
||||
if (std::sscanf(line, "%f, %f, %f", origin, origin + 1, origin + 2) != 3)
|
||||
{
|
||||
I_strncpyz(error_text, line, sizeof(error_text));
|
||||
console::error("Expected 3 floats instead of '%s'\n", error_text);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (auto i = *line; i != '\n'; i = *++line)
|
||||
{
|
||||
if (i == '\r' || i == '\0')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return skip_white_space(line);
|
||||
}
|
||||
|
||||
const char* parse_sound(const char* line)
|
||||
{
|
||||
float origin[3], angles[3];
|
||||
char sound_alias[256]{};
|
||||
|
||||
SKIP_TEXT_PTR(line, "ent = createLoopSound();");
|
||||
|
||||
SKIP_WHITE_SPACE_PTR(line);
|
||||
|
||||
SKIP_TEXT_PTR(line, "ent.v[ \"origin\" ] = (");
|
||||
PARSE_VEC3_PTR(line, origin);
|
||||
|
||||
SKIP_TEXT_PTR(line, "ent.v[ \"angles\" ] = (");
|
||||
PARSE_VEC3_PTR(line, angles);
|
||||
|
||||
SKIP_TEXT_PTR(line, "ent.v[ \"soundalias\" ] = ");
|
||||
PARSE_STRING_PTR(line, sound_alias);
|
||||
|
||||
add_client_ent_sound(origin, sound_alias);
|
||||
return line;
|
||||
}
|
||||
|
||||
const char* parse_effect(const char* line)
|
||||
{
|
||||
float delay;
|
||||
float origin[3], angles[3];
|
||||
char sound_alias[256]{};
|
||||
char fx_def_name[256]{};
|
||||
|
||||
SKIP_TEXT_PTR(line, "ent = createOneshotEffect( ");
|
||||
PARSE_STRING_PTR(line, fx_def_name);
|
||||
|
||||
SKIP_TEXT_PTR(line, "ent.v[ \"origin\" ] = (");
|
||||
PARSE_VEC3_PTR(line, origin);
|
||||
|
||||
SKIP_TEXT_PTR(line, "ent.v[ \"angles\" ] = (");
|
||||
PARSE_VEC3_PTR(line, angles);
|
||||
|
||||
SKIP_TEXT_PTR(line, "ent.v[ \"fxid\" ] = ");
|
||||
PARSE_STRING_PTR(line, fx_def_name);
|
||||
|
||||
SKIP_TEXT_PTR(line, "ent.v[ \"delay\" ] = ");
|
||||
line = parse_float_finish(line, &delay);
|
||||
if (!line)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (match_line_starting_with(line, "ent.v[ \"soundalias\" ] = "))
|
||||
{
|
||||
SKIP_TEXT_PTR(line, "ent.v[ \"soundalias\" ] = ");
|
||||
PARSE_STRING_PTR(line, sound_alias);
|
||||
|
||||
add_client_ent_sound(origin, sound_alias);
|
||||
}
|
||||
|
||||
// Skip FX registration code
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
bool parse_client_effects(const char* buffer)
|
||||
{
|
||||
char error_text[128]{};
|
||||
|
||||
// Header section
|
||||
|
||||
SKIP_TEXT(buffer, "//_createfx generated. Do not touch!!");
|
||||
SKIP_WHITE_SPACE(buffer);
|
||||
SKIP_TEXT(buffer, "#include common_scripts\\utility;");
|
||||
SKIP_WHITE_SPACE(buffer);
|
||||
SKIP_TEXT(buffer, "#include common_scripts\\_createfx;");
|
||||
SKIP_WHITE_SPACE(buffer);
|
||||
|
||||
// Fake GSC section
|
||||
|
||||
SKIP_TEXT(buffer, "main()");
|
||||
SKIP_WHITE_SPACE(buffer);
|
||||
SKIP_TEXT(buffer, "{");
|
||||
SKIP_WHITE_SPACE(buffer);
|
||||
|
||||
SKIP_LINE(buffer, "//");
|
||||
|
||||
if (buffer)
|
||||
{
|
||||
while (*buffer != '}' && *buffer)
|
||||
{
|
||||
if (match_line_starting_with(buffer, "ent = createLoopSound();"))
|
||||
{
|
||||
buffer = parse_sound(buffer);
|
||||
if (!buffer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (match_line_starting_with(buffer, "ent = createOneshotEffect( "))
|
||||
{
|
||||
buffer = parse_effect(buffer);
|
||||
if (!buffer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
I_strncpyz(error_text, buffer, sizeof(error_text));
|
||||
console::error("Expected 'ent = createLoopSound();' or 'ent = createOneshotEffect' instead of '%s' in map's effect file\n", error_text);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer = skip_line_starting_with(buffer, "}");
|
||||
if (buffer && *buffer)
|
||||
{
|
||||
I_strncpyz(error_text, buffer, sizeof(error_text));
|
||||
console::error("Unexpected data after parsing '%s' map's effect file\n", error_text);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
6
src/game/cg_client_side_effects_mp.hpp
Normal file
6
src/game/cg_client_side_effects_mp.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace game
|
||||
{
|
||||
bool parse_client_effects(const char* buffer);
|
||||
}
|
39
src/game/q_shared.cpp
Normal file
39
src/game/q_shared.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
#include <std_include.hpp>
|
||||
|
||||
#include "q_shared.hpp"
|
||||
|
||||
namespace game
|
||||
{
|
||||
void I_strncpyz(char* dest, const char* src, std::size_t dest_size)
|
||||
{
|
||||
assert(src);
|
||||
assert(dest);
|
||||
|
||||
if (!dest)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!src)
|
||||
{
|
||||
*dest = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
auto* p = reinterpret_cast<const unsigned char*>(src - 1);
|
||||
auto* q = reinterpret_cast<unsigned char*>(dest - 1);
|
||||
auto n = dest_size + 1;
|
||||
do
|
||||
{
|
||||
if (!--n)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
*++q = *++p;
|
||||
} while (*q);
|
||||
|
||||
dest[dest_size - 1] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
6
src/game/q_shared.hpp
Normal file
6
src/game/q_shared.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace game
|
||||
{
|
||||
void I_strncpyz(char* dest, const char* src, std::size_t dest_size);
|
||||
}
|
125
src/main.cpp
Normal file
125
src/main.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
#include "std_include.hpp"
|
||||
|
||||
#include "component/console.hpp"
|
||||
#include "component/map_rotation.hpp"
|
||||
|
||||
#include "game/cg_client_side_effects_mp.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
bool load_client_effects(const std::string& filename)
|
||||
{
|
||||
if (filename.empty())
|
||||
{
|
||||
console::error("filename parameter is empty\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto data = utils::io::read_file(filename);
|
||||
if (data.empty())
|
||||
{
|
||||
console::error("'%s' is empty\n", filename.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
return game::parse_client_effects(data.data());
|
||||
}
|
||||
|
||||
bool load_map_rotation(const std::string& filename)
|
||||
{
|
||||
if (filename.empty())
|
||||
{
|
||||
console::error("filename parameter is empty\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto data = utils::io::read_file(filename);
|
||||
if (data.empty())
|
||||
{
|
||||
console::error("'%s' is empty\n", filename.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
map_rotation::rotation_data rotation_data;
|
||||
rotation_data.parse(data);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
console::error("%s: '%s' contains invalid data!\n", ex.what(), filename.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
console::info("Successfully parsed map rotation\n");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int unsafe_main(std::string&& prog, std::vector<std::string>&& args)
|
||||
{
|
||||
// Parse command-line flags (only increment i for matching flags)
|
||||
for (auto i = args.begin(); i != args.end();)
|
||||
{
|
||||
if (*i == "-createfx")
|
||||
{
|
||||
++i;
|
||||
const auto filename = i != args.end() ? *i++ : std::string();
|
||||
console::info("Parsing createfx '%s'\n", filename.data());
|
||||
|
||||
if (!load_client_effects(filename))
|
||||
{
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
else if (*i == "-map-rotation")
|
||||
{
|
||||
++i;
|
||||
const auto filename = i != args.end() ? *i++ : std::string();
|
||||
console::info("Parsing map rotation '%s'\n", filename.data());
|
||||
|
||||
if (!load_map_rotation(filename))
|
||||
{
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
console::info("X Labs IW4x validator tool\n"
|
||||
"Usage: %s OPTIONS\n"
|
||||
" -createfx <filename>\n"
|
||||
" -fx <filename>\n"
|
||||
" -map-rotation <filename>\n",
|
||||
prog.data()
|
||||
);
|
||||
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
console::set_title("X Labs IW4x-validator");
|
||||
console::log("Starting X Labs IW4x-validator");
|
||||
|
||||
try
|
||||
{
|
||||
std::string prog(argv[0]);
|
||||
std::vector<std::string> args;
|
||||
|
||||
args.reserve(argc - 1);
|
||||
args.assign(argv + 1, argv + argc);
|
||||
return unsafe_main(std::move(prog), std::move(args));
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
console::error("Fatal error: %s\n", ex.what());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
1
src/std_include.cpp
Normal file
1
src/std_include.cpp
Normal file
@ -0,0 +1 @@
|
||||
#include <std_include.hpp>
|
41
src/std_include.hpp
Normal file
41
src/std_include.hpp
Normal file
@ -0,0 +1,41 @@
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#else
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#endif
|
||||
|
||||
// min and max is required by gdi, therefore NOMINMAX won't work
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <csignal>
|
||||
#include <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <ranges>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <vector>
|
92
src/utils/io.cpp
Normal file
92
src/utils/io.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
#include <std_include.hpp>
|
||||
#include "io.hpp"
|
||||
|
||||
namespace utils
|
||||
{
|
||||
namespace io
|
||||
{
|
||||
bool remove_file(const std::string& file)
|
||||
{
|
||||
return std::remove(file.data()) == 0;
|
||||
}
|
||||
|
||||
bool move_file(const std::string& src, const std::string& target)
|
||||
{
|
||||
return std::rename(src.data(), target.data()) == 0;
|
||||
}
|
||||
|
||||
bool file_exists(const std::string& file)
|
||||
{
|
||||
return std::ifstream(file).good();
|
||||
}
|
||||
|
||||
bool write_file(const std::string& file, const std::string& data, const bool append)
|
||||
{
|
||||
auto mode = std::ios::binary | std::ofstream::out;
|
||||
if (append)
|
||||
{
|
||||
mode |= std::ofstream::app;
|
||||
}
|
||||
|
||||
std::ofstream stream(file, mode);
|
||||
|
||||
if (stream.is_open())
|
||||
{
|
||||
stream.write(data.data(), static_cast<std::streamsize>(data.size()));
|
||||
stream.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string read_file(const std::string& file)
|
||||
{
|
||||
std::string data;
|
||||
read_file(file, &data);
|
||||
return data;
|
||||
}
|
||||
|
||||
bool read_file(const std::string& file, std::string* data)
|
||||
{
|
||||
if (!data) return false;
|
||||
data->clear();
|
||||
|
||||
if (file_exists(file))
|
||||
{
|
||||
std::ifstream stream(file, std::ios::binary);
|
||||
if (!stream.is_open()) return false;
|
||||
|
||||
stream.seekg(0, std::ios::end);
|
||||
const std::streamsize size = stream.tellg();
|
||||
stream.seekg(0, std::ios::beg);
|
||||
|
||||
if (size > -1)
|
||||
{
|
||||
data->resize(static_cast<std::uint32_t>(size));
|
||||
stream.read(const_cast<char*>(data->data()), size);
|
||||
stream.close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t file_size(const std::string& file)
|
||||
{
|
||||
if (file_exists(file))
|
||||
{
|
||||
std::ifstream stream(file, std::ios::binary);
|
||||
|
||||
if (stream.good())
|
||||
{
|
||||
stream.seekg(0, std::ios::end);
|
||||
return static_cast<std::size_t>(stream.tellg());
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
17
src/utils/io.hpp
Normal file
17
src/utils/io.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
namespace io
|
||||
{
|
||||
bool remove_file(const std::string& file);
|
||||
bool move_file(const std::string& src, const std::string& target);
|
||||
bool file_exists(const std::string& file);
|
||||
bool write_file(const std::string& file, const std::string& data, bool append = false);
|
||||
bool read_file(const std::string& file, std::string* data);
|
||||
std::string read_file(const std::string& file);
|
||||
std::size_t file_size(const std::string& file);
|
||||
}
|
||||
}
|
41
src/utils/string.cpp
Normal file
41
src/utils/string.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#include <std_include.hpp>
|
||||
#include "string.hpp"
|
||||
|
||||
namespace utils::string
|
||||
{
|
||||
std::vector<std::string> split(const std::string& s, const char delim)
|
||||
{
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
std::vector<std::string> elems;
|
||||
|
||||
while (std::getline(ss, item, delim))
|
||||
{
|
||||
elems.push_back(item); // elems.push_back(std::move(item)); // if C++11
|
||||
}
|
||||
|
||||
return elems;
|
||||
}
|
||||
|
||||
std::string to_lower(const std::string& text)
|
||||
{
|
||||
std::string result;
|
||||
std::ranges::transform(text, std::back_inserter(result), [](const unsigned char input)
|
||||
{
|
||||
return static_cast<char>(std::tolower(input));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string to_upper(const std::string& text)
|
||||
{
|
||||
std::string result;
|
||||
std::ranges::transform(text, std::back_inserter(result), [](const unsigned char input)
|
||||
{
|
||||
return static_cast<char>(std::toupper(input));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
9
src/utils/string.hpp
Normal file
9
src/utils/string.hpp
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
namespace utils::string
|
||||
{
|
||||
std::vector<std::string> split(const std::string& s, char delim);
|
||||
|
||||
std::string to_lower(const std::string& text);
|
||||
std::string to_upper(const std::string& text);
|
||||
}
|
Reference in New Issue
Block a user