Files
watrbx-game-engine/RobloxStudio/MobileDevelopmentDeployer.cpp
2025-09-18 17:55:52 -04:00

483 lines
13 KiB
C++

/**
* MobileDevelopmentDeployer.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include <QNetworkInterface>
#include <QNetworkProxy>
#include <QHostInfo>
#include <QSignalMapper>
#include <qmath.h>
#include "StringConv.h"
#include "Script/script.h"
#include "MobileDevelopmentDeployer.h"
#include "AuthoringSettings.h"
#include "G3D/Random.h"
#define PAIR_CODE_SIZE 4
#define RBX_DEV_PORT 1313
MobileDevelopmentDeployer::MobileDevelopmentDeployer(QWidget* pParent)
: QWidget(pParent),
onPairBehavior(ON_PAIR_DO_NOTHING)
{
tcpServer = new QTcpServer(this);
udpSocket = new QUdpSocket(this);
timer = new QTimer();
}
MobileDevelopmentDeployer::~MobileDevelopmentDeployer()
{
disconnectAllClients();
if(tcpServer)
{
tcpServer->close();
tcpServer->deleteLater();
}
}
void MobileDevelopmentDeployer::disconnectAllClients()
{
if (tcpClients.size() > 0)
{
for(std::vector<QTcpSocket*>::iterator iter = tcpClients.begin(); iter != tcpClients.end(); ++iter)
{
if(*iter)
{
(*iter)->write("!exit");
}
}
tcpClients.clear();
}
}
void MobileDevelopmentDeployer::disconnectClientsAndShutDown()
{
disconnectAllClients();
if (tcpServer)
{
tcpServer->close();
}
if (udpSocket)
{
udpSocket->close();
}
if (timer)
{
timer->stop();
}
}
MobileDevelopmentDeployer& MobileDevelopmentDeployer::singleton()
{
static MobileDevelopmentDeployer* developmentDeployer = new MobileDevelopmentDeployer();
return *developmentDeployer;
}
int MobileDevelopmentDeployer::generateNewPairCode()
{
int newPairCode = 0;
// make current unix time the seed (should be random enough)
G3D::Random generator(std::time(NULL));
for ( int i = 0; i < forcedPairCodeSize(); ++i)
{
newPairCode += generator.integer(1, 9) * pow(10, i);
}
AuthoringSettings::singleton().setDeviceDeploymentPairingCode(newPairCode);
RBX::GlobalAdvancedSettings::singleton()->saveState();
return newPairCode;
}
int MobileDevelopmentDeployer::getSavedPairCode()
{
return AuthoringSettings::singleton().deploymentPairingCode;
}
int MobileDevelopmentDeployer::getNumOfDigits(int numToCheck)
{
if (numToCheck <= 0)
return 0;
return qFloor(log10(numToCheck)) + 1;
}
int MobileDevelopmentDeployer::forcedPairCodeSize()
{
return PAIR_CODE_SIZE;
}
int MobileDevelopmentDeployer::getPairCode()
{
int savedPairCode = getSavedPairCode();
if ( getNumOfDigits(savedPairCode) <= 0 )
{
savedPairCode = generateNewPairCode();
}
RBXASSERT(getNumOfDigits(savedPairCode) == forcedPairCodeSize());
return savedPairCode;
}
const QString MobileDevelopmentDeployer::getPairCodeString()
{
int pairCodeInt = getPairCode();
QString codeString = QString::number(pairCodeInt);
if (codeString.size() == 3)
{
codeString.prepend('0');
}
RBXASSERT(codeString.size() == forcedPairCodeSize());
RBXASSERT(pairCodeInt == codeString.toInt());
return codeString;
}
bool MobileDevelopmentDeployer::isPairCode(const QString& codeString)
{
if (codeString.isNull() || codeString.count() != forcedPairCodeSize())
return false;
int codeInt = codeString.toInt();
int pairCodeInt = getPairCode();
return ( codeInt == pairCodeInt );
}
QString MobileDevelopmentDeployer::getLocalIPAddress()
{
QTcpSocket socket;
socket.connectToHost("8.8.8.8", 53); // google DNS, or something else reliable
if (socket.waitForConnected())
{
return socket.localAddress().toString();
}
return QString("");
}
void MobileDevelopmentDeployer::broadcastReadyDatagram()
{
QString broadcastString("RbxDevPairServer readyToPair");
broadcastDatagram(broadcastString.toAscii());
}
void MobileDevelopmentDeployer::broadcastDatagram(QByteArray dataToSend)
{
sendToAllBroadcast(dataToSend);
}
void MobileDevelopmentDeployer::sendToAllBroadcast(QByteArray packet)
{
// Get network interfaces list
QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces();
// Interfaces iteration
for (int i = 0; i < ifaces.size(); i++)
{
// Now get all IP addresses for the current interface
QList<QNetworkAddressEntry> addrs = ifaces[i].addressEntries();
// And for any IP address, if it is IPv4 and the interface is active, send the packet
for (int j = 0; j < addrs.size(); j++)
{
if ((addrs[j].ip().protocol() == QAbstractSocket::IPv4Protocol) && (addrs[j].broadcast().toString() != ""))
{
udpSocket->writeDatagram(packet.data(), packet.length(), addrs[j].broadcast(), RBX_DEV_PORT);
}
}
}
}
void MobileDevelopmentDeployer::pairWithRbxDevDevices()
{
onPairBehavior = ON_PAIR_DO_NOTHING;
listenForRbxDevDevices();
}
void MobileDevelopmentDeployer::startPlaySessionWithRbxDevDevices()
{
onPairBehavior = ON_PAIR_START_SERVER;
listenForRbxDevDevices();
}
void MobileDevelopmentDeployer::listenForRbxDevDevices()
{
if(udpSocket && !udpSocket->isOpen())
{
if (!udpSocket->bind(QHostAddress::Any,RBX_DEV_PORT,QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint))
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "PairServer did not bind");
connect(udpSocket, SIGNAL(readyRead()),this, SLOT(on_receivedUdpBroadcast()));
timer->setInterval(500);
connect(timer, SIGNAL(timeout()), this, SLOT(broadcastReadyDatagram()));
timer->start();
}
}
bool MobileDevelopmentDeployer::startTcpServer()
{
if(tcpServer && !tcpServer->isListening())
{
if( tcpServer->listen(QHostAddress(getLocalIPAddress()),1315) )
{
connect(tcpServer, SIGNAL(newConnection()), this, SLOT(on_newConnection()));
return true;
}
}
return false;
}
void MobileDevelopmentDeployer::on_newConnection()
{
QTcpSocket* tcpClient = tcpServer->nextPendingConnection();
if (!tcpClient)
{
return;
}
if (tcpClient->state() == QTcpSocket::ConnectedState)
{
connect( tcpClient, SIGNAL(disconnected()), this, SLOT(on_disconnected()) );
connect( tcpClient, SIGNAL(readyRead()), this, SLOT(on_readyRead()) );
tcpClients.push_back(tcpClient);
}
}
bool MobileDevelopmentDeployer::checkForClientPairCode(const QString& messageFromClient)
{
if (messageFromClient.contains(QString::fromStdString("RbxDevClient pairCode")))
{
QStringList list = messageFromClient.split(QRegExp("\\s+"));
if(list.count() == 3)
{
QString code = list.at(2);
if ( isPairCode(code) )
{
if (udpSocket)
{
QString broadcastString("RbxDevPairServer didPair true ip ");
broadcastString.append(getLocalIPAddress());
broadcastDatagram(broadcastString.toAscii());
}
}
else
{
if (udpSocket)
{
QString broadcastString("RbxDevPairServer didPair false ip ");
broadcastString.append("0.0.0.0");
broadcastDatagram(broadcastString.toAscii());
}
}
}
return true;
}
return false;
}
bool MobileDevelopmentDeployer::checkForClientPaired(const QString& messageFromClient, const QHostAddress& sender)
{
if (messageFromClient.contains(QString::fromStdString("RbxDevClient didPair")))
{
QStringList list = messageFromClient.split(QRegExp("\\s+"));
if (list.count() == 3)
{
QString didPair = list.at(2);
if (didPair == "true")
{
if (onPairBehavior == ON_PAIR_START_SERVER)
{
startTcpServer();
QString broadcastString("RbxDevPairServer didStartServer");
broadcastDatagram(broadcastString.toAscii());
}
QHostInfo info = QHostInfo::fromName(sender.toString());
Q_EMIT connectedToClient(info.hostName());
}
}
return true;
}
return false;
}
void MobileDevelopmentDeployer::on_receivedUdpBroadcast()
{
while (udpSocket && udpSocket->hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
udpSocket->readDatagram(datagram.data(), datagram.size(),
&sender, &senderPort);
QString message(datagram);
if ( checkForClientPairCode(message) )
return;
if ( checkForClientPaired(message,sender) )
return;
if (message.contains(QString::fromStdString("RbxDevClient didConnectToServer")))
{
// todo: maybe do something here? confirmation message or something?
}
}
}
static bool isHidden(const boost::filesystem::path &p)
{
boost::filesystem::path name = p.filename();
if(name != ".." &&
name != "." &&
name.c_str()[0] == '.')
{
return true;
}
return false;
}
void MobileDevelopmentDeployer::writeCoreScriptToFile(const std::string& filePath, const std::string& fileName, std::stringstream& stream)
{
std::ifstream in(RBX::utf8_decode(filePath).c_str(), std::ios::in | std::ios::binary);
if (in)
{
std::string relativeName = filePath.substr(RBX::BaseScript::adminScriptsPath.size() + 1);
relativeName = relativeName.substr(0,relativeName.size() - 4);
stream << RBX::format("RbxScriptName%sRbxEnd RbxScriptSource",relativeName.c_str());
stream << in.rdbuf() << "RbxEnd ";
}
}
bool MobileDevelopmentDeployer::writeCoreScriptsToStream(const boost::filesystem::path& dir_path, std::stringstream& stream)
{
if (!boost::filesystem::exists(dir_path))
{
return false;
}
boost::filesystem::directory_iterator end_itr;
for ( boost::filesystem::directory_iterator itr(dir_path); itr != end_itr; ++itr )
{
bool isHiddenDir = ::isHidden(itr->path());
if ( is_directory(itr->status()) && !isHiddenDir )
{
if ( writeCoreScriptsToStream( itr->path(), stream) )
return true;
}
else if ( !isHiddenDir && is_regular_file(itr->status()) )
{
const std::string fileName = itr->path().filename().string();
if (fileName.find(".lua") != std::string::npos)
{
const std::string filePath = itr->path().string();
writeCoreScriptToFile(filePath, fileName, stream);
}
}
}
return false;
}
void MobileDevelopmentDeployer::on_readyRead()
{
if (tcpClients.size() <= 0)
return;
QTcpSocket* tcpClient = qobject_cast<QTcpSocket*>(sender());
if (!tcpClient)
return;
QByteArray ba = tcpClient->readLine();
if(strcmp(ba.constData(), "!exit\n") == 0)
{
tcpClient->disconnectFromHost();
return;
}
if(strcmp(ba.constData(), "RbxReadyForPlay") == 0)
{
QString address = getLocalIPAddress();
tcpClient->write("RbxReadyForPlay 53640 " + address.toAscii());
return;
}
if(strcmp(ba.constData(), "RbxReadyForCoreScripts") == 0)
{
if (RBX::BaseScript::hasCoreScriptReplacements())
{
tcpClient->write("RbxReadyForCoreScripts true");
}
else
{
tcpClient->write("RbxReadyForCoreScripts false");
}
return;
}
if(strcmp(ba.constData(), "RbxSendCoreScripts") == 0)
{
if (RBX::BaseScript::hasCoreScriptReplacements())
{
tcpClient->write("RbxSendCoreScripts ");
std::stringstream ss;
boost::filesystem::path rootPath = boost::filesystem::path(RBX::BaseScript::adminScriptsPath);
writeCoreScriptsToStream(rootPath, ss);
ss << "RbxCoreScriptEnd";
std::string coreScriptsString = ss.str();
tcpClient->write(coreScriptsString.c_str(), coreScriptsString.size());
}
return;
}
QString attemptToPairCorrectResponse = QString("RbxAttemptToPair ").append(getPairCodeString());
if(strcmp(ba.constData(), attemptToPairCorrectResponse.toAscii()) == 0)
{
tcpClient->write("RbxAttemptToPair true");
}
}
void MobileDevelopmentDeployer::on_disconnected()
{
if (tcpClients.size() <= 0)
return;
QTcpSocket* tcpClient = qobject_cast<QTcpSocket*>(sender());
if (!tcpClient)
return;
QObject::disconnect(tcpClient, SIGNAL(disconnected()));
QObject::disconnect(tcpClient, SIGNAL(readyRead()));
if ( tcpClient )
{
std::vector<QTcpSocket*>::iterator iter = std::find(tcpClients.begin(), tcpClients.end(), tcpClient);
tcpClients.erase(iter);
tcpClient->disconnectFromHost();
tcpClient->deleteLater();
tcpClient = NULL;
}
}