/* Copyright 2025 flyinghead This file is part of Flycast. Flycast is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. Flycast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Flycast. If not, see . */ #include "settings.h" #include "gui.h" #include "network/ggpo.h" #include "network/ice.h" #include "imgui_stdlib.h" void gui_settings_network() { ImGuiStyle& style = ImGui::GetStyle(); header("Network Type"); { DisabledScope scope(game_started); int netType = 0; if (config::GGPOEnable) netType = 1; else if (config::NetworkEnable) netType = 2; else if (config::BattleCableEnable) netType = 3; ImGui::Columns(4, "networkType", false); ImGui::RadioButton("Disabled##network", &netType, 0); ImGui::NextColumn(); ImGui::RadioButton("GGPO", &netType, 1); ImGui::SameLine(0, style.ItemInnerSpacing.x); ShowHelpMarker("Enable networking using GGPO"); ImGui::NextColumn(); ImGui::RadioButton("Naomi", &netType, 2); ImGui::SameLine(0, style.ItemInnerSpacing.x); ShowHelpMarker("Enable networking for supported Naomi and Atomiswave games"); ImGui::NextColumn(); ImGui::RadioButton("Battle Cable", &netType, 3); ImGui::SameLine(0, style.ItemInnerSpacing.x); ShowHelpMarker("Emulate the Taisen (Battle) null modem cable for games that support it"); ImGui::Columns(1, nullptr, false); config::GGPOEnable = false; config::NetworkEnable = false; config::BattleCableEnable = false; switch (netType) { case 1: config::GGPOEnable = true; break; case 2: config::NetworkEnable = true; break; case 3: config::BattleCableEnable = true; break; } } if (config::GGPOEnable || config::NetworkEnable || config::BattleCableEnable) { ImGui::Spacing(); header("Configuration"); } { if (config::GGPOEnable) { config::NetworkEnable = false; OptionCheckbox("Play as Player 1", config::ActAsServer, "Deselect to play as player 2"); ImGui::InputText("Peer", &config::NetworkServer.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); ImGui::SameLine(); ShowHelpMarker("Your peer IP address and optional port"); OptionSlider("Frame Delay", config::GGPODelay, 0, 20, "Sets Frame Delay, advisable for sessions with ping >100 ms"); ImGui::Text("Left Thumbstick:"); OptionRadioButton("Disabled##analogaxis", config::GGPOAnalogAxes, 0, "Left thumbstick not used"); ImGui::SameLine(); OptionRadioButton("Horizontal", config::GGPOAnalogAxes, 1, "Use the left thumbstick horizontal axis only"); ImGui::SameLine(); OptionRadioButton("Full", config::GGPOAnalogAxes, 2, "Use the left thumbstick horizontal and vertical axes"); OptionCheckbox("Enable Chat", config::GGPOChat, "Open the chat window when a chat message is received"); if (config::GGPOChat) { OptionCheckbox("Enable Chat Window Timeout", config::GGPOChatTimeoutToggle, "Automatically close chat window after 20 seconds"); if (config::GGPOChatTimeoutToggle) { char chatTimeout[256]; snprintf(chatTimeout, sizeof(chatTimeout), "%d", (int)config::GGPOChatTimeout); ImGui::InputText("Chat Window Timeout (seconds)", chatTimeout, sizeof(chatTimeout), ImGuiInputTextFlags_CharsDecimal, nullptr, nullptr); ImGui::SameLine(); ShowHelpMarker("Sets duration that chat window stays open after new message is received."); config::GGPOChatTimeout.set(atoi(chatTimeout)); } } OptionCheckbox("Network Statistics", config::NetworkStats, "Display network statistics on screen"); } else if (config::NetworkEnable) { OptionCheckbox("Act as Server", config::ActAsServer, "Create a local server for Naomi network games"); if (!config::ActAsServer) { ImGui::InputText("Server", &config::NetworkServer.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); ImGui::SameLine(); ShowHelpMarker("The server to connect to. Leave blank to find a server automatically on the default port"); } char localPort[256]; snprintf(localPort, sizeof(localPort), "%d", (int)config::LocalPort); ImGui::InputText("Local Port", localPort, sizeof(localPort), ImGuiInputTextFlags_CharsDecimal, nullptr, nullptr); ImGui::SameLine(); ShowHelpMarker("The local UDP port to use"); config::LocalPort.set(atoi(localPort)); } else if (config::BattleCableEnable) { #ifdef USE_ICE if (ImGui::BeginTabBar("battleMode", ImGuiTabBarFlags_NoTooltip)) { if (ImGui::BeginTabItem("Match Code")) { ice::State state = ice::getState(); ImGuiInputTextFlags textFlags = state == ice::Offline ? ImGuiInputTextFlags_CharsNoBlank : ImGuiInputTextFlags_ReadOnly; static std::string matchCode; ImGui::InputText("Code", &matchCode, textFlags); ImGui::SameLine(); ShowHelpMarker("Choose a unique word or number and share it with your opponent."); if (state == ice::Offline) { if (ImGui::Button("Connect") && !matchCode.empty()) ice::init(matchCode, true); } else { if (ImGui::Button("Disconnect")) try { ice::term(); } catch (...) {} } std::string status; switch (state) { case ice::Offline: status = ice::getStatusText(); break; case ice::Online: status = "Waiting at meeting point..."; break; case ice::ChalAccepted: status = "Preparing game..."; break; case ice::Playing: status = "Playing " + matchCode + " (" + ice::getStatusText() + ")"; break; default: break; } ImGui::TextDisabled("%s", status.c_str()); OptionCheckbox("Network Statistics", config::NetworkStats, "Display network statistics on screen"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Manual")) { #endif ImGui::InputText("Peer", &config::NetworkServer.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); ImGui::SameLine(); ShowHelpMarker("The peer to connect to. Leave blank to find a player automatically on the default port"); char localPort[256]; snprintf(localPort, sizeof(localPort), "%d", (int)config::LocalPort); ImGui::InputText("Local Port", localPort, sizeof(localPort), ImGuiInputTextFlags_CharsDecimal, nullptr, nullptr); ImGui::SameLine(); ShowHelpMarker("The local UDP port to use"); config::LocalPort.set(atoi(localPort)); #ifdef USE_ICE ImGui::EndTabItem(); } ImGui::EndTabBar(); } #endif OptionCheckbox("Act as Master", config::ActAsServer, "Only used for Maximum Speed. One of the peer must be master."); } } ImGui::Spacing(); header("Network Options"); { OptionCheckbox("Enable UPnP", config::EnableUPnP, "Automatically configure your network router for netplay"); OptionCheckbox("Broadcast Digital Outputs", config::NetworkOutput, "Broadcast digital outputs and force-feedback state on TCP port 8000. " "Compatible with the \"-output network\" MAME option. Arcade games only."); { DisabledScope scope(game_started); OptionCheckbox("Broadband Adapter Emulation", config::EmulateBBA, "Emulate the Ethernet Broadband Adapter (BBA) instead of the Modem"); } OptionCheckbox("Use DCNet", config::UseDCNet, "Use the DCNet cloud service for Dreamcast Internet access."); ImGui::InputText("ISP User Name", &config::ISPUsername.get(), ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CallbackCharFilter, [](ImGuiInputTextCallbackData *data) { return static_cast(data->EventChar <= ' ' || data->EventChar > '~'); }, nullptr); ImGui::SameLine(); ShowHelpMarker("The ISP user name stored in the console Flash RAM. Used by some online games as the player name. Leave blank to keep the current Flash RAM value."); #if !defined(NDEBUG) || defined(DEBUGFAST) { DisabledScope scope(config::UseDCNet); ImGui::InputText("DNS", &config::DNS.get(), ImGuiInputTextFlags_CharsNoBlank); ImGui::SameLine(); ShowHelpMarker("DNS server name or IP address"); } #endif } #ifdef NAOMI_MULTIBOARD ImGui::Spacing(); header("Multiboard Screens"); { //OptionRadioButton("Disabled##multiboard", config::MultiboardSlaves, 0, "Multiboard disabled (when optional)"); OptionRadioButton("1 (Twin)", config::MultiboardSlaves, 1, "One screen configuration (F355 Twin)"); ImGui::SameLine(); OptionRadioButton("3 (Deluxe)", config::MultiboardSlaves, 2, "Three screens configuration"); } #endif }