Files
2025-12-18 16:59:29 -05:00

2327 lines
76 KiB
C++

/* Copyright 2003-2007 ROBLOX Corporation, All Rights Reserved */
#include "Network/Players.h"
#include "Network/Player.h"
#include "V8DataModel/FriendService.h"
#include "Replicator.h"
#include "GuidRegistryService.h"
#include "Client.h"
#include "Server.h"
#include "Streaming.h"
#include "Util.h"
#include "util/RobloxGoogleAnalytics.h"
#include "WebChatFilter.h"
#include "RakPeerInterface.h"
#include "PacketIds.h"
#include "BitStream.h"
#include "RakNet/Source/PluginInterface2.h"
#include "ConcurrentRakPeer.h"
#include "PhysicsSender.h"
#include "PhysicsReceiver.h"
#include "Humanoid/Humanoid.h"
#include "V8DataModel/CharacterAppearance.h"
#include "V8DataModel/DataModelMesh.h"
#include "V8DataModel/Decal.h"
#include "V8DataModel/Backpack.h"
#include "V8DataModel/DebugSettings.h"
#include "V8DataModel/GameSettings.h"
#include "V8DataModel/Hopper.h"
#include "V8DataModel/InsertService.h"
#include "V8DataModel/Modelinstance.h"
#include "V8DataModel/PlayerGui.h"
#include "V8DataModel/SafeChat.h"
#include "V8DataModel/Stats.h"
#include "V8DataModel/Teams.h"
#include "V8DataModel/Workspace.h"
#include "V8DataModel/ReplicatedFirst.h"
#include "v8datamodel/HttpRbxApiService.h"
#include "Util/LuaWebService.h"
#include "Util/SystemAddress.h"
#include "V8Xml/WebParser.h"
#include "V8World/DistributedPhysics.h"
#include "Util/Statistics.h"
#include "Script/ScriptContext.h"
#include "Script/Script.h"
#include "V8Xml/XmlSerializer.h"
#include "Util/SafeToLower.h"
#include "rbx/RbxDbgInfo.h"
#include "Util/http.h"
#include "Util/Region2.h"
#include "Network/NetworkOwner.h"
#include <boost/algorithm/string.hpp>
#include <sstream>
const char* const RBX::Network::sPlayers = "Players";
static const char* const kPlaceIdUrlParamFormat = "&serverplaceid=%d";
using namespace RBX;
using namespace RBX::Network;
using namespace RakNet;
LOGGROUP(GoldenHashes)
LOGGROUP(Network)
LOGGROUP(US14116)
FASTFLAG(DebugLocalRccServerConnection)
DYNAMIC_FASTFLAG(RCCSupportCloudEdit)
DYNAMIC_LOGGROUP(WebChatFiltering)
DYNAMIC_FASTFLAGVARIABLE(GetCharacterAppearanceEnabled, false)
DYNAMIC_FASTFLAGVARIABLE(CreatePlayerGuiLocal, false)
DYNAMIC_FASTFLAGVARIABLE(FirePlayerAddedAndPlayerRemovingOnClient, false)
FASTSTRINGVARIABLE(GetUserIdUrl, "users/get-by-username?username=%s")
FASTSTRINGVARIABLE(GetUserNameUrl, "users/%i")
FASTSTRINGVARIABLE(GetFriendsUrl, "%susers/%i/friends")
DYNAMIC_FASTFLAGVARIABLE(LoadGuisWithoutChar, false)
DYNAMIC_FASTFLAGVARIABLE(FilterInvalidWhisper, true)
DYNAMIC_FASTFLAGVARIABLE(CloudEditSupportPlayersKickAndShutdown, true)
REFLECTION_BEGIN();
static Reflection::PropDescriptor<Players, int> propPlayerCount("NumPlayers", category_Data, &Players::getNumPlayers, NULL, Reflection::PropertyDescriptor::UI);
static Reflection::PropDescriptor<Players, int> depPlayerCount("numPlayers", category_Data, &Players::getNumPlayers, NULL, Reflection::PropertyDescriptor::Attributes::deprecated(propPlayerCount, Reflection::PropertyDescriptor::HIDDEN_SCRIPTING));
static Reflection::PropDescriptor<Players, int> propPlayerMaxCount("MaxPlayers", category_Data, &Players::getMaxPlayers, NULL, Reflection::PropertyDescriptor::UI);
static Reflection::PropDescriptor<Players, int> propPlayerMaxCountInternal("MaxPlayersInternal", category_Data, &Players::getMaxPlayers, &Players::setMaxPlayers, Reflection::PropertyDescriptor::STANDARD, Security::LocalUser);
static Reflection::PropDescriptor<Players, int> propPlayerPreferredCount("PreferredPlayers", category_Data, &Players::getPreferredPlayers, NULL, Reflection::PropertyDescriptor::UI);
static Reflection::PropDescriptor<Players, int> propPlayerPreferredCountInternal("PreferredPlayersInternal", category_Data, &Players::getPreferredPlayers, &Players::setPreferredPlayers, Reflection::PropertyDescriptor::STANDARD, Security::LocalUser);
Reflection::RefPropDescriptor<Players, Instance> Players::propLocalPlayer("LocalPlayer", category_Data, &Players::getLocalPlayerDangerous, NULL, Reflection::PropertyDescriptor::UI);
Reflection::RefPropDescriptor<Players, Instance> dep_propLocalPlayer("localPlayer", category_Data, &Players::getLocalPlayerDangerous, NULL, Reflection::PropertyDescriptor::Attributes::deprecated(Players::propLocalPlayer, Reflection::PropertyDescriptor::HIDDEN_SCRIPTING));
static Reflection::BoundFuncDesc<Players, shared_ptr<Instance>(int)> func_GetPlayerByID(&Players::getPlayerInstanceByID, "GetPlayerById", "userId", Security::LocalUser);
static Reflection::BoundFuncDesc<Players, shared_ptr<Instance>(int)> func_GetPlayerByIdDeprecated(&Players::getPlayerInstanceByID, "GetPlayerByID", "userID", Security::LocalUser, Reflection::PropertyDescriptor::Attributes::deprecated(func_GetPlayerByID));
Reflection::PropDescriptor<Players, bool> Players::propCharacterAutoSpawn("CharacterAutoLoads", category_Behavior, &Players::getCharacterAutoSpawnProperty, &Players::setCharacterAutoSpawnProperty, Reflection::PropertyDescriptor::STANDARD_NO_REPLICATE);
static Reflection::BoundFuncDesc<Players, void(std::string)> funcChat(&Players::chat, "Chat", "message", Security::Plugin);
static Reflection::BoundFuncDesc<Players, void(std::string)> funcTeamChat(&Players::teamChat, "TeamChat", "message", Security::Plugin);
static Reflection::BoundFuncDesc<Players, void(std::string, shared_ptr<Instance>)> funcPlayerChat(&Players::whisperChat, "WhisperChat", "message", "player", Security::LocalUser);
static Reflection::EventDesc<Players, void(Players::PlayerChatType, shared_ptr<Instance>, std::string, shared_ptr<Instance>)> event_Chatted(&Players::playerChattedSignal, "PlayerChatted", "chatType", "player", "message", "targetPlayer", Security::LocalUser);
static Reflection::EventDesc<Players, void(std::string)> event_GameAnnounce(&Players::gameAnnounceSignal, "GameAnnounce", "message", Security::RobloxScript);
static Reflection::BoundFuncDesc<Players, void(shared_ptr<Instance>, std::string, std::string)> funcReportAbuse(&Players::reportAbuseLua, "ReportAbuse", "player", "reason", "optionalMessage", Security::LocalUser);
static Reflection::BoundFuncDesc<Players, shared_ptr<const Instances>()> func_GetPlayers(&Players::getPlayers, "GetPlayers", Security::None);
static Reflection::BoundFuncDesc<Players, shared_ptr<const Instances>()> dep_players(&Players::getPlayers, "players", Security::None, Reflection::Descriptor::Attributes::deprecated(func_GetPlayers));
static Reflection::BoundFuncDesc<Players, shared_ptr<const Instances>()> dep_getPlayers(&Players::getPlayers, "getPlayers", Security::None, Reflection::Descriptor::Attributes::deprecated(func_GetPlayers));
static Reflection::BoundFuncDesc<Players, shared_ptr<Instance>(int, bool)> func_createLocalPlayer(&Players::createLocalPlayer, "CreateLocalPlayer", "userId", "isTeleport", false, Security::Plugin);
static Reflection::BoundFuncDesc<Players, void(std::string)> func_setAbuseReportUrl(&Players::setAbuseReportUrl, "SetAbuseReportUrl", "url", Security::Roblox);
static Reflection::BoundFuncDesc<Players, void(std::string)> func_setChatFilterUrl(&Players::setChatFilterUrl, "SetChatFilterUrl", "url", Security::Roblox);
static Reflection::BoundFuncDesc<Players, void(std::string)> func_setBuildUserPermissionsUrl(&Players::setBuildUserPermissionsUrl, "SetBuildUserPermissionsUrl", "url", Security::Roblox);
static Reflection::BoundFuncDesc<Players, shared_ptr<Instance>(shared_ptr<Instance>)> func_playerFromCharacter(&Players::playerFromCharacter, "GetPlayerFromCharacter", "character", Security::None);
static Reflection::BoundFuncDesc<Players, shared_ptr<Instance>(shared_ptr<Instance>)> func_playerFromCharacterOld(&Players::playerFromCharacter, "playerFromCharacter", "character", Security::None, Reflection::Descriptor::Attributes::deprecated(func_playerFromCharacter));
static Reflection::BoundFuncDesc<Players, shared_ptr<Instance>(shared_ptr<Instance>)> dep_playerFromCharacter(&Players::playerFromCharacter, "getPlayerFromCharacter", "character", Security::None, Reflection::Descriptor::Attributes::deprecated(func_playerFromCharacter));
static Reflection::EventDesc<Players, void(shared_ptr<Instance>, shared_ptr<Instance>, FriendService::FriendEventType)> event_FriendRequestEvent(&Players::friendRequestEvent, "FriendRequestEvent", "player", "player", "friendRequestEvent", Security::RobloxScript);
static Reflection::EventDesc<Players, void(shared_ptr<Instance>)> event_PlayerAddedEarly(&Players::playerAddedEarlySignal, "PlayerAddedEarly", "player", Security::LocalUser);
static Reflection::EventDesc<Players, void(shared_ptr<Instance>)> event_PlayerAdded(&Players::playerAddedSignal, "PlayerAdded", "player");
static Reflection::EventDesc<Players, void(shared_ptr<Instance>)> event_PlayerRemoving(&Players::playerRemovingSignal, "PlayerRemoving", "player");
static Reflection::EventDesc<Players, void(shared_ptr<Instance>)> event_PlayerRemovingLate(&Players::playerRemovingLateSignal, "PlayerRemovingLate", "player", Security::LocalUser);
static Reflection::BoundFuncDesc<Players, void(std::string)> func_setSysStatsUrl(&Players::setSysStatsUrl, "SetSysStatsUrl", "url", Security::LocalUser);
static Reflection::BoundFuncDesc<Players, void(std::string)> func_setSysHash(&Players::setSysHash, "SetSysStatsUrlId", "urlId", Security::LocalUser);
static Reflection::BoundFuncDesc<Players, void(std::string)> func_setLoadDataUrl(&Players::setLoadDataUrl, "SetLoadDataUrl", "url", Security::LocalUser);
static Reflection::BoundFuncDesc<Players, void(std::string)> func_setSaveDataUrl(&Players::setSaveDataUrl, "SetSaveDataUrl", "url", Security::LocalUser);
static Reflection::BoundFuncDesc<Players, void(std::string)> func_setSaveLeaderboardDataUrl(&Players::setSaveLeaderboardDataUrl, "SetSaveLeaderboardDataUrl", "url", Security::LocalUser);
static Reflection::BoundFuncDesc<Players, void(std::string)> func_addLeaderboardKey(&Players::addLeaderboardKey, "AddLeaderboardKey", "key", Security::LocalUser);
static Reflection::BoundFuncDesc<Players, void(Players::ChatOption)> func_setChatOption(&Players::setChatOption, "SetChatStyle", "style", Players::CLASSIC_CHAT, Security::Plugin);
Reflection::PropDescriptor<Players, bool> propClassicChat("ClassicChat", category_Data, &Players::getClassicChat, NULL, Reflection::PropertyDescriptor::UI);
Reflection::PropDescriptor<Players, bool> propBubbleChat("BubbleChat", category_Data, &Players::getBubbleChat, NULL, Reflection::PropertyDescriptor::UI);
static Reflection::BoundFuncDesc<Players, bool()> func_coreScriptHealthBar(&Players::getUseCoreScriptHealthBar, "GetUseCoreScriptHealthBar", Security::RobloxScript);
// Chat Ignore Support
static Reflection::RemoteEventDesc<Players, void(int, int, std::string)> event_serverFinishedBlockUser(&Players::blockUserFinishedFromServerSignal, "BlockedRequestFinishedSignal", "blockerUserId", "blockeeUserId", "didDoRequest", Security::Roblox, Reflection::RemoteEventCommon::REPLICATE_ONLY, Reflection::RemoteEventCommon::BROADCAST);
static Reflection::RemoteEventDesc<Players, void(int, int, bool)> event_blockUserFromClient(&Players::blockUserRequestFromClientSignal, "BlockUserSignal", "blockerUserId", "blockeeUserId", "blocking", Security::Roblox, Reflection::RemoteEventCommon::REPLICATE_ONLY, Reflection::RemoteEventCommon::CLIENT_SERVER);
static Reflection::BoundYieldFuncDesc<Players, std::string(int, int)> func_blockUser(&Players::blockUser, "BlockUser", "blockerUserId", "blockeeUserId", Security::RobloxScript);
static Reflection::BoundYieldFuncDesc<Players, std::string(int, int)> func_unblockUser(&Players::unblockUser, "UnblockUser", "exblockerUserId", "exblockeeUserId", Security::RobloxScript);
//UserName to userId, userId to UserName
static Reflection::BoundYieldFuncDesc<Players, int(std::string)> func_getUserIdFromName(&Players::getUserIdFromName, "GetUserIdFromNameAsync", "userName", Security::None);
static Reflection::BoundYieldFuncDesc<Players, std::string(int)> func_getNameFromUserId(&Players::getNameFromUserId, "GetNameFromUserIdAsync", "userId", Security::None);
static Reflection::BoundYieldFuncDesc<Players, shared_ptr<Instance>(int)> func_getFriends(&Players::getFriends, "GetFriendsAsync", "userId", Security::None);
static Reflection::BoundYieldFuncDesc<Players, shared_ptr<Instance>(int)> func_getCharacterAppearance(&Players::getCharacterAppearance, "GetCharacterAppearanceAsync", "userId", Security::None);
static Reflection::BoundFuncDesc<Players, shared_ptr<Instance>(int)> func_getPlayerByUserId(&Players::getPlayerInstanceByID, "GetPlayerByUserId", "userId", Security::None);
Reflection::RemoteEventDesc<Players, void(int)> Players::event_requestCloudEditKick(
&Players::requestCloudEditKick, "RequestCloudEditKick", "playerId",
Security::Roblox, Reflection::RemoteEventCommon::REPLICATE_ONLY,
Reflection::RemoteEventCommon::CLIENT_SERVER);
Reflection::RemoteEventDesc<Players, void()> Players::event_requestCloudEditShutdown(
&Players::requestCloudEditShutdown, "RequestCloudEditShutdown",
Security::Roblox, Reflection::RemoteEventCommon::REPLICATE_ONLY,
Reflection::RemoteEventCommon::CLIENT_SERVER);
REFLECTION_END();
std::set<std::string> Players::goldenHashes;
boost::mutex Players::goldenHashesMutex;
bool Players::canKickBecauseRunningInRealGameServer = false;
MemHashConfigs Players::goldMemHashes;
boost::mutex Players::goldMemHashesMutex;
namespace {
shared_ptr<RakNet::BitStream> buildBasicChatBitStream(
const Instance* sender, const Instance* receiver, unsigned char chatType) {
// serialize our data, changing the message to the filtered message
shared_ptr<RakNet::BitStream> data(new RakNet::BitStream());
*data << chatType;
if (sender) {
FASTLOGS(DFLog::WebChatFiltering, "senderGUID: %s", sender->getGuid().readableString());
RBX::Guid::Data guid;
sender->getGuid().extract(guid);
*data << guid.scope;
*data << guid.index;
}
if (receiver) {
FASTLOGS(DFLog::WebChatFiltering, "senderGUID: %s", receiver->getGuid().readableString());
RBX::Guid::Data guid;
receiver->getGuid().extract(guid);
*data << guid.scope;
*data << guid.index;
}
return data;
}
}
bool Players::isNetworkClient(Instance* instance)
{
return Instance::fastDynamicCast<RBX::Network::Client>(instance) != NULL;
}
Players::Players()
:rakPeer(NULL)
, maxPlayers(12)
, sysStatsUrl("")
, players(Instances()) // Initialize to a non-NULL value
, chatOption(CLASSIC_CHAT)
, characterAutoSpawn(true)
, testPlayerNameId(0)
, testPlayerUserId(0)
{
this->setName("Players");
}
template<class T> bool valueTableGet(shared_ptr<const Reflection::ValueTable> valueTable, const std::string& key, T* outValue)
{
Reflection::ValueTable::const_iterator it = valueTable->find(key);
if (it == valueTable->end() || !it->second.isType<T>()) {
return false;
}
*outValue = it->second.cast<T>();
return true;
}
void Players::getUserIdFromName(std::string userName, boost::function<void(int)> resumeFunction, boost::function<void(std::string)> errorFunction)
{
if (RBX::HttpRbxApiService* apiService = RBX::ServiceProvider::find<RBX::HttpRbxApiService>(this))
{
apiService->getAsync(format(FString::GetUserIdUrl.c_str(), userName.c_str()), true, RBX::PRIORITY_DEFAULT,
boost::bind(&Players::onReceivedRawGetUserIdSuccess, weak_from(DataModel::get(this)), _1, resumeFunction, errorFunction),
boost::bind(&Players::onReceivedRawGetUserIdError, weak_from(DataModel::get(this)), _1, errorFunction));
}
}
void Players::onReceivedRawGetUserIdSuccess(weak_ptr<DataModel> weakDataModel, std::string response, boost::function<void(int)> resumeFunction, boost::function<void(std::string)> errorFunction)
{
if (shared_ptr<DataModel> sharedDataModel = weakDataModel.lock())
{
shared_ptr<const Reflection::ValueTable> table;
std::string errorMessage;
if (LuaWebService::parseWebJSONResponseHelper(&response, NULL, table, errorMessage))
{
int id;
if (!valueTableGet(table, "Id", &id))
{
errorFunction("Players:GetUserIdFromName() failed because the user does not exist");
}
else
{
resumeFunction(id);
}
}
else
{
errorFunction(format("Players:GetUserIdFromName() failed because %s", errorMessage.c_str()));
}
}
else
{
errorFunction("Players:GetUserIdFromName() failed because of an unknown error.");
}
}
void Players::onReceivedRawGetUserIdError(weak_ptr<DataModel> weakDataModel, std::string error, boost::function<void(std::string)> errorFunction)
{
if (shared_ptr<DataModel> sharedDataModel = weakDataModel.lock())
{
if (!error.empty())
{
errorFunction(format("Players:GetUserIdFromName() failed because %s", error.c_str()));
}
else
{
errorFunction("Players:GetUserIdFromName() failed because of an unknown error.");
}
}
else
{
errorFunction("Players:GetUserIdFromName() failed because of an unknown error.");
}
}
void Players::getNameFromUserId(int userId, boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction)
{
if (RBX::HttpRbxApiService* apiService = RBX::ServiceProvider::find<RBX::HttpRbxApiService>(this))
{
apiService->getAsync(format(FString::GetUserNameUrl.c_str(), userId), true, RBX::PRIORITY_DEFAULT,
boost::bind(&Players::onReceivedRawGetUserNameSuccess, weak_from(DataModel::get(this)), _1, resumeFunction, errorFunction),
boost::bind(&Players::onReceivedRawGetUserNameError, weak_from(DataModel::get(this)), _1, errorFunction));
}
}
void Players::getFriends(int userId, boost::function<void(shared_ptr<Instance>)> resumeFunction, boost::function<void(std::string)> errorFunction)
{
std::string fetchUrl;
ContentProvider* cp = DataModel::get(this)->find<ContentProvider>();
if (cp)
{
fetchUrl = format(FString::GetFriendsUrl.c_str(), cp->getApiBaseUrl().c_str(), userId);
shared_ptr<Pages> pagination = Creatable<Instance>::create<FriendPages>(weak_from(DataModel::get(this)), fetchUrl);
pagination->fetchNextChunk(boost::bind(resumeFunction, pagination), errorFunction);
}
else
{
errorFunction("Content Provider not found");
}
}
void Players::setAppearanceParent(shared_ptr<Instance> model, weak_ptr<Instance> instance)
{
shared_ptr<Instance> i(instance.lock());
if (!i)
return;
if (Instance::fastDynamicCast<RBX::DataModelMesh>(i.get()) || Instance::fastDynamicCast<RBX::Decal>(i.get()) || Instance::fastDynamicCast<RBX::CharacterAppearance>(i.get()))
{
i->setParent(model.get());
}
else if (dynamic_cast<IEquipable*>(i.get()))
{
if (!i->fastDynamicCast<Tool>())
{
i->setParent(model.get());
}
}
}
void Players::doLoadAppearance(AsyncHttpQueue::RequestResult result, shared_ptr<Instances> instances, std::string contentDescription, shared_ptr<ModelInstance> model
, shared_ptr<int> amountToLoad, boost::function<void(shared_ptr<Instance>)> resumeFunction, boost::function<void(std::string)> errorFunction)
{
if (amountToLoad.get())
{
try
{
std::for_each(instances->begin(), instances->end(), boost::bind(&Players::setAppearanceParent, model, _1));
*amountToLoad.get() = *amountToLoad.get() - 1;
if (*amountToLoad.get() == 0)
{
amountToLoad.reset();
resumeFunction(model);
}
}
catch (RBX::base_exception& e)
{
amountToLoad.reset();
errorFunction(format("Players:GetCharacterAppearanceAsync() Error loading character appearance %s: %s", contentDescription.c_str(), e.what()));
}
}
}
void Players::doMakeAccoutrementRequests(std::string response, weak_ptr<DataModel> dataModel, shared_ptr<ModelInstance> model
, boost::function<void(shared_ptr<Instance>)> resumeFunction, boost::function<void(std::string)> errorFunction)
{
std::vector<std::string> strings;
boost::split(strings, response, boost::is_any_of(";"));
shared_ptr<DataModel> d(dataModel.lock());
shared_ptr<int> amountToLoad = shared_ptr<int>(new int);
*amountToLoad.get() = (int)strings.size();
if (d)
{
std::string placeIdStr;
placeIdStr = format(kPlaceIdUrlParamFormat, d->getPlaceID());
for (size_t i = 0; i < strings.size(); i++)
{
std::string contentIdString = strings[i];
contentIdString += placeIdStr;
ServiceProvider::create<ContentProvider>(d.get())->loadContent(
ContentId(contentIdString), ContentProvider::PRIORITY_DEFAULT,
boost::bind(&Players::doLoadAppearance, _1, _2, contentIdString, model, amountToLoad, resumeFunction, errorFunction),
AsyncHttpQueue::AsyncWrite
);
}
}
}
void Players::makeAccoutrementRequests(std::string* response, std::exception* err, weak_ptr<DataModel> dataModel, shared_ptr<ModelInstance> model,
boost::function<void(shared_ptr<Instance>)> resumeFunction, boost::function<void(std::string)> errorFunction)
{
if (!response)
{
if (err)
{
errorFunction(format("Players:GetCharacterAppearanceAsync() Request exception: %s", err->what()));
}
else
{
errorFunction(format("Players:GetCharacterAppearanceAsync() Request exception"));
}
return;
}
shared_ptr<DataModel> d(dataModel.lock());
if (d)
{
try
{
d->submitTask(boost::bind(&Players::doMakeAccoutrementRequests, *response, dataModel, model, resumeFunction, errorFunction), DataModelJob::Write);
}
catch (const RBX::base_exception&)
{
errorFunction("Players:GetCharacterAppearanceAsync() unexpected error");
}
}
}
void Players::getCharacterAppearance(int userId, boost::function<void(shared_ptr<Instance>)> resumeFunction, boost::function<void(std::string)> errorFunction)
{
if (!DFFlag::GetCharacterAppearanceEnabled)
{
errorFunction("Players:GetCharacterAppearanceAsync() is not yet enabled!");
return;
}
if (userId <= 0)
{
errorFunction("Players:GetCharacterAppearanceAsync() got negative userId");
return;
}
std::string characterAppearance = format("%sasset/CharacterFetch.ashx?userId=%d", ServiceProvider::create<ContentProvider>(this)->getBaseUrl().c_str(), userId);
shared_ptr<ModelInstance> model = ModelInstance::createInstance();
weak_ptr<DataModel> dm(shared_from(Instance::fastDynamicCast<DataModel>(this->getRootAncestor())));
RBX::Http(characterAppearance).get(boost::bind(&makeAccoutrementRequests, _1, _2, boost::weak_ptr<DataModel>(dm), model, resumeFunction, errorFunction));
}
void Players::onReceivedRawGetUserNameSuccess(weak_ptr<DataModel> weakDataModel, std::string response, boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction)
{
if (shared_ptr<DataModel> sharedDataModel = weakDataModel.lock())
{
shared_ptr<const Reflection::ValueTable> table;
std::string errorMessage;
if (LuaWebService::parseWebJSONResponseHelper(&response, NULL, table, errorMessage))
{
std::string userName;
if (!valueTableGet(table, "Username", &userName))
{
errorFunction("Players:GetNameFromUserId() failed because the user does not exist");
}
else
{
resumeFunction(userName);
}
}
else
{
errorFunction(format("Players:GetNameFromUserId() failed because %s", errorMessage.c_str()));
}
}
else
{
errorFunction("Players:GetNameFromUserId() failed because of an unknown error.");
}
}
void Players::onReceivedRawGetUserNameError(weak_ptr<DataModel> weakDataModel, std::string error, boost::function<void(std::string)> errorFunction)
{
if (shared_ptr<DataModel> sharedDataModel = weakDataModel.lock())
{
if (!error.empty())
{
errorFunction(format("Players:GetNameFromUserId() failed because %s", error.c_str()));
}
else
{
errorFunction("Players:GetNameFromUserId() failed because of an unknown error.");
}
}
else
{
errorFunction("Players:GetNameFromUserId() failed because of an unknown error.");
}
}
bool Players::clientIsPresent(const RBX::Instance* context, bool testInDatamodel)
{
return Client::clientIsPresent(context, testInDatamodel);
}
bool Players::serverIsPresent(const RBX::Instance* context, bool testInDatamodel)
{
return Server::serverIsPresent(context, testInDatamodel);
}
bool Players::getDistributedPhysicsEnabled()
{
return NetworkSettings::singleton().distributedPhysicsEnabled;
}
bool Players::isCloudEdit(const RBX::Instance* context)
{
if (Client* c = ServiceProvider::find<Client>(context))
{
return c->isCloudEdit();
}
else if (Server* s = ServiceProvider::find<Server>(context))
{
return s->isCloudEdit();
}
else
{
return false;
}
}
bool Players::frontendProcessing(const RBX::Instance* context, bool testInDatamodel)
{
const ServiceProvider* serviceProvider = ServiceProvider::findServiceProvider(context);
RBXASSERT(!testInDatamodel || serviceProvider != NULL);
return serviceProvider && !serverIsPresent(context, testInDatamodel);
}
bool Players::backendProcessing(const RBX::Instance* context, bool testInDatamodel)
{
const ServiceProvider* serviceProvider = ServiceProvider::findServiceProvider(context);
RBXASSERT(!testInDatamodel || serviceProvider != NULL);
return serviceProvider && !clientIsPresent(context, testInDatamodel);
}
int Players::getPlayerCount(const RBX::Instance* context)
{
const ServiceProvider* serviceProvider = ServiceProvider::findServiceProvider(context);
if (serviceProvider == NULL)
return 0;
Players* players = serviceProvider->find<Network::Players>();
return players ? players->getNumPlayers() : 0;
}
RBX::SystemAddress Players::findLocalSimulatorAddress(const RBX::Instance* context)
{
if (!getDistributedPhysicsEnabled()) {
return NetworkOwner::Unassigned();
}
if (serverIsPresent(context, false)) {
return NetworkOwner::Server();
}
return Client::findLocalSimulatorAddress(context);
}
int Players::getMaxPlayers() const
{
return maxPlayers;
}
int Players::getPreferredPlayers() const
{
return preferredPlayers;
}
void Players::setMaxPlayers(int value)
{
if (value < 0)
value = 1;
if (value != maxPlayers)
{
maxPlayers = value;
raisePropertyChanged(propPlayerMaxCount);
raisePropertyChanged(propPlayerMaxCountInternal);
}
}
void Players::setPreferredPlayers(int value)
{
if (value < 0)
value = 1;
if (value != preferredPlayers)
{
preferredPlayers = value;
raisePropertyChanged(propPlayerPreferredCount);
raisePropertyChanged(propPlayerPreferredCountInternal);
}
}
/*
id == 0
Local Player yes no
Already Exists
YES ignore reset(id)
NO create(id) create(id)
*/
// only for reflection
Instance* Players::getLocalPlayerDangerous() const
{
return localPlayer.get();
}
shared_ptr<Instance> Players::createLocalPlayer(int userId, bool teleportedIn)
{
if (localPlayer)
throw std::runtime_error("Local player already exists");
{
RBX::Security::Impersonator impersonate(RBX::Security::Replicator_);
localPlayer = Creatable<Instance>::create<Player>();
}
if (RBX::DataModel* dataModel = (RBX::DataModel*)this->getParent())
dataModel->getGuiBuilder().removeSafeChatMenu();
localPlayer->setTeleportedIn(teleportedIn);
{
RBX::Security::Impersonator impersonate(RBX::Security::Replicator_);
localPlayer->setUserId(userId);
}
RBX::RbxDbgInfo::s_instance.PlayerID = userId;
Player::prop_OsPlatform.setValue(localPlayer.get(), DebugSettings::singleton().osPlatform());
localPlayer->setParent(this);
raiseChanged(propLocalPlayer);
shared_ptr<Instance> starterGear = Creatable<Instance>::create<StarterGear>();
starterGear.get()->setParent(localPlayer.get());
if (localPlayer->calculatesSpawnLocationEarly()) {
if (backendProcessing(this) && frontendProcessing(this)) { // play solo mode
localPlayer->doFirstSpawnLocationCalculation(ServiceProvider::findServiceProvider(this), "");
}
}
//Visit GUI Fixes
if (DFFlag::LoadGuisWithoutChar && Network::Players::frontendProcessing(this) && Network::Players::backendProcessing(this))
RBX::DataModel::get(this)->setIsGameLoaded(true);
return localPlayer;
}
void Players::resetLocalPlayer() {
if (localPlayer)
{
localPlayer->unlockParent();
localPlayer->destroy();
localPlayer.reset();
}
}
bool Players::superSafeOn() const
{
return (localPlayer) ? Player::prop_SuperSafeChat.getValue(localPlayer.get()) : false;
}
Players::~Players(void)
{
setConnection(NULL);
}
static void writeMessage(const AbuseReport::Message& msg, XmlElement* messages)
{
XmlElement* element = messages->addChild(RBX::Name::declare("message"));
element->setValue(msg.text);
element->addAttribute(RBX::Name::declare("userID"), msg.userID);
element->addAttribute(RBX::Name::declare("guid"), msg.guid);
}
ChatMessage::ChatMessage(const char* message, ChatType chatType, shared_ptr<Player> source)
:message(message)
, chatType(chatType)
, source(source)
{
Guid::generateStandardGUID(guid);
}
ChatMessage::ChatMessage(const ChatMessage& other, const std::string& newMessage)
:message(newMessage)
, guid(other.guid)
, chatType(other.chatType)
, source(other.source)
, destination(other.destination)
{}
ChatMessage::ChatMessage(const char* message, ChatType chatType, shared_ptr<Player> source, shared_ptr<Player> destination)
:message(message)
, chatType(chatType)
, source(source)
, destination(destination)
{
Guid::generateStandardGUID(guid);
}
bool ChatMessage::isVisibleToPlayer(shared_ptr<Player> player) const
{
return isVisibleToPlayer(player, source, destination, chatType);
}
bool ChatMessage::isVisibleToPlayer(shared_ptr<Player> const player, shared_ptr<Player> const source, shared_ptr<Player> const destination, const ChatMessage::ChatType chatType)
{
switch (chatType)
{
case CHAT_TYPE_ALL:
case CHAT_TYPE_GAME:
return true;
case CHAT_TYPE_TEAM:
return (player && source && !source->getNeutral() && !player->getNeutral() && source->getTeamColor() == player->getTeamColor());
case CHAT_TYPE_WHISPER:
return (player && (source == player || destination == player));
default:
return false;
}
}
std::string ChatMessage::getReportAbuseMessage() const
{
switch (chatType)
{
case CHAT_TYPE_ALL:
return message;
case CHAT_TYPE_GAME:
return "[[game]]" + message;
case CHAT_TYPE_TEAM:
return "[[team]]" + message;
case CHAT_TYPE_WHISPER:
{
std::ostringstream str;
str << "[[to ";
if (destination) {
str << destination->getName();
}
else {
str << "???";
}
str << "]]" << message;
return str.str();
}
default:
return message;
}
}
void AbuseReport::addMessage(shared_ptr<Player> reportingPlayer, const ChatMessage& cm)
{
if (messages.size() >= (size_t)GameSettings::singleton().reportAbuseChatHistory)
return;
if (!reportingPlayer || cm.isVisibleToPlayer(reportingPlayer)) {
int userID = cm.source ? cm.source->getUserID() : 0;
Message m = { userID, cm.getReportAbuseMessage(), cm.guid };
messages.push_back(m);
}
}
AbuseReporter::AbuseReporter(std::string abuseUrl)
:_data(new data())
{
requestProcessor.reset(new worker_thread(boost::bind(&AbuseReporter::processRequests, _data, abuseUrl), "rbx_abusereporter"));
}
void AbuseReporter::add(AbuseReport& r, shared_ptr<Player> reportingPlayer, const std::list<ChatMessage>& chatHistory)
{
// Copy all the messages from the chat history
std::for_each(
chatHistory.begin(),
chatHistory.end(),
boost::bind(&AbuseReport::addMessage, boost::ref(r), reportingPlayer, _1)
);
// Push a new request into the queue
{
boost::mutex::scoped_lock lock(_data->requestSync);
_data->queue.push(r);
}
requestProcessor->wake();
}
worker_thread::work_result AbuseReporter::processRequests(shared_ptr<data> _data, std::string abuseUrl)
{
try
{
AbuseReport r;
{
boost::mutex::scoped_lock lock(_data->requestSync);
if (_data->queue.empty())
return worker_thread::done;
r = _data->queue.front();
}
std::stringstream body;
{
TextXmlWriter writer(body);
XmlElement root(RBX::Name::declare("report"));
root.addAttribute(RBX::Name::declare("userID"), r.submitterID);
root.addAttribute(RBX::Name::declare("placeID"), r.placeID);
root.addAttribute(RBX::Name::declare("gameJobID"), r.gameJobID);
root.addChild(new XmlElement(RBX::Name::declare("comment"), r.comment));
XmlElement* messages = root.addChild(new XmlElement(RBX::Name::declare("messages")));
std::for_each(r.messages.begin(), r.messages.end(), boost::bind(&writeMessage, _1, messages));
writer.serialize(&root);
body.flush();
}
std::string response;
Http(abuseUrl).post(body, Http::kContentTypeDefaultUnspecified, false, response);
StandardOut::singleton()->printf(MESSAGE_SENSITIVE, "Posted abuse report to %s", abuseUrl.c_str());
StandardOut::singleton()->print(MESSAGE_INFO, "Posted abuse report");
}
catch (RBX::base_exception& e)
{
StandardOut::singleton()->printf(MESSAGE_SENSITIVE, "Unable to post abuse report to %s. %s", abuseUrl.c_str(), e.what());
StandardOut::singleton()->printf(MESSAGE_WARNING, "Unable to post abuse report. %s", e.what());
}
{
boost::mutex::scoped_lock lock(_data->requestSync);
_data->queue.pop();
}
return worker_thread::more;
}
bool Players::canReportAbuse() const
{
return (abuseReporter || clientIsPresent(this));
}
void Players::reportAbuse(Player* player, const std::string& comment)
{
if (abuseReporter)
{
if (player)
{
StandardOut::singleton()->printf(MESSAGE_INFO, "Submitting abuse report on %s", player ? player->getName().c_str() : "[[[anonymous]]]");
}
else
{
StandardOut::singleton()->printf(MESSAGE_INFO, "Submitting abuse report on game");
}
AbuseReport r;
r.submitterID = localPlayer ? localPlayer->getUserID() : 0;
r.allegedAbuserID = player ? player->getUserID() : 0;
r.comment = comment;
if (RBX::DataModel* dataModel = RBX::DataModel::get(this))
{
r.placeID = dataModel->getPlaceID();
r.gameJobID = dataModel->getJobId();
}
abuseReporter->add(r, localPlayer, chatHistory);
}
else
{
// Send the abuse report to somebody else for processing
StandardOut::singleton()->print(MESSAGE_INFO, "Sending abuse report to game server via Raknet");
if (!rakPeer)
throw std::runtime_error("Can't report abuse: Not in a networked game");
shared_ptr<RakNet::BitStream> bitStream(new RakNet::BitStream());
*bitStream << (unsigned char)ID_REPORT_ABUSE;
// Write the alleged abuser (or null)
*bitStream << (player ? player->getUserID() : (int)0);
// Write the sender's comment
*bitStream << comment;
// Send ID_REPORT_ABUSE (this packet is received by the Server)
rakPeer->Send(bitStream, HIGH_PRIORITY, RELIABLE, CHAT_CHANNEL, UNASSIGNED_SYSTEM_ADDRESS, true);
}
}
void Players::reportAbuseLua(shared_ptr<Instance> instance, std::string reason, std::string comment)
{
shared_ptr<Player> player;
if (instance)
{
player = Instance::fastSharedDynamicCast<Player>(instance);
if (!player)
throw RBX::runtime_error("player must be a Player object or null");
}
if (!localPlayer)
throw RBX::runtime_error("You can only report-abuse from a client machine");
if (localPlayer->getUserID() > 0)
{
std::ostringstream strstream;
strstream << reason << ";" << comment;
reportAbuse(player ? player.get() : NULL, strstream.str());
}
}
void Players::gamechat(const std::string& message)
{
if (localPlayer)
throw std::runtime_error("Only servers can produce game chat");
if (!rakPeer)
return;
shared_ptr<RakNet::BitStream> bitStream(new RakNet::BitStream());
*bitStream << (unsigned char)ID_CHAT_GAME;
*bitStream << message;
rakPeer->Send(bitStream, CHAT_PRIORITY, CHAT_RELIABILITY, CHAT_CHANNEL, UNASSIGNED_SYSTEM_ADDRESS, true);
}
void Players::checkChat(const std::string& message)
{
if (!localPlayer)
throw std::runtime_error("No local Player to chat from");
if (superSafeOn())
{
if (message.find("/sc ") != 0)
throw std::runtime_error("SuperSafe chat is on");
}
}
static RBX::Guid::Data readPlayerIdentifier(RakNet::BitStream& inBitStream)
{
RBX::Guid::Data id;
inBitStream >> id.scope;
inBitStream >> id.index;
return id;
}
static void writePlayerIdentifier(RakNet::BitStream& bitStream, const boost::intrusive_ptr<GuidItem<Instance>::Registry>& guidRegistery, shared_ptr<Player> player)
{
// Write the sender
guidRegistery->registerGuid(player.get());
RBX::Guid::Data id;
player->getGuid().extract(id);
// This is very inefficient without a dictionary, but we can't
// use the dictionary here - these packets go out-of-order
bitStream << id.scope;
bitStream << id.index;
}
void Players::whisperChat(std::string message, shared_ptr<Instance> instance)
{
checkChat(message);
shared_ptr<Player> player = Instance::fastSharedDynamicCast<Player>(instance);
if (!player || player->getParent() != this)
throw std::runtime_error("Player object is not a player to chat to");
shared_ptr<RakNet::BitStream> bitStream(new RakNet::BitStream());
*bitStream << (unsigned char)ID_CHAT_PLAYER;
// Write the sender
writePlayerIdentifier(*bitStream, getGuidRegistry(), localPlayer);
// Write the receiver
writePlayerIdentifier(*bitStream, getGuidRegistry(), player);
// Write the text
*bitStream << message;
// Send ID_CHAT_PLAYER
rakPeer->Send(bitStream, CHAT_PRIORITY, CHAT_RELIABILITY, CHAT_CHANNEL, UNASSIGNED_SYSTEM_ADDRESS, true);
ChatMessage event(message.c_str(), ChatMessage::CHAT_TYPE_WHISPER, localPlayer, player);
raiseChatMessageSignal(event);
}
void Players::teamChat(std::string message)
{
checkChat(message);
shared_ptr<RakNet::BitStream> bitStream(new RakNet::BitStream());
*bitStream << (unsigned char)ID_CHAT_TEAM;
// Write the sender
writePlayerIdentifier(*bitStream, getGuidRegistry(), localPlayer);
// Write the text
*bitStream << message;
// Send ID_CHAT_TEAM
rakPeer->Send(bitStream, CHAT_PRIORITY, CHAT_RELIABILITY, CHAT_CHANNEL, UNASSIGNED_SYSTEM_ADDRESS, true);
ChatMessage event(message.c_str(), ChatMessage::CHAT_TYPE_TEAM, localPlayer);
raiseChatMessageSignal(event);
}
void Players::chat(std::string message)
{
checkChat(message);
if (rakPeer)
{
shared_ptr<RakNet::BitStream> bitStream(new RakNet::BitStream());
*bitStream << (unsigned char)ID_CHAT_ALL;
// Write the sender
writePlayerIdentifier(*bitStream, getGuidRegistry(), localPlayer);
// Write the text
*bitStream << message;
// Send ID_CHAT_ALL
rakPeer->Send(bitStream, CHAT_PRIORITY, CHAT_RELIABILITY, CHAT_CHANNEL, UNASSIGNED_SYSTEM_ADDRESS, true);
}
ChatMessage event(message.c_str(), ChatMessage::CHAT_TYPE_ALL, localPlayer);
raiseChatMessageSignal(event);
}
// returns true if we were able to parse an intelligible command
static bool ParseChatCommand(const ChatMessage& msg, bool isSelf, std::string* parsed_msg)
{
if (msg.message.size() <= 0 || msg.message[0] != '/')
return false;
std::vector<std::string> v;
boost::char_separator<char> sep(" -_/,");
boost::tokenizer<boost::char_separator<char> > tokens(msg.message, sep);
for (boost::tokenizer<boost::char_separator<char> > ::iterator tok_iter = tokens.begin();
tok_iter != tokens.end(); ++tok_iter)
{
v.push_back(*tok_iter);
}
if (v.size() == 0)
return false;
// Now have tokenized version of command
// lowercase the actual command
safeToLower(v[0]);
// SafeChat Speak
if (v[0] == "sc") {
if (parsed_msg)
*parsed_msg = SafeChat::singleton().getMessage(v);
return true;
}
return false;
}
void Players::raisePlayerChattedSignal(const ChatMessage& message)
{
if (message.chatType == ChatMessage::CHAT_TYPE_ALL)
{
if (message.source)
message.source->chattedSignal(message.message, message.destination);
}
if (localPlayer)
{
switch (message.chatType)
{
case ChatMessage::CHAT_TYPE_ALL:
playerChattedSignal(PLAYER_CHAT_TYPE_ALL, message.source, message.message, shared_ptr<Instance>());
break;
case ChatMessage::CHAT_TYPE_TEAM:
playerChattedSignal(PLAYER_CHAT_TYPE_TEAM, message.source, message.message, shared_ptr<Instance>());
break;
case ChatMessage::CHAT_TYPE_WHISPER:
playerChattedSignal(PLAYER_CHAT_TYPE_WHISPER, message.source, message.message, message.destination);
break;
case ChatMessage::CHAT_TYPE_GAME:
gameAnnounceSignal(message.message);
break;
}
}
}
void Players::raiseChatMessageSignal(const ChatMessage& message)
{
bool fromOthers = (localPlayer == NULL) || (message.source != localPlayer);
std::string msg;
if (ParseChatCommand(message, !fromOthers, &msg))
{
ChatMessage parsedMessage(message, msg);
chatMessageSignal(parsedMessage);
raisePlayerChattedSignal(parsedMessage);
}
else
{
// Check if we are using super safe chat - if so, forget about it
if (localPlayer && localPlayer->getSuperSafeChat())
return;
chatMessageSignal(message);
raisePlayerChattedSignal(message);
}
}
void Players::addChatMessage(const ChatMessage& message)
{
chatHistory.push_front(message);
while (chatHistory.size() > (size_t)GameSettings::singleton().chatHistory)
chatHistory.pop_back();
raiseChatMessageSignal(message);
}
const boost::intrusive_ptr<GuidItem<Instance>::Registry>& Players::getGuidRegistry()
{
if (!guidRegistry)
guidRegistry = ServiceProvider::create<GuidRegistryService>(this)->registry;
return guidRegistry;
}
Players::ReceiveResult Players::OnReceiveChat(Player* sourceValidation, RakNet::RakPeerInterface* peer, RakNet::Packet* packet, unsigned char chatType)
{
RakNet::BitStream inBitstream(packet->data, packet->length, false);
inBitstream.IgnoreBits(8); // Ignore the packet id
RBX::Guid::Data sourceGuid;
RBX::Guid::Data destinationGuid;
shared_ptr<Instance> sourceInstance;
shared_ptr<Instance> destinationInstance;
if (chatType != ID_CHAT_GAME) {
sourceGuid = readPlayerIdentifier(inBitstream);
sourceInstance = getGuidRegistry()->getByGuid(sourceGuid);
if (sourceValidation)
{
if (sourceInstance.get() != sourceValidation)
{
// bad data. Hacker?
return Players::PLAYERS_STOP_PROCESSING_AND_DEALLOCATE;
}
}
}
if (chatType == ID_CHAT_PLAYER) {
destinationGuid = readPlayerIdentifier(inBitstream);
destinationInstance = getGuidRegistry()->getByGuid(destinationGuid);
if (DFFlag::FilterInvalidWhisper && !destinationInstance.get())
{
// bad data. Hacker?
return Players::PLAYERS_STOP_PROCESSING_AND_DEALLOCATE;
}
}
// Read message
std::string message;
inBitstream >> message;
if (!sourceInstance)
{
if (chatType == ID_CHAT_GAME) {
ChatMessage event(message.c_str(), ChatMessage::CHAT_TYPE_GAME, shared_from<Player>(NULL));
addChatMessage(event);
}
else {
StandardOut::singleton()->printf(MESSAGE_SENSITIVE, "Chat message from unknown Player %s %s", RakNetAddressToString(packet->systemAddress).c_str(), message.c_str());
}
}
else if (sourceInstance->getParent() == this) // confirm the player hasn't been bumped
{
shared_ptr<Player> sourcePlayer = shared_polymorphic_downcast<Player>(sourceInstance);
const bool kIsBackendProcessing = backendProcessing(this);
// Check on the client (game client, not game server) to see if we should display the chat based on whether we're in the right team
// NOTE: Everyone RECEIVES all chat!
switch (chatType)
{
case ID_CHAT_ALL:
{
ChatMessage event(message.c_str(), ChatMessage::CHAT_TYPE_ALL, sourcePlayer);
if (kIsBackendProcessing || event.isVisibleToPlayer(localPlayer))
addChatMessage(event);
break;
}
case ID_CHAT_TEAM:
{
ChatMessage event(message.c_str(), ChatMessage::CHAT_TYPE_TEAM, sourcePlayer);
if (kIsBackendProcessing || event.isVisibleToPlayer(localPlayer))
addChatMessage(event);
break;
}
case ID_CHAT_PLAYER:
{
shared_ptr<Player> destinationPlayer;
if (DFFlag::FilterInvalidWhisper)
{
destinationPlayer = shared_dynamic_cast<Player>(destinationInstance);
if (!destinationPlayer.get())
{
// bad data. Hacker?
return Players::PLAYERS_STOP_PROCESSING_AND_DEALLOCATE;
}
}
else
{
destinationPlayer = shared_polymorphic_downcast<Player>(destinationInstance);
}
ChatMessage event(message.c_str(), ChatMessage::CHAT_TYPE_WHISPER, sourcePlayer, destinationPlayer);
if (kIsBackendProcessing || event.isVisibleToPlayer(localPlayer))
addChatMessage(event);
break;
}
}
// Note: This only happens on the game server
// Check the message through the content filter for abusive content
if (kIsBackendProcessing)
{
if (0 == message.length())
return Players::PLAYERS_STOP_PROCESSING_AND_DEALLOCATE;
// This is our switch to allow us to turn off content filtering and check it against the local Diogenes (player's client)
// TODO: The local diogenes code is ugly, we send messages to everyone and then check to see if it's not in diogenes before displaying.
// I'd like to change this to the game server to avoid player's hacking their diogenes file and seeing bad text, but
// the player who actually sends the message needs to be displayed it regardless (so they dont know we're filtering), and this
// is done when he receives the message. So I would have to have him display it on his screen manually instead of on receive.
// But then I'd have to check to see if he's received his own message to make sure he doesn't see his msgs twice... - Tim
if (chatFilterUrl == "")
{
// Forward message to other peers
peer->Send((const char*)packet->data, packet->length, CHAT_PRIORITY, CHAT_RELIABILITY, CHAT_CHANNEL, packet->systemAddress, true);
return Players::PLAYERS_STOP_PROCESSING_AND_DEALLOCATE;
}
ChatFilter::FilteredChatMessageCallback callback = boost::bind(
&Players::sendFilteredChatMessageSignalHelper, shared_from(this),
packet->systemAddress,
buildBasicChatBitStream(sourceInstance.get(), destinationInstance.get(), chatType),
sourceInstance,
shared_ptr<ChatMessage>(),
_1);
ChatFilter* chatFilter = ServiceProvider::create<WebChatFilter>(this);
chatFilter->filterMessage(sourcePlayer, destinationInstance, message, callback);
return Players::PLAYERS_STOP_PROCESSING_AND_DEALLOCATE;
}
}
return Players::PLAYERS_STOP_PROCESSING_AND_DEALLOCATE;
}
void Players::sendFilteredChatMessageSignalHelper(const RakNet::SystemAddress& systemAddress,
const shared_ptr<RakNet::BitStream>& baseData, const shared_ptr<Instance> sourceInstance, shared_ptr<ChatMessage> chatEvent, const ChatFilter::Result& result) {
DataModel::LegacyLock l(DataModel::get(this), DataModelJob::Write);
sendFilteredChatMessageSignal(systemAddress, baseData, sourceInstance,
result.whitelistFilteredMessage, result.blacklistFilteredMessage);
}
Players::ReceiveResult Players::OnReceiveReportAbuse(Player* player, RakNet::RakPeerInterface* peer, RakNet::Packet* packet)
{
// Read all the info
RakNet::BitStream inBitstream(packet->data, packet->length, false);
inBitstream.IgnoreBits(8); // Ignore the packet id
AbuseReport r;
r.submitterID = player ? player->getUserID() : 0;
inBitstream >> r.allegedAbuserID;
inBitstream >> r.comment;
if (RBX::DataModel* dataModel = RBX::DataModel::get(this))
{
r.placeID = dataModel->getPlaceID();
r.gameJobID = dataModel->getJobId();
}
{
std::ostringstream str;
str << "AbuserID:" << r.allegedAbuserID << ";" << r.comment;
r.comment = str.str();
}
shared_ptr<Player> reportingPlayer = shared_from(player);
if (abuseReporter)
abuseReporter->add(r, reportingPlayer, chatHistory);
abuseReportedReceived(r);
return Players::PLAYERS_STOP_PROCESSING_AND_DEALLOCATE; // We handled the message. Nobody else needs to
}
void Players::setAbuseReportUrl(std::string value)
{
if (value.empty())
abuseReporter.reset();
else
abuseReporter.reset(new AbuseReporter(value));
}
void Players::setChatFilterUrl(std::string value)
{
chatFilterUrl = value;
}
std::string Players::getLoadDataUrl(int userId) const
{
if (loadDataUrl.empty())
throw std::runtime_error("No LoadData url set");
return RBX::format(loadDataUrl.c_str(), userId);
}
void Players::setLoadDataUrl(std::string value)
{
loadDataUrl = value;
}
std::string Players::getSaveDataUrl(int userId) const
{
if (saveDataUrl.empty())
throw std::runtime_error("No SaveData url set");
return RBX::format(saveDataUrl.c_str(), userId);
}
void Players::setSaveDataUrl(std::string value)
{
saveDataUrl = value;
}
std::string Players::getSaveLeaderboardDataUrl(int userId) const
{
if (saveLeaderboardDataUrl.empty())
throw std::runtime_error("No SaveLeaderboardData url set");
return RBX::format(saveLeaderboardDataUrl.c_str(), userId);
}
void Players::setSaveLeaderboardDataUrl(std::string value)
{
saveLeaderboardDataUrl = value;
}
void Players::addLeaderboardKey(std::string key)
{
if (!hasLeaderboardKey(key))
leaderboardKeys.insert(key);
}
bool Players::hasLeaderboardKey(const std::string& key) const
{
return leaderboardKeys.find(key) != leaderboardKeys.end();
}
boost::unordered_set<std::string>::const_iterator Players::beginLeaderboardKey() const
{
return leaderboardKeys.begin();
}
boost::unordered_set<std::string>::const_iterator Players::endLeaderboardKey() const
{
return leaderboardKeys.end();
}
void Players::setSysHash(std::string hash)
{
//Check for new Mac hash embedded into the goldenHash
size_t pos = hash.find(":");
if (pos != std::string::npos) {
//The mac client has a different hash
goldenHash = hash.substr(0, pos);
goldenHash2 = hash.substr(pos + 1);
}
else {
goldenHash2 = hash;
goldenHash = hash;
}
FASTLOGS(FLog::GoldenHashes, "Hashses set through script, Windows hash: %s", goldenHash);
FASTLOGS(FLog::GoldenHashes, "Mac hash: %s", goldenHash2);
}
void Players::setSysStatsUrl(std::string value)
{
sysStatsUrl = value;
}
void Players::setGoldenHashes(const std::string& windowsHash, const std::string& macHash, const std::string& windowsPlayerBetaHash)
{
if (windowsHash.length() > 0)
goldenHash = windowsHash;
if (macHash.length() > 0)
goldenHash2 = macHash;
if (windowsPlayerBetaHash.length() > 0)
goldenHash3 = windowsPlayerBetaHash;
if (goldenHash.size() > 0 || goldenHash2.size() > 0 || goldenHash3.size() > 0)
canKickBecauseRunningInRealGameServer = true;
FASTLOG1(FLog::GoldenHashes, "Golden hashes set, should kick: %u", canKickBecauseRunningInRealGameServer);
FASTLOGS(FLog::GoldenHashes, "Windows hash: %s", goldenHash);
FASTLOGS(FLog::GoldenHashes, "Mac hash: %s", goldenHash2);
FASTLOGS(FLog::GoldenHashes, "WP Beta: %s ", goldenHash3);
}
void Players::setGoldenHashes2(const std::set<std::string>& hashes)
{
boost::mutex::scoped_lock lock(goldenHashesMutex);
goldenHashes = hashes;
if (goldenHashes.size() > 0)
canKickBecauseRunningInRealGameServer = true;
FASTLOG1(FLog::GoldenHashes, "Golden hashes set, should kick: %u", canKickBecauseRunningInRealGameServer);
for (std::set<std::string>::iterator i = goldenHashes.begin(); i != goldenHashes.end(); i++)
{
FASTLOGS(FLog::GoldenHashes, "hash: %s", *i);
}
}
void Players::setGoldMemHashes(const MemHashConfigs& hashes)
{
boost::mutex::scoped_lock lock(goldMemHashesMutex);
goldMemHashes = hashes;
}
void Players::setBuildUserPermissionsUrl(std::string value)
{
buildUserPermissionsUrl = value;
}
bool Players::hasBuildUserPermissionsUrl() const
{
return !buildUserPermissionsUrl.empty();
}
std::string Players::getBuildUserPermissionsUrl(int playerId) const
{
return format(buildUserPermissionsUrl.c_str(), playerId);
}
void Players::friendEventFired(int userId, int otherUserId, FriendService::FriendEventType friendEvent)
{
//Frontend and backend
shared_ptr<Player> playerA = getPlayerByID(userId);
shared_ptr<Player> playerB = getPlayerByID(otherUserId);
if (playerA && playerB) {
friendRequestEvent(playerA, playerB, friendEvent);
}
}
void Players::friendStatusChanged(int userId, int otherUserId, FriendService::FriendStatus friendStatus)
{
shared_ptr<Player> playerA = getPlayerByID(userId);
shared_ptr<Player> playerB = getPlayerByID(otherUserId);
if (playerA && playerB)
{
playerA->onFriendStatusChanged(playerB, friendStatus);
}
}
void Players::friendServiceRequest(bool makeFriends, weak_ptr<Player> sourcePlayer, int otherUserId)
{
shared_ptr<Player> playerA = sourcePlayer.lock();
shared_ptr<Player> playerB = getPlayerByID(otherUserId);
if (!playerA || !playerB)
return;
if (RBX::FriendService* fs = ServiceProvider::find<FriendService>(this)) {
if (!makeFriends) {
fs->rejectFriendRequestOrBreakFriendship(playerA->getUserID(), playerB->getUserID());
}
else {
fs->issueFriendRequestOrMakeFriendship(playerA->getUserID(), playerB->getUserID());
}
}
}
void Players::setCharacterAutoSpawnProperty(bool value)
{
if (value != characterAutoSpawn)
{
characterAutoSpawn = value;
raisePropertyChanged(propCharacterAutoSpawn);
}
}
bool Players::getShouldAutoSpawnCharacter() const
{
return !isCloudEdit(this) && characterAutoSpawn;
}
void Players::loadLocalPlayerGuis()
{
//Fixes issues with characterAutoSpawn not giving guis.
//If characterAutoSpawn is false, and we're Filtering or Visit mode
bool isVisit = (Network::Players::frontendProcessing(this) && Network::Players::backendProcessing(this));
bool isFiltering = ServiceProvider::find<Workspace>(this)->getNetworkFilteringEnabled();
if ((isVisit || isFiltering) && !characterAutoSpawn)
{
localPlayer->rebuildGui();
}
}
void Players::gotBlockUserSuccess(std::string response, bool blockUser, int blockerUserId, int blockeeUserId, boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction)
{
std::string methodName = (blockUser ? "Players:BlockUser" : "Players:UnblockUser");
if (!response.empty())
{
shared_ptr<const Reflection::ValueTable> jsonResult(rbx::make_shared<const Reflection::ValueTable>());
bool parseResult = WebParser::parseJSONTable(response, jsonResult);
if (!parseResult)
{
std::string errorString = methodName + " could not parse JSON response from web.";
if (!errorFunction.empty())
{
errorFunction(errorString);
}
else
{
event_serverFinishedBlockUser.fireAndReplicateEvent(this, blockerUserId, blockeeUserId, errorString);
}
return;
}
Reflection::Variant successVar = jsonResult->at("success");
bool success = successVar.get<bool>();
if (!resumeFunction.empty())
{
if (success)
{
resumeFunction("");
}
else
{
errorFunction(methodName + " response indicating block request failed");
}
return;
}
else
{
if (success)
{
event_serverFinishedBlockUser.fireAndReplicateEvent(this, blockerUserId, blockeeUserId, "");
}
else
{
event_serverFinishedBlockUser.fireAndReplicateEvent(this, blockerUserId, blockeeUserId, methodName + " response indicating block request failed");
}
}
}
else
{
std::string errorString = methodName + " got empty response from web.";
if (!errorFunction.empty())
{
errorFunction(errorString);
}
else
{
event_serverFinishedBlockUser.fireAndReplicateEvent(this, blockerUserId, blockeeUserId, errorString);
}
}
}
void Players::gotBlockUserError(std::string error, bool blockUser, int blockerUserId, int blockeeUserId, boost::function<void(std::string)> errorFunction)
{
const std::string methodName = (blockUser ? "Players:BlockUser" : "Players:UnblockUser");
const std::string errorString = methodName + " failed because " + error;
if (!errorFunction.empty())
{
errorFunction(errorString);
}
else
{
event_serverFinishedBlockUser.fireAndReplicateEvent(this, blockerUserId, blockeeUserId, errorString);
}
}
void Players::clientReceiveBlockUserFinished(int blockerUserId, int blockeeUserId, std::string errorString)
{
std::pair<int, int> blockerPair(blockerUserId, blockeeUserId);
if (clientBlockUserMap.find(blockerPair) != clientBlockUserMap.end())
{
if (errorString.empty())
{
clientBlockUserMap[blockerPair].first(errorString);
}
else
{
clientBlockUserMap[blockerPair].second(errorString);
}
clientBlockUserMap.erase(blockerPair);
return;
}
std::pair<int, int> reverseBlockerPair(blockeeUserId, blockerUserId);
if (clientBlockUserMap.find(reverseBlockerPair) != clientBlockUserMap.end())
{
if (errorString.empty())
{
clientBlockUserMap[blockerPair].first(errorString);
}
else
{
clientBlockUserMap[blockerPair].second(errorString);
}
clientBlockUserMap.erase(reverseBlockerPair);
return;
}
}
void Players::serverMakeBlockUserRequest(bool blockUser, int blockerUserId, int blockeeUserId, boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction)
{
if (RBX::HttpRbxApiService* apiService = RBX::ServiceProvider::find<RBX::HttpRbxApiService>(this))
{
std::stringstream params;
params << "blockerId=" << blockerUserId << "&blockeeId=" << blockeeUserId;
std::string apiPath = blockUser ? "userblock/blockuser" : "userblock/unblockuser";
apiService->postAsync(apiPath, params.str(), true, RBX::PRIORITY_DEFAULT, RBX::HttpService::APPLICATION_URLENCODED,
boost::bind(&Players::gotBlockUserSuccess, this, _1, blockUser, blockerUserId, blockeeUserId, resumeFunction, errorFunction),
boost::bind(&Players::gotBlockUserError, this, _1, blockUser, blockerUserId, blockeeUserId, errorFunction));
}
else
{
errorFunction("Players:BlockUser could not find ApiService.");
return;
}
}
void Players::internalBlockUser(int blockerUserId, int blockeeUserId, bool isBlocking, boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction)
{
if (blockerUserId <= 0 || blockeeUserId <= 0)
{
errorFunction("Players:BlockUser or Player:UnblockUser failed because the blocker and/or blockee userId is <= 0");
return;
}
if (clientIsPresent(this))
{
std::pair<int, int> blockerPair(blockerUserId, blockeeUserId);
std::pair<int, int> reverseBlockerPair(blockeeUserId, blockerUserId);
if (clientBlockUserMap.find(blockerPair) != clientBlockUserMap.end() ||
clientBlockUserMap.find(reverseBlockerPair) != clientBlockUserMap.end())
{
errorFunction(RBX::format("Currently processing block request for userIds %i and %i, please wait until these requests return a value before submitting a new request.", blockerUserId, blockeeUserId));
return;
}
clientBlockUserMap[blockerPair] = std::pair< boost::function<void(std::string)>, boost::function<void(std::string)> >(resumeFunction, errorFunction);
event_blockUserFromClient.fireAndReplicateEvent(this, blockerUserId, blockeeUserId, isBlocking);
}
else
{
serverMakeBlockUserRequest(isBlocking, blockerUserId, blockeeUserId, resumeFunction, errorFunction);
}
}
void Players::blockUser(int blockerUserId, int blockeeUserId, boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction)
{
internalBlockUser(blockerUserId, blockeeUserId, true, resumeFunction, errorFunction);
}
void Players::unblockUser(int blockerUserId, int blockeeUserId, boost::function<void(std::string)> resumeFunction, boost::function<void(std::string)> errorFunction)
{
internalBlockUser(blockerUserId, blockeeUserId, false, resumeFunction, errorFunction);
}
void Players::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider)
{
if (oldProvider)
{
blockUserClientSignalConnection.disconnect();
blockUserFinishedFromServerConnection.disconnect();
if (DFFlag::LoadGuisWithoutChar)
loadLocalPlayerGuisConnection.disconnect();
}
Super::onServiceProvider(oldProvider, newProvider);
if (newProvider)
{
if (backendProcessing(newProvider))
{
blockUserClientSignalConnection = blockUserRequestFromClientSignal.connect(boost::bind(&Players::serverMakeBlockUserRequest, this, _3, _1, _2, boost::function<void(std::string)>(), boost::function<void(std::string)>()));
}
if (frontendProcessing(newProvider))
{
blockUserFinishedFromServerConnection = blockUserFinishedFromServerSignal.connect(boost::bind(&Players::clientReceiveBlockUserFinished, this, _1, _2, _3));
if (DFFlag::LoadGuisWithoutChar)
{
if (ServiceProvider::create<ReplicatedFirst>(newProvider))
loadLocalPlayerGuisConnection = RBX::DataModel::get(newProvider)->gameLoadedSignal.connect(boost::bind(&Players::loadLocalPlayerGuis, this));
}
}
}
}
void Players::setConnection(ConcurrentRakPeer* rakPeer)
{
this->rakPeer = rakPeer;
}
bool Players::askAddChild(const Instance* instance) const
{
return Instance::fastDynamicCast<Player>(instance) != NULL;
}
ModelInstance* Players::findLocalCharacter(RBX::Instance* context)
{
if (Player* player = findLocalPlayer(context)) {
return player->getCharacter();
}
else {
return NULL;
}
}
const ModelInstance* Players::findConstLocalCharacter(const RBX::Instance* context)
{
if (const Player* player = findConstLocalPlayer(context)) {
return player->getConstCharacter();
}
else {
return NULL;
}
}
Player* Players::findLocalPlayer(Instance* context)
{
if (Players* players = ServiceProvider::find<Players>(context)) {
return players->getLocalPlayer();
}
else {
return NULL;
}
}
const Player* Players::findConstLocalPlayer(const Instance* context)
{
if (const Players* players = ServiceProvider::find<Players>(context)) {
return players->getConstLocalPlayer();
}
else {
return NULL;
}
}
shared_ptr<Player> Players::findAncestorPlayer(const RBX::Instance* descendent)
{
if (Players* players = ServiceProvider::find<Players>(descendent)) {
//First check is the descendent is from one of our Players
//Must use dynamic_cast since non-Players can be added to the Players object
Player* player = Instance::fastDynamicCast<Player>(players->findFirstAncestorOf(descendent).get());
if (player) return shared_from<Player>(player);
//Then check if this is an ancestor of one of our Players "Character" object (the ModelInstance in the Workspace)
shared_ptr<const Instances> playersVector = players->getPlayers();
for (Instances::const_iterator iter = playersVector->begin(); iter != playersVector->end(); ++iter) {
if ((player = boost::polymorphic_downcast<Player*>(iter->get()))) {
if (player->getCharacter()->isAncestorOf(descendent)) {
return shared_from<Player>(player);
}
}
}
}
return shared_ptr<Player>();
}
shared_ptr<Player> Players::findPlayerWithAddress(const RBX::SystemAddress& playerAddress, const RBX::Instance* context)
{
if (Players* players = ServiceProvider::find<Players>(context))
{
//First check is the descendent is from one of our Players
//Must use dynamic_cast since non-Players can be added to the Players object
shared_ptr<const Instances> playersVector = players->getPlayers();
for (Instances::const_iterator iter = playersVector->begin(); iter != playersVector->end(); ++iter)
{
if (shared_ptr<Player> player = Instance::fastSharedDynamicCast<Player>(*iter))
{
if (player->getRemoteAddressAsRbxAddress() == playerAddress)
{
return player;
}
}
}
}
return shared_ptr<Player>();
}
shared_ptr<Player> Players::getPlayerByID(int userID)
{
Instances::const_iterator iter = players->begin();
Instances::const_iterator end = players->end();
for (; iter != end; ++iter)
{
shared_ptr<Player> player = shared_polymorphic_downcast<Player>(*iter);
if (player->getUserID() == userID)
return player;
}
return shared_ptr<Player>();
}
shared_ptr<Instance> Players::getPlayerInstanceByID(int userID)
{
return getPlayerByID(userID);
}
shared_ptr<Instance> Players::playerFromCharacter(shared_ptr<Instance> character)
{
if (!character)
return shared_ptr<Instance>();
shared_ptr<const Instances> myPlayers = getPlayers();
Instances::const_iterator iter = myPlayers->begin();
Instances::const_iterator end = myPlayers->end();
for (; iter != end; ++iter)
{
Player* player = boost::polymorphic_downcast<Player*>(iter->get());
if (player->getCharacter() == character.get())
return *iter;
}
return shared_ptr<Instance>();
}
Player* Players::getPlayerFromCharacter(RBX::Instance* character)
{
if (Players* players = ServiceProvider::find<Network::Players>(character))
{
shared_ptr<Instance> player = players->playerFromCharacter(shared_from<Instance>(character));
return boost::polymorphic_downcast<Player*>(player.get());
}
return NULL;
}
void Players::onDescendantRemoving(const shared_ptr<Instance>& instance)
{
if (Players::backendProcessing(this, false))
if (Player* player = Instance::fastDynamicCast<Player>(instance.get()))
player->lockParent(); // prevent the player from coming back in (this is an exploit protection)
Super::onDescendantRemoving(instance);
}
void Players::onChildRemoving(Instance* child)
{
if (Player* player = Instance::fastDynamicCast<Player>(child))
{
if (FriendService* friendService = ServiceProvider::find<FriendService>(this)) {
friendService->playerRemoving(player->getUserID());
}
{
boost::shared_ptr<Instances>& c = players.write();
c->erase(std::find(c->begin(), c->end(), shared_from(child)));
}
if (DFFlag::FirePlayerAddedAndPlayerRemovingOnClient || Players::backendProcessing(this, false))
{
playerRemovingSignal(shared_from(player));
playerRemovingLateSignal(shared_from(player));
}
Http::playerCount = getNumPlayers();
}
}
static void callServerStop(DataModel* dm)
{
if (Server* server = dm->find<Server>())
{
server->stop();
}
}
void Players::processRemoteEvent(const Reflection::EventDescriptor& descriptor, const Reflection::EventArguments& args, const RBX::SystemAddress& source)
{
if (DFFlag::CloudEditSupportPlayersKickAndShutdown &&
descriptor == event_requestCloudEditKick && isCloudEdit(this))
{
if (args.size() == 1 && args[0].isNumber())
{
int playerId = args[0].cast<int>();
disconnectPlayer(playerId, Replicator::DisconnectReason::DisconnectReason_CloudEditKick);
}
}
else if (DFFlag::CloudEditSupportPlayersKickAndShutdown &&
descriptor == event_requestCloudEditShutdown && isCloudEdit(this))
{
if (DataModel* dm = DataModel::get(this))
{
dm->submitTask(&callServerStop, DataModelJob::Write);
}
}
else
{
Super::processRemoteEvent(descriptor, args, source);
}
}
void Players::reportScriptSecurityError(int userId, std::string hash, std::string error, std::string stack)
{
}
void Players::killPlayer(int userId)
{
Player* player = Instance::fastDynamicCast<Player>(getPlayerByID(userId).get());
if (player) {
// TODO: Null check???
player->getCharacter()->findFirstChildOfType<Humanoid>()->setHealth(0.0f);
}
}
static void whoCaresResponse(std::string* result, std::exception* exception)
{
}
void Players::disconnectPlayer(Instance& instance, int userId, int reason)
{
for (size_t i = 0; i < instance.numChildren(); ++i)
{
if (Replicator* proxy = Instance::fastDynamicCast<Replicator>(instance.getChild(i)))
if (Player* player = proxy->findTargetPlayer())
if (player->getUserID() == userId)
{
FASTLOG(FLog::Network, "Players service requests player disconnect");
if (frontendProcessing(player))
proxy->sendDisconnectionSignal("server", false);
proxy->requestDisconnect(Replicator::DisconnectReason(reason));
}
}
}
void Players::disconnectPlayer(int userId, int reason)
{
if (Server* server = ServiceProvider::find<Server>(this))
{
if (DataModel* dm = DataModel::get(this))
{
if (reason == Replicator::DisconnectReason_LuaKick)
{
int placeID = dm->getPlaceID();
RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "ServerLuaKick", boost::lexical_cast<std::string>(placeID).c_str());
}
}
disconnectPlayer(*server, userId, reason);
}
}
void Players::disconnectPlayerLocal(int userId, int reason)
{
if (Client* client = ServiceProvider::find<Client>(this))
{
if (DataModel* dm = DataModel::get(this))
{
if (reason == Replicator::DisconnectReason_LuaKick)
{
int placeID = dm->getPlaceID();
RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "LocalLuaKick", boost::lexical_cast<std::string>(placeID).c_str());
}
}
// Remove the comment at the down if you already prepared your sysstats.
// disconnectPlayer(*client, userId, reason);
}
}
bool Players::hashMatches(const std::string& hash)
{
if (FFlag::DebugLocalRccServerConnection)
{
return true;
}
if (DFFlag::RCCSupportCloudEdit && isCloudEdit(this))
{
return true;
}
FASTLOGS(FLog::GoldenHashes, "Checking hash: %s", hash);
boost::mutex::scoped_lock lock(goldenHashesMutex);
if (goldenHashes.find(hash) != goldenHashes.end())
return true;
// TODO: we might be able to remove this now
if (hash.empty()) return false;
std::string ios = "i"; ios += "o"; ios += "s"; ios = ios + "," + ios; // slight obfuscation
if (hash == ios) return true; // what we expect from ios clients
if (hash != goldenHash && hash != goldenHash2 && hash != goldenHash3)
{
FASTLOG(FLog::GoldenHashes, "Hash is no good");
//Their hash is no good, they failed the test
return false;
}
return true;
}
unsigned int Players::checkGoldMemHashes(const std::vector<unsigned int>& hashes)
{
boost::mutex::scoped_lock mtx(RBX::Network::Players::goldMemHashesMutex);
unsigned int bestResult = 0;
unsigned int bestErrors = 0xFFFFFFFF;
for (MemHashConfigs::const_iterator configIt = goldMemHashes.begin(); configIt < goldMemHashes.end(); ++configIt)
{
// skip empty configs to avoid "always pass"
if (configIt->empty())
{
continue;
}
// try to find the config with the fewest errors
unsigned int numErrors = 0;
unsigned int result = 0;
for (MemHashVector::const_iterator vecIt = configIt->begin(); vecIt < configIt->end(); ++vecIt)
{
// report error if out-of-bounds or different
if (hashes.size() <= vecIt->checkIdx || hashes[vecIt->checkIdx] != vecIt->value)
{
result |= vecIt->failMask;
++numErrors;
}
}
if (numErrors < bestErrors)
{
bestResult = result;
bestErrors = numErrors;
}
// stop when something matches
if (numErrors == 0)
{
break;
}
}
return bestResult;
}
bool Players::getUseCoreScriptHealthBar()
{
return true;
}
// used to report hackers
void Players::onRemoteSysStats(int userId, const std::string& stat, const std::string& message, bool desireKick)
{
StandardOut::singleton()->printf(MESSAGE_INFO,
"Call made to SysStats! UserID: %d, Stat: %s, Message: %s",
userId, stat.c_str(), message.c_str()); // logging to see if more are triggered
bool willKick = desireKick && canKickBecauseRunningInRealGameServer;
// make call to handler
if (cheatingPlayers[userId].find(stat + message) != cheatingPlayers[userId].end()) {
//Shut. It. Down.
StandardOut::singleton()->printf(MESSAGE_INFO, "Players::onRemoteSysStats disconnect not in the clist");
disconnectPlayer(userId, Replicator::DisconnectReason_OnRemoteSysStats);
return;
}
else {
FASTLOGS(FLog::US14116, "onRemoteSysStats: %s",
RBX::format("%d | %s | %s", userId, stat.c_str(), message.c_str()));
cheatingPlayers[userId].insert(stat + message);
}
if (!serverIsPresent(this)) {
// only report from server
return;
}
static const std::string kStatsCategory("RemoteSysStats");
bool isStudioServer = true;
int placeId = 0;
if (DataModel* dm = DataModel::get(this)) {
placeId = dm->getPlaceID();
isStudioServer = placeId <= 0;
}
if (isStudioServer) {
FASTLOG3(FLog::US14116, "Sending display stats: %d | %s | %s", userId, stat.c_str(), message.c_str());
}
if (sysStatsUrl.empty()){
StandardOut::singleton()->printf(MESSAGE_INFO, "SysStats URL Empty.");
return;
}
//They are using CheatEngine or Fidler or have a non-signed EXE
//Make the request and send it out
std::stringstream requestUrl;
requestUrl << sysStatsUrl << "&UserID=" << userId << "&Resolution=" << stat << "&Message=" << message;
requestUrl << char(0);
Http http(requestUrl.str());
std::string response;
try
{
http.get(boost::bind(&whoCaresResponse, _1, _2));
}
catch (std::bad_alloc&)
{
throw;
}
catch (RBX::base_exception&)
{
StandardOut::singleton()->printf(MESSAGE_INFO, "Players::onRemoteSysStats disconnect send failed");
// Remove the comment at the down if you already prepared your sysstats.
disconnectPlayer(userId, Replicator::DisconnectReason_OnRemoteSysStats);
return;
}
StandardOut::singleton()->printf(MESSAGE_INFO, "Players::onRemoteSysStats disconnect");
//Shut. It. Down.
// Remove the comment at the down if you already prepared your sysstats.
disconnectPlayer(userId, Replicator::DisconnectReason_OnRemoteSysStats);
}
void Players::onChildAdded(Instance* child)
{
if (Player* player = Instance::fastDynamicCast<Player>(child))
{
int userId = player->getUserID();
if (userId > 0)
{
shared_ptr<Instance> otherPlayer = getPlayerByID(userId);
if (otherPlayer)
{
// TODO: Wrap this into our security framework. This looks like just a hack
disconnectPlayer(userId, Replicator::DisconnectReason_OnRemoteSysStats);
StandardOut::singleton()->printf(MESSAGE_INFO, "Kicked duplicated user.");
//throw std::runtime_error(format("Player %d attempted to join twice. Booting both players", player->getUserID()));
}
}
{
boost::shared_ptr<Instances>& c = players.write();
c->push_back(shared_from(child));
}
if (FriendService* friendService = ServiceProvider::find<FriendService>(this)) {
friendService->playerAdded(userId);
}
if (Players::backendProcessing(this)) // Server does it in client/server - bail on client mode. will still execute in solo mode
{
if (userId == 0 && player->getName() == "Player")
{
// we are in a test mode, give every player a unique name and userId
// userIds are negative for an extra security measure for uploading to website, etc.
RBX::Security::Impersonator impersonate(RBX::Security::Replicator_);
player->setName(RBX::format("Player%d", ++testPlayerNameId));
player->setUserId(--testPlayerUserId);
}
Workspace* workspace = ServiceProvider::find<Workspace>(this);
if (!DFFlag::LoadGuisWithoutChar || !workspace->getNetworkFilteringEnabled() || (Network::Players::frontendProcessing(this) && Network::Players::backendProcessing(this)))
{
// this allows guis to be created if you load a character manually later
player->createPlayerGui();
}
player->scriptSecurityErrorSignal.connect(boost::bind(&Players::reportScriptSecurityError, this, userId, _1, _2, _3));
player->killPlayerSignal.connect(boost::bind(&Players::killPlayer, this, userId));
player->statsSignal.connect(boost::bind(&Players::onRemoteSysStats, this, userId, "1920x1200", _1, true));
player->remoteFriendServiceSignal.connect(boost::bind(&Players::friendServiceRequest, this, _1, weak_from(player), _2));
if (!isCloudEdit(this) && !player->calculatesSpawnLocationEarly()) {
// Team Service
if (Teams* ts = ServiceProvider::find<Teams>(this))
ts->assignNewPlayerToTeam(player);
}
if (!DFFlag::FirePlayerAddedAndPlayerRemovingOnClient)
{
playerAddedEarlySignal(shared_from(player));
playerAddedSignal(shared_from(player));
}
}
if (DFFlag::FirePlayerAddedAndPlayerRemovingOnClient)
{
playerAddedEarlySignal(shared_from(player));
playerAddedSignal(shared_from(player));
}
Http::playerCount = getNumPlayers();
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class AppendOtherCharacters {
public:
const Player* ownerPlayer;
Region2& region;
AppendOtherCharacters(const Player* _main, Region2& _region)
: ownerPlayer(_main), region(_region)
{}
void operator()(shared_ptr<Instance> instance) {
if (Player* p = Instance::fastDynamicCast<Player>(instance.get())) {
if (p != ownerPlayer) {
CoordinateFrame pos;
if (p->hasCharacterHead(pos)) {
region.appendOther(Region2::WeightedPoint(pos.translation.xz(),
p->getSimulationRadius()));
}
}
}
}
};
void Players::buildClientRegion(Region2& clientRegion)
{
clientRegion.clearEmpty();
CoordinateFrame pos;
if (localPlayer && (localPlayer->hasCharacterHead(pos)))
{
clientRegion.setOwner(Region2::WeightedPoint(pos.translation.xz(),
localPlayer->getSimulationRadius()));
AppendOtherCharacters appendOtherCharacters(localPlayer.get(), clientRegion);
this->visitChildren(appendOtherCharacters);
}
}
void Players::renderDPhysicsRegions(Adorn* adorn)
{
shared_ptr<const Instances> myPlayers = getPlayers();
Instances::const_iterator iter = myPlayers->begin();
Instances::const_iterator end = myPlayers->end();
for (; iter != end; ++iter)
{
Player* player = boost::polymorphic_downcast<Player*>(iter->get());
player->renderDPhysicsRegion(adorn);
}
}
void Players::setChatOption(ChatOption value)
{
chatOption = value;
}
void Players::setNonSuperSafeChatForAllPlayersEnabled(bool enabled)
{
nonSuperSafeChatForAllPlayersEnabled = enabled;
}
bool Players::getNonSuperSafeChatForAllPlayersEnabled() const
{
return nonSuperSafeChatForAllPlayersEnabled;
}
namespace RBX {
namespace Reflection {
template<>
EnumDesc<Players::ChatOption>::EnumDesc()
:EnumDescriptor("ChatStyle")
{
addPair(Players::CLASSIC_CHAT, "Classic");
addPair(Players::BUBBLE_CHAT, "Bubble");
addPair(Players::CLASSIC_AND_BUBBLE_CHAT, "ClassicAndBubble");
}
template<>
Players::ChatOption& Variant::convert<Players::ChatOption>(void)
{
return genericConvert<Players::ChatOption>();
}
template<>
EnumDesc<Players::PlayerChatType>::EnumDesc()
:EnumDescriptor("PlayerChatType")
{
addPair(Players::PLAYER_CHAT_TYPE_ALL, "All");
addPair(Players::PLAYER_CHAT_TYPE_TEAM, "Team");
addPair(Players::PLAYER_CHAT_TYPE_WHISPER, "Whisper");
}
template<>
Players::PlayerChatType& Variant::convert<Players::PlayerChatType>(void)
{
return genericConvert<Players::PlayerChatType>();
}
}//namespace Reflection
template<>
bool StringConverter<Players::ChatOption>::convertToValue(const std::string& text, Players::ChatOption& value)
{
return Reflection::EnumDesc<Players::ChatOption>::singleton().convertToValue(text.c_str(), value);
}
template<>
bool StringConverter<Players::PlayerChatType>::convertToValue(const std::string& text, Players::PlayerChatType& value)
{
return Reflection::EnumDesc<Players::PlayerChatType>::singleton().convertToValue(text.c_str(), value);
}
}//namespace RBX