// 启用 ImVec2 的数学运算符支持 #define IMGUI_DEFINE_MATH_OPERATORS #include "imgui.h" #include "imgui_internal.h" #include "imgui_impl_opengl3.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dobby.h" #include #include #include #include #include #include #include // 图片加载 #define STB_IMAGE_IMPLEMENTATION #define STBI_NO_SIMD #define STBI_NO_THREAD_LOCALS #include "stb_image.h" #include "HeroImages.h" #include "EquipImages.h" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "JKInternal", __VA_ARGS__) // ================================================================= // 全局配置变量与偏移声明 // ================================================================= enum ConfigType { C_BOOL, C_INT, C_FLOAT, C_STRING }; struct ConfigEntry { const char* key; ConfigType type; void* ptr; }; int g_gl_width = 0, g_gl_height = 0; int g_cached_view_width = -1, g_cached_view_height = -1; // 用于坐标转换映射 const char* g_configPath = "/data/data/com.tencent.jkchess/jkchess_config.ini"; std::mutex g_configMutex; // 文件保存专用线程锁 // --- 换肤开关与配置 --- bool g_enable_skin = false; int g_selected_skin_idx = 0; bool g_header_skin = true; bool g_enable_map_skin = false; int g_selected_map_idx = 0; bool g_header_map_skin = true; // ---------------------- bool g_predict_enemy = false, g_predict_hex = false; bool g_esp_board = true, g_esp_level = false; bool g_esp_enemy_bench = false; // 已删除自身商店、自身备战、敌方商店 bool g_show_instant_quit_window = false, g_boardLocked = false; bool g_show_card_pool = false, g_card_warning = false; // 卖牌悬浮窗配置 bool g_show_sell_window = false; float g_sell_win_x = 400.0f, g_sell_win_y = 350.0f, g_sell_win_scale = 1.0f; bool g_show_auto_window = false, g_auto_refresh = false; bool g_auto_buy = false, g_auto_buy_chosen = false; bool g_show_hero_list_window = false; bool g_show_clear_bench_btn = true; // 自动穿装备/拆装备相关配置 bool g_show_equip_window = false; bool g_auto_equip_active = false; bool g_auto_unequip_active = false; float g_equip_win_x = 400.0f, g_equip_win_y = 250.0f, g_equip_win_scale = 1.0f; bool g_draw_hero_lines = true; float g_esp_line_offset_y = 50.0f; // [深度重构]:每个格子独立的偏移记录 (独立保存,跟随格子而不跟随英雄) std::unordered_map g_cell_equip_offset; std::string g_cell_equip_offset_str = ""; std::unordered_map g_cell_sell_offset; std::string g_cell_sell_offset_str = ""; float g_sell_drop_x = 500.0f; // 卖牌全局落点X float g_sell_drop_y = 900.0f; // 卖牌全局落点Y // 模拟滑动的装备栏物理UI坐标配置 float g_equip_ui_start_x = 100.0f; float g_equip_ui_start_y = 300.0f; float g_equip_ui_step_x = 100.0f; float g_equip_ui_step_y = 100.0f; bool g_show_equip_ui_debug = false; bool g_show_sell_ui_debug = false; int g_unequip_slot_idx = 0; // 自动滑动核心调节参数与视觉反馈结构 int g_swipe_step_delay_ms = 10; int g_swipe_steps = 15; bool g_show_swipe_visuals = true; int g_swipe_down_delay_ms = 30; int g_swipe_up_delay_ms = 20; int g_equip_extra_cooldown_ms = 100; struct SwipeVisData { ImVec2 start; ImVec2 end; float lifeTime; float maxLife; }; std::vector g_active_swipes; std::mutex g_swipe_mutex; std::unordered_set g_auto_buy_heroes; std::string g_auto_buy_heroes_str = ""; std::unordered_map> g_hero_equip_bindings; std::string g_hero_equip_bindings_str = ""; std::unordered_map g_hero_equip_priority; std::string g_hero_equip_priority_str = ""; std::unordered_set g_expanded_equip_heroes; std::map> g_local_pokedex; std::vector g_local_equip_pokedex; std::atomic g_pokedex_ready{false}; std::atomic g_equip_pokedex_ready{false}; float g_auto_buy_delay_ms = 0.0f; int g_auto_refresh_money_threshold = 2; float g_auto_win_x = 400.0f, g_auto_win_y = 150.0f, g_auto_win_scale = 1.0f; float g_instant_win_x = 100.0f, g_instant_win_y = 100.0f, g_instant_win_scale = 1.0f; float g_global_esp_alpha = 1.0f; float g_enemy_bench_X = 200.0f, g_enemy_bench_Y = 200.0f, g_enemy_bench_Scale = 1.0f; // --- 偏移量配置 --- int g_off_shop_base = 0x440, g_off_bench_base = 0x448, g_off_shop_p20 = 0x20; int g_off_shop_entries = 0x10, g_off_entries_count = 0x18, g_off_entries_head = 0x20; int g_off_entries_step = 0x20, g_off_entry_value = 0x10, g_off_value_slot = 0x1c; int g_off_value_p10 = 0x10, g_off_p10_heroId = 0x14; int g_off_pool_heroku = 0x18, g_off_pool_Aheroku = 0x18, g_off_pool_entries1 = 0x10; int g_off_pool_e1_count = 0x18, g_off_pool_e1_head = 0x20, g_off_pool_e1_step = 0x20, g_off_pool_e1_val = 0x10; int g_off_pool_entries2 = 0x10, g_off_pool_e2_count = 0x18, g_off_pool_e2_head = 0x20, g_off_pool_e2_step = 0x20, g_off_pool_e2_val = 0x10; int g_off_pool_v2_id = 0x10, g_off_pool_v2_rem = 0x1c, g_off_pool_v2_tot = 0x20; // 装备与3D寻址偏移配置 int g_off_equip_base = 0x2e8; int g_off_equip_arr = 0x10; int g_off_equip_count = 0x18; int g_off_equip_head = 0x20; int g_off_equip_step = 0x8; int g_off_equip_id = 0x14; int g_off_hero_dyn = 0x2b0; int g_off_hero_arr = 0x20; int g_off_hero_count = 0x18; int g_off_hero_head = 0x20; int g_off_hero_step = 0x8; int g_off_hero_id = 0x108; int g_off_hero_unit = 0x48; int g_off_unit_battle = 0x20; int g_off_battle_posx = 0x238; int g_card_pool_rows = 2, g_card_pool_cols = 5; float g_cardPoolX = 150.0f, g_cardPoolY = 150.0f; float g_cardPoolScaleX = 1.0f, g_cardPoolScaleY = 1.0f, g_cardPoolAlpha = 1.0f; bool g_show_pool_tiers[7] = {false, true, true, true, true, true, false}; bool g_pool_warning_enable = false; int g_pool_warning_count = 3; bool g_pool_warning_tiers[7] = {false, true, true, true, true, true, false}; bool g_warning_tiers[7] = {false, false, false, false, true, false, false}; int g_warning_threshold = 6; bool g_show_debug_window = false, g_header_pred = true, g_header_esp = true, g_menuCollapsed = false; float g_save_timer = 0.0f, g_scale = 1.0f, g_autoScale = 1.0f, g_current_rendered_size = 0.0f; float g_anim[30] = {0.0f}; float g_boardScale = 2.2f, g_boardManualScale = 1.0f, g_startX = 400.0f, g_startY = 400.0f; float g_menuX = 100.0f, g_menuY = 100.0f, g_menuW = 500.0f, g_menuH = 650.0f; float g_enemy_X = 100.0f, g_enemy_Y = 100.0f, g_enemy_Scale = 1.0f; float g_hex_X = 100.0f, g_hex_Y = 220.0f, g_hex_Scale = 1.0f; float g_level_X = 1500.0f, g_level_Y = 200.0f, g_level_Scale = 1.0f, g_level_Spacing = 45.0f; float g_warn_X = 1650.0f, g_warn_Y = 200.0f, g_warn_Scale = 1.0f, g_warn_Spacing = 45.0f; float g_predictNameX = 500.0f, g_predictNameY = 300.0f, g_predictNameScale = 1.5f; bool g_needUpdateFontSafe = false; ImFont* g_mainFont = nullptr, *g_hugeNumFont = nullptr; std::deque g_collected_skins; std::deque g_collected_maps; std::mutex g_collect_mutex; bool g_collect_skins = false; bool g_collect_maps = false; ConfigEntry g_configTable[] = { {"enableSkin", C_BOOL, &g_enable_skin}, {"skinIdx", C_INT, &g_selected_skin_idx}, {"enableMapSkin", C_BOOL, &g_enable_map_skin}, {"mapSkinIdx", C_INT, &g_selected_map_idx}, {"globalEspAlpha", C_FLOAT, &g_global_esp_alpha}, {"predictEnemy", C_BOOL, &g_predict_enemy}, {"predictHex", C_BOOL, &g_predict_hex}, {"predictNameX", C_FLOAT, &g_predictNameX}, {"predictNameY", C_FLOAT, &g_predictNameY}, {"predictNameScale", C_FLOAT, &g_predictNameScale}, {"collectSkins", C_BOOL, &g_collect_skins}, {"collectMaps", C_BOOL, &g_collect_maps}, {"espBoard", C_BOOL, &g_esp_board}, {"espLevel", C_BOOL, &g_esp_level}, {"espEnemyBench", C_BOOL, &g_esp_enemy_bench}, {"offShopBase", C_INT, &g_off_shop_base}, {"offBenchBase", C_INT, &g_off_bench_base}, {"offShopP20", C_INT, &g_off_shop_p20}, {"offShopEntries", C_INT, &g_off_shop_entries}, {"offEntriesCount", C_INT, &g_off_entries_count}, {"offEntriesHead", C_INT, &g_off_entries_head}, {"offEntriesStep", C_INT, &g_off_entries_step}, {"offEntryValue", C_INT, &g_off_entry_value}, {"offValueSlot", C_INT, &g_off_value_slot}, {"offValueP10", C_INT, &g_off_value_p10}, {"offP10HeroId", C_INT, &g_off_p10_heroId}, {"offPoolHeroku", C_INT, &g_off_pool_heroku}, {"offPoolAheroku", C_INT, &g_off_pool_Aheroku}, {"offPoolE1", C_INT, &g_off_pool_entries1}, {"offPoolE1Cnt", C_INT, &g_off_pool_e1_count}, {"offPoolE1Hd", C_INT, &g_off_pool_e1_head}, {"offPoolE1Stp", C_INT, &g_off_pool_e1_step}, {"offPoolE1Val", C_INT, &g_off_pool_e1_val}, {"offPoolE2", C_INT, &g_off_pool_entries2}, {"offPoolE2Cnt", C_INT, &g_off_pool_e2_count}, {"offPoolE2Hd", C_INT, &g_off_pool_e2_head}, {"offPoolE2Stp", C_INT, &g_off_pool_e2_step}, {"offPoolE2Val", C_INT, &g_off_pool_e2_val}, {"offPoolV2Id", C_INT, &g_off_pool_v2_id}, {"offPoolV2Rem", C_INT, &g_off_pool_v2_rem}, {"offPoolV2Tot", C_INT, &g_off_pool_v2_tot}, {"showCardPool", C_BOOL, &g_show_card_pool}, {"cardPoolRows", C_INT, &g_card_pool_rows}, {"cardPoolCols", C_INT, &g_card_pool_cols}, {"cardPoolScaleX", C_FLOAT, &g_cardPoolScaleX}, {"cardPoolScaleY", C_FLOAT, &g_cardPoolScaleY}, {"cardPoolAlpha", C_FLOAT, &g_cardPoolAlpha}, {"cardPoolX", C_FLOAT, &g_cardPoolX}, {"cardPoolY", C_FLOAT, &g_cardPoolY}, {"poolWarnEn", C_BOOL, &g_pool_warning_enable}, {"poolWarnCnt", C_INT, &g_pool_warning_count}, {"poolWT1", C_BOOL, &g_pool_warning_tiers[1]}, {"poolWT2", C_BOOL, &g_pool_warning_tiers[2]}, {"poolWT3", C_BOOL, &g_pool_warning_tiers[3]}, {"poolWT4", C_BOOL, &g_pool_warning_tiers[4]}, {"poolWT5", C_BOOL, &g_pool_warning_tiers[5]}, {"autoRefresh", C_BOOL, &g_auto_refresh}, {"autoBuy", C_BOOL, &g_auto_buy}, {"autoBuyDelay", C_FLOAT, &g_auto_buy_delay_ms}, {"autoRefMoneyThr", C_INT, &g_auto_refresh_money_threshold}, {"showAutoWin", C_BOOL, &g_show_auto_window}, {"autoBuyChosen", C_BOOL, &g_auto_buy_chosen}, {"autoWinX", C_FLOAT, &g_auto_win_x}, {"autoWinY", C_FLOAT, &g_auto_win_y}, {"autoWinScale", C_FLOAT, &g_auto_win_scale}, {"showDebug", C_BOOL, &g_show_debug_window}, {"cardWarning", C_BOOL, &g_card_warning}, {"warningThreshold", C_INT, &g_warning_threshold}, {"poolTier1", C_BOOL, &g_show_pool_tiers[1]}, {"poolTier2", C_BOOL, &g_show_pool_tiers[2]}, {"poolTier3", C_BOOL, &g_show_pool_tiers[3]}, {"poolTier4", C_BOOL, &g_show_pool_tiers[4]}, {"poolTier5", C_BOOL, &g_show_pool_tiers[5]}, {"warningTier1", C_BOOL, &g_warning_tiers[1]}, {"warningTier2", C_BOOL, &g_warning_tiers[2]}, {"warningTier3", C_BOOL, &g_warning_tiers[3]}, {"warningTier4", C_BOOL, &g_warning_tiers[4]}, {"warningTier5", C_BOOL, &g_warning_tiers[5]}, {"warningTier6", C_BOOL, &g_warning_tiers[6]}, {"instant", C_BOOL, &g_show_instant_quit_window}, {"boardLocked", C_BOOL, &g_boardLocked}, {"instantWinX", C_FLOAT, &g_instant_win_x}, {"instantWinY", C_FLOAT, &g_instant_win_y}, {"instantWinScale", C_FLOAT, &g_instant_win_scale}, {"showSellWin", C_BOOL, &g_show_sell_window}, {"sellWinX", C_FLOAT, &g_sell_win_x}, {"sellWinY", C_FLOAT, &g_sell_win_y}, {"sellWinScale", C_FLOAT, &g_sell_win_scale}, {"menuX", C_FLOAT, &g_menuX}, {"menuY", C_FLOAT, &g_menuY}, {"menuW", C_FLOAT, &g_menuW}, {"menuH", C_FLOAT, &g_menuH}, {"menuScale", C_FLOAT, &g_scale}, {"menuCollapsed", C_BOOL, &g_menuCollapsed}, {"startX", C_FLOAT, &g_startX}, {"startY", C_FLOAT, &g_startY}, {"manualScale", C_FLOAT, &g_boardManualScale}, {"enemyBenchX", C_FLOAT, &g_enemy_bench_X}, {"enemyBenchY", C_FLOAT, &g_enemy_bench_Y}, {"enemyBenchScale", C_FLOAT, &g_enemy_bench_Scale}, {"enemyX", C_FLOAT, &g_enemy_X}, {"enemyY", C_FLOAT, &g_enemy_Y}, {"enemyScale", C_FLOAT, &g_enemy_Scale}, {"hexX", C_FLOAT, &g_hex_X}, {"hexY", C_FLOAT, &g_hex_Y}, {"hexScale", C_FLOAT, &g_hex_Scale}, {"levelX", C_FLOAT, &g_level_X}, {"levelY", C_FLOAT, &g_level_Y}, {"levelScale", C_FLOAT, &g_level_Scale}, {"levelSpacing", C_FLOAT, &g_level_Spacing}, {"warnX", C_FLOAT, &g_warn_X}, {"warnY", C_FLOAT, &g_warn_Y}, {"warnScale", C_FLOAT, &g_warn_Scale}, {"warnSpacing", C_FLOAT, &g_warn_Spacing}, {"headerPred", C_BOOL, &g_header_pred}, {"headerEsp", C_BOOL, &g_header_esp}, {"showHeroListWin", C_BOOL, &g_show_hero_list_window}, {"autoBuyHeroes", C_STRING, &g_auto_buy_heroes_str}, {"heroEquipBindings", C_STRING, &g_hero_equip_bindings_str}, {"heroEquipPriority", C_STRING, &g_hero_equip_priority_str}, {"showEquipWin", C_BOOL, &g_show_equip_window}, {"equipWinX", C_FLOAT, &g_equip_win_x}, {"equipWinY", C_FLOAT, &g_equip_win_y}, {"equipWinScale", C_FLOAT, &g_equip_win_scale}, {"drawHeroLines", C_BOOL, &g_draw_hero_lines}, {"offEquipBase", C_INT, &g_off_equip_base}, {"offEquipArr", C_INT, &g_off_equip_arr}, {"offEquipCount", C_INT, &g_off_equip_count}, {"offEquipHead", C_INT, &g_off_equip_head}, {"offEquipStep", C_INT, &g_off_equip_step}, {"offEquipId", C_INT, &g_off_equip_id}, {"offHeroDyn", C_INT, &g_off_hero_dyn}, {"offHeroArr", C_INT, &g_off_hero_arr}, {"offHeroCount", C_INT, &g_off_hero_count}, {"offHeroHead", C_INT, &g_off_hero_head}, {"offHeroStep", C_INT, &g_off_hero_step}, {"offHeroId", C_INT, &g_off_hero_id}, {"offHeroUnit", C_INT, &g_off_hero_unit}, {"offUnitBattle", C_INT, &g_off_unit_battle}, {"offBattlePosx", C_INT, &g_off_battle_posx}, {"espLineOffsetY", C_FLOAT, &g_esp_line_offset_y}, // 格子独立坐标配置保存 {"cellEquipOffsets", C_STRING, &g_cell_equip_offset_str}, {"cellSellOffsets", C_STRING, &g_cell_sell_offset_str}, {"sellDropX", C_FLOAT, &g_sell_drop_x}, {"sellDropY", C_FLOAT, &g_sell_drop_y}, {"showClearBenchBtn", C_BOOL, &g_show_clear_bench_btn}, {"equipUiStartX", C_FLOAT, &g_equip_ui_start_x}, {"equipUiStartY", C_FLOAT, &g_equip_ui_start_y}, {"equipUiStepX", C_FLOAT, &g_equip_ui_step_x}, {"equipUiStepY", C_FLOAT, &g_equip_ui_step_y}, {"swipeDelay", C_INT, &g_swipe_step_delay_ms}, {"swipeSteps", C_INT, &g_swipe_steps}, {"showSwipeVis", C_BOOL, &g_show_swipe_visuals}, {"swipeDownDelay", C_INT, &g_swipe_down_delay_ms}, {"swipeUpDelay", C_INT, &g_swipe_up_delay_ms}, {"equipExtraCD", C_INT, &g_equip_extra_cooldown_ms}, {"unequipSlotIdx", C_INT, &g_unequip_slot_idx} }; typedef void* (*il2cpp_domain_get_t)(); typedef void* (*il2cpp_thread_attach_t)(void* domain); typedef void (*il2cpp_thread_detach_t)(void* thread); typedef void* (*il2cpp_string_new_t)(const char* str); il2cpp_domain_get_t g_il2cpp_domain_get = nullptr; il2cpp_thread_attach_t g_il2cpp_thread_attach = nullptr; il2cpp_thread_detach_t g_il2cpp_thread_detach = nullptr; il2cpp_string_new_t g_il2cpp_string_new = nullptr; struct SkinInfo { const char* zh_name; const char* bundle; const char* asset; }; SkinInfo g_skinList[] = { {"锐雯(至臻)", "art_tft_raw/little_legend_res/model/t_rivenswordgold/t_rivenswordgold/low", "t_rivenswordgold_1_show"}, {"厄斐琉斯(至臻)", "art_tft_raw/little_legend_res/model/t_apheliosfashiongold/t_apheliosfashiongold/low", "t_apheliosfashiongold_1_show"}, {"佛耶戈(时装)", "art_tft_raw/little_legend_res/model/t_viegofashion/t_viegofashion/low", "t_viegofashion_1_show"}, {"泽丽(至臻)", "art_tft_raw/little_legend_res/model/t_zerishadowgold/t_zerishadowgold/low", "t_zerishadowgold_1_show"}, {"阿狸(福祝)", "art_tft_raw/little_legend_res/model/t_ahriblessed/t_ahriblessed/low", "t_ahriblessed_1_show"}, {"拉克丝(至臻)", "art_tft_raw/little_legend_res/model/t_luxredgold/t_luxredgold/low", "t_luxredgold_1_show"}, {"莫甘娜(至臻)", "art_tft_raw/little_legend_res/model/t_morganafashiongold/t_morganafashiongold/low", "t_morganafashiongold_1_show"}, {"塔姆(至臻)", "art_tft_raw/little_legend_res/model/t_tahmfortune/t_tahmfortune_gold/low_low", "t_tahmfortune_gold_1_show"}, {"辛德拉(美食)", "art_tft_raw/little_legend_res/model/t_syndrafood/t_syndrafood/low", "t_syndrafood_1_show"}, {"悠娜拉(美食)", "art_tft_raw/little_legend_res/model/t_yunarafood/t_yunarafood/low", "t_yunarafood_1_show"}, {"女枪(美食)", "art_tft_raw/little_legend_res/model/t_missfood/t_missfood/low", "t_missfood_1_show"}, {"俄洛伊(美食)", "art_tft_raw/little_legend_res/model/t_illaoifood/t_illaoifood/low", "t_illaoifood_1_show"}, {"扎克(天煞)", "art_tft_raw/little_legend_res/model/t_zaahenshadow/t_zaahenshadow/low", "t_zaahenshadow_1_show"}, {"莉莉娅(天煞)", "art_tft_raw/little_legend_res/model/t_lilliashadow/t_lilliashadow/low", "t_lilliashadow_1_show"}, {"悟空(天煞)", "art_tft_raw/little_legend_res/model/t_wukongshadow/t_wukongshadow/low", "t_wukongshadow_1_show"}, {"猪妹(福祝)", "art_tft_raw/little_legend_res/model/t_sejuaniblessed/t_sejuaniblessed/low", "t_sejuaniblessed_1_show"}, {"娜美(福祝)", "art_tft_raw/little_legend_res/model/t_namiblessed/t_namiblessed/low", "t_namiblessed_1_show"}, {"瑟提(福祝)", "art_tft_raw/little_legend_res/model/t_settblessed/t_settblessed/low", "t_settblessed_1_show"}, {"悠米(福祝)", "art_tft_raw/little_legend_res/model/t_yuumiblessed/t_yuumiblessed/low", "t_yuumiblessed_1_show"}, {"妖姬(红)", "art_tft_raw/little_legend_res/model/t_leblancred/t_leblancred/low", "t_leblancred_1_show"}, {"风女(红)", "art_tft_raw/little_legend_res/model/t_jannared/t_jannared/low", "t_jannared_1_show"}, {"EZ(红)", "art_tft_raw/little_legend_res/model/t_ezrealred/t_ezrealred/low", "t_ezrealred_1_show"}, {"莫甘娜(雪月)", "art_tft_raw/little_legend_res/model/t_morganasnowmoon/t_morganasnowmoon/low", "t_morganasnowmoon_1_show"}, {"贪吃蛇", "art_tft_raw/little_legend_res/model/tweisnake/tweisnake/low_low", "tweisnake_1_show"}, {"格温(时装)", "art_tft_raw/little_legend_res/model/t_gwenfashion/t_gwenfashion/low", "t_gwenfashion_1_show"}, {"寡妇(时装)", "art_tft_raw/little_legend_res/model/t_evelynnfashion/t_evelynnfashion/low", "t_evelynnfashion_1_show"}, {"吸血鬼(女仆)", "art_tft_raw/little_legend_res/model/t_vladimirmaid/t_vladimirmaid/low", "t_vladimirmaid_1_show"}, {"EZ(水墨)", "art_tft_raw/little_legend_res/model/t_ezrealink/t_ezrealink/low", "t_ezrealink_1_show"}, {"卡莎(美食)", "art_tft_raw/little_legend_res/model/t_kaisafood/t_kaisafood/low_low", "t_kaisafood_1_show"}, {"星妈(美食)", "art_tft_raw/little_legend_res/model/t_sorakafood/t_sorakafood/low_low", "t_sorakafood_1_show"}, {"泽丽(美食)", "art_tft_raw/little_legend_res/model/t_zerifood/t_zerifood/low_low", "t_zerifood_1_show"}, {"杰斯(双城2)", "art_tft_raw/little_legend_res/model/t_jaycearcanetwo/t_jaycearcanetwo/low_low", "t_jaycearcanetwo_1_show"}, {"EZ(青花瓷)", "art_tft_raw/little_legend_res/model/t_ezrealporcelain/t_ezrealporcelain/low", "t_ezrealporcelain_1_show"}, {"萨勒芬妮(凤凰)", "art_tft_raw/little_legend_res/model/t_seraphinephoenix/t_seraphinephoenix/low_low", "t_seraphinephoenix_1_show"}, {"猪妹(魄罗)", "art_tft_raw/little_legend_res/model/t_sejuanipororider/t_sejuanipororider/low_low", "t_sejuanipororider_1_show"}, {"悠米(女仆)", "art_tft_raw/little_legend_res/model/t_yuumimaid/t_yuumimaid/low", "t_yuumimaid_1_show"}, {"莉莉娅(山海)", "art_tft_raw/little_legend_res/model/t_lilliashanhai/t_lilliashanhai/low_low", "t_lilliashanhai_1_show"}, {"萨勒芬妮(KDA)", "art_tft_raw/little_legend_res/model/t_seraphinekda/t_seraphinekda/low_low", "t_seraphinekda_1_show"}, {"提莫(灵魂莲华)", "art_tft_raw/little_legend_res/model/t_teemospirit/t_teemospirit/low_low", "t_teemospirit_1_show"}, {"琴女(电玩)", "art_tft_raw/little_legend_res/model/t_sonaarcade/t_sonaarcade/low_low", "t_sonaarcade_1_show"}, {"亚索(神龙尊者)", "art_tft_raw/little_legend_res/model/t_yasuotruedragon/t_yasuotruedragon/low_low", "t_yasuotruedragon_1_show"}, {"刀妹(福星)", "art_tft_raw/little_legend_res/model/t_ireliafuxc/t_ireliafuxc/low", "t_ireliafuxc_1_show"}, {"希维尔", "art_tft_raw/little_legend_res/model/t_sivir/t_sivir/low", "t_sivir_1_show"}, {"佐伊", "art_tft_raw/little_legend_res/model/t_zoetft/t_zoetft/low", "t_zoetft_1_show"}, {"丽桑卓(基础)", "art_tft_raw/little_legend_res/model/t_lissandra/t_lissandra/low", "t_lissandra_1_show"}, {"亚索(原皮棕)", "art_tft_raw/little_legend_res/model/t_yasuobase/t_yasuobase_brown/low", "t_yasuobase_brown_1_show"}, {"蔚(双城)", "art_tft_raw/little_legend_res/model/t_viarcane/t_viarcane/low", "t_viarcane_1_show"}, {"金克丝(双城)", "art_tft_raw/little_legend_res/model/t_jinxarcane/t_jinxarcane/low", "t_jinxarcane_1_show"}, {"企鹅骑士(春节)", "art_tft_raw/little_legend_res/model/t_qieqishispring/t_qieqishispringorse/low", "t_qieqishispringorse_1_show"}, {"深渊(战斗)", "art_tft_raw/little_legend_res/model/t_abyssia/t_abyssia_battle/low", "t_abyssia_battle_1_show"}, {"天马(白星)", "art_tft_raw/little_legend_res/model/t_pegasusbase/t_pegasusbase_whitestar/low", "t_pegasusbase_whitestar_3_show"}, {"胖胖龙(美味)", "art_tft_raw/little_legend_res/model/t_dowsiedelicious/t_dowsiedelicious/low", "t_dowsiedelicious_3_show"}, {"刀汪(美味)", "art_tft_raw/little_legend_res/model/t_knifepupdelicious/t_knifepupdelicious/low", "t_knifepupdelicious_3_show"}, {"面团猫(美味)", "art_tft_raw/little_legend_res/model/t_doughcatdelicious/t_doughcatdelicious/low", "t_doughcatdelicious_1_show"}, {"胖胖龙(果冻)", "art_tft_raw/little_legend_res/model/t_dowsie/t_dowsie_jelly/low", "t_dowsie_jelly_3_show"}, {"团子(美味)", "art_tft_raw/little_legend_res/model/t_dangodelicious/t_dangodelicious/low", "t_dangodelicious_3_show"}, {"黏黏(美味)", "art_tft_raw/little_legend_res/model/t_gloopdelicious/t_gloopdelicious/low", "t_gloopdelicious_3_show"}, {"幽影(公会)", "art_tft_raw/little_legend_res/model/t_umbra/t_umbra_guild/low", "t_umbra_guild_3_show"}, {"塔姆鼠(森林)", "art_tft_raw/little_legend_res/model/tamster/tamster_forest/low", "tamster_forest_3_show"}, {"熬兴", "art_tft_raw/little_legend_res/model/t_aoshin/t_aoshin/low", "t_aoshin_3_show"}, {"河灵(波霸)", "art_tft_raw/little_legend_res/model/teliujingling/teliujingling_boba/low", "teliujingling_boba_1_show"}, {"河灵(维京)", "art_tft_raw/little_legend_res/model/teliujingling/teliujingling_viking/low", "teliujingling_viking_1_show"}, {"宠物石", "art_tft_raw/little_legend_res/model/t_petchoncc/t_petchoncc_stone/low", "t_petchoncc_stone_1_show"}, {"鼹鼠", "art_tft_raw/little_legend_res/model/t_mole/t_mole/low", "t_mole_1_show"}, {"河灵(斗篷)", "art_tft_raw/little_legend_res/model/teliujinglingdoupeng/teliujinglingdoupeng/low", "teliujinglingdoupeng_1_show"}, {"河灵(基础)", "art_tft_raw/little_legend_res/model/teliujingling/teliujingling/low", "teliujingling_1_show"}, {"永恩(SKT)", "art_tft_raw/little_legend_res/model/t_yoneskt/t_yoneskt/low", "t_yoneskt_1_show"}, {"刀妹(至臻)", "art_tft_raw/little_legend_res/model/t_ireliasword/t_ireliasword_gold/low_low", "t_ireliasword_gold_1_show"}, {"团子(饼干)", "art_tft_raw/little_legend_res/model/t_dangocookie/t_dangocookie/low", "t_dangocookie_1_show"}, {"亚索(原皮白)", "art_tft_raw/little_legend_res/model/t_yasuobase/t_yasuobase_white/low", "t_yasuobase_white_1_show"}, {"亚索(原皮黄)", "art_tft_raw/little_legend_res/model/t_yasuobase/t_yasuobase_yellow/low", "t_yasuobase_yellow_1_show"} }; struct MapInfo { const char* zh_name; const char* bundle; const char* asset; }; MapInfo g_mapList[] = { {"兰亭集序(神话)", "art_tft_raw/scenes/prefab/s11_tft_lantingpoems", "s11_tft_lantingpoems"}, {"选秀中心(测试用)", "art_tft_raw/scenes/prefab/s4_5_tft_battlefield_center", "s4_5_tft_battlefield_center"}, {"S16 皮影戏", "art_tft_raw/scenes/prefab/s16_tft_shadowplay", "s16_tft_shadowplay"}, {"S16 发财", "art_tft_raw/scenes/prefab/s16_tft_facai", "s16_tft_facai"}, {"S16 龙马", "art_tft_raw/scenes/prefab/s16_tft_longma", "s16_tft_longma"}, {"S16 大观园", "art_tft_raw/scenes/prefab/s16_tft_dgy", "s16_tft_dgy"}, {"S6 虎年新春", "art_tft_raw/scenes/prefab/s6_tft_newyear", "s6_tft_newyear"}, {"S16 时装", "art_tft_raw/scenes/prefab/s16_tft_fashion", "s16_tft_fashion"}, {"S13 饺子馆", "art_tft_raw/scenes/prefab/s13_tft_dumpling", "s13_tft_dumpling"}, {"S6 艾克实验室", "art_tft_raw/scenes/prefab/s6_tft_ekkolab", "s6_tft_ekkolab"}, {"S8 街头", "art_tft_raw/scenes/prefab/s8_tft_street", "s8_tft_street"}, {"S8 赛博朋克", "art_tft_raw/scenes/prefab/s8_tft_cyberpunk", "s8_tft_cyberpunk"}, {"S7 星之守护者", "art_tft_raw/scenes/prefab/s7_tft_xingshou", "s7_tft_xingshou"}, {"S17 富贵马", "art_tft_raw/scenes/prefab/s17_tft_richhorse", "s17_tft_richhorse"}, {"S16 2026迎新", "art_tft_raw/scenes/prefab/s16_tft_2026yx", "s16_tft_2026yx"}, {"S16 掌剑", "art_tft_raw/scenes/prefab/s16_tft_zhangjian", "s16_tft_zhangjian"}, {"S13 广州", "art_tft_raw/scenes/prefab/s13_tft_guangzhou", "s13_tft_guangzhou"}, {"S9 德玛西亚", "art_tft_raw/scenes/prefab/s9_tft_demacia", "s9_tft_demacia"}, {"S9 艾欧尼亚亚索", "art_tft_raw/scenes/prefab/s9_tft_ioniayasuo", "s9_tft_ioniayasuo"}, {"S4 凌云", "art_tft_raw/scenes/prefab/s4_tft_lingyun_001", "s4_tft_lingyun_001"} }; inline bool IsValidPtr(uintptr_t addr) { return addr > 0x10000000 && addr < 0x00007FFFFFFFFFFF && (addr % 4 == 0); } thread_local int g_safe_pipe[2] = {-1, -1}; bool SafeReadMemory(uintptr_t addr, void* buffer, size_t size) { if (addr < 0x10000000 || addr > 0x00007FFFFFFFFFFF) return false; if (g_safe_pipe[0] == -1) { if (pipe2(g_safe_pipe, O_NONBLOCK) != 0) { if (IsValidPtr(addr)) { memcpy(buffer, (void*)addr, size); return true; } return false; } } ssize_t res = write(g_safe_pipe[1], (void*)addr, size); if (res == (ssize_t)size) { read(g_safe_pipe[0], buffer, size); return true; } else if (res > 0) { char temp[256]; read(g_safe_pipe[0], temp, res); } return false; } std::deque g_appLogs; std::mutex g_logMutex; void AddLog(const char* fmt, ...) { char buf[1024]; va_list args; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); std::string logStr(buf); std::lock_guard lock(g_logMutex); static std::unordered_set seenLogs; if (seenLogs.find(logStr) != seenLogs.end()) return; seenLogs.insert(logStr); time_t now = time(nullptr); struct tm tstruct = *localtime(&now); char timebuf[80]; strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tstruct); g_appLogs.push_back(std::string("[") + timebuf + "] " + logStr); if (g_appLogs.size() > 100) g_appLogs.pop_front(); } #define SAFE_READ(type, base, offset, defaultVal) \ ([&]() -> type { \ type val = defaultVal; \ uintptr_t targetAddr = (uintptr_t)(base) + (offset); \ if (!SafeReadMemory(targetAddr, &val, sizeof(type))) val = defaultVal; \ return val; \ }()) #define SAFE_READ_PTR(base, offset) SAFE_READ(uintptr_t, base, offset, 0) std::string Utf16ToUtf8(const char16_t* u16str, size_t len) { std::string u8str; for (size_t i = 0; i < len; ++i) { char16_t wc = u16str[i]; if (wc < 0x80) u8str += (char)wc; else if (wc < 0x800) { u8str += (char)(0xC0 | (wc >> 6)); u8str += (char)(0x80 | (wc & 0x3F)); } else { u8str += (char)(0xE0 | (wc >> 12)); u8str += (char)(0x80 | ((wc >> 6) & 0x3F)); u8str += (char)(0x80 | (wc & 0x3F)); } } return u8str; } std::string ReadIl2cppStr(void* strObj) { if (!strObj || !IsValidPtr((uintptr_t)strObj)) return ""; int len = SAFE_READ(int, strObj, 0x10, 0); if (len > 0 && len < 500) { char16_t buffer[500] = {0}; if (SafeReadMemory((uintptr_t)strObj + 0x14, buffer, len * sizeof(char16_t))) { return Utf16ToUtf8(buffer, len); } } return ""; } typedef void* (*func_LoadAsset_t)(void* x0, void* x1, void* x2, void* x3, int x4); func_LoadAsset_t orig_LoadAsset_718 = nullptr; void* hook_LoadAsset_718(void* x0, void* x1, void* x2, void* x3, int x4) { if (x1 && x2) { std::string bundleStr = ReadIl2cppStr(x1); std::string assetStr = ReadIl2cppStr(x2); if (g_collect_skins && !bundleStr.empty()) { std::lock_guard lock(g_collect_mutex); std::string logStr = bundleStr + " | " + assetStr; if (std::find(g_collected_skins.begin(), g_collected_skins.end(), logStr) == g_collected_skins.end()) { g_collected_skins.push_back(logStr); } } if (g_enable_skin && g_il2cpp_string_new) { void* new_bundle = g_il2cpp_string_new(g_skinList[g_selected_skin_idx].bundle); void* new_asset = g_il2cpp_string_new(g_skinList[g_selected_skin_idx].asset); if (new_bundle && new_asset) { return orig_LoadAsset_718(x0, new_bundle, new_asset, x3, x4); } } } return orig_LoadAsset_718(x0, x1, x2, x3, x4); } typedef void* (*func_LoadMapImpl_t)(void* x0, void* bundlePath, void* assetName, void* container, uint64_t isBackground, uint64_t isMineMap); func_LoadMapImpl_t orig_LoadMapImpl = nullptr; void* hook_LoadMapImpl(void* x0, void* bundlePath, void* assetName, void* container, uint64_t isBackground, uint64_t isMineMap) { if (bundlePath && assetName) { std::string bundleStr = ReadIl2cppStr(bundlePath); std::string assetStr = ReadIl2cppStr(assetName); if (g_collect_maps && !bundleStr.empty()) { std::lock_guard lock(g_collect_mutex); std::string logStr = bundleStr + " | " + assetStr; if (std::find(g_collected_maps.begin(), g_collected_maps.end(), logStr) == g_collected_maps.end()) { g_collected_maps.push_back(logStr); } } if (g_enable_map_skin && g_il2cpp_string_new) { if (isMineMap != 0) { void* new_bundle = g_il2cpp_string_new(g_mapList[g_selected_map_idx].bundle); void* new_asset = g_il2cpp_string_new(g_mapList[g_selected_map_idx].asset); if (new_bundle && new_asset) { return orig_LoadMapImpl(x0, new_bundle, new_asset, container, isBackground, isMineMap); } } } } return orig_LoadMapImpl(x0, bundlePath, assetName, container, isBackground, isMineMap); } template class RingBuffer { T buffer[Size]; std::atomic head{0}; std::atomic tail{0}; public: bool push(const T& item) { size_t current_tail = tail.load(std::memory_order_relaxed); size_t next_tail = (current_tail + 1) % Size; if (next_tail != head.load(std::memory_order_acquire)) { buffer[current_tail] = item; tail.store(next_tail, std::memory_order_release); return true; } return false; } bool pop(T& item) { size_t current_head = head.load(std::memory_order_relaxed); if (current_head == tail.load(std::memory_order_acquire)) return false; item = buffer[current_head]; head.store((current_head + 1) % Size, std::memory_order_release); return true; } void clear() { head.store(0, std::memory_order_relaxed); tail.store(0, std::memory_order_relaxed); } }; struct PlayerRowInfo { uintptr_t obj; int rowIdx; }; struct PlayerStatInfo { uintptr_t obj; int val; }; struct MainThreadTasks { std::atomic trigger_surrender{false}; std::atomic trigger_game_end{false}; std::atomic pending_hex_cfg_obj{0}; uintptr_t processed_hex_cfg_obj = 0; std::atomic pending_turn_start_obj{0}; RingBuffer update_turn_start_queue; RingBuffer player_row_queue; RingBuffer money_queue; RingBuffer level_queue; } g_Tasks; uintptr_t g_shop_slot_instances[5] = {0}; int g_shop_slot_index = 0; typedef void (*func_reqbuyhero_t)(void* x0, void* x1, void* x2, void* x3); func_reqbuyhero_t g_reqbuyhero = nullptr; uintptr_t g_refresh_instance = 0; typedef void (*func_req_refresh_t)(void* x0); func_req_refresh_t g_req_refresh = nullptr; struct Vector3 { float x, y, z; }; // 增加 cellIndex 来记录是哪个格子(0-27场上, 28-36备战席) struct LiveHeroData { int heroId; Vector3 worldPos; Vector3 screenPos; int equipCount; int cellIndex; }; struct DebugHeroTrace { int index; uintptr_t heroObj; int heroId; uintptr_t unitData; uintptr_t battleUnit; Vector3 worldPos; Vector3 screenPos; }; struct PlayerInfo { uintptr_t objAddr = 0, midAddr = 0, strPtr = 0, dynamicAddr = 0, logicObjAddr = 0, heroArrayAddr = 0, csoGameAddr = 0; int playerId = 0, rowIdx = 0, nameLen = 0, money = 0, level = 0; int winStreak = 0, loseStreak = 0; std::string name = ""; bool isAi = false; }; struct EnemyHeroPos { int heroId; int x; int y; int equipCount; }; struct HeroSlotInfo { int heroId; int baseHeroId; int slotIndex; int cost; int starLevel; }; struct CardInfo { int heroId; int remaining; int total; int cost; }; struct PlayerWarningInfo { int rowIdx; std::vector> heroCounts; }; struct GameSnapshot { std::unordered_map players; std::unordered_map objToPlayerId; std::vector currentEnemyHeroes; std::vector myShopHeroes, myBenchHeroes, enemyShopHeroes, enemyBenchHeroes; std::unordered_map cardPoolData; std::vector playerWarnings; uintptr_t chessBattleModel = 0; uintptr_t csoGame = 0; int season = 0; int myPlayerId = -1; int nextEnemyPlayerId = -1; int cachedHexes[4] = {0, 0, 0, 0}; int validHexCount = 0; bool hex_fetched = false; uintptr_t mainCamera = 0; std::vector myItems; std::vector myLiveHeroes; std::vector myBenchLiveHeroes; std::vector debugHeroTraces; }; GameSnapshot g_BackendData; GameSnapshot g_Snapshots[2]; std::atomic g_WriteIdx{0}; std::atomic g_ReadIdx{0}; std::atomic g_is_in_match{false}; void PushSnapshot() { int write_idx = g_WriteIdx.load(std::memory_order_relaxed); int next_write = 1 - write_idx; g_Snapshots[write_idx] = g_BackendData; g_ReadIdx.store(write_idx, std::memory_order_release); g_WriteIdx.store(next_write, std::memory_order_relaxed); } const GameSnapshot* GetSnapshot() { int read_idx = g_ReadIdx.load(std::memory_order_acquire); return &g_Snapshots[read_idx]; } uintptr_t g_il2cppBase = 0, g_debug_mgrObj = 0; void* g_turnStartThisObj = nullptr; void* g_hexCfgObj = nullptr; int g_debug_TurnStartCalled = 0, g_debug_hook_3Cd6058_count = 0, g_debug_hook_set_Money_count = 0, g_debug_hook_set_Level_count = 0; int g_debug_UpdateTurnStart_count = 0, g_debug_set_IsGameEnd_count = 0, g_debug_SendWillRenderCanvases_count = 0; int g_debug_collected_players_count = 0, g_debug_hook_InitTurnDropCfg_count = 0, g_debug_bg_thread_count = 0; int g_debug_hook_6A67e48_count = 0; std::atomic g_debug_pool_base{0}; // 新增: 记录池基址以供显示 std::atomic g_collected_all_players{false}; std::atomic g_matchFetchRequested{false}, g_matchFetchReady{false}; std::chrono::steady_clock::time_point g_matchFetchTime; std::atomic g_pool_entries1{0}; // ================================================================= // 极速数学解析引擎 // ================================================================= inline void FastParseHeroId(int rawHeroId, int& baseHeroId, int& cost, int& starLevel) { if (rawHeroId < 10) { baseHeroId = rawHeroId; cost = 1; starLevel = 1; return; } if (rawHeroId >= 10000) { starLevel = rawHeroId / 10000; cost = (rawHeroId / 1000) % 10; baseHeroId = rawHeroId - (starLevel * 10000) + 10000; } else if (rawHeroId >= 1000) { starLevel = rawHeroId / 1000; cost = (rawHeroId / 100) % 10; baseHeroId = rawHeroId - (starLevel * 1000) + 1000; } else if (rawHeroId >= 100) { starLevel = rawHeroId / 100; cost = (rawHeroId / 10) % 10; baseHeroId = rawHeroId - (starLevel * 100) + 100; } else { starLevel = rawHeroId / 10; cost = rawHeroId % 10; baseHeroId = rawHeroId - (starLevel * 10) + 10; } } inline int GetCostFromHeroId(int heroId) { if (heroId < 10) return 1; int cost = 1; if (heroId >= 10000) cost = (heroId / 1000) % 10; else if (heroId >= 1000) cost = (heroId / 100) % 10; else if (heroId >= 100) cost = (heroId / 10) % 10; else cost = heroId % 10; return (cost >= 1 && cost <= 5) ? cost : 1; } void BuildLocalPokedex() { if (g_pokedex_ready.load()) return; std::thread([]() { std::map> temp_pokedex; for (int i = 1; i <= 60000; i++) { int len = 0; const unsigned char* ptr = GetHeroImageBytes(i, &len); if (ptr != nullptr && len > 0) { int cost = GetCostFromHeroId(i); if (cost >= 1 && cost <= 5) { temp_pokedex[cost].push_back(i); } } } g_local_pokedex = std::move(temp_pokedex); std::vector temp_equip_pokedex; for (int i = 1; i <= 20000; i++) { int len = 0; const unsigned char* ptr = GetEquipImageBytes(i, &len); if (ptr != nullptr && len > 0) { temp_equip_pokedex.push_back(i); } } g_local_equip_pokedex = std::move(temp_equip_pokedex); g_equip_pokedex_ready.store(true); g_pokedex_ready.store(true); }).detach(); } // ================================================================= // 异步纹理加载队列 // ================================================================= struct TexDecodedData { int w, h; unsigned char* pixels; }; std::mutex g_TexMutex; std::unordered_map g_heroTextureCache; std::vector> g_HeroTexDecodedQueue; std::unordered_map g_equipTextureCache; std::vector> g_EquipTexDecodedQueue; struct DecodeRequest { int id; bool isEquip; }; std::deque g_DecodeRequestQueue; std::mutex g_DecodeRequestMutex; void ClearAllTextureCaches() { std::lock_guard lock(g_TexMutex); for (auto& pair : g_heroTextureCache) { if (pair.second > 0) glDeleteTextures(1, &pair.second); } g_heroTextureCache.clear(); for (auto& item : g_HeroTexDecodedQueue) { if (item.second.pixels) stbi_image_free(item.second.pixels); } g_HeroTexDecodedQueue.clear(); for (auto& pair : g_equipTextureCache) { if (pair.second > 0) glDeleteTextures(1, &pair.second); } g_equipTextureCache.clear(); for (auto& item : g_EquipTexDecodedQueue) { if (item.second.pixels) stbi_image_free(item.second.pixels); } g_EquipTexDecodedQueue.clear(); } void TextureDecodingWorkerThread() { while (true) { DecodeRequest req; bool hasReq = false; { std::lock_guard lock(g_DecodeRequestMutex); if (!g_DecodeRequestQueue.empty()) { req = g_DecodeRequestQueue.front(); g_DecodeRequestQueue.pop_front(); hasReq = true; } } if (hasReq) { int imgLen = 0; const unsigned char* imgData = req.isEquip ? GetEquipImageBytes(req.id, &imgLen) : GetHeroImageBytes(req.id, &imgLen); if (imgData != nullptr && imgLen > 0) { int w, h, channels; unsigned char* data = stbi_load_from_memory(imgData, imgLen, &w, &h, &channels, 4); if (data && w > 0 && h > 0 && w <= 2048 && h <= 2048) { std::lock_guard lock(g_TexMutex); if (req.isEquip) g_EquipTexDecodedQueue.push_back({req.id, {w, h, data}}); else g_HeroTexDecodedQueue.push_back({req.id, {w, h, data}}); } else if (data) { stbi_image_free(data); } } } else { std::this_thread::sleep_for(std::chrono::milliseconds(20)); } } } GLuint GetHeroTexture(int heroId) { auto it = g_heroTextureCache.find(heroId); if (it != g_heroTextureCache.end()) return it->second; g_heroTextureCache[heroId] = 0; std::lock_guard lock(g_DecodeRequestMutex); g_DecodeRequestQueue.push_back({heroId, false}); return 0; } GLuint GetEquipTexture(int equipId) { auto it = g_equipTextureCache.find(equipId); if (it != g_equipTextureCache.end()) return it->second; g_equipTextureCache[equipId] = 0; std::lock_guard lock(g_DecodeRequestMutex); g_DecodeRequestQueue.push_back({equipId, true}); return 0; } void ProcessTextureQueue() { std::lock_guard lock(g_TexMutex); if (g_HeroTexDecodedQueue.empty() && g_EquipTexDecodedQueue.empty()) return; GLint last_unpack; glGetIntegerv(GL_UNPACK_ALIGNMENT, &last_unpack); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); for (auto& item : g_HeroTexDecodedQueue) { GLuint tex = 0; glGenTextures(1, &tex); if (tex != 0) { glBindTexture(GL_TEXTURE_2D, tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, item.second.w, item.second.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, item.second.pixels); } stbi_image_free(item.second.pixels); g_heroTextureCache[item.first] = tex; } g_HeroTexDecodedQueue.clear(); for (auto& item : g_EquipTexDecodedQueue) { GLuint tex = 0; glGenTextures(1, &tex); if (tex != 0) { glBindTexture(GL_TEXTURE_2D, tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, item.second.w, item.second.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, item.second.pixels); } stbi_image_free(item.second.pixels); g_equipTextureCache[item.first] = tex; } g_EquipTexDecodedQueue.clear(); glPixelStorei(GL_UNPACK_ALIGNMENT, last_unpack); } inline void DrawTextWithStroke(ImDrawList* d, ImFont* font, float fontSize, ImVec2 pos, ImU32 textColor, ImU32 strokeColor, const char* text) { d->AddText(font, fontSize, pos + ImVec2(1.5f, 1.5f), strokeColor, text); d->AddText(font, fontSize, pos, textColor, text); } // ================================================================= // 牌池全自动极速采集引擎 // ================================================================= typedef void* (*func_6A67e48_t)(void* x0, void* x1, void* x2, void* x3); func_6A67e48_t orig_6A67e48 = nullptr; void* hook_6A67e48(void* x0, void* x1, void* x2, void* x3) { if (!g_is_in_match.load(std::memory_order_relaxed)) return orig_6A67e48(x0, x1, x2, x3); if (g_pool_entries1.load(std::memory_order_relaxed) != 0) { return orig_6A67e48(x0, x1, x2, x3); } g_debug_hook_6A67e48_count++; if (x0 != nullptr) { uintptr_t base = (uintptr_t)x0; g_debug_pool_base.store(base, std::memory_order_relaxed); // 修改: 记录池基址 uintptr_t heroku = SAFE_READ_PTR(base, g_off_pool_heroku); if (IsValidPtr(heroku)) { uintptr_t aheroku = SAFE_READ_PTR(heroku, g_off_pool_Aheroku); if (IsValidPtr(aheroku)) { uintptr_t entries1 = SAFE_READ_PTR(aheroku, g_off_pool_entries1); if (IsValidPtr(entries1)) g_pool_entries1.store(entries1, std::memory_order_release); } } } return orig_6A67e48(x0, x1, x2, x3); } void UpdateCardPoolData() { uintptr_t pEntries1 = g_pool_entries1.load(std::memory_order_acquire); if (pEntries1 == 0 || !IsValidPtr(pEntries1)) return; static auto last_pool_update = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now(); if (std::chrono::duration_cast(now - last_pool_update).count() < 100) return; last_pool_update = now; int count1 = SAFE_READ(int, pEntries1, g_off_pool_e1_count, 0); int maxIter1 = std::clamp(count1, 0, 150); for (int i = 0; i < maxIter1; i++) { uintptr_t entry1_ptr = pEntries1 + g_off_pool_e1_head + i * g_off_pool_e1_step; uintptr_t value1 = SAFE_READ_PTR(entry1_ptr, g_off_pool_e1_val); if (!IsValidPtr(value1)) continue; uintptr_t entries2 = SAFE_READ_PTR(value1, g_off_pool_entries2); if (!IsValidPtr(entries2)) continue; int count2 = SAFE_READ(int, entries2, g_off_pool_e2_count, 0); int maxIter2 = std::clamp(count2, 0, 100); for (int j = 0; j < maxIter2; j++) { uintptr_t entry2_ptr = entries2 + g_off_pool_e2_head + j * g_off_pool_e2_step; uintptr_t value2 = SAFE_READ_PTR(entry2_ptr, g_off_pool_e2_val); if (!IsValidPtr(value2)) continue; int heroId = SAFE_READ(int, value2, g_off_pool_v2_id, 0); if (heroId > 0) { int rem = SAFE_READ(int, value2, g_off_pool_v2_rem, 0); int tot = SAFE_READ(int, value2, g_off_pool_v2_tot, 0); int cost = GetCostFromHeroId(heroId); g_BackendData.cardPoolData[heroId] = { heroId, rem, tot, cost }; } } } } // ================================================================= // 解析引擎核心 (后台线程无锁调用) // ================================================================= auto getEntriesDynamically = [](uintptr_t objAddr, int baseOff) -> uintptr_t { if (!objAddr || !IsValidPtr(objAddr)) return 0; uintptr_t pBase = SAFE_READ_PTR(objAddr, baseOff); if (!pBase) return 0; uintptr_t p20 = SAFE_READ_PTR(pBase, g_off_shop_p20); if (!p20) return 0; return SAFE_READ_PTR(p20, g_off_shop_entries); }; void EnsureChessBattleModel() { if (g_il2cppBase == 0) return; typedef void* (*func_58543a8_t)(); func_58543a8_t getMgr = (func_58543a8_t)(g_il2cppBase + 0x58543a8); void* mgrObj = getMgr(); if (mgrObj && IsValidPtr((uintptr_t)mgrObj)) { g_debug_mgrObj = (uintptr_t)mgrObj; uintptr_t current_model = SAFE_READ_PTR(mgrObj, 0x10); if (IsValidPtr(current_model)) { typedef int (*func_3fac734_t)(void* battleModel); func_3fac734_t getMyId = (func_3fac734_t)(g_il2cppBase + 0x3fac734); int myId = getMyId ? getMyId((void*)current_model) : -1; bool isNewModel = (g_BackendData.chessBattleModel != current_model) || (g_BackendData.myPlayerId != myId); if (isNewModel && myId != -1) { int season = SAFE_READ(int, current_model, 0x140, 0); g_BackendData = GameSnapshot(); g_BackendData.chessBattleModel = current_model; g_BackendData.myPlayerId = myId; g_BackendData.season = season; g_BackendData.nextEnemyPlayerId = -1; g_collected_all_players.store(false, std::memory_order_relaxed); g_debug_collected_players_count = 0; g_hexCfgObj = nullptr; g_Tasks.processed_hex_cfg_obj = 0; g_Tasks.pending_hex_cfg_obj.store(0); g_pool_entries1.store(0, std::memory_order_release); g_Tasks.player_row_queue.clear(); g_Tasks.money_queue.clear(); g_Tasks.level_queue.clear(); g_Tasks.update_turn_start_queue.clear(); g_shop_slot_index = 0; for (int i = 0; i < 5; i++) g_shop_slot_instances[i] = 0; { std::lock_guard lock(g_swipe_mutex); g_active_swipes.clear(); } g_is_in_match.store(true, std::memory_order_release); PushSnapshot(); PushSnapshot(); } } } } void CheckReadyMatchFetch() { uintptr_t modelToFetch = g_BackendData.chessBattleModel; int myIdToFetch = g_BackendData.myPlayerId; if (modelToFetch != 0 && myIdToFetch != -1 && IsValidPtr(modelToFetch)) { typedef int (*func_GetMatchPlayerId_t)(void* battleModel, int myPlayerId); func_GetMatchPlayerId_t getMatch = (func_GetMatchPlayerId_t)(g_il2cppBase + 0x3fd7a80); if (getMatch) { try { int nextEnemy = getMatch((void*)modelToFetch, myIdToFetch); if (nextEnemy != -1) { g_BackendData.nextEnemyPlayerId = nextEnemy; } } catch (...) {} } } } void UpdateShopAndBenchData() { uintptr_t myShop = 0, myBench = 0, enemyShop = 0, enemyBench = 0; uintptr_t myLogicObj = 0, myHeroArray = 0; auto myIt = g_BackendData.players.find(g_BackendData.myPlayerId); if (g_BackendData.myPlayerId != -1 && myIt != g_BackendData.players.end()) { uintptr_t myObj = myIt->second.objAddr; myShop = getEntriesDynamically(myObj, g_off_shop_base); myBench = getEntriesDynamically(myObj, g_off_bench_base); myLogicObj = myIt->second.logicObjAddr; myHeroArray = myIt->second.heroArrayAddr; } int enemyId = g_BackendData.nextEnemyPlayerId; auto enemyIt = g_BackendData.players.find(enemyId); if (enemyId != -1 && enemyIt != g_BackendData.players.end()) { uintptr_t enemyObj = enemyIt->second.objAddr; enemyShop = getEntriesDynamically(enemyObj, g_off_shop_base); enemyBench = getEntriesDynamically(enemyObj, g_off_bench_base); } if (!IsValidPtr(myHeroArray) && IsValidPtr(myLogicObj)) { uintptr_t dyn2b0 = SAFE_READ_PTR(myLogicObj, 0x2b0); if (IsValidPtr(dyn2b0)) { myHeroArray = SAFE_READ_PTR(dyn2b0, 0x20); if (IsValidPtr(myHeroArray)) g_BackendData.players[g_BackendData.myPlayerId].heroArrayAddr = myHeroArray; } } auto parseEntries = [](uintptr_t entriesAddr, int maxSlots, std::vector& outVec) { outVec.clear(); if (!IsValidPtr(entriesAddr)) return; int count = SAFE_READ(int, entriesAddr, g_off_entries_count, 0); int maxIter = std::clamp(count, 0, 50); outVec.reserve(maxIter); for (int i = 0; i < maxIter; i++) { uintptr_t entryStart = entriesAddr + g_off_entries_head + i * g_off_entries_step; uintptr_t value = SAFE_READ_PTR(entryStart, g_off_entry_value); if (!value) continue; int slotIndex = SAFE_READ(int, value, g_off_value_slot, -1); if (slotIndex >= 0 && slotIndex < maxSlots) { uintptr_t p10 = SAFE_READ_PTR(value, g_off_value_p10); if (p10) { int heroId = SAFE_READ(int, p10, g_off_p10_heroId, 0); if (heroId > 0) { int starLevel, cost, baseHeroId; FastParseHeroId(heroId, baseHeroId, cost, starLevel); outVec.push_back({heroId, baseHeroId, slotIndex, cost, starLevel}); } } } } }; parseEntries(myShop, 5, g_BackendData.myShopHeroes); parseEntries(myBench, 9, g_BackendData.myBenchHeroes); parseEntries(enemyShop, 5, g_BackendData.enemyShopHeroes); parseEntries(enemyBench, 9, g_BackendData.enemyBenchHeroes); } void UpdateEnemyBoardState() { uintptr_t heroArray = 0; uintptr_t logicObj = 0; auto it = g_BackendData.players.find(g_BackendData.nextEnemyPlayerId); if (g_BackendData.nextEnemyPlayerId != -1 && it != g_BackendData.players.end()) { logicObj = it->second.logicObjAddr; } if (IsValidPtr(logicObj)) { uintptr_t dynAddr2b0 = SAFE_READ_PTR(logicObj, 0x2b0); if (IsValidPtr(dynAddr2b0)) { uintptr_t newArray = SAFE_READ_PTR(dynAddr2b0, 0x20); if (IsValidPtr(newArray)) { heroArray = newArray; if (it != g_BackendData.players.end()) { it->second.heroArrayAddr = heroArray; } } } } if (!IsValidPtr(heroArray) && it != g_BackendData.players.end()) { heroArray = it->second.heroArrayAddr; } if (!IsValidPtr(heroArray)) return; int heroCount = SAFE_READ(int, heroArray, g_off_hero_count, 0); if (heroCount <= 0 || heroCount > 200) { auto it = g_BackendData.players.find(g_BackendData.nextEnemyPlayerId); if (it != g_BackendData.players.end()) it->second.heroArrayAddr = 0; return; } g_BackendData.currentEnemyHeroes.clear(); for (int x = 0; x < 7; ++x) { for (int y = 0; y < 4; ++y) { uintptr_t heroObj = SAFE_READ_PTR(heroArray, 0x20 + (x * 8 + y) * 8); if (IsValidPtr(heroObj)) { int heroId = SAFE_READ(int, heroObj, 0x108, 0); if (heroId > 0) { int equipCount = 0; uintptr_t equipAddr = SAFE_READ_PTR(heroObj, 0x218); if (IsValidPtr(equipAddr)) equipCount = SAFE_READ(int, equipAddr, 0x18, 0); g_BackendData.currentEnemyHeroes.push_back({heroId, x, y, equipCount}); } } } } } typedef Vector3 (*func_WorldToScreenPoint_t)(void* camera, Vector3 position); void UpdateEquipAndLiveHeroes() { auto it = g_BackendData.players.find(g_BackendData.myPlayerId); if (it == g_BackendData.players.end()) return; uintptr_t logicObj = it->second.logicObjAddr; if (!IsValidPtr(logicObj)) return; if (g_il2cppBase != 0) { typedef void* (*func_get_main_t)(); func_get_main_t get_main = (func_get_main_t)(g_il2cppBase + 0x9898204); if (get_main) { try { void* cam = get_main(); if (cam && IsValidPtr((uintptr_t)cam)) { g_BackendData.mainCamera = (uintptr_t)cam; } else { g_BackendData.mainCamera = 0; } } catch(...) {} } } g_BackendData.myItems.clear(); uintptr_t pEquipBase = SAFE_READ_PTR(logicObj, g_off_equip_base); if (IsValidPtr(pEquipBase)) { uintptr_t itemArr = SAFE_READ_PTR(pEquipBase, g_off_equip_arr); if (IsValidPtr(itemArr)) { int equipCount = SAFE_READ(int, itemArr, g_off_equip_count, 0); int maxEquipIter = std::clamp(equipCount, 0, 20); for (int i = 0; i < maxEquipIter; i++) { uintptr_t itemObj = SAFE_READ_PTR(itemArr, g_off_equip_head + i * g_off_equip_step); int equipId = 0; if (IsValidPtr(itemObj)) { equipId = SAFE_READ(int, itemObj, g_off_equip_id, 0); } g_BackendData.myItems.push_back(equipId); } } } g_BackendData.myLiveHeroes.clear(); g_BackendData.myBenchLiveHeroes.clear(); g_BackendData.debugHeroTraces.clear(); uintptr_t pDyn = SAFE_READ_PTR(logicObj, g_off_hero_dyn); uintptr_t cachedHeroArr = 0; if (IsValidPtr(pDyn)) { cachedHeroArr = SAFE_READ_PTR(pDyn, g_off_hero_arr); if (IsValidPtr(cachedHeroArr)) { it->second.heroArrayAddr = cachedHeroArr; } } if (!IsValidPtr(cachedHeroArr)) { cachedHeroArr = it->second.heroArrayAddr; } if (IsValidPtr(cachedHeroArr)) { int heroCount = SAFE_READ(int, cachedHeroArr, g_off_hero_count, 0); if (heroCount <= 0 || heroCount > 200) { it->second.heroArrayAddr = 0; return; } int maxHeroIter = std::min(heroCount, 128); for (int i = 0; i < maxHeroIter; i++) { uintptr_t heroObj = SAFE_READ_PTR(cachedHeroArr, g_off_hero_head + i * g_off_hero_step); if (IsValidPtr(heroObj)) { int hId = SAFE_READ(int, heroObj, g_off_hero_id, 0); if (hId > 0 && hId < 60000) { uintptr_t unitData = SAFE_READ_PTR(heroObj, g_off_hero_unit); uintptr_t battleUnit = 0; Vector3 pos = {0,0,0}; Vector3 screenPos = {0,0,0}; if (IsValidPtr(unitData)) { battleUnit = SAFE_READ_PTR(unitData, g_off_unit_battle); if (IsValidPtr(battleUnit)) { pos.x = SAFE_READ(float, battleUnit, g_off_battle_posx, 0.0f); pos.y = SAFE_READ(float, battleUnit, g_off_battle_posx + 4, 0.0f); pos.z = SAFE_READ(float, battleUnit, g_off_battle_posx + 8, 0.0f); // 【彻底修复追踪线乱跳】:加上严格的野指针检测与边界过滤! if (std::isnan(pos.x) || std::isnan(pos.y) || std::isnan(pos.z) || std::isinf(pos.x) || std::isinf(pos.y) || std::isinf(pos.z) || pos.y < -50.0f || pos.y > 200.0f || pos.x < -300.0f || pos.x > 300.0f || pos.z < -300.0f || pos.z > 300.0f) { continue; } if (g_BackendData.mainCamera != 0 && g_il2cppBase != 0) { func_WorldToScreenPoint_t w2s = (func_WorldToScreenPoint_t)(g_il2cppBase + 0x9897df8); try { screenPos = w2s((void*)g_BackendData.mainCamera, pos); } catch(...) {} } // 屏幕坐标过滤:摄像机背面(z<=0.01)或者转换失败的,直接丢弃 if (screenPos.z <= 0.01f || std::isnan(screenPos.x) || std::isnan(screenPos.y) || std::isinf(screenPos.x) || std::isinf(screenPos.y)) { continue; } int equipCount = 0; uintptr_t equipAddr = SAFE_READ_PTR(heroObj, 0x218); if (IsValidPtr(equipAddr)) equipCount = SAFE_READ(int, equipAddr, 0x18, 0); if (i < 28) { g_BackendData.myLiveHeroes.push_back({hId, pos, screenPos, equipCount, i}); } else { g_BackendData.myBenchLiveHeroes.push_back({hId, pos, screenPos, equipCount, i}); } } } DebugHeroTrace trace; trace.index = i; trace.heroObj = heroObj; trace.heroId = hId; trace.unitData = unitData; trace.battleUnit = battleUnit; trace.worldPos = pos; trace.screenPos = screenPos; g_BackendData.debugHeroTraces.push_back(trace); } } } } } void UpdateWarningData() { if (!g_card_warning) return; g_BackendData.playerWarnings.clear(); for (const auto& pair : g_BackendData.players) { const PlayerInfo& p = pair.second; if (p.rowIdx < 1 || p.rowIdx > 8) continue; std::unordered_map counts; auto addHeroToCount = [&](int heroId) { if (heroId <= 0) return; int star = 1, baseId = heroId, dummyCost; FastParseHeroId(heroId, baseId, dummyCost, star); counts[baseId] += (star == 3 ? 9 : (star == 2 ? 3 : 1)); }; uintptr_t shopEntries = getEntriesDynamically(p.objAddr, g_off_shop_base); if (IsValidPtr(shopEntries)) { int count = SAFE_READ(int, shopEntries, g_off_entries_count, 0); for (int i = 0; i < std::clamp(count, 0, 5); i++) { uintptr_t entryStart = shopEntries + g_off_entries_head + i * g_off_entries_step; uintptr_t value = SAFE_READ_PTR(entryStart, g_off_entry_value); if (value) { uintptr_t p10 = SAFE_READ_PTR(value, g_off_value_p10); if (p10) addHeroToCount(SAFE_READ(int, p10, g_off_p10_heroId, 0)); } } } uintptr_t benchEntries = getEntriesDynamically(p.objAddr, g_off_bench_base); if (IsValidPtr(benchEntries)) { int count = SAFE_READ(int, benchEntries, g_off_entries_count, 0); for (int i = 0; i < std::clamp(count, 0, 9); i++) { uintptr_t entryStart = benchEntries + g_off_entries_head + i * g_off_entries_step; uintptr_t value = SAFE_READ_PTR(entryStart, g_off_entry_value); if (value) { uintptr_t p10 = SAFE_READ_PTR(value, g_off_value_p10); if (p10) addHeroToCount(SAFE_READ(int, p10, g_off_p10_heroId, 0)); } } } uintptr_t heroArr = 0; if (IsValidPtr(p.logicObjAddr)) { uintptr_t dyn2b0 = SAFE_READ_PTR(p.logicObjAddr, 0x2b0); if (IsValidPtr(dyn2b0)) heroArr = SAFE_READ_PTR(dyn2b0, 0x20); } if (!IsValidPtr(heroArr)) { heroArr = p.heroArrayAddr; } if (IsValidPtr(heroArr)) { for (int i = 0; i < 28; i++) { uintptr_t heroObj = SAFE_READ_PTR(heroArr, 0x20 + i * 8); if (IsValidPtr(heroObj)) addHeroToCount(SAFE_READ(int, heroObj, 0x108, 0)); } } PlayerWarningInfo info; info.rowIdx = p.rowIdx; info.heroCounts.reserve(counts.size()); for (const auto& kv : counts) info.heroCounts.push_back({kv.first, kv.second}); g_BackendData.playerWarnings.push_back(std::move(info)); } } void UpdatePlayerStreaks() { for (auto& pair : g_BackendData.players) { uintptr_t logicObj = pair.second.logicObjAddr; if (IsValidPtr(logicObj)) { pair.second.winStreak = SAFE_READ(int, logicObj, 0xdc, 0); pair.second.loseStreak = SAFE_READ(int, logicObj, 0xe0, 0); } } } // ================================================================= // JNI Hook 与 剪贴板支持 // ================================================================= JavaVM* g_jvm = nullptr; jobject g_context = nullptr; jobject g_view_obj = nullptr; void CopyToAndroidClipboard(const char* text) { if (!g_jvm || !g_context) return; JNIEnv* env = nullptr; int status = g_jvm->GetEnv((void**)&env, JNI_VERSION_1_6); bool attached = false; if (status == JNI_EDETACHED) { if (g_jvm->AttachCurrentThread(&env, nullptr) != JNI_OK) return; attached = true; } if (!env) return; jstring jtext = env->NewStringUTF(text); jclass contextClass = env->GetObjectClass(g_context); jmethodID getSystemServiceMid = env->GetMethodID(contextClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); jstring serviceName = env->NewStringUTF("clipboard"); jobject clipboard = env->CallObjectMethod(g_context, getSystemServiceMid, serviceName); if (clipboard) { jclass clipboardClass = env->GetObjectClass(clipboard); jclass clipDataClass = env->FindClass("android/content/ClipData"); if (clipDataClass) { jmethodID newPlainTextMid = env->GetStaticMethodID(clipDataClass, "newPlainText", "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Landroid/content/ClipData;"); jstring label = env->NewStringUTF("CopiedPath"); jobject clipData = env->CallStaticObjectMethod(clipDataClass, newPlainTextMid, label, jtext); jmethodID setPrimaryClipMid = env->GetMethodID(clipboardClass, "setPrimaryClip", "(Landroid/content/ClipData;)V"); if (setPrimaryClipMid) env->CallVoidMethod(clipboard, setPrimaryClipMid, clipData); env->DeleteLocalRef(clipData); env->DeleteLocalRef(label); } else { env->ExceptionClear(); jmethodID setTextMid = env->GetMethodID(clipboardClass, "setText", "(Ljava/lang/CharSequence;)V"); if (setTextMid) env->CallVoidMethod(clipboard, setTextMid, jtext); } env->DeleteLocalRef(clipboardClass); env->DeleteLocalRef(clipboard); } env->DeleteLocalRef(serviceName); env->DeleteLocalRef(contextClass); env->DeleteLocalRef(jtext); if (attached) g_jvm->DetachCurrentThread(); } typedef void (*nativeInjectEvent_t)(JNIEnv*, jobject, jobject); nativeInjectEvent_t old_nativeInjectEvent = nullptr; void SimulateSwipe(float startX, float startY, float endX, float endY, bool instant = false) { if (g_show_swipe_visuals) { std::lock_guard lock(g_swipe_mutex); g_active_swipes.push_back({{startX, startY}, {endX, endY}, instant ? 0.5f : 2.0f, instant ? 0.5f : 2.0f}); } if (!g_jvm || !g_view_obj || !old_nativeInjectEvent) return; std::thread([startX, startY, endX, endY, instant]() { JNIEnv* env = nullptr; bool attached = false; if (g_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_EDETACHED) { if (g_jvm->AttachCurrentThread(&env, nullptr) != JNI_OK) return; attached = true; } jclass sysClockClass = env->FindClass("android/os/SystemClock"); jmethodID uptimeMid = env->GetStaticMethodID(sysClockClass, "uptimeMillis", "()J"); jclass meClass = env->FindClass("android/view/MotionEvent"); jmethodID obtainMid = env->GetStaticMethodID(meClass, "obtain", "(JJIFFI)Landroid/view/MotionEvent;"); jmethodID recycleMid = env->GetMethodID(meClass, "recycle", "()V"); jmethodID setSourceMid = env->GetMethodID(meClass, "setSource", "(I)V"); if (sysClockClass && uptimeMid && meClass && obtainMid) { long downTime = env->CallStaticLongMethod(sysClockClass, uptimeMid); float v_startX = startX, v_startY = startY, v_endX = endX, v_endY = endY; if (g_cached_view_width > 0 && g_gl_width > 0) { v_startX = startX * ((float)g_cached_view_width / g_gl_width); v_endX = endX * ((float)g_cached_view_width / g_gl_width); } if (g_cached_view_height > 0 && g_gl_height > 0) { v_startY = startY * ((float)g_cached_view_height / g_gl_height); v_endY = endY * ((float)g_cached_view_height / g_gl_height); } AddLog("[底层滑动] 渲染坐标(%.1f, %.1f)->(%.1f, %.1f)", startX, startY, endX, endY); AddLog("[底层滑动] 物理映射(%.1f, %.1f)->(%.1f, %.1f)", v_startX, v_startY, v_endX, v_endY); int steps = instant ? 0 : g_swipe_steps; int step_delay = instant ? 0 : g_swipe_step_delay_ms; int down_delay = instant ? 20 : g_swipe_down_delay_ms; int up_delay = instant ? 20 : g_swipe_up_delay_ms; auto injectTouchEvent = [&](int action, float x, float y) { long eventTime = env->CallStaticLongMethod(sysClockClass, uptimeMid); jobject meObj = env->CallStaticObjectMethod(meClass, obtainMid, downTime, eventTime, action, x, y, 0); if (meObj) { if (setSourceMid) env->CallVoidMethod(meObj, setSourceMid, 4098); old_nativeInjectEvent(env, g_view_obj, meObj); if (recycleMid) env->CallVoidMethod(meObj, recycleMid); env->DeleteLocalRef(meObj); } }; injectTouchEvent(0, v_startX, v_startY); if (down_delay > 0) std::this_thread::sleep_for(std::chrono::milliseconds(down_delay)); if (steps > 0) { for (int i = 1; i <= steps; i++) { float t = (float)i / steps; float curX = v_startX + (v_endX - v_startX) * t; float curY = v_startY + (v_endY - v_startY) * t; injectTouchEvent(2, curX, curY); if (step_delay > 0) std::this_thread::sleep_for(std::chrono::milliseconds(step_delay)); } } else { injectTouchEvent(2, v_endX, v_endY); if (step_delay > 0) std::this_thread::sleep_for(std::chrono::milliseconds(step_delay)); } if (up_delay > 0) std::this_thread::sleep_for(std::chrono::milliseconds(up_delay)); injectTouchEvent(1, v_endX, v_endY); } if (sysClockClass) env->DeleteLocalRef(sysClockClass); if (meClass) env->DeleteLocalRef(meClass); if (attached) g_jvm->DetachCurrentThread(); }).detach(); } // ================================================================= // 序列化 // ================================================================= void SaveConfig() { std::lock_guard lock(g_configMutex); g_auto_buy_heroes_str = ""; for (int id : g_auto_buy_heroes) { g_auto_buy_heroes_str += std::to_string(id) + ","; } g_hero_equip_bindings_str = ""; for(const auto& pair : g_hero_equip_bindings) { if(pair.second.empty()) continue; g_hero_equip_bindings_str += std::to_string(pair.first) + ":"; for(size_t i=0; i 0) { g_hero_equip_priority_str += std::to_string(pair.first) + ":" + std::to_string(pair.second) + ";"; } } g_cell_equip_offset_str = ""; for (const auto& kv : g_cell_equip_offset) { g_cell_equip_offset_str += std::to_string(kv.first) + ":" + std::to_string(kv.second.x) + "," + std::to_string(kv.second.y) + ";"; } g_cell_sell_offset_str = ""; for (const auto& kv : g_cell_sell_offset) { g_cell_sell_offset_str += std::to_string(kv.first) + ":" + std::to_string(kv.second.x) + "," + std::to_string(kv.second.y) + ";"; } std::ofstream out(g_configPath); if (!out.is_open()) return; for (const auto& entry : g_configTable) { out << entry.key << "="; if (entry.type == C_BOOL) out << (*(bool*)entry.ptr); else if (entry.type == C_INT) out << (*(int*)entry.ptr); else if (entry.type == C_FLOAT) out << (*(float*)entry.ptr); else if (entry.type == C_STRING) out << (*(std::string*)entry.ptr); out << "\n"; } out.close(); } // ================================================================= // 自适应分辨率:自动穿/拆装备主干逻辑 (多维排序) // ================================================================= void ExecuteMainThreadAutoEquip() { auto snap = GetSnapshot(); if (!snap) return; int cooldown_ms = g_swipe_down_delay_ms + (g_swipe_steps * g_swipe_step_delay_ms) + g_swipe_up_delay_ms + g_equip_extra_cooldown_ms; static auto last_swipe_time = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now(); if (std::chrono::duration_cast(now - last_swipe_time).count() < cooldown_ms) { return; } static auto equip_idle_start = std::chrono::steady_clock::now(); static bool was_equip_active = false; if (g_auto_equip_active && !was_equip_active) { equip_idle_start = std::chrono::steady_clock::now(); } was_equip_active = g_auto_equip_active; // --- 自动穿装备 --- if (g_auto_equip_active) { bool equipped_this_frame = false; int num_heroes = snap->myLiveHeroes.size(); struct HeroEquipTarget { const LiveHeroData* hero; int priority; int cost; int star; }; std::vector sorted_targets; for (int i = 0; i < num_heroes; i++) { const auto& liveHero = snap->myLiveHeroes[i]; if (liveHero.equipCount >= 9) continue; int baseId, cost, star; FastParseHeroId(liveHero.heroId, baseId, cost, star); int prio = std::max(g_hero_equip_priority[liveHero.heroId], g_hero_equip_priority[baseId]); sorted_targets.push_back({&liveHero, prio, cost, star}); } std::stable_sort(sorted_targets.begin(), sorted_targets.end(), [](const HeroEquipTarget& a, const HeroEquipTarget& b) { if (a.priority != b.priority) return a.priority > b.priority; if (a.cost != b.cost) return a.cost > b.cost; return a.star > b.star; }); std::unordered_set used_slots_this_frame; for (const auto& target : sorted_targets) { const auto& liveHero = *(target.hero); int heroId = liveHero.heroId; int baseId, dummyCost, dummyStar; FastParseHeroId(heroId, baseId, dummyCost, dummyStar); auto binds_it = g_hero_equip_bindings.find(heroId); if (binds_it == g_hero_equip_bindings.end()) { binds_it = g_hero_equip_bindings.find(baseId); } if (binds_it != g_hero_equip_bindings.end()) { for (int targetEquip : binds_it->second) { int found_slot_index = -1; for (size_t item_idx = 0; item_idx < snap->myItems.size(); ++item_idx) { if (snap->myItems[item_idx] == targetEquip && used_slots_this_frame.find(item_idx) == used_slots_this_frame.end()) { found_slot_index = item_idx; break; } } if (found_slot_index != -1 && liveHero.screenPos.z > 0.01f) { used_slots_this_frame.insert(found_slot_index); int col = found_slot_index / 10; int row = found_slot_index % 10; float startX = (g_equip_ui_start_x + col * g_equip_ui_step_x) * g_autoScale; float startY = (g_equip_ui_start_y + row * g_equip_ui_step_y) * g_autoScale; if (g_cell_equip_offset.find(liveHero.cellIndex) == g_cell_equip_offset.end()) { g_cell_equip_offset[liveHero.cellIndex] = ImVec2(0.0f, -80.0f); } ImVec2 off = g_cell_equip_offset[liveHero.cellIndex]; float endX = liveHero.screenPos.x + off.x * g_autoScale; float endY = g_gl_height - liveHero.screenPos.y + off.y * g_autoScale; if (endX > 0 && endY > 0 && endX < g_gl_width && endY < g_gl_height) { AddLog("[智能穿装] 目标:%d Prio:%d 装备:%d 槽位:%d", baseId, target.priority, targetEquip, found_slot_index); SimulateSwipe(startX, startY, endX, endY); last_swipe_time = std::chrono::steady_clock::now(); equipped_this_frame = true; break; } } } } if (equipped_this_frame) break; } if (equipped_this_frame) { equip_idle_start = std::chrono::steady_clock::now(); } else { auto idle_duration = std::chrono::duration_cast(std::chrono::steady_clock::now() - equip_idle_start).count(); if (idle_duration > 1500) { g_auto_equip_active = false; equip_idle_start = std::chrono::steady_clock::now(); AddLog("[穿装结束] 所有优先级目标装备已处理完毕"); } } } // --- 自动拆装备 --- if (g_auto_unequip_active) { bool swiped_this_frame = false; bool all_cleared = true; int col = g_unequip_slot_idx / 10; int row = g_unequip_slot_idx % 10; float startX = (g_equip_ui_start_x + col * g_equip_ui_step_x) * g_autoScale; float startY = (g_equip_ui_start_y + row * g_equip_ui_step_y) * g_autoScale; auto tryUnequipList = [&](const std::vector& heroes) -> bool { for (const auto& hero : heroes) { if (hero.equipCount > 0 && hero.screenPos.z > 0.01f) { all_cleared = false; if (g_cell_equip_offset.find(hero.cellIndex) == g_cell_equip_offset.end()) { g_cell_equip_offset[hero.cellIndex] = ImVec2(0.0f, -80.0f); } ImVec2 off = g_cell_equip_offset[hero.cellIndex]; float endX = hero.screenPos.x + off.x * g_autoScale; float endY = g_gl_height - hero.screenPos.y + off.y * g_autoScale; if (endX > 0 && endY > 0 && endX < g_gl_width && endY < g_gl_height) { AddLog("[智能拆装] 目标英雄ID:%d 剩余装备:%d", hero.heroId, hero.equipCount); SimulateSwipe(startX, startY, endX, endY); last_swipe_time = std::chrono::steady_clock::now(); swiped_this_frame = true; return true; } } } return false; }; if (!tryUnequipList(snap->myLiveHeroes)) { tryUnequipList(snap->myBenchLiveHeroes); } if (!swiped_this_frame && all_cleared) { g_auto_unequip_active = false; AddLog("[拆装完成] 全场英雄装备已清空"); } } } // ================================================================= // 胶囊窗口一键卖英雄核心逻辑 // ================================================================= void TriggerSellAllHeroes() { static std::atomic is_selling{false}; if (is_selling.load()) return; is_selling.store(true); std::thread([]() { int retries = 0; const int MAX_RETRIES = 8; while (retries < MAX_RETRIES) { auto snap = GetSnapshot(); if (!snap) break; std::vector sell_positions; auto add_sell_pos = [&](const std::vector& heroes) { for (const auto& hero : heroes) { if (hero.screenPos.z > 0.01f) { ImVec2 off(0.0f, -80.0f); auto it = g_cell_sell_offset.find(hero.cellIndex); if (it != g_cell_sell_offset.end()) off = it->second; float sx = hero.screenPos.x + off.x * g_autoScale; float sy = g_gl_height - hero.screenPos.y + off.y * g_autoScale; sell_positions.push_back(ImVec2(sx, sy)); } } }; add_sell_pos(snap->myBenchLiveHeroes); add_sell_pos(snap->myLiveHeroes); if (sell_positions.empty()) { if (retries > 0) AddLog("[一键卖牌] 场上已空,所有英雄成功售出"); break; } if (retries == 0) AddLog("[一键卖牌] 开始极速出售所有 %d 个英雄", (int)sell_positions.size()); else AddLog("[一键卖牌] 检测到漏卖,第 %d 次重试,剩余 %d 个", retries, (int)sell_positions.size()); for (const auto& pos : sell_positions) { float endX = g_sell_drop_x * g_autoScale; float endY = g_sell_drop_y * g_autoScale; SimulateSwipe(pos.x, pos.y, endX, endY, true); std::this_thread::sleep_for(std::chrono::milliseconds(70)); } std::this_thread::sleep_for(std::chrono::milliseconds(600)); retries++; } if (retries >= MAX_RETRIES) { AddLog("[一键卖牌] 已达到最大重试次数,已停止。可能存在无法出售的单位(如假人)"); } is_selling.store(false); }).detach(); } extern "C" void hook_nativeInjectEvent(JNIEnv* env, jobject obj, jobject event) { if (!g_jvm) env->GetJavaVM(&g_jvm); if (obj) { if (!g_view_obj || !env->IsSameObject(g_view_obj, obj)) { if (g_view_obj) env->DeleteGlobalRef(g_view_obj); g_view_obj = env->NewGlobalRef(obj); } if (!g_context) { jclass viewClass = env->GetObjectClass(obj); jmethodID getContextMid = env->GetMethodID(viewClass, "getContext", "()Landroid/content/Context;"); if (getContextMid) { jobject ctx = env->CallObjectMethod(obj, getContextMid); if (ctx) g_context = env->NewGlobalRef(ctx); } env->DeleteLocalRef(viewClass); } } if (event) { static jclass motionEventClassGlobal = nullptr; if (!motionEventClassGlobal) { jclass meClass = env->FindClass("android/view/MotionEvent"); if (meClass) { motionEventClassGlobal = (jclass)env->NewGlobalRef(meClass); env->DeleteLocalRef(meClass); } } if (motionEventClassGlobal && env->IsInstanceOf(event, motionEventClassGlobal)) { static jmethodID getWidthMid = nullptr, getHeightMid = nullptr; static jmethodID getActionMid = nullptr, getXMid = nullptr, getYMid = nullptr; if (getWidthMid == nullptr) { jclass viewClass = env->GetObjectClass(obj); getWidthMid = env->GetMethodID(viewClass, "getWidth", "()I"); getHeightMid = env->GetMethodID(viewClass, "getHeight", "()I"); env->DeleteLocalRef(viewClass); getActionMid = env->GetMethodID(motionEventClassGlobal, "getAction", "()I"); getXMid = env->GetMethodID(motionEventClassGlobal, "getX", "()F"); getYMid = env->GetMethodID(motionEventClassGlobal, "getY", "()F"); } if (getActionMid && getXMid && getYMid) { int action = env->CallIntMethod(event, getActionMid) & 255; if ((action == 0 && g_cached_view_width <= 0) || g_cached_view_width <= 0) { g_cached_view_width = env->CallIntMethod(obj, getWidthMid); g_cached_view_height = env->CallIntMethod(obj, getHeightMid); } float raw_x = env->CallFloatMethod(event, getXMid), raw_y = env->CallFloatMethod(event, getYMid); float scale_x = 1.0f, scale_y = 1.0f; if (g_cached_view_width > 0 && g_gl_width > 0) scale_x = (float)g_gl_width / g_cached_view_width; if (g_cached_view_height > 0 && g_gl_height > 0) scale_y = (float)g_gl_height / g_cached_view_height; ImGuiIO& io = ImGui::GetIO(); io.AddMousePosEvent(raw_x * scale_x, raw_y * scale_y); if (action == 0) io.AddMouseButtonEvent(0, true); else if (action == 1) io.AddMouseButtonEvent(0, false); if (io.WantCaptureMouse) return; } } } if (old_nativeInjectEvent) old_nativeInjectEvent(env, obj, event); } void FindAndHookHiddenJNI() { FILE* fp = fopen("/proc/self/maps", "r"); if (!fp) return; char line[1024]; struct MemRegion { uintptr_t start, end; bool is_rw; }; std::vector regions; while (fgets(line, sizeof(line), fp)) { if (strstr(line, "libunity.so")) { bool is_r = strstr(line, "r-") != nullptr; bool is_rw = strstr(line, "rw") != nullptr; if (is_r || is_rw) { uintptr_t start, end; sscanf(line, "%lx-%lx", &start, &end); regions.push_back({start, end, is_rw}); } } } fclose(fp); const char* target_string = "nativeInjectEvent"; std::vector string_addrs; for (const auto& reg : regions) { if (!reg.is_rw) { for (uintptr_t p = reg.start; p < reg.end - strlen(target_string); p++) { if (memcmp((void*)p, target_string, strlen(target_string)) == 0) string_addrs.push_back(p); } } } if (string_addrs.empty()) return; bool found_func = false; for (const auto& reg : regions) { uintptr_t align_start = (reg.start + 7) & ~7; for (uintptr_t p = align_start; p < reg.end - sizeof(void*)*3; p += sizeof(void*)) { uintptr_t ptr_val = *(uintptr_t*)p; for (uintptr_t str_addr : string_addrs) { if (ptr_val == str_addr) { void** fnPtr_addr = (void**)(p + 16); void* real_function_addr = *fnPtr_addr; if (real_function_addr != nullptr && (uintptr_t)real_function_addr > 0x100000) { DobbyHook(real_function_addr, (void*)hook_nativeInjectEvent, (void**)&old_nativeInjectEvent); found_func = true; break; } } } if (found_func) break; } if (found_func) break; } } typedef int (*func_3Cd6058_t)(void* playerObj); func_3Cd6058_t orig_3Cd6058 = nullptr; typedef void (*func_set_Money_t)(void* playerObj, int money); func_set_Money_t orig_set_Money = nullptr; typedef void (*func_set_Level_t)(void* playerObj, int level); func_set_Level_t orig_set_Level = nullptr; typedef void (*func_TurnStart_t)(void* thisObj); func_TurnStart_t orig_TurnStart = nullptr; typedef void (*func_set_IsGameEnd_t)(void* thisObj, uint8_t isEnd); func_set_IsGameEnd_t orig_set_IsGameEnd = nullptr; typedef void (*func_UpdateTurnStart_t)(void* arg0); func_UpdateTurnStart_t orig_UpdateTurnStart = nullptr; typedef int (*func_InitTurnDropCfg_t)(void* obj, int index); func_InitTurnDropCfg_t orig_InitTurnDropCfg = nullptr; // === 大量使用 g_is_in_match 保护机制,防止大厅/加载界面卡死 === int hook_3Cd6058(void* playerObj) { if (!g_is_in_match.load(std::memory_order_relaxed)) return orig_3Cd6058(playerObj); g_debug_hook_3Cd6058_count++; int rowIdx = orig_3Cd6058(playerObj); if (playerObj && IsValidPtr((uintptr_t)playerObj)) { g_Tasks.player_row_queue.push({(uintptr_t)playerObj, rowIdx}); } return rowIdx; } void hook_set_Money(void* playerObj, int money) { orig_set_Money(playerObj, money); if (!g_is_in_match.load(std::memory_order_relaxed)) return; g_debug_hook_set_Money_count++; if (playerObj && IsValidPtr((uintptr_t)playerObj)) { g_Tasks.money_queue.push({(uintptr_t)playerObj, money}); } } void hook_set_Level(void* playerObj, int level) { orig_set_Level(playerObj, level); if (!g_is_in_match.load(std::memory_order_relaxed)) return; g_debug_hook_set_Level_count++; if (playerObj && IsValidPtr((uintptr_t)playerObj)) { g_Tasks.level_queue.push({(uintptr_t)playerObj, level}); } } void hook_TurnStart(void* thisObj) { orig_TurnStart(thisObj); if (!g_is_in_match.load(std::memory_order_relaxed)) return; g_debug_TurnStartCalled++; if (thisObj) g_Tasks.pending_turn_start_obj.store((uintptr_t)thisObj, std::memory_order_release); } // 【修复 1】:权威状态接管 void hook_set_IsGameEnd(void* thisObj, uint8_t isEnd) { orig_set_IsGameEnd(thisObj, isEnd); g_debug_set_IsGameEnd_count++; if (isEnd == 0) { if (!g_is_in_match.load(std::memory_order_acquire)) { AddLog("[状态监控] 引擎触发 isGameEnd = 0, 正式进入对局!"); g_is_in_match.store(true, std::memory_order_release); } } else { if (g_is_in_match.load(std::memory_order_acquire)) { AddLog("[状态监控] 引擎触发 isGameEnd = %d, 退出对局,清理残余数据!", isEnd); g_is_in_match.store(false, std::memory_order_release); g_Tasks.trigger_game_end.store(true, std::memory_order_release); } } } void hook_UpdateTurnStart(void* arg0) { orig_UpdateTurnStart(arg0); if (!g_is_in_match.load(std::memory_order_relaxed)) return; if (g_collected_all_players.load(std::memory_order_relaxed)) return; g_debug_UpdateTurnStart_count++; if (arg0 && IsValidPtr((uintptr_t)arg0)) { g_Tasks.update_turn_start_queue.push((uintptr_t)arg0); } } int hook_InitTurnDropCfg(void* obj, int index) { if (!g_is_in_match.load(std::memory_order_relaxed)) return orig_InitTurnDropCfg(obj, index); g_debug_hook_InitTurnDropCfg_count++; int ret = orig_InitTurnDropCfg(obj, index); if (obj && IsValidPtr((uintptr_t)obj)) g_Tasks.pending_hex_cfg_obj.store((uintptr_t)obj, std::memory_order_release); return ret; } typedef void* (*func_46cce08_t)(void* x0, void* x1, void* x2, void* x3); func_46cce08_t orig_46cce08 = nullptr; void* hook_46cce08(void* x0, void* arg1, void* arg2, void* arg3) { if (!g_is_in_match.load(std::memory_order_relaxed)) return orig_46cce08(x0, arg1, arg2, arg3); if (g_shop_slot_index < 5 && x0 != nullptr) { uintptr_t ptr_x0 = (uintptr_t)x0; bool already_exists = false; for (int i = 0; i < g_shop_slot_index; i++) { if (g_shop_slot_instances[i] == ptr_x0) { already_exists = true; break; } } if (!already_exists) { g_shop_slot_instances[g_shop_slot_index++] = ptr_x0; } } return orig_46cce08(x0, arg1, arg2, arg3); } static auto g_last_buy_request_time = std::chrono::steady_clock::now() - std::chrono::hours(1); void ExecuteMainThreadAutoRefresh() { if (!g_auto_refresh || g_refresh_instance == 0 || g_req_refresh == nullptr) return; static auto last_refresh_time = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now(); if (std::chrono::duration_cast(now - g_last_buy_request_time).count() < 300) return; if (std::chrono::duration_cast(now - last_refresh_time).count() < g_auto_buy_delay_ms) return; auto snap = GetSnapshot(); int myMoney = 0; bool has_target_in_shop = false; auto it = snap->players.find(snap->myPlayerId); if (it != snap->players.end()) myMoney = it->second.money; for (const auto& shopHero : snap->myShopHeroes) { if (g_auto_buy_heroes.count(shopHero.baseHeroId) || g_auto_buy_heroes.count(shopHero.heroId)) { if (shopHero.starLevel == 2 && !g_auto_buy_chosen) continue; has_target_in_shop = true; break; } } if (has_target_in_shop || myMoney < g_auto_refresh_money_threshold) { if (myMoney < g_auto_refresh_money_threshold) g_auto_refresh = false; return; } try { g_req_refresh((void*)g_refresh_instance); last_refresh_time = now; } catch (...) {} } void ExecuteMainThreadAutoBuy() { if (!g_auto_buy || g_reqbuyhero == nullptr) return; auto snap = GetSnapshot(); if (g_auto_buy_heroes.empty()) return; uintptr_t myObj = 0; auto it = snap->players.find(snap->myPlayerId); if (it != snap->players.end()) myObj = it->second.objAddr; if (!myObj || !IsValidPtr(myObj)) return; uintptr_t shopEntries = getEntriesDynamically(myObj, g_off_shop_base); if (!IsValidPtr(shopEntries)) return; int count = SAFE_READ(int, shopEntries, g_off_entries_count, 0); int maxIter = std::clamp(count, 0, 5); for (int i = 0; i < maxIter; i++) { uintptr_t entryStart = shopEntries + g_off_entries_head + i * g_off_entries_step; uintptr_t value = SAFE_READ_PTR(entryStart, g_off_entry_value); if (!value) continue; int slot = SAFE_READ(int, value, g_off_value_slot, -1); if (slot >= 0 && slot < 5) { uintptr_t p10 = SAFE_READ_PTR(value, g_off_value_p10); if (p10) { int heroId = SAFE_READ(int, p10, g_off_p10_heroId, 0); if (heroId > 0) { int baseId, cost, star; FastParseHeroId(heroId, baseId, cost, star); if (star == 2 && !g_auto_buy_chosen) continue; bool is_recommended = g_auto_buy_heroes.count(baseId) || g_auto_buy_heroes.count(heroId); if (is_recommended) { uintptr_t target_x0 = g_shop_slot_instances[slot]; if (target_x0 != 0) { try { g_reqbuyhero((void*)target_x0, nullptr, nullptr, nullptr); g_reqbuyhero((void*)target_x0, nullptr, nullptr, nullptr); g_reqbuyhero((void*)target_x0, nullptr, nullptr, nullptr); g_reqbuyhero((void*)target_x0, nullptr, nullptr, nullptr); g_reqbuyhero((void*)target_x0, nullptr, nullptr, nullptr); g_last_buy_request_time = std::chrono::steady_clock::now(); } catch (...) {} } } } } } } } void ProcessMainThreadTasks() { if (g_Tasks.trigger_game_end.exchange(false, std::memory_order_acquire)) { g_BackendData = GameSnapshot(); PushSnapshot(); PushSnapshot(); g_refresh_instance = 0; g_turnStartThisObj = nullptr; g_hexCfgObj = nullptr; g_pool_entries1.store(0, std::memory_order_release); g_debug_hook_6A67e48_count = 0; g_matchFetchRequested.store(false, std::memory_order_relaxed); g_matchFetchReady.store(false, std::memory_order_relaxed); g_collected_all_players.store(false, std::memory_order_relaxed); g_debug_collected_players_count = 0; g_debug_UpdateTurnStart_count = 0; g_shop_slot_index = 0; for (int i = 0; i < 5; i++) g_shop_slot_instances[i] = 0; g_Tasks.pending_hex_cfg_obj.store(0); g_Tasks.processed_hex_cfg_obj = 0; g_Tasks.pending_turn_start_obj.store(0); g_Tasks.player_row_queue.clear(); g_Tasks.money_queue.clear(); g_Tasks.level_queue.clear(); g_Tasks.update_turn_start_queue.clear(); std::lock_guard lock(g_swipe_mutex); g_active_swipes.clear(); } uintptr_t tObj = g_Tasks.pending_turn_start_obj.exchange(0, std::memory_order_acquire); if (tObj != 0) { g_turnStartThisObj = (void*)tObj; if (!g_matchFetchRequested.load(std::memory_order_acquire)) { g_matchFetchTime = std::chrono::steady_clock::now() + std::chrono::seconds(1); g_matchFetchRequested.store(true, std::memory_order_release); } } uintptr_t hexObj = g_Tasks.pending_hex_cfg_obj.load(std::memory_order_acquire); if (hexObj != 0 && hexObj != g_Tasks.processed_hex_cfg_obj) { g_Tasks.processed_hex_cfg_obj = hexObj; g_hexCfgObj = (void*)hexObj; g_BackendData.hex_fetched = false; for(int i=0; i<4; i++) g_BackendData.cachedHexes[i] = 0; g_BackendData.validHexCount = 0; } uintptr_t pObj; while (g_Tasks.update_turn_start_queue.pop(pObj)) { bool isAi = SAFE_READ(bool, pObj, 0x58, false); uintptr_t dynAddrs = SAFE_READ_PTR(pObj, 0x50); if (IsValidPtr(dynAddrs)) { int pid = SAFE_READ(int, dynAddrs, 0x20, -1); if (pid != -1) { if (g_BackendData.players[pid].logicObjAddr != pObj) { g_BackendData.players[pid].logicObjAddr = pObj; g_BackendData.players[pid].dynamicAddr = dynAddrs; g_BackendData.players[pid].isAi = isAi; g_BackendData.players[pid].heroArrayAddr = 0; } } } int valid_count = 0; for (const auto& pair : g_BackendData.players) if (pair.second.logicObjAddr != 0) valid_count++; g_debug_collected_players_count = valid_count; if (valid_count >= 8) g_collected_all_players.store(true, std::memory_order_relaxed); } PlayerRowInfo rowItem; while (g_Tasks.player_row_queue.pop(rowItem)) { uintptr_t objAddr = rowItem.obj; int rowIdx = rowItem.rowIdx; uintptr_t tempCsoGame = SAFE_READ_PTR(objAddr, 0x30); int playerId = SAFE_READ(int, objAddr, 0x258, -1); if (playerId == -1) continue; // 【修复 3:跨局野指针终极清空保护】 if (g_BackendData.csoGame != 0 && tempCsoGame != 0 && IsValidPtr(tempCsoGame) && g_BackendData.csoGame != tempCsoGame) { AddLog("[系统] 检测到新的一局游戏,强行清空上一局残留数据!"); g_Tasks.trigger_game_end.store(true, std::memory_order_release); return; } bool needFetchName = false; if (g_BackendData.csoGame == 0 && IsValidPtr(tempCsoGame)) g_BackendData.csoGame = tempCsoGame; auto it = g_BackendData.players.find(playerId); if (it == g_BackendData.players.end() || it->second.objAddr != objAddr) { PlayerInfo& info = g_BackendData.players[playerId]; info.objAddr = objAddr; info.csoGameAddr = tempCsoGame; info.playerId = playerId; info.rowIdx = rowIdx; if(it == g_BackendData.players.end()) info.name = "Unknown"; g_BackendData.objToPlayerId[objAddr] = playerId; needFetchName = true; } else { it->second.rowIdx = rowIdx; it->second.csoGameAddr = tempCsoGame; if (it->second.name == "Unknown" || it->second.name.empty()) needFetchName = true; } if (needFetchName) { std::string name = "未知"; uintptr_t midAddr = SAFE_READ_PTR(objAddr, 0x268); uintptr_t strPtr = 0; int nameLen = 0; if (IsValidPtr(midAddr)) { strPtr = SAFE_READ_PTR(midAddr, 0x28); if (IsValidPtr(strPtr)) { nameLen = SAFE_READ(int, strPtr, 0x10, 0); if (nameLen > 0 && nameLen < 64) { char16_t chars[64] = {0}; if (SafeReadMemory(strPtr + 0x14, chars, nameLen * sizeof(char16_t))) name = Utf16ToUtf8(chars, nameLen); } } } auto it2 = g_BackendData.players.find(playerId); if (it2 != g_BackendData.players.end()) { it2->second.name = name; it2->second.midAddr = midAddr; it2->second.strPtr = strPtr; it2->second.nameLen = nameLen; } } } PlayerStatInfo statItem; while (g_Tasks.money_queue.pop(statItem)) { auto it = g_BackendData.objToPlayerId.find(statItem.obj); if (it != g_BackendData.objToPlayerId.end()) g_BackendData.players[it->second].money = statItem.val; } while (g_Tasks.level_queue.pop(statItem)) { auto it = g_BackendData.objToPlayerId.find(statItem.obj); if (it != g_BackendData.objToPlayerId.end()) g_BackendData.players[it->second].level = statItem.val; } } void BackgroundDataUpdateThread() { while (!g_il2cpp_domain_get || !g_il2cpp_thread_attach || !g_il2cpp_thread_detach) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } void* domain = g_il2cpp_domain_get(); while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(30)); g_debug_bg_thread_count++; if (g_il2cppBase == 0) continue; // 【修复 2】:完全信任 hook_set_IsGameEnd 的判断,不再主动试探引发闪退! if (!g_is_in_match.load(std::memory_order_acquire)) { ProcessMainThreadTasks(); std::this_thread::sleep_for(std::chrono::milliseconds(200)); continue; } void* current_thread_handle = g_il2cpp_thread_attach(domain); EnsureChessBattleModel(); ProcessMainThreadTasks(); if (g_matchFetchRequested.load(std::memory_order_acquire)) { if (std::chrono::steady_clock::now() >= g_matchFetchTime) { g_matchFetchRequested.store(false, std::memory_order_relaxed); g_matchFetchReady.store(true, std::memory_order_release); } } if (g_matchFetchReady.load(std::memory_order_acquire)) { g_matchFetchReady.store(false, std::memory_order_relaxed); CheckReadyMatchFetch(); } if (g_predict_hex && g_hexCfgObj != nullptr && !g_BackendData.hex_fetched) { typedef int (*func_GetNextHexLevel_t)(void* obj, int index); func_GetNextHexLevel_t GetNextHexLevel = (func_GetNextHexLevel_t)(g_il2cppBase + 0x4446EDc); if (GetNextHexLevel) { int tempHexes[5] = {0}, verifyHexes[5] = {0}; int checkCount = 5; for (int i = 0; i < checkCount; i++) tempHexes[i] = GetNextHexLevel(g_hexCfgObj, i); for (int i = 0; i < checkCount; i++) verifyHexes[i] = GetNextHexLevel(g_hexCfgObj, i); bool match = true; int validCount = 0; for (int i = 0; i < checkCount; i++) { if (tempHexes[i] != verifyHexes[i]) { match = false; break; } if (tempHexes[i] >= 1 && tempHexes[i] <= 3) { validCount++; } } if (!match) { g_BackendData.validHexCount = 0; } else if (validCount > 0) { int saveCount = (validCount > 4) ? 4 : validCount; for (int i = 0; i < saveCount; i++) { g_BackendData.cachedHexes[i] = tempHexes[i]; } g_BackendData.validHexCount = saveCount; g_BackendData.hex_fetched = true; } } } UpdateShopAndBenchData(); UpdateCardPoolData(); if (g_predict_enemy) UpdateEnemyBoardState(); UpdateWarningData(); UpdatePlayerStreaks(); UpdateEquipAndLiveHeroes(); PushSnapshot(); ExecuteMainThreadAutoEquip(); if (g_Tasks.trigger_surrender.load(std::memory_order_acquire)) { void* turnObj = g_turnStartThisObj; int myId = g_BackendData.myPlayerId; if (g_il2cppBase != 0 && turnObj != nullptr && myId != -1) { if (IsValidPtr((uintptr_t)turnObj)) { typedef void (*func_UserExit_t)(void* thisObj, int playerId, int surrenderFlag); func_UserExit_t doUserExit = (func_UserExit_t)(g_il2cppBase + 0x3da60c0); try { doUserExit(turnObj, myId, 1); } catch (...) {} } } g_Tasks.trigger_surrender.store(false, std::memory_order_release); g_Tasks.trigger_game_end.store(true, std::memory_order_release); } g_il2cpp_thread_detach(current_thread_handle); } } typedef void (*f_SendWillRenderCanvases)(); f_SendWillRenderCanvases orig_SendWillRenderCanvases = nullptr; void hook_SendWillRenderCanvases() { if (orig_SendWillRenderCanvases) orig_SendWillRenderCanvases(); if (!g_is_in_match.load(std::memory_order_relaxed)) return; g_debug_SendWillRenderCanvases_count++; ExecuteMainThreadAutoBuy(); ExecuteMainThreadAutoRefresh(); } typedef void* (*func_4848cec_t)(void* x0, void* x1, void* x2, void* x3); func_4848cec_t orig_4848cec = nullptr; void* hook_4848cec(void* x0, void* arg1, void* arg2, void* arg3) { if (!g_is_in_match.load(std::memory_order_relaxed)) return orig_4848cec(x0, arg1, arg2, arg3); if (x0 != nullptr) g_refresh_instance = (uintptr_t)x0; return orig_4848cec(x0, arg1, arg2, arg3); } void InitBusinessHooks() { while (g_il2cppBase == 0) { FILE *fp = fopen("/proc/self/maps", "r"); if (fp) { char line[512]; while (fgets(line, sizeof(line), fp)) { if (strstr(line, "libil2cpp.so") && strstr(line, "r-xp")) { sscanf(line, "%lx", &g_il2cppBase); break; } } fclose(fp); } if (g_il2cppBase == 0) sleep(1); } void* handle = dlopen("libil2cpp.so", RTLD_LAZY); if (handle) { g_il2cpp_domain_get = (il2cpp_domain_get_t)dlsym(handle, "il2cpp_domain_get"); g_il2cpp_thread_attach = (il2cpp_thread_attach_t)dlsym(handle, "il2cpp_thread_attach"); g_il2cpp_thread_detach = (il2cpp_thread_detach_t)dlsym(handle, "il2cpp_thread_detach"); g_il2cpp_string_new = (il2cpp_string_new_t)dlsym(handle, "il2cpp_string_new"); } DobbyHook((void*)(g_il2cppBase + 0x5277718), (void*)hook_LoadAsset_718, (void**)&orig_LoadAsset_718); DobbyHook((void*)(g_il2cppBase + 0x470f4b0), (void*)hook_LoadMapImpl, (void**)&orig_LoadMapImpl); DobbyHook((void*)(g_il2cppBase + 0x46cce08), (void*)hook_46cce08, (void**)&orig_46cce08); g_reqbuyhero = (func_reqbuyhero_t)(g_il2cppBase + 0x46d6e94); DobbyHook((void*)(g_il2cppBase + 0x4848cec), (void*)hook_4848cec, (void**)&orig_4848cec); g_req_refresh = (func_req_refresh_t)(g_il2cppBase + 0x483fce4); DobbyHook((void*)(g_il2cppBase + 0xAE25E80), (void*)hook_SendWillRenderCanvases, (void**)&orig_SendWillRenderCanvases); DobbyHook((void*)(g_il2cppBase + 0x3Cd6058), (void*)hook_3Cd6058, (void**)&orig_3Cd6058); DobbyHook((void*)(g_il2cppBase + 0x3cd3f80), (void*)hook_set_Money, (void**)&orig_set_Money); DobbyHook((void*)(g_il2cppBase + 0x3cd2d18), (void*)hook_set_Level, (void**)&orig_set_Level); DobbyHook((void*)(g_il2cppBase + 0x3d7c338), (void*)hook_TurnStart, (void**)&orig_TurnStart); DobbyHook((void*)(g_il2cppBase + 0x3fc3c70), (void*)hook_set_IsGameEnd, (void**)&orig_set_IsGameEnd); DobbyHook((void*)(g_il2cppBase + 0x3f7f704), (void*)hook_UpdateTurnStart, (void**)&orig_UpdateTurnStart); DobbyHook((void*)(g_il2cppBase + 0x4440520), (void*)hook_InitTurnDropCfg, (void**)&orig_InitTurnDropCfg); DobbyHook((void*)(g_il2cppBase + 0x6A67e48), (void*)hook_6A67e48, (void**)&orig_6A67e48); std::thread(BackgroundDataUpdateThread).detach(); } void LoadConfig() { std::ifstream in(g_configPath); if (!in.is_open()) return; std::string line; while (std::getline(in, line)) { size_t pos = line.find('='); if (pos == std::string::npos) continue; std::string k = line.substr(0, pos), v = line.substr(pos + 1); if (k == "playersX") { g_level_X = std::stof(v); g_warn_X = std::stof(v) + 150.0f * g_autoScale; continue; } if (k == "playersY") { g_level_Y = std::stof(v); g_warn_Y = std::stof(v); continue; } for (const auto& entry : g_configTable) { if (k == entry.key) { try { if (entry.type == C_BOOL) *(bool*)entry.ptr = (v == "1"); else if (entry.type == C_INT) *(int*)entry.ptr = std::stoi(v); else if (entry.type == C_FLOAT) *(float*)entry.ptr = std::stof(v); else if (entry.type == C_STRING) *(std::string*)entry.ptr = v; } catch (...) {} break; } } } in.close(); g_auto_buy_heroes.clear(); std::stringstream ss(g_auto_buy_heroes_str); std::string item; while (std::getline(ss, item, ',')) { if (!item.empty()) { try { g_auto_buy_heroes.insert(std::stoi(item)); } catch (...) {} } } g_hero_equip_bindings.clear(); std::stringstream ss_binds(g_hero_equip_bindings_str); std::string item_bind; while(std::getline(ss_binds, item_bind, ';')) { if(item_bind.empty()) continue; size_t colon_pos = item_bind.find(':'); if(colon_pos != std::string::npos) { try { int hId = std::stoi(item_bind.substr(0, colon_pos)); std::string eqs = item_bind.substr(colon_pos + 1); std::stringstream ss_eqs(eqs); std::string eq; while(std::getline(ss_eqs, eq, ',')) { if(!eq.empty()) g_hero_equip_bindings[hId].push_back(std::stoi(eq)); } } catch (...) {} } } g_hero_equip_priority.clear(); std::stringstream ss_prio(g_hero_equip_priority_str); std::string item_prio; while(std::getline(ss_prio, item_prio, ';')) { if(item_prio.empty()) continue; size_t colon_pos = item_prio.find(':'); if(colon_pos != std::string::npos) { try { int hId = std::stoi(item_prio.substr(0, colon_pos)); int prio = std::stoi(item_prio.substr(colon_pos + 1)); g_hero_equip_priority[hId] = prio; } catch (...) {} } } // [格子独立记录] 坐标读取 g_cell_equip_offset.clear(); std::stringstream ss_eqOff(g_cell_equip_offset_str); std::string item_eqOff; while(std::getline(ss_eqOff, item_eqOff, ';')) { if(item_eqOff.empty()) continue; size_t colon = item_eqOff.find(':'); size_t comma = item_eqOff.find(','); if(colon != std::string::npos && comma != std::string::npos) { try { int cIdx = std::stoi(item_eqOff.substr(0, colon)); float x = std::stof(item_eqOff.substr(colon + 1, comma - colon - 1)); float y = std::stof(item_eqOff.substr(comma + 1)); g_cell_equip_offset[cIdx] = ImVec2(x, y); } catch(...) {} } } g_cell_sell_offset.clear(); std::stringstream ss_sellOff(g_cell_sell_offset_str); std::string item_sellOff; while(std::getline(ss_sellOff, item_sellOff, ';')) { if(item_sellOff.empty()) continue; size_t colon = item_sellOff.find(':'); size_t comma = item_sellOff.find(','); if(colon != std::string::npos && comma != std::string::npos) { try { int cIdx = std::stoi(item_sellOff.substr(0, colon)); float x = std::stof(item_sellOff.substr(colon + 1, comma - colon - 1)); float y = std::stof(item_sellOff.substr(comma + 1)); g_cell_sell_offset[cIdx] = ImVec2(x, y); } catch(...) {} } } g_anim[1] = g_predict_enemy ? 1.0f : 0.0f; g_anim[2] = g_predict_hex ? 1.0f : 0.0f; g_anim[3] = g_esp_board ? 1.0f : 0.0f; g_anim[5] = g_esp_enemy_bench ? 1.0f : 0.0f; g_anim[7] = g_show_instant_quit_window ? 1.0f : 0.0f; g_anim[8] = g_boardLocked ? 1.0f : 0.0f; g_anim[9] = g_esp_level ? 1.0f : 0.0f; g_anim[10] = g_show_card_pool ? 1.0f : 0.0f; g_anim[11] = g_card_warning ? 1.0f : 0.0f; g_anim[12] = g_show_debug_window ? 1.0f : 0.0f; g_anim[17] = g_pool_warning_enable ? 1.0f : 0.0f; g_anim[18] = g_enable_skin ? 1.0f : 0.0f; g_anim[19] = g_enable_map_skin ? 1.0f : 0.0f; g_anim[20] = g_show_hero_list_window ? 1.0f : 0.0f; g_anim[23] = g_show_sell_window ? 1.0f : 0.0f; g_anim[24] = g_show_clear_bench_btn ? 1.0f : 0.0f; g_anim[25] = g_show_equip_ui_debug ? 1.0f : 0.0f; g_anim[26] = g_show_sell_ui_debug ? 1.0f : 0.0f; g_needUpdateFontSafe = true; } // ================================================================= // 绘制基础 UI 模块 // ================================================================= void UpdateFontHD(bool force = false) { ImGuiIO& io = ImGui::GetIO(); float screenH = (io.DisplaySize.y > 100.0f) ? io.DisplaySize.y : 2400.0f; g_autoScale = screenH / 1080.0f; float targetSize = std::clamp(20.0f * g_autoScale, 16.0f, 45.0f); if (!force && std::abs(targetSize - g_current_rendered_size) < 2.0f) return; ImGui_ImplOpenGL3_DestroyDeviceObjects(); io.Fonts->Clear(); g_mainFont = nullptr; g_hugeNumFont = nullptr; ImFontConfig configMain; configMain.OversampleH = 2; configMain.OversampleV = 2; configMain.PixelSnapH = false; ImFontConfig configNum; configNum.OversampleH = 2; configNum.OversampleV = 2; configNum.PixelSnapH = false; float mainResFactor = 1.5f; float numResFactor = (targetSize * 4.0f > 200.0f) ? (200.0f / targetSize) : 4.0f; static const ImWchar numRanges[] = { 0x0020, 0x00FF, 0 }; const char* fonts[] = { "/system/fonts/Miui-Regular.ttf", "/system/fonts/SysSans-Hans-Regular.ttf", "/system/fonts/NotoSansCJK-Regular.ttc", "/system/fonts/DroidSansFallback.ttf" }; bool loaded = false; for(const char* path : fonts) { if (access(path, R_OK) == 0) { g_mainFont = io.Fonts->AddFontFromFileTTF(path, targetSize * mainResFactor, &configMain, io.Fonts->GetGlyphRangesChineseSimplifiedCommon()); if (g_mainFont) g_mainFont->Scale = 1.0f / mainResFactor; g_hugeNumFont = io.Fonts->AddFontFromFileTTF(path, targetSize * numResFactor, &configNum, numRanges); if (g_hugeNumFont) g_hugeNumFont->Scale = 1.0f / numResFactor; loaded = true; break; } } if(!loaded || !g_mainFont) { g_mainFont = io.Fonts->AddFontDefault(); if (g_mainFont) g_mainFont->Scale = 1.0f / mainResFactor; } io.Fonts->Build(); ImGui_ImplOpenGL3_CreateDeviceObjects(); g_current_rendered_size = targetSize; } void HandleGridInteractionCore( float& out_x, float& out_y, float& out_scaleX, float& out_scaleY, float& t_x, float& t_y, float& t_scaleX, float& t_scaleY, bool& isDragging, bool& isScaling, ImVec2& dragOffset, ImVec2& scaleDragOffset, float h_dx, float h_dy, float c_dx, float c_dy, float hitMinX, float hitMinY, float hitMaxX, float hitMaxY, bool locked, bool* isOpen, bool enableScale, bool uniformScale) { ImGuiIO& io = ImGui::GetIO(); if (!locked) { ImVec2 p_scale(out_x + h_dx * out_scaleX, out_y + h_dy * out_scaleY); ImVec2 p_close(out_x + c_dx * out_scaleX, out_y + c_dy * out_scaleY); if (ImGui::IsMouseClicked(0)) { if (isOpen && ImLengthSqr(io.MousePos - p_close) < (1600.0f * g_autoScale * g_autoScale)) { *isOpen = false; return; } else if (enableScale && ImLengthSqr(io.MousePos - p_scale) < (400.0f * g_autoScale * g_autoScale)) { isScaling = true; scaleDragOffset = io.MousePos - ImVec2(t_x + h_dx * t_scaleX, t_y + h_dy * t_scaleY); } else if (ImRect(ImVec2(out_x + hitMinX * out_scaleX, out_y + hitMinY * out_scaleY), ImVec2(out_x + hitMaxX * out_scaleX, out_y + hitMaxY * out_scaleY)).Contains(io.MousePos)) { isDragging = true; dragOffset = ImVec2(t_x - io.MousePos.x, t_y - io.MousePos.y); } } if (isScaling && enableScale) { if (ImGui::IsMouseDown(0)) { ImVec2 tCenter = io.MousePos - scaleDragOffset; if (uniformScale) { float dx = tCenter.x - t_x; float dy = tCenter.y - t_y; float dist = sqrtf(dx * dx + dy * dy); float baseDist = sqrtf(h_dx * h_dx + h_dy * h_dy); t_scaleX = t_scaleY = std::clamp(dist / (baseDist > 0.01f ? baseDist : 0.01f), 0.05f, 10.0f); } else { t_scaleX = std::clamp((tCenter.x - t_x) / (h_dx > 0.01f ? h_dx : 0.01f), 0.05f, 10.0f); t_scaleY = std::clamp((tCenter.y - t_y) / (h_dy > 0.01f ? h_dy : 0.01f), 0.05f, 10.0f); } } else isScaling = false; } if (isDragging && !isScaling) { if (ImGui::IsMouseDown(0)) { t_x = io.MousePos.x + dragOffset.x; t_y = io.MousePos.y + dragOffset.y; } else isDragging = false; } } float smoothness = 1.0f - expf(-20.0f * io.DeltaTime); out_x = ImLerp(out_x, t_x, smoothness); out_y = ImLerp(out_y, t_y, smoothness); if (enableScale) { out_scaleX = ImLerp(out_scaleX, t_scaleX, smoothness); out_scaleY = uniformScale ? out_scaleX : ImLerp(out_scaleY, t_scaleY, smoothness); } } void HandleGridInteraction(float& ox, float& oy, float& os, float& tx, float& ty, float& ts, bool& d, bool& s, ImVec2& doff, ImVec2& soff, float hdx, float hdy, float cdx, float cdy, float mx, float my, float mX, float mY, bool l, bool* o, bool e) { HandleGridInteractionCore(ox, oy, os, os, tx, ty, ts, ts, d, s, doff, soff, hdx, hdy, cdx, cdy, mx, my, mX, mY, l, o, e, true); } void HandleGridInteractionXY(float& ox, float& oy, float& osX, float& osY, float& tx, float& ty, float& tsX, float& tsY, bool& d, bool& s, ImVec2& doff, ImVec2& soff, float hdx, float hdy, float cdx, float cdy, float mx, float my, float mX, float mY, bool l, bool* o) { HandleGridInteractionCore(ox, oy, osX, osY, tx, ty, tsX, tsY, d, s, doff, soff, hdx, hdy, cdx, cdy, mx, my, mX, mY, l, o, true, false); } void DrawScaleHandle(ImDrawList* d, ImVec2 p, bool isScaling) { d->AddCircleFilled(p, 10.0f * g_autoScale, IM_COL32(255, 215, 0, 240)); d->AddCircleFilled(p, 4.0f * g_autoScale, isScaling ? IM_COL32(0, 255, 180, 255) : IM_COL32(255, 255, 255, 255)); d->AddCircle(p, 14.0f * g_autoScale, IM_COL32(255, 215, 0, 150), 32, 2.0f * g_autoScale); } void DrawCloseHandle(ImDrawList* d, ImVec2 p, bool* isOpen) { if (!isOpen) return; ImGuiIO& io = ImGui::GetIO(); float cr = 13.0f * g_autoScale; static std::unordered_map hover_map; float cha = (hover_map[isOpen] = ImLerp(hover_map[isOpen], ImLengthSqr(io.MousePos - p) < (cr*cr * 2.5f) ? 1.0f : 0.0f, 1.0f - expf(-15.0f * io.DeltaTime))); d->AddCircleFilled(p, cr, IM_COL32(200 + 55*cha, 50, 50, 200 + 55*cha)); d->AddLine(p - ImVec2(cr*0.35f, cr*0.35f), p + ImVec2(cr*0.35f, cr*0.35f), IM_COL32_WHITE, 2.5f * g_autoScale); d->AddLine(p + ImVec2(cr*0.35f, -cr*0.35f), p - ImVec2(cr*0.35f, -cr*0.35f), IM_COL32_WHITE, 2.5f * g_autoScale); } void ColoredSeparator() { ImGuiWindow* win = ImGui::GetCurrentWindow(); if (win->SkipItems) return; float pad = 4.0f * g_autoScale * g_scale; ImRect bb(win->DC.CursorPos, win->DC.CursorPos + ImVec2(ImGui::GetContentRegionAvail().x, pad * 2.0f + 1.0f)); ImGui::ItemSize(bb); if (!ImGui::ItemAdd(bb, 0)) return; ImDrawList* dl = win->DrawList; float w = bb.GetWidth(); ImU32 fadeL = IM_COL32(0, 243, 255, 0); ImU32 fadeM1 = IM_COL32(0, 243, 255, 180); ImU32 fadeM2 = IM_COL32(255, 0, 128, 180); ImU32 fadeR = IM_COL32(255, 0, 128, 0); dl->AddRectFilledMultiColor(bb.Min + ImVec2(0, pad), bb.Min + ImVec2(w * 0.35f, pad + 1.0f), fadeL, fadeM1, fadeM1, fadeL); dl->AddRectFilledMultiColor(bb.Min + ImVec2(w * 0.35f, pad), bb.Min + ImVec2(w * 0.65f, pad + 1.0f), fadeM1, fadeM2, fadeM2, fadeM1); dl->AddRectFilledMultiColor(bb.Min + ImVec2(w * 0.65f, pad), bb.Min + ImVec2(w, pad + 1.0f), fadeM2, fadeR, fadeR, fadeM2); } static void ApplyMenuTheme(ImGuiStyle& style) { float s = g_autoScale; style.WindowRounding = 6.0f * s; style.ChildRounding = 6.0f * s; style.FrameRounding = 4.0f * s; style.PopupRounding = 6.0f * s; style.ScrollbarRounding = 4.0f * s; style.GrabRounding = 4.0f * s; style.TabRounding = 4.0f * s; style.ItemSpacing = ImVec2(10.0f * s, 10.0f * s); style.ItemInnerSpacing = ImVec2(8.0f * s, 6.0f * s); style.WindowPadding = ImVec2(16.0f * s, 14.0f * s); style.FramePadding = ImVec2(10.0f * s, 6.0f * s); style.WindowBorderSize = 1.0f; style.ChildBorderSize = 1.0f; style.ScrollbarSize = 12.0f * s; style.GrabMinSize = 20.0f * s; style.TabBorderSize = 0.0f; ImVec4* c = style.Colors; c[ImGuiCol_Text] = ImVec4(0.92f, 0.96f, 1.0f, 1.0f); c[ImGuiCol_TextDisabled] = ImVec4(0.40f, 0.50f, 0.65f, 1.0f); c[ImGuiCol_WindowBg] = ImVec4(0.05f, 0.07f, 0.11f, 0.96f); c[ImGuiCol_ChildBg] = ImVec4(0.08f, 0.10f, 0.16f, 0.88f); c[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.10f, 0.16f, 0.98f); c[ImGuiCol_Border] = ImVec4(0.0f, 0.95f, 1.0f, 0.35f); c[ImGuiCol_FrameBg] = ImVec4(0.10f, 0.14f, 0.22f, 1.0f); c[ImGuiCol_FrameBgHovered] = ImVec4(0.0f, 0.40f, 0.60f, 0.60f); c[ImGuiCol_FrameBgActive] = ImVec4(0.0f, 0.60f, 0.80f, 0.80f); c[ImGuiCol_TitleBg] = ImVec4(0.06f, 0.08f, 0.14f, 1.0f); c[ImGuiCol_TitleBgActive] = ImVec4(0.0f, 0.45f, 0.65f, 1.0f); c[ImGuiCol_TitleBgCollapsed] = ImVec4(0.06f, 0.08f, 0.14f, 0.75f); c[ImGuiCol_ScrollbarBg] = ImVec4(0.05f, 0.07f, 0.11f, 0.60f); c[ImGuiCol_ScrollbarGrab] = ImVec4(0.0f, 0.60f, 0.80f, 0.70f); c[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.0f, 0.95f, 1.0f, 0.85f); c[ImGuiCol_ScrollbarGrabActive] = ImVec4(1.0f, 0.0f, 0.45f, 1.0f); c[ImGuiCol_CheckMark] = ImVec4(0.0f, 0.95f, 1.0f, 1.0f); c[ImGuiCol_SliderGrab] = ImVec4(0.0f, 0.95f, 1.0f, 1.0f); c[ImGuiCol_SliderGrabActive] = ImVec4(1.0f, 0.0f, 0.45f, 1.0f); c[ImGuiCol_Button] = ImVec4(0.08f, 0.20f, 0.32f, 0.9f); c[ImGuiCol_ButtonHovered] = ImVec4(0.0f, 0.75f, 0.95f, 0.45f); c[ImGuiCol_ButtonActive] = ImVec4(1.0f, 0.0f, 0.45f, 0.75f); c[ImGuiCol_Header] = ImVec4(0.0f, 0.75f, 0.95f, 0.25f); c[ImGuiCol_HeaderHovered] = ImVec4(0.0f, 0.95f, 1.0f, 0.40f); c[ImGuiCol_HeaderActive] = ImVec4(1.0f, 0.0f, 0.45f, 0.60f); c[ImGuiCol_Tab] = ImVec4(0.08f, 0.12f, 0.20f, 1.0f); c[ImGuiCol_TabHovered] = ImVec4(0.0f, 0.95f, 1.0f, 0.40f); c[ImGuiCol_TabActive] = ImVec4(0.0f, 0.55f, 0.75f, 1.0f); c[ImGuiCol_TabUnfocused] = ImVec4(0.06f, 0.09f, 0.14f, 1.0f); c[ImGuiCol_TabUnfocusedActive] = ImVec4(0.08f, 0.14f, 0.22f, 1.0f); c[ImGuiCol_Separator] = ImVec4(0.0f, 0.95f, 1.0f, 0.25f); c[ImGuiCol_ResizeGrip] = ImVec4(0.0f, 0.95f, 1.0f, 0.30f); c[ImGuiCol_ResizeGripHovered] = ImVec4(0.0f, 0.95f, 1.0f, 0.70f); c[ImGuiCol_ResizeGripActive] = ImVec4(1.0f, 0.0f, 0.45f, 1.0f); } static void DrawMenuHeaderBanner(ImDrawList* dl, ImVec2 winPos, float winW, float fps) { float h = 52.0f * g_autoScale * g_scale; ImVec2 p0 = winPos; ImVec2 p1 = winPos + ImVec2(winW, h); dl->AddRectFilled(p0, p1, IM_COL32(14, 16, 22, 255), 14.0f * g_autoScale, ImDrawFlags_RoundCornersTop); dl->AddRectFilledMultiColor(p0, p1, IM_COL32(0, 180, 140, 35), IM_COL32(0, 120, 200, 25), IM_COL32(0, 120, 200, 10), IM_COL32(0, 180, 140, 35)); dl->AddLine(p0 + ImVec2(16.0f * g_autoScale, h - 1.0f), p0 + ImVec2(winW - 16.0f * g_autoScale, h - 1.0f), IM_COL32(0, 212, 170, 60), 1.0f); ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); float titleSz = 22.0f * g_autoScale * g_scale; const char* title = (const char*)u8"金铲铲助手"; ImVec2 titleSz2 = font->CalcTextSizeA(titleSz, FLT_MAX, 0.0f, title); dl->AddText(font, titleSz, p0 + ImVec2(18.0f * g_autoScale, (h - titleSz2.y) * 0.5f - 2.0f), IM_COL32(0, 212, 170, 255), title); float pulse = (sinf((float)ImGui::GetTime() * 3.0f) + 1.0f) * 0.5f; dl->AddCircleFilled(p0 + ImVec2(winW - 90.0f * g_autoScale, h * 0.5f), 4.0f * g_autoScale, IM_COL32(0, (int)(180 + 75 * pulse), (int)(140 + 60 * pulse), 255)); char fpsBuf[32]; snprintf(fpsBuf, sizeof(fpsBuf), "%.0f FPS", fps); float fpsSz = 14.0f * g_autoScale * g_scale; ImVec2 fpsTs = font->CalcTextSizeA(fpsSz, FLT_MAX, 0.0f, fpsBuf); dl->AddText(font, fpsSz, p0 + ImVec2(winW - 82.0f * g_autoScale, (h - fpsTs.y) * 0.5f), IM_COL32(140, 145, 160, 255), fpsBuf); } static bool BeginMenuSection(const char* title) { ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.13f, 0.14f, 0.19f, 0.90f)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.0f * g_autoScale * g_scale, 12.0f * g_autoScale * g_scale)); char childId[64]; snprintf(childId, sizeof(childId), "##sec_%s", title); ImGui::BeginChild(childId, ImVec2(-1, 0), false, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar); ImGui::TextColored(ImVec4(0.0f, 0.83f, 0.67f, 1.0f), "%s", title); ImGui::Spacing(); return true; } static void EndMenuSection() { ImGui::EndChild(); ImGui::PopStyleVar(); ImGui::PopStyleColor(); ImGui::Spacing(); } template void ModernAdjusterInternal(const char* label, T* v, T v_min, T v_max, T step, const char* format) { ImGuiWindow* win = ImGui::GetCurrentWindow(); const ImGuiStyle& style = ImGui::GetStyle(); ImGui::PushID(label); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.88f, 0.89f, 0.93f, 1.0f)); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetFrameHeight() * 2.2f + style.ItemInnerSpacing.x); ImGui::Text("%s", label); ImGui::PopStyleColor(); ImGui::SameLine(); float btn_sz = ImGui::GetFrameHeight(); float val_w = 40.0f * g_autoScale * g_scale; ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 15.0f * g_autoScale * g_scale); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.18f, 0.20f, 0.26f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.00f, 0.83f, 0.67f, 0.35f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.00f, 0.83f, 0.67f, 0.55f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.94f, 0.94f, 0.97f, 1.0f)); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f * g_autoScale); ImGui::PushButtonRepeat(true); if (ImGui::Button("-", ImVec2(btn_sz, btn_sz))) if (*v > v_min) *v -= step; ImGui::SameLine(); char buf[32]; std::is_same::value ? snprintf(buf, sizeof(buf), format, *v) : snprintf(buf, sizeof(buf), format, (float)(*v)); ImVec2 t_sz = ImGui::CalcTextSize(buf); ImVec2 val_pos = win->DC.CursorPos; win->DrawList->AddRectFilled(val_pos, val_pos + ImVec2(val_w, btn_sz), IM_COL32(30, 32, 42, 255), 6.0f * g_autoScale * g_scale); win->DrawList->AddText(ImGui::GetFont(), ImGui::GetFontSize(), val_pos + ImVec2((val_w - t_sz.x)*0.5f, (btn_sz - t_sz.y)*0.5f), IM_COL32(0, 212, 170, 255), buf); ImGui::Dummy(ImVec2(val_w, btn_sz)); ImGui::SameLine(); if (ImGui::Button("+", ImVec2(btn_sz, btn_sz))) if (*v < v_max) *v += step; ImGui::PopButtonRepeat(); ImGui::PopStyleVar(); ImGui::PopStyleColor(4); ImGui::PopID(); } void ModernNumberAdjuster(const char* label, int* v, int v_min, int v_max) { ModernAdjusterInternal(label, v, v_min, v_max, 1, "%d"); } void ModernFloatAdjuster(const char* label, float* v, float v_min, float v_max, float step) { ModernAdjusterInternal(label, v, v_min, v_max, step, "%.1f"); } void ModernSlider(const char* label, float* v, float v_min, float v_max) { ImGuiWindow* win = ImGui::GetCurrentWindow(); const ImGuiStyle& style = ImGui::GetStyle(); ImGui::PushID(label); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.88f, 0.89f, 0.93f, 1.0f)); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetFrameHeight() * 2.2f + style.ItemInnerSpacing.x); ImGui::Text("%s", label); ImGui::PopStyleColor(); ImGui::SameLine(); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 15.0f * g_autoScale * g_scale); ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.16f, 0.17f, 0.22f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.20f, 0.22f, 0.28f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.0f, 0.83f, 0.67f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, ImVec4(0.0f, 0.70f, 0.56f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.0f, 0.83f, 0.67f, 1.0f)); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f * g_autoScale); ImGui::PushItemWidth(180.0f * g_autoScale * g_scale); ImGui::SliderFloat("##sl", v, v_min, v_max, "%.2f"); ImGui::PopItemWidth(); ImGui::PopStyleVar(); ImGui::PopStyleColor(5); ImGui::PopID(); } bool ModernToggle(const char* label, bool* v, int idx) { ImGuiWindow* win = ImGui::GetCurrentWindow(); const ImGuiStyle& style = ImGui::GetStyle(); ImGuiID id = win->GetID(label); float h = ImGui::GetFrameHeight(); float w = h * 2.0f; const ImRect bb(win->DC.CursorPos, win->DC.CursorPos + ImVec2(w + style.ItemInnerSpacing.x + ImGui::CalcTextSize(label).x, h)); ImGui::ItemSize(bb, style.FramePadding.y); bool pressed = false; if (ImGui::ItemAdd(bb, id)) { bool hovered, held; if ((pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held))) *v = !(*v); } g_anim[idx] = ImLerp(g_anim[idx], *v ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime)); ImVec4 offCol = ImVec4(0.12f, 0.16f, 0.24f, 1.0f); ImVec4 onCol = ImVec4(0.0f, 0.95f, 1.0f, 1.0f); // 霓虹电光青 win->DrawList->AddRectFilled(bb.Min, bb.Min + ImVec2(w, h), ImGui::GetColorU32(ImLerp(offCol, onCol, g_anim[idx])), h*0.35f); if (g_anim[idx] > 0.01f) { win->DrawList->AddRect(bb.Min, bb.Min + ImVec2(w, h), IM_COL32(0, 243, 255, (int)(160 * g_anim[idx])), h*0.35f, 0, 1.5f); } ImVec2 hc = bb.Min + ImVec2(h*0.5f + g_anim[idx]*(w-h), h*0.5f); win->DrawList->AddCircleFilled(hc + ImVec2(0, 1.0f), h*0.5f - 2.5f * g_autoScale, IM_COL32(0, 0, 0, 80)); win->DrawList->AddCircleFilled(hc, h*0.5f - 2.5f * g_autoScale, IM_COL32(255, 255, 255, 255)); if (g_anim[idx] > 0.01f) { win->DrawList->AddCircle(hc, h*0.5f - 1.0f * g_autoScale, IM_COL32(0, 243, 255, (int)(255 * g_anim[idx])), 16, 1.5f); } ImVec4 textColor = ImLerp(ImVec4(0.55f, 0.65f, 0.78f, 1.0f), ImVec4(0.92f, 0.96f, 1.0f, 1.0f), g_anim[idx]); ImGui::PushStyleColor(ImGuiCol_Text, textColor); ImGui::RenderText(ImVec2(bb.Min.x + w + style.ItemInnerSpacing.x, bb.Min.y + (h - ImGui::GetFontSize()) * 0.5f), label); ImGui::PopStyleColor(); return pressed; } bool ModernAnimatedFolder(const char* label, bool* state) { ImGuiWindow* win = ImGui::GetCurrentWindow(); ImGuiID id = win->GetID(label); ImVec2 pos = win->DC.CursorPos; ImVec2 size(ImGui::GetContentRegionAvail().x, ImGui::GetFrameHeight() * 1.35f); const ImRect bb(pos, pos + size); ImGui::ItemSize(bb); bool hovered = false, held = false; if (ImGui::ItemAdd(bb, id)) if (ImGui::ButtonBehavior(bb, id, &hovered, &held)) *state = !(*state); float* p_anim = win->StateStorage.GetFloatRef(id, *state ? 1.0f : 0.0f); float anim = (*p_anim = ImLerp(*p_anim, *state ? 1.0f : 0.0f, 1.0f - expf(-18.0f * ImGui::GetIO().DeltaTime))); ImU32 bgCol = hovered ? IM_COL32(0, 243, 255, 25) : IM_COL32(14, 20, 32, 180); win->DrawList->AddRectFilled(bb.Min, bb.Max, bgCol, 6.0f * g_autoScale * g_scale); if (*state || anim > 0.01f || hovered) { win->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(0, 243, 255, (int)(80 * anim + (hovered ? 40 : 20))), 6.0f * g_autoScale * g_scale, 0, 1.0f); } float cx = bb.Min.x + 15.0f * g_autoScale * g_scale; float cy = bb.Min.y + size.y * 0.5f; float ang = anim * 1.5708f; float arr = 5.0f * g_autoScale * g_scale; ImU32 arrCol = ImGui::GetColorU32(ImLerp(ImVec4(0.45f, 0.55f, 0.70f, 1.0f), ImVec4(0.0f, 0.95f, 1.0f, 1.0f), anim)); win->DrawList->AddTriangleFilled(ImVec2(cx+cosf(ang)*arr, cy+sinf(ang)*arr), ImVec2(cx+cosf(ang+2.094f)*arr, cy+sinf(ang+2.094f)*arr), ImVec2(cx+cosf(ang-2.094f)*arr, cy+sinf(ang-2.094f)*arr), arrCol); ImFont* font = ImGui::GetFont(); float fSz = ImGui::GetFontSize(); ImU32 textCol = ImGui::GetColorU32(ImLerp(ImVec4(0.70f, 0.80f, 0.90f, 1.0f), ImVec4(0.92f, 0.96f, 1.0f, 1.0f), anim)); win->DrawList->AddText(font, fSz, ImVec2(cx + 25.0f * g_autoScale * g_scale, bb.Min.y + (size.y - fSz)*0.5f), textCol, label); if (*state) { ImGui::Indent(15.0f * g_autoScale * g_scale); return true; } return false; } void EndModernAnimatedFolder() { ImGui::Unindent(15.0f * g_autoScale * g_scale); } void ModernTierSelector(const char* label, bool* tierArray) { ImGuiWindow* win = ImGui::GetCurrentWindow(); const ImGuiStyle& style = ImGui::GetStyle(); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.88f, 0.89f, 0.93f, 1.0f)); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetFrameHeight() * 2.2f + style.ItemInnerSpacing.x); ImGui::Text("%s", label); ImGui::PopStyleColor(); ImGui::SameLine(); for (int i = 1; i <= 5; i++) { if (i > 1) ImGui::SameLine(0, 5.0f * g_autoScale); ImVec2 pos = win->DC.CursorPos; ImRect bb(pos, pos + ImVec2(ImGui::GetFrameHeight()*1.4f, ImGui::GetFrameHeight())); ImGui::ItemSize(bb); if (ImGui::ItemAdd(bb, win->GetID(tierArray + i))) { bool h, he; if (ImGui::ButtonBehavior(bb, win->GetID(tierArray+i), &h, &he)) tierArray[i] = !tierArray[i]; } static std::unordered_map anims_map; float& a = (anims_map[tierArray + i] = ImLerp(anims_map[tierArray + i], tierArray[i] ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime))); win->DrawList->AddRectFilled(bb.Min, bb.Max, IM_COL32(20, (int)(30+a*90), (int)(45+a*150), 255), 4.0f * g_autoScale); if (a > 0.01f) win->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(0, 243, 255, (int)(220*a)), 4.0f * g_autoScale, 0, 1.5f); char buf[8]; snprintf(buf, sizeof(buf), "%d", i); ImVec2 t_sz = ImGui::CalcTextSize(buf); win->DrawList->AddText(ImGui::GetFont(), ImGui::GetFontSize(), pos + ImVec2((bb.GetWidth() - t_sz.x)*0.5f, (bb.GetHeight() - t_sz.y)*0.5f), tierArray[i] ? IM_COL32(0, 243, 255, 255) : IM_COL32(100, 120, 145, 255), buf); } } // ================================================================= // 绘制 ESP / 功能模块 // ================================================================= void DrawSwipePaths() { if (!g_show_swipe_visuals) return; std::lock_guard lock(g_swipe_mutex); if (g_active_swipes.empty()) return; ImDrawList* d = ImGui::GetForegroundDrawList(); float dt = ImGui::GetIO().DeltaTime; ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); for (auto it = g_active_swipes.begin(); it != g_active_swipes.end(); ) { it->lifeTime -= dt; if (it->lifeTime <= 0.0f) { it = g_active_swipes.erase(it); continue; } float alpha = it->lifeTime / it->maxLife; ImU32 lineColor = IM_COL32(255, 200, 0, (int)(255 * alpha)); d->AddLine(it->start, it->end, lineColor, 3.0f * g_autoScale); d->AddCircleFilled(it->start, 12.0f * g_autoScale, IM_COL32(0, 255, 100, (int)(200 * alpha))); d->AddCircle(it->start, 16.0f * g_autoScale, IM_COL32(255, 255, 255, (int)(255 * alpha)), 0, 2.0f * g_autoScale); d->AddCircleFilled(it->end, 12.0f * g_autoScale, IM_COL32(255, 50, 50, (int)(200 * alpha))); d->AddCircle(it->end, 16.0f * g_autoScale, IM_COL32(255, 255, 255, (int)(255 * alpha)), 0, 2.0f * g_autoScale); float progress = 1.0f - (it->lifeTime / it->maxLife); ImVec2 currentPos = ImVec2(it->start.x + (it->end.x - it->start.x) * progress, it->start.y + (it->end.y - it->start.y) * progress); d->AddCircleFilled(currentPos, 8.0f * g_autoScale, IM_COL32(255, 255, 255, (int)(255 * alpha))); DrawTextWithStroke(d, font, 18.0f * g_autoScale, it->end + ImVec2(15, 15), IM_COL32(255, 255, 255, (int)(255*alpha)), IM_COL32(0,0,0,(int)(255*alpha)), "滑动释放"); ++it; } } void DrawEquipUIDebug() { bool show_equip = g_show_equip_ui_debug; bool show_sell = g_show_sell_ui_debug; if (!show_equip && !show_sell) return; ImDrawList* d = ImGui::GetForegroundDrawList(); ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); ImVec2 mouse = ImGui::GetIO().MousePos; bool mouseDown = ImGui::IsMouseDown(0); bool mouseClicked = ImGui::IsMouseClicked(0); static int dragging_idx = -1; // 0: 穿装绿圈, 1: 卖牌粉圈(起点), 2: 卖牌红圈(落点) static int dragging_cell_idx = -1; // 正在拖拽的独立格子索引 static ImVec2 drag_start_mouse; static float drag_start_off_x = 0; static float drag_start_off_y = 0; // 松手时自动保存配置 if (!mouseDown && dragging_idx != -1) { dragging_idx = -1; dragging_cell_idx = -1; g_save_timer = 1.5f; std::thread([]() { SaveConfig(); }).detach(); } auto snap = GetSnapshot(); float hitRadius = 45.0f * g_autoScale; // 触摸判定范围(大,防断触) float drawRadius = 10.0f * g_autoScale; // 实际画的圆圈尺寸 float dummyX = g_gl_width * 0.5f; float dummyY = g_gl_height * 0.5f; std::vector allHeroData; if (snap) { for (const auto& hero : snap->myLiveHeroes) { if (hero.screenPos.z > 0.01f) allHeroData.push_back(hero); } for (const auto& hero : snap->myBenchLiveHeroes) { if (hero.screenPos.z > 0.01f) allHeroData.push_back(hero); } } // ========================================== // 【核心修复 3:新增装备席物理坐标透视】 // ========================================== if (show_equip) { for (int i = 0; i < 20; i++) { int col = i / 10; int row = i % 10; float startX = (g_equip_ui_start_x + col * g_equip_ui_step_x) * g_autoScale; float startY = (g_equip_ui_start_y + row * g_equip_ui_step_y) * g_autoScale; ImVec2 pos(startX, startY); d->AddCircleFilled(pos, 15.0f * g_autoScale, IM_COL32(0, 200, 255, 80)); d->AddCircle(pos, 15.0f * g_autoScale, IM_COL32(0, 220, 255, 255), 0, 2.0f * g_autoScale); char buf[16]; snprintf(buf, sizeof(buf), "槽 %d", i); d->AddText(font, 12.0f * g_autoScale, pos - ImVec2(10 * g_autoScale, 8 * g_autoScale), IM_COL32(255, 255, 255, 255), buf); } } // ========================================== // 1. 穿拆装备可视化 (各个格子独立偏移,深绿色) // ========================================== if (show_equip) { if (allHeroData.empty()) { if (g_cell_equip_offset.find(0) == g_cell_equip_offset.end()) g_cell_equip_offset[0] = ImVec2(0.0f, -80.0f); ImVec2 targetPos(dummyX + g_cell_equip_offset[0].x * g_autoScale, dummyY + g_cell_equip_offset[0].y * g_autoScale); if (!g_boardLocked && mouseClicked && dragging_idx == -1 && ImLengthSqr(mouse - targetPos) < hitRadius * hitRadius) { dragging_idx = 0; dragging_cell_idx = 0; drag_start_mouse = mouse; drag_start_off_x = g_cell_equip_offset[0].x; drag_start_off_y = g_cell_equip_offset[0].y; } if (mouseDown && dragging_idx == 0 && dragging_cell_idx == 0) { g_cell_equip_offset[0].x = drag_start_off_x + (mouse.x - drag_start_mouse.x) / g_autoScale; g_cell_equip_offset[0].y = drag_start_off_y + (mouse.y - drag_start_mouse.y) / g_autoScale; targetPos = ImVec2(dummyX + g_cell_equip_offset[0].x * g_autoScale, dummyY + g_cell_equip_offset[0].y * g_autoScale); } d->AddCircleFilled(ImVec2(dummyX, dummyY), 4.0f * g_autoScale, IM_COL32(255,255,255,150)); d->AddText(font, 14.0f*g_autoScale, ImVec2(dummyX-30, dummyY-20), IM_COL32(255,255,255,150), "假人"); d->AddLine(ImVec2(dummyX, dummyY), targetPos, IM_COL32(0, 160, 0, 100), 2.0f * g_autoScale); d->AddCircleFilled(targetPos, drawRadius, IM_COL32(0, 160, 0, 230)); d->AddCircle(targetPos, drawRadius + 1.5f, IM_COL32(0, 220, 0, 255), 0, 1.5f * g_autoScale); d->AddText(font, 14.0f*g_autoScale, targetPos + ImVec2(drawRadius+2, -drawRadius-2), IM_COL32(0, 220, 0, 255), "格子0 穿装落点"); } else { // 点击判定 if (!g_boardLocked && mouseClicked && dragging_idx == -1) { for (const auto& hero : allHeroData) { if (g_cell_equip_offset.find(hero.cellIndex) == g_cell_equip_offset.end()) g_cell_equip_offset[hero.cellIndex] = ImVec2(0.0f, -80.0f); ImVec2 targetPos(hero.screenPos.x + g_cell_equip_offset[hero.cellIndex].x * g_autoScale, g_gl_height - hero.screenPos.y + g_cell_equip_offset[hero.cellIndex].y * g_autoScale); if (ImLengthSqr(mouse - targetPos) < hitRadius * hitRadius) { dragging_idx = 0; dragging_cell_idx = hero.cellIndex; drag_start_mouse = mouse; drag_start_off_x = g_cell_equip_offset[hero.cellIndex].x; drag_start_off_y = g_cell_equip_offset[hero.cellIndex].y; break; } } } // 拖拽应用 if (mouseDown && dragging_idx == 0 && dragging_cell_idx != -1) { g_cell_equip_offset[dragging_cell_idx].x = drag_start_off_x + (mouse.x - drag_start_mouse.x) / g_autoScale; g_cell_equip_offset[dragging_cell_idx].y = drag_start_off_y + (mouse.y - drag_start_mouse.y) / g_autoScale; } // 渲染 std::unordered_set drawn_cells; for (const auto& hero : allHeroData) { if (drawn_cells.count(hero.cellIndex)) continue; drawn_cells.insert(hero.cellIndex); if (g_cell_equip_offset.find(hero.cellIndex) == g_cell_equip_offset.end()) g_cell_equip_offset[hero.cellIndex] = ImVec2(0.0f, -80.0f); ImVec2 basePos(hero.screenPos.x, g_gl_height - hero.screenPos.y); ImVec2 targetPos(basePos.x + g_cell_equip_offset[hero.cellIndex].x * g_autoScale, basePos.y + g_cell_equip_offset[hero.cellIndex].y * g_autoScale); d->AddLine(basePos, targetPos, IM_COL32(0, 160, 0, 100), 2.0f * g_autoScale); d->AddCircleFilled(targetPos, drawRadius, IM_COL32(0, 160, 0, 230)); d->AddCircle(targetPos, drawRadius + 1.5f, IM_COL32(0, 220, 0, 255), 0, 1.5f * g_autoScale); char buf[32]; snprintf(buf, sizeof(buf), "格子%d穿装落点", hero.cellIndex); d->AddText(font, 14.0f*g_autoScale, targetPos + ImVec2(drawRadius+2, -drawRadius-2), IM_COL32(0, 220, 0, 255), buf); } } } // ========================================== // 2. 卖出英雄可视化 (各个格子独立起点 -> 统一全局落点) // ========================================== if (show_sell) { ImVec2 dropPos(g_sell_drop_x * g_autoScale, g_sell_drop_y * g_autoScale); // 点击判定 if (!g_boardLocked && mouseClicked && dragging_idx == -1) { if (ImLengthSqr(mouse - dropPos) < hitRadius * hitRadius) { // 优先点红圈 dragging_idx = 2; drag_start_mouse = mouse; drag_start_off_x = g_sell_drop_x; drag_start_off_y = g_sell_drop_y; } else { for (const auto& hero : allHeroData) { if (g_cell_sell_offset.find(hero.cellIndex) == g_cell_sell_offset.end()) g_cell_sell_offset[hero.cellIndex] = ImVec2(0.0f, -80.0f); ImVec2 grabPos(hero.screenPos.x + g_cell_sell_offset[hero.cellIndex].x * g_autoScale, g_gl_height - hero.screenPos.y + g_cell_sell_offset[hero.cellIndex].y * g_autoScale); if (ImLengthSqr(mouse - grabPos) < hitRadius * hitRadius) { dragging_idx = 1; dragging_cell_idx = hero.cellIndex; drag_start_mouse = mouse; drag_start_off_x = g_cell_sell_offset[hero.cellIndex].x; drag_start_off_y = g_cell_sell_offset[hero.cellIndex].y; break; } } } // 假人补充逻辑 if (dragging_idx == -1 && allHeroData.empty()) { if (g_cell_sell_offset.find(0) == g_cell_sell_offset.end()) g_cell_sell_offset[0] = ImVec2(0.0f, -80.0f); ImVec2 grabPos(dummyX + g_cell_sell_offset[0].x * g_autoScale, dummyY + g_cell_sell_offset[0].y * g_autoScale); if (ImLengthSqr(mouse - grabPos) < hitRadius * hitRadius) { dragging_idx = 1; dragging_cell_idx = 0; drag_start_mouse = mouse; drag_start_off_x = g_cell_sell_offset[0].x; drag_start_off_y = g_cell_sell_offset[0].y; } } } // 拖动计算 if (mouseDown) { if (dragging_idx == 1 && dragging_cell_idx != -1) { // 拖动独立格子起滑点 g_cell_sell_offset[dragging_cell_idx].x = drag_start_off_x + (mouse.x - drag_start_mouse.x) / g_autoScale; g_cell_sell_offset[dragging_cell_idx].y = drag_start_off_y + (mouse.y - drag_start_mouse.y) / g_autoScale; } else if (dragging_idx == 2) { // 拖动全局落点 g_sell_drop_x = drag_start_off_x + (mouse.x - drag_start_mouse.x) / g_autoScale; g_sell_drop_y = drag_start_off_y + (mouse.y - drag_start_mouse.y) / g_autoScale; dropPos = ImVec2(g_sell_drop_x * g_autoScale, g_sell_drop_y * g_autoScale); } } // 渲染假人 if (allHeroData.empty()) { ImVec2 grabPos(dummyX + g_cell_sell_offset[0].x * g_autoScale, dummyY + g_cell_sell_offset[0].y * g_autoScale); d->AddCircleFilled(ImVec2(dummyX, dummyY), 4.0f * g_autoScale, IM_COL32(255,255,255,150)); d->AddLine(grabPos, dropPos, IM_COL32(160, 0, 160, 150), 1.5f * g_autoScale); d->AddCircleFilled(grabPos, drawRadius, IM_COL32(160, 0, 160, 230)); d->AddCircle(grabPos, drawRadius + 1.5f, IM_COL32(220, 50, 220, 255), 0, 1.5f * g_autoScale); d->AddText(font, 14.0f*g_autoScale, grabPos + ImVec2(drawRadius+2, -drawRadius-2), IM_COL32(220, 50, 220, 255), "格子0卖出起滑"); } // 画单一落点 (深红圈) d->AddCircleFilled(dropPos, drawRadius * 1.5f, IM_COL32(180, 0, 0, 230)); d->AddCircle(dropPos, drawRadius * 1.5f + 1.5f, IM_COL32(255, 50, 50, 255), 0, 2.0f * g_autoScale); d->AddText(font, 14.0f*g_autoScale, dropPos + ImVec2(drawRadius*1.5f+2, -drawRadius*1.5f-2), IM_COL32(255, 50, 50, 255), "全局卖出落点"); // 渲染所有真实英雄独立格子起滑点 (深紫圈) 及连线 std::unordered_set drawn_cells; for (const auto& hero : allHeroData) { if (drawn_cells.count(hero.cellIndex)) continue; drawn_cells.insert(hero.cellIndex); if (g_cell_sell_offset.find(hero.cellIndex) == g_cell_sell_offset.end()) g_cell_sell_offset[hero.cellIndex] = ImVec2(0.0f, -80.0f); ImVec2 basePos(hero.screenPos.x, g_gl_height - hero.screenPos.y); ImVec2 grabPos(basePos.x + g_cell_sell_offset[hero.cellIndex].x * g_autoScale, basePos.y + g_cell_sell_offset[hero.cellIndex].y * g_autoScale); d->AddLine(grabPos, dropPos, IM_COL32(160, 0, 160, 100), 1.5f * g_autoScale); d->AddLine(basePos, grabPos, IM_COL32(160, 0, 160, 60), 1.0f * g_autoScale); d->AddCircleFilled(grabPos, drawRadius, IM_COL32(160, 0, 160, 230)); d->AddCircle(grabPos, drawRadius + 1.5f, IM_COL32(220, 50, 220, 255), 0, 1.5f * g_autoScale); char buf[32]; snprintf(buf, sizeof(buf), "格子%d卖出起滑", hero.cellIndex); d->AddText(font, 14.0f*g_autoScale, grabPos + ImVec2(drawRadius+2, -drawRadius-2), IM_COL32(220, 50, 220, 255), buf); } } } void DrawSingleSlotOverlay(const char* name, const char* rowLabel, bool& isOpen, float& posX, float& posY, float& scale, int totalSlots, const std::vector& data, ImU32 baseLabelColor) { static std::unordered_map alphas_raw; float deltaTime = ImGui::GetIO().DeltaTime; alphas_raw[name] = ImLerp(alphas_raw[name], isOpen ? 1.0f : 0.0f, 1.0f - expf(-20.0f * deltaTime)); float alpha = alphas_raw[name] * g_global_esp_alpha; if (alpha < 0.01f) return; ImDrawList* d = ImGui::GetForegroundDrawList(); static std::unordered_map t_xs, t_ys, t_scales; if (t_scales.find(name) == t_scales.end()) { t_xs[name] = posX; t_ys[name] = posY; t_scales[name] = scale; } static std::unordered_map draggings, scalings; static std::unordered_map dragOffsets, scaleDragOffsets; float baseSz = 60.0f * g_autoScale, labelW = 40.0f * g_autoScale; float totalW = labelW + totalSlots * baseSz, totalH = baseSz; HandleGridInteraction(posX, posY, scale, t_xs[name], t_ys[name], t_scales[name], draggings[name], scalings[name], dragOffsets[name], scaleDragOffsets[name], totalW + 20.0f * g_autoScale, totalH * 0.5f, -10.0f * g_autoScale, totalH * 0.5f, 0, 0, totalW, totalH, g_boardLocked, &isOpen, true); if (!g_boardLocked && alpha > 0.9f * g_global_esp_alpha) { DrawScaleHandle(d, ImVec2(posX + (totalW + 20.0f * g_autoScale) * scale, posY + totalH * 0.5f * scale), scalings[name]); DrawCloseHandle(d, ImVec2(posX - 10.0f * g_autoScale * scale, posY + totalH * 0.5f * scale), &isOpen); } ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); ImU32 labelColor = (baseLabelColor & 0x00FFFFFF) | ((int)(255 * alpha) << 24); float lFSz = 26.0f * g_autoScale * scale; ImVec2 lSz = font->CalcTextSizeA(lFSz, FLT_MAX, 0.0f, rowLabel); ImVec2 lPos = ImVec2(posX + (labelW * scale - lSz.x) * 0.5f, posY + (baseSz * scale - lSz.y) * 0.5f); DrawTextWithStroke(d, font, lFSz, lPos, labelColor, IM_COL32(0,0,0,(int)(255*alpha)), rowLabel); for (int i = 0; i < totalSlots; i++) { float cx = posX + labelW * scale + i * baseSz * scale + (baseSz * 0.5f) * scale; float cy = posY + (baseSz * 0.5f) * scale; float drawRadius = baseSz * 0.5f * scale; ImU32 borderColor = IM_COL32(100, 100, 100, (int)(255 * alpha)); int matchedBaseId = -1, matchedCost = -1, matchedStarLevel = -1; for (const auto& hero : data) { if (hero.slotIndex == i) { matchedBaseId = hero.baseHeroId; matchedCost = hero.cost; matchedStarLevel = hero.starLevel; break; } } if (matchedCost != -1) { switch (matchedCost) { case 1: borderColor = IM_COL32(200, 200, 200, (int)(255 * alpha)); break; case 2: borderColor = IM_COL32(0, 255, 0, (int)(255 * alpha)); break; case 3: borderColor = IM_COL32(0, 200, 255, (int)(255 * alpha)); break; case 4: borderColor = IM_COL32(210, 50, 255, (int)(255 * alpha)); break; case 5: borderColor = IM_COL32(255, 215, 0, (int)(255 * alpha)); break; } } if (matchedBaseId != -1) { GLuint tex = GetHeroTexture(matchedBaseId); ImVec2 pMin = ImVec2(cx - drawRadius, cy - drawRadius); ImVec2 pMax = ImVec2(cx + drawRadius, cy + drawRadius); if (tex != 0) { d->AddImageRounded((ImTextureID)(intptr_t)tex, pMin, pMax, ImVec2(0,0), ImVec2(1,1), IM_COL32(255, 255, 255, (int)(255 * alpha)), 8.0f * g_autoScale * scale); } else { char idBuf[16]; snprintf(idBuf, sizeof(idBuf), "%d", matchedBaseId); float idFSz = 20.0f * g_autoScale * scale; ImVec2 idSz = font->CalcTextSizeA(idFSz, FLT_MAX, 0.0f, idBuf); d->AddText(font, idFSz, ImVec2(cx - idSz.x * 0.5f, cy - idSz.y * 0.5f), borderColor, idBuf); } char starBuf[16]; snprintf(starBuf, sizeof(starBuf), "%d星", matchedStarLevel); float fSzStar = 20.0f * g_autoScale * scale; ImVec2 tPos = pMin + ImVec2(4.0f * scale, 4.0f * scale); DrawTextWithStroke(d, font, fSzStar, tPos, borderColor, IM_COL32(0,0,0, (int)(255 * alpha)), starBuf); } d->AddRect(ImVec2(cx - drawRadius, cy - drawRadius), ImVec2(cx + drawRadius, cy + drawRadius), borderColor, 8.0f * g_autoScale * scale, 0, 2.5f * g_autoScale * scale); } } void DrawAutoEquipWindow() { if (!g_show_equip_window) return; static float alpha = 0.0f; alpha = ImLerp(alpha, g_show_equip_window ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime)); if (alpha < 0.01f) return; ImDrawList* d = ImGui::GetForegroundDrawList(); ImGuiIO& io = ImGui::GetIO(); static float t_x = g_equip_win_x, t_y = g_equip_win_y, t_scale = g_equip_win_scale; static bool first = true; if (first) { t_x = g_equip_win_x; t_y = g_equip_win_y; t_scale = g_equip_win_scale; first = false; } static bool isDragging = false, isScaling = false; static ImVec2 dragOffset, scaleDragOffset; float baseW = 260.0f * g_autoScale, baseH = 60.0f * g_autoScale; HandleGridInteraction(g_equip_win_x, g_equip_win_y, g_equip_win_scale, t_x, t_y, t_scale, isDragging, isScaling, dragOffset, scaleDragOffset, baseW + 20.0f * g_autoScale, baseH * 0.5f, -20.0f * g_autoScale, baseH * 0.5f, 0, 0, baseW, baseH, g_boardLocked, &g_show_equip_window, true); float curW = baseW * g_equip_win_scale, curH = baseH * g_equip_win_scale; ImRect bgRect(ImVec2(g_equip_win_x, g_equip_win_y), ImVec2(g_equip_win_x + curW, g_equip_win_y + curH)); if (!g_boardLocked && alpha > 0.9f) { DrawScaleHandle(d, ImVec2(g_equip_win_x + (baseW + 20.0f * g_autoScale) * g_equip_win_scale, g_equip_win_y + baseH * 0.5f * g_equip_win_scale), isScaling); DrawCloseHandle(d, ImVec2(g_equip_win_x - 20.0f * g_autoScale * g_equip_win_scale, g_equip_win_y + baseH * 0.5f * g_equip_win_scale), &g_show_equip_window); } float rounding = curH * 0.5f; d->AddRectFilled(bgRect.Min, bgRect.Max, IM_COL32(20, 25, 30, (int)(220 * alpha)), rounding); d->AddRect(bgRect.Min, bgRect.Max, IM_COL32(80, 85, 95, (int)(200 * alpha)), rounding, 0, 2.0f * g_autoScale * g_equip_win_scale); ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); float curBtnW = 110.0f * g_autoScale * g_equip_win_scale, curBtnH = 44.0f * g_autoScale * g_equip_win_scale; float gap = (curW - curBtnW * 2.0f) / 3.0f; ImVec2 btn1Pos = ImVec2(g_equip_win_x + gap, g_equip_win_y + (curH - curBtnH) * 0.5f); ImVec2 btn2Pos = ImVec2(g_equip_win_x + gap * 2.0f + curBtnW, g_equip_win_y + (curH - curBtnH) * 0.5f); auto drawBtn = [&](ImVec2 pos, const char* text, bool& active, int id) { ImRect bb(pos, pos + ImVec2(curBtnW, curBtnH)); bool hovered = bb.Contains(io.MousePos); if (hovered && ImGui::IsMouseClicked(0) && !isDragging && !isScaling) active = !active; static std::unordered_map anims; float target = active ? 1.0f : (hovered ? 0.6f : 0.0f); float a = (anims[id] = ImLerp(anims[id], target, 1.0f - expf(-20.0f * io.DeltaTime))); float rR = curBtnH * 0.5f; float hue = fmodf((float)ImGui::GetTime() * 0.5f + id * 0.3f, 1.0f); float r, g, b; ImGui::ColorConvertHSVtoRGB(hue, 0.7f, 1.0f, r, g, b); ImU32 neonColor = IM_COL32((int)(r * 255), (int)(g * 255), (int)(b * 255), (int)(200 * alpha)); if (active) { d->AddRectFilled(bb.Min, bb.Max, IM_COL32(0, 200, 150, (int)(255 * alpha)), rR); d->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 180, (int)(255 * alpha)), rR, 0, 2.5f * g_autoScale * g_equip_win_scale); } else { d->AddRectFilled(bb.Min, bb.Max, IM_COL32(40, 45, 55, (int)(255 * alpha)), rR); d->AddRect(bb.Min, bb.Max, neonColor, rR, 0, 2.0f * g_autoScale * g_equip_win_scale); } float fSz = 20.0f * g_autoScale * g_equip_win_scale; ImVec2 tSz = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, text); d->AddText(font, fSz, pos + ImVec2((curBtnW - tSz.x)*0.5f, (curBtnH - tSz.y)*0.5f), IM_COL32(255, 255, 255, (int)(255 * alpha)), text); }; drawBtn(btn1Pos, (const char*)u8"穿装备", g_auto_equip_active, 201); drawBtn(btn2Pos, (const char*)u8"拆装备", g_auto_unequip_active, 202); } void DrawHeroLinesESP(const GameSnapshot* snap) { if (!snap || !g_draw_hero_lines || snap->mainCamera == 0) return; ImDrawList* d = ImGui::GetBackgroundDrawList(); ImGuiIO& io = ImGui::GetIO(); ImVec2 screenCenter(io.DisplaySize.x * 0.5f, 0.0f); auto drawLine = [&](const LiveHeroData& hero) { if (hero.screenPos.z > 0.01f) { float imguiX = hero.screenPos.x; float imguiY = io.DisplaySize.y - hero.screenPos.y + g_esp_line_offset_y * g_autoScale; ImVec2 targetPos(imguiX, imguiY); d->AddLine(screenCenter, targetPos, IM_COL32(0, 255, 200, 150), 2.0f); d->AddCircleFilled(targetPos, 5.0f, IM_COL32(0, 255, 200, 255)); char buf[32]; snprintf(buf, sizeof(buf), "Hero: %d", hero.heroId); ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); DrawTextWithStroke(d, font, 18.0f * g_autoScale, targetPos + ImVec2(10.0f, -10.0f), IM_COL32(255,255,255,255), IM_COL32(0,0,0,255), buf); } }; for (const auto& hero : snap->myLiveHeroes) drawLine(hero); for (const auto& hero : snap->myBenchLiveHeroes) drawLine(hero); } void DrawAutoActionWindow() { if (!g_show_auto_window) return; static float alpha = 0.0f; alpha = ImLerp(alpha, g_show_auto_window ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime)); if (alpha < 0.01f) return; ImDrawList* d = ImGui::GetForegroundDrawList(); ImGuiIO& io = ImGui::GetIO(); static float t_x = g_auto_win_x, t_y = g_auto_win_y, t_scale = g_auto_win_scale; static bool first = true; if (first) { t_x = g_auto_win_x; t_y = g_auto_win_y; t_scale = g_auto_win_scale; first = false; } static bool isDragging = false, isScaling = false; static ImVec2 dragOffset, scaleDragOffset; float baseW = 260.0f * g_autoScale; float baseH = g_show_clear_bench_btn ? (130.0f * g_autoScale) : (60.0f * g_autoScale); HandleGridInteraction(g_auto_win_x, g_auto_win_y, g_auto_win_scale, t_x, t_y, t_scale, isDragging, isScaling, dragOffset, scaleDragOffset, baseW + 20.0f * g_autoScale, baseH * 0.5f, -20.0f * g_autoScale, baseH * 0.5f, 0, 0, baseW, baseH, g_boardLocked, &g_show_auto_window, true); float curW = baseW * g_auto_win_scale, curH = baseH * g_auto_win_scale; ImRect bgRect(ImVec2(g_auto_win_x, g_auto_win_y), ImVec2(g_auto_win_x + curW, g_auto_win_y + curH)); if (!g_boardLocked && alpha > 0.9f) { DrawScaleHandle(d, ImVec2(g_auto_win_x + (baseW + 20.0f * g_autoScale) * g_auto_win_scale, g_auto_win_y + baseH * 0.5f * g_auto_win_scale), isScaling); DrawCloseHandle(d, ImVec2(g_auto_win_x - 20.0f * g_autoScale * g_auto_win_scale, g_auto_win_y + baseH * 0.5f * g_auto_win_scale), &g_show_auto_window); } float rounding = 60.0f * g_autoScale * g_auto_win_scale * 0.3f; d->AddRectFilled(bgRect.Min, bgRect.Max, IM_COL32(20, 25, 30, (int)(220 * alpha)), rounding); d->AddRect(bgRect.Min, bgRect.Max, IM_COL32(80, 85, 95, (int)(200 * alpha)), rounding, 0, 2.0f * g_autoScale * g_auto_win_scale); ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); float curBtnW = 110.0f * g_autoScale * g_auto_win_scale, curBtnH = 44.0f * g_autoScale * g_auto_win_scale; float gap = (curW - curBtnW * 2.0f) / 3.0f; ImVec2 btn1Pos = ImVec2(g_auto_win_x + gap, g_auto_win_y + gap); ImVec2 btn2Pos = ImVec2(g_auto_win_x + gap * 2.0f + curBtnW, g_auto_win_y + gap); auto drawBtn = [&](ImVec2 pos, const char* text, bool& active, int id) { ImRect bb(pos, pos + ImVec2(curBtnW, curBtnH)); bool hovered = bb.Contains(io.MousePos); if (hovered && ImGui::IsMouseClicked(0) && !isDragging && !isScaling) active = !active; static std::unordered_map anims; float target = active ? 1.0f : (hovered ? 0.6f : 0.0f); float a = (anims[id] = ImLerp(anims[id], target, 1.0f - expf(-20.0f * io.DeltaTime))); float rR = curBtnH * 0.5f; float hue = fmodf((float)ImGui::GetTime() * 0.5f + id * 0.3f, 1.0f); float r, g, b; ImGui::ColorConvertHSVtoRGB(hue, 0.7f, 1.0f, r, g, b); ImU32 neonColor = IM_COL32((int)(r * 255), (int)(g * 255), (int)(b * 255), (int)(200 * alpha)); if (active) { d->AddRectFilled(bb.Min, bb.Max, IM_COL32(0, 200, 150, (int)(255 * alpha)), rR); d->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 180, (int)(255 * alpha)), rR, 0, 2.5f * g_autoScale * g_auto_win_scale); } else { d->AddRectFilled(bb.Min, bb.Max, IM_COL32(40, 45, 55, (int)(255 * alpha)), rR); d->AddRect(bb.Min, bb.Max, neonColor, rR, 0, 2.0f * g_autoScale * g_auto_win_scale); } float fSz = 20.0f * g_autoScale * g_auto_win_scale; ImVec2 tSz = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, text); d->AddText(font, fSz, pos + ImVec2((curBtnW - tSz.x)*0.5f, (curBtnH - tSz.y)*0.5f), IM_COL32(255, 255, 255, (int)(255 * alpha)), text); }; auto drawClickBtn = [&](ImVec2 pos, float w, float h, const char* text) -> bool { ImRect bb(pos, pos + ImVec2(w, h)); bool hovered = bb.Contains(io.MousePos); bool clicked = hovered && ImGui::IsMouseClicked(0) && !isDragging && !isScaling; bool held = hovered && ImGui::IsMouseDown(0); float rR = h * 0.5f; float alpha_factor = held ? 0.8f : (hovered ? 1.0f : 0.7f); ImU32 bgColor = IM_COL32(220, 60, 60, (int)(255 * alpha * alpha_factor)); d->AddRectFilled(bb.Min, bb.Max, bgColor, rR); d->AddRect(bb.Min, bb.Max, IM_COL32(255, 100, 100, (int)(255 * alpha)), rR, 0, 2.0f * g_autoScale * g_auto_win_scale); float fSz = 20.0f * g_autoScale * g_auto_win_scale; ImVec2 tSz = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, text); d->AddText(font, fSz, pos + ImVec2((w - tSz.x)*0.5f, (h - tSz.y)*0.5f), IM_COL32(255, 255, 255, (int)(255 * alpha)), text); return clicked; }; drawBtn(btn1Pos, (const char*)u8"自动刷新", g_auto_refresh, 101); drawBtn(btn2Pos, (const char*)u8"自动拿牌", g_auto_buy, 102); if (g_show_clear_bench_btn) { ImVec2 btn3Pos = ImVec2(g_auto_win_x + gap, g_auto_win_y + gap * 2.0f + curBtnH); if (drawClickBtn(btn3Pos, curW - gap * 2.0f, curBtnH, (const char*)u8"一键清空备战席")) { TriggerSellAllHeroes(); } } } void DrawInstantQuitWindow() { if (!g_show_instant_quit_window) return; static float alpha = 0.0f; alpha = ImLerp(alpha, g_show_instant_quit_window ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime)); if (alpha < 0.01f) return; ImDrawList* d = ImGui::GetForegroundDrawList(); ImGuiIO& io = ImGui::GetIO(); static float t_x = g_instant_win_x, t_y = g_instant_win_y, t_scale = g_instant_win_scale; static bool first = true; if (first) { t_x = g_instant_win_x; t_y = g_instant_win_y; t_scale = g_instant_win_scale; first = false; } static bool isDragging = false, isScaling = false; static ImVec2 dragOffset, scaleDragOffset; float baseW = 160.0f * g_autoScale, baseH = 60.0f * g_autoScale; HandleGridInteraction(g_instant_win_x, g_instant_win_y, g_instant_win_scale, t_x, t_y, t_scale, isDragging, isScaling, dragOffset, scaleDragOffset, baseW + 20.0f * g_autoScale, baseH * 0.5f, -20.0f * g_autoScale, baseH * 0.5f, 0, 0, baseW, baseH, g_boardLocked, &g_show_instant_quit_window, true); float curW = baseW * g_instant_win_scale, curH = baseH * g_instant_win_scale; ImRect bgRect(ImVec2(g_instant_win_x, g_instant_win_y), ImVec2(g_instant_win_x + curW, g_instant_win_y + curH)); if (!g_boardLocked && alpha > 0.9f) { DrawScaleHandle(d, ImVec2(g_instant_win_x + (baseW + 20.0f * g_autoScale) * g_instant_win_scale, g_instant_win_y + baseH * 0.5f * g_instant_win_scale), isScaling); DrawCloseHandle(d, ImVec2(g_instant_win_x - 20.0f * g_autoScale * g_instant_win_scale, g_instant_win_y + baseH * 0.5f * g_instant_win_scale), &g_show_instant_quit_window); } float rounding = curH * 0.5f; d->AddRectFilled(bgRect.Min, bgRect.Max, IM_COL32(20, 25, 30, (int)(220 * alpha)), rounding); d->AddRect(bgRect.Min, bgRect.Max, IM_COL32(80, 85, 95, (int)(200 * alpha)), rounding, 0, 2.0f * g_autoScale * g_instant_win_scale); ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); float curBtnW = 130.0f * g_autoScale * g_instant_win_scale, curBtnH = 44.0f * g_autoScale * g_instant_win_scale; ImVec2 btnPos = ImVec2(g_instant_win_x + (curW - curBtnW) * 0.5f, g_instant_win_y + (curH - curBtnH) * 0.5f); ImRect bb(btnPos, btnPos + ImVec2(curBtnW, curBtnH)); static int confirmState = 0; static float confirmTimer = 0.0f; if (confirmState == 1) { confirmTimer -= io.DeltaTime; if (confirmTimer <= 0.0f) confirmState = 0; } bool hovered = bb.Contains(io.MousePos); if (hovered && ImGui::IsMouseClicked(0) && !isDragging && !isScaling) { if (confirmState == 0) { confirmState = 1; confirmTimer = 3.0f; } else { g_Tasks.trigger_surrender.store(true); confirmState = 0; } } static float animA = 0.0f; animA = ImLerp(animA, confirmState == 1 ? 1.0f : 0.0f, 1.0f - expf(-15.0f * io.DeltaTime)); float rR = curBtnH * 0.5f; ImU32 normalBg = IM_COL32(40, 45, 55, (int)(255 * alpha)); ImU32 confirmBg = IM_COL32(200, 50, 50, (int)(255 * alpha)); float pulse = (sinf((float)ImGui::GetTime() * 8.0f) + 1.0f) * 0.5f; ImU32 normalBorder = hovered ? IM_COL32(150, 150, 150, (int)(255 * alpha)) : IM_COL32(100, 100, 100, (int)(200 * alpha)); ImU32 confirmBorder = IM_COL32(255, 100 + (int)(100*pulse), 100 + (int)(100*pulse), (int)(255 * alpha)); auto lerpColor = [](ImU32 c1, ImU32 c2, float t) -> ImU32 { int r = (int)( ((c1 >> 0) & 0xFF) * (1-t) + ((c2 >> 0) & 0xFF) * t ); int g = (int)( ((c1 >> 8) & 0xFF) * (1-t) + ((c2 >> 8) & 0xFF) * t ); int b = (int)( ((c1 >> 16) & 0xFF) * (1-t) + ((c2 >> 16) & 0xFF) * t ); int a = (int)( ((c1 >> 24) & 0xFF) * (1-t) + ((c2 >> 24) & 0xFF) * t ); return IM_COL32(r, g, b, a); }; d->AddRectFilled(bb.Min, bb.Max, lerpColor(normalBg, confirmBg, animA), rR); d->AddRect(bb.Min, bb.Max, lerpColor(normalBorder, confirmBorder, animA), rR, 0, 2.0f * g_autoScale * g_instant_win_scale); float fSz = 20.0f * g_autoScale * g_instant_win_scale; const char* textNormal = (const char*)u8"极速退游"; const char* textConfirm = (const char*)u8"确认退游?"; if (animA < 0.99f) { ImVec2 tSzNormal = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, textNormal); d->AddText(font, fSz, btnPos + ImVec2((curBtnW - tSzNormal.x)*0.5f, (curBtnH - tSzNormal.y)*0.5f), IM_COL32(255, 255, 255, (int)(255 * alpha * (1.0f - animA))), textNormal); } if (animA > 0.01f) { ImVec2 tSzConfirm = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, textConfirm); d->AddText(font, fSz, btnPos + ImVec2((curBtnW - tSzConfirm.x)*0.5f, (curBtnH - tSzConfirm.y)*0.5f), IM_COL32(255, 255, 255, (int)(255 * alpha * animA)), textConfirm); } } void DrawSellHeroWindow() { if (!g_show_sell_window) return; static float alpha = 0.0f; alpha = ImLerp(alpha, g_show_sell_window ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime)); if (alpha < 0.01f) return; ImDrawList* d = ImGui::GetForegroundDrawList(); ImGuiIO& io = ImGui::GetIO(); static float t_x = g_sell_win_x, t_y = g_sell_win_y, t_scale = g_sell_win_scale; static bool first = true; if (first) { t_x = g_sell_win_x; t_y = g_sell_win_y; t_scale = g_sell_win_scale; first = false; } static bool isDragging = false, isScaling = false; static ImVec2 dragOffset, scaleDragOffset; float baseW = 120.0f * g_autoScale, baseH = 44.0f * g_autoScale; HandleGridInteraction(g_sell_win_x, g_sell_win_y, g_sell_win_scale, t_x, t_y, t_scale, isDragging, isScaling, dragOffset, scaleDragOffset, baseW + 20.0f * g_autoScale, baseH * 0.5f, -20.0f * g_autoScale, baseH * 0.5f, 0, 0, baseW, baseH, g_boardLocked, &g_show_sell_window, true); float curW = baseW * g_sell_win_scale, curH = baseH * g_sell_win_scale; ImRect bgRect(ImVec2(g_sell_win_x, g_sell_win_y), ImVec2(g_sell_win_x + curW, g_sell_win_y + curH)); if (!g_boardLocked && alpha > 0.9f) { DrawScaleHandle(d, ImVec2(g_sell_win_x + (baseW + 20.0f * g_autoScale) * g_sell_win_scale, g_sell_win_y + baseH * 0.5f * g_sell_win_scale), isScaling); DrawCloseHandle(d, ImVec2(g_sell_win_x - 20.0f * g_autoScale * g_sell_win_scale, g_sell_win_y + baseH * 0.5f * g_sell_win_scale), &g_show_sell_window); } float rounding = curH * 0.5f; bool hovered = bgRect.Contains(io.MousePos); bool clicked = hovered && ImGui::IsMouseClicked(0) && !isDragging && !isScaling; bool held = hovered && ImGui::IsMouseDown(0); if (clicked) { TriggerSellAllHeroes(); } float alpha_factor = held ? 0.8f : (hovered ? 1.0f : 0.7f); ImU32 bgColor = IM_COL32(220, 50, 50, (int)(255 * alpha * alpha_factor)); d->AddRectFilled(bgRect.Min, bgRect.Max, bgColor, rounding); d->AddRect(bgRect.Min, bgRect.Max, IM_COL32(255, 100, 100, (int)(200 * alpha)), rounding, 0, 2.0f * g_autoScale * g_sell_win_scale); ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); float fSz = 20.0f * g_autoScale * g_sell_win_scale; const char* text = (const char*)u8"卖英雄"; ImVec2 tSz = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, text); d->AddText(font, fSz, ImVec2(g_sell_win_x + (curW - tSz.x)*0.5f, g_sell_win_y + (curH - tSz.y)*0.5f), IM_COL32(255, 255, 255, (int)(255 * alpha)), text); } void DrawHeroSelectionWindow() { if (!g_show_hero_list_window) return; ImGui::SetNextWindowPos(ImVec2(g_menuX + g_menuW + 10.0f * g_autoScale, g_menuY), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(480.0f * g_autoScale, 650.0f * g_autoScale), ImGuiCond_FirstUseEver); ImGuiWindowFlags flags = ImGuiWindowFlags_NoSavedSettings; if (ImGui::Begin((const char*)u8"自动拿牌图鉴 (随时配置,无需开局)", &g_show_hero_list_window, flags)) { static float h_scale = 1.0f; if (!ImGui::IsWindowCollapsed()) { float curW = ImGui::GetWindowSize().x; h_scale = std::clamp(curW / (480.0f * g_autoScale), 0.5f, 3.0f); } ImGui::SetWindowFontScale(h_scale); if (!g_pokedex_ready.load()) { ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), (const char*)u8"正在极速扫描本地图片数据,请稍候..."); } else if (g_local_pokedex.empty()) { ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), (const char*)u8"未扫描到有效的图片数据,请确认 HeroImages.h 已正确包含!"); } else { ImGuiStyle& style = ImGui::GetStyle(); float window_visible_x2 = ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x; ImDrawList* draw_list = ImGui::GetWindowDrawList(); static int active_hero_id = -1; static double press_time = 0.0; static bool just_opened_popup = false; auto renderHeroBlock = [&](int heroId, ImU32 costColor, bool bottomList) { ImGui::PushID(heroId + (bottomList ? 50000 : 0)); bool is_selected = g_auto_buy_heroes.count(heroId) > 0; auto& binds = g_hero_equip_bindings[heroId]; float smallEqBoxSz = 24.0f * g_autoScale * h_scale; float smEqSpaceX = 4.0f * g_autoScale * h_scale; float smEqSpaceY = 4.0f * g_autoScale * h_scale; float smallIconSz = 3.0f * smallEqBoxSz + 2.0f * smEqSpaceX; float prioH = 20.0f * g_autoScale * h_scale; float triHeight = 28.0f * g_autoScale * h_scale; bool is_expanded = g_expanded_equip_heroes.count(heroId) > 0; int cols_in_fold = 3; int eq_rows = is_expanded ? std::ceil((float)binds.size() / cols_in_fold) : 0; if (eq_rows == 0 && is_expanded) eq_rows = 1; ImVec2 startPos = ImGui::GetCursorScreenPos(); ImGui::BeginGroup(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); int prio = g_hero_equip_priority[heroId]; ImGui::PushID("prio_minus"); if (ImGui::InvisibleButton("-", ImVec2(smallIconSz * 0.3f, prioH))) { prio = std::max(0, prio - 1); g_hero_equip_priority[heroId] = prio; g_save_timer = 1.0f; SaveConfig(); } ImGui::PopID(); ImGui::SameLine(0, 0); ImGui::Dummy(ImVec2(smallIconSz * 0.4f, prioH)); ImGui::SameLine(0, 0); ImGui::PushID("prio_plus"); if (ImGui::InvisibleButton("+", ImVec2(smallIconSz * 0.3f, prioH))) { prio = std::min(5, prio + 1); g_hero_equip_priority[heroId] = prio; g_save_timer = 1.0f; SaveConfig(); } ImGui::PopID(); ImGui::PushID("hero"); ImGui::InvisibleButton("img", ImVec2(smallIconSz, smallIconSz)); bool isHovered = ImGui::IsItemHovered(); bool isActive = ImGui::IsItemActive(); bool isDeactivated = ImGui::IsItemDeactivated(); ImGui::PopID(); if (isActive) { if (active_hero_id != heroId) { active_hero_id = heroId; press_time = ImGui::GetTime(); just_opened_popup = false; } else if (!just_opened_popup && (ImGui::GetTime() - press_time) > 0.45) { ImGui::OpenPopup("EquipPopup"); just_opened_popup = true; } } else if (isDeactivated) { if (active_hero_id== heroId) { if (!just_opened_popup && (ImGui::GetTime() - press_time) <= 0.25) { if (bottomList) { g_auto_buy_heroes.erase(heroId); } else { if (is_selected) g_auto_buy_heroes.erase(heroId); else g_auto_buy_heroes.insert(heroId); } g_save_timer = 1.0f; SaveConfig(); } active_hero_id = -1; } } ImGui::PushID("tri"); if (ImGui::InvisibleButton("fold", ImVec2(smallIconSz, triHeight))) { if (is_expanded) g_expanded_equip_heroes.erase(heroId); else g_expanded_equip_heroes.insert(heroId); } ImGui::PopID(); ImGui::PopStyleVar(); if (is_expanded) { float eqH = 4.0f * g_autoScale * h_scale + eq_rows * (smallEqBoxSz + smEqSpaceY); ImGui::Dummy(ImVec2(smallIconSz, eqH)); } ImGui::EndGroup(); ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); ImU32 pColor; switch(prio) { case 5: pColor = IM_COL32(255, 30, 30, 255); break; case 4: pColor = IM_COL32(255, 120, 0, 255); break; case 3: pColor = IM_COL32(255, 200, 0, 255); break; case 2: pColor = IM_COL32(50, 200, 50, 255); break; case 1: pColor = IM_COL32(50, 150, 255, 255); break; default: pColor = IM_COL32(100, 100, 100, 255); break; } ImVec2 prioBarMin = startPos; ImVec2 prioBarMax = ImVec2(startPos.x + smallIconSz, startPos.y + prioH); draw_list->AddRectFilled(prioBarMin, prioBarMax, IM_COL32(40, 40, 40, 255), 4.0f * g_autoScale * h_scale, ImDrawFlags_RoundCornersTop); ImVec2 mBtnMax = ImVec2(prioBarMin.x + smallIconSz * 0.3f, prioBarMax.y); draw_list->AddRectFilled(prioBarMin, mBtnMax, IM_COL32(70, 70, 70, 255), 4.0f * g_autoScale * h_scale, ImDrawFlags_RoundCornersTopLeft); ImVec2 mTxtSz = font->CalcTextSizeA(16.0f*g_autoScale*h_scale, FLT_MAX, 0.0f, "-"); draw_list->AddText(font, 16.0f*g_autoScale*h_scale, ImVec2(prioBarMin.x + (smallIconSz*0.3f - mTxtSz.x)*0.5f, prioBarMin.y + (prioH - mTxtSz.y)*0.5f), IM_COL32_WHITE, "-"); ImVec2 pBtnMin = ImVec2(prioBarMin.x + smallIconSz * 0.7f, prioBarMin.y); draw_list->AddRectFilled(pBtnMin, prioBarMax, IM_COL32(70, 70, 70, 255), 4.0f * g_autoScale * h_scale, ImDrawFlags_RoundCornersTopRight); ImVec2 pTxtSz = font->CalcTextSizeA(16.0f*g_autoScale*h_scale, FLT_MAX, 0.0f, "+"); draw_list->AddText(font, 16.0f*g_autoScale*h_scale, ImVec2(pBtnMin.x + (smallIconSz*0.3f - pTxtSz.x)*0.5f, pBtnMin.y + (prioH - pTxtSz.y)*0.5f), IM_COL32_WHITE, "+"); char pText[8]; if (prio == 0) snprintf(pText, sizeof(pText), "无"); else snprintf(pText, sizeof(pText), "P%d", prio); ImVec2 tSz = font->CalcTextSizeA(14.0f*g_autoScale*h_scale, FLT_MAX, 0.0f, pText); draw_list->AddText(font, 14.0f*g_autoScale*h_scale, ImVec2(prioBarMin.x + smallIconSz*0.5f - tSz.x*0.5f, prioBarMin.y + (prioH - tSz.y)*0.5f), pColor, pText); ImVec2 heroMin = ImVec2(startPos.x, startPos.y + prioH); ImVec2 heroMax = ImVec2(heroMin.x + smallIconSz, heroMin.y + smallIconSz); GLuint tex = GetHeroTexture(heroId); if (tex) draw_list->AddImage((ImTextureID)(intptr_t)tex, heroMin, heroMax); else draw_list->AddRectFilled(heroMin, heroMax, IM_COL32(50,50,50,255)); if (is_selected) { draw_list->AddRect(heroMin, heroMax, IM_COL32(0, 255, 180, 255), 0, 0, 3.0f * g_autoScale * h_scale); draw_list->AddCircleFilled(ImVec2(heroMax.x - 10.0f*g_autoScale*h_scale, heroMax.y - 10.0f*g_autoScale*h_scale), 8.0f*g_autoScale*h_scale, IM_COL32(0, 200, 100, 255)); } else { draw_list->AddRect(heroMin, heroMax, costColor, 0, 0, 1.5f * g_autoScale * h_scale); } ImVec2 triMin = ImVec2(startPos.x, heroMax.y); ImVec2 triMax = ImVec2(startPos.x + smallIconSz, triMin.y + triHeight); draw_list->AddRectFilled(triMin, triMax, IM_COL32(35, 35, 45, 255), 4.0f * g_autoScale * h_scale, ImDrawFlags_RoundCornersBottom); float triSz = 6.0f * g_autoScale * h_scale; ImVec2 triC = ImVec2(triMin.x + smallIconSz * 0.5f, triMin.y + triHeight * 0.5f); if (is_expanded) { draw_list->AddTriangleFilled(triC - ImVec2(triSz, -triSz), triC + ImVec2(triSz, triSz), triC - ImVec2(0, triSz), IM_COL32_WHITE); } else { draw_list->AddTriangleFilled(triC - ImVec2(triSz, triSz), triC + ImVec2(triSz, -triSz), triC + ImVec2(0, triSz), IM_COL32(180, 180, 180, 255)); } if (is_expanded) { ImVec2 equipStartPos = ImVec2(startPos.x, triMax.y + 4.0f * g_autoScale * h_scale); int slots_to_draw = std::max((int)binds.size(), cols_in_fold); for(int k = 0; k < slots_to_draw; k++) { int row = k / cols_in_fold; int col = k % cols_in_fold; ImVec2 eqMin = ImVec2(equipStartPos.x + col * (smallEqBoxSz + smEqSpaceX), equipStartPos.y + row * (smallEqBoxSz + smEqSpaceY)); ImVec2 eqMax = ImVec2(eqMin.x + smallEqBoxSz, eqMin.y + smallEqBoxSz); if (k < binds.size()) { GLuint eqTex = GetEquipTexture(binds[k]); if (eqTex) draw_list->AddImageRounded((ImTextureID)(intptr_t)eqTex, eqMin, eqMax, ImVec2(0,0), ImVec2(1,1), IM_COL32_WHITE, 2.0f*h_scale); else draw_list->AddRectFilled(eqMin, eqMax, IM_COL32(100,100,100,255), 2.0f*h_scale); draw_list->AddRect(eqMin, eqMax, IM_COL32(150,150,150,200), 2.0f*h_scale, 0, 1.0f); char orderBuf[4]; snprintf(orderBuf, sizeof(orderBuf), "%d", k + 1); draw_list->AddText(font, 13.0f * g_autoScale * h_scale, eqMin + ImVec2(1.0f, 1.0f), IM_COL32(0,0,0,255), orderBuf); draw_list->AddText(font, 13.0f * g_autoScale * h_scale, eqMin, IM_COL32(0,255,150,255), orderBuf); } else { draw_list->AddRectFilled(eqMin, eqMax, IM_COL32(30,30,30,100), 2.0f*h_scale); draw_list->AddRect(eqMin, eqMax, IM_COL32(80,80,80,150), 2.0f*h_scale, 0, 1.0f); } } } ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10 * g_autoScale * h_scale, 10 * g_autoScale * h_scale)); if (ImGui::BeginPopup("EquipPopup")) { ImGui::SetWindowFontScale(h_scale); ImGui::TextColored(ImColor(costColor).Value, "为英雄 %d 选择推荐装备 (最多18件,按点击顺序定优先级)", heroId); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.2f, 0.2f, 0.8f)); if (ImGui::Button((const char*)u8"一键清空已选装备", ImVec2(-1, 30 * g_autoScale * h_scale))) { binds.clear(); g_save_timer = 1.0f; SaveConfig(); } ImGui::PopStyleColor(); ImGui::Separator(); if (!g_equip_pokedex_ready.load()) ImGui::TextColored(ImVec4(1,1,0,1), "扫描中..."); else { int cols = 8; for (size_t e = 0; e < g_local_equip_pokedex.size(); e++) { int equipId = g_local_equip_pokedex[e]; ImGui::PushID(equipId + 20000); GLuint equipTex = GetEquipTexture(equipId); ImVec2 btnSize = ImVec2(45 * g_autoScale * h_scale, 45 * g_autoScale * h_scale); auto it_bind = std::find(binds.begin(), binds.end(), equipId); bool is_equipped = (it_bind != binds.end()); int order_idx = is_equipped ? std::distance(binds.begin(), it_bind) + 1 : 0; if (is_equipped) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0.8f, 0.4f, 0.6f)); else ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); ImVec2 btnMin = ImGui::GetCursorScreenPos(); if (equipTex != 0) { if (ImGui::ImageButton("##eq2", (ImTextureID)(intptr_t)equipTex, btnSize)) { if (is_equipped) binds.erase(std::remove(binds.begin(), binds.end(), equipId), binds.end()); else if (binds.size() < 18) binds.push_back(equipId); g_save_timer = 1.0f; SaveConfig(); } } else { if (ImGui::Button(std::to_string(equipId).c_str(), btnSize)) { if (is_equipped) binds.erase(std::remove(binds.begin(), binds.end(), equipId), binds.end()); else if (binds.size() < 18) binds.push_back(equipId); g_save_timer = 1.0f; SaveConfig(); } } if (is_equipped) { char numBuf[4]; snprintf(numBuf, sizeof(numBuf), "%d", order_idx); draw_list->AddText(font, 16.0f * g_autoScale * h_scale, btnMin + ImVec2(4.0f, 4.0f), IM_COL32(0,0,0,255), numBuf); draw_list->AddText(font, 16.0f * g_autoScale * h_scale, btnMin + ImVec2(2.0f, 2.0f), IM_COL32(0,255,150,255), numBuf); } ImGui::PopStyleColor(); if ((e + 1) % cols != 0 && (e + 1) < g_local_equip_pokedex.size()) ImGui::SameLine(); ImGui::PopID(); } } ImGui::EndPopup(); } ImGui::PopStyleVar(); if (isHovered) { ImGui::SetTooltip((const char*)u8"单击选定/取消 | 长按改装备"); } float last_button_x2 = ImGui::GetItemRectMax().x; float next_button_x2 = last_button_x2 + style.ItemSpacing.x + smallIconSz; if (next_button_x2 < window_visible_x2) { ImGui::SameLine(); } }; for (int cost = 1; cost <= 5; cost++) { if (g_local_pokedex[cost].empty()) continue; ImU32 costColor; switch(cost) { case 1: costColor = IM_COL32(200, 200, 200, 255); break; case 2: costColor = IM_COL32(0, 255, 0, 255); break; case 3: costColor = IM_COL32(0, 200, 255, 255); break; case 4: costColor = IM_COL32(210, 50, 255, 255); break; case 5: costColor = IM_COL32(255, 215, 0, 255); break; default:costColor = IM_COL32(255, 255, 255, 255); break; } ImGui::PushStyleColor(ImGuiCol_Text, costColor); ImGui::Text("%d费英雄", cost); ImGui::PopStyleColor(); ImGui::Separator(); for (size_t i = 0; i < g_local_pokedex[cost].size(); i++) { renderHeroBlock(g_local_pokedex[cost][i], costColor, false); } ImGui::NewLine(); ImGui::Spacing(); } ImGui::Separator(); ImGui::TextColored(ImVec4(0.0f, 0.85f, 0.55f, 1.0f), (const char*)u8"★ 已选自动拿牌列表 (长按头像弹装 | 点击三角折叠)"); for (int cost = 1; cost <= 5; cost++) { std::vector selected_heroes; for (int heroId : g_local_pokedex[cost]) { if (g_auto_buy_heroes.count(heroId)) selected_heroes.push_back(heroId); } if (selected_heroes.empty()) continue; ImU32 costColor; switch(cost) { case 1: costColor = IM_COL32(200, 200, 200, 255); break; case 2: costColor = IM_COL32(0, 255, 0, 255); break; case 3: costColor = IM_COL32(0, 200, 255, 255); break; case 4: costColor = IM_COL32(210, 50, 255, 255); break; case 5: costColor = IM_COL32(255, 215, 0, 255); break; default:costColor = IM_COL32(255, 255, 255, 255); break; } ImGui::TextColored(ImColor(costColor), "[%d费]", cost); ImGui::SameLine(); for (size_t i = 0; i < selected_heroes.size(); i++) { renderHeroBlock(selected_heroes[i], costColor, true); } ImGui::NewLine(); } } } ImGui::End(); } void DrawShopESP(const GameSnapshot* snap) { if (!snap) return; // 已经移除了自己和敌方商店的单槽位透视调用,这里可以安全留空。 } void DrawBenchESP(const GameSnapshot* snap) { if (!snap) return; // 只保留敌方备战席的透视调用 DrawSingleSlotOverlay("EnemyBench", "敌方备战席", g_esp_enemy_bench, g_enemy_bench_X, g_enemy_bench_Y, g_enemy_bench_Scale, 9, snap->enemyBenchHeroes, IM_COL32(255, 100, 100, 255)); } void DrawPurePredictEnemy(const GameSnapshot* snap) { if (!snap) return; static float alpha_raw = 0.0f; alpha_raw = ImLerp(alpha_raw, g_predict_enemy ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime)); float alpha = alpha_raw * g_global_esp_alpha; if (alpha < 0.01f) return; ImDrawList* d = ImGui::GetForegroundDrawList(); static float t_x = g_enemy_X, t_y = g_enemy_Y, t_scale = g_enemy_Scale; static bool first = true; if (first) { t_x = g_enemy_X; t_y = g_enemy_Y; t_scale = g_enemy_Scale; first = false; } static bool isDragging = false, isScaling = false; static ImVec2 dragOffset, scaleDragOffset; int display_row = 1; bool isAi = false; if (snap->nextEnemyPlayerId != -1) { auto it = snap->players.find(snap->nextEnemyPlayerId); if (it != snap->players.end()) { display_row = it->second.rowIdx; isAi = it->second.isAi; } } HandleGridInteraction(g_enemy_X, g_enemy_Y, g_enemy_Scale, t_x, t_y, t_scale, isDragging, isScaling, dragOffset, scaleDragOffset, 25.0f * g_autoScale * 2.5f, 25.0f * g_autoScale, -15.0f * g_autoScale, 25.0f * g_autoScale, 0, 0, 50.0f * g_autoScale, 50.0f * g_autoScale, g_boardLocked, &g_predict_enemy, true); float drawRadius = 25.0f * g_autoScale * g_enemy_Scale; ImVec2 center(g_enemy_X + drawRadius, g_enemy_Y + drawRadius); float r, g, b; ImGui::ColorConvertHSVtoRGB(fmodf((float)ImGui::GetTime() * 0.5f, 1.0f), 0.8f, 1.0f, r, g, b); d->AddCircleFilled(center, drawRadius, IM_COL32((int)(r*255), (int)(g*255), (int)(b*255), (int)(255 * alpha)), 32); if (isAi) { ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); float fSz = 20.0f * g_autoScale * g_enemy_Scale; ImVec2 tSz = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, "Ai"); DrawTextWithStroke(d, font, fSz, center - ImVec2(tSz.x*0.5f, tSz.y*0.5f), IM_COL32(255, 255, 255, (int)(255 * alpha)), IM_COL32(0, 0, 0, (int)(255 * alpha)), "Ai"); } if (!g_boardLocked && alpha > 0.9f * g_global_esp_alpha) { DrawScaleHandle(d, ImVec2(g_enemy_X + 62.5f * g_autoScale * g_enemy_Scale, g_enemy_Y + 25.0f * g_autoScale * g_enemy_Scale), isScaling); DrawCloseHandle(d, ImVec2(g_enemy_X - 15.0f * g_autoScale * g_enemy_Scale, g_enemy_Y + 25.0f * g_autoScale * g_enemy_Scale), &g_predict_enemy); } } void DrawPurePredictHex(const GameSnapshot* snap) { if (!snap) return; static float alpha_raw = 0.0f; alpha_raw = ImLerp(alpha_raw, g_predict_hex ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime)); float alpha = alpha_raw * g_global_esp_alpha; if (alpha < 0.01f) return; ImDrawList* d = ImGui::GetForegroundDrawList(); static float t_x = g_hex_X, t_y = g_hex_Y, t_scale = g_hex_Scale; static bool first = true; if (first) { t_x = g_hex_X; t_y = g_hex_Y; t_scale = g_hex_Scale; first = false; } static bool isDragging = false, isScaling = false; static ImVec2 dragOffset, scaleDragOffset; int drawCount = snap->validHexCount > 0 ? snap->validHexCount : 3; if (drawCount > 4) drawCount = 4; // 安全限制 float bW = (drawCount * 60.0f - 15.0f) * g_autoScale; float bH = 45.0f * g_autoScale; HandleGridInteraction(g_hex_X, g_hex_Y, g_hex_Scale, t_x, t_y, t_scale, isDragging, isScaling, dragOffset, scaleDragOffset, bW + 15.0f * g_autoScale, bH * 0.5f, -15.0f * g_autoScale, bH * 0.5f, 0, 0, bW, bH, g_boardLocked, &g_predict_hex, true); ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); float fSz = 24.0f * g_autoScale * g_hex_Scale; for (int i = 0; i < drawCount; i++) { int rarity = (snap->validHexCount > 0) ? snap->cachedHexes[i] : 0; ImU32 boxColor = IM_COL32(100, 100, 100, (int)(150 * alpha)); const char* text = "?"; if (rarity == 1) { boxColor = IM_COL32(200, 200, 200, (int)(255 * alpha)); text = "银"; } else if (rarity == 2) { boxColor = IM_COL32(255, 215, 0, (int)(255 * alpha)); text = "金"; } else if (rarity == 3) { float hue = fmodf((float)ImGui::GetTime() * 0.5f + i * 0.2f, 1.0f); float r, g, b; ImGui::ColorConvertHSVtoRGB(hue, 0.8f, 1.0f, r, g, b); boxColor = IM_COL32((int)(r*255), (int)(g*255), (int)(b*255), (int)(255 * alpha)); text = "彩"; } ImVec2 pMin(g_hex_X + i * 60.0f * g_autoScale * g_hex_Scale, g_hex_Y); ImVec2 pMax(g_hex_X + i * 60.0f * g_autoScale * g_hex_Scale + 45.0f * g_autoScale * g_hex_Scale, g_hex_Y + 45.0f * g_autoScale * g_hex_Scale); d->AddRectFilled(pMin, pMax, IM_COL32(20, 20, 20, (int)(200 * alpha)), 6.0f * g_autoScale * g_hex_Scale); d->AddRect(pMin, pMax, boxColor, 6.0f * g_autoScale * g_hex_Scale, 0, 2.0f * g_autoScale * g_hex_Scale); ImVec2 tSz = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, text); ImVec2 tPos = ImVec2(pMin.x + (45.0f * g_autoScale * g_hex_Scale - tSz.x) * 0.5f, pMin.y + (45.0f * g_autoScale * g_hex_Scale - tSz.y) * 0.5f); DrawTextWithStroke(d, font, fSz, tPos, boxColor, IM_COL32(0,0,0,(int)(255*alpha)), text); } if (!g_boardLocked && alpha > 0.9f * g_global_esp_alpha) { DrawScaleHandle(d, ImVec2(g_hex_X + (bW + 15.0f * g_autoScale) * g_hex_Scale, g_hex_Y + bH * 0.5f * g_hex_Scale), isScaling); DrawCloseHandle(d, ImVec2(g_hex_X - 15.0f * g_autoScale * g_hex_Scale, g_hex_Y + bH * 0.5f * g_hex_Scale), &g_predict_hex); } } void DrawLevelOverlay(const GameSnapshot* snap) { if (!snap || !g_esp_level) return; static float alpha_raw = 0.0f; alpha_raw = ImLerp(alpha_raw, g_esp_level ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime)); float alpha = alpha_raw * g_global_esp_alpha; if (alpha < 0.01f) return; ImDrawList* d = ImGui::GetForegroundDrawList(); static float t_x = g_level_X, t_y = g_level_Y, t_scale = g_level_Scale; static bool first = true; if (first) { t_x = g_level_X; t_y = g_level_Y; t_scale = g_level_Scale; first = false; } static bool isDragging = false, isScaling = false; static ImVec2 dragOffset, scaleDragOffset; float bW = 160.0f * g_autoScale; float bH = g_level_Spacing * g_autoScale * 7.0f + 32.0f * g_autoScale; HandleGridInteraction(g_level_X, g_level_Y, g_level_Scale, t_x, t_y, t_scale, isDragging, isScaling, dragOffset, scaleDragOffset, bW + 20.0f * g_autoScale, bH * 0.5f, -20.0f * g_autoScale, bH * 0.5f, 0, 0, bW, bH, g_boardLocked, &g_esp_level, true); if (!g_boardLocked && alpha > 0.9f * g_global_esp_alpha) { DrawScaleHandle(d, ImVec2(g_level_X + (bW + 20.0f * g_autoScale) * g_level_Scale, g_level_Y + bH * 0.5f * g_level_Scale), isScaling); DrawCloseHandle(d, ImVec2(g_level_X - 20.0f * g_autoScale * g_level_Scale, g_level_Y + bH * 0.5f * g_level_Scale), &g_esp_level); } ImFont* font = ImGui::GetFont(); float fSz = ImGui::GetFontSize() * g_level_Scale; for (int i = 0; i < 8; i++) { float cy = g_level_Y + i * g_level_Spacing * g_autoScale * g_level_Scale; int cm = 0, cl = 0, cw = 0, clost = 0; for (const auto& pair : snap->players) { if (pair.second.rowIdx == i + 1) { cm = pair.second.money; cl = pair.second.level; cw = pair.second.winStreak; clost = pair.second.loseStreak; break; } } char buf[16]; snprintf(buf, sizeof(buf), "%d", cm); ImVec2 tSz = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, buf); ImVec2 posM = ImVec2(g_level_X + (40.0f * g_autoScale * g_level_Scale - tSz.x)*0.5f, cy + (32.0f * g_autoScale * g_level_Scale - tSz.y)*0.5f); DrawTextWithStroke(d, font, fSz, posM, IM_COL32(255, 215, 0, (int)(255 * alpha)), IM_COL32(0, 0, 0, (int)(255 * alpha)), buf); snprintf(buf, sizeof(buf), "%d", cl); tSz = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, buf); ImVec2 posL = ImVec2(g_level_X + 50.0f * g_autoScale * g_level_Scale + (40.0f * g_autoScale * g_level_Scale - tSz.x)*0.5f, cy + (32.0f * g_autoScale * g_level_Scale - tSz.y)*0.5f); DrawTextWithStroke(d, font, fSz, posL, IM_COL32(0, 255, 180, (int)(255 * alpha)), IM_COL32(0, 0, 0, (int)(255 * alpha)), buf); char streakBuf[16] = "-"; ImU32 streakColor = IM_COL32(100, 100, 100, (int)(200 * alpha)); if (cw > 0) { snprintf(streakBuf, sizeof(streakBuf), "%d", cw); streakColor = IM_COL32(255, 80, 50, (int)(255 * alpha)); } else if (clost > 0) { snprintf(streakBuf, sizeof(streakBuf), "%d", clost); streakColor = IM_COL32(50, 180, 255, (int)(255 * alpha)); } tSz = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, streakBuf); ImVec2 posS = ImVec2(g_level_X + 100.0f * g_autoScale * g_level_Scale + (60.0f * g_autoScale * g_level_Scale - tSz.x)*0.5f, cy + (32.0f * g_autoScale * g_level_Scale - tSz.y)*0.5f); DrawTextWithStroke(d, font, fSz, posS, streakColor, IM_COL32(0, 0, 0, (int)(255 * alpha)), streakBuf); } } void DrawWarningOverlay(const GameSnapshot* snap) { if (!snap || !g_card_warning) return; static float alpha_raw = 0.0f; alpha_raw = ImLerp(alpha_raw, g_card_warning ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime)); float alpha = alpha_raw * g_global_esp_alpha; if (alpha < 0.01f) return; ImDrawList* d = ImGui::GetForegroundDrawList(); static float t_x = g_warn_X, t_y = g_warn_Y, t_scale = g_warn_Scale; static bool first = true; if (first) { t_x = g_warn_X; t_y = g_warn_Y; t_scale = g_warn_Scale; first = false; } static bool isDragging = false, isScaling = false; static ImVec2 dragOffset, scaleDragOffset; float baseSz = 40.0f * g_autoScale, gap = 2.0f * g_autoScale; float bH = g_warn_Spacing * g_autoScale * 7.0f + 32.0f * g_autoScale; HandleGridInteraction(g_warn_X, g_warn_Y, g_warn_Scale, t_x, t_y, t_scale, isDragging, isScaling, dragOffset, scaleDragOffset, 60.0f * g_autoScale, bH * 0.5f, -220.0f * g_autoScale, bH * 0.5f, -200.0f * g_autoScale, 0, 40.0f * g_autoScale, bH, g_boardLocked, &g_card_warning, true); if (!g_boardLocked && alpha > 0.9f * g_global_esp_alpha) { DrawScaleHandle(d, ImVec2(g_warn_X + 60.0f * g_autoScale * g_warn_Scale, g_warn_Y + bH * 0.5f * g_warn_Scale), isScaling); DrawCloseHandle(d, ImVec2(g_warn_X - 220.0f * g_autoScale * g_warn_Scale, g_warn_Y + bH * 0.5f * g_warn_Scale), &g_card_warning); } ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); float fSz = 20.0f * g_autoScale * g_warn_Scale; for (const auto& w : snap->playerWarnings) { int displayRow = w.rowIdx - 1; float cy = g_warn_Y + displayRow * g_warn_Spacing * g_autoScale * g_warn_Scale; int drawIdx = 0; for (const auto& kv : w.heroCounts) { int baseId = kv.first, count = kv.second; if (count < g_warning_threshold) continue; int cost = 1; auto pit = snap->cardPoolData.find(baseId); if (pit != snap->cardPoolData.end()) { cost = pit->second.cost; } else { if (baseId >= 100) cost = (baseId / 10) % 10; else if (baseId >= 10) cost = baseId % 10; } if (cost < 1 || cost > 5 || !g_warning_tiers[cost]) continue; ImU32 borderColor = IM_COL32(255, 255, 255, (int)(255 * alpha)); switch(cost) { case 1: borderColor = IM_COL32(200, 200, 200, (int)(255 * alpha)); break; case 2: borderColor = IM_COL32(0, 255, 0, (int)(255 * alpha)); break; case 3: borderColor = IM_COL32(0, 200, 255, (int)(255 * alpha)); break; case 4: borderColor = IM_COL32(210, 50, 255, (int)(255 * alpha)); break; case 5: borderColor = IM_COL32(255, 215, 0, (int)(255 * alpha)); break; } float cx = g_warn_X - drawIdx * (baseSz + gap) * g_warn_Scale; ImVec2 pMin = ImVec2(cx, cy), pMax = ImVec2(cx + baseSz * g_warn_Scale, cy + baseSz * g_warn_Scale); GLuint tex = GetHeroTexture(baseId); if (tex) { d->AddImageRounded((ImTextureID)(intptr_t)tex, pMin, pMax, ImVec2(0,0), ImVec2(1,1), IM_COL32(255,255,255, (int)(255*alpha)), 6.0f * g_autoScale * g_warn_Scale); } else { d->AddRectFilled(pMin, pMax, IM_COL32(30,30,30, (int)(200*alpha)), 6.0f * g_autoScale * g_warn_Scale); char idBuf[16]; snprintf(idBuf, sizeof(idBuf), "%d", baseId); float idFSz = 14.0f * g_autoScale * g_warn_Scale; ImVec2 idSz = font->CalcTextSizeA(idFSz, FLT_MAX, 0.0f, idBuf); d->AddText(font, idFSz, ImVec2(cx + (baseSz*g_warn_Scale - idSz.x)*0.5f, cy + (baseSz*g_warn_Scale - idSz.y)*0.5f), borderColor, idBuf); } d->AddRect(pMin, pMax, borderColor, 6.0f * g_autoScale * g_warn_Scale, 0, 2.0f * g_autoScale * g_warn_Scale); char cntBuf[16]; snprintf(cntBuf, sizeof(cntBuf), "%d张", count); ImVec2 tSz = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, cntBuf); ImVec2 tPos = ImVec2(cx + (baseSz*g_warn_Scale - tSz.x)*0.5f, cy + baseSz*g_warn_Scale - tSz.y * 0.2f); DrawTextWithStroke(d, font, fSz, tPos, borderColor, IM_COL32(0,0,0, (int)(255*alpha)), cntBuf); drawIdx++; } } } void DrawBoard(const GameSnapshot* snap) { if (!snap || !g_esp_board) return; ImDrawList* d = ImGui::GetForegroundDrawList(); static float t_x = g_startX, t_y = g_startY, t_scale = g_boardManualScale; static bool firstFrame = true; if (firstFrame) { t_x = g_startX; t_y = g_startY; t_scale = g_boardManualScale; firstFrame = false; } static bool isDragging = false, isScaling = false; static ImVec2 dragOffset, scaleDragOffset; float baseSz = 38.0f * g_boardScale * g_autoScale; float baseXStep = baseSz * 1.73205f, baseYStep = baseSz * 1.5f; float hitMinY = -baseSz * 2.0f; HandleGridInteraction(g_startX, g_startY, g_boardManualScale, t_x, t_y, t_scale, isDragging, isScaling, dragOffset, scaleDragOffset, -6.5f * baseXStep, 3.5f * baseYStep, 0.5f * baseXStep, 3.5f * baseYStep, -7.5f * baseXStep, hitMinY, 1.0f * baseXStep, 4.0f * baseYStep, g_boardLocked, &g_esp_board, true); static float alpha_raw = 0.0f; alpha_raw = ImLerp(alpha_raw, g_esp_board ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime)); float final_alpha = alpha_raw * g_global_esp_alpha; if (final_alpha < 0.01f) return; float curSz = baseSz * g_boardManualScale, curXStep = baseXStep * g_boardManualScale, curYStep = baseYStep * g_boardManualScale; float time = (float)ImGui::GetTime(); if (!g_boardLocked) { DrawScaleHandle(d, ImVec2(g_startX - 6.5f * baseXStep * g_boardManualScale, g_startY + 3.5f * baseYStep * g_boardManualScale), isScaling); DrawCloseHandle(d, ImVec2(g_startX + 0.5f * baseXStep * g_boardManualScale, g_startY + 3.5f * baseYStep * g_boardManualScale), &g_esp_board); } std::string display_name = "未知"; if (g_predict_enemy && snap->nextEnemyPlayerId != -1) { auto it = snap->players.find(snap->nextEnemyPlayerId); if (it != snap->players.end() && !it->second.name.empty() && it->second.name != "Unknown") { display_name = it->second.name; } } if (g_predict_enemy) { std::string full_text = std::string("下场对手预测: ") + display_name; ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); float fontSize = 24.0f * g_autoScale * g_predictNameScale; ImVec2 tSz = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, full_text.c_str()); static float t_name_x = g_predictNameX, t_name_y = g_predictNameY, t_name_scale = g_predictNameScale; static bool first_name = true; if (first_name) { t_name_x = g_predictNameX; t_name_y = g_predictNameY; t_name_scale = g_predictNameScale; first_name = false; } static bool isNameDragging = false, isNameScaling = false; static ImVec2 nameDragOffset, nameScaleDragOffset; HandleGridInteraction(g_predictNameX, g_predictNameY, g_predictNameScale, t_name_x, t_name_y, t_name_scale, isNameDragging, isNameScaling, nameDragOffset, nameScaleDragOffset, tSz.x / g_predictNameScale + 20.0f * g_autoScale, tSz.y / g_predictNameScale * 0.5f, 0, 0, 0, 0, tSz.x / g_predictNameScale, tSz.y / g_predictNameScale, g_boardLocked, nullptr, true); if (!g_boardLocked && final_alpha > 0.9f * g_global_esp_alpha) { DrawScaleHandle(d, ImVec2(g_predictNameX + (tSz.x / g_predictNameScale + 20.0f * g_autoScale) * g_predictNameScale, g_predictNameY + (tSz.y / g_predictNameScale * 0.5f) * g_predictNameScale), isNameScaling); } ImVec2 textPos(g_predictNameX, g_predictNameY); float r, g, b; ImGui::ColorConvertHSVtoRGB(fmodf(time * 0.4f, 1.0f), 0.5f, 1.0f, r, g, b); DrawTextWithStroke(d, font, fontSize, textPos, IM_COL32((int)(r*255), (int)(g*255), (int)(b*255), (int)(255 * final_alpha)), IM_COL32(0,0,0, (int)(255 * final_alpha)), full_text.c_str()); } for(int y = 0; y < 4; y++) { for(int x = 0; x < 7; x++) { float cx = g_startX - x * curXStep - (y % 2 == 1 ? curXStep * 0.5f : 0); float cy = g_startY + y * curYStep; bool hasHero = false; int cellCost = -1, cellBaseHeroId = -1, cellStarLevel = 1, cellHeroId = -1, cellEquipCount = 0; if (g_predict_enemy) { for (const auto& hero : snap->currentEnemyHeroes) { if (hero.x == x && hero.y == y) { hasHero = true; cellHeroId = hero.heroId; cellEquipCount = hero.equipCount; FastParseHeroId(hero.heroId, cellBaseHeroId, cellCost, cellStarLevel); break; } } } ImU32 hexColor = IM_COL32(255, 255, 255, (int)(150 * final_alpha)); if (hasHero && cellCost != -1) { switch(cellCost) { case 1: hexColor = IM_COL32(200, 200, 200, (int)(220 * final_alpha)); break; case 2: hexColor = IM_COL32(0, 255, 0, (int)(220 * final_alpha)); break; case 3: hexColor = IM_COL32(0, 200, 255, (int)(220 * final_alpha)); break; case 4: hexColor = IM_COL32(210, 50, 255, (int)(220 * final_alpha)); break; case 5: hexColor = IM_COL32(255, 215, 0, (int)(220 * final_alpha)); break; default:hexColor = IM_COL32(255, 255, 255, (int)(220 * final_alpha)); break; } } if (hasHero) { GLuint tex = GetHeroTexture(cellBaseHeroId); float imgSz = curSz * 1.5f; ImVec2 pMin = ImVec2(cx - imgSz*0.5f, cy - imgSz*0.5f), pMax = ImVec2(cx + imgSz*0.5f, cy + imgSz*0.5f); if (tex != 0) d->AddImageRounded((ImTextureID)(intptr_t)tex, pMin, pMax, ImVec2(0,0), ImVec2(1,1), IM_COL32(255, 255, 255, (int)(255 * final_alpha)), imgSz*0.5f); else d->AddCircleFilled(ImVec2(cx, cy), curSz * 0.4f, IM_COL32(0,0,0, (int)(255 * final_alpha))); } ImVec2 pts[6]; for(int i = 0; i < 6; i++) { float a = (60.0f * i - 30.0f) * (M_PI / 180.0f); pts[i] = ImVec2(cx + curSz * cosf(a), cy + curSz * sinf(a)); } d->AddPolyline(pts, 6, hexColor, ImDrawFlags_Closed, 12.0f * g_autoScale * g_boardManualScale); if (hasHero) { char infoBuf[16]; snprintf(infoBuf, sizeof(infoBuf), "%d/%d", cellEquipCount, cellStarLevel); ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); float fSzInfo = 70.0f * g_autoScale * g_boardManualScale; ImVec2 tSzInfo = font->CalcTextSizeA(fSzInfo, FLT_MAX, 0.0f, infoBuf); ImVec2 tPosInfo = ImVec2(cx - tSzInfo.x * 0.5f, cy + curSz * 1.5f * 0.10f); DrawTextWithStroke(d, font, fSzInfo, tPosInfo, hexColor | ((int)(255 * final_alpha) << 24), IM_COL32(0,0,0, (int)(255 * final_alpha)), infoBuf); } } } } void DrawCardPool(const GameSnapshot* snap) { if (!snap) return; static float alpha_raw = 0.0f; alpha_raw = ImLerp(alpha_raw, g_show_card_pool ? 1.0f : 0.0f, 1.0f - expf(-20.0f * ImGui::GetIO().DeltaTime)); float final_alpha = alpha_raw * g_global_esp_alpha; if (final_alpha < 0.01f) return; ImDrawList* d = ImGui::GetForegroundDrawList(); ImGuiIO& io = ImGui::GetIO(); std::vector normalGroups[6]; std::vector warningGroups[6]; for (const auto& pair : snap->cardPoolData) { int cost = pair.second.cost; if (cost >= 1 && cost <= 5 && g_show_pool_tiers[cost]) { if (g_pool_warning_enable && g_pool_warning_tiers[cost] && pair.second.remaining <= g_pool_warning_count) { warningGroups[cost].push_back(pair.second); } else { normalGroups[cost].push_back(pair.second); } } } for(int c = 1; c <= 5; c++) { std::sort(normalGroups[c].begin(), normalGroups[c].end(), [](const CardInfo& a, const CardInfo& b) { return a.heroId < b.heroId; }); std::sort(warningGroups[c].begin(), warningGroups[c].end(), [](const CardInfo& a, const CardInfo& b) { return a.heroId < b.heroId; }); } static float t_x = g_cardPoolX, t_y = g_cardPoolY, t_scaleX = g_cardPoolScaleX, t_scaleY = g_cardPoolScaleY; static bool first = true; if (first) { t_x = g_cardPoolX; t_y = g_cardPoolY; t_scaleX = g_cardPoolScaleX; t_scaleY = g_cardPoolScaleY; first = false; } static float current_cols = g_card_pool_cols; current_cols = ImLerp(current_cols, (float)g_card_pool_cols, 1.0f - expf(-15.0f * io.DeltaTime)); int draw_cols = std::max(1, (int)std::ceil(current_cols)); float baseImgSz = 45.0f * g_autoScale, gap = 5.0f * g_autoScale; int total_rows = 0; int max_items_in_row = 0; for(int cost = 1; cost <= 5; cost++) { int n_size = normalGroups[cost].size(); int w_size = warningGroups[cost].size(); if(n_size == 0 && w_size == 0) continue; if (n_size > 0) { total_rows += std::ceil((float)n_size / draw_cols); max_items_in_row = std::max(max_items_in_row, std::min(n_size, draw_cols)); } if (w_size > 0) { total_rows += std::ceil((float)w_size / draw_cols); max_items_in_row = std::max(max_items_in_row, std::min(w_size, draw_cols)); } } if(total_rows == 0) return; float totalW = max_items_in_row * baseImgSz + std::max(0, max_items_in_row - 1) * gap; float totalH = total_rows * baseImgSz + std::max(0, total_rows - 1) * gap; static bool isDragging = false, isScaling = false; static ImVec2 dragOffset, scaleDragOffset; if (g_show_card_pool) { HandleGridInteractionXY(g_cardPoolX, g_cardPoolY, g_cardPoolScaleX, g_cardPoolScaleY, t_x, t_y, t_scaleX, t_scaleY, isDragging, isScaling, dragOffset, scaleDragOffset, totalW + 10.0f * g_autoScale, totalH + 10.0f * g_autoScale, -15.0f * g_autoScale, -15.0f * g_autoScale, -15.0f * g_autoScale, -15.0f * g_autoScale, totalW + 15.0f * g_autoScale, totalH + 15.0f * g_autoScale, g_boardLocked, &g_show_card_pool); } if (!g_boardLocked && final_alpha > 0.9f * g_global_esp_alpha) { DrawScaleHandle(d, ImVec2(g_cardPoolX + (totalW + 10.0f * g_autoScale) * g_cardPoolScaleX, g_cardPoolY + (totalH + 10.0f * g_autoScale) * g_cardPoolScaleY), isScaling); DrawCloseHandle(d, ImVec2(g_cardPoolX - 15.0f * g_autoScale * g_cardPoolScaleX, g_cardPoolY - 15.0f * g_autoScale * g_cardPoolScaleY), &g_show_card_pool); } ImFont* font = g_mainFont ? g_mainFont : ImGui::GetFont(); float avgScale = (g_cardPoolScaleX + g_cardPoolScaleY) * 0.5f, fSz = 18.0f * g_autoScale * avgScale; float fa = final_alpha * g_cardPoolAlpha; int aInt = (int)(255 * fa); if (aInt <= 0) return; int current_row_offset = 0; for(int cost = 1; cost <= 5; cost++) { if(normalGroups[cost].empty() && warningGroups[cost].empty()) continue; ImU32 bcNormal; switch(cost) { case 1: bcNormal = IM_COL32(169,169,169,aInt); break; case 2: bcNormal = IM_COL32(0,255,0,aInt); break; case 3: bcNormal = IM_COL32(0,150,255,aInt); break; case 4: bcNormal = IM_COL32(200,0,255,aInt); break; case 5: bcNormal = IM_COL32(255,215,0,aInt); break; default:bcNormal = IM_COL32(255,255,255,aInt); } ImU32 bcWarning = IM_COL32(255, 50, 50, aInt); auto drawGroup = [&](const std::vector& group, ImU32 bc) { if(group.empty()) return; for (size_t i = 0; i < group.size(); i++) { int c = i % draw_cols; int r = current_row_offset + (i / draw_cols); const auto& card = group[i]; float x = g_cardPoolX + c * (baseImgSz * g_cardPoolScaleX + gap * g_cardPoolScaleX); float y = g_cardPoolY + r * (baseImgSz * g_cardPoolScaleY + gap * g_cardPoolScaleY); GLuint tex = GetHeroTexture(card.heroId); float round = 6.0f * g_autoScale * avgScale; if (tex != 0) d->AddImageRounded((ImTextureID)(intptr_t)tex, ImVec2(x, y), ImVec2(x + baseImgSz*g_cardPoolScaleX, y + baseImgSz*g_cardPoolScaleY), ImVec2(0,0), ImVec2(1,1), IM_COL32(255, 255, 255, aInt), round); else { d->AddRectFilled(ImVec2(x, y), ImVec2(x + baseImgSz*g_cardPoolScaleX, y + baseImgSz*g_cardPoolScaleY), IM_COL32(20, 20, 20, aInt), round); char idBuf[16]; snprintf(idBuf, sizeof(idBuf), "%d", card.heroId); float idFSz = 14.0f * g_autoScale * avgScale; ImVec2 idSz = font->CalcTextSizeA(idFSz, FLT_MAX, 0.0f, idBuf); d->AddText(font, idFSz, ImVec2(x + (baseImgSz*g_cardPoolScaleX - idSz.x)*0.5f, y + (baseImgSz*g_cardPoolScaleY - idSz.y)*0.5f), bc, idBuf); } float borderThick = (bc == bcWarning) ? (2.5f * g_autoScale * avgScale) : (1.0f * g_autoScale * avgScale); if (bc == bcWarning) { float pulse = (sinf((float)ImGui::GetTime() * 8.0f) + 1.0f) * 0.5f; bc = IM_COL32(255, 50 + (int)(100*pulse), 50 + (int)(100*pulse), aInt); d->AddRect(ImVec2(x, y), ImVec2(x + baseImgSz*g_cardPoolScaleX, y + baseImgSz*g_cardPoolScaleY), IM_COL32(255,0,0, (int)(aInt*0.4f)), round, 0, borderThick + 2.0f); } d->AddRect(ImVec2(x, y), ImVec2(x + baseImgSz*g_cardPoolScaleX, y + baseImgSz*g_cardPoolScaleY), bc, round, ImDrawFlags_RoundCornersAll, borderThick); char buf[32]; snprintf(buf, sizeof(buf), "%d/%d", card.remaining, card.total); ImVec2 tSz = font->CalcTextSizeA(fSz, FLT_MAX, 0.0f, buf); ImVec2 tPos = ImVec2(x + (baseImgSz*g_cardPoolScaleX - tSz.x)*0.5f, y + baseImgSz*g_cardPoolScaleY - tSz.y - 2.0f); ImU32 textColor = (bc == bcWarning) ? IM_COL32(255,100,100, aInt) : (bc | (aInt << 24)); DrawTextWithStroke(d, font, fSz, tPos, textColor, IM_COL32(0,0,0, aInt), buf); } current_row_offset += std::ceil((float)group.size() / draw_cols); }; drawGroup(normalGroups[cost], bcNormal); drawGroup(warningGroups[cost], bcWarning); } } void DrawDebugWindow(const GameSnapshot* snap) { if (!snap || !g_show_debug_window) return; ImGui::SetNextWindowSize(ImVec2(550 * g_autoScale, 600 * g_autoScale), ImGuiCond_FirstUseEver); if (ImGui::Begin((const char*)u8"调试日志 & 全功能偏移链监控", &g_show_debug_window)) { ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.5f, 1.0f), (const char*)u8"当前渲染帧率 (FPS): %.1f", ImGui::GetIO().Framerate); ImGui::Separator(); if (ImGui::BeginTabBar("DebugTabs")) { if (ImGui::BeginTabItem((const char*)u8"基址与调用次数监控")) { ImGui::BeginChild("BaseAndCallScroll", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), (const char*)u8"【核心基址】"); ImGui::Text("il2cpp Base: 0x%lX", g_il2cppBase); ImGui::Text("BattleModel: 0x%lX", snap->chessBattleModel); ImGui::Text("CSOGame: 0x%lX", snap->csoGame); ImGui::Text("Pool Base: 0x%lX", g_debug_pool_base.load()); ImGui::Text("Main Camera: 0x%lX", snap->mainCamera); ImGui::Separator(); ImGui::TextColored(ImVec4(0.2f, 0.8f, 1.0f, 1.0f), (const char*)u8"【函数调用计数 (Hook监控)】"); ImGui::Text("hook_3Cd6058: %d", g_debug_hook_3Cd6058_count); ImGui::Text("hook_set_Money: %d", g_debug_hook_set_Money_count); ImGui::Text("hook_set_Level: %d", g_debug_hook_set_Level_count); ImGui::Text("hook_TurnStart: %d", g_debug_TurnStartCalled); ImGui::Text("hook_set_IsGameEnd: %d", g_debug_set_IsGameEnd_count); ImGui::Text("hook_UpdateTurnStart: %d", g_debug_UpdateTurnStart_count); ImGui::Text("hook_InitTurnDropCfg: %d", g_debug_hook_InitTurnDropCfg_count); ImGui::Text("hook_6A67e48 (Pool): %d", g_debug_hook_6A67e48_count); ImGui::Text("hook_SendWillRenderCanvases: %d", g_debug_SendWillRenderCanvases_count); ImGui::Text("Background Threads Run: %d", g_debug_bg_thread_count); ImGui::EndChild(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem((const char*)u8"动态偏移链监控")) { ImGui::BeginChild("OffsetChainScroll", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); auto myIt = snap->players.find(snap->myPlayerId); uintptr_t myObj = (myIt != snap->players.end()) ? myIt->second.objAddr : 0; uintptr_t myLogicObj = (myIt != snap->players.end()) ? myIt->second.logicObjAddr : 0; ImGui::TextColored(ImVec4(0.2f, 1.0f, 0.5f, 1.0f), (const char*)u8"【我方商店链】"); if (myObj) { uintptr_t pBase = SAFE_READ_PTR(myObj, g_off_shop_base); uintptr_t p20 = pBase ? SAFE_READ_PTR(pBase, g_off_shop_p20) : 0; uintptr_t entries = p20 ? SAFE_READ_PTR(p20, g_off_shop_entries) : 0; ImGui::Text("PlayerObj: 0x%lX", myObj); ImGui::Text(" -> +0x%X (Base): 0x%lX", g_off_shop_base, pBase); ImGui::Text(" -> +0x%X (p20): 0x%lX", g_off_shop_p20, p20); ImGui::Text(" -> +0x%X (Entries): 0x%lX", g_off_shop_entries, entries); } else { ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), (const char*)u8"PlayerObj Not Found (等待进入对局)"); } ImGui::Separator(); ImGui::TextColored(ImVec4(0.2f, 1.0f, 0.5f, 1.0f), (const char*)u8"【我方备战席链】"); if (myObj) { uintptr_t pBase = SAFE_READ_PTR(myObj, g_off_bench_base); uintptr_t p20 = pBase ? SAFE_READ_PTR(pBase, g_off_shop_p20) : 0; uintptr_t entries = p20 ? SAFE_READ_PTR(p20, g_off_shop_entries) : 0; ImGui::Text("PlayerObj: 0x%lX", myObj); ImGui::Text(" -> +0x%X (Base): 0x%lX", g_off_bench_base, pBase); ImGui::Text(" -> +0x%X (p20): 0x%lX", g_off_shop_p20, p20); ImGui::Text(" -> +0x%X (Entries): 0x%lX", g_off_shop_entries, entries); } else { ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), (const char*)u8"PlayerObj Not Found (等待进入对局)"); } ImGui::Separator(); ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.2f, 1.0f), (const char*)u8"【卡池链 (Pool)】"); uintptr_t poolBase = g_debug_pool_base.load(); if (poolBase) { uintptr_t heroku = SAFE_READ_PTR(poolBase, g_off_pool_heroku); uintptr_t aheroku = heroku ? SAFE_READ_PTR(heroku, g_off_pool_Aheroku) : 0; uintptr_t entries1 = aheroku ? SAFE_READ_PTR(aheroku, g_off_pool_entries1) : 0; ImGui::Text("PoolBase: 0x%lX", poolBase); ImGui::Text(" -> +0x%X (Heroku): 0x%lX", g_off_pool_heroku, heroku); ImGui::Text(" -> +0x%X (AHeroku): 0x%lX", g_off_pool_Aheroku, aheroku); ImGui::Text(" -> +0x%X (Entries1): 0x%lX", g_off_pool_entries1, entries1); } else { ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), (const char*)u8"PoolBase Not Found (等待调用 hook_6A67e48)"); } ImGui::Separator(); ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), (const char*)u8"【装备链 (Equip)】"); if (myLogicObj) { uintptr_t pEquipBase = SAFE_READ_PTR(myLogicObj, g_off_equip_base); uintptr_t itemArr = pEquipBase ? SAFE_READ_PTR(pEquipBase, g_off_equip_arr) : 0; ImGui::Text("LogicObj: 0x%lX", myLogicObj); ImGui::Text(" -> +0x%X (EquipBase): 0x%lX", g_off_equip_base, pEquipBase); ImGui::Text(" -> +0x%X (EquipArr): 0x%lX", g_off_equip_arr, itemArr); } else { ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), (const char*)u8"LogicObj Not Found (等待进入对局)"); } ImGui::Separator(); ImGui::TextColored(ImVec4(0.8f, 0.5f, 1.0f, 1.0f), (const char*)u8"【英雄实体链 (Hero)】"); if (myLogicObj) { uintptr_t pDyn = SAFE_READ_PTR(myLogicObj, g_off_hero_dyn); uintptr_t heroArr = pDyn ? SAFE_READ_PTR(pDyn, g_off_hero_arr) : 0; ImGui::Text("LogicObj: 0x%lX", myLogicObj); ImGui::Text(" -> +0x%X (HeroDyn): 0x%lX", g_off_hero_dyn, pDyn); ImGui::Text(" -> +0x%X (HeroArr): 0x%lX", g_off_hero_arr, heroArr); } else { ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), (const char*)u8"LogicObj Not Found (等待进入对局)"); } ImGui::EndChild(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem((const char*)u8"3D追踪与装备偏移调试")) { auto InputHex = [](const char* label, int* v) { int step = 1, step_fast = 16; ImGui::InputScalar(label, ImGuiDataType_S32, v, &step, &step_fast, "%X", ImGuiInputTextFlags_CharsHexadecimal); }; ImGui::TextColored(ImVec4(1, 0.8f, 0.2f, 1), (const char*)u8"【装备栏偏移调节】"); InputHex("Equip Base (0x2e8)", &g_off_equip_base); InputHex("Equip Array (0x10)", &g_off_equip_arr); InputHex("Equip Count (0x18)", &g_off_equip_count); InputHex("Equip Head (0x20)", &g_off_equip_head); InputHex("Equip Step (0x8)", &g_off_equip_step); InputHex("Equip ID (0x14)", &g_off_equip_id); ImGui::Separator(); ImGui::TextColored(ImVec4(1, 0.8f, 0.2f, 1), (const char*)u8"【场上英雄实体与3D坐标偏移调节】"); InputHex("Hero Dyn Base (0x2b0)", &g_off_hero_dyn); InputHex("Hero Array (0x20)", &g_off_hero_arr); InputHex("Hero Count (0x18)", &g_off_hero_count); InputHex("Hero Head (0x20)", &g_off_hero_head); InputHex("Hero Step (0x8)", &g_off_hero_step); InputHex("Hero ID (0x108)", &g_off_hero_id); InputHex("UnitData (0x48)", &g_off_hero_unit); InputHex("BattleUnit (0x20)", &g_off_unit_battle); InputHex("Pos X (0x238)", &g_off_battle_posx); ImGui::Separator(); ImGui::TextColored(ImVec4(1, 0.5f, 0.5f, 1), (const char*)u8"【穿拆装备可视化校准】"); if (ImGui::Checkbox((const char*)u8"显示[穿拆装备]辅助圈 (直接在屏幕拖拽英雄上的绿圈)", &g_show_equip_ui_debug)) { if (g_show_equip_ui_debug) g_show_sell_ui_debug = false; } ImGui::Separator(); ImGui::TextColored(ImVec4(1, 0.5f, 0.8f, 1), (const char*)u8"【卖出英雄可视化校准】"); if (ImGui::Checkbox((const char*)u8"显示[卖出英雄]连线与圈 (拖拽英雄身上的粉圈或底部的红圈)", &g_show_sell_ui_debug)) { if (g_show_sell_ui_debug) g_show_equip_ui_debug = false; } ImGui::Separator(); ImGui::TextColored(ImVec4(0.8f, 1.0f, 0.8f, 1), (const char*)u8"【滑动动作控制】"); ImGui::Checkbox((const char*)u8"开启滑动动画视觉反馈", &g_show_swipe_visuals); ImGui::SliderInt((const char*)u8"按下后停留时间 (ms)", &g_swipe_down_delay_ms, 0, 100); ImGui::SliderInt((const char*)u8"滑动拖拽步数 (0为瞬间移动)", &g_swipe_steps, 0, 50); ImGui::SliderInt((const char*)u8"单步拖拽延迟 (ms)", &g_swipe_step_delay_ms, 0, 50); ImGui::SliderInt((const char*)u8"释放前停留时间 (ms)", &g_swipe_up_delay_ms, 0, 100); ImGui::SliderInt((const char*)u8"动作执行后额外缓冲 (ms)", &g_equip_extra_cooldown_ms, 0, 500); ImGui::Separator(); if (ImGui::Button((const char*)u8"保存上述所有配置", ImVec2(180 * g_autoScale, 30 * g_autoScale))) { SaveConfig(); g_save_timer = 1.5f; } ImGui::Separator(); ImGui::TextColored(ImVec4(0.5f, 1.0f, 0.5f, 1.0f), (const char*)u8"【寻址状态实时输出监控】"); ImGui::Text("Main Camera: 0x%lX", snap->mainCamera); ImGui::TextColored(ImVec4(0.8f, 0.6f, 1.0f, 1.0f), (const char*)u8"★ 左侧装备数组 (0-19):"); if (snap->myItems.empty()) { ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), (const char*)u8"暂未读取到装备..."); } else { std::string eqRow1 = ""; std::string eqRow2 = ""; for (int i = 0; i < snap->myItems.size(); i++) { if (i < 10) eqRow1 += std::to_string(snap->myItems[i]) + " "; else eqRow2 += std::to_string(snap->myItems[i]) + " "; } ImGui::TextWrapped("列1(0-9): %s", eqRow1.c_str()); ImGui::TextWrapped("列2(10-19): %s", eqRow2.c_str()); } ImGui::Separator(); ImGui::BeginChild("TracesScroll", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); if (snap->debugHeroTraces.empty()) { ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), (const char*)u8"暂未读取到英雄实体..."); } else { for (const auto& t : snap->debugHeroTraces) { ImGui::TextColored(ImVec4(1,1,0,1), "[槽位 %d] HeroID: %d", t.index, t.heroId); ImGui::Text(" HeroObj: 0x%lX", t.heroObj); ImGui::Text(" UnitData: 0x%lX", t.unitData); ImGui::Text(" BattleUnit: 0x%lX", t.battleUnit); ImGui::TextColored(ImVec4(0,1,1,1), " 3D World: (%.1f, %.1f, %.1f)", t.worldPos.x, t.worldPos.y, t.worldPos.z); ImGui::TextColored(ImVec4(0,1,0,1), " 2D Screen: (%.1f, %.1f)", t.screenPos.x, t.screenPos.y); ImGui::Separator(); } } ImGui::EndChild(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem((const char*)u8"自动拿牌/刷新(UI组件)状态")) { ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.5f, 1.0f), (const char*)u8"【HeroRoot 0x46cce08 拦截列表 - 自动拿牌】"); ImGui::Separator(); for (int i = 0; i < 5; i++) { if (g_shop_slot_instances[i] != 0) ImGui::TextColored(ImVec4(0.6f, 0.9f, 1.0f, 1.0f), "[买牌槽位 %d] UI 实例 x0: 0x%lX", i, g_shop_slot_instances[i]); else ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "[买牌槽位 %d] 等待商店刷新捕获...", i); } ImGui::EndTabItem(); } if (ImGui::BeginTabItem((const char*)u8"商店/备战席偏移调试")) { ImGui::TextColored(ImVec4(1, 0.8f, 0.2f, 1), (const char*)u8"【动态偏移调节】"); auto InputHex = [](const char* label, int* v) { int step = 1, step_fast = 16; ImGui::InputScalar(label, ImGuiDataType_S32, v, &step, &step_fast, "%X", ImGuiInputTextFlags_CharsHexadecimal); }; InputHex("Shop Base", &g_off_shop_base); InputHex("Bench Base", &g_off_bench_base); InputHex("Base -> p20", &g_off_shop_p20); InputHex("p20 -> Entries", &g_off_shop_entries); if (ImGui::Button((const char*)u8"保存上述偏移配置", ImVec2(180 * g_autoScale, 30 * g_autoScale))) { SaveConfig(); g_save_timer = 1.5f; } ImGui::EndTabItem(); } if (ImGui::BeginTabItem((const char*)u8"运行日志(防崩溃)")) { ImGui::BeginChild("LogScrollRegion", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); { std::lock_guard logLock(g_logMutex); if (g_appLogs.empty()) ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), (const char*)u8"暂无错误日志..."); else for (const auto& log : g_appLogs) ImGui::TextWrapped("%s", log.c_str()); if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) ImGui::SetScrollHereY(1.0f); } ImGui::EndChild(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem((const char*)u8"资源路径收集器")) { ImGui::Checkbox((const char*)u8"开启小小英雄路径收集", &g_collect_skins); ImGui::SameLine(); ImGui::Checkbox((const char*)u8"开启棋盘路径收集", &g_collect_maps); ImGui::Spacing(); if (ImGui::Button((const char*)u8"清空所有记录", ImVec2(-1, 30 * g_autoScale))) { std::lock_guard lock(g_collect_mutex); g_collected_skins.clear(); g_collected_maps.clear(); } ImGui::Separator(); ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), (const char*)u8"【截获的小小英雄模型路径】"); ImGui::BeginChild("SkinsScroll", ImVec2(0, 180 * g_autoScale), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); { std::lock_guard lock(g_collect_mutex); if (g_collected_skins.empty()) ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), (const char*)u8"暂无记录,请勾选开关并在游戏中切换界面或等待..."); static int copied_skin_idx = -1; static float skin_copy_timer = 0.0f; if (skin_copy_timer > 0.0f) skin_copy_timer -= ImGui::GetIO().DeltaTime; int s_idx = 0; for (const auto& s : g_collected_skins) { ImGui::TextWrapped("%s", s.c_str()); ImGui::PushID(s_idx); if (ImGui::Button((const char*)u8"复制路径", ImVec2(100 * g_autoScale, 26 * g_autoScale))) { ImGui::SetClipboardText(s.c_str()); CopyToAndroidClipboard(s.c_str()); copied_skin_idx = s_idx; skin_copy_timer = 2.0f; } if (copied_skin_idx == s_idx && skin_copy_timer > 0.0f) { ImGui::SameLine(); ImGui::TextColored(ImVec4(0.2f, 1.0f, 0.2f, 1.0f), (const char*)u8"✓ 已复制"); } ImGui::PopID(); ImGui::Separator(); s_idx++; } } ImGui::EndChild(); ImGui::TextColored(ImVec4(0.2f, 0.8f, 1.0f, 1.0f), (const char*)u8"【截获的竞技场/棋盘路径】"); ImGui::BeginChild("MapsScroll", ImVec2(0, 180 * g_autoScale), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); { std::lock_guard lock(g_collect_mutex); if (g_collected_maps.empty()) ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), (const char*)u8"暂无记录,请勾选开关并在游戏中切换界面或等待..."); static int copied_map_idx = -1; static float map_copy_timer = 0.0f; if (map_copy_timer > 0.0f) map_copy_timer -= ImGui::GetIO().DeltaTime; int m_idx = 0; for (const auto& m : g_collected_maps) { ImGui::TextWrapped("%s", m.c_str()); ImGui::PushID(m_idx + 10000); // 避免ID冲突 if (ImGui::Button((const char*)u8"复制路径", ImVec2(100 * g_autoScale, 26 * g_autoScale))) { ImGui::SetClipboardText(m.c_str()); CopyToAndroidClipboard(m.c_str()); copied_map_idx = m_idx; map_copy_timer = 2.0f; } if (copied_map_idx == m_idx && map_copy_timer > 0.0f) { ImGui::SameLine(); ImGui::TextColored(ImVec4(0.2f, 1.0f, 0.2f, 1.0f), (const char*)u8"✓ 已复制"); } ImGui::PopID(); ImGui::Separator(); m_idx++; } } ImGui::EndChild(); ImGui::EndTabItem(); } ImGui::EndTabBar(); } } ImGui::End(); } void DrawMenu() { ImGuiIO& io = ImGui::GetIO(); ImGuiStyle& style = ImGui::GetStyle(); // 极简圆角 style.WindowRounding = 6.0f * g_autoScale; style.FrameRounding = 4.0f * g_autoScale; style.PopupRounding = 4.0f * g_autoScale; style.ItemSpacing = ImVec2(10 * g_autoScale, 10 * g_autoScale); style.WindowPadding = ImVec2(16 * g_autoScale, 16 * g_autoScale); style.WindowBorderSize = 1.0f; style.ScrollbarSize = 35.0f * g_autoScale; style.GrabMinSize = 25.0f * g_autoScale; // Apple 亮色系配色 style.Colors[ImGuiCol_WindowBg] = ImVec4(0.96f, 0.96f, 0.97f, 0.98f); // 极简白 style.Colors[ImGuiCol_Border] = ImVec4(0.0f, 0.0f, 0.0f, 0.05f); // 极微弱边框 style.Colors[ImGuiCol_TitleBg] = ImVec4(0.9f, 0.9f, 0.92f, 1.0f); // 浅灰标题栏 style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.88f, 0.88f, 0.9f, 1.0f); style.Colors[ImGuiCol_Text] = ImVec4(0.1f, 0.1f, 0.1f, 1.0f); // 全局文字深黑 static bool firstMenuOpen = true; if (firstMenuOpen) { ImGui::SetNextWindowPos(ImVec2(g_menuX, g_menuY), ImGuiCond_Always); ImGui::SetNextWindowSize(ImVec2(g_menuW, g_menuH), ImGuiCond_Always); ImGui::SetNextWindowCollapsed(g_menuCollapsed, ImGuiCond_Always); firstMenuOpen = false; } if (ImGui::Begin((const char*)u8"金铲铲助手", NULL, ImGuiWindowFlags_NoSavedSettings)) { if (!ImGui::IsWindowCollapsed()) { float curW = ImGui::GetWindowSize().x, curH = ImGui::GetWindowSize().y; if (std::abs(curW - g_menuW) > 5.0f || std::abs(curH - g_menuH) > 5.0f) { g_menuW = curW; g_menuH = curH; g_scale = std::clamp(curW / (500.0f * g_autoScale), 0.5f, 2.5f); } } g_menuX = ImGui::GetWindowPos().x; g_menuY = ImGui::GetWindowPos().y; g_menuCollapsed = ImGui::IsWindowCollapsed(); if (!g_menuCollapsed) { ImGui::SetWindowFontScale(g_scale); ImGui::TextColored(ImVec4(0.0f, 0.85f, 0.55f, 1.0f), (const char*)u8"[+] 稳定原版运行中 | FPS: %.1f", io.Framerate); ColoredSeparator(); // =============================================== // 基址 & 监控面板(可折叠,平板适配竖向布局) // =============================================== { static bool s_mon_open = false; const GameSnapshot* snapBase = GetSnapshot(); float panelScale = g_autoScale * g_scale * 0.82f; ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.07f, 0.10f, 0.13f, 0.92f)); ImGui::BeginChild("BaseAddrPanel", ImVec2(-1, 0), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_AlwaysAutoResize); ImGui::SetWindowFontScale(panelScale); // --- 基址第1行:标题 + 展开按钮 --- ImGui::TextColored(ImVec4(0.55f, 0.55f, 0.55f, 1.0f), (const char*)u8"■ 基址监控"); ImGui::SameLine(0, 10); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.18f,0.22f,0.30f,0.95f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.28f,0.38f,0.55f,1.0f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.12f,0.20f,0.35f,1.0f)); if (ImGui::SmallButton(s_mon_open ? (const char*)u8"▲ 收起" : (const char*)u8"▼ 展开监控")) s_mon_open = !s_mon_open; ImGui::PopStyleColor(3); // --- 基址第2行:il2cpp + Battle --- { if (g_il2cppBase != 0) ImGui::TextColored(ImVec4(0.2f,1.0f,0.5f,1.0f), "il2cpp: 0x%lX", g_il2cppBase); else ImGui::TextColored(ImVec4(0.5f,0.5f,0.5f,0.6f), (const char*)u8"il2cpp: 等待注入"); ImGui::SameLine(0, 12); uintptr_t bm = snapBase ? snapBase->chessBattleModel : 0; if (bm) ImGui::TextColored(ImVec4(0.4f,0.8f,1.0f,1.0f), "Battle: 0x%lX", bm); else ImGui::TextColored(ImVec4(0.5f,0.5f,0.5f,0.6f), (const char*)u8"Battle: 无"); } // --- 基址第3行:CSO + Pool + Cam --- { uintptr_t cso = snapBase ? snapBase->csoGame : 0; if (cso) ImGui::TextColored(ImVec4(1.0f,0.7f,0.3f,1.0f), "CSO: 0x%lX", cso); else ImGui::TextColored(ImVec4(0.5f,0.5f,0.5f,0.6f), (const char*)u8"CSO: -"); ImGui::SameLine(0, 12); uintptr_t poolB = g_debug_pool_base.load(); if (poolB) ImGui::TextColored(ImVec4(1.0f,0.5f,0.2f,1.0f), "Pool: 0x%lX", poolB); else ImGui::TextColored(ImVec4(0.5f,0.5f,0.5f,0.6f), (const char*)u8"Pool: -"); ImGui::SameLine(0, 12); uintptr_t cam = snapBase ? snapBase->mainCamera : 0; if (cam) ImGui::TextColored(ImVec4(0.8f,0.5f,1.0f,1.0f), "Cam: 0x%lX", cam); else ImGui::TextColored(ImVec4(0.5f,0.5f,0.5f,0.6f), (const char*)u8"Cam: -"); // 把poolB存一份供下方用 (void)poolB; } // --- 展开区域(带滚动条) --- if (s_mon_open) { float expandH = 420.0f * g_autoScale; ImGui::Separator(); ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.05f,0.07f,0.10f,0.80f)); ImGui::BeginChild("MonScrollArea", ImVec2(-1, expandH), false, ImGuiWindowFlags_AlwaysVerticalScrollbar); ImGui::SetWindowFontScale(panelScale); // ==== Hook 调用次数 ==== ImGui::TextColored(ImVec4(0.2f,0.85f,1.0f,1.0f), (const char*)u8"[ Hook 调用次数 ]"); ImGui::Separator(); // 2列自适应(用窗口宽度各50%) float colW = ImGui::GetContentRegionAvail().x * 0.5f; ImGui::Columns(2, "HookCols", false); ImGui::SetColumnWidth(0, colW); ImGui::SetColumnWidth(1, colW); auto HookRow = [&](const char* name, int cnt) { ImVec4 col = cnt > 0 ? ImVec4(0.3f,1.0f,0.6f,1.0f) : ImVec4(0.5f,0.5f,0.5f,0.7f); ImGui::TextColored(col, "%s: %d", name, cnt); ImGui::NextColumn(); }; HookRow("3Cd6058", g_debug_hook_3Cd6058_count); HookRow("set_Money", g_debug_hook_set_Money_count); HookRow("set_Level", g_debug_hook_set_Level_count); HookRow("TurnStart", g_debug_TurnStartCalled); HookRow("IsGameEnd", g_debug_set_IsGameEnd_count); HookRow("UpdateTurnStart", g_debug_UpdateTurnStart_count); HookRow("InitTurnDropCfg", g_debug_hook_InitTurnDropCfg_count); HookRow("6A67e48(Pool)", g_debug_hook_6A67e48_count); HookRow("RenderCanvases", g_debug_SendWillRenderCanvases_count); HookRow("BgThread", g_debug_bg_thread_count); ImGui::Columns(1); // ==== 偏移链监控(每节点独立一行)==== ImGui::Spacing(); ImGui::TextColored(ImVec4(1.0f,0.85f,0.2f,1.0f), (const char*)u8"[ 偏移链实时监控 ]"); uintptr_t myObj2 = 0, myLogicObj2 = 0; if (snapBase) { auto myIt2 = snapBase->players.find(snapBase->myPlayerId); if (myIt2 != snapBase->players.end()) { myObj2 = myIt2->second.objAddr; myLogicObj2 = myIt2->second.logicObjAddr; } } uintptr_t poolB2 = g_debug_pool_base.load(); // 辅助:地址若非零显示绿色,否则灰色 auto AddrLine = [&](const char* indent, const char* label, uintptr_t addr) { if (addr) ImGui::TextColored(ImVec4(0.3f,1.0f,0.6f,1.0f), "%s%s: 0x%lX", indent, label, addr); else ImGui::TextColored(ImVec4(0.5f,0.5f,0.5f,0.7f), "%s%s: (null)", indent, label); }; ImGui::Separator(); // 商店链 ImGui::TextColored(ImVec4(0.2f,1.0f,0.5f,1.0f), (const char*)u8"▶ 商店链"); if (myObj2) { uintptr_t pBase = SAFE_READ_PTR(myObj2, g_off_shop_base); uintptr_t p20 = pBase ? SAFE_READ_PTR(pBase, g_off_shop_p20) : 0; uintptr_t entries = p20 ? SAFE_READ_PTR(p20, g_off_shop_entries) : 0; AddrLine(" ", "PlayerObj", myObj2); ImGui::Text(" +0x%X ->", g_off_shop_base); ImGui::SameLine(0,4); AddrLine("","Base",pBase); ImGui::Text(" +0x%X ->", g_off_shop_p20); ImGui::SameLine(0,4); AddrLine("","p20", p20); ImGui::Text(" +0x%X ->", g_off_shop_entries); ImGui::SameLine(0,4); AddrLine("","Entries",entries); } else { ImGui::TextColored(ImVec4(0.5f,0.5f,0.5f,0.7f), (const char*)u8" 等待对局..."); } ImGui::Separator(); // 备战席链 ImGui::TextColored(ImVec4(0.2f,1.0f,0.5f,1.0f), (const char*)u8"▶ 备战席链"); if (myObj2) { uintptr_t pBase = SAFE_READ_PTR(myObj2, g_off_bench_base); uintptr_t p20 = pBase ? SAFE_READ_PTR(pBase, g_off_shop_p20) : 0; uintptr_t entries = p20 ? SAFE_READ_PTR(p20, g_off_shop_entries) : 0; AddrLine(" ", "PlayerObj", myObj2); ImGui::Text(" +0x%X ->", g_off_bench_base); ImGui::SameLine(0,4); AddrLine("","Base",pBase); ImGui::Text(" +0x%X ->", g_off_shop_p20); ImGui::SameLine(0,4); AddrLine("","p20", p20); ImGui::Text(" +0x%X ->", g_off_shop_entries); ImGui::SameLine(0,4); AddrLine("","Entries",entries); } else { ImGui::TextColored(ImVec4(0.5f,0.5f,0.5f,0.7f), (const char*)u8" 等待对局..."); } ImGui::Separator(); // 卡池链 ImGui::TextColored(ImVec4(1.0f,0.5f,0.2f,1.0f), (const char*)u8"▶ 卡池链 (Pool)"); if (poolB2) { uintptr_t heroku = SAFE_READ_PTR(poolB2, g_off_pool_heroku); uintptr_t aheroku = heroku ? SAFE_READ_PTR(heroku, g_off_pool_Aheroku) : 0; uintptr_t entries1 = aheroku ? SAFE_READ_PTR(aheroku, g_off_pool_entries1) : 0; AddrLine(" ", "PoolBase", poolB2); ImGui::Text(" +0x%X ->", g_off_pool_heroku); ImGui::SameLine(0,4); AddrLine("","Heroku", heroku); ImGui::Text(" +0x%X ->", g_off_pool_Aheroku); ImGui::SameLine(0,4); AddrLine("","AHeroku",aheroku); ImGui::Text(" +0x%X ->", g_off_pool_entries1); ImGui::SameLine(0,4); AddrLine("","Entries1",entries1); } else { ImGui::TextColored(ImVec4(0.5f,0.5f,0.5f,0.7f), (const char*)u8" 等待 hook_6A67e48 触发..."); } ImGui::Separator(); // 装备链 ImGui::TextColored(ImVec4(1.0f,0.8f,0.2f,1.0f), (const char*)u8"▶ 装备链 (Equip)"); if (myLogicObj2) { uintptr_t pEquipBase = SAFE_READ_PTR(myLogicObj2, g_off_equip_base); uintptr_t itemArr = pEquipBase ? SAFE_READ_PTR(pEquipBase, g_off_equip_arr) : 0; AddrLine(" ", "LogicObj", myLogicObj2); ImGui::Text(" +0x%X ->", g_off_equip_base); ImGui::SameLine(0,4); AddrLine("","EquipBase",pEquipBase); ImGui::Text(" +0x%X ->", g_off_equip_arr); ImGui::SameLine(0,4); AddrLine("","ItemArr", itemArr); } else { ImGui::TextColored(ImVec4(0.5f,0.5f,0.5f,0.7f), (const char*)u8" 等待对局..."); } ImGui::Separator(); // 英雄链 ImGui::TextColored(ImVec4(0.8f,0.5f,1.0f,1.0f), (const char*)u8"▶ 英雄实体链 (Hero)"); if (myLogicObj2) { uintptr_t pDyn = SAFE_READ_PTR(myLogicObj2, g_off_hero_dyn); uintptr_t heroArr = pDyn ? SAFE_READ_PTR(pDyn, g_off_hero_arr) : 0; AddrLine(" ", "LogicObj", myLogicObj2); ImGui::Text(" +0x%X ->", g_off_hero_dyn); ImGui::SameLine(0,4); AddrLine("","HeroDyn", pDyn); ImGui::Text(" +0x%X ->", g_off_hero_arr); ImGui::SameLine(0,4); AddrLine("","HeroArr", heroArr); } else { ImGui::TextColored(ImVec4(0.5f,0.5f,0.5f,0.7f), (const char*)u8" 等待对局..."); } ImGui::EndChild(); ImGui::PopStyleColor(); // ChildBg } ImGui::SetWindowFontScale(g_scale); ImGui::EndChild(); ImGui::PopStyleColor(); // ChildBg outer } ColoredSeparator(); // =============================================== // 神话换肤UI // =============================================== if (ModernAnimatedFolder((const char*)u8"🏆 神话皮肤换肤", &g_header_skin)) { ModernToggle((const char*)u8"开启小小英雄换肤 (对局内生效)", &g_enable_skin, 18); if (g_enable_skin) { ImGui::Indent(15.0f * g_autoScale * g_scale); const int numSkins = sizeof(g_skinList)/sizeof(g_skinList[0]); std::vector skinNames(numSkins); for (int i = 0; i < numSkins; i++) { skinNames[i] = g_skinList[i].zh_name; } ImGui::PushItemWidth(260.0f * g_autoScale * g_scale); if (ImGui::Combo("##SkinSelect", &g_selected_skin_idx, skinNames.data(), numSkins, 10)) { g_save_timer = 1.5f; SaveConfig(); } ImGui::PopItemWidth(); ImGui::SameLine(); ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), (const char*)u8"选择英雄"); ImGui::Unindent(15.0f * g_autoScale * g_scale); } ImGui::Separator(); ModernToggle((const char*)u8"开启竞技场换肤 (大厅或进图前开启)", &g_enable_map_skin, 19); if (g_enable_map_skin) { ImGui::Indent(15.0f * g_autoScale * g_scale); const int numMaps = sizeof(g_mapList)/sizeof(g_mapList[0]); std::vector mapNames(numMaps); for (int i = 0; i < numMaps; i++) { mapNames[i] = g_mapList[i].zh_name; } ImGui::PushItemWidth(260.0f * g_autoScale * g_scale); if (ImGui::Combo("##MapSelect", &g_selected_map_idx, mapNames.data(), numMaps, 10)) { g_save_timer = 1.5f; SaveConfig(); } ImGui::PopItemWidth(); ImGui::SameLine(); ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f), (const char*)u8"选择棋盘"); ImGui::Unindent(15.0f * g_autoScale * g_scale); } EndModernAnimatedFolder(); ColoredSeparator(); } ModernToggle((const char*)u8"打开极速退游悬浮窗", &g_show_instant_quit_window, 7); ModernToggle((const char*)u8"打开卖空英雄悬浮窗", &g_show_sell_window, 23); ColoredSeparator(); // 穿拆装备与连线选项 ModernToggle((const char*)u8"打开穿卸装备悬浮窗", &g_show_equip_window, 21); ModernToggle((const char*)u8"开启英雄3D追踪连线", &g_draw_hero_lines, 22); ColoredSeparator(); ModernToggle((const char*)u8"主线程并发极速秒牌", &g_auto_buy, 14); if (g_auto_buy) { ImGui::Indent(15.0f * g_autoScale * g_scale); ModernToggle((const char*)u8"英雄列表及装备图鉴", &g_show_hero_list_window, 20); ImGui::Unindent(15.0f * g_autoScale * g_scale); } ModernSlider((const char*)u8"自动刷新(D牌)间隔 (毫秒)", &g_auto_buy_delay_ms, 0.0f, 1000.0f); ModernNumberAdjuster((const char*)u8"低于该金币停止刷新", &g_auto_refresh_money_threshold, 0, 200); ColoredSeparator(); ModernToggle((const char*)u8"打开自动操作悬浮窗", &g_show_auto_window, 15); if (g_show_auto_window) { ImGui::Indent(15.0f * g_autoScale * g_scale); ModernToggle((const char*)u8"自动拿天选 (二星英雄)", &g_auto_buy_chosen, 16); ModernToggle((const char*)u8"显示[一键清空备战席]按钮", &g_show_clear_bench_btn, 24); ImGui::Unindent(15.0f * g_autoScale * g_scale); } ColoredSeparator(); if (ModernAnimatedFolder((const char*)u8"预测功能", &g_header_pred)) { ModernToggle((const char*)u8"预测对手", &g_predict_enemy, 1); if (g_predict_enemy) { ImGui::Indent(15.0f * g_autoScale * g_scale); ModernSlider((const char*)u8"预测名字缩放", &g_predictNameScale, 0.5f, 5.0f); ImGui::Unindent(15.0f * g_autoScale * g_scale); } ModernToggle((const char*)u8"预测海克斯", &g_predict_hex, 2); EndModernAnimatedFolder(); } if (ModernAnimatedFolder((const char*)u8"透视功能", &g_header_esp)) { ModernSlider((const char*)u8"整体透视透明度", &g_global_esp_alpha, 0.1f, 1.0f); ModernToggle((const char*)u8"对手棋盘透视", &g_esp_board, 3); ModernToggle((const char*)u8"敌方备战席透视", &g_esp_enemy_bench, 5); ModernToggle((const char*)u8"金币等级透视", &g_esp_level, 9); if (g_esp_level) { ImGui::Indent(15.0f * g_autoScale * g_scale); ModernSlider((const char*)u8"行与行间距", &g_level_Spacing, 20.0f, 150.0f); ModernSlider((const char*)u8"字体缩放", &g_level_Scale, 0.5f, 3.0f); ImGui::Unindent(15.0f * g_autoScale * g_scale); } EndModernAnimatedFolder(); } ColoredSeparator(); ModernToggle((const char*)u8"锁定所有窗口", &g_boardLocked, 8); if (ModernToggle((const char*)u8"显示/调节 [穿装] 落点 (需解锁窗口移动)", &g_show_equip_ui_debug, 25)) { if (g_show_equip_ui_debug) g_show_sell_ui_debug = false; } if (ModernToggle((const char*)u8"显示/调节 [卖牌] 落点 (需解锁窗口移动)", &g_show_sell_ui_debug, 26)) { if (g_show_sell_ui_debug) g_show_equip_ui_debug = false; } if (g_boardLocked) { ImGui::Indent(15.0f * g_autoScale * g_scale); ImGui::TextColored(ImVec4(1, 0.8f, 0.2f, 1), (const char*)u8"【装备栏点击点微调】"); ModernFloatAdjuster((const char*)u8"起点 X", &g_equip_ui_start_x, 0.0f, 3000.0f, 1.0f); ModernFloatAdjuster((const char*)u8"起点 Y", &g_equip_ui_start_y, 0.0f, 3000.0f, 1.0f); ModernFloatAdjuster((const char*)u8"列间距 X", &g_equip_ui_step_x, 10.0f, 500.0f, 1.0f); ModernFloatAdjuster((const char*)u8"行间距 Y", &g_equip_ui_step_y, 10.0f, 500.0f, 1.0f); ImGui::Unindent(15.0f * g_autoScale * g_scale); } ModernToggle((const char*)u8"牌库显示", &g_show_card_pool, 10); if (g_show_card_pool) { ImGui::Indent(15.0f * g_autoScale * g_scale); ModernTierSelector((const char*)u8"显示等级:", g_show_pool_tiers); ModernNumberAdjuster((const char*)u8"单行最大列数", &g_card_pool_cols, 1, 30); ModernFloatAdjuster((const char*)u8"牌库透明度", &g_cardPoolAlpha, 0.1f, 1.0f, 0.1f); ColoredSeparator(); ModernToggle((const char*)u8"开启牌库剩余量红框预警", &g_pool_warning_enable, 17); if (g_pool_warning_enable) { ImGui::Indent(15.0f * g_autoScale * g_scale); ModernTierSelector((const char*)u8"预警卡费:", g_pool_warning_tiers); ModernNumberAdjuster((const char*)u8"低于几张红框并沉底", &g_pool_warning_count, 1, 30); ImGui::Unindent(15.0f * g_autoScale * g_scale); } ImGui::Unindent(15.0f * g_autoScale * g_scale); } ModernToggle((const char*)u8"卡牌预警数量", &g_card_warning, 11); if (g_card_warning) { ImGui::Indent(15.0f * g_autoScale * g_scale); ModernTierSelector((const char*)u8"预警等级:", g_warning_tiers); ModernNumberAdjuster((const char*)u8"预警张数", &g_warning_threshold, 1, 30); ModernSlider((const char*)u8"预警行间距", &g_warn_Spacing, 20.0f, 150.0f); ModernSlider((const char*)u8"预警整体缩放", &g_warn_Scale, 0.5f, 3.0f); ImGui::Unindent(15.0f * g_autoScale * g_scale); } ModernToggle((const char*)u8"全功能偏移追踪与调试", &g_show_debug_window, 12); ImGui::Spacing(); ColoredSeparator(); if (g_save_timer > 0.0f) { g_save_timer -= io.DeltaTime; ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.7f, 0.4f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.7f, 0.4f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.0f, 0.7f, 0.4f, 1.0f)); ImGui::Button((const char*)u8"✓ 配置文件已保存", ImVec2(-1, 55 * g_autoScale)); ImGui::PopStyleColor(3); } else { if (ImGui::Button((const char*)u8"保存当前配置", ImVec2(-1, 55 * g_autoScale))) { g_save_timer = 1.5f; SaveConfig(); } } } } ImGui::End(); } // ================================================================= // 核心渲染循环 EGLSwapBuffers // ================================================================= EGLBoolean (*old_eglSwap)(EGLDisplay, EGLSurface) = nullptr; std::atomic g_engine_rendering{false}; EGLBoolean hook_eglSwap(EGLDisplay dpy, EGLSurface surf) { if (!g_engine_rendering.load()) { g_engine_rendering.store(true); } eglQuerySurface(dpy, surf, EGL_WIDTH, &g_gl_width); eglQuerySurface(dpy, surf, EGL_HEIGHT, &g_gl_height); if (g_gl_width <= 0 || g_gl_height <= 0) { return old_eglSwap(dpy, surf); } static bool init = false; static auto last_time = std::chrono::high_resolution_clock::now(); static EGLContext last_ctx = EGL_NO_CONTEXT; EGLContext current_ctx = eglGetCurrentContext(); if (current_ctx != last_ctx) { if (init) { ImGui_ImplOpenGL3_DestroyDeviceObjects(); ClearAllTextureCaches(); g_needUpdateFontSafe = true; g_cached_view_width = -1; g_cached_view_height = -1; } last_ctx = current_ctx; } if (!init) { ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); io.IniFilename = nullptr; LoadConfig(); ImGui_ImplOpenGL3_Init("#version 300 es"); io.DisplaySize = ImVec2((float)g_gl_width, (float)g_gl_height); UpdateFontHD(true); init = true; } if (g_needUpdateFontSafe) { UpdateFontHD(true); g_needUpdateFontSafe = false; } auto current_time = std::chrono::high_resolution_clock::now(); float delta = std::chrono::duration(current_time - last_time).count(); last_time = current_time; ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2((float)g_gl_width, (float)g_gl_height); io.DeltaTime = delta > 0.0001f ? delta : (1.0f / 120.0f); ProcessTextureQueue(); ImGui_ImplOpenGL3_NewFrame(); ImGui::NewFrame(); glDisable(GL_SCISSOR_TEST); auto snapshot = GetSnapshot(); DrawBoard(snapshot); DrawBenchESP(snapshot); DrawPurePredictEnemy(snapshot); DrawPurePredictHex(snapshot); DrawLevelOverlay(snapshot); DrawWarningOverlay(snapshot); DrawCardPool(snapshot); DrawShopESP(snapshot); DrawAutoActionWindow(); DrawAutoEquipWindow(); DrawSellHeroWindow(); DrawHeroLinesESP(snapshot); DrawEquipUIDebug(); DrawSwipePaths(); DrawInstantQuitWindow(); DrawHeroSelectionWindow(); DrawMenu(); DrawDebugWindow(snapshot); ImGui::Render(); glViewport(0, 0, g_gl_width, g_gl_height); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); return old_eglSwap(dpy, surf); } // ================================================================= // 初始化入口 // ================================================================= void* DelayedHookThread(void*) { while (!g_engine_rendering.load()) { sleep(1); } sleep(4); FindAndHookHiddenJNI(); InitBusinessHooks(); return nullptr; } void* SetupThread(void*) { // 初始化瞬间在后台建立本地全图鉴,不会卡住游戏 BuildLocalPokedex(); // 启动图片解压专属工人线程(终结并发发热) std::thread(TextureDecodingWorkerThread).detach(); void* egl_ptr = nullptr; // [关键修复] 循环等待直到 Unity 引擎真正加载了 libEGL.so while ((egl_ptr = DobbySymbolResolver("libEGL.so", "eglSwapBuffers")) == nullptr) { sleep(1); // 每秒检测一次,直到图形库加载完成 } // 成功获取到指针后再进行 Hook DobbyHook(egl_ptr, (void*)hook_eglSwap, (void**)&old_eglSwap); pthread_t t; pthread_create(&t, 0, DelayedHookThread, 0); pthread_detach(t); return nullptr; } __attribute__((constructor)) void Init() { pthread_t t; pthread_create(&t, 0, SetupThread, 0); pthread_detach(t); }