using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using HowtoFishLocalizationAPI.Api;
using ModUpdateChecker.Configuration;
using ModUpdateChecker.Localization;
using ModUpdateChecker.Runtime;
using ModUpdateChecker.UI;
using Newtonsoft.Json;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Networking;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("ModUpdateChecker")]
[assembly: AssemblyDescription("Thunderstore update checker for How to Fish by Ice Box Studio")]
[assembly: AssemblyCompany("Ice Box Studio")]
[assembly: AssemblyProduct("ModUpdateChecker")]
[assembly: AssemblyCopyright("Copyright © 2026 Ice Box Studio All rights reserved.")]
[assembly: ComVisible(false)]
[assembly: Guid("b4d4f8f4-83a7-4ec6-bf6b-7cdaf33f2a52")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace ModUpdateChecker
{
[BepInPlugin("IceBoxStudio.HowToFish.ModUpdateChecker", "ModUpdateChecker", "1.0.0")]
[BepInDependency("IceBoxStudio.HowToFish.LocalizationAPI", "1.0.0")]
public sealed class ModUpdateCheckerPlugin : BaseUnityPlugin
{
private Harmony _harmony;
internal static ManualLogSource Log { get; private set; }
private void Awake()
{
//IL_0077: Unknown result type (might be due to invalid IL or missing references)
//IL_0081: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
try
{
Log.LogInfo((object)"=============================================");
Log.LogInfo((object)("ModUpdateChecker " + I18n.Text("plugin.initializing")));
Log.LogInfo((object)(I18n.Text("plugin.author_prefix") + "Ice Box Studio(https://steamcommunity.com/id/ibox666/)"));
ConfigManager.Init(((BaseUnityPlugin)this).Config);
ModUpdateRuntime.Init((MonoBehaviour)(object)this);
_harmony = new Harmony("IceBoxStudio.HowToFish.ModUpdateChecker");
_harmony.PatchAll(Assembly.GetExecutingAssembly());
Log.LogInfo((object)("ModUpdateChecker " + I18n.Text("plugin.initialized")));
Log.LogInfo((object)"=============================================");
}
catch (Exception arg)
{
Log.LogError((object)$"Failed to initialize ModUpdateChecker: {arg}");
}
}
private void Update()
{
ModUpdateRuntime.Update();
}
}
public static class PluginInfo
{
public const string PLUGIN_GUID = "IceBoxStudio.HowToFish.ModUpdateChecker";
public const string PLUGIN_NAME = "ModUpdateChecker";
public const string PLUGIN_VERSION = "1.0.0";
}
}
namespace ModUpdateChecker.UI
{
internal sealed class ModUpdateUi : MonoBehaviour
{
private static readonly BindingFlags PrivateInstance = BindingFlags.Instance | BindingFlags.NonPublic;
private static ModUpdateUi _current;
private GameObject _mainMenuButton;
private GameObject _pauseHolder;
private GameObject _mainScreen;
private GameObject _optionsScreen;
private GameObject _serverScreen;
private GameObject _root;
private GameObject _buttonTemplate;
private Transform _rows;
private TextMeshProUGUI _title;
private TextMeshProUGUI _summary;
private TextMeshProUGUI _checkLabel;
private TextMeshProUGUI _closeLabel;
private TextMeshProUGUI _backLabel;
private Button _checkButton;
private Button _backButton;
private bool _open;
private bool _built;
private ModUpdateEntry _candidateEntry;
internal static void Update()
{
if ((Object)(object)_current == (Object)null)
{
Attach();
}
if ((Object)(object)_current == (Object)null)
{
return;
}
if (Object.op_Implicit((Object)(object)_current._mainMenuButton))
{
_current._mainMenuButton.SetActive(MainMenuManager.IsInMenu && !PauseManager.IsPaused);
_current.RefreshMenuButton();
}
if (MainMenuManager.IsInMenu)
{
_current.AddMainMenuButton();
if (_current._open && !PauseManager.IsPaused)
{
_current.Hide();
}
}
if ((_current._built || _current.Setup()) && _current._open && Input.GetKeyDown((KeyCode)27))
{
if (_current._candidateEntry != null)
{
_current.HideCandidates();
}
else
{
_current.Hide();
}
}
}
internal static void Attach()
{
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
if (!Object.op_Implicit((Object)(object)_current))
{
Transform hostParent = GetHostParent();
if (Object.op_Implicit((Object)(object)hostParent))
{
GameObject val = new GameObject("ModUpdateChecker UI");
val.transform.SetParent(hostParent, false);
_current = val.AddComponent<ModUpdateUi>();
_current.Setup();
}
}
}
internal static void Open()
{
Attach();
if (Object.op_Implicit((Object)(object)_current))
{
_current.Show();
}
}
internal static void Toggle()
{
if (Object.op_Implicit((Object)(object)_current) && _current._open)
{
_current.Hide();
}
else
{
Open();
}
}
private static Transform GetHostParent()
{
CanvasManager val = GetStatic<CanvasManager>(typeof(CanvasManager), "_instance");
if (Object.op_Implicit((Object)(object)val))
{
return ((Component)val).transform;
}
return PlayerUI.FXCanvasTrans;
}
private void AddMainMenuButton()
{
//IL_011d: Unknown result type (might be due to invalid IL or missing references)
//IL_0127: Expected O, but got Unknown
if (Object.op_Implicit((Object)(object)_mainMenuButton))
{
return;
}
CanvasManager obj = GetStatic<CanvasManager>(typeof(CanvasManager), "_instance");
List<GameObject> field = GetField<List<GameObject>>(obj, "_allMainMenuButtons");
TextMeshProUGUI field2 = GetField<TextMeshProUGUI>(obj, "_versionText");
if (!Object.op_Implicit((Object)(object)obj) || field == null || field.Count == 0 || !Object.op_Implicit((Object)(object)field[0]) || !Object.op_Implicit((Object)(object)field2))
{
return;
}
Button val = FindMainMenuButton(field[0]);
if (Object.op_Implicit((Object)(object)val))
{
_mainMenuButton = Object.Instantiate<GameObject>(((Component)val).gameObject, ((TMP_Text)field2).transform.parent, false);
((Object)_mainMenuButton).name = "ModUpdateCheckerMainMenuButton";
_mainMenuButton.SetActive(true);
DisableExtras(_mainMenuButton);
Button val2 = UseButton(_mainMenuButton);
TextMeshProUGUI text = GetText(_mainMenuButton);
if (!Object.op_Implicit((Object)(object)val2) || !Object.op_Implicit((Object)(object)text))
{
Object.Destroy((Object)(object)_mainMenuButton);
_mainMenuButton = null;
return;
}
((TMP_Text)text).text = I18n.Text("ui.main_menu_button");
((UnityEvent)val2.onClick).AddListener(new UnityAction(OpenMenu));
(_mainMenuButton.GetComponent<LayoutElement>() ?? _mainMenuButton.AddComponent<LayoutElement>()).ignoreLayout = true;
PlaceMainMenuButton(_mainMenuButton.GetComponent<RectTransform>(), ((TMP_Text)field2).rectTransform);
_mainMenuButton.transform.SetAsLastSibling();
}
}
private static Button FindMainMenuButton(GameObject page)
{
Button[] componentsInChildren = page.GetComponentsInChildren<Button>(true);
Button val = null;
foreach (Button val2 in componentsInChildren)
{
if (Object.op_Implicit((Object)(object)val2) && ((Component)val2).gameObject.activeInHierarchy)
{
if ((Object)(object)val == (Object)null)
{
val = val2;
}
TextMeshProUGUI text = GetText(((Component)val2).gameObject);
if ((Object)(object)text != (Object)null && (((TMP_Text)text).text.IndexOf("Options", StringComparison.OrdinalIgnoreCase) >= 0 || ((TMP_Text)text).text.IndexOf("Settings", StringComparison.OrdinalIgnoreCase) >= 0))
{
return val2;
}
}
}
return val;
}
private static void PlaceMainMenuButton(RectTransform button, RectTransform version)
{
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_002c: Unknown result type (might be due to invalid IL or missing references)
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_0058: Unknown result type (might be due to invalid IL or missing references)
//IL_006d: Unknown result type (might be due to invalid IL or missing references)
//IL_007c: Unknown result type (might be due to invalid IL or missing references)
//IL_0088: Unknown result type (might be due to invalid IL or missing references)
//IL_0098: Unknown result type (might be due to invalid IL or missing references)
//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
//IL_0100: Unknown result type (might be due to invalid IL or missing references)
if (Object.op_Implicit((Object)(object)button) && Object.op_Implicit((Object)(object)version))
{
((Transform)button).SetParent(((Transform)version).parent, false);
button.anchorMin = version.anchorMin;
button.anchorMax = version.anchorMax;
button.pivot = new Vector2(1f, version.pivot.y);
button.sizeDelta = new Vector2(Mathf.Max(220f, button.sizeDelta.x), Mathf.Max(48f, button.sizeDelta.y));
float x = version.anchoredPosition.x;
float num = 1f - version.pivot.x;
Rect rect = version.rect;
float num2 = x + num * ((Rect)(ref rect)).width;
float y = version.anchoredPosition.y;
float num3 = 1f - version.pivot.y;
rect = version.rect;
float num4 = y + num3 * ((Rect)(ref rect)).height;
float y2 = version.pivot.y;
rect = button.rect;
button.anchoredPosition = new Vector2(num2, num4 + y2 * ((Rect)(ref rect)).height + 8f);
}
}
private void OpenMenu()
{
Show();
}
private bool Setup()
{
if (_built)
{
return true;
}
PauseManager val = Object.FindAnyObjectByType<PauseManager>();
GameObject val2 = (Object.op_Implicit((Object)(object)val) ? GetObject(val, "_pauseHolder") : null);
if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val2))
{
return false;
}
if (Object.op_Implicit((Object)(object)_root))
{
Object.Destroy((Object)(object)_root);
_root = null;
}
if (!BuildScreen(val, val2))
{
if (Object.op_Implicit((Object)(object)_root))
{
Object.Destroy((Object)(object)_root);
_root = null;
}
return false;
}
_built = true;
return true;
}
private bool BuildScreen(PauseManager pause, GameObject holder)
{
//IL_0147: Unknown result type (might be due to invalid IL or missing references)
//IL_0151: Expected O, but got Unknown
//IL_0191: Unknown result type (might be due to invalid IL or missing references)
//IL_0198: Expected O, but got Unknown
//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
//IL_0200: Expected O, but got Unknown
//IL_0251: Unknown result type (might be due to invalid IL or missing references)
//IL_0266: Unknown result type (might be due to invalid IL or missing references)
//IL_027b: Unknown result type (might be due to invalid IL or missing references)
//IL_0286: Unknown result type (might be due to invalid IL or missing references)
//IL_029a: Unknown result type (might be due to invalid IL or missing references)
//IL_02a9: Unknown result type (might be due to invalid IL or missing references)
//IL_02ae: Unknown result type (might be due to invalid IL or missing references)
//IL_02bb: Unknown result type (might be due to invalid IL or missing references)
//IL_02c5: Unknown result type (might be due to invalid IL or missing references)
//IL_02d1: Unknown result type (might be due to invalid IL or missing references)
//IL_02e7: Unknown result type (might be due to invalid IL or missing references)
//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
//IL_0309: Unknown result type (might be due to invalid IL or missing references)
//IL_0383: Unknown result type (might be due to invalid IL or missing references)
//IL_038a: Expected O, but got Unknown
//IL_043a: Unknown result type (might be due to invalid IL or missing references)
//IL_0444: Expected O, but got Unknown
//IL_04c3: Unknown result type (might be due to invalid IL or missing references)
//IL_04cd: Expected O, but got Unknown
_pauseHolder = holder;
_mainScreen = GetObject(pause, "_mainScreen");
_optionsScreen = GetObject(pause, "_optionsScreen");
_serverScreen = GetObject(pause, "_serverSettingsScreen");
if (!Object.op_Implicit((Object)(object)_serverScreen))
{
return false;
}
_root = Object.Instantiate<GameObject>(_serverScreen, _pauseHolder.transform, false);
((Object)_root).name = "ModUpdateCheckerScreen";
_root.SetActive(false);
Transform val = _root.transform.Find("PausedText");
Transform val2 = _root.transform.Find("ServerDisplay");
Transform val3 = _root.transform.Find("LowerButtonHolder/BackButton");
if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val2) || !Object.op_Implicit((Object)(object)val3))
{
return false;
}
_title = ((Component)val).GetComponent<TextMeshProUGUI>();
_buttonTemplate = ((Component)val3).gameObject;
Button val4 = UseButton(_buttonTemplate);
_closeLabel = GetText(_buttonTemplate);
if ((Object)(object)_title == (Object)null || (Object)(object)val4 == (Object)null || (Object)(object)_closeLabel == (Object)null)
{
return false;
}
((UnityEvent)val4.onClick).AddListener(new UnityAction(Hide));
DisableExtras(((Component)_title).gameObject);
TextMeshProUGUI componentInChildren = ((Component)val2).GetComponentInChildren<TextMeshProUGUI>(true);
if ((Object)(object)componentInChildren == (Object)null)
{
return false;
}
foreach (Transform item in _root.transform)
{
Transform val5 = item;
if ((Object)(object)val5 != (Object)(object)val && (Object)(object)val5 != (Object)(object)val2 && (Object)(object)val5 != (Object)(object)val3.parent)
{
((Component)val5).gameObject.SetActive(false);
}
}
foreach (Transform item2 in val2)
{
Transform val6 = item2;
if (((Object)val6).name != "Outline")
{
((Component)val6).gameObject.SetActive(false);
}
}
RectTransform component = ((Component)val2).GetComponent<RectTransform>();
component.anchorMin = new Vector2(0.5f, 0.5f);
component.anchorMax = new Vector2(0.5f, 0.5f);
component.pivot = new Vector2(0.5f, 0.5f);
component.anchoredPosition = Vector2.zero;
component.sizeDelta = new Vector2(860f, 580f);
GameObject val7 = new GameObject("ModUpdateContent");
val7.transform.SetParent(val2, false);
RectTransform val8 = val7.AddComponent<RectTransform>();
val8.anchorMin = Vector2.zero;
val8.anchorMax = Vector2.one;
val8.offsetMin = new Vector2(28f, 24f);
val8.offsetMax = new Vector2(-28f, -24f);
((Transform)val8).localScale = Vector3.one;
((Transform)val8).SetAsLastSibling();
VerticalLayoutGroup obj = val7.AddComponent<VerticalLayoutGroup>();
((HorizontalOrVerticalLayoutGroup)obj).spacing = 8f;
((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true;
((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
_summary = AddText(componentInChildren, (Transform)(object)val8, "Summary", 20f, 32f, (TextAlignmentOptions)514);
((TMP_Text)_summary).richText = true;
MakeRows((Transform)(object)val8, componentInChildren);
GameObject val9 = new GameObject("Footer");
val9.transform.SetParent((Transform)(object)val8, false);
val9.AddComponent<LayoutElement>().preferredHeight = 48f;
HorizontalLayoutGroup obj2 = val9.AddComponent<HorizontalLayoutGroup>();
((HorizontalOrVerticalLayoutGroup)obj2).spacing = 8f;
((HorizontalOrVerticalLayoutGroup)obj2).childControlWidth = true;
((HorizontalOrVerticalLayoutGroup)obj2).childControlHeight = true;
((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandWidth = true;
((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandHeight = false;
GameObject val10 = MakeButton(_buttonTemplate, val9.transform, "Check");
_checkButton = GetButton(val10);
_checkLabel = GetText(val10);
if ((Object)(object)_checkButton == (Object)null || (Object)(object)_checkLabel == (Object)null)
{
return false;
}
((UnityEvent)_checkButton.onClick).AddListener(new UnityAction(CheckNow));
(val10.GetComponent<LayoutElement>() ?? val10.AddComponent<LayoutElement>()).preferredHeight = 48f;
GameObject val11 = MakeButton(_buttonTemplate, val9.transform, "Back");
_backButton = GetButton(val11);
_backLabel = GetText(val11);
if ((Object)(object)_backButton == (Object)null || (Object)(object)_backLabel == (Object)null)
{
return false;
}
((UnityEvent)_backButton.onClick).AddListener(new UnityAction(HideCandidates));
(val11.GetComponent<LayoutElement>() ?? val11.AddComponent<LayoutElement>()).preferredHeight = 48f;
val11.SetActive(false);
LocalizationApi.LanguageChanged += OnLanguageChanged;
ModUpdateRuntime.Changed += Refresh;
Refresh();
return true;
}
private void MakeRows(Transform parent, TextMeshProUGUI nativeText)
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Expected O, but got Unknown
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
//IL_005e: Unknown result type (might be due to invalid IL or missing references)
//IL_009a: Unknown result type (might be due to invalid IL or missing references)
//IL_00a0: Expected O, but got Unknown
//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
//IL_0103: Unknown result type (might be due to invalid IL or missing references)
//IL_0118: Unknown result type (might be due to invalid IL or missing references)
//IL_0123: Unknown result type (might be due to invalid IL or missing references)
GameObject val = new GameObject("ModUpdateViewport");
val.transform.SetParent(parent, false);
val.AddComponent<LayoutElement>().preferredHeight = 390f;
RectTransform val2 = val.GetComponent<RectTransform>() ?? val.AddComponent<RectTransform>();
((Transform)val2).localScale = Vector3.one;
((Graphic)val.AddComponent<Image>()).color = new Color(0f, 0f, 0f, 0.08f);
val.AddComponent<RectMask2D>();
ScrollRect obj = val.AddComponent<ScrollRect>();
obj.horizontal = false;
obj.vertical = true;
obj.movementType = (MovementType)1;
obj.scrollSensitivity = 30f;
GameObject val3 = new GameObject("Rows");
val3.transform.SetParent(val.transform, false);
RectTransform val4 = val3.GetComponent<RectTransform>() ?? val3.AddComponent<RectTransform>();
val4.anchorMin = new Vector2(0f, 1f);
val4.anchorMax = new Vector2(1f, 1f);
val4.pivot = new Vector2(0.5f, 1f);
val4.anchoredPosition = Vector2.zero;
val4.sizeDelta = new Vector2(0f, 0f);
((Transform)val4).localScale = Vector3.one;
VerticalLayoutGroup obj2 = val3.AddComponent<VerticalLayoutGroup>();
((HorizontalOrVerticalLayoutGroup)obj2).spacing = 4f;
((HorizontalOrVerticalLayoutGroup)obj2).childControlWidth = true;
((HorizontalOrVerticalLayoutGroup)obj2).childControlHeight = true;
((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandWidth = true;
((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandHeight = false;
ContentSizeFitter obj3 = val3.AddComponent<ContentSizeFitter>();
obj3.horizontalFit = (FitMode)0;
obj3.verticalFit = (FitMode)2;
obj.viewport = val2;
obj.content = val4;
_rows = val3.transform;
}
private void Show()
{
if (!_built && !Setup())
{
return;
}
if (_open)
{
Refresh();
return;
}
_open = true;
_candidateEntry = null;
PauseManager.TogglePause(true);
HideNative();
_root.SetActive(true);
Refresh();
if ((Object)(object)EventSystem.current != (Object)null && (Object)(object)_checkButton != (Object)null)
{
EventSystem.current.SetSelectedGameObject(((Component)_checkButton).gameObject);
}
}
private void Hide()
{
if (_open)
{
_open = false;
_candidateEntry = null;
if (Object.op_Implicit((Object)(object)_root))
{
_root.SetActive(false);
}
PauseManager.TogglePause(false);
}
}
private void CheckNow()
{
_candidateEntry = null;
ModUpdateRuntime.Check(manual: true);
}
private void ShowCandidates(ModUpdateEntry entry)
{
if (entry != null && entry.Candidates != null && entry.Candidates.Count != 0)
{
_candidateEntry = entry;
Refresh();
SelectFirstButton();
}
}
private void HideCandidates()
{
if (_candidateEntry != null)
{
_candidateEntry = null;
Refresh();
if ((Object)(object)EventSystem.current != (Object)null && (Object)(object)_checkButton != (Object)null)
{
EventSystem.current.SetSelectedGameObject(((Component)_checkButton).gameObject);
}
}
}
private void HideNative()
{
if (Object.op_Implicit((Object)(object)_mainScreen))
{
_mainScreen.SetActive(false);
}
if (Object.op_Implicit((Object)(object)_optionsScreen))
{
_optionsScreen.SetActive(false);
}
if (Object.op_Implicit((Object)(object)_serverScreen))
{
_serverScreen.SetActive(false);
}
}
private void Refresh()
{
if ((Object)(object)_title == (Object)null || (Object)(object)_summary == (Object)null || (Object)(object)_checkLabel == (Object)null || (Object)(object)_closeLabel == (Object)null || (Object)(object)_backLabel == (Object)null || (Object)(object)_rows == (Object)null)
{
return;
}
if (_candidateEntry != null)
{
bool flag = false;
IReadOnlyList<ModUpdateEntry> results = ModUpdateRuntime.Results;
for (int i = 0; i < results.Count; i++)
{
if (results[i] == _candidateEntry)
{
flag = true;
break;
}
}
if (!flag)
{
_candidateEntry = null;
}
}
bool flag2 = _candidateEntry != null;
((TMP_Text)_title).text = (flag2 ? I18n.Text("ui.choose_package") : I18n.Text("ui.title"));
((TMP_Text)_checkLabel).text = I18n.Text("ui.check");
((TMP_Text)_closeLabel).text = I18n.Text("ui.close");
((TMP_Text)_backLabel).text = I18n.Text("ui.back");
((Component)_checkButton).gameObject.SetActive(!flag2);
((Component)_backButton).gameObject.SetActive(flag2);
((Selectable)_checkButton).interactable = !ModUpdateRuntime.IsChecking;
IReadOnlyList<ModUpdateEntry> results2 = ModUpdateRuntime.Results;
int num = 0;
for (int j = 0; j < results2.Count; j++)
{
if (ModUpdateRuntime.HasUpdate(results2[j]))
{
num++;
}
}
if (flag2)
{
((TMP_Text)_summary).text = I18n.Text("ui.choose_summary", _candidateEntry.Name, _candidateEntry.Candidates.Count);
}
else if (ModUpdateRuntime.IsChecking)
{
((TMP_Text)_summary).text = I18n.Text("ui.summary.checking");
}
else if (results2.Count == 0)
{
((TMP_Text)_summary).text = I18n.Text("ui.summary.empty");
}
else if (num > 0)
{
((TMP_Text)_summary).text = I18n.Text("ui.summary.update_alert", num) + "\n" + I18n.Text("ui.summary.ready", ModUpdateRuntime.LastChecked ?? "-", num);
}
else
{
((TMP_Text)_summary).text = I18n.Text("ui.summary.ready", ModUpdateRuntime.LastChecked ?? "-", num);
}
(((Component)_summary).GetComponent<LayoutElement>() ?? ((Component)_summary).gameObject.AddComponent<LayoutElement>()).preferredHeight = (flag2 ? 48f : ((num > 0 && !ModUpdateRuntime.IsChecking) ? 60f : 32f));
if (flag2)
{
RefreshCandidates(_candidateEntry);
}
else
{
RefreshRows(results2);
}
}
private void RefreshRows(IReadOnlyList<ModUpdateEntry> entries)
{
//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
//IL_00a8: Expected O, but got Unknown
//IL_03f1: Unknown result type (might be due to invalid IL or missing references)
//IL_03fb: Expected O, but got Unknown
for (int num = _rows.childCount - 1; num >= 0; num--)
{
Object.Destroy((Object)(object)((Component)_rows.GetChild(num)).gameObject);
}
if (entries.Count == 0)
{
((TMP_Text)AddText(GetText(_buttonTemplate), _rows, "Empty", 20f, 44f, (TextAlignmentOptions)514)).text = I18n.Text("ui.none");
return;
}
for (int i = 0; i < entries.Count; i++)
{
ModUpdateEntry modUpdateEntry = entries[i];
GameObject val = new GameObject("ModUpdateRow " + modUpdateEntry.Guid);
val.transform.SetParent(_rows, false);
float num2 = (val.AddComponent<LayoutElement>().preferredHeight = ((modUpdateEntry.State == ModUpdateState.Ambiguous) ? Mathf.Max(82f, 58f + (float)modUpdateEntry.Candidates.Count * 28f) : 62f));
HorizontalLayoutGroup obj = val.AddComponent<HorizontalLayoutGroup>();
((HorizontalOrVerticalLayoutGroup)obj).spacing = 8f;
((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false;
((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
string text = ModUpdateRuntime.StatusText(modUpdateEntry);
if (ModUpdateRuntime.HasUpdate(modUpdateEntry))
{
text = "<color=#FFD166><b>" + text + "</b></color>";
}
string text2 = modUpdateEntry.Name + "\n" + I18n.Text("ui.local", modUpdateEntry.LocalVersion) + " | " + text;
if (!string.IsNullOrWhiteSpace(modUpdateEntry.Team) && !string.IsNullOrWhiteSpace(modUpdateEntry.Package))
{
text2 = text2 + "\n" + I18n.Text("ui.package", modUpdateEntry.Team, modUpdateEntry.Package);
}
if (modUpdateEntry.State == ModUpdateState.Ambiguous)
{
for (int j = 0; j < modUpdateEntry.Candidates.Count; j++)
{
ModUpdateCandidate modUpdateCandidate = modUpdateEntry.Candidates[j];
string text3 = ModUpdateRuntime.StatusText(modUpdateCandidate);
if (modUpdateCandidate.State == ModUpdateState.Update)
{
text3 = "<color=#FFD166><b>" + text3 + "</b></color>";
}
string text4 = (string.IsNullOrWhiteSpace(modUpdateCandidate.LatestVersion) ? string.Empty : (" | " + I18n.Text("ui.latest", modUpdateCandidate.LatestVersion)));
text2 = text2 + "\n" + I18n.Text("ui.package", modUpdateCandidate.Team, modUpdateCandidate.Package) + text4 + " | " + text3;
}
}
TextMeshProUGUI val2 = AddText(GetText(_buttonTemplate), val.transform, "Details", 16f, num2, (TextAlignmentOptions)513);
((TMP_Text)val2).text = text2;
((TMP_Text)val2).richText = true;
(((Component)val2).GetComponent<LayoutElement>() ?? ((Component)val2).gameObject.AddComponent<LayoutElement>()).flexibleWidth = 1f;
GameObject val3 = MakeButton(_buttonTemplate, val.transform, "Open");
Button button = GetButton(val3);
TextMeshProUGUI text5 = GetText(val3);
if ((Object)(object)button == (Object)null || (Object)(object)text5 == (Object)null)
{
Object.Destroy((Object)(object)val3);
continue;
}
((TMP_Text)text5).text = I18n.Text("ui.open");
((Selectable)button).interactable = ((modUpdateEntry.State == ModUpdateState.Ambiguous) ? (modUpdateEntry.Candidates.Count > 0) : (!string.IsNullOrWhiteSpace(modUpdateEntry.PackageUrl)));
LayoutElement obj2 = val3.GetComponent<LayoutElement>() ?? val3.AddComponent<LayoutElement>();
obj2.preferredWidth = 110f;
obj2.preferredHeight = num2;
ModUpdateEntry selected = modUpdateEntry;
((UnityEvent)button.onClick).AddListener((UnityAction)delegate
{
if (selected.State == ModUpdateState.Ambiguous)
{
ShowCandidates(selected);
}
else
{
ModUpdateRuntime.OpenPackage(selected);
}
});
}
}
private void RefreshCandidates(ModUpdateEntry entry)
{
//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
//IL_00c1: Expected O, but got Unknown
//IL_02cf: Unknown result type (might be due to invalid IL or missing references)
//IL_02d9: Expected O, but got Unknown
for (int num = _rows.childCount - 1; num >= 0; num--)
{
Object.Destroy((Object)(object)((Component)_rows.GetChild(num)).gameObject);
}
if (entry == null || entry.Candidates == null || entry.Candidates.Count == 0)
{
((TMP_Text)AddText(GetText(_buttonTemplate), _rows, "Empty", 20f, 44f, (TextAlignmentOptions)514)).text = I18n.Text("ui.none");
return;
}
for (int i = 0; i < entry.Candidates.Count; i++)
{
ModUpdateCandidate modUpdateCandidate = entry.Candidates[i];
GameObject val = new GameObject("ModUpdateCandidate " + modUpdateCandidate.Team + " " + modUpdateCandidate.Package);
val.transform.SetParent(_rows, false);
val.AddComponent<LayoutElement>().preferredHeight = 84f;
HorizontalLayoutGroup obj = val.AddComponent<HorizontalLayoutGroup>();
((HorizontalOrVerticalLayoutGroup)obj).spacing = 8f;
((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false;
((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
string text = ModUpdateRuntime.StatusText(modUpdateCandidate);
if (modUpdateCandidate.State == ModUpdateState.Update)
{
text = "<color=#FFD166><b>" + text + "</b></color>";
}
string text2 = I18n.Text("ui.package", modUpdateCandidate.Team, modUpdateCandidate.Package) + "\n" + I18n.Text("ui.local", entry.LocalVersion);
if (!string.IsNullOrWhiteSpace(modUpdateCandidate.LatestVersion))
{
text2 = text2 + " | " + I18n.Text("ui.latest", modUpdateCandidate.LatestVersion);
}
text2 = text2 + "\n" + text;
TextMeshProUGUI val2 = AddText(GetText(_buttonTemplate), val.transform, "Details", 15f, 84f, (TextAlignmentOptions)513);
((TMP_Text)val2).text = text2;
((TMP_Text)val2).richText = true;
(((Component)val2).GetComponent<LayoutElement>() ?? ((Component)val2).gameObject.AddComponent<LayoutElement>()).flexibleWidth = 1f;
GameObject val3 = MakeButton(_buttonTemplate, val.transform, "Open");
Button button = GetButton(val3);
TextMeshProUGUI text3 = GetText(val3);
if ((Object)(object)button == (Object)null || (Object)(object)text3 == (Object)null)
{
Object.Destroy((Object)(object)val3);
continue;
}
((TMP_Text)text3).text = I18n.Text("ui.open");
((Selectable)button).interactable = !string.IsNullOrWhiteSpace(modUpdateCandidate.PackageUrl);
LayoutElement obj2 = val3.GetComponent<LayoutElement>() ?? val3.AddComponent<LayoutElement>();
obj2.preferredWidth = 110f;
obj2.preferredHeight = 84f;
ModUpdateCandidate selected = modUpdateCandidate;
((UnityEvent)button.onClick).AddListener((UnityAction)delegate
{
ModUpdateRuntime.OpenPackage(selected);
});
}
}
private void SelectFirstButton()
{
if (!((Object)(object)EventSystem.current == (Object)null) && !((Object)(object)_rows == (Object)null))
{
Button componentInChildren = ((Component)_rows).GetComponentInChildren<Button>(true);
if ((Object)(object)componentInChildren != (Object)null && ((Selectable)componentInChildren).interactable)
{
EventSystem.current.SetSelectedGameObject(((Component)componentInChildren).gameObject);
}
}
}
private void OnLanguageChanged(string language)
{
Refresh();
RefreshMenuButton();
}
private void LateUpdate()
{
if (MainMenuManager.IsInMenu)
{
RefreshMenuButton();
}
}
private void RefreshMenuButton()
{
if (!Object.op_Implicit((Object)(object)_mainMenuButton))
{
return;
}
TextMeshProUGUI text = GetText(_mainMenuButton);
if (!Object.op_Implicit((Object)(object)text))
{
return;
}
int num = 0;
IReadOnlyList<ModUpdateEntry> results = ModUpdateRuntime.Results;
for (int i = 0; i < results.Count; i++)
{
if (ModUpdateRuntime.HasUpdate(results[i]))
{
num++;
}
}
((TMP_Text)text).richText = true;
((TMP_Text)text).text = ((num > 0) ? I18n.Text("ui.main_menu_button_updates", num) : I18n.Text("ui.main_menu_button"));
}
private void OnDestroy()
{
LocalizationApi.LanguageChanged -= OnLanguageChanged;
ModUpdateRuntime.Changed -= Refresh;
if ((Object)(object)_current == (Object)(object)this)
{
_current = null;
}
}
private static TextMeshProUGUI AddText(TextMeshProUGUI template, Transform parent, string name, float size, float height, TextAlignmentOptions alignment)
{
//IL_0034: Unknown result type (might be due to invalid IL or missing references)
TextMeshProUGUI val = Object.Instantiate<TextMeshProUGUI>(template, parent, false);
((Object)((Component)val).gameObject).name = name;
((Component)val).gameObject.SetActive(true);
DisableExtras(((Component)val).gameObject);
((TMP_Text)val).fontSize = size;
((TMP_Text)val).alignment = alignment;
((Graphic)val).raycastTarget = false;
((TMP_Text)val).textWrappingMode = (TextWrappingModes)1;
(((Component)val).GetComponent<LayoutElement>() ?? ((Component)val).gameObject.AddComponent<LayoutElement>()).preferredHeight = height;
return val;
}
private static GameObject MakeButton(GameObject template, Transform parent, string name)
{
GameObject obj = Object.Instantiate<GameObject>(template, parent, false);
((Object)obj).name = name;
obj.SetActive(true);
UseButton(obj);
TextMeshProUGUI text = GetText(obj);
if ((Object)(object)text != (Object)null)
{
((TMP_Text)text).text = string.Empty;
}
return obj;
}
private static Button UseButton(GameObject buttonObject)
{
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Expected O, but got Unknown
//IL_002c: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
Button val = (((Object)(object)buttonObject == (Object)null) ? null : buttonObject.GetComponent<Button>());
if ((Object)(object)val == (Object)null)
{
return null;
}
val.onClick = new ButtonClickedEvent();
Navigation navigation = default(Navigation);
((Navigation)(ref navigation)).mode = (Mode)3;
((Selectable)val).navigation = navigation;
return val;
}
private static Button GetButton(GameObject root)
{
Button[] array = (((Object)(object)root == (Object)null) ? Array.Empty<Button>() : root.GetComponents<Button>());
for (int num = array.Length - 1; num >= 0; num--)
{
if (((Behaviour)array[num]).enabled)
{
return array[num];
}
}
return null;
}
private static TextMeshProUGUI GetText(GameObject root)
{
if (!((Object)(object)root == (Object)null))
{
return root.GetComponentInChildren<TextMeshProUGUI>(true);
}
return null;
}
private static void DisableExtras(GameObject root)
{
if ((Object)(object)root == (Object)null)
{
return;
}
MonoBehaviour[] componentsInChildren = root.GetComponentsInChildren<MonoBehaviour>(true);
foreach (MonoBehaviour val in componentsInChildren)
{
if (!(val is Graphic) && !(val is Selectable) && !(val is TMP_Text) && !(val is Shadow) && !(val is UIButton) && !(val is LayoutGroup) && !(val is LayoutElement) && !(val is ContentSizeFitter) && !(val is RectMask2D))
{
((Behaviour)val).enabled = false;
}
}
}
private static GameObject GetObject(PauseManager pause, string field)
{
return GetField<GameObject>(pause, field);
}
private static T GetField<T>(object instance, string field)
{
if (instance == null)
{
return default(T);
}
FieldInfo field2 = instance.GetType().GetField(field, PrivateInstance);
if (!(field2 == null))
{
return (T)field2.GetValue(instance);
}
return default(T);
}
private static T GetStatic<T>(Type type, string field) where T : class
{
FieldInfo field2 = type.GetField(field, BindingFlags.Static | BindingFlags.NonPublic);
if (!(field2 == null))
{
return field2.GetValue(null) as T;
}
return null;
}
}
}
namespace ModUpdateChecker.Runtime
{
internal enum ModUpdateState
{
Checking,
Current,
Update,
NotFound,
Ambiguous,
Error,
Deprecated,
NoRelease
}
internal sealed class ModUpdateEntry
{
internal string Guid;
internal string Name;
internal string LocalVersion;
internal string Team;
internal string Package;
internal string LatestVersion;
internal string PackageUrl;
internal ModUpdateState State;
internal string Error;
internal List<ModUpdateCandidate> Candidates = new List<ModUpdateCandidate>();
}
internal sealed class ModUpdateCandidate
{
internal string Team;
internal string Package;
internal string LatestVersion;
internal string PackageUrl;
internal ModUpdateState State;
internal string Error;
}
internal static class ModUpdateRuntime
{
private sealed class CandidateScore
{
internal ListingPackage Package;
internal int Score;
internal int NameScore;
}
private sealed class MappingCache
{
[JsonProperty("version")]
public int Version { get; set; }
public Dictionary<string, string> Mappings { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
private sealed class VersionParts
{
internal readonly int[] Core = new int[3];
internal readonly List<string> Pre = new List<string>();
internal static VersionParts Parse(string value)
{
VersionParts versionParts = new VersionParts();
string text = (value ?? string.Empty).Trim().TrimStart('v', 'V');
int num = text.IndexOf('+');
if (num >= 0)
{
text = text.Substring(0, num);
}
string[] array = text.Split(new char[1] { '-' }, 2);
string[] array2 = array[0].Split(new char[1] { '.' });
for (int i = 0; i < Math.Min(3, array2.Length); i++)
{
if (int.TryParse(array2[i], out var result))
{
versionParts.Core[i] = result;
}
}
if (array.Length > 1)
{
versionParts.Pre.AddRange(array[1].Split(new char[1] { '.' }));
}
return versionParts;
}
}
private sealed class ListingPage
{
[JsonProperty("next")]
public string Next { get; set; }
[JsonProperty("results")]
public List<ListingPackage> Results { get; set; }
}
private sealed class ListingPackage
{
[JsonProperty("community_identifier")]
public string CommunityIdentifier { get; set; }
[JsonProperty("namespace")]
public string Namespace { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
}
private sealed class PackageDetails
{
[JsonProperty("namespace")]
public string Namespace { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("community_listings")]
public List<CommunityListing> CommunityListings { get; set; }
[JsonProperty("is_deprecated")]
public bool IsDeprecated { get; set; }
[JsonProperty("latest")]
public PackageVersion Latest { get; set; }
}
private sealed class CommunityListing
{
[JsonProperty("community")]
public string Community { get; set; }
}
private sealed class PackageVersion
{
[JsonProperty("version_number")]
public string VersionNumber { get; set; }
}
private const string Community = "how-to-fish";
private const string ApiRoot = "https://thunderstore.io/";
private const string CacheFileName = "IceBoxStudio.HowToFish.ModUpdateChecker.json";
private const int CacheVersion = 1;
private static readonly TimeSpan SessionCacheTime = TimeSpan.FromHours(2.0);
private static readonly List<ModUpdateEntry> Entries = new List<ModUpdateEntry>();
private static readonly Dictionary<string, string> CachedMappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<string, ModUpdateEntry> SessionResults = new Dictionary<string, ModUpdateEntry>(StringComparer.OrdinalIgnoreCase);
private static MonoBehaviour _host;
private static Coroutine _check;
private static bool _startupStarted;
private static float _startupAt;
private static string _lastChecked;
private static DateTime _sessionCachedAt;
internal static IReadOnlyList<ModUpdateEntry> Results => Entries;
internal static bool IsChecking => _check != null;
internal static string LastChecked => _lastChecked;
internal static event Action Changed;
internal static bool HasUpdate(ModUpdateEntry entry)
{
if (entry != null)
{
if (entry.State != ModUpdateState.Update)
{
return entry.Candidates.Any((ModUpdateCandidate candidate) => candidate.State == ModUpdateState.Update);
}
return true;
}
return false;
}
internal static void Init(MonoBehaviour host)
{
_host = host;
_startupAt = Time.unscaledTime + ConfigManager.StartupDelay;
LoadMap();
}
internal static void Update()
{
ModUpdateUi.Update();
if (!_startupStarted && ConfigManager.CheckOnStartup && Time.unscaledTime >= _startupAt)
{
_startupStarted = true;
Check(manual: false);
}
}
internal static void Check(bool manual)
{
if (_check != null || (Object)(object)_host == (Object)null)
{
return;
}
if (manual)
{
_startupStarted = true;
}
Scan();
if (!CacheIsFresh())
{
SessionResults.Clear();
}
bool flag = true;
for (int i = 0; i < Entries.Count; i++)
{
if (!RestoreSession(Entries[i]))
{
flag = false;
}
}
if (flag)
{
Notify();
return;
}
for (int j = 0; j < Entries.Count; j++)
{
Entries[j].State = ModUpdateState.Checking;
Entries[j].Error = null;
}
Notify();
_check = _host.StartCoroutine(RunCheck());
}
internal static string StatusText(ModUpdateEntry entry)
{
return entry.State switch
{
ModUpdateState.Checking => I18n.Text("ui.checking"),
ModUpdateState.Current => I18n.Text("ui.current"),
ModUpdateState.Update => I18n.Text("ui.update", entry.LatestVersion),
ModUpdateState.NotFound => I18n.Text("ui.not_found"),
ModUpdateState.Ambiguous => I18n.Text("ui.ambiguous", entry.Candidates.Count),
ModUpdateState.Deprecated => I18n.Text("ui.deprecated"),
ModUpdateState.NoRelease => I18n.Text("ui.no_release"),
_ => I18n.Text("ui.error"),
};
}
internal static string StatusText(ModUpdateCandidate candidate)
{
if (candidate == null)
{
return I18n.Text("ui.error");
}
return candidate.State switch
{
ModUpdateState.Checking => I18n.Text("ui.checking"),
ModUpdateState.Current => I18n.Text("ui.current"),
ModUpdateState.Update => I18n.Text("ui.update", candidate.LatestVersion),
ModUpdateState.NotFound => I18n.Text("ui.not_found"),
ModUpdateState.Deprecated => I18n.Text("ui.deprecated"),
ModUpdateState.NoRelease => I18n.Text("ui.no_release"),
_ => I18n.Text("ui.error"),
};
}
internal static void OpenPackage(ModUpdateEntry entry)
{
if (entry != null && !string.IsNullOrWhiteSpace(entry.PackageUrl))
{
Application.OpenURL(entry.PackageUrl);
}
}
internal static void OpenPackage(ModUpdateCandidate candidate)
{
if (candidate != null && !string.IsNullOrWhiteSpace(candidate.PackageUrl))
{
Application.OpenURL(candidate.PackageUrl);
}
}
private static IEnumerator RunCheck()
{
for (int i = 0; i < Entries.Count; i++)
{
yield return CheckMod(Entries[i]);
CacheSession(Entries[i]);
}
_lastChecked = DateTime.Now.ToString("yyyy-MM-dd HH:mm");
_sessionCachedAt = DateTime.UtcNow;
_check = null;
SaveMap();
Notify();
}
private static IEnumerator CheckMod(ModUpdateEntry entry)
{
if (RestoreSession(entry))
{
Notify();
yield break;
}
if (!ConfigManager.TryGetMapping(entry.Guid, out var team, out var package) && !GetMapping(entry.Guid, out team, out package))
{
List<ListingPackage> candidates = null;
yield return FindPackages(entry.Name, delegate(List<ListingPackage> result)
{
candidates = result;
}, delegate(string error)
{
entry.Error = error;
});
ListingPackage listingPackage = ((candidates == null) ? null : PickCandidate(entry, candidates));
string text = OtherName(entry.Guid, entry.Name);
if (!string.Equals(text, entry.Name, StringComparison.OrdinalIgnoreCase) && (candidates == null || candidates.Count == 0 || listingPackage == null))
{
List<ListingPackage> alternateCandidates = null;
yield return FindPackages(text, delegate(List<ListingPackage> result)
{
alternateCandidates = result;
}, delegate(string error)
{
entry.Error = error;
});
if (alternateCandidates != null)
{
if (candidates == null)
{
candidates = alternateCandidates;
}
else
{
candidates = (from @group in candidates.Concat(alternateCandidates).GroupBy<ListingPackage, string>((ListingPackage candidate) => candidate.Namespace + "/" + candidate.Name, StringComparer.OrdinalIgnoreCase)
select @group.First()).ToList();
}
}
}
if (candidates == null || candidates.Count == 0)
{
entry.State = (string.IsNullOrEmpty(entry.Error) ? ModUpdateState.NotFound : ModUpdateState.Error);
Notify();
yield break;
}
listingPackage = PickCandidate(entry, candidates);
if (listingPackage == null)
{
entry.State = ModUpdateState.Ambiguous;
entry.Candidates = (from candidate in SortCandidates(entry, candidates)
select new ModUpdateCandidate
{
Team = candidate.Namespace,
Package = candidate.Name,
State = ModUpdateState.Checking
}).ToList();
Notify();
for (int i = 0; i < entry.Candidates.Count; i++)
{
ModUpdateCandidate modUpdateCandidate = entry.Candidates[i];
yield return ReadCandidate(modUpdateCandidate, entry.LocalVersion, modUpdateCandidate.Team, modUpdateCandidate.Package);
Notify();
}
yield break;
}
team = listingPackage.Namespace;
package = listingPackage.Name;
CachedMappings[entry.Guid] = team + "/" + package;
}
entry.Team = team;
entry.Package = package;
yield return ReadPackage(entry, team, package);
Notify();
}
private static IEnumerator FindPackages(string name, Action<List<ListingPackage>> success, Action<string> failure)
{
string text = "https://thunderstore.io/api/cyberstorm/listing/how-to-fish/?page=1&q=" + Uri.EscapeDataString(name ?? string.Empty);
List<ListingPackage> all = new List<ListingPackage>();
HashSet<string> visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
int pages = 0;
while (!string.IsNullOrWhiteSpace(text) && pages++ < 100 && visited.Add(text))
{
string body = null;
string error = null;
yield return Get(text, delegate(string value)
{
body = value;
}, delegate(string value)
{
error = value;
});
if (body == null)
{
failure(error ?? "Thunderstore request failed.");
yield break;
}
ListingPage listingPage;
try
{
listingPage = JsonConvert.DeserializeObject<ListingPage>(body);
}
catch (Exception ex)
{
failure(ex.Message);
yield break;
}
if (listingPage == null)
{
break;
}
if (listingPage.Results != null)
{
all.AddRange(listingPage.Results.Where(IsHowToFishPackage));
}
text = listingPage.Next;
}
success(all);
}
private static IEnumerator ReadPackage(ModUpdateEntry entry, string team, string package)
{
PackageDetails details = null;
string error = null;
yield return GetPackage(team, package, delegate(PackageDetails value)
{
details = value;
}, delegate(string value)
{
error = value;
});
if (details == null)
{
entry.State = ModUpdateState.Error;
entry.Error = error;
yield break;
}
try
{
if (details == null || details.CommunityListings == null || !details.CommunityListings.Any((CommunityListing listing) => listing != null && string.Equals(listing.Community, "how-to-fish", StringComparison.OrdinalIgnoreCase)))
{
entry.State = ModUpdateState.NotFound;
yield break;
}
if (details.Latest == null || string.IsNullOrWhiteSpace(details.Latest.VersionNumber))
{
entry.State = ModUpdateState.NoRelease;
yield break;
}
entry.Team = (string.IsNullOrWhiteSpace(details.Namespace) ? team : details.Namespace);
entry.Package = (string.IsNullOrWhiteSpace(details.Name) ? package : details.Name);
entry.LatestVersion = details.Latest.VersionNumber;
entry.PackageUrl = "https://thunderstore.io/c/how-to-fish/p/" + Uri.EscapeDataString(entry.Team) + "/" + Uri.EscapeDataString(entry.Package) + "/";
if (details.IsDeprecated)
{
entry.State = ModUpdateState.Deprecated;
yield break;
}
int num = Compare(entry.LocalVersion, entry.LatestVersion);
entry.State = ((num >= 0) ? ModUpdateState.Current : ModUpdateState.Update);
}
catch (Exception ex)
{
entry.State = ModUpdateState.Error;
entry.Error = ex.Message;
}
}
private static IEnumerator ReadCandidate(ModUpdateCandidate candidate, string localVersion, string team, string package)
{
PackageDetails details = null;
string error = null;
yield return GetPackage(team, package, delegate(PackageDetails value)
{
details = value;
}, delegate(string value)
{
error = value;
});
if (details == null)
{
candidate.State = ModUpdateState.Error;
candidate.Error = error;
yield break;
}
try
{
if (details.CommunityListings == null || !details.CommunityListings.Any((CommunityListing listing) => listing != null && string.Equals(listing.Community, "how-to-fish", StringComparison.OrdinalIgnoreCase)))
{
candidate.State = ModUpdateState.NotFound;
yield break;
}
candidate.Team = (string.IsNullOrWhiteSpace(details.Namespace) ? team : details.Namespace);
candidate.Package = (string.IsNullOrWhiteSpace(details.Name) ? package : details.Name);
candidate.PackageUrl = "https://thunderstore.io/c/how-to-fish/p/" + Uri.EscapeDataString(candidate.Team) + "/" + Uri.EscapeDataString(candidate.Package) + "/";
if (details.Latest == null || string.IsNullOrWhiteSpace(details.Latest.VersionNumber))
{
candidate.State = ModUpdateState.NoRelease;
yield break;
}
candidate.LatestVersion = details.Latest.VersionNumber;
candidate.State = (details.IsDeprecated ? ModUpdateState.Deprecated : ((Compare(localVersion, candidate.LatestVersion) >= 0) ? ModUpdateState.Current : ModUpdateState.Update));
}
catch (Exception ex)
{
candidate.State = ModUpdateState.Error;
candidate.Error = ex.Message;
}
}
private static IEnumerator GetPackage(string team, string package, Action<PackageDetails> success, Action<string> failure)
{
string url = "https://thunderstore.io/api/experimental/package/" + Uri.EscapeDataString(team) + "/" + Uri.EscapeDataString(package) + "/";
string body = null;
string error = null;
yield return Get(url, delegate(string value)
{
body = value;
}, delegate(string value)
{
error = value;
});
if (body == null)
{
failure(error ?? "Thunderstore request failed.");
yield break;
}
try
{
success(JsonConvert.DeserializeObject<PackageDetails>(body));
}
catch (Exception ex)
{
failure(ex.Message);
}
}
private static IEnumerator Get(string url, Action<string> success, Action<string> failure)
{
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result) || result.Scheme != Uri.UriSchemeHttps || !string.Equals(result.Host, "thunderstore.io", StringComparison.OrdinalIgnoreCase))
{
failure("Invalid Thunderstore URL.");
yield break;
}
UnityWebRequest request = UnityWebRequest.Get(result);
try
{
request.timeout = ConfigManager.RequestTimeout;
yield return request.SendWebRequest();
if ((int)request.result == 2 || (int)request.result == 3)
{
failure(request.error ?? "Thunderstore request failed.");
yield break;
}
success((request.downloadHandler == null) ? string.Empty : request.downloadHandler.text);
}
finally
{
((IDisposable)request)?.Dispose();
}
}
private static void Scan()
{
Entries.Clear();
foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos)
{
BepInPlugin val = ((pluginInfo.Value == null) ? null : pluginInfo.Value.Metadata);
if (val != null && !string.Equals(val.GUID, "IceBoxStudio.HowToFish.ModUpdateChecker", StringComparison.OrdinalIgnoreCase))
{
Entries.Add(new ModUpdateEntry
{
Guid = (string.IsNullOrWhiteSpace(val.GUID) ? pluginInfo.Key : val.GUID),
Name = (string.IsNullOrWhiteSpace(val.Name) ? pluginInfo.Key : val.Name),
LocalVersion = ((val.Version == null) ? "0.0.0" : val.Version.ToString())
});
}
}
Entries.Sort((ModUpdateEntry left, ModUpdateEntry right) => string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
}
private static ListingPackage PickCandidate(ModUpdateEntry entry, List<ListingPackage> candidates)
{
List<CandidateScore> list = ScoreCandidates(entry, candidates);
if (list.Count == 0)
{
return null;
}
string guidTail = GuidName(entry.Guid);
List<CandidateScore> list2 = list.Where((CandidateScore value) => string.Equals(Clean(value.Package.Name), guidTail, StringComparison.Ordinal)).ToList();
if (list2.Count == 1)
{
return list2[0].Package;
}
if (list[0].NameScore < 55)
{
return null;
}
if (list.Count > 1)
{
CandidateScore candidateScore = list[0];
CandidateScore candidateScore2 = list[1];
if (candidateScore.NameScore - candidateScore2.NameScore <= 3 && candidateScore.Score - candidateScore2.Score < 20)
{
return null;
}
}
return list[0].Package;
}
private static List<ListingPackage> SortCandidates(ModUpdateEntry entry, List<ListingPackage> candidates)
{
return (from value in ScoreCandidates(entry, candidates)
select value.Package).ToList();
}
private static List<CandidateScore> ScoreCandidates(ModUpdateEntry entry, List<ListingPackage> candidates)
{
return (from value in candidates.GroupBy<ListingPackage, string>((ListingPackage candidate) => candidate.Namespace + "/" + candidate.Name, StringComparer.OrdinalIgnoreCase).Select(delegate(IGrouping<string, ListingPackage> @group)
{
ListingPackage listingPackage = @group.First();
return new CandidateScore
{
Package = listingPackage,
NameScore = Similarity(Clean(entry.Name), Clean(listingPackage.Name)),
Score = Score(entry, listingPackage)
};
})
orderby value.Score descending, value.NameScore descending
select value).ToList();
}
private static int Score(ModUpdateEntry entry, ListingPackage package)
{
string text = Clean(entry.Name);
string text2 = Clean(package.Name);
int num = Similarity(text, text2) * 2;
if (text != text2 && (text2.Contains(text) || text.Contains(text2)))
{
num += 20;
}
if (GuidName(entry.Guid) == text2)
{
num += 25;
}
if (Clean(entry.Guid.Substring(0, Math.Max(0, entry.Guid.LastIndexOf('.')))).Contains(Clean(package.Namespace)))
{
num += 15;
}
return num;
}
private static string GuidName(string guid)
{
if (string.IsNullOrWhiteSpace(guid))
{
return string.Empty;
}
int num = guid.LastIndexOf('.');
return Clean((num < 0) ? guid : guid.Substring(num + 1));
}
private static int Similarity(string left, string right)
{
if (left.Length == 0 || right.Length == 0)
{
return 0;
}
int[] array = new int[right.Length + 1];
int[] array2 = new int[right.Length + 1];
for (int i = 0; i <= right.Length; i++)
{
array[i] = i;
}
for (int j = 1; j <= left.Length; j++)
{
array2[0] = j;
for (int k = 1; k <= right.Length; k++)
{
int num = ((left[j - 1] != right[k - 1]) ? 1 : 0);
array2[k] = Math.Min(Math.Min(array2[k - 1] + 1, array[k] + 1), array[k - 1] + num);
}
int[] array3 = array;
array = array2;
array2 = array3;
}
int num2 = Math.Max(left.Length, right.Length);
return (num2 - array[right.Length]) * 100 / num2;
}
private static bool IsHowToFishPackage(ListingPackage package)
{
if (package != null && string.Equals(package.CommunityIdentifier, "how-to-fish", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(package.Namespace))
{
return !string.IsNullOrWhiteSpace(package.Name);
}
return false;
}
private static string OtherName(string guid, string name)
{
int num = guid?.LastIndexOf('.') ?? (-1);
if (num >= 0 && num < guid.Length - 1)
{
return guid.Substring(num + 1);
}
return name ?? string.Empty;
}
private static string Clean(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}
StringBuilder stringBuilder = new StringBuilder(value.Length);
for (int i = 0; i < value.Length; i++)
{
char c = char.ToLowerInvariant(value[i]);
if (char.IsLetterOrDigit(c))
{
stringBuilder.Append(c);
}
}
return stringBuilder.ToString();
}
private static bool GetMapping(string guid, out string team, out string package)
{
team = null;
package = null;
if (!CachedMappings.TryGetValue(guid, out var value))
{
return false;
}
int num = value.IndexOf('/');
if (num <= 0 || num >= value.Length - 1)
{
return false;
}
team = value.Substring(0, num);
package = value.Substring(num + 1);
return true;
}
private static void LoadMap()
{
string path = Path.Combine(Paths.ConfigPath, "IceBoxStudio.HowToFish.ModUpdateChecker.json");
if (!File.Exists(path))
{
return;
}
try
{
MappingCache mappingCache = JsonConvert.DeserializeObject<MappingCache>(File.ReadAllText(path));
if (mappingCache == null || mappingCache.Version != 1 || mappingCache.Mappings == null)
{
return;
}
foreach (KeyValuePair<string, string> mapping in mappingCache.Mappings)
{
CachedMappings[mapping.Key] = mapping.Value;
}
}
catch
{
}
}
private static void SaveMap()
{
try
{
File.WriteAllText(Path.Combine(Paths.ConfigPath, "IceBoxStudio.HowToFish.ModUpdateChecker.json"), JsonConvert.SerializeObject((object)new MappingCache
{
Version = 1,
Mappings = CachedMappings
}, (Formatting)1));
}
catch
{
}
}
private static int Compare(string local, string remote)
{
VersionParts versionParts = VersionParts.Parse(local);
VersionParts versionParts2 = VersionParts.Parse(remote);
for (int i = 0; i < 3; i++)
{
if (versionParts.Core[i] != versionParts2.Core[i])
{
return versionParts.Core[i].CompareTo(versionParts2.Core[i]);
}
}
if (versionParts.Pre.Count == 0 && versionParts2.Pre.Count > 0)
{
return 1;
}
if (versionParts.Pre.Count > 0 && versionParts2.Pre.Count == 0)
{
return -1;
}
int num = Math.Max(versionParts.Pre.Count, versionParts2.Pre.Count);
for (int j = 0; j < num; j++)
{
if (j >= versionParts.Pre.Count)
{
return -1;
}
if (j >= versionParts2.Pre.Count)
{
return 1;
}
int result;
bool flag = int.TryParse(versionParts.Pre[j], out result);
int result2;
bool flag2 = int.TryParse(versionParts2.Pre[j], out result2);
if (flag && flag2 && result != result2)
{
return result.CompareTo(result2);
}
if (flag != flag2)
{
if (!flag)
{
return 1;
}
return -1;
}
int num2 = string.Compare(versionParts.Pre[j], versionParts2.Pre[j], StringComparison.OrdinalIgnoreCase);
if (num2 != 0)
{
return num2;
}
}
return 0;
}
private static void Notify()
{
ModUpdateRuntime.Changed?.Invoke();
}
private static bool RestoreSession(ModUpdateEntry entry)
{
if (entry == null || !CacheIsFresh())
{
return false;
}
if (!SessionResults.TryGetValue(entry.Guid, out var value) || !string.Equals(value.Name, entry.Name, StringComparison.Ordinal) || !string.Equals(value.LocalVersion, entry.LocalVersion, StringComparison.Ordinal))
{
return false;
}
CopyEntry(entry, value);
return true;
}
private static bool CacheIsFresh()
{
if (_sessionCachedAt != default(DateTime))
{
return DateTime.UtcNow - _sessionCachedAt < SessionCacheTime;
}
return false;
}
private static void CacheSession(ModUpdateEntry entry)
{
if (entry != null && !string.IsNullOrWhiteSpace(entry.Guid))
{
SessionResults[entry.Guid] = CopyEntry(entry);
}
}
private static void CopyEntry(ModUpdateEntry target, ModUpdateEntry source)
{
target.Team = source.Team;
target.Package = source.Package;
target.LatestVersion = source.LatestVersion;
target.PackageUrl = source.PackageUrl;
target.State = source.State;
target.Error = source.Error;
target.Candidates = ((source.Candidates == null) ? new List<ModUpdateCandidate>() : source.Candidates.Select(CopyCandidate).ToList());
}
private static ModUpdateEntry CopyEntry(ModUpdateEntry source)
{
ModUpdateEntry obj = new ModUpdateEntry
{
Guid = source.Guid,
Name = source.Name,
LocalVersion = source.LocalVersion
};
CopyEntry(obj, source);
return obj;
}
private static ModUpdateCandidate CopyCandidate(ModUpdateCandidate source)
{
return new ModUpdateCandidate
{
Team = source.Team,
Package = source.Package,
LatestVersion = source.LatestVersion,
PackageUrl = source.PackageUrl,
State = source.State,
Error = source.Error
};
}
}
}
namespace ModUpdateChecker.Localization
{
internal static class I18n
{
private const string FileName = "ModUpdateChecker.Localization.json";
private static readonly ModLocalizer _localizer = Load();
internal static string Text(string key, params object[] args)
{
return _localizer.GetLocalizedText(key, args);
}
private static ModLocalizer Load()
{
string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
ModLocalizer obj = LocalizationApi.For("IceBoxStudio.HowToFish.ModUpdateChecker");
obj.RegisterJson(Path.Combine(directoryName, "ModUpdateChecker.Localization.json"));
return obj;
}
}
}
namespace ModUpdateChecker.Configuration
{
internal static class ConfigManager
{
private static ConfigEntry<bool> _checkOnStartup;
private static ConfigEntry<float> _startupDelay;
private static ConfigEntry<int> _requestTimeout;
private static ConfigEntry<string> _mappings;
internal static bool CheckOnStartup
{
get
{
if (_checkOnStartup != null)
{
return _checkOnStartup.Value;
}
return true;
}
}
internal static float StartupDelay => Mathf.Max(0f, (_startupDelay == null) ? 3f : _startupDelay.Value);
internal static int RequestTimeout => Mathf.Clamp((_requestTimeout == null) ? 10 : _requestTimeout.Value, 3, 60);
internal static void Init(ConfigFile config)
{
_checkOnStartup = config.Bind<bool>("General", "CheckOnStartup", true, I18n.Text("config.check_startup"));
_startupDelay = config.Bind<float>("General", "StartupDelay", 3f, I18n.Text("config.startup_delay"));
_requestTimeout = config.Bind<int>("Network", "RequestTimeout", 10, I18n.Text("config.request_timeout"));
_mappings = config.Bind<string>("Packages", "Mappings", string.Empty, I18n.Text("config.mappings"));
config.Save();
}
internal static bool TryGetMapping(string guid, out string team, out string package)
{
team = null;
package = null;
string text = ((_mappings == null) ? string.Empty : _mappings.Value);
if (string.IsNullOrWhiteSpace(text))
{
return false;
}
string[] array = text.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < array.Length; i++)
{
string text2 = array[i].Trim();
if (text2.Length == 0 || text2.StartsWith("#", StringComparison.Ordinal))
{
continue;
}
int num = text2.IndexOf('=');
if (num > 0 && string.Equals(text2.Substring(0, num).Trim(), guid, StringComparison.OrdinalIgnoreCase))
{
string text3 = text2.Substring(num + 1).Trim();
int num2 = text3.IndexOf('/');
if (num2 <= 0 || num2 >= text3.Length - 1)
{
return false;
}
team = text3.Substring(0, num2).Trim();
package = text3.Substring(num2 + 1).Trim();
if (team.Length > 0)
{
return package.Length > 0;
}
return false;
}
}
return false;
}
}
}