using System; using System.Linq; #if UNITY_EDITOR_WIN using Microsoft.Win32; using System.Text; #elif UNITY_EDITOR_OSX using System.Diagnostics; using System.IO; using System.Text.RegularExpressions; #elif UNITY_EDITOR_LINUX using System.IO; using System.Xml; using System.Xml.Linq; #endif #pragma warning disable IDE0005 using Serilog = Meryel.Serilog; #pragma warning restore IDE0005 #nullable enable //namespace BgTools.PlayerPrefsEditor namespace Meryel.UnityCodeAssist.Editor.Preferences { public abstract class PreferanceStorageAccessor { protected string prefPath; protected string[] cachedData = new string[0]; protected abstract void FetchKeysFromSystem(); protected PreferanceStorageAccessor(string pathToPrefs) { prefPath = pathToPrefs; } public string[] GetKeys(bool reloadData = true) { if (reloadData || cachedData.Length == 0) { FetchKeysFromSystem(); } return cachedData; } public Action? PrefEntryChangedDelegate; protected bool ignoreNextChange = false; public void IgnoreNextChange() { ignoreNextChange = true; } protected virtual void OnPrefEntryChanged() { if (ignoreNextChange) { ignoreNextChange = false; return; } PrefEntryChangedDelegate?.Invoke(); } public Action? StartLoadingDelegate; public Action? StopLoadingDelegate; public abstract void StartMonitoring(); public abstract void StopMonitoring(); public abstract bool IsMonitoring(); } #if UNITY_EDITOR_WIN public class WindowsPrefStorage : PreferanceStorageAccessor { readonly RegistryMonitor monitor; public WindowsPrefStorage(string pathToPrefs) : base(pathToPrefs) { monitor = new RegistryMonitor(RegistryHive.CurrentUser, prefPath); monitor.RegChanged += new EventHandler(OnRegChanged); } private void OnRegChanged(object sender, EventArgs e) { OnPrefEntryChanged(); } protected override void FetchKeysFromSystem() { cachedData = new string[0]; using (RegistryKey rootKey = Registry.CurrentUser.OpenSubKey(prefPath)) { if (rootKey != null) { cachedData = rootKey.GetValueNames(); rootKey.Close(); } } // Clean _h3320113488 nameing //cachedData = cachedData.Select((key) => { return key.Substring(0, key.LastIndexOf("_h", StringComparison.Ordinal)); }).ToArray(); for (int i = 0; i < cachedData.Length; i++) { var indexOfSuffix = cachedData[i].LastIndexOf("_h", StringComparison.Ordinal); if (indexOfSuffix >= 0) cachedData[i] = cachedData[i].Substring(0, indexOfSuffix); } EncodeAnsiInPlace(); } public override void StartMonitoring() { monitor.Start(); } public override void StopMonitoring() { monitor.Stop(); } public override bool IsMonitoring() { return monitor.IsMonitoring; } private void EncodeAnsiInPlace() { Encoding utf8 = Encoding.UTF8; Encoding ansi = Encoding.GetEncoding(1252); for (int i = 0; i < cachedData.Length; i++) { cachedData[i] = utf8.GetString(ansi.GetBytes(cachedData[i])); } } } #elif UNITY_EDITOR_LINUX public class LinuxPrefStorage : PreferanceStorageAccessor { readonly FileSystemWatcher fileWatcher; public LinuxPrefStorage(string pathToPrefs) : base(Path.Combine(Environment.GetEnvironmentVariable("HOME"), pathToPrefs)) { fileWatcher = new FileSystemWatcher { Path = Path.GetDirectoryName(prefPath), NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite, Filter = "prefs" }; fileWatcher.Changed += OnWatchedFileChanged; } protected override void FetchKeysFromSystem() { cachedData = new string[0]; if (File.Exists(prefPath)) { XmlReaderSettings settings = new XmlReaderSettings(); XmlReader reader = XmlReader.Create(prefPath, settings); XDocument doc = XDocument.Load(reader); cachedData = doc.Element("unity_prefs").Elements().Select((e) => e.Attribute("name").Value).ToArray(); } } public override void StartMonitoring() { fileWatcher.EnableRaisingEvents = true; } public override void StopMonitoring() { fileWatcher.EnableRaisingEvents = false; } public override bool IsMonitoring() { return fileWatcher.EnableRaisingEvents; } private void OnWatchedFileChanged(object source, FileSystemEventArgs e) { OnPrefEntryChanged(); } } #elif UNITY_EDITOR_OSX public class MacPrefStorage : PreferanceStorageAccessor { private readonly FileSystemWatcher fileWatcher; private readonly DirectoryInfo prefsDirInfo; private readonly String prefsFileNameWithoutExtension; public MacPrefStorage(string pathToPrefs) : base(Path.Combine(Environment.GetEnvironmentVariable("HOME"), pathToPrefs)) { prefsDirInfo = new DirectoryInfo(Path.GetDirectoryName(prefPath)); prefsFileNameWithoutExtension = Path.GetFileNameWithoutExtension(prefPath); fileWatcher = new FileSystemWatcher { Path = Path.GetDirectoryName(prefPath), NotifyFilter = NotifyFilters.LastWrite, Filter = Path.GetFileName(prefPath) }; // MAC delete the old and create a new file instead of updating fileWatcher.Created += OnWatchedFileChanged; } protected override void FetchKeysFromSystem() { // Workaround to avoid incomplete tmp phase from MAC OS foreach (FileInfo info in prefsDirInfo.GetFiles()) { // Check if tmp PlayerPrefs file exist if (info.FullName.Contains(prefsFileNameWithoutExtension) && !info.FullName.EndsWith(".plist")) { StartLoadingDelegate?.Invoke(); return; } } StopLoadingDelegate?.Invoke(); cachedData = new string[0]; if (File.Exists(prefPath)) { string fixedPrefsPath = prefPath.Replace("\"", "\\\"").Replace("'", "\\'").Replace("`", "\\`"); var cmdStr = string.Format(@"-p '{0}'", fixedPrefsPath); string stdOut = String.Empty; string errOut = String.Empty; var process = new System.Diagnostics.Process(); process.StartInfo.UseShellExecute = false; process.StartInfo.FileName = "plutil"; process.StartInfo.Arguments = cmdStr; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.OutputDataReceived += new DataReceivedEventHandler((sender, evt) => { stdOut += evt.Data + "\n"; }); process.ErrorDataReceived += new DataReceivedEventHandler((sender, evt) => { errOut += evt.Data + "\n"; }); process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); process.WaitForExit(); MatchCollection matches = Regex.Matches(stdOut, @"(?: "")(.*)(?:"" =>.*)"); cachedData = matches.Cast().Select((e) => e.Groups[1].Value).ToArray(); } } public override void StartMonitoring() { fileWatcher.EnableRaisingEvents = true; } public override void StopMonitoring() { fileWatcher.EnableRaisingEvents = false; } public override bool IsMonitoring() { return fileWatcher.EnableRaisingEvents; } private void OnWatchedFileChanged(object source, FileSystemEventArgs e) { OnPrefEntryChanged(); } } #endif }