Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add config system and the ability to disable mods #3

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions FEZ.HAT.mm.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Helpers\ConfigHelper.cs" />
<Compile Include="Helpers\DrawingTools.cs" />
<Compile Include="Helpers\InputHelper.cs" />
<Compile Include="Installers\IHatInstaller.cs" />
Expand Down
123 changes: 123 additions & 0 deletions Helpers/ConfigHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using Common;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;

namespace HatModLoader.Helpers
{
public static class ConfigHelper
{
public const string HatConfigFileName = "HatConfig.xml";

[Serializable]
public struct ModConfig
{
[XmlAttribute] public string Name;
[XmlAttribute] public string Version;
public bool? Disabled;
}

[Serializable]
public struct HatConfig
{
public List<ModConfig> Mods;
}

public static HatConfig Config { get; private set; }

public static ModConfig GetModConfig(string Name, string Version)
{
foreach (ModConfig modConfig in Config.Mods)
{
if (modConfig.Name == Name && modConfig.Version == Version)
return modConfig;
}
ModConfig newModConfig = new ModConfig();
newModConfig.Name = Name;
newModConfig.Version = Version;
newModConfig.Disabled = false;
Config.Mods.Add(newModConfig);
return newModConfig;
}

public static void SetModConfig(ModConfig newConfig)
{
for (int i = 0; i < Config.Mods.Count; i++)
{
if (Config.Mods[i].Name == newConfig.Name && Config.Mods[i].Version == newConfig.Version)
{
Config.Mods[i] = newConfig;
return;
}
}
Config.Mods.Add(newConfig);
}

public static void LoadHatConfig()
{
HatConfig NewConfig;
if (!File.Exists(GetHatConfigFilePath()))
{
Logger.Log("HAT", "Config file doesn't exist, loading blank configuration");
NewConfig = new HatConfig();
}
else
{
try
{
using (StreamReader reader = new StreamReader(GetHatConfigFilePath()))
{
XmlSerializer serializer = new XmlSerializer(typeof(HatConfig));
NewConfig = (HatConfig)serializer.Deserialize(reader);
}
}
catch (Exception e)
{
Logger.Log("HAT", $"Exception deserializing config file, {e.Message}");
NewConfig = new HatConfig();
}
}

// Cleanup configuration and fill in missing details
if (NewConfig.Mods == null)
NewConfig.Mods = new List<ModConfig>();
for (int i = 0; i < NewConfig.Mods.Count; i++)
{
ModConfig mod = NewConfig.Mods[i];
if (!mod.Disabled.HasValue)
mod.Disabled = false;
NewConfig.Mods[i] = mod;
}

Config = NewConfig;
}

public static bool SaveHatConfig()
{
try
{
using (StreamWriter writer = new StreamWriter(GetHatConfigFilePath()))
{
XmlSerializer serializer = new XmlSerializer(typeof(HatConfig));
serializer.Serialize(writer, Config);
}
}
catch (Exception e)
{
Logger.Log("HAT", $"FAILED to save HAT config, ${e.Message}");
return false;
}
return true;
}

public static string GetHatConfigFilePath()
{
return Path.Combine(Util.LocalConfigFolder, HatConfigFileName);
}
}
}
107 changes: 69 additions & 38 deletions Installers/ModMenuInstaller.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Common;
using FezGame;
using HatModLoader.Helpers;
using HatModLoader.Source;
using MonoMod.RuntimeDetour;
using System;
Expand All @@ -9,6 +10,8 @@
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using static HatModLoader.Helpers.ConfigHelper;
using static HatModLoader.Source.Mod;

namespace HatModLoader.Installers
{
Expand Down Expand Up @@ -50,7 +53,7 @@ private static void CreateAndAddModLevel(object MenuBase)
{
MenuRoot = MainMenuType.GetField("RealMenuRoot", privBind).GetValue(MenuBase);
}

if(MenuBase.GetType() != MainMenuType || MenuRoot == null)
{
MenuRoot = MenuBaseType.GetField("MenuRoot", privBind).GetValue(MenuBase);
Expand All @@ -70,61 +73,89 @@ private static void CreateAndAddModLevel(object MenuBase)
MenuLevelType.GetField("Parent").SetValue(ModLevel, MenuRoot);
MenuLevelType.GetField("Oversized").SetValue(ModLevel, true);



var MenuLevelAddItemGeneric = MenuLevelType.GetMethods().FirstOrDefault(mi => mi.Name == "AddItem" && mi.GetParameters().Length == 5);
var MenuLevelAddItemInt = MenuLevelAddItemGeneric.MakeGenericMethod(new Type[] { typeof(int) });

if (Hat.Instance.Mods.Count > 0)
{
var menuIteratorItem = MenuLevelAddItemInt.Invoke(ModLevel, new object[] {
null, (Action)delegate { }, false,
(Func<int>) delegate{ return modMenuCurrentIndex; },
(Action<int, int>) delegate(int value, int change) {
modMenuCurrentIndex += change;
if (modMenuCurrentIndex < 0) modMenuCurrentIndex = Hat.Instance.Mods.Count-1;
if (modMenuCurrentIndex >= Hat.Instance.Mods.Count) modMenuCurrentIndex = 0;
}
});
MenuItemType.GetProperty("SuffixText").SetValue(menuIteratorItem, (Func<string>)delegate
{
return $"{modMenuCurrentIndex + 1} / {Hat.Instance.Mods.Count}";
});
}
// add created menu level to the main menu
int modsIndex = ((IList)MenuLevelType.GetField("Items").GetValue(MenuRoot)).Count - 2;
MenuLevelType.GetMethod("AddItem", new Type[] { typeof(string), typeof(Action), typeof(int) })
.Invoke(MenuRoot, new object[] { "@MODS", (Action) delegate{
MenuBaseType.GetMethod("ChangeMenuLevel").Invoke(MenuBase, new object[] { ModLevel, false });
}, modsIndex});

Action<string, Func<string>> AddInactiveStringItem = delegate (string name, Func<string> suffix)
// needed to refresh the menu before the transition to it happens (pause menu)
MenuBaseType.GetMethod("RenderToTexture", privBind).Invoke(MenuBase, new object[] { });

Action<string, Func<string>, Func<bool>> AddInactiveDisableableStringItem = delegate (string name, Func<string> suffix, Func<bool> disabled)
{
var item = MenuLevelType.GetMethod("AddItem", new Type[] { typeof(string) })
.Invoke(ModLevel, new object[] {name});
MenuItemType.GetProperty("Selectable").SetValue(item, false);
if(suffix != null)
if (disabled != null)
{
Func<string> newSuffixFunc = () => {
MenuItemType.GetProperty("Disabled").SetValue(item, disabled());
if (suffix == null)
return "";
return suffix();
};
MenuItemType.GetProperty("SuffixText").SetValue(item, newSuffixFunc);
}
else if (suffix != null)
{
MenuItemType.GetProperty("SuffixText").SetValue(item, suffix);
}
};

Action<string, Func<string>> AddInactiveStringItem = delegate (string name, Func<string> suffix)
{
AddInactiveDisableableStringItem(name, suffix, null);
};

if (Hat.Instance.Mods.Count == 0)
{
AddInactiveStringItem(null, () => "No HAT Mods Installed");
}
else
{
AddInactiveStringItem(null, null);
AddInactiveStringItem(null, () => Hat.Instance.Mods[modMenuCurrentIndex].Info.Name);
AddInactiveStringItem(null, () => Hat.Instance.Mods[modMenuCurrentIndex].Info.Description);
AddInactiveStringItem(null, () => $"made by {Hat.Instance.Mods[modMenuCurrentIndex].Info.Author}");
AddInactiveStringItem(null, () => $"version {Hat.Instance.Mods[modMenuCurrentIndex].Info.Version}");
return;
}

// add created menu level to the main menu
int modsIndex = ((IList)MenuLevelType.GetField("Items").GetValue(MenuRoot)).Count - 2;
MenuLevelType.GetMethod("AddItem", new Type[] { typeof(string), typeof(Action), typeof(int) })
.Invoke(MenuRoot, new object[] { "@MODS", (Action) delegate{
MenuBaseType.GetMethod("ChangeMenuLevel").Invoke(MenuBase, new object[] { ModLevel, false });
}, modsIndex});

// needed to refresh the menu before the transition to it happens (pause menu)
MenuBaseType.GetMethod("RenderToTexture", privBind).Invoke(MenuBase, new object[] { });
var menuIteratorItem = MenuLevelAddItemInt.Invoke(ModLevel, new object[] {
null, (Action)delegate { }, false,
(Func<int>) delegate{ return modMenuCurrentIndex; },
(Action<int, int>) delegate(int value, int change) {
modMenuCurrentIndex += change;
if (modMenuCurrentIndex < 0) modMenuCurrentIndex = Hat.Instance.Mods.Count-1;
if (modMenuCurrentIndex >= Hat.Instance.Mods.Count) modMenuCurrentIndex = 0;
}
});
MenuItemType.GetProperty("SuffixText").SetValue(menuIteratorItem, (Func<string>)delegate
{
return $"{modMenuCurrentIndex + 1} / {Hat.Instance.Mods.Count}";
});

Func<bool> shouldBeDisabled = () => !Hat.Instance.Mods[modMenuCurrentIndex].IsEnabled;
AddInactiveStringItem(null, null);
AddInactiveDisableableStringItem(null, () => Hat.Instance.Mods[modMenuCurrentIndex].Info.Name + (shouldBeDisabled() ? " (Disabled)" : ""), shouldBeDisabled);
AddInactiveDisableableStringItem(null, () => Hat.Instance.Mods[modMenuCurrentIndex].Info.Description, shouldBeDisabled);
AddInactiveDisableableStringItem(null, () => $"made by {Hat.Instance.Mods[modMenuCurrentIndex].Info.Author}", shouldBeDisabled);
AddInactiveDisableableStringItem(null, () => $"version {Hat.Instance.Mods[modMenuCurrentIndex].Info.Version}", shouldBeDisabled);

var EnableDisableButton = MenuLevelType.GetMethod("AddItem", new Type[] { typeof(string), typeof(Action) })
.Invoke(ModLevel, new object[] { null, (Action) delegate {
Metadata modInfo = Hat.Instance.Mods[modMenuCurrentIndex].Info;
ModConfig config = ConfigHelper.GetModConfig(modInfo.Name, modInfo.Version);
Logger.Log("HAT", $"Previous disabled state: {config.Disabled.HasValue && config.Disabled.Value == true}");
config.Disabled = !(config.Disabled.HasValue && config.Disabled.Value == true);
Logger.Log("HAT", $"New disabled state: {config.Disabled.HasValue && config.Disabled.Value == true}");
ConfigHelper.SetModConfig(config);
ConfigHelper.SaveHatConfig();
}});
Func<string> EnableDisableText = delegate
{
Metadata modInfo = Hat.Instance.Mods[modMenuCurrentIndex].Info;
ModConfig config = ConfigHelper.GetModConfig(modInfo.Name, modInfo.Version);
return $"{((config.Disabled.HasValue && config.Disabled.Value == true) ? "Enable" : "Disable")} (restart required)";
};
MenuItemType.GetProperty("SuffixText").SetValue(EnableDisableButton, EnableDisableText);
}

public void Uninstall()
Expand Down
5 changes: 4 additions & 1 deletion Patches/FezLogo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ public override void Draw(GameTime gameTime)
Viewport viewport = DrawingTools.GetViewport();
int modCount = Hat.Instance.Mods.Count;
string hatText = $"HAT Mod Loader, version {Hat.Version}, {modCount} mod{(modCount != 1 ? "s" : "")} installed";
if (modCount == 69) hatText += "... nice";
int enabledModCount = Hat.Instance.EnabledMods.Count;
if (enabledModCount != modCount)
hatText += $", {enabledModCount} mod{(enabledModCount != 1 ? "s" : "")} enabled";
if (enabledModCount == 69) hatText += "... nice";
Color textColor = Color.Lerp(Color.White, Color.Black, alpha);

DrawingTools.BeginBatch();
Expand Down
Loading