the game where you go into mines and start crafting! but for consoles (forked directly from smartcmd's github)
1#include "stdafx.h"
2
3#include "DQRNetworkManager.h"
4#include "PartyController.h"
5#include <collection.h>
6#include <ppltasks.h>
7#include <ws2tcpip.h>
8#include "..\Minecraft.World\StringHelpers.h"
9#include "base64.h"
10
11#ifdef _DURANGO
12#include "..\Minecraft.World\DurangoStats.h"
13#endif
14
15#include "ChatIntegrationLayer.h"
16
17using namespace Concurrency;
18using namespace Windows::Foundation::Collections;
19
20// Returns true if we are already processing a request to find game parties of friends
21bool DQRNetworkManager::FriendPartyManagerIsBusy()
22{
23 if( m_GetFriendPartyThread )
24 {
25 if( m_GetFriendPartyThread->isRunning() )
26 {
27 return true;
28 }
29 }
30 return false;
31}
32
33// Returns the total count of game parties that we found for our friends
34int DQRNetworkManager::FriendPartyManagerGetCount()
35{
36 return m_sessionResultCount;
37}
38
39// Initiate the (asynchronous) search for game parties of our friends
40bool DQRNetworkManager::FriendPartyManagerSearch()
41{
42 if( m_GetFriendPartyThread )
43 {
44 if( m_GetFriendPartyThread->isRunning() )
45 {
46 return false;
47 }
48 }
49
50 m_sessionResultCount = 0;
51 delete [] m_sessionSearchResults;
52 m_sessionSearchResults = NULL;
53
54 m_GetFriendPartyThread = new C4JThread(&_GetFriendsThreadProc,this,"GetFriendsThreadProc");
55 m_GetFriendPartyThread->Run();
56
57 return true;
58}
59
60// Get a particular search result for a game party that we have discovered. Index should be from 0 to the value returned by FriendPartyManagerGetCount.
61void DQRNetworkManager::FriendPartyManagerGetSessionInfo(int idx, SessionSearchResult *searchResult)
62{
63 assert( idx < m_sessionResultCount );
64 assert( ( m_GetFriendPartyThread == NULL ) || ( !m_GetFriendPartyThread->isRunning()) );
65
66 // Need to make sure that copied data has independently allocated m_extData, so both copies can be freed
67 *searchResult = m_sessionSearchResults[idx];
68 searchResult->m_extData = malloc(sizeof(GameSessionData));
69 memcpy(searchResult->m_extData, m_sessionSearchResults[idx].m_extData, sizeof(GameSessionData));
70}
71
72int DQRNetworkManager::_GetFriendsThreadProc(void* lpParameter)
73{
74 DQRNetworkManager *pDQR = (DQRNetworkManager *)lpParameter;
75 return pDQR->GetFriendsThreadProc();
76}
77
78// This is the main thread that is kicked off to find game sessions associated with our friends. We have to do this
79// by finding parties associated with our friends, and from the parties get the assocated game session.
80int DQRNetworkManager::GetFriendsThreadProc()
81{
82 LogComment(L"Starting GetFriendsThreadProc");
83 WXS::User^ primaryUser = ProfileManager.GetUser(0);
84
85 if( primaryUser == nullptr )
86 {
87 return -1;
88 }
89 MXS::XboxLiveContext^ primaryUserXboxLiveContext = ref new MXS::XboxLiveContext(primaryUser);
90 if( primaryUserXboxLiveContext == nullptr )
91 {
92 return -1;
93 }
94
95 MXSS::XboxSocialRelationshipResult^ socialRelationshipResult = nullptr;
96
97 // First get our friends list (people we follow who may or may not follow us back), note we're requesting all friends
98 auto getSocialRelationshipsAsync = primaryUserXboxLiveContext->SocialService->GetSocialRelationshipsAsync(MXSS::SocialRelationship::All, 0, 1100);
99 create_task(getSocialRelationshipsAsync).then([this,&socialRelationshipResult](task<MXSS::XboxSocialRelationshipResult^> t)
100 {
101 try
102 {
103 socialRelationshipResult = t.get();
104 }
105 catch (Platform::COMException^ ex)
106 {
107 LogCommentWithError( L"GetSocialRelationshipsAsync failed", ex->HResult );
108 }
109 })
110 .wait();
111 if( socialRelationshipResult == nullptr )
112 {
113 return -1;
114 }
115
116 IVector<Platform::String^>^ friendXUIDs = ref new Platform::Collections::Vector<Platform::String^>;
117
118 // Now construct a vector of these users, that follow us back - these are our "friends"
119 for( int i = 0; i < socialRelationshipResult->TotalCount; i++ )
120 {
121 MXSS::XboxSocialRelationship^ relationship = socialRelationshipResult->Items->GetAt(i);
122 if(relationship->IsFollowingCaller)
123 {
124 friendXUIDs->Append(relationship->XboxUserId);
125 }
126 }
127
128 // If we don't have any such friends, we're done
129 if( friendXUIDs->Size == 0 )
130 {
131 return 0;
132 }
133
134 // Now get party associations for these friends
135 auto getPartyAssociationsAsync = WXM::Party::GetUserPartyAssociationsAsync(primaryUser, friendXUIDs->GetView() );
136
137 IVectorView<WXM::UserPartyAssociation^>^ partyResults = nullptr;
138
139 create_task(getPartyAssociationsAsync).then([this,&partyResults](task<IVectorView<WXM::UserPartyAssociation^>^> t)
140 {
141 try
142 {
143 partyResults = t.get();
144 }
145 catch (Platform::COMException^ ex)
146 {
147 LogCommentWithError( L"getPartyAssociationsAsync failed", ex->HResult );
148 }
149 })
150 .wait();
151
152 if( partyResults == nullptr )
153 {
154 return -1;
155 }
156
157 if( partyResults->Size == 0 )
158 {
159 return 0;
160 }
161
162 // Filter these parties by whether we have permission to see them online
163 partyResults = FilterPartiesByPermission(primaryUserXboxLiveContext, partyResults);
164
165
166 // At this point, we have Party Ids for our friends. Now we need to get Party Views for each of these Ids.
167
168 LogComment("Parties found");
169
170 // Get party views for each of the user party associations that we have. These seem to be able to (individually) raise errors, so
171 // accumulate results into 2 matched vectors declared below so that we can ignore any broken UserPartyAssociations from now
172 vector<WXM::PartyView^> partyViewVector;
173 vector<WXM::UserPartyAssociation^> partyResultsVector;
174
175 vector<task<void>> taskVector;
176 for each(WXM::UserPartyAssociation^ remoteParty in partyResults)
177 {
178 auto asyncOp = WXM::Party::GetPartyViewByPartyIdAsync( primaryUser, remoteParty->PartyId );
179 task<WXM::PartyView^> asyncTask = create_task(asyncOp);
180
181 taskVector.push_back(asyncTask.then([this, &partyViewVector, &partyResultsVector, remoteParty] (task<WXM::PartyView^> t)
182 {
183 try
184 {
185 WXM::PartyView^ partyView = t.get();
186
187 if( partyView != nullptr )
188 {
189 app.DebugPrintf("Got party view\n");
190 EnterCriticalSection(&m_csPartyViewVector);
191 partyViewVector.push_back(partyView);
192 partyResultsVector.push_back(remoteParty);
193 LeaveCriticalSection(&m_csPartyViewVector);
194 }
195 }
196 catch ( Platform::COMException^ ex )
197 {
198 app.DebugPrintf("Getting party view error 0x%x\n",ex->HResult);
199 }
200 }));
201 }
202 for( auto it = taskVector.begin(); it != taskVector.end(); it++ )
203 {
204 it->wait();
205 }
206
207 if( partyViewVector.size() == 0 )
208 {
209 return 0;
210 }
211
212 // Filter the party view, and party results vector (partyResultsVector) this is matched to, to remove any that don't have game sessions - or game sessions that aren't this game
213 vector<WXM::PartyView^> partyViewVectorFiltered;
214 vector<WXM::UserPartyAssociation^> partyResultsFiltered;
215
216 for( int i = 0; i < partyViewVector.size(); i++ )
217 {
218 WXM::PartyView^ partyView = partyViewVector[i];
219
220 if( partyView->Joinability == WXM::SessionJoinability::JoinableByFriends )
221 {
222 if( partyView->GameSession )
223 {
224 if( partyView->GameSession->ServiceConfigurationId == SERVICE_CONFIG_ID )
225 {
226 partyViewVectorFiltered.push_back( partyView );
227 partyResultsFiltered.push_back( partyResultsVector[i] );
228 }
229 }
230 }
231 }
232
233 // We now have matched vectors:
234 //
235 // partyResultsFiltered
236 // partyViewVectorFiltered
237 //
238 // and, from the party views, we can now attempt to get game sessions
239
240 vector<MXSM::MultiplayerSession^> sessionVector;
241 vector<WXM::PartyView^> partyViewVectorValid;
242 vector<WXM::UserPartyAssociation^> partyResultsValid;
243
244 for( int i = 0; i < partyViewVectorFiltered.size(); i++ )
245 {
246 WXM::PartyView^ partyView = partyViewVectorFiltered[i];
247 Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference^ sessionRef = ConvertToMicrosoftXboxServicesMultiplayerSessionReference(partyView->GameSession);
248
249 LogComment(L"Party view vector " + sessionRef->SessionName + L" " + partyResultsFiltered[i]->QueriedXboxUserIds->GetAt(0));
250
251 MXSM::MultiplayerSession^ session = nullptr;
252 auto asyncOp = primaryUserXboxLiveContext->MultiplayerService->GetCurrentSessionAsync( sessionRef );
253 create_task(asyncOp).then([&session] (task<MXSM::MultiplayerSession^> t)
254 {
255 try
256 {
257 session = t.get();
258 }
259 catch (Platform::COMException^ ex)
260 {
261 }
262 })
263 .wait();
264 if( session )
265 {
266 sessionVector.push_back(session);
267 partyViewVectorValid.push_back(partyView);
268 partyResultsValid.push_back(partyResultsFiltered[i]);
269 }
270 }
271
272 if( sessionVector.size() == 0 )
273 {
274 return 0;
275 }
276
277 // We now have matched vectors:
278 //
279 // partyResultsValid
280 // partyViewVectorValid
281 // sessionVector
282
283 // The next stage is to resolve the display names for the XUIDs of all the players in each of the sessions. It is possible that
284 // a session won't have any XUIDs to resolve, which would make GetUserProfilesAsync unhappy, so we'll only be creating a task
285 // when there are members. Creating new matching arrays for party results and sessions, to match the results (we don't care about the party view anymore)
286
287 vector<task<IVectorView<MXSS::XboxUserProfile^>^>> nameResolveTaskVector;
288 vector<IVectorView<MXSS::XboxUserProfile^>^> nameResolveVector;
289 vector<MXSM::MultiplayerSession^> newSessionVector;
290 vector<WXM::UserPartyAssociation^> newPartyVector;
291
292 for( int j = 0; j < sessionVector.size(); j++ )
293 {
294 MXSM::MultiplayerSession^ session = sessionVector[j];
295 IVector<Platform::String^>^ memberXUIDs = ref new Platform::Collections::Vector<Platform::String^>;
296
297 Windows::Data::Json::JsonArray^ roomSyncArray = nullptr;
298 try
299 {
300 Windows::Data::Json::JsonObject^ customJson = Windows::Data::Json::JsonObject::Parse(session->SessionProperties->SessionCustomPropertiesJson);
301 Windows::Data::Json::JsonValue^ customValue = customJson->GetNamedValue(L"RoomSyncData");
302 roomSyncArray = customValue->GetArray();
303 LogComment("Attempting to parse RoomSyncData");
304 for( int i = 0; i < roomSyncArray->Size; i++ )
305 {
306 LogComment(roomSyncArray->GetAt(i)->GetString());
307 }
308 }
309 catch (Platform::COMException^ ex)
310 {
311 LogCommentWithError( L"Custom RoomSyncData Parse/GetNamedValue failed", ex->HResult );
312 continue;
313 }
314
315 if( roomSyncArray && ( roomSyncArray->Size > 0 ) )
316 {
317 // For each session, we want to order these XUIDs so the display name of the first one is what we will name the session by. Prioritise doing this by:
318 //
319 // (1) If the host player (indicated by having a small id of 0) is our friend, use that
320 // (2) Otherwise use anyone who is our friend
321
322 // Default to true
323 bool friendsOfFriends = true;
324
325 int hostIndexFound = -1;
326 int friendIndexFound = -1;
327
328 friendsOfFriends = IsSessionFriendsOfFriends(session);
329
330 for( int i = 0; i < roomSyncArray->Size; i++ )
331 {
332 Platform::String^ roomSyncXuid = roomSyncArray->GetAt(i)->GetString();
333
334 // Determine if this player is a friend
335 bool isFriend = false;
336 for each( Platform::String^ friendXUID in friendXUIDs )
337 {
338 if( friendXUID == roomSyncXuid )
339 {
340 isFriend = true;
341 break;
342 }
343 }
344
345 bool isHost = i == 0;
346
347 // Store that what we found at this index if it is a friend, or a friend who is a host
348 if( isFriend && ( friendsOfFriends || isHost ) )
349 {
350 friendIndexFound = i;
351 if( isHost ) // Host is always in slot 0
352 {
353 hostIndexFound = i;
354 }
355 }
356 }
357
358 // Prefer to use index of host who is our friend
359 int bestIndex = friendIndexFound;
360 if( hostIndexFound != -1 )
361 {
362 bestIndex = hostIndexFound;
363 }
364
365 // Only consider if we have at least found one friend in the list of players
366 if( bestIndex != -1 )
367 {
368 // Compile list of XUIDs to resolve with our specially chosen player as entry 0, then the rest
369 memberXUIDs->Append(roomSyncArray->GetAt(bestIndex)->GetString());
370 for( int i = 0; i < roomSyncArray->Size; i++ )
371 {
372 if( i != bestIndex )
373 {
374 memberXUIDs->Append(roomSyncArray->GetAt(i)->GetString());
375 }
376 }
377 nameResolveTaskVector.push_back( create_task( primaryUserXboxLiveContext->ProfileService->GetUserProfilesAsync( memberXUIDs->GetView() ) ) );
378 newSessionVector.push_back(session);
379 newPartyVector.push_back(partyResultsValid[j]);
380 }
381 }
382 }
383
384 try
385 {
386 auto joinTask = when_all(begin(nameResolveTaskVector), end(nameResolveTaskVector) ).then([this, &nameResolveVector](vector<IVectorView<MXSS::XboxUserProfile^>^> results)
387 {
388 nameResolveVector = results;
389 })
390 .wait();
391 }
392 catch(Platform::COMException^ ex)
393 {
394 return -1;
395 }
396
397 // We now have matched vectors:
398 //
399 // newPartyVector - contains the party Ids that we'll need should we wish to join
400 // nameResolveVector - contains vectors views of the names of the members of the session each of these parties is in
401 // newSessionVector - contains the session information itself associated with each of the parties
402
403 // Construct the final result vector
404 m_sessionResultCount = newSessionVector.size();
405 m_sessionSearchResults = new SessionSearchResult[m_sessionResultCount];
406 for( int i = 0; i < m_sessionResultCount; i++ )
407 {
408 m_sessionSearchResults[i].m_partyId = newPartyVector[i]->PartyId->Data();
409 m_sessionSearchResults[i].m_sessionName = newSessionVector[i]->SessionReference->SessionName->Data();
410 for( int j = 0; j < nameResolveVector[i]->Size; j++ )
411 {
412 m_sessionSearchResults[i].m_playerNames[j] = nameResolveVector[i]->GetAt(j)->GameDisplayName->Data();
413 m_sessionSearchResults[i].m_playerXuids[j] = PlayerUID(nameResolveVector[i]->GetAt(j)->XboxUserId->Data());
414 }
415 m_sessionSearchResults[i].m_playerCount = nameResolveVector[i]->Size;
416 m_sessionSearchResults[i].m_usedSlotCount = newSessionVector[i]->Members->Size;
417 if( m_sessionSearchResults[i].m_usedSlotCount > MAX_ONLINE_PLAYER_COUNT )
418 {
419 // Don't think this could ever happen, but no harm in checking
420 m_sessionSearchResults[i].m_usedSlotCount = MAX_ONLINE_PLAYER_COUNT;
421 }
422 for( int j = 0; j < m_sessionSearchResults[i].m_usedSlotCount; j++ )
423 {
424 m_sessionSearchResults[i].m_sessionXuids[j] = wstring( newSessionVector[i]->Members->GetAt(j)->XboxUserId->Data() );
425 }
426
427 m_sessionSearchResults[i].m_extData = malloc( sizeof(GameSessionData) );
428 memset( m_sessionSearchResults[i].m_extData, 0, sizeof(GameSessionData) );
429
430 GetGameSessionData(newSessionVector[i], m_sessionSearchResults[i].m_extData);
431 }
432
433 return 0;
434}
435
436// Filters list of parties based on online presence permission (whether the friend is set to invisible or not)
437IVectorView<WXM::UserPartyAssociation^>^ DQRNetworkManager::FilterPartiesByPermission(MXS::XboxLiveContext ^context, IVectorView<WXM::UserPartyAssociation^>^ partyResults)
438{
439 Platform::Collections::Vector<WXM::UserPartyAssociation^>^ filteredPartyResults = ref new Platform::Collections::Vector<WXM::UserPartyAssociation^>();
440
441 // List of permissions we want
442 auto permissionIds = ref new Platform::Collections::Vector<Platform::String^>(1, ref new Platform::String(L"ViewTargetPresence"));
443
444 // List of target users
445 auto targetXboxUserIds = ref new Platform::Collections::Vector<Platform::String^>();
446 for (int i = 0; i < partyResults->Size; i++)
447 {
448 assert(partyResults->GetAt(i)->QueriedXboxUserIds->Size > 0);
449 targetXboxUserIds->Append( partyResults->GetAt(i)->QueriedXboxUserIds->GetAt(0) );
450 }
451
452 // Check
453 auto checkPermissionsAsync = context->PrivacyService->CheckMultiplePermissionsWithMultipleTargetUsersAsync(permissionIds->GetView(), targetXboxUserIds->GetView());
454 create_task(checkPermissionsAsync).then([&partyResults, &filteredPartyResults](task<IVectorView<MXS::Privacy::MultiplePermissionsCheckResult^>^> t)
455 {
456 try
457 {
458 auto results = t.get();
459
460 // For each party, check to see if we have permission for the user
461 for (int i = 0; i < partyResults->Size; i++)
462 {
463 // For each permissions result
464 for (int j = 0; j < results->Size; j++)
465 {
466 auto result = results->GetAt(j);
467
468 // If allowed to see this user AND it's the same user, add the party to the just
469 if ((result->Items->GetAt(0)->IsAllowed) && (partyResults->GetAt(i)->QueriedXboxUserIds->GetAt(0) == result->XboxUserId))
470 {
471 filteredPartyResults->Append(partyResults->GetAt(i));
472 break;
473 }
474 }
475 }
476 }
477 catch (Platform::COMException^ ex)
478 {
479 LogCommentWithError( L"CheckMultiplePermissionsWithMultipleTargetUsersAsync failed", ex->HResult );
480 }
481 })
482 .wait();
483
484 app.DebugPrintf("DQRNetworkManager::FilterPartiesByPermission: Removed %i parties because of online presence permissions\n", partyResults->Size - filteredPartyResults->Size);
485
486 return filteredPartyResults->GetView();
487}
488
489// Get all friends (list of XUIDs) syncronously from the service (slow, may take 300ms+), returns empty list if something goes wrong
490Platform::Collections::Vector<Platform::String^>^ DQRNetworkManager::GetFriends()
491{
492 auto friends = ref new Platform::Collections::Vector<Platform::String^>;
493
494 auto primaryUser = ProfileManager.GetUser(0);
495 if (primaryUser == nullptr)
496 {
497 // Return empty
498 return friends;
499 }
500
501 auto xboxLiveContext = ref new MXS::XboxLiveContext(primaryUser);
502
503 // Request ALL friends because there's no other way to check friendships without using the REST API
504 auto getSocialRelationshipsAsync = xboxLiveContext->SocialService->GetSocialRelationshipsAsync(MXSS::SocialRelationship::All, 0, 1100);
505 MXSS::XboxSocialRelationshipResult^ socialRelationshipResult = nullptr;
506
507 // First get our friends list (people we follow who may or may not follow us back)
508 Concurrency::create_task(getSocialRelationshipsAsync).then([&socialRelationshipResult](Concurrency::task<MXSS::XboxSocialRelationshipResult^> t)
509 {
510 try
511 {
512 socialRelationshipResult = t.get();
513 }
514 catch (Platform::COMException^ ex)
515 {
516 app.DebugPrintf("DQRNetworkManager::GetFriends: GetSocialRelationshipsAsync failed ()\n", ex->HResult);
517 }
518 })
519 .wait();
520
521 if (socialRelationshipResult == nullptr)
522 {
523 // Return empty
524 return friends;
525 }
526
527 app.DebugPrintf("DQRNetworkManager::GetFriends: Retrieved %i relationships\n", socialRelationshipResult->TotalCount);
528
529 // Now construct a vector of these users, that follow us back - these are our "friends"
530 for( int i = 0; i < socialRelationshipResult->TotalCount; i++ )
531 {
532 MXSS::XboxSocialRelationship^ relationship = socialRelationshipResult->Items->GetAt(i);
533 if(relationship->IsFollowingCaller)
534 {
535 app.DebugPrintf("DQRNetworkManager::GetFriends: Found friend \"%ls\"\n", relationship->XboxUserId->Data());
536 friends->Append(relationship->XboxUserId);
537 }
538 }
539
540 app.DebugPrintf("DQRNetworkManager::GetFriends: Found %i 2-way friendships\n", friends->Size);
541
542 return friends;
543}
544
545// If data for game settings exists returns FriendsOfFriends value, otherwise returns true
546bool DQRNetworkManager::IsSessionFriendsOfFriends(MXSM::MultiplayerSession^ session)
547{
548 // Default to true, don't want to incorrectly prevent joining
549 bool friendsOfFriends = true;
550
551 // We retrieve the game session data later too, shouldn't really duplicate this
552 void *gameSessionData = malloc( sizeof(GameSessionData));
553 memset(gameSessionData, 0, sizeof(GameSessionData));
554
555 bool result = GetGameSessionData(session, gameSessionData);
556
557 if (result)
558 {
559 friendsOfFriends = app.GetGameHostOption(((GameSessionData *)gameSessionData)->m_uiGameHostSettings, eGameHostOption_FriendsOfFriends);
560 }
561
562 free(gameSessionData);
563
564 return friendsOfFriends;
565}
566
567// Parses custom json data from session and populates game session data param, return true if parse succeeded
568bool DQRNetworkManager::GetGameSessionData(MXSM::MultiplayerSession^ session, void *gameSessionData)
569{
570 Platform::String ^gameSessionDataJson = session->SessionProperties->SessionCustomPropertiesJson;
571 if( gameSessionDataJson )
572 {
573 try
574 {
575 Windows::Data::Json::JsonObject^ customParam = Windows::Data::Json::JsonObject::Parse(gameSessionDataJson);
576 Windows::Data::Json::JsonValue^ customValue = customParam->GetNamedValue(L"GameSessionData");
577 Platform::String ^customValueString = customValue->GetString();
578 if( customValueString )
579 {
580 base64_decode( customValueString, (unsigned char *)gameSessionData, sizeof(GameSessionData) );
581 return true;
582 }
583 }
584 catch (Platform::COMException^ ex)
585 {
586 LogCommentWithError( L"Custom GameSessionData parameter Parse/GetNamedValue failed", ex->HResult );
587 }
588 }
589
590 return false;
591}