/// <summary> /// Old loading logic from folders /// </summary> internal static Texture2D GetOldStyleOverlayTex(TexType texType, ChaControl chaControl) { var charFullname = chaControl.fileParam?.fullname; if (!string.IsNullOrEmpty(charFullname)) { var texFilename = GetTexFilename(charFullname, texType); if (File.Exists(texFilename)) { Logger.Log(LogLevel.Info, $"[KSOX] Importing texture data for {charFullname} from file {texFilename}"); try { var fileTexBytes = File.ReadAllBytes(texFilename); var overlayTex = Util.TextureFromBytes(fileTexBytes); if (overlayTex != null) { return(overlayTex); } } catch (Exception ex) { Logger.Log(LogLevel.Error, "[KSOX] Failed to load texture from file - " + ex.Message); } } } return(null); }
private void uncensor() { // Crashes if not executed on main thread var customdelegate = new Action <object>(delegate(object param) { try { foreach (string objectPath in MonitorObjects) { GameObject gameObject = GameObject.Find(objectPath); if (gameObject != null && gameObject.activeSelf) { debugmsg("Disabling " + objectPath); gameObject.SetActive(false); } } } catch (Exception ex) { Logger.Log(LogLevel.Error, ex.ToString()); } }); customdelegate.Invoke(null); }
private static void DoRegister(string name, object content, ReplaceType replaceType, bool overwrite) { CheckOrFixName(ref name); Logger.Log(LogLevel.Info, "Trying to register " + name); if (File.Exists(_assetsPath + name) && !overwrite) { Logger.Log(LogLevel.Error, "Error: File already exists, overwrite not specified"); throw new ArgumentException("File already exists, overwrite not specified.", nameof(name)); } try { if (replaceType != ReplaceType.Text) { if (replaceType == ReplaceType.Bytes) { File.WriteAllBytes(_assetsPath + name, content as byte[] ?? throw new InvalidOperationException()); } } else { File.WriteAllText(_assetsPath + name, content as string, Encoding.UTF8); } Logger.Log(LogLevel.Info, "File registered"); } catch (Exception ex) { Logger.Log(LogLevel.Error, "Error registering file"); LogException(ex); throw; } }
/// <summary> /// Construct a list of all folders that contain a .png /// </summary> protected void BuildPngFolderList(ZipFile arc) { foreach (ZipEntry entry in arc) { //Only list folders for .pngs in abdata folder //i.e. skip preview pics or character cards that might be included with the mod if (entry.Name.StartsWith("abdata/", StringComparison.OrdinalIgnoreCase) && entry.Name.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) { string assetBundlePath = entry.Name; assetBundlePath = assetBundlePath.Remove(0, assetBundlePath.IndexOf('/') + 1); //Remove "abdata/" //Make a list of all the .png files and archive they come from if (PngList.ContainsKey(entry.Name)) { Logger.Log(LogLevel.Warning, $"[SIDELOADER] Duplicate asset detected! {assetBundlePath}"); } else { PngList.Add(entry.Name, arc); } assetBundlePath = assetBundlePath.Remove(assetBundlePath.LastIndexOf('/')); //Remove the .png filename if (!PngFolderList.Contains(assetBundlePath)) { //Make a unique list of all folders that contain a .png PngFolderList.Add(assetBundlePath); } } } }
/// <summary> /// Scene has to be fully loaded for all characters and objects to exist in the game /// </summary> private void SceneLoaded(Scene s, LoadSceneMode lsm) { if (s.name == "StudioNotification" && LoadClicked) { LoadClicked = false; try { IKObjectInfoList.Clear(); PluginData ExtendedData = ExtendedSave.GetSceneExtendedDataById(PluginName); if (ExtendedData != null && ExtendedData.data.ContainsKey("AnimationInfo")) { List <AnimationControllerInfo> AnimationControllerInfoList; AnimationControllerInfoList = ((object[])ExtendedData.data["AnimationInfo"]).Select(x => AnimationControllerInfo.Unserialize((byte[])x)).ToList(); foreach (var AnimInfo in AnimationControllerInfoList) { IKObjectInfo LoadedAnimInfo = new IKObjectInfo(); var Character = Singleton <Studio.Studio> .Instance.dicObjectCtrl.Where(x => x.Key == AnimInfo.CharDicKey).Select(x => x.Value as OCIChar).First(); LoadedAnimInfo.CharacterKey = AnimInfo.CharDicKey; LoadedAnimInfo.CharacterObject = GameObject.Find(Character.charInfo.name); LoadedAnimInfo.IKPart = AnimInfo.IKPart; LoadedAnimInfo.IKTarget = Character.listIKTarget.Where(x => x.boneObject.name == AnimInfo.IKPart).First(); var LinkedItem = Singleton <Studio.Studio> .Instance.dicObjectCtrl.Where(x => x.Key == AnimInfo.ItemDicKey).Select(x => x.Value).First(); switch (LinkedItem) { case OCIItem Item: LoadedAnimInfo.ObjectKey = Item.objectInfo.dicKey; LoadedAnimInfo.SelectedObject = Item.childRoot.gameObject; break; case OCIFolder Folder: LoadedAnimInfo.ObjectKey = Folder.objectInfo.dicKey; LoadedAnimInfo.SelectedObject = Folder.childRoot.gameObject; break; case OCIRoute Route: LoadedAnimInfo.ObjectKey = Route.objectInfo.dicKey; LoadedAnimInfo.SelectedObject = Route.childRoot.gameObject; break; } IKObjectInfoList.Add(LoadedAnimInfo); } } Logger.Log(LogLevel.Debug, "Loaded KK_AnimationController animations"); } catch (Exception ex) { Logger.Log(LogLevel.Error | LogLevel.Message, "Could not load KK_AnimationController animations."); Logger.Log(LogLevel.Error, ex.ToString()); } } }
private static void UntranslateTextAll() { TextHooks.TranslationHooksEnabled = false; var aliveCount = 0; foreach (var kv in OriginalTranslations) { try { switch (kv.Key) { case TMP_Text tmtext: tmtext.text = kv.Value; break; case UnityEngine.UI.Text tmtext: tmtext.text = kv.Value; break; } aliveCount++; } catch { // Bloody APIs crashing up my house } } Logger.Log(LogLevel.Message, $"{aliveCount} translations reloaded."); TextHooks.TranslationHooksEnabled = true; }
private void Update() { if (_bytesToLoad != null) { try { var tex = Util.TextureFromBytes(_bytesToLoad); var recommendedSize = GetRecommendedTexSize(_typeToLoad); if (tex.width != tex.height || tex.height != recommendedSize) { Logger.Log(LogLevel.Message | LogLevel.Warning, $"[KSOX] WARNING - Unusual texture resolution! It's recommended to use {recommendedSize}x{recommendedSize} for {_typeToLoad}."); } else { Logger.Log(LogLevel.Message, "[KSOX] Texture imported successfully"); } SetTexAndUpdate(tex, _typeToLoad); } catch (Exception ex) { _lastError = ex; } _bytesToLoad = null; } if (_lastError != null) { Logger.Log(LogLevel.Error | LogLevel.Message, "[KSOX] Failed to load texture from file - " + _lastError.Message); Logger.Log(LogLevel.Debug, _lastError); _lastError = null; } }
private static void ExtendedSceneSave(string path) { try { Dictionary <string, object> ExtendedData = new Dictionary <string, object>(); List <AnimationControllerInfo> AnimationControllerInfoList = new List <AnimationControllerInfo>(); foreach (var IKObj in IKObjectInfoList) { AnimationControllerInfoList.Add(new AnimationControllerInfo { CharDicKey = IKObj.CharacterKey, ItemDicKey = IKObj.ObjectKey, IKPart = IKObj.IKPart }); } if (AnimationControllerInfoList.Count == 0) { ExtendedSave.SetSceneExtendedDataById(PluginName, null); } else { ExtendedData.Add("AnimationInfo", AnimationControllerInfoList.Select(x => x.Serialize()).ToList()); ExtendedSave.SetSceneExtendedDataById(PluginName, new PluginData { data = ExtendedData }); } Logger.Log(LogLevel.Debug, "Saved KK_AnimationController animations"); } catch (Exception ex) { Logger.Log(LogLevel.Error | LogLevel.Message, "Could not save KK_AnimationController animations."); Logger.Log(LogLevel.Error, ex.ToString()); } }
public Sideloader() { //ilmerge AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { if (args.Name == "I18N, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756" || args.Name == "I18N.West, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null") { return(Assembly.GetExecutingAssembly()); } return(null); }; //install hooks Hooks.InstallHooks(); AutoResolver.Hooks.InstallHooks(); ResourceRedirector.ResourceRedirector.AssetResolvers.Add(RedirectHook); ResourceRedirector.ResourceRedirector.AssetBundleResolvers.Add(AssetBundleRedirectHook); MissingModWarning = new ConfigWrapper <bool>(nameof(MissingModWarning), this, true); //check mods directory var modDirectory = Path.Combine(Paths.GameRootPath, "mods"); if (!Directory.Exists(modDirectory)) { Logger.Log(LogLevel.Warning, "[SIDELOADER] Could not find the \"mods\" directory"); return; } LoadModsFromDirectory(modDirectory); }
private void DrawEditableValue(ICacheEntry field, object value, params GUILayoutOption[] layoutParams) { var isBeingEdited = _currentlyEditingTag == field; var text = isBeingEdited ? _currentlyEditingText : ExtractText(value); var result = GUILayout.TextField(text, layoutParams); if (!Equals(text, result) || isBeingEdited) { if (_userHasHitReturn) { _currentlyEditingTag = null; _userHasHitReturn = false; try { var converted = Convert.ChangeType(result, field.Type()); if (!Equals(converted, value)) { field.SetValue(converted); } } catch (Exception ex) { Logger.Log(LogLevel.Error, "Failed to set value - " + ex.Message); } } else { _currentlyEditingText = result; _currentlyEditingTag = field; } } }
private bool CheckLineForErrors(string line, string fileName, int lineNumber) { if (!line.Contains("=")) { Logger.Log(LogLevel.Warning, $"File {fileName} Line {lineNumber } has no ="); return(true); } var Line2Split = line.Split('='); if (Line2Split.Count() > 2) { Logger.Log(LogLevel.Warning, $"File {fileName} Line {lineNumber} has more than one ="); return(true); } if (Line2Split[0].IsNullOrEmpty()) { Logger.Log(LogLevel.Error, $"File {fileName} Line {lineNumber } has nothing on the left side of ="); return(true); } if (!Line2Split[0].StartsWith(@"//") && Line2Split[1].IsNullOrEmpty()) { Logger.Log(LogLevel.Error, $"File {fileName} Line {lineNumber } is uncommented but has nothing on the right side of ="); return(true); } return(false); }
/// <summary> /// Sets the visible state of the game object and all it's children. /// </summary> private static void IterateVisible(GameObject go, bool Visible) { //Logger.Log(LogLevel.Info, $"Game Object:{DebugFullObjectPath(go)}"); for (int i = 0; i < go.transform.childCount; i++) { if (HideHairAccessories.Value && go.name.StartsWith("a_n_") && go.transform.parent.gameObject.name == "ct_hairB") { //change visibility of accessories built in to back hairs IterateVisible(go.transform.GetChild(i).gameObject, Visible); } else if (go.name.StartsWith("a_n_")) { //do not change visibility of attached items such as studio items and character accessories Logger.Log(LogLevel.None, $"not hiding attached items for {go.name}"); } else { //change visibility of everything else IterateVisible(go.transform.GetChild(i).gameObject, Visible); } } if (go.GetComponent <Renderer>()) { go.GetComponent <Renderer>().enabled = Visible; } }
protected void LoadAllUnityArchives(ZipFile arc) { foreach (ZipEntry entry in arc) { if (entry.Name.EndsWith(".unity3d", StringComparison.OrdinalIgnoreCase)) { string assetBundlePath = entry.Name; if (assetBundlePath.Contains('/')) { assetBundlePath = assetBundlePath.Remove(0, assetBundlePath.IndexOf('/') + 1); } Func <AssetBundle> getBundleFunc = () => { var stream = arc.GetInputStream(entry); byte[] buffer = new byte[entry.Size]; stream.Read(buffer, 0, (int)entry.Size); BundleManager.RandomizeCAB(buffer); return(AssetBundle.LoadFromMemory(buffer)); }; BundleManager.AddBundleLoader(getBundleFunc, assetBundlePath, out string warning); if (!string.IsNullOrEmpty(warning)) { Logger.Log(LogLevel.Warning, $"[SIDELOADER] WARNING! {warning}"); } } } }
private void UntranslateAll() { Hooks.TranslationHooksEnabled = false; var aliveCount = 0; foreach (var kv in originalTranslations) { if (!kv.Key.IsAlive) { continue; } aliveCount++; switch (kv.Key.Target) { case TMP_Text tmtext: tmtext.text = kv.Value; break; case Text tmtext: tmtext.text = kv.Value; break; } } Logger.Log(LogLevel.Message, $"{aliveCount} translations reloaded."); Hooks.TranslationHooksEnabled = true; }
private static void LoadSceneTranslations(int sceneIndex) { Logger.Log(LogLevel.Debug, $"Loading translations for scene {sceneIndex}"); Translations.Clear(); regexTranslations.Clear(); foreach (Archive arc in TLArchives) { foreach (Section section in arc.Sections) { if (!section.Exe.Equals("all", StringComparison.OrdinalIgnoreCase) && !section.Exe.Equals(CurrentExe, StringComparison.OrdinalIgnoreCase)) { continue; } foreach (var line in section.Lines) { if (line.Levels.Any(x => x == (byte)sceneIndex || x == 255)) { if (line.Flags.IsOriginalRegex) { regexTranslations[new Regex(line.OriginalLine)] = line; } else { Translations[line.OriginalLine] = line; } } } } } Logger.Log(LogLevel.Debug, $"Filtered {Translations.Count} / {TLArchives.Sum(x => x.Sections.Sum(y => y.Lines.Count))} lines"); }
private void Main() { //Don't even bother if there's no mods directory if (!Directory.Exists(Path.Combine(Paths.GameRootPath, "mods")) || !Directory.Exists(Paths.PluginPath)) { Logger.Log(LogLevel.Warning, "KK_GUIDMigration was not loaded due to missing mods folder."); return; } //Only do migration if there's a .csv file and it has stuff in it if (!File.Exists(GUIDMigrationFilePath)) { Logger.Log(LogLevel.Error | LogLevel.Message, "KK_GUIDMigration was not loaded due to missing KK_GUIDMigration.csv file."); return; } GenerateMigrationInfo(); if (MigrationInfoList.Count == 0) { Logger.Log(LogLevel.Error | LogLevel.Message, "KK_GUIDMigration was not loaded due to empty KK_GUIDMigration.csv file."); return; } var harmony = HarmonyInstance.Create(GUID); harmony.PatchAll(typeof(KK_GUIDMigration)); }
public static void LoadListInfoAllPostfix(ChaListControl __instance) { if (!Directory.Exists(ListOverrideFolder)) { return; } int counter = 0; Dictionary <ChaListDefine.CategoryNo, Dictionary <int, ListInfoBase> > dictListInfo = Traverse.Create(__instance).Field("dictListInfo").GetValue() as Dictionary <ChaListDefine.CategoryNo, Dictionary <int, ListInfoBase> >; foreach (var fileName in Directory.GetFiles(ListOverrideFolder)) { try { XDocument doc = XDocument.Load(fileName); foreach (var overrideElement in doc.Root.Elements()) { ChaListDefine.CategoryNo categoryNo; if (int.TryParse(overrideElement.Attribute("Category").Value, out int category)) { categoryNo = (ChaListDefine.CategoryNo)category; } else { categoryNo = (ChaListDefine.CategoryNo)Enum.Parse(typeof(ChaListDefine.CategoryNo), overrideElement.Attribute("Category").Value); } ChaListDefine.KeyType keyType; if (int.TryParse(overrideElement.Attribute("KeyType").Value, out int key)) { keyType = (ChaListDefine.KeyType)key; } else { keyType = (ChaListDefine.KeyType)Enum.Parse(typeof(ChaListDefine.KeyType), overrideElement.Attribute("KeyType").Value); } int id = int.Parse(overrideElement.Attribute("ID").Value); string value = overrideElement.Attribute("Value").Value; //Don't allow people to change IDs, that's sure to break everything. if (keyType == ChaListDefine.KeyType.ID) { continue; } dictListInfo[categoryNo][id].dictInfo[(int)keyType] = value; counter++; } } catch (Exception ex) { Logger.Log(LogLevel.Error, $"Failed to load {PluginNameInternal} xml file."); Logger.Log(LogLevel.Error, ex); } } Logger.Log(LogLevel.Debug, $"[{PluginNameInternal}] Loaded {counter} overrides"); }
public Sideloader() { //ilmerge AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { if (args.Name == "I18N, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756" || args.Name == "I18N.West, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null") { return(Assembly.GetExecutingAssembly()); } return(null); }; //install hooks Hooks.InstallHooks(); AutoResolver.Hooks.InstallHooks(); ResourceRedirector.ResourceRedirector.AssetResolvers.Add(RedirectHook); //check mods directory string modDirectory = Path.Combine(Paths.GameRootPath, "mods"); if (!Directory.Exists(modDirectory)) { return; } //load zips foreach (string archivePath in Directory.GetFiles(modDirectory, "*.zip", SearchOption.AllDirectories)) { var archive = new ZipFile(archivePath); if (!Manifest.TryLoadFromZip(archive, out Manifest manifest)) { Logger.Log(LogLevel.Warning, $"[SIDELOADER] Cannot load {Path.GetFileName(archivePath)} due to missing/invalid manifest."); continue; } if (LoadedManifests.Any(x => x.GUID == manifest.GUID)) { Logger.Log(LogLevel.Warning, $"[SIDELOADER] Skipping {Path.GetFileName(archivePath)} due to duplicate GUID \"{manifest.GUID}\"."); continue; } var manifestName = !string.IsNullOrEmpty(manifest.Name?.Trim()) ? manifest.Name : Path.GetFileName(archivePath); Logger.Log(LogLevel.Info, $"[SIDELOADER] Loaded {manifestName} {manifest.Version ?? ""}"); Archives.Add(archive); LoadedManifests.Add(manifest); LoadAllUnityArchives(archive); LoadAllLists(archive, manifest); } }
protected void LoadAllUnityArchives(ZipFile arc, string archiveFilename) { foreach (ZipEntry entry in arc) { if (entry.Name.EndsWith(".unity3d", StringComparison.OrdinalIgnoreCase)) { string assetBundlePath = entry.Name; if (assetBundlePath.Contains('/')) { assetBundlePath = assetBundlePath.Remove(0, assetBundlePath.IndexOf('/') + 1); } AssetBundle getBundleFunc() { AssetBundle bundle; if (entry.CompressionMethod == CompressionMethod.Stored) { long index = (long)locateZipEntryMethodInfo.Invoke(arc, new object[] { entry }); if (DebugLogging.Value) { Logger.Log(LogLevel.Debug, $"[SIDELOADER] Streaming {entry.Name} ({archiveFilename}) unity3d file from disk, offset {index}"); } bundle = AssetBundle.LoadFromFile(archiveFilename, 0, (ulong)index); } else { Logger.Log(LogLevel.Debug, $"[SIDELOADER] Cannot stream {entry.Name} ({archiveFilename}) unity3d file from disk, loading to RAM instead"); var stream = arc.GetInputStream(entry); byte[] buffer = new byte[entry.Size]; stream.Read(buffer, 0, (int)entry.Size); BundleManager.RandomizeCAB(buffer); bundle = AssetBundle.LoadFromMemory(buffer); } if (bundle == null) { Logger.Log(LogLevel.Error, $"[SIDELOADER] Asset bundle \"{entry.Name}\" ({Path.GetFileName(archiveFilename)}) failed to load. It might have a conflicting CAB string."); } return(bundle); } BundleManager.AddBundleLoader(getBundleFunc, assetBundlePath, out string warning); if (!string.IsNullOrEmpty(warning)) { Logger.Log(LogLevel.Warning, $"[SIDELOADER] {warning}"); } } } }
void DumpText() { int a = DumpCommunicationText(); int b = DumpScenarioText(); int c = DumpHText(); Logger.Log(LogLevel.Info, $"Total lines:{a + b + c}"); }
private void Start() { if (DisableIKCalc.Value && BepInEx.Bootstrap.Chainloader.Plugins.Select(MetadataHelper.GetMetadata).Any(x => x.GUID == "com.essu.stiletto")) { DisableIKCalc.Value = false; Logger.Log(LogLevel.Warning, "Stiletto detected, disabling the DisableIKCalc optimization"); } }
private static IEnumerable <ReadonlyCacheEntry> GetRootGOScanner() { Logger.Log(LogLevel.Debug, "[CheatTools] Looking for Root Game Objects..."); foreach (GameObject go in Resources.FindObjectsOfTypeAll <GameObject>().Where(x => x.transform == x.transform.root)) { yield return(new ReadonlyCacheEntry($"GameObject({go.name})", go)); } }
public static void IterateCardPrefixesPrefix(ref IEnumerable <ResolveInfo> extInfo, ChaFile file) { try { extInfo = MigrateGUID(extInfo, file.parameter.fullname.Trim()); } catch { Logger.Log(LogLevel.Error | LogLevel.Message, $"GUID migration failed. Please update KK_GUIDMigration and/or BepisPlugins."); } }
public static void IterateCoordinatePrefixesPrefix(ref IEnumerable <ResolveInfo> extInfo, ChaFileCoordinate coordinate) { try { extInfo = MigrateGUID(extInfo); } catch { Logger.Log(LogLevel.Error | LogLevel.Message, $"GUID migration failed. Please update KK_GUIDMigration and/or BepisPlugins."); } }
private static AsyncOperation RunUnloadUnusedAssets() { if ((DateTime.Now - LastUnload).TotalSeconds >= 5) { LastUnload = DateTime.Now; Logger.Log(LogLevel.Info, "RunUnloadUnusedAssets"); return(originalUnload()); } return(null); }
private static void LogException(Exception e) { var text = "Exception info: \n" + e.Message + "\n"; var flag = e.InnerException != null; if (flag) { text = text + e.InnerException.Message + "\n"; } text += e.StackTrace; Logger.Log(LogLevel.Error, text); }
private static void LoadSceneCharacter(string path, bool forceAdd) { var charaCtrl = new ChaFileControl(); if (!charaCtrl.LoadCharaFile(path, 1, true, true)) { return; } if (!forceAdd) { if (Studio.Studio.GetCtrlInfo(Singleton <Studio.Studio> .Instance.treeNodeCtrl.selectNode) is OCIChar) { var anySexChanged = false; var anyChanged = false; foreach (var oCIChar in Singleton <GuideObjectManager> .Instance.selectObjectKey .Select(Studio.Studio.GetCtrlInfo) .OfType <OCIChar>()) { if (oCIChar.oiCharInfo.sex != charaCtrl.parameter.sex) { anySexChanged = true; } charaCtrl.parameter.sex = (byte)oCIChar.oiCharInfo.sex; oCIChar.ChangeChara(path); anyChanged = true; } if (anySexChanged) { Logger.Log(LogLevel.Message, "Warning: The character's sex has been changed to match the selected character(s)."); } // Prevent adding a new character if we alraedy replaced an existing one if (anyChanged) { return; } } } if (charaCtrl.parameter.sex == 0) { Singleton <Studio.Studio> .Instance.AddMale(path); } else if (charaCtrl.parameter.sex == 1) { Singleton <Studio.Studio> .Instance.AddFemale(path); } }
private static IEnumerable <ReadonlyCacheEntry> GetMonoBehaviourScanner() { Logger.Log(LogLevel.Debug, "[CheatTools] Looking for MonoBehaviours..."); var mbt = typeof(MonoBehaviour); var types = GetAllComponentTypes().Where(x => mbt.IsAssignableFrom(x)); foreach (var component in ScanComponentTypes(types, true)) { yield return(component); } }
private static IEnumerable <ReadonlyCacheEntry> GetTransformScanner() { Logger.Log(LogLevel.Debug, "[CheatTools] Looking for Transforms..."); var trt = typeof(Transform); var types = GetAllComponentTypes().Where(x => trt.IsAssignableFrom(x)); foreach (var component in ScanComponentTypes(types, false)) { yield return(component); } }
public void Awake() { try { var harmony = HarmonyInstance.Create("meidodev.io.demosaic"); harmony.PatchAll(typeof(IOBetterMirror)); } catch (System.Exception e) { Logger.Log(LogLevel.Error, e.Message); } }