summaryrefslogtreecommitdiff
path: root/Runtime/Export/XboxServices.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Export/XboxServices.cs')
-rw-r--r--Runtime/Export/XboxServices.cs794
1 files changed, 794 insertions, 0 deletions
diff --git a/Runtime/Export/XboxServices.cs b/Runtime/Export/XboxServices.cs
new file mode 100644
index 0000000..23cddf6
--- /dev/null
+++ b/Runtime/Export/XboxServices.cs
@@ -0,0 +1,794 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Threading;
+using UnityEngine;
+
+namespace UnityEngine
+{
+ #if UNITY_XENON_API && ENABLE_XENON_SOCIALAPI
+ // TODO: We have no concept of achievement types like xbox has, not exposed
+ // TODO: No concept of user privilege levels, not exposed
+ // NOTE: There doesn't seem to be any way to explicitly set your own state (online/offline/away/busy/playing)
+ public class XboxLive : ISocial
+ {
+ private static LocalUser s_LocalUser;
+ private Action<AchievementDescription[]> m_AchievementDescriptionCallback;
+ private Action<bool> m_AchievementReportingCallback;
+
+ private static GameObject s_SessionObject;
+ private static GameObject s_LeaderboardRoutine;
+
+ internal const uint kDefaultUserIndex = 0;
+
+ public static bool onlineMode { get; set; }
+
+ public XboxLive()
+ {
+ if (X360Core.IsUserSignedIn(0, true))
+ {
+ Debug.Log("User already signed in, in online mode");
+ onlineMode = true;
+ }
+
+ // DEBUG: Log when these callbacks are triggered but have not been set elsewhere
+ X360Achievements.OnAchievementsEnumerated = () => { Debug.Log("OnAchievementsEnumerated called"); };
+ X360Achievements.OnUserAchievementsUpdated = (id) => { Debug.Log("OnUserAchievementsUpdated called for user " + id); };
+ X360Achievements.OnAward =
+ (uid, aid, status) => { Debug.Log("OnAward called for user=" + uid + " achievement=" + aid + " status=" + status); };
+ }
+
+ public void ShowAchievementsUI()
+ {
+ X360Achievements.ShowUI(kDefaultUserIndex);
+ }
+
+ // TODO: These should include an action which is triggered (OnSystemUIVisibilityChange) when the UI state changes (UI is dismissed)
+ public void ShowAchievementsUI(uint userIndex)
+ {
+ X360Achievements.ShowUI(userIndex);
+ }
+
+ public void ShowLeaderboardUI()
+ {
+ Debug.Log("Not implemented");
+ }
+
+ public void ShowFriendsUI(uint userIndex)
+ {
+ X360Friends.ShowFriendsUI(userIndex);
+ }
+
+ // NOTE: localUser.underage is not implemented on Xbox Live
+ public LocalUser localUser
+ {
+ get
+ {
+ if (s_LocalUser == null)
+ s_LocalUser = new LocalUser();
+ GetLocalUser(kDefaultUserIndex, ref s_LocalUser);
+ return s_LocalUser;
+ }
+ }
+
+ // TODO: Cache user so he's not recreated every time
+ // TODO: Maybe set this up as a generic list, the list is populated as users are logged in and valid, no need to create on access, no need for seperate GetCount() type functions
+ public LocalUser this[uint index]
+ {
+ get
+ {
+ LocalUser user = new LocalUser();
+ GetLocalUser(index, ref user);
+ return user;
+ }
+ }
+
+ private void GetLocalUser(uint index, ref LocalUser user)
+ {
+ user.m_Authenticated = X360Core.IsUserSignedIn(index, onlineMode);
+ if (!user.m_Authenticated)
+ {
+ //Debug.Log("Must be logged in before getting local user details");
+ return;
+ }
+ user.m_UserName = X360Core.GetUserName(index);
+ user.m_UserId = X360Core.GetUserOnlinePlayerId(index).Raw.ToString();
+ user.m_Friends = GetFriendsList(index);
+ user.m_Image = X360Core.GetUserGamerPicture(index, true);
+ }
+
+ public void Authenticate(Action<bool> callback)
+ {
+ // Request online login of exactly 1 user
+ X360Core.RequestSignIn(1, 1, onlineMode);
+ X360Core.OnUserStateChange = delegate() { callback(true); };
+ }
+
+ // Request sign-in for min users up to max user count
+ public void Authenticate(uint minUsers, uint maxUsers, Action<bool> callback)
+ {
+ X360Core.RequestSignIn(minUsers, maxUsers, onlineMode);
+ // Xbox core doesn't report success/failure of sign-in attempts
+ // so lets just wrap it in another delegate which always reports true
+ // I assume this means all users have signed in
+ X360Core.OnUserStateChange = delegate() { callback(true); };
+ }
+
+ // This is kind of pointless here as the friends list is not loaded seperately
+ public void LoadFriends(Action<bool> callback)
+ {
+ /*if (X360Friends.IsInitialized(0))
+ {
+ Debug.Log("Not initialized yet... ");
+ X360Friends.OnFriendsUpdated = delegate(uint index) { callback(true); };
+ return;
+ }*/
+ callback(true);
+ Debug.Log("Friends list is always populated in the local user automatically");
+ }
+
+ private UserProfile[] GetFriendsList(uint index)
+ {
+ var friends = new List<UserProfile>();
+ for (uint i = 0; i < X360Friends.GetFriendCount(index); i++)
+ {
+ UserProfile friend = new UserProfile();
+ friend.m_UserName = X360Friends.GetFriendName(index, i);
+ friend.m_UserId = X360Friends.GetFriendPlayerId(index, i).Raw.ToString();
+ friend.m_IsFriend = true;
+ X360FriendState state = X360Friends.GetFriendState(index, i);
+ friend.m_State = ConvertState(state);
+ friend.m_Image = X360Core.GetPlayerGamerPicture(index, X360Friends.GetFriendPlayerId(index, i), false);
+ friends.Add(friend);
+ }
+ return friends.ToArray();
+ }
+
+ private UserState ConvertState(X360FriendState state)
+ {
+ if (state.IsOnline) return UserState.Online;
+ if (state.IsOnlineAndAway) return UserState.OnlineAndAway;
+ if (state.IsOnlineAndBusy) return UserState.OnlineAndBusy;
+ if (state.IsPlaying) return UserState.Playing;
+ return UserState.Offline;
+ }
+
+ // Achievements are set up with the Xbox 360 and LIVE Authoring Submission Tool (XLAST)
+ // The points (or gamescore) of each one depends on the title type (retail/arcade).
+ // They are actually loaded automatically at startup, so this doesn't actually load
+ // anything.
+ public void LoadAchievementDescriptions(Action<AchievementDescription[]> callback)
+ {
+ if (!X360Achievements.IsEnumerated())
+ {
+ m_AchievementDescriptionCallback = callback;
+ X360Achievements.OnAchievementsEnumerated = CallbackAchivementDescriptionLoader;
+ }
+ else if (X360Achievements.GetCount() == 0)
+ {
+ Debug.Log("No achievement descriptions found");
+ callback(new AchievementDescription[0]);
+ }
+ else
+ {
+ callback(PopulateAchievementDescriptions());
+ }
+ }
+
+ private void CallbackAchivementDescriptionLoader()
+ {
+ if (m_AchievementDescriptionCallback != null)
+ m_AchievementDescriptionCallback(PopulateAchievementDescriptions());
+ }
+
+ private AchievementDescription[] PopulateAchievementDescriptions()
+ {
+ var achievements = new List<AchievementDescription>();
+ for (uint i = 0; i < X360Achievements.GetCount(); ++i)
+ {
+ // TODO: Should the points maybe just be an uint like xbox uses? Not like you get negative points ever.
+ X360Achievement xboxAchoo = new X360Achievement(i);
+ AchievementDescription achievement = new AchievementDescription(
+ xboxAchoo.Id.ToString(),
+ xboxAchoo.Label,
+ xboxAchoo.Picture,
+ xboxAchoo.Description,
+ xboxAchoo.Unachieved,
+ xboxAchoo.ShowUnachieved,
+ (int)xboxAchoo.Cred);
+ achievements.Add(achievement);
+ }
+ return achievements.ToArray();
+ }
+
+ // Apparently xbox has no concept of unhiding achievements by reporting 0 progress
+ // TODO: This is printed in the log:
+ // '[XUI] Warning: XuiControlPlayOptionalVisual: no fallback for control: PopupControl. Trying to play: "Normal"->"EndNormal"'
+ public void ReportProgress(string id, double progress, Action<bool> callback)
+ {
+ uint numericId;
+ if (!XboxLiveUtil.TryExtractId(id, out numericId)) return;
+
+ ReportProgress(numericId, progress, callback);
+ }
+
+ public void ReportProgress(uint id, double progress, Action<bool> callback)
+ {
+ m_AchievementReportingCallback = callback;
+ X360Achievements.OnAward = CallbackAchivementReported;
+ X360Achievements.AwardUser(kDefaultUserIndex, id);
+ }
+
+ private void CallbackAchivementReported(uint userIndex, uint achievementId, X360AchievementStatus status)
+ {
+ // TODO: Check if the desired achievement ID actually got updated.
+ // TODO: We should have enums here instead of bools
+ if (m_AchievementReportingCallback != null)
+ {
+ if (status == X360AchievementStatus.AlreadyAwarded || status == X360AchievementStatus.Succeeded)
+ m_AchievementReportingCallback(true);
+ else
+ m_AchievementReportingCallback(false);
+ }
+ }
+
+ public void LoadAchievements(Action<Achievement[]> callback)
+ {
+ // TODO: This should really be communicated back with an enum, but since users can re-enumerate
+ // it's kind of useless. We should allow that or automatically handle it.
+ if (!X360Achievements.IsEnumerated())
+ {
+ Debug.Log("Achievements not yet enumerated");
+ callback(new Achievement[0]);
+ }
+ if (X360Achievements.GetCount() == 0)
+ {
+ Debug.Log("No achievements found or achieved");
+ callback(new Achievement[0]);
+ }
+ else
+ {
+ callback(PopulateAchievements(kDefaultUserIndex));
+ }
+ }
+
+ private Achievement[] PopulateAchievements(uint index)
+ {
+ var achievements = new List<Achievement>();
+ for (uint i = 0; i < X360Achievements.GetCount(); ++i)
+ {
+ X360Achievement xboxAchoo = new X360Achievement(i);
+ if (X360Achievements.IsUnlocked(index, xboxAchoo.Id, true))
+ {
+ Achievement achievement = new Achievement(
+ xboxAchoo.Id.ToString(),
+ 100.0,
+ true,
+ false,
+ X360Achievements.GetUnlockTime(index, xboxAchoo.Id));
+ achievements.Add(achievement);
+ }
+ }
+ return achievements.ToArray();
+ }
+
+ // Got this:
+ // WRN[XGI]: Mismatched types for property 0x10000001. XUSER_PROPERTY.value.type = 2 but property type is 1. Skipping write. Pass the correct type or 0.
+ public void ReportScore(long score, string board, Action<bool> callback)
+ {
+ uint boardId;
+ if (!XboxLiveUtil.TryExtractId(board, out boardId)) return;
+
+ // There must be a Score property assigned to this leaderboard and it must expect a 64 bit value
+ uint propertyId;
+ if (XboxLiveUtil.TryExtractId("PROPERTY_SCORE", out propertyId))
+ {
+ Debug.Log("Found Score property: " + propertyId);
+ var properties = new X360UserProperty[1];
+ properties[0].Id = propertyId;
+ properties[0].Value.Type = X360UserDataType.Int64;
+ properties[0].Value.ValueInt64 = score;
+ ReportScore(boardId, properties, callback);
+ }
+ else
+ Debug.LogError("Failed to report score to " + board);
+ }
+
+ // TODO: The API is set up so you have one leaderboard object, and muliple ones if you need to report/read
+ // to/from multiple leaderboards. Xbox has the X360StatsViewProperties value which allows you to report
+ // scores etc to multiple leaderboard views at a time. Here we always have one such property.
+ public void ReportScore(uint boardId, X360UserProperty[] props, Action<bool> callback)
+ {
+ if (X360Core.GetTotalOnlineUsers() == 0)
+ {
+ Debug.Log("ERROR: Leaderboards can only be used when the user is logged in online (online=" + X360Core.GetTotalOnlineUsers() + " signed-in=" + X360Core.GetTotalSignedInUsers() + ")");
+ callback(false);
+ return;
+ }
+
+ LeaderboardRoutine routine = GetRoutine();
+ XboxScore xboxScore = new XboxScore();
+ xboxScore.boardId = boardId;
+ xboxScore.properties = props;
+ routine.ReportScore(xboxScore, callback);
+ }
+
+ public void LoadScores(string category, Action<Score[]> callback)
+ {
+ uint categoryId;
+ if (!XboxLiveUtil.TryExtractId(category, out categoryId)) return;
+
+ // This will find all columns assigned to this leaderboard, if specific columns are desired
+ // you need to use the xbox specific LoadScores call which has the columns parameter
+ ushort[] columnIds;
+ if (XboxLiveUtil.TryExtractId("STATS_COLUMN_" + category.ToUpper(), out columnIds))
+ LoadScores(categoryId, columnIds, callback);
+ else
+ Debug.LogError("Failed to load scores from " + category);
+ }
+
+ // We convert X360StatsRow + X360StatsColumn objects to Score objects.
+ // NOTE: Each row contains user info + all the columns for him. Here we will only support the user info + 1 single column (value/score/points)
+ // NOTE: We only support getting long (64bit) values, not floats etc.
+ // NOTE: The date field in the Score class is not populated, not supported here unless we support it as a custom column in the leaderboard config.
+ // TODO: Fix formattedValue field, it should be possible to populate based on the localized string accociated with a column
+ // TODO: Unused row fields: Rating + Gamertag
+ // NOTE: Arbitrated leaderboards + the TrueSkill system complicates matters here. An
+ // arbitrated session must be started to be able to report scores on an arbitrated leaderboard.
+ // When using TrueSkill, every player must report all scores (also for other players) and the server
+ // verifies they are correct.
+ public void LoadScores(uint boardId, ushort[] columnIds, Action<Score[]> callback)
+ {
+ LeaderboardRoutine routine = GetRoutine();
+ routine.LoadScores(boardId, columnIds, callback);
+ }
+
+ private LeaderboardRoutine GetRoutine()
+ {
+ LeaderboardRoutine routine;
+ if (s_LeaderboardRoutine == null)
+ {
+ s_LeaderboardRoutine = new GameObject();
+ routine = s_LeaderboardRoutine.AddComponent<LeaderboardRoutine>();
+ }
+ else
+ routine = s_LeaderboardRoutine.GetComponent<LeaderboardRoutine>();
+ return routine;
+ }
+
+ internal static XboxLiveSession GetSession()
+ {
+ XboxLiveSession session;
+ if (s_SessionObject == null)
+ {
+ Debug.Log("Creating new session object");
+ s_SessionObject = new GameObject();
+ session = s_SessionObject.AddComponent<XboxLiveSession>();
+ }
+ else
+ {
+ Debug.Log("Reusing old session");
+ session = s_SessionObject.GetComponent<XboxLiveSession>();
+ }
+ return session;
+ }
+
+ public void LoadScores(Leaderboard board, Action<bool> callback)
+ {
+ board.m_Loading = true;
+ var routine = GetRoutine();
+ routine.LoadScores((XboxLeaderboard) board, callback);
+ }
+
+ public bool GetLoading(Leaderboard board)
+ {
+ return board.m_Loading;
+ }
+
+ public static void OpenSession(Action<bool> callback)
+ {
+ XboxLiveSession session = GetSession();
+
+ if (session.running)
+ {
+ callback(true);
+ return;
+ }
+
+ session.SetupSession(callback);
+ }
+
+ public static void CloseSession(Action<bool> callback)
+ {
+ if (s_SessionObject == null)
+ {
+ callback(true);
+ return;
+ }
+
+ var routine = s_SessionObject.GetComponent<XboxLiveSession>();
+ routine.Cleanup(callback);
+ }
+ }
+
+ class LeaderboardRoutine : MonoBehaviour
+ {
+ private Action<Score[]> m_ScoresCallback;
+ private Action<bool> m_LeaderboardCallback;
+ // If it's possible with the Xbox SDK to do parallel leaderboard queries, then expand this into a boardID=>board hashtable
+ private XboxLeaderboard m_CurrentBoard;
+
+ public void ReportScore(XboxScore score, Action<bool> callback)
+ {
+ StartCoroutine(DoReportScore(score, callback));
+ }
+
+ // TODO: Public/private slots needs to be exposed somehow, but this is only relevant for multiplayer games
+ // TODO: No callbacks are accociated with writes (as they happen later)?
+ // TODO: Deal with this error: WRN[XGI]: Invalid leaderboard id: 0x00000000
+ public IEnumerator DoReportScore(XboxScore score, Action<bool> callback)
+ {
+ /*yield return StartCoroutine(SetupSession());
+ Debug.Log("Session setup done");
+ if (m_Session == null)
+ {
+ callback(false);
+ yield break;
+ }*/
+ XboxLiveSession session = XboxLive.GetSession();
+ if (!session.running)
+ {
+ Debug.Log("Must open a session first");
+ callback(false);
+ yield break;
+ }
+
+ var viewProp = new X360StatsViewProperties[1];
+ viewProp[0] = new X360StatsViewProperties { ViewId = score.boardId, Properties = score.properties };
+ X360PlayerId onlineId = X360Core.GetUserOnlinePlayerId(XboxLive.kDefaultUserIndex);
+ if (!session.activeSession.WriteStats(onlineId, viewProp))
+ {
+ Debug.LogError("Failed to send leaderboard data to server");
+ //yield return StartCoroutine(TearDownSession());
+ callback(false);
+ }
+
+ // DEBUG: Maybe also skip this yield
+ yield return new WaitForSeconds(0.1F);
+ Debug.Log(DateTime.Now + ": Waiting for session to become available.");
+ yield return !session.activeSession.IsIdle();
+
+ //Debug.Log("Flush stats");
+ //m_Session.FlushStats();
+ //yield return new WaitForSeconds(0.1F);
+ //Debug.Log(DateTime.Now + ": Waiting for session to become available.");
+ //yield return !m_Session.IsIdle();
+
+ // TODO: Was it actually a success?
+ callback(true);
+ }
+
+ public void LoadScores(uint categoryId, ushort[] columnIds, Action<Score[]> callback)
+ {
+ m_ScoresCallback = callback;
+ XboxLeaderboard board = new XboxLeaderboard();
+ board.boardId = categoryId;
+ board.columnIds = columnIds;
+ board.playerScope = PlayerScope.FriendsOnly;
+ StartCoroutine(DoLoadScores(board));
+ }
+
+ public void LoadScores(XboxLeaderboard leaderboard, Action<bool> callback)
+ {
+ m_LeaderboardCallback = callback;
+ m_CurrentBoard = leaderboard;
+
+ string categoryString = "";
+ if (!XboxLiveUtil.TryExtractString(leaderboard.boardId, "STATS_VIEW_", out categoryString))
+ Debug.Log("Failed to set leaderboard category name");
+ else
+ m_CurrentBoard.category = categoryString;
+ StartCoroutine(DoLoadScores(leaderboard));
+ }
+
+ public IEnumerator DoLoadScores(XboxLeaderboard board)
+ {
+ XboxLiveSession session = XboxLive.GetSession();
+ if (!session.running)
+ {
+ Debug.Log("Must open a session first");
+ yield break;
+ }
+
+ /*yield return StartCoroutine(SetupSession());
+ if (m_Session == null)
+ {
+ Debug.Log("Session setup failed");
+ FinishCallbacks(false, new Score[0]);
+ yield break;
+ }*/
+
+ if (board.timeScope != TimeScope.AllTime)
+ Debug.Log("Time scope filtering is not supported, scores from any time are always used.");
+
+ // TODO: Figure out what should be supported, ReadPlayerStats only returns to score
+ // of the local player, this should go into Leaderboard.localPlayerScore
+ if (board.playerScope == PlayerScope.FriendsOnly)
+ Debug.Log("Friends only support not implemented yet, loading from all players");
+ X360Stats.ReadLeaderboardByIndex(
+ board.boardId,
+ board.columnIds,
+ (uint) board.range.from,
+ (uint) board.range.count,
+ ProcessLeaderboardResult);
+
+ // TODO: If the scores returned do not contain the local player score we need to fetch it
+ // seperately so the localPlayerScore property can be populated.
+ //X360Stats.ReadPlayerStats(board.boardId, board.columnIds, m_OnlineID, PopulateLocalPlayerScore);
+
+ Debug.Log(DateTime.Now + ": Waiting for session to become available.");
+ yield return !session.activeSession.IsIdle();
+ if (session.activeSession.LastFunctionFailed())
+ {
+ Debug.Log("Failed to read leaderboard info");
+ //m_Session = null;
+ yield break;
+ }
+ }
+
+ private void FinishCallbacks(bool result, Score[] scores)
+ {
+ if (m_CurrentBoard != null)
+ {
+ m_CurrentBoard.m_Scores = scores;
+ m_CurrentBoard.m_Loading = false;
+ if (m_LeaderboardCallback != null)
+ {
+ m_LeaderboardCallback(result);
+ m_LeaderboardCallback = null;
+ }
+ m_CurrentBoard = null;
+ }
+ else if (m_ScoresCallback != null)
+ {
+ m_ScoresCallback(scores);
+ m_ScoresCallback = null;
+ }
+ }
+
+ // TODO: This currently assumes one and only one value (column) per score element
+ private void ProcessLeaderboardResult(UInt32 viewId, UInt32 totalRows, X360StatsRow[] rows)
+ {
+ Debug.Log("Received " + rows.Length + " out of " + totalRows + " rows for id " + viewId);
+ m_CurrentBoard.m_MaxRange = totalRows;
+ var scores = new List<Score>();
+ foreach (X360StatsRow row in rows)
+ {
+ string playerId = row.Xuid.ToString();
+ long value = -1;
+ if (row.Columns.Length >= 1)
+ value = row.Columns[0].Value.ValueInt64;
+ DateTime date = DateTime.Now;
+ int rank = (int)row.Rank;
+
+ Score score = new Score(viewId.ToString(), value, playerId, date, value.ToString(), rank);
+ scores.Add(score);
+ }
+ FinishCallbacks(true, scores.ToArray());
+ }
+ }
+
+ public class XboxScore : Score
+ {
+ public X360UserProperty[] properties { get; set; }
+ public uint boardId { get; set; }
+ }
+
+ public class XboxLeaderboard : Leaderboard
+ {
+ public uint boardId { get; set; }
+ public ushort[] columnIds { get; set; }
+
+ public XboxLeaderboard()
+ {
+ boardId = 0;
+ columnIds = new ushort[0];
+ }
+
+ public override string ToString()
+ {
+ return base.ToString() + " BoardID: '" + boardId + "' ColumnIds: '" + columnIds.Length + "'";
+ }
+ }
+
+
+ internal class XboxLiveSession : MonoBehaviour
+ {
+ private X360Session m_Session;
+ private bool m_SessionRunning;
+ private X360PlayerId m_OnlineID;
+ private bool m_HazError;
+
+ internal bool running { get { return m_SessionRunning; } }
+ internal X360Session activeSession { get { return m_Session; } }
+
+ XboxLiveSession()
+ {
+ Debug.Log("XboxLiveSession GO created");
+ // Get the online ID, sanity checks is performed earlier to ensure that the user is actually online
+ m_OnlineID = X360Core.GetUserOnlinePlayerId(XboxLive.kDefaultUserIndex);
+ }
+
+ public void Cleanup(Action<bool> callback)
+ {
+ StartCoroutine(TearDownSession(callback));
+ }
+
+ internal void SetupSession(System.Action<bool> callback = null)
+ {
+ StartCoroutine(DoSetupSession(callback));
+ }
+
+ // TODO: These sessions calls should all be returning success boolean...
+ // TODO: Deal with this error: WRN[XGI]: User at index 0 is already a member of a presence session.
+ private IEnumerator DoSetupSession(System.Action<bool> callback)
+ {
+ if (m_SessionRunning)
+ {
+ Debug.Log("Skip session creation as we already have one running");
+ yield break;
+ }
+
+ m_HazError = false;
+
+ if (m_Session == null || m_Session.IsDead())
+ {
+ Debug.Log(DateTime.Now + ": Creating session.");
+ m_Session = X360Session.CreateSinglePlayerSessionWithStats(XboxLive.kDefaultUserIndex, 4, 0);
+ if (m_Session == null) yield break;
+ }
+ yield return StartCoroutine(ValidateOperation("Session creation failed", callback));
+ if (m_HazError) yield break;
+
+ Debug.Log(DateTime.Now + ": Session is ready. Joining.");
+ if (!m_Session.Join(m_OnlineID, false))
+ {
+ Debug.LogError("Failed to join session");
+ m_Session = null;
+ yield break;
+ }
+ yield return StartCoroutine(ValidateOperation("Session joining failed", callback));
+ if (m_HazError) yield break;
+
+ Debug.Log(DateTime.Now + ": Session is ready. Starting.");
+ m_Session.Start();
+ yield return StartCoroutine(ValidateOperation("Session failed to start", callback));
+ if (m_HazError) yield break;
+
+ Debug.Log("Session now running");
+ m_SessionRunning = true;
+ if (callback != null) callback(true);
+ }
+
+ // We do not delete the running session completely but only end it's operation. It will
+ // be reused if OpenSession is called again.
+ private IEnumerator TearDownSession(Action<bool> callback = null)
+ {
+ if (m_Session == null || m_Session.IsDead())
+ {
+ if (callback != null) callback(true);
+ yield break;
+ }
+ m_HazError = false;
+
+ Debug.Log("Leaving running session.");
+ m_Session.Leave(m_OnlineID, false);
+ yield return StartCoroutine(ValidateOperation("Failed to leave session", callback));
+ if (m_HazError) yield break;
+
+ Debug.Log("End running session");
+ m_Session.End();
+ yield return StartCoroutine(ValidateOperation("Failed to end session", callback));
+ if (m_HazError) yield break;
+
+ //Debug.Log("Deleting running session.");
+ //m_Session.Delete();
+ //yield return StartCoroutine(ValidateOperation("Failed to delete session", callback));
+ //if (m_HazError) yield break;
+
+ m_SessionRunning = false;
+ Debug.Log("Successfully tore down session.");
+ if (callback != null) callback(true);
+ }
+
+ private IEnumerator ValidateOperation(string error, Action<bool> callback)
+ {
+ //Debug.Log(DateTime.Now + ": Waiting for session to become available.");
+ yield return !m_Session.IsIdle();
+ if (m_Session.LastFunctionFailed())
+ {
+ Debug.Log(error);
+ m_Session = null;
+ if (callback != null) callback(false);
+ m_HazError = true;
+ yield break;
+ }
+ }
+ }
+
+
+ static internal class XboxLiveUtil
+ {
+ // TODO: Also look out for javascript/boo assemblies
+ internal static Assembly s_UserScriptingAssembly = Assembly.Load(new AssemblyName("Assembly-CSharp"));
+
+ static internal bool TryExtractId(string id, out ushort[] shorts)
+ {
+ List<ushort> foundShorts = new List<ushort>();
+ bool success = false;
+ foreach (FieldInfo info in GetSpaConfigFields())
+ {
+ //Debug.Log("Haz " + info);
+ if (info.Name.Contains(id.ToUpper()))
+ {
+ // The reflected enum contains uint values, so it must first be cast to that type
+ ushort value = (ushort)(uint)info.GetValue(info.GetType());
+ foundShorts.Add(value);
+ success = true;
+ //Debug.Log("Found desired ID: " + value);
+ }
+ }
+ shorts = foundShorts.ToArray();
+ if (!success) Debug.Log("Failed to find " + id + " in SPAConfig enum");
+ return success;
+ }
+
+ static internal bool TryExtractId(string id, out uint numericId)
+ {
+ if (uint.TryParse(id, out numericId)) return true;
+
+ foreach (FieldInfo info in GetSpaConfigFields())
+ {
+ //Debug.Log("Haz " + info);
+ if (info.Name.Contains(id.ToUpper()))
+ {
+ numericId = (uint)info.GetValue(info.GetType());
+ //Debug.Log("Found desired ID: " + numericId);
+ return true;
+ }
+ }
+
+ Debug.Log("Failed to find " + id + " in SPAConfig enum");
+ return false;
+ }
+
+ static internal bool TryExtractString(uint numericId, string pattern, out string outputString)
+ {
+ outputString = "";
+ foreach (FieldInfo info in GetSpaConfigFields())
+ {
+ if (info.Name.Contains(pattern) && (uint)info.GetValue(info.GetType()) == numericId)
+ {
+ outputString = info.Name;
+ return true;
+ }
+ }
+
+ Debug.Log("Failed to find " + pattern + " field in SPAConfig enum which matched " + numericId);
+ return false;
+ }
+
+ static internal FieldInfo[] GetSpaConfigFields()
+ {
+ if (s_UserScriptingAssembly == null) return new FieldInfo[0];
+ object spaObject = s_UserScriptingAssembly.CreateInstance("spaconfig", true);
+ if (spaObject == null) return new FieldInfo[0];
+ Type spaType = spaObject.GetType();
+ FieldInfo[] spaFields = spaType.GetFields();
+ return spaFields;
+ }
+ }
+
+#endif
+}