using System; using System.Collections; using System.Collections.Generic; using System.Text; using UnityEngine; using System.Threading.Tasks; using System.IO; using System.Reflection; using System.Linq; [ModTitle ("DevTools")] [ModDescription ("This mod re-adds the DevTools that were removed in RML 4.1.3.")] [ModAuthor ("traxam")] [ModIconUrl ("")] [ModWallpaperUrl ("")] [ModVersion ("0.1.0")] [RaftVersion ("Update 9 (3602784)")] public class DevToolsMod : Mod { private ModInfo HotloadedModInfo = null; private GameObject HotloadedModGO = null; private void Start () { RConsole.registerCommand (typeof (DevToolsMod), "Access the DevTools.", "hotload", HandleHotloadCommand); RConsole.registerCommand (typeof (DevToolsMod), "Do stuff with RML's mod list.", "mods", HandleModsCommand); Info("Mod has loaded!"); } private async void HandleModsCommand() { MainMenu mainMenu = ComponentManager.Value; if (RConsole.lcargs.Length == 1) { Info("Mod list:"); foreach(ModInfo modInfo in mainMenu.modList) { FollowUpLog("- " + modInfo.modName); } } else { switch(RConsole.lcargs[1]) { case "help": Info("Mod list commands:"); FollowUpLog("- mods help\t\t\t\tDisplays this help message."); FollowUpLog("- mods refresh\t\t\tRe-loads the mod list from the default mods directory."); FollowUpLog("- mods reload \tRe-loads the mod list from the default mods directory."); break; case "refresh": Info("Refreshing mod list from default mods directory..."); await mainMenu.RefreshMods(true); break; case "reload": if (RConsole.lcargs.Length == 2) { Error("Please specify the mod you want to reload using 'mods reload '."); } else { string modName = RConsole.lcargs[2]; ModInfo modToReload = null; foreach(ModInfo modInfo in mainMenu.modList) { if (modInfo.modName == modName) { modToReload = modInfo; break; } } if (modToReload == null) { Error("Cannot reload mod '" + modName + "'. No mod with this name was found."); } else { Info("Reloading mod '" + modName + "'..."); mainMenu.UnloadMod(modToReload); await Task.Delay(1000); // Wait for mod to be unloaded await mainMenu.LoadMod(modToReload); await Task.Delay(1000); // Wait for mod to be loaded Info("Reloading mod '" + modName + "' was successful."); } } break; default: Error("There is no command 'mods " + RConsole.lcargs[1] + "'. Use 'mods help' for a list of sub-commands."); break; } } } private void Info(string message) { RConsole.Log("[info]\ttraxam's DevTools: " + message); } private void FollowUpLog(string message) { RConsole.Log("\t" + message); } private void Error(string message) { RConsole.LogError("[error]\ttraxam's DevTools: " + message); } private async void HandleHotloadCommand () { if (RConsole.lcargs.Length == 1) { if (HotloadedModInfo == null) { Info("There currently is not mod loaded using hotload. Use hotload to load one."); } else { Info("Currently loaded hotload mod: " + HotloadedModInfo.modName + ""); FollowUpLog("Use hotload unload to unload this mod."); } } else if (RConsole.lcargs[1] == "unload") { UnloadHotloadedMod(); } else if (RConsole.lcargs.Length == 2) { string filePath = RConsole.lcargs[1]; HotloadMod(filePath); } else { Error("Usage: devtools "); } } private IEnumerator WatchHotloadFile() { Info("Starting file watcher for hotloaded mod file..."); while (HotloadedModInfo != null) { string newHash = MainMenu.CalculateMD5(HotloadedModInfo.modFile); if (newHash != HotloadedModInfo.fileHash) { HotloadedModInfo.fileHash = newHash; Info("Hotloaded mod '" + HotloadedModInfo.modName + "' was changed, reloading..."); string file = HotloadedModInfo.modFile; UnloadHotloadedMod(); HotloadMod(file); } yield return new WaitForSeconds(2.0f); } Info("Hotloaded mod file watcher stopped."); } private async void HotloadMod(string filePath) { if (HotloadedModInfo != null) { Error("Tried to hotload a mod, but there already is a mod loaded using hotload!"); } else { Info("Hotloading '" + filePath + "'..."); // check file existence if (!File.Exists(filePath)) { Error("The file '" + filePath + "' does not exist!"); return; } else if(Path.GetExtension(filePath) != ".cs") { Error("'" + filePath + "' is not a .cs file!"); return; } // load mod from specified file path ModInfo modInfo = null; try { // construct mod info modInfo = new ModInfo() { modType = ModInfo.ModTypeEnum.rawfile, modFile = filePath }; // insert file hash modInfo.fileHash = MainMenu.CalculateMD5(modInfo.modFile); // attribute list? List atr = new List() { "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown" }; string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RaftModLoader/CSModsCache", modInfo.fileHash + ".rmlmod"); FollowUpLog("Compiling '" + modInfo.modFile + "'..."); // if already exists --> unchanged, just load assembly if (File.Exists(path)) { FollowUpLog("'" + modInfo.modFile + "' (" + modInfo.fileHash + ") was already compiled, loading..."); modInfo.assembly = Assembly.Load(File.ReadAllBytes(path)); } else { modInfo.assembly = await RawSharp.CompileAssembly(modInfo.modFile); } FollowUpLog("Compilation succeeded."); // find all classes in assembly that extend Mod IEnumerable allTypes = (IEnumerable)modInfo.assembly.GetTypes(); IEnumerable source = allTypes.Where((Func)(t => t.IsSubclassOf(typeof(Mod)))); // check if there is *exactly* one mod in the assembly if (source.Count() != 1) { Error("The mod " + Path.GetFileName(modInfo.modFile) + " doesn't specify a mod class or specifies more than one"); modInfo.modState = ModInfo.ModStateEnum.errored; } else { atr = source.First().GetModInfo(); modInfo.modName = atr[0]; modInfo.modDesc = atr[1]; modInfo.modAuthor = atr[2]; modInfo.modVersion = atr[3]; modInfo.raftVersion = atr[4]; modInfo.modIconUrl = atr[5]; modInfo.modWallpaperUrl = atr[6]; GameObject ModObj = new GameObject(); ModObj.transform.parent = transform; ModObj.name = Path.GetFileName(modInfo.modFile); ModObj.AddComponent(source.First()); HotloadedModGO = ModObj; modInfo.modState = ModInfo.ModStateEnum.running; HotloadedModInfo = modInfo; StartCoroutine(WatchHotloadFile()); FollowUpLog(modInfo.modFile + " will now be reloaded when changes are saved."); } } catch (Exception ex) { Error("Could not load '" + modInfo.modFile + "': " + ex.ToString()); if (modInfo != null) { modInfo.modState = ModInfo.ModStateEnum.errored; } } } } private void UnloadHotloadedMod() { // check whether there is a hotloaded mod if (HotloadedModInfo == null) Error("Tried to unload hotloaded mod, but there currently is no mod loaded using hotload!"); else { Info("Unloading hotloaded mod '" + HotloadedModInfo.modName + "'..."); // do nothing but status change if GO does not exist if (HotloadedModGO != null) { // find mod class type IEnumerable source = ((IEnumerable)HotloadedModInfo.assembly.GetTypes()).Where((Func)(t => t.IsSubclassOf(typeof(Mod)))); System.Type modType = source.First(); // perform 'friendly' unload calls if the mod still exists if (modType != null && HotloadedModGO.GetComponent(modType) != null) { UnregisterAllCommands(modType); CallOnModUnload(modType, (Mod) HotloadedModGO.GetComponent(modType)); } // destroy game object UnityEngine.Object.Destroy(HotloadedModGO.gameObject); HotloadedModGO = null; } HotloadedModInfo.modState = ModInfo.ModStateEnum.idle; Info("Hotloaded mod '" + HotloadedModInfo.modName + " was unloaded."); HotloadedModInfo = null; } } private void UnregisterAllCommands(System.Type modType) { List stringList = new List(); foreach (Command command in RConsole.commands) { if (command.mod != null && command.mod.Equals(modType)) stringList.Add(command.command); } foreach (string command in stringList) RConsole.unregisterCommand(command); } private void CallOnModUnload(System.Type type, Mod modInstance) { MethodInfo method = type.GetMethod("OnModUnload"); if (method == null) { Info("Hotloaded mod '" + HotloadedModInfo.modName + "' doesn't support unload! (Trying to manually unload it... Could lead into some issues...)"); } else { try { method.Invoke(modInstance, null); } catch (Exception e) { Error("An error occurred while unloading hotloaded mod: " + e.StackTrace); } } } public void OnModUnload () { Info("Mod has been unloaded!"); if (HotloadedModInfo != null) { UnloadHotloadedMod(); } Destroy (gameObject); } }