Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Core/GameEngine/Include/GameNetwork/GameSpyOverlay.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@
void ClearGSMessageBoxes(); ///< Tear down any GS message boxes (e.g. in case we have a new one to put up)
void GSMessageBoxOk(UnicodeString titleString,UnicodeString bodyString, GameWinMsgBoxFunc okFunc = nullptr); ///< Display a Message box with Ok button and track it
void GSMessageBoxOkCancel(UnicodeString title, UnicodeString message, GameWinMsgBoxFunc okFunc, GameWinMsgBoxFunc cancelFunc); ///< Display a Message box with Ok/Cancel buttons and track it
void GSMessageBoxOkCancelWithLabels(
UnicodeString title,
UnicodeString message,
UnicodeString okLabel,
UnicodeString cancelLabel,
GameWinMsgBoxFunc okFunc,
GameWinMsgBoxFunc cancelFunc); ///< Display a tracked Ok/Cancel message box with caller-provided button labels
void GSMessageBoxYesNo(UnicodeString title, UnicodeString message, GameWinMsgBoxFunc yesFunc, GameWinMsgBoxFunc noFunc); ///< Display a Message box with Yes/No buttons and track it
void RaiseGSMessageBox(); ///< Bring GS message box to the foreground (if we transition screens while a message box is up)

Expand Down
28 changes: 28 additions & 0 deletions Core/GameEngine/Source/GameNetwork/GameSpyOverlay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "Common/AudioEventRTS.h"

#include "GameClient/GadgetListBox.h"
#include "GameClient/GadgetPushButton.h"
#include "GameClient/GameText.h"
#include "GameClient/MessageBox.h"
#include "GameClient/ShellHooks.h"
Expand Down Expand Up @@ -125,6 +126,33 @@ void GSMessageBoxOkCancel(UnicodeString title, UnicodeString message, GameWinMsg
cancelFunc = newCancelFunc;
}

void GSMessageBoxOkCancelWithLabels(
UnicodeString title,
UnicodeString message,
UnicodeString okLabel,
UnicodeString cancelLabel,
GameWinMsgBoxFunc newOkFunc,
GameWinMsgBoxFunc newCancelFunc)
{
GSMessageBoxOkCancel(title, message, newOkFunc, newCancelFunc);
if (messageBoxWindow == nullptr)
{
return;
}

GameWindow* buttonOk = TheWindowManager->winGetWindowFromId(messageBoxWindow, TheNameKeyGenerator->nameToKey("MessageBox.wnd:ButtonOk"));
GameWindow* buttonCancel = TheWindowManager->winGetWindowFromId(messageBoxWindow, TheNameKeyGenerator->nameToKey("MessageBox.wnd:ButtonCancel"));
if (buttonOk != nullptr)
{
GadgetButtonSetText(buttonOk, okLabel);
}

if (buttonCancel != nullptr)
{
GadgetButtonSetText(buttonCancel, cancelLabel);
}
}

/**
* GSMessageBoxYesNo puts up a Yes/No dialog box and saves the
* pointers to it and its callbacks.
Expand Down
2 changes: 2 additions & 0 deletions GeneralsMD/Code/GameEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,7 @@ set(GAMEENGINE_SRC
Include/GameNetwork/GeneralsOnline/OnlineServices_Auth.h
Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h
Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h
Include/GameNetwork/GeneralsOnline/OnlineServices_Moderation.h
Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h
Include/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.h
Include/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.h
Expand Down Expand Up @@ -1193,6 +1194,7 @@ set(GAMEENGINE_SRC
Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp
Source/GameNetwork/GeneralsOnline/OnlineServices_Auth.cpp
Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp
Source/GameNetwork/GeneralsOnline/OnlineServices_Moderation.cpp
Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp
Source/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.cpp
Source/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ void NetworkLog(ELogVerbosity logVerbosity, const char* fmt, ...);

std::string to_utf8(const std::wstring& wstr);
std::wstring from_utf8(const std::string& utf8_str);
std::wstring NormalizeSingleLineText(const std::wstring& text);

int RoundUpLatencyToFrameInterval(int latency, int frameInterval);
int ConvertMSLatencyToFrames(int ms);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ enum EWebSocketMessageID
AC_REGISTER_PLAYER = 40,
AC_DEREGISTER_PLAYER = 41,
WS_KEEPALIVE = 42,
WS_KEEPALIVE_CLIENT = 43
WS_KEEPALIVE_CLIENT = 43,
MODERATION_ACTION = 46
};

enum class EQoSRegions
Expand Down Expand Up @@ -124,9 +125,16 @@ enum class EGOTearDownReason
LOST_CONNECTION = 0,
USER_LOGOUT = 1,
USER_REQUESTED_SILENT = 2,
AUTH_FAILED = 3
AUTH_FAILED = 3,
MODERATION_BAN = 4,
MODERATION_KICK = 5
};

constexpr bool IsModerationTeardownReason(EGOTearDownReason reason) noexcept
{
return reason == EGOTearDownReason::MODERATION_BAN || reason == EGOTearDownReason::MODERATION_KICK;
}

class WebSocket
{
public:
Expand Down Expand Up @@ -500,7 +508,16 @@ class NGMP_OnlineServicesManager

std::string& GetMOTD() { return m_strMOTD; }

void SetPendingFullTeardown(EGOTearDownReason reason) { m_bPendingFullTeardown = true; m_teardownReason = reason; }
void SetPendingFullTeardown(EGOTearDownReason reason)
{
if (IsModerationTeardownReason(m_teardownReason) && !IsModerationTeardownReason(reason))
{
return;
}

m_bPendingFullTeardown = true;
m_teardownReason = reason;
}
bool IsPendingFullTeardown() const { return m_bPendingFullTeardown; }
EGOTearDownReason GetTeardownReason() const { return m_teardownReason; }
void ConsumePendingFullTeardown() { m_bPendingFullTeardown = false; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

#include <string>

enum class EOnlineModerationAction
{
BAN,
KICK
};

void ShowLoginBanDialog(const std::string& reason);
void HandleModerationDisconnect(EOnlineModerationAction action, const std::string& reason);
2 changes: 1 addition & 1 deletion GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ void TearDownGeneralsOnline()

EGOTearDownReason teardownReason = NGMP_OnlineServicesManager::GetInstance()->GetTeardownReason();

if (teardownReason != EGOTearDownReason::USER_REQUESTED_SILENT)
if (teardownReason != EGOTearDownReason::USER_REQUESTED_SILENT && !IsModerationTeardownReason(teardownReason))
{
UnicodeString title, body;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "GameNetwork/GeneralsOnline/NGMP_include.h"
#include <chrono>
#include <cwctype>
#include <ctime>
#include <mutex>
#include <string>
Expand All @@ -25,6 +26,31 @@ std::wstring from_utf8(const std::string& utf8_str)
return converter.from_bytes(utf8_str);
}

std::wstring NormalizeSingleLineText(const std::wstring& text)
{
std::wstring normalizedText;
normalizedText.reserve(text.size());
bool pendingSpace = false;

for (wchar_t character : text)
{
if (std::iswspace(character) || std::iswcntrl(character))
{
pendingSpace = !normalizedText.empty();
continue;
}

if (pendingSpace)
{
normalizedText += L' ';
pendingSpace = false;
}
normalizedText += character;
}

return normalizedText;
}

void NetworkLog(ELogVerbosity logVerbosity, const char* fmt, ...)
{
if (!NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "GameNetwork/GeneralsOnline/HTTP/HTTPManager.h"
#include "GameNetwork/GeneralsOnline/HTTP/HTTPRequest.h"
#include "GameNetwork/GeneralsOnline/OnlineServices_Moderation.h"
#include "GameNetwork/GeneralsOnline/json.hpp"
#include <shellapi.h>
#include <algorithm>
Expand Down Expand Up @@ -44,6 +45,24 @@ struct AuthResponse
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(AuthResponse, result, session_token, refresh_token, user_id, display_name, ws_uri)
};

namespace
{
std::string GetBanReason(const std::string& responseBody)
{
nlohmann::json jsonObject = nlohmann::json::parse(responseBody, nullptr, false, true);
if (jsonObject.is_object())
{
const auto banReason = jsonObject.find("ban_reason");
if (banReason != jsonObject.end() && banReason->is_string())
{
return banReason->get_ref<const std::string&>();
}
}

return std::string();
}
}

struct MOTDResponse
{
std::string MOTD;
Expand Down Expand Up @@ -219,6 +238,16 @@ void NGMP_OnlineServices_AuthInterface::RefreshToken()
{
if (statusCode >= 400 && statusCode < 500)
{
if (statusCode == 423)
{
NetworkLog(ELogVerbosity::LOG_RELEASE, "[AUTH]: Account ban detected during token refresh, tearing down");
m_tokenCreationTime = -1;
m_nextRefreshRetryTime = -1;
m_currentRefreshAttempt = 0;
HandleModerationDisconnect(EOnlineModerationAction::BAN, GetBanReason(strBody));
return;
}

OnRefreshTokenFailed(std::format("HTTP {}", statusCode).c_str(), strBody);
}
else
Expand Down Expand Up @@ -352,11 +381,7 @@ void NGMP_OnlineServices_AuthInterface::BeginLogin()
{
if (statusCode == 423)
{
ClearGSMessageBoxes();
GSMessageBoxOk(UnicodeString(L"Account Banned"), UnicodeString(L"You are banned. You can file an appeal in Discord."), []()
{
TheShell->pop();
});
ShowLoginBanDialog(GetBanReason(strBody));
return;
}
else
Expand Down Expand Up @@ -530,11 +555,7 @@ void NGMP_OnlineServices_AuthInterface::Tick()
if (statusCode == 423)
{
m_bWaitingLogin = false;
ClearGSMessageBoxes();
GSMessageBoxOk(UnicodeString(L"Account Banned"), UnicodeString(L"You are banned. You can file an appeal in Discord."), []()
{
TheShell->pop();
});
ShowLoginBanDialog(GetBanReason(strBody));
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#include "GameNetwork/GeneralsOnline/OnlineServices_Moderation.h"

#include "GameNetwork/GeneralsOnline/NGMP_include.h"
#include "GameNetwork/GeneralsOnline/OnlineServices_Init.h"
#include "GameNetwork/GameSpyOverlay.h"

#include <stdexcept>
#include <windows.h>
#include <shellapi.h>

namespace
{
enum class EModerationDialogContext
{
LOGIN,
ACTIVE_SESSION
};

void LeaveLoginScreen()
{
TheShell->pop();
}

void OpenDiscord()
{
ShellExecuteA(nullptr, "open", "https://discord.playgenerals.online", nullptr, nullptr, SW_SHOWNORMAL);
}

void OpenDiscordFromLogin()
{
OpenDiscord();
LeaveLoginScreen();
}

std::wstring GetNormalizedReason(const std::string& reason)
{
try
{
return NormalizeSingleLineText(from_utf8(reason));
}
catch (const std::range_error&)
{
NetworkLog(ELogVerbosity::LOG_RELEASE, "[MODERATION]: Ignoring an invalid UTF-8 reason");
return std::wstring();
}
}

void AppendReason(std::wstring& message, const std::string& reason)
{
const std::wstring normalizedReason = GetNormalizedReason(reason);
if (!normalizedReason.empty())
{
message += L"\n\nReason: ";
message += normalizedReason;
}
}

void ShowModerationDialog(
UnicodeString title,
std::wstring message,
const std::string& reason,
EModerationDialogContext context)
{
AppendReason(message, reason);
message += L"\n\nVisit Discord for more information or support.";

GameWinMsgBoxFunc discordCallback = OpenDiscord;
GameWinMsgBoxFunc closeCallback = nullptr;
if (context == EModerationDialogContext::LOGIN)
{
discordCallback = OpenDiscordFromLogin;
closeCallback = LeaveLoginScreen;
}

GSMessageBoxOkCancelWithLabels(
title,
UnicodeString(message.c_str()),
UnicodeString(L"Discord"),
UnicodeString(L"Close"),
discordCallback,
closeCallback);
}

void ShowBanDialog(const std::string& reason, EModerationDialogContext context)
{
ShowModerationDialog(
UnicodeString(L"Banned"),
L"You have been banned from Generals Online.",
reason,
context);
}

void ShowKickDialog(const std::string& reason)
{
ShowModerationDialog(
UnicodeString(L"Kicked"),
L"You have been kicked from Generals Online.",
reason,
EModerationDialogContext::ACTIVE_SESSION);
}
}

void ShowLoginBanDialog(const std::string& reason)
{
ShowBanDialog(reason, EModerationDialogContext::LOGIN);
}

void HandleModerationDisconnect(EOnlineModerationAction action, const std::string& reason)
{
NGMP_OnlineServicesManager* manager = NGMP_OnlineServicesManager::GetInstance();
if (manager == nullptr || IsModerationTeardownReason(manager->GetTeardownReason()))
{
return;
}

switch (action)
{
case EOnlineModerationAction::BAN:
manager->SetPendingFullTeardown(EGOTearDownReason::MODERATION_BAN);
ShowBanDialog(reason, EModerationDialogContext::ACTIVE_SESSION);
break;

case EOnlineModerationAction::KICK:
manager->SetPendingFullTeardown(EGOTearDownReason::MODERATION_KICK);
ShowKickDialog(reason);
break;
}
}
Loading
Loading