Decompiled source of SidsCompetitiveRounds v1.35.5

plugins\CompetitiveRounds.dll

Decompiled 2 days 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;
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.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[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("Thunderstore")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+297d79046324c6db5231e1bf5776ee47a55497ba")]
[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;

			public string rank_name;

			public string rank_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 lfp_seconds_left;

			public int best_ranked_game_streak;

			public int current_ranked_game_streak;

			public int best_ranked_series_streak;

			public int current_ranked_series_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_title_sku;

			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 bool appear_offline;

			public string discord_display_name;

			public bool show_discord;

			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 List<float> rating_history_times;

			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;

			public float avg_keys_per_sec;

			public int avg_keys_per_game;

			public int avg_game_seconds;

			public int bets_won;

			public int bets_lost;

			public int bet_gold_net;

			public int sweeps_given;

			public int sweeps_taken;

			public int casual_wins;

			public int casual_losses;

			public string rank_name;

			public string rank_color;

			public float team_rating;

			public int team_completed_series;

			public int ovt_solo_wins;

			public int ovt_solo_losses;

			public int ovt_duo_wins;

			public int ovt_duo_losses;

			public int ffa_games;

			public int ffa_wins;

			public int ffa_top3;

			public float ffa_avg_placement;

			public float ffa_avg_kills;

			public float ffa_avg_damage;

			public int ffa_damage_games;
		}

		[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;

			public int player_bullets_fired;

			public int player_bullets_hit;

			public int player_blocks_activated;

			public int player_blocks_successful;

			public int player_keys_pressed;

			public float player_active_seconds;

			public int opp_bullets_fired;

			public int opp_bullets_hit;

			public int opp_blocks_activated;

			public int opp_blocks_successful;

			public int opp_keys_pressed;

			public float opp_active_seconds;

			public string player_fps_timeline;

			public string opp_fps_timeline;

			public int player_ping_avg;

			public int opponent_ping_avg;

			public string player_ping_timeline;

			public string opp_ping_timeline;

			public string player_hit_timeline;

			public string opp_hit_timeline;

			public string player_block_timeline;

			public string opp_block_timeline;

			public string point_times;

			public string point_timeline;

			public int duration_seconds;
		}

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

		[Serializable]
		public class AchievementData
		{
			public string achievement_key;

			public bool unlocked;

			public string unlocked_at;

			public float global_pct;

			public int gold;
		}

		private class PendingReport
		{
			public string url;

			public string json;

			public int attempts;

			public float nextAt;
		}

		[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;

			public bool settled;

			public int payout;

			public string vs_name;

			public string created_at;
		}

		public class RankTierEntry
		{
			public float floor;

			public string name;

			public string color;
		}

		public class MultimodeSeriesEntry
		{
			public string mode;

			public string id;

			public string ended_at;

			public string left_label;

			public string right_label;

			public string score;

			public float left_rating_change;

			public float right_rating_change;

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

		public class MultimodeBet
		{
			public string bettor_name;

			public string bettor_steam_id;

			public string bet_on_label;

			public int amount;

			public int payout;

			public float odds_multiplier;

			public string state;
		}

		[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 string artist_steam_id;

			public string artist_name;

			public int stock_limit;

			public int stock_sold;
		}

		public class FlaggedMatchEntry
		{
			public string id;

			public string match_id;

			public string game_code;

			public string series_id;

			public string flag_reason;

			public string p1_name;

			public string p2_name;

			public string p1_steam_id;

			public string p2_steam_id;

			public string suspect_steam_text;

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

			public bool auto_invalidated;

			public bool match_invalidated;

			public bool is_ranked;

			public bool restoration_required;

			public int duration_seconds;

			public int discord_evidence_revision;

			public string duration_text;

			public string review_action;

			public string match_invalidation_reason;

			public string created_at;

			public string score_summary;

			public string cards_summary;

			public string point_progress;

			public string evidence_summary;

			public string telemetry_summary;

			public string connection_summary;

			public string match_context;
		}

		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;
		}

		public class QuarantineReportEntry
		{
			public string id;

			public string mode;

			public string reason;

			public string created_at;

			public string score;

			public string room;

			public string status;

			public int http_status;

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

			public bool can_accept;

			public string accept_blocked_reason;
		}

		public class ArtistItemEntry
		{
			public string sku;

			public string kind;

			public string name;

			public string rarity;

			public string description;

			public int price;

			public int stock_limit;

			public int sold;

			public int gifted;

			public bool catalog_ready;

			public int earned;
		}

		public class ArtistBlockEntry
		{
			public string steam_id;

			public string display_name;
		}

		public class ArtistSaleEntry
		{
			public string item;

			public string buyer;

			public string when;

			public int price;

			public int earned;
		}

		public class PlayerSearchResult
		{
			public string steam_id;

			public string display_name;

			public int rating;
		}

		public class CosmeticSubmission
		{
			public int id;

			public float render_scale = 1f;

			public float render_offset_x;

			public float render_offset_y;

			public int placement_revision = 1;

			public string placement_status;

			public string placement_review_note;

			public string review_kind;

			public float approved_render_scale;

			public float approved_render_offset_x;

			public float approved_render_offset_y;

			public int approved_placement_revision;

			public int published_placement_revision;

			public string name;

			public string slot;

			public string status;

			public string review_note;

			public string shop_sku;

			public string created_at;

			public string artist_name;

			public string artist_steam_id;

			public string png_base64;
		}

		public class CosmeticReleaseCandidate
		{
			public int id;

			public int placement_revision;

			public int published_placement_revision;

			public int png_bytes;

			public string shop_sku;

			public string name;

			public string slot;

			public string artist_name;

			public string artist_steam_id;

			public string approved_at;

			public float render_scale;

			public float render_offset_x;

			public float render_offset_y;

			public bool catalog_ready;
		}

		public class AdminSeriesEntry
		{
			public string ladder;

			public string series_id;

			public string status;

			public string created_at;

			public string completed_at;

			public string score;

			public int series_number;

			public int winner_team;

			public int num_players;

			public bool incomplete;

			public string dc_name;

			public string dc_steam;

			public string p1_name;

			public string p1_steam;

			public string p1_cards;

			public int p1_team;

			public string p2_name;

			public string p2_steam;

			public string p2_cards;

			public int p2_team;

			public string p3_name;

			public string p3_steam;

			public string p3_cards;

			public int p3_team;

			public string p4_name;

			public string p4_steam;

			public string p4_cards;

			public int p4_team;
		}

		[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,
			Leaving
		}

		[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 class OnlinePlayerEntry
		{
			public string name;

			public int rating;

			public int minutesAgo;

			public string title;

			public string titleColor;
		}

		public class NewestCosmeticEntry
		{
			public string sku;

			public string kind;

			public string name;

			public string rarity;

			public int price;

			public string previewColor;

			public string artistName;

			public string added;

			public bool onSale;
		}

		public class ReleaseNoteEntry
		{
			public string tag;

			public string title;

			public string date;

			public string body;
		}

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

		[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;
		}

		public class TeamTelemetry
		{
			public string fpsTimeline;

			public string pingTimeline;

			public string hitTimeline;

			public string blockTimeline;

			public int pingAvg;

			public int bulletsFired;

			public int bulletsHit;

			public int blocksActivated;

			public int blocksSuccessful;

			public int keysPressed;

			public float activeSeconds;
		}

		[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 TeamPlayerTele
		{
			public string fps_timeline;

			public string ping_timeline;

			public string hit_timeline;

			public string block_timeline;

			public int ping_avg;

			public int bullets_fired;

			public int bullets_hit;

			public int blocks_activated;

			public int blocks_successful;
		}

		[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 int duration_seconds;

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

			public Dictionary<string, TeamPlayerTele> telemetry_by_player = new Dictionary<string, TeamPlayerTele>();

			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;
		}

		public class OvtLeaderboardEntry
		{
			public int rank;

			public string steam_id;

			public string display_name;

			public int games_played;

			public int wins;

			public int losses;

			public int solo_games;

			public int duo_games;

			public int level;

			public int solo_wins;

			public int solo_losses;

			public int duo_wins;

			public int duo_losses;

			public float win_rate;

			public string title;

			public string title_color;

			public string last_played;
		}

		public class OvtQueueListEntry
		{
			public string steam_id;

			public string display_name;

			public string status;

			public string region;

			public int rating_1v1;

			public int rating_2v2;

			public int preferred_side;

			public int side_assigned;

			public int wait_seconds;

			public bool solo_extra_pick;
		}

		public class OvtRecentMatch
		{
			public string match_id;

			public string ended_at;

			public int winner_side;

			public int solo_rounds;

			public int duo_rounds;

			public int solo_points;

			public int duo_points;

			public int duration_seconds;

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

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

		public class OvtRecentSeries
		{
			public string series_id;

			public string status;

			public string created_at;

			public string completed_at;

			public int winner_side;

			public int solo_wins;

			public int duo_wins;

			public bool solo_extra_pick;

			public string solo_steam;

			public string solo_name;

			public string duo_a_steam;

			public string duo_a_name;

			public string duo_b_steam;

			public string duo_b_name;

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

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

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

		public class FfaLeaderboardEntry
		{
			public int rank;

			public int rating;

			public int rd;

			public int games_played;

			public int wins;

			public int top3;

			public int level;

			public string steam_id;

			public string display_name;

			public string title;

			public string title_color;

			public float avg_placement;

			public float win_rate;

			public int ffa_gold_earned;

			public int ffa_xp_earned;
		}

		public class FfaRecentPlayer
		{
			public string steam_id;

			public string display_name;

			public string title;

			public string title_color;

			public int slot;

			public int placement;

			public int rounds_won;

			public int points_total;

			public int kills;

			public int fps_avg;

			public int xp_gained;

			public int gold_gained;

			public bool left_early;

			public float rating_change;

			public bool has_rating_change;

			public List<FfaRecentCard> cards = new List<FfaRecentCard>();
		}

		public class FfaRecentCard
		{
			public string name;

			public bool rolled;
		}

		public class FfaRecentMatch
		{
			public string match_id;

			public string photon_room_id;

			public string ended_at;

			public string winner_steam_id;

			public string timeline;

			public int player_count;

			public int duration_seconds;

			public bool is_ranked;

			public List<FfaRecentPlayer> players = new List<FfaRecentPlayer>();
		}

		public class PlayerTeamHistoryEntry
		{
			public string series_id;

			public string score;

			public string mate;

			public string completed_at;

			public bool won;

			public bool has_rating_change;

			public float rating_change;

			public List<string> opponents = new List<string>();
		}

		public class PlayerOvtHistoryEntry
		{
			public string match_id;

			public string role;

			public string score;

			public string solo;

			public string ended_at;

			public bool won;

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

			public int gold_gained;

			public int series_gold_gained;
		}

		public class PlayerFfaHistoryEntry
		{
			public string match_id;

			public string ended_at;

			public int player_count;

			public int placement;

			public int kills;

			public int rounds_won;

			public int points_total;

			public bool has_rating_change;

			public float rating_change;

			public List<string> participants = new List<string>();
		}

		public class FfaBettablePlayer
		{
			public string steam_id;

			public string display_name;

			public int rating;

			public float odds_multiplier;

			public bool bettable;
		}

		public class FfaBettableLobby
		{
			public string lobby_id;

			public int player_count;

			public int game_number;

			public bool is_member;

			public bool already_bet;

			public bool bets_open;

			public List<FfaBettablePlayer> players = new List<FfaBettablePlayer>();
		}

		public class FfaLobbyMemberEntry
		{
			public string steam_id;

			public string display_name;

			public int rating;

			public int rating_1v1;

			public int wait_seconds;

			public bool is_host;
		}

		public class FfaOpenLobbyEntry
		{
			public string lobby_id;

			public string host_name;

			public int player_count;

			public int max_players;

			public int age_seconds;
		}

		public class FfaQueueListEntry
		{
			public string steam_id;

			public string display_name;

			public string status;

			public string region;

			public int rating;

			public int rating_1v1;

			public int slot;

			public int wait_seconds;
		}

		public class FfaReportPlayer
		{
			public string steamId;

			public string displayName;

			public int slot;

			public int rounds;

			public int points;

			public int kills;

			public int fps;

			public bool leftEarly;

			public bool absent;

			public List<MatchTracker.CardPickData> cards;

			public TeamTelemetry telemetry;

			public int damageDealt;

			public string killTimeline;

			public string damageTimeline;
		}

		public class AchEarnerData
		{
			public string display_name;

			public int rating;

			public string unlocked_at;

			public string title;

			public string title_color;
		}

		[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 int prize_players;

			public int prize_gold_1;

			public int prize_gold_2;

			public int prize_gold_3;

			public int prize_xp_1;

			public int prize_xp_2;

			public int prize_xp_3;

			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;

			public int ready_seconds_left;

			public int scheduled_seconds_left;

			public bool my_early_ok;

			public bool opp_early_ok;

			public float fetched_at_realtime;
		}

		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] { "Sid Slayer", "Win against Sid in a ranked series" }
			},
			{
				"stan_slayer",
				new string[2] { "Stan Slayer", "Win against Stan 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 1980 rating in ranked (1v1 or 2v2)" }
			},
			{
				"team_sweep",
				new string[2] { "Tag Team Sweep", "Win a 2v2 game 5-0" }
			},
			{
				"grand_master",
				new string[2] { "Grand Master", "Reach 2330 rating in ranked (1v1 or 2v2)" }
			},
			{
				"flawless",
				new string[2] { "Flawless", "Win five games 5-0 in a row" }
			},
			{
				"bullet_hell",
				new string[2] { "Bullet Hell", "Win a game 5-0 with Barrage in your build" }
			},
			{
				"spray_and_pray",
				new string[2] { "Spray and Pray", "Win a game 5-0 with Spray in your build" }
			},
			{
				"demolitionist",
				new string[2] { "Demolitionist", "Win a game 5-0 with Explosive Bullet in your build" }
			},
			{
				"controlled_burst",
				new string[2] { "Controlled Burst", "Win a game 5-0 with Burst in your build" }
			},
			{
				"field_medic",
				new string[2] { "Field Medic", "Win a game 5-0 with Healing Field in your build" }
			},
			{
				"god_build",
				new string[2] { "God Build", "Win with Shields Up, exactly 1 ammo, and a lightning-fast reload" }
			},
			{
				"double_nova",
				new string[2] { "Double Nova", "Win with two or more Supernovas" }
			},
			{
				"lumberjack",
				new string[2] { "Lumberjack", "Win with two or more Saws" }
			},
			{
				"pristine_perfection",
				new string[2] { "Pristine Perfection", "Win with two or more Pristines" }
			},
			{
				"silent_drill",
				new string[2] { "Silly Drill", "Win with Sneaky and Drill together" }
			},
			{
				"double_glass",
				new string[2] { "Living on the Edge", "Win with two Glass Cannons in your build" }
			},
			{
				"sustained_power",
				new string[2] { "Sustained Power", "Win with Empower and Healing Field together" }
			},
			{
				"deep_end",
				new string[2] { "Into the Deep End", "Win with Abyssal Countdown as your FIRST pick, activating it every round" }
			},
			{
				"clutch",
				new string[2] { "Clutch", "Win a game after being down 0-3" }
			},
			{
				"collector",
				new string[2] { "Collector", "Win with four copies of the same card" }
			},
			{
				"grounded",
				new string[2] { "Grounded", "Win a game without ever jumping" }
			},
			{
				"instinct",
				new string[2] { "Instinct", "Win taking only the left-most card on every pick, without ever looking at the others" }
			},
			{
				"rising_star",
				new string[2] { "Rising Star", "Reach 1700 rating in ranked (1v1 or 2v2)" }
			},
			{
				"on_fire",
				new string[2] { "On Fire", "Win 25 ranked series in a row" }
			},
			{
				"unstoppable",
				new string[2] { "Unstoppable", "Win 50 ranked series in a row" }
			},
			{
				"immortal",
				new string[2] { "Immortal", "Win 100 ranked series in a row" }
			},
			{
				"casual_century",
				new string[2] { "Century Club", "Win 100 casual games in a row" }
			},
			{
				"casual_conqueror",
				new string[2] { "Casual Conqueror", "Win 200 casual games in a row" }
			},
			{
				"touch_grass",
				new string[2] { "Touch Grass", "Win 500 casual games in a row" }
			},
			{
				"twins",
				new string[2] { "Twins!", "Finish a game with the exact same 5 cards (and copies) as your opponent" }
			}
		};

		private static readonly List<PendingReport> _pendingReports = new List<PendingReport>();

		private static bool _outboxLoopStarted;

		private const int OUTBOX_MAX_ATTEMPTS = 20;

		private const float OUTBOX_RETRY_SECONDS = 60f;

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

		private static readonly Dictionary<string, float> _heartbeatDispatchTime = new Dictionary<string, float>();

		private static int _hbTournamentCurrentTick;

		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 bool _adminSecretWarned = false;

		public static List<RankTierEntry> CachedRankTiers = null;

		private static bool _rankTiersInFlight;

		private static float _rankTiersAt = -99999f;

		private const float RANK_TIERS_TTL = 420f;

		public static List<MultimodeSeriesEntry> CachedRecentMultimode = null;

		private static bool _multimodeInFlight;

		private static string _lastPublishedCosmeticSig;

		private static string selectedAchSteamId = "";

		public static readonly Dictionary<string, Dictionary<string, AchievementData>> CompareAchievements = new Dictionary<string, Dictionary<string, AchievementData>>();

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

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

		public const int HISTORY_CHUNK = 400;

		public static bool MatchHistoryLoadedAll;

		private static bool _historyFetchInFlight;

		public static int HistoryTotalMatches = -1;

		public static int HistoryTotalRankedGroups = -1;

		public static int HistoryTotalCasual = -1;

		public static readonly Dictionary<string, int[]> CachedOppLifetime = new Dictionary<string, int[]>();

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

		private static readonly Dictionary<string, float> _oppLifetimeRefetchAfter = new Dictionary<string, float>();

		public static string ActiveRankedSeriesId;

		private static float queuePollTimer = 0f;

		private static float queuePollInterval = 3f;

		private const float LEAVE_STUCK_TIMEOUT = 45f;

		private static float rankedLeavingSince = -999f;

		private static int queueGen = 0;

		private static float _ffaRoomlessSince = -1f;

		private const string ESCAPE_PREF = "cr_escape_pending";

		private static float _escapeRetryAt = -999f;

		private static float queueCountTimer = 0f;

		private static float queueCountInterval = 10f;

		public static List<OnlinePlayerEntry> CachedOnlinePlayers;

		public static List<OnlinePlayerEntry> CachedRecentPlayers;

		public static int CachedOnlineListCount;

		public static List<NewestCosmeticEntry> CachedNewestCosmetics;

		public static List<ReleaseNoteEntry> CachedReleaseNotes;

		private static bool _releasesFetchInFlight;

		private const string GITHUB_API_RELEASES = "https://api.github.com/repos/SidNDeed/SidsCompetitiveRounds/releases?per_page=3";

		private static float teamQueueListTimer = 0f;

		private const float TEAM_QUEUE_LIST_INTERVAL = 2f;

		private static float teamLeavingSince = -999f;

		private static int teamGen = 0;

		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;

		public static List<OvtLeaderboardEntry> CachedOvtLeaderboard = null;

		public static int CachedOvtLeaderboardTotal = 0;

		public static List<OvtLeaderboardEntry> CachedOvtLeaderboardSolo = null;

		public static List<OvtLeaderboardEntry> CachedOvtLeaderboardDuo = null;

		public static string OvtQueueStatus = "";

		public static int OvtQueueCount = 0;

		public static string ActiveOvt1v2SeriesId = null;

		public static int OvtMySide = 0;

		public static bool OvtSoloExtraPick = false;

		public static bool IsOvtQueuePolling = false;

		private static float _ovtLastPollAt = -999f;

		public static string OvtLockedSoloName = null;

		public static List<TeamQueueMember> OvtLockedDuo = null;

		public static List<OvtQueueListEntry> CachedOvtQueueList = null;

		private static float _ovtListLastAt = -999f;

		private static int _ovtLastPreferredSide = 0;

		private static bool _ovtLastSoloExtraPick = false;

		private static float _ovtLastAutoRejoinAt = -999f;

		private static float _ovtLeavingSince = -999f;

		private static int ovtGen = 0;

		public static List<OvtRecentSeries> CachedOvtRecent = null;

		public static int CachedOvtRecentTotal = 0;

		public static int CachedOvtRecentPages = 0;

		public static List<FfaLeaderboardEntry> CachedFfaLeaderboard = null;

		public static int CachedFfaLeaderboardTotal = 0;

		public static List<FfaRecentMatch> CachedFfaRecent = null;

		public static int CachedFfaRecentTotal = 0;

		public static int CachedFfaRecentPages = 0;

		public static readonly Dictionary<string, List<PlayerTeamHistoryEntry>> CachedPlayerTeamHistory = new Dictionary<string, List<PlayerTeamHistoryEntry>>();

		public static readonly Dictionary<string, List<PlayerOvtHistoryEntry>> CachedPlayerOvtHistory = new Dictionary<string, List<PlayerOvtHistoryEntry>>();

		public static readonly Dictionary<string, List<PlayerFfaHistoryEntry>> CachedPlayerFfaHistory = new Dictionary<string, List<PlayerFfaHistoryEntry>>();

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

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

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

		public static List<FfaBettableLobby> CachedFfaBettable = null;

		private static bool _ffaBettableInFlight;

		public static string FfaQueueStatus = "";

		public static int FfaQueueCount = 0;

		public static int FfaGatherSecondsLeft = -1;

		public static string ActiveFfaLobbyId = null;

		public static int FfaMySlot = -1;

		public static int FfaLobbyPlayerCount = 0;

		public static bool IsFfaQueuePolling = false;

		public static List<TeamQueueMember> FfaLockedRoster = null;

		private static float _ffaLastPollAt = -999f;

		public static string OpenFfaLobbyId = null;

		public static bool FfaLobbyIsHost = false;

		public static bool FfaLobbyCanStart = false;

		public static int FfaLobbyMemberCount = 0;

		public static int FfaLobbyMinPlayers = 3;

		public static int FfaLobbyMaxPlayers = 10;

		public static List<FfaLobbyMemberEntry> FfaLobbyMembers = null;

		public static List<FfaOpenLobbyEntry> CachedFfaLobbies = null;

		public static bool FfaLobbiesUnavailable = false;

		private static float _ffaLobbiesLastAt = -999f;

		private static float _ffaLobbyRejoinAt = -999f;

		private static bool _ffaLobbyActionInFlight = false;

		private static float _ffaLobbyActionAt = -999f;

		private static int _ffaLobbyActionGen = 0;

		private static bool _ffaLeaveIntent = false;

		private static float _ffaLeaveRetryAt = -999f;

		private static string _ffaLeaveTargetLobby = null;

		private static float _ffaAmbiguousPollUntil = -999f;

		public static List<FfaQueueListEntry> CachedFfaQueueList = null;

		private static float _ffaListLastAt = -999f;

		private static float _ffaLastAutoRejoinAt = -999f;

		private static string _ffaLastReadyFireKey = null;

		private static int _ffaReadyFireCount = 0;

		private static string _ovtLastReadyFireKey = null;

		private static int _ovtReadyFireCount = 0;

		private static float _ffaLeavingSince = -999f;

		private static int ffaGen = 0;

		private static bool _ffaProbed = false;

		private static string _ffaJoinCountdownKey;

		private static int _ffaJoinCountdownToken;

		private static int _ffaJoinCountdownActiveToken;

		private static float _ffaJoinCountdownArmedAt = -999f;

		private static bool blocksLoaded = false;

		public static float LastApiSuccessAt = -999f;

		public static float LastApiAttemptAt = -999f;

		public static bool LastResponseWasMaintenance = false;

		private static bool _earnersInFlight;

		private static string _earnersWantedKey;

		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 UsingLegacyFallback { get; private set; }

		private static string OutboxPath => Path.Combine(Paths.ConfigPath, "CompetitiveRounds.pending-reports.jsonl");

		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<QuarantineReportEntry> CachedQuarantineReports { get; private set; } = new List<QuarantineReportEntry>();

		public static bool IsAdminQuarantineLoading { get; private set; }

		public static bool AdminQuarantineLoaded { get; private set; }

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

		public static bool IsArtist { get; private set; }

		public static List<ArtistItemEntry> CachedArtistItems { get; private set; } = new List<ArtistItemEntry>();

		public static List<ArtistBlockEntry> CachedArtistBlocks { get; private set; } = new List<ArtistBlockEntry>();

		public static List<ArtistSaleEntry> CachedArtistSales { get; private set; } = new List<ArtistSaleEntry>();

		public static List<ArtistBlockEntry> CachedAllArtists { get; private set; } = new List<ArtistBlockEntry>();

		public static List<CosmeticSubmission> CachedMySubmissions { get; private set; } = new List<CosmeticSubmission>();

		public static List<AdminSeriesEntry> CachedAdminSeries { get; private set; } = new List<AdminSeriesEntry>();

		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 bool EscapePending
		{
			get
			{
				try
				{
					return PlayerPrefs.GetInt("cr_escape_pending", 0) == 1;
				}
				catch
				{
					return false;
				}
			}
			private set
			{
				try
				{
					PlayerPrefs.SetInt("cr_escape_pending", value ? 1 : 0);
					PlayerPrefs.Save();
				}
				catch
				{
				}
			}
		}

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

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

		public static int CachedOnlineCount { 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 string EarnersKey { get; private set; }

		public static List<AchEarnerData> CachedEarners { get; private set; }

		public static int CachedEarnersTotal { get; private set; }

		public static void Initialize(string url)
		{
			baseUrl = url.TrimEnd('/');
			Plugin.Log.LogInfo((object)("API client initialized: " + baseUrl));
			if (string.Equals(baseUrl, "https://competitive-rounds.duckdns.org:8444".TrimEnd('/'), StringComparison.OrdinalIgnoreCase))
			{
				((MonoBehaviour)Plugin.Instance).StartCoroutine(ProbeEndpointThenStart());
			}
			else
			{
				CheckModVersion();
			}
			((MonoBehaviour)Plugin.Instance).StartCoroutine(TournamentHeartbeatLoop());
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PresenceLoop());
			((MonoBehaviour)Plugin.Instance).StartCoroutine(SteamAuthLoop());
			LoadOutbox();
		}

		private static IEnumerator SteamAuthLoop()
		{
			yield return (object)new WaitForSecondsRealtime(2f);
			while (true)
			{
				try
				{
					SteamAuth.MaybeRefresh();
				}
				catch
				{
				}
				yield return (object)new WaitForSecondsRealtime(SteamAuth.HasFreshToken ? 30f : 3f);
			}
		}

		public static void EnqueueFailedReport(string url, string json)
		{
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(json))
				{
					return;
				}
				foreach (PendingReport pendingReport in _pendingReports)
				{
					if (pendingReport.url == url && pendingReport.json == json)
					{
						return;
					}
				}
				float num = (IsSilentOutboxUrl(url) ? 120f : 30f);
				_pendingReports.Add(new PendingReport
				{
					url = url,
					json = json,
					attempts = 0,
					nextAt = Time.realtimeSinceStartup + num
				});
				PersistOutbox();
				EnsureOutboxLoop();
				if (!IsSilentOutboxUrl(url))
				{
					Plugin.Log.LogWarning((object)$"[OUTBOX] report queued for retry ({_pendingReports.Count} pending)");
					CompetitiveUI.ShowNotification("Report failed — will retry in background", new Color(1f, 0.75f, 0.3f), 4f);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[OUTBOX] enqueue failed: " + ex.Message));
			}
		}

		private static bool IsSilentOutboxUrl(string url)
		{
			return !string.IsNullOrEmpty(url) && url.EndsWith("/api/v1/matches/macro-evidence", StringComparison.Ordinal);
		}

		private static void RemovePendingReport(string url, string json)
		{
			bool flag = false;
			for (int num = _pendingReports.Count - 1; num >= 0; num--)
			{
				PendingReport pendingReport = _pendingReports[num];
				if (!(pendingReport.url != url) && !(pendingReport.json != json))
				{
					_pendingReports.RemoveAt(num);
					flag = true;
				}
			}
			if (flag)
			{
				PersistOutbox();
			}
		}

		private static void PersistOutbox()
		{
			try
			{
				StringBuilder stringBuilder = new StringBuilder();
				foreach (PendingReport pendingReport in _pendingReports)
				{
					stringBuilder.Append(pendingReport.url).Append('\t').Append(pendingReport.json.Replace("\n", " ").Replace("\r", " "))
						.Append('\n');
				}
				if (stringBuilder.Length == 0)
				{
					if (File.Exists(OutboxPath))
					{
						File.Delete(OutboxPath);
					}
				}
				else
				{
					File.WriteAllText(OutboxPath, stringBuilder.ToString());
				}
			}
			catch
			{
			}
		}

		private static void LoadOutbox()
		{
			try
			{
				if (!File.Exists(OutboxPath))
				{
					return;
				}
				string[] array = File.ReadAllLines(OutboxPath);
				foreach (string text in array)
				{
					int num = text.IndexOf('\t');
					if (num > 0 && num < text.Length - 1)
					{
						_pendingReports.Add(new PendingReport
						{
							url = text.Substring(0, num),
							json = text.Substring(num + 1),
							attempts = 0,
							nextAt = Time.realtimeSinceStartup + 20f
						});
					}
				}
				if (_pendingReports.Count > 0)
				{
					Plugin.Log.LogInfo((object)$"[OUTBOX] loaded {_pendingReports.Count} unsent report(s) from previous session");
					EnsureOutboxLoop();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[OUTBOX] load failed: " + ex.Message));
			}
		}

		private static void EnsureOutboxLoop()
		{
			if (!_outboxLoopStarted && !((Object)(object)Plugin.Instance == (Object)null))
			{
				_outboxLoopStarted = true;
				((MonoBehaviour)Plugin.Instance).StartCoroutine(OutboxLoop());
			}
		}

		private static IEnumerator OutboxLoop()
		{
			while (true)
			{
				yield return (object)new WaitForSecondsRealtime(10f);
				if (_pendingReports.Count == 0)
				{
					continue;
				}
				float now = Time.realtimeSinceStartup;
				bool changed = false;
				for (int i = _pendingReports.Count - 1; i >= 0; i--)
				{
					PendingReport p = _pendingReports[i];
					if (!(p.nextAt > now))
					{
						p.attempts++;
						p.nextAt = now + 60f * (float)Math.Min(4, p.attempts);
						bool done = false;
						bool ok = false;
						string resp = null;
						yield return PostRequest(p.url, p.json, delegate(bool s, string r)
						{
							done = true;
							ok = s;
							resp = r;
						});
						while (!done)
						{
							yield return null;
						}
						if (ok)
						{
							Plugin.Log.LogInfo((object)$"[OUTBOX] queued report delivered on retry {p.attempts}");
							if (!IsSilentOutboxUrl(p.url))
							{
								CompetitiveUI.ShowNotification("Match report delivered", new Color(0.4f, 1f, 0.5f), 4f);
							}
							_pendingReports.RemoveAt(i);
							changed = true;
						}
						else
						{
							bool retryableMacroResponse = IsSilentOutboxUrl(p.url) && resp != null && (resp.Contains("401") || resp.Contains("409") || resp.Contains("429"));
							bool retryableTransient = resp != null && (resp.Contains("HTTP 429") || resp.Contains("HTTP/1.1 429") || resp.Contains("HTTP 401") || resp.Contains("HTTP/1.1 401"));
							if ((!retryableMacroResponse && !retryableTransient && resp != null && (resp.StartsWith("HTTP 4", StringComparison.Ordinal) || resp.Contains("HTTP/1.1 4") || resp.Contains("duplicate key"))) || p.attempts >= 20)
							{
								Plugin.Log.LogWarning((object)string.Format("[OUTBOX] dropping report after {0} attempt(s): {1}", p.attempts, Trunc(resp ?? "(no response)", 160)));
								_pendingReports.RemoveAt(i);
								changed = true;
							}
						}
					}
				}
				if (changed || _pendingReports.Count > 0)
				{
					PersistOutbox();
				}
			}
		}

		private static string Trunc(string s, int n)
		{
			return (string.IsNullOrEmpty(s) || s.Length <= n) ? s : (s.Substring(0, n) + "..");
		}

		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;
				}
				if (++_hbTournamentCurrentTick >= 3)
				{
					_hbTournamentCurrentTick = 0;
					try
					{
						FetchTournamentCurrent(sid);
					}
					catch
					{
					}
				}
				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))
					{
						continue;
					}
					seen.Add(m.tournament_id);
					TournamentReady(m.tournament_id, sid);
					yield return (object)new WaitForSeconds(0.2f);
					bool isSyncReady = m.kind == "sync" && m.status == "ready" && m.my_ready && m.opp_ready && !string.IsNullOrEmpty(m.photon_room_name) && !string.IsNullOrEmpty(m.match_id);
					if (isSyncReady && !_heartbeatDispatchedMatches.Contains(m.match_id))
					{
						if (Plugin.PendingRankedRoom != m.photon_room_name)
						{
							_heartbeatDispatchedMatches.Add(m.match_id);
							_heartbeatDispatchTime[m.match_id] = Time.unscaledTime;
							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));
						}
					}
					else if (isSyncReady && _heartbeatDispatchedMatches.Contains(m.match_id))
					{
						float at;
						bool oldEnough = !_heartbeatDispatchTime.TryGetValue(m.match_id, out at) || Time.unscaledTime - at > 60f;
						bool inTargetRoom = false;
						try
						{
							inTargetRoom = PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null && PhotonNetwork.CurrentRoom.Name == m.photon_room_name;
						}
						catch
						{
						}
						if (oldEnough && !inTargetRoom && Plugin.PendingRankedRoom != m.photon_room_name)
						{
							_heartbeatDispatchedMatches.Remove(m.match_id);
							Plugin.Log.LogInfo((object)("[TOURNAMENT-HB] Not in match room 60s after dispatch — re-arming auto-connect for " + m.match_id));
						}
					}
				}
				if (CachedMyActiveTournamentMatches.Count == 0)
				{
					_heartbeatDispatchedMatches.Clear();
					continue;
				}
				HashSet<string> stillActive = new HashSet<string>();
				foreach (ActiveTournamentMatch m2 in CachedMyActiveTournamentMatches)
				{
					if (!string.IsNullOrEmpty(m2.match_id))
					{
						stillActive.Add(m2.match_id);
					}
				}
				_heartbeatDispatchedMatches.RemoveWhere((string id) => !stillActive.Contains(id));
			}
		}

		private static IEnumerator ProbeEndpointThenStart()
		{
			bool reachable = false;
			for (int i = 0; i < 2; i++)
			{
				if (reachable)
				{
					break;
				}
				UnityWebRequest probe = UnityWebRequest.Get(baseUrl + "/api/v1/mod-version");
				try
				{
					probe.timeout = 8;
					StampVersionHeader(probe);
					yield return probe.SendWebRequest();
					object obj;
					if ((int)probe.result != 1)
					{
						obj = "";
					}
					else
					{
						DownloadHandler downloadHandler = probe.downloadHandler;
						obj = ((downloadHandler != null) ? downloadHandler.text : null) ?? "";
					}
					string body = (string)obj;
					if (!string.IsNullOrEmpty(ExtractJsonString(body, "version")))
					{
						reachable = true;
					}
					else
					{
						Plugin.Log.LogWarning((object)($"[API] TLS endpoint probe {i + 1}/{2} unusable: " + $"result={probe.result} code={probe.responseCode} err={probe.error}"));
					}
				}
				finally
				{
					((IDisposable)probe)?.Dispose();
				}
			}
			if (!reachable)
			{
				baseUrl = "http://competitive-rounds.duckdns.org:8443".TrimEnd('/');
				UsingLegacyFallback = true;
				Plugin.Log.LogWarning((object)("[API] https://competitive-rounds.duckdns.org:8444 unreachable — falling back to " + baseUrl + " for this session. Config is left untouched, so TLS is retried on next launch."));
				try
				{
					ChatClient.Retarget(baseUrl);
				}
				catch (Exception ex)
				{
					Exception ex2 = ex;
					Plugin.Log.LogWarning((object)("[API] chat retarget failed: " + ex2.Message));
				}
			}
			CheckModVersion();
		}

		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 ver = ExtractJsonString(req.downloadHandler.text, "version");
			string minVer = ExtractJsonString(req.downloadHandler.text, "min_version");
			if (!string.IsNullOrEmpty(ver))
			{
				LatestModVersion = ver;
				if (ver != "1.35.5")
				{
					Plugin.Log.LogWarning((object)("[VERSION] Update available: v1.35.5 → v" + ver));
				}
				else
				{
					Plugin.Log.LogInfo((object)("[VERSION] Mod is up to date (v" + ver + ")"));
				}
			}
			if (!string.IsNullOrEmpty(minVer))
			{
				MinModVersion = minVer;
				if (CompareVersion("1.35.5", minVer) < 0)
				{
					ForceUpdateRequired = true;
					Plugin.Log.LogWarning((object)("[VERSION] Mod is BELOW server minimum (v1.35.5 < v" + minVer + ") — update required"));
				}
			}
			if (!string.IsNullOrEmpty(LatestModVersion) && CompareVersion("1.35.5", 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)
				{
					return (result >= result2) ? 1 : (-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 "";
			}
		}

		private static string ComputeAdminHmacHex(string message)
		{
			string text = null;
			try
			{
				text = Plugin.AdminHmacSecret?.Value;
			}
			catch
			{
			}
			if (string.IsNullOrWhiteSpace(text))
			{
				if (!_adminSecretWarned)
				{
					_adminSecretWarned = true;
					Plugin.Log.LogWarning((object)"[ADMIN] AdminSecret is not set in the config — admin actions will be rejected by the server (security A2).");
				}
				return "";
			}
			try
			{
				using HMACSHA256 hMACSHA = new HMACSHA256(Encoding.UTF8.GetBytes(text.Trim()));
				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 "";
			}
		}

		private static string ComputeSha256Hex(string value)
		{
			try
			{
				using SHA256 sHA = SHA256.Create();
				byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(value ?? ""));
				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 FetchRankTiers()
		{
			if (_rankTiersInFlight || (CachedRankTiers != null && Time.realtimeSinceStartup - _rankTiersAt < 420f))
			{
				return;
			}
			_rankTiersAt = Time.realtimeSinceStartup;
			_rankTiersInFlight = true;
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(baseUrl + "/api/v1/rank-tiers", delegate(bool ok, string resp)
			{
				_rankTiersInFlight = false;
				if (!ok || string.IsNullOrEmpty(resp))
				{
					return;
				}
				try
				{
					List<RankTierEntry> list = new List<RankTierEntry>();
					int num = resp.IndexOf("\"tiers\":", StringComparison.Ordinal);
					int num2 = ((num >= 0) ? resp.IndexOf('[', num) : (-1));
					int num3 = ((num2 >= 0) ? FindMatchingBracketStringAware(resp, num2) : (-1));
					if (num2 >= 0 && num3 > num2)
					{
						foreach (string item in SliceTopLevelObjects(resp.Substring(num2 + 1, num3 - num2 - 1)))
						{
							RankTierEntry rankTierEntry = new RankTierEntry
							{
								floor = ExtractJsonInt(item, "floor"),
								name = ExtractJsonString(item, "name"),
								color = ExtractJsonString(item, "color")
							};
							if (!string.IsNullOrEmpty(rankTierEntry.name))
							{
								list.Add(rankTierEntry);
							}
						}
						if (list.Count > 0)
						{
							CachedRankTiers = list;
							NativeUI.MarkDirty();
						}
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("[RANK-TIERS] parse failed: " + ex.Message));
				}
			}));
		}

		public static void FetchRecentMultimodeSeries(int limit = 60)
		{
			if (_multimodeInFlight)
			{
				return;
			}
			_multimodeInFlight = true;
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest($"{baseUrl}/api/v1/series/recent-multimode?limit={limit}", delegate(bool ok, string resp)
			{
				_multimodeInFlight = false;
				if (!ok || string.IsNullOrEmpty(resp))
				{
					return;
				}
				try
				{
					List<MultimodeSeriesEntry> list = new List<MultimodeSeriesEntry>();
					int num = resp.IndexOf("\"entries\":", StringComparison.Ordinal);
					int num2 = ((num >= 0) ? resp.IndexOf('[', num) : (-1));
					int num3 = ((num2 >= 0) ? FindMatchingBracketStringAware(resp, num2) : (-1));
					if (num2 >= 0 && num3 > num2)
					{
						foreach (string item in SliceTopLevelObjects(resp.Substring(num2 + 1, num3 - num2 - 1)))
						{
							MultimodeSeriesEntry multimodeSeriesEntry = new MultimodeSeriesEntry
							{
								mode = ExtractJsonString(item, "mode"),
								id = ExtractJsonString(item, "id"),
								ended_at = ExtractJsonString(item, "ended_at"),
								left_label = ExtractJsonString(item, "left_label"),
								right_label = ExtractJsonString(item, "right_label"),
								score = ExtractJsonString(item, "score"),
								left_rating_change = ExtractJsonFloat(item, "left_rating_change"),
								right_rating_change = ExtractJsonFloat(item, "right_rating_change")
							};
							int num4 = item.IndexOf("\"bets\":", StringComparison.Ordinal);
							int num5 = ((num4 >= 0) ? item.IndexOf('[', num4) : (-1));
							int num6 = ((num5 >= 0) ? FindMatchingBracketStringAware(item, num5) : (-1));
							if (num5 >= 0 && num6 > num5)
							{
								foreach (string item2 in SliceTopLevelObjects(item.Substring(num5 + 1, num6 - num5 - 1)))
								{
									MultimodeBet multimodeBet = new MultimodeBet
									{
										bettor_name = ExtractJsonString(item2, "bettor_name"),
										bettor_steam_id = ExtractJsonString(item2, "bettor_steam_id"),
										bet_on_label = ExtractJsonString(item2, "bet_on_label"),
										amount = ExtractJsonInt(item2, "amount"),
										payout = ExtractJsonInt(item2, "payout"),
										odds_multiplier = ExtractJsonFloat(item2, "odds_multiplier"),
										state = ExtractJsonString(item2, "state")
									};
									if (!string.IsNullOrEmpty(multimodeBet.bettor_name))
									{
										multimodeSeriesEntry.bets.Add(multimodeBet);
									}
								}
							}
							if (!string.IsNullOrEmpty(multimodeSeriesEntry.mode))
							{
								list.Add(multimodeSeriesEntry);
							}
						}
						CachedRecentMultimode = list;
						Plugin.Log.LogInfo((object)$"Recent multimode series loaded: {list.Count}");
						NativeUI.MarkDirty();
					}
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("[MULTIMODE] parse failed: " + ex.Message));
				}
			}));
		}

		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"),
								settled = !string.IsNullOrEmpty(ExtractJsonString(json, "settled_at")),
								payout = ExtractJsonInt(json, "payout"),
								vs_name = ExtractJsonString(json, "vs_name"),
								created_at = ExtractJsonString(json, "created_at")
							};
							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 SetAppearOffline(string steamId, bool on, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex($"appear_offline:{steamId}:{(on ? 1 : 0)}");
			string url = baseUrl + "/api/v1/players/" + steamId + "/appear-offline?on=" + (on ? "true" : "false") + "&sig=" + text;
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(url, "", delegate(bool ok, string resp)
			{
				Plugin.Log.LogInfo((object)$"[HOME] set appear_offline {on}: ok={ok} resp={resp}");
				callback?.Invoke(ok, resp);
				if (ok)
				{
					FetchPlayerStats(steamId);
					FetchOnlinePlayers();
				}
			}));
		}

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

		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");
				shopItemData.artist_steam_id = ExtractJsonString(json, "artist_steam_id");
				shopItemData.artist_name = ExtractJsonString(json, "artist_name");
				shopItemData.stock_limit = ExtractJsonInt(json, "stock_limit");
				shopItemData.stock_sold = ExtractJsonInt(json, "stock_sold");
				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();
				string sentToken = SteamAuth.SessionToken;
				StampVersionHeader(request);
				request.timeout = 20;
				yield return request.SendWebRequest();
				bool success = (int)request.result == 1;
				if (!success)
				{
					HandleSessionReject(request, sentToken);
				}
				object arg;
				if (!success)
				{
					arg = request.error;
				}
				else
				{
					DownloadHandler downloadHandler = request.downloadHandler;
					arg = ((downloadHandler != null) ? downloadHandler.text : null) ?? "";
				}
				callback(success, (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);
				CheckArtistStatus(steamId);
			}));
		}

		public static void CheckArtistStatus(string steamId, Action<bool> callback = null)
		{
			if (string.IsNullOrEmpty(steamId))
			{
				IsArtist = false;
				callback?.Invoke(obj: false);
				return;
			}
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(baseUrl + "/api/v1/artist/status/" + steamId, delegate(bool ok, string body)
			{
				bool flag = false;
				if (ok && !string.IsNullOrEmpty(body))
				{
					flag = body.Contains("\"is_artist\":true") || body.Contains("\"is_artist\": true");
				}
				IsArtist = flag;
				if (flag)
				{
					Plugin.Log.LogInfo((object)("[ARTIST] " + steamId + " is an artist"));
				}
				callback?.Invoke(flag);
			}));
		}

		public static void FetchArtistItems(string steamId)
		{
			if (string.IsNullOrEmpty(steamId) || steamId == "unknown")
			{
				return;
			}
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(baseUrl + "/api/v1/artist/" + steamId + "/items", delegate(bool ok, string resp)
			{
				if (!ok)
				{
					Plugin.Log.LogWarning((object)("[ARTIST] items fetch failed: " + resp));
				}
				else
				{
					List<ArtistItemEntry> list = new List<ArtistItemEntry>();
					List<ArtistBlockEntry> list2 = new List<ArtistBlockEntry>();
					try
					{
						int num = resp.IndexOf("\"blocked\"", StringComparison.Ordinal);
						string text = ((num > 0) ? resp.Substring(0, num) : resp);
						string text2 = ((num > 0) ? resp.Substring(num) : "");
						string[] array = text.Split(new string[1] { "\"sku\":" }, StringSplitOptions.None);
						for (int i = 1; i < array.Length; i++)
						{
							string json = "\"sku\":" + array[i];
							ArtistItemEntry artistItemEntry = new ArtistItemEntry
							{
								sku = ExtractJsonString(json, "sku"),
								kind = ExtractJsonString(json, "kind"),
								name = ExtractJsonString(json, "name"),
								description = ExtractJsonString(json, "description"),
								rarity = ExtractJsonString(json, "rarity"),
								price = ExtractJsonInt(json, "price"),
								stock_limit = ExtractJsonInt(json, "stock_limit"),
								catalog_ready = ExtractJsonBool(json, "catalog_ready"),
								sold = ExtractJsonInt(json, "sold"),
								gifted = ExtractJsonInt(json, "gifted"),
								earned = ExtractJsonInt(json, "earned")
							};
							if (!string.IsNullOrEmpty(artistItemEntry.sku))
							{
								list.Add(artistItemEntry);
							}
						}
						string[] array2 = text2.Split(new string[1] { "\"steam_id\":" }, StringSplitOptions.None);
						for (int j = 1; j < array2.Length; j++)
						{
							string json2 = "\"steam_id\":" + array2[j];
							ArtistBlockEntry artistBlockEntry = new ArtistBlockEntry
							{
								steam_id = ExtractJsonString(json2, "steam_id"),
								display_name = ExtractJsonString(json2, "display_name")
							};
							if (!string.IsNullOrEmpty(artistBlockEntry.steam_id))
							{
								list2.Add(artistBlockEntry);
							}
						}
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("[ARTIST] items parse: " + ex.Message));
					}
					CachedArtistItems = list;
					CachedArtistBlocks = list2;
					NativeUI.MarkDirty();
				}
			}));
		}

		public static void FetchArtistSales(string steamId)
		{
			if (string.IsNullOrEmpty(steamId) || steamId == "unknown")
			{
				return;
			}
			((MonoBehaviour)Plugin.Instance).StartCoroutine(GetRequest(baseUrl + "/api/v1/artist/" + steamId + "/sales?limit=50", delegate(bool ok, string resp)
			{
				if (!ok)
				{
					Plugin.Log.LogWarning((object)("[ARTIST] sales fetch failed: " + resp));
				}
				else
				{
					List<ArtistSaleEntry> list = new List<ArtistSaleEntry>();
					try
					{
						int num = resp.IndexOf("\"sales\"", StringComparison.Ordinal);
						int num2 = ((num >= 0) ? resp.IndexOf('[', num) : (-1));
						int num3 = ((num2 >= 0) ? FindMatchingBracketStringAware(resp, num2) : (-1));
						if (num2 >= 0 && num3 > num2)
						{
							foreach (string item in SliceTopLevelObjects(resp.Substring(num2 + 1, num3 - num2 - 1)))
							{
								ArtistSaleEntry artistSaleEntry = new ArtistSaleEntry
								{
									item = ExtractJsonString(item, "item"),
									buyer = ExtractJsonString(item, "buyer"),
									when = ExtractJsonString(item, "when"),
									price = ExtractJsonInt(item, "price"),
									earned = ExtractJsonInt(item, "earned")
								};
								if (!string.IsNullOrEmpty(artistSaleEntry.item))
								{
									list.Add(artistSaleEntry);
								}
							}
						}
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("[ARTIST] sales parse: " + ex.Message));
					}
					CachedArtistSales = list;
					NativeUI.MarkDirty();
				}
			}));
		}

		public static void ArtistSetPrice(string steamId, string sku, int price, Action<bool, string> callback = null)
		{
			string text = ComputeHmacHex($"artist:{steamId}:set-price:{sku}:{price}");
			string url = $"{baseUrl}/api/v1/artist/set-price?steam_id={Escape(steamId)}&sku={Escape(sku)}&price={price}&sig={text}";
			((MonoBehaviour)Plugin.Instance).StartCoroutine(PostRequest(url, "", delegate(bool ok, string resp)
			{
				Plugin.Log.LogInfo((object)$"[ARTIST] set-price {sku}={price}: ok={ok}");
				if (o