[
  {
    "path": ".gitignore",
    "content": "[Ll]ibrary/\n[Tt]emp/\n[Oo]bj/\n[Bb]uild/\n[Bb]uilds/\nAssets/AssetStoreTools*\n\n# Visual Studio cache directory\n.vs/\n\n# Autogenerated VS/MD/Consulo solution and project files\nExportedObj/\n.consulo/\n*.csproj\n*.unityproj\n*.sln\n*.suo\n*.tmp\n*.user\n*.userprefs\n*.pidb\n*.booproj\n*.svd\n*.pdb\n*.opendb\n\n# Unity3D generated meta files\n*.pidb.meta\n*.pdb.meta\n\n# Unity3D Generated File On Crash Reports\nsysinfo.txt\n\n# Builds\n*.apk\n*.unitypackage\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2018 Greg M\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "LICENSE.md.meta",
    "content": "fileFormatVersion: 2\nguid: f4b327eab8d866e4087e166da8cafc09\nDefaultImporter:\n  externalObjects: {}\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "README.md",
    "content": "# Recent news! 11/2022\nThe 2022 unite presentation has shown progress on unity's multiplayer abilities, including a built-in process that seems to mimic this approach but in an even better way! That would be rad. Upon its release, this repository may become redundant. \n\n![cloner](https://user-images.githubusercontent.com/30280876/48310703-37780100-e561-11e8-8319-0ecbaeb8c8e4.gif)\n# UnityProjectCloner\nA tool to let the user quickly duplicate their unity project *without copying all the assets*, for multiplayer testing\n\n# Why?\nOne method of quickly debugging multiplayer code is to run multiple unity editors of the same project, and inspect each instance as it works its way through the server/client functions. This is disabled by design for a unity project because of file IO concerns, so this tool lets you get around that by cloning your unity project and creating a series of hard links/junctions in your new cloned folder. These links point back to the original, which can let you edit code and see the results fairly quickly in each cloned unity.\n\n# How?\nThere's a couple ways you can add this to a unity project:\n1. Place this code anywhere in your project!\n2. Check this project out somewhere else and point to it with your Unity package manifest:\n```\"com.hwaet.projectcloner\":  \"file:../../../../[relative path from your manifest file to the package.json]\"```\n3. If you've got git installed on your machine, (and unity is > 2018.3.0b7) add a line in your package manifest that points straight here!\n```\"com.hwaet.projectcloner\": \"https://github.com/hwaet/UnityProjectCloner.git\"```\n4. In recent unity versions, you can add the above git link directly in the editor without manually editing your manifest:\n![image](https://user-images.githubusercontent.com/30280876/132378040-9d985e3b-634b-46ee-a0c5-5fd52cf37f0b.png)\n\n\n# Where?\nThe new cloned project will be placed right next to the original. So your folder structure will look like:\n- Root/ProjectName\n- Root/ProjectName_clone_0\n- Root/ProjectName_clone_1\n...\n\nTo open the window which allows to create a clone and manage it, in Unity Editor go to \"Tools/Project Cloner\". After the clone is created, you can launch it from the same window (\"Open clone project\" button will appear there). No need to add the clone to Unity Hub or anywhere else.\n\n# Future Plans:\n- I'm currently using a terminal command to create the folder links instead of kernel32, and that would be much wiser. So that'll be implemented in future passes of this.\n- Mac and linux suport would be swell.\n\n# Known Issues:\n- When using scriptable objects in one Editor instance, the user will need to trigger a save (either scene or project saves seem to work) in order for the clone to notice that change. Otherwise the scriptable object's values seem to be cached. Save often while you work with SOs!\n"
  },
  {
    "path": "README.md.meta",
    "content": "fileFormatVersion: 2\nguid: fa3f2fa6aced9b54b970c8996957df3b\nTextScriptImporter:\n  externalObjects: {}\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "UnityProjectCloner/Editor/ProjectCloner.cs",
    "content": "﻿using System.Collections;\nusing System.Collections.Generic;\nusing UnityEngine;\nusing UnityEditor;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing System.IO;\nusing UnityProjectCloner;\n\nnamespace UnityProjectCloner\n{\n    /// <summary>\n    /// Contains all required methods for creating a linked clone of the Unity project.\n    /// </summary>\n    public class ProjectCloner\n    {\n        /// <summary>\n        /// Name used for an identifying file created in the clone project directory.\n        /// </summary>\n        /// <remarks>\n        /// (!) Do not change this after the clone was created, because then connection will be lost.\n        /// </remarks>\n        public const string CloneFileName = \".clone\";\n\n        /// <summary>\n        /// Suffix added to the end of the project clone name when it is created.\n        /// </summary>\n        /// <remarks>\n        /// (!) Do not change this after the clone was created, because then connection will be lost.\n        /// </remarks>\n        public const string CloneNameSuffix = \"_clone\";\n\n        public const int MaxCloneProjectCount = 10;\n\n        #region Managing clones\n        /// <summary>\n        /// Creates clone from the project currently open in Unity Editor.\n        /// </summary>\n        /// <returns></returns>\n        public static Project CreateCloneFromCurrent()\n        {\n            if (IsClone())\n            {\n                Debug.LogError(\"This project is already a clone. Cannot clone it.\");\n                return null;\n            }\n\n            string currentProjectPath = ProjectCloner.GetCurrentProjectPath();\n            return ProjectCloner.CreateCloneFromPath(currentProjectPath);\n        }\n\n        /// <summary>\n        /// Creates clone of the project located at the given path.\n        /// </summary>\n        /// <param name=\"sourceProjectPath\"></param>\n        /// <returns></returns>\n        public static Project CreateCloneFromPath(string sourceProjectPath)\n        {\n            Project sourceProject = new Project(sourceProjectPath);\n\n            string cloneProjectPath = null;\n\n            //Find available clone suffix id\n            for (int i = 0; i < MaxCloneProjectCount; i++)\n            {\n                string originalProjectPath = ProjectCloner.GetCurrentProject().projectPath;\n                string possibleCloneProjectPath = originalProjectPath + ProjectCloner.CloneNameSuffix + \"_\" + i;\n\n                if (!Directory.Exists(possibleCloneProjectPath))\n                {\n                    cloneProjectPath = possibleCloneProjectPath;\n                    break;\n                }\n            }\n            if (string.IsNullOrEmpty(cloneProjectPath))\n            {\n                Debug.LogError(\"The number of cloned projects has reach its limit. Limit: \" + MaxCloneProjectCount);\n                return null;\n            }\n\n            Project cloneProject = new Project(cloneProjectPath);\n\n            Debug.Log(\"Start project name: \" + sourceProject);\n            Debug.Log(\"Clone project name: \" + cloneProject);\n\n            ProjectCloner.CreateProjectFolder(cloneProject);\n            ProjectCloner.CopyLibraryFolder(sourceProject, cloneProject);\n\n            ProjectCloner.LinkFolders(sourceProject.assetPath, cloneProject.assetPath);\n            ProjectCloner.LinkFolders(sourceProject.projectSettingsPath, cloneProject.projectSettingsPath);\n            ProjectCloner.LinkFolders(sourceProject.packagesPath, cloneProject.packagesPath);\n            ProjectCloner.LinkFolders(sourceProject.autoBuildPath, cloneProject.autoBuildPath);\n\n            ProjectCloner.RegisterClone(cloneProject);\n\n            return cloneProject;\n        }\n\n        /// <summary>\n        /// Registers a clone by placing an identifying \".clone\" file in its root directory.\n        /// </summary>\n        /// <param name=\"cloneProject\"></param>\n        private static void RegisterClone(Project cloneProject)\n        {\n            /// Add clone identifier file.\n            string identifierFile = Path.Combine(cloneProject.projectPath, ProjectCloner.CloneFileName);\n            File.Create(identifierFile).Dispose();\n\n            /// Add collabignore.txt to stop the clone from messing with Unity Collaborate if it's enabled. Just in case.\n            string collabignoreFile = Path.Combine(cloneProject.projectPath, \"collabignore.txt\");\n            File.WriteAllText(collabignoreFile, \"*\"); /// Make it ignore ALL files in the clone.\n        }\n\n        /// <summary>\n        /// Opens a project located at the given path (if one exists).\n        /// </summary>\n        /// <param name=\"projectPath\"></param>\n        public static void OpenProject(string projectPath)\n        {\n            if (!Directory.Exists(projectPath))\n            {\n                Debug.LogError(\"Cannot open the project - provided folder (\" + projectPath + \") does not exist.\");\n                return;\n            }\n            if (projectPath == ProjectCloner.GetCurrentProjectPath())\n            {\n                Debug.LogError(\"Cannot open the project - it is already open.\");\n                return;\n            }\n\n            string fileName = EditorApplication.applicationPath;\n            string args = \"-projectPath \\\"\" + projectPath + \"\\\"\";\n            Debug.Log(\"Opening project \\\"\" + fileName + \" \" + args + \"\\\"\");\n            ProjectCloner.StartHiddenConsoleProcess(fileName, args);\n        }\n\n        /// <summary>\n        /// Deletes the clone of the currently open project, if such exists.\n        /// </summary>\n        public static void DeleteClone(string cloneProjectPath)\n        {\n            /// Clone won't be able to delete itself.\n            if (ProjectCloner.IsClone()) return;\n\n            ///Extra precautions.\n            if (cloneProjectPath == string.Empty) return;\n            if (cloneProjectPath == ProjectCloner.GetOriginalProjectPath()) return;\n            if (!cloneProjectPath.EndsWith(ProjectCloner.CloneNameSuffix)) return;\n\n            //Check what OS is\n            switch (Application.platform)\n            {\n                case (RuntimePlatform.WindowsEditor):\n                    Debug.Log(\"Attempting to delete folder \\\"\" + cloneProjectPath + \"\\\"\");\n                    string args = \"/c \" + @\"rmdir /s/q \" + string.Format(\"\\\"{0}\\\"\", cloneProjectPath);\n                    StartHiddenConsoleProcess(\"cmd.exe\", args);\n                   \n                    break;\n                case (RuntimePlatform.OSXEditor):\n                    throw new System.NotImplementedException(\"No Mac function implement yet :(\");\n                    //break;\n                case (RuntimePlatform.LinuxEditor):\n                    throw new System.NotImplementedException(\"No linux support yet :(\");\n                    //break;\n                default:\n                    Debug.LogWarning(\"Not in a known editor. Where are you!?\");\n                    break;\n            }\n        }\n        #endregion\n\n        #region Creating project folders\n        /// <summary>\n        /// Creates an empty folder using data in the given Project object\n        /// </summary>\n        /// <param name=\"project\"></param>\n        public static void CreateProjectFolder(Project project)\n        {\n            string path = project.projectPath;\n            Debug.Log(\"Creating new empty folder at: \" + path);\n            Directory.CreateDirectory(path);\n        }\n\n        /// <summary>\n        /// Copies the full contents of the unity library. We want to do this to avoid the lengthy reserialization of the whole project when it opens up the clone.\n        /// </summary>\n        /// <param name=\"sourceProject\"></param>\n        /// <param name=\"destinationProject\"></param>\n        public static void CopyLibraryFolder(Project sourceProject, Project destinationProject)\n        {\n            if (Directory.Exists(destinationProject.libraryPath))\n            {\n                Debug.LogWarning(\"Library copy: destination path already exists! \");\n                return;\n            }\n\n            Debug.Log(\"Library copy: \" + destinationProject.libraryPath);\n            ProjectCloner.CopyDirectoryWithProgressBar(sourceProject.libraryPath, destinationProject.libraryPath, \"Cloning project '\" + sourceProject.name + \"'. \");\n        }\n        #endregion\n\n        #region Creating symlinks\n        /// <summary>\n        /// Creates a symlink between destinationPath and sourcePath (Mac version).\n        /// </summary>\n        /// <param name=\"sourcePath\"></param>\n        /// <param name=\"destinationPath\"></param>\n        private static void CreateLinkMac(string sourcePath, string destinationPath)\n        {\n            Debug.LogWarning(\"This hasn't been tested yet! I am mac-less :( Please chime in on the github if it works for you.\");\n\n            string cmd = \"ln \" + string.Format(\"\\\"{0}\\\" \\\"{1}\\\"\", destinationPath, sourcePath);\n            Debug.Log(\"Mac hard link \" + cmd);\n\n            ProjectCloner.StartHiddenConsoleProcess(\"/bin/bash\", cmd);\n        }\n\n        /// <summary>\n        /// Creates a symlink between destinationPath and sourcePath (Windows version).\n        /// </summary>\n        /// <param name=\"sourcePath\"></param>\n        /// <param name=\"destinationPath\"></param>\n        private static void CreateLinkWin(string sourcePath, string destinationPath)\n        {\n            string cmd = \"/C mklink /J \" + string.Format(\"\\\"{0}\\\" \\\"{1}\\\"\", destinationPath, sourcePath);\n            Debug.Log(\"Windows junction: \" + cmd);\n            ProjectCloner.StartHiddenConsoleProcess(\"cmd.exe\", cmd);\n        }\n\n        /// <summary>\n        /// Creates a symlink between destinationPath and sourcePath (Linux version).\n        /// </summary>\n        /// <param name=\"sourcePath\"></param>\n        /// <param name=\"destinationPath\"></param>\n        private static void CreateLinkLunux(string sourcePath, string destinationPath)\n        {\n            string cmd = string.Format(\"-c \\\"ln -s {0} {1}\\\"\", sourcePath, destinationPath);\n            Debug.Log(\"Linux junction: \" + cmd);\n            ProjectCloner.StartHiddenConsoleProcess(\"/bin/bash\", cmd);\n        }\n\n        //TODO avoid terminal calls and use proper api stuff. See below for windows! \n        ////https://docs.microsoft.com/en-us/windows/desktop/api/ioapiset/nf-ioapiset-deviceiocontrol\n        //[DllImport(\"kernel32.dll\", CharSet = CharSet.Auto, SetLastError = true)]\n        //private static extern bool DeviceIoControl(System.IntPtr hDevice, uint dwIoControlCode,\n        //\tSystem.IntPtr InBuffer, int nInBufferSize,\n        //\tSystem.IntPtr OutBuffer, int nOutBufferSize,\n        //\tout int pBytesReturned, System.IntPtr lpOverlapped);\n\n        /// <summary>\n        /// Create a link / junction from the real project to it's clone.\n        /// </summary>\n        /// <param name=\"sourcePath\"></param>\n        /// <param name=\"destinationPath\"></param>\n        public static void LinkFolders(string sourcePath, string destinationPath)\n        {\n            if ((Directory.Exists(destinationPath) == false) && (Directory.Exists(sourcePath) == true))\n            {\n                switch (Application.platform)\n                {\n                    case (RuntimePlatform.WindowsEditor):\n                        CreateLinkWin(sourcePath, destinationPath);\n                        break;\n                    case (RuntimePlatform.OSXEditor):\n                        CreateLinkMac(sourcePath, destinationPath);\n                        break;\n                    case (RuntimePlatform.LinuxEditor):\n                        CreateLinkLunux(sourcePath, destinationPath);\n                        break;\n                    default:\n                        Debug.LogWarning(\"Not in a known editor. Where are you!?\");\n                        break;\n                }\n            }\n            else\n            {\n                Debug.LogWarning(\"Skipping Asset link, it already exists: \" + destinationPath);\n            }\n        }\n        #endregion\n\n        #region Utility methods\n        /// <summary>\n        /// Returns true is the project currently open in Unity Editor is a clone.\n        /// </summary>\n        /// <returns></returns>\n        public static bool IsClone()\n        {\n            /// The project is a clone if its root directory contains an empty file named \".clone\".\n            string cloneFilePath = Path.Combine(ProjectCloner.GetCurrentProjectPath(), ProjectCloner.CloneFileName);\n            bool isClone = File.Exists(cloneFilePath);\n            return isClone;\n        }\n\n        /// <summary>\n        /// Get the path to the current unityEditor project folder's info\n        /// </summary>\n        /// <returns></returns>\n        public static string GetCurrentProjectPath()\n        {\n            return Application.dataPath.Replace(\"/Assets\", \"\");\n        }\n\n        /// <summary>\n        /// Return a project object that describes all the paths we need to clone it.\n        /// </summary>\n        /// <returns></returns>\n        public static Project GetCurrentProject()\n        {\n            string pathString = ProjectCloner.GetCurrentProjectPath();\n            return new Project(pathString);\n        }\n\n        /// <summary>\n        /// Returns the path to the original project.\n        /// If currently open project is the original, returns its own path.\n        /// If the original project folder cannot be found, retuns an empty string.\n        /// </summary>\n        /// <returns></returns>\n        public static string GetOriginalProjectPath()\n        {\n            if (IsClone())\n            {\n                /// If this is a clone...\n                /// Original project path can be deduced by removing the suffix from the clone's path.\n                string cloneProjectPath = ProjectCloner.GetCurrentProject().projectPath;\n\n                int index = cloneProjectPath.LastIndexOf(ProjectCloner.CloneNameSuffix);\n                if (index > 0)\n                {\n                    string originalProjectPath = cloneProjectPath.Substring(0, index);\n                    if (Directory.Exists(originalProjectPath)) return originalProjectPath;\n                }\n\n                return string.Empty;\n            }\n            else\n            {\n                /// If this is the original, we return its own path.\n                return ProjectCloner.GetCurrentProjectPath();\n            }\n        }\n\n        /// <summary>\n        /// Returns all clone projects path.\n        /// </summary>\n        /// <returns></returns>\n        public static List<string> GetCloneProjectsPath()\n        {\n            List<string> projectsPath = new List<string>();\n            for (int i = 0; i < MaxCloneProjectCount; i++)\n            {\n                string originalProjectPath = ProjectCloner.GetCurrentProject().projectPath;\n                string cloneProjectPath = originalProjectPath + ProjectCloner.CloneNameSuffix + \"_\" + i;\n\n                if (Directory.Exists(cloneProjectPath))\n                    projectsPath.Add(cloneProjectPath);\n            }\n            return projectsPath;\n        }\n\n        /// <summary>\n        /// Copies directory located at sourcePath to destinationPath. Displays a progress bar.\n        /// </summary>\n        /// <param name=\"source\">Directory to be copied.</param>\n        /// <param name=\"destination\">Destination directory (created automatically if needed).</param>\n        /// <param name=\"progressBarPrefix\">Optional string added to the beginning of the progress bar window header.</param>\n        public static void CopyDirectoryWithProgressBar(string sourcePath, string destinationPath, string progressBarPrefix = \"\")\n        {\n            var source = new DirectoryInfo(sourcePath);\n            var destination = new DirectoryInfo(destinationPath);\n\n            long totalBytes = 0;\n            long copiedBytes = 0;\n\n            ProjectCloner.CopyDirectoryWithProgressBarRecursive(source, destination, ref totalBytes, ref copiedBytes, progressBarPrefix);\n            EditorUtility.ClearProgressBar();\n        }\n\n        /// <summary>\n        /// Copies directory located at sourcePath to destinationPath. Displays a progress bar.\n        /// Same as the previous method, but uses recursion to copy all nested folders as well.\n        /// </summary>\n        /// <param name=\"source\">Directory to be copied.</param>\n        /// <param name=\"destination\">Destination directory (created automatically if needed).</param>\n        /// <param name=\"totalBytes\">Total bytes to be copied. Calculated automatically, initialize at 0.</param>\n        /// <param name=\"copiedBytes\">To track already copied bytes. Calculated automatically, initialize at 0.</param>\n        /// <param name=\"progressBarPrefix\">Optional string added to the beginning of the progress bar window header.</param>\n        private static void CopyDirectoryWithProgressBarRecursive(DirectoryInfo source, DirectoryInfo destination, ref long totalBytes, ref long copiedBytes, string progressBarPrefix = \"\")\n        {\n            /// Directory cannot be copied into itself.\n            if (source.FullName.ToLower() == destination.FullName.ToLower())\n            {\n                Debug.LogError(\"Cannot copy directory into itself.\");\n                return;\n            }\n\n            /// Calculate total bytes, if required.\n            if (totalBytes == 0)\n            {\n                totalBytes = ProjectCloner.GetDirectorySize(source, true, progressBarPrefix);\n            }\n\n            /// Create destination directory, if required.\n            if (!Directory.Exists(destination.FullName))\n            {\n                Directory.CreateDirectory(destination.FullName);\n            }\n\n            /// Copy all files from the source.\n            foreach (FileInfo file in source.GetFiles())\n            {\n                try\n                {\n                    file.CopyTo(Path.Combine(destination.ToString(), file.Name), true);\n                }\n                catch (IOException)\n                {\n                    /// Some files may throw IOException if they are currently open in Unity editor.\n                    /// Just ignore them in such case.\n                }\n\n                /// Account the copied file size.\n                copiedBytes += file.Length;\n\n                /// Display the progress bar.\n                float progress = (float)copiedBytes / (float)totalBytes;\n                bool cancelCopy = EditorUtility.DisplayCancelableProgressBar(\n                    progressBarPrefix + \"Copying '\" + source.FullName + \"' to '\" + destination.FullName + \"'...\",\n                    \"(\" + (progress * 100f).ToString(\"F2\") + \"%) Copying file '\" + file.Name + \"'...\",\n                    progress);\n                if (cancelCopy) return;\n            }\n\n            /// Copy all nested directories from the source.\n            foreach (DirectoryInfo sourceNestedDir in source.GetDirectories())\n            {\n                DirectoryInfo nextDestingationNestedDir = destination.CreateSubdirectory(sourceNestedDir.Name);\n                ProjectCloner.CopyDirectoryWithProgressBarRecursive(sourceNestedDir, nextDestingationNestedDir, ref totalBytes, ref copiedBytes, progressBarPrefix);\n            }\n        }\n\n        /// <summary>\n        /// Calculates the size of the given directory. Displays a progress bar.\n        /// </summary>\n        /// <param name=\"directory\">Directory, which size has to be calculated.</param>\n        /// <param name=\"includeNested\">If true, size will include all nested directories.</param>\n        /// <param name=\"progressBarPrefix\">Optional string added to the beginning of the progress bar window header.</param>\n        /// <returns>Size of the directory in bytes.</returns>\n        private static long GetDirectorySize(DirectoryInfo directory, bool includeNested = false, string progressBarPrefix = \"\")\n        {\n            EditorUtility.DisplayProgressBar(progressBarPrefix + \"Calculating size of directories...\", \"Scanning '\" + directory.FullName + \"'...\", 0f);\n\n            /// Calculate size of all files in directory.\n            long filesSize = directory.EnumerateFiles().Sum((FileInfo file) => file.Length);\n\n            /// Calculate size of all nested directories.\n            long directoriesSize = 0;\n            if (includeNested)\n            {\n                IEnumerable<DirectoryInfo> nestedDirectories = directory.EnumerateDirectories();\n                foreach (DirectoryInfo nestedDir in nestedDirectories)\n                {\n                    directoriesSize += ProjectCloner.GetDirectorySize(nestedDir, true, progressBarPrefix);\n                }\n            }\n\n            return filesSize + directoriesSize;\n        }\n\n        /// <summary>\n        /// Starts process in the system console, taking the given fileName and args.\n        /// </summary>\n        /// <param name=\"fileName\"></param>\n        /// <param name=\"args\"></param>\n        private static void StartHiddenConsoleProcess(string fileName, string args)\n        {\n            var process = new System.Diagnostics.Process();\n            //process.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;\n            process.StartInfo.FileName = fileName;\n            process.StartInfo.Arguments = args;\n\n            process.Start();\n        }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "UnityProjectCloner/Editor/ProjectCloner.cs.meta",
    "content": "fileFormatVersion: 2\nguid: 6148e48ed6b61d748b187d06d3687b83\nMonoImporter:\n  externalObjects: {}\n  serializedVersion: 2\n  defaultReferences: []\n  executionOrder: 0\n  icon: {instanceID: 0}\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "UnityProjectCloner/Editor/ProjectClonerWindow.cs",
    "content": "﻿using System.Collections;\nusing System.Collections.Generic;\nusing UnityEngine;\nusing UnityEditor;\nusing UnityProjectCloner;\nusing System.IO;\n\nnamespace UnityProjectCloner\n{\n    /// <summary>\n    /// Provides Unity Editor window for ProjectCloner.\n    /// </summary>\n\tpublic class ProjectClonerWindow : EditorWindow\n    {\n        /// <summary>\n        /// True if currently open project is a clone.\n        /// </summary>\n        public bool isClone\n        {\n            get { return ProjectCloner.IsClone(); }\n        }\n\n        /// <summary>\n        /// Returns true if project clone exists.\n        /// </summary>\n        public bool isCloneCreated\n        {\n            get { return ProjectCloner.GetCloneProjectsPath().Count >= 1; }\n        }\n\n        [MenuItem(\"Tools/Project Cloner\")]\n        private static void InitWindow()\n        {\n            ProjectClonerWindow window = (ProjectClonerWindow)EditorWindow.GetWindow(typeof(ProjectClonerWindow));\n            window.titleContent = new GUIContent(\"Project Cloner\");\n            window.Show();\n        }\n\n        private void OnGUI()\n        {\n            if (isClone)\n            {\n                /// If it is a clone project...\n                string originalProjectPath = ProjectCloner.GetOriginalProjectPath();\n                if (originalProjectPath == string.Empty)\n                {\n                    /// If original project cannot be found, display warning message.\n                    string thisProjectName = ProjectCloner.GetCurrentProject().name;\n                    string supposedOriginalProjectName = ProjectCloner.GetCurrentProject().name.Replace(\"_clone\", \"\");\n                    EditorGUILayout.HelpBox(\n                        \"This project is a clone, but the link to the original seems lost.\\nYou have to manually open the original and create a new clone instead of this one.\\nThe original project should have a name '\" + supposedOriginalProjectName + \"', if it was not changed.\",\n                        MessageType.Warning);\n                }\n                else\n                {\n                    /// If original project is present, display some usage info.\n                    EditorGUILayout.HelpBox(\n                        \"This project is a clone of the project '\" + Path.GetFileName(originalProjectPath) + \"'.\\nIf you want to make changes the project files or manage clones, please open the original project through Unity Hub.\",\n                        MessageType.Info);\n                }\n            }\n            else\n            {\n                /// If it is an original project...\n                if (isCloneCreated)\n                {\n                    GUILayout.BeginVertical(\"HelpBox\");\n                    GUILayout.Label(\"Clones of this Project\");\n                    /// If clone(s) is created, we can either open it or delete it.\n                    var cloneProjectsPath = ProjectCloner.GetCloneProjectsPath();\n                    for (int i = 0; i < cloneProjectsPath.Count; i++)\n                    {\n                      \n                        GUILayout.BeginVertical(\"GroupBox\");\n                        string cloneProjectPath = cloneProjectsPath[i];\n                        EditorGUILayout.LabelField(\"Clone \" + i);\n                        EditorGUILayout.TextField(\"Clone project path\", cloneProjectPath, EditorStyles.textField);\n                        if (GUILayout.Button(\"Open in New Editor\"))\n                        {\n                            ProjectCloner.OpenProject(cloneProjectPath);\n                        }\n                        GUILayout.BeginHorizontal();\n                        if (GUILayout.Button(\"Delete\"))\n                        {\n                            bool delete = EditorUtility.DisplayDialog(\n                                \"Delete the clone?\",\n                                \"Are you sure you want to delete the clone project '\" + ProjectCloner.GetCurrentProject().name + \"_clone'? If so, you can always create a new clone from ProjectCloner window.\",\n                                \"Delete\",\n                                \"Cancel\");\n                            if (delete)\n                            {\n                                ProjectCloner.DeleteClone(cloneProjectPath);\n                            }\n                        }\n\n                        //Offer a solution to user in-case they are stuck with deleting project\n                        if (GUILayout.Button(\"?\", GUILayout.Width(30)))\n                        {\n                            var openUrl = EditorUtility.DisplayDialog(\"Can't delete clone?\",\n                            \"Sometime clone can't be deleted due to it's still being opened by another unity instance running in the background.\" +\n                            \"\\nYou can read this answer from ServerFault on how to find and kill the process.\", \"Open Answer\");\n                            if (openUrl)\n                            {\n                                Application.OpenURL(\"https://serverfault.com/a/537762\");\n                            }\n                        }\n                        GUILayout.EndHorizontal();\n                        GUILayout.EndVertical();\n                      \n                    }\n                    GUILayout.EndVertical();\n                    //Have difficulty with naming\n                    //GUILayout.Label(\"Other\", EditorStyles.boldLabel);\n                    if (GUILayout.Button(\"Add new clone\"))\n                    {\n                        ProjectCloner.CreateCloneFromCurrent();\n                    }\n                }\n                else\n                {\n                    /// If no clone created yet, we must create it.\n                    EditorGUILayout.HelpBox(\"No project clones found. Create a new one!\", MessageType.Info);\n                    if (GUILayout.Button(\"Create new clone\"))\n                    {\n                        ProjectCloner.CreateCloneFromCurrent();\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "UnityProjectCloner/Editor/ProjectClonerWindow.cs.meta",
    "content": "fileFormatVersion: 2\nguid: a041d83486c20b84bbf5077ddfbbca37\nMonoImporter:\n  externalObjects: {}\n  serializedVersion: 2\n  defaultReferences: []\n  executionOrder: 0\n  icon: {instanceID: 0}\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "UnityProjectCloner/Editor.meta",
    "content": "fileFormatVersion: 2\nguid: b0e79e43e01ff3e4991e8902ac89b322\nfolderAsset: yes\nDefaultImporter:\n  externalObjects: {}\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "UnityProjectCloner/Project.cs",
    "content": "﻿using System.Collections;\nusing System.Collections.Generic;\nusing UnityEngine;\nusing System.Linq;\nusing UnityProjectCloner;\n\nnamespace UnityProjectCloner\n{\n\tpublic class Project : System.ICloneable\n\t{\n\t\tpublic string name;\n\t\tpublic string projectPath;\n\t\tstring rootPath;\n\t\tpublic string assetPath;\n\t\tpublic string projectSettingsPath;\n\t\tpublic string libraryPath;\n\t\tpublic string packagesPath;\n\t\tpublic string autoBuildPath;\n\t\tchar[] separator = new char[1] { '/' };\n\n\n\t\t/// <summary>\n\t\t/// Default constructor\n\t\t/// </summary>\n\t\tpublic Project()\n\t\t{\n\n\t\t}\n\n\n\t\t/// <summary>\n\t\t/// Initialize the project object by parsing its full path returned by Unity into a bunch of individual folder names and paths.\n\t\t/// </summary>\n\t\t/// <param name=\"path\"></param>\n\t\tpublic Project(string path)\n\t\t{\n\t\t\tParsePath(path);\n\t\t}\n\n\n\t\t/// <summary>\n\t\t/// Create a new object with the same settings\n\t\t/// </summary>\n\t\t/// <returns></returns>\n\t\tpublic object Clone()\n\t\t{\n\t\t\tProject newProject = new Project();\n\t\t\tnewProject.rootPath = rootPath;\n\t\t\tnewProject.projectPath = projectPath;\n\t\t\tnewProject.assetPath = assetPath;\n\t\t\tnewProject.projectSettingsPath = projectSettingsPath;\n\t\t\tnewProject.libraryPath = libraryPath;\n\t\t\tnewProject.name = name;\n\t\t\tnewProject.separator = separator;\n\t\t\tnewProject.packagesPath = packagesPath;\n\t\t\tnewProject.autoBuildPath = autoBuildPath;\n\n\t\t\treturn newProject;\n\t\t}\n\n\n\t\t/// <summary>\n\t\t/// Update the project object by renaming and reparsing it. Pass in the new name of a project, and it'll update the other member variables to match.\n\t\t/// </summary>\n\t\t/// <param name=\"name\"></param>\n\t\tpublic void updateNewName(string newName)\n\t\t{\n\t\t\tname = newName;\n\t\t\tParsePath(rootPath + \"/\" + name + \"/Assets\");\n\t\t}\n\n\n\t\t/// <summary>\n\t\t/// Debug override so we can quickly print out the project info.\n\t\t/// </summary>\n\t\t/// <returns></returns>\n\t\tpublic override string ToString()\n\t\t{\n\t\t\tstring printString = name + \"\\n\" +\n\t\t\t\t\t\t\t\t rootPath + \"\\n\" +\n\t\t\t\t\t\t\t\t projectPath + \"\\n\" +\n\t\t\t\t\t\t\t\t assetPath + \"\\n\" +\n\t\t\t\t\t\t\t\t projectSettingsPath + \"\\n\" +\n\t\t\t\t\t\t\t\t packagesPath + \"\\n\" +\n\t\t\t\t\t\t\t\t autoBuildPath + \"\\n\" +\n\t\t\t\t\t\t\t\t libraryPath;\n\t\t\treturn (printString);\n\t\t}\n\n\t\tprivate void ParsePath(string path)\n\t\t{\n\t\t\t//Unity's Application functions return the Assets path in the Editor. \n\t\t\tprojectPath = path;\n\n\t\t\t//pop off the last part of the path for the project name, keep the rest for the root path\n\t\t\tList<string> pathArray = projectPath.Split(separator).ToList<string>();\n\t\t\tname = pathArray.Last();\n\n\t\t\tpathArray.RemoveAt(pathArray.Count() - 1);\n\t\t\trootPath = string.Join(separator[0].ToString(), pathArray);\n\n\t\t\tassetPath = projectPath + \"/Assets\";\n\t\t\tprojectSettingsPath = projectPath + \"/ProjectSettings\";\n\t\t\tlibraryPath = projectPath + \"/Library\";\n\t\t\tpackagesPath = projectPath + \"/Packages\";\n\t\t\tautoBuildPath = projectPath + \"/AutoBuild\";\n\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "UnityProjectCloner/Project.cs.meta",
    "content": "fileFormatVersion: 2\nguid: ec8d3a1577179ef44815739178cf75b4\nMonoImporter:\n  externalObjects: {}\n  serializedVersion: 2\n  defaultReferences: []\n  executionOrder: 0\n  icon: {instanceID: 0}\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "UnityProjectCloner/projectCloner.asmdef",
    "content": "{\n    \"name\": \"projectCloner\",\n    \"references\": [],\n    \"optionalUnityReferences\": [],\n\t\"includePlatforms\": [\n        \"Editor\"\n    ],\n    \"includePlatforms\": [],\n    \"excludePlatforms\": [],\n    \"allowUnsafeCode\": false,\n    \"overrideReferences\": false,\n    \"precompiledReferences\": [],\n    \"autoReferenced\": true,\n    \"defineConstraints\": []\n}"
  },
  {
    "path": "UnityProjectCloner/projectCloner.asmdef.meta",
    "content": "fileFormatVersion: 2\nguid: 894a6cc6ed5cd2645bb542978cbed6a9\nAssemblyDefinitionImporter:\n  externalObjects: {}\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "UnityProjectCloner.meta",
    "content": "fileFormatVersion: 2\nguid: 8f5fec620d3bc9546a41a5b67cb9f8b6\nfolderAsset: yes\nDefaultImporter:\n  externalObjects: {}\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"com.hwaet.projectcloner\",\n    \"displayName\": \"Project Cloner\",\n    \"version\": \"0.0.1\",\n    \"unity\": \"2018.2\",\n    \"description\": \"A tool to let the user quickly clone their project, for multiplayer testing.\",\n    \"keywords\": [\n        \"multiplayer\",\n        \"unity\",\n\t\t\"editor\"\n    ],\n    \"category\": \"Unity\"\n}"
  },
  {
    "path": "package.json.meta",
    "content": "fileFormatVersion: 2\nguid: ab4f81f15974d5a4a88263c7a3f67737\nTextScriptImporter:\n  externalObjects: {}\n  userData: \n  assetBundleName: \n  assetBundleVariant: \n"
  }
]