From 26cd620fd94a97c2f9560c85c10bd59a1181666e Mon Sep 17 00:00:00 2001 From: Anairkoen Schno Date: Sun, 29 Jul 2018 22:09:05 -0500 Subject: [PATCH] Re-added support for legacy IPA plugins for backwards compatability --- IPA.sln => BSIPA.sln | 2 +- .../CompositeBSPlugin.cs} | 20 ++-- IllusionInjector/IPA/CompositeIPAPlugin.cs | 77 ++++++++++++ IllusionInjector/IllusionInjector.csproj | 3 +- IllusionInjector/PluginComponent.cs | 37 ++++-- IllusionInjector/PluginManager.cs | 110 ++++++++++++------ ...ionInjector.csproj.CoreCompileInputs.cache | 2 +- .../IBeatSaberPlugin.cs} | 4 +- .../BeatSaber/IEnhancedBeatSaberPlugin.cs | 10 ++ IllusionPlugin/IPA/IEnhancedPlugin.cs | 11 ++ .../IGenericEnhancedPlugin.cs} | 36 +++--- IllusionPlugin/IPA/IPlugin.cs | 58 +++++++++ IllusionPlugin/IllusionPlugin.csproj | 7 +- IllusionPlugin/Logger.cs | 6 +- IllusionPlugin/ModPrefs.cs | 6 +- ...usionPlugin.csproj.CoreCompileInputs.cache | 2 +- 16 files changed, 303 insertions(+), 88 deletions(-) rename IPA.sln => BSIPA.sln (98%) rename IllusionInjector/{CompositePlugin.cs => BeatSaber/CompositeBSPlugin.cs} (86%) create mode 100644 IllusionInjector/IPA/CompositeIPAPlugin.cs rename IllusionPlugin/{IPlugin.cs => BeatSaber/IBeatSaberPlugin.cs} (92%) create mode 100644 IllusionPlugin/BeatSaber/IEnhancedBeatSaberPlugin.cs create mode 100644 IllusionPlugin/IPA/IEnhancedPlugin.cs rename IllusionPlugin/{IEnhancedPlugin.cs => IPA/IGenericEnhancedPlugin.cs} (80%) create mode 100644 IllusionPlugin/IPA/IPlugin.cs diff --git a/IPA.sln b/BSIPA.sln similarity index 98% rename from IPA.sln rename to BSIPA.sln index 29b719c4..6323b2d4 100644 --- a/IPA.sln +++ b/BSIPA.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Blend for Visual Studio 15 +# Visual Studio 15 VisualStudioVersion = 15.0.27428.2043 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IPA", "IPA\IPA.csproj", "{14092533-98BB-40A4-9AFC-27BB75672A70}" diff --git a/IllusionInjector/CompositePlugin.cs b/IllusionInjector/BeatSaber/CompositeBSPlugin.cs similarity index 86% rename from IllusionInjector/CompositePlugin.cs rename to IllusionInjector/BeatSaber/CompositeBSPlugin.cs index e2285bdf..b2cb45a4 100644 --- a/IllusionInjector/CompositePlugin.cs +++ b/IllusionInjector/BeatSaber/CompositeBSPlugin.cs @@ -8,14 +8,15 @@ using UnityEngine.SceneManagement; using Logger = IllusionPlugin.Logger; namespace IllusionInjector { - public class CompositePlugin : IPlugin { - IEnumerable plugins; + public class CompositeBSPlugin : IBeatSaberPlugin + { + IEnumerable plugins; - private delegate void CompositeCall(IPlugin plugin); + private delegate void CompositeCall(IBeatSaberPlugin plugin); private Logger debugLogger => PluginManager.debugLogger; - public CompositePlugin(IEnumerable plugins) { + public CompositeBSPlugin(IEnumerable plugins) { this.plugins = plugins; } @@ -59,8 +60,7 @@ namespace IllusionInjector { } } } - - + private void Invoke(CompositeCall callback) { foreach (var plugin in plugins) { try { @@ -71,8 +71,7 @@ namespace IllusionInjector { } } } - - + public void OnUpdate() { Invoke(plugin => plugin.OnUpdate()); } @@ -81,7 +80,6 @@ namespace IllusionInjector { Invoke(plugin => plugin.OnFixedUpdate()); } - public string Name { get { throw new NotImplementedException(); } } @@ -92,8 +90,8 @@ namespace IllusionInjector { public void OnLateUpdate() { Invoke(plugin => { - if (plugin is IEnhancedPlugin) - ((IEnhancedPlugin) plugin).OnLateUpdate(); + if (plugin is IEnhancedBeatSaberPlugin) + ((IEnhancedBeatSaberPlugin) plugin).OnLateUpdate(); }); } } diff --git a/IllusionInjector/IPA/CompositeIPAPlugin.cs b/IllusionInjector/IPA/CompositeIPAPlugin.cs new file mode 100644 index 00000000..5b64bfaa --- /dev/null +++ b/IllusionInjector/IPA/CompositeIPAPlugin.cs @@ -0,0 +1,77 @@ +using IllusionPlugin; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.SceneManagement; +using Logger = IllusionPlugin.Logger; + +namespace IllusionInjector { +#pragma warning disable CS0618 // Type or member is obsolete + public class CompositeIPAPlugin : IPlugin + { + IEnumerable plugins; + + private delegate void CompositeCall(IPlugin plugin); + + private Logger debugLogger => PluginManager.debugLogger; + + public CompositeIPAPlugin(IEnumerable plugins) { + this.plugins = plugins; + } + + public void OnApplicationStart() { + Invoke(plugin => plugin.OnApplicationStart()); + } + + public void OnApplicationQuit() { + Invoke(plugin => plugin.OnApplicationQuit()); + } + + private void Invoke(CompositeCall callback) { + foreach (var plugin in plugins) { + try { + callback(plugin); + } + catch (Exception ex) { + debugLogger.Exception($"{plugin.Name}: {ex}"); + } + } + } + + public void OnUpdate() { + Invoke(plugin => plugin.OnUpdate()); + } + + public void OnFixedUpdate() { + Invoke(plugin => plugin.OnFixedUpdate()); + } + + public string Name { + get { throw new NotImplementedException(); } + } + + public string Version { + get { throw new NotImplementedException(); } + } + + public void OnLateUpdate() { + Invoke(plugin => { + if (plugin is IEnhancedBeatSaberPlugin) + ((IEnhancedBeatSaberPlugin) plugin).OnLateUpdate(); + }); + } + + public void OnLevelWasLoaded(int level) + { + Invoke(plugin => plugin.OnLevelWasLoaded(level)); + } + + public void OnLevelWasInitialized(int level) + { + Invoke(plugin => plugin.OnLevelWasInitialized(level)); + } + } +#pragma warning restore CS0618 // Type or member is obsolete +} \ No newline at end of file diff --git a/IllusionInjector/IllusionInjector.csproj b/IllusionInjector/IllusionInjector.csproj index 43c2ecff..acee2e3f 100644 --- a/IllusionInjector/IllusionInjector.csproj +++ b/IllusionInjector/IllusionInjector.csproj @@ -47,9 +47,10 @@ - + + diff --git a/IllusionInjector/PluginComponent.cs b/IllusionInjector/PluginComponent.cs index 877a59bb..19250623 100644 --- a/IllusionInjector/PluginComponent.cs +++ b/IllusionInjector/PluginComponent.cs @@ -8,7 +8,8 @@ namespace IllusionInjector { public class PluginComponent : MonoBehaviour { - private CompositePlugin plugins; + private CompositeBSPlugin bsPlugins; + private CompositeIPAPlugin ipaPlugins; private bool quitting = false; public static PluginComponent Create() @@ -20,8 +21,10 @@ namespace IllusionInjector { DontDestroyOnLoad(gameObject); - plugins = new CompositePlugin(PluginManager.Plugins); - plugins.OnApplicationStart(); + bsPlugins = new CompositeBSPlugin(PluginManager.BSPlugins); + ipaPlugins = new CompositeIPAPlugin(PluginManager.IPAPlugins); + bsPlugins.OnApplicationStart(); + ipaPlugins.OnApplicationStart(); SceneManager.activeSceneChanged += OnActiveSceneChanged; SceneManager.sceneLoaded += OnSceneLoaded; @@ -30,17 +33,20 @@ namespace IllusionInjector void Update() { - plugins.OnUpdate(); + bsPlugins.OnUpdate(); + ipaPlugins.OnUpdate(); } void LateUpdate() { - plugins.OnLateUpdate(); + bsPlugins.OnLateUpdate(); + ipaPlugins.OnLateUpdate(); } void FixedUpdate() { - plugins.OnFixedUpdate(); + bsPlugins.OnFixedUpdate(); + ipaPlugins.OnFixedUpdate(); } void OnDestroy() @@ -57,22 +63,33 @@ namespace IllusionInjector SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; - plugins.OnApplicationQuit(); + bsPlugins.OnApplicationQuit(); + ipaPlugins.OnApplicationQuit(); quitting = true; } + void OnLevelWasLoaded(int level) + { + ipaPlugins.OnLevelWasLoaded(level); + } + + public void OnLevelWasInitialized(int level) + { + ipaPlugins.OnLevelWasInitialized(level); + } + void OnSceneLoaded(Scene scene, LoadSceneMode sceneMode) { - plugins.OnSceneLoaded(scene, sceneMode); + bsPlugins.OnSceneLoaded(scene, sceneMode); } private void OnSceneUnloaded(Scene scene) { - plugins.OnSceneUnloaded(scene); + bsPlugins.OnSceneUnloaded(scene); } private void OnActiveSceneChanged(Scene prevScene, Scene nextScene) { - plugins.OnActiveSceneChanged(prevScene, nextScene); + bsPlugins.OnActiveSceneChanged(prevScene, nextScene); } } diff --git a/IllusionInjector/PluginManager.cs b/IllusionInjector/PluginManager.cs index 39bcf545..9996082f 100644 --- a/IllusionInjector/PluginManager.cs +++ b/IllusionInjector/PluginManager.cs @@ -13,24 +13,38 @@ namespace IllusionInjector { public static class PluginManager { - private static List _Plugins = null; +#pragma warning disable CS0618 // Type or member is obsolete (IPlugin) internal static readonly Logger debugLogger = new Logger("IllusionInjector"); /// /// Gets the list of loaded plugins and loads them if necessary. /// - public static IEnumerable Plugins + public static IEnumerable BSPlugins { get { - if(_Plugins == null) + if(_bsPlugins == null) { LoadPlugins(); } - return _Plugins; + return _bsPlugins; } } + private static List _bsPlugins = null; + + public static IEnumerable IPAPlugins + { + get + { + if (_ipaPlugins == null) + { + LoadPlugins(); + } + return _ipaPlugins; + } + } + private static List _ipaPlugins = null; private static void LoadPlugins() @@ -41,17 +55,20 @@ namespace IllusionInjector // so we need to resort to P/Invoke string exeName = Path.GetFileNameWithoutExtension(AppInfo.StartupPath); debugLogger.Log(exeName); - _Plugins = new List(); + _bsPlugins = new List(); + _ipaPlugins = new List(); if (!Directory.Exists(pluginDirectory)) return; - if (!Directory.Exists(Path.Combine(pluginDirectory, ".cache"))) + string cacheDir = Path.Combine(pluginDirectory, ".cache"); + + if (!Directory.Exists(cacheDir)) { - Directory.CreateDirectory(Path.Combine(pluginDirectory, ".cache")); + Directory.CreateDirectory(cacheDir); } else { - foreach (string plugin in Directory.GetFiles(Path.Combine(pluginDirectory, ".cache"), "*")) + foreach (string plugin in Directory.GetFiles(cacheDir, "*")) { File.Delete(plugin); } @@ -61,37 +78,66 @@ namespace IllusionInjector string[] originalPlugins = Directory.GetFiles(pluginDirectory, "*.dll"); foreach (string s in originalPlugins) { - string pluginCopy = pluginDirectory + "\\.cache" + s.Substring(s.LastIndexOf('\\')); + string pluginCopy = Path.Combine(cacheDir, Path.GetFileName(s)); File.Copy(Path.Combine(pluginDirectory, s), pluginCopy); } //Load copied plugins - string copiedPluginsDirectory = pluginDirectory + "\\.cache"; - string[] copiedPlugins = Directory.GetFiles(copiedPluginsDirectory, "*.dll"); + string[] copiedPlugins = Directory.GetFiles(cacheDir, "*.dll"); foreach (string s in copiedPlugins) { - _Plugins.AddRange(LoadPluginsFromFile(s, exeName)); + var result = LoadPluginsFromFile(s, exeName); + _bsPlugins.AddRange(result.Item1); + _ipaPlugins.AddRange(result.Item2); } // DEBUG debugLogger.Log($"Running on Unity {UnityEngine.Application.unityVersion}"); debugLogger.Log("-----------------------------"); - debugLogger.Log($"Loading plugins from {pluginDirectory} and found {_Plugins.Count}"); + debugLogger.Log($"Loading plugins from {pluginDirectory} and found {_bsPlugins.Count}"); debugLogger.Log("-----------------------------"); - foreach (var plugin in _Plugins) + foreach (var plugin in _bsPlugins) { debugLogger.Log($"{plugin.Name}: {plugin.Version}"); } debugLogger.Log("-----------------------------"); } - private static IEnumerable LoadPluginsFromFile(string file, string exeName) + private static Tuple, IEnumerable> LoadPluginsFromFile(string file, string exeName) { - List plugins = new List(); + List bsPlugins = new List(); + List ipaPlugins = new List(); if (!File.Exists(file) || !file.EndsWith(".dll", true, null)) - return plugins; + return new Tuple, IEnumerable>(bsPlugins, ipaPlugins); + + T OptionalGetPlugin(Type t) where T : class + { + // use typeof() to allow for easier renaming (in an ideal world this compiles to a string, but ¯\_(ツ)_/¯) + if (t.GetInterface(typeof(T).Name) != null) + { + try + { + T pluginInstance = Activator.CreateInstance(t) as T; + string[] filter = null; + + if (pluginInstance is IGenericEnhancedPlugin) + { + filter = ((IGenericEnhancedPlugin)pluginInstance).Filter; + } + + if (filter == null || filter.Contains(exeName, StringComparer.OrdinalIgnoreCase)) + return pluginInstance; + } + catch (Exception e) + { + debugLogger.Exception($"Could not load plugin {t.FullName} in {Path.GetFileName(file)}! {e}"); + } + } + + return null; + } try { @@ -99,25 +145,17 @@ namespace IllusionInjector foreach (Type t in assembly.GetTypes()) { - if (t.GetInterface("IPlugin") != null) + IBeatSaberPlugin bsPlugin = OptionalGetPlugin(t); + if (bsPlugin != null) { - try - { - - IPlugin pluginInstance = Activator.CreateInstance(t) as IPlugin; - string[] filter = null; - - if (pluginInstance is IEnhancedPlugin) - { - filter = ((IEnhancedPlugin)pluginInstance).Filter; - } - - if(filter == null || filter.Contains(exeName, StringComparer.OrdinalIgnoreCase)) - plugins.Add(pluginInstance); - } - catch (Exception e) + bsPlugins.Add(bsPlugin); + } + else + { + IPlugin ipaPlugin = OptionalGetPlugin(t); + if (ipaPlugin != null) { - debugLogger.Exception($"Could not load plugin {t.FullName} in {Path.GetFileName(file)}! {e}"); + ipaPlugins.Add(ipaPlugin); } } } @@ -128,7 +166,7 @@ namespace IllusionInjector debugLogger.Error($"Could not load {Path.GetFileName(file)}! {e}"); } - return plugins; + return new Tuple, IEnumerable>(bsPlugins, ipaPlugins); } public class AppInfo @@ -146,6 +184,6 @@ namespace IllusionInjector } } } - +#pragma warning restore CS0618 // Type or member is obsolete (IPlugin) } } diff --git a/IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache b/IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache index 2a3e91d1..3d667553 100644 --- a/IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache +++ b/IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -e3db2f4e6085eb1ed3ec468191bb328ebb0f8b7f +26a9be3d697b3ac9c2a8af98062cb97cf748184e diff --git a/IllusionPlugin/IPlugin.cs b/IllusionPlugin/BeatSaber/IBeatSaberPlugin.cs similarity index 92% rename from IllusionPlugin/IPlugin.cs rename to IllusionPlugin/BeatSaber/IBeatSaberPlugin.cs index 44c22a21..8a692109 100644 --- a/IllusionPlugin/IPlugin.cs +++ b/IllusionPlugin/BeatSaber/IBeatSaberPlugin.cs @@ -6,10 +6,10 @@ using UnityEngine.SceneManagement; namespace IllusionPlugin { /// - /// Interface for generic Illusion unity plugins. Every class that implements this will be loaded if the DLL is placed at + /// Interface for Beat Saber plugins. Every class that implements this will be loaded if the DLL is placed at /// data/Managed/Plugins. /// - public interface IPlugin + public interface IBeatSaberPlugin { /// diff --git a/IllusionPlugin/BeatSaber/IEnhancedBeatSaberPlugin.cs b/IllusionPlugin/BeatSaber/IEnhancedBeatSaberPlugin.cs new file mode 100644 index 00000000..a8575c41 --- /dev/null +++ b/IllusionPlugin/BeatSaber/IEnhancedBeatSaberPlugin.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace IllusionPlugin +{ + public interface IEnhancedBeatSaberPlugin : IBeatSaberPlugin, IGenericEnhancedPlugin + { + } +} diff --git a/IllusionPlugin/IPA/IEnhancedPlugin.cs b/IllusionPlugin/IPA/IEnhancedPlugin.cs new file mode 100644 index 00000000..0ba8d5fb --- /dev/null +++ b/IllusionPlugin/IPA/IEnhancedPlugin.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace IllusionPlugin +{ + [Obsolete("When building plugins for Beat Saber, use IEnhancedBeatSaberPlugin")] + public interface IEnhancedPlugin : IPlugin, IGenericEnhancedPlugin + { + } +} \ No newline at end of file diff --git a/IllusionPlugin/IEnhancedPlugin.cs b/IllusionPlugin/IPA/IGenericEnhancedPlugin.cs similarity index 80% rename from IllusionPlugin/IEnhancedPlugin.cs rename to IllusionPlugin/IPA/IGenericEnhancedPlugin.cs index a3ed3f75..6beb8bea 100644 --- a/IllusionPlugin/IEnhancedPlugin.cs +++ b/IllusionPlugin/IPA/IGenericEnhancedPlugin.cs @@ -1,17 +1,19 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace IllusionPlugin -{ - public interface IEnhancedPlugin : IPlugin - { - /// - /// Gets a list of executables this plugin should be excuted on (without the file ending) - /// - /// { "PlayClub", "PlayClubStudio" } - string[] Filter { get; } - - void OnLateUpdate(); - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IllusionPlugin +{ + public interface IGenericEnhancedPlugin + { + /// + /// Gets a list of executables this plugin should be excuted on (without the file ending) + /// + /// { "PlayClub", "PlayClubStudio" } + string[] Filter { get; } + + void OnLateUpdate(); + } +} diff --git a/IllusionPlugin/IPA/IPlugin.cs b/IllusionPlugin/IPA/IPlugin.cs new file mode 100644 index 00000000..0610da73 --- /dev/null +++ b/IllusionPlugin/IPA/IPlugin.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace IllusionPlugin +{ + /// + /// Interface for generic Illusion unity plugins. Every class that implements this will be loaded if the DLL is placed at + /// data/Managed/Plugins. + /// + [Obsolete("When building plugins for Beat Saber, use IBeatSaberPlugin")] + public interface IPlugin + { + + /// + /// Gets the name of the plugin. + /// + string Name { get; } + + /// + /// Gets the version of the plugin. + /// + string Version { get; } + + /// + /// Gets invoked when the application is started. + /// + void OnApplicationStart(); + + /// + /// Gets invoked when the application is closed. + /// + void OnApplicationQuit(); + + /// + /// Gets invoked whenever a level is loaded. + /// + /// + void OnLevelWasLoaded(int level); + + /// + /// Gets invoked after the first update cycle after a level was loaded. + /// + /// + void OnLevelWasInitialized(int level); + + /// + /// Gets invoked on every graphic update. + /// + void OnUpdate(); + + + /// + /// Gets invoked on ever physics update. + /// + void OnFixedUpdate(); + } +} \ No newline at end of file diff --git a/IllusionPlugin/IllusionPlugin.csproj b/IllusionPlugin/IllusionPlugin.csproj index 91d11847..c69201b2 100644 --- a/IllusionPlugin/IllusionPlugin.csproj +++ b/IllusionPlugin/IllusionPlugin.csproj @@ -43,9 +43,12 @@ - + - + + + + diff --git a/IllusionPlugin/Logger.cs b/IllusionPlugin/Logger.cs index 3cb26e1b..15079c97 100644 --- a/IllusionPlugin/Logger.cs +++ b/IllusionPlugin/Logger.cs @@ -53,7 +53,7 @@ namespace IllusionPlugin { _logFile.Create().Close(); } - public Logger(IPlugin plugin) + public Logger(IBeatSaberPlugin plugin) { SetupStatic(); _logFile = GetPath(plugin); @@ -135,7 +135,7 @@ namespace IllusionPlugin { } } - FileInfo GetPath(IPlugin plugin) => GetPath(plugin.Name); + FileInfo GetPath(IBeatSaberPlugin plugin) => GetPath(plugin.Name); FileInfo GetPath(string modName) { ModName = modName; var logsDir = new DirectoryInfo($"./Logs/{modName}/{DateTime.Now:dd-MM-yy}"); @@ -145,7 +145,7 @@ namespace IllusionPlugin { } public static class LoggerExtensions { - public static Logger GetLogger(this IPlugin plugin) { + public static Logger GetLogger(this IBeatSaberPlugin plugin) { return new Logger(plugin); } } diff --git a/IllusionPlugin/ModPrefs.cs b/IllusionPlugin/ModPrefs.cs index 561eff11..e3423a17 100644 --- a/IllusionPlugin/ModPrefs.cs +++ b/IllusionPlugin/ModPrefs.cs @@ -11,11 +11,11 @@ namespace IllusionPlugin /// Allows to get and set preferences for your mod. /// public class ModPrefs { - internal static Dictionary ModPrefses { get; set; } = new Dictionary(); + internal static Dictionary ModPrefses { get; set; } = new Dictionary(); private IniFile Instance; - public ModPrefs(IPlugin plugin) { + public ModPrefs(IBeatSaberPlugin plugin) { Instance = new IniFile(Path.Combine(Environment.CurrentDirectory, $"UserData/ModPrefs/{plugin.Name}.ini")); ModPrefses.Add(plugin, this); } @@ -160,7 +160,7 @@ namespace IllusionPlugin } public static class ModPrefsExtensions { - public static ModPrefs GetModPrefs(this IPlugin plugin) { + public static ModPrefs GetModPrefs(this IBeatSaberPlugin plugin) { return ModPrefs.ModPrefses.First(o => o.Key == plugin).Value; } } diff --git a/IllusionPlugin/obj/Debug/IllusionPlugin.csproj.CoreCompileInputs.cache b/IllusionPlugin/obj/Debug/IllusionPlugin.csproj.CoreCompileInputs.cache index 907a8434..7fc6e6e8 100644 --- a/IllusionPlugin/obj/Debug/IllusionPlugin.csproj.CoreCompileInputs.cache +++ b/IllusionPlugin/obj/Debug/IllusionPlugin.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -3916871581af94648b18c3cc477ec3f5b61ffa03 +eb108721e2940659deee6bd068631e6d4749736e