Browse Source

Added everything needed to run EOS on Xbox One, including a whole bunch of memory management nonsense that EOS apparently needs to initialize at all.

XSTS authentication currently fails, but that's likely to do with configuration outside the app code.
master
Nico de Poel 5 years ago
parent
commit
ac2d028f9b
  1. 8
      Assets/Plugins/EpicOnlineServices/Bin/XSX.meta
  2. BIN
      Assets/Plugins/EpicOnlineServices/Bin/XSX/EOSSDK-XSX-Shipping.dll
  3. 72
      Assets/Plugins/EpicOnlineServices/Bin/XSX/EOSSDK-XSX-Shipping.dll.meta
  4. 8
      Assets/Plugins/EpicOnlineServices/Bin/XboxOneGDK.meta
  5. BIN
      Assets/Plugins/EpicOnlineServices/Bin/XboxOneGDK/EOSSDK-XboxOneGDK-Shipping.dll
  6. 72
      Assets/Plugins/EpicOnlineServices/Bin/XboxOneGDK/EOSSDK-XboxOneGDK-Shipping.dll.meta
  7. 20
      Assets/Plugins/EpicOnlineServices/Source/Core/Config.cs
  8. 154
      Assets/Scripts/MagnificentVoiceChat.cs
  9. 32
      Assets/Scripts/SystemLibrary.cs
  10. 2
      ProjectSettings/GameCoreXboxOneSettings.asset
  11. 6
      ProjectSettings/XboxOneGame.config

8
Assets/Plugins/EpicOnlineServices/Bin/XSX.meta

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1618c8f79f89c134088963ea48d5feec
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Plugins/EpicOnlineServices/Bin/XSX/EOSSDK-XSX-Shipping.dll

72
Assets/Plugins/EpicOnlineServices/Bin/XSX/EOSSDK-XSX-Shipping.dll.meta

@ -0,0 +1,72 @@
fileFormatVersion: 2
guid: bdedd06abc64213438d3d7cdf13f6c2f
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Editor: 1
Exclude GameCoreScarlett: 0
Exclude GameCoreXboxOne: 1
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude PS4: 1
Exclude PS5: 1
Exclude Win: 1
Exclude Win64: 1
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
GameCoreScarlett: GameCoreScarlett
second:
enabled: 1
settings: {}
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
userData:
assetBundleName:
assetBundleVariant:

8
Assets/Plugins/EpicOnlineServices/Bin/XboxOneGDK.meta

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 83f49b86b5dfd7c4d8b27c6610a41102
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Plugins/EpicOnlineServices/Bin/XboxOneGDK/EOSSDK-XboxOneGDK-Shipping.dll

72
Assets/Plugins/EpicOnlineServices/Bin/XboxOneGDK/EOSSDK-XboxOneGDK-Shipping.dll.meta

@ -0,0 +1,72 @@
fileFormatVersion: 2
guid: 825f617a5f5c90649a592f99a77ed57f
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Editor: 1
Exclude GameCoreScarlett: 1
Exclude GameCoreXboxOne: 0
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude PS4: 1
Exclude PS5: 1
Exclude Win: 1
Exclude Win64: 1
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
GameCoreXboxOne: GameCoreXboxOne
second:
enabled: 1
settings: {}
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
userData:
assetBundleName:
assetBundleVariant:

20
Assets/Plugins/EpicOnlineServices/Source/Core/Config.cs

@ -8,7 +8,7 @@
#define EOS_EDITOR #define EOS_EDITOR
#endif #endif
#if UNITY_EDITOR || UNITY_STANDALONE || UNITY_PS4 || UNITY_XBOXONE || UNITY_SWITCH || UNITY_IOS || UNITY_ANDROID
#if UNITY_EDITOR || UNITY_STANDALONE || UNITY_PS4 || UNITY_XBOXONE || UNITY_SWITCH || UNITY_IOS || UNITY_ANDROID || UNITY_PS5 || UNITY_GAMECORE
#define EOS_UNITY #define EOS_UNITY
#endif #endif
@ -40,6 +40,15 @@
#elif UNITY_ANDROID || __ANDROID__ #elif UNITY_ANDROID || __ANDROID__
#define EOS_PLATFORM_ANDROID #define EOS_PLATFORM_ANDROID
#elif UNITY_PS5
#define EOS_PLATFORM_PS5
#elif UNITY_GAMECORE_XBOXONE
#define EOS_PLATFORM_XBOXONE_GDK
#elif UNITY_GAMECORE_SCARLETT
#define EOS_PLATFORM_XBOXSERIESX
#endif #endif
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -79,6 +88,15 @@ namespace Epic.OnlineServices
#elif EOS_PLATFORM_ANDROID #elif EOS_PLATFORM_ANDROID
"EOSSDK" "EOSSDK"
#elif EOS_PLATFORM_PS4
""
#elif EOS_PLATFORM_PS5
""
#elif EOS_PLATFORM_XBOXONE_GDK
"EOSSDK-XboxOneGDK-Shipping.dll"
#elif EOS_PLATFORM_XBOXSERIESX
"EOSSDK-XSX-Shipping.dll"
#else #else
#error Unable to determine the name of the EOSSDK library. Ensure you have set the correct EOS compilation symbol for the current platform, such as EOS_PLATFORM_WINDOWS_32 or EOS_PLATFORM_WINDOWS_64, so that the correct EOSSDK library can be targeted. #error Unable to determine the name of the EOSSDK library. Ensure you have set the correct EOS compilation symbol for the current platform, such as EOS_PLATFORM_WINDOWS_32 or EOS_PLATFORM_WINDOWS_64, so that the correct EOSSDK library can be targeted.
"EOSSDK-UnknownPlatform-Shipping" "EOSSDK-UnknownPlatform-Shipping"

154
Assets/Scripts/MagnificentVoiceChat.cs

@ -2,6 +2,7 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using Epic.OnlineServices; using Epic.OnlineServices;
using Epic.OnlineServices.Auth; using Epic.OnlineServices.Auth;
@ -16,6 +17,11 @@ using Credentials = Epic.OnlineServices.Auth.Credentials;
using LoginCallbackInfo = Epic.OnlineServices.Auth.LoginCallbackInfo; using LoginCallbackInfo = Epic.OnlineServices.Auth.LoginCallbackInfo;
using LoginOptions = Epic.OnlineServices.Auth.LoginOptions; using LoginOptions = Epic.OnlineServices.Auth.LoginOptions;
#if UNITY_GAMECORE
using Unity.GameCore;
using UnityEngine.GameCore;
#endif
public class MagnificentVoiceChat : MonoBehaviour public class MagnificentVoiceChat : MonoBehaviour
{ {
private const string DebugLobbyId = "a0f6a51f-6b61-4c95-9ed8-a1d508fe4eb6"; private const string DebugLobbyId = "a0f6a51f-6b61-4c95-9ed8-a1d508fe4eb6";
@ -35,6 +41,8 @@ public class MagnificentVoiceChat : MonoBehaviour
private EOSVoiceChat voiceChat; private EOSVoiceChat voiceChat;
private string xstsToken;
private string XAudio29DllPath => private string XAudio29DllPath =>
#if UNITY_EDITOR #if UNITY_EDITOR
Path.Combine(Application.dataPath, @"Plugins\EpicOnlineServices\Bin\x64\xaudio2_9redist.dll"); Path.Combine(Application.dataPath, @"Plugins\EpicOnlineServices\Bin\x64\xaudio2_9redist.dll");
@ -42,27 +50,63 @@ public class MagnificentVoiceChat : MonoBehaviour
Path.Combine(Application.dataPath, @"Plugins\x86_64\xaudio2_9redist.dll"); Path.Combine(Application.dataPath, @"Plugins\x86_64\xaudio2_9redist.dll");
#endif #endif
void Start()
IEnumerator Start()
{ {
#if UNITY_EDITOR
#if UNITY_EDITOR && UNITY_STANDALONE
LoadLibrary(); LoadLibrary();
#elif UNITY_GAMECORE
int hresult = SDK.XGameRuntimeInitialize();
if (HR.FAILED(hresult))
{
Debug.LogError($"Error initializing game runtime, hresult = 0x{hresult:X}");
yield break;
}
Debug.Log("GXDK game runtime initialized.");
hresult = SDK.XBL.XblInitialize(GameCoreSettings.SCID);
if (HR.FAILED(hresult))
{
Debug.LogError($"Error initializing Xbox Live services, hresult = 0x{hresult:X}");
yield break;
}
hresult = SDK.XGameGetXboxTitleId(out uint titleId);
if (HR.FAILED(hresult))
{
Debug.LogError($"Error obtaining Xbox Title ID, hresult = 0x{hresult:X}");
yield break;
}
Debug.Log("Xbox Live services initialized.");
yield return new WaitForSeconds(5f);
#endif #endif
var result = PlatformInterface.Initialize(new InitializeOptions var result = PlatformInterface.Initialize(new InitializeOptions
{ {
ProductName = "WW1 Test",
ProductVersion = "0.1",
ProductName = "WW1Test",
ProductVersion = "1.0.0.0",
#if UNITY_GAMECORE
AllocateMemoryFunction = Marshal.GetFunctionPointerForDelegate((SystemLibrary.AllocateMemoryFunc)SystemLibrary.AllocateMemory),
ReallocateMemoryFunction = Marshal.GetFunctionPointerForDelegate((SystemLibrary.ReallocateMemoryFunc)SystemLibrary.ReallocateMemory),
ReleaseMemoryFunction = Marshal.GetFunctionPointerForDelegate((SystemLibrary.ReleaseMemoryFunc)SystemLibrary.ReleaseMemory),
#endif
}); });
if (result != Result.Success) if (result != Result.Success)
{ {
Debug.LogError("Failed to initialize EOS, result = " + result); Debug.LogError("Failed to initialize EOS, result = " + result);
return;
status.AppendLine("EOS initialization failed...");
yield break;
} }
LoggingInterface.SetLogLevel(LogCategory.AllCategories, LogLevel.Warning); LoggingInterface.SetLogLevel(LogCategory.AllCategories, LogLevel.Warning);
LoggingInterface.SetCallback(OnEOSLogMessage); LoggingInterface.SetCallback(OnEOSLogMessage);
#if UNITY_STANDALONE_WIN
Debug.Log("XAudio library path: " + XAudio29DllPath);
var options = new WindowsOptions // Okay so this will need to be platform-specific var options = new WindowsOptions // Okay so this will need to be platform-specific
{ {
ProductId = "b21a28c2c5404c8099d72f5a28c59c16", ProductId = "b21a28c2c5404c8099d72f5a28c59c16",
@ -90,8 +134,29 @@ public class MagnificentVoiceChat : MonoBehaviour
} }
}, },
}; };
#else
var options = new Options
{
ProductId = "b21a28c2c5404c8099d72f5a28c59c16",
SandboxId = "b630f2c3933a4838a971ce53d5b0db3b",
DeploymentId = "9a36f589572c492fbee14bd299173c12",
Debug.Log("XAudio library path: " + XAudio29DllPath);
ClientCredentials = new ClientCredentials
{
ClientId = "xyza7891UOFoUhfvfbKgO2xRCIiuAIjH",
ClientSecret = "NArwIQT1laFfsS7fdcN1MKDdgwy490w9MBJsAlHN4QI",
},
Flags = PlatformFlags.DisableOverlay,
CacheDirectory = null,
EncryptionKey = null,
IsServer = false,
RTCOptions = new RTCOptions(),
};
#endif
platformInterface = PlatformInterface.Create(options); platformInterface = PlatformInterface.Create(options);
@ -99,7 +164,7 @@ public class MagnificentVoiceChat : MonoBehaviour
{ {
Debug.LogError("Failed to create EOS platform interface"); Debug.LogError("Failed to create EOS platform interface");
status.AppendLine("Failed to create EOS platform interface"); status.AppendLine("Failed to create EOS platform interface");
return;
yield break;
} }
Debug.Log("EOS platform interface successfully created!"); Debug.Log("EOS platform interface successfully created!");
@ -120,22 +185,57 @@ public class MagnificentVoiceChat : MonoBehaviour
audioInterface = rtcInterface.GetAudioInterface(); audioInterface = rtcInterface.GetAudioInterface();
status.AppendLine("Audio interface: " + audioInterface); status.AppendLine("Audio interface: " + audioInterface);
authInterface.Login(new LoginOptions
{
Credentials = GetEpicCredentials(),
ScopeFlags = AuthScopeFlags.BasicProfile,
}, null, HandleLoginResult);
voiceChat = new EOSVoiceChat(lobbyInterface, rtcInterface, audioInterface, () => localProductUserId); voiceChat = new EOSVoiceChat(lobbyInterface, rtcInterface, audioInterface, () => localProductUserId);
voiceChat.OnChatConnected += () => status.AppendLine("Chat lobby successfully connected!"); voiceChat.OnChatConnected += () => status.AppendLine("Chat lobby successfully connected!");
voiceChat.OnChatConnectionFailed += () => status.AppendLine("Chat lobby connection failed..."); voiceChat.OnChatConnectionFailed += () => status.AppendLine("Chat lobby connection failed...");
voiceChat.OnChatDisconnected += () => status.AppendLine("Chat lobby disconnected"); voiceChat.OnChatDisconnected += () => status.AppendLine("Chat lobby disconnected");
voiceChat.OnChatUserJoined += userId => status.AppendLine($"Chat user {userId} joined"); voiceChat.OnChatUserJoined += userId => status.AppendLine($"Chat user {userId} joined");
voiceChat.OnChatUserLeft += userId => status.AppendLine($"Chat user {userId} left"); voiceChat.OnChatUserLeft += userId => status.AppendLine($"Chat user {userId} left");
#if UNITY_GAMECORE
SDK.XUserAddAsync(XUserAddOptions.AddDefaultUserAllowingUI, (hr, userHandle) =>
{
if (HR.FAILED(hr))
{
Debug.LogError($"Add Xbox user failed, hresult = 0x{hr:X}");
status.AppendLine("Add Xbox user failed...");
return;
} }
status.AppendLine("Xbox user added");
SDK.XUserGetTokenAndSignatureUtf16Async(userHandle, XUserGetTokenAndSignatureOptions.ForceRefresh,
"GET", "https://api.epicgames.dev/", null, null, HandleTokenAndSignature);
});
yield return new WaitUntil(() => !string.IsNullOrEmpty(xstsToken));
#endif
authInterface.Login(new LoginOptions
{
Credentials = GetEpicCredentials(),
ScopeFlags = AuthScopeFlags.BasicProfile,
}, null, HandleLoginResult);
}
#if UNITY_GAMECORE
private void HandleTokenAndSignature(int hresult, XUserGetTokenAndSignatureUtf16Data tokenAndSignature)
{
if (HR.FAILED(hresult))
{
Debug.LogError($"Xbox Live authentication failed, hresult = 0x{hresult:X}");
status.AppendLine("Xbox Live authentication failed...");
return;
}
Debug.Log($"Xbox Live authenticated, XSTS token = {tokenAndSignature.Token}");
status.AppendLine("Xbox Live successfully authenticated");
xstsToken = tokenAndSignature.Token;
}
#endif
private Credentials GetEpicCredentials() // This is platform-specific private Credentials GetEpicCredentials() // This is platform-specific
{ {
#if UNITY_STANDALONE
return new Credentials return new Credentials
{ {
// Type = LoginCredentialType.AccountPortal, // Use ExternalAuth on console platform // Type = LoginCredentialType.AccountPortal, // Use ExternalAuth on console platform
@ -143,6 +243,15 @@ public class MagnificentVoiceChat : MonoBehaviour
Type = LoginCredentialType.Developer, Type = LoginCredentialType.Developer,
Token = SystemInfo.deviceName, Token = SystemInfo.deviceName,
}; };
#elif UNITY_GAMECORE
return new Credentials
{
Type = LoginCredentialType.ExternalAuth,
ExternalType = ExternalCredentialType.XblXstsToken,
Token = xstsToken,
};
#elif UNITY_PS4 || UNITY_PS5
#endif
} }
private void HandleLoginResult(LoginCallbackInfo data) private void HandleLoginResult(LoginCallbackInfo data)
@ -155,14 +264,24 @@ public class MagnificentVoiceChat : MonoBehaviour
status.AppendLine("EOS login successful: " + localEpicAccountId); status.AppendLine("EOS login successful: " + localEpicAccountId);
authInterface.CopyUserAuthToken(new CopyUserAuthTokenOptions(), localEpicAccountId, out Token token); authInterface.CopyUserAuthToken(new CopyUserAuthTokenOptions(), localEpicAccountId, out Token token);
Debug.Log($"User auth access token: {token.AccessToken}");
connectInterface.Login(new Epic.OnlineServices.Connect.LoginOptions connectInterface.Login(new Epic.OnlineServices.Connect.LoginOptions
{ {
#if UNITY_STANDALONE
Credentials = new Epic.OnlineServices.Connect.Credentials Credentials = new Epic.OnlineServices.Connect.Credentials
{ {
Type = ExternalCredentialType.Epic, // Can be XSTS or PSN ID as well, platform-specific Type = ExternalCredentialType.Epic, // Can be XSTS or PSN ID as well, platform-specific
Token = token.AccessToken, Token = token.AccessToken,
}, },
#elif UNITY_GAMECORE
Credentials = new Epic.OnlineServices.Connect.Credentials
{
Type = ExternalCredentialType.XblXstsToken, // Isn't this kinda redundant? Should we be logging in to our Epic account here?
Token = xstsToken,
},
#elif UNITY_PS4 || UNITY_PS5
#endif
}, null, HandleConnectResult); }, null, HandleConnectResult);
break; break;
default: default:
@ -225,7 +344,12 @@ public class MagnificentVoiceChat : MonoBehaviour
void Update() void Update()
{ {
if (platformInterface != null)
platformInterface.Tick(); platformInterface.Tick();
#if UNITY_GAMECORE
SDK.XTaskQueueDispatch();
#endif
} }
private void OnDestroy() private void OnDestroy()
@ -244,12 +368,12 @@ public class MagnificentVoiceChat : MonoBehaviour
PlatformInterface.Shutdown(); PlatformInterface.Shutdown();
#if UNITY_EDITOR
#if UNITY_EDITOR && UNITY_STANDALONE
UnloadLibrary(); UnloadLibrary();
#endif #endif
} }
#if UNITY_EDITOR
#if UNITY_EDITOR && UNITY_STANDALONE
private IntPtr eosLbraryHandle; private IntPtr eosLbraryHandle;
private void LoadLibrary() private void LoadLibrary()

32
Assets/Scripts/SystemLibrary.cs

@ -1,6 +1,8 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using AOT;
using Epic.OnlineServices;
public static class SystemLibrary public static class SystemLibrary
{ {
@ -27,4 +29,34 @@ public static class SystemLibrary
[DllImport("kernel32")] [DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
#endif #endif
[UnmanagedFunctionPointer(Config.LibraryCallingConvention)]
public delegate IntPtr AllocateMemoryFunc(IntPtr size, IntPtr alignment);
[MonoPInvokeCallback(typeof(AllocateMemoryFunc))]
public static IntPtr AllocateMemory(IntPtr size, IntPtr alignment)
{
return Marshal.AllocHGlobal(size);
}
[UnmanagedFunctionPointer(Config.LibraryCallingConvention)]
public delegate IntPtr ReallocateMemoryFunc(IntPtr pointer, IntPtr size, IntPtr alignment);
[MonoPInvokeCallback(typeof(ReallocateMemoryFunc))]
public static IntPtr ReallocateMemory(IntPtr pointer, IntPtr size, IntPtr alignment)
{
if (pointer == IntPtr.Zero)
return Marshal.AllocHGlobal(size);
return Marshal.ReAllocHGlobal(pointer, size);
}
[UnmanagedFunctionPointer(Config.LibraryCallingConvention)]
public delegate void ReleaseMemoryFunc(IntPtr pointer);
[MonoPInvokeCallback(typeof(ReleaseMemoryFunc))]
public static void ReleaseMemory(IntPtr pointer)
{
Marshal.FreeHGlobal(pointer);
}
} }

2
ProjectSettings/GameCoreXboxOneSettings.asset

@ -2,7 +2,7 @@
"buildSubtarget": 0, "buildSubtarget": 0,
"deploymentMethod": 0, "deploymentMethod": 0,
"deploymentDrive": 0, "deploymentDrive": 0,
"scid": "00000000-0000-0000-0000-000000000000",
"scid": "d4e10100-9ffc-4e73-961d-26353b554207",
"packageEncryption": 0, "packageEncryption": 0,
"xboxOneResolutionWidth": 1920, "xboxOneResolutionWidth": 1920,
"xboxOneResolutionHeight": 1080, "xboxOneResolutionHeight": 1080,

6
ProjectSettings/XboxOneGame.config

@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Game xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" configVersion="0"> <Game xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" configVersion="0">
<Identity Name="EpicVoiceTest" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" Version="1.0.0.0" />
<Identity Name="BlackMillGames.Verdun" Publisher="CN=4786CBEC-5B76-4412-8227-4A643F59DAC6" Version="1.0.0.0" />
<ShellVisuals DefaultDisplayName="EpicVoiceTest" PublisherDisplayName="DefaultCompany" StoreLogo="StoreLogo.png" Square150x150Logo="Logo.png" Square44x44Logo="SmallLogo.png" Description="EpicVoiceTest" BackgroundColor="#000000" SplashScreenImage="SplashScreen.png" /> <ShellVisuals DefaultDisplayName="EpicVoiceTest" PublisherDisplayName="DefaultCompany" StoreLogo="StoreLogo.png" Square150x150Logo="Logo.png" Square44x44Logo="SmallLogo.png" Description="EpicVoiceTest" BackgroundColor="#000000" SplashScreenImage="SplashScreen.png" />
<StoreId>C1N8H8WTPDSC</StoreId>
<VirtualMachine /> <VirtualMachine />
<MSAAppId>00000000442372A0</MSAAppId>
<TitleId>3b554207</TitleId>
<RequiresXboxLive>true</RequiresXboxLive>
</Game> </Game>
Loading…
Cancel
Save