Files
spice2x.github.io/src/spice2x/overlay/overlay.cpp
T
Will e452734bc1 Build a WinXP compatible version of Spice (#611)
This was a lot more background work than expected, but that is the
nature of unnecessary hobbies I guess. Most things in here are obvious,
so here's the non-obvious thoughts and notes:

1. All the XP stuff is optional, if the toolchain isn't available it
won't even try
2. The docker build is the "known good" build and now has all the
required tools for XP and checking binaries
a. If you're OK with it, can add the XP_MUST_BUILD flag to
build_docker.sh
4. New `SPICE_XP` flag to comment out blocks
5. We could technically build using the Docker image I've made for my XP
compatible LLVM toolchain, but combining the binaries into a single zip
at the end of it is somewhat tedious, so I've chosen to grab the
binaries from my release
6. I also wrote a DLL checker to make sure the final binaries are
_actually_ XP compatible, which found some issues on x86_64 (I don't
know a single game that runs on 64 bit XP, but it wasn't too much effort
so whatever). This also won't run if missing, just to save the trouble.
7. The build script is still pretty shit, I've made some minor
improvements
  a. `/usr/bin/env bash` instead of `/bin/bash` as it's more correct
  b. Adding `-it` to the docker build, so Ctrl+C actually halts it
c. Main issue is that failures don't halt, because of the custom error
catching function. This has caused a lot of frustration, maybe we can
change that

## Testing
It runs on 32 and 64 bit installs of XP. My jubeat cab is on Win10 these
days and I haven't got around to testing an actual game, but I've
distributed test builds of this to people who *have* confirmed it works
as expected.
2026-04-06 01:14:29 -07:00

742 lines
22 KiB
C++

#include "overlay.h"
#include "avs/game.h"
#include "cfg/configurator.h"
#include "games/io.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h"
#include "hooks/graphics/graphics.h"
#include "misc/eamuse.h"
#include "touch/touch.h"
#include "util/fileutils.h"
#include "util/logging.h"
#include "util/resutils.h"
#include "build/resource.h"
#include "external/imgui/backends/imgui_impl_dx9.h"
#include "overlay/imgui/impl_spice.h"
#include "overlay/imgui/impl_sw.h"
#include "window.h"
#ifdef SPICE64
#include "windows/camera_control.h"
#endif
#include "windows/card_manager.h"
#include "windows/screen_resize.h"
#include "windows/config.h"
#include "windows/control.h"
#include "windows/fps.h"
#include "windows/generic_sub.h"
#include "windows/iidx_seg.h"
#include "windows/iidx_sub.h"
#include "windows/gfdm_sub.h"
#include "windows/drs_dancefloor.h"
#include "windows/iopanel.h"
#include "windows/iopanel_ddr.h"
#include "windows/iopanel_gfdm.h"
#include "windows/iopanel_iidx.h"
#include "windows/sdvx_sub.h"
#include "windows/keypad.h"
#include "windows/log.h"
#include "windows/patch_manager.h"
#include "windows/exitprompt.cpp"
static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) \
{ return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); }
namespace overlay {
// settings
bool ENABLED = true;
bool AUTO_SHOW_FPS = false;
bool AUTO_SHOW_SUBSCREEN = false;
bool AUTO_SHOW_IOPANEL = false;
bool AUTO_SHOW_KEYPAD_P1 = false;
bool AUTO_SHOW_KEYPAD_P2 = false;
bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = false;
bool FPS_SHOULD_FLIP = false;
std::optional<uint32_t> UI_SCALE_PERCENT;
// global
std::mutex OVERLAY_MUTEX;
std::unique_ptr<overlay::SpiceOverlay> OVERLAY = nullptr;
ImFont* DSEG_FONT = nullptr;
bool SHOW_DEBUG_LOG_WINDOW = false;
ImVec2 apply_scaling_to_vector(float x, float y) {
if (!UI_SCALE_PERCENT.has_value()) {
return ImVec2(x, y);
}
return ImVec2(apply_scaling(x), apply_scaling(y));
}
ImVec2 apply_scaling_to_vector(const ImVec2& input) {
return apply_scaling_to_vector(input.x, input.y);
}
}
static void *ImGui_Alloc(size_t sz, void *user_data) {
void *data = malloc(sz);
if (!data) {
return nullptr;
}
memset(data, 0, sz);
return data;
}
static void ImGui_Free(void *data, void *user_data) {
free(data);
}
void overlay::create_d3d9(HWND hWnd, IDirect3D9 *d3d, IDirect3DDevice9 *device) {
if (!overlay::ENABLED) {
return;
}
const std::lock_guard<std::mutex> lock(OVERLAY_MUTEX);
if (!overlay::OVERLAY) {
overlay::OVERLAY = std::make_unique<overlay::SpiceOverlay>(hWnd, d3d, device);
}
}
void overlay::create_software(HWND hWnd) {
if (!overlay::ENABLED) {
return;
}
const std::lock_guard<std::mutex> lock(OVERLAY_MUTEX);
if (!overlay::OVERLAY) {
overlay::OVERLAY = std::make_unique<overlay::SpiceOverlay>(hWnd);
}
}
void overlay::destroy(HWND hWnd) {
if (!overlay::ENABLED) {
return;
}
const std::lock_guard<std::mutex> lock(OVERLAY_MUTEX);
if (overlay::OVERLAY && (hWnd == nullptr || overlay::OVERLAY->uses_window(hWnd))) {
overlay::OVERLAY.reset();
}
}
overlay::SpiceOverlay::SpiceOverlay(HWND hWnd, IDirect3D9 *d3d, IDirect3DDevice9 *device)
: renderer(OverlayRenderer::D3D9), hWnd(hWnd), d3d(d3d), device(device) {
log_info("overlay", "initializing (D3D9)");
// increment reference counts
this->d3d->AddRef();
this->device->AddRef();
// get creation parameters
HRESULT ret;
ret = this->device->GetCreationParameters(&this->creation_parameters);
if (FAILED(ret)) {
log_fatal("overlay", "GetCreationParameters failed, hr={}", FMT_HRESULT(ret));
}
// get adapter identifier
ret = this->d3d->GetAdapterIdentifier(
creation_parameters.AdapterOrdinal,
0,
&this->adapter_identifier);
if (FAILED(ret)) {
log_fatal("overlay", "GetAdapterIdentifier failed, hr={}", FMT_HRESULT(ret));
}
// init
this->init();
}
overlay::SpiceOverlay::SpiceOverlay(HWND hWnd)
: renderer(OverlayRenderer::SOFTWARE), hWnd(hWnd) {
log_info("overlay", "initializing (SOFTWARE)");
// init
this->init();
}
void overlay::SpiceOverlay::init() {
// init imgui
IMGUI_CHECKVERSION();
ImGui::SetAllocatorFunctions(ImGui_Alloc, ImGui_Free, nullptr);
ImGui::CreateContext();
ImGui::GetIO();
// set style
ImGui::StyleColorsDark();
if (this->renderer == OverlayRenderer::SOFTWARE) {
imgui_sw::make_style_fast();
ImVec4* colors = ImGui::GetStyle().Colors;
colors[ImGuiCol_Border].w = 0;
colors[ImGuiCol_Separator].w = 0.25f;
} else {
auto &style = ImGui::GetStyle();
style.WindowRounding = 0;
}
if (UI_SCALE_PERCENT.has_value()) {
const auto scale = static_cast<float>(UI_SCALE_PERCENT.value()) / 100.f;
ImGui::GetStyle().ScaleAllSizes(scale);
ImGui::GetStyle().FontScaleMain = scale;
}
// based on CrimsonVesuvius theme from
// https://github.com/ocornut/imgui/issues/707#issuecomment-4107169777
ImVec4* colors = ImGui::GetStyle().Colors;
// Text
colors[ImGuiCol_Text] = ImVec4(1.00f, 0.90f, 0.90f, 1.00f); // Slight pinkish tint to off-white
colors[ImGuiCol_TextDisabled] = ImVec4(0.35f, 0.26f, 0.26f, 1.00f);
// Backgrounds
colors[ImGuiCol_WindowBg] = ImVec4(0.08f, 0.07f, 0.07f, 1.00f); // Deep charcoal
colors[ImGuiCol_ChildBg] = ImVec4(0.10f, 0.09f, 0.09f, 1.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.07f, 0.07f, 0.96f);
// Borders
colors[ImGuiCol_Border] = ImVec4(0.25f, 0.15f, 0.15f, 0.80f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
// Frames
colors[ImGuiCol_FrameBg] = ImVec4(0.15f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.25f, 0.15f, 0.15f, 1.00f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.35f, 0.20f, 0.20f, 1.00f);
// Title Bars
colors[ImGuiCol_TitleBg] = ImVec4(0.12f, 0.08f, 0.08f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.25f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.05f, 0.05f, 0.05f, 1.00f);
// Menus
colors[ImGuiCol_MenuBarBg] = ImVec4(0.12f, 0.08f, 0.08f, 1.00f);
// Scrollbars
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.05f, 0.05f, 0.05f, 1.00f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.25f, 0.12f, 0.12f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.35f, 0.15f, 0.15f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.45f, 0.20f, 0.20f, 1.00f);
// Interactables (The High-Intensity Red)
colors[ImGuiCol_CheckMark] = ImVec4(0.85f, 0.15f, 0.15f, 1.00f); // Sharp Red
colors[ImGuiCol_SliderGrab] = ImVec4(0.60f, 0.12f, 0.12f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.85f, 0.15f, 0.15f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.30f, 0.12f, 0.12f, 1.00f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.50f, 0.18f, 0.18f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.70f, 0.25f, 0.25f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.30f, 0.12f, 0.12f, 1.00f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.50f, 0.18f, 0.18f, 1.00f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.70f, 0.25f, 0.25f, 1.00f);
// Tabs
colors[ImGuiCol_Tab] = ImVec4(0.15f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_TabHovered] = ImVec4(0.50f, 0.18f, 0.18f, 1.00f);
colors[ImGuiCol_TabActive] = ImVec4(0.35f, 0.12f, 0.12f, 1.00f);
// Misc
colors[ImGuiCol_PlotLines] = ImVec4(0.85f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.85f, 0.15f, 0.15f, 0.35f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.85f, 0.15f, 0.15f, 1.00f);
// spice custom
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.025f);
colors[ImGuiCol_Separator] = ImVec4(0.32f, 0.22f, 0.22f, 1.00f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.42f, 0.22f, 0.22f, 1.00f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.52f, 0.22f, 0.22f, 1.00f);
#ifdef IMGUI_HAS_DOCK
colors[ImGuiCol_DockingPreview] = ImVec4(0.85f, 0.15f, 0.15f, 0.40f);
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.08f, 0.07f, 0.07f, 1.00f);
#endif
// configure IO
auto &io = ImGui::GetIO();
io.UserData = this;
io.ConfigFlags = ImGuiConfigFlags_NavEnableKeyboard
| ImGuiConfigFlags_NavEnableGamepad
| ImGuiConfigFlags_NavEnableSetMousePos;
if (!cfg::CONFIGURATOR_STANDALONE) {
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
}
if (is_touch_available("SpiceOverlay::init")) {
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
}
// temporarily turn this off as it can cause crashes during font load failures
// turns back on in ImGui_ImplSpice_Init
io.ConfigErrorRecoveryEnableTooltip = false;
io.MouseDrawCursor = !GRAPHICS_SHOW_CURSOR;
// disable config
io.IniFilename = nullptr;
// allow CTRL+WHEEL scaling
io.FontAllowUserScaling = true;
// add default font
io.Fonts->AddFontDefaultBitmap();
// add fallback fonts for missing glyph ranges
ImFontConfig config {};
config.MergeMode = true;
log_misc("overlay", "loading fonts, failures are not fatal");
add_font("simsun.ttc", &config, io.Fonts->GetGlyphRangesChineseSimplifiedCommon());
add_font("arial.ttf", &config, io.Fonts->GetGlyphRangesCyrillic());
add_font("meiryu.ttc", &config, io.Fonts->GetGlyphRangesJapanese());
add_font("meiryo.ttc", &config, io.Fonts->GetGlyphRangesJapanese());
add_font("gulim.ttc", &config, io.Fonts->GetGlyphRangesKorean());
add_font("cordia.ttf", &config, io.Fonts->GetGlyphRangesThai());
add_font("arial.ttf", &config, io.Fonts->GetGlyphRangesVietnamese());
// add special font
if (avs::game::is_model("LDJ")) {
DWORD size;
ImFontConfig config {};
config.FontDataOwnedByAtlas = false;
auto buffer = resutil::load_file(IDR_DSEGFONT, &size);
DSEG_FONT = io.Fonts->AddFontFromMemoryTTF((void *)buffer, size,
overlay::windows::IIDX_SEGMENT_FONT_SIZE);
}
// init implementation
ImGui_ImplSpice_Init(this->hWnd);
switch (this->renderer) {
case OverlayRenderer::D3D9:
ImGui_ImplDX9_Init(this->device);
break;
case OverlayRenderer::SOFTWARE:
imgui_sw::bind_imgui_painting();
break;
}
// create empty frame
switch (this->renderer) {
case OverlayRenderer::D3D9:
ImGui_ImplDX9_NewFrame();
break;
case OverlayRenderer::SOFTWARE:
break;
}
ImGui_ImplSpice_NewFrame();
ImGui::NewFrame();
ImGui::EndFrame();
// fix navigation buttons causing crash on overlay activation
ImGui::Begin("Temp");
ImGui::End();
bool set_overlay_active = false;
// referenced windows
this->window_add(window_fps = new overlay::windows::FPS(this));
if (!cfg::CONFIGURATOR_STANDALONE && AUTO_SHOW_FPS) {
window_fps->set_active(true);
set_overlay_active = true;
}
this->window_add(window_main_menu = new overlay::windows::ExitPrompt(this));
// add default windows
this->window_add(window_config = new overlay::windows::Config(this));
this->window_add(window_control = new overlay::windows::Control(this));
this->window_add(window_log = new overlay::windows::Log(this));
#if SPICE64 && !SPICE_XP
if (avs::game::is_model("LDJ")) {
this->window_add(window_camera = new overlay::windows::CameraControl(this));
}
#endif
this->window_add(window_cards = new overlay::windows::CardManager(this));
if (!cfg::CONFIGURATOR_STANDALONE) {
this->window_add(window_resize = new overlay::windows::ScreenResize(this));
}
this->window_add(new overlay::windows::PatchManager(this));
{
window_keypad1 = new overlay::windows::Keypad(this, 0);
this->window_add(window_keypad1);
if (!cfg::CONFIGURATOR_STANDALONE && AUTO_SHOW_KEYPAD_P1) {
window_keypad1->set_active(true);
set_overlay_active = true;
}
}
if (eamuse_get_game_keypads() > 1) {
window_keypad2 = new overlay::windows::Keypad(this, 1);
this->window_add(window_keypad2);
if (!cfg::CONFIGURATOR_STANDALONE && AUTO_SHOW_KEYPAD_P2) {
window_keypad2->set_active(true);
set_overlay_active = true;
}
}
// IO panel needs to know what game is running
if (!cfg::CONFIGURATOR_STANDALONE) {
window_iopanel = nullptr;
if (avs::game::is_model("LDJ")) {
window_iopanel = new overlay::windows::IIDXIOPanel(this);
} else if (avs::game::is_model("MDX")) {
window_iopanel = new overlay::windows::DDRIOPanel(this);
} else if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"}) &&
!games::gitadora::is_arena_model()) {
window_iopanel = new overlay::windows::GitadoraIOPanel(this);
} else {
window_iopanel = new overlay::windows::IOPanel(this);
}
if (window_iopanel) {
this->window_add(window_iopanel);
if (AUTO_SHOW_IOPANEL) {
window_iopanel->set_active(true);
set_overlay_active = true;
}
}
}
// subscreens need DirectX, so don't try to initialize them in standalone
if (!cfg::CONFIGURATOR_STANDALONE) {
window_sub = nullptr;
if (avs::game::is_model("LDJ")) {
if (games::iidx::TDJ_MODE) {
window_sub = new overlay::windows::IIDXSubScreen(this);
} else {
window_sub = new overlay::windows::IIDXSegmentDisplay(this);
}
} else if (avs::game::is_model("REC")) {
window_sub = new overlay::windows::DRSDanceFloorDisplay(this);
} else if (avs::game::is_model("KFC")) {
window_sub = new overlay::windows::SDVXSubScreen(this);
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
window_sub = new overlay::windows::GitaDoraSubScreen(this);
}
if (window_sub) {
this->window_add(window_sub);
if (AUTO_SHOW_SUBSCREEN) {
window_sub->set_active(true);
set_overlay_active = true;
}
}
}
if (set_overlay_active) {
this->set_active(true);
}
}
overlay::SpiceOverlay::~SpiceOverlay() {
// imgui shutdown
ImGui_ImplSpice_Shutdown();
switch (this->renderer) {
case OverlayRenderer::D3D9:
ImGui_ImplDX9_Shutdown();
// drop references
this->device->Release();
this->d3d->Release();
break;
case OverlayRenderer::SOFTWARE:
imgui_sw::unbind_imgui_painting();
break;
}
ImGui::DestroyContext();
}
void overlay::SpiceOverlay::window_add(Window *wnd) {
this->windows.emplace_back(std::unique_ptr<Window>(wnd));
}
void overlay::SpiceOverlay::new_frame() {
// update implementation
ImGui_ImplSpice_NewFrame();
this->total_elapsed += ImGui::GetIO().DeltaTime;
// check if inactive
if (!this->active) {
return;
}
// init frame
switch (this->renderer) {
case OverlayRenderer::D3D9:
ImGui_ImplDX9_NewFrame();
break;
case OverlayRenderer::SOFTWARE:
ImGui_ImplSpice_UpdateDisplaySize();
break;
}
ImGui::NewFrame();
// build windows
for (auto &window : this->windows) {
window->build();
}
if (SHOW_DEBUG_LOG_WINDOW) {
ImGui::ShowDebugLogWindow(&SHOW_DEBUG_LOG_WINDOW);
}
// end frame
ImGui::EndFrame();
}
void overlay::SpiceOverlay::render() {
// check if inactive
if (!this->active) {
return;
}
// imgui render
ImGui::Render();
// implementation render
switch (this->renderer) {
case OverlayRenderer::D3D9:
ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());
break;
case OverlayRenderer::SOFTWARE: {
// get display metrics
auto &io = ImGui::GetIO();
auto width = static_cast<size_t>(std::ceil(io.DisplaySize.x));
auto height = static_cast<size_t>(std::ceil(io.DisplaySize.y));
auto pixels = width * height;
// make sure buffer is big enough
if (this->pixel_data.size() < pixels) {
this->pixel_data.resize(pixels, 0);
}
// reset buffer
memset(&this->pixel_data[0], 0, width * height * sizeof(uint32_t));
// render to pixel data
imgui_sw::SwOptions options {
.optimize_text = true,
.optimize_rectangles = true,
};
imgui_sw::paint_imgui(&this->pixel_data[0], width, height, options);
pixel_data_width = width;
pixel_data_height = height;
break;
}
}
for (auto &window : this->windows) {
window->after_render();
}
}
void overlay::SpiceOverlay::update() {
// check overlay toggle
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
bool toggle_down_new = overlay_buttons
&& this->hotkeys_triggered()
&& GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(games::OverlayButtons::ToggleOverlay));
if (toggle_down_new && !this->toggle_down) {
toggle_active(true);
}
this->toggle_down = toggle_down_new;
// check main menu
const auto main_menu_down_new = overlay_buttons
&& this->hotkeys_triggered()
&& GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(games::OverlayButtons::ToggleMainMenu));
if (main_menu_down_new && !this->main_menu_down) {
show_main_menu();
}
this->main_menu_down = main_menu_down_new;
// update windows
for (auto &window : this->windows) {
window->update();
}
// deactivate if no windows are shown
bool window_active = false;
for (auto &window : this->windows) {
if (window->get_active()) {
window_active = true;
break;
}
}
if (!window_active) {
this->set_active(false);
}
}
bool overlay::SpiceOverlay::update_cursor() {
return ImGui_ImplSpice_UpdateMouseCursor();
}
void overlay::SpiceOverlay::toggle_active(bool overlay_key) {
// invert active state
this->active = !this->active;
// get rid of main menu if it was visible
if (this->window_main_menu) {
this->window_main_menu->set_active(false);
}
// show FPS window if toggled with overlay key
if (overlay_key) {
this->window_fps->set_active(this->active);
}
}
void overlay::SpiceOverlay::show_main_menu() {
if (!this->window_main_menu) {
return;
}
if (this->window_main_menu->get_active()) {
// window already visible - close the window
this->window_main_menu->set_active(false);
return;
}
if (ImGui::IsPopupOpen(0, ImGuiPopupFlags_AnyPopup)) {
return;
}
if (this->get_active()) {
if (!ImGui::IsAnyItemActive() && !ImGui::IsAnyItemFocused()) {
this->window_main_menu->set_active(true);
}
} else {
this->set_active(true);
this->window_main_menu->set_active(true);
}
}
void overlay::SpiceOverlay::set_active(bool new_active) {
// toggle if different
if (this->active != new_active) {
this->toggle_active();
}
}
bool overlay::SpiceOverlay::get_active() {
return this->active;
}
bool overlay::SpiceOverlay::has_focus() {
return this->get_active() && ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow);
}
bool overlay::SpiceOverlay::hotkeys_triggered() {
// check if disabled first
if (!this->hotkeys_enable) {
return false;
}
// get buttons
auto buttons = games::get_buttons_overlay(eamuse_get_game());
if (!buttons) {
return false;
}
auto &hotkey1 = buttons->at(games::OverlayButtons::HotkeyEnable1);
auto &hotkey2 = buttons->at(games::OverlayButtons::HotkeyEnable2);
auto &toggle = buttons->at(games::OverlayButtons::HotkeyToggle);
// check hotkey toggle
auto toggle_state = GameAPI::Buttons::getState(RI_MGR, toggle);
if (toggle_state) {
if (!this->hotkey_toggle_last) {
this->hotkey_toggle_last = true;
this->hotkey_toggle = !this->hotkey_toggle;
}
} else {
this->hotkey_toggle_last = false;
}
// hotkey toggle overrides hotkey enable button states
if (hotkey_toggle) {
return true;
}
// check hotkey enable buttons
bool triggered = true;
if (hotkey1.isSet() && !GameAPI::Buttons::getState(RI_MGR, hotkey1)) {
triggered = false;
}
if (hotkey2.isSet() && !GameAPI::Buttons::getState(RI_MGR, hotkey2)) {
triggered = false;
}
return triggered;
}
void overlay::SpiceOverlay::reset_invalidate() {
ImGui_ImplDX9_InvalidateDeviceObjects();
}
void overlay::SpiceOverlay::reset_recreate() {
ImGui_ImplDX9_CreateDeviceObjects();
}
void overlay::SpiceOverlay::input_char(unsigned int c) {
// add character to ImGui
ImGui::GetIO().AddInputCharacter(c);
}
uint32_t *overlay::SpiceOverlay::sw_get_pixel_data(int *width, int *height) {
// check if active
if (!this->active) {
*width = 0;
*height = 0;
return nullptr;
}
// ensure buffer has the right size
const size_t total_size = this->pixel_data_width * this->pixel_data_height;
if (this->pixel_data.size() < total_size) {
this->pixel_data.resize(total_size, 0);
}
// check for empty surface
if (this->pixel_data.empty()) {
*width = 0;
*height = 0;
return nullptr;
}
// copy and return pointer to data
*width = this->pixel_data_width;
*height = this->pixel_data_height;
return &this->pixel_data[0];
}
void overlay::SpiceOverlay::add_font(const char* font, ImFontConfig* config, const ImWchar* glyphs) {
CHAR fonts_dir[MAX_PATH];
ExpandEnvironmentStringsA(R"(%SYSTEMROOT%\Fonts\)", fonts_dir, MAX_PATH);
std::filesystem::path full_path = fonts_dir;
full_path += font;
if (fileutils::file_exists(full_path)) {
log_misc("overlay", "loading font: {}", full_path.string());
ImGui::GetIO().Fonts->AddFontFromFileTTF(
full_path.string().c_str(),
13.0f,
config,
glyphs);
} else {
log_misc("overlay", "font not found: {}", full_path.string());
}
}