2024-04-14 12:32:49 +02:00
|
|
|
//
|
|
|
|
// Created by beerpsi on 4/14/2024.
|
|
|
|
//
|
|
|
|
|
|
|
|
#include <format>
|
|
|
|
#include <fstream>
|
|
|
|
#include <random>
|
|
|
|
|
|
|
|
#include <imgui.h>
|
|
|
|
|
|
|
|
#include "configurator.h"
|
|
|
|
#include "extensions/imgui.h"
|
|
|
|
#include "games/io.h"
|
|
|
|
#include "option.h"
|
|
|
|
#include "imgui_internal.h"
|
|
|
|
|
|
|
|
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
|
|
|
|
|
|
|
// Card-related fields are initialized by the read_card() function.
|
|
|
|
#pragma clang diagnostic push
|
|
|
|
#pragma ide diagnostic ignored "cppcoreguidelines-pro-type-member-init"
|
|
|
|
Configurator::Configurator() {
|
|
|
|
const std::vector<std::string>& names = games::get_games();
|
|
|
|
|
|
|
|
for (const std::string& game_name : names) {
|
|
|
|
this->game_names.push_back(game_name.c_str());
|
|
|
|
|
|
|
|
std::vector<std::string>* file_hints = games::get_game_file_hints(game_name);
|
|
|
|
|
|
|
|
if (!file_hints) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (std::string& file_hint : *file_hints) {
|
|
|
|
std::filesystem::file_status status = std::filesystem::status(std::filesystem::path(file_hint));
|
|
|
|
|
|
|
|
if (status.type() != std::filesystem::file_type::regular) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
this->game_selected = this->game_names.size() - 1;
|
|
|
|
this->game_selected_name = game_name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
read_card();
|
|
|
|
}
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
|
|
|
|
void Configurator::read_card() {
|
2024-04-14 15:50:08 +02:00
|
|
|
this->aime_gen = GetPrivateProfileIntA(
|
|
|
|
"aime",
|
|
|
|
"aimeGen",
|
|
|
|
1,
|
|
|
|
".\\segatools.ini"
|
|
|
|
);
|
|
|
|
|
2024-04-14 12:32:49 +02:00
|
|
|
GetPrivateProfileStringA(
|
|
|
|
"aime",
|
|
|
|
"aimePath",
|
|
|
|
"DEVICE\\aime.txt",
|
|
|
|
this->aime_card_path,
|
|
|
|
MAX_PATH,
|
|
|
|
".\\segatools.ini"
|
|
|
|
);
|
|
|
|
|
|
|
|
std::ifstream f(this->aime_card_path);
|
|
|
|
|
|
|
|
if (!f || !f.is_open()) {
|
|
|
|
this->aime_card_id[0] = 0;
|
|
|
|
} else {
|
|
|
|
f.seekg(0, std::ios::end);
|
|
|
|
auto length = (size_t)f.tellg();
|
|
|
|
f.seekg(0, std::ios::beg);
|
|
|
|
|
|
|
|
f.read(this->aime_card_id, 20);
|
|
|
|
this->aime_card_id[length < 20 ? length : 20] = 0;
|
|
|
|
f.close();
|
|
|
|
}
|
|
|
|
|
2024-04-14 15:50:08 +02:00
|
|
|
this->felica_gen = GetPrivateProfileIntA(
|
|
|
|
"aime",
|
|
|
|
"felicaGen",
|
|
|
|
0,
|
|
|
|
".\\segatools.ini"
|
|
|
|
);
|
|
|
|
|
2024-04-14 12:32:49 +02:00
|
|
|
GetPrivateProfileStringA(
|
|
|
|
"aime",
|
|
|
|
"felicaPath",
|
|
|
|
"DEVICE\\felica.txt",
|
|
|
|
this->felica_card_path,
|
|
|
|
MAX_PATH,
|
|
|
|
".\\segatools.ini"
|
|
|
|
);
|
|
|
|
|
|
|
|
std::ifstream f2(this->felica_card_path);
|
|
|
|
|
|
|
|
if (!f2 || !f2.is_open()) {
|
|
|
|
this->felica_card_id[0] = 0;
|
|
|
|
} else {
|
|
|
|
f2.seekg(0, std::ios::end);
|
|
|
|
auto length = (size_t)f2.tellg();
|
|
|
|
f2.seekg(0, std::ios::beg);
|
|
|
|
|
|
|
|
f2.read(this->felica_card_id, 16);
|
|
|
|
this->felica_card_id[length < 16 ? length : 16] = 0;
|
|
|
|
f2.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Configurator::write_card() {
|
2024-04-14 15:50:08 +02:00
|
|
|
WritePrivateProfileStringA(
|
|
|
|
"aime",
|
|
|
|
"aimeGen",
|
|
|
|
this->aime_gen ? "1" : "0",
|
|
|
|
".\\segatools.ini"
|
|
|
|
);
|
2024-04-14 12:32:49 +02:00
|
|
|
WritePrivateProfileStringA(
|
|
|
|
"aime",
|
|
|
|
"aimePath",
|
|
|
|
this->aime_card_path,
|
|
|
|
".\\segatools.ini"
|
|
|
|
);
|
|
|
|
|
|
|
|
int id_len = strlen(this->aime_card_id);
|
|
|
|
|
|
|
|
if (id_len == 20) {
|
|
|
|
std::ofstream f(this->aime_card_path);
|
|
|
|
if (f) {
|
|
|
|
f.write(this->aime_card_id, id_len);
|
|
|
|
f.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-14 15:50:08 +02:00
|
|
|
WritePrivateProfileStringA(
|
|
|
|
"aime",
|
|
|
|
"felicaGen",
|
|
|
|
this->felica_gen ? "1" : "0",
|
|
|
|
".\\segatools.ini"
|
|
|
|
);
|
2024-04-14 12:32:49 +02:00
|
|
|
WritePrivateProfileStringA(
|
|
|
|
"aime",
|
|
|
|
"felicaPath",
|
|
|
|
this->felica_card_path,
|
|
|
|
".\\segatools.ini"
|
|
|
|
);
|
|
|
|
|
|
|
|
id_len = strlen(this->felica_card_id);
|
|
|
|
|
|
|
|
if (id_len == 16) {
|
|
|
|
std::ofstream f(this->felica_card_path);
|
|
|
|
if (f) {
|
|
|
|
f.write(this->felica_card_id, id_len);
|
|
|
|
f.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Configurator::build_content() {
|
|
|
|
ImGui::PushItemWidth(MAX(1, 463 - MAX(0, 580 - ImGui::GetWindowSize().x)));
|
|
|
|
ImGui::Combo("Game Selection",
|
|
|
|
&this->game_selected, game_names.data(),
|
|
|
|
(int) game_names.size());
|
|
|
|
ImGui::PopItemWidth();
|
|
|
|
|
|
|
|
if (this->game_selected >= 0 && this->game_selected < game_names.size()) {
|
|
|
|
this->game_selected_name = std::string(game_names.at(this->game_selected));
|
|
|
|
} else {
|
|
|
|
this->game_selected_name = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this->game_selected_name.empty()) {
|
|
|
|
ImGui::TextUnformatted("Please select a game!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ImGui::BeginTabBar("Config Tabs", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) {
|
|
|
|
if (ImGui::BeginTabItem("Buttons")) {
|
|
|
|
build_buttons("SW", &games::get_sw_buttons(this->game_selected_name));
|
|
|
|
|
|
|
|
ImGui::TextUnformatted("");
|
|
|
|
|
|
|
|
build_buttons("Game", games::get_buttons(this->game_selected_name));
|
|
|
|
ImGui::EndTabItem();
|
|
|
|
}
|
|
|
|
if (ImGui::BeginTabItem("Cards")) {
|
|
|
|
build_cards();
|
|
|
|
ImGui::EndTabItem();
|
|
|
|
}
|
|
|
|
if (ImGui::BeginTabItem("Options")) {
|
|
|
|
auto options = parse_options();
|
|
|
|
std::vector<std::string> categories = { "Game Options", "VFS", "Aime", "Network", "Graphics", "Keychip", "Clock", "Other" };
|
|
|
|
|
|
|
|
for (const auto& category : categories) {
|
|
|
|
build_options(options.get(), category);
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::EndTabItem();
|
|
|
|
}
|
|
|
|
if (ImGui::BeginTabItem("Advanced")) {
|
|
|
|
auto options = parse_options();
|
|
|
|
std::vector<std::string> categories = { "Game Options (Advanced)", "Graphics (Advanced)", "Keychip (Advanced)", "System (Advanced)", "ALLS (Advanced)", "AMEX (Advanced)" };
|
|
|
|
|
|
|
|
for (const auto& category : categories) {
|
|
|
|
build_options(options.get(), category);
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::EndTabItem();
|
|
|
|
}
|
|
|
|
if (ImGui::BeginTabItem("About")) {
|
|
|
|
build_about();
|
|
|
|
ImGui::EndTabItem();
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::EndTabBar();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Configurator::open_card_file_selector(const std::filesystem::path& pwd, const wchar_t* key) {
|
|
|
|
card_select_done = false;
|
|
|
|
card_select_thread = new std::thread([pwd, key, this] {
|
|
|
|
auto ofn_path = std::make_unique<wchar_t[]>(MAX_PATH);
|
|
|
|
|
|
|
|
OPENFILENAMEW ofn {};
|
|
|
|
memset(&ofn, 0, sizeof(ofn));
|
|
|
|
ofn.lStructSize = sizeof(ofn);
|
|
|
|
ofn.hwndOwner = nullptr;
|
|
|
|
ofn.lpstrFilter = L"";
|
|
|
|
ofn.lpstrFile = ofn_path.get();
|
|
|
|
ofn.nMaxFile = 512;
|
|
|
|
ofn.Flags = OFN_EXPLORER;
|
|
|
|
ofn.lpstrDefExt = L"txt";
|
|
|
|
|
|
|
|
if (GetSaveFileNameW(&ofn)) {
|
|
|
|
// I don't know why but the PWD is changed lmao
|
|
|
|
std::filesystem::current_path(pwd);
|
|
|
|
WritePrivateProfileStringW(
|
|
|
|
L"aime",
|
|
|
|
key,
|
|
|
|
ofn_path.get(),
|
|
|
|
L".\\segatools.ini"
|
|
|
|
);
|
|
|
|
read_card();
|
|
|
|
} else {
|
|
|
|
auto error = CommDlgExtendedError();
|
|
|
|
|
|
|
|
OutputDebugStringA(std::format("Failed to get save file name: {}", error).c_str());
|
|
|
|
};
|
|
|
|
|
|
|
|
card_select_done = true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void Configurator::build_cards() {
|
|
|
|
bool updated = false;
|
|
|
|
|
|
|
|
ImGui::PushID("CardAime");
|
|
|
|
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "AiMe");
|
|
|
|
|
2024-04-14 15:50:08 +02:00
|
|
|
ImGui::AlignTextToFramePadding();
|
|
|
|
ImGui::Text("Automatically generate if not exist when scanning");
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Checkbox(this->aime_gen ? "Enabled" : "Disabled", &this->aime_gen)) {
|
|
|
|
updated = true;
|
|
|
|
}
|
|
|
|
|
2024-04-14 12:32:49 +02:00
|
|
|
if (ImGui::InputTextWithHint("Card Path", "DEVICE\\aime.txt", this->aime_card_path, sizeof(this->aime_card_path))) {
|
|
|
|
updated = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Button("Open...")) {
|
|
|
|
open_card_file_selector(std::filesystem::current_path(), L"aimePath");
|
|
|
|
}
|
|
|
|
|
|
|
|
bool card_valid = this->aime_card_id[0] == 0 || strlen(this->aime_card_id) == 20;
|
|
|
|
|
|
|
|
if (this->aime_card_id[0] != 0 && card_valid) {
|
|
|
|
for (int i = 0; i < 20; i++) {
|
|
|
|
char c = this->aime_card_id[i];
|
|
|
|
bool is_digit = '0' <= c && c <= '9';
|
|
|
|
|
|
|
|
if (!is_digit) {
|
|
|
|
card_valid = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Text,
|
|
|
|
card_valid ? ImVec4(1.f, 1.f, 1.f, 1.f) :
|
|
|
|
ImVec4(1.f, 0.f, 0.f, 1.f));
|
|
|
|
ImGui::InputTextWithHint("Access Code", "01234567890123456789",
|
|
|
|
this->aime_card_id, sizeof(this->aime_card_id),
|
|
|
|
ImGuiInputTextFlags_CharsDecimal);
|
|
|
|
ImGui::PopStyleColor();
|
|
|
|
|
|
|
|
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
|
|
|
updated = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::HelpMarker("Click Generate to create a random access code and save it to the configured file.");
|
|
|
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Button("Generate")) {
|
|
|
|
std::random_device rd;
|
|
|
|
std::mt19937 generator(rd());
|
|
|
|
std::uniform_int_distribution<> uniform(0, 9);
|
|
|
|
|
|
|
|
uint8_t card_bytes[10];
|
|
|
|
|
|
|
|
// AiMe IDs should not start with 3, due to a missing check for BananaPass IDs
|
|
|
|
do {
|
|
|
|
for (unsigned char & card_byte : card_bytes) {
|
|
|
|
card_byte = uniform(generator) << 4 | uniform(generator);
|
|
|
|
}
|
|
|
|
} while (card_bytes[0] >> 4 == 3);
|
|
|
|
|
|
|
|
sprintf(this->aime_card_id,
|
|
|
|
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
|
|
|
card_bytes[0], card_bytes[1], card_bytes[2], card_bytes[3], card_bytes[4],
|
|
|
|
card_bytes[5], card_bytes[6], card_bytes[7], card_bytes[8], card_bytes[9]);
|
|
|
|
write_card();
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::PopID();
|
|
|
|
ImGui::Separator();
|
|
|
|
|
|
|
|
ImGui::PushID("CardFeliCa");
|
|
|
|
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "FeliCa");
|
|
|
|
|
2024-04-14 15:50:08 +02:00
|
|
|
ImGui::AlignTextToFramePadding();
|
|
|
|
ImGui::Text("Automatically generate if not exist when scanning");
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Checkbox(this->felica_gen ? "Enabled" : "Disabled", &this->felica_gen)) {
|
|
|
|
updated = true;
|
|
|
|
}
|
|
|
|
|
2024-04-14 12:32:49 +02:00
|
|
|
if (ImGui::InputTextWithHint("Card Path", "DEVICE\\felica.txt", this->felica_card_path, sizeof(this->felica_card_path))) {
|
|
|
|
updated = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Button("Open...")) {
|
|
|
|
open_card_file_selector(std::filesystem::current_path(), L"felicaPath");
|
|
|
|
}
|
|
|
|
|
|
|
|
card_valid = this->felica_card_id[0] == 0 || strlen(this->felica_card_id) == 16;
|
|
|
|
|
|
|
|
if (this->felica_card_id[0] != 0 && card_valid) {
|
|
|
|
for (int i = 0; i < 20; i++) {
|
|
|
|
char c = this->aime_card_id[i];
|
|
|
|
bool is_digit = '0' <= c && c <= '9';
|
|
|
|
bool is_hex = ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F');
|
|
|
|
|
|
|
|
if (!is_digit && !is_hex) {
|
|
|
|
card_valid = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Text,
|
|
|
|
card_valid ? ImVec4(1.f, 1.f, 1.f, 1.f) :
|
|
|
|
ImVec4(1.f, 0.f, 0.f, 1.f));
|
|
|
|
ImGui::InputTextWithHint("Card Number", "0B01020304050607",
|
|
|
|
this->felica_card_id, sizeof(this->felica_card_id),
|
|
|
|
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase);
|
|
|
|
ImGui::PopStyleColor();
|
|
|
|
|
|
|
|
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
|
|
|
updated = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::HelpMarker("Click Generate to create a random card number and save it to the configured file.");
|
|
|
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Button("Generate")) {
|
|
|
|
std::random_device rd;
|
|
|
|
std::mt19937 generator(rd());
|
|
|
|
std::uniform_int_distribution<> uniform(0, 255);
|
|
|
|
|
|
|
|
uint8_t card_bytes[8];
|
|
|
|
|
|
|
|
for (unsigned char & card_byte : card_bytes) {
|
|
|
|
card_byte = uniform(generator);
|
|
|
|
}
|
|
|
|
|
|
|
|
// FeliCa IDm values should have a 0 in their high nibble. Apparently.
|
|
|
|
card_bytes[0] &= 0x0F;
|
|
|
|
|
|
|
|
sprintf(this->felica_card_id,
|
|
|
|
"%02X%02X%02X%02X%02X%02X%02X%02X",
|
|
|
|
card_bytes[0], card_bytes[1], card_bytes[2], card_bytes[3],
|
|
|
|
card_bytes[4], card_bytes[5], card_bytes[6], card_bytes[7]);
|
|
|
|
|
|
|
|
write_card();
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::PopID();
|
|
|
|
|
|
|
|
// clean up thread when needed
|
|
|
|
if (card_select_done) {
|
|
|
|
card_select_done = false;
|
|
|
|
card_select_thread->join();
|
|
|
|
delete card_select_thread;
|
|
|
|
card_select_thread = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (updated) {
|
|
|
|
write_card();
|
|
|
|
read_card();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Configurator::build_options(std::vector<Option> *options, const std::string &category) {
|
|
|
|
ImGui::Columns(3, "OptionsColumns", true);
|
|
|
|
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), category.c_str());
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
|
|
|
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Key");
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::HelpMarker("This is what the option corresponds to in segatools.ini.");
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
|
|
|
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Setting");
|
|
|
|
ImGui::NextColumn();
|
|
|
|
ImGui::Separator();
|
|
|
|
|
|
|
|
if (options->empty()) {
|
|
|
|
ImGui::TextUnformatted("-");
|
|
|
|
ImGui::NextColumn();
|
|
|
|
ImGui::TextUnformatted("-");
|
|
|
|
ImGui::NextColumn();
|
|
|
|
ImGui::TextUnformatted("-");
|
|
|
|
ImGui::NextColumn();
|
|
|
|
}
|
|
|
|
|
|
|
|
games::HWFamily hw_family = games::get_hw_family(this->game_selected_name);
|
|
|
|
|
|
|
|
for (auto &option : *options) {
|
|
|
|
auto definition = option.get_definition();
|
|
|
|
|
|
|
|
// check category
|
|
|
|
if (!category.empty() && definition.category != category) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!definition.game_name.empty() && definition.game_name != this->game_selected_name) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (definition.hw_family != games::HW_FAMILY_UNKNOWN && definition.hw_family != hw_family) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// list entry
|
|
|
|
ImGui::PushID(&option);
|
|
|
|
ImGui::AlignTextToFramePadding();
|
|
|
|
ImGui::HelpMarker(definition.desc.c_str());
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
|
|
|
if (option.value != definition.default_value) {
|
|
|
|
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1.f), "%s", definition.title.c_str());
|
|
|
|
} else {
|
|
|
|
ImGui::Text("%s", definition.title.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
|
|
|
ImGui::AlignTextToFramePadding();
|
|
|
|
ImGui::Text("%s.%s", definition.section.c_str(), definition.key.c_str());
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
|
|
|
if (option.disabled) {
|
|
|
|
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (definition.type) {
|
|
|
|
case OptionType::Bool: {
|
|
|
|
bool state = option.value_bool();
|
|
|
|
|
|
|
|
if (ImGui::Checkbox(state ? "Enabled" : "Disabled", &state)) {
|
|
|
|
option.value_set(state ? "1" : "0");
|
|
|
|
option.update_config();
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case OptionType::Integer: {
|
|
|
|
char buffer[512];
|
|
|
|
|
|
|
|
strncpy(buffer, option.value.c_str(), sizeof(buffer) - 1);
|
|
|
|
buffer[sizeof(buffer) - 1] = '\0';
|
|
|
|
|
|
|
|
auto digits_filter = [](ImGuiInputTextCallbackData* data) {
|
|
|
|
if ('0' <= data->EventChar && data->EventChar <= '9') {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
};
|
|
|
|
|
|
|
|
const char *hint = definition.setting_name.empty() ? "Enter number..."
|
|
|
|
: definition.setting_name.c_str();
|
|
|
|
|
|
|
|
if (ImGui::InputTextWithHint("", hint,
|
|
|
|
buffer, sizeof(buffer) - 1,
|
|
|
|
ImGuiInputTextFlags_CallbackCharFilter, digits_filter)) {
|
|
|
|
option.value = buffer;
|
|
|
|
option.update_config();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case OptionType::Text: {
|
|
|
|
char buffer[definition.max_string_length + 1];
|
|
|
|
|
|
|
|
strncpy(buffer, option.value.c_str(), sizeof(buffer));
|
|
|
|
buffer[sizeof(buffer) - 1] = '\0';
|
|
|
|
|
|
|
|
const char *hint = definition.setting_name.empty() ? "Enter value..."
|
|
|
|
: definition.setting_name.c_str();
|
|
|
|
|
|
|
|
if (ImGui::InputTextWithHint("", hint, buffer, sizeof(buffer))) {
|
|
|
|
option.value = buffer;
|
|
|
|
option.update_config();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case OptionType::Enum: {
|
|
|
|
std::string current_item = option.value_text();
|
|
|
|
for (auto &element : definition.elements) {
|
|
|
|
if (element.first == current_item) {
|
|
|
|
current_item += std::format(" ({})", element.second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (current_item.empty()) {
|
|
|
|
current_item = "Default";
|
|
|
|
}
|
|
|
|
if (ImGui::BeginCombo("##combo", current_item.c_str(), 0)) {
|
|
|
|
for (auto &element : definition.elements) {
|
|
|
|
bool selected = current_item == element.first;
|
|
|
|
std::string label = element.first;
|
|
|
|
if (!element.second.empty()) {
|
|
|
|
label += std::format(" ({})", element.second);
|
|
|
|
}
|
|
|
|
if (ImGui::Selectable(label.c_str(), selected)) {
|
|
|
|
option.value = element.first;
|
|
|
|
option.update_config();
|
|
|
|
}
|
|
|
|
if (selected) {
|
|
|
|
ImGui::SetItemDefaultFocus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ImGui::EndCombo();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
ImGui::Text("Unsupported option type");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (option.disabled) {
|
|
|
|
ImGui::PopItemFlag();
|
|
|
|
ImGui::PopStyleVar();
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::PopID();
|
|
|
|
ImGui::NextColumn();
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::Columns(1);
|
|
|
|
ImGui::TextUnformatted("");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Configurator::build_buttons(const std::string &name, std::vector<Button> *buttons) {
|
|
|
|
ImGui::Columns(3, "ButtonsColumns", true);
|
|
|
|
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "%s Button", name.c_str());
|
|
|
|
ImGui::NextColumn();
|
|
|
|
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Binding");
|
|
|
|
ImGui::NextColumn();
|
|
|
|
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Actions");
|
|
|
|
ImGui::NextColumn();
|
|
|
|
ImGui::Separator();
|
|
|
|
|
|
|
|
// check if empty
|
|
|
|
if (!buttons || buttons->empty()) {
|
|
|
|
ImGui::TextUnformatted("-");
|
|
|
|
ImGui::NextColumn();
|
|
|
|
ImGui::TextUnformatted("-");
|
|
|
|
ImGui::NextColumn();
|
|
|
|
ImGui::TextUnformatted("-");
|
|
|
|
ImGui::NextColumn();
|
|
|
|
ImGui::Columns();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto &button : *buttons) {
|
|
|
|
ImGui::PushID(&button);
|
|
|
|
|
|
|
|
bool button_pressed = (GetAsyncKeyState(button.vKey) & 0x8000) > 0;
|
|
|
|
|
|
|
|
if (button_pressed) {
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 0.7f, 0.f, 1.f));
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::AlignTextToFramePadding();
|
|
|
|
ImGui::Text("%s", button.name.c_str());
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
|
|
|
ImGui::AlignTextToFramePadding();
|
|
|
|
ImGui::Text("%s", button.getVKeyName().c_str());
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
|
|
|
if (button_pressed) {
|
|
|
|
ImGui::PopStyleColor();
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::AlignTextToFramePadding();
|
|
|
|
|
|
|
|
std::string bind_name = "Bind " + button.name;
|
|
|
|
|
|
|
|
if (ImGui::Button("Bind")) {
|
|
|
|
ImGui::OpenPopup(bind_name.c_str());
|
|
|
|
|
|
|
|
this->previous_vk = this->vk;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ImGui::BeginPopupModal(bind_name.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
|
|
|
bool isHovered = ImGui::IsItemHovered();
|
|
|
|
|
|
|
|
ImGui::Text("Please press any button.");
|
|
|
|
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Hint: Press ESC to cancel!");
|
|
|
|
|
|
|
|
if (this->game_selected_name == "O.N.G.E.K.I.") {
|
|
|
|
// XXX: How do I check if the mouse is hovering over the entire popup?
|
|
|
|
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Hint 2: Click on this popup's title bar to bind to a mouse button.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ImGui::Button("Cancel")) {
|
|
|
|
ImGui::CloseCurrentPopup();
|
|
|
|
} else if (this->vk != this->previous_vk) {
|
|
|
|
if (this->vk != VK_ESCAPE) {
|
|
|
|
button.vKey = this->vk;
|
|
|
|
|
|
|
|
WritePrivateProfileStringA(
|
|
|
|
button.section.c_str(),
|
|
|
|
button.key.c_str(),
|
|
|
|
std::format("{:#x}", button.vKey).c_str(),
|
|
|
|
".\\segatools.ini"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::CloseCurrentPopup();
|
|
|
|
} else if (isHovered && this->game_selected_name == "O.N.G.E.K.I.") {
|
|
|
|
int vKey = -1;
|
|
|
|
const ImGuiIO& io = ImGui::GetIO();
|
|
|
|
|
|
|
|
if (io.MouseDown[0]) {
|
|
|
|
vKey = VK_LBUTTON;
|
|
|
|
} else if (io.MouseDown[1]) {
|
|
|
|
vKey = VK_RBUTTON;
|
|
|
|
} else if (io.MouseDown[2]) {
|
|
|
|
vKey = VK_MBUTTON;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vKey != -1) {
|
|
|
|
button.vKey = vKey;
|
|
|
|
|
|
|
|
WritePrivateProfileStringA(
|
|
|
|
button.section.c_str(),
|
|
|
|
button.key.c_str(),
|
|
|
|
std::format("{:#x}", button.vKey).c_str(),
|
|
|
|
".\\segatools.ini"
|
|
|
|
);
|
|
|
|
|
|
|
|
ImGui::CloseCurrentPopup();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::EndPopup();
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
|
|
|
if (ImGui::Button("Default")) {
|
|
|
|
button.vKey = button.defaultVKey;
|
|
|
|
|
|
|
|
WritePrivateProfileStringA(
|
|
|
|
button.section.c_str(),
|
|
|
|
button.key.c_str(),
|
|
|
|
std::format("{:#x}", button.vKey).c_str(),
|
|
|
|
".\\segatools.ini"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
|
|
|
ImGui::PopID();
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::Columns();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Configurator::build_about() {
|
|
|
|
ImGui::TextUnformatted("segatools-configurator");
|
|
|
|
ImGui::TextUnformatted("");
|
|
|
|
ImGui::TextUnformatted("Based on spice2x/SpiceTools Configurator. Check them out if you play 573 rhythm games:");
|
|
|
|
|
|
|
|
if (ImGui::Button("https://spice2x.github.io")) {
|
|
|
|
std::thread t([] {
|
|
|
|
ShellExecuteA(nullptr, "open", "https://spice2x.github.io", nullptr, nullptr, SW_SHOWNORMAL);
|
|
|
|
});
|
|
|
|
t.join();
|
|
|
|
}
|
|
|
|
}
|