From ba75f294ca3084b40f7bdd80eaeddd2224608d31 Mon Sep 17 00:00:00 2001 From: Kosten <120324510+Kosten47@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:31:20 +0100 Subject: [PATCH 1/9] Add files via upload From ff5a000f027f17be5104823d34a1d90731492efb Mon Sep 17 00:00:00 2001 From: Kosten <120324510+Kosten47@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:33:32 +0100 Subject: [PATCH 2/9] Add files via upload --- src/main.cpp | 516 +++++++++++++++++++++++++-------------------------- 1 file changed, 249 insertions(+), 267 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index f2ca56b4..ce75151c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,268 +1,250 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef GEODE_IS_WINDOWS -#include "gui.hpp" -#endif -#include "gui_mobile.hpp" -#include "config.hpp" -#include "hacks.hpp" - -void CheckDir(const std::filesystem::path &path) -{ - if (!std::filesystem::is_directory(path) || !std::filesystem::exists(path)) - { - std::filesystem::create_directory(path); - } -} - -$execute { - auto *mod = geode::Mod::get(); - ImGuiCocos::get().setForceLegacy(mod->getSettingValue("legacy-render")); - - auto& config = Config::get(); - config.load(fileDataPath); - - float speedhack_value = config.get("speedhack_value", 1.f); - speedhack_value = std::clamp(speedhack_value, 0.25f, 500.f); - config.set("speedhack_value", speedhack_value); - - float tps_value = config.get("tps_value", 60.f); - tps_value = std::clamp(tps_value, 30.f, 240.f); - config.set("tps_value", tps_value); - - CheckDir(folderMacroPath); - CheckDir(Config::get().get("showcases_path", folderPath / "Showcases")); -} - -static bool inited = false; - -void setupButton(cocos2d::CCNode* parent, const cocos2d::CCPoint& position, const std::string& buttonID, const std::string& childID = "") { - auto sprite = cocos2d::CCSprite::create(Config::get().get("invisible_ui_button", false) ? "GDH_buttonInvisible.png"_spr : "GDH_buttonUI.png"_spr); - sprite->setScale(0.85f); - auto myButton = geode::prelude::CCMenuItemExt::createSpriteExtra(sprite, [](CCMenuItemSpriteExtra* sender) { - auto& config = Config::get(); - if (!config.get("license_accepted", false)) { - FLAlertLayer::create("GDH Beta Testing", "Note: GDH is currently in beta testing, so some elements may be unstable/unfinished\n\nIn case of any issues/crashes, please report the problem in the GDH issues section on GitHub", "OK")->show(); - config.set("license_accepted", true); - return; - } - HacksLayer::create()->show(); - }); - myButton->setID(childID); - - if (childID.empty()) { - myButton->setPosition(position); - - auto menu = cocos2d::CCMenu::create(); - menu->setPosition(0, 0); - menu->addChild(myButton); - parent->addChild(menu); - } - else { - auto menu = parent->getChildByID(childID); - if (menu) { - menu->addChild(myButton); - menu->updateLayout(); - } - } - -} - -class $modify(MenuLayer) { - bool init() { - if (!MenuLayer::init()) return false; - - if (!inited) { - auto& hacks = Hacks::get(); - hacks.Init(); - hacks.loadKeybinds(); - - #ifdef GEODE_IS_WINDOWS - auto& gui = Gui::get(); - ImGuiCocos::get().setup([&gui] { - gui.Init(); - }).draw([&gui] { - gui.Render(); - }); - #endif - - inited = true; - } - - #ifdef GEODE_IS_ANDROID - - setupButton(this, {0, 0}, "hacks-button"_spr, "bottom-menu"); - - #endif - - return true; - } -}; - -#ifdef GEODE_IS_ANDROID - -class $modify(PauseLayer) { - void customSetup() { - PauseLayer::customSetup(); - - static geode::Mod* nodeId = geode::Loader::get()->getLoadedMod("geode.node-ids"); - static bool hasNodeIdMod = nodeId != nullptr && nodeId->isEnabled(); - - auto top = cocos2d::CCDirector::sharedDirector()->getScreenTop(); - if (hasNodeIdMod) - setupButton(this, {40, top - 45.f}, "hacks-button"_spr, "left-button-menu"); - else - setupButton(this, {40, top - 45.f}, "hacks-button"_spr); - } -}; - -class $modify(LevelInfoLayer) { - bool init(GJGameLevel *level, bool challenge) { - if (!LevelInfoLayer::init(level, challenge)) return false; - - static geode::Mod* nodeId = geode::Loader::get()->getLoadedMod("geode.node-ids"); - static bool hasNodeIdMod = nodeId != nullptr && nodeId->isEnabled(); - - auto top = cocos2d::CCDirector::sharedDirector()->getScreenTop(); - if (hasNodeIdMod) - setupButton(this, {0, 0}, "hacks-button"_spr, "left-side-menu"); - - return true; - } -}; - -class $modify(LevelSelectLayer) { - bool init(int page) { - if (!LevelSelectLayer::init(page)) return false; - setupButton(this, {30, cocos2d::CCDirector::sharedDirector()->getScreenTop() - 70.f}, "hacks-button"_spr); - return true; - } -}; - -class $modify(CreatorLayer) { - bool init() { - if (!CreatorLayer::init()) return false; - - static geode::Mod* nodeId = geode::Loader::get()->getLoadedMod("geode.node-ids"); - static bool hasNodeIdMod = nodeId != nullptr && nodeId->isEnabled(); - - auto top = cocos2d::CCDirector::sharedDirector()->getScreenTop(); - if (hasNodeIdMod) - setupButton(this, {0, 0}, "hacks-button"_spr, "bottom-left-menu"); - else - setupButton(this, {30, cocos2d::CCDirector::sharedDirector()->getWinSize().height / 2}, "hacks-button"_spr); - - return true; - } -}; - -class $modify(LevelBrowserLayer) { - bool init(GJSearchObject* p0) { - if (!LevelBrowserLayer::init(p0)) return false; - setupButton(this, {30, (cocos2d::CCDirector::sharedDirector()->getWinSize().height / 2) - 45.f}, "hacks-button"_spr); - return true; - } -}; - -class $modify(EditorPauseLayer) { - bool init(LevelEditorLayer* p0) { - if (!EditorPauseLayer::init(p0)) return false; - - static geode::Mod* nodeId = geode::Loader::get()->getLoadedMod("geode.node-ids"); - static bool hasNodeIdMod = nodeId != nullptr && nodeId->isEnabled(); - - auto top = cocos2d::CCDirector::sharedDirector()->getScreenTop(); - if (hasNodeIdMod) - setupButton(this, {0, 0}, "hacks-button"_spr, "guidelines-menu"); - else - setupButton(this, {cocos2d::CCDirector::sharedDirector()->getScreenRight() - 25.f, - cocos2d::CCDirector::sharedDirector()->getScreenTop() - 25.f}, "hacks-button"_spr); - return true; - } -}; - -class $modify(LevelSearchLayer) { - bool init(int p0) { - if (!LevelSearchLayer::init(p0)) return false; - setupButton(this, {30, cocos2d::CCDirector::sharedDirector()->getWinSize().height / 2}, "hacks-button"_spr); - return true; - } -}; - - -class $modify(EditLevelLayer) { - bool init(GJGameLevel *p0) { - if (!EditLevelLayer::init(p0)) return false; - static geode::Mod* nodeId = geode::Loader::get()->getLoadedMod("geode.node-ids"); - static bool hasNodeIdMod = nodeId != nullptr && nodeId->isEnabled(); - - if (hasNodeIdMod) - setupButton(this, {0, 0}, "hacks-button"_spr, "level-actions-menu"); - return true; - } -}; - -#endif - - -#ifdef GEODE_IS_WINDOWS -class $modify(cocos2d::CCEGLView) { - static void onModify(auto& self) { - (void)self.setHookPriority("cocos2d::CCEGLView::onGLFWKeyCallback", geode::Priority::First); - } - - void onGLFWKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { - CCEGLView::onGLFWKeyCallback(window, key, scancode, action, mods); - - if (inited) { - if (action == GLFW_PRESS || action == GLFW_REPEAT) { - auto& gui = Gui::get(); - auto& hacks = Hacks::get(); - if (gui.m_keybindMode && gui.m_waitingForBindKey) { - if (key == GLFW_KEY_BACKSPACE) - gui.m_keyToSet = 0; - else - gui.m_keyToSet = key; - gui.m_waitingForBindKey = false; - } - else if (!gui.m_keybindMode) { - auto& io = ImGui::GetIO(); - if (key == gui.m_toggleKey) { - gui.Toggle(); - } - - if (!io.WantCaptureKeyboard) { - hacks.toggleKeybinds(key); - } - } - } - } - } -}; - -class $modify(MouseKeybindingsManagerCCEGLVHook, cocos2d::CCEGLView) { - void onGLFWMouseCallBack(GLFWwindow* window, int button, int action, int mods) { - CCEGLView::onGLFWMouseCallBack(window, button, action, mods); - if (inited) { - if (action == GLFW_PRESS && button == GLFW_MOUSE_BUTTON_RIGHT) { - auto& io = ImGui::GetIO(); - io.AddMouseButtonEvent(1, true); - } - else if (action == GLFW_RELEASE && button == GLFW_MOUSE_BUTTON_RIGHT) { - auto& io = ImGui::GetIO(); - io.AddMouseButtonEvent(1, false); - } - } - } -}; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef GEODE_IS_WINDOWS +#include "gui.hpp" +#endif +#include "gui_mobile.hpp" +#include "config.hpp" +#include "hacks.hpp" + +void CheckDir(const std::filesystem::path &path) +{ + if (!std::filesystem::is_directory(path) || !std::filesystem::exists(path)) + { + std::filesystem::create_directory(path); + } +} + +$execute { + auto *mod = geode::Mod::get(); + ImGuiCocos::get().setForceLegacy(mod->getSettingValue("legacy-render")); + + auto& config = Config::get(); + config.load(fileDataPath); + + float speedhack_value = config.get("speedhack_value", 1.f); + speedhack_value = std::clamp(speedhack_value, 0.25f, 500.f); + config.set("speedhack_value", speedhack_value); + + float tps_value = config.get("tps_value", 60.f); + tps_value = std::clamp(tps_value, 30.f, 240.f); + config.set("tps_value", tps_value); + + CheckDir(folderMacroPath); + CheckDir(Config::get().get("showcases_path", folderPath / "Showcases")); +} + +static bool inited = false; + +void setupButton(cocos2d::CCNode* parent, const cocos2d::CCPoint& position, const std::string& buttonID, const std::string& childID = "") { + auto sprite = cocos2d::CCSprite::create(Config::get().get("invisible_ui_button", false) ? "GDH_buttonInvisible.png"_spr : "GDH_buttonUI.png"_spr); + sprite->setScale(0.85f); + auto myButton = geode::prelude::CCMenuItemExt::createSpriteExtra(sprite, [](CCMenuItemSpriteExtra* sender) { + auto& config = Config::get(); + if (!config.get("license_accepted", false)) { + FLAlertLayer::create("GDH Beta Testing", "Note: GDH is currently in beta testing, so some elements may be unstable/unfinished\n\nIn case of any issues/crashes, please report the problem in the GDH issues section on GitHub", "OK")->show(); + config.set("license_accepted", true); + return; + } + HacksLayer::create()->show(); + }); + myButton->setID(childID); + + if (childID.empty()) { + myButton->setPosition(position); + + auto menu = cocos2d::CCMenu::create(); + menu->setPosition(0, 0); + menu->addChild(myButton); + parent->addChild(menu); + } + else { + auto menu = parent->getChildByID(childID); + if (menu) { + menu->addChild(myButton); + menu->updateLayout(); + } + } + +} + +class $modify(MenuLayer) { + bool init() { + if (!MenuLayer::init()) return false; + + if (!inited) { + auto& hacks = Hacks::get(); + hacks.Init(); + hacks.loadKeybinds(); + + #ifdef GEODE_IS_WINDOWS + auto& gui = Gui::get(); + ImGuiCocos::get().setup([&gui] { + gui.Init(); + }).draw([&gui] { + gui.Render(); + }); + #endif + + inited = true; + } + + #ifdef GEODE_IS_ANDROID + + setupButton(this, {0, 0}, "hacks-button"_spr, "bottom-menu"); + + #endif + + return true; + } +}; + +#ifdef GEODE_IS_ANDROID + +class $modify(PauseLayer) { + void customSetup() { + PauseLayer::customSetup(); + + static geode::Mod* nodeId = geode::Loader::get()->getLoadedMod("geode.node-ids"); + static bool hasNodeIdMod = nodeId != nullptr && nodeId->isEnabled(); + + auto top = cocos2d::CCDirector::sharedDirector()->getScreenTop(); + if (hasNodeIdMod) + setupButton(this, {40, top - 45.f}, "hacks-button"_spr, "left-button-menu"); + else + setupButton(this, {40, top - 45.f}, "hacks-button"_spr); + } +}; + +class $modify(LevelInfoLayer) { + bool init(GJGameLevel *level, bool challenge) { + if (!LevelInfoLayer::init(level, challenge)) return false; + + static geode::Mod* nodeId = geode::Loader::get()->getLoadedMod("geode.node-ids"); + static bool hasNodeIdMod = nodeId != nullptr && nodeId->isEnabled(); + + auto top = cocos2d::CCDirector::sharedDirector()->getScreenTop(); + if (hasNodeIdMod) + setupButton(this, {0, 0}, "hacks-button"_spr, "left-side-menu"); + + return true; + } +}; + +class $modify(LevelSelectLayer) { + bool init(int page) { + if (!LevelSelectLayer::init(page)) return false; + setupButton(this, {30, cocos2d::CCDirector::sharedDirector()->getScreenTop() - 70.f}, "hacks-button"_spr); + return true; + } +}; + +class $modify(CreatorLayer) { + bool init() { + if (!CreatorLayer::init()) return false; + + static geode::Mod* nodeId = geode::Loader::get()->getLoadedMod("geode.node-ids"); + static bool hasNodeIdMod = nodeId != nullptr && nodeId->isEnabled(); + + auto top = cocos2d::CCDirector::sharedDirector()->getScreenTop(); + if (hasNodeIdMod) + setupButton(this, {0, 0}, "hacks-button"_spr, "bottom-left-menu"); + else + setupButton(this, {30, cocos2d::CCDirector::sharedDirector()->getWinSize().height / 2}, "hacks-button"_spr); + + return true; + } +}; + +class $modify(LevelBrowserLayer) { + bool init(GJSearchObject* p0) { + if (!LevelBrowserLayer::init(p0)) return false; + setupButton(this, {30, (cocos2d::CCDirector::sharedDirector()->getWinSize().height / 2) - 45.f}, "hacks-button"_spr); + return true; + } +}; + +class $modify(EditorPauseLayer) { + bool init(LevelEditorLayer* p0) { + if (!EditorPauseLayer::init(p0)) return false; + + static geode::Mod* nodeId = geode::Loader::get()->getLoadedMod("geode.node-ids"); + static bool hasNodeIdMod = nodeId != nullptr && nodeId->isEnabled(); + + auto top = cocos2d::CCDirector::sharedDirector()->getScreenTop(); + if (hasNodeIdMod) + setupButton(this, {0, 0}, "hacks-button"_spr, "guidelines-menu"); + else + setupButton(this, {cocos2d::CCDirector::sharedDirector()->getScreenRight() - 25.f, + cocos2d::CCDirector::sharedDirector()->getScreenTop() - 25.f}, "hacks-button"_spr); + return true; + } +}; + +class $modify(LevelSearchLayer) { + bool init(int p0) { + if (!LevelSearchLayer::init(p0)) return false; + setupButton(this, {30, cocos2d::CCDirector::sharedDirector()->getWinSize().height / 2}, "hacks-button"_spr); + return true; + } +}; + + +class $modify(EditLevelLayer) { + bool init(GJGameLevel *p0) { + if (!EditLevelLayer::init(p0)) return false; + static geode::Mod* nodeId = geode::Loader::get()->getLoadedMod("geode.node-ids"); + static bool hasNodeIdMod = nodeId != nullptr && nodeId->isEnabled(); + + if (hasNodeIdMod) + setupButton(this, {0, 0}, "hacks-button"_spr, "level-actions-menu"); + return true; + } +}; + +#endif + + +#ifdef GEODE_IS_WINDOWS +#include +#include "keyMapping.hpp" + +// onGLFWKeyCallback is inlined in 2.208 - use CCKeyboardDispatcher instead +// enumKeyCodes on Windows = VK codes, convert to GLFW via KeyMappingUtils +class $modify(GDHKeyboardHook, cocos2d::CCKeyboardDispatcher) { + bool dispatchKeyboardMSG(cocos2d::enumKeyCodes key, bool down, bool repeat, double time) { + if (inited && down) { + auto& gui = Gui::get(); + auto& hacks = Hacks::get(); + auto& io = ImGui::GetIO(); + + // Convert VK (enumKeyCodes) to GLFW key code + int glfwKey = KeyMappingUtils::GetGLFWFromWinAPI((int)key); + + if (gui.m_keybindMode && gui.m_waitingForBindKey && !repeat) { + gui.m_keyToSet = glfwKey; + gui.m_waitingForBindKey = false; + } + else if (!gui.m_keybindMode) { + if (glfwKey == gui.m_toggleKey && !repeat) { + gui.Toggle(); + } + if (!io.WantCaptureKeyboard) { + hacks.toggleKeybinds(glfwKey); + } + } + } + return CCKeyboardDispatcher::dispatchKeyboardMSG(key, down, repeat, time); + } +}; #endif \ No newline at end of file From 0da62017bbe1336ce0b027b99f8bed901e8893cc Mon Sep 17 00:00:00 2001 From: Kosten <120324510+Kosten47@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:34:28 +0100 Subject: [PATCH 3/9] Add files via upload --- src/gui.cpp | 3866 ++++++++++++++++++++++--------------------- src/gui_mobile.cpp | 85 +- src/gui_mobile.hpp | 21 +- src/hooks.cpp | 20 +- src/labels.cpp | 12 +- src/labels.hpp | 5 +- src/popupSystem.cpp | 7 +- src/popupSystem.hpp | 4 +- src/utils.cpp | 2 +- 9 files changed, 2021 insertions(+), 2001 deletions(-) diff --git a/src/gui.cpp b/src/gui.cpp index 7bbc63ef..5a9d9d09 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -1,1921 +1,1945 @@ -#include "gui.hpp" -#include -#include -#include "config.hpp" -#include "json.hpp" -#include "hacks.hpp" -#include "memory.hpp" -#include "labels.hpp" -#include "replayEngine.hpp" -#include "recorder.hpp" -#include "utils.hpp" -#ifdef GEODE_IS_WINDOWS -#include -#include "keyMapping.hpp" -#endif - -std::chrono::steady_clock::time_point animationStartTime; -bool isAnimating = false; -bool isFadingIn = false; - -std::string search_text; - -int selected_label_corner = 0; -int selected_label_type = 0; -std::string selected_label_text; - -void Gui::License() { - auto& config = Config::get(); - if (!m_license_inited) { - ImGui::SetNextWindowSize({590 * m_scale, 390 * m_scale}); - ImVec2 center = ImGui::GetMainViewport()->GetCenter(); - ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - m_license_inited = true; - } - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {20 * m_scale, 20 * m_scale}); - ImGui::Begin("Welcome", 0, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); - - ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_Button]); - ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]); - ImGui::Text("New era of GDH :)"); - ImGui::PopFont(); - ImGui::PopStyleColor(); - - ImGui::Text("GDH is an open-source mod menu under the MIT license"); - - ImGui::PushStyleColor(ImGuiCol_Text, ImColor(255, 128, 128).Value); - ImGui::TextWrapped("Note: GDH is currently in beta testing, so some elements may be unstable/unfinished\n\nIn case of any issues/crashes, please report the problem in the GDH issues section on GitHub"); - ImGui::PopStyleColor(); - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {10 * m_scale, 10 * m_scale}); - ImGui::BeginChild("##LicenseChild", {0, ImGui::GetContentRegionAvail().y - 40 * m_scale}, true); - ImGui::Text("Customize a comfortable size for GDH:"); - - const char* items[] = {"25%", "50%", "75%", "80%", "85%", "90%", "95%", "100%", "125%", "150%", "175%", "200%", "250%", "300%", "400%"}; - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGuiH::Combo("##Menu scale", &m_index_scale, items, IM_ARRAYSIZE(items))) { - m_scale = float(atof(items[m_index_scale])) / 100.0f; - config.set("gui_scale", m_scale); - config.set("gui_index_scale", m_index_scale); - m_needRescale = true; - ImGuiCocos::get().reload(); - } - - ImGui::Text("Pick your favorite accent color:"); - - int alpha = int(ImGui::GetStyle().Alpha * 255); - for (int i = 0; i < themes.size(); ++i) { - const auto& theme = themes[i]; - ImColor button_color(theme.color_bg.r, theme.color_bg.g, theme.color_bg.b, alpha); - - if (i == 0) - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.f * m_scale); - - if (ImGuiH::CircularButton(fmt::format("COLOR_{}", i).c_str(), 10.f * m_scale, button_color)) { - config.set("gui_color_index", i); - ApplyColor(theme); - } - ImGui::SameLine(); - } - - bool inverted = config.get("gui_inverted", false); - ImColor inverted_button_color = inverted ? ImColor(255, 255, 255) : ImColor(27, 27, 29, alpha); - ImColor inverted_button_hover_color = ImColor(64, 64, 64, alpha); - if (ImGuiH::CircularButton("TOGGLE_INVERT", 10.f * m_scale, inverted_button_color, true, inverted_button_hover_color)) { - config.set("gui_inverted", !inverted); - ImGui::GetIO().Inverted = !inverted; - ApplyGuiColors(!inverted); - } - - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Invert theme (beta)"); - - ImGui::Text("Menu Key:"); - - #ifdef GEODE_IS_WINDOWS - - renderKeyButton("Menu Key: ", m_toggleKey, true); - - #endif - - ImGui::EndChild(); - ImGui::PopStyleVar(); - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - if (ImGuiH::Button("Processed", {ImGui::GetContentRegionAvail().x, 30 * m_scale})) { - config.set("license_accepted", true); - } - ImGui::End(); - ImGui::PopStyleVar(); -} - -void Gui::updateCursorState() { - bool canShowInLevel = true; - if (auto* playLayer = PlayLayer::get()) { - canShowInLevel = playLayer->m_hasCompletedLevel || - playLayer->m_isPaused || - GameManager::sharedState()->getGameVariable("0024"); - } - if (m_show || canShowInLevel) - PlatformToolbox::showCursor(); - else - PlatformToolbox::hideCursor(); -} - -void Gui::animateAlpha() -{ - ImGuiStyle& style = ImGui::GetStyle(); - - auto currentTime = std::chrono::steady_clock::now(); - std::chrono::duration diff = currentTime - animationStartTime; - float elapsed = diff.count(); - - float time = Config::get().get("gui_anim_durr", 100) / 1000.0f; - if (elapsed >= time) - { - style.Alpha = isFadingIn ? 1.0f : 0.0f; - isAnimating = false; - - if (!isFadingIn) - { - m_show = !m_show; - Config::get().save(fileDataPath); - Labels::get().save(); - RGBIcons::get().save(); - Hacks::get().saveKeybinds(); - updateCursorState(); - search_text = ""; - Recorder::get().settings_openned = false; - } - - return; - } - - float t = elapsed / time; - float alpha = isFadingIn ? 0.0f + t: 1.0f - t; - style.Alpha = alpha; -} - -std::vector stretchedWindows; -void Gui::Render() { - - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1); - ImGuiH::Popup::get().render(); - - ImGui::PopStyleVar(); - - if (isAnimating) { - animateAlpha(); - } - - if (!m_show) return; - - auto &config = Config::get(); - if (!config.get("license_accepted", false)) { - License(); - return; - } - - auto& hacks = Hacks::get(); - for (auto& win : hacks.m_windows) { - std::string windowName = win.name; - if (std::find(stretchedWindows.begin(), stretchedWindows.end(), windowName) == stretchedWindows.end()) - { - ImVec2 windowSize = ImVec2(win.w, win.h); - ImVec2 windowPos = ImVec2(win.x, win.y); - - if (m_needRescale) { - windowSize = ImVec2(win.orig_w * m_scale, win.orig_h * m_scale); - windowPos = ImVec2(win.orig_x * m_scale, win.orig_y * m_scale); - } - - ImGui::SetNextWindowSize(windowSize); - ImGui::SetNextWindowPos(windowPos); - - stretchedWindows.push_back(windowName); - } - - auto& engine = ReplayEngine::get(); - bool re_needRecolor = (windowName == "Replay Engine") && (engine.mode != state::disable); - if (re_needRecolor) { - ImGui::PushStyleColor(ImGuiCol_Text, ImColor(0, 0, 0).Value); - if (engine.mode == state::record) { - ImGui::PushStyleColor(ImGuiCol_TitleBg, ImColor(255, 128, 128).Value); - ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImColor(255, 128, 128).Value); - ImGui::PushStyleColor(ImGuiCol_TitleBgCollapsed, ImColor(255, 128, 128).Value); - } - else { - ImGui::PushStyleColor(ImGuiCol_TitleBg, ImColor(34, 177, 76).Value); - ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImColor(34, 177, 76).Value); - ImGui::PushStyleColor(ImGuiCol_TitleBgCollapsed, ImColor(34, 177, 76).Value); - } - } - - ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); - ImGui::Begin(windowName.c_str(), 0, ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoNavInputs); - ImGui::PopFont(); - - if (re_needRecolor) ImGui::PopStyleColor(4); - - if (!ImGui::IsWindowCollapsed()) { - auto size = ImGui::GetWindowSize(); - auto pos = ImGui::GetWindowPos(); - - win.w = size.x; - win.h = size.y; - win.x = pos.x; - win.y = pos.y; - } - - - ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); - - - if (windowName == "GDH Settings") { - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputTextWithHint("##Search", "Search:", &search_text); - - if (ImGui::GetIO().MouseWheel != 0 && ImGui::IsItemActive()) - ImGui::SetWindowFocus(NULL); - - const char* items[] = {"25%", "50%", "75%", "80%", "85%", "90%", "95%", "100%", "125%", "150%", "175%", "200%", "250%", "300%", "400%"}; - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGuiH::Combo("##Menu scale", &m_index_scale, items, IM_ARRAYSIZE(items))) { - m_scale = float(atof(items[m_index_scale])) / 100.0f; - config.set("gui_scale", m_scale); - config.set("gui_index_scale", m_index_scale); - m_needRescale = true; - ImGuiCocos::get().reload(); - } - - int alpha = int(ImGui::GetStyle().Alpha * 255); - - for (int i = 0; i < themes.size(); ++i) { - const auto& theme = themes[i]; - ImColor button_color(theme.color_bg.r, theme.color_bg.g, theme.color_bg.b, alpha); - - if (i == 0) - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.f * m_scale); - - if (ImGuiH::CircularButton(fmt::format("COLOR_{}", i).c_str(), 10.f * m_scale, button_color)) { - config.set("gui_color_index", i); - ApplyColor(theme); - } - - ImGui::SameLine(); - } - - bool inverted = config.get("gui_inverted", false); - ImColor inverted_button_color = inverted ? ImColor(255, 255, 255) : ImColor(27, 27, 29, alpha); - ImColor inverted_button_hover_color = ImColor(64, 64, 64, alpha); - - if (ImGuiH::CircularButton("TOGGLE_INVERT", 10.f * m_scale, inverted_button_color, true, inverted_button_hover_color)) { - config.set("gui_inverted", !inverted); - ImGui::GetIO().Inverted = !inverted; - ApplyGuiColors(!inverted); - } - - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Invert theme (beta)"); - - #ifdef GEODE_IS_WINDOWS - - if (ImGui::BeginPopupModal("Keybinds Settings", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::BeginChild("##Keybinds", {420.f * m_scale, 400.f * m_scale}); - - bool useKeybindsOnlyInGame = config.get("use_keybinds_only_in_game", true); - if (ImGuiH::Checkbox("Use keybinds only in game", &useKeybindsOnlyInGame, m_scale)) { - config.set("use_keybinds_only_in_game", useKeybindsOnlyInGame); - } - - ImGui::Text("UI"); - ImGui::Separator(); - ImGui::Spacing(); - - renderKeyButton("Menu Key: ", m_toggleKey); - - ImGui::Text("Framerate"); - ImGui::Separator(); - ImGui::Spacing(); - - renderKeyButton("Speedhack Key: ", m_speedhackKey); - renderKeyButton("TPS Bypass Key: ", m_tpsKey); - - ImGui::Text("Replay Engine"); - ImGui::Separator(); - ImGui::Spacing(); - - renderKeyButton("Disable/Playback Macro: ", m_playbackKey); - renderKeyButton("Save Macro by Current Level Name: ", m_saveMacroByCurrentNameKey); - renderKeyButton("Load Macro by Current Level Name: ", m_loadMacroByCurrentNameKey); - renderKeyButton("Enable Frame Advance + Next Frame: ", m_frameAdvanceEnableKey); - renderKeyButton("Disable Frame Advance: ", m_frameAdvanceDisableKey); - - ImGui::Text("Shortcuts"); - ImGui::Separator(); - ImGui::Spacing(); - - renderKeyButton("Options: ", m_optionsKey); - renderKeyButton("Reset Level: ", m_resetLevelKey); - renderKeyButton("Practice Mode: ", m_practiceModeKey); - renderKeyButton("Reset Volume: ", m_resetVolumeKey); - renderKeyButton("Uncomplete Level: ", m_uncompleteLevelKey); - - ImGui::Text("Startpos Switcher"); - ImGui::Separator(); - ImGui::Spacing(); - - renderKeyButton("Startpos Switcher Left: ", m_startposSwitcherLeftKey); - renderKeyButton("Startpos Switcher Right: ", m_startposSwitcherRightKey); - - ImGui::EndChild(); - - ImGui::Separator(); - - ImGui::Text("Tip: To disable the bind, bind it to backspace"); - - if (ImGuiH::Button("Close", {420 * m_scale, NULL})) - ImGui::CloseCurrentPopup(); - - ImGui::EndPopup(); - } - - ImGuiH::Checkbox("Keybinds Mode", &m_keybindMode, m_scale); - - if (m_keybindMode && ImGui::IsItemHovered()) - ImGui::SetTooltip("Binds for toggle UI, Speedhack, Replay Engine, and more in the More Keybinds"); - - #endif - - if (ImGuiH::Button(m_keybindMode ? "More Keybinds" : "More Settings", {ImGui::GetContentRegionAvail().x, 0})) { - if (!m_keybindMode) - ImGui::OpenPopup("GDH More Settings"); - else - ImGui::OpenPopup("Keybinds Settings"); - } - - if (ImGui::BeginPopupModal("GDH More Settings", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { - int anim_durr = config.get("gui_anim_durr", 100); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::DragInt("##gui_anim_durr", &anim_durr, 1, 0, 10000, "Transition Time: %ims")) { - config.set("gui_anim_durr", anim_durr); - } - - if (ImGuiH::Button("Sort Windows", {ImGui::GetContentRegionAvail().x, NULL})) - stretchedWindows.clear(); - - if (ImGuiH::Button("Close", {400 * m_scale, 0})) { - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } - - #ifdef GEODE_IS_WINDOWS - - static int priorityIndex = 3; - const char* priorityNames[] = {"Real-time", "High", "Above Normal", "Normal", "Below Normal", "Low"}; - DWORD priorityValues[] = {REALTIME_PRIORITY_CLASS, HIGH_PRIORITY_CLASS, ABOVE_NORMAL_PRIORITY_CLASS, - NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, IDLE_PRIORITY_CLASS}; - - ImGui::Text("Process Priority"); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGuiH::Combo("##Process Priority", &priorityIndex, priorityNames, IM_ARRAYSIZE(priorityNames))) { - if (SetPriorityClass(GetCurrentProcess(), priorityValues[priorityIndex])) { - ImGuiH::Popup::get().add_popup(fmt::format("Priority changed to: {}", priorityNames[priorityIndex])); - } else { - ImGuiH::Popup::get().add_popup("Failed to change priority!"); - } - } - - #endif - - } - else if (windowName == "Framerate") { - bool tps_enabled = config.get("tps_enabled", false); - float tps_value = config.get("tps_value", 60.f);; - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - (35 + 5) * m_scale); - if (ImGui::DragFloat("##tps_value", &tps_value, 1, 1, FLT_MAX, "%0.f TPS")) - config.set("tps_value", tps_value); - - ImGui::SameLine(); - if (ImGuiH::Checkbox("##tps_enabled", &tps_enabled, m_scale)) - config.set("tps_enabled", tps_enabled); - - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Multiplies the number of ticks per second, used mainly for botting\n(not recommended for normal use as it ruins the game's performance)"); - - bool speedhack_enabled = config.get("speedhack_enabled", false); - float speedhack_value = config.get("speedhack_value", 1.f); - - bool speedhackAudio_enabled = config.get("speedhackAudio_enabled", false); - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - (35 + 5) * m_scale); - if (ImGui::DragFloat("##speedhack_value", &speedhack_value, 0.01f, 0, FLT_MAX, "Speed: %.2fx")) - config.set("speedhack_value", speedhack_value); - - ImGui::SameLine(); - if (ImGuiH::Checkbox("##speedhack_enabled", &speedhack_enabled, m_scale)) - config.set("speedhack_enabled", speedhack_enabled); - - if (ImGuiH::Checkbox("Speedhack Audio", &speedhackAudio_enabled, m_scale)) - config.set("speedhackAudio_enabled", speedhackAudio_enabled); - - ImGui::SameLine(); - - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10.f * m_scale); - if (ImGuiH::ArrowButton("##more_tps_settings", ImGuiDir_Down)) { - ImGui::OpenPopup("More TPS Settings"); - } - - if (ImGui::BeginPopup("More TPS Settings")) { - bool tps_real_time = config.get("tps::real_time", true); - if (ImGuiH::Checkbox("Real Time", &tps_real_time, m_scale)) - config.set("tps::real_time", tps_real_time); - - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Allows real-time update of multiplied ticks (may cause lags)"); - - int resumeTimer = config.get("resumeTimer_value", 2); - if (ImGui::DragInt("##ResumerTimer", &resumeTimer, 1, 2, INT_MAX, "Resume Timer: %d")) - config.set("resumeTimer_value", resumeTimer); - - ImGui::Text("Higher resume timer means a smoother start but a delay (adjust as needed)"); - } - } - else if (windowName == "Variables") { - static int type_index = 0; - static int player_index = 0; - static int creator_index = 0; - static std::string value; - - const char* types[] = {"Creator", "Player"}; - const char* player[] = {"Attempts", "Jumps", "Normal %", "Position X", "Position Y", "Practice %", "Song ID", "Speed", "Level ID"}; - const char* creator[] = {"Object ID"}; - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGuiH::Combo("##TypeVars", &type_index, types, IM_ARRAYSIZE(types)); - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (type_index == 0) - ImGuiH::Combo("##CreatorVars", &creator_index, creator, IM_ARRAYSIZE(creator)); - else if (type_index == 1) - ImGuiH::Combo("##PlayerVars", &player_index, player, IM_ARRAYSIZE(player)); - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputText("##ValueVar", &value); - - auto handleGetSetCreator = [&](bool isSet) { - auto EditorUI = EditorUI::get(); - if (!EditorUI) return; - - if (isSet) { - EditorUI::get()->m_selectedObjectIndex = std::stoi(value); - } else { - value = std::to_string(EditorUI::get()->m_selectedObjectIndex); - } - }; - - auto handleGetSetPlayer = [&](bool isSet) { - auto pl = PlayLayer::get(); - if (!pl) return; - - auto& level = *pl->m_level; - auto& player1 = *pl->m_player1; - - switch (player_index) { - case 0: // Attempts - if (isSet) level.m_attempts = std::stoi(value); - else value = std::to_string(level.m_attempts); - break; - case 1: // Jumps - if (isSet) level.m_jumps = std::stoi(value); - else value = std::to_string(level.m_jumps); - break; - case 2: // Normal % - if (isSet) level.m_normalPercent = std::stoi(value); - else value = std::to_string(level.m_normalPercent); - break; - case 3: // Position X - if (isSet) player1.m_position.x = std::stof(value); - else value = std::to_string(player1.m_position.x); - break; - case 4: // Position Y - if (isSet) player1.m_position.y = std::stof(value); - else value = std::to_string(player1.m_position.y); - break; - case 5: // Practice % - if (isSet) level.m_practicePercent = std::stoi(value); - else value = std::to_string(level.m_practicePercent); - break; - case 6: // Song ID - if (isSet) level.m_songID = std::stoi(value); - else value = std::to_string(level.m_songID); - break; - case 7: // Speed - if (isSet) player1.m_playerSpeed = std::stof(value); - else value = std::to_string(player1.m_playerSpeed); - break; - case 8: // Level ID - if (isSet) level.m_levelID = std::stoi(value); - else value = std::to_string(level.m_levelID); - break; - } - }; - - if (ImGuiH::Button("Get", {ImGui::GetContentRegionAvail().x / 2, NULL})) { - if (type_index == 0) handleGetSetCreator(false); - else if (type_index == 1) handleGetSetPlayer(false); - } - - ImGui::SameLine(); - - if (ImGuiH::Button("Set", {ImGui::GetContentRegionAvail().x, NULL})) { - if (utilsH::isNumeric(value)) { - if (type_index == 0) handleGetSetCreator(true); - else if (type_index == 1) handleGetSetPlayer(true); - } - } - } - else if (windowName == "Replay Engine") { - static geode::Mod* cbfMod = geode::Loader::get()->getLoadedMod("syzzi.click_between_frames"); - static bool hasCBF = cbfMod != nullptr && cbfMod->isEnabled(); - - if (hasCBF && !cbfMod->getSettingValue("soft-toggle")) { - ImGui::PushStyleColor(ImGuiCol_Text, ImColor(255, 128, 128).Value); - ImGui::TextWrapped("Click Between Frames mod is enabled, turn it off to use Replay Engine"); - ImGui::PopStyleColor(); - } - else { - static std::vector replay_list; - - if (ImGui::BeginPopupModal("Select Replay", 0, ImGuiWindowFlags_NoResize)) { - ImGui::BeginChild("Select Replay##2", {400 * m_scale, 300 * m_scale}); - for (int i = 0; i < (int)replay_list.size(); i++) - { - if (ImGuiH::Button(replay_list[i].filename().string().c_str(), {ImGui::GetContentRegionAvail().x, NULL})) - { - std::string extension = replay_list[i].filename().extension().string(); - if (extension != ".re" && extension != ".re2") - engine.replay_name = replay_list[i].filename().replace_extension().string(); - else - engine.replay_name = replay_list[i].filename().string(); - ImGui::CloseCurrentPopup(); - } - } - ImGui::EndChild(); - - if (ImGuiH::Button("Open Folder", {ImGui::GetContentRegionAvail().x, NULL})) { - geode::utils::file::openFolder(folderMacroPath); - } - - if (ImGuiH::Button("Close", {ImGui::GetContentRegionAvail().x, NULL})) { - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } - - int mode_ = (int)engine.mode; - - if (ImGuiH::RadioButton("Disable", &mode_, 0, m_scale)) - engine.mode = state::disable; - - ImGui::SameLine(); - - if (ImGuiH::RadioButton("Record", &mode_, 1, m_scale)) - { - bool canRecord = (!engine.engine_v2 && config.get("tps_enabled", false)) || (engine.engine_v2 && !config.get("tps_enabled", false)); - - if (canRecord) - { - if (engine.mode != state::record) { - engine.clear(); - } - engine.mode = state::record; - } - else - { - engine.mode = state::disable; - ImGuiH::Popup::get().add_popup(engine.engine_v2 ? "Disable TPS Bypass to record the replay" : "Enable TPS Bypass to record the replay"); - } - } - ImGui::SameLine(); - - if (ImGuiH::RadioButton("Play", &mode_, 2, m_scale)) { - bool canPlay = (!engine.engine_v2 && config.get("tps_enabled", false)) || (engine.engine_v2 && !config.get("tps_enabled", false)); - - if (canPlay) { - engine.mode = state::play; - } else { - engine.mode = state::disable; - ImGuiH::Popup::get().add_popup(engine.engine_v2 ? "Disable TPS Bypass to playback the replay" : "Enable TPS Bypass to playback the replay"); - } - } - - ImGui::Separator(); - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 35 * m_scale); - ImGui::InputTextWithHint("##replay_name", "Enter the macro name", &engine.replay_name); - - ImGui::SameLine(); - - if (ImGuiH::ArrowButton("##replay_select", ImGuiDir_Down)) { - replay_list.clear(); - for (const auto &entry : std::filesystem::directory_iterator(folderMacroPath)) { - std::string ext = entry.path().filename().extension().string(); - if (!engine.engine_v2 ? (ext == ".re" || ext == ".re3") : (ext == ".re21" || ext == ".re2")) { - replay_list.push_back(entry); - } - } - ImGui::OpenPopup("Select Replay"); - } - - if (ImGuiH::Button("Save", {ImGui::GetContentRegionAvail().x / 3, NULL})) { - ImGuiH::Popup::get().add_popup(engine.save(engine.replay_name)); - } - ImGui::SameLine(); - - if (ImGuiH::Button("Load", {ImGui::GetContentRegionAvail().x / 2, NULL})) { - ImGuiH::Popup::get().add_popup(engine.load(engine.replay_name)); - } - - if (ImGui::IsItemClicked(1) && ImGui::IsItemHovered()) { - ImGui::OpenPopup("P1/P2 Load"); - } - - if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) { - ImGui::SetTooltip("Right click to load P1/P2 macros"); - } - - if (ImGui::BeginPopup("P1/P2 Load")) { - if (ImGuiH::Button("Load P1")) { - ImGuiH::Popup::get().add_popup(engine.load(engine.replay_name, true, false)); - ImGui::CloseCurrentPopup(); - } - - ImGui::SameLine(); - - if (ImGuiH::Button("Load P2")) { - ImGuiH::Popup::get().add_popup(engine.load(engine.replay_name, false, true)); - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } - - - ImGui::SameLine(); - - if (ImGuiH::Button("Clear", {ImGui::GetContentRegionAvail().x, NULL})) { - engine.clear(); - ImGuiH::Popup::get().add_popup("Replay has been cleared"); - } - - ImGui::Text("Replay Size: %i/%zu", engine.get_current_index(), engine.get_actions_size()); - ImGui::Text("Frame: %i", engine.get_frame()); - - ImGui::Separator(); - - #ifdef GEODE_IS_WINDOWS - auto& recorder = Recorder::get(); - auto& recorderAudio = RecorderAudio::get(); - - if (recorder.settings_openned) { - if (first_time_re_settings) { - first_time_re_settings = false; - ImGui::SetNextWindowSize({800 * m_scale, 540 * m_scale}); - } - } - - if (ImGui::BeginPopupModal("Replay Engine More Settings", &recorder.settings_openned) && ImGui::BeginTabBar("Engine Tabs")) { - auto containsRussianLetters = [](const std::filesystem::path& p) -> bool { - auto pathStr = p.u8string(); - for (char c : pathStr) { - if ((unsigned char)c >= 0xD0 && (unsigned char)c <= 0xD1) { - return true; - } - } - return false; - }; - - if (ImGui::BeginTabItem("Settings")) { - if (ImGuiH::Checkbox("Engine v2.1 (Beta)", &engine.engine_v2, m_scale)) - config.set("engine::v2", engine.engine_v2); - - if (!engine.engine_v2) { - ImGuiH::Checkbox("Accuracy Fix", &engine.accuracy_fix, m_scale); - ImGui::SameLine(); - ImGuiH::Checkbox("Rotation Fix", &engine.rotation_fix, m_scale); - } - - // bool practice_fix = config.get("practice_fix", false); - // if (ImGuiH::Checkbox("Practice Fix", &practice_fix, m_scale)) - // config.set("practice_fix", practice_fix); - - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem("Editor")) { - static int selected_index = 0; - static int selected_frame_type = 1; - static int selected_player = 0; - static int prev_frame_type = selected_frame_type; - static int prev_player = selected_player; - - const char* frame_types[] = { "Physic Frames", "Input Frames" }; - const char* player_labels[] = { "Player 1", "Player 2" }; - - bool isPlayer1 = (selected_player == 0); - - if (prev_frame_type != selected_frame_type || prev_player != selected_player) { - selected_index = 0; - prev_frame_type = selected_frame_type; - prev_player = selected_player; - } - - if (selected_frame_type == 0) { - auto& frames = engine.getPhysicFrames(isPlayer1); - - if (frames.empty()) { - selected_index = -1; - } else if (selected_index >= frames.size()) { - selected_index = frames.size() - 1; - } - - if (ImGui::BeginTable("PhysicTable", 5, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY, - ImVec2(ImGui::GetContentRegionAvail().x - 200 * m_scale, 0))) { - - ImGui::TableSetupColumn("Frame"); - ImGui::TableSetupColumn("X"); - ImGui::TableSetupColumn("Y"); - ImGui::TableSetupColumn("Rotation"); - ImGui::TableSetupColumn("Y Accel"); - ImGui::TableHeadersRow(); - - ImGuiListClipper clipper; - clipper.Begin(frames.size()); - while (clipper.Step()) { - for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { - const auto& frame = frames[i]; - ImGui::TableNextRow(); - - bool is_selected = (selected_index == i); - ImGui::TableSetColumnIndex(0); - std::string frame_str = std::to_string(frame.frame) + "##" + std::to_string(i); - if (ImGui::Selectable(frame_str.c_str(), is_selected, ImGuiSelectableFlags_SpanAllColumns)) { - selected_index = i; - } - - ImGui::TableSetColumnIndex(1); ImGui::Text("%f", frame.x); - ImGui::TableSetColumnIndex(2); ImGui::Text("%f", frame.y); - ImGui::TableSetColumnIndex(3); ImGui::Text("%f", frame.rotation); - ImGui::TableSetColumnIndex(4); ImGui::Text("%f", frame.y_accel); - } - } - - ImGui::EndTable(); - } - } else { - auto& frames = engine.getInputFrames(isPlayer1); - - if (frames.empty()) { - selected_index = -1; - } else if (selected_index >= frames.size()) { - selected_index = frames.size() - 1; - } - - if (ImGui::BeginTable("InputTable", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY, - ImVec2(ImGui::GetContentRegionAvail().x - 200 * m_scale, 0))) { - - ImGui::TableSetupColumn("Frame"); - ImGui::TableSetupColumn("Button"); - ImGui::TableSetupColumn("Action"); - ImGui::TableHeadersRow(); - - ImGuiListClipper clipper; - clipper.Begin(frames.size()); - while (clipper.Step()) { - for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { - const auto& frame = frames[i]; - ImGui::TableNextRow(); - - bool is_selected = (selected_index == i); - ImGui::TableSetColumnIndex(0); - - std::string frame_str = std::to_string(frame.frame) + "##" + std::to_string(i); - if (ImGui::Selectable(frame_str.c_str(), is_selected, ImGuiSelectableFlags_SpanAllColumns)) { - selected_index = i; - } - - ImGui::TableSetColumnIndex(1); ImGui::Text((frame.button == 1) ? "Up" : (frame.button == 2) ? "Left" : - (frame.button == 3) ? "Right" : "Unknown"); - ImGui::TableSetColumnIndex(2); ImGui::Text("%s", frame.down ? "Push" : "Release"); - } - } - - ImGui::EndTable(); - } - } - - ImGui::SameLine(); - - if (ImGui::BeginChild("##EditorSettings", {0, ImGui::GetContentRegionAvail().y})) { - ImGui::Text("Frame Type:"); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::Combo("##Frame Type", &selected_frame_type, frame_types, IM_ARRAYSIZE(frame_types)); - - ImGui::Text("Player:"); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::Combo("##Player", &selected_player, player_labels, IM_ARRAYSIZE(player_labels)); - - ImGui::Separator(); - ImGui::Text("Actions:"); - - if (selected_frame_type == 0) { - auto& frames = engine.getPhysicFrames(isPlayer1); - - if (ImGuiH::Button("Add Action", ImVec2(ImGui::GetContentRegionAvail().x, 0))) { - replay_data new_frame; - - new_frame.x = 0.0f; - new_frame.y = 0.0f; - new_frame.rotation = 0.0f; - new_frame.y_accel = 0.0f; - new_frame.player = isPlayer1; - - if (!frames.empty() && selected_index >= 0 && selected_index < frames.size()) { - new_frame.frame = frames[selected_index].frame + 1; - - frames.insert(frames.begin() + selected_index + 1, new_frame); - selected_index++; - } else { - new_frame.frame = 0; - frames.push_back(new_frame); - selected_index = frames.size() - 1; - } - } - - if (!frames.empty() && selected_index >= 0 && selected_index < frames.size()) { - ImGui::BeginDisabled(frames.size() < 1); - if (ImGuiH::Button("Remove Action", ImVec2(ImGui::GetContentRegionAvail().x, 0))) { - frames.erase(frames.begin() + selected_index); - - if (frames.empty()) { - selected_index = -1; - } else if (selected_index >= frames.size()) { - selected_index = frames.size() - 1; - } - } - ImGui::EndDisabled(); - - ImGui::BeginDisabled(selected_index <= 0); - if (ImGuiH::Button("Move Up", ImVec2((ImGui::GetContentRegionAvail().x / 2.0f) - 2.0f, 0))) { - if (selected_index > 0) { - std::swap(frames[selected_index], frames[selected_index - 1]); - selected_index--; - } - } - ImGui::EndDisabled(); - - ImGui::SameLine(); - - ImGui::BeginDisabled(selected_index >= frames.size() - 1); - if (ImGuiH::Button("Move Down", ImVec2(ImGui::GetContentRegionAvail().x, 0))) { - if (selected_index < frames.size() - 1) { - std::swap(frames[selected_index], frames[selected_index + 1]); - selected_index++; - } - } - ImGui::EndDisabled(); - - ImGui::Separator(); - } - - if (!frames.empty() && selected_index >= 0 && selected_index < frames.size()) { - auto& selected_frame = frames[selected_index]; - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - int frame_cast = static_cast(selected_frame.frame); - if (ImGui::DragInt("Frame##Drag", &frame_cast, 1.f, 0, INT_MAX, "Frame: %i")) { - selected_frame.frame = static_cast(frame_cast); - } - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::DragFloat("X##Drag", &selected_frame.x, 0.001f, -FLT_MAX, FLT_MAX, "X Position: %f"); - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::DragFloat("Y##Drag", &selected_frame.y, 0.001f, -FLT_MAX, FLT_MAX, "Y Position: %f"); - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::DragFloat("Rotation##Drag", &selected_frame.rotation, 0.001f, -FLT_MAX, FLT_MAX, "Rotation: %f"); - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - float yAccel_cast = static_cast(selected_frame.y_accel); - if (ImGui::DragFloat("YAccel##Drag", &yAccel_cast, 0.001f, -FLT_MAX, FLT_MAX, "Y Accel: %f")) { - selected_frame.y_accel = static_cast(yAccel_cast); - } - } else { - ImGui::TextColored(ImColor(255, 128, 128).Value, "No physic frames"); - } - } else if (selected_frame_type == 1) { - auto& frames = engine.getInputFrames(isPlayer1); - - if (ImGuiH::Button("Add Action", ImVec2(ImGui::GetContentRegionAvail().x, 0))) { - replay_data2 new_frame; - - new_frame.button = 1; - new_frame.down = false; - new_frame.isPlayer1 = isPlayer1; - - if (!frames.empty() && selected_index >= 0 && selected_index < frames.size()) { - new_frame.frame = frames[selected_index].frame + 1; - - frames.insert(frames.begin() + selected_index + 1, new_frame); - selected_index++; - } else { - new_frame.frame = 0; - frames.push_back(new_frame); - selected_index = frames.size() - 1; - } - } - - if (!frames.empty() && selected_index >= 0 && selected_index < frames.size()) { - ImGui::BeginDisabled(frames.size() < 1); - if (ImGuiH::Button("Remove Action", ImVec2(ImGui::GetContentRegionAvail().x, 0))) { - frames.erase(frames.begin() + selected_index); - - if (frames.empty()) { - selected_index = -1; - } else if (selected_index >= frames.size()) { - selected_index = frames.size() - 1; - } - } - ImGui::EndDisabled(); - - ImGui::BeginDisabled(selected_index <= 0); - if (ImGuiH::Button("Move Up", ImVec2((ImGui::GetContentRegionAvail().x / 2.0f) - 2.0f, 0))) { - if (selected_index > 0) { - std::swap(frames[selected_index], frames[selected_index - 1]); - selected_index--; - } - } - ImGui::EndDisabled(); - - ImGui::SameLine(); - - ImGui::BeginDisabled(selected_index >= frames.size() - 1); - if (ImGuiH::Button("Move Down", ImVec2(ImGui::GetContentRegionAvail().x, 0))) { - if (selected_index < frames.size() - 1) { - std::swap(frames[selected_index], frames[selected_index + 1]); - selected_index++; - } - } - ImGui::EndDisabled(); - - ImGui::Separator(); - } - - if (!frames.empty() && selected_index >= 0 && selected_index < frames.size()) { - auto& selected_frame = frames[selected_index]; - - int frame_cast = static_cast(selected_frame.frame); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::DragInt("Frame##Drag", &frame_cast, 1, 0, INT_MAX, "Frame: %i")) { - selected_frame.frame = static_cast(frame_cast); - } - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::DragInt("Button##Drag", &selected_frame.button, 1, 1, 3, "Button: %i"); - - ImGuiH::Checkbox("Push", &selected_frame.down, m_scale); - } else { - ImGui::TextColored(ImColor(255, 128, 128).Value, "No input frames"); - } - } - - ImGui::EndChild(); - } - - ImGui::EndTabItem(); - } - - - if (ImGui::BeginTabItem("Recorder")) { - if (recorder.ffmpeg_installed) { - auto pl = PlayLayer::get(); - if (ImGuiH::Checkbox("Record##Recorder", &recorder.enabled, m_scale)) { - ImVec2 displaySize = ImGui::GetIO().DisplaySize; - - if (containsRussianLetters(recorder.folderShowcasesPath)) { - recorder.enabled = false; - ImGuiH::Popup::get().add_popup("Invalid path to the showcase folder. Please remove any Cyrillic characters"); - } - else if (pl && pl->m_hasCompletedLevel) { - ImGuiH::Popup::get().add_popup("Restart level to start recording"); - recorder.enabled = false; - } - else { - bool fps_enabled = config.get("tps_enabled", false); - bool check = recorder.fps <= config.get("tps_value", 60.f); - bool check2 = recorder.fps >= 60 && recorder.fps <= 240; - - if (engine.engine_v2 ? (!fps_enabled && check2) : (fps_enabled && check)) { - if (recorder.enabled) { - if (recorder.overlay_mode && !recorder.native_mode) { - auto size = ImGui::GetIO().DisplaySize; - recorder.width = static_cast(size.x); - recorder.height = static_cast(size.y); - } - - if (!recorder.advanced_mode) { - recorder.full_cmd = recorder.compile_command(); - } - - recorder.start(recorder.full_cmd); - } - else { - recorder.stop(); - } - } - else { - recorder.enabled = false; - if (engine.engine_v2) { - if (fps_enabled) - ImGuiH::Popup::get().add_popup("Disable TPS Bypass to start render"); - - if (!check2) - ImGuiH::Popup::get().add_popup("Recorder FPS values must be within the range 60 to 240"); - } - else { - recorder.enabled = false; - if (!fps_enabled) - ImGuiH::Popup::get().add_popup("Enable TPS Bypass to start render"); - - if (!check) - ImGuiH::Popup::get().add_popup("Recorder FPS should not be more than the current TPS"); - } - - } - } - } - - ImGui::SameLine(); - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputText("##videoname", &recorder.video_name); - - bool use_flvc = recorder.video_name.ends_with(".flvc"); - - if (use_flvc) { - ImGui::PushStyleColor(ImGuiCol_Text, ImColor(255, 128, 128).Value); - ImGui::Text("You are trying to encode a video in Free Lossless Video Codec (FLVC)"); - ImGui::TextWrapped("- Make sure you have enough disk space: a 1280x720 60 FPS video of 10 seconds takes more than 100 MB (even more at higher resolutions)"); - ImGui::TextWrapped("- Recommended to record the showcase on HDD (Large file volumes can compromise SSD integrity due to write cycle limitations)"); - ImGui::PopStyleColor(); - } - - - if (!use_flvc) { - ImGuiH::Checkbox("Advanced Mode", &recorder.advanced_mode, m_scale); - - if (recorder.advanced_mode) { - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputText("##full_args", &recorder.full_cmd); - if (ImGuiH::Button("Compile")) { - recorder.full_cmd = recorder.compile_command(); - } - - ImGui::SameLine(); - if (ImGuiH::Button("Copy to clipboard")) { - ImGui::SetClipboardText(recorder.full_cmd.c_str()); - } - - ImGui::SameLine(); - if (ImGuiH::Button("Paste to from clipboard")) { - recorder.full_cmd = (std::string)ImGui::GetClipboardText(); - } - } - } - - if (ImGuiH::Checkbox("Overlay Mode", &recorder.overlay_mode, m_scale)) { - bool isFullscreen = !GameManager::sharedState()->getGameVariable("0025"); - if (!isFullscreen && !recorder.native_mode) { - recorder.overlay_mode = false; - ImGuiH::Popup::get().add_popup("Please switch to fullscreen to record in overlay mode (or enable native mode)"); - } - } - - ImGui::SameLine(); - - if (ImGuiH::Checkbox("Native Mode", &recorder.native_mode, m_scale)) { - bool isFullscreen = !GameManager::sharedState()->getGameVariable("0025"); - if (isFullscreen) { - recorder.native_mode = false; - ImGuiH::Popup::get().add_popup("Please switch to windowed mode to record in native mode mode"); - } - } - - if (recorder.native_mode) { - ImGui::SameLine(); - ImGui::SetNextItemWidth(200.f * m_scale); - if (ImGui::DragFloat("##PauseLayerScale", &recorder.pauseLayerScale, 0.01f, 0, FLT_MAX, "Pause Layer Scale: %.2f")) { - if (hacks.pauseLayer) { - hacks.pauseLayer->setAnchorPoint({0.f, 1.f}); - hacks.pauseLayer->setScale(recorder.pauseLayerScale); - } - } - } - - if (recorder.overlay_mode) { - ImGui::PushStyleColor(ImGuiCol_Text, ImColor(255, 128, 128).Value); - ImGui::TextWrapped("Overlay Mode allows you to capture all overlays (like steam or reshade)"); - ImGui::TextWrapped("In this mode, you can only record in full screen mode (native screen resolution)"); - ImGui::TextWrapped("- Don't open other overlays like GDH, Steam, Reshade to make a good showcase"); - ImGui::TextWrapped("- Don't pause while recording the showcase, a frame of the pause menu may be visible"); - ImGui::PopStyleColor(); - } - - if (recorder.native_mode) { - if (recorder.overlay_mode) ImGui::NewLine(); - ImGui::PushStyleColor(ImGuiCol_Text, ImColor(255, 128, 128).Value); - ImGui::TextWrapped("Native mode adjusts the window size to match the video screen resolution"); - ImGui::TextWrapped("- Some parts of the window may be hidden"); - ImGui::TextWrapped("- Make sure you are able to enter the level to start recording (GD Buttons can be behind the screen)"); - ImGui::TextWrapped("- It lets you record in any resolution with Overlay Mode"); - ImGui::TextWrapped("- Also makes shader triggers match the quality of the recorded video resolution"); - ImGui::PopStyleColor(); - } - - - ImGui::Text("Resolution:"); - ImGui::Separator(); - - ImGui::PushItemWidth(45.f * m_scale); - if (ImGui::InputInt("##width", &recorder.width, 0) && recorder.lock_aspect_ratio) { - float aspect_ratio = 16.0f / 9.0f; - recorder.height = static_cast(recorder.width / aspect_ratio); - } - ImGui::SameLine(0, 5); - - ImGui::Text("x"); - ImGui::SameLine(0, 5); - - ImGui::PushItemWidth(45.f * m_scale); - if (ImGui::InputInt("##height", &recorder.height, 0) && recorder.lock_aspect_ratio) { - float aspect_ratio = 16.0f / 9.0f; - recorder.width = static_cast(recorder.height * aspect_ratio); - } - ImGui::SameLine(0, 5); - - ImGui::Text("@"); - ImGui::SameLine(0, 5); - - ImGui::PushItemWidth(35.f * m_scale); - ImGui::InputInt("##fps", &recorder.fps, 0); - - ImGui::SameLine(0, 5); - - ImGuiH::Checkbox("Lock Aspect Ratio (16:9)", &recorder.lock_aspect_ratio, m_scale); - - ImGui::Spacing(); - - ImGui::Text("Encoding Settings"); - ImGui::Separator(); - - if (!use_flvc) { - ImGui::PushItemWidth(50.f * m_scale); - ImGui::InputText("Bitrate", &recorder.bitrate); - - ImGui::SameLine(); - - ImGui::PushItemWidth(80.f * m_scale); - ImGui::InputText("Codec", &recorder.codec); - - ImGui::PushItemWidth(300 * m_scale); - ImGui::InputText("Extra Arguments", &recorder.extra_args); - - ImGui::PushItemWidth(300 * m_scale); - ImGui::InputText("VF Args", &recorder.vf_args); - - if (ImGuiH::Checkbox("vflip", &recorder.vflip, m_scale)) { - recorder.compile_vf_args(); - } - - if (ImGuiH::Checkbox("Fade in", &recorder.fade_in, m_scale)) { - recorder.compile_vf_args(); - } - - ImGui::SameLine(); - - ImGui::PushItemWidth(120 * m_scale); - if (ImGui::DragFloat("##fade_in_start", &recorder.fade_in_start, 0.01f, 0, FLT_MAX, "Start: %.2fs")) { - recorder.compile_vf_args(); - } - - ImGui::SameLine(); - - ImGui::PushItemWidth(120 * m_scale); - if (ImGui::DragFloat("##fade_in_end", &recorder.fade_in_end, 0.01f, 0, FLT_MAX, "End: %.2fs")) { - recorder.compile_vf_args(); - } - } - - ImGuiH::Checkbox("Fade out", &recorder.fade_out, m_scale); - ImGui::Text("Note: The length of the fade-out is calculated based on the value of \"Second to Render After\""); - - ImGuiH::Checkbox("Hide Level Complete", &recorder.hide_level_complete, m_scale); - - ImGui::Spacing(); - - ImGui::Text("Level Settings"); - ImGui::Separator(); - - ImGui::PushItemWidth(200 * m_scale); - - ImGui::InputFloat("Second to Render After", &recorder.after_end_duration, 1); - - ImGui::Spacing(); - - ImGui::Text("Presets (Thanks WarGack, ElPaan, midixd)"); - ImGui::Separator(); - - - if (ImGuiH::Button("Save")) { - nlohmann::json j; - j["width"] = recorder.width; - j["height"] = recorder.height; - j["fps"] = recorder.fps; - j["bitrate"] = recorder.bitrate; - j["codec"] = recorder.codec; - j["extra_args"] = recorder.extra_args; - j["vf_args"] = recorder.vf_args; - - std::ofstream file(folderPath / "recorder_settings.json"); - if (file.is_open()) { - file << j.dump(4); - file.close(); - - ImGuiH::Popup::get().add_popup("Configuration saved"); - } else { - ImGuiH::Popup::get().add_popup("Could not open file for writing"); - } - } - - ImGui::SameLine(); - - if (ImGuiH::Button("Load")) { - std::ifstream file(folderPath / "recorder_settings.json"); - if (file.is_open()) { - std::string file_contents((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - - nlohmann::json j = nlohmann::json::parse(file_contents, nullptr, false); - if (j.is_discarded()) { - ImGuiH::Popup::get().add_popup("JSON parsing error or invalid file"); - } else { - recorder.width = j.value("width", recorder.width); - recorder.height = j.value("height", recorder.height); - recorder.fps = j.value("fps", recorder.fps); - recorder.bitrate = j.value("bitrate", recorder.bitrate); - recorder.codec = j.value("codec", recorder.codec); - recorder.extra_args = j.value("extra_args", recorder.extra_args); - recorder.vf_args = j.value("vf_args", recorder.vf_args); - - ImGuiH::Popup::get().add_popup("Configuration loaded"); - } - file.close(); - } else { - ImGuiH::Popup::get().add_popup("Could not open file for reading"); - } - } - - if (ImGuiH::Button("HD")) - { - recorder.width = 1280; - recorder.height = 720; - recorder.fps = 60; - recorder.bitrate = "25M"; - } - - ImGui::SameLine(); - - if (ImGuiH::Button("FULL HD")) - { - recorder.width = 1920; - recorder.height = 1080; - recorder.fps = 60; - recorder.bitrate = "50M"; - } - - ImGui::SameLine(); - - if (ImGuiH::Button("2K")) - { - recorder.width = 2560; - recorder.height = 1440; - recorder.fps = 60; - recorder.bitrate = "70M"; - } - - ImGui::SameLine(); - - if (ImGuiH::Button("4K")) - { - recorder.width = 3840; - recorder.height = 2160; - recorder.fps = 60; - recorder.bitrate = "80M"; - } - - ImGui::SameLine(); - - if (ImGuiH::Button("8K")) - { - recorder.width = 7680; - recorder.height = 4320; - recorder.fps = 60; - recorder.bitrate = "250M"; - } - - if (!use_flvc) { - if (ImGuiH::Button("CPU x264")) - { - recorder.codec = "libx264"; - recorder.extra_args = "-pix_fmt yuv420p -preset ultrafast"; - } - - ImGui::SameLine(); - - if (ImGuiH::Button("CPU x265")) - { - recorder.codec = "libx265"; - recorder.extra_args = "-pix_fmt yuv420p -preset ultrafast"; - } - ImGui::SameLine(); - if (ImGuiH::Button("CPU AV1 Lossless")) - { - recorder.codec = "libsvtav1"; - recorder.extra_args = "-crf 0 -pix_fmt yuv420p"; - } - if (ImGuiH::Button("NVIDIA H264")) - { - recorder.codec = "h264_nvenc"; - recorder.extra_args = "-pix_fmt yuv420p -preset p7"; - } - ImGui::SameLine(); - if (ImGuiH::Button("NVIDIA H265")) - { - recorder.codec = "hevc_nvenc"; - recorder.extra_args = "-pix_fmt yuv420p -preset p7"; - } - ImGui::SameLine(); - - if (ImGuiH::Button("NVIDIA AV1")) - { - recorder.codec = "av1_nvenc"; - recorder.extra_args = "-pix_fmt yuv420p -preset p7"; - } - - if (ImGuiH::Button("AMD H264 Lossless")) - { - recorder.codec = "h264_amf"; - recorder.extra_args = "-pix_fmt yuv420p -rc cqp -qp_i 0 -qp_p 0 -qp_b 0"; - } - ImGui::SameLine(); - if (ImGuiH::Button("AMD H265 Lossless")) - { - recorder.codec = "hevc_amf"; - recorder.extra_args = "-pix_fmt yuv420p -rc cqp -qp_i 0 -qp_p 0 -qp_b 0"; - } - if (ImGuiH::Button("Color Fix")) - { - recorder.compile_vf_args(); - if (!recorder.vf_args.empty()) - recorder.vf_args += ","; - recorder.vf_args += "colorspace=all=bt709:iall=bt470bg:fast=1"; - } - } - - ImGui::Spacing(); - - ImGui::Text("Folders"); - ImGui::Separator(); - - if (ImGuiH::Button("Open Showcase Folder")) { - geode::utils::file::openFolder(recorder.folderShowcasesPath); - } - - ImGui::SameLine(); - - if (ImGuiH::Button("Change folder")) { - auto result = geode::utils::file::pick(geode::utils::file::PickMode::OpenFolder, {std::nullopt, {}}); - if (result.isFinished() && !result.getFinishedValue()->isErr()) { - recorder.folderShowcasesPath = result.getFinishedValue()->unwrap(); - config.set("showcases_path", recorder.folderShowcasesPath); - } - } - - ImGui::SameLine(); - - if (ImGuiH::Button("Reset Folder")) { - recorder.folderShowcasesPath = folderPath / "Showcases"; - config.set("showcases_path", recorder.folderShowcasesPath); - } - - - ImGui::Text("Showcase Folder: %s", recorder.folderShowcasesPath.string().c_str()); - } - else { - ImGui::Text("Looks like FFmpeg is not installed, here are the instructions:"); - ImGui::Text("1. Download ffmpeg (archive)"); - ImGui::SameLine(); - if (ImGuiH::Button("Download")) { - ShellExecuteA(0, "open", "https://github.com/AnimMouse/ffmpeg-autobuild/releases/latest", 0, 0, SW_SHOWNORMAL); - } - ImGui::Text("2. Extract from archive \"ffmpeg.exe\" file to Geometry Dash folder"); - ImGui::Text("3. Restart Geometry Dash"); - if (ImGuiH::Button("Bypass")) recorder.ffmpeg_installed = true; - } - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem("Audio")) { - if (ImGuiH::Checkbox("Record Buffer", &recorderAudio.enabled, m_scale)) { - if (containsRussianLetters(recorder.folderShowcasesPath)) { - recorder.enabled = false; - ImGuiH::Popup::get().add_popup("Invalid path to the showcase folder. Please remove any Cyrillic characters"); - } - else { - if (recorderAudio.enabled) { - if (!recorderAudio.showcase_mode) { - recorderAudio.start(); - ImGuiH::Popup::get().add_popup("Audio recording started!"); - } - else { - ImGuiH::Popup::get().add_popup("Rendering begins when you re-enter the level (don't forget to set up the macro playback!!)"); - recorderAudio.first_start = true; - } - } - else { - recorderAudio.stop(); - ImGuiH::Popup::get().add_popup("Audio recording stopped!"); - } - } - } - - ImGuiH::Checkbox("Showcase Mode", &recorderAudio.showcase_mode, m_scale); - - ImGui::Spacing(); - - ImGui::Text("Level Settings"); - ImGui::Separator(); - - ImGui::PushItemWidth(120 * m_scale); - ImGui::SliderFloat("##MusicRecorder", &recorderAudio.volume_music, 0.f, 1.f, "Music: %.2f"); - - ImGui::SameLine(); - - ImGui::PushItemWidth(120 * m_scale); - - ImGui::SliderFloat("##SFXRecorder", &recorderAudio.volume_sfx, 0.f, 1.f, "SFX: %.2f"); - - if (recorderAudio.showcase_mode) { - ImGui::PushItemWidth(200 * m_scale); - ImGui::InputFloat("Second to Render After##2", &recorderAudio.after_end_duration, 1); - } - - ImGui::Spacing(); - - ImGui::Text("Filename"); - ImGui::Separator(); - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputText("##audio_filename", &recorderAudio.audio_name); - - ImGui::Spacing(); - - ImGui::Text("Folders"); - ImGui::Separator(); - - if (ImGuiH::Button("Open Showcase Folder")) { - geode::utils::file::openFolder(recorder.folderShowcasesPath); - } - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem("Merge (Video + Audio)")) { - static bool shortest = true; - static std::vector videos; - static std::vector audios; - static int index_videos = 0; - static int index_audios = 0; - static std::string args = "-c:v copy -c:a libfdk_aac -vbr 5 -shortest"; - - auto compile = [&]() - { - args = "-c:v copy -c:a libfdk_aac -vbr 5"; - if (shortest) { - args += " -shortest"; - } - }; - - ImGui::BeginChild("##VideoSelect", {NULL, 190 * m_scale}, true); - for (size_t i = 0; i < videos.size(); i++) { - bool is_selected = (index_videos == i); - if (ImGui::Selectable(videos[i].filename().string().c_str(), is_selected)) { - index_videos = i; - } - } - ImGui::EndChild(); - - ImGui::BeginChild("##AudioSelect", {NULL, 190 * m_scale}, true); - for (size_t i = 0; i < audios.size(); i++) { - bool is_selected = (index_audios == i); - if (ImGui::Selectable(audios[i].filename().string().c_str(), is_selected)) { - index_audios = i; - } - } - ImGui::EndChild(); - - if (ImGuiH::Button("Refresh", {ImGui::GetContentRegionAvail().x, NULL})) { - videos.clear(); - audios.clear(); - for (const auto &entry : std::filesystem::directory_iterator(recorder.folderShowcasesPath)) { - if (entry.path().extension() == ".mp4" || - entry.path().extension() == ".mkv" || - entry.path().extension() == ".avi" || - entry.path().extension() == ".mov" || - entry.path().extension() == ".flv" || - entry.path().extension() == ".wmv" || - entry.path().extension() == ".webm" || - entry.path().extension() == ".m4v" || - entry.path().extension() == ".mpeg") { - - videos.push_back(entry.path().string()); - } - - if (entry.path().extension() == ".wav" || - entry.path().extension() == ".mp3" || - entry.path().extension() == ".m4a") { - audios.push_back(entry.path().string()); - } - } - } - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputText("##merge_command", &args); - - if (ImGuiH::Checkbox("Shortest", &shortest, m_scale)) - compile(); - - ImGui::SameLine(); - - if (ImGuiH::Button("Merge", {ImGui::GetContentRegionAvail().x, NULL})) { - if (videos.empty() || audios.empty()) { - - } else if (index_videos >= 0 && index_videos < (int)videos.size() && index_audios >= 0 && index_audios < (int)audios.size()) { - std::string command = "ffmpeg.exe -y -i \"" + videos[index_videos].string() + "\" -i \"" + audios[index_audios].string() + "\" " + args + " "; - std::filesystem::path video_rel(videos[index_videos]); - command += fmt::format("\"{}\\audio_{}\"", recorder.folderShowcasesPath, video_rel.filename().string()); - geode::log::debug("{}", command); - auto process = subprocess::Popen(command); - } - } - ImGui::EndTabItem(); - } - - ImGui::EndTabBar(); - ImGui::EndPopup(); - } - - - if (ImGui::MenuItem("More Settings")) { - recorder.settings_openned = true; - recorder.ffmpeg_installed = std::filesystem::exists("ffmpeg.exe"); - ImGui::OpenPopup("Replay Engine More Settings"); - } - #endif - } - } - else if (windowName == "Labels") { - auto &labels = Labels::get(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x/2); - if (ImGui::DragFloat("##Label Opacity", &labels.opacity, 0.01f, 0.f, 1.f, "Opacity: %.2f")) config.set("label-opacity", labels.opacity); - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::DragFloat("##Label Size", &labels.size, 0.01f, 0.f, 1.f, "Size: %.2f")) config.set("label-size", labels.size); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::DragFloat("##Label Padding", &labels.padding, 1.f, 0.f, 50.f, "Label Padding: %.1fpx")) config.set("label-padding", labels.padding); - - const char *labels_positions[] = {"Top Left", "Top Right", "Top", "Bottom Left", "Bottom Right", "Bottom"}; - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGuiH::Combo("##labelspos", &selected_label_corner, labels_positions, 6, 6); - - const char *label_types[] = { - "Time (24H)", - "Time (12H)", - "Session Time", - "Cheat Indicator", - "FPS Counter", - "Level Progress", - "Attempt", - "CPS Counter", - "Level Info", - "Noclip Accuracy", - "Death Counter", - "Startpos Switcher", - "Testmode", - "Replay Engine State", - "CBF Status", - "Rainbow Text", - "Custom Text" - }; - int label_types_count = sizeof(label_types)/sizeof(label_types[0]); - - ImGuiH::Combo("##labeladdtype", &selected_label_type, label_types, label_types_count); - if (selected_label_type == label_types_count - 1) - ImGui::InputText("##labelinput", &selected_label_text); - - ImGui::SameLine(); - if (ImGuiH::Button("+")) { - std::string text; - if (selected_label_type == label_types_count - 1) text = selected_label_text; - else if (selected_label_type == 0) text = "{time:24}"; - else if (selected_label_type == 1) text = "{time:12}"; - else if (selected_label_type == 2) text = "Session Time: {sessionTime}"; - else if (selected_label_type == 3) { - text = "{cheat_indicator}"; - config.set("cheat_indicator", true); - } - else if (selected_label_type == 4) text = "{fps} FPS"; - else if (selected_label_type == 5) text = "{progress:2f}"; - else if (selected_label_type == 6) text = "Attempt {attempt}"; - else if (selected_label_type == 7) text = "ColoredCPS({cps}/{cpsHigh}/{clicks})"; - else if (selected_label_type == 8) text = "{levelName}{byLevelCreator} ({levelId})"; - else if (selected_label_type == 9) text = "ColoredDeath({noclipAccuracy})"; - else if (selected_label_type == 10) text = "ColoredDeath({deaths} Deaths)"; - else if (selected_label_type == 11) text = "showIfStartposesExist({startposCurrentIDX}/{startposAllIDX})"; - else if (selected_label_type == 12) text = "{testmode}"; - else if (selected_label_type == 13) text = "{re_state}"; - else if (selected_label_type == 14) text = "CBF: {cbf_enabled}"; - else if (selected_label_type == 15) text = "Rainbow text!!"; - - Label l((LabelCorner) (selected_label_corner+1), text); - labels.add(l); - } - - ImGui::Separator(); - ImGui::BeginChild("Labels"); - ImGui::Spacing(); - - if (!std::any_of(labels.labels.begin(), labels.labels.end(), - [&](const Label& label) { return static_cast(label.corner - 1) == selected_label_corner; })) { - ImGui::TextDisabled("No labels in this corner"); - } - - for (size_t index = 0; index < labels.labels.size(); index++) { - Label& item = labels.labels[index]; - - if (static_cast(item.corner-1) != selected_label_corner) continue; - - ImGui::PushID(index); - - ImGui::Selectable(" =", false, 0, {20.f * m_scale, 20.f * m_scale}); - - if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) { - ImGui::SetTooltip("Double click to remove label"); - } - - ImGui::SameLine(); - - if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2 && ImGui::IsItemHovered()) { - labels.remove(index); - } - - if (ImGui::BeginDragDropSource()) { - ImGui::TextUnformatted(item.format_text.c_str()); - ImGui::SetDragDropPayload("LBLMOVE", &index, sizeof(size_t)); - ImGui::EndDragDropSource(); - } - - if (ImGui::BeginDragDropTarget()) { - if (const ImGuiPayload *payload = ImGui::AcceptDragDropPayload("LBLMOVE")) { - size_t move_from = *(size_t*)payload->Data; - labels.swap(move_from, index); - } - ImGui::EndDragDropTarget(); - } - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputText("##inptext", &item.format_text); - - ImGui::PopID(); - } - ImGui::EndChild(); - } - else if (windowName == "Shortcuts") { - if (ImGuiH::Button("Options", {ImGui::GetContentRegionAvail().x, NULL})) { - auto options_layer = OptionsLayer::create(); - auto scene = cocos2d::CCScene::get(); - - if (options_layer && scene) { - auto zOrder = scene->getHighestChildZ(); - scene->addChild(options_layer, zOrder + 1); - options_layer->showLayer(false); - } - } - - if (ImGuiH::Button("Reset Level", {ImGui::GetContentRegionAvail().x, NULL})) { - auto pl = PlayLayer::get(); - if (pl) pl->resetLevel(); - } - - if (ImGuiH::Button("Practice Mode", {ImGui::GetContentRegionAvail().x, NULL})) { - auto pl = PlayLayer::get(); - if (pl) pl->togglePracticeMode(!pl->m_isPracticeMode); - } - - if (ImGuiH::Button("Reset Volume", {ImGui::GetContentRegionAvail().x, NULL})) { - auto fmod_engine = FMODAudioEngine::sharedEngine(); - fmod_engine->setBackgroundMusicVolume(0.5f); - fmod_engine->setEffectsVolume(0.5f); - } - - if (ImGuiH::Button("Uncomplete Level", {ImGui::GetContentRegionAvail().x, NULL})) { - utilsH::UncompleteLevel(); - } - - if (ImGuiH::Button("Inject DLL", {ImGui::GetContentRegionAvail().x, NULL})) { - #ifdef GEODE_IS_WINDOWS - geode::utils::file::FilePickOptions::Filter filter = { - .description = "Dynamic Link Library", - .files = { "*.dll"} - }; - - auto result = geode::utils::file::pick(geode::utils::file::PickMode::OpenFile, {std::nullopt, {filter}}); - if (result.isFinished() && !result.getFinishedValue()->isErr()) { - std::filesystem::path path = result.getFinishedValue()->unwrap(); - HMODULE hModule = LoadLibraryW(path.wstring().c_str()); - if (hModule) { - ImGuiH::Popup::get().add_popup("Injected successfully"); - } - else { - ImGuiH::Popup::get().add_popup("Failed to inject"); - } - } - #endif - } - - if (ImGuiH::Button("Resources", {ImGui::GetContentRegionAvail().x/2, NULL})) { - std::string path = cocos2d::CCFileUtils::get()->getWritablePath2(); - geode::utils::file::openFolder(path); - } - ImGui::SameLine(); - if (ImGuiH::Button("AppData", {ImGui::GetContentRegionAvail().x, NULL})) { - auto path = geode::dirs::getSaveDir(); - geode::utils::file::openFolder(path); - } - - if (ImGuiH::Button("GDH AppData", {ImGui::GetContentRegionAvail().x, NULL})) { - geode::utils::file::openFolder(folderPath); - } - } - else { - for (auto& hck : win.hacks) { - bool enabled = config.get(hck.config, false); - - std::string search_name = hck.name; - std::string search_item = search_text; - - std::transform(search_item.begin(), search_item.end(), search_item.begin(), ::tolower); - std::transform(search_name.begin(), search_name.end(), search_name.begin(), ::tolower); - - bool founded = search_item.empty() ? true : (search_name.find(search_item) != std::string::npos); - ImGui::PushStyleColor(ImGuiCol_Text, founded ? ImGui::GetStyle().Colors[ImGuiCol_Text] : ImColor(64, 64, 64).Value); - - - if (m_keybindMode) { - #ifdef GEODE_IS_WINDOWS - if (hck.keybind == -1 && !m_waitingForBindKey && m_keyToSet != -1) { - hck.keybind = m_keyToSet; - m_keyToSet = 0; - } - - if (ImGuiH::Button(hck.keybind != -1 - ? fmt::format("{}: {}", - hck.name.length() > 16 ? hck.name.substr(0, 16) : hck.name, - KeyMappingUtils::GetNameFromGLFW(hck.keybind)).c_str() - : "Press any key...", {ImGui::GetContentRegionAvail().x, NULL})) - { - hck.keybind = -1; - m_waitingForBindKey = true; - } - #endif - } - else { - if (ImGuiH::Checkbox(hck.name.c_str(), &enabled, m_scale)) { - config.set(hck.config, enabled); - if (!hck.game_var.empty()) - GameManager::get()->setGameVariable(hck.game_var.c_str(), enabled); - if (hck.handlerFunc) hck.handlerFunc(enabled); - } - - if (ImGui::IsItemHovered() && !hck.desc.empty()) { - ImGui::SetTooltip("%s", hck.desc.c_str()); - } - - if (hck.handlerCustomWindow) { - ImGui::SameLine(); - if (ImGuiH::ArrowButton(fmt::format("{} Settings", hck.name).c_str(), ImGuiDir_Right)) { - ImGui::OpenPopup(fmt::format("{} Settings", hck.name).c_str()); - } - } - } - - if (hck.handlerCustomWindow) { - if (ImGui::BeginPopupModal(fmt::format("{} Settings", hck.name).c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize)) { - hck.handlerCustomWindow(); - - if (ImGuiH::Button("Close", {400 * m_scale, NULL})) - ImGui::CloseCurrentPopup(); - - ImGui::EndPopup(); - } - } - - ImGui::PopStyleColor(); - } - } - - ImGui::PopFont(); - ImGui::End(); - } -} - -void Gui::Init() { - auto &config = Config::get(); - auto &recorder = Recorder::get(); - - stretchedWindows.clear(); - ImGuiH::Popup::get().messages.clear(); - - m_scale = config.get("gui_scale", 1.f); - m_index_scale = config.get("gui_index_scale", 7); - - m_license_inited = false; - first_time_re_settings = true; - recorder.settings_openned = false; - - bool inverted = config.get("gui_inverted", false); - ApplyGuiColors(inverted); - ApplyColor(themes[config.get("gui_color_index", 0)]); - ApplyStyle(m_scale); - ImGuiIO &io = ImGui::GetIO(); - io.Inverted = inverted; - io.IniFilename = NULL; - io.Fonts->Clear(); - io.Fonts->AddFontFromMemoryCompressedTTF(roboto_font_data, roboto_font_size, 18.f * m_scale, nullptr, io.Fonts->GetGlyphRangesCyrillic()); - io.Fonts->AddFontFromMemoryCompressedTTF(roboto_font_data, roboto_font_size, 32.f * m_scale, nullptr, io.Fonts->GetGlyphRangesCyrillic()); - io.Fonts->AddFontFromMemoryCompressedTTF(roboto_font_data, roboto_font_size, 20.f * m_scale, nullptr, io.Fonts->GetGlyphRangesCyrillic()); - io.Fonts->Build(); -} - -void Gui::Toggle() { - if (!isAnimating) - { - isAnimating = true; - isFadingIn = !isFadingIn; - animationStartTime = std::chrono::steady_clock::now(); - - if (isFadingIn) - { - m_show = !m_show; - updateCursorState(); - } - } -} - - -void Gui::renderKeyButton(const std::string& label, int& key, bool withoutKeybindsMode) { - #ifdef GEODE_IS_WINDOWS - std::string keyStr = label + KeyMappingUtils::GetNameFromGLFW(key); - if (key == -1) { - keyStr = "Press any key..."; - if (!m_waitingForBindKey && m_keyToSet != -1) { - key = m_keyToSet; - m_keyToSet = 0; - if (withoutKeybindsMode) m_keybindMode = false; - } - } - - if (ImGuiH::Button(keyStr.c_str(), {ImGui::GetContentRegionAvail().x, 0})) { - key = -1; - if (withoutKeybindsMode) m_keybindMode = true; - m_waitingForBindKey = true; - } - #endif -}; +#include "gui.hpp" +#ifdef GEODE_IS_WINDOWS +#include +#include +#include +#endif + +#include +#include +#include "config.hpp" +#include "json.hpp" +#include "hacks.hpp" +#include "memory.hpp" +#include "labels.hpp" +#include "replayEngine.hpp" +#include "recorder.hpp" +#include "utils.hpp" +#ifdef GEODE_IS_WINDOWS +#include +#include "keyMapping.hpp" +#endif + +std::chrono::steady_clock::time_point animationStartTime; +bool isAnimating = false; +bool isFadingIn = false; + +std::string search_text; + +int selected_label_corner = 0; +int selected_label_type = 0; +std::string selected_label_text; + +void Gui::License() { + auto& config = Config::get(); + if (!m_license_inited) { + ImGui::SetNextWindowSize({590 * m_scale, 390 * m_scale}); + ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + m_license_inited = true; + } + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {20 * m_scale, 20 * m_scale}); + ImGui::Begin("Welcome", 0, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_Button]); + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]); + ImGui::Text("New era of GDH :)"); + ImGui::PopFont(); + ImGui::PopStyleColor(); + + ImGui::Text("GDH is an open-source mod menu under the MIT license"); + + ImGui::PushStyleColor(ImGuiCol_Text, ImColor(255, 128, 128).Value); + ImGui::TextWrapped("Note: GDH is currently in beta testing, so some elements may be unstable/unfinished\n\nIn case of any issues/crashes, please report the problem in the GDH issues section on GitHub"); + ImGui::PopStyleColor(); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {10 * m_scale, 10 * m_scale}); + ImGui::BeginChild("##LicenseChild", {0, ImGui::GetContentRegionAvail().y - 40 * m_scale}, true); + ImGui::Text("Customize a comfortable size for GDH:"); + + const char* items[] = {"25%", "50%", "75%", "80%", "85%", "90%", "95%", "100%", "125%", "150%", "175%", "200%", "250%", "300%", "400%"}; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGuiH::Combo("##Menu scale", &m_index_scale, items, IM_ARRAYSIZE(items))) { + m_scale = float(atof(items[m_index_scale])) / 100.0f; + config.set("gui_scale", m_scale); + config.set("gui_index_scale", m_index_scale); + m_needRescale = true; + ImGuiCocos::get().reload(); + } + + ImGui::Text("Pick your favorite accent color:"); + + int alpha = int(ImGui::GetStyle().Alpha * 255); + for (int i = 0; i < themes.size(); ++i) { + const auto& theme = themes[i]; + ImColor button_color(theme.color_bg.r, theme.color_bg.g, theme.color_bg.b, alpha); + + if (i == 0) + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.f * m_scale); + + if (ImGuiH::CircularButton(fmt::format("COLOR_{}", i).c_str(), 10.f * m_scale, button_color)) { + config.set("gui_color_index", i); + ApplyColor(theme); + } + ImGui::SameLine(); + } + + bool inverted = config.get("gui_inverted", false); + ImColor inverted_button_color = inverted ? ImColor(255, 255, 255) : ImColor(27, 27, 29, alpha); + ImColor inverted_button_hover_color = ImColor(64, 64, 64, alpha); + if (ImGuiH::CircularButton("TOGGLE_INVERT", 10.f * m_scale, inverted_button_color, true, inverted_button_hover_color)) { + config.set("gui_inverted", !inverted); + ImGui::GetIO().Inverted = !inverted; + ApplyGuiColors(!inverted); + } + + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Invert theme (beta)"); + + ImGui::Text("Menu Key:"); + + #ifdef GEODE_IS_WINDOWS + + renderKeyButton("Menu Key: ", m_toggleKey, true); + + #endif + + ImGui::EndChild(); + ImGui::PopStyleVar(); + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + if (ImGuiH::Button("Processed", {ImGui::GetContentRegionAvail().x, 30 * m_scale})) { + config.set("license_accepted", true); + } + ImGui::End(); + ImGui::PopStyleVar(); +} + +void Gui::updateCursorState() { + bool canShowInLevel = true; + if (auto* playLayer = PlayLayer::get()) { + canShowInLevel = playLayer->m_hasCompletedLevel || + playLayer->m_isPaused || + GameManager::sharedState()->getGameVariable("0024"); + } + if (m_show || canShowInLevel) + PlatformToolbox::showCursor(); + else + PlatformToolbox::hideCursor(); +} + +void Gui::animateAlpha() +{ + ImGuiStyle& style = ImGui::GetStyle(); + + auto currentTime = std::chrono::steady_clock::now(); + std::chrono::duration diff = currentTime - animationStartTime; + float elapsed = diff.count(); + + float time = Config::get().get("gui_anim_durr", 100) / 1000.0f; + if (elapsed >= time) + { + style.Alpha = isFadingIn ? 1.0f : 0.0f; + isAnimating = false; + + if (!isFadingIn) + { + m_show = !m_show; + Config::get().save(fileDataPath); + Labels::get().save(); + RGBIcons::get().save(); + Hacks::get().saveKeybinds(); + updateCursorState(); + search_text = ""; + Recorder::get().settings_openned = false; + } + + return; + } + + float t = elapsed / time; + float alpha = isFadingIn ? 0.0f + t: 1.0f - t; + style.Alpha = alpha; +} + +std::vector stretchedWindows; +void Gui::Render() { + + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1); + ImGuiH::Popup::get().render(); + + ImGui::PopStyleVar(); + + if (isAnimating) { + animateAlpha(); + } + + if (!m_show) return; + + auto &config = Config::get(); + if (!config.get("license_accepted", false)) { + License(); + return; + } + + auto& hacks = Hacks::get(); + for (auto& win : hacks.m_windows) { + std::string windowName = win.name; + if (std::find(stretchedWindows.begin(), stretchedWindows.end(), windowName) == stretchedWindows.end()) + { + ImVec2 windowSize = ImVec2(win.w, win.h); + ImVec2 windowPos = ImVec2(win.x, win.y); + + if (m_needRescale) { + windowSize = ImVec2(win.orig_w * m_scale, win.orig_h * m_scale); + windowPos = ImVec2(win.orig_x * m_scale, win.orig_y * m_scale); + } + + ImGui::SetNextWindowSize(windowSize); + ImGui::SetNextWindowPos(windowPos); + + stretchedWindows.push_back(windowName); + } + + auto& engine = ReplayEngine::get(); + bool re_needRecolor = (windowName == "Replay Engine") && (engine.mode != state::disable); + if (re_needRecolor) { + ImGui::PushStyleColor(ImGuiCol_Text, ImColor(0, 0, 0).Value); + if (engine.mode == state::record) { + ImGui::PushStyleColor(ImGuiCol_TitleBg, ImColor(255, 128, 128).Value); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImColor(255, 128, 128).Value); + ImGui::PushStyleColor(ImGuiCol_TitleBgCollapsed, ImColor(255, 128, 128).Value); + } + else { + ImGui::PushStyleColor(ImGuiCol_TitleBg, ImColor(34, 177, 76).Value); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImColor(34, 177, 76).Value); + ImGui::PushStyleColor(ImGuiCol_TitleBgCollapsed, ImColor(34, 177, 76).Value); + } + } + + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); + ImGui::Begin(windowName.c_str(), 0, ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoNavInputs); + ImGui::PopFont(); + + if (re_needRecolor) ImGui::PopStyleColor(4); + + if (!ImGui::IsWindowCollapsed()) { + auto size = ImGui::GetWindowSize(); + auto pos = ImGui::GetWindowPos(); + + win.w = size.x; + win.h = size.y; + win.x = pos.x; + win.y = pos.y; + } + + + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); + + + if (windowName == "GDH Settings") { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputTextWithHint("##Search", "Search:", &search_text); + + if (ImGui::GetIO().MouseWheel != 0 && ImGui::IsItemActive()) + ImGui::SetWindowFocus(NULL); + + const char* items[] = {"25%", "50%", "75%", "80%", "85%", "90%", "95%", "100%", "125%", "150%", "175%", "200%", "250%", "300%", "400%"}; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGuiH::Combo("##Menu scale", &m_index_scale, items, IM_ARRAYSIZE(items))) { + m_scale = float(atof(items[m_index_scale])) / 100.0f; + config.set("gui_scale", m_scale); + config.set("gui_index_scale", m_index_scale); + m_needRescale = true; + ImGuiCocos::get().reload(); + } + + int alpha = int(ImGui::GetStyle().Alpha * 255); + + for (int i = 0; i < themes.size(); ++i) { + const auto& theme = themes[i]; + ImColor button_color(theme.color_bg.r, theme.color_bg.g, theme.color_bg.b, alpha); + + if (i == 0) + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.f * m_scale); + + if (ImGuiH::CircularButton(fmt::format("COLOR_{}", i).c_str(), 10.f * m_scale, button_color)) { + config.set("gui_color_index", i); + ApplyColor(theme); + } + + ImGui::SameLine(); + } + + bool inverted = config.get("gui_inverted", false); + ImColor inverted_button_color = inverted ? ImColor(255, 255, 255) : ImColor(27, 27, 29, alpha); + ImColor inverted_button_hover_color = ImColor(64, 64, 64, alpha); + + if (ImGuiH::CircularButton("TOGGLE_INVERT", 10.f * m_scale, inverted_button_color, true, inverted_button_hover_color)) { + config.set("gui_inverted", !inverted); + ImGui::GetIO().Inverted = !inverted; + ApplyGuiColors(!inverted); + } + + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Invert theme (beta)"); + + #ifdef GEODE_IS_WINDOWS + + if (ImGui::BeginPopupModal("Keybinds Settings", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::BeginChild("##Keybinds", {420.f * m_scale, 400.f * m_scale}); + + bool useKeybindsOnlyInGame = config.get("use_keybinds_only_in_game", true); + if (ImGuiH::Checkbox("Use keybinds only in game", &useKeybindsOnlyInGame, m_scale)) { + config.set("use_keybinds_only_in_game", useKeybindsOnlyInGame); + } + + ImGui::Text("UI"); + ImGui::Separator(); + ImGui::Spacing(); + + renderKeyButton("Menu Key: ", m_toggleKey); + + ImGui::Text("Framerate"); + ImGui::Separator(); + ImGui::Spacing(); + + renderKeyButton("Speedhack Key: ", m_speedhackKey); + renderKeyButton("TPS Bypass Key: ", m_tpsKey); + + ImGui::Text("Replay Engine"); + ImGui::Separator(); + ImGui::Spacing(); + + renderKeyButton("Disable/Playback Macro: ", m_playbackKey); + renderKeyButton("Save Macro by Current Level Name: ", m_saveMacroByCurrentNameKey); + renderKeyButton("Load Macro by Current Level Name: ", m_loadMacroByCurrentNameKey); + renderKeyButton("Enable Frame Advance + Next Frame: ", m_frameAdvanceEnableKey); + renderKeyButton("Disable Frame Advance: ", m_frameAdvanceDisableKey); + + ImGui::Text("Shortcuts"); + ImGui::Separator(); + ImGui::Spacing(); + + renderKeyButton("Options: ", m_optionsKey); + renderKeyButton("Reset Level: ", m_resetLevelKey); + renderKeyButton("Practice Mode: ", m_practiceModeKey); + renderKeyButton("Reset Volume: ", m_resetVolumeKey); + renderKeyButton("Uncomplete Level: ", m_uncompleteLevelKey); + + ImGui::Text("Startpos Switcher"); + ImGui::Separator(); + ImGui::Spacing(); + + renderKeyButton("Startpos Switcher Left: ", m_startposSwitcherLeftKey); + renderKeyButton("Startpos Switcher Right: ", m_startposSwitcherRightKey); + + ImGui::EndChild(); + + ImGui::Separator(); + + ImGui::Text("Tip: To disable the bind, bind it to backspace"); + + if (ImGuiH::Button("Close", {420 * m_scale, NULL})) + ImGui::CloseCurrentPopup(); + + ImGui::EndPopup(); + } + + ImGuiH::Checkbox("Keybinds Mode", &m_keybindMode, m_scale); + + if (m_keybindMode && ImGui::IsItemHovered()) + ImGui::SetTooltip("Binds for toggle UI, Speedhack, Replay Engine, and more in the More Keybinds"); + + #endif + + if (ImGuiH::Button(m_keybindMode ? "More Keybinds" : "More Settings", {ImGui::GetContentRegionAvail().x, 0})) { + if (!m_keybindMode) + ImGui::OpenPopup("GDH More Settings"); + else + ImGui::OpenPopup("Keybinds Settings"); + } + + if (ImGui::BeginPopupModal("GDH More Settings", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { + int anim_durr = config.get("gui_anim_durr", 100); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::DragInt("##gui_anim_durr", &anim_durr, 1, 0, 10000, "Transition Time: %ims")) { + config.set("gui_anim_durr", anim_durr); + } + + if (ImGuiH::Button("Sort Windows", {ImGui::GetContentRegionAvail().x, NULL})) + stretchedWindows.clear(); + + if (ImGuiH::Button("Close", {400 * m_scale, 0})) { + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + #ifdef GEODE_IS_WINDOWS + + static int priorityIndex = 3; + const char* priorityNames[] = {"Real-time", "High", "Above Normal", "Normal", "Below Normal", "Low"}; + DWORD priorityValues[] = {REALTIME_PRIORITY_CLASS, HIGH_PRIORITY_CLASS, ABOVE_NORMAL_PRIORITY_CLASS, + NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, IDLE_PRIORITY_CLASS}; + + ImGui::Text("Process Priority"); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGuiH::Combo("##Process Priority", &priorityIndex, priorityNames, IM_ARRAYSIZE(priorityNames))) { + if (SetPriorityClass(GetCurrentProcess(), priorityValues[priorityIndex])) { + ImGuiH::Popup::get().add_popup(fmt::format("Priority changed to: {}", priorityNames[priorityIndex])); + } else { + ImGuiH::Popup::get().add_popup("Failed to change priority!"); + } + } + + #endif + + } + else if (windowName == "Framerate") { + bool tps_enabled = config.get("tps_enabled", false); + float tps_value = config.get("tps_value", 60.f);; + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - (35 + 5) * m_scale); + if (ImGui::DragFloat("##tps_value", &tps_value, 1, 1, FLT_MAX, "%0.f TPS")) + config.set("tps_value", tps_value); + + ImGui::SameLine(); + if (ImGuiH::Checkbox("##tps_enabled", &tps_enabled, m_scale)) + config.set("tps_enabled", tps_enabled); + + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Multiplies the number of ticks per second, used mainly for botting\n(not recommended for normal use as it ruins the game's performance)"); + + bool speedhack_enabled = config.get("speedhack_enabled", false); + float speedhack_value = config.get("speedhack_value", 1.f); + + bool speedhackAudio_enabled = config.get("speedhackAudio_enabled", false); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - (35 + 5) * m_scale); + if (ImGui::DragFloat("##speedhack_value", &speedhack_value, 0.01f, 0, FLT_MAX, "Speed: %.2fx")) + config.set("speedhack_value", speedhack_value); + + ImGui::SameLine(); + if (ImGuiH::Checkbox("##speedhack_enabled", &speedhack_enabled, m_scale)) + config.set("speedhack_enabled", speedhack_enabled); + + if (ImGuiH::Checkbox("Speedhack Audio", &speedhackAudio_enabled, m_scale)) + config.set("speedhackAudio_enabled", speedhackAudio_enabled); + + ImGui::SameLine(); + + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10.f * m_scale); + if (ImGuiH::ArrowButton("##more_tps_settings", ImGuiDir_Down)) { + ImGui::OpenPopup("More TPS Settings"); + } + + if (ImGui::BeginPopup("More TPS Settings")) { + bool tps_real_time = config.get("tps::real_time", true); + if (ImGuiH::Checkbox("Real Time", &tps_real_time, m_scale)) + config.set("tps::real_time", tps_real_time); + + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Allows real-time update of multiplied ticks (may cause lags)"); + + int resumeTimer = config.get("resumeTimer_value", 2); + if (ImGui::DragInt("##ResumerTimer", &resumeTimer, 1, 2, INT_MAX, "Resume Timer: %d")) + config.set("resumeTimer_value", resumeTimer); + + ImGui::Text("Higher resume timer means a smoother start but a delay (adjust as needed)"); + } + } + else if (windowName == "Variables") { + static int type_index = 0; + static int player_index = 0; + static int creator_index = 0; + static std::string value; + + const char* types[] = {"Creator", "Player"}; + const char* player[] = {"Attempts", "Jumps", "Normal %", "Position X", "Position Y", "Practice %", "Song ID", "Speed", "Level ID"}; + const char* creator[] = {"Object ID"}; + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGuiH::Combo("##TypeVars", &type_index, types, IM_ARRAYSIZE(types)); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (type_index == 0) + ImGuiH::Combo("##CreatorVars", &creator_index, creator, IM_ARRAYSIZE(creator)); + else if (type_index == 1) + ImGuiH::Combo("##PlayerVars", &player_index, player, IM_ARRAYSIZE(player)); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputText("##ValueVar", &value); + + auto handleGetSetCreator = [&](bool isSet) { + auto EditorUI = EditorUI::get(); + if (!EditorUI) return; + + if (isSet) { + EditorUI::get()->m_selectedObjectIndex = std::stoi(value); + } else { + value = std::to_string(EditorUI::get()->m_selectedObjectIndex); + } + }; + + auto handleGetSetPlayer = [&](bool isSet) { + auto pl = PlayLayer::get(); + if (!pl) return; + + auto& level = *pl->m_level; + auto& player1 = *pl->m_player1; + + switch (player_index) { + case 0: // Attempts + if (isSet) level.m_attempts = std::stoi(value); + else value = std::to_string(level.m_attempts); + break; + case 1: // Jumps + if (isSet) level.m_jumps = std::stoi(value); + else value = std::to_string(level.m_jumps); + break; + case 2: // Normal % + if (isSet) level.m_normalPercent = std::stoi(value); + else value = std::to_string(level.m_normalPercent); + break; + case 3: // Position X + if (isSet) player1.m_position.x = std::stof(value); + else value = std::to_string(player1.m_position.x); + break; + case 4: // Position Y + if (isSet) player1.m_position.y = std::stof(value); + else value = std::to_string(player1.m_position.y); + break; + case 5: // Practice % + if (isSet) level.m_practicePercent = std::stoi(value); + else value = std::to_string(level.m_practicePercent); + break; + case 6: // Song ID + if (isSet) level.m_songID = std::stoi(value); + else value = std::to_string(level.m_songID); + break; + case 7: // Speed + if (isSet) player1.m_playerSpeed = std::stof(value); + else value = std::to_string(player1.m_playerSpeed); + break; + case 8: // Level ID + if (isSet) level.m_levelID = std::stoi(value); + else value = std::to_string(level.m_levelID); + break; + } + }; + + if (ImGuiH::Button("Get", {ImGui::GetContentRegionAvail().x / 2, NULL})) { + if (type_index == 0) handleGetSetCreator(false); + else if (type_index == 1) handleGetSetPlayer(false); + } + + ImGui::SameLine(); + + if (ImGuiH::Button("Set", {ImGui::GetContentRegionAvail().x, NULL})) { + if (utilsH::isNumeric(value)) { + if (type_index == 0) handleGetSetCreator(true); + else if (type_index == 1) handleGetSetPlayer(true); + } + } + } + else if (windowName == "Replay Engine") { + static geode::Mod* cbfMod = geode::Loader::get()->getLoadedMod("syzzi.click_between_frames"); + static bool hasCBF = cbfMod != nullptr && cbfMod->isLoaded(); + + if (hasCBF && !cbfMod->getSettingValue("soft-toggle")) { + ImGui::PushStyleColor(ImGuiCol_Text, ImColor(255, 128, 128).Value); + ImGui::TextWrapped("Click Between Frames mod is enabled, turn it off to use Replay Engine"); + ImGui::PopStyleColor(); + } + else { + static std::vector replay_list; + + if (ImGui::BeginPopupModal("Select Replay", 0, ImGuiWindowFlags_NoResize)) { + ImGui::BeginChild("Select Replay##2", {400 * m_scale, 300 * m_scale}); + for (int i = 0; i < (int)replay_list.size(); i++) + { + if (ImGuiH::Button(replay_list[i].filename().string().c_str(), {ImGui::GetContentRegionAvail().x, NULL})) + { + std::string extension = replay_list[i].filename().extension().string(); + if (extension != ".re" && extension != ".re2") + engine.replay_name = replay_list[i].filename().replace_extension().string(); + else + engine.replay_name = replay_list[i].filename().string(); + ImGui::CloseCurrentPopup(); + } + } + ImGui::EndChild(); + + if (ImGuiH::Button("Open Folder", {ImGui::GetContentRegionAvail().x, NULL})) { + geode::utils::file::openFolder(folderMacroPath); + } + + if (ImGuiH::Button("Close", {ImGui::GetContentRegionAvail().x, NULL})) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + int mode_ = (int)engine.mode; + + if (ImGuiH::RadioButton("Disable", &mode_, 0, m_scale)) + engine.mode = state::disable; + + ImGui::SameLine(); + + if (ImGuiH::RadioButton("Record", &mode_, 1, m_scale)) + { + bool canRecord = (!engine.engine_v2 && config.get("tps_enabled", false)) || (engine.engine_v2 && !config.get("tps_enabled", false)); + + if (canRecord) + { + if (engine.mode != state::record) { + engine.clear(); + } + engine.mode = state::record; + } + else + { + engine.mode = state::disable; + ImGuiH::Popup::get().add_popup(engine.engine_v2 ? "Disable TPS Bypass to record the replay" : "Enable TPS Bypass to record the replay"); + } + } + ImGui::SameLine(); + + if (ImGuiH::RadioButton("Play", &mode_, 2, m_scale)) { + bool canPlay = (!engine.engine_v2 && config.get("tps_enabled", false)) || (engine.engine_v2 && !config.get("tps_enabled", false)); + + if (canPlay) { + engine.mode = state::play; + } else { + engine.mode = state::disable; + ImGuiH::Popup::get().add_popup(engine.engine_v2 ? "Disable TPS Bypass to playback the replay" : "Enable TPS Bypass to playback the replay"); + } + } + + ImGui::Separator(); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 35 * m_scale); + ImGui::InputTextWithHint("##replay_name", "Enter the macro name", &engine.replay_name); + + ImGui::SameLine(); + + if (ImGuiH::ArrowButton("##replay_select", ImGuiDir_Down)) { + replay_list.clear(); + for (const auto &entry : std::filesystem::directory_iterator(folderMacroPath)) { + std::string ext = entry.path().filename().extension().string(); + if (!engine.engine_v2 ? (ext == ".re" || ext == ".re3") : (ext == ".re21" || ext == ".re2")) { + replay_list.push_back(entry); + } + } + ImGui::OpenPopup("Select Replay"); + } + + if (ImGuiH::Button("Save", {ImGui::GetContentRegionAvail().x / 3, NULL})) { + ImGuiH::Popup::get().add_popup(engine.save(engine.replay_name)); + } + ImGui::SameLine(); + + if (ImGuiH::Button("Load", {ImGui::GetContentRegionAvail().x / 2, NULL})) { + ImGuiH::Popup::get().add_popup(engine.load(engine.replay_name)); + } + + if (ImGui::IsItemClicked(1) && ImGui::IsItemHovered()) { + ImGui::OpenPopup("P1/P2 Load"); + } + + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) { + ImGui::SetTooltip("Right click to load P1/P2 macros"); + } + + if (ImGui::BeginPopup("P1/P2 Load")) { + if (ImGuiH::Button("Load P1")) { + ImGuiH::Popup::get().add_popup(engine.load(engine.replay_name, true, false)); + ImGui::CloseCurrentPopup(); + } + + ImGui::SameLine(); + + if (ImGuiH::Button("Load P2")) { + ImGuiH::Popup::get().add_popup(engine.load(engine.replay_name, false, true)); + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + + ImGui::SameLine(); + + if (ImGuiH::Button("Clear", {ImGui::GetContentRegionAvail().x, NULL})) { + engine.clear(); + ImGuiH::Popup::get().add_popup("Replay has been cleared"); + } + + ImGui::Text("Replay Size: %i/%zu", engine.get_current_index(), engine.get_actions_size()); + ImGui::Text("Frame: %i", engine.get_frame()); + + ImGui::Separator(); + + #ifdef GEODE_IS_WINDOWS + auto& recorder = Recorder::get(); + auto& recorderAudio = RecorderAudio::get(); + + if (recorder.settings_openned) { + if (first_time_re_settings) { + first_time_re_settings = false; + ImGui::SetNextWindowSize({800 * m_scale, 540 * m_scale}); + } + } + + if (ImGui::BeginPopupModal("Replay Engine More Settings", &recorder.settings_openned) && ImGui::BeginTabBar("Engine Tabs")) { + auto containsRussianLetters = [](const std::filesystem::path& p) -> bool { + auto pathStr = p.u8string(); + for (char c : pathStr) { + if ((unsigned char)c >= 0xD0 && (unsigned char)c <= 0xD1) { + return true; + } + } + return false; + }; + + if (ImGui::BeginTabItem("Settings")) { + if (ImGuiH::Checkbox("Engine v2.1 (Beta)", &engine.engine_v2, m_scale)) + config.set("engine::v2", engine.engine_v2); + + if (!engine.engine_v2) { + ImGuiH::Checkbox("Accuracy Fix", &engine.accuracy_fix, m_scale); + ImGui::SameLine(); + ImGuiH::Checkbox("Rotation Fix", &engine.rotation_fix, m_scale); + } + + // bool practice_fix = config.get("practice_fix", false); + // if (ImGuiH::Checkbox("Practice Fix", &practice_fix, m_scale)) + // config.set("practice_fix", practice_fix); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Editor")) { + static int selected_index = 0; + static int selected_frame_type = 1; + static int selected_player = 0; + static int prev_frame_type = selected_frame_type; + static int prev_player = selected_player; + + const char* frame_types[] = { "Physic Frames", "Input Frames" }; + const char* player_labels[] = { "Player 1", "Player 2" }; + + bool isPlayer1 = (selected_player == 0); + + if (prev_frame_type != selected_frame_type || prev_player != selected_player) { + selected_index = 0; + prev_frame_type = selected_frame_type; + prev_player = selected_player; + } + + if (selected_frame_type == 0) { + auto& frames = engine.getPhysicFrames(isPlayer1); + + if (frames.empty()) { + selected_index = -1; + } else if (selected_index >= frames.size()) { + selected_index = frames.size() - 1; + } + + if (ImGui::BeginTable("PhysicTable", 5, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY, + ImVec2(ImGui::GetContentRegionAvail().x - 200 * m_scale, 0))) { + + ImGui::TableSetupColumn("Frame"); + ImGui::TableSetupColumn("X"); + ImGui::TableSetupColumn("Y"); + ImGui::TableSetupColumn("Rotation"); + ImGui::TableSetupColumn("Y Accel"); + ImGui::TableHeadersRow(); + + ImGuiListClipper clipper; + clipper.Begin(frames.size()); + while (clipper.Step()) { + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { + const auto& frame = frames[i]; + ImGui::TableNextRow(); + + bool is_selected = (selected_index == i); + ImGui::TableSetColumnIndex(0); + std::string frame_str = std::to_string(frame.frame) + "##" + std::to_string(i); + if (ImGui::Selectable(frame_str.c_str(), is_selected, ImGuiSelectableFlags_SpanAllColumns)) { + selected_index = i; + } + + ImGui::TableSetColumnIndex(1); ImGui::Text("%f", frame.x); + ImGui::TableSetColumnIndex(2); ImGui::Text("%f", frame.y); + ImGui::TableSetColumnIndex(3); ImGui::Text("%f", frame.rotation); + ImGui::TableSetColumnIndex(4); ImGui::Text("%f", frame.y_accel); + } + } + + ImGui::EndTable(); + } + } else { + auto& frames = engine.getInputFrames(isPlayer1); + + if (frames.empty()) { + selected_index = -1; + } else if (selected_index >= frames.size()) { + selected_index = frames.size() - 1; + } + + if (ImGui::BeginTable("InputTable", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY, + ImVec2(ImGui::GetContentRegionAvail().x - 200 * m_scale, 0))) { + + ImGui::TableSetupColumn("Frame"); + ImGui::TableSetupColumn("Button"); + ImGui::TableSetupColumn("Action"); + ImGui::TableHeadersRow(); + + ImGuiListClipper clipper; + clipper.Begin(frames.size()); + while (clipper.Step()) { + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { + const auto& frame = frames[i]; + ImGui::TableNextRow(); + + bool is_selected = (selected_index == i); + ImGui::TableSetColumnIndex(0); + + std::string frame_str = std::to_string(frame.frame) + "##" + std::to_string(i); + if (ImGui::Selectable(frame_str.c_str(), is_selected, ImGuiSelectableFlags_SpanAllColumns)) { + selected_index = i; + } + + ImGui::TableSetColumnIndex(1); ImGui::Text((frame.button == 1) ? "Up" : (frame.button == 2) ? "Left" : + (frame.button == 3) ? "Right" : "Unknown"); + ImGui::TableSetColumnIndex(2); ImGui::Text("%s", frame.down ? "Push" : "Release"); + } + } + + ImGui::EndTable(); + } + } + + ImGui::SameLine(); + + if (ImGui::BeginChild("##EditorSettings", {0, ImGui::GetContentRegionAvail().y})) { + ImGui::Text("Frame Type:"); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##Frame Type", &selected_frame_type, frame_types, IM_ARRAYSIZE(frame_types)); + + ImGui::Text("Player:"); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::Combo("##Player", &selected_player, player_labels, IM_ARRAYSIZE(player_labels)); + + ImGui::Separator(); + ImGui::Text("Actions:"); + + if (selected_frame_type == 0) { + auto& frames = engine.getPhysicFrames(isPlayer1); + + if (ImGuiH::Button("Add Action", ImVec2(ImGui::GetContentRegionAvail().x, 0))) { + replay_data new_frame; + + new_frame.x = 0.0f; + new_frame.y = 0.0f; + new_frame.rotation = 0.0f; + new_frame.y_accel = 0.0f; + new_frame.player = isPlayer1; + + if (!frames.empty() && selected_index >= 0 && selected_index < frames.size()) { + new_frame.frame = frames[selected_index].frame + 1; + + frames.insert(frames.begin() + selected_index + 1, new_frame); + selected_index++; + } else { + new_frame.frame = 0; + frames.push_back(new_frame); + selected_index = frames.size() - 1; + } + } + + if (!frames.empty() && selected_index >= 0 && selected_index < frames.size()) { + ImGui::BeginDisabled(frames.size() < 1); + if (ImGuiH::Button("Remove Action", ImVec2(ImGui::GetContentRegionAvail().x, 0))) { + frames.erase(frames.begin() + selected_index); + + if (frames.empty()) { + selected_index = -1; + } else if (selected_index >= frames.size()) { + selected_index = frames.size() - 1; + } + } + ImGui::EndDisabled(); + + ImGui::BeginDisabled(selected_index <= 0); + if (ImGuiH::Button("Move Up", ImVec2((ImGui::GetContentRegionAvail().x / 2.0f) - 2.0f, 0))) { + if (selected_index > 0) { + std::swap(frames[selected_index], frames[selected_index - 1]); + selected_index--; + } + } + ImGui::EndDisabled(); + + ImGui::SameLine(); + + ImGui::BeginDisabled(selected_index >= frames.size() - 1); + if (ImGuiH::Button("Move Down", ImVec2(ImGui::GetContentRegionAvail().x, 0))) { + if (selected_index < frames.size() - 1) { + std::swap(frames[selected_index], frames[selected_index + 1]); + selected_index++; + } + } + ImGui::EndDisabled(); + + ImGui::Separator(); + } + + if (!frames.empty() && selected_index >= 0 && selected_index < frames.size()) { + auto& selected_frame = frames[selected_index]; + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + int frame_cast = static_cast(selected_frame.frame); + if (ImGui::DragInt("Frame##Drag", &frame_cast, 1.f, 0, INT_MAX, "Frame: %i")) { + selected_frame.frame = static_cast(frame_cast); + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::DragFloat("X##Drag", &selected_frame.x, 0.001f, -FLT_MAX, FLT_MAX, "X Position: %f"); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::DragFloat("Y##Drag", &selected_frame.y, 0.001f, -FLT_MAX, FLT_MAX, "Y Position: %f"); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::DragFloat("Rotation##Drag", &selected_frame.rotation, 0.001f, -FLT_MAX, FLT_MAX, "Rotation: %f"); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + float yAccel_cast = static_cast(selected_frame.y_accel); + if (ImGui::DragFloat("YAccel##Drag", &yAccel_cast, 0.001f, -FLT_MAX, FLT_MAX, "Y Accel: %f")) { + selected_frame.y_accel = static_cast(yAccel_cast); + } + } else { + ImGui::TextColored(ImColor(255, 128, 128).Value, "No physic frames"); + } + } else if (selected_frame_type == 1) { + auto& frames = engine.getInputFrames(isPlayer1); + + if (ImGuiH::Button("Add Action", ImVec2(ImGui::GetContentRegionAvail().x, 0))) { + replay_data2 new_frame; + + new_frame.button = 1; + new_frame.down = false; + new_frame.isPlayer1 = isPlayer1; + + if (!frames.empty() && selected_index >= 0 && selected_index < frames.size()) { + new_frame.frame = frames[selected_index].frame + 1; + + frames.insert(frames.begin() + selected_index + 1, new_frame); + selected_index++; + } else { + new_frame.frame = 0; + frames.push_back(new_frame); + selected_index = frames.size() - 1; + } + } + + if (!frames.empty() && selected_index >= 0 && selected_index < frames.size()) { + ImGui::BeginDisabled(frames.size() < 1); + if (ImGuiH::Button("Remove Action", ImVec2(ImGui::GetContentRegionAvail().x, 0))) { + frames.erase(frames.begin() + selected_index); + + if (frames.empty()) { + selected_index = -1; + } else if (selected_index >= frames.size()) { + selected_index = frames.size() - 1; + } + } + ImGui::EndDisabled(); + + ImGui::BeginDisabled(selected_index <= 0); + if (ImGuiH::Button("Move Up", ImVec2((ImGui::GetContentRegionAvail().x / 2.0f) - 2.0f, 0))) { + if (selected_index > 0) { + std::swap(frames[selected_index], frames[selected_index - 1]); + selected_index--; + } + } + ImGui::EndDisabled(); + + ImGui::SameLine(); + + ImGui::BeginDisabled(selected_index >= frames.size() - 1); + if (ImGuiH::Button("Move Down", ImVec2(ImGui::GetContentRegionAvail().x, 0))) { + if (selected_index < frames.size() - 1) { + std::swap(frames[selected_index], frames[selected_index + 1]); + selected_index++; + } + } + ImGui::EndDisabled(); + + ImGui::Separator(); + } + + if (!frames.empty() && selected_index >= 0 && selected_index < frames.size()) { + auto& selected_frame = frames[selected_index]; + + int frame_cast = static_cast(selected_frame.frame); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::DragInt("Frame##Drag", &frame_cast, 1, 0, INT_MAX, "Frame: %i")) { + selected_frame.frame = static_cast(frame_cast); + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::DragInt("Button##Drag", &selected_frame.button, 1, 1, 3, "Button: %i"); + + ImGuiH::Checkbox("Push", &selected_frame.down, m_scale); + } else { + ImGui::TextColored(ImColor(255, 128, 128).Value, "No input frames"); + } + } + + ImGui::EndChild(); + } + + ImGui::EndTabItem(); + } + + + if (ImGui::BeginTabItem("Recorder")) { + if (recorder.ffmpeg_installed) { + auto pl = PlayLayer::get(); + if (ImGuiH::Checkbox("Record##Recorder", &recorder.enabled, m_scale)) { + ImVec2 displaySize = ImGui::GetIO().DisplaySize; + + if (containsRussianLetters(recorder.folderShowcasesPath)) { + recorder.enabled = false; + ImGuiH::Popup::get().add_popup("Invalid path to the showcase folder. Please remove any Cyrillic characters"); + } + else if (pl && pl->m_hasCompletedLevel) { + ImGuiH::Popup::get().add_popup("Restart level to start recording"); + recorder.enabled = false; + } + else { + bool fps_enabled = config.get("tps_enabled", false); + bool check = recorder.fps <= config.get("tps_value", 60.f); + bool check2 = recorder.fps >= 60 && recorder.fps <= 240; + + if (engine.engine_v2 ? (!fps_enabled && check2) : (fps_enabled && check)) { + if (recorder.enabled) { + if (recorder.overlay_mode && !recorder.native_mode) { + auto size = ImGui::GetIO().DisplaySize; + recorder.width = static_cast(size.x); + recorder.height = static_cast(size.y); + } + + if (!recorder.advanced_mode) { + recorder.full_cmd = recorder.compile_command(); + } + + recorder.start(recorder.full_cmd); + } + else { + recorder.stop(); + } + } + else { + recorder.enabled = false; + if (engine.engine_v2) { + if (fps_enabled) + ImGuiH::Popup::get().add_popup("Disable TPS Bypass to start render"); + + if (!check2) + ImGuiH::Popup::get().add_popup("Recorder FPS values must be within the range 60 to 240"); + } + else { + recorder.enabled = false; + if (!fps_enabled) + ImGuiH::Popup::get().add_popup("Enable TPS Bypass to start render"); + + if (!check) + ImGuiH::Popup::get().add_popup("Recorder FPS should not be more than the current TPS"); + } + + } + } + } + + ImGui::SameLine(); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputText("##videoname", &recorder.video_name); + + bool use_flvc = recorder.video_name.ends_with(".flvc"); + + if (use_flvc) { + ImGui::PushStyleColor(ImGuiCol_Text, ImColor(255, 128, 128).Value); + ImGui::Text("You are trying to encode a video in Free Lossless Video Codec (FLVC)"); + ImGui::TextWrapped("- Make sure you have enough disk space: a 1280x720 60 FPS video of 10 seconds takes more than 100 MB (even more at higher resolutions)"); + ImGui::TextWrapped("- Recommended to record the showcase on HDD (Large file volumes can compromise SSD integrity due to write cycle limitations)"); + ImGui::PopStyleColor(); + } + + + if (!use_flvc) { + ImGuiH::Checkbox("Advanced Mode", &recorder.advanced_mode, m_scale); + + if (recorder.advanced_mode) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputText("##full_args", &recorder.full_cmd); + if (ImGuiH::Button("Compile")) { + recorder.full_cmd = recorder.compile_command(); + } + + ImGui::SameLine(); + if (ImGuiH::Button("Copy to clipboard")) { + ImGui::SetClipboardText(recorder.full_cmd.c_str()); + } + + ImGui::SameLine(); + if (ImGuiH::Button("Paste to from clipboard")) { + recorder.full_cmd = (std::string)ImGui::GetClipboardText(); + } + } + } + + if (ImGuiH::Checkbox("Overlay Mode", &recorder.overlay_mode, m_scale)) { + bool isFullscreen = !GameManager::sharedState()->getGameVariable("0025"); + if (!isFullscreen && !recorder.native_mode) { + recorder.overlay_mode = false; + ImGuiH::Popup::get().add_popup("Please switch to fullscreen to record in overlay mode (or enable native mode)"); + } + } + + ImGui::SameLine(); + + if (ImGuiH::Checkbox("Native Mode", &recorder.native_mode, m_scale)) { + bool isFullscreen = !GameManager::sharedState()->getGameVariable("0025"); + if (isFullscreen) { + recorder.native_mode = false; + ImGuiH::Popup::get().add_popup("Please switch to windowed mode to record in native mode mode"); + } + } + + if (recorder.native_mode) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(200.f * m_scale); + if (ImGui::DragFloat("##PauseLayerScale", &recorder.pauseLayerScale, 0.01f, 0, FLT_MAX, "Pause Layer Scale: %.2f")) { + if (hacks.pauseLayer) { + hacks.pauseLayer->setAnchorPoint({0.f, 1.f}); + hacks.pauseLayer->setScale(recorder.pauseLayerScale); + } + } + } + + if (recorder.overlay_mode) { + ImGui::PushStyleColor(ImGuiCol_Text, ImColor(255, 128, 128).Value); + ImGui::TextWrapped("Overlay Mode allows you to capture all overlays (like steam or reshade)"); + ImGui::TextWrapped("In this mode, you can only record in full screen mode (native screen resolution)"); + ImGui::TextWrapped("- Don't open other overlays like GDH, Steam, Reshade to make a good showcase"); + ImGui::TextWrapped("- Don't pause while recording the showcase, a frame of the pause menu may be visible"); + ImGui::PopStyleColor(); + } + + if (recorder.native_mode) { + if (recorder.overlay_mode) ImGui::NewLine(); + ImGui::PushStyleColor(ImGuiCol_Text, ImColor(255, 128, 128).Value); + ImGui::TextWrapped("Native mode adjusts the window size to match the video screen resolution"); + ImGui::TextWrapped("- Some parts of the window may be hidden"); + ImGui::TextWrapped("- Make sure you are able to enter the level to start recording (GD Buttons can be behind the screen)"); + ImGui::TextWrapped("- It lets you record in any resolution with Overlay Mode"); + ImGui::TextWrapped("- Also makes shader triggers match the quality of the recorded video resolution"); + ImGui::PopStyleColor(); + } + + + ImGui::Text("Resolution:"); + ImGui::Separator(); + + ImGui::PushItemWidth(45.f * m_scale); + if (ImGui::InputInt("##width", &recorder.width, 0) && recorder.lock_aspect_ratio) { + float aspect_ratio = 16.0f / 9.0f; + recorder.height = static_cast(recorder.width / aspect_ratio); + } + ImGui::SameLine(0, 5); + + ImGui::Text("x"); + ImGui::SameLine(0, 5); + + ImGui::PushItemWidth(45.f * m_scale); + if (ImGui::InputInt("##height", &recorder.height, 0) && recorder.lock_aspect_ratio) { + float aspect_ratio = 16.0f / 9.0f; + recorder.width = static_cast(recorder.height * aspect_ratio); + } + ImGui::SameLine(0, 5); + + ImGui::Text("@"); + ImGui::SameLine(0, 5); + + ImGui::PushItemWidth(35.f * m_scale); + ImGui::InputInt("##fps", &recorder.fps, 0); + + ImGui::SameLine(0, 5); + + ImGuiH::Checkbox("Lock Aspect Ratio (16:9)", &recorder.lock_aspect_ratio, m_scale); + + ImGui::Spacing(); + + ImGui::Text("Encoding Settings"); + ImGui::Separator(); + + if (!use_flvc) { + ImGui::PushItemWidth(50.f * m_scale); + ImGui::InputText("Bitrate", &recorder.bitrate); + + ImGui::SameLine(); + + ImGui::PushItemWidth(80.f * m_scale); + ImGui::InputText("Codec", &recorder.codec); + + ImGui::PushItemWidth(300 * m_scale); + ImGui::InputText("Extra Arguments", &recorder.extra_args); + + ImGui::PushItemWidth(300 * m_scale); + ImGui::InputText("VF Args", &recorder.vf_args); + + if (ImGuiH::Checkbox("vflip", &recorder.vflip, m_scale)) { + recorder.compile_vf_args(); + } + + if (ImGuiH::Checkbox("Fade in", &recorder.fade_in, m_scale)) { + recorder.compile_vf_args(); + } + + ImGui::SameLine(); + + ImGui::PushItemWidth(120 * m_scale); + if (ImGui::DragFloat("##fade_in_start", &recorder.fade_in_start, 0.01f, 0, FLT_MAX, "Start: %.2fs")) { + recorder.compile_vf_args(); + } + + ImGui::SameLine(); + + ImGui::PushItemWidth(120 * m_scale); + if (ImGui::DragFloat("##fade_in_end", &recorder.fade_in_end, 0.01f, 0, FLT_MAX, "End: %.2fs")) { + recorder.compile_vf_args(); + } + } + + ImGuiH::Checkbox("Fade out", &recorder.fade_out, m_scale); + ImGui::Text("Note: The length of the fade-out is calculated based on the value of \"Second to Render After\""); + + ImGuiH::Checkbox("Hide Level Complete", &recorder.hide_level_complete, m_scale); + + ImGui::Spacing(); + + ImGui::Text("Level Settings"); + ImGui::Separator(); + + ImGui::PushItemWidth(200 * m_scale); + + ImGui::InputFloat("Second to Render After", &recorder.after_end_duration, 1); + + ImGui::Spacing(); + + ImGui::Text("Presets (Thanks WarGack, ElPaan, midixd)"); + ImGui::Separator(); + + + if (ImGuiH::Button("Save")) { + nlohmann::json j; + j["width"] = recorder.width; + j["height"] = recorder.height; + j["fps"] = recorder.fps; + j["bitrate"] = recorder.bitrate; + j["codec"] = recorder.codec; + j["extra_args"] = recorder.extra_args; + j["vf_args"] = recorder.vf_args; + + std::ofstream file(folderPath / "recorder_settings.json"); + if (file.is_open()) { + file << j.dump(4); + file.close(); + + ImGuiH::Popup::get().add_popup("Configuration saved"); + } else { + ImGuiH::Popup::get().add_popup("Could not open file for writing"); + } + } + + ImGui::SameLine(); + + if (ImGuiH::Button("Load")) { + std::ifstream file(folderPath / "recorder_settings.json"); + if (file.is_open()) { + std::string file_contents((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + + nlohmann::json j = nlohmann::json::parse(file_contents, nullptr, false); + if (j.is_discarded()) { + ImGuiH::Popup::get().add_popup("JSON parsing error or invalid file"); + } else { + recorder.width = j.value("width", recorder.width); + recorder.height = j.value("height", recorder.height); + recorder.fps = j.value("fps", recorder.fps); + recorder.bitrate = j.value("bitrate", recorder.bitrate); + recorder.codec = j.value("codec", recorder.codec); + recorder.extra_args = j.value("extra_args", recorder.extra_args); + recorder.vf_args = j.value("vf_args", recorder.vf_args); + + ImGuiH::Popup::get().add_popup("Configuration loaded"); + } + file.close(); + } else { + ImGuiH::Popup::get().add_popup("Could not open file for reading"); + } + } + + if (ImGuiH::Button("HD")) + { + recorder.width = 1280; + recorder.height = 720; + recorder.fps = 60; + recorder.bitrate = "25M"; + } + + ImGui::SameLine(); + + if (ImGuiH::Button("FULL HD")) + { + recorder.width = 1920; + recorder.height = 1080; + recorder.fps = 60; + recorder.bitrate = "50M"; + } + + ImGui::SameLine(); + + if (ImGuiH::Button("2K")) + { + recorder.width = 2560; + recorder.height = 1440; + recorder.fps = 60; + recorder.bitrate = "70M"; + } + + ImGui::SameLine(); + + if (ImGuiH::Button("4K")) + { + recorder.width = 3840; + recorder.height = 2160; + recorder.fps = 60; + recorder.bitrate = "80M"; + } + + ImGui::SameLine(); + + if (ImGuiH::Button("8K")) + { + recorder.width = 7680; + recorder.height = 4320; + recorder.fps = 60; + recorder.bitrate = "250M"; + } + + if (!use_flvc) { + if (ImGuiH::Button("CPU x264")) + { + recorder.codec = "libx264"; + recorder.extra_args = "-pix_fmt yuv420p -preset ultrafast"; + } + + ImGui::SameLine(); + + if (ImGuiH::Button("CPU x265")) + { + recorder.codec = "libx265"; + recorder.extra_args = "-pix_fmt yuv420p -preset ultrafast"; + } + ImGui::SameLine(); + if (ImGuiH::Button("CPU AV1 Lossless")) + { + recorder.codec = "libsvtav1"; + recorder.extra_args = "-crf 0 -pix_fmt yuv420p"; + } + if (ImGuiH::Button("NVIDIA H264")) + { + recorder.codec = "h264_nvenc"; + recorder.extra_args = "-pix_fmt yuv420p -preset p7"; + } + ImGui::SameLine(); + if (ImGuiH::Button("NVIDIA H265")) + { + recorder.codec = "hevc_nvenc"; + recorder.extra_args = "-pix_fmt yuv420p -preset p7"; + } + ImGui::SameLine(); + + if (ImGuiH::Button("NVIDIA AV1")) + { + recorder.codec = "av1_nvenc"; + recorder.extra_args = "-pix_fmt yuv420p -preset p7"; + } + + if (ImGuiH::Button("AMD H264 Lossless")) + { + recorder.codec = "h264_amf"; + recorder.extra_args = "-pix_fmt yuv420p -rc cqp -qp_i 0 -qp_p 0 -qp_b 0"; + } + ImGui::SameLine(); + if (ImGuiH::Button("AMD H265 Lossless")) + { + recorder.codec = "hevc_amf"; + recorder.extra_args = "-pix_fmt yuv420p -rc cqp -qp_i 0 -qp_p 0 -qp_b 0"; + } + if (ImGuiH::Button("Color Fix")) + { + recorder.compile_vf_args(); + if (!recorder.vf_args.empty()) + recorder.vf_args += ","; + recorder.vf_args += "colorspace=all=bt709:iall=bt470bg:fast=1"; + } + } + + ImGui::Spacing(); + + ImGui::Text("Folders"); + ImGui::Separator(); + + if (ImGuiH::Button("Open Showcase Folder")) { + geode::utils::file::openFolder(recorder.folderShowcasesPath); + } + + ImGui::SameLine(); + + if (ImGuiH::Button("Change folder")) { + // Use Windows native folder dialog (no coroutines = no MSVC crash) + { + BROWSEINFOW bi = {}; + bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; + bi.lpszTitle = L"Select output folder"; + LPITEMIDLIST pidl = SHBrowseForFolderW(&bi); + if (pidl) { + wchar_t buf[MAX_PATH]; + if (SHGetPathFromIDListW(pidl, buf)) { + recorder.folderShowcasesPath = std::filesystem::path(buf); + config.set("showcases_path", recorder.folderShowcasesPath); + } + CoTaskMemFree(pidl); + } + } + } + + ImGui::SameLine(); + + if (ImGuiH::Button("Reset Folder")) { + recorder.folderShowcasesPath = folderPath / "Showcases"; + config.set("showcases_path", recorder.folderShowcasesPath); + } + + + ImGui::Text("Showcase Folder: %s", recorder.folderShowcasesPath.string().c_str()); + } + else { + ImGui::Text("Looks like FFmpeg is not installed, here are the instructions:"); + ImGui::Text("1. Download ffmpeg (archive)"); + ImGui::SameLine(); + if (ImGuiH::Button("Download")) { + ShellExecuteA(0, "open", "https://github.com/AnimMouse/ffmpeg-autobuild/releases/latest", 0, 0, SW_SHOWNORMAL); + } + ImGui::Text("2. Extract from archive \"ffmpeg.exe\" file to Geometry Dash folder"); + ImGui::Text("3. Restart Geometry Dash"); + if (ImGuiH::Button("Bypass")) recorder.ffmpeg_installed = true; + } + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Audio")) { + if (ImGuiH::Checkbox("Record Buffer", &recorderAudio.enabled, m_scale)) { + if (containsRussianLetters(recorder.folderShowcasesPath)) { + recorder.enabled = false; + ImGuiH::Popup::get().add_popup("Invalid path to the showcase folder. Please remove any Cyrillic characters"); + } + else { + if (recorderAudio.enabled) { + if (!recorderAudio.showcase_mode) { + recorderAudio.start(); + ImGuiH::Popup::get().add_popup("Audio recording started!"); + } + else { + ImGuiH::Popup::get().add_popup("Rendering begins when you re-enter the level (don't forget to set up the macro playback!!)"); + recorderAudio.first_start = true; + } + } + else { + recorderAudio.stop(); + ImGuiH::Popup::get().add_popup("Audio recording stopped!"); + } + } + } + + ImGuiH::Checkbox("Showcase Mode", &recorderAudio.showcase_mode, m_scale); + + ImGui::Spacing(); + + ImGui::Text("Level Settings"); + ImGui::Separator(); + + ImGui::PushItemWidth(120 * m_scale); + ImGui::SliderFloat("##MusicRecorder", &recorderAudio.volume_music, 0.f, 1.f, "Music: %.2f"); + + ImGui::SameLine(); + + ImGui::PushItemWidth(120 * m_scale); + + ImGui::SliderFloat("##SFXRecorder", &recorderAudio.volume_sfx, 0.f, 1.f, "SFX: %.2f"); + + if (recorderAudio.showcase_mode) { + ImGui::PushItemWidth(200 * m_scale); + ImGui::InputFloat("Second to Render After##2", &recorderAudio.after_end_duration, 1); + } + + ImGui::Spacing(); + + ImGui::Text("Filename"); + ImGui::Separator(); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputText("##audio_filename", &recorderAudio.audio_name); + + ImGui::Spacing(); + + ImGui::Text("Folders"); + ImGui::Separator(); + + if (ImGuiH::Button("Open Showcase Folder")) { + geode::utils::file::openFolder(recorder.folderShowcasesPath); + } + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Merge (Video + Audio)")) { + static bool shortest = true; + static std::vector videos; + static std::vector audios; + static int index_videos = 0; + static int index_audios = 0; + static std::string args = "-c:v copy -c:a libfdk_aac -vbr 5 -shortest"; + + auto compile = [&]() + { + args = "-c:v copy -c:a libfdk_aac -vbr 5"; + if (shortest) { + args += " -shortest"; + } + }; + + ImGui::BeginChild("##VideoSelect", {NULL, 190 * m_scale}, true); + for (size_t i = 0; i < videos.size(); i++) { + bool is_selected = (index_videos == i); + if (ImGui::Selectable(videos[i].filename().string().c_str(), is_selected)) { + index_videos = i; + } + } + ImGui::EndChild(); + + ImGui::BeginChild("##AudioSelect", {NULL, 190 * m_scale}, true); + for (size_t i = 0; i < audios.size(); i++) { + bool is_selected = (index_audios == i); + if (ImGui::Selectable(audios[i].filename().string().c_str(), is_selected)) { + index_audios = i; + } + } + ImGui::EndChild(); + + if (ImGuiH::Button("Refresh", {ImGui::GetContentRegionAvail().x, NULL})) { + videos.clear(); + audios.clear(); + for (const auto &entry : std::filesystem::directory_iterator(recorder.folderShowcasesPath)) { + if (entry.path().extension() == ".mp4" || + entry.path().extension() == ".mkv" || + entry.path().extension() == ".avi" || + entry.path().extension() == ".mov" || + entry.path().extension() == ".flv" || + entry.path().extension() == ".wmv" || + entry.path().extension() == ".webm" || + entry.path().extension() == ".m4v" || + entry.path().extension() == ".mpeg") { + + videos.push_back(entry.path().string()); + } + + if (entry.path().extension() == ".wav" || + entry.path().extension() == ".mp3" || + entry.path().extension() == ".m4a") { + audios.push_back(entry.path().string()); + } + } + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputText("##merge_command", &args); + + if (ImGuiH::Checkbox("Shortest", &shortest, m_scale)) + compile(); + + ImGui::SameLine(); + + if (ImGuiH::Button("Merge", {ImGui::GetContentRegionAvail().x, NULL})) { + if (videos.empty() || audios.empty()) { + + } else if (index_videos >= 0 && index_videos < (int)videos.size() && index_audios >= 0 && index_audios < (int)audios.size()) { + std::string command = "ffmpeg.exe -y -i \"" + videos[index_videos].string() + "\" -i \"" + audios[index_audios].string() + "\" " + args + " "; + std::filesystem::path video_rel(videos[index_videos]); + command += fmt::format("\"{}\\audio_{}\"", recorder.folderShowcasesPath, video_rel.filename().string()); + geode::log::debug("{}", command); + auto process = subprocess::Popen(command); + } + } + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + ImGui::EndPopup(); + } + + + if (ImGui::MenuItem("More Settings")) { + recorder.settings_openned = true; + recorder.ffmpeg_installed = std::filesystem::exists("ffmpeg.exe"); + ImGui::OpenPopup("Replay Engine More Settings"); + } + #endif + } + } + else if (windowName == "Labels") { + auto &labels = Labels::get(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x/2); + if (ImGui::DragFloat("##Label Opacity", &labels.opacity, 0.01f, 0.f, 1.f, "Opacity: %.2f")) config.set("label-opacity", labels.opacity); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::DragFloat("##Label Size", &labels.size, 0.01f, 0.f, 1.f, "Size: %.2f")) config.set("label-size", labels.size); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::DragFloat("##Label Padding", &labels.padding, 1.f, 0.f, 50.f, "Label Padding: %.1fpx")) config.set("label-padding", labels.padding); + + const char *labels_positions[] = {"Top Left", "Top Right", "Top", "Bottom Left", "Bottom Right", "Bottom"}; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGuiH::Combo("##labelspos", &selected_label_corner, labels_positions, 6, 6); + + const char *label_types[] = { + "Time (24H)", + "Time (12H)", + "Session Time", + "Cheat Indicator", + "FPS Counter", + "Level Progress", + "Attempt", + "CPS Counter", + "Level Info", + "Noclip Accuracy", + "Death Counter", + "Startpos Switcher", + "Testmode", + "Replay Engine State", + "CBF Status", + "Rainbow Text", + "Custom Text" + }; + int label_types_count = sizeof(label_types)/sizeof(label_types[0]); + + ImGuiH::Combo("##labeladdtype", &selected_label_type, label_types, label_types_count); + if (selected_label_type == label_types_count - 1) + ImGui::InputText("##labelinput", &selected_label_text); + + ImGui::SameLine(); + if (ImGuiH::Button("+")) { + std::string text; + if (selected_label_type == label_types_count - 1) text = selected_label_text; + else if (selected_label_type == 0) text = "{time:24}"; + else if (selected_label_type == 1) text = "{time:12}"; + else if (selected_label_type == 2) text = "Session Time: {sessionTime}"; + else if (selected_label_type == 3) { + text = "{cheat_indicator}"; + config.set("cheat_indicator", true); + } + else if (selected_label_type == 4) text = "{fps} FPS"; + else if (selected_label_type == 5) text = "{progress:2f}"; + else if (selected_label_type == 6) text = "Attempt {attempt}"; + else if (selected_label_type == 7) text = "ColoredCPS({cps}/{cpsHigh}/{clicks})"; + else if (selected_label_type == 8) text = "{levelName}{byLevelCreator} ({levelId})"; + else if (selected_label_type == 9) text = "ColoredDeath({noclipAccuracy})"; + else if (selected_label_type == 10) text = "ColoredDeath({deaths} Deaths)"; + else if (selected_label_type == 11) text = "showIfStartposesExist({startposCurrentIDX}/{startposAllIDX})"; + else if (selected_label_type == 12) text = "{testmode}"; + else if (selected_label_type == 13) text = "{re_state}"; + else if (selected_label_type == 14) text = "CBF: {cbf_enabled}"; + else if (selected_label_type == 15) text = "Rainbow text!!"; + + Label l((LabelCorner) (selected_label_corner+1), text); + labels.add(l); + } + + ImGui::Separator(); + ImGui::BeginChild("Labels"); + ImGui::Spacing(); + + if (!std::any_of(labels.labels.begin(), labels.labels.end(), + [&](const Label& label) { return static_cast(label.corner - 1) == selected_label_corner; })) { + ImGui::TextDisabled("No labels in this corner"); + } + + for (size_t index = 0; index < labels.labels.size(); index++) { + Label& item = labels.labels[index]; + + if (static_cast(item.corner-1) != selected_label_corner) continue; + + ImGui::PushID(index); + + ImGui::Selectable(" =", false, 0, {20.f * m_scale, 20.f * m_scale}); + + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) { + ImGui::SetTooltip("Double click to remove label"); + } + + ImGui::SameLine(); + + if (ImGui::GetMouseClickedCount(ImGuiMouseButton_Left) >= 2 && ImGui::IsItemHovered()) { + labels.remove(index); + } + + if (ImGui::BeginDragDropSource()) { + ImGui::TextUnformatted(item.format_text.c_str()); + ImGui::SetDragDropPayload("LBLMOVE", &index, sizeof(size_t)); + ImGui::EndDragDropSource(); + } + + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload *payload = ImGui::AcceptDragDropPayload("LBLMOVE")) { + size_t move_from = *(size_t*)payload->Data; + labels.swap(move_from, index); + } + ImGui::EndDragDropTarget(); + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputText("##inptext", &item.format_text); + + ImGui::PopID(); + } + ImGui::EndChild(); + } + else if (windowName == "Shortcuts") { + if (ImGuiH::Button("Options", {ImGui::GetContentRegionAvail().x, NULL})) { + auto options_layer = OptionsLayer::create(); + auto scene = cocos2d::CCScene::get(); + + if (options_layer && scene) { + auto zOrder = scene->getHighestChildZ(); + scene->addChild(options_layer, zOrder + 1); + options_layer->showLayer(false); + } + } + + if (ImGuiH::Button("Reset Level", {ImGui::GetContentRegionAvail().x, NULL})) { + auto pl = PlayLayer::get(); + if (pl) pl->resetLevel(); + } + + if (ImGuiH::Button("Practice Mode", {ImGui::GetContentRegionAvail().x, NULL})) { + auto pl = PlayLayer::get(); + if (pl) pl->togglePracticeMode(!pl->m_isPracticeMode); + } + + if (ImGuiH::Button("Reset Volume", {ImGui::GetContentRegionAvail().x, NULL})) { + auto fmod_engine = FMODAudioEngine::sharedEngine(); + fmod_engine->setBackgroundMusicVolume(0.5f); + fmod_engine->setEffectsVolume(0.5f); + } + + if (ImGuiH::Button("Uncomplete Level", {ImGui::GetContentRegionAvail().x, NULL})) { + utilsH::UncompleteLevel(); + } + + if (ImGuiH::Button("Inject DLL", {ImGui::GetContentRegionAvail().x, NULL})) { + #ifdef GEODE_IS_WINDOWS + geode::utils::file::FilePickOptions::Filter filter = { + .description = "Dynamic Link Library", + .files = { "*.dll"} + }; + + // Use Windows native file dialog (no coroutines = no MSVC crash) + { + wchar_t buf[MAX_PATH] = {}; + OPENFILENAMEW ofn = {}; + ofn.lStructSize = sizeof(ofn); + ofn.lpstrFilter = L"DLL Files*.dllAll Files*.*"; + ofn.lpstrFile = buf; + ofn.nMaxFile = MAX_PATH; + ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; + if (GetOpenFileNameW(&ofn)) { + std::filesystem::path path(buf); + HMODULE hModule = LoadLibraryW(path.wstring().c_str()); + if (hModule) { + ImGuiH::Popup::get().add_popup("Injected successfully"); + } else { + ImGuiH::Popup::get().add_popup("Failed to inject"); + } + } + } + #endif + } + + if (ImGuiH::Button("Resources", {ImGui::GetContentRegionAvail().x/2, NULL})) { + std::string path = cocos2d::CCFileUtils::get()->getWritablePath2(); + geode::utils::file::openFolder(path); + } + ImGui::SameLine(); + if (ImGuiH::Button("AppData", {ImGui::GetContentRegionAvail().x, NULL})) { + auto path = geode::dirs::getSaveDir(); + geode::utils::file::openFolder(path); + } + + if (ImGuiH::Button("GDH AppData", {ImGui::GetContentRegionAvail().x, NULL})) { + geode::utils::file::openFolder(folderPath); + } + } + else { + for (auto& hck : win.hacks) { + bool enabled = config.get(hck.config, false); + + std::string search_name = hck.name; + std::string search_item = search_text; + + std::transform(search_item.begin(), search_item.end(), search_item.begin(), ::tolower); + std::transform(search_name.begin(), search_name.end(), search_name.begin(), ::tolower); + + bool founded = search_item.empty() ? true : (search_name.find(search_item) != std::string::npos); + ImGui::PushStyleColor(ImGuiCol_Text, founded ? ImGui::GetStyle().Colors[ImGuiCol_Text] : ImColor(64, 64, 64).Value); + + + if (m_keybindMode) { + #ifdef GEODE_IS_WINDOWS + if (hck.keybind == -1 && !m_waitingForBindKey && m_keyToSet != -1) { + hck.keybind = m_keyToSet; + m_keyToSet = 0; + } + + if (ImGuiH::Button(hck.keybind != -1 + ? fmt::format("{}: {}", + hck.name.length() > 16 ? hck.name.substr(0, 16) : hck.name, + KeyMappingUtils::GetNameFromGLFW(hck.keybind)).c_str() + : "Press any key...", {ImGui::GetContentRegionAvail().x, NULL})) + { + hck.keybind = -1; + m_waitingForBindKey = true; + } + #endif + } + else { + if (ImGuiH::Checkbox(hck.name.c_str(), &enabled, m_scale)) { + config.set(hck.config, enabled); + if (!hck.game_var.empty()) + GameManager::get()->setGameVariable(hck.game_var.c_str(), enabled); + if (hck.handlerFunc) hck.handlerFunc(enabled); + } + + if (ImGui::IsItemHovered() && !hck.desc.empty()) { + ImGui::SetTooltip("%s", hck.desc.c_str()); + } + + if (hck.handlerCustomWindow) { + ImGui::SameLine(); + if (ImGuiH::ArrowButton(fmt::format("{} Settings", hck.name).c_str(), ImGuiDir_Right)) { + ImGui::OpenPopup(fmt::format("{} Settings", hck.name).c_str()); + } + } + } + + if (hck.handlerCustomWindow) { + if (ImGui::BeginPopupModal(fmt::format("{} Settings", hck.name).c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize)) { + hck.handlerCustomWindow(); + + if (ImGuiH::Button("Close", {400 * m_scale, NULL})) + ImGui::CloseCurrentPopup(); + + ImGui::EndPopup(); + } + } + + ImGui::PopStyleColor(); + } + } + + ImGui::PopFont(); + ImGui::End(); + } +} + +void Gui::Init() { + auto &config = Config::get(); + auto &recorder = Recorder::get(); + + stretchedWindows.clear(); + ImGuiH::Popup::get().messages.clear(); + + m_scale = config.get("gui_scale", 1.f); + m_index_scale = config.get("gui_index_scale", 7); + + m_license_inited = false; + first_time_re_settings = true; + recorder.settings_openned = false; + + bool inverted = config.get("gui_inverted", false); + ApplyGuiColors(inverted); + ApplyColor(themes[config.get("gui_color_index", 0)]); + ApplyStyle(m_scale); + ImGuiIO &io = ImGui::GetIO(); + io.Inverted = inverted; + io.IniFilename = NULL; + io.Fonts->Clear(); + io.Fonts->AddFontFromMemoryCompressedTTF(roboto_font_data, roboto_font_size, 18.f * m_scale, nullptr, io.Fonts->GetGlyphRangesCyrillic()); + io.Fonts->AddFontFromMemoryCompressedTTF(roboto_font_data, roboto_font_size, 32.f * m_scale, nullptr, io.Fonts->GetGlyphRangesCyrillic()); + io.Fonts->AddFontFromMemoryCompressedTTF(roboto_font_data, roboto_font_size, 20.f * m_scale, nullptr, io.Fonts->GetGlyphRangesCyrillic()); + io.Fonts->Build(); +} + +void Gui::Toggle() { + if (!isAnimating) + { + isAnimating = true; + isFadingIn = !isFadingIn; + animationStartTime = std::chrono::steady_clock::now(); + + if (isFadingIn) + { + m_show = !m_show; + updateCursorState(); + } + } +} + + +void Gui::renderKeyButton(const std::string& label, int& key, bool withoutKeybindsMode) { + #ifdef GEODE_IS_WINDOWS + std::string keyStr = label + KeyMappingUtils::GetNameFromGLFW(key); + if (key == -1) { + keyStr = "Press any key..."; + if (!m_waitingForBindKey && m_keyToSet != -1) { + key = m_keyToSet; + m_keyToSet = 0; + if (withoutKeybindsMode) m_keybindMode = false; + } + } + + if (ImGuiH::Button(keyStr.c_str(), {ImGui::GetContentRegionAvail().x, 0})) { + key = -1; + if (withoutKeybindsMode) m_keybindMode = true; + m_waitingForBindKey = true; + } + #endif +}; diff --git a/src/gui_mobile.cpp b/src/gui_mobile.cpp index 4cc9d9bf..670e8e9e 100644 --- a/src/gui_mobile.cpp +++ b/src/gui_mobile.cpp @@ -10,7 +10,7 @@ using namespace geode::prelude; ReplaySelectLayer* ReplaySelectLayer::create(geode::TextInput* textInput) { auto ret = new ReplaySelectLayer(); - if (ret->initAnchored(200.f, 240.f, "GJ_square01.png")) { + if (ret->init(textInput)) { ret->autorelease(); ret->input = textInput; return ret; @@ -20,7 +20,8 @@ ReplaySelectLayer* ReplaySelectLayer::create(geode::TextInput* textInput) { return nullptr; } -bool ReplaySelectLayer::setup() { +bool ReplaySelectLayer::init(geode::TextInput* textInput) { + if (!Popup::init(200.f, 240.f, "GJ_square01.png")) return false; auto& engine = ReplayEngine::get(); this->setTitle("Select Replay"); @@ -164,7 +165,8 @@ CCLabelBMFont* AddTextToToggle(const char *str, CCMenuItemToggler* toggler, floa return label; } -bool HacksLayer::setup() { +bool HacksLayer::init() { + if (!Popup::init(460.f, 260.f, "GJ_square01.png")) return false; auto& config = Config::get(); auto& hacks = Hacks::get(); @@ -425,7 +427,7 @@ void HacksLayer::switchTab(int newIndex) { HacksLayer* HacksLayer::create() { auto ret = new HacksLayer(); - if (ret->initAnchored(460.f, 260.f, "GJ_square01.png")) { + if (ret->init()) { ret->autorelease(); return ret; } @@ -438,12 +440,12 @@ void HacksLayer::onExit() { Config::get().save(fileDataPath); Labels::get().save(); RGBIcons::get().save(); - geode::Popup<>::onExit(); + geode::Popup::onExit(); } RecorderLayer* RecorderLayer::create() { auto ret = new RecorderLayer(); - if (ret->initAnchored(360.f, 240.f, "GJ_square01.png")) { + if (ret->init()) { ret->autorelease(); return ret; } @@ -452,7 +454,8 @@ RecorderLayer* RecorderLayer::create() { return nullptr; } -bool RecorderLayer::setup() { +bool RecorderLayer::init() { + if (!Popup::init(360.f, 240.f, "GJ_square01.png")) return false; auto& recorder = Recorder::get(); this->setTitle("Recorder"); @@ -496,26 +499,17 @@ bool RecorderLayer::setup() { bool check2 = recorder.fps >= 60 && recorder.fps <= 240; if (engine.engine_v2 ? (!fps_enabled && check2) : (fps_enabled && check)) { if (recorder.enabled) { - geode::utils::file::pick(geode::utils::file::PickMode::OpenFolder, {std::nullopt, {}}).listen( - [&](geode::Result* path) { - if (!path->isErr()) { - auto path_final = path->unwrap(); - - recorder.folderShowcasesPath = path_final; - - recorder.video_name = fmt::format("{}.{}", recorder.video_name2, recorder.flvc_enabled ? "flvc" : "mp4"); - - if (!recorder.is_recording) { - recorder.start(""); - FLAlertLayer::create("Recorder", fmt::format("Recording started!\nFilename: {}\nPath {}\nSize: {}x{}\nFPS: {}", - recorder.video_name, recorder.folderShowcasesPath, recorder.width, recorder.height, recorder.fps), "OK")->show(); - } - } - else { - FLAlertLayer::create("Recorder", "Not selected directory", "OK")->show(); - sender->toggle(false); + // On mobile, use the writable path as default (no native folder picker) + { + auto defaultPath = std::filesystem::path(cocos2d::CCFileUtils::get()->getWritablePath2().c_str()); + recorder.folderShowcasesPath = defaultPath; + recorder.video_name = fmt::format("{}.{}", recorder.video_name2, recorder.flvc_enabled ? "flvc" : "mp4"); + if (!recorder.is_recording) { + recorder.start(""); + FLAlertLayer::create("Recorder", fmt::format("Recording started!\nFilename: {}\nPath {}\nSize: {}x{}\nFPS: {}", + recorder.video_name, recorder.folderShowcasesPath, recorder.width, recorder.height, recorder.fps), "OK")->show(); } - }); + } } else { recorder.stop(); @@ -624,7 +618,7 @@ bool RecorderLayer::setup() { RecorderAudioLayer* RecorderAudioLayer::create() { auto ret = new RecorderAudioLayer(); - if (ret->initAnchored(360.f, 240.f, "GJ_square01.png")) { + if (ret->init()) { ret->autorelease(); return ret; } @@ -633,7 +627,8 @@ RecorderAudioLayer* RecorderAudioLayer::create() { return nullptr; } -bool RecorderAudioLayer::setup() { +bool RecorderAudioLayer::init() { + if (!Popup::init(360.f, 240.f, "GJ_square01.png")) return false; auto& recorder = Recorder::get(); auto& recorderAudio = RecorderAudio::get(); this->setTitle("Recorder (Audio)"); @@ -664,28 +659,20 @@ bool RecorderAudioLayer::setup() { recorderAudio.enabled = !sender->isOn(); if (recorderAudio.enabled) { - geode::utils::file::pick(geode::utils::file::PickMode::OpenFolder, {std::nullopt, {}}).listen( - [&](geode::Result* path) { - if (!path->isErr()) { - auto path_final = path->unwrap(); - recorder.folderShowcasesPath = path_final; - recorderAudio.audio_name = fmt::format("{}.wav", recorderAudio.audio_name2); - - if (!recorderAudio.showcase_mode) { - recorderAudio.start(); - } - else - recorderAudio.first_start = true; - - FLAlertLayer::create("Recorder (Audio)", - recorderAudio.showcase_mode ? "Rendering begins when you re-enter the level (don't forget to set up the macro playback!!)" : - "Audio recording started!", "OK")->show(); - } - else { - FLAlertLayer::create("Recorder (Audio)", "Not selected directory", "OK")->show(); - sender->toggle(false); + // On mobile, use writable path as default (no native folder picker) + { + auto defaultPath = std::filesystem::path(cocos2d::CCFileUtils::get()->getWritablePath2().c_str()); + recorder.folderShowcasesPath = defaultPath; + recorderAudio.audio_name = fmt::format("{}.wav", recorderAudio.audio_name2); + if (!recorderAudio.showcase_mode) { + recorderAudio.start(); + } else { + recorderAudio.first_start = true; } - }); + FLAlertLayer::create("Recorder (Audio)", + recorderAudio.showcase_mode ? "Rendering begins when you re-enter the level (don't forget to set up the macro playback!!)" : + "Audio recording started!", "OK")->show(); + } } else { recorderAudio.stop(); diff --git a/src/gui_mobile.hpp b/src/gui_mobile.hpp index 2741e6a9..ce2c3fb1 100644 --- a/src/gui_mobile.hpp +++ b/src/gui_mobile.hpp @@ -19,7 +19,7 @@ class HacksTab : public cocos2d::CCMenu { std::map> m_togglerCallbacks; }; -class HacksLayer : public geode::Popup<> { +class HacksLayer : public geode::Popup { private: std::vector m_buttonTabs; std::vector m_tabs; @@ -27,30 +27,35 @@ class HacksLayer : public geode::Popup<> { CCMenuItemToggler* record_toggle; CCMenuItemToggler* play_toggle; +protected: + bool init(); public: static HacksLayer* create(); void switchTab(int newIndex); - bool setup(); + void onExit(); }; -class ReplaySelectLayer : public geode::Popup<> { +class ReplaySelectLayer : public geode::Popup { private: geode::TextInput* input; +protected: + bool init(geode::TextInput* textInput); public: static ReplaySelectLayer* create(geode::TextInput* textInput); - bool setup(); }; -class RecorderLayer : public geode::Popup<> { +class RecorderLayer : public geode::Popup { +protected: + bool init(); public: static RecorderLayer* create(); - bool setup(); }; -class RecorderAudioLayer : public geode::Popup<> { +class RecorderAudioLayer : public geode::Popup { +protected: + bool init(); public: static RecorderAudioLayer* create(); - bool setup(); }; \ No newline at end of file diff --git a/src/hooks.cpp b/src/hooks.cpp index 7d2bbd7a..b310b375 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -793,18 +793,18 @@ class $modify(MyPlayLayer, PlayLayer) { if (!config.get("pulse_size", false) && config.get("no_pulse", false)) { - m_audioEffectsLayer->m_notAudioScale = 0.5f; + // m_notAudioScale removed in GD 2.208 - only control via FMODAudioEngine FMODAudioEngine::get()->m_pulse1 = 0.5f; } if (config.get("pulse_size", false)) { float value = config.get("pulse_size_value", 0.5f); if (config.get("pulse_multiply", false)) { - m_audioEffectsLayer->m_notAudioScale *= value; + // m_notAudioScale removed in GD 2.208 FMODAudioEngine::get()->m_pulse1 *= value; } else { - m_audioEffectsLayer->m_notAudioScale = value; + // m_notAudioScale removed in GD 2.208 FMODAudioEngine::get()->m_pulse1 = value; } } @@ -1059,6 +1059,11 @@ class $modify(MyGJBaseGameLayer, GJBaseGameLayer) { if (!engine.engine_v2) engine.handle_update(this); + // processCommands signature changed in 2.208 - moved here from processCommands hook + NoclipAccuracy::get().handle_update(this, dt); + if (engine.engine_v2) + engine.handle_update(this); + hooksH::color_dt += dt * config.get("rgb_icons::speed", 0.20f); if (config.get("rgb_icons", false)) { auto& rgb_colors = RGBIcons::get(); @@ -1202,15 +1207,10 @@ class $modify(MyGJBaseGameLayer, GJBaseGameLayer) { gm->m_performanceMode = performanceMode; } - void processCommands(float dt) { + void processCommands(float dt, bool isHalfTick, bool isLastTick) { auto& config = Config::get(); - auto& engine = ReplayEngine::get(); - GJBaseGameLayer::processCommands(dt); - NoclipAccuracy::get().handle_update(this, dt); - - if (engine.engine_v2) - engine.handle_update(this); + GJBaseGameLayer::processCommands(dt, isHalfTick, isLastTick); if (config.get("show_hitboxes", false) && config.get("show_hitboxes::draw_trail", false)) { if (!m_player1->m_isDead) { diff --git a/src/labels.cpp b/src/labels.cpp index 99e132bb..ab219a4d 100644 --- a/src/labels.cpp +++ b/src/labels.cpp @@ -2,6 +2,7 @@ #include #include #include "labels.hpp" +#include #include "popupSystem.hpp" #include "replayEngine.hpp" #include "hooks.hpp" @@ -21,7 +22,7 @@ std::string Label::get_text() { auto now = std::chrono::system_clock::now(); auto steady_now = std::chrono::steady_clock::now(); std::time_t now_time_t = std::chrono::system_clock::to_time_t(now); - std::tm localTime = fmt::localtime(now_time_t); + std::tm localTime = geode::localtime(now_time_t); int hour12 = localTime.tm_hour; std::string period12 = (hour12 < 12) ? "AM" : "PM"; @@ -65,7 +66,7 @@ std::string Label::get_text() { result = replace_all(result, "{re_state}", engine.mode == state::disable ? "Disable" : engine.mode == state::record ? "Record" : "Play"); static geode::Mod* cbfMod = geode::Loader::get()->getLoadedMod("syzzi.click_between_frames"); - static bool hasCBF = cbfMod != nullptr && cbfMod->isEnabled(); + static bool hasCBF = cbfMod != nullptr && cbfMod->isLoaded(); result = replace_all(result, "{cbf_enabled}", hasCBF ? "Enabled" : "Disabled"); result = replace_all(result, "{\\n}", "\n"); @@ -437,9 +438,8 @@ void Labels::initMobileContext(geode::ScrollLayer* scrollLayer) { LabelsCreateLayer* LabelsCreateLayer::create(geode::ScrollLayer* scrollLayer) { auto ret = new LabelsCreateLayer(); - if (ret->initAnchored(360.f, 260.f, "GJ_square01.png")) { + if (ret->init(scrollLayer, 360.f, 260.f)) { ret->autorelease(); - ret->m_scrollLayer = scrollLayer; return ret; } @@ -447,7 +447,9 @@ LabelsCreateLayer* LabelsCreateLayer::create(geode::ScrollLayer* scrollLayer) { return nullptr; } -bool LabelsCreateLayer::setup() { +bool LabelsCreateLayer::init(geode::ScrollLayer* parentScrollLayer, float width, float height) { + if (!Popup::init(width, height, "GJ_square01.png")) return false; + m_scrollLayer = parentScrollLayer; std::array corners = {"Top Left", "Top Right", "Top", "Bottom Left", "Bottom Right", "Bottom"}; std::array label_types = {"Time (24H)", "Time (12H)", "Session Time", "Cheat Indicator", "FPS Counter", "Level Progress", "Attempt", "CPS Counter", "Level Info", "Noclip Accuracy", "Death Counter", "Testmode", "Replay Engine State", "CBF Status", "Rainbow Text", "Custom Text"}; diff --git a/src/labels.hpp b/src/labels.hpp index ecaa27ca..cf3e1d10 100644 --- a/src/labels.hpp +++ b/src/labels.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include "config.hpp" #include @@ -72,7 +73,7 @@ class Labels { }; -class LabelsCreateLayer : public geode::Popup<> { +class LabelsCreateLayer : public geode::Popup { private: std::vector m_toggles; std::vector m_labelTypeToggles; @@ -82,7 +83,7 @@ class LabelsCreateLayer : public geode::Popup<> { geode::ScrollLayer* m_scrollLayer; public: static LabelsCreateLayer* create(geode::ScrollLayer* scrollLayer); - bool setup(); + bool init(geode::ScrollLayer* parentScrollLayer, float width, float height); }; class NoclipAccuracy { diff --git a/src/popupSystem.cpp b/src/popupSystem.cpp index dc53cd1b..36fa3a40 100644 --- a/src/popupSystem.cpp +++ b/src/popupSystem.cpp @@ -3,7 +3,8 @@ using namespace geode::prelude; -bool popupSystem::setup() { +bool popupSystem::init(float width, float height) { + if (!Popup::init(width, height, "GJ_square01.png")) return false; this->setTitle("More Settings"); return true; @@ -11,7 +12,7 @@ bool popupSystem::setup() { popupSystem* popupSystem::create() { auto ret = new popupSystem(); - if (ret->initAnchored(300.f, 200.f, "GJ_square01.png")) { + if (ret->init(300.f, 200.f)) { ret->autorelease(); return ret; } @@ -21,7 +22,7 @@ popupSystem* popupSystem::create() { } void popupSystem::onExit() { - geode::Popup<>::onExit(); + geode::Popup::onExit(); } void popupSystem::AddText(std::string text, float scale, float y_space) { diff --git a/src/popupSystem.hpp b/src/popupSystem.hpp index 078c5802..5493dcb9 100644 --- a/src/popupSystem.hpp +++ b/src/popupSystem.hpp @@ -1,12 +1,12 @@ #pragma once #include -class popupSystem : public geode::Popup<> { +class popupSystem : public geode::Popup { private: float currentY = 160.f; public: static popupSystem* create(); - bool setup(); + bool init(float width, float height); void onExit(); void AddText(std::string text, float scale = 0.5f, float y_space = 25.f); diff --git a/src/utils.cpp b/src/utils.cpp index e4ca8b3f..2bc21b9b 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -126,7 +126,7 @@ void utilsH::UncompleteLevel() { level->m_bestTime = 0; level->m_bestPoints = 0; level->m_isCompletionLegitimate = true; - level->m_k111 = 0; + // m_k111 renamed in 2.208 bindings - field removed, skip this reset for (auto i = 0; i < level->m_coins; i++) { auto key = level->getCoinKey(i + 1); From 45f5f8a1d7ebabf21a81c9393d7fdfd47e943ffb Mon Sep 17 00:00:00 2001 From: Kosten <120324510+Kosten47@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:37:13 +0100 Subject: [PATCH 4/9] Update geode and gd versions in mod.json --- mod.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mod.json b/mod.json index 2311b15b..05835726 100644 --- a/mod.json +++ b/mod.json @@ -1,10 +1,10 @@ { - "geode": "4.6.3", + "geode": "5.0.0-beta.4", "gd": { - "win": "2.2074", - "android": "2.2074", - "mac": "2.2074", - "ios": "2.2074" + "win": "2.2081", + "android": "2.2081", + "mac": "2.2081", + "ios": "2.2081" }, "id": "tobyadd.gdh", "name": "GDH", From 06dd7c323a3faf01e25f6301d3c3217c3f53d43b Mon Sep 17 00:00:00 2001 From: Kosten <120324510+Kosten47@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:48:26 +0100 Subject: [PATCH 5/9] Add files via upload --- build/_deps/gd-imgui-cocos-src/hooks.cpp | 224 +++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 build/_deps/gd-imgui-cocos-src/hooks.cpp diff --git a/build/_deps/gd-imgui-cocos-src/hooks.cpp b/build/_deps/gd-imgui-cocos-src/hooks.cpp new file mode 100644 index 00000000..8d5fed37 --- /dev/null +++ b/build/_deps/gd-imgui-cocos-src/hooks.cpp @@ -0,0 +1,224 @@ +#include + +#include +#include +#include +#include +#include + +#include + +using namespace geode::prelude; + +#ifndef GEODE_IS_IOS +class $modify(CCMouseDispatcher) { + bool dispatchScrollMSG(float y, float x) { + if (!ImGuiCocos::get().isInitialized()) + return CCMouseDispatcher::dispatchScrollMSG(y, x); + + auto& io = ImGui::GetIO(); + static constexpr float scrollMult = 1.f / 10.f; + io.AddMouseWheelEvent(x * scrollMult, -y * scrollMult); + + if (!io.WantCaptureMouse) { + return CCMouseDispatcher::dispatchScrollMSG(y, x); + } + return true; + } +}; +#endif + +// 2.2 adds some new arguments to the dispatchers +#if GEODE_COMP_GD_VERSION >= 22000 + #define IF_2_2(...) __VA_ARGS__ +#else + #define IF_2_2(...) +#endif + +#if GEODE_COMP_GD_VERSION >= 22070 + #define IF_2_207(...) __VA_ARGS__ +#else + #define IF_2_207(...) +#endif + +#if GEODE_COMP_GD_VERSION >= 22080 + #define IF_2_208(...) __VA_ARGS__ +#else + #define IF_2_208(...) +#endif + +class $modify(CCIMEDispatcher) { + void dispatchInsertText(const char* text, int len IF_2_2(, enumKeyCodes keys)) { + if (!ImGuiCocos::get().isInitialized()) + return CCIMEDispatcher::dispatchInsertText(text, len IF_2_2(, keys)); + + auto& io = ImGui::GetIO(); + if (!io.WantCaptureKeyboard) { + CCIMEDispatcher::dispatchInsertText(text, len IF_2_2(, keys)); + } + std::string str(text, len); + io.AddInputCharactersUTF8(str.c_str()); + } + + void dispatchDeleteBackward() { + if (!ImGuiCocos::get().isInitialized()) + return CCIMEDispatcher::dispatchDeleteBackward(); + + auto& io = ImGui::GetIO(); + if (!io.WantCaptureKeyboard) { + CCIMEDispatcher::dispatchDeleteBackward(); + } + // is this really how youre supposed to do this + io.AddKeyEvent(ImGuiKey_Backspace, true); + io.AddKeyEvent(ImGuiKey_Backspace, false); + } +}; + +ImGuiKey cocosToImGuiKey(cocos2d::enumKeyCodes key) { + if (key >= KEY_A && key <= KEY_Z) { + return static_cast(ImGuiKey_A + (key - KEY_A)); + } + if (key >= KEY_Zero && key <= KEY_Nine) { + return static_cast(ImGuiKey_0 + (key - KEY_Zero)); + } + switch (key) { + case KEY_Up: return ImGuiKey_UpArrow; + case KEY_Down: return ImGuiKey_DownArrow; + case KEY_Left: return ImGuiKey_LeftArrow; + case KEY_Right: return ImGuiKey_RightArrow; + + case KEY_Control: return ImGuiKey_LeftCtrl; + case KEY_LeftWindowsKey: return ImGuiKey_LeftSuper; + case KEY_Shift: return ImGuiKey_LeftShift; + case KEY_Alt: return ImGuiKey_LeftAlt; + case KEY_Enter: return ImGuiKey_Enter; + + case KEY_Home: return ImGuiKey_Home; + case KEY_End: return ImGuiKey_End; + case KEY_Delete: return ImGuiKey_Delete; + + default: return ImGuiKey_None; + } +} + +bool shouldBlockInput() { + auto& inst = ImGuiCocos::get(); + return inst.isVisible() && inst.getInputMode() == ImGuiCocos::InputMode::Blocking; +} + +#ifndef GEODE_IS_IOS +class $modify(CCKeyboardDispatcher) { + bool dispatchKeyboardMSG(enumKeyCodes key, bool down IF_2_2(, bool repeat) IF_2_208(, double time)) { + if (!ImGuiCocos::get().isInitialized()) + return CCKeyboardDispatcher::dispatchKeyboardMSG(key, down IF_2_2(, repeat) IF_2_208(, time)); + + const bool shouldEatInput = ImGui::GetIO().WantCaptureKeyboard || shouldBlockInput(); + if (shouldEatInput || !down) { + const auto imKey = cocosToImGuiKey(key); + if (imKey != ImGuiKey_None) { + ImGui::GetIO().AddKeyEvent(imKey, down); + } + } + if (shouldEatInput) { + return false; + } else { + return CCKeyboardDispatcher::dispatchKeyboardMSG(key, down IF_2_2(, repeat) IF_2_208(, time)); + } + } +}; +#endif + +class $modify(CCTouchDispatcher) { + void touches(CCSet* touches, CCEvent* event, unsigned int type) { + if (!ImGuiCocos::get().isInitialized() || !touches) + return CCTouchDispatcher::touches(touches, event, type); + + auto& io = ImGui::GetIO(); + auto* touch = static_cast(touches->anyObject()); + + if (!touch) return CCTouchDispatcher::touches(touches, event, type); + + // add mouse source events, so imgui can handle touches right + if (geode::cocos::getMousePos().isZero()) { + // touch->getLocation() can be different from geode::cocos::getMousePos()! + const auto pos = ImGuiCocos::cocosToFrame(touch->getLocation()); + io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); + io.AddMousePosEvent(pos.x, pos.y); + } + + if (io.WantCaptureMouse || shouldBlockInput()) { + if (type == CCTOUCHBEGAN) { + io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); + io.AddMouseButtonEvent(0, true); + } else if (type == CCTOUCHENDED || type == CCTOUCHCANCELLED) { + io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); + io.AddMouseButtonEvent(0, false); + } + if (type == CCTOUCHMOVED) { + CCTouchDispatcher::touches(touches, event, CCTOUCHCANCELLED); + } + } else { + if (type != CCTOUCHMOVED) { + io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); + io.AddMouseButtonEvent(0, false); + } + CCTouchDispatcher::touches(touches, event, type); + } + } +}; + +// need imgui to be drawn inbetween glClear and swapBuffers: +// drawScene() { +// glClear(); +// draw current scene(); +// <- here! +// swapBuffers(); +// } +// swapBuffers on android and macos doesnt do anything, so hooking it might not work, +// and because it doesnt do anything just drawing imgui at the end of drawScene works fine + +#if defined(GEODE_IS_WINDOWS) || defined(GEODE_IS_IOS) + +#include + +class $modify(CCEGLView) { +#ifdef IMGUI_COCOS_HOOK_EARLY + static void onModify(auto& self) { + if (!self.setHookPriorityPre("cocos2d::CCEGLView::swapBuffers", Priority::Early)) { + log::warn("Failed to set hook priority for swapBuffers"); + } + } +#endif + + void swapBuffers() { + if (ImGuiCocos::get().isInitialized()) + ImGuiCocos::get().drawFrame(); + + CCEGLView::swapBuffers(); + } + +#ifdef GEODE_IS_WINDOWS + void toggleFullScreen(bool value IF_2_2(, bool borderless) IF_2_207(, bool fix)) { + if (!ImGuiCocos::get().isInitialized()) + return CCEGLView::toggleFullScreen(value IF_2_2(, borderless) IF_2_207(, fix)); + + ImGuiCocos::get().destroy(); + CCEGLView::toggleFullScreen(value IF_2_2(, borderless) IF_2_207(, fix)); + ImGuiCocos::get().setup(); + } +#endif +}; + +#else + +#include + +class $modify(CCDirector) { + void drawScene() { + CCDirector::drawScene(); + if (ImGuiCocos::get().isInitialized()) + ImGuiCocos::get().drawFrame(); + } +}; + +#endif \ No newline at end of file From fb65d6c1fe97d79e936a329705069922ce69a50d Mon Sep 17 00:00:00 2001 From: Kosten <120324510+Kosten47@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:48:52 +0100 Subject: [PATCH 6/9] Update mod.json --- mod.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod.json b/mod.json index 05835726..d578808d 100644 --- a/mod.json +++ b/mod.json @@ -1,5 +1,5 @@ { - "geode": "5.0.0-beta.4", + "geode": "5.0.0", "gd": { "win": "2.2081", "android": "2.2081", From 1366432f466ddf9a9cc637815f0adeec9a2c36d7 Mon Sep 17 00:00:00 2001 From: Kosten <120324510+Kosten47@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:41:32 +0100 Subject: [PATCH 7/9] Delete build directory --- build/_deps/gd-imgui-cocos-src/hooks.cpp | 224 ----------------------- 1 file changed, 224 deletions(-) delete mode 100644 build/_deps/gd-imgui-cocos-src/hooks.cpp diff --git a/build/_deps/gd-imgui-cocos-src/hooks.cpp b/build/_deps/gd-imgui-cocos-src/hooks.cpp deleted file mode 100644 index 8d5fed37..00000000 --- a/build/_deps/gd-imgui-cocos-src/hooks.cpp +++ /dev/null @@ -1,224 +0,0 @@ -#include - -#include -#include -#include -#include -#include - -#include - -using namespace geode::prelude; - -#ifndef GEODE_IS_IOS -class $modify(CCMouseDispatcher) { - bool dispatchScrollMSG(float y, float x) { - if (!ImGuiCocos::get().isInitialized()) - return CCMouseDispatcher::dispatchScrollMSG(y, x); - - auto& io = ImGui::GetIO(); - static constexpr float scrollMult = 1.f / 10.f; - io.AddMouseWheelEvent(x * scrollMult, -y * scrollMult); - - if (!io.WantCaptureMouse) { - return CCMouseDispatcher::dispatchScrollMSG(y, x); - } - return true; - } -}; -#endif - -// 2.2 adds some new arguments to the dispatchers -#if GEODE_COMP_GD_VERSION >= 22000 - #define IF_2_2(...) __VA_ARGS__ -#else - #define IF_2_2(...) -#endif - -#if GEODE_COMP_GD_VERSION >= 22070 - #define IF_2_207(...) __VA_ARGS__ -#else - #define IF_2_207(...) -#endif - -#if GEODE_COMP_GD_VERSION >= 22080 - #define IF_2_208(...) __VA_ARGS__ -#else - #define IF_2_208(...) -#endif - -class $modify(CCIMEDispatcher) { - void dispatchInsertText(const char* text, int len IF_2_2(, enumKeyCodes keys)) { - if (!ImGuiCocos::get().isInitialized()) - return CCIMEDispatcher::dispatchInsertText(text, len IF_2_2(, keys)); - - auto& io = ImGui::GetIO(); - if (!io.WantCaptureKeyboard) { - CCIMEDispatcher::dispatchInsertText(text, len IF_2_2(, keys)); - } - std::string str(text, len); - io.AddInputCharactersUTF8(str.c_str()); - } - - void dispatchDeleteBackward() { - if (!ImGuiCocos::get().isInitialized()) - return CCIMEDispatcher::dispatchDeleteBackward(); - - auto& io = ImGui::GetIO(); - if (!io.WantCaptureKeyboard) { - CCIMEDispatcher::dispatchDeleteBackward(); - } - // is this really how youre supposed to do this - io.AddKeyEvent(ImGuiKey_Backspace, true); - io.AddKeyEvent(ImGuiKey_Backspace, false); - } -}; - -ImGuiKey cocosToImGuiKey(cocos2d::enumKeyCodes key) { - if (key >= KEY_A && key <= KEY_Z) { - return static_cast(ImGuiKey_A + (key - KEY_A)); - } - if (key >= KEY_Zero && key <= KEY_Nine) { - return static_cast(ImGuiKey_0 + (key - KEY_Zero)); - } - switch (key) { - case KEY_Up: return ImGuiKey_UpArrow; - case KEY_Down: return ImGuiKey_DownArrow; - case KEY_Left: return ImGuiKey_LeftArrow; - case KEY_Right: return ImGuiKey_RightArrow; - - case KEY_Control: return ImGuiKey_LeftCtrl; - case KEY_LeftWindowsKey: return ImGuiKey_LeftSuper; - case KEY_Shift: return ImGuiKey_LeftShift; - case KEY_Alt: return ImGuiKey_LeftAlt; - case KEY_Enter: return ImGuiKey_Enter; - - case KEY_Home: return ImGuiKey_Home; - case KEY_End: return ImGuiKey_End; - case KEY_Delete: return ImGuiKey_Delete; - - default: return ImGuiKey_None; - } -} - -bool shouldBlockInput() { - auto& inst = ImGuiCocos::get(); - return inst.isVisible() && inst.getInputMode() == ImGuiCocos::InputMode::Blocking; -} - -#ifndef GEODE_IS_IOS -class $modify(CCKeyboardDispatcher) { - bool dispatchKeyboardMSG(enumKeyCodes key, bool down IF_2_2(, bool repeat) IF_2_208(, double time)) { - if (!ImGuiCocos::get().isInitialized()) - return CCKeyboardDispatcher::dispatchKeyboardMSG(key, down IF_2_2(, repeat) IF_2_208(, time)); - - const bool shouldEatInput = ImGui::GetIO().WantCaptureKeyboard || shouldBlockInput(); - if (shouldEatInput || !down) { - const auto imKey = cocosToImGuiKey(key); - if (imKey != ImGuiKey_None) { - ImGui::GetIO().AddKeyEvent(imKey, down); - } - } - if (shouldEatInput) { - return false; - } else { - return CCKeyboardDispatcher::dispatchKeyboardMSG(key, down IF_2_2(, repeat) IF_2_208(, time)); - } - } -}; -#endif - -class $modify(CCTouchDispatcher) { - void touches(CCSet* touches, CCEvent* event, unsigned int type) { - if (!ImGuiCocos::get().isInitialized() || !touches) - return CCTouchDispatcher::touches(touches, event, type); - - auto& io = ImGui::GetIO(); - auto* touch = static_cast(touches->anyObject()); - - if (!touch) return CCTouchDispatcher::touches(touches, event, type); - - // add mouse source events, so imgui can handle touches right - if (geode::cocos::getMousePos().isZero()) { - // touch->getLocation() can be different from geode::cocos::getMousePos()! - const auto pos = ImGuiCocos::cocosToFrame(touch->getLocation()); - io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); - io.AddMousePosEvent(pos.x, pos.y); - } - - if (io.WantCaptureMouse || shouldBlockInput()) { - if (type == CCTOUCHBEGAN) { - io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); - io.AddMouseButtonEvent(0, true); - } else if (type == CCTOUCHENDED || type == CCTOUCHCANCELLED) { - io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); - io.AddMouseButtonEvent(0, false); - } - if (type == CCTOUCHMOVED) { - CCTouchDispatcher::touches(touches, event, CCTOUCHCANCELLED); - } - } else { - if (type != CCTOUCHMOVED) { - io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); - io.AddMouseButtonEvent(0, false); - } - CCTouchDispatcher::touches(touches, event, type); - } - } -}; - -// need imgui to be drawn inbetween glClear and swapBuffers: -// drawScene() { -// glClear(); -// draw current scene(); -// <- here! -// swapBuffers(); -// } -// swapBuffers on android and macos doesnt do anything, so hooking it might not work, -// and because it doesnt do anything just drawing imgui at the end of drawScene works fine - -#if defined(GEODE_IS_WINDOWS) || defined(GEODE_IS_IOS) - -#include - -class $modify(CCEGLView) { -#ifdef IMGUI_COCOS_HOOK_EARLY - static void onModify(auto& self) { - if (!self.setHookPriorityPre("cocos2d::CCEGLView::swapBuffers", Priority::Early)) { - log::warn("Failed to set hook priority for swapBuffers"); - } - } -#endif - - void swapBuffers() { - if (ImGuiCocos::get().isInitialized()) - ImGuiCocos::get().drawFrame(); - - CCEGLView::swapBuffers(); - } - -#ifdef GEODE_IS_WINDOWS - void toggleFullScreen(bool value IF_2_2(, bool borderless) IF_2_207(, bool fix)) { - if (!ImGuiCocos::get().isInitialized()) - return CCEGLView::toggleFullScreen(value IF_2_2(, borderless) IF_2_207(, fix)); - - ImGuiCocos::get().destroy(); - CCEGLView::toggleFullScreen(value IF_2_2(, borderless) IF_2_207(, fix)); - ImGuiCocos::get().setup(); - } -#endif -}; - -#else - -#include - -class $modify(CCDirector) { - void drawScene() { - CCDirector::drawScene(); - if (ImGuiCocos::get().isInitialized()) - ImGuiCocos::get().drawFrame(); - } -}; - -#endif \ No newline at end of file From 726f2a05bcb155483f4799b3dc9b12c99ed2faec Mon Sep 17 00:00:00 2001 From: Kosten <120324510+Kosten47@users.noreply.github.com> Date: Mon, 23 Feb 2026 21:45:54 +0100 Subject: [PATCH 8/9] Update mod.json --- mod.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod.json b/mod.json index d578808d..4fbfd20e 100644 --- a/mod.json +++ b/mod.json @@ -8,7 +8,7 @@ }, "id": "tobyadd.gdh", "name": "GDH", - "version": "v5.0.0-beta.8", + "version": "v5.0.0-beta.9", "developer": "TobyAdd", "description": "GDH is an open-source Geometry Dash mod menu that aims to improve the game's performance and add new features", "early-load": true, From a1e59d235e29148503b3b55af9b46e221ce168b5 Mon Sep 17 00:00:00 2001 From: Kosten <120324510+Kosten47@users.noreply.github.com> Date: Mon, 23 Feb 2026 21:47:06 +0100 Subject: [PATCH 9/9] Update imgui-cocos package version --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7814f985..a73349ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE libs libs/ffmpeg) set(HAS_IMGUI ON) add_subdirectory(libs/imgui) -CPMAddPackage("gh:matcool/gd-imgui-cocos#8973953") +CPMAddPackage("gh:matcool/gd-imgui-cocos#9764333") target_link_libraries(${PROJECT_NAME} imgui-cocos imgui) @@ -38,3 +38,4 @@ if (ANDROID AND ANDROID_ABI STREQUAL "arm64-v8a") endif() setup_geode_mod(${PROJECT_NAME}) +