diff --git a/.gitignore b/.gitignore index 0210746..ed82ae9 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ sysinfo.txt # Builds *.apk *.unitypackage + +bin/ diff --git a/BWModLoader.csproj b/BWModLoader.csproj index 5e5de05..12881a9 100644 --- a/BWModLoader.csproj +++ b/BWModLoader.csproj @@ -1,4 +1,4 @@ - + Debug @@ -7,7 +7,7 @@ Library BWModLoader BWModLoader - v4.7 + v3.5 true @@ -35,13 +35,13 @@ + + - - - + \ No newline at end of file diff --git a/src/Loader.cs b/src/Loader.cs index e2d9412..9d4c0c7 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -1,73 +1,49 @@ using System; +using System.Collections.Generic; using System.IO; using System.Reflection; using UnityEngine; -namespace ModLoader + +namespace BWModLoader { public static class Loader { - public static void Log(string output) - { - Console.WriteLine("[BWML]" + output); - //UnityEngine.Debug.Log("[BWML]" + output); - } public static void Load() { - Log("Starting mod loader..."); + string logfile = null; +#if DEBUG + logfile = ModLoader.LogPath + "\\modloader.log"; +#endif + ModLogger logger = new ModLogger("[BWML]", logfile); + logger.ClearLog(); + logger.Log("Starting mod loader..."); + logger.DebugLog("Mods dir: " + ModLoader.ModsPath); - string dllpath = new System.Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath; - string Path = new FileInfo(dllpath).Directory.FullName; - string modsPath = Path + "\\Mods"; - string assetsPath = modsPath + "\\Assets"; - - Log("Dll dir: "+Path); - Log("Mods dir: "+modsPath); - - if (!Directory.Exists(modsPath)) + if (!Directory.Exists(ModLoader.ModsPath)) { - Directory.CreateDirectory(modsPath); + Directory.CreateDirectory(ModLoader.ModsPath); } - if (!Directory.Exists(assetsPath)) + if (!Directory.Exists(ModLoader.AssetsPath)) { - Directory.CreateDirectory(assetsPath); + Directory.CreateDirectory(ModLoader.AssetsPath); } - DirectoryInfo d = new DirectoryInfo(modsPath); - - GameObject modObjects = new GameObject(); - //For each DLL in "Blackwake/Blackwake_Data/Managed/Mods/" //Open them, Get the mod class, then add it in the game. - foreach (var file in d.GetFiles("*.dll")) + ModLoader loader = new ModLoader(logger); + ModLoader.Instance = loader; + loader.RefreshModFiles(); + foreach (FileInfo file in loader.GetAllMods().Keys) { - try - { - Assembly modDll = Assembly.LoadFrom(modsPath + "/" + file.Name); - Type[] modType = modDll.GetTypes(); - foreach (Type t in modType) - { - Log("Found type in " + file.Name + ": " + t.Name); - if (t.IsClass && t.IsSubclassOf(typeof(MonoBehaviour))) - { - modObjects.AddComponent(t); - Log("Loaded '" + t.Name + "' in " + file.Name); - } - } - } - catch (Exception e) - { - Log("Exception raised while loading mod " + file.Name); - Log(e.Message); - Log("Skipped loading this mod"); - } + loader.Load(file); } - Log("All Mods have been Loaded!"); - modObjects.AddComponent(); - Log("GUI has been loaded"); + logger.Log("All Mods have been Loaded!"); + loader.ModObjects.AddComponent(); + logger.Log("GUI has been loaded"); //Keep mods active - UnityEngine.Object.DontDestroyOnLoad(modObjects); + UnityEngine.Object.DontDestroyOnLoad(loader.ModObjects); } } } diff --git a/src/ModGUI.cs b/src/ModGUI.cs index 3c3fd32..c8c8cff 100644 --- a/src/ModGUI.cs +++ b/src/ModGUI.cs @@ -1,14 +1,143 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.IO; +using System.Linq; using UnityEngine; -namespace ModGUI +namespace BWModLoader.ModGUI { + public enum ScreenType + { + MOD, + LOG + } + + /// + /// Manages contents and behaviour of the Debug Window + /// public class ModGUI : MonoBehaviour { + static bool debugEnabled; + static int currentScreen; + static Vector2 scrollPosition; + static Vector2 size; + static Vector2 position; + + void Start() + { + currentScreen = (int)ScreenType.MOD; + scrollPosition = Vector2.zero; + debugEnabled = false; + size = new Vector2(1000, 1000); + position = new Vector2((Screen.width / 2) - (size.x / 2), + (Screen.height / 2) - (size.x / 2)); + } + + /// + /// Toggles the Window when the Hotkey is pressed + /// + void Update() + { + if (Input.GetKeyUp("insert")) + { + debugEnabled = !debugEnabled; + } + + } + + /// + /// Code that executes if the window was opened + /// void OnGUI() { - GUI.color = Color.red; - GUI.Label(new Rect(5f, 0f, 200f, 20f), "ModLoader v0.3"); + if (debugEnabled) + { + GUI.ModalWindow(0, new Rect(position, size), DebugWindow, "[BWML]Debug Menu"); + } + } + + /// + /// Manages contents of the window + /// + /// ID of the Debug Window + void DebugWindow(int windowID) + { + GUI.DragWindow(new Rect(0, 0, 10000, 20)); + currentScreen = GUI.SelectionGrid(new Rect(25, 25, size.x - 50, 75), currentScreen, + new string[] { "Mods", "Logs" }, 2); + if (currentScreen == (int)ScreenType.MOD) + { + ModWindow(); + } + else + { + LogWindow(); + } + } + + /// + /// Logging window content + /// + void LogWindow() + { + GUI.Label(new Rect(0, 100, 100, 25), "LogWindow"); + int logNum = 0; + if (ModLoader.Instance.Logger.Logs.Any()) + { + scrollPosition = GUI.BeginScrollView(new Rect(0, 100, size.x, size.y - 100), scrollPosition, new Rect(0, 0, size.x, 50)); + foreach (string log in ModLoader.Instance.Logger.Logs) + { + logNum++; + GUI.Label(new Rect(0, 25 * logNum, 1000, 25), log); + } + GUI.EndScrollView(); + } + } + + /// + /// Mod window content + /// + void ModWindow() + { + if (GUI.Button(new Rect(0, 100, 100, 25), "Reload all mods")) + { + ModLoader.Instance.RefreshModFiles(); + } + + scrollPosition = GUI.BeginScrollView(new Rect(0, 100, size.x, size.y - 100), scrollPosition, new Rect(0, 0, size.x, 50)); + int modNum = 0; + var allmods = ModLoader.Instance.GetAllMods(); + foreach (FileInfo file in allmods.Keys) + { + foreach (Type mod in allmods[file]) + { + modNum++; + //GUI.Label(new Rect(0, modNum * 25, 100, 25), mod.Name); + //GUI.Toggle(new Rect(0, modNum * 25, 100, 25), ModLoader.Instance.IsLoaded(mod), mod.Name); + bool newCheckboxStatus = GUI.Toggle(new Rect(20, modNum * 25, 100, 25), ModLoader.Instance.IsLoaded(mod), mod.Name); + if (newCheckboxStatus) + { + if (!ModLoader.Instance.IsLoaded(mod)) + { + ModLoader.Instance.Load(file); + } + } + else + { + if (ModLoader.Instance.IsLoaded(mod)) + { + ModLoader.Instance.Unload(file); + } + } + + // Reload the respective Mod + if (GUI.Button(new Rect(200, modNum * 25, 100, 25), "Reload")) + { + ModLoader.Instance.RefreshModFiles(); + } + } + } + GUI.EndScrollView(); } } } \ No newline at end of file diff --git a/src/ModLoader.cs b/src/ModLoader.cs new file mode 100644 index 0000000..aa5ef07 --- /dev/null +++ b/src/ModLoader.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using UnityEngine; + +namespace BWModLoader +{ + public class ModLoader + { + public static ModLoader Instance { get; internal set; } + + private static readonly string dllpath = new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath; + private static readonly string folderPath = Path.GetDirectoryName(dllpath); + public static string ModsPath => folderPath + "\\Mods"; + public static string AssetsPath => ModsPath + "\\Assets"; + public static string LogPath => ModsPath + "\\Logs"; + + public ModLogger Logger; + + /// + /// All known mods files + /// + private readonly Dictionary> allMods = new Dictionary>(); + + /// + /// Gets all known mods files + /// + /// Prevent modification from outside by making copy + public Dictionary> GetAllMods() => new Dictionary>(allMods); + + /// + /// GameObject that holds our mods + /// + public GameObject ModObjects { get; } = new GameObject(); + + public ModLoader(ModLogger logger) + { + this.Logger = logger; + } + + /// + /// Checks if a mod is loaded + /// + /// + /// + public bool IsLoaded(Type mod) + { + return ModObjects.GetComponent(mod) != null; + } + + /// + /// Refresh all known mods + /// + public void RefreshModFiles() + { + DirectoryInfo dir = new DirectoryInfo(ModsPath); + //Unloads & clears known mods + foreach (var mod in allMods) + { + Unload(mod.Key); + } + allMods.Clear(); + + //Find all files to refresh + foreach (FileInfo file in dir.GetFiles("*.dll")) + { + //Save mod types and file path + allMods.Add(file, LoadModTypes(file)); + Logger.Log("Found dll: " + file.Name); + } + } + + /// + /// Finds and loads all mod classes in a file + /// + /// The file to load + /// + private List LoadModTypes(FileInfo file) + { + List mods = new List(); + try + { + Assembly modDll = Assembly.LoadFrom(file.FullName); + Type[] modType = modDll.GetTypes(); + foreach (Type t in modType) + { + Logger.Log("Found type in " + file.Name + ": " + t.Name); + if (t.IsClass && typeof(MonoBehaviour).IsAssignableFrom(t) && !t.IsAbstract && t.IsPublic) + { + mods.Add(t); + } + } + } + catch (Exception e) + { + Logger.Log("Exception raised while loading mod " + file.Name); + Logger.Log(e.Message); + Logger.Log("Skipped loading this mod"); + } + return mods; + } + + /// + /// will load a mod from memory + /// + /// + public void Load(FileInfo file) + { + if (allMods.TryGetValue(file, out var types)) + { + foreach (Type mod in types) + { + //if mod is not loaded + if (!IsLoaded(mod)) + { + ModObjects.AddComponent(mod); + Logger.Log("Loaded: " + mod.Name + " From file: " + file.Name); + } + } + } + } + + //Unloads a mod from game + public void Unload(FileInfo file) + { + if (allMods.TryGetValue(file, out var types)) + { + foreach (Type mod in types) + { + //if mod is loaded + if (IsLoaded(mod)) + { + UnityEngine.Object.Destroy(ModObjects.GetComponent(mod)); + Logger.Log("Unloaded: " + mod.Name + " From file: " + file.Name); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ModLogger.cs b/src/ModLogger.cs new file mode 100644 index 0000000..927d7f2 --- /dev/null +++ b/src/ModLogger.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace BWModLoader +{ + /// + /// Handles logging + /// + public class ModLogger + { + string prefix; + string file; + + /// + /// Log history + /// + public List Logs { get; } = new List(); + + public ModLogger(string prefix, string file = null) + { + this.prefix = prefix; + this.file = file; + if (!string.IsNullOrEmpty(file) && !File.Exists(file)) + { + Directory.CreateDirectory(Path.GetDirectoryName(file)); + File.WriteAllText(file, ""); + } + } + + /// + /// Clears ingame log and log files + /// + public void ClearLog() + { + Logs.Clear(); + if (!string.IsNullOrEmpty(file) && File.Exists(file)) + { + File.Delete(file); + } + } + + /// + /// Log to debugger and output.txt + /// + public void Log(string output) + { + Console.WriteLine(prefix + output); + Logs.Add(prefix + output); + if (!string.IsNullOrEmpty(file)) + { + File.AppendAllText(file, prefix + output + Environment.NewLine); + } + } + + /// + /// Only logs messages in Debug builds of the modloader + /// + public void DebugLog(string output) + { +#if DEBUG + Log(output); +#endif // DEBUG + } + } +}