diff --git a/.github/workflows/multi-platform.yml b/.github/workflows/multi-platform.yml
index 6a5086b..0f608c3 100644
--- a/.github/workflows/multi-platform.yml
+++ b/.github/workflows/multi-platform.yml
@@ -15,6 +15,10 @@ jobs:
- name: Windows
os: windows-latest
+ - name: Android64
+ os: ubuntu-latest
+ target: Android64
+
name: ${{ matrix.config.name }}
runs-on: ${{ matrix.config.os }}
diff --git a/docs/css/style.css b/docs/css/style.css
deleted file mode 100644
index 35e6948..0000000
--- a/docs/css/style.css
+++ /dev/null
@@ -1,316 +0,0 @@
-
-@import url('https://fonts.cdnfonts.com/css/google-sans');
-
-:root {
- --background: rgb(27, 27, 29);
- --foreground: rgb(27, 27, 29);
- --foreground-light: rgb(27, 27, 29);
- --bg-accent: rgb(27, 27, 29);
-}
-
-* {
- margin: 0;
- padding: 0;
- font-family: 'Product Sans', sans-serif;
- color: var(--foreground);
- box-sizing: border-box;
- transition: all 200ms;
-}
-
-img {
- vertical-align: bottom;
-}
-
-html {
- scroll-behavior: smooth;
-}
-
-body {
- background-color: var(--background);
-}
-
-.kbd {
- padding: 0 8px;
- color: var(--foreground);
- background-color: var(--bg-accent);
- border-radius: 8px;
-}
-
-.wrapper {
- width: 80vw;
- margin: 0 auto;
-}
-
-.header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin: 16px auto;
- padding: 0 16px;
-}
-
-.links {
- display: flex;
- gap: 16px;
- list-style: none;
-}
-
-.links a {
- text-decoration: none;
- padding: 8px 16px;
- color: var(--foreground);
- background-color: var(--bg-accent);
- border-radius: 32px;
- font-size: 24px;
-}
-
-.links a:hover {
- color: var(--background);
- background-color: var(--foreground);
-}
-
-.logo {
- color: var(--background);
- font-size: 32px;
- background-color: var(--foreground);
- padding: 8px 16px;
- border-radius: 32px;
-}
-
-.hero {
- display: flex;
- height: 80vh;
- flex-direction: column;
- justify-content: center;
- background-color: var(--bg-accent);
- padding: 32px;
- border-radius: 25px;
-}
-
-.hero > h1 {
- color: var(--foreground);
- font-size: 64px;
-}
-
-.hero > p {
- color: var(--foreground);
- font-size: 32px;
-}
-
-.overview {
- margin: 16px auto;
-}
-
-.overview h2 {
- /* display: inline-block; */
- color: var(--background);
- font-size: 32px;
- background-color: var(--foreground);
- padding: 8px 16px;
- margin-bottom: 16px;
- border-radius: 32px;
-}
-
-.overview-info p {
- color: var(--foreground);
- font-size: 32px;
- margin-bottom: 16px;
- border-radius: 32px;
- text-wrap: balance;
-}
-
-.overview-info ul {
- color: var(--foreground);
- font-size: 24px;
- margin-bottom: 16px;
- border-radius: 32px;
- padding-left: 32px;
-}
-
-.overview-split {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.overview-image {
- padding: 8px;
- background-color: var(--foreground);
- border-radius: 24px;
-}
-
-.overview-image:hover {
- background-color: var(--bg-accent);
-}
-
-.overview-image img {
- margin-bottom: 0;
- width: 600px;
- border-radius: 16px;
-}
-
-.install {
- margin: 16px auto;
-}
-
-.install h2 {
- /* display: inline-block; */
- color: var(--background);
- font-size: 32px;
- background-color: var(--foreground);
- padding: 8px 16px;
- margin-bottom: 16px;
- border-radius: 32px;
-}
-
-.install-info {
- color: var(--foreground);
- font-size: 32px;
- margin-left: 32px;
- margin-bottom: 16px;
- border-radius: 32px;
- text-wrap: balance;
-}
-
-.install-info ol {
- color: var(--foreground);
- font-size: 24px;
- margin-bottom: 16px;
- border-radius: 32px;
- padding-left: 32px;
-}
-
-.install-split {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.install-image {
- padding: 8px;
- background-color: var(--foreground);
- border-radius: 24px;
-}
-
-.install-image:hover {
- background-color: var(--bg-accent);
-}
-.install-image img {
- margin-bottom: 0;
- width: 600px;
- border-radius: 16px;
-}
-
-.footer {
- display: flex;
- flex-direction: column;
- align-items: center;
- margin-top: 48px;
- margin-bottom: 16px;
-}
-
-.footer * {
- margin-bottom: 8px;
- color: var(--foreground-light);
-}
-
-.footer ul {
- display: flex;
- list-style: none;
-}
-
-.footer ul li {
- margin-right: 8px;
- margin-left: 8px;
-}
-
-.footer h3 {
- font-size: 24px;
-}
-
-.footer p {
- font-size: 16px;
-}
-
-#theme-switch svg {
- transform: translate(0, 3px);
-}
-
-#theme-switch * {
- color: var(--foreground);
-}
-
-#theme-switch:hover * {
- color: var(--background);
-}
-
-@media (max-width: 1210px) {
- .menu-link {
- display: none;
- }
-}
-
-@media (max-width: 1100px) {
- body {
- width: auto;
- padding: 4px;
- }
-
- .hero {
- height: 50vh;
- }
-
- header > .wrapper {
- padding-left: 0;
- padding-right: 0;
- }
-
- .hero > h1 {
- font-size: 32px;
- }
-
- .hero > p {
- font-size: 24px;
- }
-
- .overview > h2 {
- padding: 8px 24px;
- }
-
- .overview-split {
- flex-direction: column;
- }
-
- .overview-image img {
- width: 80vw;
- height: auto;
- }
-
- .install > h2 {
- padding: 8px 24px;
- }
-
- .install-split {
- flex-direction: column-reverse;
- }
-
- .install-image img {
- width: 80vw;
- height: auto;
- }
-
- .install-info {
- margin-left: 0;
- margin-top: 16px;
- }
-
- .footer {
- margin-top: 48px;
- }
-
- .footer ul {
- flex-direction: column;
- align-items: center;
- margin-bottom: 16px;
- }
-}
diff --git a/docs/img/install.png b/docs/img/install.png
deleted file mode 100644
index af9e2a1..0000000
Binary files a/docs/img/install.png and /dev/null differ
diff --git a/docs/img/screenshot.png b/docs/img/screenshot.png
deleted file mode 100644
index 0f6aaf2..0000000
Binary files a/docs/img/screenshot.png and /dev/null differ
diff --git a/docs/index.html b/docs/index.html
deleted file mode 100644
index 3271ba5..0000000
--- a/docs/index.html
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
-
-
-
-
-
- GDH - Official website
-
-
-
-
-
-
- GDH is a next-level mod menu.
- GDH is a mod menu made to be both easy to use & have a lot of features
-
-
-
- Feature Overview
-
-
- GDH has a lot of features, here are the main ones:
-
- - Basic Hacks
- - Bot called ReplayEngine
- - TPS Unlocker
- - Labels
- - Keybinds
- - Hitboxes
- - And more!
-
-
-
-
-
-
-
-
-
- Install Guide
-
-
-
-
-
- GDH is really easy to install, here's the guide:
-
- - Ensure Geode is installed
- - Open Download tab in Geode menu
- - Find GDH, install it and restart GD
- - Done! Press Tab to open integrated menu!
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/js/script.js b/docs/js/script.js
deleted file mode 100644
index b1abcc7..0000000
--- a/docs/js/script.js
+++ /dev/null
@@ -1,22 +0,0 @@
-
-var theme = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)').matches : true;
-
-var bulb = '';
-var noBulb = '';
-
-function setTheme(dark) {
- document.documentElement.style.setProperty("--background", dark ? "rgb(27, 27, 29)" : "rgb(229, 255, 240)");
- document.documentElement.style.setProperty("--foreground", dark ? "rgb(198, 243, 221)" : "rgb(27, 32, 30)");
- document.documentElement.style.setProperty("--foreground-light", dark ? "rgb(100, 100, 100)" : "rgb(130, 130, 130)");
- document.documentElement.style.setProperty("--bg-accent", dark ? "rgb(73, 110, 84)" : "rgb(178, 223, 191)");
- document.getElementById("theme-switch").innerHTML = dark ? noBulb : bulb;
-}
-
-function switchTheme() {
- theme = !theme;
- setTheme(theme);
-}
-
-window.onload = () => {
- setTheme(theme);
-}
diff --git a/src/gui.cpp b/src/gui.cpp
index eca2b83..e81c9f0 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -4,6 +4,7 @@
#include "config.hpp"
#include
#include "hacks.hpp"
+#include "memory.hpp"
std::chrono::steady_clock::time_point animationStartTime;
bool isAnimating = false;
@@ -55,6 +56,17 @@ void Gui::animateAlpha()
std::vector stretchedWindows;
void Gui::Render() {
+ #ifdef GEODE_IS_ANDROID
+ ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1);
+ ImGui::Begin("toggle gui");
+ if (ImGui::Button("toggle")) {
+ Toggle();
+ }
+ ImGui::End();
+ ImGui::PopStyleVar();
+ #endif
+
+
if (isAnimating) {
animateAlpha();
}
@@ -110,6 +122,8 @@ void Gui::Render() {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::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();
}
@@ -140,8 +154,46 @@ void Gui::Render() {
ApplyGuiColors(!inverted);
}
}
- else if (windowName == "Variables") {
- ImGui::Button("ff");
+ else if (windowName == "Framerate") {
+ bool tps_enabled = config.get("tps_enabled", false);
+ float tps_value = config.get("tps_value", 240.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);
+
+ 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);
+ }
+ else if (windowName == "Variables") {
+ static uintptr_t address = geode::base::get();
+ ImGui::Text("%s", fmt::format("{:x} ({:x})", address, address - geode::base::get()).c_str());
+
+ if (ImGui::Button("scan"))
+ address = memory::PatternScan(address+1, 0, "00 60 6A 48");
+
+ if (ImGui::Button("scan2"))
+ address = memory::PatternScan(address+1, 0, "80 67 6A 48");
+
+ if (ImGui::Button("reset"))
+ address = geode::base::get();
}
else {
for (auto& hck : win.hacks) {
@@ -160,14 +212,30 @@ void Gui::Render() {
config.set(hck.config, enabled);
if (!hck.game_var.empty())
GameManager::get()->setGameVariable(hck.game_var.c_str(), enabled);
- hck.handlerFunc(enabled);
+ if (hck.handlerFunc) hck.handlerFunc(enabled);
}
-
- ImGui::PopStyleColor();
if (ImGui::IsItemHovered() && !hck.desc.empty()) {
ImGui::SetTooltip("%s", hck.desc.c_str());
}
+
+ if (hck.handlerCustomWindow) {
+ ImGui::SameLine();
+ if (ImGui::ArrowButton(fmt::format("{} Settings", hck.name).c_str(), ImGuiDir_Right)) {
+ ImGui::OpenPopup(fmt::format("{} Settings", hck.name).c_str());
+ }
+
+ if (ImGui::BeginPopupModal(fmt::format("{} Settings", hck.name).c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
+ hck.handlerCustomWindow();
+
+ if (ImGui::Button("Close", {400 * m_scale, NULL})) {
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::EndPopup();
+ }
+ }
+
+ ImGui::PopStyleColor();
}
}
@@ -180,6 +248,10 @@ void Gui::Init() {
auto &config = Config::get();
stretchedWindows.clear();
+
+ m_scale = config.get("gui_scale", 1.f);
+ m_index_scale = config.get("gui_index_scale", 7);
+
ApplyGuiColors(config.get("gui_inverted", false));
ApplyColor(themes[config.get("gui_color_index", 0)]);
ApplyStyle(m_scale);
diff --git a/src/hacks.cpp b/src/hacks.cpp
index b6b2621..03ac695 100644
--- a/src/hacks.cpp
+++ b/src/hacks.cpp
@@ -1,11 +1,13 @@
#include "hacks.hpp"
#include "config.hpp"
+#include "gui.hpp"
+#include
void Hacks::Init() {
m_windows = {
{"Core", 10, 10, 200, 230,
{
- {"Free Window Resize", "Allows free window resizing", "free_win_resize"},
+ {"Free Window Resize", "Allows free window resizing", "free_win_resize"}, // +
{"Noclip", "The player will be invincible to obstacles", "noclip"}, // +
{"Unlock Items", "The following elements will be unlocked:\n- Icons + Colors\n- Practice Music Sync\n- Music Unlocker", "unlock_items"}, // +
{"No Respawn Blink", "Upon respawning, the cube will not produce an unpleasant flicker", "no_respawn_blink"}, // +
@@ -32,15 +34,14 @@ void Hacks::Init() {
{"Auto Pickup Coins", "Collects all coins in the level", "auto_pickup_coins"}, // +
{"Auto Practice Mode", "Auto-enables practice mode", "auto_practice_mode"}, // +
{"Auto Song Download", "Automatic downloading of song when you enter an online level", "auto_song_download"}, // +
- {"Allow Low Volume", "Removes the limit on minimum volume percentage", "allow_low_volume"}, // +
- {"Anticheat Bypass", "Disables level kicking at level completion", "anticheat_bypass"},
+ {"Allow Low Volume", "Removes the limit on minimum volume percentage", "allow_low_volume"}, // +
{"Coins In Practice", "The ability to collect coins in practice", "coins_in_practice"},
{"Confirm Exit", "Warning before level exit", "confim_exit", "0167"}, // +
{"Fast Chest Open", "Removes the delay for opening chests", "fast_chest_open"},
{"Force Dont Enter", "Disables effects when objects enter the viewable play area", "force_dont_enter"},
{"Force Dont Fade", "Disables effects when objects leave the viewable play area", "force_dont_fade"},
{"Random Seed", "Changes the seed game so that the random trigger is not triggered randomly", "random_seed"},
- {"Respawn Time", "Changes respawn time on death", "respawn_time"},
+ {"Respawn Time", "Changes respawn time on death", "respawn_time"}, // +
{"Restart Level", "Reload the level", "restart_level"},
{"Practice Mode", "Enter practice mode", "practice_mode"},
{"Ignore ESC", "Prevents exiting the level", "ignore_esc"}, // +
@@ -48,12 +49,13 @@ void Hacks::Init() {
{"Jump Hack", "Removes the barrier to jump gravity", "jump_hack"}, // +
{"Show Percentage", "Show percentages in level progress", "show_percentage", "0040"}, // +
{"Smart Startpos", "Restores correct gameplay without startpos settings", "smart_startpos"},
- {"Startpos Switcher", "The ability to switch between starting positions using the keys that you setted in keybinds", "startpos_switcher"},
- {"Reset Camera", "When switching between starting positions, the camera may move, so this feature fixes that unpleasant switch", "reset_cameara"},
+ {"Startpos Switcher", "The ability to switch between starting positions using the keys that you setted in keybinds", "startpos_switcher"}, // +
{"RGB Icons", "LGBT icons, yes :3", "rgb_icons"},
{"Solid Wave Trail", "Disables wave blending", "solid_wave_trail"},
{"Show Triggers", "Displaying triggers on the PlayLayer", "show_triggers"},
{"Show Hitboxes", "Visualizes hitbox levels", "show_hitboxes"},
+ {"Show Total Attempts", "", "show_total_attempts"}, // +
+ {"Stop triggers on death", "Stops move/rotation triggers on death so you can see what killed you", "stop_triggers_on_death"}, // +
{"All Modes Platformer", "Removes the limit on all modes in the platformer", "all_modes_platformer"},
{"Force Platformer", "Enables platformer mode in all levels", "force_platformer"},
{"Hide Attempts", "Hides the attempt count in-game", "hide_attempts", "0135"}, // +
@@ -66,32 +68,31 @@ void Hacks::Init() {
{"No Shaders", "Disabling shaders in levels", "no_shaders"}, // +
{"No Particles", "Disables resuming the particle system", "no_particles"}, // +
{"No Short Numbers", "All numbers are displayed in full\n(For example, \"1.5M\" becomes \"1500000\")", "no_short_numbers"}, // +
- {"No BG Flash", "Removes the unpleasant flicker when triggering the portal", "no_bg_flash"},
+ {"No BG Flash", "Removes the unpleasant flicker when triggering the portal", "no_bg_flash"}, // +
{"No Glow", "Disables glow on objects", "no_glow"}, // +
{"No Mirror", "Disables level mirroring", "no_mirror_portal"}, // +
{"No New Best Popup", "Disable the new best popup", "no_new_best_popup"}, // +
{"No Portal Lighting", "Disables lightning when entering mini/large portal", "no_portal_lighting"},
- {"No Pulse", "Disables pulsation of falls, orbs, etc", "no_pulse"},
+ {"No Pulse", "Disables pulsation of falls, orbs, etc", "no_pulse"}, // +
+ {"Pulse Size", "Changes pulsation of falls, orbs, etc", "pulse_size"}, // +
{"No Robot Fire", "Hides robot boost fire", "no_robot_fire"}, // +
{"No Spider Dash", "Disables spider dash trail when teleporting", "no_spider_dash"}, // +
- {"No Trail", "Removes the trail located near the player", "no_trail"},
- {"Always Trail", "Displays the trail near the player at any location", "always_trail"},
- {"No Wave Trail", "Disables the trail on the wave", "no_wave_trail"},
- {"Wave Trail Size", "Resizes the trail", "wave_trail_size"},
- {"No Wave Pulse", "Disables the pulsation of the trail on the wave", "no_wave_pulse"},
- {"Wave Trail Fix", "Corrects distortion in the wave (an attempt by RobTop to make a non-glitchy wave, but again a fail)", "wave_trail_fix"}
+ {"No Trail", "Removes the trail located near the player", "no_trail"}, // +
+ {"Always Trail", "Displays the trail near the player at any location", "always_trail"}, // +
+ {"Wave Trail Size", "Resizes the wave trail", "wave_trail_size"}, // +
+ {"Wave Trail On Death", "", "wave_trail_on_death"} // +
}
},
{"Creator", 450, 10, 220, 250,
{
{"Copy Hack", "Copy any online level without a password", "copy_hack"}, // +
{"Custom Object Bypass", "Removes the limit restricted to 1000 objects", "custom_object_bypass"},
- {"Default Song Bypass", "Removes restrictions on secret official songs", "default_song_bypass"},
- {"Scale Snap Bypass", "Removes the slider snapping when stretched from 0.97 to 1.03", "scale_snap_bypass"},
- {"Verify Hack", "Publish a level without verification", "verify_hack"},
- {"Smooth Editor Trail", "Makes the wave smoother in the editor", "smooth_editor_trail"},
+ {"Default Song Bypass", "Removes restrictions on secret official songs", "default_song_bypass"}, // +
+ {"Editor Extension", "", "editor_extension"},
+ {"Verify Hack", "Publish a level without verification", "verify_hack"}, // +
+ {"Smooth Editor Trail", "Makes the wave smoother in the editor", "smooth_editor_trail"}, // +
{"Level Edit", "Edit any online level", "level_edit"}, // +
- {"No (C) Mark", "Removes copyright on copied levels", "no_c_mark"}
+ {"No (C) Mark", "Removes copyright on copied levels", "no_c_mark"} // +
}
},
{"Framerate", 450, 270, 220, 130},
@@ -102,6 +103,107 @@ void Hacks::Init() {
};
auto &config = Config::get();
+
+ #ifdef GEODE_IS_WINDOWS
+ SetHandlerByConfig("free_win_resize", [this](bool enabled) {
+ static auto result1 = geode::Mod::get()->patch((void*)(geode::base::getCocos() + 0xd6eca), {0x90, 0x90, 0x90, 0x90, 0x90});
+ static auto patch1 = result1.isErr() ? nullptr : result1.unwrap();
+
+ static auto result2 = geode::Mod::get()->patch((void*)(geode::base::getCocos() + 0xd5089), {0x90, 0x90, 0x90, 0x90, 0x90, 0x90});
+ static auto patch2 = result1.isErr() ? nullptr : result2.unwrap();
+
+ static auto result3 = geode::Mod::get()->patch((void*)(geode::base::getCocos() + 0xd6567), {0x48, 0xe9});
+ static auto patch3 = result1.isErr() ? nullptr : result3.unwrap();
+
+ if (enabled) {
+ if (patch1) (void) patch1->enable();
+ if (patch2) (void) patch1->enable();
+ if (patch3) (void) patch1->enable();
+ } else {
+ if (patch1) (void) patch1->disable();
+ if (patch2) (void) patch1->disable();
+ if (patch3) (void) patch1->disable();
+ }
+ });
+
+ #endif
+
+ SetHandlerByConfig("editor_extension", [this](bool enabled) {
+ uintptr_t address1; //Pattern: 00 60 6A 48 (2x)
+ uintptr_t address2; //Pattern: 80 67 6A 48 (1x)
+
+ #ifdef GEODE_IS_WINDOWS
+ address1 = geode::base::get() + 0x607ca0;
+ address2 = geode::base::get() + 0x607ca4;
+ #elif defined(GEODE_IS_ANDROID64)
+ address1 = geode::base::get() + 0x65d740;
+ address2 = geode::base::get() + 0x67790c;
+ #endif
+
+
+ static auto result1 = geode::Mod::get()->patch((void*)(address1), {0x00, 0x60, 0xEA, 0x4B});
+ static auto patch1 = result1.isErr() ? nullptr : result1.unwrap();
+
+ static auto result2 = geode::Mod::get()->patch((void*)(address2), {0x00, 0x60, 0xEA, 0x4B});
+ static auto patch2 = result1.isErr() ? nullptr : result2.unwrap();
+
+ if (enabled) {
+ if (patch1) (void) patch1->enable();
+ if (patch2) (void) patch2->enable();
+ } else {
+ if (patch1) (void) patch1->disable();
+ if (patch2) (void) patch2->disable();
+ }
+ });
+
+
+ SetHandlerByConfig("hide_pause_menu", [this](bool enabled) {
+ auto pl = PlayLayer::get();
+ if (pl && pl->m_isPaused && pauseLayer != nullptr)
+ pauseLayer->setVisible(!enabled);
+ });
+
+ SetCustomWindowHandlerByConfig("wave_trail_size", [this, &config]() {
+ float value = config.get("wave_trail_size_value", 1.f);
+
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ if (ImGui::DragFloat("##waveTrailSize", &value, 0.01f, 0.0f, FLT_MAX, "Wave Trail Size: %.2f"))
+ config.set("wave_trail_size_value", value);
+ });
+
+ SetCustomWindowHandlerByConfig("respawn_time", [this, &config]() {
+ float value = config.get("respawn_time_value", 1.f);
+
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ if (ImGui::DragFloat("##respawn_time_value", &value, 0.01f, 0.0f, FLT_MAX, "Respawn Time: %.2f"))
+ config.set("respawn_time_value", value);
+ });
+
+ SetCustomWindowHandlerByConfig("pulse_size", [this, &config]() {
+ auto &gui = Gui::get();
+
+ float value = config.get("pulse_size_value", 0.5f);
+ bool multiply = config.get("pulse_multiply", false);
+
+ if (ImGuiH::Checkbox("Multiply pulsation", &multiply, gui.m_scale)) {
+ config.set("pulse_multiply", multiply);
+ }
+
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ if (ImGui::DragFloat("##pulse_size_value", &value, 0.01f, 0.0f, FLT_MAX, "Pulse Size: %.2f"))
+ config.set("pulse_size_value", value);
+ });
+
+ SetCustomWindowHandlerByConfig("startpos_switcher", [this, &config]() {
+ auto &gui = Gui::get();
+
+ bool reset_camera = config.get("startos_switcher::reset_camera", true);
+
+ if (ImGuiH::Checkbox("Reset Camera", &reset_camera, gui.m_scale)) {
+ config.set("startos_switcher::reset_camera", reset_camera);
+ }
+ });
+
for (auto& win : m_windows) {
win.orig_x = win.x;
win.orig_y = win.y;
@@ -109,15 +211,8 @@ void Hacks::Init() {
win.orig_h = win.h;
for (auto& hck : win.hacks) {
- if (!hck.game_var.empty()) {
- config.set(hck.config, GameManager::get()->getGameVariable(hck.game_var.c_str()));
- }
+ if (!hck.game_var.empty()) config.set(hck.config, GameManager::get()->getGameVariable(hck.game_var.c_str()));
+ if (config.get(hck.config, false) && hck.handlerFunc) hck.handlerFunc(true);
}
}
-
- SetHandlerByConfig("hide_pause_menu", [this](bool enabled) {
- auto pl = PlayLayer::get();
- if (pl && pl->m_isPaused && pauseLayer != nullptr)
- pauseLayer->setVisible(!enabled);
- });
}
\ No newline at end of file
diff --git a/src/hacks.hpp b/src/hacks.hpp
index ceeb5f9..ee8b74c 100644
--- a/src/hacks.hpp
+++ b/src/hacks.hpp
@@ -8,9 +8,12 @@ struct hack {
std::string config;
std::string game_var;
- std::function handlerFunc = [](bool) {};
+ std::function handlerFunc = nullptr;
void setHandler(std::function func) { handlerFunc = func; }
+ std::function handlerCustomWindow = nullptr;
+ void setCustomWindowHandler(std::function func) { handlerCustomWindow = func; }
+
int keybind = 0;
};
@@ -54,4 +57,18 @@ class Hacks {
}
return false;
}
+
+ bool SetCustomWindowHandlerByConfig(const std::string& configName, std::function handler) {
+ for (auto& window : m_windows) {
+ auto it = std::find_if(window.hacks.begin(), window.hacks.end(), [&](const hack& h) {
+ return h.config == configName;
+ });
+
+ if (it != window.hacks.end()) {
+ it->setCustomWindowHandler(handler);
+ return true;
+ }
+ }
+ return false;
+ }
};
\ No newline at end of file
diff --git a/src/hooks.cpp b/src/hooks.cpp
index a17f3c0..10f8962 100644
--- a/src/hooks.cpp
+++ b/src/hooks.cpp
@@ -1,31 +1,89 @@
+#include
#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
#include "hacks.hpp"
#include "config.hpp"
+std::vector startPositions;
+int selectedStartpos = -1;
+
+void switchStartPos(int incBy, bool direction = true) {
+ auto &config = Config::get();
+ auto pl = PlayLayer::get();
+
+ if (!pl || startPositions.empty()) return;
+
+ selectedStartpos += incBy;
+
+ if (selectedStartpos < -1)
+ selectedStartpos = startPositions.size() - 1;
+
+ if (selectedStartpos >= startPositions.size())
+ selectedStartpos = -1;
+
+ if (direction) {
+ StartPosObject* obj = selectedStartpos == -1 ? nullptr : startPositions[selectedStartpos];
+
+ pl->m_currentCheckpoint = nullptr;
+ pl->setStartPosObject(obj);
+ pl->resetLevel();
+
+ if (config.get("startos_switcher::reset_camera", true))
+ pl->resetCamera();
+
+ pl->startMusic();
+
+ return;
+ }
+}
+
+class $modify(cocos2d::CCScheduler) {
+ void update(float dt) {
+ auto &config = Config::get();
+
+ if (config.get("speedhack_enabled", false))
+ dt *= config.get("speedhack_value", 1.f);
+
+ return CCScheduler::update(dt);
+ }
+};
+
class $modify(PlayLayer) {
struct Fields {
GameObject* anticheat_obj = nullptr;
std::vector coinsObjects;
+ cocos2d::CCMenu* startposSwitcherUI;
+
+ ~Fields() {
+ startPositions.clear();
+ selectedStartpos = -1;
+ }
};
bool init(GJGameLevel* level, bool useReplay, bool dontCreateObjects) {
@@ -35,9 +93,59 @@ class $modify(PlayLayer) {
if (config.get("auto_practice_mode", false))
togglePracticeMode(true);
+ if (config.get("startpos_switcher", false) && !startPositions.empty()) {
+ auto win_size = cocos2d::CCDirector::sharedDirector()->getWinSize();
+
+ auto label = cocos2d::CCLabelBMFont::create(fmt::format("{}/{}", selectedStartpos+1, startPositions.size()).c_str(), "bigFont.fnt");
+ label->setScale(0.5f);
+ label->setPosition(win_size.width/2, 20.f);
+ label->setOpacity(100);
+ label->setID("startposSwitcherLabels"_spr);
+
+ auto left_arrow = cocos2d::CCSprite::createWithSpriteFrameName("GJ_arrow_02_001.png");
+ left_arrow->setScale(0.5f);
+ auto left_arrowClick = geode::cocos::CCMenuItemExt::createSpriteExtra(left_arrow, [this, label](CCMenuItemSpriteExtra* sender) {
+ switchStartPos(-1);
+ label->setCString(fmt::format("{}/{}", selectedStartpos+1, startPositions.size()).c_str());
+ });
+ left_arrowClick->setPosition(win_size.width/2 - 50, cocos2d::CCDirector::sharedDirector()->getScreenBottom() + left_arrowClick->getScaledContentHeight());
+ left_arrowClick->setOpacity(100);
+ left_arrowClick->setID("startposSwitcherLeftArrowClick"_spr);
+
+ auto right_arrow = cocos2d::CCSprite::createWithSpriteFrameName("GJ_arrow_02_001.png");
+ right_arrow->setScale(0.5f);
+ right_arrow->setFlipX(true);
+ auto right_arrowClick = geode::cocos::CCMenuItemExt::createSpriteExtra(right_arrow, [this, label](CCMenuItemSpriteExtra* sender) {
+ switchStartPos(1);
+ label->setCString(fmt::format("{}/{}", selectedStartpos+1, startPositions.size()).c_str());
+ });
+ right_arrowClick->setPosition(win_size.width/2 + 50, cocos2d::CCDirector::sharedDirector()->getScreenBottom() + right_arrowClick->getScaledContentHeight());
+ right_arrowClick->setOpacity(100);
+ right_arrowClick->setID("startpos_switcher_rightArrowClick"_spr);
+
+
+ m_fields->startposSwitcherUI = cocos2d::CCMenu::create();
+ m_fields->startposSwitcherUI->setID("startposSwitcherUI"_spr);
+ m_fields->startposSwitcherUI->setPosition(0, 0);
+ m_fields->startposSwitcherUI->setZOrder(999);
+
+ m_fields->startposSwitcherUI->addChild(left_arrowClick);
+ m_fields->startposSwitcherUI->addChild(right_arrowClick);
+ m_fields->startposSwitcherUI->addChild(label);
+
+ m_uiLayer->addChild(m_fields->startposSwitcherUI);
+ }
+
return true;
}
+ void togglePracticeMode(bool practiceMode) {
+ if (m_fields->startposSwitcherUI) {
+ m_fields->startposSwitcherUI->setVisible(!practiceMode);
+ }
+ PlayLayer::togglePracticeMode(practiceMode);
+ }
+
void addObject(GameObject* obj) {
auto& config = Config::get();
@@ -60,6 +168,9 @@ class $modify(PlayLayer) {
if (obj->m_objectID == 1329 || obj->m_objectID == 142) {
m_fields->coinsObjects.push_back(obj);
}
+ else if (obj->m_objectID == 31) {
+ startPositions.push_back(static_cast(obj));
+ }
}
void destroyPlayer(PlayerObject* player, GameObject* obj) {
@@ -80,6 +191,19 @@ class $modify(PlayLayer) {
PlayLayer::destroyPlayer(player, obj);
m_isTestMode = testmode;
}
+
+ if (config.get("respawn_time", false)) {
+ if (auto* respawnSequence = this->getActionByTag(0x10)) {
+ this->stopAction(respawnSequence);
+ auto* newSequence = cocos2d::CCSequence::create(
+ cocos2d::CCDelayTime::create(config.get("respawn_time_value", 1.f)),
+ cocos2d::CCCallFunc::create(this, callfunc_selector(PlayLayer::delayedResetLevel)),
+ nullptr
+ );
+ newSequence->setTag(0x10);
+ this->runAction(newSequence);
+ }
+ }
}
@@ -88,9 +212,11 @@ class $modify(PlayLayer) {
PlayLayer::resetLevel();
- if (config.get("no_do_not_flip", false) && m_attemptLabel) {
+ if (config.get("no_do_not_flip", false) && m_attemptLabel)
m_attemptLabel->setScaleY(1);
- }
+
+ if (config.get("show_total_attempts", false) && m_attemptLabel)
+ m_attemptLabel->setCString(fmt::format("Attempt {}", static_cast(m_level->m_attempts)).c_str());
if (config.get("safe_mode", false))
m_level->m_attempts = m_level->m_attempts - 1;
@@ -102,6 +228,14 @@ class $modify(PlayLayer) {
pickupItem(static_cast(coin));
}
}
+
+ if (config.get("instant_complete", false)) {
+ //bypass anticheat
+ m_timePlayed = 10.0;
+ m_bestAttemptTime = 10.0;
+
+ PlayLayer::playEndAnimationToPos({0, 0});
+ }
}
void levelComplete() {
@@ -120,16 +254,40 @@ class $modify(PlayLayer) {
if (Config::get().get("no_new_best_popup", false)) return;
PlayLayer::showNewBest(p0, p1, p2, p3, p4, p5);
}
+
+ void updateVisibility(float dt) {
+ auto& config = Config::get();
+
+ if (!config.get("pulse_size", false) && config.get("no_pulse", false))
+ m_audioEffectsLayer->m_notAudioScale = 0.5;
+
+ 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;
+ else
+ m_audioEffectsLayer->m_notAudioScale = value;
+ }
+
+ PlayLayer::updateVisibility(dt);
+ }
};
class $modify(GJBaseGameLayer) {
+ static void onModify(auto& self) {
+ (void) self.setHookPriority("GJBaseGameLayer::update", 0x99999);
+ }
+
void update(float dt) {
auto& config = Config::get();
- GJBaseGameLayer::update(dt);
+ if (config.get("stop_triggers_on_death", false) && m_player1->m_isDead || m_player2->m_isDead)
+ return;
if (config.get("jump_hack", false))
m_player1->m_isOnGround = true;
+
+ GJBaseGameLayer::update(dt);
}
bool canBeActivatedByPlayer(PlayerObject *p0, EffectGameObject *p1) {
@@ -145,6 +303,34 @@ class $modify(GJBaseGameLayer) {
GJBaseGameLayer::updateZoom(p0, p1, p2, p3, p4, p5);
}
+
+
+ void teleportPlayer(TeleportPortalObject *p0, PlayerObject *p1) {
+ auto& config = Config::get();
+ bool hasNoEffects = p0->m_hasNoEffects;
+
+ if (config.get("no_bg_flash", false))
+ p0->m_hasNoEffects = true;
+
+ GJBaseGameLayer::teleportPlayer(p0, p1);
+ p0->m_hasNoEffects = hasNoEffects;
+ }
+
+ void lightningFlash(cocos2d::CCPoint from, cocos2d::CCPoint to, cocos2d::ccColor3B color, float lineWidth, float duration, int displacement, bool flash, float opacity) {
+ auto& config = Config::get();
+ auto gm = GameManager::get();
+
+ auto performanceMode = gm->m_performanceMode;
+
+ if (config.get("no_portal_lighting", false))
+ gm->m_performanceMode = true;
+
+ if (config.get("no_bg_flash", false))
+ flash = false;
+
+ GJBaseGameLayer::lightningFlash(from, to, color, lineWidth, duration, displacement, flash, opacity);
+ gm->m_performanceMode = performanceMode;
+ }
};
class $modify(CameraTriggerGameObject) {
@@ -156,6 +342,7 @@ class $modify(CameraTriggerGameObject) {
}
};
+
class $modify(GameStatsManager) {
bool isItemUnlocked(UnlockType p0, int p1) {
if (GameStatsManager::isItemUnlocked(p0, p1))
@@ -166,9 +353,8 @@ class $modify(GameStatsManager) {
}
};
-
class $modify(PlayerObject) {
- virtual void update(float dt)
+ void update(float dt)
{
PlayerObject::update(dt);
@@ -194,11 +380,15 @@ class $modify(PlayerObject) {
PlayerObject::incrementJumps();
}
- void playSpiderDashEffect(cocos2d::CCPoint from, cocos2d::CCPoint to)
- {
+ void playSpiderDashEffect(cocos2d::CCPoint from, cocos2d::CCPoint to) {
if (!Config::get().get("no_spider_dash", false))
PlayerObject::playSpiderDashEffect(from, to);
}
+
+ void fadeOutStreak2(float p0) {
+ if (!Config::get().get("wave_trail_on_death", false))
+ PlayerObject::fadeOutStreak2(p0);
+ }
};
class $modify(CCTextInputNode){
@@ -274,7 +464,7 @@ class $modify(GJScaleControl) {
if (m_sliderXY && m_sliderXY->m_touchLogic->m_activateThumb) {
m_sliderXY->getThumb()->setPositionX(convertToNodeSpace(touch->getLocation()).x);
m_sliderXY->updateBar();
- float value = m_sliderXY->getThumb()->getValue();
+ float value = scaleFromValue(m_sliderXY->getThumb()->getValue());
updateLabelXY(value);
sliderChanged(m_sliderXY->getThumb());
if (EditorUI::get())
@@ -514,4 +704,101 @@ class $modify(GameToolbox) {
gd::string str = fmt::format("{}", value);
return str;
}
+};
+
+class $modify(HardStreak) {
+ void updateStroke(float p0) {
+ auto& config = Config::get();
+
+ if (config.get("wave_trail_size", false))
+ m_pulseSize = config.get("wave_trail_size_value", 1.f);
+
+ HardStreak::updateStroke(p0);
+ }
+};
+
+class $modify(cocos2d::CCMotionStreak) {
+ void update(float delta) {
+ auto& config = Config::get();
+ if (config.get("always_trail", false))
+ m_bStroke = true;
+
+ if (config.get("no_trail", false))
+ m_bStroke = false;
+
+ CCMotionStreak::update(delta);
+ }
+};
+
+
+class $modify(SongSelectNode) {
+ void audioNext(cocos2d::CCObject* p0) {
+ if (Config::get().get("default_song_bypass", false)) {
+ m_selectedSongID++;
+ getLevelSettings()->m_level->m_audioTrack = m_selectedSongID;
+
+ return SongSelectNode::updateAudioLabel();
+ }
+
+ SongSelectNode::audioNext(p0);
+ }
+
+ void audioPrevious(cocos2d::CCObject* p0) {
+ if (Config::get().get("default_song_bypass", false)) {
+ m_selectedSongID--;
+ getLevelSettings()->m_level->m_audioTrack = m_selectedSongID;
+
+ return SongSelectNode::updateAudioLabel();
+ }
+
+ SongSelectNode::audioPrevious(p0);
+ }
+};
+
+class $modify(MoreSearchLayer) {
+ void audioPrevious(cocos2d::CCObject* sender) {
+ if (!Config::get().get("default_song_bypass", false))
+ return audioPrevious(sender);
+
+ int song = std::max(1, GameLevelManager::get()->getIntForKey("song_filter"));
+ MoreSearchLayer::selectSong(--song);
+ }
+
+ void audioNext(cocos2d::CCObject* sender) {
+ if (!Config::get().get("default_song_bypass", false))
+ return audioNext(sender);
+
+ int song = std::max(1, GameLevelManager::get()->getIntForKey("song_filter"));
+ MoreSearchLayer::selectSong(++song);
+ }
+
+ void selectSong(int songID) {
+ if (!Config::get().get("default_song_bypass", false))
+ return selectSong(songID);
+
+ GameLevelManager::get()->setIntForKey(songID, "song_filter");
+ updateAudioLabel();
+ }
+};
+
+class $modify(EditLevelLayer) {
+ bool init(GJGameLevel *gjgl) {
+ if (Config::get().get("verify_hack", false))
+ gjgl->m_isVerified = true;
+ return EditLevelLayer::init(gjgl);
+ }
+
+ void onShare(CCObject* sender) {
+ if (Config::get().get("no_c_mark", false)) m_level->m_originalLevel = 0;
+
+ EditLevelLayer::onShare(sender);
+ }
+};
+
+class $modify(LevelEditorLayer) {
+ void postUpdate(float dt) {
+ if (Config::get().get("smooth_editor_trail", false))
+ m_trailTimer = 0.1f;
+ LevelEditorLayer::postUpdate(dt);
+ }
};
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index b4fd27a..5fb7764 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -36,6 +36,7 @@ class $modify(MenuLayer) {
}
};
+#ifdef GEODE_IS_WINDOWS
class $modify(cocos2d::CCEGLView) {
void onGLFWKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
CCEGLView::onGLFWKeyCallback(window, key, scancode, action, mods);
@@ -49,4 +50,5 @@ class $modify(cocos2d::CCEGLView) {
}
}
}
-};
\ No newline at end of file
+};
+#endif
\ No newline at end of file
diff --git a/src/memory.cpp b/src/memory.cpp
new file mode 100644
index 0000000..bb35aec
--- /dev/null
+++ b/src/memory.cpp
@@ -0,0 +1,46 @@
+#include "memory.hpp"
+
+struct PatternByte {
+ bool isWildcard;
+ uint8_t value;
+};
+
+uintptr_t memory::PatternScan(uintptr_t base, uintptr_t scanSize, const std::string signature) {
+ std::vector patternData;
+
+ for (size_t i = 0; i < signature.size(); ++i) {
+ if (signature[i] == ' ') {
+ continue;
+ }
+
+ if (signature[i] == '?') {
+ patternData.push_back({ true, 0 });
+ }
+ else {
+ std::string byteStr = signature.substr(i, 2);
+ patternData.push_back({ false, static_cast(std::stoul(byteStr, nullptr, 16)) });
+ i++;
+ }
+ }
+
+ for (uintptr_t i = base; /*i < base + scanSize*/; ++i) {
+ bool found = true;
+
+ for (size_t j = 0; j < patternData.size(); ++j) {
+ if (patternData[j].isWildcard) {
+ continue;
+ }
+
+ if (patternData[j].value != *reinterpret_cast(i + j)) {
+ found = false;
+ break;
+ }
+ }
+
+ if (found) {
+ return i;
+ }
+ }
+
+ return 0;
+}
\ No newline at end of file
diff --git a/src/memory.hpp b/src/memory.hpp
new file mode 100644
index 0000000..d87f17e
--- /dev/null
+++ b/src/memory.hpp
@@ -0,0 +1,9 @@
+#pragma once
+#include
+#include
+#include
+#include
+
+namespace memory {
+ uintptr_t PatternScan(uintptr_t base, uintptr_t scanSize, const std::string signature);
+}
\ No newline at end of file