// // Copyright (C) 2014 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Keep this even on unsupported configurations. namespace GooglePlayGames.Editor { using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Xml; using UnityEditor; using UnityEngine; /// /// Utility class to perform various tasks in the editor. /// public static class GPGSUtil { /// Property key for project settings. public const string SERVICEIDKEY = "App.NearbdServiceId"; /// Property key for project settings. public const string APPIDKEY = "proj.AppId"; /// Property key for project settings. public const string CLASSDIRECTORYKEY = "proj.classDir"; /// Property key for project settings. public const string CLASSNAMEKEY = "proj.ConstantsClassName"; /// Property key for project settings. public const string WEBCLIENTIDKEY = "and.ClientId"; /// Property key for project settings. public const string ANDROIDRESOURCEKEY = "and.ResourceData"; /// Property key for project settings. public const string ANDROIDSETUPDONEKEY = "android.SetupDone"; /// Property key for project settings. public const string ANDROIDBUNDLEIDKEY = "and.BundleId"; /// Property key for plugin version. public const string PLUGINVERSIONKEY = "proj.pluginVersion"; /// Property key for nearby settings done. public const string NEARBYSETUPDONEKEY = "android.NearbySetupDone"; /// Property key for project settings. public const string LASTUPGRADEKEY = "lastUpgrade"; /// Constant for token replacement private const string SERVICEIDPLACEHOLDER = "__NEARBY_SERVICE_ID__"; private const string SERVICEID_ELEMENT_PLACEHOLDER = "__NEARBY_SERVICE_ELEMENT__"; private const string NEARBY_PERMISSIONS_PLACEHOLDER = "__NEARBY_PERMISSIONS__"; /// Constant for token replacement private const string APPIDPLACEHOLDER = "__APP_ID__"; /// Constant for token replacement private const string CLASSNAMEPLACEHOLDER = "__Class__"; /// Constant for token replacement private const string WEBCLIENTIDPLACEHOLDER = "__WEB_CLIENTID__"; /// Constant for token replacement private const string PLUGINVERSIONPLACEHOLDER = "__PLUGIN_VERSION__"; /// Constant for require google plus token replacement private const string REQUIREGOOGLEPLUSPLACEHOLDER = "__REQUIRE_GOOGLE_PLUS__"; /// Property key for project settings. private const string TOKENPERMISSIONKEY = "proj.tokenPermissions"; /// Constant for token replacement private const string NAMESPACESTARTPLACEHOLDER = "__NameSpaceStart__"; /// Constant for token replacement private const string NAMESPACEENDPLACEHOLDER = "__NameSpaceEnd__"; /// Constant for token replacement private const string CONSTANTSPLACEHOLDER = "__Constant_Properties__"; /// /// The game info file path. This is a generated file. /// private const string GameInfoPath = "Assets/GooglePlayGames/GameInfo.cs"; /// /// The manifest path. /// /// The Games SDK requires additional metadata in the AndroidManifest.xml /// file. private const string ManifestPath = "Assets/GooglePlayGames/Plugins/Android/GooglePlayGamesManifest.plugin/AndroidManifest.xml"; /// /// The map of replacements for filling in code templates. The /// key is the string that appears in the template as a placeholder, /// the value is the key into the GPGSProjectSettings. /// private static Dictionary replacements = new Dictionary() { // Put this element placeholder first, since it has embedded placeholder {SERVICEID_ELEMENT_PLACEHOLDER, SERVICEID_ELEMENT_PLACEHOLDER}, { SERVICEIDPLACEHOLDER, SERVICEIDKEY }, { APPIDPLACEHOLDER, APPIDKEY }, { CLASSNAMEPLACEHOLDER, CLASSNAMEKEY }, { WEBCLIENTIDPLACEHOLDER, WEBCLIENTIDKEY }, { PLUGINVERSIONPLACEHOLDER, PLUGINVERSIONKEY}, // Causes the placeholder to be replaced with overridden value at runtime. { NEARBY_PERMISSIONS_PLACEHOLDER, NEARBY_PERMISSIONS_PLACEHOLDER} }; /// /// Replaces / in file path to be the os specific separator. /// /// The path. /// Path with correct separators. public static string SlashesToPlatformSeparator(string path) { return path.Replace("/", System.IO.Path.DirectorySeparatorChar.ToString()); } /// /// Reads the file. /// /// The file contents. The slashes are corrected. /// File path. public static string ReadFile(string filePath) { filePath = SlashesToPlatformSeparator(filePath); if (!File.Exists(filePath)) { Alert("Plugin error: file not found: " + filePath); return null; } StreamReader sr = new StreamReader(filePath); string body = sr.ReadToEnd(); sr.Close(); return body; } /// /// Reads the editor template. /// /// The editor template contents. /// Name of the template in the editor directory. public static string ReadEditorTemplate(string name) { return ReadFile(SlashesToPlatformSeparator("Assets/GooglePlayGames/Editor/" + name + ".txt")); } /// /// Writes the file. /// /// File path - the slashes will be corrected. /// Body of the file to write. public static void WriteFile(string file, string body) { file = SlashesToPlatformSeparator(file); using (var wr = new StreamWriter(file, false)) { wr.Write(body); } } /// /// Validates the string to be a valid nearby service id. /// /// true, if like valid service identifier was looksed, false otherwise. /// string to test. public static bool LooksLikeValidServiceId(string s) { if (s.Length < 3) { return false; } foreach (char c in s) { if (!char.IsLetterOrDigit(c) && c != '.') { return false; } } return true; } /// /// Looks the like valid app identifier. /// /// true, if valid app identifier, false otherwise. /// the string to test. public static bool LooksLikeValidAppId(string s) { if (s.Length < 5) { return false; } foreach (char c in s) { if (c < '0' || c > '9') { return false; } } return true; } /// /// Looks the like valid client identifier. /// /// true, if valid client identifier, false otherwise. /// the string to test. public static bool LooksLikeValidClientId(string s) { return s.EndsWith(".googleusercontent.com"); } /// /// Looks the like a valid bundle identifier. /// /// true, if valid bundle identifier, false otherwise. /// the string to test. public static bool LooksLikeValidBundleId(string s) { return s.Length > 3; } /// /// Looks like a valid package. /// /// true, if valid package name, false otherwise. /// the string to test. public static bool LooksLikeValidPackageName(string s) { if (string.IsNullOrEmpty(s)) { throw new Exception("cannot be empty"); } string[] parts = s.Split(new char[] { '.' }); foreach (string p in parts) { char[] bytes = p.ToCharArray(); for (int i = 0; i < bytes.Length; i++) { if (i == 0 && !char.IsLetter(bytes[i])) { throw new Exception("each part must start with a letter"); } else if (char.IsWhiteSpace(bytes[i])) { throw new Exception("cannot contain spaces"); } else if (!char.IsLetterOrDigit(bytes[i]) && bytes[i] != '_') { throw new Exception("must be alphanumeric or _"); } } } return parts.Length >= 1; } /// /// Determines if is setup done. /// /// true if is setup done; otherwise, false. public static bool IsSetupDone() { bool doneSetup = true; #if UNITY_ANDROID doneSetup = GPGSProjectSettings.Instance.GetBool(ANDROIDSETUPDONEKEY, false); // check gameinfo if (File.Exists(GameInfoPath)) { string contents = ReadFile(GameInfoPath); if (contents.Contains(APPIDPLACEHOLDER)) { Debug.Log("GameInfo not initialized with AppId. " + "Run Window > Google Play Games > Setup > Android Setup..."); return false; } } else { Debug.Log("GameInfo.cs does not exist. Run Window > Google Play Games > Setup > Android Setup..."); return false; } #endif return doneSetup; } /// /// Makes legal identifier from string. /// Returns a legal C# identifier from the given string. The transformations are: /// - spaces => underscore _ /// - punctuation => empty string /// - leading numbers are prefixed with underscore. /// /// the id /// Key to convert to an identifier. public static string MakeIdentifier(string key) { string s; string retval = string.Empty; if (string.IsNullOrEmpty(key)) { return "_"; } s = key.Trim().Replace(' ', '_'); foreach (char c in s) { if (char.IsLetterOrDigit(c) || c == '_') { retval += c; } } return retval; } /// /// Displays an error dialog. /// /// the message public static void Alert(string s) { Alert(GPGSStrings.Error, s); } /// /// Displays a dialog with the given title and message. /// /// the title. /// the message. public static void Alert(string title, string message) { EditorUtility.DisplayDialog(title, message, GPGSStrings.Ok); } /// /// Gets the android sdk path. /// /// The android sdk path. public static string GetAndroidSdkPath() { string sdkPath = EditorPrefs.GetString("AndroidSdkRoot"); if (sdkPath != null && (sdkPath.EndsWith("/") || sdkPath.EndsWith("\\"))) { sdkPath = sdkPath.Substring(0, sdkPath.Length - 1); } return sdkPath; } /// /// Determines if the android sdk exists. /// /// true if android sdk exists; otherwise, false. public static bool HasAndroidSdk() { string sdkPath = GetAndroidSdkPath(); return sdkPath != null && sdkPath.Trim() != string.Empty && System.IO.Directory.Exists(sdkPath); } /// /// Gets the unity major version. /// /// The unity major version. public static int GetUnityMajorVersion() { #if UNITY_5 string majorVersion = Application.unityVersion.Split('.')[0]; int ver; if (!int.TryParse(majorVersion, out ver)) { ver = 0; } return ver; #elif UNITY_4_6 return 4; #else return 0; #endif } /// /// Checks for the android manifest file exsistance. /// /// true, if the file exists false otherwise. public static bool AndroidManifestExists() { string destFilename = GPGSUtil.SlashesToPlatformSeparator(ManifestPath); return File.Exists(destFilename); } /// /// Generates the android manifest. /// public static void GenerateAndroidManifest() { string destFilename = GPGSUtil.SlashesToPlatformSeparator(ManifestPath); // Generate AndroidManifest.xml string manifestBody = GPGSUtil.ReadEditorTemplate("template-AndroidManifest"); Dictionary overrideValues = new Dictionary(); if (!string.IsNullOrEmpty (GPGSProjectSettings.Instance.Get (SERVICEIDKEY))) { overrideValues [NEARBY_PERMISSIONS_PLACEHOLDER] = " \n" + " \n" + " \n" + " \n" + " \n" + " \n"; overrideValues [SERVICEID_ELEMENT_PLACEHOLDER] = " \n" + " \n"; } else { overrideValues [NEARBY_PERMISSIONS_PLACEHOLDER] = ""; overrideValues [SERVICEID_ELEMENT_PLACEHOLDER] = ""; } foreach (KeyValuePair ent in replacements) { string value = GPGSProjectSettings.Instance.Get(ent.Value, overrideValues); manifestBody = manifestBody.Replace(ent.Key, value); } GPGSUtil.WriteFile(destFilename, manifestBody); GPGSUtil.UpdateGameInfo(); } /// /// Writes the resource identifiers file. This file contains the /// resource ids copied (downloaded?) from the play game app console. /// /// Class directory. /// Class name. /// Resource keys. public static void WriteResourceIds(string classDirectory, string className, Hashtable resourceKeys) { string constantsValues = string.Empty; string[] parts = className.Split('.'); string dirName = classDirectory; if (string.IsNullOrEmpty(dirName)) { dirName = "Assets"; } string nameSpace = string.Empty; for (int i = 0; i < parts.Length - 1; i++) { dirName += "/" + parts[i]; if (nameSpace != string.Empty) { nameSpace += "."; } nameSpace += parts[i]; } EnsureDirExists(dirName); foreach (DictionaryEntry ent in resourceKeys) { string key = MakeIdentifier((string)ent.Key); constantsValues += " public const string " + key + " = \"" + ent.Value + "\"; // \n"; } string fileBody = GPGSUtil.ReadEditorTemplate("template-Constants"); if (nameSpace != string.Empty) { fileBody = fileBody.Replace( NAMESPACESTARTPLACEHOLDER, "namespace " + nameSpace + "\n{"); } else { fileBody = fileBody.Replace(NAMESPACESTARTPLACEHOLDER, string.Empty); } fileBody = fileBody.Replace(CLASSNAMEPLACEHOLDER, parts[parts.Length - 1]); fileBody = fileBody.Replace(CONSTANTSPLACEHOLDER, constantsValues); if (nameSpace != string.Empty) { fileBody = fileBody.Replace( NAMESPACEENDPLACEHOLDER, "}"); } else { fileBody = fileBody.Replace(NAMESPACEENDPLACEHOLDER, string.Empty); } WriteFile(Path.Combine(dirName, parts[parts.Length - 1] + ".cs"), fileBody); } /// /// Updates the game info file. This is a generated file containing the /// app and client ids. /// public static void UpdateGameInfo() { string fileBody = GPGSUtil.ReadEditorTemplate("template-GameInfo"); foreach (KeyValuePair ent in replacements) { string value = GPGSProjectSettings.Instance.Get(ent.Value); fileBody = fileBody.Replace(ent.Key, value); } GPGSUtil.WriteFile(GameInfoPath, fileBody); } /// /// Ensures the dir exists. /// /// Directory to check. public static void EnsureDirExists(string dir) { dir = SlashesToPlatformSeparator(dir); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } } /// /// Deletes the dir if exists. /// /// Directory to delete. public static void DeleteDirIfExists(string dir) { dir = SlashesToPlatformSeparator(dir); if (Directory.Exists(dir)) { Directory.Delete(dir, true); } } /// /// Gets the Google Play Services library version. This is only /// needed for Unity versions less than 5. /// /// The GPS version. /// Lib proj path. private static int GetGPSVersion(string libProjPath) { string versionFile = libProjPath + "/res/values/version.xml"; XmlTextReader reader = new XmlTextReader(new StreamReader(versionFile)); bool inResource = false; int version = -1; while (reader.Read()) { if (reader.Name == "resources") { inResource = true; } if (inResource && reader.Name == "integer") { if ("google_play_services_version".Equals( reader.GetAttribute("name"))) { reader.Read(); Debug.Log("Read version string: " + reader.Value); version = Convert.ToInt32(reader.Value); } } } reader.Close(); return version; } } }