diff --git a/ChobbyLauncher/ChobbyLoopbackMessages.cs b/ChobbyLauncher/ChobbyLoopbackMessages.cs index 4f1cb0ff40..28f351c224 100644 --- a/ChobbyLauncher/ChobbyLoopbackMessages.cs +++ b/ChobbyLauncher/ChobbyLoopbackMessages.cs @@ -121,13 +121,20 @@ public class DownloadFileProgress [ChobbyMessage] public class SteamOnline { + public string AuthToken { get; set; } - public List Friends { get; set; } public string FriendSteamID { get; set; } public string SuggestedName { get; set; } public List Dlc { get; set; } } + [ChobbyMessage] + public class SteamFriendList + { + + public List Friends { get; set; } + } + [ChobbyMessage] public class SteamOffline { diff --git a/ChobbyLauncher/Chobbyla.cs b/ChobbyLauncher/Chobbyla.cs index a770715e49..a9ea214206 100644 --- a/ChobbyLauncher/Chobbyla.cs +++ b/ChobbyLauncher/Chobbyla.cs @@ -185,7 +185,7 @@ private void ClearSdp() public bool Run(ulong initialConnectLobbyID, TextWriter writer) { Status = "Connecting to steam API"; - using (var steam = new SteamClientHelper()) + using (var steam = new SteamClientHelper(this)) { steam.ConnectToSteam(); diff --git a/ChobbyLauncher/ChobbylaLocalListener.cs b/ChobbyLauncher/ChobbylaLocalListener.cs index 2108b36221..65807f38d0 100644 --- a/ChobbyLauncher/ChobbylaLocalListener.cs +++ b/ChobbyLauncher/ChobbylaLocalListener.cs @@ -15,6 +15,7 @@ using PlasmaDownloader; using PlasmaShared; using ZkData; +using static ChobbyLauncher.SteamOnline; using Timer = System.Threading.Timer; namespace ChobbyLauncher @@ -47,6 +48,7 @@ public ChobbylaLocalListener(Chobbyla chobbyla, SteamClientHelper steam, ulong i steam.OverlayActivated += SteamOnOverlayActivated; steam.SteamOnline += () => { SendSteamOnline(); }; steam.SteamOffline += () => { SendSteamOffline(); }; + steam.FriendListUpdate += (freund) => { SendFriendList(); }; discordController = new DiscordController(GlobalConst.ZeroKDiscordID, GlobalConst.SteamAppID.ToString()); discordController.OnJoin += DiscordOnJoinCallback; discordController.OnDisconnected += DiscordOnDisconnectedCallback; @@ -722,6 +724,7 @@ private async Task OnConnected() try { await SendSteamOnline(); + await SendFriendList(); } catch (Exception ex) @@ -756,13 +759,12 @@ private async Task SendSteamOnline() { var friendId = initialConnectLobbyID != 0 ? steam.GetLobbyOwner(initialConnectLobbyID) : null; - + await SendCommand(new SteamOnline() { AuthToken = steam.AuthToken, - Friends = steam.Friends.Select(x => x.ToString()).ToList(), FriendSteamID = friendId?.ToString(), SuggestedName = steam.MySteamNameSanitized, Dlc = steam.GetDlcList() @@ -772,6 +774,17 @@ private async Task SendSteamOnline() } } + private async Task SendFriendList() + { + if (steam.IsOnline) + { + await SendCommand(new SteamFriendList() + { + Friends = steam.Friends.Values.ToList() + }); + } + } + private async Task SendSteamOffline() { await SendCommand(new SteamOffline()); diff --git a/ChobbyLauncher/SteamClient.cs b/ChobbyLauncher/SteamClient.cs index 1281b8a792..caf5cea321 100644 --- a/ChobbyLauncher/SteamClient.cs +++ b/ChobbyLauncher/SteamClient.cs @@ -14,6 +14,11 @@ using ZkData; using System.Net.NetworkInformation; using Timer = System.Timers.Timer; +using System.IO; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Drawing.Imaging; +using Newtonsoft.Json; namespace ChobbyLauncher { @@ -36,6 +41,7 @@ public enum OverlayOption private bool isDisposed; + private Callback friendStateChangeCallback; private Callback lobbyJoinRequestCallback; private Callback newConnectionCallback; private Callback overlayActivatedCallback; @@ -47,7 +53,7 @@ public enum OverlayOption public string AuthToken { get; private set; } - public List Friends { get; private set; } + public ConcurrentDictionary Friends { get; private set; } public bool IsOnline { get; private set; } public ChobbylaLocalListener Listener { get; set; } @@ -55,6 +61,12 @@ public enum OverlayOption public string MySteamNameSanitized { get; set; } + private Chobbyla chobbyla; + + public SteamClientHelper(Chobbyla chobbyla) + { + this.chobbyla = chobbyla; + } public void Dispose() { @@ -85,11 +97,11 @@ public void ConnectToSteam() public ulong? GetLobbyOwner(ulong lobbyID) { if (IsOnline) - foreach (var f in GetFriends()) + foreach (var f in GetFriendIDs()) { FriendGameInfo_t gi; - SteamFriends.GetFriendGamePlayed(new CSteamID(f), out gi); - if (gi.m_steamIDLobby.m_SteamID == lobbyID) return f; + SteamFriends.GetFriendGamePlayed(f, out gi); + if (gi.m_steamIDLobby.m_SteamID == lobbyID) return f.m_SteamID; } return null; } @@ -118,6 +130,9 @@ public void InviteFriendToGame(ulong lobbyID, ulong friendID) if (IsOnline) SteamMatchmaking.InviteUserToLobby(new CSteamID(lobbyID), new CSteamID(friendID)); } + + public event Action FriendListUpdate = (freund) => { }; + public event Action JoinFriendRequest = (steamID) => { }; public void OpenOverlaySection(OverlayOption option) @@ -155,7 +170,7 @@ public void PrepareToHostP2PGame(SteamHostGameRequest request) ulong.TryParse(player.SteamID, out playerSteamID); p2pProxies[playerSteamID] = null; - SendSteamMessage(playerSteamID, new SteamP2PRequestPrepareProxy() {Channel = steamChannelCounter++}); + SendSteamMessage(playerSteamID, new SteamP2PRequestPrepareProxy() { Channel = steamChannelCounter++ }); } // wait for response @@ -191,7 +206,7 @@ public void PrepareToHostP2PGame(SteamHostGameRequest request) ScriptPassword = player.ScriptPassword }); } - + // send command to start spring to self Listener.SendCommand(new SteamHostGameSuccess() { HostPort = gameHostUdpPort }); }); @@ -241,13 +256,34 @@ private string GetClientAuthTokenHex() } - private List GetFriends() + private ConcurrentDictionary GetFriends() + { + if (IsOnline) + { + var dic = new ConcurrentDictionary(GetFriendIDs().ToDictionary(x => x.m_SteamID, x => new SteamFriend() + { + Name = SteamFriends.GetFriendPersonaName(x), + CSteamID = x + })); + dic.ForEach(freund => + { + var state = SteamFriends.GetFriendPersonaState(freund.Value.CSteamID); + freund.Value.IsOnline = state.HasFlag(EPersonaState.k_EPersonaStateOnline); + FriendGameInfo_t game; + freund.Value.IsPlaying = SteamFriends.GetFriendGamePlayed(freund.Value.CSteamID, out game) && (game.m_gameID.AppID().m_AppId == GlobalConst.SteamAppID); + }); + return dic; + } + return null; + } + + private List GetFriendIDs() { if (IsOnline) { - var ret = new List(); + var ret = new List(); var cnt = SteamFriends.GetFriendCount(EFriendFlags.k_EFriendFlagImmediate); - for (var i = 0; i < cnt; i++) ret.Add(SteamFriends.GetFriendByIndex(i, EFriendFlags.k_EFriendFlagImmediate).m_SteamID); + for (var i = 0; i < cnt; i++) ret.Add(SteamFriends.GetFriendByIndex(i, EFriendFlags.k_EFriendFlagImmediate)); return ret; } return null; @@ -265,6 +301,44 @@ private ulong GetSteamID() return 0; } + private void DownloadAvatars(ICollection users) + { + using (var wc = new WebClient()) + { + var targetDir = Path.Combine(chobbyla.paths.WritableDirectory, "LuaUI", "Configs", "SteamAvatars"); + if (!Directory.Exists(targetDir)) Directory.CreateDirectory(targetDir); + foreach (var user in users) + { + //download image via steamfriends + int handle = SteamFriends.GetMediumFriendAvatar(user); + uint imgW, imgH; + SteamUtils.GetImageSize(handle, out imgW, out imgH); + byte[] imgBuffer = new byte[4 * imgW * imgH]; + GCHandle pinnedArray = GCHandle.Alloc(imgBuffer, GCHandleType.Pinned); //please make a better implementation of this + IntPtr pointer = pinnedArray.AddrOfPinnedObject(); + SteamUtils.GetImageRGBA(handle, imgBuffer, imgBuffer.Length); + var bitmap = new Bitmap((int)imgW, (int)imgH, 4 * (int)imgW, PixelFormat.Format32bppArgb, pointer); + + var path = Path.Combine(targetDir, string.Format("{0}.png", user.m_SteamID)); + if (File.Exists(path)) File.Delete(path); + bitmap.Save(path, ImageFormat.Png); + + pinnedArray.Free(); + } + } + } + + private void OnFriendStateUpdate(PersonaStateChange_t u) + { + if (!Friends.ContainsKey(u.m_ulSteamID)) return; //Needed because this method is called for the player himself (not part of friend list) + var freund = Friends[u.m_ulSteamID]; + if (u.m_nChangeFlags.HasFlag(EPersonaChange.k_EPersonaChangeComeOnline)) freund.IsOnline = true; + if (u.m_nChangeFlags.HasFlag(EPersonaChange.k_EPersonaChangeGoneOffline)) freund.IsOnline = false; + FriendGameInfo_t game; + freund.IsPlaying = SteamFriends.GetFriendGamePlayed(new CSteamID(u.m_ulSteamID), out game) && (game.m_gameID.AppID().m_AppId == GlobalConst.SteamAppID); + FriendListUpdate?.Invoke(freund); + } + private void OnSteamOnline() { Trace.TraceInformation("Steam online"); @@ -272,9 +346,10 @@ private void OnSteamOnline() lobbyJoinRequestCallback = new Callback(t => { JoinFriendRequest(t.m_steamIDFriend.m_SteamID); }); overlayActivatedCallback = new Callback(t => { OverlayActivated(t.m_bActive != 0); }); newConnectionCallback = Callback.Create(t => SteamNetworking.AcceptP2PSessionWithUser(t.m_steamIDRemote)); + friendStateChangeCallback = new Callback(t => OnFriendStateUpdate(t)); MySteamNameSanitized = Utils.StripInvalidLobbyNameChars(GetMyName()); - + var ev = new EventWaitHandle(false, EventResetMode.ManualReset); AuthToken = GetClientAuthTokenHex(); CreateLobbyAsync((lobbyID) => @@ -284,8 +359,10 @@ private void OnSteamOnline() }); SteamNetworking.AllowP2PPacketRelay(true); Friends = GetFriends(); + Task.Factory.StartNew(() => DownloadAvatars(Friends.Values.Select(x => x.CSteamID).ToList())); ev.WaitOne(2000); SteamOnline?.Invoke(); + FriendListUpdate?.Invoke(null); } private void ProcessMessage(ulong remoteUser, Dummy cmd) @@ -323,7 +400,7 @@ private void DisposeExistingProxies() /// private void ProcessMessage(ulong remoteUser, SteamP2PConfirmCreateProxy cmd) { - p2pProxies[remoteUser] = new SteamP2PPortProxy(cmd.Channel, new CSteamID(remoteUser), gameHostUdpPort); + p2pProxies[remoteUser] = new SteamP2PPortProxy(cmd.Channel, new CSteamID(remoteUser), gameHostUdpPort); } /// @@ -425,4 +502,16 @@ private void TimerOnElapsed(object sender) } } } + + public class SteamFriend + { + private CSteamID _cSteamID; + + [JsonIgnore] + public CSteamID CSteamID { get { return _cSteamID; } set { _cSteamID = value; SteamID = value.m_SteamID; } } + public ulong SteamID { get; private set; } + public string Name; + public bool IsOnline; + public bool IsPlaying; //whether friend is playing zk right now + } } \ No newline at end of file