Files
2025-09-18 17:55:52 -04:00

3291 lines
103 KiB
C++

#include "XboxService.h"
#include <locale>
#include <ppltasks.h>
#include <collection.h>
#include <atomic>
#include <thread>
#include "async.h"
#include "xdpevents.h"
#include "XboxUtils.h"
#include "Marshaller.h"
#include "RenderJob.h"
#include "ControllerBuffer.h"
#include "UserTranslator.h"
#include "KeyboardProvider.h"
#include "rbx/Profiler.h"
#include "v8datamodel/Game.h"
#include "v8datamodel/PlatformService.h"
#include "v8datamodel/DataModel.h"
#include "v8datamodel/InputObject.h"
#include "v8datamodel/ContentProvider.h"
#include "v8datamodel/DataModel.h"
#include "v8datamodel/TimerService.h"
#include "v8datamodel/GuiService.h"
#include "v8datamodel/GuiBuilder.h"
#include "v8datamodel/GamepadService.h"
#include "v8datamodel/FastLogSettings.h"
#include "v8datamodel/GameSettings.h"
#include "V8DataModel/GameBasicSettings.h"
#include "v8datamodel/ModelInstance.h"
#include "v8xml/WebParser.h"
#include "GfxBase/ViewBase.h"
#include "GfxBase/RenderSettings.h"
#include "GfxBase/FrameRateManager.h"
#include "Network/Players.h"
#include "Network/Player.h"
#include "RenderSettingsItem.h"
#include "script/ScriptContext.h"
#include "util/Statistics.h"
#include "g3d/g3dmath.h"
#include "Util/StandardOut.h"
#include "Util/SoundService.h"
#include "Util/HttpPlatformImpl.h"
FASTINTVARIABLE(CheckValidSessionInterval, 60) // validation check
FASTINTVARIABLE(XboxFriendsPolling, 30)
FASTFLAGVARIABLE(UseNewCarouselInXboxApp, true)
FASTFLAGVARIABLE(XboxPlayMyPlace, true)
FASTFLAGVARIABLE(ConvertMyPlaceNameInXboxApp, true)
using namespace Windows::Xbox::Input;
using namespace Windows::Xbox::System;
using namespace Windows::Xbox::UI;
using namespace Windows::Xbox::ApplicationModel;
using namespace Windows::Xbox::ApplicationModel::Core;
using namespace Windows::Xbox::Multiplayer;
using namespace Windows::Foundation::Collections;
using namespace Windows::Foundation;
using namespace Windows::Networking::Connectivity;
using namespace Windows::ApplicationModel;
using namespace Windows::ApplicationModel::Core;
using namespace Windows::ApplicationModel::Activation;
using namespace Windows::UI::Core;
using namespace RBX;
using namespace Microsoft::Xbox::Services;
using namespace Microsoft::Xbox::Services::Presence;
using namespace Microsoft::Xbox::Services::Social;
using namespace Microsoft::Xbox::Services::Privacy;
using namespace Microsoft::Xbox::Services::Achievements;
using G3D::clamp;
#ifdef RBX_XBOX_SITETEST1
const char* const kBaseUrl = "https://www.sitetest1.pizzaboxer.fun/";
const std::string kApiUrl = "https://api.sitetest1.pizzaboxer.fun/";
#else
const char* const kBaseUrl = "https://www.watrbx.wtf/";
const std::string kApiUrl = "https://api.watrbx.wtf/";
#endif
//const char* const kBaseUrl = "https://www.sitetest3.pizzaboxer.fun/";
//const std::string kApiUrl = "https://api.sitetest3.pizzaboxer.fun/";
//const char* const kBaseUrl = "https://www.gametest5.pizzaboxer.fun/";
//const std::string kApiUrl = "https://api.gametest5.pizzaboxer.fun/";
//const char* const kBaseUrl = "https://www.gametest2.pizzaboxer.fun/";
//const std::string kApiUrl = "https://api.gametest2.pizzaboxer.fun/";
//const char* const kBaseUrl = "https://www.sitetest2.pizzaboxer.fun/";
//const std::string kApiUrl = "https://api.sitetest2.pizzaboxer.fun/";
const std::string kInventoryEndpoint = "/xboxlive/get-inventory";
const std::string kConsumeInventoryEnpoint = "/xboxlive/consume-all";
const std::string kUserIdTranslatorEndpoint = "/xbox/translate";
const std::string kAuthEndpoint = "/xboxlive/connect";
const std::string kXboxUserInfoEndpoint = "/xboxlive/get-roblox-userInfo";
const std::string kGetPartyInfoEndpoint = "/xbox/get-party-info";
const std::string kXboxAccountLinkEndpoint = "/xboxlive/link-existing-user";
const std::string kUnlinkAccountEndpoint = "/xboxlive/disconnect";
const std::string kXboxSetRobloxUsernamePasswordEndpoint = "/xboxlive/set-roblox-username-password";
const std::string kHasLinkedAccountEndpoint = "/xboxlive/has-linked-account";
const std::string kHasSetUsernamePasswordEndpoint = "/xboxlive/has-set-username-password";
const std::string kBrowserTrackerCookieEndpoint = "/device/initialize";
const char* kIntroPlace = "rbxasset://ScaledWorldv4.7.rbxl"; // "rbxasset://ScaledWorldv3.rbxl"; // "rbxasset://particles7.rbxl";
#define TITLE_PARENT_ID L"c79323fd-00f8-462a-a97a-39a0eb61791e"
#define MAX_DETAILS_BATCH_ITEMS 10
FASTFLAG(Durango3DBackground)
FASTFLAG(TweenCallbacksDuringRenderStep)
DYNAMIC_FASTSTRING(HttpInfluxURL);
DYNAMIC_FASTFLAG(TextScaleDontWrapInWords)
DYNAMIC_FASTFLAG(UrlReconstructToAssetGame);
DYNAMIC_FASTFLAG(UrlReconstructToAssetGameSecure);
extern void dprintf( const char* fmt, ... );
extern int isRetail();
static void overrideFastFlags()
{
FFlag::TweenCallbacksDuringRenderStep = true;
DFString::HttpInfluxURL = "";
DFFlag::TextScaleDontWrapInWords = false;
DFFlag::UrlReconstructToAssetGame = true;
}
void enableVerboseXboxErrors(XboxLiveContext^ xboxLiveContext)
{
if( isRetail() ) return;
// Set this to print VERBOSE error statements
// Set up debug tracing to the Output window in Visual Studio.
xboxLiveContext->Settings->DiagnosticsTraceLevel = XboxServicesDiagnosticsTraceLevel::Verbose;
// Set up debug tracing of the Xbox Live Services API traffic to the game UI.
xboxLiveContext->Settings->EnableServiceCallRoutedEvents = true;
xboxLiveContext->Settings->ServiceCallRouted += ref new Windows::Foundation::EventHandler<Microsoft::Xbox::Services::XboxServiceCallRoutedEventArgs^>(
[=]( Platform::Object^, Microsoft::Xbox::Services::XboxServiceCallRoutedEventArgs^ args )
{
dprintf("[URL]: %s %s\n", ws2s(args->HttpMethod->Data()), ws2s(args->Url->AbsoluteUri->Data()));
dprintf("[Response]: %s %s\n", ws2s(args->HttpStatus.ToString()->Data()),ws2s(args->ResponseBody->Data()));
});
// (Optional.) Set up debug tracing of the Xbox Live Service call API failures to the game UI.
Microsoft::Xbox::Services::Configuration::EnableDebugOutputEvents = true;
Microsoft::Xbox::Services::Configuration::DebugOutput += ref new Windows::Foundation::EventHandler<Platform::String^>(
[=]( Platform::Object^, Platform::String^ debugOutput )
{
// Display Xbox Live Services API debug trace to the screen for easy debugging.
dprintf( "[XSAPI Trace]: %s\n", ws2s(debugOutput->Data()));
});
}
static int createParty( std::vector<std::string>& players, std::string* partyGuid );
//////////////////////////////////////////////////////////////////////////
XboxPlatform::XboxPlatform()
{
activated = true;
marshaller = 0;
game = 0;
current = 0;
intro = 0;
view = 0;
gameShutdownGuard = 0;
xboxLiveContext = nullptr;
returnToEngagementFlag = 0;
consumeAllFlag = 0;
currentController = nullptr;
currentUser = nullptr;
trySignInOnActivation = false;
Windows::UI::Core::CoreWindow^ window = Windows::UI::Core::CoreWindow::GetForCurrentThread();
if(!window) RBXASSERT(false);
window->Activated += ref new TypedEventHandler<Windows::UI::Core::CoreWindow^,WindowActivatedEventArgs^>([=]( Platform::Object^ , WindowActivatedEventArgs^ args )
{
onWindowActivatedChanged(args);
} );
OSContext context;
context.width = window->Bounds.Right - window->Bounds.Left;
context.height = window->Bounds.Bottom - window->Bounds.Top;
context.hWnd = (HWND)this;
dprintf( "SetWindow: %d x %d\n", context.width, context.height );
marshaller = new Marshaller;
::SetBaseURL(kBaseUrl);
ContentProvider::setAssetFolder("content");
Game::globalInit(false);
loadLocalFFlags();
fetchFFlags(kBaseUrl); // try fetching the flags anyway, might work
overrideFastFlags();
if(1)
{
Platform::String^ localStoragePathW = Windows::Storage::ApplicationData::Current->LocalFolder->Path;
std::string localStoragePath = ws2s(localStoragePathW->Begin()) + "\\settings.xml";
RBX::GlobalAdvancedSettings::singleton()->setSaveTarget( localStoragePath );
RBX::GlobalAdvancedSettings::singleton()->loadState("");
{
RBX::Security::Impersonator impersonate(RBX::Security::RobloxGameScript_);
std::string basicLocalStoragePath = ws2s(localStoragePathW->Begin()) + "\\basicsettings.xml";
RBX::GlobalBasicSettings::singleton()->setSaveTarget( basicLocalStoragePath );
RBX::GlobalBasicSettings::singleton()->loadState(basicLocalStoragePath);
}
CRenderSettingsItem::singleton().setFrameRateManagerMode(CRenderSettings::FrameRateManagerOff);
CRenderSettingsItem::singleton().setQualityLevel(CRenderSettings::QualityLevel21);
}
startControllerBuffering();
//////////////////////////////////////////////////////////////////////////
intro = new UnsecuredStudioGame(0, kBaseUrl, false, false);
if(1)
{
DataModel::LegacyLock lock( intro->getDataModel().get(), DataModelJob::Write);
controller = boost::shared_ptr<XboxGameController>(new XboxGameController(intro->getDataModel(), controllerBuffer));
if (auto uis = RBX::ServiceProvider::create<UserInputService>(intro->getDataModel().get()))
{
uis->setGamepadEnabled(true);
}
ServiceProvider::create<GamepadService>(intro->getDataModel().get());
if (auto ps = ServiceProvider::create<PlatformService>(intro->getDataModel().get()))
{
ps->setPlatform(this, PlatformDatamodelType::AppShellDatamodel);
ps->tintColor = Color3(0,0,0); // start with black tint to hide loading. Core scripts are going to set it back
}
keyboard.reset(new KeyboardProvider());
keyboard->registerTextBoxListener(intro->getDataModel().get());
}
ViewBase::InitPluginModules();
view = ViewBase::CreateView(CRenderSettings::Direct3D11, &context, &CRenderSettingsItem::singleton());
view->initResources();
switchView(intro);
intro->getDataModel()->submitTask(
[=](...) -> void
{
shared_ptr<DataModel> dm = intro->getDataModel();
Security::Impersonator impersonate(Security::RobloxGameScript_);
dm->startCoreScripts(true, "XStarterScript");
if( kIntroPlace && FFlag::Durango3DBackground)
dm->loadContent( ContentId(kIntroPlace));
auto player = Instance::fastSharedDynamicCast<Network::Player>(dm->create<Network::Players>()->createLocalPlayer(1));
player->loadCharacter(true, "");
dm->create<RunService>()->run();
dm->gameLoaded();
dprintf("DATAMODEL INTRO: 0x%p\n", dm.get());
},
DataModelJob::Write
);
userTranslator.reset(new UserIDTranslator(kApiUrl + kUserIdTranslatorEndpoint));
xboxEvents_init();
// this avoids a bug introduced in July XDK
try { ref new XboxLiveContext( nullptr ); } catch(...){};
//////////////////////////////////////////////////////////////////////////
// Initialize the Xbox Event Handling
initializeAuthEventHandlers();
initializeNetworkConnectionEventHandlers();
}
XboxPlatform::~XboxPlatform()
{
xboxEvents_shutdown();
switchView(0);
shutdownDataModels();
}
void XboxPlatform::onWindowActivatedChanged(Windows::UI::Core::WindowActivatedEventArgs^ args)
{
if (args->WindowActivationState == Windows::UI::Core::CoreWindowActivationState::CodeActivated ||
args->WindowActivationState == Windows::UI::Core::CoreWindowActivationState::PointerActivated)
{
onActivated();
}
else
{
onDeactivated();
}
}
void XboxPlatform::onProtocolActivated( Windows::ApplicationModel::Activation::IProtocolActivatedEventArgs^ args )
{
// assumes that the play who recieved the game join notification(xbox) starts the game
bool temporaryGamepadUser = false;
dprintf("OnProtocolActivated(): args->Kind = %d\n", args->Kind);
try
{
auto uri = ref new Uri(args->Uri->RawUri);
auto raw = uri->RawUri;
auto host = uri->Host;
Platform::String^ myUserXuid;
Platform::String^ targetUserXuid;
// use the current active user as the currentgamepaduser
if(!currentUser)
{
if(CoreApplicationContext::CurrentUser != nullptr)
{
currentUser = CoreApplicationContext::CurrentUser;
temporaryGamepadUser = true;
}
else
{
// failed join? signal for error message
return;
}
}
XboxLiveContext^ tempUser = ref new XboxLiveContext(currentUser);
MultiplayerSession^ session = nullptr;
// Retrieve the target session using the activity handle
Platform::String^ handle = uri->QueryParsed->GetFirstValueByName("handle");
async(tempUser->MultiplayerService->GetCurrentSessionByHandleAsync(handle)).complete(
[&session](MultiplayerSession^ t)
{
session = t;
}
).except( [](Platform::Exception^ e) -> void
{
dprintf( "%s exception [0x%x]: %S\n", __FUNCTION__, (int)e->HResult, e->Message->Data() );
}
).join();
if(!session)
{
fireGameJoinedEvent(GameStartResult::GameStart_NoPlayer);
return;
}
dprintf("Game::OnProtocolActivated:\n\tCurrentUser_XUID = %ws\n\tHost = %ws \n\tURI = %ws\n", currentUser->XboxUserId->Data(), host->Data(), raw->Data());
if(isStringEqualCaseInsensitive(host, L"inviteHandleAccept"))
{
dprintf("inviteHandleAccept\n");
myUserXuid = uri->QueryParsed->GetFirstValueByName("invitedXuid");
targetUserXuid = uri->QueryParsed->GetFirstValueByName("senderXuid");
}
else if(isStringEqualCaseInsensitive(host, L"activityHandleJoin"))
{
dprintf("activityHandleJoin\n");
myUserXuid = uri->QueryParsed->GetFirstValueByName("joinerXuid");
targetUserXuid = uri->QueryParsed->GetFirstValueByName("joineeXuid");
}
for(int i = 0; i < session->Members->Size; i++)
{
MultiplayerSessionMember^ player = session->Members->GetAt(i);
if(isStringEqual(player->XboxUserId,targetUserXuid))
{
int rbxId;
if (XboxMultiplayerManager::parseFromXboxCustomJson(player->MemberCustomConstantsJson, "rbxId", &rbxId))
{
int pmpCreator;
XboxMultiplayerManager::parseFromXboxCustomJson(session->SessionConstants->CustomConstantsJson, "pmpCreator", &pmpCreator);
gameInviteHandle.followUserId = rbxId;
gameInviteHandle.targetUserXuid = ws2s(targetUserXuid->Data());
gameInviteHandle.pmpCreator = pmpCreator;
break;
}
}
}
if(temporaryGamepadUser) {
currentUser = nullptr;
}
if(gameInviteHandle.followUserId == -1)
{
// error signal here TODO
return;
}
// if the player is already logged in, we want to
// shutdown any existing games and follow the join/invite
// otherwise we wait until the user authenticates
if(!currentUser) return;
boost::thread(
[=]()
{
requestGameShutdown(false);
startGame3( GameJoin_Follow, gameInviteHandle.followUserId );
gameInviteHandle.targetUserXuid = "";
gameInviteHandle.followUserId = -1;
gameInviteHandle.pmpCreator = 0;
}
).detach();
}catch(Platform::Exception^ e)
{
// error signal here TODO
dprintf( "%s exception [0x%x]: %S\n", __FUNCTION__, (int)e->HResult, e->Message->Data() );
if(temporaryGamepadUser) {
currentUser = nullptr;
}
}
}
void XboxPlatform::onActivated()
{
dprintf("OnActivated\n");
activated = true;
if (trySignInOnActivation)
{
tryToSignInActiveUser(currentController);
}
}
void XboxPlatform::onDeactivated()
{
activated = false;
}
void XboxPlatform::startEventHandlers()
{
// reinits both pollers
initPartyPoller();
initValidSessionPoller();
}
void XboxPlatform::stopEventHandlers()
{
// reinits both pollers
removePartyPoller();
removeValidSessionPoller();
}
void XboxPlatform::shutdownDataModels()
{
RBX::GlobalBasicSettings::singleton()->saveState();
RBX::GlobalAdvancedSettings::singleton()->saveState();
if (game)
{
game->shutdown();
delete game;
game = 0;
}
if(intro)
{
intro->shutdown();
delete intro;
intro = 0;
}
delete exitVerb;
exitVerb = 0;
}
void XboxPlatform::suspendViewXbox()
{
if( game )
{
if (PlatformService* ps = ServiceProvider::find<PlatformService>(game->getDataModel().get()))
{
ps->suspendSignal();
}
}
if(multiplayerManager)
{
dprintf("Multiplayer stopEventHandlers\n");
multiplayerManager->stopMultiplayerListeners();
if(current == game)
{
dprintf("\t leaveGameSession\n");
multiplayerManager->leaveGameSession();
}
}
RBX::GlobalBasicSettings::singleton()->saveState();
RBX::GlobalAdvancedSettings::singleton()->saveState();
///////////////////////////////////////////////////////////////////////////
view->suspendView();// this needs to be the last thing we do on suspend
}
void XboxPlatform::voiceChatSetMuteState(int userId, bool mute)
{
if (multiplayerManager)
{
if (mute)
multiplayerManager->muteVoiceChatPlayer(userId);
else
multiplayerManager->unmuteVoiceChatPlayer(userId);
}
}
unsigned XboxPlatform::voiceChatGetState(int userId)
{
if (multiplayerManager)
{
return multiplayerManager->voiceChatGetState(userId);
}
return VoiceChatState::voiceChatState_UnknownUser;
}
void XboxPlatform::resumeViewXbox()
{
view->resumeView();// this needs to be the first thing we do on resume
///////////////////////////////////////////////////////////////////////////
dprintf("resumeViewXbox\n");
}
void XboxPlatform::onNormalResume()
{
if(multiplayerManager)
{
dprintf("Multiplayer startEventHandlers\n");
multiplayerManager->startMultiplayerListeners();
if (current == game)
{
dprintf("\t createGameSession\n");
multiplayerManager->createGameSession();
}
}
}
void XboxPlatform::xbEventPlayerSessionStart()
{
if( !xboxLiveContext || !currentUser ) return;
EventWritePlayerSessionStart(
xboxLiveContext->User->XboxUserId->Data(), // UserId
&playerSessionGuid, //PlayerSessionId
L"", //MulitlayercorrelationId
0 , //GameplayModeId
0 ); //DifficultyLevelId
}
void XboxPlatform::xbEventPlayerSessionEnd()
{
if( !xboxLiveContext || !currentUser ) return;
EventWritePlayerSessionEnd(
xboxLiveContext->User->XboxUserId->Data(), // UserId
&playerSessionGuid, //PlayerSessionId
L"", //MulitlayercorrelationId
0 , //GameplayModeId
0 , //DifficultyLevelId
0 ); // ExitStatusId
}
void XboxPlatform::xbEventPlayerSessionPause()
{
if( !xboxLiveContext || !currentUser ) return;
EventWritePlayerSessionPause(
xboxLiveContext->User->XboxUserId->Data(), // UserId
&playerSessionGuid, //PlayerSessionId
L"" ); //MulitlayercorrelationId
}
void XboxPlatform::xbEventPlayerSessionResume()
{
if( !xboxLiveContext || !currentUser ) return;
EventWritePlayerSessionResume(
xboxLiveContext->User->XboxUserId->Data(), // UserId
&playerSessionGuid, //PlayerSessionId
L"", //MulitlayercorrelationId
0 , //GameplayModeId
0 ); //DifficultyLevelId
}
// check the following to for a list of xbox privilege IDs
// https://developer.xboxlive.com/en-us/platform/development/documentation/software/Pages/KnownPrivileges_WXASx_T_jun15.aspx
bool XboxPlatform::checkXboxPrivileges(unsigned privilegeID)
{
bool retval = false;
if(currentUser)
{
try{
async( Windows::Xbox::ApplicationModel::Store::Product::CheckPrivilegeAsync(currentUser, privilegeID, true, nullptr) ).complete(
[&retval]( Windows::Xbox::ApplicationModel::Store::PrivilegeCheckResult pcr )
{
retval = (pcr == Windows::Xbox::ApplicationModel::Store::PrivilegeCheckResult::NoIssue);
}
).join();
}
catch(Platform::Exception^ e)
{
dprintf( "CheckPrivilege: %s exception [0x%x]: %S\n", __FUNCTION__, (int)e->HResult, e->Message->Data() );
}
}
return retval;
}
void XboxPlatform::startControllerBuffering()
{
controllerBuffer = shared_ptr<ControllerBuffer>(new ControllerBuffer);
boost::thread( [=]()-> void
{
RBX::Timer<RBX::Time::Fast> deltaTimer;
while (controllerBuffer)
{
deltaTimer.reset();
controllerBuffer->updateBuffer();
float updateTime = deltaTimer.reset().msec();
float sleepTime = controllerBuffer->getPollingMsec() - updateTime;
if (sleepTime > 0.0f)
{
Sleep(sleepTime);
}
}
} ).detach();
}
void XboxPlatform::endControllerBuffering()
{
controllerBuffer.reset();
}
#define ON_BUTTON_DOWN(curr, prev, flag) (((curr^prev)&curr)&flag)
#define ON_BUTTON_UP(curr, prev, flag) (((curr^prev)&prev)&flag)
void XboxPlatform::tick()
{
RBXPROFILER_SCOPE("xbox",__FUNCTION__);
marshaller->waitEvents();
marshaller->processEvents();
this->handlePartyJoin();
if( isRetail() ) return;
// cheat codes:
IVectorView<IGamepad^>^ gamepads = Windows::Xbox::Input::Gamepad::Gamepads;
IGamepad^ gamepad;
if (gamepads->Size)
{
value class RawGamepadReading gr = gamepads->GetAt(0)->GetRawCurrentReading();
static value class RawGamepadReading prev_gr = Windows::Xbox::Input::Gamepad::Gamepads->GetAt(0)->GetRawCurrentReading();
if (gr.LeftTrigger >= 0.98 && gr.RightTrigger > 0.98)
{
// interactive screen resize
if(gr.RightThumbstickX >= 0.5){
GameSettings::singleton().overscanPX += 1/1920.0f;
}
if(gr.RightThumbstickX <= -0.5){
GameSettings::singleton().overscanPX -= 1/1920.0f;
}
if(gr.RightThumbstickY >= 0.5){
GameSettings::singleton().overscanPY += 1/1080.0f;
}
if(gr.RightThumbstickY <= -0.5){
GameSettings::singleton().overscanPY -= 1/1080.0f;
}
if(int(gr.Buttons & GamepadButtons::LeftShoulder))
{
switchView(intro);
}
if(int(gr.Buttons & GamepadButtons::RightShoulder) && game)
{
switchView(game);
}
if(int(ON_BUTTON_UP(gr.Buttons,prev_gr.Buttons, GamepadButtons::A)))
{
// crossroads 1818
// Cube Eat Cube 248409235
// Heroes 255280267
/*
boost::thread(
[=]()-> void
{
static long flag = 0;
if( RBX::InterlockedCompareExchange( &flag, 1, 0) == 0 )
{
startGame3( GameJoin_Normal, 248409235 );
RBX::InterlockedExchange( &flag, 0 );
}
}
).detach();
*/
//bool result = performHasLinkedAccount();
//dprintf("performHasLinkedAccount Result: %s\n",(result)?"true":"false");
}
if(int(ON_BUTTON_UP(gr.Buttons,prev_gr.Buttons, GamepadButtons::B)))
{
/*
if(game)
requestGameShutdown();
else
{
ExitProcess(0);
}
*/
//bool result = performHasRobloxCredentials();
//dprintf("performHasRobloxCredentials Results: %s\n", (result)?"true":"false");
}
if(int(ON_BUTTON_UP(gr.Buttons,prev_gr.Buttons, GamepadButtons::X)))
{
//multiplayerManager->requestJoinParty();
/*
std::string robloxname = "robloxCred8";
std::string robloxpass = "hackproof123";
std::string result;
performAccountLink(robloxname,robloxpass,&result);
dprintf("performAccountLink Results: %s\n", result.c_str());
*/
//multiplayerManager->recentPlayersList();
popupGameInviteUI();
}
if(int(ON_BUTTON_UP(gr.Buttons, prev_gr.Buttons,GamepadButtons::Y)))
{
/*
std::string robloxname = "robloxCred8";
std::string robloxpass = "hackproof123";
std::string result;
performSetRobloxCredentials(robloxname, robloxpass, &result);
dprintf("performSetRobloxCredentials Results: %s\n", result.c_str());
*/
//multiplayerManager->getInGamePlayers();
//popupAccountPickerUI(InputObject::UserInputType::TYPE_GAMEPAD1);
}
if(int(ON_BUTTON_UP(gr.Buttons, prev_gr.Buttons,GamepadButtons::LeftThumbstick)))
{
popupHelpUI();
}
if(int(ON_BUTTON_UP(gr.Buttons, prev_gr.Buttons,GamepadButtons::RightThumbstick)))
{
if(1){
// used to print out inventory items
UINT itemsFound = 0;
IAsyncOperation<Marketplace::InventoryItemsResult^>^ pAsyncOp =
xboxLiveContext->InventoryService->GetInventoryItemsAsync( Microsoft::Xbox::Services::Marketplace::MediaItemType::GameConsumable );
using namespace Concurrency;
create_task( pAsyncOp )
.then( [this, &itemsFound] ( task< Marketplace::InventoryItemsResult^ > inventoryItemsTask )
{
Concurrency::critical_section m_InventoryListsLock;
try
{
Marketplace::InventoryItemsResult^ inventoryResult = inventoryItemsTask.get();
// Because we may be compiling a list of multiple content types from separate
// calls, we just append the results to the passed in vector.
critical_section::scoped_lock lock( m_InventoryListsLock );
for( itemsFound = 0; itemsFound < inventoryResult->Items->Size; ++itemsFound )
{
inventoryResult->Items->GetAt( itemsFound );
unsigned itemnumber = itemsFound;
std::string uri = ws2s(inventoryResult->Items->GetAt( itemsFound )->ConsumableUrl->DisplayUri->Data());
unsigned balance = inventoryResult->Items->GetAt( itemsFound )->ConsumableBalance;
dprintf("item %d: num: %d uri: %s \n",itemnumber,balance, uri.c_str()) ;
}
}
catch (Platform::Exception^ ex)
{
}
}).wait();
}
}
if(int(ON_BUTTON_UP(gr.Buttons, prev_gr.Buttons,GamepadButtons::Menu)))
{
boost::thread( [this]() { requestGameShutdown(false); } ).detach();
}
if(int(ON_BUTTON_UP(gr.Buttons,prev_gr.Buttons, GamepadButtons::DPadUp)))
{
current->getDataModel()->getGuiBuilder().toggleGeneralStats();
}
if(int(ON_BUTTON_UP(gr.Buttons,prev_gr.Buttons, GamepadButtons::DPadDown)))
{
current->getDataModel()->getGuiBuilder().togglePhysicsStats();
}
if(int(ON_BUTTON_UP(gr.Buttons,prev_gr.Buttons, GamepadButtons::DPadLeft)))
{
current->getDataModel()->getGuiBuilder().toggleNetworkStats();
}
if(int(ON_BUTTON_UP(gr.Buttons,prev_gr.Buttons, GamepadButtons::DPadRight)))
{
current->getDataModel()->getGuiBuilder().toggleRenderStats();
}
}
prev_gr = gr;
}
}
#undef ON_BUTTON_UP
#undef ON_BUTTON_DOWN
void XboxPlatform::requestGameShutdown(bool teleport)
{
dprintf("requestGameShutdown");
RBX::mutex::scoped_lock lock(gameMutex);
requestGameShutdown_nolock(teleport);
}
void XboxPlatform::requestGameShutdown_nolock(bool teleport)
{
if( !game ) return;
removeVoiceChatPoller();
switchView( teleport? 0 : intro);
game->shutdown();
delete game; game = 0;
delete exitVerb; exitVerb = 0;
RBX::GlobalBasicSettings::singleton()->saveState();
RBX::GlobalAdvancedSettings::singleton()->saveState();
InterlockedExchange( &gameShutdownGuard, 0 ); // release the guard
if(!teleport) // do not clear the gamesession, since we need the pmpCreatorId from it to presist correct xbox join behavior
{
boost::thread( // when you lose internet connection, this thing takes some time to realize that.
[this]() ->void
{
if(multiplayerManager)
{
multiplayerManager->leaveGameSession();
}
}
).detach();
}
}
std::pair<unsigned, unsigned> XboxPlatform::getRenderSize() const
{
GameSettings& gs = GameSettings::singleton();
if( current == intro || gs.overscanPX < 0 || gs.overscanPY < 0 )
return std::make_pair(1920u, 1080u);
unsigned w = 0.5f + 1920 * clamp( gs.overscanPX, 0.1f, 1.0f );
unsigned h = 0.5f + 1080 * clamp( gs.overscanPY, 0.1f, 1.0f );
return std::make_pair(w, h);
}
//////////////////////////////////////////////////////////////////////////
// Wrapper function for firing game join event since startGame3 has multiple return statements
void XboxPlatform::fireGameJoinedEvent(GameStartResult result)
{
intro->getDataModel()->submitTask(
[result](DataModel* dm) -> void
{
ServiceProvider::find<PlatformService>(dm)->gameJoinedSignal((int)result);
},
DataModelJob::Write);
}
GameStartResult XboxPlatform::startGame3( RBX::GameJoinType gt, int id )
{
if( GetCurrentThreadId() == marshaller->getThreadId() )
{
RBXASSERT( !"Do not call startGame3() on the marshaller thread, it won't work!" );
fireGameJoinedEvent(GameStart_Weird);
return GameStart_Weird;
}
// Prevent gameplay based on xbox privileges here
if( !checkXboxPrivileges((unsigned)Windows::Xbox::ApplicationModel::Store::KnownPrivileges::XPRIVILEGE_MULTIPLAYER_SESSIONS) ||
!checkXboxPrivileges((unsigned)Windows::Xbox::ApplicationModel::Store::KnownPrivileges::XPRIVILEGE_USER_CREATED_CONTENT))
{
fireGameJoinedEvent(GameStart_NoAccess);
return GameStart_NoAccess;
}
const std::string reportCategory = "PlaceLauncherDuration";
Time startTime = Time::nowFast();
std::string requestType;
switch(gt)
{
case RBX::GameJoin_PMPCreator:
case RBX::GameJoin_PartyLeader: // partyleader join is considered the same kind of join
case RBX::GameJoin_Normal: requestType = "RequestGame"; break;
case RBX::GameJoin_Instance: requestType = "RequestGameJob"; break;
case RBX::GameJoin_Follow: requestType = "RequestFollowUser"; break;
case RBX::GameJoin_Party: requestType = "RequestPlayWithParty"; break;
default: requestType = ""; break;
}
if(1)
{
RBX::mutex::scoped_lock lock(gameMutex);
// at this point we are sure we can attempt a game join
if (game)
{
fireGameJoinedEvent(GameStart_Already);
return GameStart_Already; // game already started
}
game = new SecurePlayerGame(0, kBaseUrl, true);
// lets show something on the screen as soon as possible
switchView(game);
std::string auth, token, scripturl;
GameStartResult result;
if( GameStart_OK != (result = prepareGameLaunch(auth, token, scripturl, gt, id))
|| GameStart_OK != (result = initGame(auth, token, scripturl)) )
{
dprintf("startGame3() failed - cleanup and abort...\n");
Analytics::EphemeralCounter::reportStats(reportCategory + "_Failed_" + requestType, (Time::nowFast() - startTime).seconds());
// game was unable to init; maybe send a message to lua?
switchView(intro);
game->shutdown();
delete game;
game = 0;
dprintf("startGame3() failed - end.\n");
fireGameJoinedEvent(result);
return result;
}
}
RBX::GlobalBasicSettings::singleton()->saveState();
RBX::GlobalAdvancedSettings::singleton()->saveState();
fireGameJoinedEvent(GameStart_OK);
if (multiplayerManager)
{
if(gt == RBX::GameJoin_PMPCreator)
{
multiplayerManager->createGameSession(multiplayerManager->getLocalRobloxUserId());
}
else
{
multiplayerManager->createGameSession();
}
}
else
{
dprintf("error: how did multiplayermanager get reset during startgame3?\n");
}
Analytics::EphemeralCounter::reportStats(reportCategory + "_Success_" + requestType, (Time::nowFast() - startTime).seconds());
dprintf("Game Started.\n");
return GameStart_OK;
}
int XboxPlatform::netConnectionCheck()
{
return 0;
}
void XboxPlatform::setScreenResolution(double px, double py)
{
GameSettings::singleton().overscanPX = px;
GameSettings::singleton().overscanPY = py;
}
int XboxPlatform::fetchFriends(InputObject::UserInputType gamepadId, std::string* result)
{
return ::getFriends(result, userTranslator.get(), xboxLiveContext);
}
// this assigns the account selected to pair with the controller supplied
int XboxPlatform::popupAccountPickerUI(InputObject::UserInputType gamepadId)
{
if (gamepadId == InputObject::UserInputType::TYPE_NONE)
{
dprintf("No gamepad passed in\n");
return AccountAuth_MissingGamePad;
}
// get the controller that prompted the accountpicker
uint64 controllerGuid = controller->getGamepadGuid(gamepadId);
IVectorView<IGamepad^>^ gamepads = Windows::Xbox::Input::Gamepad::Gamepads;
IController ^ userGamePad = nullptr;
for(int i = 0; i < gamepads->Size; i++)
{
if(gamepads->GetAt(i)->Id == controllerGuid)
{
userGamePad = gamepads->GetAt(i);
break;
}
}
if(userGamePad)
{
RBX::mutex::scoped_lock lock(controllerPairingMutex);
// accountpicker sets the controller's user to the account selected
async(Windows::Xbox::UI::SystemUI::ShowAccountPickerAsync(userGamePad, Windows::Xbox::UI::AccountPickerOptions::None)).complete(
[this, &userGamePad](Windows::Xbox::UI::AccountPickerResult^ result)
{
if(!userGamePad || !currentUser)
{
return; // user was signed out
}
if(!isStringEqual(userGamePad->User->XboxUserId, currentUser->XboxUserId))
{
// new user selected, nothing needs to be changed
returnToEngagementScreen(ReturnToEngage_AccountReselect);
}
}
).join();
}
return 0;
}
void XboxPlatform::popupGameInviteUI()
{
if(!game)
{
dprintf("Can't call gameInvite when not in game\n");
return;
}
multiplayerManager->sendGameInvite();
}
int XboxPlatform::popupHelpUI()
{
dprintf("Snap on Help App\n");
if(!currentUser)
{
dprintf("Xbox User not initialized\n");
return -1;
}
try
{
User^ user = currentUser;
async( Windows::Xbox::ApplicationModel::Help::Show(user) ).detach();
}
catch( Platform::Exception^ exc )
{
dprintf("Exception: [0x%x] %S\n", exc->HResult, exc->Message->Data() );
return -1;
}
return 0;
}
int XboxPlatform::launchPlatformUri(const std::string baseUri)
{
dprintf("Open a URI in App\n");
if(!currentUser)
{
dprintf("Xbox User not initialized\n");
return -1;
}
// NOTE: can make this yielding to catch errors
Windows::Foundation::Uri^ uri = ref new Windows::Foundation::Uri(ref new Platform::String(s2ws(&baseUri).c_str()));
async(Windows::System::Launcher::LaunchUriAsync(uri)).detach();
return 0;
}
int XboxPlatform::popupPartyUI(InputObject::UserInputType gamepadId)
{
dprintf("NOTE: popupPartyUI() needs to use Xbox Controller to properly identify the user. ");
if( User::Users->Size == 0 )
{
dprintf("User not signed in, friends are not available.\n");
return -1;
}
try{
User^ user = currentUser;
async( Windows::Xbox::UI::SystemUI::LaunchPartyAsync( user ) ).complete(
[](void) -> void
{
//dprintf("LOL\n"); do not rely on this function, it gets called immediately after the party app starts
}
).detach();
}
catch( Platform::Exception^ exc )
{
dprintf("Exception: [0x%x] %S\n", exc->HResult, exc->Message->Data() );
return -1;
}
return 0;
}
int XboxPlatform::popupProfileUI(RBX::InputObject::UserInputType gamepadId, std::string xuid)
{
dprintf("Note: popupProfileUI() needs to use Xbox Controller to propertly identify the user");
if (User::Users->Size == 0)
{
dprintf("User not signed in, cannot open ProfileCard\n");
return -1;
}
try
{
User^ user = currentUser;
std::wstring wxuid(xuid.begin(), xuid.end());
Platform::String^ pxuid = ref new Platform::String(wxuid.c_str());
async (Windows::Xbox::UI::SystemUI::ShowProfileCardAsync(user, pxuid)).detach();
}
catch (Platform::Exception^ exc)
{
dprintf("Exception: [0x%x] %S\n", exc->HResult, exc->Message->Data());
return -1;
}
return 0;
}
int XboxPlatform::getPMPCreatorId()
{
if(multiplayerManager)
{
return multiplayerManager->getPMPCreatorId();
}
return -1;
}
int XboxPlatform::getTitleId()
{
std::wstring wTitleId(Windows::Xbox::Services::XboxLiveConfiguration::TitleId->Data());
int titleId = std::stoi(wTitleId);
return titleId;
}
shared_ptr<const RBX::Reflection::ValueTable> XboxPlatform::getVersionIdInfo()
{
shared_ptr<RBX::Reflection::ValueTable> versionInfo = rbx::make_shared<Reflection::ValueTable>();
Windows::ApplicationModel::Package^ currentPackage = Windows::ApplicationModel::Package::Current;
if (currentPackage)
{
Windows::ApplicationModel::PackageVersion currentPackageVersion = currentPackage->Id->Version;
(*versionInfo)["Major"] = (int)currentPackageVersion.Major;
(*versionInfo)["Minor"] = (int)currentPackageVersion.Minor;
(*versionInfo)["Build"] = (int)currentPackageVersion.Build;
(*versionInfo)["Revision"] = (int)currentPackageVersion.Revision;
}
return versionInfo;
}
void XboxPlatform::showKeyBoard(std::string& title, std::string& description, std::string& defaultText, unsigned keyboardType, DataModel* dm)
{
keyboard->showKeyBoardLua(title, description, defaultText, (XboxKeyBoardType)keyboardType, dm);
}
shared_ptr<const Reflection::ValueTable> XboxPlatform::getPlatformUserInfo()
{
shared_ptr<Reflection::ValueTable> userInfoTable = rbx::make_shared<Reflection::ValueTable>();
if (currentUser)
{
Network::Player* localplayer = intro->getDataModel()->find<Network::Players>()->getLocalPlayer();
(*userInfoTable)["Gamertag"] = ws2s(currentUser->DisplayInfo->Gamertag->Data());
(*userInfoTable)["XboxUserId"] = ws2s(currentUser->XboxUserId->Data());
(*userInfoTable)["RobloxUserName"] = localplayer->getName();
(*userInfoTable)["RobloxUserId"] = localplayer->getUserID();
}
return userInfoTable;
}
void XboxPlatform::doTeleport(const std::string& auth, const std::string& ticket, const std::string& join)
{
intro->getDataModel()->submitTask(
[=](...)->void
{
GameStartResult result = GameStart_Weird;
if(1)
{
RBX::mutex::scoped_lock lock(gameMutex); // NOTE: relies on the fact that RBX::mutex is Win32 CriticalSection which is recursive
dprintf("Teleport:\n\tauth: %s\n\tticket:%s\n\tjoinScript:%s\n\n", auth.c_str(), ticket.c_str(), join.c_str() );
requestGameShutdown_nolock(true); // we hold the mutex
RBXASSERT( !game );
game = new SecurePlayerGame( 0, kBaseUrl, true );
switchView(game);
try
{
std::string url = auth + "?suggest" + ticket;
std::string response;
dprintf("Teleport re-auth: %s\n", url.c_str() );
Http h(url);
h.additionalHeaders[Http::kRBXAuthenticationNegotiation] = "watrbx.wtf";
h.get(response);
dprintf("Teleport re-auth response: %s\n", response.c_str());
}
catch( std::exception& e )
{
dprintf("Teleport re-auth failed:\n %s\n", e.what());
goto fail;
}
result = initGame( auth, ticket, join );
if( result != GameStart_OK )
goto fail;
} // mutex
if (multiplayerManager)
{
unsigned int pmpCreatorId = multiplayerManager->getPMPCreatorId();
if( pmpCreatorId > 0)
multiplayerManager->createGameSession(pmpCreatorId); //For PMP
else
multiplayerManager->createGameSession();
}else{
result = GameStart_Weird;
goto fail;
}
return;
fail:
switchView(intro); // teleport failed, kick to main menu
game->shutdown();
delete game;
game = 0;
fireGameJoinedEvent(result);
return;
},
DataModelJob::None
);
}
bool XboxPlatform::isTeleportEnabled() const
{
return true;
}
//////////////////////////////////////////////////////////////////////////
static std::string makePlaceLauncherUrl_Normal( int placeid, const std::string& gamerTag )
{
return format("%s/Game/PlaceLauncher.ashx?request=RequestGame&placeId=%d&gamerTag=%s", kBaseUrl, placeid, gamerTag.c_str());
}
static std::string makePlaceLauncherUrl_Instance( int instanceId, const std::string& gamerTag )
{
return format("%s/Game/PlaceLauncher.ashx?request=RequestGameJob&gameId=%d&gamerTag=%s", kBaseUrl, instanceId, gamerTag.c_str());
}
static std::string makePlaceLauncherUrl_Follow( int followPlayer, const std::string& gamerTag )
{
return format("%s/Game/PlaceLauncher.ashx?request=RequestFollowUser&userId=%d&gamerTag=%s", kBaseUrl, followPlayer, gamerTag.c_str());
}
static std::string makePlaceLauncherUrl_Party( const std::string& gamerTag )
{
return format("%s/Game/PlaceLauncher.ashx?request=RequestPlayWithParty&gamerTag=%s", kBaseUrl, gamerTag.c_str());
}
static std::string makePlaceLauncherUrl_PartyLeader( int placeid, const std::string& gamerTag )
{
return format("%s/Game/PlaceLauncher.ashx?request=RequestGame&IsPartyLeader=True&placeId=%d&gamerTag=%s", kBaseUrl, placeid, gamerTag.c_str());
}
RBX::GameStartResult XboxPlatform::prepareGameLaunch( std::string& authenticationUrl, std::string& authenticationTicket, std::string& scriptUrl, GameJoinType joinType, int id )
{
// note: assumes gameMutex is locked
Game* game = this->game;
if( !game )
return GameStart_Weird;
std::string validationUrl = kApiUrl + kXboxUserInfoEndpoint;
HttpFuture validationResult = HttpAsync::get(validationUrl);
if( !fetchFFlags(kBaseUrl) )
return GameStart_WebError;
overrideFastFlags();
std::string gamerTag;
if( xboxLiveContext )
{
gamerTag = ws2s( xboxLiveContext->User->DisplayInfo->Gamertag->Data() );
}
else
{
return GameStart_Weird; // game start requires xbox auth, should not call befire that
}
std::string launcherUrl;
std::vector<std::string> party;
std::string partyGuid;
switch (joinType)
{
case RBX::GameJoin_PMPCreator:
case RBX::GameJoin_Normal:
case RBX::GameJoin_PartyLeader:
if( 0 == getParty( &party, true, userTranslator.get(), xboxLiveContext ) ) // join as a party
{
if( 0 == createParty(party, &partyGuid) )
{
marshaller->execute( [=]() { setPartyCreateStatus(partyGuid); } );
break;
}
}
break;
default: break;
}
switch (joinType)
{
case RBX::GameJoin_PMPCreator:
case RBX::GameJoin_Normal: launcherUrl = partyGuid.empty()? makePlaceLauncherUrl_Normal(id, Http::urlEncode(gamerTag))
: makePlaceLauncherUrl_PartyLeader(id, Http::urlEncode(gamerTag)); break;
case RBX::GameJoin_Instance: launcherUrl = makePlaceLauncherUrl_Instance(id, Http::urlEncode(gamerTag)); break;
case RBX::GameJoin_Follow: launcherUrl = makePlaceLauncherUrl_Follow(id, Http::urlEncode(gamerTag)); break;
case RBX::GameJoin_Party: launcherUrl = makePlaceLauncherUrl_Party(Http::urlEncode(gamerTag)); break;
case RBX::GameJoin_PartyLeader: launcherUrl = makePlaceLauncherUrl_PartyLeader(id, Http::urlEncode(gamerTag)); break;
default: launcherUrl = ""; break;
}
if( launcherUrl.empty() )
return GameStart_Weird; // should not happen
marshaller->submit( [=]() { setPartyTriedStatus(); } );
try{
validationResult.wait();
}catch(http_status_error &e){
dprintf("FAIL: validationResult in PrepareGamelaunch: %s\n", e.what());
return GameStart_NoPlayer;
}
catch(std::exception &e){
dprintf("validationResult in PrepareGamelaunch: %s\n", e.what());
}
enum { kNumRetries = 30, kWaitPeriod = 1000 };
for( int j=0; ; ++j)
{
if( j>kNumRetries )
{
dprintf("could not join the game [%s]", launcherUrl.c_str());
return GameStart_WebError;
}
auto r = requestPlaceInfo(launcherUrl, authenticationUrl, authenticationTicket, scriptUrl);
if( r == PlaceLaunch_Joining )
break; // ok
if( r < PlaceLaunch_Joining || r == PlaceLaunch_Error )
{
SleepEx(kWaitPeriod, FALSE);
dprintf("Retrying connection attempt (%d)...\n", j+1);
continue;
}
// errors
switch (r)
{
case PlaceLaunch_Error:
dprintf( "Request '%s' got status = %d, might want to look into it.\n", launcherUrl.c_str(), r ); // might mean 'no such game', or 'bad request', ask our web team
return GameStart_WebError;
case PlaceLaunch_SomethingReallyBad:
return GameStart_WebError;
case PlaceLaunch_Disabled:
case PlaceLaunch_Restricted:
case PlaceLaunch_Unauthorized:
return GameStart_NoAccess;
case PlaceLaunch_UserLeft:
return GameStart_NoPlayer;
case PlaceLaunch_GameEnded:
return GameStart_NoInstance;
case PlaceLaunch_GameFull:
return GameStart_Full;
default:
return GameStart_Weird;
}
}
return GameStart_OK;
}
RBX::GameStartResult XboxPlatform::initGame( const std::string& authUrl, const std::string& ticket, const std::string joinScriptUrl )
{
std::string joinScript;
try
{
dprintf("InitGame2():\n Auth: %s\n Ticket: %s\n joinScript: %s\n", authUrl.c_str(), ticket.c_str(), joinScriptUrl.c_str());
Http(joinScriptUrl).get(joinScript);
}
catch( const RBX::http_status_error& e )
{
dprintf( "initGame2(): join game: HTTP %d\n", e.statusCode );
return GameStart_WebError;
}
catch( const std::exception& e )
{
dprintf( "initGame2(): join game: %s\n", e.what() );
return GameStart_WebError;
}
if( joinScript.empty() )
{
dprintf( "initGame2(): bad server response (expected joinscript)\n" );
return GameStart_WebError;
}
dprintf( "Join script:\n%s\n", joinScript.c_str());
shared_ptr<DataModel> dm = game->getDataModel();
DataModel::hash = "ios,ios";
if (1)
{
DataModel::LegacyLock lock( game->getDataModel().get(), DataModelJob::Write);
Security::Impersonator impersonate(Security::RobloxGameScript_);
// make sure the gamepad navigation info is the same in the game datamodel as it is in the appshell
if (auto uis = RBX::ServiceProvider::create<UserInputService>(intro->getDataModel().get()))
{
uis->setGamepadEnabled(true);
if (auto gameUIS = RBX::ServiceProvider::create<UserInputService>(dm.get()))
{
gameUIS->setGamepadEnabled(true);
for (int i = 0; i < RBX_MAX_GAMEPADS; ++i)
{
InputObject::UserInputType gamepadType = GamepadService::getGamepadEnumForInt(i);
bool isNavigationGamepad = uis->isNavigationGamepad(gamepadType);
gameUIS->setNavigationGamepad(gamepadType, isNavigationGamepad);
}
}
}
ServiceProvider::create<GamepadService>(dm.get());
ServiceProvider::create<PlatformService>(dm.get())->setPlatform(this, PlatformDatamodelType::GameDatamodel);
TeleportService::SetCallback(this);
{
std::string tpbaseurl = kBaseUrl;
if( tpbaseurl.back() == '/' ) tpbaseurl.pop_back();
TeleportService::SetBaseUrl(tpbaseurl.c_str());
}
keyboard->registerTextBoxListener(dm.get());
int firstNewLineIndex = joinScript.find("\r\n");
game->configurePlayer(Security::COM, joinScript.substr(firstNewLineIndex+2), 0);
dm->gameLoaded();
dm->create<RunService>()->run();
dm->startCoreScripts(true);
}
dprintf("DATAMODEL GAME: 0x%p\n", dm.get());
struct ExitVerb : Verb
{
XboxPlatform* platform;
ExitVerb( XboxPlatform* plt, DataModel* dm ) : Verb(dm, "Exit"), platform(plt) {}
virtual void doIt(IDataState*) override { platform->marshaller->submit( [=]() -> void { platform->requestGameShutdown(false); } ); }
};
exitVerb = new ExitVerb(this, game->getDataModel().get());
initVoiceChatPoller();
return GameStart_OK;
}
void XboxPlatform::switchView( RBX::Game* to )
{
marshaller->execute(
[this, to]()
{
// reset vibration to 0 whenever we switch datamodels
if (controller)
{
shared_ptr<RBX::Reflection::Tuple> tuple(new RBX::Reflection::Tuple(1));
tuple->values[0] = 0.0f;
controller->setVibrationMotor(InputObject::TYPE_GAMEPAD1, RBX::HapticService::MOTOR_LARGE, tuple);
controller->setVibrationMotor(InputObject::TYPE_GAMEPAD1, RBX::HapticService::MOTOR_SMALL, tuple);
controller->setVibrationMotor(InputObject::TYPE_GAMEPAD1, RBX::HapticService::MOTOR_LEFTTRIGGER, tuple);
controller->setVibrationMotor(InputObject::TYPE_GAMEPAD1, RBX::HapticService::MOTOR_RIGHTTRIGGER, tuple);
}
// unbind
if (current)
{
TaskScheduler::singleton().removeBlocking(renderJob, boost::bind( &Marshaller::processEvents, marshaller ) );
DataModel::LegacyLock lock( current->getDataModel().get(), DataModelJob::Write);
if (RBX::Soundscape::SoundService* soundService = RBX::ServiceProvider::create<RBX::Soundscape::SoundService>(current->getDataModel().get()))
{
soundService->setMasterVolumeFadeOut(500.0f);
}
view->bindWorkspace(0);
renderJob.reset();
}
current = to;
if (current)
{
DataModel::LegacyLock lock( current->getDataModel().get(), DataModelJob::Write);
if (RBX::Soundscape::SoundService* soundService = RBX::ServiceProvider::create<RBX::Soundscape::SoundService>(current->getDataModel().get()))
{
soundService->setMasterVolumeFadeIn(1000.0f);
}
view->bindWorkspace(current->getDataModel());
renderJob.reset(new RenderJob(view, marshaller));
TaskScheduler::singleton().add(renderJob);
controller->setDataModel( current->getDataModel() );
enum ViewType
{
AppShell = 0,
Game
};
// this event is purposely fired on intro (appshell) in both cases.
ViewType gameType = current == intro ? AppShell : Game;
intro->getDataModel()->submitTask(
[gameType](DataModel* dm) -> void
{
if (PlatformService* ps = ServiceProvider::find<PlatformService>(dm))
ps->viewChanged((int)gameType);
},
DataModelJob::Write );
if (gameType == AppShell)
{
boost::thread( [=]()-> void {
RBX::HttpPlatformImpl::Cache::CacheCleanOptions opt;
opt.numFilesRequiredBeforeCleaning = 5000;
opt.numFilesToKeep = 4000;
opt.numGigaBytesAvailableTrigger = 1;
opt.flagCleanUpBasedOnMemory = true;
RBX::HttpPlatformImpl::Cache::cleanCache(opt);
} ).detach();
}
}
}
);
setRichPresence();
}
// used in DeviceD3D11aux.cpp
std::pair<unsigned int, unsigned int> xboxPlatformGetRenderSize_Hack(void* h)
{
RBXASSERT(h);
return reinterpret_cast<XboxPlatform*>(h)->getRenderSize();
}
///////////////////////////////////////////////////////////////////////////////
// Purchase
///////////////////////////////////////////////////////////////////////////////
int XboxPlatform::fetchInventoryInfo(shared_ptr<Reflection::ValueArray> values)
{
int status = -1;
if(!xboxLiveContext) return status;
async(xboxLiveContext->InventoryService->GetInventoryItemsAsync( Microsoft::Xbox::Services::Marketplace::MediaItemType::GameConsumable )).complete(
[this, &values, &status](Marketplace::InventoryItemsResult^ inventoryResult)
{
try
{
if(inventoryResult->Items->Size == 0)return;
for(int i = 0; i < inventoryResult->Items->Size; i++)
{
auto inventoryItem = inventoryResult->Items->GetAt(i);
if(inventoryItem->ConsumableBalance == 0) continue;
shared_ptr<Reflection::ValueTable> temp(rbx::make_shared<Reflection::ValueTable>());
temp->insert(std::pair<std::string, std::string>("ProductId", ws2s(inventoryItem->ProductId->Data())));
temp->insert(std::pair<std::string, int>("ConsumableBalance", inventoryItem->ConsumableBalance ));
Reflection::Variant tempValue = shared_ptr<const RBX::Reflection::ValueTable>(temp);
values->push_back(tempValue);
}
status = 0;
}
catch (Platform::Exception^ ex)
{
dprintf("error in fetchingInventoryInfo");
status = -1 ;
}
}
).except(
[](Platform::Exception^ e)
{
dprintf("ignoring all exceptions for now\n");
}
).join();
return status;
}
int XboxPlatform::fetchCatalogInfo(shared_ptr<Reflection::ValueArray> values)
{
if(!xboxLiveContext)
{
dprintf("fetchCatalogInfo: xboxLiveContext uninitialized\n");
return 1;
}
if(catalogItemDetails.size() == 0)
{
getCatalogInfo();
}
for(int i = 0; i < catalogItemDetails.size(); i++)
{
// hacky way to get rid of the 240 Test Item
if(ws2s(catalogItemDetails[i]->Name->Data()).find("Test") != std::string::npos) continue;
shared_ptr<Reflection::ValueTable> temp(rbx::make_shared<Reflection::ValueTable>());
temp->insert(std::pair<std::string, std::string>("Name", ws2s(catalogItemDetails[i]->Name->Data()) ));
temp->insert(std::pair<std::string, std::string>("ReducedName", ws2s(catalogItemDetails[i]->ReducedName->Data() )));
temp->insert(std::pair<std::string, std::string>("Description", ws2s(catalogItemDetails[i]->Description->Data()) ));
temp->insert(std::pair<std::string, std::string>("ProductId", ws2s(catalogItemDetails[i]->ProductId->Data() ) ));
temp->insert(std::pair<std::string, bool>("IsBundle",catalogItemDetails[i]->IsBundle));
temp->insert(std::pair<std::string, bool>("IsPartOfAnyBundle",catalogItemDetails[i]->IsPartOfAnyBundle));
temp->insert(std::pair<std::string, int>("TitleId",catalogItemDetails[i]->TitleId ));
temp->insert(std::pair<std::string, std::string>("DisplayPrice", ws2s(catalogItemDetails[i]->Availabilities->GetAt(0)->DisplayPrice->Data()) ));
temp->insert(std::pair<std::string, std::string>("DisplayListPrice", ws2s(catalogItemDetails[i]->Availabilities->GetAt(0)->DisplayListPrice->Data()) ));
Reflection::Variant tempValue = shared_ptr<const RBX::Reflection::ValueTable>(temp);
values->push_back(tempValue);
}
return 0; // success
}
void XboxPlatform::getCatalogInfo()
{
if( !xboxLiveContext )
{
dprintf("getCatalogInfo: xboxLiveContext not init");
return;
}
if(catalogItemDetails.size() > 0)
{
dprintf("getCatalogInfo: catalog already initialized for user");
return;
}
XboxLiveContext^ xblContext = xboxLiveContext;
// Create a task for the browsing work so that we don't block the main thread
concurrency::create_task( [this, xblContext] ()
{
try
{
IVectorView< Marketplace::CatalogItem^ >^ CatalogItems;
IAsyncOperation< Marketplace::BrowseCatalogResult^ >^ asyncOp1 =
xblContext->CatalogService->BrowseCatalogAsync(
TITLE_PARENT_ID, // parentId for Sample App
Marketplace::MediaItemType::Game, // parent MediaType
Marketplace::MediaItemType::GameConsumable, // desired MediaType for results
Marketplace::CatalogSortOrder::DigitalReleaseDate, // desired sort order
0, // skip to item
0 ); // max items (0 for all)
concurrency::create_task( asyncOp1 )
.then( [&CatalogItems] ( concurrency::task< Marketplace::BrowseCatalogResult^ > catalogBrowseTask )
{
try
{
Marketplace::BrowseCatalogResult^ browseResults = catalogBrowseTask.get();
CatalogItems = browseResults->Items;
}
catch( Platform::Exception^ ex )
{
dprintf( "SAMPLE: Error 0x%x browsing catalog\n", ex->HResult );
}
})
.wait(); // Because this is not on the main thread it is OK to wait for this task
if (!CatalogItems)
{
return;
}
dprintf( "SAMPLE: %i Consumables Found\n", CatalogItems->Size );
// NOTE: The Details service can only support 10 CatalogItems per request (MAX_DETAILS_ITEMS)
// So batch up the calls with 10 or less CatalogItems
for( UINT iBatchStartPosition = 0; iBatchStartPosition < CatalogItems->Size; iBatchStartPosition += MAX_DETAILS_BATCH_ITEMS )
{
// Extract just the ID's to make the call for the details of each one
IVector< Platform::String^ >^ vProductIds = ref new Platform::Collections::Vector< Platform::String^ >();
UINT iBatchEndPosition = iBatchStartPosition + MAX_DETAILS_BATCH_ITEMS;
if ( iBatchEndPosition > CatalogItems->Size )
{
iBatchEndPosition = CatalogItems->Size;
}
for( UINT i = iBatchStartPosition; i < iBatchEndPosition; ++i )
{
vProductIds->Append( CatalogItems->GetAt( i )->ProductId );
}
IAsyncOperation< IVectorView<Marketplace::CatalogItemDetails^ >^ >^ asyncOp =
xblContext->CatalogService->GetCatalogItemDetailsAsync( vProductIds->GetView() );
concurrency::create_task( asyncOp )
.then( [this] ( concurrency::task< IVectorView< Marketplace::CatalogItemDetails^ >^ > detailsResultTask )
{
try
{
// Get the results and append them to the return vector
IVectorView< Marketplace::CatalogItemDetails^ >^ detailsResults = detailsResultTask.get();
// Add any catalog products to our list for available consumabes in the UI
dprintf( "SAMPLE: %i Details returned\n", detailsResults->Size );
catalogItemDetails.clear();
for(int i = 0; i < detailsResults->Size; i++){
if(detailsResults->GetAt(i)->Availabilities->GetAt(0)->IsPurchasable){
catalogItemDetails.push_back(detailsResults->GetAt(i));
}
}
}
catch( Platform::Exception^ ex )
{
dprintf( "SAMPLE: Error 0x%x obtaining details from catalog\n", ex->HResult );
}
})
.wait(); // Because this is not on the main thread it is OK to wait for this task, but with multiple batches it isn't
// the best methood. This is just done for simplicity.
}
}
catch (Platform::Exception^ ex )
{
dprintf( "SAMPLE: Error 0x%x obtaining consumable product details\n", ex->HResult );
}
}).wait();
}
void XboxPlatform::sendConsumeAllRequest(bool is_purchase)
{
dprintf("SendingConsumeAllRequest...\n");
Windows::Xbox::System::User^ user = currentUser;
Microsoft::Xbox::Services::XboxLiveContext^ xbliveContext =xboxLiveContext;
concurrency::create_task( [user, xbliveContext, is_purchase, this] ()
{
if(InterlockedCompareExchange(&consumeAllFlag, 1, 0))
{
return;
}
std::string consumeAllUrl = kApiUrl + kConsumeInventoryEnpoint;
std::wstring userhash = user->XboxUserHash->Data(); //need to update to controller user when we set it up
std::wstring wxuid = user->XboxUserId->Data(); //need to update to controller user when we set it up
PlatformPurchaseResult retval = PurchaseResult_Error;
bool doConsumeAll;
const int max_attempts = 7;
for(int curr_attempt = 0; curr_attempt < max_attempts;curr_attempt++)
{
if (!user || !xboxLiveContext)
{
break;
}
unsigned sleeptime = 1000;
sleeptime = sleeptime << curr_attempt;
Sleep(sleeptime);
doConsumeAll = false;
// check inventory ourselves before sending a call to Roblox
async(xbliveContext->InventoryService->GetInventoryItemsAsync( Microsoft::Xbox::Services::Marketplace::MediaItemType::GameConsumable )).complete(
[this, &doConsumeAll](Marketplace::InventoryItemsResult^ inventoryResult){
try
{
if(inventoryResult->Items->Size > 0)
{
for(int i = 0; i < inventoryResult->Items->Size; i++)
{
if(inventoryResult->Items->GetAt(i)->ConsumableBalance > 0)
{
doConsumeAll = true; // do the sendConsumeAll right after the purchase is complete
return;
}
}
}
}
catch (Platform::Exception^ ex)
{
}
}).join();
if(!doConsumeAll)
break;
Http http(consumeAllUrl);
http.additionalHeaders["xbl-authz-actor-10"] = std::string(userhash.begin(), userhash.end());
http.additionalHeaders["xuid"] = std::string(wxuid.begin(), wxuid.end());
retval = PurchaseResult_Error;
std::string response;
try
{
response.clear();
std::string randomstr;
std::stringstream inputStream = std::stringstream(randomstr);
http.post(inputStream, Http::kContentTypeDefaultUnspecified, false, response);
dprintf("consumeAll Transaction: %s\n", response);
if(response.compare("OK") == 0)
{
retval = PurchaseResult_RobuxUpdated;
continue;
}else if(response.compare("Failed") == 0)
{
retval = PurchaseResult_ConsumeRequestFail;
continue;
}
else if(response.compare("Retry") == 0)
{
// nothing special todo except follow the flow
}
else if(!is_purchase)
{
dprintf("ConsumeAll called from Login: continue without retries\n");
retval = PurcahseResult_NoActionNeeded;
break;
}
}
catch(const RBX::http_status_error& e)
{
dprintf("sendConsumeAllRequest httpStatusError: %d\n", e.statusCode);
}
catch( const std::exception& e )
{
dprintf("Http error: %s\n", e.what());
}
if(curr_attempt == max_attempts)
dprintf("sendConsumeAllRequest() out of retries\n");
}
intro->getDataModel()->submitTask(
[retval](DataModel* dm) -> void
{
ServiceProvider::find<PlatformService>(dm)->robuxAmountChangedSignal((int)retval);
},
DataModelJob::Write );
InterlockedExchange(&consumeAllFlag, 0);
});
}
RBX::PlatformPurchaseResult XboxPlatform::requestPurchase(const std::string& productId)
{
std::wstring wproductId(productId.begin(), productId.end());
for(int i = 0; i < catalogItemDetails.size(); i++)
{
std::wstring wId = catalogItemDetails[i]->ProductId->Data();
if(wId.compare(wproductId) == 0)
{
if( purchaseItem(catalogItemDetails[i]->Availabilities->GetAt(0)->SignedOffer->Data()) )
{
dprintf("Completed Purchase for %S, productId: %S\n",catalogItemDetails[i]->Name, catalogItemDetails[i]->ProductId->Data() );
// The comsumeAll request will be called when the App is unconstrained.
// this will happen immediately after the app is done.
return RBX::PurchaseResult_Success; // success!
}
return RBX::PurchaseResult_UserCancelled; // failed on xbox
}
}
return RBX::PurchaseResult_Error; // no items found with given product id
}
bool XboxPlatform::purchaseItem( const std::wstring& wstrSignedOffer )
{
auto pAsyncOp = Windows::Xbox::ApplicationModel::Store::Product::ShowPurchaseAsync( currentUser , ref new Platform::String( wstrSignedOffer.c_str() ) );
bool status = false;
concurrency::create_task(pAsyncOp).then([this, &status] (concurrency::task<void> t)
{
try
{
t.get(); // to get potential error messages
dprintf( "ShowPurchaseAsync returned\n" );
status = true;
}
catch(Platform::Exception^ ex)
{
dprintf( " Failed Purchase of signed offer 0x%x\n", ex->HResult );
}
}).wait();
return status;
}
bool XboxPlatform::setRichPresence()
{
if (xboxLiveContext)
{
Platform::String^ presenceId = (current == intro) ? "Browsing" : "Playing";
PresenceData^ presenceData = ref new PresenceData(Windows::Xbox::Services::XboxLiveConfiguration::PrimaryServiceConfigId, presenceId);
XboxLiveContext^ xblContext = xboxLiveContext;
async(xblContext->PresenceService->SetPresenceAsync(true, presenceData)).complete(
[]() -> void
{
dprintf("Rich presence set\n");
}
).detach();
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
// Authorization/Login/Network
///////////////////////////////////////////////////////////////////////////////
void XboxPlatform::returnToEngagementScreen(ReturnToEngageScreenStatus status)
{
if(InterlockedCompareExchange(&returnToEngagementFlag, 1, 0))
{
return;
}
controller->resetControllerGuidMap();
marshaller->waitEvents();
// one of these job removals locks up when we are coming back from suspension
removePartyPoller();
removeValidSessionPoller();
requestGameShutdown(false);
multiplayerManager.reset();
currentUser = nullptr;
currentController = nullptr;
catalogItemDetails.clear();
// Send an event to XboxLive to tell them player Session has ended
if(xboxLiveContext)
xbEventPlayerSessionEnd();
xboxLiveContext = nullptr;
ReturnToEngageScreenStatus status2 = status;
intro->getDataModel()->submitTask(
[status2](DataModel* dm) -> void
{
ServiceProvider::find<PlatformService>(dm)->userAccountChangeSignal((int)status2);
},
DataModelJob::Write );
InterlockedExchange(&returnToEngagementFlag, 0);
}
void XboxPlatform::initializeNetworkConnectionEventHandlers()
{
Windows::Networking::Connectivity::NetworkInformation::NetworkStatusChanged +=
ref new Windows::Networking::Connectivity::NetworkStatusChangedEventHandler(
[this] ( Platform::Object^ )
{
auto internetprofile = Windows::Networking::Connectivity::NetworkInformation::GetInternetConnectionProfile();
if( internetprofile )
{
switch(internetprofile->GetNetworkConnectivityLevel())
{
case Windows::Networking::Connectivity::NetworkConnectivityLevel::XboxLiveAccess:
// we have internet access and all tokens have been authenticated for xbox live
return;
case Windows::Networking::Connectivity::NetworkConnectivityLevel::InternetAccess:
// we have internet but not XboxLive
return;
// the remaining cases mean we can't connect to the internet
case Windows::Networking::Connectivity::NetworkConnectivityLevel::LocalAccess:
{
intro->getDataModel()->submitTask(
[](DataModel* dm) -> void
{
ServiceProvider::find<PlatformService>(dm)->networkStatusChangedSignal("LocalAccessOnly");
},
DataModelJob::Write );
return;
}
case Windows::Networking::Connectivity::NetworkConnectivityLevel::ConstrainedInternetAccess:
{
intro->getDataModel()->submitTask(
[](DataModel* dm) -> void
{
ServiceProvider::find<PlatformService>(dm)->networkStatusChangedSignal("ConstrainedInternet");
},
DataModelJob::Write );
return;
}
case Windows::Networking::Connectivity::NetworkConnectivityLevel::None:
{
intro->getDataModel()->submitTask(
[](DataModel* dm) -> void
{
ServiceProvider::find<PlatformService>(dm)->networkStatusChangedSignal("NoInternetConnection");
},
DataModelJob::Write );
return;
}
default:
break;
}
}
// if there is any connection error, we prompt the user to check their connection
}
);
}
void XboxPlatform::storeLocalGamerPicture(XboxLiveContext^ xboxLiveContext)
{
if (!xboxLiveContext)
{
return;
}
const int OneMeg = 1048576;
auto buffer = ref new Windows::Storage::Streams::Buffer(64 * OneMeg);
Windows::Foundation::IAsyncOperation<GetPictureResult^>^ getGamerPicOperation = xboxLiveContext->User->DisplayInfo->GetGamerPictureAsync(Windows::Xbox::System::UserPictureSize::Large, buffer);
getGamerPicOperation->Completed = ref new Windows::Foundation::AsyncOperationCompletedHandler<GetPictureResult^>
([=](Windows::Foundation::IAsyncOperation<GetPictureResult^>^ operation, Windows::Foundation::AsyncStatus status)
{
if(status == Windows::Foundation::AsyncStatus::Completed)
{
if (buffer->Length > 0)
{
boost::filesystem::path gamerpicPath = RBX::FileSystem::getUserDirectory(true, DirAppData, "");
gamerpicPath /= "gamerpic.png";
Windows::Storage::Streams::DataReader^ dataReader = Windows::Storage::Streams::DataReader::FromBuffer(buffer);
std::ofstream outputFile;
outputFile.open(gamerpicPath.c_str(), std::ios::binary | std::ios::out);
while (dataReader->UnconsumedBufferLength > 0)
{
unsigned char dataByte = dataReader->ReadByte();
outputFile << dataByte;
}
outputFile.close();
marshaller->submit( [=]()->void {
if (view)
{
view->immediateAssetReload("rbxapp://xbox/localgamerpic");
}
} );
}
}
}
);
}
User^ XboxPlatform::getActiveUser()
{
User^ newActiveUser = nullptr;
bool waitingForAButton = true;
bool didGetGamepad = true;
while (waitingForAButton)
{
IVectorView<IGamepad^>^ gamepads = Windows::Xbox::Input::Gamepad::Gamepads;
IGamepad^ gamepad;
if (gamepads->Size)
{
for (int i = 0; i < gamepads->Size; i++)
{
IGamepad^ gamepad = gamepads->GetAt(i);
value class RawGamepadReading gr = gamepad->GetRawCurrentReading();
if(int(gr.Buttons & GamepadButtons::A))
{
currentController = gamepad;
if (!gamepad->User|| !gamepad->User->IsSignedIn || gamepad->User != currentUser)
{
if (tryToSignInActiveUser(gamepad))
{
waitingForAButton = false;
}
}
else
{
controller->remapXboxLiveIdToRBXGamepadId(currentUser->XboxUserId, gamepad->Id, RBX::InputObject::TYPE_GAMEPAD1);
waitingForAButton = false;
}
if (!waitingForAButton)
{
newActiveUser = gamepad->User;
// wait to break until we get a button up (fixes issues with UI button press)
value class RawGamepadReading gr = gamepad->GetRawCurrentReading();
while (int(gr.Buttons & GamepadButtons::A))
{
Sleep(1);
gr = gamepad->GetRawCurrentReading();
}
break;
}
}
}
}
if (waitingForAButton)
{
Sleep(1);
}
}
return newActiveUser;
}
void XboxPlatform::attemptToSignActiveUserIn(std::string userToSignIn, IController^ controllerToUse)
{
current->getDataModel()->submitTask([=](...)
{
if(PlatformService* p = ServiceProvider::find<RBX::PlatformService>(current->getDataModel().get()))
p->lostActiveUser(userToSignIn);
}
, DataModelJob::Write);
boost::thread([=]()
{
User^ activeUser = getActiveUser();
Platform::String^ displayName = activeUser->DisplayInfo->Gamertag;
std::string displayNameString = ws2s(displayName->Data());
shared_ptr<RBX::DataModel> dm = current->getDataModel();
dm->submitTask([=](...)
{
if(PlatformService* p = ServiceProvider::find<RBX::PlatformService>(dm.get()))
p->gainedActiveUser(displayNameString);
}
, DataModelJob::Write);
if (!currentUser || !isStringEqual(activeUser->XboxUserId, currentUser->XboxUserId))
{
returnToEngagementScreen(ReturnToEngage_SignOut);
}
} ).detach();
}
void XboxPlatform::attemptToReconnectController(std::string userToSignIn)
{
controller->resetControllerGuidMap();
current->getDataModel()->submitTask([=](...)
{
if(PlatformService* p = ServiceProvider::find<RBX::PlatformService>(current->getDataModel().get()))
p->lostUserGamepad(userToSignIn);
}
, DataModelJob::Write);
boost::thread([=]()
{
User^ activeUser = getActiveUser();
Platform::String^ displayName = activeUser->DisplayInfo->Gamertag;
std::string displayNameString = ws2s(displayName->Data());
shared_ptr<RBX::DataModel> dm = current->getDataModel();
dm->submitTask([=](...)
{
if(PlatformService* p = ServiceProvider::find<RBX::PlatformService>(dm.get()))
p->gainedUserGamepad(displayNameString);
}
, DataModelJob::Write);
if (!currentUser || activeUser->XboxUserId != currentUser->XboxUserId)
{
returnToEngagementScreen(ReturnToEngage_ControllerChange);
}
} ).detach();
}
User^ XboxPlatform::tryToSignInActiveUser(IController^ controllerToUse)
{
trySignInOnActivation = false;
if (!controllerToUse)
{
return nullptr;
}
auto asyncOp = Windows::Xbox::UI::SystemUI::ShowAccountPickerAsync(controllerToUse, Windows::Xbox::UI::AccountPickerOptions::None);
while ( asyncOp->Status == Windows::Foundation::AsyncStatus::Started )
{
Sleep(1);
}
if (asyncOp->Status == Windows::Foundation::AsyncStatus::Canceled ||
asyncOp->Status == Windows::Foundation::AsyncStatus::Error ||
!controllerToUse->User )
{
return nullptr;
}
if (currentUser && currentUser->XboxUserId == controllerToUse->User->XboxUserId)
{
controller->remapXboxLiveIdToRBXGamepadId(controllerToUse->User->XboxUserId, controllerToUse->Id, InputObject::TYPE_GAMEPAD1);
}
return controllerToUse->User;
}
std::vector<IGamepad^> XboxPlatform::getGamepadsForCurrentUser()
{
std::vector<IGamepad^> gamepads;
if (currentUser)
{
IVectorView<IController^>^ controllers = currentUser->Controllers;
for(int i = 0; i < controllers->Size; i++)
{
IController^ controller = controllers->GetAt(i);
if (controller->User && controller->User->XboxUserId == currentUser->XboxUserId)
{
if (IGamepad^ gamepad = dynamic_cast<IGamepad^>(controller))
{
gamepads.push_back(gamepad);
}
}
}
}
return gamepads;
}
void XboxPlatform::handleCurrentControllerChanged()
{
currentController = nullptr;
controller->resetControllerGuidMap();
Platform::String^ displayName = currentUser->DisplayInfo->Gamertag;
std::string displayNameString = ws2s(displayName->Data());
attemptToReconnectController(displayNameString);
}
void XboxPlatform::initializeAuthEventHandlers()
{
User::UserDisplayInfoChanged += ref new EventHandler<UserDisplayInfoChangedEventArgs^ >( [=]( Platform::Object^ , UserDisplayInfoChangedEventArgs^ args )
{
dprintf("UserDisplayInfoChanged Event\n");
//TODO: NEED TO RESOLVE PROFILE PIC CHANGE(do nothing) and need to decide if username changing is required to reauth.
if(currentUser && args->User->XboxUserId == currentUser->XboxUserId)
{
storeLocalGamerPicture(xboxLiveContext);
//for now the only userdisplayinfo that we act on is the displayname
if(!isStringEqual(currentUser->DisplayInfo->Gamertag, args->User->DisplayInfo->Gamertag))
{
Platform::String^ displayName = currentUser->DisplayInfo->Gamertag;
returnToEngagementScreen(ReturnToEngage_DisplayInfoChange);
}
}
} );
User::SignInCompleted += ref new EventHandler<SignInCompletedEventArgs^ >( [=]( Platform::Object^ , SignInCompletedEventArgs^ args )
{
dprintf("SignInCompleted Event\n");
// make sure we keep our user fresh (this is required by XRs)
if (currentUser && args->User && args->User->XboxUserId == currentUser->XboxUserId)
{
//reinit user
currentUser = args->User;
while(!xboxLiveContext){
dprintf("initializing xboxlivecontext\n");
xboxLiveContext = ref new XboxLiveContext( currentUser );
}
multiplayerManager.reset( new XboxMultiplayerManager(xboxLiveContext, playerSessionGuid, intro->getDataModel()->find<Network::Players>()->getLocalPlayer()->getUserID() ) );
initVoiceChatPoller();
if (current == game)
{
multiplayerManager->startMultiplayerListeners();
multiplayerManager->createGameSession();
}
}
} );
User::SignOutStarted += ref new EventHandler<SignOutStartedEventArgs^ >( [=]( Platform::Object^ , SignOutStartedEventArgs^ args )
{
dprintf("SignOutStarted Event\n");
if(currentUser && args->User == currentUser && currentController)
{
if(multiplayerManager)
multiplayerManager->stopMultiplayerListeners();
multiplayerManager.reset();
if(xboxLiveContext && game == current)
{
Platform::String^ displayName = args->User->DisplayInfo->Gamertag;
attemptToSignActiveUserIn(ws2s(displayName->Data()), currentController);
}
else
{
returnToEngagementScreen(ReturnToEngage_SignOut);
}
xboxLiveContext = nullptr;
}
} );
Controller::ControllerRemoved += ref new EventHandler<ControllerRemovedEventArgs^ >( [=]( Platform::Object^ , ControllerRemovedEventArgs^ args )
{
dprintf("ControllerRemoved Event\n");
if (currentController && args->Controller->Id == currentController->Id)
{
currentController = nullptr;
handleCurrentControllerChanged();
}
} );
Controller::ControllerPairingChanged += ref new EventHandler<ControllerPairingChangedEventArgs^ >( [=]( Platform::Object^ , ControllerPairingChangedEventArgs^ args )
{
dprintf("ControllerPairingChanged Event\n");
RBX::mutex::scoped_lock lock(controllerPairingMutex);
if (!args->Controller->User || !currentUser || !currentController || !dynamic_cast<IGamepad^>(args->Controller))
{
return;
}
unsigned long long argControllerId = args->Controller->Id;
User^ argOldUser = args->PreviousUser;
User^ argNewUser = args->User;
// our current controller is being affected, need to do something
if (argControllerId == currentController->Id)
{
if (argNewUser == currentUser && currentController->User == args->Controller->User)
{
return;
}
currentController = nullptr;
if (!currentUser->IsSignedIn || (argNewUser && argNewUser->IsGuest))
{
if (argOldUser)
{
Platform::String^ displayName = argOldUser->DisplayInfo->Gamertag;
attemptToSignActiveUserIn(ws2s(displayName->Data()), args->Controller);
}
}
else
{
if (argOldUser && argOldUser->XboxUserId == currentUser->XboxUserId && !argNewUser)
{
Platform::String^ displayName = currentUser->DisplayInfo->Gamertag;
attemptToSignActiveUserIn(ws2s(displayName->Data()), args->Controller);
}
else
{
handleCurrentControllerChanged();
}
}
}
} );
}
void XboxPlatform::setGameProgress()
{
if(!xboxLiveContext) return;
int titleId = ServiceProvider::find<PlatformService>(intro->getDataModel().get())->getTitleId();
XboxLiveContext^ xblcontext = xboxLiveContext;
// Get gamerscore that this user had collected
async(xblcontext->AchievementService->GetAchievementsForTitleIdAsync(currentUser->XboxUserId, titleId, AchievementType::All, false, AchievementOrderBy::Default, 0, 100))
.complete(
[=](AchievementsResult^ achievements) -> void
{
int usersGamerscore = 0;
for( Achievement^ achievement : achievements->Items )
{
if (achievement->ProgressState == AchievementProgressState::Achieved)
{
for( AchievementReward^ reward : achievement->Rewards )
{
if (reward->RewardType == AchievementRewardType::Gamerscore)
{
int rewardValue = _wtoi(reward->Data->Data());
usersGamerscore += rewardValue;
}
}
}
}
double gameProgress = (double)usersGamerscore * 0.1;
xboxEvents_send(xblcontext, "GameProgress", &gameProgress);
}
).detach();
}
// reentrant
RBX::AwardResult XboxPlatform::awardAchievement(const std::string& eventName)
{
RBX::AwardResult res = xboxEvents_send(xboxLiveContext, eventName.c_str());
setGameProgress();
return res;
}
// reentrant
RBX::AwardResult XboxPlatform::setHeroStat(const std::string& eventName, double* value)
{
return xboxEvents_send(xboxLiveContext, eventName.c_str(), value);
}
//////////////////////////////////////////////////////////////////////////
AccountAuthResult httpErrorToAccountLinkResult(const std::string& status)
{
// this looks terrible. needs a better way to approach this
if(status.compare("SignUpDisabled") == 0)
return AccountAuth_SignUpDisabled;
else if(status.compare("Flooded") == 0)
return AccountAuth_Flooded;
else if(status.compare("LeaseLocked") == 0)
return AccountAuth_LeaseLocked;
else if(status.compare("AccountLinkingDisabled") == 0)
return AccountAuth_AccountLinkingDisabled;
else if(status.compare("InvalidRobloxUser") == 0)
return AccountAuth_InvalidRobloxUser;
else if(status.compare("RobloxUserAlreadyLinked") == 0)
return AccountAuth_RobloxUserAlreadyLinked;
else if(status.compare("XboxUserAlreadyLinked") == 0)
return AccountAuth_XboxUserAlreadyLinked;
else if(status.compare("IllegalChildAccountLinking") == 0)
return AccountAuth_IllegalChildAccountLinking;
else if(status.compare("InvalidPassword") == 0)
return AccountAuth_InvalidPassword;
else if(status.compare("UsernamePasswordNotSet") == 0)
return AccountAuth_UsernamePasswordNotSet;
else if(status.compare("Already Taken") == 0)
return AccountAuth_UsernameAlreadyTaken;
else if(status.compare("InvalidCredentials") == 0)
return AccountAuth_InvalidCredentials;
else
{
dprintf("Unknown AccountLink error: %s", status);
return AccountAuth_Error;
}
}
AccountAuthResult XboxPlatform::xboxAuthHttpHelper( const std::string& url, std::string* response, bool isPost){
if(currentUser==nullptr)
{
dprintf("xboxAuthHttpHelper: currentUserNotKnown not init\n");
return AccountAuth_NoUserDetected;
}
try // exceptions are all over the place and there's no escape
{
//TODO: this needs some cleanup, maybe move the setting userId to another function?
Http http(url);
std::wstring userhash = currentUser->XboxUserHash->Data();
std::string userHash2(userhash.begin(), userhash.end());
http.additionalHeaders["xbl-authz-actor-10"] = userHash2;
std::stringstream inputStream = std::stringstream(*response);
if(isPost)
{
http.post(inputStream, Http::kContentTypeDefaultUnspecified, false, *response);
}
else
{
http.get(*response);
}
dprintf("xboxAuthHttpHelper Response: %s\n", response->c_str());
}
catch (const RBX::http_status_error& e){
dprintf("Http status Error: %d : %s\n", e.statusCode, response->c_str());
return AccountAuth_HttpErrorDetected;
}
catch (Platform::COMException ^e){
if (e->HResult == 0x87DD0021)
{
dprintf("FAILED UserAuth COMException: [0x%x] (%S): endpoint does not need authToken...\n", e->HResult, e->Message->Data());
}
else if( e->HResult == 0x8015dc12)
{
dprintf("FAILED UserAuth COMException: [0x%x] (%S): Check that you are using the correct SandBoxID...\n", e->HResult, e->Message->Data());
}
else
{
dprintf("FAILED UserAuth COMException: [0x%x] (%S)\n", e->HResult, e->Message->Data());
}
return AccountAuth_HttpErrorDetected;
}
catch ( Platform::Exception ^e )
{
dprintf( "FAILED UserAuth Exception: [0x%x] (%S)\n", e->HResult, e->Message->Data());
return AccountAuth_Error;
}
catch (const std::exception& e)
{
dprintf("Error: %s", e.what());
return AccountAuth_Error;
}
return AccountAuth_Success;
}
int XboxPlatform::performAccountLink(const std::string& accountName, const std::string& password, std::string* response)
{
dprintf("Attempting to AccountLink %s...\n", accountName);
std::string url = kApiUrl + kXboxAccountLinkEndpoint;
url = url + "?username=" + accountName + "&password=" + password;
AccountAuthResult retval = xboxAuthHttpHelper(url, response, true);
if(retval == AccountAuth_Success){
return authAndInitUser();
}else{
return httpErrorToAccountLinkResult(*response);
}
}
int XboxPlatform::performUnlinkAccount(std::string* response)
{
dprintf("Attempting to UnlinkAccount...\n");
AccountAuthResult status = performHasRobloxCredentials();
if (status == AccountAuth_Success)
{
std::string url = kApiUrl + kUnlinkAccountEndpoint;
status = xboxAuthHttpHelper(url, response, true);
if (status == AccountAuth_Success)
{
returnToEngagementScreen(ReturnToEngage_UnlinkSuccess);
}
else
{
status = httpErrorToAccountLinkResult(*response);
}
}
return status;
}
int XboxPlatform::performSetRobloxCredentials( const std::string& accountName, const std::string& password, std::string* response)
{
dprintf("Attempting to setRobloxCredentials of %s...\n", accountName);
std::string url = kApiUrl + kXboxSetRobloxUsernamePasswordEndpoint;
url = url + "?username=" + accountName + "&password=" + password;
AccountAuthResult retval = xboxAuthHttpHelper(url, response, true);
if(retval == AccountAuth_Success){
//clean up before re-auth
marshaller->waitEvents();
removeGenericDataModelJob(validSessionPoller, [=]() { marshaller->processEvents(); } );
removeGenericDataModelJob(partyPoller, [=]() { marshaller->processEvents(); } );
multiplayerManager.reset();
xboxLiveContext = nullptr;
catalogItemDetails.clear();
return authAndInitUser();
}else{
return httpErrorToAccountLinkResult(*response);
}
}
static std::string getCacheFighterString()
{
SYSTEMTIME curTime = {0};
GetSystemTime(&curTime);
std::stringstream ss;
ss << curTime.wMilliseconds;
ss << curTime.wMinute;
ss << curTime.wHour;
ss << curTime.wDay;
ss << curTime.wMonth;
ss << curTime.wYear;
return "cache-fighter=" + ss.str();
}
AccountAuthResult XboxPlatform::performHasLinkedAccount()
{
dprintf("Attempting to check if hasLinkedAccount...\n");
std::string url = kApiUrl + kHasLinkedAccountEndpoint;
std::string result;
AccountAuthResult retval = xboxAuthHttpHelper(url, &result, false);
if(retval == AccountAuth_Success){
boost::shared_ptr<const Reflection::ValueTable> parameters = rbx::make_shared<const Reflection::ValueTable>();
WebParser::parseJSONTable(result, parameters);
RBX::Reflection::ValueTable::const_iterator iter = parameters->find("hasLinkedAccount");
if (iter != parameters->end() && iter->second.isType<bool>())
{
retval = iter->second.get<bool>() ? AccountAuth_Success : AccountAuth_AccountUnlinked;
}
}
return retval;
}
AccountAuthResult XboxPlatform::performHasRobloxCredentials()
{
dprintf("Attempting to check if hasRobloxCredentials...\n");
std::string url = kApiUrl + kHasSetUsernamePasswordEndpoint;
std::string result;
AccountAuthResult retval = xboxAuthHttpHelper(url, &result, false);
if(retval == AccountAuth_Success){
boost::shared_ptr<const Reflection::ValueTable> parameters = rbx::make_shared<const Reflection::ValueTable>();
WebParser::parseJSONTable(result, parameters);
RBX::Reflection::ValueTable::const_iterator iter = parameters->find("hasSetUsernameAndPassword");
if (iter != parameters->end() && iter->second.isType<bool>())
{
retval = iter->second.get<bool>() ? AccountAuth_Success : AccountAuth_UsernamePasswordNotSet;
}
}
return retval;
}
void XboxPlatform::checkValidSessionAsync()
{
try{
const std::string url = kApiUrl+kXboxUserInfoEndpoint;
Http(url).get(
[this](std::string* r, std::exception* e)
{
if( http_status_error* st = dynamic_cast<http_status_error*>(e))
{
dprintf( "validSessionPoller: HTTP %d\n", st->statusCode );
if(st->statusCode == 403 || st->statusCode == 401){
returnToEngagementScreen(ReturnToEngage_InvalidSession);
}
}
}
);
}
catch(std::exception& e){
("validSessionAsyncError: %s ", e.what());
}
}
void XboxPlatform::initValidSessionPoller()
{
if(!xboxLiveContext || !xboxLiveContext->User->IsSignedIn) return;
// lambda to check sessions status
removeGenericDataModelJob(validSessionPoller, [=]()->void{marshaller->processEvents();} );
validSessionPoller = addGenericDataModelJob( "validSessionPoller", DataModelJob::None, intro->getDataModel(), FInt::CheckValidSessionInterval*1.0f,
[=]()mutable -> void
{
checkValidSessionAsync();
});//lamda
}
void XboxPlatform::removeValidSessionPoller()
{
removeGenericDataModelJob(validSessionPoller, [=]() { marshaller->processEvents(); } );
}
AccountAuthResult XboxPlatform::performAuthorization(InputObject::UserInputType gamepadId, bool unlinkedCheck)
{
if (gamepadId == InputObject::UserInputType::TYPE_NONE)
{
dprintf("No gamepad, auth not possible.\n");
return AccountAuth_MissingGamePad;
}
// get the user that hit the engagement button
controller->setupGamepadHandling();
uint64 controllerGuid = controller->getGamepadGuid(gamepadId);
IVectorView<IGamepad^>^ gamepads = Windows::Xbox::Input::Gamepad::Gamepads;
IController ^ userGamePad = nullptr;
for(int i = 0; i < gamepads->Size; i++)
{
if(gamepads->GetAt(i)->Id == controllerGuid)
{
userGamePad = gamepads->GetAt(i);
break;
}
}
if(!userGamePad )
{
dprintf("userGamepad is NULL\n");
return AccountAuth_MissingGamePad;
}
if(!(userGamePad->User && userGamePad->User->IsSignedIn) )
{
dprintf("Error in userAuth: No user paired to active controller\n");
auto asyncOp = Windows::Xbox::UI::SystemUI::ShowAccountPickerAsync(userGamePad, Windows::Xbox::UI::AccountPickerOptions::None); // We do not allow Xbox Guests
while ( asyncOp->Status == Windows::Foundation::AsyncStatus::Started )
{
Sleep(1);
}
}
if(!userGamePad->User)
{
dprintf("userGamepad does not have an associated user selected\n");
return AccountAuth_NoUserDetected;
}
// currently only use gamepad1, should be fixed
currentUser = userGamePad->User;
currentController = userGamePad;
AccountAuthResult authResult = AccountAuth_Error;
//Check if linked
if(unlinkedCheck)
{
authResult = performHasLinkedAccount();
if (authResult == AccountAuth_Success)
{
authResult = authAndInitUser();
}
}
else
{
authResult = authAndInitUser();
}
if (authResult == AccountAuth_Success && currentUser )
{
//todo: allow for more than controller (need this now for xbox cert)
controller->remapXboxLiveIdToRBXGamepadId(currentUser->XboxUserId, currentController->Id, InputObject::TYPE_GAMEPAD1);
}
boost::thread([=]()
{
std::string trackerUrl = kApiUrl + kBrowserTrackerCookieEndpoint;
std::string response;
RBX::AccountAuthResult retval = xboxAuthHttpHelper(trackerUrl, &response, true);
}).detach();
return authResult;
}
AccountAuthResult XboxPlatform::authAndInitUser()
{
if(!currentUser){
return AccountAuth_NoUserDetected;
}
dprintf("Attempting to authenticate %S...\n", currentUser->DisplayInfo->Gamertag->Data());
std::string strAuthUrl = kApiUrl+kAuthEndpoint;
std::string response;
AccountAuthResult retval = xboxAuthHttpHelper(strAuthUrl, &response, true);
if(retval != AccountAuth_Success)
{
return httpErrorToAccountLinkResult(response);
}
dprintf("UserHash: %s \n", ws2s(currentUser->XboxUserHash->Data()));
dprintf("Authen_Response: %s \n", response.c_str());
try // exceptions are all over the place and there's no escape
{
// Update the userId
std::string getUserInfoUrl = kApiUrl + kXboxUserInfoEndpoint;
Http userInfohttp(getUserInfoUrl);
response.clear();
userInfohttp.get(response);
dprintf("UserInfoReponse: %s\n", response.c_str());
int userId = -1;
boost::shared_ptr<const Reflection::ValueTable> parameters = rbx::make_shared<const Reflection::ValueTable>();
WebParser::parseJSONTable(response, parameters);
RBX::Reflection::ValueTable::const_iterator iter = parameters->find("userId");
if (iter != parameters->end())
userId = iter->second.get<int>();
if(userId == -1)
dprintf("Was unable to parse userid from response\n");
intro->getDataModel()->submitTask(
[=](...) -> void
{
Network::Players* localplayers = intro->getDataModel()->find<Network::Players>();
localplayers->resetLocalPlayer();
localplayers->createLocalPlayer(userId);
},
DataModelJob::Write
);
}
catch (const RBX::http_status_error& e)
{
dprintf("Http status Error: %d : %s\n", e.statusCode, e.what());
return AccountAuth_HttpErrorDetected;
}
catch (Platform::COMException ^e)
{
if (e->HResult == 0x87DD0021) {
dprintf("FAILED UserAuth COMException: [0x%x] (%S): endpoint does not need authToken...\n", e->HResult, e->Message->Data());
}else if( e->HResult == 0x8015dc12){
dprintf("FAILED UserAuth COMException: [0x%x] (%S): Check that you are using the correct SandBoxID...\n", e->HResult, e->Message->Data());
}else{
dprintf("FAILED UserAuth COMException: [0x%x] (%S)\n", e->HResult, e->Message->Data());
}
return AccountAuth_HttpErrorDetected;
}
catch ( Platform::Exception ^e )
{
dprintf( "FAILED UserAuth Exception: [0x%x] (%S)\n", e->HResult, e->Message->Data());
return AccountAuth_Error;
}
catch (const std::exception& e)
{
dprintf("Error: %s", e.what());
return AccountAuth_Error;
}
try
{
// Initialize xboxLiveContext on successful authentification// note this is a work around
while(!xboxLiveContext )
{
dprintf("initializing xboxlivecontext\n");
xboxLiveContext = ref new XboxLiveContext( currentUser );
}
// we want to use this but until Microsoft fixes the xboxLiveContext bug, we cant do much here
/*
plat->xboxLiveContext = ref new XboxLiveContext( plat->currentGamepadUser );
if(plat->xboxLiveContext != nullptr){
dprintf("xboxLiveContext was not initialized\n");
}
*/
//enableVerboseXboxErrors(xboxLiveContext); // Uncomment this if you want to see the http results from xboxlive
dprintf("xboxLiveContext now initialized for user %S\n", currentUser->DisplayInfo->GameDisplayName->Data());
storeLocalGamerPicture(xboxLiveContext);
getCatalogInfo();
sendConsumeAllRequest(false);
setRichPresence();
setGameProgress();
initPartyPoller();
initValidSessionPoller();
CoCreateGuid(&playerSessionGuid);
multiplayerManager.reset(new XboxMultiplayerManager(xboxLiveContext, playerSessionGuid, intro->getDataModel()->find<Network::Players>()->getLocalPlayer()->getUserID()));
// let xboxlive know we have started a player session. MUST make sure guid is set first
xbEventPlayerSessionStart();
// handle game invite/join here
if(!gameInviteHandle.targetUserXuid.empty())
{
if(gameInviteHandle.followUserId != -1)
{
Platform::String^ inviteRef = ref new Platform::String(s2ws(&gameInviteHandle.targetUserXuid).c_str());
startGame3( GameJoin_Follow, gameInviteHandle.followUserId );
}
else
{
// how...
dprintf("failed game join.\n");
}
//reset after consumed
gameInviteHandle.targetUserXuid = "";
gameInviteHandle.followUserId = -1;
gameInviteHandle.pmpCreator = 0;
}
return AccountAuth_Success;
}
catch(Platform::Exception ^ ex)
{
dprintf("Error getting signed-in user: %S 0x%x\n", ex->HResult.ToString()->Data(), ex->HResult);
}
return AccountAuth_Error;
}
static int createParty( std::vector<std::string>& players, std::string* partyGuid )
{
RBXASSERT( partyGuid );
std::stringstream ss;
ss << "{ \"ids\" : [";
bool comma = false;
for( auto& gamertag : players )
{
if(comma) ss<<", ";
ss << " \"" << gamertag << "\" ";
comma = true;
}
ss << " ] }";
ss.flush();
ss.seekp(0);
std::string response;
try
{
std::string url = RBX::format("%sxbox/create-party", kApiUrl.c_str());
dprintf( "HTTP POST %s | %s", url.c_str(), ss.str().c_str() );
Http(url).post(ss, "application/json", false, response);
dprintf( " | OK | %s\n", response.c_str() );
}
catch( const RBX::http_status_error& e )
{
dprintf( "createParty(): HTTP %d\n", e.statusCode );
return -1;
}
catch( const std::exception& e )
{
dprintf( "createParty(): %s\n", e.what() );
return -1;
}
auto jsonResult(rbx::make_shared<const RBX::Reflection::ValueTable>());
if (!RBX::WebParser::parseJSONTable(response, jsonResult))
{
dprintf( "createParty(): bad web response.\n" );
return -1;
}
RBX::Reflection::ValueTable::const_iterator itData = jsonResult->find("PartyId");
if( itData == jsonResult->end() || !itData->second.isString() )
{
dprintf( "createParty(): failed.\n");
return -1;
}
partyGuid->assign( itData->second.get<std::string>() );
if( partyGuid->empty() )
{
dprintf( "createParty(): got empty guid???" );
return -1;
}
return 0;
}
void XboxPlatform::initPartyPoller()
{
if(!xboxLiveContext || !xboxLiveContext->User->IsSignedIn) return;
removeGenericDataModelJob(partyPoller, [=]()->void{marshaller->processEvents();} );
std::string curPartyGuid;
XboxLiveContext^ xblcontext = xboxLiveContext;
partyPoller = addGenericDataModelJob( "partyPoller", DataModelJob::None, intro->getDataModel(), 2.0,
[=]()mutable -> void
{
static volatile long flag = 0;
if( InterlockedCompareExchange( &flag, 1, 0) == 0 )
{
do{
//dprintf("partyPoller!\n");
if (!xblcontext || !PartyChat::IsPartyChatActive) break; // party unavailable
PartyChatView^ partyChatView;
try{
async( PartyChat::GetPartyChatViewAsync() ).complete(
[&]( PartyChatView^ pcv )
{
partyChatView = pcv;
}
).join();
}
catch(Platform::Exception^ e)
{
break;
}
if (!partyChatView) break;
if( partyChatView->Members->Size < 2 ) break; // not enough people
static const std::string url = kApiUrl + kGetPartyInfoEndpoint;
std::string response;
try
{
Http(url).get(response);
}
catch( const RBX::http_status_error& e )
{
dprintf("partyPoller: got HTTP %d\n", e.statusCode );
break;
}
catch( const std::exception& e )
{
dprintf("partyPoller: %s\n", e.what());
break;
}
auto jsonResult(rbx::make_shared<const RBX::Reflection::ValueTable>());
if (!RBX::WebParser::parseJSONTable(response, jsonResult))
{
dprintf( "partyPoller: bad web response #1.\n" );
break;
}
auto it = jsonResult->find("IsPartyLeaderInGame");
if( it == jsonResult->end() || !it->second.isType<bool>() )
{
dprintf("partyPoller: bad web response #2.\n" );
}
bool leaderInGame = it->second.get<bool>();
std::string partyGuid;
it = jsonResult->find("PartyId");
if( it != jsonResult->end() && it->second.isString() )
partyGuid = it->second.get<std::string>();
// success
marshaller->submit( [=]()->void { this->setPartyPollStatus(partyGuid, leaderInGame); } );
InterlockedExchange( &flag, 0 );
return;
}while (0);
marshaller->submit( [=]() -> void { this->setPartyPollStatus("",false); } );
InterlockedExchange( &flag, 0 );
}
} // lambda
);
}
void XboxPlatform::removePartyPoller()
{
removeGenericDataModelJob(partyPoller, [=]() { marshaller->processEvents(); } );
}
void XboxPlatform::initVoiceChatPoller()
{
removeGenericDataModelJob(voiceChatPoller, [=]()->void{marshaller->processEvents();} );
std::string curPartyGuid;
XboxLiveContext^ xblcontext = xboxLiveContext;
voiceChatPoller = addGenericDataModelJob( "voiceChatPoller", DataModelJob::Read, game->getDataModel(), 1.0 / 10.0,
[=]()mutable -> void
{
RBXPROFILER_SCOPE("Xbox", "VoiceChatUpdate");
if (multiplayerManager && game)
{
RBX::Network::Players* players = game->getDataModel()->find<RBX::Network::Players>();
if (players)
{
RBX::Network::Player* localplayer = players->getLocalPlayer();
if (localplayer)
if (localplayer->getNeutral())
multiplayerManager->setTeamChannel(0);
else
{
size_t teamId = localplayer->getTeamColor().getClosestPaletteIndex();
RBXASSERT(teamId < 255);
multiplayerManager->setTeamChannel((unsigned char)teamId + 1); // +1 because 0 is for neutral
}
std::vector<RBX::Network::Player> playerVector;
PlatformService* ps = ServiceProvider::find<PlatformService>(game->getDataModel().get());
shared_ptr<const Instances> instances = players->getPlayers();
for(Instances::const_iterator iter = instances->begin(); iter != instances->end(); ++iter)
{
if(shared_ptr<RBX::Network::Player> player = Instance::fastSharedDynamicCast<RBX::Network::Player>(*iter))
{
RBX::VoiceChatBase::TalkingDelta talkingDelta;
float distance = 0;
if (localplayer->getSharedCharacter() && player->getSharedCharacter() && localplayer->getUserID() != player->getUserID())
distance = (localplayer->getSharedCharacter()->getLocation().translation - player->getSharedCharacter()->getLocation().translation).length();
multiplayerManager->updatePlayer(player->getUserID(), distance, &talkingDelta);
if (ps)
{
if (talkingDelta == VoiceChatBase::TalkingChange_Start)
dprintf("voice start \n");// ps->voiceChatUserTalkingStartSignal(player->getUserID());
else if (talkingDelta == VoiceChatBase::TalkingChange_End)
dprintf("voice end \n");//ps->voiceChatUserTalkingEndSignal(player->getUserID());
}
}
}
}
}
});
}
void XboxPlatform::removeVoiceChatPoller()
{
removeGenericDataModelJob(voiceChatPoller, [=]() { marshaller->processEvents(); } );
}
void XboxPlatform::setPartyPollStatus(const std::string& newGuid, bool leaderIn )
{
if( GetCurrentThreadId() != marshaller->getThreadId() )
{
RBXASSERT( !"This function must be called on marshaller thread." );
return;
}
if( partyStatus.pollPartyGuid != newGuid )
{
partyStatus.tried = false;
partyStatus.pollPartyGuid = newGuid;
}
partyStatus.leaderInGame = leaderIn;
//dprintf( "partyPollStatus: [%s], %d\n", newGuid.c_str(), (int)!!leaderIn );
}
void XboxPlatform::setPartyCreateStatus( const std::string& current )
{
if( GetCurrentThreadId() != marshaller->getThreadId() )
{
RBXASSERT( !"This function must be called on marshaller thread." );
return;
}
partyStatus.currentPartyGuid = current;
dprintf( "partyCreateStatus: [%s]\n", current.c_str() );
}
void XboxPlatform::setPartyTriedStatus()
{
if( GetCurrentThreadId() != marshaller->getThreadId() )
{
RBXASSERT( !"This function must be called on marshaller thread." );
return;
}
partyStatus.tried = true;
dprintf( "partyTriedStatus: [%s] - True\n", partyStatus.currentPartyGuid.c_str() );
}
void XboxPlatform::handlePartyJoin()
{
if( GetCurrentThreadId() != marshaller->getThreadId() )
{
RBXASSERT( !"This function must be called on marshaller thread." );
return;
}
DWORD ticks = GetTickCount();
if( ticks - partyStatus.lastCheckTime < 1000 )
return; // too early
partyStatus.lastCheckTime = ticks;
//
if( !partyStatus.tried // haven't tried it yet
&& partyStatus.leaderInGame // leader must be in-game
&& !partyStatus.pollPartyGuid.empty() // must have a new party guid
&& partyStatus.pollPartyGuid != partyStatus.currentPartyGuid // and it must be different from the current one
)
{
// join the game!
dprintf( "Party join initiated: cur: %s new %s\n", partyStatus.currentPartyGuid.c_str(), partyStatus.pollPartyGuid.c_str() );
partyStatus.currentPartyGuid = partyStatus.pollPartyGuid;
partyStatus.pollPartyGuid.clear();
boost::thread( [=]() { requestGameShutdown(false); startGame3( GameJoin_Party, 0 ); } ).detach();
}
}
int XboxPlatform::getPlatformPartyMembers(shared_ptr<Reflection::ValueArray> result)
{
std::vector<std::string> party;
int r = getParty( &party, true, userTranslator.get(), xboxLiveContext );
if (!party.empty())
{
for (auto partyMember : party)
{
result->push_back(partyMember);
}
}
return r;
}
int XboxPlatform::getInGamePlayers(shared_ptr<Reflection::ValueArray> result)
{
result->clear();
if(multiplayerManager)
{
multiplayerManager->getInGamePlayers(result);
return 0;
}
return 1;
}
// needed for teleports
std::string XboxPlatform::xBox_getGamerTag() const
{
if( !xboxLiveContext ) return "";
return ws2s( xboxLiveContext->User->DisplayInfo->Gamertag->Data() );
}