mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 220b27159b | |||
| b06a0a7d47 | |||
| d66af8e16b | |||
| ad366ecdd0 | |||
| 22cb4d689c | |||
| b38ea463aa | |||
| 056d1b2d90 | |||
| 37693916d9 | |||
| 889e8b43eb |
@@ -266,6 +266,7 @@ set_source_files_properties(cfg/manifest.rc PROPERTIES LANGUAGE RC)
|
||||
set_source_files_properties(cfg/icon.rc PROPERTIES LANGUAGE RC)
|
||||
set_source_files_properties(cfg/Win32D.rc PROPERTIES LANGUAGE RC)
|
||||
set_source_files_properties(build/manifest64.rc PROPERTIES LANGUAGE RC)
|
||||
set_source_files_properties(stubs/manifest.rc PROPERTIES LANGUAGE RC)
|
||||
|
||||
# sources
|
||||
#########
|
||||
@@ -813,7 +814,8 @@ endif()
|
||||
|
||||
# nvcuda.dll
|
||||
set(SOURCE_FILES stubs/nvcuda.cpp)
|
||||
add_library(spicetools_stubs_nvcuda SHARED ${SOURCE_FILES} stubs/nvcuda.def)
|
||||
set(RESOURCE_FILES stubs/manifest.rc)
|
||||
add_library(spicetools_stubs_nvcuda SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvcuda.def)
|
||||
set_target_properties(spicetools_stubs_nvcuda PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_stubs_nvcuda PROPERTIES OUTPUT_NAME "nvcuda")
|
||||
|
||||
@@ -823,7 +825,8 @@ endif()
|
||||
|
||||
# nvcuvid.dll
|
||||
set(SOURCE_FILES stubs/nvcuvid.cpp)
|
||||
add_library(spicetools_stubs_nvcuvid SHARED ${SOURCE_FILES} stubs/nvcuvid.def)
|
||||
set(RESOURCE_FILES stubs/manifest.rc)
|
||||
add_library(spicetools_stubs_nvcuvid SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvcuvid.def)
|
||||
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES OUTPUT_NAME "nvcuvid")
|
||||
|
||||
@@ -833,7 +836,8 @@ endif()
|
||||
|
||||
# nvEncodeAPI64.dll
|
||||
set(SOURCE_FILES stubs/nvEncodeAPI64.cpp)
|
||||
add_library(spicetools_stubs_nvEncodeAPI64 SHARED ${SOURCE_FILES} stubs/nvEncodeAPI64.def)
|
||||
set(RESOURCE_FILES stubs/manifest.rc)
|
||||
add_library(spicetools_stubs_nvEncodeAPI64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvEncodeAPI64.def)
|
||||
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES OUTPUT_NAME "nvEncodeAPI64")
|
||||
|
||||
@@ -843,7 +847,8 @@ endif()
|
||||
|
||||
# cpusbxpkm.dll (32 bit)
|
||||
set(SOURCE_FILES stubs/cpusbxpkm.cpp)
|
||||
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} stubs/cpusbxpkm.def)
|
||||
set(RESOURCE_FILES stubs/manifest.rc)
|
||||
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/cpusbxpkm.def)
|
||||
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES OUTPUT_NAME "cpusbxpkm")
|
||||
|
||||
|
||||
+27
-3
@@ -217,7 +217,23 @@ namespace avs {
|
||||
ea3_config_name = "prop/eamuse-config.xml";
|
||||
}
|
||||
|
||||
log_info("avs-ea3", "booting (using {})", ea3_config_name);
|
||||
if (avs::core::file_exists(ea3_config_name)) {
|
||||
log_info("avs-ea3", "booting (using {})", ea3_config_name);
|
||||
} else {
|
||||
log_warning("avs-ea3", "looked for the following files in order:");
|
||||
log_warning("avs-ea3", " * prop/ea3-config.xml");
|
||||
log_warning("avs-ea3", " * prop/ea3-cfg.xml");
|
||||
if (avs::game::DLL_NAME == "beatstream1.dll") {
|
||||
log_warning("avs-ea3", " * prop/ea3-config-1.xml");
|
||||
}
|
||||
if (avs::game::DLL_NAME == "beatstream2.dll") {
|
||||
log_warning("avs-ea3", " * prop/ea3-config-2.xml");
|
||||
}
|
||||
log_warning("avs-ea3", " * prop/eamuse-config.xml");
|
||||
log_warning("avs-ea3", "none of the files above were found, game will fail to boot");
|
||||
log_warning("avs-ea3", "usually, this means your game data is incomplete");
|
||||
log_fatal("avs-ea3", "no ea3 config file found in prop directory; check log messages");
|
||||
}
|
||||
|
||||
// remember new config path
|
||||
CFG_PATH = ea3_config_name;
|
||||
@@ -554,9 +570,17 @@ namespace avs {
|
||||
auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param");
|
||||
|
||||
// call the game init
|
||||
log_info("avs-ea3", "calling entry init (init code = {})", init_code_str);
|
||||
log_info("avs-ea3", "calling dll_entry_init (init code = {})", init_code_str);
|
||||
if (!avs::game::entry_init(init_code_str.data(), app_param)) {
|
||||
log_fatal("avs-ea3", "entry init failed :(");
|
||||
for (size_t i = 0; i < 255; i++) {
|
||||
log_warning("avs-ea3", "dll_entry_init failed :(");
|
||||
}
|
||||
deferredlogs::defer_error_messages({
|
||||
"dll_entry_init failed",
|
||||
" init code: " + init_code_str,
|
||||
" this is a fatal error that causes boot failure",
|
||||
" most likely, your prop files have errors"
|
||||
});
|
||||
}
|
||||
|
||||
// accommodate changes to soft id code
|
||||
|
||||
@@ -1507,6 +1507,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
// early hooks
|
||||
for (auto &hook : early_hooks) {
|
||||
log_info("launcher", "loading early hook DLL {}", hook);
|
||||
libutils::print_dll_info(hook);
|
||||
HMODULE module;
|
||||
if (!(module = libutils::try_library(hook))) {
|
||||
log_warning("launcher", "failed to load early hook {}: {}", hook, get_last_error_string());
|
||||
@@ -2307,6 +2308,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
// load hooks
|
||||
for (auto &hook : game_hooks) {
|
||||
log_info("launcher", "loading hook DLL {}", hook);
|
||||
libutils::print_dll_info(hook);
|
||||
HMODULE module;
|
||||
if (!(module = libutils::try_library(hook))) {
|
||||
log_warning("launcher", "failed to load hook {}: {}", hook, get_last_error_string());
|
||||
|
||||
@@ -163,4 +163,17 @@ namespace ImGui {
|
||||
ImGui::PopID();
|
||||
return clicked;
|
||||
}
|
||||
|
||||
void InvisibleTableRowSelectable() {
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::PushStyleColor(ImGuiCol_Header, 0);
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, 0);
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderActive, 0);
|
||||
ImGui::Selectable("##row", false,
|
||||
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);
|
||||
ImGui::PopStyleColor(3);
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(200, 200, 200, 24));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,4 +20,5 @@ namespace ImGui {
|
||||
void TextTruncated(const std::string& p_text, float p_truncated_width);
|
||||
bool DeleteButton(const std::string& tooltip);
|
||||
bool ClearButton(const std::string& tooltip);
|
||||
void InvisibleTableRowSelectable();
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ void overlay::SpiceOverlay::init() {
|
||||
|
||||
// Text
|
||||
colors[ImGuiCol_Text] = ImVec4(1.00f, 0.90f, 0.90f, 1.00f); // Slight pinkish tint to off-white
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.40f, 0.40f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.35f, 0.26f, 0.26f, 1.00f);
|
||||
|
||||
// Backgrounds
|
||||
colors[ImGuiCol_WindowBg] = ImVec4(0.08f, 0.07f, 0.07f, 1.00f); // Deep charcoal
|
||||
|
||||
@@ -58,6 +58,7 @@ namespace overlay::windows {
|
||||
const auto PROJECT_URL = "https://spice2x.github.io";
|
||||
|
||||
constexpr ImVec4 TEXT_COLOR_GREEN(0.f, 1.f, 0.f, 1.f);
|
||||
constexpr uint32_t OPTION_INPUT_TEXT_WIDTH = 512;
|
||||
|
||||
std::unique_ptr<AsioDriverList> asio_driver_list;
|
||||
|
||||
@@ -202,25 +203,6 @@ namespace overlay::windows {
|
||||
}
|
||||
}
|
||||
|
||||
void Config::write_card(int player) {
|
||||
|
||||
// get path
|
||||
auto bindings = ::Config::getInstance().getKeypadBindings(this->games_selected_name);
|
||||
std::filesystem::path path;
|
||||
if (!bindings.card_paths[player].empty()) {
|
||||
path = bindings.card_paths[player];
|
||||
} else {
|
||||
path = player > 0 ? "card1.txt" : "card0.txt";
|
||||
}
|
||||
|
||||
// write file
|
||||
std::ofstream f(path);
|
||||
if (f) {
|
||||
f.write(this->keypads_card_number[player], strlen(this->keypads_card_number[player]));
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
|
||||
void Config::build_content() {
|
||||
|
||||
// if standalone then fullscreen window
|
||||
@@ -703,12 +685,18 @@ namespace overlay::windows {
|
||||
|
||||
// primary
|
||||
build_button(name, primary_button, &primary_button, button_it, button_it_max, 0);
|
||||
ImGui::PushID(&primary_button);
|
||||
ImGui::InvisibleTableRowSelectable();
|
||||
ImGui::PopID();
|
||||
|
||||
// alternatives
|
||||
int alt_index = 1; // 0 is primary
|
||||
for (auto &alt : primary_button.getAlternatives()) {
|
||||
if (alt.isValid()) {
|
||||
build_button(name, primary_button, &alt, button_it, button_it_max, alt_index);
|
||||
ImGui::PushID(&alt);
|
||||
ImGui::InvisibleTableRowSelectable();
|
||||
ImGui::PopID();
|
||||
}
|
||||
alt_index++;
|
||||
}
|
||||
@@ -864,6 +852,51 @@ namespace overlay::windows {
|
||||
ImGui::HelpTooltip("Bind a button to a device using Windows RawInput API.");
|
||||
}
|
||||
|
||||
if (this->analog_as_button_warning_show_next_frame.first == button->getName() &&
|
||||
this->analog_as_button_warning_show_next_frame.second == alt_index) {
|
||||
|
||||
this->analog_as_button_warning_show_next_frame = std::make_pair("", 0);
|
||||
ImGui::OpenPopup("Warning##AnalogAsButtonWarning");
|
||||
|
||||
// stop bind-many
|
||||
RI_MGR->devices_midi_freeze(false);
|
||||
this->buttons_bind_active = false;
|
||||
this->buttons_many_active = false;
|
||||
}
|
||||
if (ImGui::BeginPopupModal("Warning##AnalogAsButtonWarning", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::TextUnformatted(
|
||||
"You just bound an analog axis to a digital button.\n"
|
||||
"\n"
|
||||
"If your controller supports analog input (turntable,\n"
|
||||
"knob, slider) you should instead use the Analog tab and\n"
|
||||
"bind as an analog axis.");
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// yes
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.6f, 0.2f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.2f, 0.9f, 0.2f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.2f, 0.7f, 0.2f, 1.0f));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Bullet();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("I understand, undo binding")) {
|
||||
clear_button(button, alt_index);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
// no
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Bullet();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Ignore and continue anyway")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
bind_button_popup(bind_name, button, button_it_max, alt_index);
|
||||
|
||||
// naive binding
|
||||
@@ -1208,6 +1241,22 @@ namespace overlay::windows {
|
||||
buttons_bind_active = false;
|
||||
inc_buttons_many_index(button_it_max);
|
||||
RI_MGR->devices_midi_freeze(false);
|
||||
|
||||
// usually, turntables are X, knobs are X and Y
|
||||
// gamepad triggers are Z, and Rx/Ry are right thumb stick
|
||||
// one day we will label all I/O modules and flag which one are button-as-analog
|
||||
// so that we don't have to do string comparions like below
|
||||
if ((value_name == "X" || value_name == "Y") &&
|
||||
(button->getName().find("Press") == std::string::npos) && // museca
|
||||
(button->getName().find("Slowdown") == std::string::npos) && // bishibashi
|
||||
(button->getName().find("TT+") != std::string::npos ||
|
||||
button->getName().find("TT-") != std::string::npos || // iidx
|
||||
button->getName().find("Knob") != std::string::npos || // gitadora guitar
|
||||
button->getName().find("Disk") != std::string::npos || // museca, bishibashi
|
||||
button->getName().find("VOL-") != std::string::npos)) { // sdvx
|
||||
this->analog_as_button_warning_show_next_frame =
|
||||
std::make_pair(button->getName(), alt_index);
|
||||
}
|
||||
break;
|
||||
|
||||
} else if (diff > 0.3f) {
|
||||
@@ -2127,6 +2176,9 @@ namespace overlay::windows {
|
||||
|
||||
edit_analog_popup(analog);
|
||||
|
||||
// row hover detection (invisible selectable that spans entire row)
|
||||
ImGui::InvisibleTableRowSelectable();
|
||||
|
||||
// clean up
|
||||
ImGui::PopID();
|
||||
}
|
||||
@@ -2747,13 +2799,21 @@ namespace overlay::windows {
|
||||
}
|
||||
|
||||
build_light(light, &light, i, 0);
|
||||
ImGui::PushID(&light);
|
||||
ImGui::InvisibleTableRowSelectable();
|
||||
ImGui::PopID();
|
||||
int alt_index = 1;
|
||||
for (auto &alt : light.getAlternatives()) {
|
||||
if (alt.isValid()) {
|
||||
build_light(light, &alt, i, alt_index);
|
||||
ImGui::PushID(&alt);
|
||||
ImGui::InvisibleTableRowSelectable();
|
||||
ImGui::PopID();
|
||||
}
|
||||
alt_index++;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
if (table_begin) {
|
||||
ImGui::EndTable();
|
||||
@@ -3694,14 +3754,133 @@ namespace overlay::windows {
|
||||
|
||||
void Config::build_cards() {
|
||||
|
||||
constexpr float TEXT_INPUT_WIDTH = 240.f;
|
||||
|
||||
// early quit
|
||||
if (this->games_selected < 0 || this->games_selected_name.empty()) {
|
||||
ImGui::Text("Please select a game first.");
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Card overrides");
|
||||
ImGui::Spacing();
|
||||
ImGui::SeparatorText("Card files (card0.txt / card1.txt)");
|
||||
ImGui::TextUnformatted(
|
||||
"Specify hardcoded card numbers here. This will always take priority when Insert Card is pressed.");
|
||||
ImGui::Spacing();
|
||||
|
||||
// read in values from options
|
||||
auto options = games::get_options(this->games_selected_name);
|
||||
for (int player = 0; player < 2; player++) {
|
||||
ImGui::PushID(("OverrideCardP" + to_string(player)).c_str());
|
||||
|
||||
// intentionally using the same buffer length as options tab
|
||||
char buffer[OPTION_INPUT_TEXT_WIDTH];
|
||||
bool card_changed = false;
|
||||
|
||||
// read in values from options
|
||||
auto &option = (player == 0)
|
||||
? options->at(launcher::Options::Player1Card)
|
||||
: options->at(launcher::Options::Player2Card);
|
||||
if (option.is_active()) {
|
||||
strncpy(buffer, option.value.c_str(), sizeof(buffer) - 1);
|
||||
} else {
|
||||
buffer[0] = '\0';
|
||||
}
|
||||
|
||||
const bool tree_open = ImGui::TreeNodeEx("##OverrideCardTree", ImGuiTreeNodeFlags_DefaultOpen);
|
||||
if (this->keypads_card_override_valid[player]) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, TEXT_COLOR_GREEN);
|
||||
}
|
||||
ImGui::SameLine(0.0f, 0.0f);
|
||||
ImGui::Text("Player %i (-card%i)", player + 1, player);
|
||||
if (this->keypads_card_override_valid[player]) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
if (tree_open) {
|
||||
// card number box
|
||||
ImGui::PushStyleColor(
|
||||
ImGuiCol_Text,
|
||||
this->keypads_card_override_valid[player] ? ImVec4(1.f, 1.f, 1.f, 1.f) :
|
||||
ImVec4(1.f, 0.f, 0.f, 1.f));
|
||||
ImGui::SetNextItemWidth(TEXT_INPUT_WIDTH);
|
||||
ImGui::BeginDisabled(option.disabled);
|
||||
ImGui::InputTextWithHint("##OverrideCard", "E004010000000000",
|
||||
buffer, sizeof(buffer) - 1,
|
||||
ImGuiInputTextFlags_CharsUppercase |
|
||||
ImGuiInputTextFlags_CharsHexadecimal |
|
||||
ImGuiInputTextFlags_EscapeClearsAll);
|
||||
ImGui::PopStyleColor();
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
card_changed = true;
|
||||
}
|
||||
|
||||
// check if valid on every frame;
|
||||
// do this after the widget and remember the result for next frame
|
||||
this->keypads_card_override_valid[player] = false;
|
||||
const auto buffer_len = strlen(buffer);
|
||||
if (buffer_len == 16) {
|
||||
this->keypads_card_override_valid[player] = validate_ea_card(buffer);
|
||||
} else if (buffer_len == 0) {
|
||||
this->keypads_card_override_valid[player] = false;
|
||||
}
|
||||
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (!option.disabled) {
|
||||
// generate button
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Generate")) {
|
||||
generate_ea_card(buffer);
|
||||
card_changed = true;
|
||||
}
|
||||
|
||||
// clear button
|
||||
if (option.is_active()) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear")) {
|
||||
buffer[0] = '\0';
|
||||
card_changed = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
|
||||
ImGui::HelpTooltip(
|
||||
"This option cannot be edited because it was overriden by command-line options.\n"
|
||||
"Run spicecfg.exe to configure the options and then run spice(64).exe directly.");
|
||||
}
|
||||
}
|
||||
|
||||
// bad card number warning
|
||||
if (!this->keypads_card_override_valid[player] && buffer_len > 0) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1.f, 0.f, 0.f, 1.f), "Invalid card number");
|
||||
}
|
||||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
if (player == 0) {
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
|
||||
if (card_changed) {
|
||||
this->options_dirty = true;
|
||||
option.value = buffer;
|
||||
::Config::getInstance().updateBinding(games_list[games_selected], option);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Card from text files");
|
||||
ImGui::Spacing();
|
||||
ImGui::TextUnformatted(
|
||||
"Use text files on disk; its content will be read when Insert Card is pressed.");
|
||||
ImGui::Spacing();
|
||||
|
||||
// get bindings and copy paths
|
||||
@@ -3718,133 +3897,148 @@ namespace overlay::windows {
|
||||
// card settings for each player
|
||||
for (int player = 0; player < 2; player++) {
|
||||
|
||||
// check overrides first
|
||||
const auto &override = (player == 0)
|
||||
? options->at(launcher::Options::Player1Card)
|
||||
: options->at(launcher::Options::Player2Card);
|
||||
|
||||
// custom ID and title
|
||||
ImGui::PushID(("KeypadP" + to_string(player)).c_str());
|
||||
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Player %i", player + 1);
|
||||
|
||||
// card path
|
||||
std::string hint = "card" + to_string(player) + ".txt";
|
||||
if (ImGui::InputTextWithHint("Card Path", hint.c_str(),
|
||||
this->keypads_card_path[player], sizeof(this->keypads_card_path[0]) - 1))
|
||||
{
|
||||
bindings.card_paths[player] = this->keypads_card_path[player];
|
||||
bindings_updated = true;
|
||||
const bool tree_open = ImGui::TreeNodeEx("##KeypadTreeNode", ImGuiTreeNodeFlags_DefaultOpen);
|
||||
const bool card_content_valid =
|
||||
!override.is_active() &&
|
||||
this->keypads_card_file_contents_valid[player] &&
|
||||
this->keypads_card_number[player][0] != 0;
|
||||
if (card_content_valid) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, TEXT_COLOR_GREEN);
|
||||
}
|
||||
ImGui::SameLine(0.0f, 0.0f);
|
||||
ImGui::Text("Player %i (card%i.txt)", player + 1, player);
|
||||
if (card_content_valid) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
// help marker
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Leave this empty to use the card file in your game directory.\n"
|
||||
"Hint: You can place 'card0.txt' (P1) / 'card1.txt' (P2) into the root of your USB "
|
||||
"flash drive and it will trigger a card insert when you connect it!");
|
||||
if (tree_open) {
|
||||
if (override.is_active()) {
|
||||
ImGui::TextDisabled("Disabled - override is in use.");
|
||||
} else {
|
||||
// card path
|
||||
std::string hint = "using .\\card" + to_string(player) + ".txt (default)";
|
||||
ImGui::SetNextItemWidth(TEXT_INPUT_WIDTH);
|
||||
if (ImGui::InputTextWithHint("File Path", hint.c_str(),
|
||||
this->keypads_card_path[player], sizeof(this->keypads_card_path[0]) - 1))
|
||||
{
|
||||
bindings.card_paths[player] = this->keypads_card_path[player];
|
||||
bindings_updated = true;
|
||||
}
|
||||
|
||||
// card path file selector
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Open...")) {
|
||||
// help marker
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Leave this empty to use the card file in your game directory.\n"
|
||||
"Hint: You can place 'card0.txt' (P1) / 'card1.txt' (P2) into the root of your USB "
|
||||
"flash drive and it will trigger a card insert when you connect it!");
|
||||
|
||||
// standalone version opens native file browser
|
||||
if (cfg::CONFIGURATOR_STANDALONE && !keypads_card_select) {
|
||||
// card path file selector
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Open...")) {
|
||||
|
||||
// run in separate thread otherwise we get a crash
|
||||
keypads_card_select_done = false;
|
||||
keypads_card_select = new std::thread([this, bindings, player, game] {
|
||||
// standalone version opens native file browser
|
||||
if (cfg::CONFIGURATOR_STANDALONE && !keypads_card_select) {
|
||||
|
||||
// open dialog to get path
|
||||
auto ofn_path = std::make_unique<wchar_t[]>(512);
|
||||
ofn_path[0] = L'\0';
|
||||
OPENFILENAMEW ofn {};
|
||||
ofn.lStructSize = sizeof(ofn);
|
||||
ofn.lpstrFile = ofn_path.get();
|
||||
ofn.nMaxFile = 512;
|
||||
ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR;
|
||||
ofn.lpstrDefExt = L"txt";
|
||||
ofn.lpstrInitialDir = L".";
|
||||
// run in separate thread otherwise we get a crash
|
||||
keypads_card_select_done = false;
|
||||
keypads_card_select = new std::thread([this, bindings, player, game] {
|
||||
|
||||
// check for success
|
||||
auto guard = rawinput::set_os_window_focus_guard();
|
||||
if (GetSaveFileNameW(&ofn)) {
|
||||
// open dialog to get path
|
||||
auto ofn_path = std::make_unique<wchar_t[]>(512);
|
||||
ofn_path[0] = L'\0';
|
||||
OPENFILENAMEW ofn {};
|
||||
ofn.lStructSize = sizeof(ofn);
|
||||
ofn.lpstrFile = ofn_path.get();
|
||||
ofn.nMaxFile = 512;
|
||||
ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR;
|
||||
ofn.lpstrDefExt = L"txt";
|
||||
ofn.lpstrInitialDir = L".";
|
||||
|
||||
// update card path
|
||||
auto new_bindings = bindings;
|
||||
new_bindings.card_paths[player] = std::filesystem::path(ofn_path.get());
|
||||
::Config::getInstance().updateBinding(game, new_bindings);
|
||||
eamuse_update_keypad_bindings();
|
||||
// check for success
|
||||
auto guard = rawinput::set_os_window_focus_guard();
|
||||
if (GetSaveFileNameW(&ofn)) {
|
||||
|
||||
read_card(player);
|
||||
} else {
|
||||
auto error = CommDlgExtendedError();
|
||||
if (error) {
|
||||
log_warning("cfg", "failed to get save file name: {}", error);
|
||||
} else {
|
||||
log_warning("cfg", "failed to get save file name");
|
||||
}
|
||||
// update card path
|
||||
auto new_bindings = bindings;
|
||||
new_bindings.card_paths[player] = std::filesystem::path(ofn_path.get());
|
||||
::Config::getInstance().updateBinding(game, new_bindings);
|
||||
eamuse_update_keypad_bindings();
|
||||
|
||||
read_card(player);
|
||||
} else {
|
||||
auto error = CommDlgExtendedError();
|
||||
if (error) {
|
||||
log_warning("cfg", "failed to get save file name: {}", error);
|
||||
} else {
|
||||
log_warning("cfg", "failed to get save file name");
|
||||
}
|
||||
}
|
||||
|
||||
// clean up
|
||||
keypads_card_select_done = true;
|
||||
});
|
||||
}
|
||||
|
||||
// clean up
|
||||
keypads_card_select_done = true;
|
||||
});
|
||||
}
|
||||
|
||||
// in-game version opens ImGui file browser
|
||||
if (!cfg::CONFIGURATOR_STANDALONE && !this->keypads_card_select_browser[player].IsOpened()) {
|
||||
this->keypads_card_select_browser[player].SetTitle("Card Select");
|
||||
this->keypads_card_select_browser[player].SetTypeFilters({".txt", "*"});
|
||||
// this->keypads_card_select_browser[player].flags_ |= ImGuiFileBrowserFlags_EnterNewFilename;
|
||||
this->keypads_card_select_browser[player].Open();
|
||||
}
|
||||
}
|
||||
|
||||
// clear button
|
||||
if (!bindings.card_paths[player].empty()) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear")) {
|
||||
bindings.card_paths[player] = "";
|
||||
bindings_updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// verify card number
|
||||
auto card_valid = true;
|
||||
if (this->keypads_card_number[player][0] != 0) {
|
||||
for (int n = 0; n < 16; n++) {
|
||||
char c = this->keypads_card_number[player][n];
|
||||
bool digit = c >= '0' && c <= '9';
|
||||
bool character_big = c >= 'A' && c <= 'F';
|
||||
bool character_small = c >= 'a' && c <= 'f';
|
||||
if (!digit && !character_big && !character_small) {
|
||||
card_valid = false;
|
||||
// in-game version opens ImGui file browser
|
||||
if (!cfg::CONFIGURATOR_STANDALONE && !this->keypads_card_select_browser[player].IsOpened()) {
|
||||
this->keypads_card_select_browser[player].SetTitle("Card Select");
|
||||
this->keypads_card_select_browser[player].SetTypeFilters({".txt", "*"});
|
||||
// this->keypads_card_select_browser[player].flags_ |= ImGuiFileBrowserFlags_EnterNewFilename;
|
||||
this->keypads_card_select_browser[player].Open();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Edit")) {
|
||||
std::filesystem::path path;
|
||||
if (!bindings.card_paths[player].empty()) {
|
||||
path = bindings.card_paths[player];
|
||||
} else {
|
||||
path = (player > 0) ? "card1.txt" : "card0.txt";
|
||||
}
|
||||
// using notepad here in case the file doesn't exist
|
||||
launch_shell("notepad.exe", path.string().c_str());
|
||||
}
|
||||
|
||||
// clear button
|
||||
if (!bindings.card_paths[player].empty()) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear")) {
|
||||
bindings.card_paths[player] = "";
|
||||
bindings_updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// verify card number
|
||||
this->keypads_card_file_contents_valid[player] = true;
|
||||
if (this->keypads_card_number[player][0] != 0) {
|
||||
this->keypads_card_file_contents_valid[player] =
|
||||
validate_ea_card(this->keypads_card_number[player]);
|
||||
}
|
||||
|
||||
// card number box
|
||||
ImGui::PushStyleColor(
|
||||
ImGuiCol_Text,
|
||||
this->keypads_card_file_contents_valid[player] ? ImVec4(1.f, 1.f, 1.f, 1.f) :
|
||||
ImVec4(1.f, 0.f, 0.f, 1.f));
|
||||
ImGui::SetNextItemWidth(TEXT_INPUT_WIDTH);
|
||||
ImGui::BeginDisabled();
|
||||
if (this->keypads_card_number[player][0] != 0) {
|
||||
ImGui::Text("Card from file: %s", this->keypads_card_number[player]);
|
||||
} else {
|
||||
ImGui::TextUnformatted("Failed to read file.");
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
}
|
||||
|
||||
// card number box
|
||||
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", "E004010000000000",
|
||||
this->keypads_card_number[player], sizeof(this->keypads_card_number[0]) - 1,
|
||||
ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsHexadecimal);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
// write card after edit
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
write_card(player);
|
||||
read_card(1 - player);
|
||||
}
|
||||
|
||||
// help marker
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Click on Generate button to randomize a valid card number and automatically it save to specified file.");
|
||||
|
||||
// generate button
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Generate")) {
|
||||
// don't know why this file insists on using 18 chars to store the card ID
|
||||
char new_card[17];
|
||||
generate_ea_card(new_card);
|
||||
strcpy_s(this->keypads_card_number[player], new_card);
|
||||
write_card(player);
|
||||
read_card(1 - player);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
// render card select browser
|
||||
@@ -3881,12 +4075,14 @@ namespace overlay::windows {
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::SeparatorText("NFC card reader status");
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "NFC card reader status");
|
||||
ImGui::Spacing();
|
||||
if (cfg::CONFIGURATOR_STANDALONE) {
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextWrapped("Test NFC card readers and card insertions over API");
|
||||
ImGui::TextWrapped("Test NFC card readers and card insertions over API.");
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Enable card readers in Advanced tab, under Card Readers section, and restart. "
|
||||
@@ -3897,31 +4093,35 @@ namespace overlay::windows {
|
||||
// show scanned card numbers
|
||||
for (int player = 0; player < 2; player++) {
|
||||
ImGui::PushID(("CardReaderDisp" + to_string(player)).c_str());
|
||||
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "Last card detected for player %i", player + 1);
|
||||
if (ImGui::TreeNodeEx(
|
||||
fmt::format("Last card detected for player {}", player + 1).c_str(),
|
||||
ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
|
||||
char card_uid[8];
|
||||
const bool card_present = eamuse_scanned_card_peek_noninvasive(player, card_uid);
|
||||
if (card_present) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
const auto card_str = bin2hex(card_uid, 8);
|
||||
ImGui::Text(
|
||||
"%s %s %s %s",
|
||||
card_str.substr(0, 4).c_str(),
|
||||
card_str.substr(4, 4).c_str(),
|
||||
card_str.substr(8, 4).c_str(),
|
||||
card_str.substr(12, 4).c_str()
|
||||
);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Copy")) {
|
||||
clipboard::copy_text(card_str);
|
||||
char card_uid[8];
|
||||
const bool card_present = eamuse_scanned_card_peek_noninvasive(player, card_uid);
|
||||
if (card_present) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
const auto card_str = bin2hex(card_uid, 8);
|
||||
ImGui::Text(
|
||||
"%s %s %s %s",
|
||||
card_str.substr(0, 4).c_str(),
|
||||
card_str.substr(4, 4).c_str(),
|
||||
card_str.substr(8, 4).c_str(),
|
||||
card_str.substr(12, 4).c_str()
|
||||
);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Copy")) {
|
||||
clipboard::copy_text(card_str);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear")) {
|
||||
eamuse_scanned_card_clear(player);
|
||||
}
|
||||
} else {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextDisabled("Card not present");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear")) {
|
||||
eamuse_scanned_card_clear(player);
|
||||
}
|
||||
} else {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextDisabled("Card not present");
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
@@ -3938,7 +4138,9 @@ namespace overlay::windows {
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::SeparatorText("More tips");
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "More tips");
|
||||
ImGui::Spacing();
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::TextWrapped("To debug card reader issues, run spice.exe -cfg in command line and check the log.");
|
||||
@@ -3948,6 +4150,19 @@ namespace overlay::windows {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
bool Config::validate_ea_card(char card_number[16]) {
|
||||
for (int n = 0; n < 16; n++) {
|
||||
char c = card_number[n];
|
||||
bool digit = c >= '0' && c <= '9';
|
||||
bool character_big = c >= 'A' && c <= 'F';
|
||||
bool character_small = c >= 'a' && c <= 'f';
|
||||
if (!digit && !character_big && !character_small) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Config::build_option_value_picker_title(const OptionDefinition& definition) {
|
||||
// need to make these all unique since they are also used as ID
|
||||
// if not unique, append ## per ImGui rules to create unique ones
|
||||
@@ -4213,17 +4428,19 @@ namespace overlay::windows {
|
||||
|
||||
// render table
|
||||
// tables must share the same ID to have synced column settings
|
||||
if (ImGui::BeginTable("OptionsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
|
||||
ImGui::TableSetupColumn("Option", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn(
|
||||
"CMD Line Parameter",
|
||||
ImGuiTableColumnFlags_WidthFixed,
|
||||
overlay::apply_scaling(216));
|
||||
ImGui::TableSetupColumn(
|
||||
"Setting",
|
||||
ImGuiTableColumnFlags_WidthFixed,
|
||||
overlay::apply_scaling(240));
|
||||
|
||||
const int num_columns = (filter != nullptr) ? 3 : 2;
|
||||
if (ImGui::BeginTable("OptionsTable", num_columns, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
|
||||
auto widget_col_width = 0;
|
||||
if (filter != nullptr) {
|
||||
ImGui::TableSetupColumn("Category", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(160));
|
||||
ImGui::TableSetupColumn("Option", ImGuiTableColumnFlags_WidthStretch);
|
||||
widget_col_width = overlay::apply_scaling(220);
|
||||
} else {
|
||||
ImGui::TableSetupColumn("Option", ImGuiTableColumnFlags_WidthStretch);
|
||||
widget_col_width = overlay::apply_scaling(264);
|
||||
}
|
||||
ImGui::TableSetupColumn("Setting", ImGuiTableColumnFlags_WidthFixed, widget_col_width);
|
||||
|
||||
// iterate options
|
||||
options_count = 0;
|
||||
for (auto &option : *options) {
|
||||
@@ -4271,10 +4488,24 @@ namespace overlay::windows {
|
||||
ImGui::PushID(&option);
|
||||
ImGui::TableNextRow();
|
||||
|
||||
// category (only shown for search results)
|
||||
if (filter != nullptr) {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
if (definition.category.empty()) {
|
||||
ImGui::TextDisabled("-");
|
||||
} else {
|
||||
ImGui::TextDisabled(definition.category.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// option name
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Indent(INDENT);
|
||||
if (filter == nullptr) {
|
||||
// add indent to make options align under category header
|
||||
ImGui::Indent(INDENT);
|
||||
}
|
||||
if (option.is_active()) {
|
||||
// active option
|
||||
if (option.disabled || definition.disabled) {
|
||||
@@ -4290,21 +4521,22 @@ namespace overlay::windows {
|
||||
// normal text
|
||||
ImGui::TextUnformatted(definition.title.c_str());
|
||||
}
|
||||
ImGui::Unindent(INDENT);
|
||||
if (filter == nullptr) {
|
||||
ImGui::Unindent(INDENT);
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
|
||||
ImGui::HelpTooltip(definition.desc.c_str());
|
||||
}
|
||||
|
||||
// command line parameter
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
std::string param = "-";
|
||||
if (definition.display_name.empty()) {
|
||||
param += definition.name;
|
||||
} else {
|
||||
param += definition.display_name;
|
||||
}
|
||||
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.f), "%s", param.c_str());
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.27f, 0.27f, 0.27f, 1.f), " %s", param.c_str());
|
||||
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
|
||||
const auto help =
|
||||
param +
|
||||
@@ -4325,6 +4557,10 @@ namespace overlay::windows {
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
|
||||
}
|
||||
if (definition.type != OptionType::Bool) {
|
||||
// take up width of column, minus some room for clear button and Pick button
|
||||
ImGui::SetNextItemWidth(widget_col_width - overlay::apply_scaling(70));
|
||||
}
|
||||
switch (definition.type) {
|
||||
case OptionType::Bool: {
|
||||
bool state = !option.value.empty();
|
||||
@@ -4404,7 +4640,7 @@ namespace overlay::windows {
|
||||
break;
|
||||
}
|
||||
case OptionType::Text: {
|
||||
char buffer[512];
|
||||
char buffer[OPTION_INPUT_TEXT_WIDTH];
|
||||
strncpy(buffer, option.value.c_str(), sizeof(buffer) - 1);
|
||||
buffer[sizeof(buffer) - 1] = '\0';
|
||||
|
||||
@@ -4499,7 +4735,7 @@ namespace overlay::windows {
|
||||
if (option.disabled && !definition.disabled) {
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"This option can not be edited because it was overriden by command-line options.\n"
|
||||
"This option cannot be edited because it was overriden by command-line options.\n"
|
||||
"Run spicecfg.exe to configure the options and then run spice(64).exe directly.");
|
||||
}
|
||||
|
||||
@@ -4541,6 +4777,9 @@ namespace overlay::windows {
|
||||
}
|
||||
}
|
||||
|
||||
// row hover detection (invisible selectable that spans entire row)
|
||||
ImGui::InvisibleTableRowSelectable();
|
||||
|
||||
// next item
|
||||
ImGui::PopID();
|
||||
}
|
||||
@@ -5053,6 +5292,7 @@ namespace overlay::windows {
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::InvisibleTableRowSelectable();
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,8 @@ namespace overlay::windows {
|
||||
bool keypads_card_select_done = false;
|
||||
ImGui::FileBrowser keypads_card_select_browser[2];
|
||||
char keypads_card_number[2][18] {};
|
||||
bool keypads_card_override_valid[2] = { false, false };
|
||||
bool keypads_card_file_contents_valid[2] = { false, false };
|
||||
|
||||
// presets tab
|
||||
std::vector<ControllerTemplate> templates_cache;
|
||||
@@ -135,6 +137,8 @@ namespace overlay::windows {
|
||||
std::thread *file_picker_thread = nullptr;
|
||||
bool file_picker_done = false;
|
||||
|
||||
std::pair<std::string, int> analog_as_button_warning_show_next_frame = { "", 0 };
|
||||
|
||||
void build_buttons(const std::string &name, std::vector<Button> *buttons, int min = 0, int max = -1);
|
||||
void build_button(
|
||||
const std::string &name,
|
||||
@@ -195,12 +199,13 @@ namespace overlay::windows {
|
||||
|
||||
void set_alternating_row_colors(const int row_index);
|
||||
|
||||
bool validate_ea_card(char card_number[16]);
|
||||
|
||||
public:
|
||||
Config(SpiceOverlay *overlay);
|
||||
~Config() override;
|
||||
|
||||
void read_card(int player = -1);
|
||||
void write_card(int player);
|
||||
void build_content() override;
|
||||
|
||||
};
|
||||
|
||||
@@ -844,6 +844,10 @@ namespace overlay::windows {
|
||||
ImGui::TextUnformatted(patch_checked ? "ON" : "off");
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
// row hover detection (invisible selectable that spans entire row)
|
||||
ImGui::InvisibleTableRowSelectable();
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
1 VERSIONINFO
|
||||
FILEVERSION 1,0,0,0
|
||||
PRODUCTVERSION 1,0,0,0
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904E4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "SpiceTools"
|
||||
VALUE "FileDescription", "SpiceTools DLL Stub"
|
||||
VALUE "FileVersion", "1.0.0.0"
|
||||
VALUE "ProductName", "SpiceTools DLL Stub"
|
||||
VALUE "ProductVersion", "1.0.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x0409, 1252
|
||||
END
|
||||
END
|
||||
@@ -9,6 +9,16 @@
|
||||
#include "peb.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/dependencies.h"
|
||||
#include "util/unique_plain_ptr.h"
|
||||
|
||||
#define DEBUG_VERBOSE 0
|
||||
|
||||
#if DEBUG_VERBOSE
|
||||
#define log_debug(module, format_str, ...) logger::push( \
|
||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
||||
#else
|
||||
#define log_debug(module, format_str, ...)
|
||||
#endif
|
||||
|
||||
std::filesystem::path libutils::module_file_name(HMODULE module) {
|
||||
std::wstring buf;
|
||||
@@ -361,11 +371,106 @@ void libutils::check_duplicate_dlls() {
|
||||
void libutils::warn_if_dll_exists(const std::string &file_name) {
|
||||
if (fileutils::file_exists(MODULE_PATH / file_name)) {
|
||||
log_info("libutils", "found user-supplied {} in modules directory", file_name);
|
||||
libutils::print_dll_info(MODULE_PATH / file_name);
|
||||
return;
|
||||
}
|
||||
const auto &spice_bin_path = libutils::module_file_name(nullptr).parent_path();
|
||||
if (fileutils::file_exists(spice_bin_path / file_name)) {
|
||||
log_info("libutils", "found user-supplied {} next to spice executable path", file_name);
|
||||
libutils::print_dll_info(spice_bin_path / file_name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void libutils::print_dll_info(std::filesystem::path filename) {
|
||||
DWORD handle;
|
||||
const auto size = GetFileVersionInfoSizeA(filename.string().c_str(), &handle);
|
||||
if (size == 0) {
|
||||
log_debug(
|
||||
"libutils",
|
||||
"GetFileVersionInfoSizeA failed for {}: {}",
|
||||
filename.filename().string(),
|
||||
get_last_error_string());
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = util::make_unique_plain<VOID>(size);
|
||||
if (!GetFileVersionInfoA(filename.string().c_str(), handle, size, data.get())) {
|
||||
log_debug(
|
||||
"libutils",
|
||||
"GetFileVersionInfoA failed for {}: {}",
|
||||
filename.filename().string(),
|
||||
get_last_error_string());
|
||||
return;
|
||||
}
|
||||
|
||||
struct LANGANDCODEPAGE {
|
||||
WORD wLanguage;
|
||||
WORD wCodePage;
|
||||
} *lpTranslate = nullptr;
|
||||
|
||||
UINT cbTranslate = 0;
|
||||
if (!VerQueryValueA(data.get(), "\\VarFileInfo\\Translation", (LPVOID*)&lpTranslate, &cbTranslate)) {
|
||||
log_debug(
|
||||
"libutils",
|
||||
"VerQueryValueA failed for {}: {}",
|
||||
filename.filename().string(),
|
||||
get_last_error_string());
|
||||
return;
|
||||
}
|
||||
if (cbTranslate == 0 || lpTranslate == nullptr) {
|
||||
log_debug(
|
||||
"libutils",
|
||||
"VerQueryValueA returned invalid results for {}",
|
||||
filename.filename().string());
|
||||
return;
|
||||
}
|
||||
|
||||
// pick language - prefer English
|
||||
WORD lang = 0, codepage = 0;
|
||||
for (UINT i = 0; i < cbTranslate / sizeof(*lpTranslate); i++) {
|
||||
if (lpTranslate[i].wLanguage == 0x0409) { // en-us
|
||||
lang = lpTranslate[i].wLanguage;
|
||||
codepage = lpTranslate[i].wCodePage;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lang == 0) {
|
||||
lang = lpTranslate[0].wLanguage;
|
||||
codepage = lpTranslate[0].wCodePage;
|
||||
}
|
||||
|
||||
// StringFileInfo helper
|
||||
auto query_string = [&](const char* key) -> std::string {
|
||||
std::string subBlock = fmt::format(
|
||||
"\\StringFileInfo\\{:04x}{:04x}\\{}",
|
||||
lang, codepage, key
|
||||
);
|
||||
|
||||
char* value = nullptr;
|
||||
UINT size_out = 0;
|
||||
|
||||
if (VerQueryValueA(data.get(), subBlock.c_str(), (LPVOID*)&value, &size_out) && value) {
|
||||
return value;
|
||||
}
|
||||
log_debug(
|
||||
"libutils",
|
||||
"VerQueryValueA({}) failed for {}: {}",
|
||||
subBlock,
|
||||
filename.filename().string(),
|
||||
get_last_error_string());
|
||||
return "";
|
||||
};
|
||||
|
||||
const auto company_name = query_string("CompanyName");
|
||||
const auto product_name = query_string("ProductName");
|
||||
const auto version_str = query_string("FileVersion");
|
||||
|
||||
log_info(
|
||||
"libutils",
|
||||
"DLL info for {}: CompanyName = {}, ProductName = {}, Version = {}",
|
||||
filename.filename().string(),
|
||||
company_name.empty() ? "?" : company_name,
|
||||
product_name.empty() ? "?" : product_name,
|
||||
version_str.empty() ? "?" : version_str);
|
||||
}
|
||||
|
||||
@@ -77,4 +77,6 @@ namespace libutils {
|
||||
intptr_t rva2offset(const std::filesystem::path &path, intptr_t rva);
|
||||
intptr_t offset2rva(IMAGE_NT_HEADERS *nt_headers, intptr_t offset);
|
||||
intptr_t offset2rva(const std::filesystem::path &path, intptr_t offset);
|
||||
|
||||
void print_dll_info(std::filesystem::path filename);
|
||||
}
|
||||
|
||||
@@ -62,9 +62,9 @@ void show_popup_for_crash() {
|
||||
text += "Game has crashed.\n\n";
|
||||
text += "Check log.txt and look for error messages near the end of file.\n\n";
|
||||
text += "Unsure what to do next?\n";
|
||||
text += " * update spice2x to the latest version\n";
|
||||
text += " * check the FAQ on spice2x github wiki\n";
|
||||
text += " * do NOT screenshot this, instead, share log.txt with someone and ask for help\n\n";
|
||||
text += " * Update spice2x to the latest version\n";
|
||||
text += " * Check the FAQ on spice2x github wiki\n";
|
||||
text += " * Do NOT screenshot this, instead, share log.txt with someone and ask for help\n\n";
|
||||
text += "Press Enter, Esc, Alt+F4, or click OK to exit. Otherwise, game will close in 30 seconds.";
|
||||
show_popup(text);
|
||||
}
|
||||
@@ -76,9 +76,9 @@ void show_popup_for_fatal_error(std::string message) {
|
||||
text += message;
|
||||
text += "----------------------------------------------------------\n\n";
|
||||
text += "Unsure what to do next?\n";
|
||||
text += " * update spice2x to the latest version\n";
|
||||
text += " * check the FAQ on spice2x github wiki\n";
|
||||
text += " * do NOT screenshot this, instead, share log.txt with someone and ask for help\n\n";
|
||||
text += " * Update spice2x to the latest version\n";
|
||||
text += " * Check the FAQ on spice2x github wiki\n";
|
||||
text += " * Do NOT screenshot this, instead, share log.txt with someone and ask for help\n\n";
|
||||
text += "Press Enter, Esc, Alt+F4, or click OK to exit. Otherwise, game will close in 30 seconds.";
|
||||
show_popup(text);
|
||||
}
|
||||
Reference in New Issue
Block a user