From 01b06b76c98d41b4cf3e57f9c47d4cb15bb69ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 7 Jan 2025 17:43:02 +0100 Subject: [PATCH] Infrastructure username handling: Add separate username, pick Nickname as default if valid. --- Common/StringUtils.cpp | 32 ++++++++++++++++++++++++++++++++ Common/StringUtils.h | 7 +++++++ Common/UI/PopupScreens.cpp | 5 +++-- Common/UI/PopupScreens.h | 8 ++++++++ Core/Config.cpp | 21 ++++++++++++++++++--- Core/Config.h | 4 +++- Core/ConfigSettings.cpp | 2 +- Core/ConfigSettings.h | 2 +- Core/HLE/sceNp.cpp | 26 ++++++++++++++++++++------ UI/GameSettingsScreen.cpp | 30 ++++++++++-------------------- UI/GameSettingsScreen.h | 1 - 11 files changed, 103 insertions(+), 35 deletions(-) diff --git a/Common/StringUtils.cpp b/Common/StringUtils.cpp index a84a21ca39..3d7ca6f5df 100644 --- a/Common/StringUtils.cpp +++ b/Common/StringUtils.cpp @@ -105,6 +105,38 @@ int countChar(std::string_view haystack, char needle) { return count; } +std::string SanitizeString(std::string_view input, StringRestriction restriction, int maxLength) { + if (restriction == StringRestriction::None) { + return std::string(input); + } + // First, remove any chars not in A-Za-z0-9_-. This will effectively get rid of any Unicode char, emojis etc too. + std::string sanitized; + for (auto c : input) { + switch (restriction) { + case StringRestriction::None: + sanitized.push_back(c); + break; + case StringRestriction::AlphaNumDashUnderscore: + if ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '-' || c == '_') { + // Allowed chars. + sanitized.push_back(c); + } + break; + } + } + + if (maxLength >= 0) { + // TODO: Cut at whole UTF-8 chars! + if (sanitized.size() > maxLength) { + sanitized.resize(maxLength); + } + } + + return sanitized; +} + bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list args) { int writtenCount = vsnprintf(out, outsize, format, args); diff --git a/Common/StringUtils.h b/Common/StringUtils.h index 7f5ffaff31..dd19c6e6a9 100644 --- a/Common/StringUtils.h +++ b/Common/StringUtils.h @@ -71,6 +71,13 @@ inline bool equalsNoCase(std::string_view str, std::string_view key) { bool containsNoCase(std::string_view haystack, std::string_view needle); +enum class StringRestriction { + None, + AlphaNumDashUnderscore, // Used for infrastructure usernames +}; + +std::string SanitizeString(std::string_view username, StringRestriction restriction, int maxLength = -1); + void DataToHexString(const uint8_t *data, size_t size, std::string *output); void DataToHexString(int indent, uint32_t startAddr, const uint8_t* data, size_t size, std::string* output); diff --git a/Common/UI/PopupScreens.cpp b/Common/UI/PopupScreens.cpp index 6a1b0e942b..af484ac4a9 100644 --- a/Common/UI/PopupScreens.cpp +++ b/Common/UI/PopupScreens.cpp @@ -519,7 +519,7 @@ void SliderFloatPopupScreen::OnCompleted(DialogResult result) { } PopupTextInputChoice::PopupTextInputChoice(RequesterToken token, std::string *value, std::string_view title, std::string_view placeholder, int maxLen, ScreenManager *screenManager, LayoutParams *layoutParams) - : AbstractChoiceWithValueDisplay(title, layoutParams), screenManager_(screenManager), value_(value), placeHolder_(placeholder), maxLen_(maxLen), token_(token) { + : AbstractChoiceWithValueDisplay(title, layoutParams), screenManager_(screenManager), value_(value), placeHolder_(placeholder), maxLen_(maxLen), token_(token), restriction_(StringRestriction::None) { OnClick.Handle(this, &PopupTextInputChoice::HandleClick); } @@ -529,7 +529,7 @@ EventReturn PopupTextInputChoice::HandleClick(EventParams &e) { // Choose method depending on platform capabilities. if (System_GetPropertyBool(SYSPROP_HAS_TEXT_INPUT_DIALOG)) { System_InputBoxGetString(token_, text_, *value_, passwordMasking_, [=](const std::string &enteredValue, int) { - *value_ = StripSpaces(enteredValue); + *value_ = SanitizeString(StripSpaces(enteredValue), restriction_, maxLen_); EventParams params{}; OnChange.Trigger(params); }); @@ -553,6 +553,7 @@ std::string PopupTextInputChoice::ValueText() const { } EventReturn PopupTextInputChoice::HandleChange(EventParams &e) { + *value_ = StripSpaces(SanitizeString(*value_, restriction_, maxLen_)); e.v = this; OnChange.Trigger(e); diff --git a/Common/UI/PopupScreens.h b/Common/UI/PopupScreens.h index 422be1efa5..54032d275f 100644 --- a/Common/UI/PopupScreens.h +++ b/Common/UI/PopupScreens.h @@ -8,6 +8,9 @@ #include "Common/UI/View.h" #include "Common/UI/ScrollView.h" +// from StringUtils +enum class StringRestriction; + namespace UI { static const float NO_DEFAULT_FLOAT = -1000000.0f; @@ -371,6 +374,10 @@ public: Event OnChange; + void SetRestriction(StringRestriction restriction) { + restriction_ = restriction; + } + protected: std::string ValueText() const override; @@ -384,6 +391,7 @@ private: std::string defaultText_; int maxLen_; bool restoreFocus_ = false; + StringRestriction restriction_; }; class ChoiceWithValueDisplay : public AbstractChoiceWithValueDisplay { diff --git a/Core/Config.cpp b/Core/Config.cpp index 545f104ce0..2a8810e308 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -111,7 +111,7 @@ GPUBackend GPUBackendFromString(std::string_view backend) { return GPUBackend::OPENGL; } -const char *DefaultLangRegion() { +std::string DefaultLangRegion() { // Unfortunate default. There's no need to use bFirstRun, since this is only a default. static std::string defaultLangRegion = "en_US"; std::string langRegion = System_GetProperty(SYSPROP_LANGREGION); @@ -136,7 +136,7 @@ const char *DefaultLangRegion() { } } - return defaultLangRegion.c_str(); + return defaultLangRegion; } static int DefaultDepthRaster() { @@ -602,6 +602,20 @@ static std::string FastForwardModeToString(int v) { return "CONTINUOUS"; } +static std::string DefaultInfrastructureUsername() { + // If the user has already picked a Nickname that satisfies the rules and is not "PPSSPP", + // let's use that. + // NOTE: This type of dependency means that network settings must be AFTER system settings in sections[]. + if (g_Config.sNickName != "PPSSPP" && + !g_Config.sNickName.empty() && + g_Config.sNickName == SanitizeString(g_Config.sNickName, StringRestriction::AlphaNumDashUnderscore, 16)) { + return g_Config.sNickName; + } + + // Otherwise let's leave it empty, which will result in login failure and a warning. + return std::string(); +} + static const ConfigSetting graphicsSettings[] = { ConfigSetting("EnableCardboardVR", &g_Config.bEnableCardboardVR, false, CfgFlag::PER_GAME), ConfigSetting("CardboardScreenSize", &g_Config.iCardboardScreenSize, 50, CfgFlag::PER_GAME), @@ -893,6 +907,7 @@ static const ConfigSetting networkSettings[] = { ConfigSetting("ForcedFirstConnect", &g_Config.bForcedFirstConnect, false, CfgFlag::PER_GAME), ConfigSetting("EnableUPnP", &g_Config.bEnableUPnP, false, CfgFlag::PER_GAME), ConfigSetting("UPnPUseOriginalPort", &g_Config.bUPnPUseOriginalPort, false, CfgFlag::PER_GAME), + ConfigSetting("InfrastructureUsername", &g_Config.sInfrastructureUsername, &DefaultInfrastructureUsername, CfgFlag::PER_GAME), ConfigSetting("EnableNetworkChat", &g_Config.bEnableNetworkChat, false, CfgFlag::PER_GAME), ConfigSetting("ChatButtonPosition", &g_Config.iChatButtonPosition, (int)ScreenEdgePosition::BOTTOM_LEFT, CfgFlag::PER_GAME), @@ -993,8 +1008,8 @@ static const ConfigSectionSettings sections[] = { {"Graphics", graphicsSettings, ARRAY_SIZE(graphicsSettings)}, {"Sound", soundSettings, ARRAY_SIZE(soundSettings)}, {"Control", controlSettings, ARRAY_SIZE(controlSettings)}, - {"Network", networkSettings, ARRAY_SIZE(networkSettings)}, {"SystemParam", systemParamSettings, ARRAY_SIZE(systemParamSettings)}, + {"Network", networkSettings, ARRAY_SIZE(networkSettings)}, {"Debugger", debuggerSettings, ARRAY_SIZE(debuggerSettings)}, {"JIT", jitSettings, ARRAY_SIZE(jitSettings)}, {"Upgrade", upgradeSettings, ARRAY_SIZE(upgradeSettings)}, diff --git a/Core/Config.h b/Core/Config.h index 9bc5443b54..5215ab4735 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -443,8 +443,9 @@ public: bool bDiscardRegsOnJRRA; // SystemParam - std::string sNickName; + std::string sNickName; // AdHoc and system nickname std::string sMACAddress; + int iLanguage; int iTimeFormat; int iDateFormat; @@ -459,6 +460,7 @@ public: std::string proAdhocServer; std::string primaryDNSServer; std::string secondaryDNSServer; + std::string sInfrastructureUsername; // Username used for Infrastructure play. Different restrictions. bool bEnableWlan; std::map mHostToAlias; // TODO: mPostShaderSetting bool bEnableAdhocServer; diff --git a/Core/ConfigSettings.cpp b/Core/ConfigSettings.cpp index 6de81dc3a0..ba6b83005c 100644 --- a/Core/ConfigSettings.cpp +++ b/Core/ConfigSettings.cpp @@ -36,7 +36,7 @@ bool ConfigSetting::Get(const Section *section) const { case TYPE_FLOAT: return section->Get(iniKey_, ptr_.f, cb_.f ? cb_.f() : default_.f); case TYPE_STRING: - return section->Get(iniKey_, ptr_.s, cb_.s ? cb_.s() : default_.s); + return section->Get(iniKey_, ptr_.s, cb_.s ? cb_.s().c_str() : default_.s); case TYPE_TOUCH_POS: { ConfigTouchPos defaultTouchPos = cb_.touchPos ? cb_.touchPos() : default_.touchPos; diff --git a/Core/ConfigSettings.h b/Core/ConfigSettings.h index 440e22ca26..2c4f373240 100644 --- a/Core/ConfigSettings.h +++ b/Core/ConfigSettings.h @@ -57,7 +57,7 @@ struct ConfigSetting { typedef uint32_t(*Uint32DefaultCallback)(); typedef uint64_t(*Uint64DefaultCallback)(); typedef float (*FloatDefaultCallback)(); - typedef const char *(*StringDefaultCallback)(); + typedef std::string (*StringDefaultCallback)(); typedef ConfigTouchPos(*TouchPosDefaultCallback)(); typedef const char *(*PathDefaultCallback)(); typedef ConfigCustomButton(*CustomButtonDefaultCallback)(); diff --git a/Core/HLE/sceNp.cpp b/Core/HLE/sceNp.cpp index 8b3d413665..e43ea67d53 100644 --- a/Core/HLE/sceNp.cpp +++ b/Core/HLE/sceNp.cpp @@ -20,7 +20,10 @@ #include #include -#include + +#include "Common/System/OSD.h" +#include "Common/Data/Text/I18n.h" +#include "Common/StringUtils.h" #include "Core/MemMapHelpers.h" #include "Core/CoreTiming.h" #include "Core/Config.h" @@ -52,7 +55,6 @@ std::recursive_mutex npAuthEvtMtx; std::deque npAuthEvents; std::map npAuthHandlers; - // Tickets data are in big-endian based on captured packets static int writeTicketParam(u8* buffer, const u16_be type, const char* data = nullptr, const u16_be size = 0) { if (buffer == nullptr) return 0; @@ -112,12 +114,16 @@ static void notifyNpAuthHandlers(u32 id, u32 result, u32 argAddr) { static int sceNpInit() { ERROR_LOG(Log::sceNet, "UNIMPL %s()", __FUNCTION__); - npOnlineId = g_Config.sNickName; - // Truncate the nickname to 16 chars exactly - longer names are not support. - if (npOnlineId.size() > 16) { - npOnlineId.resize(16); + + // We'll sanitize an extra time here, just to be safe from ini modifications. + if (g_Config.sInfrastructureUsername == SanitizeString(g_Config.sInfrastructureUsername, StringRestriction::AlphaNumDashUnderscore, 16)) { + npOnlineId = g_Config.sInfrastructureUsername; + } else { + npOnlineId.clear(); } + // NOTE: Checking validity and returning -1 here doesn't seem to work. Instead, we will fail to generate a ticket. + return 0; } @@ -402,6 +408,14 @@ int sceNpAuthGetTicket(u32 requestId, u32 bufferAddr, u32 length) if (!Memory::IsValidAddress(bufferAddr)) return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_INVALID_ARGUMENT, "invalid arg"); + // We have validated, and this will be empty if the ID is bad. + if (npOnlineId.empty()) { + auto n = GetI18NCategory(I18NCat::NETWORKING); + // Temporary message. + g_OSD.Show(OSDType::MESSAGE_ERROR, n->T("To play in Infrastructure Mode, you must enter a username"), 5.0f); + return hleLogError(Log::sceNet, SCE_NP_AUTH_ERROR_UNKNOWN, "Missing npOnlineId"); + } + int result = length; Memory::Memset(bufferAddr, 0, length, "NpAuthGetTicket"); SceNpTicket ticket = {}; diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 8afadd7ac7..d6b40e7446 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -941,6 +941,16 @@ void GameSettingsScreen::CreateNetworkingSettings(UI::ViewGroup *networkingSetti useOriPort->SetEnabledPtr(&g_Config.bEnableUPnP); networkingSettings->Add(new ItemHeader(n->T("Infrastructure"))); + if (g_Config.sInfrastructureUsername.empty()) { + networkingSettings->Add(new NoticeView(NoticeLevel::WARN, n->T("To play in Infrastructure Mode, you must enter a username"), "")); + } + PopupTextInputChoice *usernameChoice = networkingSettings->Add(new PopupTextInputChoice(GetRequesterToken(), &g_Config.sInfrastructureUsername, n->T("Username"), "", 16, screenManager())); + usernameChoice->SetRestriction(StringRestriction::AlphaNumDashUnderscore); + usernameChoice->OnChange.Add([this](UI::EventParams &e) { + RecreateViews(); + return UI::EVENT_DONE; + }); + networkingSettings->Add(new PopupTextInputChoice(GetRequesterToken(), &g_Config.primaryDNSServer, n->T("Primary DNS server"), "", 32, screenManager())); networkingSettings->Add(new PopupTextInputChoice(GetRequesterToken(), &g_Config.secondaryDNSServer, n->T("Secondary DNS server"), "", 32, screenManager())); @@ -1682,62 +1692,42 @@ UI::EventReturn GameSettingsScreen::OnAudioDevice(UI::EventParams &e) { } UI::EventReturn GameSettingsScreen::OnChangeQuickChat0(UI::EventParams &e) { -#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(SWITCH) auto n = GetI18NCategory(I18NCat::NETWORKING); System_InputBoxGetString(GetRequesterToken(), n->T("Enter Quick Chat 1"), g_Config.sQuickChat0, false, [](const std::string &value, int) { g_Config.sQuickChat0 = value; }); -#endif return UI::EVENT_DONE; } UI::EventReturn GameSettingsScreen::OnChangeQuickChat1(UI::EventParams &e) { -#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(SWITCH) auto n = GetI18NCategory(I18NCat::NETWORKING); System_InputBoxGetString(GetRequesterToken(), n->T("Enter Quick Chat 2"), g_Config.sQuickChat1, false, [](const std::string &value, int) { g_Config.sQuickChat1 = value; }); -#endif return UI::EVENT_DONE; } UI::EventReturn GameSettingsScreen::OnChangeQuickChat2(UI::EventParams &e) { -#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(SWITCH) auto n = GetI18NCategory(I18NCat::NETWORKING); System_InputBoxGetString(GetRequesterToken(), n->T("Enter Quick Chat 3"), g_Config.sQuickChat2, false, [](const std::string &value, int) { g_Config.sQuickChat2 = value; }); -#endif return UI::EVENT_DONE; } UI::EventReturn GameSettingsScreen::OnChangeQuickChat3(UI::EventParams &e) { -#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(SWITCH) auto n = GetI18NCategory(I18NCat::NETWORKING); System_InputBoxGetString(GetRequesterToken(), n->T("Enter Quick Chat 4"), g_Config.sQuickChat3, false, [](const std::string &value, int) { g_Config.sQuickChat3 = value; }); -#endif return UI::EVENT_DONE; } UI::EventReturn GameSettingsScreen::OnChangeQuickChat4(UI::EventParams &e) { -#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(SWITCH) auto n = GetI18NCategory(I18NCat::NETWORKING); System_InputBoxGetString(GetRequesterToken(), n->T("Enter Quick Chat 5"), g_Config.sQuickChat4, false, [](const std::string &value, int) { g_Config.sQuickChat4 = value; }); -#endif - return UI::EVENT_DONE; -} - -UI::EventReturn GameSettingsScreen::OnChangeNickname(UI::EventParams &e) { -#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(SWITCH) - auto n = GetI18NCategory(I18NCat::NETWORKING); - System_InputBoxGetString(GetRequesterToken(), n->T("Enter a new PSP nickname"), g_Config.sNickName, false, [](const std::string &value, int) { - g_Config.sNickName = StripSpaces(value); - }); -#endif return UI::EVENT_DONE; } diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index cc31d7c3ea..4032a143e6 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -89,7 +89,6 @@ private: UI::EventReturn OnChangeQuickChat2(UI::EventParams &e); UI::EventReturn OnChangeQuickChat3(UI::EventParams &e); UI::EventReturn OnChangeQuickChat4(UI::EventParams &e); - UI::EventReturn OnChangeNickname(UI::EventParams &e); UI::EventReturn OnChangeproAdhocServerAddress(UI::EventParams &e); UI::EventReturn OnChangeBackground(UI::EventParams &e); UI::EventReturn OnFullscreenChange(UI::EventParams &e);