diff --git a/CFramework/CFramework.vcxproj b/CFramework/CFramework.vcxproj
index 37e7dd6..cc0052b 100644
--- a/CFramework/CFramework.vcxproj
+++ b/CFramework/CFramework.vcxproj
@@ -143,6 +143,9 @@
+
+
+
@@ -169,6 +172,8 @@
+
+
diff --git a/CFramework/Cheat/Framework.h b/CFramework/Cheat/Framework.h
index a286a9e..55adb45 100644
--- a/CFramework/Cheat/Framework.h
+++ b/CFramework/Cheat/Framework.h
@@ -260,6 +260,13 @@ private:
void AimMemory(const Vector3& target, const Vector3& localPos, uintptr_t localAddress);
void ScanRotationOffset(); // 2阶段扫描: Player->MC 与 MC->Rotation 偏移
+ // Menu pages are UI-only renderers. They keep the existing Globals/config bindings intact.
+ void RenderAimPage(float columnWidth, float gap);
+ void RenderVisualPage(float columnWidth, float gap);
+ void RenderFeaturesPage(float columnWidth, float gap);
+ void RenderSystemPage(float columnWidth, float gap, const char*& configStatus);
+ void RenderDebugPage(float columnWidth, float gap);
+
// Runtime feature backup state is declared above.
diff --git a/CFramework/Cheat/Menu.cpp b/CFramework/Cheat/Menu.cpp
index f6d729f..4229c1f 100644
--- a/CFramework/Cheat/Menu.cpp
+++ b/CFramework/Cheat/Menu.cpp
@@ -1,357 +1,192 @@
-#include "Framework.h"
-#include "../Framework/Input/Input.h"
+#include "Framework.h"
+#include "UI/MenuTheme.h"
+#include "UI/MenuWidgets.h"
+#include "../Framework/Config/Config.h"
-// ImGui::Combo/ImGui::List等で使う文字列群
-const char* BoxTypeList[] = { "简单", "棱角" };
-const char* CrosshairList[] = { "十字", "圆圈" };
-const char* AimMethodList[] = { "内存", "鼠标" };
-const char* AimBoneList[] = { "头部", "颈部", "身体" };
-std::vector MenuStringList = { "自瞄", "视觉", "功能", "设置" };
-std::vector MenuIconList = { ICON_FA_CROSSHAIRS, ICON_FA_EYE, ICON_FA_BARS, ICON_FA_GEAR };
+#include
+
+namespace
+{
+ enum class MenuPage
+ {
+ Aim,
+ Visual,
+ Features,
+ System,
+ Debug,
+ };
+}
void CFramework::RenderMenu()
{
- // Setup
- static int Index = 0;
- const auto runtimeSnapshot = GetDynamicSnapshot();
- const bool runtimeFresh = runtimeSnapshot && IsSnapshotFresh(runtimeSnapshot->capturedAt);
- const bool runtimeInRaid = runtimeFresh && runtimeSnapshot->inRaid;
- const uintptr_t runtimeLocalAddress = runtimeFresh ? runtimeSnapshot->local.m_address : 0;
- const std::size_t runtimeEntityCount = runtimeFresh ? runtimeSnapshot->players.size() : 0;
+ static MenuPage currentPage = MenuPage::Aim;
+ static const char* configStatus = "自动加载;可手动保存/读取";
- ImGui::SetNextWindowBgAlpha(0.975f);
- ImGui::SetNextWindowSize(ImVec2(725.f, 450.f));
- ImGui::Begin("塔科夫 PvE [ 外部辅助 ]", &g.ShowMenu, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
+ const bool debug = g.g_DebugMode;
+ if (currentPage == MenuPage::Debug && !debug)
+ currentPage = MenuPage::System;
- //---// Clild 0 //-----------------------------------//
- ImGui::BeginChild("##SelectChild", ImVec2(150.f, ImGui::GetContentRegionAvail().y), false);
+ UI::Theme::ApplyTheme();
+ ImGui::SetNextWindowPos(ImVec2(70.f, 40.f), ImGuiCond_FirstUseEver);
+ ImGui::SetNextWindowSize(ImVec2(920.f, 620.f), ImGuiCond_FirstUseEver);
+ ImGui::SetNextWindowSizeConstraints(ImVec2(760.f, 520.f), ImVec2(FLT_MAX, FLT_MAX));
+ ImGui::SetNextWindowBgAlpha(1.f);
- ImGui::NewLine();
+ const ImGuiWindowFlags windowFlags =
+ ImGuiWindowFlags_NoTitleBar |
+ ImGuiWindowFlags_NoCollapse |
+ ImGuiWindowFlags_NoMove |
+ ImGuiWindowFlags_NoScrollbar |
+ ImGuiWindowFlags_NoSavedSettings;
- ImGui::PushFont(icon);
-
- for (int i = 0; i < MenuIconList.size(); i++) {
- if (ImGui::CustomButton(MenuIconList[i], MenuStringList[i], ImVec2(ImGui::GetContentRegionAvail().x, 35.f), Index == i ? true : false))
- Index = i;
+ if (!ImGui::Begin("##CFW_ControlDeck", nullptr, windowFlags))
+ {
+ ImGui::End();
+ return;
}
- ImGui::PopFont();
-
- ImGui::EndChild();
- //---// Clild 0 End //-------------------------------//
-
- ImGui::SameLine();
-
- //---// Clild 1 //-----------------------------------//
- ImGui::BeginChild("##ContextChild", ImVec2(ImGui::GetContentRegionAvail()), false);
-
- //---// Left //--------------------------------------//
- ImGui::BeginChild("##LeftChild", ImVec2(ImGui::GetContentRegionAvail().x / 2.f - 16.f, ImGui::GetContentRegionAvail().y), false);
-
- ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.1f, 0.1f, 0.1f, 1.f));
-
- switch (Index)
+ const auto drawHeader = [&]()
{
- case 0: // AimBot
- ImGui::BeginChild("##C000", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 2.f), true);
+ const ImVec2 headerPos = ImGui::GetCursorScreenPos();
+ const float headerWidth = ImGui::GetContentRegionAvail().x;
+ const float headerHeight = UI::Theme::HeaderHeight;
+ ImDrawList* drawList = ImGui::GetWindowDrawList();
- ImGui::Text("自瞄");
- ImGui::Separator();
- ImGui::Spacing();
+ drawList->AddRectFilled(
+ headerPos,
+ ImVec2(headerPos.x + headerWidth, headerPos.y + headerHeight),
+ UI::Theme::U32(UI::Theme::CardBackground),
+ UI::Theme::CardRounding);
+ drawList->AddRectFilled(
+ headerPos,
+ ImVec2(headerPos.x + headerWidth, headerPos.y + 2.f),
+ UI::Theme::U32(UI::Theme::Accent),
+ 1.f);
- ImGui::Checkbox("启用", &g.g_Aim);
- ImGui::Combo("方式", &g.g_AimMethod, AimMethodList, IM_ARRAYSIZE(AimMethodList));
- ImGui::CustomSliderFloat("锁定范围", "##AimFov", &g.g_AimFov, 10.f, 500.f);
- ImGui::CustomSliderFloat("平滑(0-100)", "##AimSmooth", &g.g_AimSmooth, 0.f, 100.f);
- ImGui::CustomSliderFloat("最大距离", "##AimDist", &g.g_AimMaxDist, 50.f, 2000.f);
- ImGui::Combo("瞄准部位", &g.g_AimBone, AimBoneList, IM_ARRAYSIZE(AimBoneList));
+ ImGui::SetCursorScreenPos(ImVec2(headerPos.x + 16.f, headerPos.y + 14.f));
+ if (icon)
+ {
+ ImGui::PushFont(icon);
+ ImGui::TextColored(UI::Theme::Accent, "%s", ICON_FA_SHIELD_HALVED);
+ ImGui::PopFont();
+ ImGui::SameLine(0.f, 9.f);
+ }
+ ImGui::TextColored(UI::Theme::TextPrimary, "PvE 战术控制台");
+ ImGui::SameLine(0.f, 8.f);
+ ImGui::TextColored(UI::Theme::TextSecondary, "SPT-AKI 3.11.3");
+ ImGui::SameLine(0.f, 8.f);
+ ImGui::TextColored(UI::Theme::AccentStrong, "v2.1");
- ImGui::EndChild();
- ImGui::BeginChild("##C001", ImVec2(ImGui::GetContentRegionAvail()), true);
+ const float statusX = headerPos.x + headerWidth * 0.53f;
+ ImGui::SetCursorScreenPos(ImVec2(statusX, headerPos.y + 15.f));
+ UI::StatusBadge("ESP", g.g_ESP);
+ ImGui::SameLine(0.f, 6.f);
+ UI::StatusBadge("AIM", g.g_Aim);
+ ImGui::SameLine(0.f, 6.f);
+ UI::StatusBadge("RECOIL", g.g_Recoil);
+ ImGui::SameLine(0.f, 6.f);
+ UI::StatusBadge("STREAM", g.g_StreamProof);
- ImGui::Text("鼠标模式");
- ImGui::Separator();
- ImGui::Spacing();
+ const float hideWidth = 66.f;
+ ImGui::SetCursorScreenPos(ImVec2(headerPos.x + headerWidth - hideWidth - 12.f, headerPos.y + 12.f));
+ if (UI::Button("隐藏", ImVec2(hideWidth, 28.f)))
+ g.ShowMenu = false;
- ImGui::CustomSliderFloat("灵敏度", "##AimSens", &g.g_AimSens, 0.1f, 5.f);
- ImGui::CustomSliderFloat("最大速度", "##AimMaxSpeed", &g.g_AimMaxSpeed, 1.f, 300.f);
+ const bool headerHovered = ImGui::IsMouseHoveringRect(
+ headerPos,
+ ImVec2(headerPos.x + headerWidth, headerPos.y + headerHeight));
+ if (headerHovered && !ImGui::IsItemHovered() && ImGui::IsMouseDragging(ImGuiMouseButton_Left))
+ {
+ const ImVec2 delta = ImGui::GetIO().MouseDelta;
+ const ImVec2 currentWindowPos = ImGui::GetWindowPos();
+ ImGui::SetWindowPos(ImVec2(currentWindowPos.x + delta.x, currentWindowPos.y + delta.y));
+ }
- ImGui::Separator();
- ImGui::Spacing();
- ImGui::Text("压枪");
- ImGui::Separator();
- ImGui::Spacing();
+ ImGui::SetCursorScreenPos(ImVec2(headerPos.x, headerPos.y + headerHeight + 12.f));
+ };
+ drawHeader();
- ImGui::Checkbox("启用", &g.g_Recoil);
- ImGui::CustomSliderFloat("力度", "##RecStr", &g.g_RecoilStrength, 0.f, 20.f);
- ImGui::CustomSliderInt("间隔(ms)", "##RecInt", &g.g_RecoilInterval, 1, 100);
-
- ImGui::EndChild();
- break;
- case 1: // Visual
- ImGui::BeginChild("##C010", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 3.f), true);
-
- ImGui::Text("视觉");
- ImGui::Separator();
- ImGui::Spacing();
-
- ImGui::Checkbox("ESP", &g.g_ESP);
- ImGui::Checkbox("物资ESP", &g.g_ESP_Item);
- ImGui::Checkbox("尸体ESP", &g.g_ESP_Corpse);
- ImGui::Checkbox("容器ESP", &g.g_ESP_Container);
- ImGui::Checkbox("撤离点ESP", &g.g_ESP_Exfil);
- ImGui::Checkbox("手雷ESP", &g.g_ESP_Grenade);
-
- ImGui::EndChild();
- ImGui::BeginChild("##C011", ImVec2(ImGui::GetContentRegionAvail()), true);
-
- ImGui::Text("ESP选项");
- ImGui::Separator();
- ImGui::Spacing();
-
- ImGui::Checkbox("连线", &g.g_ESP_Line);
- ImGui::Checkbox("方框", &g.g_ESP_Box);
- ImGui::Checkbox("填充方框", &g.g_ESP_BoxFilled);
- ImGui::Checkbox("骨骼", &g.g_ESP_Skeleton);
- ImGui::Checkbox("距离", &g.g_ESP_Distance);
- ImGui::Checkbox("名称", &g.g_ESP_Name);
- ImGui::Checkbox("血条", &g.g_ESP_HealthBar);
- ImGui::CustomSliderFloat("物资最大距离", "##ItemDist", &g.g_ESP_MaxItemDistance, 0.f, 1000.f);
- ImGui::CustomSliderFloat("容器最大距离", "##ContainerDist", &g.g_ESP_MaxContainerDistance, 0.f, 1000.f);
-
- ImGui::EndChild();
- break;
- case 2: // Misc
- ImGui::BeginChild("##C020", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 3.f), true);
-
- ImGui::Text("功能");
- ImGui::Separator();
- ImGui::Spacing();
-
- ImGui::Checkbox("无限耐力", &g.g_InfStamina);
- ImGui::Checkbox("无跌落伤害", &g.g_NoFallDamage);
- ImGui::Checkbox("无摇摆", &g.g_NoSway);
- ImGui::Checkbox("无后座(内存)", &g.g_NoRecoil);
- ImGui::Checkbox("无故障/卡弹", &g.g_NoMalfunction);
-
- ImGui::Spacing();
- ImGui::Separator();
- ImGui::TextDisabled("只写入 dump 已确认的对象字段");
- ImGui::TextDisabled("弹道追踪:暂不写入,等待 runtime 验证");
- ImGui::EndChild();
-
- ImGui::BeginChild("##C021", ImVec2(ImGui::GetContentRegionAvail()), true);
-
- ImGui::EndChild();
- break;
- case 3: // Setting
- ImGui::BeginChild("##C030", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 3.f), true);
- ImGui::Text("系统");
- ImGui::Separator();
- ImGui::Spacing();
-
- ImGui::Checkbox("串流隐藏", &g.g_StreamProof);
-
- ImGui::EndChild();
- ImGui::BeginChild("##C031", ImVec2(ImGui::GetContentRegionAvail()), true);
-
- ImGui::Text("准星");
- ImGui::Separator();
- ImGui::Spacing();
-
- ImGui::Checkbox("准星", &g.g_Crosshair);
- ImGui::CustomSliderInt("准星大小", "##SizeCH", &g.g_CrosshairSize, 1, 10);
- ImGui::ColorEdit4("颜色##C", &CrosshairColor.Value.x);
- ImGui::Combo("类型##C", &g.g_CrosshairType, CrosshairList, IM_ARRAYSIZE(CrosshairList));
-
- ImGui::EndChild();
- break;
- default:
- break;
+ if (UI::Tab("自瞄", currentPage == MenuPage::Aim))
+ currentPage = MenuPage::Aim;
+ ImGui::SameLine(0.f, 6.f);
+ if (UI::Tab("视觉", currentPage == MenuPage::Visual))
+ currentPage = MenuPage::Visual;
+ ImGui::SameLine(0.f, 6.f);
+ if (UI::Tab("功能", currentPage == MenuPage::Features))
+ currentPage = MenuPage::Features;
+ ImGui::SameLine(0.f, 6.f);
+ if (UI::Tab("系统", currentPage == MenuPage::System))
+ currentPage = MenuPage::System;
+ if (debug)
+ {
+ ImGui::SameLine(0.f, 6.f);
+ if (UI::Tab("调试", currentPage == MenuPage::Debug))
+ currentPage = MenuPage::Debug;
}
+ UI::Divider();
- ImGui::PopStyleColor();
-
- ImGui::EndChild();
- //---------------------------------------------------//
-
- ImGui::SameLine();
-
- //---// Right //--------------------------------------//
- ImGui::BeginChild("##RightChild", ImVec2(ImGui::GetContentRegionAvail()), false);
-
- ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.1f, 0.1f, 0.1f, 1.f));
-
- switch (Index)
+ const float footerHeight = UI::Theme::FooterHeight;
+ if (ImGui::BeginChild(
+ "##CFW_Content",
+ ImVec2(0.f, -footerHeight),
+ ImGuiChildFlags_None,
+ ImGuiWindowFlags_NoBackground))
{
- case 0: // AimBot
- ImGui::BeginChild("##C100", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 2.f), true);
-
- ImGui::Text("目标过滤");
- ImGui::Separator();
- ImGui::Spacing();
-
- ImGui::Checkbox("PMC", &g.g_AimTargetPMC);
- ImGui::Checkbox("SCAV", &g.g_AimTargetScav);
- ImGui::Checkbox("BOSS", &g.g_AimTargetBoss);
- ImGui::Checkbox("忽略血量", &g.g_AimIgnoreHealth);
- ImGui::Checkbox("强制开启(免按键)", &g.g_AimForce);
- ImGui::Checkbox("显示范围圈", &g.g_AimDrawFov);
- ImGui::Checkbox("显示目标线", &g.g_AimDrawTarget);
-
- ImGui::Spacing();
- ImGui::Separator();
- ImGui::Spacing();
-
- // 数据链路状态: 哪一环断了看这里
- ImGui::Text("自瞄状态");
- ImGui::Text("局内 : %s", runtimeInRaid ? "是" : "否");
- ImGui::Text("本地 : 0x%llX", (unsigned long long)runtimeLocalAddress);
- ImGui::Text("实体 : %zu", runtimeEntityCount);
- ImGui::Text("扫描 : %d 实体 / %d 血量 / %d 跳过", m_diagTotal, m_diagHealth, m_diagSkipped);
- ImGui::Text("目标 : %s (距 %0.fpx)", m_diagBestAddr ? "锁定" : "无", m_diagBestDist);
+ const float gap = UI::Theme::ColumnGap;
+ const float columnWidth = UI::ColumnWidth(gap);
+ switch (currentPage)
{
- bool k2 = (GetAsyncKeyState(g.g_AimKey2) & 0x8000) != 0;
- ImGui::Text("按键 : %s (右键/左键/F9)", (IsKeyDown(g.g_AimKey0) || IsKeyDown(g.g_AimKey1) || k2) ? "按住" : "---");
+ case MenuPage::Aim:
+ RenderAimPage(columnWidth, gap);
+ break;
+ case MenuPage::Visual:
+ RenderVisualPage(columnWidth, gap);
+ break;
+ case MenuPage::Features:
+ RenderFeaturesPage(columnWidth, gap);
+ break;
+ case MenuPage::System:
+ RenderSystemPage(columnWidth, gap, configStatus);
+ break;
+ case MenuPage::Debug:
+ RenderDebugPage(columnWidth, gap);
+ break;
}
+ }
+ ImGui::EndChild();
- ImGui::EndChild();
- ImGui::BeginChild("##C101", ImVec2(ImGui::GetContentRegionAvail()), true);
+ ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::Theme::WindowBackground);
+ if (ImGui::BeginChild(
+ "##CFW_Footer",
+ ImVec2(0.f, footerHeight),
+ ImGuiChildFlags_AlwaysUseWindowPadding,
+ ImGuiWindowFlags_NoScrollbar))
+ {
+ UI::Divider();
+ const float rowStart = ImGui::GetCursorPosX();
+ const float rightEdge = rowStart + ImGui::GetContentRegionAvail().x;
+ constexpr float buttonWidth = 88.f;
+ constexpr float buttonGap = 8.f;
+ constexpr float actionsWidth = buttonWidth * 3.f + buttonGap * 2.f;
- ImGui::Text("偏移调试(内存)");
- ImGui::Separator();
- ImGui::Spacing();
-
- if (runtimeInRaid && runtimeLocalAddress != 0)
- {
- uintptr_t mc = m.Read(runtimeLocalAddress + (uintptr_t)g.g_AimMcOffset);
- if (mc)
- {
- Vector2 rot = m.Read(mc + (uintptr_t)g.g_AimRotOffset);
- ImGui::Text("水平 : %.3f", rot.x);
- ImGui::Text("俯仰 : %.3f", rot.y);
- ImGui::TextDisabled("移动鼠标验证偏移是否正确");
- }
- else
- {
- ImGui::TextDisabled("MC 为空 (偏移错误?)");
- }
- }
- else
- {
- ImGui::TextDisabled("仅限局内显示");
- }
-
- ImGui::Spacing();
- ImGui::Separator();
- ImGui::Spacing();
-
- ImGui::Text("偏移(十六进制)");
- ImGui::InputInt("MC", &g.g_AimMcOffset, 8, 8, ImGuiInputTextFlags_CharsHexadecimal);
- ImGui::InputInt("旋转", &g.g_AimRotOffset, 4, 4, ImGuiInputTextFlags_CharsHexadecimal);
-
- // dump.txt: MovementContext = Player +0x58, Rotation_1 = MovementContext +0x27C
- if (ImGui::Button("重置偏移 (0x58/0x27C)", ImVec2(ImGui::GetContentRegionAvail().x, 0.f)))
- {
- g.g_AimMcOffset = 0x58;
- g.g_AimRotOffset = 0x27C;
- }
-
- if (ImGui::Button("扫描旋转偏移(两步)", ImVec2(ImGui::GetContentRegionAvail().x, 0.f)))
- ScanRotationOffset();
-
- if (m_scanPhase == 1)
- ImGui::TextColored(TEXT_COLOR_ATTENTION.Value, "水平转动鼠标后,再点一次扫描");
-
- if (m_scanResult[0])
- ImGui::TextWrapped("%s", m_scanResult);
-
- ImGui::EndChild();
- break;
- case 1: // Visual
- ImGui::BeginChild("##C110", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 2.5f), true);
-
- ImGui::Text("ESP设置");
- ImGui::Separator();
- ImGui::Spacing();
-
- ImGui::CustomSliderFloat("玩家", "##Dist", &g.g_ESP_MaxDistance, 100.f, 2000.f);
- ImGui::CustomSliderFloat("物资", "##ItemDist", &g.g_ESP_MaxItemDistance, 10.f, 1000.f);
-
- ImGui::Spacing();
- ImGui::Spacing();
-
- ImGui::Combo("方框样式", &g.g_ESP_BoxStyle, BoxTypeList, IM_ARRAYSIZE(BoxTypeList));
-
- ImGui::EndChild();
- ImGui::BeginChild("##C111", ImVec2(ImGui::GetContentRegionAvail()), true);
-
- ImGui::Text("ESP颜色");
- ImGui::Separator();
- ImGui::Spacing();
-
- if (ImGui::TreeNode("玩家/SCAV"))
- {
- ImGui::ColorEdit4("PMC", &Col_ESP_PMC.Value.x);
- ImGui::ColorEdit4("SCAV", &Col_ESP_Scav.Value.x);
- ImGui::ColorEdit4("玩家SCAV", &Col_ESP_pScav.Value.x);
- ImGui::ColorEdit4("精英SCAV", &Col_ESP_SpecialScav.Value.x);
- ImGui::ColorEdit4("BOSS", &Col_ESP_Boss.Value.x);
- ImGui::ColorEdit4("填充", &Col_ESP_Filled.Value.x);
-
- ImGui::TreePop();
- }
- if (ImGui::TreeNode("游戏"))
- {
- ImGui::Text("[+] 物资");
- ImGui::ColorEdit4("稀有", &Col_ESP_RareItem.Value.x);
-
- ImGui::NewLine();
- ImGui::Spacing();
-
- ImGui::Text("[+] 撤离点");
- ImGui::ColorEdit4("开启", &Col_ESP_ExfilOpen.Value.x);
- ImGui::ColorEdit4("关闭", &Col_ESP_ExfilClose.Value.x);
-
- ImGui::TreePop();
- }
-
- ImGui::EndChild();
- break;
- case 2: // Misc
- ImGui::BeginChild("##C200", ImVec2(ImGui::GetContentRegionAvail()), true);
-
- ImGui::Text("功能状态");
- ImGui::Separator();
- ImGui::Spacing();
- ImGui::Text("局内:%s", runtimeInRaid ? "是" : "否");
- ImGui::Text("本地:0x%llX", (unsigned long long)runtimeLocalAddress);
- ImGui::TextDisabled("关闭开关或离开局内会恢复原值");
- ImGui::TextDisabled("非法/不完整指针链不会写入");
-
- ImGui::EndChild();
- break;
- case 3: // Setting
-
- ImGui::BeginChild("##C300", ImVec2(ImGui::GetContentRegionAvail()), true);
-
- ImGui::Text("退出");
- ImGui::Separator();
- ImGui::Spacing();
- if (ImGui::Button("退出", ImVec2(ImGui::GetContentRegionAvail().x, 30.f)))
+ ImGui::TextColored(
+ UI::Theme::TextSecondary,
+ "配置 %s · %s",
+ Config::FileName(),
+ configStatus);
+ const float actionX = (std::max)(ImGui::GetCursorPosX() + 12.f, rightEdge - actionsWidth);
+ ImGui::SameLine(actionX);
+ if (UI::Button("读取配置", ImVec2(buttonWidth, 30.f)))
+ configStatus = Config::Load() ? "配置已读取" : "配置读取失败(文件不存在或不可读)";
+ ImGui::SameLine(0.f, buttonGap);
+ if (UI::Button("保存配置", ImVec2(buttonWidth, 30.f), false, true))
+ configStatus = Config::Save() ? "配置已保存" : "配置保存失败";
+ ImGui::SameLine(0.f, buttonGap);
+ if (UI::Button("退出", ImVec2(buttonWidth, 30.f), true))
g.process_active = false;
-
- ImGui::EndChild();
- break;
- default:
- break;
}
-
+ ImGui::EndChild();
ImGui::PopStyleColor();
- ImGui::EndChild();
- ImGui::EndChild();
- //---------------------------------------------------//
-
ImGui::End();
-}
\ No newline at end of file
+}
diff --git a/CFramework/Cheat/MenuPages.cpp b/CFramework/Cheat/MenuPages.cpp
new file mode 100644
index 0000000..6c371b9
--- /dev/null
+++ b/CFramework/Cheat/MenuPages.cpp
@@ -0,0 +1,309 @@
+#include "Framework.h"
+#include "UI/MenuWidgets.h"
+#include "../Framework/Config/Config.h"
+#include "../Framework/Input/Input.h"
+#include "../Framework/Memory/Memory.h"
+
+namespace
+{
+ const char* kBoxTypeList[] = { "简单", "棱角" };
+ const char* kCrosshairList[] = { "十字", "圆圈" };
+ const char* kAimMethodList[] = { "内存", "鼠标" };
+ const char* kAimBoneList[] = { "头部", "颈部", "身体" };
+
+ void EndRow()
+ {
+ ImGui::Spacing();
+ ImGui::Spacing();
+ }
+}
+
+void CFramework::RenderAimPage(float columnWidth, float gap)
+{
+ if (UI::BeginCard("##aim_basic", "基础设置", UI::Theme::Accent, columnWidth))
+ {
+ UI::Toggle("自瞄", &g.g_Aim);
+ UI::Combo("方式", &g.g_AimMethod, kAimMethodList, IM_ARRAYSIZE(kAimMethodList));
+ UI::Combo("部位", &g.g_AimBone, kAimBoneList, IM_ARRAYSIZE(kAimBoneList));
+ UI::SliderFloat("锁定范围", &g.g_AimFov, 10.f, 500.f);
+ UI::SliderFloat("平滑", &g.g_AimSmooth, 0.f, 100.f);
+ UI::SliderFloat("最大距离", &g.g_AimMaxDist, 50.f, 2000.f);
+ UI::SliderFloat("灵敏度", &g.g_AimSens, 0.1f, 5.f, "%.2f");
+ UI::SliderFloat("最大速度", &g.g_AimMaxSpeed, 1.f, 300.f);
+ }
+ UI::EndCard();
+ UI::NextColumn(gap);
+
+ if (UI::BeginCard("##aim_behavior", "行为设置", UI::Theme::AccentStrong, columnWidth))
+ {
+ UI::Toggle("PMC", &g.g_AimTargetPMC);
+ UI::Toggle("SCAV", &g.g_AimTargetScav);
+ UI::Toggle("BOSS", &g.g_AimTargetBoss);
+ UI::Toggle("忽略血量", &g.g_AimIgnoreHealth);
+ UI::Toggle("强制开启(免按键)", &g.g_AimForce);
+ UI::Toggle("显示范围圈", &g.g_AimDrawFov);
+ UI::Toggle("显示目标线", &g.g_AimDrawTarget);
+ ImGui::Spacing();
+ UI::StatusBadge("自瞄", g.g_Aim);
+ ImGui::SameLine(0.f, 8.f);
+ UI::StatusBadge("压枪", g.g_Recoil);
+ UI::Note("强制开启会绕过触发键;范围圈和目标线只影响可视化。 ");
+ }
+ UI::EndCard();
+ EndRow();
+
+ if (UI::BeginCard("##aim_recoil", "压枪 / 后座", ImVec4(0.95f, 0.62f, 0.31f, 1.f), columnWidth))
+ {
+ UI::Toggle("压枪", &g.g_Recoil);
+ UI::SliderFloat("压枪力度", &g.g_RecoilStrength, 0.f, 20.f);
+ UI::SliderInt("压枪间隔(ms)", &g.g_RecoilInterval, 1, 100);
+ UI::Note("力度和间隔直接绑定现有压枪参数,不改变原有更新路径。 ");
+ }
+ UI::EndCard();
+ UI::NextColumn(gap);
+
+ if (UI::BeginCard("##aim_hotkeys", "按键绑定", ImVec4(0.40f, 0.82f, 0.62f, 1.f), columnWidth))
+ {
+ UI::Hotkey("菜单键", &g.g_MenuKey);
+ UI::Hotkey("触发键 1", &g.g_AimKey0, g.g_MenuKey);
+ UI::Hotkey("触发键 2", &g.g_AimKey1, g.g_MenuKey);
+ UI::Hotkey("备用键", &g.g_AimKey2, g.g_MenuKey);
+ UI::Note("点击右侧按键框后输入新键;显示 Press a key... 时按 Escape 取消。 ");
+ }
+ UI::EndCard();
+ EndRow();
+}
+
+void CFramework::RenderVisualPage(float columnWidth, float gap)
+{
+ if (UI::BeginCard("##visual_modules", "ESP 模块", UI::Theme::Accent, columnWidth))
+ {
+ UI::Toggle("ESP", &g.g_ESP);
+ UI::Toggle("物资 ESP", &g.g_ESP_Item);
+ UI::Toggle("尸体 ESP", &g.g_ESP_Corpse);
+ UI::Toggle("容器 ESP", &g.g_ESP_Container);
+ UI::Toggle("撤离点 ESP", &g.g_ESP_Exfil);
+ UI::Toggle("手雷 ESP", &g.g_ESP_Grenade);
+ }
+ UI::EndCard();
+ UI::NextColumn(gap);
+
+ if (UI::BeginCard("##visual_options", "显示选项", UI::Theme::AccentStrong, columnWidth))
+ {
+ UI::Toggle("连线", &g.g_ESP_Line);
+ UI::Toggle("方框", &g.g_ESP_Box);
+ UI::Toggle("填充方框", &g.g_ESP_BoxFilled);
+ UI::Toggle("骨骼", &g.g_ESP_Skeleton);
+ UI::Toggle("距离", &g.g_ESP_Distance);
+ UI::Toggle("名称", &g.g_ESP_Name);
+ UI::Toggle("血条", &g.g_ESP_HealthBar);
+ }
+ UI::EndCard();
+ EndRow();
+
+ if (UI::BeginCard("##visual_distance", "距离与样式", ImVec4(0.95f, 0.62f, 0.31f, 1.f), columnWidth))
+ {
+ UI::SliderFloat("玩家距离", &g.g_ESP_MaxDistance, 100.f, 2000.f);
+ UI::SliderFloat("物资距离", &g.g_ESP_MaxItemDistance, 0.f, 1000.f);
+ UI::SliderFloat("容器距离", &g.g_ESP_MaxContainerDistance, 0.f, 1000.f);
+ UI::Combo("方框样式", &g.g_ESP_BoxStyle, kBoxTypeList, IM_ARRAYSIZE(kBoxTypeList));
+ }
+ UI::EndCard();
+ UI::NextColumn(gap);
+
+ if (UI::BeginCard("##visual_crosshair", "准星", ImVec4(0.78f, 0.55f, 0.95f, 1.f), columnWidth))
+ {
+ UI::Toggle("准星", &g.g_Crosshair);
+ UI::SliderInt("准星大小", &g.g_CrosshairSize, 1, 10);
+ UI::ColorEdit("准星颜色", &CrosshairColor.Value);
+ UI::Combo("准星类型", &g.g_CrosshairType, kCrosshairList, IM_ARRAYSIZE(kCrosshairList));
+ }
+ UI::EndCard();
+ EndRow();
+
+ if (UI::BeginCard("##visual_player_colors", "玩家颜色", UI::Theme::Accent, columnWidth))
+ {
+ UI::ColorEdit("PMC", &Col_ESP_PMC.Value);
+ UI::ColorEdit("SCAV", &Col_ESP_Scav.Value);
+ UI::ColorEdit("玩家 SCAV", &Col_ESP_pScav.Value);
+ UI::ColorEdit("精英 SCAV", &Col_ESP_SpecialScav.Value);
+ UI::ColorEdit("BOSS", &Col_ESP_Boss.Value);
+ UI::ColorEdit("填充", &Col_ESP_Filled.Value);
+ }
+ UI::EndCard();
+ UI::NextColumn(gap);
+
+ if (UI::BeginCard("##visual_object_colors", "游戏对象颜色", ImVec4(0.40f, 0.82f, 0.62f, 1.f), columnWidth))
+ {
+ UI::ColorEdit("稀有物资", &Col_ESP_RareItem.Value);
+ UI::ColorEdit("撤离点开启", &Col_ESP_ExfilOpen.Value);
+ UI::ColorEdit("撤离点关闭", &Col_ESP_ExfilClose.Value);
+ UI::Note("颜色编辑仍写入现有 ImColor 对象,渲染端无需改动。 ");
+ }
+ UI::EndCard();
+ EndRow();
+}
+
+void CFramework::RenderFeaturesPage(float columnWidth, float gap)
+{
+ if (UI::BeginCard("##features_player", "角色增强", UI::Theme::Accent, columnWidth))
+ {
+ UI::Toggle("无限耐力", &g.g_InfStamina);
+ UI::Toggle("无跌落伤害", &g.g_NoFallDamage);
+ UI::Toggle("无摇摆", &g.g_NoSway);
+ }
+ UI::EndCard();
+ UI::NextColumn(gap);
+
+ if (UI::BeginCard("##features_weapon", "武器增强", UI::Theme::AccentStrong, columnWidth))
+ {
+ UI::Toggle("无后座(内存)", &g.g_NoRecoil);
+ UI::Toggle("无故障 / 卡弹", &g.g_NoMalfunction);
+ ImGui::Spacing();
+ UI::StatusBadge("无限耐力", g.g_InfStamina);
+ ImGui::SameLine(0.f, 8.f);
+ UI::StatusBadge("无后座", g.g_NoRecoil);
+ ImGui::SameLine(0.f, 8.f);
+ UI::StatusBadge("无故障", g.g_NoMalfunction);
+ }
+ UI::EndCard();
+ EndRow();
+}
+
+void CFramework::RenderSystemPage(float columnWidth, float gap, const char*& configStatus)
+{
+ if (UI::BeginCard("##system_switches", "系统", UI::Theme::Accent, columnWidth))
+ {
+ UI::Toggle("串流隐藏", &g.g_StreamProof);
+ UI::Toggle("调试模式", &g.g_DebugMode);
+ UI::Toggle("准星", &g.g_Crosshair);
+ UI::Note("调试模式打开后会显示额外的偏移扫描与运行时诊断页。 ");
+ }
+ UI::EndCard();
+ UI::NextColumn(gap);
+
+ if (UI::BeginCard("##system_config", "配置", ImVec4(0.95f, 0.62f, 0.31f, 1.f), columnWidth))
+ {
+ ImGui::TextColored(UI::Theme::TextPrimary, "文件 %s", Config::FileName());
+ if (UI::Button("读取配置", ImVec2(112.f, 28.f)))
+ configStatus = Config::Load() ? "配置已读取" : "配置读取失败(文件不存在或不可读)";
+ ImGui::SameLine(0.f, 8.f);
+ if (UI::Button("保存配置", ImVec2(112.f, 28.f), false, true))
+ configStatus = Config::Save() ? "配置已保存" : "配置保存失败";
+ UI::Note(configStatus);
+ UI::Note("配置文件保存/读取路径和字段集合保持不变。 ");
+ }
+ UI::EndCard();
+ EndRow();
+
+ if (UI::BeginCard("##system_hotkeys", "快捷键", ImVec4(0.40f, 0.82f, 0.62f, 1.f), columnWidth))
+ {
+ UI::Hotkey("菜单键", &g.g_MenuKey);
+ UI::Hotkey("触发键 1", &g.g_AimKey0, g.g_MenuKey);
+ UI::Hotkey("触发键 2", &g.g_AimKey1, g.g_MenuKey);
+ UI::Hotkey("备用键", &g.g_AimKey2, g.g_MenuKey);
+ }
+ UI::EndCard();
+ UI::NextColumn(gap);
+
+ if (UI::BeginCard("##system_crosshair", "准星", UI::Theme::AccentStrong, columnWidth))
+ {
+ UI::SliderInt("准星大小", &g.g_CrosshairSize, 1, 10);
+ UI::ColorEdit("准星颜色", &CrosshairColor.Value);
+ UI::Combo("准星类型", &g.g_CrosshairType, kCrosshairList, IM_ARRAYSIZE(kCrosshairList));
+ }
+ UI::EndCard();
+ EndRow();
+
+ if (UI::BeginCard("##system_about", "关于", ImVec4(0.40f, 0.82f, 0.62f, 1.f), columnWidth))
+ {
+ ImGui::TextColored(UI::Theme::TextPrimary, "PvE 战术控制台 v2.1");
+ UI::Note("SPT-AKI 3.11.3 / 只读快照渲染");
+ UI::Note("调试页签仅在调试模式开启时出现。 ");
+ }
+ UI::EndCard();
+ UI::NextColumn(gap);
+
+ if (UI::BeginCard("##system_status", "运行状态", UI::Theme::Accent, columnWidth))
+ {
+ UI::StatusBadge("ESP", g.g_ESP);
+ ImGui::SameLine(0.f, 8.f);
+ UI::StatusBadge("串流", g.g_StreamProof);
+ ImGui::SameLine(0.f, 8.f);
+ UI::StatusBadge("调试", g.g_DebugMode);
+ }
+ UI::EndCard();
+ EndRow();
+}
+
+void CFramework::RenderDebugPage(float columnWidth, float gap)
+{
+ if (UI::BeginCard("##debug_offsets", "偏移调试(内存)", UI::Theme::Danger, columnWidth))
+ {
+ ImGui::TextColored(UI::Theme::TextSecondary, "运行时偏移仅在调试页修改,绑定保持原样。");
+ ImGui::SetNextItemWidth(-1.f);
+ ImGui::InputInt("MC", &g.g_AimMcOffset, 8, 8, ImGuiInputTextFlags_CharsHexadecimal);
+ ImGui::SetNextItemWidth(-1.f);
+ ImGui::InputInt("旋转", &g.g_AimRotOffset, 4, 4, ImGuiInputTextFlags_CharsHexadecimal);
+ if (UI::Button("重置偏移(0x58 / 0x27C)", ImVec2(-1.f, 28.f)))
+ {
+ g.g_AimMcOffset = 0x58;
+ g.g_AimRotOffset = 0x27C;
+ }
+ if (UI::Button("扫描旋转偏移(两步)", ImVec2(-1.f, 28.f)))
+ ScanRotationOffset();
+ if (m_scanPhase == 1)
+ ImGui::TextColored(TEXT_COLOR_ATTENTION.Value, "水平转动鼠标后,再点一次扫描");
+ if (m_scanResult[0])
+ ImGui::TextWrapped("%s", m_scanResult);
+ }
+ UI::EndCard();
+ UI::NextColumn(gap);
+
+ if (UI::BeginCard("##debug_status", "自瞄状态", UI::Theme::Danger, columnWidth))
+ {
+ const auto runtimeSnapshot = GetDynamicSnapshot();
+ const bool runtimeFresh = runtimeSnapshot && IsSnapshotFresh(runtimeSnapshot->capturedAt);
+ const bool runtimeInRaid = runtimeFresh && runtimeSnapshot->inRaid;
+ const uintptr_t runtimeLocalAddress = runtimeFresh ? runtimeSnapshot->local.m_address : 0;
+ const std::size_t runtimeEntityCount = runtimeFresh ? runtimeSnapshot->players.size() : 0;
+
+ ImGui::Text("局内 : %s", runtimeInRaid ? "是" : "否");
+ ImGui::Text("本地 : 0x%llX", static_cast(runtimeLocalAddress));
+ ImGui::Text("实体 : %zu", runtimeEntityCount);
+ ImGui::Text("扫描 : %d 实体 / %d 血量 / %d 跳过", m_diagTotal, m_diagHealth, m_diagSkipped);
+ ImGui::Text("目标 : %s(距 %.0fpx)", m_diagBestAddr ? "锁定" : "无", m_diagBestDist);
+ const bool k2 = (GetAsyncKeyState(g.g_AimKey2) & 0x8000) != 0;
+ ImGui::Text("按键 : %s(当前触发键)",
+ (IsKeyDown(g.g_AimKey0) || IsKeyDown(g.g_AimKey1) || k2) ? "按住" : "---");
+
+ if (runtimeInRaid && runtimeLocalAddress != 0)
+ {
+ const uintptr_t mc = m.Read(runtimeLocalAddress + static_cast(g.g_AimMcOffset));
+ if (mc)
+ {
+ const Vector2 rot = m.Read(mc + static_cast(g.g_AimRotOffset));
+ ImGui::Text("水平 : %.3f", rot.x);
+ ImGui::Text("俯仰 : %.3f", rot.y);
+ UI::Note("移动鼠标验证偏移是否正确。 ");
+ }
+ else
+ {
+ UI::Note("MC 为空(偏移错误?)");
+ }
+ }
+ else
+ {
+ UI::Note("仅限局内显示。 ");
+ }
+ }
+ UI::EndCard();
+ EndRow();
+
+ if (UI::BeginCard("##debug_notes", "调试说明", UI::Theme::Danger, columnWidth))
+ {
+ UI::Note("此页仅调试模式可见。 ");
+ UI::Note("偏移调试、扫描与诊断读取都在此页。 ");
+ }
+ UI::EndCard();
+}
diff --git a/CFramework/Cheat/UI/MenuTheme.cpp b/CFramework/Cheat/UI/MenuTheme.cpp
new file mode 100644
index 0000000..a7cf7f1
--- /dev/null
+++ b/CFramework/Cheat/UI/MenuTheme.cpp
@@ -0,0 +1,71 @@
+#include "MenuTheme.h"
+
+namespace UI::Theme
+{
+ const ImVec4 WindowBackground{ 0.067f, 0.075f, 0.094f, 1.f }; // #111318
+ const ImVec4 CardBackground{ 0.090f, 0.102f, 0.125f, 1.f }; // #171A20
+ const ImVec4 SecondaryBackground{ 0.114f, 0.129f, 0.161f, 1.f }; // #1D2129
+ const ImVec4 SecondaryHover{ 0.150f, 0.173f, 0.216f, 1.f };
+ const ImVec4 TextPrimary{ 0.910f, 0.922f, 0.941f, 1.f }; // #E8EBF0
+ const ImVec4 TextSecondary{ 0.545f, 0.576f, 0.647f, 1.f }; // #8B93A5
+ const ImVec4 Accent{ 0.286f, 0.827f, 0.729f, 1.f }; // cyan teal
+ const ImVec4 AccentStrong{ 0.400f, 0.898f, 0.804f, 1.f };
+ const ImVec4 AccentSoft{ 0.286f, 0.827f, 0.729f, 0.18f };
+ const ImVec4 Border{ 1.f, 1.f, 1.f, 0.07f };
+ const ImVec4 ToggleOff{ 0.235f, 0.255f, 0.306f, 1.f };
+ const ImVec4 Danger{ 0.930f, 0.330f, 0.350f, 1.f };
+ const ImVec4 DangerSoft{ 0.390f, 0.150f, 0.170f, 1.f };
+
+ ImU32 U32(const ImVec4& color)
+ {
+ return ImGui::GetColorU32(color);
+ }
+
+ void ApplyTheme()
+ {
+ ImGuiStyle& style = ImGui::GetStyle();
+ style.WindowRounding = WindowRounding;
+ style.ChildRounding = CardRounding;
+ style.FrameRounding = FrameRounding;
+ style.PopupRounding = PopupRounding;
+ style.ScrollbarRounding = ScrollbarRounding;
+ style.GrabRounding = GrabRounding;
+ style.WindowPadding = ImVec2(16.f, 14.f);
+ style.FramePadding = ImVec2(10.f, 7.f);
+ style.ItemSpacing = ImVec2(10.f, 8.f);
+ style.ItemInnerSpacing = ImVec2(8.f, 6.f);
+ style.ScrollbarSize = 10.f;
+ style.GrabMinSize = 10.f;
+ style.WindowBorderSize = 0.f;
+ style.ChildBorderSize = 1.f;
+ style.FrameBorderSize = 0.f;
+ style.PopupBorderSize = 1.f;
+
+ style.Colors[ImGuiCol_WindowBg] = WindowBackground;
+ style.Colors[ImGuiCol_ChildBg] = CardBackground;
+ style.Colors[ImGuiCol_PopupBg] = SecondaryBackground;
+ style.Colors[ImGuiCol_Border] = Border;
+ style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.f, 0.f, 0.f, 0.f);
+ style.Colors[ImGuiCol_FrameBg] = SecondaryBackground;
+ style.Colors[ImGuiCol_FrameBgHovered] = SecondaryHover;
+ style.Colors[ImGuiCol_FrameBgActive] = AccentSoft;
+ style.Colors[ImGuiCol_TitleBg] = WindowBackground;
+ style.Colors[ImGuiCol_TitleBgActive] = WindowBackground;
+ style.Colors[ImGuiCol_Button] = SecondaryBackground;
+ style.Colors[ImGuiCol_ButtonHovered] = SecondaryHover;
+ style.Colors[ImGuiCol_ButtonActive] = AccentSoft;
+ style.Colors[ImGuiCol_Header] = SecondaryBackground;
+ style.Colors[ImGuiCol_HeaderHovered] = SecondaryHover;
+ style.Colors[ImGuiCol_HeaderActive] = AccentSoft;
+ style.Colors[ImGuiCol_SliderGrab] = Accent;
+ style.Colors[ImGuiCol_SliderGrabActive] = AccentStrong;
+ style.Colors[ImGuiCol_CheckMark] = Accent;
+ style.Colors[ImGuiCol_Separator] = Border;
+ style.Colors[ImGuiCol_SeparatorHovered] = AccentSoft;
+ style.Colors[ImGuiCol_SeparatorActive] = Accent;
+ style.Colors[ImGuiCol_Text] = TextPrimary;
+ style.Colors[ImGuiCol_TextDisabled] = TextSecondary;
+ style.Colors[ImGuiCol_TextSelectedBg] = AccentSoft;
+ style.Colors[ImGuiCol_NavHighlight] = AccentSoft;
+ }
+}
diff --git a/CFramework/Cheat/UI/MenuTheme.h b/CFramework/Cheat/UI/MenuTheme.h
new file mode 100644
index 0000000..d348435
--- /dev/null
+++ b/CFramework/Cheat/UI/MenuTheme.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "../../Framework/ImGui/imgui.h"
+
+namespace UI::Theme
+{
+ extern const ImVec4 WindowBackground;
+ extern const ImVec4 CardBackground;
+ extern const ImVec4 SecondaryBackground;
+ extern const ImVec4 SecondaryHover;
+ extern const ImVec4 TextPrimary;
+ extern const ImVec4 TextSecondary;
+ extern const ImVec4 Accent;
+ extern const ImVec4 AccentStrong;
+ extern const ImVec4 AccentSoft;
+ extern const ImVec4 Border;
+ extern const ImVec4 ToggleOff;
+ extern const ImVec4 Danger;
+ extern const ImVec4 DangerSoft;
+
+ constexpr float WindowRounding = 10.f;
+ constexpr float CardRounding = 8.f;
+ constexpr float FrameRounding = 6.f;
+ constexpr float PopupRounding = 8.f;
+ constexpr float ScrollbarRounding = 6.f;
+ constexpr float GrabRounding = 6.f;
+ constexpr float HeaderHeight = 52.f;
+ constexpr float FooterHeight = 80.f;
+ constexpr float ColumnGap = 14.f;
+
+ ImU32 U32(const ImVec4& color);
+ void ApplyTheme();
+}
diff --git a/CFramework/Cheat/UI/MenuWidgets.cpp b/CFramework/Cheat/UI/MenuWidgets.cpp
new file mode 100644
index 0000000..cad7a0f
--- /dev/null
+++ b/CFramework/Cheat/UI/MenuWidgets.cpp
@@ -0,0 +1,447 @@
+#include "MenuWidgets.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace
+{
+ std::unordered_map g_toggleAnimation;
+ ImGuiID g_captureId = 0;
+ bool g_captureArmed = false;
+
+ ImVec4 Mix(const ImVec4& a, const ImVec4& b, float t)
+ {
+ return ImVec4(
+ a.x + (b.x - a.x) * t,
+ a.y + (b.y - a.y) * t,
+ a.z + (b.z - a.z) * t,
+ a.w + (b.w - a.w) * t);
+ }
+
+ bool AnyVirtualKeyDown()
+ {
+ for (int vk = 1; vk < 256; ++vk)
+ {
+ if ((GetAsyncKeyState(vk) & 0x8000) != 0)
+ return true;
+ }
+ return false;
+ }
+
+ int ReadVirtualKey()
+ {
+ for (int vk = 1; vk < 256; ++vk)
+ {
+ if ((GetAsyncKeyState(vk) & 0x8000) != 0)
+ return vk;
+ }
+ return 0;
+ }
+
+ std::string VirtualKeyName(int vk)
+ {
+ switch (vk)
+ {
+ case VK_LBUTTON: return "LMB";
+ case VK_RBUTTON: return "RMB";
+ case VK_MBUTTON: return "MMB";
+ case VK_XBUTTON1: return "X1";
+ case VK_XBUTTON2: return "X2";
+ default: break;
+ }
+
+ char keyName[64]{};
+ const UINT scanCode = MapVirtualKeyA(static_cast(vk), MAPVK_VK_TO_VSC);
+ LONG keyData = static_cast(scanCode << 16);
+ switch (vk)
+ {
+ case VK_LEFT:
+ case VK_UP:
+ case VK_RIGHT:
+ case VK_DOWN:
+ case VK_PRIOR:
+ case VK_NEXT:
+ case VK_END:
+ case VK_HOME:
+ case VK_INSERT:
+ case VK_DELETE:
+ case VK_DIVIDE:
+ case VK_NUMLOCK:
+ keyData |= 1 << 24;
+ break;
+ default:
+ break;
+ }
+
+ if (GetKeyNameTextA(keyData, keyName, static_cast(sizeof(keyName))) > 0)
+ return keyName;
+
+ char fallback[16]{};
+ sprintf_s(fallback, "VK %d", vk);
+ return fallback;
+ }
+
+ bool DrawSliderTrack(const char* label, float& fraction, const char* valueText)
+ {
+ ImGui::PushID(label);
+ const float rowStart = ImGui::GetCursorPosX();
+ const float contentRight = rowStart + ImGui::GetContentRegionAvail().x;
+
+ ImGui::TextUnformatted(label);
+ const float labelEnd = ImGui::GetCursorPosX();
+ const float valueWidth = 54.f;
+ const float gap = 8.f;
+ ImGui::SameLine(labelEnd + 10.f);
+ const float barStartX = ImGui::GetCursorPosX();
+ const float barWidth = (std::max)(64.f, contentRight - barStartX - valueWidth - gap);
+ const ImVec2 barPos = ImGui::GetCursorScreenPos();
+ ImGui::InvisibleButton("##slider", ImVec2(barWidth, 24.f));
+
+ const bool hovered = ImGui::IsItemHovered();
+ const bool active = ImGui::IsItemActive();
+ bool changed = false;
+ if ((hovered || active) && ImGui::IsMouseDown(ImGuiMouseButton_Left))
+ {
+ const float next = (std::clamp)((ImGui::GetIO().MousePos.x - barPos.x) / barWidth, 0.f, 1.f);
+ changed = next != fraction;
+ fraction = next;
+ }
+
+ const ImVec2 trackMin(barPos.x, barPos.y + 9.f);
+ const ImVec2 trackMax(barPos.x + barWidth, barPos.y + 15.f);
+ const ImVec2 fillMax(barPos.x + barWidth * fraction, barPos.y + 15.f);
+ const ImU32 trackColor = UI::Theme::U32(hovered ? UI::Theme::SecondaryHover : UI::Theme::ToggleOff);
+ const ImU32 fillColor = UI::Theme::U32(hovered ? UI::Theme::AccentStrong : UI::Theme::Accent);
+ ImDrawList* drawList = ImGui::GetWindowDrawList();
+ drawList->AddRectFilled(trackMin, trackMax, trackColor, 3.f);
+ if (fraction > 0.f)
+ drawList->AddRectFilled(trackMin, fillMax, fillColor, 3.f);
+ drawList->AddCircleFilled(ImVec2(fillMax.x, barPos.y + 12.f), hovered || active ? 5.f : 4.f, fillColor, 16);
+
+ ImGui::SameLine(0.f, gap);
+ ImGui::TextColored(UI::Theme::TextPrimary, "%s", valueText);
+ ImGui::PopID();
+ return changed;
+ }
+}
+
+namespace UI
+{
+ bool BeginCard(const char* id, const char* title, const ImVec4& accent, float width)
+ {
+ ImGui::PushStyleColor(ImGuiCol_ChildBg, Theme::CardBackground);
+ ImGui::PushStyleColor(ImGuiCol_Border, Theme::Border);
+ ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, Theme::CardRounding);
+ ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.f);
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.f, 12.f));
+
+ const float cardWidth = width > 0.f ? width : ImGui::GetContentRegionAvail().x;
+ const bool visible = ImGui::BeginChild(
+ id,
+ ImVec2(cardWidth, 0.f),
+ ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysUseWindowPadding,
+ ImGuiWindowFlags_NoScrollbar);
+ if (visible)
+ SectionTitle(title, accent);
+ return visible;
+ }
+
+ void EndCard()
+ {
+ ImGui::EndChild();
+ ImGui::PopStyleVar(3);
+ ImGui::PopStyleColor(2);
+ }
+
+ bool Toggle(const char* label, bool* value)
+ {
+ if (!value)
+ return false;
+
+ ImGui::PushID(label);
+ const float rowStart = ImGui::GetCursorPosX();
+ const float contentRight = rowStart + ImGui::GetContentRegionAvail().x;
+ const float switchWidth = 44.f;
+ const float switchHeight = 24.f;
+
+ ImGui::TextColored(Theme::TextPrimary, "%s", label);
+ const float labelEnd = ImGui::GetCursorPosX();
+ ImGui::SameLine((std::max)(labelEnd + 8.f, contentRight - switchWidth));
+ const ImVec2 switchPos = ImGui::GetCursorScreenPos();
+ const bool clicked = ImGui::InvisibleButton("##toggle", ImVec2(switchWidth, switchHeight));
+ if (clicked)
+ *value = !*value;
+
+ const ImGuiID animationId = ImGui::GetID("##toggle");
+ float& transition = g_toggleAnimation[animationId];
+ const float target = *value ? 1.f : 0.f;
+ transition += (target - transition) * (std::min)(1.f, ImGui::GetIO().DeltaTime * 14.f);
+ if (std::abs(target - transition) < 0.001f)
+ transition = target;
+
+ const ImVec4 track = Mix(Theme::ToggleOff, Theme::Accent, transition);
+ const ImVec2 trackMax(switchPos.x + switchWidth, switchPos.y + switchHeight);
+ const float knobX = switchPos.x + 4.f + transition * (switchWidth - switchHeight);
+ ImDrawList* drawList = ImGui::GetWindowDrawList();
+ drawList->AddRectFilled(switchPos, trackMax, Theme::U32(track), switchHeight * 0.5f);
+ drawList->AddCircleFilled(
+ ImVec2(knobX + (switchHeight - 8.f) * 0.5f, switchPos.y + switchHeight * 0.5f),
+ (switchHeight - 8.f) * 0.5f,
+ Theme::U32(Theme::TextPrimary),
+ 16);
+
+ ImGui::PopID();
+ return clicked;
+ }
+
+ bool SliderFloat(const char* label, float* value, float vmin, float vmax, const char* format)
+ {
+ if (!value || vmax <= vmin)
+ return false;
+
+ char valueText[32]{};
+ sprintf_s(valueText, format, *value);
+ float fraction = (std::clamp)((*value - vmin) / (vmax - vmin), 0.f, 1.f);
+ const bool changed = DrawSliderTrack(label, fraction, valueText);
+ if (changed)
+ *value = vmin + fraction * (vmax - vmin);
+ return changed;
+ }
+
+ bool SliderInt(const char* label, int* value, int vmin, int vmax, const char* format)
+ {
+ if (!value || vmax <= vmin)
+ return false;
+
+ char valueText[32]{};
+ sprintf_s(valueText, format, *value);
+ float fraction = (std::clamp)(static_cast(*value - vmin) / static_cast(vmax - vmin), 0.f, 1.f);
+ const bool changed = DrawSliderTrack(label, fraction, valueText);
+ if (changed)
+ *value = vmin + static_cast(fraction * static_cast(vmax - vmin) + 0.5f);
+ return changed;
+ }
+
+ bool Combo(const char* label, int* value, const char* const items[], int count)
+ {
+ if (!value || !items || count <= 0)
+ return false;
+
+ ImGui::PushID(label);
+ const float rowStart = ImGui::GetCursorPosX();
+ const float contentRight = rowStart + ImGui::GetContentRegionAvail().x;
+ ImGui::TextColored(Theme::TextPrimary, "%s", label);
+ const float labelEnd = ImGui::GetCursorPosX();
+ const float comboWidth = (std::min)(160.f, (std::max)(92.f, contentRight - labelEnd - 10.f));
+ ImGui::SameLine(contentRight - comboWidth);
+ ImGui::SetNextItemWidth(comboWidth);
+ const bool changed = ImGui::Combo("##combo", value, items, count);
+ ImGui::PopID();
+ return changed;
+ }
+
+ bool ColorEdit(const char* label, ImVec4* color)
+ {
+ if (!color)
+ return false;
+
+ ImGui::PushID(label);
+ const float rowStart = ImGui::GetCursorPosX();
+ const float contentRight = rowStart + ImGui::GetContentRegionAvail().x;
+ const float swatchWidth = 28.f;
+ ImGui::TextColored(Theme::TextPrimary, "%s", label);
+ const float labelEnd = ImGui::GetCursorPosX();
+ ImGui::SameLine((std::max)(labelEnd + 8.f, contentRight - swatchWidth));
+ const ImVec2 pos = ImGui::GetCursorScreenPos();
+ const bool clicked = ImGui::InvisibleButton("##color", ImVec2(swatchWidth, 24.f));
+ if (clicked)
+ ImGui::OpenPopup("##color_popup");
+
+ ImDrawList* drawList = ImGui::GetWindowDrawList();
+ drawList->AddRectFilled(pos, ImVec2(pos.x + swatchWidth, pos.y + 24.f), ImGui::GetColorU32(*color), 5.f);
+ drawList->AddRect(pos, ImVec2(pos.x + swatchWidth, pos.y + 24.f), Theme::U32(Theme::Border), 5.f);
+
+ bool changed = false;
+ if (ImGui::BeginPopup("##color_popup"))
+ {
+ changed = ImGui::ColorEdit4("##picker", &color->x, ImGuiColorEditFlags_NoInputs);
+ ImGui::EndPopup();
+ }
+ ImGui::PopID();
+ return changed;
+ }
+
+ bool Hotkey(const char* label, int* value, int blockedKey)
+ {
+ if (!value)
+ return false;
+
+ ImGui::PushID(label);
+ const ImGuiID id = ImGui::GetID("##hotkey");
+ const float rowStart = ImGui::GetCursorPosX();
+ const float contentRight = rowStart + ImGui::GetContentRegionAvail().x;
+ const float controlWidth = 132.f;
+ const float controlHeight = 26.f;
+ ImGui::TextColored(Theme::TextPrimary, "%s", label);
+ const float labelEnd = ImGui::GetCursorPosX();
+ ImGui::SameLine((std::max)(labelEnd + 8.f, contentRight - controlWidth));
+ const ImVec2 pos = ImGui::GetCursorScreenPos();
+ const bool clicked = ImGui::InvisibleButton("##hotkey", ImVec2(controlWidth, controlHeight));
+ if (clicked)
+ {
+ g_captureId = id;
+ g_captureArmed = false;
+ }
+
+ if (g_captureId == id)
+ {
+ if (!g_captureArmed)
+ {
+ if (!AnyVirtualKeyDown())
+ g_captureArmed = true;
+ }
+ else
+ {
+ const int captured = ReadVirtualKey();
+ if (captured != 0)
+ {
+ if (captured == VK_ESCAPE)
+ {
+ g_captureId = 0;
+ }
+ else if (captured != blockedKey)
+ {
+ *value = captured;
+ g_captureId = 0;
+ }
+ }
+ }
+ }
+
+ const bool waiting = g_captureId == id;
+ const std::string text = waiting ? "Press a key..." : VirtualKeyName(*value);
+ const ImU32 fill = waiting ? Theme::U32(Theme::AccentSoft) : Theme::U32(Theme::SecondaryBackground);
+ const ImU32 border = waiting ? Theme::U32(Theme::Accent) : Theme::U32(Theme::Border);
+ ImDrawList* drawList = ImGui::GetWindowDrawList();
+ drawList->AddRectFilled(pos, ImVec2(pos.x + controlWidth, pos.y + controlHeight), fill, 6.f);
+ drawList->AddRect(pos, ImVec2(pos.x + controlWidth, pos.y + controlHeight), border, 6.f);
+ const ImVec2 textSize = ImGui::CalcTextSize(text.c_str());
+ drawList->AddText(
+ ImVec2(pos.x + (controlWidth - textSize.x) * 0.5f, pos.y + (controlHeight - textSize.y) * 0.5f),
+ Theme::U32(waiting ? Theme::AccentStrong : Theme::TextPrimary),
+ text.c_str());
+
+ ImGui::PopID();
+ return clicked;
+ }
+
+ bool Tab(const char* label, bool active)
+ {
+ ImGui::PushID(label);
+ const ImVec2 textSize = ImGui::CalcTextSize(label);
+ const ImVec2 pos = ImGui::GetCursorScreenPos();
+ const ImVec2 size(textSize.x + 28.f, 36.f);
+ const bool clicked = ImGui::InvisibleButton("##tab", size);
+ const bool hovered = ImGui::IsItemHovered();
+ ImDrawList* drawList = ImGui::GetWindowDrawList();
+ if (active || hovered)
+ {
+ const ImVec4 bg = active ? Theme::AccentSoft : Theme::SecondaryBackground;
+ drawList->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), Theme::U32(bg), 6.f);
+ }
+ if (active)
+ {
+ drawList->AddRectFilled(
+ ImVec2(pos.x + 8.f, pos.y + size.y - 3.f),
+ ImVec2(pos.x + size.x - 8.f, pos.y + size.y),
+ Theme::U32(Theme::Accent),
+ 1.5f);
+ }
+ const ImVec4 textColor = active ? Theme::TextPrimary : (hovered ? Theme::TextPrimary : Theme::TextSecondary);
+ drawList->AddText(
+ ImVec2(pos.x + (size.x - textSize.x) * 0.5f, pos.y + (size.y - textSize.y) * 0.5f),
+ Theme::U32(textColor),
+ label);
+ ImGui::PopID();
+ return clicked;
+ }
+
+ bool Button(const char* label, const ImVec2& size, bool danger, bool primary)
+ {
+ const ImVec4 normal = danger ? Theme::DangerSoft : (primary ? Theme::Accent : Theme::SecondaryBackground);
+ const ImVec4 hovered = danger ? Theme::Danger : (primary ? Theme::AccentStrong : Theme::SecondaryHover);
+ const ImVec4 active = danger ? Theme::Danger : Theme::AccentStrong;
+ ImGui::PushStyleColor(ImGuiCol_Button, normal);
+ ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hovered);
+ ImGui::PushStyleColor(ImGuiCol_ButtonActive, active);
+ ImGui::PushStyleColor(ImGuiCol_Text, primary ? Theme::WindowBackground : Theme::TextPrimary);
+ const bool clicked = ImGui::Button(label, size);
+ ImGui::PopStyleColor(4);
+ return clicked;
+ }
+
+ void SectionTitle(const char* title, const ImVec4& accent)
+ {
+ const ImVec2 pos = ImGui::GetCursorScreenPos();
+ ImGui::GetWindowDrawList()->AddRectFilled(
+ pos,
+ ImVec2(pos.x + 30.f, pos.y + 3.f),
+ Theme::U32(accent),
+ 1.5f);
+ ImGui::Dummy(ImVec2(0.f, 7.f));
+ ImGui::TextColored(Theme::TextPrimary, "%s", title);
+ ImGui::Dummy(ImVec2(0.f, 3.f));
+ }
+
+ void StatusBadge(const char* label, bool on)
+ {
+ const char* suffix = on ? "ON" : "OFF";
+ const ImVec2 labelSize = ImGui::CalcTextSize(label);
+ const ImVec2 suffixSize = ImGui::CalcTextSize(suffix);
+ const float width = labelSize.x + suffixSize.x + 31.f;
+ const float height = 22.f;
+ const ImVec2 pos = ImGui::GetCursorScreenPos();
+ ImGui::Dummy(ImVec2(width, height));
+
+ const ImVec4 background = on ? Theme::AccentSoft : Theme::SecondaryBackground;
+ const ImVec4 foreground = on ? Theme::AccentStrong : Theme::TextSecondary;
+ ImDrawList* drawList = ImGui::GetWindowDrawList();
+ drawList->AddRectFilled(pos, ImVec2(pos.x + width, pos.y + height), Theme::U32(background), 11.f);
+ drawList->AddCircleFilled(ImVec2(pos.x + 10.f, pos.y + height * 0.5f), 3.f, Theme::U32(foreground), 12);
+ drawList->AddText(ImVec2(pos.x + 17.f, pos.y + 3.f), Theme::U32(Theme::TextPrimary), label);
+ drawList->AddText(ImVec2(pos.x + 17.f + labelSize.x + 5.f, pos.y + 3.f), Theme::U32(foreground), suffix);
+ }
+
+ void Note(const char* text)
+ {
+ ImGui::PushTextWrapPos(0.f);
+ ImGui::TextColored(Theme::TextSecondary, "%s", text);
+ ImGui::PopTextWrapPos();
+ }
+
+ void Divider()
+ {
+ const ImVec2 pos = ImGui::GetCursorScreenPos();
+ const float width = ImGui::GetContentRegionAvail().x;
+ ImGui::GetWindowDrawList()->AddLine(
+ ImVec2(pos.x, pos.y + 4.f),
+ ImVec2(pos.x + width, pos.y + 4.f),
+ Theme::U32(Theme::Border),
+ 1.f);
+ ImGui::Dummy(ImVec2(width, 12.f));
+ }
+
+ float ColumnWidth(float gap)
+ {
+ return (std::max)(220.f, (ImGui::GetContentRegionAvail().x - gap) * 0.5f);
+ }
+
+ void NextColumn(float gap)
+ {
+ ImGui::SameLine(0.f, gap);
+ }
+}
diff --git a/CFramework/Cheat/UI/MenuWidgets.h b/CFramework/Cheat/UI/MenuWidgets.h
new file mode 100644
index 0000000..46cf011
--- /dev/null
+++ b/CFramework/Cheat/UI/MenuWidgets.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "../../Framework/ImGui/imgui.h"
+#include "MenuTheme.h"
+
+namespace UI
+{
+ bool BeginCard(const char* id, const char* title, const ImVec4& accent, float width = 0.f);
+ void EndCard();
+
+ bool Toggle(const char* label, bool* value);
+ bool SliderFloat(const char* label, float* value, float vmin, float vmax, const char* format = "%.0f");
+ bool SliderInt(const char* label, int* value, int vmin, int vmax, const char* format = "%d");
+ bool Combo(const char* label, int* value, const char* const items[], int count);
+ bool ColorEdit(const char* label, ImVec4* color);
+ bool Hotkey(const char* label, int* value, int blockedKey = 0);
+
+ bool Tab(const char* label, bool active);
+ bool Button(const char* label, const ImVec2& size, bool danger = false, bool primary = false);
+ void SectionTitle(const char* title, const ImVec4& accent);
+ void StatusBadge(const char* label, bool on);
+ void Note(const char* text);
+ void Divider();
+
+ float ColumnWidth(float gap = Theme::ColumnGap);
+ void NextColumn(float gap = Theme::ColumnGap);
+}