// // Created by beerpsi on 4/14/2024. // #include #include #include #include #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& names = games::get_games(); for (const std::string& game_name : names) { this->game_names.push_back(game_name.c_str()); std::vector* 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() { this->aime_gen = GetPrivateProfileIntA( "aime", "aimeGen", 1, ".\\segatools.ini" ); 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(); } this->felica_gen = GetPrivateProfileIntA( "aime", "felicaGen", 0, ".\\segatools.ini" ); 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() { WritePrivateProfileStringA( "aime", "aimeGen", this->aime_gen ? "1" : "0", ".\\segatools.ini" ); 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(); } } WritePrivateProfileStringA( "aime", "felicaGen", this->felica_gen ? "1" : "0", ".\\segatools.ini" ); 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 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 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(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"); 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; } 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"); 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; } 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