mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
3222 lines
111 KiB
C++
3222 lines
111 KiB
C++
/* Copyright 2003-2006 ROBLOX Corporation, All Rights Reserved */
|
|
|
|
#include "ClientReplicator.h"
|
|
#include "Client.h"
|
|
#include "Util.h"
|
|
#include "Marker.h"
|
|
|
|
#include "network/Players.h"
|
|
#include "ConcurrentRakPeer.h"
|
|
#include "FastLog.h"
|
|
#include "Util/ProgramMemoryChecker.h"
|
|
#include "V8DataModel/HackDefines.h"
|
|
#include "Util/ScopedAssign.h"
|
|
#include "V8DataModel/HackDefines.h"
|
|
#include "V8DataModel/JointInstance.h"
|
|
#include "V8DataModel/JointsService.h"
|
|
#include "V8DataModel/TimerService.h"
|
|
#include "V8DataModel/message.h"
|
|
#include "V8World/Joint.h"
|
|
#include "v8datamodel/MegaCluster.h"
|
|
#include "v8datamodel/PlayerGui.h"
|
|
#include "script/ScriptContext.h"
|
|
#include "RoundRobinPhysicsSender.h"
|
|
#include "DirectPhysicsReceiver.h"
|
|
|
|
|
|
// TODO - eliminate this - better place for distributed physics switch
|
|
#include "V8DataModel/Workspace.h"
|
|
#include "v8datamodel/DataModel.h"
|
|
#include "V8DataModel/PartInstance.h"
|
|
#include "v8datamodel/TeleportService.h"
|
|
#include "v8datamodel/ReplicatedFirst.h"
|
|
#include "V8World/Assembly.h"
|
|
#include "Replicator.StatsItem.h"
|
|
#include "Replicator.GCJob.h"
|
|
#include "NetworkProfiler.h"
|
|
#include "NetworkFilter.h"
|
|
#include "humanoid/Humanoid.h"
|
|
|
|
// For security
|
|
#include "humanoid/HumanoidState.h"
|
|
#include "humanoid/FallingDown.h"
|
|
|
|
// TODO remove this
|
|
#include "v8world/World.h"
|
|
|
|
#include <boost/scoped_ptr.hpp>
|
|
#include "VMProtectSDK.h"
|
|
#include "Replicator.StreamJob.h"
|
|
#include "Replicator.HashItem.h"
|
|
#include "Replicator.TagItem.h"
|
|
#include "Replicator.ChangePropertyItem.h"
|
|
#include "Replicator.RockyItem.h"
|
|
#include "util/PhysicalProperties.h"
|
|
#include "util/ProtectedString.h"
|
|
#include "util/UDim.h"
|
|
#include "util/Axes.h"
|
|
|
|
#include "rbx/Profiler.h"
|
|
|
|
#if defined(_WIN32) && !defined(RBX_PLATFORM_DURANGO)
|
|
#include "util/CheatEngine.h"
|
|
#include "security/ApiSecurity.h"
|
|
#endif
|
|
|
|
FASTFLAG(DebugLocalRccServerConnection)
|
|
DYNAMIC_LOGVARIABLE(PartStreamingRequests, 0)
|
|
DYNAMIC_FASTINTVARIABLE(ClientInstanceQuotaCap, 10000)
|
|
DYNAMIC_FASTINTVARIABLE(ClientInstanceQuotaInitial, 2000)
|
|
FASTFLAG(DebugProtocolSynchronization)
|
|
FASTINT(StreamingCriticalLowMemWatermarkMB)
|
|
FASTFLAG(RemoveUnusedPhysicsSenders)
|
|
|
|
FASTFLAG(ClientABTestingEnabled)
|
|
FASTFLAGVARIABLE(CopyArrayReferences, true)
|
|
|
|
FASTFLAG(FilterSinglePass)
|
|
|
|
namespace{
|
|
static RBX::Network::MccReport mccReport = {};
|
|
}
|
|
|
|
namespace RBX { namespace Network {
|
|
|
|
class ClientReplicator::ClientStatsItem : public Replicator::Stats
|
|
{
|
|
Item* itemCount;
|
|
Item* pendingInstanceRequests;
|
|
Stats::Item* streamPacketsReceived;
|
|
Stats::Item* streamReceiverAvgTimePerPacket;
|
|
|
|
Item* numRegionsToGC;
|
|
Item* gcDistance;
|
|
Item* numStreamedRegions;
|
|
|
|
public:
|
|
ClientStatsItem(const shared_ptr<const ClientReplicator>& replicator)
|
|
:Replicator::Stats(replicator)
|
|
{
|
|
Item* item = createChildItem("PropSync");
|
|
itemCount = item->createChildItem("ItemCount");
|
|
item->createBoundChildItem("AckCount", replicator->propSync.ackCount);
|
|
|
|
Item* receivedStreamData = createChildItem("Received Stream Data");
|
|
receivedStreamData->createBoundChildItem("Avg Read Time per item", replicator->avgStreamDataReadTime);
|
|
receivedStreamData->createBoundChildItem("Avg Instances per item", replicator->avgInstancesPerStreamData);
|
|
receivedStreamData->createBoundChildItem("Avg Request count", replicator->avgRequestCount);
|
|
pendingInstanceRequests = receivedStreamData->createChildItem("Pending Request count");
|
|
numRegionsToGC = receivedStreamData->createChildItem("Num Regions To GC");
|
|
gcDistance = receivedStreamData->createChildItem("GC Distance");
|
|
numStreamedRegions = receivedStreamData->createChildItem("Num Regions");
|
|
}
|
|
|
|
/*override*/ void update()
|
|
{
|
|
Replicator::Stats::update();
|
|
|
|
if(shared_ptr<const ClientReplicator> locked = shared_static_cast<const ClientReplicator>(replicator.lock()))
|
|
{
|
|
pendingInstanceRequests->formatValue(locked->pendingInstanceRequests - locked->numInstancesRead);
|
|
itemCount->formatValue(locked->propSync.itemCount());
|
|
|
|
numRegionsToGC->formatValue(locked->getNumRegionsToGC());
|
|
gcDistance->formatValue((int)locked->getGCDistance());
|
|
numStreamedRegions->formatValue(locked->getNumStreamedRegions());
|
|
}
|
|
}
|
|
};
|
|
|
|
#if defined(_WIN32) && !defined(RBX_STUDIO_BUILD) && !defined(RBX_PLATFORM_DURANGO)
|
|
// Periodically check that the program memory hashing job is still running
|
|
class ClientReplicator::BadAppCheckerJob : public DataModelJob {
|
|
shared_ptr<ClientReplicator> clientReplicator;
|
|
Time lastRunTime;
|
|
HwndScanner hwndScanner;
|
|
FileScanner fileScanner;
|
|
DbvmCanary dbvmScanner;
|
|
SpeedhackDetect speedhackScanner;
|
|
|
|
unsigned char scanCounter;
|
|
static const unsigned int kNumScans = 7;
|
|
static const unsigned int kCeHwndScanMask = 1<<0;
|
|
static const unsigned int kCeTitleScanMask = 1<<1;
|
|
static const unsigned int kCeAttachScanMask = 1<<2;
|
|
static const unsigned int kCeLogScanMask = 1<<3;
|
|
static const unsigned int kCeDbvmGtx = 1<<4;
|
|
static const unsigned int kCeDbvmStx = 1<<5;
|
|
static const unsigned int kCeDll = 1<<6;
|
|
static const unsigned int kCeAllScansMask = (1<<kNumScans) - 1;
|
|
|
|
void doScans(unsigned int scanMask)
|
|
{
|
|
#if defined(_WIN32) && !defined(_NOOPT) && !defined(_DEBUG)
|
|
VMProtectBeginMutation(NULL);
|
|
bool isCeDetectedLocal = false;
|
|
DataModel* dataModel = DataModel::get(clientReplicator.get());
|
|
if (scanMask & kCeDll)
|
|
{
|
|
isCeDetectedLocal = isCeDetectedLocal || (isCeBadDll());
|
|
}
|
|
if (scanMask & kCeDbvmGtx)
|
|
{
|
|
dbvmScanner.checkAndLocalUpdate();
|
|
}
|
|
if (scanMask & kCeDbvmStx)
|
|
{
|
|
dbvmScanner.kernelUpdate();
|
|
}
|
|
if (ceHwndChecks && (kCeHwndScanMask & scanMask))
|
|
{
|
|
hwndScanner.scan();
|
|
}
|
|
isCeDetectedLocal = isCeDetectedLocal || (ceHwndChecks
|
|
&& (scanMask & kCeTitleScanMask) && hwndScanner.detectTitle());
|
|
isCeDetectedLocal = isCeDetectedLocal || (ceHwndChecks
|
|
&& (scanMask & kCeAttachScanMask) && hwndScanner.detectFakeAttach());
|
|
isCeDetectedLocal = isCeDetectedLocal || (ceHwndChecks
|
|
&& (scanMask * kCeAttachScanMask) && hwndScanner.detectEarlyKick());
|
|
#if 0
|
|
// This code results in false positives, but it isn't clear why.
|
|
// This should be debugged.
|
|
if (speedhackScanner.isSpeedhack())
|
|
{
|
|
RBX::Tokens::simpleToken |= HATE_SPEEDHACK;
|
|
}
|
|
#endif
|
|
VMProtectEnd();
|
|
VMProtectBeginVirtualization(NULL);
|
|
if(ceDetected || isCeDetectedLocal)
|
|
{
|
|
dataModel->addHackFlag(HATE_CHEATENGINE_OLD);
|
|
RBX::Tokens::sendStatsToken.addFlagSafe(HATE_CHEATENGINE_OLD);
|
|
}
|
|
VMProtectEnd();
|
|
#endif
|
|
}
|
|
|
|
public:
|
|
BadAppCheckerJob(shared_ptr<ClientReplicator> clientReplicator) :
|
|
DataModelJob("BatteryProfiler", DataModelJob::None, false,
|
|
shared_from(DataModel::get(clientReplicator.get())), Time::Interval(0)),
|
|
clientReplicator(clientReplicator),
|
|
lastRunTime(Time::nowFast()),
|
|
scanCounter(0) {}
|
|
|
|
virtual Time::Interval sleepTime(const Stats& stats)
|
|
{
|
|
return computeStandardSleepTime(stats, 1);
|
|
}
|
|
|
|
virtual Error error(const Stats& stats)
|
|
{
|
|
return computeStandardErrorCyclicExecutiveSleeping(stats, 1);
|
|
}
|
|
|
|
virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
|
{
|
|
VMProtectBeginMutation("27");
|
|
doScans(1<<scanCounter);
|
|
lastRunTime = Time::nowFast();
|
|
scanCounter = (scanCounter + 1) % kNumScans;
|
|
VMProtectEnd();
|
|
return TaskScheduler::Stepped;
|
|
}
|
|
|
|
Time getLastRunTime() const
|
|
{
|
|
return lastRunTime;
|
|
}
|
|
|
|
};
|
|
#endif // #ifdef _WIN32
|
|
|
|
|
|
|
|
#if !defined(RBX_STUDIO_BUILD) && !defined(RBX_PLATFORM_DURANGO)
|
|
// Periodically hash program memory, and raise an alert if the hash changes
|
|
class ClientReplicator::MemoryCheckerJob : public DataModelJob {
|
|
|
|
boost::shared_ptr<ClientReplicator> clientReplicator;
|
|
boost::scoped_ptr<ProgramMemoryChecker> memoryChecker;
|
|
unsigned int firstHash;
|
|
Time lastRunTime;
|
|
Time lastHashTime;
|
|
#ifdef _WIN32
|
|
NtApiCaller ntApi;
|
|
#endif
|
|
Humanoid hsceFakeHumanoid;
|
|
shared_ptr<HUMAN::Dead> hsceFakeHumanoidState;
|
|
static const unsigned int kTimeToCompleteHash = 3;
|
|
static const unsigned int kStepFrequency = ProgramMemoryChecker::kSteps/kTimeToCompleteHash;
|
|
|
|
public:
|
|
rbx::signal<void()> hashReadySignal;
|
|
|
|
MemoryCheckerJob(shared_ptr<ClientReplicator> clientReplicator) :
|
|
DataModelJob("US14116", DataModelJob::None, false,
|
|
shared_from(DataModel::get(clientReplicator.get())), Time::Interval(0)),
|
|
clientReplicator(clientReplicator),
|
|
lastRunTime(Time::nowFast()),
|
|
lastHashTime(Time::nowFast())
|
|
{
|
|
memoryChecker.reset(new ProgramMemoryChecker());
|
|
// after memory checker is confirmed to work, make this into a const again.
|
|
firstHash = memoryChecker->getLastCompletedHash();
|
|
hsceFakeHumanoidState.reset(new HUMAN::Dead(&hsceFakeHumanoid, HUMAN::DEAD));
|
|
}
|
|
|
|
virtual Time::Interval sleepTime(const Stats& stats)
|
|
{
|
|
return computeStandardSleepTime(stats, kStepFrequency);
|
|
}
|
|
|
|
virtual Error error(const Stats& stats)
|
|
{
|
|
return computeStandardError(stats, kStepFrequency);
|
|
}
|
|
|
|
virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
|
{
|
|
VMProtectBeginMutation(NULL);
|
|
bool isHsceHashFail = false;
|
|
bool isLuaLockFail = false;
|
|
bool isHsceUnitFail = false;
|
|
unsigned int codeChanged = 0;
|
|
unsigned int goldCodeChanged = 0;
|
|
DataModel* thisModel = DataModel::get(clientReplicator.get());
|
|
|
|
#ifdef _WIN32
|
|
// debugger check
|
|
CONTEXT context;
|
|
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
|
HANDLE thisThread = reinterpret_cast<HANDLE>(~(size_t)(1));
|
|
if (ntApi.getThreadContext(thisThread, &context) && (context.Dr7 & 0xff) != 0)
|
|
{
|
|
RBX::Security::setHackFlagVmp<LINE_RAND4>(RBX::Security::hackFlag9, HATE_DEBUGGER);
|
|
return TaskScheduler::Stepped;
|
|
}
|
|
#endif
|
|
unsigned int sendSignal = memoryChecker->step();
|
|
// firstHash, initialProgramHash, and lastCompletedHash should all be equal
|
|
int diff = (firstHash != initialProgramHash)
|
|
| (memoryChecker->getLastCompletedHash() != initialProgramHash);
|
|
codeChanged = static_cast<unsigned int>(diff | -diff) >> 31;
|
|
#ifdef _WIN32
|
|
unsigned int thisHsceHash = memoryChecker->updateHsceHash();
|
|
isHsceHashFail = ((thisHsceHash != memoryChecker->getHsceAndHash()) || thisHsceHash != memoryChecker->getHsceOrHash());
|
|
#else
|
|
const unsigned int mask = codeChanged * HATE_OSX_MEMORY_HASH_CHANGED;
|
|
thisModel->addHackFlag(mask);
|
|
#endif
|
|
|
|
#if !defined(_DEBUG) && !defined(_NOOPT) && defined(_WIN32)
|
|
// The golden hash is a char* to make it identifable to be patched in the exe
|
|
// Underlying data is actually an unsigned int.
|
|
int goldDiff = (memoryChecker->getLastGoldenHash() != RBX::Security::rbxGoldHash);
|
|
goldCodeChanged = static_cast<unsigned int>(goldDiff | -goldDiff) >> 31;
|
|
#endif
|
|
|
|
lastHashTime = memoryChecker->getLastCompletedTime();
|
|
lastRunTime = Time::nowFast();
|
|
|
|
if (sendSignal == ProgramMemoryChecker::kAllDone)
|
|
{
|
|
hashReadySignal();
|
|
#if defined(_WIN32) && !defined(RBX_PLATFORM_DURNAGO)
|
|
isLuaLockFail = ((memoryChecker->isLuaLockOk() != ProgramMemoryChecker::kLuaLockOk));
|
|
|
|
MEMORY_BASIC_INFORMATION trapInfo;
|
|
ntApi.virtualQuery(&RBX::writecopyTrap, &trapInfo, sizeof(trapInfo));
|
|
if (trapInfo.Protect != PAGE_WRITECOPY)
|
|
{
|
|
// report
|
|
RBX::Security::setHackFlagVmp<LINE_RAND4>(RBX::Security::hackFlag3, HATE_CE_ASM);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// this is to detect DBVM's changes to this part of the program.
|
|
isHsceUnitFail = (hsceFakeHumanoidState->checkComputeEvent() != HUMAN::HumanoidState::kCorrectCheckValue);
|
|
|
|
VMProtectEnd();
|
|
VMProtectBeginVirtualization(NULL);
|
|
if (codeChanged)
|
|
{
|
|
RBX::Security::setHackFlagVmp<LINE_RAND4>(RBX::Security::hackFlag1, HATE_MEMORY_HASH_CHANGED);
|
|
}
|
|
if (isHsceHashFail)
|
|
{
|
|
RBX::Security::setHackFlagVmp<LINE_RAND4>(RBX::Security::hackFlag2, HATE_HSCE_HASH_CHANGED);
|
|
}
|
|
if (goldCodeChanged)
|
|
{
|
|
RBX::Security::setHackFlagVmp<LINE_RAND4>(RBX::Security::hackFlag3, HATE_MEMORY_HASH_CHANGED);
|
|
}
|
|
if (isLuaLockFail)
|
|
{
|
|
RBX::Security::setHackFlagVmp<LINE_RAND4>(RBX::Security::hackFlag4, HATE_DLL_INJECTION);
|
|
}
|
|
if (isHsceUnitFail)
|
|
{
|
|
RBX::Security::setHackFlagVmp<LINE_RAND4>(RBX::Security::hackFlag5, HATE_HSCE_HASH_CHANGED);
|
|
}
|
|
VMProtectEnd();
|
|
return TaskScheduler::Stepped;
|
|
}
|
|
|
|
Time getLastRunTime() const {
|
|
return lastRunTime;
|
|
}
|
|
|
|
Time getLastHashTime() const {
|
|
return lastHashTime;
|
|
}
|
|
};
|
|
#endif
|
|
|
|
#if defined(_WIN32) && !defined(RBX_PLATFORM_DURANGO) && !defined(RBX_STUDIO_BUILD)
|
|
// Periodically check that the program memory hashing job is still running
|
|
class ClientReplicator::MemoryCheckerCheckerJob : public DataModelJob {
|
|
|
|
shared_ptr<ClientReplicator> clientReplicator;
|
|
Time lastKnownValidXXHashRun;
|
|
Time lastCheckTime;
|
|
int runSlips;
|
|
int hashSlips;
|
|
NtApiCaller ntApi;
|
|
int runCount;
|
|
unsigned int pageSize;
|
|
unsigned int encodedReport;
|
|
|
|
__forceinline bool memPermissionChanged(uintptr_t regionBase, size_t regionSize, size_t protection)
|
|
{
|
|
MEMORY_BASIC_INFORMATION memInfo;
|
|
if (0 == ntApi.virtualQuery(((void*)(regionBase)), &memInfo, sizeof(MEMORY_BASIC_INFORMATION))) {
|
|
unsigned int sizeDiff = memInfo.RegionSize - regionSize; // makes use of the overflow for the "too small" case.
|
|
if ((sizeDiff > pageSize) || (memInfo.Protect != protection))
|
|
{
|
|
memset(&memInfo, 0, sizeof(memInfo));
|
|
return true;
|
|
}
|
|
}
|
|
memset(&memInfo, 0, sizeof(memInfo));
|
|
return false;
|
|
}
|
|
|
|
__forceinline void populateMccReport(RBX::Network::MccReport& report)
|
|
{
|
|
report.localChecksEncoded = encodedReport;
|
|
report.memcheckRunTime = static_cast<unsigned int>(clientReplicator->memoryCheckerJob->getLastRunTime().timestampSeconds());
|
|
report.memcheckDoneTime = static_cast<unsigned int>(clientReplicator->memoryCheckerJob->getLastHashTime().timestampSeconds());
|
|
report.badAppRunTime = static_cast<unsigned int>(clientReplicator->badAppCheckerJob->getLastRunTime().timestampSeconds());
|
|
report.mccRunTime = static_cast<unsigned int>(Time::nowFast().timestampSeconds());
|
|
}
|
|
|
|
public:
|
|
rbx::signal<void()> reportReadySignal;
|
|
|
|
MemoryCheckerCheckerJob(shared_ptr<ClientReplicator> clientReplicator) :
|
|
DataModelJob("US14116_pt2", DataModelJob::None, false,
|
|
shared_from(DataModel::get(clientReplicator.get())), Time::Interval(0)),
|
|
clientReplicator(clientReplicator),
|
|
runSlips(0),
|
|
hashSlips(0),
|
|
lastKnownValidXXHashRun(Time::nowFast()),
|
|
lastCheckTime(Time::nowFast()),
|
|
runCount(0),
|
|
encodedReport(kGf2EncodeLut[MCC_INIT_IDX])
|
|
{
|
|
SYSTEM_INFO info;
|
|
GetSystemInfo(&info);
|
|
pageSize = info.dwPageSize;
|
|
}
|
|
|
|
virtual Time::Interval sleepTime(const Stats& stats)
|
|
{
|
|
return computeStandardSleepTime(stats, 1);
|
|
}
|
|
|
|
virtual Error error(const Stats& stats)
|
|
{
|
|
return computeStandardErrorCyclicExecutiveSleeping(stats, 1);
|
|
}
|
|
|
|
|
|
virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
|
{
|
|
VMProtectBeginMutation(NULL);
|
|
bool isBadTextSection = false;
|
|
bool isBadVmpSection = false;
|
|
bool isBadRdataSection = false;
|
|
bool isHwbpSet = false;
|
|
bool isGtxHook = false;
|
|
bool isVehUnhook = false;
|
|
bool isFreeConsoleHooked = false;
|
|
++runCount;
|
|
#if (!defined(NOOPT) && !defined(DEBUG)) && !defined(RBX_PLATFORM_DURANGO)
|
|
// a switch statement might result in a jump table, which is not compatible with VMProtect.
|
|
if (runCount % 8 == 0)
|
|
{
|
|
isBadTextSection =(memPermissionChanged(RBX::Security::rbxTextBase, RBX::Security::rbxTextSize, PAGE_EXECUTE_READ));
|
|
}
|
|
else if (runCount % 8 == 2)
|
|
{
|
|
isBadVmpSection = (memPermissionChanged(RBX::Security::rbxVmpBase, RBX::Security::rbxVmpSize, PAGE_EXECUTE_READ));
|
|
}
|
|
else if (runCount % 8 == 4)
|
|
{
|
|
isBadRdataSection = (memPermissionChanged(RBX::Security::rbxRdataBase, RBX::Security::rbxRdataSize, PAGE_READONLY));
|
|
}
|
|
else if (runCount % 8 == 6)
|
|
{
|
|
// Debugger Check
|
|
CONTEXT ctx;
|
|
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
|
HANDLE thisThread = reinterpret_cast<HANDLE>(~(size_t)(1));
|
|
isHwbpSet = (ntApi.getThreadContext(thisThread, &ctx) && ((ctx.Dr7 & 0xFF) != 0));
|
|
}
|
|
#endif
|
|
const unsigned short kHotPatchProlog = 0xFF8B;
|
|
isGtxHook = (*((unsigned short*)(&::GetThreadContext)) != kHotPatchProlog); // check for early hook
|
|
if (vehHookLocationHv)
|
|
{
|
|
uintptr_t hookLoc = vehHookLocationHv;
|
|
uintptr_t relJump = *reinterpret_cast<uintptr_t*>(hookLoc);
|
|
uintptr_t jumpTarget = vehStubLocationHv;
|
|
isVehUnhook = ((hookLoc + sizeof(void*) + relJump) != jumpTarget)
|
|
|| (!ntApi.isNtdllAddress(reinterpret_cast<uintptr_t>(vehHookContinue))); // check for basic hook
|
|
}
|
|
if (runCount % 8 == 7)
|
|
{
|
|
populateMccReport(::mccReport);
|
|
reportReadySignal();
|
|
|
|
// this is just to troll exploit developers who are too lazy to write a GUI.
|
|
if (FFlag::CopyArrayReferences)
|
|
{
|
|
FreeConsole();
|
|
}
|
|
if (*reinterpret_cast<uint16_t*>(&FreeConsole) != 0xFF8B)
|
|
{
|
|
isFreeConsoleHooked = true;
|
|
}
|
|
}
|
|
VMProtectEnd();
|
|
VMProtectBeginVirtualization(NULL);
|
|
if (isBadTextSection)
|
|
{
|
|
RBX::Security::setHackFlagVmp<LINE_RAND4>(RBX::Security::hackFlag4, HATE_NEW_AV_CHECK);
|
|
encodedReport ^= kGf2EncodeLut[MCC_TEXT_IDX];
|
|
}
|
|
if (isBadVmpSection)
|
|
{
|
|
RBX::Security::setHackFlagVmp<LINE_RAND4>(RBX::Security::hackFlag5, HATE_NEW_AV_CHECK);
|
|
encodedReport ^= kGf2EncodeLut[MCC_VMP_IDX];
|
|
}
|
|
if (isBadRdataSection)
|
|
{
|
|
RBX::Security::setHackFlagVmp<LINE_RAND4>(RBX::Security::hackFlag6, HATE_NEW_AV_CHECK);
|
|
encodedReport ^= kGf2EncodeLut[MCC_RDATA_IDX];
|
|
}
|
|
else if (runCount % 8 == 5)
|
|
{
|
|
encodedReport ^= kGf2EncodeLut[MCC_NULL0_IDX];
|
|
}
|
|
else
|
|
{
|
|
encodedReport ^= kGf2EncodeLut[MCC_NULL1_IDX];
|
|
}
|
|
if (isHwbpSet)
|
|
{
|
|
RBX::Security::setHackFlagVmp<LINE_RAND4>(RBX::Security::hackFlag3, HATE_NEW_HWBP);
|
|
encodedReport ^= kGf2EncodeLut[MCC_HWBP_IDX];
|
|
}
|
|
if (isGtxHook)
|
|
{
|
|
RBX::Security::setHackFlagVmp<LINE_RAND4>(RBX::Security::hackFlag11, HATE_HOOKED_GTX); // change this later
|
|
encodedReport ^= kGf2EncodeLut[MCC_GTX_IDX];
|
|
}
|
|
if (isVehUnhook)
|
|
{
|
|
RBX::Security::setHackFlagVmp<LINE_RAND4>(RBX::Security::hackFlag1, HATE_UNHOOKED_VEH); // change this later
|
|
encodedReport ^= kGf2EncodeLut[MCC_VEH_IDX];
|
|
}
|
|
if (isFreeConsoleHooked)
|
|
{
|
|
encodedReport ^= kGf2EncodeLut[MCC_FREECONSOLE_IDX];
|
|
}
|
|
if ( (runCount % 8 == 6) && !FFlag::FilterSinglePass)
|
|
{
|
|
encodedReport ^= kGf2EncodeLut[MCC_FAKE_FFLAG_IDX];
|
|
}
|
|
VMProtectEnd();
|
|
return TaskScheduler::Stepped;
|
|
}
|
|
};
|
|
#endif
|
|
}}
|
|
|
|
using namespace RBX;
|
|
using namespace RBX::Network;
|
|
|
|
const char* const RBX::Network::sClientReplicator = "ClientReplicator";
|
|
|
|
REFLECTION_BEGIN();
|
|
static Reflection::BoundFuncDesc<ClientReplicator, void(bool)> func_requestServerStats(&ClientReplicator::requestServerStats, "RequestServerStats", "request", Security::RobloxScript);
|
|
static Reflection::EventDesc<ClientReplicator, void(shared_ptr<const Reflection::ValueTable>)> event_StatsReceived(&ClientReplicator::statsReceivedSignal, "StatsReceived", "stats", Security::RobloxScript);
|
|
REFLECTION_END();
|
|
|
|
shared_ptr<Replicator::Stats> ClientReplicator::createStatsItem()
|
|
{
|
|
return Creatable<Instance>::create<ClientStatsItem>(shared_from(this));
|
|
};
|
|
|
|
class CFrameAcknowledgementItem : public PooledItem
|
|
{
|
|
ClientReplicator* client;
|
|
const shared_ptr<const PartInstance> instance;
|
|
public:
|
|
CFrameAcknowledgementItem(ClientReplicator* client, shared_ptr<const PartInstance> instance):PooledItem(*client),instance(instance),client(client)
|
|
{}
|
|
|
|
/*implement*/ bool write(RakNet::BitStream& bitStream)
|
|
{
|
|
client->writePropAcknowledgementIfNeeded(instance.get(), PartInstance::prop_CFrame, bitStream);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
ClientReplicator::ClientReplicator(RakNet::SystemAddress systemAddress, Client* client, RakNet::SystemAddress clientAddress, NetworkSettings* networkSettings)
|
|
: Super( systemAddress, client->rakPeer, networkSettings, /*ClusterDebounce*/true)
|
|
, clientAddress(clientAddress)
|
|
, receivedGlobals(false)
|
|
, numInstancesRead(0)
|
|
, pendingInstanceRequests(0)
|
|
, loggedLowMemWarning(false)
|
|
, avgInstancesPerStreamData(0.05, 25) // estimated default value 25
|
|
, avgStreamDataReadTime(0.1, 0.05)
|
|
, avgRequestCount(0.1)
|
|
, clientInstanceQuota(0)
|
|
, sampleTimer(-1.0f) // guarantee fire on first call
|
|
, memoryLevel(RBX::MemoryStats::MEMORYLEVEL_OK)
|
|
{
|
|
setName("ClientReplicator");
|
|
cframePool.reset(new AutoMemPool(sizeof(CFrameAcknowledgementItem)));
|
|
|
|
// overwrite replicator default, at start up we want to process all the packets from server asap to allow faster join
|
|
processAllPacketsPerStep = true;
|
|
|
|
canTimeout = false;
|
|
}
|
|
|
|
ClientReplicator::~ClientReplicator()
|
|
{
|
|
sendDisconnectionSignal("", false);
|
|
}
|
|
|
|
Player* ClientReplicator::findTargetPlayer() const
|
|
{
|
|
return players ? players->getLocalPlayer() : 0;
|
|
}
|
|
|
|
bool ClientReplicator::isLegalSendInstance(const Instance* instance)
|
|
{
|
|
if (Instance::fastDynamicCast<Message>(instance))
|
|
return false;
|
|
|
|
if (strictFilter && (strictFilter->filterNew(instance, instance->getParent()) == Reject))
|
|
{
|
|
if (settings().printDataFilters)
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_WARNING, "Filtering is enabled. New Instance %s will not be replicated.", instance->getFullName().c_str());
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ClientReplicator::isLegalSendProperty(Instance* instance, const Reflection::PropertyDescriptor& desc)
|
|
{
|
|
if (strictFilter)
|
|
{
|
|
bool isLegal = true;
|
|
if (desc == Instance::propParent)
|
|
isLegal = strictFilter->filterParent(instance, instance->getParent()) == Accept;
|
|
else
|
|
isLegal = strictFilter->filterChangedProperty(instance, desc) == Accept;
|
|
|
|
if (!isLegal && (desc.canReplicate() || (desc == Instance::propParent)) && settings().printDataFilters)
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_WARNING, "Filtering is enabled. Property %s change for instance %s will not be replicated.", desc.name.c_str(), instance->getFullName().c_str());
|
|
|
|
return isLegal;
|
|
}
|
|
|
|
if (desc == Script::prop_EmbeddedSourceCode || desc == ModuleScript::prop_Source)
|
|
{
|
|
if (isCloudEdit())
|
|
{
|
|
if (LuaSourceContainer* lsc = Instance::fastDynamicCast<LuaSourceContainer>(instance))
|
|
{
|
|
return lsc->getCurrentEditor() == NULL;
|
|
}
|
|
else
|
|
{
|
|
RBXASSERT(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ClientReplicator::isLegalSendEvent(Instance* instance, const Reflection::EventDescriptor& desc)
|
|
{
|
|
if (strictFilter && (strictFilter->filterEvent(instance, desc) == Reject))
|
|
{
|
|
if (settings().printDataFilters)
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_WARNING, "Filtering is enabled. Event %s for instance %s will not be replicated.", desc.name.c_str(), instance->getFullName().c_str());
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ClientReplicator::canSendItems()
|
|
{
|
|
return receivedGlobals;
|
|
}
|
|
|
|
bool ClientReplicator::isCloudEdit() const
|
|
{
|
|
if (const Client *client = Instance::fastDynamicCast<const Client>(getParent()))
|
|
{
|
|
return client->isCloudEdit();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class ClientReplicator::RequestCharacterItem : public Item
|
|
{
|
|
public:
|
|
RequestCharacterItem(Replicator* replicator):Item(*replicator)
|
|
{}
|
|
|
|
/*implement*/ bool write(RakNet::BitStream& bitStream)
|
|
{
|
|
Player* targetPlayer = replicator.findTargetPlayer();
|
|
|
|
// NOTE: We assume that by now the local player has a replication ID
|
|
if (!targetPlayer)
|
|
throw std::runtime_error("Attempting to send a Character request without a local Player");
|
|
|
|
writeItemType(bitStream, ItemTypeRequestCharacter);
|
|
|
|
// protocol version 12 -- using int for send stats instead of char
|
|
#if !defined(LOVE_ALL_ACCESS)
|
|
unsigned int sendStats = DataModel::sendStats |
|
|
DataModel::get(&replicator)->allHackFlagsOredTogether();
|
|
#else
|
|
unsigned int sendStats = 0;
|
|
#endif
|
|
|
|
bitStream << sendStats;
|
|
|
|
bitStream << TeleportService::GetSpawnName();
|
|
|
|
replicator.serializeId(bitStream, targetPlayer);
|
|
|
|
if (replicator.settings().printInstances)
|
|
{
|
|
RBX::Guid::Data id;
|
|
targetPlayer->getGuid().extract(id);
|
|
|
|
StandardOut::singleton()->printf(MESSAGE_SENSITIVE, "%s: Requesting character for %s",
|
|
RakNetAddressToString(replicator.remotePlayerId).c_str(),
|
|
id.readableString().c_str());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
RakNet::PluginReceiveResult ClientReplicator::OnReceive(RakNet::Packet *packet)
|
|
{
|
|
if (packet->systemAddress!=remotePlayerId)
|
|
return Replicator::OnReceive(packet);
|
|
|
|
switch ((unsigned char) packet->data[0])
|
|
{
|
|
case ID_CONNECTION_REQUEST_ACCEPTED:
|
|
{
|
|
sendDictionaries();
|
|
}
|
|
return RR_CONTINUE_PROCESSING;
|
|
|
|
case ID_DICTIONARY_FORMAT:
|
|
{
|
|
RakNet::BitStream bitStream(packet->data, packet->length, false);
|
|
bitStream.IgnoreBits(8); // Ignore the packet id
|
|
bitStream >> protocolSyncEnabled;
|
|
bitStream >> apiDictionaryCompression;
|
|
#ifdef NETWORK_DEBUG
|
|
StandardOut::singleton()->printf(MESSAGE_INFO, "Protocol sync enabled: %s", protocolSyncEnabled?"true":"false");
|
|
StandardOut::singleton()->printf(MESSAGE_INFO, "API dictionary compression enabled: %s", apiDictionaryCompression?"true":"false");
|
|
#endif
|
|
}
|
|
return RR_CONTINUE_PROCESSING;
|
|
|
|
case ID_PROTOCAL_MISMATCH:
|
|
{
|
|
std::string mismatchString = RBX::format("Protocol mismatch from %s", RakNetAddressToString(packet->systemAddress).c_str());
|
|
|
|
StandardOut::singleton()->print(MESSAGE_SENSITIVE, mismatchString.c_str());
|
|
// Also log this so we can see in output (primarily for iOS)
|
|
FASTLOGS(FLog::Network,"Protocol mismatch %s",mismatchString.c_str());
|
|
|
|
Client *client = getParent()->fastDynamicCast<Client>();
|
|
RBXASSERT(client);
|
|
client->connectionFailedSignal(RakNetAddressToString(packet->systemAddress), (int) packet->data[0], "Network protocol mismatch. Please upgrade.");
|
|
requestDisconnect(DisconnectReason_ProtocolMismatch);
|
|
return RR_CONTINUE_PROCESSING;
|
|
}
|
|
case ID_PLACEID_VERIFICATION:
|
|
{
|
|
RakNet::BitStream bitStream(packet->data, packet->length, false);
|
|
bitStream.IgnoreBits(8); // Ignore the packet id
|
|
bool retry;
|
|
bitStream >> retry;
|
|
if (retry)
|
|
{
|
|
// server is still verifying the placeID, request again
|
|
StandardOut::singleton()->printf(MESSAGE_INFO, "Waiting for server to authenticate the placeID before spawning...");
|
|
pendingItems.push_back(new RequestCharacterItem(this));
|
|
}
|
|
else
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_ERROR, "Place ID verification failed.");
|
|
Client *client = getParent()->fastDynamicCast<Client>();
|
|
RBXASSERT(client);
|
|
client->connectionFailedSignal(RakNetAddressToString(packet->systemAddress), (int) packet->data[0], "Illegal teleport destination.");
|
|
requestDisconnect(DisconnectReason_IllegalTeleport);
|
|
}
|
|
}
|
|
return RR_STOP_PROCESSING_AND_DEALLOCATE;
|
|
default:
|
|
return Replicator::OnReceive(packet);
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::processPacket(Packet *packet)
|
|
{
|
|
RBXPROFILER_SCOPE("Network", "processPacket");
|
|
RBXPROFILER_LABELF("Network", "ID %d (%d bytes)", packet->data[0], packet->length);
|
|
|
|
switch (packet->data[0])
|
|
{
|
|
case ID_SCHEMA_SYNC:
|
|
{
|
|
RakNet::BitStream inBitstream(packet->data, packet->length, false);
|
|
inBitstream.IgnoreBits(8); // Ignore the packet id
|
|
learnSchema(inBitstream);
|
|
}
|
|
break;
|
|
case ID_SET_GLOBALS:
|
|
{
|
|
RakNet::BitStream inBitstream(packet->data, packet->length, false);
|
|
|
|
inBitstream.IgnoreBits(8); // Ignore the packet id
|
|
|
|
{
|
|
bool distributedPhysicsEnabled;
|
|
inBitstream >> distributedPhysicsEnabled;
|
|
NetworkSettings::prop_DistributedPhysics.setValue(networkSettings, distributedPhysicsEnabled);
|
|
if (distributedPhysicsEnabled)
|
|
{
|
|
if(FFlag::RemoveUnusedPhysicsSenders)
|
|
{
|
|
physicsSender.reset(new RoundRobinPhysicsSender(*this));
|
|
PhysicsSender::start(physicsSender);
|
|
}
|
|
else
|
|
{
|
|
createPhysicsSender(NetworkSettings::RoundRobin); // Always sends round robin to the server
|
|
}
|
|
}
|
|
if(FFlag::RemoveInterpolationReciever)
|
|
{
|
|
physicsReceiver.reset(new DirectPhysicsReceiver(this, false));
|
|
physicsReceiver->start(physicsReceiver);
|
|
}
|
|
else
|
|
{
|
|
createPhysicsReceiver(NetworkSettings::Direct, false);
|
|
}
|
|
|
|
// create GC job if server is stream data
|
|
inBitstream >> streamingEnabled;
|
|
if (streamingEnabled)
|
|
{
|
|
gcJob.reset(new ClientReplicator::GCJob(*this));
|
|
TaskScheduler::singleton().add(gcJob);
|
|
}
|
|
|
|
bool networkFilterEnabled;
|
|
inBitstream >> networkFilterEnabled;
|
|
|
|
if (Workspace* workspace = ServiceProvider::find<Workspace>(this))
|
|
{
|
|
workspace->setNetworkFilteringEnabled(networkFilterEnabled);
|
|
if (canUseProtocolVersion(32))
|
|
{
|
|
bool allowThirdPartySales;
|
|
inBitstream >> allowThirdPartySales;
|
|
workspace->setAllowThirdPartySales(allowThirdPartySales);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RBXASSERT(false);
|
|
}
|
|
|
|
if (networkFilterEnabled)
|
|
strictFilter.reset(new StrictNetworkFilter(this));
|
|
|
|
bool characterAutoLoad;
|
|
inBitstream >> characterAutoLoad;
|
|
if (players)
|
|
{
|
|
players->setCharacterAutoSpawnProperty(characterAutoLoad);
|
|
}
|
|
}
|
|
|
|
std::string scopeName;
|
|
inBitstream >> scopeName;
|
|
this->serverScope.set(scopeName);
|
|
|
|
if (LuaVM::useSecureReplication() || FFlag::DebugLocalRccServerConnection)
|
|
{
|
|
unsigned int scriptKey;
|
|
inBitstream >> scriptKey;
|
|
|
|
unsigned int coreScriptModKey;
|
|
inBitstream >> coreScriptModKey;
|
|
|
|
if (ScriptContext* scriptContext = ServiceProvider::find<ScriptContext>(this))
|
|
{
|
|
unsigned int xorKey = boost::hash_value(DataModel::get(this)->getPlaceID());
|
|
|
|
scriptContext->setKeys(scriptKey ^ xorKey, coreScriptModKey ^ xorKey);
|
|
}
|
|
else
|
|
RBXASSERT(false);
|
|
}
|
|
|
|
uint8_t numTopRepContainers;
|
|
inBitstream >> numTopRepContainers;
|
|
|
|
for (unsigned int i = 0; i < numTopRepContainers; i ++)
|
|
{
|
|
const Reflection::ClassDescriptor* classDescriptor;
|
|
classDictionary.receive(inBitstream, classDescriptor, false /*we don't do version check for class here, if the properties or events of the class are changed, they will be handled later*/);
|
|
|
|
RBX::Guid::Data id;
|
|
deserializeId(inBitstream, id);
|
|
|
|
TopReplContsMap::iterator iter = topReplicationContainersMap.find(classDescriptor);
|
|
if (iter != topReplicationContainersMap.end())
|
|
{
|
|
Instance* inst = *(iter->second);
|
|
guidRegistry->assignGuid(inst, id);
|
|
resolvePendingReferences(inst, id);
|
|
|
|
topReplicationContainers.erase(iter->second);
|
|
}
|
|
}
|
|
|
|
// disconnect replication data for left over top containers, server does not have these
|
|
for (TopReplConts::iterator iter = topReplicationContainers.begin(); iter != topReplicationContainers.end(); iter++)
|
|
{
|
|
disconnectReplicationData(shared_from(*iter));
|
|
}
|
|
|
|
// no longer need top replication containers list
|
|
topReplicationContainers.clear();
|
|
topReplicationContainersMap.clear();
|
|
|
|
receivedGlobals = true;
|
|
receivedGlobalsSignal();
|
|
|
|
enableDeserializePacketThread();
|
|
}
|
|
break;
|
|
default:
|
|
Super::processPacket(packet);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::receiveCluster(RakNet::BitStream& inBitstream, Instance* instance, bool usingOneQuarterIterator)
|
|
{
|
|
if (streamingEnabled) {
|
|
FASTLOG(DFLog::PartStreamingRequests, "Received 1 terrain region.");
|
|
++numInstancesRead; // safe to do here because cluster data is always sent before parts data
|
|
}
|
|
Super::receiveCluster(inBitstream, instance, usingOneQuarterIterator);
|
|
}
|
|
|
|
void ClientReplicator::terrainCellChanged(const Voxel::CellChangeInfo& info)
|
|
{
|
|
if (!clusterDebounce && strictFilter && strictFilter->filterTerrainCellChange() == Reject)
|
|
{
|
|
if (settings().printDataFilters)
|
|
StandardOut::singleton()->printf(MESSAGE_WARNING, "Filtering is enabled, terrain cell change will not be replicated.");
|
|
|
|
return;
|
|
}
|
|
|
|
Super::terrainCellChanged(info);
|
|
}
|
|
|
|
void ClientReplicator::onTerrainRegionChanged(const Voxel2::Region& region)
|
|
{
|
|
if (!clusterDebounce && strictFilter && strictFilter->filterTerrainCellChange() == Reject)
|
|
{
|
|
if (settings().printDataFilters)
|
|
StandardOut::singleton()->printf(MESSAGE_WARNING, "Filtering is enabled, terrain cell change will not be replicated.");
|
|
|
|
return;
|
|
}
|
|
|
|
Super::onTerrainRegionChanged(region);
|
|
}
|
|
|
|
bool ClientReplicator::wantReplicate(const Instance* source) const
|
|
{
|
|
if (strictFilter)
|
|
{
|
|
if (Super::wantReplicate(source))
|
|
{
|
|
Player* player = findTargetPlayer();
|
|
PlayerGui* playerGui = player->findFirstChildOfType<PlayerGui>();
|
|
|
|
// don't replicate objects under PlayerGui
|
|
return !source->isDescendantOf(playerGui);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return Super::wantReplicate(source);
|
|
}
|
|
|
|
void ClientReplicator::postProcessPacket()
|
|
{
|
|
if (streamingEnabled) {
|
|
updateClientCapacity();
|
|
}
|
|
}
|
|
|
|
shared_ptr<DeserializedItem> ClientReplicator::deserializeItem(RakNet::BitStream& inBitstream, RBX::Network::Item::ItemType itemType)
|
|
{
|
|
RBXPROFILER_SCOPE("Network", "deserializeItem");
|
|
|
|
shared_ptr<DeserializedItem> item = shared_ptr<DeserializedItem>();
|
|
|
|
switch (itemType)
|
|
{
|
|
default:
|
|
item = Super::deserializeItem(inBitstream, itemType);
|
|
break;
|
|
case Item::ItemTypeStreamData:
|
|
NETPROFILE_START("readStreamData", &inBitstream);
|
|
item = StreamJob::StreamDataItem::read(*this, inBitstream);
|
|
NETPROFILE_END("readStreamData", &inBitstream);
|
|
break;
|
|
case Item::ItemTypeTag:
|
|
NETPROFILE_START("readTag", &inBitstream);
|
|
item = TagItem::read(*this, inBitstream);
|
|
NETPROFILE_END("readTag", &inBitstream);
|
|
break;
|
|
case Item::ItemTypeStats:
|
|
NETPROFILE_START("readStats", &inBitstream);
|
|
item = StatsItem::read(*this, inBitstream);
|
|
NETPROFILE_END("readStats", &inBitstream);
|
|
break;
|
|
case Item::ItemTypeRocky:
|
|
NETPROFILE_START("readBonus", &inBitstream);
|
|
item = RockyItem::read(*this, inBitstream);
|
|
NETPROFILE_END("readBonus", &inBitstream);
|
|
break;
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
void ClientReplicator::readItem(RakNet::BitStream& inBitstream, RBX::Network::Item::ItemType itemType)
|
|
{
|
|
switch (itemType)
|
|
{
|
|
default:
|
|
Super::readItem(inBitstream, itemType);
|
|
break;
|
|
case Item::ItemTypeStreamData:
|
|
NETPROFILE_START("readStreamData", &inBitstream);
|
|
readStreamData(inBitstream);
|
|
NETPROFILE_END("readStreamData", &inBitstream);
|
|
break;
|
|
case Item::ItemTypeTag:
|
|
NETPROFILE_START("readTag", &inBitstream);
|
|
readTag(inBitstream);
|
|
NETPROFILE_END("readTag", &inBitstream);
|
|
break;
|
|
case Item::ItemTypeStats:
|
|
NETPROFILE_START("readStats", &inBitstream);
|
|
statsReceivedSignal(readStats(inBitstream));
|
|
NETPROFILE_END("readStats", &inBitstream);
|
|
break;
|
|
case Item::ItemTypeRocky:
|
|
NETPROFILE_START("readBonus", &inBitstream);
|
|
processRockyItem(inBitstream);
|
|
NETPROFILE_END("readBonus", &inBitstream);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::processTag(int tag)
|
|
{
|
|
// Alert the ReplicatedFirst service it has received all its descendants
|
|
// This means the service can start running local scripts
|
|
if (tag == REPLICATED_FIRST_FINISHED_TAG)
|
|
{
|
|
if (ReplicatedFirst* repFirst = RBX::ServiceProvider::find<ReplicatedFirst>(this) )
|
|
{
|
|
repFirst->setAllInstancesHaveReplicated();
|
|
}
|
|
}
|
|
else if (tag == TOP_REPLICATION_CONTAINER_FINISHED_TAG)
|
|
{
|
|
// we signal game loading to the client/lua here... we should formalize these client start up functions and move this with it at some point
|
|
if(DataModel* dataModel = DataModel::get(this))
|
|
{
|
|
dataModel->gameLoaded();
|
|
}
|
|
|
|
processAllPacketsPerStep = false;
|
|
|
|
gameLoadedSignal();
|
|
|
|
// reset ping timers
|
|
canTimeout = true;
|
|
replicatorStats.lastReceivedPingTime = RakNet::GetTimeMS();
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::readTag(RakNet::BitStream& inBitstream)
|
|
{
|
|
int id;
|
|
inBitstream >> id;
|
|
if (settings().printInstances) {
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_SENSITIVE,
|
|
"Received tag %d from %s", id,
|
|
RakNetAddressToString(remotePlayerId).c_str());
|
|
}
|
|
|
|
processTag(id);
|
|
}
|
|
|
|
void ClientReplicator::readTagItem(DeserializedTagItem* item)
|
|
{
|
|
processTag(item->id);
|
|
}
|
|
|
|
shared_ptr<Reflection::ValueTable> ClientReplicator::readStats(RakNet::BitStream& inBitstream)
|
|
{
|
|
shared_ptr<Reflection::ValueTable> jobs(new Reflection::ValueTable());
|
|
shared_ptr<Reflection::ValueTable> scripts(new Reflection::ValueTable());
|
|
|
|
|
|
// version 2
|
|
// read job info
|
|
bool end;
|
|
inBitstream >> end;
|
|
while (!end)
|
|
{
|
|
std::string name;
|
|
inBitstream >> name;
|
|
|
|
float dutyCycle, stepsPerSec, stepTime;
|
|
inBitstream >> dutyCycle;
|
|
inBitstream >> stepsPerSec;
|
|
inBitstream >> stepTime;
|
|
|
|
shared_ptr<Reflection::ValueArray> jobInfo(new Reflection::ValueArray());
|
|
jobInfo->push_back(dutyCycle);
|
|
jobInfo->push_back(stepsPerSec);
|
|
jobInfo->push_back(stepTime);
|
|
|
|
jobs->insert(std::make_pair(name, shared_ptr<const Reflection::ValueArray>(jobInfo)));
|
|
|
|
inBitstream >> end;
|
|
}
|
|
|
|
// read script info
|
|
inBitstream >> end;
|
|
while (!end)
|
|
{
|
|
std::string name;
|
|
inBitstream >> name;
|
|
float activity;
|
|
inBitstream >> activity;
|
|
int invocationCount;
|
|
inBitstream >> invocationCount;
|
|
|
|
shared_ptr<Reflection::ValueArray> scriptInfo(new Reflection::ValueArray());
|
|
scriptInfo->push_back(activity);
|
|
scriptInfo->push_back(invocationCount);
|
|
|
|
scripts->insert(std::make_pair(name, shared_ptr<const Reflection::ValueArray>(scriptInfo)));
|
|
|
|
inBitstream >> end;
|
|
}
|
|
|
|
// version 1
|
|
float avgPing;
|
|
inBitstream >> avgPing;
|
|
|
|
float avgPhysicsSenderFPS;
|
|
inBitstream >> avgPhysicsSenderFPS;
|
|
|
|
float totalDataKbps;
|
|
inBitstream >> totalDataKbps;
|
|
|
|
float totalPhysicsKbps;
|
|
inBitstream >> totalPhysicsKbps;
|
|
|
|
float dataThroughput;
|
|
inBitstream >> dataThroughput;
|
|
|
|
shared_ptr<Reflection::ValueTable> stats(new Reflection::ValueTable());
|
|
stats->insert(std::make_pair("Avg Ping ms", avgPing));
|
|
stats->insert(std::make_pair("Avg Physics Sender Pkt/s", avgPhysicsSenderFPS));
|
|
stats->insert(std::make_pair("Total Data KB/s", totalDataKbps));
|
|
stats->insert(std::make_pair("Total Physics KB/s", totalPhysicsKbps));
|
|
stats->insert(std::make_pair("Data Throughput ratio", dataThroughput));
|
|
|
|
stats->insert(std::make_pair("Jobs", shared_ptr<const Reflection::ValueTable>(jobs)));
|
|
stats->insert(std::make_pair("Scripts", shared_ptr<const Reflection::ValueTable>(scripts)));
|
|
|
|
return stats;
|
|
}
|
|
|
|
void ClientReplicator::readRockyItem(RakNet::BitStream& inBitstream, uint8_t& idx, RBX::Security::NetPmcChallenge& key)
|
|
{
|
|
uint8_t subtype;
|
|
inBitstream >> subtype;
|
|
inBitstream >> idx;
|
|
inBitstream >> key.base;
|
|
inBitstream >> key.size;
|
|
inBitstream >> key.seed;
|
|
inBitstream >> key.result;
|
|
}
|
|
|
|
void ClientReplicator::doNetPmcCheck(shared_ptr<ClientReplicator> rep, uint8_t idx, RBX::Security::NetPmcChallenge challenge)
|
|
{
|
|
#if defined(_WIN32) && !defined(RBX_STUDIO_BUILD)
|
|
challenge ^= *const_cast<const RBX::Security::NetPmcChallenge*>(&RBX::Security::kChallenges[idx]);
|
|
uint32_t result = netPmcHashCheck(challenge);
|
|
rep->pendingItems.push_back(new Replicator::NetPmcResponseItem(rep.get(), result, challenge.result, idx));
|
|
#endif
|
|
}
|
|
|
|
void ClientReplicator::processRockyItem(RakNet::BitStream& inBitstream)
|
|
{
|
|
uint8_t idx;
|
|
RBX::Security::NetPmcChallenge challenge;
|
|
readRockyItem(inBitstream, idx, challenge);
|
|
DataModel::get(this)->submitTask(
|
|
boost::bind(&ClientReplicator::doNetPmcCheck, shared_from(this), idx, challenge), DataModelJob::Write);
|
|
}
|
|
|
|
|
|
void ClientReplicator::markerReceived()
|
|
{
|
|
}
|
|
|
|
//shared_ptr<Instance> ClientReplicator::sendMarker()
|
|
//{
|
|
// shared_ptr<Instance> superMarker = Super::sendMarker();
|
|
//
|
|
// if (Marker* marker = Instance::fastDynamicCast<Marker>(superMarker.get()))
|
|
// {
|
|
// marker->receivedSignal.connect(boost::bind(&ClientReplicator::markerReceived, this));
|
|
// }
|
|
//
|
|
// return superMarker;
|
|
//}
|
|
|
|
void ClientReplicator::processStreamDataRegionId(Replicator::StreamJob::RegionIteratorSuccessor successorBitMask, StreamRegion::Id id)
|
|
{
|
|
if (successorBitMask == Replicator::StreamJob::ITER_INCX)
|
|
{
|
|
id = lastReadStreamId + Vector3int32(1,0,0);
|
|
}
|
|
else if (successorBitMask == Replicator::StreamJob::ITER_INCY)
|
|
{
|
|
id = lastReadStreamId + Vector3int32(0,1,0);
|
|
}
|
|
else if (successorBitMask == Replicator::StreamJob::ITER_INCZ)
|
|
{
|
|
id = lastReadStreamId + Vector3int32(0,0,1);
|
|
}
|
|
else if (successorBitMask != Replicator::StreamJob::ITER_NONE)
|
|
{
|
|
RBXASSERT(false);
|
|
}
|
|
|
|
lastReadStreamId = id;
|
|
|
|
if (gcJob)
|
|
gcJob->insertRegion(id);
|
|
}
|
|
|
|
void ClientReplicator::readStreamDataItem(DeserializedStreamDataItem* item)
|
|
{
|
|
Timer<Time::Precise> timer;
|
|
|
|
processStreamDataRegionId((Replicator::StreamJob::RegionIteratorSuccessor)item->successorBitMask, item->id);
|
|
|
|
if (item->deserializedJoinDataItem)
|
|
{
|
|
int numRead = readJoinDataItem(item->deserializedJoinDataItem.get());
|
|
numInstancesRead += numRead;
|
|
|
|
if (numRead > 0)
|
|
{
|
|
avgInstancesPerStreamData.sample(numRead);
|
|
avgStreamDataReadTime.sample(timer.delta().seconds());
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::readStreamData(RakNet::BitStream& bitStream)
|
|
{
|
|
NETPROFILE_START("readStreamDataHeader", &bitStream);
|
|
Timer<Time::Precise> timer;
|
|
// stream data item is just join data plus an extra StreamRegion::Id
|
|
StreamRegion::Id id;
|
|
|
|
bool lowBit, highBit;
|
|
bitStream >> lowBit;
|
|
bitStream >> highBit;
|
|
Replicator::StreamJob::RegionIteratorSuccessor successorBitMask = (Replicator::StreamJob::RegionIteratorSuccessor)((int)lowBit + (((int)highBit)<<1));
|
|
|
|
if (successorBitMask == Replicator::StreamJob::ITER_NONE)
|
|
{
|
|
bitStream >> id;
|
|
}
|
|
|
|
NETPROFILE_END("readStreamDataHeader", &bitStream);
|
|
|
|
processStreamDataRegionId(successorBitMask, id);
|
|
|
|
NETPROFILE_START("readJoinData", &bitStream);
|
|
int numRead = readJoinData(bitStream);
|
|
NETPROFILE_END("readJoinData", &bitStream);
|
|
numInstancesRead += numRead;
|
|
|
|
if (numRead > 0)
|
|
{
|
|
avgInstancesPerStreamData.sample(numRead);
|
|
avgStreamDataReadTime.sample(timer.delta().seconds());
|
|
}
|
|
|
|
FASTLOG2(DFLog::PartStreamingRequests, "Received %d instances, %d pending requests remaining",
|
|
numRead,
|
|
pendingInstanceRequests - numInstancesRead);
|
|
}
|
|
|
|
bool ClientReplicator::checkDistributedReceive(PartInstance* part)
|
|
{
|
|
bool clientIsOwner = (part->getNetworkOwner() == RakNetToRbxAddress(clientAddress));
|
|
return !clientIsOwner;
|
|
}
|
|
|
|
bool ClientReplicator::checkDistributedSend(const PartInstance* part)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// assumes part is root
|
|
bool ClientReplicator::checkDistributedSendFast(const PartInstance* part)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
class ClientReplicator::ClientCapacityUpdateItem : public Item
|
|
{
|
|
int numInstanceDiff;
|
|
short maxRegionRadius;
|
|
|
|
public:
|
|
ClientCapacityUpdateItem(Replicator* replicator, int _numInstanceDiff, short _maxRegionRadius):Item(*replicator), numInstanceDiff(_numInstanceDiff), maxRegionRadius(_maxRegionRadius)
|
|
{}
|
|
|
|
/*implement*/ bool write(RakNet::BitStream& bitStream)
|
|
{
|
|
writeItemType(bitStream, ItemTypeUpdateClientQuota);
|
|
bitStream << numInstanceDiff;
|
|
bitStream << maxRegionRadius;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
void ClientReplicator::requestCharacter()
|
|
{
|
|
requestCharacterImpl();
|
|
}
|
|
|
|
void ClientReplicator::requestCharacterImpl()
|
|
{
|
|
pendingItems.push_back(new RequestCharacterItem(this));
|
|
|
|
// we signal game loading to the client/lua here... we should formalize these client start up functions and move this with it at some point
|
|
if(DataModel* dataModel = DataModel::get(this))
|
|
{
|
|
dataModel->gameLoaded();
|
|
}
|
|
|
|
if (Player* targetPlayer = findTargetPlayer())
|
|
playerCharacterAddedConnection = targetPlayer->characterAddedSignal.connect(boost::bind(&ClientReplicator::onPlayerCharacterAdded, this));
|
|
else
|
|
{
|
|
RBXASSERT(false);
|
|
processAllPacketsPerStep = false;
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::requestServerStats(bool request)
|
|
{
|
|
RakNet::BitStream bitStream;
|
|
bitStream << (unsigned char) ID_REQUEST_STATS;
|
|
bitStream << request;
|
|
if (request)
|
|
{
|
|
bitStream << STATS_ITEM_VERSION;
|
|
}
|
|
|
|
rakPeer->rawPeer()->Send(&bitStream, networkSettings->getDataSendPriority(), DATAMODEL_RELIABILITY, DATA_CHANNEL, remotePlayerId, false);
|
|
}
|
|
|
|
bool ClientReplicator::hasEnoughMemoryToReceiveInstances()
|
|
{
|
|
return (!gcJob->pendingGC()) && memoryLevel >= RBX::MemoryStats::MEMORYLEVEL_OK;
|
|
}
|
|
|
|
bool ClientReplicator::needGC()
|
|
{
|
|
return gcJob->pendingGC() || memoryLevel <= RBX::MemoryStats::MEMORYLEVEL_ALL_LOW;
|
|
}
|
|
|
|
bool ClientReplicator::canUpdateClientCapacity()
|
|
{
|
|
double nowTime = Time::nowFastSec();
|
|
if (nowTime - sampleTimer > kMaxIncomePacketWaitTime/2)
|
|
{
|
|
sampleTimer = nowTime;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::updateMemoryStats()
|
|
{
|
|
memoryLevel = RBX::MemoryStats::slowCheckMemoryLevel(((RBX::MemoryStats::memsize_t)NetworkSettings::singleton().getExtraMemoryUsedInMB())*1024*1024);
|
|
if (memoryLevel == RBX::MemoryStats::MEMORYLEVEL_ONLY_PHYSICAL_CRITICAL_LOW)
|
|
{
|
|
// if the physical memory level is critical while pool memory is ample, release all the pool memory
|
|
MemoryStats::releaseAllPoolMemory();
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::updateClientCapacity()
|
|
{
|
|
if (canUpdateClientCapacity())
|
|
{
|
|
const bool haveEnoughMemory = hasEnoughMemoryToReceiveInstances();
|
|
|
|
if (!haveEnoughMemory) {
|
|
if (!loggedLowMemWarning) {
|
|
FASTLOG2(DFLog::PartStreamingRequests, "Not enough memory to request more parts: %u free, %u additional required",
|
|
MemoryStats::freeMemoryBytes(), (FInt::StreamingCriticalLowMemWatermarkMB*1024*1024) - MemoryStats::freeMemoryBytes());
|
|
loggedLowMemWarning = true;
|
|
}
|
|
} else {
|
|
loggedLowMemWarning = false;
|
|
}
|
|
|
|
int lastClientInstanceQuota = clientInstanceQuota;
|
|
float predictedTotalInstanceProcessTime = 0.0f;
|
|
if (!haveEnoughMemory)
|
|
{
|
|
clientInstanceQuota = 0; // this will clear the existing server pending queue
|
|
}
|
|
else
|
|
{
|
|
if (!avgStreamDataReadTime.hasSampled())
|
|
{
|
|
// no sample yet, use initial value
|
|
clientInstanceQuota = DFInt::ClientInstanceQuotaInitial;
|
|
}
|
|
else
|
|
{
|
|
float avgInstanceProcessTime = avgStreamDataReadTime.value() / avgInstancesPerStreamData.value();
|
|
predictedTotalInstanceProcessTime = avgInstanceProcessTime * avgInstancesPerStreamData.value() * incomingPacketsCount();
|
|
// evaluate the client capacity
|
|
clientInstanceQuota = (RBX::Network::kMaxIncomePacketWaitTime - predictedTotalInstanceProcessTime) / avgInstanceProcessTime;
|
|
if (clientInstanceQuota < 1)
|
|
{
|
|
clientInstanceQuota = 1; // this will not clear the existing server pending queue
|
|
}
|
|
else if (clientInstanceQuota > DFInt::ClientInstanceQuotaCap)
|
|
{
|
|
clientInstanceQuota = DFInt::ClientInstanceQuotaCap;
|
|
}
|
|
}
|
|
}
|
|
bool maxRegionDistanceChanged = gcJob->updateMaxRegionDistance();
|
|
int instanceQuotaDiff = clientInstanceQuota - lastClientInstanceQuota;
|
|
if (instanceQuotaDiff != 0 || maxRegionDistanceChanged)
|
|
{
|
|
if (settings().printStreamInstanceQuota)
|
|
{
|
|
StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "clientInstanceQuota %d, packet in queue %d, predictedTotalInstanceProcessTime %f, avgStreamDataReadTime %f, avgInstancesPerStreamData %f", clientInstanceQuota, (int)incomingPacketsCount(), predictedTotalInstanceProcessTime, avgStreamDataReadTime.value(), avgInstancesPerStreamData.value());
|
|
}
|
|
pendingItems.push_back(new ClientCapacityUpdateItem(this, instanceQuotaDiff, gcJob->getMaxRegionDistance()));
|
|
}
|
|
numInstancesRead = 0;
|
|
avgRequestCount.sample(clientInstanceQuota);
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::onPlayerCharacterAdded()
|
|
{
|
|
// we are only interested in first time this happen to signify the player has successfully joined the game
|
|
playerCharacterAddedConnection.disconnect();
|
|
|
|
processAllPacketsPerStep = false;
|
|
}
|
|
|
|
void ClientReplicator::dataOutStep()
|
|
{
|
|
propSync.expireItems();
|
|
Super::dataOutStep();
|
|
}
|
|
|
|
bool ClientReplicator::processChangedParentPropertyForStreaming(const Guid::Data& parentId, Reflection::Property prop)
|
|
{
|
|
RBXASSERT(streamingEnabled && prop.getDescriptor() == Instance::propParent);
|
|
|
|
Instance* instance = static_cast<Instance*>(prop.getInstance());
|
|
shared_ptr<Instance> parent;
|
|
bool recognizedId = guidRegistry->lookupByGuid(parentId, parent);
|
|
|
|
// ships in the night:
|
|
// client sent a GC notice for Part A
|
|
// simultaneously server sent a reparent message setting parent to Part A
|
|
// resolution: GC reparented instance (and its descendants)
|
|
RBXASSERT(gcJob);
|
|
if (gcJob && instance && !recognizedId)
|
|
{
|
|
// TODO: add an ACK mechanism to GC instance list, and only allow unknown
|
|
// parents to be interpreted as ships-in-the-night with gc if we know the
|
|
// parent in question has a GC notice in flight
|
|
gcJob->notifyServerGcingInstanceAndDescendants(shared_from(instance));
|
|
streamOutInstance(instance, true);
|
|
|
|
// done processing this property change
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ClientReplicator::readChangedProperty(RakNet::BitStream& bitStream, Reflection::Property prop)
|
|
{
|
|
// This is the mirror image of ServerReplicator::writeChangedProperty
|
|
// also, if you make changes, please also take care of ClientReplicator::skipChangedProperty
|
|
bool versionReset;
|
|
bitStream >> versionReset;
|
|
propSync.onReceivedPropertyChanged(prop, versionReset);
|
|
|
|
// if we are streaming, check to see if we are setting parent to an unknown guid
|
|
if (streamingEnabled && prop.getDescriptor() == Instance::propParent)
|
|
{
|
|
// Remember the read offset before we read out the id from the stream.
|
|
RakNet::BitSize_t currentReadOffset = bitStream.GetReadOffset();
|
|
|
|
RBX::Guid::Data parentId;
|
|
deserializeId(bitStream, parentId);
|
|
|
|
if (processChangedParentPropertyForStreaming(parentId, prop))
|
|
return;
|
|
else
|
|
{
|
|
// undo reading the property value, and let the normal property update path continue.
|
|
bitStream.SetReadOffset(currentReadOffset);
|
|
}
|
|
}
|
|
|
|
if (prop.getDescriptor() == PartInstance::prop_CFrame)
|
|
{
|
|
// special-case CFrames. Send acknowledgement right away so that physics data won't get filtered
|
|
const PartInstance* p = boost::polymorphic_downcast<const PartInstance*>(prop.getInstance());
|
|
if (p)
|
|
pendingItems.push_back(new (cframePool.get()) CFrameAcknowledgementItem(this, shared_from(p)));
|
|
}
|
|
|
|
Super::readChangedProperty(bitStream, prop);
|
|
}
|
|
|
|
void ClientReplicator::readChangedPropertyItem(DeserializedChangePropertyItem* item, Reflection::Property prop)
|
|
{
|
|
propSync.onReceivedPropertyChanged(prop, item->versionReset);
|
|
|
|
if (streamingEnabled && prop.getDescriptor() == Instance::propParent)
|
|
{
|
|
Guid::Data parentId = item->value.get<Guid::Data>();
|
|
if (processChangedParentPropertyForStreaming(parentId, prop))
|
|
return;
|
|
}
|
|
else if (prop.getDescriptor() == PartInstance::prop_CFrame)
|
|
{
|
|
// special-case CFrames. Send acknowledgement right away so that physics data won't get filtered
|
|
const PartInstance* p = boost::polymorphic_downcast<const PartInstance*>(prop.getInstance());
|
|
if (p)
|
|
pendingItems.push_back(new (cframePool.get()) CFrameAcknowledgementItem(this, shared_from(p)));
|
|
}
|
|
|
|
Super::readChangedPropertyItem(item, prop);
|
|
|
|
}
|
|
|
|
void ClientReplicator::writePropAcknowledgementIfNeeded(const Instance* instance, const Reflection::PropertyDescriptor& desc, RakNet::BitStream& outBitStream)
|
|
{
|
|
DescriptorSender<RBX::Reflection::PropertyDescriptor>::IdContainer idContainer = propDictionary.getId(&desc);
|
|
if (idContainer.outdated)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int version;
|
|
if (propSync.onPropertySend(Reflection::ConstProperty(desc, instance), version) == PropSync::Slave::DontSendAcknowledgement)
|
|
return;
|
|
|
|
Item::writeItemType(outBitStream, Item::ItemTypePropAcknowledgement);
|
|
outBitStream << version;
|
|
propDictionary.send(outBitStream, idContainer.id);
|
|
serializeId(outBitStream, instance);
|
|
}
|
|
|
|
bool ClientReplicator::isLimitedByOutgoingBandwidthLimit() const {
|
|
if (const RakNet::RakNetStatistics* rakStats = getRakNetStats()) {
|
|
return rakStats->isLimitedByOutgoingBandwidthLimit;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ClientReplicator::writeChangedProperty(const Instance* instance, const Reflection::PropertyDescriptor& desc, RakNet::BitStream& outBitStream)
|
|
{
|
|
// Write the ItemTypePropAcknowledgement message before sending the property
|
|
writePropAcknowledgementIfNeeded(instance, desc, outBitStream);
|
|
|
|
Super::writeChangedProperty(instance, desc, outBitStream);
|
|
}
|
|
|
|
void ClientReplicator::writeChangedRefProperty(const Instance* instance,
|
|
const Reflection::RefPropertyDescriptor& desc, const Guid::Data& newRefGuid,
|
|
RakNet::BitStream& outBitStream)
|
|
{
|
|
// Write the ItemTypePropAcknowledgement message before sending the property
|
|
writePropAcknowledgementIfNeeded(instance, desc, outBitStream);
|
|
|
|
Super::writeChangedRefProperty(instance, desc, newRefGuid, outBitStream);
|
|
}
|
|
|
|
FilterResult ClientReplicator::filterChangedProperty(Instance* instance, const Reflection::PropertyDescriptor& desc)
|
|
{
|
|
FilterResult result = Super::filterChangedProperty(instance, desc);
|
|
if (streamingEnabled && result == Accept)
|
|
{
|
|
if (Humanoid* humanoid = instance->fastDynamicCast<Humanoid>())
|
|
{
|
|
if (!humanoid->getTorsoSlow())
|
|
{
|
|
return Reject;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
FilterResult ClientReplicator::filterReceivedParent(Instance* instance, Instance* parent)
|
|
{
|
|
FilterResult result = Super::filterReceivedParent(instance, parent);
|
|
if (result == Accept)
|
|
{
|
|
// Can't set if parent is locked
|
|
if (instance->getIsParentLocked())
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_WARNING, "trying to set locked parent!");
|
|
return Reject;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void ClientReplicator::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider)
|
|
{
|
|
#if !defined(RBX_STUDIO_BUILD) && !defined(RBX_PLATFORM_DURANGO)
|
|
#ifdef _WIN32
|
|
TaskScheduler::singleton().remove(memoryCheckerCheckerJob);
|
|
memoryCheckerCheckerJob.reset();
|
|
#endif
|
|
|
|
#if defined(_WIN32) || (defined(__APPLE__) && !defined(RBX_PLATFORM_IOS))
|
|
TaskScheduler::singleton().remove(memoryCheckerJob);
|
|
memoryCheckerJob.reset();
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
TaskScheduler::singleton().remove(badAppCheckerJob);
|
|
badAppCheckerJob.reset();
|
|
#endif
|
|
#endif
|
|
|
|
if (gcJob)
|
|
{
|
|
TaskScheduler::singleton().remove(gcJob);
|
|
gcJob->unregisterCoarseMovementCallback();
|
|
gcJob.reset();
|
|
}
|
|
|
|
Super::onServiceProvider(oldProvider, newProvider);
|
|
|
|
hashReadyConnection.disconnect();
|
|
mccReadyConnection.disconnect();
|
|
|
|
if (newProvider)
|
|
{
|
|
#if !defined(RBX_STUDIO_BUILD)
|
|
VMProtectBeginMutation("30");
|
|
#if !defined(LOVE_ALL_ACCESS) && defined(_WIN32) && !defined(RBX_PLATFORM_DURANGO)
|
|
badAppCheckerJob.reset(new BadAppCheckerJob(shared_from(this)));
|
|
TaskScheduler::singleton().add(badAppCheckerJob);
|
|
#endif
|
|
|
|
#if !defined(LOVE_ALL_ACCESS) && (defined(_WIN32) || (defined(__APPLE__) && !defined(RBX_PLATFORM_IOS))) && !defined(RBX_PLATFORM_DURANGO)
|
|
memoryCheckerJob.reset(new MemoryCheckerJob(shared_from(this)));
|
|
TaskScheduler::singleton().add(memoryCheckerJob);
|
|
#endif
|
|
|
|
#if !defined(LOVE_ALL_ACCESS) && defined(_WIN32) && !defined(RBX_PLATFORM_DURANGO)
|
|
memoryCheckerCheckerJob.reset(new MemoryCheckerCheckerJob(shared_from(this)));
|
|
TaskScheduler::singleton().add(memoryCheckerCheckerJob);
|
|
hashReadyConnection = memoryCheckerJob->hashReadySignal.connect(boost::bind(&ClientReplicator::onHashReady, this));
|
|
mccReadyConnection = memoryCheckerCheckerJob->reportReadySignal.connect(boost::bind(&ClientReplicator::onMccReady, this));
|
|
#endif
|
|
VMProtectEnd();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::onHashReady()
|
|
{
|
|
unsigned long long thisTag = Tokens::apiToken.crypt();
|
|
unsigned long long prevTag = Tokens::apiToken.getPrev();
|
|
pendingItems.push_back(new HashItem(this, &pmcHash, Tokens::sendStatsToken.crypt(), thisTag, prevTag));
|
|
}
|
|
|
|
void ClientReplicator::onMccReady()
|
|
{
|
|
pendingItems.push_back(new RockyItem(this, ::mccReport));
|
|
}
|
|
|
|
bool ClientReplicator::canUseProtocolVersion(int protocolVersion) const {
|
|
return protocolVersion <= NETWORK_PROTOCOL_VERSION;
|
|
}
|
|
|
|
void ClientReplicator::deserializeSFFlags(RakNet::BitStream& inBitStream)
|
|
{
|
|
unsigned short numOfSFFlags;
|
|
inBitStream.Read(numOfSFFlags);
|
|
while (numOfSFFlags--)
|
|
{
|
|
RakNet::RakString name;
|
|
RakNet::RakString varValue;
|
|
inBitStream.Read(name);
|
|
inBitStream.Read(varValue);
|
|
//RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO,
|
|
// "Received FFLag: %s: %s", name.C_String(), varValue.C_String());
|
|
std::string stdName(name.C_String());
|
|
std::string stdValue(varValue.C_String());
|
|
FLog::SetValueFromServer(stdName, stdValue);
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::streamOutPartHelper(const Guid::Data& data,
|
|
PartInstance* part, shared_ptr<Instance> descendant) {
|
|
|
|
if (shared_ptr<JointInstance> jointInstance =
|
|
Instance::fastSharedDynamicCast<JointInstance>(descendant)) {
|
|
|
|
const Reflection::RefPropertyDescriptor* refPropDescriptor = NULL;
|
|
if (jointInstance->getPart0() == part) {
|
|
refPropDescriptor = &JointInstance::prop_Part0;
|
|
} else if (jointInstance->getPart1() == part) {
|
|
refPropDescriptor = &JointInstance::prop_Part1;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
// HACK: re-use the "removingInstance" functionality to suppress
|
|
// replication of events and property changes while we set the joint's
|
|
// part reference (to the streamed out part) to NULL.
|
|
{
|
|
RBX::ScopedAssign<Instance*> assign(removingInstance, jointInstance.get());
|
|
refPropDescriptor->setRefValue(jointInstance.get(), NULL);
|
|
}
|
|
|
|
addPendingRef(refPropDescriptor, jointInstance, data);
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::streamOutAutoJointHelper(std::vector<shared_ptr<PartInstance> > pendingRemovalParts, shared_ptr<Instance> instance)
|
|
{
|
|
if (shared_ptr<JointInstance> jointInstance =
|
|
Instance::fastSharedDynamicCast<JointInstance>(instance))
|
|
{
|
|
RBXASSERT(Joint::isManualJoint(jointInstance->getJoint()) == false);
|
|
|
|
PartInstance* partInstance0 = jointInstance->getPart0();
|
|
PartInstance* partInstance1 = jointInstance->getPart1();
|
|
|
|
if (partInstance0 == NULL && partInstance1 == NULL)
|
|
return;
|
|
for (std::vector<shared_ptr<PartInstance> >::iterator iter = pendingRemovalParts.begin(); iter != pendingRemovalParts.end(); iter++)
|
|
{
|
|
shared_ptr<PartInstance> pendingRemovalPart = *iter;
|
|
if (pendingRemovalPart)
|
|
{
|
|
const Reflection::RefPropertyDescriptor* refPropDescriptor = NULL;
|
|
Guid::Data data;
|
|
if (partInstance0 == pendingRemovalPart.get())
|
|
{
|
|
refPropDescriptor = &JointInstance::prop_Part0;
|
|
partInstance0->getGuid().extract(data);
|
|
}
|
|
else if (partInstance1 == pendingRemovalPart.get())
|
|
{
|
|
refPropDescriptor = &JointInstance::prop_Part1;
|
|
partInstance1->getGuid().extract(data);
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
{
|
|
RBX::ScopedAssign<Instance*> assign(removingInstance, jointInstance.get());
|
|
refPropDescriptor->setRefValue(jointInstance.get(), NULL);
|
|
}
|
|
addPendingRef(refPropDescriptor, jointInstance, data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void unregisterHelper(GuidItem<Instance>::Registry* registry, const shared_ptr<Instance>& instance)
|
|
{
|
|
registry->tryUnregister(instance.get());
|
|
}
|
|
|
|
void ClientReplicator::streamOutTerrain(const Vector3int16 &cellPos) {
|
|
ScopedAssign<bool> assignment(clusterDebounce, true); // prevent replication of garbage collection
|
|
if (megaClusterInstance)
|
|
{
|
|
megaClusterInstance->getVoxelGrid()->setCell(cellPos, Voxel::Constants::kUniqueEmptyCellRepresentation, Voxel::CELL_MATERIAL_Unspecified);
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::streamOutInstance(Instance* instance, bool deleteImmediately)
|
|
{
|
|
RBXASSERT(instance);
|
|
|
|
CPUPROFILER_START(RBX::Network::NetworkProfiler::PROFILER_streamOutPart);
|
|
|
|
disconnectReplicationData(shared_from(instance));
|
|
|
|
CPUPROFILER_STEP(RBX::Network::NetworkProfiler::PROFILER_streamOutPart);
|
|
// Unregistration happens in the instance destructor. Perform it here
|
|
// to be on the safe side. This ensures the streamed out instance is not
|
|
// used if the guid is seen later, possibly before all of the shared
|
|
// pointers to the instance are released.
|
|
Guid::Data data;
|
|
instance->getGuid().extract(data);
|
|
instance->visitDescendants(boost::bind(&unregisterHelper, guidRegistry.get(), _1));
|
|
guidRegistry->unregister(instance);
|
|
|
|
CPUPROFILER_STEP(RBX::Network::NetworkProfiler::PROFILER_streamOutPart);
|
|
|
|
if (PartInstance* part = Instance::fastDynamicCast<PartInstance>(instance))
|
|
{
|
|
part->setIsCurrentlyStreamRemovingPart();
|
|
|
|
// Clean manual joints
|
|
Primitive* prim = part->getPartPrimitive();
|
|
for (int i = 0; i < prim->getNumJoints(); ++i) {
|
|
Joint* joint = prim->getJoint(i);
|
|
if (Joint::isManualJoint(joint)) {
|
|
JointInstance* jointInstance =
|
|
static_cast<JointInstance*>(joint->getJointOwner());
|
|
// Don't worry about joints that are descendents of the part, they
|
|
// should be streamed back when the part comes back.
|
|
if (!jointInstance->isDescendantOf(part)) {
|
|
streamOutPartHelper(data, part, shared_from(jointInstance));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deleteImmediately)
|
|
{
|
|
// Clean auto joints
|
|
// This mechanism can't rely on primitive->getJoint() because that function
|
|
// only returns active joints (where both part0 and part1 are set). If the
|
|
// second part of a joint is streamed out, we still want to null out the
|
|
// part reference in that joint.
|
|
if (JointsService* jointsService = ServiceProvider::find<JointsService>(this)) {
|
|
jointsService->visitDescendants(
|
|
boost::bind(&ClientReplicator::streamOutPartHelper, this, data, part, _1));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// not a part instance, we can safely delete it
|
|
deleteImmediately = true;
|
|
}
|
|
|
|
CPUPROFILER_STEP(RBX::Network::NetworkProfiler::PROFILER_streamOutPart);
|
|
|
|
// Normally removingInstance is set after the replicator sees the
|
|
// setParent(NULL) property change event. Set it before hand to
|
|
// suppress any associated events and property changes. This makes the
|
|
// removal local (not replicated).
|
|
|
|
// TODO: destroy can throw if the part or any of its children have
|
|
// locked parents. Is it possible to stream out an instance with a
|
|
// locked parent?
|
|
if (deleteImmediately)
|
|
{
|
|
RBX::ScopedAssign<Instance*> assign(removingInstance, instance);
|
|
instance->setParent(NULL);
|
|
}
|
|
|
|
CPUPROFILER_STEP(RBX::Network::NetworkProfiler::PROFILER_streamOutPart);
|
|
}
|
|
|
|
int ClientReplicator::getNumRegionsToGC() const
|
|
{
|
|
return gcJob ? gcJob->getNumRegionsToGC() : 0;
|
|
}
|
|
|
|
short ClientReplicator::getGCDistance() const
|
|
{
|
|
return gcJob ? gcJob->getGCDistance() : 0;
|
|
}
|
|
|
|
void ClientReplicator::renderStreamedRegions(Adorn* adorn)
|
|
{
|
|
if (gcJob)
|
|
gcJob->render3dAdorn(adorn);
|
|
}
|
|
|
|
int ClientReplicator::getNumStreamedRegions() const
|
|
{
|
|
return gcJob ? gcJob->getNumRegions() : 0;
|
|
}
|
|
|
|
void ClientReplicator::renderPartMovementPath(Adorn* adorn)
|
|
{
|
|
if (physicsReceiver)
|
|
{
|
|
physicsReceiver->renderPartMovementPath(adorn);
|
|
}
|
|
}
|
|
|
|
using namespace RBX::Reflection;
|
|
|
|
void ClientReplicator::learnSchema(RakNet::BitStream& inBitStream)
|
|
{
|
|
#ifdef NETWORK_DEBUG
|
|
StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Syncing schema from server..");
|
|
#endif
|
|
RakNet::BitStream bitStream;
|
|
decompressBitStream(inBitStream, bitStream);
|
|
|
|
unsigned numTotalEnum, enumMSB, numClass, numProp, numEvent, numArg;
|
|
unsigned int classId, propId, typeId, eventId;
|
|
RakNet::RakString enumName, className, propName, propType, eventName;
|
|
unsigned char replicationLevel;
|
|
bool canReplicate, isEnum;
|
|
|
|
// --- Enums
|
|
bitStream >> numTotalEnum;
|
|
for (size_t i=0; i<numTotalEnum; i++)
|
|
{
|
|
bitStream.Read(enumName);
|
|
bitStream >> enumMSB;
|
|
|
|
// can we find the enum locally?
|
|
const Name& enumNameName = Name::declare(enumName.C_String());
|
|
|
|
serverEnums.insert(std::make_pair(&enumNameName, ReflectionEnumContainer(enumMSB)));
|
|
}
|
|
|
|
// --- Classes
|
|
bitStream >> numClass;
|
|
for (size_t i=0; i<numClass; i++)
|
|
{
|
|
// read class desc
|
|
bitStream >> classId;
|
|
bitStream.Read(className);
|
|
bitStream >> replicationLevel;
|
|
|
|
const Name& classNameName = Name::declare(className.C_String());
|
|
|
|
shared_ptr<ReflectionClassContainer> classContainer(new ReflectionClassContainer(classId, classNameName, (Reflection::ReplicationLevel)replicationLevel));
|
|
|
|
const ClassDescriptor* classDesc;
|
|
classDictionary.getValue(classId, classDesc);
|
|
|
|
if (!classDesc)
|
|
{
|
|
classContainer->needSync = true;
|
|
}
|
|
|
|
bool classIsDesync = false;
|
|
// --- Properties
|
|
bitStream >> numProp;
|
|
for (size_t j=0; j<numProp; j++)
|
|
{
|
|
// read prop desc
|
|
bitStream >> propId;
|
|
bitStream.Read(propName);
|
|
bitStream >> typeId;
|
|
bitStream.Read(propType);
|
|
bitStream >> canReplicate;
|
|
bitStream >> isEnum;
|
|
if (isEnum)
|
|
{
|
|
bitStream >> enumMSB;
|
|
}
|
|
else
|
|
{
|
|
enumMSB = 0;
|
|
}
|
|
const Reflection::Type* type;
|
|
typeDictionary.getValue(typeId, type);
|
|
shared_ptr<ReflectionPropertyContainer> propertyContainer(new ReflectionPropertyContainer(propId, Name::declare(propName.C_String()), typeId, std::string(propType.C_String()), type, canReplicate, enumMSB));
|
|
if (!type)
|
|
{
|
|
if (!isEnum)
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_WARNING, "A new type (%s) is used for property '%s', please make sure this property will not be replicated to or from the legacy clients.", propType.C_String(), propName.C_String());
|
|
}
|
|
// we are using a new type for this property, we should always use server schema for it
|
|
propertyContainer->needSync = true;
|
|
}
|
|
|
|
const Reflection::PropertyDescriptor* propDesc;
|
|
propDictionary.getValue(propId, propDesc);
|
|
if (propDesc)
|
|
{
|
|
// has the property attributes changed?
|
|
if (propDesc->canReplicate() != canReplicate
|
|
|| strcmp(propType.C_String(), propDesc->type.name.c_str()) != 0)
|
|
{
|
|
// property attribute changed
|
|
propertyContainer->needSync = true;
|
|
}
|
|
#ifdef NETWORK_DEBUG
|
|
if (FFlag::DebugProtocolSynchronization)
|
|
{
|
|
// This is to simulate property adding / changing scenario
|
|
if (classContainer->name == "WedgePart" && propertyContainer->name == "BrickColor")
|
|
{
|
|
StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Simulating property adding / changing on WedgePart.BrickColor");
|
|
propertyContainer->needSync = true;
|
|
ClassDescriptor::ClassDescriptors::const_iterator iter = ClassDescriptor::all_begin();
|
|
while (iter!=ClassDescriptor::all_end())
|
|
{
|
|
if ((*iter)->name == classDesc->name)
|
|
{
|
|
*((*iter)->isOutdated) = true;
|
|
break;
|
|
}
|
|
++iter;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
//StandardOut::singleton()->printf(RBX::MESSAGE_WARNING, "Unknown property %s (%s)", propName, propType);
|
|
propertyContainer->needSync = true;
|
|
}
|
|
if (classContainer->needSync == true)
|
|
{
|
|
// if the class is new or removed, we invalidate all its properties
|
|
propertyContainer->needSync = true;
|
|
}
|
|
|
|
classContainer->properties.push_back(propertyContainer);
|
|
if (propertyContainer->needSync == true)
|
|
{
|
|
// if a class' property is new, removed, or modified, we mark the class as changed
|
|
classIsDesync = true;
|
|
}
|
|
}
|
|
// --- Events
|
|
bitStream >> numEvent;
|
|
for (size_t j=0; j<numEvent; j++)
|
|
{
|
|
// read event desc
|
|
bitStream >> eventId;
|
|
bitStream.Read(eventName);
|
|
bitStream >> numArg;
|
|
shared_ptr<ReflectionEventContainer> eventContainer(new ReflectionEventContainer(eventId, Name::declare(eventName.C_String())));
|
|
for (size_t k=0; k<numArg; k++)
|
|
{
|
|
bitStream >> typeId;
|
|
const Reflection::Type* type;
|
|
typeDictionary.getValue(typeId, type);
|
|
RBXASSERT(type);
|
|
eventContainer->argTypes.push_back(type);
|
|
}
|
|
//StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Event %s, %d", eventContainer->name.c_str(), eventContainer->argTypes.size());
|
|
|
|
const Reflection::EventDescriptor* eventDesc;
|
|
eventDictionary.getValue(eventId, eventDesc);
|
|
if (eventDesc)
|
|
{
|
|
if (eventDesc->getSignature().arguments.size() != numArg)
|
|
{
|
|
eventContainer->needSync = true;
|
|
}
|
|
else
|
|
{
|
|
size_t l = 0;
|
|
for(std::list<Reflection::SignatureDescriptor::Item>::const_iterator argIter = eventDesc->getSignature().arguments.begin();
|
|
argIter != eventDesc->getSignature().arguments.end();
|
|
++argIter)
|
|
{
|
|
const Reflection::SignatureDescriptor::Item arg = *argIter;
|
|
if (arg.type->name != eventContainer->argTypes[l++]->name)
|
|
{
|
|
eventContainer->needSync = true;
|
|
}
|
|
// we don't perform enum check here because we want to give some tolerance to enum changes
|
|
// e.g., if we add a new value to the end of enum, we should still expect the legacy client behave normally
|
|
// In this case, changing or removing enum values should be prohibited or customized code should be added to handle legacy clients
|
|
//else if (const Reflection::EnumDescriptor* enumDesc = Reflection::EnumDescriptor::lookupDescriptor(*typeIter->type))
|
|
//{
|
|
// size_t newMSB = 0;
|
|
// if (hasEnumChanged(*enumDesc, newMSB))
|
|
// {
|
|
// eventContainer->needSync = true;
|
|
// }
|
|
//}
|
|
}
|
|
}
|
|
#ifdef NETWORK_DEBUG
|
|
if (FFlag::DebugProtocolSynchronization)
|
|
{
|
|
// This is to simulate event adding / changing scenario
|
|
if (classContainer->name == "Player" && eventContainer->name == "OnTeleport")
|
|
{
|
|
StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Simulating event adding / changing on Player.OnTeleport");
|
|
eventContainer->needSync = true;
|
|
ClassDescriptor::ClassDescriptors::const_iterator iter = ClassDescriptor::all_begin();
|
|
while (iter!=ClassDescriptor::all_end())
|
|
{
|
|
if ((*iter)->name == classDesc->name)
|
|
{
|
|
*((*iter)->isOutdated) = true;
|
|
break;
|
|
}
|
|
++iter;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// event added to newer version
|
|
eventContainer->needSync = true;
|
|
}
|
|
|
|
if (classContainer->needSync)
|
|
{
|
|
// if the class is new or removed, we invalidate all its events
|
|
eventContainer->needSync = true;
|
|
}
|
|
|
|
classContainer->events.push_back(eventContainer);
|
|
if (eventContainer->needSync)
|
|
{
|
|
classIsDesync = true;
|
|
}
|
|
}
|
|
|
|
if (classIsDesync || (classDesc && *(classDesc->isOutdated)))
|
|
{
|
|
classContainer->needSync = true;
|
|
}
|
|
|
|
serverClasses.insert(std::make_pair(&classNameName, classContainer));
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::writeProperties(const Instance* instance, RakNet::BitStream& outBitstream, PropertyCacheType cacheType, bool useDictionary)
|
|
{
|
|
if (*(instance->getDescriptor().isOutdated))
|
|
{
|
|
// class is outdated, serialize using server schema
|
|
bool cacheable = (cacheType != PropertyCacheType_NonCacheable);
|
|
|
|
ReflectionClassMap::const_iterator classIter = serverClasses.find(&instance->getClassName());
|
|
|
|
if (classIter != serverClasses.end())
|
|
{
|
|
const shared_ptr<ReflectionClassContainer>& reflectionClass = classIter->second;
|
|
|
|
if (reflectionClass->needSync == true)
|
|
{
|
|
// the content of this class has changed, we need use server scheme to deserialize the properties
|
|
ReflectionPropertyList::const_iterator propIter = reflectionClass->properties.begin();
|
|
RBX::Reflection::ConstPropertyIterator iter = instance->properties_begin();
|
|
for (; propIter != reflectionClass->properties.end(); propIter++)
|
|
{
|
|
const shared_ptr<ReflectionPropertyContainer>& reflectionProperty = *propIter;
|
|
if (reflectionProperty->needSync == true)
|
|
{
|
|
if (reflectionProperty->canReplicate)
|
|
{
|
|
// new property or modified property, let's use the default value
|
|
if (cacheType == PropertyCacheType_All || isPropertyCacheable(reflectionProperty->type, reflectionProperty->enumMSB > 0) == cacheable)
|
|
{
|
|
// optimization for bool properties
|
|
if (settings().printBits) {
|
|
std::string str = RBX::format(
|
|
" write %s %s, 1 bit (using server schema)",
|
|
reflectionProperty->type ? reflectionProperty->type->name.c_str() : "Unknown",
|
|
reflectionProperty->name.c_str()
|
|
);
|
|
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_SENSITIVE, "%s", str.c_str());
|
|
|
|
}
|
|
outBitstream << true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Reflection::ConstProperty property = *iter;
|
|
#ifdef NETWORK_DEBUG
|
|
if (FFlag::DebugProtocolSynchronization)
|
|
{
|
|
// Cope with property adding / changing simulation
|
|
if (instance->getDescriptor().name == "WedgePart" && property.getName().toString() == "BrickColor")
|
|
{
|
|
++iter;
|
|
property = *iter;
|
|
}
|
|
}
|
|
#endif
|
|
while (property.getName() != reflectionProperty->name && iter != instance->properties_end())
|
|
{
|
|
++iter;
|
|
property = *iter;
|
|
}
|
|
|
|
writePropertiesInternal(instance, property, outBitstream, cacheType, useDictionary);
|
|
|
|
++iter;
|
|
}
|
|
}
|
|
RBXASSERT(iter == instance->properties_end());
|
|
}
|
|
else
|
|
{
|
|
// client schema is same as server's, why we get here?
|
|
RBXASSERT(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// server does not know about our type
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Super::writeProperties(instance, outBitstream, cacheType, useDictionary);
|
|
}
|
|
}
|
|
|
|
bool ClientReplicator::ProcessOutdatedChangedProperty(RakNet::BitStream& inBitstream, const RBX::Guid::Data& id, const Instance* instance, const Reflection::PropertyDescriptor* propertyDescriptor, unsigned int propId)
|
|
{
|
|
if (instance && !(*(instance->getDescriptor().isOutdated)))
|
|
{
|
|
// if the class is not outdated, its properties won't be too because the class checksum is an accumulation of its property and event checksums
|
|
return false;
|
|
}
|
|
bool skipProperty = false;
|
|
if (propertyDescriptor == NULL)
|
|
{
|
|
// we don't have the property definition locally, let's skip the property
|
|
skipProperty = true;
|
|
}
|
|
else
|
|
{
|
|
if (instance)
|
|
{
|
|
// do client and server agree on the property attributes (e.g., type)?
|
|
shared_ptr<ReflectionPropertyContainer> serverBasedProperty;
|
|
if (getServerBasedProperty(instance->getClassName(), propId, serverBasedProperty))
|
|
{
|
|
if (serverBasedProperty->needSync)
|
|
{
|
|
// they don't match, skip it..
|
|
skipProperty = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RBXASSERT(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
skipProperty = true;
|
|
}
|
|
}
|
|
|
|
if (skipProperty)
|
|
{
|
|
const Name* className = &Name::getNullName();
|
|
|
|
if (instance) // instance could be NULL because the legacy client might not recognize the class and have not created the instance
|
|
{
|
|
className = &instance->getClassName();
|
|
}
|
|
else
|
|
{
|
|
InstanceClassMap::const_iterator findIter = serverInstanceClassMap.find(id);
|
|
if (findIter != serverInstanceClassMap.end())
|
|
{
|
|
className = &findIter->second->name;
|
|
}
|
|
else
|
|
{
|
|
// This must be a null instance with a known class, we proceed with regular deserialization
|
|
return false;
|
|
}
|
|
}
|
|
shared_ptr<ReflectionPropertyContainer> serverBasedProperty;
|
|
if (getServerBasedProperty(*className, propId, serverBasedProperty))
|
|
{
|
|
skipChangedProperty(inBitstream, serverBasedProperty);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
RBXASSERT(false);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ClientReplicator::ProcessOutdatedProperties(RakNet::BitStream& inBitstream, Instance* instance, PropertyCacheType cacheType, bool useDictionary, bool preventBounceBack, std::vector<PropValuePair>* valueArray)
|
|
{
|
|
bool cacheable = (cacheType != PropertyCacheType_NonCacheable);
|
|
|
|
if (!(*(instance->getDescriptor().isOutdated)))
|
|
{
|
|
// if the class is not outdated, its properties won't be too because the class checksum is an accumulation of its property and event checksums
|
|
return false;
|
|
}
|
|
|
|
ReflectionClassMap::const_iterator classIter = serverClasses.find(&instance->getClassName());
|
|
|
|
if (classIter != serverClasses.end())
|
|
{
|
|
const shared_ptr<ReflectionClassContainer>& reflectionClass = classIter->second;
|
|
|
|
if (reflectionClass->needSync == true)
|
|
{
|
|
// the content of this class has changed, we need use server scheme to deserialize the properties
|
|
ReflectionPropertyList::const_iterator propIter = reflectionClass->properties.begin();
|
|
for (; propIter != reflectionClass->properties.end(); propIter++)
|
|
{
|
|
const shared_ptr<ReflectionPropertyContainer>& reflectionProperty = *propIter;
|
|
if (!reflectionProperty->canReplicate)
|
|
{
|
|
continue;
|
|
}
|
|
if (reflectionProperty->needSync == true)
|
|
{
|
|
if ((cacheType == PropertyCacheType_All || isPropertyCacheable(reflectionProperty->type, reflectionProperty->enumMSB > 0) == cacheable)
|
|
&& (reflectionProperty->name != Instance::propParent.name)) // don't read parent for now
|
|
{
|
|
// skip the new/changed properties
|
|
skipPropertiesInternal(reflectionProperty, inBitstream, useDictionary);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// for each recognizable property, we iterate through the instance property collection and set the value
|
|
RBX::Reflection::PropertyIterator iter = instance->properties_begin();
|
|
RBX::Reflection::PropertyIterator end = instance->properties_end();
|
|
while (iter!=end)
|
|
{
|
|
Reflection::Property property = *iter;
|
|
const Reflection::PropertyDescriptor& descriptor = property.getDescriptor();
|
|
if (descriptor.name == reflectionProperty->name)
|
|
{
|
|
if (descriptor.canReplicate()
|
|
&& (cacheType == PropertyCacheType_All || isPropertyCacheable(descriptor.type) == cacheable)
|
|
&& (!cacheable || descriptor != Instance::propParent)) // don't read parent if it's cacheable (PropertyCacheType_Cacheable) or potentially cacheable (PropertyCacheType_All)
|
|
{
|
|
if (valueArray)
|
|
{
|
|
Reflection::Variant value;
|
|
readPropertiesInternal(property, inBitstream, useDictionary, preventBounceBack, &value);
|
|
if (!value.isVoid())
|
|
valueArray->push_back(PropValuePair(property.getDescriptor(), value));
|
|
}
|
|
else
|
|
readPropertiesInternal(property, inBitstream, useDictionary, preventBounceBack, NULL);
|
|
}
|
|
break;
|
|
}
|
|
++iter;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// client schema is same as server's, why are we here?
|
|
RBXASSERT(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we got the object from the server, how can it not be in the map?
|
|
RBXASSERT(false);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ClientReplicator::ProcessOutdatedInstance(RakNet::BitStream& inBitstream, bool isJoinData, const RBX::Guid::Data& id, const Reflection::ClassDescriptor* classDescriptor, unsigned int classId)
|
|
{
|
|
#ifdef NETWORK_DEBUG
|
|
if (FFlag::DebugProtocolSynchronization)
|
|
{
|
|
// This is to simulate class adding scenario
|
|
if (classDescriptor && classDescriptor->name.toString() == "VehicleSeat")
|
|
{
|
|
StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Simulating class adding on %s", classDescriptor->name.c_str());
|
|
classDescriptor = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (FFlag::DebugProtocolSynchronization)
|
|
{
|
|
if (classDescriptor && classDescriptor->name.toString() == "TheNewClass")
|
|
{
|
|
classDescriptor = NULL;
|
|
}
|
|
}
|
|
|
|
if (classDescriptor == NULL)
|
|
{
|
|
// we don't have the class definition locally, let's read through the instance creation message and do nothing
|
|
ReflectionClassMap::const_iterator classIter = serverClasses.begin();
|
|
for (; classIter != serverClasses.end(); classIter++)
|
|
{
|
|
const shared_ptr<ReflectionClassContainer>& reflectionClass = classIter->second;
|
|
if (reflectionClass->id == classId)
|
|
{
|
|
// we found the class defined at the server side
|
|
// let's map the instance GUID to the class definition so we can deserialize property changes about this instance properly
|
|
serverInstanceClassMap.insert(std::pair<RBX::Guid::Data, shared_ptr<ReflectionClassContainer> >(id, reflectionClass));
|
|
|
|
if (settings().printInstances) {
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_SENSITIVE,
|
|
"Skipping: %s:%s << %s", // note: remove player always on the left
|
|
reflectionClass->name.c_str(),
|
|
id.readableString().c_str(),
|
|
RakNetAddressToString(remotePlayerId).c_str()
|
|
);
|
|
}
|
|
bool deleteOnDisconnect;
|
|
inBitstream >> deleteOnDisconnect;
|
|
RBX::Guid::Data parentId;
|
|
if (!isJoinData)
|
|
{
|
|
// read all non-cachable properties first
|
|
ReflectionPropertyList::const_iterator propIter = reflectionClass->properties.begin();
|
|
for (; propIter != reflectionClass->properties.end(); propIter++)
|
|
{
|
|
const shared_ptr<ReflectionPropertyContainer> reflectionProp = *propIter;
|
|
const Reflection::Type* propType;
|
|
typeDictionary.getValue(reflectionProp->typeId, propType);
|
|
if (reflectionProp->canReplicate & !isPropertyCacheable(*propType))
|
|
{
|
|
skipPropertiesInternal(reflectionProp, inBitstream, true);
|
|
}
|
|
}
|
|
|
|
// read all cachable properties except for parent
|
|
propIter = reflectionClass->properties.begin();
|
|
for (; propIter != reflectionClass->properties.end(); propIter++)
|
|
{
|
|
const shared_ptr<ReflectionPropertyContainer> reflectionProp = *propIter;
|
|
const Reflection::Type* propType;
|
|
typeDictionary.getValue(reflectionProp->typeId, propType);
|
|
if (reflectionProp->canReplicate & isPropertyCacheable(*propType))
|
|
{
|
|
// Parent are never sent here
|
|
if (reflectionProp->name != Instance::propParent.name.toString())
|
|
{
|
|
skipPropertiesInternal(reflectionProp, inBitstream, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
deserializeId(inBitstream, parentId);
|
|
}
|
|
else
|
|
{
|
|
// read all properties except for parent
|
|
ReflectionPropertyList::const_iterator propIter = reflectionClass->properties.begin();
|
|
for (; propIter != reflectionClass->properties.end(); propIter++)
|
|
{
|
|
const shared_ptr<ReflectionPropertyContainer> reflectionProp = *propIter;
|
|
const Reflection::Type* propType;
|
|
typeDictionary.getValue(reflectionProp->typeId, propType);
|
|
if (reflectionProp->canReplicate)
|
|
{
|
|
// don't write the parent yet
|
|
if (reflectionProp->name != Instance::propParent.name.toString())
|
|
{
|
|
skipPropertiesInternal(reflectionProp, inBitstream, false);
|
|
}
|
|
}
|
|
}
|
|
deserializeIdWithoutDictionary(inBitstream, parentId);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ClientReplicator::ProcessOutdatedEventInvocation(RakNet::BitStream& inBitstream, const RBX::Guid::Data& id, const Instance* instance, const Reflection::EventDescriptor* eventDescriptor, unsigned int eventId)
|
|
{
|
|
if (instance && !(*(instance->getDescriptor().isOutdated)))
|
|
{
|
|
// if the class is not outdated, its events won't be too because the class checksum is an accumulation of its property and event checksums
|
|
return false;
|
|
}
|
|
|
|
bool skipEvent = false;
|
|
if (eventDescriptor == NULL)
|
|
{
|
|
// we don't have the event definition locally, let's skip it
|
|
skipEvent = true;
|
|
}
|
|
else
|
|
{
|
|
if (instance)
|
|
{
|
|
// do client and server agree on the event attributes?
|
|
shared_ptr<ReflectionEventContainer> serverBasedEvent;
|
|
if (getServerBasedEvent(instance->getClassName(), eventId, serverBasedEvent))
|
|
{
|
|
if (serverBasedEvent->needSync)
|
|
{
|
|
skipEvent = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RBXASSERT(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
skipEvent = true;
|
|
}
|
|
}
|
|
if (skipEvent)
|
|
{
|
|
const Name* className = &Name::getNullName();
|
|
|
|
if (instance)
|
|
{
|
|
className = &instance->getClassName();
|
|
}
|
|
else
|
|
{
|
|
InstanceClassMap::const_iterator findIter = serverInstanceClassMap.find(id);
|
|
if (findIter != serverInstanceClassMap.end())
|
|
{
|
|
className = &findIter->second->name;
|
|
}
|
|
else
|
|
{
|
|
RBXASSERT(false);
|
|
}
|
|
}
|
|
shared_ptr<ReflectionEventContainer> serverBasedEvent;
|
|
if (getServerBasedEvent(*className, eventId, serverBasedEvent))
|
|
{
|
|
skipEventInvocation(inBitstream, serverBasedEvent);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
RBXASSERT(false);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ClientReplicator::ProcessOutdatedEnumSerialization(const Reflection::Type& type, const Reflection::Variant& value, RakNet::BitStream& outBitStream)
|
|
{
|
|
if (const Reflection::EnumDescriptor* enumDesc = Reflection::EnumDescriptor::lookupDescriptor(type))
|
|
{
|
|
size_t newMSB = 0;
|
|
if (hasEnumChanged(*enumDesc, newMSB))
|
|
{
|
|
// the enum definition has changed at server side, let's use server schema to serialize
|
|
serializeEnum(enumDesc, value, outBitStream, newMSB);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ClientReplicator::ProcessOutdatedEnumDeserialization(RakNet::BitStream& inBitStream, const Reflection::Type& type, Reflection::Variant& value)
|
|
{
|
|
size_t newMSB = 0;
|
|
if (const Reflection::EnumDescriptor* enumDesc = Reflection::EnumDescriptor::lookupDescriptor(type))
|
|
{
|
|
if (hasEnumChanged(*enumDesc, newMSB))
|
|
{
|
|
// the enum definition has changed at server side, let's use server schema to deserialize
|
|
// Note, we try to be very tolerant to enum changes so adding a new value at the end of enum will still work most of the time
|
|
// However, it will break if:
|
|
// 1) enum values get removed
|
|
// 2) order of enum values gets changed
|
|
// Also, overflowed new enum values will be discarded on old clients
|
|
deserializeEnum(enumDesc, value, inBitStream, newMSB);
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we can't find the enum locally, skip the value using server schema
|
|
auto enumIter = serverEnums.find(&type.name);
|
|
|
|
if (enumIter != serverEnums.end())
|
|
{
|
|
inBitStream.IgnoreBits(enumIter->second.enumMSB+1);
|
|
return true;
|
|
}
|
|
|
|
RBXASSERT(false);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ClientReplicator::ProcessOutdatedPropertyEnumSerialization(const Reflection::ConstProperty& property, RakNet::BitStream& outBitStream)
|
|
{
|
|
const EnumPropertyDescriptor& enumDesc = static_cast<const EnumPropertyDescriptor&>(property.getDescriptor());
|
|
size_t newMSB = 0;
|
|
if (hasEnumChanged(enumDesc.enumDescriptor, newMSB))
|
|
{
|
|
// the enum definition has changed at server side, let's use server schema to serialize
|
|
serializeEnumProperty(property, outBitStream, newMSB);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ClientReplicator::ProcessOutdatedPropertyEnumDeserialization(Reflection::Property& property, RakNet::BitStream& inBitStream)
|
|
{
|
|
size_t newMSB = 0;
|
|
const EnumPropertyDescriptor& enumDesc = static_cast<const EnumPropertyDescriptor&>(property.getDescriptor());
|
|
if (hasEnumChanged(enumDesc.enumDescriptor, newMSB))
|
|
{
|
|
// the enum definition has changed at server side, let's use server schema to deserialize
|
|
// Note, we try to be very tolerant to enum changes so adding a new value at the end of enum will still work most of the time
|
|
// However, it will break if:
|
|
// 1) enum values get removed
|
|
// 2) order of enum values gets changed
|
|
// Also, overflowed new enum values will be discarded on old clients
|
|
deserializeEnumProperty(property, inBitStream, newMSB);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ClientReplicator::skipPropertyValue(RakNet::BitStream& inBitStream, const shared_ptr<ClientReplicator::ReflectionPropertyContainer>& prop, bool useDictionary)
|
|
{
|
|
const Reflection::Type& type = *prop->type;
|
|
if (prop->enumMSB > 0) // this is an enum property
|
|
{
|
|
int value = 0;
|
|
readFastN( inBitStream, value, prop->enumMSB+1 );
|
|
}
|
|
else if (type == Reflection::Type::singleton<RBX::ProtectedString>())
|
|
{
|
|
BinaryString strValue;
|
|
|
|
if (useDictionary)
|
|
getSharedPropertyBinaryDictionaryById(prop->id).deserializeString(strValue, inBitStream);
|
|
else
|
|
inBitStream >> strValue;
|
|
}
|
|
else if (type == Reflection::Type::singleton<std::string>())
|
|
{
|
|
std::string strValue;
|
|
if (useDictionary)
|
|
{
|
|
getSharedPropertyDictionaryById(prop->id).deserializeString(strValue, inBitStream);
|
|
}
|
|
else
|
|
{
|
|
inBitStream >> strValue;
|
|
}
|
|
}
|
|
else if (type==Reflection::Type::singleton<BinaryString>())
|
|
{
|
|
BinaryString strValue;
|
|
inBitStream >> strValue;
|
|
}
|
|
else if (type==Reflection::Type::singleton<bool>())
|
|
{
|
|
bool dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (type==Reflection::Type::singleton<int>())
|
|
{
|
|
int dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (type==Reflection::Type::singleton<float>())
|
|
{
|
|
float dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (type==Reflection::Type::singleton<double>())
|
|
{
|
|
double dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (type==Reflection::Type::singleton<UDim>())
|
|
{
|
|
UDim dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (type==Reflection::Type::singleton<UDim2>())
|
|
{
|
|
UDim2 dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (type==Reflection::Type::singleton<RBX::RbxRay>())
|
|
{
|
|
RBX::RbxRay dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (type==Reflection::Type::singleton<Faces>())
|
|
{
|
|
Faces dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (type==Reflection::Type::singleton<Axes>())
|
|
{
|
|
Axes dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (type==Reflection::Type::singleton<BrickColor>())
|
|
{
|
|
BrickColor dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (type==Reflection::Type::singleton<G3D::Color3>())
|
|
{
|
|
G3D::Color3 dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (type==Reflection::Type::singleton<G3D::Vector2>())
|
|
{
|
|
G3D::Vector2 dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (type==Reflection::Type::singleton<G3D::Vector3>())
|
|
{
|
|
if (prop->name == PartInstance::prop_Size.name.toString())
|
|
{
|
|
G3D::Vector3 value;
|
|
readBrickVector(inBitStream, value);
|
|
}
|
|
else
|
|
{
|
|
G3D::Vector3 dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
}
|
|
else if (type==Reflection::Type::singleton<G3D::Vector2int16>())
|
|
{
|
|
G3D::Vector2int16 dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (type==Reflection::Type::singleton<G3D::CoordinateFrame>())
|
|
{
|
|
G3D::CoordinateFrame dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (Reflection::RefPropertyDescriptor::isRefPropertyDescriptor(type))
|
|
{
|
|
RBX::Guid::Data id;
|
|
if (useDictionary)
|
|
{
|
|
deserializeId(inBitStream, id);
|
|
}
|
|
else
|
|
{
|
|
deserializeIdWithoutDictionary(inBitStream, id);
|
|
}
|
|
}
|
|
else if (type==Reflection::Type::singleton<RBX::ContentId>())
|
|
{
|
|
RBX::ContentId dummy;
|
|
if (useDictionary)
|
|
contentIdDictionary.receive(inBitStream, dummy);
|
|
else
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (type==Reflection::Type::singleton<RBX::SystemAddress>())
|
|
{
|
|
RBX::SystemAddress value;
|
|
if (useDictionary)
|
|
systemAddressDictionary.receive(inBitStream, value);
|
|
else
|
|
inBitStream >> value;
|
|
}
|
|
else if (type==Reflection::Type::singleton<Rect2D>())
|
|
{
|
|
Rect2D dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else if (type==Reflection::Type::singleton<PhysicalProperties>())
|
|
{
|
|
PhysicalProperties dummy;
|
|
inBitStream >> dummy;
|
|
}
|
|
else
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_ERROR, "A new property type is not supported on this legacy client. Please flag off the replication before client and server are synchronized");
|
|
RBXASSERT(false);
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::skipPropertiesInternal(const shared_ptr<ClientReplicator::ReflectionPropertyContainer>& prop, RakNet::BitStream& inBitstream, bool useDictionary)
|
|
{
|
|
// in line with readPropertiesInternal
|
|
bool boolValue;
|
|
if (prop->type && prop->type->isType<bool>())
|
|
{
|
|
inBitstream >> boolValue;
|
|
if (settings().printBits) {
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO,
|
|
" skip %s %s, 1 bit",
|
|
prop->typeName.c_str(),
|
|
prop->name.c_str()
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool isDefault;
|
|
inBitstream >> isDefault;
|
|
if (isDefault)
|
|
{
|
|
if (settings().printBits) {
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO,
|
|
" skip %s %s, 1 bit (default)",
|
|
prop->typeName.c_str(),
|
|
prop->name.c_str()
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const int start = inBitstream.GetReadOffset();
|
|
|
|
skipPropertyValue(inBitstream, prop, useDictionary);
|
|
|
|
if (settings().printBits) {
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO,
|
|
" skip %s %s, %d bits",
|
|
prop->typeName.c_str(),
|
|
prop->name.c_str(),
|
|
inBitstream.GetReadOffset() - start + 1
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::skipChangedProperty(RakNet::BitStream& bitStream, const shared_ptr<ClientReplicator::ReflectionPropertyContainer>& prop)
|
|
{
|
|
bool versionReset;
|
|
bitStream >> versionReset;
|
|
if (prop->name == Instance::propParent.name.toString())
|
|
{
|
|
RBX::Guid::Data parentId;
|
|
deserializeId(bitStream, parentId);
|
|
}
|
|
else
|
|
{
|
|
skipPropertyValue(bitStream, prop, true/*useDictionary*/);
|
|
}
|
|
}
|
|
|
|
void ClientReplicator::skipEventInvocation(RakNet::BitStream& bitStream, const shared_ptr<ReflectionEventContainer>& event)
|
|
{
|
|
int numArgs;
|
|
bitStream >> numArgs;
|
|
|
|
for (int i=0; i<numArgs; i++)
|
|
{
|
|
const Reflection::Type& type = *event->argTypes[i];
|
|
Reflection::Variant argument;
|
|
if(type == Reflection::Type::singleton<std::string>())
|
|
{
|
|
std::string value;
|
|
getSharedEventDictionaryById(event->id).deserializeString(value, bitStream);
|
|
}
|
|
else{
|
|
deserializeValue(bitStream, type, argument);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ClientReplicator::getServerBasedProperty(const Name& className, int propertyId, shared_ptr<ClientReplicator::ReflectionPropertyContainer>& serverBasedProperty)
|
|
{
|
|
ReflectionClassMap::const_iterator classIter = serverClasses.find(&className);
|
|
|
|
if (classIter != serverClasses.end())
|
|
{
|
|
const shared_ptr<ReflectionClassContainer>& reflectionClass = classIter->second;
|
|
|
|
ReflectionPropertyList::const_iterator propIter = reflectionClass->properties.begin();
|
|
for (; propIter != reflectionClass->properties.end(); propIter++)
|
|
{
|
|
const shared_ptr<ReflectionPropertyContainer>& reflectionProperty = *propIter;
|
|
if (reflectionProperty->id == propertyId)
|
|
{
|
|
serverBasedProperty = reflectionProperty;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ClientReplicator::getServerBasedEvent(const Name& className, int eventId, shared_ptr<ClientReplicator::ReflectionEventContainer>& serverBasedEvent)
|
|
{
|
|
ReflectionClassMap::const_iterator classIter = serverClasses.find(&className);
|
|
|
|
if (classIter != serverClasses.end())
|
|
{
|
|
const shared_ptr<ReflectionClassContainer>& reflectionClass = classIter->second;
|
|
|
|
ReflectionEventList::const_iterator eventIter = reflectionClass->events.begin();
|
|
for (; eventIter != reflectionClass->events.end(); eventIter++)
|
|
{
|
|
const shared_ptr<ReflectionEventContainer>& reflectionEvent = *eventIter;
|
|
if (reflectionEvent->id == eventId)
|
|
{
|
|
serverBasedEvent = reflectionEvent;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ClientReplicator::isClassRemoved(const Instance* instance)
|
|
{
|
|
#ifdef NETWORK_DEBUG
|
|
if (FFlag::DebugProtocolSynchronization)
|
|
{
|
|
// This is to simulate class removal scenario
|
|
if (instance->getClassNameStr() == "TrussPart")
|
|
{
|
|
StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Simulating class removal TrussPart");
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return !(*(instance->getDescriptor().isReplicable));
|
|
}
|
|
|
|
bool ClientReplicator::isPropertyRemoved(const Instance* instance, const Name& propertyName)
|
|
{
|
|
#ifdef NETWORK_DEBUG
|
|
if (FFlag::DebugProtocolSynchronization)
|
|
{
|
|
// This is to simulate property removal scenario
|
|
if (instance->getClassNameStr() == "WedgePart" && propertyName == "Material")
|
|
{
|
|
StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Simulating property removal on WedgePart.Material");
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
if (!(*(instance->getDescriptor().isOutdated)))
|
|
{
|
|
// the class is not outdated, this means nothing has been changed in the class
|
|
return false;
|
|
}
|
|
|
|
ReflectionClassMap::const_iterator classIter = serverClasses.find(&instance->getClassName());
|
|
|
|
if (classIter != serverClasses.end())
|
|
{
|
|
const shared_ptr<ReflectionClassContainer>& reflectionClass = classIter->second;
|
|
|
|
ReflectionPropertyList::const_iterator propIter = reflectionClass->properties.begin();
|
|
for (; propIter != reflectionClass->properties.end(); propIter++)
|
|
{
|
|
const shared_ptr<ReflectionPropertyContainer>& reflectionProperty = *propIter;
|
|
if (reflectionProperty->name == propertyName)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// we can't find the property in server class schema, it should have been removed in server code
|
|
return true;
|
|
}
|
|
|
|
bool ClientReplicator::isEventRemoved(const Instance* instance, const Name& eventName)
|
|
{
|
|
#ifdef NETWORK_DEBUG
|
|
if (FFlag::DebugProtocolSynchronization)
|
|
{
|
|
// This is to simulate event removal scenario
|
|
if (instance->getClassNameStr() == "Tool" && eventName == "Activated")
|
|
{
|
|
StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Simulating event removal on Tool.Activated");
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!(*(instance->getDescriptor().isOutdated)))
|
|
{
|
|
// the class is not outdated, this means nothing has been changed in the class
|
|
return false;
|
|
}
|
|
|
|
ReflectionClassMap::const_iterator classIter = serverClasses.find(&instance->getClassName());
|
|
|
|
if (classIter != serverClasses.end())
|
|
{
|
|
const shared_ptr<ReflectionClassContainer>& reflectionClass = classIter->second;
|
|
|
|
ReflectionEventList::const_iterator eventIter = reflectionClass->events.begin();
|
|
for (; eventIter != reflectionClass->events.end(); eventIter++)
|
|
{
|
|
const shared_ptr<ReflectionEventContainer>& reflectionEvent = *eventIter;
|
|
if (reflectionEvent->name == eventName)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// we can't find the event in server class schema, it should have been removed in server code
|
|
return true;
|
|
}
|
|
|
|
bool ClientReplicator::hasEnumChanged(const Reflection::EnumDescriptor& enumDesc, size_t& newMSB)
|
|
{
|
|
#ifdef NETWORK_DEBUG
|
|
if (FFlag::DebugProtocolSynchronization)
|
|
{
|
|
// This is to simulate enum change scenario
|
|
if (!strcmp(enumDesc.name.c_str(), "TeleportState") || !strcmp(enumDesc.name.c_str(), "Material"))
|
|
{
|
|
StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Simulating enum modification on %s", enumDesc.name.c_str());
|
|
newMSB = enumDesc.getEnumCountMSB();
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
auto enumIter = serverEnums.find(&enumDesc.name);
|
|
|
|
if (enumIter != serverEnums.end())
|
|
{
|
|
newMSB = enumIter->second.enumMSB;
|
|
|
|
return newMSB != enumDesc.getEnumCountMSB();
|
|
}
|
|
|
|
// we can't find the enum in server enum schema, it should have been removed in server code
|
|
return true;
|
|
}
|
|
|
|
SharedStringProtectedDictionary& ClientReplicator::getSharedPropertyProtectedDictionaryById(unsigned int propertyId)
|
|
{
|
|
const Reflection::PropertyDescriptor* descriptor;
|
|
propDictionary.getValue(propertyId, descriptor);
|
|
if (descriptor)
|
|
{
|
|
return getSharedPropertyProtectedDictionary(*descriptor);
|
|
}
|
|
else
|
|
{
|
|
DummyPropertyProtectedStrings::iterator iter = dummyProtectedStrings.find(propertyId);
|
|
if (iter==dummyProtectedStrings.end())
|
|
{
|
|
shared_ptr<SharedStringProtectedDictionary> result(new SharedStringProtectedDictionary(isProtectedStringEnabled()));
|
|
dummyProtectedStrings[propertyId] = result;
|
|
return *result;
|
|
}
|
|
return *iter->second;
|
|
}
|
|
}
|
|
|
|
SharedStringDictionary& ClientReplicator::getSharedPropertyDictionaryById(unsigned int propertyId)
|
|
{
|
|
const Reflection::PropertyDescriptor* descriptor;
|
|
propDictionary.getValue(propertyId, descriptor);
|
|
if (descriptor)
|
|
{
|
|
return getSharedPropertyDictionary(*descriptor);
|
|
}
|
|
else
|
|
{
|
|
DummyPropertyStrings::iterator iter = dummyStrings.find(propertyId);
|
|
if (iter==dummyStrings.end())
|
|
{
|
|
shared_ptr<SharedStringDictionary> result(new SharedStringDictionary());
|
|
dummyStrings[propertyId] = result;
|
|
return *result;
|
|
}
|
|
return *iter->second;
|
|
}
|
|
}
|
|
|
|
SharedStringDictionary& ClientReplicator::getSharedEventDictionaryById(unsigned int eventId)
|
|
{
|
|
const Reflection::EventDescriptor* descriptor;
|
|
eventDictionary.getValue(eventId, descriptor);
|
|
if (descriptor)
|
|
{
|
|
return getSharedEventDictionary(*descriptor);
|
|
}
|
|
else
|
|
{
|
|
DummyEventStrings::iterator iter = dummyEventStrings.find(eventId);
|
|
if (iter==dummyEventStrings.end())
|
|
{
|
|
shared_ptr<SharedStringDictionary> result(new SharedStringDictionary());
|
|
dummyEventStrings[eventId] = result;
|
|
return *result;
|
|
}
|
|
return *iter->second;
|
|
}
|
|
}
|
|
|
|
SharedBinaryStringDictionary& ClientReplicator::getSharedPropertyBinaryDictionaryById(unsigned int propertyId)
|
|
{
|
|
const Reflection::PropertyDescriptor* descriptor;
|
|
propDictionary.getValue(propertyId, descriptor);
|
|
if (descriptor)
|
|
{
|
|
return getSharedPropertyBinaryDictionary(*descriptor);
|
|
}
|
|
else
|
|
{
|
|
DummyPropertyBinaryStrings::iterator iter = dummyBinaryStrings.find(propertyId);
|
|
if (iter==dummyBinaryStrings.end())
|
|
{
|
|
shared_ptr<SharedBinaryStringDictionary> result(new SharedBinaryStringDictionary());
|
|
dummyBinaryStrings[propertyId] = result;
|
|
return *result;
|
|
}
|
|
return *iter->second;
|
|
}
|
|
}
|
|
|
|
bool ClientReplicator::isProtectedStringEnabled()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
std::string ClientReplicator::encodeProtectedString(const ProtectedString& value, const Instance* instance, const Reflection::PropertyDescriptor& desc)
|
|
{
|
|
if (LuaVM::useSecureReplication())
|
|
return value.getBytecode();
|
|
else
|
|
return value.getSource();
|
|
}
|
|
|
|
boost::optional<ProtectedString> ClientReplicator::decodeProtectedString(const std::string& value, const Instance* instance, const Reflection::PropertyDescriptor& desc)
|
|
{
|
|
if (LuaVM::useSecureReplication())
|
|
return ProtectedString::fromBytecode(value);
|
|
else
|
|
return ProtectedString::fromTrustedSource(value);
|
|
}
|
|
|