using System; using System.IO; using System.Linq; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace ZenFulcrum.EmbeddedBrowser { public static class FileLocations { public const string SlaveExecutable = "ZFGameBrowser"; private static CEFDirs _dirs; public static CEFDirs Dirs { get { return _dirs ?? (_dirs = GetCEFDirs()); } } public class CEFDirs { /** Where to find cef.pak, et al */ public string resourcesPath; /** Where to find .dll, .so, natives_blob.bin, etc */ public string binariesPath; /** Where to find en-US.pak et al */ public string localesPath; /** The executable to run for browser processes. */ public string subprocessFile; /** Editor/application log file */ public string logFile; /** True if the given log file is also the Unity log. */ public bool logFileIsUnityLog = true; } /// /// Tries to mimic Unity's game/company name to directory name mapping. /// For log folders at least. /// private static string GetFolderName(string name) { //Folders: //Com`~!@#$%^&*()_+-=[ ]\{}|;':",./<>?←→‽pany // becomes the Windows log folder //Com`_!@#__^__()_+-=[ ]_{}_;'__,.____←→‽pany // though it looks like the Editor analytics go to //Com`~!@#$_^&_()_+-=[ ]_{}_;'__,_____←→‽pany // And in the registry nothing is escaped and the name literally gets split across sub-folders: // «Com`~!@#$%^&*()_+-=[ ]» with a folder in it called «{}|;':",./<>?←→‽pany» // And on Linux the log folder is //Com`~!@#$_^&_()_+-=[ ]_{}_;'__,_____←→‽pany // And the Linux analytics folder is //Com`~!@#$%^&_()_+-=[ ]_{}_;'__,.____←→‽pany // BUT WAIT THERES MORE! // Under Linux Unity 2019.2 we get //Com`_!@#__^__()_+-=[ ]_{}_;'__,.____←→‽pany //Game name: //Gam`~!@#$%^&*()_+-=[ ]\{}|;':",./<>?←→‽e // get the log folder //Gam`_!@#__^__()_+-=[ ]_{}_;'__,.____←→‽e // becomes the executable //Ga`~!@#$%^&()_+-=[]{};',.←→‽me.exe // and the Linux the log folder is //Gam`~!@#$%^&_()_+-=[ ]_{}_;'__,.____←→‽e // And the Linux analytics folder is //Gam`~!@#$_^&_()_+-=[ ]_{}_;'__,_____←→‽e // and Linux Unity 2019.2 //Gam`_!@#__^__()_+-=[ ]_{}_;'__,.____←→‽e //Gam`~!@#$%^&*()_+-=[ ]\{}|;':",./<>?←→‽e src //Gam`_!@#__^__()_+-=[ ]_{}_;'__,.____←→‽e Windows //Gam`~!@#$%^&_()_+-=[ ]_{}_;'__,.____←→‽e Linux //Gam`_!@#__^__()_+-=[ ]_{}_;'__,.____←→‽e Linux 2019.2+ char[] nixChars = { #if (UNITY_EDITOR_WIN || (UNITY_STANDALONE_WIN && !UNITY_EDITOR)) || (UNITY_STANDALONE_LINUX && UNITY_2019_2_OR_NEWER && !UNITY_EDITOR) '~', '$', '%', '&', #endif '*', '\\', '|', ':', '"', '/', '<', '>', '?', }; return nixChars.Aggregate(name, (current, ch) => current.Replace(ch, '_')); } private static CEFDirs GetCEFDirs() { //Note on "Editor-browser.log" and "Player-browser.log": //Starting with 2019.1 Unity seems to use a different method to write to the log. It no longer appends //to the log at the current end, but just keeps writing from the last position. //This causes our CEF log messages to get written over, so we use a separate file for CEF. #if UNITY_EDITOR //In the editor we don't know exactly where we are at, but we can look up one of our scripts and move from there var guids = AssetDatabase.FindAssets("EditorWebResources"); if (guids.Length != 1) throw new FileNotFoundException("Failed to locate a single EditorWebResources file"); string scriptPath = AssetDatabase.GUIDToAssetPath(guids[0]); // ReSharper disable once PossibleNullReferenceException var baseDir = Directory.GetParent(scriptPath).Parent.FullName + "/Plugins"; string resourcesPath, localesDir; var platformDir = baseDir; #if UNITY_EDITOR_WIN #if UNITY_EDITOR_64 platformDir += "/w64"; #else platformDir += "/w32"; #endif resourcesPath = platformDir + "/CEFResources"; localesDir = resourcesPath + "/locales"; //Silly MS. resourcesPath = resourcesPath.Replace("/", "\\"); localesDir = localesDir.Replace("/", "\\"); platformDir = platformDir.Replace("/", "\\"); var subprocessFile = platformDir + "/" + SlaveExecutable + ".exe"; var logFile = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "/Unity/Editor/"; #elif UNITY_EDITOR_LINUX platformDir += "/l64"; resourcesPath = platformDir + "/CEFResources"; localesDir = resourcesPath + "/locales"; var subprocessFile = platformDir + "/" + SlaveExecutable; var logFile = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/unity3d/"; #elif UNITY_EDITOR_OSX platformDir += "/m64"; resourcesPath = platformDir + "/BrowserLib.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources"; localesDir = resourcesPath; //Chromium's base::mac::GetAppBundlePath will walk up the tree until it finds an ".app" folder and start //looking for pieces from there. That's why everything is hidden in a fake "BrowserLib.app" //folder that's not actually an app. var subprocessFile = platformDir + "/BrowserLib.app/Contents/Frameworks/" + SlaveExecutable + ".app/Contents/MacOS/" + SlaveExecutable; var logFile = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "/Library/Logs/Unity/"; #else //If you want to build your app without ZFBrowser on some platforms change this to an exception or //tweak the .asmdef #error ZFBrowser is not supported on this platform #endif return new CEFDirs() { resourcesPath = resourcesPath, binariesPath = platformDir, localesPath = localesDir, subprocessFile = subprocessFile, #if UNITY_2019_1_OR_NEWER logFile = logFile + "Editor-browser.log", logFileIsUnityLog = false, #else logFile = logFile + "Editor.log", #endif }; #elif UNITY_STANDALONE_WIN var resourcesPath = Application.dataPath + "/Plugins"; var logFileIsUnityLog = true; var logFile = Application.dataPath + "/output_log.txt"; #if UNITY_2017_2_OR_NEWER var appLowDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "Low/" + GetFolderName(Application.companyName) + "/" + GetFolderName(Application.productName); if (Directory.Exists(appLowDir)) { #if UNITY_2019_1_OR_NEWER logFile = appLowDir + "/Player-browser.log"; logFileIsUnityLog = false; #else logFile = appLowDir + "/output_log.txt"; #endif } #endif return new CEFDirs() { resourcesPath = resourcesPath, binariesPath = resourcesPath, localesPath = resourcesPath + "/locales", subprocessFile = resourcesPath + "/" + SlaveExecutable + ".exe", logFile = logFile, logFileIsUnityLog = logFileIsUnityLog, }; #elif UNITY_STANDALONE_LINUX var resourcesPath = Application.dataPath + "/Plugins"; #if !UNITY_2017_2_OR_NEWER // Unity 2017.1 and older //We can write to a log file of our choice, but that doesn't stop CEF from writing a copy of messages to stderr, //which Unity puts in the main log file anyway. (And at the start no less - I can't stop this form happening.) //We'll pass on having a separate log file. var logFile = "/dev/null"; var logFileIsUnityLog = false; #elif !UNITY_2019_1 // Unity 2017.2+, except 2019.1 //Newer versions of Unity don't copy stderr into the log file. (But they do copy stdout. :-/) var logFile = Environment.GetEnvironmentVariable("HOME"); var logFileIsUnityLog = false; if (string.IsNullOrEmpty(logFile)) logFile = "/dev/null"; else { logFile += "/.config/unity3d/" + GetFolderName(Application.companyName) + "/" + GetFolderName(Application.productName) + "/Player-browser.log"; logFileIsUnityLog = false; } #else //Unity 2019.1.0 //And here Unity just...I'm not sure what's going on. Their log location isn't consistent with their documentation. var logFile = Environment.GetEnvironmentVariable("HOME"); var logFileIsUnityLog = false; if (string.IsNullOrEmpty(logFile)) logFile = "/dev/null"; else { logFile += "/.config/unity3d/Editor/Player-browser.log"; logFileIsUnityLog = false; } #endif return new CEFDirs() { resourcesPath = resourcesPath, binariesPath = resourcesPath, localesPath = resourcesPath + "/locales", subprocessFile = resourcesPath + "/" + SlaveExecutable, logFile = logFile, logFileIsUnityLog = logFileIsUnityLog, }; #elif UNITY_STANDALONE_OSX return new CEFDirs() { resourcesPath = Application.dataPath + "/Frameworks/Chromium Embedded Framework.framework/Resources", binariesPath = Application.dataPath + "/Plugins", localesPath = Application.dataPath + "/Frameworks/Chromium Embedded Framework.framework/Resources", subprocessFile = Application.dataPath + "/Frameworks/ZFGameBrowser.app/Contents/MacOS/" + SlaveExecutable, #if UNITY_2019_1_OR_NEWER logFile = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "/Library/Logs/Unity/Player-browser.log", logFileIsUnityLog = false, #else logFile = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "/Library/Logs/Unity/Player.log", #endif }; #else #error Web textures are not supported on this platform #endif } } }