Files
ppsspp/UI/Settings/NetworkingPage.cpp
T
Henrik RydgårdandClaude Opus 5.5 a5b470b2c3 Settings: Move the settings tabs' builders to a file each in UI/Settings
Only moves code. The tabs other than System were built by static functions
in GameSettingsScreen.cpp, in place of the old methods, to keep the previous
change's diff readable. Now each tab has its own file, with just the
includes it needs, and MainSettingsPages.h declares their builders.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 16:01:34 +01:00

271 lines
13 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// This program 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, version 2.0 or later versions.
// This program 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <string>
#include "Common/Data/Text/I18n.h"
#include "Common/File/Path.h"
#include "Common/StringUtils.h"
#include "Common/System/Request.h"
#include "Common/System/System.h"
#include "Common/UI/Notice.h"
#include "Common/UI/PopupScreens.h"
#include "Common/UI/ScreenManager.h"
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
#include "Core/Config.h"
#include "Core/HLE/sceNetAdhoc.h"
#include "Core/System.h"
#include "Core/Util/PortManager.h"
#include "UI/AdhocServerScreen.h"
#include "UI/DiscordIntegration.h"
#include "UI/SettingsPage.h"
#include "UI/Settings/MainSettingsPages.h"
// Compound view just like the audio file choosers
class MacAddressChooser : public UI::LinearLayout {
public:
MacAddressChooser(RequesterToken token, Path gamePath, std::string *value, std::string_view title, ScreenManager *screenManager, UI::LayoutParams *layoutParams = nullptr);
};
MacAddressChooser::MacAddressChooser(RequesterToken token, Path gamePath, std::string *value, std::string_view title, ScreenManager *screenManager, UI::LayoutParams *layoutParams) : UI::LinearLayout(ORIENT_HORIZONTAL, layoutParams) {
using namespace UI;
SetSpacing(5.0f);
if (!layoutParams) {
layoutParams_->width = FILL_PARENT;
layoutParams_->height = ITEM_HEIGHT;
}
auto n = GetI18NCategory(I18NCat::NETWORKING);
std::string initialValue = *value;
Add(new PopupTextInputChoice(token, value, title, g_Config.sMACAddress, 17, screenManager, new LinearLayoutParams(1.0f)))->OnChange.Add([=](UI::EventParams &e) {
// Validate the chosen address, and restore to initialValue if bad.
if (g_Config.sMACAddress.size() != 17) {
// TODO: Alert the user
*value = initialValue;
}
});
Add(new Choice(n->T("Randomize"), new LinearLayoutParams(WRAP_CONTENT, ITEM_HEIGHT)))->OnClick.Add([=](UI::EventParams &) {
auto n = GetI18NCategory(I18NCat::NETWORKING);
auto di = GetI18NCategory(I18NCat::DIALOG);
std::string_view confirmMessage = n->T("ChangeMacSaveConfirm", "Generate a new MAC address?");
std::string_view warningMessage = n->T("ChangeMacSaveWarning", "Some games verify the MAC address when loading savedata, so this may break old saves.");
std::string combined = g_Config.sMACAddress + "\n\n" + std::string(confirmMessage) + "\n\n" + std::string(warningMessage);
auto confirmScreen = new MessagePopupScreen(
title,
combined, di->T("Yes"), di->T("No"),
[](bool success) {
if (success) {
g_Config.sMACAddress = CreateRandMAC();
}
});
screenManager->push(confirmScreen);
});
}
// The nickname is shown on both the Networking and System tabs.
UI::View *AddNicknameChoice(SettingsPageHost *host, UI::ViewGroup *parent) {
using namespace UI;
auto sy = GetI18NCategory(I18NCat::SYSTEM);
PopupTextInputChoice *nickname = parent->Add(new PopupTextInputChoice(host->PageRequesterToken(), &g_Config.sNickName, sy->T("Nickname"), "", 32, host->PageScreenManager()));
if (g_Config.sNickName == "PPSSPP") {
nickname->SetIconRight(ImageID("I_WARNING"));
}
nickname->OnChange.Add([host](UI::EventParams &e) {
// Copy to infrastructure name if valid and not already set.
if (g_Config.sInfrastructureUsername.empty()) {
if (g_Config.sNickName == SanitizeString(g_Config.sNickName, StringRestriction::AlphaNumDashUnderscore, 3, 16)) {
g_Config.sInfrastructureUsername = g_Config.sNickName;
}
}
// Updates the warning icon, and the username notice on the Networking tab.
host->RecreatePageViews();
});
return nickname;
}
void BuildNetworkingSettings(SettingsPage *page) {
using namespace UI;
SettingsPageHost *host = page->Host();
page->Header({ I18NCat::MAINSETTINGS, "Networking" });
page->Choice("Quick start guide for multiplayer", ImageID("I_LINK_OUT")).OnClick([](EventParams &e) {
auto n = GetI18NCategory(I18NCat::NETWORKING);
std::string url(n->T("MultiplayerQuickStartURL", "https://www.ppsspp.org/docs/multiplayer/quickstart/"));
System_LaunchUrl(LaunchUrlType::BROWSER_URL, url);
});
// Ad hoc play, the built-in server and UPnP all talk to the local network.
auto requestLocalNetwork = [](UI::EventParams &e) {
CheckBox *checkBox = static_cast<CheckBox *>(e.v);
if (checkBox->Toggled()) {
System_RequestLocalNetworkPermission();
}
};
page->Setting(&g_Config.bEnableWlan).OnChange(requestLocalNetwork);
page->Custom("MAC address", [host](UI::ViewGroup *parent) -> UI::View * {
return parent->Add(new MacAddressChooser(host->PageRequesterToken(), host->PageGamePath(), &g_Config.sMACAddress, T(I18NCat::NETWORKING, "MAC address"), host->PageScreenManager()));
});
page->Header("Ad Hoc multiplayer");
page->Custom("Ad hoc server address", [host](UI::ViewGroup *parent) -> UI::View * {
auto n = GetI18NCategory(I18NCat::NETWORKING);
auto launchAdhocServerScreen = [host](UI::EventParams &) {
auto n = GetI18NCategory(I18NCat::NETWORKING);
host->PageScreenManager()->push(new AdhocServerScreen(&g_Config.sProAdhocServer, n->T("Ad hoc server address")));
};
if (AdhocServerNameIsCustom()) {
LinearLayout *serverRow = parent->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
serverRow->SetSpacing(0.0f);
serverRow->Add(new ChoiceWithValueDisplay(&g_Config.sProAdhocServer, n->T("Ad hoc server address"), I18NCat::NONE, new LinearLayoutParams(1.0f)))->OnClick.Add(launchAdhocServerScreen);
serverRow->Add(new Choice(ImageID("I_EDIT_TEXT"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT)))->OnClick.Add([host](UI::EventParams &) {
AskToEditCurrentServer(host->PageRequesterToken(), host->PageScreenManager());
});
return serverRow;
}
AdhocServerListEntry entry;
if (!g_Config.sProAdhocServer.empty() && AdhocGetServerByHost(g_Config.sProAdhocServer, &entry)) {
Choice *choice = parent->Add(new ChoiceWithFixedValueDisplay(ApplySafeSubstitutions("%1 (%2)", entry.name, entry.host), n->T("Ad hoc server address")));
choice->OnClick.Add(launchAdhocServerScreen);
return choice;
}
// This case happens when updating from a previous version, as well as clicking on one of the local addresses
Choice *choice = parent->Add(new ChoiceWithFixedValueDisplay(g_Config.sProAdhocServer, n->T("Ad hoc server address")));
choice->OnClick.Add(launchAdhocServerScreen);
return choice;
});
AdhocServerListEntry serverEntry;
if (!AdhocServerNameIsCustom() && !g_Config.sProAdhocServer.empty() && AdhocGetServerByHost(g_Config.sProAdhocServer, &serverEntry)) {
page->Custom("Server status: %1", [host, serverEntry](UI::ViewGroup *parent) -> UI::View * {
Choice *status = parent->Add(new Choice(ApplySafeSubstitutions(T(I18NCat::NETWORKING, "Server status: %1"), serverEntry.name)));
status->SetIconLeft(ImageID("I_INFO"));
status->OnClick.Add([host, serverEntry](UI::EventParams &) {
host->PageScreenManager()->push(new AdhocServerInfoScreen(serverEntry));
});
return status;
});
}
page->Setting(&g_Config.iAdhocServerRelayMode).EnabledFunc([] {
return !PSP_IsInited();
});
page->Hint({ I18NCat::NETWORKING, "PacketRelayHint", "Available on servers that provide 'aemu_postoffice' packet relay, like socom.cc. Disable this for LAN or VPN play. Can be more reliable, but sometimes slower." });
page->Custom({ I18NCat::SYSTEM, "Nickname" }, [host](UI::ViewGroup *parent) {
return AddNicknameChoice(host, parent);
});
page->Hint("Try to pick a unique nickname for ad hoc play");
page->Setting(&g_Config.bEnableAdhocServer).OnChange(requestLocalNetwork);
page->Setting(&g_Config.bAdhocUseUdp).EnabledFunc([] {
return !PSP_IsInited();
});
page->Hint("Try switching between TCP/UDP if you suffer frequent adhoc server disconnects. Some network environments favor TCP and some UDP.");
page->Setting(&g_Config.bAdhocRelayModeEnetPdpReliable).EnabledFunc([] {
return !PSP_IsInited() && g_Config.bAdhocUseUdp;
});
page->Hint("When disabled, packet loss on datagram adhoc sockets will be exposed to game's netcode. Keep this enabled if you don't know what that means.");
page->Header("Infrastructure");
if (g_Config.sInfrastructureUsername.empty()) {
page->Custom("To play in Infrastructure Mode, you must enter a username", [](UI::ViewGroup *parent) -> UI::View * {
NoticeView *notice = parent->Add(new NoticeView(NoticeLevel::WARN, T(I18NCat::NETWORKING, "To play in Infrastructure Mode, you must enter a username"), ""));
notice->SetWrapText(true);
return notice;
});
}
page->Custom({ I18NCat::DIALOG, "Username" }, [host](UI::ViewGroup *parent) -> UI::View * {
PopupTextInputChoice *usernameChoice = parent->Add(new PopupTextInputChoice(host->PageRequesterToken(), &g_Config.sInfrastructureUsername, T(I18NCat::DIALOG, "Username"), "", 16, host->PageScreenManager()));
usernameChoice->SetRestriction(StringRestriction::AlphaNumDashUnderscore, 3);
usernameChoice->OnChange.Add([host](UI::EventParams &e) {
host->RecreatePageViews();
});
return usernameChoice;
});
page->Setting(&g_Config.bInfrastructureAutoDNS);
page->Custom("DNS server", [host](UI::ViewGroup *parent) -> UI::View * {
return parent->Add(new PopupTextInputChoice(host->PageRequesterToken(), &g_Config.sInfrastructureDNSServer, T(I18NCat::NETWORKING, "DNS server"), "", 32, host->PageScreenManager()));
}).Disabled(&g_Config.bInfrastructureAutoDNS);
page->Header("UPnP (port-forwarding)");
// Only togglable outside a game - sceNet latches settings like UPnPUseOriginalPort at boot,
// and a game that's already mapped its ports wouldn't cope with them disappearing.
page->Setting(&g_Config.bEnableUPnP).EnabledFunc([] {
return !PSP_IsInited();
}).OnChange([requestLocalNetwork](UI::EventParams &e) {
requestLocalNetwork(e);
// Wake the UPnP service thread so it connects (or tears its mappings back down) right
// away, instead of waiting for a port request that may never come.
UPnP_Notify();
});
page->Setting(&g_Config.bUPnPUseOriginalPort).Enabled(&g_Config.bEnableUPnP);
page->Hint({ I18NCat::NETWORKING, "UseOriginalPort Tip", "May not work for all devices or games, see wiki." });
page->Header("Chat");
page->Setting(&g_Config.bEnableNetworkChat);
page->Setting(&g_Config.iChatButtonPosition).Enabled(&g_Config.bEnableNetworkChat);
page->Setting(&g_Config.iChatScreenPosition).Enabled(&g_Config.bEnableNetworkChat);
page->Setting(&g_Config.bChatTimestamps).Enabled(&g_Config.bEnableNetworkChat);
page->Header("Quick chat");
page->Setting(&g_Config.bEnableQuickChat).Enabled(&g_Config.bEnableNetworkChat);
for (int i = 0; i < 5; i++) {
page->Custom("Quick chat %1", [host, i](UI::ViewGroup *parent) -> UI::View * {
return parent->Add(new PopupTextInputChoice(host->PageRequesterToken(), &g_Config.sQuickChat[i], ApplySafeSubstitutions(T(I18NCat::NETWORKING, "Quick chat %1"), i + 1), "", 32, host->PageScreenManager()));
}).EnabledFunc([] {
return g_Config.bEnableQuickChat && g_Config.bEnableNetworkChat;
});
}
page->Header({ I18NCat::NETWORKING, "Misc", "Misc (default = compatibility)" });
page->Choice("Open PPSSPP Multiplayer Wiki Page", ImageID("I_LINK_OUT")).OnClick([](EventParams &e) {
auto n = GetI18NCategory(I18NCat::NETWORKING);
std::string url(n->T("MultiplayerHowToURL", "https://github.com/hrydgard/ppsspp/wiki/How-to-play-multiplayer-games-with-PPSSPP"));
System_LaunchUrl(LaunchUrlType::BROWSER_URL, url);
});
static const char *wlanChannels[] = {"Auto", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"};
page->Setting(&g_Config.iWlanAdhocChannel).Setup([](View *view) {
PopupMultiChoice *wlanChannelChoice = static_cast<PopupMultiChoice *>(view);
wlanChannelChoice->SetChoicesUntranslated(1, (int)ARRAY_SIZE(wlanChannels) - 1);
for (int i = 0; i < 4; i++) {
wlanChannelChoice->HideChoice(i + 2);
wlanChannelChoice->HideChoice(i + 7);
}
});
page->Setting(&g_Config.iPortOffset);
page->Hint("PortOffsetHint");
page->Setting(&g_Config.iMinTimeout);
page->Setting(&g_Config.bForcedFirstConnect);
page->Setting(&g_Config.bAllowSpeedControlWhileConnected);
if (Discord::IsAvailable()) {
page->Setting(&g_Config.bDiscordRichPresence);
}
}