From a0359bf8b5cdd0f26220fdcaff91fbdff16a3fd9 Mon Sep 17 00:00:00 2001 From: Narknon Date: Mon, 17 Jun 2024 15:06:52 -0400 Subject: [PATCH 1/4] Add open_file_skipping_BOM function to RC::File --- deps/first/File/include/File/File.hpp | 2 + deps/first/File/src/File.cpp | 54 ++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/deps/first/File/include/File/File.hpp b/deps/first/File/include/File/File.hpp index c0e38bf13..14ec3c762 100644 --- a/deps/first/File/include/File/File.hpp +++ b/deps/first/File/include/File/File.hpp @@ -12,5 +12,7 @@ namespace RC::File OverwriteExistingFile = OverwriteExistingFile::No, CreateIfNonExistent = CreateIfNonExistent::No) -> Handle; + RC_FILE_API auto open_file_skip_BOM(const std::wstring& filename) -> StreamType; + RC_FILE_API auto delete_file(const std::filesystem::path& file_path_and_name) -> void; } // namespace RC::File diff --git a/deps/first/File/src/File.cpp b/deps/first/File/src/File.cpp index 6869475cb..ba1f8158b 100644 --- a/deps/first/File/src/File.cpp +++ b/deps/first/File/src/File.cpp @@ -1,3 +1,5 @@ +#include +#include #include namespace RC::File @@ -22,8 +24,58 @@ namespace RC::File return construct_handle(file_path_and_name, open_properties); } + // BOM sequences + const std::string BOM_UTF8 = "\xEF\xBB\xBF"; + const std::wstring BOM_UTF16_LE = L"\xFEFF"; + const std::wstring BOM_UTF16_BE = L"\xFFFE"; + + // Function to check for BOM and reopen the file as a wide stream + auto open_file_skip_BOM(const std::wstring& filename) -> StreamType + { + std::ifstream file(filename, std::ios::binary); + if (!file.is_open()) + { + throw std::runtime_error("Failed to open the file."); + } + + char bom[3] = {0}; + file.read(bom, 3); + if (std::string(bom, 3) == BOM_UTF8) + { + // UTF-8 BOM detected + file.close(); + StreamType wfile(filename); + wfile.seekg(3, std::ios::beg); // Skip BOM + return wfile; + } + else + { + // Check for wide BOM + wchar_t wbom; + file.seekg(0, std::ios::beg); // Rewind to start + file.read(reinterpret_cast(&wbom), sizeof(wchar_t)); + file.close(); + if (wbom == BOM_UTF16_LE[0] || wbom == BOM_UTF16_BE[0]) + { + StreamType wfile(filename); +#pragma warning(disable : 4996) + std::locale loc(wfile.getloc(), new std::codecvt_utf8_utf16); +#pragma warning(default : 4996) + wfile.imbue(loc); + wfile.seekg(sizeof(wchar_t), std::ios::beg); // Skip BOM + return wfile; + } + else + { + // No BOM detected + file.close(); + return StreamType(filename); + } + } + } + auto delete_file(const std::filesystem::path& file_path_and_name) -> void { Handle::FileType::delete_file(file_path_and_name); } -} // namespace RC::File \ No newline at end of file +} // namespace RC::File From 4a5de8a2ef38b9edd02cfe5f55fe1d878f220aa1 Mon Sep 17 00:00:00 2001 From: narknon <73571427+narknon@users.noreply.github.com> Date: Thu, 30 May 2024 21:20:22 -0400 Subject: [PATCH 2/4] Converts mods.txt to use mods.json Maintains legacy support for mods.txt and favors mods.txt for settings mods.txt should only be favored for one minor release cycle post 4.0 Our packaging script will need to be updated, but after mods.txt and ue4sssettings are converted to json it should be able to be simplified significantly. --- UE4SS/include/UE4SSProgram.hpp | 12 +++ UE4SS/src/UE4SSProgram.cpp | 133 +++++++++++++++++++++------------ assets/Mods/mods.json | 80 ++++++++++---------- 3 files changed, 137 insertions(+), 88 deletions(-) diff --git a/UE4SS/include/UE4SSProgram.hpp b/UE4SS/include/UE4SSProgram.hpp index 672e92ec6..81f234d7d 100644 --- a/UE4SS/include/UE4SSProgram.hpp +++ b/UE4SS/include/UE4SSProgram.hpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include // Used to set up ImGui context and allocator in DLL mods #define UE4SS_ENABLE_IMGUI() \ @@ -207,8 +209,18 @@ namespace RC auto fire_dll_load_for_cpp_mods(std::wstring_view dll_name) -> void; public: + struct ModData { + std::string mod_name; + bool mod_enabled; + + GLZ_LOCAL_META(ModData, mod_name, mod_enabled); + }; + auto init() -> void; auto is_program_started() -> bool; + static auto read_mods_json(std::string enabled_mods_file, std::vector& mod_data_vector) -> void; + static auto write_mods_json(std::string enabled_mods_file, std::vector& mod_data_vector) -> void; + static auto convert_legacy_mods_file(StringType legacy_enabled_mods_file, std::vector& mod_data_vector) -> void; auto reinstall_mods() -> void; auto get_object_dumper_output_directory() -> const File::StringType; RC_UE4SS_API auto get_module_directory() -> File::StringViewType; diff --git a/UE4SS/src/UE4SSProgram.cpp b/UE4SS/src/UE4SSProgram.cpp index e753c31d3..008ca5756 100644 --- a/UE4SS/src/UE4SSProgram.cpp +++ b/UE4SS/src/UE4SSProgram.cpp @@ -1117,67 +1117,104 @@ namespace RC } } + auto UE4SSProgram::read_mods_json(std::string enabled_mods_file, std::vector& mod_data_vector) -> void + { + auto ec = glz::read_file_json(mod_data_vector, enabled_mods_file, std::string{}); + } + + auto UE4SSProgram::write_mods_json(std::string enabled_mods_file, std::vector& mod_data_vector) -> void + { + std::string mod_data_buffer; + glz::write(mod_data_vector, mod_data_buffer); + glz::error_code ec = glz::buffer_to_file(mod_data_buffer, enabled_mods_file); + } + + auto UE4SSProgram::convert_legacy_mods_file(StringType legacy_enabled_mods_file, std::vector& mod_data_vector) -> void + { + Output::send(STR("Converting legacy mods.txt to mods.json...\n")); + + // 'mods.txt' exists, lets parse it + std::wifstream mods_stream{legacy_enabled_mods_file}; + + std::wstring current_line; + while (std::getline(mods_stream, current_line)) + { + // Don't parse any lines with ';' + if (current_line.find(L";") != current_line.npos) + { + continue; + } + + // Don't parse if the line is impossibly short (empty lines for example) + if (current_line.size() <= 4) + { + continue; + } + + // Remove all spaces + auto end = std::remove(current_line.begin(), current_line.end(), L' '); + current_line.erase(end, current_line.end()); + + // Parse the line into something that can be converted into proper data + std::wstring mod_name = explode_by_occurrence(current_line, L':', 1); + std::wstring mod_enabled = explode_by_occurrence(current_line, L':', ExplodeType::FromEnd); + bool mod_enabled_out = !mod_enabled.empty() && mod_enabled[0] == L'1'; + + auto it = std::find_if(mod_data_vector.begin(), mod_data_vector.end(), + [&mod_name](const ModData& element) { + return element.mod_name == to_string(mod_name); + }); + + if (it == mod_data_vector.end()) { + mod_data_vector.push_back({to_string(mod_name), mod_enabled_out}); + } + } + + Output::send(STR("mods.txt entries converted to mods.json. Please delete mods.txt if no mod managers rely on it to avoid conflicting with changes to the json...\n")); + } + + template auto start_mods() -> std::string { ProfilerScope(); - // Part #1: Start all mods that are enabled in mods.txt. - Output::send(STR("Starting mods (from mods.txt load order)...\n")); - + // Part #1: Start all mods that are enabled in mods.json. std::filesystem::path mods_directory = UE4SSProgram::get_program().get_mods_directory(); - std::wstring enabled_mods_file{mods_directory / "mods.txt"}; - if (!std::filesystem::exists(enabled_mods_file)) + StringType legacy_enabled_mods_file{mods_directory / "mods.txt"}; + std::filesystem::path enabled_mods_file{mods_directory / "mods.json"}; + std::vector mod_data_vector{}; + + if (std::filesystem::exists(legacy_enabled_mods_file)) { - Output::send(STR("No mods.txt file found...\n")); + UE4SSProgram::convert_legacy_mods_file(legacy_enabled_mods_file, mod_data_vector); } - else + if (std::filesystem::exists(enabled_mods_file)) { - // 'mods.txt' exists, lets parse it - std::wifstream mods_stream{enabled_mods_file}; + UE4SSProgram::read_mods_json(enabled_mods_file.string(), mod_data_vector); + } + + UE4SSProgram::write_mods_json(enabled_mods_file.string(), mod_data_vector); - std::wstring current_line; - while (std::getline(mods_stream, current_line)) + Output::send(STR("Starting mods (from mods.json load order)...\n")); + for (auto it = mod_data_vector.begin(); it != mod_data_vector.end(); ++it) + { + auto mod = UE4SSProgram::find_mod_by_name(it->mod_name, UE4SSProgram::IsInstalled::Yes); + if (!mod || !dynamic_cast(mod)) { - // Don't parse any lines with ';' - if (current_line.find(L";") != current_line.npos) - { - continue; - } - - // Don't parse if the line is impossibly short (empty lines for example) - if (current_line.size() <= 4) - { - continue; - } - - // Remove all spaces - auto end = std::remove(current_line.begin(), current_line.end(), L' '); - current_line.erase(end, current_line.end()); - - // Parse the line into something that can be converted into proper data - std::wstring mod_name = explode_by_occurrence(current_line, L':', 1); - std::wstring mod_enabled = explode_by_occurrence(current_line, L':', ExplodeType::FromEnd); - - auto mod = UE4SSProgram::find_mod_by_name(mod_name, UE4SSProgram::IsInstalled::Yes); - if (!mod || !dynamic_cast(mod)) - { - continue; - } + continue; + } - if (!mod_enabled.empty() && mod_enabled[0] == L'1') - { - Output::send(STR("Starting {} mod '{}'\n"), std::is_same_v ? STR("Lua") : STR("C++"), mod->get_name().data()); - mod->start_mod(); - } - else - { - Output::send(STR("Mod '{}' disabled in mods.txt.\n"), mod_name); - } + if (it->mod_enabled) + { + Output::send(STR("Starting {} mod '{}'\n"), std::is_same_v ? STR("Lua") : STR("C++"), mod->get_name().data()); + mod->start_mod(); + } + else + { + Output::send(STR("Mod '{}' disabled in mods.json.\n"), to_wstring(it->mod_name)); } } - - // Part #2: Start all mods that have enabled.txt present in the mod directory. - Output::send(STR("Starting mods (from enabled.txt, no defined load order)...\n")); + for (const auto& mod_directory : std::filesystem::directory_iterator(mods_directory)) { diff --git a/assets/Mods/mods.json b/assets/Mods/mods.json index d79717fa9..b8c6dcc6c 100644 --- a/assets/Mods/mods.json +++ b/assets/Mods/mods.json @@ -1,42 +1,42 @@ [ - { - "mod_name": "CheatManagerEnablerMod", - "mod_enabled": true - }, - { - "mod_name": "ActorDumperMod", - "mod_enabled": false - }, - { - "mod_name": "ConsoleCommandsMod", - "mod_enabled": true - }, - { - "mod_name": "ConsoleEnablerMod", - "mod_enabled": true - }, - { - "mod_name": "SplitScreenMod", - "mod_enabled": false - }, - { - "mod_name": "LineTraceMod", - "mod_enabled": true - }, - { - "mod_name": "BPML_GenericFunctions", - "mod_enabled": true - }, - { - "mod_name": "BPModLoaderMod", - "mod_enabled": true - }, - { - "mod_name": "jsbLuaProfilerMod", - "mod_enabled": false - }, - { - "mod_name": "Keybinds", - "mod_enabled": true - } + { + "mod_name": "CheatManagerEnablerMod", + "mod_enabled": true + }, + { + "mod_name": "ActorDumperMod", + "mod_enabled": false + }, + { + "mod_name": "ConsoleCommandsMod", + "mod_enabled": true + }, + { + "mod_name": "ConsoleEnablerMod", + "mod_enabled": true + }, + { + "mod_name": "SplitScreenMod", + "mod_enabled": false + }, + { + "mod_name": "LineTraceMod", + "mod_enabled": true + }, + { + "mod_name": "BPML_GenericFunctions", + "mod_enabled": true + }, + { + "mod_name": "BPModLoaderMod", + "mod_enabled": true + }, + { + "mod_name": "jsbLuaProfilerMod", + "mod_enabled": false + }, + { + "mod_name": "Keybinds", + "mod_enabled": true + } ] \ No newline at end of file From 81a56ce3a057ba2589cd5f4c72177d399fe03de2 Mon Sep 17 00:00:00 2001 From: narknon <73571427+narknon@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:24:51 -0400 Subject: [PATCH 3/4] Make mods.txt parsing skip BOM; make .json only write if moddatavector is different from existing json --- UE4SS/include/UE4SSProgram.hpp | 2 ++ UE4SS/src/UE4SSProgram.cpp | 47 +++++++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/UE4SS/include/UE4SSProgram.hpp b/UE4SS/include/UE4SSProgram.hpp index 81f234d7d..fd01d235d 100644 --- a/UE4SS/include/UE4SSProgram.hpp +++ b/UE4SS/include/UE4SSProgram.hpp @@ -215,6 +215,8 @@ namespace RC GLZ_LOCAL_META(ModData, mod_name, mod_enabled); }; + std::vector global_mod_data_vector; + auto init() -> void; auto is_program_started() -> bool; diff --git a/UE4SS/src/UE4SSProgram.cpp b/UE4SS/src/UE4SSProgram.cpp index 008ca5756..cc24686d3 100644 --- a/UE4SS/src/UE4SSProgram.cpp +++ b/UE4SS/src/UE4SSProgram.cpp @@ -384,7 +384,7 @@ namespace RC std::filesystem::path game_exe_path = exe_path_buffer; std::filesystem::path game_directory_path = game_exe_path.parent_path(); m_legacy_root_directory = game_directory_path; - + m_working_directory = m_root_directory; m_mods_directory = m_working_directory / "Mods"; m_game_executable_directory = game_directory_path; @@ -1119,7 +1119,24 @@ namespace RC auto UE4SSProgram::read_mods_json(std::string enabled_mods_file, std::vector& mod_data_vector) -> void { - auto ec = glz::read_file_json(mod_data_vector, enabled_mods_file, std::string{}); + std::string buffer{}; + glz::parse_error pe = glz::read_file_json(mod_data_vector, enabled_mods_file, buffer); + if (pe) + { + std::string descriptive_error = glz::format_error(pe, buffer); + + size_t count = descriptive_error.size() + 1; + wchar_t* converted_method_name = new wchar_t[count]; + + size_t num_of_char_converted = 0; + mbstowcs_s(&num_of_char_converted, converted_method_name, count, descriptive_error.data(), count); + + auto converted = File::StringViewType(converted_method_name); + + delete[] converted_method_name; + + Output::send(STR("{}\n\nError parsing mods.json file, please fix the file...\n"), converted); + } } auto UE4SSProgram::write_mods_json(std::string enabled_mods_file, std::vector& mod_data_vector) -> void @@ -1128,17 +1145,20 @@ namespace RC glz::write(mod_data_vector, mod_data_buffer); glz::error_code ec = glz::buffer_to_file(mod_data_buffer, enabled_mods_file); } - + auto UE4SSProgram::convert_legacy_mods_file(StringType legacy_enabled_mods_file, std::vector& mod_data_vector) -> void { Output::send(STR("Converting legacy mods.txt to mods.json...\n")); - - // 'mods.txt' exists, lets parse it - std::wifstream mods_stream{legacy_enabled_mods_file}; - + std::wifstream mods_stream = File::open_file_skip_BOM(legacy_enabled_mods_file); + try { + mods_stream = File::open_file_skip_BOM(legacy_enabled_mods_file); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + } std::wstring current_line; while (std::getline(mods_stream, current_line)) { + // Don't parse any lines with ';' if (current_line.find(L";") != current_line.npos) { @@ -1180,21 +1200,24 @@ namespace RC ProfilerScope(); // Part #1: Start all mods that are enabled in mods.json. std::filesystem::path mods_directory = UE4SSProgram::get_program().get_mods_directory(); - StringType legacy_enabled_mods_file{mods_directory / "mods.txt"}; + std::filesystem::path legacy_enabled_mods_file{mods_directory / "mods.txt"}; std::filesystem::path enabled_mods_file{mods_directory / "mods.json"}; std::vector mod_data_vector{}; + UE4SSProgram& program = UE4SSProgram::get_program(); - if (std::filesystem::exists(legacy_enabled_mods_file)) - { - UE4SSProgram::convert_legacy_mods_file(legacy_enabled_mods_file, mod_data_vector); - } if (std::filesystem::exists(enabled_mods_file)) { UE4SSProgram::read_mods_json(enabled_mods_file.string(), mod_data_vector); } + if (std::filesystem::exists(legacy_enabled_mods_file)) + { + UE4SSProgram::convert_legacy_mods_file(legacy_enabled_mods_file, mod_data_vector); + } + UE4SSProgram::write_mods_json(enabled_mods_file.string(), mod_data_vector); + Output::send(STR("Starting mods (from mods.json load order)...\n")); for (auto it = mod_data_vector.begin(); it != mod_data_vector.end(); ++it) { From 60a8402ff41cf5de7c2fb19f146c5ea53eb4c04b Mon Sep 17 00:00:00 2001 From: narknon <73571427+narknon@users.noreply.github.com> Date: Wed, 5 Jun 2024 11:35:37 -0400 Subject: [PATCH 4/4] Rename enabled_mods_file to mod_list_file for clarity against enabled.txt --- UE4SS/include/UE4SSProgram.hpp | 6 +++--- UE4SS/src/UE4SSProgram.cpp | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/UE4SS/include/UE4SSProgram.hpp b/UE4SS/include/UE4SSProgram.hpp index fd01d235d..3e9b60253 100644 --- a/UE4SS/include/UE4SSProgram.hpp +++ b/UE4SS/include/UE4SSProgram.hpp @@ -220,9 +220,9 @@ namespace RC auto init() -> void; auto is_program_started() -> bool; - static auto read_mods_json(std::string enabled_mods_file, std::vector& mod_data_vector) -> void; - static auto write_mods_json(std::string enabled_mods_file, std::vector& mod_data_vector) -> void; - static auto convert_legacy_mods_file(StringType legacy_enabled_mods_file, std::vector& mod_data_vector) -> void; + static auto read_mods_json(std::string mod_list_file, std::vector& mod_data_vector) -> void; + static auto write_mods_json(std::string mod_list_file, std::vector& mod_data_vector) -> void; + static auto convert_legacy_mods_file(StringType legacy_mod_list_file, std::vector& mod_data_vector) -> void; auto reinstall_mods() -> void; auto get_object_dumper_output_directory() -> const File::StringType; RC_UE4SS_API auto get_module_directory() -> File::StringViewType; diff --git a/UE4SS/src/UE4SSProgram.cpp b/UE4SS/src/UE4SSProgram.cpp index cc24686d3..50ef52625 100644 --- a/UE4SS/src/UE4SSProgram.cpp +++ b/UE4SS/src/UE4SSProgram.cpp @@ -1117,10 +1117,10 @@ namespace RC } } - auto UE4SSProgram::read_mods_json(std::string enabled_mods_file, std::vector& mod_data_vector) -> void + auto UE4SSProgram::read_mods_json(std::string mod_list_file, std::vector& mod_data_vector) -> void { std::string buffer{}; - glz::parse_error pe = glz::read_file_json(mod_data_vector, enabled_mods_file, buffer); + glz::parse_error pe = glz::read_file_json(mod_data_vector, mod_list_file, buffer); if (pe) { std::string descriptive_error = glz::format_error(pe, buffer); @@ -1139,19 +1139,19 @@ namespace RC } } - auto UE4SSProgram::write_mods_json(std::string enabled_mods_file, std::vector& mod_data_vector) -> void + auto UE4SSProgram::write_mods_json(std::string mod_list_file, std::vector& mod_data_vector) -> void { std::string mod_data_buffer; glz::write(mod_data_vector, mod_data_buffer); - glz::error_code ec = glz::buffer_to_file(mod_data_buffer, enabled_mods_file); + glz::error_code ec = glz::buffer_to_file(mod_data_buffer, mod_list_file); } - auto UE4SSProgram::convert_legacy_mods_file(StringType legacy_enabled_mods_file, std::vector& mod_data_vector) -> void + auto UE4SSProgram::convert_legacy_mods_file(StringType legacy_mod_list_file, std::vector& mod_data_vector) -> void { Output::send(STR("Converting legacy mods.txt to mods.json...\n")); - std::wifstream mods_stream = File::open_file_skip_BOM(legacy_enabled_mods_file); + std::wifstream mods_stream = File::open_file_skip_BOM(legacy_mod_list_file); try { - mods_stream = File::open_file_skip_BOM(legacy_enabled_mods_file); + mods_stream = File::open_file_skip_BOM(legacy_mod_list_file); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } @@ -1200,22 +1200,22 @@ namespace RC ProfilerScope(); // Part #1: Start all mods that are enabled in mods.json. std::filesystem::path mods_directory = UE4SSProgram::get_program().get_mods_directory(); - std::filesystem::path legacy_enabled_mods_file{mods_directory / "mods.txt"}; - std::filesystem::path enabled_mods_file{mods_directory / "mods.json"}; + std::filesystem::path legacy_mod_list_file{mods_directory / "mods.txt"}; + std::filesystem::path mod_list_file{mods_directory / "mods.json"}; std::vector mod_data_vector{}; UE4SSProgram& program = UE4SSProgram::get_program(); - if (std::filesystem::exists(enabled_mods_file)) + if (std::filesystem::exists(mod_list_file)) { - UE4SSProgram::read_mods_json(enabled_mods_file.string(), mod_data_vector); + UE4SSProgram::read_mods_json(mod_list_file.string(), mod_data_vector); } - if (std::filesystem::exists(legacy_enabled_mods_file)) + if (std::filesystem::exists(legacy_mod_list_file)) { - UE4SSProgram::convert_legacy_mods_file(legacy_enabled_mods_file, mod_data_vector); + UE4SSProgram::convert_legacy_mods_file(legacy_mod_list_file, mod_data_vector); } - UE4SSProgram::write_mods_json(enabled_mods_file.string(), mod_data_vector); + UE4SSProgram::write_mods_json(mod_list_file.string(), mod_data_vector); Output::send(STR("Starting mods (from mods.json load order)...\n"));