#include #include "control.h" #include #include #include "acio/acio.h" #include "api/controller.h" #include "avs/core.h" #include "avs/ea3.h" #include "avs/game.h" #include "build/resource.h" #include "cfg/analog.h" #include "cfg/button.h" #include "external/imgui/imgui_memory_editor.h" #include "games/io.h" #include "games/iidx/io.h" #include "games/shared/lcdhandle.h" #include "hooks/graphics/graphics.h" #include "launcher/launcher.h" #include "launcher/shutdown.h" #include "misc/eamuse.h" #include "rawinput/rawinput.h" #include "util/cpuutils.h" #include "util/memutils.h" #include "util/netutils.h" #include "util/libutils.h" #include "util/peb.h" #include "util/resutils.h" #include "util/utils.h" #include "touch/touch.h" #include "acio_status_buffers.h" #include "eadev.h" #include "wnd_manager.h" #include "midi.h" namespace overlay::windows { Control::Control(SpiceOverlay *overlay) : Window(overlay) { this->title = "SpiceTools Control"; this->flags = ImGuiWindowFlags_AlwaysAutoResize; this->toggle_button = games::OverlayButtons::ToggleControl; this->init_pos = ImVec2(10, 10); this->size_min.x = 300; } Control::~Control() { } void Control::build_content() { top_row_buttons(); img_gui_view(); ImGui::Separator(); avs_info_view(); acio_view(); cpu_view(); graphics_view(); buttons_view(); analogs_view(); lights_view(); cards_view(); coin_view(); control_view(); api_view(); raw_input_view(); touch_view(); lcd_view(); about_view(); ddr_timing_view(); iidx_effectors_view(); } void Control::top_row_buttons() { // memory editor button ImGui::SetNextItemWidth(-1.f); if (ImGui::Button("Memory Editor")) { this->memory_editor_open = true; } // memory editor window if (this->memory_editor_open) { static MemoryEditor memory_editor = MemoryEditor(); ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_Once); if (ImGui::Begin("Memory Editor", &this->memory_editor_open)) { // draw filter if (this->memory_editor_filter.Draw("Filter")) { memory_editor_modules.clear(); memory_editor_names.clear(); memory_editor_selection = -1; } // obtain modules if (memory_editor_modules.empty()) { peb::obtain_modules(&memory_editor_modules); // extract names for combobox for (size_t i = 0; i < memory_editor_modules.size();) { auto s = memory_editor_modules[i].first.c_str(); // check if passes filter if (memory_editor_filter.PassFilter(s)) { memory_editor_names.emplace_back(s); i++; } else { memory_editor_modules.erase(memory_editor_modules.begin() + i); } } } // draw combo box ImGui::Combo("DLL Selection", &memory_editor_selection, &memory_editor_names[0], static_cast(memory_editor_names.size())); ImGui::Separator(); if (memory_editor_selection >= 0) { HMODULE module = memory_editor_modules[memory_editor_selection].second; // get module information MODULEINFO module_info{}; if (GetModuleInformation( GetCurrentProcess(), module, &module_info, sizeof(MODULEINFO))) { /* * unprotect memory * small hack: don't reset the mode since multiple pages with different modes are affected * they'd get overridden by the original mode of the first page */ memutils::VProtectGuard guard( module_info.lpBaseOfDll, module_info.SizeOfImage, PAGE_EXECUTE_READWRITE, false); // draw memory editor memory_editor.DrawContents( module_info.lpBaseOfDll, module_info.SizeOfImage, (size_t) module_info.lpBaseOfDll); } else { ImGui::Text("Could not get module information"); } } else { ImGui::Text("Please select a module"); } } ImGui::End(); } // EA-Dev ImGui::SameLine(); if (ImGui::Button("EA-Dev")) { this->children.emplace_back(new EADevWindow(this->overlay)); } // Window Manager ImGui::SameLine(); if (ImGui::Button("Window Manager")) { this->children.emplace_back(new WndManagerWindow(this->overlay)); } } void Control::img_gui_view() { if (ImGui::CollapsingHeader("ImGui")) { // display size ImGui::Text("Display Size: %dx%d", static_cast(ImGui::GetIO().DisplaySize.x), static_cast(ImGui::GetIO().DisplaySize.y)); // removed for size (along with setting IMGUI_DISABLE_DEMO_WINDOWS // and IMGUI_DISABLE_DEBUG_TOOLS) - saves about 300kb in each // binary // metrics button // this->metrics_open |= ImGui::Button("Metrics Window"); // if (this->metrics_open) { // ImGui::ShowMetricsWindow(&this->metrics_open); // } // demo button // ImGui::SameLine(); // if (ImGui::Button("Demo Window")) { // this->demo_open = true; // } // if (this->demo_open) { // ImGui::ShowDemoWindow(&this->demo_open); // } } } void Control::avs_info_view() { if (ImGui::CollapsingHeader("AVS")) { // game ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (ImGui::TreeNode("Game")) { ImGui::BulletText("DLL Name: %s", avs::game::DLL_NAME.c_str()); ImGui::BulletText("Identifier: %s", avs::game::get_identifier().c_str()); ImGui::TreePop(); } // core ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (ImGui::TreeNode("Core")) { ImGui::BulletText("DLL Name: %s", avs::core::DLL_NAME.c_str()); ImGui::BulletText("Version: %s", avs::core::VERSION_STR.c_str()); ImGui::BulletText("%s", fmt::format("Heap Size: {}{}", (uint64_t) avs::core::HEAP_SIZE, avs::core::DEFAULT_HEAP_SIZE_SET ? " (Default)" : "").c_str()); ImGui::BulletText("Log Path: %s", avs::core::LOG_PATH.c_str()); ImGui::BulletText("Config Path: %s", avs::core::CFG_PATH.c_str()); ImGui::TreePop(); } // EA3 ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (ImGui::TreeNode("EA3")) { ImGui::BulletText("DLL Name: %s", avs::ea3::DLL_NAME.c_str()); ImGui::BulletText("Version: %s", avs::ea3::VERSION_STR.c_str()); ImGui::BulletText("Config Path: %s", avs::ea3::CFG_PATH.c_str()); ImGui::BulletText("App Path: %s", avs::ea3::APP_PATH.c_str()); ImGui::BulletText("Services: %s", avs::ea3::EA3_BOOT_URL.c_str()); ImGui::TreePop(); } } } void Control::acio_view() { if (ImGui::CollapsingHeader("ACIO")) { ImGui::Columns(4, "acio_columns"); ImGui::Separator(); ImGui::Text("Name"); ImGui::NextColumn(); ImGui::Text("Hook"); ImGui::NextColumn(); ImGui::Text("Attached"); ImGui::NextColumn(); ImGui::NextColumn(); ImGui::Separator(); for (auto &module : acio::MODULES) { ImGui::PushID((void *) module); ImGui::Text("%s", module->name.c_str()); ImGui::NextColumn(); ImGui::Text("%s", acio::hook_mode_str(module->hook_mode)); ImGui::NextColumn(); ImGui::Text("%s", module->attached ? "true" : "false"); ImGui::NextColumn(); if (module->status_buffer && module->status_buffer_size) { if (ImGui::Button("Status")) { this->children.emplace_back(new ACIOStatusBuffers(overlay, module)); } } ImGui::NextColumn(); ImGui::Separator(); ImGui::PopID(); } ImGui::Columns(1); } } void Control::cpu_view() { auto cpu_load_values = cpuutils::get_load(); if (cpu_load_values.size() && ImGui::CollapsingHeader("CPU")) { // print detected cores ImGui::BulletText("Detected cores: %i", (int) std::thread::hardware_concurrency()); // make sure the temporary buffer has enough space while (this->cpu_values.size() < cpu_load_values.size()) { this->cpu_values.emplace_back(0.f); } // iterate cores for (size_t cpu = 0; cpu < cpu_load_values.size(); cpu++) { // update average auto avg_load = MIN(MAX(this->cpu_values[cpu] + (cpu_load_values[cpu] - this->cpu_values[cpu]) * ImGui::GetIO().DeltaTime, 0), 100); this->cpu_values[cpu] = avg_load; // draw content ImGui::BulletText("CPU #%i:", (int) cpu + 1); ImGui::SameLine(); ImGui::ProgressBar(avg_load * 0.01f, ImVec2(64, 0)); } } } void Control::graphics_view() { if (ImGui::CollapsingHeader("Graphics")) { // screenshot button if (ImGui::Button("Take Screenshot")) { graphics_screenshot_trigger(); } // graphics information ImGui::BulletText("D3D9 Adapter ID: %lu", overlay->adapter_identifier.DeviceId); ImGui::BulletText("D3D9 Adapter Name: %s", overlay->adapter_identifier.DeviceName); ImGui::BulletText("D3D9 Adapter Revision: %lu", overlay->adapter_identifier.Revision); ImGui::BulletText("D3D9 Adapter SubSys ID: %lu", overlay->adapter_identifier.SubSysId); ImGui::BulletText("D3D9 Adapter Vendor ID: %lu", overlay->adapter_identifier.VendorId); ImGui::BulletText("D3D9 Adapter WQHL Level: %lu", overlay->adapter_identifier.WHQLLevel); ImGui::BulletText("D3D9 Adapter Driver: %s", overlay->adapter_identifier.Driver); ImGui::BulletText("%s", fmt::format("D3D9 Adapter Driver Version: {}", overlay->adapter_identifier.DriverVersion.QuadPart).c_str()); ImGui::BulletText("D3D9 Adapter Description: %s", overlay->adapter_identifier.Description); ImGui::BulletText("D3D9 Adapter GUID: %s", guid2s(overlay->adapter_identifier.DeviceIdentifier).c_str()); } } void Control::buttons_view() { auto buttons = games::get_buttons(eamuse_get_game()); if (buttons && !buttons->empty() && ImGui::CollapsingHeader("Buttons")) { // print each button state for (auto &button : *buttons) { // state float state = GameAPI::Buttons::getVelocity(RI_MGR, button); ImGui::ProgressBar(state, ImVec2(32.f, 0)); // mouse down handler if (ImGui::IsItemHovered()) { if (ImGui::IsAnyMouseDown()) { button.override_state = GameAPI::Buttons::BUTTON_PRESSED; button.override_velocity = 1.f; button.override_enabled = true; } else { button.override_enabled = false; } } // mark overridden items if (button.override_enabled) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 0.f, 1.f)); } // text ImGui::SameLine(0.f, ImGui::GetStyle().ItemInnerSpacing.x); if (RI_MGR && button.isSet()) { ImGui::Text("%s [%s]", button.getName().c_str(), button.getDisplayString(RI_MGR.get()).c_str()); } else { ImGui::Text("%s", button.getName().c_str()); } // pop override color if (button.override_enabled) { ImGui::PopStyleColor(); } } } } void Control::analogs_view() { auto analogs = games::get_analogs(eamuse_get_game()); if (analogs && !analogs->empty() && ImGui::CollapsingHeader("Analogs")) { // print each button state for (auto &analog : *analogs) { // state float state = GameAPI::Analogs::getState(RI_MGR, analog); ImGui::ProgressBar(state, ImVec2(32.f, 0)); // mouse down handler if (ImGui::IsItemHovered()) { if (ImGui::IsAnyMouseDown()) { analog.override_state = 1.f; analog.override_enabled = true; } else { analog.override_enabled = false; } } // mark overridden items if (analog.override_enabled) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 0.f, 1.f)); } // text ImGui::SameLine(0.f, ImGui::GetStyle().ItemInnerSpacing.x); if (RI_MGR && analog.isSet()) { ImGui::Text("%s [%s]", analog.getName().c_str(), analog.getDisplayString(RI_MGR.get()).c_str()); } else { ImGui::Text("%s", analog.getName().c_str()); } // pop override color if (analog.override_enabled) { ImGui::PopStyleColor(); } } } } void Control::lights_view() { auto lights = games::get_lights(eamuse_get_game()); if (lights && !lights->empty() && ImGui::CollapsingHeader("Lights")) { // print each button state for (auto &light : *lights) { // state float state = GameAPI::Lights::readLight(RI_MGR, light); ImGui::ProgressBar(state, ImVec2(32.f, 0)); // mouse down handler if (ImGui::IsItemHovered()) { if (ImGui::IsAnyMouseDown()) { light.override_state = 1.f; light.override_enabled = true; } else { light.override_enabled = false; } GameAPI::Lights::writeLight(RI_MGR, light, light.last_state); RI_MGR->devices_flush_output(); } // mark overridden items if (light.override_enabled) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 0.f, 1.f)); } // text ImGui::SameLine(0.f, ImGui::GetStyle().ItemInnerSpacing.x); if (RI_MGR && light.isSet()) { ImGui::Text("%s [%s]", light.getName().c_str(), light.getDisplayString(RI_MGR.get()).c_str()); } else { ImGui::Text("%s", light.getName().c_str()); } // pop override color if (light.override_enabled) { ImGui::PopStyleColor(); } } } } void Control::cards_view() { if (ImGui::CollapsingHeader("Cards")) { ImGui::InputTextWithHint("Card ID", "E0040123456789AB", this->card_input, std::size(this->card_input), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); if (strlen(this->card_input) < 16) { ImGui::Text("Please enter your card identifier..."); } else { if (ImGui::Button("Insert P1")) { uint8_t card_data[8]; if (hex2bin(this->card_input, card_data)) { eamuse_card_insert(0, card_data); } } if (eamuse_get_game_keypads() > 1) { ImGui::SameLine(); if (ImGui::Button("Insert P2")) { uint8_t card_data[8]; if (hex2bin(this->card_input, card_data)) { eamuse_card_insert(1, card_data); } } } } } } void Control::coin_view() { if (ImGui::CollapsingHeader("Coins")) { auto coinstock = eamuse_coin_get_stock(); ImGui::Text("Blocker: %s", eamuse_coin_get_block() ? "closed" : "open"); ImGui::Text("Coinstock: %i", coinstock); ImGui::Separator(); if (ImGui::Button("Add Coin")) { eamuse_coin_add(); } if (coinstock != 0) { ImGui::SameLine(); if (ImGui::Button("Consume")) { eamuse_coin_consume_stock(); } } } } void Control::control_view() { if (ImGui::CollapsingHeader("Control")) { // launcher utils ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (ImGui::TreeNode("Launcher Utils")) { if (ImGui::Button("Restart")) { launcher::restart(); } if (ImGui::Button("Terminate")) { launcher::shutdown(0); } ImGui::TreePop(); } // signal triggers ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (ImGui::TreeNode("Signal Triggers")) { if (ImGui::Button("Raise SIGABRT")) { ::raise(SIGABRT); } if (ImGui::Button("Raise SIGFPE")) { ::raise(SIGFPE); } if (ImGui::Button("Raise SIGILL")) { ::raise(SIGILL); } if (ImGui::Button("Raise SIGINT")) { ::raise(SIGINT); } if (ImGui::Button("Raise SIGSEGV")) { ::raise(SIGSEGV); } if (ImGui::Button("Raise SIGTERM")) { ::raise(SIGTERM); } ImGui::TreePop(); } } } void Control::api_view() { if (API_CONTROLLER != nullptr && ImGui::CollapsingHeader("API")) { std::vector client_states; API_CONTROLLER->obtain_client_states(&client_states); // show ip addresses auto ip_addresses = netutils::get_local_addresses(); if (!ip_addresses.empty()) { ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (ImGui::TreeNode("Local IP-Addresses")) { for (auto &adr : ip_addresses) { ImGui::BulletText("%s", adr.c_str()); } ImGui::TreePop(); ImGui::Separator(); } } // client count ImGui::Text("Connected clients: %u", (unsigned int) client_states.size()); // iterate clients for (auto &client : client_states) { auto address = API_CONTROLLER->get_ip_address(client.address); if (ImGui::TreeNode(("Client @ " + address).c_str())) { if (client.password.empty()) { ImGui::Text("No password set."); } else { ImGui::Text("Password set."); } if (ImGui::TreeNode("Modules")) { for (auto &module : client.modules) { if (ImGui::TreeNode(module->name.c_str())) { ImGui::Text("Password force: %i", module->password_force); ImGui::TreePop(); } } ImGui::TreePop(); } ImGui::TreePop(); } } } } void Control::raw_input_view() { if (RI_MGR != nullptr && ImGui::CollapsingHeader("RawInput")) { // midi control if (ImGui::Button("MIDI-Control")) { this->children.push_back(new MIDIWindow(this->overlay)); } // device count auto devices = RI_MGR->devices_get(); ImGui::Text("Devices detected: %u", (unsigned int) devices.size()); // iterate devices for (auto &device : devices) { if (ImGui::TreeNode(("#" + to_string(device.id) + ": " + device.desc).c_str())) { ImGui::Text("GUID: %s", device.info.guid_str.c_str()); ImGui::Text("Output: %i", device.output_enabled); if (device.input_hz > 0 || device.input_hz_max > 0) { ImGui::Text("Input rate (cur): %.2fHz", device.input_hz); ImGui::Text("Input rate (max): %.2fHz", device.input_hz_max); } switch (device.type) { case rawinput::MOUSE: { auto mouse = device.mouseInfo; ImGui::Text("Type: Mouse"); ImGui::Text("X: %ld", mouse->pos_x); ImGui::Text("Y: %ld", mouse->pos_y); ImGui::Text("Wheel: %ld", mouse->pos_wheel); // keys std::stringstream keys; keys << "["; for (auto key : mouse->key_states) { keys << (key ? "1," : "0,"); } keys << "]"; ImGui::Text("Keys: %s", keys.str().c_str()); break; } case rawinput::KEYBOARD: { auto keyboard = device.keyboardInfo; ImGui::Text("Type: Keyboard"); // keys std::stringstream keys; keys << "["; for (size_t i = 0; i < std::size(keyboard->key_states); i++) { if (keyboard->key_states[i]) { keys << i << ","; } } keys << "]"; ImGui::Text("Keys: %s", keys.str().c_str()); break; } case rawinput::HID: { auto hid = device.hidInfo; ImGui::Text("Type: HID"); ImGui::Text("VID: %04X", hid->attributes.VendorID); ImGui::Text("PID: %04X", hid->attributes.ProductID); ImGui::Text("VER: %i", hid->attributes.VersionNumber); switch (hid->driver) { case rawinput::HIDDriver::PacDrive: ImGui::Text("Driver: PacDrive"); break; default: ImGui::Text("Driver: Default"); break; } // button states if (!hid->button_states.empty() && ImGui::TreeNode("Button States")) { size_t button_cap_name = 0; for (auto state_list : hid->button_states) { for (auto state : state_list) { ImGui::Text("%s: %i", hid->button_caps_names[button_cap_name++].c_str(), state ? 1 : 0); } } ImGui::TreePop(); } // button output states if (!hid->button_output_states.empty() && ImGui::TreeNode("Button Output States")) { size_t button_output_cap_name = 0; for (auto state_list : hid->button_output_states) { for (auto state : state_list) { ImGui::Text("%s: %i", hid->button_output_caps_names[button_output_cap_name++].c_str(), state ? 1 : 0); } } ImGui::TreePop(); } // analog states if (!hid->value_states.empty() && ImGui::TreeNode("Analog States")) { size_t value_cap_name = 0; for (auto analog_state : hid->value_states) { ImGui::Text("%s: %.2f", hid->value_caps_names[value_cap_name++].c_str(), analog_state); } ImGui::TreePop(); } // analog output states if (!hid->value_output_states.empty() && ImGui::TreeNode("Analog Output States")) { size_t value_output_cap_name = 0; for (auto analog_state : hid->value_output_states) { ImGui::Text("%s: %.2f", hid->value_output_caps_names[value_output_cap_name++].c_str(), analog_state); } ImGui::TreePop(); } break; } case rawinput::MIDI: { ImGui::Text("Type: MIDI"); break; } case rawinput::SEXTET_OUTPUT: { ImGui::Text("Type: Sextet"); break; } case rawinput::PIUIO_DEVICE: { ImGui::Text("Type: PIUIO"); break; } case rawinput::DESTROYED: { ImGui::Text("Disconnected."); break; } case rawinput::UNKNOWN: default: ImGui::Text("Type: Unknown"); } ImGui::TreePop(); } } } } void Control::touch_view() { if (ImGui::CollapsingHeader("Touch")) { // status ImGui::Text("Status: %s", is_touch_available() ? "available" : "unavailable"); // touch points ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (ImGui::TreeNode("Touch Points")) { // get touch points std::vector touch_points; touch_get_points(touch_points); for (auto &tp : touch_points) { // draw touch point ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (ImGui::TreeNode((void *) (size_t) tp.id, "TP #%lu", tp.id)) { ImGui::Text("X: %ld", tp.x); ImGui::Text("Y: %ld", tp.y); ImGui::TreePop(); } } ImGui::TreePop(); } } } void Control::lcd_view() { if (games::shared::LCD_ENABLED && ImGui::CollapsingHeader("LCD")) { ImGui::Text("Enabled: %s", games::shared::LCD_ENABLED ? "true" : "false"); ImGui::Text("CSM: %s", games::shared::LCD_CSM.c_str()); ImGui::Text("BRI: %i", games::shared::LCD_BRI); ImGui::Text("CON: %i", games::shared::LCD_CON); ImGui::Text("RED: %i", games::shared::LCD_RED); ImGui::Text("GREEN: %i", games::shared::LCD_GREEN); ImGui::Text("BLUE: %i", games::shared::LCD_BLUE); ImGui::Text("BL: %i", games::shared::LCD_BL); } } void Control::about_view() { if (ImGui::CollapsingHeader("About")) { if (ImGui::TreeNode("Changelog")) { ImGui::Separator(); if (ImGui::BeginChild("changelog", ImVec2(400, 400))) { ImGui::TextUnformatted(resutil::load_file_string(IDR_CHANGELOG).c_str()); } ImGui::EndChild(); } if (ImGui::TreeNode("Licenses")) { ImGui::Separator(); if (ImGui::BeginChild("changelog", ImVec2(400, 400), false, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar)) { ImGui::TextUnformatted(resutil::load_file_string(IDR_LICENSES).c_str()); } ImGui::EndChild(); } } } void Control::ddr_timing_view() { if (avs::game::is_model("MDX") && ImGui::CollapsingHeader("DDR Timing")) { // patches struct ddr_patch { const char *ext; const char *name; const char *format; int min; int max; size_t offset; intptr_t offset_ptr = 0; }; static struct ddr_patch PATCHES[] = { // patches for MDX-001-2019042200 { "2019042200", "Sound Offset", "%d ms", 0, 1000, 0x1CCC5 }, { "2019042200", "Render Offset", "%d ms", 0, 1000, 0x1CD0A }, { "2019042200", "Input Offset", "%d ms", 0, 1000, 0x1CCE5 }, { "2019042200", "Bomb Offset", "%d frames", 0, 10, 0x1CCC0 }, { "2019042200", "SSQ Offset", "%d ms", -1000, 1000, 0x1CCCA }, { "2019042200", "Cabinet Type", "%d", 0, 6, 0x1CDAE }, }; // check if patches available bool patches_available = false; for (auto &patch : PATCHES) { if (avs::game::is_ext(patch.ext)) { patches_available = true; break; } } // show message if no patches available if (!patches_available) { ImGui::Text("No offsets known for this version."); } else { // iterate patches for (auto &patch : PATCHES) { if (avs::game::is_ext(patch.ext)) { // check if pointer is uninitialized if (patch.offset_ptr == 0) { // get module information auto dll_path = MODULE_PATH / "gamemdx.dll"; // get dll_module auto dll_module = libutils::try_module(dll_path); if (!dll_module) { // no fatal error, might just not be loaded yet break; } // get module information MODULEINFO dll_module_info {}; if (GetModuleInformation( GetCurrentProcess(), dll_module, &dll_module_info, sizeof(MODULEINFO))) { // convert offset to RVA auto rva = libutils::offset2rva(dll_path, patch.offset); if (rva && rva != ~0) { // get data pointer patch.offset_ptr = reinterpret_cast(dll_module_info.lpBaseOfDll) + rva; } else { // invalidate patch.offset_ptr = -1; } } else { // invalidate patch.offset_ptr = -1; } } // check if pointer is valid if (patch.offset_ptr != -1) { auto *value_ptr = reinterpret_cast(patch.offset_ptr); // draw drag widget int value = *value_ptr; ImGui::DragInt(patch.name, &value, 0.2f, patch.min, patch.max, patch.format); // write value back if (value != *value_ptr) { memutils::VProtectGuard guard(value_ptr, sizeof(uint16_t)); *value_ptr = value; } } } } } } } void Control::iidx_effectors_view() { if (avs::game::is_model("LDJ") && ImGui::CollapsingHeader("IIDX Effectors")) { // effector analog entries static const std::map ANALOG_ENTRIES { { games::iidx::Analogs::VEFX, "VEFX" }, { games::iidx::Analogs::LowEQ, "LoEQ" }, { games::iidx::Analogs::HiEQ, "HiEQ" }, { games::iidx::Analogs::Filter, "Flt" }, { games::iidx::Analogs::PlayVolume, "Vol" }, }; // iterate analogs float hue = 0.f; bool overridden = false; static auto analogs = games::get_analogs(eamuse_get_game()); for (auto &[index, name] : ANALOG_ENTRIES) { // safety check if (index >= analogs->size()) { continue; } // get analog auto &analog = (*analogs)[index]; overridden |= analog.override_enabled; // push id and style ImGui::PushID((void *) name); ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4) ImColor::HSV(hue, 0.5f, 0.5f)); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4) ImColor::HSV(hue, 0.6f, 0.5f)); ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4) ImColor::HSV(hue, 0.7f, 0.5f)); ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4) ImColor::HSV(hue, 0.9f, 0.9f)); if (analog.override_enabled) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 1.f, 0.f, 1.f)); } else { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.8f, 1.f)); } // vertical slider auto new_state = analog.override_enabled ? analog.override_state : GameAPI::Analogs::getState(RI_MGR, analog); if (hue > 0.f) { ImGui::SameLine(); } ImGui::VSliderFloat("##v", ImVec2(32, 160), &new_state, 0.f, 1.f, name); if (new_state != analog.override_state) { analog.override_state = new_state; analog.override_enabled = true; } // pop id and style ImGui::PopStyleColor(5); ImGui::PopID(); // rainbow hue += 1.f / 7; } // reset button if (overridden) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 1.f, 0.f, 1.f)); if (ImGui::Button("Reset")) { for (auto &[index, name] : ANALOG_ENTRIES) { if (index < analogs->size()) { (*analogs)[index].override_enabled = false; } } } ImGui::PopStyleColor(); } } } }