Initial re-upload of spice2x-24-08-24
This commit is contained in:
70
overlay/imgui/extensions.cpp
Normal file
70
overlay/imgui/extensions.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "extensions.h"
|
||||
#include <cmath>
|
||||
|
||||
#include "external/imgui/imgui.h"
|
||||
|
||||
|
||||
namespace ImGui {
|
||||
|
||||
void HelpMarker(const char* desc) {
|
||||
ImGui::TextDisabled("(?)");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
|
||||
ImGui::TextUnformatted(desc);
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
void WarnMarker(const char* desc, const char* warn) {
|
||||
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImVec4(1.f, 1.f, 0.f, 1.f));
|
||||
ImGui::TextDisabled("(!)");
|
||||
ImGui::PopStyleColor();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
|
||||
if (desc) {
|
||||
ImGui::TextUnformatted(desc);
|
||||
ImGui::TextUnformatted("");
|
||||
}
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 0.f, 1.f));
|
||||
if (warn) {
|
||||
ImGui::TextUnformatted("WARNING:");
|
||||
ImGui::TextUnformatted(warn);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
void DummyMarker() {
|
||||
// dummy marker that is the same width as HelpMarker/WarnMarker.
|
||||
ImGui::Dummy(ImVec2(22, 0));
|
||||
}
|
||||
|
||||
void Knob(float fraction, float size, float thickness, float pos_x, float pos_y) {
|
||||
|
||||
// get values
|
||||
auto radius = size * 0.5f;
|
||||
auto pos = ImGui::GetCursorScreenPos();
|
||||
if (pos_x >= 0) pos.x = pos_x;
|
||||
if (pos_y >= 0) pos.y = pos_y;
|
||||
auto center = ImVec2(pos.x + radius, pos.y + radius);
|
||||
auto draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
// dummy for spacing knob with other content
|
||||
if (pos_x < 0 && pos_y < 0) {
|
||||
ImGui::Dummy(ImVec2(size, size));
|
||||
}
|
||||
|
||||
// draw knob
|
||||
auto angle = (fraction + 0.25f) * (3.141592f * 2);
|
||||
draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32(ImGuiCol_FrameBg), 16);
|
||||
draw_list->AddLine(center,
|
||||
ImVec2(center.x + cosf(angle) * radius, center.y + sinf(angle) * radius),
|
||||
ImGui::GetColorU32(ImGuiCol_PlotHistogram),
|
||||
thickness);
|
||||
}
|
||||
}
|
||||
10
overlay/imgui/extensions.h
Normal file
10
overlay/imgui/extensions.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace ImGui {
|
||||
|
||||
void HelpMarker(const char* desc);
|
||||
void WarnMarker(const char* desc, const char* warn);
|
||||
void DummyMarker();
|
||||
void Knob(float fraction, float size, float thickness = 2.f,
|
||||
float pos_x = -1.f, float pos_y = -1.f);
|
||||
}
|
||||
360
overlay/imgui/impl_dx9.cpp
Normal file
360
overlay/imgui/impl_dx9.cpp
Normal file
@@ -0,0 +1,360 @@
|
||||
// dear imgui: Renderer for DirectX9
|
||||
// This needs to be used along with a Platform Binding (e.g. Win32)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp.
|
||||
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices.
|
||||
|
||||
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
|
||||
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
|
||||
// https://github.com/ocornut/imgui
|
||||
|
||||
// CHANGELOG
|
||||
// (minor and older changes stripped away, please see git history for details)
|
||||
// 2019-05-29: DirectX9: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
|
||||
// 2019-04-30: DirectX9: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
|
||||
// 2019-03-29: Misc: Fixed erroneous assert in ImGui_ImplDX9_InvalidateDeviceObjects().
|
||||
// 2019-01-16: Misc: Disabled fog before drawing UI's. Fixes issue #2288.
|
||||
// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
|
||||
// 2018-06-08: Misc: Extracted imgui_impl_dx9.cpp/.h away from the old combined DX9+Win32 example.
|
||||
// 2018-06-08: DirectX9: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
|
||||
// 2018-05-07: Render: Saving/restoring Transform because they don't seem to be included in the StateBlock. Setting shading mode to Gouraud.
|
||||
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX9_RenderDrawData() in the .h file so you can call it yourself.
|
||||
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
|
||||
|
||||
#include "impl_dx9.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
// DirectX
|
||||
#include <d3d9.h>
|
||||
|
||||
#include "external/imgui/imgui.h"
|
||||
|
||||
// allow std::min use
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
// DirectX data
|
||||
static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
|
||||
static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
|
||||
static LPDIRECT3DINDEXBUFFER9 g_pIB = NULL;
|
||||
static LPDIRECT3DTEXTURE9 g_FontTexture = NULL;
|
||||
static int g_VertexBufferSize = 5000, g_IndexBufferSize = 10000;
|
||||
|
||||
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1)
|
||||
|
||||
// Render function.
|
||||
// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop)
|
||||
void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data) {
|
||||
|
||||
// Avoid rendering when minimized
|
||||
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
|
||||
return;
|
||||
|
||||
// Create and grow buffers if needed
|
||||
if (!g_pVB || g_VertexBufferSize < draw_data->TotalVtxCount) {
|
||||
if (g_pVB) {
|
||||
g_pVB->Release();
|
||||
g_pVB = NULL;
|
||||
}
|
||||
g_VertexBufferSize = draw_data->TotalVtxCount + 5000;
|
||||
if (g_pd3dDevice->CreateVertexBuffer(g_VertexBufferSize * sizeof(ImDrawVert),
|
||||
D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX,
|
||||
D3DPOOL_DEFAULT, &g_pVB, NULL) < 0)
|
||||
return;
|
||||
}
|
||||
if (!g_pIB || g_IndexBufferSize < draw_data->TotalIdxCount) {
|
||||
if (g_pIB) {
|
||||
g_pIB->Release();
|
||||
g_pIB = NULL;
|
||||
}
|
||||
g_IndexBufferSize = draw_data->TotalIdxCount + 10000;
|
||||
if (g_pd3dDevice->CreateIndexBuffer(g_IndexBufferSize * sizeof(ImDrawIdx),
|
||||
D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY,
|
||||
sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32,
|
||||
D3DPOOL_DEFAULT, &g_pIB, NULL) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// Backup the DX9 state
|
||||
IDirect3DStateBlock9 *d3d9_state_block = NULL;
|
||||
if (g_pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block) < 0)
|
||||
return;
|
||||
|
||||
// Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to)
|
||||
D3DMATRIX last_world, last_view, last_projection;
|
||||
g_pd3dDevice->GetTransform(D3DTS_WORLD, &last_world);
|
||||
g_pd3dDevice->GetTransform(D3DTS_VIEW, &last_view);
|
||||
g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &last_projection);
|
||||
|
||||
// Copy all vertices into a single contiguous buffer
|
||||
ImDrawVert *vtx_dst;
|
||||
ImDrawIdx *idx_dst;
|
||||
if (g_pVB->Lock(0, (UINT) (draw_data->TotalVtxCount * sizeof(ImDrawVert)), (void **) &vtx_dst,
|
||||
D3DLOCK_DISCARD) < 0)
|
||||
return;
|
||||
if (g_pIB->Lock(0, (UINT) (draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void **) &idx_dst,
|
||||
D3DLOCK_DISCARD) < 0)
|
||||
return;
|
||||
for (int n = 0; n < draw_data->CmdListsCount; n++) {
|
||||
const ImDrawList *cmd_list = draw_data->CmdLists[n];
|
||||
memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
|
||||
memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
|
||||
vtx_dst += cmd_list->VtxBuffer.Size;
|
||||
idx_dst += cmd_list->IdxBuffer.Size;
|
||||
}
|
||||
g_pVB->Unlock();
|
||||
g_pIB->Unlock();
|
||||
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(ImDrawVert));
|
||||
g_pd3dDevice->SetIndices(g_pIB);
|
||||
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
|
||||
|
||||
// Setup viewport
|
||||
D3DVIEWPORT9 vp;
|
||||
vp.X = vp.Y = 0;
|
||||
vp.Width = (DWORD) draw_data->DisplaySize.x;
|
||||
vp.Height = (DWORD) draw_data->DisplaySize.y;
|
||||
vp.MinZ = 0.0f;
|
||||
vp.MaxZ = 1.0f;
|
||||
g_pd3dDevice->SetViewport(&vp);
|
||||
|
||||
g_pd3dDevice->SetPixelShader(nullptr);
|
||||
g_pd3dDevice->SetVertexShader(nullptr);
|
||||
|
||||
D3DCAPS9 caps {};
|
||||
if (FAILED(g_pd3dDevice->GetDeviceCaps(&caps))) {
|
||||
caps.NumSimultaneousRTs = 0UL;
|
||||
}
|
||||
|
||||
IDirect3DSurface9 *back_buffer = nullptr;
|
||||
IDirect3DSurface9 *depth_stencil = nullptr;
|
||||
IDirect3DSurface9 *render_targets[8];
|
||||
|
||||
// save all previous render target state
|
||||
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) {
|
||||
if (FAILED(g_pd3dDevice->GetRenderTarget(target, &render_targets[target]))) {
|
||||
render_targets[target] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// get the previous depth stencil
|
||||
if (FAILED(g_pd3dDevice->GetDepthStencilSurface(&depth_stencil))) {
|
||||
depth_stencil = nullptr;
|
||||
}
|
||||
|
||||
// set the back buffer as the current render target
|
||||
if (SUCCEEDED(g_pd3dDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &back_buffer))) {
|
||||
g_pd3dDevice->SetRenderTarget(0, back_buffer);
|
||||
g_pd3dDevice->SetDepthStencilSurface(nullptr);
|
||||
|
||||
for (size_t target = 1; target < std::min(8UL, caps.NumSimultaneousRTs); target++) {
|
||||
g_pd3dDevice->SetRenderTarget(target, nullptr);
|
||||
}
|
||||
} else {
|
||||
back_buffer = nullptr;
|
||||
}
|
||||
|
||||
// Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient)
|
||||
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, false);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, false);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, true);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_FOGENABLE, false);
|
||||
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
|
||||
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
|
||||
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
|
||||
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
|
||||
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
|
||||
|
||||
// Setup orthographic projection matrix
|
||||
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
|
||||
// Being agnostic of whether <d3dx9.h> or <DirectXMath.h> can be used, we aren't relying on D3DXMatrixIdentity()/D3DXMatrixOrthoOffCenterLH() or DirectX::XMMatrixIdentity()/DirectX::XMMatrixOrthographicOffCenterLH()
|
||||
{
|
||||
float L = draw_data->DisplayPos.x + 0.5f;
|
||||
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f;
|
||||
float T = draw_data->DisplayPos.y + 0.5f;
|
||||
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y + 0.5f;
|
||||
D3DMATRIX mat_identity = {{{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}};
|
||||
D3DMATRIX mat_projection =
|
||||
{{{
|
||||
2.0f / (R - L), 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 2.0f / (T - B), 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.5f, 0.0f,
|
||||
(L + R) / (L - R), (T + B) / (B - T), 0.5f, 1.0f
|
||||
}}};
|
||||
g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity);
|
||||
g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity);
|
||||
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection);
|
||||
}
|
||||
|
||||
// Render command lists
|
||||
// (Because we merged all buffers into a single one, we maintain our own offset into them)
|
||||
int global_vtx_offset = 0;
|
||||
int global_idx_offset = 0;
|
||||
ImVec2 clip_off = draw_data->DisplayPos;
|
||||
for (int n = 0; n < draw_data->CmdListsCount; n++) {
|
||||
const ImDrawList *cmd_list = draw_data->CmdLists[n];
|
||||
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) {
|
||||
const ImDrawCmd *pcmd = &cmd_list->CmdBuffer[cmd_i];
|
||||
if (pcmd->UserCallback != NULL) {
|
||||
pcmd->UserCallback(cmd_list, pcmd);
|
||||
} else {
|
||||
const RECT r = {(LONG) (pcmd->ClipRect.x - clip_off.x), (LONG) (pcmd->ClipRect.y - clip_off.y),
|
||||
(LONG) (pcmd->ClipRect.z - clip_off.x), (LONG) (pcmd->ClipRect.w - clip_off.y)};
|
||||
auto texture = reinterpret_cast<IDirect3DBaseTexture9 *>(pcmd->TextureId);
|
||||
g_pd3dDevice->SetTexture(0, texture);
|
||||
g_pd3dDevice->SetScissorRect(&r);
|
||||
g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,
|
||||
pcmd->VtxOffset + global_vtx_offset, 0,
|
||||
(UINT) cmd_list->VtxBuffer.Size,
|
||||
pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
|
||||
}
|
||||
}
|
||||
global_idx_offset += cmd_list->IdxBuffer.Size;
|
||||
global_vtx_offset += cmd_list->VtxBuffer.Size;
|
||||
}
|
||||
|
||||
if (back_buffer) {
|
||||
back_buffer->Release();
|
||||
back_buffer = nullptr;
|
||||
}
|
||||
|
||||
// restore previous depth stencil
|
||||
if (depth_stencil) {
|
||||
g_pd3dDevice->SetDepthStencilSurface(depth_stencil);
|
||||
depth_stencil->Release();
|
||||
depth_stencil = nullptr;
|
||||
}
|
||||
|
||||
// restore all render target state
|
||||
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) {
|
||||
auto render_target = render_targets[target];
|
||||
|
||||
if (render_target) {
|
||||
g_pd3dDevice->SetRenderTarget(target, render_target);
|
||||
render_target->Release();
|
||||
}
|
||||
}
|
||||
|
||||
// restore the DX9 transform
|
||||
g_pd3dDevice->SetTransform(D3DTS_WORLD, &last_world);
|
||||
g_pd3dDevice->SetTransform(D3DTS_VIEW, &last_view);
|
||||
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &last_projection);
|
||||
|
||||
// restore the DX9 state
|
||||
d3d9_state_block->Apply();
|
||||
d3d9_state_block->Release();
|
||||
}
|
||||
|
||||
bool ImGui_ImplDX9_Init(IDirect3DDevice9 *device) {
|
||||
|
||||
// Setup back-end capabilities flags
|
||||
auto &io = ImGui::GetIO();
|
||||
io.BackendRendererName = "imgui_impl_dx9";
|
||||
|
||||
// We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
|
||||
|
||||
g_pd3dDevice = device;
|
||||
g_pd3dDevice->AddRef();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplDX9_Shutdown() {
|
||||
ImGui_ImplDX9_InvalidateDeviceObjects();
|
||||
|
||||
if (g_pd3dDevice) {
|
||||
g_pd3dDevice->Release();
|
||||
g_pd3dDevice = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool ImGui_ImplDX9_CreateFontsTexture() {
|
||||
// Build texture atlas
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
unsigned char *pixels;
|
||||
int width, height, bytes_per_pixel;
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel);
|
||||
|
||||
// Upload texture to graphics system
|
||||
g_FontTexture = NULL;
|
||||
if (g_pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8,
|
||||
D3DPOOL_DEFAULT, &g_FontTexture, NULL) < 0)
|
||||
return false;
|
||||
D3DLOCKED_RECT tex_locked_rect;
|
||||
if (g_FontTexture->LockRect(0, &tex_locked_rect, NULL, 0) != D3D_OK)
|
||||
return false;
|
||||
for (int y = 0; y < height; y++)
|
||||
memcpy((unsigned char *) tex_locked_rect.pBits + tex_locked_rect.Pitch * y,
|
||||
pixels + (width * bytes_per_pixel) * y, (width * bytes_per_pixel));
|
||||
g_FontTexture->UnlockRect(0);
|
||||
|
||||
// Store our identifier
|
||||
io.Fonts->TexID = (ImTextureID) g_FontTexture;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImGui_ImplDX9_CreateDeviceObjects() {
|
||||
if (!g_pd3dDevice) {
|
||||
return false;
|
||||
}
|
||||
return ImGui_ImplDX9_CreateFontsTexture();
|
||||
}
|
||||
|
||||
void ImGui_ImplDX9_InvalidateDeviceObjects() {
|
||||
if (!g_pd3dDevice)
|
||||
return;
|
||||
if (g_pVB) {
|
||||
g_pVB->Release();
|
||||
g_pVB = NULL;
|
||||
}
|
||||
if (g_pIB) {
|
||||
g_pIB->Release();
|
||||
g_pIB = NULL;
|
||||
}
|
||||
if (g_FontTexture) {
|
||||
g_FontTexture->Release();
|
||||
g_FontTexture = NULL;
|
||||
ImGui::GetIO().Fonts->TexID = NULL;
|
||||
} // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well.
|
||||
}
|
||||
|
||||
void ImGui_ImplDX9_NewFrame() {
|
||||
if (!g_FontTexture) {
|
||||
ImGui_ImplDX9_CreateDeviceObjects();
|
||||
}
|
||||
|
||||
IDirect3DSwapChain9 *swap_chain = nullptr;
|
||||
if (SUCCEEDED(g_pd3dDevice->GetSwapChain(0, &swap_chain))) {
|
||||
auto &io = ImGui::GetIO();
|
||||
|
||||
D3DPRESENT_PARAMETERS present_params {};
|
||||
|
||||
if (SUCCEEDED(swap_chain->GetPresentParameters(&present_params))) {
|
||||
if (present_params.BackBufferWidth != 0 && present_params.BackBufferHeight != 0) {
|
||||
io.DisplaySize.x = static_cast<float>(present_params.BackBufferWidth);
|
||||
io.DisplaySize.y = static_cast<float>(present_params.BackBufferHeight);
|
||||
} else {
|
||||
RECT rect {};
|
||||
GetClientRect(present_params.hDeviceWindow, &rect);
|
||||
|
||||
io.DisplaySize.x = static_cast<float>(rect.right - rect.left);
|
||||
io.DisplaySize.y = static_cast<float>(rect.bottom - rect.top);
|
||||
}
|
||||
}
|
||||
|
||||
swap_chain->Release();
|
||||
}
|
||||
}
|
||||
25
overlay/imgui/impl_dx9.h
Normal file
25
overlay/imgui/impl_dx9.h
Normal file
@@ -0,0 +1,25 @@
|
||||
// dear imgui: Renderer for DirectX9
|
||||
// This needs to be used along with a Platform Binding (e.g. Win32)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp.
|
||||
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices.
|
||||
|
||||
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
|
||||
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
|
||||
// https://github.com/ocornut/imgui
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "external/imgui/imgui.h"
|
||||
|
||||
struct IDirect3DDevice9;
|
||||
|
||||
IMGUI_IMPL_API bool ImGui_ImplDX9_Init(IDirect3DDevice9 *device);
|
||||
IMGUI_IMPL_API void ImGui_ImplDX9_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplDX9_NewFrame();
|
||||
IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data);
|
||||
|
||||
// Use if you want to reset your rendering device without losing ImGui state.
|
||||
IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects();
|
||||
IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects();
|
||||
377
overlay/imgui/impl_spice.cpp
Normal file
377
overlay/imgui/impl_spice.cpp
Normal file
@@ -0,0 +1,377 @@
|
||||
#include "impl_spice.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "games/io.h"
|
||||
#include "launcher/launcher.h"
|
||||
#include "launcher/superexit.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
// state
|
||||
static HWND g_hWnd = nullptr;
|
||||
static INT64 g_Time = 0;
|
||||
static INT64 g_TicksPerSecond = 0;
|
||||
static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_COUNT;
|
||||
|
||||
bool ImGui_ImplSpice_Init(HWND hWnd) {
|
||||
log_misc("imgui_impl_spice", "init");
|
||||
|
||||
// check if already initialized
|
||||
if (g_hWnd != nullptr) {
|
||||
if (g_hWnd == hWnd) {
|
||||
return true;
|
||||
} else {
|
||||
ImGui_ImplSpice_Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// init performance stuff
|
||||
if (!::QueryPerformanceFrequency((LARGE_INTEGER *)&g_TicksPerSecond))
|
||||
return false;
|
||||
if (!::QueryPerformanceCounter((LARGE_INTEGER *)&g_Time))
|
||||
return false;
|
||||
|
||||
// setup back-end capabilities flags
|
||||
g_hWnd = hWnd;
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;
|
||||
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;
|
||||
io.BackendPlatformName = "imgui_impl_spice";
|
||||
|
||||
// keyboard mapping
|
||||
io.KeyMap[ImGuiKey_Tab] = VK_TAB;
|
||||
io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT;
|
||||
io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT;
|
||||
io.KeyMap[ImGuiKey_UpArrow] = VK_UP;
|
||||
io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN;
|
||||
io.KeyMap[ImGuiKey_PageUp] = VK_PRIOR;
|
||||
io.KeyMap[ImGuiKey_PageDown] = VK_NEXT;
|
||||
io.KeyMap[ImGuiKey_Home] = VK_HOME;
|
||||
io.KeyMap[ImGuiKey_End] = VK_END;
|
||||
io.KeyMap[ImGuiKey_Insert] = VK_INSERT;
|
||||
io.KeyMap[ImGuiKey_Delete] = VK_DELETE;
|
||||
io.KeyMap[ImGuiKey_Backspace] = VK_BACK;
|
||||
io.KeyMap[ImGuiKey_Space] = VK_SPACE;
|
||||
io.KeyMap[ImGuiKey_Enter] = VK_RETURN;
|
||||
io.KeyMap[ImGuiKey_Escape] = VK_ESCAPE;
|
||||
io.KeyMap[ImGuiKey_KeyPadEnter] = VK_RETURN;
|
||||
io.KeyMap[ImGuiKey_A] = 'A';
|
||||
io.KeyMap[ImGuiKey_C] = 'C';
|
||||
io.KeyMap[ImGuiKey_V] = 'V';
|
||||
io.KeyMap[ImGuiKey_X] = 'X';
|
||||
io.KeyMap[ImGuiKey_Y] = 'Y';
|
||||
io.KeyMap[ImGuiKey_Z] = 'Z';
|
||||
|
||||
// get display size
|
||||
ImGui_ImplSpice_UpdateDisplaySize();
|
||||
|
||||
// return success
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplSpice_Shutdown() {
|
||||
log_misc("imgui_impl_spice", "shutdown");
|
||||
|
||||
// reset window handle
|
||||
g_hWnd = nullptr;
|
||||
}
|
||||
|
||||
void ImGui_ImplSpice_UpdateDisplaySize() {
|
||||
|
||||
// get display size
|
||||
RECT rect;
|
||||
::GetClientRect(g_hWnd, &rect);
|
||||
ImGui::GetIO().DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top));
|
||||
}
|
||||
|
||||
bool ImGui_ImplSpice_UpdateMouseCursor() {
|
||||
|
||||
// check if cursor should be changed
|
||||
auto &io = ImGui::GetIO();
|
||||
if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// update cursor
|
||||
ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
|
||||
if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor) {
|
||||
|
||||
// hide OS mouse cursor if imgui is drawing it or if it wants no cursor
|
||||
::SetCursor(nullptr);
|
||||
|
||||
} else {
|
||||
|
||||
// show OS mouse cursor
|
||||
LPTSTR win32_cursor = IDC_ARROW;
|
||||
switch (imgui_cursor) {
|
||||
case ImGuiMouseCursor_Arrow:
|
||||
win32_cursor = IDC_ARROW;
|
||||
break;
|
||||
case ImGuiMouseCursor_TextInput:
|
||||
win32_cursor = IDC_IBEAM;
|
||||
break;
|
||||
case ImGuiMouseCursor_ResizeAll:
|
||||
win32_cursor = IDC_SIZEALL;
|
||||
break;
|
||||
case ImGuiMouseCursor_ResizeEW:
|
||||
win32_cursor = IDC_SIZEWE;
|
||||
break;
|
||||
case ImGuiMouseCursor_ResizeNS:
|
||||
win32_cursor = IDC_SIZENS;
|
||||
break;
|
||||
case ImGuiMouseCursor_ResizeNESW:
|
||||
win32_cursor = IDC_SIZENESW;
|
||||
break;
|
||||
case ImGuiMouseCursor_ResizeNWSE:
|
||||
win32_cursor = IDC_SIZENWSE;
|
||||
break;
|
||||
case ImGuiMouseCursor_Hand:
|
||||
win32_cursor = IDC_HAND;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
::SetCursor(::LoadCursor(nullptr, win32_cursor));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ImGui_ImplSpice_UpdateMousePos() {
|
||||
|
||||
// get current window size
|
||||
RECT rect;
|
||||
if (GetClientRect(g_hWnd, &rect)) {
|
||||
ImVec2 window_size(
|
||||
(float)(rect.right - rect.left),
|
||||
(float)(rect.bottom - rect.top));
|
||||
|
||||
// set OS mouse position if requested
|
||||
auto &io = ImGui::GetIO();
|
||||
if (io.WantSetMousePos) {
|
||||
POINT pos {
|
||||
.x = static_cast<long>(io.MousePos.x),
|
||||
.y = static_cast<long>(io.MousePos.y),
|
||||
};
|
||||
::ClientToScreen(g_hWnd, &pos);
|
||||
::SetCursorPos(
|
||||
static_cast<int>(pos.x / io.DisplaySize.x * window_size.x),
|
||||
static_cast<int>(pos.y / io.DisplaySize.y * window_size.y));
|
||||
}
|
||||
|
||||
// set mouse position
|
||||
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
|
||||
POINT pos;
|
||||
if (HWND active_window = ::GetForegroundWindow()) {
|
||||
if (active_window == g_hWnd
|
||||
|| ::IsChild(active_window, g_hWnd)
|
||||
|| ::IsChild(g_hWnd, active_window)
|
||||
|| active_window == SPICETOUCH_TOUCH_HWND) {
|
||||
if (::GetCursorPos(&pos) && ::ScreenToClient(g_hWnd, &pos)) {
|
||||
io.MousePos = ImVec2(
|
||||
(float) pos.x * io.DisplaySize.x / window_size.x,
|
||||
(float) pos.y * io.DisplaySize.y / window_size.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to touch hwnd
|
||||
if (io.MousePos.x == -FLT_MAX || io.MousePos.y == -FLT_MAX) {
|
||||
if (SPICETOUCH_TOUCH_HWND) {
|
||||
if (::GetCursorPos(&pos) && ::ScreenToClient(SPICETOUCH_TOUCH_HWND, &pos)) {
|
||||
io.MousePos = ImVec2(
|
||||
(float) pos.x * io.DisplaySize.x / window_size.x,
|
||||
(float) pos.y * io.DisplaySize.y / window_size.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// alternatively check touch
|
||||
std::vector<TouchPoint> touch_points;
|
||||
touch_get_points(touch_points, true);
|
||||
static size_t delay_touch = 0;
|
||||
static size_t delay_touch_target = 2;
|
||||
static DWORD last_touch_id = ~0u;
|
||||
if (!touch_points.empty()) {
|
||||
|
||||
// use the first touch point
|
||||
auto &tp = touch_points[0];
|
||||
io.MousePos.x = tp.x * io.DisplaySize.x / window_size.x;
|
||||
io.MousePos.y = tp.y * io.DisplaySize.y / window_size.y;
|
||||
|
||||
// update cursor position
|
||||
if (!tp.mouse) {
|
||||
pos.x = static_cast<long>(io.MousePos.x);
|
||||
pos.y = static_cast<long>(io.MousePos.y);
|
||||
::ClientToScreen(g_hWnd, &pos);
|
||||
::SetCursorPos(
|
||||
static_cast<long>(pos.x / io.DisplaySize.x * window_size.x),
|
||||
static_cast<long>(pos.y / io.DisplaySize.y * window_size.y));
|
||||
}
|
||||
|
||||
// delay press
|
||||
io.MouseDown[0] = delay_touch++ >= delay_touch_target && last_touch_id == tp.id;
|
||||
if (last_touch_id == ~0u) {
|
||||
last_touch_id = tp.id;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// reset
|
||||
delay_touch = 0;
|
||||
last_touch_id = ~0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImGui_ImplSpice_NewFrame() {
|
||||
|
||||
// check if font is built
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
IM_ASSERT(io.Fonts->IsBuilt());
|
||||
|
||||
// setup time step
|
||||
INT64 current_time;
|
||||
::QueryPerformanceCounter((LARGE_INTEGER *)¤t_time);
|
||||
io.DeltaTime = (float) (current_time - g_Time) / g_TicksPerSecond;
|
||||
g_Time = current_time;
|
||||
|
||||
// remember old state
|
||||
BYTE KeysDownOld[sizeof(io.KeysDown)];
|
||||
for (size_t i = 0; i < sizeof(io.KeysDown); i++) {
|
||||
KeysDownOld[i] = io.KeysDown[i] ? ~0 : 0;
|
||||
}
|
||||
KeysDownOld[VK_SHIFT] |= KeysDownOld[VK_LSHIFT];
|
||||
KeysDownOld[VK_SHIFT] |= KeysDownOld[VK_RSHIFT];
|
||||
|
||||
// reset keys state
|
||||
io.MouseWheel = 0;
|
||||
io.KeyCtrl = false;
|
||||
io.KeyShift = false;
|
||||
io.KeyAlt = false;
|
||||
io.KeySuper = false;
|
||||
memset(io.KeysDown, false, sizeof(io.KeysDown));
|
||||
memset(io.MouseDown, false, sizeof(io.MouseDown));
|
||||
|
||||
// early quit if window not in focus
|
||||
if (!superexit::has_focus()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// read keyboard modifiers inputs
|
||||
io.KeyCtrl = (::GetKeyState(VK_CONTROL) & 0x8000) != 0;
|
||||
io.KeyShift = (::GetKeyState(VK_SHIFT) & 0x8000) != 0;
|
||||
io.KeyAlt = (::GetKeyState(VK_MENU) & 0x8000) != 0;
|
||||
io.KeySuper = (::GetKeyState(VK_LWIN) & 0x8000) != 0;
|
||||
io.KeySuper |= (::GetKeyState(VK_RWIN) & 0x8000) != 0;
|
||||
|
||||
// apply windows mouse buttons
|
||||
io.MouseDown[0] |= (GetAsyncKeyState(VK_LBUTTON)) != 0;
|
||||
io.MouseDown[1] |= (GetAsyncKeyState(VK_RBUTTON)) != 0;
|
||||
io.MouseDown[2] |= (GetAsyncKeyState(VK_MBUTTON)) != 0;
|
||||
|
||||
// read new keys state
|
||||
static long mouse_wheel_last = 0;
|
||||
long mouse_wheel = 0;
|
||||
if (RI_MGR != nullptr) {
|
||||
auto devices = RI_MGR->devices_get();
|
||||
for (auto &device : devices) {
|
||||
switch (device.type) {
|
||||
case rawinput::MOUSE: {
|
||||
auto &mouse = device.mouseInfo;
|
||||
|
||||
// mouse button triggers
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
|
||||
io.MouseDown[0] = true;
|
||||
}
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
|
||||
io.MouseDown[1] = true;
|
||||
}
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_MIDDLE]) {
|
||||
io.MouseDown[2] = true;
|
||||
}
|
||||
|
||||
// final mouse wheel value should be all devices combined
|
||||
mouse_wheel += mouse->pos_wheel;
|
||||
|
||||
break;
|
||||
}
|
||||
case rawinput::KEYBOARD: {
|
||||
|
||||
// iterate all virtual key codes
|
||||
for (size_t vKey = 0; vKey < 256; vKey++) {
|
||||
|
||||
// get state (combined from all pages)
|
||||
auto &key_states = device.keyboardInfo->key_states;
|
||||
bool state = false;
|
||||
for (size_t page_index = 0; page_index < 1024; page_index += 256) {
|
||||
state |= key_states[page_index + vKey];
|
||||
}
|
||||
|
||||
// trigger
|
||||
io.KeysDown[vKey] |= state;
|
||||
|
||||
// generate character input, but only if WM_CHAR didn't take over the
|
||||
// functionality
|
||||
if (!overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT && !KeysDownOld[vKey] && state) {
|
||||
UCHAR buf[2];
|
||||
auto ret = ToAscii(
|
||||
static_cast<UINT>(vKey),
|
||||
0,
|
||||
static_cast<const BYTE *>(KeysDownOld),
|
||||
reinterpret_cast<LPWORD>(buf),
|
||||
0);
|
||||
if (ret > 0) {
|
||||
for (int i = 0; i < ret; i++) {
|
||||
overlay::OVERLAY->input_char(buf[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// navigator input
|
||||
auto buttons = games::get_buttons_overlay(eamuse_get_game());
|
||||
if (buttons && (!overlay::OVERLAY || overlay::OVERLAY->hotkeys_triggered())) {
|
||||
struct {
|
||||
size_t index;
|
||||
Button &btn;
|
||||
} NAV_MAPPING[] = {
|
||||
{ ImGuiNavInput_Activate, buttons->at(games::OverlayButtons::NavigatorActivate )},
|
||||
{ ImGuiNavInput_Cancel, buttons->at(games::OverlayButtons::NavigatorCancel) },
|
||||
{ ImGuiNavInput_DpadUp, buttons->at(games::OverlayButtons::NavigatorUp) },
|
||||
{ ImGuiNavInput_DpadDown, buttons->at(games::OverlayButtons::NavigatorDown) },
|
||||
{ ImGuiNavInput_DpadLeft, buttons->at(games::OverlayButtons::NavigatorLeft) },
|
||||
{ ImGuiNavInput_DpadRight, buttons->at(games::OverlayButtons::NavigatorRight) },
|
||||
};
|
||||
for (auto mapping : NAV_MAPPING) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, mapping.btn)) {
|
||||
io.NavInputs[mapping.index] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set mouse wheel
|
||||
auto mouse_diff = mouse_wheel - mouse_wheel_last;
|
||||
mouse_wheel_last = mouse_wheel;
|
||||
io.MouseWheel = mouse_diff;
|
||||
|
||||
// update OS mouse position
|
||||
ImGui_ImplSpice_UpdateMousePos();
|
||||
|
||||
// update OS mouse cursor with the cursor requested by imgui
|
||||
ImGuiMouseCursor mouse_cursor = io.MouseDrawCursor ? ImGuiMouseCursor_None : ImGui::GetMouseCursor();
|
||||
if (g_LastMouseCursor != mouse_cursor) {
|
||||
g_LastMouseCursor = mouse_cursor;
|
||||
ImGui_ImplSpice_UpdateMouseCursor();
|
||||
}
|
||||
}
|
||||
10
overlay/imgui/impl_spice.h
Normal file
10
overlay/imgui/impl_spice.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include "external/imgui/imgui.h"
|
||||
|
||||
IMGUI_IMPL_API bool ImGui_ImplSpice_Init(HWND hWnd);
|
||||
IMGUI_IMPL_API void ImGui_ImplSpice_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplSpice_UpdateDisplaySize();
|
||||
IMGUI_IMPL_API bool ImGui_ImplSpice_UpdateMouseCursor();
|
||||
IMGUI_IMPL_API void ImGui_ImplSpice_NewFrame();
|
||||
731
overlay/imgui/impl_sw.cpp
Normal file
731
overlay/imgui/impl_sw.cpp
Normal file
@@ -0,0 +1,731 @@
|
||||
// Original File By Emil Ernerfeldt 2018
|
||||
// https://github.com/emilk/imgui_software_renderer
|
||||
// LICENSE:
|
||||
// This software is dual-licensed to the public domain and under the following
|
||||
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||
// publish, and distribute this file as you see fit.
|
||||
#include "impl_sw.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "external/imgui/imgui.h"
|
||||
|
||||
namespace imgui_sw {
|
||||
namespace {
|
||||
|
||||
struct Texture
|
||||
{
|
||||
const uint8_t* pixels; // 8-bit.
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
struct PaintTarget
|
||||
{
|
||||
uint32_t* pixels;
|
||||
int width;
|
||||
int height;
|
||||
ImVec2 scale; // Multiply ImGui (point) coordinates with this to get pixel coordinates.
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
struct ColorInt
|
||||
{
|
||||
uint32_t a, b, g, r;
|
||||
|
||||
ColorInt() = default;
|
||||
|
||||
explicit ColorInt(uint32_t x)
|
||||
{
|
||||
a = (x >> IM_COL32_A_SHIFT) & 0xFFu;
|
||||
b = (x >> IM_COL32_B_SHIFT) & 0xFFu;
|
||||
g = (x >> IM_COL32_G_SHIFT) & 0xFFu;
|
||||
r = (x >> IM_COL32_R_SHIFT) & 0xFFu;
|
||||
}
|
||||
|
||||
uint32_t toUint32() const
|
||||
{
|
||||
#ifdef IMGUI_USE_BGRA_PACKED_COLOR
|
||||
return (a << 24u) | (r << 16u) | (g << 8u) | b;
|
||||
#else
|
||||
return (a << 24u) | (b << 16u) | (g << 8u) | r;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
ColorInt blend(ColorInt target, ColorInt source)
|
||||
{
|
||||
ColorInt result;
|
||||
result.a = 0; // Whatever.
|
||||
result.b = (source.b * source.a + target.b * (255 - source.a)) / 255;
|
||||
result.g = (source.g * source.a + target.g * (255 - source.a)) / 255;
|
||||
result.r = (source.r * source.a + target.r * (255 - source.a)) / 255;
|
||||
return result;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Used for interpolating vertex attributes (color and texture coordinates) in a triangle.
|
||||
|
||||
struct Barycentric
|
||||
{
|
||||
float w0, w1, w2;
|
||||
};
|
||||
|
||||
Barycentric operator*(const float f, const Barycentric& va)
|
||||
{
|
||||
return { f * va.w0, f * va.w1, f * va.w2 };
|
||||
}
|
||||
|
||||
void operator+=(Barycentric& a, const Barycentric& b)
|
||||
{
|
||||
a.w0 += b.w0;
|
||||
a.w1 += b.w1;
|
||||
a.w2 += b.w2;
|
||||
}
|
||||
|
||||
Barycentric operator+(const Barycentric& a, const Barycentric& b)
|
||||
{
|
||||
return Barycentric{ a.w0 + b.w0, a.w1 + b.w1, a.w2 + b.w2 };
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Useful operators on ImGui vectors:
|
||||
|
||||
ImVec2 operator*(const float f, const ImVec2& v)
|
||||
{
|
||||
return ImVec2{f * v.x, f * v.y};
|
||||
}
|
||||
|
||||
ImVec2 operator+(const ImVec2& a, const ImVec2& b)
|
||||
{
|
||||
return ImVec2{a.x + b.x, a.y + b.y};
|
||||
}
|
||||
|
||||
ImVec2 operator-(const ImVec2& a, const ImVec2& b)
|
||||
{
|
||||
return ImVec2{a.x - b.x, a.y - b.y};
|
||||
}
|
||||
|
||||
bool operator!=(const ImVec2& a, const ImVec2& b)
|
||||
{
|
||||
return a.x != b.x || a.y != b.y;
|
||||
}
|
||||
|
||||
ImVec4 operator*(const float f, const ImVec4& v)
|
||||
{
|
||||
return ImVec4{f * v.x, f * v.y, f * v.z, f * v.w};
|
||||
}
|
||||
|
||||
ImVec4 operator+(const ImVec4& a, const ImVec4& b)
|
||||
{
|
||||
return ImVec4{a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w};
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copies of functions in ImGui, inlined for speed:
|
||||
|
||||
ImVec4 color_convert_u32_to_float4(ImU32 in)
|
||||
{
|
||||
const float s = 1.0f / 255.0f;
|
||||
return ImVec4(
|
||||
((in >> IM_COL32_R_SHIFT) & 0xFF) * s,
|
||||
((in >> IM_COL32_G_SHIFT) & 0xFF) * s,
|
||||
((in >> IM_COL32_B_SHIFT) & 0xFF) * s,
|
||||
((in >> IM_COL32_A_SHIFT) & 0xFF) * s);
|
||||
}
|
||||
|
||||
ImU32 color_convert_float4_to_u32(const ImVec4& in)
|
||||
{
|
||||
ImU32 out;
|
||||
out = uint32_t(in.x * 255.0f + 0.5f) << IM_COL32_R_SHIFT;
|
||||
out |= uint32_t(in.y * 255.0f + 0.5f) << IM_COL32_G_SHIFT;
|
||||
out |= uint32_t(in.z * 255.0f + 0.5f) << IM_COL32_B_SHIFT;
|
||||
out |= uint32_t(in.w * 255.0f + 0.5f) << IM_COL32_A_SHIFT;
|
||||
return out;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// For fast and subpixel-perfect triangle rendering we used fixed point arithmetic.
|
||||
// To keep the code simple we use 64 bits to avoid overflows.
|
||||
|
||||
using Int = int64_t;
|
||||
const Int kFixedBias = 256;
|
||||
|
||||
struct Point
|
||||
{
|
||||
Int x, y;
|
||||
};
|
||||
|
||||
Int orient2d(const Point& a, const Point& b, const Point& c)
|
||||
{
|
||||
return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
||||
}
|
||||
|
||||
Int as_int(float v)
|
||||
{
|
||||
return static_cast<Int>(std::floor(v * kFixedBias));
|
||||
}
|
||||
|
||||
Point as_point(ImVec2 v)
|
||||
{
|
||||
return Point{as_int(v.x), as_int(v.y)};
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
float min3(float a, float b, float c)
|
||||
{
|
||||
if (a < b && a < c) { return a; }
|
||||
return b < c ? b : c;
|
||||
}
|
||||
|
||||
float max3(float a, float b, float c)
|
||||
{
|
||||
if (a > b && a > c) { return a; }
|
||||
return b > c ? b : c;
|
||||
}
|
||||
|
||||
float barycentric(const ImVec2& a, const ImVec2& b, const ImVec2& point)
|
||||
{
|
||||
return (b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x);
|
||||
}
|
||||
|
||||
inline uint8_t sample_texture(const Texture& texture, const ImVec2& uv)
|
||||
{
|
||||
int tx = static_cast<int>(uv.x * (texture.width - 1.0f) + 0.5f);
|
||||
int ty = static_cast<int>(uv.y * (texture.height - 1.0f) + 0.5f);
|
||||
|
||||
// Clamp to inside of texture:
|
||||
tx = std::max(tx, 0);
|
||||
tx = std::min(tx, texture.width - 1);
|
||||
ty = std::max(ty, 0);
|
||||
ty = std::min(ty, texture.height - 1);
|
||||
|
||||
return texture.pixels[ty * texture.width + tx];
|
||||
}
|
||||
|
||||
void paint_uniform_rectangle(
|
||||
const PaintTarget& target,
|
||||
const ImVec2& min_f,
|
||||
const ImVec2& max_f,
|
||||
const ColorInt& color,
|
||||
Stats* stats)
|
||||
{
|
||||
// Integer bounding box [min, max):
|
||||
int min_x_i = static_cast<int>(target.scale.x * min_f.x + 0.5f);
|
||||
int min_y_i = static_cast<int>(target.scale.y * min_f.y + 0.5f);
|
||||
int max_x_i = static_cast<int>(target.scale.x * max_f.x + 0.5f);
|
||||
int max_y_i = static_cast<int>(target.scale.y * max_f.y + 0.5f);
|
||||
|
||||
// Clamp to render target:
|
||||
min_x_i = std::max(min_x_i, 0);
|
||||
min_y_i = std::max(min_y_i, 0);
|
||||
max_x_i = std::min(max_x_i, target.width);
|
||||
max_y_i = std::min(max_y_i, target.height);
|
||||
|
||||
stats->uniform_rectangle_pixels += (max_x_i - min_x_i) * (max_y_i - min_y_i);
|
||||
|
||||
// We often blend the same colors over and over again, so optimize for this (saves 25% total cpu):
|
||||
uint32_t last_target_pixel = target.pixels[min_y_i * target.width + min_x_i];
|
||||
uint32_t last_output = blend(ColorInt(last_target_pixel), color).toUint32();
|
||||
|
||||
for (int y = min_y_i; y < max_y_i; ++y) {
|
||||
for (int x = min_x_i; x < max_x_i; ++x) {
|
||||
uint32_t& target_pixel = target.pixels[y * target.width + x];
|
||||
if (target_pixel == last_target_pixel) {
|
||||
target_pixel = last_output;
|
||||
continue;
|
||||
}
|
||||
last_target_pixel = target_pixel;
|
||||
target_pixel = blend(ColorInt(target_pixel), color).toUint32();
|
||||
last_output = target_pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void paint_uniform_textured_rectangle(
|
||||
const PaintTarget& target,
|
||||
const Texture& texture,
|
||||
const ImVec4& clip_rect,
|
||||
const ImDrawVert& min_v,
|
||||
const ImDrawVert& max_v,
|
||||
Stats* stats)
|
||||
{
|
||||
const ImVec2 min_p = ImVec2(target.scale.x * min_v.pos.x, target.scale.y * min_v.pos.y);
|
||||
const ImVec2 max_p = ImVec2(target.scale.x * max_v.pos.x, target.scale.y * max_v.pos.y);
|
||||
|
||||
// Find bounding box:
|
||||
float min_x_f = min_p.x;
|
||||
float min_y_f = min_p.y;
|
||||
float max_x_f = max_p.x;
|
||||
float max_y_f = max_p.y;
|
||||
|
||||
// Clip against clip_rect:
|
||||
min_x_f = std::max(min_x_f, target.scale.x * clip_rect.x);
|
||||
min_y_f = std::max(min_y_f, target.scale.y * clip_rect.y);
|
||||
max_x_f = std::min(max_x_f, target.scale.x * clip_rect.z - 0.5f);
|
||||
max_y_f = std::min(max_y_f, target.scale.y * clip_rect.w - 0.5f);
|
||||
|
||||
// Integer bounding box [min, max):
|
||||
int min_x_i = static_cast<int>(min_x_f);
|
||||
int min_y_i = static_cast<int>(min_y_f);
|
||||
int max_x_i = static_cast<int>(max_x_f + 1.0f);
|
||||
int max_y_i = static_cast<int>(max_y_f + 1.0f);
|
||||
|
||||
// Clip against render target:
|
||||
min_x_i = std::max(min_x_i, 0);
|
||||
min_y_i = std::max(min_y_i, 0);
|
||||
max_x_i = std::min(max_x_i, target.width);
|
||||
max_y_i = std::min(max_y_i, target.height);
|
||||
|
||||
stats->font_pixels += (max_x_i - min_x_i) * (max_y_i - min_y_i);
|
||||
|
||||
const auto topleft = ImVec2(min_x_i + 0.5f * target.scale.x,
|
||||
min_y_i + 0.5f * target.scale.y);
|
||||
|
||||
const ImVec2 delta_uv_per_pixel = {
|
||||
(max_v.uv.x - min_v.uv.x) / (max_p.x - min_p.x),
|
||||
(max_v.uv.y - min_v.uv.y) / (max_p.y - min_p.y),
|
||||
};
|
||||
const ImVec2 uv_topleft = {
|
||||
min_v.uv.x + (topleft.x - min_v.pos.x) * delta_uv_per_pixel.x,
|
||||
min_v.uv.y + (topleft.y - min_v.pos.y) * delta_uv_per_pixel.y,
|
||||
};
|
||||
ImVec2 current_uv = uv_topleft;
|
||||
|
||||
for (int y = min_y_i; y < max_y_i; ++y, current_uv.y += delta_uv_per_pixel.y) {
|
||||
current_uv.x = uv_topleft.x;
|
||||
for (int x = min_x_i; x < max_x_i; ++x, current_uv.x += delta_uv_per_pixel.x) {
|
||||
uint32_t& target_pixel = target.pixels[y * target.width + x];
|
||||
const uint8_t texel = sample_texture(texture, current_uv);
|
||||
|
||||
// The font texture is all black or all white, so optimize for this:
|
||||
if (texel == 0) { continue; }
|
||||
|
||||
// Other textured rectangles
|
||||
ColorInt source_color = ColorInt(min_v.col);
|
||||
source_color.a = source_color.a * texel / 255;
|
||||
target_pixel = blend(ColorInt(target_pixel), source_color).toUint32();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When two triangles share an edge, we want to draw the pixels on that edge exactly once.
|
||||
// The edge will be the same, but the direction will be the opposite
|
||||
// (assuming the two triangles have the same winding order).
|
||||
// Which edge wins? This functions decides.
|
||||
bool is_dominant_edge(ImVec2 edge)
|
||||
{
|
||||
// return edge.x < 0 || (edge.x == 0 && edge.y > 0);
|
||||
return edge.y > 0 || (edge.y == 0 && edge.x < 0);
|
||||
}
|
||||
|
||||
// Handles triangles in any winding order (CW/CCW)
|
||||
void paint_triangle(
|
||||
const PaintTarget& target,
|
||||
const Texture* texture,
|
||||
const ImVec4& clip_rect,
|
||||
const ImDrawVert& v0,
|
||||
const ImDrawVert& v1,
|
||||
const ImDrawVert& v2,
|
||||
Stats* stats)
|
||||
{
|
||||
const ImVec2 p0 = ImVec2(target.scale.x * v0.pos.x, target.scale.y * v0.pos.y);
|
||||
const ImVec2 p1 = ImVec2(target.scale.x * v1.pos.x, target.scale.y * v1.pos.y);
|
||||
const ImVec2 p2 = ImVec2(target.scale.x * v2.pos.x, target.scale.y * v2.pos.y);
|
||||
|
||||
const auto rect_area = barycentric(p0, p1, p2); // Can be positive or negative depending on winding order
|
||||
if (rect_area == 0.0f) { return; }
|
||||
// if (rect_area < 0.0f) { return paint_triangle(target, texture, clip_rect, v0, v2, v1, stats); }
|
||||
|
||||
// Find bounding box:
|
||||
float min_x_f = min3(p0.x, p1.x, p2.x);
|
||||
float min_y_f = min3(p0.y, p1.y, p2.y);
|
||||
float max_x_f = max3(p0.x, p1.x, p2.x);
|
||||
float max_y_f = max3(p0.y, p1.y, p2.y);
|
||||
|
||||
// Clip against clip_rect:
|
||||
min_x_f = std::max(min_x_f, target.scale.x * clip_rect.x);
|
||||
min_y_f = std::max(min_y_f, target.scale.y * clip_rect.y);
|
||||
max_x_f = std::min(max_x_f, target.scale.x * clip_rect.z - 0.5f);
|
||||
max_y_f = std::min(max_y_f, target.scale.y * clip_rect.w - 0.5f);
|
||||
|
||||
// Integer bounding box [min, max):
|
||||
int min_x_i = static_cast<int>(min_x_f);
|
||||
int min_y_i = static_cast<int>(min_y_f);
|
||||
int max_x_i = static_cast<int>(max_x_f + 1.0f);
|
||||
int max_y_i = static_cast<int>(max_y_f + 1.0f);
|
||||
|
||||
// Clip against render target:
|
||||
min_x_i = std::max(min_x_i, 0);
|
||||
min_y_i = std::max(min_y_i, 0);
|
||||
max_x_i = std::min(max_x_i, target.width);
|
||||
max_y_i = std::min(max_y_i, target.height);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Set up interpolation of barycentric coordinates:
|
||||
|
||||
const auto topleft = ImVec2(min_x_i + 0.5f * target.scale.x,
|
||||
min_y_i + 0.5f * target.scale.y);
|
||||
const auto dx = ImVec2(1, 0);
|
||||
const auto dy = ImVec2(0, 1);
|
||||
|
||||
const auto w0_topleft = barycentric(p1, p2, topleft);
|
||||
const auto w1_topleft = barycentric(p2, p0, topleft);
|
||||
const auto w2_topleft = barycentric(p0, p1, topleft);
|
||||
|
||||
const auto w0_dx = barycentric(p1, p2, topleft + dx) - w0_topleft;
|
||||
const auto w1_dx = barycentric(p2, p0, topleft + dx) - w1_topleft;
|
||||
const auto w2_dx = barycentric(p0, p1, topleft + dx) - w2_topleft;
|
||||
|
||||
const auto w0_dy = barycentric(p1, p2, topleft + dy) - w0_topleft;
|
||||
const auto w1_dy = barycentric(p2, p0, topleft + dy) - w1_topleft;
|
||||
const auto w2_dy = barycentric(p0, p1, topleft + dy) - w2_topleft;
|
||||
|
||||
const Barycentric bary_0 { 1, 0, 0 };
|
||||
const Barycentric bary_1 { 0, 1, 0 };
|
||||
const Barycentric bary_2 { 0, 0, 1 };
|
||||
|
||||
const auto inv_area = 1 / rect_area;
|
||||
const Barycentric bary_topleft = inv_area * (w0_topleft * bary_0 + w1_topleft * bary_1 + w2_topleft * bary_2);
|
||||
const Barycentric bary_dx = inv_area * (w0_dx * bary_0 + w1_dx * bary_1 + w2_dx * bary_2);
|
||||
const Barycentric bary_dy = inv_area * (w0_dy * bary_0 + w1_dy * bary_1 + w2_dy * bary_2);
|
||||
|
||||
Barycentric bary_current_row = bary_topleft;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// For pixel-perfect inside/outside testing:
|
||||
|
||||
const int sign = rect_area > 0 ? 1 : -1; // winding order?
|
||||
|
||||
const int bias0i = is_dominant_edge(p2 - p1) ? 0 : -1;
|
||||
const int bias1i = is_dominant_edge(p0 - p2) ? 0 : -1;
|
||||
const int bias2i = is_dominant_edge(p1 - p0) ? 0 : -1;
|
||||
|
||||
const auto p0i = as_point(p0);
|
||||
const auto p1i = as_point(p1);
|
||||
const auto p2i = as_point(p2);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
const bool has_uniform_color = (v0.col == v1.col && v0.col == v2.col);
|
||||
|
||||
const ImVec4 c0 = color_convert_u32_to_float4(v0.col);
|
||||
const ImVec4 c1 = color_convert_u32_to_float4(v1.col);
|
||||
const ImVec4 c2 = color_convert_u32_to_float4(v2.col);
|
||||
|
||||
// We often blend the same colors over and over again, so optimize for this (saves 10% total cpu):
|
||||
uint32_t last_target_pixel = 0;
|
||||
uint32_t last_output = blend(ColorInt(last_target_pixel), ColorInt(v0.col)).toUint32();
|
||||
|
||||
for (int y = min_y_i; y < max_y_i; ++y) {
|
||||
auto bary = bary_current_row;
|
||||
|
||||
bool has_been_inside_this_row = false;
|
||||
|
||||
for (int x = min_x_i; x < max_x_i; ++x) {
|
||||
const auto w0 = bary.w0;
|
||||
const auto w1 = bary.w1;
|
||||
const auto w2 = bary.w2;
|
||||
bary += bary_dx;
|
||||
|
||||
{
|
||||
// Inside/outside test:
|
||||
const auto p = Point{kFixedBias * x + kFixedBias / 2, kFixedBias * y + kFixedBias / 2};
|
||||
const auto w0i = sign * orient2d(p1i, p2i, p) + bias0i;
|
||||
const auto w1i = sign * orient2d(p2i, p0i, p) + bias1i;
|
||||
const auto w2i = sign * orient2d(p0i, p1i, p) + bias2i;
|
||||
if (w0i < 0 || w1i < 0 || w2i < 0) {
|
||||
if (has_been_inside_this_row) {
|
||||
break; // Gives a nice 10% speedup
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
has_been_inside_this_row = true;
|
||||
|
||||
uint32_t& target_pixel = target.pixels[y * target.width + x];
|
||||
|
||||
if (has_uniform_color && !texture) {
|
||||
stats->uniform_triangle_pixels += 1;
|
||||
if (target_pixel == last_target_pixel) {
|
||||
target_pixel = last_output;
|
||||
continue;
|
||||
}
|
||||
last_target_pixel = target_pixel;
|
||||
target_pixel = blend(ColorInt(target_pixel), ColorInt(v0.col)).toUint32();
|
||||
last_output = target_pixel;
|
||||
continue;
|
||||
}
|
||||
|
||||
ImVec4 src_color;
|
||||
|
||||
if (has_uniform_color) {
|
||||
src_color = c0;
|
||||
} else {
|
||||
stats->gradient_triangle_pixels += 1;
|
||||
src_color = w0 * c0 + w1 * c1 + w2 * c2;
|
||||
}
|
||||
|
||||
if (texture) {
|
||||
stats->textured_triangle_pixels += 1;
|
||||
const ImVec2 uv = w0 * v0.uv + w1 * v1.uv + w2 * v2.uv;
|
||||
src_color.w *= sample_texture(*texture, uv) / 255.0f;
|
||||
}
|
||||
|
||||
if (src_color.w <= 0.0f) { continue; } // Transparent.
|
||||
if (src_color.w >= 1.0f) {
|
||||
// Opaque, no blending needed:
|
||||
target_pixel = color_convert_float4_to_u32(src_color);
|
||||
continue;
|
||||
}
|
||||
|
||||
ImVec4 target_color = color_convert_u32_to_float4(target_pixel);
|
||||
const auto blended_color = src_color.w * src_color + (1.0f - src_color.w) * target_color;
|
||||
target_pixel = color_convert_float4_to_u32(blended_color);
|
||||
}
|
||||
|
||||
bary_current_row += bary_dy;
|
||||
}
|
||||
}
|
||||
|
||||
void paint_draw_cmd(
|
||||
const PaintTarget& target,
|
||||
const ImDrawVert* vertices,
|
||||
const ImDrawIdx* idx_buffer,
|
||||
const ImDrawCmd& pcmd,
|
||||
const SwOptions& options,
|
||||
Stats* stats)
|
||||
{
|
||||
const auto texture = reinterpret_cast<const Texture*>(pcmd.TextureId);
|
||||
const auto offset = pcmd.IdxOffset;
|
||||
|
||||
// ImGui uses the first pixel for "white".
|
||||
const ImVec2 white_uv = ImVec2(0.5f / texture->width, 0.5f / texture->height);
|
||||
|
||||
for (size_t i = 0; i + 3 <= pcmd.ElemCount; ) {
|
||||
const auto io = i + offset;
|
||||
const ImDrawVert& v0 = vertices[idx_buffer[io + 0]];
|
||||
const ImDrawVert& v1 = vertices[idx_buffer[io + 1]];
|
||||
const ImDrawVert& v2 = vertices[idx_buffer[io + 2]];
|
||||
|
||||
// Text is common, and is made of textured rectangles. So let's optimize for it.
|
||||
// This assumes the ImGui way to layout text does not change.
|
||||
if (options.optimize_text && i + 6 <= pcmd.ElemCount &&
|
||||
idx_buffer[io + 3] == idx_buffer[io + 0] && idx_buffer[io + 4] == idx_buffer[io + 2]) {
|
||||
const ImDrawVert& v3 = vertices[idx_buffer[io + 5]];
|
||||
|
||||
if (v0.pos.x == v3.pos.x &&
|
||||
v1.pos.x == v2.pos.x &&
|
||||
v0.pos.y == v1.pos.y &&
|
||||
v2.pos.y == v3.pos.y &&
|
||||
v0.uv.x == v3.uv.x &&
|
||||
v1.uv.x == v2.uv.x &&
|
||||
v0.uv.y == v1.uv.y &&
|
||||
v2.uv.y == v3.uv.y)
|
||||
{
|
||||
const bool has_uniform_color =
|
||||
v0.col == v1.col &&
|
||||
v0.col == v2.col &&
|
||||
v0.col == v3.col;
|
||||
|
||||
const bool has_texture =
|
||||
v0.uv != white_uv ||
|
||||
v1.uv != white_uv ||
|
||||
v2.uv != white_uv ||
|
||||
v3.uv != white_uv;
|
||||
|
||||
if (has_uniform_color && has_texture)
|
||||
{
|
||||
paint_uniform_textured_rectangle(target, *texture, pcmd.ClipRect, v0, v2, stats);
|
||||
i += 6;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A lot of the big stuff are uniformly colored rectangles,
|
||||
// so we can save a lot of CPU by detecting them:
|
||||
if (options.optimize_rectangles && i + 6 <= pcmd.ElemCount) {
|
||||
const ImDrawVert& v3 = vertices[idx_buffer[io + 3]];
|
||||
const ImDrawVert& v4 = vertices[idx_buffer[io + 4]];
|
||||
const ImDrawVert& v5 = vertices[idx_buffer[io + 5]];
|
||||
|
||||
ImVec2 min, max;
|
||||
min.x = min3(v0.pos.x, v1.pos.x, v2.pos.x);
|
||||
min.y = min3(v0.pos.y, v1.pos.y, v2.pos.y);
|
||||
max.x = max3(v0.pos.x, v1.pos.x, v2.pos.x);
|
||||
max.y = max3(v0.pos.y, v1.pos.y, v2.pos.y);
|
||||
|
||||
// Not the prettiest way to do this, but it catches all cases
|
||||
// of a rectangle split into two triangles.
|
||||
// TODO: Stop it from also assuming duplicate triangles is one rectangle.
|
||||
if ((v0.pos.x == min.x || v0.pos.x == max.x) &&
|
||||
(v0.pos.y == min.y || v0.pos.y == max.y) &&
|
||||
(v1.pos.x == min.x || v1.pos.x == max.x) &&
|
||||
(v1.pos.y == min.y || v1.pos.y == max.y) &&
|
||||
(v2.pos.x == min.x || v2.pos.x == max.x) &&
|
||||
(v2.pos.y == min.y || v2.pos.y == max.y) &&
|
||||
(v3.pos.x == min.x || v3.pos.x == max.x) &&
|
||||
(v3.pos.y == min.y || v3.pos.y == max.y) &&
|
||||
(v4.pos.x == min.x || v4.pos.x == max.x) &&
|
||||
(v4.pos.y == min.y || v4.pos.y == max.y) &&
|
||||
(v5.pos.x == min.x || v5.pos.x == max.x) &&
|
||||
(v5.pos.y == min.y || v5.pos.y == max.y))
|
||||
{
|
||||
const bool has_uniform_color =
|
||||
v0.col == v1.col &&
|
||||
v0.col == v2.col &&
|
||||
v0.col == v3.col &&
|
||||
v0.col == v4.col &&
|
||||
v0.col == v5.col;
|
||||
|
||||
const bool has_texture =
|
||||
v0.uv != white_uv ||
|
||||
v1.uv != white_uv ||
|
||||
v2.uv != white_uv ||
|
||||
v3.uv != white_uv ||
|
||||
v4.uv != white_uv ||
|
||||
v5.uv != white_uv;
|
||||
|
||||
min.x = std::max(min.x, pcmd.ClipRect.x);
|
||||
min.y = std::max(min.y, pcmd.ClipRect.y);
|
||||
max.x = std::min(max.x, pcmd.ClipRect.z - 0.5f);
|
||||
max.y = std::min(max.y, pcmd.ClipRect.w - 0.5f);
|
||||
|
||||
if (max.x < min.x || max.y < min.y) { i += 6; continue; } // Completely clipped
|
||||
|
||||
const auto num_pixels = (max.x - min.x) * (max.y - min.y) * target.scale.x * target.scale.y;
|
||||
|
||||
if (has_uniform_color) {
|
||||
if (has_texture) {
|
||||
stats->textured_rectangle_pixels += num_pixels;
|
||||
} else {
|
||||
paint_uniform_rectangle(target, min, max, ColorInt(v0.col), stats);
|
||||
i += 6;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (has_texture) {
|
||||
// I have never encountered these.
|
||||
stats->gradient_textured_rectangle_pixels += num_pixels;
|
||||
} else {
|
||||
// Color picker. TODO: Optimize
|
||||
stats->gradient_rectangle_pixels += num_pixels;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool has_texture = (v0.uv != white_uv || v1.uv != white_uv || v2.uv != white_uv);
|
||||
paint_triangle(target, has_texture ? texture : nullptr, pcmd.ClipRect, v0, v1, v2, stats);
|
||||
i += 3;
|
||||
}
|
||||
}
|
||||
|
||||
void paint_draw_list(const PaintTarget& target, const ImDrawList* cmd_list, const SwOptions& options, Stats* stats)
|
||||
{
|
||||
const ImDrawIdx* idx_buffer = &cmd_list->IdxBuffer[0];
|
||||
const ImDrawVert* vertices = cmd_list->VtxBuffer.Data;
|
||||
|
||||
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.size(); cmd_i++)
|
||||
{
|
||||
const ImDrawCmd& pcmd = cmd_list->CmdBuffer[cmd_i];
|
||||
if (pcmd.UserCallback) {
|
||||
pcmd.UserCallback(cmd_list, &pcmd);
|
||||
} else {
|
||||
paint_draw_cmd(target, vertices, idx_buffer, pcmd, options, stats);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void make_style_fast()
|
||||
{
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
|
||||
style.AntiAliasedLines = false;
|
||||
style.AntiAliasedFill = false;
|
||||
style.WindowRounding = 0;
|
||||
}
|
||||
|
||||
void restore_style()
|
||||
{
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
const ImGuiStyle default_style = ImGuiStyle();
|
||||
style.AntiAliasedLines = default_style.AntiAliasedLines;
|
||||
style.AntiAliasedFill = default_style.AntiAliasedFill;
|
||||
style.WindowRounding = default_style.WindowRounding;
|
||||
}
|
||||
|
||||
void bind_imgui_painting()
|
||||
{
|
||||
// make sure it doesn't get called twice
|
||||
static bool done = false;
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
done = true;
|
||||
|
||||
// Load default font (embedded in code):
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
uint8_t* tex_data;
|
||||
int font_width, font_height;
|
||||
io.Fonts->GetTexDataAsAlpha8(&tex_data, &font_width, &font_height);
|
||||
const auto texture = new Texture{tex_data, font_width, font_height};
|
||||
io.Fonts->TexID = texture;
|
||||
}
|
||||
|
||||
static Stats s_stats; // TODO: pass as an argument?
|
||||
|
||||
void paint_imgui(uint32_t* pixels, int width_pixels, int height_pixels, const SwOptions& options)
|
||||
{
|
||||
const float width_points = ImGui::GetIO().DisplaySize.x;
|
||||
const float height_points = ImGui::GetIO().DisplaySize.y;
|
||||
const ImVec2 scale{width_pixels / width_points, height_pixels / height_points};
|
||||
PaintTarget target{pixels, width_pixels, height_pixels, scale};
|
||||
const ImDrawData* draw_data = ImGui::GetDrawData();
|
||||
|
||||
s_stats = Stats{};
|
||||
for (int i = 0; i < draw_data->CmdListsCount; ++i) {
|
||||
paint_draw_list(target, draw_data->CmdLists[i], options, &s_stats);
|
||||
}
|
||||
}
|
||||
|
||||
void unbind_imgui_painting()
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
delete reinterpret_cast<Texture*>(io.Fonts->TexID);
|
||||
io.Fonts = nullptr;
|
||||
}
|
||||
|
||||
bool show_options(SwOptions* io_options)
|
||||
{
|
||||
bool changed = false;
|
||||
changed |= ImGui::Checkbox("optimize_text", &io_options->optimize_text);
|
||||
changed |= ImGui::Checkbox("optimize_rectangles", &io_options->optimize_rectangles);
|
||||
return changed;
|
||||
}
|
||||
|
||||
void show_stats()
|
||||
{
|
||||
ImGui::Text("uniform_triangle_pixels: %7d", s_stats.uniform_triangle_pixels);
|
||||
ImGui::Text("textured_triangle_pixels: %7d", s_stats.textured_triangle_pixels);
|
||||
ImGui::Text("gradient_triangle_pixels: %7d", s_stats.gradient_triangle_pixels);
|
||||
ImGui::Text("font_pixels: %7d", s_stats.font_pixels);
|
||||
ImGui::Text("uniform_rectangle_pixels: %7.0f", s_stats.uniform_rectangle_pixels);
|
||||
ImGui::Text("textured_rectangle_pixels: %7.0f", s_stats.textured_rectangle_pixels);
|
||||
ImGui::Text("gradient_rectangle_pixels: %7.0f", s_stats.gradient_rectangle_pixels);
|
||||
ImGui::Text("gradient_textured_rectangle_pixels: %7.0f", s_stats.gradient_textured_rectangle_pixels);
|
||||
}
|
||||
|
||||
Stats get_stats() {
|
||||
return s_stats;
|
||||
}
|
||||
|
||||
} // namespace imgui_sw
|
||||
65
overlay/imgui/impl_sw.h
Normal file
65
overlay/imgui/impl_sw.h
Normal file
@@ -0,0 +1,65 @@
|
||||
// Original File By Emil Ernerfeldt 2018
|
||||
// https://github.com/emilk/imgui_software_renderer
|
||||
// LICENSE:
|
||||
// This software is dual-licensed to the public domain and under the following
|
||||
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||
// publish, and distribute this file as you see fit.
|
||||
// WHAT:
|
||||
// This is a software renderer for Dear ImGui.
|
||||
// It is decently fast, but has a lot of room for optimization.
|
||||
// The goal was to get something fast and decently accurate in not too many lines of code.
|
||||
// LIMITATIONS:
|
||||
// * It is not pixel-perfect, but it is good enough for must use cases.
|
||||
// * It does not support painting with any other texture than the default font texture.
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace imgui_sw {
|
||||
|
||||
struct SwOptions
|
||||
{
|
||||
bool optimize_text = true; // No reason to turn this off.
|
||||
bool optimize_rectangles = true; // No reason to turn this off.
|
||||
};
|
||||
|
||||
|
||||
struct Stats
|
||||
{
|
||||
int uniform_triangle_pixels = 0;
|
||||
int textured_triangle_pixels = 0;
|
||||
int gradient_triangle_pixels = 0;
|
||||
int font_pixels = 0;
|
||||
double uniform_rectangle_pixels = 0;
|
||||
double textured_rectangle_pixels = 0;
|
||||
double gradient_rectangle_pixels = 0;
|
||||
double gradient_textured_rectangle_pixels = 0;
|
||||
};
|
||||
|
||||
/// Optional: tweak ImGui style to make it render faster.
|
||||
void make_style_fast();
|
||||
|
||||
/// Undo what make_style_fast did.
|
||||
void restore_style();
|
||||
|
||||
/// Call once a the start of your program.
|
||||
void bind_imgui_painting();
|
||||
|
||||
/// The buffer is assumed to follow how ImGui packs pixels, i.e. ABGR by default.
|
||||
/// Change with IMGUI_USE_BGRA_PACKED_COLOR.
|
||||
/// If width/height differs from ImGui::GetIO().DisplaySize then
|
||||
/// the function scales the UI to fit the given pixel buffer.
|
||||
void paint_imgui(uint32_t* pixels, int width_pixels, int height_pixels, const SwOptions& options = {});
|
||||
|
||||
/// Free the resources allocated by bind_imgui_painting.
|
||||
void unbind_imgui_painting();
|
||||
|
||||
/// Show ImGui controls for rendering options if you want to.
|
||||
bool show_options(SwOptions* io_options);
|
||||
|
||||
/// Show rendering stats in an ImGui window if you want to.
|
||||
void show_stats();
|
||||
|
||||
Stats get_stats();
|
||||
|
||||
} // namespace imgui_sw
|
||||
652
overlay/overlay.cpp
Normal file
652
overlay/overlay.cpp
Normal file
@@ -0,0 +1,652 @@
|
||||
#include "overlay.h"
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "cfg/configurator.h"
|
||||
#include "games/io.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/resutils.h"
|
||||
#include "build/resource.h"
|
||||
|
||||
#include "imgui/impl_dx9.h"
|
||||
#include "imgui/impl_spice.h"
|
||||
#include "imgui/impl_sw.h"
|
||||
#include "overlay/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/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/kfcontrol.h"
|
||||
#include "windows/log.h"
|
||||
#include "windows/patch_manager.h"
|
||||
#include "windows/vr.h"
|
||||
|
||||
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;
|
||||
|
||||
// global
|
||||
std::mutex OVERLAY_MUTEX;
|
||||
std::unique_ptr<overlay::SpiceOverlay> OVERLAY = nullptr;
|
||||
ImFont* DSEG_FONT = nullptr;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// red theme based on:
|
||||
// https://github.com/ocornut/imgui/issues/707#issuecomment-760220280
|
||||
// r, g, b, a
|
||||
ImVec4* colors = ImGui::GetStyle().Colors;
|
||||
// colors[ImGuiCol_Text] = ImVec4(0.75f, 0.75f, 0.75f, 1.00f);
|
||||
// colors[ImGuiCol_TextDisabled] = ImVec4(0.35f, 0.35f, 0.35f, 1.00f);
|
||||
|
||||
// colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.94f);
|
||||
// colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.f, 0.f, 0.94f);
|
||||
|
||||
colors[ImGuiCol_Border] = ImVec4(0.00f, 0.00f, 0.00f, 0.50f);
|
||||
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_FrameBg] = ImVec4(0.37f, 0.14f, 0.00f, 0.54f);
|
||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.37f, 0.14f, 0.14f, 0.67f);
|
||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.39f, 0.20f, 0.20f, 0.67f);
|
||||
colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgActive] = ImVec4(0.48f, 0.16f, 0.16f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.48f, 0.16f, 0.16f, 1.00f);
|
||||
colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(0.56f, 0.10f, 0.10f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 0.19f, 0.19f, 0.40f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.89f, 0.00f, 0.19f, 1.00f);
|
||||
|
||||
colors[ImGuiCol_Button] = ImVec4(1.00f, 0.19f, 0.19f, 0.40f);
|
||||
colors[ImGuiCol_ButtonHovered] = ImVec4(0.80f, 0.17f, 0.00f, 1.00f);
|
||||
colors[ImGuiCol_ButtonActive] = ImVec4(0.89f, 0.00f, 0.19f, 1.00f);
|
||||
|
||||
colors[ImGuiCol_Header] = ImVec4(0.33f, 0.35f, 0.36f, 0.53f);
|
||||
colors[ImGuiCol_HeaderHovered] = ImVec4(0.76f, 0.28f, 0.44f, 0.67f);
|
||||
colors[ImGuiCol_HeaderActive] = ImVec4(0.47f, 0.47f, 0.47f, 0.67f);
|
||||
colors[ImGuiCol_Separator] = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
|
||||
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
|
||||
colors[ImGuiCol_SeparatorActive] = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
|
||||
colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.85f);
|
||||
colors[ImGuiCol_ResizeGripHovered] = ImVec4(1.00f, 1.00f, 1.00f, 0.60f);
|
||||
colors[ImGuiCol_ResizeGripActive] = ImVec4(1.00f, 1.00f, 1.00f, 0.90f);
|
||||
|
||||
colors[ImGuiCol_Tab] = colors[ImGuiCol_Button];
|
||||
colors[ImGuiCol_TabHovered] = colors[ImGuiCol_ButtonHovered];
|
||||
colors[ImGuiCol_TabActive] = colors[ImGuiCol_ButtonActive];
|
||||
colors[ImGuiCol_TabUnfocused] = colors[ImGuiCol_Tab] * ImVec4(1.0f, 1.0f, 1.0f, 0.6f);
|
||||
colors[ImGuiCol_TabUnfocusedActive] = colors[ImGuiCol_TabActive] * ImVec4(1.0f, 1.0f, 1.0f, 0.6f);
|
||||
|
||||
colors[ImGuiCol_DockingPreview] = ImVec4(0.47f, 0.47f, 0.47f, 0.47f);
|
||||
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
|
||||
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
|
||||
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
|
||||
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f);
|
||||
colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f);
|
||||
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.04f);
|
||||
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
|
||||
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
|
||||
colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
|
||||
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
|
||||
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
|
||||
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
|
||||
|
||||
// configure IO
|
||||
auto &io = ImGui::GetIO();
|
||||
io.UserData = this;
|
||||
io.ConfigFlags = ImGuiConfigFlags_NavEnableKeyboard
|
||||
| ImGuiConfigFlags_NavEnableGamepad
|
||||
| ImGuiConfigFlags_NavEnableSetMousePos
|
||||
| ImGuiConfigFlags_DockingEnable
|
||||
| ImGuiConfigFlags_ViewportsEnable;
|
||||
if (is_touch_available()) {
|
||||
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
|
||||
}
|
||||
|
||||
io.MouseDrawCursor = !GRAPHICS_SHOW_CURSOR;
|
||||
|
||||
// disable config
|
||||
io.IniFilename = nullptr;
|
||||
|
||||
// allow CTRL+WHEEL scaling
|
||||
io.FontAllowUserScaling = true;
|
||||
|
||||
// add default font
|
||||
io.Fonts->AddFontDefault();
|
||||
|
||||
// add fallback fonts for missing glyph ranges
|
||||
ImFontConfig config {};
|
||||
config.MergeMode = true;
|
||||
io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\simsun.ttc)",
|
||||
13.0f, &config, io.Fonts->GetGlyphRangesChineseSimplifiedCommon());
|
||||
io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\arial.ttf)",
|
||||
13.0f, &config, io.Fonts->GetGlyphRangesCyrillic());
|
||||
io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\meiryu.ttc)",
|
||||
13.0f, &config, io.Fonts->GetGlyphRangesJapanese());
|
||||
io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\meiryo.ttc)",
|
||||
13.0f, &config, io.Fonts->GetGlyphRangesJapanese());
|
||||
io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\gulim.ttc)",
|
||||
13.0f, &config, io.Fonts->GetGlyphRangesKorean());
|
||||
io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\cordia.ttf)",
|
||||
13.0f, &config, io.Fonts->GetGlyphRangesThai());
|
||||
io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\arial.ttf)",
|
||||
13.0f, &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;
|
||||
}
|
||||
|
||||
// add default windows
|
||||
this->window_add(new overlay::windows::Config(this));
|
||||
this->window_add(new overlay::windows::Control(this));
|
||||
this->window_add(new overlay::windows::Log(this));
|
||||
#ifdef SPICE64
|
||||
this->window_add(new overlay::windows::CameraControl(this));
|
||||
#endif
|
||||
this->window_add(new overlay::windows::CardManager(this));
|
||||
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||
this->window_add(new overlay::windows::ScreenResize(this));
|
||||
}
|
||||
this->window_add(new overlay::windows::PatchManager(this));
|
||||
this->window_add(new overlay::windows::KFControl(this));
|
||||
this->window_add(new overlay::windows::VRWindow(this));
|
||||
|
||||
{
|
||||
const auto keypad_p1 = new overlay::windows::Keypad(this, 0);
|
||||
this->window_add(keypad_p1);
|
||||
if (!cfg::CONFIGURATOR_STANDALONE && AUTO_SHOW_KEYPAD_P1) {
|
||||
keypad_p1->set_active(true);
|
||||
set_overlay_active = true;
|
||||
}
|
||||
}
|
||||
if (eamuse_get_game_keypads() > 1) {
|
||||
const auto keypad_p2 = new overlay::windows::Keypad(this, 1);
|
||||
this->window_add(keypad_p2);
|
||||
if (!cfg::CONFIGURATOR_STANDALONE && AUTO_SHOW_KEYPAD_P2) {
|
||||
keypad_p2->set_active(true);
|
||||
set_overlay_active = true;
|
||||
}
|
||||
}
|
||||
|
||||
// IO panel needs to know what game is running
|
||||
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||
overlay::Window *iopanel = nullptr;
|
||||
if (avs::game::is_model("LDJ")) {
|
||||
iopanel = new overlay::windows::IIDXIOPanel(this);
|
||||
} else if (avs::game::is_model("MDX")) {
|
||||
iopanel = new overlay::windows::DDRIOPanel(this);
|
||||
} else if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) {
|
||||
iopanel = new overlay::windows::GitadoraIOPanel(this);
|
||||
} else {
|
||||
iopanel = new overlay::windows::IOPanel(this);
|
||||
}
|
||||
if (iopanel) {
|
||||
this->window_add(iopanel);
|
||||
if (AUTO_SHOW_IOPANEL) {
|
||||
iopanel->set_active(true);
|
||||
set_overlay_active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// subscreens need DirectX, so don't try to initialize them in standalone
|
||||
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||
overlay::Window *subscreen = nullptr;
|
||||
if (avs::game::is_model("LDJ")) {
|
||||
if (games::iidx::TDJ_MODE) {
|
||||
subscreen = new overlay::windows::IIDXSubScreen(this);
|
||||
} else {
|
||||
subscreen = new overlay::windows::IIDXSegmentDisplay(this);
|
||||
}
|
||||
} else if (avs::game::is_model("KFC")) {
|
||||
subscreen = new overlay::windows::SDVXSubScreen(this);
|
||||
}
|
||||
if (subscreen) {
|
||||
this->window_add(subscreen);
|
||||
if (AUTO_SHOW_SUBSCREEN) {
|
||||
subscreen->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();
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
|
||||
// show FPS window if toggled with overlay key
|
||||
if (overlay_key) {
|
||||
this->window_fps->set_active(this->active);
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
125
overlay/overlay.h
Normal file
125
overlay/overlay.h
Normal file
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <windows.h>
|
||||
#include <d3d9.h>
|
||||
|
||||
#include "external/imgui/imgui.h"
|
||||
|
||||
namespace overlay {
|
||||
class Window;
|
||||
|
||||
enum class OverlayRenderer {
|
||||
D3D9,
|
||||
SOFTWARE,
|
||||
};
|
||||
|
||||
// settings
|
||||
extern bool ENABLED;
|
||||
extern bool AUTO_SHOW_FPS;
|
||||
extern bool AUTO_SHOW_SUBSCREEN;
|
||||
extern bool AUTO_SHOW_IOPANEL;
|
||||
extern bool AUTO_SHOW_KEYPAD_P1;
|
||||
extern bool AUTO_SHOW_KEYPAD_P2;
|
||||
extern bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT;
|
||||
|
||||
class SpiceOverlay {
|
||||
public:
|
||||
|
||||
D3DDEVICE_CREATION_PARAMETERS creation_parameters {};
|
||||
D3DADAPTER_IDENTIFIER9 adapter_identifier {};
|
||||
bool hotkeys_enable = true;
|
||||
|
||||
explicit SpiceOverlay(HWND hWnd, IDirect3D9 *d3d, IDirect3DDevice9 *device);
|
||||
explicit SpiceOverlay(HWND hWnd);
|
||||
~SpiceOverlay();
|
||||
|
||||
void window_add(Window *wnd);
|
||||
void new_frame();
|
||||
void render();
|
||||
void update();
|
||||
void toggle_active(bool overlay_key = false);
|
||||
void set_active(bool active);
|
||||
bool get_active();
|
||||
bool has_focus();
|
||||
bool hotkeys_triggered();
|
||||
|
||||
static bool update_cursor();
|
||||
static void reset_invalidate();
|
||||
static void reset_recreate();
|
||||
void input_char(unsigned int c);
|
||||
|
||||
uint32_t *sw_get_pixel_data(int *width, int *height);
|
||||
|
||||
inline bool uses_window(HWND hWnd) {
|
||||
return this->hWnd == hWnd;
|
||||
}
|
||||
inline bool uses_context(IDirect3D9 *other) {
|
||||
return this->d3d == other;
|
||||
}
|
||||
inline bool uses_device(IDirect3DDevice9 *other) {
|
||||
return this->device == other;
|
||||
}
|
||||
inline IDirect3DDevice9 *get_device() {
|
||||
return this->device;
|
||||
}
|
||||
|
||||
bool can_transform_touch_input() {
|
||||
return (this->subscreen_mouse_handler != nullptr);
|
||||
}
|
||||
|
||||
bool transform_touch_point(LONG *x, LONG *y) {
|
||||
if (this->get_active() && this->subscreen_mouse_handler) {
|
||||
return this->subscreen_mouse_handler(x, y);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_subscreen_mouse_handler(const std::function<bool(LONG *, LONG *)> &f) {
|
||||
this->subscreen_mouse_handler = f;
|
||||
}
|
||||
|
||||
// renderer
|
||||
OverlayRenderer renderer;
|
||||
float total_elapsed = 0.f;
|
||||
|
||||
private:
|
||||
|
||||
HWND hWnd = nullptr;
|
||||
|
||||
// D3D9
|
||||
IDirect3D9 *d3d = nullptr;
|
||||
IDirect3DDevice9 *device = nullptr;
|
||||
|
||||
// software
|
||||
std::vector<uint32_t> pixel_data;
|
||||
size_t pixel_data_width = 0;
|
||||
size_t pixel_data_height = 0;
|
||||
|
||||
std::vector<std::unique_ptr<Window>> windows;
|
||||
Window *window_fps = nullptr;
|
||||
|
||||
std::function<bool(LONG *, LONG *)> subscreen_mouse_handler = nullptr;
|
||||
|
||||
bool active = false;
|
||||
bool toggle_down = false;
|
||||
bool hotkey_toggle = false;
|
||||
bool hotkey_toggle_last = false;
|
||||
|
||||
void init();
|
||||
};
|
||||
|
||||
// global
|
||||
extern std::mutex OVERLAY_MUTEX;
|
||||
extern std::unique_ptr<overlay::SpiceOverlay> OVERLAY;
|
||||
extern ImFont* DSEG_FONT;
|
||||
|
||||
// synchronized helpers
|
||||
void create_d3d9(HWND hWnd, IDirect3D9 *d3d, IDirect3DDevice9 *device);
|
||||
void create_software(HWND hWnd);
|
||||
void destroy(HWND hWnd = nullptr);
|
||||
}
|
||||
157
overlay/window.cpp
Normal file
157
overlay/window.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
#include "window.h"
|
||||
#include "cfg/configurator.h"
|
||||
#include "util/logging.h"
|
||||
#include "games/io.h"
|
||||
#include "misc/eamuse.h"
|
||||
|
||||
|
||||
overlay::Window::Window(SpiceOverlay *overlay) : overlay(overlay) {
|
||||
}
|
||||
|
||||
overlay::Window::~Window() {
|
||||
|
||||
// kill children
|
||||
for (auto &child : this->children) {
|
||||
delete child;
|
||||
}
|
||||
}
|
||||
|
||||
void overlay::Window::update() {
|
||||
|
||||
// check if toggle is enabled
|
||||
if (this->toggle_button != ~0u) {
|
||||
|
||||
// get state
|
||||
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
|
||||
bool toggle_button_new = overlay_buttons
|
||||
&& this->overlay->hotkeys_triggered()
|
||||
&& GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(this->toggle_button));
|
||||
if (toggle_button_new && !this->toggle_button_state) {
|
||||
|
||||
// if the overlay is hidden just reactivate it
|
||||
if (!this->overlay->get_active()) {
|
||||
this->active = true;
|
||||
this->overlay->set_active(true);
|
||||
} else {
|
||||
this->toggle_active();
|
||||
}
|
||||
}
|
||||
this->toggle_button_state = toggle_button_new;
|
||||
}
|
||||
|
||||
// update children
|
||||
auto it = this->children.begin();
|
||||
while (it != this->children.end()) {
|
||||
(*it)->update();
|
||||
if ((*it)->active) {
|
||||
it++;
|
||||
} else {
|
||||
delete (*it);
|
||||
this->children.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void overlay::Window::build() {
|
||||
|
||||
// check if active
|
||||
if (!this->active) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->draws_window) {
|
||||
|
||||
// automatic max window size
|
||||
if (!cfg::CONFIGURATOR_STANDALONE && (size_max.x < 0 || size_max.y < 0)) {
|
||||
auto &display_size = ImGui::GetIO().DisplaySize;
|
||||
ImVec2 size_max_auto(display_size.x - 100, display_size.y - 100);
|
||||
ImGui::SetNextWindowSizeConstraints(size_min, size_max_auto, resize_callback);
|
||||
} else {
|
||||
ImGui::SetNextWindowSizeConstraints(size_min, size_max, resize_callback);
|
||||
}
|
||||
|
||||
// background alpha
|
||||
if (this->bg_alpha != 1.f) {
|
||||
ImGui::SetNextWindowBgAlpha(this->bg_alpha);
|
||||
}
|
||||
|
||||
if (this->remove_window_padding) {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
}
|
||||
|
||||
// create window
|
||||
if (ImGui::Begin(
|
||||
(this->title + "###" + to_string(this)).c_str(),
|
||||
&this->active,
|
||||
this->flags)) {
|
||||
|
||||
// window attributes
|
||||
this->calculate_initial_window();
|
||||
ImGui::SetWindowPos(this->init_pos, ImGuiCond_Once);
|
||||
ImGui::SetWindowSize(this->init_size, ImGuiCond_Once);
|
||||
|
||||
// add content
|
||||
this->build_content();
|
||||
|
||||
// build children
|
||||
for (auto &child : this->children) {
|
||||
child->build();
|
||||
}
|
||||
|
||||
// end window
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (this->remove_window_padding) {
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// add raw content
|
||||
this->build_content();
|
||||
}
|
||||
}
|
||||
|
||||
void overlay::Window::toggle_active() {
|
||||
|
||||
// flip bool
|
||||
this->active = !this->active;
|
||||
|
||||
// update children
|
||||
for (auto &child : this->children) {
|
||||
child->toggle_active();
|
||||
}
|
||||
}
|
||||
|
||||
void overlay::Window::set_active(bool active) {
|
||||
|
||||
// toggle if different
|
||||
if (this->get_active() != active) {
|
||||
|
||||
// flip bool
|
||||
this->active = !this->active;
|
||||
|
||||
// update children
|
||||
for (auto &child : this->children) {
|
||||
child->set_active(this->active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool overlay::Window::get_active() {
|
||||
|
||||
// check for active children
|
||||
for (auto &child : this->children) {
|
||||
if (child->get_active()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// now it depends on us
|
||||
return this->active;
|
||||
}
|
||||
52
overlay/window.h
Normal file
52
overlay/window.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "external/imgui/imgui.h"
|
||||
#include "external/imgui/imgui_stdlib.h"
|
||||
#include "overlay.h"
|
||||
|
||||
namespace overlay {
|
||||
|
||||
class Window {
|
||||
public:
|
||||
virtual ~Window();
|
||||
|
||||
// an opportunity to calculate initial window position and size within the ImGui
|
||||
// window context, since it may not be possible in the Window constructor
|
||||
virtual void calculate_initial_window() {}
|
||||
|
||||
virtual void build_content() = 0;
|
||||
virtual void update();
|
||||
virtual void after_render() {};
|
||||
|
||||
void build();
|
||||
void toggle_active();
|
||||
void set_active(bool active);
|
||||
bool get_active();
|
||||
protected:
|
||||
|
||||
// state
|
||||
SpiceOverlay *overlay;
|
||||
bool active = false;
|
||||
std::vector<Window*> children;
|
||||
|
||||
// settings
|
||||
bool remove_window_padding = false;
|
||||
bool draws_window = true;
|
||||
ImGuiSizeCallback resize_callback = nullptr;
|
||||
std::string title = "Title";
|
||||
ImGuiWindowFlags flags = 0;
|
||||
size_t toggle_button = ~0u;
|
||||
bool toggle_button_state = false;
|
||||
|
||||
// init settings
|
||||
ImVec2 init_pos = ImVec2(0, 0);
|
||||
ImVec2 init_size = ImVec2(0, 0);
|
||||
ImVec2 size_min = ImVec2(0, 0);
|
||||
ImVec2 size_max = ImVec2(-1, -1);
|
||||
float bg_alpha = 0.8f;
|
||||
|
||||
Window(SpiceOverlay *overlay);
|
||||
};
|
||||
}
|
||||
44
overlay/windows/acio_status_buffers.cpp
Normal file
44
overlay/windows/acio_status_buffers.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "acio_status_buffers.h"
|
||||
#include "overlay/imgui/extensions.h"
|
||||
#include "external/imgui/imgui_memory_editor.h"
|
||||
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
ACIOStatusBuffers::ACIOStatusBuffers(SpiceOverlay *overlay, acio::ACIOModule *module)
|
||||
: Window(overlay), module(module) {
|
||||
this->title = module->name + " Status Buffers";
|
||||
this->init_size = ImVec2(600, 400);
|
||||
this->init_pos = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
|
||||
ImGui::GetIO().DisplaySize.y / 2 - this->init_size.y / 2);
|
||||
this->active = true;
|
||||
|
||||
// configure editor defaults
|
||||
this->editor = new MemoryEditor();
|
||||
this->editor->OptShowDataPreview = true;
|
||||
this->editor->PreviewDataType = ImGuiDataType_U16;
|
||||
}
|
||||
|
||||
ACIOStatusBuffers::~ACIOStatusBuffers() {
|
||||
|
||||
// kill editor
|
||||
delete this->editor;
|
||||
}
|
||||
|
||||
void ACIOStatusBuffers::build_content() {
|
||||
|
||||
// freeze checkbox
|
||||
if (module->status_buffer_freeze) {
|
||||
ImGui::Checkbox("Freeze", module->status_buffer_freeze);
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Prevent automatic modifications to the buffer.");
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
// draw editor
|
||||
this->editor->DrawContents(
|
||||
this->module->status_buffer,
|
||||
this->module->status_buffer_size);
|
||||
}
|
||||
}
|
||||
22
overlay/windows/acio_status_buffers.h
Normal file
22
overlay/windows/acio_status_buffers.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "overlay/window.h"
|
||||
#include "acio/acio.h"
|
||||
|
||||
struct MemoryEditor;
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class ACIOStatusBuffers : public Window {
|
||||
public:
|
||||
|
||||
ACIOStatusBuffers(SpiceOverlay *overlay, acio::ACIOModule *module);
|
||||
~ACIOStatusBuffers() override;
|
||||
|
||||
void build_content() override;
|
||||
|
||||
private:
|
||||
acio::ACIOModule *module;
|
||||
MemoryEditor *editor;
|
||||
};
|
||||
}
|
||||
192
overlay/windows/camera_control.cpp
Normal file
192
overlay/windows/camera_control.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
#include <games/io.h>
|
||||
#include "camera_control.h"
|
||||
|
||||
#include <strmif.h>
|
||||
|
||||
#include "games/iidx/camera.h"
|
||||
#include "games/iidx/local_camera.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "overlay/imgui/extensions.h"
|
||||
|
||||
using namespace games::iidx;
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
CameraControl::CameraControl(SpiceOverlay *overlay) : Window(overlay) {
|
||||
this->title = "Camera Control";
|
||||
this->flags |= ImGuiWindowFlags_AlwaysAutoResize;
|
||||
this->init_pos = ImVec2(0, 0);
|
||||
this->toggle_button = games::OverlayButtons::ToggleCameraControl;
|
||||
}
|
||||
|
||||
CameraControl::~CameraControl() {
|
||||
}
|
||||
|
||||
void CameraControl::build_content() {
|
||||
if (!CAMERA_READY) {
|
||||
ImGui::TextColored(ImVec4(1.f, 1.f, 0.f, 1.f), "%s", "Camera not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
// camera combo box
|
||||
auto numCameras = LOCAL_CAMERA_LIST.size();
|
||||
|
||||
if (numCameras == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
IIDXLocalCamera *selectedCamera = LOCAL_CAMERA_LIST.at(m_selectedCameraIndex);
|
||||
auto selectedCameraChanged = ImGui::BeginCombo(
|
||||
"Source", selectedCamera->GetName().c_str()
|
||||
);
|
||||
|
||||
if (selectedCameraChanged) {
|
||||
for (size_t i = 0; i < numCameras; i++) {
|
||||
IIDXLocalCamera *cameraItem = LOCAL_CAMERA_LIST.at(i);
|
||||
const bool is_selected = (m_selectedCameraIndex == (int) i);
|
||||
if (ImGui::Selectable(cameraItem->GetName().c_str(), is_selected)) {
|
||||
m_selectedCameraIndex = i;
|
||||
}
|
||||
|
||||
if (is_selected) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
selectedCamera = LOCAL_CAMERA_LIST.at(m_selectedCameraIndex);
|
||||
|
||||
// Render parameters
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Rendering");
|
||||
|
||||
// Media Type Selector
|
||||
int selectedMediaTypeIndex = selectedCamera->m_selectedMediaTypeIndex;
|
||||
auto numMediaTypes = selectedCamera->m_mediaTypeInfos.size();
|
||||
|
||||
auto selectedMediaTypeIndexChanged = ImGui::BeginCombo(
|
||||
"Media Type", selectedCamera->m_mediaTypeInfos.at(selectedMediaTypeIndex).description.c_str()
|
||||
);
|
||||
|
||||
if (selectedMediaTypeIndexChanged) {
|
||||
for (size_t i = 0; i < numMediaTypes; i++) {
|
||||
const bool is_selected = (selectedMediaTypeIndex == (int) i);
|
||||
if (ImGui::Selectable(selectedCamera->m_mediaTypeInfos.at(i).description.c_str(), is_selected)) {
|
||||
selectedMediaTypeIndex = i;
|
||||
}
|
||||
|
||||
if (is_selected) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
if (selectedMediaTypeIndexChanged && selectedMediaTypeIndex != selectedCamera->m_selectedMediaTypeIndex) {
|
||||
selectedCamera->m_useAutoMediaType = false;
|
||||
selectedCamera->ChangeMediaType(selectedCamera->m_mediaTypeInfos.at(selectedMediaTypeIndex).p_mediaType);
|
||||
}
|
||||
|
||||
// Auto media type
|
||||
bool isAutoMediaType = selectedCamera->m_useAutoMediaType;
|
||||
ImGui::SameLine(300);
|
||||
if (ImGui::Checkbox("Auto##MediaType", &isAutoMediaType)) {
|
||||
selectedCamera->m_useAutoMediaType = isAutoMediaType;
|
||||
if (isAutoMediaType) {
|
||||
selectedCamera->ChangeMediaType(selectedCamera->m_pAutoMediaType);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw mode
|
||||
int selectedDrawModeIndex = selectedCamera->m_drawMode;
|
||||
if (ImGui::BeginCombo("Draw Mode", DRAW_MODE_LABELS[selectedCamera->m_drawMode].c_str())) {
|
||||
for (size_t i = 0; i < DRAW_MODE_SIZE; i++) {
|
||||
const bool is_selected = (selectedDrawModeIndex == (int) i);
|
||||
if (ImGui::Selectable(DRAW_MODE_LABELS[i].c_str(), is_selected)) {
|
||||
selectedDrawModeIndex = i;
|
||||
}
|
||||
|
||||
if (is_selected) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Stretch: direct copy from source to destination\n\n"
|
||||
"Crop: Keep aspect ratio (16:9) and cut off horizontally or vertically\n\n"
|
||||
"Letterbox: Keep aspect ratio (16:9) and add black space horizontally or vertically\n\n"
|
||||
"Crop to 4:3: Crop to display as 4:3\n\n"
|
||||
"Letterbox to 4:3: Like Letterbox, but target 4:3"
|
||||
);
|
||||
|
||||
if (selectedDrawModeIndex != selectedCamera->m_drawMode) {
|
||||
selectedCamera->m_drawMode = (LocalCameraDrawMode)selectedDrawModeIndex;
|
||||
selectedCamera->UpdateDrawRect();
|
||||
}
|
||||
|
||||
// Camera control parameters
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Camera control");
|
||||
|
||||
// some high end webcams store settings on its onboard memory, with the user configuring it
|
||||
// via proprietary software outside of the game, so don't mess with it unless the user
|
||||
// explicitly wants to change things here
|
||||
ImGui::Checkbox("Allow manual control", &selectedCamera->m_allowManualControl);
|
||||
|
||||
ImGui::BeginDisabled(!selectedCamera->m_allowManualControl);
|
||||
IAMCameraControl *pCameraControl = selectedCamera->GetCameraControl();
|
||||
if (pCameraControl) {
|
||||
for (size_t i = 0; i < CAMERA_CONTROL_PROP_SIZE; i++) {
|
||||
CameraControlProp prop = {};
|
||||
selectedCamera->GetCameraControlProp(i, &prop);
|
||||
|
||||
auto value = prop.value;
|
||||
|
||||
bool isDisabled = (prop.defFlags == 0 || prop.valueFlags & CameraControl_Flags_Auto);
|
||||
ImGui::BeginDisabled(isDisabled);
|
||||
|
||||
int sliderFlag = ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_NoRoundToFormat;
|
||||
|
||||
bool isDefAuto = prop.defFlags & CameraControl_Flags_Auto;
|
||||
|
||||
if (ImGui::SliderInt(CAMERA_CONTROL_LABELS[i].c_str(), (int*) &value, prop.minValue, prop.maxValue, "%d", sliderFlag)) {
|
||||
selectedCamera->SetCameraControlProp(i, value, prop.valueFlags);
|
||||
}
|
||||
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (isDefAuto) {
|
||||
ImGui::SameLine(300);
|
||||
if (ImGui::CheckboxFlags(("Auto##" + CAMERA_CONTROL_LABELS[i]).c_str(), (int*) &prop.valueFlags, CameraControl_Flags_Auto)) {
|
||||
selectedCamera->SetCameraControlProp(i, value, prop.valueFlags);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// reset button
|
||||
if (ImGui::Button("Reset")) {
|
||||
selectedCamera->ResetCameraControlProps();
|
||||
}
|
||||
|
||||
ImGui::EndDisabled();
|
||||
|
||||
// save button
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Save")) {
|
||||
this->config_save();
|
||||
}
|
||||
}
|
||||
|
||||
void CameraControl::config_save() {
|
||||
camera_config_save();
|
||||
}
|
||||
}
|
||||
20
overlay/windows/camera_control.h
Normal file
20
overlay/windows/camera_control.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "overlay/window.h"
|
||||
#include <strmif.h>
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class CameraControl : public Window {
|
||||
public:
|
||||
CameraControl(SpiceOverlay *overlay);
|
||||
~CameraControl() override;
|
||||
|
||||
void build_content() override;
|
||||
|
||||
private:
|
||||
int m_selectedCameraIndex = 0;
|
||||
bool config_dirty = false;
|
||||
void config_save();
|
||||
};
|
||||
}
|
||||
493
overlay/windows/card_manager.cpp
Normal file
493
overlay/windows/card_manager.cpp
Normal file
@@ -0,0 +1,493 @@
|
||||
#include <random>
|
||||
#include <games/io.h>
|
||||
#include "card_manager.h"
|
||||
|
||||
#include "external/rapidjson/document.h"
|
||||
#include "external/rapidjson/prettywriter.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "cfg/configurator.h"
|
||||
|
||||
using namespace rapidjson;
|
||||
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
CardManager::CardManager(SpiceOverlay *overlay) : Window(overlay) {
|
||||
this->title = "Card Manager";
|
||||
this->init_size = ImVec2(420, 420);
|
||||
|
||||
if (cfg::CONFIGURATOR_STANDALONE) {
|
||||
this->init_pos = ImVec2(40, 40);
|
||||
} else {
|
||||
this->init_pos = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
|
||||
ImGui::GetIO().DisplaySize.y / 2 - this->init_size.y / 2);
|
||||
}
|
||||
|
||||
this->toggle_button = games::OverlayButtons::ToggleCardManager;
|
||||
this->config_path = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools_card_manager.json";
|
||||
if (fileutils::file_exists(this->config_path)) {
|
||||
this->config_load();
|
||||
}
|
||||
}
|
||||
|
||||
CardManager::~CardManager() {
|
||||
}
|
||||
|
||||
void CardManager::build_content() {
|
||||
ImGui::SeparatorText("Selected card");
|
||||
build_card();
|
||||
ImGui::SeparatorText("Available cards");
|
||||
build_card_list();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
build_footer();
|
||||
|
||||
build_card_editor();
|
||||
}
|
||||
|
||||
void CardManager::build_card() {
|
||||
ImGui::BeginDisabled(this->current_card == nullptr);
|
||||
// insert P1 button
|
||||
if (ImGui::Button("Insert P1")) {
|
||||
const auto card = this->current_card;
|
||||
uint8_t card_bin[8];
|
||||
if (card && card->id.length() == 16 && hex2bin(card->id.c_str(), card_bin)) {
|
||||
eamuse_card_insert(0, card_bin);
|
||||
}
|
||||
}
|
||||
|
||||
// insert P2 button
|
||||
if (eamuse_get_game_keypads() > 1) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Insert P2")) {
|
||||
const auto card = this->current_card;
|
||||
uint8_t card_bin[8];
|
||||
if (card && card->id.length() == 16 && hex2bin(card->id.c_str(), card_bin)) {
|
||||
eamuse_card_insert(1, card_bin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// edit selected card
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Edit Card")) {
|
||||
open_card_editor();
|
||||
}
|
||||
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// card ui
|
||||
if (this->current_card) {
|
||||
const auto card = this->current_card;
|
||||
const ImVec4 color(card->color[0], card->color[1], card->color[2], 1.f);
|
||||
float bg_luminance = (0.299f * card->color[0] + 0.587 * card->color[1] + 0.114 * card->color[2]);
|
||||
|
||||
// text color
|
||||
ImVec4 text_color;
|
||||
if (0.5f < bg_luminance) {
|
||||
text_color = ImVec4(0.f, 0.f, 0.f, 1.f); // black
|
||||
} else {
|
||||
text_color = ImVec4(1.f, 1.f, 1.f, 1.f); // white
|
||||
}
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, color);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, color);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, color);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, text_color);
|
||||
if (ImGui::Button(fmt::format(
|
||||
" {}\n {} ",
|
||||
card->name.empty() ? "<blank>" : card->name.substr(0, 16),
|
||||
card->id
|
||||
).c_str())) {
|
||||
|
||||
open_card_editor();
|
||||
}
|
||||
ImGui::PopStyleColor(4);
|
||||
} else {
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::Button(" <No card> \n xxxxxxxxxxxxxxxx ");
|
||||
ImGui::EndDisabled();
|
||||
ImGui::PopStyleColor(4);
|
||||
}
|
||||
}
|
||||
|
||||
void CardManager::open_card_editor() {
|
||||
if (this->current_card) {
|
||||
const auto card = this->current_card;
|
||||
strcpy_s(this->name_buffer, std::size(this->name_buffer), card->name.c_str());
|
||||
strcpy_s(this->card_buffer, std::size(this->card_buffer), card->id.c_str());
|
||||
this->color_buffer[0] = card->color[0];
|
||||
this->color_buffer[1] = card->color[1];
|
||||
this->color_buffer[2] = card->color[2];
|
||||
ImGui::OpenPopup("Card Editor");
|
||||
}
|
||||
}
|
||||
|
||||
void CardManager::build_card_editor() {
|
||||
// new/edit card popup
|
||||
if (ImGui::BeginPopupModal("Card Editor", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
// card ID field (only editable for new cards)
|
||||
ImGui::BeginDisabled(this->current_card);
|
||||
ImGui::InputTextWithHint("Card ID", "E0040123456789AB",
|
||||
this->card_buffer,
|
||||
std::size(this->card_buffer),
|
||||
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase);
|
||||
if (this->current_card == nullptr) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Generate")) {
|
||||
generate_ea_card(this->card_buffer);
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
// name field
|
||||
ImGui::InputTextWithHint("Card Name", "Main Card",
|
||||
this->name_buffer, std::size(this->name_buffer));
|
||||
|
||||
// color
|
||||
ImGui::ColorEdit3("Color", this->color_buffer, ImGuiColorEditFlags_DisplayHex);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Random")) {
|
||||
generate_random_color();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// add/update button
|
||||
ImGui::BeginDisabled(strlen(this->card_buffer) != 16);
|
||||
if (ImGui::Button(this->current_card ? "Update Card" : "Save Card")) {
|
||||
if (this->current_card) {
|
||||
// update existing card
|
||||
this->current_card->name = strtrim(this->name_buffer);
|
||||
this->current_card->color[0] = this->color_buffer[0];
|
||||
this->current_card->color[1] = this->color_buffer[1];
|
||||
this->current_card->color[2] = this->color_buffer[2];
|
||||
generate_search_string(this->current_card);
|
||||
} else {
|
||||
// create a new card
|
||||
CardEntry card {
|
||||
.name = strtrim(this->name_buffer),
|
||||
.id = std::string(this->card_buffer),
|
||||
.color = {this->color_buffer[0], this->color_buffer[1], this->color_buffer[2]}
|
||||
};
|
||||
generate_search_string(&card);
|
||||
this->cards.emplace_back(card);
|
||||
|
||||
// mark this card as the selected one
|
||||
this->current_card = &this->cards.back();
|
||||
}
|
||||
this->config_save();
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
// delete current card button
|
||||
if (this->current_card) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Delete Card")) {
|
||||
std::erase_if(this->cards, [&](CardEntry &card) {
|
||||
return &card == this->current_card;
|
||||
});
|
||||
this->current_card = nullptr;
|
||||
this->config_save();
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void CardManager::build_card_list() {
|
||||
|
||||
// search for card
|
||||
//
|
||||
// setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below
|
||||
// to return true, making it necessary to provide a callback...
|
||||
ImGui::SetNextItemWidth(220);
|
||||
if (ImGui::InputTextWithHint("", "Type here to search..", &this->search_filter)) {
|
||||
this->search_filter_in_lower_case = strtolower(this->search_filter);
|
||||
}
|
||||
if (!this->search_filter.empty()) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear")) {
|
||||
this->search_filter.clear();
|
||||
this->search_filter_in_lower_case.clear();
|
||||
}
|
||||
} else {
|
||||
ImGui::SameLine();
|
||||
|
||||
// move selected up/down the list
|
||||
ImGui::BeginDisabled(this->current_card == nullptr);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Move Up")) {
|
||||
for (auto it = this->cards.begin(); it != this->cards.end(); ++it) {
|
||||
if (&*it == this->current_card && it != this->cards.begin()) {
|
||||
std::iter_swap(it, it - 1);
|
||||
this->current_card = &*(it - 1);
|
||||
this->config_dirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Move Down")) {
|
||||
for (auto it = this->cards.begin(); it != this->cards.end(); ++it) {
|
||||
if (&*it == this->current_card && (it + 1) != this->cards.end()) {
|
||||
std::iter_swap(it, it + 1);
|
||||
this->current_card = &*(it + 1);
|
||||
this->config_dirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// cards list
|
||||
// use all available vertical space, minus height of buttons, minus separator
|
||||
if (ImGui::BeginChild(
|
||||
"cards",
|
||||
ImVec2(0, ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing() - 8.f))) {
|
||||
for (auto &card : this->cards) {
|
||||
|
||||
// get card name
|
||||
std::string card_name = card.id;
|
||||
if (!card.name.empty()) {
|
||||
card_name += " - ";
|
||||
card_name += card.name;
|
||||
}
|
||||
|
||||
if (!this->search_filter_in_lower_case.empty() && !card.search_string.empty()) {
|
||||
const bool matched =
|
||||
card.search_string.find(this->search_filter_in_lower_case) != std::string::npos;
|
||||
|
||||
if (!matched) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// draw entry
|
||||
ImGui::PushID(&card);
|
||||
|
||||
ImVec4 color(card.color[0], card.color[1], card.color[2], 1.f);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, color);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, color);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, color);
|
||||
ImGui::SmallButton(" ");
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Selectable(card_name.c_str(), this->current_card == &card)) {
|
||||
this->current_card = &card;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void CardManager::build_footer() {
|
||||
|
||||
// add new card
|
||||
if (ImGui::Button("Add New Card")) {
|
||||
memset(this->name_buffer, 0, sizeof(this->name_buffer));
|
||||
memset(this->card_buffer, 0, sizeof(this->card_buffer));
|
||||
generate_random_color();
|
||||
this->current_card = nullptr;
|
||||
ImGui::OpenPopup("Card Editor");
|
||||
}
|
||||
|
||||
// save button
|
||||
if (this->config_dirty) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Save")) {
|
||||
this->config_save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CardManager::config_load() {
|
||||
log_info("cardmanager", "loading config");
|
||||
|
||||
// clear cards
|
||||
this->cards.clear();
|
||||
|
||||
// read config file
|
||||
std::string config = fileutils::text_read(this->config_path);
|
||||
if (!config.empty()) {
|
||||
|
||||
// parse document
|
||||
Document doc;
|
||||
doc.Parse(config.c_str());
|
||||
|
||||
// check parse error
|
||||
auto error = doc.GetParseError();
|
||||
if (error) {
|
||||
log_warning("cardmanager", "config parse error: {}", error);
|
||||
}
|
||||
|
||||
// verify root is a dict
|
||||
if (doc.IsObject()) {
|
||||
|
||||
// find pages
|
||||
auto pages = doc.FindMember("pages");
|
||||
if (pages != doc.MemberEnd() && pages->value.IsArray()) {
|
||||
|
||||
// iterate pages
|
||||
for (auto &page : pages->value.GetArray()) {
|
||||
if (page.IsObject()) {
|
||||
|
||||
// get cards
|
||||
auto cards = page.FindMember("cards");
|
||||
if (cards != doc.MemberEnd() && cards->value.IsArray()) {
|
||||
|
||||
// iterate cards
|
||||
for (auto &card : cards->value.GetArray()) {
|
||||
if (card.IsObject()) {
|
||||
|
||||
// find attributes
|
||||
auto name = card.FindMember("name");
|
||||
if (name == doc.MemberEnd() || !name->value.IsString()) {
|
||||
log_warning("cardmanager", "card name not found");
|
||||
continue;
|
||||
}
|
||||
auto id = card.FindMember("id");
|
||||
if (id == doc.MemberEnd() || !id->value.IsString()) {
|
||||
log_warning("cardmanager", "card id not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
// save entry
|
||||
CardEntry entry {
|
||||
.name = name->value.GetString(),
|
||||
.id = id->value.GetString(),
|
||||
};
|
||||
generate_search_string(&entry);
|
||||
|
||||
// optional color
|
||||
auto color = card.FindMember("color");
|
||||
if (color != doc.MemberEnd() && color->value.IsArray()) {
|
||||
auto c = color->value.GetArray();
|
||||
if (c.Size() == 3 && c[0].IsFloat()) {
|
||||
entry.color[0] = c[0].GetFloat();
|
||||
entry.color[1] = c[1].GetFloat();
|
||||
entry.color[2] = c[2].GetFloat();
|
||||
}
|
||||
}
|
||||
|
||||
this->cards.emplace_back(entry);
|
||||
|
||||
} else {
|
||||
log_warning("cardmanager", "card is not an object");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log_warning("cardmanager", "cards not found or not an array");
|
||||
}
|
||||
} else {
|
||||
log_warning("cardmanager", "page is not an object");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log_warning("cardmanager", "pages not found or not an array");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CardManager::config_save() {
|
||||
log_info("cardmanager", "saving config");
|
||||
|
||||
// create document
|
||||
Document doc;
|
||||
doc.Parse(
|
||||
"{"
|
||||
" \"pages\": ["
|
||||
" {"
|
||||
" \"cards\": ["
|
||||
" ]"
|
||||
" }"
|
||||
" ]"
|
||||
"}"
|
||||
);
|
||||
|
||||
// check parse error
|
||||
auto error = doc.GetParseError();
|
||||
if (error) {
|
||||
log_warning("cardmanager", "template parse error: {}", error);
|
||||
}
|
||||
|
||||
// add cards
|
||||
auto &cards = doc["pages"][0]["cards"];
|
||||
for (auto &entry : this->cards) {
|
||||
Value card(kObjectType);
|
||||
card.AddMember("name", StringRef(entry.name.c_str()), doc.GetAllocator());
|
||||
card.AddMember("id", StringRef(entry.id.c_str()), doc.GetAllocator());
|
||||
|
||||
Value color(kArrayType);
|
||||
color.PushBack(entry.color[0], doc.GetAllocator());
|
||||
color.PushBack(entry.color[1], doc.GetAllocator());
|
||||
color.PushBack(entry.color[2], doc.GetAllocator());
|
||||
card.AddMember("color", color, doc.GetAllocator());
|
||||
|
||||
cards.PushBack(card, doc.GetAllocator());
|
||||
}
|
||||
|
||||
// build JSON; using pretty writer so people can manually edit it
|
||||
StringBuffer buffer;
|
||||
PrettyWriter<StringBuffer> writer(buffer);
|
||||
doc.Accept(writer);
|
||||
|
||||
// save to file
|
||||
if (fileutils::text_write(this->config_path, buffer.GetString())) {
|
||||
this->config_dirty = false;
|
||||
} else {
|
||||
log_warning("cardmanager", "unable to save config file to {}", this->config_path.string());
|
||||
}
|
||||
}
|
||||
|
||||
void CardManager::generate_search_string(CardEntry *card) {
|
||||
card->search_string = strtolower(card->name) + " " + strtolower(card->id);
|
||||
}
|
||||
|
||||
void CardManager::generate_random_color() {
|
||||
// these are colors on a hue wheel, starting from red
|
||||
static const char colors[48][7] = {
|
||||
"FF0000","FF2000","FF4000","FF6000","FF8000","FFAA00","FFCC00","FFEE00",
|
||||
"FFFF00","DDFF00","CCFF00","AAFF00","80FF00","60FF00","40FF00","20FF00",
|
||||
"00FF00","00FF20","00FF40","00FF60","00FF80","00FFAA","00FFCC","00FFDD",
|
||||
"00FFFF","00DDFF","00CCFF","0099FF","0080FF","0060FF","0040FF","0020FF",
|
||||
"0000FF","2000FF","4000FF","6000FF","8000FF","AA00FF","CC00FF","DD00FF",
|
||||
"FF00FF","FF00EE","FF00CC","FF00AA","FF0080","FF0060","FF0040","FF0020"
|
||||
};
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 generator(rd());
|
||||
std::uniform_int_distribution<> uniform(12, 36);
|
||||
|
||||
// skip, ignoring half the hue wheel close to current index
|
||||
static int index = 0;
|
||||
index = (index + uniform(generator)) % 48;
|
||||
|
||||
const auto hex = colors[index];
|
||||
|
||||
uint8_t r, g, b;
|
||||
std::sscanf(hex, "%02hhx%02hhx%02hhx", &r, &g, &b);
|
||||
this->color_buffer[0] = r / 255.f;
|
||||
this->color_buffer[1] = g / 255.f;
|
||||
this->color_buffer[2] = b / 255.f;
|
||||
}
|
||||
}
|
||||
50
overlay/windows/card_manager.h
Normal file
50
overlay/windows/card_manager.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "overlay/window.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
struct CardEntry {
|
||||
std::string name = "unnamed";
|
||||
std::string id = "E004010000000000";
|
||||
std::string search_string = "";
|
||||
float color[3] {};
|
||||
};
|
||||
|
||||
class CardManager : public Window {
|
||||
public:
|
||||
|
||||
CardManager(SpiceOverlay *overlay);
|
||||
~CardManager() override;
|
||||
|
||||
void build_content() override;
|
||||
|
||||
private:
|
||||
|
||||
std::filesystem::path config_path;
|
||||
bool config_dirty = false;
|
||||
std::vector<CardEntry> cards;
|
||||
char name_buffer[65] {};
|
||||
char card_buffer[17] {};
|
||||
float color_buffer[3] {};
|
||||
|
||||
CardEntry *current_card = nullptr;
|
||||
|
||||
std::string search_filter = "";
|
||||
std::string search_filter_in_lower_case = "";
|
||||
|
||||
void config_load();
|
||||
void config_save();
|
||||
|
||||
void generate_search_string(CardEntry *card);
|
||||
void generate_random_color();
|
||||
|
||||
void build_card();
|
||||
void open_card_editor();
|
||||
void build_card_editor();
|
||||
void build_card_list();
|
||||
void build_footer();
|
||||
};
|
||||
}
|
||||
2605
overlay/windows/config.cpp
Normal file
2605
overlay/windows/config.cpp
Normal file
File diff suppressed because it is too large
Load Diff
104
overlay/windows/config.h
Normal file
104
overlay/windows/config.h
Normal file
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "cfg/game.h"
|
||||
#include "overlay/window.h"
|
||||
#include "rawinput/device.h"
|
||||
#include "external/imgui/imgui_filebrowser.h"
|
||||
#include "patch_manager.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
enum class ConfigTab {
|
||||
CONFIG_TAB_INVALID,
|
||||
CONFIG_TAB_BUTTONS,
|
||||
CONFIG_TAB_ANALOGS,
|
||||
CONFIG_TAB_OVERLAY,
|
||||
CONFIG_TAB_LIGHTS,
|
||||
CONFIG_TAB_CARDS,
|
||||
CONFIG_TAB_PATCHES,
|
||||
CONFIG_TAB_API,
|
||||
CONFIG_TAB_OPTIONS,
|
||||
CONFIG_TAB_ADVANCED,
|
||||
CONFIG_TAB_DEV,
|
||||
CONFIG_TAB_SEARCH,
|
||||
};
|
||||
|
||||
class Config : public Window {
|
||||
private:
|
||||
|
||||
// game selection
|
||||
int games_selected = -1;
|
||||
std::string games_selected_name = "";
|
||||
std::vector<Game> games_list;
|
||||
std::vector<const char *> games_names;
|
||||
|
||||
// tabs ui
|
||||
ConfigTab tab_selected = ConfigTab::CONFIG_TAB_INVALID;
|
||||
|
||||
// buttons tab
|
||||
int buttons_page = 0;
|
||||
bool buttons_keyboard_state[0xFF];
|
||||
bool buttons_bind_active = false;
|
||||
bool buttons_many_active = false;
|
||||
std::string buttons_many_active_section = "";
|
||||
bool buttons_many_naive = false;
|
||||
int buttons_many_delay = 0;
|
||||
int buttons_many_index = -1;
|
||||
|
||||
void inc_buttons_many_index(int index_max);
|
||||
|
||||
// analogs tab
|
||||
std::vector<rawinput::Device *> analogs_devices;
|
||||
int analogs_devices_selected = -1;
|
||||
int analogs_devices_control_selected = -1;
|
||||
|
||||
// lights tab
|
||||
int lights_page = 0;
|
||||
std::vector<rawinput::Device *> lights_devices;
|
||||
int lights_devices_selected = -1;
|
||||
int lights_devices_control_selected = -1;
|
||||
|
||||
// keypads tab
|
||||
int keypads_selected[2] {};
|
||||
char keypads_card_path[2][1024] {};
|
||||
std::thread *keypads_card_select = nullptr;
|
||||
bool keypads_card_select_done = false;
|
||||
ImGui::FileBrowser keypads_card_select_browser[2];
|
||||
char keypads_card_number[2][18] {};
|
||||
|
||||
// patches tab
|
||||
std::unique_ptr<PatchManager> patch_manager;
|
||||
|
||||
// options tab
|
||||
bool options_show_hidden = false;
|
||||
bool options_dirty = false;
|
||||
int options_category = 0;
|
||||
std::string search_filter = "";
|
||||
std::string search_filter_in_lower_case = "";
|
||||
|
||||
public:
|
||||
Config(SpiceOverlay *overlay);
|
||||
~Config() override;
|
||||
|
||||
void read_card(int player = -1);
|
||||
void write_card(int player);
|
||||
void build_content() override;
|
||||
void build_buttons(const std::string &name, std::vector<Button> *buttons,
|
||||
int min = 0, int max = -1);
|
||||
void build_analogs(const std::string &name, std::vector<Analog> *analogs);
|
||||
void build_lights(const std::string &name, std::vector<Light> *lights);
|
||||
void build_cards();
|
||||
void build_options(
|
||||
std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr);
|
||||
void build_about();
|
||||
void build_licenses();
|
||||
void build_launcher();
|
||||
void launch_shell(LPCSTR app, LPCSTR file);
|
||||
|
||||
static void build_page_selector(int *page);
|
||||
void build_menu(int *game_selected);
|
||||
void shutdown_system(bool force, bool reboot_instead);
|
||||
};
|
||||
}
|
||||
987
overlay/windows/control.cpp
Normal file
987
overlay/windows/control.cpp
Normal file
@@ -0,0 +1,987 @@
|
||||
#include <winsock2.h>
|
||||
|
||||
#include "control.h"
|
||||
|
||||
#include <csignal>
|
||||
|
||||
#include <psapi.h>
|
||||
|
||||
#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<int>(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<int>(ImGui::GetIO().DisplaySize.x),
|
||||
static_cast<int>(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<api::ClientState> 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<TouchPoint> 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<intptr_t>(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<uint16_t *>(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<size_t, const char *> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
53
overlay/windows/control.h
Normal file
53
overlay/windows/control.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "overlay/window.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class Control : public Window {
|
||||
public:
|
||||
|
||||
Control(SpiceOverlay *overlay);
|
||||
~Control() override;
|
||||
|
||||
void build_content() override;
|
||||
|
||||
private:
|
||||
|
||||
// state
|
||||
char card_input[17] {};
|
||||
|
||||
// other windows
|
||||
bool demo_open = false;
|
||||
bool metrics_open = false;
|
||||
std::vector<float> cpu_values;
|
||||
|
||||
// memory editor
|
||||
bool memory_editor_open = false;
|
||||
int memory_editor_selection = -1;
|
||||
std::vector<std::pair<std::string, HMODULE>> memory_editor_modules;
|
||||
std::vector<const char*> memory_editor_names;
|
||||
ImGuiTextFilter memory_editor_filter;
|
||||
|
||||
// pane views
|
||||
void top_row_buttons();
|
||||
void img_gui_view();
|
||||
void avs_info_view();
|
||||
void acio_view();
|
||||
void cpu_view();
|
||||
void graphics_view();
|
||||
void buttons_view();
|
||||
void analogs_view();
|
||||
void lights_view();
|
||||
void cards_view();
|
||||
void coin_view();
|
||||
void control_view();
|
||||
void api_view();
|
||||
void raw_input_view();
|
||||
void touch_view();
|
||||
void lcd_view();
|
||||
void about_view();
|
||||
void ddr_timing_view();
|
||||
void iidx_effectors_view();
|
||||
};
|
||||
}
|
||||
116
overlay/windows/eadev.cpp
Normal file
116
overlay/windows/eadev.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
#include "eadev.h"
|
||||
|
||||
#include "avs/automap.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "overlay/imgui/extensions.h"
|
||||
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
EADevWindow::EADevWindow(SpiceOverlay *overlay) : Window(overlay) {
|
||||
this->title = "EA-Dev";
|
||||
this->init_size = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x * 0.8f,
|
||||
ImGui::GetIO().DisplaySize.y * 0.8f);
|
||||
this->size_min = ImVec2(250, 200);
|
||||
this->init_pos = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
|
||||
ImGui::GetIO().DisplaySize.y / 2 - this->init_size.y / 2);
|
||||
this->active = true;
|
||||
|
||||
// read existing automap contents from file
|
||||
if (avs::automap::DUMP_FILENAME.length() > 0) {
|
||||
auto contents = fileutils::text_read(avs::automap::DUMP_FILENAME);
|
||||
if (contents.length() > 0) {
|
||||
this->automap_hook(this, contents.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// add hook for receiving automap messages
|
||||
avs::automap::hook_add(automap_hook, this);
|
||||
}
|
||||
|
||||
EADevWindow::~EADevWindow() {
|
||||
avs::automap::hook_remove(automap_hook, this);
|
||||
}
|
||||
|
||||
void EADevWindow::build_content() {
|
||||
|
||||
// automap
|
||||
ImGui::SetNextItemOpen(true, ImGuiCond_Once);
|
||||
if (ImGui::CollapsingHeader("Automap")) {
|
||||
|
||||
// enable checkbox
|
||||
if (ImGui::Checkbox("Enabled", &avs::automap::ENABLED)) {
|
||||
if (avs::automap::ENABLED) {
|
||||
avs::automap::enable();
|
||||
} else {
|
||||
avs::automap::disable();
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Enable this module.");
|
||||
|
||||
// dump checkbox
|
||||
ImGui::Checkbox("Dump", &avs::automap::DUMP);
|
||||
if (avs::automap::DUMP_FILENAME.length() > 0) {
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("- %s", avs::automap::DUMP_FILENAME.c_str());
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Dump all destroyed props to file.");
|
||||
|
||||
// json checkbox
|
||||
ImGui::Checkbox("JSON", &avs::automap::JSON);
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Output in JSON instead of XML.");
|
||||
|
||||
// patch checkbox
|
||||
ImGui::Checkbox("Patch", &avs::automap::PATCH);
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Try to dynamically add all non-existing nodes which are being accessed. (WIP)");
|
||||
|
||||
// network checkbox
|
||||
ImGui::Checkbox("Network Only", &avs::automap::RESTRICT_NETWORK);
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Restrict functionality to calls/responses.");
|
||||
|
||||
// autoscroll checkbox
|
||||
ImGui::Checkbox("Auto-Scroll", &this->automap_autoscroll);
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Automatically scroll to bottom.");
|
||||
|
||||
// clear button
|
||||
if (!this->automap_data.empty()) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear")) {
|
||||
this->automap_data.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// log view
|
||||
ImGui::Separator();
|
||||
ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
for (size_t i = 0; i < automap_data.size(); i++) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, (i % 2) == 0
|
||||
? ImVec4(1.0f, 0.7f, 0.7f, 1.f)
|
||||
: ImVec4(0.7f, 1.0f, 0.7f, 1.f));
|
||||
ImGui::TextUnformatted(automap_data[i].c_str());
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
if (this->automap_scroll_to_bottom) {
|
||||
this->automap_scroll_to_bottom = false;
|
||||
ImGui::SetScrollHereY(1.f);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
void EADevWindow::automap_hook(void *user, const char *data) {
|
||||
auto This = (EADevWindow*) user;
|
||||
This->automap_data.emplace_back(std::string(data));
|
||||
if (This->automap_autoscroll) {
|
||||
This->automap_scroll_to_bottom = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
overlay/windows/eadev.h
Normal file
22
overlay/windows/eadev.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "overlay/window.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class EADevWindow : public Window {
|
||||
public:
|
||||
|
||||
EADevWindow(SpiceOverlay *overlay);
|
||||
~EADevWindow() override;
|
||||
|
||||
void build_content() override;
|
||||
static void automap_hook(void *user, const char *data);
|
||||
|
||||
private:
|
||||
|
||||
bool automap_autoscroll = true;
|
||||
bool automap_scroll_to_bottom = false;
|
||||
std::vector<std::string> automap_data;
|
||||
};
|
||||
}
|
||||
52
overlay/windows/fps.cpp
Normal file
52
overlay/windows/fps.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include "fps.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
FPS::FPS(SpiceOverlay *overlay) : Window(overlay) {
|
||||
this->title = "Stats";
|
||||
this->flags = ImGuiWindowFlags_NoTitleBar
|
||||
| ImGuiWindowFlags_NoResize
|
||||
| ImGuiWindowFlags_NoCollapse
|
||||
| ImGuiWindowFlags_AlwaysAutoResize
|
||||
| ImGuiWindowFlags_NoFocusOnAppearing
|
||||
| ImGuiWindowFlags_NoNavFocus
|
||||
| ImGuiWindowFlags_NoNavInputs
|
||||
| ImGuiWindowFlags_NoDocking;
|
||||
this->bg_alpha = 0.4f;
|
||||
this->start_time = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
void FPS::calculate_initial_window() {
|
||||
// width is 114x82 px with window decoration, 98x47 for the content
|
||||
this->init_pos = ImVec2(ImGui::GetIO().DisplaySize.x - 120, 8);
|
||||
}
|
||||
|
||||
void FPS::build_content() {
|
||||
|
||||
// frame timers
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
ImGui::Text("FPS: %.1f", io.Framerate);
|
||||
// ImGui::Text("FT: %.2fms", 1000 / io.Framerate);
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
|
||||
// current time
|
||||
{
|
||||
auto now_t = std::chrono::system_clock::to_time_t(now);
|
||||
static CHAR buf[48];
|
||||
std::strftime(buf, sizeof(buf), "Time: %H:%M:%S", std::localtime(&now_t));
|
||||
ImGui::Text(buf);
|
||||
}
|
||||
|
||||
// elapsed time
|
||||
{
|
||||
auto d = now - this->start_time;
|
||||
const auto h = std::chrono::duration_cast<std::chrono::hours>(d);
|
||||
const auto m = std::chrono::duration_cast<std::chrono::minutes>(d - h);
|
||||
const auto s = std::chrono::duration_cast<std::chrono::seconds>(d - h - m);
|
||||
ImGui::Text("Up: %02d:%02d:%02d", (int)h.count(), (int)m.count(), (int)s.count());
|
||||
}
|
||||
}
|
||||
}
|
||||
19
overlay/windows/fps.h
Normal file
19
overlay/windows/fps.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include "overlay/window.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class FPS : public Window {
|
||||
private:
|
||||
std::chrono::system_clock::time_point start_time;
|
||||
|
||||
public:
|
||||
|
||||
FPS(SpiceOverlay *overlay);
|
||||
|
||||
void calculate_initial_window() override;
|
||||
void build_content() override;
|
||||
};
|
||||
}
|
||||
228
overlay/windows/generic_sub.cpp
Normal file
228
overlay/windows/generic_sub.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#undef CINTERFACE
|
||||
|
||||
#include "generic_sub.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "games/io.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
|
||||
#include "hooks/graphics/backends/d3d9/d3d9_device.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
#include "touch/touch.h"
|
||||
|
||||
int GENERIC_SUB_WINDOW_X = 0;
|
||||
int GENERIC_SUB_WINDOW_Y = 0;
|
||||
int GENERIC_SUB_WINDOW_WIDTH = 0;
|
||||
int GENERIC_SUB_WINDOW_HEIGHT = 0;
|
||||
bool GENERIC_SUB_WINDOW_FULLSIZE = false;
|
||||
|
||||
// #define OVERLAYDBG 1
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
const ImVec4 YELLOW(1.f, 1.f, 0.f, 1.f);
|
||||
const ImVec4 WHITE(1.f, 1.f, 1.f, 1.f);
|
||||
|
||||
GenericSubScreen::GenericSubScreen(SpiceOverlay *overlay) : Window(overlay), device(overlay->get_device()) {
|
||||
this->remove_window_padding = true;
|
||||
// ImGuiWindowFlags_NoBackground is needed as the background is drawn on top of the subscreen image
|
||||
this->flags = ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoBackground |
|
||||
ImGuiWindowFlags_NoDocking;
|
||||
|
||||
this->size_max = ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y);
|
||||
this->size_min = ImVec2(240, 135 + ImGui::GetFrameHeight());
|
||||
this->init_size = size_min;
|
||||
this->resize_callback = keep_16_by_9;
|
||||
|
||||
this->toggle_button = games::OverlayButtons::ToggleSubScreen;
|
||||
this->texture_width = 0;
|
||||
this->texture_height = 0;
|
||||
|
||||
overlay->set_subscreen_mouse_handler([this](LONG *x, LONG *y) -> bool {
|
||||
// convert to normalized form (relative window coordinates 0.f-1.f)
|
||||
ImVec2 xy;
|
||||
|
||||
// log_misc("sub::overlay", "mouse handler {} {}", to_string(*x), to_string(*y));
|
||||
// log_misc("sub::overlay", "spicetouch coords {} {} {} {}", to_string(SPICETOUCH_TOUCH_X), to_string(SPICETOUCH_TOUCH_Y), to_string(SPICETOUCH_TOUCH_WIDTH), to_string(SPICETOUCH_TOUCH_HEIGHT));
|
||||
|
||||
float ratio_x, ratio_y;
|
||||
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
// input coords are relative to spicetouch wnd
|
||||
ratio_x = (float)*x / SPICETOUCH_TOUCH_WIDTH;
|
||||
ratio_y = (float)*y / SPICETOUCH_TOUCH_HEIGHT;
|
||||
} else {
|
||||
// inputs coords are relative to (0,0) for non-windowed mode
|
||||
ratio_x = (float)*x / ImGui::GetIO().DisplaySize.x;
|
||||
ratio_y = (float)*y / ImGui::GetIO().DisplaySize.y;
|
||||
}
|
||||
// log_misc("sub::overlay", "game coords {} {}", to_string(ratio_x), to_string(ratio_y));
|
||||
|
||||
// transform to subscreen overlay coords
|
||||
if (!GENERIC_SUB_WINDOW_FULLSIZE) {
|
||||
ratio_x = (ratio_x * ImGui::GetIO().DisplaySize.x - GENERIC_SUB_WINDOW_X) / GENERIC_SUB_WINDOW_WIDTH;
|
||||
ratio_y = (ratio_y * ImGui::GetIO().DisplaySize.y - GENERIC_SUB_WINDOW_Y) / GENERIC_SUB_WINDOW_HEIGHT;
|
||||
// log_misc("sub::overlay", "overlay coords {} {} {} {}", to_string(GENERIC_SUB_WINDOW_X), to_string(GENERIC_SUB_WINDOW_Y), to_string(GENERIC_SUB_WINDOW_WIDTH), to_string(GENERIC_SUB_WINDOW_HEIGHT));
|
||||
}
|
||||
|
||||
xy.x = ratio_x;
|
||||
xy.y = ratio_y;
|
||||
// log_misc("sub::overlay", "ratio {} {}", to_string(xy.x), to_string(xy.y));
|
||||
|
||||
// x/y can be outside of window
|
||||
if (xy.x < 0.f || 1.f < xy.x || xy.y < 0.f || 1.f < xy.y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// call into child
|
||||
this->touch_transform(xy, x, y);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void GenericSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {}
|
||||
|
||||
void GenericSubScreen::build_content() {
|
||||
if (this->disabled_message.has_value()) {
|
||||
this->flags &= ~ImGuiWindowFlags_NoBackground;
|
||||
ImGui::TextColored(YELLOW, "%s", this->disabled_message.value().c_str());
|
||||
return;
|
||||
}
|
||||
this->draw_texture();
|
||||
|
||||
#if OVERLAYDBG
|
||||
if (this->status_message.has_value()) {
|
||||
log_warning("sub::overlay", "{}", this->status_message.value().c_str());
|
||||
}
|
||||
if (this->status_message.has_value()) {
|
||||
ImGui::TextColored(YELLOW, "%s", this->status_message.value().c_str());
|
||||
} else if (this->texture) {
|
||||
ImGui::TextColored(WHITE, "Successfully acquired surface texture");
|
||||
} else {
|
||||
ImGui::TextColored(YELLOW, "Failed to acquire surface texture");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool GenericSubScreen::build_texture(IDirect3DSurface9 *surface, UINT width, UINT height) {
|
||||
HRESULT hr;
|
||||
|
||||
D3DSURFACE_DESC desc {};
|
||||
hr = surface->GetDesc(&desc);
|
||||
if (FAILED(hr)) {
|
||||
this->status_message = fmt::format("Failed to get surface descriptor, hr={}", FMT_HRESULT(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = this->device->CreateTexture(width, height, 0, desc.Usage, desc.Format,
|
||||
desc.Pool, &this->texture, nullptr);
|
||||
if (FAILED(hr)) {
|
||||
this->status_message = fmt::format("Failed to create render target, hr={}", FMT_HRESULT(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
this->texture_width = width;
|
||||
this->texture_height = height;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GenericSubScreen::draw_texture() {
|
||||
HRESULT hr;
|
||||
|
||||
auto surface = graphics_d3d9_ldj_get_sub_screen();
|
||||
if (surface == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->draws_window) {
|
||||
// calculate the **content** location and size.
|
||||
// GetContentRegionAvail returns the correct dimension, up to (and including) the resize handle
|
||||
// don't be tempted to change it to some other ImGui routine that accomplishes similar things!
|
||||
overlay_content_top_left = ImGui::GetCursorScreenPos();
|
||||
overlay_content_size = ImGui::GetContentRegionAvail();
|
||||
} else {
|
||||
// no window, full screen
|
||||
overlay_content_top_left = ImVec2(0, 0);
|
||||
overlay_content_size = ImGui::GetIO().DisplaySize;
|
||||
}
|
||||
|
||||
GENERIC_SUB_WINDOW_X = overlay_content_top_left.x;
|
||||
GENERIC_SUB_WINDOW_Y = overlay_content_top_left.y;
|
||||
GENERIC_SUB_WINDOW_WIDTH = overlay_content_size.x;
|
||||
GENERIC_SUB_WINDOW_HEIGHT = overlay_content_size.y;
|
||||
|
||||
if (this->draws_window &&
|
||||
this->texture &&
|
||||
((UINT)overlay_content_size.x != this->texture_width)) {
|
||||
|
||||
#if OVERLAYDBG
|
||||
log_info("sub::overlay", "resize {} != {} ", overlay_content_size.x, this->texture_width);
|
||||
#endif
|
||||
|
||||
// hack needed for SDVX; resizing the texture results in darker image, so allocate texture
|
||||
// again with the new size when the window is resized
|
||||
this->texture->Release();
|
||||
this->texture = nullptr;
|
||||
this->texture_width = 0;
|
||||
this->texture_height = 0;
|
||||
}
|
||||
|
||||
if (this->texture == nullptr) {
|
||||
if (!this->build_texture(surface, overlay_content_size.x, overlay_content_size.y)) {
|
||||
this->texture = nullptr;
|
||||
this->texture_width = 0;
|
||||
this->texture_height = 0;
|
||||
surface->Release();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
IDirect3DSurface9 *texture_surface = nullptr;
|
||||
hr = this->texture->GetSurfaceLevel(0, &texture_surface);
|
||||
if (FAILED(hr)) {
|
||||
this->status_message = fmt::format("Failed to get texture surface, hr={}", FMT_HRESULT(hr));
|
||||
surface->Release();
|
||||
return;
|
||||
}
|
||||
|
||||
hr = this->device->StretchRect(surface, nullptr, texture_surface, nullptr, D3DTEXF_LINEAR);
|
||||
if (FAILED(hr)) {
|
||||
this->status_message = fmt::format("Failed to copy back buffer contents, hr={}", FMT_HRESULT(hr));
|
||||
surface->Release();
|
||||
texture_surface->Release();
|
||||
return;
|
||||
}
|
||||
|
||||
surface->Release();
|
||||
texture_surface->Release();
|
||||
|
||||
// draw the subscreen (this draws *under* ImGui windows, over the game surface)
|
||||
auto bottom_right = overlay_content_size;
|
||||
bottom_right.x += overlay_content_top_left.x;
|
||||
bottom_right.y += overlay_content_top_left.y;
|
||||
ImGui::GetBackgroundDrawList()->AddImage(
|
||||
reinterpret_cast<void *>(this->texture),
|
||||
overlay_content_top_left,
|
||||
bottom_right);
|
||||
|
||||
if (this->draws_window) {
|
||||
// draw an invisible button so that it swallows mouse input
|
||||
// this is needed to prevent the window from being dragged around
|
||||
// (alternatively io.ConfigWindowsMoveFromTitleBarOnly can be set but that is global)
|
||||
ImGui::InvisibleButton(
|
||||
(this->title + "__InvisibleButton").c_str(),
|
||||
overlay_content_size,
|
||||
ImGuiButtonFlags_None);
|
||||
}
|
||||
|
||||
#if OVERLAYDBG
|
||||
log_info("sub::overlay", "{} / {} = {}", overlay_content_size.x, overlay_content_size.y, overlay_content_size.x / overlay_content_size.y);
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
56
overlay/windows/generic_sub.h
Normal file
56
overlay/windows/generic_sub.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef SPICETOOLS_OVERLAY_WINDOWS_GENERIC_SUB_H
|
||||
#define SPICETOOLS_OVERLAY_WINDOWS_GENERIC_SUB_H
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <windows.h>
|
||||
#include <d3d9.h>
|
||||
|
||||
#include "overlay/window.h"
|
||||
|
||||
//=================================================================================================
|
||||
// Global variable to track the coords of Subscreen.
|
||||
// Values are in original coord-space (not scaled to Windowed mode size).
|
||||
//
|
||||
// For use with touch coords transformation.
|
||||
//=================================================================================================
|
||||
|
||||
extern int GENERIC_SUB_WINDOW_X;
|
||||
extern int GENERIC_SUB_WINDOW_Y;
|
||||
extern int GENERIC_SUB_WINDOW_WIDTH;
|
||||
extern int GENERIC_SUB_WINDOW_HEIGHT;
|
||||
extern bool GENERIC_SUB_WINDOW_FULLSIZE;
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class GenericSubScreen : public Window {
|
||||
public:
|
||||
GenericSubScreen(SpiceOverlay *overlay);
|
||||
|
||||
void build_content() override;
|
||||
|
||||
protected:
|
||||
virtual void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out);
|
||||
ImVec2 overlay_content_top_left;
|
||||
ImVec2 overlay_content_size;
|
||||
std::optional<std::string> disabled_message = std::nullopt;
|
||||
|
||||
private:
|
||||
static void keep_16_by_9(ImGuiSizeCallbackData* data) {
|
||||
data->DesiredSize.y = (data->DesiredSize.x * 9.f / 16.f) + ImGui::GetFrameHeight();
|
||||
}
|
||||
|
||||
bool build_texture(IDirect3DSurface9 *surface, UINT width, UINT height);
|
||||
void draw_texture();
|
||||
|
||||
std::optional<std::string> status_message = std::nullopt;
|
||||
|
||||
IDirect3DDevice9 *device = nullptr;
|
||||
IDirect3DTexture9 *texture = nullptr;
|
||||
|
||||
UINT texture_width;
|
||||
UINT texture_height;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // SPICETOOLS_OVERLAY_WINDOWS_GENERIC_SUB_H
|
||||
148
overlay/windows/iidx_seg.cpp
Normal file
148
overlay/windows/iidx_seg.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
#include <map>
|
||||
|
||||
#include "iidx_seg.h"
|
||||
#include "games/io.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
uint32_t IIDX_SEGMENT_FONT_SIZE = 64;
|
||||
std::optional<uint32_t> IIDX_SEGMENT_FONT_COLOR = std::nullopt;
|
||||
std::string IIDX_SEGMENT_LOCATION = "bottom";
|
||||
|
||||
static const size_t TICKER_SIZE = 9;
|
||||
static const ImVec4 DARK_GRAY(0.1f, 0.1f, 0.1f, 1.f);
|
||||
static const ImVec4 RED(1.f, 0.f, 0.f, 1.f);
|
||||
static const int PADDING_Y = 8;
|
||||
static const int PADDING_X = 4;
|
||||
|
||||
static const std::map<char, std::pair<char, char>> CHARMAP = {\
|
||||
// period - add a space afterwards (game sends 'm' for period)
|
||||
{'m', {'.', ' '}},
|
||||
// exclamation mark (use ./ to make it look like one)
|
||||
{'!', {'.', '/'}},
|
||||
// font doesn't have tilde so using a dash instead
|
||||
{'~', {'-', '\0'}},
|
||||
};
|
||||
|
||||
IIDXSegmentDisplay::IIDXSegmentDisplay(SpiceOverlay *overlay) : Window(overlay) {
|
||||
if (!DSEG_FONT) {
|
||||
log_fatal("iidx_seg", "DSEG_FONT is null");
|
||||
}
|
||||
|
||||
this->title = "IIDX LED Segment Display";
|
||||
this->toggle_button = games::OverlayButtons::ToggleSubScreen;
|
||||
this->remove_window_padding = true;
|
||||
this->size_max = ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y);
|
||||
this->flags = ImGuiWindowFlags_NoTitleBar
|
||||
| ImGuiWindowFlags_NoScrollbar
|
||||
| ImGuiWindowFlags_NoResize
|
||||
| ImGuiWindowFlags_NoNavFocus
|
||||
| ImGuiWindowFlags_NoNavInputs
|
||||
| ImGuiWindowFlags_NoDocking;
|
||||
|
||||
if (IIDX_SEGMENT_FONT_COLOR.has_value()) {
|
||||
const auto rgb = IIDX_SEGMENT_FONT_COLOR.value();
|
||||
const auto rgba = IM_COL32_A_MASK | rgb;
|
||||
this->color = ImGui::ColorConvertU32ToFloat4(rgba);
|
||||
} else {
|
||||
this->color = RED;
|
||||
}
|
||||
}
|
||||
|
||||
void IIDXSegmentDisplay::calculate_initial_window() {
|
||||
// ImGui::CalcTextSize doesn't seem to work here, so manually calculate
|
||||
ImGui::PushFont(DSEG_FONT);
|
||||
this->init_size = ImGui::CalcTextSize("~.~.~.~.~.~.~.~.~.");
|
||||
this->init_size.x += PADDING_X * 2;
|
||||
this->init_size.y += PADDING_Y * 2;
|
||||
ImGui::PopFont();
|
||||
|
||||
// initial horizontal position
|
||||
if (IIDX_SEGMENT_LOCATION.find("left") != std::string::npos) {
|
||||
this->init_pos.x = 0;
|
||||
} else if (IIDX_SEGMENT_LOCATION.find("right") != std::string::npos) {
|
||||
this->init_pos.x = ImGui::GetIO().DisplaySize.x - this->init_size.x;
|
||||
} else {
|
||||
// center
|
||||
this->init_pos.x = ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2;
|
||||
}
|
||||
|
||||
// initial vertical position
|
||||
if (IIDX_SEGMENT_LOCATION.rfind("top", 0) == 0) {
|
||||
this->init_pos.y = 0;
|
||||
} else {
|
||||
// bottom
|
||||
this->init_pos.y = ImGui::GetIO().DisplaySize.y - this->init_size.y;
|
||||
}
|
||||
}
|
||||
|
||||
void IIDXSegmentDisplay::build_content() {
|
||||
char input_ticker[TICKER_SIZE];
|
||||
|
||||
// get ticker content from game
|
||||
games::iidx::IIDX_LED_TICKER_LOCK.lock();
|
||||
memcpy(input_ticker, games::iidx::IIDXIO_LED_TICKER, TICKER_SIZE);
|
||||
games::iidx::IIDX_LED_TICKER_LOCK.unlock();
|
||||
|
||||
// since every input character can result in up to two characters,
|
||||
// need double size for output
|
||||
const size_t TICKER_OUTPUT_SIZE = TICKER_SIZE * 2 + 1;
|
||||
char output_ticker[TICKER_OUTPUT_SIZE];
|
||||
int output_ticker_index = 0;
|
||||
|
||||
// look at each input char and convert into output char(s)
|
||||
for (const auto c : input_ticker) {
|
||||
// see if there is an applicable rule for this input character
|
||||
if (0 == CHARMAP.count(c)) {
|
||||
// if not, copy the first character and keep going
|
||||
output_ticker[output_ticker_index] = c;
|
||||
output_ticker_index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// there is a replacement rule for this input character
|
||||
auto replacements = CHARMAP.at(c);
|
||||
|
||||
// replace the first character...
|
||||
output_ticker[output_ticker_index] = replacements.first;
|
||||
output_ticker_index += 1;
|
||||
|
||||
// and optionally add the second character
|
||||
if (replacements.second != '\0') {
|
||||
output_ticker[output_ticker_index] = replacements.second;
|
||||
output_ticker_index += 1;
|
||||
}
|
||||
}
|
||||
if ((int)TICKER_OUTPUT_SIZE <= output_ticker_index) {
|
||||
log_fatal("iidx_seg", "{} is beyond array bounds", output_ticker_index);
|
||||
}
|
||||
|
||||
// terminating null...
|
||||
output_ticker[output_ticker_index] = '\0';
|
||||
|
||||
// finally, draw UI elements
|
||||
draw_ticker(output_ticker);
|
||||
}
|
||||
|
||||
void IIDXSegmentDisplay::draw_ticker(char *ticker_string) {
|
||||
ImGui::PushFont(DSEG_FONT);
|
||||
|
||||
const auto pos = ImVec2(PADDING_X, PADDING_Y);
|
||||
|
||||
// to imitate LED "off"
|
||||
ImGui::SetCursorPos(pos);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, DARK_GRAY);
|
||||
ImGui::TextUnformatted("~.~.~.~.~.~.~.~.~.");
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
// ... then draw the LED "on" above it
|
||||
ImGui::SetCursorPos(pos);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, this->color);
|
||||
ImGui::TextUnformatted(ticker_string);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::PopFont();
|
||||
}
|
||||
}
|
||||
21
overlay/windows/iidx_seg.h
Normal file
21
overlay/windows/iidx_seg.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include "overlay/window.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
extern uint32_t IIDX_SEGMENT_FONT_SIZE;
|
||||
extern std::optional<uint32_t> IIDX_SEGMENT_FONT_COLOR;
|
||||
extern std::string IIDX_SEGMENT_LOCATION;
|
||||
|
||||
class IIDXSegmentDisplay : public Window {
|
||||
public:
|
||||
IIDXSegmentDisplay(SpiceOverlay *overlay);
|
||||
void calculate_initial_window() override;
|
||||
void build_content() override;
|
||||
private:
|
||||
ImVec4 color;
|
||||
void draw_ticker(char *ticker_string);
|
||||
};
|
||||
}
|
||||
61
overlay/windows/iidx_sub.cpp
Normal file
61
overlay/windows/iidx_sub.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#undef CINTERFACE
|
||||
|
||||
#include "iidx_sub.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "touch/touch.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
IIDXSubScreen::IIDXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
|
||||
this->title = "IIDX Sub Screen";
|
||||
|
||||
if (GRAPHICS_IIDX_WSUB) {
|
||||
this->disabled_message =
|
||||
"Close this overlay and use the second window.\n"
|
||||
"Or, turn on -iidxnosub to use the overlay instead.";
|
||||
this->draws_window = false;
|
||||
}
|
||||
|
||||
float size = 0.5f;
|
||||
if (games::iidx::SUBSCREEN_OVERLAY_SIZE.has_value()) {
|
||||
if (games::iidx::SUBSCREEN_OVERLAY_SIZE.value() == "large") {
|
||||
size = 0.8f;
|
||||
} else if (games::iidx::SUBSCREEN_OVERLAY_SIZE.value() == "small") {
|
||||
size = 0.3f;
|
||||
} else if (games::iidx::SUBSCREEN_OVERLAY_SIZE.value() == "fullscreen") {
|
||||
GENERIC_SUB_WINDOW_FULLSIZE = true;
|
||||
this->draws_window = false;
|
||||
}
|
||||
}
|
||||
this->init_size = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x * size,
|
||||
(ImGui::GetIO().DisplaySize.x * size * 9 / 16) + ImGui::GetFrameHeight());
|
||||
|
||||
this->size_max = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x - ImGui::GetFrameHeight() * 2,
|
||||
ImGui::GetIO().DisplaySize.y - ImGui::GetFrameHeight() * 2);
|
||||
|
||||
// middle / bottom
|
||||
this->init_pos = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
|
||||
ImGui::GetIO().DisplaySize.y - this->init_size.y - (ImGui::GetFrameHeight() / 2));
|
||||
}
|
||||
|
||||
void IIDXSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {
|
||||
if (!this->get_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
// Touch needs to be registered on global coords
|
||||
*x_out = SPICETOUCH_TOUCH_X + xy_in.x * SPICETOUCH_TOUCH_WIDTH;
|
||||
*y_out = SPICETOUCH_TOUCH_Y + xy_in.y * SPICETOUCH_TOUCH_HEIGHT;
|
||||
} else {
|
||||
// Fullscreen mode, scale to game coords
|
||||
*x_out = xy_in.x * ImGui::GetIO().DisplaySize.x;
|
||||
*y_out = xy_in.y * ImGui::GetIO().DisplaySize.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
overlay/windows/iidx_sub.h
Normal file
18
overlay/windows/iidx_sub.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef SPICETOOLS_OVERLAY_WINDOWS_IIDX_SUB_H
|
||||
#define SPICETOOLS_OVERLAY_WINDOWS_IIDX_SUB_H
|
||||
|
||||
#include "overlay/window.h"
|
||||
#include "overlay/windows/generic_sub.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class IIDXSubScreen : public GenericSubScreen {
|
||||
public:
|
||||
IIDXSubScreen(SpiceOverlay *overlay);
|
||||
|
||||
protected:
|
||||
void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) override;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // SPICETOOLS_OVERLAY_WINDOWS_IIDX_SUB_H
|
||||
121
overlay/windows/iopanel.cpp
Normal file
121
overlay/windows/iopanel.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#include "iopanel.h"
|
||||
#include "cfg/api.h"
|
||||
#include "launcher/launcher.h"
|
||||
#include "games/io.h"
|
||||
#include "misc/eamuse.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
IOPanel::IOPanel(SpiceOverlay *overlay) : Window(overlay) {
|
||||
this->title = "Operator";
|
||||
this->flags = ImGuiWindowFlags_AlwaysAutoResize;
|
||||
|
||||
this->init_size = ImVec2(
|
||||
80,
|
||||
ImGui::GetFrameHeight() +
|
||||
ImGui::GetStyle().WindowPadding.y * 2 +
|
||||
ImGui::GetFrameHeightWithSpacing() * 3 +
|
||||
ImGui::GetFrameHeight()); // title + windows padding + 4 buttons
|
||||
|
||||
this->init_pos =ImVec2(10, ImGui::GetIO().DisplaySize.y - this->init_size.y - 10);
|
||||
this->toggle_button = games::OverlayButtons::ToggleIOPanel;
|
||||
this->find_buttons();
|
||||
}
|
||||
|
||||
void IOPanel::find_buttons() {
|
||||
const auto buttons = games::get_buttons(eamuse_get_game());
|
||||
for (auto &button : *buttons) {
|
||||
// since the array indices are different for each game, use string match instead
|
||||
if (button.getName() == "Service") {
|
||||
this->service_button = &button;
|
||||
} else if (button.getName() == "Test") {
|
||||
this->test_button = &button;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float IOPanel::get_suggested_height() {
|
||||
// height of 4 buttons stacked vertically
|
||||
// (coin, unlock, service, test, plus spacing between them)
|
||||
return ImGui::GetFrameHeightWithSpacing() * 3 + ImGui::GetFrameHeight();
|
||||
}
|
||||
|
||||
void IOPanel::build_content() {
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.2f, 0.1f, 0.1f, 1.f));
|
||||
ImGui::PushID(this);
|
||||
|
||||
ImGui::BeginGroup();
|
||||
this->build_operator_menu();
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::SameLine();
|
||||
this->build_io_panel();
|
||||
|
||||
ImGui::PopID();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
void IOPanel::build_io_panel() {}
|
||||
|
||||
void IOPanel::build_operator_menu() {
|
||||
const ImVec2 wide(60, 0);
|
||||
const float spacing_x = ImGui::GetStyle().ItemSpacing.y;
|
||||
const ImVec2 tall(
|
||||
ImGui::GetFrameHeight(),
|
||||
ImGui::GetFrameHeightWithSpacing() + ImGui::GetFrameHeight());
|
||||
|
||||
if (ImGui::Button("COIN", ImVec2(wide.x + spacing_x + tall.x, wide.y))) {
|
||||
eamuse_coin_add();
|
||||
}
|
||||
|
||||
ImGui::Checkbox("unlock", &operator_unlocked);
|
||||
ImGui::BeginDisabled(!operator_unlocked);
|
||||
|
||||
this->build_button("SERVICE", wide, this->service_button);
|
||||
this->build_button("TEST", wide, this->test_button);
|
||||
|
||||
// service + test button (for test menu navigation)
|
||||
ImGui::SameLine(0, spacing_x);
|
||||
ImGui::BeginGroup();
|
||||
{
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetFrameHeightWithSpacing());
|
||||
this->build_button("+", tall, this->test_button, this->service_button);
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
void IOPanel::build_button(
|
||||
const char *label, const ImVec2 &size, Button *button, Button *button_alt, Light *light) {
|
||||
|
||||
ImGui::BeginDisabled(button == nullptr);
|
||||
|
||||
int pushed = 0;
|
||||
if (light && 0.5f < GameAPI::Lights::readLight(RI_MGR, *light)) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.72f, 0.15f, 0.00f, 0.9f));
|
||||
pushed += 1;
|
||||
}
|
||||
|
||||
ImGui::Button(label, size);
|
||||
|
||||
if (ImGui::IsItemActivated()) {
|
||||
button->override_state = GameAPI::Buttons::BUTTON_PRESSED;
|
||||
button->override_velocity = 1.f;
|
||||
button->override_enabled = true;
|
||||
if (button_alt) {
|
||||
button_alt->override_state = GameAPI::Buttons::BUTTON_PRESSED;
|
||||
button_alt->override_velocity = 1.f;
|
||||
button_alt->override_enabled = true;
|
||||
}
|
||||
} else if (ImGui::IsItemDeactivated()) {
|
||||
button->override_enabled = false;
|
||||
if (button_alt) {
|
||||
button_alt->override_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor(pushed);
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
}
|
||||
32
overlay/windows/iopanel.h
Normal file
32
overlay/windows/iopanel.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "overlay/window.h"
|
||||
#include "cfg/button.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class IOPanel : public Window {
|
||||
public:
|
||||
IOPanel(SpiceOverlay *overlay);
|
||||
void build_content() override;
|
||||
|
||||
protected:
|
||||
virtual void build_io_panel();
|
||||
void build_button(
|
||||
const char *label,
|
||||
const ImVec2 &size,
|
||||
Button *button,
|
||||
Button *button_alt = nullptr,
|
||||
Light *light = nullptr);
|
||||
|
||||
float get_suggested_height();
|
||||
|
||||
private:
|
||||
void find_buttons();
|
||||
void build_operator_menu();
|
||||
|
||||
bool operator_unlocked = false;
|
||||
Button *test_button = nullptr;
|
||||
Button *service_button = nullptr;
|
||||
};
|
||||
}
|
||||
95
overlay/windows/iopanel_ddr.cpp
Normal file
95
overlay/windows/iopanel_ddr.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#include "iopanel_ddr.h"
|
||||
#include "games/io.h"
|
||||
#include "games/ddr/io.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
DDRIOPanel::DDRIOPanel(SpiceOverlay *overlay) : IOPanel(overlay) {
|
||||
this->title = "DDR IO Panel";
|
||||
find_ddr_buttons();
|
||||
}
|
||||
|
||||
void DDRIOPanel::find_ddr_buttons() {
|
||||
const auto buttons = games::get_buttons(eamuse_get_game());
|
||||
const auto lights = games::get_lights(eamuse_get_game());
|
||||
|
||||
// SD cabs don't have lights for these buttons, so just use the HD ones
|
||||
|
||||
this->start[0] = &(*buttons)[games::ddr::Buttons::P1_START];
|
||||
this->up[0] = &(*buttons)[games::ddr::Buttons::P1_MENU_UP];
|
||||
this->down[0] = &(*buttons)[games::ddr::Buttons::P1_MENU_DOWN];
|
||||
this->left[0] = &(*buttons)[games::ddr::Buttons::P1_MENU_LEFT];
|
||||
this->right[0] = &(*buttons)[games::ddr::Buttons::P1_MENU_RIGHT];
|
||||
this->start_light[0] = &(*lights)[games::ddr::Lights::HD_P1_START];
|
||||
this->updown_light[0] = &(*lights)[games::ddr::Lights::HD_P1_UP_DOWN];
|
||||
this->leftright_light[0] = &(*lights)[games::ddr::Lights::HD_P1_LEFT_RIGHT];
|
||||
|
||||
this->start[1] = &(*buttons)[games::ddr::Buttons::P2_START];
|
||||
this->up[1] = &(*buttons)[games::ddr::Buttons::P2_MENU_UP];
|
||||
this->down[1] = &(*buttons)[games::ddr::Buttons::P2_MENU_DOWN];
|
||||
this->left[1] = &(*buttons)[games::ddr::Buttons::P2_MENU_LEFT];
|
||||
this->right[1] = &(*buttons)[games::ddr::Buttons::P2_MENU_RIGHT];
|
||||
this->start_light[1] = &(*lights)[games::ddr::Lights::HD_P2_START];
|
||||
this->updown_light[1] = &(*lights)[games::ddr::Lights::HD_P2_UP_DOWN];
|
||||
this->leftright_light[1] = &(*lights)[games::ddr::Lights::HD_P2_LEFT_RIGHT];
|
||||
}
|
||||
|
||||
void DDRIOPanel::build_io_panel() {
|
||||
ImGui::Dummy(ImVec2(12, 0));
|
||||
|
||||
ImGui::SameLine();
|
||||
this->draw_buttons(0);
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::Dummy(ImVec2(12, 0));
|
||||
|
||||
ImGui::SameLine();
|
||||
this->draw_buttons(1);
|
||||
}
|
||||
|
||||
void DDRIOPanel::draw_buttons(const int p) {
|
||||
// 2x2
|
||||
const ImVec2 start_button_size(
|
||||
ImGui::GetFrameHeightWithSpacing() + ImGui::GetFrameHeight(),
|
||||
ImGui::GetFrameHeightWithSpacing() + ImGui::GetFrameHeight()
|
||||
);
|
||||
// 2x1
|
||||
const ImVec2 updown_size(start_button_size.x, ImGui::GetFrameHeight());
|
||||
// 1x2
|
||||
const ImVec2 leftright_size(ImGui::GetFrameHeight(), start_button_size.y);
|
||||
|
||||
ImGui::BeginGroup();
|
||||
{
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetFrameHeightWithSpacing());
|
||||
this->build_button("<", leftright_size, this->left[p], nullptr, this->leftright_light[p]);
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemSpacing.y);
|
||||
|
||||
ImGui::BeginGroup();
|
||||
{
|
||||
this->build_button("^", updown_size, this->up[p], nullptr, this->updown_light[p]);
|
||||
const char *label;
|
||||
if (p == 0) {
|
||||
label = " 1 P\nStart";
|
||||
} else {
|
||||
label = " 2 P\nStart";
|
||||
}
|
||||
this->build_button(label, start_button_size, this->start[p], nullptr, this->start_light[p]);
|
||||
this->build_button("v", updown_size, this->down[p], nullptr, this->updown_light[p]);
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemSpacing.y);
|
||||
|
||||
ImGui::BeginGroup();
|
||||
{
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetFrameHeightWithSpacing());
|
||||
this->build_button(">", leftright_size, this->right[p], nullptr, this->leftright_light[p]);
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
}
|
||||
29
overlay/windows/iopanel_ddr.h
Normal file
29
overlay/windows/iopanel_ddr.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "overlay/window.h"
|
||||
#include "overlay/windows/iopanel.h"
|
||||
#include "cfg/button.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class DDRIOPanel : public IOPanel {
|
||||
public:
|
||||
DDRIOPanel(SpiceOverlay *overlay);
|
||||
|
||||
protected:
|
||||
void build_io_panel() override;
|
||||
|
||||
private:
|
||||
void find_ddr_buttons();
|
||||
void draw_buttons(const int player);
|
||||
|
||||
Button *start[2];
|
||||
Button *up[2];
|
||||
Button *down[2];
|
||||
Button *left[2];
|
||||
Button *right[2];
|
||||
Light *start_light[2];
|
||||
Light *updown_light[2];
|
||||
Light *leftright_light[2];
|
||||
};
|
||||
}
|
||||
174
overlay/windows/iopanel_gfdm.cpp
Normal file
174
overlay/windows/iopanel_gfdm.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#include "iopanel_gfdm.h"
|
||||
#include "avs/game.h"
|
||||
#include "games/io.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/gitadora/io.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
GitadoraIOPanel::GitadoraIOPanel(SpiceOverlay *overlay) : IOPanel(overlay) {
|
||||
this->title = "GITADORA IO Panel";
|
||||
|
||||
// by default, make a safer assumption that there are two players
|
||||
this->two_players = true;
|
||||
// by default, enable the extra input only available on DX cabs...
|
||||
this->has_guitar_knobs = true;
|
||||
|
||||
// drummania can only have one player, no guitar knobs
|
||||
if (avs::game::is_model({ "J32", "K32", "L32" }) ||
|
||||
(avs::game::is_model("M32") && avs::game::SPEC[0] == 'B')) {
|
||||
this->two_players = false;
|
||||
this->has_guitar_knobs = false;
|
||||
}
|
||||
|
||||
// and cab type 3 (white cab) only has one guitar
|
||||
if (games::gitadora::CAB_TYPE.has_value() && games::gitadora::CAB_TYPE == 3) {
|
||||
this->two_players = false;
|
||||
}
|
||||
|
||||
// disable guitar knobs on known non-DX cabs
|
||||
if (games::gitadora::CAB_TYPE.has_value() &&
|
||||
(games::gitadora::CAB_TYPE == 2 || games::gitadora::CAB_TYPE == 3)) {
|
||||
this->has_guitar_knobs = false;
|
||||
}
|
||||
|
||||
find_gfdm_buttons();
|
||||
}
|
||||
|
||||
void GitadoraIOPanel::find_gfdm_buttons() {
|
||||
const auto buttons = games::get_buttons(eamuse_get_game());
|
||||
const auto lights = games::get_lights(eamuse_get_game());
|
||||
|
||||
// device emulation treats drum controls to be the same as guitar 1p
|
||||
|
||||
this->start[0] = &(*buttons)[games::gitadora::Buttons::GuitarP1Start];
|
||||
this->help[0] = &(*buttons)[games::gitadora::Buttons::GuitarP1Help];
|
||||
this->up[0] = &(*buttons)[games::gitadora::Buttons::GuitarP1Up];
|
||||
this->down[0] = &(*buttons)[games::gitadora::Buttons::GuitarP1Down];
|
||||
this->left[0] = &(*buttons)[games::gitadora::Buttons::GuitarP1Left];
|
||||
this->right[0] = &(*buttons)[games::gitadora::Buttons::GuitarP1Right];
|
||||
|
||||
this->start_light[0] = &(*lights)[games::gitadora::Lights::P1MenuStart];
|
||||
this->help_light[0] = &(*lights)[games::gitadora::Lights::P1MenuHelp];
|
||||
this->updown_light[0] = &(*lights)[games::gitadora::Lights::P1MenuUpDown];
|
||||
this->leftright_light[0] = &(*lights)[games::gitadora::Lights::P1MenuLeftRight];
|
||||
|
||||
this->start[1] = &(*buttons)[games::gitadora::Buttons::GuitarP2Start];
|
||||
this->help[1] = &(*buttons)[games::gitadora::Buttons::GuitarP2Help];
|
||||
this->up[1] = &(*buttons)[games::gitadora::Buttons::GuitarP2Up];
|
||||
this->down[1] = &(*buttons)[games::gitadora::Buttons::GuitarP2Down];
|
||||
this->left[1] = &(*buttons)[games::gitadora::Buttons::GuitarP2Left];
|
||||
this->right[1] = &(*buttons)[games::gitadora::Buttons::GuitarP2Right];
|
||||
|
||||
this->start_light[1] = &(*lights)[games::gitadora::Lights::P2MenuStart];
|
||||
this->help_light[1] = &(*lights)[games::gitadora::Lights::P2MenuHelp];
|
||||
this->updown_light[1] = &(*lights)[games::gitadora::Lights::P2MenuUpDown];
|
||||
this->leftright_light[1] = &(*lights)[games::gitadora::Lights::P2MenuLeftRight];
|
||||
}
|
||||
|
||||
void GitadoraIOPanel::build_io_panel() {
|
||||
ImGui::Dummy(ImVec2(12, 0));
|
||||
|
||||
ImGui::SameLine();
|
||||
this->draw_buttons(0);
|
||||
if (this->has_guitar_knobs) {
|
||||
ImGui::SameLine();
|
||||
this->draw_sliders(0);
|
||||
}
|
||||
|
||||
// draw p2 only if guitar freaks
|
||||
if (this->two_players) {
|
||||
ImGui::SameLine();
|
||||
ImGui::Dummy(ImVec2(12, 0));
|
||||
ImGui::SameLine();
|
||||
this->draw_buttons(1);
|
||||
if (this->has_guitar_knobs) {
|
||||
ImGui::SameLine();
|
||||
this->draw_sliders(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GitadoraIOPanel::draw_buttons(const int p) {
|
||||
// 2x2
|
||||
const ImVec2 start_button_size(
|
||||
ImGui::GetFrameHeightWithSpacing() + ImGui::GetFrameHeight(),
|
||||
ImGui::GetFrameHeightWithSpacing() + ImGui::GetFrameHeight()
|
||||
);
|
||||
// 2x1
|
||||
const ImVec2 updown_size(start_button_size.x, ImGui::GetFrameHeight());
|
||||
// 1x2
|
||||
const ImVec2 leftright_size(ImGui::GetFrameHeight(), start_button_size.y);
|
||||
// 1x1
|
||||
const ImVec2 tiny_size(ImGui::GetFrameHeight(), ImGui::GetFrameHeight());
|
||||
|
||||
ImGui::BeginGroup();
|
||||
{
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetFrameHeightWithSpacing());
|
||||
this->build_button("<", leftright_size, this->left[p], nullptr, this->leftright_light[p]);
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemSpacing.y);
|
||||
|
||||
ImGui::BeginGroup();
|
||||
{
|
||||
this->build_button("^", updown_size, this->up[p], nullptr, this->updown_light[p]);
|
||||
const char *label;
|
||||
if (this->two_players) {
|
||||
if (p == 0) {
|
||||
label = " 1 P\nStart";
|
||||
} else {
|
||||
label = " 2 P\nStart";
|
||||
}
|
||||
} else {
|
||||
label = "Start";
|
||||
}
|
||||
this->build_button(label, start_button_size, this->start[p], nullptr, this->start_light[p]);
|
||||
this->build_button("v", updown_size, this->down[p], nullptr, this->updown_light[p]);
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemSpacing.y);
|
||||
|
||||
ImGui::BeginGroup();
|
||||
{
|
||||
this->build_button("?", tiny_size, this->help[p], nullptr, this->help_light[p]);
|
||||
this->build_button(">", leftright_size, this->right[p], nullptr, this->leftright_light[p]);
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
|
||||
void GitadoraIOPanel::draw_sliders(const int p) {
|
||||
const auto index =
|
||||
(p == 0) ?
|
||||
games::gitadora::Analogs::GuitarP1Knob :
|
||||
games::gitadora::Analogs::GuitarP2Knob;
|
||||
|
||||
const ImVec2 slider_size(32, get_suggested_height());
|
||||
|
||||
// get analog
|
||||
const auto analogs = games::get_analogs(eamuse_get_game());
|
||||
auto &analog = (*analogs)[index];
|
||||
|
||||
// vertical slider
|
||||
auto new_state = analog.override_enabled ? analog.override_state
|
||||
: GameAPI::Analogs::getState(RI_MGR, analog);
|
||||
|
||||
ImGui::PushID(index);
|
||||
ImGui::VSliderFloat(
|
||||
"##v",
|
||||
slider_size,
|
||||
&new_state,
|
||||
0.f, 1.f,
|
||||
"Knob",
|
||||
ImGuiSliderFlags_AlwaysClamp);
|
||||
ImGui::PopID();
|
||||
if (new_state != analog.override_state) {
|
||||
analog.override_state = new_state;
|
||||
analog.override_enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
overlay/windows/iopanel_gfdm.h
Normal file
36
overlay/windows/iopanel_gfdm.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "overlay/window.h"
|
||||
#include "overlay/windows/iopanel.h"
|
||||
#include "cfg/button.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class GitadoraIOPanel : public IOPanel {
|
||||
public:
|
||||
GitadoraIOPanel(SpiceOverlay *overlay);
|
||||
|
||||
protected:
|
||||
void build_io_panel() override;
|
||||
|
||||
private:
|
||||
void find_gfdm_buttons();
|
||||
void draw_buttons(const int player);
|
||||
void draw_sliders(const int player);
|
||||
|
||||
bool two_players;
|
||||
bool has_guitar_knobs;
|
||||
|
||||
Button *start[2];
|
||||
Button *help[2];
|
||||
Button *up[2];
|
||||
Button *down[2];
|
||||
Button *left[2];
|
||||
Button *right[2];
|
||||
|
||||
Light *start_light[2];
|
||||
Light *help_light[2];
|
||||
Light *updown_light[2];
|
||||
Light *leftright_light[2];
|
||||
};
|
||||
}
|
||||
168
overlay/windows/iopanel_iidx.cpp
Normal file
168
overlay/windows/iopanel_iidx.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#include <map>
|
||||
#include "iopanel_iidx.h"
|
||||
#include "games/io.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/iidx/io.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
IIDXIOPanel::IIDXIOPanel(SpiceOverlay *overlay) : IOPanel(overlay) {
|
||||
this->title = "IIDX IO Panel";
|
||||
find_iidx_buttons();
|
||||
}
|
||||
|
||||
void IIDXIOPanel::find_iidx_buttons() {
|
||||
const auto buttons = games::get_buttons(eamuse_get_game());
|
||||
const auto lights = games::get_lights(eamuse_get_game());
|
||||
|
||||
this->start_1p = &(*buttons)[games::iidx::Buttons::P1_Start];
|
||||
this->start_1p_light = &(*lights)[games::iidx::Lights::P1_Start];
|
||||
|
||||
this->start_2p = &(*buttons)[games::iidx::Buttons::P2_Start];
|
||||
this->start_2p_light = &(*lights)[games::iidx::Lights::P2_Start];
|
||||
|
||||
this->vefx = &(*buttons)[games::iidx::Buttons::VEFX];
|
||||
this->vefx_light = &(*lights)[games::iidx::Lights::VEFX];
|
||||
|
||||
this->effect_on = &(*buttons)[games::iidx::Buttons::Effect];
|
||||
this->effect_on_light = &(*lights)[games::iidx::Lights::Effect];
|
||||
}
|
||||
|
||||
void IIDXIOPanel::build_io_panel() {
|
||||
ImGui::Dummy(ImVec2(12, 0));
|
||||
|
||||
ImGui::SameLine();
|
||||
this->draw_buttons();
|
||||
|
||||
if (!games::iidx::TDJ_MODE) {
|
||||
ImGui::SameLine();
|
||||
ImGui::Dummy(ImVec2(12, 0));
|
||||
|
||||
ImGui::SameLine();
|
||||
this->draw_sliders();
|
||||
}
|
||||
}
|
||||
|
||||
void IIDXIOPanel::draw_buttons() {
|
||||
const auto big_button_size = ImVec2(get_suggested_height(), get_suggested_height());
|
||||
|
||||
// width = same as above,
|
||||
// height = 2 rows of buttons
|
||||
const auto mid_button_size = ImVec2(
|
||||
big_button_size.x,
|
||||
ImGui::GetFrameHeight() * 2 + ImGui::GetStyle().ItemSpacing.y
|
||||
);
|
||||
|
||||
// width = half of above,
|
||||
// height = 4 rows of buttons
|
||||
const auto tall_button_size = ImVec2(
|
||||
big_button_size.x / 2,
|
||||
big_button_size.y
|
||||
);
|
||||
|
||||
this->build_button(
|
||||
" 1 P\n"
|
||||
"Start",
|
||||
big_button_size,
|
||||
this->start_1p, nullptr,
|
||||
this->start_1p_light);
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginGroup();
|
||||
this->build_button(
|
||||
"EFFECT",
|
||||
mid_button_size,
|
||||
this->effect_on, nullptr,
|
||||
this->effect_on_light);
|
||||
this->build_button(
|
||||
"VEFX",
|
||||
mid_button_size,
|
||||
this->vefx, nullptr,
|
||||
this->vefx_light);
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::SameLine();
|
||||
this->build_button(
|
||||
"EFF\n"
|
||||
" +\n"
|
||||
"VFX",
|
||||
tall_button_size,
|
||||
this->effect_on,
|
||||
this->vefx);
|
||||
|
||||
ImGui::SameLine();
|
||||
this->build_button(
|
||||
" 2 P\n"
|
||||
"Start",
|
||||
big_button_size,
|
||||
this->start_2p, nullptr,
|
||||
this->start_2p_light);
|
||||
}
|
||||
|
||||
void IIDXIOPanel::draw_sliders() {
|
||||
// effector analog entries
|
||||
static const std::map<size_t, const char *> ANALOG_ENTRIES {
|
||||
{ games::iidx::Analogs::VEFX, "VEFX" },
|
||||
{ games::iidx::Analogs::LowEQ, "LoEQ" },
|
||||
{ games::iidx::Analogs::HiEQ, "HiEQ" },
|
||||
{ games::iidx::Analogs::Filter, "Fltr" },
|
||||
{ games::iidx::Analogs::PlayVolume, "Vol." },
|
||||
};
|
||||
|
||||
const ImVec2 slider_size(32, get_suggested_height());
|
||||
const auto reset_button_size = ImVec2(16, get_suggested_height());
|
||||
|
||||
// iterate analogs
|
||||
bool overridden = false;
|
||||
const 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);
|
||||
|
||||
// vertical slider
|
||||
auto new_state = analog.override_enabled ? analog.override_state
|
||||
: GameAPI::Analogs::getState(RI_MGR, analog);
|
||||
if (0 < index) {
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
ImGui::VSliderFloat(
|
||||
"##v",
|
||||
slider_size,
|
||||
&new_state,
|
||||
0.f, 1.f,
|
||||
name,
|
||||
ImGuiSliderFlags_AlwaysClamp);
|
||||
|
||||
if (new_state != analog.override_state) {
|
||||
analog.override_state = new_state;
|
||||
analog.override_enabled = true;
|
||||
}
|
||||
|
||||
// pop id and style
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
// reset button
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("r\ne\ns\ne\nt", reset_button_size)) {
|
||||
for (auto &[index, name] : ANALOG_ENTRIES) {
|
||||
if (index < analogs->size()) {
|
||||
(*analogs)[index].override_enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
overlay/windows/iopanel_iidx.h
Normal file
33
overlay/windows/iopanel_iidx.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "overlay/window.h"
|
||||
#include "overlay/windows/iopanel.h"
|
||||
#include "cfg/button.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class IIDXIOPanel : public IOPanel {
|
||||
public:
|
||||
IIDXIOPanel(SpiceOverlay *overlay);
|
||||
|
||||
protected:
|
||||
void build_io_panel() override;
|
||||
|
||||
private:
|
||||
void find_iidx_buttons();
|
||||
void draw_buttons();
|
||||
void draw_sliders();
|
||||
|
||||
Button *start_1p = nullptr;
|
||||
Light *start_1p_light = nullptr;
|
||||
|
||||
Button *start_2p = nullptr;
|
||||
Light *start_2p_light = nullptr;
|
||||
|
||||
Button *effect_on = nullptr;
|
||||
Light *effect_on_light = nullptr;
|
||||
|
||||
Button *vefx = nullptr;
|
||||
Light *vefx_light = nullptr;
|
||||
};
|
||||
}
|
||||
94
overlay/windows/keypad.cpp
Normal file
94
overlay/windows/keypad.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include <games/io.h>
|
||||
#include "keypad.h"
|
||||
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
Keypad::Keypad(SpiceOverlay *overlay, size_t unit) : Window(overlay), unit(unit) {
|
||||
this->title = "Keypad P" + to_string(unit + 1);
|
||||
this->flags = ImGuiWindowFlags_NoResize
|
||||
| ImGuiWindowFlags_NoCollapse
|
||||
| ImGuiWindowFlags_AlwaysAutoResize;
|
||||
|
||||
switch (this->unit) {
|
||||
case 0: {
|
||||
this->toggle_button = games::OverlayButtons::ToggleVirtualKeypadP1;
|
||||
this->init_pos = ImVec2(
|
||||
26,
|
||||
ImGui::GetIO().DisplaySize.y - 264);
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
this->toggle_button = games::OverlayButtons::ToggleVirtualKeypadP2;
|
||||
this->init_pos = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x - 220,
|
||||
ImGui::GetIO().DisplaySize.y - 264);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Keypad::~Keypad() {
|
||||
|
||||
// reset overrides
|
||||
eamuse_set_keypad_overrides_overlay(this->unit, 0);
|
||||
}
|
||||
|
||||
void Keypad::build_content() {
|
||||
|
||||
// buttons
|
||||
static const struct {
|
||||
const char *text;
|
||||
int flag;
|
||||
} BUTTONS[] = {
|
||||
{ "7", 1 << EAM_IO_KEYPAD_7 },
|
||||
{ "8", 1 << EAM_IO_KEYPAD_8 },
|
||||
{ "9", 1 << EAM_IO_KEYPAD_9 },
|
||||
{ "4", 1 << EAM_IO_KEYPAD_4 },
|
||||
{ "5", 1 << EAM_IO_KEYPAD_5 },
|
||||
{ "6", 1 << EAM_IO_KEYPAD_6 },
|
||||
{ "1", 1 << EAM_IO_KEYPAD_1 },
|
||||
{ "2", 1 << EAM_IO_KEYPAD_2 },
|
||||
{ "3", 1 << EAM_IO_KEYPAD_3 },
|
||||
{ "0", 1 << EAM_IO_KEYPAD_0 },
|
||||
{ "00", 1 << EAM_IO_KEYPAD_00 },
|
||||
{ ".", 1 << EAM_IO_KEYPAD_DECIMAL },
|
||||
{ "Insert Card", 1 << EAM_IO_INSERT },
|
||||
};
|
||||
|
||||
// reset overrides
|
||||
eamuse_set_keypad_overrides_overlay(this->unit, 0);
|
||||
|
||||
// build grid
|
||||
for (size_t i = 0; i < std::size(BUTTONS); i++) {
|
||||
auto &button = BUTTONS[i];
|
||||
|
||||
// push id and alignment
|
||||
ImGui::PushID(4096 + i);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.5f));
|
||||
|
||||
// add selectable (fill last line)
|
||||
if (i == std::size(BUTTONS) - 1) {
|
||||
ImGui::Selectable(button.text, false, 0, ImVec2(112, 32));
|
||||
} else {
|
||||
ImGui::Selectable(button.text, false, 0, ImVec2(32, 32));
|
||||
}
|
||||
|
||||
// mouse down handler
|
||||
if (ImGui::IsItemActive()) {
|
||||
eamuse_set_keypad_overrides_overlay(this->unit, button.flag);
|
||||
}
|
||||
|
||||
// pop id and alignment
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopID();
|
||||
|
||||
// line join
|
||||
if ((i % 3) < 2) {
|
||||
ImGui::SameLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
overlay/windows/keypad.h
Normal file
19
overlay/windows/keypad.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "overlay/window.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class Keypad : public Window {
|
||||
private:
|
||||
|
||||
size_t unit = 0;
|
||||
|
||||
public:
|
||||
|
||||
Keypad(SpiceOverlay *overlay, size_t unit);
|
||||
~Keypad() override;
|
||||
|
||||
void build_content() override;
|
||||
};
|
||||
}
|
||||
1449
overlay/windows/kfcontrol.cpp
Normal file
1449
overlay/windows/kfcontrol.cpp
Normal file
File diff suppressed because it is too large
Load Diff
126
overlay/windows/kfcontrol.h
Normal file
126
overlay/windows/kfcontrol.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include "overlay/window.h"
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
enum class HueFunc : int {
|
||||
Sine, Cosine, Absolute, Linear,
|
||||
Count
|
||||
};
|
||||
|
||||
class KFControl : public Window {
|
||||
public:
|
||||
|
||||
KFControl(SpiceOverlay *overlay);
|
||||
~KFControl() override;
|
||||
|
||||
void config_save();
|
||||
void config_load();
|
||||
ImVec4 hsv_transform(ImVec4 col,
|
||||
float hue = 0.f, float sat = 1.f, float val = 1.f);
|
||||
ImVec4 hue_shift(ImVec4 col, float amp, float per, uint64_t ms);
|
||||
|
||||
void build_content() override;
|
||||
|
||||
private:
|
||||
std::filesystem::path config_path;
|
||||
int config_profile = 0;
|
||||
|
||||
std::unique_ptr<std::thread> worker;
|
||||
bool worker_running = false;
|
||||
void worker_start();
|
||||
void worker_func();
|
||||
std::mutex worker_m;
|
||||
void worker_button_check(bool state_new, bool *state_old, int scan,
|
||||
int profile_switch = -1);
|
||||
void worker_button_set(int scan, bool state);
|
||||
void worker_mouse_click(bool state);
|
||||
void worker_mouse_move(int dx, int dy);
|
||||
|
||||
int poll_delay = 1;
|
||||
float vol_deadzone = 0.003f;
|
||||
uint64_t vol_timeout = 32;
|
||||
bool vol_mouse = false;
|
||||
float vol_mouse_sensitivity = 512;
|
||||
bool start_click = false;
|
||||
bool kp_profiles = false;
|
||||
|
||||
float vol_sound = 0.f;
|
||||
float vol_headphone = 0.f;
|
||||
float vol_external = 0.f;
|
||||
float vol_woofer = 0.f;
|
||||
bool vol_mute = false;
|
||||
|
||||
char icca_file[512] = "";
|
||||
uint64_t icca_timeout = 4000;
|
||||
uint64_t coin_timeout = 250;
|
||||
|
||||
ImVec4 light_wing_left_up {};
|
||||
ImVec4 light_wing_left_low {};
|
||||
ImVec4 light_wing_right_up {};
|
||||
ImVec4 light_wing_right_low {};
|
||||
ImVec4 light_woofer {};
|
||||
ImVec4 light_controller {};
|
||||
ImVec4 light_generator {};
|
||||
|
||||
bool light_buttons = true;
|
||||
bool light_hue_preview = false;
|
||||
bool light_hue_disable = false;
|
||||
HueFunc light_hue_func = HueFunc::Sine;
|
||||
float light_wing_left_up_hue_amp = 0.f;
|
||||
float light_wing_left_up_hue_per = 1.f;
|
||||
float light_wing_left_low_hue_amp = 0.f;
|
||||
float light_wing_left_low_hue_per = 1.f;
|
||||
float light_wing_right_up_hue_amp = 0.f;
|
||||
float light_wing_right_up_hue_per = 1.f;
|
||||
float light_wing_right_low_hue_amp = 0.f;
|
||||
float light_wing_right_low_hue_per = 1.f;
|
||||
float light_woofer_hue_amp = 0.f;
|
||||
float light_woofer_hue_per = 1.f;
|
||||
float light_controller_hue_amp = 0.f;
|
||||
float light_controller_hue_per = 1.f;
|
||||
float light_generator_hue_amp = 0.f;
|
||||
float light_generator_hue_per = 1.f;
|
||||
|
||||
/*
|
||||
* Keyboard Scancodes
|
||||
* Check: http://kbdlayout.info/kbdus/overview+scancodes
|
||||
*/
|
||||
|
||||
int scan_service = 3;
|
||||
int scan_test = 4;
|
||||
int scan_coin_mech = 5;
|
||||
int scan_bt_a = 32;
|
||||
int scan_bt_b = 33;
|
||||
int scan_bt_c = 36;
|
||||
int scan_bt_d = 37;
|
||||
int scan_fx_l = 46;
|
||||
int scan_fx_r = 50;
|
||||
int scan_start = 2;
|
||||
int scan_headphone = 0;
|
||||
int scan_vol_l_left = 17;
|
||||
int scan_vol_l_right = 18;
|
||||
int scan_vol_r_left = 24;
|
||||
int scan_vol_r_right = 25;
|
||||
int scan_icca = 6;
|
||||
int scan_coin = 7;
|
||||
int scan_kp_0 = 0;
|
||||
int scan_kp_1 = 0;
|
||||
int scan_kp_2 = 0;
|
||||
int scan_kp_3 = 0;
|
||||
int scan_kp_4 = 0;
|
||||
int scan_kp_5 = 0;
|
||||
int scan_kp_6 = 0;
|
||||
int scan_kp_7 = 0;
|
||||
int scan_kp_8 = 0;
|
||||
int scan_kp_9 = 0;
|
||||
int scan_kp_00 = 0;
|
||||
int scan_kp_decimal = 0;
|
||||
};
|
||||
}
|
||||
122
overlay/windows/log.cpp
Normal file
122
overlay/windows/log.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "log.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "games/io.h"
|
||||
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
Log::Log(SpiceOverlay *overlay) : Window(overlay) {
|
||||
this->title = "Log";
|
||||
this->toggle_button = games::OverlayButtons::ToggleLog;
|
||||
this->init_size = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x * 0.8f,
|
||||
ImGui::GetIO().DisplaySize.y * 0.8f);
|
||||
this->size_min = ImVec2(250, 200);
|
||||
this->init_pos = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
|
||||
ImGui::GetIO().DisplaySize.y / 2 - this->init_size.y / 2);
|
||||
|
||||
// read existing contents from file
|
||||
if (LOG_FILE_PATH.length() > 0) {
|
||||
auto contents = fileutils::text_read(LOG_FILE_PATH);
|
||||
if (contents.length() > 0) {
|
||||
this->log_hook(this, contents, logger::Style::DEFAULT, contents);
|
||||
}
|
||||
}
|
||||
|
||||
// add log hook
|
||||
logger::hook_add(&log_hook, this);
|
||||
}
|
||||
|
||||
Log::~Log() {
|
||||
|
||||
// remove log hook
|
||||
logger::hook_remove(&log_hook, this);
|
||||
}
|
||||
|
||||
void Log::clear() {
|
||||
|
||||
// lock and clear the data vector
|
||||
std::lock_guard<std::mutex> lock(this->log_data_m);
|
||||
this->log_data.clear();
|
||||
}
|
||||
|
||||
void Log::build_content() {
|
||||
|
||||
// clear button
|
||||
if (ImGui::Button("Clear")) {
|
||||
this->clear();
|
||||
}
|
||||
|
||||
// autoscroll option
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Autoscroll", &this->autoscroll);
|
||||
|
||||
// filter
|
||||
ImGui::SameLine();
|
||||
this->filter.Draw("Filter", -50.f);
|
||||
|
||||
// log area
|
||||
ImGui::Separator();
|
||||
ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
|
||||
// iterate log data
|
||||
this->log_data_m.lock();
|
||||
for (auto &data : log_data) {
|
||||
|
||||
// ignore empty lines and check filter
|
||||
if (data.first != "\r\n" && this->filter.PassFilter(data.first.c_str())) {
|
||||
|
||||
// decide on color
|
||||
ImVec4 col(1.f, 1.f, 1.f, 1.f);
|
||||
switch (data.second) {
|
||||
case logger::GREY:
|
||||
col = ImVec4(0.6f, 0.6f, 0.6f, 1.f);
|
||||
break;
|
||||
case logger::YELLOW:
|
||||
col = ImVec4(1.f, 1.f, 0.f, 1.f);
|
||||
break;
|
||||
case logger::RED:
|
||||
col = ImVec4(1.f, 0.f, 0.f, 1.f);
|
||||
break;
|
||||
case logger::DEFAULT:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// draw text
|
||||
ImGui::TextColored(col, "%s", data.first.c_str());
|
||||
}
|
||||
}
|
||||
this->log_data_m.unlock();
|
||||
|
||||
// automatic scrolling to bottom
|
||||
if (scroll_to_bottom) {
|
||||
scroll_to_bottom = false;
|
||||
ImGui::SetScrollHereY(1.f);
|
||||
}
|
||||
|
||||
// end log area
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
bool Log::log_hook(void *user, const std::string &data, logger::Style style, std::string &out) {
|
||||
|
||||
// get reference from user pointer
|
||||
auto This = reinterpret_cast<Log *>(user);
|
||||
|
||||
// copy log data
|
||||
This->log_data_m.lock();
|
||||
This->log_data.emplace_back(data, style);
|
||||
This->log_data_m.unlock();
|
||||
|
||||
// autoscroll
|
||||
if (This->autoscroll) {
|
||||
This->scroll_to_bottom = true;
|
||||
}
|
||||
|
||||
// don't replace log data
|
||||
return false;
|
||||
}
|
||||
}
|
||||
28
overlay/windows/log.h
Normal file
28
overlay/windows/log.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include "overlay/window.h"
|
||||
#include "launcher/logger.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class Log : public Window {
|
||||
private:
|
||||
|
||||
std::vector<std::pair<std::string, logger::Style>> log_data;
|
||||
std::mutex log_data_m;
|
||||
ImGuiTextFilter filter;
|
||||
bool scroll_to_bottom = true;
|
||||
bool autoscroll = true;
|
||||
|
||||
void clear();
|
||||
|
||||
public:
|
||||
|
||||
Log(SpiceOverlay *overlay);
|
||||
~Log() override;
|
||||
|
||||
void build_content() override;
|
||||
static bool log_hook(void *user, const std::string &data, logger::Style style, std::string &out);
|
||||
};
|
||||
}
|
||||
129
overlay/windows/midi.cpp
Normal file
129
overlay/windows/midi.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "midi.h"
|
||||
#include "launcher/launcher.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
static std::string midi_cmd_str(uint8_t cmd) {
|
||||
const char *name = "UNKNOWN";
|
||||
switch (cmd) {
|
||||
case 0x8:
|
||||
name = "NOTE OFF";
|
||||
break;
|
||||
case 0x9:
|
||||
name = "NOTE ON";
|
||||
break;
|
||||
case 0xA:
|
||||
name = "POLY.PRESS.";
|
||||
break;
|
||||
case 0xB:
|
||||
name = "CTRL CHANGE";
|
||||
break;
|
||||
case 0xC:
|
||||
name = "PRG CHANGE";
|
||||
break;
|
||||
case 0xD:
|
||||
name = "CHAN.PRESS.";
|
||||
break;
|
||||
case 0xE:
|
||||
name = "PITCH BEND";
|
||||
break;
|
||||
case 0xF:
|
||||
name = "SYSTEM";
|
||||
break;
|
||||
}
|
||||
return fmt::format("{} (0x{:2X})", name, cmd);
|
||||
}
|
||||
|
||||
MIDIWindow::MIDIWindow(SpiceOverlay *overlay) : Window(overlay) {
|
||||
this->title = "MIDI Control";
|
||||
this->init_size = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x * 0.8f,
|
||||
ImGui::GetIO().DisplaySize.y * 0.8f);
|
||||
this->size_min = ImVec2(250, 200);
|
||||
this->init_pos = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
|
||||
ImGui::GetIO().DisplaySize.y / 2 - this->init_size.y / 2);
|
||||
this->active = true;
|
||||
|
||||
// add hook for receiving midi messages
|
||||
RI_MGR->add_callback_midi(this, MIDIWindow::midi_hook);
|
||||
}
|
||||
|
||||
MIDIWindow::~MIDIWindow() {
|
||||
RI_MGR->remove_callback_midi(this, MIDIWindow::midi_hook);
|
||||
}
|
||||
|
||||
void MIDIWindow::build_content() {
|
||||
|
||||
// reset button
|
||||
if (ImGui::Button("Reset")) {
|
||||
this->midi_data.clear();
|
||||
}
|
||||
|
||||
// autoscroll checkbox
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Autoscroll", &this->autoscroll);
|
||||
|
||||
// log section
|
||||
ImGui::BeginChild("MidiLog", ImVec2(), false);
|
||||
|
||||
// header
|
||||
ImGui::Columns(5, "MidiLogColumns", true);
|
||||
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Device"); ImGui::NextColumn();
|
||||
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Command"); ImGui::NextColumn();
|
||||
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Channel"); ImGui::NextColumn();
|
||||
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Data 1"); ImGui::NextColumn();
|
||||
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Data 2"); ImGui::NextColumn();
|
||||
|
||||
// data
|
||||
ImGui::Separator();
|
||||
for (auto &data : this->midi_data) {
|
||||
|
||||
// set color
|
||||
srand(data.device->id * 2111);
|
||||
float hue = ((float) rand()) / ((float) RAND_MAX);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImColor::HSV(hue, 0.8f, 0.8f, 1.f).Value);
|
||||
|
||||
// data cells
|
||||
ImGui::Text("%i: %s", (int) data.device->id, data.device->desc.c_str());
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("%s", midi_cmd_str(data.cmd).c_str());
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("0x%02X - %i", data.ch, data.ch);
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("0x%02X", data.b1);
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("0x%02X", data.b2);
|
||||
ImGui::NextColumn();
|
||||
|
||||
// clean up
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
// autoscroll
|
||||
if (this->autoscroll_apply) {
|
||||
this->autoscroll_apply = false;
|
||||
ImGui::SetScrollHereY(1.f);
|
||||
}
|
||||
|
||||
// clean up section
|
||||
ImGui::Columns();
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void MIDIWindow::midi_hook(void *user, rawinput::Device *device,
|
||||
uint8_t cmd, uint8_t ch, uint8_t b1, uint8_t b2) {
|
||||
auto This = (MIDIWindow*) user;
|
||||
This->midi_data.emplace_back(MIDIData {
|
||||
.device = device,
|
||||
.cmd = cmd,
|
||||
.ch = ch,
|
||||
.b1 = b1,
|
||||
.b2 = b2,
|
||||
});
|
||||
if (This->autoscroll) {
|
||||
This->autoscroll_apply = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
overlay/windows/midi.h
Normal file
30
overlay/windows/midi.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "overlay/window.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
struct MIDIData {
|
||||
rawinput::Device *device;
|
||||
uint8_t cmd, ch;
|
||||
uint8_t b1, b2;
|
||||
};
|
||||
|
||||
class MIDIWindow : public Window {
|
||||
public:
|
||||
|
||||
MIDIWindow(SpiceOverlay *overlay);
|
||||
~MIDIWindow() override;
|
||||
|
||||
void build_content() override;
|
||||
static void midi_hook(void *user, rawinput::Device *device,
|
||||
uint8_t cmd, uint8_t ch, uint8_t b1, uint8_t b2);
|
||||
|
||||
private:
|
||||
|
||||
std::vector<MIDIData> midi_data;
|
||||
bool autoscroll = true;
|
||||
bool autoscroll_apply = false;
|
||||
};
|
||||
}
|
||||
2999
overlay/windows/patch_manager.cpp
Normal file
2999
overlay/windows/patch_manager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
168
overlay/windows/patch_manager.h
Normal file
168
overlay/windows/patch_manager.h
Normal file
@@ -0,0 +1,168 @@
|
||||
#pragma once
|
||||
|
||||
#include "overlay/window.h"
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include "external/rapidjson/document.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
enum class PatchType {
|
||||
Unknown,
|
||||
Memory,
|
||||
Signature,
|
||||
Union,
|
||||
Integer,
|
||||
};
|
||||
|
||||
enum class PatchStatus {
|
||||
Error,
|
||||
Disabled,
|
||||
Enabled,
|
||||
};
|
||||
|
||||
enum class PatchUrlStatus {
|
||||
Valid,
|
||||
Invalid,
|
||||
Unapplied,
|
||||
ValidButNoData,
|
||||
Partial,
|
||||
};
|
||||
|
||||
struct MemoryPatch {
|
||||
std::string dll_name = "";
|
||||
std::shared_ptr<uint8_t[]> data_disabled = nullptr;
|
||||
size_t data_disabled_len = 0;
|
||||
std::shared_ptr<uint8_t[]> data_enabled = nullptr;
|
||||
size_t data_enabled_len = 0;
|
||||
uint64_t data_offset = 0;
|
||||
uint8_t *data_offset_ptr = nullptr;
|
||||
bool fatal_error = false;
|
||||
};
|
||||
|
||||
struct PatchData;
|
||||
struct SignaturePatch {
|
||||
std::string dll_name = "";
|
||||
std::string signature = "", replacement = "";
|
||||
uint64_t offset = 0;
|
||||
int64_t usage = 0;
|
||||
|
||||
MemoryPatch to_memory(PatchData *patch);
|
||||
};
|
||||
|
||||
struct UnionPatch {
|
||||
std::string name = "";
|
||||
std::string dll_name = "";
|
||||
std::shared_ptr<uint8_t[]> data = nullptr;
|
||||
size_t data_len = 0;
|
||||
uint64_t offset = 0;
|
||||
uint8_t* data_offset_ptr = nullptr;
|
||||
bool fatal_error = false;
|
||||
};
|
||||
|
||||
struct NumberPatch {
|
||||
std::string dll_name = "";
|
||||
uint64_t data_offset = 0;
|
||||
uint8_t* data_offset_ptr = nullptr;
|
||||
int32_t min;
|
||||
int32_t max;
|
||||
int32_t value;
|
||||
size_t size_in_bytes;
|
||||
bool fatal_error = false;
|
||||
};
|
||||
|
||||
struct PatchData {
|
||||
bool enabled;
|
||||
std::string game_code;
|
||||
int datecode_min = 0;
|
||||
int datecode_max = 0;
|
||||
std::string name, description, caution;
|
||||
std::string name_in_lower_case = "";
|
||||
PatchType type;
|
||||
bool preset;
|
||||
std::vector<MemoryPatch> patches_memory;
|
||||
std::vector<UnionPatch> patches_union;
|
||||
NumberPatch patch_number;
|
||||
PatchStatus last_status;
|
||||
std::string hash;
|
||||
bool unverified = false;
|
||||
std::string peIdentifier;
|
||||
std::string error_reason = "";
|
||||
|
||||
// for union patch only
|
||||
std::string selected_union_name = "";
|
||||
};
|
||||
|
||||
std::string get_game_identifier(const std::filesystem::path& dll_path);
|
||||
|
||||
class PatchManager : public Window {
|
||||
public:
|
||||
|
||||
PatchManager(SpiceOverlay *overlay, bool apply_patches = false);
|
||||
~PatchManager() override;
|
||||
|
||||
void build_content() override;
|
||||
void reload_local_patches(bool apply_patches = false);
|
||||
bool import_remote_patches_to_disk();
|
||||
bool load_from_patches_json(bool apply_patches);
|
||||
bool import_remote_patches_for_dll(const std::string& url, const std::string& dll_name);
|
||||
void hard_apply_patches();
|
||||
void load_embedded_patches(bool apply_patches);
|
||||
|
||||
private:
|
||||
|
||||
// configuration
|
||||
static std::filesystem::path config_path;
|
||||
static bool config_dirty;
|
||||
static bool setting_auto_apply;
|
||||
static std::vector<std::string> setting_auto_apply_list;
|
||||
static std::vector<std::string> setting_patches_enabled;
|
||||
static std::map<std::string, std::string> setting_union_patches_enabled;
|
||||
static std::map<std::string, int64_t> setting_int_patches_enabled;
|
||||
static std::string patch_url;
|
||||
static std::string patch_name_filter;
|
||||
|
||||
static std::filesystem::path LOCAL_PATCHES_PATH;
|
||||
static std::string ACTIVE_JSON_FILE;
|
||||
|
||||
// patches
|
||||
static std::vector<PatchData> patches;
|
||||
static bool local_patches_initialized;
|
||||
|
||||
void config_load();
|
||||
void config_save();
|
||||
|
||||
void append_patches(
|
||||
std::string &patches_json,
|
||||
bool apply_patches = false,
|
||||
std::function<bool(const PatchData&)> filter = std::function<bool(const PatchData&)>(),
|
||||
std::string pe_identifier_for_patch = "");
|
||||
|
||||
};
|
||||
|
||||
PatchStatus is_patch_active(PatchData &patch);
|
||||
bool apply_patch(PatchData &patch, bool active);
|
||||
|
||||
int64_t parse_little_endian_int(uint8_t* bytes, size_t size);
|
||||
void int_to_little_endian_bytes(int64_t value, uint8_t* bytes, size_t size);
|
||||
|
||||
std::vector<uint8_t>* find_in_dll_map(
|
||||
const std::string& dll_name, size_t offset, size_t size);
|
||||
std::vector<uint8_t>* find_in_dll_map_org(
|
||||
const std::string& dll_name, size_t offset, size_t size);
|
||||
|
||||
bool restore_bytes_from_dll_map_org(
|
||||
uint8_t* destination, const std::string& dll_name, size_t offset, size_t size);
|
||||
|
||||
void create_dll_backup(
|
||||
std::vector<std::string>& written_list, const std::filesystem::path& dll_path);
|
||||
std::string fix_up_dll_name(const std::string& dll_name);
|
||||
uint8_t* get_dll_offset_for_patch_apply(
|
||||
const std::string& dll_name, const uint64_t data_offset, const size_t size_in_bytes);
|
||||
|
||||
uint64_t parse_json_data_offset(
|
||||
const std::string &patch_name, const rapidjson::Value &value);
|
||||
|
||||
void print_auto_apply_status(PatchData &patch);
|
||||
}
|
||||
223
overlay/windows/screen_resize.cpp
Normal file
223
overlay/windows/screen_resize.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
#include <games/io.h>
|
||||
|
||||
#include "screen_resize.h"
|
||||
#include "avs/game.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "overlay/imgui/extensions.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
ScreenResize::ScreenResize(SpiceOverlay *overlay) : Window(overlay) {
|
||||
this->title = "Screen Resize";
|
||||
this->flags = ImGuiWindowFlags_AlwaysAutoResize;
|
||||
this->init_pos = ImVec2(10, 10);
|
||||
this->toggle_button = games::OverlayButtons::ToggleScreenResize;
|
||||
this->toggle_screen_resize = games::OverlayButtons::ScreenResize;
|
||||
}
|
||||
|
||||
ScreenResize::~ScreenResize() {
|
||||
}
|
||||
|
||||
HWND ScreenResize::get_first_window() {
|
||||
if (GRAPHICS_WINDOWS.size() == 0) {
|
||||
return NULL;
|
||||
}
|
||||
return GRAPHICS_WINDOWS[0];
|
||||
}
|
||||
|
||||
void ScreenResize::reset_window() {
|
||||
this->reset_vars_to_default();
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
const auto window = get_first_window();
|
||||
if (window) {
|
||||
graphics_move_resize_window(window);
|
||||
graphics_update_window_style(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenResize::reset_vars_to_default() {
|
||||
cfg::SCREENRESIZE->enable_screen_resize = false;
|
||||
cfg::SCREENRESIZE->enable_linear_filter = true;
|
||||
cfg::SCREENRESIZE->keep_aspect_ratio = true;
|
||||
cfg::SCREENRESIZE->centered = true;
|
||||
cfg::SCREENRESIZE->offset_x = 0;
|
||||
cfg::SCREENRESIZE->offset_y = 0;
|
||||
cfg::SCREENRESIZE->scale_x = 1.f;
|
||||
cfg::SCREENRESIZE->scale_y = 1.f;
|
||||
|
||||
cfg::SCREENRESIZE->enable_window_resize = false;
|
||||
cfg::SCREENRESIZE->window_always_on_top = false;
|
||||
cfg::SCREENRESIZE->window_decoration = cfg::WindowDecorationMode::Default;
|
||||
cfg::SCREENRESIZE->window_offset_x = 0;
|
||||
cfg::SCREENRESIZE->window_offset_y = 0;
|
||||
cfg::SCREENRESIZE->client_keep_aspect_ratio = true;
|
||||
cfg::SCREENRESIZE->client_width = cfg::SCREENRESIZE->init_client_width;
|
||||
cfg::SCREENRESIZE->client_height = cfg::SCREENRESIZE->init_client_height;
|
||||
}
|
||||
|
||||
void ScreenResize::build_content() {
|
||||
ImGui::Text("For: %s", eamuse_get_game().c_str());
|
||||
{
|
||||
if (ImGui::TreeNodeEx("Image Resize", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
this->build_fullscreen_config();
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(!GRAPHICS_WINDOWED);
|
||||
{
|
||||
int flags = 0;
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
flags |= ImGuiTreeNodeFlags_DefaultOpen;
|
||||
}
|
||||
if (ImGui::TreeNodeEx("Window Size", flags)) {
|
||||
build_windowed_config();
|
||||
ImGui::TreePop();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
build_footer();
|
||||
}
|
||||
|
||||
void ScreenResize::build_fullscreen_config() {
|
||||
// enable checkbox
|
||||
ImGui::TextWrapped("Hint: bind a key to Screen Resize for quickly toggling this on/off.");
|
||||
ImGui::Checkbox("Enable", &cfg::SCREENRESIZE->enable_screen_resize);
|
||||
|
||||
ImGui::BeginDisabled(!cfg::SCREENRESIZE->enable_screen_resize);
|
||||
|
||||
// general settings
|
||||
ImGui::Checkbox("Linear Filter", &cfg::SCREENRESIZE->enable_linear_filter);
|
||||
ImGui::Checkbox("Centered", &cfg::SCREENRESIZE->centered);
|
||||
if (!cfg::SCREENRESIZE->centered) {
|
||||
ImGui::InputInt("X Offset", &cfg::SCREENRESIZE->offset_x);
|
||||
ImGui::InputInt("Y Offset", &cfg::SCREENRESIZE->offset_y);
|
||||
}
|
||||
|
||||
// aspect ratio
|
||||
ImGui::Checkbox("Keep Aspect Ratio", &cfg::SCREENRESIZE->keep_aspect_ratio);
|
||||
if (cfg::SCREENRESIZE->keep_aspect_ratio) {
|
||||
if (ImGui::SliderFloat("Scale", &cfg::SCREENRESIZE->scale_x, 0.65f, 2.0f)) {
|
||||
cfg::SCREENRESIZE->scale_y = cfg::SCREENRESIZE->scale_x;
|
||||
}
|
||||
} else {
|
||||
ImGui::SliderFloat("Width Scale", &cfg::SCREENRESIZE->scale_x, 0.65f, 2.0f);
|
||||
ImGui::SliderFloat("Height Scale", &cfg::SCREENRESIZE->scale_y, 0.65f, 2.0f);
|
||||
}
|
||||
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
void ScreenResize::build_windowed_config() {
|
||||
// for now, only supports the first window
|
||||
const auto window = get_first_window();
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::TextUnformatted("Warning: may cause some games to crash.");
|
||||
ImGui::BeginDisabled(graphics_window_change_crashes_game());
|
||||
if (ImGui::Combo(
|
||||
"Window Style",
|
||||
&cfg::SCREENRESIZE->window_decoration,
|
||||
"Default\0Borderless\0Resizable Window\0\0")) {
|
||||
graphics_update_window_style(window);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Change window decoration. Resizable Window may not cause your mouse cursor to change, "
|
||||
"but you can still drag to resize. Disabled for some games due to incompatibility.");
|
||||
|
||||
if (ImGui::Checkbox("Always on Top", &cfg::SCREENRESIZE->window_always_on_top) ) {
|
||||
graphics_update_z_order(window);
|
||||
}
|
||||
ImGui::Checkbox("Keep Aspect Ratio", &cfg::SCREENRESIZE->client_keep_aspect_ratio);
|
||||
ImGui::Checkbox("Manual window move/resize", &cfg::SCREENRESIZE->enable_window_resize);
|
||||
ImGui::BeginDisabled(!cfg::SCREENRESIZE->enable_window_resize);
|
||||
|
||||
bool changed = false;
|
||||
const uint32_t step = 1;
|
||||
const uint32_t step_fast = 10;
|
||||
ImGui::BeginDisabled(cfg::SCREENRESIZE->client_keep_aspect_ratio);
|
||||
ImGui::InputScalar(
|
||||
"Width",
|
||||
ImGuiDataType_U32,
|
||||
&cfg::SCREENRESIZE->client_width,
|
||||
&step, &step_fast, nullptr);
|
||||
changed |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::InputScalar(
|
||||
"Height",
|
||||
ImGuiDataType_U32,
|
||||
&cfg::SCREENRESIZE->client_height,
|
||||
&step, &step_fast, nullptr);
|
||||
changed |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
|
||||
ImGui::InputScalar(
|
||||
"X Offset",
|
||||
ImGuiDataType_S32,
|
||||
&cfg::SCREENRESIZE->window_offset_x,
|
||||
&step, &step_fast, nullptr);
|
||||
changed |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
|
||||
ImGui::InputScalar(
|
||||
"Y Offset",
|
||||
ImGuiDataType_S32,
|
||||
&cfg::SCREENRESIZE->window_offset_y,
|
||||
&step, &step_fast, nullptr);
|
||||
changed |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
|
||||
if (changed) {
|
||||
if (cfg::SCREENRESIZE->client_keep_aspect_ratio) {
|
||||
cfg::SCREENRESIZE->client_width =
|
||||
cfg::SCREENRESIZE->client_height * cfg::SCREENRESIZE->init_client_aspect_ratio;
|
||||
}
|
||||
graphics_move_resize_window(window);
|
||||
}
|
||||
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
void ScreenResize::build_footer() {
|
||||
// reset button
|
||||
if (ImGui::Button("Reset")) {
|
||||
this->reset_window();
|
||||
}
|
||||
|
||||
// load button
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Load")) {
|
||||
cfg::SCREENRESIZE->config_load();
|
||||
}
|
||||
|
||||
// save button
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Save")) {
|
||||
cfg::SCREENRESIZE->config_save();
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenResize::update() {
|
||||
Window::update();
|
||||
if (this->toggle_screen_resize != ~0u) {
|
||||
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
|
||||
bool toggle_screen_resize_new = overlay_buttons
|
||||
&& this->overlay->hotkeys_triggered()
|
||||
&& GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(this->toggle_screen_resize));
|
||||
|
||||
if (toggle_screen_resize_new && !this->toggle_screen_resize_state) {
|
||||
cfg::SCREENRESIZE->enable_screen_resize = !cfg::SCREENRESIZE->enable_screen_resize;
|
||||
}
|
||||
this->toggle_screen_resize_state = toggle_screen_resize_new;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
overlay/windows/screen_resize.h
Normal file
33
overlay/windows/screen_resize.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "overlay/window.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class ScreenResize : public Window {
|
||||
public:
|
||||
ScreenResize(SpiceOverlay *overlay);
|
||||
~ScreenResize() override;
|
||||
|
||||
void build_content() override;
|
||||
void update();
|
||||
|
||||
private:
|
||||
size_t toggle_screen_resize = ~0u;
|
||||
bool toggle_screen_resize_state = false;
|
||||
|
||||
void build_fullscreen_config();
|
||||
void build_windowed_config();
|
||||
void build_footer();
|
||||
std::string hwnd_preview(int index, HWND hwnd);
|
||||
HWND get_first_window();
|
||||
|
||||
void reset_window();
|
||||
void reset_vars_to_default();
|
||||
|
||||
static LRESULT CALLBACK screen_resize_wndproc(
|
||||
HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
static void wndproc_wm_sizing(int edge, RECT& rect);
|
||||
};
|
||||
}
|
||||
68
overlay/windows/sdvx_sub.cpp
Normal file
68
overlay/windows/sdvx_sub.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#undef CINTERFACE
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "sdvx_sub.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
SDVXSubScreen::SDVXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
|
||||
this->title = "SDVX Sub Screen";
|
||||
|
||||
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
|
||||
if (!isValkyrieCabinetMode) {
|
||||
this->disabled_message = "Valkyrie Model mode is not enabled!";
|
||||
} else if (GRAPHICS_WINDOWED) {
|
||||
if (GRAPHICS_PREVENT_SECONDARY_WINDOW) {
|
||||
this->disabled_message = "Subscreen has been disabled by the user (-sdvxnosub).";
|
||||
} else {
|
||||
this->disabled_message = "Overlay unavailable in windowed mode! Use the second window instead.";
|
||||
}
|
||||
}
|
||||
|
||||
const auto padding = ImGui::GetFrameHeight() / 2;
|
||||
|
||||
this->init_size = ImVec2(ImGui::GetIO().DisplaySize.x - (padding * 2), 0.f);
|
||||
this->init_size.y = (this->init_size.x * 9 / 16) + ImGui::GetFrameHeight();
|
||||
|
||||
this->init_pos = ImVec2(ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2, 0);
|
||||
switch (games::sdvx::OVERLAY_POS) {
|
||||
case games::sdvx::SDVX_OVERLAY_TOP:
|
||||
this->init_pos.y = padding;
|
||||
break;
|
||||
case games::sdvx::SDVX_OVERLAY_BOTTOM:
|
||||
this->init_pos.y = ImGui::GetIO().DisplaySize.y - this->init_size.y - padding;
|
||||
break;
|
||||
case games::sdvx::SDVX_OVERLAY_MIDDLE:
|
||||
default:
|
||||
this->init_pos.y = (ImGui::GetIO().DisplaySize.y / 2) - (this->init_size.y / 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SDVXSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {
|
||||
if (!this->get_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// SDVX Valkyrie cab needs math to make things work
|
||||
// what the game expects from touch screen:
|
||||
// [1080, 0 1920, 1080]
|
||||
// [0, 0 0, 1920]
|
||||
|
||||
// actual cursor position given by x_in (on the overlay, normalized):
|
||||
// [0, 0 1, 0]
|
||||
// [0, 1 1, 1]
|
||||
|
||||
// Basically, the game operates on rotated coordinates, but on top of that,
|
||||
// it needs to be adjusted so they match up with the subscreen overlay.
|
||||
|
||||
// so the math is:
|
||||
// Xin(0, 1) => Yout(0, 1920)
|
||||
// Yin(0, 1) => Xout(1080, 0)
|
||||
|
||||
*x_out = ImGui::GetIO().DisplaySize.x * (1.f - xy_in.y);
|
||||
*y_out = ImGui::GetIO().DisplaySize.y * xy_in.x;
|
||||
}
|
||||
}
|
||||
22
overlay/windows/sdvx_sub.h
Normal file
22
overlay/windows/sdvx_sub.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef SPICETOOLS_OVERLAY_WINDOWS_SDVX_SUB_H
|
||||
#define SPICETOOLS_OVERLAY_WINDOWS_SDVX_SUB_H
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "overlay/window.h"
|
||||
#include "overlay/windows/generic_sub.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class SDVXSubScreen : public GenericSubScreen {
|
||||
public:
|
||||
SDVXSubScreen(SpiceOverlay *overlay);
|
||||
|
||||
protected:
|
||||
void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) override;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // SPICETOOLS_OVERLAY_WINDOWS_SDVX_SUB_H
|
||||
218
overlay/windows/vr.cpp
Normal file
218
overlay/windows/vr.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
#include "vr.h"
|
||||
#include "misc/vrutil.h"
|
||||
#include "util/logging.h"
|
||||
#include "games/io.h"
|
||||
#include "games/drs/drs.h"
|
||||
#include "avs/game.h"
|
||||
#include "overlay/imgui/extensions.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
VRWindow::VRWindow(SpiceOverlay *overlay) : Window(overlay) {
|
||||
this->title = "VR";
|
||||
this->flags = ImGuiWindowFlags_None;
|
||||
this->toggle_button = games::OverlayButtons::ToggleVRControl;
|
||||
this->init_size = ImVec2(500, 800);
|
||||
this->size_min = ImVec2(250, 200);
|
||||
}
|
||||
|
||||
VRWindow::~VRWindow() {
|
||||
}
|
||||
|
||||
void VRWindow::build_content() {
|
||||
ImGui::BeginTabBar("VRTabBar");
|
||||
if (ImGui::BeginTabItem("Info")) {
|
||||
build_info();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (avs::game::is_model("REC")) {
|
||||
if (ImGui::BeginTabItem("Dancefloor")) {
|
||||
build_dancefloor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VRWindow::build_info() {
|
||||
|
||||
// status
|
||||
auto status = vrutil::STATUS;
|
||||
switch (status) {
|
||||
case vrutil::VRStatus::Disabled:
|
||||
ImGui::TextColored(
|
||||
ImVec4(0.4f, 0.4f, 0.4f, 1.f),
|
||||
"Disabled");
|
||||
if (ImGui::Button("Start")) {
|
||||
vrutil::init();
|
||||
if (avs::game::is_model("REC")) {
|
||||
games::drs::start_vr();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case vrutil::VRStatus::Error:
|
||||
ImGui::TextColored(
|
||||
ImVec4(0.8f, 0.1f, 0.1f, 1.f),
|
||||
"Error");
|
||||
if (ImGui::Button("Restart")) {
|
||||
vrutil::shutdown();
|
||||
vrutil::init();
|
||||
}
|
||||
break;
|
||||
case vrutil::VRStatus::Running:
|
||||
ImGui::TextColored(
|
||||
ImVec4(0.1f, 0.8f, 0.1f, 1.f),
|
||||
"Running");
|
||||
if (ImGui::Button("Stop")) {
|
||||
vrutil::shutdown();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// rescan
|
||||
if (ImGui::Button("Rescan Devices")) {
|
||||
vrutil::scan(true);
|
||||
}
|
||||
|
||||
// data table header
|
||||
ImGui::Columns(2);
|
||||
ImGui::Text("Device");
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Position");
|
||||
ImGui::NextColumn();
|
||||
ImGui::Separator();
|
||||
|
||||
// HMD/Left/Right data
|
||||
vr::TrackedDevicePose_t hmd_pose, left_pose, right_pose;
|
||||
vr::VRControllerState_t left_state, right_state;
|
||||
vrutil::get_hmd_pose(&hmd_pose);
|
||||
vrutil::get_con_pose(vrutil::INDEX_LEFT, &left_pose, &left_state);
|
||||
vrutil::get_con_pose(vrutil::INDEX_RIGHT, &right_pose, &right_state);
|
||||
auto hmd_pos = vrutil::get_translation(hmd_pose.mDeviceToAbsoluteTracking);
|
||||
auto left_pos = vrutil::get_translation(left_pose.mDeviceToAbsoluteTracking);
|
||||
auto right_pos = vrutil::get_translation(right_pose.mDeviceToAbsoluteTracking);
|
||||
ImGui::Text("HMD");
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextUnformatted(fmt::format(
|
||||
"X={:3f} Y={:3f} Z={:3f}",
|
||||
hmd_pos.x, hmd_pos.y, hmd_pos.z).c_str());
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Left");
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextUnformatted(fmt::format(
|
||||
"X={:3f} Y={:3f} Z={:3f}",
|
||||
left_pos.x, left_pos.y, left_pos.z).c_str());
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Right");
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextUnformatted(fmt::format(
|
||||
"X={:3f} Y={:3f} Z={:3f}",
|
||||
right_pos.x, right_pos.y, right_pos.z).c_str());
|
||||
ImGui::NextColumn();
|
||||
}
|
||||
|
||||
void VRWindow::build_dancefloor() {
|
||||
ImGui::Separator();
|
||||
|
||||
// settings
|
||||
ImGui::DragFloat3("Scale", &games::drs::VR_SCALE[0], 0.1f);
|
||||
ImGui::DragFloat3("Offset", &games::drs::VR_OFFSET[0], 0.1f);
|
||||
ImGui::DragFloat("Rotation", &games::drs::VR_ROTATION, 0.5f);
|
||||
for (int i = 0; i < (int) std::size(games::drs::VR_FOOTS); ++i) {
|
||||
auto &foot = games::drs::VR_FOOTS[i];
|
||||
ImGui::Separator();
|
||||
ImGui::PushID(&foot);
|
||||
ImGui::Text("%s Foot", i == 0 ? "Left" : "Right");
|
||||
ImGui::InputInt("Device Index", (int*) &foot.index, 1, 1);
|
||||
ImGui::DragFloat("Length", &foot.length,
|
||||
0.005f, 0.001f, 1000.f);
|
||||
ImGui::DragFloat("Size Base", &foot.size_base,
|
||||
0.005f, 0.001f, 1000.f);
|
||||
ImGui::DragFloat("Size Scale", &foot.size_scale,
|
||||
0.005f, 0.001f, 1000.f);
|
||||
ImGui::DragFloat4("Rotation Quat", &foot.rotation.x, 0.001f, -1, 1);
|
||||
if (ImGui::Button("Calibrate")) {
|
||||
vr::TrackedDevicePose_t pose;
|
||||
vr::VRControllerState_t state;
|
||||
vrutil::get_con_pose(foot.get_index(), &pose, &state);
|
||||
foot.length = foot.height + 0.02f;
|
||||
auto pose_rot = vrutil::get_rotation(pose.mDeviceToAbsoluteTracking.m);
|
||||
foot.rotation = linalg::qmul(linalg::qinv(pose_rot),
|
||||
vrutil::get_rotation((float) M_PI * -0.5f, 0, 0));
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Place the controller to the lower part of your leg "
|
||||
"and press this button to auto calibrate angle and length");
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
// prepare view
|
||||
auto draw_list = ImGui::GetWindowDrawList();
|
||||
auto canvas_pos = ImGui::GetCursorScreenPos();
|
||||
auto canvas_size = ImGui::GetContentRegionAvail();
|
||||
float offset_x = canvas_size.x * 0.5f;
|
||||
float offset_y = canvas_size.y * 0.1f;
|
||||
float off_x = offset_x + canvas_pos.x;
|
||||
float off_y = offset_y + canvas_pos.y;
|
||||
float scale = std::min(canvas_size.x, canvas_size.y) / 60;
|
||||
|
||||
// axis
|
||||
draw_list->AddLine(
|
||||
ImVec2(canvas_pos.x, off_y),
|
||||
ImVec2(canvas_pos.x + canvas_size.x, off_y),
|
||||
ImColor(255, 0, 0, 128));
|
||||
draw_list->AddLine(
|
||||
ImVec2(off_x, canvas_pos.y),
|
||||
ImVec2(off_x, canvas_pos.y + canvas_size.y),
|
||||
ImColor(0, 255, 0, 128));
|
||||
|
||||
// tiles
|
||||
for (int x = 0; x < 38; x++) {
|
||||
for (int y = 0; y < 49; y++) {
|
||||
auto &led = games::drs::DRS_TAPELED[x + y * 38];
|
||||
ImColor color((int) led[0], (int) led[1], (int) led[2]);
|
||||
ImVec2 p1((x - 19) * scale + off_x, (y + 0) * scale + off_y);
|
||||
ImVec2 p2((x - 18) * scale + off_x, (y + 1) * scale + off_y);
|
||||
draw_list->AddRectFilled(p1, p2, color, 0.f);
|
||||
}
|
||||
}
|
||||
|
||||
// foots
|
||||
const float foot_box = 2.f * scale;
|
||||
for (auto &foot : games::drs::VR_FOOTS) {
|
||||
vr::TrackedDevicePose_t pose;
|
||||
vr::VRControllerState_t state;
|
||||
vrutil::get_con_pose(foot.get_index(), &pose, &state);
|
||||
if (pose.bPoseIsValid) {
|
||||
|
||||
// position
|
||||
auto pos = vrutil::get_translation(pose.mDeviceToAbsoluteTracking);
|
||||
pos = foot.to_world(pos);
|
||||
pos.x -= 19;
|
||||
pos *= scale;
|
||||
ImColor color(255, 0, 255);
|
||||
if (foot.event.type == games::drs::DRS_DOWN
|
||||
|| (foot.event.type == games::drs::DRS_MOVE)) {
|
||||
auto size_factor = foot.event.width / (foot.size_base + foot.size_scale);
|
||||
color = ImColor((int) (size_factor * 127) + 128, 0, 0);
|
||||
}
|
||||
ImVec2 p1(pos.x + off_x - foot_box * 0.5f,
|
||||
pos.y + off_y - foot_box * 0.5f);
|
||||
ImVec2 p2(pos.x + off_x + foot_box * 0.5f,
|
||||
pos.y + off_y + foot_box * 0.5f);
|
||||
draw_list->AddRectFilled(p1, p2, color, 0.f);
|
||||
|
||||
// direction
|
||||
auto direction = -linalg::qzdir(linalg::qmul(
|
||||
vrutil::get_rotation(pose.mDeviceToAbsoluteTracking.m),
|
||||
foot.rotation));
|
||||
direction = linalg::aliases::float3 {
|
||||
-direction.z, direction.x, direction.y
|
||||
};
|
||||
auto end = pos + direction * foot.length * scale;
|
||||
draw_list->AddLine(
|
||||
ImVec2(pos.x + off_x, pos.y + off_y),
|
||||
ImVec2(end.x + off_x, end.y + off_y),
|
||||
ImColor(0, 255, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
overlay/windows/vr.h
Normal file
17
overlay/windows/vr.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "overlay/window.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class VRWindow : public Window {
|
||||
public:
|
||||
|
||||
VRWindow(SpiceOverlay *overlay);
|
||||
~VRWindow() override;
|
||||
|
||||
void build_content() override;
|
||||
void build_info();
|
||||
void build_dancefloor();
|
||||
};
|
||||
}
|
||||
153
overlay/windows/wnd_manager.cpp
Normal file
153
overlay/windows/wnd_manager.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
#include "wnd_manager.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
WndManagerWindow::WndManagerWindow(SpiceOverlay *overlay) : Window(overlay) {
|
||||
this->title = "Window Manager";
|
||||
this->flags |= ImGuiWindowFlags_AlwaysAutoResize;
|
||||
this->init_pos = ImVec2(
|
||||
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
|
||||
ImGui::GetIO().DisplaySize.y / 2 - this->init_size.y / 2);
|
||||
this->size_min = ImVec2(400, 400);
|
||||
this->active = true;
|
||||
}
|
||||
|
||||
WndManagerWindow::~WndManagerWindow() {
|
||||
}
|
||||
|
||||
static std::string hwnd_preview(int index, HWND hwnd) {
|
||||
char hwnd_title[256];
|
||||
if (GetWindowText(hwnd, hwnd_title, sizeof(hwnd_title)) > 0) {
|
||||
return hwnd_title;
|
||||
} else {
|
||||
return fmt::format("{}: {}", index, (void*) hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
void WndManagerWindow::build_content() {
|
||||
|
||||
// get current window
|
||||
auto &windows_list = GRAPHICS_WINDOWS;
|
||||
HWND hwnd_current = 0;
|
||||
std::string preview = "None";
|
||||
if (this->window_current >= (int) windows_list.size()) {
|
||||
this->window_current = windows_list.size() - 1;
|
||||
}
|
||||
if (this->window_current >= 0) {
|
||||
hwnd_current = windows_list[this->window_current];
|
||||
preview = hwnd_preview(this->window_current, hwnd_current);
|
||||
}
|
||||
|
||||
// window selection
|
||||
if (ImGui::BeginCombo("Window Selection", preview.c_str(), 0)) {
|
||||
size_t count = 0;
|
||||
for (auto &hwnd : windows_list) {
|
||||
bool selected = hwnd_current == hwnd;
|
||||
auto cur_preview = hwnd_preview(count, hwnd);
|
||||
if (ImGui::Selectable(cur_preview.c_str(), selected)) {
|
||||
this->window_current = count;
|
||||
}
|
||||
if (selected) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
count++;
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
// window information
|
||||
ImGui::Separator();
|
||||
if (hwnd_current == 0) {
|
||||
ImGui::TextColored(ImVec4(1.f, 0.f, 0.f, 1.f),
|
||||
"Please select a window first...");
|
||||
} else {
|
||||
|
||||
// window information
|
||||
ImGui::SetNextItemOpen(true, ImGuiCond_Once);
|
||||
if (ImGui::CollapsingHeader("Information")) {
|
||||
static struct {
|
||||
const char *desc;
|
||||
int index;
|
||||
} INFORMATION [] {
|
||||
{ .desc = "GWL_EXSTYLE", .index = GWL_EXSTYLE },
|
||||
{ .desc = "GWLP_HINSTANCE", .index = -6 },
|
||||
{ .desc = "GWLP_HWNDPARENT", .index = -8 },
|
||||
{ .desc = "GWLP_ID", .index = GWL_ID },
|
||||
{ .desc = "GWL_STYLE", .index = GWL_STYLE },
|
||||
{ .desc = "GWLP_USERDATA", .index = -21 },
|
||||
{ .desc = "GWLP_WNDPROC", .index = -4 },
|
||||
};
|
||||
|
||||
// columns header
|
||||
ImGui::Columns(2);
|
||||
ImGui::TextUnformatted("Index"); ImGui::NextColumn();
|
||||
ImGui::TextUnformatted("Value"); ImGui::NextColumn();
|
||||
|
||||
// add information
|
||||
ImGui::Separator();
|
||||
for (auto &entry : INFORMATION) {
|
||||
|
||||
// index
|
||||
ImGui::TextUnformatted(entry.desc);
|
||||
ImGui::NextColumn();
|
||||
|
||||
// value
|
||||
ImGui::Text("%p", (void*) GetWindowLongPtr(hwnd_current, entry.index));
|
||||
ImGui::NextColumn();
|
||||
}
|
||||
|
||||
// end columns
|
||||
ImGui::Columns();
|
||||
}
|
||||
|
||||
// size information
|
||||
ImGui::SetNextItemOpen(true, ImGuiCond_Once);
|
||||
if (ImGui::CollapsingHeader("Sizes")) {
|
||||
|
||||
// window rect
|
||||
RECT hwnd_rect {};
|
||||
if (GetWindowRect(hwnd_current, &hwnd_rect)) {
|
||||
ImGui::Text("Window Rect: %ld %ld %ld %ld - %ld %ld",
|
||||
hwnd_rect.left, hwnd_rect.top, hwnd_rect.right, hwnd_rect.bottom,
|
||||
hwnd_rect.right - hwnd_rect.left, hwnd_rect.bottom - hwnd_rect.top);
|
||||
|
||||
// client rect
|
||||
RECT client_rect {};
|
||||
if (GetClientRect(hwnd_current, &client_rect)) {
|
||||
ImGui::Text("Client Rect: %ld %ld %ld %ld - %ld %ld",
|
||||
client_rect.left, client_rect.top, client_rect.right, client_rect.bottom,
|
||||
client_rect.right - client_rect.left, client_rect.bottom - client_rect.top);
|
||||
ImGui::Text("Decoration Size: %ld %ld",
|
||||
(hwnd_rect.right - hwnd_rect.left) - (client_rect.right - client_rect.left),
|
||||
(hwnd_rect.bottom - hwnd_rect.top) - (client_rect.bottom - client_rect.top));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// position information
|
||||
ImGui::SetNextItemOpen(true, ImGuiCond_Once);
|
||||
if (ImGui::CollapsingHeader("Positions")) {
|
||||
|
||||
// window position
|
||||
RECT hwnd_rect {};
|
||||
if (GetWindowRect(hwnd_current, &hwnd_rect)) {
|
||||
ImGui::Text("Window Position: %ld %ld",
|
||||
hwnd_rect.left, hwnd_rect.top);
|
||||
}
|
||||
|
||||
// cursor position
|
||||
POINT cursor_pos;
|
||||
if (GetCursorPos(&cursor_pos)) {
|
||||
ImGui::Text("Cursor Position: %ld %ld",
|
||||
cursor_pos.x, cursor_pos.y);
|
||||
if (ScreenToClient(hwnd_current, &cursor_pos)) {
|
||||
ImGui::Text("Cursor Client Position: %ld %ld",
|
||||
cursor_pos.x, cursor_pos.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
overlay/windows/wnd_manager.h
Normal file
19
overlay/windows/wnd_manager.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "overlay/window.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
class WndManagerWindow : public Window {
|
||||
public:
|
||||
|
||||
WndManagerWindow(SpiceOverlay *overlay);
|
||||
~WndManagerWindow() override;
|
||||
|
||||
void build_content() override;
|
||||
|
||||
private:
|
||||
|
||||
int window_current = -1;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user