Decompiled source of SidsCompetitiveRounds v1.28.2

plugins\CompetitiveRounds.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using InControl;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using Steamworks;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;
using UnityEngine.Rendering.PostProcessing;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: IgnoresAccessChecksTo("PhotonRealtime")]
[assembly: IgnoresAccessChecksTo("PhotonUnityNetworking")]
[assembly: AssemblyCompany("CompetitiveRounds")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+878af82075371aad57e60ea72a1f31862239cca9")]
[assembly: AssemblyProduct("CompetitiveRounds")]
[assembly: AssemblyTitle("CompetitiveRounds")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CompetitiveRounds
{
	public static class ApiClient
	{
		public class RecentSeriesEntry
		{
			public string winner_name;

			public string p1_name;

			public string p2_name;

			public int p1_wins;

			public int p2_wins;

			public int p1_rating;

			public int p2_rating;

			public float p1_rating_change;

			public float p2_rating_change;

			public string winner_steam_id;

			public string p1_steam_id;

			public string p2_steam_id;

			public string completed_at;

			public List<SeriesBetEntry> bets = new List<SeriesBetEntry>();
		}

		public class SeriesBetEntry
		{
			public string bettor_name;

			public string bet_on_name;

			public string bettor_steam_id;

			public string bet_on_steam_id;

			public int amount;

			public int payout;

			public float odds_multiplier;

			public bool won;
		}

		[Serializable]
		public class MatchResponse
		{
			public string match_id;

			public string winner_steam_id;

			public string message;
		}

		[Serializable]
		public class LeaderboardData
		{
			public LeaderboardEntry[] entries;

			public int total_players;
		}

		[Serializable]
		public class LeaderboardEntry
		{
			public int rank;

			public string steam_id;

			public string display_name;

			public int rating;

			public int rd;

			public int total_matches;

			public int wins;

			public int losses;

			public float win_rate;

			public int level;

			public int gold;

			public string title;

			public string title_color;
		}

		[Serializable]
		public class PlayerStatsData
		{
			public string steam_id;

			public string display_name;

			public float rating;

			public float rating_deviation;

			public float peak_rating;

			public int total_matches;

			public int wins;

			public int losses;

			public float win_rate;

			public bool ranked_enabled;

			public int level;

			public int total_xp;

			public int xp_into_level;

			public int xp_for_next_level;

			public int best_ranked_streak;

			public int best_casual_streak;

			public int ranked_series_wins;

			public int ranked_series_losses;

			public int ranked_dc_count;

			public string discord_id;

			public string discord_username;

			public int gold_earned;

			public int gold_spent;

			public long bullets_fired;

			public long bullets_hit;

			public long blocks_activated;

			public long blocks_successful;

			public string active_title;

			public string active_title_color;

			public string active_trail_sku;

			public string active_trail_color;

			public int active_trail_price;

			public string active_color_sku;

			public List<string> active_color_skus;

			public string active_player_color_sku;

			public string active_player_color_hex;

			public string active_player_color_name;

			public string active_cursor_color_sku;

			public string active_cursor_color_hex;

			public string active_player_effect_sku;

			public bool hide_gold;

			public List<string> active_nametag_skus;

			public List<string> top_card_names;

			public List<int> top_card_picks;

			public List<float> top_card_win_rates;

			public List<string> recent_form;

			public List<float> rating_history;

			public int avg_fps;

			public float avg_cards_per_game;

			public int achievements_unlocked;

			public List<string> worst_card_names;

			public List<int> worst_card_picks;

			public List<float> worst_card_win_rates;

			public List<string> region_names;

			public List<int> region_matches;

			public string mod_version;

			public int h2h_ranked_wins;

			public int h2h_ranked_losses;

			public int h2h_casual_wins;

			public int h2h_casual_losses;

			public int h2h_series_wins;

			public int h2h_series_losses;
		}

		[Serializable]
		public class CardStatData
		{
			public string card_name;

			public string card_rarity;

			public int times_picked;

			public int wins_with_card;

			public float win_rate;

			public int times_offered;

			public float pass_rate;
		}

		[Serializable]
		private class CardStatsWrapper
		{
			public CardStatData[] items;
		}

		[Serializable]
		public class ModCheckResponse
		{
			public bool registered;

			public bool ranked;

			public string display_name;

			public bool banned;

			public string ban_reason;
		}

		[Serializable]
		public class MatchHistoryEntry
		{
			public string match_id;

			public string opponent_steam_id;

			public string opponent_name;

			public string opponent_title;

			public string opponent_title_color;

			public int player_rounds_won;

			public int opponent_rounds_won;

			public int player_points;

			public int opponent_points;

			public bool won;

			public bool is_ranked;

			public string ended_at;

			public string cards_display;

			public string opp_cards_display;

			public string series_id;

			public string series_score;

			public float series_rating_change;

			public int xp_gained;

			public int gold_gained;

			public int series_gold_gained;

			public int player_fps_avg;

			public int opponent_fps_avg;
		}

		[Serializable]
		private class MatchHistoryWrapper
		{
			public MatchHistoryEntry[] items;
		}

		[Serializable]
		public class AchievementData
		{
			public string achievement_key;

			public bool unlocked;

			public string unlocked_at;
		}

		[Serializable]
		public class ActiveSeriesEntry
		{
			public string series_id;

			public string p1_steam_id;

			public string p1_name;

			public int p1_rating;

			public int p1_wins;

			public float p1_odds;

			public string p2_steam_id;

			public string p2_name;

			public int p2_rating;

			public int p2_wins;

			public float p2_odds;

			public int live_p1_points;

			public int live_p2_points;

			public bool bets_locked;

			public string lock_reason;

			public bool is_private;

			public bool is_tournament;

			public string tournament_kind;

			public string phase;
		}

		public class MyBetEntry
		{
			public string series_id;

			public string bet_on_steam_id;

			public string bet_on_name;

			public string series_status;

			public string series_score;

			public int amount;

			public float odds_multiplier;
		}

		[Serializable]
		public class ActiveTeamSeriesEntry
		{
			public string series_id;

			public string t1a_steam;

			public string t1a_name;

			public string t1b_steam;

			public string t1b_name;

			public string t2a_steam;

			public string t2a_name;

			public string t2b_steam;

			public string t2b_name;

			public int t1_rating;

			public int t2_rating;

			public int t1a_rating;

			public int t1b_rating;

			public int t2a_rating;

			public int t2b_rating;

			public int t1_wins;

			public int t2_wins;

			public float t1_odds;

			public float t2_odds;

			public bool bets_locked;

			public string lock_reason;

			public string started_at;

			public string dc_grace_until;
		}

		[Serializable]
		public class ShopItemData
		{
			public long id;

			public string sku;

			public string kind;

			public string name;

			public string description;

			public int price;

			public string rarity;

			public string preview_color;

			public bool owned;
		}

		public class FlaggedMatchEntry
		{
			public string id;

			public string match_id;

			public string series_id;

			public string flag_reason;

			public string p1_name;

			public string p2_name;

			public List<string> player_steam_ids = new List<string>();

			public bool auto_invalidated;

			public bool match_invalidated;

			public bool is_ranked;

			public int duration_seconds;

			public string review_action;

			public string created_at;

			public string flag_details_summary;
		}

		public class BannedUserEntry
		{
			public string id;

			public string steam_id;

			public string display_name;

			public string reason;

			public string banned_by_steam_id;

			public string banned_at;
		}

		[Serializable]
		public class BugReportSummary
		{
			public string id;

			public int bug_number;

			public string created_at;

			public string steam_id;

			public string display_name;

			public string mod_version;

			public string severity;

			public string category;

			public string status;

			public string description;

			public bool has_log;

			public int log_bytes;
		}

		[Serializable]
		public class BugReportEventEntry
		{
			public string id;

			public string actor_steam_id;

			public string actor_name;

			public string event_type;

			public string old_status;

			public string new_status;

			public string comment;

			public string created_at;
		}

		[Serializable]
		public class BugReportDetail
		{
			public string id;

			public int bug_number;

			public string steam_id;

			public string display_name;

			public string mod_version;

			public string game_version;

			public string severity;

			public string category;

			public string description;

			public string repro_steps;

			public string status;

			public string triage_notes;

			public string created_at;

			public string log_text;

			public int log_bytes;

			public List<BugReportEventEntry> events = new List<BugReportEventEntry>();
		}

		public enum QueueState
		{
			Idle,
			Searching,
			Matched,
			ReadySent
		}

		[Serializable]
		public class QueuePollData
		{
			public string status;

			public int wait_time;

			public int queue_size;

			public int elo_range;

			public string opponent_steam_id;

			public string opponent_name;

			public float opponent_rating;

			public bool opponent_ready;

			public string room_name;

			public string photon_region;
		}

		public enum TeamQueueState
		{
			Idle,
			Searching,
			Matched,
			ReadySent
		}

		[Serializable]
		public class TeamQueueMember
		{
			public string steam_id;

			public string display_name;

			public int rating;

			public string region;

			public int team_assigned;

			public bool using_fallback_rating;

			public int balance_rating;

			public int completed_series;

			public bool ready;
		}

		[Serializable]
		public class TeamQueuePollData
		{
			public string status;

			public int queue_count;

			public int elo_range;

			public string series_id;

			public int team_assigned;

			public List<TeamQueueMember> teammates = new List<TeamQueueMember>();

			public List<TeamQueueMember> opponents = new List<TeamQueueMember>();

			public string room_name;

			public string room_region;

			public int match_age_seconds;

			public bool my_ready;
		}

		[Serializable]
		public class TeamQueueListEntry
		{
			public string steam_id;

			public string display_name;

			public int rating;

			public int balance_rating;

			public bool using_fallback_rating;

			public int completed_series;

			public string region;

			public string status;

			public int team_assigned;

			public string series_id;

			public int wait_seconds;

			public bool manual_pick_enabled;

			public int preferred_team;

			public string queue_type;
		}

		[Serializable]
		public class TeamLeaderboardEntry
		{
			public int rank;

			public string steam_id;

			public string display_name;

			public int rating;

			public int rd;

			public int completed_series;

			public int series_wins;

			public int series_losses;

			public float win_rate;

			public int level;

			public string title;

			public string title_color;

			public int avg_teammate_elo;

			public int team_gold_earned;

			public int team_xp_earned;
		}

		[Serializable]
		public class TeamSeriesSlot
		{
			public string steam_id;

			public string name;

			public string title;

			public string title_color;

			public int rating;

			public float rating_change;

			public int gold_earned;

			public int xp_earned;
		}

		[Serializable]
		public class TeamSeriesMatch
		{
			public string match_id;

			public string ended_at;

			public int t1_rounds_won;

			public int t2_rounds_won;

			public int t1_points_total;

			public int t2_points_total;

			public Dictionary<string, List<string>> cards_by_player = new Dictionary<string, List<string>>();
		}

		[Serializable]
		public class TeamSeriesPagedEntry
		{
			public string series_id;

			public string completed_at;

			public int winner_team;

			public int t1_series_wins;

			public int t2_series_wins;

			public TeamSeriesSlot t1a;

			public TeamSeriesSlot t1b;

			public TeamSeriesSlot t2a;

			public TeamSeriesSlot t2b;

			public List<TeamSeriesMatch> matches = new List<TeamSeriesMatch>();
		}

		public class TeamMatchHistoryEntry
		{
			public string match_id;

			public string series_id;

			public string ended_at;

			public bool won;

			public int my_team;

			public string t1a_steam_id;

			public string t1a_name;

			public string t1b_steam_id;

			public string t1b_name;

			public string t2a_steam_id;

			public string t2a_name;

			public string t2b_steam_id;

			public string t2b_name;

			public int t1_rounds_won;

			public int t2_rounds_won;

			public int t1_points_total;

			public int t2_points_total;

			public string series_score;

			public float series_rating_change;

			public Dictionary<string, int> fps_by_player = new Dictionary<string, int>();

			public Dictionary<string, List<string>> cards_by_player = new Dictionary<string, List<string>>();
		}

		[Serializable]
		public class TeamStatsData
		{
			public string steam_id;

			public string display_name;

			public float rating;

			public float rating_deviation;

			public float peak_rating;

			public int completed_series;

			public int series_wins;

			public int series_losses;

			public float series_win_rate;

			public int match_wins;

			public int match_losses;

			public int current_streak;
		}

		[Serializable]
		public class TournamentSnapshot
		{
			public string tournament_id;

			public string status;

			public string kind;

			public string default_start_ts;

			public string scheduled_start_ts;

			public string lock_at;

			public string started_at;

			public string ended_at;

			public int min_players;

			public int max_players;

			public string my_signup_id;

			public bool my_ready;

			public float my_penalty_pct;

			public bool my_discord_linked;

			public int force_vote_count;

			public string photon_region;

			public string[] my_votes;

			public string[] time_slot_options;

			public TimeVoteTally[] time_slot_tallies;

			public TournamentSignupRow[] signups;

			public TournamentMatchRow[] matches;
		}

		[Serializable]
		public class TimeVoteTally
		{
			public string slot_ts;

			public int votes;
		}

		[Serializable]
		public class TournamentSignupRow
		{
			public string signup_id;

			public string steam_id;

			public string display_name;

			public bool is_speculative;

			public int seed;

			public float penalty_at_signup;

			public bool ready;

			public bool forfeited;

			public int placed_rank;

			public string progress_label;
		}

		[Serializable]
		public class TournamentMatchRow
		{
			public string match_id;

			public int round;

			public string bracket_side;

			public int slot_idx;

			public string p1_signup_id;

			public string p2_signup_id;

			public string p1_display_name;

			public string p2_display_name;

			public bool is_bye;

			public string status;

			public string series_id;

			public string winner_signup_id;

			public int p1_series_wins;

			public int p2_series_wins;

			public string deadline_at;

			public string[] prereq_match_ids;

			public string photon_room_name;
		}

		[Serializable]
		public class PlayerTournamentHistory
		{
			public string steam_id;

			public int winner_count;

			public int runner_up_count;

			public int third_place_count;

			public int participant_count;

			public PlayerTournamentEntry[] recent;
		}

		[Serializable]
		public class PlayerTournamentEntry
		{
			public string tournament_id;

			public string ended_at;

			public int placed_rank;

			public string kind;

			public int signup_count;

			public string winner_display_name;
		}

		[Serializable]
		public class ActiveTournamentMatch
		{
			public string tournament_id;

			public string kind;

			public string match_id;

			public string status;

			public string bracket_side;

			public int round;

			public string opponent_steam_id;

			public string opponent_display_name;

			public string photon_room_name;

			public string photon_region;

			public bool my_ready;

			public bool opp_ready;
		}

		private static string baseUrl = "";

		private static float lastFetchTime = 0f;

		private static float fetchCooldown = 5f;

		public static readonly Dictionary<string, string[]> AchievementDefs = new Dictionary<string, string[]>
		{
			{
				"untouchable",
				new string[2] { "Untouchable", "Win a game without taking any damage" }
			},
			{
				"silent_assassin",
				new string[2] { "Silent Assassin", "5-0 someone with Sneaky" }
			},
			{
				"total_mayhem",
				new string[2] { "Total Mayhem", "5-0 someone with Mayhem" }
			},
			{
				"fragile_perfection",
				new string[2] { "Fragile Perfection", "5-0 someone with Glass Cannon" }
			},
			{
				"no_escape",
				new string[2] { "No Escape", "5-0 someone with Chase" }
			},
			{
				"rise_from_the_ashes",
				new string[2] { "Rise from the Ashes", "Win 5-0 with Phoenix without losing a life" }
			},
			{
				"the_comeback_kid",
				new string[2] { "The Comeback Kid", "Win after being down 0-4" }
			},
			{
				"stacked_deck",
				new string[2] { "Stacked Deck", "Get 5 copies of one card in a game" }
			},
			{
				"regicide",
				new string[2] { "Regicide", "Win against Sid in a ranked series" }
			},
			{
				"pacifist",
				new string[2] { "Pacifist", "Win a game without firing a single shot" }
			},
			{
				"immovable_object",
				new string[2] { "Immovable Object", "Win a game without moving or jumping" }
			},
			{
				"master_rank",
				new string[2] { "Master", "Reach 2030 rating in ranked (1v1 or 2v2)" }
			},
			{
				"team_sweep",
				new string[2] { "Tag Team Sweep", "Win a 2v2 game 5-0" }
			}
		};

		private static readonly HashSet<string> _heartbeatDispatchedMatches = new HashSet<string>();

		private const string GITHUB_API_LATEST = "https://api.github.com/repos/SidNDeed/SidsCompetitiveRounds/releases/latest";

		private static readonly byte[] _hkE = new byte[48]
		{
			226, 96, 125, 70, 209, 29, 174, 212, 243, 51,
			60, 47, 71, 237, 154, 50, 215, 106, 11, 24,
			165, 4, 227, 236, 220, 80, 34, 24, 21, 166,
			231, 46, 216, 70, 3, 51, 167, 90, 209, 209,
			253, 120, 28, 58, 2, 144, 239, 45
		};

		private static readonly byte[] _hkX = new byte[16]
		{
			178, 25, 79, 119, 230, 111, 150, 181, 186, 10,
			106, 95, 119, 223, 174, 75
		};

		private static string selectedAchSteamId = "";

		public static string ActiveRankedSeriesId;

		private static float queuePollTimer = 0f;

		private static float queuePollInterval = 3f;

		private static float queueCountTimer = 0f;

		private static float queueCountInterval = 10f;

		private static float teamQueueListTimer = 0f;

		private const float TEAM_QUEUE_LIST_INTERVAL = 2f;

		private static float teamQueuePollTimer = 0f;

		private static float teamQueueCountTimer = 0f;

		private const float TEAM_QUEUE_POLL_INTERVAL = 3f;

		private const float TEAM_QUEUE_COUNT_INTERVAL = 10f;

		public static string LastSeriesStateStatus = null;

		public static string LastSeriesStateReason = null;

		public static int LastSeriesStateConfirmations = 0;

		public static int LastSeriesDcGraceSeconds = 0;

		public static int LastSeriesDcTeamRemaining = 0;

		public static int LastSeriesT1Wins = 0;

		public static int LastSeriesT2Wins = 0;

		private static float teamSeriesStateTimer = 0f;

		private const float TEAM_SERIES_STATE_INTERVAL = 2f;

		private static bool blocksLoaded = false;

		public static float LastApiSuccessAt = -999f;

		public static float LastApiAttemptAt = -999f;

		public static bool LastResponseWasMaintenance = false;

		public const int BUG_REPORT_LOG_CAP_CHARS = 3500000;

		public static string CachedTournamentJson;

		public static TournamentSnapshot CachedTournament;

		private static float _tournamentRefreshAt;

		public static string TournamentKind = "sync";

		public static readonly Dictionary<string, PlayerTournamentHistory> CachedPlayerTournaments = new Dictionary<string, PlayerTournamentHistory>();

		public static List<ActiveTournamentMatch> CachedMyActiveTournamentMatches = new List<ActiveTournamentMatch>();

		private static float _myActiveMatchesRefreshAt;

		public static PlayerTournamentEntry[] CachedSiteTournamentHistory;

		public static string[] CachedSiteTournamentHistoryNames;

		public static LeaderboardData CachedLeaderboard { get; private set; }

		public static PlayerStatsData CachedPlayerStats { get; private set; }

		public static List<CardStatData> CachedCardStats { get; private set; }

		public static bool IsLoading { get; private set; } = false;

		public static string LastError { get; private set; } = "";

		public static string LatestModVersion { get; private set; } = null;

		public static string MinModVersion { get; private set; } = null;

		public static bool ForceUpdateRequired { get; private set; } = false;

		public static List<RecentSeriesEntry> CachedRecentSeries { get; private set; }

		public static List<MatchHistoryEntry> CachedMatchHistory { get; private set; }

		public static Dictionary<string, AchievementData> CachedAchievements { get; private set; }

		public static bool IsUpdating { get; private set; } = false;

		public static bool UpdateReady { get; private set; } = false;

		public static List<ActiveSeriesEntry> CachedActiveSeries { get; private set; }

		public static List<MyBetEntry> CachedMyBets { get; private set; } = new List<MyBetEntry>();

		public static List<ActiveTeamSeriesEntry> CachedActiveTeamSeries { get; private set; } = new List<ActiveTeamSeriesEntry>();

		public static List<ShopItemData> CachedShopItems { get; private set; }

		public static List<ShopItemData> CachedInventory { get; private set; }

		public static bool IsAdmin { get; private set; }

		public static List<FlaggedMatchEntry> CachedFlaggedMatches { get; private set; } = new List<FlaggedMatchEntry>();

		public static List<BannedUserEntry> CachedBannedUsers { get; private set; } = new List<BannedUserEntry>();

		public static List<BugReportSummary> CachedBugReports { get; private set; } = new List<BugReportSummary>();

		public static BugReportDetail CachedBugReportDetail { get; set; }

		public static Dictionary<string, AchievementData> SelectedPlayerAchievements { get; private set; }

		public static QueueState CurrentQueueState { get; private set; } = QueueState.Idle;

		public static QueuePollData LastPollData { get; private set; }

		public static bool IsQueuePolling { get; private set; } = false;

		public static int CachedQueueSearching { get; private set; } = 0;

		public static int CachedQueueTotal { get; private set; } = 0;

		public static List<TeamQueueListEntry> CachedTeamQueueList { get; private set; } = new List<TeamQueueListEntry>();

		public static List<TeamQueueListEntry> CachedTeamQueueAuto { get; private set; } = new List<TeamQueueListEntry>();

		public static List<TeamQueueListEntry> CachedTeamQueueManual { get; private set; } = new List<TeamQueueListEntry>();

		public static TeamQueueState CurrentTeamQueueState { get; private set; } = TeamQueueState.Idle;

		public static TeamQueuePollData LastTeamPollData { get; private set; }

		public static string ActiveTeamSeriesId { get; set; }

		public static bool IsTeamQueuePolling { get; private set; } = false;

		public static int CachedTeamQueueSearching { get; private set; } = 0;

		public static string CurrentTeamQueueType { get; private set; } = "auto";

		public static List<TeamLeaderboardEntry> CachedTeamLeaderboard { get; private set; } = new List<TeamLeaderboardEntry>();

		public static int CachedTeamLeaderboardTotal { get; private set; } = 0;

		public static string CachedTeamLeaderboardSort { get; private set; } = "rating";

		public static List<TeamSeriesPagedEntry> CachedTeamSeriesPaged { get; private set; } = new List<TeamSeriesPagedEntry>();

		public static int CachedTeamSeriesTotal { get; private set; } = 0;

		public static int CachedTeamSeriesPage { get; private set; } = 0;

		public static int CachedTeamSeriesPageSize { get; private set; } = 3;

		public static int CachedTeamSeriesTotalPages { get; private set; } = 0;

		public static List<TeamMatchHistoryEntry> CachedTeamMatchHistory { get; private set; } = new List<TeamMatchHistoryEntry>();

		public static TeamStatsData CachedTeamStats { get; private set; }

		public static HashSet<string> BlockedSteamIds { get; private set; } = new HashSet<string>();

		public static bool ApiLooksDown
		{
			get
			{
				if (LastApiAttemptAt < 0f)
				{
					return false;
				}
				float unscaledTime = Time.unscaledTime;
				if (unscaledTime - LastApiAttemptAt > 15f)
				{
					return false;
				}
				return unscaledTime - LastApiSuccessAt > 10f;
			}
		}

		public static void Initialize(string url)
		{
			baseUrl = url.TrimEnd('/');
			Plugin.Log.LogInfo((object)("API client initialized: " + baseUrl));
			CheckModVersion();
			((MonoBehaviour)Plugin.Instance).StartCoroutine(TournamentHeartbeatLoop());
		}

		private static IEnumerator TournamentHeartbeatLoop()
		{
			yield return (object)new WaitForSeconds(10f);
			while (true)
			{
				yield return (object)new WaitForSeconds(20f);
				string sid = MatchTracker.LocalSteamId;
				if (string.IsNullOrEmpty(sid) || sid == "unknown")
				{
					continue;
				}
				FetchMyActiveTournamentMatches(sid);
				if (CachedMyActiveTournamentMatches == null)
				{
					continue;
				}
				HashSet<string> seen = new HashSet<string>();
				foreach (ActiveTournamentMatch m in CachedMyActiveTournamentMatches)
				{
					if (!string.IsNullOrEmpty(m.tournament_id) && !seen.Contains(m.tournament_id))
					{
						seen.Add(m.tournament_id);
						TournamentReady(m.tournament_id, sid);
						yield return (object)new WaitForSeconds(0.2f);
						if (m.kind == "sync" && m.status == "ready" && m.my_ready && m.opp_ready && !string.IsNullOrEmpty(m.photon_room_name) && !string.IsNullOrEmpty(m.match_id) && !_heartbeatDispatchedMatches.Contains(m.match_id) && Plugin.PendingRankedRoom != m.photon_room_name)
						{
							_heartbeatDispatchedMatches.Add(m.match_id);
							Plugin.SetPendingRoom(m.photon_room_name, m.photon_region);
							Plugin.Log.LogInfo((object)("[TOURNAMENT-HB] Dispatch from heartbeat loop: room=" + m.photon_room_name + " region=" + (m.photon_region ?? "default") + " match=" + m.match_id));
						}
					}
				}
				if (CachedMyActiveTournamentMatches.Count == 0)
				{
					_heartbeatDispatchedMatches.Clear();
					continue;
				}
				HashSet<string> stillActive = new HashSet<string>();
				foreach (ActiveTournamentMatch cachedMyActiveTournamentMatch in CachedMyActiveTournamentMatches)
				{
					if (!string.IsNullOrEmpty(cachedMyActiveTournamentMatch.match_id))
					{
						stillActive.Add(cachedMyActiveTournamentMatch.match_id);
					}
				}
				_heartbeatDispatchedMatches.RemoveWhere((string id) => !stillActive.Contains(id));
			}
		}

		public static void CheckModVersion()
		{
			((MonoBehaviour)Plugin.Instance).StartCoroutine(DoCheckModVersion());
		}

		private static IEnumerator DoCheckModVersion()
		{
			UnityWebRequest req = UnityWebRequest.Get(baseUrl + "/api/v1/mod-version");
			yield return req.SendWebRequest();
			if ((int)req.result != 1)
			{
				yield break;
			}
			string text = ExtractJsonString(req.downloadHandler.text, "version");
			string text2 = ExtractJsonString(req.downloadHandler.text, "min_version");
			if (!string.IsNullOrEmpty(text))
			{
				LatestModVersion = text;
				if (text != "1.28.2")
				{
					Plugin.Log.LogWarning((object)("[VERSION] Update available: v1.28.2 → v" + text));
				}
				else
				{
					Plugin.Log.LogInfo((object)("[VERSION] Mod is up to date (v" + text + ")"));
				}
			}
			if (!string.IsNullOrEmpty(text2))
			{
				MinModVersion = text2;
				if (CompareVersion("1.28.2", text2) < 0)
				{
					ForceUpdateRequired = true;
					Plugin.Log.LogWarning((object)("[VERSION] Mod is BELOW server minimum (v1.28.2 < v" + text2 + ") — update required"));
				}
			}
			if (!string.IsNullOrEmpty(LatestModVersion) && CompareVersion("1.28.2", LatestModVersion) < 0 && !IsUpdating && !UpdateReady)
			{
				Plugin.Log.LogInfo((object)"[VERSION] Auto-firing update on launch");
				StartAutoUpdate();
			}
		}

		private static int CompareVersion(string a, string b)
		{
			if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b))
			{
				return 0;
			}
			string[] array = a.Split('.');
			string[] array2 = b.Split('.');
			int num = Math.Max(array.Length, array2.Length);
			for (int i = 0; i < num; i++)
			{
				int.TryParse((i < array.Length) ? array[i] : "0", out var result);
				int.TryParse((i < array2.Length) ? array2[i] : "0", out var result2);
				if (result != result2)
				{
					if (result >= result2)
					{
						return 1;
					}
					return -1;
				}
			}
			return 0;
		}

		public static void StartAutoUpdate()
		{
			if (!IsUpdating && !UpdateReady)
			{
				IsUpdating = true;
				((MonoBehaviour)Plugin.Instance).StartCoroutine(DoAutoUpdate());
			}
		}

		private static IEnumerator DoAutoUpdate()
		{
			Plugin.Log.LogInfo((object)"[UPDATE] Thunderstore build — auto-update disabled, use mod manager");
			CompetitiveUI.ShowNotification("Update available — update through your mod manager", Color.cyan, 6f);
			IsUpdating = false;
			NativeUI.MarkDirty();
			yield break;
		}

		private static byte[] GetHmacKeyBytes()
		{
			byte[] array = new byte[_hkE.Length];
			for (int i = 0; i < _hkE.Length; i++)
			{
				array[i] = (byte)(_hkE[i] ^ _hkX[i % _hkX.Length]);
			}
			return array;
		}

		private static string ComputeHmac(string p1SteamId, string p2SteamId, int p1Rounds, int p2Rounds, bool isRanked, string reporterSteamId, string roomId)
		{
			string s = $"{p1SteamId}:{p2SteamId}:{p1Rounds}:{p2Rounds}:" + (isRanked ? "true" : "false") + ":" + reporterSteamId + ":" + roomId;
			try
			{
				using HMACSHA256 hMACSHA = new HMACSHA256(GetHmacKeyBytes());
				byte[] array = hMACSHA.ComputeHash(Encoding.UTF8.GetBytes(s));
				StringBuilder stringBuilder = new StringBuilder(array.Length * 2);
				byte[] array2 = array;
				foreach (byte b in array2)
				{
					stringBuilder.Append(b.ToString("x2"));
				}
				return stringBuilder.ToString();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[HMAC] Computation failed: " + ex.Message));
				return "";
			}
		}

		private static string ComputeHmacHex(string message)
		{
			try
			{
				using HMACSHA256 hMACSHA = new HMACSHA256(GetHmacKeyBytes());
				byte[] array = hMACSHA.ComputeHash(Encoding.UTF8.GetBytes(message));
				StringBuilder stringBuilder = new StringBuilder(array.Length * 2);
				byte[] array2 = array;
				foreach (byte b in array2)
				{
					stringBuilder.Append(b.ToString("x2"));
				}
				return stringBuilder.ToString();
			}
			catch
			{
				return "";
			}
		}

		public static void FetchActiveSeries()
		{
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(baseUrl + "/api/v1/series/active", delegate(bool ok, string resp)
			{
				if (ok)
				{
					List<ActiveSeriesEntry> list = new List<ActiveSeriesEntry>();
					try
					{
						string[] array = resp.Split(new string[1] { "\"series_id\":" }, StringSplitOptions.None);
						for (int i = 1; i < array.Length; i++)
						{
							string text = "\"series_id\":" + array[i];
							ActiveSeriesEntry activeSeriesEntry = new ActiveSeriesEntry
							{
								series_id = ExtractJsonString(text, "series_id"),
								p1_steam_id = ExtractJsonString(text, "p1_steam_id"),
								p1_name = ExtractJsonString(text, "p1_name"),
								p1_rating = ExtractJsonInt(text, "p1_rating"),
								p1_wins = ExtractJsonInt(text, "p1_wins"),
								p1_odds = ExtractJsonFloat(text, "p1_odds"),
								p2_steam_id = ExtractJsonString(text, "p2_steam_id"),
								p2_name = ExtractJsonString(text, "p2_name"),
								p2_rating = ExtractJsonInt(text, "p2_rating"),
								p2_wins = ExtractJsonInt(text, "p2_wins"),
								p2_odds = ExtractJsonFloat(text, "p2_odds"),
								live_p1_points = ExtractJsonInt(text, "live_p1_points"),
								live_p2_points = ExtractJsonInt(text, "live_p2_points"),
								bets_locked = (text.Contains("\"bets_locked\":true") || text.Contains("\"bets_locked\": true")),
								lock_reason = ExtractJsonString(text, "lock_reason"),
								is_private = (text.Contains("\"is_private\":true") || text.Contains("\"is_private\": true")),
								is_tournament = (text.Contains("\"is_tournament\":true") || text.Contains("\"is_tournament\": true")),
								tournament_kind = ExtractJsonString(text, "tournament_kind"),
								phase = ExtractJsonString(text, "phase")
							};
							if (!string.IsNullOrEmpty(activeSeriesEntry.series_id))
							{
								list.Add(activeSeriesEntry);
							}
						}
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("[BET] active-series parse: " + ex.Message));
					}
					CachedActiveSeries = list;
					NativeUI.MarkDirty();
				}
			}));
		}

		public static void FetchMyBets(string steamId)
		{
			if (string.IsNullOrEmpty(steamId) || steamId == "unknown")
			{
				return;
			}
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(baseUrl + "/api/v1/players/" + steamId + "/bets?limit=50", delegate(bool ok, string resp)
			{
				if (ok)
				{
					List<MyBetEntry> list = new List<MyBetEntry>();
					try
					{
						string[] array = resp.Split(new string[1] { "\"id\":" }, StringSplitOptions.None);
						for (int i = 1; i < array.Length; i++)
						{
							string json = array[i];
							MyBetEntry myBetEntry = new MyBetEntry
							{
								amount = ExtractJsonInt(json, "amount"),
								odds_multiplier = ExtractJsonFloat(json, "odds_multiplier"),
								series_id = ExtractJsonString(json, "series_id"),
								bet_on_steam_id = ExtractJsonString(json, "bet_on_steam_id"),
								bet_on_name = ExtractJsonString(json, "bet_on_name"),
								series_status = ExtractJsonString(json, "series_status"),
								series_score = ExtractJsonString(json, "series_score")
							};
							if (!string.IsNullOrEmpty(myBetEntry.series_id))
							{
								list.Add(myBetEntry);
							}
						}
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("[BET] my-bets parse: " + ex.Message));
					}
					CachedMyBets = list;
					NativeUI.MarkDirty();
				}
			}));
		}

		public static MyBetEntry GetMyBetForSeries(string seriesId)
		{
			if (string.IsNullOrEmpty(seriesId) || CachedMyBets == null)
			{
				return null;
			}
			foreach (MyBetEntry cachedMyBet in CachedMyBets)
			{
				if (cachedMyBet.series_id == seriesId)
				{
					return cachedMyBet;
				}
			}
			return null;
		}

		public static void PostLivePoints(string seriesId, string reporterSteamId, int p1Points, int p2Points)
		{
			if (!string.IsNullOrEmpty(seriesId) && !string.IsNullOrEmpty(reporterSteamId))
			{
				string text = ComputeHmacHex($"live-points:{seriesId}:{reporterSteamId}:{p1Points}:{p2Points}");
				string url = baseUrl + "/api/v1/series/" + Escape(seriesId) + "/live-points" + $"?p1_points={p1Points}&p2_points={p2Points}" + "&reporter_steam_id=" + Escape(reporterSteamId) + "&sig=" + text;
				((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(url, "", delegate(bool ok, string resp)
				{
					Plugin.Log.LogInfo((object)$"[LIVE-POINTS] series={seriesId.Substring(0, Math.Min(8, seriesId.Length))} {p1Points}-{p2Points} ok={ok}");
				}));
			}
		}

		public static void PlaceBet(string bettorSteamId, string seriesId, string betOnSteamId, int amount, Action<bool, string> callback)
		{
			string text = ComputeHmacHex($"bet:{bettorSteamId}:{seriesId}:{betOnSteamId}:{amount}");
			string url = $"{baseUrl}/api/v1/bets?steam_id={Escape(bettorSteamId)}&series_id={Escape(seriesId)}&bet_on_steam_id={Escape(betOnSteamId)}&amount={amount}&sig={text}";
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(url, "", delegate(bool ok, string resp)
			{
				Plugin.Log.LogInfo((object)$"[BET] place {amount} on {betOnSteamId}: ok={ok} resp={resp}");
				callback?.Invoke(ok, resp);
				if (ok)
				{
					FetchPlayerStats(bettorSteamId);
					FetchActiveSeries();
				}
			}));
		}

		public static void FetchActiveTeamSeries()
		{
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(baseUrl + "/api/v1/team/series/active", delegate(bool success, string response)
			{
				if (!success)
				{
					return;
				}
				try
				{
					ParseActiveTeamSeries(response);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("[TEAM-BET] active parse: " + ex.Message));
				}
			}));
		}

		private static void ParseActiveTeamSeries(string response)
		{
			List<ActiveTeamSeriesEntry> list = new List<ActiveTeamSeriesEntry>();
			int num = response.IndexOf("\"series\"");
			if (num < 0)
			{
				CachedActiveTeamSeries = list;
				return;
			}
			int num2 = response.IndexOf('[', num);
			int num3 = FindMatchingBracket(response, num2);
			if (num2 < 0 || num3 < 0)
			{
				CachedActiveTeamSeries = list;
				return;
			}
			string text = response.Substring(num2 + 1, num3 - num2 - 1);
			int num4 = 0;
			while (num4 < text.Length)
			{
				int num5 = text.IndexOf('{', num4);
				if (num5 < 0)
				{
					break;
				}
				int num6 = 1;
				int i;
				for (i = num5 + 1; i < text.Length; i++)
				{
					if (num6 <= 0)
					{
						break;
					}
					if (text[i] == '{')
					{
						num6++;
					}
					else if (text[i] == '}')
					{
						num6--;
					}
				}
				if (num6 != 0)
				{
					break;
				}
				string json = text.Substring(num5, i - num5);
				list.Add(new ActiveTeamSeriesEntry
				{
					series_id = ExtractJsonString(json, "series_id"),
					t1a_steam = ExtractJsonString(json, "t1a_steam"),
					t1a_name = ExtractJsonString(json, "t1a_name"),
					t1b_steam = ExtractJsonString(json, "t1b_steam"),
					t1b_name = ExtractJsonString(json, "t1b_name"),
					t2a_steam = ExtractJsonString(json, "t2a_steam"),
					t2a_name = ExtractJsonString(json, "t2a_name"),
					t2b_steam = ExtractJsonString(json, "t2b_steam"),
					t2b_name = ExtractJsonString(json, "t2b_name"),
					t1_rating = ExtractJsonInt(json, "t1_rating"),
					t2_rating = ExtractJsonInt(json, "t2_rating"),
					t1a_rating = ExtractJsonInt(json, "t1a_rating"),
					t1b_rating = ExtractJsonInt(json, "t1b_rating"),
					t2a_rating = ExtractJsonInt(json, "t2a_rating"),
					t2b_rating = ExtractJsonInt(json, "t2b_rating"),
					t1_wins = ExtractJsonInt(json, "t1_wins"),
					t2_wins = ExtractJsonInt(json, "t2_wins"),
					t1_odds = ExtractJsonFloat(json, "t1_odds"),
					t2_odds = ExtractJsonFloat(json, "t2_odds"),
					bets_locked = ExtractJsonBool(json, "bets_locked"),
					lock_reason = ExtractJsonString(json, "lock_reason"),
					started_at = ExtractJsonString(json, "started_at"),
					dc_grace_until = ExtractJsonString(json, "dc_grace_until")
				});
				num4 = i;
			}
			CachedActiveTeamSeries = list;
		}

		public static void PlaceTeamBet(string bettorSteamId, string seriesId, int betOnTeam, int amount, Action<bool, string> callback)
		{
			string text = ComputeHmacHex($"team-bet:{bettorSteamId}:{seriesId}:{betOnTeam}:{amount}");
			string url = $"{baseUrl}/api/v1/team-bets?steam_id={Escape(bettorSteamId)}&team_series_id={Escape(seriesId)}&bet_on_team={betOnTeam}&amount={amount}&sig={text}";
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(url, "", delegate(bool ok, string resp)
			{
				Plugin.Log.LogInfo((object)$"[TEAM-BET] place {amount} on team {betOnTeam}: ok={ok} resp={resp}");
				callback?.Invoke(ok, resp);
				if (ok)
				{
					FetchPlayerStats(bettorSteamId);
					FetchActiveTeamSeries();
				}
			}));
		}

		public static void FetchShopItems(string steamId = null)
		{
			string text = baseUrl + "/api/v1/shop/items";
			if (!string.IsNullOrEmpty(steamId))
			{
				text = text + "?steam_id=" + Escape(steamId);
			}
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(text, delegate(bool success, string response)
			{
				if (!success)
				{
					Plugin.Log.LogWarning((object)("[SHOP] list failed: " + response));
				}
				else
				{
					CachedShopItems = ParseShopItems(response);
					Plugin.Log.LogInfo((object)$"[SHOP] loaded {CachedShopItems.Count} items");
					NativeUI.MarkDirty();
				}
			}));
		}

		public static void FetchInventory(string steamId)
		{
			if (string.IsNullOrEmpty(steamId))
			{
				return;
			}
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(baseUrl + "/api/v1/players/" + steamId + "/inventory", delegate(bool success, string response)
			{
				if (success)
				{
					CachedInventory = ParseShopItems(response);
					NativeUI.MarkDirty();
				}
			}));
		}

		public static void PurchaseItem(string steamId, string sku, Action<bool, string> callback)
		{
			Plugin.Log.LogInfo((object)("[SHOP] PurchaseItem ENTRY sku=" + sku + " steamId=" + steamId));
			try
			{
				string text = ComputeHmacHex("buy:" + steamId + ":" + sku);
				Plugin.Log.LogInfo((object)$"[SHOP] sig computed ({text?.Length ?? 0} chars)");
				string text2 = baseUrl + "/api/v1/shop/purchase?steam_id=" + steamId + "&sku=" + sku + "&sig=" + text;
				Plugin.Log.LogInfo((object)("[SHOP] POST " + text2.Substring(0, Math.Min(text2.Length, 100)) + "..."));
				((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(text2, "", delegate(bool ok, string resp)
				{
					Plugin.Log.LogInfo((object)string.Format("[SHOP] purchase callback {0}: ok={1} resp={2}", sku, ok, (resp != null && resp.Length > 120) ? (resp.Substring(0, 120) + "...") : resp));
					try
					{
						callback?.Invoke(ok, resp);
					}
					catch (Exception ex2)
					{
						Plugin.Log.LogWarning((object)("[SHOP] callback threw: " + ex2.Message));
					}
					if (ok)
					{
						FetchPlayerStats(steamId);
						FetchShopItems(steamId);
						FetchInventory(steamId);
					}
				}));
				Plugin.Log.LogInfo((object)"[SHOP] coroutine dispatched");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)$"[SHOP] PurchaseItem threw: {ex}");
				try
				{
					callback?.Invoke(arg1: false, ex.Message);
				}
				catch
				{
				}
			}
		}

		public static void SetActiveTitle(string steamId, long itemId, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex($"title:{steamId}:{itemId}");
			string text2 = baseUrl + "/api/v1/players/" + steamId + "/active-title?sig=" + text;
			if (itemId > 0)
			{
				text2 += $"&item_id={itemId}";
			}
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(text2, "", delegate(bool ok, string resp)
			{
				Plugin.Log.LogInfo((object)$"[SHOP] set title {itemId}: ok={ok} resp={resp}");
				callback?.Invoke(ok, resp);
				if (ok)
				{
					FetchPlayerStats(steamId);
				}
			}));
		}

		public static void SetActiveTrail(string steamId, long itemId, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex($"trail:{steamId}:{itemId}");
			string text2 = baseUrl + "/api/v1/players/" + steamId + "/active-trail?sig=" + text;
			if (itemId > 0)
			{
				text2 += $"&item_id={itemId}";
			}
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(text2, "", delegate(bool ok, string resp)
			{
				Plugin.Log.LogInfo((object)$"[SHOP] set trail {itemId}: ok={ok} resp={resp}");
				callback?.Invoke(ok, resp);
				if (ok)
				{
					FetchPlayerStats(steamId);
				}
			}));
		}

		public static void SetActiveColor(string steamId, long itemId, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex($"color:{steamId}:{itemId}");
			string text2 = baseUrl + "/api/v1/players/" + steamId + "/active-color?sig=" + text;
			if (itemId > 0)
			{
				text2 += $"&item_id={itemId}";
			}
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(text2, "", delegate(bool ok, string resp)
			{
				Plugin.Log.LogInfo((object)$"[SHOP] set color {itemId}: ok={ok} resp={resp}");
				callback?.Invoke(ok, resp);
				if (ok)
				{
					FetchPlayerStats(steamId);
				}
			}));
		}

		public static void SetActivePlayerColor(string steamId, long itemId, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex($"player_color:{steamId}:{itemId}");
			string text2 = baseUrl + "/api/v1/players/" + steamId + "/active-player-color?sig=" + text;
			if (itemId > 0)
			{
				text2 += $"&item_id={itemId}";
			}
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(text2, "", delegate(bool ok, string resp)
			{
				Plugin.Log.LogInfo((object)$"[SHOP] set player_color {itemId}: ok={ok} resp={resp}");
				callback?.Invoke(ok, resp);
				if (ok)
				{
					FetchPlayerStats(steamId);
				}
			}));
		}

		public static void SetActiveCursorColor(string steamId, long itemId, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex($"cursor_color:{steamId}:{itemId}");
			string text2 = baseUrl + "/api/v1/players/" + steamId + "/active-cursor-color?sig=" + text;
			if (itemId > 0)
			{
				text2 += $"&item_id={itemId}";
			}
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(text2, "", delegate(bool ok, string resp)
			{
				Plugin.Log.LogInfo((object)$"[SHOP] set cursor_color {itemId}: ok={ok} resp={resp}");
				callback?.Invoke(ok, resp);
				if (ok)
				{
					FetchPlayerStats(steamId);
				}
			}));
		}

		public static void SetActivePlayerEffect(string steamId, long itemId, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex($"player_effect:{steamId}:{itemId}");
			string text2 = baseUrl + "/api/v1/players/" + steamId + "/active-player-effect?sig=" + text;
			if (itemId > 0)
			{
				text2 += $"&item_id={itemId}";
			}
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(text2, "", delegate(bool ok, string resp)
			{
				Plugin.Log.LogInfo((object)$"[SHOP] set player_effect {itemId}: ok={ok} resp={resp}");
				callback?.Invoke(ok, resp);
				if (ok)
				{
					FetchPlayerStats(steamId);
				}
			}));
		}

		public static void SetHideGold(string steamId, bool on, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex($"hide_gold:{steamId}:{(on ? 1 : 0)}");
			string url = baseUrl + "/api/v1/players/" + steamId + "/hide-gold?on=" + (on ? "true" : "false") + "&sig=" + text;
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(url, "", delegate(bool ok, string resp)
			{
				Plugin.Log.LogInfo((object)$"[SHOP] set hide_gold {on}: ok={ok} resp={resp}");
				callback?.Invoke(ok, resp);
				if (ok)
				{
					FetchPlayerStats(steamId);
					FetchLeaderboard();
				}
			}));
		}

		public static void ToggleNametagStyle(string steamId, long itemId, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex($"nametag:{steamId}:{itemId}");
			string url = $"{baseUrl}/api/v1/players/{steamId}/nametag-toggle?item_id={itemId}&sig={text}";
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(url, "", delegate(bool ok, string resp)
			{
				Plugin.Log.LogInfo((object)$"[SHOP] toggle nametag {itemId}: ok={ok} resp={resp}");
				callback?.Invoke(ok, resp);
				if (ok)
				{
					FetchPlayerStats(steamId);
				}
			}));
		}

		public static void ToggleMapColor(string steamId, long itemId, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex($"color:{steamId}:{itemId}");
			string url = $"{baseUrl}/api/v1/players/{steamId}/color-toggle?item_id={itemId}&sig={text}";
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(url, "", delegate(bool ok, string resp)
			{
				Plugin.Log.LogInfo((object)$"[SHOP] toggle color {itemId}: ok={ok} resp={resp}");
				callback?.Invoke(ok, resp);
				if (ok)
				{
					FetchPlayerStats(steamId);
				}
			}));
		}

		private static List<ShopItemData> ParseShopItems(string response)
		{
			List<ShopItemData> list = new List<ShopItemData>();
			if (string.IsNullOrEmpty(response))
			{
				return list;
			}
			string[] array = response.Split(new string[1] { "{\"id\":" }, StringSplitOptions.None);
			for (int i = 1; i < array.Length; i++)
			{
				string json = "{\"id\":" + array[i];
				ShopItemData shopItemData = new ShopItemData();
				shopItemData.id = ExtractJsonInt(json, "id");
				shopItemData.sku = ExtractJsonString(json, "sku");
				shopItemData.kind = ExtractJsonString(json, "kind");
				shopItemData.name = ExtractJsonString(json, "name");
				shopItemData.description = ExtractJsonString(json, "description");
				shopItemData.price = ExtractJsonInt(json, "price");
				shopItemData.rarity = ExtractJsonString(json, "rarity");
				shopItemData.preview_color = ExtractJsonString(json, "preview_color");
				shopItemData.owned = ExtractJsonBool(json, "owned");
				if (!string.IsNullOrEmpty(shopItemData.sku) || !string.IsNullOrEmpty(shopItemData.name))
				{
					list.Add(shopItemData);
				}
			}
			return list;
		}

		public static void FetchRecentChat(int limit = 50)
		{
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest($"{baseUrl}/api/v1/chat/recent?limit={limit}", delegate(bool success, string response)
			{
				if (!success || string.IsNullOrEmpty(response))
				{
					return;
				}
				try
				{
					string[] array = response.Split(new string[1] { "{\"source\"" }, StringSplitOptions.None);
					for (int i = 1; i < array.Length; i++)
					{
						string text = "{\"source\"" + array[i];
						int num = text.IndexOf("\"}");
						if (num >= 0)
						{
							string obj = text.Substring(0, num + 2);
							try
							{
								ChatClient.OnMessage?.Invoke(obj);
							}
							catch
							{
							}
						}
					}
					Plugin.Log.LogInfo((object)$"[CHAT] Scrollback loaded: {array.Length - 1} messages");
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("[CHAT] Scrollback parse error: " + ex.Message));
				}
			}));
		}

		public static void DeletePlayerData(string steamId, Action<bool, string> callback)
		{
			if (string.IsNullOrEmpty(steamId))
			{
				callback(arg1: false, "no-steam-id");
				return;
			}
			string text = ComputeHmacHex("delete:" + steamId);
			string url = baseUrl + "/api/v1/players/" + steamId + "/data?sig=" + text;
			((MonoBehaviour)Plugin.Instance).StartCoroutine(DoDelete(url, callback));
		}

		private static IEnumerator DoDelete(string url, Action<bool, string> callback)
		{
			UnityWebRequest request = UnityWebRequest.Delete(url);
			try
			{
				request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
				StampVersionHeader(request);
				request.timeout = 20;
				yield return request.SendWebRequest();
				bool flag = (int)request.result == 1;
				object arg;
				if (!flag)
				{
					arg = request.error;
				}
				else
				{
					DownloadHandler downloadHandler = request.downloadHandler;
					arg = ((downloadHandler != null) ? downloadHandler.text : null) ?? "";
				}
				callback(flag, (string)arg);
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		public static void CheckAdminStatus(string steamId, Action<bool> callback = null)
		{
			if (string.IsNullOrEmpty(steamId))
			{
				IsAdmin = false;
				callback?.Invoke(obj: false);
				return;
			}
			string url = baseUrl + "/api/v1/admin/check-status?steam_id=" + steamId;
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(url, delegate(bool ok, string body)
			{
				bool flag = false;
				if (ok && !string.IsNullOrEmpty(body))
				{
					flag = body.Contains("\"is_admin\":true") || body.Contains("\"is_admin\": true");
				}
				IsAdmin = flag;
				Plugin.Log.LogInfo((object)$"[ADMIN] check-status for {steamId}: is_admin={flag}");
				callback?.Invoke(flag);
			}));
		}

		public static void FetchFlaggedMatches(string adminSteamId, bool includeReviewed = false, Action<bool> callback = null)
		{
			if (string.IsNullOrEmpty(adminSteamId))
			{
				callback?.Invoke(obj: false);
				return;
			}
			string text = ComputeHmacHex("admin:" + adminSteamId + ":list_flagged:");
			string url = baseUrl + "/api/v1/admin/flagged-matches?admin_steam_id=" + adminSteamId + "&hmac_signature=" + text + "&include_reviewed=" + (includeReviewed ? "true" : "false") + "&limit=50";
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(url, delegate(bool ok, string body)
			{
				if (!ok)
				{
					Plugin.Log.LogWarning((object)("[ADMIN] flagged fetch failed: " + body));
					callback?.Invoke(obj: false);
					return;
				}
				try
				{
					CachedFlaggedMatches = ParseFlaggedMatches(body);
					callback?.Invoke(obj: true);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("[ADMIN] flagged parse: " + ex.Message));
					callback?.Invoke(obj: false);
				}
			}));
		}

		public static void FetchBannedUsers(string adminSteamId, Action<bool> callback = null)
		{
			if (string.IsNullOrEmpty(adminSteamId))
			{
				callback?.Invoke(obj: false);
				return;
			}
			string text = ComputeHmacHex("admin:" + adminSteamId + ":list_bans:");
			string url = baseUrl + "/api/v1/admin/banned-users?admin_steam_id=" + adminSteamId + "&hmac_signature=" + text;
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(url, delegate(bool ok, string body)
			{
				if (!ok)
				{
					Plugin.Log.LogWarning((object)("[ADMIN] bans fetch failed: " + body));
					callback?.Invoke(obj: false);
					return;
				}
				try
				{
					CachedBannedUsers = ParseBannedUsers(body);
					callback?.Invoke(obj: true);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("[ADMIN] bans parse: " + ex.Message));
					callback?.Invoke(obj: false);
				}
			}));
		}

		public static void AdminBan(string adminSteamId, string targetSteamId, string reason, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex("admin:" + adminSteamId + ":ban:" + targetSteamId);
			string json = "{\"admin_steam_id\":\"" + Escape(adminSteamId) + "\",\"target_steam_id\":\"" + Escape(targetSteamId) + "\",\"reason\":\"" + Escape(reason) + "\",\"hmac_signature\":\"" + text + "\"}";
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequestWithRetry(baseUrl + "/api/v1/admin/ban", json, delegate(bool ok, string resp)
			{
				callback?.Invoke(ok, resp);
			}));
		}

		public static void AdminUnban(string adminSteamId, string targetSteamId, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex("admin:" + adminSteamId + ":unban:" + targetSteamId);
			string json = "{\"admin_steam_id\":\"" + Escape(adminSteamId) + "\",\"target_steam_id\":\"" + Escape(targetSteamId) + "\",\"hmac_signature\":\"" + text + "\"}";
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequestWithRetry(baseUrl + "/api/v1/admin/unban", json, delegate(bool ok, string resp)
			{
				callback?.Invoke(ok, resp);
			}));
		}

		public static void AdminGrantAchievement(string adminSteamId, string targetSteamId, string achievementKey, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex("admin:" + adminSteamId + ":grant_achievement:" + targetSteamId);
			string json = "{\"admin_steam_id\":\"" + Escape(adminSteamId) + "\",\"target_steam_id\":\"" + Escape(targetSteamId) + "\",\"achievement_key\":\"" + Escape(achievementKey) + "\",\"hmac_signature\":\"" + text + "\"}";
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequestWithRetry(baseUrl + "/api/v1/admin/grant-achievement", json, delegate(bool ok, string resp)
			{
				callback?.Invoke(ok, resp);
			}));
		}

		public static void AdminReverseSeries(string adminSteamId, string seriesId, string reason, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex("admin:" + adminSteamId + ":reverse_series:" + seriesId);
			string json = "{\"admin_steam_id\":\"" + Escape(adminSteamId) + "\",\"series_id\":\"" + Escape(seriesId) + "\",\"reason\":\"" + Escape(reason) + "\",\"hmac_signature\":\"" + text + "\"}";
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequestWithRetry(baseUrl + "/api/v1/admin/reverse-series", json, delegate(bool ok, string resp)
			{
				callback?.Invoke(ok, resp);
			}));
		}

		public static void AdminReviewFlag(string adminSteamId, string flagId, string action, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex("admin:" + adminSteamId + ":review_flag:" + flagId);
			string json = "{\"admin_steam_id\":\"" + Escape(adminSteamId) + "\",\"flag_id\":\"" + Escape(flagId) + "\",\"review_action\":\"" + Escape(action) + "\",\"hmac_signature\":\"" + text + "\"}";
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequestWithRetry(baseUrl + "/api/v1/admin/review-flag", json, delegate(bool ok, string resp)
			{
				callback?.Invoke(ok, resp);
			}));
		}

		public static void FetchBugReports(string adminSteamId, Action<bool> callback = null)
		{
			if (string.IsNullOrEmpty(adminSteamId))
			{
				callback?.Invoke(obj: false);
				return;
			}
			string text = ComputeHmacHex("admin:" + adminSteamId + ":bug_reports:list");
			string url = baseUrl + "/api/v1/bug-reports?admin_steam_id=" + adminSteamId + "&hmac_signature=" + text + "&limit=100";
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(url, delegate(bool ok, string body)
			{
				if (!ok)
				{
					Plugin.Log.LogWarning((object)("[BUG-REPORTS] fetch failed: " + body));
					callback?.Invoke(obj: false);
					return;
				}
				try
				{
					CachedBugReports = ParseBugReportList(body);
					callback?.Invoke(obj: true);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("[BUG-REPORTS] parse: " + ex.Message));
					callback?.Invoke(obj: false);
				}
			}));
		}

		public static void FetchBugReportDetail(string adminSteamId, string reportId, Action<bool> callback = null)
		{
			if (string.IsNullOrEmpty(adminSteamId) || string.IsNullOrEmpty(reportId))
			{
				callback?.Invoke(obj: false);
				return;
			}
			string text = ComputeHmacHex("admin:" + adminSteamId + ":bug_reports:" + reportId);
			string url = baseUrl + "/api/v1/bug-reports/" + reportId + "?admin_steam_id=" + adminSteamId + "&hmac_signature=" + text + "&include_log=true";
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(url, delegate(bool ok, string body)
			{
				if (!ok)
				{
					Plugin.Log.LogWarning((object)("[BUG-REPORTS] detail fetch failed: " + body));
					callback?.Invoke(obj: false);
					return;
				}
				try
				{
					CachedBugReportDetail = ParseBugReportDetail(body);
					callback?.Invoke(obj: true);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("[BUG-REPORTS] detail parse: " + ex.Message));
					callback?.Invoke(obj: false);
				}
			}));
		}

		private static List<BugReportSummary> ParseBugReportList(string json)
		{
			List<BugReportSummary> list = new List<BugReportSummary>();
			if (string.IsNullOrEmpty(json))
			{
				return list;
			}
			int num = json.IndexOf("\"reports\":[");
			if (num < 0)
			{
				return list;
			}
			int num2 = num + "\"reports\":[".Length;
			int num3 = 1;
			int i;
			for (i = num2; i < json.Length; i++)
			{
				if (num3 <= 0)
				{
					break;
				}
				if (json[i] == '[')
				{
					num3++;
				}
				else if (json[i] == ']')
				{
					num3--;
				}
			}
			if (num3 != 0)
			{
				return list;
			}
			string text = json.Substring(num2, i - num2 - 1);
			int num4 = 0;
			while (num4 < text.Length)
			{
				int num5 = text.IndexOf('{', num4);
				if (num5 < 0)
				{
					break;
				}
				int num6 = 1;
				int j;
				for (j = num5 + 1; j < text.Length; j++)
				{
					if (num6 <= 0)
					{
						break;
					}
					if (text[j] == '{')
					{
						num6++;
					}
					else if (text[j] == '}')
					{
						num6--;
					}
				}
				if (num6 != 0)
				{
					break;
				}
				string json2 = text.Substring(num5, j - num5);
				list.Add(new BugReportSummary
				{
					id = ExtractJsonString(json2, "id"),
					bug_number = ExtractJsonInt(json2, "bug_number"),
					created_at = ExtractJsonString(json2, "created_at"),
					steam_id = ExtractJsonString(json2, "steam_id"),
					display_name = ExtractJsonString(json2, "display_name"),
					mod_version = ExtractJsonString(json2, "mod_version"),
					severity = ExtractJsonString(json2, "severity"),
					category = ExtractJsonString(json2, "category"),
					status = ExtractJsonString(json2, "status"),
					description = ExtractJsonString(json2, "description"),
					has_log = ExtractJsonBool(json2, "has_log"),
					log_bytes = ExtractJsonInt(json2, "log_bytes")
				});
				num4 = j;
			}
			return list;
		}

		private static BugReportDetail ParseBugReportDetail(string json)
		{
			BugReportDetail bugReportDetail = new BugReportDetail
			{
				id = ExtractJsonString(json, "id"),
				bug_number = ExtractJsonInt(json, "bug_number"),
				steam_id = ExtractJsonString(json, "steam_id"),
				display_name = ExtractJsonString(json, "display_name"),
				mod_version = ExtractJsonString(json, "mod_version"),
				game_version = ExtractJsonString(json, "game_version"),
				severity = ExtractJsonString(json, "severity"),
				category = ExtractJsonString(json, "category"),
				description = ExtractJsonString(json, "description"),
				repro_steps = ExtractJsonString(json, "repro_steps"),
				status = ExtractJsonString(json, "status"),
				triage_notes = ExtractJsonString(json, "triage_notes"),
				created_at = ExtractJsonString(json, "created_at"),
				log_text = ExtractJsonString(json, "log_text"),
				log_bytes = ExtractJsonInt(json, "log_bytes")
			};
			try
			{
				int num = json.IndexOf("\"events\":[");
				if (num >= 0)
				{
					int num2 = num + "\"events\":[".Length;
					int num3 = 1;
					int i;
					for (i = num2; i < json.Length; i++)
					{
						if (num3 <= 0)
						{
							break;
						}
						if (json[i] == '[')
						{
							num3++;
						}
						else if (json[i] == ']')
						{
							num3--;
						}
					}
					if (num3 == 0)
					{
						string text = json.Substring(num2, i - num2 - 1);
						int num4 = 0;
						while (num4 < text.Length)
						{
							int num5 = text.IndexOf('{', num4);
							if (num5 < 0)
							{
								break;
							}
							int num6 = 1;
							int j;
							for (j = num5 + 1; j < text.Length; j++)
							{
								if (num6 <= 0)
								{
									break;
								}
								if (text[j] == '{')
								{
									num6++;
								}
								else if (text[j] == '}')
								{
									num6--;
								}
							}
							if (num6 == 0)
							{
								string json2 = text.Substring(num5, j - num5);
								bugReportDetail.events.Add(new BugReportEventEntry
								{
									id = ExtractJsonString(json2, "id"),
									actor_steam_id = ExtractJsonString(json2, "actor_steam_id"),
									actor_name = ExtractJsonString(json2, "actor_name"),
									event_type = ExtractJsonString(json2, "event_type"),
									old_status = ExtractJsonString(json2, "old_status"),
									new_status = ExtractJsonString(json2, "new_status"),
									comment = ExtractJsonString(json2, "comment"),
									created_at = ExtractJsonString(json2, "created_at")
								});
								num4 = j;
								continue;
							}
							break;
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[BUG-REPORTS] events parse: " + ex.Message));
			}
			return bugReportDetail;
		}

		public static void AdminBugReportComment(string adminSteamId, string reportId, string comment, Action<bool, string> callback = null)
		{
			if (string.IsNullOrEmpty(adminSteamId) || string.IsNullOrEmpty(reportId))
			{
				callback?.Invoke(arg1: false, "missing args");
				return;
			}
			string text = ComputeHmacHex("admin:" + adminSteamId + ":bug_reports_comment:" + reportId);
			string json = "{\"admin_steam_id\":\"" + Escape(adminSteamId) + "\",\"hmac_signature\":\"" + text + "\",\"comment\":\"" + Escape(comment ?? "") + "\"}";
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(baseUrl + "/api/v1/bug-reports/" + reportId + "/comment", json, delegate(bool ok, string resp)
			{
				callback?.Invoke(ok, resp);
			}));
		}

		public static void AdminBugReportStatus(string adminSteamId, string reportId, string newStatus, string comment, Action<bool, string> callback = null)
		{
			if (string.IsNullOrEmpty(adminSteamId) || string.IsNullOrEmpty(reportId) || string.IsNullOrEmpty(newStatus))
			{
				callback?.Invoke(arg1: false, "missing args");
				return;
			}
			string text = ComputeHmacHex("admin:" + adminSteamId + ":bug_reports_status:" + reportId);
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("{");
			stringBuilder.Append("\"admin_steam_id\":\"" + Escape(adminSteamId) + "\"");
			stringBuilder.Append(",\"hmac_signature\":\"" + text + "\"");
			stringBuilder.Append(",\"new_status\":\"" + Escape(newStatus) + "\"");
			if (!string.IsNullOrEmpty(comment))
			{
				stringBuilder.Append(",\"comment\":\"" + Escape(comment) + "\"");
			}
			stringBuilder.Append("}");
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(baseUrl + "/api/v1/bug-reports/" + reportId + "/status", stringBuilder.ToString(), delegate(bool ok, string resp)
			{
				callback?.Invoke(ok, resp);
			}));
		}

		private static List<FlaggedMatchEntry> ParseFlaggedMatches(string json)
		{
			List<FlaggedMatchEntry> list = new List<FlaggedMatchEntry>();
			if (string.IsNullOrEmpty(json))
			{
				return list;
			}
			string[] array = json.Split(new string[1] { "\"id\":\"" }, StringSplitOptions.None);
			for (int i = 1; i < array.Length; i++)
			{
				string text = array[i];
				int num = text.IndexOf('"');
				if (num >= 0)
				{
					FlaggedMatchEntry flaggedMatchEntry = new FlaggedMatchEntry();
					flaggedMatchEntry.id = text.Substring(0, num);
					flaggedMatchEntry.match_id = ExtractJsonString(text, "match_id");
					flaggedMatchEntry.series_id = ExtractJsonString(text, "series_id");
					flaggedMatchEntry.flag_reason = ExtractJsonString(text, "flag_reason");
					flaggedMatchEntry.p1_name = ExtractJsonString(text, "p1_name");
					flaggedMatchEntry.p2_name = ExtractJsonString(text, "p2_name");
					flaggedMatchEntry.auto_invalidated = text.Contains("\"auto_invalidated\":true") || text.Contains("\"auto_invalidated\": true");
					flaggedMatchEntry.match_invalidated = text.Contains("\"match_invalidated\":true") || text.Contains("\"match_invalidated\": true");
					flaggedMatchEntry.is_ranked = text.Contains("\"is_ranked\":true") || text.Contains("\"is_ranked\": true");
					flaggedMatchEntry.duration_seconds = ExtractJsonInt(text, "duration_seconds");
					flaggedMatchEntry.review_action = ExtractJsonString(text, "review_action");
					flaggedMatchEntry.created_at = ExtractJsonString(text, "created_at");
					flaggedMatchEntry.flag_details_summary = string.Format("{0}  {1}  {2}s  {3}", flaggedMatchEntry.flag_reason, flaggedMatchEntry.is_ranked ? "R" : "C", flaggedMatchEntry.duration_seconds, flaggedMatchEntry.auto_invalidated ? "[auto-inv]" : "[advisory]");
					list.Add(flaggedMatchEntry);
				}
			}
			return list;
		}

		private static List<BannedUserEntry> ParseBannedUsers(string json)
		{
			List<BannedUserEntry> list = new List<BannedUserEntry>();
			if (string.IsNullOrEmpty(json))
			{
				return list;
			}
			string[] array = json.Split(new string[1] { "\"id\":\"" }, StringSplitOptions.None);
			for (int i = 1; i < array.Length; i++)
			{
				string text = array[i];
				int num = text.IndexOf('"');
				if (num >= 0)
				{
					BannedUserEntry bannedUserEntry = new BannedUserEntry();
					bannedUserEntry.id = text.Substring(0, num);
					bannedUserEntry.steam_id = ExtractJsonString(text, "steam_id");
					bannedUserEntry.display_name = ExtractJsonString(text, "display_name");
					bannedUserEntry.reason = ExtractJsonString(text, "reason");
					bannedUserEntry.banned_by_steam_id = ExtractJsonString(text, "banned_by_steam_id");
					bannedUserEntry.banned_at = ExtractJsonString(text, "banned_at");
					list.Add(bannedUserEntry);
				}
			}
			return list;
		}

		public static void CheckOpponentRanked(string steamId, Action<bool> callback)
		{
			if (string.IsNullOrEmpty(steamId) || steamId.StartsWith("photon_") || steamId == "unknown")
			{
				callback(obj: false);
				return;
			}
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(baseUrl + "/api/v1/mod/check/" + steamId, delegate(bool success, string response)
			{
				//IL_006b: Unknown result type (might be due to invalid IL or missing references)
				if (success)
				{
					try
					{
						ModCheckResponse modCheckResponse = JsonUtility.FromJson<ModCheckResponse>(response);
						if (modCheckResponse.banned)
						{
							Plugin.Log.LogWarning((object)("[BAN] Opponent " + steamId + " is mod-banned (" + modCheckResponse.ban_reason + ") — leaving match"));
							CompetitiveUI.ShowNotification("Opponent is banned from the mod for cheating - leaving match.", new Color(1f, 0.3f, 0.3f), 8f);
							try
							{
								if (PhotonNetwork.InRoom)
								{
									PhotonNetwork.LeaveRoom(true);
								}
							}
							catch (Exception ex)
							{
								Plugin.Log.LogWarning((object)("[BAN] LeaveRoom failed: " + ex.Message));
							}
							callback(obj: false);
						}
						else
						{
							bool obj = modCheckResponse.registered && modCheckResponse.ranked;
							Plugin.Log.LogInfo((object)$"Opponent ranked check: registered={modCheckResponse.registered}, ranked={modCheckResponse.ranked}");
							callback(obj);
						}
						return;
					}
					catch
					{
						callback(obj: false);
						return;
					}
				}
				Plugin.Log.LogWarning((object)("Opponent rank check failed: " + response));
				callback(obj: false);
			}));
		}

		public static void ReportMatch(string p1SteamId, string p1Name, string p2SteamId, string p2Name, int p1RoundsWon, int p2RoundsWon, int p1PointsTotal, int p2PointsTotal, List<MatchTracker.CardPickData> p1Cards, List<MatchTracker.CardPickData> p2Cards, List<MatchTracker.CardOfferData> p1Offers, List<MatchTracker.CardOfferData> p2Offers, string photonRoomId, string region, int durationSeconds, DateTime startedAt, string reporterSteamId, bool isRanked, int localShotsFired = 0, int localBlocksRaised = 0, int localBulletsFired = 0, int localBulletsHit = 0, int localBlocksActivated = 0, int localBlocksSuccessful = 0, int localAvgFps = 0, int opponentAvgFps = 0)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("{");
			stringBuilder.Append("\"player1\":{\"steam_id\":\"" + Escape(p1SteamId) + "\",\"display_name\":\"" + Escape(p1Name) + "\",\"cards\":[");
			AppendCards(stringBuilder, p1Cards);
			stringBuilder.Append("],\"card_offers\":[");
			AppendOffers(stringBuilder, p1Offers);
			stringBuilder.Append("]},");
			stringBuilder.Append("\"player2\":{\"steam_id\":\"" + Escape(p2SteamId) + "\",\"display_name\":\"" + Escape(p2Name) + "\",\"cards\":[");
			AppendCards(stringBuilder, p2Cards);
			stringBuilder.Append("],\"card_offers\":[");
			AppendOffers(stringBuilder, p2Offers);
			stringBuilder.Append("]},");
			stringBuilder.Append($"\"p1_rounds_won\":{p1RoundsWon},");
			stringBuilder.Append($"\"p2_rounds_won\":{p2RoundsWon},");
			stringBuilder.Append($"\"p1_points_total\":{p1PointsTotal},");
			stringBuilder.Append($"\"p2_points_total\":{p2PointsTotal},");
			stringBuilder.Append("\"photon_room_id\":\"" + Escape(photonRoomId) + "\",");
			stringBuilder.Append("\"game_version\":\"v" + Application.version + "\",");
			stringBuilder.Append("\"region\":\"" + Escape(region) + "\",");
			stringBuilder.Append($"\"match_duration\":{durationSeconds},");
			stringBuilder.Append($"\"started_at\":\"{startedAt:yyyy-MM-ddTHH:mm:ssZ}\",");
			stringBuilder.Append("\"is_ranked\":" + (isRanked ? "true" : "false") + ",");
			stringBuilder.Append("\"reported_by_steam_id\":\"" + Escape(reporterSteamId) + "\",");
			stringBuilder.Append($"\"local_shots_fired\":{localShotsFired},");
			stringBuilder.Append($"\"local_blocks_raised\":{localBlocksRaised},");
			stringBuilder.Append($"\"local_bullets_fired\":{localBulletsFired},");
			stringBuilder.Append($"\"local_bullets_hit\":{localBulletsHit},");
			stringBuilder.Append($"\"local_blocks_activated\":{localBlocksActivated},");
			stringBuilder.Append($"\"local_blocks_successful\":{localBlocksSuccessful},");
			stringBuilder.Append($"\"local_avg_fps\":{localAvgFps},");
			stringBuilder.Append($"\"opponent_avg_fps\":{opponentAvgFps},");
			string text = ComputeHmac(p1SteamId, p2SteamId, p1RoundsWon, p2RoundsWon, isRanked, reporterSteamId, photonRoomId);
			stringBuilder.Append("\"hmac_signature\":\"" + text + "\"");
			stringBuilder.Append("}");
			string json = stringBuilder.ToString();
			string text2 = (isRanked ? "RANKED" : "CASUAL");
			Plugin.Log.LogInfo((object)("Reporting " + text2 + " match to API..."));
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequestWithRetry(baseUrl + "/api/v1/matches", json, delegate(bool success, string response)
			{
				//IL_03a2: Unknown result type (might be due to invalid IL or missing references)
				//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
				//IL_0248: Unknown result type (might be due to invalid IL or missing references)
				//IL_0212: Unknown result type (might be due to invalid IL or missing references)
				//IL_02cb: Unknown result type (might be due to invalid IL or missing references)
				//IL_030c: Unknown result type (might be due to invalid IL or missing references)
				if (success)
				{
					Plugin.Log.LogInfo((object)("Match reported successfully: " + response));
					int num = ExtractJsonInt(response, "xp_gained");
					int num2 = ExtractJsonInt(response, "level");
					ExtractJsonInt(response, "total_xp");
					int lastKnownLevel = CompetitiveUI.LastKnownLevel;
					string text3 = $"+{num} XP";
					List<string> list = new List<string>();
					if (response.Contains("Win x"))
					{
						list.Add("Win x1.5");
					}
					if (response.Contains("Ranked x"))
					{
						list.Add("Ranked x1.2");
					}
					if (response.Contains("Sweep"))
					{
						list.Add("Sweep +100");
					}
					if (response.Contains("Top 5"))
					{
						list.Add("Top 5 +150");
					}
					if (list.Count > 0)
					{
						text3 = text3 + "  [" + string.Join(", ", list.ToArray()) + "]";
					}
					CompetitiveUI.ShowNotification(text3, new Color(0.4f, 0.8f, 1f), 4f);
					int num3 = ExtractJsonInt(response, "gold_gained");
					if (num3 > 0)
					{
						List<string> list2 = new List<string>();
						int num4 = response.IndexOf("\"gold_bonuses\":");
						if (num4 >= 0)
						{
							int num5 = response.IndexOf('[', num4);
							int num6 = ((num5 >= 0) ? response.IndexOf(']', num5) : (-1));
							if (num5 >= 0 && num6 > num5)
							{
								string[] array = response.Substring(num5 + 1, num6 - num5 - 1).Split(',');
								for (int i = 0; i < array.Length; i++)
								{
									string text4 = array[i].Trim().Trim('"').Trim();
									if (!string.IsNullOrEmpty(text4))
									{
										list2.Add(text4);
									}
								}
							}
						}
						string text5 = $"+{num3} gold";
						if (list2.Count > 0)
						{
							text5 = text5 + "  [" + string.Join(", ", list2.ToArray()) + "]";
						}
						CompetitiveUI.QueueNotification(text5, new Color(1f, 0.85f, 0.3f), 4f);
					}
					if (num2 > lastKnownLevel && lastKnownLevel >= 0)
					{
						CompetitiveUI.QueueNotification($"LEVEL UP!  Level {num2}", new Color(1f, 0.85f, 0.3f), 4f);
					}
					CompetitiveUI.LastKnownLevel = num2;
					FetchPlayerStats(MatchTracker.LocalSteamId);
					FetchMatchHistory(MatchTracker.LocalSteamId);
					if (isRanked)
					{
						string text6 = ExtractJsonString(response, "series_status");
						string text7 = ExtractJsonString(response, "series_score");
						ActiveRankedSeriesId = null;
						if (text6 == "active")
						{
							CompetitiveUI.QueueNotification("Series: " + text7, new Color(1f, 0.85f, 0.3f));
						}
						else if (text6 == "completed")
						{
							CompetitiveUI.QueueNotification("SERIES COMPLETE " + text7 + "!", new Color(0.3f, 1f, 0.3f), 4f);
							try
							{
								string localSteamId = MatchTracker.LocalSteamId;
								GameStateWatcher.IncrementSessionRankedSeries((!string.IsNullOrEmpty(localSteamId) && localSteamId == p1SteamId) ? (p1RoundsWon > p2RoundsWon) : (p2RoundsWon > p1RoundsWon));
							}
							catch (Exception ex)
							{
								Plugin.Log.LogWarning((object)("[SESSION] series tally update failed: " + ex.Message));
							}
							GameStateWatcher.pendingRegicideCheck = false;
						}
					}
				}
				else
				{
					Plugin.Log.LogError((object)("Failed to report match: " + response));
					CompetitiveUI.ShowNotification("Failed to report match", Color.red);
				}
			}));
		}

		private static void AppendCards(StringBuilder sb, List<MatchTracker.CardPickData> cards)
		{
			for (int i = 0; i < cards.Count; i++)
			{
				if (i > 0)
				{
					sb.Append(",");
				}
				MatchTracker.CardPickData cardPickData = cards[i];
				sb.Append("{");
				sb.Append("\"card_name\":\"" + Escape(cardPickData.CardName) + "\",");
				sb.Append("\"card_rarity\":\"" + Escape(cardPickData.CardRarity ?? "Unknown") + "\",");
				sb.Append($"\"pick_order\":{cardPickData.PickOrder},");
				sb.Append($"\"round_number\":{cardPickData.RoundNumber}");
				sb.Append("}");
			}
		}

		private static void AppendOffers(StringBuilder sb, List<MatchTracker.CardOfferData> offers)
		{
			if (offers == null)
			{
				return;
			}
			for (int i = 0; i < offers.Count; i++)
			{
				if (i > 0)
				{
					sb.Append(",");
				}
				MatchTracker.CardOfferData cardOfferData = offers[i];
				sb.Append("{");
				sb.Append("\"card_name\":\"" + Escape(cardOfferData.CardName) + "\",");
				sb.Append($"\"round_number\":{cardOfferData.RoundNumber},");
				sb.Append("\"was_picked\":" + (cardOfferData.WasPicked ? "true" : "false"));
				sb.Append("}");
			}
		}

		public static void FetchLeaderboard(int limit = 100, int minMatches = 1)
		{
			IsLoading = true;
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest($"{baseUrl}/api/v1/leaderboard?limit={limit}&min_matches={minMatches}", delegate(bool success, string response)
			{
				IsLoading = false;
				if (success)
				{
					try
					{
						LeaderboardData leaderboardData = new LeaderboardData
						{
							total_players = ExtractJsonInt(response, "total_players")
						};
						List<LeaderboardEntry> list = new List<LeaderboardEntry>();
						string[] array = response.Split(new string[1] { "\"rank\"" }, StringSplitOptions.None);
						for (int i = 1; i < array.Length; i++)
						{
							string text = array[i];
							LeaderboardEntry leaderboardEntry = new LeaderboardEntry();
							try
							{
								int num = text.IndexOf(':');
								if (num < 0)
								{
									num = -1;
								}
								int num2 = text.IndexOf(',', num + 1);
								if (num >= 0 && num2 > num)
								{
									string s = text.Substring(num + 1, num2 - num - 1).Trim();
									leaderboardEntry.rank = int.Parse(s);
								}
							}
							catch
							{
								leaderboardEntry.rank = i;
							}
							leaderboardEntry.steam_id = ExtractJsonString(text, "steam_id");
							leaderboardEntry.display_name = ExtractJsonString(text, "display_name");
							leaderboardEntry.rating = ExtractJsonInt(text, "rating");
							leaderboardEntry.rd = ExtractJsonInt(text, "rd");
							leaderboardEntry.total_matches = ExtractJsonInt(text, "total_matches");
							leaderboardEntry.wins = ExtractJsonInt(text, "wins");
							leaderboardEntry.losses = ExtractJsonInt(text, "losses");
							try
							{
								int num3 = text.IndexOf("\"win_rate\":");
								if (num3 >= 0)
								{
									for (num3 += "\"win_rate\":".Length; num3 < text.Length && text[num3] == ' '; num3++)
									{
									}
									int j;
									for (j = num3; j < text.Length && (char.IsDigit(text[j]) || text[j] == '.' || text[j] == '-'); j++)
									{
									}
									if (j > num3)
									{
										leaderboardEntry.win_rate = float.Parse(text.Substring(num3, j - num3), CultureInfo.InvariantCulture);
									}
								}
							}
							catch
							{
								leaderboardEntry.win_rate = 0f;
							}
							leaderboardEntry.level = ExtractJsonInt(text, "level");
							leaderboardEntry.gold = ExtractJsonInt(text, "gold");
							leaderboardEntry.title = ExtractJsonString(text, "title");
							leaderboardEntry.title_color = ExtractJsonString(text, "title_color");
							if (!string.IsNullOrEmpty(leaderboardEntry.steam_id))
							{
								list.Add(leaderboardEntry);
							}
						}
						leaderboardData.entries = list.ToArray();
						CachedLeaderboard = leaderboardData;
						ManualLogSource log = Plugin.Log;
						LeaderboardEntry[] entries = CachedLeaderboard.entries;
						log.LogInfo((object)$"Leaderboard loaded: {((entries != null) ? entries.Length : 0)} entries");
						return;
					}
					catch (Exception ex)
					{
						Plugin.Log.LogError((object)("Failed to parse leaderboard: " + ex.Message));
						LastError = "Failed to parse leaderboard data";
						return;
					}
				}
				LastError = "Failed to fetch leaderboard: " + response;
				Plugin.Log.LogError((object)LastError);
			}));
		}

		public static void FetchRecentSeries(int limit = 100)
		{
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest($"{baseUrl}/api/v1/series/recent?minutes=10080&limit={limit}", delegate(bool success, string response)
			{
				if (success)
				{
					try
					{
						List<RecentSeriesEntry> list = new List<RecentSeriesEntry>();
						string[] array = response.Split(new string[1] { "\"series_id\"" }, StringSplitOptions.None);
						for (int i = 1; i < array.Length; i++)
						{
							if (list.Count >= limit)
							{
								break;
							}
							RecentSeriesEntry recentSeriesEntry = new RecentSeriesEntry
							{
								winner_name = ExtractJsonString(array[i], "winner_name"),
								p1_name = ExtractJsonString(array[i], "p1_name"),
								p2_name = ExtractJsonString(array[i], "p2_name"),
								p1_wins = ExtractJsonInt(array[i], "p1_series_wins"),
								p2_wins = ExtractJsonInt(array[i], "p2_series_wins"),
								winner_steam_id = ExtractJsonString(array[i], "winner_steam_id"),
								p1_steam_id = ExtractJsonString(array[i], "p1_steam_id"),
								p2_steam_id = ExtractJsonString(array[i], "p2_steam_id")
							};
							try
							{
								int num = array[i].IndexOf("\"p1_rating_change\":");
								if (num >= 0)
								{
									num += "\"p1_rating_change\":".Length;
									int j;
									for (j = num; j < array[i].Length && (char.IsDigit(array[i][j]) || array[i][j] == '.' || array[i][j] == '-'); j++)
									{
									}
									if (j > num)
									{
										recentSeriesEntry.p1_rating_change = float.Parse(array[i].Substring(num, j - num), CultureInfo.InvariantCulture);
									}
								}
							}
							catch
							{
							}
							try
							{
								int num2 = array[i].IndexOf("\"p2_rating_change\":");
								if (num2 >= 0)
								{
									num2 += "\"p2_rating_change\":".Length;
									int k;
									for (k = num2; k < array[i].Length && (char.IsDigit(array[i][k]) || array[i][k] == '.' || array[i][k] == '-'); k++)
									{
									}
									if (k > num2)
									{
										recentSeriesEntry.p2_rating_change = float.Parse(array[i].Substring(num2, k - num2), CultureInfo.InvariantCulture);
									}
								}
							}
							catch
							{
							}
							recentSeriesEntry.p1_rating = ExtractJsonInt(array[i], "p1_rating");
							recentSeriesEntry.p2_rating = ExtractJsonInt(array[i], "p2_rating");
							int num3 = array[i].IndexOf("\"bets\":");
							if (num3 >= 0)
							{
								int num4 = array[i].IndexOf('[', num3);
								int num5 = array[i].IndexOf(']', num4);
								if (num4 >= 0 && num5 > num4)
								{
									string[] array2 = array[i].Substring(num4, num5 - num4 + 1).Split(new string[1] { "\"bettor_name\":" }, StringSplitOptions.None);
									for (int l = 1; l < array2.Length; l++)
									{
										string text = "\"bettor_name\":" + array2[l];
										SeriesBetEntry seriesBetEntry = new SeriesBetEntry
										{
											bettor_name = ExtractJsonString(text, "bettor_name"),
											bettor_steam_id = ExtractJsonString(text, "bettor_steam_id"),
											bet_on_name = ExtractJsonString(text, "bet_on_name"),
											bet_on_steam_id = ExtractJsonString(text, "bet_on_steam_id"),
											amount = ExtractJsonInt(text, "amount"),
											payout = ExtractJsonInt(text, "payout"),
											odds_multiplier = ExtractJsonFloat(text, "odds_multiplier"),
											won = (text.Contains("\"won\":true") || text.Contains("\"won\": true"))
										};
										if (!string.IsNullOrEmpty(seriesBetEntry.bettor_name))
										{
											recentSeriesEntry.bets.Add(seriesBetEntry);
										}
									}
								}
							}
							if (!string.IsNullOrEmpty(recentSeriesEntry.p1_name))
							{
								list.Add(recentSeriesEntry);
							}
						}
						CachedRecentSeries = list;
						Plugin.Log.LogInfo((object)$"Recent series loaded: {list.Count}");
						NativeUI.MarkDirty();
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("Recent series parse error: " + ex.Message));
					}
				}
			}));
		}

		public static void FetchPlayerStats(string steamId, bool force = false)
		{
			if (string.IsNullOrEmpty(steamId) || steamId == "unknown" || (!force && IsLoading))
			{
				return;
			}
			IsLoading = true;
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(baseUrl + "/api/v1/players/" + steamId, delegate(bool success, string response)
			{
				IsLoading = false;
				if (success)
				{
					try
					{
						CachedPlayerStats = JsonUtility.FromJson<PlayerStatsData>(response);
						ParseTopCards(CachedPlayerStats, response);
						Plugin.Log.LogInfo((object)("Player stats loaded for " + CachedPlayerStats.display_name));
						try
						{
							TrailCosmetic.PublishLocalProps();
						}
						catch
						{
						}
						try
						{
							PlayerColorCosmetic.PublishLocalProps();
						}
						catch
						{
						}
						try
						{
							PlayerEffectCosmetic.PublishLocalProps();
						}
						catch
						{
						}
						try
						{
							CursorColorCosmetic.ApplyFromStats();
						}
						catch
						{
						}
						try
						{
							NametagStyler.PublishToPhoton();
							return;
						}
						catch
						{
							return;
						}
					}
					catch (Exception ex)
					{
						Plugin.Log.LogError((object)("Failed to parse player stats: " + ex.Message));
						return;
					}
				}
				if (!response.Contains("404"))
				{
					Plugin.Log.LogError((object)("Failed to fetch player stats: " + response));
				}
			}));
		}

		public static void FetchPlayerStatsForView(string steamId, Action<PlayerStatsData> callback)
		{
			if (string.IsNullOrEmpty(steamId))
			{
				callback(null);
				return;
			}
			string localSteamId = MatchTracker.LocalSteamId;
			string text = ((!string.IsNullOrEmpty(localSteamId) && localSteamId != "unknown" && localSteamId != steamId) ? ("?viewer_steam_id=" + Escape(localSteamId)) : "");
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(baseUrl + "/api/v1/players/" + steamId + text, delegate(bool success, string response)
			{
				if (success)
				{
					try
					{
						PlayerStatsData playerStatsData = JsonUtility.FromJson<PlayerStatsData>(response);
						ParseTopCards(playerStatsData, response);
						callback(playerStatsData);
						return;
					}
					catch
					{
						callback(null);
						return;
					}
				}
				callback(null);
			}));
		}

		private static void ParseTopCards(PlayerStatsData data, string response)
		{
			data.top_card_names = new List<string>();
			data.top_card_picks = new List<int>();
			data.top_card_win_rates = new List<float>();
			data.recent_form = new List<string>();
			data.worst_card_names = new List<string>();
			data.worst_card_picks = new List<int>();
			data.worst_card_win_rates = new List<float>();
			data.region_names = new List<string>();
			data.region_matches = new List<int>();
			try
			{
				int num = response.IndexOf("\"top_cards\"");
				if (num >= 0)
				{
					int num2 = response.IndexOf("[", num);
					int num3 = FindMatchingBracket(response, num2);
					if (num2 >= 0 && num3 >= 0)
					{
						string text = response.Substring(num2, num3 - num2 + 1);
						if (text != "[]")
						{
							string[] array = text.Split(new string[1] { "\"card_name\"" }, StringSplitOptions.None);
							for (int i = 1; i < array.Length && i <= 10; i++)
							{
								string text2 = ExtractJsonString(array[i], "");
								int item = ExtractJsonInt(array[i], "times_picked");
								float item2 = 0f;
								try
								{
									int num4 = array[i].IndexOf("\"win_rate\":");
									if (num4 >= 0)
									{
										for (num4 += "\"win_rate\":".Length; num4 < array[i].Length && array[i][num4] == ' '; num4++)
										{
										}
										int j;
										for (j = num4; j < array[i].Length && (char.IsDigit(array[i][j]) || array[i][j] == '.' || array[i][j] == '-'); j++)
										{
										}
										if (j > num4)
										{
											item2 = float.Parse(array[i].Substring(num4, j - num4), CultureInfo.InvariantCulture);
										}
									}
								}
								catch
								{
								}
								if (!string.IsNullOrEmpty(text2))
								{
									data.top_card_names.Add(text2);
									data.top_card_picks.Add(item);
									data.top_card_win_rates.Add(item2);
								}
							}
						}
					}
				}
			}
			catch
			{
			}
			try
			{
				int num5 = response.IndexOf("\"worst_cards\"");
				if (num5 >= 0)
				{
					int num6 = response.IndexOf("[", num5);
					int num7 = FindMatchingBracket(response, num6);
					if (num6 >= 0 && num7 >= 0)
					{
						string text3 = response.Substring(num6, num7 - num6 + 1);
						if (text3 != "[]")
						{
							string[] array2 = text3.Split(new string[1] { "\"card_name\"" }, StringSplitOptions.None);
							for (int k = 1; k < array2.Length && k <= 10; k++)
							{
								string text4 = ExtractJsonString(array2[k], "");
								int item3 = ExtractJsonInt(array2[k], "times_picked");
								float item4 = ExtractJsonFloat(array2[k], "win_rate");
								if (!string.IsNullOrEmpty(text4))
								{
									data.worst_card_names.Add(text4);
									data.worst_card_picks.Add(item3);
									data.worst_card_win_rates.Add(item4);
								}
							}
						}
					}
				}
			}
			catch
			{
			}
			try
			{
				int num8 = response.IndexOf("\"region_breakdown\"");
				if (num8 >= 0)
				{
					int num9 = response.IndexOf("[", num8);
					int num10 = FindMatchingBracket(response, num9);
					if (num9 >= 0 && num10 >= 0)
					{
						string text5 = response.Substring(num9, num10 - num9 + 1);
						if (text5 != "[]")
						{
							string[] array3 = text5.Split(new string[1] { "\"region\"" }, StringSplitOptions.None);
							for (int l = 1; l < array3.Length; l++)
							{
								string text6 = ExtractJsonString(array3[l], "");
								int item5 = ExtractJsonInt(array3[l], "matches");
								if (!string.IsNullOrEmpty(text6))
								{
									data.region_names.Add(text6);
									data.region_matches.Add(item5);
								}
							}
						}
					}
				}
			}
			catch
			{
			}
			try
			{
				int num11 = response.IndexOf("\"recent_form\"");
				if (num11 >= 0)
				{
					int num12 = response.IndexOf("[", num11);
					int num13 = FindMatchingBracket(response, num12);
					if (num12 >= 0 && num13 >= 0)
					{
						string text7 = response.Substring(num12, num13 - num12 + 1);
						if (text7 != "[]")
						{
							string[] array4 = text7.Split(new string[1] { "\"result\"" }, StringSplitOptions.None);
							for (int m = 1; m < array4.Length; m++)
							{
								string text8 = ExtractJsonString(array4[m], "");
								if (!string.IsNullOrEmpty(text8))
								{
									data.recent_form.Add(text8);
								}
							}
						}
					}
				}
			}
			catch
			{
			}
			data.active_color_skus = new List<string>();
			try
			{
				int num14 = response.IndexOf("\"active_color_skus\"");
				if (num14 >= 0)
				{
					int num15 = response.IndexOf("[", num14);
					int num16 = FindMatchingBracket(response, num15);
					if (num15 >= 0 && num16 >= 0 && num16 > num15)
					{
						string text9 = response.Substring(num15 + 1, num16 - num15 - 1);
						int num17 = 0;
						while (num17 < text9.Length)
						{
							int num18 = text9.IndexOf('"', num17);
							if (num18 >= 0)
							{
								int num19 = text9.IndexOf('"', num18 + 1);
								if (num19 >= 0)
								{
									string text10 = text9.Substring(num18 + 1, num19 - num18 - 1);
									if (!string.IsNullOrEmpty(text10))
									{
										data.active_color_skus.Add(text10);
									}
									num17 = num19 + 1;
									continue;
								}
								break;
							}
							break;
						}
					}
				}
			}
			catch
			{
			}
			data.active_nametag_skus = new List<string>();
			try
			{
				int num20 = response.IndexOf("\"active_nametag_skus\"");
				if (num20 >= 0)
				{
					int num21 = response.IndexOf("[", num20);
					int num22 = FindMatchingBracket(response, num21);
					if (num21 >= 0 && num22 >= 0 && num22 > num21)
					{
						string text11 = response.Substring(num21 + 1, num22 - num21 - 1);
						int num23 = 0;
						while (num23 < text11.Length)
						{
							int num24 = text11.IndexOf('"', num23);
							if (num24 >= 0)
							{
								int num25 = text11.IndexOf('"', num24 + 1);
								if (num25 >= 0)
								{
									string text12 = text11.Substring(num24 + 1, num25 - num24 - 1);
									if (!string.IsNullOrEmpty(text12))
									{
										data.active_nametag_skus.Add(text12);
									}
									num23 = num25 + 1;
									continue;
								}
								break;
							}
							break;
						}
					}
				}
			}
			catch
			{
			}
			data.rating_history = new List<float>();
			try
			{
				int num26 = response.IndexOf("\"recent_rating_history\"");
				if (num26 >= 0)
				{
					int num27 = response.IndexOf("[", num26);
					int num28 = FindMatchingBracket(response, num27);
					if (num27 >= 0 && num28 >= 0)
					{
						string text13 = response.Substring(num27, num28 - num27 + 1);
						if (text13 != "[]")
						{
							string[] array5 = text13.Split(new string[1] { "\"rating\"" }, StringSplitOptions.None);
							for (int n = 1; n < array5.Length; n++)
							{
								int num29 = array5[n].IndexOf(':');
								if (num29 >= 0)
								{
									int num30;
									for (num30 = num29 + 1; num30 < array5[n].Length && array5[n][num30] == ' '; num30++)
									{
									}
									int num31;
									for (num31 = num30; num31 < array5[n].Length && (char.IsDigit(array5[n][num31]) || array5[n][num31] == '.' || array5[n][num31] == '-'); num31++)
									{
									}
									if (num31 > num30)
									{
										float item6 = float.Parse(array5[n].Substring(num30, num31 - num30), CultureInfo.InvariantCulture);
										data.rating_history.Add(item6);
									}
								}
							}
						}
					}
				}
			}
			catch
			{
			}
			if (data.rating_history.Count > 0 && data.rating_history[0] != 1500f)
			{
				data.rating_history.Insert(0, 1500f);
			}
			Plugin.Log.LogInfo((object)$"[STATS] Parsed {data.rating_history.Count} rating history points for {data.display_name} (oldest→newest, 1500 baseline prepended)");
		}

		public static void FetchAchievementsForView(string steamId)
		{
			if (string.IsNullOrEmpty(steamId) || steamId == "unknown" || (steamId == selectedAchSteamId && SelectedPlayerAchievements != null))
			{
				return;
			}
			selectedAchSteamId = steamId;
			SelectedPlayerAchievements = null;
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(baseUrl + "/api/v1/achievements/" + steamId, delegate(bool success, string response)
			{
				if (success)
				{
					try
					{
						Dictionary<string, AchievementData> dictionary = new Dictionary<string, AchievementData>();
						string[] array = response.Split(new string[1] { "\"achievement_key\"" }, StringSplitOptions.None);
						for (int i = 1; i < array.Length; i++)
						{
							string text = ExtractJsonString(array[i], "");
							if (!string.IsNullOrEmpty(text))
							{
								bool unlocked = array[i].Contains("\"unlocked\":true") || array[i].Contains("\"unlocked\": true");
								dictionary[text] = new AchievementData
								{
									achievement_key = text,
									unlocked = unlocked
								};
							}
						}
						SelectedPlayerAchievements = dictionary;
						NativeUI.MarkDirty();
					}
					catch
					{
					}
				}
			}));
		}

		private static int FindMatchingBracket(string s, int openPos)
		{
			if (openPos < 0 || openPos >= s.Length)
			{
				return -1;
			}
			int num = 0;
			for (int i = openPos; i < s.Length; i++)
			{
				if (s[i] == '[')
				{
					num++;
				}
				else if (s[i] == ']')
				{
					num--;
					if (num == 0)
					{
						return i;
					}
				}
			}
			return -1;
		}

		private static int FindMatchingBrace(string s, int openPos)
		{
			if (openPos < 0 || openPos >= s.Length)
			{
				return -1;
			}
			int num = 0;
			for (int i = openPos; i < s.Length; i++)
			{
				if (s[i] == '{')
				{
					num++;
				}
				else if (s[i] == '}')
				{
					num--;
					if (num == 0)
					{
						return i;
					}
				}
			}
			return -1;
		}

		public static void FetchCardTiers(string steamId, string filter, Action<Dictionary<string, string>> onLoaded)
		{
			if (string.IsNullOrEmpty(steamId) || steamId == "unknown")
			{
				onLoaded?.Invoke(new Dictionary<string, string>());
				return;
			}
			string url = baseUrl + "/api/v1/players/" + Escape(steamId) + "/card-tiers?filter=" + Escape(filter);
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(url, delegate(bool success, string response)
			{
				Dictionary<string, string> dictionary = new Dictionary<string, string>();
				if (!success || string.IsNullOrEmpty(response))
				{
					onLoaded?.Invoke(dictionary);
				}
				else
				{
					try
					{
						int num = response.IndexOf("\"tiers\"");
						if (num < 0)
						{
							onLoaded?.Invoke(dictionary);
							return;
						}
						int num2 = response.IndexOf('{', num);
						int num3 = FindMatchingBrace(response, num2);
						if (num2 < 0 || num3 < 0)
						{
							onLoaded?.Invoke(dictionary);
							return;
						}
						string text = response.Substring(num2 + 1, num3 - num2 - 1);
						int num4 = 0;
						while (num4 < text.Length)
						{
							int num5 = text.IndexOf('"', num4);
							if (num5 < 0)
							{
								break;
							}
							int num6 = text.IndexOf('"', num5 + 1);
							if (num6 < 0)
							{
								break;
							}
							string text2 = text.Substring(num5 + 1, num6 - num5 - 1);
							int num7 = text.IndexOf(':', num6);
							if (num7 < 0)
							{
								break;
							}
							int num8 = text.IndexOf('"', num7);
							if (num8 < 0)
							{
								break;
							}
							int num9 = text.IndexOf('"', num8 + 1);
							if (num9 < 0)
							{
								break;
							}
							string value = text.Substring(num8 + 1, num9 - num8 - 1);
							dictionary[text2.ToLower()] = value;
							num4 = num9 + 1;
						}
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("[CARD-TIER] parse: " + ex.Message));
					}
					onLoaded?.Invoke(dictionary);
				}
			}));
		}

		public static void SetCardTier(string steamId, string cardName, string filter, string tier)
		{
			if (string.IsNullOrEmpty(steamId) || steamId == "unknown")
			{
				return;
			}
			string text = ComputeHmacHex("card-tier:" + steamId + ":" + cardName + ":" + filter + ":" + tier);
			string url = baseUrl + "/api/v1/players/" + Escape(steamId) + "/card-tiers?card_name=" + Escape(cardName) + "&filter=" + Escape(filter) + "&tier=" + Escape(tier) + "&sig=" + text;
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(url, "", delegate(bool ok, string resp)
			{
				if (!ok)
				{
					Plugin.Log.LogWarning((object)("[CARD-TIER] set failed: " + resp));
				}
			}));
		}

		public static void FetchCardStats(int limit = 30, string steamId = null, string sortBy = "times_picked", string isRanked = null)
		{
			IsLoading = true;
			string text = $"{baseUrl