diff --git a/common/util/StringUtil.cpp b/common/util/StringUtil.cpp index 7bf3ed5719..fb04649b5e 100644 --- a/common/util/StringUtil.cpp +++ b/common/util/StringUtil.cpp @@ -1,9 +1,15 @@ #include "StringUtil.h" +#include + namespace str_util { const std::string WHITESPACE = " \n\r\t\f\v"; +bool contains(const std::string& s, const std::string& substr) { + return s.find(substr) != std::string::npos; +} + bool starts_with(const std::string& s, const std::string& prefix) { return s.rfind(prefix) == 0; } @@ -31,4 +37,14 @@ int line_count(const std::string& str) { } return result; } + +// NOTE - this won't work running within gk.exe! +bool valid_regex(const std::string& regex) { + try { + std::regex re(regex); + } catch (const std::regex_error& e) { + return false; + } + return true; +} } // namespace str_util diff --git a/common/util/StringUtil.h b/common/util/StringUtil.h index 3f6bb9e6b2..3e6b8f9209 100644 --- a/common/util/StringUtil.h +++ b/common/util/StringUtil.h @@ -1,9 +1,11 @@ #include namespace str_util { +bool contains(const std::string& s, const std::string& substr); bool starts_with(const std::string& s, const std::string& prefix); std::string ltrim(const std::string& s); std::string rtrim(const std::string& s); std::string trim(const std::string& s); int line_count(const std::string& str); +bool valid_regex(const std::string& regex); } // namespace str_util diff --git a/decompiler/config/jak2/anonymous_function_types.jsonc b/decompiler/config/jak2/anonymous_function_types.jsonc index 9896d743a7..168ff8037b 100644 --- a/decompiler/config/jak2/anonymous_function_types.jsonc +++ b/decompiler/config/jak2/anonymous_function_types.jsonc @@ -650,7 +650,5 @@ [1, "(function float)"], [3, "(function none :behavior scene-player)"] ], - "collectables": [ - [69, "(function part-tracker vector)"] - ] + "collectables": [[69, "(function part-tracker vector)"]] } diff --git a/decompiler/config/jak2/type_casts.jsonc b/decompiler/config/jak2/type_casts.jsonc index 82d0ddadfa..a3b8d786ff 100644 --- a/decompiler/config/jak2/type_casts.jsonc +++ b/decompiler/config/jak2/type_casts.jsonc @@ -5474,7 +5474,7 @@ [19, "a0", "int"], [23, "a0", "int"], [155, "v1", "process-drawable"] - ], + ], "(anon-function 69 collectables)": [ //[[1, 6], "v1", "handle"], [2, "v1", "handle"], @@ -5483,12 +5483,8 @@ [13, "v1", "collectable"], [[34, 39], "a0", "process-focusable"] ], - "check-blue-suck": [ - [19, "v1", "collide-shape"] - ], - "add-blue-motion": [ - [27, "s2", "process-focusable"] - ], + "check-blue-suck": [[19, "v1", "collide-shape"]], + "add-blue-motion": [[27, "s2", "process-focusable"]], "collectable-standard-event-handler": [ [84, "a0", "vector"], [102, "a0", "vector"], @@ -5499,16 +5495,12 @@ [17, "a0", "vector"], [18, "v1", "vector"] ], - "(code die eco)": [ - [55, "v1", "float"] - ], + "(code die eco)": [[55, "v1", "float"]], "(code pickup eco)": [ [39, "v0", "state"], [41, "t9", "(function none)"] ], - "(method 10 gem)": [ - [12, "t9", "(function gem none)"] - ], + "(method 10 gem)": [[12, "t9", "(function gem none)"]], "(method 9 fact-info)": [ [245, "a0", "process-drawable"], [293, "a0", "process-drawable"] diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 21a6c5c6da..200b7d1942 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -167,7 +167,8 @@ set(RUNTIME_SOURCE graphics/pipelines/opengl.cpp system/vm/dmac.cpp system/vm/vm.cpp - tools/subtitles/subtitle_editor.cpp) + tools/subtitles/subtitle_editor.cpp + tools/filter_menu/filter_menu.cpp) find_package(Git) @@ -212,5 +213,5 @@ else() target_link_libraries(runtime pthread dl) endif() -add_executable(gk main.cpp) +add_executable(gk main.cpp "tools/filter_menu/filter_menu.cpp" "tools/filter_menu/filter_menu.h") target_link_libraries(gk runtime) diff --git a/game/graphics/display.h b/game/graphics/display.h index 72e48b7915..a38ea328c6 100644 --- a/game/graphics/display.h +++ b/game/graphics/display.h @@ -80,7 +80,11 @@ class GfxDisplay { GfxDisplayMode last_fullscreen_mode() const { return m_last_fullscreen_mode; } GfxDisplayMode fullscreen_mode() { return m_fullscreen_mode; } int fullscreen_screen() const { return m_fullscreen_screen; } - void set_imgui_visible(bool visible) { m_imgui_visible = visible; } + void set_imgui_visible(bool visible) { + m_imgui_visible = visible; + Gfx::g_debug_settings.show_imgui = visible; + Gfx::g_debug_settings.save_settings(); + } bool is_imgui_visible() const { return m_imgui_visible; } bool windowed() { return fullscreen_mode() == GfxDisplayMode::Windowed; } diff --git a/game/graphics/gfx.cpp b/game/graphics/gfx.cpp index 68f07e1a52..143d85f117 100644 --- a/game/graphics/gfx.cpp +++ b/game/graphics/gfx.cpp @@ -54,6 +54,40 @@ namespace Gfx { std::function vsync_callback; GfxGlobalSettings g_global_settings; GfxSettings g_settings; +DebugSettings g_debug_settings; + +void DebugSettings::load_settings(const ghc::filesystem::path& filepath) { + auto file_txt = file_util::read_text_file(filepath); + auto json = parse_commented_json(file_txt, filepath.string()); + + if (json.contains("show_imgui")) { + show_imgui = json["show_imgui"].get(); + } + if (json.contains("ignore_imgui_hide_keybind")) { + ignore_imgui_hide_keybind = json["ignore_imgui_hide_keybind"].get(); + } + if (json.contains("debug_text_check_range")) { + debug_text_check_range = json["debug_text_check_range"].get(); + } + if (json.contains("debug_text_max_range")) { + debug_text_max_range = json["debug_text_max_range"].get(); + } + // TODO - not loading filters because they aren't being persisted +} + +void DebugSettings::save_settings() { + nlohmann::json json; + json["show_imgui"] = show_imgui; + json["ignore_imgui_hide_keybind"] = ignore_imgui_hide_keybind; + json["debug_text_check_range"] = debug_text_check_range; + json["debug_text_max_range"] = debug_text_max_range; + // TODO - persist the filters as well, not doing it yet because i havn't added a way to remove em + // via the UI + auto debug_settings_filename = + file_util::get_user_misc_dir(g_game_version) / "debug-settings.json"; + file_util::create_dir_if_needed_for_file(debug_settings_filename); + file_util::write_text_file(debug_settings_filename, json.dump(2)); +} Pad::MappingInfo& get_button_mapping() { return g_settings.pad_mapping_info; @@ -84,15 +118,8 @@ const std::pair analog_map[] = { {"Right Y Axis", Pad::Analog::Right_Y}, }; -bool g_is_debug_menu_visible_on_startup = false; - -bool get_debug_menu_visible_on_startup() { - return g_is_debug_menu_visible_on_startup; -} - void DumpToJson(ghc::filesystem::path& filename) { nlohmann::json json; - json["Debug Menu Visibility"] = false; // Assume start up debug display is disabled auto& peripherals_json = json["Peripherals"]; json["Use Mouse"] = g_settings.pad_mapping_info.use_mouse; @@ -152,10 +179,6 @@ void LoadPeripheralSettings(const ghc::filesystem::path& filepath) { auto file_txt = file_util::read_text_file(filepath); auto configuration = parse_commented_json(file_txt, filepath.string()); - if (configuration.find("Debug Menu Visibility") != configuration.end()) { - g_is_debug_menu_visible_on_startup = configuration["Debug Menu Visibility"].get(); - } - if (configuration.find("Use Mouse") != configuration.end()) { g_settings.pad_mapping_info.use_mouse = configuration["Use Mouse"].get(); } @@ -224,14 +247,28 @@ void LoadPeripheralSettings(const ghc::filesystem::path& filepath) { } void LoadSettings() { - auto filename = (file_util::get_user_config_dir() / "controller" / "controller-settings.json"); - if (fs::exists(filename)) { - LoadPeripheralSettings(filename); - lg::info("Loaded graphics configuration file."); - return; + // load controller settings + // TODO - make this game specific as well + auto controller_settings_filename = + file_util::get_user_config_dir() / "controller" / "controller-settings.json"; + if (fs::exists(controller_settings_filename)) { + LoadPeripheralSettings(controller_settings_filename); + lg::info("Loaded controller configuration file."); } else { SavePeripheralSettings(); - lg::info("Couldn't find controller-settings.json creating new controller settings file."); + lg::info( + "Couldn't find $USER/controller/controller-settings.json creating new controller settings " + "file."); + } + // load debug settings + auto debug_settings_filename = + file_util::get_user_misc_dir(g_game_version) / "debug-settings.json"; + if (fs::exists(debug_settings_filename)) { + g_debug_settings.load_settings(debug_settings_filename); + lg::info("Loaded debug settings file."); + } else { + lg::info("Couldn't find $USER/misc/debug-settings.json creating new controller settings file."); + g_debug_settings.save_settings(); } } diff --git a/game/graphics/gfx.h b/game/graphics/gfx.h index 5bf6597fe9..0b31c156bd 100644 --- a/game/graphics/gfx.h +++ b/game/graphics/gfx.h @@ -10,10 +10,12 @@ #include #include "common/common_types.h" +#include "common/util/FileUtil.h" #include "common/versions.h" #include "game/kernel/common/kboot.h" #include "game/system/newpad.h" +#include "game/tools/filter_menu/filter_menu.h" // forward declarations struct GfxSettings; @@ -122,6 +124,19 @@ namespace Gfx { extern GfxGlobalSettings g_global_settings; extern GfxSettings g_settings; +struct DebugSettings { + bool show_imgui = false; + bool ignore_imgui_hide_keybind = false; + std::vector debug_text_filters = {}; + bool debug_text_check_range = false; + float debug_text_max_range = 0; + + void load_settings(const ghc::filesystem::path& filepath); + void save_settings(); +}; + +extern DebugSettings g_debug_settings; + const GfxRendererModule* GetCurrentRenderer(); u32 Init(GameVersion version); @@ -159,7 +174,6 @@ void set_msaa(int samples); void input_mode_set(u32 enable); void input_mode_save(); s64 get_mapped_button(s64 pad, s64 button); -bool get_debug_menu_visible_on_startup(); int PadIsPressed(Pad::Button button, int port); int PadGetAnalogValue(Pad::Analog analog, int port); diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index e2eac7d5ff..3c81097134 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -543,6 +543,10 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) { m_subtitle_editor.draw_window(); } + if (settings.draw_filters_window) { + m_filters_menu.draw_window(); + } + if (settings.save_screenshot) { Fbo* screenshot_src; int read_buffer; diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.h b/game/graphics/opengl_renderer/OpenGLRenderer.h index 93088f42e9..f336e748ba 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.h +++ b/game/graphics/opengl_renderer/OpenGLRenderer.h @@ -10,6 +10,7 @@ #include "game/graphics/opengl_renderer/Profiler.h" #include "game/graphics/opengl_renderer/Shader.h" #include "game/graphics/opengl_renderer/opengl_utils.h" +#include "game/tools/filter_menu/filter_menu.h" #include "game/tools/subtitles/subtitle_editor.h" struct RenderOptions { @@ -17,6 +18,7 @@ struct RenderOptions { bool draw_profiler_window = false; bool draw_small_profiler_window = false; bool draw_subtitle_editor_window = false; + bool draw_filters_window = false; // internal rendering settings - The OpenGLRenderer will internally use this resolution/format. int msaa_samples = 4; @@ -136,6 +138,7 @@ class OpenGLRenderer { Profiler m_profiler; SmallProfiler m_small_profiler; SubtitleEditor m_subtitle_editor; + FiltersMenu m_filters_menu; std::vector> m_bucket_renderers; std::vector m_bucket_categories; diff --git a/game/graphics/opengl_renderer/debug_gui.cpp b/game/graphics/opengl_renderer/debug_gui.cpp index ee78630469..05b21376f3 100644 --- a/game/graphics/opengl_renderer/debug_gui.cpp +++ b/game/graphics/opengl_renderer/debug_gui.cpp @@ -104,6 +104,7 @@ void OpenGlDebugGui::draw(const DmaStats& dma_stats) { if (ImGui::BeginMenu("Tools")) { ImGui::MenuItem("Subtitle Editor", nullptr, &m_subtitle_editor); + ImGui::MenuItem("Filters", nullptr, &m_filters_menu); ImGui::EndMenu(); } @@ -141,10 +142,6 @@ void OpenGlDebugGui::draw(const DmaStats& dma_stats) { } ImGui::EndMenu(); } - - if (ImGui::BeginMenu(fmt::format("WORK IN PROGRESS VERSION ({})!", GIT_VERSION).c_str())) { - ImGui::EndMenu(); - } } ImGui::EndMainMenuBar(); diff --git a/game/graphics/opengl_renderer/debug_gui.h b/game/graphics/opengl_renderer/debug_gui.h index 21e38a7619..967814be18 100644 --- a/game/graphics/opengl_renderer/debug_gui.h +++ b/game/graphics/opengl_renderer/debug_gui.h @@ -45,6 +45,7 @@ class OpenGlDebugGui { bool should_draw_render_debug() const { return m_draw_debug; } bool should_draw_profiler() const { return m_draw_profiler; } bool should_draw_subtitle_editor() const { return m_subtitle_editor; } + bool should_draw_filters_menu() const { return m_filters_menu; } const char* screenshot_name() const { return m_screenshot_save_name; } bool should_advance_frame() { return m_frame_timer.should_advance_frame(); } @@ -74,6 +75,7 @@ class OpenGlDebugGui { bool m_draw_profiler = false; bool m_draw_debug = false; bool m_subtitle_editor = false; + bool m_filters_menu = false; bool m_want_screenshot = false; char m_screenshot_save_name[256] = "screenshot.png"; float target_fps_input = 60.f; diff --git a/game/graphics/pipelines/opengl.cpp b/game/graphics/pipelines/opengl.cpp index 53529938df..aa3b400f45 100644 --- a/game/graphics/pipelines/opengl.cpp +++ b/game/graphics/pipelines/opengl.cpp @@ -216,7 +216,7 @@ static std::shared_ptr gl_make_display(int width, } auto display = std::make_shared(window, is_main); - display->set_imgui_visible(Gfx::get_debug_menu_visible_on_startup()); + display->set_imgui_visible(Gfx::g_debug_settings.show_imgui); display->update_cursor_visibility(window, display->is_imgui_visible()); // lg::debug("init display #x{:x}", (uintptr_t)display); @@ -326,7 +326,8 @@ void GLDisplay::on_key(GLFWwindow* window, int key, int /*scancode*/, int action switch (key) { case GLFW_KEY_LEFT_ALT: case GLFW_KEY_RIGHT_ALT: - if (glfwGetWindowAttrib(window, GLFW_FOCUSED)) { + if (glfwGetWindowAttrib(window, GLFW_FOCUSED) && + !Gfx::g_debug_settings.ignore_imgui_hide_keybind) { set_imgui_visible(!is_imgui_visible()); update_cursor_visibility(window, is_imgui_visible()); } @@ -459,6 +460,7 @@ void render_game_frame(int game_width, options.draw_render_debug_window = g_gfx_data->debug_gui.should_draw_render_debug(); options.draw_profiler_window = g_gfx_data->debug_gui.should_draw_profiler(); options.draw_subtitle_editor_window = g_gfx_data->debug_gui.should_draw_subtitle_editor(); + options.draw_filters_window = g_gfx_data->debug_gui.should_draw_filters_menu(); options.save_screenshot = false; options.gpu_sync = g_gfx_data->debug_gui.should_gl_finish(); options.borderless_windows_hacks = windows_borderless_hack; diff --git a/game/kernel/common/kmachine.cpp b/game/kernel/common/kmachine.cpp index fdb17a35cd..7f5ed7fbae 100644 --- a/game/kernel/common/kmachine.cpp +++ b/game/kernel/common/kmachine.cpp @@ -4,6 +4,7 @@ #include "common/log/log.h" #include "common/symbols.h" #include "common/util/FileUtil.h" +#include "common/util/StringUtil.h" #include "common/util/Timer.h" #include "game/graphics/gfx.h" @@ -530,4 +531,43 @@ void pc_texture_upload_now(u32 page, u32 mode) { void pc_texture_relocate(u32 dst, u32 src, u32 format) { Gfx::texture_relocate(dst, src, format); -} \ No newline at end of file +} + +u64 pc_filter_debug_string(u32 str_ptr, u32 dist_ptr) { + auto str = std::string(Ptr(str_ptr).c()->data()); + float dist; + memcpy(&dist, &dist_ptr, 4); + + // Check distance first + if (Gfx::g_debug_settings.debug_text_check_range) { + if (dist / 4096.0 > Gfx::g_debug_settings.debug_text_max_range) { + return s7.offset + true_symbol_offset(g_game_version); + } + } + + // Get the current filters + const auto& filters = Gfx::g_debug_settings.debug_text_filters; + if (filters.empty()) { + // there are no filters, exit early + return s7.offset; + } + + // Currently very dumb contains check + for (const auto& filter : filters) { + if (filter.type == DebugTextFilter::Type::CONTAINS) { + if (!str.empty() && !filter.content.empty() && !str_util::contains(str, filter.content)) { + return s7.offset + true_symbol_offset(g_game_version); + } + } else if (filter.type == DebugTextFilter::Type::NOT_CONTAINS) { + if (!str.empty() && !filter.content.empty() && str_util::contains(str, filter.content)) { + return s7.offset + true_symbol_offset(g_game_version); + } + } else if (filter.type == DebugTextFilter::Type::REGEX) { + if (str_util::valid_regex(filter.content) && + std::regex_match(str, std::regex(filter.content))) { + return s7.offset + true_symbol_offset(g_game_version); + } + } + } + return s7.offset; +} diff --git a/game/kernel/common/kmachine.h b/game/kernel/common/kmachine.h index 284376b720..da26e8230d 100644 --- a/game/kernel/common/kmachine.h +++ b/game/kernel/common/kmachine.h @@ -82,4 +82,5 @@ void vif_interrupt_callback(int bucket_id); u64 pc_get_mips2c(u32 name); void send_gfx_dma_chain(u32 /*bank*/, u32 chain); void pc_texture_upload_now(u32 page, u32 mode); -void pc_texture_relocate(u32 dst, u32 src, u32 format); \ No newline at end of file +void pc_texture_relocate(u32 dst, u32 src, u32 format); +u64 pc_filter_debug_string(u32 str_ptr, u32 distance); diff --git a/game/kernel/jak2/kmachine.cpp b/game/kernel/jak2/kmachine.cpp index 7619c9cf65..b1e4189dbd 100644 --- a/game/kernel/jak2/kmachine.cpp +++ b/game/kernel/jak2/kmachine.cpp @@ -556,6 +556,9 @@ void InitMachine_PCPort() { // profiler make_function_symbol_from_c("pc-prof", (void*)prof_event); + // debugging tools + make_function_symbol_from_c("pc-filter-debug-string?", (void*)pc_filter_debug_string); + // init ps2 VM if (VM::use) { make_function_symbol_from_c("vm-ptr", (void*)VM::get_vm_ptr); diff --git a/game/tools/filter_menu/filter_menu.cpp b/game/tools/filter_menu/filter_menu.cpp new file mode 100644 index 0000000000..e31b6b6c70 --- /dev/null +++ b/game/tools/filter_menu/filter_menu.cpp @@ -0,0 +1,71 @@ +#include "filter_menu.h" + +#include "game/graphics/gfx.h" + +#include "third-party/fmt/core.h" +#include "third-party/imgui/imgui.h" +#include "third-party/imgui/imgui_stdlib.h" + +// TODO: +// - persist filters (need ability to remove option too) + +FiltersMenu::FiltersMenu() {} + +void FiltersMenu::draw_window() { + ImGui::Begin("Filters"); + + ImGui::SetNextItemOpen(true); + if (ImGui::TreeNode("Debug Text Filters")) { + auto& current_filters = Gfx::g_debug_settings.debug_text_filters; + // Iterate and display all current debug text filters + for (int i = 0; i < current_filters.size(); i++) { + std::string label = "contains?"; + if (current_filters[i].type == DebugTextFilter::Type::NOT_CONTAINS) { + label = "not-contains?"; + } else if (current_filters[i].type == DebugTextFilter::Type::REGEX) { + label = "regex?"; + } + ImGui::Text(label.c_str()); + ImGui::SameLine(); + ImGui::InputText(fmt::format("##filter-{}", i).c_str(), ¤t_filters[i].content); + } + + if (ImGui::Button("Add Contains")) { + DebugTextFilter new_filter; + new_filter.type = DebugTextFilter::Type::CONTAINS; + current_filters.push_back(new_filter); + } + ImGui::SameLine(); + if (ImGui::Button("Add Not-Contains")) { + DebugTextFilter new_filter; + new_filter.type = DebugTextFilter::Type::NOT_CONTAINS; + current_filters.push_back(new_filter); + } + if (ImGui::Button("Clear Filters")) { + current_filters.clear(); + } + // TODO - can't use regexes because i can't check it it's a valid regex without using a + // try-catch and this has issues running within gk + // + // An option is to bring in boost's regex lib + /*ImGui::SameLine(); + if (ImGui::Button("Add Regex")) { + DebugTextFilter new_filter; + new_filter.type = DebugTextFilter::Type::REGEX; + current_filters.push_back(new_filter); + }*/ + + if (ImGui::Checkbox("Enable Distance Check", &Gfx::g_debug_settings.debug_text_check_range)) { + Gfx::g_debug_settings.save_settings(); + } + if (Gfx::g_debug_settings.debug_text_check_range) { + if (ImGui::SliderFloat("Max Range", &Gfx::g_debug_settings.debug_text_max_range, 0, 250)) { + Gfx::g_debug_settings.save_settings(); + } + } + + ImGui::TreePop(); + } + + ImGui::End(); +} diff --git a/game/tools/filter_menu/filter_menu.h b/game/tools/filter_menu/filter_menu.h new file mode 100644 index 0000000000..8e195e15fe --- /dev/null +++ b/game/tools/filter_menu/filter_menu.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +#include "third-party/imgui/imgui.h" + +struct DebugTextFilter { + enum class Type { CONTAINS, NOT_CONTAINS, REGEX }; + + std::string content; + Type type; +}; + +class FiltersMenu { + public: + FiltersMenu(); + void draw_window(); +}; diff --git a/goal_src/jak2/engine/debug/debug.gc b/goal_src/jak2/engine/debug/debug.gc index 31b6e4aac3..25ae63d077 100644 --- a/goal_src/jak2/engine/debug/debug.gc +++ b/goal_src/jak2/engine/debug/debug.gc @@ -884,6 +884,11 @@ ) "Draw text at the given point. screen-offset can be #f." (when enable + (#when PC_PORT + ;; Check to see if the string should be filtered or not + (when (pc-filter-debug-string? text (vector-vector-distance position (target-pos 0))) + ;; no-op the function! + (return #f))) (cond (*debug-draw-pauseable* (let ((v1-2 (get-debug-text-3d))) diff --git a/goal_src/jak2/kernel-defs.gc b/goal_src/jak2/kernel-defs.gc index 4cc318558c..80710ecd38 100644 --- a/goal_src/jak2/kernel-defs.gc +++ b/goal_src/jak2/kernel-defs.gc @@ -218,4 +218,4 @@ ) (define-extern pc-prof (function string pc-prof-event none)) - +(define-extern pc-filter-debug-string? (function string float symbol))