diff --git a/KK_ReloadCharaListOnChange/KK_ReloadCharaListOnChange.cs b/KK_ReloadCharaListOnChange/KK_ReloadCharaListOnChange.cs index 0278550a..cb8f1174 100644 --- a/KK_ReloadCharaListOnChange/KK_ReloadCharaListOnChange.cs +++ b/KK_ReloadCharaListOnChange/KK_ReloadCharaListOnChange.cs @@ -3,9 +3,7 @@ using ChaCustom; using Harmony; using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Reflection; using System.Threading; using UnityEngine.SceneManagement; @@ -14,29 +12,32 @@ namespace KK_ReloadCharaListOnChange { /// - /// Watches the character folders for changes and updates the character/coordinate list in the chara maker. - /// Probably should be expanded to support studio lists too. + /// Watches the character folders for changes and updates the character/coordinate list in the chara maker and studio. /// [BepInDependency("com.bepis.bepinex.sideloader")] [BepInPlugin("com.deathweasel.bepinex.reloadcharalistonchange", "Reload Chara List On Change", Version)] public class KK_ReloadCharaListOnChange : BaseUnityPlugin { - public const string Version = "1.3"; + public const string Version = "1.4"; private static FileSystemWatcher CharacterCardWatcher; private static FileSystemWatcher CoordinateCardWatcher; + private static FileSystemWatcher StudioFemaleCardWatcher; + private static FileSystemWatcher StudioMaleCardWatcher; + private static FileSystemWatcher StudioCoordinateCardWatcher; private static readonly string FemaleCardPath = Path.Combine(Paths.GameRootPath, "UserData\\chara\\female"); private static readonly string MaleCardPath = Path.Combine(Paths.GameRootPath, "UserData\\chara\\male"); private static readonly string CoordinateCardPath = Path.Combine(Paths.GameRootPath, "UserData\\coordinate"); - private static List CharacterCardEventList = new List(); - private static List CoordinateCardEventList = new List(); private static bool DoRefresh = false; private static bool EventFromCharaMaker = false; private static bool InCharaMaker = false; - private static CustomCharaFile listCtrlCharacter; - private static CustomFileListCtrl listCtrlCoordinate; - private static List lstFileInfoCoordinate; + private static CustomCharaFile CustomCharaFileInstance; + private static CustomCoordinateFile CustomCoordinateFileInstance; + private static Studio.CharaList StudioFemaleListInstance; + private static Studio.CharaList StudioMaleListInstance; + private static object StudioCoordinateListInstance; private static Timer CardTimer; - private static ReaderWriterLockSlim rwlock = new ReaderWriterLockSlim(); + private static CardEventType EventType; + private static readonly ReaderWriterLockSlim rwlock = new ReaderWriterLockSlim(); public void Main() { @@ -44,64 +45,23 @@ public void Main() var harmony = HarmonyInstance.Create("com.deathweasel.bepinex.reloadcharalistonchange"); harmony.PatchAll(typeof(KK_ReloadCharaListOnChange)); + harmony.Patch(typeof(Studio.MPCharCtrl).GetNestedType("CostumeInfo", BindingFlags.NonPublic).GetMethod("InitFileList", AccessTools.all), + new HarmonyMethod(typeof(KK_ReloadCharaListOnChange).GetMethod(nameof(StudioCoordinateListPrefix), AccessTools.all)), null); } /// - /// When cards are added or removed from the folder create a list of them + /// When cards are added or removed from the folder set a flag /// - private static void CreateCharacterEventLists(object sender, FileSystemEventArgs args) + private static void CardEvent(CardEventType eventType) { - try - { - //Needs to be locked since dumping a bunch of cards in the folder will trigger this event a whole bunch of times that all run at once - //which sometimes ends up with the list being modified while we're cycling through it later, which is very bad - rwlock.EnterWriteLock(); + //Needs to be locked since dumping a bunch of cards in the folder will trigger this event a whole bunch of times that all run at once + rwlock.EnterWriteLock(); - //Start a timer which will be reset every time a card is added/removed for when the user dumps in a whole bunch at once - //Once the timer elapses, a flag will be set to do the refresh on all the cards that were add/remove at once - if (CardTimer == null) - { - //First file, start timer - CardTimer = new Timer(1000); - CardTimer.Elapsed += (o, ee) => DoRefresh = true; - CardTimer.Start(); - } - else - { - //Subsequent file, reset timer - CardTimer.Stop(); - CardTimer.Start(); - } - - string CardPath = args.FullPath; - string CardName = CardPath.Remove(0, CardPath.LastIndexOf('\\') + 1); - CardName = CardName.Remove(CardName.IndexOf('.')); - - CharacterCardEventList.Add(new CardEventInfo(CardName, CardPath, args.ChangeType)); - } - catch (Exception ex) - { - Logger.Log(LogLevel.Error, ex); - CardTimer?.Dispose(); - CharacterCardEventList.Clear(); - } - finally - { - rwlock.ExitWriteLock(); - } - } - /// - /// When cards are added or removed from the folder create a list of them - /// - private static void CreateCoordinateEventLists(object sender, FileSystemEventArgs args) - { try { - //Needs to be locked since dumping a bunch of cards in the folder will trigger this event a whole bunch of times that all run at once - //which sometimes ends up with the list being modified while we're cycling through it later, which is very bad - rwlock.EnterWriteLock(); + EventType = eventType; //Start a timer which will be reset every time a card is added/removed for when the user dumps in a whole bunch at once - //Once the timer elapses, a flag will be set to do the refresh on all the cards that were add/remove at once + //Once the timer elapses, a flag will be set to do the refresh, which will then happen on the next Update. if (CardTimer == null) { //First file, start timer @@ -115,39 +75,48 @@ private static void CreateCoordinateEventLists(object sender, FileSystemEventArg CardTimer.Stop(); CardTimer.Start(); } - - string CardPath = args.FullPath; - string CardName = CardPath.Remove(0, CardPath.LastIndexOf('\\') + 1); - CardName = CardName.Remove(CardName.IndexOf('.')); - - CoordinateCardEventList.Add(new CardEventInfo(CardName, CardPath, args.ChangeType)); } catch (Exception ex) { Logger.Log(LogLevel.Error, ex); CardTimer?.Dispose(); - CharacterCardEventList.Clear(); - } - finally - { - rwlock.ExitWriteLock(); } + + rwlock.ExitWriteLock(); } /// - /// Add or remove the cards from the list, then refresh the list + /// Refresh the list /// - private static void RefreshCharacterList() + private static void RefreshList() { try { //Turn off resolving to prevent spam since modded stuff isn't relevent for making this list. Sideloader.AutoResolver.Hooks.IsResolving = false; - - typeof(CustomCharaFile).GetMethod("Initialize", BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(listCtrlCharacter, null); + switch (EventType) + { + case CardEventType.CharaMakerCharacter: + typeof(CustomCharaFile).GetMethod("Initialize", AccessTools.all)?.Invoke(CustomCharaFileInstance, null); + break; + case CardEventType.CharaMakerCoordinate: + typeof(CustomCoordinateFile).GetMethod("Initialize", AccessTools.all)?.Invoke(CustomCoordinateFileInstance, null); + break; + case CardEventType.StudioFemale: + StudioFemaleListInstance.InitCharaList(true); + break; + case CardEventType.StudioMale: + StudioMaleListInstance.InitCharaList(true); + break; + case CardEventType.StudioCoordinate: + var sex = Traverse.Create(StudioCoordinateListInstance).Field("sex").GetValue(); + typeof(Studio.MPCharCtrl).GetNestedType("CostumeInfo", BindingFlags.NonPublic).GetMethod("InitList", AccessTools.all)?.Invoke(StudioCoordinateListInstance, new object[] { 100 }); + Traverse.Create(StudioCoordinateListInstance).Field("sex").SetValue(sex); + break; + } } catch (Exception ex) { - Logger.Log(LogLevel.Error | LogLevel.Message, "An error occured attempting to refresh the character list. Please restart the chara maker."); + Logger.Log(LogLevel.Error | LogLevel.Message, "An error occured attempting to refresh the list."); Logger.Log(LogLevel.Error, $"KK_ReloadCharaListOnChange error: {ex.Message}"); Logger.Log(LogLevel.Error, ex); } @@ -157,89 +126,26 @@ private static void RefreshCharacterList() } } /// - /// Add or remove the cards from the list, then refresh the list - /// - private static void RefreshCoordinateList() - { - bool DidAddOrRemove = false; - - //Turn off resolving to prevent spam since modded stuff isn't relevent for making this list. - Sideloader.AutoResolver.Hooks.IsResolving = false; - - try - { - foreach (CardEventInfo CardEvent in CoordinateCardEventList) - { - if (CardEvent.EventType == WatcherChangeTypes.Deleted) - { - CustomFileInfo CardInfo = lstFileInfoCoordinate.FirstOrDefault(x => x.FileName == CardEvent.CardName); - if (CardInfo == null) - Logger.Log(LogLevel.Warning, $"{CardEvent.CardName}.png was removed from the folder but could not be found on character list, skipping."); - else - { - listCtrlCoordinate.RemoveList(CardInfo.index); - DidAddOrRemove = true; - } - } - else if (CardEvent.EventType == WatcherChangeTypes.Created) - { - ChaFileCoordinate AddedCoordinate = new ChaFileCoordinate(); - - if (!AddedCoordinate.LoadFile(CardEvent.CardPath)) - { - Logger.Log(LogLevel.Warning | LogLevel.Message, $"{CardEvent.CardName}.png is not a coordinate card."); - } - else - { - DateTime CardTime = File.GetLastWriteTime(CardEvent.CardPath); - - //Find the highest index to use for our new index - int Index = lstFileInfoCoordinate.Count == 0 ? 0 : lstFileInfoCoordinate.Max(x => x.index) + 1; - - listCtrlCoordinate.AddList(Index, AddedCoordinate.coordinateName, string.Empty, string.Empty, CardEvent.CardPath, CardEvent.CardName, CardTime); - DidAddOrRemove = true; - } - } - } - if (DidAddOrRemove) - listCtrlCoordinate.ReCreate(); - } - catch (Exception ex) - { - Logger.Log(LogLevel.Error | LogLevel.Message, "An error occured attempting to refresh the coordinate list. Please restart the chara maker."); - Logger.Log(LogLevel.Error, $"KK_ReloadCharaListOnChange error: {ex.Message}"); - Logger.Log(LogLevel.Error, ex); - } - Sideloader.AutoResolver.Hooks.IsResolving = true; - } - /// - /// On a game update run the actual refresh. It must be run from an update or it causes all sorts of errors for reasons I can't figure out. + /// On a game update run the actual refresh. It must be run from an update or it causes all sorts of errors. /// private void Update() { if (EventFromCharaMaker && DoRefresh) { - //If we saved or deleted a card from the chara maker itself clear the events so cards don't get added twice - CharacterCardEventList.Clear(); - CoordinateCardEventList.Clear(); + //If we saved or deleted a card from the chara maker itself there's no need to refresh, the game will handle that. CardTimer.Dispose(); EventFromCharaMaker = false; DoRefresh = false; } else if (DoRefresh) { - if (CharacterCardEventList.Count > 0) - RefreshCharacterList(); - if (CoordinateCardEventList.Count > 0) - RefreshCoordinateList(); - CharacterCardEventList.Clear(); - CoordinateCardEventList.Clear(); + RefreshList(); CardTimer.Dispose(); DoRefresh = false; } } /// - /// Destroy the file watcher when the chara maker ends + /// End the file watcher and set variables back to default for next time the chara maker is started /// private void SceneUnloaded(Scene s) { @@ -249,47 +155,32 @@ private void SceneUnloaded(Scene s) DoRefresh = false; EventFromCharaMaker = false; CardTimer?.Dispose(); - CharacterCardWatcher.Dispose(); - CharacterCardEventList.Clear(); - CoordinateCardWatcher.Dispose(); - CoordinateCardEventList.Clear(); - } - } - - private class CardEventInfo - { - public string CardName; - public string CardPath; - public WatcherChangeTypes EventType; - - public CardEventInfo(string cardName, string cardPath, WatcherChangeTypes eventType) - { - CardName = cardName; - CardPath = cardPath; - EventType = eventType; + CardTimer = null; + CharacterCardWatcher?.Dispose(); + CharacterCardWatcher = null; + CoordinateCardWatcher?.Dispose(); + CoordinateCardWatcher = null; } } /// - /// When saving the a new character card in game set a flag + /// When saving a new character card in game set a flag /// [HarmonyPrefix] [HarmonyPatch(typeof(ChaFileControl), "SaveCharaFile", new[] { typeof(BinaryWriter), typeof(bool) })] public static void SaveCharaFilePrefix() { - if (InCharaMaker) - if (Singleton.Instance.customCtrl.saveNew == true) - EventFromCharaMaker = true; + if (InCharaMaker && Singleton.Instance.customCtrl.saveNew == true) + EventFromCharaMaker = true; } /// - /// When saving the a new coordinate card in game set a flag + /// When saving a new coordinate card in game set a flag /// [HarmonyPrefix] [HarmonyPatch(typeof(ChaFileCoordinate), nameof(ChaFileCoordinate.SaveFile), new[] { typeof(string) })] public static void SaveCoordinateFilePrefix(string path) { - if (InCharaMaker) - if (!File.Exists(path)) //saving new - EventFromCharaMaker = true; + if (InCharaMaker && !File.Exists(path)) + EventFromCharaMaker = true; } /// /// When deleting a chara card in game set a flag @@ -318,17 +209,19 @@ public static void DeleteCoordinateFilePrefix() [HarmonyPatch(typeof(CustomCharaFile), "Initialize")] public static void CustomCharaFileInitializePrefix(CustomCharaFile __instance) { - //Get some references to fields we'll be using later on - listCtrlCharacter = __instance; + if (CharacterCardWatcher == null) + { + CustomCharaFileInstance = __instance; - CharacterCardWatcher = new FileSystemWatcher(); - CharacterCardWatcher.Path = Singleton.Instance.modeSex == 0 ? MaleCardPath : FemaleCardPath; - CharacterCardWatcher.NotifyFilter = NotifyFilters.FileName; - CharacterCardWatcher.Filter = "*.png"; - CharacterCardWatcher.EnableRaisingEvents = true; - CharacterCardWatcher.Created += new FileSystemEventHandler(CreateCharacterEventLists); - CharacterCardWatcher.Deleted += new FileSystemEventHandler(CreateCharacterEventLists); - CharacterCardWatcher.IncludeSubdirectories = true; + CharacterCardWatcher = new FileSystemWatcher(); + CharacterCardWatcher.Path = Singleton.Instance.modeSex == 0 ? MaleCardPath : FemaleCardPath; + CharacterCardWatcher.NotifyFilter = NotifyFilters.FileName; + CharacterCardWatcher.Filter = "*.png"; + CharacterCardWatcher.EnableRaisingEvents = true; + CharacterCardWatcher.Created += (o, ee) => CardEvent(CardEventType.CharaMakerCharacter); + CharacterCardWatcher.Deleted += (o, ee) => CardEvent(CardEventType.CharaMakerCharacter); + CharacterCardWatcher.IncludeSubdirectories = true; + } InCharaMaker = true; } @@ -339,17 +232,80 @@ public static void CustomCharaFileInitializePrefix(CustomCharaFile __instance) [HarmonyPatch(typeof(CustomCoordinateFile), "Initialize")] public static void CustomCoordinateFileInitializePrefix(CustomCoordinateFile __instance) { - //Get some references to fields we'll be using later on - listCtrlCoordinate = Traverse.Create(__instance).Field("listCtrl").GetValue(); - lstFileInfoCoordinate = Traverse.Create(listCtrlCoordinate).Field("lstFileInfo").GetValue>(); + if (CoordinateCardWatcher == null) + { + CustomCoordinateFileInstance = __instance; + + CoordinateCardWatcher = new FileSystemWatcher(); + CoordinateCardWatcher.Path = CoordinateCardPath; + CoordinateCardWatcher.NotifyFilter = NotifyFilters.FileName; + CoordinateCardWatcher.Filter = "*.png"; + CoordinateCardWatcher.EnableRaisingEvents = true; + CoordinateCardWatcher.Created += (o, ee) => CardEvent(CardEventType.CharaMakerCoordinate); + CoordinateCardWatcher.Deleted += (o, ee) => CardEvent(CardEventType.CharaMakerCoordinate); + CoordinateCardWatcher.IncludeSubdirectories = true; + } + } + /// + /// Initialize the file watcher once the list has been initiated + /// + [HarmonyPrefix, HarmonyPatch(typeof(Studio.CharaList), "InitFemaleList")] + public static void StudioFemaleListPrefix(Studio.CharaList __instance) + { + if (StudioFemaleCardWatcher == null) + { + StudioFemaleListInstance = __instance; + + StudioFemaleCardWatcher = new FileSystemWatcher(); + StudioFemaleCardWatcher.Path = FemaleCardPath; + StudioFemaleCardWatcher.NotifyFilter = NotifyFilters.FileName; + StudioFemaleCardWatcher.Filter = "*.png"; + StudioFemaleCardWatcher.EnableRaisingEvents = true; + StudioFemaleCardWatcher.Created += (o, ee) => CardEvent(CardEventType.StudioFemale); + StudioFemaleCardWatcher.Deleted += (o, ee) => CardEvent(CardEventType.StudioFemale); + StudioFemaleCardWatcher.IncludeSubdirectories = true; + } + } + /// + /// Initialize the file watcher once the list has been initiated + /// + [HarmonyPrefix, HarmonyPatch(typeof(Studio.CharaList), "InitMaleList")] + public static void StudioMaleListPrefix(Studio.CharaList __instance) + { + if (StudioMaleCardWatcher == null) + { + StudioMaleListInstance = __instance; + + StudioMaleCardWatcher = new FileSystemWatcher(); + StudioMaleCardWatcher.Path = MaleCardPath; + StudioMaleCardWatcher.NotifyFilter = NotifyFilters.FileName; + StudioMaleCardWatcher.Filter = "*.png"; + StudioMaleCardWatcher.EnableRaisingEvents = true; + StudioMaleCardWatcher.Created += (o, ee) => CardEvent(CardEventType.StudioMale); + StudioMaleCardWatcher.Deleted += (o, ee) => CardEvent(CardEventType.StudioMale); + StudioMaleCardWatcher.IncludeSubdirectories = true; + } + } + /// + /// Initialize the file watcher once the list has been initiated + /// + public static void StudioCoordinateListPrefix(object __instance) + { + if (StudioCoordinateCardWatcher == null) + { + StudioCoordinateListInstance = __instance; - CoordinateCardWatcher = new FileSystemWatcher(); - CoordinateCardWatcher.Path = CoordinateCardPath; - CoordinateCardWatcher.NotifyFilter = NotifyFilters.FileName; - CoordinateCardWatcher.Filter = "*.png"; - CoordinateCardWatcher.EnableRaisingEvents = true; - CoordinateCardWatcher.Created += new FileSystemEventHandler(CreateCoordinateEventLists); - CoordinateCardWatcher.Deleted += new FileSystemEventHandler(CreateCoordinateEventLists); + StudioCoordinateCardWatcher = new FileSystemWatcher(); + StudioCoordinateCardWatcher.Path = CoordinateCardPath; + StudioCoordinateCardWatcher.NotifyFilter = NotifyFilters.FileName; + StudioCoordinateCardWatcher.Filter = "*.png"; + StudioCoordinateCardWatcher.EnableRaisingEvents = true; + StudioCoordinateCardWatcher.Created += (o, ee) => CardEvent(CardEventType.StudioCoordinate); + StudioCoordinateCardWatcher.Deleted += (o, ee) => CardEvent(CardEventType.StudioCoordinate); + StudioCoordinateCardWatcher.IncludeSubdirectories = true; + } } + + public enum CardEventType { CharaMakerCharacter, CharaMakerCoordinate, StudioMale, StudioFemale, StudioCoordinate } } }