using System.Collections; using System.Collections.Generic; using System.IO; using System; using System.Security.AccessControl; using System.Security.Principal; using System.Linq; #pragma warning disable IDE0005 using Serilog = Meryel.Serilog; #pragma warning restore IDE0005 #nullable enable namespace Meryel.UnityCodeAssist.Editor { // copied from Exporter.cs in VSIX public static class Cleanup { public static bool DoCleanup() { var assetsPath = UnityEngine.Application.dataPath; var _solutionDirectory = CommonTools.GetProjectPath(); var destination = _solutionDirectory; var oldDestination = assetsPath; var succeed = true; try { Cleanup1(oldDestination); Cleanup2(oldDestination); Cleanup3(oldDestination, _solutionDirectory); Cleanup4(oldDestination); } catch (Exception ex) { succeed = false; Serilog.Log.Error(ex, "DoCleanup failed at {Destination}", destination); } return succeed; } private static void DeleteFileAndItsMeta(string filePath) { if (File.Exists(filePath)) { Serilog.Log.Debug("Deleting file {File}", filePath); //File.Delete(filePath); DeleteFileAux(filePath); Serilog.Log.Debug("Deleted file {File} {Exists}", filePath, File.Exists(filePath)); } var metaFilePath = filePath + ".meta"; if (File.Exists(metaFilePath)) { Serilog.Log.Debug("Deleting meta file {File}", metaFilePath); //File.Delete(metaFilePath); DeleteFileAux(metaFilePath); Serilog.Log.Debug("Deleted file {File} {Exists}", metaFilePath, File.Exists(metaFilePath)); } } private static bool IsDirectoryExistsAndEmpty(string path) { return Directory.Exists(path) && !Directory.EnumerateFileSystemEntries(path).Any(); } private static void DeleteDirectoryAndItsMeta(string directoryPath) { if (IsDirectoryExistsAndEmpty(directoryPath)) { Serilog.Log.Debug("Deleting directory {Dir}", directoryPath); Directory.Delete(directoryPath); Serilog.Log.Debug("Deleted directory {Dir} {Exists}", directoryPath, IsDirectoryExistsAndEmpty(directoryPath)); var metaFilePath = directoryPath + ".meta"; if (File.Exists(metaFilePath)) { Serilog.Log.Debug("Deleting directory meta file {File}", metaFilePath); //File.Delete(metaFilePath); DeleteFileAux(metaFilePath); Serilog.Log.Debug("Deleted directory meta file {File} {Exists}", metaFilePath, File.Exists(metaFilePath)); } } } private static void DeleteFileAux(string filePath) { try { File.Delete(filePath); } catch (UnauthorizedAccessException) { var fileDirectoryPath = Path.GetDirectoryName(filePath); SetEveryoneAccessToDirectory(fileDirectoryPath, out _); TakeOwnership(filePath); File.Delete(filePath); } } /// /// Set Everyone Full Control permissions for selected directory /// /// /// static bool SetEveryoneAccessToDirectory(string dirName, out string _lastError) { try { // Make sure directory exists if (Directory.Exists(dirName) == false) throw new Exception(string.Format("Directory {0} does not exist, so permissions cannot be set.", dirName)); // Get directory access info DirectoryInfo dinfo = new DirectoryInfo(dirName); DirectorySecurity dSecurity = dinfo.GetAccessControl(); // Add the FileSystemAccessRule to the security settings. dSecurity.AddAccessRule(new FileSystemAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), FileSystemRights.FullControl, InheritanceFlags.ObjectInherit | InheritanceFlags.ContainerInherit, PropagationFlags.NoPropagateInherit, AccessControlType.Allow)); // Set the access control dinfo.SetAccessControl(dSecurity); _lastError = String.Format("Everyone FullControl Permissions were set for directory {0}", dirName); return true; } catch (Exception ex) { _lastError = ex.Message; return false; } } /// /// /// /// /// /// /// /// /// 7 if C# 7.3 or lower, /// 8 if C# 8.0 (.netstandard2.0), /// 9 if C# 9.0 (.netstandard2.1), /// -1 if error /// public static int GetCSharpVersionFromUnityProjectVersionFile(string? solutionDirectory) { if (string.IsNullOrEmpty(solutionDirectory)) return -1; var projectVersionFilePath = System.IO.Path.Combine(solutionDirectory, "ProjectSettings/ProjectVersion.txt"); if (!System.IO.File.Exists(projectVersionFilePath)) return -1; string? version = null; string[]? readText = null; try { readText = System.IO.File.ReadAllLines(projectVersionFilePath); // format is m_EditorVersion: 2018.2.0b7 string[] versionText = readText[0].Split(' '); version = versionText[1]; } catch (Exception ex) { Serilog.Log.Error(ex, "Project version file parsing error {FirstLine}", readText?.FirstOrDefault()); return -1; } if (version == null) { Serilog.Log.Error("Parsed project version is null"); return -1; } // see my personal notes for Unity version X C# version table // which is at OneNote->ShinSekai->CEPostRelease->UnityCompilerC#VersionTable if (version.StartsWith("5.") || version.StartsWith("2017.") || version.StartsWith("2018.") || version.StartsWith("2019.") || version.StartsWith("2020.1.")) return 7; if (version.StartsWith("2020.") || version.StartsWith("2021.1.")) return 8; if (version.StartsWith("2021.") || version.StartsWith("2022.") || version.StartsWith("2023.") || version.StartsWith("6000.")) return 9; Serilog.Log.Error("Parsed project version is unknown {Version}", version); return -1; } private static void TakeOwnership(string filename) { // Remove read-only attribute File.SetAttributes(filename, File.GetAttributes(filename) & ~FileAttributes.ReadOnly); FileSecurity security = new FileSecurity(); SecurityIdentifier sid = WindowsIdentity.GetCurrent().User; security.SetOwner(sid); security.SetAccessRule(new FileSystemAccessRule(sid, FileSystemRights.FullControl, AccessControlType.Allow)); File.SetAccessControl(filename, security); } private static void Cleanup1(string destination) { // prior to version UCA.v.1.1.9, syncronizerModel and yamlDotNet dll files were located at ProjectPath/Assets/Plugins/CodeAssist/Editor/ExternalReferences/Release/netstandard2.0 // with version UCA.v.1.1.9 and newer versions, they are located at ProjectPath/Assets/Plugins/CodeAssist/Editor/ExternalReferences // delete ProjectPath/Assets/Plugins/CodeAssist/Editor/ExternalReferences/Release var oldBinariesDirectory = Path.Combine(destination, "Plugins/CodeAssist/Editor/ExternalReferences/Release/netstandard2.0"); if (Directory.Exists(oldBinariesDirectory)) { // dont just delete the directory for security reasons, instead delete binary files one by one Serilog.Log.Debug("Old binaries directory exists at {Location}", oldBinariesDirectory); var files = new string[] { "UnityCodeAssistSynchronizerModel.deps.json", "UnityCodeAssistSynchronizerModel.dll", "UnityCodeAssistSynchronizerModel.pdb", "UnityCodeAssistYamlDotNet.deps.json", "UnityCodeAssistYamlDotNet.dll", "UnityCodeAssistYamlDotNet.pdb", "UnityCodeAssistYamlDotNet.xml", }; foreach (var file in files) { var filePath = Path.Combine(oldBinariesDirectory, file); DeleteFileAndItsMeta(filePath); } DeleteDirectoryAndItsMeta(oldBinariesDirectory); } var oldBinariesDirectory2 = Path.Combine(destination, "Plugins/CodeAssist/Editor/ExternalReferences/Release"); DeleteDirectoryAndItsMeta(oldBinariesDirectory2); // also delete old vsix, it's now renamed as Meryel.UnityCodeAssist.VSIX.vsix var oldVsixFilePath = Path.Combine(destination, "Plugins/CodeAssist/UnityCodeAssistVSIX.vsix"); DeleteFileAndItsMeta(oldVsixFilePath); } private static void Cleanup2(string destination) { // with version 1.1.12, dll files has been customized (renamed and changed their namespace) (so that they dont conflict with user's other dll files, if he tries to use them) // delete AsyncIO.dll, and use Meryel.UnityCodeAssist.AsyncIO.dll instead var files = new string[] { "AsyncIO.dll", "NaCl.dll", "NetMQ.dll", "Serilog.dll", "Serilog.Sinks.PersistentFile.dll", }; var binariesDirectory = Path.Combine(destination, "Plugins/CodeAssist/Editor/ExternalReferences"); foreach (var file in files) { var filePath = Path.Combine(binariesDirectory, file); DeleteFileAndItsMeta(filePath); } } private static void Cleanup3(string destination, string solutionDirectory) { // as it turns out, .netstandard2.1 does not need system binaries (for C#9.0, unity versions 2021.2 and newer) //var cSharpVersion = CommonVS.VSCommonTools.GetCSharpVersionFromUnityProjectVersionFile(solutionDirectory); var cSharpVersion = GetCSharpVersionFromUnityProjectVersionFile(solutionDirectory); if (cSharpVersion < 9) return; var systemBinaryFiles = new string[] { "System.Buffers.dll", "System.Memory.dll", "System.Runtime.CompilerServices.Unsafe.dll", "System.Threading.Tasks.Extensions.dll", }; var binariesDirectory = Path.Combine(destination, "Plugins/CodeAssist/Editor/ExternalReferences"); foreach (var file in systemBinaryFiles) { var filePath = Path.Combine(binariesDirectory, file); DeleteFileAndItsMeta(filePath); } } private static void Cleanup4(string destination) { // with version 1.2, asset directory moved from Assets/Plugins/CodeAssist to Packages/com.merry-yellow.code-assist // so remove all files from the old directory var directory = Path.Combine(destination, "Plugins/CodeAssist/Editor"); var content = new string[] { @"TinyJson\JsonWriter.cs", @"TinyJson\JsonParser.cs", @"Preferences\RegistryMonitor.cs", @"Preferences\PreferenceStorageAccessor.cs", @"Preferences\PreferenceMonitor.cs", @"Preferences\PreferenceEntryHolder.cs", @"Logger\UnitySink.cs", @"Logger\MemorySink.cs", @"Logger\ELogger.cs", @"Logger\DomainHashEnricher.cs", @"Logger\CommonTools.cs", @"Logger\Attributes.cs", @"Input\UnityInputManager.cs", @"Input\Text2Yaml.cs", @"Input\InputManagerMonitor.cs", @"Input\Binary2TextExec.cs", @"ExternalReferences\Meryel.UnityCodeAssist.YamlDotNet.xml", @"ExternalReferences\Meryel.UnityCodeAssist.YamlDotNet.pdb", @"ExternalReferences\Meryel.UnityCodeAssist.YamlDotNet.dll", @"ExternalReferences\Meryel.UnityCodeAssist.YamlDotNet.deps.json", @"ExternalReferences\Meryel.UnityCodeAssist.SynchronizerModel.pdb", @"ExternalReferences\Meryel.UnityCodeAssist.SynchronizerModel.dll", @"ExternalReferences\Meryel.UnityCodeAssist.SynchronizerModel.deps.json", @"ExternalReferences\Meryel.UnityCodeAssist.Serilog.xml", @"ExternalReferences\Meryel.UnityCodeAssist.Serilog.Sinks.PersistentFile.pdb", @"ExternalReferences\Meryel.UnityCodeAssist.Serilog.Sinks.PersistentFile.dll", @"ExternalReferences\Meryel.UnityCodeAssist.Serilog.Sinks.PersistentFile.deps.json", @"ExternalReferences\Meryel.UnityCodeAssist.Serilog.pdb", @"ExternalReferences\Meryel.UnityCodeAssist.Serilog.dll", @"ExternalReferences\Meryel.UnityCodeAssist.NetMQ.xml", @"ExternalReferences\Meryel.UnityCodeAssist.NetMQ.pdb", @"ExternalReferences\Meryel.UnityCodeAssist.NetMQ.dll", @"ExternalReferences\Meryel.UnityCodeAssist.NetMQ.deps.json", @"ExternalReferences\Meryel.UnityCodeAssist.NaCl.xml", @"ExternalReferences\Meryel.UnityCodeAssist.NaCl.pdb", @"ExternalReferences\Meryel.UnityCodeAssist.NaCl.dll", @"ExternalReferences\Meryel.UnityCodeAssist.AsyncIO.pdb", @"ExternalReferences\Meryel.UnityCodeAssist.AsyncIO.dll", @"EditorCoroutines\EditorWindowCoroutineExtension.cs", @"EditorCoroutines\EditorWaitForSeconds.cs", @"EditorCoroutines\EditorCoroutineUtility.cs", @"EditorCoroutines\EditorCoroutine.cs", @"UnityClassExtensions.cs", @"StatusWindow.cs", @"ScriptFinder.cs", @"NetMQPublisher.cs", @"NetMQInitializer.cs", @"Monitor.cs", @"MerryYellow.CodeAssist.Editor.asmdef", @"MainThreadDispatcher.cs", @"LazyInitializer.cs", @"FeedbackWindow.cs", @"Assister.cs", @"AboutWindow.cs", //@"TinyJson", //@"Preferences", //@"Logger", //@"Input", //@"ExternalReferences", //@"EditorCoroutines", }; foreach (var c in content) { var path = Path.Combine(directory, c); DeleteFileAndItsMeta(path); } } } }