Initial commit

This commit is contained in:
2025-12-17 16:47:48 +00:00
commit 13813f3363
4964 changed files with 1079753 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
add_executable(Player
resources/qt.qrc
src/main.cpp
src/Application.cpp
src/Application.hpp
src/AvatarViewService.cpp
src/AvatarViewService.hpp
src/Document.cpp
src/Document.hpp
src/FunctionMarshaller.cpp
src/FunctionMarshaller.hpp
src/GameVerbs.cpp
src/GameVerbs.hpp
src/LauncherView.cpp
src/LauncherView.hpp
src/RenderJob.cpp
src/RenderJob.hpp
src/View.cpp
src/View.hpp
src/Window.cpp
src/Window.hpp
${CLIENT_DIR}/common/AppSettings.cpp
${CLIENT_DIR}/common/AppSettings.hpp
${CLIENT_DIR}/common/GrayChatBar.hpp
${CLIENT_DIR}/common/GrayChatBar.cpp
${CLIENT_DIR}/common/SDLGameController.cpp
${CLIENT_DIR}/common/SDLGameController.hpp
)
target_link_libraries(Player
3D
AppPlayer
Core
RakNet
BulletPhysics
NetworkPlayer
Graphics
)
if(AYA_OS_WINDOWS)
target_sources(Player PRIVATE
resources/winrc.h
resources/script.rc
)
set_target_properties(Player PROPERTIES WIN32_EXECUTABLE TRUE)
windeployqt(Player)
endif()
target_compile_definitions(Player PRIVATE QT_NO_KEYWORDS)
target_include_directories(Player PRIVATE src resources)
set_target_properties(Player PROPERTIES OUTPUT_NAME "Aya.Player")
add_custom_command(TARGET Player POST_BUILD
COMMENT "Copying runtime files to build directory"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${RUNTIME_FILES}"
"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}"
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -0,0 +1,5 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>icon.ico</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,49 @@
#include "winrc.h"
#if defined(__MINGW64__) || defined(__MINGW32__)
// MinGW-w64, MinGW
#if defined(__has_include) && __has_include(<winres.h>)
#include <winres.h>
#else
#include <afxres.h>
#include <winresrc.h>
#endif
#else
// MSVC, Windows SDK
#include <winres.h>
#endif
IDI_ICON1 ICON APP_ICON
LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_RESOURCE
PRODUCTVERSION VERSION_RESOURCE
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x4L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", APP_ORGANIZATION
VALUE "FileDescription", APP_DESCRIPTION
VALUE "FileVersion", VERSION_RESOURCE_STR
VALUE "LegalCopyright", APP_COPYRIGHT
VALUE "ProductName", APP_NAME
VALUE "ProductVersion", VERSION_RESOURCE_STR
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", PRODUCT_LANGUAGE, PRODUCT_CHARSET
END
END

View File

@@ -0,0 +1,24 @@
#pragma once
#define VERSION_MAJOR_MINOR_STR AYA_VERSION_MAJOR_STR "." AYA_VERSION_MINOR_STR
#define VERSION_MAJOR_MINOR_PATCH_STR VERSION_MAJOR_MINOR_STR "." AYA_VERSION_PATCH_STR
#ifdef AYA_VERSION_TYPE
#define VERSION_FULL_STR VERSION_MAJOR_MINOR_PATCH_STR "-" AYA_VERSION_TYPE
#else
#define VERSION_FULL_STR VERSION_MAJOR_MINOR_PATCH_STR
#endif
#define VERSION_RESOURCE AYA_VERSION_MAJOR, AYA_VERSION_MINOR, AYA_VERSION_PATCH, 0
#define VERSION_RESOURCE_STR VERSION_FULL_STR "\0"
/*
* These properties are part of VarFileInfo.
* For more information, please see: https://learn.microsoft.com/en-us/windows/win32/menurc/varfileinfo-block
*/
#define PRODUCT_LANGUAGE 0x0409 // en-US
#define PRODUCT_CHARSET 1200 // Unicode
#define APP_ICON "icon.ico"
#define APP_NAME AYA_PROJECT_NAME "\0"
#define APP_DESCRIPTION AYA_PROJECT_NAME " Player\0"
#define APP_ORGANIZATION AYA_PROJECT_NAME "\0"
#define APP_COPYRIGHT AYA_PROJECT_NAME " License\0"

View File

@@ -0,0 +1,472 @@
#include "Application.hpp"
#include "DataModel/GameBasicSettings.hpp"
#include "Reflection/Reflection.hpp"
#include "Utility/AyaService.hpp"
#include "Utility/StandardOut.hpp"
#include "View.hpp"
#include "DataModel/Game.hpp"
#include "FunctionMarshaller.hpp"
#include "Document.hpp"
#include "Utility/Statistics.hpp"
#include "DataModel/ContentProvider.hpp"
#include "Render/VisualEngine.hpp"
#include "Xml/XmlSerializer.hpp"
#include "Reflection/ReflectionMetadata.hpp"
#include "boost/any.hpp"
#include "boost/intrusive/list.hpp"
#include "Xml/WebParser.hpp"
#include "DataModel/Stats.hpp"
#include "DataModel/TeleportService.hpp"
#include "DataModel/DebugSettings.hpp"
#include "API.hpp"
#include "Utility/HttpAsync.hpp"
#include "AvatarViewService.hpp"
#include "Script/CoreScript.hpp"
#include "Script/ScriptContext.hpp"
#include <filesystem>
#include <QMessageBox>
#include <QSettings>
#include "AppSettings.hpp"
#include "winrc.h"
FASTFLAG(PlaceLauncherUsePOST)
namespace Aya
{
AYA_REGISTER_CLASS(AvatarViewService);
Application::Application()
{
gameReady = false;
launchMode = Play;
}
#ifdef _WIN32
class PrintfLogger
{
Aya::signals::scoped_connection messageConnection;
HANDLE handle;
Aya::spin_mutex mutex;
public:
PrintfLogger()
: handle(GetStdHandle(STD_OUTPUT_HANDLE))
{
messageConnection = Aya::StandardOut::singleton()->messageOut.connect(boost::bind(&PrintfLogger::onMessage, this, boost::placeholders::_1));
}
protected:
void onMessage(const Aya::StandardOutMessage& message)
{
Aya::spin_mutex::scoped_lock lock(mutex);
time_t now = time(NULL);
struct tm* timeinfo = localtime(&now);
char buffer[30];
strftime(buffer, sizeof(buffer), "[%m/%d/%Y %I:%M:%S %p]", timeinfo);
SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
printf("%s ", buffer);
const char* levelStr = "UNKNOWN";
WORD levelColor = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
switch (message.type)
{
case Aya::MESSAGE_OUTPUT:
levelColor = FOREGROUND_BLUE | FOREGROUND_INTENSITY;
levelStr = "OUTPUT";
break;
case Aya::MESSAGE_INFO:
levelColor = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
levelStr = "INFO";
break;
case Aya::MESSAGE_WARNING:
levelColor = FOREGROUND_RED | FOREGROUND_GREEN;
levelStr = "WARNING";
break;
case Aya::MESSAGE_ERROR:
levelColor = FOREGROUND_RED | FOREGROUND_INTENSITY;
levelStr = "ERROR";
break;
}
SetConsoleTextAttribute(handle, levelColor);
printf("[%s] ", levelStr);
// white
SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
printf("%s\n", message.message.c_str());
// reset
SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
}
};
#endif
static std::string readStringValue(shared_ptr<const Reflection::ValueTable> jsonResult, std::string name)
{
Reflection::ValueTable::const_iterator itData = jsonResult->find(name);
if (itData != jsonResult->end())
{
return itData->second.get<std::string>();
}
else
{
throw std::runtime_error(Aya::format("Unexpected string result for %s", name.c_str()));
}
}
static int readIntValue(shared_ptr<const Reflection::ValueTable> jsonResult, std::string name)
{
Reflection::ValueTable::const_iterator itData = jsonResult->find(name);
if (itData != jsonResult->end())
{
return itData->second.get<int>();
}
else
{
throw std::runtime_error(Aya::format("Unexpected int result for %s", name.c_str()));
}
}
Application::RequestPlaceInfoResult Application::requestPlaceInfo(
const std::string url, std::string& authenticationUrl, std::string& ticket, std::string& scriptUrl) const
{
try
{
std::string response;
if (FFlag::PlaceLauncherUsePOST)
{
std::istringstream input("");
Aya::Http(url).post(input, Aya::Http::kContentTypeDefaultUnspecified, false, response);
}
else
{
Aya::Http(url).get(response);
}
std::stringstream jsonStream;
jsonStream << response;
shared_ptr<const Reflection::ValueTable> jsonResult(Aya::make_shared<const Reflection::ValueTable>());
bool parseResult = WebParser::parseJSONTable(jsonStream.str(), jsonResult);
if (parseResult)
{
int status = readIntValue(jsonResult, "status");
if (status == 2)
{
authenticationUrl = readStringValue(jsonResult, "authenticationUrl");
ticket = readStringValue(jsonResult, "authenticationTicket");
scriptUrl = readStringValue(jsonResult, "joinScriptUrl");
return SUCCESS;
}
else if (status == 6)
return GAME_FULL;
else if (status == 10)
return USER_LEFT;
else
{
// 0 or 1 is not an error - it is a sign that we should wait
if (status == 0 || status == 1)
return RETRY;
}
}
}
catch (Aya::base_exception& e)
{
StandardOut::singleton()->printf(MESSAGE_ERROR, "Exception when requesting place info: %s. ", e.what());
}
return FAILED;
}
static HttpFuture fetchJoinScriptAsync(const std::string& url)
{
if (ContentProvider::isUrl(url) && Aya::Network::isTrustedContent(url.c_str()))
{
return HttpAsync::getWithRetries(url, 5);
}
else
{
// silent error is harder to hack
return boost::shared_future<std::string>();
}
}
void Application::Initialize(OgreWidget* window)
{
mainWindow = window;
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SYSTEM, "Welcome to %s Player v%s!", AYA_PROJECT_NAME, VERSION_FULL_STR);
// should set to governor baseurl, initial setup ask for master server url?
QSettings settings;
std::string url = settings.value("JsHelpers/masterServerUrl").toString().toStdString();
Game::globalInit(false);
std::string authenticationUrl;
std::string authenticationTicket;
std::string scriptUrl;
bool scriptIsPlaceLauncher = false;
if (vm.count("authenticationUrl") > 0 && vm.count("authenticationTicket") > 0 && vm.count("joinScriptUrl") > 0)
{
authenticationUrl = vm["authenticationUrl"].as<std::string>();
authenticationTicket = vm["authenticationTicket"].as<std::string>();
scriptUrl = vm["joinScriptUrl"].as<std::string>();
if (vm.find("browserTrackerId") != vm.end())
{
Stats::setBrowserTrackerId(vm["browserTrackerId"].as<std::string>());
TeleportService::SetBrowserTrackerId(vm["browserTrackerId"].as<std::string>());
}
std::string lowerScriptUrl = scriptUrl;
std::transform(lowerScriptUrl.begin(), lowerScriptUrl.end(), lowerScriptUrl.begin(), tolower);
if (lowerScriptUrl.find("placelauncher.ashx") != std::string::npos)
{
launchMode = Play_Protocol;
scriptIsPlaceLauncher = true;
}
}
marshaller = FunctionMarshaller::GetWindow();
TaskScheduler::singleton().setThreadCount(TaskSchedulerSettings::singleton().getThreadPoolConfig());
if (scriptUrl != "")
{
HttpFuture joinScriptResult;
if (!scriptIsPlaceLauncher)
joinScriptResult = fetchJoinScriptAsync(scriptUrl);
InitializeNewGame(joinScriptResult, GameBasicSettings::VirtualVersion::VERSION_2016);
}
}
shared_ptr<DataModel> Application::getDM()
{
return currentDocument->getGame()->getDataModel();
}
void Application::InitializeNewGame(HttpFuture& scriptResult, GameBasicSettings::VirtualVersion vv)
{
gameReady = true;
InitializeNewGame(vv);
if (boost::shared_ptr<Aya::DataModel> datamodel = currentDocument->getGame()->getDataModel())
{
currentDocument->startedSignal.connect(boost::bind(&Application::onDocumentStarted, this, _1));
datamodel->submitTask(boost::bind(&Document::StartResult, currentDocument.get(), scriptResult, launchMode, false), DataModelJob::Write);
}
}
void Application::SendScript(std::string script, GameBasicSettings::VirtualVersion virtualVersion)
{
gameReady = true;
if (boost::shared_ptr<Aya::DataModel> datamodel = currentDocument->getGame()->getDataModel())
{
currentDocument->startedSignal.connect(boost::bind(&Application::onDocumentStarted, this, _1));
datamodel->submitTask(boost::bind(&Document::Start, currentDocument.get(), script, launchMode, false), DataModelJob::Write);
datamodel->setInitialVersion(virtualVersion);
}
}
void Application::InitializeNewGame(GameBasicSettings::VirtualVersion vv)
{
mainWindow->setVisible(true);
if (currentDocument)
{
currentDocument->PrepareShutdown();
mainView->Stop();
currentDocument->Shutdown();
currentDocument.reset();
}
currentDocument.reset(new Document());
currentDocument->Initialize(mainWindow, true, true, true, vv);
mainView.reset(new View(mainWindow));
mainView->setOgreWindow(dynamic_cast<OgreWindow*>(mainWindow->getOgreWindow()));
mainView->Start(currentDocument->getGame());
initVerbs();
}
void Application::InitializeNewEmptyGame()
{
mainWindow->setVisible(true);
if (currentDocument)
{
currentDocument->PrepareShutdown();
mainView->Stop();
currentDocument->Shutdown();
currentDocument.reset();
}
Document* document = new Document();
currentDocument.reset(document);
currentDocument->Initialize(mainWindow, true, false, false, GameBasicSettings::VirtualVersion::VERSION_2012);
mainView.reset(new View(mainWindow));
mainView->Start(currentDocument->getGame());
shared_ptr<Aya::DataModel> dataModel = currentDocument->getGame()->getDataModel();
try
{
if (boost::optional<ProtectedString> source = CoreScript::fetchSource("AvatarView"))
{
if (ScriptContext* sc = Aya::ServiceProvider::create<ScriptContext>(dataModel.get()))
{
sc->executeInNewThread(Aya::Security::RobloxGameScript_, *source, "AvatarView");
}
}
}
catch (std::exception& e)
{
StandardOut::singleton()->printf(MESSAGE_ERROR, "AvatarView Error: %s", e.what());
}
initVerbs();
}
void Application::sendInputEvent(shared_ptr<InputObject> obj)
{
currentDocument->sendInputObject(obj);
}
void Application::onResize(int x, int y)
{
mainView->onResize(x, y);
}
void Application::loadAppSettings()
{
this->settings = new AppSettings(QCoreApplication::applicationDirPath().toStdString());
if (!settings->load())
{
// show error
QMessageBox::warning(
nullptr, "Error", "Failed to load application settings from AppSettings.ini. Make sure that the file exists and is free of any errors.");
this->shutdown();
}
}
void Application::parseCommandLine(int argc, char** argv)
{
std::vector<std::string> args;
for (int i = 0; i < argc; i++)
args.push_back(argv[i]);
namespace po = boost::program_options;
po::options_description desc("Options");
desc.add_options()("help,?", "produce help message")("globalBasicSettingsPath,g", po::value<std::string>(),
"path to GlobalBasicSettings_(n).xml")("version,v", "print version string")("id", po::value<int>(), "id of the place to join")("content,c",
po::value<std::string>(), "path to the content directory")("authenticationUrl,a", po::value<std::string>(), "authentication url from server")(
"authenticationTicket,t", po::value<std::string>(), "game session ticket from server")("joinScriptUrl,j", po::value<std::string>(),
"url of launch script from server")("browserTrackerId,b", po::value<std::string>(), "browser tracking id from website")(
"waitEvent,w", po::value<std::string>(), "window is invisible until this named event is signaled")(
"API", po::value<std::string>(), "output API file")("dmp,d", "upload crash dmp")("play", "specifies the launching of a game")(
"app", "specifies the launching of an app")("fast", "uses fast startup path")("console", "developer console output")(
"nochromium", "disable cef initialization")("httpClientSettings,S", "use website for clientsettings")("vite", "use local vite server")
;
po::store(po::command_line_parser(args).options(desc).run(), vm);
if (vm.count("console") > 0)
{
#ifdef _WIN32
AllocConsole();
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
freopen("CONIN$", "r", stdin);
static boost::scoped_ptr<PrintfLogger> standardOutLog(new PrintfLogger());
#else
StandardOut::singleton()->printf(MESSAGE_WARNING, "--console is not currently available on non-Windows platforms.");
#endif
}
if (vm.count("help") || args.size() == 0)
{
std::basic_stringstream<char> options;
desc.print(options);
printf("%s", options.str().c_str());
exit(-1);
}
if (vm.count("API") > 0)
{
std::string fileName = vm["API"].as<std::string>();
std::ofstream stream(fileName.c_str());
Aya::Reflection::Metadata::writeEverything(stream);
exit(-1);
}
if (vm.count("content"))
{
std::string contentDir(vm["content"].as<std::string>());
ContentProvider::setAssetFolder(contentDir.c_str());
}
// used to determine how we will initialize datamodel
if (vm.count("play"))
launchMode = Play;
}
void Application::onDocumentStarted(bool isTeleport)
{
printf("onDocumentStarted\n");
}
void Application::initVerbs()
{
DataModel* dm = currentDocument->getGame()->getDataModel().get();
leaveGameVerb.reset(new LeaveGameVerb(*mainView, dm));
Aya::ViewBase* gfx = mainView->GetGfxView();
toggleFullscreenVerb.reset(new ToggleFullscreenVerb(*mainView, dm));
}
void Application::shutdownVerbs()
{
toggleFullscreenVerb.reset();
leaveGameVerb.reset();
}
void Application::prepareToShutdown()
{
if (currentDocument) // KILL
currentDocument->PrepareShutdown();
if (mainView)
mainView->AboutToShutdown();
}
void Application::shutdown()
{
shutdownVerbs();
if (mainView)
{ // STOP
mainView->Stop();
mainView.reset();
}
if (currentDocument)
{
currentDocument->Shutdown();
currentDocument.reset();
}
Aya::GlobalBasicSettings::singleton()->saveState();
Game::globalExit();
}
} // namespace Aya

View File

@@ -0,0 +1,96 @@
#pragma once
#include "intrusive_ptr_target.hpp"
#include "DataModel/DataModel.hpp"
#include "atomic.hpp"
#include "boost/scoped_ptr.hpp"
#include "View.hpp"
#include "Document.hpp"
#include "GameVerbs.hpp"
#include "boost/program_options.hpp"
#include "Window.hpp"
#include "AppSettings.hpp"
namespace Aya
{
// more forward declarations
class Game;
class UserInput;
class RenderJob;
class LeaveGameVerb;
class ToggleFullscreenVerb;
class FunctionMarshaller;
class Document;
struct StandardOutMessage;
class View;
namespace Tasks
{
class Sequence;
}
class Application
{
boost::scoped_ptr<ToggleFullscreenVerb> toggleFullscreenVerb;
boost::scoped_ptr<LeaveGameVerb> leaveGameVerb;
boost::scoped_ptr<Document> currentDocument;
OgreWidget* mainWindow;
FunctionMarshaller* marshaller;
boost::scoped_ptr<View> mainView;
LaunchMode launchMode;
boost::program_options::variables_map vm;
std::string joinScript;
bool gameReady;
void HandleSDLMessage();
public:
enum RequestPlaceInfoResult
{
SUCCESS,
FAILED,
RETRY,
GAME_FULL,
USER_LEFT,
};
Application();
void parseCommandLine(int argc, char** argv);
bool isGameReady()
{
return gameReady;
};
RequestPlaceInfoResult requestPlaceInfo(const std::string url, std::string& authenticationUrl, std::string& ticket, std::string& scriptUrl) const;
bool requestPlaceInfo(int placeId, std::string& authenticationUrl, std::string& ticket, std::string& scriptUrl) const;
void initVerbs();
void shutdownVerbs();
void loadAppSettings();
void sendInputEvent(shared_ptr<InputObject> obj);
shared_ptr<DataModel> getDM();
void Initialize(OgreWidget* window);
void InitializeNewGame(HttpFuture& scriptResult, GameBasicSettings::VirtualVersion vv);
void InitializeNewGame(GameBasicSettings::VirtualVersion vv);
void InitializeNewEmptyGame();
void SendScript(std::string script, GameBasicSettings::VirtualVersion virtualVersion);
void onResize(int x, int y);
void onDocumentStarted(bool isTeleport);
void prepareToShutdown();
void shutdown();
boost::program_options::variables_map getVm()
{
return vm;
}
AppSettings* settings;
};
} // namespace Aya

View File

@@ -0,0 +1,48 @@
#include "AvatarViewService.hpp"
#include "LauncherView.hpp"
#include "Reflection/Reflection.hpp"
#include "Utility/StandardOut.hpp"
#include <QString>
namespace Aya
{
static Reflection::BoundFuncDesc<AvatarViewService, void(std::string)> avs_setColor(
&AvatarViewService::setColor, "SetColor", "colorJSON", Security::None);
static Reflection::BoundFuncDesc<AvatarViewService, std::string()> avs_getColor(&AvatarViewService::getColors, "GetColor", Security::None);
const char* const sAvatarViewService = "AvatarViewService";
static Reflection::EventDesc<AvatarViewService, void(int64_t, std::string)> event_assetWornSignal(
&AvatarViewService::assetWornSignal, "AssetWorn", "assetId", "assetType");
AvatarViewService::AvatarViewService()
{
setName(sAvatarViewService);
}
AvatarViewService::~AvatarViewService() {}
void AvatarViewService::setJsHelpers(JsHelpers* helpers)
{
this->helpers = helpers;
}
void AvatarViewService::setColor(std::string json)
{
if (!this->helpers)
throw std::runtime_error("helpers invalid");
this->helpers->setBodyColorJson(QString::fromStdString(json));
// this->helpers->onMapPicked()
}
std::string AvatarViewService::getColors()
{
if (!this->helpers)
throw std::runtime_error("helpers invalid");
QString json = this->helpers->getBodyColorJson();
return json.toUtf8().constData();
;
// this->helpers->onMapPicked()
}
} // namespace Aya

View File

@@ -0,0 +1,28 @@
#include "Tree/Instance.hpp"
#include "Tree/Service.hpp"
#include "LauncherView.hpp"
#include "Utility/BrickColor.hpp"
namespace Aya
{
extern const char* const sAvatarViewService;
class AvatarViewService
: public DescribedCreatable<AvatarViewService, Instance, sAvatarViewService, Reflection::ClassDescriptor::RUNTIME_LOCAL>
, public Service
{
typedef DescribedCreatable<AvatarViewService, Instance, sAvatarViewService, Reflection::ClassDescriptor::RUNTIME_LOCAL> Super;
JsHelpers* helpers;
public:
AvatarViewService();
virtual ~AvatarViewService();
void setJsHelpers(JsHelpers* helpers);
Aya::signal<void(int64_t, std::string)> assetWornSignal;
Aya::signal<void(std::string, BrickColor)> setBodyColorSignal;
void setColor(std::string json);
std::string getColors();
};
} // namespace Aya

View File

@@ -0,0 +1,250 @@
#include "DataModel/GameBasicSettings.hpp"
#include "Document.hpp"
#include "FunctionMarshaller.hpp"
#include "GameVerbs.hpp"
#include "Utility/AyaService.hpp"
#include "Script/ScriptContext.hpp"
#include "Utility/Statistics.hpp"
#include "DataModel/ContentProvider.hpp"
#include "DataModel/DataModel.hpp"
#include "DataModel/DebugSettings.hpp"
#include "DataModel/Game.hpp"
#include "DataModel/GuiService.hpp"
#include "DataModel/HackDefines.hpp"
#include "DataModel/UserInputService.hpp"
#include "DataModel/UserController.hpp"
#include "API.hpp"
#include "InitializationError.hpp"
#include "View.hpp"
#include <string>
LOGGROUP(PlayerShutdownLuaTimeoutSeconds)
namespace Aya
{
Document::Document()
: marshaller(NULL)
{
}
Document::~Document() {}
void Document::StartResult(HttpFuture& scriptResult, const LaunchMode launchMode, bool isTeleport)
{
printf("Document::startResult\n");
startedSignal(isTeleport);
executeScript(scriptResult, launchMode);
}
void Document::Start(std::string script, const LaunchMode launchMode, bool isTeleport)
{
printf("Document::start\n");
startedSignal(isTeleport);
{
Security::Impersonator impersonate(Security::COM);
if (script[0] == '{')
game->configurePlayer(Security::COM, script, launchMode);
else
{
if (shared_ptr<DataModel> dm = game->getDataModel())
{
ScriptContext* context = dm->create<ScriptContext>();
ProtectedString ps = ProtectedString::fromTrustedSource(script);
context->executeInNewThread(Security::COM, ps, "Start Game");
}
}
}
}
static void setUiMessageImpl(shared_ptr<DataModel> dm, const std::string& message)
{
StandardOut::singleton()->printf(MESSAGE_SENSITIVE, "setUiMessageImpl %s", message.c_str());
if (message.length() > 0)
{
dm->setUiMessage(message);
}
else
{
StandardOut::singleton()->printf(MESSAGE_SENSITIVE, "cleared setUiMessageImpl %s", message.c_str());
dm->clearUiMessage();
}
if (GuiService* gs = dm->create<GuiService>())
gs->setUiMessage(GuiService::UIMESSAGE_INFO, message);
}
void Document::SetUiMessage(const std::string& message)
{
if (shared_ptr<DataModel> dm = game->getDataModel())
{
dm->submitTask(boost::bind(setUiMessageImpl, dm, message), DataModelJob::Write);
}
}
void Document::PrepareShutdown()
{
// give scripts a deadline to finish
if (game && game->getDataModel())
if (FLog::PlayerShutdownLuaTimeoutSeconds > 0)
if (ScriptContext* scriptContext = game->getDataModel()->find<ScriptContext>())
scriptContext->setTimeout(FLog::PlayerShutdownLuaTimeoutSeconds);
}
void Document::Shutdown()
{
if (marshaller)
FunctionMarshaller::ReleaseWindow(marshaller);
if (game)
{
game->shutdown();
game.reset();
}
}
void Document::configureDataModelServices(bool useChat, Aya::DataModel* dataModel)
{
if (!dataModel)
return;
DataModel::LegacyLock lock(dataModel, DataModelJob::Write);
// Inform the UserInputService what kind of input we are providing (this may have to change if we use this with windows 8 touch devices)
if (UserInputService* userInputService = dataModel->find<UserInputService>())
{
userInputService->setKeyboardEnabled(true);
userInputService->setMouseEnabled(true);
}
}
void Document::Initialize(
OgreWidget* hWnd, bool useChat, bool shouldShowLoadingScreen, bool shouldShowCorescripts, GameBasicSettings::VirtualVersion vv)
{
marshaller = FunctionMarshaller::GetWindow();
game.reset(new Aya::SecurePlayerGame(NULL, GetBaseURL().c_str(), shouldShowLoadingScreen, true, vv));
AyaService* ayaService = ServiceProvider::create<AyaService>(game->getDataModel().get());
ayaService->shouldShowCorescripts = shouldShowCorescripts;
configureDataModelServices(useChat, game->getDataModel().get());
DataModel::LegacyLock lock(game->getDataModel().get(), DataModelJob::Write);
if (FLog::PlayerShutdownLuaTimeoutSeconds > 0)
game->getDataModel()->create<ScriptContext>();
game->getDataModel().get()->gameLoadedSignal.connect(boost::bind(&Document::gameIsLoaded, this));
}
void Document::sendInputObject(shared_ptr<InputObject> object)
{
if (Aya::UserInputService* userInputService = Aya::ServiceProvider::find<Aya::UserInputService>(game->getDataModel().get()))
userInputService->fireInputEvent(object, NULL);
}
void Document::gameIsLoaded()
{
// MainLogManager::getMainLogManager()->setGameLoaded();
}
// Executes the 'script' as part of the game initialization.
void Document::executeScript(HttpFuture& scriptResult, const LaunchMode launchMode) const
{
shared_ptr<Aya::DataModel> dataModel = game->getDataModel();
Security::Impersonator impersonate(Security::COM);
std::string data;
try
{
data = scriptResult.get();
printf("%s\n", data.c_str());
}
catch (const std::exception& e)
{
std::string err = Aya::format("Exception occured in Document::executeScript: %s", e.what());
// LogManager::ReportEvent(EVENTLOG_ERROR_TYPE, err.c_str());
if (GuiService* gs = dataModel->create<GuiService>())
gs->setUiMessage(GuiService::UIMESSAGE_INFO, "Unable to join game. Please try again later.");
return;
}
ProtectedString verifiedSource;
try
{
verifiedSource = ProtectedString::fromTrustedSource(data);
ContentProvider::verifyScriptSignature(verifiedSource, true);
}
catch (std::bad_alloc& e)
{
std::string err = Aya::format("Exception occured in Document::executeScript: %s", e.what());
// LogManager::ReportEvent(EVENTLOG_ERROR_TYPE, err.c_str());
throw;
}
catch (std::exception& e)
{
std::string err = Aya::format("Exception occured in Document::executeScript: %s", e.what());
// LogManager::ReportEvent(EVENTLOG_ERROR_TYPE, err.c_str());
// SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, AYA_PROJECT_NAME " error", err.c_str(), NULL);
}
if (dataModel->isClosed())
return;
int firstNewLineIndex = data.find("\r\n");
if (data[firstNewLineIndex + 2] == '{')
{
game->configurePlayer(Security::COM, data.substr(firstNewLineIndex + 2), launchMode);
}
else
{
ScriptContext* context = dataModel->create<ScriptContext>();
context->executeInNewThread(Security::COM, verifiedSource, "Start Game");
}
}
void Document::executeScript(std::string& data) const
{
shared_ptr<Aya::DataModel> dataModel = game->getDataModel();
Security::Impersonator impersonate(Security::COM);
if (dataModel->isClosed())
return;
ProtectedString verifiedSource;
verifiedSource = ProtectedString::fromTrustedSource("\r\n" + data);
ContentProvider::verifyScriptSignature(verifiedSource, true);
ScriptContext* context = dataModel->create<ScriptContext>();
context->executeInNewThread(Security::COM, verifiedSource, "Start Avatar View");
}
FunctionMarshaller* Document::GetMarshaller() const
{
return marshaller;
}
} // namespace Aya

View File

@@ -0,0 +1,71 @@
#pragma once
#include "DataModel/GameBasicSettings.hpp"
#include "intrusive_ptr_target.hpp"
#include "signal.hpp"
#include "Utility/HttpAsync.hpp"
#include "DataModel/DataModel.hpp"
#include "Window.hpp"
namespace Aya
{
enum LaunchMode
{
Play,
Play_Protocol,
Build,
Edit
};
// Forward declarations
class FunctionMarshaller;
class Game;
class View;
class PlayerConfigurer;
// Class responsible for the game state
class Document
{
public:
Aya::signal<void(bool)> startedSignal;
Document();
~Document();
void Initialize(OgreWidget* hWnd, bool useChat, bool shouldShowLoadingScreen = true, bool shouldShowCorescripts = true,
GameBasicSettings::VirtualVersion vv = GameBasicSettings::VERSION_2016);
void StartResult(HttpFuture& scriptResult, const LaunchMode launchMode, bool isTelport);
void Start(std::string script, const LaunchMode launchMode, bool isTelport); // TODO: be able to parse lua scripts
void executeScript(std::string& data) const;
void Shutdown();
void SetUiMessage(const std::string& message);
void PrepareShutdown(); // call before destroying the view
void sendInputObject(shared_ptr<InputObject> obj);
FunctionMarshaller* GetMarshaller() const;
boost::shared_ptr<Game> getGame()
{
return game;
}
private:
FunctionMarshaller* marshaller;
// The game to run.
boost::shared_ptr<Game> game;
// Executes the 'script' as part of the game initialization.
void executeScript(HttpFuture& scriptResult, const LaunchMode launchMode) const;
void configureDataModelServices(bool useChat, Aya::DataModel* dataModel);
void dataModelDidRestart();
void dataModelWillShutdown();
void gameIsLoaded();
};
} // namespace Aya

View File

@@ -0,0 +1,175 @@
// THIS FILE PURPOSE BUILT FOR SDL AND TRUCKS
#include "FunctionMarshaller.hpp"
#undef min
#undef max
#include "Utility/StandardOut.hpp"
#include "boost.hpp"
#include <QApplication>
#include <QWidget>
// ----------------------------------------------------------------------------
using namespace Aya;
const QEvent::Type Aya::TYPE_FUNCTION_MARSHALLER = static_cast<QEvent::Type>(QEvent::registerEventType());
FunctionMarshallerEvent::FunctionMarshallerEvent(void* closure)
: QEvent(TYPE_FUNCTION_MARSHALLER)
{
this->closure = closure;
}
FunctionMarshaller::FunctionMarshaller(DWORD threadID)
: refCount(0)
{
this->threadID = threadID;
}
FunctionMarshaller::~FunctionMarshaller()
{
boost::function<void()>* f;
while (asyncCalls.pop_if_present(f))
delete f;
AYAASSERT(threadID == GetCurrentThreadId());
#ifdef _DEBUG
{
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
AYAASSERT(refCount == 0);
// Nobody is using this window
AYAASSERT(staticData().windows.find(threadID) == staticData().windows.end());
}
#endif
}
FunctionMarshaller* FunctionMarshaller::GetWindow()
{
// Share a common FunctionMarshaller in a given Thread
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
DWORD threadID = GetCurrentThreadId();
std::map<DWORD, FunctionMarshaller*>::iterator find = staticData().windows.find(threadID);
if (find != staticData().windows.end())
{
// We already created a window, so use it again
find->second->refCount++;
return find->second;
}
else
{
// Create a new window
FunctionMarshaller* window = new FunctionMarshaller(threadID);
staticData().windows[threadID] = window;
window->refCount++;
return window;
}
}
void FunctionMarshaller::ReleaseWindow(FunctionMarshaller* window)
{
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
window->refCount--;
if (window->refCount == 0)
{
// Nobody is using this window
staticData().windows.erase(window->threadID);
// window->DestroyWindow();
}
}
void FunctionMarshaller::handleAppEvent(void* pClosure)
{
FunctionMarshaller::Closure* closure = (FunctionMarshaller::Closure*)pClosure;
Aya::CEvent* pWaitEvent = closure->waitEvent;
try
{
boost::function<void()>* pF = closure->f;
(*pF)();
delete pF;
delete closure;
}
catch (Aya::base_exception& e)
{
StandardOut::singleton()->print(Aya::MESSAGE_ERROR, e);
closure->errorMessage = e.what();
}
// If a task is waiting on an event, set it
if (pWaitEvent)
{
pWaitEvent->Set();
}
}
void FunctionMarshaller::freeAppEvent(void* pClosure)
{
FunctionMarshaller::Closure* closure = (FunctionMarshaller::Closure*)pClosure;
boost::function<void()>* pF = closure->f;
delete pF;
delete closure;
}
extern QApplication* qtAppPtr;
void FunctionMarshaller::Execute(boost::function<void()> job, CEvent* waitEvent)
{
if (threadID == GetCurrentThreadId())
{
job();
}
else
{
Closure* pClosure = new Closure;
pClosure->f = new boost::function<void()>(job);
pClosure->waitEvent = waitEvent;
/*SDL_Event event;
event.type = SDL_EVENT_USER;
event.user.code = 0xc10554e;
event.user.data1 = pClosure;
SDL_PushEvent(&event);*/
const QWidgetList topLevelWidgets = QApplication::topLevelWidgets();
qtAppPtr->postEvent(topLevelWidgets.at(0), new FunctionMarshallerEvent(pClosure));
}
}
void FunctionMarshaller::Submit(boost::function<void()> job)
{
Closure* pClosure = new Closure;
pClosure->f = new boost::function<void()>(job);
pClosure->waitEvent = nullptr;
/*SDL_Event event;
event.type = SDL_EVENT_USER;
event.user.code = 0xc10554e;
event.user.data1 = pClosure;
SDL_PushEvent(&event);*/
const QWidgetList topLevelWidgets = QApplication::topLevelWidgets();
qtAppPtr->postEvent(topLevelWidgets.at(0), new FunctionMarshallerEvent(pClosure));
}
void FunctionMarshaller::ProcessMessages() {}
/*
void FunctionMarshaller::OnFinalMessage(HWND hWnd)
{
delete this;
}
*/
FunctionMarshaller::StaticData::~StaticData()
{
// for (std::map<DWORD, FunctionMarshaller*>::iterator iter = windows.begin(); iter != windows.end(); ++iter)
// iter->second->DestroyWindow();
}

View File

@@ -0,0 +1,78 @@
// This file is used for two targets:
// 1. Used in Mac Roblox Player app, this is the place where actual file resides
// 2. Used in Qt version of Roblox Studio as a soft link from above
#pragma once
#include <map>
#include "threadsafe.hpp"
#include "CEvent.hpp"
#include <QEvent>
namespace Aya
{
extern const QEvent::Type TYPE_FUNCTION_MARSHALLER;
class FunctionMarshallerEvent : public QEvent
{
void* closure;
public:
explicit FunctionMarshallerEvent(void* closure);
void* getClosure()
{
return closure;
}
};
// A very handy class for marshalling a function across Windows threads (sync and async)
class FunctionMarshaller
{
public:
private:
struct StaticData
{
std::map<DWORD, FunctionMarshaller*> windows;
boost::recursive_mutex windowsCriticalSection; // TODO: Would non-recursive be safe here?
~StaticData();
};
SAFE_STATIC(StaticData, staticData)
Aya::safe_queue<boost::function<void()>*> asyncCalls;
int refCount;
DWORD threadID;
FunctionMarshaller(DWORD threadID);
~FunctionMarshaller();
public:
// TODO: Wrap with a reference counter and then remove ~StaticData() cleanup code and remove ReleaseWindow()
static FunctionMarshaller* GetWindow();
static void ReleaseWindow(FunctionMarshaller* window);
static void handleAppEvent(void* pClosure);
static void freeAppEvent(void* pClosure);
struct Closure
{
boost::function<void()>* f;
std::string errorMessage;
Aya::CEvent* waitEvent;
};
void Execute(boost::function<void()> job, CEvent* waitEvent);
void Submit(boost::function<void()> job);
// Call this only from the Window's thread
void ProcessMessages();
// virtual void OnFinalMessage(HWND hWnd);
private:
// LRESULT OnEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
// LRESULT OnAsyncEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
};
} // namespace Aya

View File

@@ -0,0 +1,72 @@
#include "intrusive_ptr_target.hpp"
#include "GameVerbs.hpp"
#include "Document.hpp"
#include "FunctionMarshaller.hpp"
#include "DataModel/RenderSettingsItem.hpp"
#include "Utility/FileSystem.hpp"
#include "Utility/Http.hpp"
#include "Utility/Statistics.hpp"
#include "DataModel/Game.hpp"
#include "DataModel/GameBasicSettings.hpp"
#include "View.hpp"
#include "Application.hpp"
extern OgreWindow* qtOgreWidget;
namespace Aya
{
LeaveGameVerb::LeaveGameVerb(View& view, VerbContainer* container)
: Verb(container, "Exit")
, v(view)
{
}
void LeaveGameVerb::doIt(IDataState* dataState)
{
// MainLogManager::getMainLogManager()->setLeaveGame();
FunctionMarshaller::GetWindow()->Submit(
[this]
{
v.CloseWindow();
OgreWindow* _ogreWindow = dynamic_cast<OgreWindow*>(qtOgreWidget);
if (!_ogreWindow)
return;
GrayChatBar* textInput = dynamic_cast<OgreWindow*>(qtOgreWidget)->getTextInput();
textInput->setVisible(false);
});
}
ToggleFullscreenVerb::ToggleFullscreenVerb(View& view, VerbContainer* container)
: Verb(container, "ToggleFullScreen")
, view(view)
{
}
bool ToggleFullscreenVerb::isChecked() const
{
return false;
}
bool ToggleFullscreenVerb::isEnabled() const
{
return false;
}
void ToggleFullscreenVerb::doIt(Aya::IDataState* dataState)
{
FASTLOG(FLog::Verbs, "Gui:ToggleFullscreen");
// view.SetFullscreen(!view.IsFullscreen());
}
} // namespace Aya

View File

@@ -0,0 +1,43 @@
#pragma once
#include "Tree/Verb.hpp"
#include "CEvent.hpp"
namespace Aya
{
class Game;
class View;
class ViewBase;
class Document;
class VideoControl;
class WebBrowserDialog;
class Application;
// Request to leave the game. Results in process shutdown.
class LeaveGameVerb : public Verb
{
View& v;
public:
LeaveGameVerb(View& v, VerbContainer* container);
virtual ~LeaveGameVerb() {}
virtual void doIt(IDataState* dataState);
};
// Request to toggle fullscreen
class ToggleFullscreenVerb : public Aya::Verb
{
private:
View& view;
public:
ToggleFullscreenVerb(View& view, VerbContainer* container);
virtual bool isChecked() const;
virtual bool isEnabled() const;
virtual void doIt(Aya::IDataState* dataState);
};
} // namespace Aya

View File

@@ -0,0 +1,17 @@
#pragma once
#include <stdexcept>
namespace Aya
{
class initialization_error : public std::runtime_error
{
public:
initialization_error(const char* const errorMessage)
: std::runtime_error(errorMessage)
{
}
};
} // namespace Aya

View File

@@ -0,0 +1,656 @@
#include "LauncherView.hpp"
#include "Utility/BrickColor.hpp"
#include "AvatarViewService.hpp"
#include "Tree/Service.hpp"
#include "Application.hpp"
#include "Window.hpp"
#include "DataModel/ContentProvider.hpp"
#include "Utility/Statistics.hpp"
#include <QWebEngineScript>
#include <QWebEngineScriptCollection>
#include <QFileDialog>
#include <QInputDialog>
#include <QDialog>
#include <QVBoxLayout>
#include <QPushButton>
#include <QProgressDialog>
#include <QFrame>
#include <QWebEngineSettings>
#include <QSettings>
#include <QDialogButtonBox>
#include <QTabWidget>
#include <QLabel>
#include <QMessageBox>
#include <QApplication>
#include <QNetworkRequest>
#include <QCheckBox>
#include <qnetworkrequest.h>
#include <QString>
#include <qsettings.h>
#include "winrc.h"
#include <rapidjson/document.h>
LauncherView::LauncherView(QWidget* parent)
: QWebEngineView(parent)
{
channel = new QWebChannel(page());
page()->setWebChannel(channel);
setContextMenuPolicy(Qt::NoContextMenu);
jsHelpers = new JsHelpers();
channel->registerObject(QStringLiteral("transport"), jsHelpers);
QFile apiFile(":/qtwebchannel/qwebchannel.js"); // load the API from the resources
if (!apiFile.open(QIODevice::ReadOnly))
qDebug() << "Couldn't load Qt's QWebChannel API!";
QString apiScript = QString::fromLatin1(apiFile.readAll());
apiFile.close();
QWebEngineScript script;
script.setSourceCode(apiScript);
script.setName("qtwebchannel.js");
script.setWorldId(QWebEngineScript::MainWorld);
script.setInjectionPoint(QWebEngineScript::DocumentCreation);
script.setRunsOnSubFrames(false);
page()->scripts().insert(script);
std::string assetFolderPath = Aya::ContentProvider::assetFolder();
QString qAssetFolderPath = QString::fromStdString(assetFolderPath);
#ifdef AYA_TEST_BUILD
QUrl url = QUrl("http://localhost:5173");
#else
QUrl url = QUrl::fromLocalFile(qAssetFolderPath + "/app/index.html");
#endif
QWebEngineSettings* settings = page()->settings();
settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
settings->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, true);
settings->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
settings->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
settings->setAttribute(QWebEngineSettings::DnsPrefetchEnabled, true);
settings->setAttribute(QWebEngineSettings::Accelerated2dCanvasEnabled, true);
#ifdef AYA_TEST_BUILD
// Enable DevTools
settings->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true);
#endif
page()->load(url);
page()->setBackgroundColor(Qt::transparent); // to see avatar when customizing
}
JsHelpers::JsHelpers(QWidget* parentWidget)
{
reloadSettings();
netManager = new QNetworkAccessManager();
this->parentWidget = parentWidget;
QSettings settings;
selectedFilePath = settings.value("JsHelpers/selectedFilePath").toString().toStdString();
}
void JsHelpers::reloadSettings()
{
QSettings settings;
masterServerAuthorization = settings.value("JsHelpers/masterServerAuthorization", "").toString().toStdString();
masterServerUrl = settings.value("JsHelpers/masterServerUrl").toString().toStdString();
robloSecurityCookie = settings.value("JsHelpers/robloSecurityCookie").toString().toStdString();
currentServerAuthorization = "";
}
extern QApplication* qtAppPtr;
int JsHelpers::getCompileTime()
{
return AYA_BUILD_TIMESTAMP;
}
QString JsHelpers::getPlatformName()
{
return AYA_PLATFORM_NAME;
}
QString JsHelpers::getCompilerName()
{
return AYA_COMPILER_ID;
}
QString JsHelpers::getVersion()
{
return VERSION_FULL_STR;
}
bool JsHelpers::isUsingInstance()
{
return IsUsingInstance();
}
QString JsHelpers::getInstanceURL()
{
return QString::fromStdString(GetBaseURL());
}
QString JsHelpers::getInstanceName()
{
return QString::fromStdString(GetInstanceName());
}
QString JsHelpers::getInstanceMotd()
{
return QString::fromStdString(GetInstanceMotd());
}
QString JsHelpers::getMasterServerURL()
{
if (masterServerUrl == "")
{
QMessageBox::critical(parentWidget, "Request Error", "There is no master server set. Use Advanced Settings to select one.");
return QString();
}
return QString::fromStdString(masterServerUrl);
}
void JsHelpers::setSetting(QString setting, QString value)
{
QSettings settings;
settings.beginGroup("JsHelpers");
settings.setValue(setting, value);
settings.endGroup();
}
void JsHelpers::setMasterServerURL(QString url)
{
QSettings settings;
settings.setValue("JsHelpers/masterServerUrl", url);
}
void JsHelpers::setRobloSecurityCookie(QString cookie)
{
QSettings settings;
settings.setValue("JsHelpers/robloSecurityCookie", cookie);
}
QString JsHelpers::getRobloSecurityCookie()
{
if (robloSecurityCookie == "")
{
return QString();
}
return QString::fromStdString(robloSecurityCookie);
}
QString JsHelpers::grabServers()
{
if (masterServerUrl == "")
{
QMessageBox::critical(parentWidget, "Request Error", "There is no master server set. Use Advanced Settings to select one.");
return QString();
}
QUrl url = QUrl::fromUserInput(QString::fromStdString(masterServerUrl) + "/ping");
printf("%s\n", url.toString().toStdString().c_str());
if (!url.isValid())
{
QMessageBox::critical(parentWidget, "Request Error", "The URL for the master server is invalid.");
return QString();
}
QNetworkRequest req = QNetworkRequest();
if (masterServerAuthorization != "")
{
req.setRawHeader("Authorization", QByteArray(masterServerAuthorization.c_str(), masterServerAuthorization.length()));
}
req.setUrl(url);
QNetworkReply* reply = netManager->get(req);
QNetworkReply::NetworkError error; // = reply->error();
QProgressDialog progress("Grabbing servers...", "Cancel", 0, 1, parentWidget);
progress.setWindowModality(Qt::WindowModal);
while (!reply->isFinished())
{
progress.setValue(0);
progress.update();
qtAppPtr->processEvents();
}
progress.close();
error = reply->error();
if (error)
{
printf("Error %i '%s'\n", error, reply->errorString().toStdString().c_str());
QMessageBox::critical(parentWidget, "Request Error", "Aya could not reach the selected master server.");
}
QByteArray responseB = reply->readAll();
QString response(responseB);
printf("%s\n", response.toStdString().c_str());
return response;
}
QString JsHelpers::userName()
{
QSettings settings;
QString username = settings.value("JsHelpers/Username").toString();
if (username.isEmpty())
{
QString sysUsername = qEnvironmentVariable("USER");
if (sysUsername.isEmpty())
sysUsername = qEnvironmentVariable("USERNAME");
settings.setValue("JsHelpers/Username", sysUsername);
return sysUsername;
}
return username;
}
QString JsHelpers::characterAppearanceJSON()
{
QSettings settings;
QString json = settings.value("JsHelpers/CharApp").toString();
if (json.isEmpty())
{
settings.setValue("JsHelpers/CharApp", "[]");
return QString("[]");
}
return json;
}
void JsHelpers::setBodyColorJson(QString json)
{
QSettings settings;
settings.setValue("JsHelpers/BodyColors", json);
}
QString JsHelpers::getBodyColorJson()
{
QSettings settings;
return settings.value("JsHelpers/BodyColors").toString();
}
void JsHelpers::setCharacterAppearanceJSON(QString json)
{
QSettings settings;
settings.setValue("JsHelpers/CharApp", json);
}
void JsHelpers::setUserName(QString username)
{
QSettings settings;
settings.setValue("JsHelpers/Username", username);
}
extern OgreWindow* qtOgreWidget;
void JsHelpers::enableChatBarWidget()
{
OgreWindow* _ogreWindow = dynamic_cast<OgreWindow*>(qtOgreWidget);
if (!_ogreWindow)
return;
GrayChatBar* textInput = dynamic_cast<OgreWindow*>(qtOgreWidget)->getTextInput();
textInput->setVisible(true);
}
void JsHelpers::launchStudio()
{
OgreWindow* _ogreWindow = dynamic_cast<OgreWindow*>(qtOgreWidget);
if (!_ogreWindow)
return;
QString studioPath = "Aya.Studio";
#ifdef _WIN32
studioPath += ".exe";
#endif
if (QFile::exists(studioPath))
{
QProcess::startDetached(studioPath);
}
else
{
QMessageBox::critical(parentWidget, "Cannot launch Studio", "Aya Studio was not found in the current directory");
}
}
void JsHelpers::openMapPickerDialog()
{
QString filename = QFileDialog::getOpenFileName(nullptr, tr("Open Place File"), QString(), tr("Level (*.rbxl)"));
selectedFilePath = filename.toStdString();
QSettings settings;
settings.setValue("JsHelpers/selectedFilePath", filename);
Q_EMIT onMapPicked(filename);
}
class MotdDialog : public QDialog
{
Q_OBJECT
QWebEngineView* webView;
QPushButton* okButton;
void okButtonPressed()
{
close();
}
public:
MotdDialog(QString motd, QWidget* parent = NULL)
: QDialog(parent)
{
QVBoxLayout* layout = new QVBoxLayout(this);
setMinimumSize(0, 0);
QSettings qsettings;
restoreGeometry(qsettings.value("Player/MotdGeometry").toByteArray());
setAttribute(Qt::WA_DeleteOnClose);
webView = new QWebEngineView();
webView->setHtml(motd);
webView->setMinimumSize(300, 200);
QWebEngineSettings* settings = webView->page()->settings();
settings->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
settings->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
settings->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, false);
settings->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, false);
settings->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, false);
settings->setAttribute(QWebEngineSettings::WebGLEnabled, false);
settings->setAttribute(QWebEngineSettings::Accelerated2dCanvasEnabled, false);
settings->setAttribute(QWebEngineSettings::AutoLoadIconsForPage, false);
settings->setAttribute(QWebEngineSettings::DnsPrefetchEnabled, false);
settings->setAttribute(QWebEngineSettings::JavascriptCanPaste, false);
settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, false);
layout->addWidget(webView);
okButton = new QPushButton();
okButton->setText("OK");
okButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
layout->addWidget(okButton);
setWindowTitle("Message of the day");
connect(okButton, &QPushButton::pressed, this, &MotdDialog::okButtonPressed);
}
~MotdDialog()
{
QSettings settings;
settings.setValue("Player/MotdGeometry", saveGeometry());
}
};
void JsHelpers::presentMotd(QString motd)
{
MotdDialog* d = new MotdDialog(motd, parentWidget);
d->show();
}
bool JsHelpers::readPassword(const QString& type, bool read)
{
const QString MASTER_SERVER = "masterServer";
const QString CURRENT_SERVER = "serverPassword";
if (read)
{
bool ok;
QString key = QInputDialog::getText(parentWidget, tr("Enter key"), tr("Enter key"), QLineEdit::Password, QString(), &ok);
if (ok)
{
if (type == MASTER_SERVER)
{
QSettings settings;
masterServerAuthorization = key.toStdString();
settings.setValue("JsHelpers/masterServerAuthorization", key);
}
else if (type == CURRENT_SERVER)
{
currentServerAuthorization = key.toStdString();
}
}
else
{
if (type == CURRENT_SERVER)
{
currentServerAuthorization = "";
}
}
}
if (type == MASTER_SERVER)
{
return !masterServerAuthorization.empty();
}
else if (type == CURRENT_SERVER)
{
return !masterServerAuthorization.empty();
}
return false;
}
bool JsHelpers::hasServer()
{
QString fname;
#ifdef _WIN32
fname = "Aya.Server.exe";
#else
fname = "Aya.Server";
#endif
return (QFile::exists(fname));
}
extern Aya::Application* appPtr;
void JsHelpers::wearAsset(int64_t assetId, QString category)
{
// TODO: at a later date
/*QUrl url = QUrl::fromUserInput(QString::fromStdString(masterServerUrl) + "/wear");
printf("%s\n", url.toString().toStdString().c_str());
if (!url.isValid())
{
QMessageBox::critical(parentWidget, "Request Error", "The URL for the master server is invalid.");
return;
}
QUrlQuery postData;
postData.addQueryItem("assetId", assetId);
QNetworkRequest req = QNetworkRequest();
if (masterServerAuthorization != "")
{
req.setRawHeader("Authorization", QByteArray(masterServerAuthorization.c_str(), masterServerAuthorization.length()));
}
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
req.setUrl(url);
QNetworkReply* reply = netManager->post(req, postData.toString(QUrl::FullyEncoded).toUtf8());
connect(reply, &QNetworkReply::finished, this, [=](){
if(reply->error()) {
QMessageBox::critical(parentWidget, "Request Error", reply->errorString());
}
}); */
Aya::AvatarViewService* avatarViewService = Aya::ServiceProvider::create<Aya::AvatarViewService>(appPtr->getDM().get());
avatarViewService->assetWornSignal(assetId, category.toStdString());
}
void JsHelpers::setBodyColor(QString bodyPart, int bodyColor)
{
Aya::AvatarViewService* avatarViewService = Aya::ServiceProvider::create<Aya::AvatarViewService>(appPtr->getDM().get());
avatarViewService->setBodyColorSignal(bodyPart.toStdString(), Aya::BrickColor(bodyColor));
}
class SettingsDialog : public QDialog
{
struct SettingsEntry
{
QString name; // visual
QString property; //
QString type;
int tab;
QWidget* widget;
QString filePathValue; // path value, used only in `file' mode
};
std::vector<SettingsEntry> entries;
void addEntry(QString name, QString property, QString type, int tab = 0)
{
SettingsEntry e;
e.name = name;
e.property = property;
e.type = type;
e.tab = tab;
entries.push_back(e);
};
QTabWidget* tabWidget;
QDialogButtonBox* buttonBox;
void accept()
{
QSettings settings;
for (auto& e : entries)
{
if (e.type == "password" || e.type == "text")
{
settings.setValue(e.property, ((QLineEdit*)e.widget)->text());
}
else if (e.type == "bool")
{
Qt::CheckState cs = ((QCheckBox*)e.widget)->checkState();
settings.setValue(e.property, cs == Qt::Checked);
}
else if (e.type == "file")
{
settings.setValue(e.property, e.filePathValue);
}
}
QDialog::accept();
}
void openButton() {}
public:
SettingsDialog(QWidget* parent = NULL)
{
setAttribute(Qt::WA_DeleteOnClose);
tabWidget = new QTabWidget;
QWidget* generalTab = new QWidget();
QWidget* serverTab = new QWidget();
QWidget* creditsTab = new QWidget();
tabWidget->addTab(generalTab, tr("General"));
tabWidget->addTab(serverTab, tr("Server"));
tabWidget->addTab(creditsTab, tr("Credits"));
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
QVBoxLayout* mainLayout = new QVBoxLayout;
mainLayout->addWidget(tabWidget);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
QVBoxLayout* generalLayout = new QVBoxLayout;
QVBoxLayout* serverLayout = new QVBoxLayout;
std::vector<QVBoxLayout*> tgtLayouts;
tgtLayouts.push_back(generalLayout);
tgtLayouts.push_back(serverLayout);
generalLayout->addStretch(1);
generalLayout->setAlignment(Qt::AlignTop);
generalTab->setLayout(generalLayout);
serverLayout->addStretch(1);
serverLayout->setAlignment(Qt::AlignTop);
serverTab->setLayout(serverLayout);
// HOW TO ADD NEW TABS:
// create a QWidget for your tab
// create a box layout for the QWidget
// add it to tgtLayouts, then use the tab parameter of addEntry to set which settings get put where
addEntry("Master Server URL", "JsHelpers/masterServerUrl", "text");
addEntry("Master Server Key", "JsHelpers/masterServerAuthorization", "password");
addEntry("Host Server Password", "JsHelpers/serverHostPassword", "password");
addEntry("Server MOTD Preview Use Place File", "JsHelpers/serverMotdPreviewUsePlaceFile", "bool", 1);
addEntry("Server MOTD Preview", "JsHelpers/serverMotdPreview", "text", 1);
addEntry("Server MOTD File (*.html, *.txt)", "JsHelpers/serverMotdFile", "file", 1);
QSettings settings;
for (auto& e : entries)
{
QLabel* label = new QLabel(e.name);
QVariant val = settings.value(e.property);
tgtLayouts[e.tab]->addWidget(label);
e.widget = NULL;
if (e.type == "password" || e.type == "text")
{
QLineEdit* ed = new QLineEdit(val.toString());
if (e.type == "password")
ed->setEchoMode(QLineEdit::Password);
e.widget = ed;
}
else if (e.type == "bool")
{
QCheckBox* cb = new QCheckBox();
cb->setCheckState(val.toBool() ? Qt::Checked : Qt::Unchecked);
e.widget = cb;
}
else if (e.type == "file")
{
QPushButton* pb = new QPushButton();
pb->setText("Select File");
QObject::connect(pb, &QPushButton::clicked,
[this, &e](bool checked)
{
e.filePathValue = QFileDialog::getOpenFileName(this, tr("Select File"), QString(), e.name);
});
e.widget = pb;
} // TODO: file picker
if (e.widget)
tgtLayouts[e.tab]->addWidget(e.widget);
}
QFile creditsFile(":/credits.txt"); // load the credits from the resources
if (!creditsFile.open(QIODevice::ReadOnly))
qDebug() << "Couldn't load Credits!";
QString credits = QString::fromLatin1(creditsFile.readAll());
creditsFile.close();
QLabel* tbdLabel = new QLabel(credits);
tbdLabel->setWordWrap(true);
QVBoxLayout* creditsLayout = new QVBoxLayout;
creditsLayout->setAlignment(Qt::AlignTop);
creditsLayout->addWidget(tbdLabel);
creditsTab->setLayout(creditsLayout);
}
};
void JsHelpers::openSettings()
{
SettingsDialog* d = new SettingsDialog();
d->exec();
reloadSettings();
}
#include "LauncherView.moc"

View File

@@ -0,0 +1,123 @@
#pragma once
#include <QWebEngineView>
#include <QtWebChannel>
#include <QObject>
#include <qwebengineview.h>
#include <thread>
#include <QNetworkAccessManager>
class JsHelpers : public QObject
{
Q_OBJECT
QWidget* parentWidget;
QNetworkAccessManager* netManager;
std::string masterServerAuthorization;
std::string currentServerAuthorization;
std::string masterServerUrl;
std::string selectedFilePath;
std::string robloSecurityCookie;
void reloadSettings();
public:
std::string getCurrentServerAuthorization()
{
return currentServerAuthorization;
};
std::string getFilePath()
{
return selectedFilePath;
}
JsHelpers(QWidget* parentWidget = NULL);
public Q_SLOTS:
// send game configuration to aya; can use lua script
void launchGame(const QString& gameConfiguration, const int& virtualVersion)
{
Q_EMIT onLaunchingGame(gameConfiguration, virtualVersion);
};
void startAvatarView()
{
Q_EMIT onStartAvatarView();
};
void stopAvatarView()
{
Q_EMIT onStopAvatarView();
};
// open settings panel
void openSettings();
// return JSON string from server endpoint (should be set after using openSettings)
QString grabServers();
QString getMasterServerURL();
QString getRobloSecurityCookie();
// get current username or logged in username
QString userName();
QString characterAppearanceJSON();
void setSetting(QString setting, QString value);
void setMasterServerURL(QString url);
void setRobloSecurityCookie(QString cookie);
void setUserName(QString string);
void enableChatBarWidget();
void setCharacterAppearanceJSON(QString json);
void launchStudio();
int getCompileTime();
QString getPlatformName();
QString getCompilerName();
QString getVersion();
bool isUsingInstance();
QString getInstanceName();
QString getInstanceMotd();
QString getInstanceURL();
// get current map
QString currentMap()
{
return QString::fromStdString(selectedFilePath);
}
// selects map
void openMapPickerDialog();
// present motd for server
void presentMotd(QString motd);
// start rccservice in another thread and connect using lua script
void startRcc(const QString& serverConfiguration, int port, int virtualVersion, bool forceVirtualVersion)
{
Q_EMIT onStartRcc(serverConfiguration, port, virtualVersion, forceVirtualVersion);
};
// reads to password type if read is true
// returns true if type is set, returns false if type is blank
// types: "masterServer", "serverPassword"
bool readPassword(const QString& type, bool read);
bool hasServer();
void wearAsset(int64_t assetId, QString category);
void setBodyColorJson(QString json);
QString getBodyColorJson();
void setBodyColor(QString bodyPart, int bodyColor);
Q_SIGNALS:
void onStartAvatarView();
void onStopAvatarView();
void onLaunchingGame(const QString& gameConfiguration, const int& virtualVersion);
void onMapPicked(QString& filePicked);
void onStartRcc(const QString& serverConfiguration, int port, int virtualVersion, bool forceVirtualVersion);
};
class LauncherView : public QWebEngineView
{
Q_OBJECT
QWebChannel* channel;
JsHelpers* jsHelpers;
public:
LauncherView(QWidget* parent);
JsHelpers* getJsHelpers()
{
return jsHelpers;
}
};

View File

@@ -0,0 +1,219 @@
#include "RenderJob.hpp"
#include "FunctionMarshaller.hpp"
#include "Base/ViewBase.hpp"
#include "Base/FrameRateManager.hpp"
#include "API.hpp"
#include "Players.hpp"
#include "Log.hpp"
#include "SystemUtil.hpp"
#include "DataModel/RenderSettingsItem.hpp"
#include "DataModel/HackDefines.hpp"
#include "DataModel/ModelInstance.hpp"
#include "DataModel/Workspace.hpp"
#include "View.hpp"
#include "boost/format.hpp"
#include "boost/weak_ptr.hpp"
FASTFLAG(RenderLowLatencyLoop)
namespace Aya
{
RenderJob::RenderJob(View* robloxView, FunctionMarshaller* marshaller, boost::shared_ptr<DataModel> dataModel)
: BaseRenderJob(CRenderSettingsItem::singleton().getMinFrameRate(), CRenderSettingsItem::singleton().getMaxFrameRate(), dataModel)
, robloxView(robloxView)
, marshaller(marshaller)
, stopped(0)
, prepareBeginEvent(false)
, prepareEndEvent(false)
{
}
void RenderJob::stop()
{
stopped = 1;
}
Time::Interval RenderJob::timeSinceLastRender() const
{
return Time::now<Time::Fast>() - lastRenderTime;
}
Time::Interval RenderJob::sleepTime(const Stats& stats)
{
if (isAwake)
return computeStandardSleepTime(stats, maxFrameRate);
else
return Aya::Time::Interval::max();
}
static void remoteCheatHelper(boost::weak_ptr<DataModel> weakDataModel)
{
boost::shared_ptr<DataModel> dataModel = weakDataModel.lock();
if (dataModel)
{
// this will send special item to server and server will kick user off
Network::getSystemUrlLocal(dataModel.get());
}
}
static void reportHacker(boost::weak_ptr<DataModel> weakDataModel, const char* stat)
{
if (boost::shared_ptr<DataModel> dataModel = weakDataModel.lock())
{
if (Network::Players* players = dataModel->find<Network::Players>())
{
if (Network::Player* player = players->getLocalPlayer())
{
player->reportStat(stat);
}
}
}
}
void RenderJob::scheduleRender(weak_ptr<RenderJob> selfWeak, ViewBase* view, double timeJobStart)
{
shared_ptr<RenderJob> self = selfWeak.lock();
if (!self)
return;
self->prepareBeginEvent.Wait();
view->renderPrepare(self.get());
self->prepareEndEvent.Set();
view->renderPerform(timeJobStart);
self->wake();
}
static void scheduleRenderPerform(const weak_ptr<RenderJob>& selfWeak, ViewBase* view, double timeJobStart)
{
if (shared_ptr<RenderJob> self = selfWeak.lock())
{
view->renderPerform(timeJobStart);
self->wake();
}
}
TaskScheduler::StepResult RenderJob::stepDataModelJob(const Stats& stats)
{
boost::shared_ptr<DataModel> dm = robloxView->getDataModel();
if (!dm || stopped)
return TaskScheduler::Done;
double timeJobStart = Time::nowFastSec();
ViewBase* view = robloxView->GetGfxView();
if (!FFlag::RenderLowLatencyLoop)
{
Aya::DataModel::scoped_write_request request(dm.get());
const double renderDelta = timeSinceLastRender().seconds();
lastRenderTime = Aya::Time::now<Aya::Time::Fast>();
isAwake = false;
marshaller->Submit(boost::bind(&scheduleRender, weak_from(this), view, timeJobStart));
dm->renderStep(renderDelta);
prepareBeginEvent.Set();
prepareEndEvent.Wait(1000);
}
else
{
{
DataModel::scoped_write_request request(robloxView->getDataModel().get());
float secondsElapsed = robloxView->GetGfxView()->getFrameRateManager()->GetFrameTimeStats().getLatest() / 1000.f;
dm->renderStep(secondsElapsed);
isAwake = false;
marshaller->Execute(boost::bind(&ViewBase::renderPrepare, view, this), NULL);
lastRenderTime = Time::now<Time::Fast>();
}
{
marshaller->Submit(boost::bind(&scheduleRenderPerform, weak_from(this), view, timeJobStart));
}
}
return TaskScheduler::Stepped;
}
// Return information (via IMetric interface, probable asker was DataModel)
std::string RenderJob::getMetric(const std::string& metric) const
{
if (metric == "Graphics Mode")
return Aya::Reflection::EnumDesc<CRenderSettings::GraphicsMode>::singleton().convertToString(robloxView->GetLatchedGraphicsMode());
if (metric == "Render")
{
boost::format fmt("%.1f/s %d%%");
fmt % averageStepsPerSecond() % (int)(100.0 * averageDutyCycle());
return fmt.str();
}
ViewBase* view = robloxView->GetGfxView();
Aya::FrameRateManager* frm = view ? view->getFrameRateManager() : 0;
if (frm)
{
if (metric == "FRM")
return (frm && frm->IsBlockCullingEnabled()) ? "On" : "Off";
if (metric == "Anti-Aliasing")
return (frm && frm->getAntialiasingMode() == CRenderSettings::AntialiasingOn) ? "On" : "Off";
}
// If we got here it means we were explicitly asked for information we do
// not have.
AYAASSERT(0);
return "?";
}
// Return information (via IMetric interface, probable asker was DataModel)
double RenderJob::getMetricValue(const std::string& metric) const
{
ViewBase* view = robloxView->GetGfxView();
if (metric == "Render Duty")
return averageDutyCycle();
if (metric == "Render FPS")
return averageStepsPerSecond();
if (metric == "Render Job Time")
return averageStepTime();
if (metric == "Render Nominal FPS")
return 1000.0 / view->getFrameRateManager()->GetRenderTimeAverage();
if (metric == "Delta Between Renders")
return view->getMetricValue(metric);
if (metric == "Total Render")
return view->getMetricValue(metric);
if (metric == "Present Time")
return view->getMetricValue(metric);
if (metric == "GPU Delay")
return view->getMetricValue(metric);
// If we got here it means we were explicitly asked for information we do
// not have.
AYAASSERT(0);
return 0.0;
}
} // namespace Aya

View File

@@ -0,0 +1,57 @@
#pragma once
#include <boost/weak_ptr.hpp>
#include "DataModel/BaseRenderJob.hpp"
#include "DataModel/DataModel.hpp"
#include "time.hpp"
#include "TaskScheduler.hpp"
#include "TaskScheduler.Job.hpp"
#include "Utility/IMetric.hpp"
namespace Aya
{
class FunctionMarshaller;
class View;
class ViewBase;
// This job calls ViewBase::render(), which needs to be done exclusive to the
// DataModel. This is why it has the DataModelJob::Render enum, which
// prevents concurrent writes to DataModel. It also needs to run in the view's
// thread for OpenGL.
// TODO: Can Ogre be modified to not require the thread?
class RenderJob
: public BaseRenderJob
, public IMetric
{
FunctionMarshaller* marshaller;
View* robloxView;
volatile int stopped;
CEvent prepareBeginEvent;
CEvent prepareEndEvent;
static void scheduleRender(weak_ptr<RenderJob> selfWeak, ViewBase* view, double timeJobStart);
public:
RenderJob(View* robloxView, FunctionMarshaller* marshaller, boost::shared_ptr<DataModel> dataModel);
Time::Interval timeSinceLastRender() const;
Time::Interval sleepTime(const Stats& stats);
virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats);
virtual std::string getMetric(const std::string& metric) const;
virtual double getMetricValue(const std::string& metric) const;
void stop();
};
} // namespace Aya

283
client/player/src/View.cpp Normal file
View File

@@ -0,0 +1,283 @@
#include "DataModel/DebugSettings.hpp"
#include "DataModel/GameBasicSettings.hpp"
#include "Script/ScriptContext.hpp"
#include "FunctionMarshaller.hpp"
#include <qguiapplication_platform.h>
#include <qpa/qplatformnativeinterface.h>
#include "Coordinator.hpp"
#include "RenderJob.hpp"
#include "DataModel/RenderSettingsItem.hpp"
#include "Utility/ScopedAssign.hpp"
#include "DataModel/Game.hpp"
#include "DataModel/UserController.hpp"
#include "InitializationError.hpp"
#include "View.hpp"
#include "format_string.hpp"
#include "Log.hpp"
#include "SystemUtil.hpp"
#include "Render/VisualEngine.hpp"
LOGGROUP(PlayerShutdownLuaTimeoutSeconds)
LOGGROUP(RobloxWndInit)
FASTFLAGVARIABLE(GraphicsReportingInitErrorsToGAEnabled, true)
FASTFLAGVARIABLE(UseNewAppBridgeInputWindows, false)
FASTFLAGVARIABLE(GraphicsEnableBGFX, false)
DYNAMIC_FASTFLAGVARIABLE(FullscreenRefocusingFix, false)
namespace
{
OgreWidget* SetFocusWrapper(OgreWidget* hwnd)
{
return hwnd;
}
} // namespace
namespace Aya
{
static const char* kSavedScreenSizeRegistryKey = "HKEY_CURRENT_USER\\Software\\Kiseki\\Kiseki\\Settings\\RobloxPlayerV4WindowSizeAndPosition";
View::View(OgreWidget* h)
: marshaller(NULL)
{
#ifdef __linux__
QPlatformNativeInterface* native = QGuiApplication::platformNativeInterface();
context.hWnd = native->nativeResourceForWindow("surface", h);
context.display = native->nativeResourceForIntegration("display");
#else
context.hWnd = (HWND)h->winId();
#endif
context.width = h->width();
context.height = h->height();
marshaller = FunctionMarshaller::GetWindow();
// window = dynamic_cast<OgreWindow*>(h->parentWidget());
initializeView();
}
View::~View()
{
AYAASSERT(!this->game && "Call Stop() before shutting down!");
view.reset();
if (marshaller)
FunctionMarshaller::ReleaseWindow(marshaller);
}
void View::AboutToShutdown() {}
void View::initializeView()
{
ViewBase::InitPluginModules();
char* rgLogSuffix[5];
memset(rgLogSuffix, 0, sizeof(rgLogSuffix));
rgLogSuffix[(size_t)CRenderSettings::BGFX] = "gfx_bgfx";
rgLogSuffix[(size_t)CRenderSettings::OpenGL] = "gfx_gl";
std::vector<CRenderSettings::GraphicsMode> modes;
Aya::CRenderSettings::GraphicsMode graphicsMode = CRenderSettingsItem::singleton().getLatchedGraphicsMode();
// this is very experimental right now
if (FFlag::GraphicsEnableBGFX)
modes.push_back(CRenderSettings::BGFX);
modes.push_back(CRenderSettings::OpenGL);
std::string lastMessage;
bool success = false;
size_t modei = 0;
printf("%i modes\n", modes.size());
while (!success && modei < modes.size())
{
graphicsMode = modes[modei];
try
{
view.reset(ViewBase::CreateView(graphicsMode, &context, &CRenderSettingsItem::singleton()));
view->initResources();
success = true;
}
catch (std::exception& e)
{
// d9mz - gutted dx, so we don't need to report this anymore
printf("Mode %d failed: \"%s\"\n", graphicsMode, e.what());
lastMessage += e.what();
lastMessage += " | ";
modei++;
if (modei < modes.size())
{
printf("Trying mode %d...\n", modes[modei]);
}
}
}
if (!success)
{
throw initialization_error("Failed to initialize view");
}
AYAASSERT(view);
initializeSizes();
}
void View::resetScheduler()
{
TaskScheduler& taskScheduler = TaskScheduler::singleton();
taskScheduler.add(renderJob);
}
void View::initializeJobs()
{
shared_ptr<DataModel> dataModel = game->getDataModel();
renderJob.reset(new RenderJob(this, marshaller, dataModel));
}
void View::initializeInput()
{
/*userInput.reset(new UserInput(GetHWnd(), game, this));
if (userInput)
{
DataModel::LegacyLock lock(game->getDataModel(), DataModelJob::Write);
ControllerService* service = ServiceProvider::create<ControllerService>(game->getDataModel().get());
service->setHardwareDevice(userInput.get());
}*/
}
void View::RemoveJobs()
{
if (renderJob)
{
boost::function<void()> callback = boost::bind(&FunctionMarshaller::ProcessMessages, marshaller);
TaskScheduler::singleton().removeBlocking(renderJob, callback);
}
// RenderJob is sure to be completed at this point, since removeBlocking returned - but it might have marshalled
// renderPerform asynchronously before exiting, which means that we might still have a callback that uses this view
// in the marshaller queue.
// This makes sure that all pending marshalled events are processed to avoid a use after free.
marshaller->ProcessMessages();
// All render processing is complete; it's safe to reset job pointers now
renderJob.reset();
}
shared_ptr<DataModel> View::getDataModel()
{
return game ? game->getDataModel() : shared_ptr<DataModel>();
}
CRenderSettings::GraphicsMode View::GetLatchedGraphicsMode()
{
return CRenderSettingsItem::singleton().getLatchedGraphicsMode();
}
void View::initializeSizes()
{
if (GetHWnd() == NULL)
{
// LogManager::ReportEvent(EVENTLOG_WARNING_TYPE, "Attempt to initialize monitor sizes without valid HWND");
return;
}
G3D::Vector2int16 currentDisplaySize = G3D::Vector2int16(800, 600);
G3D::Vector2int16 fullscreenSize, windowSize;
Aya::CRenderSettings::ResolutionPreset preference = CRenderSettingsItem::singleton().getResolutionPreference();
if (preference == Aya::CRenderSettings::ResolutionAuto)
{
fullscreenSize = currentDisplaySize;
}
else
{
const Aya::CRenderSettings::RESOLUTIONENTRY& res = CRenderSettingsItem::singleton().getResolutionPreset(preference);
fullscreenSize.x = res.width;
fullscreenSize.y = res.height;
}
// validate mode
windowSize = fullscreenSize;
windowSize.x = std::min((int)windowSize.x, (int)currentDisplaySize.x);
windowSize.y = std::min((int)windowSize.y, (int)currentDisplaySize.y);
CRenderSettingsItem::singleton().setWindowSize(windowSize);
CRenderSettingsItem::singleton().setFullscreenSize(fullscreenSize);
}
void View::onResize(int x, int y)
{
printf("%i, %i\n", x, y);
view->onResize(x, y);
}
void View::unbindWorkspace()
{
shared_ptr<DataModel> dm = getDataModel();
DataModel::LegacyLock lock(dm, DataModelJob::Write);
view->bindWorkspace(boost::shared_ptr<DataModel>());
}
void View::bindWorkspace()
{
shared_ptr<DataModel> dm = getDataModel();
DataModel::LegacyLock lock(dm, DataModelJob::Write);
view->bindWorkspace(game->getDataModel());
view->buildGui();
}
void View::Start(const shared_ptr<Game>& game)
{
AYAASSERT(!this->game);
this->game = game;
bindWorkspace();
initializeJobs();
initializeInput(); // NOTE: have to do this here, Input requires datamodel access
resetScheduler();
// ensure keyboard is in focus (DE6272)
// if (userInput)
// userInput->setKeyboardDesired(true);
}
void View::Stop()
{
AYAASSERT(this->game);
this->RemoveJobs();
if (game && game->getDataModel())
if (Aya::ControllerService* service = Aya::ServiceProvider::create<Aya::ControllerService>(game->getDataModel().get()))
service->setHardwareDevice(NULL);
// if (userInput)
// {
// userInput->removeJobs();
// userInput.reset();
// }
unbindWorkspace();
game.reset();
}
void View::CloseWindow()
{
window->closeGame();
}
} // namespace Aya

View File

@@ -0,0 +1,84 @@
#pragma once
#include "Base/ViewBase.hpp"
#include "Window.hpp"
namespace Aya
{
// Forward declarations
class FunctionMarshaller;
class Game;
struct OSContext;
class RenderJob;
class ViewBase;
namespace Tasks
{
class Sequence;
}
// Class responsible for the game view
class View
{
OgreWindow* window;
public:
View(OgreWidget* h);
~View();
void setOgreWindow(OgreWindow* wnd) { window = wnd; }
void AboutToShutdown();
void Start(const boost::shared_ptr<Game>& game);
void Stop();
void ShowWindow();
void CloseWindow();
OgreWidget* GetHWnd() const
{
return static_cast<OgreWidget*>(context.hWnd);
}
// TODO: refactor verbs so this isn't needed
ViewBase* GetGfxView() const
{
return view.get();
}
CRenderSettings::GraphicsMode GetLatchedGraphicsMode();
boost::shared_ptr<DataModel> getDataModel();
void onResize(int x, int y);
private:
// View references, but doesn't own the game
boost::shared_ptr<Game> game;
// The OS context used by the view.
OSContext context;
// The view into the game world.
boost::scoped_ptr<Aya::ViewBase> view;
boost::shared_ptr<Tasks::Sequence> sequence;
boost::shared_ptr<RenderJob> renderJob;
FunctionMarshaller* marshaller;
void initializeSizes();
void bindWorkspace();
void unbindWorkspace();
void initializeView();
void initializeInput();
void resetScheduler();
void initializeJobs();
void RemoveJobs();
};
} // namespace Aya

View File

@@ -0,0 +1,978 @@
#include "Window.hpp"
#include <QSurfaceFormat>
#include "DataModel/GameBasicSettings.hpp"
#include "FunctionMarshaller.hpp"
#include "Application.hpp"
#include "DataModel/UserInputService.hpp"
#include "DataModel/PlayerGui.hpp"
#include "Players.hpp"
#include "API.hpp"
#include "Tree/Service.hpp"
#include "Utility/AyaService.hpp"
#include "AvatarViewService.hpp"
#include "Utility/StandardOut.hpp"
#include "DataModel/Lighting.hpp"
#include "DataModel/Workspace.hpp"
#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QApplication>
#include <QVBoxLayout>
#include <codecvt>
#include <locale>
#include <boost/algorithm/string.hpp>
#include <qnamespace.h>
#ifdef __linux
#include <sys/select.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#endif
extern QApplication* qtAppPtr;
extern Aya::Application* appPtr;
OgreWidget::OgreWidget(QWidget* ogreWindow, QScreen* parent)
: QWindow(parent)
{
this->ogreWindow = ogreWindow;
disableClosures = false;
setMinimumSize(QSize(100, 100));
setMaximumSize(QSize(3800, 2100));
setCursor(Qt::BlankCursor);
firstClosure = false;
connect(dynamic_cast<OgreWindow*>(ogreWindow)->getTextInput(), &GrayChatBar::enteredText, this, &OgreWidget::onChatBarEnteredText);
}
void OgreWidget::resizeEvent(QResizeEvent* event)
{
QSize size = event->size();
appPtr->onResize(size.width(), size.height());
}
void OgreWidget::onChatBarEnteredText(const QString& _text)
{
if (Aya::Network::Players* players = Aya::ServiceProvider::create<Aya::Network::Players>(appPtr->getDM().get()))
{
try
{
std::string text = _text.toStdString();
boost::trim(text);
players->chat(text);
}
catch (std::exception& e)
{
printf("%s\n", e.what());
}
// return focus to the ogre widget...
// setFocus();
}
}
OgreWindow::OgreWindow(QWidget* parent)
: QWidget(parent)
{
QVBoxLayout* layout = new QVBoxLayout(this);
QWidget* innerWidget = new QWidget(this);
QHBoxLayout* innerLayout = new QHBoxLayout(innerWidget);
launcherView = new LauncherView(innerWidget);
setWindowIcon(QIcon(":/icon.ico"));
textInput = new GrayChatBar(this);
myOgreWidget = new OgreWidget(this, QGuiApplication::primaryScreen());
// myOgreWidget->setSurfaceType(QWindow::RasterSurface);
myOgreWidget->create();
myOgreWidgetContainer = QWidget::createWindowContainer(myOgreWidget);
myOgreWidgetContainer->setAttribute(Qt::WA_PaintOnScreen, true);
myOgreWidgetContainer->setAttribute(Qt::WA_NativeWindow, true);
myOgreWidgetContainer->setAttribute(Qt::WA_DontCreateNativeAncestors, true);
myOgreWidgetContainer->setAttribute(Qt::WA_OpaquePaintEvent, true);
myOgreWidgetContainer->setAttribute(Qt::WA_NoSystemBackground, true);
myOgreWidgetContainer->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
myOgreWidgetContainer->setContextMenuPolicy(Qt::PreventContextMenu);
myOgreWidgetContainer->setMouseTracking(true);
myOgreWidgetContainer->setFocusPolicy(Qt::StrongFocus);
myOgreWidgetContainer->setAutoFillBackground(true);
myOgreWidgetContainer->setAcceptDrops(true);
setMinimumSize(800, 600);
setMaximumSize(800, 600);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
myOgreWidgetContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
textInput->setFocusPolicy(Qt::NoFocus);
innerLayout->addWidget(launcherView, 2);
innerLayout->addWidget(myOgreWidgetContainer, 1);
innerLayout->setContentsMargins(0, 0, 0, 0);
innerLayout->setSpacing(0);
layout->addWidget(innerWidget, 1);
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(textInput, 1);
launcherView->setVisible(false);
Aya::GameBasicSettings::singleton().virtualVersionChangedSignal.connect(boost::bind(&OgreWindow::handleVirtualVersionChange, this, _1));
connect(launcherView->getJsHelpers(), &JsHelpers::onStartAvatarView, this, &OgreWindow::startAvatarView);
connect(launcherView->getJsHelpers(), &JsHelpers::onStopAvatarView, this, &OgreWindow::stopAvatarView);
connect(launcherView->getJsHelpers(), &JsHelpers::onLaunchingGame, this, &OgreWindow::launchGame);
connect(launcherView->getJsHelpers(), &JsHelpers::onStartRcc, this, &OgreWindow::startRcc);
QSettings settings;
restoreGeometry(settings.value("Player/Geometry").toByteArray());
}
OgreWindow::~OgreWindow()
{
QSettings settings;
settings.setValue("Player/Geometry", saveGeometry());
if (runRcc)
shutdownRcc();
}
void OgreWindow::closeGame()
{
printf("OgreWindow::closeGame\n");
myOgreWidgetContainer->setVisible(false);
appPtr->prepareToShutdown();
appPtr->shutdown();
if (runRcc)
shutdownRcc();
setMaximumSize(800, 600);
launcherView->setVisible(true);
launcherView->repaint();
#ifdef _WIN32
HWND hwnd = (HWND)winId();
LONG style = GetWindowLong(hwnd, GWL_STYLE) & ~(WS_MAXIMIZEBOX | WS_SIZEBOX);
SetWindowLong(hwnd, GWL_STYLE, style);
SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
#endif
setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint);
}
void OgreWindow::closeEvent(QCloseEvent* ev)
{
appPtr->prepareToShutdown();
qtAppPtr->quit();
}
void OgreWindow::shutdownRcc()
{
if (!runRcc)
return;
runRcc = false;
if (rccThread.joinable())
rccThread.join();
}
#ifdef _WIN32
std::wstring MultiByteToWideString(const std::string& str)
{
int size_needed = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), NULL, 0);
std::wstring wstrTo(size_needed, 0);
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), &wstrTo[0], size_needed);
return wstrTo;
}
#endif
#define CAPTURE_SERVER_STDOUT
void OgreWindow::rccThreadProc(std::atomic<bool>& runAtomic, std::string password, OgreWindow* window, int port, int virtualVersion)
{
LauncherView* lview = window->getLauncherView();
JsHelpers* jhp = lview->getJsHelpers();
Aya::GameBasicSettings::VirtualVersion vv = static_cast<Aya::GameBasicSettings::VirtualVersion>(virtualVersion);
std::string vvStr = std::to_string(virtualVersion);
std::vector<char*> argv;
#ifdef _WIN32
// argv.push_back("Aya.Server.exe");
#else
argv.push_back("Aya.Server");
#endif
argv.push_back("--contentPath");
argv.push_back("content");
argv.push_back("--port");
argv.push_back("-1"); // disable SOAP http port
argv.push_back("--virtualVersion");
argv.push_back((char*)vvStr.c_str()); // disable SOAP http port
argv.push_back("--localServer");
argv.push_back("--noconsole");
if (!password.empty())
{
argv.push_back("--password");
argv.push_back((char*)password.c_str());
}
// TODO: msHost, msName, msMotdPreview, msMotdFile
QSettings settings;
std::string key = settings.value("JsHelpers/masterServerAuthorization").toString().toStdString();
std::string url = settings.value("JsHelpers/masterServerUrl").toString().toStdString();
std::string place = jhp->getFilePath();
std::string username = settings.value("JsHelpers/Username").toString().toStdString();
if (url != "")
{
argv.push_back("--masterServerUrl");
argv.push_back((char*)url.c_str());
argv.push_back("--baseUrl");
argv.push_back((char*)url.c_str());
if (key != "")
{
argv.push_back("--masterServerAuthorization");
argv.push_back((char*)key.c_str());
}
}
if (place != "")
{
place = "\"file://" + place + "\"";
argv.push_back("--localServerPlace");
argv.push_back((char*)place.c_str());
}
argv.push_back("--localServerPort");
std::string _port = std::to_string(port);
argv.push_back((char*)_port.c_str());
argv.push_back("--msHost");
argv.push_back((char*)username.c_str());
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_INFO, "dmkmm: %s", username.c_str());
#if defined(__linux__) && !defined(__APPLE__)
FILE* xterm = fopen("/usr/bin/xterm", "r");
if (!xterm)
{
throw std::runtime_error("You do not have xterm installed, or it is not readable by your current user");
}
fclose(xterm);
pid_t pid;
pid = fork();
if (pid == -1)
{
throw std::runtime_error("fork() returned -1");
}
else if (pid == 0)
{
pthread_setname_np(pthread_self(), "Server Execution Thread");
char currentPath[PATH_MAX];
char serverPath[PATH_MAX];
std::string cmdline;
getcwd(currentPath, PATH_MAX);
snprintf(serverPath, PATH_MAX, "%s/Aya.Server", currentPath);
argv[0] = serverPath;
for (auto p : argv)
cmdline += std::string(p) + " ";
printf("%s\n", cmdline.c_str());
argv.clear();
argv.push_back("xterm");
argv.push_back("-e");
argv.push_back((char*)cmdline.c_str());
std::string xterm_cmdline = "";
for (auto p : argv)
xterm_cmdline += std::string(p) + " ";
printf("%s\n", xterm_cmdline.c_str());
snprintf(serverPath, PATH_MAX, "/usr/bin/xterm");
argv.push_back(NULL);
int r = execv(serverPath, argv.data());
printf("Error (%s) %i\n", serverPath, r);
}
else
{
pthread_setname_np(pthread_self(), "Server Manager Thread");
while (runAtomic)
{
sleep(1);
std::this_thread::yield();
}
kill(pid, SIGKILL);
printf("Sent sigkill to pid %i\n", pid);
}
#elif defined(AYA_OS_WINDOWS)
STARTUPINFOW si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
HANDLE hReadPipe, hWritePipe;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// Set up the security attributes struct
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
#ifdef CAPTURE_SERVER_STDOUT
// Create a pipe for the child process's STDOUT
if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0))
{
throw std::runtime_error("CreatePipe failed");
}
// Ensure the read handle to the pipe for STDOUT is not inherited
SetHandleInformation(hReadPipe, HANDLE_FLAG_INHERIT, 0);
si.hStdError = hWritePipe;
si.hStdOutput = hWritePipe;
si.dwFlags |= STARTF_USESTDHANDLES;
#endif
wchar_t currentPath[MAX_PATH];
wchar_t serverPath[MAX_PATH];
GetCurrentDirectoryW(MAX_PATH, currentPath);
_snwprintf_s(serverPath, MAX_PATH, _TRUNCATE, L"%s\\Aya.Server.exe", currentPath);
wchar_t cmdLine[MAX_PATH];
std::string args;
for (const auto& arg : argv)
{
args += arg;
args += " ";
}
std::wstring widestr = MultiByteToWideString(args);
std::wstring fullCommandLine = serverPath;
fullCommandLine += L" ";
fullCommandLine += MultiByteToWideString(args);
printf("args %ls", fullCommandLine.c_str());
int dwProcess = CreateProcessW(serverPath, // Module name
fullCommandLine.data(), // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
TRUE, // Set handle inheritance to TRUE
CREATE_UNICODE_ENVIRONMENT, // Creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure
);
if (dwProcess)
{
wprintf(L"Process created successfully with PID %lu\n", pi.dwProcessId);
}
else
{
DWORD dwLastErrorCode = GetLastError();
wprintf(L"CreateProcessW failed with error code: %d\n", dwLastErrorCode);
}
// Set thread name
SetThreadDescription(GetCurrentThread(), L"Server Manager Thread");
#ifdef CAPTURE_SERVER_STDOUT
char buffer[4096];
DWORD bytesRead;
DWORD bytesAvail;
#endif
while (runAtomic)
{
#ifdef CAPTURE_SERVER_STDOUT
// Check if there's any output to read
if (PeekNamedPipe(hReadPipe, NULL, 0, NULL, &bytesAvail, NULL) && bytesAvail > 0)
{
if (ReadFile(hReadPipe, buffer, sizeof(buffer) - 1, &bytesRead, NULL) && bytesRead > 0)
{
buffer[bytesRead] = '\0';
// Convert to wide string for Unicode-correct output
int wideSize = MultiByteToWideChar(CP_UTF8, 0, buffer, -1, NULL, 0);
std::wstring wideStr(wideSize, 0);
MultiByteToWideChar(CP_UTF8, 0, buffer, -1, &wideStr[0], wideSize);
wprintf(L"%s", wideStr.c_str());
}
}
#endif
Sleep(1000); // Sleep for 1 second
std::this_thread::yield();
}
#ifdef CAPTURE_SERVER_STDOUT
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
#endif
// Terminate the child process
TerminateProcess(pi.hProcess, 0);
wprintf(L"Terminated process with PID %lu\n", pi.dwProcessId);
// Close process and thread handles
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
#elif defined(__APPLE__)
/// Show Message Box TTo User displaying different options of fruits and produce to choose from.
#endif
}
void OgreWindow::startRcc(const QString& serverConfiguration, int port, int virtualVersion, bool forceVirtualVersion)
{
Aya::GameBasicSettings::VirtualVersion vv = static_cast<Aya::GameBasicSettings::VirtualVersion>(virtualVersion);
myOgreWidget->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
enterWidget();
QSettings settings;
std::string password = settings.value("JsHelpers/serverHostPassword").toString().toStdString();
runRcc = true;
rccThread = std::thread(&OgreWindow::rccThreadProc, std::ref(runRcc), password, this, port, virtualVersion);
appPtr->InitializeNewGame(vv);
appPtr->SendScript(serverConfiguration.toStdString(), vv);
Aya::AyaService::localServer = true;
if (password != "")
{
Aya::Network::setPassword(password.c_str());
}
}
void OgreWindow::launchGame(const QString& gameConfiguration, const int& virtualVersion)
{ // will be static casted to enum.
Aya::GameBasicSettings::VirtualVersion vv = static_cast<Aya::GameBasicSettings::VirtualVersion>(virtualVersion);
myOgreWidget->show();
myOgreWidget->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
enterWidget();
appPtr->InitializeNewGame(vv);
appPtr->SendScript(gameConfiguration.toStdString(), vv);
std::string password = launcherView->getJsHelpers()->getCurrentServerAuthorization();
if (password != "")
{
Aya::Network::setPassword(password.c_str());
}
}
void OgreWindow::startAvatarView()
{
myOgreWidget->show();
myOgreWidgetContainer->setMaximumWidth(QWIDGETSIZE_MAX);
myOgreWidget->setCursor(Qt::OpenHandCursor);
isShowingAvatarPage = true;
appPtr->InitializeNewEmptyGame();
Aya::Workspace* w = Aya::ServiceProvider::find<Aya::Workspace>(appPtr->getDM().get());
w->setImageServerView(false);
w->getCamera()->zoom(-2);
Aya::AvatarViewService* avs = Aya::ServiceProvider::create<Aya::AvatarViewService>(appPtr->getDM().get());
avs->setJsHelpers(launcherView->getJsHelpers());
Aya::Lighting* lighting = Aya::ServiceProvider::create<Aya::Lighting>(appPtr->getDM().get());
lighting->suppressSky(true);
lighting->setClearAlpha(0);
}
void OgreWindow::stopAvatarView()
{
Aya::AvatarViewService* avs = Aya::ServiceProvider::create<Aya::AvatarViewService>(appPtr->getDM().get());
avs->setJsHelpers(NULL);
myOgreWidgetContainer->setVisible(false);
appPtr->prepareToShutdown();
appPtr->shutdown();
launcherView->setVisible(true);
isShowingAvatarPage = false;
}
void OgreWindow::enterWidget()
{
printf("OgreWindow::enterWidget\n");
launcherView->setVisible(false);
}
void OgreWindow::hideOgreWidget()
{
printf("OgreWindow::hideOgreWidget\n");
launcherView->setVisible(true);
myOgreWidgetContainer->setVisible(false);
}
void OgreWindow::handleVirtualVersionChange(Aya::GameBasicSettings::VirtualVersion version)
{
if (version == Aya::GameBasicSettings::VERSION_2012)
{
if (Aya::StarterGuiService* gui = Aya::ServiceProvider::create<Aya::StarterGuiService>(appPtr->getDM().get()))
{
if (gui->getCoreGuiEnabled(Aya::StarterGuiService::COREGUI_CHATGUI) && !isShowingAvatarPage)
{
textInput->setVisible(true);
}
}
}
else
{
textInput->setVisible(false);
}
}
bool OgreWindow::event(QEvent* event)
{
if (event->type() == Aya::TYPE_FUNCTION_MARSHALLER)
{
return myOgreWidget->event(event);
}
return QWidget::event(event);
}
void OgreWindow::changeEvent(QEvent* event)
{
if (event->type() == QEvent::PaletteChange)
{
QPalette palette = this->palette();
QString mode = palette.color(QPalette::Window).lightness() < 128 ? "dark" : "light";
launcherView->page()->runJavaScript(QString("document.documentElement.setAttribute('data-mode', '%1');").arg(mode));
}
QWidget::changeEvent(event);
}
// SCARY LAND OF HANDLING EVENTS
static int translateKeyModifiers(Qt::KeyboardModifiers state, const QString& text)
{
// Convert modifier combos to a single integer.
int result = 0;
if ((state & Qt::ShiftModifier) && (text.size() == 0 || !text.at(0).isPrint() || text.at(0).isLetter() || text.at(0).isSpace()))
result |= Qt::SHIFT;
if (state & Qt::ControlModifier)
result |= Qt::CTRL;
if (state & Qt::MetaModifier)
result |= Qt::META;
if (state & Qt::AltModifier)
result |= Qt::ALT;
return result;
}
static Aya::KeyCode keyCodeTOUIKeyCode(int keyCode)
{
/// Handle all albhabets
if (keyCode >= 65 && keyCode <= 90)
return Aya::KeyCode(keyCode - Qt::Key_A + 'a');
// Handle Numbers
if (keyCode >= Qt::Key_0 && keyCode <= Qt::Key_9)
return Aya::KeyCode(keyCode);
// Handle F1 to F15
if (keyCode >= Qt::Key_F1 && keyCode <= Qt::Key_F15)
return Aya::KeyCode(keyCode - 0xFFFF16);
Aya::KeyCode rbxKey = Aya::SDLK_UNKNOWN;
// Handle Special Unordered Keys Here
switch (keyCode)
{
case Qt::Key_CapsLock:
rbxKey = Aya::SDLK_CAPSLOCK;
break;
case Qt::Key_Backspace:
rbxKey = Aya::SDLK_BACKSPACE;
break;
case Qt::Key_Up:
rbxKey = Aya::SDLK_UP;
break;
case Qt::Key_Down:
rbxKey = Aya::SDLK_DOWN;
break;
case Qt::Key_Left:
rbxKey = Aya::SDLK_LEFT;
break;
case Qt::Key_Right:
rbxKey = Aya::SDLK_RIGHT;
break;
case Qt::Key_Insert:
rbxKey = Aya::SDLK_INSERT;
break;
case Qt::Key_Delete:
rbxKey = Aya::SDLK_DELETE;
break;
case Qt::Key_Home:
rbxKey = Aya::SDLK_HOME;
break;
case Qt::Key_End:
rbxKey = Aya::SDLK_END;
break;
case Qt::Key_PageUp:
rbxKey = Aya::SDLK_PAGEUP;
break;
case Qt::Key_PageDown:
rbxKey = Aya::SDLK_PAGEDOWN;
break;
case Qt::Key_Space:
rbxKey = Aya::SDLK_SPACE;
break;
case Qt::Key_Control:
rbxKey = Aya::SDLK_LCTRL;
break;
case Qt::Key_Alt:
rbxKey = Aya::SDLK_LALT;
break;
case Qt::Key_Shift:
rbxKey = Aya::SDLK_LSHIFT;
break;
case Qt::Key_Escape:
rbxKey = Aya::SDLK_ESCAPE;
break;
case Qt::Key_Minus:
rbxKey = Aya::SDLK_MINUS;
break;
case Qt::Key_Equal:
rbxKey = Aya::SDLK_EQUALS;
break;
case Qt::Key_Tab:
rbxKey = Aya::SDLK_TAB;
break;
case Qt::Key_Backtab:
{
rbxKey = Aya::SDLK_TAB;
break;
}
case Qt::Key_BracketLeft:
rbxKey = Aya::SDLK_LEFTBRACKET;
break;
case Qt::Key_BracketRight:
rbxKey = Aya::SDLK_RIGHTBRACKET;
break;
case Qt::Key_Return:
case Qt::Key_Enter:
rbxKey = Aya::SDLK_RETURN;
break;
case Qt::Key_Semicolon:
rbxKey = Aya::SDLK_SEMICOLON;
break;
case Qt::Key_QuoteLeft:
rbxKey = Aya::SDLK_BACKQUOTE;
break;
case Qt::Key_Apostrophe:
rbxKey = Aya::SDLK_QUOTE;
break;
case Qt::Key_QuoteDbl:
rbxKey = Aya::SDLK_QUOTEDBL;
break;
case Qt::Key_Backslash:
rbxKey = Aya::SDLK_BACKSLASH;
break;
case Qt::Key_Comma:
rbxKey = Aya::SDLK_COMMA;
break;
case Qt::Key_Period:
rbxKey = Aya::SDLK_PERIOD;
break;
case Qt::Key_Slash:
rbxKey = Aya::SDLK_SLASH;
break;
case Qt::Key_multiply:
rbxKey = Aya::SDLK_KP_MULTIPLY;
break;
case Qt::Key_NumLock:
rbxKey = Aya::SDLK_NUMLOCK;
break;
case Qt::Key_ScrollLock:
rbxKey = Aya::SDLK_SCROLLOCK;
break;
case Qt::Key_Asterisk:
rbxKey = Aya::SDLK_ASTERISK;
break;
case Qt::Key_Plus:
rbxKey = Aya::SDLK_PLUS;
break;
}
return rbxKey;
}
static Aya::ModCode modifiersToUIModCode(int modifier)
{
unsigned int modCode = 0;
if (modifier & Qt::ShiftModifier)
{
modCode = modCode | Aya::KMOD_LSHIFT;
}
if (modifier & Qt::ControlModifier)
{
modCode = modCode | Aya::KMOD_LCTRL;
}
if (modifier & Qt::AltModifier)
{
modCode = modCode | Aya::KMOD_LALT;
}
return (Aya::ModCode)modCode;
}
void OgreWidget::sendMouseButtonEvents(QMouseEvent* me, QPoint delta, QPoint position)
{
Aya::InputObject::UserInputType t;
Qt::MouseButton mb = me->button();
Aya::InputObject::UserInputType type;
switch (mb)
{
case Qt::LeftButton:
type = Aya::InputObject::TYPE_MOUSEBUTTON1;
break;
case Qt::RightButton:
type = Aya::InputObject::TYPE_MOUSEBUTTON2;
break;
case Qt::MiddleButton:
type = Aya::InputObject::TYPE_MOUSEBUTTON3;
break;
default:
return;
}
OgreWindow* _ogreWindow = qobject_cast<OgreWindow*>(ogreWindow);
Aya::InputObject::UserInputState state;
switch (me->type())
{
case QEvent::MouseButtonPress:
state = Aya::InputObject::INPUT_STATE_BEGIN;
if (_ogreWindow->isShowingAvatarPage)
setCursor(Qt::ClosedHandCursor);
break;
case QEvent::MouseButtonRelease:
state = Aya::InputObject::INPUT_STATE_END;
if (_ogreWindow->isShowingAvatarPage)
setCursor(Qt::OpenHandCursor);
break;
default:
return;
}
boost::shared_ptr<Aya::InputObject> inputObject1 = boost::make_shared<Aya::InputObject>(
type, state, Aya::Vector3(position.x(), position.y(), 0), Aya::Vector3(delta.x(), delta.y(), 0), appPtr->getDM().get());
appPtr->sendInputEvent(inputObject1);
oldMouseButtons = me->buttons();
}
bool OgreWidget::event(QEvent* event)
{
if (event->type() == Aya::TYPE_FUNCTION_MARSHALLER)
{
if (disableClosures)
return false;
setVisible(true);
Aya::FunctionMarshaller::GetWindow()->handleAppEvent((static_cast<Aya::FunctionMarshallerEvent*>(event))->getClosure());
firstClosure = true;
return true;
}
else
{
if (!firstClosure)
return false;
OgreWindow* _ogreWindow = dynamic_cast<OgreWindow*>(ogreWindow);
if (!_ogreWindow)
return false;
if (!isVisible())
return false;
GrayChatBar* textInput = dynamic_cast<OgreWindow*>(ogreWindow)->getTextInput();
if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)
{
QKeyEvent* ke = static_cast<QKeyEvent*>(event);
Aya::InputObject::UserInputState state;
switch (ke->type())
{
case QEvent::KeyPress:
state = Aya::InputObject::INPUT_STATE_BEGIN;
break;
case QEvent::KeyRelease:
state = Aya::InputObject::INPUT_STATE_END;
break;
default:
return false;
}
if (ke->key() == Qt::Key_Slash && textInput->isVisible())
{
textInput->setFocus();
return false;
}
if (ke->key() == Qt::Key_Tab && ke->modifiers() & Qt::AltModifier)
{
return false;
}
if (ke->isAutoRepeat())
return false;
int keyCode = ke->key() | translateKeyModifiers(ke->modifiers(), ke->text());
std::string text = ke->text().toStdString();
boost::shared_ptr<Aya::InputObject> inputObject =
boost::make_shared<Aya::InputObject>(Aya::InputObject::TYPE_KEYBOARD, state, keyCodeTOUIKeyCode(ke->key()),
modifiersToUIModCode(ke->modifiers()), (text.length() > 0) ? text.c_str()[0] : NULL, appPtr->getDM().get());
appPtr->sendInputEvent(inputObject);
return false;
}
else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease)
{
QMouseEvent* me = static_cast<QMouseEvent*>(event);
QPoint delta = me->pos() - lastPosition;
lastPosition = me->pos();
QPoint position = me->pos();
sendMouseButtonEvents(me, delta, position);
return true;
}
else if (event->type() == QEvent::MouseMove)
{
QMouseEvent* me = static_cast<QMouseEvent*>(event);
QPoint savedLastPosition = lastPosition;
QPoint delta = me->pos() - lastPosition;
if (delta.isNull())
return true;
float scale = Aya::GameBasicSettings::singleton().getMouseSensitivity();
lastPositionFrac += delta * scale;
QPoint newPos = lastPosition + lastPositionFrac;
delta = newPos - lastPosition;
lastPositionFrac -= delta;
lastPosition = newPos;
QPoint position = me->pos();
QSize _size = size();
QPoint center = QPoint(_size.width() / 2, _size.height() / 2);
if (Aya::UserInputService* userInputService = Aya::ServiceProvider::find<Aya::UserInputService>(appPtr->getDM().get()))
{
switch (userInputService->getMouseWrapMode())
{
case Aya::UserInputService::WRAP_NONE: // WRAP_NONE - keep mouse where it is on right click drag
{
boost::shared_ptr<Aya::InputObject> inputObject = boost::make_shared<Aya::InputObject>(Aya::InputObject::TYPE_MOUSEMOVEMENT,
Aya::InputObject::INPUT_STATE_CHANGE, Aya::Vector3(savedLastPosition.x(), savedLastPosition.y(), 0),
Aya::Vector3(delta.x(), delta.y(), 0), appPtr->getDM().get());
appPtr->sendInputEvent(inputObject);
}
break;
case Aya::UserInputService::WRAP_NONEANDCENTER: // WRAP_NONEANDCENTER - keep mouse at the center of the screen
{
boost::shared_ptr<Aya::InputObject> inputObject =
boost::make_shared<Aya::InputObject>(Aya::InputObject::TYPE_MOUSEMOVEMENT, Aya::InputObject::INPUT_STATE_CHANGE,
Aya::Vector3(center.x(), center.y(), 0), Aya::Vector3(delta.x(), delta.y(), 0), appPtr->getDM().get());
appPtr->sendInputEvent(inputObject);
}
break;
case Aya::UserInputService::WRAP_AUTO: // WRAP_AUTO - control wrapping based on whatever... this is where we wrap for fullscreen
// chatbox (which has code still inside of UserInputUtil::wrapFullScreen)
default:
{
boost::shared_ptr<Aya::InputObject> inputObject =
boost::make_shared<Aya::InputObject>(Aya::InputObject::TYPE_MOUSEMOVEMENT, Aya::InputObject::INPUT_STATE_CHANGE,
Aya::Vector3(position.x(), position.y(), 0), Aya::Vector3(delta.x(), delta.y(), 0), appPtr->getDM().get());
appPtr->sendInputEvent(inputObject);
}
break;
}
}
boost::shared_ptr<Aya::InputObject> inputObject2 =
boost::make_shared<Aya::InputObject>(Aya::InputObject::TYPE_MOUSEDELTA, Aya::InputObject::INPUT_STATE_CHANGE,
Aya::Vector3(position.x(), position.y(), 0), Aya::Vector3(delta.x(), delta.y(), 0), appPtr->getDM().get());
appPtr->sendInputEvent(inputObject2);
if (Aya::UserInputService* userInputService = Aya::ServiceProvider::find<Aya::UserInputService>(appPtr->getDM().get()))
{
OgreWindow* _ogreWindow = dynamic_cast<OgreWindow*>(ogreWindow);
switch (userInputService->getMouseWrapMode())
{ // TODO: WRAP_CENTER, WRAP_HYBRID, WRAP_AUTO
case Aya::UserInputService::WRAP_NONE:
QCursor::setPos(mapToGlobal(savedLastPosition));
lastPosition = savedLastPosition;
break;
case Aya::UserInputService::WRAP_NONEANDCENTER:
{
QCursor::setPos(mapToGlobal(center));
lastPosition = center;
}
break;
case Aya::UserInputService::WRAP_AUTO:
default:
QCursor::setPos(mapToGlobal(position));
break;
}
}
return true;
}
else if (event->type() == QEvent::Wheel)
{
QWheelEvent* we = static_cast<QWheelEvent*>(event);
QPointF currentPos = we->position();
QPoint angleDelta = we->angleDelta();
boost::shared_ptr<Aya::InputObject> inputObject =
boost::make_shared<Aya::InputObject>(Aya::InputObject::TYPE_MOUSEWHEEL, Aya::InputObject::INPUT_STATE_CHANGE,
Aya::Vector3(currentPos.x(), currentPos.y(), angleDelta.y()), Aya::Vector3(0, 0, 0), appPtr->getDM().get());
appPtr->sendInputEvent(inputObject);
return true;
}
}
return false;
}

View File

@@ -0,0 +1,107 @@
#pragma once
#include "intrusive_ptr_target.hpp"
#include <QWindow>
#include <QWidget>
#include <QEvent>
#include <QPoint>
#include "GrayChatBar.hpp"
#include "LauncherView.hpp"
#include "DataModel/GameBasicSettings.hpp"
// This will not work unless if it is made by an OgreWindow
class OgreWidget : public QWindow
{
Q_OBJECT
QPoint lastPosition;
QPoint lastPositionFrac;
bool firstClosure;
bool disableClosures;
Qt::MouseButtons oldMouseButtons;
QWidget* ogreWindow;
bool textInputCanGrabFocus;
void sendMouseButtonEvents(QMouseEvent* event, QPoint delta, QPoint position);
void onChatBarEnteredText(const QString& text);
protected:
void resizeEvent(QResizeEvent* event) override;
public:
explicit OgreWidget(QWidget* ogreWindow, QScreen* parent);
bool event(QEvent* event);
QWidget* getOgreWindow()
{
return ogreWindow;
}
bool setClosuresDisabled(bool b)
{
disableClosures = b;
};
QPaintEngine* paintEngine() const
{
return nullptr; // Disable QPainter
}
};
class OgreWindow : public QWidget
{
Q_OBJECT
QWidget* myOgreWidgetContainer;
OgreWidget* myOgreWidget;
GrayChatBar* textInput;
LauncherView* launcherView;
std::thread rccThread;
std::atomic<bool> runRcc;
void launchGame(const QString& gameConfiguration, const int& virtualVersion);
void stopAvatarView();
void startAvatarView();
void startRcc(const QString& serverConfiguration, int port, int virtualVersion, bool forceVirtualVersion);
void wearAsset(int64_t assetId, const QString& category);
static void rccThreadProc(std::atomic<bool>& runAtomic, std::string password, OgreWindow* window, int port, int virtualVersion);
virtual void closeEvent(QCloseEvent* ev);
public:
explicit OgreWindow(QWidget* parent);
~OgreWindow();
void shutdownRcc();
void closeGame(); // called by view gameverbs
OgreWidget* getOgreWidget()
{
return myOgreWidget;
};
QWidget* getOgreWidgetContainer()
{
return myOgreWidgetContainer;
};
GrayChatBar* getTextInput()
{
return textInput;
}
LauncherView* getLauncherView()
{
return launcherView;
}
bool event(QEvent* event);
void changeEvent(QEvent* event);
void enterWidget();
void hideOgreWidget();
bool isShowingAvatarPage = false;
void handleVirtualVersionChange(Aya::GameBasicSettings::VirtualVersion version);
};

109
client/player/src/main.cpp Normal file
View File

@@ -0,0 +1,109 @@
#include "Application.hpp"
#include "InitializationError.hpp"
#include "FunctionMarshaller.hpp"
#include "Utility/Statistics.hpp"
#include "DataModel/FastLogSettings.hpp"
#include "Utility/MicrophoneInput.hpp"
#include "Utility/CefIntegration.hpp"
#include "CoordinateFrame.hpp"
#include <boost/format.hpp>
#include <stdlib.h>
#include <QSurfaceFormat>
#include <QApplication>
#include "Window.hpp"
Aya::Application* appPtr;
QApplication* qtAppPtr;
OgreWindow* qtOgreWidget;
int main(int argc, char** argv)
{
qputenv("QT_XCB_GL_INTEGRATION", "none");
#ifdef AYA_TEST_BUILD
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--remote-debugging-port=9222 --remote-allow-origins=*");
#endif
QApplication qtApp(argc, argv);
qtAppPtr = &qtApp;
QApplication::setApplicationName(AYA_PROJECT_NAME);
Aya::Application app;
appPtr = &app;
app.loadAppSettings();
try
{
app.parseCommandLine(argc, argv);
}
catch (std::exception& e)
{
printf("app.parseCommandLine: %s\n", e.what());
}
// need client settings here before we create window
std::string clientSettingsString;
FetchClientSettingsData(CLIENT_APP_SETTINGS_STRING, CLIENT_SETTINGS_API_KEY, &clientSettingsString);
// Apply client settings
LoadClientSettingsFromString(CLIENT_APP_SETTINGS_STRING, clientSettingsString, &Aya::ClientAppSettings::singleton());
OgreWindow widget(NULL);
widget.setFocus();
widget.show();
widget.setWindowTitle("Aya");
qtOgreWidget = &widget;
try
{
app.Initialize(widget.getOgreWidget());
}
catch (const Aya::initialization_error& e)
{
printf("Error initailzie: %s", e.what());
return -1;
}
#if defined(ENABLE_CHROMIUM_FRAMES)
if (app.getVm().count("nochromium") == 0)
{
Aya::CefIntegration::initialize();
}
#endif
#if defined(ENABLE_VOICE_CHAT)
Aya::MicrophoneInput::initialize();
#endif
int r = -1;
try
{
if (app.isGameReady())
widget.enterWidget();
else
widget.hideOgreWidget();
r = qtApp.exec();
}
catch (std::runtime_error const& exp)
{
printf("Error: %s\n", exp.what());
}
if (r != 0)
{
printf("Error: %d", r);
}
printf("Shutting down...\n");
widget.shutdownRcc();
app.shutdown();
#if defined(ENABLE_VOICE_CHAT)
Aya::MicrophoneInput::shutdown();
#endif
#if defined(ENABLE_CHROMIUM_FRAMES)
Aya::CefIntegration::shutdown();
#endif
return r;
}