public async Task <GameListData> GetGameList(bool admin) { using (var http = new HttpClient()) { var res_raw = await http.GetAsync(queryUrl).ConfigureAwait(false); var res = await res_raw.Content.ReadAsStringAsync(); var gamelist = JsonConvert.DeserializeObject <Dictionary <string, Lobby> >(res); SessionItem DefaultSession = new SessionItem(); DefaultSession.Type = GAMELIST_TERMS.TYPE_LISTEN; DefaultSession.Attributes.Add(GAMELIST_TERMS.ATTRIBUTE_LISTSERVER, $"Rebellion"); DataCache Metadata = new DataCache(); if (res_raw.Content.Headers.LastModified.HasValue) { Metadata.AddObjectPath($"{GAMELIST_TERMS.ATTRIBUTE_LISTSERVER}:Rebellion:Timestamp", res_raw.Content.Headers.LastModified.Value.ToUniversalTime().UtcDateTime); } DataCache DataCache = new DataCache(); DataCache Mods = new DataCache(); DataCache Heroes = new DataCache(); List <SessionItem> Sessions = new List <SessionItem>(); List <Task> Tasks = new List <Task>(); SemaphoreSlim DataCacheLock = new SemaphoreSlim(1); SemaphoreSlim ModsLock = new SemaphoreSlim(1); SemaphoreSlim HeroesLock = new SemaphoreSlim(1); SemaphoreSlim SessionsLock = new SemaphoreSlim(1); foreach (var raw in gamelist.Values) { if (raw.LobbyType != Lobby.ELobbyType.Game) { continue; } if (raw.isPrivate && !(raw.IsPassworded ?? false)) { continue; } Tasks.Add(Task.Run(async() => { SessionItem game = new SessionItem(); game.Name = raw.Name; game.Address["LobbyID"] = $"B{raw.id}"; game.PlayerTypes.Add(new PlayerTypeData() { Types = new List <string>() { GAMELIST_TERMS.PLAYERTYPE_PLAYER }, Max = raw.PlayerLimit }); game.PlayerCount.Add(GAMELIST_TERMS.PLAYERTYPE_PLAYER, raw.userCount); string modID = (raw.WorkshopID ?? @"0"); string mapID = System.IO.Path.GetFileNameWithoutExtension(raw.MapFile).ToLowerInvariant(); game.Level["ID"] = $"{modID}:{mapID}"; game.Level["MapFile"] = raw.MapFile; game.Level["CRC32"] = raw.CRC32; Task <MapData> mapDataTask = mapDataInterface.GetJson($"{mapUrl.TrimEnd('/')}/getdata.php?map={mapID}&mods={modID},0"); if (!string.IsNullOrWhiteSpace(raw.WorkshopID) && raw.WorkshopID != "0") { game.Level.Add("Mod", raw.WorkshopID); } if (raw.TimeLimit.HasValue && raw.TimeLimit > 0) { game.Level.AddObjectPath("Attributes:TimeLimit", raw.TimeLimit); } if (raw.KillLimit.HasValue && raw.KillLimit > 0) { game.Level.AddObjectPath("Attributes:KillLimit", raw.KillLimit); } if (raw.Lives.HasValue && raw.Lives.Value > 0) { game.Level.AddObjectPath("Attributes:Lives", raw.Lives.Value); } if (raw.SatelliteEnabled.HasValue) { game.Level.AddObjectPath("Attributes:Satellite", raw.SatelliteEnabled.Value); } if (raw.BarracksEnabled.HasValue) { game.Level.AddObjectPath("Attributes:Barracks", raw.BarracksEnabled.Value); } if (raw.SniperEnabled.HasValue) { game.Level.AddObjectPath("Attributes:Sniper", raw.SniperEnabled.Value); } if (raw.SplinterEnabled.HasValue) { game.Level.AddObjectPath("Attributes:Splinter", raw.SplinterEnabled.Value); } // unlocked in progress games with SyncJoin will trap the user due to a bug, just list as locked if (raw.SyncJoin.HasValue && raw.SyncJoin.Value && (!raw.IsEnded && raw.IsLaunched)) { game.Status.Add(GAMELIST_TERMS.STATUS_LOCKED, true); } else { game.Status.Add(GAMELIST_TERMS.STATUS_LOCKED, raw.isLocked); } game.Status.Add(GAMELIST_TERMS.STATUS_PASSWORD, raw.IsPassworded); game.Status.Add(GAMELIST_TERMS.STATUS_STATE, Enum.GetName(typeof(ESessionState), raw.IsEnded ? ESessionState.PostGame : raw.IsLaunched ? ESessionState.InGame : ESessionState.PreGame)); foreach (var dr in raw.users.Values) { PlayerItem player = new PlayerItem(); player.Name = dr.name; player.Type = GAMELIST_TERMS.PLAYERTYPE_PLAYER; if (admin) { player.Attributes.Add("wanAddress", dr.wanAddress); } player.Attributes.Add("Launched", dr.Launched); if (admin) { player.Attributes.Add("lanAddresses", JArray.FromObject(dr.lanAddresses)); } player.Attributes.Add("isAuth", dr.isAuth); if (dr.Team.HasValue) { player.Team = new PlayerTeam(); player.Team.ID = dr.Team.Value.ToString(); player.GetIDData("Slot").Add("ID", dr.Team); } //player.Attributes.Add("Vehicle", dr.Vehicle); if (dr.Vehicle != null) { player.Hero = new PlayerHero(); player.Hero.ID = (raw.WorkshopID ?? @"0") + @":" + dr.Vehicle.ToLowerInvariant(); player.Hero.Attributes["ODF"] = dr.Vehicle; } if (!string.IsNullOrWhiteSpace(dr.id)) { player.GetIDData("BZRNet").Add("ID", dr.id); if (dr.id == raw.owner) { player.Attributes.Add("IsOwner", true); } switch (dr.id[0]) { case 'S': // dr.authType == "steam" { player.GetIDData("Steam").Add("Raw", dr.id.Substring(1)); try { ulong playerID = 0; if (ulong.TryParse(dr.id.Substring(1), out playerID)) { player.GetIDData("Steam").Add("ID", playerID.ToString()); await DataCacheLock.WaitAsync(); try { if (!DataCache.ContainsPath($"Players:IDs:Steam:{playerID.ToString()}")) { PlayerSummaryModel playerData = await steamInterface.Users(playerID); DataCache.AddObjectPath($"Players:IDs:Steam:{playerID.ToString()}:AvatarUrl", playerData.AvatarFullUrl); DataCache.AddObjectPath($"Players:IDs:Steam:{playerID.ToString()}:Nickname", playerData.Nickname); DataCache.AddObjectPath($"Players:IDs:Steam:{playerID.ToString()}:ProfileUrl", playerData.ProfileUrl); } } finally { DataCacheLock.Release(); } } } catch { } } break; case 'G': { player.GetIDData("Gog").Add("Raw", dr.id.Substring(1)); try { ulong playerID = 0; if (ulong.TryParse(dr.id.Substring(1), out playerID)) { playerID = GogInterface.CleanGalaxyUserId(playerID); player.GetIDData("Gog").Add("ID", playerID.ToString()); await DataCacheLock.WaitAsync(); try { if (!DataCache.ContainsPath($"Players:IDs:Gog:{playerID.ToString()}")) { GogUserData playerData = await gogInterface.Users(playerID); DataCache.AddObjectPath($"Players:IDs:Gog:{playerID.ToString()}:AvatarUrl", playerData.Avatar.sdk_img_184 ?? playerData.Avatar.large_2x ?? playerData.Avatar.large); DataCache.AddObjectPath($"Players:IDs:Gog:{playerID.ToString()}:Username", playerData.username); DataCache.AddObjectPath($"Players:IDs:Gog:{playerID.ToString()}:ProfileUrl", $"https://www.gog.com/u/{playerData.username}"); } } finally { DataCacheLock.Release(); } } } catch { } } break; } } game.Players.Add(player); } if (!string.IsNullOrWhiteSpace(raw.clientVersion)) { game.Game["Version"] = raw.clientVersion; } else if (!string.IsNullOrWhiteSpace(raw.GameVersion)) { game.Game["Version"] = raw.GameVersion; } if (raw.SyncJoin.HasValue) { game.Attributes.Add("SyncJoin", raw.SyncJoin.Value); } if (raw.MetaDataVersion.HasValue) { game.Attributes.Add("MetaDataVersion", raw.MetaDataVersion); } MapData mapData = null; if (mapDataTask != null) { mapData = await mapDataTask; } if (mapData != null) { game.Level["Image"] = $"{mapUrl.TrimEnd('/')}/{mapData.image ?? "nomap.png"}"; game.Level["Name"] = mapData?.map?.title; game.Level["GameType"] = mapData?.map?.type; if (string.IsNullOrWhiteSpace(mapData?.map?.custom_type)) { if (!string.IsNullOrWhiteSpace(mapData?.map?.type)) { switch (mapData?.map?.type) { case "D": game.Level["GameMode"] = "Deathmatch"; break; case "S": game.Level["GameMode"] = "Strategy"; break; case "K": game.Level["GameMode"] = "King of the Hill"; break; case "M": game.Level["GameMode"] = "Mission MPI"; break; case "A": game.Level["GameMode"] = "Action MPI"; break; } } } else { game.Level["GameMode"] = mapData?.map?.custom_type; } await ModsLock.WaitAsync(); if (mapData?.mods != null) { foreach (var mod in mapData.mods) { if (!Mods.ContainsKey(mod.Key)) { Mods.AddObjectPath($"{mod.Key}:Name", mod.Value?.name ?? mod.Value?.workshop_name); Mods.AddObjectPath($"{mod.Key}:ID", mod.Key); if (UInt64.TryParse(mod.Key, out _)) { Mods.AddObjectPath($"{mod.Key}:Url", $"http://steamcommunity.com/sharedfiles/filedetails/?id={mod.Key}"); } } } } ModsLock.Release(); game.Level["AllowedHeroes"] = new JArray(mapData.map.vehicles.Select(dr => $"{dr}").ToArray()); foreach (var vehicle in mapData.vehicles) { await HeroesLock.WaitAsync(); try { if (!Heroes.ContainsPath($"{vehicle.Key.Replace(":", "\\:")}")) { Heroes.AddObjectPath($"{vehicle.Key.Replace(":", "\\:")}:Name", vehicle.Value.name); if (vehicle.Value.description != null) { if (vehicle.Value.description.ContainsKey("en")) { Heroes.AddObjectPath($"{vehicle.Key.Replace(":", "\\:")}:Description", vehicle.Value.description["en"].content); } else if (vehicle.Value.description.ContainsKey("default")) { Heroes.AddObjectPath($"{vehicle.Key.Replace(":", "\\:")}:Description", vehicle.Value.description["default"].content); } } } } finally { HeroesLock.Release(); } } foreach (var player in game.Players) { if (player.Hero != null) { string ODF = player.Hero.Attributes["ODF"]?.Value <string>(); if (!string.IsNullOrWhiteSpace(ODF)) { string ProperHeroID = mapData.map?.vehicles?.Where(heroData => heroData.EndsWith($":{ODF}")).FirstOrDefault(); if (!string.IsNullOrWhiteSpace(ProperHeroID)) { player.Hero.ID = ProperHeroID; } else { ProperHeroID = Heroes.Where(heroData => heroData.Key.EndsWith($":{ODF}")).Select(dr => dr.Key).FirstOrDefault(); if (!string.IsNullOrWhiteSpace(ProperHeroID)) { player.Hero.ID = ProperHeroID; } } } } } } await SessionsLock.WaitAsync(); try { Sessions.Add(game); } finally { SessionsLock.Release(); } })); } Task.WaitAll(Tasks.ToArray()); return(new GameListData() { Metadata = Metadata, SessionDefault = DefaultSession, DataCache = DataCache, Sessions = Sessions, Mods = Mods, Heroes = Heroes, Raw = admin ? res : null, }); } }
public async Task <(SessionItem, IEnumerable <SessionItem>, JToken)> GetGameList() { using (var http = new HttpClient()) { var res = await http.GetStringAsync(queryUrl).ConfigureAwait(false); var gamelist = JsonConvert.DeserializeObject <BZCCRaknetData>(res); SessionItem DefaultSession = new SessionItem() { Type = "listen", SpectatorPossible = false, // unless we add special mod support //SpectatorSeperate = false, }; List <SessionItem> Sessions = new List <SessionItem>(); foreach (var raw in gamelist.GET) { SessionItem game = new SessionItem(); game.Name = raw.Name; if (!string.IsNullOrWhiteSpace(raw.MOTD)) { game.Message = raw.MOTD; } game.PlayerCount = raw.CurPlayers; game.PlayerMax = raw.MaxPlayers; game.Level.Add("MapFile", raw.MapFile); game.Level.Add("MapID", GameID + @":" + (raw.Mods?.FirstOrDefault() ?? @"0") + @":" + raw.MapFile); game.Status.Add("Locked", raw.Locked); game.Status.Add("Passworded", raw.Passworded); if (raw.ServerInfoMode.HasValue) { switch (raw.ServerInfoMode) { case 1: game.Status.Add("State", @"Lobby"); break; case 2: game.Status.Add("State", @"Loading"); // guess break; case 3: game.Status.Add("State", @"InGame"); break; case 4: game.Status.Add("State", @"Over"); // guess break; } } if ((raw.Mods?.Length ?? 0) > 0) { game.Attributes.Add("Mods", JArray.FromObject(raw.Mods)); } if (!string.IsNullOrWhiteSpace(raw.v)) { game.Attributes.Add("Version", raw.v); } if (raw.TPS.HasValue && raw.TPS > 0) { game.Attributes.Add("TPS", raw.TPS); } if (raw.MaxPing.HasValue && raw.MaxPing > 0) { game.Attributes.Add("MaxPing", raw.MaxPing); } if (raw.TimeLimit.HasValue && raw.TimeLimit > 0) { game.Attributes.Add("TimeLimit", raw.TimeLimit); } if (raw.KillLimit.HasValue && raw.KillLimit > 0) { game.Attributes.Add("KillLimit", raw.KillLimit); } if (!string.IsNullOrWhiteSpace(raw.t)) { switch (raw.t) { case "0": game.Attributes.Add("NAT", $"NONE"); /// Works with anyone break; case "1": game.Attributes.Add("NAT", $"FULL CONE"); /// Accepts any datagrams to a port that has been previously used. Will accept the first datagram from the remote peer. break; case "2": game.Attributes.Add("NAT", $"ADDRESS RESTRICTED"); /// Accepts datagrams to a port as long as the datagram source IP address is a system we have already sent to. Will accept the first datagram if both systems send simultaneously. Otherwise, will accept the first datagram after we have sent one datagram. break; case "3": game.Attributes.Add("NAT", $"PORT RESTRICTED"); /// Same as address-restricted cone NAT, but we had to send to both the correct remote IP address and correct remote port. The same source address and port to a different destination uses the same mapping. break; case "4": game.Attributes.Add("NAT", $"SYMMETRIC"); /// A different port is chosen for every remote destination. The same source address and port to a different destination uses a different mapping. Since the port will be different, the first external punchthrough attempt will fail. For this to work it requires port-prediction (MAX_PREDICTIVE_PORT_RANGE>1) and that the router chooses ports sequentially. break; case "5": game.Attributes.Add("NAT", $"UNKNOWN"); /// Hasn't been determined. NATTypeDetectionClient does not use this, but other plugins might break; case "6": game.Attributes.Add("NAT", $"DETECTION IN PROGRESS"); /// In progress. NATTypeDetectionClient does not use this, but other plugins might break; case "7": game.Attributes.Add("NAT", $"SUPPORTS UPNP"); /// Didn't bother figuring it out, as we support UPNP, so it is equivalent to NAT_TYPE_NONE. NATTypeDetectionClient does not use this, but other plugins might break; default: game.Attributes.Add("NAT", $"[" + raw.t + "]"); break; } } switch (raw.proxySource) { case "Rebellion": game.Attributes.Add("List", $"Rebellion"); break; default: game.Attributes.Add("List", $"IonDriver"); break; } bool m_TeamsOn = false; bool m_OnlyOneTeam = false; switch (raw.GameType) { case 0: game.Attributes.Add("Type", $"All"); break; case 1: { int GetGameModeOutput = raw.GameSubType.Value % (int)GameMode.GAMEMODE_MAX; // extract if we are team or not int detailed = raw.GameSubType.Value / (int)GameMode.GAMEMODE_MAX; // ivar7 bool RespawnSameRace = (detailed & 256) == 256; bool RespawnAnyRace = (detailed & 512) == 512; game.Attributes.Add("Respawn", RespawnSameRace ? "Race" : RespawnAnyRace ? "Any" : "One"); detailed = (detailed & 0xff); switch ((GameMode)GetGameModeOutput) { case GameMode.GAMEMODE_TEAM_DM: case GameMode.GAMEMODE_TEAM_KOTH: case GameMode.GAMEMODE_TEAM_CTF: case GameMode.GAMEMODE_TEAM_LOOT: case GameMode.GAMEMODE_TEAM_RACE: m_TeamsOn = true; break; case GameMode.GAMEMODE_DM: case GameMode.GAMEMODE_KOTH: case GameMode.GAMEMODE_CTF: case GameMode.GAMEMODE_LOOT: case GameMode.GAMEMODE_RACE: default: m_TeamsOn = false; break; } switch (detailed) // first byte of ivar7? might be all of ivar7 // Deathmatch subtype (0 = normal; 1 = KOH; 2 = CTF; add 256 for random respawn on same race, or add 512 for random respawn w/o regard to race) { case 0: game.Level.Add("GameMode", (m_TeamsOn ? "TEAM " : String.Empty) + $"DM"); break; case 1: game.Level.Add("GameMode", (m_TeamsOn ? "TEAM " : String.Empty) + $"KOTH"); break; case 2: game.Level.Add("GameMode", (m_TeamsOn ? "TEAM " : String.Empty) + $"CTF"); break; case 3: game.Level.Add("GameMode", (m_TeamsOn ? "TEAM " : String.Empty) + $"Loot"); break; case 4: game.Level.Add("GameMode", (m_TeamsOn ? "TEAM " : String.Empty) + $"DM [RESERVED]"); break; case 5: game.Level.Add("GameMode", (m_TeamsOn ? "TEAM " : String.Empty) + $"Race"); break; case 6: game.Level.Add("GameMode", (m_TeamsOn ? "TEAM " : String.Empty) + $"Race (Vehicle Only)"); break; case 7: game.Level.Add("GameMode", (m_TeamsOn ? "TEAM " : String.Empty) + $"DM (Vehicle Only)"); break; default: game.Level.Add("GameMode", (m_TeamsOn ? "TEAM " : String.Empty) + $"DM [UNKNOWN {raw.GameSubType}]"); break; } } break; case 2: { int GetGameModeOutput = raw.GameSubType.Value % (int)GameMode.GAMEMODE_MAX; // extract if we are team or not switch ((GameMode)GetGameModeOutput) { case GameMode.GAMEMODE_TEAM_STRAT: game.Level.Add("GameMode", $"TEAM STRAT"); m_TeamsOn = true; m_OnlyOneTeam = false; break; case GameMode.GAMEMODE_STRAT: game.Level.Add("GameMode", $"STRAT"); m_TeamsOn = false; m_OnlyOneTeam = false; break; case GameMode.GAMEMODE_MPI: game.Level.Add("GameMode", $"MPI"); m_TeamsOn = true; m_OnlyOneTeam = true; break; default: game.Level.Add("GameMode", $"STRAT [UNKNOWN {GetGameModeOutput}]"); break; } } break; case 3: // impossible, BZCC limits to 0-2 game.Attributes.Add("Type", $"MPI [Invalid]"); break; } if (!string.IsNullOrWhiteSpace(raw.d)) { game.Attributes.Add("ModHash", raw.d); // base64 encoded CRC32 } foreach (var dr in raw.pl) { PlayerItem player = new PlayerItem(); player.Name = dr.Name; if ((dr.Team ?? 255) != 255) // 255 means not on a team yet? could be understood as -1 { player.Team = new PlayerTeam(); if (m_TeamsOn) { if (!m_OnlyOneTeam) { if (dr.Team >= 1 && dr.Team <= 5) { player.Team.ID = 1; } if (dr.Team >= 6 && dr.Team <= 10) { player.Team.ID = 2; } if (dr.Team == 1 || dr.Team == 6) { player.Team.Leader = true; } } } player.Team.SubTeam = new PlayerTeam() { ID = dr.Team.Value }; player.GetIDData("Slot").Add("ID", dr.Team); } if (dr.Kills.HasValue) { player.Stats.Add("Kills", dr.Kills); } if (dr.Deaths.HasValue) { player.Stats.Add("Deaths", dr.Deaths); } if (dr.Score.HasValue) { player.Stats.Add("Score", dr.Score); } if (!string.IsNullOrWhiteSpace(dr.PlayerID)) { player.GetIDData("BZRNet").Add("ID", dr.PlayerID); switch (dr.PlayerID[0]) { case 'S': { player.GetIDData("Steam").Add("Raw", dr.PlayerID.Substring(1)); try { ulong playerID = 0; if (ulong.TryParse(dr.PlayerID.Substring(1), out playerID)) { player.GetIDData("Steam").Add("ID", playerID); PlayerSummaryModel playerData = await steamInterface.Users(playerID); player.GetIDData("Steam").Add("AvatarUrl", playerData.AvatarFullUrl); player.GetIDData("Steam").Add("Nickname", playerData.Nickname); player.GetIDData("Steam").Add("ProfileUrl", playerData.ProfileUrl); } } catch { } } break; case 'G': { player.GetIDData("Gog").Add("Raw", dr.PlayerID.Substring(1)); try { ulong playerID = 0; if (ulong.TryParse(dr.PlayerID.Substring(1), out playerID)) { //player.GetIDData("Gog").Add("LargeID", playerID); playerID &= 0x00ffffffffffffff; player.GetIDData("Gog").Add("ID", playerID); GogUserData playerData = await gogInterface.Users(playerID); player.GetIDData("Gog").Add("AvatarUrl", playerData.Avatar.sdk_img_184 ?? playerData.Avatar.large_2x ?? playerData.Avatar.large); player.GetIDData("Gog").Add("UserName", playerData.username); player.GetIDData("Gog").Add("ProfileUrl", $"https://www.gog.com/u/{playerData.username}"); } } catch { } } break; } } game.Players.Add(player); } if (raw.GameTimeMinutes.HasValue) { if (raw.GameTimeMinutes.Value == 255) // 255 appears to mean it maxed out? Does for currently playing. { game.Attributes.Add("GameTimeMinutes", "255+"); } else { game.Attributes.Add("GameTimeMinutes", raw.GameTimeMinutes); } } Sessions.Add(game); } return(DefaultSession, Sessions, JObject.Parse(res)); } }
public async Task <GameListData> GetGameList(bool admin) { using (var http = new HttpClient()) { var res = await http.GetStringAsync(queryUrl).ConfigureAwait(false); var gamelist = JsonConvert.DeserializeObject <BZCCRaknetData>(res); SessionItem DefaultSession = new SessionItem(); DefaultSession.Type = GAMELIST_TERMS.TYPE_LISTEN; DataCache Metadata = new DataCache(); foreach (var proxyStatus in gamelist.proxyStatus) { Metadata.AddObjectPath($"{GAMELIST_TERMS.ATTRIBUTE_LISTSERVER}:{proxyStatus.Key}:Status", proxyStatus.Value.status); Metadata.AddObjectPath($"{GAMELIST_TERMS.ATTRIBUTE_LISTSERVER}:{proxyStatus.Key}:Success", proxyStatus.Value.success); Metadata.AddObjectPath($"{GAMELIST_TERMS.ATTRIBUTE_LISTSERVER}:{proxyStatus.Key}:Timestamp", proxyStatus.Value.updated); } DataCache DataCache = new DataCache(); DataCache Mods = new DataCache(); List <SessionItem> Sessions = new List <SessionItem>(); List <Task> Tasks = new List <Task>(); SemaphoreSlim DataCacheLock = new SemaphoreSlim(1); SemaphoreSlim ModsLock = new SemaphoreSlim(1); SemaphoreSlim SessionsLock = new SemaphoreSlim(1); foreach (var raw in gamelist.GET) { Tasks.Add(Task.Run(async() => { SessionItem game = new SessionItem(); game.Address["NAT"] = raw.g; //if (!raw.Passworded) //{ // game.Address["Rich"] = string.Join(null, $"N,{raw.Name.Length},{raw.Name},{raw.mm.Length},{raw.mm},{raw.g},0,".Select(dr => $"{((int)dr):x2}")); //} game.Name = raw.Name; if (!string.IsNullOrWhiteSpace(raw.MOTD)) { game.Message = raw.MOTD; } game.PlayerTypes.Add(new PlayerTypeData() { Types = new List <string>() { GAMELIST_TERMS.PLAYERTYPE_PLAYER }, Max = raw.MaxPlayers }); game.PlayerCount.Add(GAMELIST_TERMS.PLAYERTYPE_PLAYER, raw.CurPlayers); if (!string.IsNullOrWhiteSpace(raw.MapFile)) { game.Level["MapFile"] = raw.MapFile + @".bzn"; } string modID = (raw.Mods?.FirstOrDefault() ?? @"0"); string mapID = raw.MapFile?.ToLowerInvariant(); game.Level["ID"] = $"{modID}:{mapID}"; Task <MapData> mapDataTask = null; if (!string.IsNullOrWhiteSpace(raw.MapFile)) { mapDataTask = mapDataInterface.GetJson($"{mapUrl.TrimEnd('/')}/getdata.php?map={mapID}&mod={modID}"); } game.Status.Add(GAMELIST_TERMS.STATUS_LOCKED, raw.Locked); game.Status.Add(GAMELIST_TERMS.STATUS_PASSWORD, raw.Passworded); string ServerState = null; if (raw.ServerInfoMode.HasValue) { switch (raw.ServerInfoMode) { case 0: // ServerInfoMode_Unknown ServerState = Enum.GetName(typeof(ESessionState), ESessionState.Unknown); break; case 1: // ServerInfoMode_OpenWaiting case 2: // ServerInfoMode_ClosedWaiting (full) ServerState = Enum.GetName(typeof(ESessionState), ESessionState.PreGame); break; case 3: // ServerInfoMode_OpenPlaying case 4: // ServerInfoMode_ClosedPlaying (full) ServerState = Enum.GetName(typeof(ESessionState), ESessionState.InGame); break; case 5: // ServerInfoMode_Exiting ServerState = Enum.GetName(typeof(ESessionState), ESessionState.PostGame); break; } } if (!string.IsNullOrWhiteSpace(ServerState)) { game.Status.Add("State", ServerState); } int ModsLen = (raw.Mods?.Length ?? 0); if (ModsLen > 0 && raw.Mods[0] != "0") { game.Game.Add("Mod", raw.Mods[0]); } if (ModsLen > 1) { game.Game.Add("Mods", JArray.FromObject(raw.Mods.Skip(1))); } if (!string.IsNullOrWhiteSpace(raw.v)) { game.Game["Version"] = raw.v; } if (raw.TPS.HasValue && raw.TPS > 0) { game.Attributes.Add("TPS", raw.TPS); } if (raw.MaxPing.HasValue && raw.MaxPing > 0) { game.Attributes.Add("MaxPing", raw.MaxPing); } if (raw.TimeLimit.HasValue && raw.TimeLimit > 0) { game.Level.AddObjectPath("Attributes:TimeLimit", raw.TimeLimit); } if (raw.KillLimit.HasValue && raw.KillLimit > 0) { game.Level.AddObjectPath("Attributes:KillLimit", raw.KillLimit); } if (!string.IsNullOrWhiteSpace(raw.t)) { switch (raw.t) { case "0": game.Address.Add("NAT_TYPE", $"NONE"); /// Works with anyone break; case "1": game.Address.Add("NAT_TYPE", $"FULL CONE"); /// Accepts any datagrams to a port that has been previously used. Will accept the first datagram from the remote peer. break; case "2": game.Address.Add("NAT_TYPE", $"ADDRESS RESTRICTED"); /// Accepts datagrams to a port as long as the datagram source IP address is a system we have already sent to. Will accept the first datagram if both systems send simultaneously. Otherwise, will accept the first datagram after we have sent one datagram. break; case "3": game.Address.Add("NAT_TYPE", $"PORT RESTRICTED"); /// Same as address-restricted cone NAT, but we had to send to both the correct remote IP address and correct remote port. The same source address and port to a different destination uses the same mapping. break; case "4": game.Address.Add("NAT_TYPE", $"SYMMETRIC"); /// A different port is chosen for every remote destination. The same source address and port to a different destination uses a different mapping. Since the port will be different, the first external punchthrough attempt will fail. For this to work it requires port-prediction (MAX_PREDICTIVE_PORT_RANGE>1) and that the router chooses ports sequentially. break; case "5": game.Address.Add("NAT_TYPE", $"UNKNOWN"); /// Hasn't been determined. NATTypeDetectionClient does not use this, but other plugins might break; case "6": game.Address.Add("NAT_TYPE", $"DETECTION IN PROGRESS"); /// In progress. NATTypeDetectionClient does not use this, but other plugins might break; case "7": game.Address.Add("NAT_TYPE", $"SUPPORTS UPNP"); /// Didn't bother figuring it out, as we support UPNP, so it is equivalent to NAT_TYPE_NONE. NATTypeDetectionClient does not use this, but other plugins might break; default: game.Address.Add("NAT_TYPE", $"[" + raw.t + "]"); break; } } if (string.IsNullOrWhiteSpace(raw.proxySource)) { game.Attributes.Add(GAMELIST_TERMS.ATTRIBUTE_LISTSERVER, $"IonDriver"); } else { game.Attributes.Add(GAMELIST_TERMS.ATTRIBUTE_LISTSERVER, raw.proxySource); } bool m_TeamsOn = false; bool m_OnlyOneTeam = false; switch (raw.GameType) { case 0: game.Level["GameType"] = $"All"; break; case 1: { int GetGameModeOutput = raw.GameSubType.Value % (int)GameMode.GAMEMODE_MAX; // extract if we are team or not int detailed = raw.GameSubType.Value / (int)GameMode.GAMEMODE_MAX; // ivar7 bool RespawnSameRace = (detailed & 256) == 256; bool RespawnAnyRace = (detailed & 512) == 512; game.Level.AddObjectPath("Attributes:Respawn", RespawnSameRace ? "Race" : RespawnAnyRace ? "Any" : "One"); detailed = (detailed & 0xff); switch ((GameMode)GetGameModeOutput) { case GameMode.GAMEMODE_TEAM_DM: case GameMode.GAMEMODE_TEAM_KOTH: case GameMode.GAMEMODE_TEAM_CTF: case GameMode.GAMEMODE_TEAM_LOOT: case GameMode.GAMEMODE_TEAM_RACE: m_TeamsOn = true; break; case GameMode.GAMEMODE_DM: case GameMode.GAMEMODE_KOTH: case GameMode.GAMEMODE_CTF: case GameMode.GAMEMODE_LOOT: case GameMode.GAMEMODE_RACE: default: m_TeamsOn = false; break; } switch (detailed) // first byte of ivar7? might be all of ivar7 // Deathmatch subtype (0 = normal; 1 = KOH; 2 = CTF; add 256 for random respawn on same race, or add 512 for random respawn w/o regard to race) { case 0: game.Level["GameType"] = "DM"; game.Level["GameMode"] = (m_TeamsOn ? "Team " : String.Empty) + "Deathmatch"; break; case 1: game.Level["GameType"] = "KOTH"; game.Level["GameMode"] = (m_TeamsOn ? "Team " : String.Empty) + "King of the Hill"; break; case 2: game.Level["GameType"] = "CTF"; game.Level["GameMode"] = (m_TeamsOn ? "Team " : String.Empty) + "Capture the Flag"; break; case 3: game.Level["GameType"] = "Loot"; game.Level["GameMode"] = (m_TeamsOn ? "Team " : String.Empty) + "Loot"; break; case 4: // DM [RESERVED] game.Level["GameType"] = "DM"; break; case 5: game.Level["GameType"] = "Race"; game.Level["GameMode"] = (m_TeamsOn ? "Team " : String.Empty) + "Race"; break; case 6: // Race (Vehicle Only) game.Level["GameType"] = "Race"; game.Level["GameMode"] = (m_TeamsOn ? "Team " : String.Empty) + "Race"; game.Level.AddObjectPath("Attributes:VehicleOnly", true); break; case 7: // DM (Vehicle Only) game.Level["GameType"] = "DM"; game.Level["GameMode"] = (m_TeamsOn ? "Team " : String.Empty) + "Deathmatch"; game.Level.AddObjectPath("Attributes:VehicleOnly", true); break; default: game.Level["GameType"] = "DM"; //game.Level["GameMode"] = (m_TeamsOn ? "TEAM " : String.Empty) + "DM [UNKNOWN {raw.GameSubType}]"; break; } } break; case 2: { int GetGameModeOutput = raw.GameSubType.Value % (int)GameMode.GAMEMODE_MAX; // extract if we are team or not switch ((GameMode)GetGameModeOutput) { case GameMode.GAMEMODE_TEAM_STRAT: game.Level["GameType"] = "STRAT"; game.Level["GameMode"] = "STRAT"; m_TeamsOn = true; m_OnlyOneTeam = false; break; case GameMode.GAMEMODE_STRAT: game.Level["GameType"] = "STRAT"; game.Level["GameMode"] = "FFA"; m_TeamsOn = false; m_OnlyOneTeam = false; break; case GameMode.GAMEMODE_MPI: game.Level["GameType"] = "MPI"; game.Level["GameMode"] = "MPI"; m_TeamsOn = true; m_OnlyOneTeam = true; break; default: //game.Level["GameType"] = $"STRAT [UNKNOWN {GetGameModeOutput}]"; game.Level["GameType"] = "STRAT"; //game.Level["GameMode"] = null; break; } } break; case 3: // impossible, BZCC limits to 0-2 game.Level["GameType"] = "MPI"; // "MPI [Invalid]"; break; } if (!string.IsNullOrWhiteSpace(raw.d)) { game.Game.Add("ModHash", raw.d); // base64 encoded CRC32 } foreach (var dr in raw.pl) { PlayerItem player = new PlayerItem(); player.Name = dr.Name; player.Type = GAMELIST_TERMS.PLAYERTYPE_PLAYER; if ((dr.Team ?? 255) != 255) // 255 means not on a team yet? could be understood as -1 { player.Team = new PlayerTeam(); if (m_TeamsOn) { if (!m_OnlyOneTeam) { if (dr.Team >= 1 && dr.Team <= 5) { player.Team.ID = "1"; } if (dr.Team >= 6 && dr.Team <= 10) { player.Team.ID = "2"; } if (dr.Team == 1 || dr.Team == 6) { player.Team.Leader = true; } } } player.Team.SubTeam = new PlayerTeam() { ID = dr.Team.Value.ToString() }; player.GetIDData("Slot").Add("ID", dr.Team); } if (dr.Kills.HasValue) { player.Stats.Add("Kills", dr.Kills); } if (dr.Deaths.HasValue) { player.Stats.Add("Deaths", dr.Deaths); } if (dr.Score.HasValue) { player.Stats.Add("Score", dr.Score); } if (!string.IsNullOrWhiteSpace(dr.PlayerID)) { player.GetIDData("BZRNet").Add("ID", dr.PlayerID); switch (dr.PlayerID[0]) { case 'S': { player.GetIDData("Steam").Add("Raw", dr.PlayerID.Substring(1)); try { ulong playerID = 0; if (ulong.TryParse(dr.PlayerID.Substring(1), out playerID)) { player.GetIDData("Steam").Add("ID", playerID.ToString()); await DataCacheLock.WaitAsync(); try { if (!DataCache.ContainsPath($"Players:IDs:Steam:{playerID.ToString()}")) { PlayerSummaryModel playerData = await steamInterface.Users(playerID); DataCache.AddObjectPath($"Players:IDs:Steam:{playerID.ToString()}:AvatarUrl", playerData.AvatarFullUrl); DataCache.AddObjectPath($"Players:IDs:Steam:{playerID.ToString()}:Nickname", playerData.Nickname); DataCache.AddObjectPath($"Players:IDs:Steam:{playerID.ToString()}:ProfileUrl", playerData.ProfileUrl); } } finally { DataCacheLock.Release(); } } } catch { } } break; case 'G': { player.GetIDData("Gog").Add("Raw", dr.PlayerID.Substring(1)); try { ulong playerID = 0; if (ulong.TryParse(dr.PlayerID.Substring(1), out playerID)) { playerID = GogInterface.CleanGalaxyUserId(playerID); player.GetIDData("Gog").Add("ID", playerID.ToString()); await DataCacheLock.WaitAsync(); try { if (!DataCache.ContainsPath($"Players:IDs:Gog:{playerID.ToString()}")) { GogUserData playerData = await gogInterface.Users(playerID); DataCache.AddObjectPath($"Players:IDs:Gog:{playerID.ToString()}:AvatarUrl", playerData.Avatar.sdk_img_184 ?? playerData.Avatar.large_2x ?? playerData.Avatar.large); DataCache.AddObjectPath($"Players:IDs:Gog:{playerID.ToString()}:Username", playerData.username); DataCache.AddObjectPath($"Players:IDs:Gog:{playerID.ToString()}:ProfileUrl", $"https://www.gog.com/u/{playerData.username}"); } } finally { DataCacheLock.Release(); } } } catch { } } break; } } game.Players.Add(player); } if (raw.GameTimeMinutes.HasValue) { game.Time.AddObjectPath("Seconds", raw.GameTimeMinutes * 60); game.Time.AddObjectPath("Resolution", 60); game.Time.AddObjectPath("Max", raw.GameTimeMinutes.Value == 255); // 255 appears to mean it maxed out? Does for currently playing. if (!string.IsNullOrWhiteSpace(ServerState)) { game.Time.AddObjectPath("Context", ServerState); } } MapData mapData = null; if (mapDataTask != null) { mapData = await mapDataTask; } if (mapData != null) { game.Level["Image"] = $"{mapUrl.TrimEnd('/')}/{mapData.image ?? "nomap.png"}"; game.Level["Name"] = mapData?.title; game.Level["Description"] = mapData?.description; //game.Level.AddObjectPath("Attributes:Vehicles", new JArray(mapData.map.vehicles.Select(dr => $"{modID}:{dr}").ToArray())); await ModsLock.WaitAsync(); if (mapData?.mods != null) { foreach (var mod in mapData.mods) { if (!Mods.ContainsKey(mod.Key)) { Mods.AddObjectPath($"{mod.Key}:Name", mod.Value?.name ?? mod.Value?.workshop_name); Mods.AddObjectPath($"{mod.Key}:ID", mod.Key); if (UInt64.TryParse(mod.Key, out _)) { Mods.AddObjectPath($"{mod.Key}:Url", $"http://steamcommunity.com/sharedfiles/filedetails/?id={mod.Key}"); } } } } ModsLock.Release(); } await SessionsLock.WaitAsync(); try { Sessions.Add(game); } finally { SessionsLock.Release(); } })); } Task.WaitAll(Tasks.ToArray()); return(new GameListData() { Metadata = Metadata, SessionDefault = DefaultSession, DataCache = DataCache, Sessions = Sessions, Mods = Mods, Raw = admin ? res : null, }); } }
public async Task <(SessionItem, IEnumerable <SessionItem>, JToken)> GetGameList() { using (var http = new HttpClient()) { var res = await http.GetStringAsync(queryUrl).ConfigureAwait(false); var gamelist = JsonConvert.DeserializeObject <Dictionary <string, Lobby> >(res); SessionItem DefaultSession = new SessionItem() { Type = "listen", SpectatorPossible = false, // unless we add special mod support //SpectatorSeperate = false, }; List <SessionItem> Sessions = new List <SessionItem>(); foreach (var raw in gamelist.Values) { if (raw.LobbyType != Lobby.ELobbyType.Game) { continue; } SessionItem game = new SessionItem(); game.Name = raw.Name; //if (!string.IsNullOrWhiteSpace(raw.MOTD)) // game.Message = raw.MOTD; game.PlayerCount = raw.userCount; game.PlayerMax = raw.PlayerLimit; game.Level.Add("MapFile", raw.MapFile); game.Level.Add("MapID", GameID + @":" + (raw.WorkshopID ?? @"0") + @":" + raw.MapFile); game.Status.Add("Locked", raw.isLocked); game.Status.Add("Passworded", raw.IsPassworded); game.Status.Add("State", raw.IsEnded ? "Over" : raw.IsLaunched ? "InGame" : "Lobby"); if (!string.IsNullOrWhiteSpace(raw.WorkshopID)) { game.Attributes.Add("Mod", raw.WorkshopID); } if (!string.IsNullOrWhiteSpace(raw.clientVersion)) { game.Attributes.Add("Version", raw.clientVersion); } if (raw.TimeLimit.HasValue && raw.TimeLimit > 0) { game.Attributes.Add("TimeLimit", raw.TimeLimit); } if (raw.KillLimit.HasValue && raw.KillLimit > 0) { game.Attributes.Add("KillLimit", raw.KillLimit); } if (raw.Lives.HasValue && raw.Lives.Value > 0) { game.Attributes.Add("Lives", raw.Lives.Value); } if (raw.SyncJoin.HasValue) { game.Attributes.Add("SyncJoin", raw.SyncJoin.Value); } if (raw.SatelliteEnabled.HasValue) { game.Attributes.Add("Satellite", raw.SatelliteEnabled.Value); } if (raw.BarracksEnabled.HasValue) { game.Attributes.Add("Barracks", raw.BarracksEnabled.Value); } if (raw.SniperEnabled.HasValue) { game.Attributes.Add("Sniper", raw.SniperEnabled.Value); } if (raw.SplinterEnabled.HasValue) { game.Attributes.Add("Splinter", raw.SplinterEnabled.Value); } foreach (var dr in raw.users.Values) { PlayerItem player = new PlayerItem(); player.Name = dr.name; if (dr.Team.HasValue) { player.Team = new PlayerTeam(); player.Team.ID = dr.Team; player.GetIDData("Slot").Add("ID", dr.Team); } player.Attributes.Add("Vehicle", dr.Vehicle); if (!string.IsNullOrWhiteSpace(dr.id)) { player.GetIDData("BZRNet").Add("ID", dr.id); switch (dr.id[0]) { case 'S': // dr.authType == "steam" { player.GetIDData("Steam").Add("Raw", dr.id.Substring(1)); try { ulong playerID = 0; if (ulong.TryParse(dr.id.Substring(1), out playerID)) { player.GetIDData("Steam").Add("ID", playerID); PlayerSummaryModel playerData = await steamInterface.Users(playerID); player.GetIDData("Steam").Add("AvatarUrl", playerData.AvatarFullUrl); player.GetIDData("Steam").Add("Nickname", playerData.Nickname); player.GetIDData("Steam").Add("ProfileUrl", playerData.ProfileUrl); } } catch { } } break; case 'G': { player.GetIDData("Gog").Add("Raw", dr.id.Substring(1)); try { ulong playerID = 0; if (ulong.TryParse(dr.id.Substring(1), out playerID)) { //player.GetIDData("Gog").Add("LargeID", playerID); playerID &= 0x00ffffffffffffff; player.GetIDData("Gog").Add("ID", playerID); GogUserData playerData = await gogInterface.Users(playerID); player.GetIDData("Gog").Add("AvatarUrl", playerData.Avatar.sdk_img_184 ?? playerData.Avatar.large_2x ?? playerData.Avatar.large); player.GetIDData("Gog").Add("UserName", playerData.username); player.GetIDData("Gog").Add("ProfileUrl", $"https://www.gog.com/u/{playerData.username}"); } } catch { } } break; } } game.Players.Add(player); } Sessions.Add(game); } return(DefaultSession, Sessions, JObject.Parse(res)); } }