Files
aya/client/server/src/oldserver.cpp
2025-12-17 16:47:48 +00:00

1828 lines
62 KiB
C++

// d9mz - This file makes me really sad
#if defined(_WIN32) || defined(_WIN64)
#include <atlsync.h>
#endif
#include "rapidjson/document.h"
#include "generated/soapRCCServiceSoapService.h"
#include "ProcessPerfCounter.hpp"
#include "Debug.hpp"
#include "Utility/ProtectedString.hpp"
#include "Utility/StandardOut.hpp"
#include "boost/noncopyable.hpp"
#include "boost/thread/xtime.hpp"
#include "boost/foreach.hpp"
#include "DataModel/Workspace.hpp"
#include "DataModel/ContentProvider.hpp"
#include "DataModel/DataModel.hpp"
#include "DataModel/DebugSettings.hpp"
#include "DataModel/PhysicsSettings.hpp"
#include "DataModel/FastLogSettings.hpp"
#include "DataModel/GameBasicSettings.hpp"
#include "Script/LuaSettings.hpp"
#include "DataModel/GameSettings.hpp"
#include "DataModel/MarketplaceService.hpp"
#include "DataModel/AdService.hpp"
#include "DataModel/CSGDictionaryService.hpp"
#include "DataModel/NonReplicatedCSGDictionaryService.hpp"
#include "DataModel/Message.hpp"
#include "Utility/RobloxServicesTools.hpp"
#include "Script/ScriptContext.hpp"
#include "ThumbnailGenerator.hpp"
#include <string>
#include <sstream>
#include "DataModel/factoryregistration.hpp"
#include "Utility/Profiling.hpp"
#include "Utility/SoundService.hpp"
#include "Utility/Guid.hpp"
#include "Utility/Http.hpp"
#include "Utility/Statistics.hpp"
#include "Utility/rbxrandom.hpp"
#include "API.hpp"
#include "Utility/AyaService.hpp"
#include <queue>
#include "Base/ViewBase.hpp"
#include "Utility/FileSystem.hpp"
#include "Players.hpp"
#include "Xml/XmlSerializer.hpp"
#include "Xml/WebParser.hpp"
#include "Utility/RobloxServicesTools.hpp"
#include "Utility/Utilities.hpp"
#include "ChatFilter.hpp"
#include "WebChatFilter.hpp"
#include "DataModel/ContentProvider.hpp"
#include "CountersClient.hpp"
#include "SimpleJSON.hpp"
#include "AyaFormat.hpp"
#if !defined(__linux) && !defined(__APPLE__)
#include "VersionInfo.hpp"
#include "DumpErrorUploader.hpp"
#endif
#include "Utility/Analytics.hpp"
#include "boost/date_time/posix_time/posix_time.hpp"
#include <boost/algorithm/string.hpp>
#if !defined(__linux) && !defined(__APPLE__)
#include <strsafe.h>
#endif
long diagCount = 0;
long batchJobCount = 0;
long openJobCount = 0;
long closeJobCount = 0;
long helloWorldCount = 0;
long getVersionCount = 0;
long renewLeaseCount = 0;
long executeCount = 0;
long getExpirationCount = 0;
long getStatusCount = 0;
long getAllJobsCount = 0;
long closeExpiredJobsCount = 0;
long closeAllJobsCount = 0;
// #define DIAGNOSTICS
#ifdef DIAGNOSTICS
#define BEGIN_PRINT(func, msg) \
static int func = 0; \
int counter = func++; \
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_INFO, "Begin-%s%d\r\n", msg, counter)
#define END_PRINT(func, msg) Aya::StandardOut::singleton()->printf(Aya::MESSAGE_INFO, "End---%s%d\r\n", msg, counter)
#else
#define BEGIN_PRINT(func, msg)
#define END_PRINT(func, msg)
#endif
LOGVARIABLE(RCCServiceInit, 1);
LOGVARIABLE(RCCServiceJobs, 1);
LOGVARIABLE(RCCDataModelInit, 1);
// See TaskScheduler::ThreadPoolConfig enum for valid values
DYNAMIC_FASTINTVARIABLE(TaskSchedulerThreadCountEnum, 1)
DYNAMIC_FASTFLAGVARIABLE(DebugCrashOnFailToLoadClientSettings, false)
DYNAMIC_FASTFLAGVARIABLE(UseNewSecurityKeyApi, false);
DYNAMIC_FASTFLAGVARIABLE(UseNewMemHashApi, false);
DYNAMIC_FASTSTRINGVARIABLE(MemHashConfig, "");
FASTINTVARIABLE(RCCServiceThreadCount, Aya::TaskScheduler::Threads1)
DYNAMIC_FASTFLAGVARIABLE(US30476, false);
FASTFLAGVARIABLE(UseDataDomain, true);
FASTFLAGVARIABLE(Dep, true)
namespace Aya
{
class Explosion;
class Cofm;
class NormalBreakConnector;
class ContactConnector;
class RevoluteLink;
class SimBody;
class BallBallContact;
class BallBlockContact;
class BlockBlockContact;
} // namespace Aya
class CrashAfterTimeout
{
shared_ptr<boost::condition_variable_any> event;
shared_ptr<boost::mutex> mutex;
static void run(shared_ptr<boost::condition_variable_any> event, int timeount, shared_ptr<boost::mutex> mutex)
{
boost::xtime xt;
boost::xtime_get(&xt, boost::TIME_UTC_);
xt.sec += static_cast<boost::xtime::xtime_sec_t>(timeount);
boost::mutex::scoped_lock lock(*mutex);
bool set = event->timed_wait(lock, xt);
if (!set)
AYACRASH();
}
public:
CrashAfterTimeout(int seconds)
: event(new boost::condition_variable_any())
, mutex(new boost::mutex())
{
boost::thread thread(boost::bind(&CrashAfterTimeout::run, event, seconds, mutex));
}
~CrashAfterTimeout()
{
boost::mutex::scoped_lock lock(*mutex);
event->notify_all();
}
};
class RCCServiceSettings : public Aya::FastLogJSON
{
public:
START_DATA_MAP(RCCServiceSettings);
DECLARE_DATA_STRING(WindowsMD5);
DECLARE_DATA_STRING(WindowsPlayerBetaMD5);
DECLARE_DATA_STRING(MacMD5);
DECLARE_DATA_INT(SecurityDataTimer);
DECLARE_DATA_INT(ClientSettingsTimer);
END_DATA_MAP();
};
class SecurityDataUpdater
{
std::string apiUrl;
protected:
std::string data;
virtual void processDataArray(shared_ptr<const Aya::Reflection::ValueArray> dataArray) = 0;
bool fetchData()
{
std::string newData;
try
{
Aya::Http request(apiUrl);
request.get(newData);
// no need to continue if data did not change
if (newData == data)
return false;
}
catch (std::exception& ex)
{
Aya::StandardOut::singleton()->printf(
Aya::MESSAGE_WARNING, "SecurityDataUpdater failed to fetch data from %s, %s", apiUrl.c_str(), ex.what());
return false;
}
data = newData;
return true;
}
public:
SecurityDataUpdater(const std::string& url)
: apiUrl(url)
{
}
virtual ~SecurityDataUpdater() {}
void run()
{
if (!fetchData())
return;
shared_ptr<const Aya::Reflection::ValueTable> jsonResult(Aya::make_shared<const Aya::Reflection::ValueTable>());
if (Aya::WebParser::parseJSONTable(data, jsonResult))
{
Aya::Reflection::ValueTable::const_iterator iter = jsonResult->find("data");
if (iter != jsonResult->end())
{
shared_ptr<const Aya::Reflection::ValueArray> dataArray = iter->second.cast<shared_ptr<const Aya::Reflection::ValueArray>>();
processDataArray(dataArray);
}
}
}
};
class MD5Updater : public SecurityDataUpdater
{
protected:
void processDataArray(shared_ptr<const Aya::Reflection::ValueArray> dataArray)
{
std::set<std::string> hashes;
for (Aya::Reflection::ValueArray::const_iterator it = dataArray->begin(); it != dataArray->end(); ++it)
{
std::string value = it->get<std::string>();
hashes.insert(value);
}
// always add hash for ios
hashes.insert("ios,ios");
Aya::Network::Players::setGoldenHashes2(hashes);
}
public:
MD5Updater(const std::string& url)
: SecurityDataUpdater(url)
{
}
~MD5Updater() {}
};
class SecurityKeyUpdater : public SecurityDataUpdater
{
protected:
void processDataArray(shared_ptr<const Aya::Reflection::ValueArray> dataArray)
{
std::vector<std::string> versions;
for (Aya::Reflection::ValueArray::const_iterator it = dataArray->begin(); it != dataArray->end(); ++it)
{
// version = data + salt
std::string value = it->get<std::string>();
std::string version = Aya::sha1(value + "askljfLUZF");
versions.push_back(version);
}
Aya::Network::setSecurityVersions(versions);
}
public:
SecurityKeyUpdater(const std::string& url)
: SecurityDataUpdater(url)
{
}
~SecurityKeyUpdater() {}
};
class MemHashUpdater : public SecurityDataUpdater
{
public:
// this is public until we get a web api to do this better.
static void populateMemHashConfigs(Aya::Network::MemHashConfigs& hashConfigs, const std::string& cfgStr)
{
hashConfigs.push_back(Aya::Network::MemHashVector());
Aya::Network::MemHashVector& thisHashVector = hashConfigs.back();
std::vector<std::string> hashStrInfo;
boost::split(hashStrInfo, cfgStr, boost::is_any_of(";"));
for (std::vector<std::string>::const_iterator hpIt = hashStrInfo.begin(); hpIt != hashStrInfo.end(); ++hpIt)
{
std::vector<std::string> args;
boost::split(args, *hpIt, boost::is_any_of(","));
if (args.size() >= 3)
{
Aya::Network::MemHash thisHash;
thisHash.checkIdx = boost::lexical_cast<unsigned int>(args[0]);
thisHash.value = boost::lexical_cast<unsigned int>(args[1]);
thisHash.failMask = boost::lexical_cast<unsigned int>(args[2]);
thisHashVector.push_back(thisHash);
}
}
};
protected:
void processDataArray(shared_ptr<const Aya::Reflection::ValueArray> dataArray)
{
Aya::Network::MemHashConfigs hashConfigs;
for (Aya::Reflection::ValueArray::const_iterator it = dataArray->begin(); it != dataArray->end(); ++it)
{
populateMemHashConfigs(hashConfigs, it->get<std::string>());
}
Aya::Network::Players::setGoldMemHashes(hashConfigs);
}
public:
MemHashUpdater(const std::string& url)
: SecurityDataUpdater(url)
{
}
~MemHashUpdater() {}
};
class RCCServiceDynamicSettings : public Aya::FastLogJSON
{
typedef std::map<std::string, std::string> FVariables;
FVariables fVars;
public:
virtual void ProcessVariable(const std::string& valueName, const std::string& valueData, bool dynamic)
{
AYAASSERT(dynamic);
fVars.insert(std::make_pair(valueName, valueData));
}
virtual bool DefaultHandler(const std::string& valueName, const std::string& valueData)
{
// only process dynamic flags and logs
if (valueName[0] == 'D')
return FastLogJSON::DefaultHandler(valueName, valueData);
return false;
}
void UpdateSettings()
{
for (FVariables::iterator i = fVars.begin(); i != fVars.end(); i++)
FLog::SetValue(i->first, i->second, FASTVARTYPE_DYNAMIC, false);
fVars.clear();
}
};
DATA_MAP_IMPL_START(RCCServiceSettings)
IMPL_DATA(WindowsMD5, "");
IMPL_DATA(MacMD5, "");
IMPL_DATA(WindowsPlayerBetaMD5, "");
IMPL_DATA(SecurityDataTimer, 300); // in seconds, default 5 min
IMPL_DATA(ClientSettingsTimer, 120); // 2 min
IMPL_DATA(HttpUseCurlPercentageRCC, 0); // do not use CURL by default
DATA_MAP_IMPL_END()
#if !defined(__linux) && !defined(__APPLE__)
static boost::scoped_ptr<MainLogManager> mainLogManager(new MainLogManager("Kiseki Web Service", ".dmp", ".crashevent"));
#endif
#ifdef AYA_TEST_BUILD
std::string RCCServiceSettingsKeyOverwrite;
#endif
class CWebService
{
#if !defined(__linux) && !defined(__APPLE__)
boost::shared_ptr<CProcessPerfCounter> s_perfCounter;
ATL::CEvent doneEvent;
#endif
boost::scoped_ptr<boost::thread> perfData;
boost::scoped_ptr<boost::thread> fetchSecurityDataThread;
boost::scoped_ptr<boost::thread> fetchClientSettingsThread;
RCCServiceSettings rccSettings;
RCCServiceDynamicSettings rccDynamicSettings;
std::string settingsKey;
std::string securityVersionData;
std::string clientSettingsData;
std::string thumbnailSettingsData;
bool isThumbnailer;
boost::scoped_ptr<MD5Updater> md5Updater;
boost::scoped_ptr<SecurityDataUpdater> securityKeyUpdater;
boost::scoped_ptr<MemHashUpdater> memHashUpdater;
boost::scoped_ptr<CountersClient> counters;
public:
struct JobItem : boost::noncopyable
{
enum JobItemRunStatus
{
RUNNING_JOB,
JOB_DONE,
JOB_ERROR
};
const std::string id;
shared_ptr<Aya::DataModel> dataModel;
Aya::Time expirationTime;
int category;
double cores;
#if !defined(__linux) && !defined(__APPLE__)
ATL::CEvent jobCheckLeaseEvent;
#endif
Aya::signals::connection notifyAliveConnection;
JobItemRunStatus status;
std::string errorMessage;
JobItem(const char* id)
: id(id)
#if !defined(__linux) && !defined(__APPLE__)
, jobCheckLeaseEvent(TRUE, FALSE)
#endif
, status(RUNNING_JOB)
{
}
void touch(double seconds);
double secondsToTimeout() const;
};
typedef std::map<std::string, boost::shared_ptr<JobItem>> JobMap;
JobMap jobs;
boost::mutex sync;
long dataModelCount;
boost::mutex currentlyClosingMutex;
public:
CWebService(bool crashUploadOnly);
~CWebService();
private:
void collectPerfData();
void LoadAppSettings();
void LoadClientSettings(RCCServiceSettings& dest);
void LoadClientSettings(std::string& clientDest, std::string& thumbnailDest);
std::string GetSettingsKey();
std::vector<std::string> fetchAllowedSecurityVersions();
public:
static boost::scoped_ptr<CWebService> singleton;
JobMap::iterator getJob(const std::string& jobID)
{
JobMap::iterator iter = jobs.find(jobID);
if (iter == jobs.end())
throw Aya::runtime_error("JobItem %s not found", jobID.c_str());
return iter;
}
void validateSecurityData()
{
#if defined(__linux) || defined(__APPLE__)
std::mutex mtx;
std::condition_variable cv;
std::unique_lock<std::mutex> lock(mtx);
while (cv.wait_for(lock, std::chrono::milliseconds(static_cast<int>(rccSettings.GetValueSecurityDataTimer() * 1000.0))) ==
std::cv_status::timeout)
#else
while (::WaitForSingleObject(doneEvent.m_h, rccSettings.GetValueSecurityDataTimer() * 1000) == WAIT_TIMEOUT)
#endif
{
if (DFFlag::UseNewSecurityKeyApi)
{
if (securityKeyUpdater)
securityKeyUpdater->run();
}
else
{
std::string prevVersionData = securityVersionData;
std::vector<std::string> versions = fetchAllowedSecurityVersions();
// security versions changed, update all jobs with new versions
if (prevVersionData != securityVersionData)
Aya::Network::setSecurityVersions(versions);
}
if (DFFlag::UseNewMemHashApi)
{
if (memHashUpdater)
{
if (DFString::MemHashConfig.size() < 3)
{
memHashUpdater->run();
}
else
{
Aya::Network::MemHashConfigs hashConfigs;
MemHashUpdater::populateMemHashConfigs(hashConfigs, DFString::MemHashConfig);
Aya::Network::Players::setGoldMemHashes(hashConfigs);
}
}
}
if (md5Updater)
md5Updater->run();
}
}
void validateClientSettings()
{
#if defined(__linux) || defined(__APPLE__)
std::mutex mtx;
std::condition_variable cv;
std::unique_lock<std::mutex> lock(mtx);
while (cv.wait_for(lock, std::chrono::milliseconds(static_cast<int>(rccSettings.GetValueClientSettingsTimer() * 1000.0))) ==
std::cv_status::timeout)
#else
while (::WaitForSingleObject(doneEvent.m_h, rccSettings.GetValueClientSettingsTimer() * 1000) == WAIT_TIMEOUT)
#endif
{
std::string prevClientSettings = clientSettingsData;
std::string prevThumbnailSettings = thumbnailSettingsData;
LoadClientSettings(clientSettingsData, thumbnailSettingsData);
bool clientChanged = (prevClientSettings != clientSettingsData);
bool thumbnailChanged = isThumbnailer && (prevThumbnailSettings != thumbnailSettingsData);
if (clientChanged || thumbnailChanged)
{
// collect all dynamic settings
rccDynamicSettings.ReadFromStream(clientSettingsData.c_str());
if (isThumbnailer)
{
rccDynamicSettings.ReadFromStream(thumbnailSettingsData.c_str());
}
// submit datamodel write task to update all dynamic settings
boost::mutex::scoped_lock lock(sync);
if (jobs.size() > 0)
{
// HACK: Client settings are global, meaning all datamodels use the same set,
// current we only have 1 datamodel running per rccservice, so submit write task just on first datamodel
shared_ptr<JobItem> job = jobs.begin()->second;
job->dataModel->submitTask(
boost::bind(&RCCServiceDynamicSettings::UpdateSettings, &rccDynamicSettings), Aya::DataModelJob::Write);
}
else
rccDynamicSettings.UpdateSettings();
}
}
}
void doCheckLease(boost::shared_ptr<JobItem> job)
{
try
{
#if defined(__linux) || defined(__APPLE__)
std::mutex mtx;
std::condition_variable cv;
#endif
while (true)
{
#if defined(__linux) || defined(__APPLE__)
std::unique_lock<std::mutex> lock(mtx);
#endif
double sleepTimeInSeconds = job->secondsToTimeout();
if (sleepTimeInSeconds <= 0)
{
closeJob(job->id);
return;
}
#if !defined(__linux) && !defined(__APPLE__)
else if (::WaitForSingleObject(job->jobCheckLeaseEvent.m_h, (DWORD)(sleepTimeInSeconds * 1000.0) + 3) != WAIT_TIMEOUT)
return;
#else
else if (cv.wait_for(lock, std::chrono::milliseconds(static_cast<int>(sleepTimeInSeconds * 1000.0) + 3)) != std::cv_status::timeout)
return;
#endif
}
}
catch (std::exception& e)
{
assert(false);
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_ERROR, "doCheckLease failure: %s", e.what());
}
}
void renewLease(const std::string& id, double expirationInSeconds)
{
boost::mutex::scoped_lock lock(sync);
getJob(id)->second->touch(expirationInSeconds);
}
static void convertToLua(const Aya::Reflection::Variant& source, ns1__LuaValue& dest, soap* soap)
{
assert(dest.type == ns1__LuaType__LUA_USCORETNIL);
assert(dest.value == NULL);
if (source.isType<void>())
{
dest.type = ns1__LuaType__LUA_USCORETNIL;
return;
}
if (source.isType<bool>())
{
dest.type = ns1__LuaType__LUA_USCORETBOOLEAN;
dest.value = soap_new_std__string(soap, -1);
*dest.value = source.get<std::string>();
return;
}
if (source.isNumber())
{
dest.type = ns1__LuaType__LUA_USCORETNUMBER;
dest.value = soap_new_std__string(soap, -1);
*dest.value = source.get<std::string>();
return;
}
if (source.isType<std::string>())
{
dest.type = ns1__LuaType__LUA_USCORETSTRING;
dest.value = soap_new_std__string(soap, -1);
*dest.value = source.get<std::string>();
return;
}
if (source.isType<Aya::ContentId>())
{
dest.type = ns1__LuaType__LUA_USCORETSTRING;
dest.value = soap_new_std__string(soap, -1);
*dest.value = source.get<std::string>();
return;
}
if (source.isType<shared_ptr<const Aya::Reflection::ValueArray>>())
{
shared_ptr<const Aya::Reflection::ValueArray> collection = source.cast<shared_ptr<const Aya::Reflection::ValueArray>>();
dest.type = ns1__LuaType__LUA_USCORETTABLE;
dest.table = soap_new_ns1__ArrayOfLuaValue(soap, -1);
dest.table->LuaValue.resize(collection->size());
for (size_t i = 0; i < collection->size(); ++i)
{
dest.table->LuaValue[i] = soap_new_ns1__LuaValue(soap, -1);
convertToLua((*collection)[i], *dest.table->LuaValue[i], soap);
}
return;
}
// TODO: enums!
dest.type = ns1__LuaType__LUA_USCORETNIL;
}
static void convert(const ns1__LuaValue* source, Aya::Reflection::Variant& dest)
{
switch (source->type)
{
case ns1__LuaType__LUA_USCORETNIL:
break;
case ns1__LuaType__LUA_USCORETNUMBER:
dest = *source->value;
dest.convert<double>();
break;
case ns1__LuaType__LUA_USCORETBOOLEAN:
dest = *source->value;
dest.convert<bool>();
break;
case ns1__LuaType__LUA_USCORETSTRING:
dest = *source->value;
break;
case ns1__LuaType__LUA_USCORETTABLE:
{
const size_t count = source->table->LuaValue.size();
// Create a collection so that we can populate it
shared_ptr<Aya::Reflection::ValueArray> table(new Aya::Reflection::ValueArray(count));
for (size_t i = 0; i < count; ++i)
convert(source->table->LuaValue[i], (*table)[i]);
// Set the value to a ValueArray type
dest = shared_ptr<const Aya::Reflection::ValueArray>(table);
}
break;
}
}
// Gathers all non-global Instances that are in the heap
void arbiterActivityDump(Aya::Reflection::ValueArray& result)
{
boost::mutex::scoped_lock lock(sync);
for (JobMap::iterator iter = jobs.begin(); iter != jobs.end(); ++iter)
{
shared_ptr<Aya::DataModel> dataModel = iter->second->dataModel;
shared_ptr<Aya::Reflection::ValueArray> tuple(new Aya::Reflection::ValueArray());
tuple->push_back(dataModel->arbiterName());
tuple->push_back(dataModel->getAverageActivity());
result.push_back(shared_ptr<const Aya::Reflection::ValueArray>(tuple));
}
}
// Gathers all non-global Instances that are in the heap
void leakDump(Aya::Reflection::ValueArray& result) {}
// Gathers diagnostic data. Does NOT require locks on datamodel :)
const Aya::Reflection::ValueArray* diag(int type, shared_ptr<Aya::DataModel> dataModel)
{
std::auto_ptr<Aya::Reflection::ValueArray> tuple(new Aya::Reflection::ValueArray());
/* This is the format of the Diag data:
type == 0
DataModel Count in this process
PerfCounter data
Task Scheduler
(obsolete entry)
double threadAffinity
double numQueuedJobs
double numScheduledJobs
double numRunningJobs
long threadPoolSize
double messageRate
double messagePumpDutyCycle
DataModel Jobs Info
Machine configuration
Memory Leak Detection
type & 1
leak dump
type & 2
attempt to allocate 500k. if success, then true else false
type & 4
DataModel dutyCycles
*/
tuple->push_back(dataModelCount);
{
// preferably we should fix this
#if !defined(__linux) && !defined(__APPLE__)
shared_ptr<Aya::Reflection::ValueArray> perfCounterData(new Aya::Reflection::ValueArray());
perfCounterData->push_back(s_perfCounter->GetProcessCores());
perfCounterData->push_back(s_perfCounter->GetTotalProcessorTime());
perfCounterData->push_back(s_perfCounter->GetProcessorTime());
perfCounterData->push_back(s_perfCounter->GetPrivateBytes());
perfCounterData->push_back(s_perfCounter->GetPrivateWorkingSetBytes());
perfCounterData->push_back(-1);
perfCounterData->push_back(-1);
perfCounterData->push_back(s_perfCounter->GetElapsedTime());
perfCounterData->push_back(s_perfCounter->GetVirtualBytes());
perfCounterData->push_back(s_perfCounter->GetPageFileBytes());
perfCounterData->push_back(s_perfCounter->GetPageFaultsPerSecond());
tuple->push_back(shared_ptr<const Aya::Reflection::ValueArray>(perfCounterData));
#endif
}
{
shared_ptr<Aya::Reflection::ValueArray> taskSchedulerData(new Aya::Reflection::ValueArray());
taskSchedulerData->push_back(0); // obsolete
taskSchedulerData->push_back(Aya::TaskScheduler::singleton().threadAffinity());
taskSchedulerData->push_back(Aya::TaskScheduler::singleton().numSleepingJobs());
taskSchedulerData->push_back(Aya::TaskScheduler::singleton().numWaitingJobs());
taskSchedulerData->push_back(Aya::TaskScheduler::singleton().numRunningJobs());
taskSchedulerData->push_back((long)Aya::TaskScheduler::singleton().threadPoolSize());
taskSchedulerData->push_back(Aya::TaskScheduler::singleton().schedulerRate());
taskSchedulerData->push_back(Aya::TaskScheduler::singleton().getSchedulerDutyCyclePerThread());
tuple->push_back(shared_ptr<const Aya::Reflection::ValueArray>(taskSchedulerData));
}
if (dataModel)
tuple->push_back(dataModel->getJobsInfo());
else
tuple->push_back(0);
{
shared_ptr<Aya::Reflection::ValueArray> machineData(new Aya::Reflection::ValueArray());
machineData->push_back(0);
machineData->push_back(0);
machineData->push_back(0);
machineData->push_back(0);
tuple->push_back(shared_ptr<const Aya::Reflection::ValueArray>(machineData));
}
{
// TODO, use Aya::poolAllocationList and Aya::poolAvailablityList to get complete stats
shared_ptr<Aya::Reflection::ValueArray> memCounters(new Aya::Reflection::ValueArray());
memCounters->push_back(Aya::Diagnostics::Countable<Aya::Instance>::getCount());
memCounters->push_back(Aya::Diagnostics::Countable<Aya::PartInstance>::getCount());
memCounters->push_back(Aya::Diagnostics::Countable<Aya::ModelInstance>::getCount());
memCounters->push_back(Aya::Diagnostics::Countable<Aya::Explosion>::getCount());
memCounters->push_back(Aya::Diagnostics::Countable<Aya::Soundscape::SoundChannel>::getCount());
memCounters->push_back(Aya::Diagnostics::Countable<Aya::signals::connection>::getCount());
memCounters->push_back(Aya::Diagnostics::Countable<Aya::signals::connection::islot>::getCount());
memCounters->push_back(Aya::Diagnostics::Countable<Aya::Reflection::GenericSlotWrapper>::getCount());
memCounters->push_back(Aya::Diagnostics::Countable<Aya::TaskScheduler::Job>::getCount());
memCounters->push_back(Aya::Diagnostics::Countable<Aya::Network::Player>::getCount());
#ifdef AYA_ALLOCATOR_COUNTS
memCounters->push_back(Aya::Allocator<Aya::Body>::getCount());
memCounters->push_back(Aya::Allocator<Aya::Cofm>::getCount());
memCounters->push_back(Aya::Allocator<Aya::NormalBreakConnector>::getCount());
memCounters->push_back(Aya::Allocator<Aya::ContactConnector>::getCount());
memCounters->push_back(Aya::Allocator<Aya::RevoluteLink>::getCount());
memCounters->push_back(Aya::Allocator<Aya::SimBody>::getCount());
memCounters->push_back(Aya::Allocator<Aya::BallBallContact>::getCount());
memCounters->push_back(Aya::Allocator<Aya::BallBlockContact>::getCount());
memCounters->push_back(Aya::Allocator<Aya::BlockBlockContact>::getCount());
memCounters->push_back(Aya::Allocator<XmlAttribute>::getCount());
memCounters->push_back(Aya::Allocator<XmlElement>::getCount());
#else
for (int i = 0; i < 11; ++i)
memCounters->push_back(-1);
#endif
memCounters->push_back((int)ThumbnailGenerator::totalCount);
tuple->push_back(shared_ptr<const Aya::Reflection::ValueArray>(memCounters));
}
if (type & 1)
{
shared_ptr<Aya::Reflection::ValueArray> result(new Aya::Reflection::ValueArray());
leakDump(*result);
tuple->push_back(shared_ptr<const Aya::Reflection::ValueArray>(result));
}
if (type & 2)
{
try
{
{
std::vector<char> v1(100000);
std::vector<char> v2(100000);
std::vector<char> v3(100000);
std::vector<char> v4(100000);
std::vector<char> v5(100000);
}
tuple->push_back(true);
}
catch (...)
{
tuple->push_back(false);
}
}
if (type & 4)
{
shared_ptr<Aya::Reflection::ValueArray> result(new Aya::Reflection::ValueArray());
arbiterActivityDump(*result);
tuple->push_back(shared_ptr<const Aya::Reflection::ValueArray>(result));
}
return tuple.release();
}
void execute(const std::string& jobID, ns1__ScriptExecution* script, std::vector<ns1__LuaValue*>* result, soap* soap)
{
std::string code = *script->script;
if (Aya::ContentProvider::isHttpUrl(code))
{
Aya::Http http(code);
http.get(code);
}
shared_ptr<Aya::DataModel> dataModel;
{
boost::mutex::scoped_lock lock(sync);
JobMap::iterator iter = getJob(jobID);
dataModel = iter->second->dataModel;
}
std::auto_ptr<const Aya::Reflection::Tuple> tuple;
{
const size_t count = script->arguments ? script->arguments->LuaValue.size() : 0;
Aya::Reflection::Tuple args(count);
for (size_t i = 0; i < count; ++i)
convert(script->arguments->LuaValue[i], args.values[i]);
Aya::DataModel::LegacyLock lock(dataModel, Aya::DataModelJob::Write);
if (dataModel->isClosed())
throw std::runtime_error("The DataModel is closed");
Aya::ScriptContext* scriptContext = Aya::ServiceProvider::create<Aya::ScriptContext>(dataModel.get());
tuple = scriptContext->executeInNewThread(
Aya::Security::WebService, Aya::ProtectedString::fromTrustedSource(code), script->name->c_str(), args);
}
if (tuple.get())
{
result->resize(tuple->values.size());
for (size_t i = 0; i < tuple->values.size(); ++i)
{
(*result)[i] = soap_new_ns1__LuaValue(soap, -1);
convertToLua(tuple->values[i], *(*result)[i], soap);
}
}
else
{
result->resize(0);
}
}
void diag(int type, std::string jobID, std::vector<ns1__LuaValue*>* result, soap* soap)
{
std::auto_ptr<const Aya::Reflection::ValueArray> tuple;
{
shared_ptr<Aya::DataModel> dataModel;
if (!jobID.empty())
{
boost::mutex::scoped_lock lock(sync);
JobMap::iterator iter = getJob(jobID);
dataModel = iter->second->dataModel;
}
tuple.reset(diag(type, dataModel));
}
result->resize(tuple->size());
for (size_t i = 0; i < tuple->size(); ++i)
{
(*result)[i] = soap_new_ns1__LuaValue(soap, -1);
convertToLua((*tuple)[i], *(*result)[i], soap);
}
}
void contentDataLoaded(shared_ptr<Aya::DataModel>& dataModel)
{
Aya::CSGDictionaryService* dictionaryService = Aya::ServiceProvider::create<Aya::CSGDictionaryService>(dataModel.get());
Aya::NonReplicatedCSGDictionaryService* nrDictionaryService =
Aya::ServiceProvider::create<Aya::NonReplicatedCSGDictionaryService>(dataModel.get());
dictionaryService->reparentAllChildData();
}
void setupServerConnections(Aya::DataModel* dataModel)
{
if (!dataModel)
{
return;
}
}
shared_ptr<JobItem> createJob(const ns1__Job& job, bool startHeartbeat, shared_ptr<Aya::DataModel>& dataModel)
{
srand(Aya::randomSeed()); // make sure this thread is seeded
std::string id = job.id;
dataModel = Aya::DataModel::createDataModel(
startHeartbeat, new Aya::NullVerb(NULL, ""), false, Aya::GameBasicSettings::singleton().getVirtualVersion());
Aya::AyaService* ayaService = Aya::ServiceProvider::create<Aya::AyaService>(dataModel.get());
ayaService->setInitialVersion(Aya::GameBasicSettings::singleton().getVirtualVersion());
setupServerConnections(dataModel.get());
Aya::Network::Players* players = dataModel->find<Aya::Network::Players>();
if (players)
{
LoadClientSettings(rccSettings);
if (rccSettings.GetError())
{
AYACRASH(rccSettings.GetErrorString().c_str());
}
{
bool useCurl = rand() % 100 < rccSettings.GetValueHttpUseCurlPercentageRCC();
FASTLOG1(FLog::RCCDataModelInit, "Using CURL = %d", useCurl);
Aya::Http::SetUseCurl(useCurl);
Aya::Http::SetUseStatistics(true);
}
FASTLOGS(FLog::RCCDataModelInit, "Creating Data Model, Windows MD5: %s", rccSettings.GetValueWindowsMD5());
FASTLOGS(FLog::RCCDataModelInit, "Creating Data Model, Mac MD5: %s", rccSettings.GetValueMacMD5());
FASTLOGS(FLog::RCCDataModelInit, "Creating Data Model, Windows Player Beta MD5: %s", rccSettings.GetValueWindowsPlayerBetaMD5());
players->setGoldenHashes(rccSettings.GetValueWindowsMD5(), rccSettings.GetValueMacMD5(), rccSettings.GetValueWindowsPlayerBetaMD5());
Aya::DataModel::LegacyLock lock(dataModel, Aya::DataModelJob::Write);
dataModel->create<Aya::Network::WebChatFilter>();
}
dataModel->workspaceLoadedSignal.connect(
[this](std::shared_ptr<Aya::DataModel>& dataModel)
{
if (Aya::AdService* adService = dataModel->create<Aya::AdService>())
{
adService->sendServerVideoAdVerification.connect(boost::bind(&Aya::AdService::checkCanPlayVideoAd, adService, _1, _2));
adService->sendServerRecordImpression.connect(boost::bind(&Aya::AdService::sendAdImpression, adService, _1, _2, _3));
}
Aya::CSGDictionaryService* dictionaryService = Aya::ServiceProvider::create<Aya::CSGDictionaryService>(dataModel.get());
Aya::NonReplicatedCSGDictionaryService* nrDictionaryService =
Aya::ServiceProvider::create<Aya::NonReplicatedCSGDictionaryService>(dataModel.get());
dictionaryService->reparentAllChildData();
});
dataModel->jobId = id;
shared_ptr<JobItem> j(new JobItem(id.c_str()));
j->dataModel = dataModel;
j->category = job.category;
j->cores = job.cores;
j->touch(job.expirationInSeconds);
{
boost::mutex::scoped_lock lock(sync);
if (jobs.find(id) != jobs.end())
throw Aya::runtime_error("JobItem %s already exists", id.c_str());
jobs[id] = j;
}
return j;
}
void batchJob(const ns1__Job& job, ns1__ScriptExecution* script, std::vector<ns1__LuaValue*>* result, soap* soap)
{
std::string id = job.id;
shared_ptr<Aya::DataModel> dataModel;
#if !defined(__linux) && !defined(__APPLE__)
::InterlockedIncrement(&dataModelCount);
#endif
try
{
shared_ptr<JobItem> j = createJob(job, false, dataModel);
FASTLOGS(FLog::RCCServiceJobs, "Opened Batch JobItem %s", id.c_str());
FASTLOG1(FLog::RCCServiceJobs, "DataModel: %p", dataModel.get());
// Spin off a thread for the BatchJob to execute in
#if !defined(__linux) && !defined(__APPLE__)
boost::thread(Aya::thread_wrapper(boost::bind(&CWebService::asyncExecute, this, id, script, result, soap), "AsyncExecute"));
#else
// linux - Object destroyed immediately after creation; did you mean to name the object? (fix available)clang-tidybugprone-unused-raii
boost::thread give_me_a_name(
Aya::thread_wrapper(boost::bind(&CWebService::asyncExecute, this, id, script, result, soap), "AsyncExecute"));
std::mutex mtx;
std::condition_variable cv;
#endif
while (true)
{
#if defined(__linux) || defined(__APPLE__)
std::unique_lock<std::mutex> lock(mtx);
#endif
double sleepTimeInSeconds = j->secondsToTimeout();
if (sleepTimeInSeconds <= 0) // This case seems like an edge case (we already ran out of time)
{
#if !defined(__linux) && !defined(__APPLE__)
if (::WaitForSingleObject(j->jobCheckLeaseEvent.m_h, 1) != WAIT_TIMEOUT)
{
if (j->status == JobItem::JOB_ERROR)
throw Aya::runtime_error(j->errorMessage.c_str());
}
#endif
closeJob(id);
throw Aya::runtime_error("BatchJob Timeout");
}
#if !defined(__linux) && !defined(__APPLE__)
else if (::WaitForSingleObject(j->jobCheckLeaseEvent.m_h, (DWORD)(sleepTimeInSeconds * 1000.0) + 3) == WAIT_TIMEOUT)
#else
else if (cv.wait_for(lock, std::chrono::milliseconds(static_cast<int>(sleepTimeInSeconds * 1000.0) + 3)) == std::cv_status::timeout)
#endif
{
// do nothing, just continue looping. Probably sleepTimeInSeconds will be negative, and we'll throw
}
else
{
// jobCheckLeaseEvent was set
if (j->status == JobItem::JOB_ERROR)
throw Aya::runtime_error(j->errorMessage.c_str());
// TODO: Shouldn't this be called BEFORE we throw??????
closeJob(id);
return;
}
}
}
catch (std::exception&)
{
throw;
}
}
void asyncExecute(const std::string& id, ns1__ScriptExecution* script, std::vector<ns1__LuaValue*>* result, soap* soap)
{
try
{
this->execute(id, script, result, soap);
closeJob(id);
}
catch (std::exception& e)
{
char szMsg[1024];
#if !defined(__linux) && !defined(__APPLE__)
StringCchCopy(szMsg, ARRAYSIZE(szMsg), e.what());
#endif
closeJob(id, szMsg);
}
}
void openJob(const ns1__Job& job, ns1__ScriptExecution* script, std::vector<ns1__LuaValue*>* result, soap* soap, bool startHeartbeat)
{
std::string id = job.id;
Aya::Http::gameID = job.id;
shared_ptr<Aya::DataModel> dataModel;
#if !defined(__linux) && !defined(__APPLE__)
::InterlockedIncrement(&dataModelCount);
#endif
try
{
shared_ptr<JobItem> j = createJob(job, startHeartbeat, dataModel);
try
{
Aya::DataModel* pDataModel = dataModel.get();
pDataModel->create<Aya::ContentProvider>()->setBaseUrl(GetBaseURL());
// Monitor the job and close it if needed
boost::thread(Aya::thread_wrapper(boost::bind(&CWebService::doCheckLease, this, j), "Check Expiration"));
FASTLOGS(FLog::RCCServiceJobs, "Opened JobItem %s", id.c_str());
FASTLOG1(FLog::RCCServiceJobs, "DataModel: %p", pDataModel);
this->execute(id, script, result, soap);
Aya::RunService* runService = Aya::ServiceProvider::find<Aya::RunService>(pDataModel);
AYAASSERT(runService != NULL);
j->notifyAliveConnection = runService->heartbeatSignal.connect(boost::bind(&CWebService::notifyAlive, this, _1));
{
Aya::DataModel::LegacyLock lock(pDataModel, Aya::DataModelJob::Write);
pDataModel->loadCoreScripts();
}
}
catch (std::exception& e)
{
char szMsg[1024];
#if !defined(__linux) && !defined(__APPLE__)
StringCchCopy(szMsg, ARRAYSIZE(szMsg), e.what());
#endif
boost::mutex::scoped_lock lock(sync);
jobs.erase(id);
throw;
}
}
catch (std::exception& e)
{
char szMsg[1024];
#if !defined(__linux) && !defined(__APPLE__)
StringCchCopy(szMsg, ARRAYSIZE(szMsg), e.what());
#endif
FASTLOGS(FLog::RCCServiceJobs, "Closing DataModel due to exception: %s", szMsg);
FASTLOG1(FLog::RCCServiceJobs, "DataModel: %p", dataModel.get());
closeDataModel(dataModel);
dataModel.reset();
// ::InterlockedDecrement(&dataModelCount);
throw;
}
}
void closeDataModel(shared_ptr<Aya::DataModel> dataModel)
{
Aya::Security::Impersonator impersonate(Aya::Security::WebService);
// CrashAfterTimeout crash(90); // If after 90 seconds the datamodel doesn't close, then CRASH!!!!
Aya::DataModel::closeDataModel(dataModel);
}
void notifyAlive(const Aya::Heartbeat& h)
{
#if !defined(__linux) && !defined(__APPLE__)
mainLogManager->NotifyFGThreadAlive();
#endif
}
void closeJob(const std::string& jobID, const char* errorMessage = NULL)
{
// Take a mutex for the duration of closeJob here. The arbiter thinks that as
// soon as closeJob returns it is safe to force kill the rcc process, so make
// sure that if it is in the process of closing the datamodel any parallel requests
// to close the data model block at the mutex until the first one finishes.
boost::mutex::scoped_lock closeLock(currentlyClosingMutex);
shared_ptr<Aya::DataModel> dataModel;
{
boost::mutex::scoped_lock lock(sync);
JobMap::iterator iter = jobs.find(jobID);
if (iter != jobs.end())
{
if (errorMessage)
{
iter->second->errorMessage = errorMessage;
iter->second->status = JobItem::JOB_ERROR;
}
else
{
iter->second->status = JobItem::JOB_DONE;
}
// d9mz - I hope this line isn't important
#if !defined(__linux) && !defined(__APPLE__)
iter->second->jobCheckLeaseEvent.Set();
#endif
dataModel = iter->second->dataModel;
iter->second->notifyAliveConnection.disconnect();
jobs.erase(iter);
}
}
if (dataModel)
{
FASTLOGS(FLog::RCCServiceJobs, "Closing JobItem %s", jobID.c_str());
FASTLOG1(FLog::RCCServiceJobs, "DataModel: %p", dataModel.get());
closeDataModel(dataModel);
dataModel.reset();
#if !defined(__linux) && !defined(__APPLE__)
if (::InterlockedDecrement(&dataModelCount) == 0)
mainLogManager->DisableHangReporting();
#endif
}
}
int jobCount()
{
return jobs.size();
}
void getExpiration(const std::string& jobID, double* timeout)
{
boost::mutex::scoped_lock lock(sync);
*timeout = getJob(jobID)->second->secondsToTimeout();
}
void closeExpiredJobs(int* result)
{
std::vector<std::string> jobsToClose;
{
boost::mutex::scoped_lock lock(sync);
JobMap::iterator end = jobs.end();
for (JobMap::iterator iter = jobs.begin(); iter != end; ++iter)
if (iter->second->secondsToTimeout() <= 0)
jobsToClose.push_back(iter->first);
}
*result = jobsToClose.size();
std::for_each(jobsToClose.begin(), jobsToClose.end(), boost::bind(&CWebService::closeJob, this, _1, (const char*)NULL));
}
void closeAllJobs(int* result)
{
FASTLOG(FLog::RCCServiceJobs, "Closing all jobs command");
std::vector<std::string> jobsToClose;
{
boost::mutex::scoped_lock lock(sync);
JobMap::iterator end = jobs.end();
for (JobMap::iterator iter = jobs.begin(); iter != end; ++iter)
jobsToClose.push_back(iter->first);
}
*result = jobsToClose.size();
std::for_each(jobsToClose.begin(), jobsToClose.end(), boost::bind(&CWebService::closeJob, this, _1, (const char*)NULL));
}
void getAllJobs(std::vector<ns1__Job*>& result, soap* soap)
{
boost::mutex::scoped_lock lock(sync);
result.resize(jobs.size());
int i = 0;
JobMap::iterator iter = jobs.begin();
JobMap::iterator end = jobs.end();
while (iter != end)
{
ns1__Job* job = soap_new_ns1__Job(soap, -1);
job->expirationInSeconds = iter->second->secondsToTimeout();
job->category = iter->second->category;
job->cores = iter->second->cores;
job->id = iter->first;
result[i] = job;
++iter;
++i;
}
}
};
boost::scoped_ptr<CWebService> CWebService::singleton;
void stop_CWebService()
{
CWebService::singleton.reset();
}
void start_CWebService()
{
CWebService::singleton.reset(new CWebService(false));
}
AYA_REGISTER_CLASS(ThumbnailGenerator);
void CWebService::collectPerfData()
{
#if !defined(__linux) && !defined(__APPLE__)
while (::WaitForSingleObject(doneEvent.m_h, 1000) == WAIT_TIMEOUT) // used as an interruptible sleep.
s_perfCounter->CollectData();
#endif
}
CWebService::CWebService(bool crashUploadOnly)
#if !defined(__linux) && !defined(__APPLE__)
: doneEvent(TRUE, FALSE)
, dataModelCount(0)
#else
: dataModelCount(0)
#endif
{
{
#if !defined(__linux) && !defined(__APPLE__)
CVersionInfo vi;
vi.Load(_AtlBaseModule.m_hInst);
Aya::DebugSettings::robloxVersion = vi.GetFileVersionAsDotString();
Aya::Analytics::setReporter("RCCService");
Aya::Analytics::setAppVersion(vi.GetFileVersionAsString());
#else
Aya::DebugSettings::robloxVersion = "69.420.00.00"; // Funny
Aya::Analytics::setReporter("RCCService");
Aya::Analytics::setAppVersion("69.420.00.00");
#endif
}
#if !defined(__linux) && !defined(__APPLE__)
s_perfCounter = CProcessPerfCounter::getInstance();
#endif
perfData.reset(new boost::thread(Aya::thread_wrapper(boost::bind(&CWebService::collectPerfData, this), "CWebService::collectPerfData")));
Aya::Http::init(Aya::Http::WinHttp, Aya::Http::CookieSharingSingleProcessMultipleThreads);
Aya::Http::requester = "Server";
Aya::Profiling::init(false);
static Aya::FactoryRegistrator registerFactoryObjects; // this needs to be here so srand is called before rand
#if !defined(__linux) && !defined(__APPLE__)
RobloxCrashReporter::silent = true;
mainLogManager->WriteCrashDump();
#endif
isThumbnailer = false;
LoadAppSettings();
LoadClientSettings(rccSettings);
if (rccSettings.GetError())
{
AYACRASH(rccSettings.GetErrorString().c_str());
}
Aya::TaskSchedulerSettings::singleton();
Aya::TaskScheduler::singleton().setThreadCount(Aya::TaskScheduler::ThreadPoolConfig(FInt::RCCServiceThreadCount));
// Force loading of settings classes
Aya::GameSettings::singleton();
Aya::GameBasicSettings::singleton();
Aya::LuaSettings::singleton();
Aya::DebugSettings::singleton();
Aya::PhysicsSettings::singleton();
Aya::Soundscape::SoundService::soundDisabled = true;
// Initialize the network code
Aya::Network::initWithServerSecurity();
// If crashUploadOnly = true, don't create a separate thread of control for uploading
#if !defined(__linux) && !defined(__APPLE__)
static DumpErrorUploader dumpErrorUploader(!crashUploadOnly, "RCCService");
// KISEKI TODO: This shit hangs very easily bc its slow as fuck..
std::string dmpHandlerUrl = GetGridUrl(::GetBaseURL(), FFlag::UseDataDomain);
dumpErrorUploader.InitCrashEvent(dmpHandlerUrl, mainLogManager->getCrashEventName());
dumpErrorUploader.Upload(dmpHandlerUrl);
#endif
fetchClientSettingsThread.reset(
new boost::thread(Aya::thread_wrapper(boost::bind(&CWebService::validateClientSettings, this), "CWebService::validateClientSettings")));
// load and set security versions immediately at start up so it's guaranteed to be there when server is launched
/*
securityKeyUpdater.reset(new SecurityKeyUpdater(GetSecurityKeyUrl2(GetBaseURL(), "2b4ba7fc-5843-44cf-b107-ba22d3319dcd")));
md5Updater.reset(new MD5Updater(GetMD5HashUrl(GetBaseURL(), "2b4ba7fc-5843-44cf-b107-ba22d3319dcd")));
memHashUpdater.reset(new MemHashUpdater(GetMemHashUrl(GetBaseURL(), "2b4ba7fc-5843-44cf-b107-ba22d3319dcd")));
if (DFFlag::UseNewSecurityKeyApi)
{
securityKeyUpdater->run();
}
else
{
std::vector<std::string> versions = fetchAllowedSecurityVersions();
Aya::Network::setSecurityVersions(versions);
}
md5Updater->run();
if (DFFlag::UseNewMemHashApi)
{
if (DFString::MemHashConfig.size() < 3)
{
memHashUpdater->run();
}
else
{
Aya::Network::MemHashConfigs hashConfigs;
MemHashUpdater::populateMemHashConfigs(hashConfigs, DFString::MemHashConfig);
Aya::Network::Players::setGoldMemHashes(hashConfigs);
}
}
// this thread uses client setting values, so it must be started AFTER client settings are loaded
// create a thread to periodically check for security key changes
fetchSecurityDataThread.reset(new boost::thread(Aya::thread_wrapper(boost::bind(&CWebService::validateSecurityData, this),
"CWebService::validateSecurityData")));
counters.reset(new CountersClient(GetBaseURL().c_str(), "76E5A40C-3AE1-4028-9F10-7C62520BD94F", NULL));
*/
Aya::ViewBase::InitPluginModules();
}
CWebService::~CWebService()
{
// i hope this line isn't important (again)
#if !defined(__linux) && !defined(__APPLE__)
doneEvent.Set();
#endif
perfData->join();
// fetchSecurityDataThread->join();
// fetchClientSettingsThread->join();
// TODO: this line crashes sometimes :-(
int result;
closeAllJobs(&result);
Aya::ViewBase::ShutdownPluginModules();
}
std::string CWebService::GetSettingsKey()
{
return "RccThumbnailers";
}
void CWebService::LoadClientSettings(RCCServiceSettings& dest)
{
// cache settings in a string before processing
LoadClientSettings(clientSettingsData, thumbnailSettingsData);
LoadClientSettingsFromString("RccThumbnailers", clientSettingsData, &dest);
}
void CWebService::LoadClientSettings(std::string& clientDest, std::string& thumbnailDest)
{
clientDest.clear();
thumbnailDest.clear();
std::string key = GetSettingsKey();
FetchClientSettingsData("RccThumbnailers", "D6925E56-BFB9-4908-AAA2-A5B1EC4B2D79", &thumbnailDest);
FetchClientSettingsData("RccThumbnailers", "D6925E56-BFB9-4908-AAA2-A5B1EC4B2D79", &clientDest);
bool invalidClientSettings = (clientDest.empty() || key.length() == 0);
bool invalidThumbnailerSettings = (thumbnailDest.empty() && isThumbnailer);
if (invalidClientSettings || invalidThumbnailerSettings)
{
Aya::StandardOut::singleton()->printf(
Aya::MESSAGE_ERROR, "Unable to load ClientSettings / ThumbnailerSettings! Please check the webserver. Unexpected behavior may occur.");
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_ERROR, "-- invalidClientSettings: %s, invalidThumbnailerSettings: %s",
invalidClientSettings ? "true" : "false", invalidThumbnailerSettings ? "true" : "false");
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_ERROR, "-- clientDest.empty(): %s, key.length() == 0: %s",
clientDest.empty() ? "true" : "false", key.length() == 0 ? "true" : "false");
}
}
void CWebService::LoadAppSettings()
{
SetBaseURL(GetBaseURL());
}
std::vector<std::string> CWebService::fetchAllowedSecurityVersions()
{
const std::string& baseUrl = GetBaseURL();
if (baseUrl.size() == 0)
AYACRASH(); // y u no set BaseURL??
std::vector<std::string> versions;
std::string url = GetSecurityKeyUrl(baseUrl, "2b4ba7fc-5843-44cf-b107-ba22d3319dcd");
Aya::Http request(url);
std::string allowedSecurityVerionsData = "";
try
{
request.get(allowedSecurityVerionsData);
// no need to continue if security version did not change
if (allowedSecurityVerionsData == securityVersionData)
return std::vector<std::string>();
// parse json data
rapidjson::Document document;
if (!document.Parse(allowedSecurityVerionsData.c_str()).HasParseError())
{
if (document.HasMember("data") && document["data"].IsArray())
{
const rapidjson::Value& dataArray = document["data"];
for (rapidjson::SizeType i = 0; i < dataArray.Size(); i++)
{
// version = data + salt
#ifndef __linux
std::string version = Aya::sha1(dataArray[i].GetString() + std::string("askljfLUZF"));
#else
// this causes a linker error, im too lazy to go edit cmakelists
std::string version = (dataArray[i].GetString() + std::string("askljfLUZF"));
#endif
versions.push_back(version);
}
}
}
securityVersionData = allowedSecurityVerionsData;
}
catch (std::exception ex)
{
FASTLOG(FLog::Always, "LoadAllowedPlayerVersions exception");
}
return versions;
}
void CWebService::JobItem::touch(double seconds)
{
expirationTime = Aya::Time::now<Aya::Time::Fast>() + Aya::Time::Interval(seconds);
}
double CWebService::JobItem::secondsToTimeout() const
{
return (expirationTime - Aya::Time::now<Aya::Time::Fast>()).seconds();
}
int RCCServiceSoapService::HelloWorld(_ns1__HelloWorld* ns1__HelloWorld, _ns1__HelloWorldResponse& ns1__HelloWorldResponse)
{
BEGIN_PRINT(HelloWorld, "HelloWorld");
ns1__HelloWorldResponse.HelloWorldResult = soap_new_std__string(this, -1);
*ns1__HelloWorldResponse.HelloWorldResult = "Hello World";
END_PRINT(HelloWorld, "HelloWorld");
return 0;
}
int RCCServiceSoapService::Diag(_ns1__Diag* ns1__Diag, _ns1__DiagResponse& ns1__DiagResponse)
{
BEGIN_PRINT(Diag, "Diag");
Aya::Security::Impersonator impersonate(Aya::Security::WebService);
CWebService::singleton->diag(ns1__Diag->type, *ns1__Diag->jobID, &ns1__DiagResponse.DiagResult, this);
END_PRINT(Diag, "Diag");
return 0;
}
int RCCServiceSoapService::DiagEx(_ns1__DiagEx* ns1__DiagEx, _ns1__DiagExResponse& ns1__DiagExResponse)
{
BEGIN_PRINT(DiagEx, "DiagEx");
Aya::Security::Impersonator impersonate(Aya::Security::WebService);
ns1__DiagExResponse.DiagExResult = soap_new_ns1__ArrayOfLuaValue(this, -1);
CWebService::singleton->diag(ns1__DiagEx->type, *ns1__DiagEx->jobID, &ns1__DiagExResponse.DiagExResult->LuaValue, this);
END_PRINT(DiagEx, "DiagEx");
return 0;
}
int RCCServiceSoapService::GetVersion(_ns1__GetVersion* ns1__GetVersion, _ns1__GetVersionResponse& ns1__GetVersionResponse)
{
BEGIN_PRINT(GetVersion, "GetVersion");
ns1__GetVersionResponse.GetVersionResult = Aya::DebugSettings::robloxVersion.c_str();
END_PRINT(GetVersion, "GetVersion");
return 0;
}
int RCCServiceSoapService::GetStatus(_ns1__GetStatus* ns1__GetStatus, _ns1__GetStatusResponse& ns1__GetStatusResponse)
{
BEGIN_PRINT(GetStatus, "GetStatus");
ns1__GetStatusResponse.GetStatusResult = soap_new_ns1__Status(this, -1);
ns1__GetStatusResponse.GetStatusResult->version = soap_new_std__string(this, -1);
*ns1__GetStatusResponse.GetStatusResult->version = Aya::DebugSettings::robloxVersion.c_str();
ns1__GetStatusResponse.GetStatusResult->environmentCount = CWebService::singleton->jobCount();
END_PRINT(GetStatus, "GetStatus");
return 0;
}
int RCCServiceSoapService::OpenJob(_ns1__OpenJob* ns1__OpenJob, _ns1__OpenJobResponse& ns1__OpenJobResponse)
{
BEGIN_PRINT(OpenJob, "OpenJob");
Aya::Security::Impersonator impersonate(Aya::Security::WebService);
CWebService::singleton->openJob(*ns1__OpenJob->job, ns1__OpenJob->script, &ns1__OpenJobResponse.OpenJobResult, this, true);
END_PRINT(OpenJob, "OpenJob");
return 0;
}
int RCCServiceSoapService::OpenJobEx(_ns1__OpenJobEx* ns1__OpenJobEx, _ns1__OpenJobExResponse& ns1__OpenJobExResponse)
{
BEGIN_PRINT(OpenJobEx, "OpenJobEx");
Aya::Security::Impersonator impersonate(Aya::Security::WebService);
ns1__OpenJobExResponse.OpenJobExResult = soap_new_ns1__ArrayOfLuaValue(this, -1);
CWebService::singleton->openJob(*ns1__OpenJobEx->job, ns1__OpenJobEx->script, &ns1__OpenJobExResponse.OpenJobExResult->LuaValue, this, true);
END_PRINT(OpenJobEx, "OpenJobEx");
return 0;
}
int RCCServiceSoapService::Execute(_ns1__Execute* ns1__Execute, _ns1__ExecuteResponse& ns1__ExecuteResponse)
{
BEGIN_PRINT(Execute, "Execute");
try
{
Aya::Security::Impersonator impersonate(Aya::Security::WebService);
CWebService::singleton->execute(ns1__Execute->jobID, ns1__Execute->script, &ns1__ExecuteResponse.ExecuteResult, this);
}
catch (std::exception&)
{
throw;
}
END_PRINT(Execute, "Execute");
return 0;
}
int RCCServiceSoapService::ExecuteEx(_ns1__ExecuteEx* ns1__ExecuteEx, _ns1__ExecuteExResponse& ns1__ExecuteExResponse)
{
BEGIN_PRINT(ExecuteEx, "ExecuteEx");
try
{
Aya::Security::Impersonator impersonate(Aya::Security::WebService);
ns1__ExecuteExResponse.ExecuteExResult = soap_new_ns1__ArrayOfLuaValue(this, -1);
CWebService::singleton->execute(ns1__ExecuteEx->jobID, ns1__ExecuteEx->script, &ns1__ExecuteExResponse.ExecuteExResult->LuaValue, this);
}
catch (std::exception&)
{
throw;
}
END_PRINT(ExecuteEx, "ExecuteEx");
return 0;
}
int RCCServiceSoapService::CloseJob(_ns1__CloseJob* ns1__CloseJob, _ns1__CloseJobResponse& ns1__CloseJobResponse)
{
BEGIN_PRINT(CloseJob, "CloseJob");
try
{
Aya::Security::Impersonator impersonate(Aya::Security::WebService);
CWebService::singleton->closeJob(ns1__CloseJob->jobID);
}
catch (std::exception&)
{
throw;
}
END_PRINT(CloseJob, "CloseJob");
return 0;
}
int RCCServiceSoapService::RenewLease(_ns1__RenewLease* ns1__RenewLease, _ns1__RenewLeaseResponse& ns1__RenewLeaseResponse)
{
BEGIN_PRINT(RenewLease, "RenewLease");
try
{
Aya::Security::Impersonator impersonate(Aya::Security::WebService);
CWebService::singleton->renewLease(ns1__RenewLease->jobID, ns1__RenewLease->expirationInSeconds);
}
catch (std::exception&)
{
throw;
}
END_PRINT(RenewLease, "RenewLease");
return 0;
}
int RCCServiceSoapService::BatchJob(_ns1__BatchJob* ns1__BatchJob, _ns1__BatchJobResponse& ns1__BatchJobResponse)
{
BEGIN_PRINT(BatchJob, "BatchJob");
Aya::Security::Impersonator impersonate(Aya::Security::WebService);
// Batch jobs are completed synchronously, so there is no need to start the heartbeat
try
{
CWebService::singleton->batchJob(*ns1__BatchJob->job, ns1__BatchJob->script, &ns1__BatchJobResponse.BatchJobResult, this);
}
catch (std::exception&)
{
throw;
}
END_PRINT(BatchJob, "BatchJob");
return 0;
}
int RCCServiceSoapService::BatchJobEx(_ns1__BatchJobEx* ns1__BatchJobEx, _ns1__BatchJobExResponse& ns1__BatchJobExResponse)
{
BEGIN_PRINT(BatchJobEx, "BatchJobEx");
Aya::Security::Impersonator impersonate(Aya::Security::WebService);
ns1__BatchJobExResponse.BatchJobExResult = soap_new_ns1__ArrayOfLuaValue(this, -1);
// Batch jobs are completed synchronously, so there is no need to start the heartbeat
try
{
CWebService::singleton->batchJob(*ns1__BatchJobEx->job, ns1__BatchJobEx->script, &ns1__BatchJobExResponse.BatchJobExResult->LuaValue, this);
}
catch (std::exception&)
{
throw;
}
END_PRINT(BatchJobEx, "BatchJobEx");
return 0;
}
int RCCServiceSoapService::GetExpiration(_ns1__GetExpiration* ns1__GetExpiration, _ns1__GetExpirationResponse& ns1__GetExpirationResponse)
{
BEGIN_PRINT(GetExpiration, "GetExpiration");
try
{
CWebService::singleton->getExpiration(ns1__GetExpiration->jobID, &ns1__GetExpirationResponse.GetExpirationResult);
}
catch (std::exception&)
{
#if !defined(__linux) && !defined(__APPLE__)
::InterlockedDecrement(&getExpirationCount);
#endif
throw;
}
END_PRINT(GetExpiration, "GetExpiration");
return 0;
}
int RCCServiceSoapService::GetAllJobs(_ns1__GetAllJobs* ns1__GetAllJobs, _ns1__GetAllJobsResponse& ns1__GetAllJobsResponse)
{
BEGIN_PRINT(GetAllJobs, "GetAllJobs");
Aya::Security::Impersonator impersonate(Aya::Security::WebService);
CWebService::singleton->getAllJobs(ns1__GetAllJobsResponse.GetAllJobsResult, this);
END_PRINT(GetAllJobs, "GetAllJobs");
return 0;
}
int RCCServiceSoapService::GetAllJobsEx(_ns1__GetAllJobsEx* ns1__GetAllJobsEx, _ns1__GetAllJobsExResponse& ns1__GetAllJobsExResponse)
{
BEGIN_PRINT(GetAllJobsEx, "GetAllJobsEx");
Aya::Security::Impersonator impersonate(Aya::Security::WebService);
ns1__GetAllJobsExResponse.GetAllJobsExResult = soap_new_ns1__ArrayOfJob(this, -1);
CWebService::singleton->getAllJobs(ns1__GetAllJobsExResponse.GetAllJobsExResult->Job, this);
END_PRINT(GetAllJobsEx, "GetAllJobsEx");
return 0;
}
int RCCServiceSoapService::CloseExpiredJobs(
_ns1__CloseExpiredJobs* ns1__CloseExpiredJobs, _ns1__CloseExpiredJobsResponse& ns1__CloseExpiredJobsResponse)
{
BEGIN_PRINT(GetAllJobs, "CloseExpiredJobs");
Aya::Security::Impersonator impersonate(Aya::Security::WebService);
CWebService::singleton->closeExpiredJobs(&ns1__CloseExpiredJobsResponse.CloseExpiredJobsResult);
END_PRINT(GetAllJobs, "CloseExpiredJobs");
return 0;
}
int RCCServiceSoapService::CloseAllJobs(_ns1__CloseAllJobs* ns1__CloseAllJobs, _ns1__CloseAllJobsResponse& ns1__CloseAllJobsResponse)
{
BEGIN_PRINT(GetAllJobs, "CloseAllJobs");
Aya::Security::Impersonator impersonate(Aya::Security::WebService);
CWebService::singleton->closeAllJobs(&ns1__CloseAllJobsResponse.CloseAllJobsResult);
END_PRINT(GetAllJobs, "CloseAllJobs");
return 0;
}