From 75ceba761fd0688bc71df453d27a6e75b154d84a Mon Sep 17 00:00:00 2001 From: TobyAdd <66429886+TobyAdd@users.noreply.github.com> Date: Sat, 22 Jul 2023 17:12:16 +0300 Subject: [PATCH] Beta 3! --- CMakeLists.txt | 2 - README.md | 13 +- libs/imgui-hook/imgui-hook.cpp | 2 +- libs/imgui-hook/imgui_theme.hpp | 137 +++--- src/console.cpp | 14 +- src/console.h | 1 + src/framerate.cpp | 85 ++-- src/framerate.h | 2 + src/gui.cpp | 721 +++++++++++++++++++++++++++++--- src/hacks.cpp | 75 +++- src/hacks.h | 6 + src/hooks.cpp | 122 ++++-- src/hooks.h | 19 +- src/recorder.cpp | 22 +- src/replayEngine.cpp | 205 ++++++--- src/replayEngine.h | 52 ++- 16 files changed, 1197 insertions(+), 281 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 53854ad..5190eb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,6 @@ project(${PROJECT}) file(GLOB_RECURSE SOURCE_FILES src/*.cpp) add_library(${PROJECT} SHARED ${SOURCE_FILES}) -add_compile_definitions(UNICODE _UNICODE) - target_include_directories(${PROJECT} PRIVATE libs/minhook/include libs/gd.h/include diff --git a/README.md b/README.md index ad25f1a..a4afa60 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,9 @@ ReplayEngine is a redesigned and improved project that offers more accurate macr # Download https://github.com/TobyAdd/ReplayEngine/releases # How to install -You need to inject a DLL into Geometry Dash. You can use [Cheat Engine](https://www.cheatengine.org/index.php) or [another injector](https://www.google.com/search?q=dll+injector). Do not inject it with Mega Hack as it may not work properly. You can use [GDDLLLoader](https://github.com/adafcaefc/GDDllLoader) to inject it -Press K to open UI -# Screenshot -![image](https://github.com/TobyAdd/ReplayEngine/assets/66429886/f31b093e-eb6d-4718-918b-0d7cdca19c29) +You need to inject a DLL into Geometry Dash. You can use [Cheat Engine](https://www.cheatengine.org/index.php) or [another injector](https://www.google.com/search?q=dll+injector). Do not inject it with Mega Hack as it may not work properly. You can use [GDDLLLoader](https://github.com/adafcaefc/GDDllLoader) to inject it +# Screenshot (design is not finished yet) +![sc](https://user-images.githubusercontent.com/66429886/233846048-7295ea54-f768-48b5-879a-51904c066a1a.png) # Features - Accurate replay recording - Intergrated FPS Bypass and Speedhack @@ -16,10 +15,6 @@ Press K to open UI - Spam Bot - Straight Fly Bot - Dual Clicks -- Internal Recorder -- Hacks # Special Thanks [@FireMario211](https://github.com/FireMario211) - For the development of the GD Mod example. -[@HJfod](https://github.com/HJfod) - Help with some coding stuff. -[@matcool](https://github.com/matcool) - Internal Recorder -[@absoIute](https://github.com/absoIute) - Hacks +[@HJfod](https://github.com/HJfod) - Help with some coding stuff. diff --git a/libs/imgui-hook/imgui-hook.cpp b/libs/imgui-hook/imgui-hook.cpp index c8c6d3a..c15228b 100644 --- a/libs/imgui-hook/imgui-hook.cpp +++ b/libs/imgui-hook/imgui-hook.cpp @@ -55,8 +55,8 @@ void __fastcall CCEGLView_swapBuffers_H(CCEGLView* self) ImGuiIO& io = ImGui::GetIO(); io.Fonts->AddFontFromMemoryTTF(fontData, sizeof(fontData), 14.f); io.IniFilename = nullptr; - ApplyColor(); ApplyStyle(); + ApplyColor(); hWnd = WindowFromDC(*reinterpret_cast(reinterpret_cast(self->getWindow()) + 0x244)); originalWndProc = (WNDPROC)GetWindowLongPtr(hWnd, GWLP_WNDPROC); SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)HookedWndProc); diff --git a/libs/imgui-hook/imgui_theme.hpp b/libs/imgui-hook/imgui_theme.hpp index 01079b2..14b563a 100644 --- a/libs/imgui-hook/imgui_theme.hpp +++ b/libs/imgui-hook/imgui_theme.hpp @@ -1,63 +1,90 @@ #include "imgui/imgui.h" void ApplyColor() { - ImVec4 *colors = ImGui::GetStyle().Colors; - colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); - colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); - colors[ImGuiCol_WindowBg] = ImVec4(0.14f, 0.16f, 0.18f, 1.00f); - colors[ImGuiCol_ChildBg] = ImVec4(0.14f, 0.16f, 0.18f, 0.00f); - colors[ImGuiCol_PopupBg] = ImVec4(0.15f, 0.17f, 0.18f, 1.00f); - colors[ImGuiCol_Border] = ImVec4(0.83f, 0.83f, 0.84f, 0.47f); - colors[ImGuiCol_BorderShadow] = ImVec4(0.83f, 0.83f, 0.84f, 0.00f); - colors[ImGuiCol_FrameBg] = ImVec4(0.22f, 0.24f, 0.26f, 1.00f); - colors[ImGuiCol_FrameBgHovered] = ImVec4(0.31f, 0.33f, 0.34f, 1.00f); - colors[ImGuiCol_FrameBgActive] = ImVec4(0.40f, 0.41f, 0.42f, 1.00f); - colors[ImGuiCol_TitleBg] = ImVec4(0.22f, 0.24f, 0.26f, 1.00f); - colors[ImGuiCol_TitleBgActive] = ImVec4(0.22f, 0.24f, 0.26f, 1.00f); - colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.22f, 0.24f, 0.26f, 1.00f); - colors[ImGuiCol_MenuBarBg] = ImVec4(0.20f, 0.22f, 0.23f, 1.00f); - colors[ImGuiCol_ScrollbarBg] = ImVec4(0.14f, 0.16f, 0.18f, 1.00f); - colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f); - colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); - colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); - colors[ImGuiCol_CheckMark] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); - colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.59f); - colors[ImGuiCol_SliderGrabActive] = ImVec4(1.00f, 1.00f, 1.00f, 0.59f); - colors[ImGuiCol_Button] = ImVec4(0.22f, 0.24f, 0.26f, 0.90f); - colors[ImGuiCol_ButtonHovered] = ImVec4(0.31f, 0.33f, 0.34f, 0.90f); - colors[ImGuiCol_ButtonActive] = ImVec4(0.40f, 0.41f, 0.42f, 0.90f); - colors[ImGuiCol_Header] = ImVec4(0.22f, 0.24f, 0.26f, 1.00f); - colors[ImGuiCol_HeaderHovered] = ImVec4(0.31f, 0.33f, 0.34f, 1.00f); - colors[ImGuiCol_HeaderActive] = ImVec4(0.40f, 0.41f, 0.42f, 1.00f); - colors[ImGuiCol_Separator] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); - colors[ImGuiCol_SeparatorHovered] = ImVec4(0.31f, 0.33f, 0.34f, 1.00f); - colors[ImGuiCol_SeparatorActive] = ImVec4(0.40f, 0.41f, 0.42f, 1.00f); - colors[ImGuiCol_ResizeGrip] = ImVec4(0.22f, 0.24f, 0.26f, 1.00f); - colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.31f, 0.33f, 0.34f, 1.00f); - colors[ImGuiCol_ResizeGripActive] = ImVec4(0.40f, 0.41f, 0.42f, 1.00f); - colors[ImGuiCol_Tab] = ImVec4(0.22f, 0.24f, 0.26f, 1.00f); - colors[ImGuiCol_TabHovered] = ImVec4(0.31f, 0.33f, 0.34f, 1.00f); - colors[ImGuiCol_TabActive] = ImVec4(0.31f, 0.33f, 0.34f, 1.00f); - colors[ImGuiCol_TabUnfocused] = ImVec4(0.22f, 0.24f, 0.26f, 1.00f); - colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.22f, 0.24f, 0.26f, 1.00f); - colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); - colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); - colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); - colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); - colors[ImGuiCol_TableHeaderBg] = ImVec4(0.22f, 0.24f, 0.26f, 1.00f); - colors[ImGuiCol_TableBorderStrong] = ImVec4(1.00f, 1.00f, 1.00f, 0.47f); - colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f); - colors[ImGuiCol_TableRowBg] = ImVec4(0.22f, 0.24f, 0.26f, 1.00f); - colors[ImGuiCol_TableRowBgAlt] = ImVec4(0.22f, 0.24f, 0.26f, 1.00f); - colors[ImGuiCol_TextSelectedBg] = ImVec4(0.40f, 0.41f, 0.42f, 1.00f); - colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); - colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); - colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); - colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); - colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); + auto* colors = ImGui::GetStyle().Colors; + colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.04f, 0.15f, 0.28f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.04f, 0.15f, 0.28f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.04f, 0.15f, 0.28f, 1.00f); + colors[ImGuiCol_Border] = ImVec4(0.17f, 0.45f, 0.70f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.17f, 0.45f, 0.70f, 0.50f); + colors[ImGuiCol_FrameBg] = ImVec4(0.13f, 0.26f, 0.45f, 0.54f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.20f, 0.39f, 0.70f, 0.54f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.17f, 0.45f, 0.70f, 0.54f); + colors[ImGuiCol_TitleBg] = ImVec4(0.13f, 0.26f, 0.45f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.13f, 0.26f, 0.45f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.13f, 0.26f, 0.45f, 1.00f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.13f, 0.26f, 0.45f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.04f, 0.15f, 0.28f, 1.00f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.17f, 0.45f, 0.70f, 0.54f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.24f, 0.56f, 0.70f, 0.54f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.17f, 0.45f, 0.70f, 0.54f); + colors[ImGuiCol_CheckMark] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.50f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(1.00f, 1.00f, 1.00f, 0.50f); + colors[ImGuiCol_Button] = ImVec4(0.13f, 0.26f, 0.45f, 0.54f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.20f, 0.39f, 0.70f, 0.54f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.17f, 0.45f, 0.70f, 0.54f); + colors[ImGuiCol_Header] = ImVec4(0.13f, 0.26f, 0.45f, 0.54f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.20f, 0.39f, 0.70f, 0.54f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.17f, 0.45f, 0.70f, 0.54f); + colors[ImGuiCol_Separator] = ImVec4(0.17f, 0.45f, 0.70f, 0.50f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.17f, 0.45f, 0.70f, 0.50f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.17f, 0.45f, 0.70f, 0.50f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.13f, 0.26f, 0.45f, 0.54f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.20f, 0.39f, 0.70f, 0.54f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.17f, 0.45f, 0.70f, 0.50f); + colors[ImGuiCol_Tab] = ImVec4(0.13f, 0.26f, 0.45f, 0.54f); + colors[ImGuiCol_TabHovered] = ImVec4(0.20f, 0.39f, 0.70f, 0.54f); + colors[ImGuiCol_TabActive] = ImVec4(0.08f, 0.33f, 0.58f, 1.00f); + colors[ImGuiCol_TabUnfocused] = ImVec4(0.10f, 0.09f, 0.14f, 1.00f); + colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.13f, 0.26f, 0.45f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.38f, 0.38f, 0.38f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.26f, 0.21f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.56f, 0.32f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.38f, 0.00f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.08f, 0.26f, 0.45f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.08f, 0.26f, 0.45f, 1.00f); + colors[ImGuiCol_TableBorderLight] = ImVec4(0.08f, 0.26f, 0.45f, 1.00f); + colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.15f, 0.35f, 0.60f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = ImVec4(0.15f, 0.35f, 0.60f, 1.00f); + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); + colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); } void ApplyStyle() { ImGuiStyle& style = ImGui::GetStyle(); - style.WindowBorderSize = 0; + style.WindowPadding = ImVec2(8.00f, 8.00f); + style.FramePadding = ImVec2(4.00f, 4.00f); + style.CellPadding = ImVec2(4.00f, 2.00f); + style.ItemSpacing = ImVec2(4.00f, 4.00f); + style.ItemInnerSpacing = ImVec2(4.00f, 4.00f); + style.TouchExtraPadding = ImVec2(0.00f, 0.00f); + style.IndentSpacing = 21.00f; + style.ScrollbarSize = 16.00f; + style.GrabMinSize = 12.00f; + style.WindowBorderSize = 1.00f; + style.ChildBorderSize = 1.00f; + style.PopupBorderSize = 1.00f; + style.FrameBorderSize = 0.00f; + style.TabBorderSize = 0.00f; + style.WindowRounding = 6.00f; + style.ChildRounding = 6.00f; + style.FrameRounding = 6.00f; + style.PopupRounding = 6.00f; + style.ScrollbarRounding = 6.00f; + style.GrabRounding = 6.00f; + style.LogSliderDeadzone = 6.00f; + style.TabRounding = 6.00f; + style.WindowTitleAlign = ImVec2(0.50f, 0.50f); + style.WindowMenuButtonPosition = 0; + style.ColorButtonPosition = 1; + style.ButtonTextAlign = ImVec2(0.50f, 0.50f); + style.SelectableTextAlign = ImVec2(0.00f, 0.00f); + style.DisplaySafeAreaPadding = ImVec2(3.00f, 3.00f); } \ No newline at end of file diff --git a/src/console.cpp b/src/console.cpp index 02f59ca..b77a8d9 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -3,6 +3,8 @@ namespace Console { + bool console_inited = false; + void Console::Write(string text) { freopen("CONOUT$", "w", stdout); @@ -42,6 +44,16 @@ namespace Console void Console::Init() { - AllocConsole(); + if (!console_inited) { + console_inited = true; + AllocConsole(); + HWND hwndConsole = GetConsoleWindow(); + if (hwndConsole != NULL) + { + EnableMenuItem(GetSystemMenu(hwndConsole, FALSE), SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED); + } + + } + } } \ No newline at end of file diff --git a/src/console.h b/src/console.h index d954508..fb3e956 100644 --- a/src/console.h +++ b/src/console.h @@ -6,6 +6,7 @@ using namespace std; namespace Console { + extern bool console_inited; void Write(string text); void WriteLine(string text); string Input(); diff --git a/src/framerate.cpp b/src/framerate.cpp index 0bba518..0df448a 100644 --- a/src/framerate.cpp +++ b/src/framerate.cpp @@ -3,67 +3,64 @@ #include "replayEngine.h" #include "recorder.hpp" + +bool keyPressed = false; +bool isFirstPress = true; +std::chrono::steady_clock::time_point lastKeyPressTime; + namespace framerate { bool g_disable_render = false; float g_left_over = 0.f; + bool enabled = true; + bool enabled_fps = true; void(__thiscall *CCScheduler_update)(CCScheduler *, float); - void __fastcall CCScheduler_update_H(CCScheduler *self, int, float dt) - { - auto pl = gd::GameManager::sharedState()->getPlayLayer(); - auto el = gd::GameManager::sharedState()->getEditorLayer(); - if (pl || el || recorder.m_recording) - { - if (pl && frameAdvance.enabled && frameAdvance.triggered) - { - float newdt = 1.f / replay.fps_value / 1.f; - CCScheduler_update(self, newdt); - frameAdvance.triggered = false; - return; - } + void __fastcall CCScheduler_update_H(CCScheduler* self, int, float dt) { + const auto play_layer = gd::GameManager::sharedState()->getPlayLayer(); + if (enabled && play_layer && (replay.mode == record || replay.mode == play || !play_layer->m_isPaused || recorder.m_recording)) { + dt *= replay.speed_value; + + const float target_dt = 1.f / replay.fps_value; + + if (enabled_fps) + CCDirector::sharedDirector()->setAnimationInterval(target_dt); + + if (!replay.real_time) + return CCScheduler_update(self, target_dt); - if (!frameAdvance.enabled) - { - dt *= replay.speed_value; - const float newdt = 1.f / replay.fps_value / 1.f; - if (!replay.real_time) - return CCScheduler_update(self, newdt); - g_disable_render = false; + if (frameAdvance.enabled) { + if (frameAdvance.triggered) { + frameAdvance.triggered = false; + return CCScheduler_update(self, target_dt); + } + else { + return; + } + } - unsigned times = static_cast((dt + g_left_over) / newdt); - if (dt == 0.f) - return CCScheduler_update(self, newdt); - auto start = std::chrono::high_resolution_clock::now(); - for (unsigned i = 0; i < times; ++i) { - CCScheduler_update(self, newdt); - using namespace std::literals; - if (std::chrono::high_resolution_clock::now() - start > 33.333ms) { - times = i + 1; - break; - } + unsigned times = static_cast((dt + g_left_over) / target_dt); + if (dt == 0.f) + return CCScheduler_update(self, target_dt); + auto start = std::chrono::high_resolution_clock::now(); + for (unsigned i = 0; i < times; ++i) { + CCScheduler_update(self, target_dt); + using namespace std::literals; + if (std::chrono::high_resolution_clock::now() - start > 33.333ms) { + times = i + 1; + break; } - g_left_over += dt - newdt * times; } + g_left_over += dt - target_dt * times; + } else { + CCScheduler_update(self, dt); } - else - { - return CCScheduler_update(self, dt); - } - } - - void(__thiscall *PlayLayer_updateVisibility)(void *); - void __fastcall PlayLayer_updateVisibility_H(void *self) - { - if (!g_disable_render) - PlayLayer_updateVisibility(self); } void initHooks() { auto cocos = GetModuleHandleA("libcocos2d.dll"); - MH_CreateHook((void *)(gd::base + 0x205460), PlayLayer_updateVisibility_H, (void **)&PlayLayer_updateVisibility); MH_CreateHook(GetProcAddress(cocos, "?update@CCScheduler@cocos2d@@UAEXM@Z"), CCScheduler_update_H, (void **)&CCScheduler_update); } } \ No newline at end of file diff --git a/src/framerate.h b/src/framerate.h index 0f11cfd..a21f19e 100644 --- a/src/framerate.h +++ b/src/framerate.h @@ -3,4 +3,6 @@ namespace framerate { void initHooks(); + extern bool enabled; + extern bool enabled_fps; } \ No newline at end of file diff --git a/src/gui.cpp b/src/gui.cpp index aa3107c..ce46381 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -9,6 +9,7 @@ #include "hooks.h" #include "hacks.h" #include "recorder.hpp" +#include "framerate.h" static int currentTab = 0; bool gui::show = false; @@ -57,12 +58,7 @@ void SelectReplay() auto itemRectMax = ImGui::GetItemRectMax(); auto itemRectSize = ImGui::GetItemRectSize(); - ImVec2 inputTextPos = itemRectMax; - ImGui::GetWindowDrawList()->AddLine(ImVec2(inputTextPos.x, inputTextPos.y - itemRectSize.y - 1), - ImVec2(inputTextPos.x, inputTextPos.y - 1), - ImColor(255, 255, 255, int(ImGui::GetStyle().Alpha * 255))); - - ImGui::SameLine(0, 0); + ImGui::SameLine(); if (ImGui::ArrowButton("##comboopen", opennedSP ? ImGuiDir_Up : ImGuiDir_Down)) { @@ -84,7 +80,7 @@ void SelectReplay() if (opennedSP) { ImGui::SetNextWindowPos(ImVec2(itemRectMin.x, itemRectMax.y + 4)); - ImGui::SetNextWindowSize(ImVec2(itemRectSize.x + ImGui::GetItemRectSize().x, NULL)); + ImGui::SetNextWindowSize(ImVec2(itemRectSize.x + ImGui::GetItemRectSize().x + 5, 300)); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1); ImGui::Begin("##replaylist", 0, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); for (int i = 0; i < (int)replay_list.size(); i++) @@ -100,51 +96,42 @@ void SelectReplay() } } -char *items[] = {"General", "Assist", "Hacks", "Recorder", "About"}; +char *items[] = {"General", "Assist", "Hacks", "Editor", "Recorder", "Converter", "Sequence", "Clickbot (Beta)", "About"}; int items_index = 0; -void gui::BeginWindow(const char *title) -{ - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - ImGui::Begin(title, nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize); - ImGui::PopStyleVar(); - ImVec2 pos = ImGui::GetCursorScreenPos(); - ImVec2 endPos = ImVec2(pos.x + ImGui::GetContentRegionAvailWidth(), pos.y + 4); - ImDrawList *drawList = ImGui::GetWindowDrawList(); - ImU32 startColor = ImColor(76, 81, 84, int(ImGui::GetStyle().Alpha * 255)); - ImU32 endColor = ImColor(145, 148, 150, int(ImGui::GetStyle().Alpha * 255)); - drawList->AddRectFilledMultiColor(pos, endPos, startColor, endColor, endColor, startColor); - ImGui::Spacing(); -} +int editor_indexInputs = 0; +int editor_indexPhysics = 0; +char *items_editor[] = {"Inputs", "Physics"}; +int itemsEditor_index = 0; + +char *converterTypes[] = {"Plain Text (.txt)"}; void gui::Render() { MetaRender(); if (gui::show) - { + { if (!gui::inited) { gui::inited = true; ImGui::SetNextWindowSize(ImVec2(600, 400)); ImVec2 center = ImGui::GetMainViewport()->GetCenter(); ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - hacks::load(); + hacks::load(); } - BeginWindow("Replay Engine"); - //dont know the best way to reinit imgui window stuff when fullscreen toggle happens - ImVec2 windowSize = ImGui::GetWindowSize(); - if (windowSize.x != 600 || windowSize.y != 400) { - gui::inited = false; - } + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::Begin("Replay Engine", nullptr, ImGuiWindowFlags_NoTitleBar); + ImGui::PopStyleVar(); ImGui::PushStyleColor(ImGuiCol_Border, 1); ImGui::BeginChild("##menu", ImGui::GetContentRegionAvail(), true); ImGui::PopStyleColor(); - - ImGui::BeginChild("##leftside", ImVec2(150, NULL)); + ImGui::BeginChild("##leftsideMain", ImVec2(150, NULL), false, ImGuiWindowFlags_NoScrollbar); + + ImGui::BeginChild("##leftside", ImVec2(150, (items_index == 3) ? (ImGui::GetContentRegionAvail().y - 40) : NULL), false); ImGui::Text("Replay Engine"); ImGui::Separator(); ImGui::Spacing(); @@ -156,6 +143,20 @@ void gui::Render() } ImGui::EndChild(); + if (items_index == 3) + { + ImGui::BeginChild("##leftside2", ImVec2(150, NULL), false); + for (int i = 0; i < IM_ARRAYSIZE(items_editor); i++) + { + bool is_selected = (itemsEditor_index == i); + if (ImGui::Selectable(items_editor[i], is_selected)) + itemsEditor_index = i; + } + ImGui::EndChild(); + } + + ImGui::EndChild(); + ImGui::SameLine(0, 5); ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); ImGui::SameLine(0, 5); @@ -183,6 +184,14 @@ void gui::Render() if (ImGui::RadioButton("Play", &mode, 2)) replay.mode = (state)mode; + + ImGui::SameLine(); + + if (ImGui::RadioButton("Continue", &mode, 3)) + replay.mode = (state)mode; + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("This mode will allow you to continue recording level. First, you need to load unfinished replay and switch to the \"Continue\" mode\nAfter the next attempt, the mode will switch to \"Play\", and when the level reaches the frame that is the last frame of the replay, the mode will switch to \"Record\"\nPractice mode is activated automatically after switching to the \"Continue\" recording mode. Additionally, you can place checkpoints"); + ImGui::Separator(); ImGui::InputText("##ReplayName", replay.replay_name, IM_ARRAYSIZE(replay.replay_name)); @@ -226,11 +235,9 @@ void gui::Render() ImGui::PushItemWidth(137.f); ImGui::DragFloat("##Speed", &replay.speed_value, 0.01f, 0.f, FLT_MAX, "Speed: %.2f"); - ImGui::Separator(); - bool alwaysTrue = true; - ImGui::Checkbox("Practice Fix", &alwaysTrue); + ImGui::Checkbox("Practice Fix", &replay.practice_fix); ImGui::SameLine(); ImGui::Checkbox("Accuracy Fix", &replay.accuracy_fix); @@ -243,6 +250,14 @@ void gui::Render() ImGui::Separator(); + ImGui::Checkbox("FPS Bypass", &framerate::enabled); + ImGui::SameLine(); + ImGui::Checkbox("FPS Multiplier", &framerate::enabled_fps); + ImGui::SameLine(); + ImGui::Checkbox("Orb Fix", &practiceFix.orb_fix); + + ImGui::Separator(); + ImGui::Checkbox("Ignore Inputs on Playing", &replay.ignore_input); ImGui::SameLine(); ImGui::Checkbox("Real Time", &replay.real_time); @@ -267,6 +282,7 @@ void gui::Render() ImGui::Separator(); ImGui::Text("Frame: %i\nReplay Size: %i", replay.get_frame(), replay.replay2.size()); + ImGui::PushItemWidth(135.f); ImGui::End(); } else if (items_index == 1) @@ -322,32 +338,267 @@ void gui::Render() } ImGui::SameLine(); - ImGui::DragInt("##StraightFlyAcc", &straightFly.accuracy, 1, 0, 100, "Accuracy: %i"); + ImGui::DragInt("##StraightFlyAcc", &straightFly.accuracy, 1, 0, 100, "Y Accuracy: %i"); ImGui::Text("Note: Straight Fly Bot works only on first player"); + + ImGui::Separator(); + + ImGui::Text("Noclip"); + ImGui::Checkbox("Player 1##2", &hacks::noclipP1); + ImGui::SameLine(); + ImGui::Checkbox("Player 2##2", &hacks::noclipP2); + + ImGui::Separator(); + if (ImGui::Checkbox("Disable achievements", &hacks::disable_achievements)) + { + hacks::disable_achievements_f(hacks::disable_achievements); + } + // ImGui::Separator(); + // if (ImGui::Button("Crash GD")) { + // exit(-1); + // } } else if (items_index == 2) { hacks::render(); } else if (items_index == 3) + { + auto size = ImGui::GetWindowSize(); + auto pos = ImGui::GetWindowPos(); + ImGui::SetNextWindowPos({size.x + pos.x + 20, pos.y - 8}); + ImGui::SetNextWindowSize({185, (float)(itemsEditor_index == 0 ? 180 : 250)}); + ImGui::Begin("Editor", 0, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + if (itemsEditor_index == 0) + { + if (!replay.replay2.empty()) + { + if (ImGui::Button("Add Action") && editor_indexInputs >= 0 && editor_indexInputs <= int(replay.replay2.size())) + { + if (!replay.replay2.empty() || editor_indexInputs != replay.replay2.size()) + { + replay.replay2.insert(replay.replay2.begin() + editor_indexInputs + 1, {(unsigned)editor_indexInputs + 1, false, true}); + editor_indexInputs++; + } + } + + ImGui::SameLine(); + + if (ImGui::Button("Remove Action") && editor_indexInputs >= 0 && editor_indexInputs < int(replay.replay2.size())) + { + replay.replay2.erase(replay.replay2.begin() + editor_indexInputs); + if (editor_indexInputs > 0) + { + editor_indexInputs--; + } + } + + if (ImGui::Button("Move Up", {75, NULL}) && editor_indexInputs > 0) + { + swap(replay.replay2[editor_indexInputs], replay.replay2[editor_indexInputs - 1]); + editor_indexInputs = (editor_indexInputs - 1 >= 0) ? (editor_indexInputs - 1) : 0; + } + + ImGui::SameLine(); + + if (ImGui::Button("Move Down", {90, NULL}) && editor_indexInputs < int(replay.replay2.size()) - 1) + { + swap(replay.replay2[editor_indexInputs], replay.replay2[editor_indexInputs + 1]); + editor_indexInputs = (editor_indexInputs + 1 < int(replay.replay2.size())) ? (editor_indexInputs + 1) : (int(replay.replay2.size()) - 1); + } + + ImGui::Separator(); + int frame = replay.replay2[editor_indexInputs].frame; + ImGui::PushItemWidth(165.f); + if (ImGui::DragInt("##frame_editor", &frame, 1, 0, INT_MAX, "Frame %i")) + { + replay.replay2[editor_indexInputs].frame = frame; + } + + ImGui::Checkbox(replay.replay2[editor_indexInputs].hold ? "Push" : "Release", &replay.replay2[editor_indexInputs].hold); + ImGui::Checkbox(replay.replay2[editor_indexInputs].player ? "First" : "Second", &replay.replay2[editor_indexInputs].player); + } + else + { + ImGui::Text("No Actions!"); + if (ImGui::Button("Add action")) + { + replay.replay2.push_back({0, false, true}); + } + } + } + else if (itemsEditor_index == 1) + { + if (!replay.replay.empty()) + { + if (ImGui::Button("Add Action") && editor_indexPhysics >= 0 && editor_indexPhysics <= int(replay.replay.size())) + { + if (!replay.replay.empty() || editor_indexPhysics != replay.replay.size()) + { + replay.replay.insert(replay.replay.begin() + editor_indexPhysics + 1, {(unsigned)editor_indexPhysics + 1, 0, 0, 0, 0, true}); + editor_indexPhysics++; + } + } + + ImGui::SameLine(); + + if (ImGui::Button("Remove Action") && editor_indexPhysics >= 0 && editor_indexPhysics < int(replay.replay.size())) + { + replay.replay.erase(replay.replay.begin() + editor_indexPhysics); + if (editor_indexPhysics > 0) + { + editor_indexPhysics--; + } + } + + if (ImGui::Button("Move Up", {75, NULL}) && editor_indexPhysics > 0) + { + swap(replay.replay[editor_indexPhysics], replay.replay[editor_indexPhysics - 1]); + editor_indexPhysics = (editor_indexPhysics - 1 >= 0) ? (editor_indexPhysics - 1) : 0; + } + + ImGui::SameLine(); + + if (ImGui::Button("Move Down", {90, NULL}) && editor_indexPhysics < int(replay.replay.size()) - 1) + { + swap(replay.replay[editor_indexPhysics], replay.replay[editor_indexPhysics + 1]); + editor_indexPhysics = (editor_indexPhysics + 1 < int(replay.replay.size())) ? (editor_indexPhysics + 1) : (int(replay.replay.size()) - 1); + } + + ImGui::Separator(); + int frame = replay.replay[editor_indexPhysics].frame; + ImGui::PushItemWidth(165.f); + if (ImGui::DragInt("##frame_editor", &frame, 1, 0, INT_MAX, "Frame %i")) + { + replay.replay[editor_indexPhysics].frame = frame; + } + + ImGui::DragFloat("##x_editor", &replay.replay[editor_indexPhysics].x, 0.000010f, 0, FLT_MAX, "X Position %f"); + ImGui::DragFloat("##y_editor", &replay.replay[editor_indexPhysics].y, 0.000010f, 0, FLT_MAX, "Y Position %f"); + ImGui::DragFloat("##rotation_editor", &replay.replay[editor_indexPhysics].rotation, 0.000010f, 0, FLT_MAX, "Rotation %f"); + + float y_accel = static_cast(replay.replay[editor_indexPhysics].y_accel); + if (ImGui::DragFloat("##yaccel_editor", &y_accel, 0.000010f, 0, FLT_MAX, "Y Accel: %f")) + { + replay.replay[editor_indexPhysics].y_accel = y_accel; + } + + ImGui::Checkbox(replay.replay[editor_indexPhysics].player ? "First" : "Second", &replay.replay[editor_indexPhysics].player); + } + else + { + ImGui::Text("No Actions!"); + if (ImGui::Button("Add action")) + { + replay.replay.push_back({0, 0, 0, 0, 0, true}); + } + } + } + + ImGui::End(); + + if (itemsEditor_index == 0) + { + if (ImGui::BeginTable("##editor_table", 3, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Borders | 2)) + { + ImGui::TableNextColumn(); + ImGui::TableHeader("Frame"); + ImGui::TableNextColumn(); + ImGui::TableHeader("Player"); + ImGui::TableNextColumn(); + ImGui::TableHeader("Action"); + for (size_t i = 0; i < replay.replay2.size(); i++) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + bool is_selected = (editor_indexInputs == i); + string frame = to_string(replay.replay2[i].frame) + "##" + to_string(i); + if (ImGui::Selectable(frame.c_str(), is_selected, ImGuiSelectableFlags_SpanAllColumns)) + { + editor_indexInputs = i; + } + ImGui::TableNextColumn(); + ImGui::Text(replay.replay2[i].player ? "First" : "Second"); + ImGui::TableNextColumn(); + ImGui::Text(replay.replay2[i].hold ? "Push" : "Release"); + } + ImGui::EndTable(); + } + } + if (itemsEditor_index == 1) + { + if (ImGui::BeginTable("##editor_table", 6, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Borders | ImGuiWindowFlags_NoResize)) + { + ImGui::TableNextColumn(); + ImGui::TableHeader("Frame"); + ImGui::TableNextColumn(); + ImGui::TableHeader("Position X"); + ImGui::TableNextColumn(); + ImGui::TableHeader("Position Y"); + ImGui::TableNextColumn(); + ImGui::TableHeader("Rotation"); + ImGui::TableNextColumn(); + ImGui::TableHeader("Y Accel"); + ImGui::TableNextColumn(); + ImGui::TableHeader("Player"); + for (size_t i = 0; i < replay.replay.size(); i++) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + bool is_selected = (editor_indexPhysics == i); + string frame = to_string(replay.replay[i].frame) + "##" + to_string(i); + if (ImGui::Selectable(frame.c_str(), is_selected, ImGuiSelectableFlags_SpanAllColumns)) + { + editor_indexPhysics = i; + } + ImGui::TableNextColumn(); + ImGui::Text("%f", replay.replay[i].x); + ImGui::TableNextColumn(); + ImGui::Text("%f", replay.replay[i].y); + ImGui::TableNextColumn(); + ImGui::Text("%f", replay.replay[i].rotation); + ImGui::TableNextColumn(); + ImGui::Text("%f", replay.replay[i].y_accel); + ImGui::TableNextColumn(); + ImGui::Text(replay.replay[i].player ? "First" : "Second"); + } + ImGui::EndTable(); + } + } + } + else if (items_index == 4) { auto pl = gd::GameManager::sharedState()->getPlayLayer(); - if (!pl) { + if (!pl) + { ImGui::Text("Enter the level to record it"); } - else { + else + { ImGui::Text("Action"); ImGui::Separator(); static bool isRecording = false; - if (ImGui::Checkbox("Record", &isRecording)) { - if (isRecording) { - recorder.start("ReplayEngine/Videos/" + string(recorder.video_name)); + if (ImGui::Checkbox("Record", &isRecording)) + { + if (filesystem::exists("ffmpeg.exe")) + { + Console::Init(); + if (isRecording) + { + recorder.start("ReplayEngine/Videos/" + string(recorder.video_name)); + } + else + { + recorder.stop(); + } } - else { - recorder.stop(); + else + { + gd::FLAlertLayer::create(nullptr, "Info", "Ok", nullptr, "ffmpeg.exe doesn't exits")->show(); + isRecording = false; } - } ImGui::SameLine(); @@ -355,14 +606,14 @@ void gui::Render() ImGui::Checkbox("Render Until the End", &recorder.m_until_end); ImGui::SameLine(); - ImGui::Checkbox("Include Audio", &recorder.m_include_audio); - ImGui::PushItemWidth(260); + ImGui::Checkbox("Include Audio", &recorder.m_include_audio); + ImGui::PushItemWidth(260); ImGui::InputText("##replay_name", recorder.video_name, IM_ARRAYSIZE(recorder.video_name)); ImGui::Spacing(); ImGui::Text("Resolution:"); - ImGui::Separator(); + ImGui::Separator(); ImGui::PushItemWidth(40); ImGui::InputInt("##width", &recorder.m_width, 0); @@ -384,12 +635,13 @@ void gui::Render() ImGui::Spacing(); ImGui::Text("Encoding Settings"); - ImGui::Separator(); + ImGui::Separator(); char bitrate[128]; strcpy_s(bitrate, recorder.m_bitrate.c_str()); ImGui::PushItemWidth(50); - if (ImGui::InputText("Bitrate", bitrate, sizeof(bitrate))) { + if (ImGui::InputText("Bitrate", bitrate, sizeof(bitrate))) + { recorder.m_bitrate = string(bitrate); } @@ -398,49 +650,400 @@ void gui::Render() char codec[128]; strcpy_s(codec, recorder.m_codec.c_str()); ImGui::PushItemWidth(80); - if (ImGui::InputText("Codec", codec, sizeof(codec))) { + if (ImGui::InputText("Codec", codec, sizeof(codec))) + { recorder.m_codec = string(codec); } char extra_args[512]; strcpy_s(extra_args, recorder.m_extra_args.c_str()); ImGui::PushItemWidth(260); - if (ImGui::InputText("Extra Arguments", extra_args, sizeof(extra_args))) { + if (ImGui::InputText("Extra Arguments", extra_args, sizeof(extra_args))) + { recorder.m_extra_args = string(extra_args); } char extra_args_audio[512]; strcpy_s(extra_args_audio, recorder.m_extra_audio_args.c_str()); ImGui::PushItemWidth(260); - if (ImGui::InputText("Extra Arguments (Audio)", extra_args_audio, sizeof(extra_args_audio))) { + if (ImGui::InputText("Extra Arguments (Audio)", extra_args_audio, sizeof(extra_args_audio))) + { recorder.m_extra_audio_args = string(extra_args_audio); } ImGui::Spacing(); ImGui::Text("Level Settings"); - ImGui::Separator(); + ImGui::Separator(); ImGui::InputFloat("Second to Render After", &recorder.m_after_end_duration, 1); + ImGui::Spacing(); + + ImGui::Text("Presets"); + ImGui::Separator(); + + if (ImGui::Button("HD")) + { + recorder.m_width = 1280; + recorder.m_height = 720; + recorder.m_fps = 60; + recorder.m_bitrate = "15M"; + } + + ImGui::SameLine(); + + if (ImGui::Button("FULL HD")) + { + recorder.m_width = 1920; + recorder.m_height = 1080; + recorder.m_fps = 60; + recorder.m_bitrate = "50M"; + } + + ImGui::SameLine(); + + if (ImGui::Button("4K")) + { + recorder.m_width = 3840; + recorder.m_height = 2160; + recorder.m_fps = 60; + recorder.m_bitrate = "70M"; + } } - } - else if (items_index == 4) + else if (items_index == 5) + { + ImGui::Combo("##ConverterType", &converter.converterType, converterTypes, IM_ARRAYSIZE(converterTypes)); + + ImGui::InputText(".txt##Replay Name", converter.replay_name, sizeof(converter.replay_name)); + + if (ImGui::Button("Convert")) + { + if (strlen(converter.replay_name) == 0) + { + ImGui::Text("Please enter a replay name."); + } + else + { + converter.convert(); + } + } + + ImGui::SameLine(); + + bool import_hovered = false; + if (ImGui::Button("Import")) + { + if (strlen(converter.replay_name) != 0) + { + converter.import(); + } + } + if (ImGui::IsItemHovered()) + import_hovered = true; + + ImGui::SameLine(); + + if (ImGui::Button("Matcool Converter")) + { + ShellExecuteA(0, "open", "https://matcool.github.io/gd-macro-converter/", 0, 0, SW_SHOWNORMAL); + } + + if (converter.converterType == 0) + { + if (strlen(converter.replay_name) == 0) + { + ImGui::Text("Please enter replay name"); + } + else + { + if (!import_hovered) + ImGui::Text("Replay will be saved to \"ReplayEngine/Converter/%s.txt\"", converter.replay_name); + else + ImGui::Text("Replay will be converted to Replay Engine Replay"); + } + } + + float windowHeight = ImGui::GetWindowSize().y; + float textHeight = ImGui::GetTextLineHeight(); + float textPosY = windowHeight - textHeight * 3; + ImGui::SetCursorPosY(textPosY); + ImGui::Text("I'm too lazy to make conversions for every bot because my code\nwould become shitcoded\nInstead you can use Mat's converter to convert plain text to another replay"); + } + else if (items_index == 6) + { + ImGui::Checkbox("Toggle", &sequence.enable_sqp); + ImGui::SameLine(); + ImGui::Checkbox("Random", &sequence.random_sqp); + ImGui::InputText("##sqp_replay", sequence.replay_sq_name, IM_ARRAYSIZE(sequence.replay_sq_name)); + ImGui::SameLine(); + if (ImGui::Button("Add")) + { + string path = "ReplayEngine/Replays/" + (string)sequence.replay_sq_name + ".re"; + if (filesystem::exists(path)) + { + sequence.replays.push_back(sequence.replay_sq_name); + } + } + + ImGui::SameLine(); + + if (ImGui::Button("Remove")) + { + if (sequence.replays.size() > (size_t)sequence.current_idx) + { + sequence.replays.erase(sequence.replays.begin() + sequence.current_idx); + sequence.first_sqp = true; + } + } + + ImGui::SameLine(); + + if (ImGui::Button("Remove All")) + { + sequence.replays.clear(); + sequence.first_sqp = true; + } + + ImGui::BeginChild("##sequence_window", {NULL, NULL}, true); + for (size_t n = 0; n < sequence.replays.size(); n++) + { + bool is_selected = (sequence.current_idx == n); + string anticonflict = sequence.replays[n] + "##" + to_string(n); + if (ImGui::Selectable(anticonflict.c_str(), is_selected)) + sequence.current_idx = n; + } + ImGui::EndChild(); + } + else if (items_index == 7) + { + static vector clicks; + static vector videos; + static int idx_clicks = 0; + static int idx_videos = 0; + static char output_clicks[128]; + static bool first = true; + static float audio_volume = 0.25; + static float clicks_volume = 5; + static bool soft_clicks_e = true; + static bool hard_clicks_e = false; + static int soft_clicks = 200; + static int hard_clicks = 500; + + auto updateClicksAndVideos = [&]() + { + clicks.clear(); + videos.clear(); + + for (const auto &entry : filesystem::directory_iterator("ReplayEngine\\Clicks")) + { + if (filesystem::is_directory(entry)) + { + clicks.push_back(entry.path().string()); + } + } + + for (const auto &entry : filesystem::directory_iterator("ReplayEngine\\Videos")) + { + if (!filesystem::is_directory(entry)) + { + videos.push_back(entry.path().filename().string()); + } + } + }; + + if (first) + { + first = false; + updateClicksAndVideos(); + } + + if (clicks.empty()) + { + ImGui::Text("No clickpacks! \"ReplayEngine/Clicks\" empty"); + if (ImGui::Button("Update")) + { + updateClicksAndVideos(); + } + } + else + { + if (ImGui::BeginTabBar("Clickbot")) + { + if (ImGui::BeginTabItem("Render##Tab")) + { + ImGui::BeginChild("##clickpack", {0, 200}, true); + for (size_t n = 0; n < clicks.size(); n++) + { + bool is_selected = (idx_clicks == n); + string anticonflict = clicks[n] + "##" + to_string(n); + if (ImGui::Selectable(anticonflict.c_str(), is_selected)) + idx_clicks = n; + } + ImGui::EndChild(); + + if (ImGui::Button("Update")) + { + updateClicksAndVideos(); + } + ImGui::SameLine(); + ImGui::InputText(".mp3", output_clicks, IM_ARRAYSIZE(output_clicks)); + ImGui::SameLine(); + if (ImGui::Button("Render")) + { + if (!filesystem::exists("clicks.exe")) + { + gd::FLAlertLayer::create(nullptr, "Info", "Ok", nullptr, "clicks.exe doesn't exist")->show(); + } + else + { + Console::Init(); + if (strlen(output_clicks) != 0) + { + if (replay.save("converted") != "Replay doesn't have actions") + { + std::thread([&]() + { + std::stringstream stream; + stream << "\"clicks\" -r\"ReplayEngine\\Replays\\converted.re\" -c\"" << + clicks[idx_clicks] << "\" "; + + if (soft_clicks_e) { + stream << "-softc" << soft_clicks << " "; + } + + if (hard_clicks_e) { + stream << "-hardc" << hard_clicks << " "; + } + + stream << "-end3 "; + + stream << "-o\"ReplayEngine\\Clicks\\" << static_cast(output_clicks) << ".mp3\""; + + auto process = subprocess::Popen(stream.str()); + if (process.close() != 0) { + Console::WriteLine("Clicks render went wrong :("); + } }) + .detach(); + } + else + { + Console::WriteLine("Replay doesn't have actions"); + } + } + else + { + Console::WriteLine("Output filename is empty"); + } + } + } + + ImGui::Checkbox("Softclicks", &soft_clicks_e); + ImGui::SameLine(); + ImGui::PushItemWidth(120.f); + ImGui::DragInt("##softclicks", &soft_clicks, 1, 0, INT_MAX, "ms: %i"); + + ImGui::Checkbox("Hardclicks", &hard_clicks_e); + ImGui::SameLine(); + ImGui::PushItemWidth(120.f); + ImGui::DragInt("##hardclicks", &hard_clicks, 1, 0, INT_MAX, "ms: %i"); + + float windowHeight = ImGui::GetWindowSize().y; + float textHeight = ImGui::GetTextLineHeight(); + float textPosY = windowHeight - textHeight; + ImGui::SetCursorPosY(textPosY); + ImGui::Text("All outputs will be saved to \"ReplayEngine/Clicks\" folder"); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Merge##Tab")) + { + ImGui::BeginChild("##videos", {0, 200}, true); + for (size_t n = 0; n < videos.size(); n++) + { + bool is_selected = (idx_videos == n); + string anticonflict = videos[n] + "##" + to_string(n); + if (ImGui::Selectable(anticonflict.c_str(), is_selected)) + idx_videos = n; + } + ImGui::EndChild(); + + if (ImGui::Button("Update")) + { + updateClicksAndVideos(); + } + ImGui::SameLine(); + ImGui::InputText(".mp3", output_clicks, IM_ARRAYSIZE(output_clicks)); + ImGui::SameLine(); + if (ImGui::Button("Merge")) + { + if (!filesystem::exists("ffmpeg.exe")) + { + gd::FLAlertLayer::create(nullptr, "Info", "Ok", nullptr, "ffmpeg.exe doesn't exist")->show(); + } + else + { + Console::Init(); + std::thread([&]() + { + string ffmpeg_command = "ffmpeg -i \"ReplayEngine/Videos/" + videos[idx_videos] + + "\" -i \"ReplayEngine/Clicks/" + static_cast(output_clicks) + + ".mp3\" -filter_complex \"[0:a]volume=" + to_string(audio_volume) + + "[video_audio];[1:a]volume=" + to_string(clicks_volume) + + "[clicks_audio];[video_audio][clicks_audio]amix=inputs=2:duration=longest[a]\" " + + "-map 0:v -map \"[a]\" -c:v copy -c:a aac -strict experimental -y \"ReplayEngine/Videos/clicks_" + videos[idx_videos] + "\""; + + auto process = subprocess::Popen(ffmpeg_command); + if (process.close()) { + Console::WriteLine("R.I.P. FFmpeg"); + } + else { + Console::WriteLine("Audio combine should be done"); + } }) + .detach(); + } + } + ImGui::DragFloat("##audio_volume", &audio_volume, 0.01f, 0, FLT_MAX, "Audio Volume %.2f"); + ImGui::DragFloat("##clicks_volume", &clicks_volume, 0.01f, 0, FLT_MAX, "Clicks Volume %.2f"); + + float windowHeight = ImGui::GetWindowSize().y; + float textHeight = ImGui::GetTextLineHeight(); + float textPosY = windowHeight - textHeight; + ImGui::SetCursorPosY(textPosY); + ImGui::Text("Video will be saved to \"ReplayEngine/Videos/clicks_%s\"", videos[idx_videos].c_str()); + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + } + } + else if (items_index == 8) { ImGui::Text("About"); ImGui::Separator(); - ImGui::Text("Replay Engine Beta 2 by TobyAdd"); + ImGui::Text("Replay Engine Beta 3 by TobyAdd"); if (ImGui::MenuItem("Graphical interface is made using ImGui")) ShellExecuteA(NULL, "open", "https://github.com/ocornut/imgui", NULL, NULL, SW_SHOWNORMAL); + + ImGui::Text("Made in "); + ImGui::SameLine(); + ImVec2 flagSize(ImGui::GetTextLineHeight() * 1.5f, ImGui::GetTextLineHeight() * 1.125f); + ImVec2 startPos = ImGui::GetCursorScreenPos(); + ImVec2 endPos = ImVec2(startPos.x + flagSize.x, startPos.y + flagSize.y); + ImDrawList *drawList = ImGui::GetWindowDrawList(); + drawList->AddRectFilled(startPos, endPos, IM_COL32(255, 215, 0, 255)); + float stripeHeight = flagSize.y / 2.0f; + drawList->AddRectFilled(ImVec2(startPos.x, startPos.y), ImVec2(endPos.x, startPos.y + stripeHeight), IM_COL32(0, 87, 183, 255)); + ImGui::Spacing(); ImGui::Text("Keybinds"); ImGui::Separator(); ImGui::Text("K - Toggle UI"); ImGui::Text("C - Enable Frame Advance + Next Frame"); - ImGui::Text("F - Disable Frame Advance"); - ImGui::Text("S - Toggle Spam Bot"); - ImGui::Text("D - Toggle Straight Fly Bot"); + ImGui::Text("V - Disable Frame Advance"); + ImGui::Text("P - Toggle Playback/Disable"); ImGui::Spacing(); ImGui::Text("Special Thanks"); ImGui::Separator(); @@ -448,6 +1051,7 @@ void gui::Render() ImGui::Text("HJfod - Help with some coding stuff"); ImGui::Text("Mat - Internal Recorder"); ImGui::Text("Absolute - Hacks"); + ImGui::Text("Acid - Clickbot"); } ImGui::EndChild(); @@ -459,5 +1063,4 @@ void gui::Toggle() { gui::show = !gui::show; opennedSP = false; - } \ No newline at end of file diff --git a/src/hacks.cpp b/src/hacks.cpp index 7c537d2..9a62d2c 100644 --- a/src/hacks.cpp +++ b/src/hacks.cpp @@ -6,24 +6,33 @@ json hacksContent; namespace hacks { + bool noclipP1; + bool noclipP2; + bool disable_achievements; void render() { - if (ImGui::BeginTabBar("Hacks")) { - for (auto& item : hacksContent.items()) { - if (ImGui::BeginTabItem(item.key().c_str())) { + if (ImGui::BeginTabBar("Hacks")) + { + for (auto &item : hacksContent.items()) + { + if (ImGui::BeginTabItem(item.key().c_str())) + { ImGui::BeginChild("HacksChild"); - json& tabContent = item.value(); + json &tabContent = item.value(); - for (size_t i = 0; i < tabContent.size(); i++) { - json& itemHack = tabContent.at(i); + for (size_t i = 0; i < tabContent.size(); i++) + { + json &itemHack = tabContent.at(i); bool enabled = itemHack["enabled"]; - if (ImGui::Checkbox(itemHack["name"].get().c_str(), &enabled)) { + if (ImGui::Checkbox(itemHack["name"].get().c_str(), &enabled)) + { itemHack["enabled"] = enabled; json opcodes = itemHack["opcodes"]; - for (auto& opcode : opcodes) { + for (auto &opcode : opcodes) + { string addrStr = opcode["addr"]; string bytesStr = enabled ? opcode["on"] : opcode["off"]; @@ -31,7 +40,8 @@ namespace hacks sscanf_s(addrStr.c_str(), "%x", &address); DWORD base = gd::base; - if (!opcode["lib"].is_null() && string(opcode["lib"]) == "libcocos2d.dll") { + if (!opcode["lib"].is_null() && string(opcode["lib"]) == "libcocos2d.dll") + { base = (DWORD)GetModuleHandleA("libcocos2d.dll"); } @@ -56,7 +66,8 @@ namespace hacks } } - void load() { + void load() + { ifstream file("ReplayEngine/hacks.json"); if (!file.is_open()) return; @@ -66,18 +77,23 @@ namespace hacks hacksContent = json::parse(file_content); - for (auto item : hacksContent.items()) { + for (auto item : hacksContent.items()) + { json tabContent = item.value(); - for (size_t i = 0; i < tabContent.size(); i++) { + for (size_t i = 0; i < tabContent.size(); i++) + { json itemHack = tabContent.at(i); - if (itemHack["enabled"]) { + if (itemHack["enabled"]) + { json opcodes = itemHack["opcodes"]; - for (int j = 0; j < (int)opcodes.size(); j++) { + for (int j = 0; j < (int)opcodes.size(); j++) + { json opcode = opcodes.at(j); uintptr_t address; sscanf_s(opcode["addr"].get().c_str(), "%x", &address); DWORD base = gd::base; - if (!opcode["lib"].is_null() && string(opcode["lib"]) == "libcocos2d.dll") { + if (!opcode["lib"].is_null() && string(opcode["lib"]) == "libcocos2d.dll") + { base = (DWORD)GetModuleHandleA("libcocos2d.dll"); } @@ -86,27 +102,42 @@ namespace hacks } } } - } - bool writemem(uintptr_t address, std::string bytes) { + bool writemem(uintptr_t address, std::string bytes) + { std::vector byteVec; std::stringstream byteStream(bytes); std::string byteStr; - while (getline(byteStream, byteStr, ' ')) { + while (getline(byteStream, byteStr, ' ')) + { unsigned int byte = std::stoul(byteStr, nullptr, 16); byteVec.push_back(static_cast(byte)); } DWORD oldProtect; - if (VirtualProtect(reinterpret_cast(address), byteVec.size(), PAGE_EXECUTE_READWRITE, &oldProtect)) { - memcpy(reinterpret_cast(address), byteVec.data(), byteVec.size()); - VirtualProtect(reinterpret_cast(address), byteVec.size(), oldProtect, &oldProtect); + if (VirtualProtect(reinterpret_cast(address), byteVec.size(), PAGE_EXECUTE_READWRITE, &oldProtect)) + { + memcpy(reinterpret_cast(address), byteVec.data(), byteVec.size()); + VirtualProtect(reinterpret_cast(address), byteVec.size(), oldProtect, &oldProtect); return true; } - else { + else + { return false; } } + + void disable_achievements_f(bool enable) + { + if (enable) + { + writemem(gd::base + 0x8927, "0F 84"); + } + else + { + writemem(gd::base + 0x8927, "0F 85"); + } + } } diff --git a/src/hacks.h b/src/hacks.h index ba95292..5ffc612 100644 --- a/src/hacks.h +++ b/src/hacks.h @@ -10,4 +10,10 @@ namespace hacks void unload(); void render(); bool writemem(uintptr_t address, string bytes); + + extern bool noclipP1; + extern bool noclipP2; + extern bool disable_achievements; + + void disable_achievements_f(bool enable); } \ No newline at end of file diff --git a/src/hooks.cpp b/src/hooks.cpp index fc0c662..014131c 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -5,8 +5,6 @@ #include "hacks.h" #include "recorder.hpp" -extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); - namespace hooks { bool __fastcall playLayer_initHook(gd::PlayLayer *self, int edx, gd::GJGameLevel *level) @@ -17,6 +15,8 @@ namespace hooks strcpy_s(recorder.video_name, string(self->m_level->levelName + ".mp4").c_str()); practiceFix.frame_offset = 0; practiceFix.clear(); + practiceFix.activated_objects_p1.clear(); + practiceFix.activated_objects_p2.clear(); return ret; } @@ -24,10 +24,17 @@ namespace hooks { if (recorder.m_recording) recorder.handle_recording(self, deltaTime); + playLayer_update(self, deltaTime); + if (replay.mode == play) { replay.handle_playing(self); + if (replay.continue_toggled && !replay.replay.empty() && replay.get_frame() >= replay.replay.back().frame) + { + replay.mode = record; + replay.continue_toggled = false; + } } else if (replay.mode == record) { @@ -49,16 +56,28 @@ namespace hooks void __fastcall playLayer_resetLevelHook(gd::PlayLayer *self) { + sequence.do_some_magic(); playLayer_resetLevel(self); replay.handle_reseting(self); + if (replay.mode == continue_record) + { + replay.continue_toggled = true; + replay.mode = play; + if (!self->m_isPracticeMode) + playLayer_togglePractice(self, true); + } } void __fastcall playLayer_onQuitHook(gd::PlayLayer *self) { playLayer_onQuit(self); - if (replay.mode == record) + if (replay.mode == record || replay.mode == continue_record) + { replay.mode = disable; + replay.continue_toggled = false; + } frameAdvance.enabled = false; + sequence.first_sqp = true; } void __fastcall playLayer_levelCompleteHook(gd::PlayLayer *self) @@ -68,6 +87,23 @@ namespace hooks replay.mode = disable; } + int __fastcall playLayer_deathHook(gd::PlayLayer *self, void *, gd::PlayerObject *player, gd::GameObject *obj) + { + int ret = 0; + + if (player == self->m_player1 && hacks::noclipP1) + { + return ret; + } + else if (player == self->m_player2 && hacks::noclipP2) + { + return ret; + } + + ret = playLayer_death(self, player, obj); + return ret; + } + bool __fastcall playLayer_pushButtonHook(gd::PlayLayer *self, uintptr_t, int state, bool player) { if (replay.mode == play && replay.ignore_input) @@ -134,39 +170,73 @@ namespace hooks practiceFix.update_frame_offset(); } - void __fastcall dispatchKeyboardMSGHook(void* self, void*, int key, bool down) { + bool __fastcall levelEditorLayer_initHook(gd::LevelEditorLayer *self, int edx, gd::GJGameLevel *level) + { + if (!levelEditorLayer_init(self, level)) + return false; + if (replay.mode == record || replay.mode == continue_record) + { + replay.mode = disable; + replay.continue_toggled = false; + } + sequence.first_sqp = true; + frameAdvance.enabled = false; + return true; + } + + void __fastcall dispatchKeyboardMSGHook(void *self, void *, int key, bool down) + { dispatchKeyboardMSG(self, key, down); auto pl = gd::GameManager::sharedState()->getPlayLayer(); - if (pl && down && key == 'C') { + if (pl && down && key == 'C') + { frameAdvance.enabled = true; frameAdvance.triggered = true; } - else if (pl && down && key == 'F') { + else if (pl && down && key == 'V') + { frameAdvance.enabled = false; } - else if (pl && down && key == 'S') { - spamBot.enabled = !spamBot.enabled; - spamBot.reset_temp(); - if (!spamBot.enabled) + else if (pl && down && key == 'R') + { + hooks::playLayer_resetLevelHook(pl); + } + else if (pl && down && key == 'P') + { + if (replay.mode == play) { - hooks::playLayer_releaseButtonHook(pl, 0, 0, true); - hooks::playLayer_releaseButtonHook(pl, 0, 0, false); + replay.mode = disable; } - } - else if (pl && down && key == 'D') { - straightFly.enabled = !straightFly.enabled; - straightFly.start(pl); - if (!straightFly.enabled) + else { - hooks::playLayer_releaseButtonHook(pl, 0, 0, true); - hooks::playLayer_releaseButtonHook(pl, 0, 0, false); + replay.mode = play; } } - else if (pl && down && key == 'R') { - hooks::playLayer_resetLevelHook(pl); - } + } + void __fastcall PlayerObject_ringJumpHook(gd::PlayerObject *self, void *, gd::GameObject *ring) + { + bool a = ring->m_hasBeenActivated; + bool b = ring->m_hasBeenActivatedP2; + PlayerObject_ringJump(self, ring); + practiceFix.handle_activated_object(a, b, ring); + } + + void __fastcall GameObject_activateObjectHook(gd::GameObject *self, void *, gd::PlayerObject *player) + { + bool a = self->m_hasBeenActivated; + bool b = self->m_hasBeenActivatedP2; + GameObject_activateObject(self, player); + practiceFix.handle_activated_object(a, b, self); + } + + void __fastcall GJBaseGameLayer_bumpPlayerHook(gd::GJBaseGameLayer *self, void *, gd::PlayerObject *player, gd::GameObject *object) + { + bool a = object->m_hasBeenActivated; + bool b = object->m_hasBeenActivatedP2; + GJBaseGameLayer_bumpPlayer(self, player, object); + practiceFix.handle_activated_object(a, b, object); } void initHooks() @@ -176,14 +246,18 @@ namespace hooks MH_CreateHook((PVOID)(gd::base + 0x20BF00), playLayer_resetLevelHook, (LPVOID *)&playLayer_resetLevel); MH_CreateHook((PVOID)(gd::base + 0x20D810), playLayer_onQuitHook, (LPVOID *)&playLayer_onQuit); MH_CreateHook((PVOID)(gd::base + 0x1FD3D0), playLayer_levelCompleteHook, (LPVOID *)&playLayer_levelComplete); + MH_CreateHook((PVOID)(gd::base + 0x20A1A0), playLayer_deathHook, (LPVOID *)&playLayer_death); MH_CreateHook((PVOID)(gd::base + 0x111500), playLayer_pushButtonHook, (LPVOID *)&playLayer_pushButton); MH_CreateHook((PVOID)(gd::base + 0x111660), playLayer_releaseButtonHook, (LPVOID *)&playLayer_releaseButton); MH_CreateHook((PVOID)(gd::base + 0x20B050), playLayer_createCheckpointHook, (LPVOID *)&playLayer_createCheckpoint); MH_CreateHook((PVOID)(gd::base + 0x20B830), playLayer_removeCheckpointHook, (LPVOID *)&playLayer_removeCheckpoint); MH_CreateHook((PVOID)(gd::base + 0x20D0D0), playLayer_togglePracticeHook, (LPVOID *)&playLayer_togglePractice); + MH_CreateHook((PVOID)(gd::base + 0x15EE00), levelEditorLayer_initHook, (LPVOID *)&levelEditorLayer_init); MH_CreateHook( (PVOID)(GetProcAddress(GetModuleHandleA("libcocos2d.dll"), "?dispatchKeyboardMSG@CCKeyboardDispatcher@cocos2d@@QAE_NW4enumKeyCodes@2@_N@Z")), - dispatchKeyboardMSGHook, (LPVOID*)&dispatchKeyboardMSG - ); + dispatchKeyboardMSGHook, (LPVOID *)&dispatchKeyboardMSG); + MH_CreateHook((PVOID)(gd::base + 0x1f4ff0), PlayerObject_ringJumpHook, (LPVOID *)&PlayerObject_ringJump); + MH_CreateHook((PVOID)(gd::base + 0xef0e0), GameObject_activateObjectHook, (LPVOID *)&GameObject_activateObject); + MH_CreateHook((PVOID)(gd::base + 0x10ed50), GJBaseGameLayer_bumpPlayerHook, (LPVOID *)&GJBaseGameLayer_bumpPlayer); } } \ No newline at end of file diff --git a/src/hooks.h b/src/hooks.h index 07ae15e..9bd15fd 100644 --- a/src/hooks.h +++ b/src/hooks.h @@ -17,6 +17,9 @@ namespace hooks inline void(__thiscall *playLayer_levelComplete)(gd::PlayLayer *self); void __fastcall playLayer_levelCompleteHook(gd::PlayLayer *self); + inline int(__thiscall *playLayer_death)(gd::PlayLayer *self, gd::PlayerObject *player, gd::GameObject *obj); + int __fastcall playLayer_deathHook(gd::PlayLayer *self, void *, gd::PlayerObject *player, gd::GameObject *obj); + inline bool(__thiscall *playLayer_pushButton)(gd::PlayLayer *self, int state, bool player); bool __fastcall playLayer_pushButtonHook(gd::PlayLayer *self, uintptr_t, int state, bool player); @@ -32,8 +35,20 @@ namespace hooks inline void(__thiscall *playLayer_togglePractice)(void *self, bool practice); void __fastcall playLayer_togglePracticeHook(void *self, int edx, bool practice); - inline void(__thiscall* dispatchKeyboardMSG)(void* self, int key, bool down); - void __fastcall dispatchKeyboardMSGHook(void* self, void*, int key, bool down); + inline bool(__thiscall *levelEditorLayer_init)(gd::LevelEditorLayer *self, gd::GJGameLevel *GJGameLevel); + bool __fastcall levelEditorLayer_initHook(gd::LevelEditorLayer *self, int edx, gd::GJGameLevel *GJGameLevel); + + inline void(__thiscall *dispatchKeyboardMSG)(void *self, int key, bool down); + void __fastcall dispatchKeyboardMSGHook(void *self, void *, int key, bool down); + + inline void(__thiscall *PlayerObject_ringJump)(gd::PlayerObject *self, gd::GameObject *ring); + void __fastcall PlayerObject_ringJumpHook(gd::PlayerObject *self, void *, gd::GameObject *ring); + + inline void(__thiscall *GameObject_activateObject)(gd::GameObject *self, gd::PlayerObject *player); + void __fastcall GameObject_activateObjectHook(gd::GameObject *self, void *, gd::PlayerObject *player); + + inline void(__thiscall *GJBaseGameLayer_bumpPlayer)(gd::GJBaseGameLayer *self, gd::PlayerObject *player, gd::GameObject *object); + void __fastcall GJBaseGameLayer_bumpPlayerHook(gd::GJBaseGameLayer *self, void *, gd::PlayerObject *player, gd::GameObject *object); void initHooks(); } \ No newline at end of file diff --git a/src/recorder.cpp b/src/recorder.cpp index b9d9244..2cec980 100644 --- a/src/recorder.cpp +++ b/src/recorder.cpp @@ -10,7 +10,7 @@ Recorder::Recorder() : m_height(720), m_fps(60) {} -void Recorder::start(const std::string& path) { +void Recorder::start(const string& path) { m_recording = true; m_frame_has_data = false; m_current_frame.resize(m_width * m_height * 3, 0); @@ -31,8 +31,8 @@ void Recorder::start(const std::string& path) { song_file = CCFileUtils::sharedFileUtils()->fullPathForFilename(song_file.c_str(), false); auto is_testmode = play_layer->m_isTestMode; auto song_offset = m_song_start_offset; - std::thread([&, path, song_file, fade_in, fade_out, bg_volume, sfx_volume, is_testmode, song_offset]() { - std::stringstream stream; + thread([&, path, song_file, fade_in, fade_out, bg_volume, sfx_volume, is_testmode, song_offset]() { + stringstream stream; stream << '"' << m_ffmpeg_path << '"' << " -y -f rawvideo -pix_fmt rgb24 -s " << m_width << "x" << m_height << " -r " << m_fps << " -i - "; if (!m_codec.empty()) @@ -59,15 +59,13 @@ void Recorder::start(const std::string& path) { if (process.close()) { return; } - if (!m_include_audio || !std::filesystem::exists(song_file)) + if (!m_include_audio || !filesystem::exists(song_file)) return; - std::string tempDir = std::filesystem::path("ReplayEngine/Temp").string(); - std::filesystem::create_directory(tempDir); - std::string tempPath = tempDir + "/output.mp4"; - std::string finalPath = path; + string tempPath = "ReplayEngine/Temp/output.mp4"; + string finalPath = path; { - std::stringstream stream; + stringstream stream; stream << '"' << m_ffmpeg_path << '"' << " -y -ss " << song_offset << " -i \"" << song_file << "\" -i \"" << path << "\" -t " << m_last_frame_t << " -c:v copy "; if (!m_extra_audio_args.empty()) @@ -83,8 +81,8 @@ void Recorder::start(const std::string& path) { return; } } - std::filesystem::remove(finalPath); - std::filesystem::rename(tempPath, finalPath); + filesystem::remove(finalPath); + filesystem::rename(tempPath, finalPath); }).detach(); } @@ -112,7 +110,7 @@ void MyRenderTexture::begin() { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_old_fbo); } -void MyRenderTexture::capture(std::mutex& lock, std::vector& data, volatile bool& lul) { +void MyRenderTexture::capture(mutex& lock, vector& data, volatile bool& lul) { glViewport(0, 0, m_width, m_height); glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &m_old_fbo); diff --git a/src/replayEngine.cpp b/src/replayEngine.cpp index 67ad3c3..1798654 100644 --- a/src/replayEngine.cpp +++ b/src/replayEngine.cpp @@ -1,12 +1,13 @@ #include "replayEngine.h" #include "hooks.h" -#include Replay replay; PracticeFix practiceFix; FrameAdvance frameAdvance; SpamBot spamBot; StraightFly straightFly; +Converter converter; +Sequence sequence; unsigned Replay::get_frame() { @@ -23,15 +24,8 @@ void Replay::handle_recording(gd::PlayLayer *self, bool player) unsigned frame = get_frame(); - bool frameExists = false; - for (const auto &data : replay) - { - if (data.frame == frame && data.player == player) - { - frameExists = true; - break; - } - } + bool frameExists = std::find_if(replay.begin(), replay.end(), [&](const auto &data) + { return data.frame == frame && data.player == player; }) != replay.end(); if (frameExists) { @@ -103,56 +97,53 @@ void Replay::handle_reseting(gd::PlayLayer *self) if (mode == state::play) { reset_replay(); + hooks::playLayer_releaseButton(self, 0, true); + hooks::playLayer_releaseButton(self, 0, false); } - else if (mode == state::record) + + auto zero_frame = [this]() { - auto zero_frame = [this]() + if (mode == record) { - if (mode == record) - { - Replay::clear(); - } - practiceFix.clear(); - practiceFix.update_frame_offset(); - }; + Replay::clear(); + } + practiceFix.clear(); + practiceFix.update_frame_offset(); + practiceFix.activated_objects_p1.clear(); + practiceFix.activated_objects_p2.clear(); + }; - if (self->m_isPracticeMode) + if (self->m_isPracticeMode) + { + if (practiceFix.fix_respawn(self)) { - if (practiceFix.fix_respawn(self)) + practiceFix.update_frame_offset(); + if (mode == record) { - practiceFix.update_frame_offset(); - if (mode == record) + remove_actions(get_frame()); + bool holding = self->m_player1->m_isHolding; + if ((holding && replay2.empty()) || (!replay2.empty() && replay2.back().hold != holding)) { - remove_actions(get_frame()); - bool holding = self->m_player1->m_isHolding; - if ((holding && replay2.empty()) || (!replay2.empty() && replay2.back().hold != holding)) - { - handle_recording2(true, holding); - if (holding) - { - hooks::playLayer_releaseButton(self, 0, true); - hooks::playLayer_pushButton(self, 0, true); - self->m_player1->m_hasJustHeld = true; - } - } - else if (!replay2.empty() && replay2.back().hold && holding && !practiceFix.checkpoints_p1.empty() && - practiceFix.checkpoints_p1.back().has_just_held) + handle_recording2(true, holding); + if (holding) { hooks::playLayer_releaseButton(self, 0, true); hooks::playLayer_pushButton(self, 0, true); + self->m_player1->m_hasJustHeld = true; } - if (self->m_levelSettings->m_twoPlayerMode) - handle_recording(false, false); } - } - else - { - zero_frame(); + + if (self->m_levelSettings->m_twoPlayerMode) + handle_recording2(false, false); } } else + { zero_frame(); + } } + else + zero_frame(); } void Replay::remove_actions(unsigned frame) @@ -189,7 +180,7 @@ void Replay::reset_replay() string Replay::save(string name) { - if (replay.empty()) + if (replay2.empty()) return "Replay doesn't have actions"; ofstream file("ReplayEngine/Replays/" + name + ".re", std::ios::binary); @@ -209,12 +200,19 @@ string Replay::save(string name) return "Replay saved"; } -string Replay::load(string name) +string Replay::load(string name, bool overwrite) { - if (!replay.empty()) + if (overwrite) + { + clear(); + } + + if (!replay2.empty()) return "Please clear replay before loading another"; ifstream file("ReplayEngine/Replays/" + name + ".re", std::ios::binary); + if (!file) + return "Replay doesn't exist"; file.read(reinterpret_cast(&fps_value), sizeof(fps_value)); @@ -255,7 +253,8 @@ void PracticeFix::handle_checkpoint(gd::PlayLayer *self) self->m_player1->m_isHolding, self->m_player1->m_isHolding2, self->m_player1->m_hasJustHeld, - self->m_player1->m_hasJustHeld2}); + self->m_player1->m_hasJustHeld2, + activated_objects_p1.size()}); checkpoints_p2.push_back({replay.get_frame(), self->m_player2->m_position.x, @@ -276,7 +275,8 @@ void PracticeFix::handle_checkpoint(gd::PlayLayer *self) self->m_player2->m_isHolding, self->m_player2->m_isHolding2, self->m_player2->m_hasJustHeld, - self->m_player2->m_hasJustHeld2}); + self->m_player2->m_hasJustHeld2, + activated_objects_p2.size()}); } bool PracticeFix::fix_respawn(gd::PlayLayer *self) @@ -323,6 +323,28 @@ bool PracticeFix::fix_respawn(gd::PlayLayer *self) // self->m_player2->m_isHolding2 = checkpoints_p2.back().is_holding2; // self->m_player2->m_hasJustHeld = checkpoints_p2.back().has_just_held; // self->m_player2->m_hasJustHeld2 = checkpoints_p2.back().has_just_held2; + + if (orb_fix) + { + constexpr auto delete_from = [&](auto &vec, size_t index) + { + vec.erase(vec.begin() + index, vec.end()); + }; + + delete_from(practiceFix.activated_objects_p1, practiceFix.checkpoints_p1.back().activated_objects_size); + delete_from(practiceFix.activated_objects_p2, practiceFix.checkpoints_p2.back().activated_objects_size); + + for (const auto &object : practiceFix.activated_objects_p1) + { + object->m_hasBeenActivated = true; + } + + for (const auto &object : practiceFix.activated_objects_p2) + { + object->m_hasBeenActivatedP2 = true; + } + } + return true; } @@ -421,4 +443,91 @@ void StraightFly::handle_straightfly(gd::PlayLayer *self) void StraightFly::start(gd::PlayLayer *self) { start_y = self ? self->m_player1->m_position.y : 0.0f; +} + +void Converter::convert() +{ + if (converterType == 0 && !replay.replay2.empty()) + { + ofstream out("ReplayEngine/Converter/" + string(replay_name) + ".txt"); + out << replay.fps_value << "\n"; + for (size_t i = 0; i < replay.replay2.size(); i++) + { + out << replay.replay2[i].frame << " " << replay.replay2[i].hold << " " << replay.replay2[i].player; + if (i != replay.replay2.size() - 1) + { + out << "\n"; + } + } + out.close(); + } +} + +ReplayData2 Converter::pasreline(string line) +{ + istringstream splitstr(line); + string splitword; + vector splitwords; + while (getline(splitstr, splitword, ' ')) + { + splitwords.push_back(splitword); + } + return {(unsigned)(stoi(splitwords[0])), (bool)(stoi(splitwords[1])), (bool)(stoi(splitwords[2]))}; +} + +void Converter::import() +{ + if (converterType == 0) + { + ifstream file("ReplayEngine/Converter/" + string(replay_name) + ".txt"); + if (file.is_open()) + { + string line; + replay.clear(); + getline(file, line); + replay.fps_value = stof(line); + while (getline(file, line)) + { + replay.replay2.push_back(pasreline(line)); + } + } + file.close(); + } +} + +int getRandomNumber(int min, int max) +{ + std::random_device rd; + std::mt19937_64 generator(rd()); + + std::uniform_int_distribution distribution(min, max); + + int randomNum = distribution(generator); + return randomNum; +} + +void Sequence::do_some_magic() +{ + if (sequence.enable_sqp && replay.mode == state::play && !sequence.replays.empty()) + { + if (!sequence.random_sqp) + { + if (sequence.first_sqp) + { + sequence.current_idx = 0; + sequence.first_sqp = false; + } + else + sequence.current_idx++; + if ((int)sequence.replays.size() <= sequence.current_idx) + sequence.current_idx = 0; + replay.load(sequence.replays[sequence.current_idx].c_str()); + } + else + { + sequence.current_idx = getRandomNumber(0, (int)sequence.replays.size() - 1); + Console::WriteLine(to_string(sequence.current_idx) + " - " + sequence.replays[sequence.current_idx]); + replay.load(sequence.replays[sequence.current_idx].c_str(), true); + } + } } \ No newline at end of file diff --git a/src/replayEngine.h b/src/replayEngine.h index 5213b89..b1dbd8f 100644 --- a/src/replayEngine.h +++ b/src/replayEngine.h @@ -1,11 +1,13 @@ #include "pch.h" #include +#include enum state { disable, record, - play + play, + continue_record }; struct ReplayData @@ -32,6 +34,7 @@ struct CheckpointData double x_accel, y_accel, jump_accel; bool is_upsidedown, can_robot_jump, is_on_ground, is_dashing, is_sliding, is_rising, black_orb, is_holding, is_holding2, has_just_held, has_just_held2; + size_t activated_objects_size; }; class Replay @@ -51,8 +54,10 @@ class Replay bool real_time = true; bool dual_clicks = false; + bool practice_fix = true; bool accuracy_fix = true; bool disable_rotationfix = true; + bool continue_toggled = false; unsigned get_frame(); void handle_recording(gd::PlayLayer *self, bool player); @@ -64,7 +69,7 @@ class Replay bool empty(); void reset_replay(); string save(string name); - string load(string name); + string load(string name, bool overwrite = false); size_t replay_size() { return replay.size() + replay2.size(); } string replay_size_text() { @@ -84,6 +89,8 @@ class PracticeFix public: vector checkpoints_p1; vector checkpoints_p2; + vector activated_objects_p1, activated_objects_p2; + bool orb_fix = true; void handle_checkpoint(gd::PlayLayer *self); bool fix_respawn(gd::PlayLayer *self); @@ -102,6 +109,18 @@ class PracticeFix } void update_frame_offset(); unsigned frame_offset = 0; + + void handle_activated_object(bool a, bool b, gd::GameObject *object) + { + auto play_layer = gd::GameManager::sharedState()->getPlayLayer(); + if (play_layer && play_layer->m_isPracticeMode) + { + if (object->m_hasBeenActivated && !a) + activated_objects_p1.push_back(object); + if (object->m_hasBeenActivatedP2 && !b) + activated_objects_p2.push_back(object); + } + } }; extern PracticeFix practiceFix; @@ -149,3 +168,32 @@ class StraightFly }; extern StraightFly straightFly; + +class Converter +{ +public: + int converterType = 0; + char replay_name[128]; + + void convert(); + void import(); + +private: + ReplayData2 pasreline(string line); +}; + +extern Converter converter; + +class Sequence +{ +public: + char replay_sq_name[128]; + vector replays; + int current_idx; + bool enable_sqp; + bool random_sqp; + bool first_sqp; + void do_some_magic(); +}; + +extern Sequence sequence;