mirror of
https://github.com/overte-org/overte.git
synced 2025-04-11 13:42:38 +02:00
merge with master and try to fix opacity mode stuff
This commit is contained in:
commit
1e100a672e
1614 changed files with 60652 additions and 9556 deletions
|
@ -71,9 +71,13 @@ RUN mkdir "$HIFI_BASE" && \
|
|||
mkdir "$HIFI_VCPKG_BASE" && \
|
||||
mkdir "$HIFI_ANDROID_PRECOMPILED"
|
||||
|
||||
RUN git clone https://github.com/jherico/hifi.git && \
|
||||
# Checkout a relatively recent commit from the main repository and use it to cache the
|
||||
# gradle and vcpkg dependencies
|
||||
# This commit ID should be updated whenever someone changes the dependency list
|
||||
# in cmake/ports
|
||||
RUN git clone https://github.com/highfidelity/hifi.git && \
|
||||
cd ~/hifi && \
|
||||
git checkout quest/build
|
||||
git checkout 796bfb5d6715ff14c2e60f3ee8fac1465b7578c6
|
||||
|
||||
WORKDIR /home/jenkins/hifi
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ Agent::Agent(ReceivedMessage& message) :
|
|||
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
||||
|
||||
DependencyManager::set<ResourceManager>();
|
||||
DependencyManager::set<PluginManager>();
|
||||
DependencyManager::set<PluginManager>()->instantiate();
|
||||
|
||||
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
|
||||
|
||||
|
@ -433,7 +433,7 @@ void Agent::executeScript() {
|
|||
|
||||
using namespace recording;
|
||||
static const FrameType AUDIO_FRAME_TYPE = Frame::registerFrameType(AudioConstants::getAudioFrameName());
|
||||
Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [this, &scriptedAvatar](Frame::ConstPointer frame) {
|
||||
Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [this, &player, &scriptedAvatar](Frame::ConstPointer frame) {
|
||||
if (_shouldMuteRecordingAudio) {
|
||||
return;
|
||||
}
|
||||
|
@ -442,9 +442,18 @@ void Agent::executeScript() {
|
|||
|
||||
QByteArray audio(frame->data);
|
||||
|
||||
int16_t* samples = reinterpret_cast<int16_t*>(audio.data());
|
||||
int numSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL;
|
||||
|
||||
auto volume = player->getVolume();
|
||||
if (volume >= 0.0f && volume < 1.0f) {
|
||||
int32_t fract = (int32_t)(volume * (float)(1 << 16)); // Q16
|
||||
for (int i = 0; i < numSamples; i++) {
|
||||
samples[i] = (fract * (int32_t)samples[i]) >> 16;
|
||||
}
|
||||
}
|
||||
|
||||
if (_isNoiseGateEnabled) {
|
||||
int16_t* samples = reinterpret_cast<int16_t*>(audio.data());
|
||||
int numSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL;
|
||||
_audioGate.render(samples, samples, numSamples);
|
||||
}
|
||||
|
||||
|
@ -511,6 +520,7 @@ void Agent::executeScript() {
|
|||
|
||||
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
|
||||
|
||||
DependencyManager::get<ScriptEngines>()->runScriptInitializers(_scriptEngine);
|
||||
_scriptEngine->run();
|
||||
|
||||
Frame::clearFrameHandler(AUDIO_FRAME_TYPE);
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <AccountManager.h>
|
||||
#include <AddressManager.h>
|
||||
#include <Assignment.h>
|
||||
#include <CrashAnnotations.h>
|
||||
#include <LogHandler.h>
|
||||
#include <LogUtils.h>
|
||||
#include <LimitedNodeList.h>
|
||||
|
@ -81,6 +82,9 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
|
|||
}
|
||||
|
||||
_assignmentServerSocket = HifiSockAddr(_assignmentServerHostname, assignmentServerPort, true);
|
||||
if (_assignmentServerSocket.isNull()) {
|
||||
qCCritical(assignment_client) << "PAGE: Couldn't resolve domain server address" << _assignmentServerHostname;
|
||||
}
|
||||
_assignmentServerSocket.setObjectName("AssignmentServer");
|
||||
nodeList->setAssignmentServerSocket(_assignmentServerSocket);
|
||||
|
||||
|
@ -144,6 +148,7 @@ AssignmentClient::~AssignmentClient() {
|
|||
}
|
||||
|
||||
void AssignmentClient::aboutToQuit() {
|
||||
crash::annotations::setShutdownState(true);
|
||||
stopAssignmentClient();
|
||||
}
|
||||
|
||||
|
@ -173,6 +178,7 @@ void AssignmentClient::sendStatusPacketToACM() {
|
|||
|
||||
void AssignmentClient::sendAssignmentRequest() {
|
||||
if (!_currentAssignment && !_isAssigned) {
|
||||
crash::annotations::setShutdownState(false);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
|
@ -180,16 +186,21 @@ void AssignmentClient::sendAssignmentRequest() {
|
|||
// we want to check again for the local domain-server port in case the DS has restarted
|
||||
quint16 localAssignmentServerPort;
|
||||
if (nodeList->getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, localAssignmentServerPort)) {
|
||||
if (localAssignmentServerPort != _assignmentServerSocket.getPort()) {
|
||||
qCDebug(assignment_client) << "Port for local assignment server read from shared memory is"
|
||||
<< localAssignmentServerPort;
|
||||
if (localAssignmentServerPort == 0) {
|
||||
qCWarning(assignment_client) << "ALERT: Server port from shared memory is 0";
|
||||
} else {
|
||||
if (localAssignmentServerPort != _assignmentServerSocket.getPort()) {
|
||||
qCDebug(assignment_client) << "Port for local assignment server read from shared memory is"
|
||||
<< localAssignmentServerPort;
|
||||
|
||||
_assignmentServerSocket.setPort(localAssignmentServerPort);
|
||||
nodeList->setAssignmentServerSocket(_assignmentServerSocket);
|
||||
_assignmentServerSocket.setPort(localAssignmentServerPort);
|
||||
nodeList->setAssignmentServerSocket(_assignmentServerSocket);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qCWarning(assignment_client) << "Failed to read local assignment server port from shared memory"
|
||||
<< "- will send assignment request to previous assignment server socket.";
|
||||
qCWarning(assignment_client) << "ALERT: Failed to read local assignment server port from shared memory ("
|
||||
<< DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY
|
||||
<< ")- will send assignment request to previous assignment server socket.";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,7 +258,7 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer<ReceivedMessa
|
|||
// Starts an event loop, and emits workerThread->started()
|
||||
workerThread->start();
|
||||
} else {
|
||||
qCWarning(assignment_client) << "Received an assignment that could not be unpacked. Re-requesting.";
|
||||
qCWarning(assignment_client) << "ALERT: Received an assignment that could not be unpacked. Re-requesting.";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,6 +300,8 @@ void AssignmentClient::handleAuthenticationRequest() {
|
|||
}
|
||||
|
||||
void AssignmentClient::assignmentCompleted() {
|
||||
crash::annotations::setShutdownState(true);
|
||||
|
||||
// we expect that to be here the previous assignment has completely cleaned up
|
||||
assert(_currentAssignment.isNull());
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <HifiConfigVariantMap.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <ShutdownEventListener.h>
|
||||
#include <shared/ScriptInitializerMixin.h>
|
||||
|
||||
#include "Assignment.h"
|
||||
#include "AssignmentClient.h"
|
||||
|
@ -239,7 +240,11 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
|
|||
|
||||
QThread::currentThread()->setObjectName("main thread");
|
||||
|
||||
LogHandler::getInstance().moveToThread(thread());
|
||||
LogHandler::getInstance().setupRepeatedMessageFlusher();
|
||||
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
DependencyManager::set<ScriptInitializers>();
|
||||
|
||||
if (numForks || minForks || maxForks) {
|
||||
AssignmentClientMonitor* monitor = new AssignmentClientMonitor(numForks, minForks, maxForks,
|
||||
|
|
|
@ -33,7 +33,7 @@ const QString ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME = "assignment-client-monitor
|
|||
const int WAIT_FOR_CHILD_MSECS = 1000;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
HANDLE PROCESS_GROUP = createProcessGroup();
|
||||
void* PROCESS_GROUP = createProcessGroup();
|
||||
#endif
|
||||
|
||||
AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmentClientForks,
|
||||
|
|
|
@ -24,7 +24,9 @@
|
|||
ThreadedAssignment* AssignmentFactory::unpackAssignment(ReceivedMessage& message) {
|
||||
|
||||
quint8 packedType;
|
||||
message.peekPrimitive(&packedType);
|
||||
if (message.peekPrimitive(&packedType) != sizeof(packedType)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Assignment::Type unpackedType = (Assignment::Type) packedType;
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
|||
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket");
|
||||
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
|
||||
this, "handleOctreePacket");
|
||||
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnership");
|
||||
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "queueIncomingPacket");
|
||||
|
||||
packetReceiver.registerListenerForTypes({
|
||||
PacketType::ReplicatedAvatarIdentity,
|
||||
|
@ -499,6 +499,8 @@ void AvatarMixer::handleAvatarKilled(SharedNodePointer avatarNode) {
|
|||
} else {
|
||||
_sessionDisplayNames.erase(displayNameIter);
|
||||
}
|
||||
|
||||
nodeData->getAvatar().stopChallengeTimer();
|
||||
}
|
||||
|
||||
std::unique_ptr<NLPacket> killPacket;
|
||||
|
@ -1136,16 +1138,6 @@ void AvatarMixer::entityChange() {
|
|||
_dirtyHeroStatus = true;
|
||||
}
|
||||
|
||||
void AvatarMixer::handleChallengeOwnership(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
if (senderNode->getType() == NodeType::Agent && senderNode->getLinkedData()) {
|
||||
auto clientData = static_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
|
||||
auto avatar = clientData->getAvatarSharedPointer();
|
||||
if (avatar) {
|
||||
avatar->handleChallengeResponse(message.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::aboutToFinish() {
|
||||
DependencyManager::destroy<ResourceManager>();
|
||||
DependencyManager::destroy<ResourceCacheSharedItems>();
|
||||
|
|
|
@ -65,7 +65,6 @@ private slots:
|
|||
void domainSettingsRequestComplete();
|
||||
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
|
||||
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleChallengeOwnership(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void start();
|
||||
|
||||
private:
|
||||
|
|
|
@ -74,6 +74,9 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData
|
|||
case PacketType::BulkAvatarTraitsAck:
|
||||
processBulkAvatarTraitsAckMessage(*packet);
|
||||
break;
|
||||
case PacketType::ChallengeOwnership:
|
||||
_avatar->processChallengeResponse(*packet);
|
||||
break;
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
|
|
@ -265,7 +265,7 @@ static const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45;
|
|||
void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
|
||||
quint64 start = usecTimestampNow();
|
||||
|
||||
if (node->getType() == NodeType::Agent && node->getLinkedData() && node->getActiveSocket() && !node->isUpstream()) {
|
||||
if ((node->getType() == NodeType::Agent || node->getType() == NodeType::EntityScriptServer) && node->getLinkedData() && node->getActiveSocket() && !node->isUpstream()) {
|
||||
broadcastAvatarDataToAgent(node);
|
||||
} else if (node->getType() == NodeType::DownstreamAvatarMixer) {
|
||||
broadcastAvatarDataToDownstreamMixer(node);
|
||||
|
@ -448,13 +448,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
// or that somehow we haven't sent
|
||||
if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) {
|
||||
++numAvatarsHeldBack;
|
||||
|
||||
// BUGZ-781 verbose debugging:
|
||||
auto usecLastTimeSent = destinationNodeData->getLastOtherAvatarEncodeTime(sourceAvatarNodeData->getNodeLocalID());
|
||||
if (usecLastTimeSent != 0 && startIgnoreCalculation - usecLastTimeSent > 10 * USECS_PER_SECOND) {
|
||||
qCDebug(avatars) << "Not sent avatar" << *sourceAvatarNode << "to Node" << *destinationNode << "in > 10 s";
|
||||
}
|
||||
|
||||
sendAvatar = false;
|
||||
} else if (lastSeqFromSender == 0) {
|
||||
// We have have not yet received any data about this avatar. Ignore it for now
|
||||
|
|
|
@ -27,13 +27,33 @@
|
|||
#include "ClientTraitsHandler.h"
|
||||
#include "AvatarLogging.h"
|
||||
|
||||
MixerAvatar::~MixerAvatar() {
|
||||
if (_challengeTimeout) {
|
||||
_challengeTimeout->deleteLater();
|
||||
}
|
||||
MixerAvatar::MixerAvatar() {
|
||||
static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s
|
||||
|
||||
_challengeTimer.setSingleShot(true);
|
||||
_challengeTimer.setInterval(CHALLENGE_TIMEOUT_MS);
|
||||
|
||||
_challengeTimer.callOnTimeout(this, [this]() {
|
||||
if (_verifyState == challengeClient) {
|
||||
_pendingEvent = false;
|
||||
_verifyState = verificationFailed;
|
||||
_needsIdentityUpdate = true;
|
||||
qCDebug(avatars) << "Dynamic verification TIMED-OUT for" << getDisplayName() << getSessionUUID();
|
||||
} else {
|
||||
qCDebug(avatars) << "Ignoring timeout of avatar challenge";
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const char* MixerAvatar::stateToName(VerifyState state) {
|
||||
return QMetaEnum::fromType<VerifyState>().valueToKey(state);
|
||||
}
|
||||
|
||||
void MixerAvatar::fetchAvatarFST() {
|
||||
if (_verifyState >= requestingFST && _verifyState <= challengeClient) {
|
||||
qCDebug(avatars) << "WARNING: Avatar verification restarted; old state:" << stateToName(_verifyState);
|
||||
}
|
||||
_verifyState = nonCertified;
|
||||
|
||||
_pendingEvent = false;
|
||||
|
@ -80,7 +100,7 @@ void MixerAvatar::fetchAvatarFST() {
|
|||
void MixerAvatar::fstRequestComplete() {
|
||||
ResourceRequest* fstRequest = static_cast<ResourceRequest*>(QObject::sender());
|
||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||
if (fstRequest == _avatarRequest) {
|
||||
if (_verifyState == requestingFST && fstRequest == _avatarRequest) {
|
||||
auto result = fstRequest->getResult();
|
||||
if (result != ResourceRequest::Success) {
|
||||
_verifyState = error;
|
||||
|
@ -90,11 +110,11 @@ void MixerAvatar::fstRequestComplete() {
|
|||
_verifyState = receivedFST;
|
||||
_pendingEvent = true;
|
||||
}
|
||||
_avatarRequest->deleteLater();
|
||||
_avatarRequest = nullptr;
|
||||
} else {
|
||||
qCDebug(avatars) << "Incorrect request for" << getDisplayName();
|
||||
qCDebug(avatars) << "Incorrect or outdated FST request for" << getDisplayName();
|
||||
}
|
||||
fstRequest->deleteLater();
|
||||
}
|
||||
|
||||
bool MixerAvatar::generateFSTHash() {
|
||||
|
@ -108,7 +128,7 @@ bool MixerAvatar::generateFSTHash() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool MixerAvatar::validateFSTHash(const QString& publicKey) {
|
||||
bool MixerAvatar::validateFSTHash(const QString& publicKey) const {
|
||||
// Guess we should refactor this stuff into a Authorization namespace ...
|
||||
return EntityItemProperties::verifySignature(publicKey, _certificateHash,
|
||||
QByteArray::fromBase64(_certificateIdFromFST.toUtf8()));
|
||||
|
@ -171,7 +191,9 @@ void MixerAvatar::ownerRequestComplete() {
|
|||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||
QNetworkReply* networkReply = static_cast<QNetworkReply*>(QObject::sender());
|
||||
|
||||
if (networkReply->error() == QNetworkReply::NoError) {
|
||||
if (_verifyState != requestingOwner) {
|
||||
qCDebug(avatars) << "WARNING: outdated avatar-owner information received in state" << stateToName(_verifyState);
|
||||
} else if (networkReply->error() == QNetworkReply::NoError) {
|
||||
_dynamicMarketResponse = networkReply->readAll();
|
||||
_verifyState = ownerResponse;
|
||||
_pendingEvent = true;
|
||||
|
@ -259,60 +281,27 @@ void MixerAvatar::processCertifyEvents() {
|
|||
}
|
||||
sendOwnerChallenge();
|
||||
_verifyState = challengeClient;
|
||||
_pendingEvent = true;
|
||||
} else {
|
||||
_verifyState = error;
|
||||
qCDebug(avatars) << "Get owner status - couldn't parse response for" << getSessionUUID()
|
||||
<< ":" << _dynamicMarketResponse;
|
||||
}
|
||||
} else {
|
||||
qCDebug(avatars) << "Get owner status failed for " << getDisplayName() << _marketplaceIdFromURL <<
|
||||
qCDebug(avatars) << "Get owner status failed for" << getDisplayName() << _marketplaceIdFromURL <<
|
||||
"message:" << responseJson["message"].toString();
|
||||
_verifyState = error;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case challengeResponse:
|
||||
{
|
||||
if (_challengeResponse.length() < 8) {
|
||||
_verifyState = error;
|
||||
_pendingEvent = false;
|
||||
break;
|
||||
}
|
||||
|
||||
int avatarIDLength;
|
||||
int signedNonceLength;
|
||||
{
|
||||
QDataStream responseStream(_challengeResponse);
|
||||
responseStream.setByteOrder(QDataStream::LittleEndian);
|
||||
responseStream >> avatarIDLength >> signedNonceLength;
|
||||
}
|
||||
QByteArray avatarID(_challengeResponse.data() + 2 * sizeof(int), avatarIDLength);
|
||||
QByteArray signedNonce(_challengeResponse.data() + 2 * sizeof(int) + avatarIDLength, signedNonceLength);
|
||||
|
||||
bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash,
|
||||
QByteArray::fromBase64(signedNonce));
|
||||
_verifyState = challengeResult ? verificationSucceeded : verificationFailed;
|
||||
_needsIdentityUpdate = true;
|
||||
if (_verifyState == verificationFailed) {
|
||||
qCDebug(avatars) << "Dynamic verification FAILED for " << getDisplayName() << getSessionUUID();
|
||||
} else {
|
||||
qCDebug(avatars) << "Dynamic verification SUCCEEDED for " << getDisplayName() << getSessionUUID();
|
||||
}
|
||||
_pendingEvent = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case requestingOwner:
|
||||
case challengeClient:
|
||||
{ // Qt networking done on this thread:
|
||||
QCoreApplication::processEvents();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
qCDebug(avatars) << "Unexpected verify state" << _verifyState;
|
||||
qCDebug(avatars) << "Unexpected verify state" << stateToName(_verifyState);
|
||||
break;
|
||||
|
||||
} // close switch
|
||||
|
@ -334,32 +323,53 @@ void MixerAvatar::sendOwnerChallenge() {
|
|||
QCryptographicHash nonceHash(QCryptographicHash::Sha256);
|
||||
nonceHash.addData(nonce);
|
||||
_challengeNonceHash = nonceHash.result();
|
||||
|
||||
static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s
|
||||
if (_challengeTimeout) {
|
||||
_challengeTimeout->deleteLater();
|
||||
}
|
||||
_challengeTimeout = new QTimer();
|
||||
_challengeTimeout->setInterval(CHALLENGE_TIMEOUT_MS);
|
||||
_challengeTimeout->setSingleShot(true);
|
||||
_challengeTimeout->connect(_challengeTimeout, &QTimer::timeout, this, [this]() {
|
||||
if (_verifyState == challengeClient) {
|
||||
_pendingEvent = false;
|
||||
_verifyState = verificationFailed;
|
||||
_needsIdentityUpdate = true;
|
||||
qCDebug(avatars) << "Dynamic verification TIMED-OUT for " << getDisplayName() << getSessionUUID();
|
||||
}
|
||||
});
|
||||
_challengeTimeout->start();
|
||||
_pendingEvent = false;
|
||||
|
||||
// QTimer::start is a set of overloaded functions.
|
||||
QMetaObject::invokeMethod(&_challengeTimer, static_cast<void(QTimer::*)()>(&QTimer::start));
|
||||
}
|
||||
|
||||
void MixerAvatar::handleChallengeResponse(ReceivedMessage* response) {
|
||||
void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
|
||||
QByteArray avatarID;
|
||||
QByteArray encryptedNonce;
|
||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||
stopChallengeTimer();
|
||||
if (_verifyState == challengeClient) {
|
||||
_challengeResponse = response->readAll();
|
||||
_verifyState = challengeResponse;
|
||||
_pendingEvent = true;
|
||||
QByteArray responseData = response.readAll();
|
||||
if (responseData.length() < 8) {
|
||||
_verifyState = error;
|
||||
qCDebug(avatars) << "Avatar challenge response packet too small, length:" << responseData.length();
|
||||
return;
|
||||
}
|
||||
|
||||
int avatarIDLength;
|
||||
int signedNonceLength;
|
||||
{
|
||||
QDataStream responseStream(responseData);
|
||||
responseStream.setByteOrder(QDataStream::LittleEndian);
|
||||
responseStream >> avatarIDLength >> signedNonceLength;
|
||||
}
|
||||
QByteArray avatarID(responseData.data() + 2 * sizeof(int), avatarIDLength);
|
||||
QByteArray signedNonce(responseData.data() + 2 * sizeof(int) + avatarIDLength, signedNonceLength);
|
||||
|
||||
bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash,
|
||||
QByteArray::fromBase64(signedNonce));
|
||||
_verifyState = challengeResult ? verificationSucceeded : verificationFailed;
|
||||
_needsIdentityUpdate = true;
|
||||
if (_verifyState == verificationFailed) {
|
||||
qCDebug(avatars) << "Dynamic verification FAILED for" << getDisplayName() << getSessionUUID();
|
||||
} else {
|
||||
qCDebug(avatars) << "Dynamic verification SUCCEEDED for" << getDisplayName() << getSessionUUID();
|
||||
}
|
||||
|
||||
} else {
|
||||
qCDebug(avatars) << "WARNING: Unexpected avatar challenge-response in state" << stateToName(_verifyState);
|
||||
}
|
||||
}
|
||||
|
||||
void MixerAvatar::stopChallengeTimer() {
|
||||
if (QThread::currentThread() == thread()) {
|
||||
_challengeTimer.stop();
|
||||
} else {
|
||||
QMetaObject::invokeMethod(&_challengeTimer, &QTimer::stop);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
// Avatar class for use within the avatar mixer - encapsulates data required only for
|
||||
// sorting priorities within the mixer.
|
||||
// Avatar class for use within the avatar mixer - includes avatar-verification state.
|
||||
|
||||
#ifndef hifi_MixerAvatar_h
|
||||
#define hifi_MixerAvatar_h
|
||||
|
@ -20,8 +19,10 @@
|
|||
class ResourceRequest;
|
||||
|
||||
class MixerAvatar : public AvatarData {
|
||||
Q_OBJECT
|
||||
public:
|
||||
~MixerAvatar();
|
||||
MixerAvatar();
|
||||
|
||||
bool getNeedsHeroCheck() const { return _needsHeroCheck; }
|
||||
void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; }
|
||||
|
||||
|
@ -31,15 +32,20 @@ public:
|
|||
void setNeedsIdentityUpdate(bool value = true) { _needsIdentityUpdate = value; }
|
||||
|
||||
void processCertifyEvents();
|
||||
void handleChallengeResponse(ReceivedMessage* response);
|
||||
void processChallengeResponse(ReceivedMessage& response);
|
||||
|
||||
void stopChallengeTimer();
|
||||
|
||||
// Avatar certification/verification:
|
||||
enum VerifyState {
|
||||
nonCertified, requestingFST, receivedFST, staticValidation, requestingOwner, ownerResponse,
|
||||
challengeClient, verified, verificationFailed, verificationSucceeded, error
|
||||
};
|
||||
Q_ENUM(VerifyState)
|
||||
|
||||
private:
|
||||
bool _needsHeroCheck { false };
|
||||
|
||||
// Avatar certification/verification:
|
||||
enum VerifyState { nonCertified, requestingFST, receivedFST, staticValidation, requestingOwner, ownerResponse,
|
||||
challengeClient, challengeResponse, verified, verificationFailed, verificationSucceeded, error };
|
||||
Q_ENUM(VerifyState);
|
||||
static const char* stateToName(VerifyState state);
|
||||
VerifyState _verifyState { nonCertified };
|
||||
std::atomic<bool> _pendingEvent { false };
|
||||
QMutex _avatarCertifyLock;
|
||||
|
@ -53,12 +59,11 @@ private:
|
|||
QString _dynamicMarketResponse;
|
||||
QString _ownerPublicKey;
|
||||
QByteArray _challengeNonceHash;
|
||||
QByteArray _challengeResponse;
|
||||
QTimer* _challengeTimeout { nullptr };
|
||||
QTimer _challengeTimer;
|
||||
bool _needsIdentityUpdate { false };
|
||||
|
||||
bool generateFSTHash();
|
||||
bool validateFSTHash(const QString& publicKey);
|
||||
bool validateFSTHash(const QString& publicKey) const;
|
||||
QByteArray canonicalJson(const QString fstFile);
|
||||
void sendOwnerChallenge();
|
||||
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
#include <QJsonDocument>
|
||||
|
||||
#include <EntityTree.h>
|
||||
#include <SimpleEntitySimulation.h>
|
||||
#include <ResourceCache.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <plugins/PluginManager.h>
|
||||
#include <EntityEditFilters.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <hfm/ModelFormatRegistry.h>
|
||||
|
@ -36,12 +36,13 @@ const char* LOCAL_MODELS_PERSIST_FILE = "resources/models.svo";
|
|||
|
||||
EntityServer::EntityServer(ReceivedMessage& message) :
|
||||
OctreeServer(message),
|
||||
_entitySimulation(NULL),
|
||||
_entitySimulation(nullptr),
|
||||
_dynamicDomainVerificationTimer(this)
|
||||
{
|
||||
DependencyManager::set<ResourceManager>();
|
||||
DependencyManager::set<ResourceCacheSharedItems>();
|
||||
DependencyManager::set<ScriptCache>();
|
||||
DependencyManager::set<PluginManager>()->instantiate();
|
||||
|
||||
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
|
||||
DependencyManager::set<AssignmentDynamicFactory>();
|
||||
|
|
|
@ -16,9 +16,11 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include "EntityItem.h"
|
||||
#include <EntityItem.h>
|
||||
#include <EntityTree.h>
|
||||
#include <SimpleEntitySimulation.h>
|
||||
|
||||
#include "EntityServerConsts.h"
|
||||
#include "EntityTree.h"
|
||||
|
||||
/// Handles assignments of type EntityServer - sending entities to various clients.
|
||||
|
||||
|
@ -27,9 +29,6 @@ struct ViewerSendingStats {
|
|||
quint64 lastEdited;
|
||||
};
|
||||
|
||||
class SimpleEntitySimulation;
|
||||
using SimpleEntitySimulationPointer = std::shared_ptr<SimpleEntitySimulation>;
|
||||
|
||||
class EntityServer : public OctreeServer, public NewlyCreatedEntityHook {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
class EntitySimulation;
|
||||
|
||||
/**jsdoc
|
||||
* The <code>EntityViewer</code> API provides a headless viewer for assignment client scripts, so that they can "see" entities
|
||||
* in order for them to be available in the {@link Entities} API.
|
||||
*
|
||||
* @namespace EntityViewer
|
||||
*
|
||||
* @hifi-assignment-client
|
||||
|
|
|
@ -28,6 +28,7 @@ public:
|
|||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* Updates the entities currently in view.
|
||||
* @function EntityViewer.queryOctree
|
||||
*/
|
||||
void queryOctree();
|
||||
|
@ -36,26 +37,30 @@ public slots:
|
|||
// setters for camera attributes
|
||||
|
||||
/**jsdoc
|
||||
* Sets the position of the view frustum.
|
||||
* @function EntityViewer.setPosition
|
||||
* @param {Vec3} position
|
||||
* @param {Vec3} position - The position of the view frustum.
|
||||
*/
|
||||
void setPosition(const glm::vec3& position) { _hasViewFrustum = true; _viewFrustum.setPosition(position); }
|
||||
|
||||
/**jsdoc
|
||||
* Sets the orientation of the view frustum.
|
||||
* @function EntityViewer.setOrientation
|
||||
* @param {Quat} orientation
|
||||
* @param {Quat} orientation - The orientation of the view frustum.
|
||||
*/
|
||||
void setOrientation(const glm::quat& orientation) { _hasViewFrustum = true; _viewFrustum.setOrientation(orientation); }
|
||||
|
||||
/**jsdoc
|
||||
* Sets the radius of the center "keyhole" in the view frustum.
|
||||
* @function EntityViewer.setCenterRadius
|
||||
* @param {number} radius
|
||||
* @param {number} radius - The radius of the center "keyhole" in the view frustum.
|
||||
*/
|
||||
void setCenterRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); }
|
||||
|
||||
/**jsdoc
|
||||
* Sets the radius of the center "keyhole" in the view frustum.
|
||||
* @function EntityViewer.setKeyholeRadius
|
||||
* @param {number} radius
|
||||
* @param {number} radius - The radius of the center "keyhole" in the view frustum.
|
||||
* @deprecated This function is deprecated and will be removed. Use {@link EntityViewer.setCenterRadius|setCenterRadius}
|
||||
* instead.
|
||||
*/
|
||||
|
@ -66,33 +71,38 @@ public slots:
|
|||
|
||||
/**jsdoc
|
||||
* @function EntityViewer.setVoxelSizeScale
|
||||
* @param {number} sizeScale
|
||||
* @param {number} sizeScale - The voxel size scale.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
void setVoxelSizeScale(float sizeScale) { _octreeQuery.setOctreeSizeScale(sizeScale) ; }
|
||||
|
||||
/**jsdoc
|
||||
* @function EntityViewer.setBoundaryLevelAdjust
|
||||
* @param {number} boundaryLevelAdjust
|
||||
* @param {number} boundaryLevelAdjust - The boundary level adjust factor.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _octreeQuery.setBoundaryLevelAdjust(boundaryLevelAdjust); }
|
||||
|
||||
/**jsdoc
|
||||
* Sets the maximum number of entity packets to receive from the domain server per second.
|
||||
* @function EntityViewer.setMaxPacketsPerSecond
|
||||
* @param {number} maxPacketsPerSecond
|
||||
* @param {number} maxPacketsPerSecond - The maximum number of entity packets to receive per second.
|
||||
*/
|
||||
void setMaxPacketsPerSecond(int maxPacketsPerSecond) { _octreeQuery.setMaxQueryPacketsPerSecond(maxPacketsPerSecond); }
|
||||
|
||||
// getters for camera attributes
|
||||
|
||||
/**jsdoc
|
||||
* Gets the position of the view frustum.
|
||||
* @function EntityViewer.getPosition
|
||||
* @returns {Vec3}
|
||||
* @returns {Vec3} The position of the view frustum.
|
||||
*/
|
||||
const glm::vec3& getPosition() const { return _viewFrustum.getPosition(); }
|
||||
|
||||
/**jsdoc
|
||||
* Gets the orientation of the view frustum.
|
||||
* @function EntityViewer.getOrientation
|
||||
* @returns {Quat}
|
||||
* @returns {Quat} The orientation of the view frustum.
|
||||
*/
|
||||
const glm::quat& getOrientation() const { return _viewFrustum.getOrientation(); }
|
||||
|
||||
|
@ -101,26 +111,30 @@ public slots:
|
|||
|
||||
/**jsdoc
|
||||
* @function EntityViewer.getVoxelSizeScale
|
||||
* @returns {number}
|
||||
* @returns {number} The voxel size scale.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
float getVoxelSizeScale() const { return _octreeQuery.getOctreeSizeScale(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function EntityViewer.getBoundaryLevelAdjust
|
||||
* @returns {number}
|
||||
* @returns {number} The boundary level adjust factor.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
int getBoundaryLevelAdjust() const { return _octreeQuery.getBoundaryLevelAdjust(); }
|
||||
|
||||
/**jsdoc
|
||||
* Gets the maximum number of entity packets to receive from the domain server per second.
|
||||
* @function EntityViewer.getMaxPacketsPerSecond
|
||||
* @returns {number}
|
||||
* @returns {number} The maximum number of entity packets to receive per second.
|
||||
*/
|
||||
int getMaxPacketsPerSecond() const { return _octreeQuery.getMaxQueryPacketsPerSecond(); }
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Gets the number of nodes in the octree.
|
||||
* @function EntityViewer.getOctreeElementsCount
|
||||
* @returns {number}
|
||||
* @returns {number} The number of nodes in the octree.
|
||||
*/
|
||||
unsigned getOctreeElementsCount() const { return _tree->getOctreeElementsCount(); }
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig
|
|||
DependencyManager::set<ResourceScriptingInterface>();
|
||||
|
||||
DependencyManager::set<ResourceManager>();
|
||||
DependencyManager::set<PluginManager>();
|
||||
DependencyManager::set<PluginManager>()->instantiate();
|
||||
|
||||
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
|
||||
|
||||
|
@ -86,8 +86,6 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig
|
|||
this, "handleOctreePacket");
|
||||
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
|
||||
|
||||
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
||||
|
||||
packetReceiver.registerListener(PacketType::ReloadEntityServerScript, this, "handleReloadEntityServerScriptPacket");
|
||||
packetReceiver.registerListener(PacketType::EntityScriptGetStatus, this, "handleEntityScriptGetStatusPacket");
|
||||
packetReceiver.registerListener(PacketType::EntityServerScriptLog, this, "handleEntityServerScriptLogPacket");
|
||||
|
@ -255,6 +253,7 @@ void EntityScriptServer::handleEntityScriptCallMethodPacket(QSharedPointer<Recei
|
|||
void EntityScriptServer::run() {
|
||||
DependencyManager::set<ScriptEngines>(ScriptEngine::ENTITY_SERVER_SCRIPT);
|
||||
DependencyManager::set<EntityScriptServerServices>();
|
||||
DependencyManager::set<AvatarHashMap>();
|
||||
|
||||
// make sure we request our script once the agent connects to the domain
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -301,10 +300,17 @@ void EntityScriptServer::run() {
|
|||
|
||||
entityScriptingInterface->setEntityTree(_entityViewer.getTree());
|
||||
|
||||
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
|
||||
auto treePtr = _entityViewer.getTree();
|
||||
DependencyManager::set<AssignmentParentFinder>(treePtr);
|
||||
|
||||
if (!_entitySimulation) {
|
||||
SimpleEntitySimulationPointer simpleSimulation { new SimpleEntitySimulation() };
|
||||
simpleSimulation->setEntityTree(treePtr);
|
||||
treePtr->setSimulation(simpleSimulation);
|
||||
_entitySimulation = simpleSimulation;
|
||||
}
|
||||
|
||||
auto tree = _entityViewer.getTree().get();
|
||||
auto tree = treePtr.get();
|
||||
connect(tree, &EntityTree::deletingEntity, this, &EntityScriptServer::deletingEntity, Qt::QueuedConnection);
|
||||
connect(tree, &EntityTree::addingEntity, this, &EntityScriptServer::addingEntity, Qt::QueuedConnection);
|
||||
connect(tree, &EntityTree::entityServerScriptChanging, this, &EntityScriptServer::entityServerScriptChanging, Qt::QueuedConnection);
|
||||
|
@ -441,6 +447,7 @@ void EntityScriptServer::resetEntitiesScriptEngine() {
|
|||
newEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue);
|
||||
|
||||
newEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
|
||||
newEngine->registerGlobalObject("AvatarList", DependencyManager::get<AvatarHashMap>().data());
|
||||
|
||||
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
|
||||
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
|
||||
|
@ -451,10 +458,11 @@ void EntityScriptServer::resetEntitiesScriptEngine() {
|
|||
|
||||
connect(newEngine.data(), &ScriptEngine::update, this, [this] {
|
||||
_entityViewer.queryOctree();
|
||||
_entityViewer.getTree()->preUpdate();
|
||||
_entityViewer.getTree()->update();
|
||||
});
|
||||
|
||||
|
||||
scriptEngines->runScriptInitializers(newEngine);
|
||||
newEngine->runInThread();
|
||||
auto newEngineSP = qSharedPointerCast<EntitiesScriptEngineProvider>(newEngine);
|
||||
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(newEngineSP);
|
||||
|
@ -548,7 +556,51 @@ void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, bool
|
|||
}
|
||||
|
||||
void EntityScriptServer::sendStatsPacket() {
|
||||
QJsonObject statsObject;
|
||||
|
||||
QJsonObject octreeStats;
|
||||
octreeStats["elementCount"] = (double)OctreeElement::getNodeCount();
|
||||
octreeStats["internalElementCount"] = (double)OctreeElement::getInternalNodeCount();
|
||||
octreeStats["leafElementCount"] = (double)OctreeElement::getLeafNodeCount();
|
||||
statsObject["octree_stats"] = octreeStats;
|
||||
|
||||
QJsonObject scriptEngineStats;
|
||||
int numberRunningScripts = 0;
|
||||
const auto scriptEngine = _entitiesScriptEngine;
|
||||
if (scriptEngine) {
|
||||
numberRunningScripts = scriptEngine->getNumRunningEntityScripts();
|
||||
}
|
||||
scriptEngineStats["number_running_scripts"] = numberRunningScripts;
|
||||
statsObject["script_engine_stats"] = scriptEngineStats;
|
||||
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
QJsonObject nodesObject;
|
||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
||||
QJsonObject clientStats;
|
||||
const QString uuidString(uuidStringWithoutCurlyBraces(node->getUUID()));
|
||||
clientStats["node_type"] = NodeType::getNodeTypeName(node->getType());
|
||||
auto& nodeStats = node->getConnectionStats();
|
||||
|
||||
static const QString NODE_OUTBOUND_KBPS_STAT_KEY("outbound_kbit/s");
|
||||
static const QString NODE_INBOUND_KBPS_STAT_KEY("inbound_kbit/s");
|
||||
|
||||
// add the key to ask the domain-server for a username replacement, if it has it
|
||||
clientStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidString;
|
||||
|
||||
clientStats[NODE_OUTBOUND_KBPS_STAT_KEY] = node->getOutboundKbps();
|
||||
clientStats[NODE_INBOUND_KBPS_STAT_KEY] = node->getInboundKbps();
|
||||
|
||||
using namespace std::chrono;
|
||||
const float statsPeriod = duration<float, seconds::period>(nodeStats.endTime - nodeStats.startTime).count();
|
||||
clientStats["unreliable_packet/s"] = (nodeStats.sentUnreliablePackets + nodeStats.receivedUnreliablePackets) / statsPeriod;
|
||||
clientStats["reliable_packet/s"] = (nodeStats.sentPackets + nodeStats.receivedPackets) / statsPeriod;
|
||||
|
||||
nodesObject[uuidString] = clientStats;
|
||||
});
|
||||
|
||||
statsObject["nodes"] = nodesObject;
|
||||
addPacketStatsAndSendStatsPacket(statsObject);
|
||||
}
|
||||
|
||||
void EntityScriptServer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <EntityEditPacketSender.h>
|
||||
#include <plugins/CodecPlugin.h>
|
||||
#include <ScriptEngine.h>
|
||||
#include <SimpleEntitySimulation.h>
|
||||
#include <ThreadedAssignment.h>
|
||||
#include "../entities/EntityTreeHeadlessViewer.h"
|
||||
|
||||
|
@ -75,6 +76,7 @@ private:
|
|||
|
||||
static int _entitiesScriptEngineCount;
|
||||
ScriptEnginePointer _entitiesScriptEngine;
|
||||
SimpleEntitySimulationPointer _entitySimulation;
|
||||
EntityEditPacketSender _entityEditSender;
|
||||
EntityTreeHeadlessViewer _entityViewer;
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ if (WIN32)
|
|||
list(APPEND CMAKE_PREFIX_PATH "${WINDOW_SDK_PATH}")
|
||||
|
||||
# /wd4351 disables warning C4351: new behavior: elements of array will be default initialized
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4351")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP${HIFI_MAX_BUILD_CORES} /wd4351")
|
||||
# /LARGEADDRESSAWARE enables 32-bit apps to use more than 2GB of memory.
|
||||
# Caveats: http://stackoverflow.com/questions/2288728/drawbacks-of-using-largeaddressaware-for-32-bit-windows-executables
|
||||
# TODO: Remove when building 64-bit.
|
||||
|
|
4
cmake/externals/wasapi/CMakeLists.txt
vendored
4
cmake/externals/wasapi/CMakeLists.txt
vendored
|
@ -6,8 +6,8 @@ if (WIN32)
|
|||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://public.highfidelity.com/dependencies/qtaudio_wasapi11.zip
|
||||
URL_MD5 d0eb8489455e7f79d59155535a2c8861
|
||||
URL https://public.highfidelity.com/dependencies/qtaudio_wasapi13.zip
|
||||
URL_MD5 aa56a45f19c18caee13d29a40d1d7d28
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
|
|
@ -38,7 +38,7 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
set(BUILD_ORGANIZATION "High Fidelity")
|
||||
set(HIGH_FIDELITY_PROTOCOL "hifi")
|
||||
set(HIGH_FIDELITY_APP_PROTOCOL "hifiapp")
|
||||
set(INTERFACE_BUNDLE_NAME "Interface")
|
||||
set(INTERFACE_BUNDLE_NAME "interface")
|
||||
set(INTERFACE_ICON_PREFIX "interface")
|
||||
|
||||
# add definition for this release type
|
||||
|
@ -61,7 +61,7 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
set(PR_BUILD 1)
|
||||
set(BUILD_VERSION "PR${RELEASE_NUMBER}")
|
||||
set(BUILD_ORGANIZATION "High Fidelity - PR${RELEASE_NUMBER}")
|
||||
set(INTERFACE_BUNDLE_NAME "Interface")
|
||||
set(INTERFACE_BUNDLE_NAME "interface")
|
||||
set(INTERFACE_ICON_PREFIX "interface-beta")
|
||||
|
||||
# add definition for this release type
|
||||
|
@ -70,7 +70,7 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
set(DEV_BUILD 1)
|
||||
set(BUILD_VERSION "dev")
|
||||
set(BUILD_ORGANIZATION "High Fidelity - ${BUILD_VERSION}")
|
||||
set(INTERFACE_BUNDLE_NAME "Interface")
|
||||
set(INTERFACE_BUNDLE_NAME "interface")
|
||||
set(INTERFACE_ICON_PREFIX "interface-beta")
|
||||
|
||||
# add definition for this release type
|
||||
|
@ -192,12 +192,12 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
|
||||
# shortcut names
|
||||
if (PRODUCTION_BUILD)
|
||||
set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface")
|
||||
set(INTERFACE_SHORTCUT_NAME "High Fidelity")
|
||||
set(CONSOLE_SHORTCUT_NAME "Console")
|
||||
set(SANDBOX_SHORTCUT_NAME "Sandbox")
|
||||
set(APP_USER_MODEL_ID "com.highfidelity.console")
|
||||
else ()
|
||||
set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface - ${BUILD_VERSION_NO_SHA}")
|
||||
set(INTERFACE_SHORTCUT_NAME "High Fidelity - ${BUILD_VERSION_NO_SHA}")
|
||||
set(CONSOLE_SHORTCUT_NAME "Console - ${BUILD_VERSION_NO_SHA}")
|
||||
set(SANDBOX_SHORTCUT_NAME "Sandbox - ${BUILD_VERSION_NO_SHA}")
|
||||
endif ()
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
#
|
||||
macro(SETUP_HIFI_CLIENT_SERVER_PLUGIN)
|
||||
set(${TARGET_NAME}_SHARED 1)
|
||||
setup_hifi_library(${ARGV})
|
||||
set(PLUGIN_SUBFOLDER ${ARGN})
|
||||
setup_hifi_library()
|
||||
|
||||
if (BUILD_CLIENT)
|
||||
add_dependencies(interface ${TARGET_NAME})
|
||||
|
@ -27,6 +28,11 @@ macro(SETUP_HIFI_CLIENT_SERVER_PLUGIN)
|
|||
set(SERVER_PLUGIN_PATH "plugins")
|
||||
endif()
|
||||
|
||||
if (PLUGIN_SUBFOLDER)
|
||||
set(CLIENT_PLUGIN_PATH "${CLIENT_PLUGIN_PATH}/${PLUGIN_SUBFOLDER}")
|
||||
set(SERVER_PLUGIN_PATH "${SERVER_PLUGIN_PATH}/${PLUGIN_SUBFOLDER}")
|
||||
endif()
|
||||
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_GENERATOR STREQUAL "Unix Makefiles")
|
||||
set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/${CLIENT_PLUGIN_PATH}/")
|
||||
set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/${SERVER_PLUGIN_PATH}/")
|
||||
|
|
|
@ -20,7 +20,7 @@ macro(SETUP_HIFI_LIBRARY)
|
|||
foreach(SRC ${AVX_SRCS})
|
||||
if (WIN32)
|
||||
set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS /arch:AVX)
|
||||
elseif (APPLE OR UNIX)
|
||||
elseif (APPLE OR (UNIX AND NOT ANDROID))
|
||||
set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS -mavx)
|
||||
endif()
|
||||
endforeach()
|
||||
|
@ -30,7 +30,7 @@ macro(SETUP_HIFI_LIBRARY)
|
|||
foreach(SRC ${AVX2_SRCS})
|
||||
if (WIN32)
|
||||
set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS /arch:AVX2)
|
||||
elseif (APPLE OR UNIX)
|
||||
elseif (APPLE OR (UNIX AND NOT ANDROID))
|
||||
set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS "-mavx2 -mfma")
|
||||
endif()
|
||||
endforeach()
|
||||
|
@ -44,7 +44,7 @@ macro(SETUP_HIFI_LIBRARY)
|
|||
if (COMPILER_SUPPORTS_AVX512)
|
||||
set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS /arch:AVX512)
|
||||
endif()
|
||||
elseif (APPLE OR UNIX)
|
||||
elseif (APPLE OR (UNIX AND NOT ANDROID))
|
||||
check_cxx_compiler_flag("-mavx512f" COMPILER_SUPPORTS_AVX512)
|
||||
if (COMPILER_SUPPORTS_AVX512)
|
||||
set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS -mavx512f)
|
||||
|
|
|
@ -15,9 +15,11 @@ macro(TARGET_WEBRTC)
|
|||
# select_library_configurations(WEBRTC)
|
||||
else()
|
||||
set(WEBRTC_INCLUDE_DIRS "${VCPKG_INSTALL_ROOT}/include/webrtc")
|
||||
find_library(WEBRTC_LIBRARY NAMES webrtc PATHS ${VCPKG_INSTALL_ROOT}/lib/ NO_DEFAULT_PATH)
|
||||
target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${WEBRTC_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${WEBRTC_LIBRARY})
|
||||
find_library(WEBRTC_LIBRARY_RELEASE webrtc PATHS ${VCPKG_INSTALL_ROOT}/lib NO_DEFAULT_PATH)
|
||||
find_library(WEBRTC_LIBRARY_DEBUG webrtc PATHS ${VCPKG_INSTALL_ROOT}/debug/lib NO_DEFAULT_PATH)
|
||||
select_library_configurations(WEBRTC)
|
||||
target_link_libraries(${TARGET_NAME} ${WEBRTC_LIBRARIES})
|
||||
endif()
|
||||
|
||||
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
#
|
||||
# FindiViewHMD.cmake
|
||||
#
|
||||
# Try to find the SMI iViewHMD eye tracker library
|
||||
#
|
||||
# You must provide a IVIEWHMD_ROOT_DIR which contains 3rdParty, include, and libs directories
|
||||
#
|
||||
# Once done this will define
|
||||
#
|
||||
# IVIEWHMD_FOUND - system found iViewHMD
|
||||
# IVIEWHMD_INCLUDE_DIRS - the iViewHMD include directory
|
||||
# IVIEWHMD_LIBRARIES - link this to use iViewHMD
|
||||
#
|
||||
# Created on 27 Jul 2015 by David Rowe
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
#
|
||||
|
||||
if (WIN32)
|
||||
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("iViewHMD")
|
||||
|
||||
find_path(IVIEWHMD_INCLUDE_DIRS iViewHMDAPI.h PATH_SUFFIXES include HINTS ${IVIEWHMD_SEARCH_DIRS})
|
||||
find_library(IVIEWHMD_LIBRARIES NAMES iViewHMDAPI PATH_SUFFIXES libs/x86 HINTS ${IVIEWHMD_SEARCH_DIRS})
|
||||
find_path(IVIEWHMD_API_DLL_PATH iViewHMDAPI.dll PATH_SUFFIXES libs/x86 HINTS ${IVIEWHMD_SEARCH_DIRS})
|
||||
list(APPEND IVIEWHMD_REQUIREMENTS IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES IVIEWHMD_API_DLL_PATH)
|
||||
|
||||
find_path(IVIEWHMD_DLL_PATH_3RD_PARTY libiViewNG.dll PATH_SUFFIXES 3rdParty HINTS ${IVIEWHMD_SEARCH_DIRS})
|
||||
list(APPEND IVIEWHMD_REQUIREMENTS IVIEWHMD_DLL_PATH_3RD_PARTY)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(IVIEWHMD DEFAULT_MSG ${IVIEWHMD_REQUIREMENTS})
|
||||
|
||||
add_paths_to_fixup_libs(${IVIEWHMD_API_DLL_PATH} ${IVIEWHMD_DLL_PATH_3RD_PARTY})
|
||||
|
||||
mark_as_advanced(IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES IVIEWHMD_SEARCH_DIRS)
|
||||
|
||||
endif()
|
36
cmake/ports/bullet3/bullet-git-fix-build-clang-8.patch
Normal file
36
cmake/ports/bullet3/bullet-git-fix-build-clang-8.patch
Normal file
|
@ -0,0 +1,36 @@
|
|||
From 7638b7c5a659dceb4e580ae87d4d60b00847ef94 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Emil=20Nord=C3=A9n?= <emilnorden@yahoo.se>
|
||||
Date: Sat, 4 May 2019 08:38:53 +0200
|
||||
Subject: [PATCH] fixed build on latest version of clang
|
||||
|
||||
---
|
||||
src/Bullet3Common/b3Vector3.h | 2 +-
|
||||
src/LinearMath/btVector3.h | 2 +-
|
||||
2 files changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/Bullet3Common/b3Vector3.h b/src/Bullet3Common/b3Vector3.h
|
||||
index 56e6c13311..a70d68d6e1 100644
|
||||
--- a/src/Bullet3Common/b3Vector3.h
|
||||
+++ b/src/Bullet3Common/b3Vector3.h
|
||||
@@ -36,7 +36,7 @@ subject to the following restrictions:
|
||||
#pragma warning(disable : 4556) // value of intrinsic immediate argument '4294967239' is out of range '0 - 255'
|
||||
#endif
|
||||
|
||||
-#define B3_SHUFFLE(x, y, z, w) ((w) << 6 | (z) << 4 | (y) << 2 | (x))
|
||||
+#define B3_SHUFFLE(x, y, z, w) (((w) << 6 | (z) << 4 | (y) << 2 | (x)) & 0xff)
|
||||
//#define b3_pshufd_ps( _a, _mask ) (__m128) _mm_shuffle_epi32((__m128i)(_a), (_mask) )
|
||||
#define b3_pshufd_ps(_a, _mask) _mm_shuffle_ps((_a), (_a), (_mask))
|
||||
#define b3_splat3_ps(_a, _i) b3_pshufd_ps((_a), B3_SHUFFLE(_i, _i, _i, 3))
|
||||
diff --git a/src/LinearMath/btVector3.h b/src/LinearMath/btVector3.h
|
||||
index 61fd8d1e46..d65ed9808d 100644
|
||||
--- a/src/LinearMath/btVector3.h
|
||||
+++ b/src/LinearMath/btVector3.h
|
||||
@@ -36,7 +36,7 @@ subject to the following restrictions:
|
||||
#pragma warning(disable : 4556) // value of intrinsic immediate argument '4294967239' is out of range '0 - 255'
|
||||
#endif
|
||||
|
||||
-#define BT_SHUFFLE(x, y, z, w) ((w) << 6 | (z) << 4 | (y) << 2 | (x))
|
||||
+#define BT_SHUFFLE(x, y, z, w) (((w) << 6 | (z) << 4 | (y) << 2 | (x)) & 0xff)
|
||||
//#define bt_pshufd_ps( _a, _mask ) (__m128) _mm_shuffle_epi32((__m128i)(_a), (_mask) )
|
||||
#define bt_pshufd_ps(_a, _mask) _mm_shuffle_ps((_a), (_a), (_mask))
|
||||
#define bt_splat3_ps(_a, _i) bt_pshufd_ps((_a), BT_SHUFFLE(_i, _i, _i, 3))
|
|
@ -27,6 +27,7 @@ vcpkg_from_github(
|
|||
REF ab8f16961e19a86ee20c6a1d61f662392524cc77
|
||||
SHA512 927742db29867517283d45e475f0c534a9a57e165cae221f26e08e88057253a1682ac9919b2dc547b9cf388ba0b931b175623461d44f28c9184796ba90b1ed55
|
||||
HEAD_REF master
|
||||
PATCHES "bullet-git-fix-build-clang-8.patch"
|
||||
)
|
||||
|
||||
vcpkg_configure_cmake(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Source: hifi-deps
|
||||
Version: 0.1
|
||||
Version: 0.3
|
||||
Description: Collected dependencies for High Fidelity applications
|
||||
Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openexr (!android), openssl (windows), tbb (!android&!osx), zlib, webrtc (!android)
|
||||
|
|
|
@ -14,9 +14,9 @@ elseif (WIN32)
|
|||
elseif (APPLE)
|
||||
vcpkg_download_distfile(
|
||||
WEBRTC_SOURCE_ARCHIVE
|
||||
URLS https://hifi-public.s3.amazonaws.com/seth/webrtc-20190626-osx.tar.gz
|
||||
SHA512 fc70cec1b5ee87395137b7090f424e2fc2300fc17d744d5ffa1cf7aa0e0f1a069a9d72ba1ad2fb4a640ebeb6c218bda24351ba0083e1ff96c4a4b5032648a9d2
|
||||
FILENAME webrtc-20190626-osx.tar.gz
|
||||
URLS https://hifi-public.s3.amazonaws.com/seth/webrtc-m78-osx.tar.gz
|
||||
SHA512 8b547da921cc96f5c22b4253a1c9e707971bb627898fbdb6b238ef1318c7d2512e878344885c936d4bd6a66005cc5b63dfc3fa5ddd14f17f378dcaa17b5e25df
|
||||
FILENAME webrtc-m78-osx.tar.gz
|
||||
)
|
||||
else ()
|
||||
# else Linux desktop
|
||||
|
|
|
@ -1156,7 +1156,17 @@ FunctionEnd
|
|||
|
||||
Section "-Core installation"
|
||||
|
||||
;The following delete blocks are temporary and can be removed once users who had the initial installer have updated
|
||||
; 2016-02-25 - The following delete blocks are temporary and can be removed once users who had the initial installer have updated
|
||||
; 2019-09-10 - (3 and a half years later) Sure they are buddy. Sure they are.
|
||||
|
||||
; MessageBox MB_OK|MB_ICONEXCLAMATION "installer type is @INSTALLER_TYPE@"
|
||||
|
||||
;Delete any server executables that might have been installed by bad versions of the client-only installer, but ONLY if we are a client-only installer
|
||||
${If} "@INSTALLER_TYPE@" == "client_only"
|
||||
; MessageBox MB_OK|MB_ICONEXCLAMATION "trying to delete server binaries"
|
||||
Delete "$INSTDIR\assignment-client.exe"
|
||||
Delete "$INSTDIR\domain-server.exe"
|
||||
${EndIf}
|
||||
|
||||
;Delete any server-console files installed before it was placed in sub-folder
|
||||
Delete "$INSTDIR\server-console.exe"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": 2.3,
|
||||
"version": 2.4,
|
||||
"settings": [
|
||||
{
|
||||
"name": "metaverse",
|
||||
|
@ -1705,6 +1705,114 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "oauth",
|
||||
"label": "OAuth",
|
||||
"show_on_enable": true,
|
||||
"settings": [
|
||||
{
|
||||
"name": "enable",
|
||||
"type": "checkbox",
|
||||
"default": false,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "admin-users",
|
||||
"label": "Admin Users",
|
||||
"type": "table",
|
||||
"can_add_new_rows": true,
|
||||
"help": "Any of these users can administer the domain.",
|
||||
"numbered": false,
|
||||
"backup": false,
|
||||
"advanced": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "username",
|
||||
"label": "Username",
|
||||
"can_set": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "admin-roles",
|
||||
"label": "Admin Roles",
|
||||
"type": "table",
|
||||
"can_add_new_rows": true,
|
||||
"help": "Any user with any of these metaverse roles can administer the domain.",
|
||||
"numbered": false,
|
||||
"backup": false,
|
||||
"advanced": true,
|
||||
"columns": [
|
||||
{
|
||||
"name": "role",
|
||||
"label": "Role",
|
||||
"can_set": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "client-id",
|
||||
"label": "Client ID",
|
||||
"help": "OAuth client ID.",
|
||||
"default": "",
|
||||
"advanced": true,
|
||||
"backup": false
|
||||
},
|
||||
{
|
||||
"name": "client-secret",
|
||||
"label": "Client Secret",
|
||||
"help": "OAuth client secret.",
|
||||
"type": "password",
|
||||
"password_placeholder": "******",
|
||||
"value-hidden": true,
|
||||
"advanced": true,
|
||||
"backup": false
|
||||
},
|
||||
{
|
||||
"name": "provider",
|
||||
"label": "Provider",
|
||||
"help": "OAuth provider URL.",
|
||||
"default": "https://metaverse.highfidelity.com",
|
||||
"advanced": true,
|
||||
"backup": false
|
||||
},
|
||||
{
|
||||
"name": "hostname",
|
||||
"label": "Hostname",
|
||||
"help": "OAuth hostname.",
|
||||
"default": "",
|
||||
"advanced": true,
|
||||
"backup": false
|
||||
},
|
||||
{
|
||||
"name": "key-passphrase",
|
||||
"label": "SSL Private Key Passphrase",
|
||||
"help": "SSL Private Key Passphrase",
|
||||
"type": "password",
|
||||
"password_placeholder": "******",
|
||||
"value-hidden": true,
|
||||
"advanced": true,
|
||||
"backup": false
|
||||
},
|
||||
{
|
||||
"name": "cert-fingerprint",
|
||||
"type": "hidden",
|
||||
"readonly": true,
|
||||
"advanced": true,
|
||||
"backup": false
|
||||
},
|
||||
{
|
||||
"name": "cert",
|
||||
"advanced": true,
|
||||
"backup": false
|
||||
},
|
||||
{
|
||||
"name": "key",
|
||||
"advanced": true,
|
||||
"backup": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "automatic_content_archives",
|
||||
"label": "Automatic Content Archives",
|
||||
|
|
|
@ -2,6 +2,9 @@ var DomainInfo = null;
|
|||
|
||||
var viewHelpers = {
|
||||
getFormGroup: function(keypath, setting, values, isAdvanced) {
|
||||
if (setting.hidden) {
|
||||
return "";
|
||||
}
|
||||
form_group = "<div class='form-group " +
|
||||
(isAdvanced ? Settings.ADVANCED_CLASS : "") + " " +
|
||||
(setting.deprecated ? Settings.DEPRECATED_CLASS : "" ) + "' " +
|
||||
|
@ -82,8 +85,9 @@ var viewHelpers = {
|
|||
"placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") +
|
||||
"' value='" + (_.has(setting, 'password_placeholder') ? setting.password_placeholder : setting_value) + "'/>"
|
||||
}
|
||||
|
||||
form_group += "<span class='help-block'>" + setting.help + "</span>"
|
||||
if (setting.help) {
|
||||
form_group += "<span class='help-block'>" + setting.help + "</span>"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,12 +118,17 @@ function reloadSettings(callback) {
|
|||
data.descriptions.push(Settings.extraGroupsAtEnd[endGroupIndex]);
|
||||
}
|
||||
|
||||
data.descriptions = data.descriptions.map(function(x) {
|
||||
x.hidden = x.hidden || (x.show_on_enable && data.values[x.name] && !data.values[x.name].enable);
|
||||
return x;
|
||||
});
|
||||
|
||||
$('#panels').html(Settings.panelsTemplate(data));
|
||||
|
||||
Settings.data = data;
|
||||
Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true);
|
||||
|
||||
Settings.afterReloadActions();
|
||||
Settings.afterReloadActions(data);
|
||||
|
||||
// setup any bootstrap switches
|
||||
$('.toggle-checkbox').bootstrapSwitch();
|
||||
|
@ -129,10 +138,14 @@ function reloadSettings(callback) {
|
|||
Settings.pendingChanges = 0;
|
||||
|
||||
// call the callback now that settings are loaded
|
||||
callback(true);
|
||||
if (callback) {
|
||||
callback(true);
|
||||
}
|
||||
}).fail(function() {
|
||||
// call the failure object since settings load faild
|
||||
callback(false)
|
||||
if (callback) {
|
||||
callback(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -237,14 +250,14 @@ $(document).ready(function(){
|
|||
|
||||
// set focus to the first input in the new row
|
||||
$target.closest('table').find('tr.inputs input:first').focus();
|
||||
}
|
||||
} else {
|
||||
var tableRows = sibling.parent();
|
||||
var tableBody = tableRows.parent();
|
||||
|
||||
var tableRows = sibling.parent();
|
||||
var tableBody = tableRows.parent();
|
||||
|
||||
// if theres no more siblings, we should jump to a new row
|
||||
if (sibling.next().length == 0 && tableRows.nextAll().length == 1) {
|
||||
tableBody.find("." + Settings.ADD_ROW_BUTTON_CLASS).click();
|
||||
// if theres no more siblings, we should jump to a new row
|
||||
if (sibling.next().length == 0 && tableRows.nextAll().length == 1) {
|
||||
tableBody.find("." + Settings.ADD_ROW_BUTTON_CLASS).click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -136,6 +136,23 @@ function getCurrentDomainIDType() {
|
|||
return DOMAIN_ID_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
function isCloudDomain() {
|
||||
|
||||
if (!domainIDIsSet()) {
|
||||
return false;
|
||||
}
|
||||
if (typeof DomainInfo === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
if (DomainInfo === null) {
|
||||
return false;
|
||||
}
|
||||
if (typeof DomainInfo.cloud_domain !== "boolean") {
|
||||
return false;
|
||||
}
|
||||
return DomainInfo.cloud_domain;
|
||||
}
|
||||
|
||||
function showLoadingDialog(msg) {
|
||||
var message = '<div class="text-center">';
|
||||
message += '<span class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span> ' + msg;
|
||||
|
|
|
@ -18,7 +18,19 @@ $(document).ready(function(){
|
|||
Settings.extraGroupsAtIndex = Settings.extraDomainGroupsAtIndex;
|
||||
var METAVERSE_URL = URLs.METAVERSE_URL;
|
||||
|
||||
Settings.afterReloadActions = function() {
|
||||
var SSL_PRIVATE_KEY_FILE_ID = 'ssl-private-key-file';
|
||||
var SSL_PRIVATE_KEY_CONTENTS_ID = 'key-contents';
|
||||
var SSL_PRIVATE_KEY_CONTENTS_NAME = 'oauth.key-contents';
|
||||
var SSL_CERT_UPLOAD_ID = 'ssl-cert-button';
|
||||
var SSL_CERT_FILE_ID = 'ssl-cert-file';
|
||||
var SSL_CERT_FINGERPRINT_ID = 'cert-fingerprint';
|
||||
var SSL_CERT_FINGERPRINT_SPAN_ID = 'cert-fingerprint-span-id';
|
||||
var SSL_CERT_CONTENTS_ID = 'cert-contents';
|
||||
var SSL_CERT_CONTENTS_NAME = 'oauth.cert-contents';
|
||||
var SSL_PRIVATE_KEY_PATH = 'oauth.key';
|
||||
var SSL_CERT_PATH = 'oauth.cert';
|
||||
|
||||
Settings.afterReloadActions = function(data) {
|
||||
|
||||
getMetaverseUrl(function(metaverse_url) {
|
||||
METAVERSE_URL = metaverse_url;
|
||||
|
@ -32,6 +44,8 @@ $(document).ready(function(){
|
|||
setupDomainNetworkingSettings();
|
||||
// setupDomainLabelSetting();
|
||||
|
||||
setupSettingsOAuth(data);
|
||||
|
||||
setupSettingsBackup();
|
||||
|
||||
if (domainIDIsSet()) {
|
||||
|
@ -124,6 +138,48 @@ $(document).ready(function(){
|
|||
}
|
||||
}
|
||||
|
||||
if (formJSON["oauth"]) {
|
||||
var private_key = formJSON["oauth"]["key-contents"];
|
||||
var cert = formJSON["oauth"]["cert-contents"];
|
||||
var oauthErrors = "";
|
||||
if (private_key != undefined) {
|
||||
var pattern = /-+BEGIN PRIVATE KEY-+[A-Za-z0-9+/\n=]*-+END PRIVATE KEY-+/m;
|
||||
if (!pattern.test(private_key)) {
|
||||
oauthErrors += "Private key must be in PEM format<BR/>";
|
||||
}
|
||||
}
|
||||
if (cert != undefined) {
|
||||
var pattern = /-+BEGIN CERTIFICATE-+[A-Za-z0-9+/\n=]*-+END CERTIFICATE-+/m;
|
||||
if (!pattern.test(cert)) {
|
||||
oauthErrors += "Certificate must be in PEM format<BR/>";
|
||||
}
|
||||
}
|
||||
if ($('#oauth.panel').length) {
|
||||
if (!$('input[name="oauth.client-id"]').val()) {
|
||||
oauthErrors += "OAuth requires a client Id.<BR/>";
|
||||
}
|
||||
if (!$('input[name="oauth.provider"]').val()) {
|
||||
oauthErrors += "OAuth requires a provider.<BR/>";
|
||||
}
|
||||
if (!$('input[name="oauth.hostname"]').val()) {
|
||||
oauthErrors += "OAuth requires a hostname.<BR/>";
|
||||
}
|
||||
if (!$('input[name="' + SSL_PRIVATE_KEY_PATH + '"]').val() && !$('input[name="' + SSL_PRIVATE_KEY_CONTENTS_NAME + '"]').val()) {
|
||||
oauthErrors += "OAuth requires an SSL Private Key.<BR/>";
|
||||
}
|
||||
if (!$('input[name="' + SSL_CERT_PATH + '"]').val() && !$('input[name="' + SSL_CERT_CONTENTS_NAME + '"]').val()) {
|
||||
oauthErrors += "OAuth requires an SSL Certificate.<BR/>";
|
||||
}
|
||||
if (!$("table[name='oauth.admin-users'] tr.value-row").length &&
|
||||
!$("table[name='oauth.admin-roles'] tr.value-row").length) {
|
||||
oauthErrors += "OAuth must have at least one admin user or admin role.<BR/>";
|
||||
}
|
||||
}
|
||||
if (oauthErrors) {
|
||||
bootbox.alert({ "message": oauthErrors, "title": "OAuth Configuration Error" });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
postSettings(formJSON);
|
||||
};
|
||||
|
||||
|
@ -550,6 +606,9 @@ $(document).ready(function(){
|
|||
var address = DomainInfo.network_address === null ? '' : DomainInfo.network_address;
|
||||
var port = DomainInfo.network_port === null ? '' : DomainInfo.network_port;
|
||||
var modal_body = "<div class='form-group'>";
|
||||
if (isCloudDomain()) {
|
||||
modal_body += '<div style="color:red;font-weight: bold">Changing the network settings may actually break your domain.</div>';
|
||||
}
|
||||
if (includeAddress) {
|
||||
modal_body += "<label class='control-label'>Address</label>";
|
||||
modal_body += "<input type='text' id='network-address-input' class='form-control' value='" + address + "'>";
|
||||
|
@ -811,6 +870,10 @@ $(document).ready(function(){
|
|||
}
|
||||
}
|
||||
|
||||
if (getCurrentDomainIDType() === DOMAIN_ID_TYPE_TEMP) {
|
||||
$(Settings.DOMAIN_ID_SELECTOR).siblings('span').append(" <b>This is a temporary domain and will not be visible in your domain list.</b>");
|
||||
}
|
||||
|
||||
if (accessTokenIsSet()) {
|
||||
appendAddButtonToPlacesTable();
|
||||
}
|
||||
|
@ -1035,6 +1098,67 @@ $(document).ready(function(){
|
|||
});
|
||||
}
|
||||
|
||||
function setupSettingsOAuth(data) {
|
||||
// construct the HTML needed for the settings backup panel
|
||||
var html = "<div class='form-group undefined'>";
|
||||
html += "<label class='control-label'>SSL Private Key</label><BR/>";
|
||||
html += "<label id='key-path-label'class='control-label'>Path</label>";
|
||||
html += "<input id='" + SSL_PRIVATE_KEY_FILE_ID + "' type='file' accept='.key'/>";
|
||||
html += "<input id='" + SSL_PRIVATE_KEY_CONTENTS_ID + "' name='" + SSL_PRIVATE_KEY_CONTENTS_NAME + "' type='hidden'/>";
|
||||
html += "</div>";
|
||||
html += "<div class='form-group undefined'>";
|
||||
html += "<label class='control-label'>SSL Cert</label>";
|
||||
html += "<div id='cert-fingerprint'><b>Fingerprint:</b><span id='" + SSL_CERT_FINGERPRINT_SPAN_ID + "'>" + data.values.oauth["cert-fingerprint"] + "</span></div>";
|
||||
html += "<label id='cert-path-label' class='control-label'>Path</label>";
|
||||
html += "<input id='" + SSL_CERT_FILE_ID + "' type='file' accept='.cer,.crt'/>";
|
||||
html += "<input id='" + SSL_CERT_CONTENTS_ID + "' name='" + SSL_CERT_CONTENTS_NAME + "' type='hidden'/>";
|
||||
html += "</div>";
|
||||
|
||||
$('#oauth-advanced').append(html);
|
||||
|
||||
$('#key-path-label').after($('[data-keypath="' + SSL_PRIVATE_KEY_PATH + '"]'));
|
||||
$('#cert-path-label').after($('[data-keypath="' + SSL_CERT_PATH + '"]'));
|
||||
$('[name="' + SSL_PRIVATE_KEY_PATH + '"]').val(data.values.oauth.key);
|
||||
$('[name="' + SSL_CERT_PATH + '"]').val(data.values.oauth.cert);
|
||||
|
||||
$('body').on('change input propertychange', '#' + SSL_PRIVATE_KEY_FILE_ID, function(e){
|
||||
var f = e.target.files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
$('#' + SSL_PRIVATE_KEY_CONTENTS_ID).val(reader.result);
|
||||
$('#' + SSL_PRIVATE_KEY_CONTENTS_ID).attr('data-changed', true);
|
||||
$('[name="' + SSL_PRIVATE_KEY_PATH + '"]').val('');
|
||||
badgeForDifferences($('#' + SSL_PRIVATE_KEY_CONTENTS_ID));
|
||||
}
|
||||
reader.readAsText(f);
|
||||
});
|
||||
$('body').on('change input propertychange', '#' + SSL_CERT_FILE_ID, function(e){
|
||||
var f = e.target.files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
$('#' + SSL_CERT_CONTENTS_ID).val(reader.result);
|
||||
$('#' + SSL_CERT_CONTENTS_ID).attr('data-changed', true);
|
||||
$('[name="' + SSL_CERT_PATH + '"]').val('');
|
||||
$('#' + SSL_CERT_FINGERPRINT_SPAN_ID).text('');
|
||||
badgeForDifferences($('#' + SSL_CERT_CONTENTS_ID));
|
||||
}
|
||||
reader.readAsText(f);
|
||||
});
|
||||
|
||||
$('body').on('change input propertychange', '[name="' + SSL_PRIVATE_KEY_PATH + '"]', function(e){
|
||||
$('#' + SSL_PRIVATE_KEY_FILE_ID).val('');
|
||||
$('#' + SSL_PRIVATE_KEY_CONTENTS_ID).val('');
|
||||
badgeForDifferences($('[name="' + SSL_PRIVATE_KEY_PATH + '"]').attr('data-changed', true));
|
||||
});
|
||||
|
||||
$('body').on('change input propertychange', '[name="' + SSL_CERT_PATH + '"]', function(e){
|
||||
$('#' + SSL_CERT_FILE_ID).val('');
|
||||
$('#' + SSL_CERT_CONTENTS_ID).val('');
|
||||
$('#' + SSL_CERT_FINGERPRINT_SPAN_ID).text('');
|
||||
badgeForDifferences($('[name="' + SSL_CERT_PATH + '"]').attr('data-changed', true));
|
||||
});
|
||||
}
|
||||
|
||||
var RESTORE_SETTINGS_UPLOAD_ID = 'restore-settings-button';
|
||||
var RESTORE_SETTINGS_FILE_ID = 'restore-settings-file';
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include <AccountManager.h>
|
||||
#include <AssetClient.h>
|
||||
#include <BuildInfo.h>
|
||||
#include <CrashAnnotations.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <HifiConfigVariantMap.h>
|
||||
#include <HTTPConnection.h>
|
||||
|
@ -174,6 +175,9 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
|
||||
LogUtils::init();
|
||||
|
||||
LogHandler::getInstance().moveToThread(thread());
|
||||
LogHandler::getInstance().setupRepeatedMessageFlusher();
|
||||
|
||||
qDebug() << "Setting up domain-server";
|
||||
qDebug() << "[VERSION] Build sequence:" << qPrintable(applicationVersion());
|
||||
qDebug() << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION;
|
||||
|
@ -182,6 +186,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
qDebug() << "[VERSION] BUILD_GLOBAL_SERVICES:" << BuildInfo::BUILD_GLOBAL_SERVICES;
|
||||
qDebug() << "[VERSION] We will be using this name to find ICE servers:" << _iceServerAddr;
|
||||
|
||||
connect(this, &QCoreApplication::aboutToQuit, this, &DomainServer::aboutToQuit);
|
||||
|
||||
// make sure we have a fresh AccountManager instance
|
||||
// (need this since domain-server can restart itself and maintain static variables)
|
||||
|
@ -226,9 +231,10 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
|
||||
setupGroupCacheRefresh();
|
||||
|
||||
// if we were given a certificate/private key or oauth credentials they must succeed
|
||||
if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) {
|
||||
return;
|
||||
optionallySetupOAuth();
|
||||
|
||||
if (_oauthEnable) {
|
||||
_oauthEnable = optionallyReadX509KeyAndCertificate();
|
||||
}
|
||||
|
||||
_settingsManager.apiRefreshGroupInformation();
|
||||
|
@ -319,7 +325,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
|
||||
connect(_contentManager.get(), &DomainContentBackupManager::recoveryCompleted, this, &DomainServer::restart);
|
||||
|
||||
static const int NODE_PING_MONITOR_INTERVAL_MSECS = 1 * MSECS_PER_SECOND;
|
||||
static const int NODE_PING_MONITOR_INTERVAL_MSECS = 1 * MSECS_PER_SECOND;
|
||||
_nodePingMonitorTimer = new QTimer{ this };
|
||||
connect(_nodePingMonitorTimer, &QTimer::timeout, this, &DomainServer::nodePingMonitor);
|
||||
_nodePingMonitorTimer->start(NODE_PING_MONITOR_INTERVAL_MSECS);
|
||||
|
@ -379,7 +385,7 @@ void DomainServer::parseCommandLine(int argc, char* argv[]) {
|
|||
}
|
||||
|
||||
if (_iceServerAddr.isEmpty()) {
|
||||
qCWarning(domain_server_ice) << "Could not parse an IP address and port combination from" << hostnamePortString;
|
||||
qCWarning(domain_server_ice) << "ALERT: Could not parse an IP address and port combination from" << hostnamePortString;
|
||||
::exit(0);
|
||||
}
|
||||
}
|
||||
|
@ -428,6 +434,10 @@ DomainServer::~DomainServer() {
|
|||
DependencyManager::destroy<LimitedNodeList>();
|
||||
}
|
||||
|
||||
void DomainServer::aboutToQuit() {
|
||||
crash::annotations::setShutdownState(true);
|
||||
}
|
||||
|
||||
void DomainServer::queuedQuit(QString quitMessage, int exitCode) {
|
||||
if (!quitMessage.isEmpty()) {
|
||||
qWarning() << qPrintable(quitMessage);
|
||||
|
@ -447,8 +457,9 @@ QUuid DomainServer::getID() {
|
|||
}
|
||||
|
||||
bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
||||
const QString X509_CERTIFICATE_OPTION = "cert";
|
||||
const QString X509_PRIVATE_KEY_OPTION = "key";
|
||||
const QString X509_CERTIFICATE_OPTION = "oauth.cert";
|
||||
const QString X509_PRIVATE_KEY_OPTION = "oauth.key";
|
||||
const QString X509_PRIVATE_KEY_PASSPHRASE_OPTION = "oauth.key-passphrase";
|
||||
const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE";
|
||||
|
||||
QString certPath = _settingsManager.valueForKeyPath(X509_CERTIFICATE_OPTION).toString();
|
||||
|
@ -459,7 +470,12 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
|||
// this is used for Oauth callbacks when authorizing users against a data server
|
||||
// let's make sure we can load the key and certificate
|
||||
|
||||
QString keyPassphraseString = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV);
|
||||
QString keyPassphraseEnv = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV);
|
||||
QString keyPassphraseString = _settingsManager.valueForKeyPath(X509_PRIVATE_KEY_PASSPHRASE_OPTION).toString();
|
||||
|
||||
if (!keyPassphraseEnv.isEmpty()) {
|
||||
keyPassphraseString = keyPassphraseEnv;
|
||||
}
|
||||
|
||||
qDebug() << "Reading certificate file at" << certPath << "for HTTPS.";
|
||||
qDebug() << "Reading key file at" << keyPath << "for HTTPS.";
|
||||
|
@ -473,16 +489,15 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
|||
QSslCertificate sslCertificate(&certFile);
|
||||
QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8());
|
||||
|
||||
if (privateKey.isNull()) {
|
||||
qCritical() << "SSL Private Key Not Loading. Bad password or key format?";
|
||||
}
|
||||
|
||||
_httpsManager.reset(new HTTPSManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this));
|
||||
|
||||
qDebug() << "TCP server listening for HTTPS connections on" << DOMAIN_SERVER_HTTPS_PORT;
|
||||
|
||||
} else if (!certPath.isEmpty() || !keyPath.isEmpty()) {
|
||||
static const QString MISSING_CERT_ERROR_MSG = "Missing certificate or private key. domain-server will now quit.";
|
||||
static const int MISSING_CERT_ERROR_CODE = 3;
|
||||
|
||||
QMetaObject::invokeMethod(this, "queuedQuit", Qt::QueuedConnection,
|
||||
Q_ARG(QString, MISSING_CERT_ERROR_MSG), Q_ARG(int, MISSING_CERT_ERROR_CODE));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -490,10 +505,12 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
|||
}
|
||||
|
||||
bool DomainServer::optionallySetupOAuth() {
|
||||
const QString OAUTH_PROVIDER_URL_OPTION = "oauth-provider";
|
||||
const QString OAUTH_CLIENT_ID_OPTION = "oauth-client-id";
|
||||
const QString OAUTH_ENABLE_OPTION = "oauth.enable";
|
||||
const QString OAUTH_PROVIDER_URL_OPTION = "oauth.provider";
|
||||
const QString OAUTH_CLIENT_ID_OPTION = "oauth.client-id";
|
||||
const QString OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET";
|
||||
const QString REDIRECT_HOSTNAME_OPTION = "hostname";
|
||||
const QString OAUTH_CLIENT_SECRET_OPTION = "oauth.client-secret";
|
||||
const QString REDIRECT_HOSTNAME_OPTION = "oauth.hostname";
|
||||
|
||||
_oauthProviderURL = QUrl(_settingsManager.valueForKeyPath(OAUTH_PROVIDER_URL_OPTION).toString());
|
||||
|
||||
|
@ -502,22 +519,24 @@ bool DomainServer::optionallySetupOAuth() {
|
|||
_oauthProviderURL = NetworkingConstants::METAVERSE_SERVER_URL();
|
||||
}
|
||||
|
||||
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
|
||||
if (_oauthClientSecret.isEmpty()) {
|
||||
_oauthClientSecret = _settingsManager.valueForKeyPath(OAUTH_CLIENT_SECRET_OPTION).toString();
|
||||
}
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
accountManager->setAuthURL(_oauthProviderURL);
|
||||
|
||||
_oauthClientID = _settingsManager.valueForKeyPath(OAUTH_CLIENT_ID_OPTION).toString();
|
||||
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
|
||||
_hostname = _settingsManager.valueForKeyPath(REDIRECT_HOSTNAME_OPTION).toString();
|
||||
|
||||
if (!_oauthClientID.isEmpty()) {
|
||||
_oauthEnable = _settingsManager.valueForKeyPath(OAUTH_ENABLE_OPTION).toBool();
|
||||
|
||||
if (_oauthEnable) {
|
||||
if (_oauthProviderURL.isEmpty()
|
||||
|| _hostname.isEmpty()
|
||||
|| _oauthClientID.isEmpty()
|
||||
|| _oauthClientSecret.isEmpty()) {
|
||||
static const QString MISSING_OAUTH_INFO_MSG = "Missing OAuth provider URL, hostname, client ID, or client secret. domain-server will now quit.";
|
||||
static const int MISSING_OAUTH_INFO_ERROR_CODE = 4;
|
||||
QMetaObject::invokeMethod(this, "queuedQuit", Qt::QueuedConnection,
|
||||
Q_ARG(QString, MISSING_OAUTH_INFO_MSG), Q_ARG(int, MISSING_OAUTH_INFO_ERROR_CODE));
|
||||
_oauthEnable = false;
|
||||
return false;
|
||||
} else {
|
||||
qDebug() << "OAuth will be used to identify clients using provider at" << _oauthProviderURL.toString();
|
||||
|
@ -857,7 +876,7 @@ void DomainServer::setupAutomaticNetworking() {
|
|||
nodeList->startSTUNPublicSocketUpdate();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
|
||||
qCCritical(domain_server) << "PAGE: Cannot enable domain-server automatic networking without a domain ID."
|
||||
<< "Please add an ID to your config file or via the web interface.";
|
||||
return;
|
||||
}
|
||||
|
@ -1616,8 +1635,9 @@ void DomainServer::handleFailedICEServerAddressUpdate(QNetworkReply* requestRepl
|
|||
} else {
|
||||
const int ICE_SERVER_UPDATE_RETRY_MS = 2 * 1000;
|
||||
|
||||
qCWarning(domain_server_ice) << "Failed to update ice-server address (" << _iceServerSocket << ") with High Fidelity Metaverse - error was"
|
||||
<< requestReply->errorString();
|
||||
qCWarning(domain_server_ice) << "PAGE: Failed to update ice-server address (" << _iceServerSocket <<
|
||||
") with Metaverse (" << requestReply->url() << ") (critical error for auto-networking) error:" <<
|
||||
requestReply->errorString();
|
||||
qCWarning(domain_server_ice) << "\tRe-attempting in" << ICE_SERVER_UPDATE_RETRY_MS / 1000 << "seconds";
|
||||
|
||||
QTimer::singleShot(ICE_SERVER_UPDATE_RETRY_MS, this, SLOT(sendICEServerAddressToMetaverseAPI()));
|
||||
|
@ -1746,8 +1766,8 @@ void DomainServer::nodePingMonitor() {
|
|||
}
|
||||
|
||||
void DomainServer::processOctreeDataPersistMessage(QSharedPointer<ReceivedMessage> message) {
|
||||
qDebug() << "Received octree data persist message";
|
||||
auto data = message->readAll();
|
||||
qDebug() << "Received octree data persist message" << (data.size() / 1000) << "kbytes.";
|
||||
auto filePath = getEntitiesFilePath();
|
||||
|
||||
QDir dir(getEntitiesDirPath());
|
||||
|
@ -1759,12 +1779,16 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointer<ReceivedMessag
|
|||
QFile f(filePath);
|
||||
if (f.open(QIODevice::WriteOnly)) {
|
||||
f.write(data);
|
||||
#ifdef EXPENSIVE_NETWORK_DIAGNOSTICS
|
||||
// These diagnostics take take more than 200ms (depending on content size),
|
||||
// causing Socket::readPendingDatagrams to overrun its timebox.
|
||||
OctreeUtils::RawEntityData entityData;
|
||||
if (entityData.readOctreeDataInfoFromData(data)) {
|
||||
qCDebug(domain_server) << "Wrote new entities file" << entityData.id << entityData.dataVersion;
|
||||
} else {
|
||||
qCDebug(domain_server) << "Failed to read new octree data info";
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
qCDebug(domain_server) << "Failed to write new entities file:" << filePath;
|
||||
}
|
||||
|
@ -2689,8 +2713,8 @@ void DomainServer::profileRequestFinished() {
|
|||
std::pair<bool, QString> DomainServer::isAuthenticatedRequest(HTTPConnection* connection) {
|
||||
|
||||
static const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie";
|
||||
static const QString ADMIN_USERS_CONFIG_KEY = "admin-users";
|
||||
static const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles";
|
||||
static const QString ADMIN_USERS_CONFIG_KEY = "oauth.admin-users";
|
||||
static const QString ADMIN_ROLES_CONFIG_KEY = "oauth.admin-roles";
|
||||
static const QString BASIC_AUTH_USERNAME_KEY_PATH = "security.http_username";
|
||||
static const QString BASIC_AUTH_PASSWORD_KEY_PATH = "security.http_password";
|
||||
const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)";
|
||||
|
@ -2700,8 +2724,7 @@ std::pair<bool, QString> DomainServer::isAuthenticatedRequest(HTTPConnection* c
|
|||
QVariant adminUsersVariant = _settingsManager.valueForKeyPath(ADMIN_USERS_CONFIG_KEY);
|
||||
QVariant adminRolesVariant = _settingsManager.valueForKeyPath(ADMIN_ROLES_CONFIG_KEY);
|
||||
|
||||
if (!_oauthProviderURL.isEmpty()
|
||||
&& (adminUsersVariant.isValid() || adminRolesVariant.isValid())) {
|
||||
if (_oauthEnable) {
|
||||
QString cookieString = connection->requestHeader(HTTP_COOKIE_HEADER_KEY);
|
||||
|
||||
QRegExp cookieUUIDRegex(COOKIE_UUID_REGEX_STRING);
|
||||
|
@ -3428,8 +3451,9 @@ void DomainServer::randomizeICEServerAddress(bool shouldTriggerHostLookup) {
|
|||
// we ended up with an empty list since everything we've tried has failed
|
||||
// so clear the set of failed addresses and start going through them again
|
||||
|
||||
qCWarning(domain_server_ice) << "All current ice-server addresses have failed - re-attempting all current addresses for"
|
||||
<< _iceServerAddr;
|
||||
qCWarning(domain_server_ice) <<
|
||||
"PAGE: All current ice-server addresses have failed - re-attempting all current addresses for"
|
||||
<< _iceServerAddr;
|
||||
|
||||
_failedIceServerAddresses.clear();
|
||||
candidateICEAddresses = _iceServerAddresses;
|
||||
|
|
|
@ -136,6 +136,8 @@ private slots:
|
|||
void tokenGrantFinished();
|
||||
void profileRequestFinished();
|
||||
|
||||
void aboutToQuit();
|
||||
|
||||
signals:
|
||||
void iceServerChanged();
|
||||
void userConnected();
|
||||
|
@ -236,6 +238,7 @@ private:
|
|||
|
||||
bool _isUsingDTLS { false };
|
||||
|
||||
bool _oauthEnable { false };
|
||||
QUrl _oauthProviderURL;
|
||||
QString _oauthClientID;
|
||||
QString _oauthClientSecret;
|
||||
|
|
|
@ -22,7 +22,9 @@
|
|||
#include <QtCore/QThread>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QUrlQuery>
|
||||
#include <QtNetwork/QSslKey>
|
||||
#include <QSaveFile>
|
||||
#include <QPair>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <Assignment.h>
|
||||
|
@ -46,10 +48,14 @@ const QString DESCRIPTION_SETTINGS_KEY = "settings";
|
|||
const QString SETTING_DEFAULT_KEY = "default";
|
||||
const QString DESCRIPTION_NAME_KEY = "name";
|
||||
const QString DESCRIPTION_GROUP_LABEL_KEY = "label";
|
||||
const QString DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY = "show_on_enable";
|
||||
const QString DESCRIPTION_ENABLE_KEY = "enable";
|
||||
const QString DESCRIPTION_BACKUP_FLAG_KEY = "backup";
|
||||
const QString SETTING_DESCRIPTION_TYPE_KEY = "type";
|
||||
const QString DESCRIPTION_COLUMNS_KEY = "columns";
|
||||
const QString CONTENT_SETTING_FLAG_KEY = "content_setting";
|
||||
static const QString SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY = "domain_settings";
|
||||
static const QString SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY = "content_settings";
|
||||
|
||||
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
|
||||
|
||||
|
@ -136,6 +142,10 @@ void DomainServerSettingsManager::splitSettingsDescription() {
|
|||
|
||||
settingsDropdownGroup[DESCRIPTION_GROUP_LABEL_KEY] = groupObject[DESCRIPTION_GROUP_LABEL_KEY];
|
||||
|
||||
if (groupObject.contains(DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY)) {
|
||||
settingsDropdownGroup[DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY] = groupObject[DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY];
|
||||
}
|
||||
|
||||
static const QString DESCRIPTION_GROUP_HTML_ID_KEY = "html_id";
|
||||
if (groupObject.contains(DESCRIPTION_GROUP_HTML_ID_KEY)) {
|
||||
settingsDropdownGroup[DESCRIPTION_GROUP_HTML_ID_KEY] = groupObject[DESCRIPTION_GROUP_HTML_ID_KEY];
|
||||
|
@ -170,9 +180,6 @@ void DomainServerSettingsManager::splitSettingsDescription() {
|
|||
|
||||
// populate the settings menu groups with what we've collected
|
||||
|
||||
static const QString SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY = "domain_settings";
|
||||
static const QString SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY = "content_settings";
|
||||
|
||||
_settingsMenuGroups[SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY] = domainSettingsMenuGroups;
|
||||
_settingsMenuGroups[SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY] = contentSettingsMenuGroups;
|
||||
}
|
||||
|
@ -448,6 +455,77 @@ void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilena
|
|||
packPermissions();
|
||||
}
|
||||
|
||||
if (oldVersion < 2.4) {
|
||||
// migrate oauth settings to their own group
|
||||
const QString ADMIN_USERS = "admin-users";
|
||||
const QString OAUTH_ADMIN_USERS = "oauth.admin-users";
|
||||
const QString OAUTH_CLIENT_ID = "oauth.client-id";
|
||||
const QString ALT_ADMIN_USERS = "admin.users";
|
||||
const QString ADMIN_ROLES = "admin-roles";
|
||||
const QString OAUTH_ADMIN_ROLES = "oauth.admin-roles";
|
||||
const QString OAUTH_ENABLE = "oauth.enable";
|
||||
|
||||
QVector<QPair<const char*, const char*> > conversionMap = {
|
||||
{"key", "oauth.key"},
|
||||
{"cert", "oauth.cert"},
|
||||
{"hostname", "oauth.hostname"},
|
||||
{"oauth-client-id", "oauth.client-id"},
|
||||
{"oauth-provider", "oauth.provider"}
|
||||
};
|
||||
|
||||
for (auto & conversion : conversionMap) {
|
||||
QVariant* prevValue = _configMap.valueForKeyPath(conversion.first);
|
||||
if (prevValue) {
|
||||
auto newValue = _configMap.valueForKeyPath(conversion.second, true);
|
||||
*newValue = *prevValue;
|
||||
}
|
||||
}
|
||||
|
||||
QVariant* client_id = _configMap.valueForKeyPath(OAUTH_CLIENT_ID);
|
||||
if (client_id) {
|
||||
QVariant* oauthEnable = _configMap.valueForKeyPath(OAUTH_ENABLE, true);
|
||||
|
||||
*oauthEnable = QVariant(true);
|
||||
}
|
||||
|
||||
QVariant* oldAdminUsers = _configMap.valueForKeyPath(ADMIN_USERS);
|
||||
QVariant* newAdminUsers = _configMap.valueForKeyPath(OAUTH_ADMIN_USERS, true);
|
||||
QVariantList adminUsers(newAdminUsers->toList());
|
||||
if (oldAdminUsers) {
|
||||
QStringList adminUsersList = oldAdminUsers->toStringList();
|
||||
for (auto & user : adminUsersList) {
|
||||
if (!adminUsers.contains(user)) {
|
||||
adminUsers.append(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
QVariant* altAdminUsers = _configMap.valueForKeyPath(ALT_ADMIN_USERS);
|
||||
if (altAdminUsers) {
|
||||
QStringList adminUsersList = altAdminUsers->toStringList();
|
||||
for (auto & user : adminUsersList) {
|
||||
if (!adminUsers.contains(user)) {
|
||||
adminUsers.append(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*newAdminUsers = adminUsers;
|
||||
|
||||
QVariant* oldAdminRoles = _configMap.valueForKeyPath(ADMIN_ROLES);
|
||||
QVariant* newAdminRoles = _configMap.valueForKeyPath(OAUTH_ADMIN_ROLES, true);
|
||||
QVariantList adminRoles(newAdminRoles->toList());
|
||||
if (oldAdminRoles) {
|
||||
QStringList adminRoleList = oldAdminRoles->toStringList();
|
||||
for (auto & role : adminRoleList) {
|
||||
if (!adminRoles.contains(role)) {
|
||||
adminRoles.append(role);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*newAdminRoles = adminRoles;
|
||||
}
|
||||
|
||||
|
||||
// write the current description version to our settings
|
||||
*versionVariant = _descriptionVersion;
|
||||
|
@ -1185,7 +1263,24 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
|
|||
|
||||
return true;
|
||||
} else if (url.path() == SETTINGS_MENU_GROUPS_PATH) {
|
||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(_settingsMenuGroups).toJson(), "application/json");
|
||||
|
||||
QJsonObject settings;
|
||||
for (auto & key : _settingsMenuGroups.keys()) {
|
||||
const QJsonArray& settingGroups = _settingsMenuGroups[key].toArray();
|
||||
QJsonArray groups;
|
||||
foreach (const QJsonValue& group, settingGroups) {
|
||||
QJsonObject groupObject = group.toObject();
|
||||
QVariant* enableKey = _configMap.valueForKeyPath(groupObject[DESCRIPTION_NAME_KEY].toString() + "." + DESCRIPTION_ENABLE_KEY);
|
||||
|
||||
if (!groupObject.contains(DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY)
|
||||
|| (groupObject[DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY].toBool() && enableKey && enableKey->toBool() )) {
|
||||
groups.append(groupObject);
|
||||
}
|
||||
}
|
||||
settings[key] = groups;
|
||||
}
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(settings).toJson(), "application/json");
|
||||
|
||||
return true;
|
||||
} else if (url.path() == SETTINGS_BACKUP_PATH) {
|
||||
|
@ -1446,6 +1541,28 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt
|
|||
}
|
||||
}
|
||||
|
||||
// add 'derived' values used primarily for UI
|
||||
|
||||
const QString X509_CERTIFICATE_OPTION = "oauth.cert";
|
||||
|
||||
QString certPath = valueForKeyPath(X509_CERTIFICATE_OPTION).toString();
|
||||
if (!certPath.isEmpty()) {
|
||||
// the user wants to use the following cert and key for HTTPS
|
||||
// this is used for Oauth callbacks when authorizing users against a data server
|
||||
// let's make sure we can load the key and certificate
|
||||
|
||||
qDebug() << "Reading certificate file at" << certPath << "for HTTPS.";
|
||||
|
||||
QFile certFile(certPath);
|
||||
certFile.open(QIODevice::ReadOnly);
|
||||
|
||||
QSslCertificate sslCertificate(&certFile);
|
||||
QString digest = sslCertificate.digest().toHex(':');
|
||||
auto groupObject = responseObject["oauth"].toObject();
|
||||
groupObject["cert-fingerprint"] = digest;
|
||||
responseObject["oauth"] = groupObject;
|
||||
}
|
||||
|
||||
return responseObject;
|
||||
}
|
||||
|
||||
|
@ -1551,23 +1668,65 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson
|
|||
return QJsonObject();
|
||||
}
|
||||
|
||||
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
|
||||
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedSettingsObject,
|
||||
SettingsType settingsType) {
|
||||
|
||||
// take a write lock since we're about to overwrite settings in the config map
|
||||
QWriteLocker locker(&_settingsLock);
|
||||
|
||||
QJsonObject postedObject(postedSettingsObject);
|
||||
|
||||
static const QString SECURITY_ROOT_KEY = "security";
|
||||
static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist";
|
||||
static const QString BROADCASTING_KEY = "broadcasting";
|
||||
static const QString WIZARD_KEY = "wizard";
|
||||
static const QString DESCRIPTION_ROOT_KEY = "descriptors";
|
||||
static const QString OAUTH_ROOT_KEY = "oauth";
|
||||
static const QString OAUTH_KEY_CONTENTS = "key-contents";
|
||||
static const QString OAUTH_CERT_CONTENTS = "cert-contents";
|
||||
static const QString OAUTH_CERT_PATH = "cert";
|
||||
static const QString OAUTH_KEY_PASSPHRASE = "key-passphrase";
|
||||
static const QString OAUTH_KEY_PATH = "key";
|
||||
|
||||
auto& settingsVariant = _configMap.getConfig();
|
||||
bool needRestart = false;
|
||||
|
||||
auto& filteredDescriptionArray = settingsType == DomainSettings ? _domainSettingsDescription : _contentSettingsDescription;
|
||||
|
||||
auto oauthObject = postedObject[OAUTH_ROOT_KEY].toObject();
|
||||
if (oauthObject.contains(OAUTH_CERT_CONTENTS)) {
|
||||
QSslCertificate cert(oauthObject[OAUTH_CERT_CONTENTS].toString().toUtf8());
|
||||
if (!cert.isNull()) {
|
||||
static const QString CERT_FILE_NAME = "certificate.crt";
|
||||
auto certPath = PathUtils::getAppDataFilePath(CERT_FILE_NAME);
|
||||
QFile file(certPath);
|
||||
if (file.open(QFile::WriteOnly)) {
|
||||
file.write(cert.toPem());
|
||||
file.close();
|
||||
}
|
||||
oauthObject[OAUTH_CERT_PATH] = certPath;
|
||||
}
|
||||
oauthObject.remove(OAUTH_CERT_CONTENTS);
|
||||
}
|
||||
if (oauthObject.contains(OAUTH_KEY_CONTENTS)) {
|
||||
QString keyPassphraseString = oauthObject[OAUTH_KEY_PASSPHRASE].toString();
|
||||
QSslKey key(oauthObject[OAUTH_KEY_CONTENTS].toString().toUtf8(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8());
|
||||
if (!key.isNull()) {
|
||||
static const QString KEY_FILE_NAME = "certificate.key";
|
||||
auto keyPath = PathUtils::getAppDataFilePath(KEY_FILE_NAME);
|
||||
QFile file(keyPath);
|
||||
if (file.open(QFile::WriteOnly)) {
|
||||
file.write(key.toPem());
|
||||
file.close();
|
||||
file.setPermissions(QFile::ReadOwner | QFile::WriteOwner);
|
||||
}
|
||||
oauthObject[OAUTH_KEY_PATH] = keyPath;
|
||||
}
|
||||
oauthObject.remove(OAUTH_KEY_CONTENTS);
|
||||
}
|
||||
|
||||
postedObject[OAUTH_ROOT_KEY] = oauthObject;
|
||||
|
||||
// Iterate on the setting groups
|
||||
foreach(const QString& rootKey, postedObject.keys()) {
|
||||
const QJsonValue& rootValue = postedObject[rootKey];
|
||||
|
@ -1752,6 +1911,8 @@ void DomainServerSettingsManager::persistToFile() {
|
|||
_configMap.loadConfig();
|
||||
return; // defend against future code
|
||||
}
|
||||
|
||||
QFile(settingsFilename).setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
||||
}
|
||||
|
||||
QStringList DomainServerSettingsManager::getAllKnownGroupNames() {
|
||||
|
|
|
@ -15,9 +15,10 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <BuildInfo.h>
|
||||
#include <CrashAnnotations.h>
|
||||
#include <LogHandler.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <BuildInfo.h>
|
||||
|
||||
#include "DomainServer.h"
|
||||
|
||||
|
@ -32,6 +33,7 @@ int main(int argc, char* argv[]) {
|
|||
|
||||
// use a do-while to handle domain-server restart
|
||||
do {
|
||||
crash::annotations::setShutdownState(false);
|
||||
DomainServer domainServer(argc, argv);
|
||||
currentExitCode = domainServer.exec();
|
||||
} while (currentExitCode == DomainServer::EXIT_CODE_REBOOT);
|
||||
|
@ -39,4 +41,3 @@ int main(int argc, char* argv[]) {
|
|||
qInfo() << "Quitting.";
|
||||
return currentExitCode;
|
||||
}
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@ endif()
|
|||
downloadVcpkg = True
|
||||
|
||||
if not downloadVcpkg and not os.path.isfile(self.exe):
|
||||
print("Missing executable, boostrapping")
|
||||
print("Missing executable, boot-strapping")
|
||||
downloadVcpkg = True
|
||||
|
||||
# Make sure we have a vcpkg executable
|
||||
|
@ -265,7 +265,7 @@ endif()
|
|||
if platform.system() == 'Windows':
|
||||
url = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/qt5-install-5.12.3-windows3.tar.gz'
|
||||
elif platform.system() == 'Darwin':
|
||||
url = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/qt5-install-5.12.3-macos3.tar.gz'
|
||||
url = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/qt5-install-5.12.3-macos.tar.gz?versionId=bLAgnoJ8IMKpqv8NFDcAu8hsyQy3Rwwz'
|
||||
elif platform.system() == 'Linux':
|
||||
if platform.linux_distribution()[1][:3] == '16.':
|
||||
url = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/qt5-install-5.12.3-ubuntu-16.04-with-symbols.tar.gz'
|
||||
|
|
|
@ -70,12 +70,16 @@ file(GLOB_RECURSE INTERFACE_SRCS "src/*.cpp" "src/*.h")
|
|||
GroupSources("src")
|
||||
list(APPEND INTERFACE_SRCS ${RESOURCES_RCC})
|
||||
|
||||
# grab the Objective-C sources on OS X
|
||||
if (APPLE)
|
||||
file(GLOB_RECURSE INTERFACE_OBJCPP_SRCS "src/*.m" "src/*.mm")
|
||||
list(APPEND INTERFACE_SRCS ${INTERFACE_OBJCPP_SRCS})
|
||||
endif ()
|
||||
|
||||
# Add SpeechRecognizer if on Windows or OS X, otherwise remove
|
||||
if (WIN32)
|
||||
# Use .cpp and .h files as is.
|
||||
elseif (APPLE)
|
||||
file(GLOB INTERFACE_OBJCPP_SRCS "src/SpeechRecognizer.mm")
|
||||
set(INTERFACE_SRCS ${INTERFACE_SRCS} ${INTERFACE_OBJCPP_SRCS})
|
||||
get_filename_component(SPEECHRECOGNIZER_CPP "src/SpeechRecognizer.cpp" ABSOLUTE)
|
||||
list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP})
|
||||
else ()
|
||||
|
@ -117,6 +121,7 @@ if (APPLE)
|
|||
# configure CMake to use a custom Info.plist
|
||||
set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in)
|
||||
|
||||
set(MACOSX_BUNDLE_BUNDLE_NAME "High Fidelity")
|
||||
if (PRODUCTION_BUILD)
|
||||
set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.interface)
|
||||
else ()
|
||||
|
@ -151,7 +156,7 @@ elseif (WIN32)
|
|||
set(CONFIGURE_ICON_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Icon.rc")
|
||||
configure_file("${HF_CMAKE_DIR}/templates/Icon.rc.in" ${CONFIGURE_ICON_RC_OUTPUT})
|
||||
|
||||
set(APP_FULL_NAME "High Fidelity Interface")
|
||||
set(APP_FULL_NAME "High Fidelity")
|
||||
set(CONFIGURE_VERSION_INFO_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/VersionInfo.rc")
|
||||
configure_file("${HF_CMAKE_DIR}/templates/VersionInfo.rc.in" ${CONFIGURE_VERSION_INFO_RC_OUTPUT})
|
||||
|
||||
|
|
14
interface/external/iViewHMD/readme.txt
vendored
14
interface/external/iViewHMD/readme.txt
vendored
|
@ -1,14 +0,0 @@
|
|||
|
||||
Instructions for adding SMI HMD Eye Tracking to Interface on Windows
|
||||
David Rowe, 27 Jul 2015.
|
||||
|
||||
1. Download and install the SMI HMD Eye Tracking software from http://update.smivision.com/iViewNG-HMD.exe.
|
||||
|
||||
2. Copy the SDK folders (3rdParty, include, libs) from the SDK installation folder C:\Program Files (x86)\SMI\iViewNG-HMD\SDK
|
||||
into the interface/externals/iViewHMD folder. This readme.txt should be there as well.
|
||||
|
||||
You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different
|
||||
checkouts and different projects). If so, set the ENV variable "HIFI_LIB_DIR" to a directory containing a subfolder
|
||||
"iViewHMD" that contains the folders mentioned above.
|
||||
|
||||
3. Clear your build directory, run cmake and build, and you should be all set.
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 26 KiB |
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 26 KiB |
20
interface/interface.entitlements
Normal file
20
interface/interface.entitlements
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>high-fidelity.hifi</string>
|
||||
</array>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
interface/resources/avatar/animations/idle_aimoffsets.fbx
Normal file
BIN
interface/resources/avatar/animations/idle_aimoffsets.fbx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue