diff --git a/.vscode/settings.json b/.vscode/settings.json index ee84d275..f9e4b76f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -88,7 +88,12 @@ "unordered_set": "cpp", "valarray": "cpp", "variant": "cpp", - "xtree": "cpp" + "xtree": "cpp", + "codecvt": "cpp", + "coroutine": "cpp", + "resumable": "cpp", + "stack": "cpp" }, - "C_Cpp.default.configurationProvider": "maxmitti.cmake-tools-fork" + "C_Cpp.default.configurationProvider": "maxmitti.cmake-tools-fork", + "C_Cpp.errorSquiggles": "disabled" } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f890f70..1786a3e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,9 +29,10 @@ target_include_directories(${PROJECT_NAME} PRIVATE "${SRC_DIR}" "${imgui_SOURCE_DIR}" "${minhook_SOURCE_DIR}/include" + "${minhook_SOURCE_DIR}/src/hde" "${rdr_classes_SOURCE_DIR}" "${vulkan_SOURCE_DIR}/include" ) message(STATUS "Setting up linked libraries") -target_link_libraries(${PROJECT_NAME} PRIVATE AsyncLogger imgui minhook nlohmann_json::nlohmann_json "${DEPS_DIR}/vulkan-1.lib") \ No newline at end of file +target_link_libraries(${PROJECT_NAME} PRIVATE AsyncLogger imgui minhook nlohmann_json::nlohmann_json dbghelp "${DEPS_DIR}/vulkan-1.lib") \ No newline at end of file diff --git a/cmake/rdr-classes.cmake b/cmake/rdr-classes.cmake index a0578cf0..623f3b93 100644 --- a/cmake/rdr-classes.cmake +++ b/cmake/rdr-classes.cmake @@ -3,7 +3,7 @@ include(FetchContent) FetchContent_Declare( rdr_classes GIT_REPOSITORY https://github.com/YimMenu/RDR-Classes.git - GIT_TAG 6bf009fae77419a9a1e249ddb711987422cf9822 + GIT_TAG 0132075d1c6a88066ce6f6225f168a49e76a51a3 GIT_PROGRESS TRUE CONFIGURE_COMMAND "" BUILD_COMMAND "" diff --git a/src/common.hpp b/src/common.hpp index 2dd0d3ff..81b1c30a 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -1,5 +1,6 @@ #pragma once #define WIN32_LEAN_AND_MEAN +#define NOMINMAX #include #include diff --git a/src/core/commands/BoolCommand.cpp b/src/core/commands/BoolCommand.cpp index 88d918d9..0d2fa911 100644 --- a/src/core/commands/BoolCommand.cpp +++ b/src/core/commands/BoolCommand.cpp @@ -1,4 +1,5 @@ #include "BoolCommand.hpp" +#include "game/backend/FiberPool.hpp" // TODO: game import in core namespace YimMenu { @@ -12,6 +13,16 @@ namespace YimMenu SetState(!m_State); } + void BoolCommand::SaveState(nlohmann::json& value) + { + value = m_State; + } + + void BoolCommand::LoadState(nlohmann::json& value) + { + m_State = value; + } + bool BoolCommand::GetState() { return m_State; @@ -20,10 +31,20 @@ namespace YimMenu void BoolCommand::SetState(bool state) { if (state && !m_State) - OnEnable(); + FiberPool::Push([this] { + OnEnable(); + }); else if (!state && m_State) - OnDisable(); + FiberPool::Push([this] { + OnDisable(); + }); m_State = state; + MarkDirty(); + } + + void BoolCommand::Shutdown() + { + OnDisable(); } } \ No newline at end of file diff --git a/src/core/commands/BoolCommand.hpp b/src/core/commands/BoolCommand.hpp index 7e29ded6..62b083cf 100644 --- a/src/core/commands/BoolCommand.hpp +++ b/src/core/commands/BoolCommand.hpp @@ -9,6 +9,8 @@ namespace YimMenu virtual void OnEnable(){}; virtual void OnDisable(){}; virtual void OnCall() override; + virtual void SaveState(nlohmann::json& value) override; + virtual void LoadState(nlohmann::json& value) override; bool m_State = false; @@ -16,5 +18,6 @@ namespace YimMenu BoolCommand(std::string name, std::string label, std::string description); bool GetState(); void SetState(bool state); + void Shutdown(); }; } \ No newline at end of file diff --git a/src/core/commands/Command.cpp b/src/core/commands/Command.cpp index b977a3a3..2aa2f039 100644 --- a/src/core/commands/Command.cpp +++ b/src/core/commands/Command.cpp @@ -18,4 +18,9 @@ namespace YimMenu { OnCall(); } + + void Command::MarkDirty() + { + Commands::MarkDirty(); + } } \ No newline at end of file diff --git a/src/core/commands/Command.hpp b/src/core/commands/Command.hpp index 0620a6b2..02e3bf4d 100644 --- a/src/core/commands/Command.hpp +++ b/src/core/commands/Command.hpp @@ -16,12 +16,15 @@ namespace YimMenu protected: virtual void OnCall() = 0; - + void MarkDirty(); public: Command(std::string name, std::string label, std::string description, int num_args = 0); void Call(); + virtual void SaveState(nlohmann::json& value){}; + virtual void LoadState(nlohmann::json& value){}; + const std::string& GetName() { return m_Name; diff --git a/src/core/commands/Commands.cpp b/src/core/commands/Commands.cpp index 01c78b50..5508eefd 100644 --- a/src/core/commands/Commands.cpp +++ b/src/core/commands/Commands.cpp @@ -4,6 +4,11 @@ namespace YimMenu { + Commands::Commands() : + IStateSerializer("commands") + { + } + void Commands::AddCommandImpl(Command* command) { m_Commands.insert({command->GetHash(), command}); @@ -27,4 +32,31 @@ namespace YimMenu return it->second; return nullptr; } + + void Commands::SaveStateImpl(nlohmann::json& state) + { + for (auto& command : m_Commands) + { + if (!state.contains(command.second->GetName())) + state[command.second->GetName()] = nlohmann::json::object(); + + command.second->SaveState(state[command.second->GetName()]); + } + } + + void Commands::LoadStateImpl(nlohmann::json& state) + { + for (auto& command : m_Commands) + { + if (state.contains(command.second->GetName())) + command.second->LoadState(state[command.second->GetName()]); + } + } + + void Commands::ShutdownImpl() + { + for (auto& command : m_LoopedCommands) + if (command->GetState()) + command->Shutdown(); + } } \ No newline at end of file diff --git a/src/core/commands/Commands.hpp b/src/core/commands/Commands.hpp index 2ccba107..79b7e6ae 100644 --- a/src/core/commands/Commands.hpp +++ b/src/core/commands/Commands.hpp @@ -1,17 +1,19 @@ #pragma once #include "util/Joaat.hpp" +#include "core/settings/IStateSerializer.hpp" namespace YimMenu { class Command; class LoopedCommand; - class Commands + class Commands : + private IStateSerializer { private: std::unordered_map m_Commands; std::vector m_LoopedCommands; - Commands(){}; + Commands(); public: static void AddCommand(Command* command) @@ -46,11 +48,24 @@ namespace YimMenu return GetInstance().m_LoopedCommands; } + static void MarkDirty() + { + GetInstance().MarkStateDirty(); + } + + static void Shutdown() + { + GetInstance().ShutdownImpl(); + } + private: void AddCommandImpl(Command* command); void AddLoopedCommandImpl(LoopedCommand* command); void RunLoopedCommandsImpl(); Command* GetCommandImpl(joaat_t hash); + virtual void SaveStateImpl(nlohmann::json& state) override; + virtual void LoadStateImpl(nlohmann::json& state) override; + void ShutdownImpl(); static Commands& GetInstance() { diff --git a/src/core/commands/HotkeySystem.cpp b/src/core/commands/HotkeySystem.cpp index c75a254d..7bc9c86e 100644 --- a/src/core/commands/HotkeySystem.cpp +++ b/src/core/commands/HotkeySystem.cpp @@ -1,33 +1,29 @@ #include "HotkeySystem.hpp" -#include "game/rdr/Natives.hpp" +#include "game/rdr/Natives.hpp" // TODO: game import in core #include "game/backend/ScriptMgr.hpp" #include "Commands.hpp" #include "LoopedCommand.hpp" -// TODO: hotkeys - +// TODO: serialization isn't stable namespace YimMenu { + HotkeySystem::HotkeySystem() : + IStateSerializer("hotkeys") + { + } void HotkeySystem::RegisterCommands() { auto Commands = Commands::GetCommands(); - auto LoopedCommands = Commands::GetLoopedCommands(); for (auto [Hash, Command] : Commands) { - CommandLink link(false); + CommandLink link; m_CommandHotkeys.insert(std::make_pair(Command->GetHash(), link)); } - for (auto looped_command : LoopedCommands) - { - CommandLink link(true); - m_CommandHotkeys.insert(std::make_pair(looped_command->GetHash(), link)); - } - LOG(INFO) << "Registered " << m_CommandHotkeys.size() << " commands"; } @@ -41,7 +37,7 @@ namespace YimMenu return false; }; - //VK_OEM_CLEAR Is about the limit in terms of virtual key codes + // VK_OEM_CLEAR Is about the limit in terms of virtual key codes for (int i = 0; i < VK_OEM_CLEAR; i++) { if ((GetKeyState(i) & 0x8000) && i != 1 && !IsKeyBlacklisted(i)) @@ -54,7 +50,8 @@ namespace YimMenu return false; } - //Will return the keycode if there are no labels + + // Will return the keycode if there are no labels std::string HotkeySystem::GetHotkeyLabel(int HotkeyModifier) { char KeyName[32]; @@ -66,7 +63,7 @@ namespace YimMenu return KeyName; } - //Meant to be called in a loop + // Meant to be called in a loop void HotkeySystem::CreateHotkey(std::vector& Hotkey) { static auto IsKeyUnique = [this](int Key, std::vector List) -> bool { @@ -88,6 +85,8 @@ namespace YimMenu Hotkey.push_back(PressedKey); } } + + MarkStateDirty(); } void HotkeySystem::FeatureCommandsHotkeyLoop() @@ -109,27 +108,35 @@ namespace YimMenu if (AllKeysPressed && GetForegroundWindow() == Pointers.Hwnd) { - if (Link.Looped) - { - auto LoopedCommand_ = Commands::GetCommand(Hash); - - if (LoopedCommand_) - LoopedCommand_->SetState(!LoopedCommand_->GetState()); - - LOG(INFO) << "Hotkey detected for looped command " << LoopedCommand_->GetName(); - } - else + auto Command = Commands::GetCommand(Hash); + if (Command) { - auto Command = Commands::GetCommand(Hash); - if (Command) - { - Command->Call(); - LOG(INFO) << "Hotkey detected for command " << Command->GetName(); - } + Command->Call(); + LOG(INFO) << "Hotkey detected for command " << Command->GetName(); } - + ScriptMgr::Yield(100ms); } } } + + void HotkeySystem::SaveStateImpl(nlohmann::json& state) + { + for (auto& hotkey : m_CommandHotkeys) + { + if (!hotkey.second.Hotkey.empty()) + { + state[std::to_string(hotkey.first).data()] = hotkey.second.Hotkey; + } + } + } + + void HotkeySystem::LoadStateImpl(nlohmann::json& state) + { + for (auto& [key, value] : state.items()) + { + if (m_CommandHotkeys.contains(std::atoi(key.data()))) + m_CommandHotkeys[std::atoi(key.data())].Hotkey = value.get>(); + } + } } diff --git a/src/core/commands/HotkeySystem.hpp b/src/core/commands/HotkeySystem.hpp index d1f8fef1..1283efd0 100644 --- a/src/core/commands/HotkeySystem.hpp +++ b/src/core/commands/HotkeySystem.hpp @@ -1,25 +1,23 @@ #pragma once -#include "../../common.hpp" +#include "core/settings/IStateSerializer.hpp" namespace YimMenu { struct CommandLink { public: - bool Looped; std::vector Hotkey{}; + bool Listening = false; - CommandLink(bool looped) : - Looped(looped) - { - } - - bool Listening; + CommandLink(){}; }; - class HotkeySystem + class HotkeySystem : + private IStateSerializer { public: + HotkeySystem(); + std::map m_CommandHotkeys; void RegisterCommands(); bool ListenAndApply(int& Hotkey, std::vector blacklist = {0}); @@ -27,6 +25,9 @@ namespace YimMenu void CreateHotkey(std::vector& Hotkey); void FeatureCommandsHotkeyLoop(); + + virtual void SaveStateImpl(nlohmann::json& state) override; + virtual void LoadStateImpl(nlohmann::json& state) override; }; inline HotkeySystem g_HotkeySystem; diff --git a/src/core/filemgr/File.hpp b/src/core/filemgr/File.hpp index 9f3534cd..99f13f38 100644 --- a/src/core/filemgr/File.hpp +++ b/src/core/filemgr/File.hpp @@ -7,7 +7,6 @@ namespace YimMenu { public: File(const std::filesystem::path& path); - File Move(std::filesystem::path newPath); }; diff --git a/src/core/frontend/Notifications.cpp b/src/core/frontend/Notifications.cpp new file mode 100644 index 00000000..d8a73570 --- /dev/null +++ b/src/core/frontend/Notifications.cpp @@ -0,0 +1,98 @@ +#include "Notifications.hpp" + +#include "core/logger/LogHelper.hpp" +#include "game/backend/FiberPool.hpp" // TODO: game import in core + +namespace YimMenu +{ + void Notifications::ShowImpl(std::string title, std::string message, NotificationType type, int duration, std::function context_function, std::string context_function_name) + { + if (title.empty() || message.empty()) + return; + + auto exists = std::find_if(m_Notifications.begin(), m_Notifications.end(), [&](auto& notification) { + return notification.second.GetIdentifier() == std::string(title + message); + }); + + if (exists != m_Notifications.end()) + return; + + Notification notification{}; + notification.m_Title = title; + notification.m_Message = message; + notification.m_Type = type; + notification.m_created_on = std::chrono::system_clock::now(); + notification.m_Duration = duration; + + if (context_function) + { + notification.m_context_function = context_function; + notification.m_context_function_name = context_function_name.empty() ? "Context Function" : context_function_name; + } + + m_Notifications.insert(std::make_pair(title + message, notification)); + } + + // Could arguably look a bit nicer + static void DrawNotification(Notification& notification, int position) + { + float y_pos = position * 100; + ImVec2 cardSize(350, 100); + + ImGui::SetNextWindowSize(cardSize, ImGuiCond_Always); + ImGui::SetNextWindowPos(ImVec2(10, y_pos + 10), ImGuiCond_Always); + + std::string windowTitle = "Notification " + std::to_string(position + 1); + ImGui::Begin(windowTitle.c_str(), nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + auto timeElapsed = (float)std::chrono::duration_cast( + std::chrono::system_clock::now() - notification.m_created_on) + .count(); + + auto depletionProgress = 1.0f - ( timeElapsed / (float) notification.m_Duration); + + ImGui::ProgressBar(depletionProgress, ImVec2(-1, 1), ""); + + // TODO: Add icon for type + if (notification.m_Type == NotificationType::Info) + ImGui::Text("Info: %s", notification.m_Title.c_str()); + else if (notification.m_Type == NotificationType::Success) + ImGui::Text("Success: %s", notification.m_Title.c_str()); + else if (notification.m_Type == NotificationType::Warning) + ImGui::Text("Warning: %s", notification.m_Title.c_str()); + else if (notification.m_Type == NotificationType::Error) + ImGui::Text("Error: %s", notification.m_Title.c_str()); + + ImGui::Separator(); + + ImGui::TextWrapped("%s", notification.m_Message.c_str()); + + if (notification.m_context_function) + { + ImGui::Spacing(); + ImGui::Separator(); + if (ImGui::Selectable(notification.m_context_function_name.c_str())) + FiberPool::Push([notification] { + notification.m_context_function(); + }); + } + + ImGui::End(); + } + + void Notifications::DrawImpl() + { + int position = 0; + for (auto& [id, notification] : m_Notifications) + { + DrawNotification(notification, position); + position++; + + if ((float)std::chrono::duration_cast( + std::chrono::system_clock::now() - notification.m_created_on) + .count() + >= notification.m_Duration) + m_Notifications.erase(id); + } + } +} \ No newline at end of file diff --git a/src/core/frontend/Notifications.hpp b/src/core/frontend/Notifications.hpp new file mode 100644 index 00000000..c065176d --- /dev/null +++ b/src/core/frontend/Notifications.hpp @@ -0,0 +1,57 @@ +#pragma once + +namespace YimMenu +{ + enum class NotificationType + { + Info, + Success, + Warning, + Error + }; + + struct Notification + { + NotificationType m_Type; + std::string m_Title; + std::string m_Message; + std::chrono::time_point m_created_on; + int m_Duration; + std::function m_context_function; + std::string m_context_function_name; + + std::string GetIdentifier() + { + return m_Title + m_Message; + } + }; + + class Notifications + { + private: + std::unordered_map m_Notifications = {}; + + // duration is in milliseconds + void ShowImpl(std::string title, std::string message, NotificationType type, int duration, std::function context_function, std::string context_function_name); + void DrawImpl(); + + static Notifications& GetInstance() + { + static Notifications instance; + return instance; + } + + public: + static void Show(std::string title, std::string message, NotificationType type = NotificationType::Info, int duration = 5000, std::function context_function = nullptr, std::string context_function_name = "") + { + GetInstance().ShowImpl(title, message, type, duration, context_function, context_function_name); + } + + static void Draw() + { + GetInstance().DrawImpl(); + } + + }; + +} \ No newline at end of file diff --git a/src/core/frontend/manager/Category.cpp b/src/core/frontend/manager/Category.cpp new file mode 100644 index 00000000..0107f857 --- /dev/null +++ b/src/core/frontend/manager/Category.cpp @@ -0,0 +1,10 @@ +#include "Category.hpp" + +namespace YimMenu +{ + void Category::Draw() + { + for (auto& item : m_Items) + item->Draw(); + } +} \ No newline at end of file diff --git a/src/core/frontend/manager/Category.hpp b/src/core/frontend/manager/Category.hpp new file mode 100644 index 00000000..ea660d3d --- /dev/null +++ b/src/core/frontend/manager/Category.hpp @@ -0,0 +1,27 @@ +#pragma once +#include "UIItem.hpp" + +namespace YimMenu +{ + class Category + { + public: + constexpr Category(std::string_view name) : + m_Name(name) + { + } + + void AddItem(std::shared_ptr&& item) + { + m_Items.push_back(std::move(item)); + } + + void Draw(); + + private: + std::vector> m_Items; + + public: + std::string m_Name; + }; +} \ No newline at end of file diff --git a/src/core/frontend/manager/Submenu.cpp b/src/core/frontend/manager/Submenu.cpp new file mode 100644 index 00000000..50e1a4a9 --- /dev/null +++ b/src/core/frontend/manager/Submenu.cpp @@ -0,0 +1,54 @@ +#include "Submenu.hpp" + +namespace YimMenu +{ + void Submenu::SetActiveCategory(const std::shared_ptr category) + { + m_ActiveCategory = category; + } + + void Submenu::AddCategory(std::shared_ptr&& category) + { + if (!m_ActiveCategory) + m_ActiveCategory = category; + + m_Categories.push_back(std::move(category)); + } + + void Submenu::DrawCategorySelectors() + { + for (auto& category : m_Categories) + { + if (category) + { + auto& style = ImGui::GetStyle(); + auto color = style.Colors[ImGuiCol_Button]; + color.w -= 0.5; + + auto active = category == GetActiveCategory(); + + if (active) + ImGui::PushStyleColor(ImGuiCol_Button, color); + + if (ImGui::Button(category->m_Name.data(), ImVec2(75, 35))) + { + SetActiveCategory(category); + } + + if (active) + ImGui::PopStyleColor(); + + if (m_Categories.back() != category) + ImGui::SameLine(); + } + } + } + + void Submenu::Draw() + { + if (m_ActiveCategory) + { + m_ActiveCategory->Draw(); + } + } +} \ No newline at end of file diff --git a/src/core/frontend/manager/Submenu.hpp b/src/core/frontend/manager/Submenu.hpp new file mode 100644 index 00000000..7ebbd85f --- /dev/null +++ b/src/core/frontend/manager/Submenu.hpp @@ -0,0 +1,33 @@ +#pragma once +#include "Category.hpp" + +namespace YimMenu +{ + class Submenu + { + public: + constexpr Submenu(std::string name, std::string icon = "") : + m_Name(name), + m_Icon(icon) + { + } + + std::shared_ptr GetActiveCategory() const + { + return m_ActiveCategory; + } + + void AddCategory(std::shared_ptr&& category); + void DrawCategorySelectors(); + void SetActiveCategory(const std::shared_ptr category); + void Draw(); + + private: + std::shared_ptr m_ActiveCategory; + + public: + std::vector> m_Categories; + std::string m_Name; + std::string m_Icon; // currently unused + }; +} \ No newline at end of file diff --git a/src/core/frontend/manager/UIItem.hpp b/src/core/frontend/manager/UIItem.hpp new file mode 100644 index 00000000..c54d713f --- /dev/null +++ b/src/core/frontend/manager/UIItem.hpp @@ -0,0 +1,12 @@ +#pragma once +#include "util/Joaat.hpp" + +namespace YimMenu +{ + // Preferably, all items should be saved in a global instance to make UI elements searchable + class UIItem + { + public: + virtual void Draw() = 0; + }; +} \ No newline at end of file diff --git a/src/core/frontend/manager/UIManager.cpp b/src/core/frontend/manager/UIManager.cpp new file mode 100644 index 00000000..a76e9e07 --- /dev/null +++ b/src/core/frontend/manager/UIManager.cpp @@ -0,0 +1,81 @@ +#include "UIManager.hpp" + +namespace YimMenu +{ + void UIManager::AddSubmenuImpl(const std::shared_ptr&& submenu) + { + if (!m_ActiveSubmenu) + m_ActiveSubmenu = submenu; + + m_Submenus.push_back(std::move(submenu)); + } + + void UIManager::SetActiveSubmenuImpl(const std::shared_ptr Submenu) + { + m_ActiveSubmenu = Submenu; + } + + void UIManager::DrawImpl() + { + const auto& pos = ImGui::GetCursorPos(); + + if (ImGui::BeginChild("##submenus", ImVec2(120, ImGui::GetContentRegionAvail().y - 20), true)) + { + for (auto& submenu : m_Submenus) + { + if (ImGui::Selectable(submenu->m_Name.data(), (submenu == m_ActiveSubmenu))) + { + SetActiveSubmenu(submenu); + } + } + } + ImGui::EndChild(); + + ImGui::Text("HorseMenu"); + + ImGui::SetCursorPos(ImVec2(pos.x + 130, pos.y)); + + if (ImGui::BeginChild("##minisubmenus", ImVec2(0, 50), true, ImGuiWindowFlags_NoScrollbar)) + { + if (m_ActiveSubmenu) + m_ActiveSubmenu->DrawCategorySelectors(); + } + ImGui::EndChild(); + + ImGui::SetCursorPos(ImVec2(pos.x + 130, pos.y + 60)); + + if (ImGui::BeginChild("##options", ImVec2(0, 0), true)) + { + if (m_OptionsFont) + ImGui::PushFont(m_OptionsFont); + + if (m_ActiveSubmenu) + m_ActiveSubmenu->Draw(); + + if (m_OptionsFont) + ImGui::PopFont(); + } + + ImGui::EndChild(); + } + + std::shared_ptr UIManager::GetActiveSubmenuImpl() + { + if (m_ActiveSubmenu) + { + return m_ActiveSubmenu; + } + + return nullptr; + } + + std::shared_ptr UIManager::GetActiveCategoryImpl() + { + if (m_ActiveSubmenu) + { + return m_ActiveSubmenu->GetActiveCategory(); + } + + return nullptr; + } +} \ No newline at end of file diff --git a/src/core/frontend/manager/UIManager.hpp b/src/core/frontend/manager/UIManager.hpp new file mode 100644 index 00000000..1fba1cd7 --- /dev/null +++ b/src/core/frontend/manager/UIManager.hpp @@ -0,0 +1,58 @@ +#pragma once +#include "Category.hpp" +#include "Submenu.hpp" + +namespace YimMenu +{ + class UIManager + { + public: + static void AddSubmenu(const std::shared_ptr&& submenu) + { + GetInstance().AddSubmenuImpl(std::move(submenu)); + } + + static void SetActiveSubmenu(const std::shared_ptr submenu) + { + GetInstance().SetActiveSubmenuImpl(submenu); + } + + static void Draw() + { + GetInstance().DrawImpl(); + } + + static std::shared_ptr GetActiveSubmenu() + { + return GetInstance().GetActiveSubmenuImpl(); + } + + static std::shared_ptr GetActiveCategory() + { + return GetInstance().GetActiveCategoryImpl(); + } + + static void SetOptionsFont(ImFont* font) + { + GetInstance().m_OptionsFont = font; + } + + private: + static inline UIManager& GetInstance() + { + static UIManager instance; + return instance; + } + + void AddSubmenuImpl(const std::shared_ptr&& submenu); + void SetActiveSubmenuImpl(const std::shared_ptr submenu); + void DrawImpl(); + std::shared_ptr GetActiveSubmenuImpl(); + std::shared_ptr GetActiveCategoryImpl(); + + std::shared_ptr m_ActiveSubmenu; + std::vector> m_Submenus; + ImFont* m_OptionsFont = nullptr; + }; +} + diff --git a/src/game/frontend/imguiwidgets/toggle/imgui_offset_rect.hpp b/src/core/frontend/widgets/toggle/imgui_offset_rect.hpp similarity index 100% rename from src/game/frontend/imguiwidgets/toggle/imgui_offset_rect.hpp rename to src/core/frontend/widgets/toggle/imgui_offset_rect.hpp diff --git a/src/game/frontend/imguiwidgets/toggle/imgui_toggle.cpp b/src/core/frontend/widgets/toggle/imgui_toggle.cpp similarity index 100% rename from src/game/frontend/imguiwidgets/toggle/imgui_toggle.cpp rename to src/core/frontend/widgets/toggle/imgui_toggle.cpp diff --git a/src/game/frontend/imguiwidgets/toggle/imgui_toggle.hpp b/src/core/frontend/widgets/toggle/imgui_toggle.hpp similarity index 100% rename from src/game/frontend/imguiwidgets/toggle/imgui_toggle.hpp rename to src/core/frontend/widgets/toggle/imgui_toggle.hpp diff --git a/src/game/frontend/imguiwidgets/toggle/imgui_toggle_math.hpp b/src/core/frontend/widgets/toggle/imgui_toggle_math.hpp similarity index 100% rename from src/game/frontend/imguiwidgets/toggle/imgui_toggle_math.hpp rename to src/core/frontend/widgets/toggle/imgui_toggle_math.hpp diff --git a/src/game/frontend/imguiwidgets/toggle/imgui_toggle_palette.cpp b/src/core/frontend/widgets/toggle/imgui_toggle_palette.cpp similarity index 100% rename from src/game/frontend/imguiwidgets/toggle/imgui_toggle_palette.cpp rename to src/core/frontend/widgets/toggle/imgui_toggle_palette.cpp diff --git a/src/game/frontend/imguiwidgets/toggle/imgui_toggle_palette.hpp b/src/core/frontend/widgets/toggle/imgui_toggle_palette.hpp similarity index 100% rename from src/game/frontend/imguiwidgets/toggle/imgui_toggle_palette.hpp rename to src/core/frontend/widgets/toggle/imgui_toggle_palette.hpp diff --git a/src/game/frontend/imguiwidgets/toggle/imgui_toggle_presets.cpp b/src/core/frontend/widgets/toggle/imgui_toggle_presets.cpp similarity index 100% rename from src/game/frontend/imguiwidgets/toggle/imgui_toggle_presets.cpp rename to src/core/frontend/widgets/toggle/imgui_toggle_presets.cpp diff --git a/src/game/frontend/imguiwidgets/toggle/imgui_toggle_presets.hpp b/src/core/frontend/widgets/toggle/imgui_toggle_presets.hpp similarity index 100% rename from src/game/frontend/imguiwidgets/toggle/imgui_toggle_presets.hpp rename to src/core/frontend/widgets/toggle/imgui_toggle_presets.hpp diff --git a/src/game/frontend/imguiwidgets/toggle/imgui_toggle_renderer.cpp b/src/core/frontend/widgets/toggle/imgui_toggle_renderer.cpp similarity index 100% rename from src/game/frontend/imguiwidgets/toggle/imgui_toggle_renderer.cpp rename to src/core/frontend/widgets/toggle/imgui_toggle_renderer.cpp diff --git a/src/game/frontend/imguiwidgets/toggle/imgui_toggle_renderer.hpp b/src/core/frontend/widgets/toggle/imgui_toggle_renderer.hpp similarity index 100% rename from src/game/frontend/imguiwidgets/toggle/imgui_toggle_renderer.hpp rename to src/core/frontend/widgets/toggle/imgui_toggle_renderer.hpp diff --git a/src/core/hooking/Hooking.cpp b/src/core/hooking/Hooking.cpp index d59c38a1..2e8d2daf 100644 --- a/src/core/hooking/Hooking.cpp +++ b/src/core/hooking/Hooking.cpp @@ -31,7 +31,26 @@ namespace YimMenu BaseHook::Add(new DetourHook("RunScriptThreads", Pointers.RunScriptThreads, Hooks::Script::RunScriptThreads)); + BaseHook::Add(new DetourHook("SendMetric", Pointers.SendMetric, Hooks::Anticheat::SendMetric)); + BaseHook::Add(new DetourHook("QueueDependency", Pointers.QueueDependency, Hooks::Anticheat::QueueDependency)); + BaseHook::Add(new DetourHook("UnkFunction", Pointers.UnkFunction, Hooks::Anticheat::UnkFunction)); + + BaseHook::Add(new DetourHook("HandleNetGameEvent", Pointers.HandleNetGameEvent, Hooks::Protections::HandleNetGameEvent)); + BaseHook::Add(new DetourHook("HandleCloneCreate", Pointers.HandleCloneCreate, Hooks::Protections::HandleCloneCreate)); + BaseHook::Add(new DetourHook("HandleCloneSync", Pointers.HandleCloneSync, Hooks::Protections::HandleCloneSync)); + BaseHook::Add(new DetourHook("CanApplyData", Pointers.CanApplyData, Hooks::Protections::CanApplyData)); + BaseHook::Add(new DetourHook("ResetSyncNodes", Pointers.ResetSyncNodes, Hooks::Protections::ResetSyncNodes)); + BaseHook::Add(new DetourHook("HandleScriptedGameEvent", Pointers.HandleScriptedGameEvent, Hooks::Protections::HandleScriptedGameEvent)); + BaseHook::Add(new DetourHook("AddObjectToCreationQueue", Pointers.AddObjectToCreationQueue, Hooks::Protections::AddObjectToCreationQueue)); + + BaseHook::Add(new DetourHook("EnumerateAudioDevices", Pointers.EnumerateAudioDevices, Hooks::Voice::EnumerateAudioDevices)); + BaseHook::Add( + new DetourHook("DirectSoundCaptureCreate", Pointers.DirectSoundCaptureCreate, Hooks::Voice::DirectSoundCaptureCreate)); + + BaseHook::Add(new DetourHook("ThrowFatalError", Pointers.ThrowFatalError, Hooks::Misc::ThrowFatalError)); + BaseHook::Add(new DetourHook("NetworkReqeust", Pointers.NetworkRequest, Hooks::Info::NetworkRequest)); + BaseHook::Add(new DetourHook("AssignPhysicalIndex", Pointers.AssignPhysicalIndex, Hooks::Info::AssignPhysicalIndex)); } Hooking::~Hooking() diff --git a/src/core/logger/ExceptionHandler.cpp b/src/core/logger/ExceptionHandler.cpp new file mode 100644 index 00000000..ea74bc47 --- /dev/null +++ b/src/core/logger/ExceptionHandler.cpp @@ -0,0 +1,80 @@ +#include "ExceptionHandler.hpp" +#include "StackTrace.hpp" + +#include +#include + +namespace YimMenu +{ + inline auto HashStackTrace(std::vector stack_trace) + { + auto data = reinterpret_cast(stack_trace.data()); + std::size_t size = stack_trace.size() * sizeof(uint64_t); + + return std::hash()({data, size}); + } + + ExceptionHandler::ExceptionHandler() + { + m_OldErrorMode = SetErrorMode(0); + m_Handler = SetUnhandledExceptionFilter(&VectoredExceptionHandler); + } + + ExceptionHandler::~ExceptionHandler() + { + SetErrorMode(m_OldErrorMode); + SetUnhandledExceptionFilter(reinterpret_cast(m_Handler)); + } + + inline static StackTrace trace; + LONG VectoredExceptionHandler(EXCEPTION_POINTERS* exception_info) + { + const auto exception_code = exception_info->ExceptionRecord->ExceptionCode; + if (exception_code == EXCEPTION_BREAKPOINT || exception_code == DBG_PRINTEXCEPTION_C || exception_code == DBG_PRINTEXCEPTION_WIDE_C) + return EXCEPTION_CONTINUE_SEARCH; + + static std::unordered_set logged_exceptions; + + trace.NewStackTrace(exception_info); + const auto trace_hash = HashStackTrace(trace.GetFramePointers()); + if (const auto it = logged_exceptions.find(trace_hash); it == logged_exceptions.end()) + { + LOG(FATAL) << trace; + Logger::FlushQueue(); + + logged_exceptions.insert(trace_hash); + } + + if (IsBadReadPtr(reinterpret_cast(exception_info->ContextRecord->Rip), 8)) + { + auto return_address_ptr = (uint64_t*)exception_info->ContextRecord->Rsp; + if (IsBadReadPtr(reinterpret_cast(return_address_ptr), 8)) + { + LOG(FATAL) << "Cannot resume execution, crashing"; + return EXCEPTION_CONTINUE_SEARCH; + } + else + { + exception_info->ContextRecord->Rip = *return_address_ptr; + exception_info->ContextRecord->Rsp += 8; + } + } + else + { + hde64s opcode{}; + hde64_disasm(reinterpret_cast(exception_info->ContextRecord->Rip), &opcode); + if (opcode.flags & F_ERROR) + { + LOG(FATAL) << "Cannot resume execution, crashing"; + return EXCEPTION_CONTINUE_SEARCH; + } + exception_info->ContextRecord->Rip += opcode.len; + } + + return EXCEPTION_CONTINUE_EXECUTION; + } +} + +static YimMenu::ExceptionHandler _ExceptionHandler{}; +// 48 89 5C 24 08 48 89 74 24 10 57 48 83 EC 20 33 DB 44 0F +// \ No newline at end of file diff --git a/src/core/logger/ExceptionHandler.hpp b/src/core/logger/ExceptionHandler.hpp new file mode 100644 index 00000000..579c2f33 --- /dev/null +++ b/src/core/logger/ExceptionHandler.hpp @@ -0,0 +1,17 @@ +#pragma once + +namespace YimMenu +{ + class ExceptionHandler final + { + public: + ExceptionHandler(); + virtual ~ExceptionHandler(); + + private: + void* m_Handler; + uint32_t m_OldErrorMode; + }; + + extern LONG VectoredExceptionHandler(EXCEPTION_POINTERS* exception_info); +} \ No newline at end of file diff --git a/src/core/logger/StackTrace.cpp b/src/core/logger/StackTrace.cpp new file mode 100644 index 00000000..53329a37 --- /dev/null +++ b/src/core/logger/StackTrace.cpp @@ -0,0 +1,208 @@ +#include "StackTrace.hpp" +#include +#include + +namespace YimMenu +{ + StackTrace::StackTrace() : + m_FramePointers(32) + { + SymInitialize(GetCurrentProcess(), nullptr, true); + } + + StackTrace::~StackTrace() + { + SymCleanup(GetCurrentProcess()); + } + + const std::vector& StackTrace::GetFramePointers() + { + return m_FramePointers; + } + + void StackTrace::NewStackTrace(EXCEPTION_POINTERS* exception_info) + { + static std::mutex m; + std::lock_guard lock(m); + + m_ExceptionInfo = exception_info; + + m_Dump << ExceptionCodeToString(exception_info->ExceptionRecord->ExceptionCode) << '\n'; + + DumpModuleInfo(); + DumpRegisters(); + DumpStacktrace(); + DumpCPPExceptionInfo(); + + m_Dump << "\n--------End of exception--------\n"; + } + + std::string StackTrace::GetString() const + { + return m_Dump.str(); + } + + // I'd prefer to make some sort of global instance that cache all modules once instead of doing this every time + void StackTrace::DumpModuleInfo() + { + // modules cached already + if (m_Modules.size()) + return; + + m_Dump << "Dumping modules:\n"; + + const auto peb = reinterpret_cast(NtCurrentTeb()->ProcessEnvironmentBlock); + if (!peb) + return; + + const auto ldr_data = peb->Ldr; + if (!ldr_data) + return; + + const auto module_list = &ldr_data->InMemoryOrderModuleList; + auto module_entry = module_list->Flink; + for (; module_list != module_entry; module_entry = module_entry->Flink) + { + const auto table_entry = CONTAINING_RECORD(module_entry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + if (!table_entry) + continue; + + if (table_entry->FullDllName.Buffer) + { + auto mod_info = ModuleInfo(table_entry->FullDllName.Buffer, table_entry->DllBase); + + m_Dump << mod_info.m_Path.filename().string() << " Base Address: " << HEX(mod_info.m_Base) + << " Size: " << mod_info.m_Size << '\n'; + + m_Modules.emplace_back(std::move(mod_info)); + } + } + } + + void StackTrace::DumpRegisters() + { + const auto context = m_ExceptionInfo->ContextRecord; + + m_Dump << "Dumping registers:\n" + << "RAX: " << HEX(context->Rax) << '\n' + << "RCX: " << HEX(context->Rcx) << '\n' + << "RDX: " << HEX(context->Rdx) << '\n' + << "RBX: " << HEX(context->Rbx) << '\n' + << "RSI: " << HEX(context->Rsi) << '\n' + << "RDI: " << HEX(context->Rdi) << '\n' + << "RSP: " << HEX(context->Rsp) << '\n' + << "RBP: " << HEX(context->Rbp) << '\n' + << "R8: " << HEX(context->R8) << '\n' + << "R9: " << HEX(context->R9) << '\n' + << "R10: " << HEX(context->R10) << '\n' + << "R11: " << HEX(context->R11) << '\n' + << "R12: " << HEX(context->R12) << '\n' + << "R13: " << HEX(context->R13) << '\n' + << "R14: " << HEX(context->R14) << '\n' + << "R15: " << HEX(context->R15) << '\n'; + } + + void StackTrace::DumpStacktrace() + { + m_Dump << "Dumping stacktrace:"; + GrabStacktrace(); + + // alloc once + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME]; + auto symbol = reinterpret_cast(buffer); + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; + + DWORD64 displacement64; + DWORD displacement; + + + IMAGEHLP_LINE64 line; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + for (size_t i = 0; i < m_FramePointers.size() && m_FramePointers[i]; ++i) + { + const auto addr = m_FramePointers[i]; + + m_Dump << "\n[" << i << "]\t"; + if (SymFromAddr(GetCurrentProcess(), addr, &displacement64, symbol)) + { + if (SymGetLineFromAddr64(GetCurrentProcess(), addr, &displacement, &line)) + { + m_Dump << line.FileName << " L: " << line.LineNumber << " " << std::string_view(symbol->Name, symbol->NameLen); + + continue; + } + const auto module_info = GetModuleByAddress(addr); + + if (module_info->m_Base == (uint64_t)GetModuleHandle(0)) + m_Dump << module_info->m_Path.filename().string() << " " << std::string_view(symbol->Name, symbol->NameLen) << " (" + << module_info->m_Path.filename().string() << "+" << HEX(addr - module_info->m_Base) << ")"; + else + m_Dump << module_info->m_Path.filename().string() << " " << std::string_view(symbol->Name, symbol->NameLen); + + continue; + } + const auto module_info = GetModuleByAddress(addr); + m_Dump << module_info->m_Path.filename().string() << "+" << HEX(addr - module_info->m_Base) << " " << HEX(addr); + } + } + + void StackTrace::DumpCPPExceptionInfo() + { + constexpr DWORD msvc_exception_code = 0xe06d7363; + if (m_ExceptionInfo->ExceptionRecord->ExceptionCode == msvc_exception_code) + { + m_Dump + << reinterpret_cast(m_ExceptionInfo->ExceptionRecord->ExceptionInformation[1])->what() << '\n'; + } + } + + void StackTrace::GrabStacktrace() + { + CONTEXT context = *m_ExceptionInfo->ContextRecord; + + STACKFRAME64 frame{}; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Mode = AddrModeFlat; + frame.AddrPC.Offset = context.Rip; + frame.AddrFrame.Offset = context.Rbp; + frame.AddrStack.Offset = context.Rsp; + + for (size_t i = 0; i < m_FramePointers.size(); ++i) + { + if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &frame, &context, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) + { + break; + } + m_FramePointers[i] = frame.AddrPC.Offset; + } + } + + const StackTrace::ModuleInfo* StackTrace::GetModuleByAddress(uint64_t addr) const + { + for (auto& mod_info : m_Modules) + { + if (mod_info.m_Base < addr && addr < mod_info.m_Base + mod_info.m_Size) + { + return &mod_info; + } + } + return nullptr; + } + + std::string StackTrace::ExceptionCodeToString(const DWORD code) + { +#define MAP_PAIR_STRINGIFY(x) \ + { \ + x, #x \ + } + static const std::map exceptions = {MAP_PAIR_STRINGIFY(EXCEPTION_ACCESS_VIOLATION), MAP_PAIR_STRINGIFY(EXCEPTION_ARRAY_BOUNDS_EXCEEDED), MAP_PAIR_STRINGIFY(EXCEPTION_DATATYPE_MISALIGNMENT), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_DENORMAL_OPERAND), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_DIVIDE_BY_ZERO), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_INEXACT_RESULT), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_INEXACT_RESULT), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_INVALID_OPERATION), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_OVERFLOW), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_STACK_CHECK), MAP_PAIR_STRINGIFY(EXCEPTION_FLT_UNDERFLOW), MAP_PAIR_STRINGIFY(EXCEPTION_ILLEGAL_INSTRUCTION), MAP_PAIR_STRINGIFY(EXCEPTION_IN_PAGE_ERROR), MAP_PAIR_STRINGIFY(EXCEPTION_INT_DIVIDE_BY_ZERO), MAP_PAIR_STRINGIFY(EXCEPTION_INT_OVERFLOW), MAP_PAIR_STRINGIFY(EXCEPTION_INVALID_DISPOSITION), MAP_PAIR_STRINGIFY(EXCEPTION_NONCONTINUABLE_EXCEPTION), MAP_PAIR_STRINGIFY(EXCEPTION_PRIV_INSTRUCTION), MAP_PAIR_STRINGIFY(EXCEPTION_STACK_OVERFLOW), MAP_PAIR_STRINGIFY(EXCEPTION_BREAKPOINT), MAP_PAIR_STRINGIFY(EXCEPTION_SINGLE_STEP)}; + + if (const auto& it = exceptions.find(code); it != exceptions.end()) + return it->second; + + return "UNKNOWN_EXCEPTION: CODE: " + std::to_string(code); + } +} \ No newline at end of file diff --git a/src/core/logger/StackTrace.hpp b/src/core/logger/StackTrace.hpp new file mode 100644 index 00000000..cb66e231 --- /dev/null +++ b/src/core/logger/StackTrace.hpp @@ -0,0 +1,66 @@ +#pragma once + +namespace YimMenu +{ + class StackTrace + { + public: + StackTrace(); + virtual ~StackTrace(); + + const std::vector& GetFramePointers(); + void NewStackTrace(EXCEPTION_POINTERS* exception_info); + std::string GetString() const; + + friend std::ostream& operator<<(std::ostream& os, const StackTrace& st); + friend std::ostream& operator<<(std::ostream& os, const StackTrace* st); + + private: + struct ModuleInfo + { + ModuleInfo(std::filesystem::path path, void* base) : + m_Path(path), + m_Base(reinterpret_cast(base)) + { + const auto dos_header = reinterpret_cast(base); + const auto nt_header = reinterpret_cast(m_Base + dos_header->e_lfanew); + + m_Size = nt_header->OptionalHeader.SizeOfCode; + } + + std::filesystem::path m_Path; + uintptr_t m_Base; + size_t m_Size; + }; + + private: + void DumpModuleInfo(); + void DumpRegisters(); + void DumpStacktrace(); + void DumpCPPExceptionInfo(); + void GrabStacktrace(); + const ModuleInfo* GetModuleByAddress(uint64_t addr) const; + + static std::string ExceptionCodeToString(const DWORD code); + + private: + EXCEPTION_POINTERS* m_ExceptionInfo; + + std::stringstream m_Dump; + std::vector m_FramePointers; + + inline static std::vector m_Modules; + }; + + inline std::ostream& operator<<(std::ostream& os, const StackTrace& st) + { + os << st.GetString(); + return os; + } + + inline std::ostream& operator<<(std::ostream& os, const StackTrace* st) + { + os << st->GetString(); + return os; + } +} \ No newline at end of file diff --git a/src/core/memory/Module.hpp b/src/core/memory/Module.hpp index ed35bbab..3622d6ca 100644 --- a/src/core/memory/Module.hpp +++ b/src/core/memory/Module.hpp @@ -25,6 +25,8 @@ namespace YimMenu */ template T GetExport(const std::string_view symbolName) const; + template + T GetExport(int ordinal) const; /** * @brief Gets the address of the import function * @@ -85,4 +87,28 @@ namespace YimMenu LOG(FATAL) << "Cannot find export: " << symbolName; return nullptr; } + + template + inline T Module::GetExport(int ordinal) const + { + const auto ntHeader = GetNtHeader(); + if (!ntHeader) + return nullptr; + + const auto imageDataDirectory = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + const auto exportDirectory = m_Base.Add(imageDataDirectory.VirtualAddress).As(); + const auto ordinalOffsets = m_Base.Add(exportDirectory->AddressOfNameOrdinals).As(); + const auto funcOffsets = m_Base.Add(exportDirectory->AddressOfFunctions).As(); + + for (std::size_t i = 0; i < exportDirectory->NumberOfFunctions; i++) + { + if (ordinalOffsets[i] != ordinal) + continue; + + return m_Base.Add(funcOffsets[i]).As(); + } + + LOG(FATAL) << "Cannot find export: " << ordinal; + return nullptr; + } } \ No newline at end of file diff --git a/src/core/renderer/Renderer.cpp b/src/core/renderer/Renderer.cpp index c0dbe9b1..9823ca9d 100644 --- a/src/core/renderer/Renderer.cpp +++ b/src/core/renderer/Renderer.cpp @@ -2,7 +2,7 @@ #include "game/pointers/Pointers.hpp" #include "game/frontend/GUI.hpp" -#include "game/frontend/menu/Menu.hpp" +#include "game/frontend/Menu.hpp" #include "core/memory/ModuleMgr.hpp" #include "core/memory/PatternScanner.hpp" @@ -551,7 +551,7 @@ namespace YimMenu ImGui::CreateContext(&GetInstance().m_FontAtlas); ImGui_ImplWin32_Init(Pointers.Hwnd); - Menu::Style(); + Menu::SetupStyle(); SetResizing(false); } diff --git a/src/core/settings/IStateSerializer.cpp b/src/core/settings/IStateSerializer.cpp new file mode 100644 index 00000000..8d86a71f --- /dev/null +++ b/src/core/settings/IStateSerializer.cpp @@ -0,0 +1,19 @@ +#include "IStateSerializer.hpp" +#include "Settings.hpp" + +namespace YimMenu +{ + IStateSerializer::IStateSerializer(const std::string& name) : + m_SerComponentName(name), + m_IsDirty(false) + { + if (Settings::InitialLoadDone()) + { + LOG(FATAL) << "Component initialized too late: " << name; + LOG(FATAL) << "Attempting recovery"; + Settings::LoadComponent(this); + } + + Settings::AddComponent(this); + } +} \ No newline at end of file diff --git a/src/core/settings/IStateSerializer.hpp b/src/core/settings/IStateSerializer.hpp new file mode 100644 index 00000000..f95260d1 --- /dev/null +++ b/src/core/settings/IStateSerializer.hpp @@ -0,0 +1,41 @@ +#pragma once + +namespace YimMenu +{ + class IStateSerializer + { + std::string m_SerComponentName; + bool m_IsDirty; + public: + IStateSerializer(const std::string& name); + virtual void SaveStateImpl(nlohmann::json& state) = 0; + virtual void LoadStateImpl(nlohmann::json& state) = 0; + + inline void SaveState(nlohmann::json& state) + { + SaveStateImpl(state); + m_IsDirty = false; + } + + inline void LoadState(nlohmann::json& state) + { + LoadStateImpl(state); + m_IsDirty = false; + } + + inline bool IsStateDirty() + { + return m_IsDirty; + } + + inline void MarkStateDirty() + { + m_IsDirty = true; + } + + inline const std::string& GetSerializerComponentName() + { + return m_SerComponentName; + } + }; +} \ No newline at end of file diff --git a/src/core/settings/Settings.cpp b/src/core/settings/Settings.cpp new file mode 100644 index 00000000..f943f729 --- /dev/null +++ b/src/core/settings/Settings.cpp @@ -0,0 +1,88 @@ +#include "Settings.hpp" +#include "IStateSerializer.hpp" +#include "Settings.hpp" + +namespace YimMenu +{ + Settings::Settings() : + m_SettingsFile(), + m_StateSerializers(), + m_InitialLoadDone(false) + { + } + + void Settings::InitializeImpl(File settingsFile) + { + m_SettingsFile = settingsFile; + + if (!settingsFile.Exists()) + Reset(); + + std::ifstream file(m_SettingsFile); + + try + { + file >> m_Json; + file.close(); + } + catch (std::exception&) + { + LOG(WARNING) << "Detected corrupt settings, resetting settings..."; + Reset(); + } + + for (auto& serializer : m_StateSerializers) + LoadComponentImpl(serializer); + + LOG(VERBOSE) << "m_InitialLoadDone = true"; + m_InitialLoadDone = true; + } + + void Settings::SaveImpl() + { + if (m_InitialLoadDone && ShouldSave()) + { + for (auto& serializer : m_StateSerializers) + if (serializer->IsStateDirty()) + SaveComponentImpl(serializer); + + std::ofstream file(m_SettingsFile, std::ios::out | std::ios::trunc); + file << m_Json.dump(4); + file.close(); + } + } + + void Settings::LoadComponentImpl(IStateSerializer* serializer) + { + LOG(VERBOSE) << "Loading component: " << serializer->GetSerializerComponentName(); + + if (!m_Json.contains(serializer->GetSerializerComponentName()) + || !m_Json[serializer->GetSerializerComponentName()].is_object()) + m_Json[serializer->GetSerializerComponentName()] = nlohmann::json::object(); + + serializer->LoadState(m_Json[serializer->GetSerializerComponentName()]); + } + + void Settings::SaveComponentImpl(IStateSerializer* serializer) + { + LOG(VERBOSE) << "Saving component: " << serializer->GetSerializerComponentName(); + serializer->SaveState(m_Json[serializer->GetSerializerComponentName()]); + } + + void Settings::Reset() + { + std::ofstream file(m_SettingsFile, std::ios::out | std::ios::trunc); + file << "{}" << std::endl; + file.close(); + m_Json = "{}"; + } + + bool Settings::ShouldSave() + { + for (auto& serializer : m_StateSerializers) + if (serializer->IsStateDirty()) + return true; + + return false; + } +} \ No newline at end of file diff --git a/src/core/settings/Settings.hpp b/src/core/settings/Settings.hpp new file mode 100644 index 00000000..a0258d5b --- /dev/null +++ b/src/core/settings/Settings.hpp @@ -0,0 +1,59 @@ +#pragma once +#include "core/filemgr/File.hpp" + +namespace YimMenu +{ + class IStateSerializer; + + class Settings + { + private: + std::filesystem::path m_SettingsFile; + std::vector m_StateSerializers; + bool m_InitialLoadDone; + nlohmann::json m_Json; + + public: + Settings(); + + static void Initialize(File settingsFile) + { + GetInstance().InitializeImpl(settingsFile); + } + + static void Save() + { + GetInstance().SaveImpl(); + } + + static void AddComponent(IStateSerializer* serializer) + { + GetInstance().m_StateSerializers.push_back(serializer); + } + + // TODO: this is broken + static void LoadComponent(IStateSerializer* serializer) + { + GetInstance().LoadComponentImpl(serializer); + } + + static bool InitialLoadDone() + { + return GetInstance().m_InitialLoadDone; + } + + private: + static Settings& GetInstance() + { + static Settings Instance; + return Instance; + } + + void InitializeImpl(File settingsFile); + void SaveImpl(); + void LoadComponentImpl(IStateSerializer* serializer); + void SaveComponentImpl(IStateSerializer* serializer); + void Reset(); + bool ShouldSave(); + }; +} \ No newline at end of file diff --git a/src/game/backend/Players.cpp b/src/game/backend/Players.cpp new file mode 100644 index 00000000..78f8aa32 --- /dev/null +++ b/src/game/backend/Players.cpp @@ -0,0 +1,36 @@ +#include "Players.hpp" +#include +#include +#include "game/pointers/Pointers.hpp" + +namespace YimMenu +{ + Players::Players() + { + if (*Pointers.IsSessionStarted) + for (int i = 0; i < 32; i++) + if (Pointers.NetworkPlayerMgr->m_PlayerList[i] && Pointers.NetworkPlayerMgr->m_PlayerList[i]->IsValid()) + HandlePlayerJoinImpl(Pointers.NetworkPlayerMgr->m_PlayerList[i]); + } + + void Players::HandlePlayerJoinImpl(CNetGamePlayer* player) + { + m_Players.insert({ player->GetName(), player }); + } + + void Players::HandlePlayerLeaveImpl(CNetGamePlayer* player) + { + if (m_SelectedPlayer == player) + m_SelectedPlayer = nullptr; + + if (auto it = std::find_if(m_Players.begin(), + m_Players.end(), + [player](const auto& p) { + return p.second == Player(player); + }); + it != m_Players.end()) + { + m_Players.erase(it); + } + } +} \ No newline at end of file diff --git a/src/game/backend/Players.hpp b/src/game/backend/Players.hpp new file mode 100644 index 00000000..390b0f0a --- /dev/null +++ b/src/game/backend/Players.hpp @@ -0,0 +1,48 @@ +#pragma once +#include "game/rdr/Player.hpp" + +namespace YimMenu +{ + class Players + { + std::multimap m_Players{}; + Player m_SelectedPlayer = nullptr; + + public: + static void HandlePlayerJoin(CNetGamePlayer* player) + { + GetInstance().HandlePlayerJoinImpl(player); + } + + static void HandlePlayerLeave(CNetGamePlayer* player) + { + GetInstance().HandlePlayerLeaveImpl(player); + } + + static Player GetSelected() + { + return GetInstance().m_SelectedPlayer; + } + + static void SetSelected(Player player) + { + GetInstance().m_SelectedPlayer = player; + } + + static std::multimap& GetPlayers() + { + return GetInstance().m_Players; + } + + private: + static Players& GetInstance() + { + static Players Instance; + return Instance; + } + + Players(); + void HandlePlayerJoinImpl(CNetGamePlayer* player); + void HandlePlayerLeaveImpl(CNetGamePlayer* player); + }; +} \ No newline at end of file diff --git a/src/game/backend/Protections.cpp b/src/game/backend/Protections.cpp new file mode 100644 index 00000000..6290638d --- /dev/null +++ b/src/game/backend/Protections.cpp @@ -0,0 +1,6 @@ +#include "Protections.hpp" + +namespace YimMenu +{ + +} \ No newline at end of file diff --git a/src/game/backend/Protections.hpp b/src/game/backend/Protections.hpp new file mode 100644 index 00000000..06dcce31 --- /dev/null +++ b/src/game/backend/Protections.hpp @@ -0,0 +1,26 @@ +#pragma once +#include "game/rdr/Player.hpp" + +namespace YimMenu +{ + class Protections + { + Player m_SyncingPlayer{nullptr}; + + static Protections& GetInstance() + { + static Protections Instance; + return Instance; + } + public: + static Player& GetSyncingPlayer() + { + return GetInstance().m_SyncingPlayer; + } + + static void SetSyncingPlayer(Player player) + { + GetInstance().m_SyncingPlayer = player; + } + }; +} \ No newline at end of file diff --git a/src/game/backend/ScriptMgr.cpp b/src/game/backend/ScriptMgr.cpp index 8208166e..2cd49ce8 100644 --- a/src/game/backend/ScriptMgr.cpp +++ b/src/game/backend/ScriptMgr.cpp @@ -62,6 +62,10 @@ namespace YimMenu if (startup) { + static bool enable = ([this] { + m_CanTick = true; + }(), true); + Scripts::RunAsScript(startup, [this]() { std::lock_guard lock(m_Mutex); static bool ensure_main_fiber = (ConvertThreadToFiber(nullptr), true); diff --git a/src/game/backend/ScriptMgr.hpp b/src/game/backend/ScriptMgr.hpp index 76b3d1e8..12c84c2e 100644 --- a/src/game/backend/ScriptMgr.hpp +++ b/src/game/backend/ScriptMgr.hpp @@ -60,9 +60,15 @@ namespace YimMenu GetInstance().AddScriptImpl(std::move(script)); } + static bool CanTick() + { + return GetInstance().m_CanTick; + } + private: std::mutex m_Mutex; std::vector> m_Scripts; + bool m_CanTick = false; void InitImpl(); void DestroyImpl(); diff --git a/src/game/commands/PlayerCommand.cpp b/src/game/commands/PlayerCommand.cpp new file mode 100644 index 00000000..ff46502c --- /dev/null +++ b/src/game/commands/PlayerCommand.cpp @@ -0,0 +1,34 @@ +#include "PlayerCommand.hpp" +#include "game/backend/Players.hpp" + +namespace YimMenu +{ + PlayerAllCommand::PlayerAllCommand(std::string name, std::string label, std::string description, int num_args, PlayerCommand* parent) : + Command(name + "all", label, description, num_args), + m_PlayerCommand(parent) + { + } + + void PlayerAllCommand::OnCall() + { + for (auto& p : Players::GetPlayers()) + m_PlayerCommand->Call(p.second); + } + + PlayerCommand::PlayerCommand(std::string name, std::string label, std::string description, int num_args, bool all_version) : + Command::Command(name, label, description, num_args + 1) + { + if (all_version) + m_AllCommand = std::make_unique(name, label, description, num_args, this); + } + + void PlayerCommand::OnCall() + { + LOG(WARNING) << GetName() << " requires a player argument"; + } + + void PlayerCommand::Call(Player target) + { + OnCall(target); + } +} \ No newline at end of file diff --git a/src/game/commands/PlayerCommand.hpp b/src/game/commands/PlayerCommand.hpp new file mode 100644 index 00000000..fe8e617d --- /dev/null +++ b/src/game/commands/PlayerCommand.hpp @@ -0,0 +1,29 @@ +#pragma once +#include "core/commands/Command.hpp" +#include "game/rdr/Player.hpp" + +namespace YimMenu +{ + class PlayerCommand; + class PlayerAllCommand : public Command + { + PlayerCommand* m_PlayerCommand; + + public: + PlayerAllCommand(std::string name, std::string label, std::string description, int num_args, PlayerCommand* parent); + virtual void OnCall() override; + }; + + class PlayerCommand : public Command + { + virtual void OnCall() override; + std::unique_ptr m_AllCommand; + + protected: + virtual void OnCall(Player) = 0; + + public: + PlayerCommand(std::string name, std::string label, std::string description, int num_args = 0, bool all_version = true); + void Call(Player target); // TODO + }; +} \ No newline at end of file diff --git a/src/game/features/Features.cpp b/src/game/features/Features.cpp index 496d86c3..49ab4963 100644 --- a/src/game/features/Features.cpp +++ b/src/game/features/Features.cpp @@ -17,12 +17,15 @@ namespace YimMenu if (PED::IS_PED_IN_ANY_VEHICLE(Self::PlayerPed, true)) Self::Veh = PED::GET_VEHICLE_PED_IS_IN(Self::PlayerPed, true); + else + Self::Veh = 0; if (PED::IS_PED_ON_MOUNT(Self::PlayerPed)) Self::Mount = PED::GET_MOUNT(Self::PlayerPed); - - if (!ENTITY::DOES_ENTITY_EXIST(Self::Mount) && ENTITY::DOES_ENTITY_EXIST(PED::_GET_LAST_MOUNT(Self::PlayerPed))) + else if (ENTITY::DOES_ENTITY_EXIST(PED::_GET_LAST_MOUNT(Self::PlayerPed))) Self::Mount = PED::_GET_LAST_MOUNT(Self::PlayerPed); + else + Self::Mount = 0; } void FeatureLoop() @@ -30,6 +33,7 @@ namespace YimMenu while (true) { UpdateSelfVars(); + *Pointers.RageSecurityInitialized = false; Commands::RunLoopedCommands(); g_HotkeySystem.FeatureCommandsHotkeyLoop(); ScriptMgr::Yield(); diff --git a/src/game/features/Features.hpp b/src/game/features/Features.hpp index 7acb7ccc..a76a95ea 100644 --- a/src/game/features/Features.hpp +++ b/src/game/features/Features.hpp @@ -6,7 +6,7 @@ namespace YimMenu namespace Self { inline Ped PlayerPed; - inline Player Id; + inline ::Player Id; inline Vector3 Pos; inline Vector3 Rot; inline Vehicle Veh; diff --git a/src/game/features/mount/HorseGodmode.cpp b/src/game/features/mount/HorseGodmode.cpp new file mode 100644 index 00000000..a68e1c71 --- /dev/null +++ b/src/game/features/mount/HorseGodmode.cpp @@ -0,0 +1,24 @@ +#include "core/commands/LoopedCommand.hpp" +#include "game/features/Features.hpp" +#include "game/rdr/Enums.hpp" +#include "game/rdr/Natives.hpp" + +namespace YimMenu::Features +{ + class HorseGodmode : public LoopedCommand + { + using LoopedCommand::LoopedCommand; + + virtual void OnTick() override + { + ENTITY::SET_ENTITY_INVINCIBLE(Self::Mount, true); + } + + virtual void OnDisable() override + { + PED::SET_PED_CAN_RAGDOLL(Self::Mount, false); + } + }; + + static HorseGodmode _HorseGodmode{"horsegodmode", "Godmode", "Blocks all incoming damage for your horse"}; +} \ No newline at end of file diff --git a/src/game/features/mount/HorseNoRagdoll.cpp b/src/game/features/mount/HorseNoRagdoll.cpp new file mode 100644 index 00000000..e9dc72fa --- /dev/null +++ b/src/game/features/mount/HorseNoRagdoll.cpp @@ -0,0 +1,25 @@ +#include "core/commands/LoopedCommand.hpp" +#include "game/features/Features.hpp" +#include "game/rdr/Enums.hpp" +#include "game/rdr/Natives.hpp" + +namespace YimMenu::Features +{ + class HorseNoRagdoll : public LoopedCommand + { + using LoopedCommand::LoopedCommand; + + virtual void OnTick() override + { + if(PED::CAN_PED_RAGDOLL(Self::Mount)) + PED::SET_PED_CAN_RAGDOLL(Self::Mount, false); + } + + virtual void OnDisable() override + { + PED::SET_PED_CAN_RAGDOLL(Self::Mount, true); + } + }; + + static HorseNoRagdoll _HorseNoRagdoll{"horsenoragdoll", "No Ragdoll", "Your horse will never ragdoll"}; +} \ No newline at end of file diff --git a/src/game/features/network/VoiceChatOverride.cpp b/src/game/features/network/VoiceChatOverride.cpp new file mode 100644 index 00000000..4b2e391a --- /dev/null +++ b/src/game/features/network/VoiceChatOverride.cpp @@ -0,0 +1,29 @@ +#include "core/commands/LoopedCommand.hpp" +#include "game/features/Features.hpp" +#include "game/rdr/Enums.hpp" +#include "game/rdr/Natives.hpp" + +namespace YimMenu::Features +{ + class VoiceChatOverride : public LoopedCommand + { + using LoopedCommand::LoopedCommand; + + virtual void OnEnable() override + { + + } + + virtual void OnTick() override + { + VOICE::_0x58125B691F6827D5(99999.0f); + } + + virtual void OnDisable() override + { + + } + }; + + static VoiceChatOverride _VoiceChatOverride{"voicechatoverride", "Voice Chat Override", "Plays the audio.wav file in the project folder through voice chat. The wave file must be encoded with a mono 16 bit 16kHz PCM format. You have to reset voice chat settings whenever you load the menu for the sound to play"}; +} \ No newline at end of file diff --git a/src/game/features/players/toxic/ActivateDefensiveMode.cpp b/src/game/features/players/toxic/ActivateDefensiveMode.cpp new file mode 100644 index 00000000..a94d79fe --- /dev/null +++ b/src/game/features/players/toxic/ActivateDefensiveMode.cpp @@ -0,0 +1,22 @@ +#include "game/commands/PlayerCommand.hpp" +#include "game/features/Features.hpp" +#include "game/rdr/Scripts.hpp" + +namespace YimMenu::Features +{ + class ActivateDefensiveMode : public PlayerCommand + { + using PlayerCommand::PlayerCommand; + + virtual void OnCall(Player player) override + { + uint64_t data[13]{}; + data[0] = 36; + data[1] = Self::Id; + data[8] = 2; + Scripts::SendScriptEvent(data, 13, 1 << player.GetId()); + } + }; + + static ActivateDefensiveMode _ActivateDefensiveMode{"defensive", "Activate Defensive Mode", "Forces the player into defensive mode"}; +} \ No newline at end of file diff --git a/src/game/features/players/toxic/ActivateOffensiveMode.cpp b/src/game/features/players/toxic/ActivateOffensiveMode.cpp new file mode 100644 index 00000000..e5ca56a3 --- /dev/null +++ b/src/game/features/players/toxic/ActivateOffensiveMode.cpp @@ -0,0 +1,21 @@ +#include "game/commands/PlayerCommand.hpp" +#include "game/features/Features.hpp" +#include "game/rdr/Scripts.hpp" + +namespace YimMenu::Features +{ + class ActivateOffensiveMode : public PlayerCommand + { + using PlayerCommand::PlayerCommand; + + virtual void OnCall(Player player) override + { + uint64_t data[13]{}; + data[0] = 37; + data[1] = Self::Id; + Scripts::SendScriptEvent(data, 13, 1 << player.GetId()); + } + }; + + static ActivateOffensiveMode _ActivateOffensiveMode{"offensive", "Activate Offensive Mode", "Forces the player into offensive mode"}; +} \ No newline at end of file diff --git a/src/game/features/players/toxic/MaximumHonor.cpp b/src/game/features/players/toxic/MaximumHonor.cpp new file mode 100644 index 00000000..fe1a2bb6 --- /dev/null +++ b/src/game/features/players/toxic/MaximumHonor.cpp @@ -0,0 +1,52 @@ +#include "game/commands/PlayerCommand.hpp" +#include "game/features/Features.hpp" +#include "game/rdr/Scripts.hpp" +#include "game/backend/ScriptMgr.hpp" + +namespace YimMenu::Features +{ + void MaxHonor(int bits) + { + uint64_t data[7]{}; + + for (int i = 0; i < 5; i++) + { + data[0] = 188; + data[1] = Self::Id; + data[4] = 2; + data[5] = "PERSONA_HONOR_ACTION__FME_BOUNTY_RETURNED_ALIVE"_J; + data[6] = 1; + Scripts::SendScriptEvent(data, 13, bits); + data[5] = "PERSONA_HONOR_ACTION__HORSE_CARE"_J; + Scripts::SendScriptEvent(data, 13, bits); + data[5] = "PERSONA_HONOR_ACTION__NB_KIDNAPPED_RESCUE"_J; + Scripts::SendScriptEvent(data, 13, bits); + data[5] = "PERSONA_HONOR_ACTION__MISSION_POS_FIFTY"_J; + Scripts::SendScriptEvent(data, 13, bits); + ScriptMgr::Yield(40ms); + } + } + + class MaximumHonor : public PlayerCommand + { + using PlayerCommand::PlayerCommand; + + virtual void OnCall(Player player) override + { + MaxHonor(1 << player.GetId()); + } + }; + + class MaximumHonorAll : public Command + { + using Command::Command; + + virtual void OnCall() override + { + MaxHonor(-1 & ~(1 << Self::Id)); + } + }; + + static MaximumHonor _MaximumHonor{"maxhonor", "Max Honor", "Sets the player's honor to the maximum value", 0, false}; + static MaximumHonorAll _MaximumHonorAll{"maxhonorall", "Max Honor", "Sets the player's honor to the maximum value"}; +} \ No newline at end of file diff --git a/src/game/features/players/toxic/MinimumHonor.cpp b/src/game/features/players/toxic/MinimumHonor.cpp new file mode 100644 index 00000000..70a5f746 --- /dev/null +++ b/src/game/features/players/toxic/MinimumHonor.cpp @@ -0,0 +1,52 @@ +#include "game/backend/ScriptMgr.hpp" +#include "game/commands/PlayerCommand.hpp" +#include "game/features/Features.hpp" +#include "game/rdr/Scripts.hpp" + +namespace YimMenu::Features +{ + void MinHonor(int bits) + { + uint64_t data[7]{}; + + for (int i = 0; i < 5; i++) + { + data[0] = 188; + data[1] = Self::Id; + data[4] = 2; + data[5] = "PERSONA_HONOR_ACTION__MISSION_NEG_FIFTY"_J; + data[6] = 1; + Scripts::SendScriptEvent(data, 13, bits); + data[5] = "PERSONA_HONOR_ACTION__MISSION_NEG_FORTYFIVE"_J; + Scripts::SendScriptEvent(data, 13, bits); + data[5] = "PERSONA_HONOR_ACTION__MURDER_RAMPAGE"_J; + Scripts::SendScriptEvent(data, 13, bits); + data[5] = "PERSONA_HONOR_ACTION__MURDER_BUTCHER"_J; + Scripts::SendScriptEvent(data, 13, bits); + ScriptMgr::Yield(40ms); + } + } + + class MinimumHonor : public PlayerCommand + { + using PlayerCommand::PlayerCommand; + + virtual void OnCall(Player player) override + { + MinHonor(1 << player.GetId()); + } + }; + + class MinimumHonorAll : public Command + { + using Command::Command; + + virtual void OnCall() override + { + MinHonor(-1 & ~(1 << Self::Id)); + } + }; + + static MinimumHonor _MinimumHonor{"minhonor", "Min Honor", "Sets the player's honor to the minimum value", 0, false}; + static MinimumHonorAll _MinimumHonorAll{"minhonorall", "Min Honor", "Sets the player's honor to the minimum value"}; +} \ No newline at end of file diff --git a/src/game/features/self/Godmode.cpp b/src/game/features/self/Godmode.cpp new file mode 100644 index 00000000..80406e01 --- /dev/null +++ b/src/game/features/self/Godmode.cpp @@ -0,0 +1,24 @@ +#include "core/commands/LoopedCommand.hpp" +#include "game/features/Features.hpp" +#include "game/rdr/Enums.hpp" +#include "game/rdr/Natives.hpp" + +namespace YimMenu::Features +{ + class Godmode : public LoopedCommand + { + using LoopedCommand::LoopedCommand; + + virtual void OnTick() override + { + ENTITY::SET_ENTITY_INVINCIBLE(Self::PlayerPed, true); + } + + virtual void OnDisable() override + { + ENTITY::SET_ENTITY_INVINCIBLE(Self::PlayerPed, false); + } + }; + + static Godmode _Godmode{"godmode", "God Mode", "Blocks all incoming damage"}; +} \ No newline at end of file diff --git a/src/game/features/self/InfiniteAmmo.cpp b/src/game/features/self/InfiniteAmmo.cpp index 0d8622ed..1175eff7 100644 --- a/src/game/features/self/InfiniteAmmo.cpp +++ b/src/game/features/self/InfiniteAmmo.cpp @@ -11,17 +11,12 @@ namespace YimMenu::Features virtual void OnTick() override { - Hash current_weapon{}; - int max_ammo{}; - - WEAPON::GET_CURRENT_PED_WEAPON(Self::PlayerPed, ¤t_weapon, false, false, false); - WEAPON::GET_MAX_AMMO(Self::PlayerPed, &max_ammo, current_weapon); - - auto current_weapon_ammo_type = WEAPON::_GET_CURRENT_PED_WEAPON_AMMO_TYPE(Self::PlayerPed, WEAPON::_GET_PED_WEAPON_OBJECT(Self::PlayerPed, false)); - auto current_ammo = WEAPON::GET_PED_AMMO_BY_TYPE(Self::PlayerPed, current_weapon_ammo_type); + WEAPON::SET_PED_INFINITE_AMMO(Self::PlayerPed, true, 0); + } - if (current_ammo < max_ammo) - WEAPON::SET_PED_AMMO_BY_TYPE(Self::PlayerPed, current_weapon_ammo_type, max_ammo); + virtual void OnDisable() override + { + WEAPON::SET_PED_INFINITE_AMMO(Self::PlayerPed, false, 0); } }; diff --git a/src/game/features/self/KeepBarsFilled.cpp b/src/game/features/self/KeepBarsFilled.cpp index 4f58fd68..5f2eaf85 100644 --- a/src/game/features/self/KeepBarsFilled.cpp +++ b/src/game/features/self/KeepBarsFilled.cpp @@ -15,7 +15,7 @@ namespace YimMenu::Features auto stamina_bar = PLAYER::_GET_PLAYER_STAMINA(Self::Id); auto deadeye_bar = PLAYER::_GET_PLAYER_DEAD_EYE(Self::Id); - if (health_bar < ENTITY::GET_ENTITY_MAX_HEALTH(Self::PlayerPed, false)) + if (health_bar < ENTITY::GET_ENTITY_MAX_HEALTH(Self::PlayerPed, false) && !PED::IS_PED_DEAD_OR_DYING(Self::PlayerPed, true) && !ENTITY::IS_ENTITY_DEAD(Self::PlayerPed)) ENTITY::SET_ENTITY_HEALTH(Self::PlayerPed, ENTITY::GET_ENTITY_MAX_HEALTH(Self::PlayerPed, false), 0); if (stamina_bar < PED::_GET_PED_MAX_STAMINA(Self::PlayerPed)) PED::_CHANGE_PED_STAMINA(Self::PlayerPed, PED::_GET_PED_MAX_STAMINA(Self::PlayerPed)); diff --git a/src/game/features/self/KeepClean.cpp b/src/game/features/self/KeepClean.cpp index 36eb2234..6bbbe0d1 100644 --- a/src/game/features/self/KeepClean.cpp +++ b/src/game/features/self/KeepClean.cpp @@ -11,7 +11,7 @@ namespace YimMenu::Features virtual void OnTick() override { - PED::_SET_PED_DAMAGE_CLEANLINESS(Self::PlayerPed, ePedDamageCleanliness::PED_DAMAGE_CLEANLINESS_PERFECT); + PED::_SET_PED_DAMAGE_CLEANLINESS(Self::PlayerPed, (int)ePedDamageCleanliness::PED_DAMAGE_CLEANLINESS_PERFECT); PED::CLEAR_PED_WETNESS(Self::PlayerPed); PED::CLEAR_PED_ENV_DIRT(Self::PlayerPed); PED::CLEAR_PED_BLOOD_DAMAGE(Self::PlayerPed); diff --git a/src/game/features/self/NoRagdoll.cpp b/src/game/features/self/NoRagdoll.cpp new file mode 100644 index 00000000..17bf8cec --- /dev/null +++ b/src/game/features/self/NoRagdoll.cpp @@ -0,0 +1,25 @@ +#include "core/commands/LoopedCommand.hpp" +#include "game/features/Features.hpp" +#include "game/rdr/Enums.hpp" +#include "game/rdr/Natives.hpp" + +namespace YimMenu::Features +{ + class NoRagdoll : public LoopedCommand + { + using LoopedCommand::LoopedCommand; + + virtual void OnTick() override + { + if(PED::CAN_PED_RAGDOLL(Self::PlayerPed)) + PED::SET_PED_CAN_RAGDOLL(Self::PlayerPed, false); + } + + virtual void OnDisable() override + { + PED::SET_PED_CAN_RAGDOLL(Self::PlayerPed, true); + } + }; + + static NoRagdoll _NoRagdoll{"noragdoll", "No Ragdoll", "You will never ragdoll"}; +} \ No newline at end of file diff --git a/src/game/features/self/Noclip.cpp b/src/game/features/self/Noclip.cpp new file mode 100644 index 00000000..ba9717d5 --- /dev/null +++ b/src/game/features/self/Noclip.cpp @@ -0,0 +1,101 @@ +#include "core/commands/LoopedCommand.hpp" +#include "game/features/Features.hpp" +#include "game/rdr/Enums.hpp" +#include "game/rdr/Natives.hpp" + +namespace YimMenu::Features +{ + static constexpr eNativeInputs controls[] = {eNativeInputs::INPUT_SPRINT, eNativeInputs::INPUT_MOVE_UP_ONLY, eNativeInputs::INPUT_MOVE_DOWN_ONLY, eNativeInputs::INPUT_MOVE_LEFT_ONLY, eNativeInputs::INPUT_MOVE_RIGHT_ONLY, eNativeInputs::INPUT_DUCK}; + static constexpr float speed = 0.57f; + + class Noclip : public LoopedCommand + { + using LoopedCommand::LoopedCommand; + + Entity m_Entity = 0; + float m_SpeedMultiplier; + + virtual void OnTick() override + { + for (const auto& control : controls) + PAD::DISABLE_CONTROL_ACTION(0, static_cast(control), true); + + const auto location = Self::Pos; + Entity ent = Self::PlayerPed; + + if (Self::Mount && PED::IS_PED_ON_MOUNT(Self::PlayerPed)) + ent = Self::Mount; + else if (Self::Veh) + ent = Self::Veh; + + // cleanup when changing entities + if (m_Entity != ent) + { + ENTITY::FREEZE_ENTITY_POSITION(m_Entity, false); + ENTITY::SET_ENTITY_COLLISION(m_Entity, true, false); + + m_Entity = ent; + } + + Vector3 vel{}; + + // Left Shift + if (PAD::IS_DISABLED_CONTROL_PRESSED(0, (int)eNativeInputs::INPUT_SPRINT)) + vel.z += speed / 2; + // Left Control + if (PAD::IS_DISABLED_CONTROL_PRESSED(0, (int)eNativeInputs::INPUT_DUCK)) + vel.z -= speed / 2; + // Forward + if (PAD::IS_DISABLED_CONTROL_PRESSED(0, (int)eNativeInputs::INPUT_MOVE_UP_ONLY)) + vel.y += speed; + // Backward + if (PAD::IS_DISABLED_CONTROL_PRESSED(0, (int)eNativeInputs::INPUT_MOVE_DOWN_ONLY)) + vel.y -= speed; + // Left + if (PAD::IS_DISABLED_CONTROL_PRESSED(0, (int)eNativeInputs::INPUT_MOVE_LEFT_ONLY)) + vel.x -= speed; + // Right + if (PAD::IS_DISABLED_CONTROL_PRESSED(0, (int)eNativeInputs::INPUT_MOVE_RIGHT_ONLY)) + vel.x += speed; + + auto rot = CAM::GET_GAMEPLAY_CAM_ROT(2); + ENTITY::SET_ENTITY_ROTATION(ent, 0.f, rot.y, rot.z, 2, 0); + ENTITY::SET_ENTITY_COLLISION(ent, false, false); + if (vel.x == 0.f && vel.y == 0.f && vel.z == 0.f) + { + // freeze entity to prevent drifting when standing still + ENTITY::FREEZE_ENTITY_POSITION(ent, true); + m_SpeedMultiplier = 0.f; + } + else + { + if (m_SpeedMultiplier < 20.f) + m_SpeedMultiplier += 0.07f; + + ENTITY::FREEZE_ENTITY_POSITION(ent, false); + + #if 0 + // TODO + const auto offset = ENTITY::GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(ent, vel.x, vel.y, 0.f); + vel.x = offset.x - location.x; + vel.y = offset.y - location.y; + + ENTITY::SET_ENTITY_VELOCITY(ent, vel.x * m_SpeedMultiplier, vel.y * m_SpeedMultiplier, vel.z * m_SpeedMultiplier); + #else + const auto offset = ENTITY::GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(ent, vel.x * m_SpeedMultiplier, vel.y * m_SpeedMultiplier, vel.z * m_SpeedMultiplier); + + ENTITY::SET_ENTITY_VELOCITY(ent, 0, 0, 0); + ENTITY::SET_ENTITY_COORDS_NO_OFFSET(ent, offset.x, offset.y, offset.z, true, true, true); + #endif + } + } + + virtual void OnDisable() override + { + ENTITY::FREEZE_ENTITY_POSITION(m_Entity, false); + ENTITY::SET_ENTITY_COLLISION(m_Entity, true, false); + } + }; + + static Noclip _NoClip{"noclip", "No Clip", "Allows you to fly through the map"}; +} \ No newline at end of file diff --git a/src/game/features/self/Suicide.cpp b/src/game/features/self/Suicide.cpp new file mode 100644 index 00000000..3f7c1830 --- /dev/null +++ b/src/game/features/self/Suicide.cpp @@ -0,0 +1,44 @@ +#include "core/commands/Command.hpp" +#include "game/features/Features.hpp" +#include "game/rdr/Natives.hpp" + +#include "game/backend/Players.hpp" +#include