private static void SetNonFrogtownModStatus(ModDetails details, bool enable) { string src, dst; var display = details.dllFileName.Substring(1, details.dllFileName.Length - 1); if (enable) { src = DISABLED_MOD_FOLDER + "\\" + details.dllFileName; dst = ENABLED_MOD_FOLDER + "\\" + details.dllFileName; FrogtownShared.Log("FrogShared", LogLevel.Info, display + " moved to plugins, will be loaded after restart."); } else { src = details.dllFileFullPath; if (string.IsNullOrEmpty(src)) { src = ENABLED_MOD_FOLDER + "\\" + details.dllFileName; } dst = DISABLED_MOD_FOLDER + "\\" + details.dllFileName; FrogtownShared.Log("FrogShared", LogLevel.Info, display + " moved to disabled, will be unloaded after restart."); } System.IO.File.Move(src, dst); details.enabled = enable; //Don't call afterToggle because they aren't actually changed until restart }
public void Awake() { modDetails = new FrogtownModDetails("com.frogtown.healinghelper") { description = "Turns you into a healing drone when you die, instead of spectating.", githubAuthor = "ToyDragon", githubRepo = "ROR2ModHealingHelper", thunderstoreFullName = "ToyDragon-HealingHelpers" }; FrogtownShared.RegisterMod(modDetails); On.RoR2.Stage.FixedUpdate += (orig, instance) => { StageFixedUpdatePrefix(instance); orig(instance); }; On.RoR2.CharacterMaster.OnBodyDeath += (orig, instance) => { orig(instance); CharacterMasterOnBodyDeathPostfix(instance); }; On.RoR2.Run.Start += (orig, instance) => { orig(instance); RunStartPostfix(); }; On.RoR2.Stage.RespawnCharacter += (orig, instance, characterMaster) => { StageRespawnCharacterPrefix(characterMaster); orig(instance, characterMaster); }; }
public static void ToggleMod(string GUID, bool enable) { if (modDetails.TryGetValue(GUID, out ModDetails details)) { if (CanToggleMod(GUID, out bool curEnabled)) { if (curEnabled != enable) { if (details.frogtownModDetails != null) { details.enabled = enable; details.afterToggle?.Invoke(details); details.frogtownModDetails.afterToggle?.Invoke(); var config = FrogtownShared.GetConfig(); config.Wrap("mods", GUID, "", "true").Value = enable.ToString(); config.Save(); } else { SetNonFrogtownModStatus(details, enable); } } } } }
private static void AfterModToggle(ModDetails details) { if (details.frogtownModDetails != null && details.frogtownModDetails.isNotCheaty) { return; } if (whitelistFrameworkUnlisted.Contains(details.GUID)) { return; } if (details.enabled) { modCount++; FrogtownShared.Log("FrogShared", LogLevel.Info, details.GUID + " enabled."); } else { modCount--; FrogtownShared.Log("FrogShared", LogLevel.Info, details.GUID + " disabled."); } bool newIsModded = modCount > 0; if (RoR2Application.isModded != newIsModded) { RoR2Application.isModded = newIsModded; FrogtownShared.Log("FrogShared", LogLevel.Info, "Set isModded flag to " + newIsModded); } }
public void Awake() { modDetails = new FrogtownModDetails("com.frogtown.chatcheats") { description = "Adds the /change_char and /give_item chat commands.", githubAuthor = "ToyDragon", githubRepo = "ROR2ModChatCommandCheats", thunderstoreFullName = "ToyDragon-CheatingChatCommands", OnGUI = OnSettingsGUI }; FrogtownShared.RegisterMod(modDetails); FrogtownShared.AddChatCommand("change_char", OnCharCommand); FrogtownShared.AddChatCommand("give_item", OnGiveCommand); FrogtownShared.AddChatCommand("remove_item", OnRemoveCommand); FrogtownShared.AddChatCommand("clear_items", OnClearItemsCommand); itemsByTier = new List <ItemIndex>(); foreach (var itemIndex in ItemCatalog.allItems) { itemsByTier.Add(itemIndex); } itemsByTier.Sort((a, b) => { var definitionA = ItemCatalog.GetItemDef(a); var definitionB = ItemCatalog.GetItemDef(b); return(definitionA.tier.CompareTo(definitionB.tier)); }); }
internal void SaveSettings() { var config = FrogtownShared.GetConfig(); config.Wrap("ui", "scale", "", "1").Value = uiSettings.scale.ToString(); config.Wrap("ui", "showonstart", "", "true").Value = uiSettings.showOnStart.ToString(); config.Save(); }
private void WindowFunction(int windowId) { if (Input.GetKey(KeyCode.LeftControl)) { GUI.DragWindow(windowRect); } UnityAction buttons = () => { }; GUILayout.BeginVertical("box"); GUILayout.Label("Frogtown Mod Manager 2.0.3", h1); GUILayout.BeginVertical("box"); GUILayout.Space(3); int tab = tabId; tab = GUILayout.Toolbar(tab, tabs, button, GUILayout.ExpandWidth(false)); if (tab != tabId) { tabId = tab; } GUILayout.Space(5); if (tabId == 0) { DrawModTab(ref buttons); } if (tabId == 1) { DrawSettingsTab(ref buttons); } if (tabId == 2) { DrawLogTab(ref buttons); } GUILayout.FlexibleSpace(); GUILayout.Space(5); GUILayout.BeginHorizontal(); if (GUILayout.Button(new GUIContent("Close", "Close the overlay."), button, GUILayout.ExpandWidth(false))) { ToggleWindow(); } if (GUILayout.Button(new GUIContent("Mod Folder", "Open the plugin folder where mod DLLs should be placed."), button, GUILayout.ExpandWidth(false))) { string url = "file://" + BepInEx.Paths.PluginPath; FrogtownShared.Log("FrogShared", LogLevel.Info, "Openning " + url); Application.OpenURL(url); } buttons(); GUILayout.Label(GUI.tooltip); GUILayout.EndHorizontal(); GUILayout.EndVertical(); GUILayout.EndVertical(); }
private void LoadSettings() { var config = FrogtownShared.GetConfig(); string raw = config.Wrap("ui", "scale", "", "1").Value; if (!float.TryParse(raw, out uiSettings.scale)) { uiSettings.scale = 1f; } uiSettings.scale = Mathf.Clamp(uiSettings.scale, 0.5f, 2f); raw = config.Wrap("ui", "showonstart", "", "true").Value; bool.TryParse(raw, out uiSettings.showOnStart); }
public void Awake() { modDetails = new FrogtownModDetails("com.frogtown.characterrandomizer") { description = "Switches the characters of everyone in the party randomly every stage.", githubAuthor = "ToyDragon", githubRepo = "ROR2ModCharacterRandomizer", thunderstoreFullName = "ToyDragon-CharacterRandomizer", }; FrogtownShared.RegisterMod(modDetails); On.RoR2.Stage.RespawnCharacter += (orig, instance, characterMaster) => { StageRespawnCharacterPrefix(characterMaster); orig(instance, characterMaster); }; }
private bool OnGiveCommand(string userName, string[] pieces) { if (!modDetails.enabled) { return(false); } var player = FrogtownShared.GetPlayerWithName(userName); int index = 0, count = 0; if (pieces.Length >= 2) { if (!Int32.TryParse(pieces[1], out index)) { if (Enum.TryParse(pieces[1], true, out ItemIndex result)) { index = (int)result; } else { FrogtownShared.SendChat("\"" + pieces[1] + "\" not recognized."); return(true); } } } if (index < 0 || index >= (int)ItemIndex.Count) { index = (int)ItemIndex.SprintOutOfCombat; } if (pieces.Length >= 3) { Int32.TryParse(pieces[2], out count); } if (count == 0) { count = 1; } player.master.inventory.GiveItem((ItemIndex)index, count); FrogtownShared.SendChat("Gave " + userName + " " + count + " " + ((ItemIndex)index).ToString()); return(true); }
public void Awake() { modDetails = new FrogtownModDetails("com.frogtown.engineerfixes") { description = "Allows turret kills to drop lunar coins, and turret damage count in score card.", githubAuthor = "ToyDragon", githubRepo = "ROR2ModEngineerLunarCoinFix", thunderstoreFullName = "ToyDragon-EngineerLunarCoinsFix", }; modDetails.OnlyContainsBugFixesOrUIChangesThatArentContriversial(); FrogtownShared.RegisterMod(modDetails); On.RoR2.HealthComponent.TakeDamage += (orig, instance, damageInfo) => { HealthComponentTakeDamagePrefix(damageInfo); orig(instance, damageInfo); HealthComponentTakeDamagePostfix(damageInfo); }; }
/// <summary> /// Restores all users to their original prefabs. /// </summary> public void RestoreCharacterPrefabsAndKill() { foreach (string name in originalBodyNames.Keys) { PlayerCharacterMasterController player = FrogtownShared.GetPlayerWithName(name); if (player != null) { if (originalBodyNames.TryGetValue(name, out string oldBodyName)) { var prefab = BodyCatalog.FindBodyPrefab(oldBodyName); if (prefab != null) { FrogtownShared.ChangePrefab(name, prefab); player.master.Invoke("TrueKill", 0.1f); } } } } }
private bool OnRemoveCommand(string userName, string[] pieces) { if (!modDetails.enabled) { return(false); } var player = FrogtownShared.GetPlayerWithName(userName); int index = 0, count = 0; if (pieces.Length >= 2) { if (!Int32.TryParse(pieces[1], out index)) { if (Enum.TryParse(pieces[1], true, out ItemIndex result)) { index = (int)result; } else { FrogtownShared.SendChat("\"" + pieces[1] + "\" not recognized."); return(true); } } } if (pieces.Length >= 3) { Int32.TryParse(pieces[2], out count); } int countPlayerHas = player.master.inventory.GetItemCount((ItemIndex)index); count = Math.Min(Math.Max(count, 1), countPlayerHas); if (count > 0) { player.master.inventory.GiveItem((ItemIndex)index, -count); FrogtownShared.SendChat("Took " + count + " " + ((ItemIndex)index).ToString() + " from " + userName + "."); } return(true); }
/// <summary> /// Restores user to their original prefab /// </summary> /// <param name="name"></param> public void RestoreCharacterPrefab(string name) { PlayerCharacterMasterController player = FrogtownShared.GetPlayerWithName(name); if (player != null) { if (originalBodyNames.TryGetValue(name, out string oldBodyName)) { var prefab = BodyCatalog.FindBodyPrefab(oldBodyName); if (prefab != null) { player.master.bodyPrefab = prefab; } else { FrogtownShared.SendChat("No prefab for \"" + oldBodyName + "\""); } } } }
private bool OnClearItemsCommand(string userName, string[] pieces) { if (!modDetails.enabled) { return(false); } var player = FrogtownShared.GetPlayerWithName(userName); foreach (ItemIndex itemIndex in ItemCatalog.allItems) { int count = player.master.inventory.GetItemCount(itemIndex); if (count > 0) { player.master.inventory.GiveItem(itemIndex, -count); } } FrogtownShared.SendChat("Took all items from " + userName + "."); return(true); }
public static void TriggerChatCommand(string userName, string[] pieces) { if (pieces.Length == 0) { return; } if (chatCommandList.TryGetValue(pieces[0].ToUpper(), out var list)) { foreach (var func in list) { try { func.Invoke(userName, pieces); } catch (Exception e) { FrogtownShared.Log("FrogShared", LogLevel.Error, "Command " + pieces[0] + " logged exception \"" + e.Message + "\""); FrogtownShared.Log("FrogShared", LogLevel.Error, e.StackTrace); } } } }
private static void FindActiveMods() { var config = FrogtownShared.GetConfig(); var allModDetails = (from a in AppDomain.CurrentDomain.GetAssemblies() from t in a.GetTypes() let attributeList = t.GetCustomAttributes(typeof(BepInPlugin), true).Take(1) where attributeList != null && attributeList.Count() > 0 let attribute = attributeList.First() as BepInPlugin select new ModDetails(attribute, t.Assembly.Location)).ToArray(); foreach (var info in allModDetails) { modDetails.Add(info.GUID, info); info.afterToggle += AfterModToggle; if (frogtownDetails.TryGetValue(info.GUID, out FrogtownModDetails frogDetails)) { info.frogtownModDetails = frogDetails; frogDetails.modDetails = info; string raw = config.Wrap("mods", info.GUID, "", "True").Value; bool shouldBeActive = raw.ToLower() == "true"; if (shouldBeActive) { ToggleMod(info.GUID, true); } } else { //Do not call ToggleMod because it will try to move DLLs around and shit like that. //This is intializing the enabled properties and active mod count to reflect the actual //state of what is loading. info.enabled = true; info.initialEnabled = true; info.afterToggle?.Invoke(info); } } }
private bool OnCharCommand(string userName, string[] pieces) { if (!modDetails.enabled) { return(false); } if (pieces.Length >= 2) { int prefabIndex = -1; if (!Int32.TryParse(pieces[1], out prefabIndex)) { prefabIndex = BodyCatalog.FindBodyIndexCaseInsensitive(pieces[1]); } if (prefabIndex != -1) { GameObject prefab = BodyCatalog.GetBodyPrefab(prefabIndex); if (prefab != null) { if (FrogtownShared.ChangePrefab(userName, prefab)) { FrogtownShared.SendChat(userName + " morphed into " + prefab.name); } else { FrogtownShared.SendChat(userName + " couldn't morph into " + prefab.name); } } else { FrogtownShared.SendChat("Prefab not found"); } } } return(true); }
void Awake() { instance = this; On.RoR2.Chat.AddMessage_string += (orig, message) => { orig(message); if (ParseUserAndMessage(message, out string userName, out string text)) { string[] pieces = text.Split(' '); TriggerChatCommand(userName, pieces); } }; FrogtownModDetails details = new FrogtownModDetails("com.frogtown.shared") { githubAuthor = "ToyDragon", githubRepo = "ROR2ModShared", description = "Powers this UI and contains shared resources for other mods.", noDisable = true, OnGUI = () => { bool newShowOnStart = GUILayout.Toggle(UI.instance.uiSettings.showOnStart, "Show this window on startup", GUILayout.ExpandWidth(false)); if (newShowOnStart != UI.instance.uiSettings.showOnStart) { UI.instance.uiSettings.showOnStart = newShowOnStart; UI.instance.SaveSettings(); } } }; details.OnlyContainsBugFixesOrUIChangesThatArentContriversial(); ModManager.RegisterMod(details); //I use this to test multiplayer, leave it off in releases Invoke(nameof(Init), 1f); }
private void CharacterMasterOnBodyDeathPostfix(CharacterMaster instance) { if (gameover) { return; } PlayerCharacterMasterController player = instance.GetComponent <PlayerCharacterMasterController>(); if (player != null) { if (!instance.preventGameOver) { if (modDetails.enabled) { var name = player.networkUser.GetNetworkPlayerName().GetResolvedName(); originalBodyNames.Add(name, player.master.bodyPrefab.name); var prefab = BodyCatalog.FindBodyPrefab("Drone2Body"); FrogtownShared.ChangePrefab(name, prefab); } //Still log they are a bot incase the mod was disabled in the middle of the round, so that the game can end properly. botPlayers.Add(player.networkUser.GetNetworkPlayerName().GetResolvedName()); } } }
private void DrawModTab(ref UnityAction buttons) { var minWidth = GUILayout.MinWidth(windowSize.x); scrollPositions[0] = GUILayout.BeginScrollView(scrollPositions[0], minWidth, GUILayout.ExpandHeight(false)); var amountWidth = columns.Where(x => !x.skip).Sum(x => x.width); var expandWidth = columns.Where(x => x.expand && !x.skip).Sum(x => x.width); var colWidth = columns.Select(x => x.expand ? GUILayout.Width(x.width / expandWidth * (windowSize.x - 60 + expandWidth - amountWidth)) : GUILayout.Width(x.width)).ToArray(); GUILayout.BeginVertical(); GUILayout.BeginHorizontal("box"); for (int i = 0; i < columns.Count; i++) { if (columns[i].skip) { continue; } GUILayout.Label(columns[i].name, colWidth[i]); } GUILayout.EndHorizontal(); var GUIDs = ModManager.modDetails.Keys.ToArray(); Array.Sort(GUIDs, (a, b) => { ModManager.modDetails.TryGetValue(a, out ModDetails details); string aAuthor = details.frogtownModDetails?.githubAuthor ?? "Unknown"; ModManager.modDetails.TryGetValue(b, out details); string bAuthor = details.frogtownModDetails?.githubAuthor ?? "Unknown"; return(aAuthor.CompareTo(bAuthor)); }); List <ModRow> rows = new List <ModRow>(); string lastAuthor = ""; ModRow authorRow = null; foreach (string GUID in GUIDs) { ModManager.modDetails.TryGetValue(GUID, out ModDetails details); if (ModManager.whitelistFrameworkUnlisted.Contains(details.GUID)) { continue; } string author = details.frogtownModDetails?.githubAuthor ?? "Unknown"; if (author != lastAuthor) { if (authorRow != null) { rows.Add(authorRow); authorRow = null; } if (collapsedAuthors.Contains(author)) { authorRow = new ModRow(); ModRow rowAnchor = authorRow; authorRow.canToggle = ModManager.CanToggleMod(GUID, out bool isActive); if (authorRow.canToggle) { authorRow.isActive = isActive; } authorRow.githubAuthor = author; if (authorRow.githubAuthor != "Unknown") { authorRow.url = "https://github.com/" + author; authorRow.modName = "All mods from " + author; } else { authorRow.modName = "All mods from unknown authors"; } authorRow.isAuthorCollapsed = true; authorRow.description = details.modName; authorRow.firstForAuthor = true; if (details.frogtownModDetails == null && details.enabled != details.initialEnabled) { authorRow.statusStyle = red; authorRow.statusMessage = "Mod will be " + (details.enabled ? "enabled" : "disabled") + " the next time the game is started."; } authorRow.onToggleActive += () => { FrogtownShared.Log("FrogShared", LogLevel.Info, "Setting mods from " + rowAnchor.githubAuthor + " to " + !rowAnchor.isActive); foreach (string otherGUID in GUIDs) { ModManager.modDetails.TryGetValue(otherGUID, out ModDetails otherDetails); string otherAuthor = otherDetails.frogtownModDetails?.githubAuthor ?? "Unknown"; if (otherAuthor == author) { if (ModManager.CanToggleMod(otherGUID, out bool unused)) { ModManager.ToggleMod(otherGUID, !rowAnchor.isActive); } } } }; authorRow.onToggleAuthor += () => { collapsedAuthors.Remove(rowAnchor.githubAuthor); }; lastAuthor = author; continue; } } if (authorRow != null) { bool canToggle = ModManager.CanToggleMod(GUID, out bool isActive); authorRow.canToggle = authorRow.canToggle || canToggle; if (canToggle) { authorRow.isActive = authorRow.isActive || isActive; } authorRow.description += ", " + details.modName; if (details.frogtownModDetails == null && details.enabled != details.initialEnabled) { authorRow.statusStyle = red; authorRow.statusMessage = "Mod will be " + (details.enabled ? "enabled" : "disabled") + " the next time the game is started."; } } else { ModRow row = new ModRow(); row.canToggle = ModManager.CanToggleMod(GUID, out bool isActive); row.isActive = isActive; row.githubAuthor = author; if (details.frogtownModDetails != null) { row.url = "https://github.com/" + details.frogtownModDetails.githubAuthor + "/" + details.frogtownModDetails.githubRepo; } row.isAuthorCollapsed = false; row.modName = details.modName; row.description = details.frogtownModDetails?.description ?? ""; row.firstForAuthor = author != lastAuthor; row.version = details.version; row.newVersionLoading = details.frogtownModDetails?.newVersionLoading ?? false; row.newVersion = details.frogtownModDetails?.newVersion ?? ""; row.onToggleActive += () => { ModManager.ToggleMod(GUID, !row.isActive); }; row.onToggleAuthor += () => { collapsedAuthors.Add(row.githubAuthor); }; if (details.frogtownModDetails == null && details.enabled != details.initialEnabled) { row.statusStyle = red; row.statusMessage = "Mod will be " + (details.enabled ? "enabled" : "disabled") + " the next time the game is started."; } rows.Add(row); } lastAuthor = author; } if (authorRow != null) { rows.Add(authorRow); } foreach (var row in rows) { int col = -1; GUILayout.BeginHorizontal("box"); GUILayout.BeginHorizontal(colWidth[++col]); if (row.firstForAuthor) { bool newIsCollapsed = row.isAuthorCollapsed; newIsCollapsed = GUILayout.Toggle(newIsCollapsed, new GUIContent("", "Collapse mods from " + row.githubAuthor + ".")); if (newIsCollapsed != row.isAuthorCollapsed) { row.onToggleAuthor?.Invoke(); } } else { GUILayout.Label(" ", GUILayout.Width(56f)); } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(colWidth[++col]); GUILayout.Label(new GUIContent(row.githubAuthor, row.description)); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(colWidth[++col]); GUILayout.Label(new GUIContent(row.modName, row.description)); GUILayout.FlexibleSpace(); if (!string.IsNullOrEmpty(row.url)) { if (GUILayout.Button(new GUIContent("www", row.url), button)) { Application.OpenURL(row.url); } } else { GUILayout.Label(new GUIContent("---", "No repository available.")); } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(colWidth[++col]); GUILayout.Label(row.version, curVersion, GUILayout.ExpandWidth(false)); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(colWidth[++col]); if (row.githubAuthor != "Unknown" && !row.isAuthorCollapsed) { if (row.newVersionLoading) { GUILayout.Label(new GUIContent("...", "Checking latest release...")); } else { if (!string.IsNullOrEmpty(row.newVersion)) { if (GUILayout.Button(row.newVersion, h2)) { Application.OpenURL(row.url + "/releases"); } } else { GUILayout.Label(new GUIContent("---", "No new version found.")); } } } else { GUILayout.Label(new GUIContent("---", "Can't load releases for " + row.modName + ".")); } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(colWidth[++col]); if (row.canToggle) { bool newIsActive = row.isActive; newIsActive = GUILayout.Toggle(row.isActive, new GUIContent("", (row.isActive ? "Disable" : "Enable") + " " + row.modName + ".")); if (newIsActive != row.isActive) { row.onToggleActive?.Invoke(); } } else { GUILayout.Label(new GUIContent("X", row.modName + " cannot be disabled."), nodisable); } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(colWidth[++col]); if (!string.IsNullOrEmpty(row.statusMessage)) { GUILayout.Label(new GUIContent("R", row.statusMessage), row.statusStyle); } GUILayout.EndHorizontal(); GUILayout.EndHorizontal(); } GUILayout.EndVertical(); GUILayout.EndScrollView(); }
private bool OnTestCommand(string userName, string[] pieces) { FrogtownShared.SendChat("User " + string.Join(",", pieces)); return(true); }
public void Log(LogLevel level, string message) { FrogtownShared.Log(GUID, level, message); }
internal static IEnumerator CheckForUpdates() { var config = FrogtownShared.GetConfig(); foreach (string GUID in modDetails.Keys) { if (modDetails.TryGetValue(GUID, out ModDetails details)) { if (details.frogtownModDetails != null) { details.newVersionLoading = true; } } } bool anyError = false; foreach (string GUID in modDetails.Keys) { if (modDetails.TryGetValue(GUID, out ModDetails details)) { if (details.frogtownModDetails != null) { string lastUpdateInst = config.Wrap("modupdates", "lastcheck-" + GUID, "", "0").Value; if (long.TryParse(lastUpdateInst, out long updateInst)) { DateTime lastUpdate = new DateTime(updateInst); if (lastUpdate.AddDays(0.5) > DateTime.Now) { string newV = config.Wrap("modupdates", "newestversion-" + GUID, "", "0").Value; if (newV.CompareTo(details.version) > 0) { details.frogtownModDetails.newVersion = newV; } FrogtownShared.Log("FrogShared", LogLevel.Info, "Loaded new version of " + GUID + " from cache."); details.newVersionLoading = false; continue; } } if (anyError) { details.newVersionLoading = false; continue; } string url = "https://api.github.com/repos/" + details.frogtownModDetails.githubAuthor + "/" + details.frogtownModDetails.githubRepo + "/releases"; FrogtownShared.Log("FrogShared", LogLevel.Info, "Requesting " + url); var req = UnityWebRequest.Get(url).SendWebRequest(); while (!req.isDone) { yield return(new WaitForEndOfFrame()); } details.newVersionLoading = false; if (req.webRequest.isHttpError || req.webRequest.isNetworkError) { FrogtownShared.Log("FrogShared", LogLevel.Error, "Error loading releases from " + url); anyError = true; } else { try { string text = req.webRequest.downloadHandler.text; string topRelease = ""; int ix = 0; while ((ix = text.IndexOf("\"tag_name\": \"")) >= 0) { text = text.Substring(ix + "\"tag_name\": \"".Length, text.Length - ix - "\"tag_name\": \"".Length); string version = text.Substring(0, text.IndexOf("\"")); if (version.CompareTo(topRelease) > 0 && version.CompareTo(details.version) > 0) { topRelease = version; } } details.frogtownModDetails.newVersion = topRelease; } catch (Exception e) { FrogtownShared.Log("FrogShared", LogLevel.Info, "Error parsing JSON "); FrogtownShared.Log("FrogShared", LogLevel.Info, e.Message + " " + e.StackTrace); } config.Wrap("modupdates", "lastcheck-" + GUID, "", "0").Value = DateTime.Now.Ticks.ToString(); config.Wrap("modupdates", "newestversion-" + GUID, "", "0").Value = details.frogtownModDetails.newVersion; } } } } config.Save(); }