#include "main.hh" static void set_signal(const decltype(SIGINT) signal, void (*const callback)(int)) { struct sigaction sa {}; sa.sa_handler = callback; sa.sa_flags = 0; if (sigaction(signal, &sa, nullptr) == -1) { throw std::runtime_error("failed to set signal handler for signal " + std::to_string(signal)); } } // Parse arg, store result in dest after casting with boost::lexical_cast. template static bool maybe_parse_arg(std::vector& args, const std::initializer_list& keys, T& dest) noexcept { const auto find_it = std::ranges::find_if(args, [&keys](const auto& arg) { return std::ranges::find(keys, arg) != std::end(keys); }); if (find_it == std::end(args)) { return false; } try { const auto i = static_cast(find_it - std::begin(args)); dest = boost::lexical_cast(args.at(i + 1u)); } catch (const boost::bad_lexical_cast& e) { shared::print::fault("bad type conversion for \"" + std::string{*find_it} + "\" arg\n"); std::exit(EXIT_FAILURE); } catch (const std::out_of_range& e) { shared::print::fault("missing \"" + std::string{*find_it} + "\" arg\n"); std::exit(EXIT_FAILURE); } args.erase(find_it, std::next(find_it, 2)); return true; } // Parse arg, return true or false if arg exists. static bool maybe_parse_arg(std::vector& args, const std::initializer_list& keys) noexcept { const auto find_it = std::ranges::find_if(args, [&keys](const auto& arg) { return std::ranges::find(keys, arg) != std::end(keys); }); if (find_it == std::end(args)) { return false; } args.erase(find_it); return true; } static void do_server(const std::string_view address, const std::string_view port) noexcept { try { server::main(address, port); } catch (const std::exception& e) { shared::print::fault(std::string{"exception in server!\n"} + " what: " + e.what() + '\n'); std::exit(EXIT_FAILURE); } } static void do_client(const std::string_view address, const std::string_view port) noexcept { try { client::main(address, port); } catch (const std::exception& e) { shared::print::fault(std::string{"exception in client!\n"} + " what: " + e.what() + '\n'); std::exit(EXIT_FAILURE); } } static void init() noexcept { // Because we use noexcept liberally, exceptions are often not unwound. std::set_terminate([]() { try { throw; } catch (const std::exception& e) { shared::print::fault(std::string{"unhandled exception!\n"} + " what: " + e.what() + '\n'); constexpr std::size_t BACKTRACE_BUF_SIZE = 64; std::array bt_buffer{}; const int nptrs = backtrace(std::data(bt_buffer), std::size(bt_buffer)); char** strings = backtrace_symbols(std::data(bt_buffer), nptrs); shared::print::message("\n backtrace:\n", false); for (int i = 0; i < nptrs; ++i) { shared::print::message(" " + std::string{strings[i]} + '\n', false); } free(strings); // controversial } catch (...) { } std::exit(EXIT_FAILURE); }); set_signal(SIGPIPE, SIG_IGN); set_signal(SIGINT, []([[maybe_unused]] const int signum) { shared::print::warn(" interrupt signal received\n", false); shared::should_exit = true; // graceful cleanup }); #ifndef NDEBUG shared::print::debug( "This binary is a debug build and should not be used in production.\n", false); #endif } int main(const int argc, const char* const argv[]) { GOOGLE_PROTOBUF_VERIFY_VERSION; // Default argument values, which starts a multiplayer server with a client. std::string address = "0.0.0.0"; std::string port = "8191"; bool has_address = false; bool has_headless = false; { std::vector args; for (auto i = 1; i < argc; ++i) { args.emplace_back(argv[i]); } if (maybe_parse_arg(args, {"--help", "-h"})) { // clang-format off std::cout << " --help -h : display this, quit\n" " --address -a : connect elsewhere\n" " --port -p : override the default port\n" " --headless -H : run without a client\n" " --singleplayer -s : avoid hosting a lan server\n" " --seed -S : manually set the worldseed\n" " --directory -d : manually set the directory\n" " --tickrate -t : override the default tickrate\n" " --distance -D : set the max chunk distance the server provides\n"; // clang-format on std::exit(EXIT_SUCCESS); } has_address = maybe_parse_arg(args, {"--address", "-a"}, address); has_headless = maybe_parse_arg(args, {"--headless", "-H"}); maybe_parse_arg(args, {"--port", "-p"}, port); maybe_parse_arg(args, {"--seed", "-S"}, server::state.seed); maybe_parse_arg(args, {"--directory", "-d"}, server::state.directory); maybe_parse_arg(args, {"--tickrate", "-t"}, server::state.tickrate); maybe_parse_arg(args, {"--distance", "-D"}, server::state.draw_distance); if (maybe_parse_arg(args, {"--singleplayer", "-s"})) { if (has_address) { shared::print::warn("singleplayer argument ignored due " "to conflicting address argument\n"); } address = "localhost"; } std::ranges::for_each(args, [](const auto& arg) { shared::print::warn("unused argument \"" + std::string{arg} + "\"\n"); }); } init(); // Split server/client execution, while waiting for server to boot before // running the client. std::jthread server_thread; if (!has_address) { if (has_headless) { do_server(address, port); std::exit(EXIT_SUCCESS); } server_thread = std::jthread{do_server, address, port}; while (!server::has_initialised) { continue; } } do_client(address, port); }