Skip to content

Commit

Permalink
rework config loading some more
Browse files Browse the repository at this point in the history
realized we can't safely expose a string view cross dll (in the config struct)
axing the config struct makes this a lot more similar to the old code
it also means we're not storing an extra copy of half the values
  • Loading branch information
apple1417 committed Nov 24, 2024
1 parent dd3fc8d commit bb49b6c
Show file tree
Hide file tree
Showing 16 changed files with 147 additions and 175 deletions.
185 changes: 89 additions & 96 deletions src/unrealsdk/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,24 @@
namespace unrealsdk::config {

#pragma region Config file loading
#ifndef UNREALSDK_IMPORTING

namespace {

const constexpr auto CONFIG_FILE_ENV_VAR = "UNREALSDK_CONFIG_FILE";
const constexpr auto DEFAULT_CONFIG_FILE_NAME = "unrealsdk.toml";

#ifndef UNREALSDK_IMPORTING
toml::table merged_raw_config{};
Config config{};
#endif

} // namespace

namespace {
std::filesystem::path get_base_config_file_path(void) {
static std::optional<std::filesystem::path> path = std::nullopt;
if (path.has_value()) {
return *path;
}

/**
* @brief Get the path of the base config file.
*
* @return The config file path.
*/
std::filesystem::path get_config_file_path(void) {
std::string filename = DEFAULT_CONFIG_FILE_NAME;

auto num_chars = GetEnvironmentVariableA(CONFIG_FILE_ENV_VAR, nullptr, 0);
Expand All @@ -56,9 +54,24 @@ std::filesystem::path get_config_file_path(void) {
}
}

return utils::get_this_dll().parent_path() / filename;
path = utils::get_this_dll().parent_path() / filename;
return *path;
}

std::filesystem::path get_user_config_file_path(void) {
static std::optional<std::filesystem::path> path = std::nullopt;
if (path.has_value()) {
return *path;
}

auto base_path = get_base_config_file_path();
path = base_path.replace_extension(".user" + base_path.extension().string());
return *path;
}

#ifndef UNREALSDK_IMPORTING
namespace {

/**
* @brief Recursively merges two toml tables.
*
Expand All @@ -80,114 +93,63 @@ void recursive_merge_table(toml::table& base, const toml::table& overrides) {
});
}

/**
* @brief Loads and merges the config files into `merged_raw_config`.
*/
void load_raw_config(void) {
auto base_config_path = get_config_file_path();

bool loaded_base_config = false;
if (std::filesystem::exists(base_config_path)) {
auto base_config = toml::parse_file(base_config_path.string());
if (base_config.succeeded()) {
merged_raw_config = std::move(base_config.table());
loaded_base_config = true;
} else {
LOG(ERROR, "Failed to load {}", base_config_path.string());

std::stringstream stream;
stream << base_config.error();
std::string line;
while (std::getline(stream, line)) {
LOG(ERROR, "{}", line);
}
} // namespace

// Continue anyway - maybe we can load the user config
void load(void) {
auto load_config_file = [](const std::filesystem::path&& path) -> std::optional<toml::table> {
if (!std::filesystem::exists(path)) {
return std::nullopt;
}
}

auto user_config_path =
base_config_path.replace_extension(".user" + base_config_path.extension().string());
if (!std::filesystem::exists(user_config_path)) {
return;
}
auto config = toml::parse_file(path.string());
if (config.succeeded()) {
return config.table();
}

auto user_config = toml::parse_file(user_config_path.string());
if (user_config.failed()) {
LOG(ERROR, "Failed to load {}", user_config_path.string());
LOG(ERROR, "Failed to load {}", path.string());

std::stringstream stream;
stream << user_config.error();
stream << config.error();
std::string line;
while (std::getline(stream, line)) {
LOG(ERROR, "{}", line);
}

// Either both failed, in which case we can just keep the default constructed table, or the
// base config succeeded, but this one failed, in which case there's nothing to merge
return std::nullopt;
};

auto base_config = load_config_file(get_base_config_file_path());
auto user_config = load_config_file(get_user_config_file_path());

if (!base_config.has_value()) {
if (user_config.has_value()) {
// Only user config got loaded
merged_raw_config = std::move(*user_config);
return;
}
// No config files got loaded, use the default constructed empty dict
return;
}

if (!loaded_base_config) {
// If only the user config succeeded, there's nothing to merge, just load it directly
merged_raw_config = std::move(user_config.table());
if (!user_config.has_value()) {
// Only base config got loaded
merged_raw_config = std::move(*base_config);
return;
}

// We managed to parse both config files, merge them
recursive_merge_table(merged_raw_config, user_config.table());
}

} // namespace

/**
* @brief Helper macro to do a basic load into the config struct.
*
* @param name The name of the field to load.
*/
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define BASIC_LOAD_CONFIG(name) \
do { \
auto val = unrealsdk[#name].value<decltype(config.name)>(); \
if (val.has_value()) { \
config.name = *val; \
} \
} while (0)

// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void load(void) {
load_raw_config();

auto unrealsdk = merged_raw_config["unrealsdk"];

BASIC_LOAD_CONFIG(external_console);
BASIC_LOAD_CONFIG(log_file);
BASIC_LOAD_CONFIG(console_log_level);
BASIC_LOAD_CONFIG(exe_override);
BASIC_LOAD_CONFIG(uproperty_size);
BASIC_LOAD_CONFIG(alloc_alignment);
BASIC_LOAD_CONFIG(console_key);
BASIC_LOAD_CONFIG(uconsole_console_command_vf_index);
BASIC_LOAD_CONFIG(uconsole_output_text_vf_index);
BASIC_LOAD_CONFIG(treference_controller_destroy_obj_vf_index);
BASIC_LOAD_CONFIG(treference_controller_destructor_vf_index);
BASIC_LOAD_CONFIG(ftext_get_display_string_vf_index);
BASIC_LOAD_CONFIG(locking_process_event);
BASIC_LOAD_CONFIG(log_all_calls_file);
// Both configs were loaded, we need to merge them
merged_raw_config = std::move(*base_config);
recursive_merge_table(merged_raw_config, *user_config);
}

#undef BASIC_LOAD_CONFIG

#endif

#pragma endregion

// =================================================================================================

#pragma region C API Wrappers

UNREALSDK_CAPI([[nodiscard]] const Config*, config_get) {
return &config;
}
#ifdef UNREALSDK_EXPORTING

UNREALSDK_CAPI([[nodiscard]] bool,
config_get_bool,
Expand Down Expand Up @@ -230,9 +192,26 @@ UNREALSDK_CAPI([[nodiscard]] bool,
return true;
}

const Config& get(void) {
return *UNREALSDK_MANGLE(config_get)();
}
#endif // defined(UNREALSDK_EXPORTING)

#ifdef UNREALSDK_IMPORTING

UNREALSDK_CAPI([[nodiscard]] bool,
config_get_bool,
const char* path,
size_t path_size,
bool* value);
UNREALSDK_CAPI([[nodiscard]] bool,
config_get_int,
const char* path,
size_t path_size,
int64_t* value);
UNREALSDK_CAPI([[nodiscard]] bool,
config_get_str,
const char* path,
size_t path_size,
const char** value,
size_t* value_size);

std::optional<bool> get_bool(std::string_view path) {
bool value{};
Expand All @@ -257,6 +236,20 @@ std::optional<std::string_view> get_str(std::string_view path) {
return std::nullopt;
}

#else // defined(UNREALSDK_IMPORTING)

std::optional<bool> get_bool(std::string_view path) {
return merged_raw_config.at_path(path).value<bool>();
}
std::optional<int64_t> get_int(std::string_view path) {
return merged_raw_config.at_path(path).value<int64_t>();
}
std::optional<std::string_view> get_str(std::string_view path) {
return merged_raw_config.at_path(path).value<std::string_view>();
}

#endif // defined(UNREALSDK_IMPORTING)

#pragma endregion

} // namespace unrealsdk::config
43 changes: 12 additions & 31 deletions src/unrealsdk/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,8 @@

namespace unrealsdk::config {

struct Config {
// NOLINTBEGIN(readability-magic-numbers)

bool external_console = false;
std::string_view log_file{"unrealsdk.log"};
std::string_view console_log_level{"INFO"};
std::string_view exe_override;
size_t uproperty_size = 0;
size_t alloc_alignment = 0;
std::string_view console_key{"Tilde"};
ptrdiff_t uconsole_console_command_vf_index = -1;
ptrdiff_t uconsole_output_text_vf_index = -1;
ptrdiff_t treference_controller_destroy_obj_vf_index = -1;
ptrdiff_t treference_controller_destructor_vf_index = -1;
ptrdiff_t ftext_get_display_string_vf_index = -1;
bool locking_process_event = false;
std::string_view log_all_calls_file{"unrealsdk.calls.tsv"};

// NOLINTEND(readability-magic-numbers)
};

#ifndef UNREALSDK_IMPORTING

/**
* @brief Loads the config file.
*/
Expand All @@ -35,23 +15,24 @@ void load(void);
#endif

/**
* @brief Gets a reference to the loaded config.
*
* @return A reference to the config.
*/
const Config& get(void);

/**
* @brief Gets additional values from the merged config file, suitable for extending it.
* @note Returned reference types (i.e. string view) are owned by a static table in `unrealsdk.dll`.
* @brief Gets values from the config file.
* @note Does not parse the file again, returns references into a static table in `unrealsdk.dll`.
*
* @param path The path to the value to get - e.g. "unrealsdk.outerfield[1].innerfield"
* @param path The path to the value to get - e.g. "unrealsdk.outerfield[1].innerfield".
* @return The returned value, or std::nullopt if not set or the incorrect type.
*/
std::optional<bool> get_bool(std::string_view path);
std::optional<int64_t> get_int(std::string_view path);
std::optional<std::string_view> get_str(std::string_view path);

/**
* @brief Gets the path of the config file, in case you want to do your own parsing.
*
* @return The path to the relevant config file.
*/
std::filesystem::path get_base_config_file_path(void);
std::filesystem::path get_user_config_file_path(void);

} // namespace unrealsdk::config

#endif /* UNREALSDK_CONFIG_H */
2 changes: 1 addition & 1 deletion src/unrealsdk/game/bl2/console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ bool inject_console_hook(hook_manager::Details& hook) {
if (existing_console_key != L"None"_fn || existing_console_key == L"Undefine"_fn) {
LOG(MISC, "Console key is already set to '{}'", existing_console_key);
} else {
std::string wanted_console_key{config::get().console_key};
std::string wanted_console_key{config::get_str("unrealsdk.console_key").value_or("Tilde")};
console->set<UNameProperty>(L"ConsoleKey"_fn, FName{wanted_console_key});

LOG(MISC, "Set console key to '{}'", wanted_console_key);
Expand Down
2 changes: 1 addition & 1 deletion src/unrealsdk/game/bl2/hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ static_assert(std::is_same_v<decltype(&process_event_hook), decltype(&locking_pr
bool locking(void) {
// Basically just a function so we can be sure this static is initialized late - LTO hopefully
// takes care of it
static auto locking = config::get().locking_process_event;
static auto locking = config::get_bool("unrealsdk.locking_process_event").value_or(false);
return locking;
}

Expand Down
14 changes: 7 additions & 7 deletions src/unrealsdk/game/bl3/console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ bool inject_console_hook(hook_manager::Details& hook) {

console->set<UObjectProperty>(L"ConsoleTargetPlayer"_fn, local_player);

static auto console_command_vf_idx = config::get().uconsole_console_command_vf_index >= 0
? config::get().uconsole_console_command_vf_index
: 81; // NOLINT(readability-magic-numbers)
static auto console_command_vf_idx =
config::get_int("unrealsdk.uconsole_console_command_vf_index")
.value_or(81); // NOLINT(readability-magic-numbers)

memory::detour(console->vftable[console_command_vf_idx], console_command_hook,
&console_command_ptr, "ConsoleCommand");
Expand All @@ -166,7 +166,8 @@ bool inject_console_hook(hook_manager::Details& hook) {

console_key = existing_console_key;
} else {
std::string wanted_console_key{config::get().console_key};
std::string wanted_console_key{
config::get_str("unrealsdk.console_key").value_or("Tilde")};
console_key = FName{wanted_console_key};

inner_obj->get<UStructProperty>(L"ConsoleKey"_fn)
Expand All @@ -192,9 +193,8 @@ void BL3Hook::inject_console(void) {
}

void BL3Hook::uconsole_output_text(const std::wstring& str) const {
static auto idx = config::get().uconsole_output_text_vf_index >= 0
? config::get().uconsole_output_text_vf_index
: 83; // NOLINT(readability-magic-numbers)
static auto idx = config::get_int("unrealsdk.uconsole_output_text_vf_index")
.value_or(81); // NOLINT(readability-magic-numbers)

if (console == nullptr) {
return;
Expand Down
2 changes: 1 addition & 1 deletion src/unrealsdk/game/bl3/hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ static_assert(std::is_same_v<decltype(process_event_hook), decltype(locking_proc
bool locking(void) {
// Basically just a function so we can be sure this static is initialized late - LTO hopefully
// takes care of it
static auto locking = config::get().locking_process_event;
static auto locking = config::get_bool("unrealsdk.locking_process_event").value_or(false);
return locking;
}

Expand Down
6 changes: 2 additions & 4 deletions src/unrealsdk/game/selector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,8 @@ std::unique_ptr<AbstractHook> find_correct_hook(std::string_view executable) {
} // namespace

std::unique_ptr<AbstractHook> select_based_on_executable(void) {
auto executable = config::get().exe_override.empty()
? utils::get_executable().filename().string()
: std::string{config::get().exe_override};
return find_correct_hook(executable);
auto executable_filename = utils::get_executable().filename().string();
return find_correct_hook(config::get_str("exe_override").value_or(executable_filename));
}

} // namespace unrealsdk::game
Expand Down
Loading

0 comments on commit bb49b6c

Please sign in to comment.