1414using ZkData ;
1515using System . Net . NetworkInformation ;
1616using Timer = System . Timers . Timer ;
17+ using System . IO ;
18+ using System . Drawing ;
19+ using System . Runtime . InteropServices ;
20+ using System . Drawing . Imaging ;
21+ using Newtonsoft . Json ;
1722
1823namespace ChobbyLauncher
1924{
@@ -36,6 +41,7 @@ public enum OverlayOption
3641
3742 private bool isDisposed ;
3843
44+ private Callback < PersonaStateChange_t > friendStateChangeCallback ;
3945 private Callback < GameLobbyJoinRequested_t > lobbyJoinRequestCallback ;
4046 private Callback < P2PSessionRequest_t > newConnectionCallback ;
4147 private Callback < GameOverlayActivated_t > overlayActivatedCallback ;
@@ -47,14 +53,20 @@ public enum OverlayOption
4753
4854 public string AuthToken { get ; private set ; }
4955
50- public List < SteamFriend > Friends { get ; private set ; }
56+ public ConcurrentDictionary < ulong , SteamFriend > Friends { get ; private set ; }
5157 public bool IsOnline { get ; private set ; }
5258 public ChobbylaLocalListener Listener { get ; set ; }
5359
5460 public ulong ? LobbyID { get ; set ; }
5561
5662 public string MySteamNameSanitized { get ; set ; }
5763
64+ private Chobbyla chobbyla ;
65+
66+ public SteamClientHelper ( Chobbyla chobbyla )
67+ {
68+ this . chobbyla = chobbyla ;
69+ }
5870
5971 public void Dispose ( )
6072 {
@@ -88,8 +100,8 @@ public void ConnectToSteam()
88100 foreach ( var f in GetFriendIDs ( ) )
89101 {
90102 FriendGameInfo_t gi ;
91- SteamFriends . GetFriendGamePlayed ( new CSteamID ( f ) , out gi ) ;
92- if ( gi . m_steamIDLobby . m_SteamID == lobbyID ) return f ;
103+ SteamFriends . GetFriendGamePlayed ( f , out gi ) ;
104+ if ( gi . m_steamIDLobby . m_SteamID == lobbyID ) return f . m_SteamID ;
93105 }
94106 return null ;
95107 }
@@ -118,6 +130,9 @@ public void InviteFriendToGame(ulong lobbyID, ulong friendID)
118130 if ( IsOnline ) SteamMatchmaking . InviteUserToLobby ( new CSteamID ( lobbyID ) , new CSteamID ( friendID ) ) ;
119131 }
120132
133+
134+ public event Action < SteamFriend > FriendListUpdate = ( freund ) => { } ;
135+
121136 public event Action < ulong > JoinFriendRequest = ( steamID ) => { } ;
122137
123138 public void OpenOverlaySection ( OverlayOption option )
@@ -241,26 +256,33 @@ private string GetClientAuthTokenHex()
241256 }
242257
243258
244- private List < SteamFriend > getFriends ( )
259+ private ConcurrentDictionary < ulong , SteamFriend > GetFriends ( )
245260 {
246261 if ( IsOnline )
247262 {
248- return GetFriendIDs ( ) . Select ( x => new SteamFriend ( )
263+ var dic = new ConcurrentDictionary < ulong , SteamFriend > ( GetFriendIDs ( ) . ToDictionary ( x => x . m_SteamID , x => new SteamFriend ( )
249264 {
250265 Name = SteamFriends . GetFriendPersonaName ( x ) ,
251- SteamID = x . ToString ( )
252- } ) . ToList ( ) ;
266+ CSteamID = x
267+ } ) ) ;
268+ dic . ForEach ( freund =>
269+ {
270+ var state = SteamFriends . GetFriendPersonaState ( freund . Value . CSteamID ) ;
271+ freund . Value . IsOnline = state . HasFlag ( EPersonaState . k_EPersonaStateOnline ) ;
272+ freund . Value . IsPlaying = SteamFriends . GetFriendGamePlayed ( freund . Value . CSteamID , out var game ) && ( game . m_gameID . AppID ( ) . m_AppId == GlobalConst . SteamAppID ) ;
273+ } ) ;
274+ return dic ;
253275 }
254276 return null ;
255277 }
256278
257- private List < ulong > GetFriendIDs ( )
279+ private List < CSteamID > GetFriendIDs ( )
258280 {
259281 if ( IsOnline )
260282 {
261- var ret = new List < ulong > ( ) ;
283+ var ret = new List < CSteamID > ( ) ;
262284 var cnt = SteamFriends . GetFriendCount ( EFriendFlags . k_EFriendFlagImmediate ) ;
263- for ( var i = 0 ; i < cnt ; i ++ ) ret . Add ( SteamFriends . GetFriendByIndex ( i , EFriendFlags . k_EFriendFlagImmediate ) . m_SteamID ) ;
285+ for ( var i = 0 ; i < cnt ; i ++ ) ret . Add ( SteamFriends . GetFriendByIndex ( i , EFriendFlags . k_EFriendFlagImmediate ) ) ;
264286 return ret ;
265287 }
266288 return null ;
@@ -278,13 +300,51 @@ private ulong GetSteamID()
278300 return 0 ;
279301 }
280302
303+ private void DownloadAvatars ( ICollection < CSteamID > users )
304+ {
305+ using ( var wc = new WebClient ( ) )
306+ {
307+ var targetDir = Path . Combine ( chobbyla . paths . WritableDirectory , "LuaUI" , "Configs" , "SteamAvatars" ) ;
308+ if ( ! Directory . Exists ( targetDir ) ) Directory . CreateDirectory ( targetDir ) ;
309+ foreach ( var user in users )
310+ {
311+ //download image via steamfriends
312+ int handle = SteamFriends . GetMediumFriendAvatar ( user ) ;
313+ uint imgW , imgH ;
314+ SteamUtils . GetImageSize ( handle , out imgW , out imgH ) ;
315+ byte [ ] imgBuffer = new byte [ 4 * imgW * imgH ] ;
316+ GCHandle pinnedArray = GCHandle . Alloc ( imgBuffer , GCHandleType . Pinned ) ; //please make a better implementation of this
317+ IntPtr pointer = pinnedArray . AddrOfPinnedObject ( ) ;
318+ SteamUtils . GetImageRGBA ( handle , imgBuffer , imgBuffer . Length ) ;
319+ var bitmap = new Bitmap ( ( int ) imgW , ( int ) imgH , 4 * ( int ) imgW , PixelFormat . Format32bppArgb , pointer ) ;
320+
321+ var path = Path . Combine ( targetDir , string . Format ( "{0}.png" , user . m_SteamID ) ) ;
322+ if ( File . Exists ( path ) ) File . Delete ( path ) ;
323+ bitmap . Save ( path , ImageFormat . Png ) ;
324+
325+ pinnedArray . Free ( ) ;
326+ }
327+ }
328+ }
329+
330+ private void OnFriendStateUpdate ( PersonaStateChange_t u )
331+ {
332+ if ( ! Friends . ContainsKey ( u . m_ulSteamID ) ) return ; //Needed because this method is called for the player himself (not part of friend list)
333+ var freund = Friends [ u . m_ulSteamID ] ;
334+ if ( u . m_nChangeFlags . HasFlag ( EPersonaChange . k_EPersonaChangeComeOnline ) ) freund . IsOnline = true ;
335+ if ( u . m_nChangeFlags . HasFlag ( EPersonaChange . k_EPersonaChangeGoneOffline ) ) freund . IsOnline = false ;
336+ freund . IsPlaying = SteamFriends . GetFriendGamePlayed ( new CSteamID ( u . m_ulSteamID ) , out var game ) && ( game . m_gameID . AppID ( ) . m_AppId == GlobalConst . SteamAppID ) ;
337+ FriendListUpdate ? . Invoke ( freund ) ;
338+ }
339+
281340 private void OnSteamOnline ( )
282341 {
283342 Trace . TraceInformation ( "Steam online" ) ;
284343
285344 lobbyJoinRequestCallback = new Callback < GameLobbyJoinRequested_t > ( t => { JoinFriendRequest ( t . m_steamIDFriend . m_SteamID ) ; } ) ;
286345 overlayActivatedCallback = new Callback < GameOverlayActivated_t > ( t => { OverlayActivated ( t . m_bActive != 0 ) ; } ) ;
287346 newConnectionCallback = Callback < P2PSessionRequest_t > . Create ( t => SteamNetworking . AcceptP2PSessionWithUser ( t . m_steamIDRemote ) ) ;
347+ friendStateChangeCallback = new Callback < PersonaStateChange_t > ( t => OnFriendStateUpdate ( t ) ) ;
288348 MySteamNameSanitized = Utils . StripInvalidLobbyNameChars ( GetMyName ( ) ) ;
289349
290350
@@ -296,9 +356,11 @@ private void OnSteamOnline()
296356 ev . Set ( ) ;
297357 } ) ;
298358 SteamNetworking . AllowP2PPacketRelay ( true ) ;
299- Friends = GetFriendIDs ( ) ;
359+ Friends = GetFriends ( ) ;
360+ Task . Factory . StartNew ( ( ) => DownloadAvatars ( Friends . Values . Select ( x => x . CSteamID ) . ToList ( ) ) ) ;
300361 ev . WaitOne ( 2000 ) ;
301362 SteamOnline ? . Invoke ( ) ;
363+ FriendListUpdate ? . Invoke ( null ) ;
302364 }
303365
304366 private void ProcessMessage ( ulong remoteUser , Dummy cmd )
@@ -439,10 +501,15 @@ private void TimerOnElapsed(object sender)
439501 }
440502 }
441503
442- class SteamFriend
504+ public class SteamFriend
443505 {
506+ private CSteamID _cSteamID ;
444507
445- public string SteamID ;
508+ [ JsonIgnore ]
509+ public CSteamID CSteamID { get { return _cSteamID ; } set { _cSteamID = value ; SteamID = value . m_SteamID ; } }
510+ public ulong SteamID { get ; private set ; }
446511 public string Name ;
512+ public bool IsOnline ;
513+ public bool IsPlaying ; //whether friend is playing zk right now
447514 }
448515}
0 commit comments