first commit

This commit is contained in:
murdle
2026-03-01 02:38:58 +02:00
commit 19250b9db4
19111 changed files with 4358159 additions and 0 deletions

View File

@@ -0,0 +1,805 @@
//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//// PARTICULAR PURPOSE.
////
//// Copyright (c) Microsoft Corporation. All rights reserved
#include "stdafx.h"
#include "ChatIntegrationLayer.h"
#include "DQRNetworkManager.h"
#include <robuffer.h>
using namespace Windows::Foundation;
using namespace Windows::Xbox::System;
// To integrate the Chat DLL in your game, you can use this ChatIntegrationLayer class with modifications,
// or create your own design your own class using the code in this file a guide.
std::shared_ptr<ChatIntegrationLayer> GetChatIntegrationLayer()
{
static std::shared_ptr<ChatIntegrationLayer> chatIntegrationLayerInstance;
if (chatIntegrationLayerInstance == nullptr)
{
chatIntegrationLayerInstance.reset( new ChatIntegrationLayer() );
}
return chatIntegrationLayerInstance;
}
ChatIntegrationLayer::ChatIntegrationLayer()
{
ZeroMemory( m_chatVoicePacketsStatistic, sizeof(m_chatVoicePacketsStatistic) );
}
void ChatIntegrationLayer::InitializeChatManager(
__in bool combineCaptureBuffersIntoSinglePacket,
__in bool useKinectAsCaptureSource,
__in bool applySoundEffectsToCapturedAudio,
__in bool applySoundEffectsToChatRenderedAudio,
DQRNetworkManager *pDQRNet
)
{
m_pDQRNet = pDQRNet;
{
Concurrency::critical_section::scoped_lock lock(m_chatPacketStatsLock);
ZeroMemory( m_chatVoicePacketsStatistic, sizeof(m_chatVoicePacketsStatistic) );
}
m_chatManager = ref new Microsoft::Xbox::GameChat::ChatManager();
m_chatManager->ChatSettings->DiagnosticsTraceLevel = Microsoft::Xbox::GameChat::GameChatDiagnosticsTraceLevel::Verbose;
// Optionally, change the default settings below as desired by commenting out and editing any of the following lines
// Otherwise these defaults are used.
//
// m_chatManager = ref new Microsoft::Xbox::GameChat::ChatManager( ChatSessionPeriod::ChatPeriodOf40Milliseconds );
// m_chatManager->ChatSettings->AudioThreadPeriodInMilliseconds = 40;
// m_chatManager->ChatSettings->AudioThreadAffinityMask = XAUDIO2_DEFAULT_PROCESSOR; // <- this means is core 5, same as the default XAudio2 core
// m_chatManager->ChatSettings->AudioThreadPriority = THREAD_PRIORITY_TIME_CRITICAL;
// m_chatManager->ChatSettings->AudioEncodingQuality = Windows::Xbox::Chat::EncodingQuality::Normal;
// m_chatManager->ChatSettings->DiagnosticsTraceLevel = Microsoft::Xbox::GameChat::GameChatDiagnosticsTraceLevel::Verbose;
m_chatManager->ChatSettings->CombineCaptureBuffersIntoSinglePacket = combineCaptureBuffersIntoSinglePacket; // if unset, it defaults to TRUE
m_chatManager->ChatSettings->UseKinectAsCaptureSource = useKinectAsCaptureSource; // if unset, it defaults to FALSE
m_chatManager->ChatSettings->PreEncodeCallbackEnabled = applySoundEffectsToCapturedAudio; // if unset, it defaults to FALSE
m_chatManager->ChatSettings->PostDecodeCallbackEnabled = applySoundEffectsToChatRenderedAudio; // if unset, it defaults to FALSE
InitializeCriticalSection(&m_csAddedUsers);
std::weak_ptr<ChatIntegrationLayer> weakPtrToThis = shared_from_this();
#ifdef PROFILE
m_chatManager->ChatSettings->PerformanceCountersEnabled = true;
#endif
// Upon enter constrained mode, mute everyone.
// Upon leaving constrained mode, unmute everyone who was previously muted.
m_tokenResourceAvailabilityChanged = Windows::ApplicationModel::Core::CoreApplication::ResourceAvailabilityChanged +=
ref new EventHandler< Platform::Object^ >( [weakPtrToThis] (Platform::Object^, Platform::Object^ )
{
// Using a std::weak_ptr instead of 'this' to avoid dangling pointer if caller class is released.
// Simply unregistering the callback in the destructor isn't enough to prevent a dangling pointer
std::shared_ptr<ChatIntegrationLayer> sharedPtrToThis(weakPtrToThis.lock());
if( sharedPtrToThis != nullptr )
{
if (Windows::ApplicationModel::Core::CoreApplication::ResourceAvailability == Windows::ApplicationModel::Core::ResourceAvailability::Constrained)
{
if( sharedPtrToThis->m_chatManager != nullptr )
{
sharedPtrToThis->m_chatManager->MuteAllUsersFromAllChannels();
}
}
else if(Windows::ApplicationModel::Core::CoreApplication::ResourceAvailability == Windows::ApplicationModel::Core::ResourceAvailability::Full)
{
if( sharedPtrToThis->m_chatManager != nullptr )
{
sharedPtrToThis->m_chatManager->UnmuteAllUsersFromAllChannels();
// The title should remember who was muted so when the Resume even occurs
// to avoid unmuting users who has been previously muted. Simply re-mute them here
}
}
}
});
m_tokenOnDebugMessage = m_chatManager->OnDebugMessage +=
ref new Windows::Foundation::EventHandler<Microsoft::Xbox::GameChat::DebugMessageEventArgs^>(
[weakPtrToThis] ( Platform::Object^, Microsoft::Xbox::GameChat::DebugMessageEventArgs^ args )
{
// Using a std::weak_ptr instead of 'this' to avoid dangling pointer if caller class is released.
// Simply unregistering the callback in the destructor isn't enough to prevent a dangling pointer
std::shared_ptr<ChatIntegrationLayer> sharedPtrToThis(weakPtrToThis.lock());
if( sharedPtrToThis != nullptr )
{
sharedPtrToThis->OnDebugMessageReceived(args);
}
});
m_tokenOnOutgoingChatPacketReady = m_chatManager->OnOutgoingChatPacketReady +=
ref new Windows::Foundation::EventHandler<Microsoft::Xbox::GameChat::ChatPacketEventArgs^>(
[weakPtrToThis] ( Platform::Object^, Microsoft::Xbox::GameChat::ChatPacketEventArgs^ args )
{
// Using a std::weak_ptr instead of 'this' to avoid dangling pointer if caller class is released.
// Simply unregistering the callback in the destructor isn't enough to prevent a dangling pointer
std::shared_ptr<ChatIntegrationLayer> sharedPtrToThis(weakPtrToThis.lock());
if( sharedPtrToThis != nullptr )
{
sharedPtrToThis->OnOutgoingChatPacketReady(args);
}
});
m_tokenOnCompareUniqueConsoleIdentifiers = m_chatManager->OnCompareUniqueConsoleIdentifiers +=
ref new Microsoft::Xbox::GameChat::CompareUniqueConsoleIdentifiersHandler(
[weakPtrToThis] ( Platform::Object^ obj1, Platform::Object^ obj2 )
{
// Using a std::weak_ptr instead of 'this' to avoid dangling pointer if caller class is released.
// Simply unregistering the callback in the destructor isn't enough to prevent a dangling pointer
std::shared_ptr<ChatIntegrationLayer> sharedPtrToThis(weakPtrToThis.lock());
if( sharedPtrToThis != nullptr )
{
return sharedPtrToThis->CompareUniqueConsoleIdentifiers(obj1, obj2);
}
else
{
return false;
}
});
m_tokenUserAudioDeviceAdded = WXS::User::AudioDeviceAdded +=
ref new Windows::Foundation::EventHandler<WXS::AudioDeviceAddedEventArgs^>(
[weakPtrToThis] ( Platform::Object^, WXS::AudioDeviceAddedEventArgs^ value )
{
std::shared_ptr<ChatIntegrationLayer> sharedPtrToThis(weakPtrToThis.lock());
if( sharedPtrToThis != nullptr )
{
sharedPtrToThis->EvaluateDevicesForUser(value->User);
}
});
m_tokenUserAudioDeviceRemoved = WXS::User::AudioDeviceRemoved +=
ref new Windows::Foundation::EventHandler<WXS::AudioDeviceRemovedEventArgs^>(
[weakPtrToThis] ( Platform::Object^, WXS::AudioDeviceRemovedEventArgs^ value )
{
std::shared_ptr<ChatIntegrationLayer> sharedPtrToThis(weakPtrToThis.lock());
if( sharedPtrToThis != nullptr )
{
sharedPtrToThis->EvaluateDevicesForUser(value->User);
}
});
m_tokenUserAudioDeviceChanged = WXS::User::AudioDeviceChanged +=
ref new Windows::Foundation::EventHandler<WXS::AudioDeviceChangedEventArgs^>(
[weakPtrToThis] ( Platform::Object^, WXS::AudioDeviceChangedEventArgs^ value )
{
std::shared_ptr<ChatIntegrationLayer> sharedPtrToThis(weakPtrToThis.lock());
if( sharedPtrToThis != nullptr )
{
sharedPtrToThis->EvaluateDevicesForUser(value->User);
}
});
//m_tokenSuspending = Windows::ApplicationModel::Core::CoreApplication::Suspending +=
// ref new EventHandler< Windows::ApplicationModel::SuspendingEventArgs^ >( [weakPtrToThis] (Platform::Object^, Windows::ApplicationModel::SuspendingEventArgs^ args)
//{
// // Upon Suspending, nothing needs to be done
//});
//m_tokenResuming = Windows::ApplicationModel::Core::CoreApplication::Resuming +=
// ref new EventHandler< Platform::Object^ >( [weakPtrToThis] (Platform::Object^, Windows::ApplicationModel::SuspendingEventArgs^ args)
//{
// // Upon Resuming, re-initialize the network, and reinitialize the chat session
//});
}
void ChatIntegrationLayer::Shutdown()
{
if( m_chatManager != nullptr )
{
m_chatManager->OnDebugMessage -= m_tokenOnDebugMessage;
m_chatManager->OnOutgoingChatPacketReady -= m_tokenOnOutgoingChatPacketReady;
m_chatManager->OnCompareUniqueConsoleIdentifiers -= m_tokenOnCompareUniqueConsoleIdentifiers;
Windows::ApplicationModel::Core::CoreApplication::ResourceAvailabilityChanged -= m_tokenResourceAvailabilityChanged;
if( m_chatManager->ChatSettings->PreEncodeCallbackEnabled )
{
m_chatManager->OnPreEncodeAudioBuffer -= m_tokenOnPreEncodeAudioBuffer;
}
if( m_chatManager->ChatSettings->PostDecodeCallbackEnabled )
{
m_chatManager->OnPostDecodeAudioBuffer -= m_tokenOnPostDecodeAudioBuffer;
}
WXS::User::AudioDeviceAdded -= m_tokenUserAudioDeviceAdded;
WXS::User::AudioDeviceRemoved -= m_tokenUserAudioDeviceRemoved;
WXS::User::AudioDeviceChanged -= m_tokenUserAudioDeviceChanged;
DeleteCriticalSection(&m_csAddedUsers);
m_chatManager = nullptr;
}
}
void ChatIntegrationLayer::OnDebugMessageReceived(
__in Microsoft::Xbox::GameChat::DebugMessageEventArgs^ args
)
{
// To integrate the Chat DLL in your game,
// change this to false and remove the LogComment calls,
// or integrate with your game's own UI/debug message logging system
bool outputToUI = false;
if( outputToUI )
{
if (args->ErrorCode == S_OK )
{
m_pDQRNet->LogComment(L"GameChat: " + args->Message);
}
else
{
m_pDQRNet->LogCommentWithError(L"GameChat: " + args->Message, args->ErrorCode);
}
}
else
{
// The string appear in the Visual Studio Output window
#ifndef _CONTENT_PACKAGE
OutputDebugString( args->Message->Data() );
#endif
}
}
void ChatIntegrationLayer::GameUI_RecordPacketStatistic(
__in Microsoft::Xbox::GameChat::ChatMessageType messageType,
__in ChatPacketType chatPacketType
)
{
uint32 messageTypeInt = static_cast<uint32>(messageType);
if( messageType > Microsoft::Xbox::GameChat::ChatMessageType::InvalidMessage )
{
return;
}
{
Concurrency::critical_section::scoped_lock lock(m_chatPacketStatsLock);
m_chatVoicePacketsStatistic[static_cast<int>(chatPacketType)][messageTypeInt]++;
}
}
int ChatIntegrationLayer::GameUI_GetPacketStatistic(
__in Microsoft::Xbox::GameChat::ChatMessageType messageType,
__in ChatPacketType chatPacketType
)
{
uint32 messageTypeInt = static_cast<uint32>(messageType);
if( messageType > Microsoft::Xbox::GameChat::ChatMessageType::InvalidMessage )
{
return 0;
}
{
Concurrency::critical_section::scoped_lock lock(m_chatPacketStatsLock);
return m_chatVoicePacketsStatistic[static_cast<int>(chatPacketType)][messageTypeInt];
}
}
void ChatIntegrationLayer::OnOutgoingChatPacketReady(
__in Microsoft::Xbox::GameChat::ChatPacketEventArgs^ args
)
{
byte *bytes;
int byteCount;
GetBufferBytes(args->PacketBuffer, &bytes);
byteCount = args->PacketBuffer->Length;
unsigned int address = 0;
if( !args->SendPacketToAllConnectedConsoles )
{
address = safe_cast<unsigned int>(args->UniqueTargetConsoleIdentifier);
}
m_pDQRNet->SendBytesChat(address, bytes, byteCount, args->SendReliable, args->SendInOrder, args->SendPacketToAllConnectedConsoles);
GameUI_RecordPacketStatistic( args->ChatMessageType, ChatPacketType::OutgoingPacket );
}
void ChatIntegrationLayer::OnIncomingChatMessage(
unsigned int sessionAddress,
Platform::Array<byte>^ message
)
{
// To integrate the Chat DLL in your game, change the following code to use your game's network layer.
// Ignore the OnChatMessageReceived event as that is specific to this sample's simple network layer.
// Instead your title should upon receiving a packet, extract the chat message from it, and then call m_chatManager->ProcessIncomingChatMessage as shown below
// You will need to isolate chat messages to be unique from the rest of you game's other message types.
// uniqueRemoteConsoleIdentifier is a Platform::Object^ and can be cast or unboxed to most types.
// What exactly you use doesn't matter, but optimally it would be something that uniquely identifies a console on in the session.
// A Windows::Xbox::Networking::SecureDeviceAssociation^ is perfect to use if you have access to it.
// This is how you would convert from byte array to a IBuffer^
//
// Windows::Storage::Streams::IBuffer^ destBuffer = ref new Windows::Storage::Streams::Buffer( sourceByteBufferSize );
// byte* destBufferBytes = nullptr;
// GetBufferBytes( destBuffer, &destBufferBytes );
// errno_t err = memcpy_s( destBufferBytes, destBuffer->Capacity, sourceByteBuffer, sourceByteBufferSize );
// THROW_HR_IF(err != 0, E_FAIL);
// destBuffer->Length = sourceByteBufferSize;
// This is how you would convert from an int to a Platform::Object^
// Platform::Object obj = IntToPlatformObject(5);
Windows::Storage::Streams::IBuffer^ chatMessage = ArrayToBuffer(message);
Platform::Object^ uniqueRemoteConsoleIdentifier = (Platform::Object^)sessionAddress;
if( m_chatManager != nullptr )
{
Microsoft::Xbox::GameChat::ChatMessageType chatMessageType = m_chatManager->ProcessIncomingChatMessage(chatMessage, uniqueRemoteConsoleIdentifier);
GameUI_RecordPacketStatistic( chatMessageType, ChatPacketType::IncomingPacket );
}
}
// Only add people who intend to play.
void ChatIntegrationLayer::AddAllLocallySignedInUsersToChatClient(
__in uint8 channelIndex,
__in Windows::Foundation::Collections::IVectorView<Windows::Xbox::System::User^>^ locallySignedInUsers
)
{
// To integrate the Chat DLL in your game,
// add all locally signed in users to the chat client
for each( Windows::Xbox::System::User^ user in locallySignedInUsers )
{
if( user != nullptr )
{
// LogComment(L"Adding Local User to Chat Client");
AddLocalUserToChatChannel( channelIndex, user );
}
}
}
ChatIntegrationLayer::AddedUser::AddedUser(Windows::Xbox::System::IUser^ user, bool canCaptureAudio)
{
m_user = user;
m_canCaptureAudio = canCaptureAudio;
}
void ChatIntegrationLayer::AddLocalUser( __in Windows::Xbox::System::IUser^ user )
{
// Check we haven't added already
for( int i = 0; i < m_addedUsers.size(); i++ )
{
if( m_addedUsers[i]->m_user->XboxUserId == user->XboxUserId )
{
return;
}
}
bool kinectAvailable = false;
Windows::Kinect::KinectSensor^ sensor = Windows::Kinect::KinectSensor::GetDefault();
if( sensor )
{
sensor->Open();
if( sensor->IsAvailable )
{
kinectAvailable = true;
m_pDQRNet->LogComment(L"Evaluated that kinect is available\n");
}
sensor->Close();
}
EnterCriticalSection(&m_csAddedUsers);
// First establish whether we have an appropriate audio device at this time
bool canCaptureAudio = false;
for each( WXS::IAudioDeviceInfo^ audioDevice in user->AudioDevices )
{
m_pDQRNet->LogComment(L"Evaluating device " + audioDevice->DeviceCategory.ToString() + L" " +
audioDevice->DeviceType.ToString() + L" " +
audioDevice->Id + L" " +
audioDevice->Sharing.ToString() + L" " +
audioDevice->IsMicrophoneMuted.ToString() + L"\n");
// Consider shared devices only if kinect is actually available - every machine seems to claim a shared device whether kinect is attached or not
if( ( audioDevice->DeviceType == WXS::AudioDeviceType::Capture ) && ( kinectAvailable || ( audioDevice->Sharing != WXS::AudioDeviceSharing::Shared) ) )
{
canCaptureAudio = true;
}
}
// If we can capture audio initially, then register with the chat session. Otherwise we'll reevaluate this situation when audio devices change
if( canCaptureAudio )
{
AddLocalUserToChatChannel( 0 , user );
}
// Add to vector of users that we are tracking in the chat system
m_addedUsers.push_back(new AddedUser(user, canCaptureAudio));
LeaveCriticalSection(&m_csAddedUsers);
}
// Remove from our list of tracked users, if the user is already there
void ChatIntegrationLayer::RemoveLocalUser( __in Windows::Xbox::System::IUser^ user )
{
EnterCriticalSection(&m_csAddedUsers);
for( auto it = m_addedUsers.begin(); it != m_addedUsers.end(); it++ )
{
if( (*it)->m_user->XboxUserId == user->XboxUserId )
{
delete (*it);
m_addedUsers.erase(it);
LeaveCriticalSection(&m_csAddedUsers);
return;
}
}
LeaveCriticalSection(&m_csAddedUsers);
}
// This is called when the audio devices for a user change in any way, and establishes whether we can now capture audio. Any change in this status from before will cause the user to be added/removed from the chat session.
void ChatIntegrationLayer::EvaluateDevicesForUser(__in Windows::Xbox::System::IUser^ user )
{
bool kinectAvailable = false;
Windows::Kinect::KinectSensor^ sensor = Windows::Kinect::KinectSensor::GetDefault();
if( sensor )
{
sensor->Open();
if( sensor->IsAvailable )
{
kinectAvailable = true;
m_pDQRNet->LogComment(L"Evaluated that kinect is available\n");
}
sensor->Close();
}
EnterCriticalSection(&m_csAddedUsers);
for( int i = 0; i < m_addedUsers.size(); i++ )
{
AddedUser *addedUser = m_addedUsers[i];
if( addedUser->m_user->XboxUserId == user->XboxUserId )
{
bool canCaptureAudio = false;
for each( WXS::IAudioDeviceInfo^ audioDevice in addedUser->m_user->AudioDevices )
{
// Consider shared devices only if kinect is actually available - every machine seems to claim a shared device whether kinect is attached or not
if( ( audioDevice->DeviceType == WXS::AudioDeviceType::Capture ) && ( kinectAvailable || ( audioDevice->Sharing != WXS::AudioDeviceSharing::Shared) ) )
{
canCaptureAudio = true;
break;
}
}
if( canCaptureAudio != addedUser->m_canCaptureAudio )
{
if( canCaptureAudio )
{
AddLocalUserToChatChannel(0, addedUser->m_user );
}
else
{
RemoveUserFromChatChannel(0, addedUser->m_user );
}
addedUser->m_canCaptureAudio = canCaptureAudio;
LeaveCriticalSection(&m_csAddedUsers);
return;
}
}
}
LeaveCriticalSection(&m_csAddedUsers);
}
void ChatIntegrationLayer::AddLocalUserToChatChannel(
__in uint8 channelIndex,
__in Windows::Xbox::System::IUser^ user
)
{
// Adds a local user to a specific channel.
// This is helper function waits for the task to cm_chatManageromplete so shouldn't be called from the UI thread
// Remove the .wait() and return the result of concurrency::create_task() if you want to call it from the UI thread
// and chain PPL tasks together
m_pDQRNet->LogComment( L">>>>>>>>>>>>> AddLocalUserToChatChannel" );
if( m_chatManager != nullptr )
{
auto asyncOp = m_chatManager->AddLocalUserToChatChannelAsync( channelIndex, user );
concurrency::create_task( asyncOp )
.then( [this] ( concurrency::task<void> t )
{
// Error handling
try
{
t.get();
}
catch ( Platform::Exception^ ex )
{
m_pDQRNet->LogCommentWithError( L"AddLocalUserToChatChannelAsync failed", ex->HResult );
}
})
.wait();
}
}
void ChatIntegrationLayer::RemoveRemoteConsole(
unsigned int address
)
{
// uniqueConsoleIdentifier is a Platform::Object^ and can be cast or unboxed to most types.
// What exactly you use doesn't matter, but optimally it would be something that uniquely identifies a console on in the session.
// A Windows::Xbox::Networking::SecureDeviceAssociation^ is perfect to use if you have access to it.
// This is how you would convert from an int to a Platform::Object^
// Platform::Object obj = IntToPlatformObject(5);
// This is helper function waits for the task to complete so shouldn't be called from the UI thread
// Remove the .wait() and return the result of concurrency::create_task() if you want to call it from the UI thread
// and chain PPL tasks together
Platform::Object^ uniqueRemoteConsoleIdentifier = (Platform::Object^)address;
if( m_chatManager != nullptr )
{
auto asyncOp = m_chatManager->RemoveRemoteConsoleAsync( uniqueRemoteConsoleIdentifier );
concurrency::create_task( asyncOp ).then( [this] ( concurrency::task<void> t )
{
// Error handling
try
{
t.get();
}
catch ( Platform::Exception^ ex )
{
m_pDQRNet->LogCommentWithError( L"RemoveRemoteConsoleAsync failed", ex->HResult );
}
})
.wait();
}
}
void ChatIntegrationLayer::RemoveUserFromChatChannel(
__in uint8 channelIndex,
__in Windows::Xbox::System::IUser^ user
)
{
if( m_chatManager != nullptr )
{
// This is helper function waits for the task to complete so shouldn't be called from the UI thread
// Remove the .wait() and return the result of concurrency::create_task() if you want to call it from the UI thread
// and chain PPL tasks together
auto asyncOp = m_chatManager->RemoveLocalUserFromChatChannelAsync( channelIndex, user );
concurrency::create_task( asyncOp ).then( [this] ( concurrency::task<void> t )
{
// Error handling
try
{
t.get();
}
catch ( Platform::Exception^ ex )
{
m_pDQRNet->LogCommentWithError( L"RemoveLocalUserFromChatChannelAsync failed", ex->HResult );
}
})
.wait();
}
}
void ChatIntegrationLayer::OnNewSessionAddressAdded(
__in unsigned int address
)
{
m_pDQRNet->LogCommentFormat( L">>>>>>>>>>>>> OnNewSessionAddressAdded (%d)",address );
Platform::Object^ uniqueConsoleIdentifier = (Platform::Object^)address;
/// Call this when a new console connects.
/// This adds this console to the chat layer
if( m_chatManager != nullptr )
{
m_chatManager->HandleNewRemoteConsole(uniqueConsoleIdentifier );
}
}
Windows::Foundation::Collections::IVectorView<Microsoft::Xbox::GameChat::ChatUser^>^ ChatIntegrationLayer::GetChatUsers()
{
if( m_chatManager != nullptr )
{
return m_chatManager->GetChatUsers();
}
return nullptr;
}
bool ChatIntegrationLayer::HasMicFocus()
{
if( m_chatManager != nullptr )
{
return m_chatManager->HasMicFocus;
}
return false;
}
Platform::Object^ ChatIntegrationLayer::IntToPlatformObject(
__in int val
)
{
return (Platform::Object^)val;
// You can also do the same using a PropertyValue.
//return Windows::Foundation::PropertyValue::CreateInt32(val);
}
int ChatIntegrationLayer::PlatformObjectToInt(
__in Platform::Object^ uniqueRemoteConsoleIdentifier
)
{
return safe_cast<int>( uniqueRemoteConsoleIdentifier );
// You can also do the same using a PropertyValue.
//return safe_cast<Windows::Foundation::IPropertyValue^>(uniqueRemoteConsoleIdentifier)->GetInt32();
}
void ChatIntegrationLayer::HandleChatChannelChanged(
__in uint8 oldChatChannelIndex,
__in uint8 newChatChannelIndex,
__in Microsoft::Xbox::GameChat::ChatUser^ chatUser
)
{
// We remember if the local user was currently muted from all channels. And when we switch channels,
// we ensure that the state persists. For remote users, title should implement this themselves
// based on title game design if they want to persist the muting state.
bool wasUserMuted = false;
IUser^ userBeingRemoved = nullptr;
if (chatUser != nullptr && chatUser->IsLocal)
{
wasUserMuted = chatUser->IsMuted;
userBeingRemoved = chatUser->User;
if (userBeingRemoved != nullptr)
{
RemoveUserFromChatChannel(oldChatChannelIndex, userBeingRemoved);
AddLocalUserToChatChannel(newChatChannelIndex, userBeingRemoved);
}
}
// If the local user was muted earlier, get the latest chat users and mute him again on the newly added channel.
if (wasUserMuted && userBeingRemoved != nullptr)
{
auto chatUsers = GetChatUsers();
if (chatUsers != nullptr )
{
for (UINT chatUserIndex = 0; chatUserIndex < chatUsers->Size; chatUserIndex++)
{
Microsoft::Xbox::GameChat::ChatUser^ chatUser = chatUsers->GetAt(chatUserIndex);
if( chatUser != nullptr && (chatUser->XboxUserId == userBeingRemoved->XboxUserId) )
{
m_chatManager->MuteUserFromAllChannels(chatUser);
break;
}
}
}
}
}
void ChatIntegrationLayer::ChangeChatUserMuteState(
__in Microsoft::Xbox::GameChat::ChatUser^ chatUser
)
{
/// Helper function to swap the mute state of a specific chat user
if( m_chatManager != nullptr && chatUser != nullptr)
{
if (chatUser->IsMuted)
{
m_chatManager->UnmuteUserFromAllChannels(chatUser);
}
else
{
m_chatManager->MuteUserFromAllChannels(chatUser);
}
}
}
Microsoft::Xbox::GameChat::ChatUser^ ChatIntegrationLayer::GetChatUserByXboxUserId(
__in Platform::String^ xboxUserId
)
{
Windows::Foundation::Collections::IVectorView<Microsoft::Xbox::GameChat::ChatUser^>^ chatUsers = GetChatUsers();
for each (Microsoft::Xbox::GameChat::ChatUser^ chatUser in chatUsers)
{
if (chatUser != nullptr && ( chatUser->XboxUserId == xboxUserId ) )
{
return chatUser;
}
}
return nullptr;
}
bool ChatIntegrationLayer::CompareUniqueConsoleIdentifiers(
__in Platform::Object^ uniqueRemoteConsoleIdentifier1,
__in Platform::Object^ uniqueRemoteConsoleIdentifier2
)
{
if (uniqueRemoteConsoleIdentifier1 == nullptr || uniqueRemoteConsoleIdentifier2 == nullptr)
{
return false;
}
// uniqueRemoteConsoleIdentifier is a Platform::Object^ and can be cast or unboxed to most types.
// We're using XRNS addresses, which are unsigned ints
unsigned int address1 = safe_cast<unsigned int>(uniqueRemoteConsoleIdentifier1);
unsigned int address2 = safe_cast<unsigned int>(uniqueRemoteConsoleIdentifier2);
return address1 == address2;
}
Microsoft::Xbox::GameChat::ChatPerformanceCounters^ ChatIntegrationLayer::GetChatPerformanceCounters()
{
if( m_chatManager != nullptr &&
m_chatManager->ChatSettings != nullptr &&
m_chatManager->ChatSettings->PerformanceCountersEnabled )
{
return m_chatManager->ChatPerformanceCounters;
}
return nullptr;
}
void ChatIntegrationLayer::OnControllerPairingChanged( Windows::Xbox::Input::ControllerPairingChangedEventArgs^ args )
{
#if 0
auto controller = args->Controller;
if ( controller )
{
if ( controller->Type == L"Windows.Xbox.Input.Gamepad" )
{
// Either add the user or sign one in
User^ user = args->User;
if ( user != nullptr )
{
g_sampleInstance->GetLoggingUI()->LogCommentToUI("OnControllerPairingChanged: " + user->DisplayInfo->Gamertag);
AddLocalUserToChatChannel(
g_sampleInstance->GetUISelectionChatChannelIndex(),
user
);
}
}
}
#endif
}
void ChatIntegrationLayer::ToggleRenderTargetVolume()
{
// Simple toggle logic to just show usage of LocalRenderTargetVolume property
static bool makeRenderTargetVolumeQuiet = false;
makeRenderTargetVolumeQuiet = !makeRenderTargetVolumeQuiet;
auto chatUsers = GetChatUsers();
if (chatUsers != nullptr )
{
for (UINT chatUserIndex = 0; chatUserIndex < chatUsers->Size; chatUserIndex++)
{
Microsoft::Xbox::GameChat::ChatUser^ chatUser = chatUsers->GetAt(chatUserIndex);
if( chatUser != nullptr && chatUser->IsLocal )
{
chatUser->LocalRenderTargetVolume = ( makeRenderTargetVolumeQuiet ) ? 0.1f : 1.0f;
}
}
}
}
void ChatIntegrationLayer::GetBufferBytes( __in Windows::Storage::Streams::IBuffer^ buffer, __out byte** ppOut )
{
if ( ppOut == nullptr || buffer == nullptr )
{
throw ref new Platform::InvalidArgumentException();
}
*ppOut = nullptr;
Microsoft::WRL::ComPtr<IInspectable> srcBufferInspectable(reinterpret_cast<IInspectable*>( buffer ));
Microsoft::WRL::ComPtr<Windows::Storage::Streams::IBufferByteAccess> srcBufferByteAccess;
srcBufferInspectable.As(&srcBufferByteAccess);
srcBufferByteAccess->Buffer(ppOut);
}
Windows::Storage::Streams::IBuffer^ ChatIntegrationLayer::ArrayToBuffer( __in Platform::Array<byte>^ array )
{
Windows::Storage::Streams::DataWriter^ writer = ref new Windows::Storage::Streams::DataWriter();
writer->WriteBytes(array);
return writer->DetachBuffer();
}

View File

@@ -0,0 +1,244 @@
//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//// PARTICULAR PURPOSE.
////
//// Copyright (c) Microsoft Corporation. All rights reserved
#pragma once
//#include "UserController.h"
class DQRNetworkManager;
enum ChatPacketType
{
IncomingPacket = 0,
OutgoingPacket = 1
};
class ChatIntegrationLayer
: public std::enable_shared_from_this<ChatIntegrationLayer> // shared_from_this is needed to use a weak ref to 'this' when handling delegate callbacks
{
public:
ChatIntegrationLayer();
DQRNetworkManager *m_pDQRNet;
/// <summary>
/// Initializes the chat manager
/// </summary>
void InitializeChatManager(
__in bool combineCaptureBuffersIntoSinglePacketEnabled,
__in bool useKinectAsCaptureSource,
__in bool applySoundEffectToCapture,
__in bool applySoundEffectToRender,
DQRNetworkManager *pDQRNet
);
/// <summary>
/// Shuts down the chat manager
/// </summary>
void Shutdown();
class AddedUser
{
public:
AddedUser(Windows::Xbox::System::IUser^ user, bool canCaptureAudio);
Windows::Xbox::System::IUser^ m_user;
bool m_canCaptureAudio;
};
void AddLocalUser( __in Windows::Xbox::System::IUser^ user );
void RemoveLocalUser( __in Windows::Xbox::System::IUser^ user );
void EvaluateDevicesForUser(__in Windows::Xbox::System::IUser^ user );
vector<AddedUser *> m_addedUsers;
CRITICAL_SECTION m_csAddedUsers;
private:
/// <summary>
/// Adds a local user to a specific channel
/// This is helper function waits for the task to complete so shouldn't be called from the UI thread
/// </summary>
/// <param name="channelIndex">The channel to add the user to</param>
/// <param name="user">The local user to add</param>
void AddLocalUserToChatChannel(
__in uint8 channelIndex,
__in Windows::Xbox::System::IUser^ user
);
/// <summary>
/// Removes a local user from a specific channel
/// This is helper function waits for the task to complete so shouldn't be called from the UI thread
/// </summary>
/// <param name="channelIndex">The channel to remove the user from</param>
/// <param name="user">The local user to remove</param>
void RemoveUserFromChatChannel(
__in uint8 channelIndex,
__in Windows::Xbox::System::IUser^ user
);
public:
/// <summary>
/// Removes a remote console from chat
/// This is helper function waits for the task to complete so shouldn't be called from the UI thread
/// </summary>
/// <param name="uniqueRemoteConsoleIdentifier">A unique ID for the remote console</param>
void RemoveRemoteConsole(
unsigned int address
);
/// <summary>
/// Handles incoming chat messages from the game's network layer
/// </summary>
/// <param name="chatPacket">A buffer containing the chat message</param>
/// <param name="uniqueRemoteConsoleIdentifier">A unique ID for the remote console</param>
void OnIncomingChatMessage(
unsigned int sessionAddress,
Platform::Array<byte>^ message
);
/// <summary>
/// Returns a list of chat users in the chat session
/// </summary>
Windows::Foundation::Collections::IVectorView<Microsoft::Xbox::GameChat::ChatUser^>^ GetChatUsers();
/// <summary>
/// Returns true if the game has mic focus. Otherwise another app has mic focus
/// </summary>
bool HasMicFocus();
/// <summary>
/// Helper function to swap the mute state of a specific chat user
/// </summary>
void ChangeChatUserMuteState(
__in Microsoft::Xbox::GameChat::ChatUser^ chatUser
);
/// <summary>
/// Helper function to change the channel of a specific chat user
/// </summary>
void HandleChatChannelChanged(
__in uint8 oldChatChannelIndex,
__in uint8 newChatChannelIndex,
__in Microsoft::Xbox::GameChat::ChatUser^ chatUser
);
/// <summary>
/// Call this when a new console connects.
/// This adds this console to the chat layer
/// </summary>
void OnNewSessionAddressAdded(
__in unsigned int address
);
/// <summary>
/// Adds a list of locally signed in users that have intent to play to the chat session on a specific channel index.
/// Avoid adding any user who is signed in that doesn't have intent to play otherwise users who are biometrically
/// signed in automatically will be added to the chat session
/// </summary>
/// <param name="channelIndex">The channel to add the users to</param>
/// <param name="locallySignedInUsers">A list of locally signed in users that have intent to play</param>
void AddAllLocallySignedInUsersToChatClient(
__in uint8 channelIndex,
__in Windows::Foundation::Collections::IVectorView<Windows::Xbox::System::User^>^ locallySignedInUsers
);
/// <summary>
/// Handles when a debug message is received. Send this to the UI and OutputDebugString. Games should integrate with their existing log system.
/// </summary>
/// <param name="args">Contains the debug message to log</param>
void OnDebugMessageReceived(
__in Microsoft::Xbox::GameChat::DebugMessageEventArgs^ args
);
/// <summary>
/// Send the chat packet to all connected consoles
///
/// To integrate the Chat DLL in your game, change the following code to use your game's network layer.
/// You will need to isolate chat messages to be unique from the rest of you game's other message types.
/// When args->SendPacketToAllConnectedConsoles is true, your game should send the chat message to each connected console using the game's network layer.
/// It should send the chat message with Reliable UDP if args->SendReliable is true.
/// It should send the chat message in order (if that feature is available) if args->SendInOrder is true
/// </summary>
/// <param name="args">Describes the packet to send</param>
void OnOutgoingChatPacketReady(
__in Microsoft::Xbox::GameChat::ChatPacketEventArgs^ args
);
/// <summary>
/// Example of how to cast an int to a Platform::Object^
/// </summary>
Platform::Object^ IntToPlatformObject(
__in int val
);
/// <summary>
/// Example of how to cast an Platform::Object^ to an int
/// </summary>
int PlatformObjectToInt(
__in Platform::Object^ obj
);
/// <summary>
/// Helper function to get specific ChatUser by xboxUserId
/// </summary>
Microsoft::Xbox::GameChat::ChatUser^ GetChatUserByXboxUserId(
__in Platform::String^ xboxUserId
);
/// <summary>
/// Helper function to get specific ChatUser by xboxUserId
/// </summary>
bool ChatIntegrationLayer::CompareUniqueConsoleIdentifiers(
__in Platform::Object^ uniqueRemoteConsoleIdentifier1,
__in Platform::Object^ uniqueRemoteConsoleIdentifier2
);
/// <summary>
/// Helper function to return the ChatPerformanceCounters^ from the ChatManager so perf numbers can be shown on the UI
/// These numbers will only be valid if m_chatManager->ChatSettings->PerformanceCountersEnabled is set to true.
/// </summary>
Microsoft::Xbox::GameChat::ChatPerformanceCounters^ GetChatPerformanceCounters();
/// <summary>
/// Returns a count of the number of chat packets of a specific type that have been either sent or received.
/// It is useful to monitor this number in the UI / logs to debug network issues.
/// </summary>
int GameUI_GetPacketStatistic(
__in Microsoft::Xbox::GameChat::ChatMessageType messageType,
__in ChatPacketType chatPacketType
);
void OnControllerPairingChanged(
__in Windows::Xbox::Input::ControllerPairingChangedEventArgs^ args
);
void ToggleRenderTargetVolume();
private:
void GetBufferBytes( __in Windows::Storage::Streams::IBuffer^ buffer, __out byte** ppOut );
Windows::Storage::Streams::IBuffer^ ArrayToBuffer( __in Platform::Array<byte>^ array );
Concurrency::critical_section m_lock;
Microsoft::Xbox::GameChat::ChatManager^ m_chatManager;
Windows::Foundation::EventRegistrationToken m_tokenOnDebugMessage;
Windows::Foundation::EventRegistrationToken m_tokenOnOutgoingChatPacketReady;
Windows::Foundation::EventRegistrationToken m_tokenOnCompareUniqueConsoleIdentifiers;
Windows::Foundation::EventRegistrationToken m_tokenResourceAvailabilityChanged;
Windows::Foundation::EventRegistrationToken m_tokenOnPreEncodeAudioBuffer;
Windows::Foundation::EventRegistrationToken m_tokenOnPostDecodeAudioBuffer;
Windows::Foundation::EventRegistrationToken m_tokenUserAudioDeviceAdded;
Windows::Foundation::EventRegistrationToken m_tokenUserAudioDeviceRemoved;
Windows::Foundation::EventRegistrationToken m_tokenUserAudioDeviceChanged;
// Debug stats for chat packets. Use Debug_GetPacketStatistic() to get the values.
/// It is useful to monitor this number in the UI / logs to debug network issues.
void GameUI_RecordPacketStatistic(
__in Microsoft::Xbox::GameChat::ChatMessageType messageType,
__in ChatPacketType chatPacketType
);
Concurrency::critical_section m_chatPacketStatsLock;
int m_chatVoicePacketsStatistic[2][(int)Microsoft::Xbox::GameChat::ChatMessageType::InvalidMessage+1];
};
std::shared_ptr<ChatIntegrationLayer> GetChatIntegrationLayer();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,582 @@
#pragma once
#include "DQRNetworkPlayer.h"
#include "..\Minecraft.World\C4JThread.h"
#include <collection.h>
class IDQRNetworkManagerListener;
class PartyController;
class DQRNetworkManager;
class ChatIntegrationLayer;
namespace MXS = Microsoft::Xbox::Services;
namespace MXSM = Microsoft::Xbox::Services::Multiplayer;
namespace MXSS = Microsoft::Xbox::Services::Social;
namespace WXM = Windows::Xbox::Multiplayer;
namespace WXN = Windows::Xbox::Networking;
namespace WXNRs = Windows::Xbox::Networking::RealtimeSession;
namespace WFC = Windows::Foundation::Collections;
#define MATCH_SESSION_TEMPLATE_NAME L"PeerToHostTemplate"
#define MAX_PLAYERS_IN_TEMPLATE 8
using namespace std;
ref class DQRNetworkManagerEventHandlers sealed
{
internal:
DQRNetworkManagerEventHandlers(DQRNetworkManager *pDQRNet);
public:
void Setup(WXNRs::Session^ session);
void Pulldown(WXNRs::Session^ session);
void DataReceivedHandler(Platform::Object^ session, WXNRs::DataReceivedEventArgs^ args);
void SessionAddressDataChangedHandler(Platform::Object^ session, WXNRs::SessionAddressDataChangedEventArgs^ args);
void SessionStatusUpdateHandler(Platform::Object^ session, WXNRs::SessionStatusUpdateEventArgs^ args);
void AddedSessionAddressHandler(Platform::Object^ session, WXNRs::AddedSessionAddressEventArgs^ args);
void RemovedSessionAddressHandler(Platform::Object^ session, WXNRs::RemovedSessionAddressEventArgs^ args);
void GlobalSessionDataChangedHandler(Platform::Object^ session, WXNRs::GlobalSessionDataChangedEventArgs^ args);
private:
Windows::Foundation::EventRegistrationToken m_dataReceivedToken;
Windows::Foundation::EventRegistrationToken m_sessionStatusToken;
Windows::Foundation::EventRegistrationToken m_sessionAddressToken;
Windows::Foundation::EventRegistrationToken m_addedSessionToken;
Windows::Foundation::EventRegistrationToken m_removedSessionToken;
Windows::Foundation::EventRegistrationToken m_globalDataToken;
DQRNetworkManager *m_pDQRNet;
};
typedef enum
{
DQR_INTERNAL_ASSIGN_SMALL_IDS,
DQR_INTERNAL_UNASSIGN_SMALL_ID,
DQR_INTERNAL_PLAYER_TABLE,
DQR_INTERNAL_ADD_PLAYER_FAILED,
};
class DQRConnectionInfo
{
public:
typedef enum
{
ConnectionState_HeaderByte0,
ConnectionState_HeaderByte1,
ConnectionState_ReadBytes
} eDQRConnectionState;
typedef enum
{
ConnectionState_InternalHeaderByte,
ConnectionState_InternalAssignSmallIdMask,
ConnectionState_InternalAssignSmallId0,
ConnectionState_InternalAssignSmallId1,
ConnectionState_InternalAssignSmallId2,
ConnectionState_InternalAssignSmallId3,
ConnectionState_InternalRoomSyncData,
ConnectionState_InternalAddPlayerFailedData,
} eDQRInternalDataState;
DQRConnectionInfo();
void Reset();
eDQRConnectionState m_state;
eDQRInternalDataState m_internalDataState;
int m_currentChannel;
bool m_internalFlag;
int m_bytesRemaining;
int m_roomSyncDataBytesRead;
int m_roomSyncDataBytesToRead;
unsigned char * m_pucRoomSyncData;
int m_addFailedPlayerDataBytesRead;
int m_addFailedPlayerDataBytesToRead;
unsigned char * m_pucAddFailedPlayerData;
unsigned char m_smallId[4];
bool m_channelActive[4];
unsigned char m_smallIdReadMask;
unsigned char *m_pucsmallIdReadMaskResolved;
bool m_initialPacketReceived;
};
class DQRNetworkManager
{
friend class PartyController;
friend ref class DQRNetworkManagerEventHandlers;
friend class DQRNetworkPlayer;
friend class ChatIntegrationLayer;
public:
static const int JOIN_PACKET_RESEND_DELAY_MS = 200;
static const int JOIN_PACKET_RESEND_TIMEOUT_MS = 20000;
static const int JOIN_RESERVATION_WAIT_TIME = 30000;
static const int JOIN_CREATE_SESSION_MAX_ATTEMPTS = 5;
static const int PRIMARY_PLAYER_LEAVING_PARTY_WAIT_MS = 20000; // Time between primary player leaving and when we should respond to it, to allow time for us to receive a new party for them to be going to if they are just changing party rather than leaving altogether
class SessionInfo
{
public:
SessionInfo(wstring& sessionName, wstring& serviceConfig, wstring& sessionTemplate);
SessionInfo();
bool m_detailsValid;
wstring m_sessionName;
wstring m_serviceConfig;
wstring m_sessionTemplate;
};
static const int MAX_LOCAL_PLAYER_COUNT = 4;
static const int MAX_ONLINE_PLAYER_COUNT = 8;
static const int MAX_ONLINE_PLAYER_NAME_LENGTH = 21;
// This class stores everything about a player that must be synchronised between machines.
class PlayerSyncData
{
public:
wchar_t *m_XUID; // XUID / XboxUserIds are passed round as decimal strings on Xbox 1
uint32_t m_sessionAddress; // XRNS session address for this player, ie can identify which machine this player is on
uint8_t m_smallId; // Assigned by DQRNetworkManager, to attach a permanent id to this player (until we have to wrap round), to match a similar concept in qnet
uint8_t m_channel; // Local index / communication channel within session address, of player on this machine
wchar_t m_name[MAX_ONLINE_PLAYER_NAME_LENGTH];
};
class RoomSyncData
{
public:
int playerCount;
PlayerSyncData players[MAX_ONLINE_PLAYER_COUNT];
};
class HostGamertagResolveDetails
{
public:;
DQRNetworkPlayer* m_pPlayer;
wstring m_name;
unsigned int m_sessionAddress;
int m_channel;
bool m_sync;
};
DQRNetworkManager(IDQRNetworkManagerListener *listener);
void Initialise();
void EnableDebugXBLContext(MXS::XboxLiveContext^ XBLContext);
void CreateAndJoinSession(int userMask, unsigned char *customSessionData, unsigned int customSessionDataSize, bool offline);
void JoinSession(int playerMask);
void JoinSessionFromInviteInfo(int playerMask);
bool AddUsersToSession(int playerMask, MXSM::MultiplayerSessionReference^ sessionRef );
void UpdateCustomSessionData();
bool AddLocalPlayerByUserIndex(int userIndex);
bool RemoveLocalPlayerByUserIndex(int userIndex);
bool IsHost();
bool IsInSession();
// Player retrieval
int GetPlayerCount();
int GetOnlinePlayerCount();
DQRNetworkPlayer *GetPlayerByIndex(int idx);
DQRNetworkPlayer *GetPlayerBySmallId(int idx);
DQRNetworkPlayer *GetPlayerByXuid(PlayerUID xuid);
wstring GetDisplayNameByGamertag(wstring gamertag);
DQRNetworkPlayer *GetLocalPlayerByUserIndex(int idx);
DQRNetworkPlayer *GetHostPlayer();
int GetSessionIndex(DQRNetworkPlayer *player);
void Tick();
void Tick_XRNS();
void Tick_VoiceChat();
void Tick_Party();
void Tick_CustomSessionData();
void Tick_AddAndRemoveLocalPlayers();
void Tick_ResolveGamertags();
void Tick_PartyProcess();
void Tick_StateMachine();
void Tick_CheckInviteParty();
bool ShouldMessageForFullSession();
void FlagInvitedToFullSession();
void UnflagInvitedToFullSession();
// Externally exposed state. All internal states are mapped to one of these broader states.
typedef enum
{
DNM_STATE_INITIALISING,
DNM_STATE_INITIALISE_FAILED,
DNM_STATE_IDLE,
DNM_STATE_HOSTING,
DNM_STATE_JOINING,
DNM_STATE_STARTING,
DNM_STATE_PLAYING,
DNM_STATE_LEAVING,
DNM_STATE_ENDING,
} eDQRNetworkManagerState;
eDQRNetworkManagerState GetState();
class SessionSearchResult
{
public:
SessionSearchResult() { m_extData = NULL; }
~SessionSearchResult() { free(m_extData); }
wstring m_partyId;
wstring m_sessionName;
// These names/xuids reflect the server controlled list of who is actually in the game
wstring m_playerNames[MAX_ONLINE_PLAYER_COUNT];
PlayerUID m_playerXuids[MAX_ONLINE_PLAYER_COUNT];
int m_playerCount;
// This count & set of xuids reflects the session document list of who is in the game
wstring m_sessionXuids[MAX_ONLINE_PLAYER_COUNT];
int m_usedSlotCount;
void *m_extData;
};
protected:
// NOTE: If anything changes in here, then the mapping from internal -> external state needs to be updated (m_INTtoEXTStateMappings, defined in the cpp file)
typedef enum
{
DNM_INT_STATE_INITIALISING,
DNM_INT_STATE_INITIALISE_FAILED,
DNM_INT_STATE_IDLE,
DNM_INT_STATE_HOSTING,
DNM_INT_STATE_HOSTING_WAITING_TO_PLAY,
DNM_INT_STATE_HOSTING_FAILED,
DNM_INT_STATE_JOINING,
DNM_INT_STATE_JOINING_WAITING_FOR_RESERVATIONS,
DNM_INT_STATE_JOINING_GET_SDA,
DNM_INT_STATE_JOINING_WAITING_FOR_SDA,
DNM_INT_STATE_JOINING_CREATE_SESSION,
DNM_INT_STATE_JOINING_WAITING_FOR_ACTIVE_SESSION,
DNM_INT_STATE_JOINING_SENDING_UNRELIABLE,
DNM_INT_STATE_JOINING_FAILED_TIDY_UP,
DNM_INT_STATE_JOINING_FAILED,
DNM_INT_STATE_STARTING,
DNM_INT_STATE_PLAYING,
DNM_INT_STATE_LEAVING,
DNM_INT_STATE_LEAVING_FAILED,
DNM_INT_STATE_ENDING,
DNM_INT_STATE_COUNT
} eDQRNetworkManagerInternalState;
typedef enum
{
DNM_ADD_PLAYER_STATE_IDLE,
DNM_ADD_PLAYER_STATE_PROCESSING,
DNM_ADD_PLAYER_STATE_COMPLETE_SUCCESS,
DNM_ADD_PLAYER_STATE_COMPLETE_FAIL,
DNM_ADD_PLAYER_STATE_COMPLETE_FAIL_FULL,
} eDQRAddLocalPlayerState;
typedef enum
{
DNM_REMOVE_PLAYER_STATE_IDLE,
DNM_REMOVE_PLAYER_STATE_PROCESSING,
DNM_REMOVE_PLAYER_STATE_COMPLETE_SUCCESS,
DNM_REMOVE_PLAYER_STATE_COMPLETE_FAIL,
} eDQRRemoveLocalPlayerState;
class StateChangeInfo
{
public:
eDQRNetworkManagerState m_oldState;
eDQRNetworkManagerState m_newState;
StateChangeInfo(eDQRNetworkManagerState oldState, eDQRNetworkManagerState newState) : m_oldState(oldState), m_newState(newState) {}
};
typedef enum
{
// Incoming messages
RTS_MESSAGE_DATA_RECEIVED,
RTS_MESSAGE_DATA_RECEIVED_CHAT,
RTS_MESSAGE_ADDED_SESSION_ADDRESS,
RTS_MESSAGE_REMOVED_SESSION_ADDRESS,
RTS_MESSAGE_STATUS_ACTIVE,
RTS_MESSAGE_STATUS_TERMINATED,
// Outgoing messages
RTS_MESSAGE_START_CLIENT,
RTS_MESSAGE_START_HOST,
RTS_MESSAGE_TERMINATE,
RTS_MESSAGE_SEND_DATA,
} eRTSMessageType;
typedef enum
{
RTS_MESSAGE_FLAG_BROADCAST_MODE = 1,
RTS_MESSAGE_FLAG_RELIABLE = 2,
RTS_MESSAGE_FLAG_SEQUENTIAL = 4,
RTS_MESSAGE_FLAG_COALESCE = 8,
RTS_MESSAGE_FLAG_GAME_CHANNEL = 16,
} eRTSFlags;
class RTS_Message
{
public:
eRTSMessageType m_eType;
unsigned int m_sessionAddress;
unsigned char *m_pucData;
unsigned int m_dataSize;
unsigned int m_flags;
};
std::queue<StateChangeInfo> m_stateChangeQueue;
CRITICAL_SECTION m_csStateChangeQueue;
CRITICAL_SECTION m_csSendBytes;
CRITICAL_SECTION m_csPartyViewVector;
std::queue<RTS_Message> m_RTSMessageQueueIncoming;
CRITICAL_SECTION m_csRTSMessageQueueIncoming;
std::queue<RTS_Message> m_RTSMessageQueueOutgoing;
CRITICAL_SECTION m_csRTSMessageQueueOutgoing;
private:
void SetState(DQRNetworkManager::eDQRNetworkManagerInternalState state);
static const eDQRNetworkManagerState m_INTtoEXTStateMappings[DNM_INT_STATE_COUNT];
eDQRNetworkManagerInternalState m_state;
eDQRNetworkManagerState m_stateExternal;
__int64 m_lastUnreliableSendTime;
__int64 m_firstUnreliableSendTime;
__int64 m_startedWaitingForReservationsTime;
unsigned char *m_customSessionData;
unsigned int m_customSessionDataSize;
int m_customDataDirtyUpdateTicks;
std::shared_ptr<ChatIntegrationLayer> m_chat;
eDQRAddLocalPlayerState m_addLocalPlayerState;
DQRNetworkPlayer *m_addLocalPlayerSuccessPlayer;
int m_addLocalPlayerSuccessIndex;
int m_addLocalPlayerFailedIndex;
eDQRRemoveLocalPlayerState m_removeLocalPlayerState;
int m_removeLocalPlayerIndex;
Windows::Xbox::System::User^ m_primaryUser;
MXS::XboxLiveContext^ m_primaryUserXboxLiveContext;
WXN::SecureDeviceAssociationTemplate^ m_associationTemplate;
CRITICAL_SECTION m_csRoomSyncData;
RoomSyncData m_roomSyncData;
DQRNetworkPlayer *m_players[MAX_ONLINE_PLAYER_COUNT];
IDQRNetworkManagerListener *m_listener;
PartyController *m_partyController;
DQRNetworkManagerEventHandlers^ m_eventHandlers;
WXNRs::Session^ m_XRNS_Session;
unsigned int m_XRNS_LocalAddress;
unsigned int m_XRNS_OldestAddress;
MXSM::MultiplayerSession^ m_multiplayerSession;
WXN::SecureDeviceAssociation^ m_sda;
Platform::String^ m_secureDeviceAddressBase64;
unsigned char m_currentSmallId;
unsigned char m_hostSmallId;
bool m_isHosting;
bool m_isInSession;
bool m_isOfflineGame;
public:
int m_currentUserMask;
private:
int m_joinSessionUserMask;
Platform::Array<Platform::String^>^ m_joinSessionXUIDs;
int m_joinSmallIdMask;
Platform::Array<BYTE>^ m_remoteSocketAddress;
Platform::Array<BYTE>^ m_localSocketAddress;
int m_joinCreateSessionAttempts;
C4JThread *m_CreateSessionThread;
C4JThread *m_LeaveRoomThread;
C4JThread *m_TidyUpJoinThread;
C4JThread *m_UpdateCustomSessionDataThread;
C4JThread *m_RTS_DoWorkThread;
C4JThread *m_CheckPartyInviteThread;
bool m_notifyForFullParty;
unordered_map<unsigned int,DQRConnectionInfo *> m_sessionAddressToConnectionInfoMapHost; // For host - there may be more than one remote session attached to this listening session
unsigned int m_sessionAddressFromSmallId[256]; // For host - for mapping back from small Id, to session address
unsigned char m_channelFromSmallId[256]; // For host and client, for mapping back from small Id, to channel
DQRConnectionInfo m_connectionInfoClient; // For client
unsigned int m_hostSessionAddress; // For client
CRITICAL_SECTION m_csHostGamertagResolveResults;
queue<HostGamertagResolveDetails *> m_hostGamertagResolveResults;
void AddPlayerFailed(Platform::String ^xuid);
Platform::String^ RemoveBracesFromGuidString(__in Platform::String^ guid );
void HandleSessionChange( MXSM::MultiplayerSession^ session );
MXSM::MultiplayerSession^ WriteSessionHelper( MXS::XboxLiveContext^ xboxLiveContext,
MXSM::MultiplayerSession^ multiplayerSession,
MXSM::MultiplayerSessionWriteMode writeMode,
HRESULT& hr);
MXSM::MultiplayerSessionMember^ GetUserMemberInSession( Windows::Xbox::System::User^ user, MXSM::MultiplayerSession^ session);
bool IsPlayerInSession( Platform::String^ xboxUserId, MXSM::MultiplayerSession^ session, int *smallId );
WXM::MultiplayerSessionReference^ ConvertToWindowsXboxMultiplayerSessionReference( MXSM::MultiplayerSessionReference^ sessionRef);
MXSM::MultiplayerSessionReference^ ConvertToMicrosoftXboxServicesMultiplayerSessionReference( WXM::MultiplayerSessionReference^ sessionRef );
void BytesReceived(int smallId, BYTE *bytes, int byteCount);
void BytesReceivedInternal(DQRConnectionInfo *connectionInfo, unsigned int sessionAddress, BYTE *bytes, int byteCount);
void SendBytes(int smallId, BYTE *bytes, int byteCount);
int GetQueueSizeBytes();
int GetQueueSizeMessages();
void SendBytesRaw(int smallId, BYTE *bytes, int byteCount, bool reliableAndSequential);
void SendBytesChat(unsigned int address, BYTE *bytes, int byteCount, bool reliable, bool sequential, bool broadcast);
bool AddRoomSyncPlayer(DQRNetworkPlayer *pPlayer, unsigned int sessionAddress, int channel);
void RemoveRoomSyncPlayersWithSessionAddress(unsigned int sessionAddress);
void RemoveRoomSyncPlayer(DQRNetworkPlayer *pPlayer);
void UpdateRoomSyncPlayers(RoomSyncData *pNewSyncData);
void SendRoomSyncInfo();
void SendAddPlayerFailed(Platform::String^ xuid);
void SendSmallId(bool reliableAndSequential, int playerMask);
void SendUnassignSmallId(int playerIndex);
int GetSessionIndexForSmallId(unsigned char smallId);
int GetSessionIndexAndSmallIdForHost(unsigned char *smallId);
static void LogComment( Platform::String^ strText );
static void LogCommentFormat( LPCWSTR strMsg, ... );
static void LogCommentWithError( Platform::String^ strTest, HRESULT hr );
static Platform::String^ GetErrorString( HRESULT hr );
static Platform::String^ FormatString( LPCWSTR strMsg, ... );
static Platform::String^ ConvertHResultToErrorName( HRESULT hr );
Platform::String^ GetNextSmallIdAsJsonString();
static int _HostGameThreadProc( void* lpParameter );
int HostGameThreadProc();
static int _LeaveRoomThreadProc( void* lpParameter );
int LeaveRoomThreadProc();
static int _TidyUpJoinThreadProc( void* lpParameter );
int TidyUpJoinThreadProc();
static int _UpdateCustomSessionDataThreadProc( void* lpParameter );
int UpdateCustomSessionDataThreadProc();
static int _CheckInviteThreadProc(void *lpParameter);
int CheckInviteThreadProc();
static int _RTSDoWorkThread(void* lpParameter);
int RTSDoWorkThread();
CRITICAL_SECTION m_csVecChatPlayers;
vector<int> m_vecChatPlayersJoined;
public:
void HandleNewPartyFoundForPlayer();
void HandlePlayerRemovedFromParty(int playerMask);
void ChatPlayerJoined(int idx);
bool IsReadyToPlayOrIdle();
void StartGame();
void LeaveRoom();
void TidyUpFailedJoin();
static void SetInviteReceivedFlag();
static void SetPartyProcessJoinParty();
static void SetPartyProcessJoinSession(int bootUserIndex, Platform::String^ bootSessionName, Platform::String^ bootServiceConfig, Platform::String^ bootSessionTemplate);
void SendInviteGUI(int quadrant);
bool IsAddingPlayer();
bool FriendPartyManagerIsBusy();
int FriendPartyManagerGetCount();
bool FriendPartyManagerSearch();
void FriendPartyManagerGetSessionInfo(int idx, SessionSearchResult *searchResult);
WFC::IVectorView<WXM::UserPartyAssociation^>^ FilterPartiesByPermission(MXS::XboxLiveContext ^context, WFC::IVectorView<WXM::UserPartyAssociation^>^ partyResults);
bool JoinPartyFromSearchResult(SessionSearchResult *searchResult, int playerMask);
void CancelJoinPartyFromSearchResult();
void RequestDisplayName(DQRNetworkPlayer *player);
void SetDisplayName(PlayerUID xuid, wstring displayName);
private:
__int64 m_playersLeftPartyTime;
int m_playersLeftParty;
bool GetBestPartyUserIndex();
C4JThread *m_GetFriendPartyThread;
static int _GetFriendsThreadProc( void* lpParameter );
int GetFriendsThreadProc();
bool IsSessionFriendsOfFriends(MXSM::MultiplayerSession^ session);
bool GetGameSessionData(MXSM::MultiplayerSession^ session, void *gameSessionData);
public:
static Platform::Collections::Vector<Platform::String^>^ GetFriends();
private:
SessionSearchResult *m_sessionSearchResults;
int m_sessionResultCount;
bool m_cancelJoinFromSearchResult;
map<wstring, wstring> m_displayNames; // Player display names by gamertag
typedef enum
{
DNM_PARTY_PROCESS_NONE,
DNM_PARTY_PROCESS_JOIN_PARTY,
DNM_PARTY_PROCESS_JOIN_SPECIFIED
} ePartyProcessType;
static int m_bootUserIndex;
static ePartyProcessType m_partyProcess;
static wstring m_bootSessionName;
static wstring m_bootServiceConfig;
static wstring m_bootSessionTemplate;
static bool m_inviteReceived;
static DQRNetworkManager *s_pDQRManager;
static void GetProfileCallback(LPVOID pParam, Microsoft::Xbox::Services::Social::XboxUserProfile^ profile);
// Forced signout
bool m_handleForcedSignOut;
void CheckForcedSignOut();
void HandleForcedSignOut();
unsigned int m_RTS_Stat_totalBytes;
unsigned int m_RTS_Stat_totalSends;
void UpdateRTSStats();
// Incoming messages - to be called from the main thread, to get incoming messages from the RTS work thread
void ProcessRTSMessagesIncoming();
void Process_RTS_MESSAGE_DATA_RECEIVED(RTS_Message &message);
void Process_RTS_MESSAGE_DATA_RECEIVED_CHAT(RTS_Message &message);
void Process_RTS_MESSAGE_ADDED_SESSION_ADDRESS(RTS_Message &message);
void Process_RTS_MESSAGE_REMOVED_SESSION_ADDRESS(RTS_Message &message);
void Process_RTS_MESSAGE_STATUS_ACTIVE(RTS_Message &message);
void Process_RTS_MESSAGE_STATUS_TERMINATED(RTS_Message &message);
// Outgoing messages - to be called from the RTS work thread, to process requests from the main thread
void ProcessRTSMessagesOutgoing();
void Process_RTS_MESSAGE_START_CLIENT(RTS_Message &message);
void Process_RTS_MESSAGE_START_HOST(RTS_Message &message);
void Process_RTS_MESSAGE_TERMINATE(RTS_Message &message);
void Process_RTS_MESSAGE_SEND_DATA(RTS_Message &message);
// These methods are called from the main thread, to put an outgoing message onto the queue to be processed by these previous methods
void RTS_StartCient();
void RTS_StartHost();
void RTS_Terminate();
void RTS_SendData(unsigned char *pucData, unsigned int dataSize, unsigned int sessionAddress, bool reliable, bool sequential, bool coalesce, bool includeMode, bool gameChannel );
};
// Class defining interface to be implemented for class that handles callbacks
class IDQRNetworkManagerListener
{
public:
virtual void HandleDataReceived(DQRNetworkPlayer *playerFrom, DQRNetworkPlayer *playerTo, unsigned char *data, unsigned int dataSize) = 0;
virtual void HandlePlayerJoined(DQRNetworkPlayer *player) = 0;
virtual void HandlePlayerLeaving(DQRNetworkPlayer *player) = 0;
virtual void HandleStateChange(DQRNetworkManager::eDQRNetworkManagerState oldState, DQRNetworkManager::eDQRNetworkManagerState newState) = 0;
// virtual void HandleResyncPlayerRequest(DQRNetworkPlayer **aPlayers) = 0;
virtual void HandleAddLocalPlayerFailed(int idx, bool serverFull) = 0;
virtual void HandleDisconnect(bool bLostRoomOnly) = 0;
virtual void HandleInviteReceived(int playerIndex, DQRNetworkManager::SessionInfo *pInviteInfo) = 0;
virtual bool IsSessionJoinable() = 0;
};

View File

@@ -0,0 +1,591 @@
#include "stdafx.h"
#include "DQRNetworkManager.h"
#include "PartyController.h"
#include <collection.h>
#include <ppltasks.h>
#include <ws2tcpip.h>
#include "..\Minecraft.World\StringHelpers.h"
#include "base64.h"
#ifdef _DURANGO
#include "..\Minecraft.World\DurangoStats.h"
#endif
#include "ChatIntegrationLayer.h"
using namespace Concurrency;
using namespace Windows::Foundation::Collections;
// Returns true if we are already processing a request to find game parties of friends
bool DQRNetworkManager::FriendPartyManagerIsBusy()
{
if( m_GetFriendPartyThread )
{
if( m_GetFriendPartyThread->isRunning() )
{
return true;
}
}
return false;
}
// Returns the total count of game parties that we found for our friends
int DQRNetworkManager::FriendPartyManagerGetCount()
{
return m_sessionResultCount;
}
// Initiate the (asynchronous) search for game parties of our friends
bool DQRNetworkManager::FriendPartyManagerSearch()
{
if( m_GetFriendPartyThread )
{
if( m_GetFriendPartyThread->isRunning() )
{
return false;
}
}
m_sessionResultCount = 0;
delete [] m_sessionSearchResults;
m_sessionSearchResults = NULL;
m_GetFriendPartyThread = new C4JThread(&_GetFriendsThreadProc,this,"GetFriendsThreadProc");
m_GetFriendPartyThread->Run();
return true;
}
// Get a particular search result for a game party that we have discovered. Index should be from 0 to the value returned by FriendPartyManagerGetCount.
void DQRNetworkManager::FriendPartyManagerGetSessionInfo(int idx, SessionSearchResult *searchResult)
{
assert( idx < m_sessionResultCount );
assert( ( m_GetFriendPartyThread == NULL ) || ( !m_GetFriendPartyThread->isRunning()) );
// Need to make sure that copied data has independently allocated m_extData, so both copies can be freed
*searchResult = m_sessionSearchResults[idx];
searchResult->m_extData = malloc(sizeof(GameSessionData));
memcpy(searchResult->m_extData, m_sessionSearchResults[idx].m_extData, sizeof(GameSessionData));
}
int DQRNetworkManager::_GetFriendsThreadProc(void* lpParameter)
{
DQRNetworkManager *pDQR = (DQRNetworkManager *)lpParameter;
return pDQR->GetFriendsThreadProc();
}
// This is the main thread that is kicked off to find game sessions associated with our friends. We have to do this
// by finding parties associated with our friends, and from the parties get the assocated game session.
int DQRNetworkManager::GetFriendsThreadProc()
{
LogComment(L"Starting GetFriendsThreadProc");
WXS::User^ primaryUser = ProfileManager.GetUser(0);
if( primaryUser == nullptr )
{
return -1;
}
MXS::XboxLiveContext^ primaryUserXboxLiveContext = ref new MXS::XboxLiveContext(primaryUser);
if( primaryUserXboxLiveContext == nullptr )
{
return -1;
}
MXSS::XboxSocialRelationshipResult^ socialRelationshipResult = nullptr;
// First get our friends list (people we follow who may or may not follow us back), note we're requesting all friends
auto getSocialRelationshipsAsync = primaryUserXboxLiveContext->SocialService->GetSocialRelationshipsAsync(MXSS::SocialRelationship::All, 0, 1100);
create_task(getSocialRelationshipsAsync).then([this,&socialRelationshipResult](task<MXSS::XboxSocialRelationshipResult^> t)
{
try
{
socialRelationshipResult = t.get();
}
catch (Platform::COMException^ ex)
{
LogCommentWithError( L"GetSocialRelationshipsAsync failed", ex->HResult );
}
})
.wait();
if( socialRelationshipResult == nullptr )
{
return -1;
}
IVector<Platform::String^>^ friendXUIDs = ref new Platform::Collections::Vector<Platform::String^>;
// Now construct a vector of these users, that follow us back - these are our "friends"
for( int i = 0; i < socialRelationshipResult->TotalCount; i++ )
{
MXSS::XboxSocialRelationship^ relationship = socialRelationshipResult->Items->GetAt(i);
if(relationship->IsFollowingCaller)
{
friendXUIDs->Append(relationship->XboxUserId);
}
}
// If we don't have any such friends, we're done
if( friendXUIDs->Size == 0 )
{
return 0;
}
// Now get party associations for these friends
auto getPartyAssociationsAsync = WXM::Party::GetUserPartyAssociationsAsync(primaryUser, friendXUIDs->GetView() );
IVectorView<WXM::UserPartyAssociation^>^ partyResults = nullptr;
create_task(getPartyAssociationsAsync).then([this,&partyResults](task<IVectorView<WXM::UserPartyAssociation^>^> t)
{
try
{
partyResults = t.get();
}
catch (Platform::COMException^ ex)
{
LogCommentWithError( L"getPartyAssociationsAsync failed", ex->HResult );
}
})
.wait();
if( partyResults == nullptr )
{
return -1;
}
if( partyResults->Size == 0 )
{
return 0;
}
// Filter these parties by whether we have permission to see them online
partyResults = FilterPartiesByPermission(primaryUserXboxLiveContext, partyResults);
// At this point, we have Party Ids for our friends. Now we need to get Party Views for each of these Ids.
LogComment("Parties found");
// Get party views for each of the user party associations that we have. These seem to be able to (individually) raise errors, so
// accumulate results into 2 matched vectors declared below so that we can ignore any broken UserPartyAssociations from now
vector<WXM::PartyView^> partyViewVector;
vector<WXM::UserPartyAssociation^> partyResultsVector;
vector<task<void>> taskVector;
for each(WXM::UserPartyAssociation^ remoteParty in partyResults)
{
auto asyncOp = WXM::Party::GetPartyViewByPartyIdAsync( primaryUser, remoteParty->PartyId );
task<WXM::PartyView^> asyncTask = create_task(asyncOp);
taskVector.push_back(asyncTask.then([this, &partyViewVector, &partyResultsVector, remoteParty] (task<WXM::PartyView^> t)
{
try
{
WXM::PartyView^ partyView = t.get();
if( partyView != nullptr )
{
app.DebugPrintf("Got party view\n");
EnterCriticalSection(&m_csPartyViewVector);
partyViewVector.push_back(partyView);
partyResultsVector.push_back(remoteParty);
LeaveCriticalSection(&m_csPartyViewVector);
}
}
catch ( Platform::COMException^ ex )
{
app.DebugPrintf("Getting party view error 0x%x\n",ex->HResult);
}
}));
}
for( auto it = taskVector.begin(); it != taskVector.end(); it++ )
{
it->wait();
}
if( partyViewVector.size() == 0 )
{
return 0;
}
// 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
vector<WXM::PartyView^> partyViewVectorFiltered;
vector<WXM::UserPartyAssociation^> partyResultsFiltered;
for( int i = 0; i < partyViewVector.size(); i++ )
{
WXM::PartyView^ partyView = partyViewVector[i];
if( partyView->Joinability == WXM::SessionJoinability::JoinableByFriends )
{
if( partyView->GameSession )
{
if( partyView->GameSession->ServiceConfigurationId == SERVICE_CONFIG_ID )
{
partyViewVectorFiltered.push_back( partyView );
partyResultsFiltered.push_back( partyResultsVector[i] );
}
}
}
}
// We now have matched vectors:
//
// partyResultsFiltered
// partyViewVectorFiltered
//
// and, from the party views, we can now attempt to get game sessions
vector<MXSM::MultiplayerSession^> sessionVector;
vector<WXM::PartyView^> partyViewVectorValid;
vector<WXM::UserPartyAssociation^> partyResultsValid;
for( int i = 0; i < partyViewVectorFiltered.size(); i++ )
{
WXM::PartyView^ partyView = partyViewVectorFiltered[i];
Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference^ sessionRef = ConvertToMicrosoftXboxServicesMultiplayerSessionReference(partyView->GameSession);
LogComment(L"Party view vector " + sessionRef->SessionName + L" " + partyResultsFiltered[i]->QueriedXboxUserIds->GetAt(0));
MXSM::MultiplayerSession^ session = nullptr;
auto asyncOp = primaryUserXboxLiveContext->MultiplayerService->GetCurrentSessionAsync( sessionRef );
create_task(asyncOp).then([&session] (task<MXSM::MultiplayerSession^> t)
{
try
{
session = t.get();
}
catch (Platform::COMException^ ex)
{
}
})
.wait();
if( session )
{
sessionVector.push_back(session);
partyViewVectorValid.push_back(partyView);
partyResultsValid.push_back(partyResultsFiltered[i]);
}
}
if( sessionVector.size() == 0 )
{
return 0;
}
// We now have matched vectors:
//
// partyResultsValid
// partyViewVectorValid
// sessionVector
// 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
// a session won't have any XUIDs to resolve, which would make GetUserProfilesAsync unhappy, so we'll only be creating a task
// 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)
vector<task<IVectorView<MXSS::XboxUserProfile^>^>> nameResolveTaskVector;
vector<IVectorView<MXSS::XboxUserProfile^>^> nameResolveVector;
vector<MXSM::MultiplayerSession^> newSessionVector;
vector<WXM::UserPartyAssociation^> newPartyVector;
for( int j = 0; j < sessionVector.size(); j++ )
{
MXSM::MultiplayerSession^ session = sessionVector[j];
IVector<Platform::String^>^ memberXUIDs = ref new Platform::Collections::Vector<Platform::String^>;
Windows::Data::Json::JsonArray^ roomSyncArray = nullptr;
try
{
Windows::Data::Json::JsonObject^ customJson = Windows::Data::Json::JsonObject::Parse(session->SessionProperties->SessionCustomPropertiesJson);
Windows::Data::Json::JsonValue^ customValue = customJson->GetNamedValue(L"RoomSyncData");
roomSyncArray = customValue->GetArray();
LogComment("Attempting to parse RoomSyncData");
for( int i = 0; i < roomSyncArray->Size; i++ )
{
LogComment(roomSyncArray->GetAt(i)->GetString());
}
}
catch (Platform::COMException^ ex)
{
LogCommentWithError( L"Custom RoomSyncData Parse/GetNamedValue failed", ex->HResult );
continue;
}
if( roomSyncArray && ( roomSyncArray->Size > 0 ) )
{
// 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:
//
// (1) If the host player (indicated by having a small id of 0) is our friend, use that
// (2) Otherwise use anyone who is our friend
// Default to true
bool friendsOfFriends = true;
int hostIndexFound = -1;
int friendIndexFound = -1;
friendsOfFriends = IsSessionFriendsOfFriends(session);
for( int i = 0; i < roomSyncArray->Size; i++ )
{
Platform::String^ roomSyncXuid = roomSyncArray->GetAt(i)->GetString();
// Determine if this player is a friend
bool isFriend = false;
for each( Platform::String^ friendXUID in friendXUIDs )
{
if( friendXUID == roomSyncXuid )
{
isFriend = true;
break;
}
}
bool isHost = i == 0;
// Store that what we found at this index if it is a friend, or a friend who is a host
if( isFriend && ( friendsOfFriends || isHost ) )
{
friendIndexFound = i;
if( isHost ) // Host is always in slot 0
{
hostIndexFound = i;
}
}
}
// Prefer to use index of host who is our friend
int bestIndex = friendIndexFound;
if( hostIndexFound != -1 )
{
bestIndex = hostIndexFound;
}
// Only consider if we have at least found one friend in the list of players
if( bestIndex != -1 )
{
// Compile list of XUIDs to resolve with our specially chosen player as entry 0, then the rest
memberXUIDs->Append(roomSyncArray->GetAt(bestIndex)->GetString());
for( int i = 0; i < roomSyncArray->Size; i++ )
{
if( i != bestIndex )
{
memberXUIDs->Append(roomSyncArray->GetAt(i)->GetString());
}
}
nameResolveTaskVector.push_back( create_task( primaryUserXboxLiveContext->ProfileService->GetUserProfilesAsync( memberXUIDs->GetView() ) ) );
newSessionVector.push_back(session);
newPartyVector.push_back(partyResultsValid[j]);
}
}
}
try
{
auto joinTask = when_all(begin(nameResolveTaskVector), end(nameResolveTaskVector) ).then([this, &nameResolveVector](vector<IVectorView<MXSS::XboxUserProfile^>^> results)
{
nameResolveVector = results;
})
.wait();
}
catch(Platform::COMException^ ex)
{
return -1;
}
// We now have matched vectors:
//
// newPartyVector - contains the party Ids that we'll need should we wish to join
// nameResolveVector - contains vectors views of the names of the members of the session each of these parties is in
// newSessionVector - contains the session information itself associated with each of the parties
// Construct the final result vector
m_sessionResultCount = newSessionVector.size();
m_sessionSearchResults = new SessionSearchResult[m_sessionResultCount];
for( int i = 0; i < m_sessionResultCount; i++ )
{
m_sessionSearchResults[i].m_partyId = newPartyVector[i]->PartyId->Data();
m_sessionSearchResults[i].m_sessionName = newSessionVector[i]->SessionReference->SessionName->Data();
for( int j = 0; j < nameResolveVector[i]->Size; j++ )
{
m_sessionSearchResults[i].m_playerNames[j] = nameResolveVector[i]->GetAt(j)->GameDisplayName->Data();
m_sessionSearchResults[i].m_playerXuids[j] = PlayerUID(nameResolveVector[i]->GetAt(j)->XboxUserId->Data());
}
m_sessionSearchResults[i].m_playerCount = nameResolveVector[i]->Size;
m_sessionSearchResults[i].m_usedSlotCount = newSessionVector[i]->Members->Size;
if( m_sessionSearchResults[i].m_usedSlotCount > MAX_ONLINE_PLAYER_COUNT )
{
// Don't think this could ever happen, but no harm in checking
m_sessionSearchResults[i].m_usedSlotCount = MAX_ONLINE_PLAYER_COUNT;
}
for( int j = 0; j < m_sessionSearchResults[i].m_usedSlotCount; j++ )
{
m_sessionSearchResults[i].m_sessionXuids[j] = wstring( newSessionVector[i]->Members->GetAt(j)->XboxUserId->Data() );
}
m_sessionSearchResults[i].m_extData = malloc( sizeof(GameSessionData) );
memset( m_sessionSearchResults[i].m_extData, 0, sizeof(GameSessionData) );
GetGameSessionData(newSessionVector[i], m_sessionSearchResults[i].m_extData);
}
return 0;
}
// Filters list of parties based on online presence permission (whether the friend is set to invisible or not)
IVectorView<WXM::UserPartyAssociation^>^ DQRNetworkManager::FilterPartiesByPermission(MXS::XboxLiveContext ^context, IVectorView<WXM::UserPartyAssociation^>^ partyResults)
{
Platform::Collections::Vector<WXM::UserPartyAssociation^>^ filteredPartyResults = ref new Platform::Collections::Vector<WXM::UserPartyAssociation^>();
// List of permissions we want
auto permissionIds = ref new Platform::Collections::Vector<Platform::String^>(1, ref new Platform::String(L"ViewTargetPresence"));
// List of target users
auto targetXboxUserIds = ref new Platform::Collections::Vector<Platform::String^>();
for (int i = 0; i < partyResults->Size; i++)
{
assert(partyResults->GetAt(i)->QueriedXboxUserIds->Size > 0);
targetXboxUserIds->Append( partyResults->GetAt(i)->QueriedXboxUserIds->GetAt(0) );
}
// Check
auto checkPermissionsAsync = context->PrivacyService->CheckMultiplePermissionsWithMultipleTargetUsersAsync(permissionIds->GetView(), targetXboxUserIds->GetView());
create_task(checkPermissionsAsync).then([&partyResults, &filteredPartyResults](task<IVectorView<MXS::Privacy::MultiplePermissionsCheckResult^>^> t)
{
try
{
auto results = t.get();
// For each party, check to see if we have permission for the user
for (int i = 0; i < partyResults->Size; i++)
{
// For each permissions result
for (int j = 0; j < results->Size; j++)
{
auto result = results->GetAt(j);
// If allowed to see this user AND it's the same user, add the party to the just
if ((result->Items->GetAt(0)->IsAllowed) && (partyResults->GetAt(i)->QueriedXboxUserIds->GetAt(0) == result->XboxUserId))
{
filteredPartyResults->Append(partyResults->GetAt(i));
break;
}
}
}
}
catch (Platform::COMException^ ex)
{
LogCommentWithError( L"CheckMultiplePermissionsWithMultipleTargetUsersAsync failed", ex->HResult );
}
})
.wait();
app.DebugPrintf("DQRNetworkManager::FilterPartiesByPermission: Removed %i parties because of online presence permissions\n", partyResults->Size - filteredPartyResults->Size);
return filteredPartyResults->GetView();
}
// Get all friends (list of XUIDs) syncronously from the service (slow, may take 300ms+), returns empty list if something goes wrong
Platform::Collections::Vector<Platform::String^>^ DQRNetworkManager::GetFriends()
{
auto friends = ref new Platform::Collections::Vector<Platform::String^>;
auto primaryUser = ProfileManager.GetUser(0);
if (primaryUser == nullptr)
{
// Return empty
return friends;
}
auto xboxLiveContext = ref new MXS::XboxLiveContext(primaryUser);
// Request ALL friends because there's no other way to check friendships without using the REST API
auto getSocialRelationshipsAsync = xboxLiveContext->SocialService->GetSocialRelationshipsAsync(MXSS::SocialRelationship::All, 0, 1100);
MXSS::XboxSocialRelationshipResult^ socialRelationshipResult = nullptr;
// First get our friends list (people we follow who may or may not follow us back)
Concurrency::create_task(getSocialRelationshipsAsync).then([&socialRelationshipResult](Concurrency::task<MXSS::XboxSocialRelationshipResult^> t)
{
try
{
socialRelationshipResult = t.get();
}
catch (Platform::COMException^ ex)
{
app.DebugPrintf("DQRNetworkManager::GetFriends: GetSocialRelationshipsAsync failed ()\n", ex->HResult);
}
})
.wait();
if (socialRelationshipResult == nullptr)
{
// Return empty
return friends;
}
app.DebugPrintf("DQRNetworkManager::GetFriends: Retrieved %i relationships\n", socialRelationshipResult->TotalCount);
// Now construct a vector of these users, that follow us back - these are our "friends"
for( int i = 0; i < socialRelationshipResult->TotalCount; i++ )
{
MXSS::XboxSocialRelationship^ relationship = socialRelationshipResult->Items->GetAt(i);
if(relationship->IsFollowingCaller)
{
app.DebugPrintf("DQRNetworkManager::GetFriends: Found friend \"%ls\"\n", relationship->XboxUserId->Data());
friends->Append(relationship->XboxUserId);
}
}
app.DebugPrintf("DQRNetworkManager::GetFriends: Found %i 2-way friendships\n", friends->Size);
return friends;
}
// If data for game settings exists returns FriendsOfFriends value, otherwise returns true
bool DQRNetworkManager::IsSessionFriendsOfFriends(MXSM::MultiplayerSession^ session)
{
// Default to true, don't want to incorrectly prevent joining
bool friendsOfFriends = true;
// We retrieve the game session data later too, shouldn't really duplicate this
void *gameSessionData = malloc( sizeof(GameSessionData));
memset(gameSessionData, 0, sizeof(GameSessionData));
bool result = GetGameSessionData(session, gameSessionData);
if (result)
{
friendsOfFriends = app.GetGameHostOption(((GameSessionData *)gameSessionData)->m_uiGameHostSettings, eGameHostOption_FriendsOfFriends);
}
free(gameSessionData);
return friendsOfFriends;
}
// Parses custom json data from session and populates game session data param, return true if parse succeeded
bool DQRNetworkManager::GetGameSessionData(MXSM::MultiplayerSession^ session, void *gameSessionData)
{
Platform::String ^gameSessionDataJson = session->SessionProperties->SessionCustomPropertiesJson;
if( gameSessionDataJson )
{
try
{
Windows::Data::Json::JsonObject^ customParam = Windows::Data::Json::JsonObject::Parse(gameSessionDataJson);
Windows::Data::Json::JsonValue^ customValue = customParam->GetNamedValue(L"GameSessionData");
Platform::String ^customValueString = customValue->GetString();
if( customValueString )
{
base64_decode( customValueString, (unsigned char *)gameSessionData, sizeof(GameSessionData) );
return true;
}
}
catch (Platform::COMException^ ex)
{
LogCommentWithError( L"Custom GameSessionData parameter Parse/GetNamedValue failed", ex->HResult );
}
}
return false;
}

View File

@@ -0,0 +1,303 @@
#include "stdafx.h"
#include "DQRNetworkManager.h"
#include "PartyController.h"
#include <collection.h>
#include <ppltasks.h>
#include <ws2tcpip.h>
#include "..\Minecraft.World\StringHelpers.h"
#include "base64.h"
#ifdef _DURANGO
#include "..\Minecraft.World\DurangoStats.h"
#endif
#include "ChatIntegrationLayer.h"
using namespace Concurrency;
using namespace Windows::Foundation::Collections;
void DQRNetworkManager::LogComment( Platform::String^ strText )
{
#ifndef _CONTENT_PACKAGE
static int64_t firstTime = 0;
wchar_t buf[64];
int64_t currentTime = System::currentTimeMillis();
if( firstTime != 0 )
{
_i64tow_s(currentTime - firstTime, buf, 64, 10);
OutputDebugString(buf);
OutputDebugString(L" ms: ");
}
else
{
firstTime = currentTime;
}
OutputDebugString(strText->Data());
OutputDebugString(L"\n");
#endif
}
void DQRNetworkManager::LogCommentFormat( LPCWSTR strMsg, ... )
{
WCHAR strBuffer[2048];
va_list args;
va_start(args, strMsg);
_vsnwprintf_s( strBuffer, 2048, _TRUNCATE, strMsg, args );
strBuffer[2047] = L'\0';
va_end(args);
LogComment(ref new Platform::String(strBuffer));
}
void DQRNetworkManager::LogCommentWithError( Platform::String^ strTest, HRESULT hr )
{
Platform::String^ final = strTest + GetErrorString(hr);
LogComment(final);
}
Platform::String^ DQRNetworkManager::GetErrorString( HRESULT hr )
{
Platform::String^ str = FormatString(L" %s [0x%0.8x]", ConvertHResultToErrorName(hr)->Data(), hr );
return str;
}
Platform::String^ DQRNetworkManager::FormatString( LPCWSTR strMsg, ... )
{
WCHAR strBuffer[2048];
va_list args;
va_start(args, strMsg);
_vsnwprintf_s( strBuffer, 2048, _TRUNCATE, strMsg, args );
strBuffer[2047] = L'\0';
va_end(args);
Platform::String^ str = ref new Platform::String(strBuffer);
return str;
}
Platform::String^ DQRNetworkManager::ConvertHResultToErrorName( HRESULT hr )
{
switch( hr )
{
// Generic errors
case S_OK: return L"S_OK";
case S_FALSE: return L"S_FALSE";
case E_OUTOFMEMORY: return L"E_OUTOFMEMORY";
case E_ACCESSDENIED: return L"E_ACCESSDENIED";
case E_INVALIDARG: return L"E_INVALIDARG";
case E_UNEXPECTED: return L"E_UNEXPECTED";
case E_ABORT: return L"E_ABORT";
case E_FAIL: return L"E_FAIL";
case E_NOTIMPL: return L"E_NOTIMPL";
case E_ILLEGAL_METHOD_CALL: return L"E_ILLEGAL_METHOD_CALL";
// Authentication specific errors
case 0x87DD0003: return L"AM_E_XASD_UNEXPECTED";
case 0x87DD0004: return L"AM_E_XASU_UNEXPECTED";
case 0x87DD0005: return L"AM_E_XAST_UNEXPECTED";
case 0x87DD0006: return L"AM_E_XSTS_UNEXPECTED";
case 0x87DD0007: return L"AM_E_XDEVICE_UNEXPECTED";
case 0x87DD0008: return L"AM_E_DEVMODE_NOT_AUTHORIZED";
case 0x87DD0009: return L"AM_E_NOT_AUTHORIZED";
case 0x87DD000A: return L"AM_E_FORBIDDEN";
case 0x87DD000B: return L"AM_E_UNKNOWN_TARGET";
case 0x87DD000C: return L"AM_E_INVALID_NSAL_DATA";
case 0x87DD000D: return L"AM_E_TITLE_NOT_AUTHENTICATED";
case 0x87DD000E: return L"AM_E_TITLE_NOT_AUTHORIZED";
case 0x87DD000F: return L"AM_E_DEVICE_NOT_AUTHENTICATED";
case 0x87DD0010: return L"AM_E_INVALID_USER_INDEX";
case 0x87DD0011: return L"AM_E_USER_HASH_MISSING";
case 0x87DD0012: return L"AM_E_ACTOR_NOT_SPECIFIED";
case 0x87DD0013: return L"AM_E_USER_NOT_FOUND";
case 0x87DD0014: return L"AM_E_INVALID_SUBTOKEN";
case 0x87DD0015: return L"AM_E_INVALID_ENVIRONMENT";
case 0x87DD0016: return L"AM_E_XASD_TIMEOUT";
case 0x87DD0017: return L"AM_E_XASU_TIMEOUT";
case 0x87DD0018: return L"AM_E_XAST_TIMEOUT";
case 0x87DD0019: return L"AM_E_XSTS_TIMEOUT";
case 0x8015DC00: return L"XO_E_DEVMODE_NOT_AUTHORIZED";
case 0x8015DC01: return L"XO_E_SYSTEM_UPDATE_REQUIRED";
case 0x8015DC02: return L"XO_E_CONTENT_UPDATE_REQUIRED";
case 0x8015DC03: return L"XO_E_ENFORCEMENT_BAN";
case 0x8015DC04: return L"XO_E_THIRD_PARTY_BAN";
case 0x8015DC05: return L"XO_E_ACCOUNT_PARENTALLY_RESTRICTED";
case 0x8015DC06: return L"XO_E_DEVICE_SUBSCRIPTION_NOT_ACTIVATED";
case 0x8015DC08: return L"XO_E_ACCOUNT_BILLING_MAINTENANCE_REQUIRED";
case 0x8015DC09: return L"XO_E_ACCOUNT_CREATION_REQUIRED";
case 0x8015DC0A: return L"XO_E_ACCOUNT_TERMS_OF_USE_NOT_ACCEPTED";
case 0x8015DC0B: return L"XO_E_ACCOUNT_COUNTRY_NOT_AUTHORIZED";
case 0x8015DC0C: return L"XO_E_ACCOUNT_AGE_VERIFICATION_REQUIRED";
case 0x8015DC0D: return L"XO_E_ACCOUNT_CURFEW";
case 0x8015DC0E: return L"XO_E_ACCOUNT_ZEST_MAINTENANCE_REQUIRED";
case 0x8015DC0F: return L"XO_E_ACCOUNT_CSV_TRANSITION_REQUIRED";
case 0x8015DC10: return L"XO_E_ACCOUNT_MAINTENANCE_REQUIRED";
case 0x8015DC11: return L"XO_E_ACCOUNT_TYPE_NOT_ALLOWED";
case 0x8015DC12: return L"XO_E_CONTENT_ISOLATION";
case 0x8015DC13: return L"XO_E_ACCOUNT_NAME_CHANGE_REQUIRED";
case 0x8015DC14: return L"XO_E_DEVICE_CHALLENGE_REQUIRED";
case 0x8015DC20: return L"XO_E_EXPIRED_DEVICE_TOKEN";
case 0x8015DC21: return L"XO_E_EXPIRED_TITLE_TOKEN";
case 0x8015DC22: return L"XO_E_EXPIRED_USER_TOKEN";
case 0x8015DC23: return L"XO_E_INVALID_DEVICE_TOKEN";
case 0x8015DC24: return L"XO_E_INVALID_TITLE_TOKEN";
case 0x8015DC25: return L"XO_E_INVALID_USER_TOKEN";
// winsock errors
case MAKE_HRESULT(1,7,WSAEWOULDBLOCK) : return L"WSAEWOULDBLOCK";
case MAKE_HRESULT(1,7,WSAEINPROGRESS) : return L"WSAEINPROGRESS";
case MAKE_HRESULT(1,7,WSAEALREADY) : return L"WSAEALREADY";
case MAKE_HRESULT(1,7,WSAENOTSOCK) : return L"WSAENOTSOCK";
case MAKE_HRESULT(1,7,WSAEDESTADDRREQ) : return L"WSAEDESTADDRREQ";
case MAKE_HRESULT(1,7,WSAEMSGSIZE) : return L"WSAEMSGSIZE";
case MAKE_HRESULT(1,7,WSAEPROTOTYPE) : return L"WSAEPROTOTYPE";
case MAKE_HRESULT(1,7,WSAENOPROTOOPT) : return L"WSAENOPROTOOPT";
case MAKE_HRESULT(1,7,WSAEPROTONOSUPPORT) : return L"WSAEPROTONOSUPPORT";
case MAKE_HRESULT(1,7,WSAESOCKTNOSUPPORT) : return L"WSAESOCKTNOSUPPORT";
case MAKE_HRESULT(1,7,WSAEOPNOTSUPP) : return L"WSAEOPNOTSUPP";
case MAKE_HRESULT(1,7,WSAEPFNOSUPPORT) : return L"WSAEPFNOSUPPORT";
case MAKE_HRESULT(1,7,WSAEAFNOSUPPORT) : return L"WSAEAFNOSUPPORT";
case MAKE_HRESULT(1,7,WSAEADDRINUSE) : return L"WSAEADDRINUSE";
case MAKE_HRESULT(1,7,WSAEADDRNOTAVAIL) : return L"WSAEADDRNOTAVAIL";
case MAKE_HRESULT(1,7,WSAENETDOWN) : return L"WSAENETDOWN";
case MAKE_HRESULT(1,7,WSAENETUNREACH) : return L"WSAENETUNREACH";
case MAKE_HRESULT(1,7,WSAENETRESET) : return L"WSAENETRESET";
case MAKE_HRESULT(1,7,WSAECONNABORTED) : return L"WSAECONNABORTED";
case MAKE_HRESULT(1,7,WSAECONNRESET) : return L"WSAECONNRESET";
case MAKE_HRESULT(1,7,WSAENOBUFS) : return L"WSAENOBUFS";
case MAKE_HRESULT(1,7,WSAEISCONN) : return L"WSAEISCONN";
case MAKE_HRESULT(1,7,WSAENOTCONN) : return L"WSAENOTCONN";
case MAKE_HRESULT(1,7,WSAESHUTDOWN) : return L"WSAESHUTDOWN";
case MAKE_HRESULT(1,7,WSAETOOMANYREFS) : return L"WSAETOOMANYREFS";
case MAKE_HRESULT(1,7,WSAETIMEDOUT) : return L"WSAETIMEDOUT";
case MAKE_HRESULT(1,7,WSAECONNREFUSED) : return L"WSAECONNREFUSED";
case MAKE_HRESULT(1,7,WSAELOOP) : return L"WSAELOOP";
case MAKE_HRESULT(1,7,WSAENAMETOOLONG) : return L"WSAENAMETOOLONG";
case MAKE_HRESULT(1,7,WSAEHOSTDOWN) : return L"WSAEHOSTDOWN";
case MAKE_HRESULT(1,7,WSAEHOSTUNREACH) : return L"WSAEHOSTUNREACH";
case MAKE_HRESULT(1,7,WSAENOTEMPTY) : return L"WSAENOTEMPTY";
case MAKE_HRESULT(1,7,WSAEPROCLIM) : return L"WSAEPROCLIM";
case MAKE_HRESULT(1,7,WSAEUSERS) : return L"WSAEUSERS";
case MAKE_HRESULT(1,7,WSAEDQUOT) : return L"WSAEDQUOT";
case MAKE_HRESULT(1,7,WSAESTALE) : return L"WSAESTALE";
case MAKE_HRESULT(1,7,WSAEREMOTE) : return L"WSAEREMOTE";
case MAKE_HRESULT(1,7,WSASYSNOTREADY) : return L"WSASYSNOTREADY";
case MAKE_HRESULT(1,7,WSAVERNOTSUPPORTED) : return L"WSAVERNOTSUPPORTED";
case MAKE_HRESULT(1,7,WSANOTINITIALISED) : return L"WSANOTINITIALISED";
case MAKE_HRESULT(1,7,WSAEDISCON) : return L"WSAEDISCON";
case MAKE_HRESULT(1,7,WSAENOMORE) : return L"WSAENOMORE";
case MAKE_HRESULT(1,7,WSAECANCELLED) : return L"WSAECANCELLED";
case MAKE_HRESULT(1,7,WSAEINVALIDPROCTABLE) : return L"WSAEINVALIDPROCTABLE";
case MAKE_HRESULT(1,7,WSAEINVALIDPROVIDER) : return L"WSAEINVALIDPROVIDER";
case MAKE_HRESULT(1,7,WSAEPROVIDERFAILEDINIT) : return L"WSAEPROVIDERFAILEDINIT";
case MAKE_HRESULT(1,7,WSASYSCALLFAILURE) : return L"WSASYSCALLFAILURE";
case MAKE_HRESULT(1,7,WSASERVICE_NOT_FOUND) : return L"WSASERVICE_NOT_FOUND";
case MAKE_HRESULT(1,7,WSATYPE_NOT_FOUND) : return L"WSATYPE_NOT_FOUND";
case MAKE_HRESULT(1,7,WSA_E_NO_MORE) : return L"WSA_E_NO_MORE";
case MAKE_HRESULT(1,7,WSA_E_CANCELLED) : return L"WSA_E_CANCELLED";
case MAKE_HRESULT(1,7,WSAEREFUSED) : return L"WSAEREFUSED";
case MAKE_HRESULT(1,7,WSAHOST_NOT_FOUND) : return L"WSAHOST_NOT_FOUND";
case MAKE_HRESULT(1,7,WSATRY_AGAIN) : return L"WSATRY_AGAIN";
case MAKE_HRESULT(1,7,WSANO_RECOVERY) : return L"WSANO_RECOVERY";
case MAKE_HRESULT(1,7,WSANO_DATA) : return L"WSANO_DATA";
case MAKE_HRESULT(1,7,WSA_QOS_RECEIVERS) : return L"WSA_QOS_RECEIVERS";
case MAKE_HRESULT(1,7,WSA_QOS_SENDERS) : return L"WSA_QOS_SENDERS";
case MAKE_HRESULT(1,7,WSA_QOS_NO_SENDERS) : return L"WSA_QOS_NO_SENDERS";
case MAKE_HRESULT(1,7,WSA_QOS_NO_RECEIVERS) : return L"WSA_QOS_NO_RECEIVERS";
case MAKE_HRESULT(1,7,WSA_QOS_REQUEST_CONFIRMED) : return L"WSA_QOS_REQUEST_CONFIRMED";
case MAKE_HRESULT(1,7,WSA_QOS_ADMISSION_FAILURE) : return L"WSA_QOS_ADMISSION_FAILURE";
case MAKE_HRESULT(1,7,WSA_QOS_POLICY_FAILURE) : return L"WSA_QOS_POLICY_FAILURE";
case MAKE_HRESULT(1,7,WSA_QOS_BAD_STYLE) : return L"WSA_QOS_BAD_STYLE";
case MAKE_HRESULT(1,7,WSA_QOS_BAD_OBJECT) : return L"WSA_QOS_BAD_OBJECT";
case MAKE_HRESULT(1,7,WSA_QOS_TRAFFIC_CTRL_ERROR) : return L"WSA_QOS_TRAFFIC_CTRL_ERROR";
case MAKE_HRESULT(1,7,WSA_QOS_GENERIC_ERROR) : return L"WSA_QOS_GENERIC_ERROR";
case MAKE_HRESULT(1,7,WSA_QOS_ESERVICETYPE) : return L"WSA_QOS_ESERVICETYPE";
case MAKE_HRESULT(1,7,WSA_QOS_EFLOWSPEC) : return L"WSA_QOS_EFLOWSPEC";
case MAKE_HRESULT(1,7,WSA_QOS_EPROVSPECBUF) : return L"WSA_QOS_EPROVSPECBUF";
case MAKE_HRESULT(1,7,WSA_QOS_EFILTERSTYLE) : return L"WSA_QOS_EFILTERSTYLE";
case MAKE_HRESULT(1,7,WSA_QOS_EFILTERTYPE) : return L"WSA_QOS_EFILTERTYPE";
case MAKE_HRESULT(1,7,WSA_QOS_EFILTERCOUNT) : return L"WSA_QOS_EFILTERCOUNT";
case MAKE_HRESULT(1,7,WSA_QOS_EOBJLENGTH) : return L"WSA_QOS_EOBJLENGTH";
case MAKE_HRESULT(1,7,WSA_QOS_EFLOWCOUNT) : return L"WSA_QOS_EFLOWCOUNT";
case MAKE_HRESULT(1,7,WSA_QOS_EUNKOWNPSOBJ) : return L"WSA_QOS_EUNKOWNPSOBJ";
case MAKE_HRESULT(1,7,WSA_QOS_EPOLICYOBJ) : return L"WSA_QOS_EPOLICYOBJ";
case MAKE_HRESULT(1,7,WSA_QOS_EFLOWDESC) : return L"WSA_QOS_EFLOWDESC";
case MAKE_HRESULT(1,7,WSA_QOS_EPSFLOWSPEC) : return L"WSA_QOS_EPSFLOWSPEC";
case MAKE_HRESULT(1,7,WSA_QOS_EPSFILTERSPEC) : return L"WSA_QOS_EPSFILTERSPEC";
case MAKE_HRESULT(1,7,WSA_QOS_ESDMODEOBJ) : return L"WSA_QOS_ESDMODEOBJ";
case MAKE_HRESULT(1,7,WSA_QOS_ESHAPERATEOBJ) : return L"WSA_QOS_ESHAPERATEOBJ";
case MAKE_HRESULT(1,7,WSA_QOS_RESERVED_PETYPE) : return L"WSA_QOS_RESERVED_PETYPE";
// HTTP specific errors
case WEB_E_UNSUPPORTED_FORMAT: return L"WEB_E_UNSUPPORTED_FORMAT";
case WEB_E_INVALID_XML: return L"WEB_E_INVALID_XML";
case WEB_E_MISSING_REQUIRED_ELEMENT: return L"WEB_E_MISSING_REQUIRED_ELEMENT";
case WEB_E_MISSING_REQUIRED_ATTRIBUTE: return L"WEB_E_MISSING_REQUIRED_ATTRIBUTE";
case WEB_E_UNEXPECTED_CONTENT: return L"WEB_E_UNEXPECTED_CONTENT";
case WEB_E_RESOURCE_TOO_LARGE: return L"WEB_E_RESOURCE_TOO_LARGE";
case WEB_E_INVALID_JSON_STRING: return L"WEB_E_INVALID_JSON_STRING";
case WEB_E_INVALID_JSON_NUMBER: return L"WEB_E_INVALID_JSON_NUMBER";
case WEB_E_JSON_VALUE_NOT_FOUND: return L"WEB_E_JSON_VALUE_NOT_FOUND";
case HTTP_E_STATUS_UNEXPECTED: return L"HTTP_E_STATUS_UNEXPECTED";
case HTTP_E_STATUS_UNEXPECTED_REDIRECTION: return L"HTTP_E_STATUS_UNEXPECTED_REDIRECTION";
case HTTP_E_STATUS_UNEXPECTED_CLIENT_ERROR: return L"HTTP_E_STATUS_UNEXPECTED_CLIENT_ERROR";
case HTTP_E_STATUS_UNEXPECTED_SERVER_ERROR: return L"HTTP_E_STATUS_UNEXPECTED_SERVER_ERROR";
case HTTP_E_STATUS_AMBIGUOUS: return L"HTTP_E_STATUS_AMBIGUOUS";
case HTTP_E_STATUS_MOVED: return L"HTTP_E_STATUS_MOVED";
case HTTP_E_STATUS_REDIRECT: return L"HTTP_E_STATUS_REDIRECT";
case HTTP_E_STATUS_REDIRECT_METHOD: return L"HTTP_E_STATUS_REDIRECT_METHOD";
case HTTP_E_STATUS_NOT_MODIFIED: return L"HTTP_E_STATUS_NOT_MODIFIED";
case HTTP_E_STATUS_USE_PROXY: return L"HTTP_E_STATUS_USE_PROXY";
case HTTP_E_STATUS_REDIRECT_KEEP_VERB: return L"HTTP_E_STATUS_REDIRECT_KEEP_VERB";
case HTTP_E_STATUS_BAD_REQUEST: return L"HTTP_E_STATUS_BAD_REQUEST";
case HTTP_E_STATUS_DENIED: return L"HTTP_E_STATUS_DENIED";
case HTTP_E_STATUS_PAYMENT_REQ: return L"HTTP_E_STATUS_PAYMENT_REQ";
case HTTP_E_STATUS_FORBIDDEN: return L"HTTP_E_STATUS_FORBIDDEN";
case HTTP_E_STATUS_NOT_FOUND: return L"HTTP_E_STATUS_NOT_FOUND";
case HTTP_E_STATUS_BAD_METHOD: return L"HTTP_E_STATUS_BAD_METHOD";
case HTTP_E_STATUS_NONE_ACCEPTABLE: return L"HTTP_E_STATUS_NONE_ACCEPTABLE";
case HTTP_E_STATUS_PROXY_AUTH_REQ: return L"HTTP_E_STATUS_PROXY_AUTH_REQ";
case HTTP_E_STATUS_REQUEST_TIMEOUT: return L"HTTP_E_STATUS_REQUEST_TIMEOUT";
case HTTP_E_STATUS_CONFLICT: return L"HTTP_E_STATUS_CONFLICT";
case HTTP_E_STATUS_GONE: return L"HTTP_E_STATUS_GONE";
case HTTP_E_STATUS_LENGTH_REQUIRED: return L"HTTP_E_STATUS_LENGTH_REQUIRED";
case HTTP_E_STATUS_PRECOND_FAILED: return L"HTTP_E_STATUS_PRECOND_FAILED";
case HTTP_E_STATUS_REQUEST_TOO_LARGE: return L"HTTP_E_STATUS_REQUEST_TOO_LARGE";
case HTTP_E_STATUS_URI_TOO_LONG: return L"HTTP_E_STATUS_URI_TOO_LONG";
case HTTP_E_STATUS_UNSUPPORTED_MEDIA: return L"HTTP_E_STATUS_UNSUPPORTED_MEDIA";
case HTTP_E_STATUS_RANGE_NOT_SATISFIABLE: return L"HTTP_E_STATUS_RANGE_NOT_SATISFIABLE";
case HTTP_E_STATUS_EXPECTATION_FAILED: return L"HTTP_E_STATUS_EXPECTATION_FAILED";
case HTTP_E_STATUS_SERVER_ERROR: return L"HTTP_E_STATUS_SERVER_ERROR";
case HTTP_E_STATUS_NOT_SUPPORTED: return L"HTTP_E_STATUS_NOT_SUPPORTED";
case HTTP_E_STATUS_BAD_GATEWAY: return L"HTTP_E_STATUS_BAD_GATEWAY";
case HTTP_E_STATUS_SERVICE_UNAVAIL: return L"HTTP_E_STATUS_SERVICE_UNAVAIL";
case HTTP_E_STATUS_GATEWAY_TIMEOUT: return L"HTTP_E_STATUS_GATEWAY_TIMEOUT";
case HTTP_E_STATUS_VERSION_NOT_SUP: return L"HTTP_E_STATUS_VERSION_NOT_SUP";
// WinINet specific errors
case INET_E_INVALID_URL: return L"INET_E_INVALID_URL";
case INET_E_NO_SESSION: return L"INET_E_NO_SESSION";
case INET_E_CANNOT_CONNECT: return L"INET_E_CANNOT_CONNECT";
case INET_E_RESOURCE_NOT_FOUND: return L"INET_E_RESOURCE_NOT_FOUND";
case INET_E_OBJECT_NOT_FOUND: return L"INET_E_OBJECT_NOT_FOUND";
case INET_E_DATA_NOT_AVAILABLE: return L"INET_E_DATA_NOT_AVAILABLE";
case INET_E_DOWNLOAD_FAILURE: return L"INET_E_DOWNLOAD_FAILURE";
case INET_E_AUTHENTICATION_REQUIRED: return L"INET_E_AUTHENTICATION_REQUIRED";
case INET_E_NO_VALID_MEDIA: return L"INET_E_NO_VALID_MEDIA";
case INET_E_CONNECTION_TIMEOUT: return L"INET_E_CONNECTION_TIMEOUT";
case INET_E_INVALID_REQUEST: return L"INET_E_INVALID_REQUEST";
case INET_E_UNKNOWN_PROTOCOL: return L"INET_E_UNKNOWN_PROTOCOL";
case INET_E_SECURITY_PROBLEM: return L"INET_E_SECURITY_PROBLEM";
case INET_E_CANNOT_LOAD_DATA: return L"INET_E_CANNOT_LOAD_DATA";
case INET_E_CANNOT_INSTANTIATE_OBJECT: return L"INET_E_CANNOT_INSTANTIATE_OBJECT";
case INET_E_INVALID_CERTIFICATE: return L"INET_E_INVALID_CERTIFICATE";
case INET_E_REDIRECT_FAILED: return L"INET_E_REDIRECT_FAILED";
case INET_E_REDIRECT_TO_DIR: return L"INET_E_REDIRECT_TO_DIR";
}
return L"";
}

View File

@@ -0,0 +1,409 @@
#include "stdafx.h"
#include "DQRNetworkManager.h"
#include "PartyController.h"
#include <collection.h>
#include <ppltasks.h>
#include <ws2tcpip.h>
#include "..\Minecraft.World\StringHelpers.h"
#include "base64.h"
#ifdef _DURANGO
#include "..\Minecraft.World\DurangoStats.h"
#endif
#include "ChatIntegrationLayer.h"
using namespace Concurrency;
using namespace Windows::Foundation::Collections;
// This method is called when bytes have been received that are to be passed on to the game itself. The data is associated with a small id so we can specify which network player
// that it was received for.
void DQRNetworkManager::BytesReceived(int smallId, BYTE *bytes, int byteCount)
{
DQRNetworkPlayer *host = GetPlayerBySmallId(m_hostSmallId);
DQRNetworkPlayer *client = GetPlayerBySmallId(smallId);
if( ( host == NULL ) || ( client == NULL ) )
{
return;
}
if( m_isHosting )
{
m_listener->HandleDataReceived(client, host, bytes, byteCount );
}
else
{
m_listener->HandleDataReceived(host, client, bytes, byteCount );
}
// app.DebugPrintf("%d bytes received: %s\n", byteCount, bytes);
}
// This method is called when network data is received, that is to be processed by the DQRNetworkManager itself. This is for handling internal
// updates such as assigning & unassigning of small Ids, transmission of the table of players currently in the session etc.
// Processing of these things is handled as a state machine so that we can receive a message split over more than one call to this method should
// the underlying communcation layer split data up somehow.
void DQRNetworkManager::BytesReceivedInternal(DQRConnectionInfo *connectionInfo, unsigned int sessionAddress, BYTE *bytes, int byteCount)
{
BYTE *pNextByte = bytes;
BYTE *pEndByte = pNextByte + byteCount;
do
{
BYTE byte = *pNextByte;
switch( connectionInfo->m_internalDataState )
{
case DQRConnectionInfo::ConnectionState_InternalHeaderByte:
switch( byte )
{
case DQR_INTERNAL_ASSIGN_SMALL_IDS:
connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalAssignSmallIdMask;
break;
case DQR_INTERNAL_UNASSIGN_SMALL_ID:
// Host only
if( connectionInfo->m_channelActive[connectionInfo->m_currentChannel] )
{
int smallId = connectionInfo->m_smallId[connectionInfo->m_currentChannel];
connectionInfo->m_channelActive[connectionInfo->m_currentChannel] = false;
m_sessionAddressFromSmallId[smallId] = 0;
DQRNetworkPlayer *pPlayer = GetPlayerBySmallId(smallId);
if( pPlayer )
{
RemoveRoomSyncPlayer(pPlayer);
SendRoomSyncInfo();
}
}
break;
case DQR_INTERNAL_PLAYER_TABLE:
connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalRoomSyncData;
connectionInfo->m_pucRoomSyncData = new unsigned char[4];
connectionInfo->m_roomSyncDataBytesToRead = 0;
connectionInfo->m_roomSyncDataBytesRead = 0;
break;
case DQR_INTERNAL_ADD_PLAYER_FAILED:
connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalAddPlayerFailedData;
connectionInfo->m_pucAddFailedPlayerData = new unsigned char[4];
connectionInfo->m_addFailedPlayerDataBytesToRead = 0;
connectionInfo->m_addFailedPlayerDataBytesRead = 0;
break;
default:
break;
}
pNextByte++;
break;
case DQRConnectionInfo::ConnectionState_InternalAssignSmallIdMask:
// Up to 4 smallIds are assigned at once, with the ones that are being assigned dictated by a mask byte which is passed in first.
// The small Ids themselves follow, always 4 bytes, and any that are masked as being assigned are processed, the other bytes ignored.
// In order work around a bug with the networking library, this particular packet (being the first this that is sent from a client)
// is at first sent unreliably, with retries, until a message is received back to the client, or it times out. We therefore have to be able
// to handle (and ignore) this being received more than once
DQRNetworkManager::LogCommentFormat(L"Small Ids being received");
connectionInfo->m_smallIdReadMask = byte;
// Create a uniquely allocated byte to which names have been resolved, as another one of these packets could be received whilst that asyncronous process is going o n
connectionInfo->m_pucsmallIdReadMaskResolved = new unsigned char;
*connectionInfo->m_pucsmallIdReadMaskResolved = 0;
connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalAssignSmallId0;
connectionInfo->m_initialPacketReceived = true;
pNextByte++;
break;
case DQRConnectionInfo::ConnectionState_InternalAssignSmallId0:
case DQRConnectionInfo::ConnectionState_InternalAssignSmallId1:
case DQRConnectionInfo::ConnectionState_InternalAssignSmallId2:
case DQRConnectionInfo::ConnectionState_InternalAssignSmallId3:
{
int channel = ((int)connectionInfo->m_internalDataState) - DQRConnectionInfo::ConnectionState_InternalAssignSmallId0;
if( ( connectionInfo->m_smallIdReadMask & ( 1 << channel ) ) && ( !connectionInfo->m_channelActive[channel] ) )
{
// HOST ONLY
// Store the small Id that is associated with this send channel. In order work around a bug with the networking library, this particular packet
// (being the first this that is sent from a client) is sent unreliably, with retries, until a message is received back to the client, or it times out.
// We therefore have to be able to handle (and ignore) this being received more than once - hence the check of the bool above.
// At this point, the connection is considered properly active from the point of view of the host.
int sessionIndex = GetSessionIndexForSmallId(byte);
if( sessionIndex != -1 )
{
connectionInfo->m_channelActive[channel] = true;
connectionInfo->m_smallId[channel] = byte;
m_sessionAddressFromSmallId[byte] = sessionAddress;
m_channelFromSmallId[byte] = channel;
auto pAsyncOp = m_primaryUserXboxLiveContext->ProfileService->GetUserProfileAsync(m_multiplayerSession->Members->GetAt(sessionIndex)->XboxUserId);
DQRNetworkManager::LogCommentFormat(L"Session index of %d found for player with small id %d - attempting to resolve display name\n",sessionIndex,byte);
DQRNetworkPlayer *pPlayer = new DQRNetworkPlayer(this, DQRNetworkPlayer::DNP_TYPE_REMOTE, true, 0, sessionAddress);
pPlayer->SetSmallId(byte);
pPlayer->SetUID(PlayerUID(m_multiplayerSession->Members->GetAt(sessionIndex)->XboxUserId->Data()));
HostGamertagResolveDetails *resolveDetails = new HostGamertagResolveDetails();
resolveDetails->m_pPlayer = pPlayer;
resolveDetails->m_sessionAddress = sessionAddress;
resolveDetails->m_channel = channel;
resolveDetails->m_sync = false;
int mask = 1 << channel;
unsigned char *pucsmallIdReadMaskResolved = connectionInfo->m_pucsmallIdReadMaskResolved;
unsigned char ucsmallIdReadMask = connectionInfo->m_smallIdReadMask;
create_task( pAsyncOp ).then( [this,resolveDetails,mask,pucsmallIdReadMaskResolved,ucsmallIdReadMask] (task<Microsoft::Xbox::Services::Social::XboxUserProfile^> resultTask)
{
try
{
Microsoft::Xbox::Services::Social::XboxUserProfile^ result = resultTask.get();
resolveDetails->m_name.assign(result->Gamertag->Data()); // Use the gamertag for this data, as it is synchronised round all the machines and so we can't use a display name that could be a real name
EnterCriticalSection(&m_csHostGamertagResolveResults);
// Update flags for which names have been resolved, and if this completes this set, then set the flag to say that we should synchronise these out to the clients
*pucsmallIdReadMaskResolved |= mask;
LogCommentFormat(L"<<>> Compare %d to %d",*pucsmallIdReadMaskResolved,ucsmallIdReadMask);
if(ucsmallIdReadMask == *pucsmallIdReadMaskResolved)
{
resolveDetails->m_sync = true;
delete pucsmallIdReadMaskResolved;
}
m_hostGamertagResolveResults.push(resolveDetails);
LeaveCriticalSection(&m_csHostGamertagResolveResults);
}
catch (Platform::Exception^ ex)
{
LogComment("Name resolve exception raised");
// TODO - handle errors more usefully than just not setting the name...
EnterCriticalSection(&m_csHostGamertagResolveResults);
// Update flags for which names have been resolved, and if this completes this set, then set the flag to say that we should synchronise these out to the clients
*pucsmallIdReadMaskResolved |= mask;
if(ucsmallIdReadMask == *pucsmallIdReadMaskResolved)
{
resolveDetails->m_sync = true;
delete pucsmallIdReadMaskResolved;
}
m_hostGamertagResolveResults.push(resolveDetails);
LeaveCriticalSection(&m_csHostGamertagResolveResults);
}
});
}
}
}
switch(connectionInfo->m_internalDataState)
{
case DQRConnectionInfo::ConnectionState_InternalAssignSmallId0:
connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalAssignSmallId1;
break;
case DQRConnectionInfo::ConnectionState_InternalAssignSmallId1:
connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalAssignSmallId2;
break;
case DQRConnectionInfo::ConnectionState_InternalAssignSmallId2:
connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalAssignSmallId3;
break;
case DQRConnectionInfo::ConnectionState_InternalAssignSmallId3:
connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalHeaderByte;
break;
}
pNextByte++;
break;
case DQRConnectionInfo::ConnectionState_InternalRoomSyncData:
connectionInfo->m_pucRoomSyncData[connectionInfo->m_roomSyncDataBytesRead++] = byte;
// The room sync info is sent as a 4 byte count of the length of XUID strings, then the RoomSyncData, then the XUID strings
if( connectionInfo->m_roomSyncDataBytesToRead == 0 )
{
// At first stage of reading the 4 byte count
if( connectionInfo->m_roomSyncDataBytesRead == 4 )
{
memcpy( &connectionInfo->m_roomSyncDataBytesToRead, connectionInfo->m_pucRoomSyncData, 4);
delete [] connectionInfo->m_pucRoomSyncData;
connectionInfo->m_roomSyncDataBytesToRead += sizeof(RoomSyncData);
connectionInfo->m_pucRoomSyncData = new unsigned char[ connectionInfo->m_roomSyncDataBytesToRead ];
connectionInfo->m_roomSyncDataBytesRead = 0;
}
}
else if( connectionInfo->m_roomSyncDataBytesRead == connectionInfo->m_roomSyncDataBytesToRead )
{
// Second stage of reading the variable length data - when we've read this all, we can created storage for the XUID strings and copy them all in
RoomSyncData *roomSyncData = (RoomSyncData *)connectionInfo->m_pucRoomSyncData;
wchar_t *pwcsData = (wchar_t *)((unsigned char *)connectionInfo->m_pucRoomSyncData + sizeof(RoomSyncData));
for( int i = 0; i < roomSyncData->playerCount; i++ )
{
unsigned int thisWchars = ( wcslen(pwcsData) + 1 );
roomSyncData->players[i].m_XUID = new wchar_t[thisWchars];
wcsncpy(roomSyncData->players[i].m_XUID, pwcsData, thisWchars);
pwcsData += thisWchars;
}
// Update the room sync data with this new data. This will handle notification of new and removed players
UpdateRoomSyncPlayers((RoomSyncData *)connectionInfo->m_pucRoomSyncData);
delete connectionInfo->m_pucRoomSyncData;
connectionInfo->m_pucRoomSyncData = NULL;
connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalHeaderByte;
// If we haven't actually established a connection yet for this channel, then this is the point where we can consider this active
if( !connectionInfo->m_channelActive[connectionInfo->m_currentChannel] )
{
DQRNetworkManager::LogCommentFormat(L"Received data from host, channel %d considered active (%d bytes)\n",connectionInfo->m_currentChannel,connectionInfo->m_bytesRemaining);
connectionInfo->m_channelActive[connectionInfo->m_currentChannel] = true;
// This is also the time (as a client) to inform the chat integration layer of the host's session address, since we can now (reliably) send data to it
if(connectionInfo->m_currentChannel == 0)
{
if( m_chat )
{
m_chat->OnNewSessionAddressAdded(m_hostSessionAddress);
}
}
}
// Move to starting & playing states, if we are still joining rather than adding an additional player from this client, and we have all the local players here.
// We need to check that they are all here because we could have received a broadcast room sync data caused by another machine joining, and and so we can't assume
// that we're ready to go just yet.
if( m_state == DQRNetworkManager::DNM_INT_STATE_JOINING_SENDING_UNRELIABLE )
{
bool allLocalPlayersHere = true;
for( int i = 0; i < MAX_LOCAL_PLAYERS; i++ )
{
if( m_currentUserMask & ( 1 << i ) )
{
if( GetLocalPlayerByUserIndex(i) == NULL )
{
allLocalPlayersHere = false;
}
}
}
if( allLocalPlayersHere )
{
DQRNetworkManager::LogComment(L"All local players present");
SetState(DQRNetworkManager::DNM_INT_STATE_STARTING);
SetState(DQRNetworkManager::DNM_INT_STATE_PLAYING);
}
else
{
// Our players aren't all here yet. Going to keep on sending unreliable packets though until the connection is up and running
DQRNetworkManager::LogComment(L"All local players not yet present");
}
}
}
pNextByte++;
break;
case DQRConnectionInfo::ConnectionState_InternalAddPlayerFailedData:
connectionInfo->m_pucAddFailedPlayerData[connectionInfo->m_addFailedPlayerDataBytesRead++] = byte;
// The failed player info is sent as a 4 byte count of the length of XUID string, then the string itself
if( connectionInfo->m_addFailedPlayerDataBytesToRead == 0 )
{
// At first stage of reading the 4 byte count
if( connectionInfo->m_addFailedPlayerDataBytesRead == 4 )
{
memcpy( &connectionInfo->m_addFailedPlayerDataBytesToRead, connectionInfo->m_pucAddFailedPlayerData, 4);
delete [] connectionInfo->m_pucAddFailedPlayerData;
connectionInfo->m_pucAddFailedPlayerData = new unsigned char[ connectionInfo->m_addFailedPlayerDataBytesToRead ];
connectionInfo->m_addFailedPlayerDataBytesRead = 0;
}
}
else if( connectionInfo->m_addFailedPlayerDataBytesRead == connectionInfo->m_addFailedPlayerDataBytesToRead )
{
// XUID fully read, can now handle what to do with it
AddPlayerFailed(ref new Platform::String( (wchar_t *)connectionInfo->m_pucAddFailedPlayerData ) );
delete [] connectionInfo->m_pucAddFailedPlayerData;
connectionInfo->m_pucAddFailedPlayerData = NULL;
connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalHeaderByte;
}
pNextByte++;
break;
}
} while (pNextByte != pEndByte);
}
// This method directly sends bytes via the network communication layer, used to send both game data & data internal to the DQRNetworkManager itself.
// This is used by higher level sending methods that wrap communications up with headers that can be processed at the receiving end.
void DQRNetworkManager::SendBytesRaw(int smallId, BYTE *bytes, int byteCount, bool reliableAndSequential)
{
bool broadcast;
unsigned int sessionAddress;
// app.DebugPrintf("{%d,%d - %d}\n",smallId,reliableAndSequential,byteCount);
if( smallId == -1 )
{
LogCommentFormat(L"Attempting broadcast, exception of address m_XRNS_Session->LocalSessionAddress %d %d %d", smallId, byteCount, reliableAndSequential);
// Broadcast, used from host only
broadcast = true;
sessionAddress = 0;
}
else
{
// Send to individual session address
broadcast = false;
if( m_isHosting )
{
sessionAddress = m_sessionAddressFromSmallId[ smallId ];
}
else
{
sessionAddress = m_hostSessionAddress;
}
}
RTS_SendData(bytes, byteCount, sessionAddress, reliableAndSequential, reliableAndSequential, reliableAndSequential, broadcast, true);
}
// This method is called by the chat integration layer to be able to send data
void DQRNetworkManager::SendBytesChat(unsigned int address, BYTE *bytes, int byteCount, bool reliable, bool sequential, bool broadcast)
{
unsigned int sessionAddress;
if( broadcast )
{
sessionAddress = 0;
}
else
{
// Send to individual session address
sessionAddress = address;
}
RTS_SendData(bytes, byteCount, sessionAddress, reliable, sequential, false, broadcast, false);
}
// This is the higher level sending method for sending game data - this prefixes the send with a header so that it will get routed to the correct player.
void DQRNetworkManager::SendBytes(int smallId, BYTE *bytes, int byteCount)
{
EnterCriticalSection(&m_csSendBytes);
unsigned char *tempSendBuffer = (unsigned char *)malloc(8191 + 2);
BYTE *data = bytes;
BYTE *dataEnd = bytes + byteCount;
// Data to be sent has a header to say which of our own internal channels it is on (2 bits), and number of bytes that are in the message.
// The number of bytes has to be stored in 13 bits, and so a maximum of 8191 bytes can be send at a time. Split up longer messages into
// blocks of this size.
do
{
int bytesToSend = (int)(dataEnd - data);
if( bytesToSend > 8191 ) bytesToSend = 8191;
// Send header with data sending mode - see full comment in DQRNetworkManagerEventHandlers::DataReceivedHandler
tempSendBuffer[0] = ( m_channelFromSmallId[smallId] << 5 ) | ( bytesToSend >> 8 );
tempSendBuffer[1] = bytesToSend & 0xff;
memcpy(&tempSendBuffer[2], data, bytesToSend);
SendBytesRaw(smallId, tempSendBuffer, bytesToSend + 2, true);
data += bytesToSend;
} while (data != dataEnd);
free(tempSendBuffer);
LeaveCriticalSection(&m_csSendBytes);
}
int DQRNetworkManager::GetQueueSizeBytes()
{
return m_RTS_Stat_totalBytes;
}
int DQRNetworkManager::GetQueueSizeMessages()
{
return m_RTS_Stat_totalSends;
}

View File

@@ -0,0 +1,651 @@
#include "stdafx.h"
#include "DQRNetworkManager.h"
#include "PartyController.h"
#include <collection.h>
#include <ppltasks.h>
#include <ws2tcpip.h>
#include "..\Minecraft.World\StringHelpers.h"
#include "base64.h"
#ifdef _DURANGO
#include "..\Minecraft.World\DurangoStats.h"
#endif
#include "ChatIntegrationLayer.h"
using namespace Concurrency;
using namespace Windows::Foundation::Collections;
DQRNetworkManagerEventHandlers::DQRNetworkManagerEventHandlers(DQRNetworkManager *pDQRNet)
{
m_pDQRNet = pDQRNet;
}
void DQRNetworkManagerEventHandlers::Setup(WXNRs::Session^ session)
{
try
{
m_dataReceivedToken = session->DataReceived += ref new Windows::Foundation::EventHandler<WXNRs::DataReceivedEventArgs^>(this, &DQRNetworkManagerEventHandlers::DataReceivedHandler);
m_sessionStatusToken = session->SessionStatusUpdate += ref new Windows::Foundation::EventHandler<WXNRs::SessionStatusUpdateEventArgs^>(this, &DQRNetworkManagerEventHandlers::SessionStatusUpdateHandler);
m_sessionAddressToken = session->SessionAddressDataChanged += ref new Windows::Foundation::EventHandler<WXNRs::SessionAddressDataChangedEventArgs^>(this, &DQRNetworkManagerEventHandlers::SessionAddressDataChangedHandler);
m_addedSessionToken = session->AddedSessionAddress += ref new Windows::Foundation::EventHandler<WXNRs::AddedSessionAddressEventArgs^>(this, &DQRNetworkManagerEventHandlers::AddedSessionAddressHandler);
m_removedSessionToken = session->RemovedSessionAddress += ref new Windows::Foundation::EventHandler<WXNRs::RemovedSessionAddressEventArgs^>(this, &DQRNetworkManagerEventHandlers::RemovedSessionAddressHandler);
m_globalDataToken = session->GlobalSessionDataChanged += ref new Windows::Foundation::EventHandler<WXNRs::GlobalSessionDataChangedEventArgs^>(this, &DQRNetworkManagerEventHandlers::GlobalSessionDataChangedHandler);
}
catch(Platform::COMException^ ex)
{
// swallow exceptions
}
catch(...)
{
// swallow exceptions
}
}
void DQRNetworkManagerEventHandlers::Pulldown(WXNRs::Session^ session)
{
try
{
session->DataReceived -= m_dataReceivedToken;
session->SessionStatusUpdate -= m_sessionStatusToken;
session->SessionAddressDataChanged -= m_sessionAddressToken;
session->AddedSessionAddress -= m_addedSessionToken;
session->RemovedSessionAddress -= m_removedSessionToken;
session->GlobalSessionDataChanged -= m_globalDataToken;
}
catch(Platform::COMException^ ex)
{
// swallow exceptions
}
catch(...)
{
// swallow exceptions
}
}
// This event handler is called directly by the realtime session layer, when data is received. We split this data into into data that is meant to be
// handled internally by the DQRNetworkManager, and that which is meant to be passed on to the game itself as communication between players.
void DQRNetworkManagerEventHandlers::DataReceivedHandler(Platform::Object^ session, WXNRs::DataReceivedEventArgs^ args)
{
// DQRNetworkManager::LogCommentFormat(L"DataReceivedHandler session addr: 0x%x (%d bytes)",args->SessionAddress,args->Data->Length);
if (session == m_pDQRNet->m_XRNS_Session)
{
EnterCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming);
DQRNetworkManager::RTS_Message rtsMessage;
if( args->ChannelId == WXNRs::ChannelId::DefaultChatReceive )
{
rtsMessage.m_eType = DQRNetworkManager::eRTSMessageType::RTS_MESSAGE_DATA_RECEIVED_CHAT;
}
else
{
rtsMessage.m_eType = DQRNetworkManager::eRTSMessageType::RTS_MESSAGE_DATA_RECEIVED;
}
rtsMessage.m_sessionAddress = args->SessionAddress;
rtsMessage.m_dataSize = args->Data->Length;
rtsMessage.m_pucData = (unsigned char *)malloc(rtsMessage.m_dataSize);
memcpy( rtsMessage.m_pucData, args->Data->Data, rtsMessage.m_dataSize );
m_pDQRNet->m_RTSMessageQueueIncoming.push(rtsMessage);
LeaveCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming);
}
}
// This event handler is called by the realtime session layer, when session address data is updated. We don't currently use session address data.
void DQRNetworkManagerEventHandlers::SessionAddressDataChangedHandler(Platform::Object^ session, WXNRs::SessionAddressDataChangedEventArgs^ args)
{
DQRNetworkManager::LogComment(L"SessionAddressDataChangedHandler");
}
// This event handler is called by the realtime session layer when a session changes status. We use this to determine that a connection has been made made to the host,
// and the case when a connection has been terminated.
void DQRNetworkManagerEventHandlers::SessionStatusUpdateHandler(Platform::Object^ session, WXNRs::SessionStatusUpdateEventArgs^ args)
{
DQRNetworkManager::LogComment(L"SessionStatusUpdateHandler");
if (m_pDQRNet->m_XRNS_Session == session)
{
switch(args->NewStatus)
{
case WXNRs::SessionStatus::Active:
{
DQRNetworkManager::LogComment(L"Session active");
m_pDQRNet->m_XRNS_LocalAddress = m_pDQRNet->m_XRNS_Session->LocalSessionAddress;
m_pDQRNet->m_XRNS_OldestAddress = m_pDQRNet->m_XRNS_Session->OldestSessionAddress;
EnterCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming);
DQRNetworkManager::RTS_Message rtsMessage;
rtsMessage.m_eType = DQRNetworkManager::eRTSMessageType::RTS_MESSAGE_STATUS_ACTIVE;
rtsMessage.m_sessionAddress = 0;
rtsMessage.m_dataSize = 0;
rtsMessage.m_pucData = 0;
m_pDQRNet->m_RTSMessageQueueIncoming.push(rtsMessage);
LeaveCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming);
}
break;
case WXNRs::SessionStatus::Terminated:
{
DQRNetworkManager::LogComment(L"Session terminated");
EnterCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming);
DQRNetworkManager::RTS_Message rtsMessage;
rtsMessage.m_eType = DQRNetworkManager::eRTSMessageType::RTS_MESSAGE_STATUS_TERMINATED;
rtsMessage.m_sessionAddress = 0;
rtsMessage.m_dataSize = 0;
rtsMessage.m_pucData = 0;
m_pDQRNet->m_RTSMessageQueueIncoming.push(rtsMessage);
LeaveCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming);
}
break;
case WXNRs::SessionStatus::Activating:
DQRNetworkManager::LogComment(L"Session activating");
break;
case WXNRs::SessionStatus::Terminating:
DQRNetworkManager::LogComment(L"Session terminating");
break;
}
}
}
// This event is called from the realtime session layer to notify any clients that a new endpoint has been connected into the network mesh.
void DQRNetworkManagerEventHandlers::AddedSessionAddressHandler(Platform::Object^ session, WXNRs::AddedSessionAddressEventArgs^ args)
{
DQRNetworkManager::LogCommentFormat(L"AddedSessionAddressHandler session address 0x%x",args->SessionAddress);
EnterCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming);
DQRNetworkManager::RTS_Message rtsMessage;
rtsMessage.m_eType = DQRNetworkManager::eRTSMessageType::RTS_MESSAGE_ADDED_SESSION_ADDRESS;
rtsMessage.m_sessionAddress = args->SessionAddress;
rtsMessage.m_dataSize = 0;
rtsMessage.m_pucData = 0;
m_pDQRNet->m_RTSMessageQueueIncoming.push(rtsMessage);
LeaveCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming);
}
// This event is called from the realtime session layer to notify any clients that an endpoint has been removed from the network mesh.
void DQRNetworkManagerEventHandlers::RemovedSessionAddressHandler(Platform::Object^ session, WXNRs::RemovedSessionAddressEventArgs^ args)
{
DQRNetworkManager::LogCommentFormat(L"RemovedSessionAddressHandler session address 0x%x", args->SessionAddress);
EnterCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming);
DQRNetworkManager::RTS_Message rtsMessage;
rtsMessage.m_eType = DQRNetworkManager::eRTSMessageType::RTS_MESSAGE_REMOVED_SESSION_ADDRESS;
rtsMessage.m_sessionAddress = args->SessionAddress;
rtsMessage.m_dataSize = 0;
rtsMessage.m_pucData = 0;
m_pDQRNet->m_RTSMessageQueueIncoming.push(rtsMessage);
LeaveCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming);
}
// This event is called from the realtime session layer when session global data has been updated. We don't currently use global session data.
void DQRNetworkManagerEventHandlers::GlobalSessionDataChangedHandler(Platform::Object^ session, WXNRs::GlobalSessionDataChangedEventArgs^ args)
{
DQRNetworkManager::LogComment(L"GlobalSessionDataChangedHandler");
}
void DQRNetworkManager::UpdateRTSStats()
{
Platform::Array<unsigned int> ^sessionAddresses = nullptr;
try
{
sessionAddresses = m_XRNS_Session->GetAllRemoteSessionAddresses(WXNRs::RemoteSessionAddressStateOptions::All, WXNRs::RemoteSessionAddressConnectivityOptions::All);
}
catch(Platform::COMException^ ex)
{
// swallow exceptions
}
catch(...)
{
// swallow exceptions
}
if( sessionAddresses )
{
unsigned int totalBytes = 0;
unsigned int totalSends = 0;
for( unsigned int i = 0; i < sessionAddresses->Length; i++ )
{
try
{
totalBytes += m_XRNS_Session->GetSendChannelOutstandingBytes(sessionAddresses->get(i), WXNRs::ChannelId::DefaultGameSend );
totalSends += m_XRNS_Session->GetSendChannelOutstandingSends(sessionAddresses->get(i), WXNRs::ChannelId::DefaultGameSend );
}
catch(Platform::COMException^ ex)
{
// swallow exceptions
}
catch(...)
{
// swallow exceptions
}
}
m_RTS_Stat_totalBytes = totalBytes;
m_RTS_Stat_totalSends = totalSends;
}
else
{
m_RTS_Stat_totalBytes = 0;
m_RTS_Stat_totalSends = 0;
}
}
void DQRNetworkManager::ProcessRTSMessagesIncoming()
{
EnterCriticalSection(&m_csRTSMessageQueueIncoming);
while(m_RTSMessageQueueIncoming.size() > 0 )
{
RTS_Message message = m_RTSMessageQueueIncoming.front();
switch( message.m_eType )
{
case eRTSMessageType::RTS_MESSAGE_DATA_RECEIVED:
Process_RTS_MESSAGE_DATA_RECEIVED(message);
break;
case eRTSMessageType::RTS_MESSAGE_DATA_RECEIVED_CHAT:
Process_RTS_MESSAGE_DATA_RECEIVED_CHAT(message);
break;
case eRTSMessageType::RTS_MESSAGE_ADDED_SESSION_ADDRESS:
Process_RTS_MESSAGE_ADDED_SESSION_ADDRESS(message);
break;
case eRTSMessageType::RTS_MESSAGE_REMOVED_SESSION_ADDRESS:
Process_RTS_MESSAGE_REMOVED_SESSION_ADDRESS(message);
break;
case eRTSMessageType::RTS_MESSAGE_STATUS_ACTIVE:
Process_RTS_MESSAGE_STATUS_ACTIVE(message);
break;
case eRTSMessageType::RTS_MESSAGE_STATUS_TERMINATED:
Process_RTS_MESSAGE_STATUS_TERMINATED(message);
break;
default:
break;
}
m_RTSMessageQueueIncoming.pop();
}
LeaveCriticalSection(&m_csRTSMessageQueueIncoming);
};
void DQRNetworkManager::Process_RTS_MESSAGE_DATA_RECEIVED(RTS_Message &message)
{
DQRConnectionInfo *connectionInfo;
if( m_isHosting )
{
connectionInfo = m_sessionAddressToConnectionInfoMapHost[message.m_sessionAddress];
}
else
{
connectionInfo = &m_connectionInfoClient;
}
// Handle any header data, and actual data, in our stream. Data is as follows:
// Byte 0 Byte 1
// fccsssss ssssssss
//
// Where: f is 0 if this is normal data send (to be passed up to the game), or is 1 if this is to be internally processed
// cc is the channel number that the data belongs to (0 to 3 representing actual player indices)
// sssssssssssss is the count of data bytes to follow (range 0 - 8191)
BYTE *pNextByte = message.m_pucData;
BYTE *pEndByte = pNextByte + message.m_dataSize;
do
{
BYTE byte = *pNextByte;
switch( connectionInfo->m_state )
{
case DQRConnectionInfo::ConnectionState_HeaderByte0:
connectionInfo->m_currentChannel = ( byte >> 5 ) & 3;
connectionInfo->m_internalFlag = ( ( byte & 0x80 ) == 0x80 );
// Byte transfer mode. Bits 0-4 of this byte represent the upper 5 bits of our count of bytes to transfer... lower 8-bits will follow
connectionInfo->m_bytesRemaining = ((int)( byte & 0x1f )) << 8;
connectionInfo->m_state = DQRConnectionInfo::ConnectionState_HeaderByte1;
connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalHeaderByte;
pNextByte++;
break;
case DQRConnectionInfo::ConnectionState_HeaderByte1:
// Add in the lower 8 bits of our byte count, the upper 5 were obtained from the first header byte.
connectionInfo->m_bytesRemaining |= byte;
// If there isn't any data following, then just go back to the initial state expecting another header byte.
if( connectionInfo->m_bytesRemaining == 0 )
{
connectionInfo->m_state = DQRConnectionInfo::ConnectionState_HeaderByte0;
}
else
{
connectionInfo->m_state = DQRConnectionInfo::ConnectionState_ReadBytes;
}
pNextByte++;
break;
case DQRConnectionInfo::ConnectionState_ReadBytes:
// At this stage we can send up to connectionInfo->m_bytesRemaining bytes, or the number of bytes that we have remaining in the data received, whichever is lowest.
int bytesInBuffer = (int)(pEndByte - pNextByte);
int bytesToReceive = ( ( connectionInfo->m_bytesRemaining < bytesInBuffer ) ? connectionInfo->m_bytesRemaining : bytesInBuffer );
if( connectionInfo->m_internalFlag )
{
BytesReceivedInternal(connectionInfo, message.m_sessionAddress, pNextByte, bytesToReceive );
}
else
{
BytesReceived(connectionInfo->m_smallId[connectionInfo->m_currentChannel], pNextByte, bytesToReceive );
}
// Adjust counts and pointers
pNextByte += bytesToReceive;
connectionInfo->m_bytesRemaining -= bytesToReceive;
// Set state back to expect a header if there is no more data bytes to receive
if( connectionInfo->m_bytesRemaining == 0 )
{
connectionInfo->m_state = DQRConnectionInfo::ConnectionState_HeaderByte0;
}
break;
}
} while (pNextByte != pEndByte);
free(message.m_pucData);
}
void DQRNetworkManager::Process_RTS_MESSAGE_DATA_RECEIVED_CHAT(RTS_Message &message)
{
if( m_chat )
{
m_chat->OnIncomingChatMessage(message.m_sessionAddress, Platform::ArrayReference<BYTE>(message.m_pucData, message.m_dataSize) );
free(message.m_pucData);
}
}
void DQRNetworkManager::Process_RTS_MESSAGE_ADDED_SESSION_ADDRESS(RTS_Message &message)
{
if( m_chat )
{
m_chat->OnNewSessionAddressAdded(message.m_sessionAddress);
}
// New session address - add a mapping for it
if( m_isHosting )
{
auto it = m_sessionAddressToConnectionInfoMapHost.find(message.m_sessionAddress);
DQRConnectionInfo *connectionInfo;
if( it == m_sessionAddressToConnectionInfoMapHost.end() )
{
connectionInfo = new DQRConnectionInfo();
m_sessionAddressToConnectionInfoMapHost[message.m_sessionAddress] = connectionInfo;
}
else
{
// This shouldn't happen as we should be removing mappings as session addresses are removed.
connectionInfo = it->second;
connectionInfo->Reset();
}
}
}
void DQRNetworkManager::Process_RTS_MESSAGE_REMOVED_SESSION_ADDRESS(RTS_Message &message)
{
if( m_chat )
{
m_chat->RemoveRemoteConsole(message.m_sessionAddress);
}
if( m_isHosting )
{
auto it = m_sessionAddressToConnectionInfoMapHost.find(message.m_sessionAddress);
if( it != m_sessionAddressToConnectionInfoMapHost.end() )
{
delete it->second;
m_sessionAddressToConnectionInfoMapHost.erase(it);
RemoveRoomSyncPlayersWithSessionAddress(message.m_sessionAddress);
SendRoomSyncInfo();
}
}
else
{
// As the client, if we are disonnected from the host, then it is all over. Proceed as if leaving the room.
if( message.m_sessionAddress == m_hostSessionAddress )
{
LeaveRoom();
}
}
}
void DQRNetworkManager::Process_RTS_MESSAGE_STATUS_ACTIVE(RTS_Message &message)
{
// When we detect that the session has become active, we start sending unreliable packets, until we get some data back. This is because there is an issue with the
// realtime session layer where it is telling us that the connection is active a bit to early, and it will disconnect if it receives a packet that must be reliable in this
// state.
if( !m_isHosting )
{
m_firstUnreliableSendTime = 0;
m_hostSessionAddress = m_XRNS_OldestAddress;
// Also initialise the status of this connection
m_connectionInfoClient.Reset();
SetState(DQRNetworkManager::DNM_INT_STATE_JOINING_SENDING_UNRELIABLE);
}
}
void DQRNetworkManager::Process_RTS_MESSAGE_STATUS_TERMINATED(RTS_Message &message)
{
if( m_state == DQRNetworkManager::DNM_INT_STATE_JOINING_WAITING_FOR_ACTIVE_SESSION )
{
m_joinCreateSessionAttempts++;
if( m_joinCreateSessionAttempts > DQRNetworkManager::JOIN_CREATE_SESSION_MAX_ATTEMPTS )
{
SetState(DQRNetworkManager::DNM_INT_STATE_JOINING_FAILED);
}
else
{
SetState(DQRNetworkManager::DNM_INT_STATE_JOINING_GET_SDA);
}
}
}
int DQRNetworkManager::_RTSDoWorkThread(void* lpParameter)
{
DQRNetworkManager *pDQR = (DQRNetworkManager *)lpParameter;
return pDQR->RTSDoWorkThread();
}
static const DWORD XRNS_TERMINATE_LOCAL_SESSION_FLAG_IMMEDIATE = 0x00000001;
int DQRNetworkManager::RTSDoWorkThread()
{
do
{
if( m_XRNS_Session )
{
try
{
m_XRNS_Session->DoWork(20);
}
catch(Platform::COMException^ ex)
{
// swallow exceptions
}
catch(...)
{
// swallow exceptions
}
UpdateRTSStats();
}
else
{
Sleep(20);
}
ProcessRTSMessagesOutgoing();
} while(true);
}
void DQRNetworkManager::ProcessRTSMessagesOutgoing()
{
EnterCriticalSection(&m_csRTSMessageQueueOutgoing);
while(m_RTSMessageQueueOutgoing.size() > 0 )
{
RTS_Message message = m_RTSMessageQueueOutgoing.front();
switch( message.m_eType )
{
case eRTSMessageType::RTS_MESSAGE_START_CLIENT:
Process_RTS_MESSAGE_START_CLIENT(message);
break;
case eRTSMessageType::RTS_MESSAGE_START_HOST:
Process_RTS_MESSAGE_START_HOST(message);
break;
case eRTSMessageType::RTS_MESSAGE_TERMINATE:
Process_RTS_MESSAGE_TERMINATE(message);
break;
case eRTSMessageType::RTS_MESSAGE_SEND_DATA:
Process_RTS_MESSAGE_SEND_DATA(message);
break;
default:
break;
}
m_RTSMessageQueueOutgoing.pop();
}
LeaveCriticalSection(&m_csRTSMessageQueueOutgoing);
};
void DQRNetworkManager::Process_RTS_MESSAGE_START_CLIENT(RTS_Message &message)
{
if( m_XRNS_Session )
{
m_eventHandlers->Pulldown(m_XRNS_Session);
// Close XRNS session
try
{
m_XRNS_Session->TerminateLocalSession(XRNS_TERMINATE_LOCAL_SESSION_FLAG_IMMEDIATE);
}
catch(Platform::COMException^ ex)
{
// swallow exceptions
}
catch(...)
{
// swallow exceptions
}
m_XRNS_Session = nullptr;
}
m_XRNS_Session = ref new WXNRs::Session( m_localSocketAddress, m_remoteSocketAddress, MAX_PLAYERS_IN_TEMPLATE, 0);
m_XRNS_Session->MinSendRate = 512000;
LogCommentFormat(L"connect retry period %d retries %d, data retry count %d, data retry timeout %d\n",m_XRNS_Session->ConnectRetryPeriod,m_XRNS_Session->MaxConnectRetries,m_XRNS_Session->MaxDataRetries,m_XRNS_Session->MinDataRetryTimeout);
m_XRNS_Session->MaxConnectRetries = 50; // 50 at 100ms intervals = 5 seconds of attempting to connect
m_eventHandlers->Setup(m_XRNS_Session);
}
void DQRNetworkManager::Process_RTS_MESSAGE_START_HOST(RTS_Message &message)
{
m_XRNS_Session = ref new WXNRs::Session( m_localSocketAddress, MAX_PLAYERS_IN_TEMPLATE, 0);
m_XRNS_Session->MinSendRate = 512000;
m_XRNS_Session->MaxConnectRetries = 50; // 50 at 100ms intervals = 5 seconds of attempting to connect
m_eventHandlers->Setup(m_XRNS_Session);
}
void DQRNetworkManager::Process_RTS_MESSAGE_TERMINATE(RTS_Message &message)
{
if( m_XRNS_Session )
{
m_eventHandlers->Pulldown(m_XRNS_Session);
// Close XRNS session
try
{
m_XRNS_Session->TerminateLocalSession(XRNS_TERMINATE_LOCAL_SESSION_FLAG_IMMEDIATE);
}
catch(Platform::COMException^ ex)
{
// swallow exceptions
}
catch(...)
{
// swallow exceptions
}
m_XRNS_Session = nullptr;
}
}
void DQRNetworkManager::Process_RTS_MESSAGE_SEND_DATA(RTS_Message &message)
{
if( m_XRNS_Session )
{
unsigned int sessionAddress = message.m_sessionAddress;
try
{
if( message.m_flags & eRTSFlags::RTS_MESSAGE_FLAG_BROADCAST_MODE )
{
sessionAddress = m_XRNS_Session->LocalSessionAddress;
}
m_XRNS_Session->Send( ( message.m_flags & eRTSFlags::RTS_MESSAGE_FLAG_GAME_CHANNEL ) ? WXNRs::ChannelId::DefaultGameSend : WXNRs::ChannelId::DefaultChatSend,
( message.m_flags & eRTSFlags::RTS_MESSAGE_FLAG_BROADCAST_MODE ) ? WXNRs::SendExceptionType::ExcludedAddresses : WXNRs::SendExceptionType::IncludedAddresses,
Platform::ArrayReference<unsigned int>(&message.m_sessionAddress, 1),
Platform::ArrayReference<BYTE>(message.m_pucData, message.m_dataSize),
0,
( message.m_flags & eRTSFlags::RTS_MESSAGE_FLAG_RELIABLE ) ? WXNRs::Send_Reliability::Reliable : WXNRs::Send_Reliability::NonReliable,
( message.m_flags & eRTSFlags::RTS_MESSAGE_FLAG_SEQUENTIAL ) ? WXNRs::Send_Sequence::Sequential : WXNRs::Send_Sequence::NonSequential,
WXNRs::Send_Ack::AckNormal,
( message.m_flags & eRTSFlags::RTS_MESSAGE_FLAG_COALESCE ) ? WXNRs::Send_Coalesce::CoalesceDelay : WXNRs::Send_Coalesce::CoalesceNever,
WXNRs::Send_MiscState::NoMiscState );
}
catch(Platform::COMException^ ex)
{
// swallow exceptions
}
catch(...)
{
// swallow exceptions
}
}
free(message.m_pucData);
}
void DQRNetworkManager::RTS_StartCient()
{
EnterCriticalSection(&m_csRTSMessageQueueOutgoing);
RTS_Message message;
message.m_eType = eRTSMessageType::RTS_MESSAGE_START_CLIENT;
message.m_pucData = NULL;
message.m_dataSize = 0;
m_RTSMessageQueueOutgoing.push(message);
LeaveCriticalSection(&m_csRTSMessageQueueOutgoing);
}
void DQRNetworkManager::RTS_StartHost()
{
EnterCriticalSection(&m_csRTSMessageQueueOutgoing);
RTS_Message message;
message.m_eType = eRTSMessageType::RTS_MESSAGE_START_HOST;
message.m_pucData = NULL;
message.m_dataSize = 0;
m_RTSMessageQueueOutgoing.push(message);
LeaveCriticalSection(&m_csRTSMessageQueueOutgoing);
}
void DQRNetworkManager::RTS_Terminate()
{
EnterCriticalSection(&m_csRTSMessageQueueOutgoing);
RTS_Message message;
message.m_eType = eRTSMessageType::RTS_MESSAGE_TERMINATE;
message.m_pucData = NULL;
message.m_dataSize = 0;
m_RTSMessageQueueOutgoing.push(message);
LeaveCriticalSection(&m_csRTSMessageQueueOutgoing);
}
void DQRNetworkManager::RTS_SendData(unsigned char *pucData, unsigned int dataSize, unsigned int sessionAddress, bool reliable, bool sequential, bool coalesce, bool broadcastMode, bool gameChannel )
{
EnterCriticalSection(&m_csRTSMessageQueueOutgoing);
RTS_Message message;
message.m_eType = eRTSMessageType::RTS_MESSAGE_SEND_DATA;
message.m_pucData = (unsigned char *)malloc(dataSize);
memcpy(message.m_pucData, pucData, dataSize);
message.m_dataSize = dataSize;
message.m_sessionAddress = sessionAddress;
message.m_flags = 0;
if( reliable ) message.m_flags |= eRTSFlags::RTS_MESSAGE_FLAG_RELIABLE;
if( sequential ) message.m_flags |= eRTSFlags::RTS_MESSAGE_FLAG_SEQUENTIAL;
if( coalesce ) message.m_flags |= eRTSFlags::RTS_MESSAGE_FLAG_COALESCE;
if( broadcastMode ) message.m_flags |= eRTSFlags::RTS_MESSAGE_FLAG_BROADCAST_MODE;
if( gameChannel ) message.m_flags |= eRTSFlags::RTS_MESSAGE_FLAG_GAME_CHANNEL;
m_RTSMessageQueueOutgoing.push(message);
LeaveCriticalSection(&m_csRTSMessageQueueOutgoing);
}

View File

@@ -0,0 +1,204 @@
#include "stdafx.h"
#include "DQRNetworkPlayer.h"
#include "ChatIntegrationLayer.h"
DQRNetworkPlayer::DQRNetworkPlayer()
{
}
DQRNetworkPlayer::DQRNetworkPlayer(DQRNetworkManager *manager, eDQRNetworkPlayerType playerType, bool onHost, int localPlayerIdx, unsigned int sessionAddress)
{
m_localPlayerIdx = localPlayerIdx;
m_type = playerType;
m_host = onHost;
m_manager = manager;
m_customData = 0;
m_sessionAddress = sessionAddress;
}
DQRNetworkPlayer::~DQRNetworkPlayer()
{
}
PlayerUID DQRNetworkPlayer::GetUID()
{
return m_UID;
}
void DQRNetworkPlayer::SetUID(PlayerUID UID)
{
m_UID = UID;
}
int DQRNetworkPlayer::GetLocalPlayerIndex()
{
return m_localPlayerIdx;
}
uintptr_t DQRNetworkPlayer::GetCustomDataValue()
{
return m_customData;
}
void DQRNetworkPlayer::SetCustomDataValue(uintptr_t data)
{
m_customData = data;
}
bool DQRNetworkPlayer::IsRemote()
{
return !IsLocal();
}
bool DQRNetworkPlayer::IsHost()
{
return (m_type == DNP_TYPE_HOST);
}
bool DQRNetworkPlayer::IsLocal()
{
// m_host determines whether this *machine* is hosting the game, not this player (which is determined by m_type)
if( m_host )
{
// If we are the hosting machine, then both the host & local players are local to this machine
return (m_type == DNP_TYPE_HOST) || (m_type == DNP_TYPE_LOCAL);
}
else
{
// Not hosting, just local players are actually physically local
return (m_type == DNP_TYPE_LOCAL) ;
}
}
bool DQRNetworkPlayer::IsSameSystem(DQRNetworkPlayer *other)
{
return ( m_sessionAddress == other->m_sessionAddress );
}
bool DQRNetworkPlayer::IsTalking()
{
if(m_manager->m_chat == nullptr) return false;
Microsoft::Xbox::GameChat::ChatUser^ chatUser = m_manager->m_chat->GetChatUserByXboxUserId(ref new Platform::String(m_UID.toString().c_str()));
if( chatUser == nullptr ) return false;
if( chatUser->TalkingMode == Microsoft::Xbox::GameChat::ChatUserTalkingMode::NotTalking )
{
return false;
}
else
{
return true;
}
}
bool DQRNetworkPlayer::HasVoice()
{
if(m_manager->m_chat == nullptr) return false;
Microsoft::Xbox::GameChat::ChatUser^ chatUser = m_manager->m_chat->GetChatUserByXboxUserId(ref new Platform::String(m_UID.toString().c_str()));
if( chatUser == nullptr ) return false;
if( ((int)chatUser->ParticipantType) & ((int)Windows::Xbox::Chat::ChatParticipantTypes::Talker) )
{
return true;
}
else
{
return false;
}
}
bool DQRNetworkPlayer::HasCamera()
{
return false;
}
LPCWSTR DQRNetworkPlayer::GetGamertag()
{
return m_name;
}
int DQRNetworkPlayer::GetSmallId()
{
return (int)m_smallId;
}
void DQRNetworkPlayer::SetSmallId(unsigned char smallId)
{
m_smallId = smallId;
}
int DQRNetworkPlayer::GetSessionIndex()
{
return m_manager->GetSessionIndex(this);
}
// Attempt to send data, of any size, from this player to that specified by pPlayerTarget. This may not be possible depending on the two players, due to
// our star shaped network connectivity. Data may be any size, and is copied so on returning from this method it does not need to be preserved.
void DQRNetworkPlayer::SendData( DQRNetworkPlayer *pPlayerTarget, const void *data, unsigned int dataSize )
{
// Our network is connected as a star. If we are the host, then we can send to any remote player. If we're a client, we can send only to the host.
// The host can also send to other local players, but this doesn't need to go through Rudp.
if( m_host )
{
if( ( m_type == DNP_TYPE_HOST ) && ( pPlayerTarget->m_type == DNP_TYPE_REMOTE ) )
{
// Rudp communication from host to remote player - handled by remote player instance
pPlayerTarget->SendInternal(data,dataSize);
}
else
{
// Can't do any other types of communications
assert(false);
}
}
else
{
if( ( m_type == DNP_TYPE_LOCAL ) && ( pPlayerTarget->m_type == DNP_TYPE_HOST ) )
{
// Rudp communication from client to host - handled by this player instace
SendInternal(data, dataSize);
}
else
{
// Can't do any other types of communications
assert(false);
}
}
}
void DQRNetworkPlayer::SendInternal(const void *data, unsigned int dataSize)
{
m_manager->SendBytes(m_smallId, (BYTE *)data, dataSize);
}
int DQRNetworkPlayer::GetSendQueueSizeBytes()
{
return m_manager->GetQueueSizeBytes();
}
int DQRNetworkPlayer::GetSendQueueSizeMessages()
{
return m_manager->GetQueueSizeMessages();
}
wchar_t *DQRNetworkPlayer::GetName()
{
return m_name;
}
void DQRNetworkPlayer::SetName(const wchar_t *name)
{
wcscpy_s(m_name, name);
}
// Return display name (if display name is not set, return name instead)
wstring DQRNetworkPlayer::GetDisplayName()
{
return (m_displayName == L"") ? m_name : m_displayName;
}
// Set display name
void DQRNetworkPlayer::SetDisplayName(wstring displayName)
{
m_displayName = displayName;
}

View File

@@ -0,0 +1,65 @@
#pragma once
#include "DQRNetworkManager.h"
#include <queue>
// This is the lowest level class for handling the concept of a player on Durango. This is managed by DQRNetworkManager. The game shouldn't directly communicate
// with this class, as it is wrapped by NetworkPlayerDurango which is an implementation of a platform-independent interface INetworkPlayer.
class DQRNetworkPlayer
{
public:
friend class DQRNetworkManager;
typedef enum
{
DNP_TYPE_HOST, // This player represents the host
DNP_TYPE_LOCAL, // On host - this player is a local player that needs communicated with specially not using rudp. On clients - this is a local player, where network communications can be used to communicate with the host
DNP_TYPE_REMOTE, // On host - this player can be used to communicate from between the host and this player. On clients - this is a remote player that cannot be communicated with
} eDQRNetworkPlayerType;
DQRNetworkPlayer();
DQRNetworkPlayer(DQRNetworkManager *manager, eDQRNetworkPlayerType playerType, bool onHost, int localPlayerIdx, unsigned int sessionAddress);
~DQRNetworkPlayer();
PlayerUID GetUID();
void SetUID(PlayerUID UID);
int GetLocalPlayerIndex();
uintptr_t GetCustomDataValue();
void SetCustomDataValue(uintptr_t data);
bool IsRemote();
bool IsHost();
bool IsLocal();
bool IsSameSystem(DQRNetworkPlayer *other);
bool HasVoice();
bool IsTalking();
bool HasCamera();
LPCWSTR GetGamertag();
int GetSmallId();
void SetSmallId(unsigned char smallId);
int GetSessionIndex();
void SendData( DQRNetworkPlayer *pPlayerTarget, const void *data, unsigned int dataSize );
int GetSendQueueSizeBytes();
int GetSendQueueSizeMessages();
wchar_t *GetName();
void SetName(const wchar_t *name);
std::wstring GetDisplayName();
void SetDisplayName(std::wstring displayName);
private:
void SendInternal(const void *data, unsigned int dataSize);
eDQRNetworkPlayerType m_type; // The player type
bool m_host; // Whether this actual player class is stored on a host (not whether it represents the host, or a player on the host machine)
int m_localPlayerIdx; // Index of this player on the machine to which it belongs
DQRNetworkManager *m_manager; // Pointer back to the manager that is managing this player
PlayerUID m_UID;
uintptr_t m_customData;
unsigned char m_smallId;
unsigned int m_sessionAddress;
wchar_t m_name[21];
std::wstring m_displayName;
};

View File

@@ -0,0 +1,113 @@
#include "stdafx.h"
#include "NetworkPlayerDurango.h"
NetworkPlayerDurango::NetworkPlayerDurango(DQRNetworkPlayer *qnetPlayer)
{
m_dqrPlayer = qnetPlayer;
m_pSocket = NULL;
}
unsigned char NetworkPlayerDurango::GetSmallId()
{
return m_dqrPlayer->GetSmallId();
}
void NetworkPlayerDurango::SendData(INetworkPlayer *player, const void *pvData, int dataSize, bool lowPriority)
{
m_dqrPlayer->SendData( ((NetworkPlayerDurango *)player)->m_dqrPlayer, pvData, dataSize );
}
bool NetworkPlayerDurango::IsSameSystem(INetworkPlayer *player)
{
return m_dqrPlayer->IsSameSystem(((NetworkPlayerDurango *)player)->m_dqrPlayer);
}
int NetworkPlayerDurango::GetSendQueueSizeBytes( INetworkPlayer *player, bool lowPriority )
{
return m_dqrPlayer->GetSendQueueSizeBytes();
}
int NetworkPlayerDurango::GetSendQueueSizeMessages( INetworkPlayer *player, bool lowPriority )
{
return m_dqrPlayer->GetSendQueueSizeMessages();
}
int NetworkPlayerDurango::GetCurrentRtt()
{
return 0; // TODO
}
bool NetworkPlayerDurango::IsHost()
{
return m_dqrPlayer->IsHost();
}
bool NetworkPlayerDurango::IsGuest()
{
return false; // TODO
}
bool NetworkPlayerDurango::IsLocal()
{
return m_dqrPlayer->IsLocal();
}
int NetworkPlayerDurango::GetSessionIndex()
{
return m_dqrPlayer->GetSessionIndex();
}
bool NetworkPlayerDurango::IsTalking()
{
return m_dqrPlayer->IsTalking();
}
bool NetworkPlayerDurango::IsMutedByLocalUser(int userIndex)
{
return false;
}
bool NetworkPlayerDurango::HasVoice()
{
return m_dqrPlayer->HasVoice();
}
bool NetworkPlayerDurango::HasCamera()
{
return false; // TODO
}
int NetworkPlayerDurango::GetUserIndex()
{
return m_dqrPlayer->GetLocalPlayerIndex();
}
void NetworkPlayerDurango::SetSocket(Socket *pSocket)
{
m_pSocket = pSocket;
}
Socket *NetworkPlayerDurango::GetSocket()
{
return m_pSocket;
}
const wchar_t *NetworkPlayerDurango::GetOnlineName()
{
return m_dqrPlayer->GetName();
}
wstring NetworkPlayerDurango::GetDisplayName()
{
return m_dqrPlayer->GetDisplayName();
}
PlayerUID NetworkPlayerDurango::GetUID()
{
return m_dqrPlayer->GetUID();
}
void NetworkPlayerDurango::SetUID(PlayerUID UID)
{
m_dqrPlayer->SetUID(UID);
}

View File

@@ -0,0 +1,39 @@
#pragma once
#include "..\..\Common\Network\NetworkPlayerInterface.h"
#include "DQRNetworkPlayer.h"
// This is an implementation of the INetworkPlayer interface, for Durango. It effectively wraps the DQRNetworkPlayer class in a non-platform-specific way.
class NetworkPlayerDurango : public INetworkPlayer
{
public:
// Common player interface
NetworkPlayerDurango(DQRNetworkPlayer *sqrPlayer);
virtual unsigned char GetSmallId();
virtual void SendData(INetworkPlayer *player, const void *pvData, int dataSize, bool lowPriority);
virtual bool IsSameSystem(INetworkPlayer *player);
virtual int GetSendQueueSizeBytes( INetworkPlayer *player, bool lowPriority );
virtual int GetSendQueueSizeMessages( INetworkPlayer *player, bool lowPriority );
virtual int GetCurrentRtt();
virtual bool IsHost();
virtual bool IsGuest();
virtual bool IsLocal();
virtual int GetSessionIndex();
virtual bool IsTalking();
virtual bool IsMutedByLocalUser(int userIndex);
virtual bool HasVoice();
virtual bool HasCamera();
virtual int GetUserIndex();
virtual void SetSocket(Socket *pSocket);
virtual Socket *GetSocket();
virtual const wchar_t *GetOnlineName();
virtual wstring GetDisplayName();
virtual PlayerUID GetUID();
void SetUID(PlayerUID UID);
private:
DQRNetworkPlayer *m_dqrPlayer;
Socket *m_pSocket;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//// PARTICULAR PURPOSE.
////
//// Copyright (c) Microsoft Corporation. All rights reserved
#pragma once
#include <ppltasks.h>
class DQRNetworkManager;
class PartyController
{
public:
PartyController(DQRNetworkManager *pDQRNet);
void SetPartyView( Windows::Xbox::Multiplayer::PartyView^ partyView );
Windows::Xbox::Multiplayer::PartyView^ GetPartyView();
void RefreshPartyView();
bool AddLocalUsersToParty(int userMask, Windows::Xbox::System::User^ primaryUser);
void RemoveLocalUsersFromParty(Windows::Xbox::System::User^ primaryUser);
void RemoveLocalUsersFromParty(Windows::Xbox::System::User^ primaryUser, int playerMask, Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference^ sessionReference);
void RemoveLocalUserFromParty(Windows::Xbox::System::User^ userToRemove);
void RegisterEventHandlers();
void UnregisterEventHandlers();
void UnregisterGamePlayersEventHandler();
void RegisterGamePlayersChangedEventHandler();
bool CanJoinParty();
bool CanInvitePartyToMyGame( Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ multiplayerSession );
bool IsPartyInAnotherTitle();
bool IsGameSessionReadyEventTriggered();
bool DoesPartySessionExist();
Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference ^ GetGamePartySessionReference();
void ClearGameSessionReadyEventTriggered();
int GetActiveAndReservedMemberPartySize();
bool DoesPartyAndSessionPlayersMatch(
Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ session
);
void CheckPartySessionFull(Windows::Xbox::System::User^ primaryUser);
void SetJoinability(bool isJoinable);
void DisassociateSessionFromParty( Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference^ sessionReference);
private:
Concurrency::critical_section m_lock;
bool m_isGameSessionReadyEventTriggered;
bool m_isGamePlayerEventRegistered;
DQRNetworkManager *m_pDQRNet;
static void DebugPrintPartyView( Windows::Xbox::Multiplayer::PartyView^ partyView );
void OnPartyStateChanged( Windows::Xbox::Multiplayer::PartyStateChangedEventArgs^ eventArgs );
void OnPartyRosterChanged( Windows::Xbox::Multiplayer::PartyRosterChangedEventArgs^ eventArgs );
void OnGamePlayersChanged( Windows::Xbox::Multiplayer::GamePlayersChangedEventArgs^ eventArgs );
void OnGameSessionReady( Windows::Xbox::Multiplayer::GameSessionReadyEventArgs^ eventArgs );
Windows::Xbox::Multiplayer::PartyView^ m_partyView;
Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference^ m_partyGameReadyRef;
void AddAvailableGamePlayers(
Windows::Foundation::Collections::IVectorView<Windows::Xbox::Multiplayer::GamePlayer^>^ availablePlayers,
int& remainingSlots,
Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ currentSession
);
Windows::Foundation::DateTime GetCurrentTime();
double GetTimeBetweenInSeconds(Windows::Foundation::DateTime dt1, Windows::Foundation::DateTime dt2);
// Party/Session events.
Windows::Foundation::EventRegistrationToken m_partyRosterChangedToken;
Windows::Foundation::EventRegistrationToken m_partyStateChangedToken;
Windows::Foundation::EventRegistrationToken m_partyGamePlayersChangedToken;
Windows::Foundation::EventRegistrationToken m_partyGameSessionReadyToken;
};

View File

@@ -0,0 +1,940 @@
#include "stdafx.h"
#include "..\..\..\Minecraft.World\Socket.h"
#include "..\..\..\Minecraft.World\StringHelpers.h"
#include "PlatformNetworkManagerDurango.h"
#include "NetworkPlayerDurango.h"
CPlatformNetworkManagerDurango *g_pPlatformNetworkManager;
void CPlatformNetworkManagerDurango::HandleStateChange(DQRNetworkManager::eDQRNetworkManagerState oldState, DQRNetworkManager::eDQRNetworkManagerState newState)
{
static const char * c_apszStateNames[] =
{
"DNM_STATE_INITIALISING",
"DNM_STATE_INITIALISE_FAILED",
"DNM_STATE_IDLE",
"DNM_STATE_HOSTING",
"DNM_STATE_JOINING",
"DNM_STATE_STARTING",
"DNM_STATE_PLAYING",
"DNM_STATE_LEAVING",
"DNM_STATE_ENDING",
};
app.DebugPrintf( "Network State: %s ==> %s\n",
c_apszStateNames[ oldState ],
c_apszStateNames[ newState ] );
if( newState == DQRNetworkManager::DNM_STATE_HOSTING )
{
m_bLeavingGame = false;
m_bLeaveGameOnTick = false;
m_bHostChanged = false;
g_NetworkManager.StateChange_AnyToHosting();
}
else if( newState == DQRNetworkManager::DNM_STATE_JOINING )
{
m_bLeavingGame = false;
m_bLeaveGameOnTick = false;
m_bHostChanged = false;
g_NetworkManager.StateChange_AnyToJoining();
}
else if( newState == DQRNetworkManager::DNM_STATE_IDLE && oldState == DQRNetworkManager::DNM_STATE_JOINING )
{
g_NetworkManager.StateChange_JoiningToIdle(JOIN_FAILED_NONSPECIFIC);
}
else if( newState == DQRNetworkManager::DNM_STATE_IDLE && oldState == DQRNetworkManager::DNM_STATE_HOSTING )
{
m_bLeavingGame = true;
}
else if( newState == DQRNetworkManager::DNM_STATE_STARTING )
{
m_lastPlayerEventTimeStart = app.getAppTime();
g_NetworkManager.StateChange_AnyToStarting();
}
// Fix for #93148 - TCR 001: BAS Game Stability: Title will crash for the multiplayer client if host of the game will exit during the clients loading to created world.
// 4J Stu - If the client joins just as the host is exiting, then they can skip to leaving without passing through ending
else if( newState == DQRNetworkManager::DNM_STATE_ENDING )
{
g_NetworkManager.StateChange_AnyToEnding( oldState == DQRNetworkManager::DNM_STATE_PLAYING );
if( m_pDQRNet->IsHost() )
{
m_bLeavingGame = true;
}
}
if( newState == DQRNetworkManager::DNM_STATE_IDLE )
{
g_NetworkManager.StateChange_AnyToIdle();
}
}
void CPlatformNetworkManagerDurango::HandlePlayerJoined(DQRNetworkPlayer *pDQRPlayer)
{
const char * pszDescription;
// If this is a local player, we need to inform the chat system that it has joined
if( pDQRPlayer->IsLocal() )
{
m_pDQRNet->ChatPlayerJoined(pDQRPlayer->GetLocalPlayerIndex());
}
// 4J Stu - We create a fake socket for every where that we need an INBOUND queue of game data. Outbound
// is all handled by QNet so we don't need that. Therefore each client player has one, and the host has one
// for each client player.
bool createFakeSocket = false;
bool localPlayer = false;
NetworkPlayerDurango *networkPlayer = (NetworkPlayerDurango *)addNetworkPlayer(pDQRPlayer);
// Request full display name for this player
m_pDQRNet->RequestDisplayName(pDQRPlayer);
if( pDQRPlayer->IsLocal() )
{
localPlayer = true;
if( pDQRPlayer->IsHost() )
{
pszDescription = "local host";
// 4J Stu - No socket for the localhost as it uses a special loopback queue
m_machineDQRPrimaryPlayers.push_back( pDQRPlayer );
}
else
{
pszDescription = "local";
// We need an inbound queue on all local players to receive data from the host
createFakeSocket = true;
}
}
else
{
if( pDQRPlayer->IsHost() )
{
pszDescription = "remote host";
}
else
{
pszDescription = "remote";
// If we are the host, then create a fake socket for every remote player
if( m_pDQRNet->IsHost() )
{
createFakeSocket = true;
}
}
if( m_pDQRNet->IsHost() && !m_bHostChanged )
{
// Do we already have a primary player for this system?
bool systemHasPrimaryPlayer = false;
for(AUTO_VAR(it, m_machineDQRPrimaryPlayers.begin()); it < m_machineDQRPrimaryPlayers.end(); ++it)
{
DQRNetworkPlayer *pQNetPrimaryPlayer = *it;
if( pDQRPlayer->IsSameSystem(pQNetPrimaryPlayer) )
{
systemHasPrimaryPlayer = true;
break;
}
}
if( !systemHasPrimaryPlayer )
m_machineDQRPrimaryPlayers.push_back( pDQRPlayer );
}
}
g_NetworkManager.PlayerJoining( networkPlayer );
if( createFakeSocket == true && !m_bHostChanged )
{
g_NetworkManager.CreateSocket( networkPlayer, localPlayer );
}
app.DebugPrintf( "Player 0x%p \"%ls\" joined; %s; voice %i; camera %i.\n",
pDQRPlayer,
pDQRPlayer->GetGamertag(),
pszDescription,
(int) pDQRPlayer->HasVoice(),
(int) pDQRPlayer->HasCamera() );
if( m_pDQRNet->IsHost() )
{
g_NetworkManager.UpdateAndSetGameSessionData();
SystemFlagAddPlayer( networkPlayer );
}
for( int idx = 0; idx < XUSER_MAX_COUNT; ++idx)
{
if(playerChangedCallback[idx] != NULL)
playerChangedCallback[idx]( playerChangedCallbackParam[idx], networkPlayer, false );
}
if(m_pDQRNet->GetState() == QNET_STATE_GAME_PLAY)
{
int localPlayerCount = 0;
for(unsigned int idx = 0; idx < XUSER_MAX_COUNT; ++idx)
{
if( m_pDQRNet->GetLocalPlayerByUserIndex(idx) != NULL ) ++localPlayerCount;
}
float appTime = app.getAppTime();
// Only record stats for the primary player here
m_lastPlayerEventTimeStart = appTime;
}
}
void CPlatformNetworkManagerDurango::HandlePlayerLeaving(DQRNetworkPlayer *pDQRPlayer)
{
//__debugbreak();
app.DebugPrintf( "Player 0x%p leaving.\n",
pDQRPlayer );
INetworkPlayer *networkPlayer = getNetworkPlayer(pDQRPlayer);
if( networkPlayer )
{
// Get our wrapper object associated with this player.
Socket *socket = networkPlayer->GetSocket();
if( socket != NULL )
{
// If we are in game then remove this player from the game as well.
// We may get here either from the player requesting to exit the game,
// in which case we they will already have left the game server, or from a disconnection
// where we then have to remove them from the game server
if( m_pDQRNet->IsHost() && !m_bHostChanged )
{
g_NetworkManager.CloseConnection(networkPlayer);
}
// Free the wrapper object memory.
// TODO 4J Stu - We may still be using this at the point that the player leaves the session.
// We need this as long as the game server still needs to communicate with the player
//delete socket;
networkPlayer->SetSocket( NULL );
}
if( m_pDQRNet->IsHost() && !m_bHostChanged )
{
if( isSystemPrimaryPlayer(pDQRPlayer) )
{
DQRNetworkPlayer *pNewDQRPrimaryPlayer = NULL;
for(unsigned int i = 0; i < m_pDQRNet->GetPlayerCount(); ++i )
{
DQRNetworkPlayer *pDQRPlayer2 = m_pDQRNet->GetPlayerByIndex( i );
if( pDQRPlayer2 != pDQRPlayer && pDQRPlayer2->IsSameSystem( pDQRPlayer ) )
{
pNewDQRPrimaryPlayer = pDQRPlayer2;
break;
}
}
AUTO_VAR(it, find( m_machineDQRPrimaryPlayers.begin(), m_machineDQRPrimaryPlayers.end(), pDQRPlayer));
if( it != m_machineDQRPrimaryPlayers.end() )
{
m_machineDQRPrimaryPlayers.erase( it );
}
if( pNewDQRPrimaryPlayer != NULL )
m_machineDQRPrimaryPlayers.push_back( pNewDQRPrimaryPlayer );
}
g_NetworkManager.UpdateAndSetGameSessionData( networkPlayer );
SystemFlagRemovePlayer( networkPlayer );
}
g_NetworkManager.PlayerLeaving( networkPlayer );
for( int idx = 0; idx < XUSER_MAX_COUNT; ++idx)
{
if(playerChangedCallback[idx] != NULL)
playerChangedCallback[idx]( playerChangedCallbackParam[idx], networkPlayer, true );
}
if(m_pDQRNet->GetState() == DQRNetworkManager::DNM_STATE_PLAYING)
{
int localPlayerCount = 0;
for(unsigned int idx = 0; idx < XUSER_MAX_COUNT; ++idx)
{
if( m_pDQRNet->GetLocalPlayerByUserIndex(idx) != NULL ) ++localPlayerCount;
}
float appTime = app.getAppTime();
m_lastPlayerEventTimeStart = appTime;
}
removeNetworkPlayer(pDQRPlayer);
}
}
void CPlatformNetworkManagerDurango::HandleDataReceived(DQRNetworkPlayer *playerFrom, DQRNetworkPlayer *playerTo, unsigned char *data, unsigned int dataSize)
{
#if 0
// TODO - put this back in
if(m_pSQRNet->GetState() == SQRNetworkManager::SNM_STATE_ENDING)
{
return;
}
#endif
if( playerTo->IsHost() )
{
// If we are the host we care who this came from
//app.DebugPrintf( "Pushing data into host read queue for user \"%ls\"\n", pPlayerFrom->GetGamertag());
// Push this data into the read queue for the player that sent it
INetworkPlayer *pPlayerFrom = getNetworkPlayer(playerFrom);
Socket *socket = pPlayerFrom->GetSocket();
if(socket != NULL)
socket->pushDataToQueue(data, dataSize, false);
}
else
{
// If we are not the host the message must have come from the host, so we care more about who it is addressed to
INetworkPlayer *pPlayerTo = getNetworkPlayer(playerTo);
Socket *socket = pPlayerTo->GetSocket();
//app.DebugPrintf( "Pushing data into read queue for user \"%ls\"\n", apPlayersTo[dwPlayer]->GetGamertag());
if(socket != NULL)
socket->pushDataToQueue(data, dataSize);
}
}
void CPlatformNetworkManagerDurango::HandleInviteReceived(int playerIndex, DQRNetworkManager::SessionInfo *pInviteInfo)
{
g_NetworkManager.GameInviteReceived(playerIndex, pInviteInfo);
}
bool CPlatformNetworkManagerDurango::Initialise(CGameNetworkManager *pGameNetworkManager, int flagIndexSize)
{
m_pDQRNet = new DQRNetworkManager(this);
m_pDQRNet->Initialise();
m_hostGameSessionIsJoinable = false;
m_pGameNetworkManager = pGameNetworkManager;
m_flagIndexSize = flagIndexSize;
g_pPlatformNetworkManager = this;
for( int i = 0; i < XUSER_MAX_COUNT; i++ )
{
playerChangedCallback[ i ] = NULL;
}
m_bLeavingGame = false;
m_bLeaveGameOnTick = false;
m_bHostChanged = false;
m_bSearchResultsReady = false;
m_bSearchPending = false;
m_bIsOfflineGame = false;
m_pSearchParam = NULL;
m_SessionsUpdatedCallback = NULL;
m_searchResultsCount = 0;
m_lastSearchStartTime = 0;
// The results that will be filled in with the current search
m_pSearchResults = NULL;
Windows::Networking::Connectivity::NetworkInformation::NetworkStatusChanged += ref new Windows::Networking::Connectivity::NetworkStatusChangedEventHandler( []( Platform::Object^ pxObject )
{
app.DebugPrintf("NetworkStatusChanged callback\n" );
auto internetProfile = Windows::Networking::Connectivity::NetworkInformation::GetInternetConnectionProfile();
if (internetProfile != nullptr)
{
auto connectionLevel = internetProfile->GetNetworkConnectivityLevel();
app.DebugPrintf("Connection level has changed to (%d)\n", connectionLevel);
//int iPrimaryPlayer = g_NetworkManager.GetPrimaryPad();
//bool bConnected = (connectionLevel == Windows::Networking::Connectivity::NetworkConnectivityLevel::XboxLiveAccess)?true:false;
//if((g_NetworkManager.GetLockedProfile()!=-1) && iPrimaryPlayer!=-1 && bConnected == false && g_NetworkManager.IsInSession() )
//{
// app.SetAction(iPrimaryPlayer,eAppAction_EthernetDisconnected);
//}
}
});
// Success!
return true;
}
void CPlatformNetworkManagerDurango::Terminate()
{
}
int CPlatformNetworkManagerDurango::GetJoiningReadyPercentage()
{
return 100;
}
int CPlatformNetworkManagerDurango::CorrectErrorIDS(int IDS)
{
return IDS;
}
bool CPlatformNetworkManagerDurango::isSystemPrimaryPlayer(DQRNetworkPlayer *pDQRPlayer)
{
bool playerIsSystemPrimary = false;
for(auto it = m_machineDQRPrimaryPlayers.begin(); it < m_machineDQRPrimaryPlayers.end(); ++it)
{
DQRNetworkPlayer *pDQRPrimaryPlayer = *it;
if( pDQRPrimaryPlayer == pDQRPlayer )
{
playerIsSystemPrimary = true;
break;
}
}
return playerIsSystemPrimary;
}
// We call this twice a frame, either side of the render call so is a good place to "tick" things
void CPlatformNetworkManagerDurango::DoWork()
{
m_pDQRNet->Tick();
TickSearch();
if( m_bLeaveGameOnTick )
{
m_pDQRNet->LeaveRoom();
m_bLeaveGameOnTick = false;
}
}
int CPlatformNetworkManagerDurango::GetPlayerCount()
{
return m_pDQRNet->GetPlayerCount();
}
bool CPlatformNetworkManagerDurango::ShouldMessageForFullSession()
{
return m_pDQRNet->ShouldMessageForFullSession();
}
int CPlatformNetworkManagerDurango::GetOnlinePlayerCount()
{
return m_pDQRNet->GetOnlinePlayerCount();
}
int CPlatformNetworkManagerDurango::GetLocalPlayerMask(int playerIndex)
{
return 1 << playerIndex;
}
bool CPlatformNetworkManagerDurango::AddLocalPlayerByUserIndex( int userIndex )
{
return m_pDQRNet->AddLocalPlayerByUserIndex( userIndex );
}
bool CPlatformNetworkManagerDurango::RemoveLocalPlayerByUserIndex( int userIndex )
{
return m_pDQRNet->RemoveLocalPlayerByUserIndex( userIndex );
}
bool CPlatformNetworkManagerDurango::IsInStatsEnabledSession()
{
return true;
}
bool CPlatformNetworkManagerDurango::SessionHasSpace(unsigned int spaceRequired /*= 1*/)
{
return true;
}
void CPlatformNetworkManagerDurango::SendInviteGUI(int quadrant)
{
m_pDQRNet->SendInviteGUI(quadrant);
}
bool CPlatformNetworkManagerDurango::IsAddingPlayer()
{
return m_pDQRNet->IsAddingPlayer();
}
bool CPlatformNetworkManagerDurango::LeaveGame(bool bMigrateHost)
{
if( m_bLeavingGame ) return true;
m_bLeavingGame = true;
// If we are the host wait for the game server to end
if(m_pDQRNet->IsHost() && g_NetworkManager.ServerStoppedValid())
{
// m_pDQRNet->EndGame();
g_NetworkManager.ServerStoppedWait();
g_NetworkManager.ServerStoppedDestroy();
}
return _LeaveGame(bMigrateHost, true);;
}
bool CPlatformNetworkManagerDurango::_LeaveGame(bool bMigrateHost, bool bLeaveRoom)
{
m_bLeavingGame = true;
m_bLeaveGameOnTick = true;
m_migrateHostOnLeave = bMigrateHost;
return true;
}
void CPlatformNetworkManagerDurango::HostGame(int localUsersMask, bool bOnlineGame, bool bIsPrivate, unsigned char publicSlots /*= MINECRAFT_NET_MAX_PLAYERS*/, unsigned char privateSlots /*= 0*/)
{
// #ifdef _XBOX
// 4J Stu - We probably did this earlier as well, but just to be sure!
SetLocalGame( !bOnlineGame );
SetPrivateGame( bIsPrivate );
SystemFlagReset();
// Make sure that the Primary Pad is in by default
localUsersMask |= GetLocalPlayerMask( g_NetworkManager.GetPrimaryPad() );
m_bLeavingGame = false;
_HostGame( localUsersMask, publicSlots, privateSlots );
//#endif
}
void CPlatformNetworkManagerDurango::_HostGame(int usersMask, unsigned char publicSlots /*= MINECRAFT_NET_MAX_PLAYERS*/, unsigned char privateSlots /*= 0*/)
{
memset(&m_hostGameSessionData,0,sizeof(m_hostGameSessionData));
m_hostGameSessionData.netVersion = MINECRAFT_NET_VERSION;
m_hostGameSessionData.isReadyToJoin = false;
m_hostGameSessionData.m_uiGameHostSettings = app.GetGameHostOption(eGameHostOption_All);
m_hostGameSessionIsJoinable = !IsPrivateGame();
m_pDQRNet->CreateAndJoinSession(usersMask, (unsigned char *)&m_hostGameSessionData, sizeof(m_hostGameSessionData), IsLocalGame() );
}
bool CPlatformNetworkManagerDurango::_StartGame()
{
return true;
}
int CPlatformNetworkManagerDurango::JoinGame(FriendSessionInfo *searchResult, int localUsersMask, int primaryUserIndex)
{
app.DebugPrintf("Joining game party from search result\n");
int joinPlayerCount = 0;
for( int i = 0; i < DQRNetworkManager::MAX_LOCAL_PLAYER_COUNT; i++ )
{
if( localUsersMask & ( 1 << i ) )
{
// Check if this joining user is already in the session, in which case we don't need to count it
bool isJoiningUser = false;
for( int j = 0; j < searchResult->searchResult.m_usedSlotCount; j++ )
{
Platform::String^ xuid = ref new Platform::String(searchResult->searchResult.m_sessionXuids[j].c_str());
if( xuid == ProfileManager.GetUser(i)->XboxUserId )
{
app.DebugPrintf("Joining user found to be already in session, so won't be counting to our total\n");
isJoiningUser = true;
break;
}
}
if( !isJoiningUser )
{
joinPlayerCount++;
}
}
}
app.DebugPrintf("Used slots: %d, fully playing players: %d, trying to join %d\n", searchResult->searchResult.m_usedSlotCount, searchResult->searchResult.m_playerCount, joinPlayerCount);
GameSessionData *gameSession = (GameSessionData *)(&searchResult->data);
if( ( searchResult->searchResult.m_usedSlotCount + joinPlayerCount ) > DQRNetworkManager::MAX_ONLINE_PLAYER_COUNT )
{
return CGameNetworkManager::JOINGAME_FAIL_SERVER_FULL;
}
if(m_pDQRNet->JoinPartyFromSearchResult(&searchResult->searchResult, localUsersMask))
{
app.DebugPrintf("Join success\n");
return CGameNetworkManager::JOINGAME_SUCCESS;
}
else
{
app.DebugPrintf("Join fail\n");
return CGameNetworkManager::JOINGAME_FAIL_GENERAL;
}
}
void CPlatformNetworkManagerDurango::CancelJoinGame()
{
m_pDQRNet->CancelJoinPartyFromSearchResult();
}
bool CPlatformNetworkManagerDurango::SetLocalGame(bool isLocal)
{
m_bIsOfflineGame = isLocal;
return true;
}
void CPlatformNetworkManagerDurango::SetPrivateGame(bool isPrivate)
{
app.DebugPrintf("Setting as private game: %s\n", isPrivate ? "yes" : "no" );
m_bIsPrivateGame = isPrivate;
}
void CPlatformNetworkManagerDurango::RegisterPlayerChangedCallback(int iPad, void (*callback)(void *callbackParam, INetworkPlayer *pPlayer, bool leaving), void *callbackParam)
{
playerChangedCallback[iPad] = callback;
playerChangedCallbackParam[iPad] = callbackParam;
}
void CPlatformNetworkManagerDurango::UnRegisterPlayerChangedCallback(int iPad, void (*callback)(void *callbackParam, INetworkPlayer *pPlayer, bool leaving), void *callbackParam)
{
if(playerChangedCallbackParam[iPad] == callbackParam)
{
playerChangedCallback[iPad] = NULL;
playerChangedCallbackParam[iPad] = NULL;
}
}
void CPlatformNetworkManagerDurango::HandleSignInChange()
{
return;
}
void CPlatformNetworkManagerDurango::HandleAddLocalPlayerFailed(int idx, bool serverFull)
{
g_NetworkManager.AddLocalPlayerFailed(idx, serverFull);
}
void CPlatformNetworkManagerDurango::HandleDisconnect(bool bLostRoomOnly)
{
g_NetworkManager.HandleDisconnect(bLostRoomOnly);
}
bool CPlatformNetworkManagerDurango::_RunNetworkGame()
{
m_pDQRNet->StartGame();
m_hostGameSessionData.isReadyToJoin = true;
m_pDQRNet->UpdateCustomSessionData();
return true;
}
void CPlatformNetworkManagerDurango::UpdateAndSetGameSessionData(INetworkPlayer *pNetworkPlayerLeaving /*= NULL*/)
{
if( this->m_bLeavingGame )
return;
if( GetHostPlayer() == NULL )
return;
m_hostGameSessionData.m_uiGameHostSettings = app.GetGameHostOption(eGameHostOption_All);
m_pDQRNet->UpdateCustomSessionData();
}
int CPlatformNetworkManagerDurango::RemovePlayerOnSocketClosedThreadProc( void* lpParam )
{
INetworkPlayer *pNetworkPlayer = (INetworkPlayer *)lpParam;
Socket *socket = pNetworkPlayer->GetSocket();
if( socket != NULL )
{
//printf("Waiting for socket closed event\n");
socket->m_socketClosedEvent->WaitForSignal(INFINITE);
//printf("Socket closed event has fired\n");
// 4J Stu - Clear our reference to this socket
pNetworkPlayer->SetSocket( NULL );
delete socket;
}
return g_pPlatformNetworkManager->RemoveLocalPlayer( pNetworkPlayer );
}
bool CPlatformNetworkManagerDurango::RemoveLocalPlayer( INetworkPlayer *pNetworkPlayer )
{
return true;
}
CPlatformNetworkManagerDurango::PlayerFlags::PlayerFlags(INetworkPlayer *pNetworkPlayer, unsigned int count)
{
// 4J Stu - Don't assert, just make it a multiple of 8! This count is calculated from a load of separate values,
// and makes tweaking world/render sizes a pain if we hit an assert here
count = (count + 8 - 1) & ~(8 - 1);
//assert( ( count % 8 ) == 0 );
this->m_pNetworkPlayer = pNetworkPlayer;
this->flags = new unsigned char [ count / 8 ];
memset( this->flags, 0, count / 8 );
this->count = count;
}
CPlatformNetworkManagerDurango::PlayerFlags::~PlayerFlags()
{
delete [] flags;
}
// Add a player to the per system flag storage - if we've already got a player from that system, copy its flags over
void CPlatformNetworkManagerDurango::SystemFlagAddPlayer(INetworkPlayer *pNetworkPlayer)
{
PlayerFlags *newPlayerFlags = new PlayerFlags( pNetworkPlayer, m_flagIndexSize);
// If any of our existing players are on the same system, then copy over flags from that one
for( unsigned int i = 0; i < m_playerFlags.size(); i++ )
{
if( pNetworkPlayer->IsSameSystem(m_playerFlags[i]->m_pNetworkPlayer) )
{
memcpy( newPlayerFlags->flags, m_playerFlags[i]->flags, m_playerFlags[i]->count / 8 );
break;
}
}
m_playerFlags.push_back(newPlayerFlags);
}
// Remove a player from the per system flag storage - just maintains the m_playerFlags vector without any gaps in it
void CPlatformNetworkManagerDurango::SystemFlagRemovePlayer(INetworkPlayer *pNetworkPlayer)
{
for( unsigned int i = 0; i < m_playerFlags.size(); i++ )
{
if( m_playerFlags[i]->m_pNetworkPlayer == pNetworkPlayer )
{
delete m_playerFlags[i];
m_playerFlags[i] = m_playerFlags.back();
m_playerFlags.pop_back();
return;
}
}
}
void CPlatformNetworkManagerDurango::SystemFlagReset()
{
for( unsigned int i = 0; i < m_playerFlags.size(); i++ )
{
delete m_playerFlags[i];
}
m_playerFlags.clear();
}
// Set a per system flag - this is done by setting the flag on every player that shares that system
void CPlatformNetworkManagerDurango::SystemFlagSet(INetworkPlayer *pNetworkPlayer, int index)
{
if( ( index < 0 ) || ( index >= m_flagIndexSize ) ) return;
if( pNetworkPlayer == NULL ) return;
for( unsigned int i = 0; i < m_playerFlags.size(); i++ )
{
if( pNetworkPlayer->IsSameSystem(m_playerFlags[i]->m_pNetworkPlayer) )
{
m_playerFlags[i]->flags[ index / 8 ] |= ( 128 >> ( index % 8 ) );
}
}
}
// Get value of a per system flag - can be read from the flags of the passed in player as anything else sent to that
// system should also have been duplicated here
bool CPlatformNetworkManagerDurango::SystemFlagGet(INetworkPlayer *pNetworkPlayer, int index)
{
if( ( index < 0 ) || ( index >= m_flagIndexSize ) ) return false;
if( pNetworkPlayer == NULL )
{
return false;
}
for( unsigned int i = 0; i < m_playerFlags.size(); i++ )
{
if( m_playerFlags[i]->m_pNetworkPlayer == pNetworkPlayer )
{
return ( ( m_playerFlags[i]->flags[ index / 8 ] & ( 128 >> ( index % 8 ) ) ) != 0 );
}
}
return false;
}
wstring CPlatformNetworkManagerDurango::GatherStats()
{
return L"";
}
wstring CPlatformNetworkManagerDurango::GatherRTTStats()
{
return L"";
}
void CPlatformNetworkManagerDurango::TickSearch()
{
if( m_bSearchPending )
{
if( !m_pDQRNet->FriendPartyManagerIsBusy() )
{
m_searchResultsCount = m_pDQRNet->FriendPartyManagerGetCount();
delete [] m_pSearchResults;
m_pSearchResults = new DQRNetworkManager::SessionSearchResult[m_searchResultsCount];
for( int i = 0; i < m_searchResultsCount; i++ )
{
m_pDQRNet->FriendPartyManagerGetSessionInfo(i, &m_pSearchResults[i] );
}
m_bSearchPending = false;
if( m_SessionsUpdatedCallback != NULL ) m_SessionsUpdatedCallback(m_pSearchParam);
}
}
else
{
if( !m_pDQRNet->FriendPartyManagerIsBusy() )
{
// Don't start searches unless we have registered a callback
if( m_SessionsUpdatedCallback != NULL && (m_lastSearchStartTime + MINECRAFT_DURANGO_PARTY_SEARCH_DELAY_MILLISECONDS) < GetTickCount() )
{
if( m_pDQRNet->FriendPartyManagerSearch() );
{
m_bSearchPending = true;
m_lastSearchStartTime = GetTickCount();
}
}
}
}
}
vector<FriendSessionInfo *> *CPlatformNetworkManagerDurango::GetSessionList(int iPad, int localPlayers, bool partyOnly)
{
vector<FriendSessionInfo *> *filteredList = new vector<FriendSessionInfo *>();
for( int i = 0; i < m_searchResultsCount; i++ )
{
GameSessionData *gameSessionData = (GameSessionData *)m_pSearchResults[i].m_extData;
if( ( gameSessionData->netVersion == MINECRAFT_NET_VERSION ) &&
( gameSessionData->isReadyToJoin ) )
{
FriendSessionInfo *session = new FriendSessionInfo();
session->searchResult = m_pSearchResults[i];
session->searchResult.m_extData = NULL; // We have another copy of the GameSessionData, so don't need to make another copy of this here
session->displayLabelLength = session->searchResult.m_playerNames[0].size();
session->displayLabel = new wchar_t[ session->displayLabelLength + 1 ];
memcpy(&session->data, gameSessionData, sizeof(GameSessionData));
wcscpy_s(session->displayLabel, session->displayLabelLength + 1, session->searchResult.m_playerNames[0].c_str() );
filteredList->push_back(session);
}
}
return filteredList;
}
bool CPlatformNetworkManagerDurango::GetGameSessionInfo(int iPad, SessionID sessionId, FriendSessionInfo *foundSessionInfo)
{
return false;
}
void CPlatformNetworkManagerDurango::SetSessionsUpdatedCallback( void (*SessionsUpdatedCallback)(LPVOID pParam), LPVOID pSearchParam )
{
m_SessionsUpdatedCallback = SessionsUpdatedCallback; m_pSearchParam = pSearchParam;
}
void CPlatformNetworkManagerDurango::GetFullFriendSessionInfo( FriendSessionInfo *foundSession, void (* FriendSessionUpdatedFn)(bool success, void *pParam), void *pParam )
{
FriendSessionUpdatedFn(true, pParam);
}
void CPlatformNetworkManagerDurango::ForceFriendsSessionRefresh()
{
app.DebugPrintf("Resetting friends session search data\n");
m_lastSearchStartTime = 0;
m_searchResultsCount = 0;
delete [] m_pSearchResults;
m_pSearchResults = NULL;
}
INetworkPlayer *CPlatformNetworkManagerDurango::addNetworkPlayer(DQRNetworkPlayer *pDQRPlayer)
{
NetworkPlayerDurango *pNetworkPlayer = new NetworkPlayerDurango(pDQRPlayer);
pDQRPlayer->SetCustomDataValue((ULONG_PTR)pNetworkPlayer);
currentNetworkPlayers.push_back( pNetworkPlayer );
return pNetworkPlayer;
}
void CPlatformNetworkManagerDurango::removeNetworkPlayer(DQRNetworkPlayer *pDQRPlayer)
{
INetworkPlayer *pNetworkPlayer = getNetworkPlayer(pDQRPlayer);
for( AUTO_VAR(it, currentNetworkPlayers.begin()); it != currentNetworkPlayers.end(); it++ )
{
if( *it == pNetworkPlayer )
{
currentNetworkPlayers.erase(it);
return;
}
}
}
INetworkPlayer *CPlatformNetworkManagerDurango::getNetworkPlayer(DQRNetworkPlayer *pDQRPlayer)
{
return pDQRPlayer ? (INetworkPlayer *)(pDQRPlayer->GetCustomDataValue()) : NULL;
}
INetworkPlayer *CPlatformNetworkManagerDurango::GetLocalPlayerByUserIndex(int userIndex )
{
return getNetworkPlayer(m_pDQRNet->GetLocalPlayerByUserIndex(userIndex));
}
INetworkPlayer *CPlatformNetworkManagerDurango::GetPlayerByIndex(int playerIndex)
{
return getNetworkPlayer(m_pDQRNet->GetPlayerByIndex(playerIndex));
}
INetworkPlayer * CPlatformNetworkManagerDurango::GetPlayerByXuid(PlayerUID xuid)
{
return getNetworkPlayer(m_pDQRNet->GetPlayerByXuid(xuid)) ;
}
INetworkPlayer * CPlatformNetworkManagerDurango::GetPlayerBySmallId(unsigned char smallId)
{
return getNetworkPlayer(m_pDQRNet->GetPlayerBySmallId(smallId));
}
wstring CPlatformNetworkManagerDurango::GetDisplayNameByGamertag(wstring gamertag)
{
return m_pDQRNet->GetDisplayNameByGamertag(gamertag);
}
INetworkPlayer *CPlatformNetworkManagerDurango::GetHostPlayer()
{
return getNetworkPlayer(m_pDQRNet->GetHostPlayer());
}
bool CPlatformNetworkManagerDurango::IsHost()
{
return m_pDQRNet->IsHost() && !m_bHostChanged;
}
bool CPlatformNetworkManagerDurango::JoinGameFromInviteInfo( int userIndex, int userMask, const INVITE_INFO *pInviteInfo)
{
m_pDQRNet->m_currentUserMask = userMask;
m_pDQRNet->JoinSessionFromInviteInfo(userMask);
return true;
}
void CPlatformNetworkManagerDurango::SetSessionTexturePackParentId( int id )
{
m_hostGameSessionData.texturePackParentId = id;
}
void CPlatformNetworkManagerDurango::SetSessionSubTexturePackId( int id )
{
m_hostGameSessionData.subTexturePackId = id;
}
void CPlatformNetworkManagerDurango::Notify(int ID, ULONG_PTR Param)
{
}
bool CPlatformNetworkManagerDurango::IsInSession()
{
return m_pDQRNet->IsInSession();
}
bool CPlatformNetworkManagerDurango::IsInGameplay()
{
return m_pDQRNet->GetState() == DQRNetworkManager::DNM_STATE_PLAYING;
}
bool CPlatformNetworkManagerDurango::IsReadyToPlayOrIdle()
{
return m_pDQRNet->IsReadyToPlayOrIdle();
}
bool CPlatformNetworkManagerDurango::IsSessionJoinable()
{
return m_hostGameSessionIsJoinable;
}

View File

@@ -0,0 +1,168 @@
#pragma once
using namespace std;
#include <vector>
#include "..\..\..\Minecraft.World\C4JThread.h"
#include "..\..\Common\Network\NetworkPlayerInterface.h"
#include "..\..\Common\Network\PlatformNetworkManagerInterface.h"
#include "..\..\Common\Network\SessionInfo.h"
#include "DQRNetworkManager.h"
#define MINECRAFT_DURANGO_PARTY_SEARCH_DELAY_MILLISECONDS 30000
class CPlatformNetworkManagerDurango : public CPlatformNetworkManager, IDQRNetworkManagerListener
{
friend class CGameNetworkManager;
public:
virtual bool Initialise(CGameNetworkManager *pGameNetworkManager, int flagIndexSize);
virtual void Terminate();
virtual int GetJoiningReadyPercentage();
virtual int CorrectErrorIDS(int IDS);
virtual void DoWork();
virtual int GetPlayerCount();
virtual int GetOnlinePlayerCount();
virtual int GetLocalPlayerMask(int playerIndex);
virtual bool AddLocalPlayerByUserIndex( int userIndex );
virtual bool RemoveLocalPlayerByUserIndex( int userIndex );
virtual INetworkPlayer *GetLocalPlayerByUserIndex( int userIndex );
virtual INetworkPlayer *GetPlayerByIndex(int playerIndex);
virtual INetworkPlayer * GetPlayerByXuid(PlayerUID xuid);
virtual INetworkPlayer * GetPlayerBySmallId(unsigned char smallId);
virtual wstring GetDisplayNameByGamertag(wstring gamertag);
virtual bool ShouldMessageForFullSession();
virtual INetworkPlayer *GetHostPlayer();
virtual bool IsHost();
virtual bool JoinGameFromInviteInfo( int userIndex, int userMask, const INVITE_INFO *pInviteInfo);
virtual bool LeaveGame(bool bMigrateHost);
virtual bool IsInSession();
virtual bool IsInGameplay();
virtual bool IsReadyToPlayOrIdle();
virtual bool IsInStatsEnabledSession();
virtual bool SessionHasSpace(unsigned int spaceRequired = 1);
virtual void SendInviteGUI(int quadrant);
virtual bool IsAddingPlayer();
virtual void HostGame(int localUsersMask, bool bOnlineGame, bool bIsPrivate, unsigned char publicSlots = MINECRAFT_NET_MAX_PLAYERS, unsigned char privateSlots = 0);
virtual int JoinGame(FriendSessionInfo *searchResult, int localUsersMask, int primaryUserIndex );
virtual void CancelJoinGame();
virtual bool SetLocalGame(bool isLocal);
virtual bool IsLocalGame() { return m_bIsOfflineGame; }
virtual void SetPrivateGame(bool isPrivate);
virtual bool IsPrivateGame() { return m_bIsPrivateGame; }
virtual bool IsLeavingGame() { return m_bLeavingGame; }
virtual void ResetLeavingGame() { m_bLeavingGame = false; }
virtual void RegisterPlayerChangedCallback(int iPad, void (*callback)(void *callbackParam, INetworkPlayer *pPlayer, bool leaving), void *callbackParam);
virtual void UnRegisterPlayerChangedCallback(int iPad, void (*callback)(void *callbackParam, INetworkPlayer *pPlayer, bool leaving), void *callbackParam);
virtual void HandleSignInChange();
virtual bool _RunNetworkGame();
private:
bool isSystemPrimaryPlayer(DQRNetworkPlayer *pDQRPlayer);
virtual bool _LeaveGame(bool bMigrateHost, bool bLeaveRoom);
virtual void _HostGame(int dwUsersMask, unsigned char publicSlots = MINECRAFT_NET_MAX_PLAYERS, unsigned char privateSlots = 0);
virtual bool _StartGame();
DQRNetworkManager * m_pDQRNet; // pointer to SQRNetworkManager interface
HANDLE m_notificationListener;
vector<DQRNetworkPlayer *> m_machineDQRPrimaryPlayers; // collection of players that we deem to be the main one for that system
bool m_bLeavingGame;
bool m_bLeaveGameOnTick;
bool m_migrateHostOnLeave;
bool m_bHostChanged;
bool m_bIsOfflineGame;
bool m_bIsPrivateGame;
int m_flagIndexSize;
// This is only maintained by the host, and is not valid on client machines
GameSessionData m_hostGameSessionData;
bool m_hostGameSessionIsJoinable;
CGameNetworkManager *m_pGameNetworkManager;
public:
virtual void UpdateAndSetGameSessionData(INetworkPlayer *pNetworkPlayerLeaving = NULL);
private:
// TODO 4J Stu - Do we need to be able to have more than one of these?
void (*playerChangedCallback[XUSER_MAX_COUNT])(void *callbackParam, INetworkPlayer *pPlayer, bool leaving);
void *playerChangedCallbackParam[XUSER_MAX_COUNT];
static int RemovePlayerOnSocketClosedThreadProc( void* lpParam );
virtual bool RemoveLocalPlayer( INetworkPlayer *pNetworkPlayer );
// Things for handling per-system flags
class PlayerFlags
{
public:
INetworkPlayer *m_pNetworkPlayer;
unsigned char *flags;
unsigned int count;
PlayerFlags(INetworkPlayer *pNetworkPlayer, unsigned int count);
~PlayerFlags();
};
vector<PlayerFlags *> m_playerFlags;
void SystemFlagAddPlayer(INetworkPlayer *pNetworkPlayer);
void SystemFlagRemovePlayer(INetworkPlayer *pNetworkPlayer);
void SystemFlagReset();
public:
virtual void SystemFlagSet(INetworkPlayer *pNetworkPlayer, int index);
virtual bool SystemFlagGet(INetworkPlayer *pNetworkPlayer, int index);
// For telemetry
private:
float m_lastPlayerEventTimeStart;
public:
wstring GatherStats();
wstring GatherRTTStats();
private:
vector<FriendSessionInfo *> friendsSessions[XUSER_MAX_COUNT];
int m_searchResultsCount;
int m_lastSearchStartTime;
// The results that will be filled in with the current search
DQRNetworkManager::SessionSearchResult *m_pSearchResults;
int m_lastSearchPad;
bool m_bSearchResultsReady;
bool m_bSearchPending;
LPVOID m_pSearchParam;
void (*m_SessionsUpdatedCallback)(LPVOID pParam);
void TickSearch();
vector<INetworkPlayer *>currentNetworkPlayers;
INetworkPlayer *addNetworkPlayer(DQRNetworkPlayer *pDQRPlayer);
void removeNetworkPlayer(DQRNetworkPlayer *pDQRPlayer);
static INetworkPlayer *getNetworkPlayer(DQRNetworkPlayer *pDQRPlayer);
virtual void SetSessionTexturePackParentId( int id );
virtual void SetSessionSubTexturePackId( int id );
virtual void Notify(int ID, ULONG_PTR Param);
public:
virtual vector<FriendSessionInfo *> *GetSessionList(int iPad, int localPlayers, bool partyOnly);
virtual bool GetGameSessionInfo(int iPad, SessionID sessionId,FriendSessionInfo *foundSession);
virtual void SetSessionsUpdatedCallback( void (*SessionsUpdatedCallback)(LPVOID pParam), LPVOID pSearchParam );
virtual void GetFullFriendSessionInfo( FriendSessionInfo *foundSession, void (* FriendSessionUpdatedFn)(bool success, void *pParam), void *pParam );
virtual void ForceFriendsSessionRefresh();
// ... and the new ones that have been converted to IDQRNetworkManagerListener
virtual void HandleDataReceived(DQRNetworkPlayer *playerFrom, DQRNetworkPlayer *playerTo, unsigned char *data, unsigned int dataSize);
virtual void HandlePlayerJoined(DQRNetworkPlayer *player);
virtual void HandlePlayerLeaving(DQRNetworkPlayer *player);
virtual void HandleStateChange(DQRNetworkManager::eDQRNetworkManagerState oldState, DQRNetworkManager::eDQRNetworkManagerState newState);
// virtual void HandleResyncPlayerRequest(DQRNetworkPlayer **aPlayers);
virtual void HandleAddLocalPlayerFailed(int idx, bool serverFull);
virtual void HandleDisconnect(bool bLostRoomOnly);
virtual void HandleInviteReceived(int playerIndex, DQRNetworkManager::SessionInfo *pInviteInfo);
virtual bool IsSessionJoinable();
};

View File

@@ -0,0 +1,156 @@
/*
base64.cpp and base64.h
Copyright (C) 2004-2008 Ren<65> Nyffenegger
This source code is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this source code must not be misrepresented; you must not
claim that you wrote the original source code. If you use this source code
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original source code.
3. This notice may not be removed or altered from any source distribution.
Ren<65> Nyffenegger rene.nyffenegger@adp-gmbh.ch
*/
#include "stdafx.h"
#include "base64.h"
#include <iostream>
static const std::wstring base64_chars =
L"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
L"abcdefghijklmnopqrstuvwxyz"
L"0123456789+/";
static inline bool is_base64(wchar_t c)
{
return (isalnum(c) || (c == L'+') || (c == L'/'));
}
// 4J changed to use Platform::String
Platform::String^ base64_encode(unsigned char* chars_to_encode, unsigned int in_len)
{
std::wstring ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
while (in_len--)
{
char_array_3[i++] = *(chars_to_encode++);
if (i == 3)
{
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for(int ii = 0; (ii <4) ; ii++)
{
ret += base64_chars[char_array_4[ii]];
}
i = 0;
}
}
if (i)
{
for(j = i; j < 3; j++)
{
char_array_3[j] = '\0';
}
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; (j < i + 1); j++)
{
ret += base64_chars[char_array_4[j]];
}
while((i++ < 3))
{
ret += L'=';
}
}
return ref new Platform::String(ret.c_str());
}
void base64_decode(Platform::String ^encoded_string, unsigned char *output, unsigned int out_len)
{
int in_len = encoded_string->Length();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4];
unsigned char char_array_3[3];
unsigned char *pucOut = output;
while (in_len-- && ( encoded_string->Data()[in_] != L'=') && is_base64(encoded_string->Data()[in_]))
{
char_array_4[i++] = (unsigned char)(encoded_string->Data()[in_]);
in_++;
if (i ==4)
{
for (i = 0; i <4; i++)
{
char_array_4[i] = (unsigned char )base64_chars.find(char_array_4[i]);
}
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++)
{
*pucOut++ = char_array_3[i];
if( ( pucOut - output ) >= out_len ) return;
}
i = 0;
}
}
if(i)
{
for (j = i; j <4; j++)
{
char_array_4[j] = 0;
}
for (j = 0; j <4; j++)
{
char_array_4[j] = (unsigned char )base64_chars.find(char_array_4[j]);
}
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++)
{
*pucOut++ = char_array_3[j];
if( ( pucOut - output ) >= out_len ) return;
}
}
}

View File

@@ -0,0 +1,6 @@
#pragma once
#include <string>
Platform::String^ base64_encode(unsigned char* chars_to_encode, unsigned int in_len);
void base64_decode(Platform::String ^encoded_string, unsigned char *output, unsigned int out_len);