Initial re-upload of spice2x-24-08-24

This commit is contained in:
2024-08-28 11:10:34 -04:00
commit caa9e02285
1181 changed files with 380065 additions and 0 deletions

View 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);
}
}

View 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;
};
}

View 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();
}
}

View 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();
};
}

View 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;
}
}

View 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

File diff suppressed because it is too large Load Diff

104
overlay/windows/config.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
};
}

View 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
}
}

View 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

View 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();
}
}

View 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);
};
}

View 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;
}
}
}

View 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
View 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
View 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;
};
}

View 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();
}
}

View 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];
};
}

View 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;
}
}
}

View 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];
};
}

View 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;
}
}
}
}
}

View 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;
};
}

View 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
View 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;
};
}

File diff suppressed because it is too large Load Diff

126
overlay/windows/kfcontrol.h Normal file
View 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
View 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
View 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
View 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
View 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;
};
}

File diff suppressed because it is too large Load Diff

View 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);
}

View 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;
}
}
}

View 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);
};
}

View 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;
}
}

View 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
View 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
View 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();
};
}

View 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);
}
}
}
}
}
}

View 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;
};
}